Proc macros
Fix update issue view. Refactor jirs-data.
This commit is contained in:
parent
c96a5021f0
commit
57adfac3a4
15
Cargo.lock
generated
15
Cargo.lock
generated
@ -1055,6 +1055,18 @@ dependencies = [
|
|||||||
"version_check 0.1.5",
|
"version_check 0.1.5",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "derive_enum_iter"
|
||||||
|
version = "0.1.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "derive_enum_primitive"
|
||||||
|
version = "0.1.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "derive_enum_sql"
|
||||||
|
version = "0.1.0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "derive_more"
|
name = "derive_more"
|
||||||
version = "0.99.11"
|
version = "0.99.11"
|
||||||
@ -1965,6 +1977,9 @@ version = "0.1.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"actix 0.10.0",
|
"actix 0.10.0",
|
||||||
"chrono",
|
"chrono",
|
||||||
|
"derive_enum_iter",
|
||||||
|
"derive_enum_primitive",
|
||||||
|
"derive_enum_sql",
|
||||||
"diesel",
|
"diesel",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
@ -15,6 +15,9 @@ members = [
|
|||||||
"./jirs-css",
|
"./jirs-css",
|
||||||
"./shared/jirs-config",
|
"./shared/jirs-config",
|
||||||
"./shared/jirs-data",
|
"./shared/jirs-data",
|
||||||
|
"./derive/derive_enum_iter",
|
||||||
|
"./derive/derive_enum_primitive",
|
||||||
|
"./derive/derive_enum_sql",
|
||||||
"./actors/highlight-actor",
|
"./actors/highlight-actor",
|
||||||
"./actors/database-actor",
|
"./actors/database-actor",
|
||||||
"./actors/database-actor/database_actor-derive",
|
"./actors/database-actor/database_actor-derive",
|
||||||
|
@ -7,7 +7,7 @@ index 00d1c0b..5b82ccf 100644
|
|||||||
+
|
+
|
||||||
table! {
|
table! {
|
||||||
use diesel::sql_types::*;
|
use diesel::sql_types::*;
|
||||||
use jirs_data::sql::*;
|
use jirs_data::*;
|
||||||
|
|
||||||
/// Representation of the `comments` table.
|
/// Representation of the `comments` table.
|
||||||
///
|
///
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
table! {
|
table! {
|
||||||
use diesel::sql_types::*;
|
use diesel::sql_types::*;
|
||||||
use jirs_data::sql::*;
|
use jirs_data::*;
|
||||||
|
|
||||||
/// Representation of the `comments` table.
|
/// Representation of the `comments` table.
|
||||||
///
|
///
|
||||||
@ -49,7 +49,7 @@ table! {
|
|||||||
|
|
||||||
table! {
|
table! {
|
||||||
use diesel::sql_types::*;
|
use diesel::sql_types::*;
|
||||||
use jirs_data::sql::*;
|
use jirs_data::*;
|
||||||
|
|
||||||
/// Representation of the `epics` table.
|
/// Representation of the `epics` table.
|
||||||
///
|
///
|
||||||
@ -108,7 +108,7 @@ table! {
|
|||||||
|
|
||||||
table! {
|
table! {
|
||||||
use diesel::sql_types::*;
|
use diesel::sql_types::*;
|
||||||
use jirs_data::sql::*;
|
use jirs_data::*;
|
||||||
|
|
||||||
/// Representation of the `invitations` table.
|
/// Representation of the `invitations` table.
|
||||||
///
|
///
|
||||||
@ -179,7 +179,7 @@ table! {
|
|||||||
|
|
||||||
table! {
|
table! {
|
||||||
use diesel::sql_types::*;
|
use diesel::sql_types::*;
|
||||||
use jirs_data::sql::*;
|
use jirs_data::*;
|
||||||
|
|
||||||
/// Representation of the `issue_assignees` table.
|
/// Representation of the `issue_assignees` table.
|
||||||
///
|
///
|
||||||
@ -220,7 +220,7 @@ table! {
|
|||||||
|
|
||||||
table! {
|
table! {
|
||||||
use diesel::sql_types::*;
|
use diesel::sql_types::*;
|
||||||
use jirs_data::sql::*;
|
use jirs_data::*;
|
||||||
|
|
||||||
/// Representation of the `issue_statuses` table.
|
/// Representation of the `issue_statuses` table.
|
||||||
///
|
///
|
||||||
@ -267,7 +267,7 @@ table! {
|
|||||||
|
|
||||||
table! {
|
table! {
|
||||||
use diesel::sql_types::*;
|
use diesel::sql_types::*;
|
||||||
use jirs_data::sql::*;
|
use jirs_data::*;
|
||||||
|
|
||||||
/// Representation of the `issues` table.
|
/// Representation of the `issues` table.
|
||||||
///
|
///
|
||||||
@ -374,7 +374,7 @@ table! {
|
|||||||
|
|
||||||
table! {
|
table! {
|
||||||
use diesel::sql_types::*;
|
use diesel::sql_types::*;
|
||||||
use jirs_data::sql::*;
|
use jirs_data::*;
|
||||||
|
|
||||||
/// Representation of the `messages` table.
|
/// Representation of the `messages` table.
|
||||||
///
|
///
|
||||||
@ -439,7 +439,7 @@ table! {
|
|||||||
|
|
||||||
table! {
|
table! {
|
||||||
use diesel::sql_types::*;
|
use diesel::sql_types::*;
|
||||||
use jirs_data::sql::*;
|
use jirs_data::*;
|
||||||
|
|
||||||
/// Representation of the `projects` table.
|
/// Representation of the `projects` table.
|
||||||
///
|
///
|
||||||
@ -498,7 +498,7 @@ table! {
|
|||||||
|
|
||||||
table! {
|
table! {
|
||||||
use diesel::sql_types::*;
|
use diesel::sql_types::*;
|
||||||
use jirs_data::sql::*;
|
use jirs_data::*;
|
||||||
|
|
||||||
/// Representation of the `tokens` table.
|
/// Representation of the `tokens` table.
|
||||||
///
|
///
|
||||||
@ -551,7 +551,7 @@ table! {
|
|||||||
|
|
||||||
table! {
|
table! {
|
||||||
use diesel::sql_types::*;
|
use diesel::sql_types::*;
|
||||||
use jirs_data::sql::*;
|
use jirs_data::*;
|
||||||
|
|
||||||
/// Representation of the `user_projects` table.
|
/// Representation of the `user_projects` table.
|
||||||
///
|
///
|
||||||
@ -610,7 +610,7 @@ table! {
|
|||||||
|
|
||||||
table! {
|
table! {
|
||||||
use diesel::sql_types::*;
|
use diesel::sql_types::*;
|
||||||
use jirs_data::sql::*;
|
use jirs_data::*;
|
||||||
|
|
||||||
/// Representation of the `users` table.
|
/// Representation of the `users` table.
|
||||||
///
|
///
|
||||||
|
16
derive/derive_enum_iter/Cargo.toml
Normal file
16
derive/derive_enum_iter/Cargo.toml
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
[package]
|
||||||
|
name = "derive_enum_iter"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Adrian Wozniak <adrian.wozniak@ita-prog.pl>"]
|
||||||
|
edition = "2018"
|
||||||
|
description = "JIRS (Simplified JIRA in Rust) shared data types"
|
||||||
|
repository = "https://gitlab.com/adrian.wozniak/jirs"
|
||||||
|
license = "MPL-2.0"
|
||||||
|
#license-file = "../LICENSE"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "derive_enum_iter"
|
||||||
|
path = "./src/lib.rs"
|
||||||
|
proc-macro = true
|
||||||
|
|
||||||
|
[dependencies]
|
117
derive/derive_enum_iter/src/lib.rs
Normal file
117
derive/derive_enum_iter/src/lib.rs
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
extern crate proc_macro;
|
||||||
|
|
||||||
|
use proc_macro::{TokenStream, TokenTree};
|
||||||
|
|
||||||
|
#[proc_macro_derive(EnumIter)]
|
||||||
|
pub fn derive_enum_iter(item: TokenStream) -> TokenStream {
|
||||||
|
let mut it = item.into_iter().peekable();
|
||||||
|
while let Some(token) = it.peek() {
|
||||||
|
if let TokenTree::Ident(_) = token {
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
it.next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(TokenTree::Ident(ident)) = it.next() {
|
||||||
|
if ident.to_string().as_str() != "pub" {
|
||||||
|
panic!("Expect to find keyword pub but was found {:?}", ident)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
panic!("Expect to find keyword pub but nothing was found")
|
||||||
|
}
|
||||||
|
if let Some(TokenTree::Ident(ident)) = it.next() {
|
||||||
|
if ident.to_string().as_str() != "enum" {
|
||||||
|
panic!("Expect to find keyword struct but was found {:?}", ident)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
panic!("Expect to find keyword struct but nothing was found")
|
||||||
|
}
|
||||||
|
let name = it
|
||||||
|
.next()
|
||||||
|
.expect("Expect to struct name but nothing was found");
|
||||||
|
|
||||||
|
let mut variants = vec![];
|
||||||
|
if let Some(TokenTree::Group(group)) = it.next() {
|
||||||
|
for token in group.stream() {
|
||||||
|
if let TokenTree::Ident(ident) = token {
|
||||||
|
variants.push(ident.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
panic!("Enum variants group expected");
|
||||||
|
}
|
||||||
|
if variants.is_empty() {
|
||||||
|
panic!("Enum cannot be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut code = format!(
|
||||||
|
r#"
|
||||||
|
pub struct {name}Iter(Option<{name}>);
|
||||||
|
|
||||||
|
impl std::iter::Iterator for {name}Iter {{
|
||||||
|
type Item = {name};
|
||||||
|
|
||||||
|
fn count(self) -> usize {{
|
||||||
|
{len}
|
||||||
|
}}
|
||||||
|
|
||||||
|
fn last(self) -> Option<Self::Item> {{
|
||||||
|
Some({name}::{last})
|
||||||
|
}}
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {{
|
||||||
|
let o = match self.0 {{
|
||||||
|
"#,
|
||||||
|
name = name,
|
||||||
|
len = variants.len(),
|
||||||
|
last = variants.last().unwrap(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut last_variant = "";
|
||||||
|
for (idx, variant) in variants.iter().enumerate() {
|
||||||
|
match idx {
|
||||||
|
0 => code.push_str(
|
||||||
|
format!(
|
||||||
|
"None => Some({name}::{variant}),\n",
|
||||||
|
variant = variant,
|
||||||
|
name = name
|
||||||
|
)
|
||||||
|
.as_str(),
|
||||||
|
),
|
||||||
|
_ if idx == variants.len() - 1 => code.push_str("_ => None,\n"),
|
||||||
|
_ => code.push_str(
|
||||||
|
format!(
|
||||||
|
"Some({name}::{last_variant}) => Some({name}::{variant}),\n",
|
||||||
|
last_variant = last_variant,
|
||||||
|
variant = variant,
|
||||||
|
name = name,
|
||||||
|
)
|
||||||
|
.as_str(),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
last_variant = variant.as_str();
|
||||||
|
}
|
||||||
|
|
||||||
|
code.push_str(
|
||||||
|
format!(
|
||||||
|
r#"
|
||||||
|
}};
|
||||||
|
self.0 = o;
|
||||||
|
o
|
||||||
|
}}
|
||||||
|
}}
|
||||||
|
impl std::iter::IntoIterator for {name} {{
|
||||||
|
type Item = {name};
|
||||||
|
type IntoIter = {name}Iter;
|
||||||
|
|
||||||
|
fn into_iter(self) -> Self::IntoIter {{
|
||||||
|
{name}Iter(None)
|
||||||
|
}}
|
||||||
|
}}
|
||||||
|
"#,
|
||||||
|
name = name
|
||||||
|
)
|
||||||
|
.as_str(),
|
||||||
|
);
|
||||||
|
code.parse().unwrap()
|
||||||
|
}
|
16
derive/derive_enum_primitive/Cargo.toml
Normal file
16
derive/derive_enum_primitive/Cargo.toml
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
[package]
|
||||||
|
name = "derive_enum_primitive"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Adrian Wozniak <adrian.wozniak@ita-prog.pl>"]
|
||||||
|
edition = "2018"
|
||||||
|
description = "JIRS (Simplified JIRA in Rust) shared data types"
|
||||||
|
repository = "https://gitlab.com/adrian.wozniak/jirs"
|
||||||
|
license = "MPL-2.0"
|
||||||
|
#license-file = "../LICENSE"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "derive_enum_primitive"
|
||||||
|
path = "./src/lib.rs"
|
||||||
|
proc-macro = true
|
||||||
|
|
||||||
|
[dependencies]
|
194
derive/derive_enum_primitive/src/lib.rs
Normal file
194
derive/derive_enum_primitive/src/lib.rs
Normal file
@ -0,0 +1,194 @@
|
|||||||
|
extern crate proc_macro;
|
||||||
|
|
||||||
|
use proc_macro::{TokenStream, TokenTree};
|
||||||
|
|
||||||
|
fn into_str(name: &str, variants: &[String]) -> String {
|
||||||
|
let mut code = format!(
|
||||||
|
r#"
|
||||||
|
#[cfg(feature = "frontend")]
|
||||||
|
impl {name} {{
|
||||||
|
pub fn to_str(&self) -> &'static str {{
|
||||||
|
match self {{
|
||||||
|
"#,
|
||||||
|
name = name,
|
||||||
|
);
|
||||||
|
|
||||||
|
for variant in variants {
|
||||||
|
let lower = variant.to_lowercase();
|
||||||
|
code.push_str(
|
||||||
|
format!(
|
||||||
|
" {name}::{variant} => \"{lower}\",\n",
|
||||||
|
variant = variant,
|
||||||
|
name = name,
|
||||||
|
lower = lower
|
||||||
|
)
|
||||||
|
.as_str(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
code.push_str(" }\n }\n}");
|
||||||
|
code
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_str(name: &str, variants: &[String]) -> String {
|
||||||
|
let mut code = format!(
|
||||||
|
r#"
|
||||||
|
#[cfg(feature = "frontend")]
|
||||||
|
impl FromStr for {name} {{
|
||||||
|
type Err = String;
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {{
|
||||||
|
match s {{
|
||||||
|
"#,
|
||||||
|
name = name,
|
||||||
|
);
|
||||||
|
|
||||||
|
for variant in variants {
|
||||||
|
let lower = variant.to_lowercase();
|
||||||
|
code.push_str(
|
||||||
|
format!(
|
||||||
|
" \"{lower}\" => Ok({name}::{variant}),\n",
|
||||||
|
variant = variant,
|
||||||
|
name = name,
|
||||||
|
lower = lower
|
||||||
|
)
|
||||||
|
.as_str(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
code.push_str(
|
||||||
|
format!(
|
||||||
|
" _ => Err(format!(\"Unknown {name} {{}}\", s)),",
|
||||||
|
name = name
|
||||||
|
)
|
||||||
|
.as_str(),
|
||||||
|
);
|
||||||
|
code.push_str(" }\n }\n}");
|
||||||
|
code
|
||||||
|
}
|
||||||
|
|
||||||
|
fn into_label(name: &str, variants: &[String]) -> String {
|
||||||
|
let mut code = format!(
|
||||||
|
r#"
|
||||||
|
#[cfg(feature = "frontend")]
|
||||||
|
impl {name} {{
|
||||||
|
pub fn to_label(&self) -> &'static str {{
|
||||||
|
match self {{
|
||||||
|
"#,
|
||||||
|
name = name,
|
||||||
|
);
|
||||||
|
|
||||||
|
for variant in variants {
|
||||||
|
code.push_str(
|
||||||
|
format!(
|
||||||
|
" {name}::{variant} => \"{variant}\",\n",
|
||||||
|
variant = variant,
|
||||||
|
name = name,
|
||||||
|
)
|
||||||
|
.as_str(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
code.push_str(" }\n }\n}");
|
||||||
|
code
|
||||||
|
}
|
||||||
|
|
||||||
|
fn into_u32(name: &str, variants: &[String]) -> String {
|
||||||
|
let mut code = format!(
|
||||||
|
r#"
|
||||||
|
impl Into<u32> for {name} {{
|
||||||
|
fn into(self) -> u32 {{
|
||||||
|
match self {{
|
||||||
|
"#,
|
||||||
|
name = name
|
||||||
|
);
|
||||||
|
for (idx, variant) in variants.iter().enumerate() {
|
||||||
|
code.push_str(
|
||||||
|
format!(
|
||||||
|
" {name}::{variant} => {idx},\n",
|
||||||
|
variant = variant,
|
||||||
|
name = name,
|
||||||
|
idx = idx
|
||||||
|
)
|
||||||
|
.as_str(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
code.push_str(" }\n }\n}");
|
||||||
|
code
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_u32(name: &str, variants: &[String]) -> String {
|
||||||
|
let mut code = format!(
|
||||||
|
r#"
|
||||||
|
impl Into<{name}> for u32 {{
|
||||||
|
fn into(self) -> {name} {{
|
||||||
|
match self {{
|
||||||
|
"#,
|
||||||
|
name = name
|
||||||
|
);
|
||||||
|
for (idx, variant) in variants.iter().enumerate() {
|
||||||
|
code.push_str(
|
||||||
|
format!(
|
||||||
|
" {idx} => {name}::{variant},\n",
|
||||||
|
variant = variant,
|
||||||
|
name = name,
|
||||||
|
idx = idx
|
||||||
|
)
|
||||||
|
.as_str(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
code.push_str(format!(" _ => {name}::default(),\n", name = name,).as_str());
|
||||||
|
code.push_str(" }\n }\n}");
|
||||||
|
code
|
||||||
|
}
|
||||||
|
|
||||||
|
#[proc_macro_derive(EnumPrimitive)]
|
||||||
|
pub fn derive_enum_primitive(item: TokenStream) -> TokenStream {
|
||||||
|
let mut it = item.into_iter().peekable();
|
||||||
|
while let Some(token) = it.peek() {
|
||||||
|
if let TokenTree::Ident(_) = token {
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
it.next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(TokenTree::Ident(ident)) = it.next() {
|
||||||
|
if ident.to_string().as_str() != "pub" {
|
||||||
|
panic!("Expect to find keyword pub but was found {:?}", ident)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
panic!("Expect to find keyword pub but nothing was found")
|
||||||
|
}
|
||||||
|
if let Some(TokenTree::Ident(ident)) = it.next() {
|
||||||
|
if ident.to_string().as_str() != "enum" {
|
||||||
|
panic!("Expect to find keyword struct but was found {:?}", ident)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
panic!("Expect to find keyword struct but nothing was found")
|
||||||
|
}
|
||||||
|
let name = it
|
||||||
|
.next()
|
||||||
|
.expect("Expect to struct name but nothing was found")
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
let mut variants = vec![];
|
||||||
|
if let Some(TokenTree::Group(group)) = it.next() {
|
||||||
|
for token in group.stream() {
|
||||||
|
if let TokenTree::Ident(ident) = token {
|
||||||
|
variants.push(ident.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
panic!("Enum variants group expected");
|
||||||
|
}
|
||||||
|
if variants.is_empty() {
|
||||||
|
panic!("Enum cannot be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut code = String::new();
|
||||||
|
|
||||||
|
code.push_str(from_str(&name, &variants).as_str());
|
||||||
|
code.push_str(into_str(&name, &variants).as_str());
|
||||||
|
code.push_str(into_label(&name, &variants).as_str());
|
||||||
|
code.push_str(into_u32(&name, &variants).as_str());
|
||||||
|
code.push_str(from_u32(&name, &variants).as_str());
|
||||||
|
|
||||||
|
code.parse().unwrap()
|
||||||
|
}
|
16
derive/derive_enum_sql/Cargo.toml
Normal file
16
derive/derive_enum_sql/Cargo.toml
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
[package]
|
||||||
|
name = "derive_enum_sql"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Adrian Wozniak <adrian.wozniak@ita-prog.pl>"]
|
||||||
|
edition = "2018"
|
||||||
|
description = "JIRS (Simplified JIRA in Rust) shared data types"
|
||||||
|
repository = "https://gitlab.com/adrian.wozniak/jirs"
|
||||||
|
license = "MPL-2.0"
|
||||||
|
#license-file = "../LICENSE"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "derive_enum_sql"
|
||||||
|
path = "./src/lib.rs"
|
||||||
|
proc-macro = true
|
||||||
|
|
||||||
|
[dependencies]
|
155
derive/derive_enum_sql/src/lib.rs
Normal file
155
derive/derive_enum_sql/src/lib.rs
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
extern crate proc_macro;
|
||||||
|
|
||||||
|
use proc_macro::{TokenStream, TokenTree};
|
||||||
|
|
||||||
|
fn to_lower_case(s: &str) -> String {
|
||||||
|
let mut lower = String::new();
|
||||||
|
for (idx, c) in s.chars().enumerate() {
|
||||||
|
if idx > 0 && c.is_uppercase() {
|
||||||
|
lower.push('_');
|
||||||
|
}
|
||||||
|
lower.push_str(c.to_lowercase().to_string().as_str());
|
||||||
|
}
|
||||||
|
lower
|
||||||
|
}
|
||||||
|
|
||||||
|
fn into_str(name: &str, variants: &[String]) -> String {
|
||||||
|
let mut code = format!(
|
||||||
|
r#"
|
||||||
|
#[cfg(feature = "backend")]
|
||||||
|
impl diesel::serialize::ToSql<{name}Type, diesel::pg::Pg> for {name} {{
|
||||||
|
fn to_sql<W: std::io::Write>(&self, out: &mut diesel::serialize::Output<W, diesel::pg::Pg>) -> diesel::serialize::Result {{
|
||||||
|
match *self {{
|
||||||
|
"#,
|
||||||
|
name = name,
|
||||||
|
);
|
||||||
|
|
||||||
|
for variant in variants {
|
||||||
|
let lower = to_lower_case(&variant);
|
||||||
|
code.push_str(
|
||||||
|
format!(
|
||||||
|
" {name}::{variant} => out.write_all(b\"{lower}\")?,\n",
|
||||||
|
variant = variant,
|
||||||
|
name = name,
|
||||||
|
lower = lower
|
||||||
|
)
|
||||||
|
.as_str(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
code.push_str(" };\n Ok(diesel::serialize::IsNull::No)\n }\n}");
|
||||||
|
code
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_str(name: &str, variants: &[String]) -> String {
|
||||||
|
let mut code = format!(
|
||||||
|
r#"
|
||||||
|
#[cfg(feature = "backend")]
|
||||||
|
impl {name} {{
|
||||||
|
fn from_diesel_bytes(bytes: Option<&[u8]>) -> diesel::deserialize::Result<{name}> {{
|
||||||
|
match diesel::not_none!(bytes) {{
|
||||||
|
"#,
|
||||||
|
name = name,
|
||||||
|
);
|
||||||
|
|
||||||
|
for variant in variants {
|
||||||
|
let lower = to_lower_case(&variant);
|
||||||
|
code.push_str(
|
||||||
|
format!(
|
||||||
|
" b\"{lower}\" => Ok({name}::{variant}),\n",
|
||||||
|
variant = variant,
|
||||||
|
name = name,
|
||||||
|
lower = lower
|
||||||
|
)
|
||||||
|
.as_str(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
code.push_str(format!(" _ => Ok({name}::default()),", name = name).as_str());
|
||||||
|
code.push_str(" }\n }\n}");
|
||||||
|
code.push_str(
|
||||||
|
format!(
|
||||||
|
r#"
|
||||||
|
#[cfg(feature = "backend")]
|
||||||
|
impl diesel::deserialize::FromSql<{name}Type, diesel::pg::Pg> for {name} {{
|
||||||
|
fn from_sql(bytes: Option<&[u8]>) -> deserialize::Result<Self> {{
|
||||||
|
{name}::from_diesel_bytes(bytes)
|
||||||
|
}}
|
||||||
|
}}
|
||||||
|
|
||||||
|
#[cfg(feature = "backend")]
|
||||||
|
impl diesel::deserialize::FromSql<diesel::sql_types::Text, diesel::pg::Pg> for {name} {{
|
||||||
|
fn from_sql(bytes: Option<&[u8]>) -> deserialize::Result<Self> {{
|
||||||
|
{name}::from_diesel_bytes(bytes)
|
||||||
|
}}
|
||||||
|
}}
|
||||||
|
"#,
|
||||||
|
name = name
|
||||||
|
)
|
||||||
|
.as_str(),
|
||||||
|
);
|
||||||
|
code
|
||||||
|
}
|
||||||
|
|
||||||
|
#[proc_macro_derive(EnumSql)]
|
||||||
|
pub fn derive_enum_sql(item: TokenStream) -> TokenStream {
|
||||||
|
let mut it = item.into_iter().peekable();
|
||||||
|
while let Some(token) = it.peek() {
|
||||||
|
if let TokenTree::Ident(_) = token {
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
it.next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(TokenTree::Ident(ident)) = it.next() {
|
||||||
|
if ident.to_string().as_str() != "pub" {
|
||||||
|
panic!("Expect to find keyword pub but was found {:?}", ident)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
panic!("Expect to find keyword pub but nothing was found")
|
||||||
|
}
|
||||||
|
if let Some(TokenTree::Ident(ident)) = it.next() {
|
||||||
|
if ident.to_string().as_str() != "enum" {
|
||||||
|
panic!("Expect to find keyword struct but was found {:?}", ident)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
panic!("Expect to find keyword struct but nothing was found")
|
||||||
|
}
|
||||||
|
let name = it
|
||||||
|
.next()
|
||||||
|
.expect("Expect to struct name but nothing was found")
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
let mut variants = vec![];
|
||||||
|
if let Some(TokenTree::Group(group)) = it.next() {
|
||||||
|
for token in group.stream() {
|
||||||
|
if let TokenTree::Ident(ident) = token {
|
||||||
|
variants.push(ident.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
panic!("Enum variants group expected");
|
||||||
|
}
|
||||||
|
if variants.is_empty() {
|
||||||
|
panic!("Enum cannot be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut code = format!(
|
||||||
|
r#"
|
||||||
|
#[cfg(feature = "backend")]
|
||||||
|
#[derive(diesel::SqlType)]
|
||||||
|
#[postgres(type_name = "{name}Type")]
|
||||||
|
pub struct {name}Type;
|
||||||
|
|
||||||
|
#[cfg(feature = "backend")]
|
||||||
|
impl diesel::query_builder::QueryId for {name}Type {{
|
||||||
|
type QueryId = {name};
|
||||||
|
}}
|
||||||
|
"#,
|
||||||
|
name = name
|
||||||
|
);
|
||||||
|
|
||||||
|
code.push_str(from_str(&name, &variants).as_str());
|
||||||
|
code.push_str(into_str(&name, &variants).as_str());
|
||||||
|
|
||||||
|
code.parse().unwrap()
|
||||||
|
}
|
@ -3,6 +3,6 @@
|
|||||||
|
|
||||||
[print_schema]
|
[print_schema]
|
||||||
file = "actors/database-actor/src/schema.rs"
|
file = "actors/database-actor/src/schema.rs"
|
||||||
import_types = ["diesel::sql_types::*", "jirs_data::sql::*"]
|
import_types = ["diesel::sql_types::*", "jirs_data::*"]
|
||||||
with_docs = true
|
with_docs = true
|
||||||
patch_file = "./actors/database-actor/src/schema.patch"
|
patch_file = "./actors/database-actor/src/schema.patch"
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
#![feature(or_patterns, type_ascription)]
|
#![feature(or_patterns, type_ascription, trait_alias)]
|
||||||
|
|
||||||
use {
|
use {
|
||||||
crate::{
|
crate::{
|
||||||
|
@ -23,7 +23,7 @@ where
|
|||||||
let input = StyledSelect::build()
|
let input = StyledSelect::build()
|
||||||
.name("epic")
|
.name("epic")
|
||||||
.selected(selected)
|
.selected(selected)
|
||||||
.options(model.epics.iter().map(|epic| epic.to_child()).collect())
|
.options(model.epics.iter().map(|epic| epic.to_child()))
|
||||||
.normal()
|
.normal()
|
||||||
.clearable()
|
.clearable()
|
||||||
.text_filter(modal.epic_state().text_filter.as_str())
|
.text_filter(modal.epic_state().text_filter.as_str())
|
||||||
|
@ -1,125 +0,0 @@
|
|||||||
use {
|
|
||||||
crate::{
|
|
||||||
model::{ModalType, Model},
|
|
||||||
shared::{
|
|
||||||
find_issue,
|
|
||||||
styled_button::StyledButton,
|
|
||||||
styled_field::StyledField,
|
|
||||||
styled_input::{StyledInput, StyledInputState},
|
|
||||||
styled_modal::StyledModal,
|
|
||||||
styled_select::{StyledSelect, StyledSelectState},
|
|
||||||
tracking_widget::{fibonacci_values, tracking_widget},
|
|
||||||
ToChild, ToNode,
|
|
||||||
},
|
|
||||||
EditIssueModalSection, FieldId, Msg,
|
|
||||||
},
|
|
||||||
jirs_data::{IssueFieldId, IssueId, TimeTracking},
|
|
||||||
seed::{prelude::*, *},
|
|
||||||
};
|
|
||||||
|
|
||||||
// use crate::shared::styled_select_child::*;
|
|
||||||
|
|
||||||
pub fn value_for_time_tracking(v: &Option<i32>, time_tracking_type: &TimeTracking) -> String {
|
|
||||||
match (time_tracking_type, v.as_ref()) {
|
|
||||||
(TimeTracking::Untracked, _) => "".to_string(),
|
|
||||||
(TimeTracking::Fibonacci, Some(n)) => n.to_string(),
|
|
||||||
(TimeTracking::Hourly, Some(n)) => format!("{:.1}", *n as f64 / 10.0f64),
|
|
||||||
_ => "".to_string(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn view(model: &Model, issue_id: IssueId) -> Node<Msg> {
|
|
||||||
let _issue = match find_issue(model, issue_id) {
|
|
||||||
Some(issue) => issue,
|
|
||||||
_ => return empty![],
|
|
||||||
};
|
|
||||||
|
|
||||||
let edit_issue_modal = match model.modals.get(0) {
|
|
||||||
Some(ModalType::EditIssue(_, modal)) => modal,
|
|
||||||
_ => return empty![],
|
|
||||||
};
|
|
||||||
let time_tracking_type = model
|
|
||||||
.project
|
|
||||||
.as_ref()
|
|
||||||
.map(|p| p.time_tracking)
|
|
||||||
.unwrap_or_else(|| TimeTracking::Untracked);
|
|
||||||
|
|
||||||
let modal_title = div![C!["modalTitle"], "Time tracking"];
|
|
||||||
|
|
||||||
let tracking = tracking_widget(model, edit_issue_modal);
|
|
||||||
|
|
||||||
let time_spent_field = time_tracking_field(
|
|
||||||
time_tracking_type,
|
|
||||||
FieldId::EditIssueModal(EditIssueModalSection::Issue(IssueFieldId::TimeSpent)),
|
|
||||||
"Time spent",
|
|
||||||
&edit_issue_modal.time_spent,
|
|
||||||
&edit_issue_modal.time_spent_select,
|
|
||||||
);
|
|
||||||
let time_remaining_field = time_tracking_field(
|
|
||||||
time_tracking_type,
|
|
||||||
FieldId::EditIssueModal(EditIssueModalSection::Issue(IssueFieldId::TimeRemaining)),
|
|
||||||
"Time remaining",
|
|
||||||
&edit_issue_modal.time_remaining,
|
|
||||||
&edit_issue_modal.time_remaining_select,
|
|
||||||
);
|
|
||||||
|
|
||||||
let inputs = div![
|
|
||||||
C!["inputs"],
|
|
||||||
div![C!["inputContainer"], time_spent_field],
|
|
||||||
div![C!["inputContainer"], time_remaining_field]
|
|
||||||
];
|
|
||||||
|
|
||||||
let close = StyledButton::build()
|
|
||||||
.text("Done")
|
|
||||||
.on_click(mouse_ev(Ev::Click, |_| Msg::ModalDropped))
|
|
||||||
.build()
|
|
||||||
.into_node();
|
|
||||||
|
|
||||||
StyledModal::build()
|
|
||||||
.add_class("timeTrackingModal")
|
|
||||||
.children(vec![
|
|
||||||
modal_title,
|
|
||||||
tracking,
|
|
||||||
inputs,
|
|
||||||
div![C!["actions"], close],
|
|
||||||
])
|
|
||||||
.width(400)
|
|
||||||
.build()
|
|
||||||
.into_node()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn time_tracking_field(
|
|
||||||
time_tracking_type: TimeTracking,
|
|
||||||
field_id: FieldId,
|
|
||||||
label: &str,
|
|
||||||
input_state: &StyledInputState,
|
|
||||||
select_state: &StyledSelectState,
|
|
||||||
) -> Node<Msg> {
|
|
||||||
let fibonacci_values = fibonacci_values();
|
|
||||||
let input = match time_tracking_type {
|
|
||||||
TimeTracking::Untracked => empty![],
|
|
||||||
TimeTracking::Fibonacci => StyledSelect::build()
|
|
||||||
.selected(
|
|
||||||
select_state
|
|
||||||
.values
|
|
||||||
.iter()
|
|
||||||
.map(|n| (*n).to_child())
|
|
||||||
.collect(),
|
|
||||||
)
|
|
||||||
.state(select_state)
|
|
||||||
.options(fibonacci_values.iter().map(|v| v.to_child()).collect())
|
|
||||||
.build(field_id)
|
|
||||||
.into_node(),
|
|
||||||
TimeTracking::Hourly => StyledInput::build()
|
|
||||||
.state(input_state)
|
|
||||||
.valid(true)
|
|
||||||
.build(field_id)
|
|
||||||
.into_node(),
|
|
||||||
};
|
|
||||||
StyledField::build()
|
|
||||||
.input(input)
|
|
||||||
.label(label)
|
|
||||||
.build()
|
|
||||||
.into_node()
|
|
||||||
}
|
|
@ -7,11 +7,11 @@ use {
|
|||||||
styled_button::StyledButton, styled_date_time_input::StyledDateTimeInput,
|
styled_button::StyledButton, styled_date_time_input::StyledDateTimeInput,
|
||||||
styled_field::StyledField, styled_form::StyledForm, styled_input::StyledInput,
|
styled_field::StyledField, styled_form::StyledForm, styled_input::StyledInput,
|
||||||
styled_modal::StyledModal, styled_select::StyledSelect,
|
styled_modal::StyledModal, styled_select::StyledSelect,
|
||||||
styled_textarea::StyledTextarea, ToChild, ToNode,
|
styled_textarea::StyledTextarea, IntoChild, ToChild, ToNode,
|
||||||
},
|
},
|
||||||
FieldId, Msg,
|
FieldId, Msg,
|
||||||
},
|
},
|
||||||
jirs_data::{IssueFieldId, IssuePriority, ToVec},
|
jirs_data::{IssueFieldId, IssuePriority},
|
||||||
seed::{prelude::*, *},
|
seed::{prelude::*, *},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -124,12 +124,7 @@ fn issue_type_field(modal: &AddIssueModal) -> Node<Msg> {
|
|||||||
.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(
|
.options(Type::ordered().iter().map(|t| t.to_child().name("type")))
|
||||||
Type::ordered()
|
|
||||||
.iter()
|
|
||||||
.map(|t| t.to_child().name("type"))
|
|
||||||
.collect(),
|
|
||||||
)
|
|
||||||
.selected(vec![Type::from(
|
.selected(vec![Type::from(
|
||||||
modal.type_state.values.get(0).cloned().unwrap_or_default(),
|
modal.type_state.values.get(0).cloned().unwrap_or_default(),
|
||||||
)
|
)
|
||||||
@ -182,13 +177,7 @@ fn reporter_field(model: &Model, modal: &AddIssueModal) -> Node<Msg> {
|
|||||||
.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(
|
.options(model.users.iter().map(|u| u.to_child().name("reporter")))
|
||||||
model
|
|
||||||
.users
|
|
||||||
.iter()
|
|
||||||
.map(|u| u.to_child().name("reporter"))
|
|
||||||
.collect(),
|
|
||||||
)
|
|
||||||
.selected(
|
.selected(
|
||||||
model
|
model
|
||||||
.users
|
.users
|
||||||
@ -219,13 +208,7 @@ fn assignees_field(model: &Model, modal: &AddIssueModal) -> Node<Msg> {
|
|||||||
.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(
|
.options(model.users.iter().map(|u| u.to_child().name("assignees")))
|
||||||
model
|
|
||||||
.users
|
|
||||||
.iter()
|
|
||||||
.map(|u| u.to_child().name("assignees"))
|
|
||||||
.collect(),
|
|
||||||
)
|
|
||||||
.selected(
|
.selected(
|
||||||
model
|
model
|
||||||
.users
|
.users
|
||||||
@ -251,19 +234,15 @@ fn assignees_field(model: &Model, modal: &AddIssueModal) -> Node<Msg> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn issue_priority_field(modal: &AddIssueModal) -> Node<Msg> {
|
fn issue_priority_field(modal: &AddIssueModal) -> Node<Msg> {
|
||||||
|
let priorities = IssuePriority::default().into_iter();
|
||||||
let select_priority = StyledSelect::build()
|
let select_priority = StyledSelect::build()
|
||||||
.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(
|
.options(priorities.map(|p| p.into_child().name("priority")))
|
||||||
IssuePriority::ordered()
|
.selected(vec![modal.priority.into_child().name("priority")])
|
||||||
.iter()
|
|
||||||
.map(|p| p.to_child().name("priority"))
|
|
||||||
.collect(),
|
|
||||||
)
|
|
||||||
.selected(vec![modal.priority.to_child().name("priority")])
|
|
||||||
.build(FieldId::AddIssueModal(IssueFieldId::Priority))
|
.build(FieldId::AddIssueModal(IssueFieldId::Priority))
|
||||||
.into_node();
|
.into_node();
|
||||||
StyledField::build()
|
StyledField::build()
|
||||||
|
@ -21,6 +21,8 @@ pub fn update(msg: &Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
|||||||
Msg::ResourceChanged(ResourceKind::Issue, OperationKind::SingleModified, Some(id)) => {
|
Msg::ResourceChanged(ResourceKind::Issue, OperationKind::SingleModified, Some(id)) => {
|
||||||
if let Some(issue) = model.issues_by_id.get(id) {
|
if let Some(issue) = model.issues_by_id.get(id) {
|
||||||
modal.payload = issue.clone().into();
|
modal.payload = issue.clone().into();
|
||||||
|
modal.description_state.initial_text =
|
||||||
|
issue.description_text.clone().unwrap_or_default();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Msg::StyledSelectChanged(
|
Msg::StyledSelectChanged(
|
||||||
@ -139,7 +141,6 @@ pub fn update(msg: &Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
|||||||
FieldId::EditIssueModal(EditIssueModalSection::Issue(IssueFieldId::Description)),
|
FieldId::EditIssueModal(EditIssueModalSection::Issue(IssueFieldId::Description)),
|
||||||
value,
|
value,
|
||||||
) => {
|
) => {
|
||||||
// modal.payload.description = Some(value.clone());
|
|
||||||
modal.payload.description_text = Some(value.clone());
|
modal.payload.description_text = Some(value.clone());
|
||||||
send_ws_msg(
|
send_ws_msg(
|
||||||
WsMsg::IssueUpdate(
|
WsMsg::IssueUpdate(
|
||||||
@ -148,7 +149,7 @@ pub fn update(msg: &Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
|||||||
PayloadVariant::String(
|
PayloadVariant::String(
|
||||||
modal
|
modal
|
||||||
.payload
|
.payload
|
||||||
.description
|
.description_text
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.cloned()
|
.cloned()
|
||||||
.unwrap_or_default(),
|
.unwrap_or_default(),
|
||||||
|
@ -6,12 +6,13 @@ use {
|
|||||||
shared::{
|
shared::{
|
||||||
styled_avatar::StyledAvatar, styled_button::StyledButton, styled_editor::StyledEditor,
|
styled_avatar::StyledAvatar, styled_button::StyledButton, styled_editor::StyledEditor,
|
||||||
styled_field::StyledField, styled_icon::Icon, styled_input::StyledInput,
|
styled_field::StyledField, styled_icon::Icon, styled_input::StyledInput,
|
||||||
styled_select::StyledSelect, tracking_widget::tracking_link, ToChild, ToNode,
|
styled_select::StyledSelect, tracking_widget::tracking_link, IntoChild, ToChild,
|
||||||
|
ToNode,
|
||||||
},
|
},
|
||||||
EditIssueModalSection, FieldChange, FieldId, Msg,
|
EditIssueModalSection, FieldChange, FieldId, Msg,
|
||||||
},
|
},
|
||||||
comments::*,
|
comments::*,
|
||||||
jirs_data::{CommentFieldId, IssueFieldId, IssuePriority, IssueType, TimeTracking, ToVec},
|
jirs_data::{CommentFieldId, IssueFieldId, IssuePriority, IssueType, TimeTracking},
|
||||||
seed::{prelude::*, *},
|
seed::{prelude::*, *},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -41,7 +42,14 @@ fn modal_header(_model: &Model, modal: &EditIssueModal) -> Node<Msg> {
|
|||||||
let issue_id = *id;
|
let issue_id = *id;
|
||||||
|
|
||||||
let click_handler = mouse_ev(Ev::Click, move |_| {
|
let click_handler = mouse_ev(Ev::Click, move |_| {
|
||||||
let link = format!("http://localhost:7000/issues/{id}", id = issue_id);
|
let proto = seed::window().location().protocol().unwrap_or_default();
|
||||||
|
let hostname = seed::window().location().hostname().unwrap_or_default();
|
||||||
|
let link = format!(
|
||||||
|
"{proto}//{hostname}/issues/{id}",
|
||||||
|
proto = proto,
|
||||||
|
hostname = hostname,
|
||||||
|
id = issue_id
|
||||||
|
);
|
||||||
let el = match seed::html_document().create_element("textarea") {
|
let el = match seed::html_document().create_element("textarea") {
|
||||||
Ok(el) => el
|
Ok(el) => el
|
||||||
.dyn_ref::<web_sys::HtmlTextAreaElement>()
|
.dyn_ref::<web_sys::HtmlTextAreaElement>()
|
||||||
@ -95,7 +103,6 @@ fn modal_header(_model: &Model, modal: &EditIssueModal) -> Node<Msg> {
|
|||||||
.build()
|
.build()
|
||||||
.into_node();
|
.into_node();
|
||||||
|
|
||||||
let issue_types = IssueType::ordered();
|
|
||||||
let issue_type_select = StyledSelect::build()
|
let issue_type_select = StyledSelect::build()
|
||||||
.dropdown_width(150)
|
.dropdown_width(150)
|
||||||
.name("type")
|
.name("type")
|
||||||
@ -103,16 +110,15 @@ fn modal_header(_model: &Model, modal: &EditIssueModal) -> Node<Msg> {
|
|||||||
.opened(top_type_state.opened)
|
.opened(top_type_state.opened)
|
||||||
.valid(true)
|
.valid(true)
|
||||||
.options(
|
.options(
|
||||||
issue_types
|
IssueType::default()
|
||||||
.iter()
|
.into_iter()
|
||||||
.map(|t| t.to_child().name("type"))
|
.map(|t| t.into_child().name("type")),
|
||||||
.collect(),
|
|
||||||
)
|
)
|
||||||
.selected(vec![{
|
.selected(vec![{
|
||||||
let id = modal.id;
|
let id = modal.id;
|
||||||
let issue_type = &payload.issue_type;
|
let issue_type = &payload.issue_type;
|
||||||
issue_type
|
issue_type
|
||||||
.to_child()
|
.into_child()
|
||||||
.name("type")
|
.name("type")
|
||||||
.text_owned(format!("{} - {}", issue_type, id))
|
.text_owned(format!("{} - {}", issue_type, id))
|
||||||
}])
|
}])
|
||||||
@ -244,8 +250,7 @@ fn right_modal_column(model: &Model, modal: &EditIssueModal) -> Node<Msg> {
|
|||||||
model
|
model
|
||||||
.issue_statuses
|
.issue_statuses
|
||||||
.iter()
|
.iter()
|
||||||
.map(|opt| opt.to_child().name("status"))
|
.map(|opt| opt.to_child().name("status")),
|
||||||
.collect(),
|
|
||||||
)
|
)
|
||||||
.selected(
|
.selected(
|
||||||
model
|
model
|
||||||
@ -276,8 +281,7 @@ fn right_modal_column(model: &Model, modal: &EditIssueModal) -> Node<Msg> {
|
|||||||
model
|
model
|
||||||
.users
|
.users
|
||||||
.iter()
|
.iter()
|
||||||
.map(|user| user.to_child().name("assignees"))
|
.map(|user| user.to_child().name("assignees")),
|
||||||
.collect(),
|
|
||||||
)
|
)
|
||||||
.selected(
|
.selected(
|
||||||
model
|
model
|
||||||
@ -306,8 +310,7 @@ fn right_modal_column(model: &Model, modal: &EditIssueModal) -> Node<Msg> {
|
|||||||
model
|
model
|
||||||
.users
|
.users
|
||||||
.iter()
|
.iter()
|
||||||
.map(|user| user.to_child().name("reporter"))
|
.map(|user| user.to_child().name("reporter")),
|
||||||
.collect(),
|
|
||||||
)
|
)
|
||||||
.selected(
|
.selected(
|
||||||
model
|
model
|
||||||
@ -327,19 +330,17 @@ fn right_modal_column(model: &Model, modal: &EditIssueModal) -> Node<Msg> {
|
|||||||
.build()
|
.build()
|
||||||
.into_node();
|
.into_node();
|
||||||
|
|
||||||
let issue_priorities = IssuePriority::ordered();
|
|
||||||
let priority = StyledSelect::build()
|
let priority = StyledSelect::build()
|
||||||
.name("priority")
|
.name("priority")
|
||||||
.opened(priority_state.opened)
|
.opened(priority_state.opened)
|
||||||
.empty()
|
.empty()
|
||||||
.text_filter(priority_state.text_filter.as_str())
|
.text_filter(priority_state.text_filter.as_str())
|
||||||
.options(
|
.options(
|
||||||
issue_priorities
|
IssuePriority::default()
|
||||||
.iter()
|
.into_iter()
|
||||||
.map(|p| p.to_child().name("priority"))
|
.map(|p| p.into_child().name("priority")),
|
||||||
.collect(),
|
|
||||||
)
|
)
|
||||||
.selected(vec![payload.priority.to_child().name("priority")])
|
.selected(vec![payload.priority.into_child().name("priority")])
|
||||||
.build(FieldId::EditIssueModal(EditIssueModalSection::Issue(
|
.build(FieldId::EditIssueModal(EditIssueModalSection::Issue(
|
||||||
IssueFieldId::Priority,
|
IssueFieldId::Priority,
|
||||||
)))
|
)))
|
||||||
|
@ -108,7 +108,7 @@ pub fn time_tracking_field(
|
|||||||
.collect(),
|
.collect(),
|
||||||
)
|
)
|
||||||
.state(select_state)
|
.state(select_state)
|
||||||
.options(fibonacci_values.iter().map(|v| v.to_child()).collect())
|
.options(fibonacci_values.iter().map(|v| v.to_child()))
|
||||||
.build(field_id)
|
.build(field_id)
|
||||||
.into_node(),
|
.into_node(),
|
||||||
TimeTracking::Hourly => StyledInput::build()
|
TimeTracking::Hourly => StyledInput::build()
|
||||||
|
@ -80,46 +80,41 @@ pub fn view(model: &Model) -> Node<Msg> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn build_current_project(model: &Model, page: &ProfilePage) -> Node<Msg> {
|
fn build_current_project(model: &Model, page: &ProfilePage) -> Node<Msg> {
|
||||||
let inner = if model.projects.len() <= 1 {
|
let inner =
|
||||||
let name = model
|
if model.projects.len() <= 1 {
|
||||||
.project
|
let name = model
|
||||||
.as_ref()
|
.project
|
||||||
.map(|p| p.name.as_str())
|
.as_ref()
|
||||||
.unwrap_or_default();
|
.map(|p| p.name.as_str())
|
||||||
span![name]
|
.unwrap_or_default();
|
||||||
} else {
|
span![name]
|
||||||
let mut project_by_id = HashMap::new();
|
} else {
|
||||||
for p in model.projects.iter() {
|
let mut project_by_id = HashMap::new();
|
||||||
project_by_id.insert(p.id, p);
|
for p in model.projects.iter() {
|
||||||
}
|
project_by_id.insert(p.id, p);
|
||||||
let mut joined_projects = HashMap::new();
|
}
|
||||||
for p in model.user_projects.iter() {
|
let mut joined_projects = HashMap::new();
|
||||||
joined_projects.insert(p.project_id, p);
|
for p in model.user_projects.iter() {
|
||||||
}
|
joined_projects.insert(p.project_id, p);
|
||||||
|
}
|
||||||
|
|
||||||
StyledSelect::build()
|
StyledSelect::build()
|
||||||
.name("current_project")
|
.name("current_project")
|
||||||
.normal()
|
.normal()
|
||||||
.options(
|
.options(model.projects.iter().filter_map(|project| {
|
||||||
model
|
joined_projects.get(&project.id).map(|_| project.to_child())
|
||||||
.projects
|
}))
|
||||||
.iter()
|
.selected(
|
||||||
.filter_map(|project| {
|
page.current_project
|
||||||
joined_projects.get(&project.id).map(|_| project.to_child())
|
.values
|
||||||
})
|
.iter()
|
||||||
.collect(),
|
.filter_map(|id| project_by_id.get(&((*id) as i32)).map(|p| p.to_child()))
|
||||||
)
|
.collect(),
|
||||||
.selected(
|
)
|
||||||
page.current_project
|
.state(&page.current_project)
|
||||||
.values
|
.build(FieldId::Profile(UsersFieldId::CurrentProject))
|
||||||
.iter()
|
.into_node()
|
||||||
.filter_map(|id| project_by_id.get(&((*id) as i32)).map(|p| p.to_child()))
|
};
|
||||||
.collect(),
|
|
||||||
)
|
|
||||||
.state(&page.current_project)
|
|
||||||
.build(FieldId::Profile(UsersFieldId::CurrentProject))
|
|
||||||
.into_node()
|
|
||||||
};
|
|
||||||
StyledField::build()
|
StyledField::build()
|
||||||
.label("Current project")
|
.label("Current project")
|
||||||
.input(div![C!["project-name"], inner])
|
.input(div![C!["project-name"], inner])
|
||||||
|
@ -14,11 +14,11 @@ use {
|
|||||||
styled_input::StyledInput,
|
styled_input::StyledInput,
|
||||||
styled_select::StyledSelect,
|
styled_select::StyledSelect,
|
||||||
styled_textarea::StyledTextarea,
|
styled_textarea::StyledTextarea,
|
||||||
ToChild, ToNode,
|
IntoChild, ToChild, ToNode,
|
||||||
},
|
},
|
||||||
FieldId, Msg, PageChanged, ProjectFieldId, ProjectPageChange,
|
FieldId, Msg, PageChanged, ProjectFieldId, ProjectPageChange,
|
||||||
},
|
},
|
||||||
jirs_data::{IssueStatus, ProjectCategory, TimeTracking, ToVec},
|
jirs_data::{IssueStatus, ProjectCategory, TimeTracking},
|
||||||
seed::{prelude::*, *},
|
seed::{prelude::*, *},
|
||||||
std::collections::HashMap,
|
std::collections::HashMap,
|
||||||
};
|
};
|
||||||
@ -168,20 +168,23 @@ fn description_field(page: &ProjectSettingsPage) -> Node<Msg> {
|
|||||||
|
|
||||||
/// Build project category dropdown with styled field wrapper
|
/// Build project category dropdown with styled field wrapper
|
||||||
fn category_field(page: &ProjectSettingsPage) -> Node<Msg> {
|
fn category_field(page: &ProjectSettingsPage) -> Node<Msg> {
|
||||||
let project_categories = ProjectCategory::ordered();
|
|
||||||
let category = StyledSelect::build()
|
let category = StyledSelect::build()
|
||||||
.opened(page.project_category_state.opened)
|
.opened(page.project_category_state.opened)
|
||||||
.text_filter(page.project_category_state.text_filter.as_str())
|
.text_filter(page.project_category_state.text_filter.as_str())
|
||||||
.valid(true)
|
.valid(true)
|
||||||
.normal()
|
.normal()
|
||||||
.options(project_categories.iter().map(|c| c.to_child()).collect())
|
.options(
|
||||||
|
ProjectCategory::default()
|
||||||
|
.into_iter()
|
||||||
|
.map(|c| c.into_child()),
|
||||||
|
)
|
||||||
.selected(vec![page
|
.selected(vec![page
|
||||||
.payload
|
.payload
|
||||||
.category
|
.category
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.cloned()
|
.cloned()
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
.to_child()])
|
.into_child()])
|
||||||
.build(FieldId::ProjectSettings(ProjectFieldId::Category))
|
.build(FieldId::ProjectSettings(ProjectFieldId::Category))
|
||||||
.into_node();
|
.into_node();
|
||||||
StyledField::build()
|
StyledField::build()
|
||||||
|
@ -4,12 +4,12 @@ use {
|
|||||||
shared::{
|
shared::{
|
||||||
inner_layout, styled_button::StyledButton, styled_field::StyledField,
|
inner_layout, styled_button::StyledButton, styled_field::StyledField,
|
||||||
styled_form::StyledForm, styled_input::StyledInput, styled_select::StyledSelect,
|
styled_form::StyledForm, styled_input::StyledInput, styled_select::StyledSelect,
|
||||||
ToChild, ToNode,
|
IntoChild, ToNode,
|
||||||
},
|
},
|
||||||
validations::is_email,
|
validations::is_email,
|
||||||
FieldId, Msg, PageChanged, UsersPageChange,
|
FieldId, Msg, PageChanged, UsersPageChange,
|
||||||
},
|
},
|
||||||
jirs_data::{InvitationState, ToVec, UserRole, UsersFieldId},
|
jirs_data::{InvitationState, UserRole, UsersFieldId},
|
||||||
seed::{prelude::*, *},
|
seed::{prelude::*, *},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -45,14 +45,17 @@ pub fn view(model: &Model) -> Node<Msg> {
|
|||||||
.build()
|
.build()
|
||||||
.into_node();
|
.into_node();
|
||||||
|
|
||||||
let roles = UserRole::ordered();
|
|
||||||
let user_role = StyledSelect::build()
|
let user_role = StyledSelect::build()
|
||||||
.name("user_role")
|
.name("user_role")
|
||||||
.valid(true)
|
.valid(true)
|
||||||
.normal()
|
.normal()
|
||||||
.state(&page.user_role_state)
|
.state(&page.user_role_state)
|
||||||
.selected(vec![page.user_role.to_child()])
|
.selected(vec![page.user_role.into_child()])
|
||||||
.options(roles.iter().map(|role| role.to_child()).collect())
|
.options(
|
||||||
|
UserRole::default()
|
||||||
|
.into_iter()
|
||||||
|
.map(|role| role.into_child()),
|
||||||
|
)
|
||||||
.build(FieldId::Users(UsersFieldId::UserRole))
|
.build(FieldId::Users(UsersFieldId::UserRole))
|
||||||
.into_node();
|
.into_node();
|
||||||
let user_role_field = StyledField::build()
|
let user_role_field = StyledField::build()
|
||||||
|
@ -36,6 +36,12 @@ pub trait ToChild<'l> {
|
|||||||
fn to_child<'m: 'l>(&'m self) -> Self::Builder;
|
fn to_child<'m: 'l>(&'m self) -> Self::Builder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub trait IntoChild<'l> {
|
||||||
|
type Builder: 'l;
|
||||||
|
|
||||||
|
fn into_child(self) -> Self::Builder;
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn go_to_board(orders: &mut impl Orders<Msg>) {
|
pub fn go_to_board(orders: &mut impl Orders<Msg>) {
|
||||||
go_to("board", orders);
|
go_to("board", orders);
|
||||||
|
@ -1,9 +1,16 @@
|
|||||||
use seed::{prelude::*, *};
|
use {
|
||||||
|
crate::{
|
||||||
|
shared::{
|
||||||
|
styled_icon::{Icon, StyledIcon},
|
||||||
|
styled_select_child::*,
|
||||||
|
ToNode,
|
||||||
|
},
|
||||||
|
FieldId, Msg,
|
||||||
|
},
|
||||||
|
seed::{prelude::*, *},
|
||||||
|
};
|
||||||
|
|
||||||
use crate::shared::styled_icon::{Icon, StyledIcon};
|
// pub trait ChildIter<'l> = Iterator<Item = StyledSelectChildBuilder<'l>>;
|
||||||
use crate::shared::styled_select_child::*;
|
|
||||||
use crate::shared::ToNode;
|
|
||||||
use crate::{FieldId, Msg};
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub enum StyledSelectChanged {
|
pub enum StyledSelectChanged {
|
||||||
@ -105,28 +112,37 @@ impl StyledSelectState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct StyledSelect<'l> {
|
pub struct StyledSelect<'l, Options>
|
||||||
|
where
|
||||||
|
Options: Iterator<Item = StyledSelectChildBuilder<'l>>,
|
||||||
|
{
|
||||||
id: FieldId,
|
id: FieldId,
|
||||||
variant: Variant,
|
variant: Variant,
|
||||||
dropdown_width: Option<usize>,
|
dropdown_width: Option<usize>,
|
||||||
name: Option<&'l str>,
|
name: Option<&'l str>,
|
||||||
valid: bool,
|
valid: bool,
|
||||||
is_multi: bool,
|
is_multi: bool,
|
||||||
options: Vec<StyledSelectChildBuilder<'l>>,
|
options: Option<Options>,
|
||||||
selected: Vec<StyledSelectChildBuilder<'l>>,
|
selected: Vec<StyledSelectChildBuilder<'l>>,
|
||||||
text_filter: &'l str,
|
text_filter: &'l str,
|
||||||
opened: bool,
|
opened: bool,
|
||||||
clearable: bool,
|
clearable: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'l> ToNode for StyledSelect<'l> {
|
impl<'l, Options> ToNode for StyledSelect<'l, Options>
|
||||||
|
where
|
||||||
|
Options: Iterator<Item = StyledSelectChildBuilder<'l>>,
|
||||||
|
{
|
||||||
fn into_node(self) -> Node<Msg> {
|
fn into_node(self) -> Node<Msg> {
|
||||||
render(self)
|
render(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'l> StyledSelect<'l> {
|
impl<'l, Options> StyledSelect<'l, Options>
|
||||||
pub fn build() -> StyledSelectBuilder<'l> {
|
where
|
||||||
|
Options: Iterator<Item = StyledSelectChildBuilder<'l>>,
|
||||||
|
{
|
||||||
|
pub fn build() -> StyledSelectBuilder<'l, Options> {
|
||||||
StyledSelectBuilder {
|
StyledSelectBuilder {
|
||||||
variant: None,
|
variant: None,
|
||||||
dropdown_width: None,
|
dropdown_width: None,
|
||||||
@ -143,21 +159,27 @@ impl<'l> StyledSelect<'l> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct StyledSelectBuilder<'l> {
|
pub struct StyledSelectBuilder<'l, Options>
|
||||||
|
where
|
||||||
|
Options: Iterator<Item = StyledSelectChildBuilder<'l>>,
|
||||||
|
{
|
||||||
variant: Option<Variant>,
|
variant: Option<Variant>,
|
||||||
dropdown_width: Option<usize>,
|
dropdown_width: Option<usize>,
|
||||||
name: Option<&'l str>,
|
name: Option<&'l str>,
|
||||||
valid: Option<bool>,
|
valid: Option<bool>,
|
||||||
is_multi: Option<bool>,
|
is_multi: Option<bool>,
|
||||||
options: Option<Vec<StyledSelectChildBuilder<'l>>>,
|
options: Option<Options>,
|
||||||
selected: Option<Vec<StyledSelectChildBuilder<'l>>>,
|
selected: Option<Vec<StyledSelectChildBuilder<'l>>>,
|
||||||
text_filter: Option<&'l str>,
|
text_filter: Option<&'l str>,
|
||||||
opened: Option<bool>,
|
opened: Option<bool>,
|
||||||
clearable: bool,
|
clearable: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'l> StyledSelectBuilder<'l> {
|
impl<'l, Options> StyledSelectBuilder<'l, Options>
|
||||||
pub fn build(self, id: FieldId) -> StyledSelect<'l> {
|
where
|
||||||
|
Options: Iterator<Item = StyledSelectChildBuilder<'l>>,
|
||||||
|
{
|
||||||
|
pub fn build(self, id: FieldId) -> StyledSelect<'l, Options> {
|
||||||
StyledSelect {
|
StyledSelect {
|
||||||
id,
|
id,
|
||||||
variant: self.variant.unwrap_or_default(),
|
variant: self.variant.unwrap_or_default(),
|
||||||
@ -165,7 +187,7 @@ impl<'l> StyledSelectBuilder<'l> {
|
|||||||
name: self.name,
|
name: self.name,
|
||||||
valid: self.valid.unwrap_or(true),
|
valid: self.valid.unwrap_or(true),
|
||||||
is_multi: self.is_multi.unwrap_or_default(),
|
is_multi: self.is_multi.unwrap_or_default(),
|
||||||
options: self.options.unwrap_or_default(),
|
options: self.options,
|
||||||
selected: self.selected.unwrap_or_default(),
|
selected: self.selected.unwrap_or_default(),
|
||||||
text_filter: self.text_filter.unwrap_or_default(),
|
text_filter: self.text_filter.unwrap_or_default(),
|
||||||
opened: self.opened.unwrap_or_default(),
|
opened: self.opened.unwrap_or_default(),
|
||||||
@ -173,14 +195,6 @@ impl<'l> StyledSelectBuilder<'l> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// pub fn try_state<'state: 'l>(self, state: Option<&'state StyledSelectState>) -> Self {
|
|
||||||
// if let Some(s) = state {
|
|
||||||
// self.state(s)
|
|
||||||
// } else {
|
|
||||||
// self
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
pub fn state<'state: 'l>(self, state: &'state StyledSelectState) -> Self {
|
pub fn state<'state: 'l>(self, state: &'state StyledSelectState) -> Self {
|
||||||
self.opened(state.opened)
|
self.opened(state.opened)
|
||||||
.text_filter(state.text_filter.as_str())
|
.text_filter(state.text_filter.as_str())
|
||||||
@ -211,7 +225,7 @@ impl<'l> StyledSelectBuilder<'l> {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn options(mut self, options: Vec<StyledSelectChildBuilder<'l>>) -> Self {
|
pub fn options(mut self, options: Options) -> Self {
|
||||||
self.options = Some(options);
|
self.options = Some(options);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
@ -242,7 +256,10 @@ impl<'l> StyledSelectBuilder<'l> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render(values: StyledSelect) -> Node<Msg> {
|
pub fn render<'l, Options>(values: StyledSelect<'l, Options>) -> Node<Msg>
|
||||||
|
where
|
||||||
|
Options: Iterator<Item = StyledSelectChildBuilder<'l>>,
|
||||||
|
{
|
||||||
let StyledSelect {
|
let StyledSelect {
|
||||||
id,
|
id,
|
||||||
variant,
|
variant,
|
||||||
@ -303,28 +320,34 @@ pub fn render(values: StyledSelect) -> Node<Msg> {
|
|||||||
empty![]
|
empty![]
|
||||||
};
|
};
|
||||||
|
|
||||||
let children: Vec<Node<Msg>> = options
|
let children: Vec<Node<Msg>> = if let Some(options) = options {
|
||||||
.into_iter()
|
options
|
||||||
.filter(|o| !selected.contains(&o) && o.match_text(text_filter))
|
.filter(|o| !selected.contains(&o) && o.match_text(text_filter))
|
||||||
.map(|child| {
|
.map(|child| {
|
||||||
let child = child.build(DisplayType::SelectOption);
|
let child = child.build(DisplayType::SelectOption);
|
||||||
let value = child.value();
|
let value = child.value();
|
||||||
let node = child.into_node();
|
let node = child.into_node();
|
||||||
|
|
||||||
let on_change = {
|
let on_change = {
|
||||||
let field_id = id.clone();
|
let field_id = id.clone();
|
||||||
mouse_ev(Ev::Click, move |_| {
|
mouse_ev(Ev::Click, move |_| {
|
||||||
Msg::StyledSelectChanged(field_id, StyledSelectChanged::Changed(Some(value)))
|
Msg::StyledSelectChanged(
|
||||||
})
|
field_id,
|
||||||
};
|
StyledSelectChanged::Changed(Some(value)),
|
||||||
div![
|
)
|
||||||
attrs![At::Class => "option"],
|
})
|
||||||
on_change,
|
};
|
||||||
on_handler.clone(),
|
div![
|
||||||
node
|
attrs![At::Class => "option"],
|
||||||
]
|
on_change,
|
||||||
})
|
on_handler.clone(),
|
||||||
.collect();
|
node
|
||||||
|
]
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
} else {
|
||||||
|
vec![]
|
||||||
|
};
|
||||||
|
|
||||||
let text_input = if opened {
|
let text_input = if opened {
|
||||||
seed::input![
|
seed::input![
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
use std::borrow::Cow;
|
use {
|
||||||
|
crate::{
|
||||||
use seed::{prelude::*, *};
|
shared::styled_select::Variant,
|
||||||
|
shared::{IntoChild, ToChild, ToNode},
|
||||||
use crate::shared::styled_select::Variant;
|
Msg,
|
||||||
use crate::shared::{ToChild, ToNode};
|
},
|
||||||
use crate::Msg;
|
seed::{prelude::*, *},
|
||||||
|
std::borrow::Cow,
|
||||||
|
};
|
||||||
|
|
||||||
pub enum DisplayType {
|
pub enum DisplayType {
|
||||||
SelectOption,
|
SelectOption,
|
||||||
@ -186,9 +188,10 @@ impl<'l> ToChild<'l> for jirs_data::User {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'l> ToChild<'l> for jirs_data::IssuePriority {
|
impl<'l> IntoChild<'l> for jirs_data::IssuePriority {
|
||||||
type Builder = StyledSelectChildBuilder<'l>;
|
type Builder = StyledSelectChildBuilder<'l>;
|
||||||
fn to_child<'m: 'l>(&'m self) -> Self::Builder {
|
|
||||||
|
fn into_child(self) -> Self::Builder {
|
||||||
let icon = crate::shared::styled_icon::StyledIcon::build(self.clone().into())
|
let icon = crate::shared::styled_icon::StyledIcon::build(self.clone().into())
|
||||||
.add_class(self.to_str())
|
.add_class(self.to_str())
|
||||||
.build()
|
.build()
|
||||||
@ -214,10 +217,10 @@ impl<'l> ToChild<'l> for jirs_data::IssueStatus {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'l> ToChild<'l> for jirs_data::IssueType {
|
impl<'l> IntoChild<'l> for jirs_data::IssueType {
|
||||||
type Builder = StyledSelectChildBuilder<'l>;
|
type Builder = StyledSelectChildBuilder<'l>;
|
||||||
|
|
||||||
fn to_child<'m: 'l>(&'m self) -> Self::Builder {
|
fn into_child(self) -> Self::Builder {
|
||||||
let name = self.to_label();
|
let name = self.to_label();
|
||||||
|
|
||||||
let type_icon = crate::shared::styled_icon::StyledIcon::build(self.clone().into())
|
let type_icon = crate::shared::styled_icon::StyledIcon::build(self.clone().into())
|
||||||
@ -233,10 +236,10 @@ impl<'l> ToChild<'l> for jirs_data::IssueType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'l> ToChild<'l> for jirs_data::ProjectCategory {
|
impl<'l> IntoChild<'l> for jirs_data::ProjectCategory {
|
||||||
type Builder = StyledSelectChildBuilder<'l>;
|
type Builder = StyledSelectChildBuilder<'l>;
|
||||||
|
|
||||||
fn to_child<'m: 'l>(&'m self) -> Self::Builder {
|
fn into_child(self) -> Self::Builder {
|
||||||
StyledSelectChild::build()
|
StyledSelectChild::build()
|
||||||
.add_class(self.to_str())
|
.add_class(self.to_str())
|
||||||
.text(self.to_str())
|
.text(self.to_str())
|
||||||
@ -244,10 +247,10 @@ impl<'l> ToChild<'l> for jirs_data::ProjectCategory {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'l> ToChild<'l> for jirs_data::UserRole {
|
impl<'l> IntoChild<'l> for jirs_data::UserRole {
|
||||||
type Builder = StyledSelectChildBuilder<'l>;
|
type Builder = StyledSelectChildBuilder<'l>;
|
||||||
|
|
||||||
fn to_child<'m: 'l>(&'m self) -> Self::Builder {
|
fn into_child(self) -> Self::Builder {
|
||||||
let name = self.to_str();
|
let name = self.to_str();
|
||||||
|
|
||||||
StyledSelectChild::build()
|
StyledSelectChild::build()
|
||||||
|
@ -161,21 +161,6 @@ pub fn update(msg: WsMsg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
// issues
|
|
||||||
WsMsg::ProjectIssuesLoaded(mut v) => {
|
|
||||||
v.sort_by(|a, b| (a.list_position as i64).cmp(&(b.list_position as i64)));
|
|
||||||
model.issues = v;
|
|
||||||
model.issues_by_id.clear();
|
|
||||||
for issue in model.issues.iter() {
|
|
||||||
model.issues_by_id.insert(issue.id, issue.clone());
|
|
||||||
}
|
|
||||||
|
|
||||||
orders.send_msg(Msg::ResourceChanged(
|
|
||||||
ResourceKind::Issue,
|
|
||||||
OperationKind::ListLoaded,
|
|
||||||
None,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
// issue statuses
|
// issue statuses
|
||||||
WsMsg::IssueStatusesLoaded(v) => {
|
WsMsg::IssueStatusesLoaded(v) => {
|
||||||
model.issue_statuses = v;
|
model.issue_statuses = v;
|
||||||
@ -232,8 +217,24 @@ pub fn update(msg: WsMsg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
// issues
|
// issues
|
||||||
|
WsMsg::ProjectIssuesLoaded(mut v) => {
|
||||||
|
v.sort_by(|a, b| (a.list_position as i64).cmp(&(b.list_position as i64)));
|
||||||
|
model.issues = v;
|
||||||
|
model.issues_by_id.clear();
|
||||||
|
for issue in model.issues.iter() {
|
||||||
|
model.issues_by_id.insert(issue.id, issue.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
orders.send_msg(Msg::ResourceChanged(
|
||||||
|
ResourceKind::Issue,
|
||||||
|
OperationKind::ListLoaded,
|
||||||
|
None,
|
||||||
|
));
|
||||||
|
}
|
||||||
WsMsg::IssueUpdated(mut issue) => {
|
WsMsg::IssueUpdated(mut issue) => {
|
||||||
let id = issue.id;
|
let id = issue.id;
|
||||||
|
model.issues_by_id.remove(&id);
|
||||||
|
model.issues_by_id.insert(id, issue.clone());
|
||||||
if let Some(idx) = model.issues.iter().position(|i| i.id == issue.id) {
|
if let Some(idx) = model.issues.iter().position(|i| i.id == issue.id) {
|
||||||
std::mem::swap(&mut model.issues[idx], &mut issue);
|
std::mem::swap(&mut model.issues[idx], &mut issue);
|
||||||
}
|
}
|
||||||
@ -245,16 +246,13 @@ pub fn update(msg: WsMsg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
|||||||
}
|
}
|
||||||
WsMsg::IssueDeleted(id, _count) => {
|
WsMsg::IssueDeleted(id, _count) => {
|
||||||
let mut old = vec![];
|
let mut old = vec![];
|
||||||
std::mem::swap(&mut model.issue_statuses, &mut old);
|
std::mem::swap(&mut model.issues, &mut old);
|
||||||
for is in old {
|
for is in old {
|
||||||
if is.id == id {
|
if is.id == id {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
model.issue_statuses.push(is);
|
model.issues.push(is);
|
||||||
}
|
}
|
||||||
model
|
|
||||||
.issue_statuses
|
|
||||||
.sort_by(|a, b| a.position.cmp(&b.position));
|
|
||||||
orders.send_msg(Msg::ResourceChanged(
|
orders.send_msg(Msg::ResourceChanged(
|
||||||
ResourceKind::Issue,
|
ResourceKind::Issue,
|
||||||
OperationKind::SingleRemoved,
|
OperationKind::SingleRemoved,
|
||||||
@ -264,6 +262,7 @@ pub fn update(msg: WsMsg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
|||||||
// users
|
// users
|
||||||
WsMsg::ProjectUsersLoaded(v) => {
|
WsMsg::ProjectUsersLoaded(v) => {
|
||||||
model.users = v.clone();
|
model.users = v.clone();
|
||||||
|
model.users_by_id.clear();
|
||||||
for user in v {
|
for user in v {
|
||||||
model.users_by_id.insert(user.id, user.clone());
|
model.users_by_id.insert(user.id, user.clone());
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,7 @@ name = "jirs_data"
|
|||||||
path = "./src/lib.rs"
|
path = "./src/lib.rs"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
backend = ["diesel", "actix"]
|
backend = ["diesel", "actix", "derive_enum_sql"]
|
||||||
frontend = []
|
frontend = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
@ -30,3 +30,13 @@ optional = true
|
|||||||
optional = true
|
optional = true
|
||||||
version = "1.4.5"
|
version = "1.4.5"
|
||||||
features = ["unstable", "postgres", "numeric", "extras", "uuidv07"]
|
features = ["unstable", "postgres", "numeric", "extras", "uuidv07"]
|
||||||
|
|
||||||
|
[dependencies.derive_enum_iter]
|
||||||
|
path = "../../derive/derive_enum_iter"
|
||||||
|
|
||||||
|
[dependencies.derive_enum_primitive]
|
||||||
|
path = "../../derive/derive_enum_primitive"
|
||||||
|
|
||||||
|
[dependencies.derive_enum_sql]
|
||||||
|
path = "../../derive/derive_enum_sql"
|
||||||
|
optional = true
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
#[cfg(feature = "backend")]
|
#[cfg(feature = "backend")]
|
||||||
use diesel::*;
|
use diesel::*;
|
||||||
|
|
||||||
#[cfg(feature = "backend")]
|
|
||||||
pub use sql::*;
|
|
||||||
use {
|
use {
|
||||||
chrono::NaiveDateTime,
|
chrono::NaiveDateTime,
|
||||||
|
derive_enum_iter::EnumIter,
|
||||||
|
derive_enum_primitive::EnumPrimitive,
|
||||||
serde::{Deserialize, Serialize},
|
serde::{Deserialize, Serialize},
|
||||||
std::cmp::Ordering,
|
std::cmp::Ordering,
|
||||||
std::str::FromStr,
|
std::str::FromStr,
|
||||||
@ -17,12 +17,7 @@ pub mod msg;
|
|||||||
mod payloads;
|
mod payloads;
|
||||||
|
|
||||||
#[cfg(feature = "backend")]
|
#[cfg(feature = "backend")]
|
||||||
pub mod sql;
|
use derive_enum_sql::EnumSql;
|
||||||
|
|
||||||
pub trait ToVec {
|
|
||||||
type Item;
|
|
||||||
fn ordered() -> Vec<Self::Item>;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type NumberOfDeleted = usize;
|
pub type NumberOfDeleted = usize;
|
||||||
pub type IssueId = i32;
|
pub type IssueId = i32;
|
||||||
@ -52,90 +47,34 @@ pub type Lang = String;
|
|||||||
pub type BindToken = Uuid;
|
pub type BindToken = Uuid;
|
||||||
pub type InvitationToken = Uuid;
|
pub type InvitationToken = Uuid;
|
||||||
|
|
||||||
macro_rules! enum_to_u32 {
|
#[cfg_attr(feature = "backend", derive(FromSqlRow, AsExpression, EnumSql))]
|
||||||
($kind: ident, $fallback: ident, $($e: ident => $v: expr),+) => {
|
|
||||||
#[cfg(feature = "frontend")]
|
|
||||||
impl Into<u32> for $kind {
|
|
||||||
fn into(self) -> u32 {
|
|
||||||
match self {
|
|
||||||
$($kind :: $e => $v),+
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "frontend")]
|
|
||||||
impl Into<$kind> for u32 {
|
|
||||||
fn into(self) -> $kind {
|
|
||||||
match self {
|
|
||||||
$($v => $kind :: $e),+,
|
|
||||||
_else => $kind :: $fallback,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg_attr(feature = "backend", derive(FromSqlRow, AsExpression))]
|
|
||||||
#[cfg_attr(feature = "backend", sql_type = "IssueTypeType")]
|
#[cfg_attr(feature = "backend", sql_type = "IssueTypeType")]
|
||||||
#[derive(Clone, Copy, Deserialize, Serialize, Debug, PartialOrd, PartialEq, Hash)]
|
#[derive(
|
||||||
|
Clone, Copy, Deserialize, Serialize, Debug, PartialOrd, PartialEq, Hash, EnumIter, EnumPrimitive,
|
||||||
|
)]
|
||||||
pub enum IssueType {
|
pub enum IssueType {
|
||||||
Task,
|
Task,
|
||||||
Bug,
|
Bug,
|
||||||
Story,
|
Story,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToVec for IssueType {
|
|
||||||
type Item = IssueType;
|
|
||||||
|
|
||||||
fn ordered() -> Vec<Self> {
|
|
||||||
use IssueType::*;
|
|
||||||
vec![Task, Bug, Story]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for IssueType {
|
impl Default for IssueType {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
IssueType::Task
|
IssueType::Task
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IssueType {
|
|
||||||
pub fn to_label(&self) -> &str {
|
|
||||||
use IssueType::*;
|
|
||||||
match self {
|
|
||||||
Task => "Task",
|
|
||||||
Bug => "Bug",
|
|
||||||
Story => "Story",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum_to_u32! {
|
|
||||||
IssueType, Task,
|
|
||||||
Task => 1,
|
|
||||||
Bug => 2,
|
|
||||||
Story => 3
|
|
||||||
}
|
|
||||||
|
|
||||||
impl IssueType {
|
|
||||||
pub fn to_str<'l>(&self) -> &'l str {
|
|
||||||
match self {
|
|
||||||
IssueType::Task => "task",
|
|
||||||
IssueType::Bug => "bug",
|
|
||||||
IssueType::Story => "story",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::fmt::Display for IssueType {
|
impl std::fmt::Display for IssueType {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
f.write_str(self.to_str())
|
f.write_str(self.to_str())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "backend", derive(FromSqlRow, AsExpression))]
|
#[cfg_attr(feature = "backend", derive(FromSqlRow, AsExpression, EnumSql))]
|
||||||
#[cfg_attr(feature = "backend", sql_type = "IssuePriorityType")]
|
#[cfg_attr(feature = "backend", sql_type = "IssuePriorityType")]
|
||||||
#[derive(Clone, Copy, Deserialize, Serialize, Debug, PartialOrd, PartialEq, Hash)]
|
#[derive(
|
||||||
|
Clone, Copy, Deserialize, Serialize, Debug, PartialOrd, PartialEq, Hash, EnumIter, EnumPrimitive,
|
||||||
|
)]
|
||||||
pub enum IssuePriority {
|
pub enum IssuePriority {
|
||||||
Highest,
|
Highest,
|
||||||
High,
|
High,
|
||||||
@ -144,71 +83,21 @@ pub enum IssuePriority {
|
|||||||
Lowest,
|
Lowest,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToVec for IssuePriority {
|
|
||||||
type Item = IssuePriority;
|
|
||||||
|
|
||||||
fn ordered() -> Vec<Self> {
|
|
||||||
vec![
|
|
||||||
IssuePriority::Highest,
|
|
||||||
IssuePriority::High,
|
|
||||||
IssuePriority::Medium,
|
|
||||||
IssuePriority::Low,
|
|
||||||
IssuePriority::Lowest,
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromStr for IssuePriority {
|
|
||||||
type Err = String;
|
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
||||||
match s.to_lowercase().trim() {
|
|
||||||
"highest" => Ok(IssuePriority::Highest),
|
|
||||||
"high" => Ok(IssuePriority::High),
|
|
||||||
"medium" => Ok(IssuePriority::Medium),
|
|
||||||
"low" => Ok(IssuePriority::Low),
|
|
||||||
"lowest" => Ok(IssuePriority::Lowest),
|
|
||||||
_ => Err(format!("Unknown priority {}", s)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for IssuePriority {
|
impl Default for IssuePriority {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
IssuePriority::Medium
|
IssuePriority::Medium
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IssuePriority {
|
|
||||||
pub fn to_str(&self) -> &'static str {
|
|
||||||
match self {
|
|
||||||
IssuePriority::Highest => "highest",
|
|
||||||
IssuePriority::High => "high",
|
|
||||||
IssuePriority::Medium => "medium",
|
|
||||||
IssuePriority::Low => "low",
|
|
||||||
IssuePriority::Lowest => "lowest",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::fmt::Display for IssuePriority {
|
impl std::fmt::Display for IssuePriority {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
f.write_str(self.to_str())
|
f.write_str(self.to_str())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum_to_u32!(
|
#[cfg_attr(feature = "backend", derive(FromSqlRow, AsExpression, EnumSql))]
|
||||||
IssuePriority, Medium,
|
|
||||||
Highest => 5,
|
|
||||||
High => 4,
|
|
||||||
Medium => 3,
|
|
||||||
Low => 2,
|
|
||||||
Lowest => 1
|
|
||||||
);
|
|
||||||
|
|
||||||
#[cfg_attr(feature = "backend", derive(FromSqlRow, AsExpression))]
|
|
||||||
#[cfg_attr(feature = "backend", sql_type = "UserRoleType")]
|
#[cfg_attr(feature = "backend", sql_type = "UserRoleType")]
|
||||||
#[derive(Clone, Copy, Deserialize, Serialize, Debug, PartialEq, Hash)]
|
#[derive(Clone, Copy, Deserialize, Serialize, Debug, PartialEq, Hash, EnumIter, EnumPrimitive)]
|
||||||
pub enum UserRole {
|
pub enum UserRole {
|
||||||
User,
|
User,
|
||||||
Manager,
|
Manager,
|
||||||
@ -230,122 +119,46 @@ impl PartialOrd for UserRole {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToVec for UserRole {
|
|
||||||
type Item = UserRole;
|
|
||||||
|
|
||||||
fn ordered() -> Vec<Self::Item> {
|
|
||||||
vec![UserRole::User, UserRole::Manager, UserRole::Owner]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromStr for UserRole {
|
|
||||||
type Err = String;
|
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
||||||
match s.to_lowercase().trim() {
|
|
||||||
"user" => Ok(UserRole::User),
|
|
||||||
"manager" => Ok(UserRole::Manager),
|
|
||||||
"owner" => Ok(UserRole::Owner),
|
|
||||||
_ => Err(format!("Unknown user role {}", s)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for UserRole {
|
impl Default for UserRole {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
UserRole::User
|
UserRole::User
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UserRole {
|
|
||||||
pub fn to_str<'l>(&self) -> &'l str {
|
|
||||||
match self {
|
|
||||||
UserRole::User => "user",
|
|
||||||
UserRole::Manager => "manager",
|
|
||||||
UserRole::Owner => "owner",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::fmt::Display for UserRole {
|
impl std::fmt::Display for UserRole {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
f.write_str(self.to_str())
|
f.write_str(self.to_str())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum_to_u32!(
|
#[cfg_attr(feature = "backend", derive(FromSqlRow, AsExpression, EnumSql))]
|
||||||
UserRole, User,
|
|
||||||
User => 0,
|
|
||||||
Manager => 1,
|
|
||||||
Owner => 2
|
|
||||||
);
|
|
||||||
|
|
||||||
#[cfg_attr(feature = "backend", derive(FromSqlRow, AsExpression))]
|
|
||||||
#[cfg_attr(feature = "backend", sql_type = "ProjectCategoryType")]
|
#[cfg_attr(feature = "backend", sql_type = "ProjectCategoryType")]
|
||||||
#[derive(Clone, Copy, Deserialize, Serialize, Debug, PartialOrd, PartialEq, Hash)]
|
#[derive(
|
||||||
|
Clone, Copy, Deserialize, Serialize, Debug, PartialOrd, PartialEq, Hash, EnumIter, EnumPrimitive,
|
||||||
|
)]
|
||||||
pub enum ProjectCategory {
|
pub enum ProjectCategory {
|
||||||
Software,
|
Software,
|
||||||
Marketing,
|
Marketing,
|
||||||
Business,
|
Business,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToVec for ProjectCategory {
|
|
||||||
type Item = ProjectCategory;
|
|
||||||
|
|
||||||
fn ordered() -> Vec<Self> {
|
|
||||||
vec![
|
|
||||||
ProjectCategory::Software,
|
|
||||||
ProjectCategory::Marketing,
|
|
||||||
ProjectCategory::Business,
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromStr for ProjectCategory {
|
|
||||||
type Err = String;
|
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
||||||
match s.to_lowercase().trim() {
|
|
||||||
"software" => Ok(ProjectCategory::Software),
|
|
||||||
"marketing" => Ok(ProjectCategory::Marketing),
|
|
||||||
"business" => Ok(ProjectCategory::Business),
|
|
||||||
_ => Err(format!("Unknown project category {}", s)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for ProjectCategory {
|
impl Default for ProjectCategory {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
ProjectCategory::Software
|
ProjectCategory::Software
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ProjectCategory {
|
|
||||||
pub fn to_str<'l>(&self) -> &'l str {
|
|
||||||
match self {
|
|
||||||
ProjectCategory::Software => "software",
|
|
||||||
ProjectCategory::Marketing => "marketing",
|
|
||||||
ProjectCategory::Business => "business",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::fmt::Display for ProjectCategory {
|
impl std::fmt::Display for ProjectCategory {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
f.write_str(self.to_str())
|
f.write_str(self.to_str())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum_to_u32!(
|
#[cfg_attr(feature = "backend", derive(FromSqlRow, AsExpression, EnumSql))]
|
||||||
ProjectCategory, Software,
|
|
||||||
Software => 0,
|
|
||||||
Marketing => 1,
|
|
||||||
Business => 2
|
|
||||||
);
|
|
||||||
|
|
||||||
#[cfg_attr(feature = "backend", derive(FromSqlRow, AsExpression))]
|
|
||||||
#[cfg_attr(feature = "backend", sql_type = "InvitationStateType")]
|
#[cfg_attr(feature = "backend", sql_type = "InvitationStateType")]
|
||||||
#[derive(Clone, Deserialize, Serialize, Debug, PartialOrd, PartialEq, Hash)]
|
#[derive(
|
||||||
|
Clone, Copy, Deserialize, Serialize, Debug, PartialOrd, PartialEq, Hash, EnumIter, EnumPrimitive,
|
||||||
|
)]
|
||||||
pub enum InvitationState {
|
pub enum InvitationState {
|
||||||
Sent,
|
Sent,
|
||||||
Accepted,
|
Accepted,
|
||||||
@ -360,29 +173,26 @@ impl Default for InvitationState {
|
|||||||
|
|
||||||
impl std::fmt::Display for InvitationState {
|
impl std::fmt::Display for InvitationState {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
match self {
|
f.write_str(self.to_str())
|
||||||
InvitationState::Sent => f.write_str("sent"),
|
|
||||||
InvitationState::Accepted => f.write_str("accepted"),
|
|
||||||
InvitationState::Revoked => f.write_str("revoked"),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "backend", derive(FromSqlRow, AsExpression))]
|
#[cfg_attr(feature = "backend", derive(FromSqlRow, AsExpression, EnumSql))]
|
||||||
#[cfg_attr(feature = "backend", sql_type = "TimeTrackingType")]
|
#[cfg_attr(feature = "backend", sql_type = "TimeTrackingType")]
|
||||||
#[derive(Clone, Copy, Deserialize, Serialize, Debug, PartialOrd, PartialEq, Hash)]
|
#[derive(
|
||||||
|
Clone, Copy, Deserialize, Serialize, Debug, PartialOrd, PartialEq, Hash, EnumIter, EnumPrimitive,
|
||||||
|
)]
|
||||||
pub enum TimeTracking {
|
pub enum TimeTracking {
|
||||||
Untracked,
|
Untracked,
|
||||||
Fibonacci,
|
Fibonacci,
|
||||||
Hourly,
|
Hourly,
|
||||||
}
|
}
|
||||||
|
|
||||||
enum_to_u32!(
|
impl Default for TimeTracking {
|
||||||
TimeTracking, Untracked,
|
fn default() -> Self {
|
||||||
Untracked => 0,
|
Self::Untracked
|
||||||
Fibonacci => 1,
|
}
|
||||||
Hourly => 2
|
}
|
||||||
);
|
|
||||||
|
|
||||||
#[derive(Clone, Serialize, Debug, PartialEq)]
|
#[derive(Clone, Serialize, Debug, PartialEq)]
|
||||||
pub struct ErrorResponse {
|
pub struct ErrorResponse {
|
||||||
@ -515,29 +325,26 @@ pub struct IssueAssignee {
|
|||||||
pub updated_at: NaiveDateTime,
|
pub updated_at: NaiveDateTime,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "backend", derive(FromSqlRow, AsExpression))]
|
#[cfg_attr(feature = "backend", derive(FromSqlRow, AsExpression, EnumSql))]
|
||||||
#[cfg_attr(feature = "backend", sql_type = "MessageTypeType")]
|
#[cfg_attr(feature = "backend", sql_type = "MessageTypeType")]
|
||||||
#[derive(Clone, Copy, Deserialize, Serialize, Debug, PartialOrd, PartialEq, Hash)]
|
#[derive(
|
||||||
|
Clone, Copy, Deserialize, Serialize, Debug, PartialOrd, PartialEq, Hash, EnumPrimitive,
|
||||||
|
)]
|
||||||
pub enum MessageType {
|
pub enum MessageType {
|
||||||
ReceivedInvitation,
|
ReceivedInvitation,
|
||||||
AssignedToIssue,
|
AssignedToIssue,
|
||||||
Mention,
|
Mention,
|
||||||
}
|
}
|
||||||
|
|
||||||
enum_to_u32!(
|
impl Default for MessageType {
|
||||||
MessageType, Mention,
|
fn default() -> Self {
|
||||||
ReceivedInvitation => 0,
|
Self::Mention
|
||||||
AssignedToIssue => 1,
|
}
|
||||||
Mention => 2
|
}
|
||||||
);
|
|
||||||
|
|
||||||
impl std::fmt::Display for MessageType {
|
impl std::fmt::Display for MessageType {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
match self {
|
f.write_str(self.to_label())
|
||||||
MessageType::ReceivedInvitation => f.write_str("ReceivedInvitation"),
|
|
||||||
MessageType::AssignedToIssue => f.write_str("AssignedToIssue"),
|
|
||||||
MessageType::Mention => f.write_str("Mention"),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,284 +0,0 @@
|
|||||||
use {
|
|
||||||
crate::{
|
|
||||||
InvitationState, IssuePriority, IssueType, MessageType, ProjectCategory, TimeTracking,
|
|
||||||
UserRole,
|
|
||||||
},
|
|
||||||
diesel::{deserialize::*, pg::*, serialize::*, *},
|
|
||||||
std::io::Write,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(SqlType)]
|
|
||||||
#[postgres(type_name = "IssuePriorityType")]
|
|
||||||
pub struct IssuePriorityType;
|
|
||||||
|
|
||||||
impl ToSql<IssuePriorityType, Pg> for IssuePriority {
|
|
||||||
fn to_sql<W: Write>(&self, out: &mut Output<W, Pg>) -> serialize::Result {
|
|
||||||
match *self {
|
|
||||||
IssuePriority::Highest => out.write_all(b"highest")?,
|
|
||||||
IssuePriority::High => out.write_all(b"high")?,
|
|
||||||
IssuePriority::Medium => out.write_all(b"medium")?,
|
|
||||||
IssuePriority::Low => out.write_all(b"low")?,
|
|
||||||
IssuePriority::Lowest => out.write_all(b"lowest")?,
|
|
||||||
}
|
|
||||||
Ok(IsNull::No)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn issue_priority_from_sql(bytes: Option<&[u8]>) -> deserialize::Result<IssuePriority> {
|
|
||||||
match not_none!(bytes) {
|
|
||||||
b"5" | b"highest" => Ok(IssuePriority::Highest),
|
|
||||||
b"4" | b"high" => Ok(IssuePriority::High),
|
|
||||||
b"3" | b"medium" => Ok(IssuePriority::Medium),
|
|
||||||
b"2" | b"low" => Ok(IssuePriority::Low),
|
|
||||||
b"1" | b"lowest" => Ok(IssuePriority::Lowest),
|
|
||||||
_ => Ok(IssuePriority::Lowest),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromSql<IssuePriorityType, Pg> for IssuePriority {
|
|
||||||
fn from_sql(bytes: Option<&[u8]>) -> deserialize::Result<Self> {
|
|
||||||
issue_priority_from_sql(bytes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromSql<sql_types::Text, Pg> for IssuePriority {
|
|
||||||
fn from_sql(bytes: Option<&[u8]>) -> deserialize::Result<Self> {
|
|
||||||
issue_priority_from_sql(bytes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(SqlType)]
|
|
||||||
#[postgres(type_name = "IssueTypeType")]
|
|
||||||
pub struct IssueTypeType;
|
|
||||||
|
|
||||||
fn issue_type_from_sql(bytes: Option<&[u8]>) -> deserialize::Result<IssueType> {
|
|
||||||
match not_none!(bytes) {
|
|
||||||
b"task" => Ok(IssueType::Task),
|
|
||||||
b"bug" => Ok(IssueType::Bug),
|
|
||||||
b"story" => Ok(IssueType::Story),
|
|
||||||
_ => Ok(IssueType::Task),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromSql<IssueTypeType, Pg> for IssueType {
|
|
||||||
fn from_sql(bytes: Option<&[u8]>) -> deserialize::Result<Self> {
|
|
||||||
issue_type_from_sql(bytes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromSql<sql_types::Text, Pg> for IssueType {
|
|
||||||
fn from_sql(bytes: Option<&[u8]>) -> deserialize::Result<Self> {
|
|
||||||
issue_type_from_sql(bytes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ToSql<IssueTypeType, Pg> for IssueType {
|
|
||||||
fn to_sql<W: Write>(&self, out: &mut Output<W, Pg>) -> serialize::Result {
|
|
||||||
match *self {
|
|
||||||
IssueType::Task => out.write_all(b"task")?,
|
|
||||||
IssueType::Story => out.write_all(b"story")?,
|
|
||||||
IssueType::Bug => out.write_all(b"bug")?,
|
|
||||||
}
|
|
||||||
Ok(IsNull::No)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(SqlType)]
|
|
||||||
#[postgres(type_name = "ProjectCategoryType")]
|
|
||||||
pub struct ProjectCategoryType;
|
|
||||||
|
|
||||||
impl diesel::query_builder::QueryId for ProjectCategoryType {
|
|
||||||
type QueryId = ProjectCategory;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn project_category_from_sql(bytes: Option<&[u8]>) -> deserialize::Result<ProjectCategory> {
|
|
||||||
match not_none!(bytes) {
|
|
||||||
b"software" => Ok(ProjectCategory::Software),
|
|
||||||
b"marketing" => Ok(ProjectCategory::Marketing),
|
|
||||||
b"business" => Ok(ProjectCategory::Business),
|
|
||||||
_ => Ok(ProjectCategory::Software),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromSql<ProjectCategoryType, Pg> for ProjectCategory {
|
|
||||||
fn from_sql(bytes: Option<&[u8]>) -> deserialize::Result<Self> {
|
|
||||||
project_category_from_sql(bytes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromSql<sql_types::Text, Pg> for ProjectCategory {
|
|
||||||
fn from_sql(bytes: Option<&[u8]>) -> deserialize::Result<Self> {
|
|
||||||
project_category_from_sql(bytes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ToSql<ProjectCategoryType, Pg> for ProjectCategory {
|
|
||||||
fn to_sql<W: Write>(&self, out: &mut Output<W, Pg>) -> serialize::Result {
|
|
||||||
match *self {
|
|
||||||
ProjectCategory::Software => out.write_all(b"software")?,
|
|
||||||
ProjectCategory::Marketing => out.write_all(b"marketing")?,
|
|
||||||
ProjectCategory::Business => out.write_all(b"business")?,
|
|
||||||
}
|
|
||||||
Ok(IsNull::No)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(SqlType)]
|
|
||||||
#[postgres(type_name = "UserRoleType")]
|
|
||||||
pub struct UserRoleType;
|
|
||||||
|
|
||||||
impl diesel::query_builder::QueryId for UserRoleType {
|
|
||||||
type QueryId = UserRole;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn user_role_from_sql(bytes: Option<&[u8]>) -> deserialize::Result<UserRole> {
|
|
||||||
match not_none!(bytes) {
|
|
||||||
b"user" => Ok(UserRole::User),
|
|
||||||
b"manager" => Ok(UserRole::Manager),
|
|
||||||
b"owner" => Ok(UserRole::Owner),
|
|
||||||
_ => Ok(UserRole::User),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromSql<UserRoleType, Pg> for UserRole {
|
|
||||||
fn from_sql(bytes: Option<&[u8]>) -> deserialize::Result<Self> {
|
|
||||||
user_role_from_sql(bytes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromSql<sql_types::Text, Pg> for UserRole {
|
|
||||||
fn from_sql(bytes: Option<&[u8]>) -> deserialize::Result<Self> {
|
|
||||||
user_role_from_sql(bytes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ToSql<UserRoleType, Pg> for UserRole {
|
|
||||||
fn to_sql<W: Write>(&self, out: &mut Output<W, Pg>) -> serialize::Result {
|
|
||||||
match *self {
|
|
||||||
UserRole::User => out.write_all(b"user")?,
|
|
||||||
UserRole::Manager => out.write_all(b"manager")?,
|
|
||||||
UserRole::Owner => out.write_all(b"owner")?,
|
|
||||||
}
|
|
||||||
Ok(IsNull::No)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(SqlType)]
|
|
||||||
#[postgres(type_name = "InvitationStateType")]
|
|
||||||
pub struct InvitationStateType;
|
|
||||||
|
|
||||||
impl diesel::query_builder::QueryId for InvitationStateType {
|
|
||||||
type QueryId = InvitationState;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn invitation_state_from_sql(bytes: Option<&[u8]>) -> deserialize::Result<InvitationState> {
|
|
||||||
match not_none!(bytes) {
|
|
||||||
b"sent" => Ok(InvitationState::Sent),
|
|
||||||
b"accepted" => Ok(InvitationState::Accepted),
|
|
||||||
b"revoked" => Ok(InvitationState::Revoked),
|
|
||||||
_ => Ok(InvitationState::Sent),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromSql<InvitationStateType, Pg> for InvitationState {
|
|
||||||
fn from_sql(bytes: Option<&[u8]>) -> deserialize::Result<Self> {
|
|
||||||
invitation_state_from_sql(bytes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromSql<sql_types::Text, Pg> for InvitationState {
|
|
||||||
fn from_sql(bytes: Option<&[u8]>) -> deserialize::Result<Self> {
|
|
||||||
invitation_state_from_sql(bytes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ToSql<InvitationStateType, Pg> for InvitationState {
|
|
||||||
fn to_sql<W: Write>(&self, out: &mut Output<W, Pg>) -> serialize::Result {
|
|
||||||
match *self {
|
|
||||||
InvitationState::Sent => out.write_all(b"sent")?,
|
|
||||||
InvitationState::Accepted => out.write_all(b"accepted")?,
|
|
||||||
InvitationState::Revoked => out.write_all(b"revoked")?,
|
|
||||||
}
|
|
||||||
Ok(IsNull::No)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(SqlType)]
|
|
||||||
#[postgres(type_name = "TimeTrackingType")]
|
|
||||||
pub struct TimeTrackingType;
|
|
||||||
|
|
||||||
impl diesel::query_builder::QueryId for TimeTrackingType {
|
|
||||||
type QueryId = TimeTracking;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn time_tracking_from_sql(bytes: Option<&[u8]>) -> deserialize::Result<TimeTracking> {
|
|
||||||
match not_none!(bytes) {
|
|
||||||
b"untracked" => Ok(TimeTracking::Untracked),
|
|
||||||
b"fibonacci" => Ok(TimeTracking::Fibonacci),
|
|
||||||
b"hourly" => Ok(TimeTracking::Hourly),
|
|
||||||
_ => Ok(TimeTracking::Untracked),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromSql<TimeTrackingType, Pg> for TimeTracking {
|
|
||||||
fn from_sql(bytes: Option<&[u8]>) -> deserialize::Result<TimeTracking> {
|
|
||||||
time_tracking_from_sql(bytes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromSql<sql_types::Text, Pg> for TimeTracking {
|
|
||||||
fn from_sql(bytes: Option<&[u8]>) -> deserialize::Result<TimeTracking> {
|
|
||||||
time_tracking_from_sql(bytes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ToSql<TimeTrackingType, Pg> for TimeTracking {
|
|
||||||
fn to_sql<W: Write>(&self, out: &mut Output<W, Pg>) -> serialize::Result {
|
|
||||||
match *self {
|
|
||||||
TimeTracking::Untracked => out.write_all(b"untracked")?,
|
|
||||||
TimeTracking::Fibonacci => out.write_all(b"fibonacci")?,
|
|
||||||
TimeTracking::Hourly => out.write_all(b"hourly")?,
|
|
||||||
}
|
|
||||||
Ok(IsNull::No)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(SqlType)]
|
|
||||||
#[postgres(type_name = "MessageTypeType")]
|
|
||||||
pub struct MessageTypeType;
|
|
||||||
|
|
||||||
impl diesel::query_builder::QueryId for MessageTypeType {
|
|
||||||
type QueryId = MessageType;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn message_type_type_from_sql(bytes: Option<&[u8]>) -> deserialize::Result<MessageType> {
|
|
||||||
match not_none!(bytes) {
|
|
||||||
b"received_invitation" => Ok(MessageType::ReceivedInvitation),
|
|
||||||
b"assigned_to_issue" => Ok(MessageType::AssignedToIssue),
|
|
||||||
b"mention" => Ok(MessageType::Mention),
|
|
||||||
_ => Ok(MessageType::Mention),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromSql<MessageTypeType, Pg> for MessageType {
|
|
||||||
fn from_sql(bytes: Option<&[u8]>) -> deserialize::Result<MessageType> {
|
|
||||||
message_type_type_from_sql(bytes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromSql<sql_types::Text, Pg> for MessageType {
|
|
||||||
fn from_sql(bytes: Option<&[u8]>) -> deserialize::Result<MessageType> {
|
|
||||||
message_type_type_from_sql(bytes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ToSql<MessageTypeType, Pg> for MessageType {
|
|
||||||
fn to_sql<W: Write>(&self, out: &mut Output<W, Pg>) -> serialize::Result {
|
|
||||||
match *self {
|
|
||||||
MessageType::ReceivedInvitation => out.write_all(b"received_invitation")?,
|
|
||||||
MessageType::AssignedToIssue => out.write_all(b"assigned_to_issue")?,
|
|
||||||
MessageType::Mention => out.write_all(b"mention")?,
|
|
||||||
}
|
|
||||||
Ok(IsNull::No)
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user