mirror of
https://github.com/umap-project/umap.git
synced 2025-05-12 00:51:48 +02:00
Compare commits
No commits in common. "591cfce81fb44c43a7ad222b4699fe2d13380b7e" and "0859254623871681aab919a51d56be8f13ca6c2e" have entirely different histories.
591cfce81f
...
0859254623
7 changed files with 80 additions and 129 deletions
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
|
@ -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')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue