Handle edit issue modal top dropdown

This commit is contained in:
Adrian Wozniak 2020-04-02 08:45:43 +02:00
parent 801f07648d
commit 5e81329378
5 changed files with 168 additions and 48 deletions

View File

@ -4,6 +4,7 @@ use seed::{prelude::*, *};
use jirs_data::IssueStatus; use jirs_data::IssueStatus;
use crate::model::Page; use crate::model::Page;
use crate::shared::styled_select::StyledSelectChange;
mod api; mod api;
mod api_handlers; mod api_handlers;
@ -18,9 +19,16 @@ pub type UserId = i32;
pub type IssueId = i32; pub type IssueId = i32;
pub type AvatarFilterActive = bool; pub type AvatarFilterActive = bool;
#[derive(Clone, Debug)]
pub enum FieldId {
IssueTypeEditModalTop,
}
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum Msg { pub enum Msg {
NoOp, NoOp,
StyledSelectChanged(FieldId, StyledSelectChange),
ChangePage(model::Page), ChangePage(model::Page),
CurrentProjectResult(FetchObject<String>), CurrentProjectResult(FetchObject<String>),
CurrentUserResult(FetchObject<String>), CurrentUserResult(FetchObject<String>),

View File

@ -9,9 +9,17 @@ use crate::{IssueId, UserId, HOST_URL};
pub type ProjectId = i32; pub type ProjectId = i32;
#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialOrd, PartialEq)] #[derive(Serialize, Deserialize, Clone, Debug, PartialOrd, PartialEq)]
pub enum ModalType { pub enum ModalType {
EditIssue(IssueId), EditIssue(IssueId, EditIssueModal),
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialOrd, PartialEq)]
pub struct EditIssueModal {
pub id: i32,
pub top_select_opened: bool,
pub top_select_filter: String,
pub value: IssueType,
} }
#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialOrd, PartialEq)] #[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialOrd, PartialEq)]

View File

@ -2,14 +2,14 @@ use seed::{prelude::*, *};
use jirs_data::*; use jirs_data::*;
use crate::model::{Icon, ModalType, Model, Page}; use crate::model::{EditIssueModal, Icon, ModalType, Model, Page};
use crate::shared::modal::{Modal, Variant as ModalVariant}; use crate::shared::modal::{Modal, Variant as ModalVariant};
use crate::shared::styled_avatar::StyledAvatar; use crate::shared::styled_avatar::StyledAvatar;
use crate::shared::styled_button::{StyledButton, Variant as ButtonVariant}; use crate::shared::styled_button::{StyledButton, Variant as ButtonVariant};
use crate::shared::styled_input::StyledInput; use crate::shared::styled_input::StyledInput;
use crate::shared::styled_select::StyledSelect; use crate::shared::styled_select::{StyledSelect, StyledSelectChange};
use crate::shared::{drag_ev, find_issue, inner_layout, ToNode}; use crate::shared::{drag_ev, find_issue, inner_layout, ToNode};
use crate::{IssueId, Msg}; use crate::{FieldId, IssueId, Msg};
pub fn update(msg: Msg, model: &mut crate::model::Model, orders: &mut impl Orders<Msg>) { pub fn update(msg: Msg, model: &mut crate::model::Model, orders: &mut impl Orders<Msg>) {
match msg { match msg {
@ -28,7 +28,18 @@ pub fn update(msg: Msg, model: &mut crate::model::Model, orders: &mut impl Order
orders orders
.skip() .skip()
.perform_cmd(crate::api::fetch_current_user(model.host_url.clone())); .perform_cmd(crate::api::fetch_current_user(model.host_url.clone()));
model.modal = Some(ModalType::EditIssue(issue_id)); let value = find_issue(model, issue_id)
.map(|issue| issue.issue_type.clone())
.unwrap_or_else(|| IssueType::Task);
model.modal = Some(ModalType::EditIssue(
issue_id,
EditIssueModal {
id: issue_id,
top_select_opened: false,
top_select_filter: "".to_string(),
value,
},
));
} }
Msg::ToggleAboutTooltip => { Msg::ToggleAboutTooltip => {
model.project_page.about_tooltip_visible = !model.project_page.about_tooltip_visible; model.project_page.about_tooltip_visible = !model.project_page.about_tooltip_visible;
@ -115,6 +126,23 @@ pub fn update(msg: Msg, model: &mut crate::model::Model, orders: &mut impl Order
_ => error!("Drag stopped before drop :("), _ => error!("Drag stopped before drop :("),
} }
} }
Msg::StyledSelectChanged(FieldId::IssueTypeEditModalTop, change) => {
match (change, model.modal.as_mut()) {
(StyledSelectChange::Text(ref text), Some(ModalType::EditIssue(_, modal))) => {
modal.top_select_filter = text.clone();
}
(
StyledSelectChange::DropDownVisibility(flag),
Some(ModalType::EditIssue(_, modal)),
) => {
modal.top_select_opened = flag;
}
(StyledSelectChange::Changed(value), Some(ModalType::EditIssue(_, modal))) => {
modal.value = value.into();
}
_ => {}
}
}
Msg::IssueUpdateResult(fetched) => { Msg::IssueUpdateResult(fetched) => {
crate::api_handlers::update_issue_response(&fetched, model); crate::api_handlers::update_issue_response(&fetched, model);
} }
@ -123,10 +151,10 @@ pub fn update(msg: Msg, model: &mut crate::model::Model, orders: &mut impl Order
} }
pub fn view(model: &Model) -> Node<Msg> { pub fn view(model: &Model) -> Node<Msg> {
let modal = match model.modal { let modal = match &model.modal {
Some(ModalType::EditIssue(issue_id)) => { Some(ModalType::EditIssue(issue_id, modal)) => {
if let Some(issue) = find_issue(model, issue_id) { if let Some(issue) = find_issue(model, *issue_id) {
let details = issue_details(model, issue); let details = issue_details(model, issue, &modal);
let modal = Modal { let modal = Modal {
variant: ModalVariant::Center, variant: ModalVariant::Center,
width: 1040, width: 1040,
@ -428,16 +456,30 @@ impl crate::shared::styled_select::SelectOption for IssueTypeOption {
} }
.into_node() .into_node()
} }
fn match_text_filter(&self, text_filter: &str) -> bool {
self.1
.to_string()
.to_lowercase()
.contains(&text_filter.to_lowercase())
} }
fn issue_details(_model: &Model, issue: &Issue) -> Node<Msg> { fn to_value(&self) -> u32 {
self.1.clone().into()
}
}
fn issue_details(_model: &Model, issue: &Issue, modal: &EditIssueModal) -> Node<Msg> {
let issue_id = issue.id; let issue_id = issue.id;
let issue_type_select = StyledSelect { let issue_type_select = StyledSelect {
on_change: mouse_ev(Ev::Click, |_| Msg::NoOp), id: FieldId::IssueTypeEditModalTop,
variant: crate::shared::styled_select::Variant::Empty, variant: crate::shared::styled_select::Variant::Empty,
dropdown_width: Some(150), dropdown_width: Some(150),
name: Some("type".to_string()), name: Some("type".to_string()),
placeholder: None, placeholder: None,
text_filter: modal.top_select_filter.clone(),
opened: modal.top_select_opened,
valid: true, valid: true,
is_multi: false, is_multi: false,
allow_clear: false, allow_clear: false,
@ -446,7 +488,7 @@ fn issue_details(_model: &Model, issue: &Issue) -> Node<Msg> {
IssueTypeOption(issue_id, IssueType::Task), IssueTypeOption(issue_id, IssueType::Task),
IssueTypeOption(issue_id, IssueType::Bug), IssueTypeOption(issue_id, IssueType::Bug),
], ],
selected: Some(IssueTypeOption(issue_id, IssueType::Story)), selected: vec![IssueTypeOption(issue_id, modal.value.clone())],
} }
.into_node(); .into_node();

View File

@ -1,7 +1,14 @@
use seed::{prelude::*, *}; use seed::{prelude::*, *};
use crate::shared::ToNode; use crate::shared::ToNode;
use crate::Msg; use crate::{FieldId, Msg};
#[derive(Clone, Debug, PartialEq)]
pub enum StyledSelectChange {
Text(String),
DropDownVisibility(bool),
Changed(u32),
}
#[derive(Copy, Clone, Debug, PartialEq)] #[derive(Copy, Clone, Debug, PartialEq)]
pub enum Variant { pub enum Variant {
@ -22,13 +29,17 @@ pub trait SelectOption {
fn into_option(self) -> Node<Msg>; fn into_option(self) -> Node<Msg>;
fn into_value(self) -> Node<Msg>; fn into_value(self) -> Node<Msg>;
fn match_text_filter(&self, text_filter: &str) -> bool;
fn to_value(&self) -> u32;
} }
pub struct StyledSelect<Child> pub struct StyledSelect<Child>
where where
Child: SelectOption + PartialEq, Child: SelectOption + PartialEq,
{ {
pub on_change: EventHandler<Msg>, pub id: FieldId,
pub variant: Variant, pub variant: Variant,
pub dropdown_width: Option<usize>, pub dropdown_width: Option<usize>,
pub name: Option<String>, pub name: Option<String>,
@ -37,7 +48,9 @@ where
pub is_multi: bool, pub is_multi: bool,
pub allow_clear: bool, pub allow_clear: bool,
pub options: Vec<Child>, pub options: Vec<Child>,
pub selected: Option<Child>, pub selected: Vec<Child>,
pub text_filter: String,
pub opened: bool,
} }
impl<Child> ToNode for StyledSelect<Child> impl<Child> ToNode for StyledSelect<Child>
@ -54,7 +67,7 @@ where
Child: SelectOption + PartialEq, Child: SelectOption + PartialEq,
{ {
let StyledSelect { let StyledSelect {
on_change, id,
variant, variant,
dropdown_width, dropdown_width,
name, name,
@ -64,8 +77,22 @@ where
allow_clear, allow_clear,
options, options,
selected, selected,
text_filter,
opened,
} = values; } = values;
let on_text = input_ev(Ev::KeyUp, |value| {
Msg::StyledSelectChanged(
FieldId::IssueTypeEditModalTop,
StyledSelectChange::Text(value),
)
});
let field_id = id.clone();
let visibility_handler = mouse_ev(Ev::Click, move |_| {
Msg::StyledSelectChanged(field_id, StyledSelectChange::DropDownVisibility(!opened))
});
let dropdown_style = dropdown_width let dropdown_style = dropdown_width
.map(|n| format!("width: {}px", n)) .map(|n| format!("width: {}px", n))
.unwrap_or_default(); .unwrap_or_default();
@ -76,40 +103,26 @@ where
let children: Vec<Node<Msg>> = options let children: Vec<Node<Msg>> = options
.into_iter() .into_iter()
.filter(|o| Some(o) != selected.as_ref()) .filter(|o| !selected.contains(&o) && o.match_text_filter(text_filter.as_str()))
.map(|child| render_option(child.into_option())) .map(|child| {
let value = child.to_value();
let field_id = id.clone();
let on_change = mouse_ev(Ev::Click, move |_| {
Msg::StyledSelectChanged(field_id, StyledSelectChange::Changed(value))
});
div![
attrs![At::Class => "option"],
on_change,
visibility_handler.clone(),
child.into_option()
]
})
.collect(); .collect();
let value = selected let value = selected.into_iter().map(|m| render_value(m.into_value()));
.map(|m| render_value(m.into_value()))
.unwrap_or_else(|| empty![]);
let clear_icon = match allow_clear { let text_input = match opened {
true => crate::shared::styled_icon(crate::model::Icon::Close), true => seed::input![
false => empty![],
};
let option_list = if children.is_empty() {
seed::div![attrs![At::Class => "noOptions"], "No results"]
} else {
seed::div![
attrs![
At::Class => "options",
],
children
]
};
seed::div![
on_change.clone(),
attrs![At::Class => select_class.join(" ")],
div![
attrs![At::Class => format!("valueContainer {}", variant)],
value
],
div![
attrs![At::Class => "dropDown", At::Style => dropdown_style],
seed::input![
attrs![ attrs![
At::Name => name.unwrap_or_default(), At::Name => name.unwrap_or_default(),
At::Class => "dropDownInput", At::Class => "dropDownInput",
@ -117,16 +130,44 @@ where
At::Placeholder => "Search" At::Placeholder => "Search"
At::AutoFocus => true, At::AutoFocus => true,
], ],
on_change, on_text.clone(),
], ],
_ => empty![],
};
let clear_icon = match (opened, allow_clear) {
(true, true) => crate::shared::styled_icon(crate::model::Icon::Close),
_ => empty![],
};
let option_list = match (opened, children.is_empty()) {
(false, _) => empty![],
(_, true) => seed::div![attrs![At::Class => "noOptions"], "No results"],
_ => seed::div![attrs![ At::Class => "options" ], children],
};
seed::div![
attrs![At::Class => select_class.join(" ")],
div![
attrs![At::Class => format!("valueContainer {}", variant)],
visibility_handler,
value,
],
div![
attrs![At::Class => "dropDown", At::Style => dropdown_style],
text_input,
clear_icon, clear_icon,
option_list option_list
] ]
] ]
} }
fn render_option(content: Node<Msg>) -> Node<Msg> { fn render_option(
div![attrs![At::Class => "option"], content] content: Node<Msg>,
on_change: EventHandler<Msg>,
on_click: EventHandler<Msg>,
) -> Node<Msg> {
div![attrs![At::Class => "option"], on_change, on_click, content]
} }
fn render_value(mut content: Node<Msg>) -> Node<Msg> { fn render_value(mut content: Node<Msg>) -> Node<Msg> {

View File

@ -38,6 +38,27 @@ impl IssueType {
} }
} }
impl Into<u32> for IssueType {
fn into(self) -> u32 {
match self {
IssueType::Task => 1,
IssueType::Bug => 2,
IssueType::Story => 3,
}
}
}
impl Into<IssueType> for u32 {
fn into(self) -> IssueType {
match self {
1 => IssueType::Task,
2 => IssueType::Bug,
3 => IssueType::Story,
_ => IssueType::Task,
}
}
}
impl std::fmt::Display for IssueType { impl std::fmt::Display for IssueType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self { match self {