Skip to content

Commit

Permalink
feat: support to subscribe the drop event in tree (#450)
Browse files Browse the repository at this point in the history
* feat: support to subscribe the drop event in tree

* test: improve the test about dragging

* test: improve test for folder tree service
  • Loading branch information
mortalYoung authored Sep 29, 2021
1 parent f8fdbcc commit 8626600
Show file tree
Hide file tree
Showing 9 changed files with 147 additions and 108 deletions.
107 changes: 79 additions & 28 deletions src/components/tree/__tests__/tree.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,6 @@ const mockData: ITreeNodeItemProps[] = [
},
];

async function dragExpect(fn: jest.Mock, result: any) {
await waitFor(() => {
expect(fn).toBeCalled();
expect(fn.mock.calls[0][0]).toEqual(result);
});
}

describe('Test the Tree component', () => {
afterEach(cleanup);

Expand Down Expand Up @@ -198,11 +191,11 @@ describe('Test the Tree component', () => {
expect(await findByTitle('test2')).toBeInTheDocument();
});

test('Should support to sort via drag', async () => {
test('Should NOT support to sort via drag', async () => {
const data = [
{ id: '1', name: 'test1' },
{ id: '2', name: 'test2' },
{ id: '3', name: 'test3' },
{ id: '1', name: 'test1', isLeaf: true },
{ id: '2', name: 'test2', isLeaf: true },
{ id: '3', name: 'test3', isLeaf: true },
];
const mockFn = jest.fn();
const { findByTitle } = render(
Expand All @@ -214,24 +207,47 @@ describe('Test the Tree component', () => {
await findByTitle('test1')
);

await dragExpect(mockFn, [
{ id: '1', name: 'test1' },
{ id: '3', name: 'test3' },
{ id: '2', name: 'test2' },
]);
expect(mockFn).not.toBeCalled();
});

test('Should support to drag into children', async () => {
test('Should NOT darg to the its parent node', async () => {
const data = [
{
id: '1',
name: 'test1',
children: [{ id: '1-1', name: 'test1-1' }],
isLeaf: false,
children: [{ id: '1-1', name: 'test1-1', isLeaf: true }],
},
];
const mockFn = jest.fn();
const { findByTitle } = render(
<TreeView
draggable
onDropTree={mockFn}
defaultExpandAll
data={data}
/>
);

dragToTargetNode(
await findByTitle('test1-1'),
await findByTitle('test1')
);

expect(mockFn).not.toBeCalled();
});

test('Should support to drag into children', async () => {
const source = { id: '2', name: 'test2', isLeaf: true };
const target = { id: '1-1', name: 'test1-1', isLeaf: false };
const data = [
{
id: '2',
name: 'test2',
id: '1',
name: 'test1',
isLeaf: false,
children: [target],
},
source,
];
const mockFn = jest.fn();
const { findByTitle } = render(
Expand All @@ -244,19 +260,54 @@ describe('Test the Tree component', () => {
);

dragToTargetNode(
await findByTitle('test2'),
await findByTitle('test1-1')
await findByTitle(source.name),
await findByTitle(target.name)
);

await dragExpect(mockFn, [
expect(mockFn).toBeCalled();
expect(mockFn.mock.calls[0][0]).toEqual(source);
expect(mockFn.mock.calls[0][1]).toEqual(target);
});

test('Should support to drag to the folder rather than a file', async () => {
const source = { id: '2-1', name: 'test2-1', isLeaf: true };
const target = { id: '1-1', name: 'test1-1', isLeaf: true };
const data = [
{
id: '1',
name: 'test1',
children: [
{ id: '1-1', name: 'test1-1' },
{ id: '2', name: 'test2' },
],
isLeaf: false,
children: [target],
},
]);
{
id: '2',
name: 'test2',
isLeaf: false,
children: [source],
},
];
const mockFn = jest.fn();
const { findByTitle } = render(
<TreeView
draggable
onDropTree={mockFn}
defaultExpandAll
data={data}
/>
);

dragToTargetNode(
await findByTitle(source.name),
await findByTitle(target.name)
);

expect(mockFn).toBeCalled();
expect(mockFn.mock.calls[0][0]).toEqual(source);
expect(mockFn.mock.calls[0][1]).toEqual({
id: '1',
name: 'test1',
isLeaf: false,
children: [target],
});
});
});
84 changes: 27 additions & 57 deletions src/components/tree/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { prefixClaName, classNames } from 'mo/common/className';
import type { DataNode } from 'rc-tree/lib/interface';
import { FileTypes } from 'mo/model';
import type { LoadEventData } from 'mo/controller';
import { TreeViewUtil } from 'mo/services/helper';

export interface ITreeNodeItemProps {
disabled?: boolean;
Expand All @@ -26,7 +27,7 @@ export interface ITreeProps extends Partial<TreeProps> {
index: number,
isLeaf: boolean
) => JSX.Element | string;
onDropTree?(treeNode: ITreeNodeItemProps[]): void;
onDropTree?(source: ITreeNodeItemProps, target: ITreeNodeItemProps): void;
onLoadData?: (treeNode: LoadEventData) => Promise<void>;
}

Expand All @@ -46,66 +47,35 @@ const TreeView = ({

const onDrop = (info) => {
if (!draggable) return;
const dropId = info.node.data.id;
const dragId = info.dragNode.data.id;
const dropPos = info.node.pos.split('-');
const dropPosition =
info.dropPosition - Number(dropPos[dropPos.length - 1]);

const loopTree = (
data: ITreeNodeItemProps[],
key: string,
callback: (
item: ITreeNodeItemProps,
index: number,
arr: ITreeNodeItemProps[]
) => void
) => {
data.forEach((item, index, arr) => {
if (item.id === key) {
return callback(item, index, arr);
}
if (item.children) {
return loopTree(item.children, key, callback);
}
});
};
const treeData = [...data];

let dragObj;
loopTree(treeData, dragId, (item, index, arr) => {
arr.splice(index, 1);
dragObj = item;
const source = info.dragNode;
const target = info.node;
const treeViewUtil = new TreeViewUtil({
id: Number.MAX_SAFE_INTEGER,
children: data,
});

if (!info.dropToGap) {
loopTree(treeData, dropId, (item) => {
item.children = item.children || [];
item.children.push(dragObj);
});
} else if (
(info.node.data.children || []).length > 0 &&
info.node.expanded &&
dropPosition === 1
) {
loopTree(treeData, dropId, (item) => {
item.children = item.children || [];
item.children.unshift(dragObj);
});
if (target.data.isLeaf) {
// Can't drag into a file, so the target would to be the parent of this target
const obj = treeViewUtil.indexes[target.data.id];
const targetParentId = obj.parent!;

const sourceParentId = treeViewUtil.indexes[source.data.id].parent;
// Can't drag under same folder
if (targetParentId === sourceParentId) {
return;
}
onDropTree?.(
source.data,
treeViewUtil.indexes[targetParentId].node!
);
} else {
let ar;
let i;
loopTree(treeData, dropId, (item, index, arr) => {
ar = arr;
i = index;
});
if (dropPosition === -1) {
ar.splice(i, 0, dragObj);
} else {
ar.splice(i + 1, 0, dragObj);
const sourceParentId = treeViewUtil.indexes[source.data.id].parent;
// Can't drag to the parent node
if (sourceParentId === target.data.id) {
return;
}

onDropTree?.(source.data, target.data);
}
onDropTree?.(treeData);
};

const renderTreeNodes = (
Expand Down
13 changes: 10 additions & 3 deletions src/controller/explorer/folderTree.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,10 @@ export interface IFolderTreeController {
) => void;
readonly onUpdateFileName?: (file: ITreeNodeItemProps) => void;
readonly onSelectFile?: (file: ITreeNodeItemProps) => void;
readonly onDropTree?: (treeNode: ITreeNodeItemProps[]) => void;
readonly onDropTree?: (
source: ITreeNodeItemProps,
target: ITreeNodeItemProps
) => void;
readonly onLoadData?: (treeNode: LoadEventData) => Promise<void>;
readonly onRightClick?: (treeNode: ITreeNodeItemProps) => IMenuItemProps[];
}
Expand Down Expand Up @@ -124,8 +127,12 @@ export class FolderTreeController
return menus;
};

public readonly onDropTree = (treeNode: ITreeNodeItemProps[]) => {
this.folderTreeService.onDropTree(treeNode);
public readonly onDropTree = (
source: ITreeNodeItemProps,
target: ITreeNodeItemProps
) => {
// this.folderTreeService.onDropTree(treeNode);
this.emit(FolderTreeEvent.onDrop, source, target);
};

public onUpdateFileName = (file: ITreeNodeItemProps) => {
Expand Down
1 change: 1 addition & 0 deletions src/model/workbench/explorer/folderTree.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export enum FolderTreeEvent {
onContextMenuClick = 'folderTree.onContextMenuClick',
onCreate = 'folderTree.onCreate',
onLoadData = 'folderTree.onLoadData',
onDrop = 'folderTree.onDrop',
}

export interface IFolderInputEvent {
Expand Down
11 changes: 9 additions & 2 deletions src/services/workbench/__tests__/folderTreeService.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,14 +155,14 @@ describe('Test StatusBarService', () => {
});
});

test('Should support to right click', () => {
test('Should support to create event', () => {
expectFnCalled((fn) => {
folderTreeService.onCreate(fn);
folderTreeService.emit(FolderTreeEvent.onCreate);
});
});

test('Should support to right click', () => {
test('Should support to contextMenu event', () => {
expectFnCalled((fn) => {
folderTreeService.onContextMenu(fn);
folderTreeService.emit(FolderTreeEvent.onContextMenuClick);
Expand All @@ -175,4 +175,11 @@ describe('Test StatusBarService', () => {
folderTreeService.emit(FolderTreeEvent.onLoadData);
});
});

test('Should support to subscribe drop tree event', () => {
expectFnCalled((fn) => {
folderTreeService.onDropTree(fn);
folderTreeService.emit(FolderTreeEvent.onDrop);
});
});
});
20 changes: 13 additions & 7 deletions src/services/workbench/explorer/folderTreeService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,12 @@ export interface IFolderTreeService extends Component<IFolderTree> {
* Listen to drop event
* @param treeData
*/
onDropTree(treeData: ITreeNodeItemProps[]): void;
onDropTree(
callback: (
source: ITreeNodeItemProps,
target: ITreeNodeItemProps
) => void
): void;
/**
* Listen to right click event
* @param callback
Expand Down Expand Up @@ -345,12 +350,13 @@ export class FolderTreeService
this.subscribe(FolderTreeEvent.onSelectFile, callback);
}

public onDropTree = (treeData: ITreeNodeItemProps[]) => {
this.setState({
folderTree: Object.assign(this.state.folderTree?.data, {
data: treeData,
}),
});
public onDropTree = (
callback: (
source: ITreeNodeItemProps,
target: ITreeNodeItemProps
) => void
) => {
this.subscribe(FolderTreeEvent.onDrop, callback);
};

public onRightClick = (
Expand Down
8 changes: 2 additions & 6 deletions src/workbench/sidebar/__tests__/folderTree.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -200,12 +200,8 @@ describe('The FolderTree Component', () => {
dragToTargetNode(getByTitle('file'), getByTitle('folder'));

expect(mockFn).toBeCalled();
expect(mockFn.mock.calls[0][0]).toEqual([
{
...mockTreeData[0],
children: [mockFolder, mockFile],
},
]);
expect(mockFn.mock.calls[0][0]).toEqual(mockFile);
expect(mockFn.mock.calls[0][1]).toEqual(mockFolder);
});

test('Should suppor to init contextMenu', () => {
Expand Down
7 changes: 2 additions & 5 deletions src/workbench/sidebar/explore/folderTree.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import 'reflect-metadata';
import React, { memo, useRef, useEffect, useLayoutEffect } from 'react';
import cloneDeep from 'lodash/cloneDeep';
import { IFolderTree, IFolderTreeSubItem } from 'mo/model';
import { select, getEventPosition } from 'mo/common/dom';
import Tree, { ITreeNodeItemProps } from 'mo/components/tree';
Expand Down Expand Up @@ -207,10 +206,8 @@ const FolderTree: React.FunctionComponent<IFolderTreeProps> = (props) => {
);
};

const handleDropTree = (treeData) => {
const newFolderTreeData = cloneDeep(data);
newFolderTreeData[0].children = treeData;
onDropTree?.(newFolderTreeData);
const handleDropTree = (source, target) => {
onDropTree?.(source, target);
};

useEffect(() => {
Expand Down
Loading

0 comments on commit 8626600

Please sign in to comment.