glues-tui 0.8.1

TUI and WASM frontends for the Glues note-taking experience
Documentation
use {
    crate::{
        action::{Action, OpenProxyStep, TuiAction},
        config::{self, LAST_PROXY_URL},
        input::KeyCode,
        logger::*,
        theme::THEME,
    },
    glues_core::EntryEvent,
    ratatui::{style::Stylize, text::Line, widgets::ListState},
};

#[cfg(target_arch = "wasm32")]
use crate::config::LAST_IDB_NAMESPACE;

#[cfg(not(target_arch = "wasm32"))]
use crate::{
    action::{OpenGitStep, OpenMongoStep},
    config::{LAST_FILE_PATH, LAST_GIT_PATH, LAST_MONGO_CONN_STR, LAST_REDB_PATH},
};

pub const INSTANT: &str = "[i] Instant";
#[cfg(target_arch = "wasm32")]
pub const INDEXED_DB: &str = "[d] IndexedDB";
#[cfg(not(target_arch = "wasm32"))]
pub const FILE: &str = "[l] Local";
#[cfg(not(target_arch = "wasm32"))]
pub const REDB: &str = "[r] redb";
#[cfg(not(target_arch = "wasm32"))]
pub const GIT: &str = "[g] Git";
#[cfg(not(target_arch = "wasm32"))]
pub const MONGO: &str = "[m] MongoDB";
#[cfg(target_arch = "wasm32")]
pub const PROXY: &str = "[p] Proxy";
#[cfg(not(target_arch = "wasm32"))]
pub const PROXY: &str = "[p] Proxy";
pub const HELP: &str = "[h] Help";
pub const THEME_MENU: &str = "[t] Theme";
pub const QUIT: &str = "[q] Quit";

#[cfg(not(target_arch = "wasm32"))]
pub const MENU_ITEMS: [&str; 9] = [
    INSTANT, FILE, REDB, GIT, MONGO, PROXY, HELP, THEME_MENU, QUIT,
];
#[cfg(target_arch = "wasm32")]
pub const MENU_ITEMS: [&str; 5] = [INSTANT, INDEXED_DB, PROXY, HELP, THEME_MENU];

pub struct EntryContext {
    pub list_state: ListState,
}

impl Default for EntryContext {
    fn default() -> Self {
        Self {
            list_state: ListState::default().with_selected(Some(0)),
        }
    }
}

impl EntryContext {
    pub async fn consume(&mut self, code: KeyCode) -> Action {
        #[cfg(not(target_arch = "wasm32"))]
        let open = |key, action: TuiAction| async move {
            TuiAction::Prompt {
                message: vec![
                    Line::raw("Enter the path:"),
                    Line::from("If the path does not exist, it will be created.".fg(THEME.hint)),
                ],
                action: Box::new(action.into()),
                default: config::get(key).await,
            }
            .into()
        };

        #[cfg(not(target_arch = "wasm32"))]
        let open_redb = || async move {
            TuiAction::Prompt {
                message: vec![
                    Line::raw("Provide the redb database path:"),
                    Line::from(
                        "Glues will create or reuse the single-file redb database.".fg(THEME.hint),
                    ),
                ],
                action: Box::new(TuiAction::OpenRedb.into()),
                default: config::get(LAST_REDB_PATH).await,
            }
            .into()
        };

        #[cfg(target_arch = "wasm32")]
        let open_indexed_db = || async move {
            TuiAction::Prompt {
                message: vec![
                    Line::raw("Enter the IndexedDB namespace:"),
                    Line::from("Leave empty to use the default namespace (glues).".fg(THEME.hint)),
                ],
                action: Box::new(TuiAction::OpenIndexedDb.into()),
                default: config::get(LAST_IDB_NAMESPACE).await,
            }
            .into()
        };

        #[cfg(not(target_arch = "wasm32"))]
        let open_git = || async move {
            TuiAction::Prompt {
                message: vec![
                    Line::raw("Enter the git repository path:"),
                    Line::from("The path must contain an existing .git repository.".fg(THEME.hint)),
                    Line::from("otherwise, an error will occur.".fg(THEME.hint)),
                ],
                action: Box::new(TuiAction::OpenGit(OpenGitStep::Path).into()),
                default: config::get(LAST_GIT_PATH).await,
            }
            .into()
        };

        #[cfg(not(target_arch = "wasm32"))]
        let open_mongo = || async move {
            TuiAction::Prompt {
                message: vec![
                    Line::raw("Enter the MongoDB connection string:"),
                    Line::from("e.g. mongodb://localhost:27017".fg(THEME.hint)),
                ],
                action: Box::new(TuiAction::OpenMongo(OpenMongoStep::ConnStr).into()),
                default: config::get(LAST_MONGO_CONN_STR).await,
            }
            .into()
        };

        let open_proxy = || async move {
            TuiAction::Prompt {
                message: vec![
                    Line::raw("Enter the proxy server URL:"),
                    Line::from("e.g. http://127.0.0.1:4000".fg(THEME.hint)),
                ],
                action: Box::new(TuiAction::OpenProxy(OpenProxyStep::Url).into()),
                default: config::get(LAST_PROXY_URL).await,
            }
            .into()
        };

        match code {
            #[cfg(not(target_arch = "wasm32"))]
            KeyCode::Char('q') => TuiAction::Quit.into(),
            KeyCode::Char('j') | KeyCode::Down => {
                self.list_state.select_next();
                Action::None
            }
            KeyCode::Char('k') | KeyCode::Up => {
                self.list_state.select_previous();
                Action::None
            }
            KeyCode::Char('i') => EntryEvent::OpenMemory.into(),
            #[cfg(target_arch = "wasm32")]
            KeyCode::Char('d') => open_indexed_db().await,
            #[cfg(not(target_arch = "wasm32"))]
            KeyCode::Char('l') => open(LAST_FILE_PATH, TuiAction::OpenFile).await,
            #[cfg(not(target_arch = "wasm32"))]
            KeyCode::Char('r') => open_redb().await,
            #[cfg(not(target_arch = "wasm32"))]
            KeyCode::Char('g') => open_git().await,
            #[cfg(not(target_arch = "wasm32"))]
            KeyCode::Char('m') => open_mongo().await,
            KeyCode::Char('p') => open_proxy().await,
            KeyCode::Char('h') => TuiAction::Help.into(),
            KeyCode::Char('t') => TuiAction::OpenThemeMenu.into(),

            KeyCode::Enter => {
                let i = self
                    .list_state
                    .selected()
                    .log_expect("EntryContext::consume: selected is None. This should not happen.");
                match MENU_ITEMS[i] {
                    INSTANT => EntryEvent::OpenMemory.into(),
                    #[cfg(target_arch = "wasm32")]
                    INDEXED_DB => open_indexed_db().await,
                    #[cfg(not(target_arch = "wasm32"))]
                    FILE => open(LAST_FILE_PATH, TuiAction::OpenFile).await,
                    #[cfg(not(target_arch = "wasm32"))]
                    REDB => open_redb().await,
                    #[cfg(not(target_arch = "wasm32"))]
                    GIT => open_git().await,
                    #[cfg(not(target_arch = "wasm32"))]
                    MONGO => open_mongo().await,
                    PROXY => open_proxy().await,
                    HELP => TuiAction::Help.into(),
                    THEME_MENU => TuiAction::OpenThemeMenu.into(),
                    #[cfg(not(target_arch = "wasm32"))]
                    QUIT => TuiAction::Quit.into(),
                    _ => Action::None,
                }
            }
            _ => Action::PassThrough,
        }
    }
}