Skip to content

Commit

Permalink
feat: finalize the api (#13)
Browse files Browse the repository at this point in the history
  • Loading branch information
jaredLunde authored Apr 24, 2022
1 parent 9e36720 commit 47bb5e8
Show file tree
Hide file tree
Showing 45 changed files with 1,048 additions and 720 deletions.
176 changes: 127 additions & 49 deletions README.md

Large diffs are not rendered by default.

21 changes: 12 additions & 9 deletions examples/basic/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
Node,
useDnd,
useHotkeys,
useObservable,
useObserver,
useRovingFocus,
useSelections,
useTraits,
Expand All @@ -22,12 +22,12 @@ export default function App() {
const rovingFocus = useRovingFocus(tree);
const selections = useSelections(tree);
const traits = useTraits(tree, ["selected", "focused", "drop-target"]);
const dnd = useDnd(tree);
const dnd = useDnd(tree, { windowRef });
const virtualize = useVirtualize(tree, { windowRef, nodeHeight: 24 });

useHotkeys(tree, { windowRef, rovingFocus, selections });

useObservable(selections.didChange, (value) => {
useObserver(selections.didChange, (value) => {
const selected = Array.from(value);
traits.set("selected", selected);

Expand All @@ -40,7 +40,7 @@ export default function App() {
}
});

useObservable(dnd.didChange, (event) => {
useObserver(dnd.didChange, (event) => {
if (!event) return;

if (event.type === "enter" || event.type === "expanded") {
Expand All @@ -49,7 +49,7 @@ export default function App() {
}

const nodeIds: number[] = [event.dir.id];
const nodes = [...(event.dir.nodes ?? [])];
const nodes = event.dir.nodes ? [...event.dir.nodes] : [];

while (nodes.length) {
const node = tree.getById(nodes.pop() ?? -1);
Expand All @@ -66,9 +66,12 @@ export default function App() {
traits.set("drop-target", nodeIds);
} else if (event.type === "drop") {
traits.clear("drop-target");
const selected = selections.didChange.getSnapshot();
const selected = selections.didChange.getState();

if (selected.has(event.dir.id)) {
if (
event.node === event.dir ||
(selected.has(event.node.id) && selected.has(event.dir.id))
) {
return;
}

Expand Down Expand Up @@ -97,7 +100,7 @@ export default function App() {
}
});

useObservable(rovingFocus.didChange, (value) => {
useObserver(rovingFocus.didChange, (value) => {
traits.set("focused", [value]);
});

Expand Down Expand Up @@ -126,7 +129,7 @@ export default function App() {
const styles = createStyles({});
const explorerStyles = styles.one({
...[...Array(20).keys()].reduce((acc, depth) => {
acc[`.depth-${depth}`] = {
acc[`[data-exploration-depth="${depth}"]`] = {
borderStyle: "solid",
borderWidth: 1,
borderColor: "transparent",
Expand Down
47 changes: 45 additions & 2 deletions src/file-tree.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ describe("createFileTree()", () => {
const tree = createFileTree(getNodesFromMockFs);

const handle = jest.fn();
const unsubscribe = tree.flatView.subscribe(handle);
const unobserve = tree.flatView.observe(handle);

await waitForTree(tree);
expect(handle).toHaveBeenCalledTimes(0);
Expand All @@ -247,7 +247,7 @@ describe("createFileTree()", () => {
expect(handle).toHaveBeenCalledTimes(2);
expect(tree.visibleNodes.length).toBeGreaterThan(0);

unsubscribe();
unobserve();
await tree.expand(tree.getById(tree.root.nodes[0]) as Dir<any>);
expect(handle).toHaveBeenCalledTimes(2);
expect(tree.visibleNodes.length).toBeGreaterThan(0);
Expand Down Expand Up @@ -409,6 +409,27 @@ describe("createFileTree()", () => {
]);
});

it("should produce a new tree with prompt insertion", async () => {
const tree = createFileTree(getNodesFromMockFs);

await waitForTree(tree);
const node = tree.nodesById.find(
(node) => node?.data.name === "/.github"
) as Dir;
await tree.expand(node);

tree.produce(node, ({ createPrompt, insert }) => {
insert(createPrompt());
});

expect(node.nodes.length).toBe(mockFs["/.github"].length + 1);
expect(node.nodes.map((node) => tree.getById(node).data.name)).toEqual([
"",
"/.github/ISSUE_TEMPLATE.md",
"/.github/PULL_REQUEST_TEMPLATE.md",
]);
});

it("should revert a change to the draft tree", async () => {
const tree = createFileTree(getNodesFromMockFs);

Expand Down Expand Up @@ -629,6 +650,28 @@ describe("file tree actions", () => {
expect(tree.getById(tree.root.nodes[0]).data.name).toBe("bar");
});

it("should create a prompt and sort", () => {
tree.newPrompt(tree.root);
expect(tree.getById(tree.root.nodes[0]).basename).toBe("");

tree.newDir(tree.root, { name: "bar" });
expect(tree.getById(tree.root.nodes[0]).basename).toBe("");
expect(tree.getById(tree.root.nodes[0]).path).toBe("");
});

it("should create a prompt in a directory", async () => {
const tree = createFileTree(getNodesFromMockFs);

await waitForTree(tree);
const dir = tree.getById(tree.root.nodes[0]) as Dir;

await tree.expand(dir);
tree.newPrompt(dir);

expect(tree.getById(dir.nodes[0]).basename).toBe("");
expect(tree.getById(dir.nodes[0]).path).toBe("/.github");
});

it("should move a file and sort", async () => {
tree.newDir(tree.root, { name: "bar" });
tree.newDir(tree.root, { name: "foo" });
Expand Down
83 changes: 73 additions & 10 deletions src/file-tree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export function createFileTree<Meta = {}>(

const tree = new FileTree<Meta>({
async getNodes(parent) {
const factory: FileTreeFactory<Meta> = {
const factory: Omit<FileTreeFactory<Meta>, "createPrompt"> = {
createFile(data) {
return new File(parent, data);
},
Expand Down Expand Up @@ -175,6 +175,10 @@ export class FileTree<Meta = {}> extends Tree<FileTreeData<Meta>> {
createDir(data, expanded?: boolean) {
return new Dir(dir, data, expanded);
},

createPrompt() {
return new Prompt(dir, { name: "" });
},
});

return producer;
Expand All @@ -187,7 +191,7 @@ export class FileTree<Meta = {}> extends Tree<FileTreeData<Meta>> {
* @param node - The node to move
* @param to - The new parent
*/
move(node: FileTreeNode<Meta>, to: Dir<Meta>) {
move(node: File<Meta> | Dir<Meta>, to: Dir<Meta>) {
return super.move(node, to);
}

Expand Down Expand Up @@ -216,21 +220,29 @@ export class FileTree<Meta = {}> extends Tree<FileTreeData<Meta>> {
});
}

/**
* Create a new directory in a given directory.
*
* @param inDir - The directory to create the directory in
*/
newPrompt(inDir: Dir<Meta>) {
this.produce(inDir, ({ createPrompt, insert }) => {
insert(createPrompt());
});
}

/**
* Rename a node.
*
* @param node - The node to rename
* @param newName - The new name for the node
*/
rename(node: FileTreeNode<Meta>, newName: string) {
rename(node: File<Meta> | Dir<Meta>, newName: string) {
node.data.name = newName;
const parent = node.parent;

if (parent && parent.nodes) {
this.setNodes(
parent,
parent.nodes.map((id) => this.getById(id)!)
);
this.setNodes(parent, [...parent.nodes]);
}
}
}
Expand Down Expand Up @@ -264,6 +276,32 @@ export class File<Meta = {}> extends Leaf<FileTreeData<Meta>> {
}
}

export class Prompt<Meta = {}> extends Leaf<FileTreeData<Meta>> {
/**
* The parent directory of this directory
*/
get parent(): Dir<Meta> | null {
return this.parentId === -1
? null
: (nodesById[this.parentId] as Dir<Meta>);
}

get basename() {
return "";
}

/**
* The full path of the prompt
*/
get path(): string {
if (this.parentId > -1) {
return pathFx.join(this.parent!.path, this.basename);
}

return this.data.name;
}
}

export class Dir<Meta = {}> extends Branch<FileTreeData<Meta>> {
/**
* The parent directory of this directory
Expand Down Expand Up @@ -304,7 +342,28 @@ export function defaultComparator(a: FileTreeNode, b: FileTreeNode) {
return a.basename.localeCompare(b.basename);
}

return isDir(a) ? -1 : isDir(b) ? 1 : 0;
if (isPrompt(a)) {
return -1;
} else if (isPrompt(b)) {
return 1;
} else if (isDir(a)) {
return -1;
} else if (isDir(b)) {
return 1;
}

return 0;
}

/**
* Returns `true` if the given node is a prompt
*
* @param treeNode - A tree node
*/
export function isPrompt<Meta>(
treeNode: FileTreeNode<Meta>
): treeNode is Prompt<Meta> {
return treeNode.constructor === Prompt;
}

/**
Expand All @@ -325,7 +384,7 @@ export function isDir<T>(treeNode: FileTreeNode<T>): treeNode is Dir<T> {
return treeNode.constructor === Dir;
}

export type FileTreeNode<Meta = {}> = File<Meta> | Dir<Meta>;
export type FileTreeNode<Meta = {}> = File<Meta> | Dir<Meta> | Prompt<Meta>;

export type FileTreeData<Meta = {}> = {
name: string;
Expand All @@ -346,6 +405,10 @@ export type FileTreeFactory<Meta = {}> = {
* @param expanded - Should the directory be expanded by default?
*/
createDir(data: FileTreeData<Meta>, expanded?: boolean): Dir<Meta>;
/**
* Create a prompt node that can be inserted into the tree.
*/
createPrompt(): Prompt<Meta>;
};

export type GetNodes<Meta> = {
Expand All @@ -355,7 +418,7 @@ export type GetNodes<Meta> = {
* @param parent - The parent directory to get the nodes for
* @param factory - A factory to create nodes (file/dir) with
*/
(parent: Dir<Meta>, factory: FileTreeFactory<Meta>):
(parent: Dir<Meta>, factory: Omit<FileTreeFactory<Meta>, "createPrompt">):
| Promise<FileTreeNode<Meta>[]>
| FileTreeNode<Meta>[];
};
Expand Down
8 changes: 4 additions & 4 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,18 @@ export {
} from "./file-tree";
export type { FileTreeNode, FileTreeData, FileTreeFactory } from "./file-tree";
export { Node } from "./node";
export { ObservableMap, ObservableSet } from "./observable-data";
export { SubjectMap, SubjectSet } from "./observable-data";
export * as pathFx from "./path-fx";
export { observable } from "./tree/observable";
export type { Observable } from "./tree/observable";
export { subject } from "./tree/subject";
export type { Subject } from "./tree/subject";
export type { FileTreeSnapshot, WindowRef } from "./types";
export { useDnd } from "./use-dnd";
export type { DndEvent, DndProps, UseDndPlugin, UseDndConfig } from "./use-dnd";
export { useFilter } from "./use-filter";
export { useHotkeys } from "./use-hotkeys";
export { useNodePlugins } from "./use-node-plugins";
export type { NodePlugin } from "./use-node-plugins";
export { useObservable } from "./use-observable";
export { useObserver } from "./use-observer";
export { useFileTreeSnapshot } from "./use-file-tree-snapshot";
export { useTraits } from "./use-traits";
export type { TraitsProps, UseTraitsPlugin } from "./use-traits";
Expand Down
6 changes: 4 additions & 2 deletions src/node.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,10 @@ describe("<Node>", () => {
</Node>
);

expect(element.asFragment().firstChild).toHaveAttribute("id", "exp-0");
expect(element.asFragment().firstChild).toHaveClass("depth-1");
expect(element.asFragment().firstChild).toHaveAttribute(
"data-exploration-depth",
"1"
);
expect(element.asFragment().firstChild).toHaveStyle({ height: "24px" });
});

Expand Down
Loading

0 comments on commit 47bb5e8

Please sign in to comment.