Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions changelog.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ Version 0.7
[0.7.0] -- 2025-xx-xx
---------------------

Added
+++++

- File icons for the FileList module (#287).

Updated
+++++++

Expand Down
37 changes: 37 additions & 0 deletions signac_dashboard/modules/file_list.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Copyright (c) 2022 The Regents of the University of Michigan
# All rights reserved.
# This software is licensed under the BSD 3-Clause License.
import mimetypes
import os

from flask import render_template
Expand Down Expand Up @@ -36,6 +37,41 @@ def __init__(
)
self.prefix_jobid = prefix_jobid

def _get_icon(self, filename):
_, ext = os.path.splitext(filename)
ext = ext.lstrip(".").lower()

icon_map = {
"pdf": "fa-file-pdf",
"zip": "fa-file-archive",
"tar": "fa-file-archive",
"gz": "fa-file-archive",
"7z": "fa-file-archive",
}
if ext in icon_map:
return icon_map[ext]

mtype, _ = mimetypes.guess_type(filename)
if mtype:
if mtype.startswith("image/"):
return "fa-file-image"
if mtype.startswith("audio/"):
return "fa-file-audio"
if mtype.startswith("video/"):
return "fa-file-video"
if "word" in mtype:
return "fa-file-word"
if "excel" in mtype or "spreadsheet" in mtype or "csv" in mtype:
return "fa-file-excel"
if "powerpoint" in mtype or "presentation" in mtype:
return "fa-file-powerpoint"
if "x-" in mtype or "json" in mtype:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe tighten this up a bit, since x- is pretty broad:

Suggested change
if "x-" in mtype or "json" in mtype:
if mtype.startswith("application/x-") or "json" in mtype:

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, on further review, I'm a bit unsure how to handle this -- "x-" is quite broad, but both "application/x-" and "text/x-" are required to handle all common code cases, which ends up falling back to the same behavior as far as I know. We could use something like content-types, but that's an extra dependency. Let me know your thoughts

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No big deal. We can go with "x-" in mtype if it works well for your needs. Let's avoid another dependency.

return "fa-file-code"
if mtype.startswith("text/"):
return "fa-file-alt"

return "fa-file"

def download_name(self, job, filename):
if self.prefix_jobid:
return f"{str(job)}_{filename}"
Expand All @@ -49,6 +85,7 @@ def get_cards(self, job):
"name": filename,
"jobid": job._id,
"download": self.download_name(job, filename),
"icon": self._get_icon(filename),
}
for filename in os.listdir(job.path)
),
Expand Down
5 changes: 4 additions & 1 deletion signac_dashboard/templates/cards/file_list.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
<ul>
{% for file in files %}
<li><a href="{{ url_for('get_file', jobid=file.jobid, filename=file.name, download_name=file.download) }}" download="{{ file.download }}">{{ file.name }}</a></li>
<li>
<span class="icon"><i class="fas {{ file.icon }}"></i></span>
<a href="{{ url_for('get_file', jobid=file.jobid, filename=file.name, download_name=file.download) }}" download="{{ file.download }}">{{ file.name }}</a>
</li>
{% endfor %}
</ul>
24 changes: 24 additions & 0 deletions tests/test_dashboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import unittest
from urllib.parse import quote as urlquote

import pytest
from signac import init_project

import signac_dashboard.modules
Expand Down Expand Up @@ -207,5 +208,28 @@ def test_navigator_module(self):
assert "disabled>min</div>" in response # no previous job for b


@pytest.mark.parametrize(
"filename,expected",
[
("test.pdf", "fa-file-pdf"),
("archive.zip", "fa-file-archive"),
("image.png", "fa-file-image"),
("audio.mp3", "fa-file-audio"),
("video.mp4", "fa-file-video"),
("text.txt", "fa-file-alt"),
("text.csv", "fa-file-excel"),
("code.sh", "fa-file-code"),
("code.py", "fa-file-code"),
("code.h", "fa-file-code"),
("code.c", "fa-file-code"),
("code.json", "fa-file-code"),
],
)
def test_file_list_icon(filename, expected):
"""Test that FileList._get_icon returns correct icon classes."""
file_list = signac_dashboard.modules.FileList()
assert file_list._get_icon(filename) == expected


if __name__ == "__main__":
unittest.main()
Loading