4 files changed, 0 insertions(+), 488 deletions(-)
D client/src/plugins/menu/accountlogin/mod.rs
D client/src/plugins/menu/accountlogin/ui.rs
D client/src/plugins/menu/accountregister/mod.rs
D client/src/plugins/menu/accountregister/ui.rs
D => +0 -156
@@ 1,156 0,0 @@
/*
* This file is part of laurelin/client
* Copyright (C) 2023 Jonni Liljamo <jonni@liljamo.com>
*
* Licensed under GPL-3.0-only.
* See LICENSE for licensing information.
*/
use bevy::{
prelude::*,
tasks::{AsyncComputeTaskPool, Task},
};
use bevy_console::PrintConsoleLine;
use iyes_loopless::prelude::*;
use futures_lite::future;
use crate::{
api::{
self,
user::{ResponseToken, ResponseUserInfoP},
},
cfg::{CfgDev, CfgUser},
plugins::config::{SaveEvent, SaveEventValue},
};
use super::MenuState;
pub mod ui;
pub struct AccountLoginPlugin;
impl Plugin for AccountLoginPlugin {
fn build(&self, app: &mut App) {
app.add_loopless_state(LoginState::None)
// UI system
.insert_resource(ui::InputsUserLogin::new())
.add_system_set(
ConditionSet::new()
.run_in_state(LoginState::Input)
.with_system(ui::account_login_ui)
.into(),
)
// Login system, as in calling the API
.add_enter_system(LoginState::LoggingIn, start_login_call)
.add_system_set(
ConditionSet::new()
.run_in_state(LoginState::LoggingIn)
.with_system(handle_login_call)
.into(),
);
}
}
/// Login State
#[derive(Clone, Eq, PartialEq, Debug, Hash)]
pub enum LoginState {
None,
Input,
LoggingIn,
}
struct LoginCallResponse {
token: ResponseToken,
user_infop: Option<ResponseUserInfoP>,
}
#[derive(Component)]
struct LoginCall(Task<LoginCallResponse>);
fn start_login_call(
mut commands: Commands,
cfg_dev: Res<CfgDev>,
inputs: Res<ui::InputsUserLogin>,
) {
let api_address = cfg_dev.api_server.clone();
let i = inputs.clone();
let thread_pool = AsyncComputeTaskPool::get();
let task = thread_pool.spawn(async move {
let token_response = api::user::token(api_address.clone(), i.email, i.password);
let mut user_infop_response = None;
match &token_response {
ResponseToken::Valid(res) => {
user_infop_response = Some(api::user::userinfop(
api_address,
res.token.clone(),
res.id.clone(),
));
}
ResponseToken::Error(_error) => {}
}
LoginCallResponse {
token: token_response,
user_infop: user_infop_response,
}
});
commands.spawn(LoginCall(task));
}
fn handle_login_call(
mut commands: Commands,
mut login_call_tasks: Query<(Entity, &mut LoginCall)>,
mut inputs: ResMut<ui::InputsUserLogin>,
mut cfg_user: ResMut<CfgUser>,
mut save_event_writer: EventWriter<SaveEvent>,
mut console: EventWriter<PrintConsoleLine>,
) {
let (entity, mut task) = login_call_tasks.single_mut();
if let Some(login_call_response) = future::block_on(future::poll_once(&mut task.0)) {
match login_call_response.token {
ResponseToken::Valid(res) => {
console.send(PrintConsoleLine::new(
format!("Logged in with: {}", inputs.email).into(),
));
match login_call_response.user_infop.unwrap() {
ResponseUserInfoP::Valid(infop) => {
*cfg_user = CfgUser {
logged_in: true,
user_token: res.token,
id: res.id,
username: infop.username,
email: infop.email,
};
save_event_writer.send(SaveEvent {
value: SaveEventValue::User(cfg_user.into_inner().clone()),
});
commands.insert_resource(NextState(LoginState::None));
commands.insert_resource(NextState(MenuState::AccountLoggedIn));
}
ResponseUserInfoP::Error(error) => {
console.send(PrintConsoleLine::new(format!(
"Something went wrong with getting the user information after logging in, got error: '{}'", error
).into()));
commands.insert_resource(NextState(LoginState::None));
commands.insert_resource(NextState(MenuState::AccountLoggedIn));
}
}
}
ResponseToken::Error(error) => {
inputs.error = error.error.description;
commands.insert_resource(NextState(LoginState::Input));
}
}
// Remove the task, since it's done now
commands.entity(entity).remove::<LoginCall>();
commands.entity(entity).despawn_recursive();
}
}
D => +0 -75
@@ 1,75 0,0 @@
/*
* This file is part of laurelin/client
* Copyright (C) 2023 Jonni Liljamo <jonni@liljamo.com>
*
* Licensed under GPL-3.0-only.
* See LICENSE for licensing information.
*/
use bevy::prelude::*;
use bevy_inspector_egui::bevy_egui::{egui, EguiContext};
use iyes_loopless::prelude::*;
use crate::util::eguipwd;
use crate::plugins::menu::MenuState;
use super::LoginState;
/// Login inputs
#[derive(Resource, Debug, Component, PartialEq, Eq, Clone)]
pub struct InputsUserLogin {
pub email: String,
pub password: String,
pub error: String,
}
impl InputsUserLogin {
pub fn new() -> Self {
Self {
email: "".to_string(),
password: "".to_string(),
error: "".to_string(),
}
}
}
pub fn account_login_ui(
mut commands: Commands,
mut egui_context: ResMut<EguiContext>,
mut inputs: ResMut<InputsUserLogin>,
) {
egui::Window::new("Login")
.collapsible(false)
.anchor(egui::Align2::CENTER_CENTER, egui::Vec2::ZERO)
.show(egui_context.ctx_mut(), |ui| {
ui.horizontal(|ui| {
ui.label("Email: ");
ui.text_edit_singleline(&mut inputs.email);
});
ui.horizontal(|ui| {
ui.label("Password: ");
ui.add(eguipwd::password(&mut inputs.password));
});
// Show an error if there is one
if !inputs.error.is_empty() {
ui.horizontal(|ui| {
ui.label(egui::RichText::new(inputs.error.clone()).color(egui::Color32::RED));
});
}
ui.with_layout(egui::Layout::right_to_left(egui::Align::Min), |ui| {
if ui.button("Cancel").clicked() {
commands.insert_resource(NextState(LoginState::None));
commands.insert_resource(NextState(MenuState::AccountLoggedOut));
}
if ui.button("Login").clicked() {
commands.insert_resource(NextState(LoginState::LoggingIn));
// Reset error field
inputs.error = "".to_string();
}
})
});
}
D => +0 -155
@@ 1,155 0,0 @@
/*
* This file is part of laurelin/client
* Copyright (C) 2023 Jonni Liljamo <jonni@liljamo.com>
*
* Licensed under GPL-3.0-only.
* See LICENSE for licensing information.
*/
use bevy::{
prelude::*,
tasks::{AsyncComputeTaskPool, Task},
};
use bevy_console::PrintConsoleLine;
use iyes_loopless::prelude::*;
use futures_lite::future;
use crate::{
api::{
self,
user::{ResponseRegister, ResponseToken},
},
cfg::{CfgDev, CfgUser},
plugins::config::{SaveEvent, SaveEventValue},
};
use super::MenuState;
pub mod ui;
pub struct AccountRegisterPlugin;
impl Plugin for AccountRegisterPlugin {
fn build(&self, app: &mut App) {
app.add_loopless_state(RegisterState::None)
// UI system
.insert_resource(ui::InputsUserRegister::new())
.add_system_set(
ConditionSet::new()
.run_in_state(RegisterState::Input)
.with_system(ui::account_register_ui)
.into(),
)
// Register system, as in calling the API
.add_enter_system(RegisterState::Registering, start_register_call)
.add_system_set(
ConditionSet::new()
.run_in_state(RegisterState::Registering)
.with_system(handle_register_call)
.into(),
);
}
}
/// Register State
#[derive(Clone, Eq, PartialEq, Debug, Hash)]
pub enum RegisterState {
None,
Input,
Registering,
}
struct RegisterCallResponse {
register: ResponseRegister,
token: ResponseToken,
}
#[derive(Component)]
struct RegisterCall(Task<RegisterCallResponse>);
fn start_register_call(
mut commands: Commands,
cfg_dev: Res<CfgDev>,
inputs: Res<ui::InputsUserRegister>,
) {
let api_address = cfg_dev.api_server.clone();
let i = inputs.clone();
let thread_pool = AsyncComputeTaskPool::get();
let task = thread_pool.spawn(async move {
let register_response = api::user::register(
api_address.clone(),
i.username.clone(),
i.email.clone(),
i.password.clone(),
);
// TODO: This is bad, if the above fails, this will too. Or maybe that doesn't matter?
let token_response =
api::user::token(api_address.clone(), i.email.clone(), i.password.clone());
RegisterCallResponse {
register: register_response,
token: token_response,
}
});
commands.spawn(RegisterCall(task));
}
fn handle_register_call(
mut commands: Commands,
mut register_call_tasks: Query<(Entity, &mut RegisterCall)>,
mut inputs: ResMut<ui::InputsUserRegister>,
mut cfg_user: ResMut<CfgUser>,
mut save_event_writer: EventWriter<SaveEvent>,
mut console: EventWriter<PrintConsoleLine>,
) {
let (entity, mut task) = register_call_tasks.single_mut();
if let Some(register_call_response) = future::block_on(future::poll_once(&mut task.0)) {
match register_call_response.register {
ResponseRegister::Valid(register_res) => {
console.send(PrintConsoleLine::new(
format!("Registered user: {}", register_res.username,).into(),
));
match register_call_response.token {
ResponseToken::Valid(token_res) => {
*cfg_user = CfgUser {
logged_in: true,
user_token: token_res.token,
id: register_res.id,
username: register_res.username,
email: inputs.email.clone(),
};
save_event_writer.send(SaveEvent {
value: SaveEventValue::User(cfg_user.into_inner().clone()),
});
commands.insert_resource(NextState(RegisterState::None));
commands.insert_resource(NextState(MenuState::AccountLoggedIn));
}
ResponseToken::Error(error) => {
// TODO: Handle? Is it possible to even get here without the server shitting itself between the register and token calls?
// And if the server does indeed shit itself between those calls, the user can just login normally, so 🤷♀️
console.send(PrintConsoleLine::new(format!(
"Something went wrong with getting the user token after registering, got error: '{}'", error
).into()));
commands.insert_resource(NextState(RegisterState::None));
commands.insert_resource(NextState(MenuState::AccountLoggedOut));
}
}
}
ResponseRegister::Error(error) => {
inputs.error = error.error.description;
commands.insert_resource(NextState(RegisterState::Input));
}
}
// Remove the task, since it's done now
commands.entity(entity).remove::<RegisterCall>();
commands.entity(entity).despawn_recursive();
}
}
D => +0 -102
@@ 1,102 0,0 @@
/*
* This file is part of laurelin/client
* Copyright (C) 2023 Jonni Liljamo <jonni@liljamo.com>
*
* Licensed under GPL-3.0-only.
* See LICENSE for licensing information.
*/
use bevy::prelude::*;
use bevy_inspector_egui::bevy_egui::{egui, EguiContext};
use iyes_loopless::prelude::*;
use crate::util::eguipwd;
use crate::plugins::menu::MenuState;
use super::RegisterState;
/// Register inputs
#[derive(Resource, Debug, Component, PartialEq, Eq, Clone)]
pub struct InputsUserRegister {
pub username: String,
pub email: String,
pub password: String,
pub password_confirm: String,
pub passwords_match: bool,
pub error: String,
}
impl InputsUserRegister {
pub fn new() -> Self {
Self {
username: "".to_string(),
email: "".to_string(),
password: "".to_string(),
password_confirm: "".to_string(),
passwords_match: true,
error: "".to_string(),
}
}
}
pub fn account_register_ui(
mut commands: Commands,
mut egui_context: ResMut<EguiContext>,
mut inputs: ResMut<InputsUserRegister>,
) {
egui::Window::new("Register")
.collapsible(false)
.anchor(egui::Align2::CENTER_CENTER, egui::Vec2::ZERO)
.show(egui_context.ctx_mut(), |ui| {
ui.horizontal(|ui| {
ui.label("Username: ");
ui.text_edit_singleline(&mut inputs.username);
});
ui.horizontal(|ui| {
ui.label("Email: ");
ui.text_edit_singleline(&mut inputs.email);
});
ui.horizontal(|ui| {
ui.label("Password: ");
ui.add(eguipwd::password(&mut inputs.password));
});
ui.horizontal(|ui| {
ui.label("Confirm password: ");
ui.add(eguipwd::password(&mut inputs.password_confirm));
});
inputs.passwords_match = inputs.password == inputs.password_confirm;
// Show an error if there is one
if !inputs.passwords_match {
ui.horizontal(|ui| {
ui.label(
egui::RichText::new("passwords don't match").color(egui::Color32::RED),
);
});
} else if !inputs.error.is_empty() {
ui.horizontal(|ui| {
ui.label(egui::RichText::new(inputs.error.clone()).color(egui::Color32::RED));
});
}
ui.with_layout(egui::Layout::right_to_left(egui::Align::Min), |ui| {
if ui.button("Cancel").clicked() {
commands.insert_resource(NextState(RegisterState::None));
commands.insert_resource(NextState(MenuState::AccountLoggedOut));
}
ui.add_enabled_ui(inputs.passwords_match, |ui| {
if ui.button("Register").clicked() {
commands.insert_resource(NextState(RegisterState::Registering));
// Reset error field
inputs.error = "".to_string();
}
});
})
});
}