1use ratatui::buffer::Buffer;
2use ratatui::layout::Rect;
3use std::ops::Not;
4use std::sync::atomic::AtomicBool;
5use std::sync::mpsc::Sender;
6use std::sync::Arc;
7
8use crate::disk::DiskInterface;
9use crate::network::{Network, NetworkStore, Token};
10use crate::tui::app::pages::token::TokenPage;
11use crate::tui::app::pages::Page;
12use crate::tui::app::widgets::confirm_popup::ConfirmPopup;
13use crate::tui::app::widgets::form::{Form, FormItemIndex, FormWidget};
14use crate::tui::app::SharedState;
15use crate::tui::traits::{Component, HandleResult};
16use crate::tui::Event;
17use strum::EnumIter;
18
19#[derive(EnumIter, PartialEq)]
20pub enum FormItem {
21 Heading,
22 Name,
23 NameAlchemy,
24 NameAliases,
25 ChainId,
26 Symbol,
27 NativeDecimals,
28 PriceTicker,
29 RpcUrl,
30 RpcAlchemy,
31 RpcInfura,
32 ExplorerUrl,
33 IsTestnet,
34 TokensButton,
35 SaveButton,
36 RemoveButton,
37 ErrorText, }
39impl FormItemIndex for FormItem {
40 fn index(self) -> usize {
41 self as usize
42 }
43}
44impl TryFrom<FormItem> for FormWidget {
45 type Error = crate::Error;
46 fn try_from(value: FormItem) -> crate::Result<Self> {
47 let widget = match value {
48 FormItem::Heading => FormWidget::Heading("Edit Network"),
49 FormItem::Name => FormWidget::InputBox {
50 label: "Name",
51 text: String::new(),
52 empty_text: None,
53 currency: None,
54 },
55 FormItem::NameAlchemy => FormWidget::InputBox {
56 label: "Name Alchemy",
57 text: String::new(),
58 empty_text: None,
59 currency: None,
60 },
61 FormItem::NameAliases => FormWidget::InputBox {
62 label: "Name Aliases",
63 text: String::new(),
64 empty_text: None,
65 currency: None,
66 },
67 FormItem::ChainId => FormWidget::InputBox {
68 label: "Chain Id",
69 text: String::new(),
70 empty_text: None,
71 currency: None,
72 },
73 FormItem::Symbol => FormWidget::InputBox {
74 label: "Symbol",
75 text: String::new(),
76 empty_text: None,
77 currency: None,
78 },
79 FormItem::NativeDecimals => FormWidget::InputBox {
80 label: "Native Decimals",
81 text: String::new(),
82 empty_text: None,
83 currency: None,
84 },
85 FormItem::PriceTicker => FormWidget::InputBox {
86 label: "Price Ticker",
87 text: String::new(),
88 empty_text: None,
89 currency: None,
90 },
91 FormItem::RpcUrl => FormWidget::InputBox {
92 label: "RPC Url",
93 text: String::new(),
94 empty_text: None,
95 currency: None,
96 },
97 FormItem::RpcAlchemy => FormWidget::InputBox {
98 label: "RPC Alchemy Url",
99 text: String::new(),
100 empty_text: None,
101 currency: None,
102 },
103 FormItem::RpcInfura => FormWidget::InputBox {
104 label: "RPC Infura Url",
105 text: String::new(),
106 empty_text: None,
107 currency: None,
108 },
109 FormItem::ExplorerUrl => FormWidget::InputBox {
110 label: "Explorer Url",
111 text: String::new(),
112 empty_text: None,
113 currency: None,
114 },
115 FormItem::IsTestnet => FormWidget::BooleanInput {
116 label: "Testnet",
117 value: false,
118 },
119 FormItem::TokensButton => FormWidget::Button { label: "Tokens" },
120 FormItem::SaveButton => FormWidget::Button { label: "Save" },
121 FormItem::RemoveButton => FormWidget::Button { label: "Remove" },
122 FormItem::ErrorText => FormWidget::ErrorText(String::new()),
123 };
124 Ok(widget)
125 }
126}
127pub struct NetworkCreatePage {
128 pub form: Form<FormItem>,
129 pub tokens: Vec<Token>,
130 pub network_index: usize,
131 pub network: Network,
132 pub remove_popup: ConfirmPopup,
133}
134impl NetworkCreatePage {
135 pub fn new(network_index: usize, network: Network) -> crate::Result<Self> {
136 let config = NetworkStore::load()?;
137
138 Ok(Self {
139 network: network.clone(),
140 form: Form::init(|form| {
141 *form.get_text_mut(FormItem::Name) = network.name.clone();
142 if let Some(name_alchemy) = network.name_alchemy {
143 *form.get_text_mut(FormItem::NameAlchemy) = name_alchemy;
144 }
145 if network.name_aliases.is_empty().not() {
146 *form.get_text_mut(FormItem::NameAliases) = network.name_aliases[0].clone();
147 }
148
149 *form.get_text_mut(FormItem::ChainId) = network.chain_id.to_string();
150 if let Some(symbol) = network.symbol {
151 *form.get_text_mut(FormItem::Symbol) = symbol;
152 }
153 if let Some(native_decimals) = network.native_decimals {
154 *form.get_text_mut(FormItem::NativeDecimals) = native_decimals.to_string();
155 }
156 if let Some(price_ticker) = network.price_ticker {
157 *form.get_text_mut(FormItem::PriceTicker) = price_ticker;
158 }
159 if let Some(rpc_url) = network.rpc_url {
160 *form.get_text_mut(FormItem::RpcUrl) = rpc_url;
161 }
162 if let Some(rpc_alchemy) = network.rpc_alchemy {
163 *form.get_text_mut(FormItem::RpcAlchemy) = rpc_alchemy;
164 }
165 if let Some(rpc_infura) = network.rpc_infura {
166 *form.get_text_mut(FormItem::RpcInfura) = rpc_infura;
167 }
168 if let Some(explorer_url) = network.explorer_url {
169 *form.get_text_mut(FormItem::ExplorerUrl) = explorer_url;
170 }
171 if config.networks.get(network_index).is_none() {
172 form.hide_item(FormItem::RemoveButton);
173 }
174 *form.get_boolean_mut(FormItem::IsTestnet) = network.is_testnet;
175 Ok(())
176 })?,
177 network_index,
178 tokens: network.tokens,
179 remove_popup: ConfirmPopup::new(
180 "Remove Network",
181 format!(
182 "Are you sure you want to remove the network '{}'?",
183 network.name
184 ),
185 "Remove",
186 "Cancel",
187 ),
188 })
189 }
190 fn network(form: &Form<FormItem>, tokens: &[Token]) -> Network {
191 Network {
192 name: form.get_text(FormItem::Name).clone(),
193 name_alchemy: form
194 .get_text(FormItem::NameAlchemy)
195 .is_empty()
196 .not()
197 .then(|| form.get_text(FormItem::NameAlchemy).clone()),
198 name_aliases: vec![form.get_text(FormItem::NameAliases).clone()],
199 chain_id: form.get_text(FormItem::ChainId).parse().unwrap_or_default(),
200 symbol: form
201 .get_text(FormItem::Symbol)
202 .is_empty()
203 .not()
204 .then(|| form.get_text(FormItem::Symbol).clone()),
205 native_decimals: form.get_text(FormItem::NativeDecimals).parse().ok(),
206 price_ticker: form
207 .get_text(FormItem::PriceTicker)
208 .is_empty()
209 .not()
210 .then(|| form.get_text(FormItem::PriceTicker).clone()),
211 rpc_url: form
212 .get_text(FormItem::RpcUrl)
213 .is_empty()
214 .not()
215 .then(|| form.get_text(FormItem::RpcUrl).clone()),
216 rpc_alchemy: form
217 .get_text(FormItem::RpcAlchemy)
218 .is_empty()
219 .not()
220 .then(|| form.get_text(FormItem::RpcAlchemy).clone()),
221 rpc_infura: form
222 .get_text(FormItem::RpcInfura)
223 .is_empty()
224 .not()
225 .then(|| form.get_text(FormItem::RpcInfura).clone()),
226 explorer_url: form
227 .get_text(FormItem::ExplorerUrl)
228 .is_empty()
229 .not()
230 .then(|| form.get_text(FormItem::ExplorerUrl).clone()),
231 is_testnet: form.get_boolean(FormItem::IsTestnet),
232 tokens: tokens.to_owned(),
233 }
234 }
235}
236
237impl Component for NetworkCreatePage {
238 fn handle_event(
239 &mut self,
240 event: &Event,
241 area: Rect,
242 _transmitter: &Sender<Event>,
243 _shutdown_signal: &Arc<AtomicBool>,
244 _shared_state: &SharedState,
245 ) -> crate::Result<HandleResult> {
246 let mut handle_result = HandleResult::default();
247 if self.remove_popup.is_open() {
248 self.remove_popup.handle_event(
249 event,
250 area,
251 || {
252 let mut config = NetworkStore::load()?;
253 if config.networks.get(self.network_index).is_some() {
254 config.networks.remove(self.network_index);
255 }
256 let _ = config.save();
257 handle_result.page_pops = 1;
258 handle_result.reload = true;
259 Ok(())
260 },
261 || Ok(()),
262 )?;
263 }
264 self.form.handle_event(
265 event,
266 |_, _| Ok(()),
267 |label, form| {
268 match label {
269 FormItem::SaveButton => {
270 if form.get_text(FormItem::Name).is_empty() {
271 let error = form.get_text_mut(FormItem::ErrorText);
272 *error = "Please enter name, you cannot leave it empty".to_string();
273 } else if form.get_text(FormItem::ChainId).is_empty() {
274 let error = form.get_text_mut(FormItem::ErrorText);
275 *error = "Please enter chain id, you cannot leave it empty".to_string();
276 } else {
277 let mut config = NetworkStore::load()?;
278 if config.networks.get(self.network_index).is_some() {
279 config.networks[self.network_index] =
280 Self::network(form, &self.tokens);
281 } else {
282 config.networks.push(Self::network(form, &self.tokens));
283 }
284 let _ = config.save();
285 handle_result.page_pops = 1;
286 handle_result.reload = true;
287 }
288 }
289 FormItem::TokensButton => {
290 let network = Self::network(form, &self.tokens);
291 handle_result.page_pops = 1;
292 handle_result
293 .page_inserts
294 .push(Page::Token(TokenPage::new(self.network_index, network)?));
295 handle_result.reload = true;
296 }
297 FormItem::RemoveButton => {
298 self.remove_popup.open();
299 }
300 _ => {}
301 }
302
303 Ok(())
304 },
305 )?;
306
307 Ok(handle_result)
308 }
309 fn render_component(&self, area: Rect, buf: &mut Buffer, s: &SharedState) -> Rect
310 where
311 Self: Sized,
312 {
313 self.form.render(area, buf, &s.theme);
314 self.remove_popup.render(area, buf, &s.theme.popup());
315 area
316 }
317}