umap-project 3.0.6__py3-none-any.whl → 3.1.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/forms.py +1 -1
- umap/locale/br/LC_MESSAGES/django.mo +0 -0
- umap/locale/br/LC_MESSAGES/django.po +219 -72
- umap/locale/ca/LC_MESSAGES/django.mo +0 -0
- umap/locale/ca/LC_MESSAGES/django.po +286 -95
- umap/locale/cs_CZ/LC_MESSAGES/django.mo +0 -0
- umap/locale/cs_CZ/LC_MESSAGES/django.po +211 -65
- umap/locale/da/LC_MESSAGES/django.mo +0 -0
- umap/locale/da/LC_MESSAGES/django.po +394 -202
- umap/locale/de/LC_MESSAGES/django.mo +0 -0
- umap/locale/de/LC_MESSAGES/django.po +146 -75
- umap/locale/el/LC_MESSAGES/django.mo +0 -0
- umap/locale/el/LC_MESSAGES/django.po +125 -59
- umap/locale/en/LC_MESSAGES/django.po +124 -58
- umap/locale/es/LC_MESSAGES/django.mo +0 -0
- umap/locale/es/LC_MESSAGES/django.po +125 -59
- umap/locale/et/LC_MESSAGES/django.mo +0 -0
- umap/locale/et/LC_MESSAGES/django.po +210 -64
- umap/locale/eu/LC_MESSAGES/django.mo +0 -0
- umap/locale/eu/LC_MESSAGES/django.po +212 -65
- umap/locale/fa_IR/LC_MESSAGES/django.mo +0 -0
- umap/locale/fa_IR/LC_MESSAGES/django.po +286 -95
- umap/locale/fr/LC_MESSAGES/django.mo +0 -0
- umap/locale/fr/LC_MESSAGES/django.po +125 -59
- umap/locale/gl/LC_MESSAGES/django.mo +0 -0
- umap/locale/gl/LC_MESSAGES/django.po +212 -66
- umap/locale/hu/LC_MESSAGES/django.mo +0 -0
- umap/locale/hu/LC_MESSAGES/django.po +148 -78
- umap/locale/is/LC_MESSAGES/django.mo +0 -0
- umap/locale/is/LC_MESSAGES/django.po +130 -60
- umap/locale/it/LC_MESSAGES/django.mo +0 -0
- umap/locale/it/LC_MESSAGES/django.po +125 -59
- umap/locale/ms/LC_MESSAGES/django.mo +0 -0
- umap/locale/ms/LC_MESSAGES/django.po +289 -98
- umap/locale/nl/LC_MESSAGES/django.mo +0 -0
- umap/locale/nl/LC_MESSAGES/django.po +128 -61
- umap/locale/pl/LC_MESSAGES/django.mo +0 -0
- umap/locale/pl/LC_MESSAGES/django.po +287 -96
- umap/locale/pt/LC_MESSAGES/django.mo +0 -0
- umap/locale/pt/LC_MESSAGES/django.po +211 -65
- umap/locale/zh_TW/LC_MESSAGES/django.mo +0 -0
- umap/locale/zh_TW/LC_MESSAGES/django.po +212 -66
- umap/management/commands/migrate_to_S3.py +42 -20
- umap/management/commands/purge_old_versions.py +63 -0
- umap/management/commands/switch_user.py +52 -0
- umap/managers.py +29 -2
- umap/middleware.py +1 -1
- umap/migrations/0028_map_is_template.py +21 -0
- umap/models.py +14 -4
- umap/settings/base.py +22 -0
- umap/static/umap/base.css +4 -2
- umap/static/umap/content.css +1 -1
- umap/static/umap/css/dialog.css +5 -2
- umap/static/umap/css/form.css +19 -12
- umap/static/umap/css/icon.css +6 -0
- umap/static/umap/css/importers.css +4 -0
- umap/static/umap/css/panel.css +2 -0
- umap/static/umap/img/16-white.svg +5 -1
- umap/static/umap/img/16.svg +1 -1
- umap/static/umap/img/24-white.svg +3 -2
- umap/static/umap/img/24.svg +3 -4
- umap/static/umap/img/importers/opendata.svg +1 -0
- umap/static/umap/img/source/16-white.svg +8 -4
- umap/static/umap/img/source/16.svg +1 -1
- umap/static/umap/img/source/24-white.svg +5 -4
- umap/static/umap/img/source/24.svg +5 -6
- umap/static/umap/js/components/modal.js +27 -0
- umap/static/umap/js/modules/caption.js +4 -4
- umap/static/umap/js/modules/data/features.js +40 -4
- umap/static/umap/js/modules/data/layer.js +208 -138
- umap/static/umap/js/modules/form/builder.js +6 -14
- umap/static/umap/js/modules/form/fields.js +2 -2
- umap/static/umap/js/modules/help.js +11 -3
- umap/static/umap/js/modules/importer.js +7 -4
- umap/static/umap/js/modules/importers/opendata.js +142 -0
- umap/static/umap/js/modules/permissions.js +3 -3
- umap/static/umap/js/modules/rendering/controls.js +34 -2
- umap/static/umap/js/modules/rendering/icon.js +2 -2
- umap/static/umap/js/modules/rendering/layers/base.js +1 -1
- umap/static/umap/js/modules/rendering/layers/classified.js +55 -49
- umap/static/umap/js/modules/rendering/layers/cluster.js +16 -9
- umap/static/umap/js/modules/rendering/layers/heat.js +13 -11
- umap/static/umap/js/modules/rendering/map.js +5 -0
- umap/static/umap/js/modules/rendering/ui.js +23 -0
- umap/static/umap/js/modules/rules.js +24 -23
- umap/static/umap/js/modules/schema.js +60 -4
- umap/static/umap/js/modules/sync/updaters.js +7 -3
- umap/static/umap/js/modules/tableeditor.js +7 -30
- umap/static/umap/js/modules/templates.js +122 -0
- umap/static/umap/js/modules/ui/bar.js +13 -3
- umap/static/umap/js/modules/ui/panel.js +1 -1
- umap/static/umap/js/modules/umap.js +28 -13
- umap/static/umap/js/umap.controls.js +11 -4
- umap/static/umap/locale/br.js +51 -18
- umap/static/umap/locale/br.json +51 -18
- umap/static/umap/locale/da.js +343 -310
- umap/static/umap/locale/da.json +343 -310
- umap/static/umap/locale/de.js +40 -7
- umap/static/umap/locale/de.json +40 -7
- umap/static/umap/locale/el.js +31 -4
- umap/static/umap/locale/el.json +31 -4
- umap/static/umap/locale/en.js +33 -1
- umap/static/umap/locale/en.json +33 -1
- umap/static/umap/locale/es.js +37 -4
- umap/static/umap/locale/es.json +37 -4
- umap/static/umap/locale/fr.js +34 -1
- umap/static/umap/locale/fr.json +34 -1
- umap/static/umap/locale/hu.js +44 -17
- umap/static/umap/locale/hu.json +44 -17
- umap/static/umap/locale/it.js +74 -41
- umap/static/umap/locale/it.json +74 -41
- umap/static/umap/locale/nl.js +42 -9
- umap/static/umap/locale/nl.json +42 -9
- umap/static/umap/map.css +3 -23
- umap/static/umap/vendors/textpath/leaflet.textpath.js +184 -0
- umap/storage/fs.py +19 -9
- umap/templates/umap/dashboard_menu.html +5 -0
- umap/templates/umap/design_system.html +9 -0
- umap/templates/umap/js.html +3 -0
- umap/templates/umap/map_list.html +2 -2
- umap/templates/umap/map_table.html +18 -18
- umap/templates/umap/user_dashboard.html +9 -58
- umap/templates/umap/user_map_table.html +36 -0
- umap/templates/umap/user_templates.html +19 -0
- umap/templatetags/umap_tags.py +5 -0
- umap/tests/integration/test_conditional_rules.py +57 -0
- umap/tests/integration/test_datalayer.py +16 -9
- umap/tests/integration/test_edit_marker.py +11 -0
- umap/tests/integration/test_tableeditor.py +42 -7
- umap/tests/integration/test_templates.py +44 -0
- umap/tests/test_dashboard.py +19 -0
- umap/tests/test_purge_old_versions.py +91 -0
- umap/tests/test_switch_user.py +31 -0
- umap/tests/test_views.py +67 -0
- umap/urls.py +7 -1
- umap/views.py +64 -18
- {umap_project-3.0.6.dist-info → umap_project-3.1.0.dist-info}/METADATA +14 -14
- {umap_project-3.0.6.dist-info → umap_project-3.1.0.dist-info}/RECORD +142 -129
- {umap_project-3.0.6.dist-info → umap_project-3.1.0.dist-info}/WHEEL +0 -0
- {umap_project-3.0.6.dist-info → umap_project-3.1.0.dist-info}/entry_points.txt +0 -0
- {umap_project-3.0.6.dist-info → umap_project-3.1.0.dist-info}/licenses/LICENSE +0 -0
umap/static/umap/map.css
CHANGED
|
@@ -151,29 +151,6 @@ html[dir="rtl"] .leaflet-tooltip-pane > * {
|
|
|
151
151
|
min-height: 23px;
|
|
152
152
|
height: 23px;
|
|
153
153
|
}
|
|
154
|
-
.edit-enable [type="button"]:before {
|
|
155
|
-
content: ' ';
|
|
156
|
-
width: 24px;
|
|
157
|
-
height: 24px;
|
|
158
|
-
display: inline-block;
|
|
159
|
-
vertical-align: -20%;
|
|
160
|
-
background-image: url('./img/16-white.svg');
|
|
161
|
-
background-position: -48px -48px;
|
|
162
|
-
}
|
|
163
|
-
.edit-enable [type="button"] {
|
|
164
|
-
width: initial;
|
|
165
|
-
padding: 0 20px;
|
|
166
|
-
background-color: #353c3e;
|
|
167
|
-
color: #fff;
|
|
168
|
-
background-image: none;
|
|
169
|
-
border-radius: 20px;
|
|
170
|
-
height: var(--control-size);
|
|
171
|
-
line-height: var(--control-size);
|
|
172
|
-
display: block;
|
|
173
|
-
}
|
|
174
|
-
.edit-enable [type="button"]:hover {
|
|
175
|
-
background-color: #4d5759;
|
|
176
|
-
}
|
|
177
154
|
.umap-permanent-credits-container {
|
|
178
155
|
max-width: 20rem;
|
|
179
156
|
margin-inline-start: 5px!important;
|
|
@@ -468,6 +445,9 @@ ul.photon-autocomplete {
|
|
|
468
445
|
width: 100%;
|
|
469
446
|
margin-bottom: var(--text-margin);
|
|
470
447
|
}
|
|
448
|
+
.umap-help-links li {
|
|
449
|
+
margin-bottom: var(--text-margin);
|
|
450
|
+
}
|
|
471
451
|
.umap-help {
|
|
472
452
|
font-style: italic;
|
|
473
453
|
}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Leaflet.TextPath - Shows text along a polyline
|
|
3
|
+
* Inspired by Tom Mac Wright article :
|
|
4
|
+
* https://web.archive.org/web/20130312131812/http://mapbox.com/osmdev/2012/11/20/getting-serious-about-svg/
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
(function () {
|
|
8
|
+
|
|
9
|
+
var __onAdd = L.Polyline.prototype.onAdd,
|
|
10
|
+
__onRemove = L.Polyline.prototype.onRemove,
|
|
11
|
+
__updatePath = L.Polyline.prototype._updatePath,
|
|
12
|
+
__bringToFront = L.Polyline.prototype.bringToFront;
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
var PolylineTextPath = {
|
|
16
|
+
|
|
17
|
+
onAdd: function (map) {
|
|
18
|
+
__onAdd.call(this, map);
|
|
19
|
+
this._textRedraw();
|
|
20
|
+
},
|
|
21
|
+
|
|
22
|
+
onRemove: function (map) {
|
|
23
|
+
map = map || this._map;
|
|
24
|
+
if (map && this._textNode && this._renderer._container)
|
|
25
|
+
this._renderer._container.removeChild(this._textNode);
|
|
26
|
+
__onRemove.call(this, map);
|
|
27
|
+
},
|
|
28
|
+
|
|
29
|
+
bringToFront: function () {
|
|
30
|
+
__bringToFront.call(this);
|
|
31
|
+
this._textRedraw();
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
_updatePath: function () {
|
|
35
|
+
__updatePath.call(this);
|
|
36
|
+
this._textRedraw();
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
_textRedraw: function () {
|
|
40
|
+
var text = this._text,
|
|
41
|
+
options = this._textOptions;
|
|
42
|
+
if (text) {
|
|
43
|
+
this.setText(null).setText(text, options);
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
setText: function (text, options) {
|
|
48
|
+
this._text = text;
|
|
49
|
+
this._textOptions = options;
|
|
50
|
+
|
|
51
|
+
/* If not in SVG mode or Polyline not added to map yet return */
|
|
52
|
+
/* setText will be called by onAdd, using value stored in this._text */
|
|
53
|
+
if (!L.Browser.svg || typeof this._map === 'undefined') {
|
|
54
|
+
return this;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
var defaults = {
|
|
58
|
+
repeat: false,
|
|
59
|
+
fillColor: 'black',
|
|
60
|
+
attributes: {},
|
|
61
|
+
below: false,
|
|
62
|
+
};
|
|
63
|
+
options = L.Util.extend(defaults, options);
|
|
64
|
+
|
|
65
|
+
/* If empty text, hide */
|
|
66
|
+
if (!text) {
|
|
67
|
+
if (this._textNode && this._textNode.parentNode) {
|
|
68
|
+
this._renderer._container.removeChild(this._textNode);
|
|
69
|
+
|
|
70
|
+
/* delete the node, so it will not be removed a 2nd time if the layer is later removed from the map */
|
|
71
|
+
delete this._textNode;
|
|
72
|
+
}
|
|
73
|
+
return this;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
text = text.replace(/ /g, '\u00A0'); // Non breakable spaces
|
|
77
|
+
var id = 'pathdef-' + L.Util.stamp(this);
|
|
78
|
+
var svg = this._renderer._container;
|
|
79
|
+
this._path.setAttribute('id', id);
|
|
80
|
+
|
|
81
|
+
if (options.repeat) {
|
|
82
|
+
/* Compute single pattern length */
|
|
83
|
+
var pattern = L.SVG.create('text');
|
|
84
|
+
for (var attr in options.attributes)
|
|
85
|
+
pattern.setAttribute(attr, options.attributes[attr]);
|
|
86
|
+
pattern.appendChild(document.createTextNode(text));
|
|
87
|
+
svg.appendChild(pattern);
|
|
88
|
+
var alength = pattern.getComputedTextLength();
|
|
89
|
+
svg.removeChild(pattern);
|
|
90
|
+
|
|
91
|
+
/* Create string as long as path */
|
|
92
|
+
text = new Array(Math.ceil(isNaN(this._path.getTotalLength() / alength) ? 0 : this._path.getTotalLength() / alength)).join(text);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/* Put it along the path using textPath */
|
|
96
|
+
var textNode = L.SVG.create('text'),
|
|
97
|
+
textPath = L.SVG.create('textPath');
|
|
98
|
+
|
|
99
|
+
var dy = options.offset || this._path.getAttribute('stroke-width');
|
|
100
|
+
|
|
101
|
+
textPath.setAttributeNS("http://www.w3.org/1999/xlink", "xlink:href", '#'+id);
|
|
102
|
+
textNode.setAttribute('dy', dy);
|
|
103
|
+
for (var attr in options.attributes)
|
|
104
|
+
textNode.setAttribute(attr, options.attributes[attr]);
|
|
105
|
+
textPath.appendChild(document.createTextNode(text));
|
|
106
|
+
textNode.appendChild(textPath);
|
|
107
|
+
this._textNode = textNode;
|
|
108
|
+
|
|
109
|
+
if (options.below) {
|
|
110
|
+
svg.insertBefore(textNode, svg.firstChild);
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
svg.appendChild(textNode);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/* Center text according to the path's bounding box */
|
|
117
|
+
if (options.position === 'center' || options.center) {
|
|
118
|
+
var textLength = textNode.getComputedTextLength();
|
|
119
|
+
var pathLength = this._path.getTotalLength();
|
|
120
|
+
/* Set the position for the left side of the textNode */
|
|
121
|
+
textNode.setAttribute('dx', ((pathLength / 2) - (textLength / 2)));
|
|
122
|
+
}
|
|
123
|
+
else if (options.position === 'end') {
|
|
124
|
+
var textLength = textNode.getComputedTextLength();
|
|
125
|
+
var pathLength = this._path.getTotalLength();
|
|
126
|
+
// We add pixels to try to counter-balance the space around the character.
|
|
127
|
+
const dx = pathLength - textLength + 6
|
|
128
|
+
textNode.setAttribute('dx', dx);
|
|
129
|
+
} else {
|
|
130
|
+
// "start", which is the default.
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/* Change label rotation (if required) */
|
|
134
|
+
if (options.orientation) {
|
|
135
|
+
var rotateAngle = 0;
|
|
136
|
+
switch (options.orientation) {
|
|
137
|
+
case 'flip':
|
|
138
|
+
rotateAngle = 180;
|
|
139
|
+
break;
|
|
140
|
+
case 'perpendicular':
|
|
141
|
+
rotateAngle = 90;
|
|
142
|
+
break;
|
|
143
|
+
default:
|
|
144
|
+
rotateAngle = options.orientation;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
var rotatecenterX = (textNode.getBBox().x + textNode.getBBox().width / 2);
|
|
148
|
+
var rotatecenterY = (textNode.getBBox().y + textNode.getBBox().height / 2);
|
|
149
|
+
textNode.setAttribute('transform','rotate(' + rotateAngle + ' ' + rotatecenterX + ' ' + rotatecenterY + ')');
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/* Initialize mouse events for the additional nodes */
|
|
153
|
+
if (this.options.interactive) {
|
|
154
|
+
if (L.Browser.svg || !L.Browser.vml) {
|
|
155
|
+
textPath.setAttribute('class', 'leaflet-interactive');
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
var events = ['click', 'dblclick', 'mousedown', 'mouseover',
|
|
159
|
+
'mouseout', 'mousemove', 'contextmenu'];
|
|
160
|
+
for (var i = 0; i < events.length; i++) {
|
|
161
|
+
L.DomEvent.on(textNode, events[i], this.fire, this);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return this;
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
L.Polyline.include(PolylineTextPath);
|
|
170
|
+
|
|
171
|
+
L.LayerGroup.include({
|
|
172
|
+
setText: function(text, options) {
|
|
173
|
+
for (var layer in this._layers) {
|
|
174
|
+
if (typeof this._layers[layer].setText === 'function') {
|
|
175
|
+
this._layers[layer].setText(text, options);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return this;
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
})();
|
umap/storage/fs.py
CHANGED
|
@@ -16,9 +16,15 @@ class FSDataStorage(FileSystemStorage):
|
|
|
16
16
|
name = "%s_%s.geojson" % (instance.pk, int(time.time() * 1000))
|
|
17
17
|
return root / name
|
|
18
18
|
|
|
19
|
-
def
|
|
19
|
+
def _get_names(self, instance):
|
|
20
20
|
root = self._base_path(instance)
|
|
21
|
-
|
|
21
|
+
try:
|
|
22
|
+
return self.listdir(root)[1]
|
|
23
|
+
except FileNotFoundError:
|
|
24
|
+
return []
|
|
25
|
+
|
|
26
|
+
def list_versions(self, instance):
|
|
27
|
+
names = self._get_names(instance)
|
|
22
28
|
names = [name for name in names if self._is_valid_version(name, instance)]
|
|
23
29
|
versions = [self._version_metadata(name, instance) for name in names]
|
|
24
30
|
versions.sort(reverse=True, key=operator.itemgetter("at"))
|
|
@@ -38,12 +44,12 @@ class FSDataStorage(FileSystemStorage):
|
|
|
38
44
|
return fullpath
|
|
39
45
|
|
|
40
46
|
def onDatalayerSave(self, instance):
|
|
41
|
-
self.
|
|
42
|
-
self.
|
|
47
|
+
self.purge_gzip(instance)
|
|
48
|
+
self.purge_old_versions(instance, keep=settings.UMAP_KEEP_VERSIONS)
|
|
43
49
|
|
|
44
50
|
def onDatalayerDelete(self, instance):
|
|
45
|
-
self.
|
|
46
|
-
self.
|
|
51
|
+
self.purge_gzip(instance)
|
|
52
|
+
self.purge_old_versions(instance, keep=None)
|
|
47
53
|
|
|
48
54
|
def _extract_version_ref(self, path):
|
|
49
55
|
version = path.split(".")[0]
|
|
@@ -73,11 +79,12 @@ class FSDataStorage(FileSystemStorage):
|
|
|
73
79
|
"size": self.size(self._base_path(instance) / name),
|
|
74
80
|
}
|
|
75
81
|
|
|
76
|
-
def
|
|
82
|
+
def purge_old_versions(self, instance, keep=None):
|
|
77
83
|
root = self._base_path(instance)
|
|
78
84
|
versions = self.list_versions(instance)
|
|
79
85
|
if keep is not None:
|
|
80
86
|
versions = versions[keep:]
|
|
87
|
+
deleted = 0
|
|
81
88
|
for version in versions:
|
|
82
89
|
name = version["name"]
|
|
83
90
|
# Should not be in the list, but ensure to not delete the file
|
|
@@ -88,10 +95,13 @@ class FSDataStorage(FileSystemStorage):
|
|
|
88
95
|
self.delete(root / name)
|
|
89
96
|
except FileNotFoundError:
|
|
90
97
|
pass
|
|
98
|
+
else:
|
|
99
|
+
deleted += 1
|
|
100
|
+
return deleted
|
|
91
101
|
|
|
92
|
-
def
|
|
102
|
+
def purge_gzip(self, instance):
|
|
93
103
|
root = self._base_path(instance)
|
|
94
|
-
names = self.
|
|
104
|
+
names = self._get_names(instance)
|
|
95
105
|
prefixes = [f"{instance.pk}_"]
|
|
96
106
|
if instance.old_id:
|
|
97
107
|
prefixes.append(f"{instance.old_id}_")
|
|
@@ -7,6 +7,11 @@
|
|
|
7
7
|
{% else %}
|
|
8
8
|
<a href="{% url 'user_dashboard' %}">{% trans "My Maps" %}</a>
|
|
9
9
|
{% endif %}
|
|
10
|
+
{% if selected == "templates" %}
|
|
11
|
+
<a class="selected" href="{% url 'user_templates' %}">{% blocktranslate with count=maps.paginator.count %}My Templates ({{ count }}){% endblocktranslate %}</a>
|
|
12
|
+
{% else %}
|
|
13
|
+
<a href="{% url 'user_templates' %}">{% trans "My Templates" %}</a>
|
|
14
|
+
{% endif %}
|
|
10
15
|
{% if UMAP_ALLOW_EDIT_PROFILE %}
|
|
11
16
|
<a {% if selected == "profile" %}class="selected"{% endif %}
|
|
12
17
|
href="{% url 'user_profile' %}">{% trans "My profile" %}</a>
|
|
@@ -301,6 +301,15 @@
|
|
|
301
301
|
</form>
|
|
302
302
|
</fieldset>
|
|
303
303
|
</details>
|
|
304
|
+
<details open>
|
|
305
|
+
<summary>With tabs</summary>
|
|
306
|
+
<div class="flat-tabs" data-ref="tabs">
|
|
307
|
+
<button class="flat on" data-ref="recent">Récents</button>
|
|
308
|
+
<button class="flat" data-ref="symbols">Symbole</button>
|
|
309
|
+
<button class="flat" data-ref="chars">Emoji & texte</button>
|
|
310
|
+
<button class="flat" data-ref="url">URL</button>
|
|
311
|
+
</div>
|
|
312
|
+
</details>
|
|
304
313
|
</div>
|
|
305
314
|
<h4>Importers</h4>
|
|
306
315
|
<div class="umap-dialog window importers dark">
|
umap/templates/umap/js.html
CHANGED
|
@@ -34,9 +34,12 @@
|
|
|
34
34
|
<script src="{% static 'umap/vendors/iconlayers/iconLayers.js' %}" defer></script>
|
|
35
35
|
<script src="{% static 'umap/vendors/locatecontrol/L.Control.Locate.min.js' %}"
|
|
36
36
|
defer></script>
|
|
37
|
+
<script src="{% static 'umap/vendors/textpath/leaflet.textpath.js' %}"
|
|
38
|
+
defer></script>
|
|
37
39
|
<script src="{% static 'umap/vendors/simple-statistics/simple-statistics.min.js' %}"
|
|
38
40
|
defer></script>
|
|
39
41
|
<script src="{% static 'umap/js/umap.core.js' %}" defer></script>
|
|
40
42
|
<script src="{% static 'umap/js/umap.controls.js' %}" defer></script>
|
|
41
43
|
<script type="module" src="{% static 'umap/js/components/fragment.js' %}" defer></script>
|
|
44
|
+
<script type="module" src="{% static 'umap/js/components/modal.js' %}" defer></script>
|
|
42
45
|
{% endautoescape %}
|
|
@@ -12,14 +12,14 @@
|
|
|
12
12
|
{% endfor %}
|
|
13
13
|
</ul>
|
|
14
14
|
{% endif %}
|
|
15
|
-
<h3>{{ map_inst.name }}</h3>
|
|
15
|
+
<h3>{% if map_inst.is_template %}<mark class="template-map">[{% trans "template" %}]</mark>{% endif %} {{ map_inst.name }}</h3>
|
|
16
16
|
{% with author=map_inst.get_author %}
|
|
17
17
|
{% if author %}
|
|
18
18
|
<p>{% trans "by" %} <a href="{{ author.get_url }}">{{ author }}</a></p>
|
|
19
19
|
{% endif %}
|
|
20
20
|
{% endwith %}
|
|
21
21
|
</div>
|
|
22
|
-
<a class="main" href="{{ map_inst.get_absolute_url }}">{% translate "See the map" %}</a>
|
|
22
|
+
<a class="main" href="{{ map_inst.get_absolute_url }}">{% if map_inst.is_template %}{% translate "See the template" %}{% else %}{% translate "See the map" %}{% endif %}</a>
|
|
23
23
|
</hgroup>
|
|
24
24
|
</div>
|
|
25
25
|
{% endfor %}
|
|
@@ -35,24 +35,24 @@
|
|
|
35
35
|
<a href="{{ map_inst.get_absolute_url }}">{{ map_inst.name }}</a>
|
|
36
36
|
</th>
|
|
37
37
|
<td>
|
|
38
|
-
{{ map_inst.preview_settings|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
</
|
|
53
|
-
</
|
|
54
|
-
</
|
|
55
|
-
</
|
|
38
|
+
<umap-modal data-settings='{{ map_inst.preview_settings|dumps|escape }}' data-map-id="{{ unique_id }}">
|
|
39
|
+
<button class="map-icon map-opener"
|
|
40
|
+
title="{% translate "Open preview" %}">
|
|
41
|
+
<span class="icon-dashboard icon-view"></span>
|
|
42
|
+
<span class="sr-only">{% translate "Open preview" %}</span>
|
|
43
|
+
</button>
|
|
44
|
+
<dialog>
|
|
45
|
+
<form method="dialog">
|
|
46
|
+
<div id="{{ unique_id }}_target" class="map_fragment">
|
|
47
|
+
</div>
|
|
48
|
+
<p class="close-dialog">
|
|
49
|
+
<button class="button" type="submit">
|
|
50
|
+
Close
|
|
51
|
+
</button>
|
|
52
|
+
</p>
|
|
53
|
+
</form>
|
|
54
|
+
</dialog>
|
|
55
|
+
</umap-modal>
|
|
56
56
|
</td>
|
|
57
57
|
<td>
|
|
58
58
|
{{ map_inst.get_share_status_display }}
|
|
@@ -6,63 +6,14 @@
|
|
|
6
6
|
{% translate "My Dashboard" %} - {{ SITE_DESCRIPTION }}
|
|
7
7
|
{% endblock head_title %}
|
|
8
8
|
{% block maincontent %}
|
|
9
|
-
{% trans "Search my maps" as placeholder %}
|
|
10
9
|
{% include "umap/dashboard_menu.html" with selected="maps" %}
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
name="q"
|
|
21
|
-
type="search"
|
|
22
|
-
placeholder="{% translate "Map’s title" %}"
|
|
23
|
-
value="{{ request.GET.q|default:"" }}" />
|
|
24
|
-
</span>
|
|
25
|
-
<input type="submit" value="{% trans "Search my maps" %}" />
|
|
26
|
-
</form>
|
|
27
|
-
{% if maps.object_list|length > 1 %}
|
|
28
|
-
<a href="{% url 'user_download' %}?{% spaceless %} {% for map_inst in maps %}map_id={{ map_inst.pk }}{% if not forloop.last %}&{% endif %}{% endfor %} {% endspaceless %}"
|
|
29
|
-
class="button button-download">
|
|
30
|
-
{% blocktranslate with count=maps.object_list|length trimmed %}
|
|
31
|
-
Download {{ count }} maps
|
|
32
|
-
{% endblocktranslate %}
|
|
33
|
-
</a>
|
|
34
|
-
{% endif %}
|
|
35
|
-
</div>
|
|
36
|
-
{% if maps or request.GET.q %}
|
|
37
|
-
{% include "umap/map_table.html" %}
|
|
38
|
-
{% else %}
|
|
39
|
-
<div>
|
|
40
|
-
{% blocktrans %}You have no map yet.{% endblocktrans %} <a href="{% url 'map_new' %}">{% translate "Create a map" %}</a>
|
|
41
|
-
</div>
|
|
42
|
-
{% endif %}
|
|
43
|
-
</div>
|
|
44
|
-
</div>
|
|
10
|
+
{% trans "Search my maps" as submit_label %}
|
|
11
|
+
{% trans "Map’s name" as label_title %}
|
|
12
|
+
{% blocktranslate asvar download_label with count=maps.object_list|length trimmed %}
|
|
13
|
+
Download {{ count }} maps
|
|
14
|
+
{% endblocktranslate %}
|
|
15
|
+
{% blocktranslate asvar empty_label %}You have no map yet.{% endblocktranslate %}
|
|
16
|
+
{% translate "Create a map" as create_label %}
|
|
17
|
+
{% translate "No map found." as empty_search_label %}
|
|
18
|
+
{% include "umap/user_map_table.html" with submit_label=submit_label label_title=label_title download_label=download_label empty_label=empty_label create_label=create_label empty_search_label=empty_search_label %}
|
|
45
19
|
{% endblock maincontent %}
|
|
46
|
-
{% block bottom_js %}
|
|
47
|
-
{{ block.super }}
|
|
48
|
-
<script type="module">
|
|
49
|
-
{% autoescape off %}
|
|
50
|
-
import Umap from '{% static "umap/js/modules/umap.js" %}'
|
|
51
|
-
{% endautoescape %}
|
|
52
|
-
const CACHE = {}
|
|
53
|
-
for (const mapOpener of document.querySelectorAll("button.map-opener")) {
|
|
54
|
-
mapOpener.addEventListener('click', (event) => {
|
|
55
|
-
const button = event.target.closest('button')
|
|
56
|
-
button.nextElementSibling.showModal()
|
|
57
|
-
const mapId = button.dataset.mapId
|
|
58
|
-
if (!document.querySelector(`#${mapId}_target`).children.length) {
|
|
59
|
-
const previewSettings = JSON.parse(document.getElementById(mapId).textContent)
|
|
60
|
-
const map = new Umap(`${mapId}_target`, previewSettings)
|
|
61
|
-
CACHE[mapId] = map
|
|
62
|
-
} else {
|
|
63
|
-
CACHE[mapId].invalidateSize()
|
|
64
|
-
}
|
|
65
|
-
})
|
|
66
|
-
}
|
|
67
|
-
</script>
|
|
68
|
-
{% endblock bottom_js %}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
<div class="wrapper">
|
|
2
|
+
<div class="row">
|
|
3
|
+
<div class="table-header">
|
|
4
|
+
<form action="{{ request.get_full_path }}" method="get">
|
|
5
|
+
<span>
|
|
6
|
+
<label class="sr-only" for="q">
|
|
7
|
+
{{ label_title }}
|
|
8
|
+
</label>
|
|
9
|
+
<input id="q"
|
|
10
|
+
name="q"
|
|
11
|
+
type="search"
|
|
12
|
+
placeholder="{{ label_title }}"
|
|
13
|
+
value="{{ request.GET.q|default:"" }}" />
|
|
14
|
+
</span>
|
|
15
|
+
<input type="submit" value="{{ submit_label }}" />
|
|
16
|
+
</form>
|
|
17
|
+
{% if maps.object_list|length > 1 %}
|
|
18
|
+
<a href="{% url 'user_download' %}?{% spaceless %} {% for map_inst in maps %}map_id={{ map_inst.pk }}{% if not forloop.last %}&{% endif %}{% endfor %} {% endspaceless %}"
|
|
19
|
+
class="button button-download">
|
|
20
|
+
{{ download_label }}
|
|
21
|
+
</a>
|
|
22
|
+
{% endif %}
|
|
23
|
+
</div>
|
|
24
|
+
{% if maps %}
|
|
25
|
+
{% include "umap/map_table.html" %}
|
|
26
|
+
{% else %}
|
|
27
|
+
<div>
|
|
28
|
+
{% if request.GET.q or request.GET.tags %}
|
|
29
|
+
{{ empty_search_label }}
|
|
30
|
+
{% else %}
|
|
31
|
+
{{ empty_label }} <a href="{% url 'map_new' %}">{{ create_label }}</a>
|
|
32
|
+
{% endif %}
|
|
33
|
+
</div>
|
|
34
|
+
{% endif %}
|
|
35
|
+
</div>
|
|
36
|
+
</div>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{% extends "umap/content.html" %}
|
|
2
|
+
|
|
3
|
+
{% load i18n static %}
|
|
4
|
+
|
|
5
|
+
{% block head_title %}
|
|
6
|
+
{% translate "My Templates" %} - {{ SITE_DESCRIPTION }}
|
|
7
|
+
{% endblock head_title %}
|
|
8
|
+
{% block maincontent %}
|
|
9
|
+
{% include "umap/dashboard_menu.html" with selected="templates" %}
|
|
10
|
+
{% trans "Search my templates" as submit_label %}
|
|
11
|
+
{% trans "Template’s name" as label_title %}
|
|
12
|
+
{% blocktranslate asvar download_label with count=maps.object_list|length trimmed %}
|
|
13
|
+
Download {{ count }} templates
|
|
14
|
+
{% endblocktranslate %}
|
|
15
|
+
{% blocktranslate asvar empty_label %}You have no template yet.{% endblocktranslate %}
|
|
16
|
+
{% translate "Create a template" as create_label %}
|
|
17
|
+
{% translate "No template found." as empty_search_label %}
|
|
18
|
+
{% include "umap/user_map_table.html" with submit_label=submit_label label_title=label_title download_label=download_label empty_label=empty_label create_label=create_label empty_search_label=empty_search_label %}
|
|
19
|
+
{% endblock maincontent %}
|
umap/templatetags/umap_tags.py
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from copy import deepcopy
|
|
2
|
+
|
|
1
3
|
import pytest
|
|
2
4
|
from playwright.sync_api import expect
|
|
3
5
|
|
|
@@ -308,3 +310,58 @@ def test_autocomplete_datalist(live_server, page, openmap):
|
|
|
308
310
|
expect(datalist).to_have_count(2)
|
|
309
311
|
values = {option.inner_text() for option in datalist.all()}
|
|
310
312
|
assert values == {"mytype=even", "mytype=odd"}
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
def test_can_combine_rules(live_server, page, map):
|
|
316
|
+
map.settings["properties"]["rules"] = [
|
|
317
|
+
{"condition": "mytype=odd", "options": {"color": "aliceblue"}},
|
|
318
|
+
{"condition": "mynumber>10", "options": {"iconClass": "Drop"}},
|
|
319
|
+
]
|
|
320
|
+
map.save()
|
|
321
|
+
DataLayerFactory(map=map, data=DATALAYER_DATA1)
|
|
322
|
+
DataLayerFactory(map=map, data=DATALAYER_DATA2)
|
|
323
|
+
page.goto(f"{live_server.url}{map.get_absolute_url()}#6/48.948/1.670")
|
|
324
|
+
markers = page.locator(".leaflet-marker-icon .icon_container")
|
|
325
|
+
drops = page.locator(".umap-drop-icon .icon_container")
|
|
326
|
+
expect(markers).to_have_count(5)
|
|
327
|
+
expect(drops).to_have_count(2)
|
|
328
|
+
colors = getColors(markers)
|
|
329
|
+
assert colors.count("rgb(240, 248, 255)") == 3
|
|
330
|
+
colors = getColors(drops)
|
|
331
|
+
assert colors.count("rgb(240, 248, 255)") == 2
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
def test_first_matching_rule_wins_on_given_property(live_server, page, map):
|
|
335
|
+
map.settings["properties"]["rules"] = [
|
|
336
|
+
{"condition": "mytype=odd", "options": {"color": "aliceblue"}},
|
|
337
|
+
{"condition": "mytype!=even", "options": {"color": "darkred"}},
|
|
338
|
+
]
|
|
339
|
+
map.save()
|
|
340
|
+
DataLayerFactory(map=map, data=DATALAYER_DATA1)
|
|
341
|
+
DataLayerFactory(map=map, data=DATALAYER_DATA2)
|
|
342
|
+
page.goto(f"{live_server.url}{map.get_absolute_url()}#6/48.948/1.670")
|
|
343
|
+
markers = page.locator(".leaflet-marker-icon .icon_container")
|
|
344
|
+
expect(markers).to_have_count(5)
|
|
345
|
+
colors = getColors(markers)
|
|
346
|
+
assert colors.count("rgb(240, 248, 255)") == 3
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
def test_rules_from_datalayer(live_server, page, map):
|
|
350
|
+
map.settings["properties"]["rules"] = [
|
|
351
|
+
{"condition": "mytype=odd", "options": {"color": "darkred"}}
|
|
352
|
+
]
|
|
353
|
+
map.save()
|
|
354
|
+
data = deepcopy(DATALAYER_DATA1)
|
|
355
|
+
data["_umap_options"]["rules"] = [
|
|
356
|
+
{"condition": "mytype=odd", "options": {"color": "aliceblue"}}
|
|
357
|
+
]
|
|
358
|
+
DataLayerFactory(map=map, data=data)
|
|
359
|
+
DataLayerFactory(map=map, data=DATALAYER_DATA2)
|
|
360
|
+
page.goto(f"{live_server.url}{map.get_absolute_url()}#6/48.948/1.670")
|
|
361
|
+
markers = page.locator(".leaflet-marker-icon .icon_container")
|
|
362
|
+
expect(markers).to_have_count(5)
|
|
363
|
+
colors = getColors(markers)
|
|
364
|
+
# Alice Blue should only affect layer 1
|
|
365
|
+
assert colors.count("rgb(240, 248, 255)") == 1
|
|
366
|
+
# Dark Red as for map global rules
|
|
367
|
+
assert colors.count("rgb(139, 0, 0)") == 2
|
|
@@ -54,22 +54,29 @@ def test_should_honour_fromZoom(live_server, map, datalayer, page):
|
|
|
54
54
|
expect(markers).to_be_visible()
|
|
55
55
|
|
|
56
56
|
|
|
57
|
-
def test_should_honour_toZoom(live_server, map, datalayer, page):
|
|
57
|
+
def test_should_honour_toZoom(live_server, map, datalayer, page, new_page):
|
|
58
58
|
set_options(datalayer, displayOnLoad=True, toZoom=6)
|
|
59
|
+
# Loading at zoom 7 should not show the marker
|
|
59
60
|
page.goto(f"{live_server.url}{map.get_absolute_url()}#7/48.55/14.68")
|
|
60
61
|
markers = page.locator(".leaflet-marker-icon")
|
|
61
62
|
expect(markers).to_be_hidden()
|
|
62
|
-
|
|
63
|
-
|
|
63
|
+
|
|
64
|
+
# Loading at zoom 6 should show the marker
|
|
65
|
+
page2 = new_page()
|
|
66
|
+
markers = page2.locator(".leaflet-marker-icon")
|
|
67
|
+
page2.goto(f"{live_server.url}{map.get_absolute_url()}#6/48.55/14.68")
|
|
68
|
+
expect(page2).to_have_url(re.compile(r".*#6/48\..+/14\..+"))
|
|
64
69
|
expect(markers).to_be_visible()
|
|
65
|
-
|
|
66
|
-
|
|
70
|
+
|
|
71
|
+
# Now try to unzoom/rezoom and check that markers show/hide accordingly.
|
|
72
|
+
page2.get_by_label("Zoom out").click()
|
|
73
|
+
expect(page2).to_have_url(re.compile(r".*#5/48\..+/14\..+"))
|
|
67
74
|
expect(markers).to_be_visible()
|
|
68
|
-
|
|
69
|
-
expect(
|
|
75
|
+
page2.get_by_label("Zoom in").click()
|
|
76
|
+
expect(page2).to_have_url(re.compile(r".*#6/48\..+/14\..+"))
|
|
70
77
|
expect(markers).to_be_visible()
|
|
71
|
-
|
|
72
|
-
expect(
|
|
78
|
+
page2.get_by_label("Zoom in").click()
|
|
79
|
+
expect(page2).to_have_url(re.compile(r".*#7/48\..+/14\..+"))
|
|
73
80
|
expect(markers).to_be_hidden()
|
|
74
81
|
|
|
75
82
|
|
|
@@ -118,3 +118,14 @@ def test_should_follow_datalayer_style_when_changing_datalayer(
|
|
|
118
118
|
page.get_by_role("button", name="Toggle edit mode (⇧+Click)").click()
|
|
119
119
|
page.locator(".umap-field-datalayer select").select_option(label="other datalayer")
|
|
120
120
|
expect(marker).to_have_css("background-color", "rgb(148, 0, 211)")
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def test_add_property_from_feature_properties_panel(
|
|
124
|
+
live_server, openmap, page, datalayer
|
|
125
|
+
):
|
|
126
|
+
page.goto(f"{live_server.url}{openmap.get_absolute_url()}?edit")
|
|
127
|
+
page.locator(".leaflet-marker-icon").click(modifiers=["Shift"])
|
|
128
|
+
page.get_by_role("button", name="Add a new property").click()
|
|
129
|
+
page.locator('input[name="prompt"]').fill("newprop")
|
|
130
|
+
page.get_by_role("button", name="OK").click()
|
|
131
|
+
expect(page.locator(".panel.right").get_by_text("newprop")).to_be_visible()
|