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.
This commit is contained in:
luciano committed 2026-08-02 11:59:59 +02:00
1 parent f0a29f7ca7
commit e10466d93b
3 files changed
+199 -1

No files matched your search

+67
View File
@@ -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;
}
+131
View File
@@ -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) => ({
"&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;"
}[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 = `
<input type="radio" name="upgradePickerRadio" value="${i}" />
<div class="upgrade-picker-info">
<div class="upgrade-picker-title">${_escapeHtml(_basename(c.path))}</div>
<div class="upgrade-picker-meta">${_escapeHtml(kbps)} · ${_escapeHtml(_fmtBytes(c.size))} · similarity ${simPct}%</div>
<div class="upgrade-picker-path">${_escapeHtml(c.path || "")}</div>
</div>
`;
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
// ============================================================