1use crate::{
2 app::{
3 actions::Action,
4 app_helper::{
5 handle_edit_keybinding_mode, handle_general_actions, handle_mouse_action,
6 handle_user_input_mode, prepare_config_for_new_app,
7 },
8 kanban::{Board, Boards, Card, CardPriority, CardStatus},
9 state::{AppStatus, Focus, KeyBindingEnum, KeyBindings},
10 },
11 constants::{
12 DEFAULT_CARD_WARNING_DUE_DATE_DAYS, DEFAULT_NO_OF_BOARDS_PER_PAGE,
13 DEFAULT_NO_OF_CARDS_PER_BOARD, DEFAULT_TICKRATE, DEFAULT_TOAST_DURATION, DEFAULT_VIEW,
14 FIELD_NA, IO_EVENT_WAIT_TIME, MAX_NO_BOARDS_PER_PAGE, MAX_NO_CARDS_PER_BOARD, MAX_TICKRATE,
15 MAX_WARNING_DUE_DATE_DAYS, MIN_NO_BOARDS_PER_PAGE, MIN_NO_CARDS_PER_BOARD, MIN_TICKRATE,
16 MIN_WARNING_DUE_DATE_DAYS,
17 },
18 inputs::{key::Key, mouse::Mouse},
19 io::{
20 data_handler::{self, get_available_local_save_files, get_default_save_directory},
21 io_handler::refresh_visible_boards_and_cards,
22 logger::{get_logs, RUST_KANBAN_LOGGER},
23 IoEvent,
24 },
25 ui::{
26 text_box::TextBox,
27 theme::Theme,
28 widgets::{
29 date_time_picker::CalenderType,
30 toast::{Toast, ToastType},
31 Widgets,
32 },
33 PopUp, TextColorOptions, TextModifierOptions, View,
34 },
35};
36use linked_hash_map::LinkedHashMap;
37use log::{debug, error, warn};
38use ratatui::widgets::TableState;
39use serde::{Deserialize, Serialize};
40use serde_json::Value;
41use state::AppState;
42use std::{
43 collections::HashMap,
44 fmt::{self, Display, Formatter},
45 path::PathBuf,
46 str::FromStr,
47 time::{Duration, Instant},
48 vec,
49};
50use strum::{EnumString, IntoEnumIterator};
51use strum_macros::EnumIter;
52
53pub mod actions;
54pub mod app_helper;
55pub mod kanban;
56pub mod state;
57
58#[derive(Debug, PartialEq, Eq)]
59pub enum AppReturn {
60 Exit,
61 Continue,
62}
63
64#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
65pub enum ActionHistory {
66 DeleteCard(Card, (u64, u64)),
68 CreateCard(Card, (u64, u64)),
70 DeleteBoard(Board),
72 MoveCardBetweenBoards(Card, (u64, u64), (u64, u64), usize, usize),
74 MoveCardWithinBoard((u64, u64), usize, usize),
76 CreateBoard(Board),
78 EditCard(Card, Card, (u64, u64)),
80}
81
82#[derive(Default)]
83pub struct ActionHistoryManager {
84 pub history: Vec<ActionHistory>,
85 pub history_index: usize,
86}
87
88impl ActionHistoryManager {
89 pub fn new_action(&mut self, action: ActionHistory) {
90 if self.history_index != self.history.len() {
91 self.history.truncate(self.history_index);
92 }
93 self.history.push(action);
94 self.history_index += 1;
95 }
96 pub fn reset(&mut self) {
97 self.history.clear();
98 self.history_index = 0;
99 }
100}
101
102pub struct App<'a> {
103 io_tx: tokio::sync::mpsc::Sender<IoEvent>,
104 actions: Vec<Action>,
105 is_loading: bool,
106 pub debug_mode: bool,
107 pub state: AppState<'a>,
108 pub boards: Boards,
109 pub filtered_boards: Boards,
110 pub preview_boards_and_cards: Option<Boards>,
111 pub config: AppConfig,
112 pub visible_boards_and_cards: LinkedHashMap<(u64, u64), Vec<(u64, u64)>>,
113 pub last_io_event_time: Option<Instant>,
114 pub all_themes: Vec<Theme>,
115 pub current_theme: Theme,
116 pub action_history_manager: ActionHistoryManager,
117 pub main_menu: MainMenu,
118 pub widgets: Widgets<'a>,
119}
120
121impl App<'_> {
122 pub fn new(io_tx: tokio::sync::mpsc::Sender<IoEvent>, debug_mode: bool) -> Self {
123 let actions = vec![Action::Quit];
124 let is_loading = false;
125 let state = AppState::default();
126 let boards = Boards::default();
127 let filtered_boards = Boards::default();
128 let all_themes = Theme::all_default_themes();
129 let mut theme = Theme::default();
130 let (config, config_errors, toasts) = prepare_config_for_new_app(theme.clone());
131 let default_theme = config.default_theme.clone();
132 let theme_in_all = all_themes.iter().find(|t| t.name == default_theme);
133 if let Some(theme_in_all) = theme_in_all {
134 theme = theme_in_all.clone();
135 }
136 let mut widgets = Widgets::new(
137 theme.clone(),
138 debug_mode,
139 config.date_picker_calender_format.clone(),
140 );
141 widgets.toast_widget.toasts = toasts;
142 let mut app = Self {
143 io_tx,
144 actions,
145 is_loading,
146 debug_mode,
147 state,
148 boards,
149 filtered_boards,
150 preview_boards_and_cards: None,
151 config,
152 visible_boards_and_cards: LinkedHashMap::new(),
153 last_io_event_time: None,
154 all_themes,
155 current_theme: theme,
156 action_history_manager: ActionHistoryManager::default(),
157 main_menu: MainMenu::default(),
158 widgets,
159 };
160 if !config_errors.is_empty() {
161 for error in config_errors {
162 app.send_error_toast(error, None);
163 }
164 }
165 app
166 }
167
168 pub async fn do_action(&mut self, key: Key) -> AppReturn {
169 if self.state.app_status == AppStatus::UserInput {
170 handle_user_input_mode(self, key).await
171 } else if self.state.app_status == AppStatus::KeyBindMode {
172 handle_edit_keybinding_mode(self, key).await
173 } else {
174 handle_general_actions(self, key).await
175 }
176 }
177 pub async fn dispatch(&mut self, action: IoEvent) {
178 self.is_loading = true;
179 if self
180 .last_io_event_time
181 .unwrap_or_else(|| Instant::now() - Duration::from_millis(IO_EVENT_WAIT_TIME + 10))
182 + Duration::from_millis(IO_EVENT_WAIT_TIME)
183 > Instant::now()
184 {
185 tokio::time::sleep(Duration::from_millis(IO_EVENT_WAIT_TIME)).await;
186 }
187 self.last_io_event_time = Some(Instant::now());
188 if let Err(e) = self.io_tx.send(action).await {
189 self.is_loading = false;
190 debug!("Error from dispatch {}", e);
191 error!("Error in handling request please, restart the app");
192 self.send_error_toast("Error in handling request please, restart the app", None);
193 };
194 }
195
196 pub async fn handle_mouse(&mut self, mouse_action: Mouse) -> AppReturn {
197 if self.config.enable_mouse_support {
198 handle_mouse_action(self, mouse_action).await
199 } else {
200 AppReturn::Continue
201 }
202 }
203 pub fn get_first_keybinding(&self, keybinding_enum: KeyBindingEnum) -> Option<String> {
204 self.config
205 .keybindings
206 .get_keybindings(keybinding_enum)
207 .and_then(|keys| keys.first().cloned())
208 .map(|key| key.to_string())
209 }
210 pub fn status(&self) -> &AppStatus {
211 &self.state.app_status
212 }
213 pub fn is_loading(&self) -> bool {
214 self.is_loading
215 }
216 pub fn initialized(&mut self) {
217 self.actions = Action::all();
218 self.state.set_focus(Focus::Body);
219 self.state.app_status = AppStatus::initialized()
220 }
221 pub fn loaded(&mut self) {
222 self.is_loading = false;
223 }
224 pub fn get_current_focus(&self) -> &Focus {
225 &self.state.focus
226 }
227 pub fn set_config_state(&mut self, config_state: TableState) {
228 self.state.app_table_states.config = config_state;
229 }
230 pub fn config_next(&mut self) {
231 let i = match self.state.app_table_states.config.selected() {
232 Some(i) => {
233 if i >= self.config.to_view_list().len() - 1 {
234 0
235 } else {
236 i + 1
237 }
238 }
239 None => 0,
240 };
241 self.state.app_table_states.config.select(Some(i));
242 }
243 pub fn config_prv(&mut self) {
244 let i = match self.state.app_table_states.config.selected() {
245 Some(i) => {
246 if i == 0 {
247 self.config.to_view_list().len() - 1
248 } else {
249 i - 1
250 }
251 }
252 None => 0,
253 };
254 self.state.app_table_states.config.select(Some(i));
255 }
256 pub fn main_menu_next(&mut self) {
257 let i = match self.state.app_list_states.main_menu.selected() {
258 Some(i) => {
259 if i >= self.main_menu.all().len() - 1 {
260 0
261 } else {
262 i + 1
263 }
264 }
265 None => 0,
266 };
267 self.state.app_list_states.main_menu.select(Some(i));
268 }
269 pub fn main_menu_prv(&mut self) {
270 let i = match self.state.app_list_states.main_menu.selected() {
271 Some(i) => {
272 if i == 0 {
273 self.main_menu.items.len() - 1
274 } else {
275 i - 1
276 }
277 }
278 None => 0,
279 };
280 self.state.app_list_states.main_menu.select(Some(i));
281 }
282 pub fn load_save_next(&mut self, cloud_mode: bool) {
283 let i = match self.state.app_list_states.load_save.selected() {
284 Some(i) => {
285 if cloud_mode {
286 let cloud_save_files = self.state.cloud_data.clone();
287 let cloud_save_files_len = if let Some(cloud_save_files_len) = cloud_save_files
288 {
289 cloud_save_files_len.len()
290 } else {
291 0
292 };
293 if cloud_save_files_len == 0 || i >= cloud_save_files_len - 1 {
294 0
295 } else {
296 i + 1
297 }
298 } else {
299 let local_save_files = get_available_local_save_files(&self.config);
300 let local_save_files_len = if let Some(local_save_files_len) = local_save_files
301 {
302 local_save_files_len.len()
303 } else {
304 0
305 };
306 if local_save_files_len == 0 || i >= local_save_files_len - 1 {
307 0
308 } else {
309 i + 1
310 }
311 }
312 }
313 None => 0,
314 };
315 self.state.app_list_states.load_save.select(Some(i));
316 }
317 pub fn load_save_prv(&mut self, cloud_mode: bool) {
318 let i = match self.state.app_list_states.load_save.selected() {
319 Some(i) => {
320 if cloud_mode {
321 let cloud_save_files = self.state.cloud_data.clone();
322 let cloud_save_files_len = if let Some(cloud_save_files_len) = cloud_save_files
323 {
324 cloud_save_files_len.len()
325 } else {
326 0
327 };
328 if i == 0 && cloud_save_files_len != 0 {
329 cloud_save_files_len - 1
330 } else if cloud_save_files_len == 0 {
331 0
332 } else {
333 i - 1
334 }
335 } else {
336 let local_save_files = get_available_local_save_files(&self.config);
337 let local_save_files_len = if let Some(local_save_files_len) = local_save_files
338 {
339 local_save_files_len.len()
340 } else {
341 0
342 };
343 if i == 0 && local_save_files_len != 0 {
344 local_save_files_len - 1
345 } else if local_save_files_len == 0 {
346 0
347 } else {
348 i - 1
349 }
350 }
351 }
352 None => 0,
353 };
354 self.state.app_list_states.load_save.select(Some(i));
355 }
356 pub fn config_state(&self) -> &TableState {
357 &self.state.app_table_states.config
358 }
359 pub fn edit_keybindings_next(&mut self) {
360 let keybinding_iterator = self.config.keybindings.iter();
361 let i = match self.state.app_table_states.edit_keybindings.selected() {
362 Some(i) => {
363 if i >= keybinding_iterator.count() - 1 {
364 0
365 } else {
366 i + 1
367 }
368 }
369 None => 0,
370 };
371 self.state.app_table_states.edit_keybindings.select(Some(i));
372 }
373 pub fn edit_keybindings_prv(&mut self) {
374 let keybinding_iterator = self.config.keybindings.iter();
375 let i = match self.state.app_table_states.edit_keybindings.selected() {
376 Some(i) => {
377 if i == 0 {
378 keybinding_iterator.count() - 1
379 } else {
380 i - 1
381 }
382 }
383 None => 0,
384 };
385 self.state.app_table_states.edit_keybindings.select(Some(i));
386 }
387 pub fn help_next(&mut self) {
388 let all_keybindings: Vec<_> = self.config.keybindings.iter().collect();
389 let i = match self.state.app_table_states.help.selected() {
390 Some(i) => {
391 if !all_keybindings.is_empty() {
392 if i >= (all_keybindings.len() / 2) - 1 {
393 0
394 } else {
395 i + 1
396 }
397 } else {
398 0
399 }
400 }
401 None => 0,
402 };
403 self.state.app_table_states.help.select(Some(i));
404 }
405 pub fn help_prv(&mut self) {
406 let all_keybindings: Vec<_> = self.config.keybindings.iter().collect();
407 let i = match self.state.app_table_states.help.selected() {
408 Some(i) => {
409 if !all_keybindings.is_empty() {
410 if i == 0 {
411 (all_keybindings.len() / 2) - 1
412 } else {
413 i - 1
414 }
415 } else {
416 0
417 }
418 }
419 None => 0,
420 };
421 self.state.app_table_states.help.select(Some(i));
422 }
423 pub fn select_default_view_next(&mut self) {
424 let i = match self.state.app_list_states.default_view.selected() {
425 Some(i) => {
426 if i >= View::all_views_as_string().len() - 1 {
427 0
428 } else {
429 i + 1
430 }
431 }
432 None => 0,
433 };
434 self.state.app_list_states.default_view.select(Some(i));
435 }
436 pub fn select_default_view_prv(&mut self) {
437 let i = match self.state.app_list_states.default_view.selected() {
438 Some(i) => {
439 if i == 0 {
440 View::all_views_as_string().len() - 1
441 } else {
442 i - 1
443 }
444 }
445 None => 0,
446 };
447 self.state.app_list_states.default_view.select(Some(i));
448 }
449 pub fn command_palette_command_search_prv(&mut self) {
450 let i = match self
451 .state
452 .app_list_states
453 .command_palette_command_search
454 .selected()
455 {
456 Some(i) => {
457 if let Some(results) = &self.widgets.command_palette.command_search_results {
458 if i == 0 {
459 results.len() - 1
460 } else {
461 i - 1
462 }
463 } else {
464 0
465 }
466 }
467 None => 0,
468 };
469 self.state
470 .app_list_states
471 .command_palette_command_search
472 .select(Some(i));
473 }
474 pub fn command_palette_command_search_next(&mut self) {
475 let i = match self
476 .state
477 .app_list_states
478 .command_palette_command_search
479 .selected()
480 {
481 Some(i) => {
482 if let Some(results) = &self.widgets.command_palette.command_search_results {
483 if i >= results.len() - 1 {
484 0
485 } else {
486 i + 1
487 }
488 } else {
489 0
490 }
491 }
492 None => 0,
493 };
494 self.state
495 .app_list_states
496 .command_palette_command_search
497 .select(Some(i));
498 }
499 pub fn command_palette_card_search_next(&mut self) {
500 let i = match self
501 .state
502 .app_list_states
503 .command_palette_card_search
504 .selected()
505 {
506 Some(i) => {
507 if let Some(results) = &self.widgets.command_palette.card_search_results {
508 if i >= results.len() - 1 {
509 0
510 } else {
511 i + 1
512 }
513 } else {
514 0
515 }
516 }
517 None => 0,
518 };
519 self.state
520 .app_list_states
521 .command_palette_card_search
522 .select(Some(i));
523 }
524 pub fn command_palette_card_search_prv(&mut self) {
525 let i = match self
526 .state
527 .app_list_states
528 .command_palette_card_search
529 .selected()
530 {
531 Some(i) => {
532 if let Some(results) = &self.widgets.command_palette.card_search_results {
533 if i == 0 {
534 results.len() - 1
535 } else {
536 i - 1
537 }
538 } else {
539 0
540 }
541 }
542 None => 0,
543 };
544 self.state
545 .app_list_states
546 .command_palette_card_search
547 .select(Some(i));
548 }
549 pub fn command_palette_board_search_next(&mut self) {
550 let i = match self
551 .state
552 .app_list_states
553 .command_palette_board_search
554 .selected()
555 {
556 Some(i) => {
557 if let Some(results) = &self.widgets.command_palette.board_search_results {
558 if i >= results.len() - 1 {
559 0
560 } else {
561 i + 1
562 }
563 } else {
564 0
565 }
566 }
567 None => 0,
568 };
569 self.state
570 .app_list_states
571 .command_palette_board_search
572 .select(Some(i));
573 }
574 pub fn command_palette_board_search_prv(&mut self) {
575 let i = match self
576 .state
577 .app_list_states
578 .command_palette_board_search
579 .selected()
580 {
581 Some(i) => {
582 if let Some(results) = &self.widgets.command_palette.board_search_results {
583 if i == 0 {
584 results.len() - 1
585 } else {
586 i - 1
587 }
588 } else {
589 0
590 }
591 }
592 None => 0,
593 };
594 self.state
595 .app_list_states
596 .command_palette_board_search
597 .select(Some(i));
598 }
599 pub fn send_info_toast(&mut self, message: &str, custom_duration: Option<Duration>) {
600 if let Some(duration) = custom_duration {
601 self.widgets.toast_widget.toasts.push(Toast::new(
602 message.to_string(),
603 duration,
604 ToastType::Info,
605 self.current_theme.clone(),
606 ));
607 } else {
608 self.widgets.toast_widget.toasts.push(Toast::new(
609 message.to_string(),
610 Duration::from_secs(DEFAULT_TOAST_DURATION),
611 ToastType::Info,
612 self.current_theme.clone(),
613 ));
614 }
615 }
616 pub fn send_error_toast(&mut self, message: &str, custom_duration: Option<Duration>) {
617 if let Some(duration) = custom_duration {
618 self.widgets.toast_widget.toasts.push(Toast::new(
619 message.to_string(),
620 duration,
621 ToastType::Error,
622 self.current_theme.clone(),
623 ));
624 } else {
625 self.widgets.toast_widget.toasts.push(Toast::new(
626 message.to_string(),
627 Duration::from_secs(DEFAULT_TOAST_DURATION),
628 ToastType::Error,
629 self.current_theme.clone(),
630 ));
631 }
632 }
633 pub fn send_warning_toast(&mut self, message: &str, custom_duration: Option<Duration>) {
634 if let Some(duration) = custom_duration {
635 self.widgets.toast_widget.toasts.push(Toast::new(
636 message.to_string(),
637 duration,
638 ToastType::Warning,
639 self.current_theme.clone(),
640 ));
641 } else {
642 self.widgets.toast_widget.toasts.push(Toast::new(
643 message.to_string(),
644 Duration::from_secs(DEFAULT_TOAST_DURATION),
645 ToastType::Warning,
646 self.current_theme.clone(),
647 ));
648 }
649 }
650 pub fn select_card_status_prv(&mut self) {
651 let i = match self.state.app_list_states.card_status_selector.selected() {
652 Some(i) => {
653 if i == 0 {
654 CardStatus::all().len() - 1
655 } else {
656 i - 1
657 }
658 }
659 None => 0,
660 };
661 self.state
662 .app_list_states
663 .card_status_selector
664 .select(Some(i));
665 }
666 pub fn select_card_status_next(&mut self) {
667 let i = match self.state.app_list_states.card_status_selector.selected() {
668 Some(i) => {
669 if i >= CardStatus::all().len() - 1 {
670 0
671 } else {
672 i + 1
673 }
674 }
675 None => 0,
676 };
677 self.state
678 .app_list_states
679 .card_status_selector
680 .select(Some(i));
681 }
682 pub fn select_change_theme_next(&mut self) {
683 let i = match self.state.app_list_states.theme_selector.selected() {
684 Some(i) => {
685 if i >= self.all_themes.len() - 1 {
686 0
687 } else {
688 i + 1
689 }
690 }
691 None => 0,
692 };
693 self.state.app_list_states.theme_selector.select(Some(i));
694 self.current_theme = self.all_themes[i].clone();
695 }
696 pub fn select_change_theme_prv(&mut self) {
697 let i = match self.state.app_list_states.theme_selector.selected() {
698 Some(i) => {
699 if i == 0 {
700 self.all_themes.len() - 1
701 } else {
702 i - 1
703 }
704 }
705 None => 0,
706 };
707 self.state.app_list_states.theme_selector.select(Some(i));
708 self.current_theme = self.all_themes[i].clone();
709 }
710 pub fn select_create_theme_next(&mut self) {
711 let theme_rows_len = Theme::default().to_rows(self, true).1.len();
713 let i = match self.state.app_table_states.theme_editor.selected() {
714 Some(i) => {
715 if i >= theme_rows_len - 1 {
716 0
717 } else {
718 i + 1
719 }
720 }
721 None => 0,
722 };
723 self.state.app_table_states.theme_editor.select(Some(i));
724 }
725 pub fn select_create_theme_prv(&mut self) {
726 let theme_rows_len = Theme::default().to_rows(self, true).1.len();
728 let i = match self.state.app_table_states.theme_editor.selected() {
729 Some(i) => {
730 if i == 0 {
731 theme_rows_len - 1
732 } else {
733 i - 1
734 }
735 }
736 None => 0,
737 };
738 self.state.app_table_states.theme_editor.select(Some(i));
739 }
740 pub fn select_edit_style_fg_next(&mut self) {
741 let i = match self.state.app_list_states.edit_specific_style[0].selected() {
742 Some(i) => {
743 if i >= TextColorOptions::iter().count() - 1 {
744 0
745 } else {
746 i + 1
747 }
748 }
749 None => 0,
750 };
751 self.state.app_list_states.edit_specific_style[0].select(Some(i));
752 }
753 pub fn select_edit_style_fg_prv(&mut self) {
754 let i = match self.state.app_list_states.edit_specific_style[0].selected() {
755 Some(i) => {
756 if i == 0 {
757 TextColorOptions::iter().count() - 1
758 } else {
759 i - 1
760 }
761 }
762 None => 0,
763 };
764 self.state.app_list_states.edit_specific_style[0].select(Some(i));
765 }
766 pub fn select_edit_style_bg_next(&mut self) {
767 let i = match self.state.app_list_states.edit_specific_style[1].selected() {
768 Some(i) => {
769 if i >= TextColorOptions::iter().count() - 1 {
770 0
771 } else {
772 i + 1
773 }
774 }
775 None => 0,
776 };
777 self.state.app_list_states.edit_specific_style[1].select(Some(i));
778 }
779 pub fn select_edit_style_bg_prv(&mut self) {
780 let i = match self.state.app_list_states.edit_specific_style[1].selected() {
781 Some(i) => {
782 if i == 0 {
783 TextColorOptions::iter().count() - 1
784 } else {
785 i - 1
786 }
787 }
788 None => 0,
789 };
790 self.state.app_list_states.edit_specific_style[1].select(Some(i));
791 }
792 pub fn select_edit_style_modifier_next(&mut self) {
793 let i = match self.state.app_list_states.edit_specific_style[2].selected() {
794 Some(i) => {
795 if i >= TextModifierOptions::iter().count() - 1 {
796 0
797 } else {
798 i + 1
799 }
800 }
801 None => 0,
802 };
803 self.state.app_list_states.edit_specific_style[2].select(Some(i));
804 }
805 pub fn select_edit_style_modifier_prv(&mut self) {
806 let i = match self.state.app_list_states.edit_specific_style[2].selected() {
807 Some(i) => {
808 if i == 0 {
809 TextModifierOptions::iter().count() - 1
810 } else {
811 i - 1
812 }
813 }
814 None => 0,
815 };
816 self.state.app_list_states.edit_specific_style[2].select(Some(i));
817 }
818 pub fn select_card_priority_next(&mut self) {
819 let i = match self.state.app_list_states.card_priority_selector.selected() {
820 Some(i) => {
821 if i >= CardPriority::all().len() - 1 {
822 0
823 } else {
824 i + 1
825 }
826 }
827 None => 0,
828 };
829 self.state
830 .app_list_states
831 .card_priority_selector
832 .select(Some(i));
833 }
834 pub fn select_card_priority_prv(&mut self) {
835 let i = match self.state.app_list_states.card_priority_selector.selected() {
836 Some(i) => {
837 if i == 0 {
838 CardPriority::all().len() - 1
839 } else {
840 i - 1
841 }
842 }
843 None => 0,
844 };
845 self.state
846 .app_list_states
847 .card_priority_selector
848 .select(Some(i));
849 }
850 pub fn filter_by_tag_popup_next(&mut self) {
851 let all_tags_len = if let Some(available_tags) = &self.state.all_available_tags {
852 available_tags.len()
853 } else {
854 0
855 };
856 if all_tags_len > 0 {
857 let i = match self.state.app_list_states.filter_by_tag_list.selected() {
858 Some(i) => {
859 if i >= all_tags_len - 1 {
860 0
861 } else {
862 i + 1
863 }
864 }
865 None => 0,
866 };
867 self.state
868 .app_list_states
869 .filter_by_tag_list
870 .select(Some(i));
871 }
872 }
873 pub fn filter_by_tag_popup_prv(&mut self) {
874 let all_tags_len = if let Some(available_tags) = &self.state.all_available_tags {
875 available_tags.len()
876 } else {
877 0
878 };
879 if all_tags_len > 0 {
880 let i = match self.state.app_list_states.filter_by_tag_list.selected() {
881 Some(i) => {
882 if i == 0 {
883 all_tags_len - 1
884 } else {
885 i - 1
886 }
887 }
888 None => 0,
889 };
890 self.state
891 .app_list_states
892 .filter_by_tag_list
893 .select(Some(i));
894 }
895 }
896 pub fn change_date_format_popup_next(&mut self) {
897 let i = match self.state.app_list_states.date_format_selector.selected() {
898 Some(i) => {
899 if i >= DateTimeFormat::get_all_date_formats().len() - 1 {
900 0
901 } else {
902 i + 1
903 }
904 }
905 None => 0,
906 };
907 self.state
908 .app_list_states
909 .date_format_selector
910 .select(Some(i));
911 }
912 pub fn change_date_format_popup_prv(&mut self) {
913 let i = match self.state.app_list_states.date_format_selector.selected() {
914 Some(i) => {
915 if i == 0 {
916 DateTimeFormat::get_all_date_formats().len() - 1
917 } else {
918 i - 1
919 }
920 }
921 None => 0,
922 };
923 self.state
924 .app_list_states
925 .date_format_selector
926 .select(Some(i));
927 }
928 pub fn undo(&mut self) {
929 if self.action_history_manager.history_index == 0 {
930 self.send_error_toast("No more actions to undo", None);
931 } else {
932 let history_index = self.action_history_manager.history_index - 1;
933 let history = self.action_history_manager.history[history_index].clone();
934 match history {
935 ActionHistory::DeleteCard(card, board_id) => {
936 if let Some(board) = self.boards.get_mut_board_with_id(board_id) {
937 board.cards.add_card(card.clone());
938 self.action_history_manager.history_index -= 1;
939 refresh_visible_boards_and_cards(self);
940 self.send_info_toast(&format!("Undo Delete Card '{}'", card.name), None);
941 } else {
942 self.send_error_toast(&format!("Could not undo delete card '{}' as the board with id '{:?}' was not found", card.name, board_id), None);
943 }
944 }
945 ActionHistory::CreateCard(card, board_id) => {
946 if let Some(board) = self.boards.get_mut_board_with_id(board_id) {
947 board.cards.remove_card_with_id(card.id);
948 refresh_visible_boards_and_cards(self);
949 self.action_history_manager.history_index -= 1;
950 self.send_info_toast(&format!("Undo Create Card '{}'", card.name), None);
951 } else {
952 self.send_error_toast(&format!("Could not undo create card '{}' as the board with id '{:?}' was not found", card.name, board_id), None);
953 }
954 }
955 ActionHistory::MoveCardBetweenBoards(
956 card,
957 moved_from_board_id,
958 moved_to_board_id,
959 moved_from_index,
960 moved_to_index,
961 ) => {
962 let moved_to_board = self.boards.get_board_with_id(moved_to_board_id);
963 let moved_from_board = self.boards.get_board_with_id(moved_from_board_id);
964 if moved_to_board.is_none() || moved_from_board.is_none() {
965 debug!("Could not undo move card '{}' as the move to board with id '{:?}' or the move from board with id '{:?}' was not found", card.name, moved_to_board_id, moved_from_board_id);
966 return;
967 }
968
969 let moved_from_board = moved_from_board.unwrap();
970 if moved_from_index > moved_from_board.cards.len() {
971 debug!("bad index for undo move card, from board {:?}, to board {:?}, from index {}, to index {}", moved_from_board_id, moved_to_board_id, moved_from_index, moved_to_index);
972 self.send_error_toast(
973 &format!(
974 "Could not undo move card '{}' as the index's were invalid",
975 card.name
976 ),
977 None,
978 );
979 }
980
981 let moved_to_board = self
982 .boards
983 .get_mut_board_with_id(moved_to_board_id)
984 .unwrap();
985 moved_to_board.cards.remove_card_with_id(card.id);
986
987 let moved_from_board = self
988 .boards
989 .get_mut_board_with_id(moved_from_board_id)
990 .unwrap();
991 moved_from_board
992 .cards
993 .add_card_at_index(moved_from_index, card.clone());
994
995 refresh_visible_boards_and_cards(self);
996 self.action_history_manager.history_index -= 1;
997 self.send_info_toast(&format!("Undo Move Card '{}'", card.name), None);
998 }
999 ActionHistory::MoveCardWithinBoard(board_id, moved_from_index, moved_to_index) => {
1000 if let Some(board) = self.boards.get_mut_board_with_id(board_id) {
1001 if moved_from_index >= board.cards.len()
1002 || moved_to_index >= board.cards.len()
1003 {
1004 self.send_error_toast(
1005 &format!(
1006 "Could not undo move card '{}' as the index's were invalid",
1007 FIELD_NA
1008 ),
1009 None,
1010 );
1011 return;
1012 }
1013 let card_name = board
1014 .cards
1015 .get_mut_card_with_index(moved_to_index)
1016 .unwrap()
1017 .name
1018 .clone();
1019 board.cards.swap(moved_from_index, moved_to_index);
1020 refresh_visible_boards_and_cards(self);
1021 self.action_history_manager.history_index -= 1;
1022 self.send_info_toast(&format!("Undo Move Card '{}'", card_name), None);
1023 } else {
1024 self.send_error_toast(&format!("Could not undo move card '{}' as the board with id '{:?}' was not found",FIELD_NA, board_id), None);
1025 }
1026 }
1027 ActionHistory::DeleteBoard(board) => {
1028 self.boards.add_board(board.clone());
1029 refresh_visible_boards_and_cards(self);
1030 self.action_history_manager.history_index -= 1;
1031 self.send_info_toast(&format!("Undo Delete Board '{}'", board.name), None);
1032 }
1033 ActionHistory::CreateBoard(board) => {
1034 self.boards.remove_board_with_id(board.id);
1035 refresh_visible_boards_and_cards(self);
1036 self.action_history_manager.history_index -= 1;
1037 self.send_info_toast(&format!("Undo Create Board '{}'", board.name), None);
1038 }
1039 ActionHistory::EditCard(old_card, _, board_id) => {
1040 let mut card_name = String::new();
1041 let mut card_found = false;
1042 if let Some(board) = self.boards.get_mut_board_with_id(board_id) {
1043 if let Some(card) = board.cards.get_mut_card_with_id(old_card.id) {
1044 *card = old_card.clone();
1045 card_name.clone_from(&card.name);
1046 card_found = true;
1047 } else {
1048 self.send_error_toast(
1049 &format!(
1050 "Could not undo edit card '{}' as the card was not found",
1051 old_card.name
1052 ),
1053 None,
1054 );
1055 }
1056 } else {
1057 self.send_error_toast(&format!("Could not undo edit card '{}' as the board with id '{:?}' was not found", old_card.name, board_id), None);
1058 }
1059 if card_found {
1060 self.action_history_manager.history_index -= 1;
1061 }
1062 if !card_name.is_empty() {
1063 self.send_info_toast(&format!("Undo Edit Card '{}'", card_name), None);
1064 refresh_visible_boards_and_cards(self);
1065 }
1066 }
1067 }
1068 }
1069 }
1070
1071 pub fn redo(&mut self) {
1072 if self.action_history_manager.history_index == self.action_history_manager.history.len() {
1073 self.send_error_toast("No more actions to redo", None);
1074 } else {
1075 let history_index = self.action_history_manager.history_index;
1076 let history = self.action_history_manager.history[history_index].clone();
1077 match history {
1078 ActionHistory::DeleteCard(card, board_id) => {
1079 if let Some(board) = self.boards.get_mut_board_with_id(board_id) {
1080 board.cards.remove_card_with_id(card.id);
1081 refresh_visible_boards_and_cards(self);
1082 self.action_history_manager.history_index += 1;
1083 self.send_info_toast(&format!("Redo Delete Card '{}'", card.name), None);
1084 } else {
1085 self.send_error_toast(&format!("Could not redo delete card '{}' as the board with id '{:?}' was not found", card.name, board_id), None);
1086 }
1087 }
1088 ActionHistory::CreateCard(card, board_id) => {
1089 if let Some(board) = self.boards.get_mut_board_with_id(board_id) {
1090 board.cards.add_card(card.clone());
1091 refresh_visible_boards_and_cards(self);
1092 self.action_history_manager.history_index += 1;
1093 self.send_info_toast(&format!("Redo Create Card '{}'", card.name), None);
1094 } else {
1095 self.send_error_toast(&format!("Could not redo create card '{}' as the board with id '{:?}' was not found", card.name, board_id), None);
1096 }
1097 }
1098 ActionHistory::MoveCardBetweenBoards(
1099 card,
1100 moved_from_board_id,
1101 moved_to_board_id,
1102 moved_from_index,
1103 moved_to_index,
1104 ) => {
1105 let moved_to_board = self.boards.get_board_with_id(moved_to_board_id);
1106 let moved_from_board = self.boards.get_board_with_id(moved_from_board_id);
1107 if moved_to_board.is_none() || moved_from_board.is_none() {
1108 debug!("Could not undo move card '{}' as the move to board with id '{:?}' or the move from board with id '{:?}' was not found", card.name, moved_to_board_id, moved_from_board_id);
1109 return;
1110 }
1111
1112 let moved_to_board = moved_to_board.unwrap();
1113 if moved_to_index > moved_to_board.cards.len() {
1114 debug!("bad index for redo move card, from board {:?}, to board {:?}, from index {}, to index {}", moved_from_board_id, moved_to_board_id, moved_from_index, moved_to_index);
1115 self.send_error_toast(
1116 &format!(
1117 "Could not redo move card '{}' as the index's were invalid",
1118 card.name
1119 ),
1120 None,
1121 );
1122 return;
1123 }
1124
1125 let moved_from_board = self
1126 .boards
1127 .get_mut_board_with_id(moved_from_board_id)
1128 .unwrap();
1129 moved_from_board.cards.remove_card_with_id(card.id);
1130
1131 let moved_to_board = self
1132 .boards
1133 .get_mut_board_with_id(moved_to_board_id)
1134 .unwrap();
1135 moved_to_board
1136 .cards
1137 .add_card_at_index(moved_to_index, card.clone());
1138
1139 refresh_visible_boards_and_cards(self);
1140 self.action_history_manager.history_index += 1;
1141 self.send_info_toast(&format!("Redo Move Card '{}'", card.name), None);
1142 }
1143 ActionHistory::MoveCardWithinBoard(board_id, moved_from_index, moved_to_index) => {
1144 if let Some(board) = self.boards.get_mut_board_with_id(board_id) {
1145 if moved_from_index >= board.cards.len()
1146 || moved_to_index >= board.cards.len()
1147 {
1148 self.send_error_toast(
1149 &format!(
1150 "Could not redo move card '{}' as the index's were invalid",
1151 FIELD_NA
1152 ),
1153 None,
1154 );
1155 return;
1156 }
1157 let card_name = board
1158 .cards
1159 .get_card_with_index(moved_to_index)
1160 .unwrap()
1161 .name
1162 .clone();
1163 board.cards.swap(moved_from_index, moved_to_index);
1164 refresh_visible_boards_and_cards(self);
1165 self.action_history_manager.history_index += 1;
1166 self.send_info_toast(&format!("Redo Move Card '{}'", card_name), None);
1167 } else {
1168 self.send_error_toast(&format!("Could not redo move card '{}' as the board with id '{:?}' was not found", FIELD_NA, board_id), None);
1169 }
1170 }
1171 ActionHistory::DeleteBoard(board) => {
1172 self.boards.remove_board_with_id(board.id);
1173 refresh_visible_boards_and_cards(self);
1174 self.action_history_manager.history_index += 1;
1175 self.send_info_toast(&format!("Redo Delete Board '{}'", board.name), None);
1176 }
1177 ActionHistory::CreateBoard(board) => {
1178 self.boards.add_board(board.clone());
1179 refresh_visible_boards_and_cards(self);
1180 self.action_history_manager.history_index += 1;
1181 self.send_info_toast(&format!("Redo Create Board '{}'", board.name), None);
1182 }
1183 ActionHistory::EditCard(_, new_card, board_id) => {
1184 let mut card_name = String::new();
1185 let mut card_found = false;
1186 if let Some(board) = self.boards.get_mut_board_with_id(board_id) {
1187 if let Some(card) = board.cards.get_mut_card_with_id(new_card.id) {
1188 *card = new_card.clone();
1189 card_name.clone_from(&card.name);
1190 card_found = true;
1191 } else {
1192 self.send_error_toast(
1193 &format!(
1194 "Could not redo edit card '{}' as the card was not found",
1195 new_card.name
1196 ),
1197 None,
1198 );
1199 }
1200 } else {
1201 self.send_error_toast(&format!("Could not redo edit card '{}' as the board with id '{:?}' was not found", new_card.name, board_id), None);
1202 }
1203 if card_found {
1204 self.action_history_manager.history_index += 1;
1205 }
1206 if !card_name.is_empty() {
1207 self.send_info_toast(&format!("Redo Edit Card '{}'", card_name), None);
1208 refresh_visible_boards_and_cards(self);
1209 }
1210 }
1211 }
1212 }
1213 }
1214 pub fn log_next(&mut self) {
1215 let total_logs = get_logs().len();
1216 let mut hot_log = RUST_KANBAN_LOGGER.hot_log.lock();
1217 let i = match hot_log.state.selected() {
1218 Some(i) => {
1219 if i >= total_logs - 1 {
1220 0
1221 } else {
1222 i + 1
1223 }
1224 }
1225 None => 0,
1226 };
1227 hot_log.state.select(Some(i));
1228 }
1229 pub fn log_prv(&mut self) {
1230 let total_logs = get_logs().len();
1231 let mut hot_log = RUST_KANBAN_LOGGER.hot_log.lock();
1232 let i = match hot_log.state.selected() {
1233 Some(i) => {
1234 if i == 0 {
1235 total_logs - 1
1236 } else {
1237 i - 1
1238 }
1239 }
1240 None => 0,
1241 };
1242 hot_log.state.select(Some(i));
1243 }
1244 pub fn tag_picker_next(&mut self) {
1245 let all_tags_len = self.widgets.tag_picker.available_tags.len();
1246 if all_tags_len > 0 {
1247 let i = match self.state.app_list_states.tag_picker.selected() {
1248 Some(i) => {
1249 if i >= all_tags_len - 1 {
1250 0
1251 } else {
1252 i + 1
1253 }
1254 }
1255 None => 0,
1256 };
1257 self.state.app_list_states.tag_picker.select(Some(i));
1258 }
1259 }
1260 pub fn tag_picker_prv(&mut self) {
1261 let all_tags_len = self.widgets.tag_picker.available_tags.len();
1262 if all_tags_len > 0 {
1263 let i = match self.state.app_list_states.tag_picker.selected() {
1264 Some(i) => {
1265 if i == 0 {
1266 all_tags_len - 1
1267 } else {
1268 i - 1
1269 }
1270 }
1271 None => 0,
1272 };
1273 self.state.app_list_states.tag_picker.select(Some(i));
1274 }
1275 }
1276 pub fn set_popup(&mut self, popup: PopUp) {
1277 if self.state.z_stack.contains(&popup) {
1278 debug!(
1279 "Popup already set: {:?}, z_stack {:?}",
1280 popup, self.state.z_stack
1281 );
1282 return;
1283 }
1284 self.state.z_stack.push(popup);
1285 let available_focus_targets = popup.get_available_targets();
1286 if !available_focus_targets.contains(&self.state.focus) {
1287 if available_focus_targets.is_empty() {
1288 self.state.set_focus(Focus::NoFocus);
1289 } else if available_focus_targets.len() > 1
1290 && available_focus_targets[0] == Focus::Title
1291 {
1292 self.state.set_focus(available_focus_targets[1]);
1293 } else {
1294 self.state.set_focus(available_focus_targets[0]);
1295 }
1296 }
1297 match popup {
1298 PopUp::ViewCard => {
1299 if self.state.current_board_id.is_none() || self.state.current_card_id.is_none() {
1300 self.send_error_toast("No card selected", Some(Duration::from_secs(1)));
1301 return;
1302 }
1303 if let Some(current_board) = self
1304 .boards
1305 .get_board_with_id(self.state.current_board_id.unwrap())
1306 {
1307 if let Some(current_card) = current_board
1308 .cards
1309 .get_card_with_id(self.state.current_card_id.unwrap())
1310 {
1311 self.state.set_focus(Focus::CardName);
1312 self.state.text_buffers.card_name =
1313 TextBox::from_string_with_newline_sep(current_card.name.clone(), true);
1314 self.state.text_buffers.card_description =
1315 TextBox::from_string_with_newline_sep(
1316 current_card.description.clone(),
1317 false,
1318 );
1319 } else {
1320 self.send_error_toast("No card selected", Some(Duration::from_secs(1)));
1321 }
1322 } else {
1323 self.send_error_toast("No board selected", Some(Duration::from_secs(1)));
1324 }
1325 }
1326 PopUp::CommandPalette => {
1327 self.widgets.command_palette.reset(&mut self.state);
1328 self.state.app_status = AppStatus::UserInput;
1329 self.state.set_focus(Focus::CommandPaletteCommand);
1330 }
1331 PopUp::CardStatusSelector => {
1332 self.state.set_focus(Focus::ChangeCardStatusPopup);
1333 }
1334 PopUp::CardPrioritySelector => {
1335 self.state.set_focus(Focus::ChangeCardPriorityPopup);
1336 }
1337 PopUp::EditGeneralConfig => {
1338 self.state.set_focus(Focus::EditGeneralConfigPopup);
1339 }
1340 PopUp::CustomHexColorPromptBG | PopUp::CustomHexColorPromptFG => {
1341 self.state.set_focus(Focus::TextInput);
1342 self.state.app_status = AppStatus::UserInput;
1343 }
1344 PopUp::DateTimePicker => {
1345 self.widgets.date_time_picker.open_date_picker();
1346 }
1347 _ => {
1348 debug!("No special logic for setting popup: {:?}", popup);
1349 }
1350 }
1351 }
1352
1353 pub fn close_popup(&mut self) {
1354 if let Some(popup) = self.state.z_stack.pop() {
1355 match popup {
1356 PopUp::CustomHexColorPromptBG | PopUp::CustomHexColorPromptFG => {
1357 self.state.app_status = AppStatus::Initialized;
1358 }
1359 PopUp::ViewCard => {
1360 self.state.app_status = AppStatus::Initialized;
1361 if self.state.card_being_edited.is_some() {
1362 self.set_popup(PopUp::ConfirmDiscardCardChanges);
1363 }
1364 }
1365 PopUp::ConfirmDiscardCardChanges => {
1366 self.state.app_status = AppStatus::Initialized;
1367 if let Some(card) = &self.state.card_being_edited {
1368 warn!("Discarding changes to card '{}'", card.1.name);
1369 self.send_warning_toast(
1370 &format!("Discarding changes to card '{}'", card.1.name),
1371 None,
1372 );
1373 self.state.card_being_edited = None;
1374 }
1375 }
1376 PopUp::DateTimePicker => {
1377 self.widgets.date_time_picker.close_date_picker();
1378 }
1379 _ => {}
1380 }
1381 }
1382 }
1383
1384 pub fn set_view(&mut self, view: View) {
1385 if let Some(prv_view) = self.state.prev_view {
1386 if prv_view == view {
1387 self.state.prev_view = None;
1388 } else {
1389 self.state.prev_view = Some(self.state.current_view);
1390 }
1391 } else {
1392 self.state.prev_view = Some(self.state.current_view);
1393 }
1394 self.state.current_view = view;
1395 let available_focus_targets = self.state.current_view.get_available_targets();
1396 if !available_focus_targets.contains(&self.state.focus) {
1397 if available_focus_targets.is_empty() {
1398 self.state.set_focus(Focus::NoFocus);
1399 } else if available_focus_targets.len() > 1
1400 && available_focus_targets[0] == Focus::Title
1401 {
1402 self.state.set_focus(available_focus_targets[1]);
1403 } else {
1404 self.state.set_focus(available_focus_targets[0]);
1405 }
1406 }
1407 match view {
1408 View::Login => {
1409 self.state.text_buffers.email_id.reset();
1410 self.state.text_buffers.password.reset();
1411 }
1412 View::SignUp => {
1413 self.state.text_buffers.email_id.reset();
1414 self.state.text_buffers.password.reset();
1415 self.state.text_buffers.confirm_password.reset();
1416 }
1417 View::ResetPassword => {
1418 self.state.text_buffers.email_id.reset();
1419 self.state.text_buffers.password.reset();
1420 self.state.text_buffers.confirm_password.reset();
1421 self.state.text_buffers.reset_password_link.reset();
1422 }
1423 View::CreateTheme => {
1424 self.state.text_buffers.general_config.reset();
1425 self.state.app_table_states.theme_editor.select(Some(0));
1426 }
1427 View::ConfigMenu => self.state.app_table_states.config.select(Some(0)),
1428 _ => {
1429 debug!("No special logic for setting view: {:?}", view);
1430 }
1431 }
1432 }
1433
1434 pub fn get_first_next_focus_keybinding(&self) -> &Key {
1435 self.config
1436 .keybindings
1437 .next_focus
1438 .first()
1439 .unwrap_or(&Key::Tab)
1440 }
1441
1442 pub fn get_first_prv_focus_keybinding(&self) -> &Key {
1443 self.config
1444 .keybindings
1445 .prv_focus
1446 .first()
1447 .unwrap_or(&Key::BackTab)
1448 }
1449
1450 pub fn calculate_tags(&self) -> Vec<(String, u32)> {
1451 let mut tags: Vec<(String, String)> = vec![];
1452 for board in self.boards.get_boards() {
1453 for card in board.cards.get_all_cards() {
1454 for tag in &card.tags {
1455 if tag.is_empty() {
1456 continue;
1457 }
1458 tags.push((tag.clone(), tag.to_lowercase()));
1459 }
1460 }
1461 }
1462
1463 let count_hash: HashMap<String, (String, u32)> =
1464 tags.iter()
1465 .fold(HashMap::new(), |mut acc, (original, lower)| {
1466 let entry = acc.entry(lower.clone()).or_insert((original.clone(), 0));
1467 entry.1 += 1;
1468 acc
1469 });
1470
1471 let mut tags: Vec<(String, u32)> = count_hash
1472 .iter()
1473 .map(|(_, (original, count))| (original.clone(), *count))
1474 .collect();
1475
1476 tags.sort_by(|a, b| {
1477 if a.1 == b.1 {
1478 a.0.to_lowercase().cmp(&b.0.to_lowercase())
1479 } else {
1480 b.1.cmp(&a.1)
1481 }
1482 });
1483
1484 tags
1485 }
1486}
1487
1488#[derive(Serialize, Deserialize, Debug, Clone)]
1490pub enum MainMenuItem {
1491 View,
1492 Config,
1493 Help,
1494 LoadSaveLocal,
1495 LoadSaveCloud,
1496 Quit,
1497}
1498
1499impl Display for MainMenuItem {
1500 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
1501 match *self {
1502 MainMenuItem::View => write!(f, "View your Boards"),
1503 MainMenuItem::Config => write!(f, "Configure"),
1504 MainMenuItem::Help => write!(f, "Help"),
1505 MainMenuItem::LoadSaveLocal => write!(f, "Load a Save (local)"),
1506 MainMenuItem::LoadSaveCloud => write!(f, "Load a Save (cloud)"),
1507 MainMenuItem::Quit => write!(f, "Quit"),
1508 }
1509 }
1510}
1511
1512#[derive(Debug, Clone)]
1513pub struct MainMenu {
1514 pub items: Vec<MainMenuItem>,
1515 pub logged_in: bool,
1516}
1517
1518impl Default for MainMenu {
1519 fn default() -> Self {
1520 MainMenu {
1521 items: vec![
1522 MainMenuItem::View,
1523 MainMenuItem::Config,
1524 MainMenuItem::Help,
1525 MainMenuItem::LoadSaveLocal,
1526 MainMenuItem::Quit,
1527 ],
1528 logged_in: false,
1529 }
1530 }
1531}
1532
1533impl MainMenu {
1534 pub fn all(&mut self) -> Vec<MainMenuItem> {
1535 if self.logged_in {
1536 let return_vec = vec![
1537 MainMenuItem::View,
1538 MainMenuItem::Config,
1539 MainMenuItem::Help,
1540 MainMenuItem::LoadSaveLocal,
1541 MainMenuItem::LoadSaveCloud,
1542 MainMenuItem::Quit,
1543 ];
1544 self.items.clone_from(&return_vec);
1545 return_vec
1546 } else {
1547 let return_vec = vec![
1548 MainMenuItem::View,
1549 MainMenuItem::Config,
1550 MainMenuItem::Help,
1551 MainMenuItem::LoadSaveLocal,
1552 MainMenuItem::Quit,
1553 ];
1554 self.items.clone_from(&return_vec);
1555 return_vec
1556 }
1557 }
1558
1559 pub fn from_index(&self, index: usize) -> MainMenuItem {
1560 if self.logged_in {
1561 match index {
1562 0 => MainMenuItem::View,
1563 1 => MainMenuItem::Config,
1564 2 => MainMenuItem::Help,
1565 3 => MainMenuItem::LoadSaveLocal,
1566 4 => MainMenuItem::LoadSaveCloud,
1567 5 => MainMenuItem::Quit,
1568 _ => MainMenuItem::Quit,
1569 }
1570 } else {
1571 match index {
1572 0 => MainMenuItem::View,
1573 1 => MainMenuItem::Config,
1574 2 => MainMenuItem::Help,
1575 3 => MainMenuItem::LoadSaveLocal,
1576 4 => MainMenuItem::Quit,
1577 _ => MainMenuItem::Quit,
1578 }
1579 }
1580 }
1581}
1582
1583#[derive(Serialize, Deserialize, Debug, Clone, Copy, Default, PartialEq, EnumString)]
1584pub enum DateTimeFormat {
1585 DayMonthYear,
1586 #[default]
1587 DayMonthYearTime,
1588 MonthDayYear,
1589 MonthDayYearTime,
1590 YearMonthDay,
1591 YearMonthDayTime,
1592}
1593
1594impl DateTimeFormat {
1595 pub fn to_human_readable_string(&self) -> &str {
1596 match self {
1597 DateTimeFormat::DayMonthYear => "DD/MM/YYYY",
1598 DateTimeFormat::DayMonthYearTime => "DD/MM/YYYY-HH:MM:SS",
1599 DateTimeFormat::MonthDayYear => "MM/DD/YYYY",
1600 DateTimeFormat::MonthDayYearTime => "MM/DD/YYYY-HH:MM:SS",
1601 DateTimeFormat::YearMonthDay => "YYYY/MM/DD",
1602 DateTimeFormat::YearMonthDayTime => "YYYY/MM/DD-HH:MM:SS",
1603 }
1604 }
1605 pub fn to_parser_string(&self) -> &str {
1606 match self {
1607 DateTimeFormat::DayMonthYear => "%d/%m/%Y",
1608 DateTimeFormat::DayMonthYearTime => "%d/%m/%Y-%H:%M:%S",
1609 DateTimeFormat::MonthDayYear => "%m/%d/%Y",
1610 DateTimeFormat::MonthDayYearTime => "%m/%d/%Y-%H:%M:%S",
1611 DateTimeFormat::YearMonthDay => "%Y/%m/%d",
1612 DateTimeFormat::YearMonthDayTime => "%Y/%m/%d-%H:%M:%S",
1613 }
1614 }
1615 pub fn from_json_string(json_string: &str) -> Option<DateTimeFormat> {
1616 match DateTimeFormat::from_str(json_string) {
1617 Ok(date_time_format) => Some(date_time_format),
1618 Err(_) => None,
1619 }
1620 }
1621 pub fn from_human_readable_string(human_readable_string: &str) -> Option<DateTimeFormat> {
1622 match human_readable_string {
1623 "DD/MM/YYYY" => Some(DateTimeFormat::DayMonthYear),
1624 "DD/MM/YYYY-HH:MM:SS" => Some(DateTimeFormat::DayMonthYearTime),
1625 "MM/DD/YYYY" => Some(DateTimeFormat::MonthDayYear),
1626 "MM/DD/YYYY-HH:MM:SS" => Some(DateTimeFormat::MonthDayYearTime),
1627 "YYYY/MM/DD" => Some(DateTimeFormat::YearMonthDay),
1628 "YYYY/MM/DD-HH:MM:SS" => Some(DateTimeFormat::YearMonthDayTime),
1629 _ => None,
1630 }
1631 }
1632 pub fn get_all_date_formats() -> Vec<DateTimeFormat> {
1633 vec![
1634 DateTimeFormat::DayMonthYear,
1635 DateTimeFormat::DayMonthYearTime,
1636 DateTimeFormat::MonthDayYear,
1637 DateTimeFormat::MonthDayYearTime,
1638 DateTimeFormat::YearMonthDay,
1639 DateTimeFormat::YearMonthDayTime,
1640 ]
1641 }
1642 pub fn all_formats_with_time() -> Vec<DateTimeFormat> {
1643 vec![
1644 DateTimeFormat::DayMonthYearTime,
1645 DateTimeFormat::MonthDayYearTime,
1646 DateTimeFormat::YearMonthDayTime,
1647 ]
1648 }
1649 pub fn all_formats_without_time() -> Vec<DateTimeFormat> {
1650 vec![
1651 DateTimeFormat::DayMonthYear,
1652 DateTimeFormat::MonthDayYear,
1653 DateTimeFormat::YearMonthDay,
1654 ]
1655 }
1656 pub fn add_time_to_date_format(date_format: DateTimeFormat) -> DateTimeFormat {
1657 match date_format {
1658 DateTimeFormat::DayMonthYear => DateTimeFormat::DayMonthYearTime,
1659 DateTimeFormat::MonthDayYear => DateTimeFormat::MonthDayYearTime,
1660 DateTimeFormat::YearMonthDay => DateTimeFormat::YearMonthDayTime,
1661 _ => date_format,
1662 }
1663 }
1664 pub fn remove_time_from_date_format(date_format: DateTimeFormat) -> DateTimeFormat {
1665 match date_format {
1666 DateTimeFormat::DayMonthYearTime => DateTimeFormat::DayMonthYear,
1667 DateTimeFormat::MonthDayYearTime => DateTimeFormat::MonthDayYear,
1668 DateTimeFormat::YearMonthDayTime => DateTimeFormat::YearMonthDay,
1669 _ => date_format,
1670 }
1671 }
1672}
1673
1674impl Display for DateTimeFormat {
1675 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
1676 write!(f, "{}", self.to_human_readable_string())
1677 }
1678}
1679
1680#[derive(Serialize, Deserialize, Debug, Clone)]
1681pub struct AppConfig {
1682 pub always_load_last_save: bool,
1683 pub auto_login: bool,
1684 pub date_time_format: DateTimeFormat,
1685 pub default_theme: String,
1686 pub default_view: View,
1687 pub disable_animations: bool,
1688 pub disable_scroll_bar: bool,
1689 pub enable_mouse_support: bool,
1690 pub keybindings: KeyBindings,
1691 pub no_of_boards_to_show: u16,
1692 pub no_of_cards_to_show: u16,
1693 pub date_picker_calender_format: CalenderType,
1694 pub save_directory: PathBuf,
1695 pub save_on_exit: bool,
1696 pub show_line_numbers: bool,
1697 pub tickrate: u16,
1698 pub warning_delta: u16,
1699}
1700
1701impl Default for AppConfig {
1702 fn default() -> Self {
1703 let default_view = DEFAULT_VIEW;
1704 let default_theme = Theme::default();
1705 Self {
1706 always_load_last_save: true,
1707 auto_login: true,
1708 date_time_format: DateTimeFormat::default(),
1709 default_theme: default_theme.name,
1710 default_view,
1711 disable_animations: false,
1712 disable_scroll_bar: false,
1713 enable_mouse_support: true,
1714 keybindings: KeyBindings::default(),
1715 no_of_boards_to_show: DEFAULT_NO_OF_BOARDS_PER_PAGE,
1716 no_of_cards_to_show: DEFAULT_NO_OF_CARDS_PER_BOARD,
1717 date_picker_calender_format: CalenderType::default(),
1718 save_directory: get_default_save_directory(),
1719 save_on_exit: true,
1720 show_line_numbers: true,
1721 tickrate: DEFAULT_TICKRATE,
1722 warning_delta: DEFAULT_CARD_WARNING_DUE_DATE_DAYS,
1723 }
1724 }
1725}
1726
1727impl AppConfig {
1728 pub fn to_view_list(&self) -> Vec<Vec<String>> {
1729 let mut view_list = ConfigEnum::iter()
1731 .map(|enum_variant| {
1732 let (value, index) = match enum_variant {
1733 ConfigEnum::SaveDirectory => {
1734 (self.save_directory.to_string_lossy().to_string(), 0)
1735 }
1736 ConfigEnum::DefaultView => (self.default_view.to_string(), 1),
1737 ConfigEnum::AlwaysLoadLastSave => (self.always_load_last_save.to_string(), 2),
1738 ConfigEnum::SaveOnExit => (self.save_on_exit.to_string(), 3),
1739 ConfigEnum::DisableScrollBar => (self.disable_scroll_bar.to_string(), 4),
1740 ConfigEnum::DisableAnimations => (self.disable_animations.to_string(), 5),
1741 ConfigEnum::AutoLogin => (self.auto_login.to_string(), 6),
1742 ConfigEnum::ShowLineNumbers => (self.show_line_numbers.to_string(), 7),
1743 ConfigEnum::EnableMouseSupport => (self.enable_mouse_support.to_string(), 8),
1744 ConfigEnum::WarningDelta => (self.warning_delta.to_string(), 9),
1745 ConfigEnum::Tickrate => (self.tickrate.to_string(), 10),
1746 ConfigEnum::NoOfCardsToShow => (self.no_of_cards_to_show.to_string(), 11),
1747 ConfigEnum::NoOfBoardsToShow => (self.no_of_boards_to_show.to_string(), 12),
1748 ConfigEnum::DatePickerCalenderFormat => {
1749 (self.date_picker_calender_format.to_string(), 13)
1750 }
1751 ConfigEnum::DefaultTheme => (self.default_theme.clone(), 14),
1752 ConfigEnum::DateFormat => (self.date_time_format.to_string(), 15),
1753 ConfigEnum::Keybindings => ("".to_string(), 16),
1754 };
1755 (enum_variant.to_string(), value.to_string(), index)
1756 })
1757 .collect::<Vec<(String, String, usize)>>();
1758
1759 view_list.sort_by(|a, b| a.2.cmp(&b.2));
1760 view_list
1761 .iter()
1762 .map(|(key, value, _)| vec![key.to_owned(), value.to_owned()])
1763 .collect::<Vec<Vec<String>>>()
1764 }
1765
1766 pub fn get_value_as_string(&self, config_enum: ConfigEnum) -> String {
1767 match config_enum {
1768 ConfigEnum::AlwaysLoadLastSave => self.always_load_last_save.to_string(),
1769 ConfigEnum::AutoLogin => self.auto_login.to_string(),
1770 ConfigEnum::DateFormat => self.date_time_format.to_string(),
1771 ConfigEnum::DefaultTheme => self.default_theme.clone(),
1772 ConfigEnum::DefaultView => self.default_view.to_string(),
1773 ConfigEnum::DisableAnimations => self.disable_animations.to_string(),
1774 ConfigEnum::DisableScrollBar => self.disable_scroll_bar.to_string(),
1775 ConfigEnum::EnableMouseSupport => self.enable_mouse_support.to_string(),
1776 ConfigEnum::Keybindings => {
1777 debug!("Keybindings should not be called from get_value_as_str");
1779 "".to_string()
1780 }
1781 ConfigEnum::NoOfBoardsToShow => self.no_of_boards_to_show.to_string(),
1782 ConfigEnum::NoOfCardsToShow => self.no_of_cards_to_show.to_string(),
1783 ConfigEnum::DatePickerCalenderFormat => self.date_picker_calender_format.to_string(),
1784 ConfigEnum::SaveDirectory => self.save_directory.to_string_lossy().to_string(),
1785 ConfigEnum::SaveOnExit => self.save_on_exit.to_string(),
1786 ConfigEnum::ShowLineNumbers => self.show_line_numbers.to_string(),
1787 ConfigEnum::Tickrate => self.tickrate.to_string(),
1788 ConfigEnum::WarningDelta => self.warning_delta.to_string(),
1789 }
1790 }
1791
1792 pub fn get_toggled_value_as_string(&self, config_enum: ConfigEnum) -> String {
1793 match config_enum {
1794 ConfigEnum::AlwaysLoadLastSave => (!self.always_load_last_save).to_string(),
1795 ConfigEnum::AutoLogin => (!self.auto_login).to_string(),
1796 ConfigEnum::DisableAnimations => (!self.disable_animations).to_string(),
1797 ConfigEnum::DisableScrollBar => (!self.disable_scroll_bar).to_string(),
1798 ConfigEnum::EnableMouseSupport => (!self.enable_mouse_support).to_string(),
1799 ConfigEnum::SaveOnExit => (!self.save_on_exit).to_string(),
1800 ConfigEnum::ShowLineNumbers => (!self.show_line_numbers).to_string(),
1801 ConfigEnum::DatePickerCalenderFormat => match self.date_picker_calender_format {
1802 CalenderType::MondayFirst => CalenderType::SundayFirst.to_string(),
1803 CalenderType::SundayFirst => CalenderType::MondayFirst.to_string(),
1804 },
1805 _ => {
1806 debug!("Invalid config enum to toggle: {}", config_enum);
1807 "".to_string()
1808 }
1809 }
1810 }
1811
1812 pub fn edit_config(app: &mut App, config_enum: ConfigEnum, edited_value: &str) {
1813 let mut config_copy = app.config.clone();
1814 let result = config_enum.edit_config(&mut config_copy, edited_value);
1815 if result.is_ok() {
1816 let write_status = data_handler::write_config(&config_copy);
1817 if write_status.is_ok() {
1818 app.config = config_copy;
1819 app.send_info_toast("Config updated", None);
1820 } else {
1821 app.send_error_toast("Could not write to config file", None);
1822 }
1823 } else {
1824 let error_message = format!("Could not edit config: {}", result.unwrap_err());
1825 error!("{}", error_message);
1826 app.send_error_toast(&error_message, None);
1827 }
1828 }
1829
1830 pub fn edit_keybinding(
1831 &mut self,
1832 key_index: usize,
1833 value: &[Key],
1834 ) -> Result<KeyBindingEnum, String> {
1835 let current_bindings = &self.keybindings;
1836
1837 let mut key_list = vec![];
1838 for (k, v) in current_bindings.iter() {
1839 key_list.push((k, v));
1840 }
1841 if key_index >= key_list.len() {
1842 debug!("Invalid key index: {}", key_index);
1843 error!("Unable to edit keybinding");
1844 return Err("Unable to edit keybinding 😢 ".to_string());
1845 }
1846
1847 key_list.sort_by(|a, b| a.0.to_string().cmp(&b.0.to_string()));
1848
1849 let (key, _) = key_list[key_index];
1850
1851 if !current_bindings.iter().any(|(k, _)| k == key) {
1852 debug!("Invalid key: {}", key);
1853 error!("Unable to edit keybinding");
1854 return Err("Unable to edit keybinding 😢 ".to_string());
1855 }
1856
1857 for new_value in value.iter() {
1858 for (k, v) in current_bindings.iter() {
1859 if v.contains(new_value) && k != key {
1860 error!("Value {} is already assigned to {}", new_value, k);
1861 return Err(format!("Value {} is already assigned to {}", new_value, k));
1862 }
1863 }
1864 }
1865
1866 debug!("Editing keybinding: {} to {:?}", key, value);
1867
1868 match key {
1869 KeyBindingEnum::Accept => self.keybindings.accept = value.to_vec(),
1870 KeyBindingEnum::ChangeCardStatusToActive => {
1871 self.keybindings.change_card_status_to_active = value.to_vec();
1872 }
1873 KeyBindingEnum::ChangeCardStatusToCompleted => {
1874 self.keybindings.change_card_status_to_completed = value.to_vec();
1875 }
1876 KeyBindingEnum::ChangeCardStatusToStale => {
1877 self.keybindings.change_card_status_to_stale = value.to_vec();
1878 }
1879 KeyBindingEnum::ChangeCardPriorityToHigh => {
1880 self.keybindings.change_card_priority_to_high = value.to_vec();
1881 }
1882 KeyBindingEnum::ChangeCardPriorityToLow => {
1883 self.keybindings.change_card_priority_to_low = value.to_vec();
1884 }
1885 KeyBindingEnum::ChangeCardPriorityToMedium => {
1886 self.keybindings.change_card_priority_to_medium = value.to_vec();
1887 }
1888 KeyBindingEnum::ClearAllToasts => {
1889 self.keybindings.clear_all_toasts = value.to_vec();
1890 }
1891 KeyBindingEnum::DeleteBoard => {
1892 self.keybindings.delete_board = value.to_vec();
1893 }
1894 KeyBindingEnum::DeleteCard => {
1895 self.keybindings.delete_card = value.to_vec();
1896 }
1897 KeyBindingEnum::Down => {
1898 self.keybindings.down = value.to_vec();
1899 }
1900 KeyBindingEnum::GoToMainMenu => {
1901 self.keybindings.go_to_main_menu = value.to_vec();
1902 }
1903 KeyBindingEnum::GoToPreviousViewOrCancel => {
1904 self.keybindings.go_to_previous_view_or_cancel = value.to_vec();
1905 }
1906 KeyBindingEnum::HideUiElement => {
1907 self.keybindings.hide_ui_element = value.to_vec();
1908 }
1909 KeyBindingEnum::Left => {
1910 self.keybindings.left = value.to_vec();
1911 }
1912 KeyBindingEnum::MoveCardDown => {
1913 self.keybindings.move_card_down = value.to_vec();
1914 }
1915 KeyBindingEnum::MoveCardLeft => {
1916 self.keybindings.move_card_left = value.to_vec();
1917 }
1918 KeyBindingEnum::MoveCardRight => {
1919 self.keybindings.move_card_right = value.to_vec();
1920 }
1921 KeyBindingEnum::MoveCardUp => {
1922 self.keybindings.move_card_up = value.to_vec();
1923 }
1924 KeyBindingEnum::NewBoard => {
1925 self.keybindings.new_board = value.to_vec();
1926 }
1927 KeyBindingEnum::NewCard => {
1928 self.keybindings.new_card = value.to_vec();
1929 }
1930 KeyBindingEnum::NextFocus => {
1931 self.keybindings.next_focus = value.to_vec();
1932 }
1933 KeyBindingEnum::OpenConfigMenu => {
1934 self.keybindings.open_config_menu = value.to_vec();
1935 }
1936 KeyBindingEnum::PrvFocus => {
1937 self.keybindings.prv_focus = value.to_vec();
1938 }
1939 KeyBindingEnum::Quit => {
1940 self.keybindings.quit = value.to_vec();
1941 }
1942 KeyBindingEnum::Redo => {
1943 self.keybindings.redo = value.to_vec();
1944 }
1945 KeyBindingEnum::ResetUI => {
1946 self.keybindings.reset_ui = value.to_vec();
1947 }
1948 KeyBindingEnum::Right => {
1949 self.keybindings.right = value.to_vec();
1950 }
1951 KeyBindingEnum::SaveState => {
1952 self.keybindings.save_state = value.to_vec();
1953 }
1954 KeyBindingEnum::StopUserInput => {
1955 self.keybindings.stop_user_input = value.to_vec();
1956 }
1957 KeyBindingEnum::TakeUserInput => {
1958 self.keybindings.take_user_input = value.to_vec();
1959 }
1960 KeyBindingEnum::ToggleCommandPalette => {
1961 self.keybindings.toggle_command_palette = value.to_vec();
1962 }
1963 KeyBindingEnum::Undo => {
1964 self.keybindings.undo = value.to_vec();
1965 }
1966 KeyBindingEnum::Up => {
1967 self.keybindings.up = value.to_vec();
1968 }
1969 }
1970 Ok(key)
1971 }
1972
1973 fn get_bool_or_default(
1974 serde_json_object: &serde_json::Value,
1975 config_enum: ConfigEnum,
1976 default: bool,
1977 ) -> bool {
1978 match serde_json_object[config_enum.to_json_key()].as_bool() {
1979 Some(value) => value,
1980 None => {
1981 error!(
1982 "{} is not a boolean (true/false), Resetting to default value",
1983 config_enum.to_json_key()
1984 );
1985 default
1986 }
1987 }
1988 }
1989
1990 fn get_u16_or_default(
1991 serde_json_object: &serde_json::Value,
1992 config_enum: ConfigEnum,
1993 default: u16,
1994 min: Option<u16>,
1995 max: Option<u16>,
1996 ) -> u16 {
1997 match serde_json_object[config_enum.to_json_key()].as_u64() {
1998 Some(value) => {
1999 if let Some(min) = min {
2000 if value < min as u64 {
2001 error!(
2002 "Invalid value: {} for {}, It must be greater than {}, Resetting to default value",
2003 value, config_enum.to_json_key(), min
2004 );
2005 return default;
2006 }
2007 }
2008 if let Some(max) = max {
2009 if value > max as u64 {
2010 error!(
2011 "Invalid value: {} for {}, It must be less than {}, Resetting to default value",
2012 value, config_enum.to_json_key(), max
2013 );
2014 return default;
2015 }
2016 }
2017 value as u16
2018 }
2019 None => {
2020 error!(
2021 "{} is not a number, Resetting to default value",
2022 config_enum.to_json_key()
2023 );
2024 default
2025 }
2026 }
2027 }
2028
2029 fn handle_invalid_keybinding(key: &str) {
2030 error!(
2031 "Invalid keybinding for key {}, Resetting to default keybinding",
2032 key
2033 );
2034 }
2035
2036 fn json_config_keybindings_checker(serde_json_object: &Value) -> KeyBindings {
2037 if let Some(keybindings) = serde_json_object["keybindings"].as_object() {
2038 let mut default_keybindings = KeyBindings::default();
2039 for (key, value) in keybindings.iter() {
2040 let mut keybindings = vec![];
2041 if let Some(value_array) = value.as_array() {
2042 for keybinding_value in value_array {
2043 if let Some(keybinding_value_str) = keybinding_value.as_str() {
2044 let keybinding_value = Key::from(keybinding_value_str);
2045 if keybinding_value != Key::Unknown {
2046 keybindings.push(keybinding_value);
2047 } else {
2048 Self::handle_invalid_keybinding(key);
2049 }
2050 } else if let Some(keybinding_value_obj) = keybinding_value.as_object() {
2051 let keybinding_value = Key::from(keybinding_value_obj);
2052 if keybinding_value != Key::Unknown {
2053 keybindings.push(keybinding_value);
2054 } else {
2055 Self::handle_invalid_keybinding(key);
2056 }
2057 } else {
2058 Self::handle_invalid_keybinding(key);
2059 }
2060 }
2061 if keybindings.is_empty() {
2062 Self::handle_invalid_keybinding(key);
2063 } else {
2064 default_keybindings.edit_keybinding(key, keybindings);
2065 }
2066 } else {
2067 Self::handle_invalid_keybinding(key);
2068 }
2069 }
2070 default_keybindings
2071 } else {
2072 KeyBindings::default()
2073 }
2074 }
2075
2076 pub fn from_json_string(json_string: &str) -> Result<Self, String> {
2077 let root = serde_json::from_str(json_string);
2078 if root.is_err() {
2079 error!("Unable to recover old config. Resetting to default config");
2080 debug!("Error: {}", root.unwrap_err());
2081 return Err("Unable to recover old config. Resetting to default config".to_string());
2082 }
2083 let serde_json_object: Value = root.unwrap();
2084 let default_config = AppConfig::default();
2085 let save_directory =
2086 match serde_json_object[ConfigEnum::SaveDirectory.to_json_key()].as_str() {
2087 Some(path) => {
2088 let path = PathBuf::from(path);
2089 if path.exists() {
2090 path
2091 } else {
2092 error!(
2093 "Invalid path: {}, Resetting to default save directory",
2094 path.to_str().unwrap()
2095 );
2096 default_config.save_directory
2097 }
2098 }
2099 None => {
2100 error!("Save Directory is not a string, Resetting to default save directory");
2101 default_config.save_directory
2102 }
2103 };
2104 let default_view = match serde_json_object[ConfigEnum::DefaultView.to_json_key()].as_str() {
2105 Some(view) => {
2106 let view = View::from_str(view);
2107 if let Ok(view) = view {
2108 view
2109 } else {
2110 error!("Invalid View: {:?}, Resetting to default View", view);
2111 default_config.default_view
2112 }
2113 }
2114 None => {
2115 error!("Default View is not a string, Resetting to default View");
2116 default_config.default_view
2117 }
2118 };
2119 let keybindings = AppConfig::json_config_keybindings_checker(&serde_json_object);
2120 let always_load_last_save = AppConfig::get_bool_or_default(
2121 &serde_json_object,
2122 ConfigEnum::AlwaysLoadLastSave,
2123 default_config.always_load_last_save,
2124 );
2125 let save_on_exit = AppConfig::get_bool_or_default(
2126 &serde_json_object,
2127 ConfigEnum::SaveOnExit,
2128 default_config.save_on_exit,
2129 );
2130 let disable_scroll_bar = AppConfig::get_bool_or_default(
2131 &serde_json_object,
2132 ConfigEnum::DisableScrollBar,
2133 default_config.disable_scroll_bar,
2134 );
2135 let auto_login = AppConfig::get_bool_or_default(
2136 &serde_json_object,
2137 ConfigEnum::AutoLogin,
2138 default_config.auto_login,
2139 );
2140 let show_line_numbers = AppConfig::get_bool_or_default(
2141 &serde_json_object,
2142 ConfigEnum::ShowLineNumbers,
2143 default_config.show_line_numbers,
2144 );
2145 let disable_animations = AppConfig::get_bool_or_default(
2146 &serde_json_object,
2147 ConfigEnum::DisableAnimations,
2148 default_config.disable_animations,
2149 );
2150 let enable_mouse_support = AppConfig::get_bool_or_default(
2151 &serde_json_object,
2152 ConfigEnum::EnableMouseSupport,
2153 default_config.enable_mouse_support,
2154 );
2155 let warning_delta = AppConfig::get_u16_or_default(
2156 &serde_json_object,
2157 ConfigEnum::WarningDelta,
2158 default_config.warning_delta,
2159 Some(1),
2160 None,
2161 );
2162 let tickrate = AppConfig::get_u16_or_default(
2163 &serde_json_object,
2164 ConfigEnum::Tickrate,
2165 default_config.tickrate,
2166 Some(MIN_TICKRATE),
2167 Some(MAX_TICKRATE),
2168 );
2169 let no_of_cards_to_show = AppConfig::get_u16_or_default(
2170 &serde_json_object,
2171 ConfigEnum::NoOfCardsToShow,
2172 default_config.no_of_cards_to_show,
2173 Some(MIN_NO_CARDS_PER_BOARD),
2174 Some(MAX_NO_CARDS_PER_BOARD),
2175 );
2176 let no_of_boards_to_show = AppConfig::get_u16_or_default(
2177 &serde_json_object,
2178 ConfigEnum::NoOfBoardsToShow,
2179 default_config.no_of_boards_to_show,
2180 Some(MIN_NO_BOARDS_PER_PAGE),
2181 Some(MAX_NO_BOARDS_PER_PAGE),
2182 );
2183 let default_theme = match serde_json_object[ConfigEnum::DefaultTheme.to_json_key()].as_str()
2184 {
2185 Some(default_theme) => default_theme.to_string(),
2186 None => {
2187 error!("Default Theme is not a string, Resetting to default theme");
2188 default_config.default_theme
2189 }
2190 };
2191 let date_format = match serde_json_object[ConfigEnum::DateFormat.to_json_key()].as_str() {
2192 Some(date_format) => match DateTimeFormat::from_str(date_format) {
2193 Ok(date_format) => date_format,
2194 Err(date_format_parse_error) => {
2195 error!(
2196 "Invalid date format: {}, Resetting to default date format",
2197 date_format
2198 );
2199 debug!("Error: {}", date_format_parse_error);
2200 default_config.date_time_format
2201 }
2202 },
2203 None => {
2204 error!("Date Format is not a string, Resetting to default date format");
2205 default_config.date_time_format
2206 }
2207 };
2208 let date_picker_calender_format =
2209 match serde_json_object[ConfigEnum::DatePickerCalenderFormat.to_json_key()].as_str() {
2210 Some(calender_format) => match CalenderType::from_str(calender_format) {
2211 Ok(calender_format) => calender_format,
2212 Err(calender_format_parse_error) => {
2213 error!(
2214 "Invalid calender format: {}, Resetting to default calender format",
2215 calender_format
2216 );
2217 debug!("Error: {}", calender_format_parse_error);
2218 CalenderType::default()
2219 }
2220 },
2221 None => {
2222 error!("Calender Format is not a string, Resetting to default calender format");
2223 CalenderType::default()
2224 }
2225 };
2226 Ok(Self {
2227 save_directory,
2228 default_view,
2229 always_load_last_save,
2230 save_on_exit,
2231 disable_scroll_bar,
2232 auto_login,
2233 warning_delta,
2234 keybindings,
2235 tickrate,
2236 no_of_cards_to_show,
2237 no_of_boards_to_show,
2238 date_picker_calender_format,
2239 enable_mouse_support,
2240 default_theme,
2241 date_time_format: date_format,
2242 show_line_numbers,
2243 disable_animations,
2244 })
2245 }
2246}
2247
2248#[derive(PartialEq, Copy, Clone, EnumIter)]
2249pub enum ConfigEnum {
2250 AlwaysLoadLastSave,
2251 AutoLogin,
2252 DateFormat,
2253 DefaultTheme,
2254 DefaultView,
2255 DisableAnimations,
2256 DisableScrollBar,
2257 EnableMouseSupport,
2258 Keybindings,
2259 NoOfBoardsToShow,
2260 NoOfCardsToShow,
2261 DatePickerCalenderFormat,
2262 SaveDirectory,
2263 SaveOnExit,
2264 ShowLineNumbers,
2265 Tickrate,
2266 WarningDelta,
2267}
2268
2269impl fmt::Display for ConfigEnum {
2270 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
2271 match *self {
2272 ConfigEnum::AlwaysLoadLastSave => write!(f, "Auto Load Last Save"),
2273 ConfigEnum::AutoLogin => write!(f, "Auto Login"),
2274 ConfigEnum::DateFormat => write!(f, "Date Format"),
2275 ConfigEnum::DefaultTheme => write!(f, "Default Theme"),
2276 ConfigEnum::DefaultView => write!(f, "Select Default View"),
2277 ConfigEnum::DisableAnimations => write!(f, "Disable Animations"),
2278 ConfigEnum::DisableScrollBar => write!(f, "Disable Scroll Bar"),
2279 ConfigEnum::EnableMouseSupport => write!(f, "Enable Mouse Support"),
2280 ConfigEnum::Keybindings => write!(f, "Edit Keybindings"),
2281 ConfigEnum::NoOfBoardsToShow => write!(f, "Number of Boards to Show"),
2282 ConfigEnum::NoOfCardsToShow => write!(f, "Number of Cards to Show"),
2283 ConfigEnum::DatePickerCalenderFormat => write!(f, "Date Picker Calender Format"),
2284 ConfigEnum::SaveDirectory => write!(f, "Save Directory"),
2285 ConfigEnum::SaveOnExit => write!(f, "Auto Save on Exit"),
2286 ConfigEnum::ShowLineNumbers => write!(f, "Show Line Numbers"),
2287 ConfigEnum::Tickrate => write!(f, "Tickrate"),
2288 ConfigEnum::WarningDelta => write!(f, "Number of Days to Warn Before Due Date"),
2289 }
2290 }
2291}
2292
2293impl FromStr for ConfigEnum {
2294 type Err = String;
2295 fn from_str(s: &str) -> Result<Self, Self::Err> {
2296 match s {
2297 "Auto Load Last Save" => Ok(ConfigEnum::AlwaysLoadLastSave),
2298 "Auto Login" => Ok(ConfigEnum::AutoLogin),
2299 "Auto Save on Exit" => Ok(ConfigEnum::SaveOnExit),
2300 "Date Format" => Ok(ConfigEnum::DateFormat),
2301 "Default Theme" => Ok(ConfigEnum::DefaultTheme),
2302 "Disable Animations" => Ok(ConfigEnum::DisableAnimations),
2303 "Disable Scroll Bar" => Ok(ConfigEnum::DisableScrollBar),
2304 "Edit Keybindings" => Ok(ConfigEnum::Keybindings),
2305 "Enable Mouse Support" => Ok(ConfigEnum::EnableMouseSupport),
2306 "Number of Boards to Show" => Ok(ConfigEnum::NoOfBoardsToShow),
2307 "Number of Cards to Show" => Ok(ConfigEnum::NoOfCardsToShow),
2308 "Date Picker Calender Format" => Ok(ConfigEnum::DatePickerCalenderFormat),
2309 "Number of Days to Warn Before Due Date" => Ok(ConfigEnum::WarningDelta),
2310 "Save Directory" => Ok(ConfigEnum::SaveDirectory),
2311 "Select Default View" => Ok(ConfigEnum::DefaultView),
2312 "Show Line Numbers" => Ok(ConfigEnum::ShowLineNumbers),
2313 "Tickrate" => Ok(ConfigEnum::Tickrate),
2314 _ => Err(format!("Invalid ConfigEnum: {}", s)),
2315 }
2316 }
2317}
2318
2319impl ConfigEnum {
2320 pub fn to_json_key(&self) -> &str {
2321 match self {
2322 ConfigEnum::AlwaysLoadLastSave => "always_load_last_save",
2323 ConfigEnum::AutoLogin => "auto_login",
2324 ConfigEnum::DateFormat => "date_format",
2325 ConfigEnum::DefaultTheme => "default_theme",
2326 ConfigEnum::DefaultView => "default_view",
2327 ConfigEnum::DisableAnimations => "disable_animations",
2328 ConfigEnum::DisableScrollBar => "disable_scroll_bar",
2329 ConfigEnum::EnableMouseSupport => "enable_mouse_support",
2330 ConfigEnum::Keybindings => "keybindings",
2331 ConfigEnum::NoOfBoardsToShow => "no_of_boards_to_show",
2332 ConfigEnum::NoOfCardsToShow => "no_of_cards_to_show",
2333 ConfigEnum::DatePickerCalenderFormat => "date_picker_calender_format",
2334 ConfigEnum::SaveDirectory => "save_directory",
2335 ConfigEnum::SaveOnExit => "save_on_exit",
2336 ConfigEnum::ShowLineNumbers => "show_line_numbers",
2337 ConfigEnum::Tickrate => "tickrate",
2338 ConfigEnum::WarningDelta => "warning_delta",
2339 }
2340 }
2341
2342 pub fn validate_value(&self, value: &str) -> Result<(), String> {
2343 match self {
2344 ConfigEnum::SaveDirectory => {
2345 let path = PathBuf::from(value);
2346 if path.try_exists().is_ok() && path.try_exists().unwrap() && path.is_dir() {
2347 Ok(())
2348 } else {
2349 Err(format!("Invalid path: {}", value))
2350 }
2351 }
2352 ConfigEnum::DefaultView => {
2353 let view = View::from_string(value);
2354 if view.is_some() {
2355 Ok(())
2356 } else {
2357 Err(format!("Invalid View: {}", value))
2358 }
2359 }
2360 ConfigEnum::AlwaysLoadLastSave
2361 | ConfigEnum::AutoLogin
2362 | ConfigEnum::DisableAnimations
2363 | ConfigEnum::DisableScrollBar
2364 | ConfigEnum::EnableMouseSupport
2365 | ConfigEnum::SaveOnExit
2366 | ConfigEnum::ShowLineNumbers => {
2367 let check = value.parse::<bool>();
2368 if check.is_ok() {
2369 Ok(())
2370 } else {
2371 Err(format!("Invalid boolean: {}", value))
2372 }
2373 }
2374 ConfigEnum::NoOfBoardsToShow
2375 | ConfigEnum::NoOfCardsToShow
2376 | ConfigEnum::Tickrate
2377 | ConfigEnum::WarningDelta => {
2378 let min_value = match self {
2379 ConfigEnum::WarningDelta => MIN_WARNING_DUE_DATE_DAYS,
2380 ConfigEnum::Tickrate => MIN_TICKRATE,
2381 ConfigEnum::NoOfCardsToShow => MIN_NO_CARDS_PER_BOARD,
2382 ConfigEnum::NoOfBoardsToShow => MIN_NO_BOARDS_PER_PAGE,
2383 _ => 0,
2384 };
2385 let max_value = match self {
2386 ConfigEnum::WarningDelta => MAX_WARNING_DUE_DATE_DAYS,
2387 ConfigEnum::Tickrate => MAX_TICKRATE,
2388 ConfigEnum::NoOfCardsToShow => MAX_NO_CARDS_PER_BOARD,
2389 ConfigEnum::NoOfBoardsToShow => MAX_NO_BOARDS_PER_PAGE,
2390 _ => 0,
2391 };
2392 let check = value.parse::<u16>();
2393 if check.is_ok() {
2394 let value = check.unwrap();
2395 if value >= min_value && value <= max_value {
2396 Ok(())
2397 } else {
2398 Err(format!(
2399 "Invalid number: {}, It must be between {} and {}",
2400 value, min_value, max_value
2401 ))
2402 }
2403 } else {
2404 Err(format!("Invalid number: {}", value))
2405 }
2406 }
2407 ConfigEnum::DefaultTheme => {
2408 Ok(())
2410 }
2411 ConfigEnum::DateFormat => {
2412 let date_format = DateTimeFormat::from_human_readable_string(value);
2413 if date_format.is_some() {
2414 Ok(())
2415 } else {
2416 Err(format!("Invalid DateFormat: {}", value))
2417 }
2418 }
2419 ConfigEnum::DatePickerCalenderFormat => {
2420 let calender_format = CalenderType::try_from(value);
2421 if calender_format.is_ok() {
2422 Ok(())
2423 } else {
2424 Err(format!("Invalid CalenderFormat: {}", value))
2425 }
2426 }
2427 ConfigEnum::Keybindings => {
2428 debug!("Keybindings should not be called from validate_value");
2429 Ok(())
2431 }
2432 }
2433 }
2434
2435 pub fn edit_config(&self, config: &mut AppConfig, value: &str) -> Result<(), String> {
2436 let value = value.trim();
2437 self.validate_value(value)?;
2438 match self {
2440 ConfigEnum::SaveDirectory => {
2441 config.save_directory = PathBuf::from(value);
2442 }
2443 ConfigEnum::DefaultView => {
2444 config.default_view = View::from_string(value).unwrap();
2445 }
2446 ConfigEnum::AlwaysLoadLastSave => {
2447 config.always_load_last_save = value.parse::<bool>().unwrap();
2448 }
2449 ConfigEnum::SaveOnExit => {
2450 config.save_on_exit = value.parse::<bool>().unwrap();
2451 }
2452 ConfigEnum::DisableScrollBar => {
2453 config.disable_scroll_bar = value.parse::<bool>().unwrap();
2454 }
2455 ConfigEnum::AutoLogin => {
2456 config.auto_login = value.parse::<bool>().unwrap();
2457 }
2458 ConfigEnum::ShowLineNumbers => {
2459 config.show_line_numbers = value.parse::<bool>().unwrap();
2460 }
2461 ConfigEnum::DisableAnimations => {
2462 config.disable_animations = value.parse::<bool>().unwrap();
2463 }
2464 ConfigEnum::EnableMouseSupport => {
2465 config.enable_mouse_support = value.parse::<bool>().unwrap();
2466 }
2467 ConfigEnum::WarningDelta => {
2468 config.warning_delta = value.parse::<u16>().unwrap();
2469 }
2470 ConfigEnum::Tickrate => {
2471 config.tickrate = value.parse::<u16>().unwrap();
2472 }
2473 ConfigEnum::NoOfCardsToShow => {
2474 config.no_of_cards_to_show = value.parse::<u16>().unwrap();
2475 }
2476 ConfigEnum::NoOfBoardsToShow => {
2477 config.no_of_boards_to_show = value.parse::<u16>().unwrap();
2478 }
2479 ConfigEnum::DefaultTheme => {
2480 config.default_theme = value.to_string();
2481 }
2482 ConfigEnum::DateFormat => {
2483 config.date_time_format =
2484 DateTimeFormat::from_human_readable_string(value).unwrap();
2485 }
2486 ConfigEnum::DatePickerCalenderFormat => {
2487 config.date_picker_calender_format = CalenderType::try_from(value).unwrap();
2488 }
2489 ConfigEnum::Keybindings => {
2490 debug!("Keybindings should not be called from edit_config");
2491 }
2493 }
2494 Ok(())
2495 }
2496}
2497
2498pub async fn handle_exit(app: &mut App<'_>) -> AppReturn {
2499 if app.config.save_on_exit {
2500 app.dispatch(IoEvent::AutoSave).await;
2501 }
2502 AppReturn::Exit
2503}