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
|
||||
|
@ -61,7 +80,11 @@ export default class ChangeApplicator implements FeatureSource {
|
|||
if (change.id >= 0) {
|
||||
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 => {
|
||||
const geojson = element.asGeoJson();
|
||||
geojson.id = geojson.properties.id;
|
||||
this.features.setData([{feature: geojson, freshness: element.timestamp}])
|
||||
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) {
|
||||
|
||||
|
@ -69,7 +73,7 @@ export default class OsmApiFeatureSource implements FeatureSource {
|
|||
if (doesMatch) {
|
||||
const geoJson = object.asGeoJson();
|
||||
geoJson._matching_layer_id = layer.id
|
||||
keptGeoJson.push({feature: geoJson, freshness: object.timestamp})
|
||||
keptGeoJson.push({feature: geoJson, freshness: object.timestamp})
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
protected abstract CreateChangeDescriptions(changes: Changes): ChangeDescription[]
|
||||
|
||||
|
||||
public abstract Perform(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";
|
||||
|
||||
/**
|
||||
|
@ -23,25 +22,23 @@ 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;
|
||||
|
@ -31,7 +31,7 @@ export class UIEventSource<T> {
|
|||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
|
||||
public static flatten<X>(source: UIEventSource<UIEventSource<X>>, possibleSources: UIEventSource<any>[]): UIEventSource<X> {
|
||||
const sink = new UIEventSource<X>(source.data?.data);
|
||||
|
||||
|
@ -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)
|
||||
})
|
||||
|
@ -119,16 +121,16 @@ export default class SimpleAddUI extends Toggle {
|
|||
lon: location.data.lon,
|
||||
zoom: 19
|
||||
});
|
||||
|
||||
|
||||
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;")
|
||||
}
|
||||
|
@ -143,7 +145,7 @@ export default class SimpleAddUI extends Toggle {
|
|||
.onClick(() => {
|
||||
confirm(preset.tags, (preciseInput?.GetValue() ?? location).data);
|
||||
});
|
||||
|
||||
|
||||
if (preciseInput !== undefined) {
|
||||
confirmButton = new Combine([preciseInput, confirmButton])
|
||||
}
|
||||
|
@ -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 splitAction = new SplitAction(
|
||||
<OsmWay>way, way.asGeoJson(), partOf, splitPoints.data.map(ff => ff.feature)
|
||||
)
|
||||
State.state.changes.applyAction(splitAction)
|
||||
}
|
||||
)
|
||||
|
||||
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