2021-11-03 00:44:53 +01:00
import OsmChangeAction from "./OsmChangeAction"
import { Changes } from "../Changes"
import { ChangeDescription } from "./ChangeDescription"
import { Tag } from "../../Tags/Tag"
import FeatureSource from "../../FeatureSource/FeatureSource"
import { OsmNode , OsmObject , OsmWay } from "../OsmObject"
import { GeoOperations } from "../../GeoOperations"
import StaticFeatureSource from "../../FeatureSource/Sources/StaticFeatureSource"
import CreateNewNodeAction from "./CreateNewNodeAction"
import ChangeTagAction from "./ChangeTagAction"
import { And } from "../../Tags/And"
import { Utils } from "../../../Utils"
import { OsmConnection } from "../OsmConnection"
2022-07-08 03:14:55 +02:00
import { Feature } from "@turf/turf"
2021-12-23 03:36:03 +01:00
import FeaturePipeline from "../../FeatureSource/FeaturePipeline"
2022-10-27 01:50:41 +02:00
import { Geometry , LineString , Point , Polygon } from "geojson"
2021-11-03 00:44:53 +01:00
export default class ReplaceGeometryAction extends OsmChangeAction {
2021-12-23 03:36:03 +01:00
/ * *
* The target feature - mostly used for the metadata
* /
2021-11-03 00:44:53 +01:00
private readonly feature : any
private readonly state : {
2021-12-23 03:36:03 +01:00
osmConnection : OsmConnection
featurePipeline : FeaturePipeline
2021-11-03 00:44:53 +01:00
}
private readonly wayToReplaceId : string
private readonly theme : string
2021-11-04 02:16:07 +01:00
/ * *
2021-12-23 03:36:03 +01:00
* The target coordinates that should end up in OpenStreetMap .
* This is identical to either this . feature . geometry . coordinates or - in case of a polygon - feature . geometry . coordinates [ 0 ]
2022-02-22 14:13:41 +01:00
* Format : [ lon , lat ]
2021-11-04 02:16:07 +01:00
* /
2021-11-03 00:44:53 +01:00
private readonly targetCoordinates : [ number , number ] [ ]
2021-11-04 02:16:07 +01:00
/ * *
* If a target coordinate is close to another target coordinate , 'identicalTo' will point to the first index .
* /
private readonly identicalTo : number [ ]
2021-11-03 00:44:53 +01:00
private readonly newTags : Tag [ ] | undefined
constructor (
state : {
2021-12-23 03:36:03 +01:00
osmConnection : OsmConnection
featurePipeline : FeaturePipeline
2021-11-03 00:44:53 +01:00
} ,
feature : any ,
wayToReplaceId : string ,
options : {
theme : string
newTags? : Tag [ ]
}
) {
2021-11-09 01:49:07 +01:00
super ( wayToReplaceId , false )
2021-11-03 00:44:53 +01:00
this . state = state
this . feature = feature
this . wayToReplaceId = wayToReplaceId
this . theme = options . theme
const geom = this . feature . geometry
let coordinates : [ number , number ] [ ]
if ( geom . type === "LineString" ) {
coordinates = geom . coordinates
} else if ( geom . type === "Polygon" ) {
coordinates = geom . coordinates [ 0 ]
}
2021-12-23 03:36:03 +01:00
this . targetCoordinates = coordinates
2021-11-04 02:16:07 +01:00
this . identicalTo = coordinates . map ( ( _ ) = > undefined )
for ( let i = 0 ; i < coordinates . length ; i ++ ) {
if ( this . identicalTo [ i ] !== undefined ) {
continue
}
for ( let j = i + 1 ; j < coordinates . length ; j ++ ) {
2021-11-12 18:39:38 +01:00
const d = GeoOperations . distanceBetween ( coordinates [ i ] , coordinates [ j ] )
2021-11-04 02:16:07 +01:00
if ( d < 0.1 ) {
this . identicalTo [ j ] = i
}
}
}
2021-11-03 00:44:53 +01:00
this . newTags = options . newTags
}
2021-12-23 03:36:03 +01:00
// noinspection JSUnusedGlobalSymbols
2021-11-04 02:16:07 +01:00
public async getPreview ( ) : Promise < FeatureSource > {
2022-01-05 16:36:08 +01:00
const { closestIds , allNodesById , detachedNodes , reprojectedNodes } =
await this . GetClosestIds ( )
2022-10-27 01:50:41 +02:00
const preview : Feature < Geometry > [ ] = closestIds . map ( ( newId , i ) = > {
2021-11-07 16:34:51 +01:00
if ( this . identicalTo [ i ] !== undefined ) {
2021-11-04 02:16:07 +01:00
return undefined
}
2021-11-07 16:34:51 +01:00
2021-11-03 00:44:53 +01:00
if ( newId === undefined ) {
return {
type : "Feature" ,
properties : {
newpoint : "yes" ,
2022-01-05 16:36:08 +01:00
id : "replace-geometry-move-" + i ,
2021-11-03 00:44:53 +01:00
} ,
geometry : {
type : "Point" ,
coordinates : this.targetCoordinates [ i ] ,
2022-09-08 21:40:48 +02:00
} ,
2021-11-03 00:44:53 +01:00
}
}
2022-01-05 16:36:08 +01:00
const origNode = allNodesById . get ( newId )
2021-11-03 00:44:53 +01:00
return {
type : "Feature" ,
properties : {
move : "yes" ,
"osm-id" : newId ,
2022-01-05 16:36:08 +01:00
id : "replace-geometry-move-" + i ,
"original-node-tags" : JSON . stringify ( origNode . tags ) ,
} ,
geometry : {
type : "LineString" ,
coordinates : [ [ origNode . lon , origNode . lat ] , this . targetCoordinates [ i ] ] ,
2022-09-08 21:40:48 +02:00
} ,
2022-01-05 16:36:08 +01:00
}
} )
2022-01-06 14:39:42 +01:00
reprojectedNodes . forEach ( ( { newLat , newLon , nodeId } ) = > {
2022-01-05 16:36:08 +01:00
const origNode = allNodesById . get ( nodeId )
2022-09-21 02:21:20 +02:00
const feature : Feature < LineString > = {
2022-01-05 16:36:08 +01:00
type : "Feature" ,
properties : {
move : "yes" ,
2022-01-06 14:39:42 +01:00
reprojection : "yes" ,
2022-01-05 16:36:08 +01:00
"osm-id" : nodeId ,
2022-01-06 14:39:42 +01:00
id : "replace-geometry-reproject-" + nodeId ,
2022-01-05 16:36:08 +01:00
"original-node-tags" : JSON . stringify ( origNode . tags ) ,
2021-11-03 00:44:53 +01:00
} ,
geometry : {
type : "LineString" ,
2022-01-05 16:36:08 +01:00
coordinates : [
[ origNode . lon , origNode . lat ] ,
[ newLon , newLat ] ,
2022-09-08 21:40:48 +02:00
] ,
} ,
2021-11-03 00:44:53 +01:00
}
2022-01-05 16:36:08 +01:00
preview . push ( feature )
2021-11-03 00:44:53 +01:00
} )
2021-12-23 03:36:03 +01:00
2022-01-01 01:59:50 +01:00
detachedNodes . forEach ( ( { reason } , id ) = > {
2022-01-05 16:36:08 +01:00
const origNode = allNodesById . get ( id )
2022-09-21 02:21:20 +02:00
const feature : Feature < Point > = {
2021-12-23 03:36:03 +01:00
type : "Feature" ,
properties : {
detach : "yes" ,
2022-01-01 01:59:50 +01:00
id : "replace-geometry-detach-" + id ,
2022-01-05 16:36:08 +01:00
"detach-reason" : reason ,
"original-node-tags" : JSON . stringify ( origNode . tags ) ,
2021-12-23 03:36:03 +01:00
} ,
geometry : {
type : "Point" ,
2022-01-05 16:36:08 +01:00
coordinates : [ origNode . lon , origNode . lat ] ,
2022-09-08 21:40:48 +02:00
} ,
2021-12-23 03:36:03 +01:00
}
preview . push ( feature )
2022-01-01 01:59:50 +01:00
} )
2021-12-23 03:36:03 +01:00
2022-06-05 02:24:14 +02:00
return StaticFeatureSource . fromGeojson ( Utils . NoNull ( preview ) )
2021-11-03 00:44:53 +01:00
}
/ * *
2021-12-24 02:51:01 +01:00
* For ' this . feature ` , gets a corresponding closest node that alreay exsists.
2022-01-05 16:36:08 +01:00
*
2021-12-24 02:51:01 +01:00
* This method contains the main logic for this module , as it decides which node gets moved where .
2022-01-05 16:36:08 +01:00
*
2021-11-03 00:44:53 +01:00
* /
2021-12-30 20:41:45 +01:00
public async GetClosestIds ( ) : Promise < {
2021-12-23 03:36:03 +01:00
// A list of the same length as targetCoordinates, containing which OSM-point to move. If undefined, a new point will be created
closestIds : number [ ]
allNodesById : Map < number , OsmNode >
osmWay : OsmWay
2022-01-01 01:59:50 +01:00
detachedNodes : Map <
number ,
{
2022-01-05 16:36:08 +01:00
reason : string
hasTags : boolean
}
2022-09-08 21:40:48 +02:00
>
2022-01-05 16:36:08 +01:00
reprojectedNodes : Map <
number ,
{
/*Move the node with this ID into the way as extra node, as it has some relation with the original object*/
projectAfterIndex : number
2022-01-06 14:39:42 +01:00
distance : number
2022-01-05 16:36:08 +01:00
newLat : number
newLon : number
nodeId : number
2022-01-01 01:59:50 +01:00
}
>
2021-12-23 03:36:03 +01:00
} > {
2021-11-03 00:44:53 +01:00
// TODO FIXME: if a new point has to be created, snap to already existing ways
2021-12-23 03:36:03 +01:00
2022-01-01 01:59:50 +01:00
const nodeDb = this . state . featurePipeline . fullNodeDatabase
if ( nodeDb === undefined ) {
throw "PANIC: replaceGeometryAction needs the FullNodeDatabase, which is undefined. This should be initialized by having the 'type_node'-layer enabled in your theme. (NB: the replacebutton has type_node as dependency)"
}
2022-01-05 16:36:08 +01:00
const self = this
2021-12-23 03:36:03 +01:00
let parsed : OsmObject [ ]
{
// Gather the needed OsmObjects
const splitted = this . wayToReplaceId . split ( "/" )
const type = splitted [ 0 ]
const idN = Number ( splitted [ 1 ] )
if ( idN < 0 || type !== "way" ) {
throw "Invalid ID to conflate: " + this . wayToReplaceId
}
2022-01-06 14:39:42 +01:00
const url = ` ${
this . state . osmConnection ? . _oauth_config ? . url ? ? "https://openstreetmap.org"
} / api / 0.6 / $ { this . wayToReplaceId } / full `
2021-12-23 03:36:03 +01:00
const rawData = await Utils . downloadJsonCached ( url , 1000 )
parsed = OsmObject . ParseObjects ( rawData . elements )
2021-11-03 00:44:53 +01:00
}
const allNodes = parsed . filter ( ( o ) = > o . type === "node" )
2022-01-05 16:36:08 +01:00
const osmWay = < OsmWay > parsed [ parsed . length - 1 ]
if ( osmWay . type !== "way" ) {
throw "WEIRD: expected an OSM-way as last element here!"
}
const allNodesById = new Map < number , OsmNode > ( )
for ( const node of allNodes ) {
allNodesById . set ( node . id , < OsmNode > node )
}
2021-11-03 00:44:53 +01:00
/ * *
2021-12-30 20:41:45 +01:00
* For every already existing OSM - point , we calculate :
2022-01-05 16:36:08 +01:00
*
2021-12-30 20:41:45 +01:00
* - the distance to every target point .
* - Wether this node has ( other ) parent ways , which might restrict movement
* - Wether this node has tags set
2022-01-05 16:36:08 +01:00
*
2021-12-30 20:41:45 +01:00
* Having tags and / or being connected to another way indicate that there is some _relation_ with objects in the neighbourhood .
2022-01-05 16:36:08 +01:00
*
2021-12-30 20:41:45 +01:00
* The Replace - geometry action should try its best to honour these . Some 'wiggling' is allowed ( e . g . moving an entrance a bit ) , but these relations should not be broken . l
2021-11-03 00:44:53 +01:00
* /
2021-12-30 20:41:45 +01:00
const distances = new Map <
number /* osmId*/ ,
2022-01-05 16:36:08 +01:00
/** target coordinate index --> distance (or undefined if a duplicate)*/
number [ ]
> ( )
2022-09-08 21:40:48 +02:00
2022-01-01 01:59:50 +01:00
const nodeInfo = new Map <
number /* osmId*/ ,
{
2022-01-05 16:36:08 +01:00
distances : number [ ]
2022-01-01 01:59:50 +01:00
// Part of some other way then the one that should be replaced
partOfWay : boolean
hasTags : boolean
}
> ( )
2022-01-05 16:36:08 +01:00
2021-12-23 03:36:03 +01:00
for ( const node of allNodes ) {
2022-01-01 01:59:50 +01:00
const parentWays = nodeDb . GetParentWays ( node . id )
2022-01-05 16:36:08 +01:00
if ( parentWays === undefined ) {
2022-01-01 01:59:50 +01:00
throw "PANIC: the way to replace has a node which has no parents at all. Is it deleted in the meantime?"
}
2022-01-05 16:36:08 +01:00
const parentWayIds = parentWays . data . map ( ( w ) = > w . type + "/" + w . id )
2022-01-01 01:59:50 +01:00
const idIndex = parentWayIds . indexOf ( this . wayToReplaceId )
2022-01-05 16:36:08 +01:00
if ( idIndex < 0 ) {
2022-01-01 01:59:50 +01:00
throw "PANIC: the way to replace has a node, which is _not_ part of this was according to the node..."
}
parentWayIds . splice ( idIndex , 1 )
const partOfSomeWay = parentWayIds . length > 0
2022-01-05 16:36:08 +01:00
const hasTags = Object . keys ( node . tags ) . length > 1
2021-12-23 03:36:03 +01:00
const nodeDistances = this . targetCoordinates . map ( ( _ ) = > undefined )
for ( let i = 0 ; i < this . targetCoordinates . length ; i ++ ) {
if ( this . identicalTo [ i ] !== undefined ) {
continue
2021-11-03 00:44:53 +01:00
}
2021-12-23 03:36:03 +01:00
const targetCoordinate = this . targetCoordinates [ i ]
const cp = node . centerpoint ( )
2022-01-05 16:36:08 +01:00
const d = GeoOperations . distanceBetween ( targetCoordinate , [ cp [ 1 ] , cp [ 0 ] ] )
if ( d > 25 ) {
// This is too much to move
continue
}
if ( d < 3 || ! ( hasTags || partOfSomeWay ) ) {
// If there is some relation: cap the move distance to 3m
nodeDistances [ i ] = d
}
2021-11-03 00:44:53 +01:00
}
2021-12-23 03:36:03 +01:00
distances . set ( node . id , nodeDistances )
2022-01-01 01:59:50 +01:00
nodeInfo . set ( node . id , {
distances : nodeDistances ,
partOfWay : partOfSomeWay ,
2022-01-05 16:36:08 +01:00
hasTags ,
2022-01-01 01:59:50 +01:00
} )
2021-11-03 00:44:53 +01:00
}
2021-12-30 20:41:45 +01:00
const closestIds = this . targetCoordinates . map ( ( _ ) = > undefined )
2022-01-01 01:59:50 +01:00
const unusedIds = new Map <
number ,
{
2022-01-05 16:36:08 +01:00
reason : string
hasTags : boolean
2022-01-01 01:59:50 +01:00
}
> ( )
2021-12-30 20:41:45 +01:00
{
2022-01-05 16:36:08 +01:00
// Search best merge candidate
/ * *
* Then , we search the node that has to move the least distance and add this as mapping .
* We do this until no points are left
* /
let candidate : number
let moveDistance : number
/ * *
* The list of nodes that are _not_ used anymore , typically if there are less targetCoordinates then source coordinates
* /
do {
candidate = undefined
moveDistance = Infinity
distances . forEach ( ( distances , nodeId ) = > {
const minDist = Math . min ( . . . Utils . NoNull ( distances ) )
if ( moveDistance > minDist ) {
// We have found a candidate to move
candidate = nodeId
moveDistance = minDist
}
} )
if ( candidate !== undefined ) {
// We found a candidate... Search the corresponding target id:
let targetId : number = undefined
let lowestDistance = Number . MAX_VALUE
let nodeDistances = distances . get ( candidate )
for ( let i = 0 ; i < nodeDistances . length ; i ++ ) {
const d = nodeDistances [ i ]
if ( d !== undefined && d < lowestDistance ) {
lowestDistance = d
targetId = i
}
}
// This candidates role is done, it can be removed from the distance matrix
distances . delete ( candidate )
if ( targetId !== undefined ) {
// At this point, we have our target coordinate index: targetId!
// Lets map it...
closestIds [ targetId ] = candidate
// To indicate that this targetCoordinate is taken, we remove them from the distances matrix
distances . forEach ( ( dists ) = > {
dists [ targetId ] = undefined
} )
} else {
// Seems like all the targetCoordinates have found a source point
unusedIds . set ( candidate , {
reason : "Unused by new way" ,
hasTags : nodeInfo.get ( candidate ) . hasTags ,
} )
}
2021-11-04 02:16:07 +01:00
}
2022-01-05 16:36:08 +01:00
} while ( candidate !== undefined )
}
// If there are still unused values in 'distances', they are definitively unused
distances . forEach ( ( _ , nodeId ) = > {
unusedIds . set ( nodeId , {
reason : "Unused by new way" ,
hasTags : nodeInfo.get ( nodeId ) . hasTags ,
2021-12-23 03:36:03 +01:00
} )
2022-01-05 16:36:08 +01:00
} )
2021-12-23 03:36:03 +01:00
2022-01-05 16:36:08 +01:00
const reprojectedNodes = new Map <
number ,
{
/*Move the node with this ID into the way as extra node, as it has some relation with the original object*/
projectAfterIndex : number
2022-01-06 14:39:42 +01:00
distance : number
2022-01-05 16:36:08 +01:00
newLat : number
newLon : number
nodeId : number
}
> ( )
{
// Lets check the unused ids: can they be detached or do they signify some relation with the object?
unusedIds . forEach ( ( { } , id ) = > {
const info = nodeInfo . get ( id )
if ( ! ( info . hasTags || info . partOfWay ) ) {
// Nothing special here, we detach
return
2021-11-03 00:44:53 +01:00
}
2021-12-23 03:36:03 +01:00
2022-01-05 16:36:08 +01:00
// The current node has tags and/or has an attached other building.
// We should project them and move them onto the building on an appropriate place
const node = allNodesById . get ( id )
// Project the node onto the target way to calculate the new coordinates
2022-10-27 01:50:41 +02:00
const way = < Feature < LineString > > {
2022-01-05 16:36:08 +01:00
type : "Feature" ,
properties : { } ,
geometry : {
type : "LineString" ,
coordinates : self.targetCoordinates ,
2022-09-08 21:40:48 +02:00
} ,
2022-01-05 16:36:08 +01:00
}
const projected = GeoOperations . nearestPoint ( way , [ node . lon , node . lat ] )
reprojectedNodes . set ( id , {
newLon : projected.geometry.coordinates [ 0 ] ,
newLat : projected.geometry.coordinates [ 1 ] ,
projectAfterIndex : projected.properties.index ,
2022-01-06 14:39:42 +01:00
distance : projected.properties.dist ,
2022-01-05 16:36:08 +01:00
nodeId : id ,
} )
} )
2022-01-06 14:39:42 +01:00
2022-01-05 16:36:08 +01:00
reprojectedNodes . forEach ( ( _ , nodeId ) = > unusedIds . delete ( nodeId ) )
}
return { closestIds , allNodesById , osmWay , detachedNodes : unusedIds , reprojectedNodes }
}
2021-12-23 03:36:03 +01:00
2022-01-05 16:36:08 +01:00
protected async CreateChangeDescriptions ( changes : Changes ) : Promise < ChangeDescription [ ] > {
const nodeDb = this . state . featurePipeline . fullNodeDatabase
if ( nodeDb === undefined ) {
throw "PANIC: replaceGeometryAction needs the FullNodeDatabase, which is undefined. This should be initialized by having the 'type_node'-layer enabled in your theme. (NB: the replacebutton has type_node as dependency)"
}
const { closestIds , osmWay , detachedNodes , reprojectedNodes } = await this . GetClosestIds ( )
const allChanges : ChangeDescription [ ] = [ ]
const actualIdsToUse : number [ ] = [ ]
for ( let i = 0 ; i < closestIds . length ; i ++ ) {
if ( this . identicalTo [ i ] !== undefined ) {
const j = this . identicalTo [ i ]
actualIdsToUse . push ( actualIdsToUse [ j ] )
continue
}
const closestId = closestIds [ i ]
const [ lon , lat ] = this . targetCoordinates [ i ]
if ( closestId === undefined ) {
const newNodeAction = new CreateNewNodeAction ( [ ] , lat , lon , {
allowReuseOfPreviouslyCreatedPoints : true ,
theme : this.theme ,
changeType : null ,
2022-01-01 01:59:50 +01:00
} )
2022-01-05 16:36:08 +01:00
const changeDescr = await newNodeAction . CreateChangeDescriptions ( changes )
allChanges . push ( . . . changeDescr )
actualIdsToUse . push ( newNodeAction . newElementIdNumber )
} else {
const change = < ChangeDescription > {
id : closestId ,
type : "node" ,
meta : {
theme : this.theme ,
changeType : "move" ,
} ,
changes : { lon , lat } ,
2021-11-03 00:44:53 +01:00
}
2022-01-05 16:36:08 +01:00
actualIdsToUse . push ( closestId )
allChanges . push ( change )
2021-11-03 00:44:53 +01:00
}
2022-01-05 16:36:08 +01:00
}
2021-11-03 00:44:53 +01:00
2022-01-05 16:36:08 +01:00
if ( this . newTags !== undefined && this . newTags . length > 0 ) {
const addExtraTags = new ChangeTagAction (
this . wayToReplaceId ,
new And ( this . newTags ) ,
osmWay . tags ,
{
theme : this.theme ,
changeType : "conflation" ,
}
)
allChanges . push ( . . . ( await addExtraTags . CreateChangeDescriptions ( changes ) ) )
}
2022-01-06 14:39:42 +01:00
const newCoordinates = [ . . . this . targetCoordinates ]
{
// Add reprojected nodes to the way
const proj = Array . from ( reprojectedNodes . values ( ) )
proj . sort ( ( a , b ) = > {
// Sort descending
const diff = b . projectAfterIndex - a . projectAfterIndex
2022-01-26 21:40:38 +01:00
if ( diff !== 0 ) {
2022-01-06 14:39:42 +01:00
return diff
}
return b . distance - a . distance
} )
for ( const reprojectedNode of proj ) {
const change = < ChangeDescription > {
id : reprojectedNode.nodeId ,
type : "node" ,
meta : {
theme : this.theme ,
changeType : "move" ,
} ,
changes : { lon : reprojectedNode.newLon , lat : reprojectedNode.newLat } ,
}
allChanges . push ( change )
actualIdsToUse . splice (
reprojectedNode . projectAfterIndex + 1 ,
0 ,
reprojectedNode . nodeId
)
newCoordinates . splice ( reprojectedNode . projectAfterIndex + 1 , 0 , [
reprojectedNode . newLon ,
reprojectedNode . newLat ,
] )
}
}
2022-01-05 16:36:08 +01:00
// Actually change the nodes of the way!
allChanges . push ( {
type : "way" ,
id : osmWay.id ,
changes : {
nodes : actualIdsToUse ,
2022-01-06 14:39:42 +01:00
coordinates : newCoordinates ,
2022-01-05 16:36:08 +01:00
} ,
meta : {
theme : this.theme ,
changeType : "conflation" ,
2021-12-23 03:36:03 +01:00
} ,
2022-01-05 16:36:08 +01:00
} )
// Some nodes might need to be deleted
if ( detachedNodes . size > 0 ) {
detachedNodes . forEach ( ( { hasTags , reason } , nodeId ) = > {
const parentWays = nodeDb . GetParentWays ( nodeId )
const index = parentWays . data . map ( ( w ) = > w . id ) . indexOf ( osmWay . id )
if ( index < 0 ) {
console . error (
"ReplaceGeometryAction is trying to detach node " +
nodeId +
", but it isn't listed as being part of way " +
osmWay . id
)
return
}
// We detachted this node - so we unregister
parentWays . data . splice ( index , 1 )
parentWays . ping ( )
if ( hasTags ) {
// Has tags: we leave this node alone
return
}
if ( parentWays . data . length != 0 ) {
// Still part of other ways: we leave this node alone!
return
}
console . log ( "Removing node " + nodeId , "as it isn't needed anymore by any way" )
allChanges . push ( {
meta : {
theme : this.theme ,
changeType : "delete" ,
} ,
doDelete : true ,
type : "node" ,
id : nodeId ,
} )
} )
2021-11-03 00:44:53 +01:00
}
2022-01-05 16:36:08 +01:00
return allChanges
2021-11-03 00:44:53 +01:00
}
}