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

View File

@ -9,9 +9,17 @@ use crate::{IssueId, UserId, HOST_URL};
pub type ProjectId = i32;
#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialOrd, PartialEq)]
#[derive(Serialize, Deserialize, Clone, Debug, PartialOrd, PartialEq)]
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)]

View File

@ -2,14 +2,14 @@ use seed::{prelude::*, *};
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::styled_avatar::StyledAvatar;
use crate::shared::styled_button::{StyledButton, Variant as ButtonVariant};
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::{IssueId, Msg};
use crate::{FieldId, IssueId, Msg};
pub fn update(msg: Msg, model: &mut crate::model::Model, orders: &mut impl Orders<Msg>) {
match msg {
@ -28,7 +28,18 @@ pub fn update(msg: Msg, model: &mut crate::model::Model, orders: &mut impl Order
orders
.skip()
.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 => {
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 :("),
}
}
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) => {
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> {
let modal = match model.modal {
Some(ModalType::EditIssue(issue_id)) => {
if let Some(issue) = find_issue(model, issue_id) {
let details = issue_details(model, issue);
let modal = match &model.modal {
Some(ModalType::EditIssue(issue_id, modal)) => {
if let Some(issue) = find_issue(model, *issue_id) {
let details = issue_details(model, issue, &modal);
let modal = Modal {
variant: ModalVariant::Center,
width: 1040,
@ -428,16 +456,30 @@ impl crate::shared::styled_select::SelectOption for IssueTypeOption {
}
.into_node()
}
fn match_text_filter(&self, text_filter: &str) -> bool {
self.1
.to_string()
.to_lowercase()
.contains(&text_filter.to_lowercase())
}
fn to_value(&self) -> u32 {
self.1.clone().into()
}
}
fn issue_details(_model: &Model, issue: &Issue) -> Node<Msg> {
fn issue_details(_model: &Model, issue: &Issue, modal: &EditIssueModal) -> Node<Msg> {
let issue_id = issue.id;
let issue_type_select = StyledSelect {
on_change: mouse_ev(Ev::Click, |_| Msg::NoOp),
id: FieldId::IssueTypeEditModalTop,
variant: crate::shared::styled_select::Variant::Empty,
dropdown_width: Some(150),
name: Some("type".to_string()),
placeholder: None,
text_filter: modal.top_select_filter.clone(),
opened: modal.top_select_opened,
valid: true,
is_multi: 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::Bug),
],
selected: Some(IssueTypeOption(issue_id, IssueType::Story)),
selected: vec![IssueTypeOption(issue_id, modal.value.clone())],
}
.into_node();

View File

@ -1,7 +1,14 @@
use seed::{prelude::*, *};
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)]
pub enum Variant {
@ -22,13 +29,17 @@ pub trait SelectOption {
fn into_option(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>
where
Child: SelectOption + PartialEq,
{
pub on_change: EventHandler<Msg>,
pub id: FieldId,
pub variant: Variant,
pub dropdown_width: Option<usize>,
pub name: Option<String>,
@ -37,7 +48,9 @@ where
pub is_multi: bool,
pub allow_clear: bool,
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>
@ -54,7 +67,7 @@ where
Child: SelectOption + PartialEq,
{
let StyledSelect {
on_change,
id,
variant,
dropdown_width,
name,
@ -64,8 +77,22 @@ where
allow_clear,
options,
selected,
text_filter,
opened,
} = 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
.map(|n| format!("width: {}px", n))
.unwrap_or_default();
@ -76,57 +103,71 @@ where
let children: Vec<Node<Msg>> = options
.into_iter()
.filter(|o| Some(o) != selected.as_ref())
.map(|child| render_option(child.into_option()))
.filter(|o| !selected.contains(&o) && o.match_text_filter(text_filter.as_str()))
.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();
let value = selected
.map(|m| render_value(m.into_value()))
.unwrap_or_else(|| empty![]);
let value = selected.into_iter().map(|m| render_value(m.into_value()));
let clear_icon = match allow_clear {
true => crate::shared::styled_icon(crate::model::Icon::Close),
false => empty![],
let text_input = match opened {
true => seed::input![
attrs![
At::Name => name.unwrap_or_default(),
At::Class => "dropDownInput",
At::Type => "text"
At::Placeholder => "Search"
At::AutoFocus => true,
],
on_text.clone(),
],
_ => empty![],
};
let option_list = if children.is_empty() {
seed::div![attrs![At::Class => "noOptions"], "No results"]
} else {
seed::div![
attrs![
At::Class => "options",
],
children
]
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![
on_change.clone(),
attrs![At::Class => select_class.join(" ")],
div![
attrs![At::Class => format!("valueContainer {}", variant)],
value
visibility_handler,
value,
],
div![
attrs![At::Class => "dropDown", At::Style => dropdown_style],
seed::input![
attrs![
At::Name => name.unwrap_or_default(),
At::Class => "dropDownInput",
At::Type => "text"
At::Placeholder => "Search"
At::AutoFocus => true,
],
on_change,
],
text_input,
clear_icon,
option_list
]
]
}
fn render_option(content: Node<Msg>) -> Node<Msg> {
div![attrs![At::Class => "option"], content]
fn render_option(
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> {

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 {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {