forked from MapComplete/MapComplete
		
	More work on refactoring the changes handling
This commit is contained in:
		
							parent
							
								
									42391b4ff1
								
							
						
					
					
						commit
						b55f9a25c6
					
				
					 19 changed files with 181 additions and 105 deletions
				
			
		|  | @ -7,7 +7,7 @@ export default class ChangeToElementsActor { | |||
|             for (const change of changes) { | ||||
|                 const id = change.type + "/" + change.id; | ||||
|                 if (!allElements.has(id)) { | ||||
| continue; // Will be picked up later on
 | ||||
|                     continue; // Ignored as the geometryFixer will introduce this
 | ||||
|                 } | ||||
|                 const src = allElements.getEventSourceById(id) | ||||
| 
 | ||||
|  |  | |||
|  | @ -13,7 +13,7 @@ export default class SelectedFeatureHandler { | |||
|     private readonly _hash: UIEventSource<string>; | ||||
|     private readonly _selectedFeature: UIEventSource<any>; | ||||
| 
 | ||||
|     private static readonly _no_trigger_on = ["welcome","copyright","layers"] | ||||
|     private static readonly _no_trigger_on = ["welcome","copyright","layers","new"] | ||||
|     private readonly _osmApiSource: OsmApiFeatureSource; | ||||
|      | ||||
|     constructor(hash: UIEventSource<string>,  | ||||
|  | @ -60,7 +60,9 @@ export default class SelectedFeatureHandler { | |||
|         if(hash === undefined || SelectedFeatureHandler._no_trigger_on.indexOf(hash) >= 0){ | ||||
|             return; // No valid feature selected
 | ||||
|         } | ||||
|         // We should have a valid osm-ID and zoom to it
 | ||||
|         // We should have a valid osm-ID and zoom to it... But we wrap it in try-catch to be sure
 | ||||
|         try{ | ||||
|              | ||||
|         OsmObject.DownloadObject(hash).addCallbackAndRunD(element => { | ||||
|             const centerpoint = element.centerpoint(); | ||||
|             console.log("Zooming to location for select point: ", centerpoint) | ||||
|  | @ -68,6 +70,9 @@ export default class SelectedFeatureHandler { | |||
|             location.data.lon = centerpoint[1] | ||||
|             location.ping(); | ||||
|         }) | ||||
|         }catch(e){ | ||||
|             console.error("Could not download OSM-object with id", hash, " - probably a weird hash") | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     private downloadFeature(hash:  string){ | ||||
|  |  | |||
|  | @ -5,6 +5,7 @@ import {ChangeDescription} from "../Osm/Actions/ChangeDescription"; | |||
| import {Utils} from "../../Utils"; | ||||
| import {OsmNode, OsmRelation, OsmWay} from "../Osm/OsmObject"; | ||||
| 
 | ||||
| 
 | ||||
| /** | ||||
|  * Applies changes from 'Changes' onto a featureSource | ||||
|  */ | ||||
|  | @ -16,25 +17,41 @@ export default class ChangeApplicator implements FeatureSource { | |||
| 
 | ||||
|         this.name = "ChangesApplied(" + source.name + ")" | ||||
|         this.features = source.features | ||||
| 
 | ||||
|         const seenChanges = new Set<ChangeDescription>(); | ||||
|         const self = this; | ||||
|         let runningUpdate = false; | ||||
|         source.features.addCallbackAndRunD(features => { | ||||
|             if(runningUpdate){ | ||||
|                 return; // No need to ping again
 | ||||
|             } | ||||
|             ChangeApplicator.ApplyChanges(features, changes.pendingChanges.data) | ||||
|             seenChanges.clear() | ||||
|         }) | ||||
| 
 | ||||
|         changes.pendingChanges.addCallbackAndRunD(changes => { | ||||
|             ChangeApplicator.ApplyChanges(source.features.data, changes) | ||||
|             runningUpdate = true; | ||||
|             changes = changes.filter(ch => !seenChanges.has(ch)) | ||||
|             changes.forEach(c => seenChanges.add(c)) | ||||
|             console.log("Called back", changes) | ||||
|             ChangeApplicator.ApplyChanges(self.features.data, changes) | ||||
|             source.features.ping() | ||||
|             runningUpdate = false; | ||||
|         }) | ||||
| 
 | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     private static ApplyChanges(features: { feature: any, freshness: Date }[], cs: ChangeDescription[]) { | ||||
|         if (cs.length === 0 || features === undefined) { | ||||
|             return features; | ||||
|     /** | ||||
|      * Returns true if the geometry is changed and the source should be pinged | ||||
|      */ | ||||
|     private static ApplyChanges(features: {feature: any, freshness: Date}[], cs: ChangeDescription[]): boolean { | ||||
|         if (cs.length === 0 || features === undefined   ) { | ||||
|             return ; | ||||
|         } | ||||
| 
 | ||||
|         console.log("Applying changes ", this.name, cs) | ||||
|         let geometryChanged = false; | ||||
|         const changesPerId: Map<string, ChangeDescription[]> = new Map<string, ChangeDescription[]>() | ||||
|         for (const c of cs) { | ||||
|             const id = c.type + "/" + c.id | ||||
|  | @ -52,6 +69,8 @@ export default class ChangeApplicator implements FeatureSource { | |||
|                 feature: feature, | ||||
|                 freshness: now | ||||
|             }) | ||||
|             console.log("Added a new feature: ", feature) | ||||
|             geometryChanged = true; | ||||
|         } | ||||
| 
 | ||||
|         // First, create the new features - they have a negative ID
 | ||||
|  | @ -62,6 +81,10 @@ export default class ChangeApplicator implements FeatureSource { | |||
|                     return; // Nothing to do here, already created
 | ||||
|                 } | ||||
|                  | ||||
|                 if(change.changes === undefined){ | ||||
|                     // An update to the object - not the actual created
 | ||||
|                     return; | ||||
|                 } | ||||
| 
 | ||||
|                 try { | ||||
| 
 | ||||
|  | @ -93,8 +116,8 @@ export default class ChangeApplicator implements FeatureSource { | |||
| 
 | ||||
| 
 | ||||
|         for (const feature of features) { | ||||
|             const id = feature.feature.properties.id; | ||||
|             const f = feature.feature; | ||||
|             const id = f.properties.id; | ||||
|             if (!changesPerId.has(id)) { | ||||
|                 continue; | ||||
|             } | ||||
|  | @ -118,11 +141,12 @@ export default class ChangeApplicator implements FeatureSource { | |||
| 
 | ||||
|                 // Apply other changes to the object
 | ||||
|                 if (change.changes !== undefined) { | ||||
|                     geometryChanged = true; | ||||
|                     switch (change.type) { | ||||
|                         case "node": | ||||
|                             // @ts-ignore
 | ||||
|                             const coor: { lat, lon } = change.changes; | ||||
|                             f.geometry.coordinates = [[coor.lon, coor.lat]] | ||||
|                             f.geometry.coordinates = [coor.lon, coor.lat] | ||||
|                             break; | ||||
|                         case "way": | ||||
|                             f.geometry.coordinates = change.changes["locations"] | ||||
|  | @ -134,5 +158,6 @@ export default class ChangeApplicator implements FeatureSource { | |||
|                 } | ||||
|             } | ||||
|         } | ||||
|         return geometryChanged | ||||
|     } | ||||
| } | ||||
|  | @ -15,15 +15,19 @@ export default class OsmApiFeatureSource implements FeatureSource { | |||
| 
 | ||||
| 
 | ||||
|     public load(id: string) { | ||||
|         if(id.indexOf("-") >= 0){ | ||||
|         if (id.indexOf("-") >= 0) { | ||||
|             // Newly added point - not yet in OSM
 | ||||
|             return; | ||||
|         } | ||||
|         console.debug("Downloading", id, "from the OSM-API") | ||||
|         OsmObject.DownloadObject(id).addCallbackAndRunD(element => { | ||||
|             try { | ||||
|                 const geojson = element.asGeoJson(); | ||||
|                 geojson.id = geojson.properties.id; | ||||
|                 this.features.setData([{feature: geojson, freshness: element.timestamp}]) | ||||
|             } catch (e) { | ||||
|                 console.error(e) | ||||
|             } | ||||
|         }) | ||||
|     } | ||||
| 
 | ||||
|  | @ -58,7 +62,7 @@ export default class OsmApiFeatureSource implements FeatureSource { | |||
|             const bounds = Utils.tile_bounds(z, x, y); | ||||
|             console.log("Loading OSM data tile", z, x, y, " with bounds", bounds) | ||||
|             OsmObject.LoadArea(bounds, objects => { | ||||
|                 const keptGeoJson: {feature:any, freshness: Date}[] = [] | ||||
|                 const keptGeoJson: { feature: any, freshness: Date }[] = [] | ||||
|                 // Which layer does the object match?
 | ||||
|                 for (const object of objects) { | ||||
| 
 | ||||
|  |  | |||
|  | @ -37,7 +37,7 @@ export default class ChangeTagAction extends OsmChangeAction { | |||
|         return {k: key.trim(), v: value.trim()}; | ||||
|     } | ||||
| 
 | ||||
|     Perform(changes: Changes): ChangeDescription [] { | ||||
|     CreateChangeDescriptions(changes: Changes): ChangeDescription [] { | ||||
|         const changedTags: { k: string, v: string }[] = this._tagsFilter.asChange(this._currentTags).map(ChangeTagAction.checkChange) | ||||
|         const typeId = this._elementId.split("/") | ||||
|         const type = typeId[0] | ||||
|  |  | |||
|  | @ -4,23 +4,27 @@ import {Changes} from "../Changes"; | |||
| import {ChangeDescription} from "./ChangeDescription"; | ||||
| import {And} from "../../Tags/And"; | ||||
| 
 | ||||
| export default class CreateNewNodeAction implements OsmChangeAction { | ||||
| export default class CreateNewNodeAction extends OsmChangeAction { | ||||
| 
 | ||||
|     private readonly _basicTags: Tag[]; | ||||
|     private readonly _lat: number; | ||||
|     private readonly _lon: number; | ||||
| 
 | ||||
|     public newElementId : string = undefined | ||||
|      | ||||
|     constructor(basicTags: Tag[], lat: number, lon: number) { | ||||
|         super() | ||||
|         this._basicTags = basicTags; | ||||
|         this._lat = lat; | ||||
|         this._lon = lon; | ||||
|     } | ||||
| 
 | ||||
|     Perform(changes: Changes): ChangeDescription[] { | ||||
|     CreateChangeDescriptions(changes: Changes): ChangeDescription[] { | ||||
|         const id = changes.getNewID() | ||||
|         const properties = { | ||||
|             id: "node/" + id | ||||
|         } | ||||
|         this.newElementId = "node/"+id | ||||
|         for (const kv of this._basicTags) { | ||||
|             if (typeof kv.value !== "string") { | ||||
|                 throw "Invalid value: don't use a regex in a preset" | ||||
|  |  | |||
|  | @ -7,10 +7,17 @@ import {ChangeDescription} from "./ChangeDescription"; | |||
| 
 | ||||
| export default abstract class OsmChangeAction { | ||||
| 
 | ||||
|     private isUsed = false | ||||
| 
 | ||||
|     public Perform(changes: Changes) { | ||||
|         if (this.isUsed) { | ||||
|             throw "This ChangeAction is already used: " + this.constructor.name | ||||
|         } | ||||
|         this.isUsed = true; | ||||
|         return this.CreateChangeDescriptions(changes) | ||||
|     } | ||||
| 
 | ||||
|     public abstract Perform(changes: Changes): ChangeDescription[] | ||||
|      | ||||
|     protected abstract CreateChangeDescriptions(changes: Changes): ChangeDescription[] | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
|  | @ -12,7 +12,7 @@ export default class RelationSplitlHandler extends OsmChangeAction{ | |||
|         super() | ||||
|     } | ||||
| 
 | ||||
|     Perform(changes: Changes): ChangeDescription[] { | ||||
|     CreateChangeDescriptions(changes: Changes): ChangeDescription[] { | ||||
|         return []; | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -42,7 +42,7 @@ export default class SplitAction extends OsmChangeAction { | |||
|         return wayParts.filter(wp => wp.length > 0) | ||||
|     } | ||||
| 
 | ||||
|     Perform(changes: Changes): ChangeDescription[] { | ||||
|     CreateChangeDescriptions(changes: Changes): ChangeDescription[] { | ||||
|         const splitPoints = this._splitPoints | ||||
|         // We mark the new split points with a new id
 | ||||
|         console.log(splitPoints) | ||||
|  | @ -72,7 +72,6 @@ export default class SplitAction extends OsmChangeAction { | |||
| 
 | ||||
|         // Next up is creating actual parts from this
 | ||||
|         const wayParts: SplitInfo[][] = SplitAction.SegmentSplitInfo(splitInfo); | ||||
| console.log("WayParts", wayParts, "by", splitInfo) | ||||
|         // Allright! At this point, we have our new ways!
 | ||||
|         // Which one is the longest of them (and can keep the id)?
 | ||||
| 
 | ||||
|  | @ -144,7 +143,7 @@ console.log("WayParts", wayParts, "by", splitInfo) | |||
| 
 | ||||
|         // At last, we still have to check that we aren't part of a relation...
 | ||||
|         // At least, the order of the ways is identical, so we can keep the same roles
 | ||||
|         changeDescription.push(...new RelationSplitlHandler(partOf, newWayIds, originalNodes).Perform(changes)) | ||||
|         changeDescription.push(...new RelationSplitlHandler(partOf, newWayIds, originalNodes).CreateChangeDescriptions(changes)) | ||||
| 
 | ||||
|         // And we have our objects!
 | ||||
|         // Time to upload
 | ||||
|  |  | |||
|  | @ -4,7 +4,6 @@ import {UIEventSource} from "../UIEventSource"; | |||
| import Constants from "../../Models/Constants"; | ||||
| import OsmChangeAction from "./Actions/OsmChangeAction"; | ||||
| import {ChangeDescription} from "./Actions/ChangeDescription"; | ||||
| import {LocalStorageSource} from "../Web/LocalStorageSource"; | ||||
| import {Utils} from "../../Utils"; | ||||
| 
 | ||||
| /** | ||||
|  | @ -24,24 +23,22 @@ export class Changes { | |||
|     public readonly pendingChanges = new UIEventSource<ChangeDescription[]>([]) //  LocalStorageSource.GetParsed<ChangeDescription[]>("pending-changes", [])
 | ||||
|     private readonly isUploading = new UIEventSource(false); | ||||
|      | ||||
|     private readonly previouslyCreated : OsmObject[] = [] | ||||
| 
 | ||||
|     constructor() { | ||||
|         this.isUploading.addCallbackAndRun(u => { | ||||
|             if (u) { | ||||
|                 console.trace("Uploading set!") | ||||
|             } | ||||
|         }) | ||||
|         | ||||
|     } | ||||
| 
 | ||||
|     public static createChangesetFor(csId: string, | ||||
|     private static createChangesetFor(csId: string, | ||||
|                                      allChanges: { | ||||
|                                          modifiedObjects?: OsmObject[], | ||||
|                                          newElements?: OsmObject[], | ||||
|                                          deletedElements?: OsmObject[] | ||||
|                                          modifiedObjects: OsmObject[], | ||||
|                                          newObjects: OsmObject[], | ||||
|                                          deletedObjects: OsmObject[] | ||||
|                                      }): string { | ||||
| 
 | ||||
|         const changedElements = allChanges.modifiedObjects ?? [] | ||||
|         const newElements = allChanges.newElements ?? [] | ||||
|         const deletedElements = allChanges.deletedElements ?? [] | ||||
|         const newElements = allChanges.newObjects ?? [] | ||||
|         const deletedElements = allChanges.deletedObjects ?? [] | ||||
| 
 | ||||
|         let changes = `<osmChange version='0.6' generator='Mapcomplete ${Constants.vNumber}'>`; | ||||
|         if (newElements.length > 0) { | ||||
|  | @ -73,7 +70,7 @@ export class Changes { | |||
|             .map(c => c.type + "/" + c.id)) | ||||
|     } | ||||
| 
 | ||||
|     private static CreateChangesetObjects(changes: ChangeDescription[], downloadedOsmObjects: OsmObject[]): { | ||||
|     private CreateChangesetObjects(changes: ChangeDescription[], downloadedOsmObjects: OsmObject[]): { | ||||
|         newObjects: OsmObject[], | ||||
|         modifiedObjects: OsmObject[] | ||||
|         deletedObjects: OsmObject[] | ||||
|  | @ -87,12 +84,21 @@ export class Changes { | |||
|             states.set(o.type + "/" + o.id, "unchanged") | ||||
|         } | ||||
| 
 | ||||
|         for (const o of this.previouslyCreated) { | ||||
|             objects.set(o.type + "/" + o.id, o)  | ||||
|             states.set(o.type + "/" + o.id, "unchanged") | ||||
|         } | ||||
| 
 | ||||
|         let changed = false; | ||||
|         for (const change of changes) { | ||||
|             const id = change.type + "/" + change.id | ||||
|             if (!objects.has(id)) { | ||||
|                 if(change.id >= 0){ | ||||
|                     throw "Did not get an object that should be known: "+id | ||||
|                 } | ||||
|                 // This is a new object that should be created
 | ||||
|                 states.set(id, "created") | ||||
|                 console.log("Creating object for changeDescription", change) | ||||
|                 let osmObj: OsmObject = undefined; | ||||
|                 switch (change.type) { | ||||
|                     case "node": | ||||
|  | @ -116,6 +122,7 @@ export class Changes { | |||
|                     throw "Hmm? This is a bug" | ||||
|                 } | ||||
|                 objects.set(id, osmObj) | ||||
|                 this.previouslyCreated.push(osmObj) | ||||
|             } | ||||
| 
 | ||||
|             const state = states.get(id) | ||||
|  | @ -195,8 +202,8 @@ export class Changes { | |||
|             newObjects: [], | ||||
|             modifiedObjects: [], | ||||
|             deletedObjects: [] | ||||
| 
 | ||||
|         } | ||||
|          | ||||
|         objects.forEach((v, id) => { | ||||
| 
 | ||||
|             const state = states.get(id) | ||||
|  | @ -228,20 +235,18 @@ export class Changes { | |||
|      */ | ||||
|     public flushChanges(flushreason: string = undefined) { | ||||
|         if (this.pendingChanges.data.length === 0) { | ||||
|             console.log("No pending changes") | ||||
|             return; | ||||
|         } | ||||
|         if (flushreason !== undefined) { | ||||
|             console.log(flushreason) | ||||
|         } | ||||
|          | ||||
|         if (this.isUploading.data) { | ||||
|             console.log("Is uploading... Abort") | ||||
|             console.log("Is already uploading... Abort") | ||||
|             return; | ||||
|         } | ||||
|          | ||||
|        | ||||
|         this.isUploading.setData(true) | ||||
|         | ||||
|         console.log("Beginning upload..."); | ||||
|         console.log("Beginning upload... "+flushreason ?? ""); | ||||
|         // At last, we build the changeset and upload
 | ||||
|         const self = this; | ||||
|         const pending = self.pendingChanges.data; | ||||
|  | @ -249,8 +254,12 @@ export class Changes { | |||
|         console.log("Needed ids", neededIds) | ||||
|         OsmObject.DownloadAll(neededIds, true).addCallbackAndRunD(osmObjects => { | ||||
|             console.log("Got the fresh objects!", osmObjects, "pending: ", pending) | ||||
|             const changes = Changes.CreateChangesetObjects(pending, osmObjects) | ||||
|             console.log("Changes", changes) | ||||
|             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") | ||||
|                 this.pendingChanges.setData([]) | ||||
|  | @ -262,11 +271,8 @@ export class Changes { | |||
|             State.state.osmConnection.UploadChangeset( | ||||
|                 State.state.layoutToUse.data, | ||||
|                 State.state.allElements, | ||||
|                 (csId) => { | ||||
|                     return Changes.createChangesetFor(csId, changes); | ||||
|                 }, | ||||
|                 (csId) => Changes.createChangesetFor(csId, changes), | ||||
|                 () => { | ||||
|                     // When done
 | ||||
|                     console.log("Upload successfull!") | ||||
|                     self.pendingChanges.setData([]); | ||||
|                     self.isUploading.setData(false) | ||||
|  |  | |||
|  | @ -23,7 +23,7 @@ export abstract class OsmObject { | |||
|         this.id = id; | ||||
|         this.type = type; | ||||
|         this.tags = { | ||||
|             id: id | ||||
|             id: `${this.type}/${id}` | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  | @ -52,6 +52,9 @@ export abstract class OsmObject { | |||
|         const splitted = id.split("/"); | ||||
|         const type = splitted[0]; | ||||
|         const idN = Number(splitted[1]); | ||||
|         if(idN <0){ | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         OsmObject.objectCache.set(id, src); | ||||
|         const newContinuation = (element: OsmObject) => { | ||||
|  | @ -69,7 +72,7 @@ export abstract class OsmObject { | |||
|                 new OsmRelation(idN).Download(newContinuation); | ||||
|                 break; | ||||
|             default: | ||||
|                 throw "Invalid road type:" + type; | ||||
|                 throw "Invalid object type:" + type + id; | ||||
| 
 | ||||
|         } | ||||
|         return src; | ||||
|  | @ -105,7 +108,7 @@ export abstract class OsmObject { | |||
|         if (OsmObject.referencingRelationsCache.has(id)) { | ||||
|             return OsmObject.referencingRelationsCache.get(id); | ||||
|         } | ||||
|         const relsSrc = new UIEventSource<OsmRelation[]>([]) | ||||
|         const relsSrc = new UIEventSource<OsmRelation[]>(undefined) | ||||
|         OsmObject.referencingRelationsCache.set(id, relsSrc); | ||||
|         Utils.downloadJson(`${OsmObject.backendURL}api/0.6/${id}/relations`) | ||||
|             .then(data => { | ||||
|  |  | |||
|  | @ -6,7 +6,7 @@ export class UIEventSource<T> { | |||
|     public data: T; | ||||
|     public trace: boolean; | ||||
|     private readonly tag: string; | ||||
|     private _callbacks = []; | ||||
|     private _callbacks: ((t: T) => (boolean | void | any)) [] = []; | ||||
| 
 | ||||
|     constructor(data: T, tag: string = "") { | ||||
|         this.tag = tag; | ||||
|  | @ -63,7 +63,13 @@ export class UIEventSource<T> { | |||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     public addCallback(callback: ((latestData: T) => void)): UIEventSource<T> { | ||||
|     /** | ||||
|      * Adds a callback | ||||
|      * | ||||
|      * If the result of the callback is 'true', the callback is considered finished and will be removed again | ||||
|      * @param callback | ||||
|      */ | ||||
|     public addCallback(callback: ((latestData: T) => (boolean | void | any))): UIEventSource<T> { | ||||
|         if (callback === console.log) { | ||||
|             // This ^^^ actually works!
 | ||||
|             throw "Don't add console.log directly as a callback - you'll won't be able to find it afterwards. Wrap it in a lambda instead." | ||||
|  | @ -90,8 +96,21 @@ export class UIEventSource<T> { | |||
|     } | ||||
| 
 | ||||
|     public ping(): void { | ||||
|         let toDelete = undefined | ||||
|         for (const callback of this._callbacks) { | ||||
|             callback(this.data); | ||||
|             if (callback(this.data) === true) { | ||||
|                 // This callback wants to be deleted
 | ||||
|                 if (toDelete === undefined) { | ||||
|                     toDelete = [callback] | ||||
|                 } else { | ||||
|                     toDelete.push(callback) | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         if (toDelete !== undefined) { | ||||
|             for (const toDeleteElement of toDelete) { | ||||
|                 this._callbacks.splice(this._callbacks.indexOf(toDeleteElement), 1) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										18
									
								
								State.ts
									
										
									
									
									
								
							
							
						
						
									
										18
									
								
								State.ts
									
										
									
									
									
								
							|  | @ -20,7 +20,7 @@ import PendingChangesUploader from "./Logic/Actors/PendingChangesUploader"; | |||
| import {Relation} from "./Logic/Osm/ExtractRelations"; | ||||
| import OsmApiFeatureSource from "./Logic/FeatureSource/OsmApiFeatureSource"; | ||||
| import ChangeToElementsActor from "./Logic/Actors/ChangeToElementsActor"; | ||||
| import FeaturePipeline from "./Logic/FeatureSource/FeaturePipeline"; | ||||
| import FeatureSource from "./Logic/FeatureSource/FeatureSource"; | ||||
| 
 | ||||
| /** | ||||
|  * Contains the global state: a bunch of UI-event sources | ||||
|  | @ -32,7 +32,7 @@ export default class State { | |||
|     public static state: State; | ||||
| 
 | ||||
| 
 | ||||
|     public readonly layoutToUse = new UIEventSource<LayoutConfig>(undefined); | ||||
|     public readonly layoutToUse = new UIEventSource<LayoutConfig>(undefined, "layoutToUse"); | ||||
| 
 | ||||
|     /** | ||||
|      The mapping from id -> UIEventSource<properties> | ||||
|  | @ -45,7 +45,7 @@ export default class State { | |||
|     /** | ||||
|      The leaflet instance of the big basemap | ||||
|      */ | ||||
|     public leafletMap = new UIEventSource<L.Map>(undefined); | ||||
|     public leafletMap = new UIEventSource<L.Map>(undefined, "leafletmap"); | ||||
|     /** | ||||
|      * Background layer id | ||||
|      */ | ||||
|  | @ -70,7 +70,7 @@ export default class State { | |||
|     }[]> = new UIEventSource<{ | ||||
|         readonly isDisplayed: UIEventSource<boolean>, | ||||
|         readonly  layerDef: LayerConfig; | ||||
|     }[]>([]) | ||||
|     }[]>([], "filteredLayers") | ||||
| 
 | ||||
| 
 | ||||
|     /** | ||||
|  | @ -101,13 +101,13 @@ export default class State { | |||
|     public readonly featureSwitchFakeUser: UIEventSource<boolean>; | ||||
| 
 | ||||
| 
 | ||||
|     public readonly featurePipeline: FeaturePipeline; | ||||
|     public featurePipeline: FeatureSource; | ||||
| 
 | ||||
| 
 | ||||
|     /** | ||||
|      * The map location: currently centered lat, lon and zoom | ||||
|      */ | ||||
|     public readonly locationControl = new UIEventSource<Loc>(undefined); | ||||
|     public readonly locationControl = new UIEventSource<Loc>(undefined, "locationControl"); | ||||
|     public backgroundLayer; | ||||
|     public readonly backgroundLayerId: UIEventSource<string>; | ||||
| 
 | ||||
|  | @ -149,11 +149,13 @@ export default class State { | |||
|                 .syncWith(LocalStorageSource.Get("lon"))); | ||||
| 
 | ||||
| 
 | ||||
|             this.locationControl = new UIEventSource<Loc>({ | ||||
|             this.locationControl.setData({ | ||||
|                 zoom: Utils.asFloat(zoom.data), | ||||
|                 lat: Utils.asFloat(lat.data), | ||||
|                 lon: Utils.asFloat(lon.data), | ||||
|             }).addCallback((latlonz) => { | ||||
|             }) | ||||
|             this.locationControl.addCallback((latlonz) => { | ||||
|                 // Sync th location controls
 | ||||
|                 zoom.setData(latlonz.zoom); | ||||
|                 lat.setData(latlonz.lat); | ||||
|                 lon.setData(latlonz.lon); | ||||
|  |  | |||
|  | @ -21,6 +21,7 @@ import {InputElement} from "../Input/InputElement"; | |||
| import Loc from "../../Models/Loc"; | ||||
| import AvailableBaseLayers from "../../Logic/Actors/AvailableBaseLayers"; | ||||
| import CreateNewNodeAction from "../../Logic/Osm/Actions/CreateNewNodeAction"; | ||||
| import Hash from "../../Logic/Web/Hash"; | ||||
| 
 | ||||
| /* | ||||
| * The SimpleAddUI is a single panel, which can have multiple states: | ||||
|  | @ -71,10 +72,11 @@ export default class SimpleAddUI extends Toggle { | |||
|                     } | ||||
|                     return SimpleAddUI.CreateConfirmButton(preset, | ||||
|                         (tags, location) => { | ||||
|                             let changes = | ||||
|                                 State.state.changes.applyAction(new CreateNewNodeAction(tags, location.lat, location.lon)) | ||||
|                             State.state.selectedElement.setData(changes.newFeatures[0]); | ||||
|                         const newElementAction = new CreateNewNodeAction(tags, location.lat, location.lon) | ||||
|                             State.state.changes.applyAction(newElementAction) | ||||
|                             selectedPreset.setData(undefined) | ||||
|                             isShown.setData(false) | ||||
|                             Hash.hash.setData(newElementAction.newElementId) | ||||
|                         }, () => { | ||||
|                             selectedPreset.setData(undefined) | ||||
|                         }) | ||||
|  | @ -121,13 +123,13 @@ export default class SimpleAddUI extends Toggle { | |||
|             }); | ||||
| 
 | ||||
|             let backgroundLayer = undefined; | ||||
|             if(preset.preciseInput.preferredBackground){ | ||||
|                backgroundLayer= AvailableBaseLayers.SelectBestLayerAccordingTo(locationSrc, new UIEventSource<string | string[]>(preset.preciseInput.preferredBackground)) | ||||
|             if (preset.preciseInput.preferredBackground) { | ||||
|                 backgroundLayer = AvailableBaseLayers.SelectBestLayerAccordingTo(locationSrc, new UIEventSource<string | string[]>(preset.preciseInput.preferredBackground)) | ||||
|             } | ||||
| 
 | ||||
|             preciseInput = new LocationInput({ | ||||
|                 mapBackground: backgroundLayer, | ||||
|                 centerLocation:locationSrc | ||||
|                 centerLocation: locationSrc | ||||
| 
 | ||||
|             }) | ||||
|             preciseInput.SetClass("h-32 rounded-xl overflow-hidden border border-gray").SetStyle("height: 12rem;") | ||||
|  | @ -239,7 +241,7 @@ export default class SimpleAddUI extends Toggle { | |||
|             for (const preset of presets) { | ||||
| 
 | ||||
|                 const tags = TagUtils.KVtoProperties(preset.tags ?? []); | ||||
|                 let icon:() => BaseUIElement = () => layer.layerDef.GenerateLeafletStyle(new UIEventSource<any>(tags), false).icon.html | ||||
|                 let icon: () => BaseUIElement = () => layer.layerDef.GenerateLeafletStyle(new UIEventSource<any>(tags), false).icon.html | ||||
|                     .SetClass("w-12 h-12 block relative"); | ||||
|                 const presetInfo: PresetInfo = { | ||||
|                     tags: preset.tags, | ||||
|  |  | |||
|  | @ -92,18 +92,24 @@ export default class SplitRoadWizard extends Toggle { | |||
|         // Save button
 | ||||
|         const saveButton = new Button(t.split.Clone(), () => { | ||||
|             hasBeenSplit.setData(true) | ||||
|             OsmObject.DownloadObject(id).addCallbackAndRunD(way => { | ||||
|                     OsmObject.DownloadReferencingRelations(id).addCallbackAndRunD( | ||||
|                         partOf => { | ||||
|             const way = OsmObject.DownloadObject(id) | ||||
|             const partOfSrc = OsmObject.DownloadReferencingRelations(id); | ||||
|             let hasRun = false | ||||
|             way.map(way => { | ||||
|                 const partOf = partOfSrc.data | ||||
|                 if(way === undefined || partOf === undefined){ | ||||
|                     return; | ||||
|                 } | ||||
|                 if(hasRun){ | ||||
|                     return | ||||
|                 } | ||||
|                 hasRun = true | ||||
|                 const splitAction = new SplitAction( | ||||
|                     <OsmWay>way, way.asGeoJson(), partOf, splitPoints.data.map(ff => ff.feature) | ||||
|                 ) | ||||
|                 State.state.changes.applyAction(splitAction) | ||||
|                         } | ||||
|                     ) | ||||
|                  | ||||
|                 } | ||||
|             ) | ||||
|             }, [partOfSrc]) | ||||
| 
 | ||||
| 
 | ||||
|         }); | ||||
|  | @ -123,7 +129,7 @@ export default class SplitRoadWizard extends Toggle { | |||
| 
 | ||||
|         const splitTitle = new Title(t.splitTitle); | ||||
| 
 | ||||
|         const mapView = new Combine([splitTitle, miniMap, new Combine([cancelButton, saveToggle])]); | ||||
|         const mapView = new Combine([splitTitle, miniMap, new Combine([cancelButton, saveToggle]).SetClass("flex flex-row")]); | ||||
|         mapView.SetClass("question") | ||||
|         const confirm = new Toggle(mapView, splitToggle, splitClicked); | ||||
|         super(t.hasBeenSplit.Clone(), confirm, hasBeenSplit) | ||||
|  |  | |||
|  | @ -61,7 +61,6 @@ export default class ShowDataLayer { | |||
|             } | ||||
| 
 | ||||
|             const allFeats = features.data.map(ff => ff.feature); | ||||
|             console.log("Rendering ",allFeats, "features at layer ", name) | ||||
|             geoLayer = self.CreateGeojsonLayer(); | ||||
|             for (const feat of allFeats) { | ||||
|                 if (feat === undefined) { | ||||
|  | @ -87,6 +86,7 @@ export default class ShowDataLayer { | |||
|                     console.error(e) | ||||
|                 } | ||||
|             } | ||||
|             State.state.selectedElement.ping() | ||||
|         } | ||||
| 
 | ||||
|         features.addCallback(() => update()); | ||||
|  |  | |||
|  | @ -19,7 +19,6 @@ export class SubstitutedTranslation extends VariableUiElement { | |||
|         const extraMappings: SpecialVisualization[] = []; | ||||
| 
 | ||||
|         mapping?.forEach((value, key) => { | ||||
|             console.log("KV:", key, value) | ||||
|             extraMappings.push( | ||||
|                 { | ||||
|                     funcName: key, | ||||
|  | @ -73,11 +72,6 @@ export class SubstitutedTranslation extends VariableUiElement { | |||
|         } | ||||
|     }[] { | ||||
| 
 | ||||
|         if (extraMappings.length > 0) { | ||||
| 
 | ||||
|             console.log("Extra mappings are", extraMappings) | ||||
|         } | ||||
| 
 | ||||
|         for (const knownSpecial of SpecialVisualizations.specialVisualizations.concat(extraMappings)) { | ||||
| 
 | ||||
|             // Note: the '.*?' in the regex reads as 'any character, but in a non-greedy way'
 | ||||
|  |  | |||
|  | @ -109,9 +109,9 @@ export class Translation extends BaseUIElement { | |||
|                     // @ts-ignore
 | ||||
|                     const date: Date = el; | ||||
|                     rtext = date.toLocaleString(); | ||||
|                 } else if (el.ConstructElement() === undefined) { | ||||
|                     console.error("InnerREnder is not defined", el); | ||||
|                     throw "Hmmm, el.InnerRender is not defined?" | ||||
|                 } else if (el.ConstructElement === undefined) { | ||||
|                     console.error("ConstructElement is not defined", el); | ||||
|                     throw "ConstructElement is not defined, you are working with a "+(typeof el)+":"+(el.constructor.name) | ||||
|                 } else { | ||||
|                     Translation.forcedLanguage = lang; // This is a very dirty hack - it'll bite me one day
 | ||||
|                     rtext = el.ConstructElement().innerHTML; | ||||
|  |  | |||
|  | @ -33,7 +33,7 @@ | |||
|   "startZoom": 14, | ||||
|   "startLon": 3.2228, | ||||
|   "maintainer": "MapComplete", | ||||
|   "widenfactor": 0.05, | ||||
|   "widenfactor": 0.01, | ||||
|   "roamingRenderings": [ | ||||
|     { | ||||
|       "question": { | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue