use super::{
naming::clean_plan_name,
open::banner_text,
show_error, spawn_task,
store::{self, ListedPlan},
};
use crate::{
RoughPlanModel, SavedRoughPlanRow,
gui::rough_plan::{host::Host, run::ResultsSource, session::Session},
};
use slint::{ComponentHandle, ModelRc, VecModel};
use tracing::warn;
fn row_of(plan: ListedPlan) -> Option<SavedRoughPlanRow> {
let id = i32::try_from(plan.id)
.inspect_err(|_| {
warn!(
"Rough planner: saved plan {} has an id too large to list",
plan.id
);
})
.ok()?;
Some(SavedRoughPlanRow {
id,
name: plan.name.into(),
summary: plan.summary.into(),
date: plan.date.into(),
})
}
pub(super) fn refresh_list(host: &Host) {
let seq = {
let mut session = host.session.borrow_mut();
session.saved.list_seq += 1;
session.saved.list_seq
};
spawn_task(host, store::list_saved, move |host, result| {
if host.session.borrow().saved.list_seq != seq {
return;
}
match result {
Ok(plans) => {
let rows: Vec<SavedRoughPlanRow> = plans.into_iter().filter_map(row_of).collect();
host.window
.global::<RoughPlanModel>()
.set_saved_plans(ModelRc::new(VecModel::from(rows)));
}
Err(message) => show_error(host, &message),
}
});
}
fn rename_in_session(session: &mut Session, id: i64, name: &str) -> Option<i64> {
let ResultsSource::Loaded {
plan_id,
name: shown,
created_at,
} = &mut session.run.source
else {
return None;
};
if *plan_id != id {
return None;
}
*shown = name.to_string();
let created_at = *created_at;
if let Some(pending) = session
.saved
.pending_banner
.as_mut()
.filter(|pending| pending.plan_id == id)
{
pending.text = banner_text(name, created_at);
return None;
}
Some(created_at)
}
fn follow_rename(host: &Host, id: i64, name: &str) {
let created_at = rename_in_session(&mut host.session.borrow_mut(), id, name);
let Some(created_at) = created_at else {
return;
};
let model = host.window.global::<RoughPlanModel>();
if !model.get_loaded_banner().is_empty() {
model.set_loaded_banner(banner_text(name, created_at).into());
}
}
pub(super) fn rename_saved(host: &Host, id: i64, new_name: &str) {
let name = clean_plan_name(new_name);
if name.is_empty() {
show_error(host, "A saved plan needs a name.");
return;
}
let stored_name = name.clone();
spawn_task(
host,
move |db| store::rename_saved(db, id, &stored_name),
move |host, result| {
match result {
Ok(()) => follow_rename(host, id, &name),
Err(message) => show_error(host, &message),
}
refresh_list(host);
},
);
}
pub(super) fn delete_saved(host: &Host, id: i64) {
spawn_task(
host,
move |db| store::delete_saved(db, id),
|host, result| {
if let Err(message) = result {
show_error(host, &message);
}
refresh_list(host);
},
);
}
#[cfg(test)]
mod tests {
use super::*;
use crate::gui::rough_plan::saved::PendingBanner;
fn loaded(plan_id: i64, name: &str) -> ResultsSource {
ResultsSource::Loaded {
plan_id,
name: name.to_string(),
created_at: 1_790_726_400,
}
}
#[test]
fn a_rename_reaches_the_plan_on_screen_and_only_that_plan() {
let mut session = Session::default();
session.run.source = loaded(4, "Old");
assert_eq!(rename_in_session(&mut session, 5, "Other"), None);
assert_eq!(
session.run.source,
loaded(4, "Old"),
"another plan was renamed"
);
assert_eq!(
rename_in_session(&mut session, 4, "New"),
Some(1_790_726_400),
"the banner on screen needs the plan's date to be rewritten"
);
assert_eq!(session.run.source, loaded(4, "New"));
session.run.source = ResultsSource::Planned;
assert_eq!(rename_in_session(&mut session, 4, "Unrelated"), None);
assert_eq!(session.run.source, ResultsSource::Planned);
}
#[test]
fn a_banner_still_waiting_for_its_rows_gets_the_new_name_itself() {
let mut session = Session::default();
session.run.source = loaded(4, "Old");
session.saved.pending_banner = Some(PendingBanner {
plan_id: 4,
text: banner_text("Old", 1_790_726_400),
});
assert_eq!(rename_in_session(&mut session, 4, "Newer"), None);
assert_eq!(
session
.saved
.pending_banner
.as_ref()
.map(|p| p.text.as_str()),
Some(banner_text("Newer", 1_790_726_400).as_str())
);
assert_eq!(session.run.source, loaded(4, "Newer"));
}
}