Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
44 changes: 44 additions & 0 deletions signac_dashboard/modules/file_list.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
# 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

from signac_dashboard.module import Module

# Register mimetypes for C/C++ files that are not present on Windows
mimetypes.add_type("application/x-c", ".c")
mimetypes.add_type("application/x-c", ".h")
mimetypes.add_type("application/x-c++", ".cpp")
mimetypes.add_type("application/x-c++", ".hpp")
mimetypes.add_type("application/x-c++", ".cc")


class FileList(Module):
"""Lists files in the job directory with download links.
Expand Down Expand Up @@ -36,6 +44,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 mtype.startswith("application/x-") or "json" in mtype:
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 +92,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