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",
|
"georsstogeojson": "^0.2.0",
|
||||||
"jsdom": "^24.0.0",
|
"jsdom": "^24.0.0",
|
||||||
"leaflet": "1.9.4",
|
"leaflet": "1.9.4",
|
||||||
"leaflet-editable": "^1.3.0",
|
"leaflet-editable": "^1.3.1",
|
||||||
"leaflet-editinosm": "0.2.3",
|
"leaflet-editinosm": "0.2.3",
|
||||||
"leaflet-fullscreen": "1.0.2",
|
"leaflet-fullscreen": "1.0.2",
|
||||||
"leaflet-hash": "0.2.1",
|
"leaflet-hash": "0.2.1",
|
||||||
|
|
|
@ -36,7 +36,7 @@ dependencies = [
|
||||||
"psycopg==3.2.4",
|
"psycopg==3.2.4",
|
||||||
"requests==2.32.3",
|
"requests==2.32.3",
|
||||||
"rcssmin==1.2.1",
|
"rcssmin==1.2.1",
|
||||||
"rjsmin==1.2.3",
|
"rjsmin==1.2.4",
|
||||||
"social-auth-core==4.5.4",
|
"social-auth-core==4.5.4",
|
||||||
"social-auth-app-django==5.4.2",
|
"social-auth-app-django==5.4.2",
|
||||||
]
|
]
|
||||||
|
|
|
@ -335,15 +335,15 @@ class Feature {
|
||||||
// Variables mode.
|
// Variables mode.
|
||||||
if (labelKey) {
|
if (labelKey) {
|
||||||
if (Utils.hasVar(labelKey)) {
|
if (Utils.hasVar(labelKey)) {
|
||||||
return Utils.greedyTemplate(labelKey, this.extendedProperties())
|
return Utils.greedyTemplate(labelKey, this.extendedProperties()).trim()
|
||||||
}
|
}
|
||||||
keys.unshift(labelKey)
|
keys.unshift(labelKey)
|
||||||
}
|
}
|
||||||
for (const key of keys) {
|
for (const key of keys) {
|
||||||
const value = this.properties[key]
|
const value = this.properties[key]
|
||||||
if (value) return value
|
if (value) return value.trim()
|
||||||
}
|
}
|
||||||
return this.datalayer.getName()
|
return this.datalayer.getName().trim()
|
||||||
}
|
}
|
||||||
|
|
||||||
hasPopupFooter() {
|
hasPopupFooter() {
|
||||||
|
@ -640,6 +640,12 @@ class Feature {
|
||||||
window.open(permalink)
|
window.open(permalink)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
items.push({
|
||||||
|
label: translate('Layer permalink'),
|
||||||
|
action: () => {
|
||||||
|
window.open(this.datalayer.getPermalink())
|
||||||
|
},
|
||||||
|
})
|
||||||
}
|
}
|
||||||
items.push({
|
items.push({
|
||||||
label: translate('Copy as GeoJSON'),
|
label: translate('Copy as GeoJSON'),
|
||||||
|
|
|
@ -1174,6 +1174,12 @@ export class DataLayer extends ServerStored {
|
||||||
return this.options.name || translate('Untitled layer')
|
return this.options.name || translate('Untitled layer')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getPermalink() {
|
||||||
|
return `${Utils.getBaseUrl()}?${Utils.buildQueryString({ datalayers: this.id })}${
|
||||||
|
window.location.hash
|
||||||
|
}`
|
||||||
|
}
|
||||||
|
|
||||||
tableEdit() {
|
tableEdit() {
|
||||||
if (!this.isVisible()) return
|
if (!this.isVisible()) return
|
||||||
const editor = new TableEditor(this._umap, this, this._leafletMap)
|
const editor = new TableEditor(this._umap, this, this._leafletMap)
|
||||||
|
|
|
@ -60,8 +60,7 @@ const FeatureMixin = {
|
||||||
if (event.originalEvent.ctrlKey || event.originalEvent.metaKey) {
|
if (event.originalEvent.ctrlKey || event.originalEvent.metaKey) {
|
||||||
this.feature.datalayer.edit(event)
|
this.feature.datalayer.edit(event)
|
||||||
} else {
|
} else {
|
||||||
if (this.feature._toggleEditing) this.feature._toggleEditing(event)
|
this.feature.toggleEditing(event)
|
||||||
else this.feature.edit(event)
|
|
||||||
}
|
}
|
||||||
} else if (!this._map.editTools?.drawing()) {
|
} else if (!this._map.editTools?.drawing()) {
|
||||||
this._map._umap.editContextmenu.open(
|
this._map._umap.editContextmenu.open(
|
||||||
|
|
|
@ -66,7 +66,12 @@ export class SyncEngine {
|
||||||
this.peerId = Utils.generateId()
|
this.peerId = Utils.generateId()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get isOpen() {
|
||||||
|
return this.transport?.isOpen
|
||||||
|
}
|
||||||
|
|
||||||
async authenticate() {
|
async authenticate() {
|
||||||
|
if (this.isOpen) return
|
||||||
const websocketTokenURI = this._umap.urls.get('map_websocket_auth_token', {
|
const websocketTokenURI = this._umap.urls.get('map_websocket_auth_token', {
|
||||||
map_id: this._umap.id,
|
map_id: this._umap.id,
|
||||||
})
|
})
|
||||||
|
@ -198,6 +203,7 @@ export class SyncEngine {
|
||||||
*/
|
*/
|
||||||
onOperationMessage(payload) {
|
onOperationMessage(payload) {
|
||||||
if (payload.sender === this.peerId) return
|
if (payload.sender === this.peerId) return
|
||||||
|
debug('received operation', payload)
|
||||||
this._operations.storeRemoteOperations([payload])
|
this._operations.storeRemoteOperations([payload])
|
||||||
this._applyOperation(payload)
|
this._applyOperation(payload)
|
||||||
}
|
}
|
||||||
|
@ -261,7 +267,7 @@ export class SyncEngine {
|
||||||
* @param {*} operations The list of (encoded operations)
|
* @param {*} operations The list of (encoded operations)
|
||||||
*/
|
*/
|
||||||
onListOperationsResponse({ sender, message }) {
|
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
|
if (message.operations.length === 0) return
|
||||||
|
|
||||||
|
@ -502,11 +508,7 @@ export class Operations {
|
||||||
* @return {bool} true if the two operations share the same context.
|
* @return {bool} true if the two operations share the same context.
|
||||||
*/
|
*/
|
||||||
static haveSameContext(local, remote) {
|
static haveSameContext(local, remote) {
|
||||||
const shouldCheckKey =
|
const shouldCheckKey = local.key !== undefined && remote.key !== undefined
|
||||||
local.hasOwnProperty('key') &&
|
|
||||||
remote.hasOwnProperty('key') &&
|
|
||||||
typeof local.key !== 'undefined' &&
|
|
||||||
typeof remote.key !== 'undefined'
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
Utils.deepEqual(local.subject, remote.subject) &&
|
Utils.deepEqual(local.subject, remote.subject) &&
|
||||||
|
|
|
@ -54,7 +54,11 @@ export class MapUpdater extends BaseUpdater {
|
||||||
export class DataLayerUpdater extends BaseUpdater {
|
export class DataLayerUpdater extends BaseUpdater {
|
||||||
upsert({ value }) {
|
upsert({ value }) {
|
||||||
// Upsert only happens when a new datalayer is created.
|
// 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 }) {
|
update({ key, metadata, value }) {
|
||||||
|
|
|
@ -76,4 +76,8 @@ export class WebSocketTransport {
|
||||||
this.receiver.closeRequested = true
|
this.receiver.closeRequested = true
|
||||||
this.websocket.close()
|
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()) {
|
if (!event.layer.feature.hasGeom()) {
|
||||||
event.layer.feature.del()
|
event.layer.feature.del()
|
||||||
} else {
|
} else {
|
||||||
|
event.layer.feature.onCommit()
|
||||||
event.layer.feature.edit()
|
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
|
// 🍂event editable:vertex:deleted: VertexEvent
|
||||||
// Fired after a vertex has been deleted by user.
|
// Fired after a vertex has been deleted by user.
|
||||||
this.fireAndForward('editable:vertex:deleted', e)
|
this.fireAndForward('editable:vertex:deleted', e)
|
||||||
|
this.onEdited()
|
||||||
},
|
},
|
||||||
|
|
||||||
onVertexMarkerCtrlClick: function (e) {
|
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(markersA).to_have_count(1)
|
||||||
expect(markersB).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
|
# Save and quit edit mode again
|
||||||
with peerA.expect_response(re.compile("./datalayer/create/.*")):
|
with peerA.expect_response(re.compile("./datalayer/create/.*")):
|
||||||
peerA.get_by_role("button", name="Save").click()
|
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)
|
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")
|
@pytest.mark.xdist_group(name="websockets")
|
||||||
def test_should_sync_saved_status(new_page, asgi_live_server, tilelayer):
|
def test_should_sync_saved_status(new_page, asgi_live_server, tilelayer):
|
||||||
map = MapFactory(name="sync", edit_status=Map.ANONYMOUS)
|
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
|
# Peer A should not be in dirty state
|
||||||
expect(peerA.locator("body")).not_to_have_class(re.compile(".*umap-is-dirty.*"))
|
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