wiki-tui 0.8.1

A simple and easy to use Wikipedia Text User Interface
Documentation
use std::{cell::RefCell, rc::Rc};

use anyhow::Context;
use cursive::{
    view::{Nameable, Resizable},
    views::{DummyView, EditView},
    Cursive,
};

use crate::{
    config::{Config, CONFIG},
    ui::utils::display_error,
    wiki::{
        article::{Article, LanguageLink},
        language::{Language, LANGUAGES},
    },
};

use super::{
    article::{self, ArticleView, ARTICLE_PROPERTIES},
    panel::WithPanel,
    scroll_view::Scrollable,
    utils::{display_message, percentage},
    views::{RootLayout, SelectView},
};

const POPUP_NAME: &str = "language_selection_popup";
const LANGUAGE_SELECTION_NAME: &str = "language_selection";

const SELECTION_WIDTH_PERCENTAGE: f32 = 0.2;
const SELECTION_HEIGHT_PERCENTAGE: f32 = 0.5;

fn change_lang(siv: &mut Cursive, lang: impl Into<Language>) {
    let language: Language = lang.into();
    let success_msg = format!("Changed the language to {}", language.name());

    let user_data_changed = siv
        .with_user_data(|c: &mut Rc<RefCell<Config>>| {
            info!("changing the language to '{:?}'", language);
            c.borrow_mut().api_config.language = language;
            true
        })
        .is_some();

    if user_data_changed && CONFIG.api_config.language_changed_popup {
        display_message(siv, "Information", &success_msg);
    }
}

/// Displays a popup that lets the user chosse a new language
pub fn language_selection_popup(siv: &mut Cursive) {
    if siv.find_name::<RootLayout>(POPUP_NAME).is_some() {
        siv.pop_layer();
        return;
    }

    info!("displaying '{}' languages for selection", LANGUAGES.len());

    let language_search = EditView::new()
        .on_edit(|siv, text, _| {
            let sorted_languages = LANGUAGES
                .iter()
                .filter(|lang| {
                    let lang = lang.name().to_lowercase();
                    let query = text.to_lowercase();
                    lang.contains(&query)
                })
                .map(|lang| lang.to_owned())
                .collect::<Vec<Language>>();

            siv.call_on_name(
                LANGUAGE_SELECTION_NAME,
                |language_selection: &mut SelectView<String>| {
                    language_selection.clear();
                    language_selection.add_all(
                        sorted_languages
                            .iter()
                            .map(|l| (l.name(), l.code().to_owned())),
                    );
                },
            );
        })
        .on_submit(|siv, _| {
            if let Some(selected_language) = siv.call_on_name(
                LANGUAGE_SELECTION_NAME,
                |language_selection: &mut SelectView<String>| {
                    language_selection.selection().unwrap_or_default()
                },
            ) {
                siv.pop_layer();
                change_lang(siv, selected_language.as_str());
            }
        });

    let mut language_selection: SelectView<String> =
        SelectView::new().on_submit(|s, item: &String| {
            s.pop_layer();
            change_lang(s, item.as_str());
        });

    language_selection.add_all(LANGUAGES.iter().map(|l| (l.name(), l.code().to_owned())));

    let screen_size = siv.screen_size();

    let selection_width = percentage(screen_size.x, SELECTION_WIDTH_PERCENTAGE);
    let selection_height = percentage(screen_size.y, SELECTION_HEIGHT_PERCENTAGE);

    siv.add_layer(
        RootLayout::vertical(CONFIG.keybindings.clone())
            .child(language_search)
            .child(DummyView {})
            .child(
                language_selection
                    .with_name(LANGUAGE_SELECTION_NAME)
                    .scrollable(),
            )
            .input(true)
            .with_name(POPUP_NAME)
            .with_panel()
            .title("Change Language")
            .fixed_size((selection_width, selection_height)),
    );
}

const ARTICLE_POPUP_NAME: &str = "article_language_selection_popup";
const ARTICLE_LANGUAGE_SELECTION_NAME: &str = "article_language_selection";

const ARTICLE_SELECTION_WIDTH_PERCENTAGE: f32 = 0.4;
const ARTICLE_SELECTION_HEIGHT_PERCNETAGE: f32 = 0.5;

fn change_article_language(siv: &mut Cursive, language_link: Rc<LanguageLink>) {
    let success_msg = format!(
        "Changed the language of your current article to {}",
        language_link.language.name()
    );

    let article = match Article::builder()
        .page(&language_link.title)
        .endpoint(
            Config::from_siv(siv)
                .borrow()
                .api_config
                .endpoint_from_language(&language_link.language),
        )
        .language(language_link.language.clone())
        .properties(ARTICLE_PROPERTIES.to_vec())
        .fetch()
        .context("failed fetching the article")
    {
        Ok(article) => article,
        Err(error) => {
            warn!("{:?}", error);
            display_error(siv, error);
            return;
        }
    };

    siv.pop_layer();
    if let Err(err) = article::display_article(siv, article) {
        warn!("{:?}", err);
        display_error(siv, err);
    }

    if CONFIG.api_config.article_language_changed_popup {
        display_message(siv, "Information", &success_msg);
    }
}

pub fn toggle_article_language_selection_popup(siv: &mut Cursive) {
    if siv.find_name::<RootLayout>(ARTICLE_POPUP_NAME).is_some() {
        siv.pop_layer();
        return;
    }

    let article_view_name = format!("article_view-{}", siv.screen().len());
    if let Some(event_result) = siv.call_on_name(&article_view_name, |view: &mut ArticleView| {
        view.list_article_language_switcher()
    }) {
        event_result.process(siv);
    } else {
        display_message(siv, "Information", "No Article is open!")
    }
}

pub fn article_language_selection_popup(siv: &mut Cursive, languages: Vec<LanguageLink>) {
    info!(
        "displaying '{}' article languages for selection",
        languages.len()
    );

    let mut language_selection: SelectView<LanguageLink> =
        SelectView::new().on_submit(|s, item: &LanguageLink| {
            s.pop_layer();
            change_article_language(s, item.to_owned().into());
        });

    language_selection.add_all(
        languages
            .iter()
            .map(|lang_link| (lang_link.language.name(), lang_link.to_owned())),
    );

    let language_search = EditView::new()
        .on_edit(move |siv, text, _| {
            let sorted_languages = languages
                .iter()
                .filter(|lang_link| {
                    let lang = lang_link.language.name().to_lowercase();
                    let query = text.to_lowercase();
                    lang.contains(&query)
                })
                .map(|lang_link| lang_link.to_owned())
                .collect::<Vec<LanguageLink>>();

            siv.call_on_name(
                ARTICLE_LANGUAGE_SELECTION_NAME,
                move |language_selection: &mut SelectView<LanguageLink>| {
                    language_selection.clear();
                    language_selection.add_all(
                        sorted_languages
                            .iter()
                            .map(|lang_link| (lang_link.language.name(), lang_link.to_owned())),
                    )
                },
            );
        })
        .on_submit(|siv, _| {
            if let Some(Some(selected_language)) = siv.call_on_name(
                ARTICLE_LANGUAGE_SELECTION_NAME,
                |language_selection: &mut SelectView<LanguageLink>| language_selection.selection(),
            ) {
                siv.pop_layer();
                change_article_language(siv, selected_language);
            }
        });

    let screen_size = siv.screen_size();

    let selection_width = percentage(screen_size.x, ARTICLE_SELECTION_WIDTH_PERCENTAGE);
    let selection_height = percentage(screen_size.y, ARTICLE_SELECTION_HEIGHT_PERCNETAGE);

    siv.add_layer(
        RootLayout::vertical(CONFIG.keybindings.clone())
            .child(language_search)
            .child(DummyView {})
            .child(
                language_selection
                    .with_name(ARTICLE_LANGUAGE_SELECTION_NAME)
                    .scrollable(),
            )
            .input(true)
            .with_name(ARTICLE_POPUP_NAME)
            .with_panel()
            .title("Switch Article Language")
            .fixed_size((selection_width, selection_height)),
    )
}