stellation_backend/
hooks.rs

1//! Useful hooks for stellation backend.
2
3use std::cell::RefCell;
4use std::fmt;
5use std::rc::Rc;
6
7use bounce::{use_atom_value, Atom};
8use futures::future::LocalBoxFuture;
9use futures::{Future, FutureExt};
10use yew::prelude::*;
11
12type RenderAppendHead = Box<dyn FnOnce() -> LocalBoxFuture<'static, String>>;
13
14#[derive(Atom, Clone)]
15pub(crate) struct HeadContents {
16    inner: Rc<RefCell<Vec<RenderAppendHead>>>,
17}
18
19impl Default for HeadContents {
20    fn default() -> Self {
21        panic!("Attempting to use use_append_head_content on client side rendering!");
22    }
23}
24
25impl PartialEq for HeadContents {
26    // We never set this atom, so it will always be equal.
27    fn eq(&self, _other: &Self) -> bool {
28        true
29    }
30}
31
32impl Eq for HeadContents {}
33
34impl HeadContents {
35    pub(crate) fn new() -> Self {
36        Self {
37            inner: Rc::default(),
38        }
39    }
40
41    pub(crate) async fn render_into(&self, w: &mut dyn fmt::Write) {
42        for i in self.inner.take() {
43            let _ = write!(w, "{}", i().await);
44        }
45    }
46}
47
48/// A server-side hook that appends content to head element.
49/// This async function is resolved after the page is completed rendered and the returned string is
50/// appended at the location of the ` <!--%STELLATION_HEAD%-->` comment in `index.html`, after other
51/// contents.
52///
53/// # Warning
54///
55/// The content is not managed at the client side.
56/// This hook is used to facility specific crates such as a CSS-in-Rust solution.
57///
58/// If you wish to render content into the `<head>` element, you should use
59/// [`bounce::helmet::Helmet`].
60///
61///
62/// # Panics
63///
64/// This hook should be used by a server-side only component. Panics if used in client-side
65/// rendering.
66#[hook]
67pub fn use_append_head_content<F, Fut>(f: F)
68where
69    F: 'static + FnOnce() -> Fut,
70    Fut: 'static + Future<Output = String>,
71{
72    let boxed_f: RenderAppendHead = Box::new(move || f().boxed_local());
73    let head_contents = use_atom_value::<HeadContents>();
74
75    head_contents.inner.borrow_mut().push(boxed_f);
76}