Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support to subscribe the drop event in tree #450

Merged
merged 3 commits into from
Sep 29, 2021
Merged
Show file tree
Hide file tree
Changes from all 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
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