108 lines
3.6 KiB
Rust
Raw Normal View History

2022-12-26 16:15:39 -05:00
// Copyright 2022 Jeremy Wall (Jeremy@marzhilsltudios.com)
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use std::{marker::PhantomData, rc::Rc};
2022-12-26 16:15:39 -05:00
use sycamore::prelude::*;
2022-12-26 17:05:11 -05:00
/// Trait that maps a message and an original state value to a new value.
/// Implementors of this trait can implement all of their state management
/// logic in one place.
pub trait MessageMapper<Msg, Val> {
fn map<'ctx>(&self, cx: Scope<'ctx>, msg: Msg, original: &'ctx Signal<Val>);
2022-12-26 16:15:39 -05:00
}
2022-12-26 17:05:11 -05:00
/// Provides the necessary wiring for a centralized state handling
/// mechanism. The API guides you along handling the lifetimes properly
/// as well as registering all the state management logic in one place.
2022-12-26 16:37:51 -05:00
pub struct Handler<'ctx, D, T, Msg>
2022-12-26 16:15:39 -05:00
where
2022-12-26 16:37:51 -05:00
D: MessageMapper<Msg, T>,
2022-12-26 16:15:39 -05:00
{
signal: &'ctx Signal<T>,
dispatcher: &'ctx D,
_phantom: PhantomData<Msg>,
}
2022-12-26 16:37:51 -05:00
impl<'ctx, D, T, Msg> Handler<'ctx, D, T, Msg>
2022-12-26 16:15:39 -05:00
where
2022-12-26 16:37:51 -05:00
D: MessageMapper<Msg, T>,
2022-12-26 16:15:39 -05:00
{
2022-12-26 17:05:11 -05:00
/// Constructs a new Handler with a lifetime anchored to the provided Scope.
/// You will usually construct this in your top level scope and then
/// pass the handlers down into your components.
2022-12-26 16:15:39 -05:00
pub fn new(cx: Scope<'ctx>, initial: T, dispatcher: D) -> &'ctx Self {
let signal = create_signal(cx, initial);
let dispatcher = create_ref(cx, dispatcher);
create_ref(
cx,
Self {
signal,
dispatcher,
_phantom: PhantomData,
},
)
}
2022-12-26 17:24:14 -05:00
/// Directly handle a state message without requiring a binding.
pub fn dispatch(&'ctx self, cx: Scope<'ctx>, msg: Msg) {
self.dispatcher.map(cx, msg, self.signal)
2022-12-26 16:15:39 -05:00
}
2022-12-26 17:05:11 -05:00
/// Provides a ReadSignal handle for the contained Signal implementation.
2022-12-26 16:37:51 -05:00
pub fn read_signal(&'ctx self) -> &'ctx ReadSignal<T> {
2022-12-26 16:15:39 -05:00
self.signal
}
2022-12-26 16:23:19 -05:00
2022-12-26 17:05:11 -05:00
/// Binds a triggering signal and associated message mapping function as
2022-12-29 12:30:19 -06:00
/// a state update for this Handler instance. This uses [`create_effect`]
/// so be aware that it will fire at least once when you first call it. It
/// is really easy to introduce an infinite signal update loop.
pub fn bind_trigger<F, Val>(
&'ctx self,
cx: Scope<'ctx>,
trigger: &'ctx ReadSignal<Val>,
message_fn: F,
) where
F: Fn(Rc<Val>) -> Msg + 'ctx,
2022-12-26 16:23:19 -05:00
{
create_effect(cx, move || self.dispatch(cx, message_fn(trigger.get())));
2022-12-26 16:23:19 -05:00
}
2022-12-26 17:05:11 -05:00
/// Helper method to get a memoized value derived from the contained
/// state for this Handler. The provided handler only notifies subscribers
/// If the state has actually been updated.
pub fn get_selector<F, Val>(
&'ctx self,
cx: Scope<'ctx>,
selector_factory: F,
) -> &'ctx ReadSignal<Val>
where
F: Fn(&'ctx ReadSignal<T>) -> Val + 'ctx,
Val: PartialEq,
{
create_selector(cx, move || selector_factory(self.signal))
}
// Helper method to get a non reactive value from the state.
pub fn get_value<F, Val>(&'ctx self, getter_factory: F) -> Val
where
F: Fn(&'ctx ReadSignal<T>) -> Val + 'ctx,
{
getter_factory(self.signal)
}
2022-12-26 16:15:39 -05:00
}
#[cfg(test)]
mod tests;