diff --git a/.github/workflows/rust-tui-ci.yml b/.github/workflows/rust-tui-ci.yml index de6e3a857..d684ac1f7 100644 --- a/.github/workflows/rust-tui-ci.yml +++ b/.github/workflows/rust-tui-ci.yml @@ -22,6 +22,7 @@ jobs: - name: Install Rust uses: actions-rs/toolchain@v1 with: + toolchain: 1.91.1 override: true components: rustfmt, clippy @@ -54,6 +55,7 @@ jobs: - name: Install Rust uses: actions-rs/toolchain@v1 with: + toolchain: 1.91.1 override: true components: rustfmt, clippy @@ -78,6 +80,7 @@ jobs: - name: Install Rust uses: actions-rs/toolchain@v1 with: + toolchain: 1.91.1 override: true components: rustfmt, clippy @@ -98,6 +101,7 @@ jobs: - name: Install Rust uses: actions-rs/toolchain@v1 with: + toolchain: 1.91.1 override: true components: rustfmt, clippy diff --git a/rust-tui/Cargo.lock b/rust-tui/Cargo.lock index f5a306bae..e699949d3 100644 --- a/rust-tui/Cargo.lock +++ b/rust-tui/Cargo.lock @@ -2,6 +2,12 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + [[package]] name = "ahash" version = "0.8.12" @@ -179,9 +185,9 @@ dependencies = [ [[package]] name = "bytes" -version = "1.11.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" [[package]] name = "cassowary" @@ -304,6 +310,15 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + [[package]] name = "crossbeam-utils" version = "0.8.21" @@ -459,7 +474,6 @@ dependencies = [ "dotenvy", "futures", "futures-concurrency", - "hashbrown 0.15.5", "ratatui", "serde", "serde_json", @@ -472,9 +486,9 @@ dependencies = [ [[package]] name = "dittolive-ditto" -version = "4.13.1" +version = "5.0.0-rc.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf555e9ddb7eb974ed842bd27cfc15f5448b3cf028b1b521745c693acffe517" +checksum = "f05920003c976873c4ab478ea4024f47d1009a62dedd624d84859d3e4fd55497" dependencies = [ "async-trait", "async_fn_traits", @@ -491,7 +505,7 @@ dependencies = [ "hashbrown 0.14.5", "macro_rules_attribute", "never-say-never", - "rand", + "rand 0.9.2", "safer-ffi", "serde", "serde-transcode", @@ -509,14 +523,17 @@ dependencies = [ [[package]] name = "dittolive-ditto-sys" -version = "4.13.1" +version = "5.0.0-rc.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0626a0f119eaf498041b86f2565742d25b8ff1bba8bb3240723a963edc4ed8e" +checksum = "7c01bc699809a837bdbe6b620ce000ad270692b1e71fc1de921ff40b94a48068" dependencies = [ "macro_rules_attribute", + "object", "paste", "safer-ffi", + "thiserror", "tokio", + "toml", ] [[package]] @@ -606,6 +623,16 @@ version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" +[[package]] +name = "flate2" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + [[package]] name = "fnv" version = "1.0.7" @@ -1151,6 +1178,16 @@ version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + [[package]] name = "mio" version = "1.1.0" @@ -1180,9 +1217,9 @@ dependencies = [ [[package]] name = "num-conv" -version = "0.1.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" [[package]] name = "num-traits" @@ -1193,6 +1230,17 @@ dependencies = [ "autocfg", ] +[[package]] +name = "object" +version = "0.37.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" +dependencies = [ + "flate2", + "memchr", + "ruzstd", +] + [[package]] name = "once_cell" version = "1.21.3" @@ -1352,8 +1400,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha", - "rand_core", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.5", ] [[package]] @@ -1363,7 +1421,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.5", ] [[package]] @@ -1375,6 +1443,15 @@ dependencies = [ "getrandom 0.2.16", ] +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] + [[package]] name = "ratatui" version = "0.29.0" @@ -1470,6 +1547,15 @@ version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" +[[package]] +name = "ruzstd" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5ff0cc5e135c8870a775d3320910cd9b564ec036b4dc0b8741629020be63f01" +dependencies = [ + "twox-hash", +] + [[package]] name = "ryu" version = "1.0.20" @@ -1646,6 +1732,15 @@ dependencies = [ "serde_core", ] +[[package]] +name = "serde_spanned" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776" +dependencies = [ + "serde_core", +] + [[package]] name = "serde_with" version = "3.16.1" @@ -1728,6 +1823,12 @@ dependencies = [ "libc", ] +[[package]] +name = "simd-adler32" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" + [[package]] name = "slab" version = "0.4.11" @@ -1787,7 +1888,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "rand", + "rand 0.8.5", "syn 1.0.109", ] @@ -1895,30 +1996,30 @@ dependencies = [ [[package]] name = "time" -version = "0.3.44" +version = "0.3.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" dependencies = [ "deranged", "itoa", "num-conv", "powerfmt", - "serde", + "serde_core", "time-core", "time-macros", ] [[package]] name = "time-core" -version = "0.1.6" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" [[package]] name = "time-macros" -version = "0.2.24" +version = "0.2.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" dependencies = [ "num-conv", "time-core", @@ -1993,11 +2094,26 @@ dependencies = [ "tokio", ] +[[package]] +name = "toml" +version = "0.9.12+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" +dependencies = [ + "indexmap 2.12.1", + "serde_core", + "serde_spanned", + "toml_datetime", + "toml_parser", + "toml_writer", + "winnow", +] + [[package]] name = "toml_datetime" -version = "0.7.3" +version = "0.7.5+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" dependencies = [ "serde_core", ] @@ -2016,13 +2132,19 @@ dependencies = [ [[package]] name = "toml_parser" -version = "1.0.4" +version = "1.0.9+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e" +checksum = "702d4415e08923e7e1ef96cd5727c0dfed80b4d2fa25db9647fe5eb6f7c5a4c4" dependencies = [ "winnow", ] +[[package]] +name = "toml_writer" +version = "1.0.6+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607" + [[package]] name = "tracing" version = "0.1.43" @@ -2084,6 +2206,12 @@ dependencies = [ "tracing-log", ] +[[package]] +name = "twox-hash" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ea3136b675547379c4bd395ca6b938e5ad3c3d20fad76e7fe85f9e0d011419c" + [[package]] name = "unicode-ident" version = "1.0.22" diff --git a/rust-tui/Cargo.toml b/rust-tui/Cargo.toml index 2836f5b25..7208c9b48 100644 --- a/rust-tui/Cargo.toml +++ b/rust-tui/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "ditto-quickstart" version = "0.0.0" -edition = "2021" +edition = "2024" default-run = "ditto-quickstart" [lib] @@ -17,8 +17,8 @@ name = "integration_test" path = "src/bin/integration_test.rs" [dependencies] -# Ditto dependenceis -dittolive-ditto = "4.13.1" +# Ditto dependencies +dittolive-ditto = "=5.0.0-rc.2" # External dependencies anyhow = "1" @@ -28,7 +28,6 @@ crossterm = { version = "0.28", features = ["event-stream"] } dotenvy = "0.15.7" futures = "0.3.31" futures-concurrency = "7.6" -hashbrown = "0.15.2" ratatui = { version = "0.29", features = ["crossterm"] } serde = { version = "1.0.217", features = ["derive"] } serde_json = "1.0.138" diff --git a/rust-tui/README.md b/rust-tui/README.md index 250b586fd..3e8376a53 100644 --- a/rust-tui/README.md +++ b/rust-tui/README.md @@ -1,19 +1,15 @@ # Ditto Rust Quickstart App 🚀 -This directory contains Ditto's quickstart app for the Rust SDK. -This app is a Terminal User Interface (TUI) that allows for creating -a todo list that syncs between multiple peers. +This directory contains Ditto's quickstart app for the Rust SDK. This app is a Terminal User Interface (TUI) that allows for creating a todo list that syncs between multiple peers. ## Getting Started -To get started, you'll first need to create an app in the [Ditto Portal][0] -with the "Online Playground" authentication type. You'll need to find your +To get started, you'll first need to create an app in the [Ditto Portal][0] with the "Online Playground" authentication type. You'll need to find your AppID and Online Playground Token, Auth URL, and Websocket URL in order to use this quickstart. [0]: https://portal.ditto.live -From the repo root, copy the `.env.sample` file to `.env`, and fill in the -fields with your AppID, Online Playground Token, Auth URL, and Websocket URL: +From the repo root, copy the `.env.sample` file to `.env`, and fill in the fields with your AppID, Online Playground Token, Auth URL, and Websocket URL: ``` cp .sample.env .env diff --git a/rust-tui/src/bin/integration_test.rs b/rust-tui/src/bin/integration_test.rs index 977b875df..f8d4537cf 100644 --- a/rust-tui/src/bin/integration_test.rs +++ b/rust-tui/src/bin/integration_test.rs @@ -1,6 +1,7 @@ use anyhow::{Context, Result}; use ditto_quickstart::tui::Todolist; -use dittolive_ditto::{fs::TempRoot, identity::OnlinePlayground, AppId, Ditto}; +use dittolive_ditto::prelude::*; +use dittolive_ditto::{Ditto, fs::TempRoot}; use std::time::Duration; use std::{env, sync::Arc}; use tokio::time::sleep; @@ -12,14 +13,12 @@ async fn main() -> Result<()> { // Load environment variables dotenvy::dotenv().ok(); - let app_id: AppId = env::var("DITTO_APP_ID") - .context("DITTO_APP_ID not found")? - .parse() - .context("Invalid DITTO_APP_ID format")?; - let playground_token = - env::var("DITTO_PLAYGROUND_TOKEN").context("DITTO_PLAYGROUND_TOKEN not found")?; + let database_id = env::var("DITTO_APP_ID").context("DITTO_APP_ID not found")?; + + let token = env::var("DITTO_PLAYGROUND_TOKEN").context("DITTO_PLAYGROUND_TOKEN not found")?; let custom_auth_url = env::var("DITTO_AUTH_URL").unwrap_or_else(|_| "https://auth.cloud.ditto.live".to_string()); + let websocket_url = env::var("DITTO_WEBSOCKET_URL").unwrap_or_else(|_| "wss://cloud.ditto.live".to_string()); @@ -29,34 +28,31 @@ async fn main() -> Result<()> { println!("🔍 Looking for task: {}", task_to_find); + let connect_config = DittoConfigConnect::Server { + url: custom_auth_url + .parse() + .context("failed to parse custom auth URL")?, + }; + + let config = DittoConfig::new(database_id, connect_config) + .with_persistence_directory(Arc::new(TempRoot::new()).root_path()); + // Create Ditto instance (using same pattern as main.rs) - let ditto = Ditto::builder() - .with_root(Arc::new(TempRoot::new())) - .with_identity(|root| { - OnlinePlayground::new( - root, - app_id.clone(), - playground_token, - false, - Some(custom_auth_url.as_str()), - ) - })? - .build()?; + let ditto = Ditto::open_sync(config)?; + + ditto + .auth() + .context("failed to get authenticator")? + .set_expiration_handler(TokenHandler { + token: token.clone(), + }); ditto.update_transport_config(|config| { config.enable_all_peer_to_peer(); - config.connect.websocket_urls.insert(websocket_url.clone()); }); - // Disable sync with v3 peers and DQL strict mode - let _ = ditto.disable_sync_with_v3(); - let _ = ditto - .store() - .execute_v2("ALTER SYSTEM SET DQL_STRICT_MODE = false") - .await?; - // Start sync - let _ = ditto.start_sync(); + ditto.sync().start()?; println!("✅ Created Ditto instance and started sync"); // Create todolist instance (loads the app) @@ -100,9 +96,26 @@ async fn main() -> Result<()> { anyhow::bail!("Integration test failed - seeded task not found"); } - todolist.ditto.stop_sync(); + todolist.ditto.sync().stop(); println!("🛑 Stopped sync"); println!("🎉 Integration test passed! App loads and syncs with Ditto Cloud successfully."); Ok(()) } + +struct TokenHandler { + token: String, +} + +impl DittoAuthExpirationHandler for TokenHandler { + async fn on_expiration(&self, ditto: &Ditto, _duration_remaining: Duration) { + let Some(auth) = ditto.auth() else { + eprintln!("Failed to get authenticator during token refresh"); + return; + }; + match auth.login(self.token.as_str(), &identity::get_development_provider()) { + Ok(_) => println!("Authentication successful"), + Err(e) => eprintln!("Authentication failed: {e}"), + } + } +} diff --git a/rust-tui/src/bin/main.rs b/rust-tui/src/bin/main.rs index d75659af3..3f77bed7e 100644 --- a/rust-tui/src/bin/main.rs +++ b/rust-tui/src/bin/main.rs @@ -1,16 +1,17 @@ use std::{path::PathBuf, sync::Arc, time::Duration}; -use anyhow::{anyhow, Context, Result}; +use anyhow::{Context, Result, anyhow}; use clap::Parser; -use ditto_quickstart::{term, tui::TuiTask, Shutdown}; -use dittolive_ditto::{fs::TempRoot, identity::OnlinePlayground, AppId, Ditto}; +use ditto_quickstart::{Shutdown, term, tui::TuiTask}; +use dittolive_ditto::prelude::*; +use dittolive_ditto::{Ditto, fs::TempRoot}; use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; #[derive(Debug, Parser)] pub struct Cli { /// The Ditto App ID this app will use to initialize Ditto #[clap(long, env = "DITTO_APP_ID")] - app_id: AppId, + database_id: String, /// The Online Playground token this app should use for authentication #[clap(long, env = "DITTO_PLAYGROUND_TOKEN")] @@ -61,7 +62,7 @@ async fn main() -> Result<()> { // Initialize and launch app let ditto = try_init_ditto( - cli.app_id, + cli.database_id, cli.token, cli.custom_auth_url, cli.websocket_url.clone(), @@ -105,13 +106,36 @@ async fn main() -> Result<()> { Ok(()) } +struct TokenHandler { + token: String, +} + +impl DittoAuthExpirationHandler for TokenHandler { + async fn on_expiration(&self, ditto: &Ditto, _duration_remaining: Duration) { + let Some(auth) = ditto.auth() else { + tracing::error!("Failed to get authenticator during token refresh"); + return; + }; + match auth.login(self.token.as_str(), &identity::get_development_provider()) { + Ok(_) => tracing::info!("Authentication successful"), + Err(e) => tracing::error!(%e, "Authentication failed"), + } + } +} + async fn try_init_ditto( - app_id: AppId, + database_id: String, token: String, custom_auth_url: String, websocket_url: String, p2p_enabled: bool, ) -> Result { + let connect_config = DittoConfigConnect::Server { + url: custom_auth_url + .parse() + .context("failed to parse custom auth URL")?, + }; + // We use a temporary directory to store Ditto's local database. // This means that data will not be persistent between runs of the // application, but it allows us to run multiple instances of the @@ -119,18 +143,17 @@ async fn try_init_ditto( // application, we would want to store the database in a more permanent // location, and if multiple instances are needed, ensure that each // instance has its own persistence directory. - let ditto = Ditto::builder() - .with_root(Arc::new(TempRoot::new())) - .with_identity(|root| { - OnlinePlayground::new( - root, - app_id.clone(), - token, - false, // This is required to be set to false to use the correct URLs - Some(custom_auth_url.as_str()), - ) - })? - .build()?; + let config = DittoConfig::new(database_id.clone(), connect_config) + .with_persistence_directory(Arc::new(TempRoot::new()).root_path()); + + let ditto = Ditto::open_sync(config)?; + + ditto + .auth() + .context("failed to get authenticator")? + .set_expiration_handler(TokenHandler { + token: token.clone(), + }); ditto.update_transport_config(|config| { if p2p_enabled { @@ -146,20 +169,10 @@ async fn try_init_ditto( config.connect.websocket_urls.insert(websocket_url); }); - // disable sync with v3 peers, required for DQL - _ = ditto.disable_sync_with_v3(); - - // disable DQL strict mode - // https://docs.ditto.live/dql/strict-mode - _ = ditto - .store() - .execute_v2("ALTER SYSTEM SET DQL_STRICT_MODE = false") - .await?; - // Start sync - _ = ditto.start_sync(); + ditto.sync().start()?; - tracing::info!(%app_id, "Started Ditto!"); + tracing::info!(database_id = %database_id, "Started Ditto!"); Ok(ditto) } diff --git a/rust-tui/src/input.rs b/rust-tui/src/input.rs deleted file mode 100644 index 638b8e85c..000000000 --- a/rust-tui/src/input.rs +++ /dev/null @@ -1,6 +0,0 @@ -//! Input helpers -//! -//! Credit to [`hawkw`][0]. -//! [0]: https://github.com/tokio-rs/console/blob/cbf6f56a16036ecf13548c4209fcc62f8a84bae2/tokio-console/src/term.rs - -pub use crossterm::event::*; diff --git a/rust-tui/src/term.rs b/rust-tui/src/term.rs index e88a29134..9447279cb 100644 --- a/rust-tui/src/term.rs +++ b/rust-tui/src/term.rs @@ -5,7 +5,7 @@ //! [0]: https://github.com/tokio-rs/console/blob/cbf6f56a16036ecf13548c4209fcc62f8a84bae2/tokio-console/src/term.rs use anyhow::{Context, Result}; -pub use ratatui::{backend::CrosstermBackend, Terminal}; +pub use ratatui::{Terminal, backend::CrosstermBackend}; use std::io; pub fn init_crossterm() -> Result<(Terminal>, OnShutdown)> { diff --git a/rust-tui/src/tui/mod.rs b/rust-tui/src/tui/mod.rs index 60037326f..33aee338a 100644 --- a/rust-tui/src/tui/mod.rs +++ b/rust-tui/src/tui/mod.rs @@ -1,4 +1,4 @@ -use anyhow::{anyhow, Context, Result}; +use anyhow::{Context, Result, anyhow}; use crossterm::event::{Event, EventStream}; use dittolive_ditto::prelude::*; use futures::{FutureExt, Stream, StreamExt}; @@ -6,7 +6,7 @@ use ratatui::prelude::*; use std::{io::Stdout, ops::ControlFlow, time::Duration}; use tokio::task::JoinHandle; -use crate::{should_quit, Shutdown}; +use crate::{Shutdown, should_quit}; pub mod todolist; diff --git a/rust-tui/src/tui/todolist.rs b/rust-tui/src/tui/todolist.rs index ae4c4cee8..20043fa22 100644 --- a/rust-tui/src/tui/todolist.rs +++ b/rust-tui/src/tui/todolist.rs @@ -1,9 +1,9 @@ use anyhow::Context; use anyhow::Result; use crossterm::event::Event; +use dittolive_ditto::Ditto; use dittolive_ditto::store::StoreObserver; use dittolive_ditto::sync::SyncSubscription; -use dittolive_ditto::Ditto; use ratatui::prelude::*; use ratatui::widgets::Block; use ratatui::widgets::BorderType; @@ -49,15 +49,6 @@ pub struct Todolist { /// Table scrolling state pub table_state: TableState, - - /// Holds the contents of a "new todo" dialog - /// - /// When this is "None", the dialog is closed. When "Some", it contains - /// the title being typed by the user. - pub create_task_title: Option, - - /// Holds the contents of an existing TODO title to be edited - pub edit_task: Option<(String, String)>, // (ID, title) } /// Mode enum used to decide how to interpret keystrokes @@ -94,13 +85,10 @@ impl Todolist { // Register a subscription, which determines what data syncs to this peer // https://docs.ditto.live/sdk/latest/sync/syncing-data#creating-subscriptions - let tasks_subscription = ditto - .sync() - .register_subscription_v2("SELECT * FROM tasks")?; + let tasks_subscription = ditto.sync().register_subscription("SELECT * FROM tasks")?; - // register observer for live query // Register observer, which runs against the local database on this peer - let tasks_observer = ditto.store().register_observer_v2( + let tasks_observer = ditto.store().register_observer( "SELECT * FROM tasks WHERE deleted=false ORDER BY title ASC", move |query_result| { let docs = query_result @@ -120,15 +108,13 @@ impl Todolist { websocket_url, client_name, mode: TodoMode::Normal, - create_task_title: None, - edit_task: None, }) } /// Top-level render function for the Todolist pub fn render(&mut self, area: Rect, buf: &mut Buffer) { self.render_todo_table(area, buf); - self.render_new_todo_prompt(area, buf); + self.render_todo_prompt(area, buf); } /// Render a table displaying each todo and its current status @@ -155,7 +141,7 @@ impl Todolist { }) .collect::>(); - let sync_state = if self.ditto.is_sync_active() { + let sync_state = if self.ditto.sync().is_active() { " 🟢 Sync Active ".green() } else { " 🔴 Sync Inactive ".red() @@ -189,11 +175,11 @@ impl Todolist { StatefulWidget::render(table, area, buf, &mut self.table_state); } - /// Render "new todo" prompt if `create_task_title` is "Some" - fn render_new_todo_prompt(&self, area: Rect, buf: &mut Buffer) { - let title = match &self.mode { - TodoMode::CreateTask { buffer } => buffer, - TodoMode::EditTask { buffer, .. } => buffer, + /// Render create/edit prompt dialog when in CreateTask or EditTask mode + fn render_todo_prompt(&self, area: Rect, buf: &mut Buffer) { + let (dialog_title, input) = match &self.mode { + TodoMode::CreateTask { buffer } => (" New Todo ", buffer), + TodoMode::EditTask { buffer, .. } => (" Edit Todo ", buffer), _ => { return; } @@ -203,12 +189,12 @@ impl Todolist { Clear.render(space, buf); Block::bordered() .border_type(BorderType::Rounded) - .title(" New Todo ") + .title(dialog_title) .title_bottom(" (Esc: back) ") .padding(Padding::uniform(1)) .render(space, buf); let space = space.inner(Margin::new(2, 2)); - Line::raw(title).render(space, buf); + Line::raw(input).render(space, buf); } /// Apply a terminal event to update the todolist state @@ -305,10 +291,10 @@ impl Todolist { } fn toggle_sync(&mut self) -> Result<()> { - if self.ditto.is_sync_active() { - self.ditto.stop_sync(); + if self.ditto.sync().is_active() { + self.ditto.sync().stop(); } else { - self.ditto.start_sync()?; + self.ditto.sync().start()?; } Ok(()) } @@ -329,7 +315,7 @@ impl Todolist { let done = selected_task.done; self.ditto .store() - .execute_v2(( + .execute(( "UPDATE tasks SET done=:done WHERE _id=:id", serde_json::json!({ "id": id, @@ -356,7 +342,7 @@ impl Todolist { let id = selected_task.id; self.ditto .store() - .execute_v2(( + .execute(( "UPDATE tasks SET deleted=true WHERE _id=:id", serde_json::json!({ "id": id @@ -372,7 +358,7 @@ impl Todolist { let task = TodoItem::new(title); self.ditto .store() - .execute_v2(( + .execute(( "INSERT INTO tasks DOCUMENTS (:task)", serde_json::json!({ "task": task @@ -386,7 +372,7 @@ impl Todolist { pub async fn try_edit_todo(&mut self, id: &str, title: &str) -> Result<()> { self.ditto .store() - .execute_v2(( + .execute(( "UPDATE tasks SET title=:title WHERE _id=:id", serde_json::json!({ "title": title,