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:
1 parent
f0a29f7ca7
commit
e10466d93b
3 files changed
+199
-1
No files matched your search
@@ -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
@@ -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 = `
|
||||
<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
|
||||
// ============================================================
|
||||
|
||||
Reference in new issue
Block a user