Add missing RTE features, add focus after RTE change, fix handle change.
Add Epic as issue type.
This commit is contained in:
parent
7aadc12c3a
commit
ba08c08e35
916
Cargo.lock
generated
916
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -8,6 +8,15 @@
|
|||||||
#license = "MPL-2.0"
|
#license = "MPL-2.0"
|
||||||
#license-file = "./LICENSE"
|
#license-file = "./LICENSE"
|
||||||
|
|
||||||
|
[profile.dev]
|
||||||
|
opt-level = 0 # Use slightly better optimizations.
|
||||||
|
overflow-checks = false # Disable integer overflow checks.
|
||||||
|
debug = 2
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
lto = true
|
||||||
|
opt-level = 's'
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
members = [
|
members = [
|
||||||
"./jirs-cli",
|
"./jirs-cli",
|
||||||
|
@ -13,6 +13,10 @@ crate-type = ["cdylib", "rlib"]
|
|||||||
name = "jirs_client"
|
name = "jirs_client"
|
||||||
path = "./src/lib.rs"
|
path = "./src/lib.rs"
|
||||||
|
|
||||||
|
[profile.dev]
|
||||||
|
opt-level = 0 # Use slightly better optimizations.
|
||||||
|
overflow-checks = false # Disable integer overflow checks.
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
lto = true
|
lto = true
|
||||||
opt-level = 's'
|
opt-level = 's'
|
||||||
@ -21,20 +25,24 @@ opt-level = 's'
|
|||||||
jirs-data = { path = "../jirs-data" }
|
jirs-data = { path = "../jirs-data" }
|
||||||
seed = { version = "0.7.0" }
|
seed = { version = "0.7.0" }
|
||||||
serde = "*"
|
serde = "*"
|
||||||
serde_json = "*"
|
|
||||||
bincode = "1.2.1"
|
bincode = "1.2.1"
|
||||||
chrono = { version = "0.4", features = [ "serde", "wasmbind" ] }
|
chrono = { version = "0.4", features = ["serde", "wasmbind"] }
|
||||||
uuid = { version = "0.8.1", features = [ "serde" ] }
|
uuid = { version = "0.8.1", features = ["serde"] }
|
||||||
wasm-bindgen = "0.2.60"
|
|
||||||
futures = "^0.1.26"
|
futures = "^0.1.26"
|
||||||
comrak = "*"
|
comrak = "*"
|
||||||
wee_alloc = "*"
|
wee_alloc = "*"
|
||||||
|
|
||||||
|
[dependencies.wasm-bindgen]
|
||||||
|
version = "0.2.66"
|
||||||
|
features = ["enable-interning"]
|
||||||
|
|
||||||
[dependencies.js-sys]
|
[dependencies.js-sys]
|
||||||
version = "*"
|
version = "*"
|
||||||
|
default-features = false
|
||||||
|
|
||||||
[dependencies.web-sys]
|
[dependencies.web-sys]
|
||||||
version = "0.3.22"
|
version = "0.3.22"
|
||||||
|
default-features = false
|
||||||
features = [
|
features = [
|
||||||
# elements
|
# elements
|
||||||
"Window",
|
"Window",
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
server {
|
server {
|
||||||
listen 80;
|
listen 80;
|
||||||
listen 443 ssl http2;
|
# listen 443 ssl http2;
|
||||||
server_name jirs.lvh.me;
|
server_name jirs.lvh.me;
|
||||||
|
|
||||||
charset utf-8;
|
charset utf-8;
|
||||||
root /home/eraden/code/eraden/jirs/jirs-client/tmp;
|
root /home/eraden/code/eraden/jirs/jirs-client/tmp;
|
||||||
|
|
||||||
ssl_certificate /home/eraden/code/eraden/jirs/jirs-client/js/nginx-selfsigned.crt;
|
# ssl_certificate /home/eraden/code/eraden/jirs/jirs-client/js/nginx-selfsigned.crt;
|
||||||
ssl_certificate_key /home/eraden/code/eraden/jirs/jirs-client/js/nginx-selfsigned.key;
|
# ssl_certificate_key /home/eraden/code/eraden/jirs/jirs-client/js/nginx-selfsigned.key;
|
||||||
|
|
||||||
# if ($scheme != "https") {
|
# if ($scheme != "https") {
|
||||||
# return 301 https://$host$request_uri;
|
# return 301 https://$host$request_uri;
|
||||||
|
@ -41,6 +41,10 @@ i.styledIcon.story:before {
|
|||||||
content: "\ef2d";
|
content: "\ef2d";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
i.styledIcon.epic:before {
|
||||||
|
content: '\ef30';
|
||||||
|
}
|
||||||
|
|
||||||
i.styledIcon.arrowDown:before {
|
i.styledIcon.arrowDown:before {
|
||||||
content: "\ea92";
|
content: "\ea92";
|
||||||
}
|
}
|
||||||
|
@ -11,12 +11,17 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.styledRte > .bar > .row {
|
.styledRte > .bar > .row {
|
||||||
padding: 5px 0;
|
padding: 0 0 var(--rte-indent) 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
margin: 0 var(--rte-indent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.styledRte > .bar > .row:first-child {
|
||||||
|
padding: var(--rte-indent) 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.styledRte > .bar > .row > .group {
|
.styledRte > .bar > .row > .group {
|
||||||
padding: 0 5px 0 0;
|
padding: 0 var(--rte-indent) 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.styledRte > .bar > .row > .group:first-child {
|
.styledRte > .bar > .row > .group:first-child {
|
||||||
@ -30,7 +35,21 @@
|
|||||||
.styledRte > .bar > .row > .group > .styledRteButton > .styledButton,
|
.styledRte > .bar > .row > .group > .styledRteButton > .styledButton,
|
||||||
.styledRte > .bar > .row > .group > .styledButton,
|
.styledRte > .bar > .row > .group > .styledButton,
|
||||||
.styledRte > .bar > .row > .group > span.headingList {
|
.styledRte > .bar > .row > .group > span.headingList {
|
||||||
margin: 0 5px;
|
margin-right: var(--rte-indent);
|
||||||
|
font-size: var(--small-font-size);
|
||||||
|
}
|
||||||
|
|
||||||
|
.styledRte > .bar > .row > .group.font > span.headingList > .headingOption {
|
||||||
|
margin-right: var(--rte-indent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.styledRte > .bar > .row > .group > .styledRteButton > .styledButton,
|
||||||
|
.styledRte > .bar > .row > .group > .styledRteButton > .styledButton > .styledIcon,
|
||||||
|
.styledRte > .bar > .row > .group > span.headingList > .headingOption > .styledButton,
|
||||||
|
.styledRte > .bar > .row > .group > span.headingList > .headingOption > .styledButton > span {
|
||||||
|
font-size: var(--small-font-size);
|
||||||
|
line-height: calc(2 * var(--small-font-size));
|
||||||
|
height: calc(2 * var(--small-font-size));
|
||||||
}
|
}
|
||||||
|
|
||||||
.styledRte > .bar > .row > .group > .headingList {
|
.styledRte > .bar > .row > .group > .headingList {
|
||||||
@ -64,6 +83,14 @@
|
|||||||
margin-left: 25px;
|
margin-left: 25px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.styledRte > .editorWrapper > .editor ul > li {
|
||||||
|
list-style: square;
|
||||||
|
}
|
||||||
|
|
||||||
|
.styledRte > .editorWrapper > .editor ol > li {
|
||||||
|
list-style: decimal;
|
||||||
|
}
|
||||||
|
|
||||||
.styledRte > .editorWrapper > .editor table {
|
.styledRte > .editorWrapper > .editor table {
|
||||||
table-layout: fixed;
|
table-layout: fixed;
|
||||||
border-spacing: 0;
|
border-spacing: 0;
|
||||||
@ -74,13 +101,17 @@
|
|||||||
min-width: 24px;
|
min-width: 24px;
|
||||||
min-height: 24px;
|
min-height: 24px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 5px;
|
padding: var(--rte-indent);
|
||||||
border-bottom: 1px solid var(--borderLight);
|
border-bottom: 1px solid var(--borderLight);
|
||||||
border-right: 1px solid var(--borderLight);
|
border-right: 1px solid var(--borderLight);
|
||||||
border-left: 1px solid var(--borderLight);
|
border-left: 1px solid var(--borderLight);
|
||||||
border-top: 1px solid var(--borderLight);
|
border-top: 1px solid var(--borderLight);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.styledRte > .editorWrapper > .editor *:focus {
|
||||||
|
background: var(--secondary);
|
||||||
|
}
|
||||||
|
|
||||||
/**********************************************************/
|
/**********************************************************/
|
||||||
/* Table tooltip */
|
/* Table tooltip */
|
||||||
/**********************************************************/
|
/**********************************************************/
|
||||||
@ -113,7 +144,7 @@
|
|||||||
width: 24px;
|
width: 24px;
|
||||||
height: 24px;
|
height: 24px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 5px;
|
padding: var(--rte-indent);
|
||||||
border-bottom: 1px solid var(--borderLight);
|
border-bottom: 1px solid var(--borderLight);
|
||||||
border-right: 1px solid var(--borderLight);
|
border-right: 1px solid var(--borderLight);
|
||||||
border-left: 1px solid var(--borderLight);
|
border-left: 1px solid var(--borderLight);
|
||||||
|
@ -25,6 +25,7 @@
|
|||||||
--task: rgb(79, 173, 230); /* blue */
|
--task: rgb(79, 173, 230); /* blue */
|
||||||
--bug: rgb(228, 77, 66); /* red */
|
--bug: rgb(228, 77, 66); /* red */
|
||||||
--story: rgb(101, 186, 67); /* green */
|
--story: rgb(101, 186, 67); /* green */
|
||||||
|
--epic: rgb(186, 142, 67); /* gold */
|
||||||
}
|
}
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
@ -66,6 +67,13 @@
|
|||||||
--font-medium: "CircularStdMedium";
|
--font-medium: "CircularStdMedium";
|
||||||
--font-bold: "CircularStdBold";
|
--font-bold: "CircularStdBold";
|
||||||
--font-black: "CircularStdBlack";
|
--font-black: "CircularStdBlack";
|
||||||
|
--normal-font-size: 1rem;
|
||||||
|
--small-font-size: .8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root /* margin & padding */
|
||||||
|
{
|
||||||
|
--rte-indent: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
:root { /* user without avatar */
|
:root { /* user without avatar */
|
||||||
|
@ -6,7 +6,8 @@ rm -Rf tmp
|
|||||||
mkdir -p tmp
|
mkdir -p tmp
|
||||||
mkdir -p target
|
mkdir -p target
|
||||||
|
|
||||||
wasm-pack build --mode normal --dev --out-name jirs --out-dir ./tmp --target web -- --verbose
|
wasm-pack build --mode force --dev --out-name jirs --out-dir ./tmp --target web -- --verbose
|
||||||
|
|
||||||
../target/debug/jirs-css -i ./js/styles.css -O ./tmp/styles.css
|
../target/debug/jirs-css -i ./js/styles.css -O ./tmp/styles.css
|
||||||
|
|
||||||
cp -r ./static/* ./tmp
|
cp -r ./static/* ./tmp
|
||||||
|
@ -288,17 +288,18 @@ fn window_events(_model: &Model) -> Vec<EventHandler<Msg>> {
|
|||||||
.active_element()
|
.active_element()
|
||||||
.map(|el| el.tag_name())
|
.map(|el| el.tag_name())
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
let key = match tag_name.to_lowercase().as_str() {
|
let key = match tag_name.to_lowercase().as_str() {
|
||||||
"input" | "textarea" => "".to_string(),
|
"" | "input" | "textarea" => return None,
|
||||||
_ => event.key(),
|
_ => event.key(),
|
||||||
};
|
};
|
||||||
|
|
||||||
Msg::GlobalKeyDown {
|
Some(Msg::GlobalKeyDown {
|
||||||
key,
|
key,
|
||||||
shift: event.shift_key(),
|
shift: event.shift_key(),
|
||||||
ctrl: event.ctrl_key(),
|
ctrl: event.ctrl_key(),
|
||||||
alt: event.alt_key(),
|
alt: event.alt_key(),
|
||||||
}
|
})
|
||||||
},
|
},
|
||||||
)]
|
)]
|
||||||
}
|
}
|
||||||
|
@ -26,6 +26,7 @@ pub fn update(msg: &Msg, model: &mut crate::model::Model, orders: &mut impl Orde
|
|||||||
_ => return,
|
_ => return,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
modal.title_state.update(msg);
|
||||||
modal.assignees_state.update(msg, orders);
|
modal.assignees_state.update(msg, orders);
|
||||||
modal.reporter_state.update(msg, orders);
|
modal.reporter_state.update(msg, orders);
|
||||||
modal.type_state.update(msg, orders);
|
modal.type_state.update(msg, orders);
|
||||||
@ -37,7 +38,7 @@ pub fn update(msg: &Msg, model: &mut crate::model::Model, orders: &mut impl Orde
|
|||||||
let project_id = model.project.as_ref().map(|p| p.id).unwrap_or_default();
|
let project_id = model.project.as_ref().map(|p| p.id).unwrap_or_default();
|
||||||
|
|
||||||
let payload = jirs_data::CreateIssuePayload {
|
let payload = jirs_data::CreateIssuePayload {
|
||||||
title: modal.title.clone(),
|
title: modal.title_state.value.clone(),
|
||||||
issue_type: modal.issue_type,
|
issue_type: modal.issue_type,
|
||||||
issue_status_id: modal.issue_status_id,
|
issue_status_id: modal.issue_status_id,
|
||||||
priority: modal.priority,
|
priority: modal.priority,
|
||||||
@ -64,9 +65,6 @@ pub fn update(msg: &Msg, model: &mut crate::model::Model, orders: &mut impl Orde
|
|||||||
Msg::StrInputChanged(FieldId::AddIssueModal(IssueFieldId::Description), value) => {
|
Msg::StrInputChanged(FieldId::AddIssueModal(IssueFieldId::Description), value) => {
|
||||||
modal.description = Some(value.clone());
|
modal.description = Some(value.clone());
|
||||||
}
|
}
|
||||||
Msg::StrInputChanged(FieldId::AddIssueModal(IssueFieldId::Title), value) => {
|
|
||||||
modal.title = value.clone();
|
|
||||||
}
|
|
||||||
|
|
||||||
// IssueTypeAddIssueModal
|
// IssueTypeAddIssueModal
|
||||||
Msg::StyledSelectChanged(
|
Msg::StyledSelectChanged(
|
||||||
@ -144,7 +142,7 @@ pub fn view(model: &Model, modal: &AddIssueModal) -> Node<Msg> {
|
|||||||
.into_node();
|
.into_node();
|
||||||
|
|
||||||
let short_summary = StyledInput::build(FieldId::AddIssueModal(IssueFieldId::Title))
|
let short_summary = StyledInput::build(FieldId::AddIssueModal(IssueFieldId::Title))
|
||||||
.valid(true)
|
.state(&modal.title_state)
|
||||||
.build()
|
.build()
|
||||||
.into_node();
|
.into_node();
|
||||||
let short_summary_field = StyledField::build()
|
let short_summary_field = StyledField::build()
|
||||||
@ -269,7 +267,8 @@ pub fn view(model: &Model, modal: &AddIssueModal) -> Node<Msg> {
|
|||||||
.add_class("ActionButton")
|
.add_class("ActionButton")
|
||||||
.on_click(mouse_ev(Ev::Click, |ev| {
|
.on_click(mouse_ev(Ev::Click, |ev| {
|
||||||
ev.stop_propagation();
|
ev.stop_propagation();
|
||||||
Msg::AddIssue
|
ev.prevent_default();
|
||||||
|
Some(Msg::AddIssue)
|
||||||
}))
|
}))
|
||||||
.build()
|
.build()
|
||||||
.into_node();
|
.into_node();
|
||||||
@ -281,7 +280,8 @@ pub fn view(model: &Model, modal: &AddIssueModal) -> Node<Msg> {
|
|||||||
.text("Cancel")
|
.text("Cancel")
|
||||||
.on_click(mouse_ev(Ev::Click, |ev| {
|
.on_click(mouse_ev(Ev::Click, |ev| {
|
||||||
ev.stop_propagation();
|
ev.stop_propagation();
|
||||||
Msg::ModalDropped
|
ev.prevent_default();
|
||||||
|
Some(Msg::ModalDropped)
|
||||||
}))
|
}))
|
||||||
.build()
|
.build()
|
||||||
.into_node();
|
.into_node();
|
||||||
|
@ -154,7 +154,6 @@ impl EditIssueModal {
|
|||||||
|
|
||||||
#[derive(Clone, Debug, PartialOrd, PartialEq)]
|
#[derive(Clone, Debug, PartialOrd, PartialEq)]
|
||||||
pub struct AddIssueModal {
|
pub struct AddIssueModal {
|
||||||
pub title: String,
|
|
||||||
pub issue_type: IssueType,
|
pub issue_type: IssueType,
|
||||||
pub priority: IssuePriority,
|
pub priority: IssuePriority,
|
||||||
pub description: Option<String>,
|
pub description: Option<String>,
|
||||||
@ -168,6 +167,7 @@ pub struct AddIssueModal {
|
|||||||
pub issue_status_id: i32,
|
pub issue_status_id: i32,
|
||||||
|
|
||||||
// modal fields
|
// modal fields
|
||||||
|
pub title_state: StyledInputState,
|
||||||
pub type_state: StyledSelectState,
|
pub type_state: StyledSelectState,
|
||||||
pub reporter_state: StyledSelectState,
|
pub reporter_state: StyledSelectState,
|
||||||
pub assignees_state: StyledSelectState,
|
pub assignees_state: StyledSelectState,
|
||||||
@ -177,7 +177,6 @@ pub struct AddIssueModal {
|
|||||||
impl Default for AddIssueModal {
|
impl Default for AddIssueModal {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
title: Default::default(),
|
|
||||||
issue_type: Default::default(),
|
issue_type: Default::default(),
|
||||||
priority: Default::default(),
|
priority: Default::default(),
|
||||||
description: Default::default(),
|
description: Default::default(),
|
||||||
@ -189,6 +188,7 @@ impl Default for AddIssueModal {
|
|||||||
user_ids: Default::default(),
|
user_ids: Default::default(),
|
||||||
reporter_id: Default::default(),
|
reporter_id: Default::default(),
|
||||||
issue_status_id: Default::default(),
|
issue_status_id: Default::default(),
|
||||||
|
title_state: StyledInputState::new(FieldId::AddIssueModal(IssueFieldId::Title), ""),
|
||||||
type_state: StyledSelectState::new(FieldId::AddIssueModal(IssueFieldId::Type), vec![]),
|
type_state: StyledSelectState::new(FieldId::AddIssueModal(IssueFieldId::Type), vec![]),
|
||||||
reporter_state: StyledSelectState::new(
|
reporter_state: StyledSelectState::new(
|
||||||
FieldId::AddIssueModal(IssueFieldId::Reporter),
|
FieldId::AddIssueModal(IssueFieldId::Reporter),
|
||||||
|
@ -54,7 +54,7 @@ pub fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
|||||||
page.project_category_state.update(&msg, orders);
|
page.project_category_state.update(&msg, orders);
|
||||||
page.time_tracking.update(&msg);
|
page.time_tracking.update(&msg);
|
||||||
page.name.update(&msg);
|
page.name.update(&msg);
|
||||||
page.description_rte.update(&msg);
|
page.description_rte.update(&msg, orders);
|
||||||
|
|
||||||
match msg {
|
match msg {
|
||||||
Msg::StrInputChanged(FieldId::ProjectSettings(ProjectFieldId::Name), text) => {
|
Msg::StrInputChanged(FieldId::ProjectSettings(ProjectFieldId::Name), text) => {
|
||||||
|
@ -108,26 +108,7 @@ pub fn render(values: StyledEditor) -> Node<Msg> {
|
|||||||
.build()
|
.build()
|
||||||
.into_node();
|
.into_node();
|
||||||
|
|
||||||
let parsed = comrak::markdown_to_html(
|
let parsed = comrak::markdown_to_html(text.as_str(), &comrak::ComrakOptions::default());
|
||||||
text.as_str(),
|
|
||||||
&comrak::ComrakOptions {
|
|
||||||
hardbreaks: false,
|
|
||||||
smart: true,
|
|
||||||
github_pre_lang: true,
|
|
||||||
width: 0,
|
|
||||||
default_info_string: None,
|
|
||||||
unsafe_: false,
|
|
||||||
ext_strikethrough: true,
|
|
||||||
ext_tagfilter: true,
|
|
||||||
ext_table: true,
|
|
||||||
ext_autolink: true,
|
|
||||||
ext_tasklist: true,
|
|
||||||
ext_superscript: true,
|
|
||||||
ext_header_ids: None,
|
|
||||||
ext_footnotes: true,
|
|
||||||
ext_description_lists: true,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
let parsed_node = Node::from_html(parsed.as_str());
|
let parsed_node = Node::from_html(parsed.as_str());
|
||||||
|
|
||||||
let (editor_radio_node, view_radio_node) = match mode {
|
let (editor_radio_node, view_radio_node) = match mode {
|
||||||
@ -169,9 +150,9 @@ pub fn render(values: StyledEditor) -> Node<Msg> {
|
|||||||
],
|
],
|
||||||
label![
|
label![
|
||||||
if mode == Mode::Editor {
|
if mode == Mode::Editor {
|
||||||
class!["navbar editorTab activeTab"]
|
C!["navbar editorTab activeTab"]
|
||||||
} else {
|
} else {
|
||||||
class!["navbar editorTab"]
|
C!["navbar editorTab"]
|
||||||
},
|
},
|
||||||
attrs![At::For => editor_id.as_str()],
|
attrs![At::For => editor_id.as_str()],
|
||||||
"Editor",
|
"Editor",
|
||||||
|
@ -8,10 +8,13 @@ use crate::Msg;
|
|||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
#[derive(Copy, Clone, Debug)]
|
#[derive(Copy, Clone, Debug)]
|
||||||
pub enum Icon {
|
pub enum Icon {
|
||||||
Bug,
|
|
||||||
Stopwatch,
|
Stopwatch,
|
||||||
|
|
||||||
|
Bug,
|
||||||
Task,
|
Task,
|
||||||
Story,
|
Story,
|
||||||
|
Epic,
|
||||||
|
|
||||||
ArrowDown,
|
ArrowDown,
|
||||||
ArrowLeftCircle,
|
ArrowLeftCircle,
|
||||||
ArrowUp,
|
ArrowUp,
|
||||||
@ -96,7 +99,7 @@ pub enum Icon {
|
|||||||
impl Icon {
|
impl Icon {
|
||||||
pub fn to_color(self) -> Option<String> {
|
pub fn to_color(self) -> Option<String> {
|
||||||
match self {
|
match self {
|
||||||
Icon::Bug | Icon::Task | Icon::Story => Some(format!("var(--{})", self)),
|
Icon::Bug | Icon::Task | Icon::Story | Icon::Epic => Some(format!("var(--{})", self)),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -192,6 +195,7 @@ impl std::fmt::Display for Icon {
|
|||||||
Icon::Undo => "undo",
|
Icon::Undo => "undo",
|
||||||
Icon::ListingDots => "listing-dots",
|
Icon::ListingDots => "listing-dots",
|
||||||
Icon::ListingNumber => "listing-number",
|
Icon::ListingNumber => "listing-number",
|
||||||
|
Icon::Epic => "epic",
|
||||||
};
|
};
|
||||||
f.write_str(code)
|
f.write_str(code)
|
||||||
}
|
}
|
||||||
@ -203,6 +207,7 @@ impl From<IssueType> for Icon {
|
|||||||
IssueType::Task => Icon::Task,
|
IssueType::Task => Icon::Task,
|
||||||
IssueType::Bug => Icon::Bug,
|
IssueType::Bug => Icon::Bug,
|
||||||
IssueType::Story => Icon::Story,
|
IssueType::Story => Icon::Story,
|
||||||
|
IssueType::Epic => Icon::Epic,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -196,17 +196,15 @@ pub fn render(values: StyledInput) -> Node<Msg> {
|
|||||||
mut wrapper_class_list,
|
mut wrapper_class_list,
|
||||||
variant,
|
variant,
|
||||||
auto_focus,
|
auto_focus,
|
||||||
mut input_handlers,
|
input_handlers,
|
||||||
} = values;
|
} = values;
|
||||||
|
|
||||||
wrapper_class_list.push("styledInput".to_string());
|
|
||||||
wrapper_class_list.push(variant.to_string());
|
wrapper_class_list.push(variant.to_string());
|
||||||
wrapper_class_list.push(format!("{}", id));
|
wrapper_class_list.push(format!("{}", id));
|
||||||
if !valid {
|
if !valid {
|
||||||
wrapper_class_list.push("invalid".to_string());
|
wrapper_class_list.push("invalid".to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
input_class_list.push("inputElement".to_string());
|
|
||||||
input_class_list.push(variant.to_string());
|
input_class_list.push(variant.to_string());
|
||||||
if icon.is_some() {
|
if icon.is_some() {
|
||||||
input_class_list.push("withIcon".to_string());
|
input_class_list.push("withIcon".to_string());
|
||||||
@ -216,27 +214,37 @@ pub fn render(values: StyledInput) -> Node<Msg> {
|
|||||||
Some(icon) => StyledIcon::build(icon).build().into_node(),
|
Some(icon) => StyledIcon::build(icon).build().into_node(),
|
||||||
_ => empty![],
|
_ => empty![],
|
||||||
};
|
};
|
||||||
let field_id = id.clone();
|
let on_input = {
|
||||||
input_handlers.push(ev(Ev::Input, move |event| {
|
let field_id = id.clone();
|
||||||
event.stop_propagation();
|
ev(Ev::Input, move |event| {
|
||||||
let target = event.target().unwrap();
|
event.stop_propagation();
|
||||||
let input = seed::to_input(&target);
|
let target = event.target().unwrap();
|
||||||
let value = input.value();
|
let input = seed::to_input(&target);
|
||||||
Msg::StrInputChanged(field_id, value)
|
let value = input.value();
|
||||||
}));
|
Msg::StrInputChanged(field_id, value)
|
||||||
input_handlers.push(ev(Ev::KeyUp, move |event| {
|
})
|
||||||
event.stop_propagation();
|
};
|
||||||
None as Option<Msg>
|
let on_keyup = {
|
||||||
}));
|
ev(Ev::KeyUp, move |event| {
|
||||||
input_handlers.push(ev(Ev::Click, move |event| {
|
event.stop_propagation();
|
||||||
event.stop_propagation();
|
None as Option<Msg>
|
||||||
None as Option<Msg>
|
})
|
||||||
}));
|
};
|
||||||
|
let on_click = {
|
||||||
|
ev(Ev::Click, move |event| {
|
||||||
|
event.stop_propagation();
|
||||||
|
None as Option<Msg>
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
div![
|
div![
|
||||||
|
C!["styledInput"],
|
||||||
attrs!(At::Class => wrapper_class_list.join(" ")),
|
attrs!(At::Class => wrapper_class_list.join(" ")),
|
||||||
icon,
|
icon,
|
||||||
|
on_click,
|
||||||
|
on_keyup,
|
||||||
seed::input![
|
seed::input![
|
||||||
|
C!["inputElement"],
|
||||||
attrs![
|
attrs![
|
||||||
At::Id => format!("{}", id),
|
At::Id => format!("{}", id),
|
||||||
At::Class => input_class_list.join(" "),
|
At::Class => input_class_list.join(" "),
|
||||||
@ -249,6 +257,7 @@ pub fn render(values: StyledInput) -> Node<Msg> {
|
|||||||
} else {
|
} else {
|
||||||
vec![]
|
vec![]
|
||||||
},
|
},
|
||||||
|
on_input,
|
||||||
input_handlers,
|
input_handlers,
|
||||||
],
|
],
|
||||||
]
|
]
|
||||||
|
@ -8,6 +8,7 @@ use crate::{FieldId, Msg};
|
|||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub enum HeadingSize {
|
pub enum HeadingSize {
|
||||||
|
Normal,
|
||||||
H1,
|
H1,
|
||||||
H2,
|
H2,
|
||||||
H3,
|
H3,
|
||||||
@ -16,20 +17,37 @@ pub enum HeadingSize {
|
|||||||
H6,
|
H6,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl HeadingSize {
|
||||||
|
fn all() -> Vec<Self> {
|
||||||
|
use HeadingSize::*;
|
||||||
|
|
||||||
|
vec![Normal, H1, H2, H3, H4, H5, H6]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl ToString for HeadingSize {
|
impl ToString for HeadingSize {
|
||||||
fn to_string(&self) -> String {
|
fn to_string(&self) -> String {
|
||||||
|
use HeadingSize::*;
|
||||||
|
|
||||||
match self {
|
match self {
|
||||||
HeadingSize::H1 => "H1",
|
Normal => "Normal",
|
||||||
HeadingSize::H2 => "H2",
|
H1 => "H1",
|
||||||
HeadingSize::H3 => "H3",
|
H2 => "H2",
|
||||||
HeadingSize::H4 => "H4",
|
H3 => "H3",
|
||||||
HeadingSize::H5 => "H5",
|
H4 => "H4",
|
||||||
HeadingSize::H6 => "H6",
|
H5 => "H5",
|
||||||
|
H6 => "H6",
|
||||||
}
|
}
|
||||||
.to_string()
|
.to_string()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum RteIndentMsg {
|
||||||
|
Increase,
|
||||||
|
Decrease,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum RteMsg {
|
pub enum RteMsg {
|
||||||
Bold,
|
Bold,
|
||||||
@ -56,6 +74,9 @@ pub enum RteMsg {
|
|||||||
TableSetColumns(u16),
|
TableSetColumns(u16),
|
||||||
TableSetVisibility(bool),
|
TableSetVisibility(bool),
|
||||||
InsertTable { rows: u16, cols: u16 },
|
InsertTable { rows: u16, cols: u16 },
|
||||||
|
ChangeIndent(RteIndentMsg),
|
||||||
|
|
||||||
|
RequestFocus(uuid::Uuid),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@ -101,19 +122,42 @@ impl RteMsg {
|
|||||||
RteMsg::JustifyLeft => Some(ExecCommand::new("justifyLeft")),
|
RteMsg::JustifyLeft => Some(ExecCommand::new("justifyLeft")),
|
||||||
RteMsg::JustifyRight => Some(ExecCommand::new("justifyRight")),
|
RteMsg::JustifyRight => Some(ExecCommand::new("justifyRight")),
|
||||||
RteMsg::InsertParagraph => Some(ExecCommand::new("insertParagraph")),
|
RteMsg::InsertParagraph => Some(ExecCommand::new("insertParagraph")),
|
||||||
RteMsg::InsertHeading(heading) => {
|
RteMsg::InsertHeading(heading) => match heading {
|
||||||
Some(ExecCommand::new_with_param("heading", heading.to_string()))
|
HeadingSize::H1
|
||||||
}
|
| HeadingSize::H2
|
||||||
|
| HeadingSize::H3
|
||||||
|
| HeadingSize::H4
|
||||||
|
| HeadingSize::H5
|
||||||
|
| HeadingSize::H6 => {
|
||||||
|
Some(ExecCommand::new_with_param("heading", heading.to_string()))
|
||||||
|
}
|
||||||
|
HeadingSize::Normal => Some(ExecCommand::new_with_param("formatBlock", "div")),
|
||||||
|
},
|
||||||
RteMsg::InsertUnorderedList => Some(ExecCommand::new("insertUnorderedList")),
|
RteMsg::InsertUnorderedList => Some(ExecCommand::new("insertUnorderedList")),
|
||||||
RteMsg::InsertOrderedList => Some(ExecCommand::new("insertOrderedList")),
|
RteMsg::InsertOrderedList => Some(ExecCommand::new("insertOrderedList")),
|
||||||
RteMsg::RemoveFormat => Some(ExecCommand::new("removeFormat")),
|
RteMsg::RemoveFormat => Some(ExecCommand::new("removeFormat")),
|
||||||
RteMsg::Subscript => Some(ExecCommand::new("subscript")),
|
RteMsg::Subscript => Some(ExecCommand::new("subscript")),
|
||||||
RteMsg::Superscript => Some(ExecCommand::new("superscript")),
|
RteMsg::Superscript => Some(ExecCommand::new("superscript")),
|
||||||
RteMsg::InsertTable { .. } => None,
|
RteMsg::InsertTable { .. } => None,
|
||||||
|
|
||||||
|
// indent
|
||||||
|
RteMsg::ChangeIndent(RteIndentMsg::Increase) => Some(ExecCommand::new("indent")),
|
||||||
|
RteMsg::ChangeIndent(RteIndentMsg::Decrease) => Some(ExecCommand::new("outdent")),
|
||||||
|
|
||||||
// outer
|
// outer
|
||||||
RteMsg::TableSetColumns(..)
|
RteMsg::TableSetColumns(..)
|
||||||
| RteMsg::TableSetRows(..)
|
| RteMsg::TableSetRows(..)
|
||||||
| RteMsg::TableSetVisibility(..) => None,
|
| RteMsg::TableSetVisibility(..) => None,
|
||||||
|
|
||||||
|
RteMsg::RequestFocus(identifier) => {
|
||||||
|
let res = seed::document().query_selector(format!("#{}", identifier).as_str());
|
||||||
|
if let Ok(Some(el)) = res {
|
||||||
|
if let Ok(el) = el.dyn_into::<web_sys::HtmlElement>() {
|
||||||
|
el.focus().is_ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -131,6 +175,7 @@ pub struct StyledRteState {
|
|||||||
pub field_id: FieldId,
|
pub field_id: FieldId,
|
||||||
pub table_tooltip: StyledRteTableState,
|
pub table_tooltip: StyledRteTableState,
|
||||||
range: Option<web_sys::Range>,
|
range: Option<web_sys::Range>,
|
||||||
|
identifier: uuid::Uuid,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StyledRteState {
|
impl StyledRteState {
|
||||||
@ -144,10 +189,11 @@ impl StyledRteState {
|
|||||||
cols: 3,
|
cols: 3,
|
||||||
},
|
},
|
||||||
range: None,
|
range: None,
|
||||||
|
identifier: uuid::Uuid::new_v4(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update(&mut self, msg: &Msg) {
|
pub fn update(&mut self, msg: &Msg, orders: &mut impl Orders<Msg>) {
|
||||||
let m = match msg {
|
let m = match msg {
|
||||||
Msg::Rte(m, field) if field == &self.field_id => m,
|
Msg::Rte(m, field) if field == &self.field_id => m,
|
||||||
_ => return,
|
_ => return,
|
||||||
@ -166,6 +212,7 @@ impl StyledRteState {
|
|||||||
if self.restore_range().is_err() {
|
if self.restore_range().is_err() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
self.schedule_focus(orders);
|
||||||
}
|
}
|
||||||
_ => match m {
|
_ => match m {
|
||||||
RteMsg::TableSetRows(n) => {
|
RteMsg::TableSetRows(n) => {
|
||||||
@ -209,6 +256,7 @@ impl StyledRteState {
|
|||||||
if let Err(e) = r.insert_node(&table) {
|
if let Err(e) = r.insert_node(&table) {
|
||||||
log!(e);
|
log!(e);
|
||||||
}
|
}
|
||||||
|
self.schedule_focus(orders);
|
||||||
}
|
}
|
||||||
_ => log!(m),
|
_ => log!(m),
|
||||||
},
|
},
|
||||||
@ -241,11 +289,20 @@ impl StyledRteState {
|
|||||||
})?;
|
})?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn schedule_focus(&self, orders: &mut impl Orders<Msg>) {
|
||||||
|
let field_id = self.field_id.clone();
|
||||||
|
let identifier = self.identifier.clone();
|
||||||
|
orders.perform_cmd(cmds::timeout(200, move || {
|
||||||
|
Msg::Rte(RteMsg::RequestFocus(identifier), field_id)
|
||||||
|
}));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct StyledRte {
|
pub struct StyledRte {
|
||||||
field_id: FieldId,
|
field_id: FieldId,
|
||||||
table_tooltip: StyledRteTableState,
|
table_tooltip: StyledRteTableState,
|
||||||
|
identifier: Option<uuid::Uuid>,
|
||||||
// value: String,
|
// value: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -259,6 +316,7 @@ impl StyledRte {
|
|||||||
rows: 0,
|
rows: 0,
|
||||||
cols: 0,
|
cols: 0,
|
||||||
},
|
},
|
||||||
|
identifier: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -273,12 +331,14 @@ pub struct StyledRteBuilder {
|
|||||||
field_id: FieldId,
|
field_id: FieldId,
|
||||||
value: String,
|
value: String,
|
||||||
table_tooltip: StyledRteTableState,
|
table_tooltip: StyledRteTableState,
|
||||||
|
identifier: Option<uuid::Uuid>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StyledRteBuilder {
|
impl StyledRteBuilder {
|
||||||
pub fn state(mut self, state: &StyledRteState) -> Self {
|
pub fn state(mut self, state: &StyledRteState) -> Self {
|
||||||
self.value = state.value.clone();
|
self.value = state.value.clone();
|
||||||
self.table_tooltip = state.table_tooltip.clone();
|
self.table_tooltip = state.table_tooltip.clone();
|
||||||
|
self.identifier = Some(state.identifier.clone());
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -287,6 +347,7 @@ impl StyledRteBuilder {
|
|||||||
field_id: self.field_id,
|
field_id: self.field_id,
|
||||||
// value: self.value,
|
// value: self.value,
|
||||||
table_tooltip: self.table_tooltip,
|
table_tooltip: self.table_tooltip,
|
||||||
|
identifier: self.identifier,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -373,13 +434,14 @@ pub fn render(values: StyledRte) -> Node<Msg> {
|
|||||||
ev.stop_propagation();
|
ev.stop_propagation();
|
||||||
None as Option<Msg>
|
None as Option<Msg>
|
||||||
});
|
});
|
||||||
let id = values.field_id.to_string();
|
|
||||||
|
let id = values.identifier.unwrap_or_default().to_string();
|
||||||
|
|
||||||
div![
|
div![
|
||||||
class!["styledRte"],
|
C!["styledRte"],
|
||||||
attrs![At::Id => id],
|
attrs![At::Id => id],
|
||||||
div![
|
div![
|
||||||
class!["bar"],
|
C!["bar"],
|
||||||
first_row(&values),
|
first_row(&values),
|
||||||
second_row(&values),
|
second_row(&values),
|
||||||
// brush_button,
|
// brush_button,
|
||||||
@ -392,9 +454,9 @@ pub fn render(values: StyledRte) -> Node<Msg> {
|
|||||||
// text_width_button,
|
// text_width_button,
|
||||||
],
|
],
|
||||||
div![
|
div![
|
||||||
class!["editorWrapper"],
|
C!["editorWrapper"],
|
||||||
div![
|
div![
|
||||||
class!["editor"],
|
C!["editor"],
|
||||||
attrs![At::ContentEditable => true],
|
attrs![At::ContentEditable => true],
|
||||||
capture_event
|
capture_event
|
||||||
],
|
],
|
||||||
@ -633,29 +695,22 @@ fn second_row(values: &StyledRte) -> Node<Msg> {
|
|||||||
None as Option<Msg>
|
None as Option<Msg>
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
let options: Vec<Node<Msg>> = vec![
|
let options: Vec<Node<Msg>> = HeadingSize::all()
|
||||||
HeadingSize::H1,
|
.into_iter()
|
||||||
HeadingSize::H2,
|
.map(|h| {
|
||||||
HeadingSize::H3,
|
let field_id = values.field_id.clone();
|
||||||
HeadingSize::H4,
|
let button = StyledButton::build()
|
||||||
HeadingSize::H5,
|
.text(h.to_string())
|
||||||
HeadingSize::H6,
|
.on_click(mouse_ev(Ev::Click, move |ev| {
|
||||||
]
|
ev.prevent_default();
|
||||||
.into_iter()
|
Some(Msg::Rte(RteMsg::InsertHeading(h), field_id))
|
||||||
.map(|h| {
|
}))
|
||||||
let field_id = values.field_id.clone();
|
.empty()
|
||||||
let button = StyledButton::build()
|
.build()
|
||||||
.text(h.to_string())
|
.into_node();
|
||||||
.on_click(mouse_ev(Ev::Click, move |ev| {
|
span![class!["headingOption"], button]
|
||||||
ev.prevent_default();
|
})
|
||||||
Some(Msg::Rte(RteMsg::InsertHeading(h), field_id))
|
.collect();
|
||||||
}))
|
|
||||||
.empty()
|
|
||||||
.build()
|
|
||||||
.into_node();
|
|
||||||
span![class!["headingOption"], button]
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
let heading_button = span![class!["headingList"], options];
|
let heading_button = span![class!["headingList"], options];
|
||||||
|
|
||||||
/*let _field_id = values.field_id.clone();
|
/*let _field_id = values.field_id.clone();
|
||||||
@ -757,20 +812,28 @@ fn second_row(values: &StyledRte) -> Node<Msg> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let indent_outdent = {
|
let indent_outdent = {
|
||||||
|
let field_id = values.field_id.clone();
|
||||||
let indent_button = styled_rte_button(
|
let indent_button = styled_rte_button(
|
||||||
"Indent",
|
"Indent",
|
||||||
Icon::Indent,
|
Icon::Indent,
|
||||||
mouse_ev(Ev::Click, move |ev| {
|
mouse_ev(Ev::Click, move |ev| {
|
||||||
ev.prevent_default();
|
ev.prevent_default();
|
||||||
None as Option<Msg>
|
Some(Msg::Rte(
|
||||||
|
RteMsg::ChangeIndent(RteIndentMsg::Increase),
|
||||||
|
field_id,
|
||||||
|
))
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
let field_id = values.field_id.clone();
|
||||||
let outdent_button = styled_rte_button(
|
let outdent_button = styled_rte_button(
|
||||||
"Outdent",
|
"Outdent",
|
||||||
Icon::Outdent,
|
Icon::Outdent,
|
||||||
mouse_ev(Ev::Click, move |ev| {
|
mouse_ev(Ev::Click, move |ev| {
|
||||||
ev.prevent_default();
|
ev.prevent_default();
|
||||||
None as Option<Msg>
|
Some(Msg::Rte(
|
||||||
|
RteMsg::ChangeIndent(RteIndentMsg::Decrease),
|
||||||
|
field_id,
|
||||||
|
))
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
div![class!["group indentOutdent"], indent_button, outdent_button]
|
div![class!["group indentOutdent"], indent_button, outdent_button]
|
||||||
|
@ -8,3 +8,4 @@ import("/jirs.js").then(async module => {
|
|||||||
const host_url = `${location.protocol}//${process.env.JIRS_SERVER_BIND}:${process.env.JIRS_SERVER_PORT}`;
|
const host_url = `${location.protocol}//${process.env.JIRS_SERVER_BIND}:${process.env.JIRS_SERVER_PORT}`;
|
||||||
module.render(host_url, wsUrl());
|
module.render(host_url, wsUrl());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -41,13 +41,19 @@ pub enum IssueType {
|
|||||||
Task,
|
Task,
|
||||||
Bug,
|
Bug,
|
||||||
Story,
|
Story,
|
||||||
|
Epic,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToVec for IssueType {
|
impl ToVec for IssueType {
|
||||||
type Item = IssueType;
|
type Item = IssueType;
|
||||||
|
|
||||||
fn ordered() -> Vec<Self> {
|
fn ordered() -> Vec<Self> {
|
||||||
vec![IssueType::Task, IssueType::Bug, IssueType::Story]
|
vec![
|
||||||
|
IssueType::Task,
|
||||||
|
IssueType::Bug,
|
||||||
|
IssueType::Story,
|
||||||
|
IssueType::Epic,
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -63,6 +69,7 @@ impl IssueType {
|
|||||||
IssueType::Task => "Task",
|
IssueType::Task => "Task",
|
||||||
IssueType::Bug => "Bug",
|
IssueType::Bug => "Bug",
|
||||||
IssueType::Story => "Story",
|
IssueType::Story => "Story",
|
||||||
|
IssueType::Epic => "Epic",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -73,6 +80,7 @@ impl Into<u32> for IssueType {
|
|||||||
IssueType::Task => 1,
|
IssueType::Task => 1,
|
||||||
IssueType::Bug => 2,
|
IssueType::Bug => 2,
|
||||||
IssueType::Story => 3,
|
IssueType::Story => 3,
|
||||||
|
IssueType::Epic => 4,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -83,6 +91,7 @@ impl Into<IssueType> for u32 {
|
|||||||
1 => IssueType::Task,
|
1 => IssueType::Task,
|
||||||
2 => IssueType::Bug,
|
2 => IssueType::Bug,
|
||||||
3 => IssueType::Story,
|
3 => IssueType::Story,
|
||||||
|
4 => IssueType::Epic,
|
||||||
_ => IssueType::Task,
|
_ => IssueType::Task,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -94,6 +103,7 @@ impl std::fmt::Display for IssueType {
|
|||||||
IssueType::Task => f.write_str("task"),
|
IssueType::Task => f.write_str("task"),
|
||||||
IssueType::Bug => f.write_str("bug"),
|
IssueType::Bug => f.write_str("bug"),
|
||||||
IssueType::Story => f.write_str("story"),
|
IssueType::Story => f.write_str("story"),
|
||||||
|
IssueType::Epic => f.write_str("epic"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -55,6 +55,7 @@ fn issue_type_from_sql(bytes: Option<&[u8]>) -> deserialize::Result<IssueType> {
|
|||||||
b"task" => Ok(IssueType::Task),
|
b"task" => Ok(IssueType::Task),
|
||||||
b"bug" => Ok(IssueType::Bug),
|
b"bug" => Ok(IssueType::Bug),
|
||||||
b"story" => Ok(IssueType::Story),
|
b"story" => Ok(IssueType::Story),
|
||||||
|
b"epic" => Ok(IssueType::Epic),
|
||||||
_ => Ok(IssueType::Task),
|
_ => Ok(IssueType::Task),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -77,6 +78,7 @@ impl ToSql<IssueTypeType, Pg> for IssueType {
|
|||||||
IssueType::Task => out.write_all(b"task")?,
|
IssueType::Task => out.write_all(b"task")?,
|
||||||
IssueType::Story => out.write_all(b"story")?,
|
IssueType::Story => out.write_all(b"story")?,
|
||||||
IssueType::Bug => out.write_all(b"bug")?,
|
IssueType::Bug => out.write_all(b"bug")?,
|
||||||
|
IssueType::Epic => out.write_all(b"epic")?,
|
||||||
}
|
}
|
||||||
Ok(IsNull::No)
|
Ok(IsNull::No)
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,20 @@
|
|||||||
|
ALTER TABLE "issues"
|
||||||
|
ALTER COLUMN "issue_type"
|
||||||
|
SET DATA TYPE TEXT
|
||||||
|
USING "issue_type"::TEXT;
|
||||||
|
|
||||||
|
UPDATE "issues"
|
||||||
|
SET "issue_type" = 'task'
|
||||||
|
WHERE "issue_type" = 'epic';
|
||||||
|
|
||||||
|
DROP TYPE IF EXISTS "IssueTypeType" CASCADE;
|
||||||
|
CREATE TYPE "IssueTypeType" AS ENUM (
|
||||||
|
'task',
|
||||||
|
'bug',
|
||||||
|
'story'
|
||||||
|
);
|
||||||
|
|
||||||
|
ALTER TABLE "issues"
|
||||||
|
ALTER COLUMN "issue_type"
|
||||||
|
SET DATA TYPE "IssueTypeType"
|
||||||
|
USING "issue_type"::"IssueTypeType";
|
@ -0,0 +1,17 @@
|
|||||||
|
ALTER TABLE "issues"
|
||||||
|
ALTER COLUMN "issue_type"
|
||||||
|
SET DATA TYPE TEXT
|
||||||
|
USING "issue_type"::TEXT;
|
||||||
|
|
||||||
|
DROP TYPE IF EXISTS "IssueTypeType" CASCADE;
|
||||||
|
CREATE TYPE "IssueTypeType" AS ENUM (
|
||||||
|
'task',
|
||||||
|
'bug',
|
||||||
|
'story',
|
||||||
|
'epic'
|
||||||
|
);
|
||||||
|
|
||||||
|
ALTER TABLE "issues"
|
||||||
|
ALTER COLUMN "issue_type"
|
||||||
|
SET DATA TYPE "IssueTypeType"
|
||||||
|
USING "issue_type"::"IssueTypeType";
|
@ -226,7 +226,7 @@ impl Message for CreateIssue {
|
|||||||
impl Handler<CreateIssue> for DbExecutor {
|
impl Handler<CreateIssue> for DbExecutor {
|
||||||
type Result = Result<Issue, ServiceErrors>;
|
type Result = Result<Issue, ServiceErrors>;
|
||||||
|
|
||||||
fn handle(&mut self, msg: CreateIssue, _ctx: &mut Self::Context) -> Self::Result {
|
fn handle(&mut self, msg: CreateIssue, ctx: &mut Self::Context) -> Self::Result {
|
||||||
use crate::schema::issue_assignees::dsl;
|
use crate::schema::issue_assignees::dsl;
|
||||||
use crate::schema::issues::dsl::issues;
|
use crate::schema::issues::dsl::issues;
|
||||||
|
|
||||||
@ -241,10 +241,28 @@ impl Handler<CreateIssue> for DbExecutor {
|
|||||||
.get_result::<i32>(conn)
|
.get_result::<i32>(conn)
|
||||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
||||||
|
|
||||||
|
info!("{:?}", msg.issue_type);
|
||||||
|
info!("msg.issue_status_id {:?}", msg.issue_status_id);
|
||||||
|
|
||||||
|
let issue_status_id = if msg.issue_status_id == 0 {
|
||||||
|
self.handle(
|
||||||
|
crate::db::issue_statuses::LoadIssueStatuses {
|
||||||
|
project_id: msg.project_id,
|
||||||
|
},
|
||||||
|
ctx,
|
||||||
|
)
|
||||||
|
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?
|
||||||
|
.get(0)
|
||||||
|
.ok_or_else(|| ServiceErrors::DatabaseConnectionLost)?
|
||||||
|
.id
|
||||||
|
} else {
|
||||||
|
msg.issue_status_id
|
||||||
|
};
|
||||||
|
|
||||||
let form = crate::models::CreateIssueForm {
|
let form = crate::models::CreateIssueForm {
|
||||||
title: msg.title,
|
title: msg.title,
|
||||||
issue_type: msg.issue_type,
|
issue_type: msg.issue_type,
|
||||||
issue_status_id: msg.issue_status_id,
|
issue_status_id,
|
||||||
priority: msg.priority,
|
priority: msg.priority,
|
||||||
list_position,
|
list_position,
|
||||||
description: msg.description,
|
description: msg.description,
|
||||||
|
@ -159,6 +159,53 @@ table! {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
table! {
|
||||||
|
use diesel::sql_types::*;
|
||||||
|
use jirs_data::sql::*;
|
||||||
|
|
||||||
|
/// Representation of the `issue_statuses` table.
|
||||||
|
///
|
||||||
|
/// (Automatically generated by Diesel.)
|
||||||
|
issue_statuses (id) {
|
||||||
|
/// The `id` column of the `issue_statuses` table.
|
||||||
|
///
|
||||||
|
/// Its SQL type is `Int4`.
|
||||||
|
///
|
||||||
|
/// (Automatically generated by Diesel.)
|
||||||
|
id -> Int4,
|
||||||
|
/// The `name` column of the `issue_statuses` table.
|
||||||
|
///
|
||||||
|
/// Its SQL type is `Varchar`.
|
||||||
|
///
|
||||||
|
/// (Automatically generated by Diesel.)
|
||||||
|
name -> Varchar,
|
||||||
|
/// The `position` column of the `issue_statuses` table.
|
||||||
|
///
|
||||||
|
/// Its SQL type is `Int4`.
|
||||||
|
///
|
||||||
|
/// (Automatically generated by Diesel.)
|
||||||
|
position -> Int4,
|
||||||
|
/// The `project_id` column of the `issue_statuses` table.
|
||||||
|
///
|
||||||
|
/// Its SQL type is `Int4`.
|
||||||
|
///
|
||||||
|
/// (Automatically generated by Diesel.)
|
||||||
|
project_id -> Int4,
|
||||||
|
/// The `created_at` column of the `issue_statuses` table.
|
||||||
|
///
|
||||||
|
/// Its SQL type is `Timestamp`.
|
||||||
|
///
|
||||||
|
/// (Automatically generated by Diesel.)
|
||||||
|
created_at -> Timestamp,
|
||||||
|
/// The `updated_at` column of the `issue_statuses` table.
|
||||||
|
///
|
||||||
|
/// Its SQL type is `Timestamp`.
|
||||||
|
///
|
||||||
|
/// (Automatically generated by Diesel.)
|
||||||
|
updated_at -> Timestamp,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
table! {
|
table! {
|
||||||
use diesel::sql_types::*;
|
use diesel::sql_types::*;
|
||||||
use jirs_data::sql::*;
|
use jirs_data::sql::*;
|
||||||
@ -260,53 +307,6 @@ table! {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
table! {
|
|
||||||
use diesel::sql_types::*;
|
|
||||||
use jirs_data::sql::*;
|
|
||||||
|
|
||||||
/// Representation of the `issue_statuses` table.
|
|
||||||
///
|
|
||||||
/// (Automatically generated by Diesel.)
|
|
||||||
issue_statuses (id) {
|
|
||||||
/// The `id` column of the `issue_statuses` table.
|
|
||||||
///
|
|
||||||
/// Its SQL type is `Int4`.
|
|
||||||
///
|
|
||||||
/// (Automatically generated by Diesel.)
|
|
||||||
id -> Int4,
|
|
||||||
/// The `name` column of the `issue_statuses` table.
|
|
||||||
///
|
|
||||||
/// Its SQL type is `Varchar`.
|
|
||||||
///
|
|
||||||
/// (Automatically generated by Diesel.)
|
|
||||||
name -> Varchar,
|
|
||||||
/// The `position` column of the `issue_statuses` table.
|
|
||||||
///
|
|
||||||
/// Its SQL type is `Int4`.
|
|
||||||
///
|
|
||||||
/// (Automatically generated by Diesel.)
|
|
||||||
position -> Int4,
|
|
||||||
/// The `project_id` column of the `issue_statuses` table.
|
|
||||||
///
|
|
||||||
/// Its SQL type is `Int4`.
|
|
||||||
///
|
|
||||||
/// (Automatically generated by Diesel.)
|
|
||||||
project_id -> Int4,
|
|
||||||
/// The `created_at` column of the `issue_statuses` table.
|
|
||||||
///
|
|
||||||
/// Its SQL type is `Timestamp`.
|
|
||||||
///
|
|
||||||
/// (Automatically generated by Diesel.)
|
|
||||||
created_at -> Timestamp,
|
|
||||||
/// The `updated_at` column of the `issue_statuses` table.
|
|
||||||
///
|
|
||||||
/// Its SQL type is `Timestamp`.
|
|
||||||
///
|
|
||||||
/// (Automatically generated by Diesel.)
|
|
||||||
updated_at -> Timestamp,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
table! {
|
table! {
|
||||||
use diesel::sql_types::*;
|
use diesel::sql_types::*;
|
||||||
use jirs_data::sql::*;
|
use jirs_data::sql::*;
|
||||||
@ -608,8 +608,8 @@ allow_tables_to_appear_in_same_query!(
|
|||||||
comments,
|
comments,
|
||||||
invitations,
|
invitations,
|
||||||
issue_assignees,
|
issue_assignees,
|
||||||
issues,
|
|
||||||
issue_statuses,
|
issue_statuses,
|
||||||
|
issues,
|
||||||
messages,
|
messages,
|
||||||
projects,
|
projects,
|
||||||
tokens,
|
tokens,
|
||||||
|
Loading…
Reference in New Issue
Block a user