fix(purchases): persist verbose log toggle, fix dropdown stacking, wire Add to Library button

- Verbose capture toggle now persists to localStorage across sessions
- PurchasesView dropdown menus now have mutual exclusion (opening one closes others)
- Add to Library button wired to v2_library_add_folder with download destination tracking
- Added i18n keys for add-to-library success/error toasts (all 4 locales)
This commit is contained in:
vicrodh committed 2026-02-20 14:46:03 -06:00
1 parent 8051f1745b
commit 3f17c83847
8 files changed
+56 -9

No files matched your search

@@ -2,6 +2,7 @@
import { onMount } from 'svelte';
import { get } from 'svelte/store';
import { t } from '$lib/i18n';
import { invoke } from '@tauri-apps/api/core';
import { ArrowLeft, Download, Check, Loader2, AlertTriangle, Library, Play } from 'lucide-svelte';
import QualityBadge from '../QualityBadge.svelte';
import Dropdown from '../Dropdown.svelte';
@@ -10,6 +11,7 @@
import { purchaseDownloads, startAlbumDownload, startTrackDownload } from '$lib/stores/purchaseDownloadStore';
import type { TrackDownloadStatus } from '$lib/stores/purchaseDownloadStore';
import { formatDuration, getQobuzImage } from '$lib/adapters/qobuzAdapters';
import { showToast } from '$lib/stores/toastStore';
import type { PurchasedAlbum, PurchasedTrack, PurchaseFormatOption } from '$lib/types/purchases';
import type { DisplayTrack } from '$lib/types';
@@ -44,6 +46,23 @@
const downloadStatuses = $derived(albumDlState?.trackStatuses ?? {});
const isDownloadingAll = $derived(albumDlState?.isDownloadingAll ?? false);
const allComplete = $derived(albumDlState?.allComplete ?? false);
const downloadDestination = $derived(albumDlState?.destination ?? null);
let addingToLibrary = $state(false);
async function handleAddToLibrary() {
if (!downloadDestination || addingToLibrary) return;
addingToLibrary = true;
try {
await invoke('v2_library_add_folder', { path: downloadDestination });
showToast(get(t)('purchases.addToLibrarySuccess'), 'success');
} catch (err) {
console.error('Failed to add folder to library:', err);
showToast(get(t)('purchases.addToLibraryError'), 'error');
} finally {
addingToLibrary = false;
}
}
function formatPurchaseDate(ts?: number): string {
if (!ts) return '';
@@ -301,10 +320,14 @@
{/if}
<!-- Add to Library -->
{#if allComplete}
{#if allComplete && downloadDestination}
<div class="add-to-library">
<button class="add-to-library-btn">
<Library size={14} />
<button class="add-to-library-btn" onclick={handleAddToLibrary} disabled={addingToLibrary}>
{#if addingToLibrary}
<Loader2 size={14} class="spin" />
{:else}
<Library size={14} />
{/if}
<span>{$t('purchases.addToLibrary')}</span>
</button>
<span class="add-to-library-desc">{$t('purchases.addToLibraryDesc')}</span>
+13 -5
View File
@@ -101,6 +101,14 @@
$effect(() => { setUserItem('qbz-purchases-quality-filter', filterQuality); });
$effect(() => { setHideDownloaded(filterHideDownloaded); });
/** Close all dropdown menus (mutual exclusion) */
function closeAllMenus() {
showAlbumGroupMenu = false;
showAlbumSortMenu = false;
showFilterPanel = false;
showTrackGroupMenu = false;
}
const albumSortOptions = [
{ value: 'date' as SortBy, labelKey: 'purchases.sort.date' },
{ value: 'artist' as SortBy, labelKey: 'purchases.sort.artist' },
@@ -501,7 +509,7 @@
<div class="toolbar-controls">
<!-- Group dropdown -->
<div class="dropdown-container">
<button class="control-btn" onclick={() => (showAlbumGroupMenu = !showAlbumGroupMenu)}>
<button class="control-btn" onclick={() => { const wasOpen = showAlbumGroupMenu; closeAllMenus(); showAlbumGroupMenu = !wasOpen; }}>
<span>{!albumGroupingEnabled
? $t('purchases.group.off')
: albumGroupMode === 'alpha'
@@ -538,7 +546,7 @@
<!-- Sort dropdown -->
<div class="dropdown-container">
<button class="control-btn" onclick={() => (showAlbumSortMenu = !showAlbumSortMenu)}>
<button class="control-btn" onclick={() => { const wasOpen = showAlbumSortMenu; closeAllMenus(); showAlbumSortMenu = !wasOpen; }}>
<span>{$t('purchases.sortLabel')}: {$t(albumSortOptions.find(option => option.value === albumSortBy)?.labelKey ?? 'purchases.sort.date')}</span>
<ChevronDown size={14} />
</button>
@@ -565,7 +573,7 @@
<button
class="control-btn icon-only"
class:active={hasActiveFilters}
onclick={() => (showFilterPanel = !showFilterPanel)}
onclick={() => { const wasOpen = showFilterPanel; closeAllMenus(); showFilterPanel = !wasOpen; }}
title={$t('library.filters')}
>
<svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor">
@@ -653,7 +661,7 @@
<div class="toolbar-controls">
<!-- Track group dropdown -->
<div class="dropdown-container">
<button class="control-btn" onclick={() => (showTrackGroupMenu = !showTrackGroupMenu)}>
<button class="control-btn" onclick={() => { const wasOpen = showTrackGroupMenu; closeAllMenus(); showTrackGroupMenu = !wasOpen; }}>
<span>
{trackGroupingEnabled
? trackGroupMode === 'album'
@@ -704,7 +712,7 @@
<button
class="control-btn icon-only"
class:active={hasActiveFilters}
onclick={() => (showFilterPanel = !showFilterPanel)}
onclick={() => { const wasOpen = showFilterPanel; closeAllMenus(); showFilterPanel = !wasOpen; }}
title={$t('library.filters')}
>
<svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor">
+2
View File
@@ -1446,6 +1446,8 @@
"purchasedOn": "Gekauft:",
"addToLibrary": "Zur lokalen Bibliothek hinzufügen",
"addToLibraryDesc": "Den heruntergeladenen Ordner zu deiner lokalen Musikbibliothek hinzufügen.",
"addToLibrarySuccess": "Ordner zur lokalen Bibliothek hinzugefuegt.",
"addToLibraryError": "Fehler beim Hinzufuegen des Ordners zur Bibliothek.",
"downloading": "Wird heruntergeladen...",
"downloadProgress": "Herunterladen {current}/{total}...",
"complete": "Download abgeschlossen",
+2
View File
@@ -1646,6 +1646,8 @@
"purchasedOn": "Purchased:",
"addToLibrary": "Add to Local Library",
"addToLibraryDesc": "Add the downloaded folder to your local music library.",
"addToLibrarySuccess": "Folder added to local library.",
"addToLibraryError": "Failed to add folder to library.",
"downloading": "Downloading...",
"downloadProgress": "Downloading {current}/{total}...",
"complete": "Download complete",
+2
View File
@@ -1646,6 +1646,8 @@
"purchasedOn": "Comprado:",
"addToLibrary": "Agregar a Biblioteca Local",
"addToLibraryDesc": "Agregar la carpeta descargada a tu biblioteca de musica local.",
"addToLibrarySuccess": "Carpeta agregada a la biblioteca local.",
"addToLibraryError": "Error al agregar la carpeta a la biblioteca.",
"downloading": "Descargando...",
"downloadProgress": "Descargando {current}/{total}...",
"complete": "Descarga completa",
+2
View File
@@ -1446,6 +1446,8 @@
"purchasedOn": "Acheté :",
"addToLibrary": "Ajouter à la bibliothèque locale",
"addToLibraryDesc": "Ajouter le dossier téléchargé à votre bibliothèque musicale locale.",
"addToLibrarySuccess": "Dossier ajoute a la bibliotheque locale.",
"addToLibraryError": "Erreur lors de l'ajout du dossier a la bibliotheque.",
"downloading": "Téléchargement...",
"downloadProgress": "Téléchargement {current}/{total}...",
"complete": "Téléchargement terminé",
+7 -1
View File
@@ -11,6 +11,8 @@
* (reduces GC/CPU overhead in production)
*/
import { getUserItem, setUserItem } from '$lib/utils/userStorage';
interface ConsoleEntry {
level: 'log' | 'warn' | 'error' | 'info';
timestamp: string;
@@ -21,9 +23,11 @@ const MAX_ENTRIES = 2000;
let entries: ConsoleEntry[] = [];
let initialized = false;
const VERBOSE_CAPTURE_KEY = 'qbz-verbose-capture';
// Verbose capture mode - when true, captures log/info even in production
// Can be enabled via Developer Mode for debugging production issues
let verboseCapture = false;
let verboseCapture = getUserItem(VERBOSE_CAPTURE_KEY) === 'true';
// Keep references to the real console methods
const originalConsole = {
@@ -96,12 +100,14 @@ export function initConsoleCapture() {
/** Enable verbose capture (log/info) in production - for debugging */
export function enableVerboseCapture() {
verboseCapture = true;
setUserItem(VERBOSE_CAPTURE_KEY, 'true');
originalConsole.info('[ConsoleCapture] Verbose capture enabled');
}
/** Disable verbose capture (log/info) in production */
export function disableVerboseCapture() {
verboseCapture = false;
setUserItem(VERBOSE_CAPTURE_KEY, 'false');
originalConsole.info('[ConsoleCapture] Verbose capture disabled');
}
+2
View File
@@ -7,6 +7,7 @@ interface AlbumDownloadState {
trackStatuses: Record<number, TrackDownloadStatus>;
isDownloadingAll: boolean;
allComplete: boolean;
destination?: string;
}
// Persistent store: albumId -> download state (survives component unmount)
@@ -47,6 +48,7 @@ export function startAlbumDownload(
trackStatuses: {},
isDownloadingAll: true,
allComplete: false,
destination,
}));
executeAlbumDownload(albumId, trackIds, formatId, destination);
}