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",
|
||||
]
|
||||
|
||||
[[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]]
|
||||
name = "derive_more"
|
||||
version = "0.99.11"
|
||||
@ -1965,6 +1977,9 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"actix 0.10.0",
|
||||
"chrono",
|
||||
"derive_enum_iter",
|
||||
"derive_enum_primitive",
|
||||
"derive_enum_sql",
|
||||
"diesel",
|
||||
"serde",
|
||||
"serde_json",
|
||||
|
@ -15,6 +15,9 @@ members = [
|
||||
"./jirs-css",
|
||||
"./shared/jirs-config",
|
||||
"./shared/jirs-data",
|
||||
"./derive/derive_enum_iter",
|
||||
"./derive/derive_enum_primitive",
|
||||
"./derive/derive_enum_sql",
|
||||
"./actors/highlight-actor",
|
||||
"./actors/database-actor",
|
||||
"./actors/database-actor/database_actor-derive",
|
||||
|
@ -7,7 +7,7 @@ index 00d1c0b..5b82ccf 100644
|
||||
+
|
||||
table! {
|
||||
use diesel::sql_types::*;
|
||||
use jirs_data::sql::*;
|
||||
use jirs_data::*;
|
||||
|
||||
/// Representation of the `comments` table.
|
||||
///
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
table! {
|
||||
use diesel::sql_types::*;
|
||||
use jirs_data::sql::*;
|
||||
use jirs_data::*;
|
||||
|
||||
/// Representation of the `comments` table.
|
||||
///
|
||||
@ -49,7 +49,7 @@ table! {
|
||||
|
||||
table! {
|
||||
use diesel::sql_types::*;
|
||||
use jirs_data::sql::*;
|
||||
use jirs_data::*;
|
||||
|
||||
/// Representation of the `epics` table.
|
||||
///
|
||||
@ -108,7 +108,7 @@ table! {
|
||||
|
||||
table! {
|
||||
use diesel::sql_types::*;
|
||||
use jirs_data::sql::*;
|
||||
use jirs_data::*;
|
||||
|
||||
/// Representation of the `invitations` table.
|
||||
///
|
||||
@ -179,7 +179,7 @@ table! {
|
||||
|
||||
table! {
|
||||
use diesel::sql_types::*;
|
||||
use jirs_data::sql::*;
|
||||
use jirs_data::*;
|
||||
|
||||
/// Representation of the `issue_assignees` table.
|
||||
///
|
||||
@ -220,7 +220,7 @@ table! {
|
||||
|
||||
table! {
|
||||
use diesel::sql_types::*;
|
||||
use jirs_data::sql::*;
|
||||
use jirs_data::*;
|
||||
|
||||
/// Representation of the `issue_statuses` table.
|
||||
///
|
||||
@ -267,7 +267,7 @@ table! {
|
||||
|
||||
table! {
|
||||
use diesel::sql_types::*;
|
||||
use jirs_data::sql::*;
|
||||
use jirs_data::*;
|
||||
|
||||
/// Representation of the `issues` table.
|
||||
///
|
||||
@ -374,7 +374,7 @@ table! {
|
||||
|
||||
table! {
|
||||
use diesel::sql_types::*;
|
||||
use jirs_data::sql::*;
|
||||
use jirs_data::*;
|
||||
|
||||
/// Representation of the `messages` table.
|
||||
///
|
||||
@ -439,7 +439,7 @@ table! {
|
||||
|
||||
table! {
|
||||
use diesel::sql_types::*;
|
||||
use jirs_data::sql::*;
|
||||
use jirs_data::*;
|
||||
|
||||
/// Representation of the `projects` table.
|
||||
///
|
||||
@ -498,7 +498,7 @@ table! {
|
||||
|
||||
table! {
|
||||
use diesel::sql_types::*;
|
||||
use jirs_data::sql::*;
|
||||
use jirs_data::*;
|
||||
|
||||
/// Representation of the `tokens` table.
|
||||
///
|
||||
@ -551,7 +551,7 @@ table! {
|
||||
|
||||
table! {
|
||||
use diesel::sql_types::*;
|
||||
use jirs_data::sql::*;
|
||||
use jirs_data::*;
|
||||
|
||||
/// Representation of the `user_projects` table.
|
||||
///
|
||||
@ -610,7 +610,7 @@ table! {
|
||||
|
||||
table! {
|
||||
use diesel::sql_types::*;
|
||||
use jirs_data::sql::*;
|
||||
use jirs_data::*;
|
||||
|
||||
/// 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]
|
||||
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
|
||||
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 {
|
||||
crate::{
|
||||
|
@ -23,7 +23,7 @@ where
|
||||
let input = StyledSelect::build()
|
||||
.name("epic")
|
||||
.selected(selected)
|
||||
.options(model.epics.iter().map(|epic| epic.to_child()).collect())
|
||||
.options(model.epics.iter().map(|epic| epic.to_child()))
|
||||
.normal()
|
||||
.clearable()
|
||||
.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_field::StyledField, styled_form::StyledForm, styled_input::StyledInput,
|
||||
styled_modal::StyledModal, styled_select::StyledSelect,
|
||||
styled_textarea::StyledTextarea, ToChild, ToNode,
|
||||
styled_textarea::StyledTextarea, IntoChild, ToChild, ToNode,
|
||||
},
|
||||
FieldId, Msg,
|
||||
},
|
||||
jirs_data::{IssueFieldId, IssuePriority, ToVec},
|
||||
jirs_data::{IssueFieldId, IssuePriority},
|
||||
seed::{prelude::*, *},
|
||||
};
|
||||
|
||||
@ -124,12 +124,7 @@ fn issue_type_field(modal: &AddIssueModal) -> Node<Msg> {
|
||||
.text_filter(modal.type_state.text_filter.as_str())
|
||||
.opened(modal.type_state.opened)
|
||||
.valid(true)
|
||||
.options(
|
||||
Type::ordered()
|
||||
.iter()
|
||||
.map(|t| t.to_child().name("type"))
|
||||
.collect(),
|
||||
)
|
||||
.options(Type::ordered().iter().map(|t| t.to_child().name("type")))
|
||||
.selected(vec![Type::from(
|
||||
modal.type_state.values.get(0).cloned().unwrap_or_default(),
|
||||
)
|
||||
@ -182,13 +177,7 @@ fn reporter_field(model: &Model, modal: &AddIssueModal) -> Node<Msg> {
|
||||
.normal()
|
||||
.text_filter(modal.reporter_state.text_filter.as_str())
|
||||
.opened(modal.reporter_state.opened)
|
||||
.options(
|
||||
model
|
||||
.users
|
||||
.iter()
|
||||
.map(|u| u.to_child().name("reporter"))
|
||||
.collect(),
|
||||
)
|
||||
.options(model.users.iter().map(|u| u.to_child().name("reporter")))
|
||||
.selected(
|
||||
model
|
||||
.users
|
||||
@ -219,13 +208,7 @@ fn assignees_field(model: &Model, modal: &AddIssueModal) -> Node<Msg> {
|
||||
.multi()
|
||||
.text_filter(modal.assignees_state.text_filter.as_str())
|
||||
.opened(modal.assignees_state.opened)
|
||||
.options(
|
||||
model
|
||||
.users
|
||||
.iter()
|
||||
.map(|u| u.to_child().name("assignees"))
|
||||
.collect(),
|
||||
)
|
||||
.options(model.users.iter().map(|u| u.to_child().name("assignees")))
|
||||
.selected(
|
||||
model
|
||||
.users
|
||||
@ -251,19 +234,15 @@ fn assignees_field(model: &Model, modal: &AddIssueModal) -> Node<Msg> {
|
||||
}
|
||||
|
||||
fn issue_priority_field(modal: &AddIssueModal) -> Node<Msg> {
|
||||
let priorities = IssuePriority::default().into_iter();
|
||||
let select_priority = StyledSelect::build()
|
||||
.name("priority")
|
||||
.normal()
|
||||
.text_filter(modal.priority_state.text_filter.as_str())
|
||||
.opened(modal.priority_state.opened)
|
||||
.valid(true)
|
||||
.options(
|
||||
IssuePriority::ordered()
|
||||
.iter()
|
||||
.map(|p| p.to_child().name("priority"))
|
||||
.collect(),
|
||||
)
|
||||
.selected(vec![modal.priority.to_child().name("priority")])
|
||||
.options(priorities.map(|p| p.into_child().name("priority")))
|
||||
.selected(vec![modal.priority.into_child().name("priority")])
|
||||
.build(FieldId::AddIssueModal(IssueFieldId::Priority))
|
||||
.into_node();
|
||||
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)) => {
|
||||
if let Some(issue) = model.issues_by_id.get(id) {
|
||||
modal.payload = issue.clone().into();
|
||||
modal.description_state.initial_text =
|
||||
issue.description_text.clone().unwrap_or_default();
|
||||
}
|
||||
}
|
||||
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)),
|
||||
value,
|
||||
) => {
|
||||
// modal.payload.description = Some(value.clone());
|
||||
modal.payload.description_text = Some(value.clone());
|
||||
send_ws_msg(
|
||||
WsMsg::IssueUpdate(
|
||||
@ -148,7 +149,7 @@ pub fn update(msg: &Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
||||
PayloadVariant::String(
|
||||
modal
|
||||
.payload
|
||||
.description
|
||||
.description_text
|
||||
.as_ref()
|
||||
.cloned()
|
||||
.unwrap_or_default(),
|
||||
|
@ -6,12 +6,13 @@ use {
|
||||
shared::{
|
||||
styled_avatar::StyledAvatar, styled_button::StyledButton, styled_editor::StyledEditor,
|
||||
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,
|
||||
},
|
||||
comments::*,
|
||||
jirs_data::{CommentFieldId, IssueFieldId, IssuePriority, IssueType, TimeTracking, ToVec},
|
||||
jirs_data::{CommentFieldId, IssueFieldId, IssuePriority, IssueType, TimeTracking},
|
||||
seed::{prelude::*, *},
|
||||
};
|
||||
|
||||
@ -41,7 +42,14 @@ fn modal_header(_model: &Model, modal: &EditIssueModal) -> Node<Msg> {
|
||||
let issue_id = *id;
|
||||
|
||||
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") {
|
||||
Ok(el) => el
|
||||
.dyn_ref::<web_sys::HtmlTextAreaElement>()
|
||||
@ -95,7 +103,6 @@ fn modal_header(_model: &Model, modal: &EditIssueModal) -> Node<Msg> {
|
||||
.build()
|
||||
.into_node();
|
||||
|
||||
let issue_types = IssueType::ordered();
|
||||
let issue_type_select = StyledSelect::build()
|
||||
.dropdown_width(150)
|
||||
.name("type")
|
||||
@ -103,16 +110,15 @@ fn modal_header(_model: &Model, modal: &EditIssueModal) -> Node<Msg> {
|
||||
.opened(top_type_state.opened)
|
||||
.valid(true)
|
||||
.options(
|
||||
issue_types
|
||||
.iter()
|
||||
.map(|t| t.to_child().name("type"))
|
||||
.collect(),
|
||||
IssueType::default()
|
||||
.into_iter()
|
||||
.map(|t| t.into_child().name("type")),
|
||||
)
|
||||
.selected(vec![{
|
||||
let id = modal.id;
|
||||
let issue_type = &payload.issue_type;
|
||||
issue_type
|
||||
.to_child()
|
||||
.into_child()
|
||||
.name("type")
|
||||
.text_owned(format!("{} - {}", issue_type, id))
|
||||
}])
|
||||
@ -244,8 +250,7 @@ fn right_modal_column(model: &Model, modal: &EditIssueModal) -> Node<Msg> {
|
||||
model
|
||||
.issue_statuses
|
||||
.iter()
|
||||
.map(|opt| opt.to_child().name("status"))
|
||||
.collect(),
|
||||
.map(|opt| opt.to_child().name("status")),
|
||||
)
|
||||
.selected(
|
||||
model
|
||||
@ -276,8 +281,7 @@ fn right_modal_column(model: &Model, modal: &EditIssueModal) -> Node<Msg> {
|
||||
model
|
||||
.users
|
||||
.iter()
|
||||
.map(|user| user.to_child().name("assignees"))
|
||||
.collect(),
|
||||
.map(|user| user.to_child().name("assignees")),
|
||||
)
|
||||
.selected(
|
||||
model
|
||||
@ -306,8 +310,7 @@ fn right_modal_column(model: &Model, modal: &EditIssueModal) -> Node<Msg> {
|
||||
model
|
||||
.users
|
||||
.iter()
|
||||
.map(|user| user.to_child().name("reporter"))
|
||||
.collect(),
|
||||
.map(|user| user.to_child().name("reporter")),
|
||||
)
|
||||
.selected(
|
||||
model
|
||||
@ -327,19 +330,17 @@ fn right_modal_column(model: &Model, modal: &EditIssueModal) -> Node<Msg> {
|
||||
.build()
|
||||
.into_node();
|
||||
|
||||
let issue_priorities = IssuePriority::ordered();
|
||||
let priority = StyledSelect::build()
|
||||
.name("priority")
|
||||
.opened(priority_state.opened)
|
||||
.empty()
|
||||
.text_filter(priority_state.text_filter.as_str())
|
||||
.options(
|
||||
issue_priorities
|
||||
.iter()
|
||||
.map(|p| p.to_child().name("priority"))
|
||||
.collect(),
|
||||
IssuePriority::default()
|
||||
.into_iter()
|
||||
.map(|p| p.into_child().name("priority")),
|
||||
)
|
||||
.selected(vec![payload.priority.to_child().name("priority")])
|
||||
.selected(vec![payload.priority.into_child().name("priority")])
|
||||
.build(FieldId::EditIssueModal(EditIssueModalSection::Issue(
|
||||
IssueFieldId::Priority,
|
||||
)))
|
||||
|
@ -108,7 +108,7 @@ pub fn time_tracking_field(
|
||||
.collect(),
|
||||
)
|
||||
.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)
|
||||
.into_node(),
|
||||
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> {
|
||||
let inner = if model.projects.len() <= 1 {
|
||||
let name = model
|
||||
.project
|
||||
.as_ref()
|
||||
.map(|p| p.name.as_str())
|
||||
.unwrap_or_default();
|
||||
span![name]
|
||||
} else {
|
||||
let mut project_by_id = HashMap::new();
|
||||
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() {
|
||||
joined_projects.insert(p.project_id, p);
|
||||
}
|
||||
let inner =
|
||||
if model.projects.len() <= 1 {
|
||||
let name = model
|
||||
.project
|
||||
.as_ref()
|
||||
.map(|p| p.name.as_str())
|
||||
.unwrap_or_default();
|
||||
span![name]
|
||||
} else {
|
||||
let mut project_by_id = HashMap::new();
|
||||
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() {
|
||||
joined_projects.insert(p.project_id, p);
|
||||
}
|
||||
|
||||
StyledSelect::build()
|
||||
.name("current_project")
|
||||
.normal()
|
||||
.options(
|
||||
model
|
||||
.projects
|
||||
.iter()
|
||||
.filter_map(|project| {
|
||||
joined_projects.get(&project.id).map(|_| project.to_child())
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
.selected(
|
||||
page.current_project
|
||||
.values
|
||||
.iter()
|
||||
.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()
|
||||
};
|
||||
StyledSelect::build()
|
||||
.name("current_project")
|
||||
.normal()
|
||||
.options(model.projects.iter().filter_map(|project| {
|
||||
joined_projects.get(&project.id).map(|_| project.to_child())
|
||||
}))
|
||||
.selected(
|
||||
page.current_project
|
||||
.values
|
||||
.iter()
|
||||
.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()
|
||||
.label("Current project")
|
||||
.input(div![C!["project-name"], inner])
|
||||
|
@ -14,11 +14,11 @@ use {
|
||||
styled_input::StyledInput,
|
||||
styled_select::StyledSelect,
|
||||
styled_textarea::StyledTextarea,
|
||||
ToChild, ToNode,
|
||||
IntoChild, ToChild, ToNode,
|
||||
},
|
||||
FieldId, Msg, PageChanged, ProjectFieldId, ProjectPageChange,
|
||||
},
|
||||
jirs_data::{IssueStatus, ProjectCategory, TimeTracking, ToVec},
|
||||
jirs_data::{IssueStatus, ProjectCategory, TimeTracking},
|
||||
seed::{prelude::*, *},
|
||||
std::collections::HashMap,
|
||||
};
|
||||
@ -168,20 +168,23 @@ fn description_field(page: &ProjectSettingsPage) -> Node<Msg> {
|
||||
|
||||
/// Build project category dropdown with styled field wrapper
|
||||
fn category_field(page: &ProjectSettingsPage) -> Node<Msg> {
|
||||
let project_categories = ProjectCategory::ordered();
|
||||
let category = StyledSelect::build()
|
||||
.opened(page.project_category_state.opened)
|
||||
.text_filter(page.project_category_state.text_filter.as_str())
|
||||
.valid(true)
|
||||
.normal()
|
||||
.options(project_categories.iter().map(|c| c.to_child()).collect())
|
||||
.options(
|
||||
ProjectCategory::default()
|
||||
.into_iter()
|
||||
.map(|c| c.into_child()),
|
||||
)
|
||||
.selected(vec![page
|
||||
.payload
|
||||
.category
|
||||
.as_ref()
|
||||
.cloned()
|
||||
.unwrap_or_default()
|
||||
.to_child()])
|
||||
.into_child()])
|
||||
.build(FieldId::ProjectSettings(ProjectFieldId::Category))
|
||||
.into_node();
|
||||
StyledField::build()
|
||||
|
@ -4,12 +4,12 @@ use {
|
||||
shared::{
|
||||
inner_layout, styled_button::StyledButton, styled_field::StyledField,
|
||||
styled_form::StyledForm, styled_input::StyledInput, styled_select::StyledSelect,
|
||||
ToChild, ToNode,
|
||||
IntoChild, ToNode,
|
||||
},
|
||||
validations::is_email,
|
||||
FieldId, Msg, PageChanged, UsersPageChange,
|
||||
},
|
||||
jirs_data::{InvitationState, ToVec, UserRole, UsersFieldId},
|
||||
jirs_data::{InvitationState, UserRole, UsersFieldId},
|
||||
seed::{prelude::*, *},
|
||||
};
|
||||
|
||||
@ -45,14 +45,17 @@ pub fn view(model: &Model) -> Node<Msg> {
|
||||
.build()
|
||||
.into_node();
|
||||
|
||||
let roles = UserRole::ordered();
|
||||
let user_role = StyledSelect::build()
|
||||
.name("user_role")
|
||||
.valid(true)
|
||||
.normal()
|
||||
.state(&page.user_role_state)
|
||||
.selected(vec![page.user_role.to_child()])
|
||||
.options(roles.iter().map(|role| role.to_child()).collect())
|
||||
.selected(vec![page.user_role.into_child()])
|
||||
.options(
|
||||
UserRole::default()
|
||||
.into_iter()
|
||||
.map(|role| role.into_child()),
|
||||
)
|
||||
.build(FieldId::Users(UsersFieldId::UserRole))
|
||||
.into_node();
|
||||
let user_role_field = StyledField::build()
|
||||
|
@ -36,6 +36,12 @@ pub trait ToChild<'l> {
|
||||
fn to_child<'m: 'l>(&'m self) -> Self::Builder;
|
||||
}
|
||||
|
||||
pub trait IntoChild<'l> {
|
||||
type Builder: 'l;
|
||||
|
||||
fn into_child(self) -> Self::Builder;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn go_to_board(orders: &mut impl Orders<Msg>) {
|
||||
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};
|
||||
use crate::shared::styled_select_child::*;
|
||||
use crate::shared::ToNode;
|
||||
use crate::{FieldId, Msg};
|
||||
// pub trait ChildIter<'l> = Iterator<Item = StyledSelectChildBuilder<'l>>;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
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,
|
||||
variant: Variant,
|
||||
dropdown_width: Option<usize>,
|
||||
name: Option<&'l str>,
|
||||
valid: bool,
|
||||
is_multi: bool,
|
||||
options: Vec<StyledSelectChildBuilder<'l>>,
|
||||
options: Option<Options>,
|
||||
selected: Vec<StyledSelectChildBuilder<'l>>,
|
||||
text_filter: &'l str,
|
||||
opened: 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> {
|
||||
render(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'l> StyledSelect<'l> {
|
||||
pub fn build() -> StyledSelectBuilder<'l> {
|
||||
impl<'l, Options> StyledSelect<'l, Options>
|
||||
where
|
||||
Options: Iterator<Item = StyledSelectChildBuilder<'l>>,
|
||||
{
|
||||
pub fn build() -> StyledSelectBuilder<'l, Options> {
|
||||
StyledSelectBuilder {
|
||||
variant: None,
|
||||
dropdown_width: None,
|
||||
@ -143,21 +159,27 @@ impl<'l> StyledSelect<'l> {
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct StyledSelectBuilder<'l> {
|
||||
pub struct StyledSelectBuilder<'l, Options>
|
||||
where
|
||||
Options: Iterator<Item = StyledSelectChildBuilder<'l>>,
|
||||
{
|
||||
variant: Option<Variant>,
|
||||
dropdown_width: Option<usize>,
|
||||
name: Option<&'l str>,
|
||||
valid: Option<bool>,
|
||||
is_multi: Option<bool>,
|
||||
options: Option<Vec<StyledSelectChildBuilder<'l>>>,
|
||||
options: Option<Options>,
|
||||
selected: Option<Vec<StyledSelectChildBuilder<'l>>>,
|
||||
text_filter: Option<&'l str>,
|
||||
opened: Option<bool>,
|
||||
clearable: bool,
|
||||
}
|
||||
|
||||
impl<'l> StyledSelectBuilder<'l> {
|
||||
pub fn build(self, id: FieldId) -> StyledSelect<'l> {
|
||||
impl<'l, Options> StyledSelectBuilder<'l, Options>
|
||||
where
|
||||
Options: Iterator<Item = StyledSelectChildBuilder<'l>>,
|
||||
{
|
||||
pub fn build(self, id: FieldId) -> StyledSelect<'l, Options> {
|
||||
StyledSelect {
|
||||
id,
|
||||
variant: self.variant.unwrap_or_default(),
|
||||
@ -165,7 +187,7 @@ impl<'l> StyledSelectBuilder<'l> {
|
||||
name: self.name,
|
||||
valid: self.valid.unwrap_or(true),
|
||||
is_multi: self.is_multi.unwrap_or_default(),
|
||||
options: self.options.unwrap_or_default(),
|
||||
options: self.options,
|
||||
selected: self.selected.unwrap_or_default(),
|
||||
text_filter: self.text_filter.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 {
|
||||
self.opened(state.opened)
|
||||
.text_filter(state.text_filter.as_str())
|
||||
@ -211,7 +225,7 @@ impl<'l> StyledSelectBuilder<'l> {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn options(mut self, options: Vec<StyledSelectChildBuilder<'l>>) -> Self {
|
||||
pub fn options(mut self, options: Options) -> Self {
|
||||
self.options = Some(options);
|
||||
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 {
|
||||
id,
|
||||
variant,
|
||||
@ -303,28 +320,34 @@ pub fn render(values: StyledSelect) -> Node<Msg> {
|
||||
empty![]
|
||||
};
|
||||
|
||||
let children: Vec<Node<Msg>> = options
|
||||
.into_iter()
|
||||
.filter(|o| !selected.contains(&o) && o.match_text(text_filter))
|
||||
.map(|child| {
|
||||
let child = child.build(DisplayType::SelectOption);
|
||||
let value = child.value();
|
||||
let node = child.into_node();
|
||||
let children: Vec<Node<Msg>> = if let Some(options) = options {
|
||||
options
|
||||
.filter(|o| !selected.contains(&o) && o.match_text(text_filter))
|
||||
.map(|child| {
|
||||
let child = child.build(DisplayType::SelectOption);
|
||||
let value = child.value();
|
||||
let node = child.into_node();
|
||||
|
||||
let on_change = {
|
||||
let field_id = id.clone();
|
||||
mouse_ev(Ev::Click, move |_| {
|
||||
Msg::StyledSelectChanged(field_id, StyledSelectChanged::Changed(Some(value)))
|
||||
})
|
||||
};
|
||||
div![
|
||||
attrs![At::Class => "option"],
|
||||
on_change,
|
||||
on_handler.clone(),
|
||||
node
|
||||
]
|
||||
})
|
||||
.collect();
|
||||
let on_change = {
|
||||
let field_id = id.clone();
|
||||
mouse_ev(Ev::Click, move |_| {
|
||||
Msg::StyledSelectChanged(
|
||||
field_id,
|
||||
StyledSelectChanged::Changed(Some(value)),
|
||||
)
|
||||
})
|
||||
};
|
||||
div![
|
||||
attrs![At::Class => "option"],
|
||||
on_change,
|
||||
on_handler.clone(),
|
||||
node
|
||||
]
|
||||
})
|
||||
.collect()
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
|
||||
let text_input = if opened {
|
||||
seed::input![
|
||||
|
@ -1,10 +1,12 @@
|
||||
use std::borrow::Cow;
|
||||
|
||||
use seed::{prelude::*, *};
|
||||
|
||||
use crate::shared::styled_select::Variant;
|
||||
use crate::shared::{ToChild, ToNode};
|
||||
use crate::Msg;
|
||||
use {
|
||||
crate::{
|
||||
shared::styled_select::Variant,
|
||||
shared::{IntoChild, ToChild, ToNode},
|
||||
Msg,
|
||||
},
|
||||
seed::{prelude::*, *},
|
||||
std::borrow::Cow,
|
||||
};
|
||||
|
||||
pub enum DisplayType {
|
||||
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>;
|
||||
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())
|
||||
.add_class(self.to_str())
|
||||
.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>;
|
||||
|
||||
fn to_child<'m: 'l>(&'m self) -> Self::Builder {
|
||||
fn into_child(self) -> Self::Builder {
|
||||
let name = self.to_label();
|
||||
|
||||
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>;
|
||||
|
||||
fn to_child<'m: 'l>(&'m self) -> Self::Builder {
|
||||
fn into_child(self) -> Self::Builder {
|
||||
StyledSelectChild::build()
|
||||
.add_class(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>;
|
||||
|
||||
fn to_child<'m: 'l>(&'m self) -> Self::Builder {
|
||||
fn into_child(self) -> Self::Builder {
|
||||
let name = self.to_str();
|
||||
|
||||
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
|
||||
WsMsg::IssueStatusesLoaded(v) => {
|
||||
model.issue_statuses = v;
|
||||
@ -232,8 +217,24 @@ 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,
|
||||
));
|
||||
}
|
||||
WsMsg::IssueUpdated(mut issue) => {
|
||||
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) {
|
||||
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) => {
|
||||
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 {
|
||||
if is.id == id {
|
||||
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(
|
||||
ResourceKind::Issue,
|
||||
OperationKind::SingleRemoved,
|
||||
@ -264,6 +262,7 @@ pub fn update(msg: WsMsg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
||||
// users
|
||||
WsMsg::ProjectUsersLoaded(v) => {
|
||||
model.users = v.clone();
|
||||
model.users_by_id.clear();
|
||||
for user in v {
|
||||
model.users_by_id.insert(user.id, user.clone());
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ name = "jirs_data"
|
||||
path = "./src/lib.rs"
|
||||
|
||||
[features]
|
||||
backend = ["diesel", "actix"]
|
||||
backend = ["diesel", "actix", "derive_enum_sql"]
|
||||
frontend = []
|
||||
|
||||
[dependencies]
|
||||
@ -30,3 +30,13 @@ optional = true
|
||||
optional = true
|
||||
version = "1.4.5"
|
||||
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")]
|
||||
use diesel::*;
|
||||
|
||||
#[cfg(feature = "backend")]
|
||||
pub use sql::*;
|
||||
use {
|
||||
chrono::NaiveDateTime,
|
||||
derive_enum_iter::EnumIter,
|
||||
derive_enum_primitive::EnumPrimitive,
|
||||
serde::{Deserialize, Serialize},
|
||||
std::cmp::Ordering,
|
||||
std::str::FromStr,
|
||||
@ -17,12 +17,7 @@ pub mod msg;
|
||||
mod payloads;
|
||||
|
||||
#[cfg(feature = "backend")]
|
||||
pub mod sql;
|
||||
|
||||
pub trait ToVec {
|
||||
type Item;
|
||||
fn ordered() -> Vec<Self::Item>;
|
||||
}
|
||||
use derive_enum_sql::EnumSql;
|
||||
|
||||
pub type NumberOfDeleted = usize;
|
||||
pub type IssueId = i32;
|
||||
@ -52,90 +47,34 @@ pub type Lang = String;
|
||||
pub type BindToken = Uuid;
|
||||
pub type InvitationToken = Uuid;
|
||||
|
||||
macro_rules! enum_to_u32 {
|
||||
($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", derive(FromSqlRow, AsExpression, EnumSql))]
|
||||
#[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 {
|
||||
Task,
|
||||
Bug,
|
||||
Story,
|
||||
}
|
||||
|
||||
impl ToVec for IssueType {
|
||||
type Item = IssueType;
|
||||
|
||||
fn ordered() -> Vec<Self> {
|
||||
use IssueType::*;
|
||||
vec![Task, Bug, Story]
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for IssueType {
|
||||
fn default() -> Self {
|
||||
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 {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
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")]
|
||||
#[derive(Clone, Copy, Deserialize, Serialize, Debug, PartialOrd, PartialEq, Hash)]
|
||||
#[derive(
|
||||
Clone, Copy, Deserialize, Serialize, Debug, PartialOrd, PartialEq, Hash, EnumIter, EnumPrimitive,
|
||||
)]
|
||||
pub enum IssuePriority {
|
||||
Highest,
|
||||
High,
|
||||
@ -144,71 +83,21 @@ pub enum IssuePriority {
|
||||
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 {
|
||||
fn default() -> Self {
|
||||
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 {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str(self.to_str())
|
||||
}
|
||||
}
|
||||
|
||||
enum_to_u32!(
|
||||
IssuePriority, Medium,
|
||||
Highest => 5,
|
||||
High => 4,
|
||||
Medium => 3,
|
||||
Low => 2,
|
||||
Lowest => 1
|
||||
);
|
||||
|
||||
#[cfg_attr(feature = "backend", derive(FromSqlRow, AsExpression))]
|
||||
#[cfg_attr(feature = "backend", derive(FromSqlRow, AsExpression, EnumSql))]
|
||||
#[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 {
|
||||
User,
|
||||
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 {
|
||||
fn default() -> Self {
|
||||
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 {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str(self.to_str())
|
||||
}
|
||||
}
|
||||
|
||||
enum_to_u32!(
|
||||
UserRole, User,
|
||||
User => 0,
|
||||
Manager => 1,
|
||||
Owner => 2
|
||||
);
|
||||
|
||||
#[cfg_attr(feature = "backend", derive(FromSqlRow, AsExpression))]
|
||||
#[cfg_attr(feature = "backend", derive(FromSqlRow, AsExpression, EnumSql))]
|
||||
#[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 {
|
||||
Software,
|
||||
Marketing,
|
||||
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 {
|
||||
fn default() -> Self {
|
||||
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 {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str(self.to_str())
|
||||
}
|
||||
}
|
||||
|
||||
enum_to_u32!(
|
||||
ProjectCategory, Software,
|
||||
Software => 0,
|
||||
Marketing => 1,
|
||||
Business => 2
|
||||
);
|
||||
|
||||
#[cfg_attr(feature = "backend", derive(FromSqlRow, AsExpression))]
|
||||
#[cfg_attr(feature = "backend", derive(FromSqlRow, AsExpression, EnumSql))]
|
||||
#[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 {
|
||||
Sent,
|
||||
Accepted,
|
||||
@ -360,29 +173,26 @@ impl Default for InvitationState {
|
||||
|
||||
impl std::fmt::Display for InvitationState {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
InvitationState::Sent => f.write_str("sent"),
|
||||
InvitationState::Accepted => f.write_str("accepted"),
|
||||
InvitationState::Revoked => f.write_str("revoked"),
|
||||
}
|
||||
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 = "TimeTrackingType")]
|
||||
#[derive(Clone, Copy, Deserialize, Serialize, Debug, PartialOrd, PartialEq, Hash)]
|
||||
#[derive(
|
||||
Clone, Copy, Deserialize, Serialize, Debug, PartialOrd, PartialEq, Hash, EnumIter, EnumPrimitive,
|
||||
)]
|
||||
pub enum TimeTracking {
|
||||
Untracked,
|
||||
Fibonacci,
|
||||
Hourly,
|
||||
}
|
||||
|
||||
enum_to_u32!(
|
||||
TimeTracking, Untracked,
|
||||
Untracked => 0,
|
||||
Fibonacci => 1,
|
||||
Hourly => 2
|
||||
);
|
||||
impl Default for TimeTracking {
|
||||
fn default() -> Self {
|
||||
Self::Untracked
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Debug, PartialEq)]
|
||||
pub struct ErrorResponse {
|
||||
@ -515,29 +325,26 @@ pub struct IssueAssignee {
|
||||
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")]
|
||||
#[derive(Clone, Copy, Deserialize, Serialize, Debug, PartialOrd, PartialEq, Hash)]
|
||||
#[derive(
|
||||
Clone, Copy, Deserialize, Serialize, Debug, PartialOrd, PartialEq, Hash, EnumPrimitive,
|
||||
)]
|
||||
pub enum MessageType {
|
||||
ReceivedInvitation,
|
||||
AssignedToIssue,
|
||||
Mention,
|
||||
}
|
||||
|
||||
enum_to_u32!(
|
||||
MessageType, Mention,
|
||||
ReceivedInvitation => 0,
|
||||
AssignedToIssue => 1,
|
||||
Mention => 2
|
||||
);
|
||||
impl Default for MessageType {
|
||||
fn default() -> Self {
|
||||
Self::Mention
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for MessageType {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
MessageType::ReceivedInvitation => f.write_str("ReceivedInvitation"),
|
||||
MessageType::AssignedToIssue => f.write_str("AssignedToIssue"),
|
||||
MessageType::Mention => f.write_str("Mention"),
|
||||
}
|
||||
f.write_str(self.to_label())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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