feat(sync): Add a enableSync option.

This changes how the syncEngine works. At the moment, it's always
instanciated, even if no syncing is configured. It just does nothing.

This is to avoid doing `if (engine) engine.update()` calls everywhere
we use it.

You now need to `start()` and `stop()` it.
This commit is contained in:
Alexis Métaireau 2024-04-30 17:22:33 +02:00
parent 9a74cc370c
commit 5e692d2280
8 changed files with 60 additions and 28 deletions

View file

@ -1,8 +1,9 @@
import { translate } from './i18n.js'
// Possible impacts
// ['ui', 'data', 'limit-bounds', 'datalayer-index', 'remote-data', 'background']
// ['ui', 'data', 'limit-bounds', 'datalayer-index', 'remote-data', 'background' 'sync']
// This is sorted alphabetically
export const SCHEMA = {
browsable: {
impacts: ['ui'],
@ -14,6 +15,12 @@ export const SCHEMA = {
label: translate('Do you want to display a caption bar?'),
default: false,
},
captionControl: {
type: Boolean,
nullable: true,
label: translate('Display the caption control'),
default: true,
},
captionMenus: {
type: Boolean,
impacts: ['ui'],
@ -184,7 +191,6 @@ export const SCHEMA = {
type: Boolean,
impacts: ['ui'],
},
interactive: {
type: Boolean,
impacts: ['data'],
@ -373,12 +379,6 @@ export const SCHEMA = {
impacts: ['ui'],
label: translate('Allow scroll wheel zoom?'),
},
captionControl: {
type: Boolean,
nullable: true,
label: translate('Display the caption control'),
default: true,
},
searchControl: {
type: Boolean,
impacts: ['ui'],
@ -437,6 +437,13 @@ export const SCHEMA = {
inheritable: true,
default: true,
},
syncEnabled: {
type: Boolean,
impacts: ['sync', 'ui'],
label: translate('Enable real-time collaboration'),
helpEntries: 'sync',
default: false,
},
tilelayer: {
type: Object,
impacts: ['background'],

View file

@ -8,16 +8,31 @@ import {
} from './updaters.js'
export class SyncEngine {
constructor(map, webSocketURI, authToken) {
constructor(map) {
this.map = map
this.receiver = new MessagesDispatcher(this.map)
this._initialize()
}
_initialize() {
this.transport = undefined
const noop = () => undefined
// by default, all operations do nothing, until the engine is started.
this.upsert = this.update = this.delete = noop
}
start(webSocketURI, authToken) {
this.transport = new WebSocketTransport(webSocketURI, authToken, this.receiver)
this.sender = new MessagesSender(this.transport)
this.upsert = this.sender.upsert.bind(this.sender)
this.update = this.sender.update.bind(this.sender)
this.delete = this.sender.delete.bind(this.sender)
}
stop() {
if (this.transport) this.transport.close()
this._initialize()
}
}
export class MessagesDispatcher {

View file

@ -19,4 +19,8 @@ export class WebSocketTransport {
let encoded = JSON.stringify(message)
this.websocket.send(encoded)
}
close() {
this.websocket.close()
}
}

View file

@ -1186,10 +1186,8 @@ 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)
console.log('setter', field, value)
const { subject, metadata, engine } = this.obj.getSyncMetadata()
console.log('metadata', metadata)
engine.update(subject, metadata, field, value)
if (engine) engine.update(subject, metadata, field, value)
},
finish: function () {

View file

@ -248,11 +248,16 @@ U.Map = L.Map.extend({
this.backup()
this.initContextMenu()
this.on('click contextmenu.show', this.closeInplaceToolbar)
this.sync = new U.SyncEngine(this)
Promise.resolve(this.initSyncEngine())
},
initSyncEngine: async function () {
console.log('this.options.syncEnabled', this.options.syncEnabled)
if (this.options.syncEnabled != true) {
this.sync.stop()
} else {
// Get the authentication token from the server
// And pass it to the sync engine.
// FIXME: use `this.urls`
@ -260,7 +265,8 @@ U.Map = L.Map.extend({
`/map/${this.options.umap_id}/ws-token/`
)
if (!error) {
this.sync = new U.SyncEngine(this, 'ws://localhost:8001', response.token)
this.sync.start('ws://localhost:8001', response.token)
}
}
},
@ -294,6 +300,8 @@ U.Map = L.Map.extend({
case 'bounds':
this.handleLimitBounds()
break
case 'sync':
this.initSyncEngine()
}
}
},
@ -1036,7 +1044,7 @@ U.Map = L.Map.extend({
formData.append('center', JSON.stringify(this.geometry()))
formData.append('settings', JSON.stringify(geojson))
const uri = this.urls.get('map_save', { map_id: this.options.umap_id })
const [data, response, error] = await this.server.post(uri, {}, formData)
const [data, _, error] = await this.server.post(uri, {}, formData)
// FIXME: login_required response will not be an error, so it will not
// stop code while it should
if (!error) {
@ -1538,7 +1546,7 @@ U.Map = L.Map.extend({
if (!this.editEnabled) return
if (this.options.editMode !== 'advanced') return
const container = L.DomUtil.create('div', 'umap-edit-container'),
metadataFields = ['options.name', 'options.description'],
metadataFields = ['options.name', 'options.description', 'options.syncEnabled'],
title = L.DomUtil.create('h3', '', container)
title.textContent = L._('Edit map details')
const builder = new U.FormBuilder(this, metadataFields, {

View file

@ -60,6 +60,9 @@ U.Layer.Cluster = L.MarkerClusterGroup.extend({
initialize: function (datalayer) {
this.datalayer = datalayer
if (!U.Utils.isObject(this.datalayer.options.cluster)) {
this.datalayer.options.cluster = {}
}
const options = {
polygonOptions: {
color: this.datalayer.getColor(),
@ -74,10 +77,6 @@ U.Layer.Cluster = L.MarkerClusterGroup.extend({
L.MarkerClusterGroup.prototype.initialize.call(this, options)
this._markerCluster = U.MarkerCluster
this._layers = []
if (!U.Utils.isObject(this.datalayer.options.cluster)) {
this.datalayer.options.cluster = {}
}
},
onRemove: function (map) {

View file

@ -109,6 +109,7 @@ U.MapPermissions = L.Class.extend({
{ handler: 'ManageEditors', label: L._("Map's editors") },
])
}
const builder = new U.FormBuilder(this, fields)
const form = builder.build()
container.appendChild(form)

View file

@ -51,7 +51,7 @@ class OperationMessage(BaseModel):
subject: str = Literal["map", "layer", "feature"]
metadata: Optional[dict] = None
key: Optional[str] = None
value: Optional[str | bool | int | GeometryValue] = None
value: Optional[str | bool | int | GeometryValue | Geometry] = None
async def join_and_listen(