Skip to content

Commit

Permalink
Merge pull request #1 from photovoltex/trait-predefinition
Browse files Browse the repository at this point in the history
Trait definitions
  • Loading branch information
photovoltex authored Feb 20, 2024
2 parents da15cf4 + 7673d57 commit 9abdd47
Show file tree
Hide file tree
Showing 19 changed files with 403 additions and 196 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ name: Rust

on:
push:
branches: [ "main" ]
branches: [ "main", "v[0-9]+.[0-9]+.[0-9]+*" ]
pull_request:
branches: [ "main" ]
branches: [ "main", "v[0-9]+.[0-9]+.[0-9]+*" ]

env:
CARGO_TERM_COLOR: always
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
.idea/
target/
dist/
24 changes: 7 additions & 17 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

25 changes: 17 additions & 8 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[workspace]
members = [ "tauri-interop-macro" ]
package.edition = "2021"
package.version = "1.3.0"
package.version = "2.0.0-dev"
package.keywords = [ "wasm", "tauri", "command", "event", "leptos" ]
package.authors = [ "photovoltex" ]
package.repository = "https://github.com/photovoltex/tauri-interop.git"
Expand All @@ -20,8 +20,8 @@ description = "Easily connect your rust frontend and backend without writing dup
readme = "README.md"

[dependencies]
# tauri-interop-macro = { path = "./tauri-interop-macro" }
tauri-interop-macro = "1.3.0"
tauri-interop-macro = { path = "./tauri-interop-macro" }
#tauri-interop-macro = "2.0.0"

js-sys = "0.3.65"
serde = { version = "1.0.193", features = ["derive"] }
Expand All @@ -34,12 +34,21 @@ log = "0.4.20"
# leptos feature
leptos = { version = "0.5.2", optional = true }

[dev-dependencies]
# comment when target is set to wasm in .cargo/config.toml
# only include if not wasm
[target.'cfg(not(target_family = "wasm"))'.dependencies]
tauri = { version = "1.5.2", default-features = false, features = ["wry"] }

[target.'cfg(target_family = "wasm")'.dependencies]
tauri-interop-macro = { path = "./tauri-interop-macro", features = [ "wasm" ] }
# tauri-interop-macro = { version = "1.2.0", features = [ "wasm" ] }

[target.'cfg(not(target_family = "wasm"))'.dev-dependencies]
tauri = "1.5.2"

[features]
default = [ "listen" ]
wasm = []
listen = [ "tauri-interop-macro/listen" ]
# todo: remove default feature before publish
default = [ "event" ]
event = [ "tauri-interop-macro/event" ]
leptos = [ "dep:leptos", "tauri-interop-macro/leptos" ]
# used to generated the missing documentation only generated for "target_family = wasm"
wasm = []
55 changes: 35 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ fn main() {
- all arguments with type "State", "AppHandle" and "Window" are removed automatically
> the current implementation relies on the name of the type and can not separate between a
> tauri::State and a self defined "State" struct
- asynchron commands are values as is see [async-commands](https://tauri.app/v1/guides/features/command#async-commands) for a detail explanation
- asynchronous commands are values as is seen [async-commands](https://tauri.app/v1/guides/features/command#async-commands) for a detail explanation

```rust , ignore-wasm32-unknown-unknown
// let _: () = trigger_something();
Expand All @@ -85,17 +85,17 @@ fn wait_for_sync_execution(value: &str) -> String {
format!("Has to wait that the backend completes the computation and returns the {value}")
}

// let result: Result<String, String> = asynchrone_execution(true).await;
// let result: Result<String, String> = asynchronous_execution(true).await;
#[tauri_interop::command]
async fn asynchrone_execution(change: bool) -> Result<String, String> {
async fn asynchronous_execution(change: bool) -> Result<String, String> {
if change {
Ok("asynchrone execution requires result definition".into())
Ok("asynchronous execution requires result definition".into())
} else {
Err("and ".into())
}
}

// let _wait_for_completion: () = asynchrone_execution(true).await;
// let _wait_for_completion: () = asynchronous_execution(true).await;
#[tauri_interop::command]
async fn heavy_computation() {
std::thread::sleep(std::time::Duration::from_millis(5000))
Expand All @@ -104,37 +104,44 @@ async fn heavy_computation() {

### Event (Backend => Frontend Communication)
Definition for both tauri supported triplet and wasm:
```rust , ignore-wasm32-unknown-unknown
#[derive(Default)]
#[tauri_interop::emit_or_listen]
```rust
use tauri_interop::Event;

#[derive(Default, Event)]
pub struct Test {
foo: String,
pub bar: bool,
}

// when main isn't defined, `super::Test` results in an error
fn main() {}
```

Using `tauri_interop::emit_or_listen` does provides the command with two macros,
which are used depending on the `target_family`
- `tauri_interop::listen_to` is used when compiling to `wasm`
- `tauri_interop::emit` is used otherwise
- derive trait `tauri_interop::Emit` is used otherwise

To emit a variable from the above struct (which is mostly intended to be used as state) in the host triplet
```rust , ignore-wasm32-unknown-unknown
#[derive(Default)]
#[tauri_interop::emit_or_listen]
use tauri_interop::Event;

#[derive(Default, Event)]
pub struct Test {
foo: String,
pub bar: bool,
}

// via `tauri_interop::Emit` a new module named after the struct (as snake_case)
// is created where the struct Test is defined, here it creates module `test`
// in this module the related Fields are generated

// one context where `tauri::AppHandle` can be obtained
#[tauri_interop::command]
fn emit_bar(handle: tauri::AppHandle) {
let mut test = Test::default();
let mut t = Test::default();

test.emit(&handle, TestEmit::Bar); // emits `false`
test.bar = true;
test.emit(&handle, TestEmit::Bar); // emits updated value `true`
t.emit::<test::Foo>(&handle); // emits the current state: `false`
}

// a different context where `tauri::AppHandle` can be obtained
Expand All @@ -143,10 +150,10 @@ fn main() {
.setup(|app| {
let handle: tauri::AppHandle = app.handle();

let mut test = Test::default();
let mut t = Test::default();

// to emit and update an field an update function for each field is generated
test.update_foo(&handle, "Bar".into()); // emits '"Bar"'
// to emit and update a field an update function for each field is generated
t.update::<test::Foo>(&handle, "Bar".into()); // assigns "Bar" to t.foo and emits the same value

Ok(())
});
Expand All @@ -161,7 +168,11 @@ pub struct Test {
pub bar: bool,
}

let listen_handle: ListenHandle<'_> = Test::listen_to_foo(|foo| { /* use received foo here */ }).await;
async fn main() {
use tauri_interop::event::listen::Listen;

let _listen_handle: ListenHandle<'_> = Test::listen_to::<test::Foo>(|foo| { /* use received foo: String here */ }).await;
}
```

The `liste_handle` contains the provided closure and the "unlisten" method. It has to be hold in scope as long
Expand All @@ -180,7 +191,11 @@ pub struct Test {
pub bar: bool,
}

let (foo: leptos::ReadSignal<String>, set_foo: leptos::WriteSignal<String>) = Test::use_foo(String::default());
fn main() {
use tauri_interop::event::listen::Listen;

let (foo: leptos::ReadSignal<String>, set_foo: leptos::WriteSignal<String>) = Test::use_field::<test::Foo>(String::default());
}
```

## Known Issues:
Expand Down
2 changes: 1 addition & 1 deletion src/bindings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ extern "C" {
#[wasm_bindgen(catch, js_name = "invoke", js_namespace = ["window", "__TAURI__", "tauri"])]
pub async fn invoke_catch(cmd: &str, args: JsValue) -> Result<JsValue, JsValue>;

#[cfg(feature = "listen")]
#[cfg(feature = "event")]
#[wasm_bindgen(catch, js_namespace = ["window", "__TAURI__", "event"])]
pub async fn listen(
event: &str,
Expand Down
4 changes: 2 additions & 2 deletions src/command.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use serde::Deserialize;
use wasm_bindgen::JsValue;

/// Wrapper for [crate::bindings::async_invoke], to return a
/// Wrapper for [crate::bindings::async_invoke], to return an
/// expected [Deserialize] object
pub async fn async_invoke<T>(command: &str, args: JsValue) -> T
where
Expand All @@ -11,7 +11,7 @@ where
serde_wasm_bindgen::from_value(value).expect("conversion error")
}

/// Wrapper for [crate::bindings::invoke_catch], to return a
/// Wrapper for [crate::bindings::invoke_catch], to return an
/// expected [Result<T, E>] where T and E is [Deserialize]
pub async fn invoke_catch<T, E>(command: &str, args: JsValue) -> Result<T, E>
where
Expand Down
50 changes: 50 additions & 0 deletions src/event.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
use serde::{Serialize, Deserialize};
#[cfg(not(target_family = "wasm"))]
use tauri::{AppHandle, Wry, Error};

/// traits for event emitting in the host code (feat: `tauri`)
#[cfg(not(target_family = "wasm"))]
pub mod emit;
/// related generic struct and functions for autogenerated listen functions (target: `wasm` or feat: `wasm`)
#[cfg(any(target_family = "wasm", feature = "wasm"))]
pub mod listen;

/// The trait which needs to be implemented for a [Field]
///
/// Conditionally changes between [listen::Listen] and [emit::Emit]
///
/// When compiled to "target_family = wasm" then following is true.
/// ```ignore
/// trait Parent = listen::Listen;
/// ```
#[cfg(not(target_family = "wasm"))]
pub trait Parent = emit::Emit;
/// The trait which needs to be implemented for a [Field]
///
/// Conditionally changes between [listen::Listen] and [emit::Emit]
#[cfg(target_family = "wasm")]
pub trait Parent = listen::Listen;

/// Trait defining a [Field] to a related struct implementing [Parent] with the related [Field::Type]
pub trait Field<P>
where
P: Parent,
<Self as Field<P>>::Type: Clone + Serialize + for<'de> Deserialize<'de>,
{
/// The type of the field
type Type;

#[cfg(any(target_family = "wasm", feature = "wasm"))]
/// The event of the field
const EVENT_NAME: &'static str;

#[cfg(not(target_family = "wasm"))]
/// Emits event of the related field with their value
fn emit(parent: &P, handle: &AppHandle<Wry>) -> Result<(), Error>;

#[cfg(not(target_family = "wasm"))]
/// Updates the related field and emit its event
///
/// Only required for "target_family = wasm"
fn update(s: &mut P, handle: &AppHandle<Wry>, v: Self::Type) -> Result<(), Error>;
}
23 changes: 23 additions & 0 deletions src/event/emit.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
use tauri::{AppHandle, Error, Wry};

use super::Field;

/// Trait that defines the available event emitting methods
pub trait Emit {
/// Emit all field events
fn emit_all(&self, handle: &AppHandle<Wry>) -> Result<(), Error>;

/// Emit a single field event
fn emit<F: Field<Self>>(&self, handle: &AppHandle<Wry>) -> Result<(), Error>
where
Self: Sized + Emit;

/// Update a single field and emit it afterward
fn update<F: Field<Self>>(
&mut self,
handle: &AppHandle<Wry>,
field: F::Type,
) -> Result<(), Error>
where
Self: Sized + Emit;
}
31 changes: 31 additions & 0 deletions src/listen.rs → src/event/listen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ use wasm_bindgen::{closure::Closure, JsCast, JsValue};
#[cfg(feature = "leptos")]
use leptos::{ReadSignal, WriteSignal};

use super::Field;

/// The result type that is returned by [ListenHandle::register]
pub type ListenResult<'s> = Result<ListenHandle<'s>, ListenError>;

Expand Down Expand Up @@ -112,3 +114,32 @@ impl<'s> ListenHandle<'s> {
(signal, set_signal)
}
}


/// Trait that defines the available listen methods
pub trait Listen {
/// Registers an callback to a [Field]
///
/// Default Implementation: see [ListenHandle::register]
fn listen_to<'r, F: Field<Self>>(
callback: impl Fn(F::Type) + 'static,
) -> impl std::future::Future<Output = ListenResult<'r>>
where
Self: Sized + super::Parent,
{
ListenHandle::register(F::EVENT_NAME, callback)
}

/// Creates a signal to a [Field]
///
/// Default Implementation: see [ListenHandle::use_register]
#[cfg(feature = "leptos")]
fn use_field<F: Field<Self>>(
initial: F::Type,
) -> (ReadSignal<F::Type>, WriteSignal<F::Type>)
where
Self: Sized + super::Parent,
{
ListenHandle::use_register(F::EVENT_NAME, initial)
}
}
Loading

0 comments on commit 9abdd47

Please sign in to comment.