Finish visual aside
This commit is contained in:
parent
c58d9fb9f1
commit
aa5216a547
@ -95,12 +95,11 @@ aside#navbar-left:hover .item > .itemText {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
aside#navbar-left .styledTooltip {
|
||||
z-index: calc(var(--modal) + 1);
|
||||
position: fixed;
|
||||
width: 300px;
|
||||
border-radius: 3px;
|
||||
background: #fff;
|
||||
transform: translateZ(0);
|
||||
box-shadow: rgba(9, 30, 66, 0.25) 0 4px 8px -2px, rgba(9, 30, 66, 0.31) 0 0 1px;
|
||||
.styledTooltip.aboutTooltipPopup {
|
||||
bottom: 48px;
|
||||
left: 120px;
|
||||
}
|
||||
|
||||
.styledTooltip.aboutTooltipPopup #about-github-button {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
@ -3,3 +3,104 @@
|
||||
padding-top: 18px;
|
||||
border-top: 1px solid var(--borderLight);
|
||||
}
|
||||
|
||||
.styledButton {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 32px;
|
||||
vertical-align: middle;
|
||||
line-height: 1;
|
||||
white-space: nowrap;
|
||||
border-radius: 3px;
|
||||
transition: all 0.1s;
|
||||
appearance: none;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
font-size: 14.5px;
|
||||
}
|
||||
|
||||
.styledButton.withIcon > span.text {
|
||||
margin-left: 7px;
|
||||
}
|
||||
|
||||
.styledButton:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.styledButton:not(.onlyIcon) {
|
||||
padding: 0 12px;
|
||||
}
|
||||
|
||||
.styledButton.onlyIcon {
|
||||
padding: 0 9px;
|
||||
}
|
||||
|
||||
.styledButton.primary {
|
||||
color: #fff;
|
||||
background: var(--primary);
|
||||
font-family: var(--font-medium);
|
||||
}
|
||||
|
||||
.styledButton.primary:not(:disabled):hover {
|
||||
filter: brightness(115%);
|
||||
}
|
||||
|
||||
.styledButton.primary:not(:disabled):active {
|
||||
filter: brightness(110%);
|
||||
}
|
||||
|
||||
.styledButton.primary:not(:disabled).isActive {
|
||||
filter: brightness(110%);
|
||||
}
|
||||
|
||||
.styledButton.success {
|
||||
color: #fff;
|
||||
background: var(--success);
|
||||
}
|
||||
|
||||
.styledButton.danger {
|
||||
color: #fff;
|
||||
background: var(--danger);
|
||||
}
|
||||
|
||||
.styledButton.secondary {
|
||||
color: var(--textDark);
|
||||
background: var(--secondary);
|
||||
font-family: var(--font-regular);
|
||||
}
|
||||
|
||||
.styledButton.secondary:not(:disabled):hover {
|
||||
background: var(--backgroundLight);
|
||||
}
|
||||
|
||||
.styledButton.secondary:not(:disabled):active {
|
||||
color: var(--primary);
|
||||
background: var(--backgroundLightPrimary);
|
||||
}
|
||||
|
||||
.styledButton.secondary:not(:disabled).isActive {
|
||||
color: var(--primary);
|
||||
background: var(--backgroundLightPrimary);
|
||||
}
|
||||
|
||||
.styledButton.empty {
|
||||
background: #fff;
|
||||
color: var(--textDark);
|
||||
font-family: var(--font-regular);
|
||||
}
|
||||
|
||||
.styledButton.empty:not(:disabled):hover {
|
||||
background: var(--backgroundLight);
|
||||
}
|
||||
|
||||
.styledButton.empty:not(:disabled):active {
|
||||
color: var(--primary);
|
||||
background: var(--backgroundLightPrimary);
|
||||
}
|
||||
|
||||
.styledButton.empty:not(:disabled).isActive {
|
||||
color: var(--primary);
|
||||
background: var(--backgroundLightPrimary);
|
||||
}
|
||||
|
30
jirs-client/js/css/styledTooltip.css
Normal file
30
jirs-client/js/css/styledTooltip.css
Normal file
@ -0,0 +1,30 @@
|
||||
.styledTooltip {
|
||||
z-index: calc(var(--modal) + 1);
|
||||
position: fixed;
|
||||
width: 300px;
|
||||
border-radius: 3px;
|
||||
background: #fff;
|
||||
transform: translateZ(0);
|
||||
box-shadow: rgba(9, 30, 66, 0.25) 0 4px 8px -2px, rgba(9, 30, 66, 0.31) 0 0 1px;
|
||||
}
|
||||
|
||||
.styledTooltip .feedbackDropdown {
|
||||
padding: 16px 24px 24px;
|
||||
}
|
||||
|
||||
.styledTooltip .feedbackImageCont {
|
||||
padding: 24px 56px 20px;
|
||||
}
|
||||
|
||||
.styledTooltip .feedbackImage {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.styledTooltip .feedbackParagraph {
|
||||
margin-bottom: 12px;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.styledTooltip .feedbackParagraph:last-of-type {
|
||||
margin-bottom: 22px;
|
||||
}
|
@ -6,4 +6,5 @@
|
||||
@import "css/aside.css";
|
||||
@import "css/icon.css";
|
||||
@import "css/shared.css";
|
||||
@import "css/styledTooltip.css";
|
||||
@import "css/app.css";
|
||||
|
@ -1,7 +1,8 @@
|
||||
use crate::model::Page;
|
||||
use seed::fetch::FetchObject;
|
||||
use seed::{prelude::*, *};
|
||||
|
||||
use crate::model::Page;
|
||||
|
||||
mod api;
|
||||
mod login;
|
||||
mod model;
|
||||
@ -16,6 +17,7 @@ pub enum Msg {
|
||||
CurrentProjectResult(FetchObject<String>),
|
||||
CurrentUserResult(FetchObject<String>),
|
||||
InternalFailure(String),
|
||||
ToggleAboutTooltip,
|
||||
}
|
||||
|
||||
fn update(msg: Msg, model: &mut model::Model, orders: &mut impl Orders<Msg>) {
|
||||
|
@ -59,6 +59,8 @@ pub struct Model {
|
||||
pub comments_by_project_id: HashMap<ProjectId, Vec<Comment>>,
|
||||
pub page: Page,
|
||||
pub host_url: String,
|
||||
//
|
||||
pub about_tooltip_visible: bool,
|
||||
}
|
||||
|
||||
impl Default for Model {
|
||||
@ -75,6 +77,7 @@ impl Default for Model {
|
||||
comments_by_project_id: Default::default(),
|
||||
page: Page::Project,
|
||||
host_url,
|
||||
about_tooltip_visible: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,8 @@
|
||||
use seed::{prelude::*, *};
|
||||
|
||||
use crate::model::Page;
|
||||
use crate::shared::{host_client, inner_layout};
|
||||
use crate::Msg;
|
||||
use seed::{prelude::*, *};
|
||||
|
||||
pub fn update(msg: Msg, model: &mut crate::model::Model, orders: &mut impl Orders<Msg>) {
|
||||
match msg {
|
||||
@ -13,6 +14,9 @@ pub fn update(msg: Msg, model: &mut crate::model::Model, orders: &mut impl Order
|
||||
.skip()
|
||||
.perform_cmd(crate::api::fetch_current_user(model.host_url.clone()));
|
||||
}
|
||||
Msg::ToggleAboutTooltip => {
|
||||
model.about_tooltip_visible = !model.about_tooltip_visible;
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
@ -1,15 +1,20 @@
|
||||
use seed::fetch::{FetchObject, FetchResult, ResponseWithDataResult};
|
||||
use seed::fetch::{FetchObject, ResponseWithDataResult};
|
||||
use seed::{prelude::*, *};
|
||||
use serde::Deserialize;
|
||||
|
||||
use jirs_data::FullProjectResponse;
|
||||
use styled_button::*;
|
||||
|
||||
use crate::model::{Icon, Model, Page};
|
||||
use crate::Msg;
|
||||
|
||||
pub fn navbar_left(model: &Model) -> Node<Msg> {
|
||||
let mut logo_svg = Node::from_html(include_str!("../static/logo.svg"));
|
||||
pub mod styled_button;
|
||||
pub mod styled_tooltip;
|
||||
|
||||
pub fn navbar_left(model: &Model) -> Vec<Node<Msg>> {
|
||||
let logo_svg = Node::from_html(include_str!("../../static/logo.svg"));
|
||||
|
||||
vec![
|
||||
about_tooltip_popup(model),
|
||||
aside![
|
||||
id!["navbar-left"],
|
||||
a![
|
||||
@ -20,8 +25,9 @@ pub fn navbar_left(model: &Model) -> Node<Msg> {
|
||||
navbar_left_item(model, "Create Issue", Icon::Plus),
|
||||
div![
|
||||
attrs![At::Class => "bottom"],
|
||||
about_tooltip(model, navbar_left_item(model, "About", Icon::Help))
|
||||
]
|
||||
about_tooltip(model, navbar_left_item(model, "About", Icon::Help)),
|
||||
],
|
||||
],
|
||||
]
|
||||
}
|
||||
|
||||
@ -33,16 +39,74 @@ fn navbar_left_item(_model: &Model, text: &str, logo: Icon) -> Node<Msg> {
|
||||
]
|
||||
}
|
||||
|
||||
pub fn about_tooltip(_model: &Model, children: Node<Msg>) -> Node<Msg> {
|
||||
div![attrs![At::Class => "aboutTooltip"], children]
|
||||
pub fn about_tooltip(model: &Model, children: Node<Msg>) -> Node<Msg> {
|
||||
div![
|
||||
attrs![At::Class => "aboutTooltip"],
|
||||
ev(Ev::Click, |_| Msg::ToggleAboutTooltip),
|
||||
children
|
||||
]
|
||||
}
|
||||
|
||||
pub fn styled_tooltip() -> Node<Msg> {
|
||||
div![attrs![At::Class => "styledTooltip"]]
|
||||
fn about_tooltip_popup(model: &Model) -> Node<Msg> {
|
||||
styled_tooltip::StyledTooltip {
|
||||
visible: model.about_tooltip_visible,
|
||||
class_name: "aboutTooltipPopup".to_string(),
|
||||
children: div![
|
||||
ev(Ev::Click, |_| Msg::ToggleAboutTooltip),
|
||||
attrs![At::Class => "feedbackDropdown"],
|
||||
div![
|
||||
attrs![At::Class => "feedbackImageCont"],
|
||||
img![attrs![At::Src => "/feedback.png", At::Class => "feedbackImage"]]
|
||||
],
|
||||
div![
|
||||
attrs![At::Class => "feedbackParagraph"],
|
||||
"This simplified Jira clone is built with Seed.rs on the front-end and Actix-Web on the back-end."
|
||||
],
|
||||
div![
|
||||
attrs![At::Class => "feedbackParagraph"],
|
||||
"Read more on my website or reach out via ",
|
||||
a![
|
||||
attrs![At::Href => "mailto:adrian.wozniak@ita-prog.pl"],
|
||||
strong!["adrian.wozniak@ita-prog.pl"]
|
||||
]
|
||||
],
|
||||
a![
|
||||
attrs![
|
||||
At::Href => "https://gitlab.com/adrian.wozniak/jirs",
|
||||
At::Target => "_blank",
|
||||
At::Rel => "noreferrer noopener",
|
||||
],
|
||||
StyledButton {
|
||||
text: Some("Visit Website".to_string()),
|
||||
variant: Variant::Primary,
|
||||
disabled: false,
|
||||
active: false,
|
||||
icon_only: false,
|
||||
icon: None,
|
||||
}.into_node(),
|
||||
],
|
||||
a![
|
||||
id!["about-github-button"],
|
||||
attrs![
|
||||
At::Href => "https://gitlab.com/adrian.wozniak/jirs",
|
||||
At::Target => "_blank",
|
||||
At::Rel => "noreferrer noopener",
|
||||
],
|
||||
StyledButton {
|
||||
text: Some("Github Repo".to_string()),
|
||||
variant: Variant::Secondary,
|
||||
disabled: false,
|
||||
active: false,
|
||||
icon_only: false,
|
||||
icon: Some(Icon::Github),
|
||||
}.into_node()
|
||||
]
|
||||
],
|
||||
}.into_node()
|
||||
}
|
||||
|
||||
pub fn sidebar(model: &Model) -> Node<Msg> {
|
||||
let project_icon = Node::from_html(include_str!("../static/project-avatar.svg"));
|
||||
let project_icon = Node::from_html(include_str!("../../static/project-avatar.svg"));
|
||||
let project_info = match model.project.as_ref() {
|
||||
Some(project) => li![
|
||||
id!["projectInfo"],
|
||||
@ -96,12 +160,16 @@ fn sidebar_link_item(model: &Model, name: &str, icon: Icon, page: Option<Page>)
|
||||
attrs![At::Class => class_list.join(" ")],
|
||||
a![
|
||||
attrs![At::Href => path],
|
||||
i![attrs![At::Class => format!("styledIcon {}", icon)], ""],
|
||||
styled_icon(icon),
|
||||
div![attrs![At::Class => "linkText"], name],
|
||||
]
|
||||
]
|
||||
}
|
||||
|
||||
pub fn styled_icon(icon: Icon) -> Node<Msg> {
|
||||
i![attrs![At::Class => format!("styledIcon {}", icon)], ""]
|
||||
}
|
||||
|
||||
pub fn divider() -> Node<Msg> {
|
||||
div![attrs![At::Class => "divider"], ""]
|
||||
}
|
85
jirs-client/src/shared/styled_button.rs
Normal file
85
jirs-client/src/shared/styled_button.rs
Normal file
@ -0,0 +1,85 @@
|
||||
use seed::{prelude::*, *};
|
||||
|
||||
use crate::model::Icon;
|
||||
use crate::shared::styled_icon;
|
||||
use crate::Msg;
|
||||
|
||||
pub enum Variant {
|
||||
Primary,
|
||||
Success,
|
||||
Danger,
|
||||
Secondary,
|
||||
Empty,
|
||||
}
|
||||
|
||||
impl ToString for Variant {
|
||||
fn to_string(&self) -> String {
|
||||
match self {
|
||||
Variant::Primary => "primary",
|
||||
Variant::Success => "success",
|
||||
Variant::Danger => "danger",
|
||||
Variant::Secondary => "secondary",
|
||||
Variant::Empty => "empty",
|
||||
}
|
||||
.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct StyledButton {
|
||||
pub variant: Variant,
|
||||
pub icon_only: bool,
|
||||
pub disabled: bool,
|
||||
pub active: bool,
|
||||
pub text: Option<String>,
|
||||
pub icon: Option<Icon>,
|
||||
}
|
||||
|
||||
impl Into<Node<Msg>> for StyledButton {
|
||||
fn into(self) -> Node<Msg> {
|
||||
styled_button(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl StyledButton {
|
||||
pub fn into_node(self) -> Node<Msg> {
|
||||
self.into()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn styled_button(values: StyledButton) -> Node<Msg> {
|
||||
let StyledButton {
|
||||
text,
|
||||
variant,
|
||||
icon_only,
|
||||
disabled,
|
||||
active,
|
||||
icon,
|
||||
} = values;
|
||||
let mut class_list = vec!["styledButton".to_string(), variant.to_string()];
|
||||
if icon_only {
|
||||
class_list.push("iconOnly".to_string());
|
||||
}
|
||||
if active {
|
||||
class_list.push("isActive".to_string());
|
||||
}
|
||||
if icon.is_some() {
|
||||
class_list.push("withIcon".to_string());
|
||||
}
|
||||
|
||||
let icon_node = match icon {
|
||||
None => empty![],
|
||||
Some(i) => styled_icon(i),
|
||||
};
|
||||
|
||||
button![
|
||||
attrs![
|
||||
At::Class => class_list.join(" "),
|
||||
],
|
||||
match disabled {
|
||||
true => vec![attrs![At::Disabled => true]],
|
||||
false => vec![],
|
||||
},
|
||||
icon_node,
|
||||
span![attrs![At::Class => "text"], text.unwrap_or_default()],
|
||||
]
|
||||
}
|
37
jirs-client/src/shared/styled_tooltip.rs
Normal file
37
jirs-client/src/shared/styled_tooltip.rs
Normal file
@ -0,0 +1,37 @@
|
||||
use seed::{prelude::*, *};
|
||||
|
||||
use crate::Msg;
|
||||
|
||||
pub struct StyledTooltip {
|
||||
pub visible: bool,
|
||||
pub class_name: String,
|
||||
pub children: Node<Msg>,
|
||||
}
|
||||
|
||||
impl Into<Node<Msg>> for StyledTooltip {
|
||||
fn into(self) -> Node<Msg> {
|
||||
styled_tooltip(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl StyledTooltip {
|
||||
pub fn into_node(self) -> Node<Msg> {
|
||||
self.into()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn styled_tooltip(values: StyledTooltip) -> Node<Msg> {
|
||||
let StyledTooltip {
|
||||
visible,
|
||||
class_name,
|
||||
children,
|
||||
} = values;
|
||||
if visible {
|
||||
div![
|
||||
attrs![At::Class => format!("styledTooltip {}", class_name)],
|
||||
children
|
||||
]
|
||||
} else {
|
||||
empty!()
|
||||
}
|
||||
}
|
@ -83,11 +83,4 @@ joinable!(issues -> users (reporter_id));
|
||||
joinable!(tokens -> users (user_id));
|
||||
joinable!(users -> projects (project_id));
|
||||
|
||||
allow_tables_to_appear_in_same_query!(
|
||||
comments,
|
||||
issue_assignees,
|
||||
issues,
|
||||
projects,
|
||||
tokens,
|
||||
users,
|
||||
);
|
||||
allow_tables_to_appear_in_same_query!(comments, issue_assignees, issues, projects, tokens, users,);
|
||||
|
Loading…
Reference in New Issue
Block a user