Remove some builders
This commit is contained in:
parent
520d2400be
commit
e6ef4d6b0e
4
.env
4
.env
@ -1,7 +1,7 @@
|
|||||||
DEBUG=true
|
DEBUG=true
|
||||||
RUST_LOG=debug
|
RUST_LOG=debug
|
||||||
JIRS_CLIENT_PORT=7000
|
JIRS_CLIENT_PORT=80
|
||||||
JIRS_CLIENT_BIND=0.0.0.0
|
JIRS_CLIENT_BIND=jirs.lvh.me
|
||||||
DATABASE_URL=postgres://postgres@localhost:5432/jirs
|
DATABASE_URL=postgres://postgres@localhost:5432/jirs
|
||||||
JIRS_SERVER_PORT=5000
|
JIRS_SERVER_PORT=5000
|
||||||
JIRS_SERVER_BIND=0.0.0.0
|
JIRS_SERVER_BIND=0.0.0.0
|
||||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -24,3 +24,4 @@ highlight/jirs-highlight/build
|
|||||||
uploads
|
uploads
|
||||||
config
|
config
|
||||||
shared/jirs-config/target
|
shared/jirs-config/target
|
||||||
|
jirs-client/src/location.rs
|
||||||
|
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -1988,6 +1988,7 @@ dependencies = [
|
|||||||
"chrono",
|
"chrono",
|
||||||
"derive_enum_iter",
|
"derive_enum_iter",
|
||||||
"derive_enum_primitive",
|
"derive_enum_primitive",
|
||||||
|
"dotenv",
|
||||||
"futures 0.1.31",
|
"futures 0.1.31",
|
||||||
"jirs-data",
|
"jirs-data",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
|
@ -30,6 +30,8 @@ chrono = { version = "0.4", default-features = false, features = ["serde", "wasm
|
|||||||
uuid = { version = "0.8.1", features = ["serde"] }
|
uuid = { version = "0.8.1", features = ["serde"] }
|
||||||
futures = "^0.1.26"
|
futures = "^0.1.26"
|
||||||
|
|
||||||
|
dotenv = { version = "*" }
|
||||||
|
|
||||||
[dependencies.wee_alloc]
|
[dependencies.wee_alloc]
|
||||||
version = "*"
|
version = "*"
|
||||||
features = ["static_array_backend"]
|
features = ["static_array_backend"]
|
||||||
|
55
jirs-client/build.rs
Normal file
55
jirs-client/build.rs
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
#![feature(format_args_capture)]
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
if let Ok(contents) = std::fs::read_to_string("../.env") {
|
||||||
|
for line in contents.lines() {
|
||||||
|
if line.starts_with('#') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let parts: Vec<&str> = line.split('=').collect();
|
||||||
|
match (parts.get(0), parts.get(1)) {
|
||||||
|
(Some(k), Some(v)) => std::env::set_var(k, v),
|
||||||
|
_ => continue,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let addr = std::env::var("JIRS_SERVER_BIND").unwrap_or("0.0.0.0".to_string());
|
||||||
|
let addr = if addr.as_str() == "0.0.0.0" || addr.as_str() == "localhost" {
|
||||||
|
"localhost"
|
||||||
|
} else {
|
||||||
|
addr.as_str()
|
||||||
|
}
|
||||||
|
.to_string();
|
||||||
|
let port = std::env::var("JIRS_SERVER_PORT").unwrap_or("80".to_string());
|
||||||
|
let port = match port.as_str() {
|
||||||
|
"80" | "8080" | "443" => "".to_string(),
|
||||||
|
_ => format!(":{}", port),
|
||||||
|
};
|
||||||
|
let addr = format!("{}{}", addr, port);
|
||||||
|
|
||||||
|
std::fs::write(
|
||||||
|
"./src/location.rs",
|
||||||
|
format!(
|
||||||
|
"
|
||||||
|
pub fn host_url() -> &'static str {{
|
||||||
|
if cfg!(debug_assertions) {{
|
||||||
|
\"http://{addr}\"
|
||||||
|
}} else {{
|
||||||
|
\"https://{addr}\"
|
||||||
|
}}
|
||||||
|
}}
|
||||||
|
pub fn ws_url() -> &'static str {{
|
||||||
|
if cfg!(debug_assertions) {{
|
||||||
|
\"ws://{addr}/ws/\"
|
||||||
|
}} else {{
|
||||||
|
\"wss://{addr}/ws/\"
|
||||||
|
}}
|
||||||
|
}}
|
||||||
|
",
|
||||||
|
addr = addr
|
||||||
|
)
|
||||||
|
.trim(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
}
|
@ -1,14 +1,12 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
RSASS_PATH=$(command -v rsass)
|
RSASS_PATH=$(command -v rsass)
|
||||||
if [[ "${RSASS_PATH}" == "" ]];
|
if [[ "${RSASS_PATH}" == "" ]]; then
|
||||||
then
|
|
||||||
cargo install rsass --features=commandline
|
cargo install rsass --features=commandline
|
||||||
fi
|
fi
|
||||||
|
|
||||||
WASM_PACK_PATH=$(command -v wasm-pack)
|
WASM_PACK_PATH=$(command -v wasm-pack)
|
||||||
if [[ "${WASM_PACK_PATH}" == "" ]];
|
if [[ "${WASM_PACK_PATH}" == "" ]]; then
|
||||||
then
|
|
||||||
cargo install wasm-pack
|
cargo install wasm-pack
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@ -23,6 +21,7 @@ cd ${CLIENT_ROOT}
|
|||||||
. .env
|
. .env
|
||||||
|
|
||||||
cargo watch \
|
cargo watch \
|
||||||
|
-i ./jirs-client/src/location.rs \
|
||||||
-s ${CLIENT_ROOT}/scripts/run-wasm-pack.sh \
|
-s ${CLIENT_ROOT}/scripts/run-wasm-pack.sh \
|
||||||
-w ${CLIENT_ROOT}/src \
|
-w ${CLIENT_ROOT}/src \
|
||||||
-w ${CLIENT_ROOT}/Cargo.toml \
|
-w ${CLIENT_ROOT}/Cargo.toml \
|
||||||
|
@ -13,13 +13,10 @@ wasm-pack --verbose build --mode ${MODE} ${BUILD_TYPE} --out-name jirs --out-dir
|
|||||||
|
|
||||||
cd ${CLIENT_ROOT}
|
cd ${CLIENT_ROOT}
|
||||||
rm -Rf ${CLIENT_ROOT}/build/styles.css
|
rm -Rf ${CLIENT_ROOT}/build/styles.css
|
||||||
rsass -t Expanded ${PROJECT_ROOT}/jirs-client/js/styles.css > ${CLIENT_ROOT}/tmp/styles.css
|
rsass -t Expanded ${PROJECT_ROOT}/jirs-client/js/styles.css >${CLIENT_ROOT}/tmp/styles.css
|
||||||
|
|
||||||
cp -r ${CLIENT_ROOT}/static/* ${CLIENT_ROOT}/tmp
|
cp -r ${CLIENT_ROOT}/static/* ${CLIENT_ROOT}/tmp
|
||||||
|
|
||||||
cat ${CLIENT_ROOT}/static/index.js |
|
cat ${CLIENT_ROOT}/static/index.js &>${CLIENT_ROOT}/tmp/index.js
|
||||||
sed -e "s/process.env.JIRS_SERVER_BIND/'$JIRS_SERVER_BIND'/g" |
|
|
||||||
sed -e "s/process.env.JIRS_SERVER_PORT/'$JIRS_SERVER_PORT'/g" &>${CLIENT_ROOT}/tmp/index.js
|
|
||||||
|
|
||||||
cp ${CLIENT_ROOT}/build/*.{js,wasm} ${CLIENT_ROOT}/tmp/
|
cp ${CLIENT_ROOT}/build/*.{js,wasm} ${CLIENT_ROOT}/tmp/
|
||||||
cp ${CLIENT_ROOT}/js/template.html ${CLIENT_ROOT}/tmp/index.html
|
cp ${CLIENT_ROOT}/js/template.html ${CLIENT_ROOT}/tmp/index.html
|
||||||
|
@ -3,13 +3,14 @@ use {
|
|||||||
seed::{prelude::*, *},
|
seed::{prelude::*, *},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct StyledAvatar<'l> {
|
pub struct StyledAvatar<'l> {
|
||||||
avatar_url: Option<&'l str>,
|
pub avatar_url: Option<&'l str>,
|
||||||
size: u32,
|
pub size: u32,
|
||||||
name: &'l str,
|
pub name: &'l str,
|
||||||
on_click: Option<EventHandler<Msg>>,
|
pub on_click: Option<EventHandler<Msg>>,
|
||||||
class_list: Vec<&'l str>,
|
pub class_list: &'l str,
|
||||||
user_index: usize,
|
pub user_index: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'l> Default for StyledAvatar<'l> {
|
impl<'l> Default for StyledAvatar<'l> {
|
||||||
@ -19,21 +20,7 @@ impl<'l> Default for StyledAvatar<'l> {
|
|||||||
size: 32,
|
size: 32,
|
||||||
name: "",
|
name: "",
|
||||||
on_click: None,
|
on_click: None,
|
||||||
class_list: vec![],
|
class_list: "",
|
||||||
user_index: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'l> StyledAvatar<'l> {
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn build() -> StyledAvatarBuilder<'l> {
|
|
||||||
StyledAvatarBuilder {
|
|
||||||
avatar_url: None,
|
|
||||||
size: None,
|
|
||||||
name: "",
|
|
||||||
on_click: None,
|
|
||||||
class_list: vec![],
|
|
||||||
user_index: 0,
|
user_index: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -46,67 +33,6 @@ impl<'l> ToNode for StyledAvatar<'l> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct StyledAvatarBuilder<'l> {
|
|
||||||
avatar_url: Option<&'l str>,
|
|
||||||
size: Option<u32>,
|
|
||||||
name: &'l str,
|
|
||||||
on_click: Option<EventHandler<Msg>>,
|
|
||||||
class_list: Vec<&'l str>,
|
|
||||||
user_index: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'l> StyledAvatarBuilder<'l> {
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn avatar_url<'m: 'l>(mut self, avatar_url: &'m str) -> Self {
|
|
||||||
if !avatar_url.is_empty() {
|
|
||||||
self.avatar_url = Some(avatar_url);
|
|
||||||
}
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn size(mut self, size: u32) -> Self {
|
|
||||||
self.size = Some(size);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn name<'m: 'l>(mut self, name: &'m str) -> Self {
|
|
||||||
self.name = name;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn on_click(mut self, on_click: EventHandler<Msg>) -> Self {
|
|
||||||
self.on_click = Some(on_click);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn add_class<'m: 'l>(mut self, name: &'m str) -> Self {
|
|
||||||
self.class_list.push(name);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn user_index(mut self, user_index: usize) -> Self {
|
|
||||||
self.user_index = user_index;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn build(self) -> StyledAvatar<'l> {
|
|
||||||
StyledAvatar {
|
|
||||||
avatar_url: self.avatar_url,
|
|
||||||
size: self.size.unwrap_or(32),
|
|
||||||
name: self.name,
|
|
||||||
on_click: self.on_click,
|
|
||||||
class_list: self.class_list,
|
|
||||||
user_index: self.user_index,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn render(values: StyledAvatar) -> Node<Msg> {
|
pub fn render(values: StyledAvatar) -> Node<Msg> {
|
||||||
let StyledAvatar {
|
let StyledAvatar {
|
||||||
avatar_url,
|
avatar_url,
|
||||||
@ -120,10 +46,6 @@ pub fn render(values: StyledAvatar) -> Node<Msg> {
|
|||||||
let index = user_index % 8;
|
let index = user_index % 8;
|
||||||
|
|
||||||
let shared_style = format!("width: {size}px; height: {size}px", size = size);
|
let shared_style = format!("width: {size}px; height: {size}px", size = size);
|
||||||
let class_list: Attrs = {
|
|
||||||
let s: String = class_list.join(" ");
|
|
||||||
C![s.as_str()]
|
|
||||||
};
|
|
||||||
let letter = name
|
let letter = name
|
||||||
.chars()
|
.chars()
|
||||||
.rev()
|
.rev()
|
||||||
@ -138,16 +60,14 @@ pub fn render(values: StyledAvatar) -> Node<Msg> {
|
|||||||
url = url
|
url = url
|
||||||
);
|
);
|
||||||
div![
|
div![
|
||||||
C!["styledAvatar image"],
|
C!["styledAvatar image", class_list],
|
||||||
class_list,
|
|
||||||
attrs![At::Style => style, At::Title => name],
|
attrs![At::Style => style, At::Title => name],
|
||||||
on_click
|
on_click
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
div![
|
div![
|
||||||
C!["styledAvatar letter"],
|
C!["styledAvatar letter", class_list],
|
||||||
class_list,
|
|
||||||
attrs![
|
attrs![
|
||||||
At::Class => format!("avatarColor{}", index + 1),
|
At::Class => format!("avatarColor{}", index + 1),
|
||||||
At::Style => shared_style,
|
At::Style => shared_style,
|
||||||
|
@ -4,7 +4,7 @@ use {
|
|||||||
};
|
};
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
enum Variant {
|
pub enum ButtonVariant {
|
||||||
Primary,
|
Primary,
|
||||||
Success,
|
Success,
|
||||||
Danger,
|
Danger,
|
||||||
@ -12,154 +12,35 @@ enum Variant {
|
|||||||
Empty,
|
Empty,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Variant {
|
impl ButtonVariant {
|
||||||
fn to_str(&self) -> &'static str {
|
fn to_str(&self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
Variant::Primary => "primary",
|
ButtonVariant::Primary => "primary",
|
||||||
Variant::Success => "success",
|
ButtonVariant::Success => "success",
|
||||||
Variant::Danger => "danger",
|
ButtonVariant::Danger => "danger",
|
||||||
Variant::Secondary => "secondary",
|
ButtonVariant::Secondary => "secondary",
|
||||||
Variant::Empty => "empty",
|
ButtonVariant::Empty => "empty",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToString for Variant {
|
impl std::fmt::Display for ButtonVariant {
|
||||||
fn to_string(&self) -> String {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
self.to_str().to_string()
|
f.write_str(self.to_str())
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct StyledButtonBuilder<'l> {
|
|
||||||
variant: Option<Variant>,
|
|
||||||
disabled: Option<bool>,
|
|
||||||
active: Option<bool>,
|
|
||||||
text: Option<&'l str>,
|
|
||||||
icon: Option<Node<Msg>>,
|
|
||||||
on_click: Option<EventHandler<Msg>>,
|
|
||||||
children: Option<Vec<Node<Msg>>>,
|
|
||||||
class_list: Vec<&'l str>,
|
|
||||||
button_type: Option<&'l str>,
|
|
||||||
button_id: Option<ButtonId>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'l> StyledButtonBuilder<'l> {
|
|
||||||
#[inline(always)]
|
|
||||||
fn variant(mut self, value: Variant) -> Self {
|
|
||||||
self.variant = Some(value);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn primary(self) -> Self {
|
|
||||||
self.variant(Variant::Primary)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn success(self) -> Self {
|
|
||||||
self.variant(Variant::Success)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn danger(self) -> Self {
|
|
||||||
self.variant(Variant::Danger)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn secondary(self) -> Self {
|
|
||||||
self.variant(Variant::Secondary)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn empty(self) -> Self {
|
|
||||||
self.variant(Variant::Empty)
|
|
||||||
}
|
|
||||||
|
|
||||||
// pub fn button_id(mut self, button_id: ButtonId) -> Self {
|
|
||||||
// self.button_id = Some(button_id);
|
|
||||||
// self
|
|
||||||
// }
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn disabled(mut self, value: bool) -> Self {
|
|
||||||
self.disabled = Some(value);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn active(mut self, value: bool) -> Self {
|
|
||||||
self.active = Some(value);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn text(mut self, value: &'l str) -> Self {
|
|
||||||
self.text = Some(value);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn icon<I>(mut self, value: I) -> Self
|
|
||||||
where
|
|
||||||
I: ToNode,
|
|
||||||
{
|
|
||||||
self.icon = Some(value.into_node());
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn on_click(mut self, value: EventHandler<Msg>) -> Self {
|
|
||||||
self.on_click = Some(value);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn children(mut self, value: Vec<Node<Msg>>) -> Self {
|
|
||||||
self.children = Some(value);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn add_class(mut self, name: &'l str) -> Self {
|
|
||||||
self.class_list.push(name);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn set_type_reset(mut self) -> Self {
|
|
||||||
self.button_type = Some("reset");
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn build(self) -> StyledButton<'l> {
|
|
||||||
StyledButton {
|
|
||||||
variant: self.variant.unwrap_or(Variant::Primary),
|
|
||||||
disabled: self.disabled.unwrap_or(false),
|
|
||||||
active: self.active.unwrap_or(false),
|
|
||||||
text: self.text,
|
|
||||||
icon: self.icon,
|
|
||||||
on_click: self.on_click,
|
|
||||||
children: self.children.unwrap_or_default(),
|
|
||||||
class_list: self.class_list,
|
|
||||||
button_type: self.button_type.unwrap_or("submit"),
|
|
||||||
button_id: self.button_id,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct StyledButton<'l> {
|
pub struct StyledButton<'l> {
|
||||||
variant: Variant,
|
pub variant: ButtonVariant,
|
||||||
disabled: bool,
|
pub disabled: bool,
|
||||||
active: bool,
|
pub active: bool,
|
||||||
text: Option<&'l str>,
|
pub text: Option<&'l str>,
|
||||||
icon: Option<Node<Msg>>,
|
pub icon: Option<Node<Msg>>,
|
||||||
on_click: Option<EventHandler<Msg>>,
|
pub on_click: Option<EventHandler<Msg>>,
|
||||||
children: Vec<Node<Msg>>,
|
pub children: Vec<Node<Msg>>,
|
||||||
class_list: Vec<&'l str>,
|
pub class_list: &'l str,
|
||||||
button_type: &'l str,
|
pub button_type: &'l str,
|
||||||
button_id: Option<ButtonId>,
|
pub button_id: Option<ButtonId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'l> StyledButton<'l> {
|
impl<'l> StyledButton<'l> {
|
||||||
@ -168,24 +49,34 @@ impl<'l> StyledButton<'l> {
|
|||||||
I: ToNode,
|
I: ToNode,
|
||||||
{
|
{
|
||||||
Self {
|
Self {
|
||||||
variant: Variant::Secondary,
|
variant: ButtonVariant::Secondary,
|
||||||
disabled: false,
|
disabled: false,
|
||||||
active: false,
|
active: false,
|
||||||
text: Some(text),
|
text: Some(text),
|
||||||
icon: Some(icon.into_node()),
|
icon: Some(icon.into_node()),
|
||||||
on_click: None,
|
on_click: None,
|
||||||
children: vec![],
|
children: vec![],
|
||||||
class_list: vec![],
|
class_list: "",
|
||||||
button_type: "",
|
button_type: "submit",
|
||||||
button_id: None,
|
button_id: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'l> StyledButton<'l> {
|
impl<'l> Default for StyledButton<'l> {
|
||||||
#[inline(always)]
|
fn default() -> Self {
|
||||||
pub fn build() -> StyledButtonBuilder<'l> {
|
Self {
|
||||||
StyledButtonBuilder::default()
|
variant: ButtonVariant::Primary,
|
||||||
|
disabled: false,
|
||||||
|
active: false,
|
||||||
|
text: None,
|
||||||
|
icon: None,
|
||||||
|
on_click: None,
|
||||||
|
children: vec![],
|
||||||
|
class_list: "",
|
||||||
|
button_type: "",
|
||||||
|
button_id: None,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -206,20 +97,22 @@ pub fn render(values: StyledButton) -> Node<Msg> {
|
|||||||
icon,
|
icon,
|
||||||
on_click,
|
on_click,
|
||||||
children,
|
children,
|
||||||
mut class_list,
|
class_list,
|
||||||
button_type,
|
button_type,
|
||||||
button_id,
|
button_id,
|
||||||
} = values;
|
} = values;
|
||||||
class_list.push(variant.to_str());
|
let class_list = format!(
|
||||||
if children.is_empty() && text.is_none() {
|
"{} {} {} {} {}",
|
||||||
class_list.push("iconOnly");
|
class_list,
|
||||||
}
|
variant,
|
||||||
if active {
|
if children.is_empty() && text.is_none() {
|
||||||
class_list.push("isActive");
|
"iconOnly"
|
||||||
}
|
} else {
|
||||||
if icon.is_some() {
|
""
|
||||||
class_list.push("withIcon");
|
},
|
||||||
}
|
if active { "isActive" } else { "" },
|
||||||
|
if icon.is_some() { "withIcon" } else { "" }
|
||||||
|
);
|
||||||
let handler = match on_click {
|
let handler = match on_click {
|
||||||
Some(h) if !disabled => vec![h],
|
Some(h) if !disabled => vec![h],
|
||||||
_ => vec![],
|
_ => vec![],
|
||||||
@ -232,25 +125,13 @@ pub fn render(values: StyledButton) -> Node<Msg> {
|
|||||||
span![C!["text"], text.unwrap_or_default(), children]
|
span![C!["text"], text.unwrap_or_default(), children]
|
||||||
};
|
};
|
||||||
|
|
||||||
let class_list: Attrs = {
|
|
||||||
let class_list: String = class_list.join(" ");
|
|
||||||
C![class_list.as_str()]
|
|
||||||
};
|
|
||||||
let button_id = button_id.map(|id| id.to_str()).unwrap_or_default();
|
let button_id = button_id.map(|id| id.to_str()).unwrap_or_default();
|
||||||
|
|
||||||
seed::button![
|
seed::button![
|
||||||
C!["styledButton"],
|
C!["styledButton", class_list],
|
||||||
class_list,
|
attrs![At::Id => button_id, At::Type => button_type],
|
||||||
attrs![
|
IF![disabled => attrs![At::Disabled => true]],
|
||||||
At::Id => button_id,
|
|
||||||
At::Type => button_type,
|
|
||||||
],
|
|
||||||
handler,
|
handler,
|
||||||
if disabled {
|
|
||||||
vec![attrs![At::Disabled => true]]
|
|
||||||
} else {
|
|
||||||
vec![]
|
|
||||||
},
|
|
||||||
icon_node,
|
icon_node,
|
||||||
content,
|
content,
|
||||||
]
|
]
|
||||||
|
@ -7,6 +7,8 @@ use {
|
|||||||
seed::{prelude::*, EventHandler, *},
|
seed::{prelude::*, EventHandler, *},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::components::styled_button::ButtonVariant;
|
||||||
|
|
||||||
const TITLE: &str = "Warning";
|
const TITLE: &str = "Warning";
|
||||||
const MESSAGE: &str = "Are you sure you want to continue with this action?";
|
const MESSAGE: &str = "Are you sure you want to continue with this action?";
|
||||||
const CONFIRM_TEXT: &str = "Confirm";
|
const CONFIRM_TEXT: &str = "Confirm";
|
||||||
@ -14,11 +16,23 @@ const CANCEL_TEXT: &str = "Cancel";
|
|||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct StyledConfirmModal<'l> {
|
pub struct StyledConfirmModal<'l> {
|
||||||
title: &'l str,
|
pub title: &'l str,
|
||||||
message: &'l str,
|
pub message: &'l str,
|
||||||
confirm_text: &'l str,
|
pub confirm_text: &'l str,
|
||||||
cancel_text: &'l str,
|
pub cancel_text: &'l str,
|
||||||
on_confirm: Option<EventHandler<Msg>>,
|
pub on_confirm: Option<EventHandler<Msg>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'l> Default for StyledConfirmModal<'l> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
title: TITLE,
|
||||||
|
message: MESSAGE,
|
||||||
|
confirm_text: CONFIRM_TEXT,
|
||||||
|
cancel_text: CANCEL_TEXT,
|
||||||
|
on_confirm: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'l> StyledConfirmModal<'l> {
|
impl<'l> StyledConfirmModal<'l> {
|
||||||
@ -70,10 +84,10 @@ impl<'l> StyledConfirmModalBuilder<'l> {
|
|||||||
|
|
||||||
pub fn build(self) -> StyledConfirmModal<'l> {
|
pub fn build(self) -> StyledConfirmModal<'l> {
|
||||||
StyledConfirmModal {
|
StyledConfirmModal {
|
||||||
title: self.title.unwrap_or_else(|| TITLE),
|
title: self.title.unwrap_or(TITLE),
|
||||||
message: self.message.unwrap_or_else(|| MESSAGE),
|
message: self.message.unwrap_or(MESSAGE),
|
||||||
confirm_text: self.confirm_text.unwrap_or_else(|| CONFIRM_TEXT),
|
confirm_text: self.confirm_text.unwrap_or(CONFIRM_TEXT),
|
||||||
cancel_text: self.cancel_text.unwrap_or_else(|| CANCEL_TEXT),
|
cancel_text: self.cancel_text.unwrap_or(CANCEL_TEXT),
|
||||||
on_confirm: self.on_confirm,
|
on_confirm: self.on_confirm,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -88,32 +102,47 @@ pub fn render(values: StyledConfirmModal) -> Node<Msg> {
|
|||||||
on_confirm,
|
on_confirm,
|
||||||
} = values;
|
} = values;
|
||||||
|
|
||||||
|
let title = if title.is_empty() { TITLE } else { title };
|
||||||
|
let message = if message.is_empty() { MESSAGE } else { message };
|
||||||
|
let confirm_text = if confirm_text.is_empty() {
|
||||||
|
CONFIRM_TEXT
|
||||||
|
} else {
|
||||||
|
confirm_text
|
||||||
|
};
|
||||||
|
let cancel_text = if cancel_text.is_empty() {
|
||||||
|
CANCEL_TEXT
|
||||||
|
} else {
|
||||||
|
cancel_text
|
||||||
|
};
|
||||||
|
|
||||||
let message_node = match message {
|
let message_node = match message {
|
||||||
_ if message.is_empty() => empty![],
|
_ if message.is_empty() => empty![],
|
||||||
_ => p![attrs![At::Class => "message"], message],
|
_ => p![attrs![At::Class => "message"], message],
|
||||||
};
|
};
|
||||||
|
|
||||||
let confirm_button = match on_confirm {
|
let confirm_button = StyledButton {
|
||||||
Some(handler) => StyledButton::build()
|
text: Some(confirm_text),
|
||||||
.text(confirm_text)
|
on_click: on_confirm,
|
||||||
.on_click(handler)
|
..Default::default()
|
||||||
.build()
|
}
|
||||||
.into_node(),
|
.into_node();
|
||||||
_ => StyledButton::build().text(confirm_text).build().into_node(),
|
let cancel_button = StyledButton {
|
||||||
};
|
text: Some(cancel_text),
|
||||||
let cancel_button = StyledButton::build()
|
variant: ButtonVariant::Secondary,
|
||||||
.text(cancel_text)
|
on_click: Some(mouse_ev(Ev::Click, |_| Msg::ModalDropped)),
|
||||||
.secondary()
|
..Default::default()
|
||||||
.on_click(mouse_ev(Ev::Click, |_| Msg::ModalDropped))
|
}
|
||||||
.build()
|
.into_node();
|
||||||
.into_node();
|
|
||||||
|
|
||||||
StyledModal::build()
|
StyledModal {
|
||||||
.width(600)
|
width: Some(600),
|
||||||
.child(div![C!["title"], title])
|
children: vec![
|
||||||
.child(message_node)
|
div![C!["title"], title],
|
||||||
.child(div![C!["actions"], confirm_button, cancel_button])
|
message_node,
|
||||||
.add_class("confirmModal")
|
div![C!["actions"], confirm_button, cancel_button],
|
||||||
.build()
|
],
|
||||||
.into_node()
|
class_list: "confirmModal",
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
.into_node()
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,9 @@ use {
|
|||||||
std::ops::RangeInclusive,
|
std::ops::RangeInclusive,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::components::styled_button::ButtonVariant;
|
||||||
|
use crate::components::styled_tooltip::TooltipVariant;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum StyledDateTimeChanged {
|
pub enum StyledDateTimeChanged {
|
||||||
MonthChanged(Option<NaiveDateTime>),
|
MonthChanged(Option<NaiveDateTime>),
|
||||||
@ -175,18 +178,19 @@ fn render(values: StyledDateTimeInput) -> Node<Msg> {
|
|||||||
|
|
||||||
let date = last_day_of_prev_month
|
let date = last_day_of_prev_month
|
||||||
.with_day0(timestamp.day0())
|
.with_day0(timestamp.day0())
|
||||||
.unwrap_or_else(|| last_day_of_prev_month);
|
.unwrap_or(last_day_of_prev_month);
|
||||||
Msg::StyledDateTimeInputChanged(
|
Msg::StyledDateTimeInputChanged(
|
||||||
field_id,
|
field_id,
|
||||||
StyledDateTimeChanged::MonthChanged(Some(date)),
|
StyledDateTimeChanged::MonthChanged(Some(date)),
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
StyledButton::build()
|
StyledButton {
|
||||||
.on_click(on_click_left)
|
on_click: Some(on_click_left),
|
||||||
.icon(Icon::DoubleLeft)
|
icon: Some(Icon::DoubleLeft.into_node()),
|
||||||
.empty()
|
variant: ButtonVariant::Empty,
|
||||||
.build()
|
..Default::default()
|
||||||
.into_node()
|
}
|
||||||
|
.into_node()
|
||||||
};
|
};
|
||||||
let right_action = {
|
let right_action = {
|
||||||
let field_id = values.field_id.clone();
|
let field_id = values.field_id.clone();
|
||||||
@ -201,42 +205,46 @@ fn render(values: StyledDateTimeInput) -> Node<Msg> {
|
|||||||
- Duration::days(1);
|
- Duration::days(1);
|
||||||
let date = first_day_of_next_month
|
let date = first_day_of_next_month
|
||||||
.with_day0(timestamp.day0())
|
.with_day0(timestamp.day0())
|
||||||
.unwrap_or_else(|| last_day_of_next_month);
|
.unwrap_or(last_day_of_next_month);
|
||||||
Msg::StyledDateTimeInputChanged(
|
Msg::StyledDateTimeInputChanged(
|
||||||
field_id,
|
field_id,
|
||||||
StyledDateTimeChanged::MonthChanged(Some(date)),
|
StyledDateTimeChanged::MonthChanged(Some(date)),
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
StyledButton::build()
|
StyledButton {
|
||||||
.on_click(on_click_right)
|
on_click: Some(on_click_right),
|
||||||
.icon(Icon::DoubleRight)
|
icon: Some(Icon::DoubleRight.into_node()),
|
||||||
.empty()
|
variant: ButtonVariant::Empty,
|
||||||
.build()
|
..Default::default()
|
||||||
.into_node()
|
}
|
||||||
|
.into_node()
|
||||||
};
|
};
|
||||||
|
|
||||||
let header_text = current.format("%B %Y").to_string();
|
let header_text = current.format("%B %Y").to_string();
|
||||||
|
|
||||||
let tooltip = StyledTooltip::build()
|
let tooltip = StyledTooltip {
|
||||||
.visible(values.popup_visible)
|
visible: values.popup_visible,
|
||||||
.date_time_picker()
|
class_list: "",
|
||||||
.add_child(h2![left_action, span![header_text], right_action])
|
children: vec![
|
||||||
.add_child(div![
|
h2![left_action, span![header_text], right_action],
|
||||||
C!["calendar"],
|
|
||||||
div![
|
div![
|
||||||
C!["weekHeader week"],
|
C!["calendar"],
|
||||||
div![C!["day"], format!("{}", Weekday::Mon).as_str()],
|
div![
|
||||||
div![C!["day"], format!("{}", Weekday::Tue).as_str()],
|
C!["weekHeader week"],
|
||||||
div![C!["day"], format!("{}", Weekday::Wed).as_str()],
|
div![C!["day"], format!("{}", Weekday::Mon).as_str()],
|
||||||
div![C!["day"], format!("{}", Weekday::Thu).as_str()],
|
div![C!["day"], format!("{}", Weekday::Tue).as_str()],
|
||||||
div![C!["day"], format!("{}", Weekday::Fri).as_str()],
|
div![C!["day"], format!("{}", Weekday::Wed).as_str()],
|
||||||
div![C!["day"], format!("{}", Weekday::Sat).as_str()],
|
div![C!["day"], format!("{}", Weekday::Thu).as_str()],
|
||||||
div![C!["day"], format!("{}", Weekday::Sun).as_str()],
|
div![C!["day"], format!("{}", Weekday::Fri).as_str()],
|
||||||
|
div![C!["day"], format!("{}", Weekday::Sat).as_str()],
|
||||||
|
div![C!["day"], format!("{}", Weekday::Sun).as_str()],
|
||||||
|
],
|
||||||
|
weeks
|
||||||
],
|
],
|
||||||
weeks
|
],
|
||||||
])
|
variant: TooltipVariant::DateTimeBuilder,
|
||||||
.build()
|
}
|
||||||
.into_node();
|
.into_node();
|
||||||
|
|
||||||
let input = {
|
let input = {
|
||||||
let field_id = values.field_id.clone();
|
let field_id = values.field_id.clone();
|
||||||
@ -255,12 +263,13 @@ fn render(values: StyledDateTimeInput) -> Node<Msg> {
|
|||||||
.date()
|
.date()
|
||||||
.format("%d/%m/%Y")
|
.format("%d/%m/%Y")
|
||||||
.to_string();
|
.to_string();
|
||||||
StyledButton::build()
|
StyledButton {
|
||||||
.on_click(on_focus)
|
on_click: Some(on_focus),
|
||||||
.text(text.as_str())
|
text: Some(text.as_str()),
|
||||||
.empty()
|
variant: ButtonVariant::Empty,
|
||||||
.build()
|
..Default::default()
|
||||||
.into_node()
|
}
|
||||||
|
.into_node()
|
||||||
};
|
};
|
||||||
|
|
||||||
div![
|
div![
|
||||||
|
@ -27,93 +27,29 @@ impl StyledEditorState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct StyledEditor {
|
pub struct StyledEditor<'l> {
|
||||||
id: FieldId,
|
pub id: Option<FieldId>,
|
||||||
initial_text: String,
|
pub initial_text: &'l str,
|
||||||
text: String,
|
pub text: &'l str,
|
||||||
html: String,
|
pub html: &'l str,
|
||||||
mode: Mode,
|
pub mode: Mode,
|
||||||
update_event: Ev,
|
pub update_event: Ev,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StyledEditor {
|
impl<'l> Default for StyledEditor<'l> {
|
||||||
#[inline]
|
fn default() -> Self {
|
||||||
pub fn build(id: FieldId) -> StyledEditorBuilder {
|
Self {
|
||||||
StyledEditorBuilder {
|
id: None,
|
||||||
id,
|
initial_text: "",
|
||||||
initial_text: "".to_string(),
|
text: "",
|
||||||
text: "".to_string(),
|
html: "",
|
||||||
html: "".to_string(),
|
mode: Mode::Editor,
|
||||||
mode: Mode::View,
|
update_event: Ev::Cached,
|
||||||
update_event: None,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
impl<'l> ToNode for StyledEditor<'l> {
|
||||||
pub struct StyledEditorBuilder {
|
|
||||||
id: FieldId,
|
|
||||||
initial_text: String,
|
|
||||||
text: String,
|
|
||||||
html: String,
|
|
||||||
mode: Mode,
|
|
||||||
update_event: Option<Ev>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl StyledEditorBuilder {
|
|
||||||
#[inline]
|
|
||||||
pub fn text<S>(mut self, text: S) -> Self
|
|
||||||
where
|
|
||||||
S: Into<String>,
|
|
||||||
{
|
|
||||||
self.text = text.into();
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn initial_text<S>(mut self, text: S) -> Self
|
|
||||||
where
|
|
||||||
S: Into<String>,
|
|
||||||
{
|
|
||||||
self.initial_text = text.into();
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn html<S>(mut self, text: S) -> Self
|
|
||||||
where
|
|
||||||
S: Into<String>,
|
|
||||||
{
|
|
||||||
self.html = text.into();
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn mode(mut self, mode: Mode) -> Self {
|
|
||||||
self.mode = mode;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn build(self) -> StyledEditor {
|
|
||||||
StyledEditor {
|
|
||||||
id: self.id,
|
|
||||||
initial_text: self.initial_text,
|
|
||||||
text: self.text,
|
|
||||||
html: self.html,
|
|
||||||
mode: self.mode,
|
|
||||||
update_event: self.update_event.unwrap_or(Ev::KeyUp),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn update_on(mut self, ev: Ev) -> Self {
|
|
||||||
self.update_event = Some(ev);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ToNode for StyledEditor {
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn into_node(self) -> Node<Msg> {
|
fn into_node(self) -> Node<Msg> {
|
||||||
render(self)
|
render(self)
|
||||||
@ -131,6 +67,7 @@ pub fn render(values: StyledEditor) -> Node<Msg> {
|
|||||||
update_event,
|
update_event,
|
||||||
} = values;
|
} = values;
|
||||||
|
|
||||||
|
let id = id.expect("Styled Editor requires ID");
|
||||||
let on_editor_clicked = click_handler(id.clone(), Mode::Editor);
|
let on_editor_clicked = click_handler(id.clone(), Mode::Editor);
|
||||||
let on_view_clicked = click_handler(id.clone(), Mode::View);
|
let on_view_clicked = click_handler(id.clone(), Mode::View);
|
||||||
|
|
||||||
@ -138,40 +75,31 @@ pub fn render(values: StyledEditor) -> Node<Msg> {
|
|||||||
let view_id = format!("view-{}", id);
|
let view_id = format!("view-{}", id);
|
||||||
let name = format!("styled-editor-{}", id);
|
let name = format!("styled-editor-{}", id);
|
||||||
|
|
||||||
let text_area = StyledTextarea::build(id)
|
let text_area = StyledTextarea {
|
||||||
.height(40)
|
id: Some(id),
|
||||||
.update_on(update_event)
|
height: 40,
|
||||||
// .disable_auto_resize()
|
max_height: 0,
|
||||||
.value(initial_text.as_str())
|
value: initial_text,
|
||||||
.build()
|
class_list: "",
|
||||||
.into_node();
|
update_event,
|
||||||
|
placeholder: "",
|
||||||
|
disable_auto_resize: false,
|
||||||
|
}
|
||||||
|
.into_node();
|
||||||
|
|
||||||
let (editor_radio_node, view_radio_node, parsed_node) = match mode {
|
let (editor_radio_node, view_radio_node) = (
|
||||||
Mode::Editor => (
|
seed::input![
|
||||||
seed::input![
|
id![editor_id.as_str()],
|
||||||
id![editor_id.as_str()],
|
C!["editorRadio"],
|
||||||
attrs![At::Type => "radio"; At::Name => name.as_str(); At::Class => "editorRadio"; At::Checked => true],
|
attrs![At::Type => "radio"; At::Name => name.as_str(); At::Checked => true],
|
||||||
],
|
],
|
||||||
seed::input![
|
seed::input![
|
||||||
id![view_id.as_str()],
|
id![view_id.as_str()],
|
||||||
attrs![ At::Type => "radio"; At::Name => name.as_str(); At::Class => "viewRadio";],
|
C!["viewRadio"],
|
||||||
],
|
attrs![ At::Type => "radio"; At::Name => name.as_str();],
|
||||||
vec![],
|
IF![mode == Mode::View => attrs![At::Checked => true]]
|
||||||
),
|
],
|
||||||
Mode::View => (
|
);
|
||||||
seed::input![
|
|
||||||
id![editor_id.as_str()],
|
|
||||||
C!["editorRadio"],
|
|
||||||
attrs![At::Type => "radio"; At::Name => name.as_str();],
|
|
||||||
],
|
|
||||||
seed::input![
|
|
||||||
id![view_id.as_str()],
|
|
||||||
C!["viewRadio"],
|
|
||||||
attrs![ At::Type => "radio"; At::Name => name.as_str(); At::Checked => true],
|
|
||||||
],
|
|
||||||
Node::from_html(None, html.as_str()),
|
|
||||||
),
|
|
||||||
};
|
|
||||||
|
|
||||||
div![
|
div![
|
||||||
C!["styledEditor"],
|
C!["styledEditor"],
|
||||||
@ -198,7 +126,11 @@ pub fn render(values: StyledEditor) -> Node<Msg> {
|
|||||||
editor_radio_node,
|
editor_radio_node,
|
||||||
text_area,
|
text_area,
|
||||||
view_radio_node,
|
view_radio_node,
|
||||||
div![C!["view"], parsed_node],
|
div![
|
||||||
|
C!["view"],
|
||||||
|
IF![mode == Mode::Editor => empty![]],
|
||||||
|
IF![mode == Mode::View => raw![html]],
|
||||||
|
],
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,10 +5,21 @@ use {
|
|||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct StyledField<'l> {
|
pub struct StyledField<'l> {
|
||||||
label: &'l str,
|
pub label: &'l str,
|
||||||
tip: Option<&'l str>,
|
pub tip: Option<&'l str>,
|
||||||
input: Node<Msg>,
|
pub input: Node<Msg>,
|
||||||
class_list: Vec<&'l str>,
|
pub class_list: &'l str,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'l> Default for StyledField<'l> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
label: "",
|
||||||
|
tip: None,
|
||||||
|
input: Node::Empty,
|
||||||
|
class_list: "",
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'l> StyledField<'l> {
|
impl<'l> StyledField<'l> {
|
||||||
@ -28,7 +39,7 @@ pub struct StyledFieldBuilder<'l> {
|
|||||||
label: Option<&'l str>,
|
label: Option<&'l str>,
|
||||||
tip: Option<&'l str>,
|
tip: Option<&'l str>,
|
||||||
input: Option<Node<Msg>>,
|
input: Option<Node<Msg>>,
|
||||||
class_list: Vec<&'l str>,
|
class_list: &'l str,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'l> StyledFieldBuilder<'l> {
|
impl<'l> StyledFieldBuilder<'l> {
|
||||||
@ -47,8 +58,8 @@ impl<'l> StyledFieldBuilder<'l> {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_class(mut self, name: &'l str) -> Self {
|
pub fn class_list(mut self, name: &'l str) -> Self {
|
||||||
self.class_list.push(name);
|
self.class_list = name;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,8 +83,8 @@ pub fn render(values: StyledField) -> Node<Msg> {
|
|||||||
let tip_node = tip.map(|s| div![C!["styledTip"], s]).unwrap_or(empty![]);
|
let tip_node = tip.map(|s| div![C!["styledTip"], s]).unwrap_or(empty![]);
|
||||||
|
|
||||||
div![
|
div![
|
||||||
attrs![At::Class => class_list.join(" "), At::Class => "styledField"],
|
C!["styledField", class_list],
|
||||||
seed::label![attrs![At::Class => "styledLabel"], label],
|
seed::label![C!["styledLabel"], label],
|
||||||
input,
|
input,
|
||||||
tip_node,
|
tip_node,
|
||||||
]
|
]
|
||||||
|
@ -5,9 +5,9 @@ use {
|
|||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct StyledForm<'l> {
|
pub struct StyledForm<'l> {
|
||||||
heading: &'l str,
|
pub heading: &'l str,
|
||||||
fields: Vec<Node<Msg>>,
|
pub fields: Vec<Node<Msg>>,
|
||||||
on_submit: Option<EventHandler<Msg>>,
|
pub on_submit: Option<EventHandler<Msg>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'l> StyledForm<'l> {
|
impl<'l> StyledForm<'l> {
|
||||||
|
@ -107,10 +107,6 @@ impl Icon {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn into_styled_builder<'l>(self) -> StyledIconBuilder<'l> {
|
|
||||||
StyledIcon::build(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn to_str<'l>(&self) -> &'l str {
|
pub fn to_str<'l>(&self) -> &'l str {
|
||||||
match self {
|
match self {
|
||||||
Icon::Bug => "bug",
|
Icon::Bug => "bug",
|
||||||
@ -233,9 +229,12 @@ impl From<IssuePriority> for Icon {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'l> Into<StyledIcon<'l>> for Icon {
|
impl<'l> From<Icon> for StyledIcon<'l> {
|
||||||
fn into(self) -> StyledIcon<'l> {
|
fn from(icon: Icon) -> StyledIcon<'l> {
|
||||||
StyledIcon::build(self).build()
|
StyledIcon {
|
||||||
|
icon,
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -247,12 +246,12 @@ impl ToNode for Icon {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct StyledIconBuilder<'l> {
|
pub struct StyledIconBuilder<'l> {
|
||||||
icon: Icon,
|
pub icon: Icon,
|
||||||
size: Option<i32>,
|
pub size: Option<i32>,
|
||||||
class_list: Vec<Cow<'l, str>>,
|
pub class_list: &'l str,
|
||||||
style_list: Vec<Cow<'l, str>>,
|
pub style_list: Vec<Cow<'l, str>>,
|
||||||
color: Option<Cow<'l, str>>,
|
pub color: Option<Cow<'l, str>>,
|
||||||
on_click: Option<EventHandler<Msg>>,
|
pub on_click: Option<EventHandler<Msg>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'l> StyledIconBuilder<'l> {
|
impl<'l> StyledIconBuilder<'l> {
|
||||||
@ -261,8 +260,8 @@ impl<'l> StyledIconBuilder<'l> {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_class(mut self, name: &'l str) -> Self {
|
pub fn class_list(mut self, name: &'l str) -> Self {
|
||||||
self.class_list.push(Cow::Borrowed(name));
|
self.class_list = name;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -275,34 +274,23 @@ impl<'l> StyledIconBuilder<'l> {
|
|||||||
self.on_click = Some(on_click);
|
self.on_click = Some(on_click);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn build(self) -> StyledIcon<'l> {
|
|
||||||
StyledIcon {
|
|
||||||
icon: self.icon,
|
|
||||||
size: self.size,
|
|
||||||
color: self.color,
|
|
||||||
class_list: self.class_list,
|
|
||||||
style_list: self.style_list,
|
|
||||||
on_click: self.on_click,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct StyledIcon<'l> {
|
pub struct StyledIcon<'l> {
|
||||||
icon: Icon,
|
pub icon: Icon,
|
||||||
size: Option<i32>,
|
pub size: Option<i32>,
|
||||||
class_list: Vec<Cow<'l, str>>,
|
pub class_list: &'l str,
|
||||||
style_list: Vec<Cow<'l, str>>,
|
pub style_list: Vec<Cow<'l, str>>,
|
||||||
color: Option<Cow<'l, str>>,
|
pub color: Option<&'l str>,
|
||||||
on_click: Option<EventHandler<Msg>>,
|
pub on_click: Option<EventHandler<Msg>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'l> StyledIcon<'l> {
|
impl<'l> Default for StyledIcon<'l> {
|
||||||
pub fn build(icon: Icon) -> StyledIconBuilder<'l> {
|
fn default() -> Self {
|
||||||
StyledIconBuilder {
|
Self {
|
||||||
icon,
|
icon: Icon::Stopwatch,
|
||||||
size: None,
|
size: None,
|
||||||
class_list: vec![],
|
class_list: "",
|
||||||
style_list: vec![],
|
style_list: vec![],
|
||||||
color: None,
|
color: None,
|
||||||
on_click: None,
|
on_click: None,
|
||||||
@ -335,25 +323,12 @@ pub fn render(values: StyledIcon) -> Node<Msg> {
|
|||||||
let color = format!("color: {}", s);
|
let color = format!("color: {}", s);
|
||||||
attrs![At::Style => color]
|
attrs![At::Style => color]
|
||||||
}),
|
}),
|
||||||
color.map(|s| {
|
color.map(|s| attrs![At::Style => format!("color: var(--{})", s)]),
|
||||||
let s = match s {
|
|
||||||
Cow::Owned(s) => format!("color: var(--{})", s.as_str()),
|
|
||||||
Cow::Borrowed(s) => format!("color: var(--{})", s),
|
|
||||||
};
|
|
||||||
attrs![At::Style => s]
|
|
||||||
}),
|
|
||||||
]
|
]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter_map(|o| o)
|
.flatten()
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let class_list: Vec<seed::Attrs> = class_list
|
|
||||||
.into_iter()
|
|
||||||
.map(|s| match s {
|
|
||||||
Cow::Borrowed(s) => C![s],
|
|
||||||
Cow::Owned(s) => C![s.as_str()],
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
let style_list = style_list.into_iter().fold("".to_string(), |mut mem, s| {
|
let style_list = style_list.into_iter().fold("".to_string(), |mut mem, s| {
|
||||||
match s {
|
match s {
|
||||||
Cow::Borrowed(s) => {
|
Cow::Borrowed(s) => {
|
||||||
@ -368,9 +343,7 @@ pub fn render(values: StyledIcon) -> Node<Msg> {
|
|||||||
});
|
});
|
||||||
|
|
||||||
i![
|
i![
|
||||||
C!["styledIcon"],
|
C!["styledIcon", class_list, icon.to_str()],
|
||||||
class_list,
|
|
||||||
C![icon.to_str()],
|
|
||||||
styles,
|
styles,
|
||||||
attrs![ At::Style => style_list ],
|
attrs![ At::Style => style_list ],
|
||||||
on_click,
|
on_click,
|
||||||
|
@ -31,19 +31,9 @@ impl StyledImageInputState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct StyledImageInput<'l> {
|
pub struct StyledImageInput<'l> {
|
||||||
id: FieldId,
|
pub id: FieldId,
|
||||||
class_list: Vec<&'l str>,
|
pub class_list: &'l str,
|
||||||
url: Option<String>,
|
pub url: Option<&'l str>,
|
||||||
}
|
|
||||||
|
|
||||||
impl<'l> StyledImageInput<'l> {
|
|
||||||
pub fn build(field_id: FieldId) -> StyledInputInputBuilder<'l> {
|
|
||||||
StyledInputInputBuilder {
|
|
||||||
id: field_id,
|
|
||||||
class_list: vec![],
|
|
||||||
url: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'l> ToNode for StyledImageInput<'l> {
|
impl<'l> ToNode for StyledImageInput<'l> {
|
||||||
@ -52,32 +42,6 @@ impl<'l> ToNode for StyledImageInput<'l> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct StyledInputInputBuilder<'l> {
|
|
||||||
id: FieldId,
|
|
||||||
class_list: Vec<&'l str>,
|
|
||||||
url: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'l> StyledInputInputBuilder<'l> {
|
|
||||||
pub fn add_class(mut self, name: &'l str) -> Self {
|
|
||||||
self.class_list.push(name);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn state(mut self, state: &StyledImageInputState) -> Self {
|
|
||||||
self.url = state.url.as_ref().cloned();
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn build(self) -> StyledImageInput<'l> {
|
|
||||||
StyledImageInput {
|
|
||||||
id: self.id,
|
|
||||||
class_list: self.class_list,
|
|
||||||
url: self.url,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render(values: StyledImageInput) -> Node<Msg> {
|
fn render(values: StyledImageInput) -> Node<Msg> {
|
||||||
let StyledImageInput {
|
let StyledImageInput {
|
||||||
id,
|
id,
|
||||||
@ -104,8 +68,7 @@ fn render(values: StyledImageInput) -> Node<Msg> {
|
|||||||
let input_id = id.to_string();
|
let input_id = id.to_string();
|
||||||
|
|
||||||
div![
|
div![
|
||||||
C!["styledImageInput"],
|
C!["styledImageInput", class_list],
|
||||||
attrs![At::Class => class_list.join(" ")],
|
|
||||||
label![
|
label![
|
||||||
C!["label"],
|
C!["label"],
|
||||||
attrs![At::For => input_id],
|
attrs![At::For => input_id],
|
||||||
|
@ -8,22 +8,22 @@ use {
|
|||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialOrd, PartialEq)]
|
#[derive(Clone, Debug, PartialOrd, PartialEq)]
|
||||||
pub enum Variant {
|
pub enum InputVariant {
|
||||||
Normal,
|
Normal,
|
||||||
Primary,
|
Primary,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Variant {
|
impl InputVariant {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn to_str<'l>(&self) -> &'l str {
|
pub fn to_str<'l>(&self) -> &'l str {
|
||||||
match self {
|
match self {
|
||||||
Variant::Normal => "normal",
|
InputVariant::Normal => "normal",
|
||||||
Variant::Primary => "primary",
|
InputVariant::Primary => "primary",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToString for Variant {
|
impl ToString for InputVariant {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn to_string(&self) -> String {
|
fn to_string(&self) -> String {
|
||||||
self.to_str().to_string()
|
self.to_str().to_string()
|
||||||
@ -96,150 +96,70 @@ impl StyledInputState {
|
|||||||
pub fn reset(&mut self) {
|
pub fn reset(&mut self) {
|
||||||
self.value.clear();
|
self.value.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_valid(&self) -> bool {
|
||||||
|
match (
|
||||||
|
self.touched,
|
||||||
|
self.value.as_str(),
|
||||||
|
self.min.as_ref(),
|
||||||
|
self.max.as_ref(),
|
||||||
|
) {
|
||||||
|
(false, ..) => true,
|
||||||
|
(_, s, None, None) => !s.is_empty(),
|
||||||
|
(_, s, Some(min), None) => s.len() >= *min,
|
||||||
|
(_, s, None, Some(max)) => s.len() <= *max,
|
||||||
|
(_, s, Some(min), Some(max)) => s.len() >= *min && s.len() <= *max,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct StyledInput<'l, 'm: 'l> {
|
pub struct StyledInput<'l, 'm: 'l> {
|
||||||
id: FieldId,
|
pub id: Option<FieldId>,
|
||||||
icon: Option<Icon>,
|
pub icon: Option<Icon>,
|
||||||
valid: bool,
|
pub valid: bool,
|
||||||
value: Option<&'m str>,
|
pub value: &'m str,
|
||||||
input_type: Option<&'l str>,
|
pub input_type: Option<&'l str>,
|
||||||
input_class_list: Vec<&'l str>,
|
pub input_class_list: &'l str,
|
||||||
wrapper_class_list: Vec<&'l str>,
|
pub wrapper_class_list: &'l str,
|
||||||
variant: Variant,
|
pub variant: InputVariant,
|
||||||
auto_focus: bool,
|
pub auto_focus: bool,
|
||||||
input_handlers: Vec<EventHandler<Msg>>,
|
pub input_handlers: Vec<EventHandler<Msg>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'l, 'm: 'l> Default for StyledInput<'l, 'm> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
id: None,
|
||||||
|
icon: None,
|
||||||
|
valid: false,
|
||||||
|
value: "",
|
||||||
|
input_type: None,
|
||||||
|
input_class_list: "",
|
||||||
|
wrapper_class_list: "",
|
||||||
|
variant: InputVariant::Normal,
|
||||||
|
auto_focus: false,
|
||||||
|
input_handlers: vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'l, 'm: 'l> StyledInput<'l, 'm> {
|
impl<'l, 'm: 'l> StyledInput<'l, 'm> {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn new_with_id_and_value_and_valid(id: FieldId, value: &'m str, valid: bool) -> Self {
|
pub fn new_with_id_and_value_and_valid(id: FieldId, value: &'m str, valid: bool) -> Self {
|
||||||
Self {
|
Self {
|
||||||
id,
|
id: Some(id),
|
||||||
icon: None,
|
icon: None,
|
||||||
valid,
|
valid,
|
||||||
value: Some(value),
|
value,
|
||||||
input_type: None,
|
input_type: None,
|
||||||
input_class_list: vec![],
|
input_class_list: "",
|
||||||
wrapper_class_list: vec![],
|
wrapper_class_list: "",
|
||||||
variant: Variant::Normal,
|
variant: InputVariant::Normal,
|
||||||
auto_focus: false,
|
auto_focus: false,
|
||||||
input_handlers: vec![],
|
input_handlers: vec![],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn build() -> StyledInputBuilder<'l, 'm> {
|
|
||||||
StyledInputBuilder {
|
|
||||||
icon: None,
|
|
||||||
valid: None,
|
|
||||||
value: None,
|
|
||||||
input_type: None,
|
|
||||||
input_class_list: vec![],
|
|
||||||
wrapper_class_list: vec![],
|
|
||||||
variant: Variant::Normal,
|
|
||||||
auto_focus: false,
|
|
||||||
input_handlers: vec![],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct StyledInputBuilder<'l, 'm: 'l> {
|
|
||||||
icon: Option<Icon>,
|
|
||||||
valid: Option<bool>,
|
|
||||||
value: Option<&'m str>,
|
|
||||||
input_type: Option<&'l str>,
|
|
||||||
input_class_list: Vec<&'l str>,
|
|
||||||
wrapper_class_list: Vec<&'l str>,
|
|
||||||
variant: Variant,
|
|
||||||
auto_focus: bool,
|
|
||||||
input_handlers: Vec<EventHandler<Msg>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'l, 'm: 'l> StyledInputBuilder<'l, 'm> {
|
|
||||||
#[inline]
|
|
||||||
pub fn icon(mut self, icon: Icon) -> Self {
|
|
||||||
self.icon = Some(icon);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn valid(mut self, valid: bool) -> Self {
|
|
||||||
self.valid = Some(valid);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn value(mut self, v: &'m str) -> Self {
|
|
||||||
self.value = Some(v);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn state(self, state: &'m StyledInputState) -> Self {
|
|
||||||
self.value(&state.value.as_str()).valid(
|
|
||||||
match (
|
|
||||||
state.touched,
|
|
||||||
state.value.as_str(),
|
|
||||||
state.min.as_ref(),
|
|
||||||
state.max.as_ref(),
|
|
||||||
) {
|
|
||||||
(false, ..) => true,
|
|
||||||
(_, s, None, None) => !s.is_empty(),
|
|
||||||
(_, s, Some(min), None) => s.len() >= *min,
|
|
||||||
(_, s, None, Some(max)) => s.len() <= *max,
|
|
||||||
(_, s, Some(min), Some(max)) => s.len() >= *min && s.len() <= *max,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn add_input_class(mut self, name: &'l str) -> Self {
|
|
||||||
self.input_class_list.push(name);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn add_wrapper_class(mut self, name: &'l str) -> Self {
|
|
||||||
self.wrapper_class_list.push(name);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn primary(mut self) -> Self {
|
|
||||||
self.variant = Variant::Primary;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn auto_focus(mut self) -> Self {
|
|
||||||
self.auto_focus = true;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn on_input_ev(mut self, handler: EventHandler<Msg>) -> Self {
|
|
||||||
self.input_handlers.push(handler);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn build(self, id: FieldId) -> StyledInput<'l, 'm> {
|
|
||||||
StyledInput {
|
|
||||||
id,
|
|
||||||
icon: self.icon,
|
|
||||||
valid: self.valid.unwrap_or_default(),
|
|
||||||
value: self.value,
|
|
||||||
input_type: self.input_type,
|
|
||||||
input_class_list: self.input_class_list,
|
|
||||||
wrapper_class_list: self.wrapper_class_list,
|
|
||||||
variant: self.variant,
|
|
||||||
auto_focus: self.auto_focus,
|
|
||||||
input_handlers: self.input_handlers,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'l, 'm: 'l> ToNode for StyledInput<'l, 'm> {
|
impl<'l, 'm: 'l> ToNode for StyledInput<'l, 'm> {
|
||||||
@ -262,9 +182,10 @@ pub fn render(values: StyledInput) -> Node<Msg> {
|
|||||||
auto_focus,
|
auto_focus,
|
||||||
input_handlers,
|
input_handlers,
|
||||||
} = values;
|
} = values;
|
||||||
|
let id = id.expect("Input id is required");
|
||||||
|
|
||||||
let icon_node = icon
|
let icon_node = icon
|
||||||
.map(|icon| StyledIcon::build(icon).build().into_node())
|
.map(|icon| StyledIcon::from(icon).into_node())
|
||||||
.unwrap_or(Node::Empty);
|
.unwrap_or(Node::Empty);
|
||||||
|
|
||||||
let on_change = {
|
let on_change = {
|
||||||
@ -277,28 +198,27 @@ pub fn render(values: StyledInput) -> Node<Msg> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
div![
|
div![
|
||||||
C!["styledInput"],
|
C![
|
||||||
C![variant.to_str()],
|
"styledInput",
|
||||||
if !valid { Some(C!["invalid"]) } else { None },
|
format!("{}", id),
|
||||||
attrs!(
|
variant.to_str(),
|
||||||
"class" => format!("{} {}", id, wrapper_class_list.join(" ")),
|
wrapper_class_list
|
||||||
),
|
],
|
||||||
|
IF![!valid => C!["invalid"]],
|
||||||
icon_node,
|
icon_node,
|
||||||
seed::input![
|
seed::input![
|
||||||
C!["inputElement"],
|
C![
|
||||||
icon.as_ref().map(|_| C!["withIcon"]),
|
"inputElement",
|
||||||
C![variant.to_str()],
|
variant.to_str(),
|
||||||
|
input_class_list,
|
||||||
|
icon.as_ref().map(|_| "withIcon").unwrap_or_default()
|
||||||
|
],
|
||||||
attrs![
|
attrs![
|
||||||
"id" => format!("{}", id),
|
"id" => format!("{}", id),
|
||||||
At::Class => input_class_list.join(" "),
|
"value" => value,
|
||||||
"value" => value.unwrap_or_default(),
|
|
||||||
"type" => input_type.unwrap_or("text"),
|
"type" => input_type.unwrap_or("text"),
|
||||||
],
|
],
|
||||||
if auto_focus {
|
IF![auto_focus => attrs![At::AutoFocus => true]],
|
||||||
vec![attrs![At::AutoFocus => true]]
|
|
||||||
} else {
|
|
||||||
vec![]
|
|
||||||
},
|
|
||||||
on_change,
|
on_change,
|
||||||
input_handlers,
|
input_handlers,
|
||||||
],
|
],
|
||||||
|
@ -5,21 +5,21 @@ use {
|
|||||||
};
|
};
|
||||||
|
|
||||||
pub struct StyledLink<'l> {
|
pub struct StyledLink<'l> {
|
||||||
children: Vec<Node<Msg>>,
|
pub children: Vec<Node<Msg>>,
|
||||||
class_list: Vec<&'l str>,
|
pub class_list: &'l str,
|
||||||
href: &'l str,
|
pub href: &'l str,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'l> StyledLink<'l> {
|
impl<'l> StyledLink<'l> {
|
||||||
pub fn build() -> StyledLinkBuilder<'l> {
|
// pub fn build() -> StyledLinkBuilder<'l> {
|
||||||
StyledLinkBuilder::default()
|
// StyledLinkBuilder::default()
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct StyledLinkBuilder<'l> {
|
pub struct StyledLinkBuilder<'l> {
|
||||||
children: Vec<Node<Msg>>,
|
children: Vec<Node<Msg>>,
|
||||||
class_list: Vec<&'l str>,
|
class_list: &'l str,
|
||||||
href: &'l str,
|
href: &'l str,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -29,13 +29,8 @@ impl<'l> StyledLinkBuilder<'l> {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_icon(self) -> Self {
|
pub fn class_list(mut self, name: &'l str) -> Self {
|
||||||
self.add_child(crate::components::styled_icon::Icon::Link.into_node())
|
self.class_list = name;
|
||||||
.add_class("withIcon")
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add_class(mut self, name: &'l str) -> Self {
|
|
||||||
self.class_list.push(name);
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -86,11 +81,8 @@ pub fn render(values: StyledLink) -> Node<Msg> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
a![
|
a![
|
||||||
C!["styledLink"],
|
C!["styledLink", class_list],
|
||||||
attrs![
|
attrs![ At::Href => href, ],
|
||||||
At::Class => class_list.join(" "),
|
|
||||||
At::Href => href,
|
|
||||||
],
|
|
||||||
on_click,
|
on_click,
|
||||||
children,
|
children,
|
||||||
]
|
]
|
||||||
|
@ -9,44 +9,56 @@ use {
|
|||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
#[derive(Debug, Copy, Clone, PartialOrd, PartialEq)]
|
#[derive(Debug, Copy, Clone, PartialOrd, PartialEq)]
|
||||||
pub enum Variant {
|
pub enum ModalVariant {
|
||||||
Center,
|
Center,
|
||||||
Aside,
|
Aside,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Variant {
|
impl ModalVariant {
|
||||||
pub fn to_class_name(&self) -> &str {
|
pub fn to_class_name(&self) -> &str {
|
||||||
match self {
|
match self {
|
||||||
Variant::Center => "center",
|
ModalVariant::Center => "center",
|
||||||
Variant::Aside => "aside",
|
ModalVariant::Aside => "aside",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_icon_class_name(&self) -> &str {
|
pub fn to_icon_class_name(&self) -> &str {
|
||||||
match self {
|
match self {
|
||||||
Variant::Center => "modalVariantCenter",
|
ModalVariant::Center => "modalVariantCenter",
|
||||||
Variant::Aside => "modalVariantAside",
|
ModalVariant::Aside => "modalVariantAside",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct StyledModal<'l> {
|
pub struct StyledModal<'l> {
|
||||||
variant: Variant,
|
pub variant: ModalVariant,
|
||||||
width: Option<usize>,
|
pub width: Option<usize>,
|
||||||
with_icon: bool,
|
pub with_icon: bool,
|
||||||
children: Vec<Node<Msg>>,
|
pub children: Vec<Node<Msg>>,
|
||||||
class_list: Vec<&'l str>,
|
pub class_list: &'l str,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'l> Default for StyledModal<'l> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
variant: ModalVariant::Center,
|
||||||
|
width: None,
|
||||||
|
with_icon: false,
|
||||||
|
children: vec![],
|
||||||
|
class_list: "",
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'l> StyledModal<'l> {
|
impl<'l> StyledModal<'l> {
|
||||||
pub fn centered_with_width_and_body(width: usize, children: Vec<Node<Msg>>) -> Self {
|
pub fn centered_with_width_and_body(width: usize, children: Vec<Node<Msg>>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
variant: Variant::Center,
|
variant: ModalVariant::Center,
|
||||||
width: Some(width),
|
width: Some(width),
|
||||||
with_icon: false,
|
with_icon: false,
|
||||||
children,
|
children,
|
||||||
class_list: vec![],
|
class_list: "",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -57,72 +69,6 @@ impl<'l> ToNode for StyledModal<'l> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'l> StyledModal<'l> {
|
|
||||||
pub fn build() -> StyledModalBuilder<'l> {
|
|
||||||
Default::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct StyledModalBuilder<'l> {
|
|
||||||
variant: Option<Variant>,
|
|
||||||
width: Option<usize>,
|
|
||||||
with_icon: Option<bool>,
|
|
||||||
children: Option<Vec<Node<Msg>>>,
|
|
||||||
class_list: Vec<&'l str>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'l> StyledModalBuilder<'l> {
|
|
||||||
#[inline]
|
|
||||||
pub fn variant(mut self, variant: Variant) -> Self {
|
|
||||||
self.variant = Some(variant);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn center(self) -> Self {
|
|
||||||
self.variant(Variant::Center)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn width(mut self, width: usize) -> Self {
|
|
||||||
self.width = Some(width);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn child(mut self, child: Node<Msg>) -> Self {
|
|
||||||
self.children.get_or_insert(vec![]).push(child);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn children<ChildIter>(mut self, children: ChildIter) -> Self
|
|
||||||
where
|
|
||||||
ChildIter: Iterator<Item = Node<Msg>>,
|
|
||||||
{
|
|
||||||
self.children.get_or_insert(vec![]).extend(children);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn add_class(mut self, name: &'l str) -> Self {
|
|
||||||
self.class_list.push(name);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn build(self) -> StyledModal<'l> {
|
|
||||||
StyledModal {
|
|
||||||
variant: self.variant.unwrap_or(Variant::Center),
|
|
||||||
width: self.width,
|
|
||||||
with_icon: self.with_icon.unwrap_or_default(),
|
|
||||||
children: self.children.unwrap_or_default(),
|
|
||||||
class_list: self.class_list,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn render(values: StyledModal) -> Node<Msg> {
|
pub fn render(values: StyledModal) -> Node<Msg> {
|
||||||
let StyledModal {
|
let StyledModal {
|
||||||
@ -130,14 +76,16 @@ pub fn render(values: StyledModal) -> Node<Msg> {
|
|||||||
width,
|
width,
|
||||||
with_icon,
|
with_icon,
|
||||||
children,
|
children,
|
||||||
mut class_list,
|
class_list,
|
||||||
} = values;
|
} = values;
|
||||||
|
|
||||||
let icon = if with_icon {
|
let icon = if with_icon {
|
||||||
StyledIcon::build(Icon::Close)
|
StyledIcon {
|
||||||
.add_class(variant.to_icon_class_name())
|
icon: Icon::Close,
|
||||||
.build()
|
class_list: variant.to_icon_class_name(),
|
||||||
.into_node()
|
..Default::default()
|
||||||
|
}
|
||||||
|
.into_node()
|
||||||
} else {
|
} else {
|
||||||
empty![]
|
empty![]
|
||||||
};
|
};
|
||||||
@ -154,8 +102,6 @@ pub fn render(values: StyledModal) -> Node<Msg> {
|
|||||||
});
|
});
|
||||||
|
|
||||||
let clickable_class = format!("clickableOverlay {}", variant.to_class_name());
|
let clickable_class = format!("clickableOverlay {}", variant.to_class_name());
|
||||||
class_list.push("styledModal");
|
|
||||||
class_list.push(variant.to_class_name());
|
|
||||||
let styled_modal_style = match width {
|
let styled_modal_style = match width {
|
||||||
Some(0) => "".to_string(),
|
Some(0) => "".to_string(),
|
||||||
Some(n) => format!("max-width: {width}px", width = n),
|
Some(n) => format!("max-width: {width}px", width = n),
|
||||||
@ -164,10 +110,11 @@ pub fn render(values: StyledModal) -> Node<Msg> {
|
|||||||
div![
|
div![
|
||||||
C!["modal"],
|
C!["modal"],
|
||||||
div![
|
div![
|
||||||
attrs![At::Class => clickable_class],
|
C![clickable_class],
|
||||||
close_handler,
|
close_handler,
|
||||||
div![
|
div![
|
||||||
attrs![At::Class => class_list.join(" "), At::Style => styled_modal_style],
|
C![class_list, "styledModal", variant.to_class_name()],
|
||||||
|
attrs![At::Style => styled_modal_style],
|
||||||
body_handler,
|
body_handler,
|
||||||
icon,
|
icon,
|
||||||
children
|
children
|
||||||
|
@ -19,27 +19,27 @@ pub enum StyledSelectChanged {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||||
pub enum Variant {
|
pub enum SelectVariant {
|
||||||
Empty,
|
Empty,
|
||||||
Normal,
|
Normal,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Variant {
|
impl Default for SelectVariant {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Variant::Empty
|
SelectVariant::Empty
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Variant {
|
impl SelectVariant {
|
||||||
pub fn to_str<'l>(&self) -> &'l str {
|
pub fn to_str<'l>(&self) -> &'l str {
|
||||||
match self {
|
match self {
|
||||||
Variant::Empty => "empty",
|
SelectVariant::Empty => "empty",
|
||||||
Variant::Normal => "normal",
|
SelectVariant::Normal => "normal",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for Variant {
|
impl std::fmt::Display for SelectVariant {
|
||||||
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())
|
||||||
}
|
}
|
||||||
@ -114,17 +114,38 @@ pub struct StyledSelect<'l, Options>
|
|||||||
where
|
where
|
||||||
Options: Iterator<Item = StyledSelectChildBuilder<'l>>,
|
Options: Iterator<Item = StyledSelectChildBuilder<'l>>,
|
||||||
{
|
{
|
||||||
id: FieldId,
|
pub id: FieldId,
|
||||||
variant: Variant,
|
pub variant: SelectVariant,
|
||||||
dropdown_width: Option<usize>,
|
pub dropdown_width: Option<usize>,
|
||||||
name: Option<&'l str>,
|
pub name: &'l str,
|
||||||
valid: bool,
|
pub valid: bool,
|
||||||
is_multi: bool,
|
pub is_multi: bool,
|
||||||
options: Option<Options>,
|
pub options: Option<Options>,
|
||||||
selected: Vec<StyledSelectChildBuilder<'l>>,
|
pub selected: Vec<StyledSelectChildBuilder<'l>>,
|
||||||
text_filter: &'l str,
|
pub text_filter: &'l str,
|
||||||
opened: bool,
|
pub opened: bool,
|
||||||
clearable: bool,
|
pub clearable: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'l, Options> Default for StyledSelect<'l, Options>
|
||||||
|
where
|
||||||
|
Options: Iterator<Item = StyledSelectChildBuilder<'l>>,
|
||||||
|
{
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
id: FieldId::TextFilterBoard,
|
||||||
|
variant: Default::default(),
|
||||||
|
dropdown_width: None,
|
||||||
|
name: "",
|
||||||
|
valid: true,
|
||||||
|
is_multi: false,
|
||||||
|
options: None,
|
||||||
|
selected: vec![],
|
||||||
|
text_filter: "",
|
||||||
|
opened: false,
|
||||||
|
clearable: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'l, Options> ToNode for StyledSelect<'l, Options>
|
impl<'l, Options> ToNode for StyledSelect<'l, Options>
|
||||||
@ -136,124 +157,6 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'l, Options> StyledSelect<'l, Options>
|
|
||||||
where
|
|
||||||
Options: Iterator<Item = StyledSelectChildBuilder<'l>>,
|
|
||||||
{
|
|
||||||
pub fn build() -> StyledSelectBuilder<'l, Options> {
|
|
||||||
StyledSelectBuilder {
|
|
||||||
variant: None,
|
|
||||||
dropdown_width: None,
|
|
||||||
name: None,
|
|
||||||
valid: None,
|
|
||||||
is_multi: None,
|
|
||||||
options: None,
|
|
||||||
selected: None,
|
|
||||||
text_filter: None,
|
|
||||||
opened: None,
|
|
||||||
clearable: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
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<Options>,
|
|
||||||
selected: Option<Vec<StyledSelectChildBuilder<'l>>>,
|
|
||||||
text_filter: Option<&'l str>,
|
|
||||||
opened: Option<bool>,
|
|
||||||
clearable: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
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(),
|
|
||||||
dropdown_width: self.dropdown_width,
|
|
||||||
name: self.name,
|
|
||||||
valid: self.valid.unwrap_or(true),
|
|
||||||
is_multi: self.is_multi.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(),
|
|
||||||
clearable: self.clearable,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn state<'state: 'l>(self, state: &'state StyledSelectState) -> Self {
|
|
||||||
self.opened(state.opened)
|
|
||||||
.text_filter(state.text_filter.as_str())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn dropdown_width(mut self, dropdown_width: usize) -> Self {
|
|
||||||
self.dropdown_width = Some(dropdown_width);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn name(mut self, name: &'l str) -> Self {
|
|
||||||
self.name = Some(name);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn text_filter(mut self, text_filter: &'l str) -> Self {
|
|
||||||
self.text_filter = Some(text_filter);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn opened(mut self, opened: bool) -> Self {
|
|
||||||
self.opened = Some(opened);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn valid(mut self, valid: bool) -> Self {
|
|
||||||
self.valid = Some(valid);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn options(mut self, options: Options) -> Self {
|
|
||||||
self.options = Some(options);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn selected(mut self, selected: Vec<StyledSelectChildBuilder<'l>>) -> Self {
|
|
||||||
self.selected = Some(selected);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn normal(mut self) -> Self {
|
|
||||||
self.variant = Some(Variant::Normal);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn empty(mut self) -> Self {
|
|
||||||
self.variant = Some(Variant::Empty);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn multi(mut self) -> Self {
|
|
||||||
self.is_multi = Some(true);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn clearable(mut self) -> Self {
|
|
||||||
self.clearable = true;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn render<'l, Options>(values: StyledSelect<'l, Options>) -> Node<Msg>
|
pub fn render<'l, Options>(values: StyledSelect<'l, Options>) -> Node<Msg>
|
||||||
where
|
where
|
||||||
Options: Iterator<Item = StyledSelectChildBuilder<'l>>,
|
Options: Iterator<Item = StyledSelectChildBuilder<'l>>,
|
||||||
@ -286,14 +189,10 @@ where
|
|||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
let dropdown_style = dropdown_width
|
let dropdown_style = dropdown_width.map_or_else(
|
||||||
.map(|n| format!("width: {}px;", n))
|
|| "width: 100%;".to_string(),
|
||||||
.unwrap_or_else(|| "width: 100%;".to_string());
|
|n| format!("width: {}px;", n),
|
||||||
|
);
|
||||||
let mut select_class = vec!["styledSelect".to_string(), format!("{}", variant)];
|
|
||||||
if !valid {
|
|
||||||
select_class.push("invalid".to_string());
|
|
||||||
}
|
|
||||||
|
|
||||||
let action_icon = if clearable && !selected.is_empty() {
|
let action_icon = if clearable && !selected.is_empty() {
|
||||||
let on_click = {
|
let on_click = {
|
||||||
@ -304,16 +203,20 @@ where
|
|||||||
Msg::StyledSelectChanged(field_id, StyledSelectChanged::Changed(None))
|
Msg::StyledSelectChanged(field_id, StyledSelectChanged::Changed(None))
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
StyledIcon::build(Icon::Close)
|
StyledIcon {
|
||||||
.add_class("chevronIcon")
|
icon: Icon::Close,
|
||||||
.on_click(on_click)
|
class_list: "chevronIcon",
|
||||||
.build()
|
on_click: Some(on_click),
|
||||||
.into_node()
|
..Default::default()
|
||||||
} else if (selected.is_empty() || !is_multi) && variant != Variant::Empty {
|
}
|
||||||
StyledIcon::build(Icon::ChevronDown)
|
.into_node()
|
||||||
.add_class("chevronIcon")
|
} else if (selected.is_empty() || !is_multi) && variant != SelectVariant::Empty {
|
||||||
.build()
|
StyledIcon {
|
||||||
.into_node()
|
icon: Icon::ChevronDown,
|
||||||
|
class_list: "chevronIcon",
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
.into_node()
|
||||||
} else {
|
} else {
|
||||||
empty![]
|
empty![]
|
||||||
};
|
};
|
||||||
@ -335,12 +238,7 @@ where
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
div![
|
div![C!["option"], on_change, on_handler.clone(), node]
|
||||||
attrs![At::Class => "option"],
|
|
||||||
on_change,
|
|
||||||
on_handler.clone(),
|
|
||||||
node
|
|
||||||
]
|
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
} else {
|
} else {
|
||||||
@ -349,9 +247,9 @@ where
|
|||||||
|
|
||||||
let text_input = if opened {
|
let text_input = if opened {
|
||||||
seed::input![
|
seed::input![
|
||||||
|
C!["dropDownInput"],
|
||||||
attrs![
|
attrs![
|
||||||
At::Name => name.unwrap_or_default(),
|
At::Name => name,
|
||||||
At::Class => "dropDownInput",
|
|
||||||
At::Type => "text"
|
At::Type => "text"
|
||||||
At::Placeholder => "Search"
|
At::Placeholder => "Search"
|
||||||
At::AutoFocus => "true",
|
At::AutoFocus => "true",
|
||||||
@ -364,24 +262,24 @@ where
|
|||||||
|
|
||||||
let option_list = match (opened, children.is_empty()) {
|
let option_list = match (opened, children.is_empty()) {
|
||||||
(false, _) => empty![],
|
(false, _) => empty![],
|
||||||
(_, true) => seed::div![attrs![At::Class => "noOptions"], "No results"],
|
(_, true) => seed::div![C!["noOptions"], "No results"],
|
||||||
_ => seed::div![attrs![ At::Class => "options" ], children],
|
_ => seed::div![C!["options"], children],
|
||||||
};
|
};
|
||||||
|
|
||||||
let value: Vec<Node<Msg>> = if is_multi {
|
let value: Vec<Node<Msg>> = if is_multi {
|
||||||
let add_icon = StyledIcon::build(Icon::Plus).build().into_node();
|
let add_icon = StyledIcon::from(Icon::Plus).into_node();
|
||||||
let mut children: Vec<Node<Msg>> = selected
|
let mut children: Vec<Node<Msg>> = selected
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|m| into_multi_value(m, id.clone()))
|
.map(|m| into_multi_value(m, id.clone()))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
if !children.is_empty() {
|
if !children.is_empty() {
|
||||||
children.push(div![attrs![At::Class => "addMore"], add_icon, "Add more"]);
|
children.push(div![C!["addMore"], add_icon, "Add more"]);
|
||||||
} else {
|
} else {
|
||||||
children.push(div![attrs![At::Class => "placeholder"], "Select"]);
|
children.push(div![C!["placeholder"], "Select"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
vec![div![attrs![At::Class => "valueMulti"], children]]
|
vec![div![C!["valueMulti"], children]]
|
||||||
} else {
|
} else {
|
||||||
selected
|
selected
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@ -390,13 +288,14 @@ where
|
|||||||
};
|
};
|
||||||
|
|
||||||
seed::div![
|
seed::div![
|
||||||
attrs![At::Class => select_class.join(" "), At::Style => dropdown_style.as_str()],
|
C!["styledSelect", variant.to_str(), IF![!valid => "invalid"]],
|
||||||
|
attrs![At::Style => dropdown_style.as_str()],
|
||||||
keyboard_ev(Ev::KeyUp, |ev| {
|
keyboard_ev(Ev::KeyUp, |ev| {
|
||||||
ev.stop_propagation();
|
ev.stop_propagation();
|
||||||
None as Option<Msg>
|
None as Option<Msg>
|
||||||
}),
|
}),
|
||||||
div![
|
div![
|
||||||
attrs![At::Class => format!("valueContainer {}", variant)],
|
C!["valueContainer", variant.to_str()],
|
||||||
on_handler,
|
on_handler,
|
||||||
value,
|
value,
|
||||||
action_icon,
|
action_icon,
|
||||||
@ -416,21 +315,25 @@ fn render_value(mut content: Node<Msg>) -> Node<Msg> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn into_multi_value(opt: StyledSelectChildBuilder, id: FieldId) -> Node<Msg> {
|
fn into_multi_value(opt: StyledSelectChildBuilder, id: FieldId) -> Node<Msg> {
|
||||||
let close_icon = StyledIcon::build(Icon::Close).size(14).build().into_node();
|
let close_icon = StyledIcon {
|
||||||
|
icon: Icon::Close,
|
||||||
|
size: Some(14),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
.into_node();
|
||||||
let child = opt.build(DisplayType::SelectValue);
|
let child = opt.build(DisplayType::SelectValue);
|
||||||
let value = child.value();
|
let value = child.value();
|
||||||
|
|
||||||
let mut opt = child.into_node();
|
let mut opt = child.into_node();
|
||||||
opt.add_class("value");
|
opt.add_class("value").add_child(close_icon);
|
||||||
opt.add_child(close_icon);
|
|
||||||
|
|
||||||
let handler = {
|
let handler = {
|
||||||
let field_id = id.clone();
|
let field_id = id;
|
||||||
mouse_ev(Ev::Click, move |ev| {
|
mouse_ev(Ev::Click, move |ev| {
|
||||||
ev.stop_propagation();
|
ev.stop_propagation();
|
||||||
Msg::StyledSelectChanged(field_id, StyledSelectChanged::RemoveMulti(value))
|
Msg::StyledSelectChanged(field_id, StyledSelectChanged::RemoveMulti(value))
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
div![attrs![At::Class => "valueMultiItem"], opt, handler]
|
div![C!["valueMultiItem"], opt, handler]
|
||||||
}
|
}
|
||||||
|
@ -1,40 +1,45 @@
|
|||||||
use {
|
use {
|
||||||
crate::{
|
crate::{
|
||||||
components::styled_select::Variant,
|
components::styled_select::SelectVariant,
|
||||||
shared::{IntoChild, ToChild, ToNode},
|
shared::{IntoChild, ToChild, ToNode},
|
||||||
Msg,
|
Msg,
|
||||||
},
|
},
|
||||||
seed::{prelude::*, *},
|
seed::{prelude::*, *},
|
||||||
std::borrow::Cow,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::components::styled_avatar::StyledAvatar;
|
||||||
|
use crate::components::styled_icon::StyledIcon;
|
||||||
|
|
||||||
pub enum DisplayType {
|
pub enum DisplayType {
|
||||||
SelectOption,
|
SelectOption,
|
||||||
SelectValue,
|
SelectValue,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct StyledSelectChild<'l> {
|
pub struct StyledSelectChild<'l> {
|
||||||
name: Option<&'l str>,
|
pub name: Option<&'l str>,
|
||||||
icon: Option<Node<Msg>>,
|
pub icon: Option<Node<Msg>>,
|
||||||
text: Option<std::borrow::Cow<'l, str>>,
|
pub text: Option<&'l str>,
|
||||||
display_type: DisplayType,
|
pub display_type: DisplayType,
|
||||||
value: u32,
|
pub value: u32,
|
||||||
class_list: Vec<std::borrow::Cow<'l, str>>,
|
pub class_list: &'l str,
|
||||||
variant: Variant,
|
pub variant: SelectVariant,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'l> StyledSelectChild<'l> {
|
impl<'l> Default for StyledSelectChild<'l> {
|
||||||
pub fn build() -> StyledSelectChildBuilder<'l> {
|
fn default() -> Self {
|
||||||
StyledSelectChildBuilder {
|
Self {
|
||||||
|
name: None,
|
||||||
icon: None,
|
icon: None,
|
||||||
text: None,
|
text: None,
|
||||||
name: None,
|
display_type: DisplayType::SelectOption,
|
||||||
value: 0,
|
value: 0,
|
||||||
class_list: vec![],
|
class_list: "",
|
||||||
variant: Default::default(),
|
variant: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'l> StyledSelectChild<'l> {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn value(&self) -> u32 {
|
pub fn value(&self) -> u32 {
|
||||||
self.value
|
self.value
|
||||||
@ -49,12 +54,25 @@ impl<'l> ToNode for StyledSelectChild<'l> {
|
|||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct StyledSelectChildBuilder<'l> {
|
pub struct StyledSelectChildBuilder<'l> {
|
||||||
icon: Option<Node<Msg>>,
|
pub icon: Option<Node<Msg>>,
|
||||||
text: Option<std::borrow::Cow<'l, str>>,
|
pub text: Option<&'l str>,
|
||||||
name: Option<&'l str>,
|
pub name: Option<&'l str>,
|
||||||
value: u32,
|
pub value: u32,
|
||||||
class_list: Vec<std::borrow::Cow<'l, str>>,
|
pub class_list: &'l str,
|
||||||
variant: Variant,
|
pub variant: SelectVariant,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'l> Default for StyledSelectChildBuilder<'l> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
icon: None,
|
||||||
|
text: None,
|
||||||
|
name: None,
|
||||||
|
value: 0,
|
||||||
|
class_list: "",
|
||||||
|
variant: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'l> PartialEq for StyledSelectChildBuilder<'l> {
|
impl<'l> PartialEq for StyledSelectChildBuilder<'l> {
|
||||||
@ -70,12 +88,7 @@ impl<'l> StyledSelectChildBuilder<'l> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn text<'m: 'l>(mut self, text: &'m str) -> Self {
|
pub fn text<'m: 'l>(mut self, text: &'m str) -> Self {
|
||||||
self.text = Some(std::borrow::Cow::Borrowed(text));
|
self.text = Some(text);
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn text_owned(mut self, text: String) -> Self {
|
|
||||||
self.text = Some(std::borrow::Cow::Owned(text));
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -96,8 +109,8 @@ impl<'l> StyledSelectChildBuilder<'l> {
|
|||||||
.unwrap_or(true)
|
.unwrap_or(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_class<'m: 'l>(mut self, name: &'m str) -> Self {
|
pub fn class_list<'m: 'l>(mut self, name: &'m str) -> Self {
|
||||||
self.class_list.push(Cow::Borrowed(name));
|
self.class_list = name;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -125,26 +138,6 @@ pub fn render(values: StyledSelectChild) -> Node<Msg> {
|
|||||||
variant,
|
variant,
|
||||||
} = values;
|
} = values;
|
||||||
|
|
||||||
let label_class = match display_type {
|
|
||||||
DisplayType::SelectOption => vec![
|
|
||||||
"optionLabel",
|
|
||||||
variant.to_str(),
|
|
||||||
name.as_deref().unwrap_or_default(),
|
|
||||||
],
|
|
||||||
DisplayType::SelectValue => vec![
|
|
||||||
"selectItemLabel",
|
|
||||||
variant.to_str(),
|
|
||||||
name.as_deref().unwrap_or_default(),
|
|
||||||
],
|
|
||||||
}
|
|
||||||
.join(" ");
|
|
||||||
|
|
||||||
let wrapper_class = match display_type {
|
|
||||||
DisplayType::SelectOption => vec!["optionItem", name.as_deref().unwrap_or_default()],
|
|
||||||
DisplayType::SelectValue => vec!["selectItem", name.as_deref().unwrap_or_default()],
|
|
||||||
}
|
|
||||||
.join(" ");
|
|
||||||
|
|
||||||
let icon_node = match icon {
|
let icon_node = match icon {
|
||||||
Some(icon) => icon,
|
Some(icon) => icon,
|
||||||
_ => empty![],
|
_ => empty![],
|
||||||
@ -152,20 +145,32 @@ pub fn render(values: StyledSelectChild) -> Node<Msg> {
|
|||||||
|
|
||||||
let label_node = match text {
|
let label_node = match text {
|
||||||
Some(text) => div![
|
Some(text) => div![
|
||||||
attrs![
|
C![
|
||||||
At::Class => name.as_deref().map(|s| format!("{}Label", s)).unwrap_or_default(),
|
variant.to_str(),
|
||||||
At::Class => class_list.join(" "),
|
name.as_deref()
|
||||||
|
.map(|s| format!("{}Label", s))
|
||||||
|
.unwrap_or_default(),
|
||||||
|
match display_type {
|
||||||
|
DisplayType::SelectOption => "optionLabel",
|
||||||
|
DisplayType::SelectValue => "selectItemLabel",
|
||||||
|
},
|
||||||
|
class_list
|
||||||
],
|
],
|
||||||
C![label_class.as_str()],
|
|
||||||
text
|
text
|
||||||
],
|
],
|
||||||
_ => empty![],
|
_ => empty![],
|
||||||
};
|
};
|
||||||
|
|
||||||
div![
|
div![
|
||||||
C![variant.to_str()],
|
C![
|
||||||
C![wrapper_class.as_str()],
|
variant.to_str(),
|
||||||
attrs![At::Class => class_list.join(" ")],
|
name.as_deref().unwrap_or_default(),
|
||||||
|
match display_type {
|
||||||
|
DisplayType::SelectOption => "optionItem",
|
||||||
|
DisplayType::SelectValue => "selectItem",
|
||||||
|
},
|
||||||
|
class_list
|
||||||
|
],
|
||||||
icon_node,
|
icon_node,
|
||||||
label_node
|
label_node
|
||||||
]
|
]
|
||||||
@ -175,16 +180,19 @@ impl<'l> ToChild<'l> for jirs_data::User {
|
|||||||
type Builder = StyledSelectChildBuilder<'l>;
|
type Builder = StyledSelectChildBuilder<'l>;
|
||||||
|
|
||||||
fn to_child<'m: 'l>(&'m self) -> Self::Builder {
|
fn to_child<'m: 'l>(&'m self) -> Self::Builder {
|
||||||
let avatar = crate::components::styled_avatar::StyledAvatar::build()
|
let avatar = StyledAvatar {
|
||||||
.avatar_url(self.avatar_url.as_deref().unwrap_or_default())
|
size: 20,
|
||||||
.size(20)
|
name: &self.name,
|
||||||
.name(self.name.as_str())
|
avatar_url: self.avatar_url.as_deref(),
|
||||||
.build()
|
..StyledAvatar::default()
|
||||||
.into_node();
|
}
|
||||||
StyledSelectChild::build()
|
.into_node();
|
||||||
.value(self.id as u32)
|
StyledSelectChildBuilder {
|
||||||
.icon(avatar)
|
value: self.id as u32,
|
||||||
.text(self.name.as_str())
|
icon: Some(avatar),
|
||||||
|
text: Some(self.name.as_str()),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -192,17 +200,20 @@ impl<'l> IntoChild<'l> for jirs_data::IssuePriority {
|
|||||||
type Builder = StyledSelectChildBuilder<'l>;
|
type Builder = StyledSelectChildBuilder<'l>;
|
||||||
|
|
||||||
fn into_child(self) -> Self::Builder {
|
fn into_child(self) -> Self::Builder {
|
||||||
let icon = crate::components::styled_icon::StyledIcon::build(self.clone().into())
|
let icon = StyledIcon {
|
||||||
.add_class(self.to_str())
|
icon: self.clone().into(),
|
||||||
.build()
|
class_list: self.to_str(),
|
||||||
.into_node();
|
..Default::default()
|
||||||
let text = self.to_str();
|
}
|
||||||
|
.into_node();
|
||||||
|
|
||||||
StyledSelectChild::build()
|
StyledSelectChildBuilder {
|
||||||
.icon(icon)
|
icon: Some(icon),
|
||||||
.value(self.clone().into())
|
text: Some(self.to_str()),
|
||||||
.text(text)
|
class_list: self.to_str(),
|
||||||
.add_class(self.to_str())
|
value: self.into(),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -210,10 +221,12 @@ impl<'l> ToChild<'l> for jirs_data::IssueStatus {
|
|||||||
type Builder = StyledSelectChildBuilder<'l>;
|
type Builder = StyledSelectChildBuilder<'l>;
|
||||||
|
|
||||||
fn to_child<'m: 'l>(&'m self) -> Self::Builder {
|
fn to_child<'m: 'l>(&'m self) -> Self::Builder {
|
||||||
StyledSelectChild::build()
|
StyledSelectChildBuilder {
|
||||||
.value(self.id as u32)
|
value: self.id as u32,
|
||||||
.add_class(self.name.as_str())
|
class_list: self.name.as_str(),
|
||||||
.text(self.name.as_str())
|
text: Some(self.name.as_str()),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -223,16 +236,20 @@ impl<'l> IntoChild<'l> for jirs_data::IssueType {
|
|||||||
fn into_child(self) -> Self::Builder {
|
fn into_child(self) -> Self::Builder {
|
||||||
let name = self.to_label();
|
let name = self.to_label();
|
||||||
|
|
||||||
let type_icon = crate::components::styled_icon::StyledIcon::build(self.clone().into())
|
let type_icon = StyledIcon {
|
||||||
.add_class(name)
|
icon: self.clone().into(),
|
||||||
.build()
|
class_list: name,
|
||||||
.into_node();
|
..Default::default()
|
||||||
|
}
|
||||||
|
.into_node();
|
||||||
|
|
||||||
StyledSelectChild::build()
|
StyledSelectChildBuilder {
|
||||||
.add_class(name)
|
class_list: name,
|
||||||
.text(name)
|
text: Some(name),
|
||||||
.icon(type_icon)
|
icon: Some(type_icon),
|
||||||
.value(self.clone().into())
|
value: self.into(),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -240,10 +257,12 @@ impl<'l> IntoChild<'l> for jirs_data::ProjectCategory {
|
|||||||
type Builder = StyledSelectChildBuilder<'l>;
|
type Builder = StyledSelectChildBuilder<'l>;
|
||||||
|
|
||||||
fn into_child(self) -> Self::Builder {
|
fn into_child(self) -> Self::Builder {
|
||||||
StyledSelectChild::build()
|
StyledSelectChildBuilder {
|
||||||
.add_class(self.to_str())
|
class_list: self.to_str(),
|
||||||
.text(self.to_str())
|
text: Some(self.to_str()),
|
||||||
.value(self.clone().into())
|
value: self.into(),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -253,32 +272,35 @@ impl<'l> IntoChild<'l> for jirs_data::UserRole {
|
|||||||
fn into_child(self) -> Self::Builder {
|
fn into_child(self) -> Self::Builder {
|
||||||
let name = self.to_str();
|
let name = self.to_str();
|
||||||
|
|
||||||
StyledSelectChild::build()
|
StyledSelectChildBuilder {
|
||||||
.add_class(name)
|
text: Some(name),
|
||||||
.add_class("capitalize")
|
value: self.into(),
|
||||||
.text(name)
|
class_list: name,
|
||||||
.value(self.clone().into())
|
..Default::default()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
macro_rules! id_name_builder {
|
||||||
|
() => {
|
||||||
|
fn to_child<'m: 'l>(&'m self) -> Self::Builder {
|
||||||
|
StyledSelectChildBuilder {
|
||||||
|
text: Some(self.name.as_str()),
|
||||||
|
value: self.id as u32,
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
impl<'l> ToChild<'l> for jirs_data::Project {
|
impl<'l> ToChild<'l> for jirs_data::Project {
|
||||||
type Builder = StyledSelectChildBuilder<'l>;
|
type Builder = StyledSelectChildBuilder<'l>;
|
||||||
|
id_name_builder!();
|
||||||
fn to_child<'m: 'l>(&'m self) -> Self::Builder {
|
|
||||||
StyledSelectChild::build()
|
|
||||||
.text(self.name.as_str())
|
|
||||||
.value(self.id as u32)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'l> ToChild<'l> for jirs_data::Epic {
|
impl<'l> ToChild<'l> for jirs_data::Epic {
|
||||||
type Builder = StyledSelectChildBuilder<'l>;
|
type Builder = StyledSelectChildBuilder<'l>;
|
||||||
|
id_name_builder!();
|
||||||
fn to_child<'m: 'l>(&'m self) -> Self::Builder {
|
|
||||||
StyledSelectChild::build()
|
|
||||||
.text(self.name.as_str())
|
|
||||||
.value(self.id as u32)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'l> ToChild<'l> for u32 {
|
impl<'l> ToChild<'l> for u32 {
|
||||||
@ -287,10 +309,12 @@ impl<'l> ToChild<'l> for u32 {
|
|||||||
fn to_child<'m: 'l>(&'m self) -> Self::Builder {
|
fn to_child<'m: 'l>(&'m self) -> Self::Builder {
|
||||||
let name = stringify!(self);
|
let name = stringify!(self);
|
||||||
|
|
||||||
StyledSelectChild::build()
|
StyledSelectChildBuilder {
|
||||||
.add_class(name)
|
class_list: name,
|
||||||
.text(name)
|
text: Some(name),
|
||||||
.value(*self)
|
value: *self,
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -301,8 +325,10 @@ impl<'l> ToChild<'l> for (Label, Value) {
|
|||||||
type Builder = StyledSelectChildBuilder<'l>;
|
type Builder = StyledSelectChildBuilder<'l>;
|
||||||
|
|
||||||
fn to_child<'m: 'l>(&'m self) -> Self::Builder {
|
fn to_child<'m: 'l>(&'m self) -> Self::Builder {
|
||||||
StyledSelectChild::build()
|
StyledSelectChildBuilder {
|
||||||
.text(self.0.as_str())
|
text: Some(self.0.as_str()),
|
||||||
.value(self.1)
|
value: self.1,
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,103 +5,34 @@ use {
|
|||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct StyledTextarea<'l> {
|
pub struct StyledTextarea<'l> {
|
||||||
id: FieldId,
|
pub id: Option<FieldId>,
|
||||||
height: usize,
|
pub height: usize,
|
||||||
max_height: usize,
|
pub max_height: usize,
|
||||||
value: &'l str,
|
pub value: &'l str,
|
||||||
class_list: Vec<&'l str>,
|
pub class_list: &'l str,
|
||||||
update_event: Ev,
|
pub update_event: Ev,
|
||||||
placeholder: Option<&'l str>,
|
pub placeholder: &'l str,
|
||||||
disable_auto_resize: bool,
|
pub disable_auto_resize: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'l> ToNode for StyledTextarea<'l> {
|
impl<'l> Default for StyledTextarea<'l> {
|
||||||
fn into_node(self) -> Node<Msg> {
|
fn default() -> Self {
|
||||||
render(self)
|
Self {
|
||||||
}
|
id: None,
|
||||||
}
|
height: 0,
|
||||||
|
max_height: 0,
|
||||||
impl<'l> StyledTextarea<'l> {
|
|
||||||
pub fn build(field_id: FieldId) -> StyledTextareaBuilder<'l> {
|
|
||||||
StyledTextareaBuilder {
|
|
||||||
id: field_id,
|
|
||||||
height: None,
|
|
||||||
max_height: None,
|
|
||||||
on_change: None,
|
|
||||||
value: "",
|
value: "",
|
||||||
class_list: vec![],
|
class_list: "",
|
||||||
update_event: None,
|
update_event: Ev::Cached,
|
||||||
placeholder: None,
|
placeholder: "",
|
||||||
disable_auto_resize: false,
|
disable_auto_resize: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
impl<'l> ToNode for StyledTextarea<'l> {
|
||||||
pub struct StyledTextareaBuilder<'l> {
|
fn into_node(self) -> Node<Msg> {
|
||||||
id: FieldId,
|
render(self)
|
||||||
height: Option<usize>,
|
|
||||||
max_height: Option<usize>,
|
|
||||||
on_change: Option<EventHandler<Msg>>,
|
|
||||||
value: &'l str,
|
|
||||||
class_list: Vec<&'l str>,
|
|
||||||
update_event: Option<Ev>,
|
|
||||||
placeholder: Option<&'l str>,
|
|
||||||
disable_auto_resize: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'l> StyledTextareaBuilder<'l> {
|
|
||||||
#[inline]
|
|
||||||
pub fn height(mut self, height: usize) -> Self {
|
|
||||||
self.height = Some(height);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn max_height(mut self, height: usize) -> Self {
|
|
||||||
self.max_height = Some(height);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn value(mut self, value: &'l str) -> Self {
|
|
||||||
self.value = value;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn add_class(mut self, value: &'l str) -> Self {
|
|
||||||
self.class_list.push(value);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update_on(mut self, ev: Ev) -> Self {
|
|
||||||
self.update_event = Some(ev);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn placeholder(mut self, placeholder: &'l str) -> Self {
|
|
||||||
self.placeholder = Some(placeholder);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn disable_auto_resize(mut self) -> Self {
|
|
||||||
self.disable_auto_resize = true;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn build(self) -> StyledTextarea<'l> {
|
|
||||||
StyledTextarea {
|
|
||||||
id: self.id,
|
|
||||||
value: self.value,
|
|
||||||
height: self.height.unwrap_or(110),
|
|
||||||
class_list: self.class_list,
|
|
||||||
max_height: self.max_height.unwrap_or_default(),
|
|
||||||
update_event: self.update_event.unwrap_or(Ev::KeyUp),
|
|
||||||
placeholder: self.placeholder,
|
|
||||||
disable_auto_resize: self.disable_auto_resize,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -124,11 +55,12 @@ pub fn render(values: StyledTextarea) -> Node<Msg> {
|
|||||||
height,
|
height,
|
||||||
max_height,
|
max_height,
|
||||||
value,
|
value,
|
||||||
mut class_list,
|
class_list,
|
||||||
update_event,
|
update_event,
|
||||||
placeholder,
|
placeholder,
|
||||||
disable_auto_resize,
|
disable_auto_resize,
|
||||||
} = values;
|
} = values;
|
||||||
|
let id = id.expect("Text area requires FieldId");
|
||||||
let mut style_list = vec![];
|
let mut style_list = vec![];
|
||||||
|
|
||||||
let min_height = get_min_height(value, height as f64, disable_auto_resize);
|
let min_height = get_min_height(value, height as f64, disable_auto_resize);
|
||||||
@ -141,15 +73,14 @@ pub fn render(values: StyledTextarea) -> Node<Msg> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if disable_auto_resize {
|
if disable_auto_resize {
|
||||||
style_list.push("resize: none".to_string());
|
|
||||||
style_list.push(format!(
|
style_list.push(format!(
|
||||||
"height: {h}px; max-height: {h}px; min-height: {h}px",
|
"resize: none; height: {h}px; max-height: {h}px; min-height: {h}px",
|
||||||
h = max_height
|
h = max_height
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let handler_disable_auto_resize = disable_auto_resize;
|
let handler_disable_auto_resize = disable_auto_resize;
|
||||||
let resize_handler = ev(Ev::KeyUp, move |event| {
|
let resize_handler = ev(Ev::Change, move |event| {
|
||||||
event.stop_propagation();
|
event.stop_propagation();
|
||||||
if handler_disable_auto_resize {
|
if handler_disable_auto_resize {
|
||||||
return None as Option<Msg>;
|
return None as Option<Msg>;
|
||||||
@ -167,39 +98,41 @@ pub fn render(values: StyledTextarea) -> Node<Msg> {
|
|||||||
});
|
});
|
||||||
|
|
||||||
let handler_disable_auto_resize = disable_auto_resize;
|
let handler_disable_auto_resize = disable_auto_resize;
|
||||||
let text_input_handler = ev(update_event, move |event| {
|
let text_input_handler = {
|
||||||
event.stop_propagation();
|
let id = id.clone();
|
||||||
|
ev(update_event, move |event| {
|
||||||
|
event.stop_propagation();
|
||||||
|
|
||||||
let value = event
|
let value = event
|
||||||
.target()
|
.target()
|
||||||
.map(|target| seed::to_textarea(&target).value())
|
.map(|target| seed::to_textarea(&target).value())
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
if handler_disable_auto_resize && value.contains('\n') {
|
if handler_disable_auto_resize && value.contains('\n') {
|
||||||
event.prevent_default();
|
event.prevent_default();
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(Msg::StrInputChanged(
|
Some(Msg::StrInputChanged(
|
||||||
id,
|
id,
|
||||||
if handler_disable_auto_resize {
|
if handler_disable_auto_resize {
|
||||||
value.trim().to_string()
|
value.trim().to_string()
|
||||||
} else {
|
} else {
|
||||||
value
|
value
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
});
|
})
|
||||||
|
};
|
||||||
class_list.push("textAreaInput");
|
|
||||||
|
|
||||||
div![
|
div![
|
||||||
|
id![format!("styledTextArea-{}", id)],
|
||||||
C!["styledTextArea"],
|
C!["styledTextArea"],
|
||||||
div![C!["textAreaHeading"]],
|
div![C!["textAreaHeading"]],
|
||||||
textarea![
|
textarea![
|
||||||
|
C![class_list, "textAreaInput"],
|
||||||
attrs![
|
attrs![
|
||||||
At::Class => class_list.join(" ");
|
|
||||||
At::AutoFocus => "true";
|
At::AutoFocus => "true";
|
||||||
At::Style => style_list.join(";");
|
At::Style => style_list.join(";");
|
||||||
At::Placeholder => placeholder.unwrap_or_default();
|
At::Placeholder => placeholder;
|
||||||
At::Rows => if disable_auto_resize { "5" } else { "auto" }
|
At::Rows => if disable_auto_resize { "5" } else { "auto" }
|
||||||
],
|
],
|
||||||
value,
|
value,
|
||||||
|
@ -4,7 +4,7 @@ use {
|
|||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
pub enum Variant {
|
pub enum TooltipVariant {
|
||||||
About,
|
About,
|
||||||
Messages,
|
Messages,
|
||||||
TableBuilder,
|
TableBuilder,
|
||||||
@ -12,35 +12,35 @@ pub enum Variant {
|
|||||||
DateTimeBuilder,
|
DateTimeBuilder,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Variant {
|
impl Default for TooltipVariant {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Variant::Messages
|
TooltipVariant::Messages
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Variant {
|
impl TooltipVariant {
|
||||||
pub fn to_str(&self) -> &'static str {
|
pub fn to_str(&self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
Variant::About => "about",
|
TooltipVariant::About => "about",
|
||||||
Variant::Messages => "messages",
|
TooltipVariant::Messages => "messages",
|
||||||
Variant::TableBuilder => "tableTooltip",
|
TooltipVariant::TableBuilder => "tableTooltip",
|
||||||
Variant::CodeBuilder => "codeTooltip",
|
TooltipVariant::CodeBuilder => "codeTooltip",
|
||||||
Variant::DateTimeBuilder => "dateTimeTooltip",
|
TooltipVariant::DateTimeBuilder => "dateTimeTooltip",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for Variant {
|
impl std::fmt::Display for TooltipVariant {
|
||||||
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())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct StyledTooltip<'l> {
|
pub struct StyledTooltip<'l> {
|
||||||
visible: bool,
|
pub visible: bool,
|
||||||
class_list: Vec<&'l str>,
|
pub class_list: &'l str,
|
||||||
children: Vec<Node<Msg>>,
|
pub children: Vec<Node<Msg>>,
|
||||||
variant: Variant,
|
pub variant: TooltipVariant,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'l> ToNode for StyledTooltip<'l> {
|
impl<'l> ToNode for StyledTooltip<'l> {
|
||||||
@ -49,71 +49,6 @@ impl<'l> ToNode for StyledTooltip<'l> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'l> StyledTooltip<'l> {
|
|
||||||
pub fn build() -> StyledTooltipBuilder<'l> {
|
|
||||||
StyledTooltipBuilder::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct StyledTooltipBuilder<'l> {
|
|
||||||
visible: bool,
|
|
||||||
class_list: Vec<&'l str>,
|
|
||||||
children: Vec<Node<Msg>>,
|
|
||||||
variant: Variant,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'l> StyledTooltipBuilder<'l> {
|
|
||||||
pub fn visible(mut self, b: bool) -> Self {
|
|
||||||
self.visible = b;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add_class(mut self, name: &'l str) -> Self {
|
|
||||||
self.class_list.push(name);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add_child(mut self, child: Node<Msg>) -> Self {
|
|
||||||
self.children.push(child);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn about_tooltip(mut self) -> Self {
|
|
||||||
self.variant = Variant::About;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn messages_tooltip(mut self) -> Self {
|
|
||||||
self.variant = Variant::Messages;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
// pub fn table_tooltip(mut self) -> Self {
|
|
||||||
// self.variant = Variant::TableBuilder;
|
|
||||||
// self
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// pub fn code_tooltip(mut self) -> Self {
|
|
||||||
// self.variant = Variant::CodeBuilder;
|
|
||||||
// self
|
|
||||||
// }
|
|
||||||
|
|
||||||
pub fn date_time_picker(mut self) -> Self {
|
|
||||||
self.variant = Variant::DateTimeBuilder;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn build(self) -> StyledTooltip<'l> {
|
|
||||||
StyledTooltip {
|
|
||||||
visible: self.visible,
|
|
||||||
class_list: self.class_list,
|
|
||||||
children: self.children,
|
|
||||||
variant: self.variant,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn render(values: StyledTooltip) -> Node<Msg> {
|
pub fn render(values: StyledTooltip) -> Node<Msg> {
|
||||||
let StyledTooltip {
|
let StyledTooltip {
|
||||||
visible,
|
visible,
|
||||||
@ -122,10 +57,7 @@ pub fn render(values: StyledTooltip) -> Node<Msg> {
|
|||||||
variant,
|
variant,
|
||||||
} = values;
|
} = values;
|
||||||
if visible {
|
if visible {
|
||||||
div![
|
div![C!["styledTooltip", class_list, variant.to_str()], children]
|
||||||
attrs![At::Class => format!("styledTooltip {} {}", class_list.join(" "), variant)],
|
|
||||||
children
|
|
||||||
]
|
|
||||||
} else {
|
} else {
|
||||||
empty!()
|
empty!()
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@ use {
|
|||||||
styled_date_time_input::StyledDateTimeChanged,
|
styled_date_time_input::StyledDateTimeChanged,
|
||||||
styled_select::StyledSelectChanged,
|
styled_select::StyledSelectChanged,
|
||||||
styled_tooltip,
|
styled_tooltip,
|
||||||
styled_tooltip::{Variant as StyledTooltip, Variant},
|
styled_tooltip::{TooltipVariant as StyledTooltip, TooltipVariant},
|
||||||
},
|
},
|
||||||
model::{ModalType, Model, Page},
|
model::{ModalType, Model, Page},
|
||||||
shared::{go_to_board, go_to_login},
|
shared::{go_to_board, go_to_login},
|
||||||
@ -24,6 +24,7 @@ mod changes;
|
|||||||
mod components;
|
mod components;
|
||||||
mod fields;
|
mod fields;
|
||||||
mod images;
|
mod images;
|
||||||
|
mod location;
|
||||||
mod modals;
|
mod modals;
|
||||||
mod model;
|
mod model;
|
||||||
mod pages;
|
mod pages;
|
||||||
@ -217,15 +218,15 @@ fn update(msg: Msg, model: &mut model::Model, orders: &mut impl Orders<Msg>) {
|
|||||||
model.page = *page;
|
model.page = *page;
|
||||||
}
|
}
|
||||||
Msg::ToggleTooltip(variant) => match variant {
|
Msg::ToggleTooltip(variant) => match variant {
|
||||||
styled_tooltip::Variant::About => {
|
styled_tooltip::TooltipVariant::About => {
|
||||||
model.about_tooltip_visible = !model.about_tooltip_visible;
|
model.about_tooltip_visible = !model.about_tooltip_visible;
|
||||||
}
|
}
|
||||||
styled_tooltip::Variant::Messages => {
|
styled_tooltip::TooltipVariant::Messages => {
|
||||||
model.messages_tooltip_visible = !model.messages_tooltip_visible;
|
model.messages_tooltip_visible = !model.messages_tooltip_visible;
|
||||||
}
|
}
|
||||||
styled_tooltip::Variant::CodeBuilder => {}
|
styled_tooltip::TooltipVariant::CodeBuilder => {}
|
||||||
Variant::TableBuilder => {}
|
TooltipVariant::TableBuilder => {}
|
||||||
Variant::DateTimeBuilder => {}
|
TooltipVariant::DateTimeBuilder => {}
|
||||||
},
|
},
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
@ -305,16 +306,8 @@ fn resolve_page(url: Url) -> Option<Page> {
|
|||||||
Some(page)
|
Some(page)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub static mut HOST_URL: String = String::new();
|
|
||||||
pub static mut WS_URL: String = String::new();
|
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn render(host_url: String, ws_url: String) {
|
pub fn render() {
|
||||||
unsafe {
|
|
||||||
HOST_URL = host_url;
|
|
||||||
WS_URL = ws_url;
|
|
||||||
}
|
|
||||||
|
|
||||||
let app = seed::App::start("app", init, update, view);
|
let app = seed::App::start("app", init, update, view);
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -348,13 +341,10 @@ pub fn render(host_url: String, ws_url: String) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn init(url: Url, orders: &mut impl Orders<Msg>) -> Model {
|
fn init(url: Url, orders: &mut impl Orders<Msg>) -> Model {
|
||||||
let host_url = unsafe { HOST_URL.clone() };
|
let mut model = Model::new(
|
||||||
let ws_url = unsafe { WS_URL.clone() };
|
location::host_url().to_string(),
|
||||||
let mut model = Model::new(host_url, ws_url);
|
location::ws_url().to_string(),
|
||||||
unsafe {
|
);
|
||||||
HOST_URL = "".to_string();
|
|
||||||
WS_URL = "".to_string();
|
|
||||||
}
|
|
||||||
model.page = resolve_page(url).unwrap_or(Page::Project);
|
model.page = resolve_page(url).unwrap_or(Page::Project);
|
||||||
open_socket(&mut model, orders);
|
open_socket(&mut model, orders);
|
||||||
|
|
||||||
|
@ -6,11 +6,12 @@ use {
|
|||||||
|
|
||||||
pub fn view(_model: &model::Model, modal: &super::Model) -> Node<Msg> {
|
pub fn view(_model: &model::Model, modal: &super::Model) -> Node<Msg> {
|
||||||
let comment_id: CommentId = modal.comment_id;
|
let comment_id: CommentId = modal.comment_id;
|
||||||
StyledConfirmModal::build()
|
StyledConfirmModal {
|
||||||
.title("Are you sure you want to delete this comment?")
|
title: "Are you sure you want to delete this comment?",
|
||||||
.message("Once you delete, it's gone for good.")
|
message: "Once you delete, it's gone for good.",
|
||||||
.confirm_text("Delete comment")
|
confirm_text: "Delete comment",
|
||||||
.on_confirm(mouse_ev(Ev::Click, move |_| Msg::DeleteComment(comment_id)))
|
on_confirm: Some(mouse_ev(Ev::Click, move |_| Msg::DeleteComment(comment_id))),
|
||||||
.build()
|
..Default::default()
|
||||||
.into_node()
|
}
|
||||||
|
.into_node()
|
||||||
}
|
}
|
||||||
|
@ -6,11 +6,11 @@ use {
|
|||||||
pub fn view(model: &Model) -> Node<Msg> {
|
pub fn view(model: &Model) -> Node<Msg> {
|
||||||
let text = format!("{:#?}", model);
|
let text = format!("{:#?}", model);
|
||||||
let code = pre![text];
|
let code = pre![text];
|
||||||
StyledModal::build()
|
StyledModal {
|
||||||
.width(1200)
|
width: Some(1200),
|
||||||
.add_class("debugModal")
|
class_list: "debugModal",
|
||||||
.center()
|
children: vec![code],
|
||||||
.children(vec![code].into_iter())
|
..Default::default()
|
||||||
.build()
|
}
|
||||||
.into_node()
|
.into_node()
|
||||||
}
|
}
|
||||||
|
@ -9,36 +9,40 @@ use {
|
|||||||
seed::prelude::Node,
|
seed::prelude::Node,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::components::styled_select::SelectVariant;
|
||||||
|
|
||||||
pub fn epic_field<Modal>(model: &Model, modal: &Modal, field_id: FieldId) -> Option<Node<Msg>>
|
pub fn epic_field<Modal>(model: &Model, modal: &Modal, field_id: FieldId) -> Option<Node<Msg>>
|
||||||
where
|
where
|
||||||
Modal: IssueModal,
|
Modal: IssueModal,
|
||||||
{
|
{
|
||||||
if model.epics.is_empty() {
|
if model.epics.is_empty() {
|
||||||
None
|
return None;
|
||||||
} else {
|
|
||||||
let selected = modal
|
|
||||||
.epic_id_value()
|
|
||||||
.and_then(|id| model.epics.iter().find(|epic| epic.id == id as EpicId))
|
|
||||||
.map(|epic| vec![epic.to_child()])
|
|
||||||
.unwrap_or_default();
|
|
||||||
let input = StyledSelect::build()
|
|
||||||
.name("epic")
|
|
||||||
.selected(selected)
|
|
||||||
.options(model.epics.iter().map(|epic| epic.to_child()))
|
|
||||||
.normal()
|
|
||||||
.clearable()
|
|
||||||
.text_filter(modal.epic_state().text_filter.as_str())
|
|
||||||
.opened(modal.epic_state().opened)
|
|
||||||
.valid(true)
|
|
||||||
.build(field_id)
|
|
||||||
.into_node();
|
|
||||||
Some(
|
|
||||||
StyledField::build()
|
|
||||||
.label("Epic")
|
|
||||||
.tip("Feature group")
|
|
||||||
.input(input)
|
|
||||||
.build()
|
|
||||||
.into_node(),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
let selected = modal
|
||||||
|
.epic_id_value()
|
||||||
|
.and_then(|id| model.epics.iter().find(|epic| epic.id == id as EpicId))
|
||||||
|
.map(|epic| vec![epic.to_child()])
|
||||||
|
.unwrap_or_default();
|
||||||
|
let input = StyledSelect {
|
||||||
|
id: field_id,
|
||||||
|
name: "epic",
|
||||||
|
selected,
|
||||||
|
options: Some(model.epics.iter().map(|epic| epic.to_child())),
|
||||||
|
variant: SelectVariant::Normal,
|
||||||
|
clearable: true,
|
||||||
|
text_filter: modal.epic_state().text_filter.as_str(),
|
||||||
|
opened: modal.epic_state().opened,
|
||||||
|
valid: true,
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
.into_node();
|
||||||
|
Some(
|
||||||
|
StyledField {
|
||||||
|
label: "Epic",
|
||||||
|
tip: Some("Feature group"),
|
||||||
|
input,
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
.into_node(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
@ -11,25 +11,26 @@ use {
|
|||||||
|
|
||||||
pub fn view(model: &model::Model, modal: &Model) -> Node<Msg> {
|
pub fn view(model: &model::Model, modal: &Model) -> Node<Msg> {
|
||||||
if modal.related_issues.is_empty() {
|
if modal.related_issues.is_empty() {
|
||||||
StyledConfirmModal::build()
|
StyledConfirmModal {
|
||||||
.title("Delete empty epic")
|
title: "Delete empty epic",
|
||||||
.cancel_text("Cancel")
|
confirm_text: "Delete epic",
|
||||||
.confirm_text("Delete epic")
|
cancel_text: "Cancel",
|
||||||
.on_confirm(mouse_ev("click", move |ev| {
|
on_confirm: Some(mouse_ev("click", move |ev| {
|
||||||
ev.stop_propagation();
|
ev.stop_propagation();
|
||||||
ev.prevent_default();
|
ev.prevent_default();
|
||||||
Msg::DeleteEpic
|
Msg::DeleteEpic
|
||||||
}))
|
})),
|
||||||
.build()
|
..Default::default()
|
||||||
.into_node()
|
}
|
||||||
|
.into_node()
|
||||||
} else {
|
} else {
|
||||||
StyledModal::build()
|
StyledModal {
|
||||||
.add_class("deleteEpic")
|
children: vec![warning(model, modal)],
|
||||||
.center()
|
width: Some(600),
|
||||||
.width(600)
|
class_list: "deleteEpic",
|
||||||
.child(warning(model, modal))
|
..Default::default()
|
||||||
.build()
|
}
|
||||||
.into_node()
|
.into_node()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -39,7 +40,7 @@ fn warning(model: &model::Model, modal: &Model) -> Node<Msg> {
|
|||||||
.iter()
|
.iter()
|
||||||
.flat_map(|id| model.issues_by_id.get(id))
|
.flat_map(|id| model.issues_by_id.get(id))
|
||||||
.map(|issue| {
|
.map(|issue| {
|
||||||
let link = StyledIcon::build(Icon::Link).build().into_node();
|
let link = StyledIcon::from(Icon::Link).into_node();
|
||||||
li![div![
|
li![div![
|
||||||
C!["relatedIssue"],
|
C!["relatedIssue"],
|
||||||
a![
|
a![
|
||||||
@ -51,16 +52,17 @@ fn warning(model: &model::Model, modal: &Model) -> Node<Msg> {
|
|||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let close = StyledButton::build()
|
let close = StyledButton {
|
||||||
.text("Close")
|
text: Some("Close"),
|
||||||
.on_click(mouse_ev("click", move |ev| {
|
on_click: Some(mouse_ev("click", move |ev| {
|
||||||
ev.stop_propagation();
|
ev.stop_propagation();
|
||||||
ev.prevent_default();
|
ev.prevent_default();
|
||||||
Msg::ModalDropped
|
Msg::ModalDropped
|
||||||
}))
|
})),
|
||||||
.secondary()
|
variant: ButtonVariant::Secondary,
|
||||||
.build()
|
..Default::default()
|
||||||
.into_node();
|
}
|
||||||
|
.into_node();
|
||||||
|
|
||||||
section![
|
section![
|
||||||
h3![C!["header"], "Cannot delete epic"],
|
h3![C!["header"], "Cannot delete epic"],
|
||||||
|
@ -33,30 +33,34 @@ pub fn view(_model: &model::Model, modal: &Model) -> Node<Msg> {
|
|||||||
} else {
|
} else {
|
||||||
transform_into_unavailable(modal)
|
transform_into_unavailable(modal)
|
||||||
};
|
};
|
||||||
let close = StyledButton::build()
|
let close = StyledButton {
|
||||||
.on_click(mouse_ev("click", |ev| {
|
on_click: Some(mouse_ev("click", |ev| {
|
||||||
ev.stop_propagation();
|
ev.stop_propagation();
|
||||||
ev.prevent_default();
|
ev.prevent_default();
|
||||||
Msg::ModalDropped
|
Msg::ModalDropped
|
||||||
}))
|
})),
|
||||||
.empty()
|
variant: ButtonVariant::Empty,
|
||||||
.icon(Icon::Close)
|
icon: Some(Icon::Close.into_node()),
|
||||||
.build()
|
..Default::default()
|
||||||
.into_node();
|
}
|
||||||
StyledModal::build()
|
.into_node();
|
||||||
.center()
|
StyledModal {
|
||||||
.width(600)
|
width: Some(600),
|
||||||
.add_class("editEpic")
|
class_list: "editEpic",
|
||||||
.child(div![C!["header"], h1!["Edit epic"], close])
|
children: vec![
|
||||||
.child(
|
div![C!["header"], h1!["Edit epic"], close],
|
||||||
StyledInput::build()
|
StyledInput {
|
||||||
.state(&modal.name)
|
value: modal.name.value.as_str(),
|
||||||
.build(FieldId::EditEpic(EpicFieldId::Name))
|
valid: modal.name.is_valid(),
|
||||||
.into_node(),
|
id: Some(FieldId::EditEpic(EpicFieldId::Name)),
|
||||||
)
|
..Default::default()
|
||||||
.child(transform)
|
}
|
||||||
.build()
|
.into_node(),
|
||||||
.into_node()
|
transform,
|
||||||
|
],
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
.into_node()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn transform_into_available(modal: &super::Model) -> Node<Msg> {
|
fn transform_into_available(modal: &super::Model) -> Node<Msg> {
|
||||||
@ -69,15 +73,16 @@ fn transform_into_available(modal: &super::Model) -> Node<Msg> {
|
|||||||
.state(&modal.transform_into)
|
.state(&modal.transform_into)
|
||||||
.build(FieldId::EditEpic(EpicFieldId::TransformInto))
|
.build(FieldId::EditEpic(EpicFieldId::TransformInto))
|
||||||
.into_node();
|
.into_node();
|
||||||
let execute = StyledButton::build()
|
let execute = StyledButton {
|
||||||
.on_click(mouse_ev("click", |ev| {
|
on_click: Some(mouse_ev("click", |ev| {
|
||||||
ev.stop_propagation();
|
ev.stop_propagation();
|
||||||
ev.prevent_default();
|
ev.prevent_default();
|
||||||
Msg::TransformEpic
|
Msg::TransformEpic
|
||||||
}))
|
})),
|
||||||
.text("Transform")
|
text: Some("Transform"),
|
||||||
.build()
|
..Default::default()
|
||||||
.into_node();
|
}
|
||||||
|
.into_node();
|
||||||
div![C!["transform available"], div![types], div![execute]]
|
div![C!["transform available"], div![types], div![execute]]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,14 +5,14 @@ use {
|
|||||||
};
|
};
|
||||||
|
|
||||||
pub fn view(_model: &model::Model, issue_status_id: IssueStatusId) -> Node<Msg> {
|
pub fn view(_model: &model::Model, issue_status_id: IssueStatusId) -> Node<Msg> {
|
||||||
StyledConfirmModal::build()
|
StyledConfirmModal {
|
||||||
.title("Delete column")
|
title: "Delete column",
|
||||||
.cancel_text("No")
|
message: "Are you sure you want to delete column?",
|
||||||
.confirm_text("Yes")
|
confirm_text: "Yes",
|
||||||
.on_confirm(mouse_ev(Ev::Click, move |_| {
|
cancel_text: "No",
|
||||||
|
on_confirm: Some(mouse_ev(Ev::Click, move |_| {
|
||||||
Msg::DeleteIssueStatus(issue_status_id)
|
Msg::DeleteIssueStatus(issue_status_id)
|
||||||
}))
|
})),
|
||||||
.message("Are you sure you want to delete column?")
|
}
|
||||||
.build()
|
.into_node()
|
||||||
.into_node()
|
|
||||||
}
|
}
|
||||||
|
@ -67,25 +67,26 @@ impl<'l> IntoChild<'l> for Type {
|
|||||||
|
|
||||||
let type_icon = {
|
let type_icon = {
|
||||||
use crate::components::styled_icon::*;
|
use crate::components::styled_icon::*;
|
||||||
let icon = {
|
StyledIcon {
|
||||||
match self {
|
icon: match self {
|
||||||
Type::Task => Icon::Task,
|
Type::Task => Icon::Task,
|
||||||
Type::Bug => Icon::Bug,
|
Type::Bug => Icon::Bug,
|
||||||
Type::Story => Icon::Story,
|
Type::Story => Icon::Story,
|
||||||
Type::Epic => Icon::Epic,
|
Type::Epic => Icon::Epic,
|
||||||
}
|
},
|
||||||
};
|
class_list: name,
|
||||||
crate::components::styled_icon::StyledIcon::build(icon)
|
..Default::default()
|
||||||
.add_class(name)
|
}
|
||||||
.build()
|
.into_node()
|
||||||
.into_node()
|
|
||||||
};
|
};
|
||||||
|
|
||||||
StyledSelectChild::build()
|
StyledSelectChildBuilder {
|
||||||
.add_class(name)
|
class_list: name,
|
||||||
.text(name)
|
text: Some(name),
|
||||||
.icon(type_icon)
|
icon: Some(type_icon),
|
||||||
.value(value)
|
value,
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,6 +18,9 @@ use {
|
|||||||
seed::{prelude::*, *},
|
seed::{prelude::*, *},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::components::styled_button::ButtonVariant;
|
||||||
|
use crate::components::styled_select::SelectVariant;
|
||||||
|
|
||||||
pub fn view(model: &Model, modal: &AddIssueModal) -> Node<Msg> {
|
pub fn view(model: &Model, modal: &AddIssueModal) -> Node<Msg> {
|
||||||
let issue_type = modal
|
let issue_type = modal
|
||||||
.type_state
|
.type_state
|
||||||
@ -80,55 +83,54 @@ pub fn view(model: &Model, modal: &AddIssueModal) -> Node<Msg> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let submit = {
|
let submit = {
|
||||||
StyledButton::build()
|
StyledButton {
|
||||||
.primary()
|
variant: ButtonVariant::Primary,
|
||||||
.text(issue_type.submit_label())
|
text: Some(issue_type.submit_label()),
|
||||||
.add_class("action")
|
class_list: "action submit actionButton",
|
||||||
.add_class("submit")
|
on_click: Some(mouse_ev(Ev::Click, move |ev| {
|
||||||
.add_class("actionButton")
|
|
||||||
.on_click(mouse_ev(Ev::Click, move |ev| {
|
|
||||||
ev.stop_propagation();
|
ev.stop_propagation();
|
||||||
ev.prevent_default();
|
ev.prevent_default();
|
||||||
Some(issue_type.submit_action())
|
Some(issue_type.submit_action())
|
||||||
}))
|
})),
|
||||||
.build()
|
..Default::default()
|
||||||
.into_node()
|
}
|
||||||
|
.into_node()
|
||||||
};
|
};
|
||||||
let cancel = StyledButton::build()
|
let cancel = StyledButton {
|
||||||
.empty()
|
variant: ButtonVariant::Empty,
|
||||||
.add_class("action")
|
class_list: "action cancel actionButton",
|
||||||
.add_class("cancel")
|
text: Some("Cancel"),
|
||||||
.add_class("actionButton")
|
on_click: Some(mouse_ev(Ev::Click, |ev| {
|
||||||
.text("Cancel")
|
|
||||||
.on_click(mouse_ev(Ev::Click, |ev| {
|
|
||||||
ev.stop_propagation();
|
ev.stop_propagation();
|
||||||
ev.prevent_default();
|
ev.prevent_default();
|
||||||
Some(Msg::ModalDropped)
|
Some(Msg::ModalDropped)
|
||||||
}))
|
})),
|
||||||
.build()
|
..Default::default()
|
||||||
.into_node();
|
}
|
||||||
|
.into_node();
|
||||||
let actions = div![attrs![At::Class => "actions"], submit, cancel];
|
let actions = div![attrs![At::Class => "actions"], submit, cancel];
|
||||||
|
|
||||||
let form = form.add_field(actions).build().into_node();
|
let form = form.add_field(actions).build().into_node();
|
||||||
|
|
||||||
StyledModal::build()
|
StyledModal {
|
||||||
.add_class("addIssue")
|
class_list: "addIssue",
|
||||||
.width(0)
|
width: Some(0),
|
||||||
.variant(crate::components::styled_modal::Variant::Center)
|
children: vec![form],
|
||||||
.child(form)
|
..Default::default()
|
||||||
.build()
|
}
|
||||||
.into_node()
|
.into_node()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn issue_type_field(modal: &AddIssueModal) -> Node<Msg> {
|
fn issue_type_field(modal: &AddIssueModal) -> Node<Msg> {
|
||||||
let select_type = StyledSelect::build()
|
let select_type = StyledSelect {
|
||||||
.name("type")
|
id: FieldId::AddIssueModal(IssueFieldId::Type),
|
||||||
.normal()
|
name: "type",
|
||||||
.text_filter(modal.type_state.text_filter.as_str())
|
variant: SelectVariant::Normal,
|
||||||
.opened(modal.type_state.opened)
|
text_filter: modal.type_state.text_filter.as_str(),
|
||||||
.valid(true)
|
opened: modal.type_state.opened,
|
||||||
.options(Type::Task.into_iter().map(|t| t.into_child().name("type")))
|
valid: true,
|
||||||
.selected(vec![{
|
options: Some(Type::Task.into_iter().map(|t| t.into_child().name("type"))),
|
||||||
|
selected: vec![{
|
||||||
let v: Type = modal
|
let v: Type = modal
|
||||||
.type_state
|
.type_state
|
||||||
.values
|
.values
|
||||||
@ -139,43 +141,52 @@ fn issue_type_field(modal: &AddIssueModal) -> Node<Msg> {
|
|||||||
v
|
v
|
||||||
}
|
}
|
||||||
.into_child()
|
.into_child()
|
||||||
.name("type")])
|
.name("type")],
|
||||||
.build(FieldId::AddIssueModal(IssueFieldId::Type))
|
..Default::default()
|
||||||
.into_node();
|
}
|
||||||
StyledField::build()
|
.into_node();
|
||||||
.label("Issue Type")
|
StyledField {
|
||||||
.tip("Start typing to get a list of possible matches.")
|
label: "Issue Type",
|
||||||
.input(select_type)
|
tip: Some("Start typing to get a list of possible matches."),
|
||||||
.build()
|
input: select_type,
|
||||||
.into_node()
|
..Default::default()
|
||||||
|
}
|
||||||
|
.into_node()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn short_summary_field(modal: &AddIssueModal) -> Node<Msg> {
|
fn short_summary_field(modal: &AddIssueModal) -> Node<Msg> {
|
||||||
let short_summary = StyledInput::build()
|
let short_summary = StyledInput {
|
||||||
.state(&modal.title_state)
|
value: modal.title_state.value.as_str(),
|
||||||
.build(FieldId::AddIssueModal(IssueFieldId::Title))
|
valid: modal.title_state.is_valid(),
|
||||||
.into_node();
|
id: Some(FieldId::AddIssueModal(IssueFieldId::Title)),
|
||||||
StyledField::build()
|
..Default::default()
|
||||||
.label("Short Summary")
|
}
|
||||||
.tip("Concisely summarize the issue in one or two sentences.")
|
.into_node();
|
||||||
.input(short_summary)
|
StyledField {
|
||||||
.build()
|
label: "Short Summary",
|
||||||
.into_node()
|
tip: Some("Concisely summarize the issue in one or two sentences."),
|
||||||
|
input: short_summary,
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
.into_node()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn description_field() -> Node<Msg> {
|
fn description_field() -> Node<Msg> {
|
||||||
let description = StyledTextarea::build(FieldId::AddIssueModal(IssueFieldId::Description))
|
let description = StyledTextarea {
|
||||||
.height(110)
|
id: Some(FieldId::AddIssueModal(IssueFieldId::Description)),
|
||||||
.add_class("textarea")
|
height: 110,
|
||||||
.build()
|
class_list: "textarea",
|
||||||
.into_node();
|
..Default::default()
|
||||||
StyledField::build()
|
}
|
||||||
.label("Description")
|
.into_node();
|
||||||
.tip("Describe the issue in as much detail as you'd like.")
|
StyledField {
|
||||||
.input(description)
|
label: "Description",
|
||||||
.build()
|
tip: Some("Describe the issue in as much detail as you'd like."),
|
||||||
.into_node()
|
input: description,
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
.into_node()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn reporter_field(model: &Model, modal: &AddIssueModal) -> Node<Msg> {
|
fn reporter_field(model: &Model, modal: &AddIssueModal) -> Node<Msg> {
|
||||||
@ -183,58 +194,60 @@ fn reporter_field(model: &Model, modal: &AddIssueModal) -> Node<Msg> {
|
|||||||
.reporter_id
|
.reporter_id
|
||||||
.or_else(|| model.user.as_ref().map(|u| u.id))
|
.or_else(|| model.user.as_ref().map(|u| u.id))
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
let reporter = StyledSelect::build()
|
let reporter = StyledSelect {
|
||||||
.normal()
|
id: FieldId::AddIssueModal(IssueFieldId::Reporter),
|
||||||
.text_filter(modal.reporter_state.text_filter.as_str())
|
variant: SelectVariant::Normal,
|
||||||
.opened(modal.reporter_state.opened)
|
text_filter: modal.reporter_state.text_filter.as_str(),
|
||||||
.options(model.users.iter().map(|u| u.to_child().name("reporter")))
|
opened: modal.reporter_state.opened,
|
||||||
.selected(
|
options: Some(model.users.iter().map(|u| u.to_child().name("reporter"))),
|
||||||
model
|
selected: model
|
||||||
.users
|
.users
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|user| {
|
.filter_map(|user| {
|
||||||
if user.id == reporter_id {
|
if user.id == reporter_id {
|
||||||
Some(user.to_child().name("reporter"))
|
Some(user.to_child().name("reporter"))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
)
|
|
||||||
.valid(true)
|
valid: true,
|
||||||
.build(FieldId::AddIssueModal(IssueFieldId::Reporter))
|
..Default::default()
|
||||||
.into_node();
|
}
|
||||||
StyledField::build()
|
.into_node();
|
||||||
.input(reporter)
|
StyledField {
|
||||||
.label("Reporter")
|
input: reporter,
|
||||||
.tip("")
|
label: "Reporter",
|
||||||
.build()
|
..Default::default()
|
||||||
.into_node()
|
}
|
||||||
|
.into_node()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn assignees_field(model: &Model, modal: &AddIssueModal) -> Node<Msg> {
|
fn assignees_field(model: &Model, modal: &AddIssueModal) -> Node<Msg> {
|
||||||
let assignees = StyledSelect::build()
|
let assignees = StyledSelect {
|
||||||
.normal()
|
id: FieldId::AddIssueModal(IssueFieldId::Assignees),
|
||||||
.multi()
|
variant: SelectVariant::Normal,
|
||||||
.text_filter(modal.assignees_state.text_filter.as_str())
|
is_multi: true,
|
||||||
.opened(modal.assignees_state.opened)
|
text_filter: modal.assignees_state.text_filter.as_str(),
|
||||||
.options(model.users.iter().map(|u| u.to_child().name("assignees")))
|
opened: modal.assignees_state.opened,
|
||||||
.selected(
|
options: Some(model.users.iter().map(|u| u.to_child().name("assignees"))),
|
||||||
model
|
selected: model
|
||||||
.users
|
.users
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|user| {
|
.filter_map(|user| {
|
||||||
if modal.user_ids.contains(&user.id) {
|
if modal.user_ids.contains(&user.id) {
|
||||||
Some(user.to_child().name("assignees"))
|
Some(user.to_child().name("assignees"))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
)
|
|
||||||
.valid(true)
|
valid: true,
|
||||||
.build(FieldId::AddIssueModal(IssueFieldId::Assignees))
|
..Default::default()
|
||||||
.into_node();
|
}
|
||||||
|
.into_node();
|
||||||
StyledField::build()
|
StyledField::build()
|
||||||
.input(assignees)
|
.input(assignees)
|
||||||
.label("Assignees")
|
.label("Assignees")
|
||||||
@ -245,33 +258,40 @@ 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 priorities = IssuePriority::default().into_iter();
|
||||||
let select_priority = StyledSelect::build()
|
let select_priority = StyledSelect {
|
||||||
.name("priority")
|
id: FieldId::AddIssueModal(IssueFieldId::Priority),
|
||||||
.normal()
|
name: "priority",
|
||||||
.text_filter(modal.priority_state.text_filter.as_str())
|
variant: SelectVariant::Normal,
|
||||||
.opened(modal.priority_state.opened)
|
text_filter: modal.priority_state.text_filter.as_str(),
|
||||||
.valid(true)
|
opened: modal.priority_state.opened,
|
||||||
.options(priorities.map(|p| p.into_child().name("priority")))
|
valid: true,
|
||||||
.selected(vec![modal.priority.into_child().name("priority")])
|
options: Some(priorities.map(|p| p.into_child().name("priority"))),
|
||||||
.build(FieldId::AddIssueModal(IssueFieldId::Priority))
|
selected: vec![modal.priority.into_child().name("priority")],
|
||||||
.into_node();
|
..Default::default()
|
||||||
StyledField::build()
|
}
|
||||||
.label("Issue Type")
|
.into_node();
|
||||||
.tip("Priority in relation to other issues.")
|
StyledField {
|
||||||
.input(select_priority)
|
label: "Issue Type",
|
||||||
.build()
|
tip: Some("Priority in relation to other issues."),
|
||||||
.into_node()
|
input: select_priority,
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
.into_node()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn name_field(modal: &AddIssueModal) -> Node<Msg> {
|
fn name_field(modal: &AddIssueModal) -> Node<Msg> {
|
||||||
let name = StyledInput::build()
|
let name = StyledInput {
|
||||||
.state(&modal.title_state)
|
value: modal.title_state.value.as_str(),
|
||||||
.build(FieldId::AddIssueModal(IssueFieldId::Title))
|
valid: modal.title_state.is_valid(),
|
||||||
.into_node();
|
id: Some(FieldId::AddIssueModal(IssueFieldId::Title)),
|
||||||
StyledField::build()
|
..Default::default()
|
||||||
.label("Epic name")
|
}
|
||||||
.tip("Describe upcoming feature.")
|
.into_node();
|
||||||
.input(name)
|
StyledField {
|
||||||
.build()
|
label: "Epic name",
|
||||||
.into_node()
|
tip: Some("Describe upcoming feature."),
|
||||||
|
input: name,
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
.into_node()
|
||||||
}
|
}
|
||||||
|
@ -9,14 +9,12 @@ pub fn view(model: &model::Model) -> Node<Msg> {
|
|||||||
_ => return Node::Empty,
|
_ => return Node::Empty,
|
||||||
};
|
};
|
||||||
|
|
||||||
let handle_issue_delete = mouse_ev(Ev::Click, move |_| Msg::DeleteIssue(issue_id));
|
StyledConfirmModal {
|
||||||
|
title: "Are you sure you want to delete this issue?",
|
||||||
StyledConfirmModal::build()
|
message: "Once you delete, it's gone for good.",
|
||||||
.title("Are you sure you want to delete this issue?")
|
confirm_text: "Delete issue",
|
||||||
.message("Once you delete, it's gone for good.")
|
cancel_text: "Cancel",
|
||||||
.confirm_text("Delete issue")
|
on_confirm: Some(mouse_ev(Ev::Click, move |_| Msg::DeleteIssue(issue_id))),
|
||||||
.cancel_text("Cancel")
|
}
|
||||||
.on_confirm(handle_issue_delete)
|
.into_node()
|
||||||
.build()
|
|
||||||
.into_node()
|
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,11 @@ use {
|
|||||||
seed::{prelude::*, *},
|
seed::{prelude::*, *},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::components::styled_button::ButtonVariant;
|
||||||
|
use crate::components::styled_icon::StyledIcon;
|
||||||
|
use crate::components::styled_select::SelectVariant;
|
||||||
|
use crate::components::styled_select_child::StyledSelectChildBuilder;
|
||||||
|
|
||||||
mod comments;
|
mod comments;
|
||||||
|
|
||||||
pub fn view(model: &Model, modal: &EditIssueModal) -> Node<Msg> {
|
pub fn view(model: &Model, modal: &EditIssueModal) -> Node<Msg> {
|
||||||
@ -88,53 +93,87 @@ fn modal_header(_model: &Model, modal: &EditIssueModal) -> Node<Msg> {
|
|||||||
Msg::ModalOpened(ModalType::DeleteIssueConfirm(Some(issue_id)))
|
Msg::ModalOpened(ModalType::DeleteIssueConfirm(Some(issue_id)))
|
||||||
});
|
});
|
||||||
|
|
||||||
let copy_button = StyledButton::build()
|
let copy_button = StyledButton {
|
||||||
.empty()
|
variant: ButtonVariant::Empty,
|
||||||
.icon(Icon::Link)
|
icon: Some(Icon::Link.into_node()),
|
||||||
.on_click(click_handler)
|
on_click: Some(click_handler),
|
||||||
.children(vec![span![if *link_copied {
|
children: vec![span![if *link_copied {
|
||||||
"Link Copied"
|
"Link Copied"
|
||||||
} else {
|
} else {
|
||||||
"Copy link"
|
"Copy link"
|
||||||
}]])
|
}]],
|
||||||
.build()
|
..Default::default()
|
||||||
.into_node();
|
}
|
||||||
let delete_button = StyledButton::build()
|
.into_node();
|
||||||
.empty()
|
let delete_button = StyledButton {
|
||||||
.icon(Icon::Trash.into_styled_builder().size(19).build())
|
variant: ButtonVariant::Empty,
|
||||||
.on_click(delete_confirmation_handler)
|
icon: Some(
|
||||||
.build()
|
StyledIcon {
|
||||||
.into_node();
|
icon: Icon::Trash,
|
||||||
let close_button = StyledButton::build()
|
size: Some(19),
|
||||||
.empty()
|
..Default::default()
|
||||||
.icon(Icon::Close.into_styled_builder().size(24).build())
|
}
|
||||||
.on_click(close_handler)
|
.into_node(),
|
||||||
.build()
|
),
|
||||||
.into_node();
|
on_click: Some(delete_confirmation_handler),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
.into_node();
|
||||||
|
let close_button = StyledButton {
|
||||||
|
variant: ButtonVariant::Empty,
|
||||||
|
icon: Some(
|
||||||
|
StyledIcon {
|
||||||
|
icon: Icon::Close,
|
||||||
|
size: Some(24),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
.into_node(),
|
||||||
|
),
|
||||||
|
on_click: Some(close_handler),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
.into_node();
|
||||||
|
|
||||||
let issue_type_select = StyledSelect::build()
|
let issue_type_select = {
|
||||||
.dropdown_width(150)
|
let id = modal.id;
|
||||||
.name("type")
|
let issue_type = &payload.issue_type;
|
||||||
.text_filter(top_type_state.text_filter.as_str())
|
let text = format!("{} - {}", issue_type, id);
|
||||||
.opened(top_type_state.opened)
|
|
||||||
.valid(true)
|
StyledSelect {
|
||||||
.options(
|
id: FieldId::EditIssueModal(EditIssueModalSection::Issue(IssueFieldId::Type)),
|
||||||
IssueType::default()
|
name: "type",
|
||||||
.into_iter()
|
text_filter: top_type_state.text_filter.as_str(),
|
||||||
.map(|t| t.into_child().name("type")),
|
dropdown_width: Some(150),
|
||||||
)
|
valid: true,
|
||||||
.selected(vec![{
|
opened: top_type_state.opened,
|
||||||
let id = modal.id;
|
options: Some(
|
||||||
let issue_type = &payload.issue_type;
|
IssueType::default()
|
||||||
issue_type
|
.into_iter()
|
||||||
.into_child()
|
.map(|t| t.into_child().name("type")),
|
||||||
.name("type")
|
),
|
||||||
.text_owned(format!("{} - {}", issue_type, id))
|
selected: vec![{
|
||||||
}])
|
let name = payload.issue_type.to_label();
|
||||||
.build(FieldId::EditIssueModal(EditIssueModalSection::Issue(
|
|
||||||
IssueFieldId::Type,
|
let type_icon = StyledIcon {
|
||||||
)))
|
icon: payload.issue_type.clone().into(),
|
||||||
.into_node();
|
class_list: name,
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
.into_node();
|
||||||
|
|
||||||
|
StyledSelectChildBuilder {
|
||||||
|
class_list: name,
|
||||||
|
text: Some(&text),
|
||||||
|
icon: Some(type_icon),
|
||||||
|
value: payload.issue_type.into(),
|
||||||
|
name: Some("type"),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}],
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
.into_node()
|
||||||
|
};
|
||||||
|
|
||||||
div![
|
div![
|
||||||
C!["topActions"],
|
C!["topActions"],
|
||||||
@ -156,41 +195,40 @@ fn left_modal_column(model: &Model, modal: &EditIssueModal) -> Node<Msg> {
|
|||||||
..
|
..
|
||||||
} = modal;
|
} = modal;
|
||||||
|
|
||||||
let title = StyledInput::build()
|
let title = StyledInput {
|
||||||
.add_input_class("issueSummary")
|
input_class_list: "issueSummary",
|
||||||
.add_wrapper_class("issueSummary")
|
wrapper_class_list: "issueSummary textarea",
|
||||||
.add_wrapper_class("textarea")
|
value: modal.title_state.value.as_str(),
|
||||||
.state(&modal.title_state)
|
valid: modal.title_state.is_valid(),
|
||||||
.build(FieldId::EditIssueModal(EditIssueModalSection::Issue(
|
id: Some(FieldId::EditIssueModal(EditIssueModalSection::Issue(
|
||||||
IssueFieldId::Title,
|
IssueFieldId::Title,
|
||||||
)))
|
))),
|
||||||
.into_node();
|
..Default::default()
|
||||||
|
}
|
||||||
|
.into_node();
|
||||||
|
|
||||||
let description = {
|
let description = {
|
||||||
StyledEditor::build(FieldId::EditIssueModal(EditIssueModalSection::Issue(
|
StyledEditor {
|
||||||
IssueFieldId::Description,
|
id: Some(FieldId::EditIssueModal(EditIssueModalSection::Issue(
|
||||||
)))
|
IssueFieldId::Description,
|
||||||
.initial_text(description_state.initial_text.as_str())
|
))),
|
||||||
.html(payload.description.as_ref().cloned().unwrap_or_default())
|
initial_text: description_state.initial_text.as_str(),
|
||||||
.mode(description_state.mode.clone())
|
text: description_state.initial_text.as_str(),
|
||||||
.update_on(Ev::Change)
|
html: payload.description.as_deref().unwrap_or_default(),
|
||||||
.build()
|
mode: description_state.mode.clone(),
|
||||||
|
update_event: Ev::Change,
|
||||||
|
}
|
||||||
.into_node()
|
.into_node()
|
||||||
};
|
};
|
||||||
let description_field = StyledField::build().input(description).build().into_node();
|
let description_field = StyledField::build().input(description).build().into_node();
|
||||||
|
|
||||||
let user_avatar = StyledAvatar::build()
|
let user_avatar = StyledAvatar {
|
||||||
.add_class("userAvatar")
|
avatar_url: model.user.as_ref().and_then(|u| u.avatar_url.as_deref()),
|
||||||
.size(32)
|
size: 32,
|
||||||
.avatar_url(
|
class_list: "userAvatar",
|
||||||
model
|
..StyledAvatar::default()
|
||||||
.user
|
}
|
||||||
.as_ref()
|
.into_node();
|
||||||
.and_then(|u| u.avatar_url.as_deref())
|
|
||||||
.unwrap_or_default(),
|
|
||||||
)
|
|
||||||
.build()
|
|
||||||
.into_node();
|
|
||||||
|
|
||||||
let create_comment = if comment_form.creating && comment_form.id.is_none() {
|
let create_comment = if comment_form.creating && comment_form.id.is_none() {
|
||||||
build_comment_form(comment_form)
|
build_comment_form(comment_form)
|
||||||
@ -206,11 +244,7 @@ fn left_modal_column(model: &Model, modal: &EditIssueModal) -> Node<Msg> {
|
|||||||
vec![div![C!["fakeTextArea"], "Add a comment...", handler]]
|
vec![div![C!["fakeTextArea"], "Add a comment...", handler]]
|
||||||
};
|
};
|
||||||
|
|
||||||
let comments: Vec<Node<Msg>> = model
|
let comments = model.comments.iter().flat_map(|c| comment(model, modal, c));
|
||||||
.comments
|
|
||||||
.iter()
|
|
||||||
.flat_map(|c| comment(model, modal, c))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
div![
|
div![
|
||||||
C!["left"],
|
C!["left"],
|
||||||
@ -249,110 +283,105 @@ fn right_modal_column(model: &Model, modal: &EditIssueModal) -> Node<Msg> {
|
|||||||
..
|
..
|
||||||
} = modal;
|
} = modal;
|
||||||
|
|
||||||
let status = StyledSelect::build()
|
let status = StyledSelect {
|
||||||
.name("status")
|
id: FieldId::EditIssueModal(EditIssueModalSection::Issue(IssueFieldId::IssueStatusId)),
|
||||||
.opened(status_state.opened)
|
name: "status",
|
||||||
.normal()
|
opened: status_state.opened,
|
||||||
.text_filter(status_state.text_filter.as_str())
|
variant: SelectVariant::Normal,
|
||||||
.options(
|
text_filter: status_state.text_filter.as_str(),
|
||||||
|
options: Some(
|
||||||
model
|
model
|
||||||
.issue_statuses
|
.issue_statuses
|
||||||
.iter()
|
.iter()
|
||||||
.map(|opt| opt.to_child().name("status")),
|
.map(|opt| opt.to_child().name("status")),
|
||||||
)
|
),
|
||||||
.selected(
|
selected: model
|
||||||
model
|
.issue_statuses
|
||||||
.issue_statuses
|
.iter()
|
||||||
.iter()
|
.filter(|is| is.id == payload.issue_status_id)
|
||||||
.filter(|is| is.id == payload.issue_status_id)
|
.map(|is| is.to_child().name("status"))
|
||||||
.map(|is| is.to_child().name("status"))
|
.collect(),
|
||||||
.collect(),
|
|
||||||
)
|
valid: true,
|
||||||
.valid(true)
|
..Default::default()
|
||||||
.build(FieldId::EditIssueModal(EditIssueModalSection::Issue(
|
}
|
||||||
IssueFieldId::IssueStatusId,
|
.into_node();
|
||||||
)))
|
|
||||||
.into_node();
|
|
||||||
let status_field = StyledField::build()
|
let status_field = StyledField::build()
|
||||||
.input(status)
|
.input(status)
|
||||||
.label("Status")
|
.label("Status")
|
||||||
.build()
|
.build()
|
||||||
.into_node();
|
.into_node();
|
||||||
|
|
||||||
let assignees = StyledSelect::build()
|
let assignees = StyledSelect {
|
||||||
.name("assignees")
|
id: FieldId::EditIssueModal(EditIssueModalSection::Issue(IssueFieldId::Assignees)),
|
||||||
.opened(assignees_state.opened)
|
name: "assignees",
|
||||||
.empty()
|
variant: SelectVariant::Empty,
|
||||||
.multi()
|
is_multi: true,
|
||||||
.text_filter(assignees_state.text_filter.as_str())
|
opened: assignees_state.opened,
|
||||||
.options(
|
text_filter: assignees_state.text_filter.as_str(),
|
||||||
|
options: Some(
|
||||||
model
|
model
|
||||||
.users
|
.users
|
||||||
.iter()
|
.iter()
|
||||||
.map(|user| user.to_child().name("assignees")),
|
.map(|user| user.to_child().name("assignees")),
|
||||||
)
|
),
|
||||||
.selected(
|
selected: model
|
||||||
model
|
.users
|
||||||
.users
|
.iter()
|
||||||
.iter()
|
.filter(|user| payload.user_ids.contains(&user.id))
|
||||||
.filter(|user| payload.user_ids.contains(&user.id))
|
.map(|user| user.to_child().name("assignees"))
|
||||||
.map(|user| user.to_child().name("assignees"))
|
.collect(),
|
||||||
.collect(),
|
..Default::default()
|
||||||
)
|
}
|
||||||
.build(FieldId::EditIssueModal(EditIssueModalSection::Issue(
|
.into_node();
|
||||||
IssueFieldId::Assignees,
|
|
||||||
)))
|
|
||||||
.into_node();
|
|
||||||
let assignees_field = StyledField::build()
|
let assignees_field = StyledField::build()
|
||||||
.input(assignees)
|
.input(assignees)
|
||||||
.label("Assignees")
|
.label("Assignees")
|
||||||
.build()
|
.build()
|
||||||
.into_node();
|
.into_node();
|
||||||
|
|
||||||
let reporter = StyledSelect::build()
|
let reporter = StyledSelect {
|
||||||
.name("reporter")
|
id: FieldId::EditIssueModal(EditIssueModalSection::Issue(IssueFieldId::Reporter)),
|
||||||
.opened(reporter_state.opened)
|
name: "reporter",
|
||||||
.empty()
|
opened: reporter_state.opened,
|
||||||
.text_filter(reporter_state.text_filter.as_str())
|
variant: SelectVariant::Empty,
|
||||||
.options(
|
text_filter: reporter_state.text_filter.as_str(),
|
||||||
|
options: Some(
|
||||||
model
|
model
|
||||||
.users
|
.users
|
||||||
.iter()
|
.iter()
|
||||||
.map(|user| user.to_child().name("reporter")),
|
.map(|user| user.to_child().name("reporter")),
|
||||||
)
|
),
|
||||||
.selected(
|
selected: model
|
||||||
model
|
.users
|
||||||
.users
|
.iter()
|
||||||
.iter()
|
.filter(|user| payload.reporter_id == user.id)
|
||||||
.filter(|user| payload.reporter_id == user.id)
|
.map(|user| user.to_child().name("reporter"))
|
||||||
.map(|user| user.to_child().name("reporter"))
|
.collect(),
|
||||||
.collect(),
|
..Default::default()
|
||||||
)
|
}
|
||||||
.build(FieldId::EditIssueModal(EditIssueModalSection::Issue(
|
.into_node();
|
||||||
IssueFieldId::Reporter,
|
|
||||||
)))
|
|
||||||
.into_node();
|
|
||||||
let reporter_field = StyledField::build()
|
let reporter_field = StyledField::build()
|
||||||
.input(reporter)
|
.input(reporter)
|
||||||
.label("Reporter")
|
.label("Reporter")
|
||||||
.build()
|
.build()
|
||||||
.into_node();
|
.into_node();
|
||||||
|
|
||||||
let priority = StyledSelect::build()
|
let priority = StyledSelect {
|
||||||
.name("priority")
|
id: FieldId::EditIssueModal(EditIssueModalSection::Issue(IssueFieldId::Priority)),
|
||||||
.opened(priority_state.opened)
|
name: "priority",
|
||||||
.empty()
|
variant: SelectVariant::Empty,
|
||||||
.text_filter(priority_state.text_filter.as_str())
|
opened: priority_state.opened,
|
||||||
.options(
|
text_filter: priority_state.text_filter.as_str(),
|
||||||
|
options: Some(
|
||||||
IssuePriority::default()
|
IssuePriority::default()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|p| p.into_child().name("priority")),
|
.map(|p| p.into_child().name("priority")),
|
||||||
)
|
),
|
||||||
.selected(vec![payload.priority.into_child().name("priority")])
|
selected: vec![payload.priority.into_child().name("priority")],
|
||||||
.build(FieldId::EditIssueModal(EditIssueModalSection::Issue(
|
..Default::default()
|
||||||
IssueFieldId::Priority,
|
}
|
||||||
)))
|
.into_node();
|
||||||
.into_node();
|
|
||||||
let priority_field = StyledField::build()
|
let priority_field = StyledField::build()
|
||||||
.input(priority)
|
.input(priority)
|
||||||
.label("Priority")
|
.label("Priority")
|
||||||
|
@ -13,6 +13,8 @@ use {
|
|||||||
seed::{prelude::*, *},
|
seed::{prelude::*, *},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::components::styled_button::ButtonVariant;
|
||||||
|
|
||||||
pub fn build_comment_form(form: &CommentForm) -> Vec<Node<Msg>> {
|
pub fn build_comment_form(form: &CommentForm) -> Vec<Node<Msg>> {
|
||||||
let submit_comment_form = mouse_ev(Ev::Click, move |ev| {
|
let submit_comment_form = mouse_ev(Ev::Click, move |ev| {
|
||||||
ev.stop_propagation();
|
ev.stop_propagation();
|
||||||
@ -26,26 +28,30 @@ pub fn build_comment_form(form: &CommentForm) -> Vec<Node<Msg>> {
|
|||||||
))
|
))
|
||||||
});
|
});
|
||||||
|
|
||||||
let text_area = StyledTextarea::build(FieldId::EditIssueModal(EditIssueModalSection::Comment(
|
let text_area = StyledTextarea {
|
||||||
CommentFieldId::Body,
|
id: Some(FieldId::EditIssueModal(EditIssueModalSection::Comment(
|
||||||
)))
|
CommentFieldId::Body,
|
||||||
.value(form.body.as_str())
|
))),
|
||||||
.placeholder("Add a comment...")
|
value: form.body.as_str(),
|
||||||
.build()
|
placeholder: "Add a comment...",
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
.into_node();
|
.into_node();
|
||||||
|
|
||||||
let submit = StyledButton::build()
|
let submit = StyledButton {
|
||||||
.primary()
|
variant: ButtonVariant::Primary,
|
||||||
.on_click(submit_comment_form)
|
on_click: Some(submit_comment_form),
|
||||||
.text("Save")
|
text: Some("Save"),
|
||||||
.build()
|
..Default::default()
|
||||||
.into_node();
|
}
|
||||||
let cancel = StyledButton::build()
|
.into_node();
|
||||||
.empty()
|
let cancel = StyledButton {
|
||||||
.on_click(close_comment_form)
|
variant: ButtonVariant::Empty,
|
||||||
.text("Cancel")
|
on_click: Some(close_comment_form),
|
||||||
.build()
|
text: Some("Cancel"),
|
||||||
.into_node();
|
..Default::default()
|
||||||
|
}
|
||||||
|
.into_node();
|
||||||
|
|
||||||
vec![text_area, div![C!["actions"], submit, cancel]]
|
vec![text_area, div![C!["actions"], submit, cancel]]
|
||||||
}
|
}
|
||||||
@ -55,12 +61,13 @@ pub fn comment(model: &Model, modal: &EditIssueModal, comment: &Comment) -> Opti
|
|||||||
|
|
||||||
let user = model.users_by_id.get(&comment.user_id)?;
|
let user = model.users_by_id.get(&comment.user_id)?;
|
||||||
|
|
||||||
let avatar = StyledAvatar::build()
|
let avatar = StyledAvatar {
|
||||||
.size(32)
|
avatar_url: user.avatar_url.as_deref(),
|
||||||
.avatar_url(user.avatar_url.as_deref()?)
|
size: 32,
|
||||||
.add_class("userAvatar")
|
class_list: "userAvatar",
|
||||||
.build()
|
..StyledAvatar::default()
|
||||||
.into_node();
|
}
|
||||||
|
.into_node();
|
||||||
|
|
||||||
let buttons = if model.user.as_ref().map(|u| u.id) == Some(comment.user_id) {
|
let buttons = if model.user.as_ref().map(|u| u.id) == Some(comment.user_id) {
|
||||||
let comment_id = comment.id;
|
let comment_id = comment.id;
|
||||||
@ -68,26 +75,28 @@ pub fn comment(model: &Model, modal: &EditIssueModal, comment: &Comment) -> Opti
|
|||||||
ev.stop_propagation();
|
ev.stop_propagation();
|
||||||
Msg::ModalOpened(ModalType::DeleteCommentConfirm(Some(comment_id)))
|
Msg::ModalOpened(ModalType::DeleteCommentConfirm(Some(comment_id)))
|
||||||
});
|
});
|
||||||
let edit_button = StyledButton::build()
|
let edit_button = StyledButton {
|
||||||
.add_class("editButton")
|
class_list: "editButton",
|
||||||
.on_click(mouse_ev(Ev::Click, move |_| {
|
on_click: Some(mouse_ev(Ev::Click, move |_| {
|
||||||
Msg::ModalChanged(FieldChange::EditComment(
|
Msg::ModalChanged(FieldChange::EditComment(
|
||||||
FieldId::EditIssueModal(EditIssueModalSection::Comment(CommentFieldId::Body)),
|
FieldId::EditIssueModal(EditIssueModalSection::Comment(CommentFieldId::Body)),
|
||||||
comment_id,
|
comment_id,
|
||||||
))
|
))
|
||||||
}))
|
})),
|
||||||
.text("Edit")
|
text: Some("Edit"),
|
||||||
.empty()
|
variant: ButtonVariant::Empty,
|
||||||
.build()
|
..Default::default()
|
||||||
.into_node();
|
}
|
||||||
|
.into_node();
|
||||||
|
|
||||||
let cancel_button = StyledButton::build()
|
let cancel_button = StyledButton {
|
||||||
.add_class("deleteButton")
|
class_list: "deleteButton",
|
||||||
.on_click(delete_comment_handler)
|
on_click: Some(delete_comment_handler),
|
||||||
.text("Delete")
|
text: Some("Delete"),
|
||||||
.empty()
|
variant: ButtonVariant::Empty,
|
||||||
.build()
|
..Default::default()
|
||||||
.into_node();
|
}
|
||||||
|
.into_node();
|
||||||
|
|
||||||
vec![edit_button, cancel_button]
|
vec![edit_button, cancel_button]
|
||||||
} else {
|
} else {
|
||||||
|
@ -69,18 +69,20 @@ pub fn view(model: &Model, modal: &super::Model) -> Node<Msg> {
|
|||||||
div![C!["inputContainer"], time_remaining_field]
|
div![C!["inputContainer"], time_remaining_field]
|
||||||
];
|
];
|
||||||
|
|
||||||
let close = StyledButton::build()
|
let close = StyledButton {
|
||||||
.text("Done")
|
text: Some("Done"),
|
||||||
.on_click(mouse_ev(Ev::Click, |_| Msg::ModalDropped))
|
on_click: Some(mouse_ev(Ev::Click, |_| Msg::ModalDropped)),
|
||||||
.build()
|
..Default::default()
|
||||||
.into_node();
|
}
|
||||||
|
.into_node();
|
||||||
|
|
||||||
StyledModal::build()
|
StyledModal {
|
||||||
.add_class("timeTrackingModal")
|
class_list: "timeTrackingModal",
|
||||||
.children(vec![modal_title, tracking, inputs, div![C!["actions"], close]].into_iter())
|
children: vec![modal_title, tracking, inputs, div![C!["actions"], close]],
|
||||||
.width(400)
|
width: Some(400),
|
||||||
.build()
|
..Default::default()
|
||||||
.into_node()
|
}
|
||||||
|
.into_node()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
@ -94,27 +96,32 @@ pub fn time_tracking_field(
|
|||||||
let fibonacci_values = fibonacci_values();
|
let fibonacci_values = fibonacci_values();
|
||||||
let input = match time_tracking_type {
|
let input = match time_tracking_type {
|
||||||
TimeTracking::Untracked => empty![],
|
TimeTracking::Untracked => empty![],
|
||||||
TimeTracking::Fibonacci => StyledSelect::build()
|
TimeTracking::Fibonacci => StyledSelect {
|
||||||
.selected(
|
id: field_id,
|
||||||
select_state
|
selected: select_state
|
||||||
.values
|
.values
|
||||||
.iter()
|
.iter()
|
||||||
.map(|n| (*n).to_child())
|
.map(|n| (*n).to_child())
|
||||||
.collect(),
|
.collect(),
|
||||||
)
|
|
||||||
.state(select_state)
|
text_filter: select_state.text_filter.as_str(),
|
||||||
.options(fibonacci_values.iter().map(|v| v.to_child()))
|
opened: select_state.opened,
|
||||||
.build(field_id)
|
options: Some(fibonacci_values.iter().map(|v| v.to_child())),
|
||||||
.into_node(),
|
..Default::default()
|
||||||
TimeTracking::Hourly => StyledInput::build()
|
}
|
||||||
.state(input_state)
|
.into_node(),
|
||||||
.valid(true)
|
TimeTracking::Hourly => StyledInput {
|
||||||
.build(field_id)
|
valid: input_state.is_valid(),
|
||||||
.into_node(),
|
value: input_state.value.as_str(),
|
||||||
|
id: Some(field_id),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
.into_node(),
|
||||||
};
|
};
|
||||||
StyledField::build()
|
StyledField {
|
||||||
.input(input)
|
input,
|
||||||
.label(label)
|
label,
|
||||||
.build()
|
..Default::default()
|
||||||
.into_node()
|
}
|
||||||
|
.into_node()
|
||||||
}
|
}
|
||||||
|
@ -142,6 +142,12 @@ macro_rules! match_page {
|
|||||||
_ => return,
|
_ => return,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
($model: ident, $ty: ident; Empty) => {
|
||||||
|
match &$model.page_content {
|
||||||
|
PageContent::$ty(page) => page,
|
||||||
|
_ => return Node::Empty,
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! match_page_mut {
|
macro_rules! match_page_mut {
|
||||||
|
@ -4,6 +4,7 @@ use {
|
|||||||
styled_button::StyledButton, styled_field::StyledField, styled_form::StyledForm,
|
styled_button::StyledButton, styled_field::StyledField, styled_form::StyledForm,
|
||||||
styled_input::StyledInput,
|
styled_input::StyledInput,
|
||||||
},
|
},
|
||||||
|
match_page,
|
||||||
model::{Model, PageContent},
|
model::{Model, PageContent},
|
||||||
pages::invite_page::InvitePage,
|
pages::invite_page::InvitePage,
|
||||||
shared::{outer_layout, ToNode},
|
shared::{outer_layout, ToNode},
|
||||||
@ -14,11 +15,10 @@ use {
|
|||||||
seed::{prelude::*, *},
|
seed::{prelude::*, *},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::components::styled_button::ButtonVariant;
|
||||||
|
|
||||||
pub fn view(model: &Model) -> Node<Msg> {
|
pub fn view(model: &Model) -> Node<Msg> {
|
||||||
let page = match &model.page_content {
|
let page = match_page!(model, Invite; Empty);
|
||||||
PageContent::Invite(page) => page,
|
|
||||||
_ => return empty![],
|
|
||||||
};
|
|
||||||
|
|
||||||
let token_field = token_field(page);
|
let token_field = token_field(page);
|
||||||
let submit_field = submit(page);
|
let submit_field = submit(page);
|
||||||
@ -43,24 +43,32 @@ pub fn view(model: &Model) -> Node<Msg> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn submit(_page: &InvitePage) -> Node<Msg> {
|
fn submit(_page: &InvitePage) -> Node<Msg> {
|
||||||
let submit = StyledButton::build()
|
let submit = StyledButton {
|
||||||
.text("Accept")
|
text: Some("Accept"),
|
||||||
.primary()
|
variant: ButtonVariant::Primary,
|
||||||
.build()
|
..Default::default()
|
||||||
.into_node();
|
}
|
||||||
StyledField::build().input(submit).build().into_node()
|
.into_node();
|
||||||
|
StyledField {
|
||||||
|
input: submit,
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
.into_node()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn token_field(page: &InvitePage) -> Node<Msg> {
|
fn token_field(page: &InvitePage) -> Node<Msg> {
|
||||||
let token = StyledInput::build()
|
let input = StyledInput {
|
||||||
.valid(!page.token_touched || is_token(page.token.as_str()) && page.error.is_none())
|
valid: !page.token_touched || is_token(page.token.as_str()) && page.error.is_none(),
|
||||||
.value(page.token.as_str())
|
id: Some(FieldId::Invite(InviteFieldId::Token)),
|
||||||
.build(FieldId::Invite(InviteFieldId::Token))
|
value: page.token.as_str(),
|
||||||
.into_node();
|
..Default::default()
|
||||||
|
}
|
||||||
|
.into_node();
|
||||||
|
|
||||||
StyledField::build()
|
StyledField {
|
||||||
.input(token)
|
input,
|
||||||
.label("Your invite token")
|
label: "Your invite token",
|
||||||
.build()
|
..Default::default()
|
||||||
.into_node()
|
}
|
||||||
|
.into_node()
|
||||||
}
|
}
|
||||||
|
@ -15,53 +15,65 @@ use {
|
|||||||
std::collections::HashMap,
|
std::collections::HashMap,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::components::styled_button::ButtonVariant;
|
||||||
|
use crate::components::styled_input::InputVariant;
|
||||||
|
use crate::components::styled_select::SelectVariant;
|
||||||
|
|
||||||
pub fn view(model: &Model) -> Node<Msg> {
|
pub fn view(model: &Model) -> Node<Msg> {
|
||||||
let page = match &model.page_content {
|
let page = match &model.page_content {
|
||||||
PageContent::Profile(profile_page) => profile_page,
|
PageContent::Profile(profile_page) => profile_page,
|
||||||
_ => return empty![],
|
_ => return empty![],
|
||||||
};
|
};
|
||||||
|
|
||||||
let avatar = StyledImageInput::build(FieldId::Profile(UsersFieldId::Avatar))
|
let avatar = StyledImageInput {
|
||||||
.add_class("avatar")
|
id: FieldId::Profile(UsersFieldId::Avatar),
|
||||||
.state(&page.avatar)
|
class_list: "avatar",
|
||||||
.build()
|
url: page.avatar.url.as_deref(),
|
||||||
.into_node();
|
}
|
||||||
|
.into_node();
|
||||||
|
|
||||||
let username = StyledInput::build()
|
let username = StyledInput {
|
||||||
.state(&page.name)
|
id: Some(FieldId::Profile(UsersFieldId::Username)),
|
||||||
.valid(true)
|
valid: page.name.is_valid(),
|
||||||
.primary()
|
value: page.name.value.as_str(),
|
||||||
.build(FieldId::Profile(UsersFieldId::Username))
|
variant: InputVariant::Primary,
|
||||||
.into_node();
|
..Default::default()
|
||||||
let username_field = StyledField::build()
|
}
|
||||||
.label("Username")
|
.into_node();
|
||||||
.input(username)
|
let username_field = StyledField {
|
||||||
.build()
|
label: "Username",
|
||||||
.into_node();
|
input: username,
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
.into_node();
|
||||||
|
|
||||||
let email = StyledInput::build()
|
let email = StyledInput {
|
||||||
.state(&page.email)
|
id: Some(FieldId::Profile(UsersFieldId::Email)),
|
||||||
.valid(true)
|
valid: page.email.is_valid(),
|
||||||
.primary()
|
value: page.email.value.as_str(),
|
||||||
.build(FieldId::Profile(UsersFieldId::Username))
|
variant: InputVariant::Primary,
|
||||||
.into_node();
|
..Default::default()
|
||||||
let email_field = StyledField::build()
|
}
|
||||||
.label("E-Mail")
|
.into_node();
|
||||||
.input(email)
|
let email_field = StyledField {
|
||||||
.build()
|
label: "E-Mail",
|
||||||
.into_node();
|
input: email,
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
.into_node();
|
||||||
|
|
||||||
let current_project = build_current_project(model, page);
|
let current_project = build_current_project(model, page);
|
||||||
|
|
||||||
let submit = StyledButton::build()
|
let submit = StyledButton {
|
||||||
.primary()
|
variant: ButtonVariant::Primary,
|
||||||
.text("Save")
|
text: Some("Save"),
|
||||||
.on_click(mouse_ev(Ev::Click, |ev| {
|
on_click: Some(mouse_ev(Ev::Click, |ev| {
|
||||||
ev.prevent_default();
|
ev.prevent_default();
|
||||||
Msg::PageChanged(PageChanged::Profile(ProfilePageChange::SubmitForm))
|
Msg::PageChanged(PageChanged::Profile(ProfilePageChange::SubmitForm))
|
||||||
}))
|
})),
|
||||||
.build()
|
..Default::default()
|
||||||
.into_node();
|
}
|
||||||
|
.into_node();
|
||||||
let submit_field = StyledField::build().input(submit).build().into_node();
|
let submit_field = StyledField::build().input(submit).build().into_node();
|
||||||
|
|
||||||
let content = StyledForm::build()
|
let content = StyledForm::build()
|
||||||
@ -81,44 +93,47 @@ 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 =
|
let inner = if model.projects.len() <= 1 {
|
||||||
if model.projects.len() <= 1 {
|
let name = model
|
||||||
let name = model
|
.project
|
||||||
.project
|
.as_ref()
|
||||||
.as_ref()
|
.map(|p| p.name.as_str())
|
||||||
.map(|p| p.name.as_str())
|
.unwrap_or_default();
|
||||||
.unwrap_or_default();
|
span![name]
|
||||||
span![name]
|
} else {
|
||||||
} else {
|
let mut project_by_id = HashMap::new();
|
||||||
let mut project_by_id = HashMap::new();
|
for p in model.projects.iter() {
|
||||||
for p in model.projects.iter() {
|
project_by_id.insert(p.id, p);
|
||||||
project_by_id.insert(p.id, p);
|
}
|
||||||
}
|
let mut joined_projects = HashMap::new();
|
||||||
let mut joined_projects = HashMap::new();
|
for p in model.user_projects.iter() {
|
||||||
for p in model.user_projects.iter() {
|
joined_projects.insert(p.project_id, p);
|
||||||
joined_projects.insert(p.project_id, p);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
StyledSelect::build()
|
StyledSelect {
|
||||||
.name("current_project")
|
id: FieldId::Profile(UsersFieldId::CurrentProject),
|
||||||
.normal()
|
name: "current_project",
|
||||||
.options(model.projects.iter().filter_map(|project| {
|
valid: true,
|
||||||
joined_projects.get(&project.id).map(|_| project.to_child())
|
opened: page.current_project.opened,
|
||||||
}))
|
text_filter: page.current_project.text_filter.as_str(),
|
||||||
.selected(
|
variant: SelectVariant::Normal,
|
||||||
page.current_project
|
options: Some(model.projects.iter().filter_map(|project| {
|
||||||
.values
|
joined_projects.get(&project.id).map(|_| project.to_child())
|
||||||
.iter()
|
})),
|
||||||
.filter_map(|id| project_by_id.get(&((*id) as i32)).map(|p| p.to_child()))
|
selected: page
|
||||||
.collect(),
|
.current_project
|
||||||
)
|
.values
|
||||||
.state(&page.current_project)
|
.iter()
|
||||||
.build(FieldId::Profile(UsersFieldId::CurrentProject))
|
.filter_map(|id| project_by_id.get(&((*id) as i32)).map(|p| p.to_child()))
|
||||||
.into_node()
|
.collect(),
|
||||||
};
|
..Default::default()
|
||||||
StyledField::build()
|
}
|
||||||
.label("Current project")
|
|
||||||
.input(div![C!["project-name"], inner])
|
|
||||||
.build()
|
|
||||||
.into_node()
|
.into_node()
|
||||||
|
};
|
||||||
|
StyledField {
|
||||||
|
label: "Current project",
|
||||||
|
input: div![C!["project-name"], inner],
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
.into_node()
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,8 @@ use {
|
|||||||
seed::{prelude::*, *},
|
seed::{prelude::*, *},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::components::styled_button::ButtonVariant;
|
||||||
|
|
||||||
pub fn project_board_lists(model: &Model) -> Node<Msg> {
|
pub fn project_board_lists(model: &Model) -> Node<Msg> {
|
||||||
let project_page = match &model.page_content {
|
let project_page = match &model.page_content {
|
||||||
PageContent::Project(project_page) => project_page,
|
PageContent::Project(project_page) => project_page,
|
||||||
@ -35,10 +37,10 @@ pub fn project_board_lists(model: &Model) -> Node<Msg> {
|
|||||||
let epic_name = match per_epic.epic_ref.as_ref() {
|
let epic_name = match per_epic.epic_ref.as_ref() {
|
||||||
Some((id, name)) => {
|
Some((id, name)) => {
|
||||||
let id = *id;
|
let id = *id;
|
||||||
let edit_button = StyledButton::build()
|
let edit_button = StyledButton {
|
||||||
.empty()
|
variant: ButtonVariant::Empty,
|
||||||
.icon(Icon::EditAlt)
|
icon: Some(Icon::EditAlt.into_node()),
|
||||||
.on_click(mouse_ev("click", move |ev| {
|
on_click: Some(mouse_ev("click", move |ev| {
|
||||||
ev.stop_propagation();
|
ev.stop_propagation();
|
||||||
ev.prevent_default();
|
ev.prevent_default();
|
||||||
seed::Url::new()
|
seed::Url::new()
|
||||||
@ -46,13 +48,14 @@ pub fn project_board_lists(model: &Model) -> Node<Msg> {
|
|||||||
.add_path_part(id.to_string())
|
.add_path_part(id.to_string())
|
||||||
.go_and_push();
|
.go_and_push();
|
||||||
Msg::ChangePage(Page::EditEpic(id))
|
Msg::ChangePage(Page::EditEpic(id))
|
||||||
}))
|
})),
|
||||||
.build()
|
..Default::default()
|
||||||
.into_node();
|
}
|
||||||
let delete_button = StyledButton::build()
|
.into_node();
|
||||||
.empty()
|
let delete_button = StyledButton {
|
||||||
.icon(Icon::DeleteAlt)
|
variant: ButtonVariant::Empty,
|
||||||
.on_click(mouse_ev("click", move |ev| {
|
icon: Some(Icon::DeleteAlt.into_node()),
|
||||||
|
on_click: Some(mouse_ev("click", move |ev| {
|
||||||
ev.stop_propagation();
|
ev.stop_propagation();
|
||||||
ev.prevent_default();
|
ev.prevent_default();
|
||||||
seed::Url::new()
|
seed::Url::new()
|
||||||
@ -60,9 +63,10 @@ pub fn project_board_lists(model: &Model) -> Node<Msg> {
|
|||||||
.add_path_part(id.to_string())
|
.add_path_part(id.to_string())
|
||||||
.go_and_push();
|
.go_and_push();
|
||||||
Msg::ChangePage(Page::DeleteEpic(id))
|
Msg::ChangePage(Page::DeleteEpic(id))
|
||||||
}))
|
})),
|
||||||
.build()
|
..Default::default()
|
||||||
.into_node();
|
}
|
||||||
|
.into_node();
|
||||||
|
|
||||||
div![
|
div![
|
||||||
C!["epicHeader"],
|
C!["epicHeader"],
|
||||||
@ -130,26 +134,31 @@ fn project_issue(model: &Model, issue: &Issue) -> Node<Msg> {
|
|||||||
.iter()
|
.iter()
|
||||||
.filter_map(|id| model.users_by_id.get(id))
|
.filter_map(|id| model.users_by_id.get(id))
|
||||||
.map(|user| {
|
.map(|user| {
|
||||||
StyledAvatar::build()
|
StyledAvatar {
|
||||||
.size(24)
|
avatar_url: user.avatar_url.as_deref(),
|
||||||
.name(user.name.as_str())
|
size: 24,
|
||||||
.avatar_url(user.avatar_url.as_deref().unwrap_or_default())
|
name: &user.name,
|
||||||
.user_index(0)
|
..StyledAvatar::default()
|
||||||
.build()
|
}
|
||||||
.into_node()
|
.into_node()
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let issue_type_icon = StyledIcon::build(issue.issue_type.clone().into())
|
let issue_type_icon = StyledIcon {
|
||||||
.with_color(issue.issue_type.to_str())
|
icon: issue.issue_type.into(),
|
||||||
.build()
|
class_list: issue.issue_type.to_str(),
|
||||||
.into_node();
|
color: Some(issue.issue_type.to_str()),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
.into_node();
|
||||||
|
|
||||||
let priority_icon = StyledIcon::build(issue.priority.into())
|
let priority_icon = StyledIcon {
|
||||||
.add_class(issue.priority.to_str())
|
icon: issue.priority.into(),
|
||||||
.with_color(issue.priority.to_str())
|
class_list: issue.priority.to_str(),
|
||||||
.build()
|
color: Some(issue.priority.to_str()),
|
||||||
.into_node();
|
..Default::default()
|
||||||
|
}
|
||||||
|
.into_node();
|
||||||
|
|
||||||
let issue_id = issue.id;
|
let issue_id = issue.id;
|
||||||
let drag_started = drag_ev(Ev::DragStart, move |ev| {
|
let drag_started = drag_ev(Ev::DragStart, move |ev| {
|
||||||
|
@ -14,29 +14,33 @@ pub fn project_board_filters(model: &Model) -> Node<Msg> {
|
|||||||
_ => return empty![],
|
_ => return empty![],
|
||||||
};
|
};
|
||||||
|
|
||||||
let search_input = StyledInput::build()
|
let search_input = StyledInput {
|
||||||
.icon(Icon::Search)
|
value: project_page.text_filter.as_str(),
|
||||||
.valid(true)
|
valid: true,
|
||||||
.value(project_page.text_filter.as_str())
|
id: Some(FieldId::TextFilterBoard),
|
||||||
.build(FieldId::TextFilterBoard)
|
icon: Some(Icon::Search),
|
||||||
.into_node();
|
..Default::default()
|
||||||
|
}
|
||||||
|
.into_node();
|
||||||
|
|
||||||
let only_my = StyledButton::build()
|
let only_my = StyledButton {
|
||||||
.empty()
|
variant: ButtonVariant::Empty,
|
||||||
.active(project_page.only_my_filter)
|
active: project_page.only_my_filter,
|
||||||
.text("Only My Issues")
|
text: Some("Only My Issues"),
|
||||||
.add_class("filterChild")
|
class_list: "filterChild",
|
||||||
.on_click(mouse_ev(Ev::Click, |_| Msg::ProjectToggleOnlyMy))
|
on_click: Some(mouse_ev(Ev::Click, |_| Msg::ProjectToggleOnlyMy)),
|
||||||
.build()
|
..Default::default()
|
||||||
.into_node();
|
}
|
||||||
|
.into_node();
|
||||||
|
|
||||||
let recently_updated = StyledButton::build()
|
let recently_updated = StyledButton {
|
||||||
.empty()
|
variant: ButtonVariant::Empty,
|
||||||
.text("Recently Updated")
|
text: Some("Recently Updated"),
|
||||||
.add_class("filterChild")
|
class_list: "filterChild",
|
||||||
.on_click(mouse_ev(Ev::Click, |_| Msg::ProjectToggleRecentlyUpdated))
|
on_click: Some(mouse_ev(Ev::Click, |_| Msg::ProjectToggleRecentlyUpdated)),
|
||||||
.build()
|
..Default::default()
|
||||||
.into_node();
|
}
|
||||||
|
.into_node();
|
||||||
|
|
||||||
let clear_all = if project_page.only_my_filter
|
let clear_all = if project_page.only_my_filter
|
||||||
|| project_page.recently_updated_filter
|
|| project_page.recently_updated_filter
|
||||||
@ -75,17 +79,18 @@ pub fn avatars_filters(model: &Model) -> Node<Msg> {
|
|||||||
.map(|(idx, user)| {
|
.map(|(idx, user)| {
|
||||||
let user_id = user.id;
|
let user_id = user.id;
|
||||||
let active = active_avatar_filters.contains(&user_id);
|
let active = active_avatar_filters.contains(&user_id);
|
||||||
let styled_avatar = StyledAvatar::build()
|
let styled_avatar = StyledAvatar {
|
||||||
.avatar_url(user.avatar_url.as_deref().unwrap_or_default())
|
avatar_url: user.avatar_url.as_deref(),
|
||||||
.on_click(mouse_ev(Ev::Click, move |_| {
|
name: &user.name,
|
||||||
|
on_click: Some(mouse_ev(Ev::Click, move |_| {
|
||||||
Msg::ProjectAvatarFilterChanged(user_id, active)
|
Msg::ProjectAvatarFilterChanged(user_id, active)
|
||||||
}))
|
})),
|
||||||
.name(user.name.as_str())
|
user_index: idx,
|
||||||
.user_index(idx)
|
..StyledAvatar::default()
|
||||||
.build()
|
}
|
||||||
.into_node();
|
.into_node();
|
||||||
div![
|
div![
|
||||||
if active { Some(C!["isActive"]) } else { None },
|
IF![active => C!["isActive"]],
|
||||||
C!["avatarIsActiveBorder"],
|
C!["avatarIsActiveBorder"],
|
||||||
styled_avatar
|
styled_avatar
|
||||||
]
|
]
|
||||||
|
@ -21,6 +21,10 @@ use {
|
|||||||
std::collections::HashMap,
|
std::collections::HashMap,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::components::styled_button::ButtonVariant;
|
||||||
|
use crate::components::styled_input::InputVariant;
|
||||||
|
use crate::components::styled_select::SelectVariant;
|
||||||
|
|
||||||
// use crate::shared::styled_rte::StyledRte;
|
// use crate::shared::styled_rte::StyledRte;
|
||||||
|
|
||||||
static TIME_TRACKING_FIBONACCI: &str = include_str!("./time_tracking_fibonacci.txt");
|
static TIME_TRACKING_FIBONACCI: &str = include_str!("./time_tracking_fibonacci.txt");
|
||||||
@ -60,29 +64,31 @@ pub fn view(model: &model::Model) -> Node<Msg> {
|
|||||||
.build(FieldId::ProjectSettings(ProjectFieldId::TimeTracking))
|
.build(FieldId::ProjectSettings(ProjectFieldId::TimeTracking))
|
||||||
.into_node();
|
.into_node();
|
||||||
let time_tracking_type: TimeTracking = page.time_tracking.value.into();
|
let time_tracking_type: TimeTracking = page.time_tracking.value.into();
|
||||||
let time_tracking_field = StyledField::build()
|
let time_tracking_field = StyledField {
|
||||||
.input(time_tracking)
|
input: time_tracking,
|
||||||
.tip(match time_tracking_type {
|
tip: Some(match time_tracking_type {
|
||||||
TimeTracking::Fibonacci => TIME_TRACKING_FIBONACCI,
|
TimeTracking::Fibonacci => TIME_TRACKING_FIBONACCI,
|
||||||
TimeTracking::Hourly => TIME_TRACKING_HOURLY,
|
TimeTracking::Hourly => TIME_TRACKING_HOURLY,
|
||||||
_ => "",
|
_ => "",
|
||||||
})
|
}),
|
||||||
.build()
|
..Default::default()
|
||||||
.into_node();
|
}
|
||||||
|
.into_node();
|
||||||
|
|
||||||
let columns_field = columns_section(model, page);
|
let columns_field = columns_section(model, page);
|
||||||
|
|
||||||
let save_button = StyledButton::build()
|
let save_button = StyledButton {
|
||||||
.add_class("actionButton")
|
class_list: "actionButton",
|
||||||
.on_click(mouse_ev(Ev::Click, |ev| {
|
on_click: Some(mouse_ev(Ev::Click, |ev| {
|
||||||
ev.prevent_default();
|
ev.prevent_default();
|
||||||
Msg::PageChanged(PageChanged::ProjectSettings(
|
Msg::PageChanged(PageChanged::ProjectSettings(
|
||||||
ProjectPageChange::SubmitProjectSettingsForm,
|
ProjectPageChange::SubmitProjectSettingsForm,
|
||||||
))
|
))
|
||||||
}))
|
})),
|
||||||
.text("Save changes")
|
text: Some("Save changes"),
|
||||||
.build()
|
..Default::default()
|
||||||
.into_node();
|
}
|
||||||
|
.into_node();
|
||||||
|
|
||||||
let form = StyledForm::build()
|
let form = StyledForm::build()
|
||||||
.heading("Project Details")
|
.heading("Project Details")
|
||||||
@ -110,13 +116,15 @@ pub fn view(model: &model::Model) -> Node<Msg> {
|
|||||||
|
|
||||||
/// Build project name input with styled field wrapper
|
/// Build project name input with styled field wrapper
|
||||||
fn name_field(page: &ProjectSettingsPage) -> Node<Msg> {
|
fn name_field(page: &ProjectSettingsPage) -> Node<Msg> {
|
||||||
let name = StyledTextarea::build(FieldId::ProjectSettings(ProjectFieldId::Name))
|
let name = StyledTextarea {
|
||||||
.value(page.payload.name.as_deref().unwrap_or_default())
|
id: Some(FieldId::ProjectSettings(ProjectFieldId::Name)),
|
||||||
.height(39)
|
value: page.payload.name.as_deref().unwrap_or_default(),
|
||||||
.max_height(39)
|
height: 39,
|
||||||
.disable_auto_resize()
|
max_height: 39,
|
||||||
.build()
|
disable_auto_resize: true,
|
||||||
.into_node();
|
..Default::default()
|
||||||
|
}
|
||||||
|
.into_node();
|
||||||
StyledField::build()
|
StyledField::build()
|
||||||
.label("Name")
|
.label("Name")
|
||||||
.input(name)
|
.input(name)
|
||||||
@ -127,13 +135,15 @@ fn name_field(page: &ProjectSettingsPage) -> Node<Msg> {
|
|||||||
|
|
||||||
/// Build project url input with styled field wrapper
|
/// Build project url input with styled field wrapper
|
||||||
fn url_field(page: &ProjectSettingsPage) -> Node<Msg> {
|
fn url_field(page: &ProjectSettingsPage) -> Node<Msg> {
|
||||||
let url = StyledTextarea::build(FieldId::ProjectSettings(ProjectFieldId::Url))
|
let url = StyledTextarea {
|
||||||
.height(39)
|
id: Some(FieldId::ProjectSettings(ProjectFieldId::Url)),
|
||||||
.max_height(39)
|
height: 39,
|
||||||
.disable_auto_resize()
|
max_height: 39,
|
||||||
.value(page.payload.url.as_deref().unwrap_or_default())
|
disable_auto_resize: true,
|
||||||
.build()
|
value: page.payload.url.as_deref().unwrap_or_default(),
|
||||||
.into_node();
|
..Default::default()
|
||||||
|
}
|
||||||
|
.into_node();
|
||||||
StyledField::build()
|
StyledField::build()
|
||||||
.label("Url")
|
.label("Url")
|
||||||
.input(url)
|
.input(url)
|
||||||
@ -144,52 +154,53 @@ fn url_field(page: &ProjectSettingsPage) -> Node<Msg> {
|
|||||||
|
|
||||||
/// Build project description text area with styled field wrapper
|
/// Build project description text area with styled field wrapper
|
||||||
fn description_field(page: &ProjectSettingsPage) -> Node<Msg> {
|
fn description_field(page: &ProjectSettingsPage) -> Node<Msg> {
|
||||||
let description = StyledEditor::build(FieldId::ProjectSettings(ProjectFieldId::Description))
|
let description = StyledEditor {
|
||||||
.text(
|
id: Some(FieldId::ProjectSettings(ProjectFieldId::Description)),
|
||||||
page.payload
|
initial_text: page.payload.description.as_deref().unwrap_or_default(),
|
||||||
.description
|
text: page.payload.description.as_deref().unwrap_or_default(),
|
||||||
.as_ref()
|
html: page.payload.description.as_deref().unwrap_or_default(),
|
||||||
.cloned()
|
mode: page.description_mode.clone(),
|
||||||
.unwrap_or_default(),
|
update_event: Ev::Change,
|
||||||
)
|
}
|
||||||
.update_on(Ev::Change)
|
.into_node();
|
||||||
.mode(page.description_mode.clone())
|
StyledField {
|
||||||
.build()
|
label: "Description",
|
||||||
.into_node();
|
tip: Some("Describe the project in as much detail as you'd like."),
|
||||||
StyledField::build()
|
input: description,
|
||||||
.input(description)
|
..Default::default()
|
||||||
.label("Description")
|
}
|
||||||
.tip("Describe the project in as much detail as you'd like.")
|
.into_node()
|
||||||
.build()
|
|
||||||
.into_node()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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 category = StyledSelect::build()
|
let category = StyledSelect {
|
||||||
.opened(page.project_category_state.opened)
|
id: FieldId::ProjectSettings(ProjectFieldId::Category),
|
||||||
.text_filter(page.project_category_state.text_filter.as_str())
|
opened: page.project_category_state.opened,
|
||||||
.valid(true)
|
text_filter: page.project_category_state.text_filter.as_str(),
|
||||||
.normal()
|
valid: true,
|
||||||
.options(
|
variant: SelectVariant::Normal,
|
||||||
|
options: Some(
|
||||||
ProjectCategory::default()
|
ProjectCategory::default()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|c| c.into_child()),
|
.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()
|
||||||
.into_child()])
|
.into_child()],
|
||||||
.build(FieldId::ProjectSettings(ProjectFieldId::Category))
|
..Default::default()
|
||||||
.into_node();
|
}
|
||||||
StyledField::build()
|
.into_node();
|
||||||
.label("Project Category")
|
StyledField {
|
||||||
.input(category)
|
input: category,
|
||||||
.build()
|
label: "Project Category",
|
||||||
.into_node()
|
..Default::default()
|
||||||
|
}
|
||||||
|
.into_node()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Build draggable columns preview with option to remove and add new columns
|
/// Build draggable columns preview with option to remove and add new columns
|
||||||
@ -216,13 +227,13 @@ fn columns_section(model: &Model, page: &ProjectSettingsPage) -> Node<Msg> {
|
|||||||
add_column(page, column_style.as_str())
|
add_column(page, column_style.as_str())
|
||||||
]
|
]
|
||||||
];
|
];
|
||||||
StyledField::build()
|
StyledField {
|
||||||
.add_class("columnsField")
|
label: "Columns",
|
||||||
.input(columns_section)
|
tip: Some("Double-click on name to change it."),
|
||||||
.label("Columns")
|
input: columns_section,
|
||||||
.tip("Double-click on name to change it.")
|
class_list: "columnsField",
|
||||||
.build()
|
}
|
||||||
.into_node()
|
.into_node()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
@ -246,20 +257,23 @@ fn add_column(page: &ProjectSettingsPage, column_style: &str) -> Node<Msg> {
|
|||||||
)))
|
)))
|
||||||
});
|
});
|
||||||
|
|
||||||
let input = StyledInput::build()
|
let input = StyledInput {
|
||||||
.state(&page.name)
|
value: page.name.value.as_str(),
|
||||||
.primary()
|
valid: page.name.is_valid(),
|
||||||
.auto_focus()
|
auto_focus: true,
|
||||||
.on_input_ev(blur)
|
variant: InputVariant::Primary,
|
||||||
.build(FieldId::ProjectSettings(ProjectFieldId::IssueStatusName))
|
id: Some(FieldId::ProjectSettings(ProjectFieldId::IssueStatusName)),
|
||||||
.into_node();
|
input_handlers: vec![blur],
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
.into_node();
|
||||||
|
|
||||||
div![
|
div![
|
||||||
C!["columnPreview"],
|
C!["columnPreview"],
|
||||||
div![C!["columnName"], form![on_submit, input]]
|
div![C!["columnName"], form![on_submit, input]]
|
||||||
]
|
]
|
||||||
} else {
|
} else {
|
||||||
let add_column = StyledIcon::build(Icon::Plus).build().into_node();
|
let add_column = StyledIcon::from(Icon::Plus).into_node();
|
||||||
div![
|
div![
|
||||||
C!["columnPreview"],
|
C!["columnPreview"],
|
||||||
attrs![At::Style => column_style],
|
attrs![At::Style => column_style],
|
||||||
@ -282,13 +296,16 @@ fn column_preview(
|
|||||||
ProjectPageChange::EditIssueStatusName(None),
|
ProjectPageChange::EditIssueStatusName(None),
|
||||||
))
|
))
|
||||||
});
|
});
|
||||||
let input = StyledInput::build()
|
let input = StyledInput {
|
||||||
.state(&page.name)
|
value: page.name.value.as_str(),
|
||||||
.primary()
|
valid: page.name.is_valid(),
|
||||||
.auto_focus()
|
variant: InputVariant::Primary,
|
||||||
.on_input_ev(blur)
|
auto_focus: true,
|
||||||
.build(FieldId::ProjectSettings(ProjectFieldId::IssueStatusName))
|
input_handlers: vec![blur],
|
||||||
.into_node();
|
id: Some(FieldId::ProjectSettings(ProjectFieldId::IssueStatusName)),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
.into_node();
|
||||||
|
|
||||||
div![C!["columnPreview"], div![C!["columnName"], input]]
|
div![C!["columnPreview"], div![C!["columnName"], input]]
|
||||||
} else {
|
} else {
|
||||||
@ -337,13 +354,14 @@ fn show_column_preview(
|
|||||||
ev.stop_propagation();
|
ev.stop_propagation();
|
||||||
Msg::ModalOpened(ModalType::DeleteIssueStatusModal(Some(id)))
|
Msg::ModalOpened(ModalType::DeleteIssueStatusModal(Some(id)))
|
||||||
});
|
});
|
||||||
let delete = StyledButton::build()
|
let delete = StyledButton {
|
||||||
.primary()
|
variant: ButtonVariant::Primary,
|
||||||
.add_class("removeColumn")
|
class_list: "removeColumn",
|
||||||
.icon(Icon::Trash)
|
icon: Some(Icon::Trash.into_node()),
|
||||||
.on_click(on_delete)
|
on_click: Some(on_delete),
|
||||||
.build()
|
..Default::default()
|
||||||
.into_node();
|
}
|
||||||
|
.into_node();
|
||||||
div![C!["removeColumn"], delete]
|
div![C!["removeColumn"], delete]
|
||||||
} else {
|
} else {
|
||||||
div![
|
div![
|
||||||
|
@ -12,6 +12,8 @@ use {
|
|||||||
std::collections::HashMap,
|
std::collections::HashMap,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::components::styled_icon::Icon;
|
||||||
|
|
||||||
const SVG_MARGIN_X: u32 = 10;
|
const SVG_MARGIN_X: u32 = 10;
|
||||||
const SVG_DRAWABLE_HEIGHT: u32 = 300;
|
const SVG_DRAWABLE_HEIGHT: u32 = 300;
|
||||||
const SVG_HEIGHT: u32 = SVG_DRAWABLE_HEIGHT + 30;
|
const SVG_HEIGHT: u32 = SVG_DRAWABLE_HEIGHT + 30;
|
||||||
@ -200,27 +202,26 @@ fn issue_list(page: &ReportsPage, project_name: &str, this_month_updated: &[&Iss
|
|||||||
} = issue;
|
} = issue;
|
||||||
let day = date.format("%Y-%m-%d").to_string();
|
let day = date.format("%Y-%m-%d").to_string();
|
||||||
|
|
||||||
let type_icon = StyledIcon::build(issue_type.clone().into())
|
let type_icon = StyledIcon::from(Icon::from(issue_type.clone()))
|
||||||
.build()
|
|
||||||
.into_node();
|
.into_node();
|
||||||
let priority_icon = StyledIcon::build(priority.clone().into())
|
let priority_icon = StyledIcon::from(Icon::from(priority.clone()))
|
||||||
.build()
|
|
||||||
.into_node();
|
.into_node();
|
||||||
let desc = Node::from_html(None,
|
let desc = Node::from_html(None,
|
||||||
description
|
description
|
||||||
.as_deref()
|
.as_deref()
|
||||||
.unwrap_or_default()
|
.unwrap_or_default(),
|
||||||
);
|
);
|
||||||
let link = StyledLink::build()
|
let link = StyledLink {
|
||||||
.with_icon()
|
children: vec![
|
||||||
.text(format!("{}-{}", project_name, id).as_str())
|
Icon::Link.into_node(),
|
||||||
.href(format!("/issues/{}", id).as_str())
|
span![format!("{}-{}", project_name, id).as_str()]
|
||||||
.build()
|
],
|
||||||
.into_node();
|
class_list: "withIcon",
|
||||||
|
href: format!("/issues/{}", id).as_str(),
|
||||||
|
}.into_node();
|
||||||
|
|
||||||
li![
|
li![
|
||||||
C!["issue"],
|
C!["issue", selection_state.to_str()],
|
||||||
C![selection_state.to_str()],
|
|
||||||
div![C!["number"], link],
|
div![C!["number"], link],
|
||||||
div![C!["type"], type_icon],
|
div![C!["type"], type_icon],
|
||||||
IF!( selection_state != SelectionState::NotSelected => div![C!["priority"], priority_icon]),
|
IF!( selection_state != SelectionState::NotSelected => div![C!["priority"], priority_icon]),
|
||||||
|
@ -16,28 +16,34 @@ use {
|
|||||||
seed::{prelude::*, *},
|
seed::{prelude::*, *},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::components::styled_button::ButtonVariant;
|
||||||
|
|
||||||
pub fn view(model: &model::Model) -> Node<Msg> {
|
pub fn view(model: &model::Model) -> Node<Msg> {
|
||||||
let page = match &model.page_content {
|
let page = match &model.page_content {
|
||||||
PageContent::SignIn(page) => page,
|
PageContent::SignIn(page) => page,
|
||||||
_ => return empty![],
|
_ => return empty![],
|
||||||
};
|
};
|
||||||
|
|
||||||
let username = StyledInput::build()
|
let username = StyledInput {
|
||||||
.value(page.username.as_str())
|
value: page.username.as_str(),
|
||||||
.valid(is_valid_username(page.username_touched, &page.username))
|
valid: is_valid_username(page.username_touched, &page.username),
|
||||||
.build(FieldId::SignIn(SignInFieldId::Username))
|
id: Some(FieldId::SignIn(SignInFieldId::Username)),
|
||||||
.into_node();
|
..Default::default()
|
||||||
|
}
|
||||||
|
.into_node();
|
||||||
let username_field = StyledField::build()
|
let username_field = StyledField::build()
|
||||||
.label("Username")
|
.label("Username")
|
||||||
.input(username)
|
.input(username)
|
||||||
.build()
|
.build()
|
||||||
.into_node();
|
.into_node();
|
||||||
|
|
||||||
let email = StyledInput::build()
|
let email = StyledInput {
|
||||||
.value(page.email.as_str())
|
value: page.email.as_str(),
|
||||||
.valid(is_valid_email(page.email_touched, page.email.as_str()))
|
valid: is_valid_email(page.email_touched, page.email.as_str()),
|
||||||
.build(FieldId::SignIn(SignInFieldId::Email))
|
id: Some(FieldId::SignIn(SignInFieldId::Email)),
|
||||||
.into_node();
|
..Default::default()
|
||||||
|
}
|
||||||
|
.into_node();
|
||||||
let email_field = StyledField::build()
|
let email_field = StyledField::build()
|
||||||
.label("E-Mail")
|
.label("E-Mail")
|
||||||
.input(email)
|
.input(email)
|
||||||
@ -45,33 +51,39 @@ pub fn view(model: &model::Model) -> Node<Msg> {
|
|||||||
.into_node();
|
.into_node();
|
||||||
|
|
||||||
let submit = if page.login_success {
|
let submit = if page.login_success {
|
||||||
StyledButton::build()
|
StyledButton {
|
||||||
.success()
|
variant: ButtonVariant::Success,
|
||||||
.text("✓ Please check your mail")
|
text: Some("✓ Please check your mail"),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
StyledButton::build()
|
StyledButton {
|
||||||
.primary()
|
variant: ButtonVariant::Primary,
|
||||||
.text("Sign In")
|
text: Some("Sign In"),
|
||||||
.on_click(mouse_ev(Ev::Click, |_| Msg::SignInRequest))
|
on_click: Some(mouse_ev(Ev::Click, |_| Msg::SignInRequest)),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.into_node();
|
||||||
|
let register_link = StyledLink {
|
||||||
|
children: vec![span!["Register"]],
|
||||||
|
class_list: "signUpLink",
|
||||||
|
href: "/register",
|
||||||
|
}
|
||||||
|
.into_node();
|
||||||
|
let submit_field = StyledField {
|
||||||
|
input: div![C!["twoRow"], submit, register_link],
|
||||||
|
..Default::default()
|
||||||
}
|
}
|
||||||
.build()
|
|
||||||
.into_node();
|
.into_node();
|
||||||
let register_link = StyledLink::build()
|
|
||||||
.text("Register")
|
|
||||||
.href("/register")
|
|
||||||
.add_class("signUpLink")
|
|
||||||
.build()
|
|
||||||
.into_node();
|
|
||||||
let submit_field = StyledField::build()
|
|
||||||
.input(div![C!["twoRow"], submit, register_link,])
|
|
||||||
.build()
|
|
||||||
.into_node();
|
|
||||||
|
|
||||||
let help_icon = StyledIcon::build(Icon::Help)
|
let help_icon = StyledIcon {
|
||||||
.add_class("noPasswordHelp")
|
icon: Icon::Help,
|
||||||
.size(22)
|
class_list: "noPasswordHelp",
|
||||||
.build()
|
size: Some(22),
|
||||||
.into_node();
|
..Default::default()
|
||||||
|
}
|
||||||
|
.into_node();
|
||||||
|
|
||||||
let no_pass_section = div![
|
let no_pass_section = div![
|
||||||
C!["noPasswordSection"],
|
C!["noPasswordSection"],
|
||||||
@ -80,19 +92,16 @@ pub fn view(model: &model::Model) -> Node<Msg> {
|
|||||||
span!["Why I don't see password?"]
|
span!["Why I don't see password?"]
|
||||||
];
|
];
|
||||||
|
|
||||||
let sign_in_form = StyledForm::build()
|
let sign_in_form = StyledForm {
|
||||||
.heading("Sign In to your account")
|
heading: "Sign In to your account",
|
||||||
.on_submit(ev(Ev::Submit, |ev| {
|
fields: vec![username_field, email_field, submit_field, no_pass_section],
|
||||||
|
on_submit: Some(ev(Ev::Submit, |ev| {
|
||||||
ev.stop_propagation();
|
ev.stop_propagation();
|
||||||
ev.prevent_default();
|
ev.prevent_default();
|
||||||
Msg::SignInRequest
|
Msg::SignInRequest
|
||||||
}))
|
})),
|
||||||
.add_field(username_field)
|
}
|
||||||
.add_field(email_field)
|
.into_node();
|
||||||
.add_field(submit_field)
|
|
||||||
.add_field(no_pass_section)
|
|
||||||
.build()
|
|
||||||
.into_node();
|
|
||||||
|
|
||||||
let token = StyledInput::new_with_id_and_value_and_valid(
|
let token = StyledInput::new_with_id_and_value_and_valid(
|
||||||
FieldId::SignIn(SignInFieldId::Token),
|
FieldId::SignIn(SignInFieldId::Token),
|
||||||
@ -105,12 +114,13 @@ pub fn view(model: &model::Model) -> Node<Msg> {
|
|||||||
.input(token)
|
.input(token)
|
||||||
.build()
|
.build()
|
||||||
.into_node();
|
.into_node();
|
||||||
let submit_token = StyledButton::build()
|
let submit_token = StyledButton {
|
||||||
.primary()
|
variant: ButtonVariant::Primary,
|
||||||
.text("Authorize")
|
text: Some("Authorize"),
|
||||||
.on_click(mouse_ev(Ev::Click, |_| Msg::BindClientRequest))
|
on_click: Some(mouse_ev(Ev::Click, |_| Msg::BindClientRequest)),
|
||||||
.build()
|
..Default::default()
|
||||||
.into_node();
|
}
|
||||||
|
.into_node();
|
||||||
let submit_token_field = StyledField::build().input(submit_token).build().into_node();
|
let submit_token_field = StyledField::build().input(submit_token).build().into_node();
|
||||||
|
|
||||||
let bind_token_form = StyledForm::build()
|
let bind_token_form = StyledForm::build()
|
||||||
|
@ -8,6 +8,7 @@ use {
|
|||||||
styled_input::StyledInput,
|
styled_input::StyledInput,
|
||||||
styled_link::StyledLink,
|
styled_link::StyledLink,
|
||||||
},
|
},
|
||||||
|
match_page,
|
||||||
model::{self, PageContent},
|
model::{self, PageContent},
|
||||||
shared::{outer_layout, ToNode},
|
shared::{outer_layout, ToNode},
|
||||||
validations::is_email,
|
validations::is_email,
|
||||||
@ -17,64 +18,75 @@ use {
|
|||||||
seed::{prelude::*, *},
|
seed::{prelude::*, *},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::components::styled_button::ButtonVariant;
|
||||||
|
|
||||||
pub fn view(model: &model::Model) -> Node<Msg> {
|
pub fn view(model: &model::Model) -> Node<Msg> {
|
||||||
let page = match &model.page_content {
|
let page = match_page!(model, SignUp; Empty);
|
||||||
PageContent::SignUp(page) => page,
|
|
||||||
_ => return empty![],
|
|
||||||
};
|
|
||||||
|
|
||||||
let username = StyledInput::build()
|
let username = StyledInput {
|
||||||
.value(page.username.as_str())
|
value: page.username.as_str(),
|
||||||
.valid(!page.username_touched || page.username.len() > 1)
|
valid: !page.username_touched || page.username.len() > 1,
|
||||||
.build(FieldId::SignUp(SignUpFieldId::Username))
|
id: Some(FieldId::SignUp(SignUpFieldId::Username)),
|
||||||
.into_node();
|
..Default::default()
|
||||||
let username_field = StyledField::build()
|
}
|
||||||
.label("Username")
|
.into_node();
|
||||||
.input(username)
|
let username_field = StyledField {
|
||||||
.build()
|
label: "Username",
|
||||||
.into_node();
|
input: username,
|
||||||
|
..Default::default()
|
||||||
let email = StyledInput::build()
|
|
||||||
.value(page.email.as_str())
|
|
||||||
.valid(!page.email_touched || is_email(page.email.as_str()))
|
|
||||||
.build(FieldId::SignUp(SignUpFieldId::Email))
|
|
||||||
.into_node();
|
|
||||||
let email_field = StyledField::build()
|
|
||||||
.label("E-Mail")
|
|
||||||
.input(email)
|
|
||||||
.build()
|
|
||||||
.into_node();
|
|
||||||
|
|
||||||
let submit = if page.sign_up_success {
|
|
||||||
StyledButton::build()
|
|
||||||
.success()
|
|
||||||
.text("✓ Please check your mail")
|
|
||||||
} else {
|
|
||||||
StyledButton::build()
|
|
||||||
.primary()
|
|
||||||
.text("Register")
|
|
||||||
.on_click(mouse_ev(Ev::Click, |_| Msg::SignUpRequest))
|
|
||||||
}
|
}
|
||||||
.build()
|
|
||||||
.into_node();
|
.into_node();
|
||||||
|
|
||||||
let sign_in_link = StyledLink::build()
|
let email = StyledInput {
|
||||||
.text("Sign In")
|
value: page.email.as_str(),
|
||||||
.href("/login")
|
valid: !page.email_touched || is_email(page.email.as_str()),
|
||||||
.add_class("signInLink")
|
id: Some(FieldId::SignUp(SignUpFieldId::Email)),
|
||||||
.build()
|
..Default::default()
|
||||||
.into_node();
|
}
|
||||||
|
.into_node();
|
||||||
|
let email_field = StyledField {
|
||||||
|
label: "E-Mail",
|
||||||
|
input: email,
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
.into_node();
|
||||||
|
|
||||||
let submit_field = StyledField::build()
|
let submit = if page.sign_up_success {
|
||||||
.input(div![C!["twoRow"], submit, sign_in_link,])
|
StyledButton {
|
||||||
.build()
|
variant: ButtonVariant::Success,
|
||||||
.into_node();
|
text: Some("✓ Please check your mail"),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
StyledButton {
|
||||||
|
variant: ButtonVariant::Primary,
|
||||||
|
text: Some("Register"),
|
||||||
|
on_click: Some(mouse_ev(Ev::Click, |_| Msg::SignUpRequest)),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.into_node();
|
||||||
|
|
||||||
let help_icon = StyledIcon::build(Icon::Help)
|
let sign_in_link = StyledLink {
|
||||||
.add_class("noPasswordHelp")
|
children: vec![span!["Sign In"]],
|
||||||
.size(22)
|
class_list: "signInLink",
|
||||||
.build()
|
href: "/login",
|
||||||
.into_node();
|
}
|
||||||
|
.into_node();
|
||||||
|
|
||||||
|
let submit_field = StyledField {
|
||||||
|
input: div![C!["twoRow"], submit, sign_in_link],
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
.into_node();
|
||||||
|
|
||||||
|
let help_icon = StyledIcon {
|
||||||
|
icon: Icon::Help,
|
||||||
|
class_list: "noPasswordHelp",
|
||||||
|
size: Some(22),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
.into_node();
|
||||||
|
|
||||||
let no_pass_section = div![
|
let no_pass_section = div![
|
||||||
C!["noPasswordSection"],
|
C!["noPasswordSection"],
|
||||||
|
@ -7,13 +7,17 @@ use {
|
|||||||
FieldId, Msg, PageChanged, UsersPageChange, WebSocketChanged,
|
FieldId, Msg, PageChanged, UsersPageChange, WebSocketChanged,
|
||||||
},
|
},
|
||||||
jirs_data::{InvitationState, UserRole, UsersFieldId, WsMsg},
|
jirs_data::{InvitationState, UserRole, UsersFieldId, WsMsg},
|
||||||
seed::prelude::Orders,
|
seed::{log, prelude::Orders},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
pub fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
||||||
|
log!(model);
|
||||||
|
if model.user.is_none() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if let Msg::ChangePage(Page::Users) = msg {
|
if let Msg::ChangePage(Page::Users) = msg {
|
||||||
build_page_content(model);
|
build_page_content(model);
|
||||||
// return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let page = match &mut model.page_content {
|
let page = match &mut model.page_content {
|
||||||
|
@ -13,76 +13,89 @@ use {
|
|||||||
seed::{prelude::*, *},
|
seed::{prelude::*, *},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn view(model: &Model) -> Node<Msg> {
|
use crate::components::styled_button::ButtonVariant;
|
||||||
if model.user.is_none() {
|
use crate::components::styled_input::InputVariant;
|
||||||
return empty![];
|
use crate::components::styled_select::SelectVariant;
|
||||||
}
|
|
||||||
|
|
||||||
|
pub fn view(model: &Model) -> Node<Msg> {
|
||||||
let page = match &model.page_content {
|
let page = match &model.page_content {
|
||||||
PageContent::Users(page) => page,
|
PageContent::Users(page) => page,
|
||||||
_ => return empty![],
|
_ => return empty![],
|
||||||
};
|
};
|
||||||
|
|
||||||
let name = StyledInput::build()
|
let name = StyledInput {
|
||||||
.valid(!page.name_touched || page.name.len() >= 3)
|
valid: !page.name_touched || page.name.len() >= 3,
|
||||||
.value(page.name.as_str())
|
value: page.name.as_str(),
|
||||||
.build(FieldId::Users(UsersFieldId::Username))
|
variant: InputVariant::Normal,
|
||||||
.into_node();
|
id: Some(FieldId::Users(UsersFieldId::Username)),
|
||||||
let name_field = StyledField::build()
|
..Default::default()
|
||||||
.input(name)
|
}
|
||||||
.label("Name")
|
.into_node();
|
||||||
.build()
|
let name_field = StyledField {
|
||||||
.into_node();
|
label: "Name",
|
||||||
|
input: name,
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
.into_node();
|
||||||
|
|
||||||
let email = StyledInput::build()
|
let email = StyledInput {
|
||||||
.valid(!page.email_touched || is_email(page.email.as_str()))
|
id: Some(FieldId::Users(UsersFieldId::Email)),
|
||||||
.value(page.email.as_str())
|
valid: !page.email_touched || is_email(page.email.as_str()),
|
||||||
.build(FieldId::Users(UsersFieldId::Email))
|
value: page.email.as_str(),
|
||||||
.into_node();
|
variant: InputVariant::Normal,
|
||||||
let email_field = StyledField::build()
|
..Default::default()
|
||||||
.input(email)
|
}
|
||||||
.label("E-Mail")
|
.into_node();
|
||||||
.build()
|
let email_field = StyledField {
|
||||||
.into_node();
|
input: email,
|
||||||
|
label: "E-Mail",
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
.into_node();
|
||||||
|
|
||||||
let user_role = StyledSelect::build()
|
let user_role = StyledSelect {
|
||||||
.name("user_role")
|
id: FieldId::Users(UsersFieldId::UserRole),
|
||||||
.valid(true)
|
name: "user_role",
|
||||||
.normal()
|
valid: true,
|
||||||
.state(&page.user_role_state)
|
variant: SelectVariant::Normal,
|
||||||
.selected(vec![page.user_role.into_child()])
|
text_filter: page.user_role_state.text_filter.as_str(),
|
||||||
.options(
|
opened: page.user_role_state.opened,
|
||||||
|
selected: vec![page.user_role.into_child()],
|
||||||
|
options: Some(
|
||||||
UserRole::default()
|
UserRole::default()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|role| role.into_child()),
|
.map(|role| role.into_child()),
|
||||||
)
|
),
|
||||||
.build(FieldId::Users(UsersFieldId::UserRole))
|
..Default::default()
|
||||||
.into_node();
|
}
|
||||||
|
.into_node();
|
||||||
let user_role_field = StyledField::build()
|
let user_role_field = StyledField::build()
|
||||||
.input(user_role)
|
.input(user_role)
|
||||||
.label("Role")
|
.label("Role")
|
||||||
.build()
|
.build()
|
||||||
.into_node();
|
.into_node();
|
||||||
|
|
||||||
let submit = StyledButton::build()
|
let submit = StyledButton {
|
||||||
.add_class("submitUserInvite")
|
text: Some("Invite user"),
|
||||||
.active(page.form_state != InvitationFormState::Sent)
|
variant: ButtonVariant::Primary,
|
||||||
.primary()
|
class_list: "submitUserInvite",
|
||||||
.text("Invite user")
|
active: page.form_state != InvitationFormState::Sent,
|
||||||
.build()
|
..Default::default()
|
||||||
.into_node();
|
}
|
||||||
|
.into_node();
|
||||||
let submit_supplement = match page.form_state {
|
let submit_supplement = match page.form_state {
|
||||||
InvitationFormState::Succeed => StyledButton::build()
|
InvitationFormState::Succeed => StyledButton {
|
||||||
.add_class("resetUserInvite")
|
variant: ButtonVariant::Empty,
|
||||||
.active(true)
|
class_list: "resetUserInvite",
|
||||||
.empty()
|
active: true,
|
||||||
.set_type_reset()
|
on_click: Some(mouse_ev(Ev::Click, |_| {
|
||||||
.on_click(mouse_ev(Ev::Click, |_| {
|
|
||||||
Msg::PageChanged(PageChanged::Users(UsersPageChange::ResetForm))
|
Msg::PageChanged(PageChanged::Users(UsersPageChange::ResetForm))
|
||||||
}))
|
})),
|
||||||
.text("Reset")
|
text: Some("Reset"),
|
||||||
.build()
|
button_type: "reset",
|
||||||
.into_node(),
|
..Default::default()
|
||||||
|
}
|
||||||
|
.into_node(),
|
||||||
InvitationFormState::Failed => div![C!["error"], "There was an error"],
|
InvitationFormState::Failed => div![C!["error"], "There was an error"],
|
||||||
_ => empty![],
|
_ => empty![],
|
||||||
};
|
};
|
||||||
@ -109,13 +122,14 @@ pub fn view(model: &Model) -> Node<Msg> {
|
|||||||
.iter()
|
.iter()
|
||||||
.map(|user| {
|
.map(|user| {
|
||||||
let user_id = user.id;
|
let user_id = user.id;
|
||||||
let remove = StyledButton::build()
|
let remove = StyledButton {
|
||||||
.text("Remove")
|
text: Some("Remove"),
|
||||||
.on_click(mouse_ev(Ev::Click, move |_| {
|
on_click: Some(mouse_ev(Ev::Click, move |_| {
|
||||||
Msg::InvitedUserRemove(user_id)
|
Msg::InvitedUserRemove(user_id)
|
||||||
}))
|
})),
|
||||||
.build()
|
..Default::default()
|
||||||
.into_node();
|
}
|
||||||
|
.into_node();
|
||||||
let role = page
|
let role = page
|
||||||
.invitations
|
.invitations
|
||||||
.iter()
|
.iter()
|
||||||
@ -144,15 +158,21 @@ pub fn view(model: &Model) -> Node<Msg> {
|
|||||||
.iter()
|
.iter()
|
||||||
.map(|invitation| {
|
.map(|invitation| {
|
||||||
let id = invitation.id;
|
let id = invitation.id;
|
||||||
let revoke = StyledButton::build()
|
let revoke = StyledButton {
|
||||||
.text("Revoke")
|
disabled: invitation.state == InvitationState::Revoked,
|
||||||
.disabled(invitation.state == InvitationState::Revoked)
|
text: Some("Revoke"),
|
||||||
.on_click(mouse_ev(Ev::Click, move |_| Msg::InviteRevokeRequest(id)))
|
on_click: Some(mouse_ev(Ev::Click, move |_| Msg::InviteRevokeRequest(id))),
|
||||||
.build()
|
..Default::default()
|
||||||
.into_node();
|
}
|
||||||
|
.into_node();
|
||||||
|
// let revoke = StyledButton::build()
|
||||||
|
// .text("Revoke")
|
||||||
|
// .disabled(invitation.state == InvitationState::Revoked)
|
||||||
|
// .on_click(mouse_ev(Ev::Click, move |_| Msg::InviteRevokeRequest(id)))
|
||||||
|
// .build()
|
||||||
|
// .into_node();
|
||||||
li![
|
li![
|
||||||
C!["invitation"],
|
C!["invitation", format!("{}", invitation.state)],
|
||||||
attrs![At::Class => format!("{}", invitation.state)],
|
|
||||||
span![invitation.name.as_str()],
|
span![invitation.name.as_str()],
|
||||||
span![invitation.email.as_str()],
|
span![invitation.email.as_str()],
|
||||||
span![format!("{}", invitation.state)],
|
span![format!("{}", invitation.state)],
|
||||||
|
@ -98,7 +98,7 @@ fn sidebar_link_item(model: &Model, name: &str, icon: Icon, page: Option<Page>)
|
|||||||
None
|
None
|
||||||
};
|
};
|
||||||
let active_flag = page.filter(|p| *p == model.page).map(|_| C!["active"]);
|
let active_flag = page.filter(|p| *p == model.page).map(|_| C!["active"]);
|
||||||
let icon_node = StyledIcon::build(icon).build().into_node();
|
let icon_node = StyledIcon::from(icon).into_node();
|
||||||
let on_click = page.map(|p| {
|
let on_click = page.map(|p| {
|
||||||
mouse_ev("click", move |ev| {
|
mouse_ev("click", move |ev| {
|
||||||
ev.stop_propagation();
|
ev.stop_propagation();
|
||||||
@ -111,10 +111,9 @@ fn sidebar_link_item(model: &Model, name: &str, icon: Icon, page: Option<Page>)
|
|||||||
});
|
});
|
||||||
|
|
||||||
li![
|
li![
|
||||||
C!["linkItem"],
|
C!["linkItem", icon.to_str()],
|
||||||
active_flag,
|
active_flag,
|
||||||
allow_flag,
|
allow_flag,
|
||||||
C![icon.to_str()],
|
|
||||||
a![
|
a![
|
||||||
attrs![At::Href => path],
|
attrs![At::Href => path],
|
||||||
on_click,
|
on_click,
|
||||||
|
@ -15,6 +15,9 @@ use {
|
|||||||
seed::{prelude::*, *},
|
seed::{prelude::*, *},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::components::styled_button::ButtonVariant;
|
||||||
|
use crate::components::styled_tooltip::{StyledTooltip, TooltipVariant};
|
||||||
|
|
||||||
trait IntoNavItemIcon {
|
trait IntoNavItemIcon {
|
||||||
fn into_nav_item_icon(self) -> Node<Msg>;
|
fn into_nav_item_icon(self) -> Node<Msg>;
|
||||||
}
|
}
|
||||||
@ -27,7 +30,12 @@ impl IntoNavItemIcon for Node<Msg> {
|
|||||||
|
|
||||||
impl IntoNavItemIcon for Icon {
|
impl IntoNavItemIcon for Icon {
|
||||||
fn into_nav_item_icon(self) -> Node<Msg> {
|
fn into_nav_item_icon(self) -> Node<Msg> {
|
||||||
StyledIcon::build(self).size(21).build().into_node()
|
StyledIcon {
|
||||||
|
icon: self,
|
||||||
|
size: Some(21),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
.into_node()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -47,16 +55,22 @@ pub fn render(model: &Model) -> Vec<Node<Msg>> {
|
|||||||
];
|
];
|
||||||
|
|
||||||
let user_icon = match model.user.as_ref() {
|
let user_icon = match model.user.as_ref() {
|
||||||
Some(user) => i![
|
Some(user) => {
|
||||||
C!["styledIcon"],
|
let avatar = StyledAvatar {
|
||||||
StyledAvatar::build()
|
avatar_url: user.avatar_url.as_deref(),
|
||||||
.size(27)
|
size: 27,
|
||||||
.name(user.name.as_str())
|
name: &user.name,
|
||||||
.avatar_url(user.avatar_url.as_deref().unwrap_or_default())
|
..StyledAvatar::default()
|
||||||
.build()
|
}
|
||||||
.into_node()
|
.into_node();
|
||||||
],
|
i![C!["styledIcon"], avatar]
|
||||||
_ => StyledIcon::build(Icon::User).size(21).build().into_node(),
|
}
|
||||||
|
_ => StyledIcon {
|
||||||
|
icon: Icon::User,
|
||||||
|
size: Some(21),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
.into_node(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let messages = if model.messages.is_empty() {
|
let messages = if model.messages.is_empty() {
|
||||||
@ -68,7 +82,7 @@ pub fn render(model: &Model) -> Vec<Node<Msg>> {
|
|||||||
None,
|
None,
|
||||||
Some(mouse_ev(Ev::Click, |ev| {
|
Some(mouse_ev(Ev::Click, |ev| {
|
||||||
ev.prevent_default();
|
ev.prevent_default();
|
||||||
Msg::ToggleTooltip(styled_tooltip::Variant::Messages)
|
Msg::ToggleTooltip(styled_tooltip::TooltipVariant::Messages)
|
||||||
})),
|
})),
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
@ -144,14 +158,14 @@ where
|
|||||||
|
|
||||||
pub fn about_tooltip(_model: &Model, children: Node<Msg>) -> Node<Msg> {
|
pub fn about_tooltip(_model: &Model, children: Node<Msg>) -> Node<Msg> {
|
||||||
let on_click: EventHandler<Msg> = ev(Ev::Click, move |_| {
|
let on_click: EventHandler<Msg> = ev(Ev::Click, move |_| {
|
||||||
Some(Msg::ToggleTooltip(styled_tooltip::Variant::About))
|
Some(Msg::ToggleTooltip(styled_tooltip::TooltipVariant::About))
|
||||||
});
|
});
|
||||||
div![C!["aboutTooltip"], on_click, children]
|
div![C!["aboutTooltip"], on_click, children]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn messages_tooltip_popup(model: &Model) -> Node<Msg> {
|
fn messages_tooltip_popup(model: &Model) -> Node<Msg> {
|
||||||
let on_click: EventHandler<Msg> = ev(Ev::Click, move |_| {
|
let on_click: EventHandler<Msg> = ev(Ev::Click, move |_| {
|
||||||
Some(Msg::ToggleTooltip(styled_tooltip::Variant::Messages))
|
Some(Msg::ToggleTooltip(styled_tooltip::TooltipVariant::Messages))
|
||||||
});
|
});
|
||||||
let mut messages: Vec<Node<Msg>> = vec![];
|
let mut messages: Vec<Node<Msg>> = vec![];
|
||||||
for (idx, message) in model.messages.iter().enumerate() {
|
for (idx, message) in model.messages.iter().enumerate() {
|
||||||
@ -163,13 +177,13 @@ fn messages_tooltip_popup(model: &Model) -> Node<Msg> {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
let body = div![on_click, C!["messagesList"], messages];
|
let body = div![on_click, C!["messagesList"], messages];
|
||||||
styled_tooltip::StyledTooltip::build()
|
styled_tooltip::StyledTooltip {
|
||||||
.add_class("messagesPopup")
|
visible: model.messages_tooltip_visible,
|
||||||
.visible(model.messages_tooltip_visible)
|
class_list: "messagesPopup",
|
||||||
.messages_tooltip()
|
children: vec![body],
|
||||||
.add_child(body)
|
variant: TooltipVariant::Messages,
|
||||||
.build()
|
}
|
||||||
.into_node()
|
.into_node()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn message_ui(model: &Model, message: &Message) -> Option<Node<Msg>> {
|
fn message_ui(model: &Model, message: &Message) -> Option<Node<Msg>> {
|
||||||
@ -186,7 +200,7 @@ fn message_ui(model: &Model, message: &Message) -> Option<Node<Msg>> {
|
|||||||
let hyperlink = if hyper_link.is_empty() && !hyper_link.starts_with('#') {
|
let hyperlink = if hyper_link.is_empty() && !hyper_link.starts_with('#') {
|
||||||
empty![]
|
empty![]
|
||||||
} else {
|
} else {
|
||||||
let link_icon = StyledIcon::build(Icon::Link).build().into_node();
|
let link_icon = StyledIcon::from(Icon::Link).into_node();
|
||||||
div![
|
div![
|
||||||
C!["hyperlink"],
|
C!["hyperlink"],
|
||||||
a![
|
a![
|
||||||
@ -199,16 +213,17 @@ fn message_ui(model: &Model, message: &Message) -> Option<Node<Msg>> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let message_description = parse_description(model, description.as_str());
|
let message_description = parse_description(model, description.as_str());
|
||||||
let close_button = StyledButton::build()
|
let close_button = StyledButton {
|
||||||
.icon(Icon::Close)
|
variant: ButtonVariant::Empty,
|
||||||
.empty()
|
icon: Some(Icon::Close.into_node()),
|
||||||
.on_click(mouse_ev(Ev::Click, move |ev| {
|
on_click: Some(mouse_ev(Ev::Click, move |ev| {
|
||||||
ev.stop_propagation();
|
ev.stop_propagation();
|
||||||
ev.prevent_default();
|
ev.prevent_default();
|
||||||
Some(Msg::MessageSeen(message_id))
|
Some(Msg::MessageSeen(message_id))
|
||||||
}))
|
})),
|
||||||
.build()
|
..Default::default()
|
||||||
.into_node();
|
}
|
||||||
|
.into_node();
|
||||||
let top = div![
|
let top = div![
|
||||||
C!["top"],
|
C!["top"],
|
||||||
div![C!["summary"], summary],
|
div![C!["summary"], summary],
|
||||||
@ -218,30 +233,32 @@ fn message_ui(model: &Model, message: &Message) -> Option<Node<Msg>> {
|
|||||||
let node = match message_type {
|
let node = match message_type {
|
||||||
MessageType::ReceivedInvitation => {
|
MessageType::ReceivedInvitation => {
|
||||||
let token: InvitationToken = hyper_link.trim_start_matches('#').parse().ok()?;
|
let token: InvitationToken = hyper_link.trim_start_matches('#').parse().ok()?;
|
||||||
let accept = StyledButton::build()
|
let accept = StyledButton {
|
||||||
.primary()
|
variant: ButtonVariant::Primary,
|
||||||
.text("Accept")
|
active: true,
|
||||||
.active(true)
|
text: Some("Accept"),
|
||||||
.icon(Icon::Check)
|
icon: Some(Icon::Check.into_node()),
|
||||||
.on_click(mouse_ev(Ev::Click, move |ev| {
|
on_click: Some(mouse_ev(Ev::Click, move |ev| {
|
||||||
ev.stop_propagation();
|
ev.stop_propagation();
|
||||||
ev.prevent_default();
|
ev.prevent_default();
|
||||||
Some(Msg::MessageInvitationApproved(token))
|
Some(Msg::MessageInvitationApproved(token))
|
||||||
}))
|
})),
|
||||||
.build()
|
..Default::default()
|
||||||
.into_node();
|
}
|
||||||
let reject = StyledButton::build()
|
.into_node();
|
||||||
.danger()
|
let reject = StyledButton {
|
||||||
.text("Dismiss")
|
variant: ButtonVariant::Danger,
|
||||||
.icon(Icon::Close)
|
active: true,
|
||||||
.on_click(mouse_ev(Ev::Click, move |ev| {
|
text: Some("Dismiss"),
|
||||||
|
icon: Some(Icon::Close.into_node()),
|
||||||
|
on_click: Some(mouse_ev(Ev::Click, move |ev| {
|
||||||
ev.stop_propagation();
|
ev.stop_propagation();
|
||||||
ev.prevent_default();
|
ev.prevent_default();
|
||||||
Some(Msg::MessageInvitationDismiss(token))
|
Some(Msg::MessageInvitationDismiss(token))
|
||||||
}))
|
})),
|
||||||
.active(true)
|
..Default::default()
|
||||||
.build()
|
}
|
||||||
.into_node();
|
.into_node();
|
||||||
div![
|
div![
|
||||||
C!["message"],
|
C!["message"],
|
||||||
attrs![At::Class => format!("{}", message_type)],
|
attrs![At::Class => format!("{}", message_type)],
|
||||||
@ -267,28 +284,29 @@ fn message_ui(model: &Model, message: &Message) -> Option<Node<Msg>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn about_tooltip_popup(model: &Model) -> Node<Msg> {
|
fn about_tooltip_popup(model: &Model) -> Node<Msg> {
|
||||||
let visit_website = StyledButton::build()
|
let visit_website = StyledButton {
|
||||||
.text("Visit Website")
|
variant: ButtonVariant::Primary,
|
||||||
.primary()
|
text: Some("Visit Website"),
|
||||||
.build()
|
..Default::default()
|
||||||
.into_node();
|
}
|
||||||
let github_repo = StyledButton::build()
|
.into_node();
|
||||||
.text("Github Repo")
|
let github_repo = StyledButton {
|
||||||
.secondary()
|
variant: ButtonVariant::Secondary,
|
||||||
.icon(Icon::Github)
|
text: Some("Github Repo"),
|
||||||
.build()
|
icon: Some(Icon::Github.into_node()),
|
||||||
.into_node();
|
..Default::default()
|
||||||
|
}
|
||||||
|
.into_node();
|
||||||
|
|
||||||
let on_click = mouse_ev(Ev::Click, |_| {
|
let on_click = mouse_ev(Ev::Click, |_| {
|
||||||
Msg::ToggleTooltip(styled_tooltip::Variant::About)
|
Msg::ToggleTooltip(styled_tooltip::TooltipVariant::About)
|
||||||
});
|
});
|
||||||
let body = div![
|
let body = div![
|
||||||
on_click,
|
on_click,
|
||||||
C!["feedbackDropdown"],
|
C!["feedbackDropdown"],
|
||||||
div![
|
div![
|
||||||
C!["feedbackImageCont"],
|
C!["feedbackImageCont feedbackImage"],
|
||||||
img![attrs![At::Src => "/feedback.png"]],
|
img![attrs![At::Src => "/feedback.png"]],
|
||||||
C!["feedbackImage"],
|
|
||||||
],
|
],
|
||||||
div![
|
div![
|
||||||
C!["feedbackParagraph"],
|
C!["feedbackParagraph"],
|
||||||
@ -321,13 +339,13 @@ fn about_tooltip_popup(model: &Model) -> Node<Msg> {
|
|||||||
]
|
]
|
||||||
];
|
];
|
||||||
|
|
||||||
styled_tooltip::StyledTooltip::build()
|
StyledTooltip {
|
||||||
.visible(model.about_tooltip_visible)
|
visible: model.about_tooltip_visible,
|
||||||
.about_tooltip()
|
class_list: "aboutTooltipPopup",
|
||||||
.add_class("aboutTooltipPopup")
|
children: vec![body],
|
||||||
.add_child(body)
|
variant: TooltipVariant::About,
|
||||||
.build()
|
}
|
||||||
.into_node()
|
.into_node()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_description(model: &Model, desc: &str) -> Node<Msg> {
|
fn parse_description(model: &Model, desc: &str) -> Node<Msg> {
|
||||||
@ -342,12 +360,13 @@ fn parse_description(model: &Model, desc: &str) -> Node<Msg> {
|
|||||||
.find(|(_, user)| user.email == email)
|
.find(|(_, user)| user.email == email)
|
||||||
})
|
})
|
||||||
.map(|(index, user)| {
|
.map(|(index, user)| {
|
||||||
let avatar = StyledAvatar::build()
|
let avatar = StyledAvatar {
|
||||||
.avatar_url(user.avatar_url.as_deref().unwrap_or_default())
|
avatar_url: user.avatar_url.as_deref(),
|
||||||
.user_index(index)
|
size: 16,
|
||||||
.size(16)
|
user_index: index,
|
||||||
.build()
|
..StyledAvatar::default()
|
||||||
.into_node();
|
}
|
||||||
|
.into_node();
|
||||||
span![C!["mention"], avatar, user.name.as_str()]
|
span![C!["mention"], avatar, user.name.as_str()]
|
||||||
})
|
})
|
||||||
.unwrap_or_else(|| span![word]);
|
.unwrap_or_else(|| span![word]);
|
||||||
|
@ -47,11 +47,13 @@ pub fn tracking_widget(model: &Model, modal: &EditIssueModal) -> Node<Msg> {
|
|||||||
..
|
..
|
||||||
} = modal;
|
} = modal;
|
||||||
|
|
||||||
let icon = StyledIcon::build(Icon::Stopwatch)
|
let icon = StyledIcon {
|
||||||
.add_class("watchIcon")
|
icon: Icon::Stopwatch,
|
||||||
.size(32)
|
class_list: "watchIcon",
|
||||||
.build()
|
size: Some(32),
|
||||||
.into_node();
|
..Default::default()
|
||||||
|
}
|
||||||
|
.into_node();
|
||||||
let bar_width = calc_bar_width(*estimate, *time_spent, *time_remaining);
|
let bar_width = calc_bar_width(*estimate, *time_spent, *time_remaining);
|
||||||
|
|
||||||
let spent_text = match (time_spent, time_tracking_type) {
|
let spent_text = match (time_spent, time_tracking_type) {
|
||||||
|
@ -1,12 +1,7 @@
|
|||||||
const getWsHostName = () => process.env.JIRS_SERVER_BIND === "0.0.0.0" ? 'localhost' : process.env.JIRS_SERVER_BIND;
|
|
||||||
const getProtocol = () => window.location.protocol.replace(/^http/, 'ws');
|
|
||||||
const wsUrl = () => `${getProtocol()}//${getWsHostName()}:${process.env.JIRS_SERVER_PORT}/ws/`;
|
|
||||||
|
|
||||||
import("/jirs.js").then(async module => {
|
import("/jirs.js").then(async module => {
|
||||||
// window.module = module;
|
// window.module = module;
|
||||||
await module.default();
|
await module.default();
|
||||||
const host_url = `${ location.protocol }//${ process.env.JIRS_SERVER_BIND }:${ process.env.JIRS_SERVER_PORT }`;
|
module.render();
|
||||||
module.render(host_url, wsUrl());
|
|
||||||
document.querySelector('main').className = '';
|
document.querySelector('main').className = '';
|
||||||
const spinner = document.querySelector('.spinner');
|
const spinner = document.querySelector('.spinner');
|
||||||
spinner && spinner.remove();
|
spinner && spinner.remove();
|
||||||
|
Loading…
Reference in New Issue
Block a user