Handle edit issue modal top dropdown
This commit is contained in:
parent
801f07648d
commit
5e81329378
@ -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>),
|
||||
|
@ -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)]
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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,40 +103,26 @@ 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 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![
|
||||
let text_input = match opened {
|
||||
true => seed::input![
|
||||
attrs![
|
||||
At::Name => name.unwrap_or_default(),
|
||||
At::Class => "dropDownInput",
|
||||
@ -117,16 +130,44 @@ where
|
||||
At::Placeholder => "Search"
|
||||
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,
|
||||
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> {
|
||||
|
@ -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 {
|
||||
|
Loading…
Reference in New Issue
Block a user