feat(TagEditor): show real-time progress when writing tags

Backend:
- Emit 'library:tag_write_progress' events with current/total count
- Progress updates as each file is written

Frontend:
- Listen for progress events during direct write mode
- Save button shows "Writing X of Y..." dynamically
- Cleanup listener on completion or error
This commit is contained in:
vicrodh committed 2026-01-26 20:46:38 -06:00
1 parent f1ff7d4698
commit 65adf352b6
2 files changed
+29 -2

No files matched your search

+11 -1
View File
@@ -6,7 +6,7 @@ use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc; use std::sync::Arc;
use serde::Deserialize; use serde::Deserialize;
use tauri::State; use tauri::{Emitter, State};
use tokio::sync::Mutex; use tokio::sync::Mutex;
use crate::discogs::DiscogsClient; use crate::discogs::DiscogsClient;
@@ -1616,6 +1616,7 @@ pub async fn library_update_album_metadata(
#[tauri::command] #[tauri::command]
pub async fn library_write_album_metadata_to_files( pub async fn library_write_album_metadata_to_files(
app: tauri::AppHandle,
request: LibraryAlbumMetadataUpdateRequest, request: LibraryAlbumMetadataUpdateRequest,
state: State<'_, LibraryState>, state: State<'_, LibraryState>,
) -> Result<(), String> { ) -> Result<(), String> {
@@ -1663,7 +1664,16 @@ pub async fn library_write_album_metadata_to_files(
by_file.entry(track.file_path.clone()).or_insert(track); by_file.entry(track.file_path.clone()).or_insert(track);
} }
let total = by_file.len();
let mut current = 0usize;
for (file_path, track) in by_file { for (file_path, track) in by_file {
current += 1;
// Emit progress event
let _ = app.emit("library:tag_write_progress", serde_json::json!({
"current": current,
"total": total
}));
let path = Path::new(&file_path); let path = Path::new(&file_path);
if !path.is_file() { if !path.is_file() {
return Err("One or more audio files were not found on disk.".to_string()); return Err("One or more audio files were not found on disk.".to_string());
@@ -1,6 +1,7 @@
<script lang="ts"> <script lang="ts">
import Modal from './Modal.svelte'; import Modal from './Modal.svelte';
import { invoke } from '@tauri-apps/api/core'; import { invoke } from '@tauri-apps/api/core';
import { listen, type UnlistenFn } from '@tauri-apps/api/event';
import { ask } from '@tauri-apps/plugin-dialog'; import { ask } from '@tauri-apps/plugin-dialog';
import { openUrl } from '@tauri-apps/plugin-opener'; import { openUrl } from '@tauri-apps/plugin-opener';
import { showToast } from '$lib/stores/toastStore'; import { showToast } from '$lib/stores/toastStore';
@@ -90,6 +91,7 @@
let albumTotalDiscs = $state(1); let albumTotalDiscs = $state(1);
let persistence: PersistenceMode = $state('sidecar'); let persistence: PersistenceMode = $state('sidecar');
let saving = $state(false); let saving = $state(false);
let writeProgress = $state<{ current: number; total: number } | null>(null);
// Remote metadata search state // Remote metadata search state
let remoteProvider: RemoteProvider = $state('musicbrainz'); let remoteProvider: RemoteProvider = $state('musicbrainz');
@@ -345,10 +347,17 @@
if (!payload) return; if (!payload) return;
saving = true; saving = true;
writeProgress = null;
let unlisten: UnlistenFn | null = null;
try { try {
if (persistence === 'sidecar') { if (persistence === 'sidecar') {
await invoke('library_update_album_metadata', { request: payload }); await invoke('library_update_album_metadata', { request: payload });
} else { } else {
// Listen for progress events
unlisten = await listen<{ current: number; total: number }>('library:tag_write_progress', (event) => {
writeProgress = event.payload;
});
await invoke('library_write_album_metadata_to_files', { request: payload }); await invoke('library_write_album_metadata_to_files', { request: payload });
} }
showToast('Album metadata saved', 'success'); showToast('Album metadata saved', 'success');
@@ -357,6 +366,8 @@
} catch (err) { } catch (err) {
alert(`Failed to save metadata: ${err}`); alert(`Failed to save metadata: ${err}`);
} finally { } finally {
if (unlisten) unlisten();
writeProgress = null;
saving = false; saving = false;
} }
} }
@@ -571,7 +582,13 @@
<button class="btn btn-primary" onclick={handleSave} disabled={saving}> <button class="btn btn-primary" onclick={handleSave} disabled={saving}>
{#if saving} {#if saving}
<span class="spinner-inline"></span> <span class="spinner-inline"></span>
{persistence === 'direct' ? 'Writing tags...' : 'Saving...'} {#if writeProgress}
Writing {writeProgress.current} of {writeProgress.total}...
{:else if persistence === 'direct'}
Writing tags...
{:else}
Saving...
{/if}
{:else} {:else}
Save Save
{/if} {/if}