Add SQLite3MultipleCiphers support
This commit is contained in:
6
.github/workflows/pages.yaml
vendored
6
.github/workflows/pages.yaml
vendored
@@ -29,6 +29,12 @@ jobs:
|
|||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: |
|
run: |
|
||||||
|
git clone https://github.com/emscripten-core/emsdk.git
|
||||||
|
cd emsdk
|
||||||
|
./emsdk install latest
|
||||||
|
./emsdk activate latest
|
||||||
|
source ./emsdk_env.sh
|
||||||
|
cd ..
|
||||||
trunk build --release
|
trunk build --release
|
||||||
|
|
||||||
- name: Deploy
|
- name: Deploy
|
||||||
|
|||||||
30
Cargo.lock
generated
30
Cargo.lock
generated
@@ -170,6 +170,15 @@ version = "1.1.9"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8b96ec4966b5813e2c0507c1f86115c8c5abaadc3980879c3424042a02fd1ad3"
|
checksum = "8b96ec4966b5813e2c0507c1f86115c8c5abaadc3980879c3424042a02fd1ad3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cc"
|
||||||
|
version = "1.2.24"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "16595d3be041c03b09d08d0858631facccee9221e579704070e6e9e4915d3bc7"
|
||||||
|
dependencies = [
|
||||||
|
"shlex",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cfg-if"
|
name = "cfg-if"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
@@ -1773,6 +1782,12 @@ dependencies = [
|
|||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "shlex"
|
||||||
|
version = "1.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "slab"
|
name = "slab"
|
||||||
version = "0.4.9"
|
version = "0.4.9"
|
||||||
@@ -1851,9 +1866,9 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "sqlite-wasm-rs"
|
name = "sqlite-wasm-rs"
|
||||||
version = "0.3.6"
|
version = "0.3.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "git+https://github.com/Spxg/sqlite-wasm-rs#9912b461e8e551feb400b01f3abd94e4303bef21"
|
||||||
checksum = "c0c6fdb636283dad1283b20bc62157ac68a7124fde269723467689c4fd6d356c"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"cc",
|
||||||
"fragile",
|
"fragile",
|
||||||
"indexed_db_futures",
|
"indexed_db_futures",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
@@ -1861,6 +1876,7 @@ dependencies = [
|
|||||||
"parking_lot",
|
"parking_lot",
|
||||||
"thiserror 2.0.12",
|
"thiserror 2.0.12",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"wasm-array-cp",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
"wasm-bindgen-futures",
|
"wasm-bindgen-futures",
|
||||||
"web-sys",
|
"web-sys",
|
||||||
@@ -2168,6 +2184,16 @@ dependencies = [
|
|||||||
"wit-bindgen-rt",
|
"wit-bindgen-rt",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-array-cp"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bb633b3e235f0ebe0a35162adc1e0293fc4b7e3f3a6fc7b5374d80464267ff84"
|
||||||
|
dependencies = [
|
||||||
|
"js-sys",
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen"
|
name = "wasm-bindgen"
|
||||||
version = "0.2.100"
|
version = "0.2.100"
|
||||||
|
|||||||
@@ -18,14 +18,14 @@ aceditor = { path = "crates/aceditor" }
|
|||||||
split-grid = { path = "crates/split-grid" }
|
split-grid = { path = "crates/split-grid" }
|
||||||
floating-ui = { path = "crates/floating-ui" }
|
floating-ui = { path = "crates/floating-ui" }
|
||||||
|
|
||||||
sqlite-wasm-rs = { version = "0.3.6", default-features = false, features = ["precompiled"] }
|
sqlite-wasm-rs = { git = "https://github.com/Spxg/sqlite-wasm-rs", default-features = false, optional = true }
|
||||||
console_error_panic_hook = "0.1.7"
|
console_error_panic_hook = "0.1.7"
|
||||||
leptos = { version = "0.8.2", features = ["csr"] }
|
leptos = { version = "0.8.2", features = ["csr"] }
|
||||||
reactive_stores = "0.2.2"
|
reactive_stores = "0.2.2"
|
||||||
thiserror = "2.0.12"
|
thiserror = "2.0.12"
|
||||||
serde = "1.0.219"
|
serde = "1.0.219"
|
||||||
serde_json = "1.0.140"
|
serde_json = "1.0.140"
|
||||||
web-sys = { version = "0.3.77", features = ["BlobPropertyBag", "Clipboard", "DedicatedWorkerGlobalScope", "File", "FileList", "HtmlSelectElement", "MediaQueryList", "Navigator", "Storage", "Worker", "WorkerOptions", "WorkerType"] }
|
web-sys = { version = "0.3.77", features = ["BlobPropertyBag", "Clipboard", "DedicatedWorkerGlobalScope", "DomException", "File", "FileList", "HtmlSelectElement", "MediaQueryList", "Navigator", "Storage", "Worker", "WorkerOptions", "WorkerType"] }
|
||||||
serde-wasm-bindgen = "0.6.5"
|
serde-wasm-bindgen = "0.6.5"
|
||||||
parking_lot = "0.12.3"
|
parking_lot = "0.12.3"
|
||||||
once_cell = "1.21.3"
|
once_cell = "1.21.3"
|
||||||
@@ -39,3 +39,8 @@ fragile = "2.0.1"
|
|||||||
hex = "0.4.3"
|
hex = "0.4.3"
|
||||||
prettytable-rs = "0.10.0"
|
prettytable-rs = "0.10.0"
|
||||||
sqlformat = "0.3.5"
|
sqlformat = "0.3.5"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["sqlite3"]
|
||||||
|
sqlite3 = ["sqlite-wasm-rs/precompiled"]
|
||||||
|
sqlite3mc = ["sqlite-wasm-rs/sqlite3mc"]
|
||||||
|
|||||||
@@ -53,8 +53,9 @@
|
|||||||
<link data-trunk href="./assets/module.postcss/output/section.module.css" rel="css">
|
<link data-trunk href="./assets/module.postcss/output/section.module.css" rel="css">
|
||||||
<link data-trunk href="./assets/module.postcss/output/share.module.css" rel="css">
|
<link data-trunk href="./assets/module.postcss/output/share.module.css" rel="css">
|
||||||
|
|
||||||
<link data-trunk rel="rust" href="Cargo.toml" data-bin="app" data-type="main" />
|
<link data-trunk rel="rust" href="Cargo.toml" data-cargo-no-default-features data-bin="app" data-type="main" />
|
||||||
<link data-trunk rel="rust" href="Cargo.toml" data-bin="worker" data-type="worker" data-loader-shim data-bindgen-target="web" />
|
<link data-trunk rel="rust" href="Cargo.toml" data-cargo-no-default-features data-cargo-features="sqlite3" data-bin="sqlite3" data-type="worker" data-loader-shim data-bindgen-target="web" />
|
||||||
|
<link data-trunk rel="rust" href="Cargo.toml" data-cargo-no-default-features data-cargo-features="sqlite3mc" data-bin="sqlite3mc" data-type="worker" data-loader-shim data-bindgen-target="web" />
|
||||||
</head>
|
</head>
|
||||||
<body></body>
|
<body></body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -2,7 +2,8 @@ use leptos::prelude::*;
|
|||||||
use reactive_stores::Store;
|
use reactive_stores::Store;
|
||||||
|
|
||||||
use crate::app::{
|
use crate::app::{
|
||||||
GlobalState, GlobalStateStoreFields, config_element::Either, menu_group::MenuGroup,
|
GlobalState, GlobalStateStoreFields, config_element::Either, menu_aside::MenuAside,
|
||||||
|
menu_group::MenuGroup,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
@@ -25,6 +26,29 @@ pub fn AdvancedOptionsMenu() -> impl IntoView {
|
|||||||
state.run_selected_sql().set(*value);
|
state.run_selected_sql().set(*value);
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
<Either
|
||||||
|
aside=Some(
|
||||||
|
view! {
|
||||||
|
<MenuAside>
|
||||||
|
"Using an encrypted database.
|
||||||
|
https://github.com/utelle/SQLite3MultipleCiphers
|
||||||
|
https://utelle.github.io/SQLite3MultipleCiphers"
|
||||||
|
</MenuAside>
|
||||||
|
}
|
||||||
|
.into_any(),
|
||||||
|
)
|
||||||
|
id="multiple_ciphers".into()
|
||||||
|
name="Multiple Ciphers".into()
|
||||||
|
a=true
|
||||||
|
b=false
|
||||||
|
a_label=Some("On".to_string())
|
||||||
|
b_label=Some("Off".to_string())
|
||||||
|
value=move || *state.multiple_ciphers().read()
|
||||||
|
is_default=Box::new(move || !*state.multiple_ciphers().read())
|
||||||
|
on_change=move |value: &bool| {
|
||||||
|
state.multiple_ciphers().set(*value);
|
||||||
|
}
|
||||||
|
/>
|
||||||
</MenuGroup>
|
</MenuGroup>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ istyles!(
|
|||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn Either<E, V, T>(
|
pub fn Either<E, V, T>(
|
||||||
|
#[prop(default =None)] aside: Option<AnyView>,
|
||||||
on_change: E,
|
on_change: E,
|
||||||
id: String,
|
id: String,
|
||||||
a: T,
|
a: T,
|
||||||
@@ -58,7 +59,7 @@ where
|
|||||||
let b2 = Arc::clone(&b1);
|
let b2 = Arc::clone(&b1);
|
||||||
|
|
||||||
view! {
|
view! {
|
||||||
<ConfigElement name=name is_default=is_default>
|
<ConfigElement name=name is_default=is_default aside>
|
||||||
<div class=styles::toggle>
|
<div class=styles::toggle>
|
||||||
<input
|
<input
|
||||||
id=a_id.clone()
|
id=a_id.clone()
|
||||||
@@ -105,6 +106,7 @@ where
|
|||||||
#[component]
|
#[component]
|
||||||
pub fn ConfigElement(
|
pub fn ConfigElement(
|
||||||
name: String,
|
name: String,
|
||||||
|
#[prop(default =None)] aside: Option<AnyView>,
|
||||||
#[prop(default = Box::new(|| true))] is_default: Box<dyn Fn() -> bool + Send>,
|
#[prop(default = Box::new(|| true))] is_default: Box<dyn Fn() -> bool + Send>,
|
||||||
children: Children,
|
children: Children,
|
||||||
) -> impl IntoView {
|
) -> impl IntoView {
|
||||||
@@ -121,6 +123,7 @@ pub fn ConfigElement(
|
|||||||
<span class=style>{name}</span>
|
<span class=style>{name}</span>
|
||||||
<div class=styles::value>{children()}</div>
|
<div class=styles::value>{children()}</div>
|
||||||
</div>
|
</div>
|
||||||
|
{aside}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ use crate::{
|
|||||||
tools_menu::ToolsMenu,
|
tools_menu::ToolsMenu,
|
||||||
vfs_menu::VfsMenu,
|
vfs_menu::VfsMenu,
|
||||||
},
|
},
|
||||||
|
send_request,
|
||||||
};
|
};
|
||||||
|
|
||||||
istyles!(styles, "assets/module.postcss/header.module.css.map");
|
istyles!(styles, "assets/module.postcss/header.module.css.map");
|
||||||
@@ -90,8 +91,9 @@ pub fn execute(state: Store<GlobalState>) -> Box<dyn Fn() + Send + 'static> {
|
|||||||
let run_selected_code =
|
let run_selected_code =
|
||||||
!selected_code.is_empty() && state.run_selected_sql().get_untracked();
|
!selected_code.is_empty() && state.run_selected_sql().get_untracked();
|
||||||
|
|
||||||
if let Some(worker) = &*state.worker().read_untracked() {
|
send_request(
|
||||||
worker.send_task(WorkerRequest::Run(RunOptions {
|
state,
|
||||||
|
WorkerRequest::Run(RunOptions {
|
||||||
embed: false,
|
embed: false,
|
||||||
sql: if run_selected_code {
|
sql: if run_selected_code {
|
||||||
selected_code
|
selected_code
|
||||||
@@ -99,8 +101,8 @@ pub fn execute(state: Store<GlobalState>) -> Box<dyn Fn() + Send + 'static> {
|
|||||||
code
|
code
|
||||||
},
|
},
|
||||||
clear_on_prepare: !*state.keep_ctx().read_untracked(),
|
clear_on_prepare: !*state.keep_ctx().read_untracked(),
|
||||||
}));
|
}),
|
||||||
}
|
);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -248,13 +250,14 @@ fn ToolsButton(menu_container: NodeRef<html::element::Div>) -> impl IntoView {
|
|||||||
let sql = editor.get_value();
|
let sql = editor.get_value();
|
||||||
drop(editor_guard);
|
drop(editor_guard);
|
||||||
|
|
||||||
if let Some(worker) = &*state.worker().read_untracked() {
|
send_request(
|
||||||
worker.send_task(WorkerRequest::Run(RunOptions {
|
state,
|
||||||
|
WorkerRequest::Run(RunOptions {
|
||||||
embed: true,
|
embed: true,
|
||||||
sql,
|
sql,
|
||||||
clear_on_prepare: !*state.keep_ctx().read_untracked(),
|
clear_on_prepare: !*state.keep_ctx().read_untracked(),
|
||||||
}));
|
}),
|
||||||
}
|
);
|
||||||
|
|
||||||
signal.set(false);
|
signal.set(false);
|
||||||
};
|
};
|
||||||
@@ -339,9 +342,7 @@ fn DatabaseButton(
|
|||||||
let result = reader.result().unwrap();
|
let result = reader.result().unwrap();
|
||||||
let array_buffer = result.unchecked_into::<js_sys::ArrayBuffer>();
|
let array_buffer = result.unchecked_into::<js_sys::ArrayBuffer>();
|
||||||
let data = js_sys::Uint8Array::new(&array_buffer);
|
let data = js_sys::Uint8Array::new(&array_buffer);
|
||||||
if let Some(worker) = &*state.worker().read_untracked() {
|
send_request(state, WorkerRequest::LoadDb(LoadDbOptions { data }));
|
||||||
worker.send_task(WorkerRequest::LoadDb(LoadDbOptions { data }));
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
as Box<dyn FnMut(_)>));
|
as Box<dyn FnMut(_)>));
|
||||||
|
|
||||||
@@ -415,9 +416,7 @@ fn DatabaseButton(
|
|||||||
});
|
});
|
||||||
|
|
||||||
let on_download = move |_: MouseEvent, signal: WriteSignal<bool>| {
|
let on_download = move |_: MouseEvent, signal: WriteSignal<bool>| {
|
||||||
if let Some(worker) = &*state.worker().read() {
|
send_request(state, WorkerRequest::DownloadDb);
|
||||||
worker.send_task(WorkerRequest::DownloadDb);
|
|
||||||
}
|
|
||||||
signal.set(false);
|
signal.set(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -21,5 +21,5 @@ mod state;
|
|||||||
mod tools_menu;
|
mod tools_menu;
|
||||||
mod vfs_menu;
|
mod vfs_menu;
|
||||||
|
|
||||||
pub use playground::playground;
|
pub use playground::Playground;
|
||||||
pub use state::*;
|
pub use state::*;
|
||||||
|
|||||||
@@ -6,9 +6,7 @@ use leptos::tachys::html;
|
|||||||
use prettytable::{Cell, Row, Table};
|
use prettytable::{Cell, Row, Table};
|
||||||
use reactive_stores::Store;
|
use reactive_stores::Store;
|
||||||
use split_grid::{Gutter, SplitOptions};
|
use split_grid::{Gutter, SplitOptions};
|
||||||
use tokio::sync::mpsc::UnboundedReceiver;
|
|
||||||
use wasm_bindgen::{JsCast, prelude::Closure};
|
use wasm_bindgen::{JsCast, prelude::Closure};
|
||||||
use wasm_bindgen_futures::spawn_local;
|
|
||||||
use web_sys::wasm_bindgen::JsValue;
|
use web_sys::wasm_bindgen::JsValue;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@@ -18,35 +16,27 @@ use crate::{
|
|||||||
editor::Editor,
|
editor::Editor,
|
||||||
header::Header,
|
header::Header,
|
||||||
output::{Output, change_focus},
|
output::{Output, change_focus},
|
||||||
state::{GlobalState, GlobalStateStoreFields, Orientation, Theme, Vfs},
|
state::{GlobalState, GlobalStateStoreFields, Orientation, Theme},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use crate::{WorkerHandle, WorkerResponse, handle_state};
|
|
||||||
|
|
||||||
istyles!(styles, "assets/module.postcss/playground.module.css.map");
|
istyles!(styles, "assets/module.postcss/playground.module.css.map");
|
||||||
|
|
||||||
pub fn playground(
|
#[component]
|
||||||
worker: (WorkerHandle, UnboundedReceiver<WorkerResponse>),
|
pub fn Playground() -> impl IntoView {
|
||||||
) -> Box<dyn FnOnce() -> AnyView + 'static> {
|
|
||||||
Box::new(move || {
|
|
||||||
let (worker_handle, rx) = worker;
|
|
||||||
let state = GlobalState::load().unwrap_or_default();
|
let state = GlobalState::load().unwrap_or_default();
|
||||||
provide_context(Store::new(state));
|
provide_context(Store::new(state));
|
||||||
|
|
||||||
let state = expect_context::<Store<GlobalState>>();
|
let state = expect_context::<Store<GlobalState>>();
|
||||||
state.worker().set(Some(worker_handle));
|
|
||||||
|
|
||||||
handle_last_error(state);
|
handle_last_error(state);
|
||||||
handle_system_theme(state);
|
handle_system_theme(state);
|
||||||
handle_automic_orientation(state);
|
handle_automic_orientation(state);
|
||||||
handle_connect_db(state);
|
|
||||||
handle_save_state(state);
|
handle_save_state(state);
|
||||||
handle_import_progress(state);
|
handle_import_progress(state);
|
||||||
handle_ace_config(state);
|
handle_ace_config(state);
|
||||||
handle_embed_query_result(state);
|
handle_embed_query_result(state);
|
||||||
|
|
||||||
spawn_local(handle_state(state, rx));
|
|
||||||
|
|
||||||
view! {
|
view! {
|
||||||
<div id="playground" class=styles::container>
|
<div id="playground" class=styles::container>
|
||||||
<Header />
|
<Header />
|
||||||
@@ -54,7 +44,6 @@ pub fn playground(
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
.into_any()
|
.into_any()
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_embed_query_result(state: Store<GlobalState>) {
|
fn handle_embed_query_result(state: Store<GlobalState>) {
|
||||||
@@ -154,22 +143,12 @@ fn handle_save_state(state: Store<GlobalState>) {
|
|||||||
state.keep_ctx().track();
|
state.keep_ctx().track();
|
||||||
state.sql().track();
|
state.sql().track();
|
||||||
state.run_selected_sql().track();
|
state.run_selected_sql().track();
|
||||||
|
state.multiple_ciphers().track();
|
||||||
|
|
||||||
state.read_untracked().save();
|
state.read_untracked().save();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_connect_db(state: Store<GlobalState>) {
|
|
||||||
Effect::new(move || {
|
|
||||||
if let Some(worker) = &*state.worker().read() {
|
|
||||||
worker.send_task(crate::WorkerRequest::Open(crate::OpenOptions {
|
|
||||||
filename: "test.db".into(),
|
|
||||||
persist: *state.vfs().read() == Vfs::OPFS,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_last_error(state: Store<GlobalState>) {
|
fn handle_last_error(state: Store<GlobalState>) {
|
||||||
Effect::new(move || {
|
Effect::new(move || {
|
||||||
if state.last_error().read().is_some() {
|
if state.last_error().read().is_some() {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ use reactive_stores::Store;
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use web_sys::MediaQueryList;
|
use web_sys::MediaQueryList;
|
||||||
|
|
||||||
use crate::{FragileComfirmed, SQLightError, SQLiteStatementResult, WorkerHandle};
|
use crate::{FragileComfirmed, SQLightError, SQLiteStatementResult};
|
||||||
|
|
||||||
const DEFAULT_CODE: &str = "PRAGMA page_size=4096;
|
const DEFAULT_CODE: &str = "PRAGMA page_size=4096;
|
||||||
|
|
||||||
@@ -34,10 +34,9 @@ pub struct GlobalState {
|
|||||||
keep_ctx: bool,
|
keep_ctx: bool,
|
||||||
sql: String,
|
sql: String,
|
||||||
run_selected_sql: bool,
|
run_selected_sql: bool,
|
||||||
|
multiple_ciphers: bool,
|
||||||
// runtime state below
|
// runtime state below
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
worker: Option<WorkerHandle>,
|
|
||||||
#[serde(skip)]
|
|
||||||
editor: Option<Editor>,
|
editor: Option<Editor>,
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
focus: Option<Focus>,
|
focus: Option<Focus>,
|
||||||
@@ -71,7 +70,7 @@ impl Default for GlobalState {
|
|||||||
keep_ctx: false,
|
keep_ctx: false,
|
||||||
sql: DEFAULT_CODE.into(),
|
sql: DEFAULT_CODE.into(),
|
||||||
run_selected_sql: false,
|
run_selected_sql: false,
|
||||||
worker: None,
|
multiple_ciphers: false,
|
||||||
editor: None,
|
editor: None,
|
||||||
focus: None,
|
focus: None,
|
||||||
is_focused: false,
|
is_focused: false,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use leptos::prelude::*;
|
use leptos::prelude::*;
|
||||||
use sqlight::{app::playground, setup_worker};
|
use sqlight::app::Playground;
|
||||||
|
|
||||||
use wasm_bindgen::prelude::wasm_bindgen;
|
use wasm_bindgen::prelude::wasm_bindgen;
|
||||||
|
|
||||||
@@ -7,5 +7,5 @@ use wasm_bindgen::prelude::wasm_bindgen;
|
|||||||
async fn main() {
|
async fn main() {
|
||||||
console_error_panic_hook::set_once();
|
console_error_panic_hook::set_once();
|
||||||
console_log::init_with_level(log::Level::Debug).unwrap();
|
console_log::init_with_level(log::Level::Debug).unwrap();
|
||||||
mount_to_body(playground(setup_worker().await));
|
mount_to_body(Playground);
|
||||||
}
|
}
|
||||||
|
|||||||
3
src/bin/sqlite3.rs
Normal file
3
src/bin/sqlite3.rs
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
fn main() {
|
||||||
|
sqlight::worker::entry();
|
||||||
|
}
|
||||||
3
src/bin/sqlite3mc.rs
Normal file
3
src/bin/sqlite3mc.rs
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
fn main() {
|
||||||
|
sqlight::worker::entry();
|
||||||
|
}
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
use sqlight::{WorkerRequest, WorkerResponse, worker};
|
|
||||||
use tokio::sync::mpsc::UnboundedReceiver;
|
|
||||||
use wasm_bindgen::{JsCast, JsValue, prelude::Closure};
|
|
||||||
use wasm_bindgen_futures::spawn_local;
|
|
||||||
use web_sys::{DedicatedWorkerGlobalScope, MessageEvent};
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
console_error_panic_hook::set_once();
|
|
||||||
console_log::init_with_level(log::Level::Debug).unwrap();
|
|
||||||
|
|
||||||
let (tx, rx) = tokio::sync::mpsc::unbounded_channel::<JsValue>();
|
|
||||||
|
|
||||||
let scope: DedicatedWorkerGlobalScope = JsValue::from(js_sys::global()).into();
|
|
||||||
spawn_local(execute_task(scope.clone(), rx));
|
|
||||||
|
|
||||||
let on_message = Closure::<dyn Fn(MessageEvent)>::new(move |ev: MessageEvent| {
|
|
||||||
tx.send(ev.data()).unwrap();
|
|
||||||
});
|
|
||||||
|
|
||||||
scope.set_onmessage(Some(on_message.as_ref().unchecked_ref()));
|
|
||||||
scope
|
|
||||||
.post_message(&serde_wasm_bindgen::to_value(&WorkerResponse::Ready).unwrap())
|
|
||||||
.expect("Faild to send ready to window");
|
|
||||||
on_message.forget();
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn execute_task(scope: DedicatedWorkerGlobalScope, mut rx: UnboundedReceiver<JsValue>) {
|
|
||||||
while let Some(request) = rx.recv().await {
|
|
||||||
let request = serde_wasm_bindgen::from_value::<WorkerRequest>(request).unwrap();
|
|
||||||
let resp = match request {
|
|
||||||
WorkerRequest::Open(options) => WorkerResponse::Open(worker::open(options).await),
|
|
||||||
WorkerRequest::Run(options) => WorkerResponse::Run(worker::run(options).await),
|
|
||||||
WorkerRequest::LoadDb(options) => {
|
|
||||||
WorkerResponse::LoadDb(worker::load_db(options).await)
|
|
||||||
}
|
|
||||||
WorkerRequest::DownloadDb => WorkerResponse::DownloadDb(worker::download_db().await),
|
|
||||||
};
|
|
||||||
if let Err(err) = scope.post_message(&serde_wasm_bindgen::to_value(&resp).unwrap()) {
|
|
||||||
log::error!("Failed to send task to window: {resp:?}, {err:?}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
76
src/lib.rs
76
src/lib.rs
@@ -1,18 +1,20 @@
|
|||||||
pub mod app;
|
pub mod app;
|
||||||
|
#[cfg(any(feature = "sqlite3", feature = "sqlite3mc"))]
|
||||||
pub mod worker;
|
pub mod worker;
|
||||||
|
|
||||||
use aceditor::EditorError;
|
use aceditor::EditorError;
|
||||||
use app::{Exported, GlobalState, GlobalStateStoreFields};
|
use app::{Exported, GlobalState, GlobalStateStoreFields, Vfs};
|
||||||
use fragile::Fragile;
|
use fragile::Fragile;
|
||||||
use js_sys::Uint8Array;
|
use js_sys::Uint8Array;
|
||||||
use leptos::prelude::*;
|
use leptos::prelude::*;
|
||||||
use reactive_stores::Store;
|
use reactive_stores::Store;
|
||||||
use std::{
|
use std::{
|
||||||
ops::{Deref, DerefMut},
|
ops::{Deref, DerefMut},
|
||||||
sync::Arc,
|
sync::{Arc, Once},
|
||||||
};
|
};
|
||||||
use tokio::sync::mpsc::UnboundedReceiver;
|
use tokio::sync::{OnceCell, mpsc::UnboundedReceiver};
|
||||||
use wasm_bindgen::{JsCast, prelude::Closure};
|
use wasm_bindgen::{JsCast, prelude::Closure};
|
||||||
|
use wasm_bindgen_futures::spawn_local;
|
||||||
use web_sys::{MessageEvent, Worker, WorkerOptions, WorkerType};
|
use web_sys::{MessageEvent, Worker, WorkerOptions, WorkerType};
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
@@ -51,8 +53,6 @@ impl<T> DerefMut for FragileComfirmed<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const PERSIST_VFS: &str = "sqlight-sahpool";
|
|
||||||
|
|
||||||
#[derive(thiserror::Error, Debug)]
|
#[derive(thiserror::Error, Debug)]
|
||||||
pub enum SQLightError {
|
pub enum SQLightError {
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
@@ -127,16 +127,6 @@ pub struct LoadDbOptions {
|
|||||||
pub data: Uint8Array,
|
pub data: Uint8Array,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl OpenOptions {
|
|
||||||
pub fn uri(&self) -> String {
|
|
||||||
format!(
|
|
||||||
"file:{}?vfs={}",
|
|
||||||
self.filename,
|
|
||||||
if self.persist { PERSIST_VFS } else { "memvfs" }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
pub struct RunOptions {
|
pub struct RunOptions {
|
||||||
pub sql: String,
|
pub sql: String,
|
||||||
@@ -209,9 +199,55 @@ impl WorkerHandle {
|
|||||||
unsafe impl Send for WorkerHandle {}
|
unsafe impl Send for WorkerHandle {}
|
||||||
unsafe impl Sync for WorkerHandle {}
|
unsafe impl Sync for WorkerHandle {}
|
||||||
|
|
||||||
pub async fn setup_worker() -> (WorkerHandle, UnboundedReceiver<WorkerResponse>) {
|
fn send_request(state: Store<GlobalState>, req: WorkerRequest) {
|
||||||
let uri = "./worker_loader.js";
|
spawn_local(async move {
|
||||||
|
if state.multiple_ciphers().get_untracked() {
|
||||||
|
sqlite3mc(state).await
|
||||||
|
} else {
|
||||||
|
sqlite3(state).await
|
||||||
|
}
|
||||||
|
.send_task(req);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn sqlite3mc(state: Store<GlobalState>) -> &'static WorkerHandle {
|
||||||
|
static ONCE: Once = Once::new();
|
||||||
|
static WORKER: OnceCell<WorkerHandle> = OnceCell::const_new();
|
||||||
|
|
||||||
|
let worker = WORKER
|
||||||
|
.get_or_init(|| async { setup_worker(state, "./sqlite3mc_loader.js").await })
|
||||||
|
.await;
|
||||||
|
|
||||||
|
ONCE.call_once(|| {
|
||||||
|
connect_db(state, worker);
|
||||||
|
Effect::new(move || connect_db(state, worker));
|
||||||
|
});
|
||||||
|
worker
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn sqlite3(state: Store<GlobalState>) -> &'static WorkerHandle {
|
||||||
|
static ONCE: Once = Once::new();
|
||||||
|
static WORKER: OnceCell<WorkerHandle> = OnceCell::const_new();
|
||||||
|
|
||||||
|
let worker = WORKER
|
||||||
|
.get_or_init(|| async { setup_worker(state, "./sqlite3_loader.js").await })
|
||||||
|
.await;
|
||||||
|
|
||||||
|
ONCE.call_once(|| {
|
||||||
|
connect_db(state, worker);
|
||||||
|
Effect::new(move || connect_db(state, worker));
|
||||||
|
});
|
||||||
|
worker
|
||||||
|
}
|
||||||
|
|
||||||
|
fn connect_db(state: Store<GlobalState>, handle: &'static WorkerHandle) {
|
||||||
|
handle.send_task(crate::WorkerRequest::Open(crate::OpenOptions {
|
||||||
|
filename: "test.db".into(),
|
||||||
|
persist: *state.vfs().read() == Vfs::OPFS,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn setup_worker(state: Store<GlobalState>, uri: &str) -> WorkerHandle {
|
||||||
let opts = WorkerOptions::new();
|
let opts = WorkerOptions::new();
|
||||||
opts.set_type(WorkerType::Module);
|
opts.set_type(WorkerType::Module);
|
||||||
|
|
||||||
@@ -233,14 +269,16 @@ pub async fn setup_worker() -> (WorkerHandle, UnboundedReceiver<WorkerResponse>)
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
spawn_local(handle_state(state, rx));
|
||||||
|
|
||||||
worker.set_onmessage(Some(on_message.as_ref().unchecked_ref()));
|
worker.set_onmessage(Some(on_message.as_ref().unchecked_ref()));
|
||||||
on_message.forget();
|
on_message.forget();
|
||||||
wait.notified().await;
|
wait.notified().await;
|
||||||
|
|
||||||
(WorkerHandle(worker), rx)
|
WorkerHandle(worker)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn handle_state(state: Store<GlobalState>, mut rx: UnboundedReceiver<WorkerResponse>) {
|
async fn handle_state(state: Store<GlobalState>, mut rx: UnboundedReceiver<WorkerResponse>) {
|
||||||
while let Some(resp) = rx.recv().await {
|
while let Some(resp) = rx.recv().await {
|
||||||
state.last_error().set(None);
|
state.last_error().set(None);
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
mod sqlitend;
|
mod sqlitend;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
DownloadDbResponse, LoadDbOptions, OpenOptions, PERSIST_VFS, RunOptions, SQLiteRunResult,
|
DownloadDbResponse, LoadDbOptions, OpenOptions, RunOptions, SQLiteRunResult, WorkerError,
|
||||||
WorkerError,
|
WorkerRequest, WorkerResponse,
|
||||||
};
|
};
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use sqlite_wasm_rs::{
|
use sqlite_wasm_rs::{
|
||||||
@@ -14,6 +14,10 @@ use sqlitend::SQLiteDb;
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
use tokio::sync::OnceCell;
|
use tokio::sync::OnceCell;
|
||||||
|
use tokio::sync::mpsc::UnboundedReceiver;
|
||||||
|
use wasm_bindgen::{JsCast, JsValue, prelude::Closure};
|
||||||
|
use wasm_bindgen_futures::spawn_local;
|
||||||
|
use web_sys::{DedicatedWorkerGlobalScope, MessageEvent};
|
||||||
|
|
||||||
type Result<T> = std::result::Result<T, WorkerError>;
|
type Result<T> = std::result::Result<T, WorkerError>;
|
||||||
|
|
||||||
@@ -24,6 +28,29 @@ static FS_UTIL: Lazy<FSUtil> = Lazy::new(|| FSUtil {
|
|||||||
opfs: OnceCell::new(),
|
opfs: OnceCell::new(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
#[cfg(feature = "sqlite3")]
|
||||||
|
const OPFS_VFS: &str = "opfs";
|
||||||
|
#[cfg(feature = "sqlite3mc")]
|
||||||
|
const OPFS_VFS: &str = "multipleciphers-opfs";
|
||||||
|
|
||||||
|
#[cfg(feature = "sqlite3")]
|
||||||
|
const OPFS_VFS_DIR: &str = "sqlight-sahpool";
|
||||||
|
#[cfg(feature = "sqlite3mc")]
|
||||||
|
const OPFS_VFS_DIR: &str = "sqlight-sahpool-mc";
|
||||||
|
|
||||||
|
#[cfg(feature = "sqlite3")]
|
||||||
|
const MEM_VFS: &str = "memvfs";
|
||||||
|
#[cfg(feature = "sqlite3mc")]
|
||||||
|
const MEM_VFS: &str = "multipleciphers-memvfs";
|
||||||
|
|
||||||
|
fn uri(filename: &str, persist: bool) -> String {
|
||||||
|
format!(
|
||||||
|
"file:{}?vfs={}",
|
||||||
|
filename,
|
||||||
|
if persist { OPFS_VFS } else { MEM_VFS }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
struct FSUtil {
|
struct FSUtil {
|
||||||
mem: MemVfsUtil,
|
mem: MemVfsUtil,
|
||||||
opfs: OnceCell<OpfsSAHPoolUtil>,
|
opfs: OnceCell<OpfsSAHPoolUtil>,
|
||||||
@@ -53,8 +80,8 @@ async fn init_opfs_util() -> Result<&'static OpfsSAHPoolUtil> {
|
|||||||
sqlite_wasm_rs::sahpool_vfs::install(
|
sqlite_wasm_rs::sahpool_vfs::install(
|
||||||
Some(
|
Some(
|
||||||
&OpfsSAHPoolCfgBuilder::new()
|
&OpfsSAHPoolCfgBuilder::new()
|
||||||
.directory(PERSIST_VFS)
|
.directory(OPFS_VFS_DIR)
|
||||||
.vfs_name(PERSIST_VFS)
|
.vfs_name("opfs")
|
||||||
.build(),
|
.build(),
|
||||||
),
|
),
|
||||||
false,
|
false,
|
||||||
@@ -69,7 +96,7 @@ fn get_opfs_util() -> Result<&'static OpfsSAHPoolUtil> {
|
|||||||
FS_UTIL.opfs.get().ok_or(WorkerError::Unexpected)
|
FS_UTIL.opfs.get().ok_or(WorkerError::Unexpected)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn download_db() -> Result<DownloadDbResponse> {
|
async fn download_db() -> Result<DownloadDbResponse> {
|
||||||
with_worker(|worker| {
|
with_worker(|worker| {
|
||||||
let filename = &worker.open_options.filename;
|
let filename = &worker.open_options.filename;
|
||||||
let db = if worker.open_options.persist {
|
let db = if worker.open_options.persist {
|
||||||
@@ -90,34 +117,42 @@ pub async fn download_db() -> Result<DownloadDbResponse> {
|
|||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn load_db(options: LoadDbOptions) -> Result<()> {
|
async fn load_db(options: LoadDbOptions) -> Result<()> {
|
||||||
let db = copy_to_vec(&options.data);
|
let db = copy_to_vec(&options.data);
|
||||||
|
|
||||||
|
#[cfg(feature = "sqlite3")]
|
||||||
|
sqlite_wasm_rs::utils::check_import_db(&db)
|
||||||
|
.map_err(|err| WorkerError::LoadDb(format!("{err}")))?;
|
||||||
|
|
||||||
with_worker(|worker| {
|
with_worker(|worker| {
|
||||||
let _ = std::mem::replace(&mut worker.state, SQLiteState::NotOpened);
|
drop(std::mem::replace(&mut worker.state, SQLiteState::NotOpened));
|
||||||
|
|
||||||
let filename = &worker.open_options.filename;
|
let filename = &worker.open_options.filename;
|
||||||
if worker.open_options.persist {
|
if worker.open_options.persist {
|
||||||
let opfs = get_opfs_util()?;
|
let opfs = get_opfs_util()?;
|
||||||
opfs.unlink(filename).map_err(|_| WorkerError::Unexpected)?;
|
opfs.unlink(filename).map_err(|_| WorkerError::Unexpected)?;
|
||||||
if let Err(err) = opfs.import_db(filename, &db) {
|
|
||||||
|
if let Err(err) = opfs.import_db_unchecked(filename, &db) {
|
||||||
return Err(WorkerError::LoadDb(format!("{err}")));
|
return Err(WorkerError::LoadDb(format!("{err}")));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let mem_vfs = &FS_UTIL.mem;
|
let mem_vfs = &FS_UTIL.mem;
|
||||||
mem_vfs.delete_db(filename);
|
mem_vfs.delete_db(filename);
|
||||||
if let Err(err) = mem_vfs.import_db(filename, &db) {
|
if let Err(err) = mem_vfs.import_db_unchecked(filename, &db, 114514 /* unused */) {
|
||||||
return Err(WorkerError::LoadDb(format!("{err}")));
|
return Err(WorkerError::LoadDb(format!("{err}")));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
worker.state = SQLiteState::Opened(SQLiteDb::open(&worker.open_options.uri())?);
|
worker.state = SQLiteState::Opened(SQLiteDb::open(&uri(
|
||||||
|
&worker.open_options.filename,
|
||||||
|
worker.open_options.persist,
|
||||||
|
))?);
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn open(options: OpenOptions) -> Result<()> {
|
async fn open(options: OpenOptions) -> Result<()> {
|
||||||
let mut locker = DB.lock().await;
|
let mut locker = DB.lock().await;
|
||||||
locker.take();
|
locker.take();
|
||||||
|
|
||||||
@@ -125,7 +160,7 @@ pub async fn open(options: OpenOptions) -> Result<()> {
|
|||||||
init_opfs_util().await?;
|
init_opfs_util().await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let state = SQLiteState::Opened(SQLiteDb::open(&options.uri())?);
|
let state = SQLiteState::Opened(SQLiteDb::open(&uri(&options.filename, options.persist))?);
|
||||||
let worker = SQLiteWorker {
|
let worker = SQLiteWorker {
|
||||||
open_options: options,
|
open_options: options,
|
||||||
state,
|
state,
|
||||||
@@ -134,10 +169,10 @@ pub async fn open(options: OpenOptions) -> Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn run(options: RunOptions) -> Result<SQLiteRunResult> {
|
async fn run(options: RunOptions) -> Result<SQLiteRunResult> {
|
||||||
with_worker(|worker| {
|
with_worker(|worker| {
|
||||||
if options.clear_on_prepare {
|
if options.clear_on_prepare {
|
||||||
let _ = std::mem::replace(&mut worker.state, SQLiteState::NotOpened);
|
drop(std::mem::replace(&mut worker.state, SQLiteState::NotOpened));
|
||||||
|
|
||||||
let filename = &worker.open_options.filename;
|
let filename = &worker.open_options.filename;
|
||||||
if worker.open_options.persist {
|
if worker.open_options.persist {
|
||||||
@@ -149,7 +184,10 @@ pub async fn run(options: RunOptions) -> Result<SQLiteRunResult> {
|
|||||||
mem_vfs.delete_db(filename);
|
mem_vfs.delete_db(filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
worker.state = SQLiteState::Opened(SQLiteDb::open(&worker.open_options.uri())?);
|
worker.state = SQLiteState::Opened(SQLiteDb::open(&uri(
|
||||||
|
&worker.open_options.filename,
|
||||||
|
worker.open_options.persist,
|
||||||
|
))?);
|
||||||
}
|
}
|
||||||
match &worker.state {
|
match &worker.state {
|
||||||
SQLiteState::NotOpened => Err(WorkerError::InvaildState),
|
SQLiteState::NotOpened => Err(WorkerError::InvaildState),
|
||||||
@@ -165,3 +203,38 @@ pub async fn run(options: RunOptions) -> Result<SQLiteRunResult> {
|
|||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn execute_task(scope: DedicatedWorkerGlobalScope, mut rx: UnboundedReceiver<JsValue>) {
|
||||||
|
while let Some(request) = rx.recv().await {
|
||||||
|
let request = serde_wasm_bindgen::from_value::<WorkerRequest>(request).unwrap();
|
||||||
|
let resp = match request {
|
||||||
|
WorkerRequest::Open(options) => WorkerResponse::Open(open(options).await),
|
||||||
|
WorkerRequest::Run(options) => WorkerResponse::Run(run(options).await),
|
||||||
|
WorkerRequest::LoadDb(options) => WorkerResponse::LoadDb(load_db(options).await),
|
||||||
|
WorkerRequest::DownloadDb => WorkerResponse::DownloadDb(download_db().await),
|
||||||
|
};
|
||||||
|
if let Err(err) = scope.post_message(&serde_wasm_bindgen::to_value(&resp).unwrap()) {
|
||||||
|
log::error!("Failed to send task to window: {resp:?}, {err:?}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn entry() {
|
||||||
|
console_error_panic_hook::set_once();
|
||||||
|
console_log::init_with_level(log::Level::Debug).unwrap();
|
||||||
|
|
||||||
|
let (tx, rx) = tokio::sync::mpsc::unbounded_channel::<JsValue>();
|
||||||
|
|
||||||
|
let scope: DedicatedWorkerGlobalScope = JsValue::from(js_sys::global()).into();
|
||||||
|
spawn_local(execute_task(scope.clone(), rx));
|
||||||
|
|
||||||
|
let on_message = Closure::<dyn Fn(MessageEvent)>::new(move |ev: MessageEvent| {
|
||||||
|
tx.send(ev.data()).unwrap();
|
||||||
|
});
|
||||||
|
|
||||||
|
scope.set_onmessage(Some(on_message.as_ref().unchecked_ref()));
|
||||||
|
scope
|
||||||
|
.post_message(&serde_wasm_bindgen::to_value(&WorkerResponse::Ready).unwrap())
|
||||||
|
.expect("Faild to send ready to window");
|
||||||
|
on_message.forget();
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user