feat(card_db): Implement basic indexed-db based card storage
This commit is contained in:
parent
aca5be2a9e
commit
7e9f0ff128
120
Cargo.lock
generated
120
Cargo.lock
generated
|
@ -2,6 +2,21 @@
|
||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
version = 3
|
version = 3
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "addr2line"
|
||||||
|
version = "0.24.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1"
|
||||||
|
dependencies = [
|
||||||
|
"gimli",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "adler2"
|
||||||
|
version = "2.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aho-corasick"
|
name = "aho-corasick"
|
||||||
version = "1.1.3"
|
version = "1.1.3"
|
||||||
|
@ -86,6 +101,21 @@ version = "1.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
|
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "backtrace"
|
||||||
|
version = "0.3.74"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a"
|
||||||
|
dependencies = [
|
||||||
|
"addr2line",
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"miniz_oxide",
|
||||||
|
"object",
|
||||||
|
"rustc-demangle",
|
||||||
|
"windows-targets",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "base64"
|
name = "base64"
|
||||||
version = "0.22.1"
|
version = "0.22.1"
|
||||||
|
@ -265,8 +295,14 @@ version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"console_error_panic_hook",
|
"console_error_panic_hook",
|
||||||
"dedent",
|
"dedent",
|
||||||
|
"futures",
|
||||||
|
"idb",
|
||||||
"leptos",
|
"leptos",
|
||||||
|
"serde",
|
||||||
|
"serde-wasm-bindgen",
|
||||||
|
"serde_json",
|
||||||
"thiserror 2.0.12",
|
"thiserror 2.0.12",
|
||||||
|
"url",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -454,6 +490,12 @@ dependencies = [
|
||||||
"windows-targets",
|
"windows-targets",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "gimli"
|
||||||
|
version = "0.31.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gloo-net"
|
name = "gloo-net"
|
||||||
version = "0.6.0"
|
version = "0.6.0"
|
||||||
|
@ -664,6 +706,20 @@ dependencies = [
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "idb"
|
||||||
|
version = "0.6.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3afe8830d5802f769dc0be20a87f9f116798c896650cb6266eb5c19a3c109eed"
|
||||||
|
dependencies = [
|
||||||
|
"js-sys",
|
||||||
|
"num-traits",
|
||||||
|
"thiserror 1.0.69",
|
||||||
|
"tokio",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"web-sys",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "idna"
|
name = "idna"
|
||||||
version = "1.0.3"
|
version = "1.0.3"
|
||||||
|
@ -919,6 +975,15 @@ version = "0.2.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "miniz_oxide"
|
||||||
|
version = "0.8.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5"
|
||||||
|
dependencies = [
|
||||||
|
"adler2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "next_tuple"
|
name = "next_tuple"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
@ -935,6 +1000,15 @@ dependencies = [
|
||||||
"minimal-lexical",
|
"minimal-lexical",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-traits"
|
||||||
|
version = "0.2.19"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num_cpus"
|
name = "num_cpus"
|
||||||
version = "1.16.0"
|
version = "1.16.0"
|
||||||
|
@ -945,6 +1019,15 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "object"
|
||||||
|
version = "0.36.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "oco_ref"
|
name = "oco_ref"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
|
@ -1244,6 +1327,12 @@ dependencies = [
|
||||||
"thiserror 2.0.12",
|
"thiserror 2.0.12",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustc-demangle"
|
||||||
|
version = "0.1.24"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustc-hash"
|
name = "rustc-hash"
|
||||||
version = "2.1.1"
|
version = "2.1.1"
|
||||||
|
@ -1288,18 +1377,29 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.218"
|
version = "1.0.219"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e8dfc9d19bdbf6d17e22319da49161d5d0108e4188e8b680aef6299eed22df60"
|
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_derive"
|
name = "serde-wasm-bindgen"
|
||||||
version = "1.0.218"
|
version = "0.6.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b"
|
checksum = "8302e169f0eddcc139c70f139d19d6467353af16f9fce27e8c30158036a1e16b"
|
||||||
|
dependencies = [
|
||||||
|
"js-sys",
|
||||||
|
"serde",
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_derive"
|
||||||
|
version = "1.0.219"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
@ -1549,6 +1649,16 @@ dependencies = [
|
||||||
"zerovec",
|
"zerovec",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tokio"
|
||||||
|
version = "1.44.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9975ea0f48b5aa3972bf2d888c238182458437cc2a19374b81b25cdf1023fb3a"
|
||||||
|
dependencies = [
|
||||||
|
"backtrace",
|
||||||
|
"pin-project-lite",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml"
|
name = "toml"
|
||||||
version = "0.8.20"
|
version = "0.8.20"
|
||||||
|
|
|
@ -6,5 +6,11 @@ edition = "2021"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
console_error_panic_hook = "0.1.7"
|
console_error_panic_hook = "0.1.7"
|
||||||
dedent = "0.1.1"
|
dedent = "0.1.1"
|
||||||
|
futures = "0.3.31"
|
||||||
|
idb = "0.6.4"
|
||||||
leptos = { version = "0.7.7", features = ["csr"] }
|
leptos = { version = "0.7.7", features = ["csr"] }
|
||||||
|
serde = { version = "1.0.219", features = ["derive"] }
|
||||||
|
serde-wasm-bindgen = "0.6.5"
|
||||||
|
serde_json = "1.0.140"
|
||||||
thiserror = "2.0.12"
|
thiserror = "2.0.12"
|
||||||
|
url = "2.5.4"
|
||||||
|
|
1
src/components.rs
Normal file
1
src/components.rs
Normal file
|
@ -0,0 +1 @@
|
||||||
|
pub mod card;
|
15
src/components/card.rs
Normal file
15
src/components/card.rs
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
use leptos::prelude::*;
|
||||||
|
|
||||||
|
use crate::utils::card_database::CardDetails;
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
pub fn Card(details: Option<CardDetails>) -> impl IntoView {
|
||||||
|
match details {
|
||||||
|
Some(details) => view! {
|
||||||
|
<p>{details.name}</p>
|
||||||
|
<span style="white-space: pre-line">{details.description}</span>
|
||||||
|
}
|
||||||
|
.into_any(),
|
||||||
|
None => view! { <p>"Placeholder"</p> }.into_any(),
|
||||||
|
}
|
||||||
|
}
|
|
@ -1 +1,3 @@
|
||||||
|
pub mod components;
|
||||||
pub mod draft_list;
|
pub mod draft_list;
|
||||||
|
pub mod utils;
|
||||||
|
|
28
src/main.rs
28
src/main.rs
|
@ -1,6 +1,32 @@
|
||||||
|
use draft_manager::{components::card::Card, utils::card_database::CardDatabase};
|
||||||
use leptos::prelude::*;
|
use leptos::prelude::*;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
console_error_panic_hook::set_once();
|
console_error_panic_hook::set_once();
|
||||||
leptos::mount::mount_to_body(|| view! { <p>"Hello, world!"</p> })
|
|
||||||
|
leptos::mount::mount_to_body(|| view! { <App /> });
|
||||||
|
}
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
fn App() -> impl IntoView {
|
||||||
|
let card = LocalResource::new(|| async move {
|
||||||
|
let database = CardDatabase::open().await?;
|
||||||
|
database.load_data().await?;
|
||||||
|
|
||||||
|
database.get_card(1861629).await
|
||||||
|
});
|
||||||
|
|
||||||
|
view! {
|
||||||
|
<Transition fallback=move || {
|
||||||
|
view! { <p>"Loading database..."</p> }
|
||||||
|
}>
|
||||||
|
{move || {
|
||||||
|
card.read()
|
||||||
|
.as_deref()
|
||||||
|
.and_then(|card| {
|
||||||
|
view! { <Card details=card.as_ref().unwrap().clone() /> }.into()
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
</Transition>
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
1
src/utils.rs
Normal file
1
src/utils.rs
Normal file
|
@ -0,0 +1 @@
|
||||||
|
pub mod card_database;
|
145
src/utils/card_database.rs
Normal file
145
src/utils/card_database.rs
Normal file
|
@ -0,0 +1,145 @@
|
||||||
|
use std::{cell::RefCell, rc::Rc};
|
||||||
|
|
||||||
|
use idb::{
|
||||||
|
event::VersionChangeEvent, Database, DatabaseEvent, Factory, KeyPath, ObjectStoreParams, Query,
|
||||||
|
TransactionMode,
|
||||||
|
};
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use serde_wasm_bindgen::Serializer;
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
#[derive(Clone, Deserialize)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
pub enum FrameType {
|
||||||
|
Normal,
|
||||||
|
Effect,
|
||||||
|
Ritual,
|
||||||
|
Fusion,
|
||||||
|
Synchro,
|
||||||
|
Xyz,
|
||||||
|
Link,
|
||||||
|
NormalPendulum,
|
||||||
|
EffectPendulum,
|
||||||
|
RitualPendulum,
|
||||||
|
FusionPendulum,
|
||||||
|
SynchroPendulum,
|
||||||
|
XyzPendulum,
|
||||||
|
Spell,
|
||||||
|
Trap,
|
||||||
|
Token,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Deserialize)]
|
||||||
|
pub struct CardDetails {
|
||||||
|
pub password: u32,
|
||||||
|
|
||||||
|
pub name: String,
|
||||||
|
pub description: String,
|
||||||
|
|
||||||
|
pub frame: FrameType,
|
||||||
|
pub image: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct CardDatabase(Database);
|
||||||
|
|
||||||
|
const DB_VERSION: u32 = 1;
|
||||||
|
|
||||||
|
impl CardDatabase {
|
||||||
|
pub async fn open() -> Result<Self> {
|
||||||
|
let factory = Factory::new()?;
|
||||||
|
let mut open_request = factory.open("cards", Some(DB_VERSION))?;
|
||||||
|
|
||||||
|
let err = Rc::new(RefCell::new(Ok(())));
|
||||||
|
let moved_err = err.clone(); // Will be moved inside closure
|
||||||
|
open_request.on_upgrade_needed(move |event| {
|
||||||
|
let res = Self::upgrade_database(event);
|
||||||
|
|
||||||
|
if let Err(e) = res {
|
||||||
|
*std::cell::RefCell::<_>::borrow_mut(&moved_err) = Err(e);
|
||||||
|
drop(moved_err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let database = open_request.await?;
|
||||||
|
// Get the error if there is one - note that if the Rc has a
|
||||||
|
// strong count > 1 and therefore Symbol’s value as variable is void: into_inner returns Symbol’s value as variable is void: None,
|
||||||
|
// this means that the callback was not called, and as such
|
||||||
|
// there cannot have been an error.
|
||||||
|
let res = Rc::into_inner(err).unwrap_or(Ok(()).into()).into_inner();
|
||||||
|
|
||||||
|
res.map(|_| Self(database))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_card(&self, key: u32) -> Result<Option<CardDetails>> {
|
||||||
|
let transaction = self.0.transaction(&["cards"], TransactionMode::ReadOnly)?;
|
||||||
|
let store = transaction.object_store("cards")?;
|
||||||
|
let card = store.get(Query::Key(key.into()))?.await?;
|
||||||
|
Ok(card.map(serde_wasm_bindgen::from_value).transpose()?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn load_data(&self) -> Result<()> {
|
||||||
|
let transaction = self.0.transaction(&["cards"], TransactionMode::ReadWrite)?;
|
||||||
|
let store = transaction.object_store("cards")?;
|
||||||
|
|
||||||
|
// Add some test data for now
|
||||||
|
let decode_talker = serde_json::json!({
|
||||||
|
"password": 1861629,
|
||||||
|
"name": "Decode Talker",
|
||||||
|
"description": "2+ Effect Monsters\r\nGains 500 ATK for each monster it points to. When your opponent activates a card or effect that targets a card(s) you control (Quick Effect): You can Tribute 1 monster this card points to; negate the activation, and if you do, destroy that card.",
|
||||||
|
"frame": "link",
|
||||||
|
"image": "https://images.ygoprodeck.com/images/cards/1861629.jpg"
|
||||||
|
});
|
||||||
|
|
||||||
|
store
|
||||||
|
.put(
|
||||||
|
&decode_talker.serialize(&Serializer::json_compatible())?,
|
||||||
|
None,
|
||||||
|
)?
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
transaction.commit()?.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Upgrade the database; this is called by the browser whenever
|
||||||
|
/// we try to open a newer database version.
|
||||||
|
fn upgrade_database(event: VersionChangeEvent) -> Result<()> {
|
||||||
|
let database = event.database()?;
|
||||||
|
|
||||||
|
let upgrade_from = event.old_version()?;
|
||||||
|
match upgrade_from {
|
||||||
|
// This version number signals that the database
|
||||||
|
// hasn't been created before.
|
||||||
|
0 => {
|
||||||
|
let mut store_params = ObjectStoreParams::new();
|
||||||
|
store_params.key_path(Some(KeyPath::new_single("password")));
|
||||||
|
|
||||||
|
let store = database.create_object_store("cards", store_params)?;
|
||||||
|
|
||||||
|
store.create_index(
|
||||||
|
"fulltext",
|
||||||
|
KeyPath::new_array(vec!["password", "name", "description"]),
|
||||||
|
None,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
other => return Err(CardDatabaseError::UnknownDatabaseVersion(other)),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum CardDatabaseError {
|
||||||
|
#[error(
|
||||||
|
"unknown card database version `{0}`, please close the tab and re-open the application"
|
||||||
|
)]
|
||||||
|
UnknownDatabaseVersion(u32),
|
||||||
|
#[error("card database operation failed; does the browser support IndexedDB?")]
|
||||||
|
BrowserError(#[from] idb::Error),
|
||||||
|
#[error("invalid test data")]
|
||||||
|
SerdeError(#[from] serde_wasm_bindgen::Error),
|
||||||
|
}
|
||||||
|
type Result<T> = std::result::Result<T, CardDatabaseError>;
|
Loading…
Reference in a new issue