Skip to main content

max / makenotwork

8.2 KB · 209 lines History Blame Raw
1 //! project-screen key-input handler (split out of tui/input.rs).
2
3 use super::{
4 App, AppEvent, ConfirmAction, DataPayload, KeyCode, KeyEvent, MnwApiClient, Screen,
5 bulk_delete, bulk_publish, bulk_unpublish, load_blog_posts, load_item_detail,
6 load_project_items, load_tiers, mpsc,
7 };
8
9 pub(crate) async fn handle_project_input(
10 key: KeyEvent,
11 app: &mut App,
12 screen: &mut Screen,
13 api: &MnwApiClient,
14 tx: &mpsc::Sender<AppEvent>,
15 ) {
16 // Handle confirmation dialog
17 if let Some(ref action) = app.confirm_action {
18 match key.code {
19 KeyCode::Char('y' | 'Y') => {
20 let action = action.clone();
21 app.confirm_action = None;
22 let ids: Vec<String> = app
23 .selected_items
24 .iter()
25 .filter_map(|&i| app.items.get(i).map(|item| item.id.clone()))
26 .collect();
27 let api = api.clone();
28 let user_id = app.user.user_id.clone();
29 let tx = tx.clone();
30 match action {
31 ConfirmAction::BulkPublish { .. } => {
32 tokio::spawn(async move {
33 bulk_publish(&api, &user_id, ids, &tx).await;
34 });
35 }
36 ConfirmAction::BulkUnpublish { .. } => {
37 tokio::spawn(async move {
38 bulk_unpublish(&api, &user_id, ids, &tx).await;
39 });
40 }
41 ConfirmAction::BulkDelete { .. } => {
42 tokio::spawn(async move {
43 bulk_delete(&api, &user_id, ids, &tx).await;
44 });
45 }
46 _ => {}
47 }
48 }
49 _ => {
50 app.confirm_action = None;
51 }
52 }
53 return;
54 }
55
56 match key.code {
57 KeyCode::Char('j') | KeyCode::Down => app.move_down(screen),
58 KeyCode::Char('k') | KeyCode::Up => app.move_up(screen),
59 KeyCode::Char(' ')
60 // Toggle selection
61 if !app.items.is_empty() => {
62 let idx = app.selected_index;
63 if app.selected_items.contains(&idx) {
64 app.selected_items.remove(&idx);
65 } else {
66 app.selected_items.insert(idx);
67 }
68 }
69 KeyCode::Esc => {
70 if app.selected_items.is_empty() {
71 *screen = Screen::Home;
72 app.items.clear();
73 app.selected_index = 0;
74 } else {
75 app.selected_items.clear();
76 }
77 }
78 KeyCode::Enter => {
79 if let Screen::Project(pidx) = screen
80 && !app.items.is_empty()
81 {
82 let item_id = app.items[app.selected_index].id.clone();
83 let pidx = *pidx;
84 *screen = Screen::Item(pidx, item_id.clone());
85 app.item_detail = None;
86 app.item_versions.clear();
87 app.item_status = None;
88 app.item_editing = None;
89 app.selected_items.clear();
90 app.selected_index = 0;
91 app.loading = true;
92
93 let api = api.clone();
94 let user_id = app.user.user_id.clone();
95 let tx = tx.clone();
96 tokio::spawn(async move {
97 load_item_detail(&api, &user_id, &item_id, &tx).await;
98 });
99 }
100 }
101 KeyCode::Char('p' | 'P')
102 if !app.items.is_empty() => {
103 if app.selected_items.is_empty() {
104 // Single item: toggle publish state
105 let item = &app.items[app.selected_index];
106 let item_id = item.id.clone();
107 let is_public = item.is_public;
108 let api = api.clone();
109 let user_id = app.user.user_id.clone();
110 let tx = tx.clone();
111 tokio::spawn(async move {
112 let result = if is_public {
113 api.unpublish_item(&user_id, &item_id).await
114 } else {
115 api.publish_item(&user_id, &item_id).await
116 };
117 let msg = match result {
118 Ok(_) => if is_public { "Unpublished" } else { "Published" }.to_string(),
119 Err(e) => format!("Error: {}", crate::commands::sanitize_api_error(&e)),
120 };
121 let _ = tx.send(AppEvent::DataLoaded(DataPayload::BulkActionComplete { message: msg })).await;
122 });
123 } else {
124 // Bulk: check if majority are draft → publish, else unpublish
125 let draft_count = app.selected_items.iter()
126 .filter(|&&i| app.items.get(i).is_some_and(|item| !item.is_public))
127 .count();
128 let count = app.selected_items.len();
129 if draft_count > count / 2 {
130 app.confirm_action = Some(ConfirmAction::BulkPublish { count });
131 } else {
132 app.confirm_action = Some(ConfirmAction::BulkUnpublish { count });
133 }
134 }
135 }
136 KeyCode::Char('d' | 'D')
137 if !app.items.is_empty() => {
138 if app.selected_items.is_empty() {
139 // Select current item for single delete
140 app.selected_items.insert(app.selected_index);
141 }
142 let count = app.selected_items.len();
143 app.confirm_action = Some(ConfirmAction::BulkDelete { count });
144 }
145 KeyCode::Char('b' | 'B') => {
146 if let Screen::Project(idx) = screen
147 && let Some(p) = app.projects.get(*idx)
148 {
149 let pidx = *idx;
150 let project_id = p.id.clone();
151 let project_title = p.title.clone();
152 *screen = Screen::Blog(pidx, project_id.clone());
153 app.blog_posts.clear();
154 app.blog_project_title = Some(project_title);
155 app.blog_status = None;
156 app.blog_create_step = None;
157 app.selected_items.clear();
158 app.selected_index = 0;
159 app.loading = true;
160
161 let api = api.clone();
162 let user_id = app.user.user_id.clone();
163 let tx = tx.clone();
164 tokio::spawn(async move {
165 load_blog_posts(&api, &user_id, &project_id, &tx).await;
166 });
167 }
168 }
169 KeyCode::Char('t' | 'T') => {
170 if let Screen::Project(idx) = screen
171 && let Some(p) = app.projects.get(*idx)
172 {
173 let pidx = *idx;
174 let project_id = p.id.clone();
175 let project_title = p.title.clone();
176 *screen = Screen::Tiers(pidx, project_id.clone());
177 app.tiers.clear();
178 app.tiers_project_title = Some(project_title);
179 app.tiers_status = None;
180 app.selected_items.clear();
181 app.selected_index = 0;
182 app.loading = true;
183
184 let api = api.clone();
185 let user_id = app.user.user_id.clone();
186 let tx = tx.clone();
187 tokio::spawn(async move {
188 load_tiers(&api, &user_id, &project_id, &tx).await;
189 });
190 }
191 }
192 KeyCode::Char('r' | 'R') => {
193 if let Screen::Project(idx) = screen
194 && let Some(p) = app.projects.get(*idx)
195 {
196 app.loading = true;
197 let api = api.clone();
198 let project_id = p.id.clone();
199 let user_id = app.user.user_id.clone();
200 let tx = tx.clone();
201 tokio::spawn(async move {
202 load_project_items(&api, &project_id, &user_id, &tx).await;
203 });
204 }
205 }
206 _ => {}
207 }
208 }
209