diff --git a/umap/static/umap/js/modules/data/layer.js b/umap/static/umap/js/modules/data/layer.js index fba926b8..f999b96b 100644 --- a/umap/static/umap/js/modules/data/layer.js +++ b/umap/static/umap/js/modules/data/layer.js @@ -269,13 +269,11 @@ export class DataLayer extends ServerStored { } clear() { - this.layer.clearLayers() - this._features = {} - this._index = Array() - if (this._geojson) { - this.backupData() - this._geojson = null + this.sync.startBatch() + for (const feature of Object.values(this._features)) { + feature.del() } + this.sync.commitBatch() this.dataChanged() } diff --git a/umap/static/umap/js/modules/sync/undo.js b/umap/static/umap/js/modules/sync/undo.js index d16263cc..954b6955 100644 --- a/umap/static/umap/js/modules/sync/undo.js +++ b/umap/static/umap/js/modules/sync/undo.js @@ -43,7 +43,11 @@ export class UndoManager { copyOperation(stage, redo) { const operation = Utils.CopyJSON(stage.operation) - operation.value = redo ? stage.newValue : stage.oldValue + const value = redo ? stage.newValue : stage.oldValue + operation.value = value + if (['delete', 'upsert'].includes(operation.verb)) { + operation.verb = value === null || value === undefined ? 'delete' : 'upsert' + } return operation } @@ -73,18 +77,15 @@ export class UndoManager { switch (operation.verb) { case 'update': updater.update(operation) - this._syncEngine._send(operation) break case 'delete': + updater.delete(operation) + break case 'upsert': - if (operation.value === null || operation.value === undefined) { - updater.delete(operation) - } else { - updater.upsert(operation) - } - this._syncEngine._send(operation) + updater.upsert(operation) break } + this._syncEngine._send(operation) } _getUpdater(subject, metadata) { diff --git a/umap/tests/base.py b/umap/tests/base.py index 0e862cb4..59e64a76 100644 --- a/umap/tests/base.py +++ b/umap/tests/base.py @@ -18,6 +18,7 @@ DATALAYER_DATA = { "features": [ { "type": "Feature", + "id": "ExNTQ", "geometry": { "type": "Point", "coordinates": [14.68896484375, 48.55297816440071], diff --git a/umap/tests/integration/test_websocket_sync.py b/umap/tests/integration/test_websocket_sync.py index 40db583e..7dd256cf 100644 --- a/umap/tests/integration/test_websocket_sync.py +++ b/umap/tests/integration/test_websocket_sync.py @@ -659,3 +659,33 @@ def test_should_sync_line_on_escape(new_page, asgi_live_server, tilelayer): expect(peerA.locator("path")).to_have_count(1) expect(peerB.locator("path")).to_have_count(1) + + +@pytest.mark.xdist_group(name="websockets") +def test_should_sync_datalayer_clear( + new_page, asgi_live_server, tilelayer, map, datalayer +): + map.settings["properties"]["syncEnabled"] = True + map.edit_status = Map.ANONYMOUS + map.save() + + # Create two tabs + peerA = new_page("Page A") + peerA.goto(f"{asgi_live_server.url}{map.get_absolute_url()}?edit") + peerB = new_page("Page B") + peerB.goto(f"{asgi_live_server.url}{map.get_absolute_url()}?edit") + expect(peerA.locator(".leaflet-marker-icon")).to_have_count(1) + expect(peerB.locator(".leaflet-marker-icon")).to_have_count(1) + + # Clear layer in peer A + peerA.get_by_role("button", name="Manage layers").click() + peerA.get_by_role("button", name="Edit", exact=True).click() + peerA.locator("summary").filter(has_text="Advanced actions").click() + peerA.get_by_role("button", name="Empty").click() + expect(peerA.locator(".leaflet-marker-icon")).to_have_count(0) + expect(peerB.locator(".leaflet-marker-icon")).to_have_count(0) + + # Undo in peer A + peerA.get_by_role("button", name="Undo").click() + expect(peerA.locator(".leaflet-marker-icon")).to_have_count(1) + expect(peerB.locator(".leaflet-marker-icon")).to_have_count(1)