1use crate::utils::update_database_questions;
2use crate::{ctx::Ctx, executor::Executor, signals::Signals, widgets::root::Root};
3use color_eyre::Result;
4use leetcode_tui_config::{constants::EDITOR, key::Key};
5use leetcode_tui_core::{emit, Event, UBStrSender};
6use leetcode_tui_db::{DbQuestion, DbTopic};
7use leetcode_tui_shared::tui::Term;
8
9pub struct App {
10 cx: super::ctx::Ctx,
11 term: Option<Term>,
12 signals: Signals,
13}
14
15impl App {
16 pub async fn run() -> Result<()> {
17 let term = Term::start()?;
18 let signals = Signals::start()?;
19 let mut app = Self {
20 cx: Ctx::new().await,
21 term: Some(term),
22 signals,
23 };
24 emit!(Render);
25 while let Some(event) = app.signals.recv().await {
26 match event {
27 Event::Quit => {
28 break;
30 }
31 Event::Input(sender, default_input) => app.dispatch_input(sender, default_input),
32 Event::Key(key) => app.dispatch_key(key),
33 Event::Render(_) => app.dispatch_render(),
34 Event::Topic(topic) => app.dispatch_topic_update(topic),
35 Event::AddQuestions(qs) => app.dispatch_add_question(qs),
36 Event::AdhocQuestion(qs) => app.dispatch_adhoc_question(qs),
37 Event::Questions(qs) => app.dispatch_question_update(qs),
38 Event::Popup(title, lines) => app.dispatch_popup(title, lines),
39 Event::SelectPopup(maybe_title, lines, result_sender) => {
40 app.dispatch_select_popup(maybe_title, lines, result_sender)
41 }
42 Event::Error(e) => app.dispatch_popup(Some("Error".into()), vec![e]),
43 Event::Open(file_path) => app.dispatch_opener(file_path),
44 Event::SyncDb => app.dispatch_db_update().await,
45 Event::ProgressUpdate(title, progress, total) => {
46 app.dispatch_progress_update(title, progress, total)
47 }
48 e => app.dispatch_module_event(e),
49 }
55 }
56 Ok(())
57 }
58
59 fn dispatch_key(&mut self, key: impl Into<Key>) {
60 if Executor::handle(&mut self.cx, key.into()) {
61 emit!(Render);
62 }
63 }
64
65 fn dispatch_topic_update(&mut self, topic: DbTopic) {
66 let questions = topic.fetch_questions().unwrap();
67 self.cx.content.get_topic_mut().set_topic(&topic);
68 self.dispatch_question_update(questions);
69 }
70
71 fn dispatch_question_update(&mut self, questions: Vec<DbQuestion>) {
72 self.cx.content.get_questions_mut().set_questions(questions);
73 emit!(Render);
74 }
75
76 fn dispatch_add_question(&mut self, questions: Vec<DbQuestion>) {
77 for ques in questions {
78 self.cx.content.get_questions_mut().add_question(ques);
79 }
80 }
81
82 fn dispatch_render(&mut self) {
83 if let Some(term) = &mut self.term {
84 let _ = term.draw(|f| {
85 f.render_widget(Root::new(&mut self.cx), f.area());
86 });
87 }
88 }
89
90 fn dispatch_popup(&mut self, title: Option<String>, lines: Vec<String>) {
91 self.cx.popup.reset(title, lines);
92 self.cx.popup.toggle();
93 emit!(Render);
94 }
95
96 fn dispatch_select_popup(
97 &mut self,
98 maybe_title: Option<String>,
99 lines: Vec<String>,
100 sender: tokio::sync::oneshot::Sender<Option<usize>>,
101 ) {
102 self.cx.select_popup.with_items(maybe_title, lines, sender);
103 self.cx.select_popup.toggle();
104 emit!(Render);
105 }
106
107 fn dispatch_opener(&mut self, file_path: std::path::PathBuf) {
108 self.signals.stop_looking_for_io_events();
110 if let Some(term) = &mut self.term {
111 term.suspend().unwrap();
112 let editor = EDITOR.get().expect("editor not set");
113 std::process::Command::new("sh")
114 .arg("-c")
115 .arg(&format!(
116 r#"{} "{}""#,
117 editor,
118 file_path.as_os_str().to_str().unwrap()
119 ))
120 .spawn()
121 .unwrap()
122 .wait()
123 .unwrap();
124 term.resume().unwrap();
125 emit!(Render);
126 }
127 self.signals.start_looking_for_io_events();
128 }
129
130 fn dispatch_module_event(&mut self, e: Event) {
131 match e {
132 Event::QuestionFilter(needle) => self.cx.content.get_questions_mut().filter_by(needle),
133 Event::QuestionUpdate => self.cx.content.get_topic().notify_change(),
134 _ => (),
135 }
136 emit!(Render);
137 }
138
139 fn dispatch_input(&mut self, sender: UBStrSender, default_input: Option<String>) {
140 self.cx.input.toggle();
141 self.cx.input.reset_with(sender, default_input);
142 emit!(Render);
143 }
144
145 fn dispatch_adhoc_question(&mut self, qs: DbQuestion) {
146 self.cx.content.get_questions_mut().set_adhoc(qs);
147 emit!(Render);
148 }
149
150 async fn dispatch_db_update(&mut self) {
151 tokio::spawn(async move {
152 update_database_questions(true).await.unwrap();
153 emit!(Topic(DbTopic {
154 slug: "all".to_string()
155 }));
156 emit!(Render);
157 });
158 }
159
160 fn dispatch_progress_update(&mut self, title: String, progress: u32, total: u32) {
161 self.cx.progress.set_progress(title, progress, total);
162 emit!(Render);
163 }
164}