patch: Adjust to new API

This commit is contained in:
Erik Kundt
2024-06-04 17:45:52 +02:00
parent e5cd71450b
commit f24fc7574f
3 changed files with 303 additions and 439 deletions
+269 -16
View File
@@ -11,16 +11,23 @@ use radicle::Profile;
use radicle_tui as tui;
use ratatui::layout::Constraint;
use ratatui::style::Stylize;
use ratatui::text::{Line, Span, Text};
use termion::event::Key;
use tui::cob::patch;
use tui::store;
use tui::ui::items::{Filter, PatchItem, PatchItemFilter};
use tui::ui::widget::window::{Window, WindowProps};
use tui::ui::widget::ToWidget;
use tui::ui::span;
use tui::ui::widget::container::{Column, Container, Footer, FooterProps, Header, HeaderProps};
use tui::ui::widget::text::{Paragraph, ParagraphProps};
use tui::ui::widget::window::{Page, PageProps, Shortcuts, ShortcutsProps, Window, WindowProps};
use tui::ui::widget::{ToWidget, Widget};
use tui::{BoxedAny, Channel, Exit, PageStack};
use self::ui::HelpPage;
use self::ui::{BrowserPage, BrowserPageProps};
use self::ui::{Browser, BrowserProps};
use super::common::Mode;
@@ -38,7 +45,7 @@ pub struct App {
}
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
pub enum Page {
pub enum AppPage {
Browse,
Help,
}
@@ -72,7 +79,7 @@ pub struct HelpState {
#[derive(Clone, Debug)]
pub struct State {
mode: Mode,
pages: PageStack<Page>,
pages: PageStack<AppPage>,
browser: BrowserState,
help: HelpState,
}
@@ -96,7 +103,7 @@ impl TryFrom<&Context> for State {
Ok(Self {
mode: context.mode.clone(),
pages: PageStack::new(vec![Page::Browse]),
pages: PageStack::new(vec![AppPage::Browse]),
browser: BrowserState {
items,
selected: Some(0),
@@ -176,7 +183,7 @@ impl store::State<Selection> for State {
None
}
Message::OpenHelp => {
self.pages.push(Page::Help);
self.pages.push(AppPage::Help);
None
}
Message::LeavePage => {
@@ -204,17 +211,12 @@ impl App {
let tx = channel.tx.clone();
let window = Window::default()
.page(
Page::Browse,
BrowserPage::new(tx.clone())
.to_widget(tx.clone())
.on_update(|state| BrowserPageProps::from(state).to_boxed_any().into()),
)
.page(Page::Help, HelpPage::new(tx.clone()).to_widget(tx.clone()))
.page(AppPage::Browse, browser_page(&state, &channel))
.page(AppPage::Help, help_page(&state, &channel))
.to_widget(tx.clone())
.on_update(|state| {
WindowProps::default()
.current_page(state.pages.peek().unwrap_or(&Page::Browse).clone())
.current_page(state.pages.peek().unwrap_or(&AppPage::Browse).clone())
.to_boxed_any()
.into()
});
@@ -222,3 +224,254 @@ impl App {
tui::run(channel, state, window).await
}
}
fn browser_page(_state: &State, channel: &Channel<Message>) -> Widget<State, Message> {
let tx = channel.tx.clone();
let content = Browser::new(tx.clone())
.to_widget(tx.clone())
.on_update(|state| BrowserProps::from(state).to_boxed_any().into());
let shortcuts = Shortcuts::default()
.to_widget(tx.clone())
.on_update(|state: &State| {
let shortcuts = if state.browser.show_search {
vec![("esc", "cancel"), ("enter", "apply")]
} else {
match state.mode {
Mode::Id => vec![("enter", "select"), ("/", "search")],
Mode::Operation => vec![
("enter", "show"),
("c", "checkout"),
("d", "diff"),
("/", "search"),
("?", "help"),
],
}
};
ShortcutsProps::default()
.shortcuts(&shortcuts)
.to_boxed_any()
.into()
});
Page::default()
.content(content)
.shortcuts(shortcuts)
.to_widget(tx.clone())
.on_event(|key, _, props| {
let default = PageProps::default();
let props = props
.and_then(|props| props.inner_ref::<PageProps>())
.unwrap_or(&default);
if props.handle_keys {
match key {
Key::Esc | Key::Ctrl('c') => Some(Message::Exit { selection: None }),
Key::Char('?') => Some(Message::OpenHelp),
_ => None,
}
} else {
None
}
})
.on_update(|state: &State| {
PageProps::default()
.page_size(state.browser.page_size)
.handle_keys(!state.browser.show_search)
.to_boxed_any()
.into()
})
.on_render(|props, render| {
let default = PageProps::default();
let props = props
.and_then(|props| props.inner_ref::<PageProps>())
.unwrap_or(&default);
let page_size = render.area.height.saturating_sub(6) as usize;
if page_size != props.page_size {
return Some(Message::BrowserPageSize(page_size));
}
None
})
}
fn help_page(_state: &State, channel: &Channel<Message>) -> Widget<State, Message> {
let tx = channel.tx.clone();
let content = Container::default()
.header(Header::default().to_widget(tx.clone()).on_update(|_| {
HeaderProps::default()
.columns([Column::new(" Help ", Constraint::Fill(1))].to_vec())
.to_boxed_any()
.into()
}))
.content(
Paragraph::default()
.to_widget(tx.clone())
.on_event(|_, s, _| {
Some(Message::ScrollHelp {
progress: s.and_then(|p| p.unwrap_usize()).unwrap_or_default(),
})
})
.on_update(|state: &State| {
ParagraphProps::default()
.text(&help_text())
.page_size(state.help.page_size)
.to_boxed_any()
.into()
}),
)
.footer(
Footer::default()
.to_widget(tx.clone())
.on_update(|state: &State| {
FooterProps::default()
.columns(
[
Column::new(Text::raw(""), Constraint::Fill(1)),
Column::new(
span::default(&format!("{}%", state.help.progress)).dim(),
Constraint::Min(4),
),
]
.to_vec(),
)
.to_boxed_any()
.into()
}),
)
.to_widget(tx.clone());
let shortcuts = Shortcuts::default().to_widget(tx.clone()).on_update(|_| {
ShortcutsProps::default()
.shortcuts(&[("?", "close")])
.to_boxed_any()
.into()
});
Page::default()
.content(content)
.shortcuts(shortcuts)
.to_widget(tx.clone())
.on_event(|key, _, _| match key {
Key::Esc | Key::Ctrl('c') => Some(Message::Exit { selection: None }),
Key::Char('?') => Some(Message::LeavePage),
_ => None,
})
.on_update(|state: &State| {
PageProps::default()
.page_size(state.help.page_size)
.handle_keys(true)
.to_boxed_any()
.into()
})
.on_render(|props, render| {
let default = PageProps::default();
let props = props
.and_then(|props| props.inner_ref::<PageProps>())
.unwrap_or(&default);
let page_size = render.area.height.saturating_sub(6) as usize;
if page_size != props.page_size {
return Some(Message::HelpPageSize(page_size));
}
None
})
}
fn help_text() -> Text<'static> {
Text::from(
[
Line::from(Span::raw("Generic keybindings").cyan()),
Line::raw(""),
Line::from(vec![
Span::raw(format!("{key:>10}", key = "↑,k")).gray(),
Span::raw(" "),
Span::raw("move cursor one line up").gray().dim(),
]),
Line::from(vec![
Span::raw(format!("{key:>10}", key = "↓,j")).gray(),
Span::raw(" "),
Span::raw("move cursor one line down").gray().dim(),
]),
Line::from(vec![
Span::raw(format!("{key:>10}", key = "PageUp")).gray(),
Span::raw(" "),
Span::raw("move cursor one page up").gray().dim(),
]),
Line::from(vec![
Span::raw(format!("{key:>10}", key = "PageDown")).gray(),
Span::raw(" "),
Span::raw("move cursor one page down").gray().dim(),
]),
Line::from(vec![
Span::raw(format!("{key:>10}", key = "Home")).gray(),
Span::raw(" "),
Span::raw("move cursor to the first line").gray().dim(),
]),
Line::from(vec![
Span::raw(format!("{key:>10}", key = "End")).gray(),
Span::raw(" "),
Span::raw("move cursor to the last line").gray().dim(),
]),
Line::raw(""),
Line::from(Span::raw("Specific keybindings").cyan()),
Line::raw(""),
Line::from(vec![
Span::raw(format!("{key:>10}", key = "enter")).gray(),
Span::raw(" "),
Span::raw("Select patch (if --mode id)").gray().dim(),
]),
Line::from(vec![
Span::raw(format!("{key:>10}", key = "enter")).gray(),
Span::raw(" "),
Span::raw("Show patch").gray().dim(),
]),
Line::from(vec![
Span::raw(format!("{key:>10}", key = "c")).gray(),
Span::raw(" "),
Span::raw("Checkout patch").gray().dim(),
]),
Line::from(vec![
Span::raw(format!("{key:>10}", key = "d")).gray(),
Span::raw(" "),
Span::raw("Show patch diff").gray().dim(),
]),
Line::from(vec![
Span::raw(format!("{key:>10}", key = "/")).gray(),
Span::raw(" "),
Span::raw("Search").gray().dim(),
]),
Line::from(vec![
Span::raw(format!("{key:>10}", key = "?")).gray(),
Span::raw(" "),
Span::raw("Show help").gray().dim(),
]),
Line::from(vec![
Span::raw(format!("{key:>10}", key = "Esc")).gray(),
Span::raw(" "),
Span::raw("Quit / cancel").gray().dim(),
]),
Line::raw(""),
Line::from(Span::raw("Searching").cyan()),
Line::raw(""),
Line::from(vec![
Span::raw(format!("{key:>10}", key = "Pattern")).gray(),
Span::raw(" "),
Span::raw("is:<state> | is:authored | authors:[<did>, <did>] | <search>")
.gray()
.dim(),
]),
Line::from(vec![
Span::raw(format!("{key:>10}", key = "Example")).gray(),
Span::raw(" "),
Span::raw("is:open is:authored improve").gray().dim(),
]),
Line::raw(""),
Line::raw(""),
]
.to_vec(),
)
}
+33 -423
View File
@@ -9,7 +9,7 @@ use termion::event::Key;
use ratatui::layout::{Constraint, Layout};
use ratatui::style::Stylize;
use ratatui::text::{Line, Span, Text};
use ratatui::text::{Line, Text};
use radicle::patch;
use radicle::patch::Status;
@@ -20,13 +20,10 @@ use tui::ui::items::{PatchItem, PatchItemFilter};
use tui::ui::span;
use tui::ui::widget;
use tui::ui::widget::container::{
Column, Container, ContainerProps, Footer, FooterProps, Header, HeaderProps, SectionGroup,
SectionGroupProps,
Column, Container, ContainerProps, Footer, FooterProps, Header, HeaderProps,
};
use tui::ui::widget::input::{TextField, TextFieldProps};
use tui::ui::widget::list::{Table, TableProps, TableUtils};
use tui::ui::widget::text::{Paragraph, ParagraphProps};
use tui::ui::widget::window::{Shortcuts, ShortcutsProps};
use tui::ui::widget::ViewProps;
use tui::ui::widget::{RenderProps, ToWidget, View};
@@ -139,15 +136,18 @@ pub struct Browser {
}
impl Browser {
fn new(tx: UnboundedSender<Message>) -> Self {
pub fn new(tx: UnboundedSender<Message>) -> Self {
Self {
patches: Container::default()
.header(
Header::default()
// .columns(props.header.clone())
// .cutoff(props.cutoff, props.cutoff_after)
.to_widget(tx.clone()),
)
.header(Header::default().to_widget(tx.clone()).on_update(|state| {
// TODO: remove and use state directly
let props = BrowserProps::from(state);
HeaderProps::default()
.columns(props.header.clone())
.cutoff(props.cutoff, props.cutoff_after)
.to_boxed_any()
.into()
}))
.content(
Table::<State, Message, PatchItem, 9>::default()
.to_widget(tx.clone())
@@ -157,8 +157,8 @@ impl Browser {
})
})
.on_update(|state| {
// TODO: remove and use state directly
let props = BrowserProps::from(state);
TableProps::default()
.columns(props.columns)
.items(state.browser.patches())
@@ -171,10 +171,11 @@ impl Browser {
}),
)
.footer(Footer::default().to_widget(tx.clone()).on_update(|state| {
// TODO: remove and use state directly
let props = BrowserProps::from(state);
FooterProps::default()
.columns(browse_footer(&props, props.selected))
.columns(browser_footer(&props, props.selected))
.to_boxed_any()
.into()
}))
@@ -185,7 +186,23 @@ impl Browser {
.to_boxed_any()
.into()
}),
search: Search::new(tx.clone()).to_widget(tx.clone()),
search: TextField::default()
.to_widget(tx.clone())
.on_event(|key, s, _| match key {
Key::Esc => Some(Message::CloseSearch),
Key::Char('\n') => Some(Message::ApplySearch),
_ => Some(Message::UpdateSearch {
value: s.and_then(|i| i.unwrap_string()).unwrap_or_default(),
}),
})
.on_update(|state: &State| {
TextFieldProps::default()
.text(&state.browser.search.read().to_string())
.title("Search")
.inline(true)
.to_boxed_any()
.into()
}),
}
}
}
@@ -277,319 +294,7 @@ impl View for Browser {
}
}
#[derive(Clone, Default)]
pub struct BrowserPageProps<'a> {
/// Current page size (height of table content).
page_size: usize,
/// If this pages' keys should be handled (`false` if search is shown).
handle_keys: bool,
/// This pages' shortcuts.
shortcuts: Vec<(&'a str, &'a str)>,
}
impl<'a> From<&State> for BrowserPageProps<'a> {
fn from(state: &State) -> Self {
Self {
page_size: state.browser.page_size,
handle_keys: !state.browser.show_search,
shortcuts: if state.browser.show_search {
vec![("esc", "cancel"), ("enter", "apply")]
} else {
match state.mode {
Mode::Id => vec![("enter", "select"), ("/", "search")],
Mode::Operation => vec![
("enter", "show"),
("c", "checkout"),
("d", "diff"),
("/", "search"),
("?", "help"),
],
}
},
}
}
}
pub struct BrowserPage {
/// Sections widget
sections: Widget,
/// Shortcut widget
shortcuts: Widget,
}
impl BrowserPage {
pub fn new(tx: UnboundedSender<Message>) -> Self {
Self {
sections: SectionGroup::default()
.section(Browser::new(tx.clone()).to_widget(tx.clone()))
.to_widget(tx.clone())
.on_update(|state| {
let props = BrowserPageProps::from(state);
SectionGroupProps::default()
.handle_keys(props.handle_keys)
.to_boxed_any()
.into()
}),
shortcuts: Shortcuts::default()
.to_widget(tx.clone())
.on_update(|state| {
ShortcutsProps::default()
.shortcuts(&BrowserPageProps::from(state).shortcuts)
.to_boxed_any()
.into()
}),
}
}
}
impl View for BrowserPage {
type Message = Message;
type State = State;
fn handle_event(&mut self, props: Option<&ViewProps>, key: Key) -> Option<Self::Message> {
let default = BrowserPageProps::default();
let props = props
.and_then(|props| props.inner_ref::<BrowserPageProps>())
.unwrap_or(&default);
self.sections.handle_event(key);
if props.handle_keys {
return match key {
Key::Esc | Key::Ctrl('c') => Some(Message::Exit { selection: None }),
Key::Char('?') => Some(Message::OpenHelp),
_ => None,
};
}
None
}
fn update(&mut self, _props: Option<&ViewProps>, state: &Self::State) {
self.sections.update(state);
self.shortcuts.update(state);
}
fn render(&self, props: Option<&ViewProps>, render: RenderProps, frame: &mut Frame) {
let default = BrowserPageProps::default();
let props = props
.and_then(|props| props.inner_ref::<BrowserPageProps>())
.unwrap_or(&default);
let page_size = render.area.height.saturating_sub(6) as usize;
let [content_area, shortcuts_area] =
Layout::vertical([Constraint::Min(1), Constraint::Length(1)]).areas(render.area);
self.sections.render(
RenderProps::from(content_area)
.layout(Layout::horizontal([Constraint::Min(1)]))
.focus(true),
frame,
);
self.shortcuts
.render(RenderProps::from(shortcuts_area), frame);
// TODO: Find better solution
if page_size != props.page_size {
self.sections.send(Message::BrowserPageSize(page_size));
}
}
}
#[derive(Clone)]
pub struct SearchProps {}
pub struct Search {
/// Search input field
input: Widget,
}
impl Search {
fn new(tx: UnboundedSender<Message>) -> Self
where
Self: Sized,
{
Self {
input: TextField::default()
.to_widget(tx.clone())
.on_event(|s, _| {
Some(Message::UpdateSearch {
value: s.and_then(|i| i.unwrap_string()).unwrap_or_default(),
})
})
.on_update(|state: &State| {
TextFieldProps::default()
.text(&state.browser.search.read().to_string())
.title("Search")
.inline(true)
.to_boxed_any()
.into()
}),
}
}
}
impl View for Search {
type Message = Message;
type State = State;
fn handle_event(&mut self, _props: Option<&ViewProps>, key: Key) -> Option<Self::Message> {
match key {
Key::Esc => Some(Message::CloseSearch),
Key::Char('\n') => Some(Message::ApplySearch),
_ => {
self.input.handle_event(key);
None
}
}
}
fn update(&mut self, _props: Option<&ViewProps>, state: &Self::State) {
self.input.update(state);
}
fn render(&self, _props: Option<&ViewProps>, render: RenderProps, frame: &mut Frame) {
let layout = Layout::horizontal(Constraint::from_mins([0]))
.horizontal_margin(1)
.split(render.area);
self.input.render(RenderProps::from(layout[0]), frame);
}
}
#[derive(Clone, Default)]
struct HelpPageProps<'a> {
/// Current page size (height of table content).
page_size: usize,
/// Scroll progress of help paragraph.
help_progress: usize,
/// This pages' shortcuts.
shortcuts: Vec<(&'a str, &'a str)>,
}
impl<'a> From<&State> for HelpPageProps<'a> {
fn from(state: &State) -> Self {
Self {
page_size: state.help.page_size,
help_progress: state.help.progress,
shortcuts: vec![("?", "close")],
}
}
}
pub struct HelpPage {
/// Content widget
content: Widget,
/// Shortcut widget
shortcuts: Widget,
}
impl HelpPage {
pub fn new(tx: UnboundedSender<Message>) -> Self
where
Self: Sized,
{
Self {
content: Container::default()
.header(Header::default().to_widget(tx.clone()).on_update(|_| {
HeaderProps::default()
.columns([Column::new(" Help ", Constraint::Fill(1))].to_vec())
.to_boxed_any()
.into()
}))
.content(
Paragraph::default()
.to_widget(tx.clone())
.on_event(|_, s, _| {
Some(Message::ScrollHelp {
progress: s.and_then(|p| p.unwrap_usize()).unwrap_or_default(),
})
})
.on_update(|state| {
let props = HelpPageProps::from(state);
ParagraphProps::default()
.text(&help_text())
.page_size(props.page_size)
.to_boxed_any()
.into()
}),
)
.footer(Footer::default().to_widget(tx.clone()).on_update(|state| {
let props = HelpPageProps::from(state);
FooterProps::default()
.columns(
[
Column::new(Text::raw(""), Constraint::Fill(1)),
Column::new(
span::default(&format!("{}%", props.help_progress)).dim(),
Constraint::Min(4),
),
]
.to_vec(),
)
.to_boxed_any()
.into()
}))
.to_widget(tx.clone()),
shortcuts: Shortcuts::default()
.to_widget(tx.clone())
.on_update(|state| {
ShortcutsProps::default()
.shortcuts(&HelpPageProps::from(state).shortcuts)
.to_boxed_any()
.into()
}),
}
}
}
impl View for HelpPage {
type Message = Message;
type State = State;
fn handle_event(&mut self, _props: Option<&ViewProps>, key: Key) -> Option<Self::Message> {
match key {
Key::Esc | Key::Ctrl('c') => Some(Message::Exit { selection: None }),
Key::Char('?') => Some(Message::LeavePage),
_ => {
self.content.handle_event(key);
None
}
}
}
fn update(&mut self, _props: Option<&ViewProps>, state: &Self::State) {
self.content.update(state);
self.shortcuts.update(state);
}
fn render(&self, props: Option<&ViewProps>, render: RenderProps, frame: &mut Frame) {
let default = HelpPageProps::default();
let props = props
.and_then(|props| props.inner_ref::<HelpPageProps>())
.unwrap_or(&default);
let page_size = render.area.height.saturating_sub(6) as usize;
let [content_area, shortcuts_area] =
Layout::vertical([Constraint::Min(1), Constraint::Length(1)]).areas(render.area);
self.content
.render(RenderProps::from(content_area).focus(true), frame);
self.shortcuts
.render(RenderProps::from(content_area).focus(true), frame);
// TODO: Find better solution
if page_size != props.page_size {
self.content.send(Message::HelpPageSize(page_size));
}
}
}
fn browse_footer<'a>(props: &BrowserProps<'a>, selected: Option<usize>) -> Vec<Column<'a>> {
fn browser_footer<'a>(props: &BrowserProps<'a>, selected: Option<usize>) -> Vec<Column<'a>> {
let filter = PatchItemFilter::from_str(&props.search).unwrap_or_default();
let search = Line::from(vec![
@@ -673,98 +378,3 @@ fn browse_footer<'a>(props: &BrowserProps<'a>, selected: Option<usize>) -> Vec<C
],
}
}
fn help_text() -> Text<'static> {
Text::from(
[
Line::from(Span::raw("Generic keybindings").cyan()),
Line::raw(""),
Line::from(vec![
Span::raw(format!("{key:>10}", key = "↑,k")).gray(),
Span::raw(" "),
Span::raw("move cursor one line up").gray().dim(),
]),
Line::from(vec![
Span::raw(format!("{key:>10}", key = "↓,j")).gray(),
Span::raw(" "),
Span::raw("move cursor one line down").gray().dim(),
]),
Line::from(vec![
Span::raw(format!("{key:>10}", key = "PageUp")).gray(),
Span::raw(" "),
Span::raw("move cursor one page up").gray().dim(),
]),
Line::from(vec![
Span::raw(format!("{key:>10}", key = "PageDown")).gray(),
Span::raw(" "),
Span::raw("move cursor one page down").gray().dim(),
]),
Line::from(vec![
Span::raw(format!("{key:>10}", key = "Home")).gray(),
Span::raw(" "),
Span::raw("move cursor to the first line").gray().dim(),
]),
Line::from(vec![
Span::raw(format!("{key:>10}", key = "End")).gray(),
Span::raw(" "),
Span::raw("move cursor to the last line").gray().dim(),
]),
Line::raw(""),
Line::from(Span::raw("Specific keybindings").cyan()),
Line::raw(""),
Line::from(vec![
Span::raw(format!("{key:>10}", key = "enter")).gray(),
Span::raw(" "),
Span::raw("Select patch (if --mode id)").gray().dim(),
]),
Line::from(vec![
Span::raw(format!("{key:>10}", key = "enter")).gray(),
Span::raw(" "),
Span::raw("Show patch").gray().dim(),
]),
Line::from(vec![
Span::raw(format!("{key:>10}", key = "c")).gray(),
Span::raw(" "),
Span::raw("Checkout patch").gray().dim(),
]),
Line::from(vec![
Span::raw(format!("{key:>10}", key = "d")).gray(),
Span::raw(" "),
Span::raw("Show patch diff").gray().dim(),
]),
Line::from(vec![
Span::raw(format!("{key:>10}", key = "/")).gray(),
Span::raw(" "),
Span::raw("Search").gray().dim(),
]),
Line::from(vec![
Span::raw(format!("{key:>10}", key = "?")).gray(),
Span::raw(" "),
Span::raw("Show help").gray().dim(),
]),
Line::from(vec![
Span::raw(format!("{key:>10}", key = "Esc")).gray(),
Span::raw(" "),
Span::raw("Quit / cancel").gray().dim(),
]),
Line::raw(""),
Line::from(Span::raw("Searching").cyan()),
Line::raw(""),
Line::from(vec![
Span::raw(format!("{key:>10}", key = "Pattern")).gray(),
Span::raw(" "),
Span::raw("is:<state> | is:authored | authors:[<did>, <did>] | <search>")
.gray()
.dim(),
]),
Line::from(vec![
Span::raw(format!("{key:>10}", key = "Example")).gray(),
Span::raw(" "),
Span::raw("is:open is:authored improve").gray().dim(),
]),
Line::raw(""),
Line::raw(""),
]
.to_vec(),
)
}
+1
View File
@@ -209,6 +209,7 @@ where
// .and_then(|props| props.inner_ref::<TableProps<R, W>>())
// .unwrap_or(&default);
// TODO
// if props.selected != self.state.selected() {
// self.state.select(props.selected);
// }