Merge pull request #1478 from umap-project/preview-map-on-click

Preview map only on click in user’s dashboard
This commit is contained in:
Yohan Boniface 2024-01-03 22:17:55 +01:00 committed by GitHub
commit ab82fd975f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 198 additions and 88 deletions

View file

@ -11,6 +11,7 @@ from django.urls import reverse
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from .managers import PublicManager from .managers import PublicManager
from .utils import _urls_for_js
# Did not find a clean way to do this in Django # Did not find a clean way to do this in Django
@ -193,6 +194,34 @@ class Map(NamedModel):
objects = models.Manager() objects = models.Manager()
public = PublicManager() public = PublicManager()
@property
def preview_settings(self):
layers = self.datalayer_set.all()
datalayer_data = [c.metadata() for c in layers]
map_settings = self.settings
if "properties" not in map_settings:
map_settings["properties"] = {}
map_settings["properties"].update(
{
"tilelayers": [TileLayer.get_default().json],
"datalayers": datalayer_data,
"urls": _urls_for_js(),
"STATIC_URL": settings.STATIC_URL,
"editMode": "disabled",
"hash": False,
"attributionControl": False,
"scrollWheelZoom": False,
"umapAttributionControl": False,
"noControl": True,
"umap_id": self.pk,
"onLoadPanel": "none",
"captionBar": False,
"default_iconUrl": "%sumap/img/marker.png" % settings.STATIC_URL,
"slideshow": {},
}
)
return map_settings
def get_absolute_url(self): def get_absolute_url(self):
return reverse("map", kwargs={"slug": self.slug or "map", "map_id": self.pk}) return reverse("map", kwargs={"slug": self.slug or "map", "map_id": self.pk})

View file

@ -134,6 +134,9 @@ h2.section {
padding-top: 28px; padding-top: 28px;
} }
h2.tabs a { h2.tabs a {
color: #666;
}
h2.tabs a:not(.selected) {
font-weight: normal; font-weight: normal;
color: #666; color: #666;
} }
@ -296,9 +299,9 @@ table.maps {
width: 100%; width: 100%;
} }
table.maps .map_fragment { table.maps .map_fragment {
height: 100px; display: block;
height: 80vh;
width: 100%; width: 100%;
min-width: 200px;
} }
table.maps tbody tr:nth-child(odd) { table.maps tbody tr:nth-child(odd) {
background-color: #f4f4f4; background-color: #f4f4f4;
@ -309,6 +312,42 @@ table.maps td {
table.maps thead tr { table.maps thead tr {
line-height: 2em; line-height: 2em;
} }
table.maps .button {
margin-bottom: 2px;
padding:4px 6px;
height: 36px;
line-height: 23px;
}
/* **************************** */
/* Dialog */
/* **************************** */
dialog {
width: 90vw;
height: 90vh;
}
dialog::backdrop {
background: #fff5;
backdrop-filter: blur(4px);
}
.close-dialog {
text-align: center;
margin-bottom: 0;
}
/* ********************************* */
/* Pagination */
/* ********************************* */
.pagination {
display: flex;
flex-direction: row;
justify-content: space-around;
margin: 1rem;
border-top: 1px solid gray;
}
.pagination > * {
padding: 1rem;
}
/* ************************************************* */ /* ************************************************* */

View file

@ -64,8 +64,10 @@ L.U.UI = L.Evented.extend({
}, },
closePanel: function () { closePanel: function () {
if (L.DomUtil.hasClass(this.parent, 'umap-ui')) {
L.DomUtil.removeClass(this.parent, 'umap-ui') L.DomUtil.removeClass(this.parent, 'umap-ui')
this.fire('panel:closed') this.fire('panel:closed')
}
}, },
alert: function (e) { alert: function (e) {

View file

@ -3,7 +3,7 @@
{% block maincontent %} {% block maincontent %}
<div class="col wide"> <div class="col wide">
<h2 class="section tabs"> <h2 class="section tabs">
<a href="{% url "user_dashboard" %}">{% trans "My Dashboard" %}</a> | {% trans "My Profile" %} <a href="{% url "user_dashboard" %}">{% trans "My Dashboard" %}</a> | <a class="selected" href="{% url 'user_profile' %}">{% trans "My Profile" %}</a>
</h2> </h2>
</div> </div>
<div class="wrapper"> <div class="wrapper">

View file

@ -67,7 +67,7 @@
const container = this.parentNode const container = this.parentNode
container.innerHTML = data container.innerHTML = data
Array.prototype.forEach.call( Array.prototype.forEach.call(
container.querySelectorAll('script'), container.querySelectorAll('script:not([type="application/json"])'),
function (item) { function (item) {
eval(item.firstChild.textContent) eval(item.firstChild.textContent)
} }

View file

@ -1,4 +1,4 @@
{% load umap_tags umap_tags i18n %} {% load umap_tags i18n %}
{% for map_inst in maps %} {% for map_inst in maps %}
<hr /> <hr />
<div class="col wide"> <div class="col wide">

View file

@ -1,24 +1,34 @@
{% load umap_tags umap_tags i18n %} {% load umap_tags i18n %}
<table class="maps"> <table class="maps">
{% if not is_ajax %}
<thead> <thead>
<tr> <tr>
<th>{% blocktrans %}Map{% endblocktrans %}</th>
<th>{% blocktrans %}Name{% endblocktrans %}</th> <th>{% blocktrans %}Name{% endblocktrans %}</th>
<th>{% blocktrans %}Preview{% endblocktrans %}</th>
<th>{% blocktrans %}Who can see / edit{% endblocktrans %}</th> <th>{% blocktrans %}Who can see / edit{% endblocktrans %}</th>
<th>{% blocktrans %}Last save{% endblocktrans %}</th> <th>{% blocktrans %}Last save{% endblocktrans %}</th>
<th>{% blocktrans %}Owner{% endblocktrans %}</th> <th>{% blocktrans %}Owner{% endblocktrans %}</th>
<th>{% blocktrans %}Actions{% endblocktrans %}</th> <th>{% blocktrans %}Actions{% endblocktrans %}</th>
</tr> </tr>
</thead> </thead>
{% endif %}
<tbody> <tbody>
{% for map_inst in maps %} {% for map_inst in maps %}
{% with unique_id="map_"|addstr:map_inst.pk %}
{{ map_inst.preview_settings|json_script:unique_id }}
<tr> <tr>
<td>{% map_fragment map_inst prefix=prefix page=request.GET.p %}</td>
<td> <td>
<a href="{{ map_inst.get_absolute_url }}">{{ map_inst.name }}</a> <a href="{{ map_inst.get_absolute_url }}">{{ map_inst.name }}</a>
</td> </td>
<td>
<button class="button map-opener neutral" data-map-id="{{ unique_id }}">{% blocktranslate %}Open preview{% endblocktranslate %}</button>
<dialog>
<form method="dialog">
<div id="{{ unique_id }}_target" class="map_fragment"></div>
<p class="close-dialog">
<button class="button" type="submit">Close</button>
</p>
</form>
</dialog>
</td>
<td>{{ map_inst.get_share_status_display }} / {{ map_inst.get_edit_status_display }}</td> <td>{{ map_inst.get_share_status_display }} / {{ map_inst.get_edit_status_display }}</td>
<td>{{ map_inst.modified_at }}</td> <td>{{ map_inst.modified_at }}</td>
<td> <td>
@ -30,12 +40,34 @@
<a href="{% url 'map_download' map_inst.pk %}">{% translate "Download" %}</a> <a href="{% url 'map_download' map_inst.pk %}">{% translate "Download" %}</a>
</td> </td>
</tr> </tr>
{% endwith %}
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
{% if maps.has_next %} <div class="pagination">
<div class="col wide"> {% if maps.has_previous %}
<a href="?{% paginate_querystring maps.next_page_number %}" <a href="?p=1{% if q %}&q={{ q }}{% endif %}">« {% translate "first" %}</a>
class="button more_button neutral">{% trans "More" %}</a> <a href="?p={{ maps.previous_page_number }}{% if q %}&q={{ q }}{% endif %}"> {% translate "previous" %}</a>
</div> {% else %}
{# djlint:off #}
<span></span>
<span></span>
{# djlint:on #}
{% endif %} {% endif %}
<span class="current">
{% blocktranslate with maps_number=maps.number num_pages=maps.paginator.num_pages %}
Page {{ maps_number }} of {{ num_pages }}
{% endblocktranslate %}
</span>
{% if maps.has_next %}
<a href="?p={{ maps.next_page_number }}{% if q %}&q={{ q }}{% endif %}">{% translate "next" %} </a>
<a href="?p={{ maps.paginator.num_pages }}{% if q %}&q={{ q }}{% endif %}">{% translate "last" %} »</a>
{% else %}
{# djlint:off #}
<span></span>
<span></span>
{# djlint:on #}
{% endif %}
</div>

View file

@ -7,7 +7,7 @@
{% trans "Search my maps" as placeholder %} {% trans "Search my maps" as placeholder %}
<div class="col wide"> <div class="col wide">
<h2 class="section tabs"> <h2 class="section tabs">
{% trans "My Dashboard" %} | <a href="{% url 'user_profile' %}">{% trans "My profile" %}</a> <a class="selected" href="{% url 'user_dashboard' %}">{% trans "My Dashboard" %}</a> | <a href="{% url 'user_profile' %}">{% trans "My profile" %}</a>
</h2> </h2>
{% include "umap/search_bar.html" with action=request.get_full_path placeholder=placeholder %} {% include "umap/search_bar.html" with action=request.get_full_path placeholder=placeholder %}
</div> </div>
@ -23,3 +23,25 @@
</div> </div>
</div> </div>
{% endblock maincontent %} {% endblock maincontent %}
{% block bottom_js %}
{{ block.super }}
<script type="text/javascript">
!(function () {
const CACHE = {}
for (const mapOpener of document.querySelectorAll("button.map-opener")) {
mapOpener.addEventListener('click', (event) => {
event.target.nextElementSibling.showModal()
const mapId = event.target.dataset.mapId
if (!document.querySelector(`#${mapId}_target`).hasChildNodes()) {
const previewSettings = JSON.parse(document.getElementById(mapId).textContent)
const map = new L.U.Map(`${mapId}_target`, previewSettings)
CACHE[mapId] = map
} else {
CACHE[mapId].invalidateSize()
}
})
}
})()
</script>
{% endblock bottom_js %}

View file

@ -4,9 +4,6 @@ from copy import copy
from django import template from django import template
from django.conf import settings from django.conf import settings
from ..models import DataLayer, TileLayer
from ..views import _urls_for_js
register = template.Library() register = template.Library()
@ -22,30 +19,7 @@ def umap_js(locale=None):
@register.inclusion_tag("umap/map_fragment.html") @register.inclusion_tag("umap/map_fragment.html")
def map_fragment(map_instance, **kwargs): def map_fragment(map_instance, **kwargs):
layers = DataLayer.objects.filter(map=map_instance) map_settings = map_instance.preview_settings
datalayer_data = [c.metadata() for c in layers]
map_settings = map_instance.settings
if "properties" not in map_settings:
map_settings["properties"] = {}
map_settings["properties"].update(
{
"tilelayers": [TileLayer.get_default().json],
"datalayers": datalayer_data,
"urls": _urls_for_js(),
"STATIC_URL": settings.STATIC_URL,
"editMode": "disabled",
"hash": False,
"attributionControl": False,
"scrollWheelZoom": False,
"umapAttributionControl": False,
"noControl": True,
"umap_id": map_instance.pk,
"onLoadPanel": "none",
"captionBar": False,
"default_iconUrl": "%sumap/img/marker.png" % settings.STATIC_URL,
"slideshow": {},
}
)
map_settings["properties"].update(kwargs) map_settings["properties"].update(kwargs)
prefix = kwargs.pop("prefix", None) or "map" prefix = kwargs.pop("prefix", None) or "map"
page = kwargs.pop("page", None) or "" page = kwargs.pop("page", None) or ""
@ -86,3 +60,9 @@ def ipdb(what):
ipdb.set_trace() ipdb.set_trace()
return "" return ""
@register.filter
def addstr(arg1, arg2):
# Necessity: https://stackoverflow.com/a/23783666
return str(arg1) + str(arg2)

View file

@ -1,9 +1,26 @@
import gzip import gzip
import os import os
from django.conf import settings
from django.urls import URLPattern, URLResolver, get_resolver from django.urls import URLPattern, URLResolver, get_resolver
def _urls_for_js(urls=None):
"""
Return templated URLs prepared for javascript.
"""
if urls is None:
# prevent circular import
from .urls import i18n_urls, urlpatterns
urls = [
url.name for url in urlpatterns + i18n_urls if getattr(url, "name", None)
]
urls = dict(zip(urls, [get_uri_template(url) for url in urls]))
urls.update(getattr(settings, "UMAP_EXTRA_URLS", {}))
return urls
def get_uri_template(urlname, args=None, prefix=""): def get_uri_template(urlname, args=None, prefix=""):
""" """
Utility function to return an URI Template from a named URL in django Utility function to return an URI Template from a named URL in django

View file

@ -62,7 +62,7 @@ from .forms import (
UserProfileForm, UserProfileForm,
) )
from .models import DataLayer, Licence, Map, Pictogram, Star, TileLayer from .models import DataLayer, Licence, Map, Pictogram, Star, TileLayer
from .utils import ConflictError, get_uri_template, gzip_file, is_ajax, merge_features from .utils import ConflictError, _urls_for_js, gzip_file, is_ajax, merge_features
User = get_user_model() User = get_user_model()
@ -270,7 +270,12 @@ class UserDashboard(PaginatorMixin, DetailView, SearchMixin):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
kwargs.update( kwargs.update(
{"maps": self.paginate(self.get_maps(), settings.UMAP_MAPS_PER_PAGE_OWNER)} {
"q": self.request.GET.get("q"),
"maps": self.paginate(
self.get_maps(), settings.UMAP_MAPS_PER_PAGE_OWNER
),
}
) )
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
@ -390,22 +395,6 @@ ajax_proxy = AjaxProxy.as_view()
# ############## # # ############## #
def _urls_for_js(urls=None):
"""
Return templated URLs prepared for javascript.
"""
if urls is None:
# prevent circular import
from .urls import i18n_urls, urlpatterns
urls = [
url.name for url in urlpatterns + i18n_urls if getattr(url, "name", None)
]
urls = dict(zip(urls, [get_uri_template(url) for url in urls]))
urls.update(getattr(settings, "UMAP_EXTRA_URLS", {}))
return urls
def simple_json_response(**kwargs): def simple_json_response(**kwargs):
return HttpResponse(json.dumps(kwargs), content_type="application/json") return HttpResponse(json.dumps(kwargs), content_type="application/json")