Add some filters
This commit is contained in:
parent
55a8904210
commit
1f382d9ac6
@ -37,6 +37,33 @@
|
|||||||
width: 160px;
|
width: 160px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#projectPage > #projectBoardFilters > #avatars {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row-reverse;
|
||||||
|
margin: 0 12px 0 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#projectPage > #projectBoardFilters > #avatars > .avatarIsActiveBorder {
|
||||||
|
display: inline-flex;
|
||||||
|
margin-left: -2px;
|
||||||
|
border-radius: 50%;
|
||||||
|
transition: transform 0.1s;
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#projectPage > #projectBoardFilters > #avatars > .avatarIsActiveBorder.isActive {
|
||||||
|
box-shadow: 0 0 0 4px var(--primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
#projectPage > #projectBoardFilters > #avatars > .avatarIsActiveBorder:hover {
|
||||||
|
transform: translateY(-5px);
|
||||||
|
}
|
||||||
|
|
||||||
|
#projectPage > #projectBoardFilters > #avatars > .avatarIsActiveBorder > .styledAvatar {
|
||||||
|
box-shadow: 0 0 0 2px #fff;
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 1100px) {
|
@media (max-width: 1100px) {
|
||||||
#projectPage {
|
#projectPage {
|
||||||
padding: 25px 20px 50px calc(var(--appNavBarLeftWidth) + var(--secondarySideBarWidth) + 20px);
|
padding: 25px 20px 50px calc(var(--appNavBarLeftWidth) + var(--secondarySideBarWidth) + 20px);
|
||||||
|
29
jirs-client/js/css/styledAvatar.css
Normal file
29
jirs-client/js/css/styledAvatar.css
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
.styledAvatar.image {
|
||||||
|
display: inline-block;
|
||||||
|
border-radius: 100%;
|
||||||
|
/*background-image: url("${imageURL}");*/
|
||||||
|
background-position: 50% 50%;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: cover;
|
||||||
|
background-color: var(--backgroundLight);
|
||||||
|
}
|
||||||
|
|
||||||
|
.styledAvatar.letter {
|
||||||
|
display: inline-block;
|
||||||
|
/*width: ${props => props.size} px;*/
|
||||||
|
/*height: ${props => props.size} px;*/
|
||||||
|
border-radius: 100%;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: #fff;
|
||||||
|
/*background: ${props => props.color};*/
|
||||||
|
font-family: var(--font-medium);
|
||||||
|
font-weight: normal;
|
||||||
|
/*${props => font.size(Math.round(props.size / 1.7))}*/
|
||||||
|
}
|
||||||
|
|
||||||
|
.styledAvatar.letter > span {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 100%;
|
||||||
|
}
|
@ -63,3 +63,14 @@
|
|||||||
--font-bold: "CircularStdBold";
|
--font-bold: "CircularStdBold";
|
||||||
--font-black: "CircularStdBlack";
|
--font-black: "CircularStdBlack";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--avatar-color-1: #DA7657;
|
||||||
|
--avatar-color-2: #6ADA57;
|
||||||
|
--avatar-color-3: #5784DA;
|
||||||
|
--avatar-color-4: #AA57DA;
|
||||||
|
--avatar-color-5: #DA5757;
|
||||||
|
--avatar-color-6: #DA5792;
|
||||||
|
--avatar-color-7: #57DACA;
|
||||||
|
--avatar-color-8: #57A5DA;
|
||||||
|
}
|
||||||
|
@ -7,5 +7,6 @@
|
|||||||
@import "css/icon.css";
|
@import "css/icon.css";
|
||||||
@import "css/shared.css";
|
@import "css/shared.css";
|
||||||
@import "css/styledTooltip.css";
|
@import "css/styledTooltip.css";
|
||||||
|
@import "css/styledAvatar.css";
|
||||||
@import "css/app.css";
|
@import "css/app.css";
|
||||||
@import "css/project.css";
|
@import "css/project.css";
|
||||||
|
@ -11,6 +11,9 @@ mod project_settings;
|
|||||||
mod register;
|
mod register;
|
||||||
mod shared;
|
mod shared;
|
||||||
|
|
||||||
|
pub type UserId = i32;
|
||||||
|
pub type AvatarFilterActive = bool;
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum Msg {
|
pub enum Msg {
|
||||||
ChangePage(model::Page),
|
ChangePage(model::Page),
|
||||||
@ -21,6 +24,8 @@ pub enum Msg {
|
|||||||
|
|
||||||
// project
|
// project
|
||||||
ProjectTextFilterChanged(String),
|
ProjectTextFilterChanged(String),
|
||||||
|
ProjectAvatarFilterChanged(UserId, AvatarFilterActive),
|
||||||
|
ProjectToggleOnlyMy,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(msg: Msg, model: &mut model::Model, orders: &mut impl Orders<Msg>) {
|
fn update(msg: Msg, model: &mut model::Model, orders: &mut impl Orders<Msg>) {
|
||||||
|
@ -51,6 +51,8 @@ pub struct UpdateProjectForm {
|
|||||||
pub struct ProjectPage {
|
pub struct ProjectPage {
|
||||||
pub about_tooltip_visible: bool,
|
pub about_tooltip_visible: bool,
|
||||||
pub text_filter: String,
|
pub text_filter: String,
|
||||||
|
pub active_avatar_filters: Vec<i32>,
|
||||||
|
pub only_my_filter: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
@ -85,6 +87,8 @@ impl Default for Model {
|
|||||||
project_page: ProjectPage {
|
project_page: ProjectPage {
|
||||||
about_tooltip_visible: false,
|
about_tooltip_visible: false,
|
||||||
text_filter: "".to_string(),
|
text_filter: "".to_string(),
|
||||||
|
active_avatar_filters: vec![],
|
||||||
|
only_my_filter: false,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
use seed::{prelude::*, *};
|
use seed::{prelude::*, *};
|
||||||
|
|
||||||
use crate::model::{Icon, Model, Page};
|
use crate::model::{Icon, Model, Page};
|
||||||
|
use crate::shared::styled_avatar::StyledAvatar;
|
||||||
use crate::shared::styled_button::{StyledButton, Variant};
|
use crate::shared::styled_button::{StyledButton, Variant};
|
||||||
use crate::shared::styled_input::StyledInput;
|
use crate::shared::styled_input::StyledInput;
|
||||||
use crate::shared::{host_client, inner_layout};
|
use crate::shared::{host_client, inner_layout, ToNode};
|
||||||
use crate::Msg;
|
use crate::Msg;
|
||||||
|
|
||||||
pub fn update(msg: Msg, model: &mut crate::model::Model, orders: &mut impl Orders<Msg>) {
|
pub fn update(msg: Msg, model: &mut crate::model::Model, orders: &mut impl Orders<Msg>) {
|
||||||
@ -22,6 +23,23 @@ pub fn update(msg: Msg, model: &mut crate::model::Model, orders: &mut impl Order
|
|||||||
Msg::ProjectTextFilterChanged(text) => {
|
Msg::ProjectTextFilterChanged(text) => {
|
||||||
model.project_page.text_filter = text;
|
model.project_page.text_filter = text;
|
||||||
}
|
}
|
||||||
|
Msg::ProjectAvatarFilterChanged(user_id, active) => match active {
|
||||||
|
true => {
|
||||||
|
model.project_page.active_avatar_filters = model
|
||||||
|
.project_page
|
||||||
|
.active_avatar_filters
|
||||||
|
.iter()
|
||||||
|
.filter(|id| **id != user_id)
|
||||||
|
.map(|id| *id)
|
||||||
|
.collect();
|
||||||
|
}
|
||||||
|
false => {
|
||||||
|
model.project_page.active_avatar_filters.push(user_id);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Msg::ProjectToggleOnlyMy => {
|
||||||
|
model.project_page.only_my_filter = !model.project_page.only_my_filter;
|
||||||
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -56,6 +74,7 @@ fn header() -> Node<Msg> {
|
|||||||
active: false,
|
active: false,
|
||||||
text: Some("Github Repo".to_string()),
|
text: Some("Github Repo".to_string()),
|
||||||
icon: Some(Icon::Github),
|
icon: Some(Icon::Github),
|
||||||
|
on_click: None,
|
||||||
}
|
}
|
||||||
.into_node();
|
.into_node();
|
||||||
div![
|
div![
|
||||||
@ -68,7 +87,7 @@ fn header() -> Node<Msg> {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn project_board_filters(_model: &Model) -> Node<Msg> {
|
fn project_board_filters(model: &Model) -> Node<Msg> {
|
||||||
let search_input = StyledInput {
|
let search_input = StyledInput {
|
||||||
icon: Some(Icon::Search),
|
icon: Some(Icon::Search),
|
||||||
id: Some("searchInput".to_string()),
|
id: Some("searchInput".to_string()),
|
||||||
@ -76,5 +95,54 @@ fn project_board_filters(_model: &Model) -> Node<Msg> {
|
|||||||
on_change: input_ev(Ev::Change, |value| Msg::ProjectTextFilterChanged(value)),
|
on_change: input_ev(Ev::Change, |value| Msg::ProjectTextFilterChanged(value)),
|
||||||
}
|
}
|
||||||
.into_node();
|
.into_node();
|
||||||
div![id!["projectBoardFilters"], search_input]
|
|
||||||
|
let only_my = StyledButton {
|
||||||
|
variant: Variant::Empty,
|
||||||
|
icon_only: false,
|
||||||
|
disabled: false,
|
||||||
|
active: model.project_page.only_my_filter,
|
||||||
|
text: Some("Only my".to_string()),
|
||||||
|
icon: None,
|
||||||
|
on_click: Some(mouse_ev(Ev::Click, |_| Msg::ProjectToggleOnlyMy)),
|
||||||
|
}
|
||||||
|
.into_node();
|
||||||
|
|
||||||
|
div![
|
||||||
|
id!["projectBoardFilters"],
|
||||||
|
search_input,
|
||||||
|
avatars_filters(model),
|
||||||
|
only_my,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn avatars_filters(model: &Model) -> Node<Msg> {
|
||||||
|
let project = match model.project.as_ref() {
|
||||||
|
Some(p) => p,
|
||||||
|
_ => return empty![],
|
||||||
|
};
|
||||||
|
let active_avatar_filters = &model.project_page.active_avatar_filters;
|
||||||
|
let avatars: Vec<Node<Msg>> = project
|
||||||
|
.users
|
||||||
|
.iter()
|
||||||
|
.map(|user| {
|
||||||
|
let mut class_list = vec!["avatarIsActiveBorder"];
|
||||||
|
let user_id = user.id;
|
||||||
|
let active = active_avatar_filters.contains(&user_id);
|
||||||
|
if active {
|
||||||
|
class_list.push("isActive");
|
||||||
|
}
|
||||||
|
let styled_avatar = StyledAvatar {
|
||||||
|
avatar_url: user.avatar_url.clone(),
|
||||||
|
size: 32,
|
||||||
|
name: user.name.clone(),
|
||||||
|
on_click: Some(mouse_ev(Ev::Click, move |_| {
|
||||||
|
Msg::ProjectAvatarFilterChanged(user_id, active)
|
||||||
|
})),
|
||||||
|
}
|
||||||
|
.into_node();
|
||||||
|
div![attrs![At::Class => class_list.join(" ")], styled_avatar]
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
div![id!["avatars"], avatars]
|
||||||
}
|
}
|
||||||
|
@ -8,10 +8,15 @@ use crate::Msg;
|
|||||||
|
|
||||||
pub mod aside;
|
pub mod aside;
|
||||||
pub mod navbar_left;
|
pub mod navbar_left;
|
||||||
|
pub mod styled_avatar;
|
||||||
pub mod styled_button;
|
pub mod styled_button;
|
||||||
pub mod styled_input;
|
pub mod styled_input;
|
||||||
pub mod styled_tooltip;
|
pub mod styled_tooltip;
|
||||||
|
|
||||||
|
pub trait ToNode {
|
||||||
|
fn into_node(self) -> Node<Msg>;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn styled_icon(icon: Icon) -> Node<Msg> {
|
pub fn styled_icon(icon: Icon) -> Node<Msg> {
|
||||||
i![attrs![At::Class => format!("styledIcon {}", icon)], ""]
|
i![attrs![At::Class => format!("styledIcon {}", icon)], ""]
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ use seed::{prelude::*, *};
|
|||||||
|
|
||||||
use crate::model::{Icon, Model};
|
use crate::model::{Icon, Model};
|
||||||
use crate::shared::styled_button::{StyledButton, Variant};
|
use crate::shared::styled_button::{StyledButton, Variant};
|
||||||
use crate::shared::styled_tooltip;
|
use crate::shared::{styled_tooltip, ToNode};
|
||||||
use crate::Msg;
|
use crate::Msg;
|
||||||
|
|
||||||
pub fn render(model: &Model) -> Vec<Node<Msg>> {
|
pub fn render(model: &Model) -> Vec<Node<Msg>> {
|
||||||
@ -43,6 +43,27 @@ pub fn about_tooltip(_model: &Model, children: Node<Msg>) -> Node<Msg> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn about_tooltip_popup(model: &Model) -> Node<Msg> {
|
fn about_tooltip_popup(model: &Model) -> Node<Msg> {
|
||||||
|
let visit_website = StyledButton {
|
||||||
|
text: Some("Visit Website".to_string()),
|
||||||
|
variant: Variant::Primary,
|
||||||
|
disabled: false,
|
||||||
|
active: false,
|
||||||
|
icon_only: false,
|
||||||
|
icon: None,
|
||||||
|
on_click: None,
|
||||||
|
}
|
||||||
|
.into_node();
|
||||||
|
let github_repo = StyledButton {
|
||||||
|
text: Some("Github Repo".to_string()),
|
||||||
|
variant: Variant::Secondary,
|
||||||
|
disabled: false,
|
||||||
|
active: false,
|
||||||
|
icon_only: false,
|
||||||
|
icon: Some(Icon::Github),
|
||||||
|
on_click: None,
|
||||||
|
}
|
||||||
|
.into_node();
|
||||||
|
|
||||||
styled_tooltip::StyledTooltip {
|
styled_tooltip::StyledTooltip {
|
||||||
visible: model.project_page.about_tooltip_visible,
|
visible: model.project_page.about_tooltip_visible,
|
||||||
class_name: "aboutTooltipPopup".to_string(),
|
class_name: "aboutTooltipPopup".to_string(),
|
||||||
@ -71,14 +92,7 @@ fn about_tooltip_popup(model: &Model) -> Node<Msg> {
|
|||||||
At::Target => "_blank",
|
At::Target => "_blank",
|
||||||
At::Rel => "noreferrer noopener",
|
At::Rel => "noreferrer noopener",
|
||||||
],
|
],
|
||||||
StyledButton {
|
visit_website,
|
||||||
text: Some("Visit Website".to_string()),
|
|
||||||
variant: Variant::Primary,
|
|
||||||
disabled: false,
|
|
||||||
active: false,
|
|
||||||
icon_only: false,
|
|
||||||
icon: None,
|
|
||||||
}.into_node(),
|
|
||||||
],
|
],
|
||||||
a![
|
a![
|
||||||
id!["about-github-button"],
|
id!["about-github-button"],
|
||||||
@ -87,14 +101,7 @@ fn about_tooltip_popup(model: &Model) -> Node<Msg> {
|
|||||||
At::Target => "_blank",
|
At::Target => "_blank",
|
||||||
At::Rel => "noreferrer noopener",
|
At::Rel => "noreferrer noopener",
|
||||||
],
|
],
|
||||||
StyledButton {
|
github_repo
|
||||||
text: Some("Github Repo".to_string()),
|
|
||||||
variant: Variant::Secondary,
|
|
||||||
disabled: false,
|
|
||||||
active: false,
|
|
||||||
icon_only: false,
|
|
||||||
icon: Some(Icon::Github),
|
|
||||||
}.into_node()
|
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
}.into_node()
|
}.into_node()
|
||||||
|
52
jirs-client/src/shared/styled_avatar.rs
Normal file
52
jirs-client/src/shared/styled_avatar.rs
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
use crate::shared::ToNode;
|
||||||
|
use crate::Msg;
|
||||||
|
use seed::{prelude::*, *};
|
||||||
|
|
||||||
|
pub struct StyledAvatar {
|
||||||
|
pub avatar_url: Option<String>,
|
||||||
|
pub size: u32,
|
||||||
|
pub name: String,
|
||||||
|
pub on_click: Option<EventHandler<Msg>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for StyledAvatar {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
avatar_url: None,
|
||||||
|
size: 32,
|
||||||
|
name: "".to_string(),
|
||||||
|
on_click: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToNode for StyledAvatar {
|
||||||
|
fn into_node(self) -> Node<Msg> {
|
||||||
|
render(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render(values: StyledAvatar) -> Node<Msg> {
|
||||||
|
let StyledAvatar {
|
||||||
|
avatar_url,
|
||||||
|
size,
|
||||||
|
name,
|
||||||
|
on_click,
|
||||||
|
} = values;
|
||||||
|
let shared_style = format!("width: {size}px; height: {size}px", size = size);
|
||||||
|
let handler = match on_click {
|
||||||
|
None => vec![],
|
||||||
|
Some(h) => vec![h],
|
||||||
|
};
|
||||||
|
match avatar_url {
|
||||||
|
Some(url) => div![
|
||||||
|
attrs![At::Class => "styledAvatar image", At::Style => format!("{shared}; background-image: url({url});", shared = shared_style, url = url)],
|
||||||
|
handler,
|
||||||
|
],
|
||||||
|
_ => div![
|
||||||
|
attrs![At::Class => "styledAvatar letter", At::Style => shared_style],
|
||||||
|
span![name],
|
||||||
|
handler
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +1,7 @@
|
|||||||
use seed::{prelude::*, *};
|
use seed::{prelude::*, *};
|
||||||
|
|
||||||
use crate::model::Icon;
|
use crate::model::Icon;
|
||||||
use crate::shared::styled_icon;
|
use crate::shared::{styled_icon, ToNode};
|
||||||
use crate::Msg;
|
use crate::Msg;
|
||||||
|
|
||||||
pub enum Variant {
|
pub enum Variant {
|
||||||
@ -32,21 +32,16 @@ pub struct StyledButton {
|
|||||||
pub active: bool,
|
pub active: bool,
|
||||||
pub text: Option<String>,
|
pub text: Option<String>,
|
||||||
pub icon: Option<Icon>,
|
pub icon: Option<Icon>,
|
||||||
|
pub on_click: Option<EventHandler<Msg>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Into<Node<Msg>> for StyledButton {
|
impl ToNode for StyledButton {
|
||||||
fn into(self) -> Node<Msg> {
|
fn into_node(self) -> Node<Msg> {
|
||||||
styled_button(self)
|
render(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StyledButton {
|
pub fn render(values: StyledButton) -> Node<Msg> {
|
||||||
pub fn into_node(self) -> Node<Msg> {
|
|
||||||
self.into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn styled_button(values: StyledButton) -> Node<Msg> {
|
|
||||||
let StyledButton {
|
let StyledButton {
|
||||||
text,
|
text,
|
||||||
variant,
|
variant,
|
||||||
@ -54,6 +49,7 @@ pub fn styled_button(values: StyledButton) -> Node<Msg> {
|
|||||||
disabled,
|
disabled,
|
||||||
active,
|
active,
|
||||||
icon,
|
icon,
|
||||||
|
on_click,
|
||||||
} = values;
|
} = values;
|
||||||
let mut class_list = vec!["styledButton".to_string(), variant.to_string()];
|
let mut class_list = vec!["styledButton".to_string(), variant.to_string()];
|
||||||
if icon_only {
|
if icon_only {
|
||||||
@ -65,6 +61,10 @@ pub fn styled_button(values: StyledButton) -> Node<Msg> {
|
|||||||
if icon.is_some() {
|
if icon.is_some() {
|
||||||
class_list.push("withIcon".to_string());
|
class_list.push("withIcon".to_string());
|
||||||
}
|
}
|
||||||
|
let handler = match on_click {
|
||||||
|
Some(h) if !disabled => vec![h],
|
||||||
|
_ => vec![],
|
||||||
|
};
|
||||||
|
|
||||||
let icon_node = match icon {
|
let icon_node = match icon {
|
||||||
None => empty![],
|
None => empty![],
|
||||||
@ -75,6 +75,7 @@ pub fn styled_button(values: StyledButton) -> Node<Msg> {
|
|||||||
attrs![
|
attrs![
|
||||||
At::Class => class_list.join(" "),
|
At::Class => class_list.join(" "),
|
||||||
],
|
],
|
||||||
|
handler,
|
||||||
match disabled {
|
match disabled {
|
||||||
true => vec![attrs![At::Disabled => true]],
|
true => vec![attrs![At::Disabled => true]],
|
||||||
false => vec![],
|
false => vec![],
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use seed::{prelude::*, *};
|
use seed::{prelude::*, *};
|
||||||
|
|
||||||
use crate::model::Icon;
|
use crate::model::Icon;
|
||||||
use crate::shared::styled_icon;
|
use crate::shared::{styled_icon, ToNode};
|
||||||
use crate::Msg;
|
use crate::Msg;
|
||||||
|
|
||||||
pub struct StyledInput {
|
pub struct StyledInput {
|
||||||
@ -11,18 +11,12 @@ pub struct StyledInput {
|
|||||||
pub on_change: EventHandler<Msg>,
|
pub on_change: EventHandler<Msg>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Into<Node<Msg>> for StyledInput {
|
impl ToNode for StyledInput {
|
||||||
fn into(self) -> Node<Msg> {
|
fn into_node(self) -> Node<Msg> {
|
||||||
render(self)
|
render(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StyledInput {
|
|
||||||
pub fn into_node(self) -> Node<Msg> {
|
|
||||||
self.into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn render(values: StyledInput) -> Node<Msg> {
|
pub fn render(values: StyledInput) -> Node<Msg> {
|
||||||
let StyledInput {
|
let StyledInput {
|
||||||
id,
|
id,
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
use seed::{prelude::*, *};
|
use seed::{prelude::*, *};
|
||||||
|
|
||||||
|
use crate::shared::ToNode;
|
||||||
use crate::Msg;
|
use crate::Msg;
|
||||||
|
|
||||||
pub struct StyledTooltip {
|
pub struct StyledTooltip {
|
||||||
@ -8,19 +9,13 @@ pub struct StyledTooltip {
|
|||||||
pub children: Node<Msg>,
|
pub children: Node<Msg>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Into<Node<Msg>> for StyledTooltip {
|
impl ToNode for StyledTooltip {
|
||||||
fn into(self) -> Node<Msg> {
|
fn into_node(self) -> Node<Msg> {
|
||||||
styled_tooltip(self)
|
render(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StyledTooltip {
|
pub fn render(values: StyledTooltip) -> Node<Msg> {
|
||||||
pub fn into_node(self) -> Node<Msg> {
|
|
||||||
self.into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn styled_tooltip(values: StyledTooltip) -> Node<Msg> {
|
|
||||||
let StyledTooltip {
|
let StyledTooltip {
|
||||||
visible,
|
visible,
|
||||||
class_name,
|
class_name,
|
||||||
|
0
migrations/.gitkeep
Normal file
0
migrations/.gitkeep
Normal file
6
migrations/00000000000000_diesel_initial_setup/down.sql
Normal file
6
migrations/00000000000000_diesel_initial_setup/down.sql
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
-- This file was automatically created by Diesel to setup helper functions
|
||||||
|
-- and other internal bookkeeping. This file is safe to edit, any future
|
||||||
|
-- changes will be added to existing projects as new migrations.
|
||||||
|
|
||||||
|
DROP FUNCTION IF EXISTS diesel_manage_updated_at(_tbl regclass);
|
||||||
|
DROP FUNCTION IF EXISTS diesel_set_updated_at();
|
36
migrations/00000000000000_diesel_initial_setup/up.sql
Normal file
36
migrations/00000000000000_diesel_initial_setup/up.sql
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
-- This file was automatically created by Diesel to setup helper functions
|
||||||
|
-- and other internal bookkeeping. This file is safe to edit, any future
|
||||||
|
-- changes will be added to existing projects as new migrations.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-- Sets up a trigger for the given table to automatically set a column called
|
||||||
|
-- `updated_at` whenever the row is modified (unless `updated_at` was included
|
||||||
|
-- in the modified columns)
|
||||||
|
--
|
||||||
|
-- # Example
|
||||||
|
--
|
||||||
|
-- ```sql
|
||||||
|
-- CREATE TABLE users (id SERIAL PRIMARY KEY, updated_at TIMESTAMP NOT NULL DEFAULT NOW());
|
||||||
|
--
|
||||||
|
-- SELECT diesel_manage_updated_at('users');
|
||||||
|
-- ```
|
||||||
|
CREATE OR REPLACE FUNCTION diesel_manage_updated_at(_tbl regclass) RETURNS VOID AS $$
|
||||||
|
BEGIN
|
||||||
|
EXECUTE format('CREATE TRIGGER set_updated_at BEFORE UPDATE ON %s
|
||||||
|
FOR EACH ROW EXECUTE PROCEDURE diesel_set_updated_at()', _tbl);
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE plpgsql;
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION diesel_set_updated_at() RETURNS trigger AS $$
|
||||||
|
BEGIN
|
||||||
|
IF (
|
||||||
|
NEW IS DISTINCT FROM OLD AND
|
||||||
|
NEW.updated_at IS NOT DISTINCT FROM OLD.updated_at
|
||||||
|
) THEN
|
||||||
|
NEW.updated_at := current_timestamp;
|
||||||
|
END IF;
|
||||||
|
RETURN NEW;
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE plpgsql;
|
@ -1,55 +1,51 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import { Image, Letter } from './Styles';
|
import { Image, Letter } from './Styles';
|
||||||
|
|
||||||
const propTypes = {
|
|
||||||
className: PropTypes.string,
|
|
||||||
avatarUrl: PropTypes.string,
|
|
||||||
name: PropTypes.string,
|
|
||||||
size: PropTypes.number,
|
|
||||||
};
|
|
||||||
|
|
||||||
const defaultProps = {
|
|
||||||
className: undefined,
|
|
||||||
avatarUrl: null,
|
|
||||||
name: '',
|
|
||||||
size: 32,
|
|
||||||
};
|
|
||||||
|
|
||||||
const Avatar = ({ className, avatarUrl, name, size, ...otherProps }) => {
|
const Avatar = ({ className, avatarUrl, name, size, ...otherProps }) => {
|
||||||
const sharedProps = {
|
const sharedProps = {
|
||||||
className,
|
className,
|
||||||
size,
|
size,
|
||||||
'data-testid': name ? `avatar:${name}` : 'avatar',
|
'data-testid': name ? `avatar:${name}` : 'avatar',
|
||||||
...otherProps,
|
...otherProps,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (avatarUrl) {
|
if (avatarUrl) {
|
||||||
return <Image avatarUrl={avatarUrl} {...sharedProps} />;
|
return <Image avatarUrl={avatarUrl} {...sharedProps} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Letter color={getColorFromName(name)} {...sharedProps}>
|
<Letter color={getColorFromName(name)} {...sharedProps}>
|
||||||
<span>{name.charAt(0)}</span>
|
<span>{name.charAt(0)}</span>
|
||||||
</Letter>
|
</Letter>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const colors = [
|
const colors = [
|
||||||
'#DA7657',
|
'#DA7657',
|
||||||
'#6ADA57',
|
'#6ADA57',
|
||||||
'#5784DA',
|
'#5784DA',
|
||||||
'#AA57DA',
|
'#AA57DA',
|
||||||
'#DA5757',
|
'#DA5757',
|
||||||
'#DA5792',
|
'#DA5792',
|
||||||
'#57DACA',
|
'#57DACA',
|
||||||
'#57A5DA',
|
'#57A5DA',
|
||||||
];
|
];
|
||||||
|
|
||||||
const getColorFromName = name => colors[name.toLocaleLowerCase().charCodeAt(0) % colors.length];
|
const getColorFromName = name => colors[name.toLocaleLowerCase().charCodeAt(0) % colors.length];
|
||||||
|
|
||||||
Avatar.propTypes = propTypes;
|
Avatar.propTypes = {
|
||||||
Avatar.defaultProps = defaultProps;
|
className: PropTypes.string,
|
||||||
|
avatarUrl: PropTypes.string,
|
||||||
|
name: PropTypes.string,
|
||||||
|
size: PropTypes.number,
|
||||||
|
};
|
||||||
|
Avatar.defaultProps = {
|
||||||
|
className: undefined,
|
||||||
|
avatarUrl: null,
|
||||||
|
name: '',
|
||||||
|
size: 32,
|
||||||
|
};
|
||||||
|
|
||||||
export default Avatar;
|
export default Avatar;
|
||||||
|
Loading…
Reference in New Issue
Block a user