Skip to content

Commit

Permalink
Add onWillDrop to tree dnd proposal
Browse files Browse the repository at this point in the history
Part of #32592
  • Loading branch information
alexr00 committed Nov 16, 2021
1 parent 56681ed commit adcda6a
Show file tree
Hide file tree
Showing 6 changed files with 67 additions and 38 deletions.
4 changes: 2 additions & 2 deletions src/vs/workbench/api/browser/mainThreadTreeViews.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,8 +169,8 @@ class TreeViewDragAndDropController implements ITreeViewDragAndDropController {
constructor(private readonly treeViewId: string,
private readonly _proxy: ExtHostTreeViewsShape) { }

async onDrop(dataTransfer: ITreeDataTransfer, targetTreeItem: ITreeItem): Promise<void> {
return this._proxy.$onDrop(this.treeViewId, await TreeDataTransferConverter.toTreeDataTransferDTO(dataTransfer), targetTreeItem.handle);
async onDrop(dataTransfer: ITreeDataTransfer, targetTreeItem: ITreeItem, sourceTreeId?: string, sourceTreeItemHandles?: string[]): Promise<void> {
return this._proxy.$onDrop(this.treeViewId, await TreeDataTransferConverter.toTreeDataTransferDTO(dataTransfer), targetTreeItem.handle, sourceTreeId, sourceTreeItemHandles);
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/vs/workbench/api/common/extHost.protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1271,7 +1271,7 @@ export interface ExtHostDocumentsAndEditorsShape {

export interface ExtHostTreeViewsShape {
$getChildren(treeViewId: string, treeItemHandle?: string): Promise<ITreeItem[] | undefined>;
$onDrop(treeViewId: string, treeDataTransfer: TreeDataTransferDTO, newParentTreeItemHandle: string): Promise<void>;
$onDrop(destinationViewId: string, treeDataTransfer: TreeDataTransferDTO, newParentTreeItemHandle: string, sourceViewId?: string, sourceTreeItemHandles?: string[]): Promise<void>;
$setExpanded(treeViewId: string, treeItemHandle: string, expanded: boolean): void;
$setSelection(treeViewId: string, treeItemHandles: string[]): void;
$setVisible(treeViewId: string, visible: boolean): void;
Expand Down
41 changes: 28 additions & 13 deletions src/vs/workbench/api/common/extHostTreeViews.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { URI } from 'vs/base/common/uri';
import { Emitter, Event } from 'vs/base/common/event';
import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle';
import { ExtHostTreeViewsShape, MainThreadTreeViewsShape } from './extHost.protocol';
import { ITreeItem, TreeViewItemHandleArg, ITreeItemLabel, IRevealOptions, TREE_ITEM_DATA_TRANSFER_TYPE } from 'vs/workbench/common/views';
import { ITreeItem, TreeViewItemHandleArg, ITreeItemLabel, IRevealOptions } from 'vs/workbench/common/views';
import { ExtHostCommands, CommandsConverter } from 'vs/workbench/api/common/extHostCommands';
import { asPromise } from 'vs/base/common/async';
import { TreeItemCollapsibleState, ThemeIcon, MarkdownString as MarkdownStringType } from 'vs/workbench/api/common/extHostTypes';
Expand Down Expand Up @@ -129,22 +129,22 @@ export class ExtHostTreeViews implements ExtHostTreeViewsShape {
return treeView.getChildren(treeItemHandle);
}

async $onDrop(treeViewId: string, treeDataTransferDTO: TreeDataTransferDTO, newParentItemHandle: string): Promise<void> {
const treeView = this.treeViews.get(treeViewId);
async $onDrop(destinationViewId: string, treeDataTransferDTO: TreeDataTransferDTO, newParentItemHandle: string, sourceViewId?: string, sourceTreeItemHandles?: string[]): Promise<void> {
const treeView = this.treeViews.get(destinationViewId);
if (!treeView) {
return Promise.reject(new Error(localize('treeView.notRegistered', 'No tree view with id \'{0}\' registered.', treeViewId)));
return Promise.reject(new Error(localize('treeView.notRegistered', 'No tree view with id \'{0}\' registered.', destinationViewId)));
}

const treeDataTransfer = TreeDataTransferConverter.toITreeDataTransfer(treeDataTransferDTO);
if (treeDataTransfer.items.has(TREE_ITEM_DATA_TRANSFER_TYPE)) {
const sourceHandles: string[] = JSON.parse(await treeDataTransfer.items.get(TREE_ITEM_DATA_TRANSFER_TYPE)!.asString());
const sourceElements = sourceHandles.map(handle => treeView.getExtensionElement(handle)).filter(element => !!element);
if (sourceElements.length > 0) {
treeDataTransfer.items.set(TREE_ITEM_DATA_TRANSFER_TYPE, {
asString: async () => JSON.stringify(sourceElements)
});
} else {
treeDataTransfer.items.delete(TREE_ITEM_DATA_TRANSFER_TYPE);
if ((sourceViewId === destinationViewId) && sourceTreeItemHandles) {
const additionalTransferItems = await treeView.onWillDrop(sourceTreeItemHandles);
if (additionalTransferItems) {
for (const key of additionalTransferItems.items.keys()) {
const item = additionalTransferItems.items.get(key);
if (item) {
treeDataTransfer.items.set(key, item);
}
}
}
}
return treeView.onDrop(treeDataTransfer, newParentItemHandle);
Expand Down Expand Up @@ -404,6 +404,21 @@ class ExtHostTreeView<T> extends Disposable {
}
}

async onWillDrop(sourceTreeItemHandles: TreeItemHandle[]): Promise<vscode.TreeDataTransfer | undefined> {
const extensionTreeItems: T[] = [];
for (const sourceHandle of sourceTreeItemHandles) {
const extensionItem = this.getExtensionElement(sourceHandle);
if (extensionItem) {
extensionTreeItems.push(extensionItem);
}
}

if (!this.dndController?.onWillDrop || (extensionTreeItems.length === 0)) {
return;
}
return this.dndController.onWillDrop(extensionTreeItems);
}

async onDrop(treeDataTransfer: vscode.TreeDataTransfer, targetHandleOrNode: TreeItemHandle): Promise<void> {
const target = this.getExtensionElement(targetHandleOrNode);
if (!target) {
Expand Down
37 changes: 28 additions & 9 deletions src/vs/workbench/browser/parts/views/treeView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { MenuId, IMenuService, registerAction2, Action2 } from 'vs/platform/actions/common/actions';
import { IContextKeyService, ContextKeyExpr, RawContextKey, IContextKey } from 'vs/platform/contextkey/common/contextkey';
import { ITreeView, ITreeViewDescriptor, IViewsRegistry, Extensions, IViewDescriptorService, ITreeItem, TreeItemCollapsibleState, ITreeViewDataProvider, TreeViewItemHandleArg, ITreeItemLabel, ViewContainer, ViewContainerLocation, ResolvableTreeItem, ITreeViewDragAndDropController, ITreeDataTransfer, TREE_ITEM_DATA_TRANSFER_TYPE } from 'vs/workbench/common/views';
import { ITreeView, ITreeViewDescriptor, IViewsRegistry, Extensions, IViewDescriptorService, ITreeItem, TreeItemCollapsibleState, ITreeViewDataProvider, TreeViewItemHandleArg, ITreeItemLabel, ViewContainer, ViewContainerLocation, ResolvableTreeItem, ITreeViewDragAndDropController, ITreeDataTransfer } from 'vs/workbench/common/views';
import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IThemeService, FileThemeIcon, FolderThemeIcon, registerThemingParticipant, ThemeIcon } from 'vs/platform/theme/common/themeService';
Expand Down Expand Up @@ -216,7 +216,7 @@ abstract class AbstractTreeView extends Disposable implements ITreeView {
this.collapseAllToggleContext = this.collapseAllToggleContextKey.bindTo(contextKeyService);
this.refreshContextKey = new RawContextKey<boolean>(`treeView.${this.id}.enableRefresh`, false, localize('treeView.enableRefresh', "Whether the tree view with id {0} enables refresh.", this.id));
this.refreshContext = this.refreshContextKey.bindTo(contextKeyService);
this.treeViewDnd = this.instantiationService.createInstance(CustomTreeViewDragAndDrop);
this.treeViewDnd = this.instantiationService.createInstance(CustomTreeViewDragAndDrop, this.id);

this._register(this.themeService.onDidFileIconThemeChange(() => this.doRefresh([this.root]) /** soft refresh **/));
this._register(this.themeService.onDidColorThemeChange(() => this.doRefresh([this.root]) /** soft refresh **/));
Expand Down Expand Up @@ -1215,8 +1215,16 @@ export class TreeView extends AbstractTreeView {
}
}

const TREE_DRAG_SOURCE_INFO_MIME_TYPE = 'tree/internalsourceinfo';
interface TreeDragSourceInfo {
id: string,
itemHandles: string[];
}

export class CustomTreeViewDragAndDrop implements ITreeDragAndDrop<ITreeItem> {
constructor(@ILabelService private readonly labelService: ILabelService) { }
constructor(
private readonly treeId: string,
@ILabelService private readonly labelService: ILabelService) { }

private dndController: ITreeViewDragAndDropController | undefined;
set controller(controller: ITreeViewDragAndDropController | undefined) {
Expand All @@ -1225,8 +1233,13 @@ export class CustomTreeViewDragAndDrop implements ITreeDragAndDrop<ITreeItem> {

onDragStart(data: IDragAndDropData, originalEvent: DragEvent): void {
if (originalEvent.dataTransfer) {
originalEvent.dataTransfer.setData(TREE_ITEM_DATA_TRANSFER_TYPE,
JSON.stringify((data as ElementsDragAndDropData<ITreeItem, ITreeItem[]>).getData().map(treeItem => treeItem.handle)));
const treeItemsData = (data as ElementsDragAndDropData<ITreeItem, ITreeItem[]>).getData();
const sourceInfo: TreeDragSourceInfo = {
id: this.treeId,
itemHandles: treeItemsData.map(item => item.handle)
};
originalEvent.dataTransfer.setData(TREE_DRAG_SOURCE_INFO_MIME_TYPE,
JSON.stringify(sourceInfo));
}
}

Expand Down Expand Up @@ -1268,6 +1281,8 @@ export class CustomTreeViewDragAndDrop implements ITreeDragAndDrop<ITreeItem> {
}
return previous;
}, 0);

let treeSourceInfo: TreeDragSourceInfo | undefined;
await new Promise<void>(resolve => {
if (!originalEvent.dataTransfer || !this.dndController || !targetNode) {
return;
Expand All @@ -1276,9 +1291,13 @@ export class CustomTreeViewDragAndDrop implements ITreeDragAndDrop<ITreeItem> {
if (dataItem.kind === 'string') {
const type = dataItem.type;
dataItem.getAsString(dataValue => {
treeDataTransfer.items.set(type, {
asString: () => Promise.resolve(dataValue)
});
if (type === TREE_DRAG_SOURCE_INFO_MIME_TYPE) {
treeSourceInfo = JSON.parse(dataValue);
} else {
treeDataTransfer.items.set(type, {
asString: () => Promise.resolve(dataValue)
});
}
stringCount--;
if (stringCount === 0) {
resolve();
Expand All @@ -1287,6 +1306,6 @@ export class CustomTreeViewDragAndDrop implements ITreeDragAndDrop<ITreeItem> {
}
}
});
return this.dndController.onDrop(treeDataTransfer, targetNode);
return this.dndController.onDrop(treeDataTransfer, targetNode, treeSourceInfo?.id, treeSourceInfo?.itemHandles);
}
}
3 changes: 1 addition & 2 deletions src/vs/workbench/common/views.ts
Original file line number Diff line number Diff line change
Expand Up @@ -838,9 +838,8 @@ export interface ITreeViewDataProvider {
getChildren(element?: ITreeItem): Promise<ITreeItem[] | undefined>;
}

export const TREE_ITEM_DATA_TRANSFER_TYPE = 'text/treeitems';
export interface ITreeViewDragAndDropController {
onDrop(elements: ITreeDataTransfer, target: ITreeItem): Promise<void>;
onDrop(elements: ITreeDataTransfer, target: ITreeItem, sourceTreeId?: string, sourceTreeItemHandles?: string[]): Promise<void>;
}

export interface IEditableData {
Expand Down
18 changes: 7 additions & 11 deletions src/vscode-dts/vscode.proposed.treeViewDragAndDrop.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,32 +33,28 @@ declare module 'vscode' {
export interface TreeDataTransfer {
/**
* A map containing a mapping of the mime type of the corresponding data.
* The type for tree elements is text/treeitem.
* For example, you can reconstruct the your tree elements:
* ```ts
* JSON.parse(await (items.get('text/treeitem')!.asString()))
* ```
* Trees that support drag and drop can implement `DragAndDropController.onWillDrop` to add additional mime types
* when the drop occurs on an item in the same tree.
*/
items: { get: (mimeType: string) => TreeDataTransferItem | undefined };
items: {
get: (mimeType: string) => TreeDataTransferItem | undefined
keys: () => IterableIterator<string>;
};
}

export interface DragAndDropController<T> extends Disposable {
readonly supportedTypes: string[];

/**
* todo@API maybe
*
* When the user drops an item from this DragAndDropController on **another tree item** in **the same tree**,
* `onWillDrop` will be called with the dropped tree item. This is the DragAndDropController's opportunity to
* package the data from the dropped tree item into whatever format they want the target tree item to receive.
*
* The returned `TreeDataTransfer` will be merged with the original`TreeDataTransfer` for the operation.
*
* Note for implementation later: This means that the `text/treeItem` mime type will go away.
*
* @param source
*/
// onWillDrop?(source: T): Thenable<TreeDataTransfer>;
onWillDrop(source: T[]): Thenable<TreeDataTransfer>;

/**
* Extensions should fire `TreeDataProvider.onDidChangeTreeData` for any elements that need to be refreshed.
Expand Down

0 comments on commit adcda6a

Please sign in to comment.