Skip to content

Commit

Permalink
New Nodes Code
Browse files Browse the repository at this point in the history
  • Loading branch information
jameskerr committed Mar 13, 2024
1 parent 5b5ad72 commit 9d759e2
Show file tree
Hide file tree
Showing 8 changed files with 231 additions and 0 deletions.
37 changes: 37 additions & 0 deletions modules/react-arborist/src/nodes/accessor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
export type NodeDataAccessors<T> = {
id: (d: T) => string;
children: (d: T) => T[];
isLeaf: (d: T) => boolean;
};

export class Accessor<T> {
constructor(public accessors: Partial<NodeDataAccessors<T>> = {}) {}

getId(d: T): string {
if (this.accessors.id) {
return this.accessors.id(d);
} else if (d && typeof d === "object" && "id" in d) {
return d.id as string;
} else {
throw new Error("No id found for node data. Specify an id accessor.");
}
}

getChildren(d: T): null | T[] {
if (this.accessors.children) {
return this.accessors.children(d);
} else if (d && typeof d === "object" && "children" in d) {
return d.children as T[];
} else {
return null;
}
}

getIsLeaf(d: T): boolean {
if (this.accessors.isLeaf) {
return this.accessors.isLeaf(d);
} else {
return !this.getChildren(d);
}
}
}
3 changes: 3 additions & 0 deletions modules/react-arborist/src/nodes/arborist.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export class Arborist {
constructor(root: NodeStruct, accessors: Accessor)
}
16 changes: 16 additions & 0 deletions modules/react-arborist/src/nodes/create-tree.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { useEffect, useState } from "react";
import { Accessor, NodeDataAccessors } from "./accessor";
import { createRootNodeStruct } from "./root-node-struct";
import { TreeStruct } from "./tree-struct";

type Options<T> = NodeDataAccessors<T>;

export function createTree<T>(data: T[], options: Partial<Options<T>> = {}) {
const accessor = new Accessor(options);
const root = createRootNodeStruct(data, accessor));
const tree = new TreeStruct(root, accessor)

return {
nodes: tree.nodes
}
}
25 changes: 25 additions & 0 deletions modules/react-arborist/src/nodes/flatten.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { TreeApi } from "../interfaces/tree-api";
import { NodeStruct } from "./node-struct";

export type RowStruct<T> = {
node: NodeStruct<T>;
index: number;
};

export function flatten<T>(tree: TreeApi<T>): RowStruct<T>[] {
const list: RowStruct<T>[] = [];
const queue = [...tree.root.children!];
let node = queue.shift();
let index = 0;
while (node) {
list.push({
node,
index: index++,
});
if (!node.isLeaf && tree.isOpen(node.id)) {
queue.unshift(...node.children!);
}
node = queue.shift();
}
return list;
}
57 changes: 57 additions & 0 deletions modules/react-arborist/src/nodes/node-struct.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { Accessor } from "./accessor";

export type NodeStruct<T> = {
id: string;
data: T;
children: NodeStruct<T>[] | null;
parent: NodeStruct<T> | null;
isLeaf: boolean;
level: number;
};

export function createNodeStruct<T>(args: {
data: T;
level: number;
parent: NodeStruct<T> | null;
accessor: Accessor<T>;
}) {
const { data, accessor, level, parent } = args;
const id = accessor.getId(data);
const isLeaf = accessor.getIsLeaf(data);
const children = isLeaf ? null : accessor.getChildren(data);
const node: NodeStruct<T> = {
id,
isLeaf,
data,
level,
parent,
children: null,
};
if (children) {
node.children = children.map((child) =>
createNodeStruct({
data: child,
parent: node,
level: level + 1,
accessor,
}),
);
}
return node;
}

export function find<T>(
id: string,
current?: NodeStruct<T>,
): NodeStruct<T> | null {
if (!current) return null;
if (current.id === id) return current;
if (current.children) {
for (let child of current.children) {
const found = find(id, child);
if (found) return found;
}
return null;
}
return null;
}
19 changes: 19 additions & 0 deletions modules/react-arborist/src/nodes/root-node-struct.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Accessor } from "./accessor";
import { NodeStruct, createNodeStruct } from "./node-struct";

export const ROOT_ID = "__REACT_ARBORIST_INTERNAL_ROOT__";

export function createRootNodeStruct<T>(data: T[], accessor: Accessor<T>) {
const root: NodeStruct<T> = {
id: ROOT_ID,
data: null as unknown as T,
level: -1,
isLeaf: false,
parent: null,
children: null,
};
root.children = data.map((child) =>
createNodeStruct({ data: child, parent: root, level: 0, accessor }),
);
return root;
}
55 changes: 55 additions & 0 deletions modules/react-arborist/src/nodes/tree-struct.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { Accessor } from "./accessor";
import { NodeStruct, find } from "./node-struct";

/* We wrap and mutate the data provided */
export class TreeStruct<T> {
constructor(
public root: NodeStruct<T>,
public accessor: Accessor<T>,
) {}

get data() {
return this.root.children?.map((node) => node.data);
}

get nodes() {
return this.root; // maybe this returns an array
}

create(args: { parentId: string | null; index: number; data: T }) {
const { parentId, index, data } = args;
const parent = parentId ? find(parentId, this.root) : this.root;
if (!parent) return null;
const siblings = this.accessor.getChildren(parent.data)!;

siblings.splice(index, 0, data);
}

update(args: { id: string; changes: Partial<T> }) {
const { id, changes } = args;
const node = find(id, this.root);

if (node) node.data = { ...node.data, ...changes };
}

move(args: { id: string; parentId: string | null; index: number }) {
const { id, parentId, index } = args;
const node = find(id, this.root);
const parent = parentId ? find(parentId, this.root) : this.root;
if (!node || !parent) return;
const newSiblings = this.accessor.getChildren(parent.data)!;
const oldSiblings = this.accessor.getChildren(node.parent!.data)!;
const oldIndex = oldSiblings.indexOf(node.data);

newSiblings.splice(index, 0, node.data); // Add to new parent
oldSiblings.splice(oldIndex, 1); // Remove from old parent
}

destroy(args: { id: string }) {
const node = find(args.id, this.root);
if (!node) return;
const siblings = this.accessor.getChildren(node.parent!.data)!;
const index = siblings.indexOf(node.data);
siblings.splice(index);
}
}
19 changes: 19 additions & 0 deletions modules/react-arborist/src/nodes/use-nodes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { useEffect, useState } from "react";
import { Accessor, NodeDataAccessors } from "./accessor";
import { createRootNodeStruct } from "./root-node-struct";

type Options<T> = NodeDataAccessors<T>;

export function useNodes<T>(data: T[], options: Partial<Options<T>> = {}) {
const accessor = new Accessor(options);
const [value, set] = useState(() => createRootNodeStruct(data, accessor));

useEffect(() => {
set(createRootNodeStruct(data, accessor));
}, [data]);

return {
value,
set,
};
}

0 comments on commit 9d759e2

Please sign in to comment.