From e10466d93b5ec4f06315ee43d641ee213ec1bf38 Mon Sep 17 00:00:00 2001 From: luciano Date: Sun, 2 Aug 2026 11:59:59 +0200 Subject: [PATCH] ui: UpgradeUI archive_dir + candidates picker + bump v1.9.3 Frontend Upgrade: - state.upArchiveDir + handler Sfoglia/Rimuovi per la seconda path-display - start_upgrade payload include archive_dir - bridge handler 'upgrade:candidates_needed' apre openUpgradePicker() con lista candidati (basename + bitrate + size + similarity + path) - 3 azioni utente: Usa selezionato / Cerca YouTube / Salta; ESC e click su backdrop = Salta. Chiusura implicita quando upgrade:done. - CSS minimale (.upgrade-picker-*) accodato dopo le sezioni esistenti. Version bump v1.9.2 -> v1.9.3. --- core/config.py | 2 +- webui/css/style.css | 67 ++++++++++++++++++++++ webui/js/app.js | 131 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 199 insertions(+), 1 deletion(-) diff --git a/core/config.py b/core/config.py index b75780b..842c7ff 100644 --- a/core/config.py +++ b/core/config.py @@ -5,7 +5,7 @@ import os import sys from pathlib import Path -VERSION = "v1.9.2" +VERSION = "v1.9.3" APP_NAME = "MusicTools" diff --git a/webui/css/style.css b/webui/css/style.css index 5c1ed70..f381aa4 100644 --- a/webui/css/style.css +++ b/webui/css/style.css @@ -1708,3 +1708,70 @@ input[type="number"]::-webkit-inner-spin-button { .hint-inline { margin-left: 0; } .beatport-table .col-state { width: 110px; } } + +/* =============================== + UPGRADE — picker modal (match locale) + =============================== */ +.modal-body.upgrade-picker-body { + /* Override del default .modal-body (mono + pre-wrap): qui è UI, non log */ + font-family: inherit; + font-size: 13px; + color: var(--text); + white-space: normal; + user-select: none; +} +.modal-sub { + font-size: 12px; + color: var(--text-2); + margin-bottom: 12px; + word-break: break-word; +} +.upgrade-picker-list { + display: flex; + flex-direction: column; + gap: 6px; + max-height: 380px; + overflow-y: auto; +} +.upgrade-picker-row { + display: flex; + align-items: flex-start; + gap: 10px; + padding: 10px 12px; + border: 1px solid var(--border); + border-radius: var(--r-md, 10px); + cursor: pointer; + transition: border-color 0.15s ease, background 0.15s ease; + background: var(--bg-input, transparent); +} +.upgrade-picker-row:hover { border-color: var(--text-2); } +.upgrade-picker-row.selected { + border-color: var(--blue, #3b82f6); + background: rgba(59, 130, 246, 0.08); +} +.upgrade-picker-row input[type="radio"] { + margin-top: 3px; + cursor: pointer; +} +.upgrade-picker-info { + flex: 1; + min-width: 0; +} +.upgrade-picker-title { + font-weight: 600; + color: var(--text); + word-break: break-word; +} +.upgrade-picker-meta { + font-size: 12px; + color: var(--text-2); + margin-top: 3px; + font-variant-numeric: tabular-nums; +} +.upgrade-picker-path { + font-size: 11px; + color: var(--text-3); + margin-top: 2px; + word-break: break-all; + font-family: "SF Mono", Menlo, Consolas, monospace; +} diff --git a/webui/js/app.js b/webui/js/app.js index 2ba9a1c..ccc8b80 100644 --- a/webui/js/app.js +++ b/webui/js/app.js @@ -12,6 +12,8 @@ const state = { loaded: null, // { kind: "urls"|"tracks", urls?, tracks?, count } dlOutputDir: "", upDir: "", + upArchiveDir: "", + upPicker: { file: "", candidates: [], selectedIdx: -1 }, videoOutputDir: "", showSecret: false, downloading: false, @@ -310,8 +312,13 @@ const bridgeHandlers = { state.upgrading = false; $("#upgradeBtn").disabled = false; $("#stopUpgradeBtn").disabled = true; + // Chiudi eventuale modal picker rimasto aperto + const modal = $("#upgradePickerModal"); + if (modal && !modal.hidden) modal.hidden = true; }, + "upgrade:candidates_needed": (p) => openUpgradePicker(p), + "video:progress": (p) => { if (typeof p.overall === "number") { const pct = Math.round(p.overall * 100); @@ -714,6 +721,26 @@ $("#upBrowseBtn").addEventListener("click", async () => { $("#upRecursive").addEventListener("change", refreshUpgradeScan); +// --- Cartella archivio (opzionale) --- +$("#upArchiveBrowseBtn").addEventListener("click", async () => { + const path = await window.pywebview.api.browse_directory(); + if (path) { + state.upArchiveDir = path; + const disp = $("#upArchivePathDisplay"); + disp.textContent = path; + disp.classList.remove("empty"); + $("#upArchiveClearBtn").hidden = false; + } +}); + +$("#upArchiveClearBtn").addEventListener("click", () => { + state.upArchiveDir = ""; + const disp = $("#upArchivePathDisplay"); + disp.textContent = "Nessuna cartella"; + disp.classList.add("empty"); + $("#upArchiveClearBtn").hidden = true; +}); + async function refreshUpgradeScan() { if (!state.upDir) return; const res = await window.pywebview.api.scan_audio_folder(state.upDir, $("#upRecursive").checked); @@ -748,6 +775,7 @@ $("#upgradeBtn").addEventListener("click", async () => { directory: state.upDir, recursive: $("#upRecursive").checked, threshold: parseInt($("#upThreshold").value, 10) || 310, + archive_dir: state.upArchiveDir || "", }); if (!res.ok) { if (!handleGateBlock(res)) toast(res.error || "Errore", "error"); @@ -760,6 +788,109 @@ $("#stopUpgradeBtn").addEventListener("click", async () => { $("#stopUpgradeBtn").disabled = true; }); +// --- Modal picker "Match locali multipli" --- +function _fmtBytes(n) { + if (!n || n < 0) return "—"; + if (n < 1024) return n + " B"; + if (n < 1024 * 1024) return (n / 1024).toFixed(1) + " KB"; + if (n < 1024 * 1024 * 1024) return (n / (1024 * 1024)).toFixed(1) + " MB"; + return (n / (1024 * 1024 * 1024)).toFixed(2) + " GB"; +} + +function _basename(p) { + if (!p) return ""; + const parts = String(p).split(/[/\\]/); + return parts[parts.length - 1] || p; +} + +function _escapeHtml(s) { + return String(s ?? "").replace(/[&<>"']/g, (c) => ({ + "&": "&", "<": "<", ">": ">", '"': """, "'": "'" + }[c])); +} + +function openUpgradePicker(payload) { + const modal = $("#upgradePickerModal"); + if (!modal) return; + const file = (payload && payload.file) || ""; + const candidates = (payload && payload.candidates) || []; + state.upPicker = { file, candidates, selectedIdx: -1 }; + + $("#upgradePickerFilename").textContent = "Brano: " + file; + const list = $("#upgradePickerList"); + list.innerHTML = ""; + candidates.forEach((c, i) => { + const row = document.createElement("label"); + row.className = "upgrade-picker-row"; + row.dataset.idx = String(i); + const simPct = Math.round((c.similarity || 0) * 100); + const kbps = c.bitrate ? `${c.bitrate}k` : "—"; + row.innerHTML = ` + +
+
${_escapeHtml(_basename(c.path))}
+
${_escapeHtml(kbps)} · ${_escapeHtml(_fmtBytes(c.size))} · similarity ${simPct}%
+
${_escapeHtml(c.path || "")}
+
+ `; + row.addEventListener("click", () => { + state.upPicker.selectedIdx = i; + $("#upgradePickerUseBtn").disabled = false; + $$("#upgradePickerList .upgrade-picker-row").forEach((r) => + r.classList.toggle("selected", r === row) + ); + const radio = row.querySelector("input[type=radio]"); + if (radio) radio.checked = true; + }); + list.appendChild(row); + }); + $("#upgradePickerUseBtn").disabled = true; + modal.hidden = false; +} + +function closeUpgradePicker() { + const modal = $("#upgradePickerModal"); + if (modal) modal.hidden = true; +} + +async function _sendUpgradeChoice(choice) { + try { + await window.pywebview.api.upgrade_resolve_candidates(choice); + } catch (_e) { /* backend può essere già terminato: ignora */ } + closeUpgradePicker(); +} + +$("#upgradePickerUseBtn")?.addEventListener("click", () => { + const idx = state.upPicker.selectedIdx; + if (idx < 0 || idx >= state.upPicker.candidates.length) return; + const path = state.upPicker.candidates[idx].path; + _sendUpgradeChoice({ action: "use_local", path }); +}); + +$("#upgradePickerYoutubeBtn")?.addEventListener("click", () => { + _sendUpgradeChoice({ action: "use_youtube" }); +}); + +$("#upgradePickerSkipBtn")?.addEventListener("click", () => { + _sendUpgradeChoice({ action: "skip" }); +}); + +$("#upgradePickerCloseBtn")?.addEventListener("click", () => { + _sendUpgradeChoice({ action: "skip" }); +}); + +$("#upgradePickerModal")?.addEventListener("click", (e) => { + // Click sul backdrop (non sulla card) → skip + if (e.target === e.currentTarget) _sendUpgradeChoice({ action: "skip" }); +}); + +document.addEventListener("keydown", (e) => { + if (e.key === "Escape") { + const modal = $("#upgradePickerModal"); + if (modal && !modal.hidden) _sendUpgradeChoice({ action: "skip" }); + } +}); + // ============================================================ // RECORDER tab // ============================================================