Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proxy tree furthur functions #22

Open
3 of 4 tasks
github-actions bot opened this issue Nov 29, 2021 · 0 comments
Open
3 of 4 tasks

Proxy tree furthur functions #22

github-actions bot opened this issue Nov 29, 2021 · 0 comments
Labels

Comments

@github-actions
Copy link

Proxy tree furthur functions

  • Esc for exist expand mode

  • T for test latency of current group

  • S for switch between sorting strategies

  • / for searching

  • Remove Enter from InterfaceEvent::ToggleHold

  • Maybe a new InterfaceEvent::Confirm correstponds to Enter

  • T, S, / in proxy event handling

So use map & expect instead of Option#and_then

// TODO Proxy tree furthur functions

use std::{cmp::Ordering, collections::HashMap};
use std::{fmt::Debug, marker::PhantomData};

use clashctl_interactive::{EndlessSelf, ProxySort, Sortable};
use crossterm::event::KeyCode;
use tui::{
    style::{Color, Modifier, Style},
    text::Span,
};

use crate::{
    components::{Footer, FooterItem, MovableListManage, ProxyGroup, ProxyItem},
    help_footer,
    model::Proxies,
    tagged_footer, Action, Coord, ListEvent, Wrap,
};

// TODO Proxy tree furthur functions
//
// - [X] Right & Enter can be used to apply selection
// - [X] Esc for exist expand mode
// - [X] T for test latency of current group
// - [X] S for switch between sorting strategies
// - [ ] / for searching
//
// In order for functions to be implemented, these are required:
// - Remove Enter from InterfaceEvent::ToggleHold
// - Maybe a new InterfaceEvent::Confirm correstponds to Enter
// - `T`, `S`, `/` in proxy event handling
#[derive(Clone, Debug, PartialEq)]
pub struct ProxyTree<'a> {
    pub(super) groups: Vec<ProxyGroup<'a>>,
    pub(super) expanded: bool,
    pub(super) cursor: usize,
    pub(super) testing: bool,
    pub(super) footer: Footer<'a>,
    sort_method: ProxySort,
}

impl<'a> Default for ProxyTree<'a> {
    fn default() -> Self {
        let mut ret = Self {
            groups: Default::default(),
            expanded: Default::default(),
            cursor: Default::default(),
            footer: Default::default(),
            testing: Default::default(),
            sort_method: Default::default(),
        };
        ret.update_footer();
        ret
    }
}

impl<'a> ProxyTree<'a> {
    #[inline]
    pub fn cursor(&self) -> usize {
        self.cursor
    }

    #[inline]
    pub fn current_group(&self) -> &ProxyGroup {
        &self.groups[self.cursor]
    }

    #[inline]
    pub fn is_testing(&self) -> bool {
        self.testing
    }

    #[inline]
    pub fn start_testing(&mut self) -> &mut Self {
        self.testing = true;
        self.update_footer()
    }

    #[inline]
    pub fn end_testing(&mut self) -> &mut Self {
        self.testing = false;
        self.update_footer()
    }

    pub fn sort_groups_with_frequency(&mut self, freq: &HashMap<String, usize>) -> &mut Self {
        self.groups
            .sort_by(|a, b| match (freq.get(&a.name), freq.get(&b.name)) {
                (Some(a_freq), Some(b_freq)) => b_freq.cmp(a_freq),
                (Some(_), None) => Ordering::Less,
                (None, Some(_)) => Ordering::Greater,
                (None, None) => a.name.cmp(&b.name),
            });
        self
    }

    pub fn update_footer(&mut self) -> &mut Self {
        let mut footer = Footer::default();
        let current_group = match self.groups.get(self.cursor) {
            Some(grp) => grp,
            _ => return self,
        };

        if !self.expanded {
            let group_name = current_group.name.clone();
            let style = Style::default()
                .fg(Color::Blue)
                .add_modifier(Modifier::REVERSED);

            let highlight = style.add_modifier(Modifier::BOLD);
            let sort = tagged_footer("Sort", style, self.sort_method);

            let mut left = vec![
                FooterItem::span(Span::styled(" FREE ", style)),
                FooterItem::span(Span::styled(" SPACE to expand ", style)),
                if self.testing {
                    FooterItem::span(Span::styled(" Testing ", highlight.fg(Color::Green)))
                } else {
                    FooterItem::spans(help_footer("Test", style, highlight)).wrapped()
                },
                FooterItem::spans(sort),
            ];

            footer.append_left(&mut left);

            let name = FooterItem::span(Span::styled(group_name, style)).wrapped();
            footer.push_right(name);

            if let Some(now) = current_group.current {
                footer.push_right(
                    FooterItem::span(Span::raw(current_group.members[now].name.to_owned()))
                        .wrapped(),
                );
            }
        } else {
            let style = Style::default()
                .fg(Color::Green)
                .add_modifier(Modifier::REVERSED);
            let highlight = style.add_modifier(Modifier::BOLD);

            footer.push_left(FooterItem::span(Span::styled(" [^] ▲ ▼ Move ", style)));

            if current_group.proxy_type.is_selector() {
                footer.push_left(FooterItem::span(Span::styled(" ▶ Select ", style)));
            }

            footer.push_left(if self.testing {
                FooterItem::span(Span::styled(" Testing ", highlight.fg(Color::Blue)))
            } else {
                FooterItem::spans(help_footer("Test", style, highlight)).wrapped()
            });

            footer.push_left(tagged_footer("Sort", style, self.sort_method).into());

            if let Some(ref now) = current_group.members[current_group.cursor].now {
                footer.push_right(FooterItem::span(Span::raw(now.to_owned())).wrapped());
            }
        }
        self.footer = footer;
        self
    }

    pub fn replace_with(&mut self, mut new_tree: ProxyTree<'a>) -> &mut Self {
        // let map = HashMap::<_, _, RandomState>::from_iter(self.groups.iter().map(|x| (&x.name, x)));
        let old_groups = &self.groups;
        let current_group = self.groups.get(self.cursor);
        for (index, new_group) in new_tree.groups.iter_mut().enumerate() {
            if let Some(true) = current_group.map(|x| x.name == new_group.name) {
                new_tree.cursor = index;
            }
            if let Some(old_group) = old_groups.iter().find(|group| group.name == new_group.name) {
                new_group.cursor = old_group
                    .members
                    .get(old_group.cursor)
                    .and_then(|old_member| {
                        new_group
                            .members
                            .iter()
                            .position(|new_member| new_member.name == old_member.name)
                    })
                    .or(new_group.current)
                    .unwrap_or_default()
            }
        }
        self.groups = new_tree.groups;
        let method = self.sort_method;
        self.sort_with(&method);
        self.update_footer()
    }
}

impl<'a> From<Proxies> for ProxyTree<'a> {
    fn from(val: Proxies) -> Self {
        let mut ret = Self {
            groups: Vec::with_capacity(val.len()),
            ..Default::default()
        };
        for (name, group) in val.groups() {
            let all = group
                .all
                .as_ref()
                .expect("ProxyGroup should have member vec");
            let mut members = Vec::with_capacity(all.len());
            for x in all.iter() {
                let member = (
                    x.as_str(),
                    val.get(x)
                        .to_owned()
                        .expect("Group member should be in all proxies"),
                )
                    .into();
                members.push(member);
            }

            // if group.now.is_some then it must be in all proxies
            // So use map & expect instead of Option#and_then
            let current = group.now.as_ref().map(|name| {
                members
                    .iter()
                    .position(|item: &ProxyItem| &item.name == name)
                    .expect("Group member should be in all proxies")
            });

            ret.groups.push(ProxyGroup {
                _life: PhantomData,
                name: name.to_owned(),
                proxy_type: group.proxy_type,
                cursor: current.unwrap_or_default(),
                current,
                members,
            })
        }

        ret
    }
}

impl<'a> MovableListManage for ProxyTree<'a> {
    fn sort(&mut self) -> &mut Self {
        let method = self.sort_method;
        self.sort_with(&method);
        self
    }

    fn next_sort(&mut self) -> &mut Self {
        self.sort_method.next_self();
        let method = self.sort_method;
        self.sort_with(&method);
        self.update_footer()
    }

    fn prev_sort(&mut self) -> &mut Self {
        self.sort_method.prev_self();
        let method = self.sort_method;
        self.sort_with(&method);
        self.update_footer()
    }

    fn current_pos(&self) -> Coord {
        Default::default()
    }

    #[inline]
    fn toggle(&mut self) -> &mut Self {
        self.expanded = !self.expanded;
        self.update_footer()
    }

    #[inline]
    fn end(&mut self) -> &mut Self {
        self.expanded = false;
        self.update_footer()
    }

    #[inline]
    fn len(&self) -> usize {
        self.groups.len()
    }

    #[inline]
    fn is_empty(&self) -> bool {
        self.groups.is_empty()
    }

    fn hold(&mut self) -> &mut Self {
        self.expanded = true;
        self
    }

    fn handle(&mut self, event: ListEvent) -> Option<Action> {
        if self.expanded {
            let step = if event.fast { 3 } else { 1 };
            let group = &mut self.groups[self.cursor];
            match event.code {
                KeyCode::Up => {
                    if group.cursor > 0 {
                        group.cursor = group.cursor.saturating_sub(step)
                    }
                }
                KeyCode::Down => {
                    let left = group.members.len().saturating_sub(group.cursor + 1);

                    group.cursor += left.min(step)
                }
                KeyCode::Right | KeyCode::Enter => {
                    if group.proxy_type.is_selector() {
                        let current = group.members[group.cursor].name.to_owned();
                        return Some(Action::ApplySelection {
                            group: group.name.to_owned(),
                            proxy: current,
                        });
                    }
                }
                _ => {}
            }
        } else {
            match event.code {
                KeyCode::Up => {
                    if self.cursor > 0 {
                        self.cursor = self.cursor.saturating_sub(1)
                    }
                }
                KeyCode::Down => {
                    if self.cursor < self.groups.len() - 1 {
                        self.cursor = self.cursor.saturating_add(1)
                    }
                }
                KeyCode::Enter => self.expanded = true,
                _ => {}
            }
        }
        self.update_footer();
        None
    }

    fn offset(&self) -> &crate::Coord {
        &Coord {
            x: 0,
            y: 0,
            hold: false,
        }
    }
}

2c85b9591f557c1016bafacbbb97da1098d70465

@github-actions github-actions bot added the todo label Nov 29, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

0 participants