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 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>),
|
||||||
|
@ -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)]
|
||||||
|
@ -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();
|
||||||
|
|
||||||
|
@ -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> {
|
||||||
|
@ -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 {
|
||||||
|
Loading…
Reference in New Issue
Block a user