use crate::AppModel;
use crate::actions::{close_enabled, move_enabled, unclaim_enabled};
use crate::boundary::Action;
use crate::cli_outbound::Cli;
use crate::projects::join::{JoinRow, owner_name};
use std::path::Path;
const CLOSE_HINT: &str = "Deliver this ball (`bl close`): folds `main` into its worktree, runs the \
project's pre-commit gate, squashes the work onto the target branch, and \
removes the worktree. A failing gate aborts and leaves the ball claimed (c).";
const RELEASE_HINT: &str = "Let this ball go (`bl unclaim`): the workspace stops holding it and anyone \
can claim it again. Nothing already committed in its worktree is lost (r).";
const MOVE_HINT: &str = "Re-home this ball to that workspace — released here, claimed there, in \
one gesture. The destination is a pick, so its keyboard spelling is the line: \
`/move [id] <to>`.";
pub(super) fn actions(
ui: &mut egui::Ui,
model: &mut AppModel,
lernie: &Cli,
bl: &Cli,
join: Option<&JoinRow>,
) {
let Some(row) = join else {
return;
};
let owner = owner_name(row);
let targets = model.move_targets(&owner);
ui.horizontal(|ui| {
ui.label(format!("ball {}", row.ball_id));
if ui
.add_enabled(close_enabled(row.state), egui::Button::new("Close"))
.on_hover_text(CLOSE_HINT)
.on_disabled_hover_text("only the workspace holding a ball can close it")
.clicked()
{
close_row(model, lernie, bl, row);
}
if ui
.add_enabled(unclaim_enabled(row.state), egui::Button::new("Release"))
.on_hover_text(RELEASE_HINT)
.on_disabled_hover_text(
"this workspace is not holding the ball, so it has none to let go",
)
.clicked()
{
release_row(model, lernie, bl, row);
}
});
if move_enabled(row.state) && !targets.is_empty() {
ui.horizontal(|ui| {
ui.label("move to:").on_hover_text(MOVE_HINT);
for to in &targets {
if ui.button(to).on_hover_text(MOVE_HINT).clicked() {
move_ball(model, lernie, bl, &row.project, &row.ball_id, &owner, to);
}
}
});
}
}
fn close_row(model: &mut AppModel, lernie: &Cli, bl: &Cli, row: &JoinRow) {
close_ball(
model,
lernie,
bl,
&row.project,
&row.ball_id,
&owner_name(row),
);
}
fn release_row(model: &mut AppModel, lernie: &Cli, bl: &Cli, row: &JoinRow) {
release_ball(
model,
lernie,
bl,
&row.project,
&row.ball_id,
&owner_name(row),
);
}
pub(super) fn close_ball(
model: &mut AppModel,
lernie: &Cli,
bl: &Cli,
project: &Path,
id: &str,
owner: &str,
) {
fire(
model,
lernie,
bl,
project,
Action::Close {
project: project.to_path_buf(),
id: id.to_owned(),
name: owner.to_owned(),
},
);
}
pub(super) fn release_ball(
model: &mut AppModel,
lernie: &Cli,
bl: &Cli,
project: &Path,
id: &str,
owner: &str,
) {
fire(
model,
lernie,
bl,
project,
Action::Release {
project: project.to_path_buf(),
id: id.to_owned(),
name: owner.to_owned(),
},
);
}
pub(super) fn move_ball(
model: &mut AppModel,
lernie: &Cli,
bl: &Cli,
project: &Path,
id: &str,
owner: &str,
to: &str,
) {
fire(
model,
lernie,
bl,
project,
Action::Move {
project: project.to_path_buf(),
id: id.to_owned(),
from: owner.to_owned(),
to: to.to_owned(),
},
);
}
pub(super) fn assign_ball(
model: &mut AppModel,
lernie: &Cli,
bl: &Cli,
project: &Path,
id: &str,
to: &str,
) {
fire(
model,
lernie,
bl,
project,
Action::Assign {
project: project.to_path_buf(),
id: id.to_owned(),
name: to.to_owned(),
},
);
}
fn fire(model: &mut AppModel, lernie: &Cli, bl: &Cli, project: &Path, action: Action) {
let deps = model.boundary_deps(lernie, bl);
let _ = model.dispatch(&deps, &super::now_ts(), &action);
model.after_bl_verb(project);
}
pub(super) fn release_focused(model: &mut AppModel, lernie: &Cli, bl: &Cli) {
let Some(row) = model.focused_join().cloned() else {
return;
};
if unclaim_enabled(row.state) {
release_row(model, lernie, bl, &row);
}
}
pub(super) fn close_focused(model: &mut AppModel, lernie: &Cli, bl: &Cli) {
let Some(row) = model.focused_join().cloned() else {
return;
};
if close_enabled(row.state) {
close_row(model, lernie, bl, &row);
}
}