-
Notifications
You must be signed in to change notification settings - Fork 139
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
8 changed files
with
231 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export class Arborist { | ||
constructor(root: NodeStruct, accessors: Accessor) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
}; | ||
} |