From 04e39aac11a7fd45e5cde71981d6658280b1b3f9 Mon Sep 17 00:00:00 2001 From: Kasper Date: Sun, 21 Jan 2024 07:32:02 +0100 Subject: [PATCH] Add basic history page --- CHANGELOG.md | 1 + bindings.ts | 6 +++ src-tauri/src/data.rs | 50 ++++++++++++++++++++++- src-tauri/src/db.rs | 14 ++++--- src-tauri/src/main.rs | 8 ++++ src/lib/Link.svelte | 1 + src/lib/modals/Settings.svelte | 2 +- src/routes/+layout.svelte | 2 + src/routes/channels/+page.svelte | 1 + src/routes/history/+page.svelte | 69 ++++++++++++++++++++++++++++++++ 10 files changed, 146 insertions(+), 8 deletions(-) create mode 100644 src/routes/history/+page.svelte diff --git a/CHANGELOG.md b/CHANGELOG.md index f4bde10..4041806 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # Changelog ## Next +- Add basic history page, accessible via `Cmd/Ctrl+Y`. - Fix selection staying in the same position when videos update - Keep video selected if it moves position - Fix settings link in Get Started popup diff --git a/bindings.ts b/bindings.ts index ba6e656..298213f 100644 --- a/bindings.ts +++ b/bindings.ts @@ -38,6 +38,10 @@ export function checkNow() { return invoke()("check_now") } +export function getHistory() { + return invoke()("get_history") +} + export function getVideos(options: Options, after: After | null) { return invoke()("get_videos", { options,after }) } @@ -55,4 +59,6 @@ export type Options = { show_all: boolean; show_archived: boolean; channel_filte export type Video = { id: string; title: string; description: string; publishTimeMs: number; durationMs: number; thumbnailStandard: boolean; thumbnailMaxres: boolean; channelId: string; channelName: string; unread: boolean; archived: boolean } export type After = { publishTimeMs: number; id: string } export type Channel = { id: string; name: string; icon: string; uploads_playlist_id: string; from_time: number; refresh_rate_ms: number; tags: string[] } +export type UndoHistory = { entries: ([number, Action])[] } export type AddChannelOptions = { url: string; from_time: number; refresh_rate_ms: number; tags: string[] } +export type Action = "CheckNow" | { Archive: string } | { Unarchive: string } | { AddChannel: string } | { UpdateOrDeleteChannels: string } diff --git a/src-tauri/src/data.rs b/src-tauri/src/data.rs index d7b055a..141b91d 100644 --- a/src-tauri/src/data.rs +++ b/src-tauri/src/data.rs @@ -3,14 +3,16 @@ use crate::settings::{Channel, Settings, VersionedSettings}; use crate::{api, background, throw}; use atomicwrites::{AtomicFile, OverwriteBehavior}; use scraper::{Html, Selector}; -use serde::Deserialize; +use serde::{Deserialize, Serialize}; use specta::Type; use sqlx::SqlitePool; use std::collections::HashSet; +use std::convert::TryInto; use std::env; use std::io::Write; use std::path::{Path, PathBuf}; use std::sync::Arc; +use std::time::{SystemTime, UNIX_EPOCH}; use tauri::{command, Config, State}; use tokio::sync::Mutex; use url::Url; @@ -41,6 +43,7 @@ pub struct Data { pub versioned_settings: VersionedSettings, pub paths: AppPaths, pub window: tauri::Window, + pub user_history: UndoHistory, } impl Data { pub fn settings(&mut self) -> &mut Settings { @@ -122,6 +125,7 @@ pub async fn tags(data: DataState<'_>) -> Result, String> { pub async fn check_now(data: DataState<'_>) -> Result<(), String> { let mut data = data.0.lock().await; data.check_now()?; + data.user_history.push(Action::CheckNow); Ok(()) } @@ -227,6 +231,8 @@ pub async fn set_channels(channels: Vec, data: DataState<'_>) -> Result let mut data = data.0.lock().await; data.settings().channels = channels; data.save_settings()?; + data.user_history + .push(Action::UpdateOrDeleteChannels("".to_string())); Ok(()) } @@ -256,7 +262,7 @@ pub async fn add_channel(options: AddChannelOptions, data: DataState<'_>) -> Res }; settings.channels.push(Channel { - id: channel.id, + id: channel.id.clone(), name: channel.snippet.title, icon: channel.snippet.thumbnails.medium.url, uploads_playlist_id: channel.contentDetails.relatedPlaylists.uploads, @@ -265,6 +271,7 @@ pub async fn add_channel(options: AddChannelOptions, data: DataState<'_>) -> Res tags: options.tags, }); data.save_settings()?; + data.user_history.push(Action::AddChannel(channel.id)); Ok(()) } @@ -291,3 +298,42 @@ impl ArcData { Self(Arc::new(Mutex::new(data))) } } + +#[derive(Serialize, Clone, Type)] +pub struct UndoHistory { + pub entries: Vec<(u32, Action)>, +} + +impl UndoHistory { + pub fn new() -> Self { + Self { entries: vec![] } + } + pub fn push(&mut self, action: Action) { + let time: u32 = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_secs() + .try_into() + .unwrap(); + self.entries.push((time, action)); + if self.entries.len() > 100 { + self.entries.remove(0); + } + } +} + +#[derive(Serialize, Clone, Type)] +pub enum Action { + CheckNow, + Archive(String), + Unarchive(String), + AddChannel(String), + UpdateOrDeleteChannels(String), +} + +#[command] +#[specta::specta] +pub async fn get_history(data: DataState<'_>) -> Result { + let data = data.0.lock().await; + Ok(data.user_history.clone()) +} diff --git a/src-tauri/src/db.rs b/src-tauri/src/db.rs index 18bb8cd..5fe5743 100644 --- a/src-tauri/src/db.rs +++ b/src-tauri/src/db.rs @@ -1,5 +1,5 @@ use crate::api::playlist_items; -use crate::data::{AppPaths, DataState}; +use crate::data::{Action, AppPaths, DataState}; use crate::throw; use serde::{Deserialize, Serialize}; use specta::Type; @@ -225,19 +225,23 @@ async fn set_archived(pool: &SqlitePool, id: &str, value: bool) -> Result<(), St #[command] #[specta::specta] pub async fn archive(id: String, data: DataState<'_>) -> Result<(), String> { - let data = data.0.lock().await; + let mut data = data.0.lock().await; match set_archived(&data.db_pool, &id, true).await { - Ok(()) => Ok(()), + Ok(()) => (), Err(e) => throw!("Error archiving video: {}", e), } + data.user_history.push(Action::Archive(id)); + Ok(()) } #[command] #[specta::specta] pub async fn unarchive(id: String, data: DataState<'_>) -> Result<(), String> { - let data = data.0.lock().await; + let mut data = data.0.lock().await; match set_archived(&data.db_pool, &id, false).await { - Ok(()) => Ok(()), + Ok(()) => (), Err(e) => throw!("Error unarchiving video: {}", e), } + data.user_history.push(Action::Unarchive(id)); + Ok(()) } diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 0c2e2c6..1dd7636 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -6,6 +6,7 @@ use crate::data::{AppPaths, ArcData, Data}; use crate::settings::yt_email_notifier; use crate::settings::VersionedSettings; +use data::UndoHistory; use tauri::api::{dialog, shell}; #[cfg(target_os = "macos")] use tauri::AboutMetadata; @@ -93,6 +94,7 @@ async fn main() { data::add_channel, data::set_general_settings, data::check_now, + data::get_history, db::get_videos, db::archive, db::unarchive @@ -137,6 +139,7 @@ async fn main() { data::add_channel, data::set_general_settings, data::check_now, + data::get_history, db::get_videos, db::archive, db::unarchive, @@ -184,6 +187,7 @@ async fn main() { versioned_settings: settings, paths: app_paths, window: win.clone(), + user_history: UndoHistory::new(), }; app.manage(ArcData::new(data)); @@ -269,6 +273,10 @@ async fn main() { .accelerator("Alt+CmdOrCtrl+A") .into(), MenuItem::Separator.into(), + CustomMenuItem::new("History", "History") + .accelerator("CmdOrCtrl+Y") + .into(), + MenuItem::Separator.into(), MenuItem::EnterFullScreen.into(), ]), )), diff --git a/src/lib/Link.svelte b/src/lib/Link.svelte index 3579529..a185c37 100644 --- a/src/lib/Link.svelte +++ b/src/lib/Link.svelte @@ -17,6 +17,7 @@