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