feat: Takeout walkthrough, design-system restyle, replacing import
Adds a seven-step in-app guide to exporting subscriptions from Google Takeout, with the real URLs opened in the system browser. Restyles the app onto the supplied design system: slate/sky palette, 9-15px type ladder, outline-first controls, tiered radii, borders for separation and shadows only for elevation. Light and dark are both designed, with a System/Light/Dark control and a pre-paint script so the window does not flash light on a dark machine. Importing now replaces the subscription list rather than merging, per request. Because that can delete downloaded files, a confirmation dialog names exactly what will go first.
This commit is contained in:
@@ -3,7 +3,9 @@
|
||||
use crate::db::Db;
|
||||
use crate::downloader::{self, Progress};
|
||||
use crate::feed;
|
||||
use crate::models::{ChannelWithCount, DownloadState, FeedFilter, FeedItem, Prereqs};
|
||||
use crate::models::{
|
||||
Channel, ChannelWithCount, DownloadState, FeedFilter, FeedItem, ImportPreview, Prereqs,
|
||||
};
|
||||
use crate::net;
|
||||
use crate::takeout;
|
||||
use crate::thumbs;
|
||||
@@ -113,12 +115,8 @@ pub async fn check_prereqs(state: State<'_, AppState>) -> Result<Prereqs, String
|
||||
})
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn import_takeout_csv(
|
||||
path: String,
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<usize, String> {
|
||||
let raw = tokio::fs::read(&path)
|
||||
async fn read_channels(path: &str) -> Result<Vec<Channel>, String> {
|
||||
let raw = tokio::fs::read(path)
|
||||
.await
|
||||
.map_err(|e| format!("Cannot read {path}: {e}"))?;
|
||||
// Takeout exports are UTF-8, sometimes with a BOM.
|
||||
@@ -129,8 +127,42 @@ pub async fn import_takeout_csv(
|
||||
if channels.is_empty() {
|
||||
return Err("No channels found in that file.".into());
|
||||
}
|
||||
Ok(channels)
|
||||
}
|
||||
|
||||
/// Reports what a replacing import would add and destroy, so the UI can name
|
||||
/// the consequences before the user commits to them.
|
||||
#[tauri::command]
|
||||
pub async fn preview_takeout_import(
|
||||
path: String,
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<ImportPreview, String> {
|
||||
let channels = read_channels(&path).await?;
|
||||
state.db.lock().await.preview_replace(&channels)
|
||||
}
|
||||
|
||||
/// The imported CSV becomes the entire subscription list. Channels that are no
|
||||
/// longer in it are removed along with their videos, download records, and the
|
||||
/// downloaded files themselves — leaving those on disk would orphan gigabytes
|
||||
/// the app can no longer show or delete.
|
||||
#[tauri::command]
|
||||
pub async fn import_takeout_csv(
|
||||
path: String,
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<usize, String> {
|
||||
let channels = read_channels(&path).await?;
|
||||
|
||||
let doomed = state
|
||||
.db
|
||||
.lock()
|
||||
.await
|
||||
.paths_dropped_by_replace(&channels)?;
|
||||
for p in doomed {
|
||||
let _ = tokio::fs::remove_file(&p).await;
|
||||
}
|
||||
|
||||
let mut db = state.db.lock().await;
|
||||
db.upsert_channels(&channels)
|
||||
db.replace_channels(&channels)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
|
||||
Reference in New Issue
Block a user