Skip to content

Commit

Permalink
Add prepending node feature
Browse files Browse the repository at this point in the history
  • Loading branch information
yoychen committed Apr 1, 2020
1 parent 773fb74 commit 91527ba
Show file tree
Hide file tree
Showing 6 changed files with 192 additions and 31 deletions.
43 changes: 29 additions & 14 deletions src/core/Indicator.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,18 @@
function getPadding(e) {
const {
paddingTop, paddingLeft, paddingRight, paddingBottom,
} = getComputedStyle(e);
const padding = {
paddingTop, paddingLeft, paddingRight, paddingBottom,
};

Object.keys(padding).forEach((key) => {
padding[key] = parseInt(padding[key].slice(0, -2), 10);
});

return padding;
}

class Indicator {
constructor(barSize = 2) {
this.barSize = barSize;
Expand Down Expand Up @@ -55,27 +70,27 @@ class Indicator {
pointInside(element) {
this.showIndicator();

function getPadding(e) {
const {
paddingTop, paddingLeft, paddingRight, paddingBottom,
} = getComputedStyle(e);
const padding = {
paddingTop, paddingLeft, paddingRight, paddingBottom,
};
const padding = getPadding(element);
const {
top, left, width, height,
} = element.getBoundingClientRect();

Object.keys(padding).forEach((key) => {
padding[key] = parseInt(padding[key].slice(0, -2), 10);
});
this.position.top = top + height - padding.paddingBottom;
this.position.left = left + padding.paddingLeft;

return padding;
}
this.size.width = width - padding.paddingLeft - padding.paddingRight;
this.size.height = this.barSize;
}

pointInsideTop(element) {
this.showIndicator();

const padding = getPadding(element);
const {
top, left, width, height,
top, left, width,
} = element.getBoundingClientRect();

this.position.top = top + height - padding.paddingBottom;
this.position.top = top + padding.paddingTop;
this.position.left = left + padding.paddingLeft;

this.size.width = width - padding.paddingLeft - padding.paddingRight;
Expand Down
12 changes: 12 additions & 0 deletions src/core/Node.js
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,18 @@ class Node {
incommingNode.parent = this;
}

prepend(incommingNode) {
if (!this.isDroppable(incommingNode)) {
throw new Error(`${this.componentName} is not droppable with the incommingNode - ${incommingNode.componentName}.`);
}

incommingNode.makeOrphan();

this.children.splice(0, 0, incommingNode);
// eslint-disable-next-line no-param-reassign
incommingNode.parent = this;
}

canBeSibling(targetNode) {
if (targetNode === this) {
return false;
Expand Down
25 changes: 23 additions & 2 deletions src/core/services/NodeService.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,16 @@ class NodeService {
return false;
}

onTopHelf({ clientY }) {
const { top, height } = this.getElementBoundingClientRect();

if (clientY < (top + (height / 2))) {
return true;
}

return false;
}

onEdge({ clientX, clientY }, edgeThickness = 8) {
const {
top, left, width, height,
Expand Down Expand Up @@ -67,8 +77,13 @@ class NodeService {

if (this.onEdge({ clientX, clientY })) {
this.handleElementDragOver(cursor);
return;
}

editor.indicator.setIsForbidden(!this.getCurrentNode().isDroppable(editor.draggedNode));
if (this.onTopHelf(cursor)) {
editor.indicator.pointInsideTop(this.getElement());
} else {
editor.indicator.setIsForbidden(!this.getCurrentNode().isDroppable(editor.draggedNode));
editor.indicator.pointInside(this.getElement());
}
}
Expand Down Expand Up @@ -105,7 +120,13 @@ class NodeService {
return;
}

if (currentNode.isDroppable(draggedNode)) {
if (!currentNode.isDroppable(draggedNode)) {
return;
}

if (this.onTopHelf(cursor)) {
currentNode.prepend(draggedNode);
} else {
currentNode.append(draggedNode);
}
}
Expand Down
47 changes: 35 additions & 12 deletions tests/unit/core/Indicator.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ describe('setIsForbidden', () => {
});
});

function createElementStub() {
function createStubElement() {
return {
getBoundingClientRect: () => ({
top: 50,
Expand All @@ -37,7 +37,7 @@ function createElementStub() {
describe('pointBefore', () => {
it('shows indicator on the left side of the element', () => {
const indicator = createIndicator();
const element = createElementStub();
const element = createStubElement();

indicator.pointBefore(element);

Expand All @@ -56,7 +56,7 @@ describe('pointBefore', () => {
describe('pointAfter', () => {
it('shows indicator on the right side of the element', () => {
const indicator = createIndicator();
const element = createElementStub();
const element = createStubElement();

indicator.pointAfter(element);

Expand All @@ -72,18 +72,21 @@ describe('pointAfter', () => {
});
});

function stubGetComputedStyle(style) {
const defaultStyle = {
paddingTop: '2px',
paddingLeft: '20px',
paddingRight: '25px',
paddingBottom: '15px',
};
window.getComputedStyle = () => style || defaultStyle;
}

describe('pointInside', () => {
it('shows indicator on the bottom of the content of the element', () => {
const indicator = createIndicator();
const element = createElementStub();

const style = {
paddingTop: '2px',
paddingLeft: '20px',
paddingRight: '25px',
paddingBottom: '15px',
};
window.getComputedStyle = () => style;
const element = createStubElement();
stubGetComputedStyle();

indicator.pointInside(element);

Expand All @@ -98,3 +101,23 @@ describe('pointInside', () => {
});
});
});

describe('pointInsideTop', () => {
it('shows indicator on the top of the content of the element', () => {
const indicator = createIndicator();
const element = createStubElement();
stubGetComputedStyle();

indicator.pointInsideTop(element);

expect(indicator.show).toBe(true);
expect(indicator.position).toStrictEqual({
top: 52, // element.offsetTop + element.paddingTop
left: 60, // element.offsetLeft + element.paddingLeft
});
expect(indicator.size).toStrictEqual({
width: 555, // element.offsetWidth - element.paddingLeft - element.paddingRight
height: 2,
});
});
});
41 changes: 41 additions & 0 deletions tests/unit/core/Node.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,47 @@ describe('append', () => {
});
});

describe('prepend', () => {
it('prepends the incomming node into its children when it is droppable', () => {
const node = createNode();
node.children = [createNode(), createNode()];
const incommingNode = createNode();

node.isDroppable = () => true;

node.prepend(incommingNode);

expect(node.children.length).toEqual(3);
expect(node.children[0]).toEqual(incommingNode);
expect(incommingNode.parent).toEqual(node);
});

it('calls makeOrphan() of the incomming node when prepends the incomming node into its children', () => {
const node = createNode();
const incommingNode = createNode();

node.isDroppable = () => true;

incommingNode.makeOrphan = jest.fn();

node.prepend(incommingNode);

expect(incommingNode.makeOrphan.mock.calls.length).toBe(1);
});

it('throws error when it is not droppable', () => {
const node = createNode();
const incommingNode = createNode();

node.isDroppable = () => false;

expect(() => node.prepend(incommingNode)).toThrow();

expect(node.children).not.toEqual(expect.arrayContaining([incommingNode]));
expect(incommingNode.parent).not.toEqual(node);
});
});

describe('canBeSibling', () => {
it('returns false when the parent of the target node does not exist', () => {
const node = createNode();
Expand Down
55 changes: 52 additions & 3 deletions tests/unit/core/services/NodeService.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,26 @@ describe('onLeftHelf', () => {
});
});

describe('onTopHelf', () => {
it('returns true when the cursor is on the top half of element', () => {
const nodeService = createNodeService();
const cursor = createCursor(300, 250);

const onTopHelf = nodeService.onTopHelf(cursor);

expect(onTopHelf).toBe(true);
});

it('returns false when the cursor is not on the top half of element', () => {
const nodeService = createNodeService();
const cursor = createCursor(300, 301);

const onTopHelf = nodeService.onTopHelf(cursor);

expect(onTopHelf).toBe(false);
});
});

describe('onEdge', () => {
it('returns true when the cursor is on the edge of element', () => {
const nodeService = createNodeService();
Expand Down Expand Up @@ -84,16 +104,29 @@ describe('handleCanvasDragOver', () => {
expect(nodeService.handleElementDragOver.mock.calls.length).toBe(1);
});

it('calls pointInside() when the cursor is not on the edge of element', () => {
it('calls pointInside() when the cursor is not on the edge of element and not on the top half of element', () => {
const nodeService = createNodeService();
nodeService.onEdge = () => false;
nodeService.onTopHelf = () => false;
nodeService.getEditor().indicator.pointInside = jest.fn();
const cursor = createCursor(210, 210);

nodeService.handleCanvasDragOver(cursor);

expect(nodeService.getEditor().indicator.pointInside.mock.calls.length).toBe(1);
});

it('calls pointInsideTop() when the cursor is not on the edge of element and on the top half of element', () => {
const nodeService = createNodeService();
nodeService.onEdge = () => false;
nodeService.onTopHelf = () => true;
nodeService.getEditor().indicator.pointInsideTop = jest.fn();
const cursor = createCursor(210, 210);

nodeService.handleCanvasDragOver(cursor);

expect(nodeService.getEditor().indicator.pointInsideTop.mock.calls.length).toBe(1);
});
});

describe('handleElementDragOver', () => {
Expand Down Expand Up @@ -171,9 +204,10 @@ describe('handleElementDrop', () => {
});

describe('handleCanvasDrop', () => {
it('calls append() when the cursor is not on the edge of element and the current node is droppable', () => {
it('calls append() when the cursor is not on the edge of element and not on the top half of element and the current node is droppable', () => {
const nodeService = createNodeService();
nodeService.onEdge = () => false;
nodeService.onTopHelf = () => false;
nodeService.getCurrentNode().isDroppable = () => true;
nodeService.getCurrentNode().append = jest.fn();
const cursor = createCursor(210, 210);
Expand All @@ -183,16 +217,31 @@ describe('handleCanvasDrop', () => {
expect(nodeService.getCurrentNode().append.mock.calls.length).toBe(1);
});

it('does not call append() when the cursor is not on the edge of element and the current node is not droppable', () => {
it('calls prepend() when the cursor is not on the edge of element and on the top half of element and the current node is droppable', () => {
const nodeService = createNodeService();
nodeService.onEdge = () => false;
nodeService.onTopHelf = () => true;
nodeService.getCurrentNode().isDroppable = () => true;
nodeService.getCurrentNode().prepend = jest.fn();
const cursor = createCursor(210, 210);

nodeService.handleCanvasDrop(cursor);

expect(nodeService.getCurrentNode().prepend.mock.calls.length).toBe(1);
});

it('does nothing when the cursor is not on the edge of element and the current node is not droppable', () => {
const nodeService = createNodeService();
nodeService.onEdge = () => false;
nodeService.getCurrentNode().isDroppable = () => false;
nodeService.getCurrentNode().append = jest.fn();
nodeService.getCurrentNode().prepend = jest.fn();
const cursor = createCursor(210, 210);

nodeService.handleCanvasDrop(cursor);

expect(nodeService.getCurrentNode().append.mock.calls.length).toBe(0);
expect(nodeService.getCurrentNode().prepend.mock.calls.length).toBe(0);
});

it('calls handleElementDrop() when the cursor is on the edge of element', () => {
Expand Down

0 comments on commit 91527ba

Please sign in to comment.