Skip to main content

gm_lib/tui/app/pages/
network_create.rs

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, //TODO: Add tokens
38}
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}