diff --git a/changelog.txt b/changelog.txt index 421b2417..9d965c8a 100644 --- a/changelog.txt +++ b/changelog.txt @@ -10,6 +10,11 @@ Version 0.7 [0.7.0] -- 2025-xx-xx --------------------- +Added ++++++ + +- File icons for the FileList module (#287). + Updated +++++++ diff --git a/signac_dashboard/modules/file_list.py b/signac_dashboard/modules/file_list.py index df2fc202..79e5028c 100644 --- a/signac_dashboard/modules/file_list.py +++ b/signac_dashboard/modules/file_list.py @@ -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. @@ -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}" @@ -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) ), diff --git a/signac_dashboard/templates/cards/file_list.html b/signac_dashboard/templates/cards/file_list.html index c7234556..3fefacc7 100644 --- a/signac_dashboard/templates/cards/file_list.html +++ b/signac_dashboard/templates/cards/file_list.html @@ -1,5 +1,8 @@ diff --git a/tests/test_dashboard.py b/tests/test_dashboard.py index 291e459c..caebab98 100644 --- a/tests/test_dashboard.py +++ b/tests/test_dashboard.py @@ -8,6 +8,7 @@ import unittest from urllib.parse import quote as urlquote +import pytest from signac import init_project import signac_dashboard.modules @@ -207,5 +208,28 @@ def test_navigator_module(self): assert "disabled>min" 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()