yjs-orderedtree
Version:
An ordered tree class for yjs. Lets you use a Y.Map like an ordered tree with insert, delete, and move operations. The children are ordered and the position of a child amongst its sibling can be manipulated
243 lines (242 loc) • 9.17 kB
TypeScript
/**
* YTree uses https://madebyevan.com/algos/crdt-mutable-tree-hierarchy/ and https://madebyevan.com/algos/crdt-fractional-indexing/
* It uses them to implement an ordered tree data structure, on top of YJS https://github.com/yjs/yjs, that maintains synchronization and consistency across multiple clients.
*
*
*/
/**
*
* @param {Y.Map} yMap
* @returns {boolean}
*
* @description Checks if the Ymap has been initialized for YTree operations.
*
* Specifically checks if root node exists.
*
* This should be done any time you want to load a ytree and not create it.
*/
export function checkForYTree(yMap: Y.Map<any>): boolean;
/**
* @typedef {Object} ComputedMapNode
* @property {string} id
* @property {ComputedMapNode} parent
* @property {Array} children
* @property {Map} edges
*/
/**
* Class representing a YTree.
*/
export class YTree {
/**
*
* @param {Y.Map} yMap
*
*
* Constructor is required to be called with a Y.Map instance that is bound to a Y.Doc.
* The YMap is required to be empty or initialized.
* If given an empty (uninitialized) YMap then it initializes the YMap for YTree operations by creating a root node.
*
*
* If you don't want to accidentally create a Ytree then use checkForYTree beforehand to ensure that a YTree has been initialized.
*/
constructor(yMap: Y.Map<any>);
/**
* @type {Y.Map} Node Map
*
* The Y.Map associated with the YTree.
* It holds all the nodes of the YTree in a 'flat' way.
* When a node is created, it should be given a globally unique key which can be generated by YTree.generateNodeKey.
* Each node holds a child Y.Map which has two entries: parent history and value
* Parent History holds entries for the node's previous parents, each entry holding the counter for that parent and the order index for when that node was part of the parent.
* Value is for the user, can be whatever value a Y.Map allows.
*/
_ymap: Y.Map<any>;
/**
* @type {Y.Doc}
* required
*/
_ydoc: Y.Doc;
/**
* @type {Map<string, ComputedMapNode>}
*
* As virtual map that the YTree holds.
* Keys are node keys, and their values are the node's parent, children and parent history
*
* This should be recomputed every time parent history changes or new nodes are added or deleted.
*/
computedMap: Map<string, ComputedMapNode>;
/**
* @type {Array<function():void>}
*/
_callbacks: Array<() => void>;
/**
* @returns string
* @description Generates a globally unique node key
* Concatenate ydoc client and a random number to generate global unique node id
*/
generateNodeKey(): string;
/**
* Add to callback list
* @param {function():void} f
*/
observe(f: () => void): void;
/**
* Remove from callback list
* @param {function} f
*/
unobserve(f: Function): void;
/**
* Get the Y.Map of the YTree.
* @returns {Y.Map} The Y.Map of the YTree.
*/
getYMap(): Y.Map<any>;
/**
* @param {string} nodeKey - The key of the new node.
* @param {object | boolean | string | number | Uint8Array | Y.AbstractType} value - The value to set for the new node.
* @description sets a value on a node
*/
setNodeValueFromKey(nodeKey: string, value: object | boolean | string | number | Uint8Array | Y.AbstractType<any>): void;
/**
* @param {string} nodeKey - The key of the node.
* @returns {object | boolean | string | number | Uint8Array | Y.AbstractType} value - The value of the node.
* @description get node value from key
*/
getNodeValueFromKey(nodeKey: string): object | boolean | string | number | Uint8Array | Y.AbstractType<any>;
/**
* @param {string} nodeKey
* @returns {Array<string>}
* @description get a list of keys of the node's children from the virtual (computed) map
*/
getNodeChildrenFromKey(nodeKey: string): Array<string>;
/**
* @param {string} nodeKey
* @return {string}
* @description get the key of the parent of the node from the virtual (computed) map
*/
getNodeParentFromKey(nodeKey: string): string;
/**
* Creates a Node with the given parent ad the value
* @param {string} parentKey - The key of the parent node.
* @param {string} nodeKey - The key of the new node.
* @param {object | boolean | string | number | Uint8Array | Y.AbstractType} value - The value to set for the new node.
* @description Create a new node in the YTree.
*/
createNode(parentKey: string, nodeKey: string, value: object | boolean | string | number | Uint8Array | Y.AbstractType<any>): void;
/**
* @param {string} nodeKey - The key of the new node.
* @description deletes the node and its descendants
*/
deleteNodeAndDescendants(nodeKey: string): void;
/**
* @param {string} childKey - The key of the node.
* @param {string} parentKey - The key of the new parent node.
* @description Reparents or moves a child to a new parent
* https://madebyevan.com/algos/crdt-mutable-tree-hierarchy/
* Before the move operation is done, it must be ensured that the old parent and the new parent are both still rooted.
* This is done by simply just going up the branch, making sure that a node (old/new parent and their parents and so on)'s parent is the key in the node's parent history that has the highest counter.
* If that's not the casefor the node, then it is made so by adding an operation, operation which should set the parent as the key in the node's parent history with the highest counter.
*/
moveChildToParent(childKey: string, parentKey: string): void;
/**
* This holds the main logic of the function. Read here to understand it: https://madebyevan.com/algos/crdt-mutable-tree-hierarchy/
* This is largely the same as the source code from the website.
*/
recomputeParentsAndChildren(): void;
/**
*
* @param {string} nodeKey
* @param {Array<String>} allDescendants
*
* @description Gets all the descendants of the node.
*/
getAllDescendants(nodeKey: string, allDescendants: Array<string>): void;
/**
*
* @param {ComputedMapNode} node
* @param {ComputedMapNode} other
* @returns {boolean}
*
* @description Returns true if and only if "node" is in the subtree under "other".
* This function is safe to call in the presence of parent cycles.
* https://cp-algorithms.com/others/tortoise_and_hare.html
*/
isNodeUnderOtherNode(node: ComputedMapNode, other: ComputedMapNode): boolean;
/**
* @param {string} nodeKey
* @description sets the nodes's order index below its siblings
*/
setNodeOrderToStart(nodeKey: string): void;
/**
*
* @param {string} nodeKey
* @description sets the node's order index above its siblings
*/
setNodeOrderToEnd(nodeKey: string): void;
/**
*
* @param {string} nodeKey
* @param {string} target
*
* @description sets the the node's order index to be right after the target
*
* The node and target must share the same parent.
*/
setNodeAfter(nodeKey: string, target: string): void;
/**
*
* @param {string} nodeKey
* @param {string} target
*
* @description sets the the node's order index to be right before the target
*
* The node and target must share the same parent.
*/
setNodeBefore(nodeKey: string, target: string): void;
/**
* Constructs an ordered array of objects based on their positions.
*
* @param {Array<string>} children - The array of objects to be ordered.
* @param {string} parentKey
* @returns {Array<string>} - An array of objects sorted by their positions.
*/
sortChildrenByOrder(children: Array<string>, parentKey: string): Array<string>;
/**
* gets the next order index in the array from the current item
*
* @param {Array<string>} children
* @param {string} key
* @param {string} parentKey
* @returns {String}
*/
getNextOrderIndex(key: string, children: Array<string>, parentKey: string): string;
/**
* gets the next order index in the array from the current item
*
* @param {Array<string>} children
* @param {string} key
* @param {string} parentKey
* @returns {String}
*/
getPreviousOrderIndex(key: string, children: Array<string>, parentKey: string): string;
/**
*
* @param {Array<string>} children
* @param {string} parentKey
* @returns {string}
*/
_getHighestOrderIndex(children: Array<string>, parentKey: string): string;
/**
*
* @param {Array<string>} children
* @param {string} parentKey
* @returns {string}
*/
_getLowestOrderIndex(children: Array<string>, parentKey: string): string;
}
export type ComputedMapNode = {
id: string;
parent: ComputedMapNode;
children: any[];
edges: Map<any, any>;
};
import * as Y from "yjs";