mirror of
https://github.com/loro-dev/loro.git
synced 2025-02-05 20:17:13 +00:00
refactor: use hierarchy value for tree value (#470)
* refactor: use hierarchy value for tree value * fix: get_container_deep_value_with_id * feat: get tree nodes by flat format
This commit is contained in:
parent
bb494df230
commit
3fe619bc81
13 changed files with 310 additions and 169 deletions
|
@ -479,64 +479,41 @@ struct Node {
|
|||
position: String,
|
||||
}
|
||||
|
||||
struct FlatNode {
|
||||
id: String,
|
||||
parent: Option<String>,
|
||||
meta: FxHashMap<String, LoroValue>,
|
||||
index: usize,
|
||||
position: String,
|
||||
}
|
||||
|
||||
impl FlatNode {
|
||||
fn from_loro_value(value: &LoroValue) -> Self {
|
||||
let map = value.as_map().unwrap();
|
||||
let id = map.get("id").unwrap().as_string().unwrap().to_string();
|
||||
let parent = map
|
||||
.get("parent")
|
||||
.unwrap()
|
||||
.as_string()
|
||||
.map(|x| x.to_string());
|
||||
|
||||
let meta = map.get("meta").unwrap().as_map().unwrap().as_ref().clone();
|
||||
let index = *map.get("index").unwrap().as_i64().unwrap() as usize;
|
||||
let position = map
|
||||
.get("fractional_index")
|
||||
.unwrap()
|
||||
.as_string()
|
||||
.unwrap()
|
||||
.to_string();
|
||||
FlatNode {
|
||||
id,
|
||||
parent,
|
||||
meta,
|
||||
index,
|
||||
position,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Node {
|
||||
fn from_loro_value(value: &[LoroValue]) -> Vec<Self> {
|
||||
let mut node_map = FxHashMap::default();
|
||||
let mut parent_child_map = FxHashMap::default();
|
||||
for node in value.iter() {
|
||||
let map = node.as_map().unwrap();
|
||||
let id = map.get("id").unwrap().as_string().unwrap().to_string();
|
||||
let parent = map
|
||||
.get("parent")
|
||||
.unwrap()
|
||||
.as_string()
|
||||
.map(|x| x.to_string());
|
||||
|
||||
for flat_node in value.iter() {
|
||||
let flat_node = FlatNode::from_loro_value(flat_node);
|
||||
let meta = map.get("meta").unwrap().as_map().unwrap().as_ref().clone();
|
||||
let index = *map.get("index").unwrap().as_i64().unwrap() as usize;
|
||||
let position = map
|
||||
.get("fractional_index")
|
||||
.unwrap()
|
||||
.as_string()
|
||||
.unwrap()
|
||||
.to_string();
|
||||
let children = map.get("children").unwrap().as_list().unwrap();
|
||||
let children = Node::from_loro_value(children);
|
||||
let tree_node = Node {
|
||||
// id: flat_node.id.clone(),
|
||||
// parent: flat_node.parent.clone(),
|
||||
children: vec![],
|
||||
meta: flat_node.meta,
|
||||
// index: flat_node.index,
|
||||
position: flat_node.position,
|
||||
children,
|
||||
meta,
|
||||
position,
|
||||
};
|
||||
|
||||
node_map.insert(flat_node.id.clone(), tree_node);
|
||||
node_map.insert(id.clone(), tree_node);
|
||||
|
||||
parent_child_map
|
||||
.entry(flat_node.parent)
|
||||
.entry(parent)
|
||||
.or_insert_with(Vec::new)
|
||||
.push((flat_node.index, flat_node.id));
|
||||
.push((index, id));
|
||||
}
|
||||
let mut node_map_clone = node_map.clone();
|
||||
for (parent_id, child_ids) in parent_child_map.iter() {
|
||||
|
|
|
@ -488,18 +488,9 @@ impl ApplyDiff for TreeTracker {
|
|||
|
||||
fn to_value(&self) -> LoroValue {
|
||||
let mut list: Vec<FxHashMap<_, _>> = Vec::new();
|
||||
let mut q = VecDeque::from_iter(
|
||||
self.tree
|
||||
.iter()
|
||||
.sorted_unstable_by_key(|x| &x.position)
|
||||
.enumerate(),
|
||||
);
|
||||
|
||||
while let Some((i, node)) = q.pop_front() {
|
||||
for (i, node) in self.tree.iter().enumerate() {
|
||||
list.push(node.to_value(i));
|
||||
q.extend(node.children.iter().enumerate());
|
||||
}
|
||||
|
||||
list.into()
|
||||
}
|
||||
}
|
||||
|
@ -537,6 +528,15 @@ impl TreeNode {
|
|||
);
|
||||
map.insert("fractional_index".to_string(), self.position.clone().into());
|
||||
map.insert("index".to_string(), (index as i64).into());
|
||||
map.insert(
|
||||
"children".to_string(),
|
||||
self.children
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, n)| n.to_value(i))
|
||||
.collect::<Vec<_>>()
|
||||
.into(),
|
||||
);
|
||||
map
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,8 +30,21 @@ fn updates_with_commit_message_can_be_imported_to_016() {
|
|||
let doc2 = loro_016::LoroDoc::new();
|
||||
doc2.import(&doc1.export_snapshot()).unwrap();
|
||||
assert_eq!(
|
||||
doc2.get_deep_value().to_json(),
|
||||
doc1.get_deep_value().to_json()
|
||||
doc2.get_text("text").to_string(),
|
||||
doc1.get_text("text").to_string()
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
doc2.get_tree("tree")
|
||||
.nodes()
|
||||
.into_iter()
|
||||
.map(|x| x.to_string())
|
||||
.collect::<Vec<_>>(),
|
||||
doc1.get_tree("tree")
|
||||
.nodes()
|
||||
.into_iter()
|
||||
.map(|x| x.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
);
|
||||
|
||||
{
|
||||
|
|
|
@ -4016,7 +4016,7 @@ mod test {
|
|||
.unwrap();
|
||||
assert_eq!(meta, 123.into());
|
||||
assert_eq!(
|
||||
r#"[{"parent":null,"meta":{"a":123},"id":"0@1","index":0,"fractional_index":"80"}]"#,
|
||||
r#"[{"parent":null,"meta":{"a":123},"id":"0@1","index":0,"children":[],"fractional_index":"80"}]"#,
|
||||
tree.get_deep_value().to_json()
|
||||
);
|
||||
let bytes = loro.export_snapshot();
|
||||
|
|
|
@ -11,7 +11,7 @@ use smallvec::smallvec;
|
|||
use crate::{
|
||||
container::tree::tree_op::TreeOp,
|
||||
delta::{TreeDiffItem, TreeExternalDiff},
|
||||
state::{FractionalIndexGenResult, NodePosition, TreeParentId},
|
||||
state::{FractionalIndexGenResult, NodePosition, TreeNode, TreeNodeWithChildren, TreeParentId},
|
||||
txn::{EventHint, Transaction},
|
||||
BasicHandler, HandlerTrait, MapHandler,
|
||||
};
|
||||
|
@ -816,10 +816,34 @@ impl TreeHandler {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn get_nodes_under(&self, parent: TreeParentId) -> Vec<TreeNode> {
|
||||
match &self.inner {
|
||||
MaybeDetached::Detached(_t) => {
|
||||
unreachable!()
|
||||
}
|
||||
MaybeDetached::Attached(a) => a.with_state(|state| {
|
||||
let a = state.as_tree_state().unwrap();
|
||||
a.get_all_tree_nodes_under(parent)
|
||||
}),
|
||||
}
|
||||
}
|
||||
pub fn roots(&self) -> Vec<TreeID> {
|
||||
self.children(&TreeParentId::Root).unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn get_all_hierarchy_nodes_under(&self, parent: TreeParentId) -> Vec<TreeNodeWithChildren> {
|
||||
match &self.inner {
|
||||
MaybeDetached::Detached(_t) => {
|
||||
// TODO: implement
|
||||
unimplemented!()
|
||||
}
|
||||
MaybeDetached::Attached(a) => a.with_state(|state| {
|
||||
let a = state.as_tree_state().unwrap();
|
||||
a.get_all_hierarchy_nodes_under(parent)
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
pub fn __internal__next_tree_id(&self) -> TreeID {
|
||||
match &self.inner {
|
||||
|
|
|
@ -264,7 +264,7 @@ impl ContainerHistoryCache {
|
|||
op: Arc::new(TreeOp::Create {
|
||||
target: node.id,
|
||||
parent: node.parent.tree_id(),
|
||||
position: node.position.clone(),
|
||||
position: node.fractional_index.clone(),
|
||||
}),
|
||||
effected: true,
|
||||
})
|
||||
|
|
|
@ -21,7 +21,7 @@ pub use loro::LoroDoc;
|
|||
pub use loro_common;
|
||||
pub use oplog::OpLog;
|
||||
pub use state::DocState;
|
||||
pub use state::TreeParentId;
|
||||
pub use state::{TreeNode, TreeNodeWithChildren, TreeParentId};
|
||||
pub use undo::UndoManager;
|
||||
pub use utils::subscription::Subscription;
|
||||
pub mod awareness;
|
||||
|
@ -70,6 +70,7 @@ pub(crate) use loro_common::InternalString;
|
|||
|
||||
pub use container::ContainerType;
|
||||
pub use encoding::json_schema::json;
|
||||
pub use fractional_index::FractionalIndex;
|
||||
pub use loro_common::{loro_value, to_value};
|
||||
#[cfg(feature = "wasm")]
|
||||
pub use value::wasm;
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
use std::{
|
||||
borrow::Cow,
|
||||
f32::consts::E,
|
||||
io::Write,
|
||||
sync::{
|
||||
atomic::{AtomicU64, Ordering},
|
||||
|
@ -52,8 +51,8 @@ pub(crate) use container_store::GcStore;
|
|||
pub(crate) use list_state::ListState;
|
||||
pub(crate) use map_state::MapState;
|
||||
pub(crate) use richtext_state::RichtextState;
|
||||
pub use tree_state::TreeParentId;
|
||||
pub(crate) use tree_state::{get_meta_value, FractionalIndexGenResult, NodePosition, TreeState};
|
||||
pub use tree_state::{TreeNode, TreeNodeWithChildren, TreeParentId};
|
||||
|
||||
use self::{container_store::ContainerWrapper, unknown_state::UnknownState};
|
||||
|
||||
|
@ -1028,27 +1027,29 @@ impl DocState {
|
|||
match value {
|
||||
LoroValue::Container(_) => unreachable!(),
|
||||
LoroValue::List(mut list) => {
|
||||
// FIXME: doesn't work for tree
|
||||
if list.iter().all(|x| !x.is_container()) {
|
||||
return LoroValue::Map(Arc::new(fx_map!(
|
||||
"cid".into() => cid_str,
|
||||
"value".into() => LoroValue::List(list)
|
||||
)));
|
||||
}
|
||||
if container.get_type() == ContainerType::Tree {
|
||||
get_meta_value(Arc::make_mut(&mut list), self);
|
||||
} else {
|
||||
if list.iter().all(|x| !x.is_container()) {
|
||||
return LoroValue::Map(Arc::new(fx_map!(
|
||||
"cid".into() => cid_str,
|
||||
"value".into() => LoroValue::List(list)
|
||||
)));
|
||||
}
|
||||
|
||||
let list_mut = Arc::make_mut(&mut list);
|
||||
for item in list_mut.iter_mut() {
|
||||
if item.is_container() {
|
||||
let container = item.as_container().unwrap();
|
||||
let container_idx = self.arena.register_container(container);
|
||||
let value = self.get_container_deep_value_with_id(
|
||||
container_idx,
|
||||
Some(container.clone()),
|
||||
);
|
||||
*item = value;
|
||||
let list_mut = Arc::make_mut(&mut list);
|
||||
for item in list_mut.iter_mut() {
|
||||
if item.is_container() {
|
||||
let container = item.as_container().unwrap();
|
||||
let container_idx = self.arena.register_container(container);
|
||||
let value = self.get_container_deep_value_with_id(
|
||||
container_idx,
|
||||
Some(container.clone()),
|
||||
);
|
||||
*item = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LoroValue::Map(Arc::new(fx_map!(
|
||||
"cid".into() => cid_str,
|
||||
"value".into() => LoroValue::List(list)
|
||||
|
|
|
@ -719,15 +719,18 @@ impl TreeState {
|
|||
!self.trees.contains_key(target)
|
||||
}
|
||||
|
||||
// Get all flat tree nodes, excluding deleted nodes.
|
||||
pub(crate) fn tree_nodes(&self) -> Vec<TreeNode> {
|
||||
self.get_all_tree_nodes_under(TreeParentId::Root)
|
||||
}
|
||||
|
||||
// Get all flat deleted nodes
|
||||
#[allow(unused)]
|
||||
pub(crate) fn deleted_tree_nodes(&self) -> Vec<TreeNode> {
|
||||
self.get_all_tree_nodes_under(TreeParentId::Deleted)
|
||||
}
|
||||
|
||||
// Get all flat tree nodes under the parent
|
||||
pub(crate) fn get_all_tree_nodes_under(&self, root: TreeParentId) -> Vec<TreeNode> {
|
||||
let mut ans = vec![];
|
||||
let children = self.children.get(&root);
|
||||
|
@ -739,7 +742,7 @@ impl TreeState {
|
|||
ans.push(TreeNode {
|
||||
id: target,
|
||||
parent,
|
||||
position: position.position.clone(),
|
||||
fractional_index: position.position.clone(),
|
||||
index,
|
||||
last_move_op: self.trees.get(&target).map(|x| x.last_move_op).unwrap(),
|
||||
});
|
||||
|
@ -757,6 +760,26 @@ impl TreeState {
|
|||
ans
|
||||
}
|
||||
|
||||
pub(crate) fn get_all_hierarchy_nodes_under(
|
||||
&self,
|
||||
root: TreeParentId,
|
||||
) -> Vec<TreeNodeWithChildren> {
|
||||
let mut ans = vec![];
|
||||
let Some(children) = self.children.get(&root) else {
|
||||
return ans;
|
||||
};
|
||||
for (index, (position, &target)) in children.iter().enumerate() {
|
||||
ans.push(TreeNodeWithChildren {
|
||||
id: target,
|
||||
parent: root,
|
||||
fractional_index: position.position.clone(),
|
||||
index,
|
||||
children: self.get_all_hierarchy_nodes_under(TreeParentId::Node(target)),
|
||||
})
|
||||
}
|
||||
ans
|
||||
}
|
||||
|
||||
fn bfs_all_nodes_for_fast_snapshot(&self) -> Vec<TreeNode> {
|
||||
let mut ans = vec![];
|
||||
self._bfs_all_nodes(TreeParentId::Root, &mut ans);
|
||||
|
@ -771,7 +794,7 @@ impl TreeState {
|
|||
ans.push(TreeNode {
|
||||
id: *target,
|
||||
parent: root,
|
||||
position: position.position.clone(),
|
||||
fractional_index: position.position.clone(),
|
||||
index,
|
||||
last_move_op: self.trees.get(target).map(|x| x.last_move_op).unwrap(),
|
||||
});
|
||||
|
@ -1210,7 +1233,7 @@ impl ContainerState for TreeState {
|
|||
}
|
||||
|
||||
fn get_value(&mut self) -> LoroValue {
|
||||
self.tree_nodes()
|
||||
self.get_all_hierarchy_nodes_under(TreeParentId::Root)
|
||||
.into_iter()
|
||||
.map(|x| x.into_value())
|
||||
.collect::<Vec<_>>()
|
||||
|
@ -1291,26 +1314,36 @@ impl ContainerState for TreeState {
|
|||
}
|
||||
|
||||
// convert map container to LoroValue
|
||||
#[allow(clippy::ptr_arg)]
|
||||
pub(crate) fn get_meta_value(nodes: &mut Vec<LoroValue>, state: &mut DocState) {
|
||||
for node in nodes.iter_mut() {
|
||||
let map = Arc::make_mut(node.as_map_mut().unwrap());
|
||||
let meta = map.get_mut("meta").unwrap();
|
||||
let id = meta.as_container().unwrap();
|
||||
*meta = state.get_container_deep_value(state.arena.register_container(id));
|
||||
let children = map.get_mut("children").unwrap().as_list_mut().unwrap();
|
||||
get_meta_value(Arc::make_mut(children), state);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct TreeNode {
|
||||
pub(crate) id: TreeID,
|
||||
pub(crate) parent: TreeParentId,
|
||||
pub(crate) position: FractionalIndex,
|
||||
pub(crate) index: usize,
|
||||
pub(crate) last_move_op: IdFull,
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TreeNode {
|
||||
pub id: TreeID,
|
||||
pub parent: TreeParentId,
|
||||
pub fractional_index: FractionalIndex,
|
||||
pub index: usize,
|
||||
pub last_move_op: IdFull,
|
||||
}
|
||||
|
||||
impl TreeNode {
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TreeNodeWithChildren {
|
||||
pub id: TreeID,
|
||||
pub parent: TreeParentId,
|
||||
pub fractional_index: FractionalIndex,
|
||||
pub index: usize,
|
||||
pub children: Vec<TreeNodeWithChildren>,
|
||||
}
|
||||
|
||||
impl TreeNodeWithChildren {
|
||||
fn into_value(self) -> LoroValue {
|
||||
let mut t = FxHashMap::default();
|
||||
t.insert("id".to_string(), self.id.to_string().into());
|
||||
|
@ -1327,7 +1360,15 @@ impl TreeNode {
|
|||
t.insert("index".to_string(), (self.index as i64).into());
|
||||
t.insert(
|
||||
"fractional_index".to_string(),
|
||||
self.position.to_string().into(),
|
||||
self.fractional_index.to_string().into(),
|
||||
);
|
||||
t.insert(
|
||||
"children".to_string(),
|
||||
self.children
|
||||
.into_iter()
|
||||
.map(|x| x.into_value())
|
||||
.collect::<Vec<_>>()
|
||||
.into(),
|
||||
);
|
||||
t.into()
|
||||
}
|
||||
|
@ -1477,7 +1518,7 @@ mod snapshot {
|
|||
let mut position_register = ValueRegister::new();
|
||||
let mut id_to_idx = FxHashMap::default();
|
||||
for node in input.iter() {
|
||||
position_set.insert(node.position.clone());
|
||||
position_set.insert(node.fractional_index.clone());
|
||||
let idx = node_ids.len();
|
||||
node_ids.push(EncodedTreeNodeId {
|
||||
peer_idx: peers.register(&node.id.peer),
|
||||
|
@ -1503,7 +1544,7 @@ mod snapshot {
|
|||
last_set_peer_idx: peers.register(&last_set_id.peer),
|
||||
last_set_counter: last_set_id.counter,
|
||||
last_set_lamport_sub_counter: last_set_id.lamport as i32 - last_set_id.counter,
|
||||
fractional_index_idx: position_register.register(&node.position),
|
||||
fractional_index_idx: position_register.register(&node.fractional_index),
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -24,12 +24,12 @@ use loro_internal::{
|
|||
undo::{UndoItemMeta, UndoOrRedo},
|
||||
version::Frontiers,
|
||||
ContainerType, DiffEvent, FxHashMap, HandlerTrait, LoroDoc as LoroDocInner, LoroValue,
|
||||
MovableListHandler, TreeParentId, UndoManager as InnerUndoManager,
|
||||
MovableListHandler, TreeNodeWithChildren, TreeParentId, UndoManager as InnerUndoManager,
|
||||
VersionVector as InternalVersionVector,
|
||||
};
|
||||
use rle::HasLength;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{cell::RefCell, cmp::Ordering, rc::Rc, sync::Arc};
|
||||
use std::{cell::RefCell, cmp::Ordering, option, rc::Rc, sync::Arc};
|
||||
use wasm_bindgen::{__rt::IntoJsResult, prelude::*, throw_val};
|
||||
use wasm_bindgen_derive::TryFromJsValue;
|
||||
|
||||
|
@ -102,6 +102,8 @@ extern "C" {
|
|||
pub type JsTreeID;
|
||||
#[wasm_bindgen(typescript_type = "TreeID | undefined")]
|
||||
pub type JsParentTreeID;
|
||||
#[wasm_bindgen(typescript_type = "{ withDelted: boolean }")]
|
||||
pub type JsGetNodesProp;
|
||||
#[wasm_bindgen(typescript_type = "LoroTreeNode | undefined")]
|
||||
pub type JsTreeNodeOrUndefined;
|
||||
#[wasm_bindgen(typescript_type = "string | undefined")]
|
||||
|
@ -3433,33 +3435,56 @@ impl LoroTree {
|
|||
Ok(ans)
|
||||
}
|
||||
|
||||
/// Get the flat array of the forest.
|
||||
/// Get the hierarchy array of the forest.
|
||||
///
|
||||
/// Note: the metadata will be not resolved. So if you don't only care about hierarchy
|
||||
/// but also the metadata, you should use `toJson()`.
|
||||
///
|
||||
// TODO: perf
|
||||
#[wasm_bindgen(js_name = "toArray", skip_typescript)]
|
||||
pub fn to_array(&mut self) -> JsResult<Array> {
|
||||
let value = self.handler.get_value().into_list().unwrap();
|
||||
pub fn to_array(&self) -> JsResult<Array> {
|
||||
let value = self
|
||||
.handler
|
||||
.get_all_hierarchy_nodes_under(TreeParentId::Root);
|
||||
self.get_node_with_children(value)
|
||||
}
|
||||
|
||||
/// Get the flat array of the forest. If `with_deleted` is true, the deleted nodes will be included.
|
||||
#[wasm_bindgen(js_name = "getNodes", skip_typescript)]
|
||||
pub fn get_nodes(&self, options: JsGetNodesProp) -> JsResult<Array> {
|
||||
let with_deleted = if options.is_undefined() {
|
||||
false
|
||||
} else {
|
||||
Reflect::get(&options.into(), &JsValue::from_str("withDeleted"))?
|
||||
.as_bool()
|
||||
.unwrap_or(false)
|
||||
};
|
||||
let nodes = Array::new();
|
||||
for v in self.handler.get_nodes_under(TreeParentId::Root) {
|
||||
let node = LoroTreeNode::from_tree(v.id, self.handler.clone(), self.doc.clone());
|
||||
nodes.push(&node.into());
|
||||
}
|
||||
if with_deleted {
|
||||
for v in self.handler.get_nodes_under(TreeParentId::Deleted) {
|
||||
let node = LoroTreeNode::from_tree(v.id, self.handler.clone(), self.doc.clone());
|
||||
nodes.push(&node.into());
|
||||
}
|
||||
}
|
||||
Ok(nodes)
|
||||
}
|
||||
|
||||
fn get_node_with_children(&self, value: Vec<TreeNodeWithChildren>) -> JsResult<Array> {
|
||||
let ans = Array::new();
|
||||
for v in value.as_ref() {
|
||||
let v = v.as_map().unwrap();
|
||||
let id: JsValue = TreeID::try_from(v["id"].as_string().unwrap().as_str())
|
||||
.unwrap()
|
||||
.into();
|
||||
for v in value {
|
||||
let id: JsValue = v.id.into();
|
||||
let id: JsTreeID = id.into();
|
||||
let parent = if let LoroValue::String(p) = &v["parent"] {
|
||||
Some(TreeID::try_from(p.as_str())?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let parent = v.parent.tree_id();
|
||||
let parent: JsParentTreeID = parent
|
||||
.map(|x| LoroTreeNode::from_tree(x, self.handler.clone(), self.doc.clone()).into())
|
||||
.unwrap_or(JsValue::undefined())
|
||||
.into();
|
||||
let index = *v["index"].as_i64().unwrap() as u32;
|
||||
let position = v["fractional_index"].as_string().unwrap();
|
||||
let index = v.index;
|
||||
let position = v.fractional_index.to_string();
|
||||
let map: LoroMap = self.get_node_by_id(&id).unwrap().data()?;
|
||||
let obj = Object::new();
|
||||
js_sys::Reflect::set(&obj, &"id".into(), &id)?;
|
||||
|
@ -3468,15 +3493,17 @@ impl LoroTree {
|
|||
js_sys::Reflect::set(
|
||||
&obj,
|
||||
&"fractional_index".into(),
|
||||
&JsValue::from_str(position),
|
||||
&JsValue::from_str(&position),
|
||||
)?;
|
||||
js_sys::Reflect::set(&obj, &"meta".into(), &map.into())?;
|
||||
let children = self.get_node_with_children(v.children)?;
|
||||
js_sys::Reflect::set(&obj, &"children".into(), &children)?;
|
||||
ans.push(&obj);
|
||||
}
|
||||
Ok(ans)
|
||||
}
|
||||
|
||||
/// Get the flat array with metadata of the forest.
|
||||
/// Get the hierarchy array with metadata of the forest.
|
||||
///
|
||||
/// @example
|
||||
/// ```ts
|
||||
|
@ -3486,7 +3513,7 @@ impl LoroTree {
|
|||
/// const tree = doc.getTree("tree");
|
||||
/// const root = tree.createNode();
|
||||
/// root.data.set("color", "red");
|
||||
/// // [ { id: '0@F2462C4159C4C8D1', parent: null, meta: { color: 'red' } } ]
|
||||
/// // [ { id: '0@F2462C4159C4C8D1', parent: null, meta: { color: 'red' }, children: [] } ]
|
||||
/// console.log(tree.toJSON());
|
||||
/// ```
|
||||
#[wasm_bindgen(js_name = "toJSON")]
|
||||
|
@ -4530,10 +4557,12 @@ export type TreeNodeValue = {
|
|||
index: number,
|
||||
fractionalIndex: string,
|
||||
meta: LoroMap,
|
||||
children: TreeNodeValue[],
|
||||
}
|
||||
|
||||
interface LoroTree{
|
||||
toArray(): TreeNodeValue[];
|
||||
getNodes(options?: { withDeleted: boolean = false }): LoroTreeNode[];
|
||||
}
|
||||
|
||||
interface LoroMovableList {
|
||||
|
|
|
@ -17,8 +17,10 @@ use loro_internal::obs::LocalUpdateCallback;
|
|||
use loro_internal::undo::{OnPop, OnPush};
|
||||
use loro_internal::version::ImVersionVector;
|
||||
use loro_internal::DocState;
|
||||
use loro_internal::FractionalIndex;
|
||||
use loro_internal::LoroDoc as InnerLoroDoc;
|
||||
use loro_internal::OpLog;
|
||||
use loro_internal::TreeNode as TreeNodeWithId;
|
||||
use loro_internal::TreeParentId;
|
||||
use loro_internal::{
|
||||
handler::Handler as InnerHandler, ListHandler as InnerListHandler,
|
||||
|
@ -1481,6 +1483,14 @@ impl ContainerTrait for LoroTree {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TreeNode {
|
||||
pub id: TreeID,
|
||||
pub parent: TreeParentId,
|
||||
pub fractional_index: FractionalIndex,
|
||||
pub index: usize,
|
||||
}
|
||||
|
||||
impl LoroTree {
|
||||
/// Create a new container that is detached from the document.
|
||||
///
|
||||
|
@ -1711,6 +1721,22 @@ impl LoroTree {
|
|||
self.handler.nodes()
|
||||
}
|
||||
|
||||
/// Return all nodes, if `with_deleted` is true, the deleted nodes will be included.
|
||||
pub fn get_nodes(&self, with_deleted: bool) -> Vec<TreeNode> {
|
||||
let mut ans = self.handler.get_nodes_under(TreeParentId::Root);
|
||||
if with_deleted {
|
||||
ans.extend(self.handler.get_nodes_under(TreeParentId::Deleted));
|
||||
}
|
||||
ans.into_iter()
|
||||
.map(|x| TreeNode {
|
||||
id: x.id,
|
||||
parent: x.parent,
|
||||
fractional_index: x.fractional_index,
|
||||
index: x.index,
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Return all children of the target node.
|
||||
///
|
||||
/// If the parent node does not exist, return `None`.
|
||||
|
|
|
@ -402,7 +402,7 @@ fn tree() {
|
|||
root_meta.insert("color", "red").unwrap();
|
||||
assert_eq!(
|
||||
tree.get_value_with_meta().to_json(),
|
||||
r#"[{"parent":null,"meta":{"color":"red"},"id":"0@1","index":0,"fractional_index":"80"},{"parent":"0@1","meta":{},"id":"1@1","index":0,"fractional_index":"80"}]"#
|
||||
r#"[{"parent":null,"meta":{"color":"red"},"id":"0@1","index":0,"children":[{"parent":"0@1","meta":{},"id":"1@1","index":0,"children":[],"fractional_index":"80"}],"fractional_index":"80"}]"#
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -1332,22 +1332,23 @@ fn test_tree_checkout_on_trimmed_doc() -> LoroResult<()> {
|
|||
"meta":{},
|
||||
"id": "0@0",
|
||||
"index": 0,
|
||||
"children": [{
|
||||
"parent": "0@0",
|
||||
"meta":{},
|
||||
"id": "1@0",
|
||||
"index": 0,
|
||||
"children": [],
|
||||
"fractional_index": "80",
|
||||
},{
|
||||
"parent": "0@0",
|
||||
"meta":{},
|
||||
"id": "3@0",
|
||||
"index": 1,
|
||||
"children": [],
|
||||
"fractional_index": "8180",
|
||||
},],
|
||||
"fractional_index": "80",
|
||||
},
|
||||
{
|
||||
"parent": "0@0",
|
||||
"meta":{},
|
||||
"id": "1@0",
|
||||
"index": 0,
|
||||
"fractional_index": "80",
|
||||
},
|
||||
{
|
||||
"parent": "0@0",
|
||||
"meta":{},
|
||||
"id": "3@0",
|
||||
"index": 1,
|
||||
"fractional_index": "8180",
|
||||
},
|
||||
]
|
||||
})
|
||||
);
|
||||
|
@ -1361,15 +1362,17 @@ fn test_tree_checkout_on_trimmed_doc() -> LoroResult<()> {
|
|||
"meta":{},
|
||||
"id": "0@0",
|
||||
"index": 0,
|
||||
"children":[{
|
||||
"parent": "0@0",
|
||||
"meta":{},
|
||||
"id": "1@0",
|
||||
"index": 0,
|
||||
"children": [],
|
||||
"fractional_index": "80",
|
||||
}],
|
||||
"fractional_index": "80",
|
||||
},
|
||||
{
|
||||
"parent": "0@0",
|
||||
"meta":{},
|
||||
"id": "1@0",
|
||||
"index": 0,
|
||||
"fractional_index": "80",
|
||||
},
|
||||
|
||||
]
|
||||
})
|
||||
);
|
||||
|
@ -1383,6 +1386,7 @@ fn test_tree_checkout_on_trimmed_doc() -> LoroResult<()> {
|
|||
"meta":{},
|
||||
"id": "0@0",
|
||||
"index": 0,
|
||||
"children": [],
|
||||
"fractional_index": "80",
|
||||
},
|
||||
{
|
||||
|
@ -1390,6 +1394,7 @@ fn test_tree_checkout_on_trimmed_doc() -> LoroResult<()> {
|
|||
"meta":{},
|
||||
"id": "1@0",
|
||||
"index": 1,
|
||||
"children": [],
|
||||
"fractional_index": "8180",
|
||||
},
|
||||
]
|
||||
|
@ -1405,22 +1410,27 @@ fn test_tree_checkout_on_trimmed_doc() -> LoroResult<()> {
|
|||
"meta":{},
|
||||
"id": "0@0",
|
||||
"index": 0,
|
||||
"children": [
|
||||
{
|
||||
"parent": "0@0",
|
||||
"meta":{},
|
||||
"id": "1@0",
|
||||
"index": 0,
|
||||
"children": [],
|
||||
"fractional_index": "80",
|
||||
},
|
||||
{
|
||||
"parent": "0@0",
|
||||
"meta":{},
|
||||
"id": "3@0",
|
||||
"index": 1,
|
||||
"children": [],
|
||||
"fractional_index": "8180",
|
||||
},
|
||||
],
|
||||
"fractional_index": "80",
|
||||
},
|
||||
{
|
||||
"parent": "0@0",
|
||||
"meta":{},
|
||||
"id": "1@0",
|
||||
"index": 0,
|
||||
"fractional_index": "80",
|
||||
},
|
||||
{
|
||||
"parent": "0@0",
|
||||
"meta":{},
|
||||
"id": "3@0",
|
||||
"index": 1,
|
||||
"fractional_index": "8180",
|
||||
},
|
||||
|
||||
]
|
||||
})
|
||||
);
|
||||
|
@ -1473,22 +1483,25 @@ fn test_tree_with_other_ops_checkout_on_trimmed_doc() -> LoroResult<()> {
|
|||
"meta":{},
|
||||
"id": "0@0",
|
||||
"index": 0,
|
||||
"children": [{
|
||||
"parent": "0@0",
|
||||
"meta":{},
|
||||
"id": "1@0",
|
||||
"index": 0,
|
||||
"children": [],
|
||||
"fractional_index": "80",
|
||||
},
|
||||
{
|
||||
"parent": "0@0",
|
||||
"meta":{},
|
||||
"id": "3@0",
|
||||
"index": 1,
|
||||
"children": [],
|
||||
"fractional_index": "8180",
|
||||
},],
|
||||
"fractional_index": "80",
|
||||
},
|
||||
{
|
||||
"parent": "0@0",
|
||||
"meta":{},
|
||||
"id": "1@0",
|
||||
"index": 0,
|
||||
"fractional_index": "80",
|
||||
},
|
||||
{
|
||||
"parent": "0@0",
|
||||
"meta":{},
|
||||
"id": "3@0",
|
||||
"index": 1,
|
||||
"fractional_index": "8180",
|
||||
},
|
||||
|
||||
]
|
||||
}
|
||||
)
|
||||
|
|
|
@ -87,13 +87,29 @@ describe("loro tree", () => {
|
|||
tree2.createNode(root.id);
|
||||
tree2.createNode(root.id);
|
||||
const arr = tree2.toArray();
|
||||
assertEquals(arr.length, 3);
|
||||
assertEquals(arr.length, 1);
|
||||
assertEquals(arr[0].children.length, 2)
|
||||
const keys = Object.keys(arr[0]);
|
||||
assert(keys.includes("id"));
|
||||
assert(keys.includes("parent"));
|
||||
assert(keys.includes("index"));
|
||||
assert(keys.includes("fractional_index"));
|
||||
assert(keys.includes("meta"));
|
||||
assert(keys.includes("children"));
|
||||
});
|
||||
|
||||
it("getNodes", ()=>{
|
||||
const loro2 = new LoroDoc();
|
||||
const tree2 = loro2.getTree("root");
|
||||
const root = tree2.createNode();
|
||||
const child = root.createNode();
|
||||
const nodes = tree2.getNodes({withDeleted: false});
|
||||
assertEquals(nodes.length, 2);
|
||||
assertEquals(nodes.map((n)=>{return n.id}), [root.id, child.id])
|
||||
tree2.delete(child.id);
|
||||
const nodesWithDeleted = tree2.getNodes({withDeleted:true});
|
||||
assertEquals(nodesWithDeleted.map((n)=>{return n.id}), [root.id, child.id]);
|
||||
assertEquals(tree2.getNodes({withDeleted: false}).map((n)=>{return n.id}), [root.id]);
|
||||
});
|
||||
|
||||
it("subscribe", async () => {
|
||||
|
|
Loading…
Reference in a new issue