diff --git a/Cargo.lock b/Cargo.lock index 6d3c08e..1aa1158 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -108,6 +108,21 @@ dependencies = [ "web-sys", ] +[[package]] +name = "dynatos-title" +version = "0.1.0" +dependencies = [ + "anyhow", + "duplicate", + "dynatos-reactive", + "dynatos-util", + "extend", + "js-sys", + "tracing", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "dynatos-util" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 1ccd33e..227f3ff 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,7 @@ members = [ "dynatos-logger", "dynatos-reactive", "dynatos-router", + "dynatos-title", "dynatos-util", ] resolver = "2" @@ -20,6 +21,7 @@ dynatos-html = { path = "dynatos-html" } dynatos-logger = { path = "dynatos-logger" } dynatos-reactive = { path = "dynatos-reactive" } dynatos-router = { path = "dynatos-router" } +dynatos-title = { path = "dynatos-title" } dynatos-util = { path = "dynatos-util" } anyhow = "1.0.79" diff --git a/dynatos-title/Cargo.toml b/dynatos-title/Cargo.toml new file mode 100644 index 0000000..48f6dbf --- /dev/null +++ b/dynatos-title/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "dynatos-title" +version = "0.1.0" +edition = "2021" + +[dependencies] + +dynatos-reactive = { workspace = true } +dynatos-util = { workspace = true } + +anyhow = { workspace = true } +duplicate = { workspace = true } +extend = { workspace = true } +js-sys = { workspace = true } +tracing = { workspace = true } +wasm-bindgen = { workspace = true } +web-sys = { workspace = true } diff --git a/dynatos-title/src/lib.rs b/dynatos-title/src/lib.rs new file mode 100644 index 0000000..520bd79 --- /dev/null +++ b/dynatos-title/src/lib.rs @@ -0,0 +1,123 @@ +//! Title management for [`dynatos`] + +// TODO: It seems that titles aren't getting dropped for some reason. + +// Features +#![feature(lint_reasons)] + +// Imports +use {dynatos_util::ObjectSetProp, std::cell::RefCell, wasm_bindgen::prelude::wasm_bindgen}; + +thread_local! { + /// Title stack. + static TITLE_STACK: RefCell>> = RefCell::new(vec![]); +} + +/// Title. +/// +/// Sets the title for as long as this lives. +#[derive(Debug)] +pub struct Title { + /// Title index + title_idx: usize, +} + +impl Title { + /// Creates a title. + pub fn new(title: S) -> Self + where + S: Into, + { + let title = title.into(); + + // Set and add the title to the stack + self::set_title(&title); + let title_idx = TITLE_STACK.with_borrow_mut(|stack| { + // If no title exists, add the current one + if stack.is_empty() { + stack.push(Some(self::cur_title())); + } + + // Then add ours + let title_idx = stack.len(); + stack.push(Some(title)); + + title_idx + }); + + Self { title_idx } + } +} + +impl Drop for Title { + fn drop(&mut self) { + // Get the value + TITLE_STACK.with_borrow_mut(|stack| { + // Remove our title + let _prev_title = stack + .get_mut(self.title_idx) + .and_then(Option::take) + .expect("Title was already taken"); + + // Then find the next title to set back to. + let next_title = loop { + let last = stack.last().expect("Should contain at least 1 title"); + match last { + Some(title) => break title, + None => { + stack.pop().expect("Just checked the value existed"); + }, + } + }; + self::set_title(next_title); + }); + } +} + +/// Extension trait to attach a title to an object. +#[extend::ext(name = ObjectAttachTitle)] +pub impl js_sys::Object { + /// Attaches a title to this object + fn attach_title(&self, title: &str) { + let prop_name: &str = "__dynatos_title"; + let title = Title::new(title); + self.set_prop(prop_name, WasmTitle(title)); + } +} + +/// Extension trait to attach a title to an object. +#[extend::ext(name = ObjectWithTitle)] +pub impl T +where + T: AsRef, +{ + /// Attaches a title to this object. + /// + /// Returns the object, for chaining + fn with_title(self, title: &str) -> Self { + self.as_ref().attach_title(title); + self + } +} + +/// Returns the current title +fn cur_title() -> String { + web_sys::window() + .expect("Unable to get window") + .document() + .expect("Unable to get document") + .title() +} + +/// Sets the title +fn set_title(title: &str) { + web_sys::window() + .expect("Unable to get window") + .document() + .expect("Unable to get document") + .set_title(title); +} + +/// A wasm `Title` type. +#[wasm_bindgen] +struct WasmTitle(Title); diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 6ad96e4..174a47b 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -12,6 +12,7 @@ dynatos-html = { path = "../dynatos-html" } dynatos-logger = { path = "../dynatos-logger" } dynatos-reactive = { path = "../dynatos-reactive" } dynatos-router = { path = "../dynatos-router" } +dynatos-title = { path = "../dynatos-title" } dynatos-util = { path = "../dynatos-util" } anyhow = "1.0.79"