diff --git a/src/components/tree/__tests__/tree.test.tsx b/src/components/tree/__tests__/tree.test.tsx index ba0dcd25e..dd928125c 100644 --- a/src/components/tree/__tests__/tree.test.tsx +++ b/src/components/tree/__tests__/tree.test.tsx @@ -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); @@ -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( @@ -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( + + ); + + 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( @@ -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( + + ); + + 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], + }); }); }); diff --git a/src/components/tree/index.tsx b/src/components/tree/index.tsx index cc27a3f45..43b3dcdf1 100644 --- a/src/components/tree/index.tsx +++ b/src/components/tree/index.tsx @@ -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; @@ -26,7 +27,7 @@ export interface ITreeProps extends Partial { index: number, isLeaf: boolean ) => JSX.Element | string; - onDropTree?(treeNode: ITreeNodeItemProps[]): void; + onDropTree?(source: ITreeNodeItemProps, target: ITreeNodeItemProps): void; onLoadData?: (treeNode: LoadEventData) => Promise; } @@ -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 = ( diff --git a/src/controller/explorer/folderTree.tsx b/src/controller/explorer/folderTree.tsx index 8f3e55b2a..db469047c 100644 --- a/src/controller/explorer/folderTree.tsx +++ b/src/controller/explorer/folderTree.tsx @@ -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; readonly onRightClick?: (treeNode: ITreeNodeItemProps) => IMenuItemProps[]; } @@ -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) => { diff --git a/src/model/workbench/explorer/folderTree.tsx b/src/model/workbench/explorer/folderTree.tsx index 4087d2719..d4bea1f1c 100644 --- a/src/model/workbench/explorer/folderTree.tsx +++ b/src/model/workbench/explorer/folderTree.tsx @@ -22,6 +22,7 @@ export enum FolderTreeEvent { onContextMenuClick = 'folderTree.onContextMenuClick', onCreate = 'folderTree.onCreate', onLoadData = 'folderTree.onLoadData', + onDrop = 'folderTree.onDrop', } export interface IFolderInputEvent { diff --git a/src/services/workbench/__tests__/folderTreeService.test.ts b/src/services/workbench/__tests__/folderTreeService.test.ts index 6484ee7f1..872fec136 100644 --- a/src/services/workbench/__tests__/folderTreeService.test.ts +++ b/src/services/workbench/__tests__/folderTreeService.test.ts @@ -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); @@ -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); + }); + }); }); diff --git a/src/services/workbench/explorer/folderTreeService.ts b/src/services/workbench/explorer/folderTreeService.ts index 4385ba875..e4a31dc38 100644 --- a/src/services/workbench/explorer/folderTreeService.ts +++ b/src/services/workbench/explorer/folderTreeService.ts @@ -98,7 +98,12 @@ export interface IFolderTreeService extends Component { * 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 @@ -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 = ( diff --git a/src/workbench/sidebar/__tests__/folderTree.test.tsx b/src/workbench/sidebar/__tests__/folderTree.test.tsx index 02df35266..0bb1a0331 100644 --- a/src/workbench/sidebar/__tests__/folderTree.test.tsx +++ b/src/workbench/sidebar/__tests__/folderTree.test.tsx @@ -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', () => { diff --git a/src/workbench/sidebar/explore/folderTree.tsx b/src/workbench/sidebar/explore/folderTree.tsx index 277a0fde0..978431bcd 100644 --- a/src/workbench/sidebar/explore/folderTree.tsx +++ b/src/workbench/sidebar/explore/folderTree.tsx @@ -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'; @@ -207,10 +206,8 @@ const FolderTree: React.FunctionComponent = (props) => { ); }; - const handleDropTree = (treeData) => { - const newFolderTreeData = cloneDeep(data); - newFolderTreeData[0].children = treeData; - onDropTree?.(newFolderTreeData); + const handleDropTree = (source, target) => { + onDropTree?.(source, target); }; useEffect(() => { diff --git a/stories/extensions/test/index.tsx b/stories/extensions/test/index.tsx index 218a8086a..adf3d1e2c 100644 --- a/stories/extensions/test/index.tsx +++ b/stories/extensions/test/index.tsx @@ -183,5 +183,9 @@ export const ExtendTestPane: IExtension = { }; molecule.editor.open(tabData); }); + + molecule.folderTree.onDropTree((source, target) => { + console.log(source, target); + }); }, };