anyhow used
This commit is contained in:
5
Cargo.lock
generated
5
Cargo.lock
generated
@@ -109,9 +109,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anyhow"
|
name = "anyhow"
|
||||||
version = "1.0.97"
|
version = "1.0.98"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f"
|
checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "arc-swap"
|
name = "arc-swap"
|
||||||
@@ -423,6 +423,7 @@ dependencies = [
|
|||||||
name = "client"
|
name = "client"
|
||||||
version = "0.3.13"
|
version = "0.3.13"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"common",
|
"common",
|
||||||
"crossterm",
|
"crossterm",
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ edition.workspace = true
|
|||||||
license.workspace = true
|
license.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
anyhow = "1.0.98"
|
||||||
async-trait = "0.1.88"
|
async-trait = "0.1.88"
|
||||||
common = { path = "../common" }
|
common = { path = "../common" }
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
use anyhow::{Context, Result};
|
||||||
use crossterm::event::{KeyCode, KeyModifiers};
|
use crossterm::event::{KeyCode, KeyModifiers};
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Default)]
|
#[derive(Debug, Deserialize, Default)]
|
||||||
@@ -43,11 +44,11 @@ pub struct ModeKeybindings {
|
|||||||
|
|
||||||
impl Config {
|
impl Config {
|
||||||
/// Loads the configuration from "config.toml" in the client crate directory.
|
/// Loads the configuration from "config.toml" in the client crate directory.
|
||||||
pub fn load() -> Result<Self, Box<dyn std::error::Error>> {
|
pub fn load() -> Result<Self> {
|
||||||
let manifest_dir = env!("CARGO_MANIFEST_DIR");
|
let manifest_dir = env!("CARGO_MANIFEST_DIR");
|
||||||
let config_path = Path::new(manifest_dir).join("config.toml");
|
let config_path = Path::new(manifest_dir).join("config.toml");
|
||||||
let config_str = std::fs::read_to_string(&config_path)
|
let config_str = std::fs::read_to_string(&config_path)
|
||||||
.map_err(|e| format!("Failed to read config file at {:?}: {}", config_path, e))?;
|
.with_context(|| format!("Failed to read config file at {:?}", config_path))?;
|
||||||
let config: Config = toml::from_str(&config_str)?;
|
let config: Config = toml::from_str(&config_str)?;
|
||||||
Ok(config)
|
Ok(config)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ use crate::ui::handlers::rat_state::UiStateHandler;
|
|||||||
use crate::ui::handlers::context::UiContext;
|
use crate::ui::handlers::context::UiContext;
|
||||||
use crate::ui::handlers::context::DialogPurpose;
|
use crate::ui::handlers::context::DialogPurpose;
|
||||||
use crate::functions::common::buffer;
|
use crate::functions::common::buffer;
|
||||||
use std::error::Error;
|
use anyhow::{Context, Result};
|
||||||
use crate::tui::{
|
use crate::tui::{
|
||||||
terminal::core::TerminalCore,
|
terminal::core::TerminalCore,
|
||||||
functions::{
|
functions::{
|
||||||
@@ -68,7 +68,7 @@ pub struct EventHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl EventHandler {
|
impl EventHandler {
|
||||||
pub async fn new(login_result_sender: mpsc::Sender<LoginResult>) -> Result<Self, Box<dyn Error + Send + Sync>> {
|
pub async fn new(login_result_sender: mpsc::Sender<LoginResult>) -> Result<Self> {
|
||||||
Ok(EventHandler {
|
Ok(EventHandler {
|
||||||
command_mode: false,
|
command_mode: false,
|
||||||
command_input: String::new(),
|
command_input: String::new(),
|
||||||
@@ -100,7 +100,7 @@ impl EventHandler {
|
|||||||
app_state: &mut AppState,
|
app_state: &mut AppState,
|
||||||
total_count: u64,
|
total_count: u64,
|
||||||
current_position: &mut u64,
|
current_position: &mut u64,
|
||||||
) -> Result<EventOutcome, Box<dyn std::error::Error>> {
|
) -> Result<EventOutcome> {
|
||||||
let current_mode = ModeManager::derive_mode(app_state, self);
|
let current_mode = ModeManager::derive_mode(app_state, self);
|
||||||
app_state.update_mode(current_mode);
|
app_state.update_mode(current_mode);
|
||||||
|
|
||||||
@@ -257,7 +257,9 @@ impl EventHandler {
|
|||||||
spawn(async move {
|
spawn(async move {
|
||||||
let login_outcome = match AuthClient::new().await {
|
let login_outcome = match AuthClient::new().await {
|
||||||
Ok(mut auth_client) => {
|
Ok(mut auth_client) => {
|
||||||
match auth_client.login(username, password).await {
|
match auth_client.login(username.clone(), password).await
|
||||||
|
.with_context(|| format!("Spawned login task failed for identifier: {}", username))
|
||||||
|
{
|
||||||
Ok(response) => login::LoginResult::Success(response),
|
Ok(response) => login::LoginResult::Success(response),
|
||||||
Err(e) => login::LoginResult::Failure(format!("{}", e)),
|
Err(e) => login::LoginResult::Failure(format!("{}", e)),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,20 +5,22 @@ use common::proto::multieko2::auth::{
|
|||||||
LoginRequest, LoginResponse,
|
LoginRequest, LoginResponse,
|
||||||
RegisterRequest, AuthResponse,
|
RegisterRequest, AuthResponse,
|
||||||
};
|
};
|
||||||
use std::error::Error;
|
use anyhow::{Context, Result};
|
||||||
|
|
||||||
pub struct AuthClient {
|
pub struct AuthClient {
|
||||||
client: AuthServiceClient<Channel>,
|
client: AuthServiceClient<Channel>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AuthClient {
|
impl AuthClient {
|
||||||
pub async fn new() -> Result<Self, Box<dyn Error + Send + Sync>> {
|
pub async fn new() -> Result<Self> {
|
||||||
let client = AuthServiceClient::connect("http://[::1]:50051").await?;
|
let client = AuthServiceClient::connect("http://[::1]:50051")
|
||||||
|
.await
|
||||||
|
.context("Failed to connect to auth service")?;
|
||||||
Ok(Self { client })
|
Ok(Self { client })
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Login user via gRPC.
|
/// Login user via gRPC.
|
||||||
pub async fn login(&mut self, identifier: String, password: String) -> Result<LoginResponse, Box<dyn std::error::Error>> {
|
pub async fn login(&mut self, identifier: String, password: String) -> Result<LoginResponse> {
|
||||||
let request = tonic::Request::new(LoginRequest { identifier, password });
|
let request = tonic::Request::new(LoginRequest { identifier, password });
|
||||||
let response = self.client.login(request).await?.into_inner();
|
let response = self.client.login(request).await?.into_inner();
|
||||||
Ok(response)
|
Ok(response)
|
||||||
@@ -32,7 +34,7 @@ impl AuthClient {
|
|||||||
password: Option<String>,
|
password: Option<String>,
|
||||||
password_confirmation: Option<String>,
|
password_confirmation: Option<String>,
|
||||||
role: Option<String>,
|
role: Option<String>,
|
||||||
) -> Result<AuthResponse, Box<dyn std::error::Error>> {
|
) -> Result<AuthResponse> {
|
||||||
let request = tonic::Request::new(RegisterRequest {
|
let request = tonic::Request::new(RegisterRequest {
|
||||||
username,
|
username,
|
||||||
email,
|
email,
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ use common::proto::multieko2::table_definition::{
|
|||||||
table_definition_client::TableDefinitionClient,
|
table_definition_client::TableDefinitionClient,
|
||||||
ProfileTreeResponse
|
ProfileTreeResponse
|
||||||
};
|
};
|
||||||
|
use anyhow::{Context, Result};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct GrpcClient {
|
pub struct GrpcClient {
|
||||||
@@ -19,7 +20,7 @@ pub struct GrpcClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl GrpcClient {
|
impl GrpcClient {
|
||||||
pub async fn new() -> Result<Self, Box<dyn std::error::Error>> {
|
pub async fn new() -> Result<Self> {
|
||||||
let adresar_client = AdresarClient::connect("http://[::1]:50051").await?;
|
let adresar_client = AdresarClient::connect("http://[::1]:50051").await?;
|
||||||
let table_structure_client = TableStructureServiceClient::connect("http://[::1]:50051").await?;
|
let table_structure_client = TableStructureServiceClient::connect("http://[::1]:50051").await?;
|
||||||
let table_definition_client = TableDefinitionClient::connect("http://[::1]:50051").await?;
|
let table_definition_client = TableDefinitionClient::connect("http://[::1]:50051").await?;
|
||||||
@@ -31,37 +32,37 @@ impl GrpcClient {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_adresar_count(&mut self) -> Result<u64, Box<dyn std::error::Error>> {
|
pub async fn get_adresar_count(&mut self) -> Result<u64> {
|
||||||
let request = tonic::Request::new(Empty::default());
|
let request = tonic::Request::new(Empty::default());
|
||||||
let response: CountResponse = self.adresar_client.get_adresar_count(request).await?.into_inner();
|
let response: CountResponse = self.adresar_client.get_adresar_count(request).await?.into_inner();
|
||||||
Ok(response.count as u64)
|
Ok(response.count as u64)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_adresar_by_position(&mut self, position: u64) -> Result<AdresarResponse, Box<dyn std::error::Error>> {
|
pub async fn get_adresar_by_position(&mut self, position: u64) -> Result<AdresarResponse> {
|
||||||
let request = tonic::Request::new(PositionRequest { position: position as i64 });
|
let request = tonic::Request::new(PositionRequest { position: position as i64 });
|
||||||
let response: AdresarResponse = self.adresar_client.get_adresar_by_position(request).await?.into_inner();
|
let response: AdresarResponse = self.adresar_client.get_adresar_by_position(request).await?.into_inner();
|
||||||
Ok(response)
|
Ok(response)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn post_adresar(&mut self, request: PostAdresarRequest) -> Result<tonic::Response<AdresarResponse>, Box<dyn std::error::Error>> {
|
pub async fn post_adresar(&mut self, request: PostAdresarRequest) -> Result<tonic::Response<AdresarResponse>> {
|
||||||
let request = tonic::Request::new(request);
|
let request = tonic::Request::new(request);
|
||||||
let response = self.adresar_client.post_adresar(request).await?;
|
let response = self.adresar_client.post_adresar(request).await?;
|
||||||
Ok(response)
|
Ok(response)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn put_adresar(&mut self, request: PutAdresarRequest) -> Result<tonic::Response<AdresarResponse>, Box<dyn std::error::Error>> {
|
pub async fn put_adresar(&mut self, request: PutAdresarRequest) -> Result<tonic::Response<AdresarResponse>> {
|
||||||
let request = tonic::Request::new(request);
|
let request = tonic::Request::new(request);
|
||||||
let response = self.adresar_client.put_adresar(request).await?;
|
let response = self.adresar_client.put_adresar(request).await?;
|
||||||
Ok(response)
|
Ok(response)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_table_structure(&mut self) -> Result<TableStructureResponse, Box<dyn std::error::Error>> {
|
pub async fn get_table_structure(&mut self) -> Result<TableStructureResponse> {
|
||||||
let request = tonic::Request::new(Empty::default());
|
let request = tonic::Request::new(Empty::default());
|
||||||
let response = self.table_structure_client.get_adresar_table_structure(request).await?;
|
let response = self.table_structure_client.get_adresar_table_structure(request).await?;
|
||||||
Ok(response.into_inner())
|
Ok(response.into_inner())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_profile_tree(&mut self) -> Result<ProfileTreeResponse, Box<dyn std::error::Error>> {
|
pub async fn get_profile_tree(&mut self) -> Result<ProfileTreeResponse> {
|
||||||
let request = tonic::Request::new(Empty::default());
|
let request = tonic::Request::new(Empty::default());
|
||||||
let response = self.table_definition_client.get_profile_tree(request).await?;
|
let response = self.table_definition_client.get_profile_tree(request).await?;
|
||||||
Ok(response.into_inner())
|
Ok(response.into_inner())
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ use crate::services::grpc_client::GrpcClient;
|
|||||||
use crate::state::pages::form::FormState;
|
use crate::state::pages::form::FormState;
|
||||||
use crate::tui::functions::common::form::SaveOutcome;
|
use crate::tui::functions::common::form::SaveOutcome;
|
||||||
use crate::state::app::state::AppState;
|
use crate::state::app::state::AppState;
|
||||||
|
use anyhow::{Context, Result};
|
||||||
|
|
||||||
pub struct UiService;
|
pub struct UiService;
|
||||||
|
|
||||||
@@ -13,7 +14,7 @@ impl UiService {
|
|||||||
app_state: &mut AppState,
|
app_state: &mut AppState,
|
||||||
) -> Result<Vec<String>, Box<dyn std::error::Error>> {
|
) -> Result<Vec<String>, Box<dyn std::error::Error>> {
|
||||||
// Fetch profile tree
|
// Fetch profile tree
|
||||||
let profile_tree = grpc_client.get_profile_tree().await?;
|
let profile_tree = grpc_client.get_profile_tree().await.context("Failed to get profile tree")?;
|
||||||
app_state.profile_tree = profile_tree;
|
app_state.profile_tree = profile_tree;
|
||||||
|
|
||||||
// Fetch table structure
|
// Fetch table structure
|
||||||
@@ -32,8 +33,8 @@ impl UiService {
|
|||||||
pub async fn initialize_adresar_count(
|
pub async fn initialize_adresar_count(
|
||||||
grpc_client: &mut GrpcClient,
|
grpc_client: &mut GrpcClient,
|
||||||
app_state: &mut AppState,
|
app_state: &mut AppState,
|
||||||
) -> Result<(), Box<dyn std::error::Error>> {
|
) -> Result<()> {
|
||||||
let total_count = grpc_client.get_adresar_count().await?;
|
let total_count = grpc_client.get_adresar_count().await.await.context("Failed to get adresar count")?;
|
||||||
app_state.update_total_count(total_count);
|
app_state.update_total_count(total_count);
|
||||||
app_state.update_current_position(total_count.saturating_add(1)); // Start in new entry mode
|
app_state.update_current_position(total_count.saturating_add(1)); // Start in new entry mode
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -42,8 +43,8 @@ impl UiService {
|
|||||||
pub async fn update_adresar_count(
|
pub async fn update_adresar_count(
|
||||||
grpc_client: &mut GrpcClient,
|
grpc_client: &mut GrpcClient,
|
||||||
app_state: &mut AppState,
|
app_state: &mut AppState,
|
||||||
) -> Result<(), Box<dyn std::error::Error>> {
|
) -> Result<()> {
|
||||||
let total_count = grpc_client.get_adresar_count().await?;
|
let total_count = grpc_client.get_adresar_count().await.context("Failed to get adresar by position")?;
|
||||||
app_state.update_total_count(total_count);
|
app_state.update_total_count(total_count);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -53,7 +54,7 @@ impl UiService {
|
|||||||
_app_state: &mut AppState,
|
_app_state: &mut AppState,
|
||||||
form_state: &mut FormState,
|
form_state: &mut FormState,
|
||||||
position: u64,
|
position: u64,
|
||||||
) -> Result<String, Box<dyn std::error::Error>> {
|
) -> Result<String> {
|
||||||
match grpc_client.get_adresar_by_position(position).await {
|
match grpc_client.get_adresar_by_position(position).await {
|
||||||
Ok(response) => {
|
Ok(response) => {
|
||||||
// Set the ID properly
|
// Set the ID properly
|
||||||
@@ -92,8 +93,8 @@ impl UiService {
|
|||||||
save_outcome: SaveOutcome,
|
save_outcome: SaveOutcome,
|
||||||
grpc_client: &mut GrpcClient,
|
grpc_client: &mut GrpcClient,
|
||||||
app_state: &mut AppState,
|
app_state: &mut AppState,
|
||||||
form_state: &mut FormState, // Needed to potentially update position/ID
|
form_state: &mut FormState,
|
||||||
) -> Result<(), Box<dyn std::error::Error>> {
|
) -> Result<()> {
|
||||||
match save_outcome {
|
match save_outcome {
|
||||||
SaveOutcome::CreatedNew(new_id) => {
|
SaveOutcome::CreatedNew(new_id) => {
|
||||||
// A new record was created, update the count!
|
// A new record was created, update the count!
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ use std::env;
|
|||||||
use common::proto::multieko2::table_definition::ProfileTreeResponse;
|
use common::proto::multieko2::table_definition::ProfileTreeResponse;
|
||||||
use crate::modes::handlers::mode_manager::AppMode;
|
use crate::modes::handlers::mode_manager::AppMode;
|
||||||
use crate::ui::handlers::context::DialogPurpose;
|
use crate::ui::handlers::context::DialogPurpose;
|
||||||
|
use anyhow::{Context, Result};
|
||||||
|
|
||||||
pub struct DialogState {
|
pub struct DialogState {
|
||||||
pub dialog_show: bool,
|
pub dialog_show: bool,
|
||||||
@@ -43,7 +44,7 @@ pub struct AppState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl AppState {
|
impl AppState {
|
||||||
pub fn new() -> Result<Self, Box<dyn std::error::Error>> {
|
pub fn new() -> Result<Self> {
|
||||||
let current_dir = env::current_dir()?
|
let current_dir = env::current_dir()?
|
||||||
.to_string_lossy()
|
.to_string_lossy()
|
||||||
.to_string();
|
.to_string();
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ use crate::state::pages::canvas_state::CanvasState;
|
|||||||
use crate::ui::handlers::context::DialogPurpose;
|
use crate::ui::handlers::context::DialogPurpose;
|
||||||
use crate::modes::handlers::event::EventOutcome;
|
use crate::modes::handlers::event::EventOutcome;
|
||||||
use common::proto::multieko2::auth::LoginResponse;
|
use common::proto::multieko2::auth::LoginResponse;
|
||||||
|
use anyhow::{Context, Result};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum LoginResult {
|
pub enum LoginResult {
|
||||||
@@ -19,13 +20,12 @@ pub enum LoginResult {
|
|||||||
|
|
||||||
/// Attempts to log the user in using the provided credentials via gRPC.
|
/// Attempts to log the user in using the provided credentials via gRPC.
|
||||||
/// Updates AuthState and AppState on success or failure.
|
/// Updates AuthState and AppState on success or failure.
|
||||||
/// (This is your existing function - remains unchanged)
|
|
||||||
pub async fn save(
|
pub async fn save(
|
||||||
auth_state: &mut AuthState,
|
auth_state: &mut AuthState,
|
||||||
login_state: &mut LoginState,
|
login_state: &mut LoginState,
|
||||||
auth_client: &mut AuthClient,
|
auth_client: &mut AuthClient,
|
||||||
app_state: &mut AppState,
|
app_state: &mut AppState,
|
||||||
) -> Result<String, Box<dyn std::error::Error>> {
|
) -> Result<String> {
|
||||||
let identifier = login_state.username.clone();
|
let identifier = login_state.username.clone();
|
||||||
let password = login_state.password.clone();
|
let password = login_state.password.clone();
|
||||||
|
|
||||||
@@ -40,7 +40,7 @@ pub async fn save(
|
|||||||
DialogPurpose::LoginFailed,
|
DialogPurpose::LoginFailed,
|
||||||
);
|
);
|
||||||
login_state.error_message = Some(error_message.clone());
|
login_state.error_message = Some(error_message.clone());
|
||||||
return Ok(error_message);
|
return Err(anyhow::anyhow!(error_message));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear previous error/dialog state before attempting
|
// Clear previous error/dialog state before attempting
|
||||||
@@ -48,7 +48,9 @@ pub async fn save(
|
|||||||
app_state.hide_dialog(); // Hide any previous dialog
|
app_state.hide_dialog(); // Hide any previous dialog
|
||||||
|
|
||||||
// Call the gRPC login method
|
// Call the gRPC login method
|
||||||
match auth_client.login(identifier, password).await {
|
match auth_client.login(identifier.clone(), password).await
|
||||||
|
.with_context(|| format!("gRPC login attempt failed for identifier: {}", identifier))
|
||||||
|
{
|
||||||
Ok(response) => {
|
Ok(response) => {
|
||||||
// Store authentication details using correct field names
|
// Store authentication details using correct field names
|
||||||
auth_state.auth_token = Some(response.access_token.clone());
|
auth_state.auth_token = Some(response.access_token.clone());
|
||||||
@@ -92,28 +94,11 @@ pub async fn save(
|
|||||||
login_state.set_has_unsaved_changes(true);
|
login_state.set_has_unsaved_changes(true);
|
||||||
login_state.username.clear();
|
login_state.username.clear();
|
||||||
login_state.password.clear();
|
login_state.password.clear();
|
||||||
Ok(format!("Login failed: {}", error_message))
|
Err(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Add this new function ---
|
|
||||||
/// Sets the stage for login: shows loading dialog and sets the pending flag.
|
|
||||||
/// Call this from the event handler when login is triggered.
|
|
||||||
pub async fn initiate_login(
|
|
||||||
app_state: &mut AppState,
|
|
||||||
login_state: &mut LoginState,
|
|
||||||
) -> Result<EventOutcome, Box<dyn std::error::Error>> {
|
|
||||||
// Show the loading dialog immediately
|
|
||||||
app_state.show_loading_dialog("Logging In", "Please wait...");
|
|
||||||
// Set the flag in LoginState to indicate the actual save should run next loop
|
|
||||||
login_state.login_request_pending = true;
|
|
||||||
// Return immediately to allow redraw
|
|
||||||
Ok(EventOutcome::Ok("Login initiated.".to_string()))
|
|
||||||
}
|
|
||||||
// --- End of new function ---
|
|
||||||
|
|
||||||
|
|
||||||
/// Reverts the login form fields to empty and returns to the previous screen (Intro).
|
/// Reverts the login form fields to empty and returns to the previous screen (Intro).
|
||||||
pub async fn revert(
|
pub async fn revert(
|
||||||
login_state: &mut LoginState,
|
login_state: &mut LoginState,
|
||||||
|
|||||||
@@ -7,13 +7,14 @@ use crossterm::{
|
|||||||
};
|
};
|
||||||
use ratatui::{backend::CrosstermBackend, Terminal};
|
use ratatui::{backend::CrosstermBackend, Terminal};
|
||||||
use std::io::{self, stdout, Write};
|
use std::io::{self, stdout, Write};
|
||||||
|
use anyhow::{Context, Result};
|
||||||
|
|
||||||
pub struct TerminalCore {
|
pub struct TerminalCore {
|
||||||
terminal: Terminal<CrosstermBackend<io::Stdout>>,
|
terminal: Terminal<CrosstermBackend<io::Stdout>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TerminalCore {
|
impl TerminalCore {
|
||||||
pub fn new() -> Result<Self, Box<dyn std::error::Error>> {
|
pub fn new() -> Result<Self> {
|
||||||
enable_raw_mode()?;
|
enable_raw_mode()?;
|
||||||
let mut stdout = stdout();
|
let mut stdout = stdout();
|
||||||
execute!(
|
execute!(
|
||||||
@@ -27,7 +28,7 @@ impl TerminalCore {
|
|||||||
Ok(Self { terminal })
|
Ok(Self { terminal })
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn draw<F>(&mut self, f: F) -> Result<(), Box<dyn std::error::Error>>
|
pub fn draw<F>(&mut self, f: F) -> Result<()>
|
||||||
where
|
where
|
||||||
F: FnOnce(&mut ratatui::Frame),
|
F: FnOnce(&mut ratatui::Frame),
|
||||||
{
|
{
|
||||||
@@ -35,7 +36,7 @@ impl TerminalCore {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn cleanup(&mut self) -> Result<(), Box<dyn std::error::Error>> {
|
pub fn cleanup(&mut self) -> Result<()> {
|
||||||
let backend = self.terminal.backend_mut();
|
let backend = self.terminal.backend_mut();
|
||||||
execute!(
|
execute!(
|
||||||
backend,
|
backend,
|
||||||
@@ -56,7 +57,7 @@ impl TerminalCore {
|
|||||||
pub fn set_cursor_style(
|
pub fn set_cursor_style(
|
||||||
&mut self,
|
&mut self,
|
||||||
style: SetCursorStyle,
|
style: SetCursorStyle,
|
||||||
) -> Result<(), Box<dyn std::error::Error>> {
|
) -> Result<()> {
|
||||||
execute!(
|
execute!(
|
||||||
self.terminal.backend_mut(),
|
self.terminal.backend_mut(),
|
||||||
style,
|
style,
|
||||||
@@ -65,7 +66,7 @@ impl TerminalCore {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn show_cursor(&mut self) -> Result<(), Box<dyn std::error::Error>> {
|
pub fn show_cursor(&mut self) -> Result<()> {
|
||||||
execute!(
|
execute!(
|
||||||
self.terminal.backend_mut(),
|
self.terminal.backend_mut(),
|
||||||
Show
|
Show
|
||||||
@@ -73,7 +74,7 @@ impl TerminalCore {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn hide_cursor(&mut self) -> Result<(), Box<dyn std::error::Error>> {
|
pub fn hide_cursor(&mut self) -> Result<()> {
|
||||||
execute!(
|
execute!(
|
||||||
self.terminal.backend_mut(),
|
self.terminal.backend_mut(),
|
||||||
Hide
|
Hide
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
// src/tui/terminal/event_reader.rs
|
// src/tui/terminal/event_reader.rs
|
||||||
|
|
||||||
use crossterm::event::{self, Event};
|
use crossterm::event::{self, Event};
|
||||||
|
use anyhow::{Context, Result};
|
||||||
|
|
||||||
pub struct EventReader;
|
pub struct EventReader;
|
||||||
|
|
||||||
@@ -9,7 +10,7 @@ impl EventReader {
|
|||||||
Self
|
Self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read_event(&self) -> Result<Event, Box<dyn std::error::Error>> {
|
pub fn read_event(&self) -> Result<Event> {
|
||||||
Ok(event::read()?)
|
Ok(event::read()?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,30 +18,28 @@ use crate::state::pages::intro::IntroState;
|
|||||||
use crate::state::app::buffer::BufferState;
|
use crate::state::app::buffer::BufferState;
|
||||||
use crate::state::app::buffer::AppView;
|
use crate::state::app::buffer::AppView;
|
||||||
use crate::state::app::state::AppState;
|
use crate::state::app::state::AppState;
|
||||||
use crate::ui::handlers::context::DialogPurpose; // <-- Add DialogPurpose import
|
use crate::ui::handlers::context::DialogPurpose;
|
||||||
// Import SaveOutcome
|
|
||||||
use crate::tui::terminal::{EventReader, TerminalCore};
|
use crate::tui::terminal::{EventReader, TerminalCore};
|
||||||
use crate::ui::handlers::render::render_ui;
|
use crate::ui::handlers::render::render_ui;
|
||||||
use crate::tui::functions::common::login;
|
|
||||||
use crate::tui::functions::common::login::LoginResult;
|
use crate::tui::functions::common::login::LoginResult;
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
use std::error::Error;
|
use anyhow::{Context, Result};
|
||||||
use crossterm::cursor::SetCursorStyle;
|
use crossterm::cursor::SetCursorStyle;
|
||||||
use crossterm::event as crossterm_event;
|
use crossterm::event as crossterm_event;
|
||||||
use tracing::{info, error};
|
use tracing::{info, error};
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
|
|
||||||
pub async fn run_ui() -> Result<(), Box<dyn Error + Send + Sync>> {
|
pub async fn run_ui() -> Result<()> {
|
||||||
let config = Config::load()?;
|
let config = Config::load().context("Failed to load configuration")?;
|
||||||
let theme = Theme::from_str(&config.colors.theme);
|
let theme = Theme::from_str(&config.colors.theme);
|
||||||
let mut terminal = TerminalCore::new()?;
|
let mut terminal = TerminalCore::new().context("Failed to initialize terminal")?;
|
||||||
let mut grpc_client = GrpcClient::new().await?;
|
let mut grpc_client = GrpcClient::new().await?;
|
||||||
let mut command_handler = CommandHandler::new();
|
let mut command_handler = CommandHandler::new();
|
||||||
|
|
||||||
// --- Channel for Login Results ---
|
// --- Channel for Login Results ---
|
||||||
let (login_result_sender, mut login_result_receiver) =
|
let (login_result_sender, mut login_result_receiver) =
|
||||||
mpsc::channel::<LoginResult>(1);
|
mpsc::channel::<LoginResult>(1);
|
||||||
let mut event_handler = EventHandler::new(login_result_sender.clone()).await?;
|
let mut event_handler = EventHandler::new(login_result_sender.clone()).await.context("Failed to create event handler")?;
|
||||||
let event_reader = EventReader::new();
|
let event_reader = EventReader::new();
|
||||||
|
|
||||||
let mut auth_state = AuthState::default();
|
let mut auth_state = AuthState::default();
|
||||||
@@ -50,16 +48,16 @@ pub async fn run_ui() -> Result<(), Box<dyn Error + Send + Sync>> {
|
|||||||
let mut intro_state = IntroState::default();
|
let mut intro_state = IntroState::default();
|
||||||
let mut admin_state = AdminState::default();
|
let mut admin_state = AdminState::default();
|
||||||
let mut buffer_state = BufferState::default();
|
let mut buffer_state = BufferState::default();
|
||||||
let mut app_state = AppState::new()?;
|
let mut app_state = AppState::new().context("Failed to create initial app state")?;
|
||||||
|
|
||||||
// Initialize app state with profile tree and table structure
|
// Initialize app state with profile tree and table structure
|
||||||
let column_names =
|
let column_names =
|
||||||
UiService::initialize_app_state(&mut grpc_client, &mut app_state)
|
UiService::initialize_app_state(&mut grpc_client, &mut app_state)
|
||||||
.await?;
|
.await.context("Failed to initialize app state from UI service")?;
|
||||||
let mut form_state = FormState::new(column_names);
|
let mut form_state = FormState::new(column_names);
|
||||||
|
|
||||||
// Fetch the total count of Adresar entries
|
// Fetch the total count of Adresar entries
|
||||||
UiService::initialize_adresar_count(&mut grpc_client, &mut app_state)
|
UiService::initialize_adresar_count(&mut grpc_client, &mut app_state).await
|
||||||
.await?;
|
.await?;
|
||||||
form_state.reset_to_empty();
|
form_state.reset_to_empty();
|
||||||
|
|
||||||
@@ -121,7 +119,7 @@ pub async fn run_ui() -> Result<(), Box<dyn Error + Send + Sync>> {
|
|||||||
current_fps,
|
current_fps,
|
||||||
&app_state,
|
&app_state,
|
||||||
);
|
);
|
||||||
})?;
|
}).context("Terminal draw call failed")?;
|
||||||
|
|
||||||
// --- Cursor Visibility Logic ---
|
// --- Cursor Visibility Logic ---
|
||||||
// (Keep existing cursor logic here - depends on state drawn above)
|
// (Keep existing cursor logic here - depends on state drawn above)
|
||||||
@@ -132,13 +130,13 @@ pub async fn run_ui() -> Result<(), Box<dyn Error + Send + Sync>> {
|
|||||||
AppMode::ReadOnly => {
|
AppMode::ReadOnly => {
|
||||||
if !app_state.ui.focus_outside_canvas { terminal.set_cursor_style(SetCursorStyle::SteadyBlock)?; }
|
if !app_state.ui.focus_outside_canvas { terminal.set_cursor_style(SetCursorStyle::SteadyBlock)?; }
|
||||||
else { terminal.set_cursor_style(SetCursorStyle::SteadyUnderScore)?; }
|
else { terminal.set_cursor_style(SetCursorStyle::SteadyUnderScore)?; }
|
||||||
terminal.show_cursor()?;
|
terminal.show_cursor().context("Failed to show cursor in ReadOnly mode")?;
|
||||||
}
|
}
|
||||||
AppMode::General => {
|
AppMode::General => {
|
||||||
if app_state.ui.focus_outside_canvas { terminal.set_cursor_style(SetCursorStyle::SteadyUnderScore)?; terminal.show_cursor()?; }
|
if app_state.ui.focus_outside_canvas { terminal.set_cursor_style(SetCursorStyle::SteadyUnderScore)?; terminal.show_cursor()?; }
|
||||||
else { terminal.hide_cursor()?; }
|
else { terminal.hide_cursor()?; }
|
||||||
}
|
}
|
||||||
AppMode::Command => { terminal.set_cursor_style(SetCursorStyle::SteadyUnderScore)?; terminal.show_cursor()?; }
|
AppMode::Command => { terminal.set_cursor_style(SetCursorStyle::SteadyUnderScore)?; terminal.show_cursor().context("Failed to show cursor in Command mode")?; }
|
||||||
}
|
}
|
||||||
// --- End Cursor Visibility Logic ---
|
// --- End Cursor Visibility Logic ---
|
||||||
|
|
||||||
@@ -150,7 +148,7 @@ pub async fn run_ui() -> Result<(), Box<dyn Error + Send + Sync>> {
|
|||||||
let mut event_outcome_result = Ok(EventOutcome::Ok(String::new()));
|
let mut event_outcome_result = Ok(EventOutcome::Ok(String::new()));
|
||||||
// Poll for events *after* drawing and checking pending actions
|
// Poll for events *after* drawing and checking pending actions
|
||||||
if crossterm_event::poll(std::time::Duration::from_millis(20))? {
|
if crossterm_event::poll(std::time::Duration::from_millis(20))? {
|
||||||
let event = event_reader.read_event()?;
|
let event = event_reader.read_event().context("Failed to read terminal event")?;
|
||||||
event_outcome_result = event_handler
|
event_outcome_result = event_handler
|
||||||
.handle_event(
|
.handle_event(
|
||||||
event,
|
event,
|
||||||
@@ -308,7 +306,7 @@ pub async fn run_ui() -> Result<(), Box<dyn Error + Send + Sync>> {
|
|||||||
&mut form_state,
|
&mut form_state,
|
||||||
current_position_to_load,
|
current_position_to_load,
|
||||||
)
|
)
|
||||||
.await?;
|
.await.with_context(|| format!("Failed to load adresar by position: {}", current_position_to_load))?;
|
||||||
|
|
||||||
let current_input = form_state.get_current_input();
|
let current_input = form_state.get_current_input();
|
||||||
let max_cursor_pos = if !event_handler.is_edit_mode
|
let max_cursor_pos = if !event_handler.is_edit_mode
|
||||||
|
|||||||
Reference in New Issue
Block a user