1use std::sync::Arc;
2
3use async_trait::async_trait;
4use imp_core::ui::{ComponentSpec, NotifyLevel, SelectOption, UserInterface, WidgetContent};
5use tokio::sync::mpsc;
6
7#[derive(Debug)]
9pub enum UiRequest {
10 Notify {
11 message: String,
12 level: NotifyLevel,
13 },
14 Confirm {
15 title: String,
16 message: String,
17 reply: tokio::sync::oneshot::Sender<Option<bool>>,
18 },
19 Select {
20 title: String,
21 context: String,
22 options: Vec<SelectOption>,
23 reply: tokio::sync::oneshot::Sender<Option<usize>>,
24 },
25 MultiSelect {
26 title: String,
27 context: String,
28 options: Vec<SelectOption>,
29 reply: tokio::sync::oneshot::Sender<Option<Vec<usize>>>,
30 },
31 Input {
32 title: String,
33 context: String,
34 placeholder: String,
35 reply: tokio::sync::oneshot::Sender<Option<String>>,
36 },
37 SetStatus {
38 key: String,
39 text: Option<String>,
40 },
41 SetWidget {
42 key: String,
43 content: Option<WidgetContent>,
44 },
45 Custom {
46 component: ComponentSpec,
47 reply: tokio::sync::oneshot::Sender<Option<serde_json::Value>>,
48 },
49}
50
51pub struct TuiInterface {
57 tx: mpsc::Sender<UiRequest>,
58}
59
60impl TuiInterface {
61 pub fn new(tx: mpsc::Sender<UiRequest>) -> Arc<Self> {
62 Arc::new(Self { tx })
63 }
64}
65
66#[async_trait]
67impl UserInterface for TuiInterface {
68 fn has_ui(&self) -> bool {
69 true
70 }
71
72 async fn notify(&self, message: &str, level: NotifyLevel) {
73 let _ = self
74 .tx
75 .send(UiRequest::Notify {
76 message: message.to_string(),
77 level,
78 })
79 .await;
80 }
81
82 async fn confirm(&self, title: &str, message: &str) -> Option<bool> {
83 let (reply_tx, reply_rx) = tokio::sync::oneshot::channel();
84 let _ = self
85 .tx
86 .send(UiRequest::Confirm {
87 title: title.to_string(),
88 message: message.to_string(),
89 reply: reply_tx,
90 })
91 .await;
92 reply_rx.await.ok().flatten()
93 }
94
95 async fn select_with_context(
96 &self,
97 title: &str,
98 context: &str,
99 options: &[SelectOption],
100 ) -> Option<usize> {
101 let (reply_tx, reply_rx) = tokio::sync::oneshot::channel();
102 let _ = self
103 .tx
104 .send(UiRequest::Select {
105 title: title.to_string(),
106 context: context.to_string(),
107 options: options.to_vec(),
108 reply: reply_tx,
109 })
110 .await;
111 reply_rx.await.ok().flatten()
112 }
113
114 async fn multi_select_with_context(
115 &self,
116 title: &str,
117 context: &str,
118 options: &[SelectOption],
119 ) -> Option<Vec<usize>> {
120 let (reply_tx, reply_rx) = tokio::sync::oneshot::channel();
121 let _ = self
122 .tx
123 .send(UiRequest::MultiSelect {
124 title: title.to_string(),
125 context: context.to_string(),
126 options: options.to_vec(),
127 reply: reply_tx,
128 })
129 .await;
130 reply_rx.await.ok().flatten()
131 }
132
133 async fn input_with_context(
134 &self,
135 title: &str,
136 context: &str,
137 placeholder: &str,
138 ) -> Option<String> {
139 let (reply_tx, reply_rx) = tokio::sync::oneshot::channel();
140 let _ = self
141 .tx
142 .send(UiRequest::Input {
143 title: title.to_string(),
144 context: context.to_string(),
145 placeholder: placeholder.to_string(),
146 reply: reply_tx,
147 })
148 .await;
149 reply_rx.await.ok().flatten()
150 }
151
152 async fn set_status(&self, key: &str, text: Option<&str>) {
153 let _ = self
154 .tx
155 .send(UiRequest::SetStatus {
156 key: key.to_string(),
157 text: text.map(String::from),
158 })
159 .await;
160 }
161
162 async fn set_widget(&self, key: &str, content: Option<WidgetContent>) {
163 let _ = self
164 .tx
165 .send(UiRequest::SetWidget {
166 key: key.to_string(),
167 content,
168 })
169 .await;
170 }
171
172 async fn custom(&self, component: ComponentSpec) -> Option<serde_json::Value> {
173 let (reply_tx, reply_rx) = tokio::sync::oneshot::channel();
174 let _ = self
175 .tx
176 .send(UiRequest::Custom {
177 component,
178 reply: reply_tx,
179 })
180 .await;
181 reply_rx.await.ok().flatten()
182 }
183}