//! project-screen key-input handler (split out of tui/input.rs). use super::{ App, AppEvent, ConfirmAction, DataPayload, KeyCode, KeyEvent, MnwApiClient, Screen, bulk_delete, bulk_publish, bulk_unpublish, load_blog_posts, load_item_detail, load_project_items, load_tiers, mpsc, }; pub(crate) async fn handle_project_input( key: KeyEvent, app: &mut App, screen: &mut Screen, api: &MnwApiClient, tx: &mpsc::Sender, ) { // Handle confirmation dialog if let Some(ref action) = app.confirm_action { match key.code { KeyCode::Char('y' | 'Y') => { let action = action.clone(); app.confirm_action = None; let ids: Vec = app .selected_items .iter() .filter_map(|&i| app.items.get(i).map(|item| item.id.clone())) .collect(); let api = api.clone(); let user_id = app.user.user_id.clone(); let tx = tx.clone(); match action { ConfirmAction::BulkPublish { .. } => { tokio::spawn(async move { bulk_publish(&api, &user_id, ids, &tx).await; }); } ConfirmAction::BulkUnpublish { .. } => { tokio::spawn(async move { bulk_unpublish(&api, &user_id, ids, &tx).await; }); } ConfirmAction::BulkDelete { .. } => { tokio::spawn(async move { bulk_delete(&api, &user_id, ids, &tx).await; }); } _ => {} } } _ => { app.confirm_action = None; } } return; } match key.code { KeyCode::Char('j') | KeyCode::Down => app.move_down(screen), KeyCode::Char('k') | KeyCode::Up => app.move_up(screen), KeyCode::Char(' ') // Toggle selection if !app.items.is_empty() => { let idx = app.selected_index; if app.selected_items.contains(&idx) { app.selected_items.remove(&idx); } else { app.selected_items.insert(idx); } } KeyCode::Esc => { if app.selected_items.is_empty() { *screen = Screen::Home; app.items.clear(); app.selected_index = 0; } else { app.selected_items.clear(); } } KeyCode::Enter => { if let Screen::Project(pidx) = screen && !app.items.is_empty() { let item_id = app.items[app.selected_index].id.clone(); let pidx = *pidx; *screen = Screen::Item(pidx, item_id.clone()); app.item_detail = None; app.item_versions.clear(); app.item_status = None; app.item_editing = None; app.selected_items.clear(); app.selected_index = 0; app.loading = true; let api = api.clone(); let user_id = app.user.user_id.clone(); let tx = tx.clone(); tokio::spawn(async move { load_item_detail(&api, &user_id, &item_id, &tx).await; }); } } KeyCode::Char('p' | 'P') if !app.items.is_empty() => { if app.selected_items.is_empty() { // Single item: toggle publish state let item = &app.items[app.selected_index]; let item_id = item.id.clone(); let is_public = item.is_public; let api = api.clone(); let user_id = app.user.user_id.clone(); let tx = tx.clone(); tokio::spawn(async move { let result = if is_public { api.unpublish_item(&user_id, &item_id).await } else { api.publish_item(&user_id, &item_id).await }; let msg = match result { Ok(_) => if is_public { "Unpublished" } else { "Published" }.to_string(), Err(e) => format!("Error: {}", crate::commands::sanitize_api_error(&e)), }; let _ = tx.send(AppEvent::DataLoaded(DataPayload::BulkActionComplete { message: msg })).await; }); } else { // Bulk: check if majority are draft → publish, else unpublish let draft_count = app.selected_items.iter() .filter(|&&i| app.items.get(i).is_some_and(|item| !item.is_public)) .count(); let count = app.selected_items.len(); if draft_count > count / 2 { app.confirm_action = Some(ConfirmAction::BulkPublish { count }); } else { app.confirm_action = Some(ConfirmAction::BulkUnpublish { count }); } } } KeyCode::Char('d' | 'D') if !app.items.is_empty() => { if app.selected_items.is_empty() { // Select current item for single delete app.selected_items.insert(app.selected_index); } let count = app.selected_items.len(); app.confirm_action = Some(ConfirmAction::BulkDelete { count }); } KeyCode::Char('b' | 'B') => { if let Screen::Project(idx) = screen && let Some(p) = app.projects.get(*idx) { let pidx = *idx; let project_id = p.id.clone(); let project_title = p.title.clone(); *screen = Screen::Blog(pidx, project_id.clone()); app.blog_posts.clear(); app.blog_project_title = Some(project_title); app.blog_status = None; app.blog_create_step = None; app.selected_items.clear(); app.selected_index = 0; app.loading = true; let api = api.clone(); let user_id = app.user.user_id.clone(); let tx = tx.clone(); tokio::spawn(async move { load_blog_posts(&api, &user_id, &project_id, &tx).await; }); } } KeyCode::Char('t' | 'T') => { if let Screen::Project(idx) = screen && let Some(p) = app.projects.get(*idx) { let pidx = *idx; let project_id = p.id.clone(); let project_title = p.title.clone(); *screen = Screen::Tiers(pidx, project_id.clone()); app.tiers.clear(); app.tiers_project_title = Some(project_title); app.tiers_status = None; app.selected_items.clear(); app.selected_index = 0; app.loading = true; let api = api.clone(); let user_id = app.user.user_id.clone(); let tx = tx.clone(); tokio::spawn(async move { load_tiers(&api, &user_id, &project_id, &tx).await; }); } } KeyCode::Char('r' | 'R') => { if let Screen::Project(idx) = screen && let Some(p) = app.projects.get(*idx) { app.loading = true; let api = api.clone(); let project_id = p.id.clone(); let user_id = app.user.user_id.clone(); let tx = tx.clone(); tokio::spawn(async move { load_project_items(&api, &project_id, &user_id, &tx).await; }); } } _ => {} } }