From 30a8b45a90018386b5da7d5d1eeff4db3d2987c2 Mon Sep 17 00:00:00 2001 From: Daniel Boros Date: Thu, 9 May 2024 23:35:43 +0200 Subject: [PATCH 01/28] feat: improve error handling --- common/src/enums.rs | 35 +++++++++++++ src-tauri/Cargo.toml | 3 ++ src-tauri/src/drivers/postgresql.rs | 81 +++++++++++++++++++++++++---- src-tauri/src/main.rs | 8 +++ src-tauri/src/utils.rs | 1 + src/context_menu/mod.rs | 3 +- src/context_menu/siderbar.rs | 19 ------- src/hooks/mod.rs | 3 +- src/hooks/use_context_menu.rs | 31 ----------- src/invoke.rs | 3 +- src/sidebar/index.rs | 8 +-- src/store/projects.rs | 29 +++++++++-- 12 files changed, 148 insertions(+), 76 deletions(-) delete mode 100644 src/context_menu/siderbar.rs delete mode 100644 src/hooks/use_context_menu.rs diff --git a/common/src/enums.rs b/common/src/enums.rs index 1ebb7e6..9f3fe78 100644 --- a/common/src/enums.rs +++ b/common/src/enums.rs @@ -27,4 +27,39 @@ pub enum ProjectConnectionStatus { Connected, #[default] Disconnected, + Failed, } + +impl std::error::Error for ProjectConnectionStatus {} +impl Display for ProjectConnectionStatus { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ProjectConnectionStatus::Connected => write!(f, "Connected"), + ProjectConnectionStatus::Disconnected => write!(f, "Disconnected"), + ProjectConnectionStatus::Failed => write!(f, "Failed"), + } + } +} + +use std::fmt; + +#[derive(Debug, Serialize, Deserialize)] +pub enum PostgresqlError { + ConnectionTimeout, + ConnectionError, + QueryTimeout, + QueryError, +} + +impl std::error::Error for PostgresqlError {} +impl fmt::Display for PostgresqlError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + &PostgresqlError::ConnectionTimeout => write!(f, "ConnectionTimeout"), + &PostgresqlError::ConnectionError => write!(f, "ConnectionError"), + &PostgresqlError::QueryTimeout => write!(f, "QueryTimeout"), + &PostgresqlError::QueryError => write!(f, "QueryError"), + } + } +} + diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 9409a8d..936021d 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -21,6 +21,9 @@ tokio = "1.36.0" tokio-postgres = "0.7.10" chrono = "0.4.31" sled = "0.34.7" +anyhow = "1.0.83" +tracing = "0.1.40" +tracing-subscriber = { version = "0.3.18", features = ["fmt"] } diff --git a/src-tauri/src/drivers/postgresql.rs b/src-tauri/src/drivers/postgresql.rs index bb20020..6c748e7 100644 --- a/src-tauri/src/drivers/postgresql.rs +++ b/src-tauri/src/drivers/postgresql.rs @@ -1,7 +1,8 @@ -use std::time::Instant; +use std::{sync::Arc, time::Instant}; -use common::projects::postgresql::PostgresqlRelation; +use common::{enums::PostgresqlError, projects::postgresql::PostgresqlRelation}; use tauri::{AppHandle, Manager, Result, State}; +use tokio::{sync::Mutex, time as tokio_time}; use tokio_postgres::{connect, NoTls}; use crate::{utils::reflective_get, AppState}; @@ -13,24 +14,82 @@ pub async fn postgresql_connector( app: AppHandle, ) -> Result> { let app_state = app.state::(); - let (client, connection) = connect(key, NoTls).await.expect("connection error"); - tokio::spawn(async move { - if let Err(e) = connection.await { - eprintln!("connection error: {}", e); + let connection = tokio_time::timeout(tokio_time::Duration::from_secs(10), connect(key, NoTls)) + .await + .map_err(|_| PostgresqlError::ConnectionTimeout); + + if connection.is_err() { + tracing::error!("Postgres connection timeout error!"); + return Err(tauri::Error::Io(std::io::Error::new( + std::io::ErrorKind::Other, + PostgresqlError::ConnectionTimeout, + ))); + } + + let connection = connection.unwrap(); + if connection.is_err() { + tracing::error!("Postgres connection error!"); + return Err(tauri::Error::Io(std::io::Error::new( + std::io::ErrorKind::Other, + PostgresqlError::ConnectionError, + ))); + } + + let is_connection_error = Arc::new(Mutex::new(false)); + let (client, connection) = connection.unwrap(); + tracing::info!("Postgres connection established!"); + + // check if connection has some error + tokio::spawn({ + let is_connection_error = Arc::clone(&is_connection_error); + async move { + if let Err(e) = connection.await { + tracing::info!("Postgres connection error: {:?}", e); + *is_connection_error.lock().await = true; + } } }); - let schemas = client - .query( + if *is_connection_error.lock().await { + tracing::error!("Postgres connection error!"); + return Err(tauri::Error::Io(std::io::Error::new( + std::io::ErrorKind::Other, + PostgresqlError::ConnectionError, + ))); + } + + let schemas = tokio_time::timeout( + tokio_time::Duration::from_secs(30), + client.query( r#" SELECT schema_name FROM information_schema.schemata WHERE schema_name NOT IN ('pg_catalog', 'information_schema'); "#, &[], - ) - .await - .unwrap(); + ), + ) + .await + .map_err(|_| PostgresqlError::QueryTimeout); + + if schemas.is_err() { + tracing::error!("Postgres schema query timeout error!"); + return Err(tauri::Error::Io(std::io::Error::new( + std::io::ErrorKind::Other, + PostgresqlError::QueryTimeout, + ))); + } + + let schemas = schemas.unwrap(); + if schemas.is_err() { + tracing::error!("Postgres schema query error!"); + return Err(tauri::Error::Io(std::io::Error::new( + std::io::ErrorKind::Other, + PostgresqlError::QueryError, + ))); + } + + let schemas = schemas.unwrap(); let schemas = schemas.iter().map(|r| r.get(0)).collect(); let mut clients = app_state.client.lock().await; let clients = clients.as_mut().unwrap(); diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 5b7796c..13d1c0c 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -13,6 +13,7 @@ use std::{collections::BTreeMap, sync::Arc}; use tauri::Manager; use tokio::sync::Mutex; use tokio_postgres::Client; +use tracing::Level; use utils::create_or_open_local_db; pub struct AppState { @@ -32,6 +33,13 @@ impl Default for AppState { } fn main() { + tracing_subscriber::fmt() + .with_file(true) + .with_line_number(true) + .with_level(true) + .with_max_level(Level::INFO) + .init(); + tauri::Builder::default() .manage(AppState::default()) .setup(|app| { diff --git a/src-tauri/src/utils.rs b/src-tauri/src/utils.rs index 6600032..1042013 100644 --- a/src-tauri/src/utils.rs +++ b/src-tauri/src/utils.rs @@ -60,3 +60,4 @@ pub fn reflective_get(row: &Row, index: usize) -> String { }; value.unwrap_or("null".to_string()) } + diff --git a/src/context_menu/mod.rs b/src/context_menu/mod.rs index d963c78..139597f 100644 --- a/src/context_menu/mod.rs +++ b/src/context_menu/mod.rs @@ -1 +1,2 @@ -pub mod siderbar; + + diff --git a/src/context_menu/siderbar.rs b/src/context_menu/siderbar.rs deleted file mode 100644 index 786fc14..0000000 --- a/src/context_menu/siderbar.rs +++ /dev/null @@ -1,19 +0,0 @@ -use leptos::ev::MouseEvent; - -use crate::invoke::{InvokeContextMenuArgs, InvokeContextMenuItem, InvokeContextMenuPosition}; - -pub fn context_menu<'a>(event: &MouseEvent) -> InvokeContextMenuArgs<'a> { - InvokeContextMenuArgs { - pos: Some(InvokeContextMenuPosition { - x: event.x() as f64, - y: event.y() as f64, - is_absolute: Some(true), - }), - items: Some(vec![InvokeContextMenuItem { - label: Some("test"), - event: Some("my_first_item"), - payload: Some("test2"), - ..Default::default() - }]), - } -} diff --git a/src/hooks/mod.rs b/src/hooks/mod.rs index f83fd17..139597f 100644 --- a/src/hooks/mod.rs +++ b/src/hooks/mod.rs @@ -1 +1,2 @@ -pub mod use_context_menu; + + diff --git a/src/hooks/use_context_menu.rs b/src/hooks/use_context_menu.rs deleted file mode 100644 index 2f73d47..0000000 --- a/src/hooks/use_context_menu.rs +++ /dev/null @@ -1,31 +0,0 @@ -use futures::StreamExt; -use leptos::{html::*, *}; -use leptos_use::use_event_listener; -use tauri_sys::{event::listen, tauri::invoke}; -use web_sys::MouseEvent; - -use crate::invoke::{Invoke, InvokeContextMenuArgs}; - -pub fn use_context_menu(f: F) -> NodeRef
-where - F: Fn(&MouseEvent) -> InvokeContextMenuArgs<'static> + 'static + Clone, -{ - let node_ref = create_node_ref(); - let _ = use_event_listener(node_ref, ev::contextmenu, move |event| { - let f = f.clone(); - spawn_local(async move { - invoke::<_, ()>(&Invoke::plugin_context_menu.to_string(), &f(&event)) - .await - .unwrap(); - }); - }); - - spawn_local(async move { - let mut evt = listen::("my_first_item").await.expect("error"); - while let Some(event) = evt.next().await { - logging::log!("{:?}", event.payload); - } - }); - - node_ref -} diff --git a/src/invoke.rs b/src/invoke.rs index 215ac47..22b1b0a 100644 --- a/src/invoke.rs +++ b/src/invoke.rs @@ -9,7 +9,6 @@ pub enum Invoke { delete_query, insert_project, insert_query, - plugin_context_menu, postgresql_connector, select_projects, select_queries, @@ -26,7 +25,6 @@ impl Display for Invoke { Invoke::insert_project => write!(f, "insert_project"), Invoke::insert_query => write!(f, "insert_query"), Invoke::postgresql_connector => write!(f, "postgresql_connector"), - Invoke::plugin_context_menu => write!(f, "plugin:context_menu|show_context_menu"), Invoke::select_projects => write!(f, "select_projects"), Invoke::select_queries => write!(f, "select_queries"), Invoke::select_schema_relations => write!(f, "select_schema_relations"), @@ -136,3 +134,4 @@ pub struct InvokeContextMenuPosition { pub y: f64, pub is_absolute: Option, } + diff --git a/src/sidebar/index.rs b/src/sidebar/index.rs index 8b84fef..7f15340 100644 --- a/src/sidebar/index.rs +++ b/src/sidebar/index.rs @@ -4,8 +4,6 @@ use leptos_use::{use_document, use_event_listener}; use tauri_sys::tauri::invoke; use crate::{ - context_menu::siderbar::context_menu, - hooks::use_context_menu, invoke::{Invoke, InvokeSelectProjectsArgs}, modals::connection::Connection, store::projects::ProjectsStore, @@ -16,7 +14,6 @@ use super::{project::Project, queries::Queries}; #[component] pub fn Sidebar() -> impl IntoView { let projects_state = use_context::().unwrap(); - let node_ref = use_context_menu::use_context_menu(context_menu); let show = create_rw_signal(false); let _ = use_event_listener(use_document(), ev::keydown, move |event| { if event.key() == "Escape" { @@ -37,10 +34,7 @@ pub fn Sidebar() -> impl IntoView { ); view! { -
+
diff --git a/src/store/projects.rs b/src/store/projects.rs index 7e976ea..253bdfa 100644 --- a/src/store/projects.rs +++ b/src/store/projects.rs @@ -1,10 +1,10 @@ use std::collections::BTreeMap; use common::{ - enums::{Project, ProjectConnectionStatus}, + enums::{PostgresqlError, Project, ProjectConnectionStatus}, projects::postgresql::PostgresqlRelation, }; -use leptos::{create_rw_signal, error::Result, RwSignal, SignalGet, SignalUpdate}; +use leptos::{create_rw_signal, error::Result, logging::log, RwSignal, SignalGet, SignalUpdate}; use tauri_sys::tauri::invoke; use crate::invoke::{ @@ -154,14 +154,34 @@ impl ProjectsStore { async fn postgresql_schema_selector(&self, project_name: &str) -> Result> { let connection_string = self.create_project_connection_string(project_name); - let mut schemas = invoke::<_, Vec>( + log!("connection_string: {}", connection_string.clone()); + let schemas = invoke::<_, Vec>( &Invoke::postgresql_connector.to_string(), &InvokePostgresConnectionArgs { project_name, key: &connection_string, }, ) - .await?; + .await + .map_err(|err| match err { + tauri_sys::Error::Command(command) => { + let error = serde_json::from_str::(&command).unwrap(); + + match error { + PostgresqlError::ConnectionTimeout => PostgresqlError::ConnectionTimeout, + PostgresqlError::ConnectionError => PostgresqlError::ConnectionError, + PostgresqlError::QueryError => PostgresqlError::QueryError, + PostgresqlError::QueryTimeout => PostgresqlError::QueryTimeout, + } + } + _ => PostgresqlError::ConnectionError, + }); + + if schemas.is_err() { + return Err(schemas.unwrap_err().into()); + } + + let mut schemas = schemas.unwrap(); schemas.sort(); Ok(schemas) } @@ -192,3 +212,4 @@ impl ProjectsStore { Ok((tables, relations)) } } + From 21df8422d4abfe8e0f9841cdb1ca93982adb3e0f Mon Sep 17 00:00:00 2001 From: Daniel Boros Date: Fri, 10 May 2024 00:22:24 +0200 Subject: [PATCH 02/28] feat: add error modal --- common/src/enums.rs | 2 +- src/app.rs | 7 +++++++ src/modals/error.rs | 20 ++++++++++++++++++++ src/modals/mod.rs | 2 ++ src/sidebar/project.rs | 14 +++++++++++++- src/store/projects.rs | 20 +++++++++++++++----- 6 files changed, 58 insertions(+), 7 deletions(-) create mode 100644 src/modals/error.rs diff --git a/common/src/enums.rs b/common/src/enums.rs index 9f3fe78..0eca722 100644 --- a/common/src/enums.rs +++ b/common/src/enums.rs @@ -43,7 +43,7 @@ impl Display for ProjectConnectionStatus { use std::fmt; -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, Clone, Copy)] pub enum PostgresqlError { ConnectionTimeout, ConnectionError, diff --git a/src/app.rs b/src/app.rs index e69511d..66e8bfe 100644 --- a/src/app.rs +++ b/src/app.rs @@ -19,12 +19,19 @@ use crate::{ // TODO: help to add custom langunage support // https://github.com/abesto/clox-rs/blob/def4bed61a1c1c6b5d84a67284549a6343c8cd06/web/src/monaco_lox.rs +#[derive(Default, Clone)] +pub struct ErrorModal { + pub message: String, + pub show: RwSignal, +} + #[component] pub fn App() -> impl IntoView { provide_context(QueryStore::default()); provide_context(ProjectsStore::default()); provide_context(create_rw_signal(QueryTableLayout::Grid)); provide_context(create_rw_signal(0.0f32)); + provide_context(ErrorModal::default()); provide_context(ActiveProjectStore::default()); provide_context(TabsStore::default()); let mut tabs = use_context::().unwrap(); diff --git a/src/modals/error.rs b/src/modals/error.rs new file mode 100644 index 0000000..2796269 --- /dev/null +++ b/src/modals/error.rs @@ -0,0 +1,20 @@ +use leptos::*; +use thaw::{Modal, ModalFooter}; + +#[component] +pub fn Error(show: RwSignal, message: String, on_click: F) -> impl IntoView +where + F: Fn() + Copy + Clone + 'static, +{ + view! { + +

{message}

+ + + +
+ } +} + diff --git a/src/modals/mod.rs b/src/modals/mod.rs index d34fe1c..d842105 100644 --- a/src/modals/mod.rs +++ b/src/modals/mod.rs @@ -1,2 +1,4 @@ pub mod connection; pub mod custom_query; +pub mod error; + diff --git a/src/sidebar/project.rs b/src/sidebar/project.rs index b86c47c..b1420fe 100644 --- a/src/sidebar/project.rs +++ b/src/sidebar/project.rs @@ -1,6 +1,10 @@ use leptos::*; -use crate::store::{active_project::ActiveProjectStore, projects::ProjectsStore}; +use crate::{ + app::ErrorModal, + modals::error::Error as Modal, + store::{active_project::ActiveProjectStore, projects::ProjectsStore}, +}; use super::schemas::Schemas; @@ -8,6 +12,7 @@ use super::schemas::Schemas; pub fn Project(project: String) -> impl IntoView { let projects_store = use_context::().unwrap(); let active_project_store = use_context::().unwrap(); + let error_modal = use_context::().unwrap(); let (show_schemas, set_show_schemas) = create_signal(false); let delete_project = create_action(move |(projects_store, project): &(ProjectsStore, String)| { let projects_store = *projects_store; @@ -17,8 +22,15 @@ pub fn Project(project: String) -> impl IntoView { } }); + let on_click = move || { + error_modal.show.update(|prev| *prev = false); + set_show_schemas(false); + active_project_store.0.set(None); + }; + view! {
+
-
- Loading...

} - }> + //
+ // Loading...

} + // }> - { - let project = project.clone(); - view! { - - - - } - } + // { + // let project = project.clone(); + // view! { + // + // + // + // } + // } -
-
+ //
+ //
} } diff --git a/src/sidebar/schemas.rs b/src/sidebar/schemas.rs index 474de6f..27067c9 100644 --- a/src/sidebar/schemas.rs +++ b/src/sidebar/schemas.rs @@ -11,19 +11,22 @@ pub fn Schemas(project: String) -> impl IntoView { || {}, move |_| { let project = project.clone(); - async move { projects_store.connect(&project).await.unwrap() } + async move { + todo!(); + //projects_store.connect(&project).await.unwrap() + } }, ); view! { - } - } - /> + // } + // } + // /> } } diff --git a/src/sidebar/table.rs b/src/sidebar/table.rs index 308d621..3d48a8f 100644 --- a/src/sidebar/table.rs +++ b/src/sidebar/table.rs @@ -23,7 +23,7 @@ pub fn Table(table: (String, String), project: String, schema: String) -> impl I .lock() .await .set_editor_value(&format!("SELECT * FROM {}.{} LIMIT 100;", schema, table)); - tabs_store.lock().await.run_query().await.unwrap() + //tabs_store.lock().await.run_query().await.unwrap() } }, ); diff --git a/src/store/projects.rs b/src/store/projects.rs index 8b8fb93..72f587e 100644 --- a/src/store/projects.rs +++ b/src/store/projects.rs @@ -152,46 +152,46 @@ impl ProjectsStore { Ok(()) } - async fn postgresql_schema_selector(&self, project_name: &str) -> Result> { - let connection_string = self.create_project_connection_string(project_name); - let schemas = invoke::<_, Vec>( - &Invoke::pgsql_connector.to_string(), - &InvokePostgresConnectionArgs { - project_name, - key: &connection_string, - }, - ) - .await - .map_err(|err| match err { - tauri_sys::Error::Command(command) => { - if command.contains("ConnectionTimeout") { - PostgresqlError::ConnectionTimeout - } else if command.contains("ConnectionError") { - PostgresqlError::ConnectionError - } else if command.contains("QueryError") { - PostgresqlError::QueryError - } else if command.contains("QueryTimeout") { - PostgresqlError::QueryTimeout - } else { - PostgresqlError::ConnectionError - } - } - _ => PostgresqlError::ConnectionError, - }); - - if schemas.is_err() { - let mut error_modal = use_context::().unwrap(); - log!("err"); - let error_message = schemas.clone().unwrap_err().to_string(); - error_modal.show.update(|prev| *prev = true); - error_modal.message = error_message; - return Err(schemas.unwrap_err().into()); - } + // async fn postgresql_schema_selector(&self, project_name: &str) -> Result> { + // let connection_string = self.create_project_connection_string(project_name); + // let schemas = invoke::<_, Vec>( + // &Invoke::pgsql_connector.to_string(), + // &InvokePostgresConnectionArgs { + // project_name, + // key: &connection_string, + // }, + // ) + // .await + // .map_err(|err| match err { + // tauri_sys::Error::Command(command) => { + // if command.contains("ConnectionTimeout") { + // PostgresqlError::ConnectionTimeout + // } else if command.contains("ConnectionError") { + // PostgresqlError::ConnectionError + // } else if command.contains("QueryError") { + // PostgresqlError::QueryError + // } else if command.contains("QueryTimeout") { + // PostgresqlError::QueryTimeout + // } else { + // PostgresqlError::ConnectionError + // } + // } + // _ => PostgresqlError::ConnectionError, + // }); + + // if schemas.is_err() { + // let mut error_modal = use_context::().unwrap(); + // log!("err"); + // let error_message = schemas.clone().unwrap_err().to_string(); + // error_modal.show.update(|prev| *prev = true); + // error_modal.message = error_message; + // return Err(schemas.unwrap_err().into()); + // } - let mut schemas = schemas.unwrap(); - schemas.sort(); - Ok(schemas) - } + // let mut schemas = schemas.unwrap(); + // schemas.sort(); + // Ok(schemas) + // } async fn postgresql_table_selector( &self, @@ -199,7 +199,7 @@ impl ProjectsStore { schema: &str, ) -> Result<(Vec<(String, String)>, Vec)> { let tables = invoke::<_, Vec<(String, String)>>( - &Invoke::select_schema_tables.to_string(), + &Invoke::pgsql_load_tables.to_string(), &InvokeSchemaTablesArgs { project_name, schema, @@ -208,7 +208,7 @@ impl ProjectsStore { .await?; let relations = invoke::<_, Vec>( - &Invoke::select_schema_relations.to_string(), + &Invoke::pgsql_load_relations.to_string(), &InvokeSchemaRelationsArgs { project_name, schema, diff --git a/src/store/tabs.rs b/src/store/tabs.rs index 6584b5e..2ef200a 100644 --- a/src/store/tabs.rs +++ b/src/store/tabs.rs @@ -53,44 +53,44 @@ impl TabsStore { } } - pub async fn run_query(&self) -> Result<()> { - self.is_loading.set(true); - let active_project = use_context::().unwrap(); - let active_project = active_project.0.get().unwrap(); - let projects_store = use_context::().unwrap(); - projects_store.connect(&active_project).await?; - let active_editor = self.select_active_editor(); - let position = active_editor - .borrow() - .as_ref() - .unwrap() - .as_ref() - .get_position() - .unwrap(); - let sql = self.select_active_editor_value(); - let sql = self - .find_query_for_line(&sql, position.line_number()) - .unwrap(); - let (cols, rows, elasped) = invoke::<_, (Vec, Vec>, f32)>( - &Invoke::select_sql_result.to_string(), - &InvokeSqlResultArgs { - project_name: &active_project, - sql: &sql.query, - }, - ) - .await?; - let sql_timer = use_context::>().unwrap(); - sql_timer.set(elasped); - self.sql_results.update(|prev| { - let index = self.convert_selected_tab_to_index(); - match prev.get_mut(index) { - Some(sql_result) => *sql_result = (cols, rows), - None => prev.push((cols, rows)), - } - }); - self.is_loading.set(false); - Ok(()) - } + // pub async fn run_query(&self) -> Result<()> { + // self.is_loading.set(true); + // let active_project = use_context::().unwrap(); + // let active_project = active_project.0.get().unwrap(); + // let projects_store = use_context::().unwrap(); + // projects_store.(&active_project).await?; + // let active_editor = self.select_active_editor(); + // let position = active_editor + // .borrow() + // .as_ref() + // .unwrap() + // .as_ref() + // .get_position() + // .unwrap(); + // let sql = self.select_active_editor_value(); + // let sql = self + // .find_query_for_line(&sql, position.line_number()) + // .unwrap(); + // let (cols, rows, elasped) = invoke::<_, (Vec, Vec>, f32)>( + // &Invoke::pgsql_run_query.to_string(), + // &InvokeSqlResultArgs { + // project_name: &active_project, + // sql: &sql.query, + // }, + // ) + // .await?; + // let sql_timer = use_context::>().unwrap(); + // sql_timer.set(elasped); + // self.sql_results.update(|prev| { + // let index = self.convert_selected_tab_to_index(); + // match prev.get_mut(index) { + // Some(sql_result) => *sql_result = (cols, rows), + // None => prev.push((cols, rows)), + // } + // }); + // self.is_loading.set(false); + // Ok(()) + // } pub fn load_query(&self, key: &str) -> Result<()> { let active_project = use_context::().unwrap(); From 5a6f5d5f8d09f873945ae45bf779484ddbbfa15a Mon Sep 17 00:00:00 2001 From: Daniel Boros Date: Sat, 11 May 2024 14:36:43 +0200 Subject: [PATCH 06/28] refactor: driver connection logic and performance --- common/src/drivers/mod.rs | 1 - common/src/drivers/postgresql.rs | 21 --- common/src/enums.rs | 7 - common/src/lib.rs | 3 +- common/src/projects/mod.rs | 1 - common/src/projects/postgresql.rs | 36 ----- src-tauri/src/dbs/project.rs | 30 ++-- src-tauri/src/drivers/postgresql.rs | 92 ++++++------ src/app.rs | 102 +++++++------- src/driver_components/mod.rs | 2 + src/driver_components/pgsql.rs | 145 +++++++++++++++++++ src/drivers/pgsql.rs | 59 ++++---- src/invoke.rs | 76 +++++----- src/main.rs | 1 + src/modals/connection.rs | 54 +++----- src/modals/custom_query.rs | 35 +++-- src/sidebar/index.rs | 18 +-- src/sidebar/project.rs | 91 ++++++------ src/sidebar/tables.rs | 17 +-- src/store/projects.rs | 208 ++-------------------------- src/store/query.rs | 7 +- 21 files changed, 449 insertions(+), 557 deletions(-) delete mode 100644 common/src/drivers/mod.rs delete mode 100644 common/src/drivers/postgresql.rs delete mode 100644 common/src/projects/mod.rs delete mode 100644 common/src/projects/postgresql.rs create mode 100644 src/driver_components/mod.rs create mode 100644 src/driver_components/pgsql.rs diff --git a/common/src/drivers/mod.rs b/common/src/drivers/mod.rs deleted file mode 100644 index 6aa782e..0000000 --- a/common/src/drivers/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod postgresql; diff --git a/common/src/drivers/postgresql.rs b/common/src/drivers/postgresql.rs deleted file mode 100644 index ef8c41d..0000000 --- a/common/src/drivers/postgresql.rs +++ /dev/null @@ -1,21 +0,0 @@ -use serde::{Deserialize, Serialize}; - -#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] -pub struct Postgresql { - pub user: String, - pub password: String, - pub host: String, - pub port: String, -} - -impl Postgresql { - pub fn new(user: String, password: String, host: String, port: String) -> Self { - Self { - user, - password, - host, - port, - } - } -} - diff --git a/common/src/enums.rs b/common/src/enums.rs index 7ac30d5..906ef21 100644 --- a/common/src/enums.rs +++ b/common/src/enums.rs @@ -2,13 +2,6 @@ use std::fmt::Display; use serde::{Deserialize, Serialize}; -use super::projects::postgresql::Postgresql; - -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -pub enum Project { - POSTGRESQL(Postgresql), -} - #[derive(Clone, Serialize, Deserialize)] pub enum Drivers { POSTGRESQL, diff --git a/common/src/lib.rs b/common/src/lib.rs index cdc608b..4be1dc3 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -1,3 +1,2 @@ -pub mod drivers; pub mod enums; -pub mod projects; + diff --git a/common/src/projects/mod.rs b/common/src/projects/mod.rs deleted file mode 100644 index 6aa782e..0000000 --- a/common/src/projects/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod postgresql; diff --git a/common/src/projects/postgresql.rs b/common/src/projects/postgresql.rs deleted file mode 100644 index 222c24c..0000000 --- a/common/src/projects/postgresql.rs +++ /dev/null @@ -1,36 +0,0 @@ -use serde::{Deserialize, Serialize}; -use std::collections::BTreeMap; - -use crate::{drivers::postgresql::Postgresql as PostgresqlDriver, enums::ProjectConnectionStatus}; - -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -pub struct Postgresql { - pub name: String, - pub driver: PostgresqlDriver, - pub schemas: Option>, - pub tables: Option>>, - pub connection_status: ProjectConnectionStatus, - pub relations: Option>, -} - -impl Default for Postgresql { - fn default() -> Self { - Self { - name: String::default(), - driver: PostgresqlDriver::default(), - schemas: None, - tables: None, - connection_status: ProjectConnectionStatus::Disconnected, - relations: None, - } - } -} - -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -pub struct PostgresqlRelation { - pub constraint_name: String, - pub table_name: String, - pub column_name: String, - pub foreign_table_name: String, - pub foreign_column_name: String, -} diff --git a/src-tauri/src/dbs/project.rs b/src-tauri/src/dbs/project.rs index ad03394..30a9d19 100644 --- a/src-tauri/src/dbs/project.rs +++ b/src-tauri/src/dbs/project.rs @@ -1,36 +1,44 @@ +use std::collections::BTreeMap; + use tauri::{Result, State}; use crate::AppState; #[tauri::command(rename_all = "snake_case")] -pub async fn project_db_select( - project_name: &str, - app_state: State<'_, AppState>, -) -> Result { +pub async fn project_db_select(app_state: State<'_, AppState>) -> Result> { let project_db = app_state.project_db.lock().await; let db = project_db.clone().unwrap(); - let project = db.get(project_name).unwrap(); - let project = project.unwrap().to_vec(); - Ok(String::from_utf8(project).unwrap()) + let mut projects = BTreeMap::new(); + for p in db.iter() { + let project = p.unwrap(); + + let project = ( + String::from_utf8(project.0.to_vec()).unwrap(), + String::from_utf8(project.1.to_vec()).unwrap(), + ); + projects.insert(project.0, project.1); + } + + Ok(projects) } #[tauri::command(rename_all = "snake_case")] pub async fn project_db_insert( - project_name: &str, + project_id: &str, project_details: &str, app_state: State<'_, AppState>, ) -> Result<()> { let project_db = app_state.project_db.lock().await; let db = project_db.clone().unwrap(); - db.insert(project_name, project_details).unwrap(); + db.insert(project_id, project_details).unwrap(); Ok(()) } #[tauri::command(rename_all = "snake_case")] -pub async fn project_db_delete(project_name: &str, app_state: State<'_, AppState>) -> Result<()> { +pub async fn project_db_delete(project_id: &str, app_state: State<'_, AppState>) -> Result<()> { let db = app_state.project_db.lock().await; let db = db.clone().unwrap(); - db.remove(project_name).unwrap(); + db.remove(project_id).unwrap(); Ok(()) } diff --git a/src-tauri/src/drivers/postgresql.rs b/src-tauri/src/drivers/postgresql.rs index a32f431..15f81cf 100644 --- a/src-tauri/src/drivers/postgresql.rs +++ b/src-tauri/src/drivers/postgresql.rs @@ -1,9 +1,6 @@ use std::{sync::Arc, time::Instant}; -use common::{ - enums::{PostgresqlError, ProjectConnectionStatus}, - projects::postgresql::PostgresqlRelation, -}; +use common::enums::{PostgresqlError, ProjectConnectionStatus}; use tauri::{AppHandle, Manager, Result, State}; use tokio::{sync::Mutex, time as tokio_time}; use tokio_postgres::{connect, NoTls}; @@ -178,48 +175,49 @@ pub async fn pgsql_load_relations( project_name: &str, schema: &str, app_state: State<'_, AppState>, -) -> Result> { - let clients = app_state.client.lock().await; - let client = clients.as_ref().unwrap().get(project_name).unwrap(); - let rows = client - .query( - r#"--sql SELECT - tc.constraint_name, - tc.table_name, - kcu.column_name, - ccu.table_name AS foreign_table_name, - ccu.column_name AS foreign_column_name - FROM information_schema.table_constraints AS tc - JOIN information_schema.key_column_usage AS kcu - ON tc.constraint_name = kcu.constraint_name - JOIN information_schema.constraint_column_usage AS ccu - ON ccu.constraint_name = tc.constraint_name - WHERE constraint_type = 'FOREIGN KEY' - AND tc.table_schema = $1; - "#, - &[&schema], - ) - .await - .unwrap(); - - let relations = rows - .iter() - .map(|row| { - let constraint_name = row.get(0); - let table_name = row.get(1); - let column_name = row.get(2); - let foreign_table_name = row.get(3); - let foreign_column_name = row.get(4); - PostgresqlRelation { - constraint_name, - table_name, - column_name, - foreign_table_name, - foreign_column_name, - } - }) - .collect::>(); - - Ok(relations) +) -> Result> { + // let clients = app_state.client.lock().await; + // let client = clients.as_ref().unwrap().get(project_name).unwrap(); + // let rows = client + // .query( + // r#"--sql SELECT + // tc.constraint_name, + // tc.table_name, + // kcu.column_name, + // ccu.table_name AS foreign_table_name, + // ccu.column_name AS foreign_column_name + // FROM information_schema.table_constraints AS tc + // JOIN information_schema.key_column_usage AS kcu + // ON tc.constraint_name = kcu.constraint_name + // JOIN information_schema.constraint_column_usage AS ccu + // ON ccu.constraint_name = tc.constraint_name + // WHERE constraint_type = 'FOREIGN KEY' + // AND tc.table_schema = $1; + // "#, + // &[&schema], + // ) + // .await + // .unwrap(); + + // let relations = rows + // .iter() + // .map(|row| { + // let constraint_name = row.get(0); + // let table_name = row.get(1); + // let column_name = row.get(2); + // let foreign_table_name = row.get(3); + // let foreign_column_name = row.get(4); + // PostgresqlRelation { + // constraint_name, + // table_name, + // column_name, + // foreign_table_name, + // foreign_column_name, + // } + // }) + // .collect::>(); + + // Ok(relations) + todo!() } diff --git a/src/app.rs b/src/app.rs index 66e8bfe..3dc467a 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,6 +1,6 @@ use leptos::*; use leptos_icons::*; -use thaw::{Button, ButtonSize, Tab, TabLabel, Tabs}; +use thaw::{Button, ButtonSize, MessageProvider, Tab, TabLabel, Tabs}; use crate::{ enums::QueryTableLayout, @@ -37,60 +37,62 @@ pub fn App() -> impl IntoView { let mut tabs = use_context::().unwrap(); view! { -
- -
-
- - - -
- {format!("Tab {}", index + 1)} - -
-
- - - + + +
+ + + + + } } - } - /> + /> - - - -
+ {"Add Tab"} + + +
+
-
+ } } diff --git a/src/driver_components/mod.rs b/src/driver_components/mod.rs new file mode 100644 index 0000000..173f9d2 --- /dev/null +++ b/src/driver_components/mod.rs @@ -0,0 +1,2 @@ +pub mod pgsql; + diff --git a/src/driver_components/pgsql.rs b/src/driver_components/pgsql.rs new file mode 100644 index 0000000..413d701 --- /dev/null +++ b/src/driver_components/pgsql.rs @@ -0,0 +1,145 @@ +use std::time::Duration; + +use leptos::*; +use leptos_icons::*; +use thaw::{use_message, MessageOptions}; + +use crate::{drivers::pgsql, store::projects::ProjectsStore}; +use common::enums::ProjectConnectionStatus; + +#[component] +pub fn Pgsql(project_id: String) -> impl IntoView { + let message = use_message(); + let projects_store = use_context::().unwrap(); + let project_details = projects_store.select_project_by_name(&project_id).unwrap(); + let connection_params = project_details + .split(':') + .into_iter() + .map(String::from) + .collect::>(); + let connection_params = connection_params + .into_iter() + .skip(1) + .map(|s| { + let kv = s.split('=').collect::>(); + kv[1].to_owned() + }) + .collect::>(); + let connection_params = Box::leak(connection_params.into_boxed_slice()); + // [user, password, host, port] + let mut pgsql = pgsql::Pgsql::new(project_id.clone()); + { + pgsql.load_connection_details( + &connection_params[0], + &connection_params[1], + &connection_params[2], + &connection_params[3], + ); + } + let connect = create_action(move |pgsql: &pgsql::Pgsql| { + let pgsql = pgsql.clone(); + async move { + let status = pgsql.connector().await.unwrap(); + match status { + ProjectConnectionStatus::Connected => { + message.create( + "Connected to project".into(), + thaw::MessageVariant::Success, + Default::default(), + ); + } + ProjectConnectionStatus::Failed => { + message.create( + "Failed to connect to project".into(), + thaw::MessageVariant::Error, + Default::default(), + ); + } + _ => { + message.create( + "Failed to connect to project".into(), + thaw::MessageVariant::Error, + Default::default(), + ); + } + } + } + }); + let delete_project = create_action( + move |(projects_store, project_id): &(ProjectsStore, String)| { + let projects_store = *projects_store; + let project_id = project_id.clone(); + async move { projects_store.delete_project(&project_id) } + }, + ); + + view! { +
+
+ + +
+ //
+ // Loading...

} + // }> + + // { + // let project = project.clone(); + // view! { + // + // + // + // } + // } + + //
+ //
+
+ } +} + diff --git a/src/drivers/pgsql.rs b/src/drivers/pgsql.rs index cbc9949..0439c65 100644 --- a/src/drivers/pgsql.rs +++ b/src/drivers/pgsql.rs @@ -1,54 +1,52 @@ use ahash::AHashMap; use common::enums::ProjectConnectionStatus; -use leptos::{RwSignal, SignalSet}; +use leptos::{error::Result, logging::log, RwSignal, SignalGet, SignalUpdate}; use tauri_sys::tauri::invoke; use crate::invoke::{Invoke, InvokePostgresConnectorArgs}; +#[derive(Debug, Clone, Copy)] pub struct Pgsql<'a> { - pub name: &'a str, + pub project_id: RwSignal, user: Option<&'a str>, password: Option<&'a str>, host: Option<&'a str>, port: Option<&'a str>, - connection_string: Option, + //connection_string: Option, pub status: RwSignal, pub schemas: RwSignal>>, } impl<'a> Pgsql<'a> { - pub fn new( - name: &'a str, - user: Option<&'a str>, - password: Option<&'a str>, - host: Option<&'a str>, - port: Option<&'a str>, - ) -> Self { + pub fn new(project_id: String) -> Self { Self { - name, + project_id: RwSignal::new(project_id), status: RwSignal::default(), schemas: RwSignal::default(), - user, - password, - host, - port, - connection_string: None, + user: None, + password: None, + host: None, + port: None, } } - pub async fn connector(&self) { - self.status.set(ProjectConnectionStatus::Connecting); + pub async fn connector(&self) -> Result { + self + .status + .update(|prev| *prev = ProjectConnectionStatus::Connecting); + let connection_string = self.generate_connection_string(); let status = invoke::<_, ProjectConnectionStatus>( - Invoke::pgsql_connector.as_ref(), + Invoke::PgsqlConnector.as_ref(), &InvokePostgresConnectorArgs { - project_name: &self.name, - key: &self.connection_string.as_ref().unwrap(), + project_name: &self.project_id.get(), + key: connection_string.as_str(), }, ) .await .unwrap(); - self.status.set(status); + self.status.update(|prev| *prev = status.clone()); + Ok(status) } pub async fn load_schemas() { @@ -63,7 +61,20 @@ impl<'a> Pgsql<'a> { unimplemented!() } - fn generate_connection_string(&mut self) { + pub fn load_connection_details( + &mut self, + user: &'a str, + password: &'a str, + host: &'a str, + port: &'a str, + ) { + self.user = Some(user); + self.password = Some(password); + self.host = Some(host); + self.port = Some(port); + } + + fn generate_connection_string(&self) -> String { let connection_string = format!( "user={} password={} host={} port={}", self.user.as_ref().unwrap(), @@ -71,7 +82,7 @@ impl<'a> Pgsql<'a> { self.host.as_ref().unwrap(), self.port.as_ref().unwrap(), ); - self.connection_string = Some(connection_string); + connection_string } } diff --git a/src/invoke.rs b/src/invoke.rs index 950ee23..b0d03c2 100644 --- a/src/invoke.rs +++ b/src/invoke.rs @@ -1,39 +1,39 @@ use std::fmt::Display; -use common::enums::Project; use serde::{Deserialize, Serialize}; -#[allow(non_camel_case_types)] pub enum Invoke { - delete_project, - delete_query, - insert_project, - insert_query, - select_projects, - select_queries, + ProjectDbSelect, + ProjectDbInsert, + ProjectDbDelete, - pgsql_connector, - pgsql_load_schemas, - pgsql_load_tables, - pgsql_load_relations, - pgsql_run_query, + QueryDbSelect, + QueryDbInsert, + QueryDbDelete, + + PgsqlConnector, + PgsqlLoadSchemas, + PgsqlLoadTables, + PgsqlLoadRelations, + PgsqlRunQuery, } impl Display for Invoke { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Invoke::delete_project => write!(f, "delete_project"), - Invoke::delete_query => write!(f, "delete_query"), - Invoke::insert_project => write!(f, "insert_project"), - Invoke::insert_query => write!(f, "insert_query"), - Invoke::select_projects => write!(f, "select_projects"), - Invoke::select_queries => write!(f, "select_queries"), - - Invoke::pgsql_connector => write!(f, "pgsql_connector"), - Invoke::pgsql_load_relations => write!(f, "pgsql_load_relations"), - Invoke::pgsql_load_tables => write!(f, "pgsql_load_tables"), - Invoke::pgsql_load_schemas => write!(f, "pgsql_load_schemas"), - Invoke::pgsql_run_query => write!(f, "pgsql_run_query"), + Invoke::ProjectDbSelect => write!(f, "project_db_select"), + Invoke::ProjectDbInsert => write!(f, "project_db_insert"), + Invoke::ProjectDbDelete => write!(f, "project_db_delete"), + + Invoke::QueryDbSelect => write!(f, "query_db_select"), + Invoke::QueryDbInsert => write!(f, "query_db_insert"), + Invoke::QueryDbDelete => write!(f, "query_db_delete"), + + Invoke::PgsqlConnector => write!(f, "pgsql_connector"), + Invoke::PgsqlLoadRelations => write!(f, "pgsql_load_relations"), + Invoke::PgsqlLoadTables => write!(f, "pgsql_load_tables"), + Invoke::PgsqlLoadSchemas => write!(f, "pgsql_load_schemas"), + Invoke::PgsqlRunQuery => write!(f, "pgsql_run_query"), } } } @@ -41,17 +41,19 @@ impl Display for Invoke { impl AsRef for Invoke { fn as_ref(&self) -> &str { match *self { - Invoke::delete_project => "delete_project", - Invoke::delete_query => "delete_query", - Invoke::insert_project => "insert_project", - Invoke::insert_query => "insert_query", - Invoke::select_projects => "select_projects", - Invoke::select_queries => "select_queries", - Invoke::pgsql_connector => "pgsql_connector", - Invoke::pgsql_load_schemas => "pgsql_load_schemas", - Invoke::pgsql_load_tables => "pgsql_load_tables", - Invoke::pgsql_load_relations => "pgsql_load_relations", - Invoke::pgsql_run_query => "pgsql_run_query", + Invoke::ProjectDbSelect => "project_db_select", + Invoke::ProjectDbInsert => "project_db_insert", + Invoke::ProjectDbDelete => "project_db_delete", + + Invoke::QueryDbSelect => "query_db_select", + Invoke::QueryDbInsert => "query_db_insert", + Invoke::QueryDbDelete => "query_db_delete", + + Invoke::PgsqlConnector => "pgsql_connector", + Invoke::PgsqlLoadSchemas => "pgsql_load_schemas", + Invoke::PgsqlLoadTables => "pgsql_load_tables", + Invoke::PgsqlLoadRelations => "pgsql_load_relations", + Invoke::PgsqlRunQuery => "pgsql_run_query", } } } @@ -85,7 +87,7 @@ pub struct InvokeSelectProjectsArgs; #[derive(Serialize, Deserialize)] pub struct InvokeInsertProjectArgs { - pub project: Project, + //pub project: Project, } #[derive(Serialize, Deserialize)] diff --git a/src/main.rs b/src/main.rs index 9ff3d08..974d16b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,6 @@ mod app; mod context_menu; +mod driver_components; mod drivers; mod enums; mod footer; diff --git a/src/modals/connection.rs b/src/modals/connection.rs index 0e1430a..0907873 100644 --- a/src/modals/connection.rs +++ b/src/modals/connection.rs @@ -1,8 +1,4 @@ -use common::{ - drivers::postgresql::Postgresql as PostgresqlDriver, - enums::{Drivers, Project}, - projects::postgresql::Postgresql, -}; +use common::enums::Drivers; use leptos::*; use tauri_sys::tauri::invoke; use thaw::{Modal, ModalFooter}; @@ -21,21 +17,21 @@ pub fn Connection(show: RwSignal) -> impl IntoView { let (db_password, set_db_password) = create_signal(String::new()); let (db_host, set_db_host) = create_signal(String::new()); let (db_port, set_db_port) = create_signal(String::new()); - let save_project = create_action(move |project_details: &Project| { - let project_details = project_details.clone(); - async move { - let project = invoke::<_, Project>( - &Invoke::insert_project.to_string(), - &InvokeInsertProjectArgs { - project: project_details, - }, - ) - .await - .unwrap(); - projects_store.insert_project(project).unwrap(); - show.set(false); - } - }); + // let save_project = create_action(move |project_details: &Project| { + // let project_details = project_details.clone(); + // async move { + // // let project = invoke::<_, Project>( + // // &Invoke::insert_project.to_string(), + // // &InvokeInsertProjectArgs { + // // project: project_details, + // // }, + // // ) + // // .await + // // .unwrap(); + // // projects_store.insert_project(project).unwrap(); + // // show.set(false); + // } + // }); view! { @@ -90,23 +86,7 @@ pub fn Connection(show: RwSignal) -> impl IntoView { || db_host().is_empty() || db_port().is_empty() } - on:click=move |_| { - let project_details = match driver() { - Drivers::POSTGRESQL => { - Project::POSTGRESQL(Postgresql { - name: project(), - driver: PostgresqlDriver::new( - db_user(), - db_password(), - db_host(), - db_port(), - ), - ..Postgresql::default() - }) - } - }; - save_project.dispatch(project_details); - } + on:click=move |_| {} > Add diff --git a/src/modals/custom_query.rs b/src/modals/custom_query.rs index 48e5345..6186469 100644 --- a/src/modals/custom_query.rs +++ b/src/modals/custom_query.rs @@ -10,14 +10,14 @@ pub fn CustomQuery(show: RwSignal) -> impl IntoView { let projects_store = use_context::().unwrap(); let query_store = use_context::().unwrap(); let (query_title, set_query_title) = create_signal(String::new()); - let projects = create_memo(move |_| projects_store.get_projects().unwrap()); + //let projects = create_memo(move |_| projects_store.get_projects().unwrap()); let active_project = use_context::().unwrap(); let (project_name, set_project_name) = create_signal(active_project.0.get().unwrap_or_default()); - create_effect(move |_| { - if !projects.get().is_empty() { - set_project_name(projects.get()[0].clone()); - } - }); + // create_effect(move |_| { + // if !projects.get().is_empty() { + // set_project_name(projects.get()[0].clone()); + // } + // }); let insert_query = create_action( move |(query_db, key, project_name): &(QueryStore, String, String)| { @@ -41,18 +41,17 @@ pub fn CustomQuery(show: RwSignal) -> impl IntoView { value=project_name default_value="teszt" placeholder="Select project.." - > - - {p} - - } - } - /> + >// + // {p} + // + // } + // } + // /> impl IntoView { - let projects_state = use_context::().unwrap(); + let projects_store = use_context::().unwrap(); let show = create_rw_signal(false); let _ = use_event_listener(use_document(), ev::keydown, move |event| { if event.key() == "Escape" { @@ -21,15 +21,9 @@ pub fn Sidebar() -> impl IntoView { } }); create_resource( - move || projects_state.0.get(), + move || projects_store.0.get(), move |_| async move { - let projects = invoke::<_, Vec<(String, Project)>>( - &Invoke::select_projects.to_string(), - &InvokeSelectProjectsArgs, - ) - .await - .unwrap(); - projects_state.set_projects(projects).unwrap() + projects_store.load_projects().await; }, ); @@ -47,9 +41,9 @@ pub fn Sidebar() -> impl IntoView {
} + children=|(project_id, _)| view! { } />
diff --git a/src/sidebar/project.rs b/src/sidebar/project.rs index 4448581..6734627 100644 --- a/src/sidebar/project.rs +++ b/src/sidebar/project.rs @@ -3,6 +3,7 @@ use leptos_icons::*; use crate::{ app::ErrorModal, + drivers::pgsql::Pgsql, modals::error::Error as Modal, store::{active_project::ActiveProjectStore, projects::ProjectsStore}, }; @@ -11,57 +12,55 @@ use super::schemas::Schemas; #[component] pub fn Project(project: String) -> impl IntoView { - let projects_store = use_context::().unwrap(); - let active_project_store = use_context::().unwrap(); - let error_modal = use_context::().unwrap(); - let (show_schemas, set_show_schemas) = create_signal(false); - let delete_project = create_action(move |(projects_store, project): &(ProjectsStore, String)| { - let projects_store = *projects_store; - let project = project.clone(); - async move { - projects_store.delete_project(&project).await.unwrap(); - } - }); + // let pgsql = Pgsql::new(&project, None, None, None, None); + // let projects_store = use_context::().unwrap(); + // let active_project_store = use_context::().unwrap(); + // let error_modal = use_context::().unwrap(); + // let (show_schemas, set_show_schemas) = create_signal(false); + // let delete_project = create_action(move |(projects_store, project): &(ProjectsStore, String)| { + // let projects_store = *projects_store; + // let project = project.clone(); + // async move {} + // }); - let on_click = move || { - error_modal.show.update(|prev| *prev = false); - set_show_schemas(false); - active_project_store.0.set(None); - }; + // let on_click = move || { + // error_modal.show.update(|prev| *prev = false); + // set_show_schemas(false); + // active_project_store.0.set(None); + // }; view! { -
- -
- - + // -
+ // "-" + // + //
//
// Loading...

} @@ -78,7 +77,7 @@ pub fn Project(project: String) -> impl IntoView { //
//
-
+ //
} } diff --git a/src/sidebar/tables.rs b/src/sidebar/tables.rs index 5127b05..dfcddfa 100644 --- a/src/sidebar/tables.rs +++ b/src/sidebar/tables.rs @@ -13,21 +13,16 @@ pub fn Tables(schema: String, project: String) -> impl IntoView { move |_| { let schema = schema_clone.clone(); let project = project_clone.clone(); - async move { - projects_store - .retrieve_tables(&project, &schema) - .await - .unwrap() - } + async move {} }, ); view! { - } - /> + // } + // /> } } diff --git a/src/store/projects.rs b/src/store/projects.rs index 72f587e..9e13901 100644 --- a/src/store/projects.rs +++ b/src/store/projects.rs @@ -1,24 +1,14 @@ use std::collections::BTreeMap; -use common::{ - enums::{PostgresqlError, Project, ProjectConnectionStatus}, - projects::postgresql::PostgresqlRelation, -}; use leptos::{ - create_rw_signal, error::Result, logging::log, use_context, RwSignal, SignalGet, SignalUpdate, + create_rw_signal, logging::log, use_context, RwSignal, SignalGet, SignalSet, SignalUpdate, }; use tauri_sys::tauri::invoke; -use crate::{ - app::ErrorModal, - invoke::{ - Invoke, InvokeDeleteProjectArgs, InvokePostgresConnectorArgs, InvokeSchemaRelationsArgs, - InvokeSchemaTablesArgs, - }, -}; +use crate::invoke::Invoke; #[derive(Clone, Copy, Debug)] -pub struct ProjectsStore(pub RwSignal>); +pub struct ProjectsStore(pub RwSignal>); impl Default for ProjectsStore { fn default() -> Self { @@ -32,191 +22,23 @@ impl ProjectsStore { Self(create_rw_signal(BTreeMap::default())) } - pub fn set_projects( - &self, - projects: Vec<(String, Project)>, - ) -> Result> { - self.0.update(|prev| { - // insert only if project does not exist - for (name, project) in projects.iter() { - if !prev.contains_key(name) { - prev.insert(name.clone(), project.clone()); - } - } - }); - Ok(self.0.get().clone()) - } - - pub fn insert_project(&self, project: Project) -> Result<()> { - self.0.update(|prev| { - let project = match project { - Project::POSTGRESQL(project) => (project.name.clone(), Project::POSTGRESQL(project)), - }; - prev.insert(project.0, project.1); - }); - Ok(()) - } - - pub fn get_projects(&self) -> Result> { - let projects = self.0.get(); - let projects = projects.keys().cloned().collect::>(); - Ok(projects) - } - - pub fn create_project_connection_string(&self, project_name: &str) -> String { - let projects = self.0.get(); - let (_, project) = projects.get_key_value(project_name).unwrap(); - - match project { - Project::POSTGRESQL(project) => { - let driver = project.driver.clone(); - format!( - "user={} password={} host={} port={}", - driver.user, driver.password, driver.host, driver.port, - ) - } - } + pub async fn load_projects(&self) { + let projects_store = use_context::().unwrap(); + let projects = invoke::<_, BTreeMap>(Invoke::ProjectDbSelect.as_ref(), &()) + .await + .unwrap(); + log!("projects: {:?}", projects); + projects_store.0.set(projects); } - // pub async fn connector(&self, project_name: &str) -> Result<()> { - // let projects = self.0; - // let _projects = projects.get(); - // let key = self.create_project_connection_string(project_name); - // let status = invoke::<_, ProjectConnectionStatus>( - // Invoke::pgsql_connector.as_ref(), - // &InvokePostgresConnectorArgs { - // project_name, - // key: &key, - // }, - // ) - // .await?; - - // match status { - // ProjectConnectionStatus::Connected => todo!(), - // ProjectConnectionStatus::Failed => todo!(), - // ProjectConnectionStatus::Disconnected => todo!(), - // } - // } - - pub async fn retrieve_tables( - &self, - project_name: &str, - schema: &str, - ) -> Result> { - let projects = self.0; - let _projects = projects.get(); - let project = _projects.get(project_name).unwrap(); - - match project { - Project::POSTGRESQL(project) => { - if let Some(tables) = &project.tables { - if let Some(tables) = tables.get(schema) { - return Ok(tables.clone()); - } - } - - let (tables, relations) = self - .postgresql_table_selector(&project.name, schema) - .await - .unwrap(); - - projects.update(|prev| { - let project = prev.get_mut(project_name).unwrap(); - match project { - Project::POSTGRESQL(project) => { - project - .tables - .as_mut() - .unwrap_or(&mut BTreeMap::>::new()) - .insert(schema.to_string(), tables.clone()); - project.relations = Some(relations.clone()); - } - } - }); - - Ok(tables) - } - } + pub fn select_project_by_name(&self, project_id: &str) -> Option { + self.0.get().get(project_id).cloned() } - pub async fn delete_project(&self, project_name: &str) -> Result<()> { - invoke( - &Invoke::delete_project.to_string(), - &InvokeDeleteProjectArgs { project_name }, - ) - .await?; - let projects = self.0; - projects.update(|prev| { - prev.remove(project_name); + pub fn delete_project(&self, project_id: &str) { + self.0.update(|projects| { + projects.remove(project_id); }); - Ok(()) - } - - // async fn postgresql_schema_selector(&self, project_name: &str) -> Result> { - // let connection_string = self.create_project_connection_string(project_name); - // let schemas = invoke::<_, Vec>( - // &Invoke::pgsql_connector.to_string(), - // &InvokePostgresConnectionArgs { - // project_name, - // key: &connection_string, - // }, - // ) - // .await - // .map_err(|err| match err { - // tauri_sys::Error::Command(command) => { - // if command.contains("ConnectionTimeout") { - // PostgresqlError::ConnectionTimeout - // } else if command.contains("ConnectionError") { - // PostgresqlError::ConnectionError - // } else if command.contains("QueryError") { - // PostgresqlError::QueryError - // } else if command.contains("QueryTimeout") { - // PostgresqlError::QueryTimeout - // } else { - // PostgresqlError::ConnectionError - // } - // } - // _ => PostgresqlError::ConnectionError, - // }); - - // if schemas.is_err() { - // let mut error_modal = use_context::().unwrap(); - // log!("err"); - // let error_message = schemas.clone().unwrap_err().to_string(); - // error_modal.show.update(|prev| *prev = true); - // error_modal.message = error_message; - // return Err(schemas.unwrap_err().into()); - // } - - // let mut schemas = schemas.unwrap(); - // schemas.sort(); - // Ok(schemas) - // } - - async fn postgresql_table_selector( - &self, - project_name: &str, - schema: &str, - ) -> Result<(Vec<(String, String)>, Vec)> { - let tables = invoke::<_, Vec<(String, String)>>( - &Invoke::pgsql_load_tables.to_string(), - &InvokeSchemaTablesArgs { - project_name, - schema, - }, - ) - .await?; - - let relations = invoke::<_, Vec>( - &Invoke::pgsql_load_relations.to_string(), - &InvokeSchemaRelationsArgs { - project_name, - schema, - }, - ) - .await?; - - Ok((tables, relations)) } } diff --git a/src/store/query.rs b/src/store/query.rs index 52bcd24..3ac725f 100644 --- a/src/store/query.rs +++ b/src/store/query.rs @@ -26,7 +26,7 @@ impl QueryStore { pub async fn select_queries(&self) -> Result> { let saved_queries = invoke::<_, BTreeMap>( - &Invoke::select_queries.to_string(), + &Invoke::QueryDbSelect.to_string(), &InvokeSelectQueriesArgs, ) .await?; @@ -41,7 +41,7 @@ impl QueryStore { let tabs_store = use_context::().unwrap(); let sql = tabs_store.select_active_editor_value(); invoke( - &Invoke::insert_query.to_string(), + &Invoke::QueryDbInsert.to_string(), &InvokeInsertQueryArgs { key: &format!("{}:{}", project_name, key), sql: sql.as_str(), @@ -54,7 +54,7 @@ impl QueryStore { pub async fn delete_query(&self, key: &str) -> Result<()> { invoke( - &Invoke::delete_query.to_string(), + &Invoke::QueryDbDelete.to_string(), &InvokeDeleteQueryArgs { key }, ) .await?; @@ -62,3 +62,4 @@ impl QueryStore { Ok(()) } } + From 05f1535a6222447996393713264456b5056715e4 Mon Sep 17 00:00:00 2001 From: Daniel Boros Date: Sat, 11 May 2024 17:00:04 +0200 Subject: [PATCH 07/28] feat: add conditional render based on driver --- common/src/enums.rs | 8 ++ src-tauri/src/drivers/mod.rs | 3 +- .../src/drivers/{postgresql.rs => pgsql.rs} | 0 src-tauri/src/main.rs | 10 +-- src/app.rs | 7 -- src/context_menu/mod.rs | 2 - src/main.rs | 3 +- src/sidebar/index.rs | 16 +++- src/sidebar/mod.rs | 2 +- src/sidebar/project.rs | 83 ------------------- 10 files changed, 32 insertions(+), 102 deletions(-) rename src-tauri/src/drivers/{postgresql.rs => pgsql.rs} (100%) delete mode 100644 src/context_menu/mod.rs delete mode 100644 src/sidebar/project.rs diff --git a/common/src/enums.rs b/common/src/enums.rs index 906ef21..e83a596 100644 --- a/common/src/enums.rs +++ b/common/src/enums.rs @@ -15,6 +15,14 @@ impl Display for Drivers { } } +impl AsRef for Drivers { + fn as_ref(&self) -> &str { + match self { + Drivers::POSTGRESQL => "POSTGRESQL", + } + } +} + #[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] pub enum ProjectConnectionStatus { Connected, diff --git a/src-tauri/src/drivers/mod.rs b/src-tauri/src/drivers/mod.rs index 6aa782e..173f9d2 100644 --- a/src-tauri/src/drivers/mod.rs +++ b/src-tauri/src/drivers/mod.rs @@ -1 +1,2 @@ -pub mod postgresql; +pub mod pgsql; + diff --git a/src-tauri/src/drivers/postgresql.rs b/src-tauri/src/drivers/pgsql.rs similarity index 100% rename from src-tauri/src/drivers/postgresql.rs rename to src-tauri/src/drivers/pgsql.rs diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index f26f55d..795e666 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -71,11 +71,11 @@ fn main() { dbs::query::query_db_select, dbs::query::query_db_insert, dbs::query::query_db_delete, - drivers::postgresql::pgsql_connector, - drivers::postgresql::pgsql_load_relations, - drivers::postgresql::pgsql_load_schemas, - drivers::postgresql::pgsql_load_tables, - drivers::postgresql::pgsql_run_query, + drivers::pgsql::pgsql_connector, + drivers::pgsql::pgsql_load_relations, + drivers::pgsql::pgsql_load_schemas, + drivers::pgsql::pgsql_load_tables, + drivers::pgsql::pgsql_run_query, ]) .run(tauri::generate_context!()) .expect("error while running tauri application"); diff --git a/src/app.rs b/src/app.rs index 3dc467a..d2a3f25 100644 --- a/src/app.rs +++ b/src/app.rs @@ -19,19 +19,12 @@ use crate::{ // TODO: help to add custom langunage support // https://github.com/abesto/clox-rs/blob/def4bed61a1c1c6b5d84a67284549a6343c8cd06/web/src/monaco_lox.rs -#[derive(Default, Clone)] -pub struct ErrorModal { - pub message: String, - pub show: RwSignal, -} - #[component] pub fn App() -> impl IntoView { provide_context(QueryStore::default()); provide_context(ProjectsStore::default()); provide_context(create_rw_signal(QueryTableLayout::Grid)); provide_context(create_rw_signal(0.0f32)); - provide_context(ErrorModal::default()); provide_context(ActiveProjectStore::default()); provide_context(TabsStore::default()); let mut tabs = use_context::().unwrap(); diff --git a/src/context_menu/mod.rs b/src/context_menu/mod.rs deleted file mode 100644 index 139597f..0000000 --- a/src/context_menu/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/src/main.rs b/src/main.rs index 974d16b..2fc58cb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,6 @@ +#![feature(pattern)] + mod app; -mod context_menu; mod driver_components; mod drivers; mod enums; diff --git a/src/sidebar/index.rs b/src/sidebar/index.rs index ed01956..612fbbb 100644 --- a/src/sidebar/index.rs +++ b/src/sidebar/index.rs @@ -8,8 +8,9 @@ use crate::{ modals::connection::Connection, store::projects::ProjectsStore, }; +use common::enums::Drivers; -use super::{project::Project, queries::Queries}; +use super::queries::Queries; #[component] pub fn Sidebar() -> impl IntoView { @@ -43,8 +44,19 @@ pub fn Sidebar() -> impl IntoView { } + children=|(project_id, project_details)| { + if project_details.contains(Drivers::POSTGRESQL.as_ref()) { + view! { +
+ +
+ } + } else { + view! {
} + } + } /> +

Saved Queries

diff --git a/src/sidebar/mod.rs b/src/sidebar/mod.rs index 35127ed..08f8ec2 100644 --- a/src/sidebar/mod.rs +++ b/src/sidebar/mod.rs @@ -1,8 +1,8 @@ pub mod index; -pub mod project; pub mod queries; pub mod query; pub mod schema; pub mod schemas; pub mod table; pub mod tables; + diff --git a/src/sidebar/project.rs b/src/sidebar/project.rs deleted file mode 100644 index 6734627..0000000 --- a/src/sidebar/project.rs +++ /dev/null @@ -1,83 +0,0 @@ -use leptos::{logging::log, *}; -use leptos_icons::*; - -use crate::{ - app::ErrorModal, - drivers::pgsql::Pgsql, - modals::error::Error as Modal, - store::{active_project::ActiveProjectStore, projects::ProjectsStore}, -}; - -use super::schemas::Schemas; - -#[component] -pub fn Project(project: String) -> impl IntoView { - // let pgsql = Pgsql::new(&project, None, None, None, None); - // let projects_store = use_context::().unwrap(); - // let active_project_store = use_context::().unwrap(); - // let error_modal = use_context::().unwrap(); - // let (show_schemas, set_show_schemas) = create_signal(false); - // let delete_project = create_action(move |(projects_store, project): &(ProjectsStore, String)| { - // let projects_store = *projects_store; - // let project = project.clone(); - // async move {} - // }); - - // let on_click = move || { - // error_modal.show.update(|prev| *prev = false); - // set_show_schemas(false); - // active_project_store.0.set(None); - // }; - - view! { - //
- // - //
- // - // - //
- //
- // Loading...

} - // }> - - // { - // let project = project.clone(); - // view! { - // - // - // - // } - // } - - //
- //
- //
- } -} - From df2e27ce03f4bbd710ef2981a05a6a3732c698fa Mon Sep 17 00:00:00 2001 From: Daniel Boros Date: Sun, 12 May 2024 00:16:22 +0200 Subject: [PATCH 08/28] raefactor: schemas --- src-tauri/src/drivers/pgsql.rs | 9 +++++---- src/driver_components/pgsql.rs | 27 +++++++-------------------- src/drivers/pgsql.rs | 24 +++++++++++++++--------- src/invoke.rs | 7 ++++++- src/sidebar/schema.rs | 9 +++++---- src/sidebar/schemas.rs | 31 ++++++++----------------------- 6 files changed, 46 insertions(+), 61 deletions(-) diff --git a/src-tauri/src/drivers/pgsql.rs b/src-tauri/src/drivers/pgsql.rs index 15f81cf..ddde822 100644 --- a/src-tauri/src/drivers/pgsql.rs +++ b/src-tauri/src/drivers/pgsql.rs @@ -9,7 +9,7 @@ use crate::{utils::reflective_get, AppState}; #[tauri::command(rename_all = "snake_case")] pub async fn pgsql_connector( - project_name: &str, + project_id: &str, key: &str, app: AppHandle, ) -> Result { @@ -51,18 +51,18 @@ pub async fn pgsql_connector( let mut clients = app_state.client.lock().await; let clients = clients.as_mut().unwrap(); - clients.insert(project_name.to_string(), client); + clients.insert(project_id.to_string(), client); Ok(ProjectConnectionStatus::Connected) } #[tauri::command(rename_all = "snake_case")] pub async fn pgsql_load_schemas( - project_name: &str, + project_id: &str, app_state: State<'_, AppState>, ) -> Result> { let clients = app_state.client.lock().await; - let client = clients.as_ref().unwrap().get(project_name).unwrap(); + let client = clients.as_ref().unwrap().get(project_id).unwrap(); let schemas = tokio_time::timeout( tokio_time::Duration::from_secs(30), @@ -97,6 +97,7 @@ pub async fn pgsql_load_schemas( let schemas = schemas.unwrap(); let schemas = schemas.iter().map(|r| r.get(0)).collect(); + tracing::info!("Postgres schemas: {:?}", schemas); Ok(schemas) } diff --git a/src/driver_components/pgsql.rs b/src/driver_components/pgsql.rs index 413d701..7615bbe 100644 --- a/src/driver_components/pgsql.rs +++ b/src/driver_components/pgsql.rs @@ -1,10 +1,8 @@ -use std::time::Duration; - use leptos::*; use leptos_icons::*; -use thaw::{use_message, MessageOptions}; +use thaw::use_message; -use crate::{drivers::pgsql, store::projects::ProjectsStore}; +use crate::{drivers::pgsql, sidebar::schemas::Schemas, store::projects::ProjectsStore}; use common::enums::ProjectConnectionStatus; #[component] @@ -123,22 +121,11 @@ pub fn Pgsql(project_id: String) -> impl IntoView { "-"
- //
- // Loading...

} - // }> - - // { - // let project = project.clone(); - // view! { - // - // - // - // } - // } - - //
- //
+
+ + + +
} } diff --git a/src/drivers/pgsql.rs b/src/drivers/pgsql.rs index 0439c65..83861d9 100644 --- a/src/drivers/pgsql.rs +++ b/src/drivers/pgsql.rs @@ -1,10 +1,8 @@ -use ahash::AHashMap; - use common::enums::ProjectConnectionStatus; -use leptos::{error::Result, logging::log, RwSignal, SignalGet, SignalUpdate}; +use leptos::{error::Result, RwSignal, SignalGet, SignalSet, SignalUpdate}; use tauri_sys::tauri::invoke; -use crate::invoke::{Invoke, InvokePostgresConnectorArgs}; +use crate::invoke::{Invoke, InvokePostgresConnectorArgs, InvokePostgresSchemasArgs}; #[derive(Debug, Clone, Copy)] pub struct Pgsql<'a> { @@ -13,9 +11,8 @@ pub struct Pgsql<'a> { password: Option<&'a str>, host: Option<&'a str>, port: Option<&'a str>, - //connection_string: Option, pub status: RwSignal, - pub schemas: RwSignal>>, + pub schemas: RwSignal>, } impl<'a> Pgsql<'a> { @@ -39,18 +36,27 @@ impl<'a> Pgsql<'a> { let status = invoke::<_, ProjectConnectionStatus>( Invoke::PgsqlConnector.as_ref(), &InvokePostgresConnectorArgs { - project_name: &self.project_id.get(), + project_id: &self.project_id.get(), key: connection_string.as_str(), }, ) .await .unwrap(); + self.load_schemas().await; self.status.update(|prev| *prev = status.clone()); Ok(status) } - pub async fn load_schemas() { - unimplemented!() + pub async fn load_schemas(&self) { + let schemas = invoke::<_, Vec>( + Invoke::PgsqlLoadSchemas.as_ref(), + &InvokePostgresSchemasArgs { + project_id: &self.project_id.get(), + }, + ) + .await + .unwrap(); + self.schemas.set(schemas); } pub async fn load_tables() { diff --git a/src/invoke.rs b/src/invoke.rs index b0d03c2..59ff7a8 100644 --- a/src/invoke.rs +++ b/src/invoke.rs @@ -60,10 +60,15 @@ impl AsRef for Invoke { #[derive(Serialize, Deserialize)] pub struct InvokePostgresConnectorArgs<'a> { - pub project_name: &'a str, + pub project_id: &'a str, pub key: &'a str, } +#[derive(Serialize, Deserialize)] +pub struct InvokePostgresSchemasArgs<'a> { + pub project_id: &'a str, +} + #[derive(Serialize, Deserialize)] pub struct InvokeSchemaRelationsArgs<'a> { pub project_name: &'a str, diff --git a/src/sidebar/schema.rs b/src/sidebar/schema.rs index 95daed5..13bba01 100644 --- a/src/sidebar/schema.rs +++ b/src/sidebar/schema.rs @@ -3,7 +3,7 @@ use leptos::*; use super::tables::Tables; #[component] -pub fn Schema(schema: String, project: String) -> impl IntoView { +pub fn Schema(schema: String) -> impl IntoView { let (show_tables, set_show_tables) = create_signal(false); view! { @@ -21,14 +21,15 @@ pub fn Schema(schema: String, project: String) -> impl IntoView { { let schema = schema.clone(); - let project = project.clone(); view! { { let schema = schema.clone(); - let project = project.clone(); - view! { } + view! { + // +
+ } }
diff --git a/src/sidebar/schemas.rs b/src/sidebar/schemas.rs index 27067c9..0288134 100644 --- a/src/sidebar/schemas.rs +++ b/src/sidebar/schemas.rs @@ -1,32 +1,17 @@ use leptos::*; use super::schema::Schema; -use crate::store::projects::ProjectsStore; #[component] -pub fn Schemas(project: String) -> impl IntoView { - let projects_store = use_context::().unwrap(); - let project_clone = project.clone(); - let schemas = create_resource( - || {}, - move |_| { - let project = project.clone(); - async move { - todo!(); - //projects_store.connect(&project).await.unwrap() - } - }, - ); - +pub fn Schemas(schemas: Vec) -> impl IntoView { view! { - // } - // } - // /> + } + } + /> } } From e6b52bf3cc20bb8915d6e048373e1d2c6b8faa65 Mon Sep 17 00:00:00 2001 From: Daniel Boros Date: Sun, 12 May 2024 12:09:34 +0200 Subject: [PATCH 09/28] refactor: driver handling --- Cargo.toml | 1 + common/src/enums.rs | 9 +- src-tauri/src/dbs/project.rs | 7 +- src-tauri/src/drivers/pgsql.rs | 2 +- src/app.rs | 9 +- src/driver_components/mod.rs | 2 - src/drivers/mod.rs | 2 - src/footer.rs | 6 +- src/grid_view.rs | 2 +- src/invoke.rs | 72 +--------- src/main.rs | 3 +- src/modals/connection.rs | 105 -------------- src/modals/custom_query.rs | 6 +- src/modals/mod.rs | 1 - src/pgsql/add.rs | 131 ++++++++++++++++++ src/{drivers/pgsql.rs => pgsql/driver.rs} | 12 +- .../pgsql.rs => pgsql/index.rs} | 46 +++--- src/pgsql/mod.rs | 4 + src/query_editor.rs | 4 +- src/query_table.rs | 4 +- src/record_view.rs | 2 +- src/sidebar/index.rs | 20 +-- src/sidebar/queries.rs | 2 +- src/sidebar/query.rs | 4 +- src/sidebar/table.rs | 4 +- src/sidebar/tables.rs | 2 +- src/store/projects.rs | 39 ++++-- src/store/query.rs | 2 +- 28 files changed, 242 insertions(+), 261 deletions(-) delete mode 100644 src/driver_components/mod.rs delete mode 100644 src/drivers/mod.rs delete mode 100644 src/modals/connection.rs create mode 100644 src/pgsql/add.rs rename src/{drivers/pgsql.rs => pgsql/driver.rs} (88%) rename src/{driver_components/pgsql.rs => pgsql/index.rs} (78%) create mode 100644 src/pgsql/mod.rs diff --git a/Cargo.toml b/Cargo.toml index 005fd09..c436b6a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,6 +23,7 @@ futures = "0.3.30" async-stream = "0.3.5" icondata = "0.3.0" ahash = "0.8.11" +leptos_toaster = { version = "0.1.6", features = ["builtin_toast"] } [workspace] members = ["src-tauri", "common"] diff --git a/common/src/enums.rs b/common/src/enums.rs index e83a596..0f3c871 100644 --- a/common/src/enums.rs +++ b/common/src/enums.rs @@ -2,15 +2,16 @@ use std::fmt::Display; use serde::{Deserialize, Serialize}; -#[derive(Clone, Serialize, Deserialize)] +#[derive(Clone, Copy, Serialize, Deserialize, Default)] pub enum Drivers { - POSTGRESQL, + #[default] + PGSQL, } impl Display for Drivers { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Drivers::POSTGRESQL => write!(f, "POSTGRESQL"), + Drivers::PGSQL => write!(f, "PGSQL"), } } } @@ -18,7 +19,7 @@ impl Display for Drivers { impl AsRef for Drivers { fn as_ref(&self) -> &str { match self { - Drivers::POSTGRESQL => "POSTGRESQL", + Drivers::PGSQL => "PGSQL", } } } diff --git a/src-tauri/src/dbs/project.rs b/src-tauri/src/dbs/project.rs index 30a9d19..8eab5c3 100644 --- a/src-tauri/src/dbs/project.rs +++ b/src-tauri/src/dbs/project.rs @@ -9,6 +9,12 @@ pub async fn project_db_select(app_state: State<'_, AppState>) -> Result) -> Result impl IntoView { provide_context(create_rw_signal(0.0f32)); provide_context(ActiveProjectStore::default()); provide_context(TabsStore::default()); - let mut tabs = use_context::().unwrap(); + let mut tabs = expect_context::(); view! { - +
@@ -85,7 +86,7 @@ pub fn App() -> impl IntoView {
-
+ } } diff --git a/src/driver_components/mod.rs b/src/driver_components/mod.rs deleted file mode 100644 index 173f9d2..0000000 --- a/src/driver_components/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod pgsql; - diff --git a/src/drivers/mod.rs b/src/drivers/mod.rs deleted file mode 100644 index 173f9d2..0000000 --- a/src/drivers/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod pgsql; - diff --git a/src/footer.rs b/src/footer.rs index 8c6b965..83324af 100644 --- a/src/footer.rs +++ b/src/footer.rs @@ -5,9 +5,9 @@ use crate::{enums::QueryTableLayout, store::active_project::ActiveProjectStore}; #[component] pub fn Footer() -> impl IntoView { - let table_view = use_context::>().unwrap(); - let acitve_project = use_context::().unwrap(); - let sql_timer = use_context::>().unwrap(); + let table_view = expect_context::>(); + let acitve_project = expect_context::(); + let sql_timer = expect_context::>(); let formatted_timer = create_memo(move |_| format!("Query complete: {}ms", sql_timer.get())); view! { diff --git a/src/grid_view.rs b/src/grid_view.rs index f44cac4..450a6b6 100644 --- a/src/grid_view.rs +++ b/src/grid_view.rs @@ -4,7 +4,7 @@ use crate::store::tabs::TabsStore; #[component] pub fn GridView() -> impl IntoView { - let tabs_store = use_context::().unwrap(); + let tabs_store = expect_context::(); view! { diff --git a/src/invoke.rs b/src/invoke.rs index 59ff7a8..7556497 100644 --- a/src/invoke.rs +++ b/src/invoke.rs @@ -59,22 +59,16 @@ impl AsRef for Invoke { } #[derive(Serialize, Deserialize)] -pub struct InvokePostgresConnectorArgs<'a> { +pub struct InvokePgsqlConnectorArgs<'a> { pub project_id: &'a str, pub key: &'a str, } #[derive(Serialize, Deserialize)] -pub struct InvokePostgresSchemasArgs<'a> { +pub struct InvokePgsqlLoadSchemasArgs<'a> { pub project_id: &'a str, } -#[derive(Serialize, Deserialize)] -pub struct InvokeSchemaRelationsArgs<'a> { - pub project_name: &'a str, - pub schema: &'a str, -} - #[derive(Serialize, Deserialize)] pub struct InvokeSchemaTablesArgs<'a> { pub project_name: &'a str, @@ -88,16 +82,14 @@ pub struct InvokeSqlResultArgs<'a> { } #[derive(Serialize, Deserialize)] -pub struct InvokeSelectProjectsArgs; - -#[derive(Serialize, Deserialize)] -pub struct InvokeInsertProjectArgs { - //pub project: Project, +pub struct InvokeProjectDbInsertArgs<'a> { + pub project_id: &'a str, + pub project_details: &'a str, } #[derive(Serialize, Deserialize)] -pub struct InvokeDeleteProjectArgs<'a> { - pub project_name: &'a str, +pub struct InvokeProjectDbDeleteArgs<'a> { + pub project_id: &'a str, } #[derive(Serialize, Deserialize)] @@ -114,53 +106,3 @@ pub struct InvokeDeleteQueryArgs<'a> { pub key: &'a str, } -#[derive(Default, Serialize, Deserialize)] -pub struct InvokeContextMenuArgs<'a> { - pub pos: Option, - #[serde(borrow)] - pub items: Option>>, -} - -#[derive(Serialize, Deserialize)] -pub struct InvokeContextMenuItem<'a> { - pub label: Option<&'a str>, - pub disabled: Option, - pub shortcut: Option<&'a str>, - pub event: Option<&'a str>, - pub payload: Option<&'a str>, - pub subitems: Option>>, - pub icon: Option>, - pub checked: Option, - pub is_separator: Option, -} - -impl<'a> Default for InvokeContextMenuItem<'a> { - fn default() -> Self { - Self { - label: None, - disabled: Some(false), - shortcut: None, - event: None, - payload: None, - subitems: None, - icon: None, - checked: Some(false), - is_separator: Some(false), - } - } -} - -#[derive(Serialize, Deserialize)] -pub struct InvokeContextItemIcon<'a> { - pub path: &'a str, - pub width: Option, - pub height: Option, -} - -#[derive(Serialize, Deserialize)] -pub struct InvokeContextMenuPosition { - pub x: f64, - pub y: f64, - pub is_absolute: Option, -} - diff --git a/src/main.rs b/src/main.rs index 2fc58cb..86a737d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,14 +1,13 @@ #![feature(pattern)] mod app; -mod driver_components; -mod drivers; mod enums; mod footer; mod grid_view; mod hooks; mod invoke; mod modals; +mod pgsql; mod query_editor; mod query_table; mod record_view; diff --git a/src/modals/connection.rs b/src/modals/connection.rs deleted file mode 100644 index 0907873..0000000 --- a/src/modals/connection.rs +++ /dev/null @@ -1,105 +0,0 @@ -use common::enums::Drivers; -use leptos::*; -use tauri_sys::tauri::invoke; -use thaw::{Modal, ModalFooter}; - -use crate::{ - invoke::{Invoke, InvokeInsertProjectArgs}, - store::projects::ProjectsStore, -}; - -#[component] -pub fn Connection(show: RwSignal) -> impl IntoView { - let projects_store = use_context::().unwrap(); - let (driver, _set_driver) = create_signal(Drivers::POSTGRESQL); - let (project, set_project) = create_signal(String::new()); - let (db_user, set_db_user) = create_signal(String::new()); - let (db_password, set_db_password) = create_signal(String::new()); - let (db_host, set_db_host) = create_signal(String::new()); - let (db_port, set_db_port) = create_signal(String::new()); - // let save_project = create_action(move |project_details: &Project| { - // let project_details = project_details.clone(); - // async move { - // // let project = invoke::<_, Project>( - // // &Invoke::insert_project.to_string(), - // // &InvokeInsertProjectArgs { - // // project: project_details, - // // }, - // // ) - // // .await - // // .unwrap(); - // // projects_store.insert_project(project).unwrap(); - // // show.set(false); - // } - // }); - - view! { - -
- - - - - - - - - -
- - -
- - -
-
-
- } -} - diff --git a/src/modals/custom_query.rs b/src/modals/custom_query.rs index 6186469..64d47d4 100644 --- a/src/modals/custom_query.rs +++ b/src/modals/custom_query.rs @@ -7,11 +7,11 @@ use crate::store::{ #[component] pub fn CustomQuery(show: RwSignal) -> impl IntoView { - let projects_store = use_context::().unwrap(); - let query_store = use_context::().unwrap(); + let projects_store = expect_context::(); + let query_store = expect_context::(); let (query_title, set_query_title) = create_signal(String::new()); //let projects = create_memo(move |_| projects_store.get_projects().unwrap()); - let active_project = use_context::().unwrap(); + let active_project = expect_context::(); let (project_name, set_project_name) = create_signal(active_project.0.get().unwrap_or_default()); // create_effect(move |_| { // if !projects.get().is_empty() { diff --git a/src/modals/mod.rs b/src/modals/mod.rs index d842105..534da11 100644 --- a/src/modals/mod.rs +++ b/src/modals/mod.rs @@ -1,4 +1,3 @@ -pub mod connection; pub mod custom_query; pub mod error; diff --git a/src/pgsql/add.rs b/src/pgsql/add.rs new file mode 100644 index 0000000..6c64e23 --- /dev/null +++ b/src/pgsql/add.rs @@ -0,0 +1,131 @@ +use common::enums::Drivers; +use leptos::*; +use thaw::{Modal, ModalFooter}; + +use crate::store::projects::ProjectsStore; + +#[derive(Default, Clone)] +struct ConnectionDetails { + pub project_id: String, + pub driver: Drivers, + pub user: String, + pub password: String, + pub host: String, + pub port: String, +} + +impl IntoIterator for ConnectionDetails { + type Item = String; + type IntoIter = std::vec::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + vec![ + self.project_id.to_owned(), + self.user.to_owned(), + self.password.to_owned(), + self.host.to_owned(), + self.port.to_owned(), + ] + .into_iter() + } +} + +#[component] +pub fn Add(show: RwSignal) -> impl IntoView { + let projects_store = expect_context::(); + let params = create_rw_signal(ConnectionDetails { + driver: Drivers::PGSQL, + ..Default::default() + }); + let save_project = create_action(move |(project_id, project_details): &(String, String)| { + let project_id = project_id.clone(); + let project_details = project_details.clone(); + async move { + projects_store + .insert_project(&project_id, &project_details) + .await; + show.set(false); + } + }); + + view! { + +
+ + + + + + + + + +
+ + +
+ + +
+
+
+ } +} + diff --git a/src/drivers/pgsql.rs b/src/pgsql/driver.rs similarity index 88% rename from src/drivers/pgsql.rs rename to src/pgsql/driver.rs index 83861d9..d1ad338 100644 --- a/src/drivers/pgsql.rs +++ b/src/pgsql/driver.rs @@ -2,7 +2,7 @@ use common::enums::ProjectConnectionStatus; use leptos::{error::Result, RwSignal, SignalGet, SignalSet, SignalUpdate}; use tauri_sys::tauri::invoke; -use crate::invoke::{Invoke, InvokePostgresConnectorArgs, InvokePostgresSchemasArgs}; +use crate::invoke::{Invoke, InvokePgsqlConnectorArgs, InvokePgsqlLoadSchemasArgs}; #[derive(Debug, Clone, Copy)] pub struct Pgsql<'a> { @@ -35,14 +35,16 @@ impl<'a> Pgsql<'a> { let connection_string = self.generate_connection_string(); let status = invoke::<_, ProjectConnectionStatus>( Invoke::PgsqlConnector.as_ref(), - &InvokePostgresConnectorArgs { + &InvokePgsqlConnectorArgs { project_id: &self.project_id.get(), key: connection_string.as_str(), }, ) .await .unwrap(); - self.load_schemas().await; + if status == ProjectConnectionStatus::Connected { + self.load_schemas().await; + } self.status.update(|prev| *prev = status.clone()); Ok(status) } @@ -50,7 +52,7 @@ impl<'a> Pgsql<'a> { pub async fn load_schemas(&self) { let schemas = invoke::<_, Vec>( Invoke::PgsqlLoadSchemas.as_ref(), - &InvokePostgresSchemasArgs { + &InvokePgsqlLoadSchemasArgs { project_id: &self.project_id.get(), }, ) @@ -59,10 +61,12 @@ impl<'a> Pgsql<'a> { self.schemas.set(schemas); } + #[allow(dead_code)] pub async fn load_tables() { unimplemented!() } + #[allow(dead_code)] pub async fn run_query() { unimplemented!() } diff --git a/src/driver_components/pgsql.rs b/src/pgsql/index.rs similarity index 78% rename from src/driver_components/pgsql.rs rename to src/pgsql/index.rs index 7615bbe..ef0ecdd 100644 --- a/src/driver_components/pgsql.rs +++ b/src/pgsql/index.rs @@ -1,14 +1,14 @@ use leptos::*; use leptos_icons::*; -use thaw::use_message; +use leptos_toaster::{Toast, ToastId, ToastVariant, Toasts}; -use crate::{drivers::pgsql, sidebar::schemas::Schemas, store::projects::ProjectsStore}; +use super::driver::Pgsql; +use crate::{sidebar::schemas::Schemas, store::projects::ProjectsStore}; use common::enums::ProjectConnectionStatus; #[component] pub fn Pgsql(project_id: String) -> impl IntoView { - let message = use_message(); - let projects_store = use_context::().unwrap(); + let projects_store = expect_context::(); let project_details = projects_store.select_project_by_name(&project_id).unwrap(); let connection_params = project_details .split(':') @@ -25,7 +25,7 @@ pub fn Pgsql(project_id: String) -> impl IntoView { .collect::>(); let connection_params = Box::leak(connection_params.into_boxed_slice()); // [user, password, host, port] - let mut pgsql = pgsql::Pgsql::new(project_id.clone()); + let mut pgsql = Pgsql::new(project_id.clone()); { pgsql.load_connection_details( &connection_params[0], @@ -34,32 +34,28 @@ pub fn Pgsql(project_id: String) -> impl IntoView { &connection_params[3], ); } - let connect = create_action(move |pgsql: &pgsql::Pgsql| { + let toast_context = expect_context::(); + let create_toast = move |variant: ToastVariant, title: String| { + let toast_id = ToastId::new(); + toast_context.toast( + view! { }, + Some(toast_id), + None, // options + ); + }; + + let connect = create_action(move |pgsql: &Pgsql| { let pgsql = pgsql.clone(); async move { let status = pgsql.connector().await.unwrap(); match status { ProjectConnectionStatus::Connected => { - message.create( - "Connected to project".into(), - thaw::MessageVariant::Success, - Default::default(), - ); + create_toast(ToastVariant::Success, "Connected to project".into()); } ProjectConnectionStatus::Failed => { - message.create( - "Failed to connect to project".into(), - thaw::MessageVariant::Error, - Default::default(), - ); - } - _ => { - message.create( - "Failed to connect to project".into(), - thaw::MessageVariant::Error, - Default::default(), - ); + create_toast(ToastVariant::Error, "Failed to connect to project".into()) } + _ => create_toast(ToastVariant::Warning, "Failed to connect to project".into()), } } }); @@ -67,7 +63,9 @@ pub fn Pgsql(project_id: String) -> impl IntoView { move |(projects_store, project_id): &(ProjectsStore, String)| { let projects_store = *projects_store; let project_id = project_id.clone(); - async move { projects_store.delete_project(&project_id) } + async move { + projects_store.delete_project(&project_id).await; + } }, ); diff --git a/src/pgsql/mod.rs b/src/pgsql/mod.rs new file mode 100644 index 0000000..534de71 --- /dev/null +++ b/src/pgsql/mod.rs @@ -0,0 +1,4 @@ +pub mod add; +pub mod driver; +pub mod index; + diff --git a/src/query_editor.rs b/src/query_editor.rs index a7d7510..182335a 100644 --- a/src/query_editor.rs +++ b/src/query_editor.rs @@ -19,7 +19,7 @@ pub const MODE_ID: &str = "pgsql"; #[component] pub fn QueryEditor() -> impl IntoView { - let tabs_store = Rc::new(RefCell::new(use_context::().unwrap())); + let tabs_store = Rc::new(RefCell::new(expect_context::())); let show = create_rw_signal(false); let _ = use_event_listener(use_document(), ev::keydown, move |event| { if event.key() == "Escape" { @@ -56,7 +56,7 @@ pub fn QueryEditor() -> impl IntoView { let e = Rc::new(RefCell::new(Some(e))); tabs_store_clone.borrow_mut().add_editor(e); }); - let tabs_store = Arc::new(Mutex::new(use_context::().unwrap())); + let tabs_store = Arc::new(Mutex::new(expect_context::())); let run_query = create_action(move |tabs_store: &Arc>| { let tabs_store = tabs_store.clone(); async move { diff --git a/src/query_table.rs b/src/query_table.rs index 417d5e5..ffbd691 100644 --- a/src/query_table.rs +++ b/src/query_table.rs @@ -5,8 +5,8 @@ use leptos::*; #[component] pub fn QueryTable() -> impl IntoView { - let tabs_store = use_context::().unwrap(); - let table_view = use_context::>().unwrap(); + let tabs_store = expect_context::(); + let table_view = expect_context::>(); view! { "Loading..."

}> diff --git a/src/record_view.rs b/src/record_view.rs index 74122f9..7c7cdde 100644 --- a/src/record_view.rs +++ b/src/record_view.rs @@ -4,7 +4,7 @@ use crate::store::tabs::TabsStore; #[component] pub fn RecordView() -> impl IntoView { - let tabs_store = use_context::().unwrap(); + let tabs_store = expect_context::(); let columns = tabs_store.select_active_editor_sql_result().unwrap().0; let first_row = tabs_store .select_active_editor_sql_result() diff --git a/src/sidebar/index.rs b/src/sidebar/index.rs index 612fbbb..bf17983 100644 --- a/src/sidebar/index.rs +++ b/src/sidebar/index.rs @@ -1,20 +1,14 @@ -use leptos::*; +use leptos::{logging::log, *}; use leptos_use::{use_document, use_event_listener}; -use tauri_sys::tauri::invoke; -use crate::{ - driver_components::pgsql::Pgsql, - invoke::{Invoke, InvokeSelectProjectsArgs}, - modals::connection::Connection, - store::projects::ProjectsStore, -}; +use crate::{pgsql::add::Add, pgsql::index::Pgsql, store::projects::ProjectsStore}; use common::enums::Drivers; use super::queries::Queries; #[component] pub fn Sidebar() -> impl IntoView { - let projects_store = use_context::().unwrap(); + let projects_store = expect_context::(); let show = create_rw_signal(false); let _ = use_event_listener(use_document(), ev::keydown, move |event| { if event.key() == "Escape" { @@ -22,7 +16,7 @@ pub fn Sidebar() -> impl IntoView { } }); create_resource( - move || projects_store.0.get(), + || {}, move |_| async move { projects_store.load_projects().await; }, @@ -30,7 +24,7 @@ pub fn Sidebar() -> impl IntoView { view! {
- +

Projects

@@ -45,10 +39,10 @@ pub fn Sidebar() -> impl IntoView { each=move || projects_store.0.get() key=|(project, _)| project.clone() children=|(project_id, project_details)| { - if project_details.contains(Drivers::POSTGRESQL.as_ref()) { + if project_details.contains(Drivers::PGSQL.as_ref()) { view! {
- +
} } else { diff --git a/src/sidebar/queries.rs b/src/sidebar/queries.rs index 42d502e..fde9180 100644 --- a/src/sidebar/queries.rs +++ b/src/sidebar/queries.rs @@ -5,7 +5,7 @@ use super::query::Query; #[component] pub fn Queries() -> impl IntoView { - let query_state = use_context::().unwrap(); + let query_state = expect_context::(); let queries = create_resource( move || query_state.0.get(), move |_| async move { query_state.select_queries().await.unwrap() }, diff --git a/src/sidebar/query.rs b/src/sidebar/query.rs index 496f7aa..3af94d8 100644 --- a/src/sidebar/query.rs +++ b/src/sidebar/query.rs @@ -5,8 +5,8 @@ use crate::store::{query::QueryStore, tabs::TabsStore}; #[component] pub fn Query(key: String) -> impl IntoView { - let query_store = use_context::().unwrap(); - let tabs_store = use_context::().unwrap(); + let query_store = expect_context::(); + let tabs_store = expect_context::(); let key_clone = key.clone(); let splitted_key = create_memo(move |_| { let key = key_clone.clone(); diff --git a/src/sidebar/table.rs b/src/sidebar/table.rs index 3d48a8f..8021c0f 100644 --- a/src/sidebar/table.rs +++ b/src/sidebar/table.rs @@ -8,8 +8,8 @@ use crate::store::{active_project::ActiveProjectStore, tabs::TabsStore}; #[component] pub fn Table(table: (String, String), project: String, schema: String) -> impl IntoView { - let tabs_store = Arc::new(Mutex::new(use_context::().unwrap())); - let active_project = use_context::().unwrap(); + let tabs_store = Arc::new(Mutex::new(expect_context::())); + let active_project = expect_context::(); let query = create_action( move |(schema, table, tabs_store): &(String, String, Arc>)| { let tabs_store = tabs_store.clone(); diff --git a/src/sidebar/tables.rs b/src/sidebar/tables.rs index dfcddfa..c919075 100644 --- a/src/sidebar/tables.rs +++ b/src/sidebar/tables.rs @@ -5,7 +5,7 @@ use crate::store::projects::ProjectsStore; #[component] pub fn Tables(schema: String, project: String) -> impl IntoView { - let projects_store = use_context::().unwrap(); + let projects_store = expect_context::(); let schema_clone = schema.clone(); let project_clone = project.clone(); let tables = create_resource( diff --git a/src/store/projects.rs b/src/store/projects.rs index 9e13901..2f00a0e 100644 --- a/src/store/projects.rs +++ b/src/store/projects.rs @@ -1,11 +1,9 @@ use std::collections::BTreeMap; -use leptos::{ - create_rw_signal, logging::log, use_context, RwSignal, SignalGet, SignalSet, SignalUpdate, -}; +use leptos::{RwSignal, SignalGet, SignalSet}; use tauri_sys::tauri::invoke; -use crate::invoke::Invoke; +use crate::invoke::{Invoke, InvokeProjectDbDeleteArgs, InvokeProjectDbInsertArgs}; #[derive(Clone, Copy, Debug)] pub struct ProjectsStore(pub RwSignal>); @@ -19,26 +17,39 @@ impl Default for ProjectsStore { impl ProjectsStore { #[must_use] pub fn new() -> Self { - Self(create_rw_signal(BTreeMap::default())) + Self(RwSignal::default()) + } + + pub fn select_project_by_name(&self, project_id: &str) -> Option { + self.0.get().get(project_id).cloned() } pub async fn load_projects(&self) { - let projects_store = use_context::().unwrap(); let projects = invoke::<_, BTreeMap>(Invoke::ProjectDbSelect.as_ref(), &()) .await .unwrap(); - log!("projects: {:?}", projects); - projects_store.0.set(projects); + self.0.set(projects); } - pub fn select_project_by_name(&self, project_id: &str) -> Option { - self.0.get().get(project_id).cloned() + pub async fn insert_project(&self, project_id: &str, project_details: &str) { + let _ = invoke::<_, ()>( + Invoke::ProjectDbInsert.as_ref(), + &InvokeProjectDbInsertArgs { + project_id, + project_details, + }, + ) + .await; + self.load_projects().await; } - pub fn delete_project(&self, project_id: &str) { - self.0.update(|projects| { - projects.remove(project_id); - }); + pub async fn delete_project(&self, project_id: &str) { + let _ = invoke::<_, ()>( + Invoke::ProjectDbDelete.as_ref(), + &InvokeProjectDbDeleteArgs { project_id }, + ) + .await; + self.load_projects().await; } } diff --git a/src/store/query.rs b/src/store/query.rs index 3ac725f..3288ece 100644 --- a/src/store/query.rs +++ b/src/store/query.rs @@ -38,7 +38,7 @@ impl QueryStore { } pub async fn insert_query(&self, key: &str, project_name: &str) -> Result<()> { - let tabs_store = use_context::().unwrap(); + let tabs_store = expect_context::(); let sql = tabs_store.select_active_editor_value(); invoke( &Invoke::QueryDbInsert.to_string(), From a31e691512ee4878a83d6e0275b3c90c2922e3d7 Mon Sep 17 00:00:00 2001 From: Daniel Boros Date: Sun, 12 May 2024 12:22:27 +0200 Subject: [PATCH 10/28] fix: add clippy fixes --- src/pgsql/index.rs | 3 +-- src/sidebar/index.rs | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/pgsql/index.rs b/src/pgsql/index.rs index ef0ecdd..bbbe040 100644 --- a/src/pgsql/index.rs +++ b/src/pgsql/index.rs @@ -12,7 +12,6 @@ pub fn Pgsql(project_id: String) -> impl IntoView { let project_details = projects_store.select_project_by_name(&project_id).unwrap(); let connection_params = project_details .split(':') - .into_iter() .map(String::from) .collect::>(); let connection_params = connection_params @@ -45,7 +44,7 @@ pub fn Pgsql(project_id: String) -> impl IntoView { }; let connect = create_action(move |pgsql: &Pgsql| { - let pgsql = pgsql.clone(); + let pgsql = *pgsql; async move { let status = pgsql.connector().await.unwrap(); match status { diff --git a/src/sidebar/index.rs b/src/sidebar/index.rs index bf17983..14a04fa 100644 --- a/src/sidebar/index.rs +++ b/src/sidebar/index.rs @@ -1,4 +1,4 @@ -use leptos::{logging::log, *}; +use leptos::*; use leptos_use::{use_document, use_event_listener}; use crate::{pgsql::add::Add, pgsql::index::Pgsql, store::projects::ProjectsStore}; From 86a7f54604c3b65b45bce542b40b8206c0b998a4 Mon Sep 17 00:00:00 2001 From: Daniel Boros Date: Sun, 12 May 2024 19:18:13 +0200 Subject: [PATCH 11/28] refactor: table handling --- Cargo.toml | 2 +- common/src/enums.rs | 10 +-- src-tauri/Cargo.toml | 1 + src-tauri/src/dbs/project.rs | 7 +- src-tauri/src/drivers/pgsql.rs | 26 ++++---- src/invoke.rs | 4 +- src/pgsql/driver.rs | 32 ++++++++-- src/pgsql/index.rs | 113 +++++++++++++++++++-------------- src/pgsql/mod.rs | 2 + src/pgsql/schema.rs | 76 ++++++++++++++++++++++ src/pgsql/table.rs | 48 ++++++++++++++ src/sidebar/index.rs | 2 +- src/sidebar/mod.rs | 4 -- src/sidebar/schema.rs | 44 ------------- src/sidebar/schemas.rs | 17 ----- src/sidebar/table.rs | 48 -------------- src/sidebar/tables.rs | 28 -------- src/store/projects.rs | 7 +- 18 files changed, 247 insertions(+), 224 deletions(-) create mode 100644 src/pgsql/schema.rs create mode 100644 src/pgsql/table.rs delete mode 100644 src/sidebar/schema.rs delete mode 100644 src/sidebar/schemas.rs delete mode 100644 src/sidebar/table.rs delete mode 100644 src/sidebar/tables.rs diff --git a/Cargo.toml b/Cargo.toml index c436b6a..d3b58d7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,7 @@ common = { path = "common" } futures = "0.3.30" async-stream = "0.3.5" icondata = "0.3.0" -ahash = "0.8.11" +ahash = { version = "0.8.11", features = ["serde"] } leptos_toaster = { version = "0.1.6", features = ["builtin_toast"] } [workspace] diff --git a/common/src/enums.rs b/common/src/enums.rs index 0f3c871..7b706df 100644 --- a/common/src/enums.rs +++ b/common/src/enums.rs @@ -58,11 +58,11 @@ pub enum PostgresqlError { impl std::error::Error for PostgresqlError {} impl fmt::Display for PostgresqlError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - &PostgresqlError::ConnectionTimeout => write!(f, "ConnectionTimeout"), - &PostgresqlError::ConnectionError => write!(f, "ConnectionError"), - &PostgresqlError::QueryTimeout => write!(f, "QueryTimeout"), - &PostgresqlError::QueryError => write!(f, "QueryError"), + match *self { + PostgresqlError::ConnectionTimeout => write!(f, "ConnectionTimeout"), + PostgresqlError::ConnectionError => write!(f, "ConnectionError"), + PostgresqlError::QueryTimeout => write!(f, "QueryTimeout"), + PostgresqlError::QueryError => write!(f, "QueryError"), } } } diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 936021d..5f0c7a9 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -24,6 +24,7 @@ sled = "0.34.7" anyhow = "1.0.83" tracing = "0.1.40" tracing-subscriber = { version = "0.3.18", features = ["fmt"] } +ahash = { version = "0.8.11", features = ["serde"] } diff --git a/src-tauri/src/dbs/project.rs b/src-tauri/src/dbs/project.rs index 8eab5c3..64ef4ea 100644 --- a/src-tauri/src/dbs/project.rs +++ b/src-tauri/src/dbs/project.rs @@ -1,14 +1,13 @@ -use std::collections::BTreeMap; - +use ahash::AHashMap; use tauri::{Result, State}; use crate::AppState; #[tauri::command(rename_all = "snake_case")] -pub async fn project_db_select(app_state: State<'_, AppState>) -> Result> { +pub async fn project_db_select(app_state: State<'_, AppState>) -> Result> { let project_db = app_state.project_db.lock().await; let db = project_db.clone().unwrap(); - let mut projects = BTreeMap::new(); + let mut projects = AHashMap::new(); if db.is_empty() { tracing::info!("No projects found in the database"); diff --git a/src-tauri/src/drivers/pgsql.rs b/src-tauri/src/drivers/pgsql.rs index add534f..1709713 100644 --- a/src-tauri/src/drivers/pgsql.rs +++ b/src-tauri/src/drivers/pgsql.rs @@ -1,4 +1,4 @@ -use std::{sync::Arc, time::Instant}; +use std::{collections::BTreeMap, sync::Arc, time::Instant}; use common::enums::{PostgresqlError, ProjectConnectionStatus}; use tauri::{AppHandle, Manager, Result, State}; @@ -64,13 +64,14 @@ pub async fn pgsql_load_schemas( let clients = app_state.client.lock().await; let client = clients.as_ref().unwrap().get(project_id).unwrap(); - let schemas = tokio_time::timeout( + let query = tokio_time::timeout( tokio_time::Duration::from_secs(10), client.query( r#" SELECT schema_name FROM information_schema.schemata - WHERE schema_name NOT IN ('pg_catalog', 'information_schema'); + WHERE schema_name NOT IN ('pg_catalog', 'information_schema') + ORDER BY schema_name; "#, &[], ), @@ -78,7 +79,7 @@ pub async fn pgsql_load_schemas( .await .map_err(|_| PostgresqlError::QueryTimeout); - if schemas.is_err() { + if query.is_err() { tracing::error!("Postgres schema query timeout error!"); return Err(tauri::Error::Io(std::io::Error::new( std::io::ErrorKind::Other, @@ -86,8 +87,8 @@ pub async fn pgsql_load_schemas( ))); } - let schemas = schemas.unwrap(); - if schemas.is_err() { + let query = query.unwrap(); + if query.is_err() { tracing::error!("Postgres schema query error!"); return Err(tauri::Error::Io(std::io::Error::new( std::io::ErrorKind::Other, @@ -95,22 +96,21 @@ pub async fn pgsql_load_schemas( ))); } - let schemas = schemas.unwrap(); - let schemas = schemas.iter().map(|r| r.get(0)).collect(); + let qeury = query.unwrap(); + let schemas = qeury.iter().map(|r| r.get(0)).collect::>(); tracing::info!("Postgres schemas: {:?}", schemas); - Ok(schemas) } #[tauri::command(rename_all = "snake_case")] pub async fn pgsql_load_tables( - project_name: &str, + project_id: &str, schema: &str, app_state: State<'_, AppState>, ) -> Result> { let clients = app_state.client.lock().await; - let client = clients.as_ref().unwrap().get(project_name).unwrap(); - let tables = client + let client = clients.as_ref().unwrap().get(project_id).unwrap(); + let query = client .query( r#"--sql SELECT @@ -127,7 +127,7 @@ pub async fn pgsql_load_tables( ) .await .unwrap(); - let tables = tables + let tables = query .iter() .map(|r| (r.get(0), r.get(1))) .collect::>(); diff --git a/src/invoke.rs b/src/invoke.rs index 7556497..872831e 100644 --- a/src/invoke.rs +++ b/src/invoke.rs @@ -70,8 +70,8 @@ pub struct InvokePgsqlLoadSchemasArgs<'a> { } #[derive(Serialize, Deserialize)] -pub struct InvokeSchemaTablesArgs<'a> { - pub project_name: &'a str, +pub struct InvokePgsqlLoadTablesArgs<'a> { + pub project_id: &'a str, pub schema: &'a str, } diff --git a/src/pgsql/driver.rs b/src/pgsql/driver.rs index d1ad338..6f92699 100644 --- a/src/pgsql/driver.rs +++ b/src/pgsql/driver.rs @@ -1,8 +1,13 @@ +use std::collections::BTreeMap; + +use ahash::AHashMap; use common::enums::ProjectConnectionStatus; use leptos::{error::Result, RwSignal, SignalGet, SignalSet, SignalUpdate}; use tauri_sys::tauri::invoke; -use crate::invoke::{Invoke, InvokePgsqlConnectorArgs, InvokePgsqlLoadSchemasArgs}; +use crate::invoke::{ + Invoke, InvokePgsqlConnectorArgs, InvokePgsqlLoadSchemasArgs, InvokePgsqlLoadTablesArgs, +}; #[derive(Debug, Clone, Copy)] pub struct Pgsql<'a> { @@ -13,6 +18,7 @@ pub struct Pgsql<'a> { port: Option<&'a str>, pub status: RwSignal, pub schemas: RwSignal>, + pub tables: RwSignal>>, } impl<'a> Pgsql<'a> { @@ -21,6 +27,7 @@ impl<'a> Pgsql<'a> { project_id: RwSignal::new(project_id), status: RwSignal::default(), schemas: RwSignal::default(), + tables: RwSignal::default(), user: None, password: None, host: None, @@ -61,9 +68,22 @@ impl<'a> Pgsql<'a> { self.schemas.set(schemas); } - #[allow(dead_code)] - pub async fn load_tables() { - unimplemented!() + pub async fn load_tables(&self, schema: &str) { + if self.tables.get().contains_key(schema) { + return; + } + let tables = invoke::<_, Vec<(String, String)>>( + Invoke::PgsqlLoadTables.as_ref(), + &InvokePgsqlLoadTablesArgs { + project_id: &self.project_id.get(), + schema, + }, + ) + .await + .unwrap(); + self.tables.update(|prev| { + prev.insert(schema.to_owned(), tables); + }); } #[allow(dead_code)] @@ -71,6 +91,10 @@ impl<'a> Pgsql<'a> { unimplemented!() } + pub fn select_tables_by_schema(&self, schema: &str) -> Option> { + self.tables.get().get(schema).cloned() + } + pub fn load_connection_details( &mut self, user: &'a str, diff --git a/src/pgsql/index.rs b/src/pgsql/index.rs index bbbe040..4d0d232 100644 --- a/src/pgsql/index.rs +++ b/src/pgsql/index.rs @@ -2,8 +2,8 @@ use leptos::*; use leptos_icons::*; use leptos_toaster::{Toast, ToastId, ToastVariant, Toasts}; -use super::driver::Pgsql; -use crate::{sidebar::schemas::Schemas, store::projects::ProjectsStore}; +use super::{driver::Pgsql, schema::Schema}; +use crate::store::projects::ProjectsStore; use common::enums::ProjectConnectionStatus; #[component] @@ -69,61 +69,76 @@ pub fn Pgsql(project_id: String) -> impl IntoView { ); view! { -
-
- - -
-
- - - + "-" + +
+
+ + } + } + /> + + +
-
+ } } diff --git a/src/pgsql/mod.rs b/src/pgsql/mod.rs index 534de71..3fdb060 100644 --- a/src/pgsql/mod.rs +++ b/src/pgsql/mod.rs @@ -1,4 +1,6 @@ pub mod add; pub mod driver; pub mod index; +pub mod schema; +pub mod table; diff --git a/src/pgsql/schema.rs b/src/pgsql/schema.rs new file mode 100644 index 0000000..4d693c7 --- /dev/null +++ b/src/pgsql/schema.rs @@ -0,0 +1,76 @@ +use std::rc::Rc; + +use leptos::*; +use leptos_icons::*; + +use super::{driver::Pgsql, table::Table}; + +#[component] +pub fn Schema(schema: String) -> impl IntoView { + let (show, set_show) = create_signal(false); + let (is_loading, set_is_loading) = create_signal(false); + let schema = Rc::new(schema); + let pgsql = expect_context::(); + let load_tables = create_action(move |schema: &String| { + let schema = schema.clone(); + async move { + pgsql.load_tables(&schema).await; + set_is_loading(false); + set_show(!show()); + } + }); + + view! { +
+ + + { + view! { +
+ + } + } + /> + + +
+ } + } + +
+ } +} + diff --git a/src/pgsql/table.rs b/src/pgsql/table.rs new file mode 100644 index 0000000..ef3fcd7 --- /dev/null +++ b/src/pgsql/table.rs @@ -0,0 +1,48 @@ +use futures::lock::Mutex; +use leptos::*; +use leptos_icons::*; + +use crate::store::{active_project::ActiveProjectStore, tabs::TabsStore}; + +#[component] +pub fn Table( + table: (String, String), + project: Option, + schema: Option, +) -> impl IntoView { + // let tabs_store = Arc::new(Mutex::new(expect_context::())); + // let active_project = expect_context::(); + // let query = create_action( + // move |(schema, table, tabs_store): &(String, String, Arc>)| { + // let tabs_store = tabs_store.clone(); + // let project = project.clone(); + // let schema = schema.clone(); + // let table = table.clone(); + // active_project.0.set(Some(project.clone())); + + // async move { + // tabs_store + // .lock() + // .await + // .set_editor_value(&format!("SELECT * FROM {}.{} LIMIT 100;", schema, table)); + // //tabs_store.lock().await.run_query().await.unwrap() + // } + // }, + // ); + + view! { +
+ // on:click={ + // let table = table.clone(); + // move |_| { query.dispatch((schema.clone(), table.0.clone(), tabs_store.clone())) } + // } + +
+ +

{table.0}

+
+

{table.1}

+
+ } +} + diff --git a/src/sidebar/index.rs b/src/sidebar/index.rs index 14a04fa..627d70b 100644 --- a/src/sidebar/index.rs +++ b/src/sidebar/index.rs @@ -23,7 +23,7 @@ pub fn Sidebar() -> impl IntoView { ); view! { -
+
diff --git a/src/sidebar/mod.rs b/src/sidebar/mod.rs index 08f8ec2..6451be4 100644 --- a/src/sidebar/mod.rs +++ b/src/sidebar/mod.rs @@ -1,8 +1,4 @@ pub mod index; pub mod queries; pub mod query; -pub mod schema; -pub mod schemas; -pub mod table; -pub mod tables; diff --git a/src/sidebar/schema.rs b/src/sidebar/schema.rs deleted file mode 100644 index 13bba01..0000000 --- a/src/sidebar/schema.rs +++ /dev/null @@ -1,44 +0,0 @@ -use leptos::*; - -use super::tables::Tables; - -#[component] -pub fn Schema(schema: String) -> impl IntoView { - let (show_tables, set_show_tables) = create_signal(false); - - view! { -
-
- {&schema} -
-
- "Loading..."

} - }> - - { - let schema = schema.clone(); - view! { - - - { - let schema = schema.clone(); - view! { - // -
- } - } - -
- } - } - -
-
-
- } -} - diff --git a/src/sidebar/schemas.rs b/src/sidebar/schemas.rs deleted file mode 100644 index 0288134..0000000 --- a/src/sidebar/schemas.rs +++ /dev/null @@ -1,17 +0,0 @@ -use leptos::*; - -use super::schema::Schema; - -#[component] -pub fn Schemas(schemas: Vec) -> impl IntoView { - view! { - } - } - /> - } -} - diff --git a/src/sidebar/table.rs b/src/sidebar/table.rs deleted file mode 100644 index 8021c0f..0000000 --- a/src/sidebar/table.rs +++ /dev/null @@ -1,48 +0,0 @@ -use std::sync::Arc; - -use futures::lock::Mutex; -use leptos::*; -use leptos_icons::*; - -use crate::store::{active_project::ActiveProjectStore, tabs::TabsStore}; - -#[component] -pub fn Table(table: (String, String), project: String, schema: String) -> impl IntoView { - let tabs_store = Arc::new(Mutex::new(expect_context::())); - let active_project = expect_context::(); - let query = create_action( - move |(schema, table, tabs_store): &(String, String, Arc>)| { - let tabs_store = tabs_store.clone(); - let project = project.clone(); - let schema = schema.clone(); - let table = table.clone(); - active_project.0.set(Some(project.clone())); - - async move { - tabs_store - .lock() - .await - .set_editor_value(&format!("SELECT * FROM {}.{} LIMIT 100;", schema, table)); - //tabs_store.lock().await.run_query().await.unwrap() - } - }, - ); - - view! { -
- -
- -

{table.0}

-
-

{table.1}

-
- } -} - diff --git a/src/sidebar/tables.rs b/src/sidebar/tables.rs deleted file mode 100644 index c919075..0000000 --- a/src/sidebar/tables.rs +++ /dev/null @@ -1,28 +0,0 @@ -use leptos::*; - -use super::table::Table; -use crate::store::projects::ProjectsStore; - -#[component] -pub fn Tables(schema: String, project: String) -> impl IntoView { - let projects_store = expect_context::(); - let schema_clone = schema.clone(); - let project_clone = project.clone(); - let tables = create_resource( - || {}, - move |_| { - let schema = schema_clone.clone(); - let project = project_clone.clone(); - async move {} - }, - ); - - view! { - // } - // /> - } -} - diff --git a/src/store/projects.rs b/src/store/projects.rs index 2f00a0e..029510f 100644 --- a/src/store/projects.rs +++ b/src/store/projects.rs @@ -1,12 +1,11 @@ -use std::collections::BTreeMap; - +use ahash::AHashMap; use leptos::{RwSignal, SignalGet, SignalSet}; use tauri_sys::tauri::invoke; use crate::invoke::{Invoke, InvokeProjectDbDeleteArgs, InvokeProjectDbInsertArgs}; #[derive(Clone, Copy, Debug)] -pub struct ProjectsStore(pub RwSignal>); +pub struct ProjectsStore(pub RwSignal>); impl Default for ProjectsStore { fn default() -> Self { @@ -25,7 +24,7 @@ impl ProjectsStore { } pub async fn load_projects(&self) { - let projects = invoke::<_, BTreeMap>(Invoke::ProjectDbSelect.as_ref(), &()) + let projects = invoke::<_, AHashMap>(Invoke::ProjectDbSelect.as_ref(), &()) .await .unwrap(); self.0.set(projects); From 3f3179b1b74d7a007e3afdb880c0208507998f37 Mon Sep 17 00:00:00 2001 From: Daniel Boros Date: Sun, 12 May 2024 19:23:50 +0200 Subject: [PATCH 12/28] feat: add db performance panel --- src/app.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/app.rs b/src/app.rs index f822668..2dd8770 100644 --- a/src/app.rs +++ b/src/app.rs @@ -35,7 +35,7 @@ pub fn App() -> impl IntoView {
-
+
impl IntoView {
+
+
+

Database performance

+
+
+
} From e297a6782d06036bf7f80c5780e69e3fdfe38742 Mon Sep 17 00:00:00 2001 From: Daniel Boros Date: Sun, 12 May 2024 19:26:53 +0200 Subject: [PATCH 13/28] refactor: add better foldering --- src/databases/mod.rs | 2 ++ src/{ => databases}/pgsql/add.rs | 0 src/{ => databases}/pgsql/driver.rs | 2 -- src/{ => databases}/pgsql/index.rs | 0 src/{ => databases}/pgsql/mod.rs | 0 src/{ => databases}/pgsql/schema.rs | 0 src/{ => databases}/pgsql/table.rs | 0 src/main.rs | 2 +- src/sidebar/index.rs | 5 ++++- 9 files changed, 7 insertions(+), 4 deletions(-) create mode 100644 src/databases/mod.rs rename src/{ => databases}/pgsql/add.rs (100%) rename src/{ => databases}/pgsql/driver.rs (98%) rename src/{ => databases}/pgsql/index.rs (100%) rename src/{ => databases}/pgsql/mod.rs (100%) rename src/{ => databases}/pgsql/schema.rs (100%) rename src/{ => databases}/pgsql/table.rs (100%) diff --git a/src/databases/mod.rs b/src/databases/mod.rs new file mode 100644 index 0000000..173f9d2 --- /dev/null +++ b/src/databases/mod.rs @@ -0,0 +1,2 @@ +pub mod pgsql; + diff --git a/src/pgsql/add.rs b/src/databases/pgsql/add.rs similarity index 100% rename from src/pgsql/add.rs rename to src/databases/pgsql/add.rs diff --git a/src/pgsql/driver.rs b/src/databases/pgsql/driver.rs similarity index 98% rename from src/pgsql/driver.rs rename to src/databases/pgsql/driver.rs index 6f92699..6757211 100644 --- a/src/pgsql/driver.rs +++ b/src/databases/pgsql/driver.rs @@ -1,5 +1,3 @@ -use std::collections::BTreeMap; - use ahash::AHashMap; use common::enums::ProjectConnectionStatus; use leptos::{error::Result, RwSignal, SignalGet, SignalSet, SignalUpdate}; diff --git a/src/pgsql/index.rs b/src/databases/pgsql/index.rs similarity index 100% rename from src/pgsql/index.rs rename to src/databases/pgsql/index.rs diff --git a/src/pgsql/mod.rs b/src/databases/pgsql/mod.rs similarity index 100% rename from src/pgsql/mod.rs rename to src/databases/pgsql/mod.rs diff --git a/src/pgsql/schema.rs b/src/databases/pgsql/schema.rs similarity index 100% rename from src/pgsql/schema.rs rename to src/databases/pgsql/schema.rs diff --git a/src/pgsql/table.rs b/src/databases/pgsql/table.rs similarity index 100% rename from src/pgsql/table.rs rename to src/databases/pgsql/table.rs diff --git a/src/main.rs b/src/main.rs index 86a737d..61ec568 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,13 +1,13 @@ #![feature(pattern)] mod app; +mod databases; mod enums; mod footer; mod grid_view; mod hooks; mod invoke; mod modals; -mod pgsql; mod query_editor; mod query_table; mod record_view; diff --git a/src/sidebar/index.rs b/src/sidebar/index.rs index 627d70b..91d9b09 100644 --- a/src/sidebar/index.rs +++ b/src/sidebar/index.rs @@ -1,7 +1,10 @@ use leptos::*; use leptos_use::{use_document, use_event_listener}; -use crate::{pgsql::add::Add, pgsql::index::Pgsql, store::projects::ProjectsStore}; +use crate::{ + databases::pgsql::{add::Add, index::Pgsql}, + store::projects::ProjectsStore, +}; use common::enums::Drivers; use super::queries::Queries; From fd206c330f46336aeb3b621b5678be1f1e7f20f6 Mon Sep 17 00:00:00 2001 From: Daniel Boros Date: Sun, 12 May 2024 19:51:45 +0200 Subject: [PATCH 14/28] fix: remove sticky temporarly --- src/databases/pgsql/schema.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/databases/pgsql/schema.rs b/src/databases/pgsql/schema.rs index 4d693c7..a2daf70 100644 --- a/src/databases/pgsql/schema.rs +++ b/src/databases/pgsql/schema.rs @@ -23,7 +23,7 @@ pub fn Schema(schema: String) -> impl IntoView { view! {
-

{formatted_timer}

- - - } -} - diff --git a/src/modals/mod.rs b/src/modals/mod.rs index 534da11..b3cd075 100644 --- a/src/modals/mod.rs +++ b/src/modals/mod.rs @@ -1,3 +1,3 @@ -pub mod custom_query; -pub mod error; +pub mod add_custom_query; +pub mod add_pgsql_connection; diff --git a/src/queries/index.rs b/src/queries/index.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/queries/mod.rs b/src/queries/mod.rs new file mode 100644 index 0000000..d1ff2a4 --- /dev/null +++ b/src/queries/mod.rs @@ -0,0 +1,2 @@ +pub mod index; + diff --git a/src/query_editor.rs b/src/query_editor.rs index 182335a..af38183 100644 --- a/src/query_editor.rs +++ b/src/query_editor.rs @@ -12,7 +12,7 @@ use monaco::{ }; use wasm_bindgen::{closure::Closure, JsCast}; -use crate::{modals::custom_query::CustomQuery, store::tabs::TabsStore}; +use crate::{modals::add_custom_query::AddCustomQuery, store::tabs::TabsStore}; pub type ModelCell = Rc>>; pub const MODE_ID: &str = "pgsql"; @@ -72,7 +72,7 @@ pub fn QueryEditor() -> impl IntoView { view! {
- +
-
- - - - - } - } - /> - - - - +
diff --git a/src/dashboard/index.rs b/src/dashboard/index.rs new file mode 100644 index 0000000..727b6b6 --- /dev/null +++ b/src/dashboard/index.rs @@ -0,0 +1,56 @@ +use leptos::*; + +#[component] +pub fn Dashboard() -> impl IntoView { + view! { + + + + +
+ {format!("Tab {}", index + 1)} + +
+
+ + +
+ +
+ } + } + /> + + + } +} + diff --git a/src/queries/mod.rs b/src/dashboard/mod.rs similarity index 100% rename from src/queries/mod.rs rename to src/dashboard/mod.rs diff --git a/src/footer.rs b/src/footer.rs index 984b31c..cf56bf5 100644 --- a/src/footer.rs +++ b/src/footer.rs @@ -1,40 +1,29 @@ use leptos::*; use leptos_icons::*; -use crate::{enums::QueryTableLayout, store::active_project::ActiveProjectStore}; +use crate::enums::QueryTableLayout; #[component] pub fn Footer() -> impl IntoView { let table_view = expect_context::>(); - let acitve_project = expect_context::(); view! { -
-
-
}> -
-

Selected project:

-

{move || acitve_project.0.get()}

-
- -
-
- - -
+ + + } } diff --git a/src/main.rs b/src/main.rs index c6b5850..3b93df1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,13 +1,13 @@ #![feature(pattern)] mod app; +mod dashboard; mod databases; mod enums; mod footer; mod grid_view; mod invoke; mod modals; -mod queries; mod query_editor; mod query_table; mod record_view; diff --git a/src/queries/index.rs b/src/queries/index.rs deleted file mode 100644 index e69de29..0000000 diff --git a/src/query_editor.rs b/src/query_editor.rs index af38183..20d2082 100644 --- a/src/query_editor.rs +++ b/src/query_editor.rs @@ -72,7 +72,7 @@ pub fn QueryEditor() -> impl IntoView { view! {
- + //
} } diff --git a/src/dashboard/mod.rs b/src/dashboard/mod.rs index d1ff2a4..6065177 100644 --- a/src/dashboard/mod.rs +++ b/src/dashboard/mod.rs @@ -1,2 +1,4 @@ pub mod index; +pub mod query_editor; +pub mod query_table; diff --git a/src/query_editor.rs b/src/dashboard/query_editor.rs similarity index 93% rename from src/query_editor.rs rename to src/dashboard/query_editor.rs index 20d2082..c494474 100644 --- a/src/query_editor.rs +++ b/src/dashboard/query_editor.rs @@ -87,6 +87,12 @@ pub fn QueryEditor() -> impl IntoView { > "Query" +
diff --git a/src/query_table.rs b/src/dashboard/query_table.rs similarity index 100% rename from src/query_table.rs rename to src/dashboard/query_table.rs diff --git a/src/invoke.rs b/src/invoke.rs index 43906ab..c29be0b 100644 --- a/src/invoke.rs +++ b/src/invoke.rs @@ -14,6 +14,7 @@ pub enum Invoke { PgsqlConnector, PgsqlLoadSchemas, PgsqlLoadTables, + #[allow(dead_code)] PgsqlLoadRelations, PgsqlRunQuery, } diff --git a/src/main.rs b/src/main.rs index 3b93df1..dc37919 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,8 +8,6 @@ mod footer; mod grid_view; mod invoke; mod modals; -mod query_editor; -mod query_table; mod record_view; mod sidebar; mod store; diff --git a/src/store/atoms.rs b/src/store/atoms.rs index 631f4cf..44db0b2 100644 --- a/src/store/atoms.rs +++ b/src/store/atoms.rs @@ -9,17 +9,3 @@ pub struct QueryPerformanceAtom { pub type QueryPerformanceContext = RwSignal>; -#[derive(Debug, Default, Clone)] -pub struct ActiveTabAtom { - pub id: usize, -} - -pub type ActiveTabContext = RwSignal; - -#[derive(Debug, Default, Clone)] -pub struct SelectedTabAtom { - pub id: String, -} - -pub type SelectedTabContext = RwSignal; - diff --git a/src/store/tabs.rs b/src/store/tabs.rs index 3b42d15..8f6ea38 100644 --- a/src/store/tabs.rs +++ b/src/store/tabs.rs @@ -5,7 +5,7 @@ use leptos::{ }; use monaco::api::CodeEditor; -use crate::query_editor::ModelCell; +use crate::dashboard::query_editor::ModelCell; use super::{active_project::ActiveProjectStore, query::QueryStore}; @@ -20,7 +20,6 @@ struct QueryInfo { #[derive(Copy, Clone, Debug)] pub struct TabsStore { - pub active_tabs: RwSignal, pub selected_tab: RwSignal, pub editors: RwSignal>, #[allow(clippy::type_complexity)] @@ -41,7 +40,6 @@ impl TabsStore { #[must_use] pub fn new() -> Self { Self { - active_tabs: create_rw_signal(1), selected_tab: create_rw_signal(String::from("0")), editors: create_rw_signal(Vec::new()), sql_results: create_rw_signal(Vec::new()), @@ -118,14 +116,10 @@ impl TabsStore { #[allow(dead_code)] pub fn remove_editor(&mut self, index: usize) { - if self.active_tabs.get() == 1 { + if self.editors.get().len() == 1 { return; } - self.active_tabs.update(|prev| { - *prev -= 1; - }); - self.editors.update(|prev| { prev.remove(index); }); From 24a2c2dfb3864aa23e214db536be1b8e0c426cb1 Mon Sep 17 00:00:00 2001 From: Daniel Boros Date: Sun, 19 May 2024 10:33:02 +0200 Subject: [PATCH 18/28] refactor: tabs --- src/dashboard/index.rs | 8 +-- src/dashboard/query_editor.rs | 99 +++++++++++++++++++++-------------- src/store/tabs.rs | 28 +++++++--- 3 files changed, 83 insertions(+), 52 deletions(-) diff --git a/src/dashboard/index.rs b/src/dashboard/index.rs index a750337..220e1fb 100644 --- a/src/dashboard/index.rs +++ b/src/dashboard/index.rs @@ -8,12 +8,12 @@ use super::{query_editor::QueryEditor, query_table::QueryTable}; #[component] pub fn Dashboard() -> impl IntoView { - let mut tabs = expect_context::(); + let tabs_store = expect_context::(); view! { - + ().unwrap_or_default() + 1)) + each=move || (0..(tabs_store.active_tabs.get())) key=|index| index.to_string() children=move |index| { view! { @@ -24,7 +24,7 @@ pub fn Dashboard() -> impl IntoView { {format!("Tab {}", index + 1)}
diff --git a/src/store/tabs.rs b/src/store/tabs.rs index 8f6ea38..f96fedf 100644 --- a/src/store/tabs.rs +++ b/src/store/tabs.rs @@ -21,6 +21,7 @@ struct QueryInfo { #[derive(Copy, Clone, Debug)] pub struct TabsStore { pub selected_tab: RwSignal, + pub active_tabs: RwSignal, pub editors: RwSignal>, #[allow(clippy::type_complexity)] pub sql_results: RwSignal, Vec>)>>, @@ -41,6 +42,7 @@ impl TabsStore { pub fn new() -> Self { Self { selected_tab: create_rw_signal(String::from("0")), + active_tabs: create_rw_signal(1), editors: create_rw_signal(Vec::new()), sql_results: create_rw_signal(Vec::new()), is_loading: create_rw_signal(false), @@ -109,22 +111,32 @@ impl TabsStore { self.editors.update(|prev| { prev.push(editor); }); - self.sql_results.update(|prev| { - prev.push((Vec::new(), Vec::new())); + } + + pub fn add_tab(&self) { + self.active_tabs.update(|prev| { + *prev += 1; + }); + + self.selected_tab.update(|prev| { + *prev = (self.active_tabs.get() - 1).to_string(); }); } - #[allow(dead_code)] - pub fn remove_editor(&mut self, index: usize) { - if self.editors.get().len() == 1 { + pub fn close_tab(&self, index: usize) { + if self.active_tabs.get() == 1 { return; } - self.editors.update(|prev| { - prev.remove(index); + self.selected_tab.update(|prev| { + *prev = (index - 1).to_string(); }); - self.sql_results.update(|prev| { + self.active_tabs.update(|prev| { + *prev -= 1; + }); + + self.editors.update(|prev| { prev.remove(index); }); } From 216054905bd9ac4a6113f479b35c7ce117d2a71d Mon Sep 17 00:00:00 2001 From: Daniel Boros Date: Sun, 19 May 2024 12:26:17 +0200 Subject: [PATCH 19/28] refactor: table default query --- Cargo.toml | 9 +++++- src-tauri/Cargo.toml | 2 +- src-tauri/src/drivers/pgsql.rs | 8 +++--- src/app.rs | 3 +- src/dashboard/index.rs | 37 +++++++++++++------------ src/dashboard/query_editor.rs | 6 ++-- src/dashboard/query_table.rs | 41 ++++++++++++++++++++++++---- src/databases/pgsql/driver.rs | 18 ++++++++++-- src/databases/pgsql/schema.rs | 7 +++-- src/databases/pgsql/table.rs | 50 ++++++++++++++-------------------- src/lib.rs | 32 ++++++++++++++++++++++ src/store/atoms.rs | 15 ++++++++++ src/store/query.rs | 20 +++++++------- src/store/tabs.rs | 35 +++++++++++++----------- 14 files changed, 189 insertions(+), 94 deletions(-) create mode 100644 src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index afb8d32..0ca9020 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "rust-sql-gui-ui" +name = "rsql" version = "1.0.0-alpha.9" edition = "2021" @@ -24,6 +24,13 @@ async-stream = "0.3.5" icondata = "0.3.0" ahash = { version = "0.8.11", features = ["serde"] } leptos_toaster = { version = "0.1.6", features = ["builtin_toast"] } +proc-macro2 = "1.0.82" +quote = "1.0.36" +syn = { version = "2.0.64", features = ["full"] } + [workspace] members = ["src-tauri", "common"] + +[lib] +proc-macro = true \ No newline at end of file diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 3c7d900..ba8be33 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "rust-sql-gui" +name = "rsql_tauri" version = "1.0.0-alpha.9" description = "PostgreSQL GUI written in Rust" authors = ["Daniel Boros"] diff --git a/src-tauri/src/drivers/pgsql.rs b/src-tauri/src/drivers/pgsql.rs index 2a9b39b..70e9361 100644 --- a/src-tauri/src/drivers/pgsql.rs +++ b/src-tauri/src/drivers/pgsql.rs @@ -139,14 +139,14 @@ pub async fn pgsql_load_tables( #[tauri::command(rename_all = "snake_case")] pub async fn pgsql_run_query( - project_name: &str, - sql: String, + project_id: &str, + sql: &str, app_state: State<'_, AppState>, ) -> Result<(Vec, Vec>, f32)> { let start = Instant::now(); let clients = app_state.client.lock().await; - let client = clients.as_ref().unwrap().get(project_name).unwrap(); - let rows = client.query(sql.as_str(), &[]).await.unwrap(); + let client = clients.as_ref().unwrap().get(project_id).unwrap(); + let rows = client.query(sql, &[]).await.unwrap(); if rows.is_empty() { return Ok((Vec::new(), Vec::new(), 0.0f32)); diff --git a/src/app.rs b/src/app.rs index 394bfe4..47bec99 100644 --- a/src/app.rs +++ b/src/app.rs @@ -7,7 +7,7 @@ use crate::{ footer::Footer, sidebar::index::Sidebar, store::{ - atoms::{QueryPerformanceAtom, QueryPerformanceContext}, + atoms::{QueryPerformanceAtom, QueryPerformanceContext, RunQueryAtom, RunQueryContext}, projects::ProjectsStore, query::QueryStore, tabs::TabsStore, @@ -23,6 +23,7 @@ pub fn App() -> impl IntoView { provide_context(ProjectsStore::default()); provide_context(RwSignal::new(QueryTableLayout::Grid)); provide_context::(RwSignal::new(Vec::::new())); + provide_context::(RwSignal::new(RunQueryAtom::default())); provide_context(TabsStore::default()); view! { diff --git a/src/dashboard/index.rs b/src/dashboard/index.rs index 220e1fb..d3f1b42 100644 --- a/src/dashboard/index.rs +++ b/src/dashboard/index.rs @@ -1,4 +1,4 @@ -use leptos::*; +use leptos::{logging::log, *}; use leptos_icons::Icon; use thaw::{Tab, TabLabel, Tabs}; @@ -9,6 +9,9 @@ use super::{query_editor::QueryEditor, query_table::QueryTable}; #[component] pub fn Dashboard() -> impl IntoView { let tabs_store = expect_context::(); + create_effect(move |_| { + log!("Selected tab: {}", tabs_store.selected_tab.get()); + }); view! { @@ -17,24 +20,22 @@ pub fn Dashboard() -> impl IntoView { key=|index| index.to_string() children=move |index| { view! { -
- - -
- {format!("Tab {}", index + 1)} - -
-
- - -
-
+ + +
+ + + + } } /> diff --git a/src/dashboard/query_editor.rs b/src/dashboard/query_editor.rs index 023547b..7fa956d 100644 --- a/src/dashboard/query_editor.rs +++ b/src/dashboard/query_editor.rs @@ -1,7 +1,7 @@ -use std::{borrow::Borrow, cell::RefCell, rc::Rc, sync::Arc}; +use std::{cell::RefCell, rc::Rc, sync::Arc}; use futures::lock::Mutex; -use leptos::{svg::A, *}; +use leptos::*; use leptos_use::{use_document, use_event_listener}; use monaco::{ api::{CodeEditor, CodeEditorOptions, TextModel}, @@ -36,7 +36,7 @@ pub fn QueryEditor() -> impl IntoView { let html_element = div_element.unchecked_ref::(); let options = CodeEditorOptions::default().to_sys_options(); let text_model = - TextModel::create("SELECT * FROM users LIMIT 100;", Some(MODE_ID), None).unwrap(); + TextModel::create("# Add your SQL query here...", Some(MODE_ID), None).unwrap(); options.set_model(Some(text_model.as_ref())); options.set_language(Some(MODE_ID)); options.set_automatic_layout(Some(true)); diff --git a/src/dashboard/query_table.rs b/src/dashboard/query_table.rs index ffbd691..ae97342 100644 --- a/src/dashboard/query_table.rs +++ b/src/dashboard/query_table.rs @@ -1,25 +1,54 @@ +use leptos::*; +use leptos_icons::*; + use crate::{ - enums::QueryTableLayout, grid_view::GridView, record_view::RecordView, store::tabs::TabsStore, + enums::QueryTableLayout, + grid_view::GridView, + record_view::RecordView, + store::{atoms::RunQueryContext, tabs::TabsStore}, }; -use leptos::*; #[component] pub fn QueryTable() -> impl IntoView { let tabs_store = expect_context::(); let table_view = expect_context::>(); + let is_query_running = expect_context::(); view! { - "Loading..."

}> + + +

"Running query..."

+
+ } + } + > + {move || match tabs_store.select_active_editor_sql_result() { - None => view! { <>"No data to display" }, + None => { + view! { +
+ "No data to display" +
+ } + } Some(_) => { view! { - <> +
{match table_view.get() { QueryTableLayout::Grid => view! { }, QueryTableLayout::Records => view! { }, }} - + +
} } }} diff --git a/src/databases/pgsql/driver.rs b/src/databases/pgsql/driver.rs index d4802ad..d5ddece 100644 --- a/src/databases/pgsql/driver.rs +++ b/src/databases/pgsql/driver.rs @@ -4,6 +4,7 @@ use common::{ types::pgsql::{PgsqlLoadSchemas, PgsqlLoadTables, PgsqlRunQuery}, }; use leptos::{error::Result, expect_context, RwSignal, SignalGet, SignalSet, SignalUpdate}; +use rsql::set_running_query; use tauri_sys::tauri::invoke; use crate::{ @@ -11,7 +12,10 @@ use crate::{ Invoke, InvokePgsqlConnectorArgs, InvokePgsqlLoadSchemasArgs, InvokePgsqlLoadTablesArgs, InvokePgsqlRunQueryArgs, }, - store::atoms::{QueryPerformanceAtom, QueryPerformanceContext}, + store::{ + atoms::{QueryPerformanceAtom, QueryPerformanceContext, RunQueryAtom, RunQueryContext}, + tabs::TabsStore, + }, }; #[derive(Debug, Clone, Copy)] @@ -91,7 +95,10 @@ impl<'a> Pgsql<'a> { }); } - pub async fn run_query(&self, sql: &str) { + #[set_running_query] + pub async fn run_default_table_query(&self, sql: &str) { + let tabs_store = expect_context::(); + tabs_store.set_editor_value(sql); let query = invoke::<_, PgsqlRunQuery>( Invoke::PgsqlRunQuery.as_ref(), &InvokePgsqlRunQueryArgs { @@ -102,6 +109,13 @@ impl<'a> Pgsql<'a> { .await .unwrap(); let (cols, rows, query_time) = query; + tabs_store.sql_results.update(|prev| { + let index = tabs_store.convert_selected_tab_to_index(); + match prev.get_mut(index) { + Some(sql_result) => *sql_result = (cols, rows), + None => prev.push((cols, rows)), + } + }); let qp_store = expect_context::(); qp_store.update(|prev| { prev.push(QueryPerformanceAtom { diff --git a/src/databases/pgsql/schema.rs b/src/databases/pgsql/schema.rs index a2daf70..a4ed5cb 100644 --- a/src/databases/pgsql/schema.rs +++ b/src/databases/pgsql/schema.rs @@ -60,8 +60,11 @@ pub fn Schema(schema: String) -> impl IntoView { } key=|table| table.0.clone() - children=move |table| { - view! {
} + children={ + let schema = schema.clone(); + move |table| { + view! {
} + } } /> diff --git a/src/databases/pgsql/table.rs b/src/databases/pgsql/table.rs index ef3fcd7..da573b3 100644 --- a/src/databases/pgsql/table.rs +++ b/src/databases/pgsql/table.rs @@ -1,41 +1,31 @@ -use futures::lock::Mutex; use leptos::*; use leptos_icons::*; -use crate::store::{active_project::ActiveProjectStore, tabs::TabsStore}; +use crate::databases::pgsql::driver::Pgsql; #[component] -pub fn Table( - table: (String, String), - project: Option, - schema: Option, -) -> impl IntoView { - // let tabs_store = Arc::new(Mutex::new(expect_context::())); - // let active_project = expect_context::(); - // let query = create_action( - // move |(schema, table, tabs_store): &(String, String, Arc>)| { - // let tabs_store = tabs_store.clone(); - // let project = project.clone(); - // let schema = schema.clone(); - // let table = table.clone(); - // active_project.0.set(Some(project.clone())); +pub fn Table(table: (String, String), schema: String) -> impl IntoView { + let pgsql = expect_context::(); + let query = create_action(move |(schema, table, pgsql): &(String, String, Pgsql)| { + let pgsql = pgsql.clone(); + let schema = schema.clone(); + let table = table.clone(); - // async move { - // tabs_store - // .lock() - // .await - // .set_editor_value(&format!("SELECT * FROM {}.{} LIMIT 100;", schema, table)); - // //tabs_store.lock().await.run_query().await.unwrap() - // } - // }, - // ); + async move { + pgsql + .run_default_table_query(&format!("SELECT * FROM {}.{} LIMIT 100;", schema, table)) + .await; + } + }); view! { -
- // on:click={ - // let table = table.clone(); - // move |_| { query.dispatch((schema.clone(), table.0.clone(), tabs_store.clone())) } - // } +
diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..fce1c62 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,32 @@ +extern crate proc_macro; + +use proc_macro::TokenStream; +use quote::quote; +use syn::{parse_macro_input, ItemFn}; + +#[proc_macro_attribute] +pub fn set_running_query(_attr: TokenStream, item: TokenStream) -> TokenStream { + let input = parse_macro_input!(item as ItemFn); + + let vis = &input.vis; + let sig = &input.sig; + let block = &input.block; + + let gen = quote! { + #vis #sig { + let run_query_atom = expect_context::(); + run_query_atom.set(RunQueryAtom { is_running: true }); + + let result = async { + #block + }.await; + + run_query_atom.set(RunQueryAtom { is_running: false }); + + result + } + }; + + TokenStream::from(gen) +} + diff --git a/src/store/atoms.rs b/src/store/atoms.rs index 44db0b2..a8c9af0 100644 --- a/src/store/atoms.rs +++ b/src/store/atoms.rs @@ -1,3 +1,5 @@ +use std::default; + use leptos::RwSignal; #[derive(Debug, Default, Clone)] @@ -9,3 +11,16 @@ pub struct QueryPerformanceAtom { pub type QueryPerformanceContext = RwSignal>; +#[derive(Debug, Clone)] +pub struct RunQueryAtom { + pub is_running: bool, +} + +impl default::Default for RunQueryAtom { + fn default() -> Self { + Self { is_running: false } + } +} + +pub type RunQueryContext = RwSignal; + diff --git a/src/store/query.rs b/src/store/query.rs index 3288ece..19e8b6d 100644 --- a/src/store/query.rs +++ b/src/store/query.rs @@ -39,16 +39,16 @@ impl QueryStore { pub async fn insert_query(&self, key: &str, project_name: &str) -> Result<()> { let tabs_store = expect_context::(); - let sql = tabs_store.select_active_editor_value(); - invoke( - &Invoke::QueryDbInsert.to_string(), - &InvokeInsertQueryArgs { - key: &format!("{}:{}", project_name, key), - sql: sql.as_str(), - }, - ) - .await?; - self.select_queries().await?; + // let sql = tabs_store.select_active_editor_value(); + // invoke( + // &Invoke::QueryDbInsert.to_string(), + // &InvokeInsertQueryArgs { + // key: &format!("{}:{}", project_name, key), + // sql: sql.as_str(), + // }, + // ) + // .await?; + // self.select_queries().await?; Ok(()) } diff --git a/src/store/tabs.rs b/src/store/tabs.rs index f96fedf..d4913dd 100644 --- a/src/store/tabs.rs +++ b/src/store/tabs.rs @@ -1,7 +1,8 @@ use std::{cell::RefCell, rc::Rc}; use leptos::{ - create_rw_signal, error::Result, use_context, RwSignal, SignalGet, SignalSet, SignalUpdate, + create_rw_signal, error::Result, logging::log, use_context, RwSignal, SignalGet, SignalSet, + SignalUpdate, }; use monaco::api::CodeEditor; @@ -95,7 +96,7 @@ impl TabsStore { let query_store = use_context::().unwrap(); let query_store = query_store.0.get(); let query = query_store.get(key).unwrap(); - self.set_editor_value(query); + //self.set_editor_value(query); Ok(()) } @@ -114,6 +115,7 @@ impl TabsStore { } pub fn add_tab(&self) { + log!("Adding tab"); self.active_tabs.update(|prev| { *prev += 1; }); @@ -124,6 +126,7 @@ impl TabsStore { } pub fn close_tab(&self, index: usize) { + log!("Closing tab"); if self.active_tabs.get() == 1 { return; } @@ -150,19 +153,19 @@ impl TabsStore { .clone() } - pub fn select_active_editor_value(&self) -> String { - self - .editors - .get() - .get(self.convert_selected_tab_to_index()) - .unwrap() - .borrow() - .as_ref() - .unwrap() - .get_model() - .unwrap() - .get_value() - } + // // pub fn select_active_editor_value(&self) -> String { + // // self + // // .editors + // // .get() + // // .get(self.convert_selected_tab_to_index()) + // // .unwrap() + // // .borrow() + // // .as_ref() + // // .unwrap() + // // .get_model() + // // .unwrap() + // // .get_value() + // // } pub fn set_editor_value(&self, value: &str) { self @@ -178,7 +181,7 @@ impl TabsStore { .set_value(value); } - pub(self) fn convert_selected_tab_to_index(&self) -> usize { + pub fn convert_selected_tab_to_index(&self) -> usize { self.selected_tab.get().parse::().unwrap() } From 6540757a420972d2c2362b4d0cd1ccda5164f101 Mon Sep 17 00:00:00 2001 From: Daniel Boros Date: Sun, 19 May 2024 13:02:06 +0200 Subject: [PATCH 20/28] feat: add query performance history --- Cargo.toml | 3 ++- src/app.rs | 12 +++++++----- src/databases/pgsql/driver.rs | 11 +++++++++-- src/main.rs | 1 + src/performane.rs | 29 +++++++++++++++++++++++++++++ src/store/atoms.rs | 5 +++-- 6 files changed, 51 insertions(+), 10 deletions(-) create mode 100644 src/performane.rs diff --git a/Cargo.toml b/Cargo.toml index 0ca9020..7fd0843 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,10 +27,11 @@ leptos_toaster = { version = "0.1.6", features = ["builtin_toast"] } proc-macro2 = "1.0.82" quote = "1.0.36" syn = { version = "2.0.64", features = ["full"] } +chrono = "0.4.38" [workspace] members = ["src-tauri", "common"] [lib] -proc-macro = true \ No newline at end of file +proc-macro = true diff --git a/src/app.rs b/src/app.rs index 47bec99..ff4ef26 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,3 +1,5 @@ +use std::collections::VecDeque; + use leptos::*; use leptos_toaster::{Toaster, ToasterPosition}; @@ -5,6 +7,7 @@ use crate::{ dashboard::index::Dashboard, enums::QueryTableLayout, footer::Footer, + performane::Performance, sidebar::index::Sidebar, store::{ atoms::{QueryPerformanceAtom, QueryPerformanceContext, RunQueryAtom, RunQueryContext}, @@ -22,7 +25,9 @@ pub fn App() -> impl IntoView { provide_context(QueryStore::default()); provide_context(ProjectsStore::default()); provide_context(RwSignal::new(QueryTableLayout::Grid)); - provide_context::(RwSignal::new(Vec::::new())); + provide_context::( + RwSignal::new(VecDeque::::new()), + ); provide_context::(RwSignal::new(RunQueryAtom::default())); provide_context(TabsStore::default()); @@ -37,10 +42,7 @@ pub fn App() -> impl IntoView {
-
-

Database performance

-
-
+
diff --git a/src/databases/pgsql/driver.rs b/src/databases/pgsql/driver.rs index d5ddece..1fb9771 100644 --- a/src/databases/pgsql/driver.rs +++ b/src/databases/pgsql/driver.rs @@ -116,10 +116,17 @@ impl<'a> Pgsql<'a> { None => prev.push((cols, rows)), } }); + let qp_store = expect_context::(); qp_store.update(|prev| { - prev.push(QueryPerformanceAtom { - message: Some(format!("Query executed in {:.2} seconds", query_time)), + prev.push_front(QueryPerformanceAtom { + id: prev.len(), + message: Some(format!( + "[{}]: {} is executed in {} ms", + chrono::Utc::now(), + sql, + query_time + )), execution_time: Some(query_time), query: Some(sql.to_string()), }) diff --git a/src/main.rs b/src/main.rs index dc37919..e9198d1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,6 +8,7 @@ mod footer; mod grid_view; mod invoke; mod modals; +mod performane; mod record_view; mod sidebar; mod store; diff --git a/src/performane.rs b/src/performane.rs new file mode 100644 index 0000000..d696904 --- /dev/null +++ b/src/performane.rs @@ -0,0 +1,29 @@ +use leptos::*; + +use crate::store::atoms::QueryPerformanceContext; + +#[component] +pub fn Performance() -> impl IntoView { + let performance = expect_context::(); + + view! { +
+

"Performance"

+
+ + {item.message.clone()} +
+ } + } + /> + +
+
+ } +} + diff --git a/src/store/atoms.rs b/src/store/atoms.rs index a8c9af0..dd6d6b8 100644 --- a/src/store/atoms.rs +++ b/src/store/atoms.rs @@ -1,15 +1,16 @@ -use std::default; +use std::{collections::VecDeque, default}; use leptos::RwSignal; #[derive(Debug, Default, Clone)] pub struct QueryPerformanceAtom { + pub id: usize, pub message: Option, pub execution_time: Option, pub query: Option, } -pub type QueryPerformanceContext = RwSignal>; +pub type QueryPerformanceContext = RwSignal>; #[derive(Debug, Clone)] pub struct RunQueryAtom { From 74c13d0db95d049bdeeb917af064acf2ecedc214 Mon Sep 17 00:00:00 2001 From: Daniel Boros Date: Sun, 19 May 2024 13:13:14 +0200 Subject: [PATCH 21/28] chore: add some clippy fix --- src/databases/pgsql/driver.rs | 2 -- src/databases/pgsql/table.rs | 2 +- src/store/atoms.rs | 10 +--------- src/store/tabs.rs | 2 -- 4 files changed, 2 insertions(+), 14 deletions(-) diff --git a/src/databases/pgsql/driver.rs b/src/databases/pgsql/driver.rs index 1fb9771..0f585d7 100644 --- a/src/databases/pgsql/driver.rs +++ b/src/databases/pgsql/driver.rs @@ -127,8 +127,6 @@ impl<'a> Pgsql<'a> { sql, query_time )), - execution_time: Some(query_time), - query: Some(sql.to_string()), }) }); } diff --git a/src/databases/pgsql/table.rs b/src/databases/pgsql/table.rs index da573b3..4b19b9d 100644 --- a/src/databases/pgsql/table.rs +++ b/src/databases/pgsql/table.rs @@ -7,7 +7,7 @@ use crate::databases::pgsql::driver::Pgsql; pub fn Table(table: (String, String), schema: String) -> impl IntoView { let pgsql = expect_context::(); let query = create_action(move |(schema, table, pgsql): &(String, String, Pgsql)| { - let pgsql = pgsql.clone(); + let pgsql = *pgsql; let schema = schema.clone(); let table = table.clone(); diff --git a/src/store/atoms.rs b/src/store/atoms.rs index dd6d6b8..e664d9f 100644 --- a/src/store/atoms.rs +++ b/src/store/atoms.rs @@ -6,22 +6,14 @@ use leptos::RwSignal; pub struct QueryPerformanceAtom { pub id: usize, pub message: Option, - pub execution_time: Option, - pub query: Option, } pub type QueryPerformanceContext = RwSignal>; -#[derive(Debug, Clone)] +#[derive(Debug, Default, Clone)] pub struct RunQueryAtom { pub is_running: bool, } -impl default::Default for RunQueryAtom { - fn default() -> Self { - Self { is_running: false } - } -} - pub type RunQueryContext = RwSignal; diff --git a/src/store/tabs.rs b/src/store/tabs.rs index d4913dd..4bed32d 100644 --- a/src/store/tabs.rs +++ b/src/store/tabs.rs @@ -26,7 +26,6 @@ pub struct TabsStore { pub editors: RwSignal>, #[allow(clippy::type_complexity)] pub sql_results: RwSignal, Vec>)>>, - pub is_loading: RwSignal, } unsafe impl Send for TabsStore {} @@ -46,7 +45,6 @@ impl TabsStore { active_tabs: create_rw_signal(1), editors: create_rw_signal(Vec::new()), sql_results: create_rw_signal(Vec::new()), - is_loading: create_rw_signal(false), } } From 2388cc414758336d11ce903e3c905ab2ad4e8503 Mon Sep 17 00:00:00 2001 From: Daniel Boros Date: Sun, 19 May 2024 14:59:13 +0200 Subject: [PATCH 22/28] refactor: new tab addition --- src/dashboard/index.rs | 2 +- src/dashboard/query_editor.rs | 28 +++--- src/databases/pgsql/driver.rs | 9 +- src/databases/pgsql/index.rs | 37 +++++--- src/databases/pgsql/schema.rs | 46 +++++----- src/modals/add_custom_query.rs | 8 +- src/store/active_project.rs | 17 ---- src/store/mod.rs | 1 - src/store/tabs.rs | 159 ++++++++++++++++++--------------- 9 files changed, 160 insertions(+), 147 deletions(-) delete mode 100644 src/store/active_project.rs diff --git a/src/dashboard/index.rs b/src/dashboard/index.rs index d3f1b42..33ebd0d 100644 --- a/src/dashboard/index.rs +++ b/src/dashboard/index.rs @@ -33,7 +33,7 @@ pub fn Dashboard() -> impl IntoView { - + } diff --git a/src/dashboard/query_editor.rs b/src/dashboard/query_editor.rs index 7fa956d..668758a 100644 --- a/src/dashboard/query_editor.rs +++ b/src/dashboard/query_editor.rs @@ -18,8 +18,12 @@ pub type ModelCell = Rc>>; pub const MODE_ID: &str = "pgsql"; #[component] -pub fn QueryEditor() -> impl IntoView { +pub fn QueryEditor(index: usize) -> impl IntoView { let tabs_store = expect_context::(); + let active_project = move || match tabs_store.selected_projects.get().get(index) { + Some(project) => Some(project.clone()), + _ => None, + }; let tabs_store_rc = Rc::new(RefCell::new(tabs_store)); let show = create_rw_signal(false); let _ = use_event_listener(use_document(), ev::keydown, move |event| { @@ -84,8 +88,16 @@ pub fn QueryEditor() -> impl IntoView { view! {
// -
-
+
+
} + > +
+ {active_project} +
+ +
-
diff --git a/src/databases/pgsql/driver.rs b/src/databases/pgsql/driver.rs index 0f585d7..d69af98 100644 --- a/src/databases/pgsql/driver.rs +++ b/src/databases/pgsql/driver.rs @@ -99,6 +99,14 @@ impl<'a> Pgsql<'a> { pub async fn run_default_table_query(&self, sql: &str) { let tabs_store = expect_context::(); tabs_store.set_editor_value(sql); + tabs_store.selected_projects.update(|prev| { + let index = tabs_store.convert_selected_tab_to_index(); + match prev.get_mut(index) { + Some(project) => *project = self.project_id.get().clone(), + None => prev.push(self.project_id.get().clone()), + } + }); + let query = invoke::<_, PgsqlRunQuery>( Invoke::PgsqlRunQuery.as_ref(), &InvokePgsqlRunQueryArgs { @@ -116,7 +124,6 @@ impl<'a> Pgsql<'a> { None => prev.push((cols, rows)), } }); - let qp_store = expect_context::(); qp_store.update(|prev| { prev.push_front(QueryPerformanceAtom { diff --git a/src/databases/pgsql/index.rs b/src/databases/pgsql/index.rs index 4d0d232..297142f 100644 --- a/src/databases/pgsql/index.rs +++ b/src/databases/pgsql/index.rs @@ -3,11 +3,12 @@ use leptos_icons::*; use leptos_toaster::{Toast, ToastId, ToastVariant, Toasts}; use super::{driver::Pgsql, schema::Schema}; -use crate::store::projects::ProjectsStore; +use crate::store::{projects::ProjectsStore, tabs::TabsStore}; use common::enums::ProjectConnectionStatus; #[component] pub fn Pgsql(project_id: String) -> impl IntoView { + let tabs_store = expect_context::(); let projects_store = expect_context::(); let project_details = projects_store.select_project_by_name(&project_id).unwrap(); let connection_params = project_details @@ -112,18 +113,32 @@ pub fn Pgsql(project_id: String) -> impl IntoView { {pgsql.project_id} - + + + +
diff --git a/src/databases/pgsql/schema.rs b/src/databases/pgsql/schema.rs index a4ed5cb..bb3313b 100644 --- a/src/databases/pgsql/schema.rs +++ b/src/databases/pgsql/schema.rs @@ -45,34 +45,28 @@ pub fn Schema(schema: String) -> impl IntoView { {(*schema).clone()} +
+ + - - } - } - } - /> - - -
- } - } + key=|table| table.0.clone() + children={ + let schema = schema.clone(); + move |table| { + view! {
} + } + } + /> + + } } diff --git a/src/modals/add_custom_query.rs b/src/modals/add_custom_query.rs index 930cbec..5d58197 100644 --- a/src/modals/add_custom_query.rs +++ b/src/modals/add_custom_query.rs @@ -1,9 +1,7 @@ use leptos::*; use thaw::{Modal, ModalFooter}; -use crate::store::{ - active_project::ActiveProjectStore, projects::ProjectsStore, query::QueryStore, -}; +use crate::store::{projects::ProjectsStore, query::QueryStore}; #[component] pub fn AddCustomQuery(show: RwSignal) -> impl IntoView { @@ -11,8 +9,8 @@ pub fn AddCustomQuery(show: RwSignal) -> impl IntoView { let query_store = expect_context::(); let (query_title, set_query_title) = create_signal(String::new()); //let projects = create_memo(move |_| projects_store.get_projects().unwrap()); - let active_project = expect_context::(); - let (project_name, set_project_name) = create_signal(active_project.0.get().unwrap_or_default()); + + let (project_name, set_project_name) = create_signal("".to_string()); // create_effect(move |_| { // if !projects.get().is_empty() { // set_project_name(projects.get()[0].clone()); diff --git a/src/store/active_project.rs b/src/store/active_project.rs deleted file mode 100644 index 52a8339..0000000 --- a/src/store/active_project.rs +++ /dev/null @@ -1,17 +0,0 @@ -use leptos::{create_rw_signal, RwSignal}; - -#[derive(Clone, Debug)] -pub struct ActiveProjectStore(pub RwSignal>); - -impl Default for ActiveProjectStore { - fn default() -> Self { - Self::new() - } -} - -impl ActiveProjectStore { - #[must_use] - pub fn new() -> Self { - Self(create_rw_signal(None)) - } -} diff --git a/src/store/mod.rs b/src/store/mod.rs index 10808ae..3c270fa 100644 --- a/src/store/mod.rs +++ b/src/store/mod.rs @@ -1,4 +1,3 @@ -pub mod active_project; pub mod atoms; pub mod projects; pub mod query; diff --git a/src/store/tabs.rs b/src/store/tabs.rs index 4bed32d..c6559c8 100644 --- a/src/store/tabs.rs +++ b/src/store/tabs.rs @@ -1,14 +1,22 @@ use std::{cell::RefCell, rc::Rc}; use leptos::{ - create_rw_signal, error::Result, logging::log, use_context, RwSignal, SignalGet, SignalSet, - SignalUpdate, + create_rw_signal, error::Result, expect_context, logging::log, use_context, RwSignal, SignalGet, + SignalSet, SignalUpdate, }; use monaco::api::CodeEditor; +use rsql::set_running_query; +use tauri_sys::tauri::invoke; -use crate::dashboard::query_editor::ModelCell; +use crate::{ + dashboard::query_editor::ModelCell, + invoke::{Invoke, InvokePgsqlRunQueryArgs}, +}; -use super::{active_project::ActiveProjectStore, query::QueryStore}; +use super::{ + atoms::{RunQueryAtom, RunQueryContext}, + query::QueryStore, +}; #[derive(Clone, Debug)] struct QueryInfo { @@ -26,6 +34,7 @@ pub struct TabsStore { pub editors: RwSignal>, #[allow(clippy::type_complexity)] pub sql_results: RwSignal, Vec>)>>, + pub selected_projects: RwSignal>, } unsafe impl Send for TabsStore {} @@ -45,65 +54,57 @@ impl TabsStore { active_tabs: create_rw_signal(1), editors: create_rw_signal(Vec::new()), sql_results: create_rw_signal(Vec::new()), + selected_projects: create_rw_signal(vec![String::new()]), } } - // pub async fn run_query(&self) -> Result<()> { - // self.is_loading.set(true); - // let active_project = use_context::().unwrap(); - // let active_project = active_project.0.get().unwrap(); - // let projects_store = use_context::().unwrap(); - // projects_store.(&active_project).await?; - // let active_editor = self.select_active_editor(); - // let position = active_editor - // .borrow() - // .as_ref() - // .unwrap() - // .as_ref() - // .get_position() - // .unwrap(); - // let sql = self.select_active_editor_value(); - // let sql = self - // .find_query_for_line(&sql, position.line_number()) - // .unwrap(); - // let (cols, rows, elasped) = invoke::<_, (Vec, Vec>, f32)>( - // &Invoke::pgsql_run_query.to_string(), - // &InvokeSqlResultArgs { - // project_name: &active_project, - // sql: &sql.query, - // }, - // ) - // .await?; - // let sql_timer = use_context::>().unwrap(); - // sql_timer.set(elasped); - // self.sql_results.update(|prev| { - // let index = self.convert_selected_tab_to_index(); - // match prev.get_mut(index) { - // Some(sql_result) => *sql_result = (cols, rows), - // None => prev.push((cols, rows)), - // } - // }); - // self.is_loading.set(false); - // Ok(()) - // } + #[set_running_query] + pub async fn run_query(&self) -> Result<()> { + let project_ids = self.selected_projects.get(); + let project_id = project_ids + .get(self.convert_selected_tab_to_index()) + .unwrap(); + let active_editor = self.select_active_editor(); + let position = active_editor + .borrow() + .as_ref() + .unwrap() + .as_ref() + .get_position() + .unwrap(); + let sql = self.select_active_editor_value(); + let sql = self + .find_query_for_line(&sql, position.line_number()) + .unwrap(); + let (cols, rows, elasped) = invoke::<_, (Vec, Vec>, f32)>( + &Invoke::PgsqlRunQuery.to_string(), + &InvokePgsqlRunQueryArgs { + project_id, + sql: &sql.query, + }, + ) + .await?; + let sql_timer = use_context::>().unwrap(); + sql_timer.set(elasped); + self.sql_results.update(|prev| { + let index = self.convert_selected_tab_to_index(); + match prev.get_mut(index) { + Some(sql_result) => *sql_result = (cols, rows), + None => prev.push((cols, rows)), + } + }); - pub fn load_query(&self, key: &str) -> Result<()> { - let active_project = use_context::().unwrap(); - let splitted_key = key.split(':').collect::>(); - active_project.0.set(Some(splitted_key[0].to_string())); - let query_store = use_context::().unwrap(); - let query_store = query_store.0.get(); - let query = query_store.get(key).unwrap(); - //self.set_editor_value(query); Ok(()) } - pub fn select_active_editor_sql_result(&self) -> Option<(Vec, Vec>)> { - self - .sql_results - .get() - .get(self.convert_selected_tab_to_index()) - .cloned() + pub fn load_query(&self, key: &str) -> Result<()> { + // let splitted_key = key.split(':').collect::>(); + // active_project.0.set(Some(splitted_key[0].to_string())); + // let query_store = use_context::().unwrap(); + // let query_store = query_store.0.get(); + // let query = query_store.get(key).unwrap(); + //self.set_editor_value(query); + Ok(()) } pub fn add_editor(&mut self, editor: Rc>>) { @@ -112,8 +113,7 @@ impl TabsStore { }); } - pub fn add_tab(&self) { - log!("Adding tab"); + pub fn add_tab(&self, project_id: &str) { self.active_tabs.update(|prev| { *prev += 1; }); @@ -121,10 +121,13 @@ impl TabsStore { self.selected_tab.update(|prev| { *prev = (self.active_tabs.get() - 1).to_string(); }); + + self.selected_projects.update(|prev| { + prev.push(project_id.to_string()); + }); } pub fn close_tab(&self, index: usize) { - log!("Closing tab"); if self.active_tabs.get() == 1 { return; } @@ -142,6 +145,18 @@ impl TabsStore { }); } + pub fn select_active_project(&self, index: usize) -> Option { + self.selected_projects.get().get(index).cloned() + } + + pub fn select_active_editor_sql_result(&self) -> Option<(Vec, Vec>)> { + self + .sql_results + .get() + .get(self.convert_selected_tab_to_index()) + .cloned() + } + pub fn select_active_editor(&self) -> ModelCell { self .editors @@ -151,19 +166,19 @@ impl TabsStore { .clone() } - // // pub fn select_active_editor_value(&self) -> String { - // // self - // // .editors - // // .get() - // // .get(self.convert_selected_tab_to_index()) - // // .unwrap() - // // .borrow() - // // .as_ref() - // // .unwrap() - // // .get_model() - // // .unwrap() - // // .get_value() - // // } + pub fn select_active_editor_value(&self) -> String { + self + .editors + .get() + .get(self.convert_selected_tab_to_index()) + .unwrap() + .borrow() + .as_ref() + .unwrap() + .get_model() + .unwrap() + .get_value() + } pub fn set_editor_value(&self, value: &str) { self From bdd9204ca903b7bec88e04898a6a7c4ea1914d4b Mon Sep 17 00:00:00 2001 From: Daniel Boros Date: Sun, 19 May 2024 15:24:33 +0200 Subject: [PATCH 23/28] refactor: tabs handling --- src-tauri/src/dbs/project.rs | 8 +++++--- src/dashboard/query_editor.rs | 5 +---- src/databases/pgsql/driver.rs | 14 +++++++++++++- src/store/projects.rs | 7 ++++--- src/store/tabs.rs | 2 +- 5 files changed, 24 insertions(+), 12 deletions(-) diff --git a/src-tauri/src/dbs/project.rs b/src-tauri/src/dbs/project.rs index 8110910..8eab5c3 100644 --- a/src-tauri/src/dbs/project.rs +++ b/src-tauri/src/dbs/project.rs @@ -1,13 +1,14 @@ -use ahash::AHashMap; +use std::collections::BTreeMap; + use tauri::{Result, State}; use crate::AppState; #[tauri::command(rename_all = "snake_case")] -pub async fn project_db_select(app_state: State<'_, AppState>) -> Result> { +pub async fn project_db_select(app_state: State<'_, AppState>) -> Result> { let project_db = app_state.project_db.lock().await; let db = project_db.clone().unwrap(); - let mut projects = AHashMap::new(); + let mut projects = BTreeMap::new(); if db.is_empty() { tracing::info!("No projects found in the database"); @@ -45,3 +46,4 @@ pub async fn project_db_delete(project_id: &str, app_state: State<'_, AppState>) db.remove(project_id).unwrap(); Ok(()) } + diff --git a/src/dashboard/query_editor.rs b/src/dashboard/query_editor.rs index 668758a..26428b2 100644 --- a/src/dashboard/query_editor.rs +++ b/src/dashboard/query_editor.rs @@ -89,10 +89,7 @@ pub fn QueryEditor(index: usize) -> impl IntoView {
//
-
} - > +
}>
{active_project}
diff --git a/src/databases/pgsql/driver.rs b/src/databases/pgsql/driver.rs index d69af98..d118d04 100644 --- a/src/databases/pgsql/driver.rs +++ b/src/databases/pgsql/driver.rs @@ -3,7 +3,9 @@ use common::{ enums::ProjectConnectionStatus, types::pgsql::{PgsqlLoadSchemas, PgsqlLoadTables, PgsqlRunQuery}, }; -use leptos::{error::Result, expect_context, RwSignal, SignalGet, SignalSet, SignalUpdate}; +use leptos::{ + error::Result, expect_context, logging::log, RwSignal, SignalGet, SignalSet, SignalUpdate, +}; use rsql::set_running_query; use tauri_sys::tauri::invoke; @@ -98,6 +100,16 @@ impl<'a> Pgsql<'a> { #[set_running_query] pub async fn run_default_table_query(&self, sql: &str) { let tabs_store = expect_context::(); + + let selected_projects = tabs_store.selected_projects.get(); + let project_id = selected_projects.get(tabs_store.convert_selected_tab_to_index()); + + if !selected_projects.is_empty() + && project_id.is_some_and(|id| id.as_str() != &self.project_id.get()) + { + tabs_store.add_tab(&self.project_id.get()); + } + tabs_store.set_editor_value(sql); tabs_store.selected_projects.update(|prev| { let index = tabs_store.convert_selected_tab_to_index(); diff --git a/src/store/projects.rs b/src/store/projects.rs index 029510f..2f00a0e 100644 --- a/src/store/projects.rs +++ b/src/store/projects.rs @@ -1,11 +1,12 @@ -use ahash::AHashMap; +use std::collections::BTreeMap; + use leptos::{RwSignal, SignalGet, SignalSet}; use tauri_sys::tauri::invoke; use crate::invoke::{Invoke, InvokeProjectDbDeleteArgs, InvokeProjectDbInsertArgs}; #[derive(Clone, Copy, Debug)] -pub struct ProjectsStore(pub RwSignal>); +pub struct ProjectsStore(pub RwSignal>); impl Default for ProjectsStore { fn default() -> Self { @@ -24,7 +25,7 @@ impl ProjectsStore { } pub async fn load_projects(&self) { - let projects = invoke::<_, AHashMap>(Invoke::ProjectDbSelect.as_ref(), &()) + let projects = invoke::<_, BTreeMap>(Invoke::ProjectDbSelect.as_ref(), &()) .await .unwrap(); self.0.set(projects); diff --git a/src/store/tabs.rs b/src/store/tabs.rs index c6559c8..6bc93f6 100644 --- a/src/store/tabs.rs +++ b/src/store/tabs.rs @@ -54,7 +54,7 @@ impl TabsStore { active_tabs: create_rw_signal(1), editors: create_rw_signal(Vec::new()), sql_results: create_rw_signal(Vec::new()), - selected_projects: create_rw_signal(vec![String::new()]), + selected_projects: create_rw_signal(Vec::new()), } } From 883f2a6ed8b984e785d8f74f552a1fdba174ea54 Mon Sep 17 00:00:00 2001 From: Daniel Boros Date: Sun, 19 May 2024 15:48:48 +0200 Subject: [PATCH 24/28] refactor: custom query loading --- src/dashboard/query_editor.rs | 11 ++++------ src/databases/pgsql/driver.rs | 15 +++---------- src/databases/pgsql/index.rs | 14 +++++++----- src/store/atoms.rs | 18 +++++++++++++-- src/store/tabs.rs | 41 ++++++++++++++++------------------- 5 files changed, 50 insertions(+), 49 deletions(-) diff --git a/src/dashboard/query_editor.rs b/src/dashboard/query_editor.rs index 26428b2..bd7a9ee 100644 --- a/src/dashboard/query_editor.rs +++ b/src/dashboard/query_editor.rs @@ -65,13 +65,10 @@ pub fn QueryEditor(index: usize) -> impl IntoView { }; let tabs_store_arc = Arc::new(Mutex::new(tabs_store)); - let run_query = create_action({ - let tabs_store = tabs_store_arc.clone(); - move |tabs_store: &Arc>| { - let tabs_store = tabs_store.clone(); - async move { - //tabs_store.lock().await.run_query().await.unwrap(); - } + let run_query = create_action(move |tabs_store: &Arc>| { + let tabs_store = tabs_store.clone(); + async move { + tabs_store.lock().await.run_query().await.unwrap(); } }); diff --git a/src/databases/pgsql/driver.rs b/src/databases/pgsql/driver.rs index d118d04..d2ce595 100644 --- a/src/databases/pgsql/driver.rs +++ b/src/databases/pgsql/driver.rs @@ -3,9 +3,7 @@ use common::{ enums::ProjectConnectionStatus, types::pgsql::{PgsqlLoadSchemas, PgsqlLoadTables, PgsqlRunQuery}, }; -use leptos::{ - error::Result, expect_context, logging::log, RwSignal, SignalGet, SignalSet, SignalUpdate, -}; +use leptos::{error::Result, expect_context, RwSignal, SignalGet, SignalSet, SignalUpdate}; use rsql::set_running_query; use tauri_sys::tauri::invoke; @@ -138,15 +136,8 @@ impl<'a> Pgsql<'a> { }); let qp_store = expect_context::(); qp_store.update(|prev| { - prev.push_front(QueryPerformanceAtom { - id: prev.len(), - message: Some(format!( - "[{}]: {} is executed in {} ms", - chrono::Utc::now(), - sql, - query_time - )), - }) + let new = QueryPerformanceAtom::new(prev.len(), sql, query_time); + prev.push_front(new); }); } diff --git a/src/databases/pgsql/index.rs b/src/databases/pgsql/index.rs index 297142f..1411146 100644 --- a/src/databases/pgsql/index.rs +++ b/src/databases/pgsql/index.rs @@ -68,6 +68,12 @@ pub fn Pgsql(project_id: String) -> impl IntoView { } }, ); + let connect = move || { + if pgsql.status.get() == ProjectConnectionStatus::Connected { + return; + } + connect.dispatch(pgsql); + }; view! { @@ -76,12 +82,7 @@ pub fn Pgsql(project_id: String) -> impl IntoView { - + - + "Query" + + + } diff --git a/src/databases/pgsql/driver.rs b/src/databases/pgsql/driver.rs index d2ce595..624806e 100644 --- a/src/databases/pgsql/driver.rs +++ b/src/databases/pgsql/driver.rs @@ -53,7 +53,7 @@ impl<'a> Pgsql<'a> { Invoke::PgsqlConnector.as_ref(), &InvokePgsqlConnectorArgs { project_id: &self.project_id.get(), - key: connection_string.as_str(), + key: Some(&connection_string), }, ) .await diff --git a/src/databases/pgsql/index.rs b/src/databases/pgsql/index.rs index 1411146..d466fba 100644 --- a/src/databases/pgsql/index.rs +++ b/src/databases/pgsql/index.rs @@ -1,3 +1,5 @@ +use std::rc::Rc; + use leptos::*; use leptos_icons::*; use leptos_toaster::{Toast, ToastId, ToastVariant, Toasts}; @@ -8,6 +10,7 @@ use common::enums::ProjectConnectionStatus; #[component] pub fn Pgsql(project_id: String) -> impl IntoView { + let project_id = Rc::new(project_id); let tabs_store = expect_context::(); let projects_store = expect_context::(); let project_details = projects_store.select_project_by_name(&project_id).unwrap(); @@ -25,7 +28,7 @@ pub fn Pgsql(project_id: String) -> impl IntoView { .collect::>(); let connection_params = Box::leak(connection_params.into_boxed_slice()); // [user, password, host, port] - let mut pgsql = Pgsql::new(project_id.clone()); + let mut pgsql = Pgsql::new(project_id.clone().to_string()); { pgsql.load_connection_details( &connection_params[0], @@ -133,7 +136,8 @@ pub fn Pgsql(project_id: String) -> impl IntoView { on:click={ let project_id = project_id.clone(); move |_| { - delete_project.dispatch((projects_store, project_id.clone())); + delete_project + .dispatch((projects_store, project_id.clone().to_string())); } } > diff --git a/src/databases/pgsql/schema.rs b/src/databases/pgsql/schema.rs index bb3313b..75c5ccd 100644 --- a/src/databases/pgsql/schema.rs +++ b/src/databases/pgsql/schema.rs @@ -28,8 +28,8 @@ pub fn Schema(schema: String) -> impl IntoView { let schema = schema.clone(); move |_| { set_is_loading(true); - let schema = (*schema).clone(); - load_tables.dispatch(schema.clone()); + let schema = schema.clone(); + load_tables.dispatch(schema.clone().to_string()); } } > @@ -43,7 +43,7 @@ pub fn Schema(schema: String) -> impl IntoView { /> - {(*schema).clone()} + {&*schema}
@@ -51,7 +51,7 @@ pub fn Schema(schema: String) -> impl IntoView { each={ let schema = schema.clone(); move || { - let schema = (*schema).clone(); + let schema = schema.clone(); pgsql.select_tables_by_schema(&schema).unwrap() } } diff --git a/src/databases/pgsql/table.rs b/src/databases/pgsql/table.rs index 4b19b9d..81d661b 100644 --- a/src/databases/pgsql/table.rs +++ b/src/databases/pgsql/table.rs @@ -1,3 +1,5 @@ +use std::rc::Rc; + use leptos::*; use leptos_icons::*; @@ -5,6 +7,8 @@ use crate::databases::pgsql::driver::Pgsql; #[component] pub fn Table(table: (String, String), schema: String) -> impl IntoView { + let table = Rc::new(table); + let schema = Rc::new(schema); let pgsql = expect_context::(); let query = create_action(move |(schema, table, pgsql): &(String, String, Pgsql)| { let pgsql = *pgsql; @@ -23,15 +27,15 @@ pub fn Table(table: (String, String), schema: String) -> impl IntoView { class="flex flex-row justify-between items-center hover:font-semibold cursor-pointer" on:click={ let table = table.clone(); - move |_| { query.dispatch((schema.clone(), table.0.clone(), pgsql)) } + move |_| { query.dispatch((schema.clone().to_string(), table.0.clone(), pgsql)) } } >
-

{table.0}

+

{table.0.to_string()}

-

{table.1}

+

{table.1.to_string()}

} } diff --git a/src/invoke.rs b/src/invoke.rs index 0d2c082..ed80513 100644 --- a/src/invoke.rs +++ b/src/invoke.rs @@ -62,7 +62,7 @@ impl AsRef for Invoke { #[derive(Serialize, Deserialize)] pub struct InvokePgsqlConnectorArgs<'a> { pub project_id: &'a str, - pub key: &'a str, + pub key: Option<&'a str>, } #[derive(Serialize, Deserialize)] diff --git a/src/modals/add_custom_query.rs b/src/modals/add_custom_query.rs index 3def322..b159147 100644 --- a/src/modals/add_custom_query.rs +++ b/src/modals/add_custom_query.rs @@ -1,29 +1,23 @@ +use std::rc::Rc; + use leptos::*; use thaw::{Modal, ModalFooter}; -use crate::store::{projects::ProjectsStore, queries::QueriesStore}; +use crate::store::queries::QueriesStore; #[component] -pub fn AddCustomQuery(show: RwSignal) -> impl IntoView { - let projects_store = expect_context::(); +pub fn AddCustomQuery(show: RwSignal, project_id: String) -> impl IntoView { + let project_id = Rc::new(project_id); + let project_id_clone = project_id.clone(); let query_store = expect_context::(); - let (query_title, set_query_title) = create_signal(String::new()); - //let projects = create_memo(move |_| projects_store.get_projects().unwrap()); - - let (project_name, set_project_name) = create_signal("".to_string()); - // create_effect(move |_| { - // if !projects.get().is_empty() { - // set_project_name(projects.get()[0].clone()); - // } - // }); - + let (title, set_title) = create_signal(String::new()); let insert_query = create_action( - move |(query_db, key, project_name): &(QueriesStore, String, String)| { + move |(query_db, project_id, title): &(QueriesStore, String, String)| { let query_db_clone = *query_db; - let key = key.clone(); - let project_name = project_name.clone(); + let project_id = project_id.clone(); + let title = title.clone(); async move { - query_db_clone.insert_query(&key, &project_name).await; + query_db_clone.insert_query(&project_id, &title).await; } }, ); @@ -31,30 +25,13 @@ pub fn AddCustomQuery(show: RwSignal) -> impl IntoView { view! {
- +

Project: {&*project_id_clone}

@@ -62,9 +39,12 @@ pub fn AddCustomQuery(show: RwSignal) -> impl IntoView {
-
-

Saved Queries

-
- +
}> +
+

Saved Queries

+
+ +
-
+ } } diff --git a/src/sidebar/queries.rs b/src/sidebar/queries.rs index d6f5bb0..b06d070 100644 --- a/src/sidebar/queries.rs +++ b/src/sidebar/queries.rs @@ -16,8 +16,8 @@ pub fn Queries() -> impl IntoView { view! { } + key=|(query_id, _)| query_id.clone() + children=move |(query_id, sql)| view! { } /> } } diff --git a/src/sidebar/query.rs b/src/sidebar/query.rs index 6a06255..905df33 100644 --- a/src/sidebar/query.rs +++ b/src/sidebar/query.rs @@ -1,53 +1,53 @@ +use std::sync::Arc; + use leptos::*; use leptos_icons::*; use crate::store::{queries::QueriesStore, tabs::TabsStore}; #[component] -pub fn Query(key: String) -> impl IntoView { +pub fn Query(query_id: String, sql: String) -> impl IntoView { + let query_id = Arc::new(query_id); + let sql = Arc::new(sql); let query_store = expect_context::(); let tabs_store = expect_context::(); - let key_clone = key.clone(); - let splitted_key = create_memo(move |_| { - let key = key_clone.clone(); - - key - .split(':') - .map(|s| s.to_string()) - .collect::>() - }); view! {
} diff --git a/src/store/tabs.rs b/src/store/tabs.rs index d276064..a7b5b87 100644 --- a/src/store/tabs.rs +++ b/src/store/tabs.rs @@ -1,15 +1,14 @@ use std::{cell::RefCell, rc::Rc}; -use leptos::{ - create_rw_signal, error::Result, expect_context, RwSignal, SignalGet, SignalSet, SignalUpdate, -}; +use common::enums::ProjectConnectionStatus; +use leptos::{create_rw_signal, expect_context, RwSignal, SignalGet, SignalSet, SignalUpdate}; use monaco::api::CodeEditor; use rsql::set_running_query; use tauri_sys::tauri::invoke; use crate::{ dashboard::query_editor::ModelCell, - invoke::{Invoke, InvokePgsqlRunQueryArgs}, + invoke::{Invoke, InvokePgsqlConnectorArgs, InvokePgsqlRunQueryArgs}, }; use super::atoms::{QueryPerformanceAtom, QueryPerformanceContext, RunQueryAtom, RunQueryContext}; @@ -52,7 +51,7 @@ impl TabsStore { } #[set_running_query] - pub async fn run_query(&self) -> Result<()> { + pub async fn run_query(&self) { let project_ids = self.selected_projects.get(); let project_id = project_ids .get(self.convert_selected_tab_to_index()) @@ -76,7 +75,8 @@ impl TabsStore { sql: &sql.query, }, ) - .await?; + .await + .unwrap(); self.sql_results.update(|prev| { let index = self.convert_selected_tab_to_index(); match prev.get_mut(index) { @@ -89,17 +89,35 @@ impl TabsStore { let new = QueryPerformanceAtom::new(prev.len(), &sql.query, query_time); prev.push_front(new); }); - Ok(()) } - pub fn load_query(&self, key: &str) -> Result<()> { - // let splitted_key = key.split(':').collect::>(); - // active_project.0.set(Some(splitted_key[0].to_string())); - // let query_store = use_context::().unwrap(); - // let query_store = query_store.0.get(); - // let query = query_store.get(key).unwrap(); - //self.set_editor_value(query); - Ok(()) + // TODO: Need to be more generic if we want to support other databases + pub async fn load_query(&self, query_id: &str, sql: &str) { + let splitted_key = query_id.split(':').collect::>(); + let selected_projects = self.selected_projects.get(); + let project_id = selected_projects.get(self.convert_selected_tab_to_index()); + if !self.selected_projects.get().is_empty() + && project_id.is_some_and(|id| id.as_str() != splitted_key[0]) + { + self.add_tab(&splitted_key[0]); + } + self.set_editor_value(sql); + self.selected_projects.update(|prev| { + let index = self.convert_selected_tab_to_index(); + match prev.get_mut(index) { + Some(project) => *project = splitted_key[0].to_string(), + None => prev.push(splitted_key[0].to_string()), + } + }); + let _ = invoke::<_, ProjectConnectionStatus>( + Invoke::PgsqlConnector.as_ref(), + &InvokePgsqlConnectorArgs { + project_id: splitted_key[0], + key: None, + }, + ) + .await; + self.run_query().await; } pub fn add_editor(&mut self, editor: Rc>>) { From 510921db96c4bc218dc31acf9f22f383d8e8cf8e Mon Sep 17 00:00:00 2001 From: Daniel Boros Date: Sun, 19 May 2024 18:06:05 +0200 Subject: [PATCH 27/28] chore: bump version --- Cargo.toml | 2 +- common/Cargo.toml | 2 +- src-tauri/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 7fd0843..4a40f20 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rsql" -version = "1.0.0-alpha.9" +version = "1.0.0-beta.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/common/Cargo.toml b/common/Cargo.toml index aad3e28..22b4532 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "common" -version = "1.0.0-alpha.9" +version = "1.0.0-beta.0" edition = "2021" [dependencies] diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index ba8be33..a570c3b 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rsql_tauri" -version = "1.0.0-alpha.9" +version = "1.0.0-beta.0" description = "PostgreSQL GUI written in Rust" authors = ["Daniel Boros"] license = "" From e5a83348e36d36dde6b9c1317b5e9e833c5b735f Mon Sep 17 00:00:00 2001 From: Daniel Boros Date: Sun, 19 May 2024 18:19:53 +0200 Subject: [PATCH 28/28] feat: make queries more general --- src/dashboard/query_editor.rs | 13 +++++++++++-- src/modals/add_custom_query.rs | 14 ++++++++++---- src/store/projects.rs | 16 ++++++++++++++++ src/store/queries.rs | 5 +++-- 4 files changed, 40 insertions(+), 8 deletions(-) diff --git a/src/dashboard/query_editor.rs b/src/dashboard/query_editor.rs index 206866c..59edf6a 100644 --- a/src/dashboard/query_editor.rs +++ b/src/dashboard/query_editor.rs @@ -12,7 +12,10 @@ use monaco::{ }; use wasm_bindgen::{closure::Closure, JsCast}; -use crate::{modals::add_custom_query::AddCustomQuery, store::tabs::TabsStore}; +use crate::{ + modals::add_custom_query::AddCustomQuery, + store::{projects::ProjectsStore, tabs::TabsStore}, +}; pub type ModelCell = Rc>>; pub const MODE_ID: &str = "pgsql"; @@ -24,6 +27,8 @@ pub fn QueryEditor(index: usize) -> impl IntoView { Some(project) => Some(project.clone()), _ => None, }; + let projects_store = expect_context::(); + let project_driver = projects_store.select_driver_by_project(active_project().as_deref()); let tabs_store_rc = Rc::new(RefCell::new(tabs_store)); let show = create_rw_signal(false); let _ = use_event_listener(use_document(), ev::keydown, move |event| { @@ -86,7 +91,11 @@ pub fn QueryEditor(index: usize) -> impl IntoView {
}> - +
{active_project}
diff --git a/src/modals/add_custom_query.rs b/src/modals/add_custom_query.rs index b159147..9f9c52e 100644 --- a/src/modals/add_custom_query.rs +++ b/src/modals/add_custom_query.rs @@ -1,23 +1,27 @@ use std::rc::Rc; +use common::enums::Drivers; use leptos::*; use thaw::{Modal, ModalFooter}; use crate::store::queries::QueriesStore; #[component] -pub fn AddCustomQuery(show: RwSignal, project_id: String) -> impl IntoView { +pub fn AddCustomQuery(show: RwSignal, project_id: String, driver: Drivers) -> impl IntoView { let project_id = Rc::new(project_id); let project_id_clone = project_id.clone(); let query_store = expect_context::(); let (title, set_title) = create_signal(String::new()); let insert_query = create_action( - move |(query_db, project_id, title): &(QueriesStore, String, String)| { + move |(query_db, project_id, title, driver): &(QueriesStore, String, String, Drivers)| { let query_db_clone = *query_db; let project_id = project_id.clone(); let title = title.clone(); + let driver = driver.clone(); async move { - query_db_clone.insert_query(&project_id, &title).await; + query_db_clone + .insert_query(&project_id, &title, &driver) + .await; } }, ); @@ -41,8 +45,10 @@ pub fn AddCustomQuery(show: RwSignal, project_id: String) -> impl IntoView class="px-4 py-2 border-1 border-neutral-200 hover:bg-neutral-200 rounded-md" on:click={ let project_id = project_id.clone(); + let driver = driver.clone(); move |_| { - insert_query.dispatch((query_store, project_id.to_string(), title())); + insert_query + .dispatch((query_store, project_id.to_string(), title(), driver)); show.set(false); } } diff --git a/src/store/projects.rs b/src/store/projects.rs index 2f00a0e..486090a 100644 --- a/src/store/projects.rs +++ b/src/store/projects.rs @@ -1,5 +1,6 @@ use std::collections::BTreeMap; +use common::enums::Drivers; use leptos::{RwSignal, SignalGet, SignalSet}; use tauri_sys::tauri::invoke; @@ -24,6 +25,21 @@ impl ProjectsStore { self.0.get().get(project_id).cloned() } + pub fn select_driver_by_project(&self, project_id: Option<&str>) -> Drivers { + if project_id.is_none() { + return Drivers::PGSQL; + } + + let project = self.select_project_by_name(project_id.unwrap()).unwrap(); + let driver = project.split(':').next().unwrap(); + let driver = driver.split('=').last(); + + match driver { + Some("PGSQL") => Drivers::PGSQL, + _ => unreachable!(), + } + } + pub async fn load_projects(&self) { let projects = invoke::<_, BTreeMap>(Invoke::ProjectDbSelect.as_ref(), &()) .await diff --git a/src/store/queries.rs b/src/store/queries.rs index 0dee59f..5a5229a 100644 --- a/src/store/queries.rs +++ b/src/store/queries.rs @@ -1,5 +1,6 @@ use std::collections::BTreeMap; +use common::enums::Drivers; use leptos::*; use tauri_sys::tauri::invoke; @@ -31,13 +32,13 @@ impl QueriesStore { }); } - pub async fn insert_query(&self, project_id: &str, title: &str) { + pub async fn insert_query(&self, project_id: &str, title: &str, driver: &Drivers) { let tabs_store = expect_context::(); let sql = tabs_store.select_active_editor_value(); let _ = invoke::<_, ()>( Invoke::QueryDbInsert.as_ref(), &InvokeQueryDbInsertArgs { - query_id: &format!("{}:{}", project_id, title), + query_id: &format!("{}:{}:{}", project_id, driver, title), sql: &sql, }, )