diff --git a/umap/static/umap/js/modules/sync/engine.js b/umap/static/umap/js/modules/sync/engine.js index 7c77ef27..7337906e 100644 --- a/umap/static/umap/js/modules/sync/engine.js +++ b/umap/static/umap/js/modules/sync/engine.js @@ -53,7 +53,7 @@ export class MessagesDispatcher { dispatch({ kind, payload }) { console.log(kind, payload) - if (kind == 'sync-protocol') { + if (kind == 'operation') { let updater = this.getUpdater(payload.subject, payload.metadata) updater.applyMessage(payload) } @@ -74,7 +74,7 @@ export class MessagesSender { } send(message) { - this._transport.send('sync-protocol', message) + this._transport.send('operation', message) } upsert(subject, metadata, value) { diff --git a/umap/static/umap/js/umap.controls.js b/umap/static/umap/js/umap.controls.js index 4ae4ffbc..2b5b2175 100644 --- a/umap/static/umap/js/umap.controls.js +++ b/umap/static/umap/js/umap.controls.js @@ -1263,6 +1263,7 @@ U.Editable = L.Editable.extend({ this.on('editable:drawing:commit', function (e) { e.layer.isDirty = true if (this.map.editedFeature !== e.layer) e.layer.edit(e) + e.layer.onCommit() }) this.on('editable:editing', (e) => { const layer = e.layer diff --git a/umap/static/umap/js/umap.features.js b/umap/static/umap/js/umap.features.js index 27c835e7..7c53cd94 100644 --- a/umap/static/umap/js/umap.features.js +++ b/umap/static/umap/js/umap.features.js @@ -1,6 +1,44 @@ U.FeatureMixin = { staticOptions: { mainColor: 'color' }, + getSyncMetadata: function () { + return { + subject: 'feature', + metadata: { + id: this.id, + layerId: this.datalayer?.id || null, + featureType: this.getClassName(), + }, + } + }, + + getSyncEngine: function () { + // FIXME use a get property / defineProperty + return this.map.sync + }, + + onCommit: function () { + this.map.sync.upsert(this.getSyncSubject(), this.getSyncMetadata(), { + geometry: this.getGeometry(), + }) + }, + + getGeometry: function () { + return this.toGeoJSON().geometry + }, + + syncUpdatedProperties: function (properties) { + if ('latlng'.includes(properties)) { + const { subject, metadata } = this.getSyncMetadata() + this.map.sync.update(subject, metadata, 'geometry', this.getGeometry()) + } + }, + + syncDelete: function () { + let { subject, metadata } = this.getSyncMetadata() + this.map.sync.delete(subject, metadata) + }, + initialize: function (map, latlng, options) { this.map = map if (typeof options === 'undefined') { @@ -241,12 +279,15 @@ U.FeatureMixin = { return false }, - del: function () { + del: function (fromSync) { this.isDirty = true this.map.closePopup() if (this.datalayer) { this.datalayer.removeLayer(this) this.disconnectFromDataLayer(this.datalayer) + + // Do not relay the event if we received it. + if (!fromSync) this.syncDelete() } }, @@ -602,6 +643,7 @@ U.Marker = L.Marker.extend({ function (e) { this.isDirty = true this.edit(e) + this.syncUpdatedProperties(['latlng']) }, this ) diff --git a/umap/static/umap/js/umap.forms.js b/umap/static/umap/js/umap.forms.js index 4fb30f8a..ca2d4ab1 100644 --- a/umap/static/umap/js/umap.forms.js +++ b/umap/static/umap/js/umap.forms.js @@ -1186,6 +1186,9 @@ U.FormBuilder = L.FormBuilder.extend({ L.FormBuilder.prototype.setter.call(this, field, value) this.obj.isDirty = true if ('render' in this.obj) this.obj.render([field], this) + const syncEngine = this.obj.getSyncEngine() + const { subject, metadata } = this.obj.getSyncMetadata() + syncEngine.update(subject, metadata, field, value) }, finish: function () { diff --git a/umap/static/umap/js/umap.js b/umap/static/umap/js/umap.js index d8d6a839..6da30a2a 100644 --- a/umap/static/umap/js/umap.js +++ b/umap/static/umap/js/umap.js @@ -249,10 +249,10 @@ U.Map = L.Map.extend({ this.initContextMenu() this.on('click contextmenu.show', this.closeInplaceToolbar) - Promise.resolve(this.getSyncEngine()) + Promise.resolve(this.initSyncEngine()) }, - getSyncEngine: async function () { + initSyncEngine: async function () { // Get the authentication token from the server // And pass it to the sync engine. const [response, _, error] = await this.server.get('/map/2/ws-token/') @@ -261,6 +261,16 @@ U.Map = L.Map.extend({ } }, + getSyncMetadata: function () { + return { + subject: 'map', + } + }, + + getSyncEngine: function () { + return this.sync + }, + render: function (fields) { let impacts = U.Utils.getImpactsFromSchema(fields) diff --git a/umap/ws.py b/umap/ws.py index 0806af26..86839cdd 100644 --- a/umap/ws.py +++ b/umap/ws.py @@ -2,7 +2,7 @@ import asyncio from collections import defaultdict -from typing import Literal +from typing import Literal, Optional import django import websockets @@ -32,11 +32,15 @@ class JoinMessage(BaseModel): token: str +# FIXME better define the different messages +# to ensure only relying valid ones. class OperationMessage(BaseModel): kind: str = "operation" + verb: str = Literal["upsert", "update", "delete"] subject: str = Literal["map", "layer", "feature"] metadata: dict - data: dict + key: str + value: Optional[str] async def join_and_listen(