diff --git a/webui/backend/app/services/__pycache__/settings_service.cpython-313.pyc b/webui/backend/app/services/__pycache__/settings_service.cpython-313.pyc index 40f6905..808d9e4 100644 Binary files a/webui/backend/app/services/__pycache__/settings_service.cpython-313.pyc and b/webui/backend/app/services/__pycache__/settings_service.cpython-313.pyc differ diff --git a/webui/backend/app/services/settings_service.py b/webui/backend/app/services/settings_service.py index b09c259..67bb70c 100644 --- a/webui/backend/app/services/settings_service.py +++ b/webui/backend/app/services/settings_service.py @@ -6,7 +6,17 @@ from backend.app.db.settings_repository import SettingsRepository from backend.app.security.path_guard import PathGuard -VALID_THEMES = {"default", "macos-soft", "midnight", "graphite", "windows11"} +VALID_THEMES = { + "default", + "macos-soft", + "midnight", + "graphite", + "windows11", + "commander-electric", + "nord-arctic", + "catppuccin-soft", + "fluent-neon", +} VALID_COLOR_MODES = {"dark", "light"} @@ -86,7 +96,10 @@ class SettingsService: raise AppError( status_code=400, code="invalid_request", - message="Theme must be one of: default, macos-soft, midnight, graphite, windows11", + message=( + "Theme must be one of: default, macos-soft, midnight, graphite, windows11, " + "commander-electric, nord-arctic, catppuccin-soft, fluent-neon" + ), ) return normalized diff --git a/webui/backend/data/tasks.db b/webui/backend/data/tasks.db index c68b04c..85b6b85 100644 Binary files a/webui/backend/data/tasks.db and b/webui/backend/data/tasks.db differ diff --git a/webui/backend/tests/golden/__pycache__/test_api_settings_golden.cpython-313.pyc b/webui/backend/tests/golden/__pycache__/test_api_settings_golden.cpython-313.pyc index 3f4b719..b586f81 100644 Binary files a/webui/backend/tests/golden/__pycache__/test_api_settings_golden.cpython-313.pyc and b/webui/backend/tests/golden/__pycache__/test_api_settings_golden.cpython-313.pyc differ diff --git a/webui/backend/tests/golden/__pycache__/test_ui_smoke_golden.cpython-313.pyc b/webui/backend/tests/golden/__pycache__/test_ui_smoke_golden.cpython-313.pyc index 6e539dd..41012b3 100644 Binary files a/webui/backend/tests/golden/__pycache__/test_ui_smoke_golden.cpython-313.pyc and b/webui/backend/tests/golden/__pycache__/test_ui_smoke_golden.cpython-313.pyc differ diff --git a/webui/backend/tests/golden/test_api_settings_golden.py b/webui/backend/tests/golden/test_api_settings_golden.py index 1f00ac6..a45dfcb 100644 --- a/webui/backend/tests/golden/test_api_settings_golden.py +++ b/webui/backend/tests/golden/test_api_settings_golden.py @@ -157,6 +157,13 @@ class SettingsApiGoldenTest(unittest.TestCase): self.assertEqual(response.json()["selected_theme"], "midnight") self.assertEqual(response.json()["selected_color_mode"], "dark") + def test_settings_selected_theme_accepts_new_built_in_family(self) -> None: + response = self._request("POST", "/api/settings", {"selected_theme": "commander-electric"}) + + self.assertEqual(response.status_code, 200) + self.assertEqual(response.json()["selected_theme"], "commander-electric") + self.assertEqual(response.json()["selected_color_mode"], "dark") + def test_settings_selected_color_mode_persistence(self) -> None: response = self._request("POST", "/api/settings", {"selected_color_mode": "light"}) diff --git a/webui/backend/tests/golden/test_ui_smoke_golden.py b/webui/backend/tests/golden/test_ui_smoke_golden.py index fafa613..8773702 100644 --- a/webui/backend/tests/golden/test_ui_smoke_golden.py +++ b/webui/backend/tests/golden/test_ui_smoke_golden.py @@ -32,6 +32,10 @@ class UiSmokeGoldenTest(unittest.TestCase): self.assertIn('/ui/theme-midnight.css', body) self.assertIn('/ui/theme-graphite.css', body) self.assertIn('/ui/theme-windows11.css', body) + self.assertIn('/ui/theme-commander-electric.css', body) + self.assertIn('/ui/theme-nord-arctic.css', body) + self.assertIn('/ui/theme-catppuccin-soft.css', body) + self.assertIn('/ui/theme-fluent-neon.css', body) self.assertIn('id="workspace"', body) self.assertIn('id="footer-bar"', body) self.assertIn('id="title-zone-actions"', body) @@ -82,6 +86,10 @@ class UiSmokeGoldenTest(unittest.TestCase): self.assertIn('value="midnight"', body) self.assertIn('value="graphite"', body) self.assertIn('value="windows11"', body) + self.assertIn('value="commander-electric"', body) + self.assertIn('value="nord-arctic"', body) + self.assertIn('value="catppuccin-soft"', body) + self.assertIn('value="fluent-neon"', body) self.assertNotIn('id="settings-selected-color-mode"', body) self.assertIn('id="settings-startup-path-left"', body) self.assertIn('id="settings-startup-path-right"', body) @@ -138,11 +146,19 @@ class UiSmokeGoldenTest(unittest.TestCase): self.assertTrue((static_root / "theme-midnight.css").exists()) self.assertTrue((static_root / "theme-graphite.css").exists()) self.assertTrue((static_root / "theme-windows11.css").exists()) + self.assertTrue((static_root / "theme-commander-electric.css").exists()) + self.assertTrue((static_root / "theme-nord-arctic.css").exists()) + self.assertTrue((static_root / "theme-catppuccin-soft.css").exists()) + self.assertTrue((static_root / "theme-fluent-neon.css").exists()) app_js = (static_root / "app.js").read_text(encoding="utf-8") self.assertIn('currentPath: "/Volumes"', app_js) self.assertIn('selectedTheme: "default"', app_js) self.assertIn('selectedColorMode: "dark"', app_js) - self.assertIn('const VALID_THEME_FAMILIES = ["default", "macos-soft", "midnight", "graphite", "windows11"];', app_js) + self.assertIn('const VALID_THEME_FAMILIES = [', app_js) + self.assertIn('"commander-electric"', app_js) + self.assertIn('"nord-arctic"', app_js) + self.assertIn('"catppuccin-soft"', app_js) + self.assertIn('"fluent-neon"', app_js) self.assertIn('document.documentElement.dataset.themeFamily', app_js) self.assertIn('document.documentElement.dataset.colorMode', app_js) self.assertIn('function effectiveThemeKey(theme, colorMode)', app_js) @@ -218,6 +234,10 @@ class UiSmokeGoldenTest(unittest.TestCase): midnight_theme_css = (static_root / "theme-midnight.css").read_text(encoding="utf-8") graphite_theme_css = (static_root / "theme-graphite.css").read_text(encoding="utf-8") windows_theme_css = (static_root / "theme-windows11.css").read_text(encoding="utf-8") + commander_theme_css = (static_root / "theme-commander-electric.css").read_text(encoding="utf-8") + nord_theme_css = (static_root / "theme-nord-arctic.css").read_text(encoding="utf-8") + catppuccin_theme_css = (static_root / "theme-catppuccin-soft.css").read_text(encoding="utf-8") + fluent_theme_css = (static_root / "theme-fluent-neon.css").read_text(encoding="utf-8") self.assertIn('#theme-toggle', base_css) self.assertIn('.settings-card', base_css) self.assertIn('.settings-tabs', base_css) @@ -226,6 +246,8 @@ class UiSmokeGoldenTest(unittest.TestCase): self.assertIn('.entry-media-icon.video', base_css) self.assertIn('.entry-media-icon.pdf', base_css) self.assertIn('.entry-media-svg', base_css) + self.assertIn('.entry-media-svg.is-filled', base_css) + self.assertIn('.entry-media-detail', base_css) self.assertIn('.entry-media-icon.file', base_css) self.assertIn('.editor-card', base_css) self.assertIn('.editor-host', base_css) @@ -236,6 +258,10 @@ class UiSmokeGoldenTest(unittest.TestCase): self.assertIn(':root[data-theme-family="midnight"][data-color-mode="dark"]', midnight_theme_css) self.assertIn(':root[data-theme-family="graphite"][data-color-mode="dark"]', graphite_theme_css) self.assertIn(':root[data-theme-family="windows11"][data-color-mode="dark"]', windows_theme_css) + self.assertIn(':root[data-theme-family="commander-electric"][data-color-mode="dark"]', commander_theme_css) + self.assertIn(':root[data-theme-family="nord-arctic"][data-color-mode="dark"]', nord_theme_css) + self.assertIn(':root[data-theme-family="catppuccin-soft"][data-color-mode="dark"]', catppuccin_theme_css) + self.assertIn(':root[data-theme-family="fluent-neon"][data-color-mode="dark"]', fluent_theme_css) app_js_url = app.url_path_for("ui", path="/app.js") base_css_url = app.url_path_for("ui", path="/base.css") diff --git a/webui/html/app.js b/webui/html/app.js index 7a32e61..80a6bad 100644 --- a/webui/html/app.js +++ b/webui/html/app.js @@ -60,7 +60,17 @@ let settingsState = { selectedTheme: "default", selectedColorMode: "dark", }; -const VALID_THEME_FAMILIES = ["default", "macos-soft", "midnight", "graphite", "windows11"]; +const VALID_THEME_FAMILIES = [ + "default", + "macos-soft", + "midnight", + "graphite", + "windows11", + "commander-electric", + "nord-arctic", + "catppuccin-soft", + "fluent-neon", +]; const VALID_COLOR_MODES = ["dark", "light"]; let searchState = { pane: "left", @@ -473,11 +483,11 @@ function iconTypeForEntry(entry) { function mediaIconSvg(type) { const icons = { - folder: '', - file: '', - image: '', - video: '', - pdf: '', + folder: '', + file: '', + image: '', + video: '', + pdf: '', text: '', markdown: '', json: '', diff --git a/webui/html/base.css b/webui/html/base.css index 6c2a127..8caa504 100644 --- a/webui/html/base.css +++ b/webui/html/base.css @@ -316,6 +316,24 @@ button:disabled { stroke-linejoin: round; } +.entry-media-svg.is-filled { + fill: currentColor; + stroke: currentColor; +} + +.entry-media-svg .entry-media-detail { + fill: none; + stroke: var(--color-surface); + stroke-width: 1.45; + stroke-linecap: round; + stroke-linejoin: round; +} + +.entry-media-svg .entry-media-detail-solid { + fill: var(--color-surface); + stroke: none; +} + .entry-media-icon.folder { color: color-mix(in srgb, #d1a85e 72%, var(--color-text-muted)); } diff --git a/webui/html/index.html b/webui/html/index.html index e197f90..ccf2473 100644 --- a/webui/html/index.html +++ b/webui/html/index.html @@ -10,6 +10,10 @@ + + + +