Skip to content

Commit

Permalink
Incorporate tree dnd API feedback
Browse files Browse the repository at this point in the history
Part of #32592
  • Loading branch information
alexr00 committed Nov 17, 2021
1 parent af67d73 commit daabfff
Show file tree
Hide file tree
Showing 10 changed files with 92 additions and 46 deletions.
6 changes: 4 additions & 2 deletions src/vs/workbench/api/browser/mainThreadTreeViews.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,14 @@ export class MainThreadTreeViews extends Disposable implements MainThreadTreeVie
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostTreeViews);
}

async $registerTreeViewDataProvider(treeViewId: string, options: { showCollapseAll: boolean, canSelectMany: boolean, canDragAndDrop: boolean }): Promise<void> {
async $registerTreeViewDataProvider(treeViewId: string, options: { showCollapseAll: boolean, canSelectMany: boolean, dragAndDropMimeTypes: string[] }): Promise<void> {
this.logService.trace('MainThreadTreeViews#$registerTreeViewDataProvider', treeViewId, options);

this.extensionService.whenInstalledExtensionsRegistered().then(() => {
const dataProvider = new TreeViewDataProvider(treeViewId, this._proxy, this.notificationService);
this._dataProviders.set(treeViewId, dataProvider);
const dndController = options.canDragAndDrop ? new TreeViewDragAndDropController(treeViewId, this._proxy) : undefined;
const dndController = (options.dragAndDropMimeTypes.length > 0)
? new TreeViewDragAndDropController(treeViewId, options.dragAndDropMimeTypes, this._proxy) : undefined;
const viewer = this.getTreeView(treeViewId);
if (viewer) {
// Order is important here. The internal tree isn't created until the dataProvider is set.
Expand Down Expand Up @@ -167,6 +168,7 @@ type TreeItemHandle = string;
class TreeViewDragAndDropController implements ITreeViewDragAndDropController {

constructor(private readonly treeViewId: string,
readonly supportedMimeTypes: string[],
private readonly _proxy: ExtHostTreeViewsShape) { }

async onDrop(dataTransfer: ITreeDataTransfer, targetTreeItem: ITreeItem, sourceTreeId?: string, sourceTreeItemHandles?: string[]): Promise<void> {
Expand Down
2 changes: 2 additions & 0 deletions src/vs/workbench/api/common/extHost.api.impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1301,6 +1301,8 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
TestTag: extHostTypes.TestTag,
TestRunProfileKind: extHostTypes.TestRunProfileKind,
TextSearchCompleteMessageType: TextSearchCompleteMessageType,
TreeDataTransfer: extHostTypes.TreeDataTransfer,
TreeDataTransferItem: extHostTypes.TreeDataTransferItem,
CoveredCount: extHostTypes.CoveredCount,
FileCoverage: extHostTypes.FileCoverage,
StatementCoverage: extHostTypes.StatementCoverage,
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 @@ -295,7 +295,7 @@ export interface MainThreadTextEditorsShape extends IDisposable {
}

export interface MainThreadTreeViewsShape extends IDisposable {
$registerTreeViewDataProvider(treeViewId: string, options: { showCollapseAll: boolean, canSelectMany: boolean, canDragAndDrop: boolean; }): Promise<void>;
$registerTreeViewDataProvider(treeViewId: string, options: { showCollapseAll: boolean, canSelectMany: boolean, dragAndDropMimeTypes: string[] }): Promise<void>;
$refresh(treeViewId: string, itemsToRefresh?: { [treeItemHandle: string]: ITreeItem; }): Promise<void>;
$reveal(treeViewId: string, itemInfo: { item: ITreeItem, parentChain: ITreeItem[] } | undefined, options: IRevealOptions): Promise<void>;
$setMessage(treeViewId: string, message: string): void;
Expand Down
8 changes: 4 additions & 4 deletions src/vs/workbench/api/common/extHostTreeViews.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,8 @@ export class ExtHostTreeViews implements ExtHostTreeViewsShape {
if (!options || !options.treeDataProvider) {
throw new Error('Options with treeDataProvider is mandatory');
}
const canDragAndDrop = options.dragAndDropController !== undefined;
const registerPromise = this._proxy.$registerTreeViewDataProvider(viewId, { showCollapseAll: !!options.showCollapseAll, canSelectMany: !!options.canSelectMany, canDragAndDrop: canDragAndDrop });
const dragAndDropMimeTypes = (options.dragAndDropController === undefined) ? [] : options.dragAndDropController.supportedMimeTypes;
const registerPromise = this._proxy.$registerTreeViewDataProvider(viewId, { showCollapseAll: !!options.showCollapseAll, canSelectMany: !!options.canSelectMany, dragAndDropMimeTypes });
const treeView = this.createExtHostTreeView(viewId, options, extension);
return {
get onDidCollapseElement() { return treeView.onDidCollapseElement; },
Expand Down Expand Up @@ -139,9 +139,9 @@ export class ExtHostTreeViews implements ExtHostTreeViewsShape {
if ((sourceViewId === destinationViewId) && sourceTreeItemHandles) {
const additionalTransferItems = await treeView.onWillDrop(sourceTreeItemHandles);
if (additionalTransferItems) {
additionalTransferItems.items.forEach((value, key) => {
additionalTransferItems.forEach((value, key) => {
if (value) {
treeDataTransfer.items.set(key, value);
treeDataTransfer.set(key, value);
}
});
}
Expand Down
24 changes: 24 additions & 0 deletions src/vs/workbench/api/common/extHostTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2296,6 +2296,30 @@ export enum TreeItemCollapsibleState {
Expanded = 2
}

@es5ClassCompat
export class TreeDataTransferItem {
async asString(): Promise<string> {
return JSON.stringify(this._value);
}

constructor(private readonly _value: any) { }
}

@es5ClassCompat
export class TreeDataTransfer<T extends TreeDataTransferItem = TreeDataTransferItem> {
private readonly _items: Map<string, T> = new Map();
get(mimeType: string): T | undefined {
return this._items.get(mimeType);
}
set(mimeType: string, value: T): void {
this._items.set(mimeType, value);
}
forEach(callbackfn: (value: T, key: string) => void): void {
this._items.forEach(callbackfn);
}
}


@es5ClassCompat
export class ThemeIcon {

Expand Down
8 changes: 3 additions & 5 deletions src/vs/workbench/api/common/shared/treeDataTransfer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,9 @@ export interface TreeDataTransferDTO {

export namespace TreeDataTransferConverter {
export function toITreeDataTransfer(value: TreeDataTransferDTO): ITreeDataTransfer {
const newDataTransfer: ITreeDataTransfer = {
items: new Map<string, ITreeDataTransferItem>()
};
const newDataTransfer: ITreeDataTransfer = new Map<string, ITreeDataTransferItem>();
value.types.forEach((type, index) => {
newDataTransfer.items.set(type, {
newDataTransfer.set(type, {
asString: async () => value.items[index].asString
});
});
Expand All @@ -32,7 +30,7 @@ export namespace TreeDataTransferConverter {
types: [],
items: []
};
const entries = Array.from(value.items.entries());
const entries = Array.from(value.entries());
for (const entry of entries) {
newDTO.types.push(entry[0]);
newDTO.items.push({
Expand Down
66 changes: 43 additions & 23 deletions src/vs/workbench/browser/parts/views/treeView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1215,16 +1215,18 @@ 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> {
private readonly treeMimeType: string;
constructor(
private readonly treeId: string,
@ILabelService private readonly labelService: ILabelService) { }
@ILabelService private readonly labelService: ILabelService) {
this.treeMimeType = `tree/${treeId.toLowerCase()}`;
}

private dndController: ITreeViewDragAndDropController | undefined;
set controller(controller: ITreeViewDragAndDropController | undefined) {
Expand All @@ -1238,16 +1240,27 @@ export class CustomTreeViewDragAndDrop implements ITreeDragAndDrop<ITreeItem> {
id: this.treeId,
itemHandles: treeItemsData.map(item => item.handle)
};
originalEvent.dataTransfer.setData(TREE_DRAG_SOURCE_INFO_MIME_TYPE,
originalEvent.dataTransfer.setData(this.treeMimeType,
JSON.stringify(sourceInfo));
}
}

onDragOver(data: IDragAndDropData, targetElement: ITreeItem, targetIndex: number, originalEvent: DragEvent): boolean | ITreeDragOverReaction {
if (!this.dndController) {
const dndController = this.dndController;
if (!dndController || !originalEvent.dataTransfer) {
return false;
}
return { accept: true, bubble: TreeDragOverBubble.Down, autoExpand: true };
const dragContainersSupportedType = originalEvent.dataTransfer.types.some((value, index) => {
if (value === this.treeMimeType) {
return true;
} else {
return dndController.supportedMimeTypes.indexOf(value) >= 0;
}
});
if (dragContainersSupportedType) {
return { accept: true, bubble: TreeDragOverBubble.Down, autoExpand: true };
}
return false;
}

getDragURI(element: ITreeItem): string | null {
Expand All @@ -1272,9 +1285,7 @@ export class CustomTreeViewDragAndDrop implements ITreeDragAndDrop<ITreeItem> {
if (!originalEvent.dataTransfer || !this.dndController || !targetNode) {
return;
}
const treeDataTransfer: ITreeDataTransfer = {
items: new Map()
};
const treeDataTransfer: ITreeDataTransfer = new Map();
let stringCount = Array.from(originalEvent.dataTransfer.items).reduce((previous, current) => {
if (current.kind === 'string') {
return previous + 1;
Expand All @@ -1284,25 +1295,34 @@ export class CustomTreeViewDragAndDrop implements ITreeDragAndDrop<ITreeItem> {

let treeSourceInfo: TreeDragSourceInfo | undefined;
await new Promise<void>(resolve => {
if (!originalEvent.dataTransfer || !this.dndController || !targetNode) {
function decrementStringCount() {
stringCount--;
if (stringCount === 0) {
resolve();
}
}

const dndController = this.dndController;
if (!originalEvent.dataTransfer || !dndController || !targetNode) {
return;
}
for (const dataItem of originalEvent.dataTransfer.items) {
const type = dataItem.type;
if (dataItem.kind === 'string') {
const type = dataItem.type;
dataItem.getAsString(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();
}
});
if ((type === this.treeMimeType) || (dndController.supportedMimeTypes.indexOf(type) >= 0)) {
dataItem.getAsString(dataValue => {
if (type === this.treeMimeType) {
treeSourceInfo = JSON.parse(dataValue);
} else {
treeDataTransfer.set(type, {
asString: () => Promise.resolve(dataValue)
});
}
decrementStringCount();
});
} else {
decrementStringCount();
}
}
}
});
Expand Down
5 changes: 2 additions & 3 deletions src/vs/workbench/common/views.ts
Original file line number Diff line number Diff line change
Expand Up @@ -646,9 +646,7 @@ export interface ITreeDataTransferItem {
asString(): Thenable<string>;
}

export interface ITreeDataTransfer {
items: Map<string, ITreeDataTransferItem>;
}
export type ITreeDataTransfer = Map<string, ITreeDataTransferItem>;

export interface ITreeView extends IDisposable {

Expand Down Expand Up @@ -839,6 +837,7 @@ export interface ITreeViewDataProvider {
}

export interface ITreeViewDragAndDropController {
readonly supportedMimeTypes: string[];
onDrop(elements: ITreeDataTransfer, target: ITreeItem, sourceTreeId?: string, sourceTreeItemHandles?: string[]): Promise<void>;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ suite('MainThreadHostTreeView', function () {
}
drain(): any { return null; }
}, new TestViewsService(), new TestNotificationService(), testExtensionService, new NullLogService());
mainThreadTreeViews.$registerTreeViewDataProvider(testTreeViewId, { showCollapseAll: false, canSelectMany: false, canDragAndDrop: false });
mainThreadTreeViews.$registerTreeViewDataProvider(testTreeViewId, { showCollapseAll: false, canSelectMany: false, dragAndDropMimeTypes: [] });
await testExtensionService.whenInstalledExtensionsRegistered();
});

Expand Down
15 changes: 8 additions & 7 deletions src/vscode-dts/vscode.proposed.treeViewDragAndDrop.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,24 +26,25 @@ declare module 'vscode' {
dragAndDropController?: DragAndDropController<T>;
}

export interface TreeDataTransferItem {
export class TreeDataTransferItem {
asString(): Thenable<string>;

constructor(value: any);
}

export interface TreeDataTransfer {
export class TreeDataTransfer<T extends TreeDataTransferItem = TreeDataTransferItem> {
/**
* A map containing a mapping of the mime type of the corresponding data.
* 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
forEach: (callbackfn: (value: TreeDataTransferItem, key: string) => void) => void;
};
get(mimeType: string): T | undefined;
set(mimeType: string, value: T): void;
forEach(callbackfn: (value: T, key: string) => void): void;
}

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

/**
* When the user drops an item from this DragAndDropController on **another tree item** in **the same tree**,
Expand Down

0 comments on commit daabfff

Please sign in to comment.