Skip to content

Commit

Permalink
Merge pull request #28 from photovoltex/initialized_value
Browse files Browse the repository at this point in the history
Feature: Initial value
  • Loading branch information
photovoltex authored Mar 15, 2024
2 parents e625ed4 + a602b30 commit 33d338b
Show file tree
Hide file tree
Showing 16 changed files with 191 additions and 51 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,10 @@ jobs:
- name: Run tests for crate
run: |
cd tauri-interop-macro
cargo test
cargo test --features event,leptos,initial_value
- name: Run tests for crate
run: cargo test
run: cargo test --all-features

- name: Build test-project (wasm)
run: |
Expand Down
15 changes: 8 additions & 7 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
[workspace]
members = [ "tauri-interop-macro" ]
members = ["tauri-interop-macro"]
package.edition = "2021"
package.version = "2.0.0-dev"
package.keywords = [ "wasm", "tauri", "command", "event", "leptos" ]
package.authors = [ "photovoltex" ]
package.keywords = ["wasm", "tauri", "command", "event", "leptos"]
package.authors = ["photovoltex"]
package.repository = "https://github.com/photovoltex/tauri-interop.git"
package.license = "MIT OR Apache-2.0"

Expand Down Expand Up @@ -39,14 +39,15 @@ leptos = { version = "0.5", optional = true }
tauri = { version = "1.5", default-features = false, features = ["wry"] }

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

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

[features]
# todo: remove default feature before publish
default = [ "event" ]
event = [ "tauri-interop-macro/event" ]
leptos = [ "dep:leptos", "tauri-interop-macro/leptos" ]
default = ["event"]
event = ["tauri-interop-macro/event"]
initial_value = ["tauri-interop-macro/initial_value"]
leptos = ["dep:leptos", "tauri-interop-macro/leptos"]
2 changes: 1 addition & 1 deletion src/command/type_aliases.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use tauri::{AppHandle, State, Window};

#[allow(unused_imports)]
#[cfg(doc)]
use tauri_interop_macro::command;

/// Type alias to easier identify [State] via [command] macro
Expand Down
36 changes: 17 additions & 19 deletions src/event.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use serde::{de::DeserializeOwned, Serialize};
use serde::{de::DeserializeOwned, Deserialize, Serialize};
#[cfg(not(target_family = "wasm"))]
use tauri::{AppHandle, Error, Wry};

Expand All @@ -15,35 +15,25 @@ mod emit;
#[cfg(any(target_family = "wasm", doc))]
mod listen;

/// The trait which needs to be implemented for a [Field]
///
/// Conditionally changes between [Listen] and [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;

/// The trait which needs to be implemented for a [Field]
///
/// Conditionally changes between [Listen] and [Emit]
#[cfg(target_family = "wasm")]
pub trait Parent = 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 + DeserializeOwned,
Self::Type: Default + Clone + Serialize + DeserializeOwned,
{
/// The type of the field
type Type;

/// The event of the field
const EVENT_NAME: &'static str;

/// Tries to retrieve the current value from the backend
///
/// only in wasm available
#[allow(async_fn_in_trait)]
#[cfg(any(all(target_family = "wasm", feature = "initial_value"), doc))]
async fn get_value() -> Result<Self::Type, EventError>;

#[cfg(not(target_family = "wasm"))]
/// Emits event of the related field with their value
///
Expand All @@ -56,3 +46,11 @@ where
/// not in wasm available
fn update(s: &mut P, handle: &AppHandle<Wry>, v: Self::Type) -> Result<(), Error>;
}

/// General errors that can happen during event exchange
#[derive(Debug, Serialize, Deserialize, thiserror::Error)]
pub enum EventError {
/// The given name (struct) is not as tauri::State registered
#[error("{0} is not as tauri state registered")]
StateIsNotRegistered(String),
}
54 changes: 52 additions & 2 deletions src/event/emit.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,49 @@
use tauri::{AppHandle, Error, Wry};

use super::Field;
#[cfg(doc)]
use super::Listen;

/// The trait which needs to be implemented for a [Field]
///
/// Conditionally changes between [Listen] and [Emit] or [ManagedEmit]
///
/// - When compiled to "target_family = wasm", the trait alias is set to [Listen]
/// - When feature "initial_value" is enabled, the trait alias is set to [ManagedEmit]
/// - Otherwise the trait alias is set to [Emit]
#[cfg(any(not(feature = "initial_value"), doc))]
pub trait Parent = Emit;

/// The trait which needs to be implemented for a [Field]
#[cfg(all(feature = "initial_value", not(doc)))]
pub trait Parent = ManagedEmit;

/// Extension of [Emit] to additionally require [Self] to be managed by tauri
#[cfg(feature = "initial_value")]
pub trait ManagedEmit: Emit
where
Self: 'static,
{
/// Gets the value of a [Field] from [AppHandle]
///
/// The default implementation acquires [Self] directly. Override the provided
/// method when [Self] is not directly managed. For example, this could be the
/// case when the [interior mutability](https://doc.rust-lang.org/reference/interior-mutability.html)
/// pattern is used to allow mutation of [Self] while being managed by tauri.
fn get_value<F: Field<Self>>(
handle: &AppHandle,
get_field_value: impl Fn(&Self) -> F::Type,
) -> Option<F::Type>
where
Self: Sized + Send + Sync,
{
use tauri::Manager;

let state = handle.try_state::<Self>()?;
let state = get_field_value(&state);
Some(state)
}
}

/// Trait that defines the available event emitting methods
pub trait Emit {
Expand All @@ -16,6 +59,8 @@ pub trait Emit {
/// foo: String,
/// pub bar: bool,
/// }
///
/// impl tauri_interop::event::ManagedEmit for Test {}
///
/// #[tauri_interop::command]
/// fn emit_bar(handle: TauriAppHandle) {
Expand All @@ -39,6 +84,8 @@ pub trait Emit {
/// pub bar: bool,
/// }
///
/// impl tauri_interop::event::ManagedEmit for Test {}
///
/// #[tauri_interop::command]
/// fn emit_bar(handle: TauriAppHandle) {
/// Test::default().emit::<test::Foo>(&handle).expect("emitting failed");
Expand All @@ -48,7 +95,7 @@ pub trait Emit {
/// ```
fn emit<F: Field<Self>>(&self, handle: &AppHandle<Wry>) -> Result<(), Error>
where
Self: Sized + Emit;
Self: Sized + Parent;

/// Update a single field and emit it afterward
///
Expand All @@ -63,6 +110,9 @@ pub trait Emit {
/// pub bar: bool,
/// }
///
/// // require because we compile
/// impl tauri_interop::event::ManagedEmit for Test {}
///
/// #[tauri_interop::command]
/// fn emit_bar(handle: TauriAppHandle) {
/// Test::default().update::<test::Bar>(&handle, true).expect("emitting failed");
Expand All @@ -76,5 +126,5 @@ pub trait Emit {
field: F::Type,
) -> Result<(), Error>
where
Self: Sized + Emit;
Self: Sized + Parent;
}
34 changes: 26 additions & 8 deletions src/event/listen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,16 @@ use wasm_bindgen::{closure::Closure, JsCast, JsValue};

use crate::command::bindings::listen;

#[cfg(doc)]
use super::Emit;
use super::Field;

/// The trait which needs to be implemented for a [Field]
///
/// Conditionally changes between [Listen] and [Emit]
#[cfg(target_family = "wasm")]
pub trait Parent = Listen;

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

Expand Down Expand Up @@ -87,22 +95,32 @@ impl ListenHandle {

/// Registers a given event and binds a returned signal to these event changes
///
/// Providing [None] will unwrap into the default value. When feature `initial_value`
/// is enabled [None] will try to get the value from tauri.
///
/// Internally it stores a created [ListenHandle] for `event` in a [leptos::RwSignal] to hold it in
/// scope, while it is used in a leptos [component](https://docs.rs/leptos_macro/0.5.2/leptos_macro/attr.component.html)
#[cfg(feature = "leptos")]
pub fn use_register<T>(event: &'static str, initial_value: T) -> ReadSignal<T>
where
T: DeserializeOwned,
pub fn use_register<P, F: Field<P>>(initial_value: Option<F::Type>) -> ReadSignal<F::Type>
where P: Sized + super::Parent
{
use leptos::SignalSet;

let (signal, set_signal) = leptos::create_signal(initial_value);
let acquire_initial_value = initial_value.is_none();
let (signal, set_signal) = leptos::create_signal(initial_value.unwrap_or_default());

// creating this signal in a leptos component holds the value in scope, and drops it automatically
let handle = leptos::create_rw_signal(None);
leptos::spawn_local(async move {
let listen_handle = ListenHandle::register(event, move |value: T| {
log::trace!("update for {}", event);
if cfg!(feature = "initial_value") && acquire_initial_value {
match F::get_value().await {
Ok(value) => set_signal.set(value),
Err(why) => log::error!("{why}")
}
}

let listen_handle = ListenHandle::register(F::EVENT_NAME, move |value: F::Type| {
log::trace!("update for {}", F::EVENT_NAME);
set_signal.set(value)
})
.await
Expand Down Expand Up @@ -172,10 +190,10 @@ pub trait Listen {
/// }
/// ```
#[cfg(feature = "leptos")]
fn use_field<F: Field<Self>>(initial: F::Type) -> ReadSignal<F::Type>
fn use_field<F: Field<Self>>(initial: Option<F::Type>) -> ReadSignal<F::Type>
where
Self: Sized + super::Parent,
{
ListenHandle::use_register(F::EVENT_NAME, initial)
ListenHandle::use_register::<Self, F>(initial)
}
}
5 changes: 3 additions & 2 deletions tauri-interop-macro/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,13 @@ log = "^0.4"
serde = "^1.0"
# required because the intented usage is to use the main crate,
# for testing we need the reexported macros from tauri-interop
tauri-interop = { path = ".." }
tauri-interop = { path = "..", features = ["initial_value"] }

[features]
# todo: remove default feature before publish
default = [ "event" ]
leptos = [ "event" ]
event = []
leptos = []
initial_value = []
# feature to get info that context is wasm
_wasm = []
3 changes: 3 additions & 0 deletions tauri-interop-macro/src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ struct Field {
name: Ident,
attributes: FieldAttributes,
event_name: String,
get_cmd: Ident,
}

struct FieldAttributes {
Expand Down Expand Up @@ -117,10 +118,12 @@ fn prepare_field(derive_input: DeriveInput) -> Field {
let name = derive_input.ident.clone();
let attributes = get_field_values(derive_input.attrs);
let event_name = format!("{}::{}", &attributes.parent, &name);
let get_cmd = format_ident!("get_{}_{}", &attributes.parent, name);

Field {
event_name,
name,
attributes,
get_cmd
}
}
27 changes: 25 additions & 2 deletions tauri-interop-macro/src/event/emit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,21 @@ pub fn derive(stream: TokenStream) -> TokenStream {
});

let event_fields = fields.iter().map(|field| &field.field_name);
let commands_attr = cfg!(feature = "initial_value")
.then_some(quote!(#[::tauri_interop::commands]))
.unwrap_or_default();
let collect_command = cfg!(feature = "initial_value")
.then_some(quote!(::tauri_interop::collect_commands!();))
.unwrap_or_default();

let stream = quote! {
#commands_attr
pub mod #mod_name {
use super::#name;
use tauri_interop::event::{Field, Emit};

#( #emit_fields )*

#collect_command
}

impl ::tauri_interop::event::Emit for #name {
Expand Down Expand Up @@ -78,6 +86,7 @@ pub fn derive_field(stream: TokenStream) -> TokenStream {
name,
attributes,
event_name,
get_cmd,
} = super::prepare_field(derive_input);

let FieldAttributes {
Expand All @@ -90,8 +99,20 @@ pub fn derive_field(stream: TokenStream) -> TokenStream {
.as_ref()
.expect("name attribute was expected");

let get_cmd = cfg!(feature = "initial_value").then_some(quote! {
#[allow(non_snake_case)]
#[tauri_interop::command]
pub fn #get_cmd(handle: ::tauri::AppHandle) -> Result<#parent_field_ty, ::tauri_interop::event::EventError> {
use ::tauri::Manager;
use ::tauri_interop::event::{Field, ManagedEmit, EventError};

#parent::get_value::<#name>(&handle, |parent| parent.#parent_field_name.clone())
.ok_or(EventError::StateIsNotRegistered(stringify!(#parent).into()))
}
}).unwrap_or_default();

let stream = quote! {
impl Field<#parent> for #name {
impl ::tauri_interop::event::Field<#parent> for #name {
type Type = #parent_field_ty;

const EVENT_NAME: &'static str = #event_name;
Expand All @@ -109,6 +130,8 @@ pub fn derive_field(stream: TokenStream) -> TokenStream {
Self::emit(parent, handle)
}
}

#get_cmd
};

TokenStream::from(stream.to_token_stream())
Expand Down
Loading

0 comments on commit 33d338b

Please sign in to comment.