diff --git a/Logic/Osm/Actions/CreateNewNodeAction.ts b/Logic/Osm/Actions/CreateNewNodeAction.ts index beea08298..1c7d37989 100644 --- a/Logic/Osm/Actions/CreateNewNodeAction.ts +++ b/Logic/Osm/Actions/CreateNewNodeAction.ts @@ -23,6 +23,7 @@ export default class CreateNewNodeAction extends OsmCreateAction { private meta: { changeType: "create" | "import"; theme: string; specialMotivation?: string }; private readonly _reusePreviouslyCreatedPoint: boolean; + constructor(basicTags: Tag[], lat: number, lon: number, options: { @@ -46,7 +47,7 @@ export default class CreateNewNodeAction extends OsmCreateAction { this.meta = { theme: options.theme, changeType: options.changeType, - + specialMotivation: options.specialMotivation } } @@ -67,6 +68,7 @@ export default class CreateNewNodeAction extends OsmCreateAction { } async CreateChangeDescriptions(changes: Changes): Promise { + if (this._reusePreviouslyCreatedPoint) { const key = this._lat + "," + this._lon @@ -139,7 +141,7 @@ export default class CreateNewNodeAction extends OsmCreateAction { locations.splice(index + 1, 0, [this._lon, this._lat]) ids.splice(index + 1, 0, id) - + // Allright, we have to insert a new point in the way return [ newPointChange, diff --git a/Logic/Osm/ChangesetHandler.ts b/Logic/Osm/ChangesetHandler.ts index d29e7e805..8ae97632b 100644 --- a/Logic/Osm/ChangesetHandler.ts +++ b/Logic/Osm/ChangesetHandler.ts @@ -43,6 +43,30 @@ export class ChangesetHandler { } + /** + * If the metatags contain a special motivation of the format ":node/-", this method will rewrite this negative number to the actual ID + * The key is changed _in place_; true will be returned if a change has been applied + * @param extraMetaTags + * @param rewriteIds + * @private + */ + private static rewriteMetaTags(extraMetaTags: ChangesetTag[], rewriteIds: Map) { + let hasChange = false; + for (const tag of extraMetaTags) { + const match = tag.key.match(/^([a-zA-Z0-9_]+):(node\/-[0-9])$/) + if (match == null) { + continue + } + // This is a special motivation which has a negative ID -> we check for rewrites + const [_, reason, id] = match + if (rewriteIds.has(id)) { + tag.key = reason + ":" + rewriteIds.get(id) + hasChange = true + } + } + return hasChange + } + /** * The full logic to upload a change to one or more elements. * @@ -81,7 +105,13 @@ export class ChangesetHandler { openChangeset.setData(csId); const changeset = generateChangeXML(csId); console.trace("Opened a new changeset (openChangeset.data is undefined):", changeset); - await this.AddChange(csId, changeset) + const changes = await this.AddChange(csId, changeset) + const hasSpecialMotivationChanges = ChangesetHandler.rewriteMetaTags(extraMetaTags, changes) + if(hasSpecialMotivationChanges){ + // At this point, 'extraMetaTags' will have changed - we need to set the tags again + this.UpdateTags(csId, extraMetaTags) + } + } catch (e) { console.error("Could not open/upload changeset due to ", e) openChangeset.setData(undefined) @@ -91,6 +121,7 @@ export class ChangesetHandler { // Let's check! const csId = openChangeset.data; try { + const oldChangesetMeta = await this.GetChangesetMeta(csId) if (!oldChangesetMeta.open) { // Mark the CS as closed... @@ -101,41 +132,11 @@ export class ChangesetHandler { return; } - const extraTagsById = new Map() - for (const extraMetaTag of extraMetaTags) { - extraTagsById.set(extraMetaTag.key, extraMetaTag) - } - const oldCsTags = oldChangesetMeta.tags - for (const key in oldCsTags) { - const newMetaTag = extraTagsById.get(key) - if (newMetaTag === undefined) { - extraMetaTags.push({ - key: key, - value: oldCsTags[key] - }) - } else if (newMetaTag.aggregate) { - let n = Number(newMetaTag.value) - if (isNaN(n)) { - n = 0 - } - let o = Number(oldCsTags[key]) - if (isNaN(o)) { - o = 0 - } - // We _update_ the tag itself, as it'll be updated in 'extraMetaTags' straight away - newMetaTag.value = "" + (n + o) - } else { - // The old value is overwritten, thus we drop - } - } - - await this.UpdateTags(csId, extraMetaTags.map(csTag => <[string, string]>[csTag.key, csTag.value])) - - - await this.AddChange( + const rewritings = await this.AddChange( csId, generateChangeXML(csId)) + await this.RewriteTagsOf(extraMetaTags, rewritings, oldChangesetMeta) } catch (e) { console.warn("Could not upload, changeset is probably closed: ", e); @@ -144,6 +145,74 @@ export class ChangesetHandler { } } + /** + * Updates the metatag of a changeset - + * @param extraMetaTags: new changeset tags to add/fuse with this changeset + * @param oldChangesetMeta: the metadata-object of the already existing changeset + * @constructor + * @private + */ + private async RewriteTagsOf(extraMetaTags: ChangesetTag[], + rewriteIds: Map, + oldChangesetMeta: { + open: boolean, + id: number + uid: number, // User ID + changes_count: number, + tags: any + }) { + + const csId = oldChangesetMeta.id + + // Note: extraMetaTags is where all the tags are collected into + + // same as 'extraMetaTag', but indexed + // Note that updates to 'extraTagsById.get().value = XYZ' is shared with extraMetatags + const extraTagsById = new Map() + for (const extraMetaTag of extraMetaTags) { + extraTagsById.set(extraMetaTag.key, extraMetaTag) + } + + const oldCsTags = oldChangesetMeta.tags + for (const key in oldCsTags) { + const newMetaTag = extraTagsById.get(key) + const existingValue = oldCsTags[key] + + if (newMetaTag !== undefined && newMetaTag.value === existingValue) { + continue + } + if (newMetaTag === undefined) { + extraMetaTags.push({ + key: key, + value: oldCsTags[key] + }) + continue + } + + if (newMetaTag.aggregate) { + let n = Number(newMetaTag.value) + if (isNaN(n)) { + n = 0 + } + let o = Number(oldCsTags[key]) + if (isNaN(o)) { + o = 0 + } + // We _update_ the tag itself, as it'll be updated in 'extraMetaTags' straight away + newMetaTag.value = "" + (n + o) + } else { + // The old value is overwritten, thus we drop this old key + } + } + + + ChangesetHandler.rewriteMetaTags(extraMetaTags, rewriteIds) + + await this.UpdateTags(csId, extraMetaTags) + + + } + private handleIdRewrite(node: any, type: string): [string, string] { const oldId = parseInt(node.attributes.old_id.value); if (node.attributes.new_id === undefined) { @@ -163,7 +232,6 @@ export class ChangesetHandler { if (oldId == newId) { return undefined; } - console.log("Rewriting id: ", type + "/" + oldId, "-->", type + "/" + newId); const element = this.allElements.getEventSourceById("node/" + oldId); if (element === undefined) { // Element to rewrite not found, probably a node or relation that is not rendered @@ -176,7 +244,7 @@ export class ChangesetHandler { return result; } - private parseUploadChangesetResponse(response: XMLDocument): void { + private parseUploadChangesetResponse(response: XMLDocument): Map { const nodes = response.getElementsByTagName("node"); const mappings = new Map() // @ts-ignore @@ -196,6 +264,7 @@ export class ChangesetHandler { } } this.changes.registerIdRewrites(mappings) + return mappings } @@ -205,7 +274,6 @@ export class ChangesetHandler { if (changesetId === undefined) { return; } - console.log("closing changeset", changesetId); self.auth.xhr({ method: 'PUT', path: '/api/0.6/changeset/' + changesetId + '/close', @@ -232,15 +300,21 @@ export class ChangesetHandler { return csData.elements[0] } + + /** + * Puts the specified tags onto the changesets as they are. + * This method will erase previously set tags + */ private async UpdateTags( csId: number, - tags: [string, string][]) { + tags: ChangesetTag[]) { + console.trace("Updating tags of " + csId) const self = this; return new Promise(function (resolve, reject) { - tags = Utils.NoNull(tags).filter(([k, v]) => k !== undefined && v !== undefined && k !== "" && v !== "") - const metadata = tags.map(kv => ``) + tags = Utils.NoNull(tags).filter(tag => tag.key !== undefined && tag.value !== undefined && tag.key !== "" && tag.value !== "") + const metadata = tags.map(kv => ``) self.auth.xhr({ method: 'PUT', @@ -258,7 +332,6 @@ export class ChangesetHandler { } }); }) - } private OpenChangeset( @@ -306,7 +379,7 @@ export class ChangesetHandler { * Upload a changesetXML */ private AddChange(changesetId: number, - changesetXML: string): Promise { + changesetXML: string): Promise> { const self = this; return new Promise(function (resolve, reject) { self.auth.xhr({ @@ -319,9 +392,9 @@ export class ChangesetHandler { console.log("err", err); reject(err); } - self.parseUploadChangesetResponse(response); + const changes = self.parseUploadChangesetResponse(response); console.log("Uploaded changeset ", changesetId); - resolve(changesetId); + resolve(changes); }); }) diff --git a/UI/BigComponents/FilterView.ts b/UI/BigComponents/FilterView.ts index ca5259398..d6275e556 100644 --- a/UI/BigComponents/FilterView.ts +++ b/UI/BigComponents/FilterView.ts @@ -117,7 +117,7 @@ export default class FilterView extends VariableUiElement { const style = "display:flex;align-items:center;padding:0.5rem 0;"; - const layerIcon = layer.defaultIcon()?.SetClass("w-8 h-8 ml-2") + const layerIcon = layer.defaultIcon()?.SetClass("w-8 h-8 ml-2 shrink-0") const layerIconUnchecked = layer.defaultIcon()?.SetClass("opacity-50 w-8 h-8 ml-2") const layerChecked = new Combine([icon, layerIcon, styledNameChecked, zoomStatus]) diff --git a/UI/Popup/ImportButton.ts b/UI/Popup/ImportButton.ts index a9472ceb6..e2a7ebb22 100644 --- a/UI/Popup/ImportButton.ts +++ b/UI/Popup/ImportButton.ts @@ -523,26 +523,25 @@ export class ImportPointButton extends AbstractImportButton { snapOnto = await OsmObject.DownloadObjectAsync(snapOntoWayId) } let specialMotivation = undefined - if (args.note_id !== undefined) { - specialMotivation = "source: https://osm.org/note/" + args.note_id + + let note_id = args.note_id + if (args.note_id !== undefined && isNaN(Number(args.note_id))) { + note_id = originalFeatureTags.data[args.note_id] + specialMotivation = "source: https://osm.org/note/" + note_id } + const newElementAction = new CreateNewNodeAction(tags, location.lat, location.lon, { theme: state.layoutToUse.id, changeType: "import", snapOnto: snapOnto, - specialMotivation + specialMotivation: specialMotivation }) await state.changes.applyAction(newElementAction) state.selectedElement.setData(state.allElements.ContainingFeatures.get( newElementAction.newElementId )) - if (args.note_id !== undefined) { - let note_id = args.note_id - if (isNaN(Number(args.note_id))) { - note_id = originalFeatureTags.data[args.note_id] - } - + if (note_id !== undefined) { state.osmConnection.closeNote(note_id, "imported") originalFeatureTags.data["closed_at"] = new Date().toISOString() originalFeatureTags.ping()