Compare commits

..

No commits in common. "591cfce81fb44c43a7ad222b4699fe2d13380b7e" and "0859254623871681aab919a51d56be8f13ca6c2e" have entirely different histories.

7 changed files with 80 additions and 129 deletions

View file

@ -19,7 +19,7 @@
.leaflet-container .edit-disable, .leaflet-container .edit-disable,
.leaflet-container .connected-peers .leaflet-container .connected-peers
{ {
display: inline-block; display: block;
height: 32px; height: 32px;
line-height: 30px; line-height: 30px;
padding: 0 20px; padding: 0 20px;
@ -78,13 +78,21 @@
background: rgba(66, 236, 230, 0.10); background: rgba(66, 236, 230, 0.10);
} }
.leaflet-container .edit-save, .leaflet-container .edit-save,
.leaflet-container .edit-undo,
.leaflet-container .edit-redo,
.leaflet-container .edit-disable,
.umap-edit-enabled .edit-enable { .umap-edit-enabled .edit-enable {
display: none; display: none;
} }
.umap-edit-enabled .edit-save, .umap-edit-enabled .edit-save,
.umap-edit-enabled .edit-disable { .umap-edit-enabled .edit-disable,
.umap-edit-enabled.umap-is-dirty .edit-undo,
.umap-edit-enabled.umap-is-dirty .edit-redo {
display: inline-block; display: inline-block;
} }
.umap-is-dirty .edit-disable {
display: none;
}
.umap-caption-bar { .umap-caption-bar {
display: none; display: none;
} }

View file

@ -31,7 +31,7 @@ function has(obj) {
function onUpdate() { function onUpdate() {
isDirty = Boolean(_queue.size) isDirty = Boolean(_queue.size)
// document.body.classList.toggle('umap-is-dirty', isDirty) document.body.classList.toggle('umap-is-dirty', isDirty)
} }
export class ServerStored { export class ServerStored {

View file

@ -138,90 +138,69 @@ export class SyncEngine {
verb: 'batch', verb: 'batch',
operations: this._batch, operations: this._batch,
}) })
const operations = this._batch.map((stage) => stage.operation) const syncOperations = this._batch.map((operation) =>
this._send({ verb: 'batch', operations: operations, subject: 'batch' }) this.convertToSyncOperation(operation)
)
this._send({ verb: 'batch', operations: syncOperations, subject: 'batch' })
this._batch = null this._batch = null
} }
convertToSyncOperation(undoOperation) {
const syncOperation = { ...undoOperation, value: undoOperation.newValue }
delete syncOperation.oldValue
delete syncOperation.newValue
return syncOperation
}
upsert(subject, metadata, value, oldValue) { upsert(subject, metadata, value, oldValue) {
const operation = { const undoOperation = {
verb: 'upsert', verb: 'upsert',
subject, subject,
metadata, metadata,
value,
}
const stage = {
operation,
newValue: value, newValue: value,
oldValue: oldValue, oldValue: oldValue,
} }
if (this._batch) { if (this._batch) {
this._batch.push(stage) this._batch.push(undoOperation)
return return
} }
this._undoManager.add(stage) this._undoManager.add(undoOperation)
this._send(operation) const syncOperation = this.convertToSyncOperation(undoOperation)
this._send(syncOperation)
} }
update(subject, metadata, key, value, oldValue) { update(subject, metadata, key, value, oldValue) {
const operation = { const undoOperation = {
verb: 'update', verb: 'update',
subject, subject,
metadata, metadata,
key, key,
value,
}
const stage = {
operation,
oldValue: oldValue, oldValue: oldValue,
newValue: value, newValue: value,
} }
if (this._batch) { if (this._batch) {
this._batch.push(stage) this._batch.push(undoOperation)
return return
} }
this._undoManager.add(stage) this._undoManager.add(undoOperation)
this._send(operation) const syncOperation = this.convertToSyncOperation(undoOperation)
this._send(syncOperation)
} }
delete(subject, metadata, oldValue) { delete(subject, metadata, oldValue) {
const operation = { const undoOperation = {
verb: 'delete', verb: 'delete',
subject, subject,
metadata, metadata,
}
const stage = {
operation,
oldValue: oldValue, oldValue: oldValue,
} }
if (this._batch) { if (this._batch) {
this._batch.push(stage) this._batch.push(undoOperation)
return return
} }
this._undoManager.add(stage) this._undoManager.add(undoOperation)
this._send(operation) const syncOperation = this.convertToSyncOperation(undoOperation)
} this._send(syncOperation)
async save() {
const needSave = new Map()
for (const operation of this._operations.sorted()) {
if (operation.dirty) {
const updater = this._getUpdater(operation.subject)
const obj = updater.getStoredObject(operation.metadata)
if (!needSave.has(obj)) {
needSave.set(obj, [])
}
needSave.get(obj).push(operation)
}
}
for (const [obj, operations] of needSave.entries()) {
await obj.save()
for (const operation of operations) {
operation.dirty = false
}
}
this.saved()
this._undoManager.toggleState()
} }
saved() { saved() {
@ -234,8 +213,8 @@ export class SyncEngine {
} }
} }
_send(operation) { _send(inputMessage) {
const message = this._operations.addLocal(operation) const message = this._operations.addLocal(inputMessage)
if (this.offline) return if (this.offline) return
if (this.transport) { if (this.transport) {
@ -398,8 +377,9 @@ export class SyncEngine {
onSavedMessage({ sender, lastKnownHLC }) { onSavedMessage({ sender, lastKnownHLC }) {
debug(`received saved message from peer ${sender}`, lastKnownHLC) debug(`received saved message from peer ${sender}`, lastKnownHLC)
this._operations.saved(lastKnownHLC) if (lastKnownHLC === this._operations.getLastKnownHLC() && SaveManager.isDirty) {
this._undoManager.toggleState() SaveManager.clear()
}
} }
/** /**
@ -471,22 +451,16 @@ export class Operations {
this._operations = new Array() this._operations = new Array()
} }
saved(hlc) {
for (const operation of this.getOperationsBefore(hlc)) {
operation.dirty = false
}
}
/** /**
* Tick the clock and store the passed message in the operations list. * Tick the clock and store the passed message in the operations list.
* *
* @param {*} inputMessage * @param {*} inputMessage
* @returns {*} clock-aware message * @returns {*} clock-aware message
*/ */
addLocal(operation) { addLocal(inputMessage) {
operation.hlc = this._hlc.tick() const message = { ...inputMessage, hlc: this._hlc.tick() }
this._operations.push(operation) this._operations.push(message)
return operation return message
} }
/** /**
@ -544,11 +518,6 @@ export class Operations {
return this._operations.filter((op) => op.hlc > hlc) return this._operations.filter((op) => op.hlc > hlc)
} }
getOperationsBefore(hlc) {
if (!hlc) return this._operations
return this._operations.filter((op) => op.hlc <= hlc)
}
/** /**
* Returns the last known HLC value. * Returns the last known HLC value.
*/ */

View file

@ -14,53 +14,35 @@ export class UndoManager {
const redoButton = document.querySelector('.edit-redo') const redoButton = document.querySelector('.edit-redo')
if (undoButton) undoButton.disabled = !this._undoStack.length if (undoButton) undoButton.disabled = !this._undoStack.length
if (redoButton) redoButton.disabled = !this._redoStack.length if (redoButton) redoButton.disabled = !this._redoStack.length
const dirty = this.isDirty()
document.body.classList.toggle('umap-is-dirty', dirty)
for (const button of document.querySelectorAll('.disabled-on-dirty')) {
button.disabled = dirty
}
} }
isDirty() { add(operation) {
for (const stage of this._undoStack) { this._redoStack = []
if (stage.operation.dirty) return true this._undoStack.push(operation)
} this.toggleState()
for (const stage of this._redoStack) {
if (stage.operation.dirty) return true
}
return false
} }
add(stage) { cleanOperation(operation, redo) {
// FIXME make it more generic const syncOperation = Utils.CopyJSON(operation)
if (stage.operation.key !== '_referenceVersion') { delete syncOperation.oldValue
stage.operation.dirty = true delete syncOperation.newValue
this._redoStack = [] syncOperation.value = redo ? operation.newValue : operation.oldValue
this._undoStack.push(stage) return syncOperation
this.toggleState()
}
}
copyOperation(stage, redo) {
const operation = Utils.CopyJSON(stage.operation)
operation.value = redo ? stage.newValue : stage.oldValue
return operation
} }
undo(redo = false) { undo(redo = false) {
const fromStack = redo ? this._redoStack : this._undoStack const fromStack = redo ? this._redoStack : this._undoStack
const toStack = redo ? this._undoStack : this._redoStack const toStack = redo ? this._undoStack : this._redoStack
const stage = fromStack.pop() const operation = fromStack.pop()
if (!stage) return if (!operation) return
stage.operation.dirty = !stage.operation.dirty if (operation.verb === 'batch') {
if (stage.operation.verb === 'batch') { for (const op of operation.operations) {
for (const op of stage.operations) { this.applyOperation(this.cleanOperation(op, redo))
this.applyOperation(this.copyOperation(op, redo))
} }
} else { } else {
this.applyOperation(this.copyOperation(stage, redo)) this.applyOperation(this.cleanOperation(operation, redo))
} }
toStack.push(stage) toStack.push(operation)
this.toggleState() this.toggleState()
} }
@ -68,21 +50,21 @@ export class UndoManager {
this.undo(true) this.undo(true)
} }
applyOperation(operation) { applyOperation(syncOperation) {
const updater = this._getUpdater(operation.subject, operation.metadata) const updater = this._getUpdater(syncOperation.subject, syncOperation.metadata)
switch (operation.verb) { switch (syncOperation.verb) {
case 'update': case 'update':
updater.update(operation) updater.update(syncOperation)
this._syncEngine._send(operation) this._syncEngine._send(syncOperation)
break break
case 'delete': case 'delete':
case 'upsert': case 'upsert':
if (operation.value === null || operation.value === undefined) { if (syncOperation.value === null || syncOperation.value === undefined) {
updater.delete(operation) updater.delete(syncOperation)
} else { } else {
updater.upsert(operation) updater.upsert(syncOperation)
} }
this._syncEngine._send(operation) this._syncEngine._send(syncOperation)
break break
} }
} }

View file

@ -51,10 +51,6 @@ export class MapUpdater extends BaseUpdater {
this._umap.onPropertiesUpdated([key]) this._umap.onPropertiesUpdated([key])
this._umap.render([key]) this._umap.render([key])
} }
getStoredObject() {
return this._umap
}
} }
export class DataLayerUpdater extends BaseUpdater { export class DataLayerUpdater extends BaseUpdater {
@ -95,10 +91,6 @@ export class DataLayerUpdater extends BaseUpdater {
datalayer.commitDelete() datalayer.commitDelete()
} }
} }
getStoredObject(metadata) {
return this.getDataLayerFromID(metadata.id)
}
} }
export class FeatureUpdater extends BaseUpdater { export class FeatureUpdater extends BaseUpdater {
@ -109,11 +101,14 @@ export class FeatureUpdater extends BaseUpdater {
// Create or update an object at a specific position // Create or update an object at a specific position
upsert({ metadata, value }) { upsert({ metadata, value }) {
console.log('updater.upsert for', metadata, value)
const { id, layerId } = metadata const { id, layerId } = metadata
const datalayer = this.getDataLayerFromID(layerId) const datalayer = this.getDataLayerFromID(layerId)
const feature = this.getFeatureFromMetadata(metadata) const feature = this.getFeatureFromMetadata(metadata)
console.log('feature', feature)
if (feature) { if (feature) {
console.log('changing feature geometry')
feature.geometry = value.geometry feature.geometry = value.geometry
} else { } else {
datalayer.makeFeature(value, false) datalayer.makeFeature(value, false)
@ -144,8 +139,4 @@ export class FeatureUpdater extends BaseUpdater {
const feature = this.getFeatureFromMetadata(metadata) const feature = this.getFeatureFromMetadata(metadata)
if (feature) feature.del(false) if (feature) feature.del(false)
} }
getStoredObject(metadata) {
return this.getDataLayerFromID(metadata.layerId)
}
} }

View file

@ -30,7 +30,7 @@ const TOP_BAR_TEMPLATE = `
<i class="icon icon-16 icon-redo"></i> <i class="icon icon-16 icon-redo"></i>
<span class="">${translate('Redo')}</span> <span class="">${translate('Redo')}</span>
</button> </button>
<button class="edit-disable round disabled-on-dirty" type="button" data-ref="view"> <button class="edit-disable round" type="button" data-ref="view">
<i class="icon icon-16 icon-eye"></i> <i class="icon icon-16 icon-eye"></i>
<span class="">${translate('View')}</span> <span class="">${translate('View')}</span>
</button> </button>
@ -159,7 +159,7 @@ export class TopBar extends WithTemplate {
redraw() { redraw() {
const syncEnabled = this._umap.getProperty('syncEnabled') const syncEnabled = this._umap.getProperty('syncEnabled')
this.elements.peers.hidden = !syncEnabled this.elements.peers.hidden = !syncEnabled
this.elements.view.disabled = this._umap.sync._undoManager.isDirty() // this.elements.cancel.hidden = syncEnabled
this.elements.saveLabel.hidden = this._umap.permissions.isDraft() this.elements.saveLabel.hidden = this._umap.permissions.isDraft()
this.elements.saveDraftLabel.hidden = !this._umap.permissions.isDraft() this.elements.saveDraftLabel.hidden = !this._umap.permissions.isDraft()
} }

View file

@ -670,10 +670,10 @@ export default class Umap extends ServerStored {
} }
async saveAll() { async saveAll() {
// if (!SAVEMANAGER.isDirty) return if (!SAVEMANAGER.isDirty) return
if (this._defaultExtent) this._setCenterAndZoom() if (this._defaultExtent) this._setCenterAndZoom()
this.backup() this.backup()
await this.sync.save() await SAVEMANAGER.save()
// Do a blind render for now, as we are not sure what could // Do a blind render for now, as we are not sure what could
// have changed, we'll be more subtil when we'll remove the // have changed, we'll be more subtil when we'll remove the
// save action // save action
@ -684,6 +684,7 @@ export default class Umap extends ServerStored {
Alert.success(translate('Map has been saved!')) Alert.success(translate('Map has been saved!'))
}) })
} }
this.sync.saved()
this.fire('saved') this.fire('saved')
} }