Ability to clone a map and overall UI improvements
This commit is contained in:
parent
5d69d3c22f
commit
8a6e992b9c
6 changed files with 144 additions and 34 deletions
|
@ -144,16 +144,19 @@ body.login header {
|
||||||
}
|
}
|
||||||
h2.section {
|
h2.section {
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
color: #666;
|
color: #263B58;
|
||||||
text-align: center;
|
|
||||||
padding-top: 28px;
|
padding-top: 28px;
|
||||||
}
|
}
|
||||||
h2.tabs a {
|
h2.tabs a {
|
||||||
color: #666;
|
color: #263B58;
|
||||||
|
text-decoration: underline;
|
||||||
|
text-decoration-thickness: 3px;
|
||||||
|
margin-right: 2rem;
|
||||||
}
|
}
|
||||||
h2.tabs a:not(.selected) {
|
h2.tabs a:not(.selected) {
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
color: #666;
|
color: #263B58;
|
||||||
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
h2.tabs a:hover {
|
h2.tabs a:hover {
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
|
@ -310,14 +313,81 @@ ul.umap-autocomplete {
|
||||||
/* **************************** */
|
/* **************************** */
|
||||||
/* Dashboard */
|
/* Dashboard */
|
||||||
/* **************************** */
|
/* **************************** */
|
||||||
|
.table-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: flex-end;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
.table-header form {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-end;
|
||||||
|
}
|
||||||
|
.table-header form input {
|
||||||
|
border: 2px solid #263B58;
|
||||||
|
border-radius: 0;
|
||||||
|
padding: .5rem 1rem;
|
||||||
|
margin-bottom: 0;
|
||||||
|
line-height: inherit;
|
||||||
|
height: 2.5rem;
|
||||||
|
}
|
||||||
|
.table-header form input[type="search"] {
|
||||||
|
width: 30ch;
|
||||||
|
}
|
||||||
|
.table-header form input[type="submit"] {
|
||||||
|
background-color: #263B58;
|
||||||
|
color: white;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-header .button-download {
|
||||||
|
width: inherit;
|
||||||
|
display: inline;
|
||||||
|
padding: .5rem 1rem;
|
||||||
|
border: 2px solid #263B58;
|
||||||
|
color: #263B58;
|
||||||
|
font-weight: bold;
|
||||||
|
background-color: initial;
|
||||||
|
margin-bottom: 0;
|
||||||
|
line-height: inherit;
|
||||||
|
height: 2.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
table.maps {
|
table.maps {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
}
|
}
|
||||||
table.maps .map_fragment {
|
table.maps .map_fragment {
|
||||||
display: block;
|
display: block;
|
||||||
height: 80vh;
|
height: 80vh;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
table.maps a,
|
||||||
|
table.maps thead {
|
||||||
|
color: #263B58;
|
||||||
|
}
|
||||||
|
table.maps a {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
table.maps form {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
table.maps input[type="submit"] {
|
||||||
|
display: inline;
|
||||||
|
background-color: transparent;
|
||||||
|
color: #263B58;
|
||||||
|
padding: 0;
|
||||||
|
width: inherit;
|
||||||
|
height: 1rem;
|
||||||
|
margin: 0;
|
||||||
|
line-height: inherit;
|
||||||
|
}
|
||||||
|
table.maps tbody tr {
|
||||||
|
border-bottom: 1px solid #BDC7D4;
|
||||||
|
}
|
||||||
|
table.maps tbody tr td {
|
||||||
|
padding: 5px 4px;
|
||||||
|
}
|
||||||
table.maps tbody tr:nth-child(odd) {
|
table.maps tbody tr:nth-child(odd) {
|
||||||
background-color: #f4f4f4;
|
background-color: #f4f4f4;
|
||||||
}
|
}
|
||||||
|
@ -357,12 +427,14 @@ dialog::backdrop {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
justify-content: space-around;
|
justify-content: space-around;
|
||||||
margin: 1rem;
|
|
||||||
border-top: 1px solid gray;
|
|
||||||
}
|
}
|
||||||
.pagination > * {
|
.pagination > * {
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
}
|
}
|
||||||
|
.pagination a {
|
||||||
|
color: #263B58;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* ************************************************* */
|
/* ************************************************* */
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
{% extends "umap/content.html" %}
|
{% extends "umap/content.html" %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% block maincontent %}
|
{% block maincontent %}
|
||||||
<div class="col wide">
|
<div class="row">
|
||||||
<h2 class="section tabs">
|
<h2 class="section tabs">
|
||||||
<a href="{% url "user_dashboard" %}">{% trans "My Maps" %}</a> | <a class="selected" href="{% url 'user_profile' %}">{% trans "My Profile" %}</a>
|
<a href="{% url "user_dashboard" %}">{% trans "My Maps" %}</a>
|
||||||
|
<a class="selected" href="{% url 'user_profile' %}">{% trans "My Profile" %}</a>
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
|
|
|
@ -4,7 +4,8 @@
|
||||||
<tr>
|
<tr>
|
||||||
<th>{% blocktrans %}Name{% endblocktrans %}</th>
|
<th>{% blocktrans %}Name{% endblocktrans %}</th>
|
||||||
<th>{% blocktrans %}Preview{% endblocktrans %}</th>
|
<th>{% blocktrans %}Preview{% endblocktrans %}</th>
|
||||||
<th>{% blocktrans %}Who can see / edit{% endblocktrans %}</th>
|
<th>{% blocktrans %}Who can see{% endblocktrans %}</th>
|
||||||
|
<th>{% blocktrans %}Who can 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>
|
||||||
|
@ -13,12 +14,12 @@
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for map_inst in maps %}
|
{% for map_inst in maps %}
|
||||||
{% with unique_id="map_"|addstr:map_inst.pk %}
|
{% with unique_id="map_"|addstr:map_inst.pk %}
|
||||||
{{ map_inst.preview_settings|json_script:unique_id }}
|
|
||||||
<tr>
|
<tr>
|
||||||
<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>
|
<td>
|
||||||
|
{{ map_inst.preview_settings|json_script:unique_id }}
|
||||||
<button class="button map-opener neutral" data-map-id="{{ unique_id }}">{% blocktranslate %}Open preview{% endblocktranslate %}</button>
|
<button class="button map-opener neutral" data-map-id="{{ unique_id }}">{% blocktranslate %}Open preview{% endblocktranslate %}</button>
|
||||||
<dialog>
|
<dialog>
|
||||||
<form method="dialog">
|
<form method="dialog">
|
||||||
|
@ -29,7 +30,8 @@
|
||||||
</form>
|
</form>
|
||||||
</dialog>
|
</dialog>
|
||||||
</td>
|
</td>
|
||||||
<td>{{ map_inst.get_share_status_display }} / {{ map_inst.get_edit_status_display }}</td>
|
<td>{{ map_inst.get_share_status_display }}</td>
|
||||||
|
<td>{{ map_inst.get_edit_status_display }}</td>
|
||||||
<td>{{ map_inst.modified_at }}</td>
|
<td>{{ map_inst.modified_at }}</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="{{ map_inst.owner.get_url }}">{{ map_inst.owner }}</a>
|
<a href="{{ map_inst.owner.get_url }}">{{ map_inst.owner }}</a>
|
||||||
|
@ -38,28 +40,16 @@
|
||||||
<a href="{{ map_inst.get_absolute_url }}?share">{% translate "Share" %}</a> |
|
<a href="{{ map_inst.get_absolute_url }}?share">{% translate "Share" %}</a> |
|
||||||
<a href="{{ map_inst.get_absolute_url }}?edit">{% translate "Edit" %}</a> |
|
<a href="{{ map_inst.get_absolute_url }}?edit">{% translate "Edit" %}</a> |
|
||||||
<a href="{% url 'map_download' map_inst.pk %}">{% translate "Download" %}</a> |
|
<a href="{% url 'map_download' map_inst.pk %}">{% translate "Download" %}</a> |
|
||||||
|
<form action="{% url 'map_clone' map_inst.pk %}" method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
<input type="submit" value="{% trans "Clone" %}" />
|
||||||
|
</form> |
|
||||||
<a href="{% url 'map_delete' map_inst.pk %}">{% translate "Delete" %}</a>
|
<a href="{% url 'map_delete' map_inst.pk %}">{% translate "Delete" %}</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
<tfoot>
|
|
||||||
<tr>
|
|
||||||
<td colspan="5"></td>
|
|
||||||
<td>
|
|
||||||
<a href="{% url 'user_download' %}?{% spaceless %}
|
|
||||||
{% for map_inst in maps %}map_id={{ map_inst.pk }}{% if not forloop.last %}&{% endif %}{% endfor %}
|
|
||||||
{% endspaceless %}" class="button"
|
|
||||||
>{% blocktranslate count counter=maps.object_list|length %}
|
|
||||||
Download this map
|
|
||||||
{% plural %}
|
|
||||||
Download these {{ counter }} maps
|
|
||||||
{% endblocktranslate %}
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tfoot>
|
|
||||||
</table>
|
</table>
|
||||||
{% if maps.has_other_pages %}
|
{% if maps.has_other_pages %}
|
||||||
<div class="pagination">
|
<div class="pagination">
|
||||||
|
@ -88,6 +78,16 @@
|
||||||
<span></span>
|
<span></span>
|
||||||
{# djlint:on #}
|
{# djlint:on #}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
<span>
|
||||||
|
{% blocktranslate with per_page=maps.paginator.per_page %}
|
||||||
|
Lines per page: {{ per_page }}
|
||||||
|
{% endblocktranslate %}
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
{% blocktranslate with count=maps.paginator.count %}
|
||||||
|
{{ count }} maps
|
||||||
|
{% endblocktranslate %}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
|
|
|
@ -5,17 +5,36 @@
|
||||||
{% endblock head_title %}
|
{% endblock head_title %}
|
||||||
{% block maincontent %}
|
{% block maincontent %}
|
||||||
{% trans "Search my maps" as placeholder %}
|
{% trans "Search my maps" as placeholder %}
|
||||||
<div class="col wide">
|
<div class="row">
|
||||||
<h2 class="section tabs">
|
<h2 class="section tabs">
|
||||||
<a class="selected" href="{% url 'user_dashboard' %}"
|
<a class="selected" href="{% url 'user_dashboard' %}"
|
||||||
>{% blocktranslate with count=maps.paginator.count %}My Maps ({{ count }}){% endblocktranslate %}
|
>{% blocktranslate with count=maps.paginator.count %}My Maps ({{ count }}){% endblocktranslate %}
|
||||||
</a> | <a href="{% url 'user_profile' %}">{% trans "My profile" %}</a>
|
</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 %}
|
|
||||||
</div>
|
</div>
|
||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
{% if maps %}
|
<div class="table-header">
|
||||||
|
<form action="{{ request.get_full_path }}" method="get">
|
||||||
|
<span>
|
||||||
|
<label for="q">{% blocktranslate %}Map’s title{% endblocktranslate %}</label>
|
||||||
|
<input id="q" name="q" type="search"
|
||||||
|
value="{{ request.GET.q|default:"" }}" />
|
||||||
|
</span>
|
||||||
|
<input type="submit" value="{% trans "Search" %}" />
|
||||||
|
</form>
|
||||||
|
{% if maps.object_list|length > 1 %}
|
||||||
|
<a href="{% url 'user_download' %}?{% spaceless %}
|
||||||
|
{% for map_inst in maps %}map_id={{ map_inst.pk }}{% if not forloop.last %}&{% endif %}{% endfor %}
|
||||||
|
{% endspaceless %}" class="button button-download"
|
||||||
|
>{% blocktranslate with count=maps.object_list|length %}
|
||||||
|
Download {{ count }} maps
|
||||||
|
{% endblocktranslate %}
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% if maps or request.GET.q %}
|
||||||
{% include "umap/map_table.html" %}
|
{% include "umap/map_table.html" %}
|
||||||
{% else %}
|
{% else %}
|
||||||
<div>
|
<div>
|
||||||
|
|
|
@ -158,9 +158,23 @@ def test_clone_map_should_create_a_new_instance(client, map):
|
||||||
url = reverse("map_clone", kwargs={"map_id": map.pk})
|
url = reverse("map_clone", kwargs={"map_id": map.pk})
|
||||||
client.login(username=map.owner.username, password="123123")
|
client.login(username=map.owner.username, password="123123")
|
||||||
response = client.post(url)
|
response = client.post(url)
|
||||||
|
assert response.status_code == 302
|
||||||
|
assert Map.objects.count() == 2
|
||||||
|
clone = Map.objects.latest("pk")
|
||||||
|
assert response["Location"] == clone.get_absolute_url()
|
||||||
|
assert clone.pk != map.pk
|
||||||
|
assert clone.name == "Clone of " + map.name
|
||||||
|
|
||||||
|
|
||||||
|
def test_clone_map_should_be_possible_via_ajax(client, map):
|
||||||
|
assert Map.objects.count() == 1
|
||||||
|
url = reverse("map_clone", kwargs={"map_id": map.pk})
|
||||||
|
client.login(username=map.owner.username, password="123123")
|
||||||
|
response = client.post(url, headers={"X-Requested-With": "XMLHttpRequest"})
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert Map.objects.count() == 2
|
assert Map.objects.count() == 2
|
||||||
clone = Map.objects.latest("pk")
|
clone = Map.objects.latest("pk")
|
||||||
|
assert response.json() == {"redirect": clone.get_absolute_url()}
|
||||||
assert clone.pk != map.pk
|
assert clone.pk != map.pk
|
||||||
assert clone.name == "Clone of " + map.name
|
assert clone.name == "Clone of " + map.name
|
||||||
|
|
||||||
|
@ -191,7 +205,7 @@ def test_clone_should_set_cloner_as_owner(client, map, user):
|
||||||
map.save()
|
map.save()
|
||||||
client.login(username=user.username, password="123123")
|
client.login(username=user.username, password="123123")
|
||||||
response = client.post(url)
|
response = client.post(url)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 302
|
||||||
assert Map.objects.count() == 2
|
assert Map.objects.count() == 2
|
||||||
clone = Map.objects.latest("pk")
|
clone = Map.objects.latest("pk")
|
||||||
assert clone.pk != map.pk
|
assert clone.pk != map.pk
|
||||||
|
@ -442,9 +456,10 @@ def test_clone_map_should_be_possible_if_edit_status_is_anonymous(client, anonym
|
||||||
anonymap.edit_status = anonymap.ANONYMOUS
|
anonymap.edit_status = anonymap.ANONYMOUS
|
||||||
anonymap.save()
|
anonymap.save()
|
||||||
response = client.post(url)
|
response = client.post(url)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 302
|
||||||
assert Map.objects.count() == 2
|
assert Map.objects.count() == 2
|
||||||
clone = Map.objects.latest("pk")
|
clone = Map.objects.latest("pk")
|
||||||
|
assert response["Location"] == clone.get_absolute_url()
|
||||||
assert clone.pk != anonymap.pk
|
assert clone.pk != anonymap.pk
|
||||||
assert clone.name == "Clone of " + anonymap.name
|
assert clone.name == "Clone of " + anonymap.name
|
||||||
assert clone.owner is None
|
assert clone.owner is None
|
||||||
|
|
|
@ -860,7 +860,10 @@ class MapClone(PermissionsMixin, View):
|
||||||
return HttpResponseForbidden()
|
return HttpResponseForbidden()
|
||||||
owner = self.request.user if self.request.user.is_authenticated else None
|
owner = self.request.user if self.request.user.is_authenticated else None
|
||||||
self.object = kwargs["map_inst"].clone(owner=owner)
|
self.object = kwargs["map_inst"].clone(owner=owner)
|
||||||
|
if is_ajax(self.request):
|
||||||
response = simple_json_response(redirect=self.object.get_absolute_url())
|
response = simple_json_response(redirect=self.object.get_absolute_url())
|
||||||
|
else:
|
||||||
|
response = HttpResponseRedirect(self.object.get_absolute_url())
|
||||||
if not self.request.user.is_authenticated:
|
if not self.request.user.is_authenticated:
|
||||||
key, value = self.object.signed_cookie_elements
|
key, value = self.object.signed_cookie_elements
|
||||||
response.set_signed_cookie(
|
response.set_signed_cookie(
|
||||||
|
|
Loading…
Reference in a new issue