Add comments section. Refactor Field ids
This commit is contained in:
parent
1663d9a485
commit
7262411676
@ -31,18 +31,26 @@ version = "*"
|
|||||||
[dependencies.web-sys]
|
[dependencies.web-sys]
|
||||||
version = "0.3.22"
|
version = "0.3.22"
|
||||||
features = [
|
features = [
|
||||||
|
# elements
|
||||||
"Window",
|
"Window",
|
||||||
"DataTransfer",
|
|
||||||
"DragEvent",
|
|
||||||
"HtmlDivElement",
|
"HtmlDivElement",
|
||||||
"DomRect",
|
|
||||||
"HtmlDocument",
|
"HtmlDocument",
|
||||||
"Document",
|
"Document",
|
||||||
|
"HtmlBodyElement",
|
||||||
|
# types
|
||||||
|
"DataTransfer",
|
||||||
|
"DomRect",
|
||||||
"Selection",
|
"Selection",
|
||||||
"CssStyleDeclaration",
|
"CssStyleDeclaration",
|
||||||
"WebSocket",
|
"WebSocket",
|
||||||
"BinaryType",
|
"BinaryType",
|
||||||
"Blob",
|
"Blob",
|
||||||
"MessageEvent",
|
"AddEventListenerOptions",
|
||||||
|
# events
|
||||||
|
"EventTarget",
|
||||||
"ErrorEvent",
|
"ErrorEvent",
|
||||||
|
"MessageEvent",
|
||||||
|
"KeyEvent",
|
||||||
|
"KeyboardEvent",
|
||||||
|
"DragEvent",
|
||||||
]
|
]
|
||||||
|
@ -34,6 +34,71 @@
|
|||||||
background: var(--backgroundLight);
|
background: var(--backgroundLight);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.issueDetails > .content > .left > .comments {
|
||||||
|
padding-top: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.issueDetails > .content > .left > .comments > .title {
|
||||||
|
font-family: var(--font-medium);
|
||||||
|
font-weight: normal;
|
||||||
|
font-size: 15px
|
||||||
|
}
|
||||||
|
|
||||||
|
.issueDetails > .content > .left > .comments > .create {
|
||||||
|
position: relative;
|
||||||
|
margin-top: 25px;
|
||||||
|
font-size: 15px
|
||||||
|
}
|
||||||
|
|
||||||
|
.issueDetails > .content > .left > .comments > .create > .userAvatar {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.issueDetails > .content > .left > .comments > .create > .right {
|
||||||
|
padding-left: 44px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.issueDetails > .content > .left > .comments > .create > .right > .fakeTextArea {
|
||||||
|
padding: 12px 16px;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 1px solid var(--borderLightest);
|
||||||
|
color: var(--textLight);
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.issueDetails > .content > .left > .comments > .create > .right > .fakeTextArea:hover {
|
||||||
|
border: 1px solid var(--borderLight);
|
||||||
|
}
|
||||||
|
|
||||||
|
.issueDetails > .content > .left > .comments > .create > .right > .proTip {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding-top: 8px;
|
||||||
|
color: var(--textMedium);
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.issueDetails > .content > .left > .comments > .create > .right > .proTip > .strong {
|
||||||
|
padding-right: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.issueDetails > .content > .left > .comments > .create > .right > .proTip > .tipLetter {
|
||||||
|
position: relative;
|
||||||
|
top: 1px;
|
||||||
|
display: inline-block;
|
||||||
|
margin: 0 4px;
|
||||||
|
padding: 0 4px;
|
||||||
|
border-radius: 2px;
|
||||||
|
color: var(--textDarkest);
|
||||||
|
background: var(--backgroundMedium);
|
||||||
|
font-family: var(--font-bold);
|
||||||
|
font-weight: normal;
|
||||||
|
font-size: 12px
|
||||||
|
}
|
||||||
|
|
||||||
.issueDetails > .content > .right {
|
.issueDetails > .content > .right {
|
||||||
width: 35%;
|
width: 35%;
|
||||||
padding-top: 5px;
|
padding-top: 5px;
|
||||||
|
@ -197,58 +197,3 @@
|
|||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.styledSelect > .dropDown > .options > .option > .optionItem {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.styledSelect > .dropDown > .options > .option > .optionItem > .styledIcon {
|
|
||||||
font-size: 18px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.styledSelect > .dropDown > .options > .option > .optionItem > .optionLabel {
|
|
||||||
padding: 0 5px 0 7px;
|
|
||||||
font-size: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* issue priority */
|
|
||||||
|
|
||||||
.styledSelect > .dropDown > .options > .option > .optionItem.priority > .optionLabel {
|
|
||||||
padding: 0 5px 0 7px;
|
|
||||||
font-size: 15px;
|
|
||||||
text-transform: capitalize;
|
|
||||||
}
|
|
||||||
|
|
||||||
.styledSelect > .valueContainer > .selectItem.priority > .selectItemLabel {
|
|
||||||
text-transform: capitalize;
|
|
||||||
}
|
|
||||||
|
|
||||||
.styledSelect > .dropDown > .options > .option > .optionItem.priority > .styledIcon {
|
|
||||||
font-size: 18px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.styledSelect > .valueContainer > .selectItem.priority.highest > .styledIcon,
|
|
||||||
.styledSelect > .dropDown > .options > .option > .optionItem.priority.highest > .styledIcon {
|
|
||||||
color: var(--highest);
|
|
||||||
}
|
|
||||||
|
|
||||||
.styledSelect > .valueContainer > .selectItem.priority.high > .styledIcon,
|
|
||||||
.styledSelect > .dropDown > .options > .option > .optionItem.priority.high > .styledIcon {
|
|
||||||
color: var(--high);
|
|
||||||
}
|
|
||||||
|
|
||||||
.styledSelect > .valueContainer > .selectItem.priority.medium > .styledIcon,
|
|
||||||
.styledSelect > .dropDown > .options > .option > .optionItem.priority.medium > .styledIcon {
|
|
||||||
color: var(--medium);
|
|
||||||
}
|
|
||||||
|
|
||||||
.styledSelect > .valueContainer > .selectItem.priority.low > .styledIcon,
|
|
||||||
.styledSelect > .dropDown > .options > .option > .optionItem.priority.low > .styledIcon {
|
|
||||||
color: var(--low);
|
|
||||||
}
|
|
||||||
|
|
||||||
.styledSelect > .valueContainer > .selectItem.priority.lowest > .styledIcon,
|
|
||||||
.styledSelect > .dropDown > .options > .option > .optionItem.priority.lowest > .styledIcon {
|
|
||||||
color: var(--lowest);
|
|
||||||
}
|
|
||||||
|
67
jirs-client/js/css/styledSelectChild.css
Normal file
67
jirs-client/js/css/styledSelectChild.css
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
.selectItem {
|
||||||
|
padding: 4px 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selectItem.priority.highest > .styledIcon,
|
||||||
|
.optionItem.priority.highest > .styledIcon {
|
||||||
|
color: var(--highest);
|
||||||
|
}
|
||||||
|
|
||||||
|
.selectItem.priority.high > .styledIcon,
|
||||||
|
.optionItem.priority.high > .styledIcon {
|
||||||
|
color: var(--high);
|
||||||
|
}
|
||||||
|
|
||||||
|
.selectItem.priority.medium > .styledIcon,
|
||||||
|
.optionItem.priority.medium > .styledIcon {
|
||||||
|
color: var(--medium);
|
||||||
|
}
|
||||||
|
|
||||||
|
.selectItem.priority.low > .styledIcon,
|
||||||
|
.optionItem.priority.low > .styledIcon {
|
||||||
|
color: var(--low);
|
||||||
|
}
|
||||||
|
|
||||||
|
.selectItem.priority.lowest > .styledIcon,
|
||||||
|
.optionItem.priority.lowest > .styledIcon {
|
||||||
|
color: var(--lowest);
|
||||||
|
}
|
||||||
|
|
||||||
|
.selectItem.priority > .selectItemLabel {
|
||||||
|
text-transform: capitalize;
|
||||||
|
}
|
||||||
|
|
||||||
|
.optionItem {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.optionItem > .styledIcon {
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selectItem > .selectItemLabel,
|
||||||
|
.optionItem > .optionLabel {
|
||||||
|
padding: 0 5px 0 7px;
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.optionItem.priority > .optionLabel {
|
||||||
|
padding: 0 5px 0 7px;
|
||||||
|
font-size: 15px;
|
||||||
|
text-transform: capitalize;
|
||||||
|
}
|
||||||
|
|
||||||
|
.optionItem.priority > .styledIcon {
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* edit issue */
|
||||||
|
.topActions > .styledSelect > .valueContainer,
|
||||||
|
.topActions > .styledSelect > .valueContainer > .selectItem {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topActions .selectItem, .topActions .optionItem {
|
||||||
|
padding: 0 12px;
|
||||||
|
}
|
@ -9,6 +9,7 @@
|
|||||||
@import "./css/styledTooltip.css";
|
@import "./css/styledTooltip.css";
|
||||||
@import "./css/styledAvatar.css";
|
@import "./css/styledAvatar.css";
|
||||||
@import "./css/styledSelect.css";
|
@import "./css/styledSelect.css";
|
||||||
|
@import "./css/styledSelectChild.css";
|
||||||
@import "./css/styledButton.css";
|
@import "./css/styledButton.css";
|
||||||
@import "./css/styledInput.css";
|
@import "./css/styledInput.css";
|
||||||
@import "./css/styledModal.css";
|
@import "./css/styledModal.css";
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
use std::sync::RwLock;
|
use std::sync::RwLock;
|
||||||
|
|
||||||
use seed::fetch::FetchObject;
|
|
||||||
use seed::{prelude::*, *};
|
use seed::{prelude::*, *};
|
||||||
|
|
||||||
use jirs_data::{IssueStatus, WsMsg};
|
use jirs_data::*;
|
||||||
|
|
||||||
use crate::api::send_ws_msg;
|
use crate::api::send_ws_msg;
|
||||||
use crate::model::{ModalType, Model, Page};
|
use crate::model::{ModalType, Model, Page};
|
||||||
@ -21,70 +20,91 @@ mod register;
|
|||||||
mod shared;
|
mod shared;
|
||||||
mod ws;
|
mod ws;
|
||||||
|
|
||||||
pub type UserId = i32;
|
|
||||||
pub type IssueId = i32;
|
|
||||||
pub type AvatarFilterActive = bool;
|
pub type AvatarFilterActive = bool;
|
||||||
|
pub type AppType = App<Msg, Model, Node<Msg>>;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialOrd, PartialEq, Hash)]
|
||||||
|
pub enum EditIssueModalFieldId {
|
||||||
|
IssueType,
|
||||||
|
Title,
|
||||||
|
Description,
|
||||||
|
Status,
|
||||||
|
Assignees,
|
||||||
|
Reporter,
|
||||||
|
Priority,
|
||||||
|
Estimate,
|
||||||
|
TimeSpend,
|
||||||
|
TimeRemaining,
|
||||||
|
// comment
|
||||||
|
CommentBody,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialOrd, PartialEq, Hash)]
|
||||||
|
pub enum AddIssueModalFieldId {
|
||||||
|
IssueType,
|
||||||
|
Summary,
|
||||||
|
Description,
|
||||||
|
Reporter,
|
||||||
|
Assignees,
|
||||||
|
Priority,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialOrd, PartialEq, Hash)]
|
#[derive(Clone, Debug, PartialOrd, PartialEq, Hash)]
|
||||||
pub enum FieldId {
|
pub enum FieldId {
|
||||||
// edit issue
|
// issue
|
||||||
IssueTypeEditModalTop,
|
AddIssueModal(AddIssueModalFieldId),
|
||||||
TitleIssueEditModal,
|
EditIssueModal(EditIssueModalFieldId),
|
||||||
DescriptionIssueEditModal,
|
|
||||||
StatusIssueEditModal,
|
|
||||||
AssigneesIssueEditModal,
|
|
||||||
ReporterIssueEditModal,
|
|
||||||
PriorityIssueEditModal,
|
|
||||||
EstimateIssueEditModal,
|
|
||||||
TimeSpendIssueEditModal,
|
|
||||||
TimeRemainingIssueEditModal,
|
|
||||||
// project boards
|
// project boards
|
||||||
TextFilterBoard,
|
TextFilterBoard,
|
||||||
//
|
|
||||||
CopyButtonLabel,
|
CopyButtonLabel,
|
||||||
// add issue
|
|
||||||
IssueTypeAddIssueModal,
|
|
||||||
SummaryAddIssueModal,
|
|
||||||
DescriptionAddIssueModal,
|
|
||||||
ReporterAddIssueModal,
|
|
||||||
AssigneesAddIssueModal,
|
|
||||||
IssuePriorityAddIssueModal,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for FieldId {
|
impl std::fmt::Display for FieldId {
|
||||||
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 {
|
||||||
FieldId::IssueTypeEditModalTop => f.write_str("issueTypeEditModalTop"),
|
FieldId::EditIssueModal(sub) => match sub {
|
||||||
|
EditIssueModalFieldId::IssueType => f.write_str("issueTypeEditModalTop"),
|
||||||
|
EditIssueModalFieldId::Title => f.write_str("titleIssueEditModal"),
|
||||||
|
EditIssueModalFieldId::Description => f.write_str("descriptionIssueEditModal"),
|
||||||
|
EditIssueModalFieldId::Status => f.write_str("statusIssueEditModal"),
|
||||||
|
EditIssueModalFieldId::Assignees => f.write_str("assigneesIssueEditModal"),
|
||||||
|
EditIssueModalFieldId::Reporter => f.write_str("reporterIssueEditModal"),
|
||||||
|
EditIssueModalFieldId::Priority => f.write_str("priorityIssueEditModal"),
|
||||||
|
EditIssueModalFieldId::Estimate => f.write_str("estimateIssueEditModal"),
|
||||||
|
EditIssueModalFieldId::TimeSpend => f.write_str("timeSpendIssueEditModal"),
|
||||||
|
EditIssueModalFieldId::TimeRemaining => f.write_str("timeRemainingIssueEditModal"),
|
||||||
|
EditIssueModalFieldId::CommentBody => f.write_str("editIssue-commentBody"),
|
||||||
|
},
|
||||||
|
FieldId::AddIssueModal(sub) => match sub {
|
||||||
|
AddIssueModalFieldId::IssueType => f.write_str("issueTypeAddIssueModal"),
|
||||||
|
AddIssueModalFieldId::Summary => f.write_str("summaryAddIssueModal"),
|
||||||
|
AddIssueModalFieldId::Description => f.write_str("descriptionAddIssueModal"),
|
||||||
|
AddIssueModalFieldId::Reporter => f.write_str("reporterAddIssueModal"),
|
||||||
|
AddIssueModalFieldId::Assignees => f.write_str("assigneesAddIssueModal"),
|
||||||
|
AddIssueModalFieldId::Priority => f.write_str("issuePriorityAddIssueModal"),
|
||||||
|
},
|
||||||
FieldId::TextFilterBoard => f.write_str("textFilterBoard"),
|
FieldId::TextFilterBoard => f.write_str("textFilterBoard"),
|
||||||
FieldId::CopyButtonLabel => f.write_str("copyButtonLabel"),
|
FieldId::CopyButtonLabel => f.write_str("copyButtonLabel"),
|
||||||
FieldId::IssueTypeAddIssueModal => f.write_str("issueTypeAddIssueModal"),
|
|
||||||
FieldId::SummaryAddIssueModal => f.write_str("summaryAddIssueModal"),
|
|
||||||
FieldId::DescriptionAddIssueModal => f.write_str("descriptionAddIssueModal"),
|
|
||||||
FieldId::ReporterAddIssueModal => f.write_str("reporterAddIssueModal"),
|
|
||||||
FieldId::AssigneesAddIssueModal => f.write_str("assigneesAddIssueModal"),
|
|
||||||
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"),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub enum FieldChange {
|
pub enum FieldChange {
|
||||||
LinkCopied(FieldId, bool),
|
LinkCopied(FieldId, bool),
|
||||||
TabChanged(FieldId, TabMode),
|
TabChanged(FieldId, TabMode),
|
||||||
|
ToggleCreateComment(FieldId, bool),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub enum Msg {
|
pub enum Msg {
|
||||||
NoOp,
|
NoOp,
|
||||||
|
GlobalKeyDown {
|
||||||
|
key: String,
|
||||||
|
shift: bool,
|
||||||
|
ctrl: bool,
|
||||||
|
alt: bool,
|
||||||
|
},
|
||||||
|
|
||||||
// Auth Token
|
// Auth Token
|
||||||
AuthTokenStored,
|
AuthTokenStored,
|
||||||
@ -93,7 +113,6 @@ pub enum Msg {
|
|||||||
StyledSelectChanged(FieldId, StyledSelectChange),
|
StyledSelectChanged(FieldId, StyledSelectChange),
|
||||||
|
|
||||||
ChangePage(model::Page),
|
ChangePage(model::Page),
|
||||||
CurrentProjectResult(FetchObject<String>),
|
|
||||||
InternalFailure(String),
|
InternalFailure(String),
|
||||||
ToggleAboutTooltip,
|
ToggleAboutTooltip,
|
||||||
|
|
||||||
@ -127,6 +146,9 @@ pub enum Msg {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn update(msg: Msg, model: &mut model::Model, orders: &mut impl Orders<Msg>) {
|
fn update(msg: Msg, model: &mut model::Model, orders: &mut impl Orders<Msg>) {
|
||||||
|
if msg == Msg::NoOp {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if cfg!(debug_assertions) {
|
if cfg!(debug_assertions) {
|
||||||
log!(msg);
|
log!(msg);
|
||||||
}
|
}
|
||||||
@ -227,6 +249,28 @@ pub fn render() {
|
|||||||
5000,
|
5000,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if let Some(body) = seed::html_document().body() {
|
||||||
|
use wasm_bindgen::JsCast;
|
||||||
|
|
||||||
|
let body = body.dyn_ref::<web_sys::HtmlBodyElement>().unwrap().clone();
|
||||||
|
let key_up_closure =
|
||||||
|
wasm_bindgen::closure::Closure::wrap(Box::new(|event: web_sys::KeyboardEvent| {
|
||||||
|
if let Some(Ok(app)) = unsafe { APP.as_mut().map(|app| app.write()) } {
|
||||||
|
let msg = Msg::GlobalKeyDown {
|
||||||
|
key: event.key(),
|
||||||
|
shift: event.shift_key(),
|
||||||
|
ctrl: event.ctrl_key(),
|
||||||
|
alt: event.alt_key(),
|
||||||
|
};
|
||||||
|
app.update(msg);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
as Box<dyn Fn(web_sys::KeyboardEvent)>);
|
||||||
|
body.add_event_listener_with_callback("keyup", key_up_closure.as_ref().unchecked_ref())
|
||||||
|
.unwrap();
|
||||||
|
key_up_closure.forget();
|
||||||
|
}
|
||||||
|
|
||||||
let app = seed::App::builder(update, view)
|
let app = seed::App::builder(update, view)
|
||||||
.routes(routes)
|
.routes(routes)
|
||||||
.build_and_start();
|
.build_and_start();
|
||||||
|
@ -1,21 +1,20 @@
|
|||||||
use seed::{prelude::*, *};
|
use seed::{prelude::*, *};
|
||||||
|
|
||||||
use jirs_data::{IssuePriority, IssueType, User};
|
use jirs_data::{IssuePriority, IssueType, ToVec};
|
||||||
|
|
||||||
use crate::api::send_ws_msg;
|
use crate::api::send_ws_msg;
|
||||||
use crate::model::{AddIssueModal, ModalType, Model};
|
use crate::model::{AddIssueModal, 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_field::StyledField;
|
||||||
use crate::shared::styled_form::StyledForm;
|
use crate::shared::styled_form::StyledForm;
|
||||||
use crate::shared::styled_icon::StyledIcon;
|
|
||||||
use crate::shared::styled_input::StyledInput;
|
use crate::shared::styled_input::StyledInput;
|
||||||
use crate::shared::styled_modal::{StyledModal, Variant as ModalVariant};
|
use crate::shared::styled_modal::{StyledModal, Variant as ModalVariant};
|
||||||
use crate::shared::styled_select::StyledSelect;
|
use crate::shared::styled_select::StyledSelect;
|
||||||
use crate::shared::styled_select::StyledSelectChange;
|
use crate::shared::styled_select::StyledSelectChange;
|
||||||
|
use crate::shared::styled_select_child::ToStyledSelectChild;
|
||||||
use crate::shared::styled_textarea::StyledTextarea;
|
use crate::shared::styled_textarea::StyledTextarea;
|
||||||
use crate::shared::ToNode;
|
use crate::shared::ToNode;
|
||||||
use crate::{FieldId, Msg};
|
use crate::{AddIssueModalFieldId, FieldId, 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>) {
|
||||||
let modal = model.modals.iter_mut().find(|modal| match modal {
|
let modal = model.modals.iter_mut().find(|modal| match modal {
|
||||||
@ -58,16 +57,16 @@ pub fn update(msg: &Msg, model: &mut crate::model::Model, orders: &mut impl Orde
|
|||||||
orders.skip().send_msg(Msg::ModalDropped);
|
orders.skip().send_msg(Msg::ModalDropped);
|
||||||
}
|
}
|
||||||
|
|
||||||
Msg::InputChanged(FieldId::DescriptionAddIssueModal, value) => {
|
Msg::InputChanged(FieldId::AddIssueModal(AddIssueModalFieldId::Description), value) => {
|
||||||
modal.description = Some(value.clone());
|
modal.description = Some(value.clone());
|
||||||
}
|
}
|
||||||
Msg::InputChanged(FieldId::SummaryAddIssueModal, value) => {
|
Msg::InputChanged(FieldId::AddIssueModal(AddIssueModalFieldId::Summary), value) => {
|
||||||
modal.title = value.clone();
|
modal.title = value.clone();
|
||||||
}
|
}
|
||||||
|
|
||||||
// IssueTypeAddIssueModal
|
// IssueTypeAddIssueModal
|
||||||
Msg::StyledSelectChanged(
|
Msg::StyledSelectChanged(
|
||||||
FieldId::IssueTypeAddIssueModal,
|
FieldId::AddIssueModal(AddIssueModalFieldId::IssueType),
|
||||||
StyledSelectChange::Changed(id),
|
StyledSelectChange::Changed(id),
|
||||||
) => {
|
) => {
|
||||||
modal.issue_type = (*id).into();
|
modal.issue_type = (*id).into();
|
||||||
@ -75,7 +74,7 @@ pub fn update(msg: &Msg, model: &mut crate::model::Model, orders: &mut impl Orde
|
|||||||
|
|
||||||
// ReporterAddIssueModal
|
// ReporterAddIssueModal
|
||||||
Msg::StyledSelectChanged(
|
Msg::StyledSelectChanged(
|
||||||
FieldId::ReporterAddIssueModal,
|
FieldId::AddIssueModal(AddIssueModalFieldId::Reporter),
|
||||||
StyledSelectChange::Changed(id),
|
StyledSelectChange::Changed(id),
|
||||||
) => {
|
) => {
|
||||||
modal.reporter_id = Some(*id as i32);
|
modal.reporter_id = Some(*id as i32);
|
||||||
@ -83,7 +82,7 @@ pub fn update(msg: &Msg, model: &mut crate::model::Model, orders: &mut impl Orde
|
|||||||
|
|
||||||
// AssigneesAddIssueModal
|
// AssigneesAddIssueModal
|
||||||
Msg::StyledSelectChanged(
|
Msg::StyledSelectChanged(
|
||||||
FieldId::AssigneesAddIssueModal,
|
FieldId::AddIssueModal(AddIssueModalFieldId::Assignees),
|
||||||
StyledSelectChange::Changed(id),
|
StyledSelectChange::Changed(id),
|
||||||
) => {
|
) => {
|
||||||
let id = *id as i32;
|
let id = *id as i32;
|
||||||
@ -92,7 +91,7 @@ pub fn update(msg: &Msg, model: &mut crate::model::Model, orders: &mut impl Orde
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Msg::StyledSelectChanged(
|
Msg::StyledSelectChanged(
|
||||||
FieldId::AssigneesAddIssueModal,
|
FieldId::AddIssueModal(AddIssueModalFieldId::Assignees),
|
||||||
StyledSelectChange::RemoveMulti(id),
|
StyledSelectChange::RemoveMulti(id),
|
||||||
) => {
|
) => {
|
||||||
let id = *id as i32;
|
let id = *id as i32;
|
||||||
@ -107,7 +106,7 @@ pub fn update(msg: &Msg, model: &mut crate::model::Model, orders: &mut impl Orde
|
|||||||
|
|
||||||
// IssuePriorityAddIssueModal
|
// IssuePriorityAddIssueModal
|
||||||
Msg::StyledSelectChanged(
|
Msg::StyledSelectChanged(
|
||||||
FieldId::IssuePriorityAddIssueModal,
|
FieldId::AddIssueModal(AddIssueModalFieldId::Priority),
|
||||||
StyledSelectChange::Changed(id),
|
StyledSelectChange::Changed(id),
|
||||||
) => {
|
) => {
|
||||||
modal.priority = (*id).into();
|
modal.priority = (*id).into();
|
||||||
@ -118,18 +117,19 @@ pub fn update(msg: &Msg, model: &mut crate::model::Model, orders: &mut impl Orde
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn view(model: &Model, modal: &AddIssueModal) -> Node<Msg> {
|
pub fn view(model: &Model, modal: &AddIssueModal) -> Node<Msg> {
|
||||||
let select_type = StyledSelect::build(FieldId::IssueTypeAddIssueModal)
|
let select_type = StyledSelect::build(FieldId::AddIssueModal(AddIssueModalFieldId::IssueType))
|
||||||
.name("type")
|
.name("type")
|
||||||
.normal()
|
.normal()
|
||||||
.text_filter(modal.type_state.text_filter.as_str())
|
.text_filter(modal.type_state.text_filter.as_str())
|
||||||
.opened(modal.type_state.opened)
|
.opened(modal.type_state.opened)
|
||||||
.valid(true)
|
.valid(true)
|
||||||
.options(vec![
|
.options(
|
||||||
IssueTypeOption(IssueType::Story),
|
IssueType::ordered()
|
||||||
IssueTypeOption(IssueType::Task),
|
.iter()
|
||||||
IssueTypeOption(IssueType::Bug),
|
.map(|t| t.to_select_child().name("type"))
|
||||||
])
|
.collect(),
|
||||||
.selected(vec![IssueTypeOption(modal.issue_type.clone())])
|
)
|
||||||
|
.selected(vec![modal.issue_type.to_select_child().name("type")])
|
||||||
.build()
|
.build()
|
||||||
.into_node();
|
.into_node();
|
||||||
let issue_type_field = StyledField::build()
|
let issue_type_field = StyledField::build()
|
||||||
@ -139,7 +139,7 @@ pub fn view(model: &Model, modal: &AddIssueModal) -> Node<Msg> {
|
|||||||
.build()
|
.build()
|
||||||
.into_node();
|
.into_node();
|
||||||
|
|
||||||
let short_summary = StyledInput::build(FieldId::SummaryAddIssueModal)
|
let short_summary = StyledInput::build(FieldId::AddIssueModal(AddIssueModalFieldId::Summary))
|
||||||
.valid(true)
|
.valid(true)
|
||||||
.build()
|
.build()
|
||||||
.into_node();
|
.into_node();
|
||||||
@ -152,7 +152,7 @@ pub fn view(model: &Model, modal: &AddIssueModal) -> Node<Msg> {
|
|||||||
|
|
||||||
let description = StyledTextarea::build()
|
let description = StyledTextarea::build()
|
||||||
.height(110)
|
.height(110)
|
||||||
.build(FieldId::DescriptionAddIssueModal)
|
.build(FieldId::AddIssueModal(AddIssueModalFieldId::Description))
|
||||||
.into_node();
|
.into_node();
|
||||||
let description_field = StyledField::build()
|
let description_field = StyledField::build()
|
||||||
.label("Description")
|
.label("Description")
|
||||||
@ -165,18 +165,24 @@ pub fn view(model: &Model, modal: &AddIssueModal) -> Node<Msg> {
|
|||||||
.reporter_id
|
.reporter_id
|
||||||
.or_else(|| model.user.as_ref().map(|u| u.id))
|
.or_else(|| model.user.as_ref().map(|u| u.id))
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
let reporter = StyledSelect::build(FieldId::ReporterAddIssueModal)
|
let reporter = StyledSelect::build(FieldId::AddIssueModal(AddIssueModalFieldId::Reporter))
|
||||||
.normal()
|
.normal()
|
||||||
.text_filter(modal.reporter_state.text_filter.as_str())
|
.text_filter(modal.reporter_state.text_filter.as_str())
|
||||||
.opened(modal.reporter_state.opened)
|
.opened(modal.reporter_state.opened)
|
||||||
.options(model.users.iter().map(|u| UserOption(u)).collect())
|
.options(
|
||||||
|
model
|
||||||
|
.users
|
||||||
|
.iter()
|
||||||
|
.map(|u| u.to_select_child().name("reporter"))
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
.selected(
|
.selected(
|
||||||
model
|
model
|
||||||
.users
|
.users
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|user| {
|
.filter_map(|user| {
|
||||||
if user.id == reporter_id {
|
if user.id == reporter_id {
|
||||||
Some(UserOption(user))
|
Some(user.to_select_child().name("reporter"))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
@ -193,19 +199,25 @@ pub fn view(model: &Model, modal: &AddIssueModal) -> Node<Msg> {
|
|||||||
.build()
|
.build()
|
||||||
.into_node();
|
.into_node();
|
||||||
|
|
||||||
let assignees = StyledSelect::build(FieldId::AssigneesAddIssueModal)
|
let assignees = StyledSelect::build(FieldId::AddIssueModal(AddIssueModalFieldId::Assignees))
|
||||||
.normal()
|
.normal()
|
||||||
.multi()
|
.multi()
|
||||||
.text_filter(modal.assignees_state.text_filter.as_str())
|
.text_filter(modal.assignees_state.text_filter.as_str())
|
||||||
.opened(modal.assignees_state.opened)
|
.opened(modal.assignees_state.opened)
|
||||||
.options(model.users.iter().map(|u| UserOption(u)).collect())
|
.options(
|
||||||
|
model
|
||||||
|
.users
|
||||||
|
.iter()
|
||||||
|
.map(|u| u.to_select_child().name("assignees"))
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
.selected(
|
.selected(
|
||||||
model
|
model
|
||||||
.users
|
.users
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|user| {
|
.filter_map(|user| {
|
||||||
if modal.user_ids.contains(&user.id) {
|
if modal.user_ids.contains(&user.id) {
|
||||||
Some(UserOption(user))
|
Some(user.to_select_child().name("assignees"))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
@ -222,20 +234,20 @@ pub fn view(model: &Model, modal: &AddIssueModal) -> Node<Msg> {
|
|||||||
.build()
|
.build()
|
||||||
.into_node();
|
.into_node();
|
||||||
|
|
||||||
let select_priority = StyledSelect::build(FieldId::IssuePriorityAddIssueModal)
|
let select_priority =
|
||||||
|
StyledSelect::build(FieldId::AddIssueModal(AddIssueModalFieldId::Priority))
|
||||||
.name("priority")
|
.name("priority")
|
||||||
.normal()
|
.normal()
|
||||||
.text_filter(modal.priority_state.text_filter.as_str())
|
.text_filter(modal.priority_state.text_filter.as_str())
|
||||||
.opened(modal.priority_state.opened)
|
.opened(modal.priority_state.opened)
|
||||||
.valid(true)
|
.valid(true)
|
||||||
.options(vec![
|
.options(
|
||||||
IssuePriorityOption(IssuePriority::Highest),
|
IssuePriority::ordered()
|
||||||
IssuePriorityOption(IssuePriority::High),
|
.iter()
|
||||||
IssuePriorityOption(IssuePriority::Medium),
|
.map(|p| p.to_select_child().name("priority"))
|
||||||
IssuePriorityOption(IssuePriority::Low),
|
.collect(),
|
||||||
IssuePriorityOption(IssuePriority::Lowest),
|
)
|
||||||
])
|
.selected(vec![modal.priority.to_select_child().name("priority")])
|
||||||
.selected(vec![IssuePriorityOption(modal.priority.clone())])
|
|
||||||
.build()
|
.build()
|
||||||
.into_node();
|
.into_node();
|
||||||
let issue_priority_field = StyledField::build()
|
let issue_priority_field = StyledField::build()
|
||||||
@ -292,139 +304,3 @@ pub fn view(model: &Model, modal: &AddIssueModal) -> Node<Msg> {
|
|||||||
.build()
|
.build()
|
||||||
.into_node()
|
.into_node()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialOrd, PartialEq, Debug)]
|
|
||||||
pub struct IssueTypeOption(pub IssueType);
|
|
||||||
|
|
||||||
impl crate::shared::styled_select::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 crate::shared::styled_select::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> crate::shared::styled_select::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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -8,12 +8,13 @@ use crate::shared::styled_avatar::StyledAvatar;
|
|||||||
use crate::shared::styled_button::StyledButton;
|
use crate::shared::styled_button::StyledButton;
|
||||||
use crate::shared::styled_editor::StyledEditor;
|
use crate::shared::styled_editor::StyledEditor;
|
||||||
use crate::shared::styled_field::StyledField;
|
use crate::shared::styled_field::StyledField;
|
||||||
use crate::shared::styled_icon::{Icon, StyledIcon};
|
use crate::shared::styled_icon::Icon;
|
||||||
use crate::shared::styled_input::StyledInput;
|
use crate::shared::styled_input::StyledInput;
|
||||||
use crate::shared::styled_select::{SelectOption, StyledSelect, StyledSelectChange};
|
use crate::shared::styled_select::{StyledSelect, StyledSelectChange};
|
||||||
|
use crate::shared::styled_select_child::ToStyledSelectChild;
|
||||||
use crate::shared::styled_textarea::StyledTextarea;
|
use crate::shared::styled_textarea::StyledTextarea;
|
||||||
use crate::shared::ToNode;
|
use crate::shared::ToNode;
|
||||||
use crate::{FieldChange, FieldId, IssueId, Msg};
|
use crate::{EditIssueModalFieldId, FieldChange, FieldId, Msg};
|
||||||
|
|
||||||
pub fn update(msg: &Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
pub fn update(msg: &Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
||||||
let modal: &mut EditIssueModal = match model.modals.get_mut(0) {
|
let modal: &mut EditIssueModal = match model.modals.get_mut(0) {
|
||||||
@ -31,35 +32,35 @@ pub fn update(msg: &Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
|||||||
modal.payload = issue.clone().into();
|
modal.payload = issue.clone().into();
|
||||||
}
|
}
|
||||||
Msg::StyledSelectChanged(
|
Msg::StyledSelectChanged(
|
||||||
FieldId::IssueTypeEditModalTop,
|
FieldId::EditIssueModal(EditIssueModalFieldId::IssueType),
|
||||||
StyledSelectChange::Changed(value),
|
StyledSelectChange::Changed(value),
|
||||||
) => {
|
) => {
|
||||||
modal.payload.issue_type = (*value).into();
|
modal.payload.issue_type = (*value).into();
|
||||||
send_ws_msg(WsMsg::IssueUpdateRequest(modal.id, modal.payload.clone()));
|
send_ws_msg(WsMsg::IssueUpdateRequest(modal.id, modal.payload.clone()));
|
||||||
}
|
}
|
||||||
Msg::StyledSelectChanged(
|
Msg::StyledSelectChanged(
|
||||||
FieldId::StatusIssueEditModal,
|
FieldId::EditIssueModal(EditIssueModalFieldId::Status),
|
||||||
StyledSelectChange::Changed(value),
|
StyledSelectChange::Changed(value),
|
||||||
) => {
|
) => {
|
||||||
modal.payload.status = (*value).into();
|
modal.payload.status = (*value).into();
|
||||||
send_ws_msg(WsMsg::IssueUpdateRequest(modal.id, modal.payload.clone()));
|
send_ws_msg(WsMsg::IssueUpdateRequest(modal.id, modal.payload.clone()));
|
||||||
}
|
}
|
||||||
Msg::StyledSelectChanged(
|
Msg::StyledSelectChanged(
|
||||||
FieldId::ReporterIssueEditModal,
|
FieldId::EditIssueModal(EditIssueModalFieldId::Reporter),
|
||||||
StyledSelectChange::Changed(value),
|
StyledSelectChange::Changed(value),
|
||||||
) => {
|
) => {
|
||||||
modal.payload.reporter_id = *value as i32;
|
modal.payload.reporter_id = *value as i32;
|
||||||
send_ws_msg(WsMsg::IssueUpdateRequest(modal.id, modal.payload.clone()));
|
send_ws_msg(WsMsg::IssueUpdateRequest(modal.id, modal.payload.clone()));
|
||||||
}
|
}
|
||||||
Msg::StyledSelectChanged(
|
Msg::StyledSelectChanged(
|
||||||
FieldId::AssigneesIssueEditModal,
|
FieldId::EditIssueModal(EditIssueModalFieldId::Assignees),
|
||||||
StyledSelectChange::Changed(value),
|
StyledSelectChange::Changed(value),
|
||||||
) => {
|
) => {
|
||||||
modal.payload.user_ids.push(*value as i32);
|
modal.payload.user_ids.push(*value as i32);
|
||||||
send_ws_msg(WsMsg::IssueUpdateRequest(modal.id, modal.payload.clone()));
|
send_ws_msg(WsMsg::IssueUpdateRequest(modal.id, modal.payload.clone()));
|
||||||
}
|
}
|
||||||
Msg::StyledSelectChanged(
|
Msg::StyledSelectChanged(
|
||||||
FieldId::AssigneesIssueEditModal,
|
FieldId::EditIssueModal(EditIssueModalFieldId::Assignees),
|
||||||
StyledSelectChange::RemoveMulti(value),
|
StyledSelectChange::RemoveMulti(value),
|
||||||
) => {
|
) => {
|
||||||
let mut old = vec![];
|
let mut old = vec![];
|
||||||
@ -73,61 +74,68 @@ pub fn update(msg: &Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
|||||||
send_ws_msg(WsMsg::IssueUpdateRequest(modal.id, modal.payload.clone()));
|
send_ws_msg(WsMsg::IssueUpdateRequest(modal.id, modal.payload.clone()));
|
||||||
}
|
}
|
||||||
Msg::StyledSelectChanged(
|
Msg::StyledSelectChanged(
|
||||||
FieldId::PriorityIssueEditModal,
|
FieldId::EditIssueModal(EditIssueModalFieldId::Priority),
|
||||||
StyledSelectChange::Changed(value),
|
StyledSelectChange::Changed(value),
|
||||||
) => {
|
) => {
|
||||||
modal.payload.priority = (*value).into();
|
modal.payload.priority = (*value).into();
|
||||||
send_ws_msg(WsMsg::IssueUpdateRequest(modal.id, modal.payload.clone()));
|
send_ws_msg(WsMsg::IssueUpdateRequest(modal.id, modal.payload.clone()));
|
||||||
}
|
}
|
||||||
Msg::InputChanged(FieldId::TitleIssueEditModal, value) => {
|
Msg::InputChanged(FieldId::EditIssueModal(EditIssueModalFieldId::Title), value) => {
|
||||||
modal.payload.title = value.clone();
|
modal.payload.title = value.clone();
|
||||||
send_ws_msg(WsMsg::IssueUpdateRequest(modal.id, modal.payload.clone()));
|
send_ws_msg(WsMsg::IssueUpdateRequest(modal.id, modal.payload.clone()));
|
||||||
}
|
}
|
||||||
Msg::InputChanged(FieldId::DescriptionIssueEditModal, value) => {
|
Msg::InputChanged(FieldId::EditIssueModal(EditIssueModalFieldId::Description), value) => {
|
||||||
modal.payload.description = Some(value.clone());
|
modal.payload.description = Some(value.clone());
|
||||||
modal.payload.description_text = Some(value.clone());
|
modal.payload.description_text = Some(value.clone());
|
||||||
send_ws_msg(WsMsg::IssueUpdateRequest(modal.id, modal.payload.clone()));
|
send_ws_msg(WsMsg::IssueUpdateRequest(modal.id, modal.payload.clone()));
|
||||||
}
|
}
|
||||||
Msg::ModalChanged(FieldChange::TabChanged(FieldId::DescriptionIssueEditModal, mode)) => {
|
Msg::ModalChanged(FieldChange::TabChanged(
|
||||||
|
FieldId::EditIssueModal(EditIssueModalFieldId::Description),
|
||||||
|
mode,
|
||||||
|
)) => {
|
||||||
modal.description_editor_mode = mode.clone();
|
modal.description_editor_mode = mode.clone();
|
||||||
}
|
}
|
||||||
|
Msg::ModalChanged(FieldChange::ToggleCreateComment(
|
||||||
|
FieldId::EditIssueModal(EditIssueModalFieldId::CommentBody),
|
||||||
|
flag,
|
||||||
|
)) => {
|
||||||
|
modal.creating_comment = *flag;
|
||||||
|
}
|
||||||
|
Msg::GlobalKeyDown { key, .. } if key.as_str() == "m" && !modal.creating_comment => {
|
||||||
|
orders
|
||||||
|
.skip()
|
||||||
|
.send_msg(Msg::ModalChanged(FieldChange::ToggleCreateComment(
|
||||||
|
FieldId::EditIssueModal(EditIssueModalFieldId::CommentBody),
|
||||||
|
true,
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn view(model: &Model, modal: &EditIssueModal) -> Node<Msg> {
|
pub fn view(model: &Model, modal: &EditIssueModal) -> Node<Msg> {
|
||||||
|
div![
|
||||||
|
attrs![At::Class => "issueDetails"],
|
||||||
|
top_modal_row(model, modal),
|
||||||
|
div![
|
||||||
|
attrs![At::Class => "content"],
|
||||||
|
left_modal_column(model, modal),
|
||||||
|
right_modal_column(model, modal),
|
||||||
|
],
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn top_modal_row(_model: &Model, modal: &EditIssueModal) -> Node<Msg> {
|
||||||
let EditIssueModal {
|
let EditIssueModal {
|
||||||
id,
|
id,
|
||||||
link_copied,
|
|
||||||
payload,
|
payload,
|
||||||
top_type_state,
|
top_type_state,
|
||||||
status_state,
|
link_copied,
|
||||||
reporter_state: _,
|
..
|
||||||
assignees_state: _,
|
|
||||||
priority_state: _,
|
|
||||||
description_editor_mode,
|
|
||||||
} = modal;
|
} = modal;
|
||||||
let issue_id = id.clone();
|
|
||||||
|
|
||||||
let issue_type_select = StyledSelect::build(FieldId::IssueTypeEditModalTop)
|
let issue_id = id.clone();
|
||||||
.dropdown_width(150)
|
|
||||||
.name("type")
|
|
||||||
.text_filter(top_type_state.text_filter.as_str())
|
|
||||||
.opened(top_type_state.opened)
|
|
||||||
.valid(true)
|
|
||||||
.options(
|
|
||||||
IssueType::ordered()
|
|
||||||
.into_iter()
|
|
||||||
.map(|t| IssueTypeTopOption(issue_id, t))
|
|
||||||
.collect(),
|
|
||||||
)
|
|
||||||
.selected(vec![IssueTypeTopOption(
|
|
||||||
issue_id,
|
|
||||||
payload.issue_type.clone(),
|
|
||||||
)])
|
|
||||||
.build()
|
|
||||||
.into_node();
|
|
||||||
|
|
||||||
let click_handler = mouse_ev(Ev::Click, move |_| {
|
let click_handler = mouse_ev(Ev::Click, move |_| {
|
||||||
use wasm_bindgen::JsCast;
|
use wasm_bindgen::JsCast;
|
||||||
@ -177,17 +185,61 @@ pub fn view(model: &Model, modal: &EditIssueModal) -> Node<Msg> {
|
|||||||
.build()
|
.build()
|
||||||
.into_node();
|
.into_node();
|
||||||
|
|
||||||
// left
|
let issue_type_select =
|
||||||
|
StyledSelect::build(FieldId::EditIssueModal(EditIssueModalFieldId::IssueType))
|
||||||
|
.dropdown_width(150)
|
||||||
|
.name("type")
|
||||||
|
.text_filter(top_type_state.text_filter.as_str())
|
||||||
|
.opened(top_type_state.opened)
|
||||||
|
.valid(true)
|
||||||
|
.options(
|
||||||
|
IssueType::ordered()
|
||||||
|
.into_iter()
|
||||||
|
.map(|t| t.to_select_child().name("type"))
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
|
.selected(vec![{
|
||||||
|
let id = modal.id.clone();
|
||||||
|
let issue_type = &payload.issue_type;
|
||||||
|
issue_type
|
||||||
|
.to_select_child()
|
||||||
|
.name("type")
|
||||||
|
.text(format!("{} - {}", issue_type, id))
|
||||||
|
}])
|
||||||
|
.build()
|
||||||
|
.into_node();
|
||||||
|
|
||||||
|
div![
|
||||||
|
attrs![At::Class => "topActions"],
|
||||||
|
issue_type_select,
|
||||||
|
div![
|
||||||
|
attrs![At::Class => "topActionsRight"],
|
||||||
|
copy_button,
|
||||||
|
delete_button,
|
||||||
|
close_button
|
||||||
|
],
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn left_modal_column(model: &Model, modal: &EditIssueModal) -> Node<Msg> {
|
||||||
|
let EditIssueModal {
|
||||||
|
payload,
|
||||||
|
description_editor_mode,
|
||||||
|
creating_comment,
|
||||||
|
..
|
||||||
|
} = modal;
|
||||||
|
|
||||||
let title = StyledTextarea::build()
|
let title = StyledTextarea::build()
|
||||||
.value(payload.title.as_str())
|
.value(payload.title.as_str())
|
||||||
.add_class("textarea")
|
.add_class("textarea")
|
||||||
.max_height(48)
|
.max_height(48)
|
||||||
.height(0)
|
.height(0)
|
||||||
.build(FieldId::TitleIssueEditModal)
|
.build(FieldId::EditIssueModal(EditIssueModalFieldId::Title))
|
||||||
.into_node();
|
.into_node();
|
||||||
|
|
||||||
let description_text = payload.description.as_ref().cloned().unwrap_or_default();
|
let description_text = payload.description.as_ref().cloned().unwrap_or_default();
|
||||||
let description = StyledEditor::build(FieldId::DescriptionIssueEditModal)
|
let description =
|
||||||
|
StyledEditor::build(FieldId::EditIssueModal(EditIssueModalFieldId::Description))
|
||||||
.text(description_text)
|
.text(description_text)
|
||||||
.mode(description_editor_mode.clone())
|
.mode(description_editor_mode.clone())
|
||||||
.update_on(Ev::Change)
|
.update_on(Ev::Change)
|
||||||
@ -195,8 +247,73 @@ pub fn view(model: &Model, modal: &EditIssueModal) -> Node<Msg> {
|
|||||||
.into_node();
|
.into_node();
|
||||||
let description_field = StyledField::build().input(description).build().into_node();
|
let description_field = StyledField::build().input(description).build().into_node();
|
||||||
|
|
||||||
// right
|
let user_avatar = StyledAvatar::build()
|
||||||
let status = StyledSelect::build(FieldId::StatusIssueEditModal)
|
.add_class("userAvatar")
|
||||||
|
.size(32)
|
||||||
|
.avatar_url(
|
||||||
|
model
|
||||||
|
.user
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|u| u.avatar_url.clone())
|
||||||
|
.unwrap_or_default(),
|
||||||
|
)
|
||||||
|
.build()
|
||||||
|
.into_node();
|
||||||
|
|
||||||
|
let create_comment = if *creating_comment {
|
||||||
|
let text_area = StyledTextarea::build()
|
||||||
|
.build(FieldId::EditIssueModal(EditIssueModalFieldId::CommentBody))
|
||||||
|
.into_node();
|
||||||
|
div![text_area]
|
||||||
|
} else {
|
||||||
|
let creating_comment = *creating_comment;
|
||||||
|
let handler = mouse_ev(Ev::Click, move |ev| {
|
||||||
|
ev.stop_propagation();
|
||||||
|
Msg::ModalChanged(FieldChange::ToggleCreateComment(
|
||||||
|
FieldId::EditIssueModal(EditIssueModalFieldId::CommentBody),
|
||||||
|
!creating_comment,
|
||||||
|
))
|
||||||
|
});
|
||||||
|
div![class!["fakeTextArea"], handler]
|
||||||
|
};
|
||||||
|
|
||||||
|
div![
|
||||||
|
class!["left"],
|
||||||
|
title,
|
||||||
|
description_field,
|
||||||
|
div![
|
||||||
|
class!["comments"],
|
||||||
|
div![class!["title"], "Comments"],
|
||||||
|
div![
|
||||||
|
class!["create"],
|
||||||
|
user_avatar,
|
||||||
|
div![
|
||||||
|
class!["right"],
|
||||||
|
create_comment,
|
||||||
|
div![
|
||||||
|
class!["proTip"],
|
||||||
|
strong![class!["strong"], "Pro tip: "],
|
||||||
|
"press ",
|
||||||
|
span![class!["tipLetter"], "M"],
|
||||||
|
" to comment"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
],
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn right_modal_column(model: &Model, modal: &EditIssueModal) -> Node<Msg> {
|
||||||
|
let EditIssueModal {
|
||||||
|
payload,
|
||||||
|
status_state,
|
||||||
|
reporter_state,
|
||||||
|
assignees_state,
|
||||||
|
priority_state,
|
||||||
|
..
|
||||||
|
} = modal;
|
||||||
|
|
||||||
|
let status = StyledSelect::build(FieldId::EditIssueModal(EditIssueModalFieldId::Status))
|
||||||
.name("status")
|
.name("status")
|
||||||
.opened(status_state.opened)
|
.opened(status_state.opened)
|
||||||
.normal()
|
.normal()
|
||||||
@ -204,10 +321,10 @@ pub fn view(model: &Model, modal: &EditIssueModal) -> Node<Msg> {
|
|||||||
.options(
|
.options(
|
||||||
IssueStatus::ordered()
|
IssueStatus::ordered()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|opt| IssueStatusOption(issue_id, opt))
|
.map(|opt| opt.to_select_child().name("status"))
|
||||||
.collect(),
|
.collect(),
|
||||||
)
|
)
|
||||||
.selected(vec![IssueStatusOption(issue_id, payload.status.clone())])
|
.selected(vec![payload.status.to_select_child().name("status")])
|
||||||
.valid(true)
|
.valid(true)
|
||||||
.build()
|
.build()
|
||||||
.into_node();
|
.into_node();
|
||||||
@ -217,19 +334,25 @@ pub fn view(model: &Model, modal: &EditIssueModal) -> Node<Msg> {
|
|||||||
.build()
|
.build()
|
||||||
.into_node();
|
.into_node();
|
||||||
|
|
||||||
let assignees = StyledSelect::build(FieldId::AssigneesIssueEditModal)
|
let assignees = StyledSelect::build(FieldId::EditIssueModal(EditIssueModalFieldId::Assignees))
|
||||||
.name("assignees")
|
.name("assignees")
|
||||||
.opened(modal.assignees_state.opened)
|
.opened(assignees_state.opened)
|
||||||
.normal()
|
.empty()
|
||||||
.multi()
|
.multi()
|
||||||
.text_filter(modal.assignees_state.text_filter.as_str())
|
.text_filter(assignees_state.text_filter.as_str())
|
||||||
.options(model.users.iter().map(|user| UserOption(user)).collect())
|
.options(
|
||||||
|
model
|
||||||
|
.users
|
||||||
|
.iter()
|
||||||
|
.map(|user| user.to_select_child().name("assignees"))
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
.selected(
|
.selected(
|
||||||
model
|
model
|
||||||
.users
|
.users
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|user| payload.user_ids.contains(&user.id))
|
.filter(|user| payload.user_ids.contains(&user.id))
|
||||||
.map(|user| UserOption(user))
|
.map(|user| user.to_select_child().name("assignees"))
|
||||||
.collect(),
|
.collect(),
|
||||||
)
|
)
|
||||||
.build()
|
.build()
|
||||||
@ -240,18 +363,24 @@ pub fn view(model: &Model, modal: &EditIssueModal) -> Node<Msg> {
|
|||||||
.build()
|
.build()
|
||||||
.into_node();
|
.into_node();
|
||||||
|
|
||||||
let reporter = StyledSelect::build(FieldId::ReporterIssueEditModal)
|
let reporter = StyledSelect::build(FieldId::EditIssueModal(EditIssueModalFieldId::Reporter))
|
||||||
.name("reporter")
|
.name("reporter")
|
||||||
.opened(modal.reporter_state.opened)
|
.opened(reporter_state.opened)
|
||||||
.normal()
|
.empty()
|
||||||
.text_filter(modal.reporter_state.text_filter.as_str())
|
.text_filter(reporter_state.text_filter.as_str())
|
||||||
.options(model.users.iter().map(|user| UserOption(user)).collect())
|
.options(
|
||||||
|
model
|
||||||
|
.users
|
||||||
|
.iter()
|
||||||
|
.map(|user| user.to_select_child().name("reporter"))
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
.selected(
|
.selected(
|
||||||
model
|
model
|
||||||
.users
|
.users
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|user| payload.reporter_id == user.id)
|
.filter(|user| payload.reporter_id == user.id)
|
||||||
.map(|user| UserOption(user))
|
.map(|user| user.to_select_child().name("reporter"))
|
||||||
.collect(),
|
.collect(),
|
||||||
)
|
)
|
||||||
.build()
|
.build()
|
||||||
@ -262,18 +391,18 @@ pub fn view(model: &Model, modal: &EditIssueModal) -> Node<Msg> {
|
|||||||
.build()
|
.build()
|
||||||
.into_node();
|
.into_node();
|
||||||
|
|
||||||
let priority = StyledSelect::build(FieldId::PriorityIssueEditModal)
|
let priority = StyledSelect::build(FieldId::EditIssueModal(EditIssueModalFieldId::Priority))
|
||||||
.name("assignees")
|
.name("priority")
|
||||||
.opened(modal.priority_state.opened)
|
.opened(priority_state.opened)
|
||||||
.normal()
|
.empty()
|
||||||
.text_filter(modal.priority_state.text_filter.as_str())
|
.text_filter(priority_state.text_filter.as_str())
|
||||||
.options(
|
.options(
|
||||||
IssuePriority::ordered()
|
IssuePriority::ordered()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|p| IssuePriorityOption(p))
|
.map(|p| p.to_select_child().name("priority"))
|
||||||
.collect(),
|
.collect(),
|
||||||
)
|
)
|
||||||
.selected(vec![IssuePriorityOption(payload.priority.clone())])
|
.selected(vec![payload.priority.to_select_child().name("priority")])
|
||||||
.build()
|
.build()
|
||||||
.into_node();
|
.into_node();
|
||||||
let priority_field = StyledField::build()
|
let priority_field = StyledField::build()
|
||||||
@ -282,7 +411,7 @@ pub fn view(model: &Model, modal: &EditIssueModal) -> Node<Msg> {
|
|||||||
.build()
|
.build()
|
||||||
.into_node();
|
.into_node();
|
||||||
|
|
||||||
let estimate = StyledInput::build(FieldId::EstimateIssueEditModal)
|
let estimate = StyledInput::build(FieldId::EditIssueModal(EditIssueModalFieldId::Estimate))
|
||||||
.valid(true)
|
.valid(true)
|
||||||
.build()
|
.build()
|
||||||
.into_node();
|
.into_node();
|
||||||
@ -292,26 +421,6 @@ pub fn view(model: &Model, modal: &EditIssueModal) -> Node<Msg> {
|
|||||||
.build()
|
.build()
|
||||||
.into_node();
|
.into_node();
|
||||||
|
|
||||||
div![
|
|
||||||
attrs![At::Class => "issueDetails"],
|
|
||||||
div![
|
|
||||||
attrs![At::Class => "topActions"],
|
|
||||||
issue_type_select,
|
|
||||||
div![
|
|
||||||
attrs![At::Class => "topActionsRight"],
|
|
||||||
copy_button,
|
|
||||||
delete_button,
|
|
||||||
close_button
|
|
||||||
],
|
|
||||||
],
|
|
||||||
div![
|
|
||||||
attrs![At::Class => "content"],
|
|
||||||
div![
|
|
||||||
attrs![At::Class => "left"],
|
|
||||||
title,
|
|
||||||
description_field,
|
|
||||||
div![attrs![At::Class => "comments"]],
|
|
||||||
],
|
|
||||||
div![
|
div![
|
||||||
attrs![At::Class => "right"],
|
attrs![At::Class => "right"],
|
||||||
status_field,
|
status_field,
|
||||||
@ -319,220 +428,5 @@ pub fn view(model: &Model, modal: &EditIssueModal) -> Node<Msg> {
|
|||||||
reporter_field,
|
reporter_field,
|
||||||
priority_field,
|
priority_field,
|
||||||
estimate_field
|
estimate_field
|
||||||
],
|
|
||||||
],
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialOrd, PartialEq, Debug)]
|
|
||||||
pub struct IssueTypeTopOption(pub IssueId, pub IssueType);
|
|
||||||
|
|
||||||
impl SelectOption for IssueTypeTopOption {
|
|
||||||
fn into_option(self) -> Node<Msg> {
|
|
||||||
let name = self.1.to_label().to_owned();
|
|
||||||
|
|
||||||
let icon = StyledIcon::build(self.1.into())
|
|
||||||
.add_class("issueTypeIcon".to_string())
|
|
||||||
.build()
|
|
||||||
.into_node();
|
|
||||||
|
|
||||||
div![
|
|
||||||
attrs![At::Class => "optionItem"],
|
|
||||||
icon,
|
|
||||||
div![attrs![At::Class => "optionLabel typeLabel"], name]
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
fn into_value(self) -> Node<Msg> {
|
|
||||||
let issue_id = self.0;
|
|
||||||
let name = self.1.to_label().to_owned();
|
|
||||||
|
|
||||||
StyledButton::build()
|
|
||||||
.empty()
|
|
||||||
.children(vec![span![format!("{}-{}", name, issue_id)]])
|
|
||||||
.icon(StyledIcon::build(self.1.into()).build())
|
|
||||||
.build()
|
|
||||||
.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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/////
|
|
||||||
#[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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,12 +1,10 @@
|
|||||||
use seed::{prelude::*, *};
|
use seed::{prelude::*, *};
|
||||||
|
|
||||||
use jirs_data::{UpdateIssuePayload, WsMsg};
|
use jirs_data::WsMsg;
|
||||||
|
|
||||||
use crate::api::send_ws_msg;
|
use crate::api::send_ws_msg;
|
||||||
use crate::model::{AddIssueModal, EditIssueModal, ModalType, Model, Page};
|
use crate::model::{AddIssueModal, EditIssueModal, ModalType, Model, Page};
|
||||||
use crate::shared::styled_editor::Mode;
|
|
||||||
use crate::shared::styled_modal::{StyledModal, Variant as ModalVariant};
|
use crate::shared::styled_modal::{StyledModal, Variant as ModalVariant};
|
||||||
use crate::shared::styled_select::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};
|
||||||
|
|
||||||
@ -91,34 +89,7 @@ fn push_edit_modal(issue_id: &i32, model: &mut Model) {
|
|||||||
Some(issue) => issue,
|
Some(issue) => issue,
|
||||||
_ => return,
|
_ => return,
|
||||||
};
|
};
|
||||||
ModalType::EditIssue(
|
ModalType::EditIssue(*issue_id, EditIssueModal::new(issue))
|
||||||
*issue_id,
|
|
||||||
EditIssueModal {
|
|
||||||
id: *issue_id,
|
|
||||||
link_copied: false,
|
|
||||||
payload: UpdateIssuePayload {
|
|
||||||
title: issue.title.clone(),
|
|
||||||
issue_type: issue.issue_type.clone(),
|
|
||||||
status: issue.status.clone(),
|
|
||||||
priority: issue.priority.clone(),
|
|
||||||
list_position: issue.list_position.clone(),
|
|
||||||
description: issue.description.clone(),
|
|
||||||
description_text: issue.description_text.clone(),
|
|
||||||
estimate: issue.estimate.clone(),
|
|
||||||
time_spent: issue.time_spent.clone(),
|
|
||||||
time_remaining: issue.time_remaining.clone(),
|
|
||||||
project_id: issue.project_id.clone(),
|
|
||||||
reporter_id: issue.reporter_id.clone(),
|
|
||||||
user_ids: issue.user_ids.clone(),
|
|
||||||
},
|
|
||||||
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),
|
|
||||||
description_editor_mode: Mode::Editor,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
};
|
};
|
||||||
model.modals.push(modal);
|
model.modals.push(modal);
|
||||||
}
|
}
|
||||||
|
@ -7,9 +7,7 @@ use jirs_data::*;
|
|||||||
|
|
||||||
use crate::shared::styled_editor::Mode;
|
use crate::shared::styled_editor::Mode;
|
||||||
use crate::shared::styled_select::StyledSelectState;
|
use crate::shared::styled_select::StyledSelectState;
|
||||||
use crate::{FieldId, IssueId, UserId, HOST_URL};
|
use crate::{AddIssueModalFieldId, EditIssueModalFieldId, FieldId, HOST_URL};
|
||||||
|
|
||||||
pub type ProjectId = i32;
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialOrd, PartialEq, Hash)]
|
#[derive(Clone, Debug, PartialOrd, PartialEq, Hash)]
|
||||||
pub enum ModalType {
|
pub enum ModalType {
|
||||||
@ -30,6 +28,48 @@ pub struct EditIssueModal {
|
|||||||
pub priority_state: StyledSelectState,
|
pub priority_state: StyledSelectState,
|
||||||
|
|
||||||
pub description_editor_mode: Mode,
|
pub description_editor_mode: Mode,
|
||||||
|
pub creating_comment: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EditIssueModal {
|
||||||
|
pub fn new(issue: &Issue) -> Self {
|
||||||
|
Self {
|
||||||
|
id: issue.id,
|
||||||
|
link_copied: false,
|
||||||
|
payload: UpdateIssuePayload {
|
||||||
|
title: issue.title.clone(),
|
||||||
|
issue_type: issue.issue_type.clone(),
|
||||||
|
status: issue.status.clone(),
|
||||||
|
priority: issue.priority.clone(),
|
||||||
|
list_position: issue.list_position.clone(),
|
||||||
|
description: issue.description.clone(),
|
||||||
|
description_text: issue.description_text.clone(),
|
||||||
|
estimate: issue.estimate.clone(),
|
||||||
|
time_spent: issue.time_spent.clone(),
|
||||||
|
time_remaining: issue.time_remaining.clone(),
|
||||||
|
project_id: issue.project_id.clone(),
|
||||||
|
reporter_id: issue.reporter_id.clone(),
|
||||||
|
user_ids: issue.user_ids.clone(),
|
||||||
|
},
|
||||||
|
top_type_state: StyledSelectState::new(FieldId::EditIssueModal(
|
||||||
|
EditIssueModalFieldId::IssueType,
|
||||||
|
)),
|
||||||
|
status_state: StyledSelectState::new(FieldId::EditIssueModal(
|
||||||
|
EditIssueModalFieldId::Status,
|
||||||
|
)),
|
||||||
|
reporter_state: StyledSelectState::new(FieldId::EditIssueModal(
|
||||||
|
EditIssueModalFieldId::Reporter,
|
||||||
|
)),
|
||||||
|
assignees_state: StyledSelectState::new(FieldId::EditIssueModal(
|
||||||
|
EditIssueModalFieldId::Assignees,
|
||||||
|
)),
|
||||||
|
priority_state: StyledSelectState::new(FieldId::EditIssueModal(
|
||||||
|
EditIssueModalFieldId::Priority,
|
||||||
|
)),
|
||||||
|
description_editor_mode: Mode::Editor,
|
||||||
|
creating_comment: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialOrd, PartialEq, Hash)]
|
#[derive(Clone, Debug, PartialOrd, PartialEq, Hash)]
|
||||||
@ -69,10 +109,18 @@ impl Default for AddIssueModal {
|
|||||||
project_id: Default::default(),
|
project_id: Default::default(),
|
||||||
user_ids: Default::default(),
|
user_ids: Default::default(),
|
||||||
reporter_id: Default::default(),
|
reporter_id: Default::default(),
|
||||||
type_state: StyledSelectState::new(FieldId::IssueTypeAddIssueModal),
|
type_state: StyledSelectState::new(FieldId::AddIssueModal(
|
||||||
reporter_state: StyledSelectState::new(FieldId::ReporterAddIssueModal),
|
AddIssueModalFieldId::IssueType,
|
||||||
assignees_state: StyledSelectState::new(FieldId::AssigneesAddIssueModal),
|
)),
|
||||||
priority_state: StyledSelectState::new(FieldId::IssuePriorityAddIssueModal),
|
reporter_state: StyledSelectState::new(FieldId::AddIssueModal(
|
||||||
|
AddIssueModalFieldId::Reporter,
|
||||||
|
)),
|
||||||
|
assignees_state: StyledSelectState::new(FieldId::AddIssueModal(
|
||||||
|
AddIssueModalFieldId::Assignees,
|
||||||
|
)),
|
||||||
|
priority_state: StyledSelectState::new(FieldId::AddIssueModal(
|
||||||
|
AddIssueModalFieldId::Priority,
|
||||||
|
)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,7 @@ use crate::shared::styled_icon::{Icon, StyledIcon};
|
|||||||
use crate::shared::styled_input::StyledInput;
|
use crate::shared::styled_input::StyledInput;
|
||||||
use crate::shared::styled_select::StyledSelectChange;
|
use crate::shared::styled_select::StyledSelectChange;
|
||||||
use crate::shared::{drag_ev, inner_layout, ToNode};
|
use crate::shared::{drag_ev, inner_layout, ToNode};
|
||||||
use crate::{FieldId, Msg};
|
use crate::{EditIssueModalFieldId, FieldId, 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 {
|
||||||
@ -47,7 +47,7 @@ pub fn update(msg: Msg, model: &mut crate::model::Model, orders: &mut impl Order
|
|||||||
model.project_page.about_tooltip_visible = !model.project_page.about_tooltip_visible;
|
model.project_page.about_tooltip_visible = !model.project_page.about_tooltip_visible;
|
||||||
}
|
}
|
||||||
Msg::StyledSelectChanged(
|
Msg::StyledSelectChanged(
|
||||||
FieldId::IssueTypeEditModalTop,
|
FieldId::EditIssueModal(EditIssueModalFieldId::IssueType),
|
||||||
StyledSelectChange::Text(text),
|
StyledSelectChange::Text(text),
|
||||||
) => {
|
) => {
|
||||||
let modal = model
|
let modal = model
|
||||||
@ -289,12 +289,11 @@ fn project_issue(model: &Model, issue: &Issue) -> Node<Msg> {
|
|||||||
.iter()
|
.iter()
|
||||||
.filter(|user| issue.user_ids.contains(&user.id))
|
.filter(|user| issue.user_ids.contains(&user.id))
|
||||||
.map(|user| {
|
.map(|user| {
|
||||||
StyledAvatar {
|
StyledAvatar::build()
|
||||||
avatar_url: user.avatar_url.clone(),
|
.size(24)
|
||||||
size: 24,
|
.name(user.name.as_str())
|
||||||
name: user.name.clone(),
|
.avatar_url(user.avatar_url.as_ref().cloned().unwrap_or_default())
|
||||||
on_click: None,
|
.build()
|
||||||
}
|
|
||||||
.into_node()
|
.into_node()
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
use seed::{prelude::*, *};
|
use seed::{prelude::*, *};
|
||||||
use wasm_bindgen::JsCast;
|
use wasm_bindgen::JsCast;
|
||||||
|
|
||||||
use jirs_data::Issue;
|
use jirs_data::*;
|
||||||
|
|
||||||
use crate::model::Model;
|
use crate::model::Model;
|
||||||
use crate::{IssueId, Msg};
|
use crate::Msg;
|
||||||
|
|
||||||
pub mod aside;
|
pub mod aside;
|
||||||
pub mod navbar_left;
|
pub mod navbar_left;
|
||||||
@ -18,6 +18,7 @@ pub mod styled_icon;
|
|||||||
pub mod styled_input;
|
pub mod styled_input;
|
||||||
pub mod styled_modal;
|
pub mod styled_modal;
|
||||||
pub mod styled_select;
|
pub mod styled_select;
|
||||||
|
pub mod styled_select_child;
|
||||||
pub mod styled_textarea;
|
pub mod styled_textarea;
|
||||||
pub mod styled_tooltip;
|
pub mod styled_tooltip;
|
||||||
|
|
||||||
|
@ -4,10 +4,11 @@ use crate::shared::ToNode;
|
|||||||
use crate::Msg;
|
use crate::Msg;
|
||||||
|
|
||||||
pub struct StyledAvatar {
|
pub struct StyledAvatar {
|
||||||
pub avatar_url: Option<String>,
|
avatar_url: Option<String>,
|
||||||
pub size: u32,
|
size: u32,
|
||||||
pub name: String,
|
name: String,
|
||||||
pub on_click: Option<EventHandler<Msg>>,
|
on_click: Option<EventHandler<Msg>>,
|
||||||
|
class_list: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for StyledAvatar {
|
impl Default for StyledAvatar {
|
||||||
@ -17,6 +18,7 @@ impl Default for StyledAvatar {
|
|||||||
size: 32,
|
size: 32,
|
||||||
name: "".to_string(),
|
name: "".to_string(),
|
||||||
on_click: None,
|
on_click: None,
|
||||||
|
class_list: vec![],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -28,6 +30,7 @@ impl StyledAvatar {
|
|||||||
size: None,
|
size: None,
|
||||||
name: "".to_string(),
|
name: "".to_string(),
|
||||||
on_click: None,
|
on_click: None,
|
||||||
|
class_list: vec![],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -39,10 +42,11 @@ impl ToNode for StyledAvatar {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct StyledAvatarBuilder {
|
pub struct StyledAvatarBuilder {
|
||||||
pub avatar_url: Option<String>,
|
avatar_url: Option<String>,
|
||||||
pub size: Option<u32>,
|
size: Option<u32>,
|
||||||
pub name: String,
|
name: String,
|
||||||
pub on_click: Option<EventHandler<Msg>>,
|
on_click: Option<EventHandler<Msg>>,
|
||||||
|
class_list: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StyledAvatarBuilder {
|
impl StyledAvatarBuilder {
|
||||||
@ -50,7 +54,10 @@ impl StyledAvatarBuilder {
|
|||||||
where
|
where
|
||||||
S: Into<String>,
|
S: Into<String>,
|
||||||
{
|
{
|
||||||
self.avatar_url = Some(avatar_url.into());
|
let url = avatar_url.into();
|
||||||
|
if !url.is_empty() {
|
||||||
|
self.avatar_url = Some(url);
|
||||||
|
}
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,12 +79,21 @@ impl StyledAvatarBuilder {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn add_class<S>(mut self, name: S) -> Self
|
||||||
|
where
|
||||||
|
S: Into<String>,
|
||||||
|
{
|
||||||
|
self.class_list.push(name.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
pub fn build(self) -> StyledAvatar {
|
pub fn build(self) -> StyledAvatar {
|
||||||
StyledAvatar {
|
StyledAvatar {
|
||||||
avatar_url: self.avatar_url,
|
avatar_url: self.avatar_url,
|
||||||
size: self.size.unwrap_or(32),
|
size: self.size.unwrap_or(32),
|
||||||
name: self.name,
|
name: self.name,
|
||||||
on_click: self.on_click,
|
on_click: self.on_click,
|
||||||
|
class_list: self.class_list,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -88,7 +104,15 @@ pub fn render(values: StyledAvatar) -> Node<Msg> {
|
|||||||
size,
|
size,
|
||||||
name,
|
name,
|
||||||
on_click,
|
on_click,
|
||||||
|
mut class_list,
|
||||||
} = values;
|
} = values;
|
||||||
|
|
||||||
|
class_list.push("styledAvatar".to_string());
|
||||||
|
match avatar_url {
|
||||||
|
Some(_) => class_list.push("image".to_string()),
|
||||||
|
_ => class_list.push("letter".to_string()),
|
||||||
|
};
|
||||||
|
|
||||||
let shared_style = format!("width: {size}px; height: {size}px", size = size);
|
let shared_style = format!("width: {size}px; height: {size}px", size = size);
|
||||||
let handler = match on_click {
|
let handler = match on_click {
|
||||||
None => vec![],
|
None => vec![],
|
||||||
@ -96,11 +120,11 @@ pub fn render(values: StyledAvatar) -> Node<Msg> {
|
|||||||
};
|
};
|
||||||
match avatar_url {
|
match avatar_url {
|
||||||
Some(url) => div![
|
Some(url) => div![
|
||||||
attrs![At::Class => "styledAvatar image", At::Style => format!("{shared}; background-image: url({url});", shared = shared_style, url = url)],
|
attrs![At::Class => class_list.join(" "), At::Style => format!("{shared}; background-image: url({url});", shared = shared_style, url = url)],
|
||||||
handler,
|
handler,
|
||||||
],
|
],
|
||||||
_ => div![
|
_ => div![
|
||||||
attrs![At::Class => "styledAvatar letter", At::Style => shared_style],
|
attrs![At::Class => class_list.join(" "), At::Style => shared_style],
|
||||||
span![name],
|
span![name],
|
||||||
handler
|
handler
|
||||||
],
|
],
|
||||||
|
@ -28,7 +28,7 @@ impl StyledEditor {
|
|||||||
StyledEditorBuilder {
|
StyledEditorBuilder {
|
||||||
id,
|
id,
|
||||||
text: String::new(),
|
text: String::new(),
|
||||||
mode: Mode::Editor,
|
mode: Mode::View,
|
||||||
update_event: None,
|
update_event: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -74,12 +74,17 @@ pub fn render(values: StyledInput) -> Node<Msg> {
|
|||||||
|
|
||||||
let mut handlers = vec![];
|
let mut handlers = vec![];
|
||||||
|
|
||||||
let input_handler = input_ev(Ev::KeyUp, move |value| Msg::InputChanged(id, value));
|
handlers.push(input_ev(Ev::KeyUp, move |value| {
|
||||||
handlers.push(input_handler);
|
Msg::InputChanged(id, value)
|
||||||
|
}));
|
||||||
|
|
||||||
div![
|
div![
|
||||||
attrs!(At::Class => wrapper_class_list.join(" ")),
|
attrs!(At::Class => wrapper_class_list.join(" ")),
|
||||||
icon,
|
icon,
|
||||||
|
keyboard_ev(Ev::KeyUp, |ev| {
|
||||||
|
ev.stop_propagation();
|
||||||
|
Msg::NoOp
|
||||||
|
}),
|
||||||
seed::input![attrs![At::Class => input_class_list.join(" ")], handlers],
|
seed::input![attrs![At::Class => input_class_list.join(" ")], handlers],
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
use seed::{prelude::*, *};
|
use seed::{prelude::*, *};
|
||||||
|
|
||||||
use crate::shared::styled_icon::{Icon, StyledIcon};
|
use crate::shared::styled_icon::{Icon, StyledIcon};
|
||||||
|
use crate::shared::styled_select_child::*;
|
||||||
use crate::shared::ToNode;
|
use crate::shared::ToNode;
|
||||||
use crate::{FieldId, Msg};
|
use crate::{FieldId, Msg};
|
||||||
|
|
||||||
@ -33,16 +34,6 @@ impl std::fmt::Display for Variant {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialOrd, PartialEq, Hash)]
|
#[derive(Debug, Clone, PartialOrd, PartialEq, Hash)]
|
||||||
pub struct StyledSelectState {
|
pub struct StyledSelectState {
|
||||||
pub field_id: FieldId,
|
pub field_id: FieldId,
|
||||||
@ -79,10 +70,7 @@ impl StyledSelectState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct StyledSelect<Child>
|
pub struct StyledSelect {
|
||||||
where
|
|
||||||
Child: SelectOption + PartialEq,
|
|
||||||
{
|
|
||||||
id: FieldId,
|
id: FieldId,
|
||||||
variant: Variant,
|
variant: Variant,
|
||||||
dropdown_width: Option<usize>,
|
dropdown_width: Option<usize>,
|
||||||
@ -90,26 +78,20 @@ where
|
|||||||
valid: bool,
|
valid: bool,
|
||||||
is_multi: bool,
|
is_multi: bool,
|
||||||
allow_clear: bool,
|
allow_clear: bool,
|
||||||
options: Vec<Child>,
|
options: Vec<StyledSelectChildBuilder>,
|
||||||
selected: Vec<Child>,
|
selected: Vec<StyledSelectChildBuilder>,
|
||||||
text_filter: String,
|
text_filter: String,
|
||||||
opened: bool,
|
opened: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Child> ToNode for StyledSelect<Child>
|
impl ToNode for StyledSelect {
|
||||||
where
|
|
||||||
Child: SelectOption + PartialEq,
|
|
||||||
{
|
|
||||||
fn into_node(self) -> Node<Msg> {
|
fn into_node(self) -> Node<Msg> {
|
||||||
render(self)
|
render(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Child> StyledSelect<Child>
|
impl StyledSelect {
|
||||||
where
|
pub fn build(id: FieldId) -> StyledSelectBuilder {
|
||||||
Child: SelectOption + PartialEq,
|
|
||||||
{
|
|
||||||
pub fn build(id: FieldId) -> StyledSelectBuilder<Child> {
|
|
||||||
StyledSelectBuilder {
|
StyledSelectBuilder {
|
||||||
id,
|
id,
|
||||||
variant: None,
|
variant: None,
|
||||||
@ -127,10 +109,7 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct StyledSelectBuilder<Child>
|
pub struct StyledSelectBuilder {
|
||||||
where
|
|
||||||
Child: SelectOption + PartialEq,
|
|
||||||
{
|
|
||||||
id: FieldId,
|
id: FieldId,
|
||||||
variant: Option<Variant>,
|
variant: Option<Variant>,
|
||||||
dropdown_width: Option<Option<usize>>,
|
dropdown_width: Option<Option<usize>>,
|
||||||
@ -138,17 +117,14 @@ where
|
|||||||
valid: Option<bool>,
|
valid: Option<bool>,
|
||||||
is_multi: Option<bool>,
|
is_multi: Option<bool>,
|
||||||
allow_clear: Option<bool>,
|
allow_clear: Option<bool>,
|
||||||
options: Option<Vec<Child>>,
|
options: Option<Vec<StyledSelectChildBuilder>>,
|
||||||
selected: Option<Vec<Child>>,
|
selected: Option<Vec<StyledSelectChildBuilder>>,
|
||||||
text_filter: Option<String>,
|
text_filter: Option<String>,
|
||||||
opened: Option<bool>,
|
opened: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Child> StyledSelectBuilder<Child>
|
impl StyledSelectBuilder {
|
||||||
where
|
pub fn build(self) -> StyledSelect {
|
||||||
Child: SelectOption + PartialEq,
|
|
||||||
{
|
|
||||||
pub fn build(self) -> StyledSelect<Child> {
|
|
||||||
StyledSelect {
|
StyledSelect {
|
||||||
id: self.id,
|
id: self.id,
|
||||||
variant: self.variant.unwrap_or_default(),
|
variant: self.variant.unwrap_or_default(),
|
||||||
@ -195,12 +171,12 @@ where
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn options(mut self, options: Vec<Child>) -> Self {
|
pub fn options(mut self, options: Vec<StyledSelectChildBuilder>) -> Self {
|
||||||
self.options = Some(options);
|
self.options = Some(options);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn selected(mut self, selected: Vec<Child>) -> Self {
|
pub fn selected(mut self, selected: Vec<StyledSelectChildBuilder>) -> Self {
|
||||||
self.selected = Some(selected);
|
self.selected = Some(selected);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
@ -210,16 +186,18 @@ where
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn empty(mut self) -> Self {
|
||||||
|
self.variant = Some(Variant::Empty);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
pub fn multi(mut self) -> Self {
|
pub fn multi(mut self) -> Self {
|
||||||
self.is_multi = Some(true);
|
self.is_multi = Some(true);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render<Child>(values: StyledSelect<Child>) -> Node<Msg>
|
pub fn render(values: StyledSelect) -> Node<Msg> {
|
||||||
where
|
|
||||||
Child: SelectOption + PartialEq,
|
|
||||||
{
|
|
||||||
let StyledSelect {
|
let StyledSelect {
|
||||||
id,
|
id,
|
||||||
variant,
|
variant,
|
||||||
@ -247,6 +225,7 @@ where
|
|||||||
let dropdown_style = dropdown_width
|
let dropdown_style = dropdown_width
|
||||||
.map(|n| format!("width: {}px;", n))
|
.map(|n| format!("width: {}px;", n))
|
||||||
.unwrap_or_else(|| format!("width: 100%;"));
|
.unwrap_or_else(|| format!("width: 100%;"));
|
||||||
|
|
||||||
let mut select_class = vec!["styledSelect".to_string(), format!("{}", variant)];
|
let mut select_class = vec!["styledSelect".to_string(), format!("{}", variant)];
|
||||||
if !valid {
|
if !valid {
|
||||||
select_class.push("invalid".to_string());
|
select_class.push("invalid".to_string());
|
||||||
@ -262,9 +241,11 @@ where
|
|||||||
|
|
||||||
let children: Vec<Node<Msg>> = options
|
let children: Vec<Node<Msg>> = options
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|o| !selected.contains(&o) && o.match_text_filter(text_filter.as_str()))
|
.filter(|o| !selected.contains(&o) && o.match_text(text_filter.as_str()))
|
||||||
.map(|child| {
|
.map(|child| {
|
||||||
let value = child.to_value();
|
let child = child.build(DisplayType::SelectOption);
|
||||||
|
let value = child.value();
|
||||||
|
let node = child.into_node();
|
||||||
let field_id = id.clone();
|
let field_id = id.clone();
|
||||||
let on_change = mouse_ev(Ev::Click, move |_| {
|
let on_change = mouse_ev(Ev::Click, move |_| {
|
||||||
Msg::StyledSelectChanged(field_id, StyledSelectChange::Changed(value))
|
Msg::StyledSelectChanged(field_id, StyledSelectChange::Changed(value))
|
||||||
@ -273,7 +254,7 @@ where
|
|||||||
attrs![At::Class => "option"],
|
attrs![At::Class => "option"],
|
||||||
on_change,
|
on_change,
|
||||||
visibility_handler.clone(),
|
visibility_handler.clone(),
|
||||||
child.into_option()
|
node
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
@ -320,12 +301,16 @@ where
|
|||||||
} else {
|
} else {
|
||||||
selected
|
selected
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|m| render_value(m.into_value()))
|
.map(|m| render_value(m.build(DisplayType::SelectValue).into_node()))
|
||||||
.collect()
|
.collect()
|
||||||
};
|
};
|
||||||
|
|
||||||
seed::div![
|
seed::div![
|
||||||
attrs![At::Class => select_class.join(" ")],
|
attrs![At::Class => select_class.join(" "), At::Style => dropdown_style.as_str()],
|
||||||
|
keyboard_ev(Ev::KeyUp, |ev| {
|
||||||
|
ev.stop_propagation();
|
||||||
|
Msg::NoOp
|
||||||
|
}),
|
||||||
div![
|
div![
|
||||||
attrs![At::Class => format!("valueContainer {}", variant)],
|
attrs![At::Class => format!("valueContainer {}", variant)],
|
||||||
visibility_handler,
|
visibility_handler,
|
||||||
@ -333,7 +318,8 @@ where
|
|||||||
chevron_down,
|
chevron_down,
|
||||||
],
|
],
|
||||||
div![
|
div![
|
||||||
attrs![At::Class => "dropDown", At::Style => dropdown_style],
|
class!["dropDown"],
|
||||||
|
attrs![At::Style => dropdown_style.as_str()],
|
||||||
text_input,
|
text_input,
|
||||||
clear_icon,
|
clear_icon,
|
||||||
option_list
|
option_list
|
||||||
@ -346,13 +332,12 @@ fn render_value(mut content: Node<Msg>) -> Node<Msg> {
|
|||||||
content
|
content
|
||||||
}
|
}
|
||||||
|
|
||||||
fn into_multi_value<Opt>(opt: Opt, field_id: FieldId) -> Node<Msg>
|
fn into_multi_value(opt: StyledSelectChildBuilder, field_id: FieldId) -> Node<Msg> {
|
||||||
where
|
|
||||||
Opt: SelectOption,
|
|
||||||
{
|
|
||||||
let close_icon = StyledIcon::build(Icon::Close).size(14).build().into_node();
|
let close_icon = StyledIcon::build(Icon::Close).size(14).build().into_node();
|
||||||
let value = opt.to_value();
|
let child = opt.build(DisplayType::SelectValue);
|
||||||
let mut opt = opt.into_value();
|
let value = child.value();
|
||||||
|
|
||||||
|
let mut opt = child.into_node();
|
||||||
opt.add_class("value");
|
opt.add_class("value");
|
||||||
opt.add_child(close_icon);
|
opt.add_child(close_icon);
|
||||||
|
|
||||||
|
240
jirs-client/src/shared/styled_select_child.rs
Normal file
240
jirs-client/src/shared/styled_select_child.rs
Normal file
@ -0,0 +1,240 @@
|
|||||||
|
use seed::{prelude::*, *};
|
||||||
|
|
||||||
|
use crate::shared::styled_select::Variant;
|
||||||
|
use crate::shared::ToNode;
|
||||||
|
use crate::Msg;
|
||||||
|
|
||||||
|
pub trait ToStyledSelectChild {
|
||||||
|
fn to_select_child(&self) -> StyledSelectChildBuilder;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum DisplayType {
|
||||||
|
SelectOption,
|
||||||
|
SelectValue,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct StyledSelectChild {
|
||||||
|
name: Option<String>,
|
||||||
|
icon: Option<Node<Msg>>,
|
||||||
|
text: Option<String>,
|
||||||
|
display_type: DisplayType,
|
||||||
|
value: u32,
|
||||||
|
class_list: Vec<String>,
|
||||||
|
variant: Variant,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StyledSelectChild {
|
||||||
|
pub fn build() -> StyledSelectChildBuilder {
|
||||||
|
StyledSelectChildBuilder {
|
||||||
|
icon: None,
|
||||||
|
text: None,
|
||||||
|
name: None,
|
||||||
|
value: 0,
|
||||||
|
class_list: vec![],
|
||||||
|
variant: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn value(&self) -> u32 {
|
||||||
|
self.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToNode for StyledSelectChild {
|
||||||
|
fn into_node(self) -> Node<Msg> {
|
||||||
|
render(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct StyledSelectChildBuilder {
|
||||||
|
icon: Option<Node<Msg>>,
|
||||||
|
text: Option<String>,
|
||||||
|
name: Option<String>,
|
||||||
|
value: u32,
|
||||||
|
class_list: Vec<String>,
|
||||||
|
variant: Variant,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq for StyledSelectChildBuilder {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.value == other.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StyledSelectChildBuilder {
|
||||||
|
pub fn icon(mut self, icon: Node<Msg>) -> Self {
|
||||||
|
self.icon = Some(icon);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn text<S>(mut self, text: S) -> Self
|
||||||
|
where
|
||||||
|
S: Into<String>,
|
||||||
|
{
|
||||||
|
self.text = Some(text.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn name<S>(mut self, name: S) -> Self
|
||||||
|
where
|
||||||
|
S: Into<String>,
|
||||||
|
{
|
||||||
|
self.name = Some(name.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn value(mut self, value: u32) -> Self {
|
||||||
|
self.value = value;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn match_text(&self, text: &str) -> bool {
|
||||||
|
self.text
|
||||||
|
.as_ref()
|
||||||
|
.map(|t| t.contains(text))
|
||||||
|
.unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_class<S>(mut self, name: S) -> Self
|
||||||
|
where
|
||||||
|
S: Into<String>,
|
||||||
|
{
|
||||||
|
self.class_list.push(name.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build(self, display_type: DisplayType) -> StyledSelectChild {
|
||||||
|
StyledSelectChild {
|
||||||
|
name: self.name,
|
||||||
|
icon: self.icon,
|
||||||
|
text: self.text,
|
||||||
|
display_type,
|
||||||
|
value: self.value,
|
||||||
|
class_list: self.class_list,
|
||||||
|
variant: self.variant,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render(values: StyledSelectChild) -> Node<Msg> {
|
||||||
|
let StyledSelectChild {
|
||||||
|
name,
|
||||||
|
icon,
|
||||||
|
text,
|
||||||
|
display_type,
|
||||||
|
value: _,
|
||||||
|
mut class_list,
|
||||||
|
variant,
|
||||||
|
} = values;
|
||||||
|
|
||||||
|
class_list.push(format!("{}", variant));
|
||||||
|
|
||||||
|
let label_class = match display_type {
|
||||||
|
DisplayType::SelectOption => vec![
|
||||||
|
"optionLabel".to_string(),
|
||||||
|
variant.to_string(),
|
||||||
|
name.as_ref().cloned().unwrap_or_default(),
|
||||||
|
name.as_ref()
|
||||||
|
.map(|s| format!("{}Label", s))
|
||||||
|
.unwrap_or_default(),
|
||||||
|
class_list.join(" "),
|
||||||
|
],
|
||||||
|
DisplayType::SelectValue => vec![
|
||||||
|
"selectItemLabel".to_string(),
|
||||||
|
variant.to_string(),
|
||||||
|
name.as_ref().cloned().unwrap_or_default(),
|
||||||
|
name.as_ref()
|
||||||
|
.map(|s| format!("{}Label", s))
|
||||||
|
.unwrap_or_default(),
|
||||||
|
class_list.join(" "),
|
||||||
|
],
|
||||||
|
}
|
||||||
|
.join(" ");
|
||||||
|
|
||||||
|
let wrapper_class = match display_type {
|
||||||
|
DisplayType::SelectOption => vec![
|
||||||
|
"optionItem".to_string(),
|
||||||
|
name.as_ref().cloned().unwrap_or_default(),
|
||||||
|
class_list.join(" "),
|
||||||
|
],
|
||||||
|
DisplayType::SelectValue => vec![
|
||||||
|
"selectItem".to_string(),
|
||||||
|
name.as_ref().cloned().unwrap_or_default(),
|
||||||
|
class_list.join(" "),
|
||||||
|
],
|
||||||
|
}
|
||||||
|
.join(" ");
|
||||||
|
|
||||||
|
let icon_node = match icon {
|
||||||
|
Some(icon) => icon,
|
||||||
|
_ => empty![],
|
||||||
|
};
|
||||||
|
|
||||||
|
let label_node = match text {
|
||||||
|
Some(text) => div![class![label_class.as_str()], text],
|
||||||
|
_ => empty![],
|
||||||
|
};
|
||||||
|
|
||||||
|
div![class![wrapper_class.as_str()], icon_node, label_node]
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToStyledSelectChild for jirs_data::User {
|
||||||
|
fn to_select_child(&self) -> StyledSelectChildBuilder {
|
||||||
|
let avatar = crate::shared::styled_avatar::StyledAvatar::build()
|
||||||
|
.avatar_url(self.avatar_url.as_ref().cloned().unwrap_or_default())
|
||||||
|
.size(20)
|
||||||
|
.name(self.name.as_str())
|
||||||
|
.build()
|
||||||
|
.into_node();
|
||||||
|
StyledSelectChild::build()
|
||||||
|
.value(self.id as u32)
|
||||||
|
.icon(avatar)
|
||||||
|
.text(self.name.as_str())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToStyledSelectChild for jirs_data::IssuePriority {
|
||||||
|
fn to_select_child(&self) -> StyledSelectChildBuilder {
|
||||||
|
let icon = crate::shared::styled_icon::StyledIcon::build(self.clone().into())
|
||||||
|
.add_class(self.to_string())
|
||||||
|
.build()
|
||||||
|
.into_node();
|
||||||
|
let text = self.to_string();
|
||||||
|
|
||||||
|
StyledSelectChild::build()
|
||||||
|
.icon(icon)
|
||||||
|
.value(self.clone().into())
|
||||||
|
.text(text)
|
||||||
|
.add_class(format!("{}", self))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToStyledSelectChild for jirs_data::IssueStatus {
|
||||||
|
fn to_select_child(&self) -> StyledSelectChildBuilder {
|
||||||
|
let text = self.to_label();
|
||||||
|
|
||||||
|
StyledSelectChild::build()
|
||||||
|
.value(self.clone().into())
|
||||||
|
.add_class(text.clone())
|
||||||
|
.text(text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToStyledSelectChild for jirs_data::IssueType {
|
||||||
|
fn to_select_child(&self) -> StyledSelectChildBuilder {
|
||||||
|
let name = self.to_label().to_owned();
|
||||||
|
|
||||||
|
let type_icon = crate::shared::styled_icon::StyledIcon::build(self.clone().into())
|
||||||
|
.add_class(name.as_str())
|
||||||
|
.build()
|
||||||
|
.into_node();
|
||||||
|
|
||||||
|
StyledSelectChild::build()
|
||||||
|
.add_class(name.as_str())
|
||||||
|
.text(name)
|
||||||
|
.icon(type_icon)
|
||||||
|
.value(self.clone().into())
|
||||||
|
}
|
||||||
|
}
|
@ -140,6 +140,10 @@ pub fn render(values: StyledTextarea) -> Node<Msg> {
|
|||||||
Msg::InputChanged(id, value)
|
Msg::InputChanged(id, value)
|
||||||
});
|
});
|
||||||
handlers.push(text_input_handler);
|
handlers.push(text_input_handler);
|
||||||
|
handlers.push(keyboard_ev(Ev::KeyUp, |ev| {
|
||||||
|
ev.stop_propagation();
|
||||||
|
Msg::NoOp
|
||||||
|
}));
|
||||||
|
|
||||||
class_list.push("textAreaInput".to_string());
|
class_list.push("textAreaInput".to_string());
|
||||||
|
|
||||||
|
@ -2,7 +2,6 @@ use jirs_data::*;
|
|||||||
|
|
||||||
use crate::api::send_ws_msg;
|
use crate::api::send_ws_msg;
|
||||||
use crate::model::Model;
|
use crate::model::Model;
|
||||||
use crate::IssueId;
|
|
||||||
|
|
||||||
pub fn drag_started(issue_id: IssueId, model: &mut Model) {
|
pub fn drag_started(issue_id: IssueId, model: &mut Model) {
|
||||||
model.project_page.dragged_issue_id = Some(issue_id);
|
model.project_page.dragged_issue_id = Some(issue_id);
|
||||||
|
@ -163,19 +163,6 @@ impl IssueStatus {
|
|||||||
IssueStatus::Done => "Done",
|
IssueStatus::Done => "Done",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_payload(&self) -> &str {
|
|
||||||
match self {
|
|
||||||
IssueStatus::Backlog => "backlog",
|
|
||||||
IssueStatus::Selected => "selected",
|
|
||||||
IssueStatus::InProgress => "in_progress",
|
|
||||||
IssueStatus::Done => "done",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn match_name(&self, name: &str) -> bool {
|
|
||||||
self.to_payload() == name
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "backend", derive(FromSqlRow, AsExpression))]
|
#[cfg_attr(feature = "backend", derive(FromSqlRow, AsExpression))]
|
||||||
|
@ -26,8 +26,7 @@ class ProjectBoardIssueDetailsCommentsCreate extends React.Component {
|
|||||||
handleCommentCreate = async () => {
|
handleCommentCreate = async () => {
|
||||||
try {
|
try {
|
||||||
this.setCreatingTrue();
|
this.setCreatingTrue();
|
||||||
const response = await api.post(`/comments`, { body: this.state.body, issueId: this.props.issueId });
|
await api.post(`/comments`, { body: this.state.body, issueId: this.props.issueId });
|
||||||
console.log(response);
|
|
||||||
await this.props.fetchIssue();
|
await this.props.fetchIssue();
|
||||||
this.setState({ isCreating: false, isFormOpen: false, body: '' });
|
this.setState({ isCreating: false, isFormOpen: false, body: '' });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
Loading…
Reference in New Issue
Block a user