feat: upgrade archivio con auto-pick, audio preview, cancel-all (v1.9.4)
Tab Upgrade: - Checkbox "Scelta automatica" per skippare il modal quando ci sono multi-match (usa bitrate DESC) - Anteprima audio nel picker: play button sul source + ogni candidato via bridge read_audio_data_url (WKWebView blocca file://) - Bottone "Annulla tutto" nel picker → interrompe l'intera coda - Fix mtime: shutil.copy (non copy2) così il file upgradato ha data odierna nel Finder - Fix stop audio → non triggera più toast di errore - Modal picker più largo (620-780px) con footer wrap Extra: campo release_date estratto anche in Beatport (publish_date) e YouTube (upload_date) — completato scaffold sort colonna Data
This commit is contained in:
1 parent
e10466d93b
commit
e7fde842f2
8 files changed
+219
-13
No files matched your search
+44
-5
@@ -887,6 +887,7 @@ class Api:
|
|||||||
|
|
||||||
directory = (payload.get("directory") or "").strip()
|
directory = (payload.get("directory") or "").strip()
|
||||||
archive_dir = (payload.get("archive_dir") or "").strip() or None
|
archive_dir = (payload.get("archive_dir") or "").strip() or None
|
||||||
|
archive_auto_pick = bool(payload.get("archive_auto_pick", False))
|
||||||
recursive = bool(payload.get("recursive", False))
|
recursive = bool(payload.get("recursive", False))
|
||||||
try:
|
try:
|
||||||
threshold = int(payload.get("threshold", 310))
|
threshold = int(payload.get("threshold", 310))
|
||||||
@@ -912,7 +913,7 @@ class Api:
|
|||||||
|
|
||||||
self._upgrade_thread = threading.Thread(
|
self._upgrade_thread = threading.Thread(
|
||||||
target=self._upgrade_worker,
|
target=self._upgrade_worker,
|
||||||
args=(directory, threshold, cookies_path, recursive, archive_dir),
|
args=(directory, threshold, cookies_path, recursive, archive_dir, archive_auto_pick),
|
||||||
daemon=True,
|
daemon=True,
|
||||||
)
|
)
|
||||||
self._upgrade_thread.start()
|
self._upgrade_thread.start()
|
||||||
@@ -929,6 +930,32 @@ class Api:
|
|||||||
self._log("upgrade", "[INFO] Interruzione richiesta...")
|
self._log("upgrade", "[INFO] Interruzione richiesta...")
|
||||||
return {"ok": True}
|
return {"ok": True}
|
||||||
|
|
||||||
|
def read_audio_data_url(self, path: str) -> dict:
|
||||||
|
"""Legge un file audio locale e lo ritorna come data URL base64
|
||||||
|
per il preview HTML5 nel modal upgrade. WKWebView (macOS pywebview)
|
||||||
|
blocca `file://` da HTML servito via file://, quindi passiamo dal
|
||||||
|
bridge. Limite: 60MB per non esplodere la memoria JS."""
|
||||||
|
import base64 as _b64
|
||||||
|
MAX = 60 * 1024 * 1024
|
||||||
|
try:
|
||||||
|
p = Path(path or "")
|
||||||
|
if not p.exists() or not p.is_file():
|
||||||
|
return {"ok": False, "error": "File non trovato"}
|
||||||
|
size = p.stat().st_size
|
||||||
|
if size > MAX:
|
||||||
|
return {"ok": False, "error": f"File troppo grande ({size // 1024 // 1024}MB, max 60MB)"}
|
||||||
|
ext = p.suffix.lower().lstrip(".")
|
||||||
|
mime_map = {
|
||||||
|
"mp3": "audio/mpeg", "m4a": "audio/mp4", "aac": "audio/aac",
|
||||||
|
"wav": "audio/wav", "flac": "audio/flac", "ogg": "audio/ogg",
|
||||||
|
"opus": "audio/opus", "webm": "audio/webm",
|
||||||
|
}
|
||||||
|
mime = mime_map.get(ext, "audio/mpeg")
|
||||||
|
b64 = _b64.b64encode(p.read_bytes()).decode("ascii")
|
||||||
|
return {"ok": True, "data_url": f"data:{mime};base64,{b64}"}
|
||||||
|
except Exception as e:
|
||||||
|
return {"ok": False, "error": str(e)}
|
||||||
|
|
||||||
def upgrade_resolve_candidates(self, choice: dict) -> dict:
|
def upgrade_resolve_candidates(self, choice: dict) -> dict:
|
||||||
"""Riceve la scelta utente dal modal 'match locali multipli' e la
|
"""Riceve la scelta utente dal modal 'match locali multipli' e la
|
||||||
deposita nella queue attesa dal resolve_callback del worker.
|
deposita nella queue attesa dal resolve_callback del worker.
|
||||||
@@ -949,7 +976,8 @@ class Api:
|
|||||||
return {"ok": False, "error": str(e)}
|
return {"ok": False, "error": str(e)}
|
||||||
|
|
||||||
def _upgrade_worker(self, directory, threshold, cookies_path, recursive,
|
def _upgrade_worker(self, directory, threshold, cookies_path, recursive,
|
||||||
archive_dir: Optional[str] = None):
|
archive_dir: Optional[str] = None,
|
||||||
|
archive_auto_pick: bool = False):
|
||||||
view = "upgrade"
|
view = "upgrade"
|
||||||
|
|
||||||
def progress_cb(idx, total, filename, status, old_kbps, new_kbps):
|
def progress_cb(idx, total, filename, status, old_kbps, new_kbps):
|
||||||
@@ -999,10 +1027,21 @@ class Api:
|
|||||||
|
|
||||||
self._emit("upgrade:progress", payload_evt)
|
self._emit("upgrade:progress", payload_evt)
|
||||||
|
|
||||||
def resolve_cb(filename: str, candidates: list) -> dict:
|
def resolve_cb(source_path: str, candidates: list) -> dict:
|
||||||
"""Bloccante: emette evento e attende scelta utente via queue."""
|
"""Bloccante: emette evento e attende scelta utente via queue.
|
||||||
|
Se archive_auto_pick=True, salta il modal e sceglie il primo
|
||||||
|
candidato (già ordinato per bitrate DESC nel core).
|
||||||
|
`source_path` è il path assoluto del file sorgente (per il preview
|
||||||
|
audio nel modal)."""
|
||||||
|
src_name = Path(source_path).name if source_path else ""
|
||||||
|
if archive_auto_pick and candidates:
|
||||||
|
top = candidates[0]
|
||||||
|
path = top.get("path") if isinstance(top, dict) else str(top)
|
||||||
|
self._log(view, f"[AUTO] {src_name}: match multipli, uso {Path(path).name}")
|
||||||
|
return {"action": "use_local", "path": path}
|
||||||
self._emit("upgrade:candidates_needed", {
|
self._emit("upgrade:candidates_needed", {
|
||||||
"file": filename,
|
"file": src_name,
|
||||||
|
"source_path": source_path,
|
||||||
"candidates": candidates,
|
"candidates": candidates,
|
||||||
})
|
})
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ class BeatportTrack:
|
|||||||
duration_sec: int
|
duration_sec: int
|
||||||
beatport_id: int
|
beatport_id: int
|
||||||
image_url: str = "" # URL cover art (default vuoto per retrocompatibilità test)
|
image_url: str = "" # URL cover art (default vuoto per retrocompatibilità test)
|
||||||
|
release_date: str = "" # ISO YYYY-MM-DD, vuoto se mancante
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def display(self) -> str:
|
def display(self) -> str:
|
||||||
@@ -177,6 +178,7 @@ def _parse_tracks(data: dict) -> list:
|
|||||||
duration_sec=length_ms // 1000,
|
duration_sec=length_ms // 1000,
|
||||||
beatport_id=int(item.get("id") or 0),
|
beatport_id=int(item.get("id") or 0),
|
||||||
image_url=_extract_image_url(item.get("image")),
|
image_url=_extract_image_url(item.get("image")),
|
||||||
|
release_date=str(item.get("publish_date") or item.get("new_release_date") or "").strip(),
|
||||||
)
|
)
|
||||||
except (TypeError, ValueError) as e:
|
except (TypeError, ValueError) as e:
|
||||||
raise BeatportParseError(f"track[{i}] shape inattesa: {e}") from e
|
raise BeatportParseError(f"track[{i}] shape inattesa: {e}") from e
|
||||||
|
|||||||
+1
-1
@@ -5,7 +5,7 @@ import os
|
|||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
VERSION = "v1.9.3"
|
VERSION = "v1.9.4"
|
||||||
|
|
||||||
|
|
||||||
APP_NAME = "MusicTools"
|
APP_NAME = "MusicTools"
|
||||||
|
|||||||
+5
-2
@@ -188,7 +188,10 @@ def _copy_or_convert_to_mp3(
|
|||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
if src.suffix.lower() == ".mp3":
|
if src.suffix.lower() == ".mp3":
|
||||||
shutil.copy2(str(src), str(dst))
|
# shutil.copy (non copy2): mtime del target = ora, non quello
|
||||||
|
# del sorgente. Così è chiaro nel Finder che il file è stato
|
||||||
|
# aggiornato dall'upgrade.
|
||||||
|
shutil.copy(str(src), str(dst))
|
||||||
return dst.exists()
|
return dst.exists()
|
||||||
# Convert non-mp3 -> mp3 320k
|
# Convert non-mp3 -> mp3 320k
|
||||||
ffmpeg_bin = find_ffmpeg() or "ffmpeg"
|
ffmpeg_bin = find_ffmpeg() or "ffmpeg"
|
||||||
@@ -431,7 +434,7 @@ def upgrade_folder(
|
|||||||
progress_callback(processed, total, filepath.name,
|
progress_callback(processed, total, filepath.name,
|
||||||
"resolve_wait", current_kbps, 0)
|
"resolve_wait", current_kbps, 0)
|
||||||
try:
|
try:
|
||||||
choice = resolve_callback(filepath.name, cand_payload) or {}
|
choice = resolve_callback(str(filepath), cand_payload) or {}
|
||||||
except Exception:
|
except Exception:
|
||||||
choice = {}
|
choice = {}
|
||||||
action = (choice.get("action") or "").strip()
|
action = (choice.get("action") or "").strip()
|
||||||
|
|||||||
@@ -63,6 +63,14 @@ def search_youtube(query: str, limit: int = 50) -> list:
|
|||||||
video_id = e.get("id") or ""
|
video_id = e.get("id") or ""
|
||||||
url = e.get("url") or (f"https://www.youtube.com/watch?v={video_id}" if video_id else "")
|
url = e.get("url") or (f"https://www.youtube.com/watch?v={video_id}" if video_id else "")
|
||||||
image_url = f"https://i.ytimg.com/vi/{video_id}/mqdefault.jpg" if video_id else ""
|
image_url = f"https://i.ytimg.com/vi/{video_id}/mqdefault.jpg" if video_id else ""
|
||||||
|
# yt-dlp `upload_date` è YYYYMMDD (stringa). Lo converto in ISO YYYY-MM-DD.
|
||||||
|
# Con --flat-playlist può essere assente; in tal caso resta stringa vuota.
|
||||||
|
raw_date = str(e.get("upload_date") or "").strip()
|
||||||
|
release_date = ""
|
||||||
|
if len(raw_date) == 8 and raw_date.isdigit():
|
||||||
|
release_date = f"{raw_date[0:4]}-{raw_date[4:6]}-{raw_date[6:8]}"
|
||||||
|
elif raw_date:
|
||||||
|
release_date = raw_date # forma sconosciuta, passa così
|
||||||
result.append({
|
result.append({
|
||||||
"id": video_id,
|
"id": video_id,
|
||||||
"url": url,
|
"url": url,
|
||||||
@@ -70,5 +78,6 @@ def search_youtube(query: str, limit: int = 50) -> list:
|
|||||||
"channel": e.get("uploader") or e.get("channel") or "",
|
"channel": e.get("uploader") or e.get("channel") or "",
|
||||||
"duration_sec": int(e.get("duration") or 0),
|
"duration_sec": int(e.get("duration") or 0),
|
||||||
"image_url": image_url,
|
"image_url": image_url,
|
||||||
|
"release_date": release_date,
|
||||||
})
|
})
|
||||||
return result
|
return result
|
||||||
@@ -1680,8 +1680,21 @@ input[type="number"]::-webkit-inner-spin-button {
|
|||||||
}
|
}
|
||||||
.beatport-table .col-pos { width: 40px; color: var(--text-3); font-variant-numeric: tabular-nums; }
|
.beatport-table .col-pos { width: 40px; color: var(--text-3); font-variant-numeric: tabular-nums; }
|
||||||
.beatport-table .col-dur { width: 70px; color: var(--text-2); font-variant-numeric: tabular-nums; text-align: right; }
|
.beatport-table .col-dur { width: 70px; color: var(--text-2); font-variant-numeric: tabular-nums; text-align: right; }
|
||||||
|
.beatport-table .col-date { width: 100px; color: var(--text-2); font-variant-numeric: tabular-nums; font-size: 12px; white-space: nowrap; }
|
||||||
.beatport-table .col-state { width: 130px; font-size: 12px; }
|
.beatport-table .col-state { width: 130px; font-size: 12px; }
|
||||||
|
|
||||||
|
.beatport-table thead th.sortable {
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
transition: color 0.15s ease;
|
||||||
|
}
|
||||||
|
.beatport-table thead th.sortable:hover { color: var(--text); }
|
||||||
|
.beatport-table thead th .sort-arrow {
|
||||||
|
font-size: 10px;
|
||||||
|
margin-left: 4px;
|
||||||
|
color: var(--indigo-text);
|
||||||
|
}
|
||||||
|
|
||||||
.beatport-table input[type="checkbox"] {
|
.beatport-table input[type="checkbox"] {
|
||||||
width: 16px;
|
width: 16px;
|
||||||
height: 16px;
|
height: 16px;
|
||||||
@@ -1720,6 +1733,17 @@ input[type="number"]::-webkit-inner-spin-button {
|
|||||||
white-space: normal;
|
white-space: normal;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Modal picker: più largo dei modal standard + footer con wrap
|
||||||
|
per far entrare i 4 bottoni senza troncare */
|
||||||
|
#upgradePickerModal .modal {
|
||||||
|
min-width: 620px;
|
||||||
|
max-width: 780px;
|
||||||
|
}
|
||||||
|
#upgradePickerModal .modal-foot {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
.modal-sub {
|
.modal-sub {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: var(--text-2);
|
color: var(--text-2);
|
||||||
@@ -1757,6 +1781,33 @@ input[type="number"]::-webkit-inner-spin-button {
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
.upgrade-picker-play {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
.upgrade-preview-btn {
|
||||||
|
width: 34px;
|
||||||
|
height: 34px;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
background: var(--bg-input, transparent);
|
||||||
|
color: var(--text);
|
||||||
|
font-size: 13px;
|
||||||
|
cursor: pointer;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 0;
|
||||||
|
margin-left: 6px;
|
||||||
|
transition: background 0.15s ease, transform 0.1s ease;
|
||||||
|
}
|
||||||
|
.upgrade-preview-btn:hover { background: var(--bg-input-hover, rgba(255,255,255,0.05)); }
|
||||||
|
.upgrade-preview-btn.playing {
|
||||||
|
background: var(--green, #1DB954);
|
||||||
|
color: #000;
|
||||||
|
border-color: var(--green, #1DB954);
|
||||||
|
}
|
||||||
.upgrade-picker-title {
|
.upgrade-picker-title {
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
|
|||||||
+7
-2
@@ -280,9 +280,13 @@
|
|||||||
<button class="btn btn-ghost pill btn-sm" id="upArchiveBrowseBtn">📂 Sfoglia</button>
|
<button class="btn btn-ghost pill btn-sm" id="upArchiveBrowseBtn">📂 Sfoglia</button>
|
||||||
<button class="btn btn-ghost pill btn-sm" id="upArchiveClearBtn" hidden>Rimuovi</button>
|
<button class="btn btn-ghost pill btn-sm" id="upArchiveClearBtn" hidden>Rimuovi</button>
|
||||||
</div>
|
</div>
|
||||||
|
<label style="display:flex;gap:6px;align-items:center;margin-top:8px;font-size:13px;color:var(--text-2);">
|
||||||
|
<input type="checkbox" id="upArchiveAutoPick"/>
|
||||||
|
<span>Scelta automatica se ci sono più match (usa quello a bitrate più alto)</span>
|
||||||
|
</label>
|
||||||
<div class="hint">
|
<div class="hint">
|
||||||
<span class="hint-ico">ⓘ</span>
|
<span class="hint-ico">ⓘ</span>
|
||||||
<div>Se selezionata, l'app cerca versioni HQ del brano nella tua libreria locale prima di scaricare da YouTube. In caso di match multipli scegli tu quale usare.</div>
|
<div>Se selezionata, l'app cerca versioni HQ del brano nella tua libreria locale prima di scaricare da YouTube. In caso di match multipli scegli tu quale usare — oppure attiva "Scelta automatica" per non fermarti.</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1289,7 +1293,8 @@
|
|||||||
<div class="modal-foot">
|
<div class="modal-foot">
|
||||||
<button class="btn btn-primary pill" id="upgradePickerUseBtn" disabled>Usa selezionato</button>
|
<button class="btn btn-primary pill" id="upgradePickerUseBtn" disabled>Usa selezionato</button>
|
||||||
<button class="btn btn-ghost pill" id="upgradePickerYoutubeBtn">Cerca su YouTube</button>
|
<button class="btn btn-ghost pill" id="upgradePickerYoutubeBtn">Cerca su YouTube</button>
|
||||||
<button class="btn btn-danger pill" id="upgradePickerSkipBtn">Salta</button>
|
<button class="btn btn-ghost pill" id="upgradePickerSkipBtn">Salta</button>
|
||||||
|
<button class="btn btn-danger pill" id="upgradePickerCancelAllBtn">■ Annulla tutto</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+100
-3
@@ -776,6 +776,7 @@ $("#upgradeBtn").addEventListener("click", async () => {
|
|||||||
recursive: $("#upRecursive").checked,
|
recursive: $("#upRecursive").checked,
|
||||||
threshold: parseInt($("#upThreshold").value, 10) || 310,
|
threshold: parseInt($("#upThreshold").value, 10) || 310,
|
||||||
archive_dir: state.upArchiveDir || "",
|
archive_dir: state.upArchiveDir || "",
|
||||||
|
archive_auto_pick: $("#upArchiveAutoPick").checked,
|
||||||
});
|
});
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
if (!handleGateBlock(res)) toast(res.error || "Errore", "error");
|
if (!handleGateBlock(res)) toast(res.error || "Errore", "error");
|
||||||
@@ -809,14 +810,98 @@ function _escapeHtml(s) {
|
|||||||
}[c]));
|
}[c]));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Audio preview per il picker (single audio element condiviso) ---
|
||||||
|
let _upgradePreviewAudio = null;
|
||||||
|
let _upgradePreviewBtn = null; // bottone corrente in "playing" state
|
||||||
|
|
||||||
|
function _fileUrl(absPath) {
|
||||||
|
if (!absPath) return "";
|
||||||
|
// Split percorso e encode ogni segment (spazi → %20, unicode → utf-8 encoded)
|
||||||
|
const parts = String(absPath).split("/").map(encodeURIComponent);
|
||||||
|
return "file://" + parts.join("/");
|
||||||
|
}
|
||||||
|
|
||||||
|
function _stopUpgradePreview() {
|
||||||
|
if (_upgradePreviewAudio) {
|
||||||
|
// Rimuovi handler error PRIMA di pause/clear così stop manuale
|
||||||
|
// non fa scattare il toast di errore
|
||||||
|
_upgradePreviewAudio.onerror = null;
|
||||||
|
_upgradePreviewAudio.onended = null;
|
||||||
|
try { _upgradePreviewAudio.pause(); } catch {}
|
||||||
|
try { _upgradePreviewAudio.removeAttribute("src"); } catch {}
|
||||||
|
_upgradePreviewAudio = null;
|
||||||
|
}
|
||||||
|
if (_upgradePreviewBtn) {
|
||||||
|
_upgradePreviewBtn.textContent = "▶";
|
||||||
|
_upgradePreviewBtn.classList.remove("playing");
|
||||||
|
_upgradePreviewBtn = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function _makePreviewBtn(absPath) {
|
||||||
|
const btn = document.createElement("button");
|
||||||
|
btn.type = "button";
|
||||||
|
btn.className = "upgrade-preview-btn";
|
||||||
|
btn.textContent = "▶";
|
||||||
|
btn.title = "Anteprima audio";
|
||||||
|
btn.addEventListener("click", async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
// Se già in play su questo bottone → stop
|
||||||
|
if (_upgradePreviewBtn === btn) {
|
||||||
|
_stopUpgradePreview();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_stopUpgradePreview();
|
||||||
|
btn.textContent = "⏳";
|
||||||
|
btn.disabled = true;
|
||||||
|
let res;
|
||||||
|
try {
|
||||||
|
res = await window.pywebview.api.read_audio_data_url(absPath);
|
||||||
|
} catch (err) {
|
||||||
|
btn.textContent = "▶";
|
||||||
|
btn.disabled = false;
|
||||||
|
toast && toast("Errore lettura file: " + err, "error");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
btn.disabled = false;
|
||||||
|
if (!res || !res.ok) {
|
||||||
|
btn.textContent = "▶";
|
||||||
|
toast && toast(res && res.error || "Impossibile leggere il file", "error");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_upgradePreviewAudio = new Audio(res.data_url);
|
||||||
|
_upgradePreviewBtn = btn;
|
||||||
|
btn.textContent = "■";
|
||||||
|
btn.classList.add("playing");
|
||||||
|
_upgradePreviewAudio.onended = _stopUpgradePreview;
|
||||||
|
_upgradePreviewAudio.onerror = () => {
|
||||||
|
// Solo se il listener è ancora agganciato (stop manuale lo azzera prima)
|
||||||
|
const wasPlaying = !!_upgradePreviewAudio;
|
||||||
|
_stopUpgradePreview();
|
||||||
|
if (wasPlaying) toast && toast("Errore riproduzione audio", "error");
|
||||||
|
};
|
||||||
|
_upgradePreviewAudio.play().catch(() => _stopUpgradePreview());
|
||||||
|
});
|
||||||
|
return btn;
|
||||||
|
}
|
||||||
|
|
||||||
function openUpgradePicker(payload) {
|
function openUpgradePicker(payload) {
|
||||||
const modal = $("#upgradePickerModal");
|
const modal = $("#upgradePickerModal");
|
||||||
if (!modal) return;
|
if (!modal) return;
|
||||||
const file = (payload && payload.file) || "";
|
const file = (payload && payload.file) || "";
|
||||||
|
const sourcePath = (payload && payload.source_path) || "";
|
||||||
const candidates = (payload && payload.candidates) || [];
|
const candidates = (payload && payload.candidates) || [];
|
||||||
state.upPicker = { file, candidates, selectedIdx: -1 };
|
state.upPicker = { file, sourcePath, candidates, selectedIdx: -1 };
|
||||||
|
|
||||||
|
// Header: filename + play button per il source
|
||||||
|
const header = $("#upgradePickerFilename");
|
||||||
|
header.innerHTML = "";
|
||||||
|
const label = document.createElement("span");
|
||||||
|
label.textContent = "Brano da upgradare: " + file + " ";
|
||||||
|
header.appendChild(label);
|
||||||
|
if (sourcePath) header.appendChild(_makePreviewBtn(sourcePath));
|
||||||
|
|
||||||
$("#upgradePickerFilename").textContent = "Brano: " + file;
|
|
||||||
const list = $("#upgradePickerList");
|
const list = $("#upgradePickerList");
|
||||||
list.innerHTML = "";
|
list.innerHTML = "";
|
||||||
candidates.forEach((c, i) => {
|
candidates.forEach((c, i) => {
|
||||||
@@ -832,8 +917,13 @@ function openUpgradePicker(payload) {
|
|||||||
<div class="upgrade-picker-meta">${_escapeHtml(kbps)} · ${_escapeHtml(_fmtBytes(c.size))} · similarity ${simPct}%</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 class="upgrade-picker-path">${_escapeHtml(c.path || "")}</div>
|
||||||
</div>
|
</div>
|
||||||
|
<span class="upgrade-picker-play"></span>
|
||||||
`;
|
`;
|
||||||
row.addEventListener("click", () => {
|
const playSlot = row.querySelector(".upgrade-picker-play");
|
||||||
|
if (playSlot && c.path) playSlot.appendChild(_makePreviewBtn(c.path));
|
||||||
|
row.addEventListener("click", (e) => {
|
||||||
|
// Se il click viene dal bottone play, non selezionare la riga
|
||||||
|
if (e.target.closest(".upgrade-preview-btn")) return;
|
||||||
state.upPicker.selectedIdx = i;
|
state.upPicker.selectedIdx = i;
|
||||||
$("#upgradePickerUseBtn").disabled = false;
|
$("#upgradePickerUseBtn").disabled = false;
|
||||||
$$("#upgradePickerList .upgrade-picker-row").forEach((r) =>
|
$$("#upgradePickerList .upgrade-picker-row").forEach((r) =>
|
||||||
@@ -849,6 +939,7 @@ function openUpgradePicker(payload) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function closeUpgradePicker() {
|
function closeUpgradePicker() {
|
||||||
|
_stopUpgradePreview();
|
||||||
const modal = $("#upgradePickerModal");
|
const modal = $("#upgradePickerModal");
|
||||||
if (modal) modal.hidden = true;
|
if (modal) modal.hidden = true;
|
||||||
}
|
}
|
||||||
@@ -875,6 +966,12 @@ $("#upgradePickerSkipBtn")?.addEventListener("click", () => {
|
|||||||
_sendUpgradeChoice({ action: "skip" });
|
_sendUpgradeChoice({ action: "skip" });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$("#upgradePickerCancelAllBtn")?.addEventListener("click", async () => {
|
||||||
|
// Skip questo file per sbloccare il worker, poi ferma l'intera coda
|
||||||
|
_sendUpgradeChoice({ action: "skip" });
|
||||||
|
try { await window.pywebview.api.stop_upgrade(); } catch {}
|
||||||
|
});
|
||||||
|
|
||||||
$("#upgradePickerCloseBtn")?.addEventListener("click", () => {
|
$("#upgradePickerCloseBtn")?.addEventListener("click", () => {
|
||||||
_sendUpgradeChoice({ action: "skip" });
|
_sendUpgradeChoice({ action: "skip" });
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in new issue
Block a user