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:
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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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é",
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user