use crate::api::packages::{PackageDetail, PackageRef};
use crate::follow::{Expectation, Shown};
use crate::keymap::Action;
use crate::nav::{Doable, row};
use crate::route::{Dest, Route, Target};
use crate::state::{Cmd, Screen, State};
use crate::work::{self, Cx, Failure, Job};
use super::Page;
use crate::data::DataKey;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DeleteVersion {
pub at: PackageRef,
pub id: u64,
pub name: String,
}
fn chosen(state: &State) -> Option<(PackageRef, u64)> {
let Screen::Page(p) = state.screen() else {
return None;
};
let Route::Site(Page::Package { at, version }) = &p.route else {
return None;
};
let row = p.selected_link().and_then(crate::ui::page::Link::url);
let row = row.and_then(|url| match Dest::from_url(url).target {
Target::Page(Route::Site(Page::Package {
at: there,
version: Some(id),
})) if there == *at => Some(id),
_ => None,
});
Some((at.clone(), row.or(*version)?))
}
fn detail<'a>(state: &'a State, at: &PackageRef) -> Option<&'a PackageDetail> {
state.picked::<PackageDetail>(&DataKey::Package(at.clone()))
}
#[must_use]
pub fn ask_delete(state: &mut State) -> Vec<Cmd> {
let Some((at, id)) = chosen(state) else {
return state.just_say("Select a version to delete");
};
let Some(d) = detail(state, &at) else {
return state.just_say("The package hasn't loaded yet");
};
let Some(v) = d.versions.items.iter().find(|v| v.id == id) else {
return state.just_say("That version isn't among those loaded");
};
let mut facts = vec![(
"It can't be brought back: pulling it by name or digest fails".to_owned(),
true,
)];
if !v.tags.is_empty() {
facts.push((format!("Its tags go with it: {}", v.tags.join(", ")), true));
}
if d.package.versions <= 1 {
facts.push((
"It's the package's only version: GitHub may refuse, and ask that the package be deleted on github.com".to_owned(),
true,
));
}
let Some(route) = state.route().cloned() else {
return Vec::new();
};
let name = match v.name.strip_prefix("sha256:") {
Some(hex) => format!("sha256:{}", hex.get(..12).unwrap_or(hex)),
None => v.name.clone(),
};
crate::forms::ask(
state,
route,
format!("Delete version {name} of {}?", at.name),
facts,
vec![(
"Delete version".into(),
crate::forms::Answer::Site(super::Answer::DeleteVersion(id)),
)],
);
Vec::new()
}
#[must_use]
pub fn delete(state: &mut State, route: &Route, id: u64) -> Vec<Cmd> {
let Route::Site(Page::Package { at, .. }) = route else {
return Vec::new();
};
let name = (detail(state, at).and_then(|d| d.versions.items.iter().find(|v| v.id == id)))
.map_or_else(|| id.to_string(), |v| v.name.clone());
crate::forms::sending(state);
let delete = DeleteVersion {
at: at.clone(),
id,
name,
};
work::start(delete)
}
impl Job for DeleteVersion {
type Out = ();
async fn run(&self, cx: &Cx) -> Result<(), Failure> {
Ok(cx.gh.delete_version(&self.at, self.id).await?)
}
fn done(self, state: &mut State, result: Result<(), Failure>) -> Vec<Cmd> {
let d = self;
let Some(route) = state.route().cloned() else {
return Vec::new();
};
match result {
Ok(()) => {
crate::forms::close_sender(state, &route);
state.info(format!("Deleted version {} of {}", d.name, d.at.name));
let page = Route::Site(Page::Package {
at: d.at.clone(),
version: None,
});
crate::follow::expect(state, page.clone(), VersionGone(d.at, d.id));
if route == page {
state.ensure_route(&page, true)
} else {
state.replace(page, true, None)
}
}
Err(why) => {
crate::forms::say(state, format!("GitHub didn't delete the version: {why}"))
}
}
}
}
#[derive(Debug)]
struct VersionGone(PackageRef, u64);
impl Expectation for VersionGone {
fn needs(&self, _: &State, _: &Route) -> Vec<DataKey> {
vec![DataKey::Package(self.0.clone())]
}
fn waiting(&self) -> &'static str {
"Waiting for GitHub to show the version gone"
}
fn shown(&self, state: &State, _: &Route) -> Shown {
let gone = |d: &PackageDetail| d.versions.items.iter().all(|v| v.id != self.1);
detail(state, &self.0).is_some_and(gone).into()
}
}
pub fn doables(state: &State, route: &Route, out: &mut Vec<Doable>) {
if let Route::Site(Page::Package { .. }) = route {
let why = chosen(state)
.is_none()
.then(|| "select a version first".to_owned());
let label = "Delete the version (needs the delete:packages scope)";
out.push(row("Edit", Action::DeleteComment, label, "", why));
}
}