use crate::collection::SharedCollection;
use crate::js::{Callback, Js, ValueRef, YRange};
use crate::transaction::{ImplicitTransaction, YTransaction};
use crate::weak::YWeakLink;
use crate::Result;
use gloo_utils::format::JsValueSerdeExt;
use std::iter::FromIterator;
use wasm_bindgen::prelude::wasm_bindgen;
use wasm_bindgen::JsValue;
use yrs::types::array::ArrayEvent;
use yrs::types::{ToJson, TYPE_REFS_ARRAY};
use yrs::{Array, ArrayRef, DeepObservable, Observable, Quotable, SharedRef, TransactionMut};
#[wasm_bindgen]
#[repr(transparent)]
pub struct YArray(pub(crate) SharedCollection<Vec<JsValue>, ArrayRef>);
#[wasm_bindgen]
impl YArray {
#[wasm_bindgen(constructor)]
pub fn new(items: Option<Vec<JsValue>>) -> Self {
YArray(SharedCollection::prelim(items.unwrap_or_default()))
}
#[wasm_bindgen(getter, js_name = type)]
#[inline]
pub fn get_type(&self) -> u8 {
TYPE_REFS_ARRAY
}
#[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 = length)]
pub fn length(&self, txn: &ImplicitTransaction) -> Result<u32> {
match &self.0 {
SharedCollection::Prelim(c) => Ok(c.len() as u32),
SharedCollection::Integrated(c) => c.readonly(txn, |c, txn| Ok(c.len(txn))),
}
}
#[wasm_bindgen(js_name = toJson)]
pub fn to_json(&self, txn: &ImplicitTransaction) -> Result<JsValue> {
match &self.0 {
SharedCollection::Prelim(c) => {
let a = js_sys::Array::new();
for js in c.iter() {
a.push(js);
}
Ok(a.into())
}
SharedCollection::Integrated(c) => c.readonly(txn, |c, txn| {
let any = c.to_json(txn);
JsValue::from_serde(&any).map_err(|e| JsValue::from_str(&e.to_string()))
}),
}
}
#[wasm_bindgen(js_name = insert)]
pub fn insert(
&mut self,
index: u32,
items: Vec<JsValue>,
txn: ImplicitTransaction,
) -> Result<()> {
match &mut self.0 {
SharedCollection::Prelim(c) => {
c.reserve(items.len());
let mut i = index as usize;
for item in items {
c.insert(i, item);
i += 1;
}
Ok(())
}
SharedCollection::Integrated(c) => {
c.mutably(txn, |c, txn| c.insert_at(txn, index, items))
}
}
}
#[wasm_bindgen(js_name = push)]
pub fn push(&mut self, items: Vec<JsValue>, txn: ImplicitTransaction) -> Result<()> {
match &mut self.0 {
SharedCollection::Prelim(c) => {
c.extend_from_slice(&items);
Ok(())
}
SharedCollection::Integrated(c) => c.mutably(txn, |c, txn| {
let len = c.len(txn);
c.insert_at(txn, len, items)
}),
}
}
#[wasm_bindgen(method, js_name = "delete")]
pub fn delete(
&mut self,
index: u32,
length: Option<u32>,
txn: ImplicitTransaction,
) -> Result<()> {
let length = length.unwrap_or(1);
match &mut self.0 {
SharedCollection::Prelim(c) => {
let start = index as usize;
let end = start + length as usize;
for _ in c.drain(start..end) { }
Ok(())
}
SharedCollection::Integrated(c) => {
c.mutably(txn, |c, txn| Ok(c.remove_range(txn, index, length)))
}
}
}
#[wasm_bindgen(js_name = move)]
pub fn move_content(
&mut self,
source: u32,
target: u32,
txn: ImplicitTransaction,
) -> Result<()> {
match &mut self.0 {
SharedCollection::Prelim(c) => {
let index = if target > source { target - 1 } else { target };
let moved = c.remove(source as usize);
c.insert(index as usize, moved);
Ok(())
}
SharedCollection::Integrated(c) => {
c.mutably(txn, |c, txn| Ok(c.move_to(txn, source, target)))
}
}
}
#[wasm_bindgen(js_name = get)]
pub fn get(&self, index: u32, txn: &ImplicitTransaction) -> Result<JsValue> {
match &self.0 {
SharedCollection::Prelim(c) => match c.get(index as usize) {
Some(item) => Ok(item.clone()),
None => Err(JsValue::from_str(crate::js::errors::OUT_OF_BOUNDS)),
},
SharedCollection::Integrated(c) => c.readonly(txn, |c, txn| match c.get(txn, index) {
Some(item) => Ok(Js::from_value(&item, txn.doc()).into()),
None => Err(JsValue::from_str(crate::js::errors::OUT_OF_BOUNDS)),
}),
}
}
#[wasm_bindgen(js_name = quote)]
pub fn quote(
&self,
lower: u32,
upper: u32,
lower_open: Option<bool>,
upper_open: Option<bool>,
txn: &ImplicitTransaction,
) -> Result<YWeakLink> {
match &self.0 {
SharedCollection::Prelim(_) => {
Err(JsValue::from_str(crate::js::errors::INVALID_PRELIM_OP))
}
SharedCollection::Integrated(c) => c.readonly(txn, |c, txn| {
let range = YRange::new(lower, upper, lower_open, upper_open);
let quote = c
.quote(txn, range)
.map_err(|e| JsValue::from_str(&e.to_string()))?;
Ok(YWeakLink::from_prelim(quote, txn.doc().clone()))
}),
}
}
#[wasm_bindgen(js_name = values)]
pub fn values(&self, txn: &ImplicitTransaction) -> Result<JsValue> {
match &self.0 {
SharedCollection::Prelim(c) => Ok(js_sys::Array::from_iter(c).into()),
SharedCollection::Integrated(c) => c.readonly(txn, |c, txn| {
let a = js_sys::Array::new();
let doc = txn.doc();
for item in c.iter(txn) {
a.push(&Js::from_value(&item, doc));
}
Ok(a.into())
}),
}
}
#[wasm_bindgen(js_name = observe)]
pub fn observe(&self, callback: js_sys::Function) -> 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 = YArrayEvent::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(&self, callback: js_sys::Function) -> 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 array = c.resolve(&txn)?;
let abi = callback.subscription_key();
Ok(array.unobserve(abi))
}
}
}
#[wasm_bindgen(js_name = observeDeep)]
pub fn observe_deep(&self, callback: js_sys::Function) -> 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(&self, callback: js_sys::Function) -> 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 array = c.resolve(&txn)?;
let abi = callback.subscription_key();
Ok(array.unobserve_deep(abi))
}
}
}
}
pub(crate) trait ArrayExt: Array + SharedRef {
fn insert_at<I>(&self, txn: &mut TransactionMut, index: u32, src: I) -> Result<()>
where
I: IntoIterator<Item = JsValue>,
{
let mut primitive = Vec::default();
let mut i = 0;
let mut j = index;
for value in src {
let js = Js::from(value);
match js.as_value()? {
ValueRef::Any(any) => primitive.push(any),
ValueRef::Shared(shared) => {
if shared.prelim() {
let len = primitive.len() as u32;
if len > 0 {
self.insert_range(txn, j, std::mem::take(&mut primitive));
j += len;
}
self.insert(txn, j, shared);
j += 1;
} else {
let err = format!("cannot insert item at index {}: shared collection is not a preliminary type", i);
return Err(JsValue::from(&err));
}
}
}
i += 1;
}
if !primitive.is_empty() {
self.insert_range(txn, j, primitive);
}
Ok(())
}
}
impl ArrayExt for ArrayRef {}
#[wasm_bindgen]
pub struct YArrayEvent {
inner: &'static ArrayEvent,
txn: &'static TransactionMut<'static>,
target: Option<JsValue>,
delta: Option<JsValue>,
}
#[wasm_bindgen]
impl YArrayEvent {
pub(crate) fn new<'doc>(event: &ArrayEvent, txn: &TransactionMut<'doc>) -> Self {
let inner: &'static ArrayEvent = unsafe { std::mem::transmute(event) };
let txn: &'static TransactionMut<'static> = unsafe { std::mem::transmute(txn) };
YArrayEvent {
inner,
txn,
target: None,
delta: None,
}
}
#[wasm_bindgen]
pub fn path(&self) -> JsValue {
crate::js::convert::path_into_js(self.inner.path())
}
#[wasm_bindgen(getter)]
pub fn target(&mut self) -> JsValue {
let target = self.inner.target();
let doc = self.txn.doc();
let js = self.target.get_or_insert_with(|| {
YArray(SharedCollection::integrated(target.clone(), doc.clone())).into()
});
js.clone()
}
#[wasm_bindgen(getter)]
pub fn origin(&mut self) -> JsValue {
let origin = self.txn.origin();
if let Some(origin) = origin {
Js::from(origin).into()
} else {
JsValue::UNDEFINED
}
}
#[wasm_bindgen(getter)]
pub fn delta(&mut self) -> JsValue {
let inner = &self.inner;
let txn = &self.txn;
let js = self.delta.get_or_insert_with(|| {
let delta = inner
.delta(txn)
.into_iter()
.map(|change| crate::js::convert::change_into_js(change, txn.doc()));
let mut result = js_sys::Array::new();
result.extend(delta);
result.into()
});
js.clone()
}
}