dioxus_document/
document.rsuse std::sync::Arc;
use super::*;
pub type DocumentContext = Arc<dyn Document>;
fn format_string_for_js(s: &str) -> String {
let escaped = s
.replace('\\', "\\\\")
.replace('\n', "\\n")
.replace('\r', "\\r")
.replace('"', "\\\"");
format!("\"{escaped}\"")
}
fn format_attributes(attributes: &[(&str, String)]) -> String {
let mut formatted = String::from("[");
for (key, value) in attributes {
formatted.push_str(&format!(
"[{}, {}],",
format_string_for_js(key),
format_string_for_js(value)
));
}
if formatted.ends_with(',') {
formatted.pop();
}
formatted.push(']');
formatted
}
fn create_element_in_head(
tag: &str,
attributes: &[(&str, String)],
children: Option<String>,
) -> String {
let helpers = include_str!("./js/head.js");
let attributes = format_attributes(attributes);
let children = children
.as_deref()
.map(format_string_for_js)
.unwrap_or("null".to_string());
let tag = format_string_for_js(tag);
format!(r#"{helpers};window.createElementInHead({tag}, {attributes}, {children});"#)
}
pub trait Document: 'static {
fn eval(&self, js: String) -> Eval;
fn set_title(&self, title: String) {
self.eval(format!("document.title = {title:?};"));
}
fn create_head_element(
&self,
name: &str,
attributes: &[(&str, String)],
contents: Option<String>,
) {
self.eval(create_element_in_head(name, attributes, contents));
}
fn create_meta(&self, props: MetaProps) {
let attributes = props.attributes();
self.create_head_element("meta", &attributes, None);
}
fn create_script(&self, props: ScriptProps) {
let attributes = props.attributes();
match (&props.src, props.script_contents()) {
(_, Ok(contents)) => self.create_head_element("script", &attributes, Some(contents)),
(Some(_), _) => self.create_head_element("script", &attributes, None),
(None, Err(err)) => err.log("Script"),
}
}
fn create_style(&self, props: StyleProps) {
let mut attributes = props.attributes();
match (&props.href, props.style_contents()) {
(_, Ok(contents)) => self.create_head_element("style", &attributes, Some(contents)),
(Some(_), _) => {
attributes.push(("type", "text/css".into()));
self.create_head_element("link", &attributes, None)
}
(None, Err(err)) => err.log("Style"),
};
}
fn create_link(&self, props: LinkProps) {
let attributes = props.attributes();
self.create_head_element("link", &attributes, None);
}
}
#[derive(Default)]
pub struct NoOpDocument;
impl Document for NoOpDocument {
fn eval(&self, _: String) -> Eval {
let owner = generational_box::Owner::default();
struct NoOpEvaluator;
impl Evaluator for NoOpEvaluator {
fn poll_join(
&mut self,
_: &mut std::task::Context<'_>,
) -> std::task::Poll<Result<serde_json::Value, EvalError>> {
std::task::Poll::Ready(Err(EvalError::Unsupported))
}
fn poll_recv(
&mut self,
_: &mut std::task::Context<'_>,
) -> std::task::Poll<Result<serde_json::Value, EvalError>> {
std::task::Poll::Ready(Err(EvalError::Unsupported))
}
fn send(&self, _data: serde_json::Value) -> Result<(), EvalError> {
Err(EvalError::Unsupported)
}
}
Eval::new(owner.insert(Box::new(NoOpEvaluator)))
}
}