Merge branch 'master' of gitlab.com:adrian.wozniak/jirs

This commit is contained in:
Adrian Wozniak 2020-05-01 22:22:10 +02:00
commit 5d335a1ed9
9 changed files with 154 additions and 21 deletions

View File

@ -56,6 +56,12 @@ aside#navbar-left .item > .styledIcon {
line-height: 42px; line-height: 42px;
} }
aside#navbar-left .item > .styledIcon > .styledAvatar {
margin-top: 7px;
width: 27px;
height: 27px;
}
aside#navbar-left > .item > .styledIcon.search { aside#navbar-left > .item > .styledIcon.search {
font-size: 22px; font-size: 22px;
color: #DEEBFE; color: #DEEBFE;

View File

@ -13,6 +13,7 @@ mod api;
mod invite; mod invite;
mod modal; mod modal;
mod model; mod model;
mod profile;
mod project; mod project;
mod project_settings; mod project_settings;
mod shared; mod shared;
@ -37,6 +38,7 @@ pub enum FieldId {
SignUp(SignUpFieldId), SignUp(SignUpFieldId),
Invite(InviteFieldId), Invite(InviteFieldId),
Users(UsersFieldId), Users(UsersFieldId),
Profile(UsersFieldId),
// issue // issue
AddIssueModal(IssueFieldId), AddIssueModal(IssueFieldId),
EditIssueModal(EditIssueModalSection), EditIssueModal(EditIssueModalSection),
@ -127,6 +129,11 @@ impl std::fmt::Display for FieldId {
UsersFieldId::Email => f.write_str("users-email"), UsersFieldId::Email => f.write_str("users-email"),
UsersFieldId::UserRole => f.write_str("users-userRole"), UsersFieldId::UserRole => f.write_str("users-userRole"),
}, },
FieldId::Profile(sub) => match sub {
UsersFieldId::Username => f.write_str("profile-username"),
UsersFieldId::Email => f.write_str("profile-email"),
UsersFieldId::UserRole => f.write_str("profile-userRole"),
},
} }
} }
} }
@ -150,10 +157,16 @@ pub enum ProjectPageChange {
SubmitForm, SubmitForm,
} }
#[derive(Clone, Debug, PartialEq)]
pub enum ProfilePageChange {
SubmitForm,
}
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub enum PageChanged { pub enum PageChanged {
Users(UsersPageChange), Users(UsersPageChange),
ProjectSettings(ProjectPageChange), ProjectSettings(ProjectPageChange),
Profile(ProfilePageChange),
} }
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
@ -260,6 +273,7 @@ fn update(msg: Msg, model: &mut model::Model, orders: &mut impl Orders<Msg>) {
Page::SignUp => sign_up::update(msg, model, orders), Page::SignUp => sign_up::update(msg, model, orders),
Page::Invite => invite::update(msg, model, orders), Page::Invite => invite::update(msg, model, orders),
Page::Users => users::update(msg, model, orders), Page::Users => users::update(msg, model, orders),
Page::Profile => profile::update(msg, model, orders),
} }
if cfg!(debug_assertions) { if cfg!(debug_assertions) {
// debug!(model); // debug!(model);
@ -275,6 +289,7 @@ fn view(model: &model::Model) -> Node<Msg> {
Page::SignUp => sign_up::view(model), Page::SignUp => sign_up::view(model),
Page::Invite => invite::view(model), Page::Invite => invite::view(model),
Page::Users => users::view(model), Page::Users => users::view(model),
Page::Profile => profile::view(model),
} }
} }
@ -289,6 +304,7 @@ fn routes(url: Url) -> Option<Msg> {
Some(Ok(id)) => Some(Msg::ChangePage(model::Page::EditIssue(id))), Some(Ok(id)) => Some(Msg::ChangePage(model::Page::EditIssue(id))),
_ => None, _ => None,
}, },
"profile" => Some(Msg::ChangePage(Page::Profile)),
"add-issue" => Some(Msg::ChangePage(Page::AddIssue)), "add-issue" => Some(Msg::ChangePage(Page::AddIssue)),
"project-settings" => Some(Msg::ChangePage(model::Page::ProjectSettings)), "project-settings" => Some(Msg::ChangePage(model::Page::ProjectSettings)),
"login" => Some(Msg::ChangePage(model::Page::SignIn)), "login" => Some(Msg::ChangePage(model::Page::SignIn)),

View File

@ -193,6 +193,7 @@ pub enum Page {
SignUp, SignUp,
Invite, Invite,
Users, Users,
Profile,
} }
impl Page { impl Page {
@ -206,6 +207,7 @@ impl Page {
Page::SignUp => "/register".to_string(), Page::SignUp => "/register".to_string(),
Page::Invite => "/invite".to_string(), Page::Invite => "/invite".to_string(),
Page::Users => "/users".to_string(), Page::Users => "/users".to_string(),
Page::Profile => "/profile".to_string(),
} }
} }
} }
@ -358,6 +360,27 @@ impl Default for UsersPage {
} }
} }
#[derive(Debug)]
pub struct ProfilePage {
pub name: StyledInputState,
pub email: StyledInputState,
}
impl ProfilePage {
pub fn new(user: &User) -> Self {
Self {
name: StyledInputState::new(
FieldId::Profile(UsersFieldId::Username),
user.name.as_str(),
),
email: StyledInputState::new(
FieldId::Profile(UsersFieldId::Email),
user.email.as_str(),
),
}
}
}
#[derive(Debug)] #[derive(Debug)]
pub enum PageContent { pub enum PageContent {
SignIn(Box<SignInPage>), SignIn(Box<SignInPage>),
@ -366,6 +389,7 @@ pub enum PageContent {
ProjectSettings(Box<ProjectSettingsPage>), ProjectSettings(Box<ProjectSettingsPage>),
Invite(Box<InvitePage>), Invite(Box<InvitePage>),
Users(Box<UsersPage>), Users(Box<UsersPage>),
Profile(Box<ProfilePage>),
} }
#[derive(Debug)] #[derive(Debug)]

View File

@ -0,0 +1,68 @@
use seed::{prelude::*, *};
use jirs_data::*;
use crate::api::send_ws_msg;
use crate::model::{Model, Page, PageContent, ProfilePage};
use crate::shared::styled_field::StyledField;
use crate::shared::styled_form::StyledForm;
use crate::shared::styled_input::StyledInput;
use crate::shared::{inner_layout, ToNode};
use crate::{FieldId, Msg};
pub fn update(msg: Msg, model: &mut crate::model::Model, _orders: &mut impl Orders<Msg>) {
let user = match model.user {
Some(ref user) => user,
_ => return,
};
match msg {
Msg::WsMsg(WsMsg::AuthorizeLoaded(..)) | Msg::ChangePage(Page::Profile) => {
send_ws_msg(WsMsg::ProjectRequest);
model.page_content = PageContent::Profile(Box::new(ProfilePage::new(user)));
}
_ => (),
}
let _project_page = match &mut model.page_content {
PageContent::Profile(profile_page) => profile_page,
_ => return,
};
}
pub fn view(model: &Model) -> Node<Msg> {
let page = match &model.page_content {
PageContent::Profile(profile_page) => profile_page,
_ => return empty![],
};
let username = StyledInput::build(FieldId::Profile(UsersFieldId::Username))
.state(&page.name)
.valid(true)
.build()
.into_node();
let username_field = StyledField::build()
.label("Username")
.input(username)
.build()
.into_node();
let email = StyledInput::build(FieldId::Profile(UsersFieldId::Username))
.state(&page.email)
.valid(true)
.build()
.into_node();
let email_field = StyledField::build()
.label("E-Mail")
.input(email)
.build()
.into_node();
let content = StyledForm::build()
.heading("Profile")
.add_field(username_field)
.add_field(email_field)
.build()
.into_node();
inner_layout(model, "profile", vec![content], empty![])
}

View File

@ -19,10 +19,6 @@ static TIME_TRACKING_FIBONACCI: &'static str = "Tracking employees time carri
static TIME_TRACKING_HOURLY: &'static str = "Employees may feel intimidated by demands to track their time. Or they could feel that theyre constantly being watched and evaluated. And for overly ambitious managers, employee time tracking may open the doors to excessive micromanaging."; static TIME_TRACKING_HOURLY: &'static str = "Employees may feel intimidated by demands to track their time. Or they could feel that theyre constantly being watched and evaluated. And for overly ambitious managers, employee time tracking may open the doors to excessive micromanaging.";
pub fn update(msg: Msg, model: &mut model::Model, orders: &mut impl Orders<Msg>) { pub fn update(msg: Msg, model: &mut model::Model, orders: &mut impl Orders<Msg>) {
if model.user.is_none() {
return;
}
if model.page != Page::ProjectSettings { if model.page != Page::ProjectSettings {
return; return;
} }
@ -32,8 +28,10 @@ pub fn update(msg: Msg, model: &mut model::Model, orders: &mut impl Orders<Msg>)
send_ws_msg(WsMsg::ProjectRequest); send_ws_msg(WsMsg::ProjectRequest);
} }
Msg::ChangePage(Page::ProjectSettings) => { Msg::ChangePage(Page::ProjectSettings) => {
send_ws_msg(WsMsg::ProjectRequest);
build_page_content(model); build_page_content(model);
if model.user.is_some() {
send_ws_msg(WsMsg::ProjectRequest);
}
} }
Msg::WsMsg(WsMsg::ProjectLoaded(..)) => { Msg::WsMsg(WsMsg::ProjectLoaded(..)) => {
build_page_content(model); build_page_content(model);
@ -41,9 +39,13 @@ pub fn update(msg: Msg, model: &mut model::Model, orders: &mut impl Orders<Msg>)
_ => (), _ => (),
} }
if model.user.is_none() || model.project.is_none() {
return;
}
let page = match &mut model.page_content { let page = match &mut model.page_content {
PageContent::ProjectSettings(page) => page, PageContent::ProjectSettings(page) => page,
_ => return, _ => return error!("bad content type"),
}; };
page.project_category_state.update(&msg, orders); page.project_category_state.update(&msg, orders);
page.time_tracking.update(&msg); page.time_tracking.update(&msg);

View File

@ -14,20 +14,17 @@ pub fn render(model: &Model) -> Node<Msg> {
id!["projectInfo"], id!["projectInfo"],
project_icon, project_icon,
div![ div![
attrs![At::Class => "projectTexts";], class!["projectTexts"],
div![attrs![At::Class => "projectName";], project.name], div![class!["projectName"], project.name],
div![ div![class!["projectCategory"], project.category.to_string()]
attrs![At::Class => "projectCategory";],
project.category.to_string()
]
], ],
], ],
_ => li![ _ => li![
id!["projectInfo"], id!["projectInfo"],
div![ div![
attrs![At::Class => "projectTexts";], class!["projectTexts"],
div![attrs![At::Class => "projectName";], ""], div![class!["projectName"], ""],
div![attrs![At::Class => "projectCategory";], ""] div![class!["projectCategory"], ""]
], ],
], ],
}; };

View File

@ -40,7 +40,7 @@ pub trait ToNode {
} }
pub fn divider() -> Node<Msg> { pub fn divider() -> Node<Msg> {
div![attrs![At::Class => "divider"], ""] div![class!["divider"], ""]
} }
pub fn inner_layout( pub fn inner_layout(

View File

@ -1,6 +1,7 @@
use seed::{prelude::*, *}; use seed::{prelude::*, *};
use crate::model::Model; use crate::model::Model;
use crate::shared::styled_avatar::StyledAvatar;
use crate::shared::styled_button::StyledButton; use crate::shared::styled_button::StyledButton;
use crate::shared::styled_icon::Icon; use crate::shared::styled_icon::Icon;
use crate::shared::{styled_tooltip, ToNode}; use crate::shared::{styled_tooltip, ToNode};
@ -9,6 +10,19 @@ use crate::Msg;
pub fn render(model: &Model) -> Vec<Node<Msg>> { pub fn render(model: &Model) -> Vec<Node<Msg>> {
let logo_svg = Node::from_html(include_str!("../../static/logo.svg")); let logo_svg = Node::from_html(include_str!("../../static/logo.svg"));
let user_icon = match model.user.as_ref() {
Some(user) => i![
class!["styledIcon"],
StyledAvatar::build()
.size(27)
.name(user.name.as_str())
.avatar_url(user.avatar_url.as_ref().cloned().unwrap_or_default())
.build()
.into_node()
],
_ => i![attrs![At::Class => format!("styledIcon {}", Icon::Plus)]],
};
vec![ vec![
about_tooltip_popup(model), about_tooltip_popup(model),
aside![ aside![
@ -24,7 +38,13 @@ pub fn render(model: &Model) -> Vec<Node<Msg>> {
span![attrs![At::Class => "itemText"], "Create Issue"] span![attrs![At::Class => "itemText"], "Create Issue"]
], ],
div![ div![
attrs![At::Class => "bottom"], class!["bottom"],
div![
class!["item"],
attrs![At::Href=> "/profile"],
user_icon,
span![class!["itemText"], "Profile"]
],
about_tooltip(model, navbar_left_item(model, "About", Icon::Help)), about_tooltip(model, navbar_left_item(model, "About", Icon::Help)),
], ],
], ],
@ -33,9 +53,9 @@ pub fn render(model: &Model) -> Vec<Node<Msg>> {
fn navbar_left_item(_model: &Model, text: &str, logo: Icon) -> Node<Msg> { fn navbar_left_item(_model: &Model, text: &str, logo: Icon) -> Node<Msg> {
div![ div![
attrs![At::Class => "item"], class!["item"],
i![attrs![At::Class => format!("styledIcon {}", logo)]], i![attrs![At::Class => format!("styledIcon {}", logo)]],
span![attrs![At::Class => "itemText"], text] span![class!["itemText"], text]
] ]
} }

View File

@ -20,13 +20,13 @@ impl Handler<LoadCurrentProject> for DbExecutor {
type Result = Result<Project, ServiceErrors>; type Result = Result<Project, ServiceErrors>;
fn handle(&mut self, msg: LoadCurrentProject, _ctx: &mut Self::Context) -> Self::Result { fn handle(&mut self, msg: LoadCurrentProject, _ctx: &mut Self::Context) -> Self::Result {
use crate::schema::projects::dsl::{id, projects}; use crate::schema::projects::dsl::projects;
let conn = &self let conn = &self
.pool .pool
.get() .get()
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?; .map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
let query = projects.filter(id.eq(msg.project_id)); let query = projects.find(msg.project_id);
debug!( debug!(
"{}", "{}",