Skip to content

Commit 971b4ee

Browse files
authored
Improve Project list page navigation #1200 (#1505)
Signed-off-by: tdruez <tdruez@nexb.com>
1 parent ac4e5b6 commit 971b4ee

File tree

8 files changed

+84
-19
lines changed

8 files changed

+84
-19
lines changed

CHANGELOG.rst

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,16 @@
11
Changelog
22
=========
33

4-
v34.9.3 (unreleased)
4+
v34.9.4 (unreleased)
5+
--------------------
6+
7+
- Improve Project list page navigation.
8+
A top previous/next page navigation was added in the header for consistency with other
9+
list views.
10+
Any paginated view can now be navigated using the left/right keyboard keys.
11+
https://github.com/aboutcode-org/scancode.io/issues/1465
12+
13+
v34.9.3 (2024-12-31)
514
--------------------
615

716
- Refine the available settings for RQ_QUEUES:

scancodeio/static/main.js

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,44 @@ function displayFormUploadProgress($form, $progress, $form_errors, update_title=
317317
xhr.send(new FormData($form));
318318
}
319319

320+
// Navigation
321+
322+
class PaginationNavigator {
323+
constructor(containerSelector) {
324+
this.container = document.querySelector(containerSelector);
325+
326+
// Ensure the container exists before attaching listeners
327+
if (!this.container) {
328+
console.warn(`PaginationNavigator: No container found for selector "${containerSelector}"`);
329+
return;
330+
}
331+
332+
this.previousPageLink = this.container.querySelector("a.previous-page");
333+
this.nextPageLink = this.container.querySelector("a.next-page");
334+
335+
this.attachKeyListener();
336+
}
337+
338+
anyInputHasFocus() {
339+
// Do not enable the navigation if an <input> or <textarea> currently has the focus
340+
return document.querySelector("input:focus, textarea:focus") !== null;
341+
}
342+
343+
attachKeyListener() {
344+
document.addEventListener("keydown", (e) => {
345+
if (e.keyCode === 37 && !this.anyInputHasFocus() && this.previousPageLink) {
346+
// Left Arrow key for previous page
347+
e.preventDefault();
348+
window.location.href = this.previousPageLink.href;
349+
} else if (e.keyCode === 39 && !this.anyInputHasFocus() && this.nextPageLink) {
350+
// Right Arrow key for next page
351+
e.preventDefault();
352+
window.location.href = this.nextPageLink.href;
353+
}
354+
});
355+
}
356+
}
357+
320358
document.addEventListener('DOMContentLoaded', function () {
321359

322360
setupOpenModalButtons();
@@ -327,6 +365,11 @@ document.addEventListener('DOMContentLoaded', function () {
327365
setupHighlightControls();
328366
setupSelectCheckbox();
329367

368+
const paginationContainer = document.querySelector("#pagination-header");
369+
if (paginationContainer) {
370+
new PaginationNavigator("#pagination-header");
371+
}
372+
330373
// Close modals and dropdowns on pressing "escape" key
331374
document.addEventListener('keydown', function (event) {
332375
var e = event || window.event;

scanpipe/templates/scanpipe/base.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@
5656
.nowrap {white-space: nowrap;}
5757
#content-header input[name="search"] {width: 225px;}
5858
button.as-link {height: auto; line-height: initial; font-size: inherit;}
59-
.button.is-smaller {font-size: .90rem;}
59+
.button.is-smaller, .input.is-smaller {font-size: .90rem;}
6060
.file.is-boxed.has-name .file-cta {border-width: medium; border-style: dashed;}
6161
progress.file-upload::before {content: 'Files upload: 'attr(value)'%'; position: absolute; top: -14%; left: 43%; color: black; font-weight: 400;}
6262
.is-clipped-list ul {--snippet-spacing: 0.95rem; max-height: calc(8 * var(--snippet-spacing)); overflow: hidden;}

scanpipe/templates/scanpipe/dropdowns/filter_dropdown.html

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
<div class="dropdown is-right is-hoverable">
22
<div class="dropdown-trigger">
3-
<button class="button" aria-haspopup="true">
3+
<button class="button is-smaller" aria-haspopup="true">
4+
{% if icon_class %}
5+
<span class="icon has-text-grey">
6+
<i class="{{ icon_class }}"></i>
7+
</span>
8+
{% endif %}
49
<span>{{ filter_form_field.label }}</span>
510
<span class="icon is-small">
611
<i class="fa-solid fa-angle-down" aria-hidden="true"></i>

scanpipe/templates/scanpipe/dropdowns/project_actions_dropdown.html

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
<div id="list-actions-dropdown" class="dropdown is-right is-hoverable is-disabled">
22
<div class="dropdown-trigger">
3-
<button class="button" aria-haspopup="true" disabled>
3+
<button class="button is-smaller" aria-haspopup="true" disabled>
4+
<span class="icon has-text-grey">
5+
<i class="fa-solid fa-wrench"></i>
6+
</span>
47
<span>Actions</span>
58
<span class="icon is-small">
69
<i class="fa-solid fa-angle-down" aria-hidden="true"></i>

scanpipe/templates/scanpipe/includes/pagination_header.html

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,21 @@
11
{% load humanize %}
2-
<div class="is-flex is-justify-content-space-between">
3-
<div>
4-
{% block paginator_count %}
5-
{{ paginator.count|intcomma }} results
6-
{% endblock %}
7-
</div>
2+
<div id="pagination-header" class="is-flex is-justify-content-space-between">
3+
{% if not hide_results_count %}
4+
<div>
5+
{% block paginator_count %}
6+
{{ paginator.count|intcomma }} results
7+
{% endblock %}
8+
</div>
9+
{% endif %}
810
<div>
911
{% if page_obj.has_previous %}
10-
<a href="?page={{ page_obj.previous_page_number }}{% if url_params_without_page %}&{{ url_params_without_page }}{% endif %}">&laquo;</a>
12+
<a class="previous-page" href="?page={{ page_obj.previous_page_number }}{% if url_params_without_page %}&{{ url_params_without_page }}{% endif %}">&laquo;</a>
1113
{% else %}
1214
<span>&laquo;</span>
1315
{% endif %}
1416
Page {{ page_obj.number|intcomma }} of {{ page_obj.paginator.num_pages|intcomma }}
1517
{% if page_obj.has_next %}
16-
<a href="?page={{ page_obj.next_page_number }}{% if url_params_without_page %}&{{ url_params_without_page }}{% endif %}">&raquo;</a>
18+
<a class="next-page" href="?page={{ page_obj.next_page_number }}{% if url_params_without_page %}&{{ url_params_without_page }}{% endif %}">&raquo;</a>
1719
{% else %}
1820
<span>&raquo;</span>
1921
{% endif %}

scanpipe/templates/scanpipe/project_list.html

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,17 +29,20 @@
2929
</a>
3030
{% endif %}
3131
</div>
32-
<a href="{% url 'project_add' %}" class="button is-smaller is-link">New Project</a>
32+
<div class="has-text-right">
33+
<a href="{% url 'project_add' %}" class="button is-smaller is-link">New Project</a>
34+
{% include 'scanpipe/includes/pagination_header.html' with hide_results_count=True %}
35+
</div>
3336
</div>
3437

3538
<div class="is-flex mb-3">
3639
<div class="is-flex-grow-1 mr-2">
37-
{% include 'scanpipe/includes/search_field.html' with hide_help=True %}
40+
{% include 'scanpipe/includes/search_field.html' with extra_class="is-smaller" hide_help=True %}
3841
</div>
3942
<div>
40-
{% include 'scanpipe/dropdowns/filter_dropdown.html' with filter_form_field=filter.form.pipeline only %}
41-
{% include 'scanpipe/dropdowns/filter_dropdown.html' with filter_form_field=filter.form.status only %}
42-
{% include 'scanpipe/dropdowns/filter_dropdown.html' with filter_form_field=filter.form.sort only %}
43+
{% include 'scanpipe/dropdowns/filter_dropdown.html' with filter_form_field=filter.form.pipeline icon_class='fa-solid fa-filter' only %}
44+
{% include 'scanpipe/dropdowns/filter_dropdown.html' with filter_form_field=filter.form.status icon_class='fa-solid fa-filter' only %}
45+
{% include 'scanpipe/dropdowns/filter_dropdown.html' with filter_form_field=filter.form.sort icon_class='fa-solid fa-sort' only %}
4346
{% include 'scanpipe/dropdowns/project_actions_dropdown.html' %}
4447
</div>
4548
</div>

scanpipe/tests/test_views.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,8 +135,8 @@ def test_scanpipe_views_project_list_state_of_filters_in_search_form(self):
135135
response = self.client.get(url, data=data)
136136

137137
expected = (
138-
'<input class="input " type="search" placeholder="Search projects" '
139-
'name="search" value="query">'
138+
'<input class="input is-smaller" type="search" '
139+
'placeholder="Search projects" name="search" value="query">'
140140
)
141141
self.assertContains(response, expected, html=True)
142142

0 commit comments

Comments
 (0)