diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..3c31ae0 --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,7 @@ +{ + "permissions": { + "allow": [ + "Bash(python:*)" + ] + } +} diff --git a/app/main.py b/app/main.py index 60ae78d..94935dc 100644 --- a/app/main.py +++ b/app/main.py @@ -822,46 +822,49 @@ def get_tag_image_count(tag_id): @main.get("/api/import-settings") def get_import_settings(): - """Get current import filter settings.""" - # Load from file first, fall back to env defaults - import json - settings_path = "/import/settings.json" + """Get current import filter settings from database.""" + from app.utils.version_migration import ( + get_setting_int, get_setting_float, get_setting_bool + ) + settings = { - "min_width": int(os.environ.get("IMPORT_MIN_WIDTH", "0")), - "min_height": int(os.environ.get("IMPORT_MIN_HEIGHT", "0")), - "skip_transparent": os.environ.get("IMPORT_SKIP_TRANSPARENT", "false").lower() == "true", - "transparency_threshold": float(os.environ.get("IMPORT_TRANSPARENCY_THRESHOLD", "0.9")), - "phash_threshold": int(os.environ.get("IMPORT_PHASH_THRESHOLD", "2")), + "min_width": get_setting_int("import_min_width", int(os.environ.get("IMPORT_MIN_WIDTH", "0"))), + "min_height": get_setting_int("import_min_height", int(os.environ.get("IMPORT_MIN_HEIGHT", "0"))), + "skip_transparent": get_setting_bool("import_skip_transparent", os.environ.get("IMPORT_SKIP_TRANSPARENT", "false").lower() == "true"), + "transparency_threshold": get_setting_float("import_transparency_threshold", float(os.environ.get("IMPORT_TRANSPARENCY_THRESHOLD", "0.9"))), + "phash_threshold": get_setting_int("phash_threshold", int(os.environ.get("IMPORT_PHASH_THRESHOLD", "10"))), } - try: - if os.path.exists(settings_path): - with open(settings_path, "r") as f: - file_settings = json.load(f) - settings.update(file_settings) - except Exception: - pass return jsonify(ok=True, settings=settings) @main.post("/api/import-settings") def save_import_settings(): """ - Save import filter settings. - Settings are stored in /import/settings.json and loaded by the importer. + Save import filter settings to database. + Settings are loaded by the Celery workers via load_import_settings(). """ - settings = { - "min_width": request.form.get("min_width", 0, type=int), - "min_height": request.form.get("min_height", 0, type=int), - "skip_transparent": request.form.get("skip_transparent") == "true", - "transparency_threshold": request.form.get("transparency_threshold", 0.9, type=float), - "phash_threshold": request.form.get("phash_threshold", 2, type=int), - } + from app.utils.version_migration import set_setting + + min_width = request.form.get("min_width", 0, type=int) + min_height = request.form.get("min_height", 0, type=int) + skip_transparent = request.form.get("skip_transparent") == "true" + transparency_threshold = request.form.get("transparency_threshold", 0.9, type=float) + phash_threshold = request.form.get("phash_threshold", 10, type=int) - import json - settings_path = "/import/settings.json" try: - with open(settings_path, "w") as f: - json.dump(settings, f, indent=2) + set_setting("import_min_width", str(min_width)) + set_setting("import_min_height", str(min_height)) + set_setting("import_skip_transparent", str(skip_transparent).lower()) + set_setting("import_transparency_threshold", str(transparency_threshold)) + set_setting("phash_threshold", str(phash_threshold)) + + settings = { + "min_width": min_width, + "min_height": min_height, + "skip_transparent": skip_transparent, + "transparency_threshold": transparency_threshold, + "phash_threshold": phash_threshold, + } return jsonify(ok=True, settings=settings) except Exception as e: return jsonify(ok=False, error=str(e)), 500 diff --git a/app/static/js/bulk-select.js b/app/static/js/bulk-select.js index ccc1a1b..1c4d542 100644 --- a/app/static/js/bulk-select.js +++ b/app/static/js/bulk-select.js @@ -262,7 +262,15 @@ // Autocomplete // --------------------------- function setupAutocomplete() { - if (!dom.tagInput || !dom.autocomplete) return; + if (!dom.tagInput) return; + + // Move autocomplete dropdown to body to escape transform containing block + // (CSS transform on parent creates new stacking context that breaks fixed positioning) + if (dom.autocomplete && dom.autocomplete.parentNode !== document.body) { + document.body.appendChild(dom.autocomplete); + } + + if (!dom.autocomplete) return; // Show autocomplete on focus dom.tagInput.addEventListener('focus', () => { diff --git a/app/static/style.css b/app/static/style.css index 8ca997f..9f7a1b1 100644 --- a/app/static/style.css +++ b/app/static/style.css @@ -1509,31 +1509,24 @@ select.form-input optgroup { } /*------------------------------------------------------------------------------ - Gallery Header Layout + Floating Select Button (Gallery) ------------------------------------------------------------------------------*/ -.gallery-header { - display: flex; - justify-content: space-between; - align-items: flex-start; - flex-wrap: wrap; - gap: 1rem; - margin-bottom: 1rem; +.floating-select-btn { + position: fixed; + top: 0.75rem; + right: 1rem; + z-index: 1001; } -.gallery-header-left { - display: flex; - flex-direction: column; - gap: 0.5rem; +.floating-select-btn .btn { + box-shadow: var(--shadow-2); } -.gallery-header-left h1 { - margin: 0; -} - -.gallery-header-actions { - display: flex; - gap: 0.5rem; - align-items: center; +@media (max-width: 640px) { + .floating-select-btn { + top: 0.5rem; + right: 0.5rem; + } } /*------------------------------------------------------------------------------ @@ -1723,13 +1716,4 @@ body.select-mode .gallery-item:hover { .bulk-editor-panel.active { transform: translateY(0); } - - .gallery-header { - flex-direction: column; - align-items: stretch; - } - - .gallery-header-actions { - justify-content: flex-end; - } } diff --git a/app/templates/gallery.html b/app/templates/gallery.html index b44da78..beea1ab 100644 --- a/app/templates/gallery.html +++ b/app/templates/gallery.html @@ -3,6 +3,11 @@ {% block title %}Gallery{% endblock %} {% block content %} + +