Skip to content

Commit 43960fc

Browse files
committed
feat: support TreeView Drag API
1 parent 9c0b433 commit 43960fc

16 files changed

Lines changed: 1627 additions & 104 deletions

File tree

packages/core-common/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,4 @@ export * from './markdown';
3434
export * from './settings';
3535
export * from './utils';
3636
export * from './clipboard';
37+
export * from './mime';

packages/core-common/src/mime.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
export const Mimes = Object.freeze({
2+
text: 'text/plain',
3+
binary: 'application/octet-stream',
4+
unknown: 'application/unknown',
5+
markdown: 'text/markdown',
6+
latex: 'text/latex',
7+
uriList: 'text/uri-list',
8+
});

packages/extension/src/browser/components/extension-tree-view-node.tsx

Lines changed: 20 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import cls from 'classnames';
2-
import React from 'react';
2+
import React, { CSSProperties, FC, MouseEvent, ReactNode, useEffect, useState } from 'react';
33

44
import { INodeRendererProps, ClasslistComposite, PromptHandle, TreeNodeType } from '@opensumi/ide-components';
55
import { Loading } from '@opensumi/ide-components';
@@ -19,23 +19,21 @@ export interface ITreeViewNodeProps {
1919
defaultLeftPadding?: number;
2020
leftPadding?: number;
2121
decorations?: ClasslistComposite;
22-
onTwistierClick?: (
23-
ev: React.MouseEvent,
24-
item: ExtensionTreeNode | ExtensionCompositeTreeNode,
25-
type: TreeNodeType,
26-
) => void;
27-
onClick: (ev: React.MouseEvent, item: ExtensionTreeNode | ExtensionCompositeTreeNode, type: TreeNodeType) => void;
28-
onContextMenu?: (
29-
ev: React.MouseEvent,
30-
item: ExtensionTreeNode | ExtensionCompositeTreeNode,
31-
type: TreeNodeType,
32-
) => void;
22+
onTwistierClick?: (ev: MouseEvent, item: ExtensionTreeNode | ExtensionCompositeTreeNode, type: TreeNodeType) => void;
23+
onClick: (ev: MouseEvent, item: ExtensionTreeNode | ExtensionCompositeTreeNode, type: TreeNodeType) => void;
24+
onContextMenu?: (ev: MouseEvent, item: ExtensionTreeNode | ExtensionCompositeTreeNode, type: TreeNodeType) => void;
25+
onDragStart?: (ev: MouseEvent, item: ExtensionTreeNode | ExtensionCompositeTreeNode) => void;
26+
onDragEnter?: (ev: MouseEvent, item: ExtensionTreeNode | ExtensionCompositeTreeNode) => void;
27+
onDrop?: (ev: MouseEvent, item: ExtensionTreeNode | ExtensionCompositeTreeNode) => void;
28+
onDragOver?: (ev: MouseEvent, item: ExtensionTreeNode | ExtensionCompositeTreeNode) => void;
29+
onDragLeave?: (ev: MouseEvent, item: ExtensionTreeNode | ExtensionCompositeTreeNode) => void;
3330
decorationService: IDecorationsService;
31+
draggable: boolean;
3432
}
3533

3634
export type TreeViewNodeRenderedProps = ITreeViewNodeProps & INodeRendererProps;
3735

38-
export const TreeViewNode: React.FC<TreeViewNodeRenderedProps> = ({
36+
export const TreeViewNode: FC<TreeViewNodeRenderedProps> = ({
3937
item,
4038
onClick,
4139
onContextMenu,
@@ -46,10 +44,11 @@ export const TreeViewNode: React.FC<TreeViewNodeRenderedProps> = ({
4644
defaultLeftPadding = 8,
4745
treeViewId,
4846
decorationService,
47+
draggable,
4948
}: TreeViewNodeRenderedProps) => {
50-
const [decoration, setDecoration] = React.useState(item.uri && decorationService.getDecoration(item.uri, false));
49+
const [decoration, setDecoration] = useState(item.uri && decorationService.getDecoration(item.uri, false));
5150

52-
React.useEffect(() => {
51+
useEffect(() => {
5352
const disposable = decorationService.onDidChangeDecorations((e) => {
5453
if (item.uri && e.affectsResource(item.uri)) {
5554
setDecoration(decorationService.getDecoration(item.uri, false));
@@ -62,13 +61,13 @@ export const TreeViewNode: React.FC<TreeViewNodeRenderedProps> = ({
6261

6362
const themeService = useInjectable<IThemeService>(IThemeService);
6463

65-
const handleClick = (ev: React.MouseEvent) => {
64+
const handleClick = (ev: MouseEvent) => {
6665
if (itemType === TreeNodeType.TreeNode || itemType === TreeNodeType.CompositeTreeNode) {
6766
onClick(ev, item, itemType);
6867
}
6968
};
7069

71-
const handlerTwistierClick = (ev: React.MouseEvent) => {
70+
const handlerTwistierClick = (ev: MouseEvent) => {
7271
if (itemType === TreeNodeType.TreeNode || itemType === TreeNodeType.CompositeTreeNode) {
7372
if (onTwistierClick) {
7473
onTwistierClick(ev, item, itemType);
@@ -78,7 +77,7 @@ export const TreeViewNode: React.FC<TreeViewNodeRenderedProps> = ({
7877
}
7978
};
8079

81-
const handleContextMenu = (ev: React.MouseEvent) => {
80+
const handleContextMenu = (ev: MouseEvent) => {
8281
if (ev.nativeEvent.which === 0 || !onContextMenu) {
8382
return;
8483
}
@@ -96,7 +95,7 @@ export const TreeViewNode: React.FC<TreeViewNodeRenderedProps> = ({
9695
height: TREE_VIEW_NODE_HEIGHT,
9796
lineHeight: `${TREE_VIEW_NODE_HEIGHT}px`,
9897
paddingLeft,
99-
} as React.CSSProperties;
98+
} as CSSProperties;
10099

101100
const renderFolderToggle = (node: ExtensionCompositeTreeNode | PromptHandle, clickHandler: any) => {
102101
if (decorations && decorations?.classlist.indexOf(styles.mod_loading) > -1) {
@@ -119,7 +118,7 @@ export const TreeViewNode: React.FC<TreeViewNodeRenderedProps> = ({
119118
const renderDisplayName = (node: ExtensionCompositeTreeNode | ExtensionTreeNode) => {
120119
const displayName = () => {
121120
if (node.highlights) {
122-
let hightlightSnaps: React.ReactNode[] = [];
121+
let hightlightSnaps: ReactNode[] = [];
123122
let endIndex = 0;
124123
const hightlights = node.highlights.sort((a, b) => a[0] - b[0]);
125124
hightlightSnaps = hightlights.map((highlight, index: number) => {
@@ -217,7 +216,7 @@ export const TreeViewNode: React.FC<TreeViewNodeRenderedProps> = ({
217216
className={cls(styles.tree_view_node, decorations ? decorations.classlist : null)}
218217
data-id={item.id}
219218
style={fileTreeNodeStyle}
220-
draggable={itemType === TreeNodeType.TreeNode || itemType === TreeNodeType.CompositeTreeNode}
219+
draggable={draggable}
221220
>
222221
<div className={cls(styles.tree_view_node_content)}>
223222
{renderTwice(item)}

packages/extension/src/browser/components/extension-tree-view.tsx

Lines changed: 101 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,15 @@
11
import { observer } from 'mobx-react-lite';
2-
import React from 'react';
2+
import React, {
3+
createRef,
4+
memo,
5+
MouseEvent,
6+
PropsWithChildren,
7+
RefObject,
8+
useCallback,
9+
useEffect,
10+
useMemo,
11+
useState,
12+
} from 'react';
313

414
import { Injector } from '@opensumi/di';
515
import { RecycleTree, INodeRendererProps, IRecycleTreeHandle, TreeNodeType } from '@opensumi/ide-components';
@@ -26,22 +36,22 @@ export interface ExtensionTabBarTreeViewProps {
2636
}
2737

2838
export const ExtensionTabBarTreeView = observer(
29-
({ viewState, model, dataProvider, treeViewId }: React.PropsWithChildren<ExtensionTabBarTreeViewProps>) => {
30-
const [isReady, setIsReady] = React.useState<boolean>(false);
31-
const [isEmpty, setIsEmpty] = React.useState(dataProvider.isTreeEmpty);
39+
({ viewState, model, dataProvider, treeViewId }: PropsWithChildren<ExtensionTabBarTreeViewProps>) => {
40+
const [isReady, setIsReady] = useState<boolean>(false);
41+
const [isEmpty, setIsEmpty] = useState(dataProvider.isTreeEmpty);
3242
const layoutService = useInjectable<IMainLayoutService>(IMainLayoutService);
3343
const decorationService = useInjectable<IDecorationsService>(IDecorationsService);
34-
const accordionService = React.useMemo(() => layoutService.getViewAccordionService(treeViewId), []);
44+
const accordionService = useMemo(() => layoutService.getViewAccordionService(treeViewId), []);
3545

36-
const isVisible = React.useMemo(() => {
46+
const isVisible = useMemo(() => {
3747
const state = accordionService?.getViewState(treeViewId);
3848
if (!state) {
3949
return false;
4050
}
4151
return !state.collapsed && !state.hidden;
4252
}, [accordionService]);
4353

44-
React.useEffect(() => {
54+
useEffect(() => {
4555
const disposable = dataProvider.onDidChangeEmpty(() => {
4656
setIsEmpty(dataProvider.isTreeEmpty);
4757
});
@@ -50,18 +60,21 @@ export const ExtensionTabBarTreeView = observer(
5060

5161
const { height } = viewState;
5262
const { canSelectMany } = model.treeViewOptions || {};
53-
const wrapperRef: React.RefObject<HTMLDivElement> = React.createRef();
63+
const wrapperRef: RefObject<HTMLDivElement> = createRef();
5464

55-
const handleTreeReady = (handle: IRecycleTreeHandle) => {
56-
model.handleTreeHandler({
57-
...handle,
58-
getModel: () => model.treeModel,
59-
hasDirectFocus: () => wrapperRef.current === document.activeElement,
60-
});
61-
};
65+
const handleTreeReady = useCallback(
66+
(handle: IRecycleTreeHandle) => {
67+
model.handleTreeHandler({
68+
...handle,
69+
getModel: () => model.treeModel,
70+
hasDirectFocus: () => wrapperRef.current === document.activeElement,
71+
});
72+
},
73+
[model],
74+
);
6275

63-
const handleTwistierClick = React.useCallback(
64-
(ev: React.MouseEvent, item: ExtensionCompositeTreeNode) => {
76+
const handleTwistierClick = useCallback(
77+
(ev: MouseEvent, item: ExtensionCompositeTreeNode) => {
6578
// 阻止点击事件冒泡
6679
ev.stopPropagation();
6780

@@ -72,21 +85,21 @@ export const ExtensionTabBarTreeView = observer(
7285
[model],
7386
);
7487

75-
const hasShiftMask = (event): boolean => {
88+
const hasShiftMask = useCallback((event): boolean => {
7689
// Ctrl/Cmd 权重更高
7790
if (hasCtrlCmdMask(event)) {
7891
return false;
7992
}
8093
return event.shiftKey;
81-
};
94+
}, []);
8295

83-
const hasCtrlCmdMask = (event): boolean => {
96+
const hasCtrlCmdMask = useCallback((event): boolean => {
8497
const { metaKey, ctrlKey } = event;
8598
return (isOSX && metaKey) || ctrlKey;
86-
};
99+
}, []);
87100

88-
const handleItemClicked = React.useCallback(
89-
(ev: React.MouseEvent, item: ExtensionTreeNode | ExtensionCompositeTreeNode, type: TreeNodeType) => {
101+
const handleItemClicked = useCallback(
102+
(ev: MouseEvent, item: ExtensionTreeNode | ExtensionCompositeTreeNode, type: TreeNodeType) => {
90103
// 阻止点击事件冒泡
91104
ev.stopPropagation();
92105

@@ -106,30 +119,55 @@ export const ExtensionTabBarTreeView = observer(
106119
handleItemClick(item, type);
107120
}
108121
},
109-
[canSelectMany],
122+
[canSelectMany, model],
110123
);
111124

112-
const handlerContextMenu = React.useCallback(
113-
(ev: React.MouseEvent, node: ExtensionTreeNode | ExtensionCompositeTreeNode) => {
125+
const handleContextMenu = useCallback(
126+
(ev: MouseEvent, node: ExtensionTreeNode | ExtensionCompositeTreeNode) => {
114127
const { handleContextMenu } = model;
115128
handleContextMenu(ev, node);
116129
},
117130
[model],
118131
);
119132

120-
const handleOuterContextMenu = (ev: React.MouseEvent) => {
133+
const handleOuterContextMenu = (ev: MouseEvent) => {
121134
const { handleContextMenu } = model;
122135
// 空白区域右键菜单
123136
handleContextMenu(ev);
124137
};
125138

126-
const handleOuterClick = (ev: React.MouseEvent) => {
139+
const handleOuterClick = useCallback(() => {
127140
// 空白区域点击,取消焦点状态
128141
const { enactiveNodeDecoration } = model;
129142
enactiveNodeDecoration();
130-
};
143+
}, [model]);
144+
145+
const handleDragStart = useCallback(
146+
(ev: MouseEvent, node: ExtensionTreeNode | ExtensionCompositeTreeNode) => {},
147+
[model],
148+
);
149+
150+
const handleDragOver = useCallback(
151+
(ev: MouseEvent, node: ExtensionTreeNode | ExtensionCompositeTreeNode) => {},
152+
[model],
153+
);
154+
155+
const handleDragEnter = useCallback(
156+
(ev: MouseEvent, node: ExtensionTreeNode | ExtensionCompositeTreeNode) => {},
157+
[model],
158+
);
159+
160+
const handleDrop = useCallback(
161+
(ev: MouseEvent, node: ExtensionTreeNode | ExtensionCompositeTreeNode) => {},
162+
[model],
163+
);
164+
165+
const handleDragLeave = useCallback(
166+
(ev: MouseEvent, node: ExtensionTreeNode | ExtensionCompositeTreeNode) => {},
167+
[model],
168+
);
131169

132-
React.useEffect(() => {
170+
useEffect(() => {
133171
let unmouted = false;
134172
(async () => {
135173
await model.whenReady;
@@ -148,7 +186,7 @@ export const ExtensionTabBarTreeView = observer(
148186
};
149187
}, [model, isVisible]);
150188

151-
React.useEffect(() => {
189+
useEffect(() => {
152190
const handleBlur = () => {
153191
model.handleTreeBlur();
154192
};
@@ -174,7 +212,13 @@ export const ExtensionTabBarTreeView = observer(
174212
handleTreeReady={handleTreeReady}
175213
handleItemClicked={handleItemClicked}
176214
handleTwistierClick={handleTwistierClick}
177-
handlerContextMenu={handlerContextMenu}
215+
handleContextMenu={handleContextMenu}
216+
handleDragStart={handleDragStart}
217+
handleDragOver={handleDragOver}
218+
handleDragEnter={handleDragEnter}
219+
handleDragLeave={handleDragLeave}
220+
handleDrop={handleDrop}
221+
draggable={model.draggable}
178222
treeViewId={treeViewId}
179223
model={model}
180224
decorationService={decorationService}
@@ -191,14 +235,16 @@ interface TreeViewProps {
191235
treeViewId: string;
192236
model: ExtensionTreeViewModel;
193237
handleTreeReady(handle: IRecycleTreeHandle): void;
194-
handleItemClicked(
195-
ev: React.MouseEvent,
196-
item: ExtensionTreeNode | ExtensionCompositeTreeNode,
197-
type: TreeNodeType,
198-
): void;
199-
handleTwistierClick(ev: React.MouseEvent, item: ExtensionCompositeTreeNode): void;
200-
handlerContextMenu(ev: React.MouseEvent, node: ExtensionTreeNode | ExtensionCompositeTreeNode): void;
238+
handleItemClicked(ev: MouseEvent, item: ExtensionTreeNode | ExtensionCompositeTreeNode, type: TreeNodeType): void;
239+
handleTwistierClick(ev: MouseEvent, item: ExtensionCompositeTreeNode): void;
240+
handleContextMenu(ev: MouseEvent, node: ExtensionTreeNode | ExtensionCompositeTreeNode): void;
241+
handleDragStart(ev: MouseEvent, node: ExtensionTreeNode | ExtensionCompositeTreeNode): void;
242+
handleDragEnter(ev: MouseEvent, node: ExtensionTreeNode | ExtensionCompositeTreeNode): void;
243+
handleDrop(ev: MouseEvent, node: ExtensionTreeNode | ExtensionCompositeTreeNode): void;
244+
handleDragLeave(ev: MouseEvent, node: ExtensionTreeNode | ExtensionCompositeTreeNode): void;
245+
handleDragOver(ev: MouseEvent, node: ExtensionTreeNode | ExtensionCompositeTreeNode): void;
201246
decorationService: IDecorationsService;
247+
draggable: boolean;
202248
}
203249

204250
function isTreeViewPropsEqual(prevProps: TreeViewProps, nextProps: TreeViewProps) {
@@ -211,7 +257,7 @@ function isTreeViewPropsEqual(prevProps: TreeViewProps, nextProps: TreeViewProps
211257
);
212258
}
213259

214-
const TreeView = React.memo(
260+
const TreeView = memo(
215261
({
216262
isReady,
217263
isEmpty,
@@ -221,21 +267,33 @@ const TreeView = React.memo(
221267
handleTreeReady,
222268
handleItemClicked,
223269
handleTwistierClick,
224-
handlerContextMenu,
270+
handleContextMenu,
271+
handleDragStart,
272+
handleDragOver,
273+
handleDragEnter,
274+
handleDragLeave,
275+
handleDrop,
276+
draggable,
225277
decorationService,
226278
}: TreeViewProps) => {
227-
const renderTreeNode = React.useCallback(
279+
const renderTreeNode = useCallback(
228280
(props: INodeRendererProps) => (
229281
<TreeViewNode
230282
item={props.item as any}
231283
itemType={props.itemType}
232284
decorations={model.decorations.getDecorations(props.item as any)}
233285
onClick={handleItemClicked}
234286
onTwistierClick={handleTwistierClick}
235-
onContextMenu={handlerContextMenu}
287+
onContextMenu={handleContextMenu}
288+
onDragStart={handleDragStart}
289+
onDragOver={handleDragOver}
290+
onDragEnter={handleDragEnter}
291+
onDragLeave={handleDragLeave}
292+
onDrop={handleDrop}
236293
defaultLeftPadding={8}
237294
leftPadding={8}
238295
treeViewId={treeViewId}
296+
draggable={draggable}
239297
decorationService={decorationService}
240298
/>
241299
),

0 commit comments

Comments
 (0)