feat(app): mark a season, or everything up to an episode, watched

Marking was per episode on the title page, or the whole title from the
library's menu. Someone joining a show at season three had to tick every
earlier episode one by one. Season headers now have Mark season watched
(or unwatched), and right-clicking an episode's tick offers Mark watched
up to here. Both go through one MarkEpisodes action with an Undo, which
SetTitleWatched now uses too.
This commit is contained in:
nvrl committed 2026-10-12 22:43:27 +02:00
1 parent 5dfb717235
commit 6d1943f2bd
2 files changed
+93 -10

No files matched your search

+42 -2
View File
@@ -218,6 +218,14 @@ pub enum Action {
key: String,
watched: bool,
},
/// Some episodes of a title — a season, or everything up to one —
/// watched or not, with an Undo. `what` names them in the toast.
MarkEpisodes {
key: String,
links: Vec<(String, String)>,
watched: bool,
what: String,
},
/// Take a title off Continue watching by forgetting where it stopped,
/// with an Undo.
ForgetPosition {
@@ -1651,9 +1659,41 @@ impl App {
let Some(title) = self.library.get(&key) else {
return;
};
let name = title.name.clone();
let links = title
.episodes()
.map(|e| (e.node.share_id.clone(), e.node.link_id.clone()))
.collect();
let what = title.name.clone();
self.apply(
ctx,
Action::MarkEpisodes {
key,
links,
watched,
what,
},
);
}
Action::MarkEpisodes {
key,
links,
watched,
what: name,
} => {
let Some(title) = self.library.get(&key) else {
return;
};
let picked = |e: &&pstr_core::library::Episode| {
links
.iter()
.any(|(share, link)| e.node.share_id == *share && e.node.link_id == *link)
};
let mut before = Vec::new();
for episode in title.episodes().filter(|e| e.is_watched() != watched) {
for episode in title
.episodes()
.filter(picked)
.filter(|e| e.is_watched() != watched)
{
let duration = episode.watch.and_then(|watch| watch.duration_secs);
before.push((
episode.node.share_id.clone(),