use alloc::boxed::Box;
#[cfg(not(feature = "static-locale"))]
use alloc::rc::Rc;
use alloc::vec::Vec;
use core::cell::RefCell;
use crate::line::{reactive_graph, tachys};
use mf2_catalog::Catalog;
use tachys::renderer::Rndr;
use tachys::renderer::types::{Element, Text};
use crate::leptos::catalog;
use crate::leptos::state::{self, TextUse};
use reactive_graph::effect::RenderEffect;
use reactive_graph::graph::{Subscriber, ToAnySubscriber, untrack};
use crate::leptos::text::{self, Stored};
pub(crate) const NONE: u32 = u32::MAX;
#[derive(Clone)]
pub(crate) enum Target {
Text(
Text,
#[cfg(feature = "mark-fallback-lang")] crate::leptos::lang::Wrapper,
),
Attribute(Element, Box<str>),
Property(Element, Box<str>),
}
impl Target {
fn text_use(&self) -> TextUse {
match self {
Target::Text(..) => TextUse::Displayed,
Target::Attribute(_, key) => state::attribute_use(key),
Target::Property(..) => TextUse::Plain,
}
}
fn write(&self, text: &str) {
match self {
Target::Text(node, ..) => Rndr::set_text(node, text),
Target::Attribute(el, key) => Rndr::set_attribute(el, key, text),
Target::Property(el, key) => {
Rndr::set_property_or_value(el, key, &wasm_bindgen::JsValue::from_str(text));
}
}
}
fn render(&self, desc: &Stored, catalog: &Catalog) {
#[cfg(feature = "mark-fallback-lang")]
if let Target::Text(node, wrapper) = self {
text::with_marked_text(desc, catalog, |text, lender| {
Rndr::set_text(node, text);
wrapper.fit(node, lender);
});
return;
}
text::with_text(desc, catalog, self.text_use(), |text| self.write(text));
}
#[cfg(all(feature = "hydrate", feature = "fn-datetime"))]
pub(crate) fn correct_zone(
&self,
desc: &Stored,
catalog: &Catalog,
page: mf2_runtime::TimeZone,
) {
let use_ = self.text_use();
let before = crate::leptos::zone::scoped(Some(page), || {
text::with_text(desc, catalog, use_, |t: &str| {
alloc::string::String::from(t)
})
});
text::with_text(desc, catalog, use_, |now| {
if now != before {
self.write(now);
}
});
}
}
#[cfg(any(
not(feature = "static-locale"),
all(feature = "hydrate", feature = "fn-datetime")
))]
#[cfg_attr(not(any(feature = "hydrate", feature = "csr")), allow(dead_code))]
pub(crate) trait Relocalize {
fn relocalize(&mut self, catalog: &Catalog);
}
enum Slot {
Value {
target: Target,
desc: Stored,
args: Option<ArgsEffect>,
},
#[cfg(not(feature = "static-locale"))]
Rich(
#[cfg_attr(not(any(feature = "hydrate", feature = "csr")), allow(dead_code))]
Rc<RefCell<dyn Relocalize>>,
),
}
impl Slot {
#[cfg_attr(
any(
not(any(feature = "hydrate", feature = "csr")),
all(feature = "csr", feature = "static-locale")
),
allow(dead_code)
)]
fn apply(&self, catalog: &Catalog) {
match self {
Slot::Value { target, desc, .. } => target.render(desc, catalog),
#[cfg(not(feature = "static-locale"))]
Slot::Rich(node) => {
if let Ok(mut node) = node.try_borrow_mut() {
node.relocalize(catalog);
}
}
}
}
}
enum Cell {
Free(u32),
Live(Slot),
}
struct Slab {
cells: Vec<Cell>,
free: u32,
live: u32,
}
impl Slab {
const fn new() -> Slab {
Slab {
cells: Vec::new(),
free: NONE,
live: 0,
}
}
fn insert(&mut self, slot: Slot) -> u32 {
self.live = self.live.saturating_add(1);
if let Some(cell) = usize::try_from(self.free)
.ok()
.and_then(|i| self.cells.get_mut(i))
&& let Cell::Free(next) = *cell
{
let index = self.free;
self.free = next;
*cell = Cell::Live(slot);
return index;
}
let Ok(index) = u32::try_from(self.cells.len()) else {
self.live = self.live.saturating_sub(1);
return NONE;
};
if self.cells.try_reserve(1).is_err() {
self.live = self.live.saturating_sub(1);
return NONE;
}
self.cells.push(Cell::Live(slot));
index
}
fn remove(&mut self, index: u32) {
let Ok(i) = usize::try_from(index) else {
return;
};
let free = self.free;
if let Some(cell @ Cell::Live(_)) = self.cells.get_mut(i) {
*cell = Cell::Free(free);
self.free = index;
self.live = self.live.saturating_sub(1);
}
}
fn get_mut(&mut self, index: u32) -> Option<&mut Slot> {
let i = usize::try_from(index).ok()?;
match self.cells.get_mut(i)? {
Cell::Live(slot) => Some(slot),
Cell::Free(_) => None,
}
}
}
std::thread_local! {
static NODES: RefCell<Slab> = const { RefCell::new(Slab::new()) };
}
pub(crate) fn insert(target: Target, desc: Stored) -> u32 {
#[cfg(feature = "static-locale")]
if !desc.has_source() {
return NONE;
}
let args = args_effect(&target, &desc);
add(Slot::Value { target, desc, args })
}
pub(crate) fn insert_hydrated(target: Target, desc: Stored) -> u32 {
#[cfg(all(feature = "hydrate", feature = "fn-datetime"))]
untrack(|| crate::leptos::zone::hydrated(&target, &desc));
insert(target, desc)
}
#[cfg(not(feature = "static-locale"))]
pub(crate) fn insert_rich(node: Rc<RefCell<dyn Relocalize>>) -> u32 {
add(Slot::Rich(node))
}
fn add(slot: Slot) -> u32 {
NODES.with(|slab| match slab.try_borrow_mut() {
Ok(mut slab) => slab.insert(slot),
Err(_) => NONE,
})
}
pub(crate) fn remove(index: u32) {
if index == NONE {
return;
}
NODES.with(|slab| {
if let Ok(mut slab) = slab.try_borrow_mut() {
slab.remove(index);
}
});
}
pub(crate) fn replace(index: &mut u32, target: impl FnOnce() -> Option<Target>, desc: Stored) {
untrack(move || replace_now(index, target, desc));
}
fn replace_now(index: &mut u32, target: impl FnOnce() -> Option<Target>, desc: Stored) {
let Some(catalog) = catalog::active() else {
return;
};
if *index == NONE {
#[cfg(feature = "static-locale")]
if let Some(target) = target() {
if desc.has_source() {
*index = insert(target, desc);
} else {
target.render(&desc, &catalog);
}
}
#[cfg(not(feature = "static-locale"))]
let _ = (target, desc);
return;
}
let index = *index;
NODES.with(|slab| {
if let Ok(mut slab) = slab.try_borrow_mut()
&& let Some(Slot::Value {
target,
desc: stored,
args,
}) = slab.get_mut(index)
{
*stored = desc;
*args = args_effect(target, stored);
target.render(stored, &catalog);
}
});
}
#[cfg(any(feature = "hydrate", feature = "csr"))]
#[cfg_attr(all(feature = "csr", feature = "static-locale"), allow(dead_code))]
pub(crate) fn relocalize_all() {
let Some(catalog) = catalog::active() else {
return;
};
NODES.with(|slab| {
if let Ok(slab) = slab.try_borrow() {
for cell in &slab.cells {
if let Cell::Live(slot) = cell {
slot.apply(&catalog);
}
}
}
});
}
#[must_use]
pub fn live_nodes() -> usize {
NODES.with(|slab| {
slab.try_borrow()
.map_or(0, |slab| usize::try_from(slab.live).unwrap_or(0))
})
}
struct ArgsEffect(RenderEffect<()>);
impl Drop for ArgsEffect {
fn drop(&mut self) {
let subscriber = self.0.to_any_subscriber();
subscriber.clear_sources(&subscriber);
}
}
fn args_effect(target: &Target, desc: &Stored) -> Option<ArgsEffect> {
if !desc.has_source() {
return None;
}
let target = target.clone();
let desc = desc.clone();
Some(ArgsEffect(RenderEffect::new(move |_| {
if let Some(catalog) = catalog::active() {
target.render(&desc, &catalog);
}
})))
}