feat: implement batch processing feature with tabbed interface

- Added `ActiveTab` enum to manage active tab state in `App`.
- Created `BatchItem` struct and integrated `batch_items` vector in `App`.
- Developed `BatchPanel` component for managing batch projects with inline forms.
- Implemented sequential processing in `App::start_batch_item()` with automatic advancement in `poll_background()`.
- Updated UI to display individual item states (` Aguardando | ⟳ Processando | ✓ Concluído | ⊸ Cancelado | ✗ Erro`).
- Blocked item addition/removal during processing.
- Enhanced session persistence to include batch projects in `SessionData`.
- Updated session save/load functions to handle new session structure.
This commit is contained in:
2026-02-28 20:28:24 -03:00
parent 5d8e92f33f
commit 121e919bbf
6 changed files with 880 additions and 124 deletions
+31 -9
View File
@@ -1,7 +1,17 @@
use crate::domain::entities::Project;
use anyhow::{Context, Result};
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
/// Dados de sessão persistidos — abrange o Projeto Único e o carrinho de Lote.
#[derive(Serialize, Deserialize, Default)]
pub struct SessionData {
/// Projeto aberto na aba "Projeto Único".
pub single_project: Option<Project>,
/// Projetos do carrinho de Lote (estados de execução são descartados ao salvar).
pub batch_projects: Vec<Project>,
}
/// Retorna o caminho do arquivo de sessão de acordo com o SO.
///
/// - Linux / macOS : `$HOME/.config/simple-mkv-editor/session.json`
@@ -25,10 +35,10 @@ pub fn session_exists() -> bool {
session_path().exists()
}
/// Serializa o `Project` para JSON e grava no arquivo de sessão.
/// Serializa a `SessionData` para JSON e grava no arquivo de sessão.
///
/// Cria o diretório pai automaticamente se ele não existir.
pub fn save_session(project: &Project) -> Result<()> {
pub fn save_session(data: &SessionData) -> Result<()> {
let path = session_path();
if let Some(parent) = path.parent() {
@@ -41,7 +51,7 @@ pub fn save_session(project: &Project) -> Result<()> {
}
let json =
serde_json::to_string_pretty(project).context("Falha ao serializar o estado do projeto")?;
serde_json::to_string_pretty(data).context("Falha ao serializar a sessão")?;
std::fs::write(&path, json)
.with_context(|| format!("Falha ao gravar sessão em: {}", path.display()))?;
@@ -49,11 +59,11 @@ pub fn save_session(project: &Project) -> Result<()> {
Ok(())
}
/// Lê e desserializa o `Project` do arquivo de sessão.
/// Lê e desserializa a `SessionData` do arquivo de sessão.
///
/// Retorna erro amigável se o arquivo estiver ausente, corrompido ou em
/// versão incompatível com a estrutura atual do domínio.
pub fn load_session() -> Result<Project> {
pub fn load_session() -> Result<SessionData> {
let path = session_path();
let json = std::fs::read_to_string(&path).with_context(|| {
@@ -63,8 +73,20 @@ pub fn load_session() -> Result<Project> {
)
})?;
serde_json::from_str::<Project>(&json).with_context(|| {
"Falha ao carregar a sessão. O arquivo pode estar corrompido ou em versão incompatível. \
Delete o arquivo de sessão e tente novamente."
})
// Tenta desserializar como SessionData (formato novo).
// Se falhar, tenta o formato antigo (Project direto) e migra automaticamente.
if let Ok(data) = serde_json::from_str::<SessionData>(&json) {
return Ok(data);
}
// Migração transparente: arquivo gerado pela Fase 7 continha apenas um Project.
serde_json::from_str::<crate::domain::entities::Project>(&json)
.map(|project| SessionData {
single_project: Some(project),
batch_projects: Vec::new(),
})
.with_context(|| {
"Falha ao carregar a sessão. O arquivo pode estar corrompido ou em versão incompatível. \
Delete o arquivo de sessão e tente novamente."
})
}