umap-project 2.8.2__py3-none-any.whl → 2.9.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of umap-project might be problematic. Click here for more details.
- umap/__init__.py +1 -1
- umap/admin.py +15 -2
- umap/asgi.py +12 -7
- umap/context_processors.py +1 -0
- umap/locale/br/LC_MESSAGES/django.mo +0 -0
- umap/locale/br/LC_MESSAGES/django.po +111 -67
- umap/locale/cs_CZ/LC_MESSAGES/django.mo +0 -0
- umap/locale/cs_CZ/LC_MESSAGES/django.po +110 -66
- umap/locale/el/LC_MESSAGES/django.mo +0 -0
- umap/locale/el/LC_MESSAGES/django.po +129 -85
- umap/locale/en/LC_MESSAGES/django.po +103 -60
- umap/locale/es/LC_MESSAGES/django.mo +0 -0
- umap/locale/es/LC_MESSAGES/django.po +114 -69
- umap/locale/fr/LC_MESSAGES/django.mo +0 -0
- umap/locale/fr/LC_MESSAGES/django.po +105 -61
- umap/locale/gl/LC_MESSAGES/django.mo +0 -0
- umap/locale/gl/LC_MESSAGES/django.po +216 -171
- umap/locale/it/LC_MESSAGES/django.mo +0 -0
- umap/locale/it/LC_MESSAGES/django.po +142 -98
- umap/locale/nl/LC_MESSAGES/django.mo +0 -0
- umap/locale/nl/LC_MESSAGES/django.po +196 -151
- umap/locale/pt/LC_MESSAGES/django.mo +0 -0
- umap/locale/pt/LC_MESSAGES/django.po +115 -71
- umap/locale/zh_TW/LC_MESSAGES/django.mo +0 -0
- umap/locale/zh_TW/LC_MESSAGES/django.po +109 -65
- umap/management/commands/empty_trash.py +12 -1
- umap/migrations/0026_datalayer_modified_at_datalayer_share_status.py +26 -0
- umap/models.py +43 -13
- umap/settings/base.py +5 -2
- umap/static/umap/base.css +5 -2
- umap/static/umap/content.css +2 -22
- umap/static/umap/css/bar.css +39 -10
- umap/static/umap/css/contextmenu.css +14 -2
- umap/static/umap/css/form.css +33 -39
- umap/static/umap/css/icon.css +47 -5
- umap/static/umap/css/panel.css +20 -2
- umap/static/umap/css/popup.css +0 -1
- umap/static/umap/css/tooltip.css +33 -31
- umap/static/umap/img/16-white.svg +5 -3
- umap/static/umap/img/16.svg +1 -1
- umap/static/umap/img/24-white.svg +17 -16
- umap/static/umap/img/24.svg +29 -18
- umap/static/umap/img/providers/bitbucket.png +0 -0
- umap/static/umap/img/providers/github.png +0 -0
- umap/static/umap/img/providers/keycloak.png +0 -0
- umap/static/umap/img/providers/openstreetmap-oauth2.png +0 -0
- umap/static/umap/img/providers/twitter-oauth2.png +0 -0
- umap/static/umap/img/source/16-white.svg +6 -4
- umap/static/umap/img/source/16.svg +1 -1
- umap/static/umap/img/source/24-white.svg +20 -18
- umap/static/umap/img/source/24.svg +30 -19
- umap/static/umap/js/components/alerts/alert.js +4 -1
- umap/static/umap/js/modules/browser.js +8 -8
- umap/static/umap/js/modules/caption.js +30 -7
- umap/static/umap/js/modules/data/features.js +101 -56
- umap/static/umap/js/modules/data/layer.js +108 -83
- umap/static/umap/js/modules/form/builder.js +242 -0
- umap/static/umap/js/modules/form/fields.js +1346 -0
- umap/static/umap/js/modules/formatter.js +9 -8
- umap/static/umap/js/modules/help.js +20 -24
- umap/static/umap/js/modules/importer.js +6 -3
- umap/static/umap/js/modules/permissions.js +11 -6
- umap/static/umap/js/modules/rendering/icon.js +5 -1
- umap/static/umap/js/modules/rendering/layers/classified.js +12 -8
- umap/static/umap/js/modules/rendering/layers/cluster.js +11 -1
- umap/static/umap/js/modules/rendering/map.js +1 -23
- umap/static/umap/js/modules/rendering/ui.js +20 -38
- umap/static/umap/js/modules/rules.js +3 -2
- umap/static/umap/js/modules/saving.js +5 -0
- umap/static/umap/js/modules/schema.js +8 -6
- umap/static/umap/js/modules/share.js +3 -3
- umap/static/umap/js/modules/sync/engine.js +56 -26
- umap/static/umap/js/modules/sync/updaters.js +15 -6
- umap/static/umap/js/modules/sync/websocket.js +50 -37
- umap/static/umap/js/modules/tableeditor.js +3 -2
- umap/static/umap/js/modules/ui/bar.js +101 -9
- umap/static/umap/js/modules/ui/base.js +7 -24
- umap/static/umap/js/modules/ui/contextmenu.js +9 -2
- umap/static/umap/js/modules/ui/panel.js +5 -1
- umap/static/umap/js/modules/ui/tooltip.js +19 -11
- umap/static/umap/js/modules/umap.js +121 -68
- umap/static/umap/js/modules/utils.js +196 -12
- umap/static/umap/js/umap.controls.js +11 -353
- umap/static/umap/locale/am_ET.js +17 -5
- umap/static/umap/locale/am_ET.json +17 -5
- umap/static/umap/locale/ar.js +17 -5
- umap/static/umap/locale/ar.json +17 -5
- umap/static/umap/locale/ast.js +17 -5
- umap/static/umap/locale/ast.json +17 -5
- umap/static/umap/locale/bg.js +17 -5
- umap/static/umap/locale/bg.json +17 -5
- umap/static/umap/locale/br.js +33 -20
- umap/static/umap/locale/br.json +33 -20
- umap/static/umap/locale/ca.js +17 -5
- umap/static/umap/locale/ca.json +17 -5
- umap/static/umap/locale/cs_CZ.js +15 -5
- umap/static/umap/locale/cs_CZ.json +15 -5
- umap/static/umap/locale/da.js +17 -5
- umap/static/umap/locale/da.json +17 -5
- umap/static/umap/locale/de.js +17 -5
- umap/static/umap/locale/de.json +17 -5
- umap/static/umap/locale/el.js +63 -51
- umap/static/umap/locale/el.json +63 -51
- umap/static/umap/locale/en.js +15 -5
- umap/static/umap/locale/en.json +15 -5
- umap/static/umap/locale/en_US.json +17 -5
- umap/static/umap/locale/es.js +25 -13
- umap/static/umap/locale/es.json +25 -13
- umap/static/umap/locale/et.js +17 -5
- umap/static/umap/locale/et.json +17 -5
- umap/static/umap/locale/eu.js +17 -5
- umap/static/umap/locale/eu.json +17 -5
- umap/static/umap/locale/fa_IR.js +17 -5
- umap/static/umap/locale/fa_IR.json +17 -5
- umap/static/umap/locale/fi.js +17 -5
- umap/static/umap/locale/fi.json +17 -5
- umap/static/umap/locale/fr.js +16 -6
- umap/static/umap/locale/fr.json +16 -6
- umap/static/umap/locale/gl.js +357 -345
- umap/static/umap/locale/gl.json +357 -345
- umap/static/umap/locale/he.js +17 -5
- umap/static/umap/locale/he.json +17 -5
- umap/static/umap/locale/hr.js +17 -5
- umap/static/umap/locale/hr.json +17 -5
- umap/static/umap/locale/hu.js +14 -5
- umap/static/umap/locale/hu.json +14 -5
- umap/static/umap/locale/id.js +17 -5
- umap/static/umap/locale/id.json +17 -5
- umap/static/umap/locale/is.js +17 -5
- umap/static/umap/locale/is.json +17 -5
- umap/static/umap/locale/it.js +125 -113
- umap/static/umap/locale/it.json +125 -113
- umap/static/umap/locale/ja.js +17 -5
- umap/static/umap/locale/ja.json +17 -5
- umap/static/umap/locale/ko.js +17 -5
- umap/static/umap/locale/ko.json +17 -5
- umap/static/umap/locale/lt.js +17 -5
- umap/static/umap/locale/lt.json +17 -5
- umap/static/umap/locale/ms.js +17 -5
- umap/static/umap/locale/ms.json +17 -5
- umap/static/umap/locale/nl.js +132 -119
- umap/static/umap/locale/nl.json +132 -119
- umap/static/umap/locale/no.js +17 -5
- umap/static/umap/locale/no.json +17 -5
- umap/static/umap/locale/pl.js +17 -5
- umap/static/umap/locale/pl.json +17 -5
- umap/static/umap/locale/pl_PL.json +17 -5
- umap/static/umap/locale/pt.js +38 -25
- umap/static/umap/locale/pt.json +38 -25
- umap/static/umap/locale/pt_BR.js +17 -5
- umap/static/umap/locale/pt_BR.json +17 -5
- umap/static/umap/locale/pt_PT.js +17 -5
- umap/static/umap/locale/pt_PT.json +17 -5
- umap/static/umap/locale/ro.js +17 -5
- umap/static/umap/locale/ro.json +17 -5
- umap/static/umap/locale/ru.js +17 -5
- umap/static/umap/locale/ru.json +17 -5
- umap/static/umap/locale/sk_SK.js +17 -5
- umap/static/umap/locale/sk_SK.json +17 -5
- umap/static/umap/locale/sl.js +17 -5
- umap/static/umap/locale/sl.json +17 -5
- umap/static/umap/locale/sr.js +17 -5
- umap/static/umap/locale/sr.json +17 -5
- umap/static/umap/locale/sv.js +17 -5
- umap/static/umap/locale/sv.json +17 -5
- umap/static/umap/locale/th_TH.js +17 -5
- umap/static/umap/locale/th_TH.json +17 -5
- umap/static/umap/locale/tr.js +17 -5
- umap/static/umap/locale/tr.json +17 -5
- umap/static/umap/locale/uk_UA.js +17 -5
- umap/static/umap/locale/uk_UA.json +17 -5
- umap/static/umap/locale/vi.js +17 -5
- umap/static/umap/locale/vi.json +17 -5
- umap/static/umap/locale/vi_VN.json +17 -5
- umap/static/umap/locale/zh.js +17 -5
- umap/static/umap/locale/zh.json +17 -5
- umap/static/umap/locale/zh_CN.json +17 -5
- umap/static/umap/locale/zh_TW.Big5.json +17 -5
- umap/static/umap/locale/zh_TW.js +15 -5
- umap/static/umap/locale/zh_TW.json +15 -5
- umap/static/umap/map.css +29 -76
- umap/static/umap/nav.css +6 -3
- umap/static/umap/unittests/utils.js +14 -0
- umap/static/umap/vars.css +3 -0
- umap/static/umap/vendors/dompurify/purify.es.js +138 -354
- umap/static/umap/vendors/dompurify/purify.es.mjs.map +1 -1
- umap/static/umap/vendors/editable/Leaflet.Editable.js +1 -0
- umap/sync/__init__.py +0 -0
- umap/sync/app.py +187 -0
- umap/sync/payloads.py +56 -0
- umap/templates/auth/user_detail.html +4 -0
- umap/templates/auth/user_form.html +9 -6
- umap/templates/auth/user_stars.html +4 -0
- umap/templates/base.html +1 -1
- umap/templates/registration/login.html +2 -5
- umap/templates/umap/about.html +5 -0
- umap/templates/umap/about_summary.html +2 -2
- umap/templates/umap/components/provider.html +8 -0
- umap/templates/umap/content_footer.html +1 -1
- umap/templates/umap/css.html +0 -2
- umap/templates/umap/js.html +0 -4
- umap/templates/umap/map_detail.html +1 -1
- umap/templates/umap/password_change.html +4 -0
- umap/templates/umap/password_change_done.html +4 -0
- umap/templates/umap/search.html +4 -0
- umap/templates/umap/search_bar.html +1 -0
- umap/templates/umap/team_confirm_delete.html +4 -0
- umap/templates/umap/team_detail.html +4 -0
- umap/templates/umap/team_form.html +4 -0
- umap/templates/umap/user_dashboard.html +1 -1
- umap/templates/umap/user_teams.html +4 -0
- umap/tests/base.py +3 -1
- umap/tests/integration/conftest.py +16 -23
- umap/tests/integration/test_anonymous_owned_map.py +2 -2
- umap/tests/integration/test_basics.py +4 -7
- umap/tests/integration/test_caption.py +1 -0
- umap/tests/integration/test_categorized_layer.py +4 -8
- umap/tests/integration/test_choropleth.py +1 -1
- umap/tests/integration/test_conditional_rules.py +3 -3
- umap/tests/integration/test_draw_polygon.py +14 -22
- umap/tests/integration/test_draw_polyline.py +6 -14
- umap/tests/integration/test_edit_datalayer.py +11 -11
- umap/tests/integration/test_edit_map.py +30 -4
- umap/tests/integration/test_edit_marker.py +5 -5
- umap/tests/integration/test_edit_polygon.py +6 -6
- umap/tests/integration/test_features_id_generation.py +2 -6
- umap/tests/integration/test_import.py +115 -29
- umap/tests/integration/test_optimistic_merge.py +1 -0
- umap/tests/integration/test_owned_map.py +1 -1
- umap/tests/integration/test_picto.py +8 -8
- umap/tests/integration/test_save.py +3 -2
- umap/tests/integration/test_star.py +13 -9
- umap/tests/integration/test_tableeditor.py +8 -7
- umap/tests/integration/test_view_marker.py +10 -0
- umap/tests/integration/test_websocket_sync.py +239 -64
- umap/tests/settings.py +2 -0
- umap/tests/test_datalayer.py +2 -3
- umap/tests/test_datalayer_views.py +20 -1
- umap/tests/test_empty_trash.py +10 -3
- umap/tests/test_map_views.py +11 -0
- umap/utils.py +27 -11
- umap/views.py +37 -6
- {umap_project-2.8.2.dist-info → umap_project-2.9.0.dist-info}/METADATA +22 -22
- {umap_project-2.8.2.dist-info → umap_project-2.9.0.dist-info}/RECORD +247 -248
- {umap_project-2.8.2.dist-info → umap_project-2.9.0.dist-info}/WHEEL +1 -1
- umap/management/commands/run_websocket_server.py +0 -23
- umap/settings/local_s3.py +0 -45
- umap/static/umap/bitbucket.png +0 -0
- umap/static/umap/github.png +0 -0
- umap/static/umap/js/umap.forms.js +0 -1242
- umap/static/umap/keycloak.png +0 -0
- umap/static/umap/openstreetmap.png +0 -0
- umap/static/umap/twitter.png +0 -0
- umap/static/umap/vendors/formbuilder/Leaflet.FormBuilder.js +0 -468
- umap/static/umap/vendors/toolbar/leaflet.toolbar.css +0 -1
- umap/static/umap/vendors/toolbar/leaflet.toolbar.js +0 -1
- umap/tests/test_websocket_server.py +0 -22
- umap/websocket_server.py +0 -202
- {umap_project-2.8.2.dist-info → umap_project-2.9.0.dist-info}/entry_points.txt +0 -0
- {umap_project-2.8.2.dist-info → umap_project-2.9.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -2,6 +2,7 @@ import * as Utils from '../utils.js'
|
|
|
2
2
|
import { HybridLogicalClock } from './hlc.js'
|
|
3
3
|
import { DataLayerUpdater, FeatureUpdater, MapUpdater } from './updaters.js'
|
|
4
4
|
import { WebSocketTransport } from './websocket.js'
|
|
5
|
+
import * as SaveManager from '../saving.js'
|
|
5
6
|
|
|
6
7
|
// Start reconnecting after 2 seconds, then double the delay each time
|
|
7
8
|
// maxing out at 32 seconds.
|
|
@@ -61,25 +62,39 @@ export class SyncEngine {
|
|
|
61
62
|
this._reconnectTimeout = null
|
|
62
63
|
this._reconnectDelay = RECONNECT_DELAY
|
|
63
64
|
this.websocketConnected = false
|
|
65
|
+
this.closeRequested = false
|
|
66
|
+
this.peerId = Utils.generateId()
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
get isOpen() {
|
|
70
|
+
return this.transport?.isOpen
|
|
64
71
|
}
|
|
65
72
|
|
|
66
73
|
async authenticate() {
|
|
74
|
+
if (this.isOpen) return
|
|
67
75
|
const websocketTokenURI = this._umap.urls.get('map_websocket_auth_token', {
|
|
68
76
|
map_id: this._umap.id,
|
|
69
77
|
})
|
|
70
78
|
|
|
71
79
|
const [response, _, error] = await this._umap.server.get(websocketTokenURI)
|
|
72
|
-
if (
|
|
73
|
-
this.
|
|
80
|
+
if (error) {
|
|
81
|
+
this.reconnect()
|
|
82
|
+
return
|
|
74
83
|
}
|
|
84
|
+
await this.start(response.token)
|
|
75
85
|
}
|
|
76
86
|
|
|
77
|
-
start(authToken) {
|
|
78
|
-
this.
|
|
79
|
-
|
|
87
|
+
async start(authToken) {
|
|
88
|
+
const path = this._umap.urls.get('ws_sync', { map_id: this._umap.id })
|
|
89
|
+
const protocol = window.location.protocol === 'http:' ? 'ws:' : 'wss:'
|
|
90
|
+
this.transport = new WebSocketTransport(this)
|
|
91
|
+
await this.transport.connect(
|
|
92
|
+
`${protocol}//${window.location.host}${path}`,
|
|
80
93
|
authToken,
|
|
81
|
-
this
|
|
94
|
+
this.peerId,
|
|
95
|
+
this._umap.properties.user?.name
|
|
82
96
|
)
|
|
97
|
+
this.onConnection()
|
|
83
98
|
}
|
|
84
99
|
|
|
85
100
|
stop() {
|
|
@@ -100,11 +115,11 @@ export class SyncEngine {
|
|
|
100
115
|
this.websocketConnected = false
|
|
101
116
|
this.updaters.map.update({ key: 'numberOfConnectedPeers' })
|
|
102
117
|
|
|
103
|
-
this._reconnectTimeout = setTimeout(() => {
|
|
118
|
+
this._reconnectTimeout = setTimeout(async () => {
|
|
104
119
|
if (this._reconnectDelay < MAX_RECONNECT_DELAY) {
|
|
105
120
|
this._reconnectDelay = this._reconnectDelay * RECONNECT_DELAY_FACTOR
|
|
106
121
|
}
|
|
107
|
-
this.authenticate()
|
|
122
|
+
await this.authenticate()
|
|
108
123
|
}, this._reconnectDelay)
|
|
109
124
|
}
|
|
110
125
|
upsert(subject, metadata, value) {
|
|
@@ -119,12 +134,22 @@ export class SyncEngine {
|
|
|
119
134
|
this._send({ verb: 'delete', subject, metadata, key })
|
|
120
135
|
}
|
|
121
136
|
|
|
137
|
+
saved() {
|
|
138
|
+
if (this.offline) return
|
|
139
|
+
if (this.transport) {
|
|
140
|
+
this.transport.send('SavedMessage', {
|
|
141
|
+
sender: this.peerId,
|
|
142
|
+
lastKnownHLC: this._operations.getLastKnownHLC(),
|
|
143
|
+
})
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
122
147
|
_send(inputMessage) {
|
|
123
148
|
const message = this._operations.addLocal(inputMessage)
|
|
124
149
|
|
|
125
150
|
if (this.offline) return
|
|
126
151
|
if (this.transport) {
|
|
127
|
-
this.transport.send('OperationMessage', message)
|
|
152
|
+
this.transport.send('OperationMessage', { sender: this.peerId, ...message })
|
|
128
153
|
}
|
|
129
154
|
}
|
|
130
155
|
|
|
@@ -140,9 +165,8 @@ export class SyncEngine {
|
|
|
140
165
|
updater.applyMessage(operation)
|
|
141
166
|
}
|
|
142
167
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
return 0
|
|
168
|
+
getPeers() {
|
|
169
|
+
return this.peers || {}
|
|
146
170
|
}
|
|
147
171
|
|
|
148
172
|
/**
|
|
@@ -163,6 +187,8 @@ export class SyncEngine {
|
|
|
163
187
|
} else if (payload.message.verb === 'ListOperationsResponse') {
|
|
164
188
|
this.onListOperationsResponse(payload)
|
|
165
189
|
}
|
|
190
|
+
} else if (kind === 'SavedMessage') {
|
|
191
|
+
this.onSavedMessage(payload)
|
|
166
192
|
} else {
|
|
167
193
|
throw new Error(`Received unknown message from the websocket server: ${kind}`)
|
|
168
194
|
}
|
|
@@ -176,6 +202,8 @@ export class SyncEngine {
|
|
|
176
202
|
* @param {Object} payload
|
|
177
203
|
*/
|
|
178
204
|
onOperationMessage(payload) {
|
|
205
|
+
if (payload.sender === this.peerId) return
|
|
206
|
+
debug('received operation', payload)
|
|
179
207
|
this._operations.storeRemoteOperations([payload])
|
|
180
208
|
this._applyOperation(payload)
|
|
181
209
|
}
|
|
@@ -187,9 +215,8 @@ export class SyncEngine {
|
|
|
187
215
|
* @param {string} payload.uuid The server-assigned uuid for this peer
|
|
188
216
|
* @param {string[]} payload.peers The list of peers uuids
|
|
189
217
|
*/
|
|
190
|
-
onJoinResponse({
|
|
191
|
-
debug('received join response', {
|
|
192
|
-
this.uuid = uuid
|
|
218
|
+
onJoinResponse({ peer, peers }) {
|
|
219
|
+
debug('received join response', { peer, peers })
|
|
193
220
|
this.onListPeersResponse({ peers })
|
|
194
221
|
|
|
195
222
|
// Get one peer at random
|
|
@@ -210,7 +237,7 @@ export class SyncEngine {
|
|
|
210
237
|
* @param {string[]} payload.peers The list of peers uuids
|
|
211
238
|
*/
|
|
212
239
|
onListPeersResponse({ peers }) {
|
|
213
|
-
debug('received peerinfo',
|
|
240
|
+
debug('received peerinfo', peers)
|
|
214
241
|
this.peers = peers
|
|
215
242
|
this.updaters.map.update({ key: 'numberOfConnectedPeers' })
|
|
216
243
|
}
|
|
@@ -240,7 +267,7 @@ export class SyncEngine {
|
|
|
240
267
|
* @param {*} operations The list of (encoded operations)
|
|
241
268
|
*/
|
|
242
269
|
onListOperationsResponse({ sender, message }) {
|
|
243
|
-
debug(`received operations from peer ${sender}`, message.operations)
|
|
270
|
+
debug(`received operations list from peer ${sender}`, message.operations)
|
|
244
271
|
|
|
245
272
|
if (message.operations.length === 0) return
|
|
246
273
|
|
|
@@ -275,6 +302,13 @@ export class SyncEngine {
|
|
|
275
302
|
// Else: apply
|
|
276
303
|
}
|
|
277
304
|
|
|
305
|
+
onSavedMessage({ sender, lastKnownHLC }) {
|
|
306
|
+
debug(`received saved message from peer ${sender}`, lastKnownHLC)
|
|
307
|
+
if (lastKnownHLC === this._operations.getLastKnownHLC() && SaveManager.isDirty) {
|
|
308
|
+
SaveManager.clear()
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
278
312
|
/**
|
|
279
313
|
* Send a message to another peer (via the transport layer)
|
|
280
314
|
*
|
|
@@ -285,7 +319,7 @@ export class SyncEngine {
|
|
|
285
319
|
sendToPeer(recipient, verb, payload) {
|
|
286
320
|
payload.verb = verb
|
|
287
321
|
this.transport.send('PeerMessage', {
|
|
288
|
-
sender: this.
|
|
322
|
+
sender: this.peerId,
|
|
289
323
|
recipient: recipient,
|
|
290
324
|
message: payload,
|
|
291
325
|
})
|
|
@@ -297,7 +331,7 @@ export class SyncEngine {
|
|
|
297
331
|
* @returns {string|bool} the selected peer uuid, or False if none was found.
|
|
298
332
|
*/
|
|
299
333
|
_getRandomPeer() {
|
|
300
|
-
const otherPeers = this.peers.filter((p) => p !== this.
|
|
334
|
+
const otherPeers = Object.keys(this.peers).filter((p) => p !== this.peerId)
|
|
301
335
|
if (otherPeers.length > 0) {
|
|
302
336
|
const random = Math.floor(Math.random() * otherPeers.length)
|
|
303
337
|
return otherPeers[random]
|
|
@@ -345,7 +379,7 @@ export class Operations {
|
|
|
345
379
|
}
|
|
346
380
|
|
|
347
381
|
/**
|
|
348
|
-
* Tick the clock and
|
|
382
|
+
* Tick the clock and store the passed message in the operations list.
|
|
349
383
|
*
|
|
350
384
|
* @param {*} inputMessage
|
|
351
385
|
* @returns {*} clock-aware message
|
|
@@ -474,16 +508,12 @@ export class Operations {
|
|
|
474
508
|
* @return {bool} true if the two operations share the same context.
|
|
475
509
|
*/
|
|
476
510
|
static haveSameContext(local, remote) {
|
|
477
|
-
const shouldCheckKey =
|
|
478
|
-
local.hasOwnProperty('key') &&
|
|
479
|
-
remote.hasOwnProperty('key') &&
|
|
480
|
-
typeof local.key !== 'undefined' &&
|
|
481
|
-
typeof remote.key !== 'undefined'
|
|
511
|
+
const shouldCheckKey = local.key !== undefined && remote.key !== undefined
|
|
482
512
|
|
|
483
513
|
return (
|
|
484
514
|
Utils.deepEqual(local.subject, remote.subject) &&
|
|
485
515
|
Utils.deepEqual(local.metadata, remote.metadata) &&
|
|
486
|
-
(!shouldCheckKey || (shouldCheckKey && local.key
|
|
516
|
+
(!shouldCheckKey || (shouldCheckKey && local.key === remote.key))
|
|
487
517
|
)
|
|
488
518
|
}
|
|
489
519
|
}
|
|
@@ -54,10 +54,11 @@ export class MapUpdater extends BaseUpdater {
|
|
|
54
54
|
export class DataLayerUpdater extends BaseUpdater {
|
|
55
55
|
upsert({ value }) {
|
|
56
56
|
// Upsert only happens when a new datalayer is created.
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
57
|
+
try {
|
|
58
|
+
this.getDataLayerFromID(value.id)
|
|
59
|
+
} catch {
|
|
60
|
+
this._umap.createDataLayer(value, false)
|
|
61
|
+
}
|
|
61
62
|
}
|
|
62
63
|
|
|
63
64
|
update({ key, metadata, value }) {
|
|
@@ -72,6 +73,14 @@ export class DataLayerUpdater extends BaseUpdater {
|
|
|
72
73
|
}
|
|
73
74
|
datalayer.render([key])
|
|
74
75
|
}
|
|
76
|
+
|
|
77
|
+
delete({ metadata }) {
|
|
78
|
+
const datalayer = this.getDataLayerFromID(metadata.id)
|
|
79
|
+
if (datalayer) {
|
|
80
|
+
datalayer.del(false)
|
|
81
|
+
datalayer.commitDelete()
|
|
82
|
+
}
|
|
83
|
+
}
|
|
75
84
|
}
|
|
76
85
|
|
|
77
86
|
export class FeatureUpdater extends BaseUpdater {
|
|
@@ -84,7 +93,7 @@ export class FeatureUpdater extends BaseUpdater {
|
|
|
84
93
|
upsert({ metadata, value }) {
|
|
85
94
|
const { id, layerId } = metadata
|
|
86
95
|
const datalayer = this.getDataLayerFromID(layerId)
|
|
87
|
-
const feature = this.getFeatureFromMetadata(metadata
|
|
96
|
+
const feature = this.getFeatureFromMetadata(metadata)
|
|
88
97
|
|
|
89
98
|
if (feature) {
|
|
90
99
|
feature.geometry = value.geometry
|
|
@@ -101,7 +110,7 @@ export class FeatureUpdater extends BaseUpdater {
|
|
|
101
110
|
return
|
|
102
111
|
}
|
|
103
112
|
if (key === 'geometry') {
|
|
104
|
-
const feature = this.getFeatureFromMetadata(metadata
|
|
113
|
+
const feature = this.getFeatureFromMetadata(metadata)
|
|
105
114
|
feature.geometry = value
|
|
106
115
|
} else {
|
|
107
116
|
this.updateObjectValue(feature, key, value)
|
|
@@ -3,49 +3,57 @@ const PING_INTERVAL = 30000
|
|
|
3
3
|
const FIRST_CONNECTION_TIMEOUT = 2000
|
|
4
4
|
|
|
5
5
|
export class WebSocketTransport {
|
|
6
|
-
constructor(
|
|
6
|
+
constructor(messagesReceiver) {
|
|
7
7
|
this.receiver = messagesReceiver
|
|
8
|
-
|
|
8
|
+
}
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
async connect(webSocketURI, authToken, peerId, username) {
|
|
11
|
+
return new Promise((resolve, reject) => {
|
|
12
|
+
this.websocket = new WebSocket(webSocketURI)
|
|
11
13
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
}
|
|
16
|
-
this.websocket.addEventListener('message', this.onMessage.bind(this))
|
|
17
|
-
this.websocket.onclose = () => {
|
|
18
|
-
console.log('websocket closed')
|
|
19
|
-
if (!this.closeRequested) {
|
|
20
|
-
console.log('Not requested, reconnecting...')
|
|
21
|
-
this.receiver.reconnect()
|
|
14
|
+
this.websocket.onopen = () => {
|
|
15
|
+
this.send('JoinRequest', { token: authToken, peer: peerId, username })
|
|
16
|
+
resolve(this.websocket)
|
|
22
17
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
18
|
+
this.websocket.addEventListener('message', this.onMessage.bind(this))
|
|
19
|
+
this.websocket.onclose = () => {
|
|
20
|
+
console.log('websocket closed')
|
|
21
|
+
if (!this.receiver.closeRequested) {
|
|
22
|
+
console.log('Not requested, reconnecting...')
|
|
23
|
+
this.receiver.reconnect()
|
|
24
|
+
}
|
|
29
25
|
}
|
|
30
|
-
}, FIRST_CONNECTION_TIMEOUT)
|
|
31
26
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
// unfortunately not possible to access it from the WebSocket object.
|
|
35
|
-
// See https://making.close.com/posts/reliable-websockets/ for more details.
|
|
36
|
-
this.pingInterval = setInterval(() => {
|
|
37
|
-
if (this.websocket.readyState === WebSocket.OPEN) {
|
|
38
|
-
this.websocket.send('ping')
|
|
39
|
-
this.pongReceived = false
|
|
40
|
-
setTimeout(() => {
|
|
41
|
-
if (!this.pongReceived) {
|
|
42
|
-
console.warn('No pong received, reconnecting...')
|
|
43
|
-
this.websocket.close()
|
|
44
|
-
clearInterval(this.pingInterval)
|
|
45
|
-
}
|
|
46
|
-
}, PONG_TIMEOUT)
|
|
27
|
+
this.websocket.onerror = (error) => {
|
|
28
|
+
console.log('WS ERROR', error)
|
|
47
29
|
}
|
|
48
|
-
|
|
30
|
+
|
|
31
|
+
this.ensureOpen = setInterval(() => {
|
|
32
|
+
if (this.websocket.readyState !== WebSocket.OPEN) {
|
|
33
|
+
this.websocket.close()
|
|
34
|
+
clearInterval(this.ensureOpen)
|
|
35
|
+
}
|
|
36
|
+
}, FIRST_CONNECTION_TIMEOUT)
|
|
37
|
+
|
|
38
|
+
// To ensure the connection is still alive, we send ping and expect pong back.
|
|
39
|
+
// Websocket provides a `ping` method to keep the connection alive, but it's
|
|
40
|
+
// unfortunately not possible to access it from the WebSocket object.
|
|
41
|
+
// See https://making.close.com/posts/reliable-websockets/ for more details.
|
|
42
|
+
this.pingInterval = setInterval(() => {
|
|
43
|
+
if (this.websocket.readyState === WebSocket.OPEN) {
|
|
44
|
+
console.log('sending ping')
|
|
45
|
+
this.websocket.send('ping')
|
|
46
|
+
this.pongReceived = false
|
|
47
|
+
setTimeout(() => {
|
|
48
|
+
if (!this.pongReceived) {
|
|
49
|
+
console.warn('No pong received, reconnecting...')
|
|
50
|
+
this.websocket.close()
|
|
51
|
+
clearInterval(this.pingInterval)
|
|
52
|
+
}
|
|
53
|
+
}, PONG_TIMEOUT)
|
|
54
|
+
}
|
|
55
|
+
}, PING_INTERVAL)
|
|
56
|
+
})
|
|
49
57
|
}
|
|
50
58
|
|
|
51
59
|
onMessage(wsMessage) {
|
|
@@ -64,7 +72,12 @@ export class WebSocketTransport {
|
|
|
64
72
|
}
|
|
65
73
|
|
|
66
74
|
close() {
|
|
67
|
-
|
|
75
|
+
console.log('Closing')
|
|
76
|
+
this.receiver.closeRequested = true
|
|
68
77
|
this.websocket.close()
|
|
69
78
|
}
|
|
79
|
+
|
|
80
|
+
get isOpen() {
|
|
81
|
+
return this.websocket?.readyState === WebSocket.OPEN
|
|
82
|
+
}
|
|
70
83
|
}
|
|
@@ -2,6 +2,7 @@ import { DomEvent, DomUtil } from '../../vendors/leaflet/leaflet-src.esm.js'
|
|
|
2
2
|
import { translate } from './i18n.js'
|
|
3
3
|
import ContextMenu from './ui/contextmenu.js'
|
|
4
4
|
import { WithTemplate, loadTemplate } from './utils.js'
|
|
5
|
+
import { MutatingForm } from './form/builder.js'
|
|
5
6
|
|
|
6
7
|
const TEMPLATE = `
|
|
7
8
|
<table>
|
|
@@ -103,7 +104,7 @@ export default class TableEditor extends WithTemplate {
|
|
|
103
104
|
}
|
|
104
105
|
|
|
105
106
|
resetProperties() {
|
|
106
|
-
this.properties = this.datalayer.
|
|
107
|
+
this.properties = this.datalayer.allProperties()
|
|
107
108
|
if (this.properties.length === 0) {
|
|
108
109
|
this.properties = [U.DEFAULT_LABEL_KEY, 'description']
|
|
109
110
|
}
|
|
@@ -205,7 +206,7 @@ export default class TableEditor extends WithTemplate {
|
|
|
205
206
|
const tr = event.target.closest('tr')
|
|
206
207
|
const feature = this.datalayer.getFeatureById(tr.dataset.feature)
|
|
207
208
|
const handler = property === 'description' ? 'Textarea' : 'Input'
|
|
208
|
-
const builder = new
|
|
209
|
+
const builder = new MutatingForm(feature, [[field, { handler }]], {
|
|
209
210
|
id: `umap-feature-properties_${L.stamp(feature)}`,
|
|
210
211
|
})
|
|
211
212
|
cell.innerHTML = ''
|
|
@@ -2,13 +2,15 @@ import { DomEvent } from '../../../vendors/leaflet/leaflet-src.esm.js'
|
|
|
2
2
|
import { translate } from '../i18n.js'
|
|
3
3
|
import { WithTemplate } from '../utils.js'
|
|
4
4
|
import ContextMenu from './contextmenu.js'
|
|
5
|
+
import * as Utils from '../utils.js'
|
|
6
|
+
import { Point, LineString, Polygon } from '../data/features.js'
|
|
5
7
|
|
|
6
8
|
const TOP_BAR_TEMPLATE = `
|
|
7
9
|
<div class="umap-main-edit-toolbox with-transition dark">
|
|
8
10
|
<div class="umap-left-edit-toolbox" data-ref="left">
|
|
9
11
|
<div class="logo"><a class="" href="/" title="${translate('Go to the homepage')}">uMap</a></div>
|
|
10
|
-
<button class="map-name" type="button" data-ref="name"></button>
|
|
11
|
-
<button class="share-status" type="button" data-ref="share"></button>
|
|
12
|
+
<button class="map-name flat" type="button" data-ref="name"></button>
|
|
13
|
+
<button class="share-status flat" type="button" data-ref="share"></button>
|
|
12
14
|
</div>
|
|
13
15
|
<div class="umap-right-edit-toolbox" data-ref="right">
|
|
14
16
|
<button class="connected-peers round" type="button" data-ref="peers">
|
|
@@ -19,7 +21,7 @@ const TOP_BAR_TEMPLATE = `
|
|
|
19
21
|
<i class="icon icon-16 icon-profile"></i>
|
|
20
22
|
<span class="username" data-ref="username"></span>
|
|
21
23
|
</button>
|
|
22
|
-
<button class="umap-help-link" type="button" title="${translate('Help')}" data-ref="help">${translate('Help')}</button>
|
|
24
|
+
<button class="umap-help-link flat" type="button" title="${translate('Help')}" data-ref="help">${translate('Help')}</button>
|
|
23
25
|
<button class="edit-cancel round" type="button" data-ref="cancel">
|
|
24
26
|
<i class="icon icon-16 icon-restore"></i>
|
|
25
27
|
<span class="">${translate('Cancel edits')}</span>
|
|
@@ -96,17 +98,22 @@ export class TopBar extends WithTemplate {
|
|
|
96
98
|
}
|
|
97
99
|
})
|
|
98
100
|
|
|
99
|
-
const connectedPeers = this._umap.sync.getNumberOfConnectedPeers()
|
|
100
101
|
this.elements.peers.addEventListener('mouseover', () => {
|
|
101
|
-
|
|
102
|
+
const connectedPeers = this._umap.sync.getPeers()
|
|
103
|
+
if (!Object.keys(connectedPeers).length) return
|
|
104
|
+
const ul = Utils.loadTemplate(
|
|
105
|
+
`<ul>${Object.entries(connectedPeers)
|
|
106
|
+
.sort((el) => el !== this._umap.user?.name)
|
|
107
|
+
.map(([id, name]) => `<li>${name || translate('Anonymous')}</li>`)
|
|
108
|
+
.join('')}</ul>`
|
|
109
|
+
)
|
|
102
110
|
this._umap.tooltip.open({
|
|
103
|
-
content:
|
|
104
|
-
connectedPeers: connectedPeers,
|
|
105
|
-
}),
|
|
111
|
+
content: ul,
|
|
106
112
|
anchor: this.elements.peers,
|
|
107
113
|
position: 'bottom',
|
|
108
114
|
delay: 500,
|
|
109
115
|
duration: 5000,
|
|
116
|
+
accent: true,
|
|
110
117
|
})
|
|
111
118
|
})
|
|
112
119
|
|
|
@@ -145,7 +152,9 @@ export class TopBar extends WithTemplate {
|
|
|
145
152
|
}
|
|
146
153
|
|
|
147
154
|
redraw() {
|
|
148
|
-
|
|
155
|
+
const syncEnabled = this._umap.getProperty('syncEnabled')
|
|
156
|
+
this.elements.peers.hidden = !syncEnabled
|
|
157
|
+
this.elements.cancel.hidden = syncEnabled
|
|
149
158
|
this.elements.saveLabel.hidden = this._umap.permissions.isDraft()
|
|
150
159
|
this.elements.saveDraftLabel.hidden = !this._umap.permissions.isDraft()
|
|
151
160
|
}
|
|
@@ -194,3 +203,86 @@ export class BottomBar extends WithTemplate {
|
|
|
194
203
|
this.elements.filter.hidden = !showMenus || !this._umap.properties.facetKey
|
|
195
204
|
}
|
|
196
205
|
}
|
|
206
|
+
|
|
207
|
+
const EDIT_BAR_TEMPLATE = `
|
|
208
|
+
<ul class="umap-edit-bar dark with-transition">
|
|
209
|
+
<li data-ref="marker"><button type="button" data-getstarted><i class="icon icon-24 icon-marker"></i></button></li>
|
|
210
|
+
<li data-ref="polyline"><button type="button" data-getstarted><i class="icon icon-24 icon-polyline"></i></button></li>
|
|
211
|
+
<li data-ref="multiline" hidden>
|
|
212
|
+
<button type="button" title="${translate('Add a line to the current multi')}"><i class="icon icon-24 icon-multiline"></i></button>
|
|
213
|
+
</li>
|
|
214
|
+
<li data-ref="polygon"><button type="button" data-getstarted><i class="icon icon-24 icon-polygon"></i></button></li>
|
|
215
|
+
<li data-ref="multipolygon" hidden>
|
|
216
|
+
<button type="button" title="${translate('Add a polygon to the current multi')}"><i class="icon icon-24 icon-multipolygon"></i></button>
|
|
217
|
+
</li>
|
|
218
|
+
<hr>
|
|
219
|
+
<li data-ref="caption" hidden><button data-getstarted type="button" title="${translate('Edit map name and caption')}"><i class="icon icon-24 icon-caption"></i></button></li>
|
|
220
|
+
<li data-ref="import" hidden><button type="button"><i class="icon icon-24 icon-upload"></i></button></li>
|
|
221
|
+
<li data-ref="layers" hidden><button type="button" title="${translate('Manage layers')}"><i class="icon icon-24 icon-layers"></i></button></li>
|
|
222
|
+
<li data-ref="tilelayers" hidden><button type="button" title="${translate('Change tilelayers')}"><i class="icon icon-24 icon-tilelayer"></i></button></li>
|
|
223
|
+
<li data-ref="center" hidden><button type="button"><i class="icon icon-24 icon-center"></i></button></li>
|
|
224
|
+
<li data-ref="permissions" hidden><button type="button" title="${translate('Update permissions and editors')}"><i class="icon icon-24 icon-key"></i></button></li>
|
|
225
|
+
<li data-ref="settings" hidden><button data-getstarted type="button" title="${translate('Map advanced properties')}"><i class="icon icon-24 icon-settings"></i></button></li>
|
|
226
|
+
</ul>
|
|
227
|
+
`
|
|
228
|
+
|
|
229
|
+
export class EditBar extends WithTemplate {
|
|
230
|
+
constructor(umap, leafletMap, parent) {
|
|
231
|
+
super()
|
|
232
|
+
this._umap = umap
|
|
233
|
+
this._leafletMap = leafletMap
|
|
234
|
+
this.loadTemplate(EDIT_BAR_TEMPLATE)
|
|
235
|
+
this.parent = parent
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
setup() {
|
|
239
|
+
this.parent.appendChild(this.element)
|
|
240
|
+
DomEvent.disableClickPropagation(this.element)
|
|
241
|
+
this._onClick('marker', () => this._leafletMap.editTools.startMarker())
|
|
242
|
+
this._onClick('polyline', () => this._leafletMap.editTools.startPolyline())
|
|
243
|
+
this._onClick('multiline', () => {
|
|
244
|
+
console.log('click click')
|
|
245
|
+
this._umap.editedFeature.ui.editor.newShape()
|
|
246
|
+
})
|
|
247
|
+
this._onClick('polygon', () => this._leafletMap.editTools.startPolygon())
|
|
248
|
+
this._onClick('multipolygon', () => this._umap.editedFeature.ui.editor.newShape())
|
|
249
|
+
this._onClick('caption', () => this._umap.editCaption())
|
|
250
|
+
this._onClick('import', () => this._umap.importer.open())
|
|
251
|
+
this._onClick('layers', () => this._umap.editDatalayers())
|
|
252
|
+
this._onClick('tilelayers', () => this._leafletMap.editTileLayers())
|
|
253
|
+
this._onClick('center', () => this._umap.editCenter())
|
|
254
|
+
this._onClick('permissions', () => this._umap.permissions.edit())
|
|
255
|
+
this._onClick('settings', () => this._umap.edit())
|
|
256
|
+
this._addTitle('import', 'IMPORT_PANEL')
|
|
257
|
+
this._addTitle('marker', 'DRAW_MARKER')
|
|
258
|
+
this._addTitle('polyline', 'DRAW_LINE')
|
|
259
|
+
this._addTitle('polygon', 'DRAW_POLYGON')
|
|
260
|
+
this._leafletMap.on('seteditedfeature', () => this.redraw())
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
redraw() {
|
|
264
|
+
const editedFeature = this._umap.editedFeature
|
|
265
|
+
this.elements.multiline.hidden = !(editedFeature instanceof LineString)
|
|
266
|
+
this.elements.multipolygon.hidden = !(editedFeature instanceof Polygon)
|
|
267
|
+
this.elements.caption.hidden = this._umap.properties.editMode !== 'advanced'
|
|
268
|
+
this.elements.import.hidden = this._umap.properties.editMode !== 'advanced'
|
|
269
|
+
this.elements.layers.hidden = this._umap.properties.editMode !== 'advanced'
|
|
270
|
+
this.elements.tilelayers.hidden = this._umap.properties.editMode !== 'advanced'
|
|
271
|
+
this.elements.center.hidden = this._umap.properties.editMode !== 'advanced'
|
|
272
|
+
this.elements.permissions.hidden = this._umap.properties.editMode !== 'advanced'
|
|
273
|
+
this.elements.settings.hidden = this._umap.properties.editMode !== 'advanced'
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
_addTitle(ref, label) {
|
|
277
|
+
this.elements[ref].querySelector('button').title = this._umap.help.displayLabel(
|
|
278
|
+
label,
|
|
279
|
+
false
|
|
280
|
+
)
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
_onClick(ref, action) {
|
|
284
|
+
// Put the click on the button, not on the li, but keep the data-ref on the li
|
|
285
|
+
// so to hide/show it when needed.
|
|
286
|
+
this.elements[ref].querySelector('button').addEventListener('click', action)
|
|
287
|
+
}
|
|
288
|
+
}
|
|
@@ -2,27 +2,18 @@ export class Positioned {
|
|
|
2
2
|
openAt({ anchor, position }) {
|
|
3
3
|
if (anchor && position === 'top') {
|
|
4
4
|
this.anchorTop(anchor)
|
|
5
|
-
} else if (anchor && position === 'left') {
|
|
6
|
-
this.anchorLeft(anchor)
|
|
7
5
|
} else if (anchor && position === 'bottom') {
|
|
8
6
|
this.anchorBottom(anchor)
|
|
9
|
-
} else {
|
|
10
|
-
this.anchorAbsolute()
|
|
11
7
|
}
|
|
12
8
|
}
|
|
13
9
|
|
|
14
|
-
|
|
15
|
-
this.container.
|
|
16
|
-
|
|
17
|
-
this.parent.offsetLeft +
|
|
18
|
-
this.parent.clientWidth / 2 -
|
|
19
|
-
this.container.clientWidth / 2
|
|
20
|
-
const top = this.parent.offsetTop + 75
|
|
21
|
-
this.setPosition({ top: top, left: left })
|
|
10
|
+
toggleClassPosition(position) {
|
|
11
|
+
this.container.classList.toggle('tooltip-bottom', position === 'bottom')
|
|
12
|
+
this.container.classList.toggle('tooltip-top', position === 'top')
|
|
22
13
|
}
|
|
23
14
|
|
|
24
15
|
anchorTop(el) {
|
|
25
|
-
this.
|
|
16
|
+
this.toggleClassPosition('top')
|
|
26
17
|
const coords = this.getPosition(el)
|
|
27
18
|
this.setPosition({
|
|
28
19
|
left: coords.left - 10,
|
|
@@ -31,23 +22,15 @@ export class Positioned {
|
|
|
31
22
|
}
|
|
32
23
|
|
|
33
24
|
anchorBottom(el) {
|
|
34
|
-
this.
|
|
25
|
+
this.toggleClassPosition('bottom')
|
|
35
26
|
const coords = this.getPosition(el)
|
|
27
|
+
const selfCoords = this.getPosition(this.container)
|
|
36
28
|
this.setPosition({
|
|
37
|
-
left: coords.left,
|
|
29
|
+
left: coords.left + coords.width / 2 - selfCoords.width / 2,
|
|
38
30
|
top: coords.bottom + 11,
|
|
39
31
|
})
|
|
40
32
|
}
|
|
41
33
|
|
|
42
|
-
anchorLeft(el) {
|
|
43
|
-
this.container.className = 'tooltip-left'
|
|
44
|
-
const coords = this.getPosition(el)
|
|
45
|
-
this.setPosition({
|
|
46
|
-
top: coords.top,
|
|
47
|
-
right: document.documentElement.offsetWidth - coords.left + 11,
|
|
48
|
-
})
|
|
49
|
-
}
|
|
50
|
-
|
|
51
34
|
getPosition(el) {
|
|
52
35
|
return el.getBoundingClientRect()
|
|
53
36
|
}
|
|
@@ -10,6 +10,9 @@ export default class ContextMenu extends Positioned {
|
|
|
10
10
|
if (options.className) {
|
|
11
11
|
this.container.classList.add(options.className)
|
|
12
12
|
}
|
|
13
|
+
if (options.orientation === 'rows') {
|
|
14
|
+
this.container.classList.add('umap-contextmenu-rows')
|
|
15
|
+
}
|
|
13
16
|
this.container.addEventListener('focusout', (event) => {
|
|
14
17
|
if (!this.container.contains(event.relatedTarget)) this.close()
|
|
15
18
|
})
|
|
@@ -37,10 +40,14 @@ export default class ContextMenu extends Positioned {
|
|
|
37
40
|
)
|
|
38
41
|
this.container.appendChild(li)
|
|
39
42
|
} else {
|
|
43
|
+
let content = item.label || ''
|
|
44
|
+
if (item.icon) {
|
|
45
|
+
content = `<i class="icon icon-16 ${item.icon}"></i>${content}`
|
|
46
|
+
}
|
|
40
47
|
const li = loadTemplate(
|
|
41
|
-
`<li class="${item.className || ''}"><button tabindex="0" class="flat"
|
|
48
|
+
`<li class="${item.className || ''}"><button tabindex="0" class="flat" title="${item.title || ''}">${content}</button></li>`
|
|
42
49
|
)
|
|
43
|
-
li.addEventListener('click', () => {
|
|
50
|
+
li.firstChild.addEventListener('click', () => {
|
|
44
51
|
this.close()
|
|
45
52
|
item.action()
|
|
46
53
|
})
|
|
@@ -25,13 +25,16 @@ export class Panel {
|
|
|
25
25
|
return this.container.classList.contains('on')
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
open({ content, className, actions = [] } = {}) {
|
|
28
|
+
open({ content, className, highlight, actions = [] } = {}) {
|
|
29
29
|
if (this.isOpen()) {
|
|
30
30
|
this.onClose()
|
|
31
31
|
}
|
|
32
32
|
this.container.className = `with-transition panel window ${this.className} ${
|
|
33
33
|
this.mode || ''
|
|
34
34
|
}`
|
|
35
|
+
if (highlight) {
|
|
36
|
+
this.container.dataset.highlight = highlight
|
|
37
|
+
}
|
|
35
38
|
document.body.classList.add(`panel-${this.className.split(' ')[0]}-on`)
|
|
36
39
|
this.container.innerHTML = ''
|
|
37
40
|
const actionsContainer = DomUtil.create('ul', 'buttons', this.container)
|
|
@@ -75,6 +78,7 @@ export class Panel {
|
|
|
75
78
|
|
|
76
79
|
close() {
|
|
77
80
|
document.body.classList.remove(`panel-${this.className.split(' ')[0]}-on`)
|
|
81
|
+
this.container.dataset.highlight = null
|
|
78
82
|
this.onClose()
|
|
79
83
|
}
|
|
80
84
|
|