use crate::ui::{Authoring, Model, keys, theme};
pub const FILE: &str = "file a ball…";
pub const ACT: &str = "act on it…";
pub const CLAIM: &str = "claim it for this workspace";
pub const CLAIM_HELP: &str = "binds this ball to the workspace, so its agents work it";
pub const DONE: &str = "done with this ball";
pub const DONE_HELP: &str = "closes the authoring block; nothing typed in it is sent";
pub const FILE_HELP: &str = "opens a block to file a new ball on this workspace's project";
pub const ACT_HELP: &str = "opens a block to amend, release, deliver or claim this ball";
pub const NEW: &str = "a ball that does not exist yet";
pub const FILE_IT: &str = "file it";
pub const AMEND: &str = "amend it";
pub const RELEASE: &str = "let it go";
pub const DELIVER: &str = "deliver it";
pub const FILE_IT_HELP: &str = "files the ball on the project above with this title and body";
pub const AMEND_HELP: &str =
"rewrites the ball's title or body, or appends the note to its journal";
pub const RELEASE_HELP: &str = "unbinds the ball from this workspace; it stays on the board";
pub const DELIVER_HELP: &str =
"closes the ball as delivered; its id above arms it, and it cannot be reopened";
pub const PROJECT_HINT: &str = "project";
pub const TITLE_HINT: &str = "title";
pub const BODY_HINT: &str = "body";
pub const NOTE_HINT: &str = "a note for its journal";
pub const ARM_HINT: &str = "the ball's id";
pub const UNFILEABLE: &str = "a project and a title are what files a ball";
pub const UNAMENDABLE: &str = "type a title, a body or a note to amend it";
pub const NOTHING: &str = "";
pub const UNARMED: &str = "type the ball's id to deliver it — a close cannot be undone";
pub fn render(ui: &mut egui::Ui, model: &mut Model) {
let Some(down) = model.channel() else {
return;
};
let Some(block) = model.authoring.as_ref() else {
return;
};
ui.separator();
ui.label(subject(block));
ui.horizontal_wrapped(|ui| {
if theme::paint::button(ui, DONE, true, DONE_HELP).clicked() {
model.close_authoring();
}
});
boxes(ui, model);
if let Some(fired) = acts(ui, model) {
model.post_ball(&down, fired);
}
}
pub fn subject(block: &Authoring) -> String {
block.ball.as_ref().map_or_else(
|| format!("{NEW} — {}", block.at.address),
|ball| format!("{} in {} as {}", ball.id, ball.project, block.name),
)
}
fn boxes(ui: &mut egui::Ui, model: &mut Model) {
let Some(block) = model.authoring.as_mut() else {
return;
};
let filing = block.filing();
if filing {
typed(ui, &mut block.project, keys::PROJECT_ID, PROJECT_HINT);
}
typed(ui, &mut block.title, keys::TITLE_ID, TITLE_HINT);
typed(ui, &mut block.body, keys::BODY_ID, BODY_HINT);
if !filing {
typed(ui, &mut block.note, keys::NOTE_ID, NOTE_HINT);
typed(ui, &mut block.arm, keys::DELIVER_ID, ARM_HINT);
}
}
fn typed(ui: &mut egui::Ui, text: &mut String, id: &str, hint: &str) {
ui.add(
egui::TextEdit::singleline(text)
.id(egui::Id::new(id))
.desired_width(f32::INFINITY)
.hint_text(hint),
);
}
fn acts(ui: &mut egui::Ui, model: &Model) -> Option<serde_json::Value> {
let block = model.authoring.as_ref()?;
let offered = if block.filing() {
vec![(
FILE_IT,
crate::verbs::CREATE,
block.filed(),
UNFILEABLE,
FILE_IT_HELP,
)]
} else {
vec![
(
AMEND,
crate::verbs::UPDATE,
block.amended(),
UNAMENDABLE,
AMEND_HELP,
),
(
RELEASE,
crate::verbs::RELEASE.word,
block.released(),
NOTHING,
RELEASE_HELP,
),
(
DELIVER,
crate::verbs::CLOSE.word,
block.delivered(),
UNARMED,
DELIVER_HELP,
),
]
};
let mut fired = None;
ui.horizontal_wrapped(|ui| {
for (word, op, gesture, why, help) in offered {
fired = spend(ui, word, op, gesture, why, help).or(fired.take());
}
});
fired
}
fn spend(
ui: &mut egui::Ui,
word: &str,
op: &str,
gesture: Option<serde_json::Value>,
why: &str,
help: &str,
) -> Option<serde_json::Value> {
let control = theme::paint::button(ui, word, gesture.is_some(), help);
crate::ui::act::tag(&control, &[op]);
if gesture.is_none() && !why.is_empty() {
ui.colored_label(theme::tone_ink(&crate::reply::convs::Tone::Weak), why);
}
control.clicked().then_some(gesture).flatten()
}
#[cfg(test)]
mod tests;