feat: improve DX of events (#12)

* feat: improve DX of events

* Update global_shortcut.rs

* Update event.rs

* deploy docs to gh pages

* Delete rustdoc.yml

* add tests for global shortcut

* improve logs produced by tauri_log

* wip docs

* update docs

* move error to separate module

* feat: simplify functions returning array backed iterators

* rebase and cleanup

* fixes
This commit is contained in:
Jonas Kruckenberg 2022-11-19 20:33:06 +01:00 committed by GitHub
parent 300fe18d22
commit e28a0bb749
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 655 additions and 289 deletions

View file

@ -12,6 +12,8 @@ console_error_panic_hook = "0.1.7"
wasm-bindgen-futures = "0.4.32"
serde = { version = "1.0.147", features = ["derive"] }
log = { version = "0.4.17", features = ["serde"] }
futures = "0.3.25"
gloo-timers = { version = "0.2.4", features = ["futures"] }
[features]
ci = []

View file

@ -3,5 +3,6 @@
<head>
<meta charset="utf-8" />
<title>Tauri + Yew App</title>
<link data-trunk rel="css" href="./styles.css">
</head>
</html>

View file

@ -4,7 +4,7 @@
)]
use std::sync::atomic::{AtomicBool, Ordering};
use tauri::{Manager, State};
use tauri::{Manager, Runtime, State, Window};
use tauri_plugin_log::{LogTarget, LoggerBuilder};
struct Received(AtomicBool);
@ -14,6 +14,22 @@ fn verify_receive(emitted: State<Received>) -> bool {
emitted.0.load(Ordering::Relaxed)
}
#[tauri::command]
async fn emit_event<R: Runtime>(win: Window<R>) -> Result<(), ()> {
let _ = win.emit("rust-event-once", "Hello World from Rust!");
Ok(())
}
#[tauri::command]
async fn emit_event_5_times<R: Runtime>(win: Window<R>) -> Result<(), ()> {
for i in 0..5 {
let _ = win.emit("rust-event-listen", i);
}
Ok(())
}
#[tauri::command]
fn exit_with_error(e: &str) -> bool {
eprintln!("{}", e);
@ -35,12 +51,12 @@ fn main() {
tauri::Builder::default()
.plugin(log_plugin)
.invoke_handler(tauri::generate_handler![verify_receive, exit_with_error])
.invoke_handler(tauri::generate_handler![verify_receive, emit_event, emit_event_5_times, exit_with_error])
.setup(|app| {
app.manage(Received(AtomicBool::new(false)));
let app_handle = app.handle();
app.listen_global("foo", move |_| {
app.listen_global("javascript-event", move |_| {
app_handle
.state::<Received>()
.0

View file

@ -53,7 +53,7 @@ pub async fn pick_files() -> anyhow::Result<()> {
.await?;
ensure!(file.is_some());
ensure!(file.unwrap().len() > 1);
ensure!(file.unwrap().count() > 1);
Ok(())
}
@ -76,7 +76,7 @@ pub async fn pick_folders() -> anyhow::Result<()> {
.await?;
ensure!(file.is_some());
ensure!(file.unwrap().len() > 1);
ensure!(file.unwrap().count() > 1);
Ok(())
}

View file

@ -1,10 +1,38 @@
use anyhow::ensure;
use futures::StreamExt;
use tauri_sys::{event, tauri};
pub async fn emit() -> anyhow::Result<()> {
event::emit("foo", &"bar").await?;
event::emit("javascript-event", &"bar").await?;
ensure!(tauri::invoke::<_, bool>("verify_receive", &()).await?);
Ok(())
}
pub async fn listen() -> anyhow::Result<()> {
let events = event::listen::<u32>("rust-event-listen").await?;
tauri::invoke::<_, ()>("emit_event_5_times", &()).await?;
let events: Vec<u32> = events
.take(5)
.map(|e| e.payload)
.collect()
.await;
ensure!(events == vec![0, 1, 2, 3, 4]);
Ok(())
}
pub async fn once() -> anyhow::Result<()> {
// this causes enough delay for `once` to register it's event listener before the event gets triggered
wasm_bindgen_futures::spawn_local(async {
tauri::invoke::<_, ()>("emit_event", &()).await.unwrap();
});
let event = event::once::<String>("rust-event-once").await?;
ensure!(event.payload == "Hello World from Rust!");
Ok(())
}

View file

@ -0,0 +1,31 @@
use std::time::Duration;
use futures::StreamExt;
use tauri_sys::global_shortcut;
pub async fn register_all() -> anyhow::Result<()> {
let task = async {
let shortcuts = ["CommandOrControl+Shift+C", "Ctrl+Alt+F12"];
let streams = futures::future::try_join_all(shortcuts.map(|s| async move {
let stream = global_shortcut::register(s).await?;
anyhow::Ok(stream.map(move |_| s))
}))
.await?;
let mut events = futures::stream::select_all(streams);
while let Some(shortcut) = events.next().await {
log::debug!("Shortcut {} triggered", shortcut)
}
anyhow::Ok(())
};
let timeout = gloo_timers::future::sleep(Duration::from_secs(20));
futures::future::select(Box::pin(task), timeout).await;
Ok(())
}

View file

@ -6,6 +6,7 @@ mod notification;
mod os;
mod tauri_log;
mod window;
mod global_shortcut;
extern crate console_error_panic_hook;
use log::LevelFilter;
@ -39,7 +40,7 @@ where
}
#[component]
pub async fn Test<'a, G: Html, F>(cx: Scope<'a>, props: TestProps<'a, F>) -> View<G>
pub async fn TestInner<'a, G: Html, F>(cx: Scope<'a>, props: TestProps<'a, F>) -> View<G>
where
F: Future<Output = anyhow::Result<()>> + 'a,
{
@ -64,9 +65,30 @@ where
}
}
#[component]
pub fn Test<'a, G: Html, F>(cx: Scope<'a>, props: TestProps<'a, F>) -> View<G>
where
F: Future<Output = anyhow::Result<()>> + 'a,
{
let fallback = view! { cx,
tr {
td { code { (props.name.to_string()) } }
td {
span(class="loader") { "" }
}
}
};
view! { cx,
Suspense(fallback=fallback) {
TestInner(name=props.name, test=props.test)
}
}
}
#[cfg(not(feature = "ci"))]
#[component]
pub async fn InteractiveTest<'a, G: Html, F>(cx: Scope<'a>, props: TestProps<'a, F>) -> View<G>
pub fn InteractiveTest<'a, G: Html, F>(cx: Scope<'a>, props: TestProps<'a, F>) -> View<G>
where
F: Future<Output = anyhow::Result<()>> + 'a,
{
@ -81,19 +103,8 @@ where
(if *render_test.get() {
let test = test.take().unwrap();
let fallback = view! { cx,
tr {
td { code { (props.name.to_string()) } }
td {
"Running Test..."
}
}
};
view! { cx,
Suspense(fallback=fallback) {
Test(name=props.name, test=test)
}
Test(name=props.name, test=test)
}
} else {
view! { cx,
@ -148,12 +159,14 @@ fn main() {
view! { cx,
table {
tbody {
Suspense(fallback=view!{ cx, "Running Tests..." }) {
// Suspense(fallback=view!{ cx, "Running Tests..." }) {
Test(name="app::get_name",test=app::get_name())
Test(name="app::get_version",test=app::get_version())
Test(name="app::get_tauri_version",test=app::get_tauri_version())
Test(name="clipboard::read_text | clipboard::write_text",test=clipboard::test())
Test(name="event::emit",test=event::emit())
Test(name="event::listen",test=event::listen())
Test(name="event::once",test=event::once())
InteractiveTest(name="dialog::message",test=dialog::message())
InteractiveTest(name="dialog::ask",test=dialog::ask())
InteractiveTest(name="dialog::confirm",test=dialog::confirm())
@ -170,11 +183,12 @@ fn main() {
Test(name="notification::is_permission_granted",test=notification::is_permission_granted())
Test(name="notification::request_permission",test=notification::request_permission())
InteractiveTest(name="notification::show_notification",test=notification::show_notification())
InteractiveTest(name="global_shortcut::register_all",test=global_shortcut::register_all())
// Test(name="window::WebviewWindow::new",test=window::create_window())
Terminate
}
// }
}
}
}

View file

@ -6,6 +6,7 @@ use tauri_sys::tauri;
struct LogArgs {
level: Level,
message: String,
location: String,
file: Option<String>,
line: Option<u32>,
}
@ -56,6 +57,7 @@ impl log::Log for TauriLogger {
if self.enabled(record.metadata()) {
let args = LogArgs {
level: record.level().into(),
location: record.target().to_string(),
message: format!("{}", record.args()),
file: record.file().map(ToString::to_string),
line: record.line(),

15
examples/test/styles.css Normal file
View file

@ -0,0 +1,15 @@
.loader {
transform-origin: baseline;
display: inline-block;
box-sizing: border-box;
animation: rotation 1.3s linear infinite;
}
@keyframes rotation {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}