From b58038e09cb3e8e4b31e9beaecc884ba94bd4b5a Mon Sep 17 00:00:00 2001 From: Benjamin Moody Date: Fri, 29 Sep 2023 22:18:12 -0400 Subject: [PATCH] files_panel: fix internal links and browser history. In the files section of a preview or published project, clicking a link to a subdirectory or parent directory should behave like a normal link: - The browser's window.location should be updated, so that you can bookmark it. - The former location should be saved in the browser's history stack, so that you can navigate with Back and Forward buttons. Doing these things requires calling history.pushState when we navigate to a new location, and setting window.onpopstate to a handler function that restores the former location. Moreover, it is cleaner and safer to avoid using inline scripts. When a directory link is clicked, we will asynchronously load the directory contents; once they're loaded, call pushState to update the page URL and then parse the new directory HTML. (pushState must be called first so that relative links are handled correctly.) When the back button is clicked ("onpopstate" function is called), we likewise load the previous HTML directory contents, but in this case call replaceState instead of pushState (again, we need to update the page URL before parsing the HTML.) (You might think this replaceState is unnecessary, but because the HTML is loaded asynchronously, there could be multiple attempts in flight to change the page location, so it's better to keep the DOM and page URL and history state all in sync with each other.) To avoid overly complicated transition logic, the existing preview_files_panel and published_files_panel views are kept as-is. Only when somebody loads the new project_preview page or published_project page will they use the new script, and only when using the new script (which adds a "v=2" query parameter) will preview_files_panel and published_files_panel return the new inline-script-free responses. Eventually, preview_files_panel and published_files_panel can be changed to return an error if the v=2 parameter is missing, and the old files_panel.html can be removed. --- .../static/project/js/dynamic-files-panel.js | 38 ++++++++++ .../templates/project/files_panel.html | 1 + .../templates/project/files_panel_v2.html | 75 +++++++++++++++++++ .../templates/project/project_preview.html | 3 +- .../templates/project/published_project.html | 3 +- physionet-django/project/views.py | 14 +++- 6 files changed, 130 insertions(+), 4 deletions(-) create mode 100644 physionet-django/project/static/project/js/dynamic-files-panel.js create mode 100644 physionet-django/project/templates/project/files_panel_v2.html diff --git a/physionet-django/project/static/project/js/dynamic-files-panel.js b/physionet-django/project/static/project/js/dynamic-files-panel.js new file mode 100644 index 0000000000..902f750f48 --- /dev/null +++ b/physionet-django/project/static/project/js/dynamic-files-panel.js @@ -0,0 +1,38 @@ +(function() { + 'use strict'; + + var panel = $('#files-panel'); + var cur_dir = panel.find('[data-dfp-cur-dir]').data('dfp-cur-dir'); + var panel_url = panel.find('[data-dfp-panel-url]').data('dfp-panel-url'); + + function navigateDir(subdir, page_url, push_history) { + $.ajax({ + type: 'GET', + url: panel_url, + data: {'subdir': subdir, 'v': '2'}, + success: function(result) { + if (push_history) + history.pushState(subdir, '', page_url); + else + history.replaceState(subdir, '', page_url); + panel.html(result); + setClickHandlers(); + }, + }); + } + + function setClickHandlers() { + panel.find('a[data-dfp-dir]').click(function(event) { + navigateDir($(this).data('dfp-dir'), this.href, true); + event.preventDefault(); + }); + } + setClickHandlers(); + + window.onpopstate = function(event) { + if (event.state !== null) { + navigateDir(event.state, window.location, false); + } + }; + history.replaceState(cur_dir, ''); +})(); diff --git a/physionet-django/project/templates/project/files_panel.html b/physionet-django/project/templates/project/files_panel.html index f4875a9f29..5caff7f0dd 100644 --- a/physionet-django/project/templates/project/files_panel.html +++ b/physionet-django/project/templates/project/files_panel.html @@ -1,3 +1,4 @@ +{# Note: This template is obsolescent. Use files_panel_v2 instead. #}
Folder Navigation: {% spaceless %} diff --git a/physionet-django/project/templates/project/files_panel_v2.html b/physionet-django/project/templates/project/files_panel_v2.html new file mode 100644 index 0000000000..be59e07d75 --- /dev/null +++ b/physionet-django/project/templates/project/files_panel_v2.html @@ -0,0 +1,75 @@ +{# Note: This template is used together with dynamic-files-panel.js. #} +
+ Folder Navigation: + {% spaceless %} + + {% for breadcrumb in dir_breadcrumbs %} + {% if forloop.counter == dir_breadcrumbs|length %} + {{ breadcrumb.name }} + {% else %} + {{ breadcrumb.name }} + / + {% endif %} + {% endfor %} + + {% endspaceless %} +
+{% if file_error %} +
+ +
+{% else %} + {% if file_warning %} +
+ +
+ {% endif %} + + + + + + + + + + + + + {% if subdir %} + + + + + + {% endif %} + {% for dir in display_dirs %} + + + + + + {% endfor %} + {% for file in display_files %} + + + + + + {% endfor %} + +
NameSizeModified
Parent Directory
{{ dir.name }}
{{ file.name }} + + (download) + + {{ file.size }}{{ file.last_modified }}
+{% endif %} diff --git a/physionet-django/project/templates/project/project_preview.html b/physionet-django/project/templates/project/project_preview.html index ee5a711345..7a65407beb 100644 --- a/physionet-django/project/templates/project/project_preview.html +++ b/physionet-django/project/templates/project/project_preview.html @@ -236,7 +236,7 @@

Files

{% endif %} {% endif %}
- {% include "project/files_panel.html" %} + {% include "project/files_panel_v2.html" %}

@@ -244,6 +244,7 @@

Files

{% endblock %} {% block local_js_bottom %} + {% endblock %} diff --git a/physionet-django/project/templates/project/published_project.html b/physionet-django/project/templates/project/published_project.html index cba490d00e..3ec8ddccd7 100644 --- a/physionet-django/project/templates/project/published_project.html +++ b/physionet-django/project/templates/project/published_project.html @@ -442,7 +442,7 @@
Access the files
{% endif %}
- {% include "project/files_panel.html" %} + {% include "project/files_panel_v2.html" %}
{% else %} {% include "project/published_project_denied_downloads.html" %} @@ -472,6 +472,7 @@
Access the files
{% endblock %} {% block local_js_bottom %} + {% endblock %} diff --git a/physionet-django/project/views.py b/physionet-django/project/views.py index 2d4688464a..a680ab43f5 100644 --- a/physionet-django/project/views.py +++ b/physionet-django/project/views.py @@ -1151,7 +1151,12 @@ def preview_files_panel(request, project_slug, **kwargs): file_warning = get_project_file_warning(display_files, display_dirs, subdir) - return render(request, 'project/files_panel.html', + if request.GET.get('v', '1') == '1': + template = 'project/files_panel.html' + else: + template = 'project/files_panel_v2.html' + + return render(request, template, {'project':project, 'subdir':subdir, 'file_error':file_error, 'dir_breadcrumbs':dir_breadcrumbs, 'parent_dir':parent_dir, 'display_files':display_files, 'display_dirs':display_dirs, @@ -1574,7 +1579,12 @@ def published_files_panel(request, project_slug, version): files_panel_url = reverse('published_files_panel', args=(project.slug, project.version)) - return render(request, 'project/files_panel.html', + if request.GET.get('v', '1') == '1': + template = 'project/files_panel.html' + else: + template = 'project/files_panel_v2.html' + + return render(request, template, {'project':project, 'subdir':subdir, 'dir_breadcrumbs':dir_breadcrumbs, 'parent_dir':parent_dir, 'display_files':display_files, 'display_dirs':display_dirs,