use std::collections::BTreeMap;
use std::fmt;
use std::fmt::Write;
use std::iter;
use std::sync::{Arc, Mutex};
use futures::channel::oneshot as sync_oneshot;
use crate::root_state::BounceStates;
use crate::Atom;
use super::state::{merge_helmet_states, HelmetState, HelmetTag};
use super::FormatTitle;
use yew::prelude::*;
pub struct StaticWriterInner {
tx: sync_oneshot::Sender<Vec<HelmetTag>>,
}
#[derive(Clone)]
pub struct StaticWriter {
inner: Arc<Mutex<Option<StaticWriterInner>>>,
}
impl PartialEq for StaticWriter {
fn eq(&self, other: &Self) -> bool {
Arc::ptr_eq(&self.inner, &other.inner)
}
}
impl Eq for StaticWriter {}
impl fmt::Debug for StaticWriter {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("StaticWriter").field("inner", &"_").finish()
}
}
impl StaticWriter {
pub(crate) fn send_helmet(
&self,
states: BounceStates,
format_title: Option<FormatTitle>,
default_title: Option<AttrValue>,
) {
let StaticWriterInner { tx } = match self.inner.lock().unwrap().take() {
Some(m) => m,
None => return,
};
let helmet_states = states.get_artifacts::<HelmetState>();
let tags = merge_helmet_states(&helmet_states, format_title.as_ref(), default_title);
let _ = tx.send(
tags.into_iter()
.map(|m| Arc::try_unwrap(m).unwrap_or_else(|e| (*e).clone()))
.collect::<Vec<_>>(),
);
}
}
#[derive(Debug)]
pub struct StaticRenderer {
rx: sync_oneshot::Receiver<Vec<HelmetTag>>,
}
impl StaticRenderer {
pub async fn render(self) -> Vec<HelmetTag> {
self.rx.await.expect("failed to receive value.")
}
}
impl HelmetTag {
fn write_attrs_from(
w: &mut dyn Write,
attrs: &BTreeMap<Arc<str>, Arc<str>>,
write_data_attr: bool,
) -> fmt::Result {
let mut data_tag_written = false;
for (index, (name, value)) in attrs
.iter()
.map(|(name, value)| (&**name, &**value))
.chain(iter::from_fn(|| {
(write_data_attr && !data_tag_written).then(|| {
data_tag_written = true;
("data-bounce-helmet", "pre-render")
})
}))
.enumerate()
{
if index > 0 {
write!(w, " ")?;
}
write!(
w,
r#"{}="{}""#,
name,
html_escape::decode_script_double_quoted_text(value)
)?;
}
Ok(())
}
pub fn write_attrs(&self, w: &mut dyn Write) -> fmt::Result {
match self {
Self::Title(_) => Ok(()),
Self::Body { attrs } | Self::Html { attrs } => Self::write_attrs_from(w, attrs, false),
Self::Meta { attrs }
| Self::Link { attrs }
| Self::Script { attrs, .. }
| Self::Style { attrs, .. }
| Self::Base { attrs } => Self::write_attrs_from(w, attrs, true),
}
}
pub fn write_static(&self, w: &mut dyn Write) -> fmt::Result {
match self {
Self::Title(m) => {
write!(w, "<title>{m}</title>")
}
Self::Script { content, attrs, .. } => {
write!(w, "<script ")?;
Self::write_attrs_from(w, attrs, true)?;
write!(w, ">{content}</script>")
}
Self::Style { content, attrs } => {
write!(w, "<style ")?;
Self::write_attrs_from(w, attrs, true)?;
write!(w, ">{content}</style>")
}
Self::Body { .. } => Ok(()),
Self::Html { .. } => Ok(()),
Self::Base { attrs } => {
write!(w, "<base ")?;
Self::write_attrs_from(w, attrs, true)?;
write!(w, ">")
}
Self::Link { attrs } => {
write!(w, "<link ")?;
Self::write_attrs_from(w, attrs, true)?;
write!(w, ">")
}
Self::Meta { attrs } => {
write!(w, "<meta ")?;
Self::write_attrs_from(w, attrs, true)?;
write!(w, ">")
}
}
}
}
#[derive(Atom, PartialEq, Default)]
pub(crate) struct StaticWriterState {
pub writer: Option<StaticWriter>,
pub default_title: Option<AttrValue>,
pub format_title: Option<FormatTitle>,
}
pub fn render_static() -> (StaticRenderer, StaticWriter) {
let (tx, rx) = sync_oneshot::channel();
(
StaticRenderer { rx },
StaticWriter {
inner: Arc::new(Mutex::new(Some(StaticWriterInner { tx }))),
},
)
}