Skip to content

Commit

Permalink
Create a VSCode Demo
Browse files Browse the repository at this point in the history
  • Loading branch information
jameskerr committed Sep 1, 2023
1 parent e49ac1d commit f7b0450
Show file tree
Hide file tree
Showing 10 changed files with 188 additions and 9 deletions.
1 change: 1 addition & 0 deletions packages/react-arborist/src/hooks/use-fresh-node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export function useFreshNode<T>(index: number) {
const original = tree.at(index);
if (!original) throw new Error(`Could not find node for index: ${index}`);

console.log(original.state.willDropInAncestor, original.id);
return useMemo(() => {
const fresh = original.clone();
tree.visibleNodes[index] = fresh; // sneaky
Expand Down
15 changes: 15 additions & 0 deletions packages/react-arborist/src/interfaces/node-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,10 @@ export class NodeApi<T = any> {
return this.tree.willReceiveDrop(this.id);
}

get willDropInAncestor() {
return this.tree.willDropInAncestor(this.id);
}

get state() {
return {
isClosed: this.isClosed,
Expand All @@ -104,6 +108,7 @@ export class NodeApi<T = any> {
isSelectedEnd: this.isSelectedEnd,
isSelectedStart: this.isSelectedStart,
willReceiveDrop: this.willReceiveDrop,
willDropInAncestor: this.willDropInAncestor,
};
}

Expand All @@ -130,6 +135,16 @@ export class NodeApi<T = any> {
return this.parent?.children![i + 1] ?? null;
}

isAncestorOf(node: NodeApi<T> | null) {
if (!node) return false;
let ancestor: NodeApi<T> | null = node;
while (ancestor) {
if (ancestor.id === this.id) return true;
ancestor = ancestor.parent;
}
return false;
}

select() {
this.tree.select(this);
}
Expand Down
9 changes: 9 additions & 0 deletions packages/react-arborist/src/interfaces/tree-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -609,6 +609,15 @@ export class TreeApi<T> {
return id === this.state.nodes.drag.idWillReceiveDrop;
}

willDropInAncestor(identity: Identity) {
const id = identifyNull(identity);
if (!id) return false;
const parent = this.get(this.state.nodes.drag.dropParentId);
const node = this.get(id);
if (!parent) return false;
return parent.isAncestorOf(node);
}

/* Tree Event Handlers */

onFocus() {
Expand Down
16 changes: 13 additions & 3 deletions packages/react-arborist/src/state/drag-slice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,23 @@ import { actions as dnd } from "./dnd-slice";

/* Types */

export type DragSlice = { id: string | null; idWillReceiveDrop: string | null };
export type DragSlice = {
id: string | null;
idWillReceiveDrop: string | null;
dropParentId: string | null;
};

/* Reducer */

export function reducer(
state: DragSlice = { id: null, idWillReceiveDrop: null },
state: DragSlice = { id: null, idWillReceiveDrop: null, dropParentId: null },
action: ActionTypes<typeof dnd>
) {
switch (action.type) {
case "DND_DRAG_START":
return { ...state, id: action.id };
case "DND_DRAG_END":
return { ...state, id: null };
return { ...state, id: null, dropParentId: null };
case "DND_CURSOR":
const c = action.cursor;
if (c.type === "highlight" && c.id !== state.idWillReceiveDrop) {
Expand All @@ -25,6 +29,12 @@ export function reducer(
} else {
return state;
}
case "DND_HOVERING":
if (action.parentId !== state.dropParentId) {
return { ...state, dropParentId: action.parentId };
} else {
return state;
}
default:
return state;
}
Expand Down
2 changes: 1 addition & 1 deletion packages/react-arborist/src/state/initial.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export const initialState = (props?: TreeProps<any>): RootState => ({
open: { filtered: {}, unfiltered: props?.initialOpenState ?? {} },
focus: { id: null, treeFocused: false },
edit: { id: null },
drag: { id: null, idWillReceiveDrop: null },
drag: { id: null, idWillReceiveDrop: null, dropParentId: null },
selection: { ids: new Set(), anchor: null, mostRecent: null },
},
dnd: {
Expand Down
4 changes: 2 additions & 2 deletions packages/showcase/next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@
const nextConfig = {
reactStrictMode: true,
swcMinify: true,
}
};

module.exports = nextConfig
module.exports = nextConfig;
2 changes: 1 addition & 1 deletion packages/showcase/pages/cities.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ export default function Cities() {
function Node({ node, style, dragHandle }: NodeRendererProps<Data>) {
const Icon = node.isInternal ? BsMapFill : BsGeoFill;
const indentSize = Number.parseFloat(`${style.paddingLeft || 0}`);

return (
<div
ref={dragHandle}
Expand Down
97 changes: 97 additions & 0 deletions packages/showcase/pages/vscode.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import useResizeObserver from "use-resize-observer";
import styles from "../styles/vscode.module.css";
import { CursorProps, NodeRendererProps, Tree } from "react-arborist";
import { SiTypescript } from "react-icons/si";
import { MdFolder } from "react-icons/md";
import clsx from "clsx";

let id = 1;

type Entry = { name: string; id: string; children?: Entry[] };

const nextId = () => (id++).toString();
const file = (name: string) => ({ name, id: nextId() });
const folder = (name: string, ...children: Entry[]) => ({
name,
id: nextId(),
children,
});

const structure = [
folder(
"src",
file("index.ts"),
folder(
"lib",
file("index.ts"),
file("worker.ts"),
file("utils.ts"),
file("model.ts")
),
folder(
"ui",
file("button.ts"),
file("form.ts"),
file("table.ts"),
folder(
"demo",
file("welcome.ts"),
file("example.ts"),
file("container.ts")
)
)
),
];

function sortChildren(node: Entry): Entry {
if (!node.children) return node;
const copy = [...node.children];
copy.sort((a, b) => {
if (!!a.children && !b.children) return -1;
if (!!b.children && !a.children) return 1;
return a.name < b.name ? -1 : 1;
});
const children = copy.map(sortChildren);
return { ...node, children };
}

function useTreeSort(data: Entry[]) {
return data.map(sortChildren);
}

function Node({ style, node, dragHandle }: NodeRendererProps<Entry>) {
return (
<div
style={style}
className={clsx(styles.node, node.state)}
ref={dragHandle}
>
{node.isInternal ? <MdFolder /> : <SiTypescript />}
{node.data.name} {node.id}
</div>
);
}

export default function VSCodeDemoPage() {
const { width, height, ref } = useResizeObserver();

const data = useTreeSort(structure);

return (
<div className={styles.root}>
<aside className={styles.sidebar} ref={ref}>
<Tree
data={data}
width={width}
height={height}
rowHeight={22}
onMove={() => {}}
renderCursor={() => null}
>
{Node}
</Tree>
</aside>
<main className={styles.main}></main>
</div>
);
}
47 changes: 47 additions & 0 deletions packages/showcase/styles/vscode.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
.root {
display: grid;
grid-template-columns: 360px 1fr;
grid-template-rows: 1fr;
height: 100vh;
width: 100vw;
}

.node {
font-size: 13px;
display: grid;
grid-template-columns: auto 1fr;
gap: 10px;
cursor: default;
height: 100%;
align-items: center;;
}

.sidebar {
background: #192226;
color: rgb(95, 122, 135);
}

.main {
background: #253238;
}

.node:global(.isInternal) svg {
fill: #80CBC4;
}

.node:global(.isLeaf) svg {
width: 10px;
fill: #3865BD;
}

.node:global(.willReceiveDrop) {
background: blue;
}

.node:hover {
color: white;
}

.node:global(.willDropInAncestor) {
background: lightskyblue;
}
4 changes: 2 additions & 2 deletions packages/showcase/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true
"incremental": true,
},
"include": [
"next-env.d.ts",
Expand All @@ -26,5 +26,5 @@
],
"exclude": [
"node_modules"
]
],
}

0 comments on commit f7b0450

Please sign in to comment.