use crate::collection::SharedCollection;
use crate::js::{Callback, Js, Shared};
use crate::transaction::YTransaction;
use crate::xml_frag::YXmlEvent;
use crate::ImplicitTransaction;
use gloo_utils::format::JsValueSerdeExt;
use std::collections::HashMap;
use std::iter::FromIterator;
use wasm_bindgen::prelude::wasm_bindgen;
use wasm_bindgen::JsValue;
use yrs::types::TYPE_REFS_XML_ELEMENT;
use yrs::{DeepObservable, GetString, Observable, Xml, XmlElementRef, XmlFragment};
pub(crate) struct PrelimXmElement {
pub name: String,
pub attributes: HashMap<String, String>,
pub children: Vec<JsValue>,
}
impl PrelimXmElement {
fn to_string(&self, txn: &ImplicitTransaction) -> crate::Result<String> {
let mut str = String::new();
for js in self.children.iter() {
let res = match Shared::from_ref(js)? {
Shared::XmlText(c) => c.to_string(txn),
Shared::XmlElement(c) => c.to_string(txn),
Shared::XmlFragment(c) => c.to_string(txn),
_ => return Err(JsValue::from_str(crate::js::errors::NOT_XML_TYPE)),
};
str.push_str(&res?);
}
Ok(str)
}
}
#[wasm_bindgen]
pub struct YXmlElement(pub(crate) SharedCollection<PrelimXmElement, XmlElementRef>);
impl YXmlElement {
pub(crate) fn try_parse_attrs(attributes: JsValue) -> Option<HashMap<String, String>> {
let mut map = HashMap::new();
if !attributes.is_undefined() || attributes.is_null() {
let object = js_sys::Object::from(attributes);
let entries = js_sys::Object::entries(&object);
for tuple in entries.iter() {
let tuple = js_sys::Array::from(&tuple);
let key: String = tuple.get(0).as_string()?;
let value = tuple.get(1).as_string()?;
map.insert(key, value);
}
}
Some(map)
}
pub(crate) fn parse_attrs(attributes: JsValue) -> crate::Result<HashMap<String, String>> {
match Self::try_parse_attrs(attributes) {
Some(attrs) => Ok(attrs),
None => Err(JsValue::from_str(crate::js::errors::INVALID_XML_ATTRS)),
}
}
}
#[wasm_bindgen]
impl YXmlElement {
#[wasm_bindgen(constructor)]
pub fn new(name: String, attributes: JsValue, children: JsValue) -> crate::Result<YXmlElement> {
let attributes = Self::parse_attrs(attributes)?;
let children = if children.is_undefined() || children.is_null() {
Vec::new()
} else {
let array = js_sys::Array::from(&children);
array.to_vec()
};
for child in children.iter() {
Js::assert_xml_prelim(child)?;
}
Ok(YXmlElement(SharedCollection::prelim(PrelimXmElement {
name,
attributes,
children,
})))
}
#[wasm_bindgen(getter, js_name = type)]
#[inline]
pub fn get_type(&self) -> u8 {
TYPE_REFS_XML_ELEMENT
}
#[wasm_bindgen(getter, js_name = id)]
#[inline]
pub fn id(&self) -> crate::Result<JsValue> {
self.0.id()
}
#[wasm_bindgen(getter)]
#[inline]
pub fn prelim(&self) -> bool {
self.0.is_prelim()
}
#[wasm_bindgen(js_name = alive)]
#[inline]
pub fn alive(&self, txn: &YTransaction) -> bool {
self.0.is_alive(txn)
}
#[wasm_bindgen(js_name = name)]
pub fn name(&self, txn: &ImplicitTransaction) -> crate::Result<String> {
match &self.0 {
SharedCollection::Prelim(c) => Ok(c.name.clone()),
SharedCollection::Integrated(c) => c.readonly(txn, |c, _| Ok(c.tag().to_string())),
}
}
#[wasm_bindgen(js_name = length)]
pub fn length(&self, txn: &ImplicitTransaction) -> crate::Result<u32> {
match &self.0 {
SharedCollection::Prelim(c) => Ok(c.children.len() as u32),
SharedCollection::Integrated(c) => c.readonly(txn, |c, txn| Ok(c.len(txn))),
}
}
#[wasm_bindgen(js_name = insert)]
pub fn insert(
&mut self,
index: u32,
xml_node: JsValue,
txn: ImplicitTransaction,
) -> crate::Result<()> {
Js::assert_xml_prelim(&xml_node)?;
match &mut self.0 {
SharedCollection::Prelim(c) => {
c.children.insert(index as usize, xml_node);
Ok(())
}
SharedCollection::Integrated(c) => c.mutably(txn, |c, txn| {
c.insert(txn, index, Js::new(xml_node));
Ok(())
}),
}
}
#[wasm_bindgen(js_name = push)]
pub fn push(&mut self, xml_node: JsValue, txn: ImplicitTransaction) -> crate::Result<()> {
Js::assert_xml_prelim(&xml_node)?;
match &mut self.0 {
SharedCollection::Prelim(c) => {
c.children.push(xml_node);
Ok(())
}
SharedCollection::Integrated(c) => c.mutably(txn, |c, txn| {
c.push_back(txn, Js::new(xml_node));
Ok(())
}),
}
}
#[wasm_bindgen(method, js_name = delete)]
pub fn delete(
&mut self,
index: u32,
length: Option<u32>,
txn: ImplicitTransaction,
) -> crate::Result<()> {
let length = length.unwrap_or(1);
match &mut self.0 {
SharedCollection::Prelim(c) => {
c.children
.drain((index as usize)..((index + length) as usize));
Ok(())
}
SharedCollection::Integrated(c) => c.mutably(txn, |c, txn| {
c.remove_range(txn, index, length);
Ok(())
}),
}
}
#[wasm_bindgen(js_name = firstChild)]
pub fn first_child(&self, txn: &ImplicitTransaction) -> crate::Result<JsValue> {
match &self.0 {
SharedCollection::Prelim(c) => {
Ok(c.children.first().cloned().unwrap_or(JsValue::UNDEFINED))
}
SharedCollection::Integrated(c) => c.readonly(txn, |c, txn| match c.first_child() {
None => Ok(JsValue::UNDEFINED),
Some(xml) => Ok(Js::from_xml(xml, txn.doc().clone()).into()),
}),
}
}
#[wasm_bindgen(js_name = nextSibling)]
pub fn next_sibling(&self, txn: &ImplicitTransaction) -> crate::Result<JsValue> {
match &self.0 {
SharedCollection::Prelim(_) => {
Err(JsValue::from_str(crate::js::errors::INVALID_PRELIM_OP))
}
SharedCollection::Integrated(c) => c.readonly(txn, |c, txn| {
let next = c.siblings(txn).next();
match next {
Some(node) => Ok(Js::from_xml(node, txn.doc().clone()).into()),
None => Ok(JsValue::UNDEFINED),
}
}),
}
}
#[wasm_bindgen(js_name = prevSibling)]
pub fn prev_sibling(&self, txn: &ImplicitTransaction) -> crate::Result<JsValue> {
match &self.0 {
SharedCollection::Prelim(_) => {
Err(JsValue::from_str(crate::js::errors::INVALID_PRELIM_OP))
}
SharedCollection::Integrated(c) => c.readonly(txn, |c, txn| {
let next = c.siblings(txn).next_back();
match next {
Some(node) => Ok(Js::from_xml(node, txn.doc().clone()).into()),
None => Ok(JsValue::UNDEFINED),
}
}),
}
}
#[wasm_bindgen(js_name = parent)]
pub fn parent(&self, txn: &ImplicitTransaction) -> crate::Result<JsValue> {
match &self.0 {
SharedCollection::Prelim(_) => {
Err(JsValue::from_str(crate::js::errors::INVALID_PRELIM_OP))
}
SharedCollection::Integrated(c) => c.readonly(txn, |c, txn| match c.parent() {
None => Ok(JsValue::UNDEFINED),
Some(node) => Ok(Js::from_xml(node, txn.doc().clone()).into()),
}),
}
}
#[wasm_bindgen(js_name = toString)]
pub fn to_string(&self, txn: &ImplicitTransaction) -> crate::Result<String> {
match &self.0 {
SharedCollection::Prelim(c) => c.to_string(txn),
SharedCollection::Integrated(c) => c.readonly(txn, |c, txn| Ok(c.get_string(txn))),
}
}
#[wasm_bindgen(js_name = setAttribute)]
pub fn set_attribute(
&mut self,
name: &str,
value: &str,
txn: ImplicitTransaction,
) -> crate::Result<()> {
match &mut self.0 {
SharedCollection::Prelim(c) => {
c.attributes.insert(name.to_string(), value.to_string());
Ok(())
}
SharedCollection::Integrated(c) => c.mutably(txn, |c, txn| {
c.insert_attribute(txn, name, value);
Ok(())
}),
}
}
#[wasm_bindgen(js_name = getAttribute)]
pub fn get_attribute(&self, name: &str, txn: &ImplicitTransaction) -> crate::Result<JsValue> {
let value = match &self.0 {
SharedCollection::Integrated(c) => {
c.readonly(txn, |c, txn| Ok(c.get_attribute(txn, name)))?
}
SharedCollection::Prelim(c) => c.attributes.get(name).cloned(),
};
match value {
None => Ok(JsValue::UNDEFINED),
Some(value) => Ok(JsValue::from_str(&value)),
}
}
#[wasm_bindgen(js_name = removeAttribute)]
pub fn remove_attribute(
&mut self,
name: String,
txn: ImplicitTransaction,
) -> crate::Result<()> {
match &mut self.0 {
SharedCollection::Prelim(c) => {
c.attributes.remove(&name);
Ok(())
}
SharedCollection::Integrated(c) => c.mutably(txn, |c, txn| {
c.remove_attribute(txn, &name);
Ok(())
}),
}
}
#[wasm_bindgen(js_name = attributes)]
pub fn attributes(&self, txn: &ImplicitTransaction) -> crate::Result<JsValue> {
match &self.0 {
SharedCollection::Prelim(c) => Ok(JsValue::from_serde(&c.attributes)
.map_err(|_| JsValue::from_str(crate::js::errors::INVALID_PRELIM_OP))?),
SharedCollection::Integrated(c) => c.readonly(txn, |c, txn| {
let map = js_sys::Object::new();
for (name, value) in c.attributes(txn) {
js_sys::Reflect::set(
&map,
&JsValue::from_str(name),
&JsValue::from_str(&value),
)?;
}
Ok(map.into())
}),
}
}
#[wasm_bindgen(js_name = treeWalker)]
pub fn tree_walker(&self, txn: &ImplicitTransaction) -> crate::Result<js_sys::Array> {
match &self.0 {
SharedCollection::Prelim(_) => {
Err(JsValue::from_str(crate::js::errors::INVALID_PRELIM_OP))
}
SharedCollection::Integrated(c) => c.readonly(txn, |c, txn| {
let doc = txn.doc();
let walker = c.successors(txn).map(|n| {
let js: JsValue = Js::from_xml(n, doc.clone()).into();
js
});
let array = js_sys::Array::from_iter(walker);
Ok(array.into())
}),
}
}
#[wasm_bindgen(js_name = observe)]
pub fn observe(&mut self, callback: js_sys::Function) -> crate::Result<()> {
match &self.0 {
SharedCollection::Prelim(_) => {
Err(JsValue::from_str(crate::js::errors::INVALID_PRELIM_OP))
}
SharedCollection::Integrated(c) => {
let txn = c.transact()?;
let array = c.resolve(&txn)?;
let abi = callback.subscription_key();
array.observe_with(abi, move |txn, e| {
let e = YXmlEvent::new(e, txn);
let txn = YTransaction::from_ref(txn);
callback
.call2(&JsValue::UNDEFINED, &e.into(), &txn.into())
.unwrap();
});
Ok(())
}
}
}
#[wasm_bindgen(js_name = unobserve)]
pub fn unobserve(&mut self, callback: js_sys::Function) -> crate::Result<bool> {
match &self.0 {
SharedCollection::Prelim(_) => {
Err(JsValue::from_str(crate::js::errors::INVALID_PRELIM_OP))
}
SharedCollection::Integrated(c) => {
let txn = c.transact()?;
let shared_ref = c.resolve(&txn)?;
let abi = callback.subscription_key();
Ok(shared_ref.unobserve(abi))
}
}
}
#[wasm_bindgen(js_name = observeDeep)]
pub fn observe_deep(&mut self, callback: js_sys::Function) -> crate::Result<()> {
match &self.0 {
SharedCollection::Prelim(_) => {
Err(JsValue::from_str(crate::js::errors::INVALID_PRELIM_OP))
}
SharedCollection::Integrated(c) => {
let txn = c.transact()?;
let array = c.resolve(&txn)?;
let abi = callback.subscription_key();
array.observe_deep_with(abi, move |txn, e| {
let e = crate::js::convert::events_into_js(txn, e);
let txn = YTransaction::from_ref(txn);
callback
.call2(&JsValue::UNDEFINED, &e, &txn.into())
.unwrap();
});
Ok(())
}
}
}
#[wasm_bindgen(js_name = unobserveDeep)]
pub fn unobserve_deep(&mut self, callback: js_sys::Function) -> crate::Result<bool> {
match &self.0 {
SharedCollection::Prelim(_) => {
Err(JsValue::from_str(crate::js::errors::INVALID_PRELIM_OP))
}
SharedCollection::Integrated(c) => {
let txn = c.transact()?;
let shared_ref = c.resolve(&txn)?;
let abi = callback.subscription_key();
Ok(shared_ref.unobserve_deep(abi))
}
}
}
}