mirror of
https://github.com/umap-project/umap.git
synced 2025-05-17 19:21:50 +02:00
Compare commits
13 commits
fc6ea191cc
...
48daa0a77f
Author | SHA1 | Date | |
---|---|---|---|
![]() |
48daa0a77f | ||
![]() |
ba20f5791c | ||
![]() |
0fe2103b71 | ||
![]() |
55d505e7d8 | ||
![]() |
14e90a1a0f | ||
![]() |
e919c5f168 | ||
![]() |
b1076dcb7b | ||
![]() |
69ca89a6ba | ||
![]() |
df0faa75aa | ||
![]() |
b400ade44b | ||
![]() |
9858fc2190 | ||
![]() |
5ddd973eae | ||
![]() |
d342336930 |
13 changed files with 243 additions and 381 deletions
|
@ -45,7 +45,7 @@
|
|||
"georsstogeojson": "^0.2.0",
|
||||
"jsdom": "^24.0.0",
|
||||
"leaflet": "1.9.4",
|
||||
"leaflet-editable": "^1.3.0",
|
||||
"leaflet-editable": "^1.3.1",
|
||||
"leaflet-editinosm": "0.2.3",
|
||||
"leaflet-fullscreen": "1.0.2",
|
||||
"leaflet-hash": "0.2.1",
|
||||
|
|
|
@ -36,7 +36,7 @@ dependencies = [
|
|||
"psycopg==3.2.4",
|
||||
"requests==2.32.3",
|
||||
"rcssmin==1.2.1",
|
||||
"rjsmin==1.2.3",
|
||||
"rjsmin==1.2.4",
|
||||
"social-auth-core==4.5.4",
|
||||
"social-auth-app-django==5.4.2",
|
||||
]
|
||||
|
|
|
@ -335,15 +335,15 @@ class Feature {
|
|||
// Variables mode.
|
||||
if (labelKey) {
|
||||
if (Utils.hasVar(labelKey)) {
|
||||
return Utils.greedyTemplate(labelKey, this.extendedProperties())
|
||||
return Utils.greedyTemplate(labelKey, this.extendedProperties()).trim()
|
||||
}
|
||||
keys.unshift(labelKey)
|
||||
}
|
||||
for (const key of keys) {
|
||||
const value = this.properties[key]
|
||||
if (value) return value
|
||||
if (value) return value.trim()
|
||||
}
|
||||
return this.datalayer.getName()
|
||||
return this.datalayer.getName().trim()
|
||||
}
|
||||
|
||||
hasPopupFooter() {
|
||||
|
@ -640,6 +640,12 @@ class Feature {
|
|||
window.open(permalink)
|
||||
},
|
||||
})
|
||||
items.push({
|
||||
label: translate('Layer permalink'),
|
||||
action: () => {
|
||||
window.open(this.datalayer.getPermalink())
|
||||
},
|
||||
})
|
||||
}
|
||||
items.push({
|
||||
label: translate('Copy as GeoJSON'),
|
||||
|
|
|
@ -1174,6 +1174,12 @@ export class DataLayer extends ServerStored {
|
|||
return this.options.name || translate('Untitled layer')
|
||||
}
|
||||
|
||||
getPermalink() {
|
||||
return `${Utils.getBaseUrl()}?${Utils.buildQueryString({ datalayers: this.id })}${
|
||||
window.location.hash
|
||||
}`
|
||||
}
|
||||
|
||||
tableEdit() {
|
||||
if (!this.isVisible()) return
|
||||
const editor = new TableEditor(this._umap, this, this._leafletMap)
|
||||
|
|
|
@ -60,8 +60,7 @@ const FeatureMixin = {
|
|||
if (event.originalEvent.ctrlKey || event.originalEvent.metaKey) {
|
||||
this.feature.datalayer.edit(event)
|
||||
} else {
|
||||
if (this.feature._toggleEditing) this.feature._toggleEditing(event)
|
||||
else this.feature.edit(event)
|
||||
this.feature.toggleEditing(event)
|
||||
}
|
||||
} else if (!this._map.editTools?.drawing()) {
|
||||
this._map._umap.editContextmenu.open(
|
||||
|
|
|
@ -66,7 +66,12 @@ export class SyncEngine {
|
|||
this.peerId = Utils.generateId()
|
||||
}
|
||||
|
||||
get isOpen() {
|
||||
return this.transport?.isOpen
|
||||
}
|
||||
|
||||
async authenticate() {
|
||||
if (this.isOpen) return
|
||||
const websocketTokenURI = this._umap.urls.get('map_websocket_auth_token', {
|
||||
map_id: this._umap.id,
|
||||
})
|
||||
|
@ -198,6 +203,7 @@ export class SyncEngine {
|
|||
*/
|
||||
onOperationMessage(payload) {
|
||||
if (payload.sender === this.peerId) return
|
||||
debug('received operation', payload)
|
||||
this._operations.storeRemoteOperations([payload])
|
||||
this._applyOperation(payload)
|
||||
}
|
||||
|
@ -261,7 +267,7 @@ export class SyncEngine {
|
|||
* @param {*} operations The list of (encoded operations)
|
||||
*/
|
||||
onListOperationsResponse({ sender, message }) {
|
||||
debug(`received operations from peer ${sender}`, message.operations)
|
||||
debug(`received operations list from peer ${sender}`, message.operations)
|
||||
|
||||
if (message.operations.length === 0) return
|
||||
|
||||
|
@ -502,11 +508,7 @@ export class Operations {
|
|||
* @return {bool} true if the two operations share the same context.
|
||||
*/
|
||||
static haveSameContext(local, remote) {
|
||||
const shouldCheckKey =
|
||||
local.hasOwnProperty('key') &&
|
||||
remote.hasOwnProperty('key') &&
|
||||
typeof local.key !== 'undefined' &&
|
||||
typeof remote.key !== 'undefined'
|
||||
const shouldCheckKey = local.key !== undefined && remote.key !== undefined
|
||||
|
||||
return (
|
||||
Utils.deepEqual(local.subject, remote.subject) &&
|
||||
|
|
|
@ -54,7 +54,11 @@ export class MapUpdater extends BaseUpdater {
|
|||
export class DataLayerUpdater extends BaseUpdater {
|
||||
upsert({ value }) {
|
||||
// Upsert only happens when a new datalayer is created.
|
||||
const datalayer = this._umap.createDataLayer(value, false)
|
||||
try {
|
||||
this.getDataLayerFromID(value.id)
|
||||
} catch {
|
||||
this._umap.createDataLayer(value, false)
|
||||
}
|
||||
}
|
||||
|
||||
update({ key, metadata, value }) {
|
||||
|
|
|
@ -76,4 +76,8 @@ export class WebSocketTransport {
|
|||
this.receiver.closeRequested = true
|
||||
this.websocket.close()
|
||||
}
|
||||
|
||||
get isOpen() {
|
||||
return this.websocket?.readyState === WebSocket.OPEN
|
||||
}
|
||||
}
|
||||
|
|
|
@ -699,6 +699,7 @@ U.Editable = L.Editable.extend({
|
|||
if (!event.layer.feature.hasGeom()) {
|
||||
event.layer.feature.del()
|
||||
} else {
|
||||
event.layer.feature.onCommit()
|
||||
event.layer.feature.edit()
|
||||
}
|
||||
})
|
||||
|
|
516
umap/static/umap/vendors/dompurify/purify.es.js
vendored
516
umap/static/umap/vendors/dompurify/purify.es.js
vendored
File diff suppressed because it is too large
Load diff
File diff suppressed because one or more lines are too long
|
@ -1248,6 +1248,7 @@
|
|||
// 🍂event editable:vertex:deleted: VertexEvent
|
||||
// Fired after a vertex has been deleted by user.
|
||||
this.fireAndForward('editable:vertex:deleted', e)
|
||||
this.onEdited()
|
||||
},
|
||||
|
||||
onVertexMarkerCtrlClick: function (e) {
|
||||
|
|
|
@ -535,6 +535,11 @@ def test_create_and_sync_map(new_page, asgi_live_server, tilelayer, login, user)
|
|||
expect(markersA).to_have_count(1)
|
||||
expect(markersB).to_have_count(1)
|
||||
|
||||
# Make sure only one layer has been created on peer B
|
||||
peerB.get_by_role("button", name="Open browser").click()
|
||||
expect(peerB.locator("h5").get_by_text("Layer 1")).to_be_visible()
|
||||
peerB.get_by_role("button", name="Close").click()
|
||||
|
||||
# Save and quit edit mode again
|
||||
with peerA.expect_response(re.compile("./datalayer/create/.*")):
|
||||
peerA.get_by_role("button", name="Save").click()
|
||||
|
@ -563,6 +568,34 @@ def test_create_and_sync_map(new_page, asgi_live_server, tilelayer, login, user)
|
|||
expect(markersB).to_have_count(2)
|
||||
|
||||
|
||||
@pytest.mark.xdist_group(name="websockets")
|
||||
def test_saved_datalayer_are_not_duplicated(new_page, asgi_live_server, tilelayer):
|
||||
map = MapFactory(name="sync", edit_status=Map.ANONYMOUS)
|
||||
map.settings["properties"]["syncEnabled"] = True
|
||||
map.save()
|
||||
|
||||
# Create one tab
|
||||
peerA = new_page("Page A")
|
||||
peerA.goto(f"{asgi_live_server.url}{map.get_absolute_url()}?edit")
|
||||
# Create a new datalayer
|
||||
peerA.get_by_title("Manage layers").click()
|
||||
peerA.get_by_title("Add a layer").click()
|
||||
peerA.locator("#map").click(position={"x": 220, "y": 220})
|
||||
# Save layer to the server, so now the datalayer exist on the server AND
|
||||
# is still in the live operations of peer A
|
||||
with peerA.expect_response(re.compile(".*/datalayer/create/.*")):
|
||||
peerA.get_by_role("button", name="Save").click()
|
||||
|
||||
# Now load the map from another tab
|
||||
peerB = new_page("Page B")
|
||||
peerB.goto(peerA.url)
|
||||
peerB.get_by_role("button", name="Open browser").click()
|
||||
expect(peerB.get_by_text("Layer 1")).to_be_visible()
|
||||
peerB.get_by_role("button", name="Edit").click()
|
||||
peerA.wait_for_timeout(300) # Let the synchro roll on.
|
||||
expect(peerB.get_by_text("Layer 1")).to_be_visible()
|
||||
|
||||
|
||||
@pytest.mark.xdist_group(name="websockets")
|
||||
def test_should_sync_saved_status(new_page, asgi_live_server, tilelayer):
|
||||
map = MapFactory(name="sync", edit_status=Map.ANONYMOUS)
|
||||
|
@ -604,3 +637,25 @@ def test_should_sync_saved_status(new_page, asgi_live_server, tilelayer):
|
|||
|
||||
# Peer A should not be in dirty state
|
||||
expect(peerA.locator("body")).not_to_have_class(re.compile(".*umap-is-dirty.*"))
|
||||
|
||||
|
||||
@pytest.mark.xdist_group(name="websockets")
|
||||
def test_should_sync_line_on_escape(new_page, asgi_live_server, tilelayer):
|
||||
map = MapFactory(name="sync", edit_status=Map.ANONYMOUS)
|
||||
map.settings["properties"]["syncEnabled"] = True
|
||||
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")
|
||||
|
||||
# Create a new marker from peerA
|
||||
peerA.get_by_title("Draw a polyline").click()
|
||||
peerA.locator("#map").click(position={"x": 220, "y": 220})
|
||||
peerA.locator("#map").click(position={"x": 200, "y": 200})
|
||||
peerA.locator("body").press("Escape")
|
||||
|
||||
expect(peerA.locator("path")).to_have_count(1)
|
||||
expect(peerB.locator("path")).to_have_count(1)
|
||||
|
|
Loading…
Reference in a new issue