From 2b2cdae172901707bafbbb99fdd7c945daed085e Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Wed, 9 Jan 2019 12:16:18 +0100 Subject: [PATCH] list: drag and drop almost working --- src/vs/base/browser/ui/list/list.ts | 10 +- src/vs/base/browser/ui/list/listView.ts | 292 ++++++++++++++++-- src/vs/base/browser/ui/list/listWidget.ts | 182 +++-------- src/vs/base/browser/ui/tree/abstractTree.ts | 6 +- src/vs/base/browser/ui/tree/asyncDataTree.ts | 6 +- src/vs/platform/list/browser/listService.ts | 10 +- .../parts/scm/electron-browser/scmViewlet.ts | 5 +- 7 files changed, 330 insertions(+), 181 deletions(-) diff --git a/src/vs/base/browser/ui/list/list.ts b/src/vs/base/browser/ui/list/list.ts index 09da2945df9..7933bcda6b9 100644 --- a/src/vs/base/browser/ui/list/list.ts +++ b/src/vs/base/browser/ui/list/list.ts @@ -7,7 +7,6 @@ import { GestureEvent } from 'vs/base/browser/touch'; import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { Event } from 'vs/base/common/event'; import { IDisposable } from 'vs/base/common/lifecycle'; -import { DragMouseEvent } from 'vs/base/browser/mouseEvent'; export interface IListVirtualDelegate { getHeight(element: T): number; @@ -82,6 +81,7 @@ export const enum DragOverEffect { export interface IDragOverReaction { accept: boolean; effect?: DragOverEffect; + feedback?: number[]; // use -1 for entire list // bubble?: DragOverBubble; // autoExpand?: boolean; } @@ -96,16 +96,16 @@ export const DragOverReactions = { }; export interface IDragAndDropData { - update(event: DragMouseEvent): void; + update(dataTransfer: DataTransfer): void; getData(): any; } export interface IDragAndDrop { getDragURI(element: T): string | null; getDragLabel?(elements: T[]): string; - onDragStart(data: IDragAndDropData, originalEvent: DragMouseEvent): void; - onDragOver(data: IDragAndDropData, targetElement: T, originalEvent: DragMouseEvent): boolean | IDragOverReaction; - drop(data: IDragAndDropData, targetElement: T, originalEvent: DragMouseEvent): void; + onDragStart(data: IDragAndDropData, originalEvent: DragEvent): void; + onDragOver(data: IDragAndDropData, targetElement: T | undefined, targetIndex: number | undefined, originalEvent: DragEvent): boolean | IDragOverReaction; + drop(data: IDragAndDropData, targetElement: T | undefined, originalEvent: DragEvent): void; } /** diff --git a/src/vs/base/browser/ui/list/listView.ts b/src/vs/base/browser/ui/list/listView.ts index d7ec9514290..c815b43f35b 100644 --- a/src/vs/base/browser/ui/list/listView.ts +++ b/src/vs/base/browser/ui/list/listView.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { getOrDefault } from 'vs/base/common/objects'; -import { IDisposable, dispose, Disposable } from 'vs/base/common/lifecycle'; +import { IDisposable, dispose, Disposable, toDisposable } from 'vs/base/common/lifecycle'; import { Gesture, EventType as TouchEventType, GestureEvent } from 'vs/base/browser/touch'; import * as DOM from 'vs/base/browser/dom'; import { Event, Emitter } from 'vs/base/common/event'; @@ -12,14 +12,15 @@ import { domEvent } from 'vs/base/browser/event'; import { ScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; import { ScrollEvent, ScrollbarVisibility, INewScrollDimensions } from 'vs/base/common/scrollable'; import { RangeMap, shift } from './rangeMap'; -import { IListVirtualDelegate, IListRenderer, IListMouseEvent, IListTouchEvent, IListGestureEvent, IDragAndDrop, IListDragEvent } from './list'; +import { IListVirtualDelegate, IListRenderer, IListMouseEvent, IListTouchEvent, IListGestureEvent, IListDragEvent, IDragAndDrop, IDragAndDropData } from './list'; import { RowCache, IRow } from './rowCache'; import { isWindows } from 'vs/base/common/platform'; import * as browser from 'vs/base/browser/browser'; import { ISpliceable } from 'vs/base/common/sequence'; import { memoize } from 'vs/base/common/decorators'; -import { DragMouseEvent } from 'vs/base/browser/mouseEvent'; import { Range, IRange } from 'vs/base/common/range'; +import { equals, distinct } from 'vs/base/common/arrays'; +import { DataTransfers } from 'vs/base/browser/dnd'; function canUseTranslate3d(): boolean { if (browser.isFirefox) { @@ -42,11 +43,16 @@ interface IItem { hasDynamicHeight: boolean; renderWidth: number | undefined; uri: string | undefined; + dropTarget: boolean; dragStartDisposable: IDisposable; } +export interface IListViewDragAndDrop extends IDragAndDrop { + getDragElements(element: T): T[]; +} + export interface IListViewOptions { - readonly dnd?: IDragAndDrop; + readonly dnd?: IListViewDragAndDrop; readonly useShadows?: boolean; readonly verticalScrollMode?: ScrollbarVisibility; readonly setRowLineHeight?: boolean; @@ -60,6 +66,7 @@ const DefaultOptions = { setRowLineHeight: true, supportDynamicHeights: false, dnd: { + getDragElements(e) { return [e]; }, getDragURI() { return null; }, onDragStart(): void { }, onDragOver() { return false; }, @@ -67,8 +74,88 @@ const DefaultOptions = { } }; +export class ElementsDragAndDropData implements IDragAndDropData { + + private elements: T[]; + + constructor(elements: T[]) { + this.elements = elements; + } + + public update(dataTransfer: DataTransfer): void { + // no-op + } + + public getData(): any { + return this.elements; + } +} + +export class ExternalElementsDragAndDropData implements IDragAndDropData { + + private elements: T[]; + + constructor(elements: T[]) { + this.elements = elements; + } + + public update(dataTransfer: DataTransfer): void { + // no-op + } + + public getData(): any { + return this.elements; + } +} + +export class DesktopDragAndDropData implements IDragAndDropData { + + private types: any[]; + private files: any[]; + + constructor() { + this.types = []; + this.files = []; + } + + public update(dataTransfer: DataTransfer): void { + if (dataTransfer.types) { + this.types = [...dataTransfer.types]; + } + + if (dataTransfer.files) { + this.files = []; + + for (let i = 0; i < dataTransfer.files.length; i++) { + const file = dataTransfer.files.item(i); + + if (file && (file.size || file.type)) { + this.files.push(file); + } + } + } + } + + public getData(): any { + return { + types: this.types, + files: this.files + }; + } +} + +function equalsDragFeedback(f1: number[] | undefined, f2: number[] | undefined): boolean { + if (Array.isArray(f1) && Array.isArray(f2)) { + return equals(f1, f2!); + } + + return f1 === f2; +} + export class ListView implements ISpliceable, IDisposable { + private static currentExternalDragAndDropData: IDragAndDropData | undefined; + readonly domNode: HTMLElement; private items: IItem[]; @@ -90,18 +177,26 @@ export class ListView implements ISpliceable, IDisposable { private dragOverMouseY: number; private setRowLineHeight: boolean; private supportDynamicHeights: boolean; - private readonly dnd: IDragAndDrop; + + private dnd: IListViewDragAndDrop; + private currentDragData: IDragAndDropData | undefined; + private currentDragFeedback: number[] | undefined; + private currentDragFeedbackDisposable: IDisposable = Disposable.None; + private onDragLeaveTimeout: IDisposable = Disposable.None; + private disposables: IDisposable[]; private _onDidChangeContentHeight = new Emitter(); readonly onDidChangeContentHeight: Event = Event.latch(this._onDidChangeContentHeight.event); get contentHeight(): number { return this.rangeMap.size; } - private _onDragStart = new Emitter<{ element: T, uri: string, event: DragMouseEvent }>(); - readonly onDragStart = this._onDragStart.event; + // private _onDragStart = new Emitter<{ element: T, uri: string, event: DragEvent }>(); + // readonly onDragStart = this._onDragStart.event; - readonly onDragOver: Event>; - readonly onDragLeave: Event; + // readonly onDragOver: Event>; + // readonly onDragLeave: Event; + // readonly onDrop: Event>; + // readonly onDragEnd: Event; constructor( container: HTMLElement, @@ -150,18 +245,14 @@ export class ListView implements ISpliceable, IDisposable { domEvent(this.scrollableElement.getDomNode(), 'scroll') (e => (e.target as HTMLElement).scrollTop = 0, null, this.disposables); - const onDragOver = Event.map(domEvent(this.domNode, 'dragover'), e => new DragMouseEvent(e)); - onDragOver(this.onDidDragOver, this, this.disposables); - - this.onDragOver = Event.map(domEvent(this.domNode, 'dragover'), e => this.toDragEvent(e)); - this.onDragLeave = Event.signal(domEvent(this.domNode, 'dragleave')); - - const onDragEnd = Event.map(domEvent(document, 'dragend'), e => new DragMouseEvent(e)); - onDragEnd(this._onDragEnd, this, this.disposables); + Event.map(domEvent(this.domNode, 'dragover'), e => this.toDragEvent(e))(this.onDragOver, this, this.disposables); + Event.map(domEvent(this.domNode, 'drop'), e => this.toDragEvent(e))(this.onDrop, this, this.disposables); + domEvent(this.domNode, 'dragleave')(this.onDragLeave, this, this.disposables); + domEvent(window, 'dragend')(this.onDragEnd, this, this.disposables); this.setRowLineHeight = getOrDefault(options, o => o.setRowLineHeight, DefaultOptions.setRowLineHeight); this.supportDynamicHeights = getOrDefault(options, o => o.supportDynamicHeights, DefaultOptions.supportDynamicHeights); - this.dnd = getOrDefault, IDragAndDrop>(options, o => o.dnd, DefaultOptions.dnd); + this.dnd = getOrDefault, IListViewDragAndDrop>(options, o => o.dnd, DefaultOptions.dnd); this.layout(); } @@ -203,6 +294,7 @@ export class ListView implements ISpliceable, IDisposable { renderWidth: undefined, row: null, uri: undefined, + dropTarget: false, dragStartDisposable: Disposable.None })); @@ -385,8 +477,8 @@ export class ListView implements ISpliceable, IDisposable { if (uri) { item.row.domNode!.draggable = true; - const onDragStart = Event.map(domEvent(item.row.domNode!, 'dragstart'), e => new DragMouseEvent(e)); - item.dragStartDisposable = onDragStart(event => this._onDragStart.fire({ element: item.element, uri, event }), this); + const onDragStart = domEvent(item.row.domNode!, 'dragstart'); + item.dragStartDisposable = onDragStart(event => this.onDragStart(item.element, uri, event)); } } @@ -402,6 +494,7 @@ export class ListView implements ISpliceable, IDisposable { item.row!.domNode!.setAttribute('data-last-element', index === this.length - 1 ? 'true' : 'false'); item.row!.domNode!.setAttribute('aria-setsize', `${this.length}`); item.row!.domNode!.setAttribute('aria-posinset', `${index + 1}`); + DOM.toggleClass(item.row!.domNode!, 'drop-target', item.dropTarget); } private removeItemFromDOM(index: number): void { @@ -509,7 +602,154 @@ export class ListView implements ISpliceable, IDisposable { // DND - private onDidDragOver(event: DragMouseEvent): void { + private onDragStart(element: T, uri: string, event: DragEvent): void { + if (!event.dataTransfer) { + return; + } + + const elements = this.dnd.getDragElements(element); + + event.dataTransfer.effectAllowed = 'copyMove'; + event.dataTransfer.setData(DataTransfers.RESOURCES, JSON.stringify([uri])); + + if (event.dataTransfer.setDragImage) { + let label: string; + + if (this.dnd.getDragLabel) { + label = this.dnd.getDragLabel(elements); + } else { + label = String(elements.length); + } + + const dragImage = DOM.$('.monaco-list-drag-image'); + dragImage.textContent = label; + document.body.appendChild(dragImage); + event.dataTransfer.setDragImage(dragImage, -10, -10); + setTimeout(() => document.body.removeChild(dragImage), 0); + } + + this.currentDragData = new ElementsDragAndDropData(elements); + ListView.currentExternalDragAndDropData = new ExternalElementsDragAndDropData(elements); + + this.dnd.onDragStart(this.currentDragData, event); + } + + private onDragOver(event: IListDragEvent): boolean { + console.log('DRAG OVER'); + + this.onDragLeaveTimeout.dispose(); + this.setupDragAndDropScrollTopAnimation(event.browserEvent); + + if (!event.browserEvent.dataTransfer) { + return false; + } + + // Drag over from outside + if (!this.currentDragData) { + if (ListView.currentExternalDragAndDropData) { + // Drag over from another list + this.currentDragData = ListView.currentExternalDragAndDropData; + + } else { + // Drag over from the desktop + if (!event.browserEvent.dataTransfer.types) { + return false; + } + + this.currentDragData = new DesktopDragAndDropData(); + } + } + + const result = this.dnd.onDragOver(this.currentDragData, event.element, event.index, event.browserEvent); + const canDrop = typeof result === 'boolean' ? result : result.accept; + // const reaction = (typeof result !== 'boolean' && result.effect === DragOverEffect.Copy) ? 'copy' : 'move'; + + if (!canDrop) { + return false; + } + + let feedback: number[]; + + if (typeof result !== 'boolean' && result.feedback) { + feedback = result.feedback; + } else { + if (typeof event.index === 'undefined') { + feedback = [-1]; + } else { + feedback = [event.index]; + } + } + + // sanitize feedback list + feedback = distinct(feedback).filter(i => i >= -1 && i < this.length).sort(); + feedback = feedback[0] === -1 ? [-1] : feedback; + + if (feedback.length === 0) { + throw new Error('Invalid empty feedback list'); + } + + if (equalsDragFeedback(this.currentDragFeedback, feedback)) { + return true; + } + + console.log(this.currentDragFeedback, feedback); + + this.currentDragFeedback = feedback; + this.currentDragFeedbackDisposable.dispose(); + + if (feedback[0] === -1) { // entire list feedback + DOM.addClass(this.domNode, 'drop-target'); + this.currentDragFeedbackDisposable = toDisposable(() => DOM.removeClass(this.domNode, 'drop-target')); + } else { + for (const index of feedback) { + const item = this.items[index]!; + item.dropTarget = true; + + if (item.row && item.row.domNode) { + DOM.addClass(item.row.domNode, 'drop-target'); + } + } + + this.currentDragFeedbackDisposable = toDisposable(() => { + for (const index of feedback) { + const item = this.items[index]!; + item.dropTarget = false; + + if (item.row && item.row.domNode) { + DOM.removeClass(item.row.domNode, 'drop-target'); + } + } + }); + } + + return true; + } + + private onDragLeave(event: DragEvent): void { + console.log('LEAVE'); + this.onDragLeaveTimeout = DOM.timeout(() => this.clearDragOverFeedback(), 100); + } + + private onDrop(e: IListDragEvent): void { + console.log('DROP'); + this.teardownDragAndDropScrollTopAnimation(); + this.clearDragOverFeedback(); + } + + private onDragEnd(): void { + console.log('DRAG END'); + this.teardownDragAndDropScrollTopAnimation(); + this.clearDragOverFeedback(); + } + + private clearDragOverFeedback(): void { + this.currentDragFeedback = undefined; + this.currentDragFeedbackDisposable.dispose(); + } + + // DND scroll top animation + + private setupDragAndDropScrollTopAnimation(event: DragEvent): void { if (!this.dragOverAnimationDisposable) { const viewTop = DOM.getTopLeftOffset(this.domNode).top; this.dragOverAnimationDisposable = DOM.animate(this.animateDragAndDropScrollTop.bind(this, viewTop)); @@ -517,11 +757,13 @@ export class ListView implements ISpliceable, IDisposable { this.dragOverAnimationStopDisposable.dispose(); this.dragOverAnimationStopDisposable = DOM.timeout(() => { - this.dragOverAnimationDisposable.dispose(); - this.dragOverAnimationDisposable = undefined; + if (this.dragOverAnimationDisposable) { + this.dragOverAnimationDisposable.dispose(); + this.dragOverAnimationDisposable = undefined; + } }, 1000); - this.dragOverMouseY = event.posy; + this.dragOverMouseY = event.pageY; } private animateDragAndDropScrollTop(viewTop: number): void { @@ -539,7 +781,7 @@ export class ListView implements ISpliceable, IDisposable { } } - private _onDragEnd(): void { + private teardownDragAndDropScrollTopAnimation(): void { this.dragOverAnimationStopDisposable.dispose(); if (this.dragOverAnimationDisposable) { diff --git a/src/vs/base/browser/ui/list/listWidget.ts b/src/vs/base/browser/ui/list/listWidget.ts index 8e839278ac1..0d11dc51b9e 100644 --- a/src/vs/base/browser/ui/list/listWidget.ts +++ b/src/vs/base/browser/ui/list/listWidget.ts @@ -16,17 +16,15 @@ import { KeyCode } from 'vs/base/common/keyCodes'; import { StandardKeyboardEvent, IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { Event, Emitter, EventBufferer } from 'vs/base/common/event'; import { domEvent } from 'vs/base/browser/event'; -import { IListVirtualDelegate, IListRenderer, IListEvent, IListContextMenuEvent, IListMouseEvent, IListTouchEvent, IListGestureEvent, IIdentityProvider, IKeyboardNavigationLabelProvider, IDragAndDrop, IDragAndDropData, IListDragEvent } from './list'; +import { IListVirtualDelegate, IListRenderer, IListEvent, IListContextMenuEvent, IListMouseEvent, IListTouchEvent, IListGestureEvent, IIdentityProvider, IKeyboardNavigationLabelProvider, IDragAndDrop } from './list'; import { ListView, IListViewOptions } from './listView'; import { Color } from 'vs/base/common/color'; -import { mixin, getOrDefault } from 'vs/base/common/objects'; +import { mixin } from 'vs/base/common/objects'; import { ScrollbarVisibility } from 'vs/base/common/scrollable'; import { ISpliceable } from 'vs/base/common/sequence'; import { CombinedSpliceable } from 'vs/base/browser/ui/list/splice'; import { clamp } from 'vs/base/common/numbers'; import { matchesPrefix } from 'vs/base/common/filters'; -import { DragMouseEvent } from 'vs/base/browser/mouseEvent'; -import { DataTransfers } from 'vs/base/browser/dnd'; interface ITraitChangeEvent { indexes: number[]; @@ -214,7 +212,7 @@ class TraitSpliceable implements ISpliceable { splice(start: number, deleteCount: number, elements: T[]): void { if (!this.identityProvider) { - return this.trait.splice(start, deleteCount, elements.map(e => false)); + return this.trait.splice(start, deleteCount, elements.map(() => false)); } const pastElementsWithTrait = this.trait.get().map(i => this.identityProvider!.getId(this.view.element(i)).toString()); @@ -714,13 +712,12 @@ export class DefaultStyleController implements IStyleController { content.push(`.monaco-list${suffix} .monaco-list-row:hover { outline: 1px dashed ${styles.listHoverOutline}; outline-offset: -1px; }`); } - // TODO - // if (styles.listDropBackground) { - // content.push(` - // .monaco-tree${suffix} .monaco-tree-wrapper.drop-target, - // .monaco-tree${suffix} .monaco-tree-rows > .monaco-tree-row.drop-target { background-color: ${styles.listDropBackground} !important; color: inherit !important; } - // `); - // } + if (styles.listDropBackground) { + content.push(` + .monaco-list${suffix}.drop-target, + .monaco-list${suffix} .monaco-list-row.drop-target { background-color: ${styles.listDropBackground} !important; color: inherit !important; } + `); + } const newStyles = content.join('\n'); if (newStyles !== this.styleElement.innerHTML) { @@ -729,19 +726,24 @@ export class DefaultStyleController implements IStyleController { } } -export interface IListOptions extends IListViewOptions, IListStyles { - identityProvider?: IIdentityProvider; - dnd?: IDragAndDrop; - keyboardNavigationLabelProvider?: IKeyboardNavigationLabelProvider; - ariaLabel?: string; - mouseSupport?: boolean; - keyboardSupport?: boolean; - verticalScrollMode?: ScrollbarVisibility; - multipleSelectionSupport?: boolean; - multipleSelectionController?: IMultipleSelectionController; - openController?: IOpenController; - styleController?: IStyleController; - accessibilityProvider?: IAccessibilityProvider; +export interface IListOptions extends IListStyles { + readonly identityProvider?: IIdentityProvider; + readonly dnd?: IDragAndDrop; + readonly keyboardNavigationLabelProvider?: IKeyboardNavigationLabelProvider; + readonly ariaLabel?: string; + readonly keyboardSupport?: boolean; + readonly multipleSelectionSupport?: boolean; + readonly multipleSelectionController?: IMultipleSelectionController; + readonly openController?: IOpenController; + readonly styleController?: IStyleController; + readonly accessibilityProvider?: IAccessibilityProvider; + + // list view options + readonly useShadows?: boolean; + readonly verticalScrollMode?: ScrollbarVisibility; + readonly setRowLineHeight?: boolean; + readonly supportDynamicHeights?: boolean; + readonly mouseSupport?: boolean; } export interface IListStyles { @@ -774,7 +776,7 @@ const defaultStyles: IListStyles = { listDropBackground: Color.fromHex('#383B3D') }; -const DefaultOptions: IListOptions = { +const DefaultOptions = { keyboardSupport: true, mouseSupport: true, multipleSelectionSupport: true, @@ -938,73 +940,8 @@ class AccessibiltyRenderer implements IListRenderer { } } -export class ElementsDragAndDropData implements IDragAndDropData { - - private elements: T[]; - - constructor(elements: T[]) { - this.elements = elements; - } - - public update(event: DragMouseEvent): void { - // no-op - } - - public getData(): any { - return this.elements; - } -} - -export class ExternalElementsDragAndDropData implements IDragAndDropData { - - private elements: T[]; - - constructor(elements: T[]) { - this.elements = elements; - } - - public update(event: DragMouseEvent): void { - // no-op - } - - public getData(): any { - return this.elements; - } -} - -export class DesktopDragAndDropData implements IDragAndDropData { - - private types: any[]; - private files: any[]; - - constructor() { - this.types = []; - this.files = []; - } - - public update(event: DragMouseEvent): void { - if (event.dataTransfer.types) { - this.types = [...event.dataTransfer.types]; - } - - if (event.dataTransfer.files) { - this.files = [...event.dataTransfer.files]; - this.files = this.files.filter(f => f.size || f.type); - } - } - - public getData(): any { - return { - types: this.types, - files: this.files - }; - } -} - export class List implements ISpliceable, IDisposable { - private static currentExternalDragAndDropData: IDragAndDropData | undefined; - private static InstanceCount = 0; private idPrefix = `list_id_${++List.InstanceCount}`; @@ -1016,9 +953,6 @@ export class List implements ISpliceable, IDisposable { private styleElement: HTMLStyleElement; private styleController: IStyleController; - private readonly dnd: IDragAndDrop; - private currentDragAndDropData: IDragAndDropData | undefined; - protected disposables: IDisposable[]; @memoize get onFocusChange(): Event> { @@ -1104,7 +1038,6 @@ export class List implements ISpliceable, IDisposable { ) { this.focus = new FocusTrait(i => this.getElementDomId(i)); this.selection = new Trait('selected'); - this.dnd = getOrDefault, IDragAndDrop>(options, o => o.dnd, DefaultOptions.dnd); mixin(options, defaultStyles, false); @@ -1116,7 +1049,20 @@ export class List implements ISpliceable, IDisposable { renderers = renderers.map(r => new PipelineRenderer(r.templateId, [...baseRenderers, r])); - this.view = new ListView(container, virtualDelegate, renderers, options); + const that = this; + const viewOptions: IListViewOptions = { + ...options, + dnd: options.dnd && { + ...options.dnd, + getDragElements(element) { + const selection = that.getSelectedElements(); + const elements = selection.indexOf(element) > -1 ? selection : [element]; + return elements; + } + } + }; + + this.view = new ListView(container, virtualDelegate, renderers, viewOptions); this.view.domNode.setAttribute('role', 'tree'); DOM.addClass(this.view.domNode, this.idPrefix); this.view.domNode.tabIndex = 0; @@ -1152,11 +1098,6 @@ export class List implements ISpliceable, IDisposable { this.disposables.push(new MouseController(this, this.view, options)); } - this.view.onDragStart(this.onDragStart, this, this.disposables); - - this.view.onDragOver(this.onDragOver, this, this.disposables); - this.view.onDragLeave(this.onDragLeave, this, this.disposables); - this.onFocusChange(this._onFocusChange, this, this.disposables); this.onSelectionChange(this._onSelectionChange, this, this.disposables); @@ -1443,45 +1384,6 @@ export class List implements ISpliceable, IDisposable { DOM.toggleClass(this.view.domNode, 'selection-multiple', selection.length > 1); } - // DND - - private onDragStart({ element, uri, event }: { element: T, uri: string, event: DragMouseEvent }): void { - const selection = this.getSelectedElements(); - const elements = selection.indexOf(element) > -1 ? selection : [element]; - - event.dataTransfer.effectAllowed = 'copyMove'; - event.dataTransfer.setData(DataTransfers.RESOURCES, JSON.stringify([uri])); - - if (event.dataTransfer.setDragImage) { - let label: string; - - if (this.dnd.getDragLabel) { - label = this.dnd.getDragLabel(elements); - } else { - label = String(elements.length); - } - - const dragImage = DOM.$('.monaco-list-drag-image'); - dragImage.textContent = label; - document.body.appendChild(dragImage); - event.dataTransfer.setDragImage(dragImage, -10, -10); - setTimeout(() => document.body.removeChild(dragImage), 0); - } - - this.currentDragAndDropData = new ElementsDragAndDropData(elements); - List.currentExternalDragAndDropData = new ExternalElementsDragAndDropData(elements); - - this.dnd.onDragStart(this.currentDragAndDropData, event); - } - - private onDragOver(event: IListDragEvent): void { - console.log(event); - } - - private onDragLeave(): void { - console.log('LEAVE'); - } - dispose(): void { this._onDidDispose.fire(); this.disposables = dispose(this.disposables); diff --git a/src/vs/base/browser/ui/tree/abstractTree.ts b/src/vs/base/browser/ui/tree/abstractTree.ts index 0ece88b2d95..494fcef7b0e 100644 --- a/src/vs/base/browser/ui/tree/abstractTree.ts +++ b/src/vs/base/browser/ui/tree/abstractTree.ts @@ -32,11 +32,11 @@ function asListOptions(options?: IAbstractTreeOptions(options?: IAsyncDataTreeOpt onDragStart(data, originalEvent) { return options.dnd!.onDragStart(data, originalEvent); }, - onDragOver(data, targetNode, originalEvent) { - return options.dnd!.onDragOver(data, targetNode.element as T, originalEvent); + onDragOver(data, targetNode, targetIndex, originalEvent) { + return options.dnd!.onDragOver(data, targetNode && targetNode.element as T, targetIndex, originalEvent); }, drop(data, targetNode, originalEvent) { - return options.dnd!.drop(data, targetNode.element as T, originalEvent); + return options.dnd!.drop(data, targetNode && targetNode.element as T, originalEvent); } }, multipleSelectionController: options.multipleSelectionController && { diff --git a/src/vs/platform/list/browser/listService.ts b/src/vs/platform/list/browser/listService.ts index 04f706b9377..d656dfc8e45 100644 --- a/src/vs/platform/list/browser/listService.ts +++ b/src/vs/platform/list/browser/listService.ts @@ -162,22 +162,24 @@ class WorkbenchOpenController implements IOpenController { } function toWorkbenchListOptions(options: IListOptions, configurationService: IConfigurationService, keybindingService: IKeybindingService): IListOptions { + const result = { ...options }; + if (options.multipleSelectionSupport !== false && !options.multipleSelectionController) { - options.multipleSelectionController = new MultipleSelectionController(configurationService); + result.multipleSelectionController = new MultipleSelectionController(configurationService); } - options.openController = new WorkbenchOpenController(configurationService, options.openController); + result.openController = new WorkbenchOpenController(configurationService, options.openController); if (options.keyboardNavigationLabelProvider) { const tlp = options.keyboardNavigationLabelProvider; - options.keyboardNavigationLabelProvider = { + result.keyboardNavigationLabelProvider = { getKeyboardNavigationLabel(e) { return tlp.getKeyboardNavigationLabel(e); }, mightProducePrintableCharacter(e) { return keybindingService.mightProducePrintableCharacter(e); } }; } - return options; + return result; } let sharedListStyleSheet: HTMLStyleElement; diff --git a/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts b/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts index 4d619e03f5d..d6b6d3a6bcb 100644 --- a/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts +++ b/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts @@ -878,6 +878,7 @@ export class RepositoryPanel extends ViewletPanel { new ResourceRenderer(this.listLabels, actionItemProvider, () => this.getSelectedResources(), this.themeService, this.menus) ]; + const that = this; this.list = this.instantiationService.createInstance(WorkbenchList, this.listContainer, delegate, renderers, { identityProvider: scmResourceIdentityProvider, keyboardNavigationLabelProvider: scmKeyboardNavigationLabelProvider, @@ -885,7 +886,9 @@ export class RepositoryPanel extends ViewletPanel { getDragURI(element) { return 'file:///foo'; }, // getDragLabel(elements) { return 'dragging'; }, onDragStart(data, originalEvent) { }, - onDragOver(data, targetElement, originalEvent) { return true; }, + onDragOver(data, targetElement, targetIndex, originalEvent) { + return { accept: true, feedback: typeof targetIndex === 'undefined' ? undefined : [targetIndex - 1, targetIndex, targetIndex + 1].filter(i => i >= 0 && i < that.list.length) }; + }, drop(data, targetElement, originalEvent) { } } }) as WorkbenchList;