Improve edit
This commit is contained in:
parent
c3c8d1b66e
commit
a97a96e9ac
@ -8,6 +8,32 @@
|
|||||||
padding-right: 50px;
|
padding-right: 50px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.issueDetails > .content > .left > .styledTextArea {
|
||||||
|
margin: 18px 0 0 -8px;
|
||||||
|
height: 44px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.issueDetails > .content > .left > .styledTextArea > textarea {
|
||||||
|
padding: 7px 7px 8px;
|
||||||
|
line-height: 1.28;
|
||||||
|
resize: none;
|
||||||
|
transition: background 0.1s;
|
||||||
|
font-size: 24px;
|
||||||
|
font-family: var(--font-medium);
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.issueDetails > .content > .left > .styledTextArea > textarea:not(:focus) {
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
box-shadow: 0 0 0 1px transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.issueDetails > .content > .left > .styledTextArea > textarea:hover:not(:focus) {
|
||||||
|
background: var(--backgroundLight);
|
||||||
|
}
|
||||||
|
|
||||||
.issueDetails > .content > .right {
|
.issueDetails > .content > .right {
|
||||||
width: 35%;
|
width: 35%;
|
||||||
padding-top: 5px;
|
padding-top: 5px;
|
||||||
|
@ -63,31 +63,3 @@
|
|||||||
.styledField > * {
|
.styledField > * {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.styledTextArea {
|
|
||||||
display: inline-block;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.styledTextArea > textarea {
|
|
||||||
overflow-y: hidden;
|
|
||||||
width: 100%;
|
|
||||||
padding: 8px 12px 9px;
|
|
||||||
border-radius: 3px;
|
|
||||||
border: 1px solid var(--borderLightest);
|
|
||||||
color: var(--textDarkest);
|
|
||||||
background: var(--backgroundLightest);
|
|
||||||
font-family: var(--font-regular);
|
|
||||||
font-weight: normal;
|
|
||||||
font-size: 15px
|
|
||||||
}
|
|
||||||
|
|
||||||
.styledTextArea > textarea:focus {
|
|
||||||
background: #fff;
|
|
||||||
border: 1px solid var(--borderInputFocus);
|
|
||||||
box-shadow: 0 0 0 1px var(--borderInputFocus);
|
|
||||||
}
|
|
||||||
|
|
||||||
.styledTextArea > textarea.invalid:focus {
|
|
||||||
border: 1px solid var(--danger);
|
|
||||||
}
|
|
||||||
|
27
jirs-client/js/css/styledTextArea.css
Normal file
27
jirs-client/js/css/styledTextArea.css
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
.styledTextArea {
|
||||||
|
display: inline-block;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.styledTextArea > textarea {
|
||||||
|
overflow-y: hidden;
|
||||||
|
width: 100%;
|
||||||
|
padding: 8px 12px 9px;
|
||||||
|
border-radius: 3px;
|
||||||
|
border: 1px solid var(--borderLightest);
|
||||||
|
color: var(--textDarkest);
|
||||||
|
background: var(--backgroundLightest);
|
||||||
|
font-family: var(--font-regular);
|
||||||
|
font-weight: normal;
|
||||||
|
font-size: 15px
|
||||||
|
}
|
||||||
|
|
||||||
|
.styledTextArea > textarea:focus {
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid var(--borderInputFocus);
|
||||||
|
box-shadow: 0 0 0 1px var(--borderInputFocus);
|
||||||
|
}
|
||||||
|
|
||||||
|
.styledTextArea > textarea.invalid:focus {
|
||||||
|
border: 1px solid var(--danger);
|
||||||
|
}
|
@ -12,6 +12,7 @@
|
|||||||
@import "./css/styledButton.css";
|
@import "./css/styledButton.css";
|
||||||
@import "./css/styledInput.css";
|
@import "./css/styledInput.css";
|
||||||
@import "./css/styledModal.css";
|
@import "./css/styledModal.css";
|
||||||
|
@import "./css/styledTextArea.css";
|
||||||
@import "./css/styledForm.css";
|
@import "./css/styledForm.css";
|
||||||
@import "./css/app.css";
|
@import "./css/app.css";
|
||||||
@import "./css/issue.css";
|
@import "./css/issue.css";
|
||||||
|
@ -28,6 +28,15 @@ pub type AvatarFilterActive = bool;
|
|||||||
pub enum FieldId {
|
pub enum FieldId {
|
||||||
// edit issue
|
// edit issue
|
||||||
IssueTypeEditModalTop,
|
IssueTypeEditModalTop,
|
||||||
|
TitleIssueEditModal,
|
||||||
|
DescriptionIssueEditModal,
|
||||||
|
StatusIssueEditModal,
|
||||||
|
AssigneesIssueEditModal,
|
||||||
|
ReporterIssueEditModal,
|
||||||
|
PriorityIssueEditModal,
|
||||||
|
EstimateIssueEditModal,
|
||||||
|
TimeSpendIssueEditModal,
|
||||||
|
TimeRemainingIssueEditModal,
|
||||||
// project boards
|
// project boards
|
||||||
TextFilterBoard,
|
TextFilterBoard,
|
||||||
//
|
//
|
||||||
@ -53,6 +62,15 @@ impl std::fmt::Display for FieldId {
|
|||||||
FieldId::ReporterAddIssueModal => f.write_str("reporterAddIssueModal"),
|
FieldId::ReporterAddIssueModal => f.write_str("reporterAddIssueModal"),
|
||||||
FieldId::AssigneesAddIssueModal => f.write_str("assigneesAddIssueModal"),
|
FieldId::AssigneesAddIssueModal => f.write_str("assigneesAddIssueModal"),
|
||||||
FieldId::IssuePriorityAddIssueModal => f.write_str("issuePriorityAddIssueModal"),
|
FieldId::IssuePriorityAddIssueModal => f.write_str("issuePriorityAddIssueModal"),
|
||||||
|
FieldId::TitleIssueEditModal => f.write_str("titleIssueEditModal"),
|
||||||
|
FieldId::DescriptionIssueEditModal => f.write_str("descriptionIssueEditModal"),
|
||||||
|
FieldId::StatusIssueEditModal => f.write_str("statusIssueEditModal"),
|
||||||
|
FieldId::AssigneesIssueEditModal => f.write_str("assigneesIssueEditModal"),
|
||||||
|
FieldId::ReporterIssueEditModal => f.write_str("reporterIssueEditModal"),
|
||||||
|
FieldId::PriorityIssueEditModal => f.write_str("priorityIssueEditModal"),
|
||||||
|
FieldId::EstimateIssueEditModal => f.write_str("estimateIssueEditModal"),
|
||||||
|
FieldId::TimeSpendIssueEditModal => f.write_str("timeSpendIssueEditModal"),
|
||||||
|
FieldId::TimeRemainingIssueEditModal => f.write_str("timeRemainingIssueEditModal"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,29 +1,45 @@
|
|||||||
use seed::{prelude::*, *};
|
use seed::{prelude::*, *};
|
||||||
|
|
||||||
use jirs_data::{Issue, IssueType};
|
use jirs_data::{Issue, IssuePriority, IssueStatus, IssueType, ToVec, User};
|
||||||
|
|
||||||
use crate::model::{EditIssueModal, ModalType, Model};
|
use crate::model::{EditIssueModal, ModalType, Model};
|
||||||
|
use crate::shared::styled_avatar::StyledAvatar;
|
||||||
use crate::shared::styled_button::StyledButton;
|
use crate::shared::styled_button::StyledButton;
|
||||||
|
use crate::shared::styled_field::StyledField;
|
||||||
use crate::shared::styled_icon::{Icon, StyledIcon};
|
use crate::shared::styled_icon::{Icon, StyledIcon};
|
||||||
use crate::shared::styled_select::StyledSelect;
|
use crate::shared::styled_select::{SelectOption, StyledSelect};
|
||||||
|
use crate::shared::styled_textarea::StyledTextarea;
|
||||||
use crate::shared::ToNode;
|
use crate::shared::ToNode;
|
||||||
use crate::{FieldChange, FieldId, IssueId, Msg};
|
use crate::{FieldChange, FieldId, IssueId, Msg};
|
||||||
|
|
||||||
|
pub fn update(msg: &Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
||||||
|
let modal: &mut EditIssueModal = match model.modals.get_mut(0) {
|
||||||
|
Some(ModalType::EditIssue(_issue_id, modal)) => modal,
|
||||||
|
_ => return,
|
||||||
|
};
|
||||||
|
modal.top_type_state.update(msg, orders);
|
||||||
|
modal.status_state.update(msg, orders);
|
||||||
|
modal.reporter_state.update(msg, orders);
|
||||||
|
modal.assignees_state.update(msg, orders);
|
||||||
|
modal.priority_state.update(msg, orders);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn view(_model: &Model, issue: &Issue, modal: &EditIssueModal) -> Node<Msg> {
|
pub fn view(_model: &Model, issue: &Issue, modal: &EditIssueModal) -> Node<Msg> {
|
||||||
let issue_id = issue.id;
|
let issue_id = issue.id;
|
||||||
|
|
||||||
let issue_type_select = StyledSelect::build(FieldId::IssueTypeEditModalTop)
|
let issue_type_select = StyledSelect::build(FieldId::IssueTypeEditModalTop)
|
||||||
.dropdown_width(150)
|
.dropdown_width(150)
|
||||||
.name("type")
|
.name("type")
|
||||||
.text_filter(modal.top_select_filter.as_str())
|
.text_filter(modal.top_type_state.text_filter.as_str())
|
||||||
.opened(modal.top_select_opened)
|
.opened(modal.top_type_state.opened)
|
||||||
.valid(true)
|
.valid(true)
|
||||||
.options(vec![
|
.options(
|
||||||
IssueTypeOption(issue_id, IssueType::Story),
|
IssueType::ordered()
|
||||||
IssueTypeOption(issue_id, IssueType::Task),
|
.into_iter()
|
||||||
IssueTypeOption(issue_id, IssueType::Bug),
|
.map(|t| IssueTypeTopOption(issue_id, t))
|
||||||
])
|
.collect(),
|
||||||
.selected(vec![IssueTypeOption(issue_id, modal.value.clone())])
|
)
|
||||||
|
.selected(vec![IssueTypeTopOption(issue_id, modal.value.clone())])
|
||||||
.build()
|
.build()
|
||||||
.into_node();
|
.into_node();
|
||||||
|
|
||||||
@ -75,6 +91,36 @@ pub fn view(_model: &Model, issue: &Issue, modal: &EditIssueModal) -> Node<Msg>
|
|||||||
.build()
|
.build()
|
||||||
.into_node();
|
.into_node();
|
||||||
|
|
||||||
|
// left
|
||||||
|
let title = StyledTextarea::build()
|
||||||
|
.value(issue.title.as_str())
|
||||||
|
.add_class("textarea")
|
||||||
|
.max_height(48)
|
||||||
|
.height(0)
|
||||||
|
.build(FieldId::TitleIssueEditModal)
|
||||||
|
.into_node();
|
||||||
|
|
||||||
|
// right
|
||||||
|
let status = StyledSelect::build(FieldId::StatusIssueEditModal)
|
||||||
|
.name("status")
|
||||||
|
.opened(modal.status_state.opened)
|
||||||
|
.text_filter(modal.status_state.text_filter.as_str())
|
||||||
|
.options(
|
||||||
|
IssueStatus::ordered()
|
||||||
|
.into_iter()
|
||||||
|
.map(|opt| IssueStatusOption(issue_id, opt))
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
|
.selected(vec![IssueStatusOption(issue_id, issue.status.clone())])
|
||||||
|
.valid(true)
|
||||||
|
.build()
|
||||||
|
.into_node();
|
||||||
|
// let status_field = StyledField::build()
|
||||||
|
// .input(status)
|
||||||
|
// .label("Status")
|
||||||
|
// .build()
|
||||||
|
// .into_node();
|
||||||
|
|
||||||
div![
|
div![
|
||||||
attrs![At::Class => "issueDetails"],
|
attrs![At::Class => "issueDetails"],
|
||||||
div![
|
div![
|
||||||
@ -91,19 +137,19 @@ pub fn view(_model: &Model, issue: &Issue, modal: &EditIssueModal) -> Node<Msg>
|
|||||||
attrs![At::Class => "content"],
|
attrs![At::Class => "content"],
|
||||||
div![
|
div![
|
||||||
attrs![At::Class => "left"],
|
attrs![At::Class => "left"],
|
||||||
div![attrs![At::Class => "title"]],
|
title,
|
||||||
div![attrs![At::Class => "description"]],
|
div![attrs![At::Class => "description"]],
|
||||||
div![attrs![At::Class => "comments"]],
|
div![attrs![At::Class => "comments"]],
|
||||||
],
|
],
|
||||||
div![attrs![At::Class => "right"]],
|
div![attrs![At::Class => "right"], status],
|
||||||
],
|
],
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialOrd, PartialEq, Debug)]
|
#[derive(PartialOrd, PartialEq, Debug)]
|
||||||
pub struct IssueTypeOption(pub IssueId, pub IssueType);
|
pub struct IssueTypeTopOption(pub IssueId, pub IssueType);
|
||||||
|
|
||||||
impl crate::shared::styled_select::SelectOption for IssueTypeOption {
|
impl SelectOption for IssueTypeTopOption {
|
||||||
fn into_option(self) -> Node<Msg> {
|
fn into_option(self) -> Node<Msg> {
|
||||||
let name = self.1.to_label().to_owned();
|
let name = self.1.to_label().to_owned();
|
||||||
|
|
||||||
@ -113,9 +159,9 @@ impl crate::shared::styled_select::SelectOption for IssueTypeOption {
|
|||||||
.into_node();
|
.into_node();
|
||||||
|
|
||||||
div![
|
div![
|
||||||
attrs![At::Class => "type"],
|
attrs![At::Class => "optionItem"],
|
||||||
icon,
|
icon,
|
||||||
div![attrs![At::Class => "typeLabel"], name]
|
div![attrs![At::Class => "optionLabel typeLabel"], name]
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -142,3 +188,173 @@ impl crate::shared::styled_select::SelectOption for IssueTypeOption {
|
|||||||
self.1.clone().into()
|
self.1.clone().into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/////
|
||||||
|
#[derive(PartialOrd, PartialEq, Debug)]
|
||||||
|
pub struct IssueStatusOption(pub IssueId, pub IssueStatus);
|
||||||
|
|
||||||
|
impl SelectOption for IssueStatusOption {
|
||||||
|
fn into_option(self) -> Node<Msg> {
|
||||||
|
let name = self.1.to_label().to_owned();
|
||||||
|
|
||||||
|
div![
|
||||||
|
attrs![At::Class => "type optionItem"],
|
||||||
|
div![attrs![At::Class => "typeLabel optionLabel"], name]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn into_value(self) -> Node<Msg> {
|
||||||
|
let name = self.1.to_label().to_owned();
|
||||||
|
|
||||||
|
div![
|
||||||
|
attrs![At::Class => "selectItem"],
|
||||||
|
div![attrs![At::Class => "selectItemLabel"], name]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn match_text_filter(&self, text_filter: &str) -> bool {
|
||||||
|
format!("{}", self.0)
|
||||||
|
.to_lowercase()
|
||||||
|
.contains(&text_filter.to_lowercase())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_value(&self) -> u32 {
|
||||||
|
self.1.clone().into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialOrd, PartialEq, Debug)]
|
||||||
|
pub struct IssueTypeOption(pub IssueType);
|
||||||
|
|
||||||
|
impl SelectOption for IssueTypeOption {
|
||||||
|
fn into_option(self) -> Node<Msg> {
|
||||||
|
let name = self.0.to_label().to_owned();
|
||||||
|
|
||||||
|
let icon = StyledIcon::build(self.0.into())
|
||||||
|
.add_class("issueTypeIcon")
|
||||||
|
.build()
|
||||||
|
.into_node();
|
||||||
|
|
||||||
|
div![
|
||||||
|
attrs![At::Class => "type optionItem"],
|
||||||
|
icon,
|
||||||
|
div![attrs![At::Class => "typeLabel optionLabel"], name]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn into_value(self) -> Node<Msg> {
|
||||||
|
let name = self.0.to_label().to_owned();
|
||||||
|
|
||||||
|
let type_icon = StyledIcon::build(self.0.into()).build().into_node();
|
||||||
|
|
||||||
|
div![
|
||||||
|
attrs![At::Class => "selectItem"],
|
||||||
|
type_icon,
|
||||||
|
div![attrs![At::Class => "selectItemLabel"], name]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn match_text_filter(&self, text_filter: &str) -> bool {
|
||||||
|
self.0
|
||||||
|
.to_string()
|
||||||
|
.to_lowercase()
|
||||||
|
.contains(&text_filter.to_lowercase())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_value(&self) -> u32 {
|
||||||
|
self.0.clone().into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub struct IssuePriorityOption(IssuePriority);
|
||||||
|
|
||||||
|
impl SelectOption for IssuePriorityOption {
|
||||||
|
fn into_option(self) -> Node<Msg> {
|
||||||
|
let name = format!("{}", self.0);
|
||||||
|
|
||||||
|
let icon = StyledIcon::build(self.0.into())
|
||||||
|
.add_class("issuePriorityIcon")
|
||||||
|
.size(18)
|
||||||
|
.build()
|
||||||
|
.into_node();
|
||||||
|
|
||||||
|
div![
|
||||||
|
attrs![At::Class => format!("priority optionItem {}", name)],
|
||||||
|
icon,
|
||||||
|
div![attrs![At::Class => "priorityLabel optionLabel"], name]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn into_value(self) -> Node<Msg> {
|
||||||
|
let name = format!("{}", self.0);
|
||||||
|
|
||||||
|
let type_icon = StyledIcon::build(self.0.into()).build().into_node();
|
||||||
|
|
||||||
|
div![
|
||||||
|
attrs![At::Class => format!("selectItem priority {}", name)],
|
||||||
|
type_icon,
|
||||||
|
div![attrs![At::Class => "selectItemLabel"], name]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn match_text_filter(&self, text_filter: &str) -> bool {
|
||||||
|
self.0
|
||||||
|
.to_string()
|
||||||
|
.to_lowercase()
|
||||||
|
.contains(&text_filter.to_lowercase())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_value(&self) -> u32 {
|
||||||
|
self.0.clone().into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub struct UserOption<'opt>(pub &'opt User);
|
||||||
|
|
||||||
|
impl<'opt> UserOption<'opt> {
|
||||||
|
fn avatar_node(&self) -> Node<Msg> {
|
||||||
|
let user = self.0;
|
||||||
|
StyledAvatar::build()
|
||||||
|
.avatar_url(user.avatar_url.as_ref().cloned().unwrap_or_default())
|
||||||
|
.size(20)
|
||||||
|
.name(user.name.as_str())
|
||||||
|
.build()
|
||||||
|
.into_node()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'opt> SelectOption for UserOption<'opt> {
|
||||||
|
fn into_option(self) -> Node<Msg> {
|
||||||
|
let user = self.0;
|
||||||
|
|
||||||
|
let styled_avatar = self.avatar_node();
|
||||||
|
|
||||||
|
div![
|
||||||
|
attrs![At::Class => "user optionItem"],
|
||||||
|
styled_avatar,
|
||||||
|
div![attrs![At::Class => "typeLabel optionLabel"], user.name]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn into_value(self) -> Node<Msg> {
|
||||||
|
let user = self.0;
|
||||||
|
|
||||||
|
let styled_avatar = self.avatar_node();
|
||||||
|
|
||||||
|
div![
|
||||||
|
attrs![At::Class => "selectItem"],
|
||||||
|
styled_avatar,
|
||||||
|
div![attrs![At::Class => "selectItemLabel"], user.name]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn match_text_filter(&self, text_filter: &str) -> bool {
|
||||||
|
self.0.name.contains(text_filter)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_value(&self) -> u32 {
|
||||||
|
self.0.id as u32
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -5,7 +5,7 @@ use jirs_data::{Issue, IssueType, UpdateIssuePayload};
|
|||||||
use crate::api::send_ws_msg;
|
use crate::api::send_ws_msg;
|
||||||
use crate::model::{AddIssueModal, EditIssueModal, ModalType, Page};
|
use crate::model::{AddIssueModal, EditIssueModal, ModalType, Page};
|
||||||
use crate::shared::styled_modal::{StyledModal, Variant as ModalVariant};
|
use crate::shared::styled_modal::{StyledModal, Variant as ModalVariant};
|
||||||
use crate::shared::styled_select::StyledSelectChange;
|
use crate::shared::styled_select::{StyledSelectChange, StyledSelectState};
|
||||||
use crate::shared::{find_issue, ToNode};
|
use crate::shared::{find_issue, ToNode};
|
||||||
use crate::{model, FieldChange, FieldId, Msg};
|
use crate::{model, FieldChange, FieldId, Msg};
|
||||||
|
|
||||||
@ -43,10 +43,13 @@ pub fn update(msg: &Msg, model: &mut model::Model, orders: &mut impl Orders<Msg>
|
|||||||
*issue_id,
|
*issue_id,
|
||||||
EditIssueModal {
|
EditIssueModal {
|
||||||
id: *issue_id,
|
id: *issue_id,
|
||||||
top_select_opened: false,
|
|
||||||
top_select_filter: "".to_string(),
|
|
||||||
value,
|
value,
|
||||||
link_copied: false,
|
link_copied: false,
|
||||||
|
top_type_state: StyledSelectState::new(FieldId::IssueTypeEditModalTop),
|
||||||
|
status_state: StyledSelectState::new(FieldId::StatusIssueEditModal),
|
||||||
|
reporter_state: StyledSelectState::new(FieldId::ReporterIssueEditModal),
|
||||||
|
assignees_state: StyledSelectState::new(FieldId::AssigneesIssueEditModal),
|
||||||
|
priority_state: StyledSelectState::new(FieldId::PriorityIssueEditModal),
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
@ -59,13 +62,13 @@ pub fn update(msg: &Msg, model: &mut model::Model, orders: &mut impl Orders<Msg>
|
|||||||
Msg::StyledSelectChanged(FieldId::IssueTypeEditModalTop, change) => {
|
Msg::StyledSelectChanged(FieldId::IssueTypeEditModalTop, change) => {
|
||||||
match (change, model.modals.last_mut()) {
|
match (change, model.modals.last_mut()) {
|
||||||
(StyledSelectChange::Text(ref text), Some(ModalType::EditIssue(_, modal))) => {
|
(StyledSelectChange::Text(ref text), Some(ModalType::EditIssue(_, modal))) => {
|
||||||
modal.top_select_filter = text.clone();
|
modal.top_type_state.text_filter = text.clone();
|
||||||
}
|
}
|
||||||
(
|
(
|
||||||
StyledSelectChange::DropDownVisibility(flag),
|
StyledSelectChange::DropDownVisibility(flag),
|
||||||
Some(ModalType::EditIssue(_, modal)),
|
Some(ModalType::EditIssue(_, modal)),
|
||||||
) => {
|
) => {
|
||||||
modal.top_select_opened = *flag;
|
modal.top_type_state.opened = *flag;
|
||||||
}
|
}
|
||||||
(
|
(
|
||||||
StyledSelectChange::Changed(value),
|
StyledSelectChange::Changed(value),
|
||||||
@ -107,6 +110,7 @@ pub fn update(msg: &Msg, model: &mut model::Model, orders: &mut impl Orders<Msg>
|
|||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
add_issue::update(msg, model, orders);
|
add_issue::update(msg, model, orders);
|
||||||
|
issue_details::update(msg, model, orders);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn view(model: &model::Model) -> Node<Msg> {
|
pub fn view(model: &model::Model) -> Node<Msg> {
|
||||||
|
@ -20,10 +20,13 @@ pub enum ModalType {
|
|||||||
#[derive(Clone, Debug, PartialOrd, PartialEq, Hash)]
|
#[derive(Clone, Debug, PartialOrd, PartialEq, Hash)]
|
||||||
pub struct EditIssueModal {
|
pub struct EditIssueModal {
|
||||||
pub id: i32,
|
pub id: i32,
|
||||||
pub top_select_opened: bool,
|
|
||||||
pub top_select_filter: String,
|
|
||||||
pub value: IssueType,
|
pub value: IssueType,
|
||||||
pub link_copied: bool,
|
pub link_copied: bool,
|
||||||
|
pub top_type_state: StyledSelectState,
|
||||||
|
pub status_state: StyledSelectState,
|
||||||
|
pub reporter_state: StyledSelectState,
|
||||||
|
pub assignees_state: StyledSelectState,
|
||||||
|
pub priority_state: StyledSelectState,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialOrd, PartialEq, Hash)]
|
#[derive(Clone, Debug, PartialOrd, PartialEq, Hash)]
|
||||||
|
@ -59,7 +59,7 @@ pub fn update(msg: Msg, model: &mut crate::model::Model, orders: &mut impl Order
|
|||||||
})
|
})
|
||||||
.last();
|
.last();
|
||||||
if let Some(m) = modal {
|
if let Some(m) = modal {
|
||||||
m.top_select_filter = text;
|
m.top_type_state.text_filter = text;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Msg::InputChanged(FieldId::TextFilterBoard, text) => {
|
Msg::InputChanged(FieldId::TextFilterBoard, text) => {
|
||||||
|
@ -66,6 +66,7 @@ pub fn render(values: StyledField) -> Node<Msg> {
|
|||||||
Some(s) => div![attrs![At::Class => "styledTip"], s],
|
Some(s) => div![attrs![At::Class => "styledTip"], s],
|
||||||
_ => empty![],
|
_ => empty![],
|
||||||
};
|
};
|
||||||
|
|
||||||
div![
|
div![
|
||||||
attrs![At::Class => "styledField"],
|
attrs![At::Class => "styledField"],
|
||||||
seed::label![attrs![At::Class => "styledLabel"], label],
|
seed::label![attrs![At::Class => "styledLabel"], label],
|
||||||
|
@ -7,6 +7,9 @@ use crate::{FieldId, Msg};
|
|||||||
pub struct StyledTextarea {
|
pub struct StyledTextarea {
|
||||||
id: FieldId,
|
id: FieldId,
|
||||||
height: usize,
|
height: usize,
|
||||||
|
max_height: usize,
|
||||||
|
value: String,
|
||||||
|
class_list: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToNode for StyledTextarea {
|
impl ToNode for StyledTextarea {
|
||||||
@ -24,19 +27,51 @@ impl StyledTextarea {
|
|||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct StyledTextareaBuilder {
|
pub struct StyledTextareaBuilder {
|
||||||
height: Option<usize>,
|
height: Option<usize>,
|
||||||
|
max_height: Option<usize>,
|
||||||
on_change: Option<EventHandler<Msg>>,
|
on_change: Option<EventHandler<Msg>>,
|
||||||
|
value: String,
|
||||||
|
class_list: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StyledTextareaBuilder {
|
impl StyledTextareaBuilder {
|
||||||
|
#[inline]
|
||||||
pub fn height(mut self, height: usize) -> Self {
|
pub fn height(mut self, height: usize) -> Self {
|
||||||
self.height = Some(height);
|
self.height = Some(height);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn max_height(mut self, height: usize) -> Self {
|
||||||
|
self.max_height = Some(height);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn value<S>(mut self, value: S) -> Self
|
||||||
|
where
|
||||||
|
S: Into<String>,
|
||||||
|
{
|
||||||
|
self.value = value.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn add_class<S>(mut self, value: S) -> Self
|
||||||
|
where
|
||||||
|
S: Into<String>,
|
||||||
|
{
|
||||||
|
self.class_list.push(value.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
pub fn build(self, id: FieldId) -> StyledTextarea {
|
pub fn build(self, id: FieldId) -> StyledTextarea {
|
||||||
StyledTextarea {
|
StyledTextarea {
|
||||||
id,
|
id,
|
||||||
|
value: self.value,
|
||||||
height: self.height.unwrap_or(110),
|
height: self.height.unwrap_or(110),
|
||||||
|
class_list: self.class_list,
|
||||||
|
max_height: self.max_height.unwrap_or_default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -55,9 +90,20 @@ const ADDITIONAL_HEIGHT: f64 = PADDING_TOP_BOTTOM + BORDER_TOP_BOTTOM;
|
|||||||
// * 17 is padding top + bottom
|
// * 17 is padding top + bottom
|
||||||
// * 2 is border top + bottom
|
// * 2 is border top + bottom
|
||||||
pub fn render(values: StyledTextarea) -> Node<Msg> {
|
pub fn render(values: StyledTextarea) -> Node<Msg> {
|
||||||
let StyledTextarea { id, height } = values;
|
let StyledTextarea {
|
||||||
|
id,
|
||||||
|
height,
|
||||||
|
max_height,
|
||||||
|
value,
|
||||||
|
mut class_list,
|
||||||
|
} = values;
|
||||||
let mut style_list = vec![];
|
let mut style_list = vec![];
|
||||||
style_list.push(format!("min-height: {}px", height));
|
if height > 0 {
|
||||||
|
style_list.push(format!("min-height: {}px", height));
|
||||||
|
}
|
||||||
|
if max_height > 0 {
|
||||||
|
style_list.push(format!("max-height: {}px", max_height));
|
||||||
|
}
|
||||||
|
|
||||||
let mut handlers = vec![];
|
let mut handlers = vec![];
|
||||||
|
|
||||||
@ -88,15 +134,18 @@ pub fn render(values: StyledTextarea) -> Node<Msg> {
|
|||||||
let text_input_handler = input_ev(Ev::KeyUp, move |value| Msg::InputChanged(id, value));
|
let text_input_handler = input_ev(Ev::KeyUp, move |value| Msg::InputChanged(id, value));
|
||||||
handlers.push(text_input_handler);
|
handlers.push(text_input_handler);
|
||||||
|
|
||||||
|
class_list.push("textAreaInput".to_string());
|
||||||
|
|
||||||
div![
|
div![
|
||||||
attrs![At::Class => "styledTextArea"],
|
attrs![At::Class => "styledTextArea"],
|
||||||
div![attrs![At::Class => "textAreaHeading"]],
|
div![attrs![At::Class => "textAreaHeading"]],
|
||||||
textarea![
|
textarea![
|
||||||
attrs![
|
attrs![
|
||||||
At::Class => "textAreaInput";
|
At::Class => class_list.join(" ");
|
||||||
At::ContentEditable => "true";
|
At::ContentEditable => "true";
|
||||||
At::Style => style_list.join(";");
|
At::Style => style_list.join(";");
|
||||||
],
|
],
|
||||||
|
value,
|
||||||
handlers,
|
handlers,
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
use seed::*;
|
|
||||||
|
|
||||||
use jirs_data::*;
|
use jirs_data::*;
|
||||||
|
|
||||||
use crate::api::send_ws_msg;
|
use crate::api::send_ws_msg;
|
||||||
|
@ -98,6 +98,17 @@ impl Default for IssueStatus {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Into<u32> for IssueStatus {
|
||||||
|
fn into(self) -> u32 {
|
||||||
|
match self {
|
||||||
|
IssueStatus::Backlog => 0,
|
||||||
|
IssueStatus::Selected => 1,
|
||||||
|
IssueStatus::InProgress => 2,
|
||||||
|
IssueStatus::Done => 3,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl FromStr for IssueStatus {
|
impl FromStr for IssueStatus {
|
||||||
type Err = String;
|
type Err = String;
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
|
||||||
import { color, font } from '../../../../shared/utils/styles';
|
import { color, font } from '../../../../shared/utils/styles';
|
||||||
import { Textarea } from '../../../../shared/components';
|
import { Textarea } from '../../../../shared/components';
|
||||||
|
|
||||||
export const TitleTextarea = styled(Textarea)`
|
export const TitleTextarea = styled(Textarea)`
|
||||||
margin: 18px 0 0 -8px;
|
margin: 18px 0 0 -8px;
|
||||||
@ -17,9 +17,10 @@ export const TitleTextarea = styled(Textarea)`
|
|||||||
box-shadow: 0 0 0 1px transparent;
|
box-shadow: 0 0 0 1px transparent;
|
||||||
transition: background 0.1s;
|
transition: background 0.1s;
|
||||||
font-size: 24px
|
font-size: 24px
|
||||||
${ font.medium };font-weight: normal;
|
${font.medium};
|
||||||
|
font-weight: normal;
|
||||||
&:hover:not(:focus) {
|
&:hover:not(:focus) {
|
||||||
background: ${ color.backgroundLight };
|
background: ${color.backgroundLight};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
Loading…
Reference in New Issue
Block a user