iconmate 1.2.1

CLI to fetch icons and save them into your Vite, NextJS, or similar TypeScript project
Documentation
use crate::{utils::IconEntry, views::main::MainState};
use std::sync::mpsc::{Receiver, Sender};
use tui_textarea::Input;

#[derive(Debug, Clone, Copy, PartialEq)]
pub enum AppFocus {
    Main,
    AddPopup,
    DeletePopup,
    RenamePopup,
    HelpPopup,
    IconifySearchPopup,
    SyncPopup,
}

#[derive(Debug, Clone)]
pub struct IconifyCollectionListItem {
    pub prefix: String,
    pub name: String,
    pub total: Option<u32>,
}

#[derive(Debug, Clone)]
pub struct IconifySearchPayload {
    pub icons: Vec<String>,
}

#[derive(Debug)]
pub enum AppEvent {
    IconifyCollectionsLoaded {
        request_id: u64,
        result: Result<Vec<IconifyCollectionListItem>, String>,
    },
    IconifySearchLoaded {
        request_id: u64,
        query: String,
        result: Result<IconifySearchPayload, String>,
    },
    IconifyCollectionIconsLoaded {
        request_id: u64,
        prefix: String,
        result: Result<Vec<String>, String>,
    },
}

#[derive(Debug, Clone)]
pub struct AppConfig {
    pub folder: String,
    pub preset: String,
    pub svg_viewer_cmd: Option<String>,
    pub svg_viewer_cmd_source: String,
    pub global_config_loaded: bool,
    pub project_config_loaded: bool,
    pub flutter_barrel_file: Option<String>,
    pub flutter_barrel_class: Option<String>,
}

pub struct App {
    pub config: AppConfig,

    // App state
    pub tx: Sender<AppEvent>,
    pub rx: Receiver<AppEvent>,

    pub should_quit: bool,

    pub selected_index: usize,

    pub items: Vec<IconEntry>,

    pub filtered_items: Vec<IconEntry>,

    pub app_focus: AppFocus,

    // Deeper states
    pub main_state: crate::views::main::MainState,
    pub add_popup_state: Option<crate::views::add_popup::AddPopupState>,
    pub delete_popup_state: Option<crate::views::delete_popup::DeletePopupState>,
    pub rename_popup_state: Option<crate::views::rename_popup::RenamePopupState>,
    pub iconify_search_popup_state:
        Option<crate::views::iconify_search_popup::IconifySearchPopupState>,
    pub sync_popup_state: Option<crate::views::sync_popup::SyncPopupState>,

    pub next_async_request_id: u64,
}

impl App {
    pub fn new(config: AppConfig) -> Self {
        let (tx, rx) = std::sync::mpsc::channel();
        let mut app = Self {
            config,

            should_quit: false,
            tx,
            rx,

            selected_index: 0,
            filtered_items: Vec::new(),
            items: Vec::new(),

            app_focus: AppFocus::Main,
            add_popup_state: None,
            delete_popup_state: None,
            rename_popup_state: None,
            iconify_search_popup_state: None,
            sync_popup_state: None,
            next_async_request_id: 0,
            main_state: MainState::new(),
        };

        app.init_icons();
        app
    }

    pub fn init_icons(&mut self) {
        // Try to read the current project's export file (or Dart barrel for Flutter).
        self.items = crate::utils::get_existing_icons_for_preset(
            &self.config.folder,
            &self.config.preset,
            self.config.flutter_barrel_file.as_deref(),
        )
        .unwrap_or_default();
        self.filtered_items = self.items.clone();
    }

    pub fn update(&mut self) {
        while let Ok(event) = self.rx.try_recv() {
            self.handle_app_event(event);
        }

        self.tick_iconify_search_popup();
    }

    pub fn handlekeys(&mut self, key: Input) {
        match self.app_focus {
            AppFocus::Main => self.handlekeys_main(key),
            AppFocus::AddPopup => self.handlekeys_add_popup(key),
            AppFocus::DeletePopup => self.handlekeys_delete_popup(key),
            AppFocus::RenamePopup => self.handlekeys_rename_popup(key),
            AppFocus::HelpPopup => self.handlekeys_help_popup(key),
            AppFocus::IconifySearchPopup => self.handlekeys_iconify_search_popup(key),
            AppFocus::SyncPopup => self.handlekeys_sync_popup(key),
        }
    }

    pub fn handle_mouse(&mut self, mouse: ratatui::crossterm::event::MouseEvent) {
        match self.app_focus {
            AppFocus::Main => self.handle_mouse_main(mouse),
            AppFocus::IconifySearchPopup => self.handle_mouse_iconify_search_popup(mouse),
            _ => {}
        }
    }
}