Skip to content
This repository has been archived by the owner on Nov 5, 2024. It is now read-only.

Parse entire tree when dropping a node #13

Merged
merged 7 commits into from
Apr 12, 2020
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 40 additions & 4 deletions packages/core/src/editor/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
Options,
NodeEvents,
SerializedNodeData,
Tree,
} from "../interfaces";
import {
ERROR_INVALID_NODEID,
Expand Down Expand Up @@ -89,6 +90,41 @@ export const Actions = (
addNodeToParentAtIndex(node, parent, index);
},

/**
* Given a tree, it adds it at the correct position among the node children
*
* @param node
* @param parentId
* @param index
*/
addTreeAtIndex(tree: Tree, parentId: NodeId, index: number) {
const parent = getParentAndValidate(parentId);

invariant(
index > -1 && index <= parent.data.nodes.length,
"AddTreeAtIndex: index must be between 0 and parentNodeLength inclusive"
);
const node = tree.nodes[tree.rootNodeId];
// first, add the node
this.addNodeAtIndex(node, parentId, index);
if (!node.data.nodes) {
return;
}
// then add all the children
const childNodes = node.data.nodes.map((nodeId) => tree.nodes[nodeId]);
const addChild = (childId, index) =>
this.addTreeAtIndex(
{ rootNodeId: childId, nodes: tree.nodes },
node.id,
index
);

const childToAdd = [...node.data.nodes];
// we need to deep clone here...
node.data.nodes = [];
childToAdd.forEach(addChild);
},

/**
* Delete a Node
* @param id
Expand Down Expand Up @@ -243,7 +279,7 @@ export const Actions = (
const rehydratedNodes = Object.keys(dehydratedNodes).reduce(
(accum: Nodes, id: string) => {
const {
type: Comp,
type: Component,
props,
parent,
nodes,
Expand All @@ -253,17 +289,17 @@ export const Actions = (
custom,
} = deserializeNode(dehydratedNodes[id], state.options.resolver);

if (!Comp) {
if (!Component) {
return accum;
}

accum[id] = query.createNode(createElement(Comp, props), {
accum[id] = query.createNode(createElement(Component, props), {
id,
data: {
...(isCanvas && { isCanvas }),
...(hidden && { hidden }),
parent,
...(isCanvas && { nodes }),
...{ nodes },
...(_childCanvas && { _childCanvas }),
custom,
},
Expand Down
50 changes: 30 additions & 20 deletions packages/core/src/editor/query.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
Options,
NodeInfo,
SerializedNodeData,
Tree,
} from "../interfaces";
import { serializeNode } from "../utils/serializeNode";
import { resolveComponent } from "../utils/resolveComponent";
Expand All @@ -28,17 +29,18 @@ import {
ERROR_INVALID_NODE_ID,
} from "@candulabs/craft-utils";
import findPosition from "../events/findPosition";
import { mergeTrees } from "../utils/mergeTrees";
import { getDeepNodes } from "../utils/getDeepNodes";
import { transformJSXToNode } from "../utils/transformJSX";

export function QueryMethods(Editor: EditorState) {
const options = Editor && Editor.options;
export function QueryMethods(state: EditorState) {
const options = state && state.options;

const _: () => QueryCallbacksFor<typeof QueryMethods> = () =>
QueryMethods(Editor);
QueryMethods(state);

const getNodeFromIdOrNode = (node: NodeId | Node) =>
typeof node === "string" ? Editor.nodes[node] : node;
typeof node === "string" ? state.nodes[node] : node;

return {
/**
Expand All @@ -57,6 +59,17 @@ export function QueryMethods(Editor: EditorState) {
return node;
},

parseTreeFromReactNode(reactNode: React.ReactElement): Tree | undefined {
const node = this.createNode(reactNode);
const childrenNodes = React.Children.map(
(reactNode.props && reactNode.props.children) || [],
(child) =>
React.isValidElement(child) && this.parseTreeFromReactNode(child)
).filter((children) => !!children);

return mergeTrees(node, childrenNodes);
},

/**
* Determine the best possible location to drop the source Node relative to the target Node
*/
Expand All @@ -65,25 +78,24 @@ export function QueryMethods(Editor: EditorState) {
target: NodeId,
pos: { x: number; y: number },
nodesToDOM: (node: Node) => HTMLElement = (node) =>
Editor.nodes[node.id].dom
state.nodes[node.id].dom
) => {
if (source === target) return;
const sourceNodeFromId =
typeof source == "string" && Editor.nodes[source],
targetNode = Editor.nodes[target],
const sourceNodeFromId = typeof source == "string" && state.nodes[source],
targetNode = state.nodes[target],
isTargetCanvas = _().node(targetNode.id).isCanvas();

const targetParent = isTargetCanvas
? targetNode
: Editor.nodes[targetNode.data.parent];
: state.nodes[targetNode.data.parent];

const targetParentNodes = targetParent.data._childCanvas
? Object.values(targetParent.data._childCanvas)
: targetParent.data.nodes || [];

const dimensionsInContainer = targetParentNodes
? targetParentNodes.reduce((result, id: NodeId) => {
const dom = nodesToDOM(Editor.nodes[id]);
const dom = nodesToDOM(state.nodes[id]);
if (dom) {
const info: NodeInfo = {
id,
Expand All @@ -104,7 +116,7 @@ export function QueryMethods(Editor: EditorState) {
);
const currentNode =
targetParentNodes.length &&
Editor.nodes[targetParentNodes[dropAction.index]];
state.nodes[targetParentNodes[dropAction.index]];

const output: Indicator = {
placement: {
Expand Down Expand Up @@ -137,11 +149,9 @@ export function QueryMethods(Editor: EditorState) {
},

getState(): Record<NodeId, SerializedNodeData> {
return Object.keys(Editor.nodes).reduce((result: any, id: NodeId) => {
const {
data: { ...data },
} = Editor.nodes[id];
result[id] = serializeNode({ ...data }, options.resolver);
return Object.keys(state.nodes).reduce((result: any, id: NodeId) => {
const { data } = state.nodes[id];
result[id] = serializeNode(data, options.resolver);
return result;
}, {});
},
Expand All @@ -153,7 +163,7 @@ export function QueryMethods(Editor: EditorState) {
node(id: NodeId) {
invariant(typeof id == "string", ERROR_INVALID_NODE_ID);

const node = Editor.nodes[id];
const node = state.nodes[id];
const nodeQuery = _().node;

return {
Expand All @@ -176,7 +186,7 @@ export function QueryMethods(Editor: EditorState) {
return result;
},
decendants: (deep = false) => {
return getDeepNodes(Editor.nodes, id, deep);
return getDeepNodes(state.nodes, id, deep);
},
isDraggable: (onError?: (err: string) => void) => {
try {
Expand Down Expand Up @@ -206,12 +216,12 @@ export function QueryMethods(Editor: EditorState) {
const targetNode = getNodeFromIdOrNode(target);

const currentParentNode =
targetNode.data.parent && Editor.nodes[targetNode.data.parent],
targetNode.data.parent && state.nodes[targetNode.data.parent],
newParentNode = node;

invariant(
currentParentNode ||
(!currentParentNode && !Editor.nodes[targetNode.id]),
(!currentParentNode && !state.nodes[targetNode.id]),
ERROR_DUPLICATE_NODEID
);

Expand Down
38 changes: 38 additions & 0 deletions packages/core/src/editor/tests/actions.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import cloneDeep from "lodash/cloneDeep";
import * as actions from "../actions";
import { produce } from "immer";
import { QueryMethods } from "../../editor/query";
Expand Down Expand Up @@ -74,6 +75,43 @@ describe("actions.addNodeAtIndex", () => {
});
});

describe("actions.addTreeAtIndex", () => {
it("should throw if we give a parentId that doesnt exist", () => {
expect(() =>
Actions(emptyState)((actions) => actions.addTreeAtIndex(leafNode))
).toThrow();
});
it("should throw if we give an invalid index", () => {
const state = Actions(documentState);
expect(() =>
state((actions) => actions.addTreeAtIndex(leafNode, rootNode.id, -1))
).toThrow();
expect(() =>
state((actions) => actions.addTreeAtIndex(leafNode, rootNode.id, 1))
).toThrow();
});
it("should be able to add a single node at 0", () => {
const tree = {
rootNodeId: leafNode.id,
nodes: { [leafNode.id]: leafNode },
};
const newState = Actions(documentState)((actions) =>
actions.addTreeAtIndex(tree, rootNode.id, 0)
);
expect(newState).toEqual(documentWithLeafState);
});
it("should be able to add a larger tree", () => {
const tree = {
rootNodeId: card.id,
nodes: cloneDeep(documentWithCardState.nodes),
};
const newState = Actions(documentState)((actions) =>
actions.addTreeAtIndex(tree, rootNode.id, 0)
);
expect(newState).toEqual(documentWithCardState);
});
});

describe("actions.delete", () => {
it("should throw if you try to a non existing node", () => {
expect(() => Actions(emptyState)((actions) => actions.delete(leafNode.id)));
Expand Down
Loading