mirror of
https://github.com/microsoft/vscode.git
synced 2026-07-10 08:45:56 +01:00
list: drag and drop almost working
This commit is contained in:
@@ -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<T> {
|
||||
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<T> {
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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<T> {
|
||||
hasDynamicHeight: boolean;
|
||||
renderWidth: number | undefined;
|
||||
uri: string | undefined;
|
||||
dropTarget: boolean;
|
||||
dragStartDisposable: IDisposable;
|
||||
}
|
||||
|
||||
export interface IListViewDragAndDrop<T> extends IDragAndDrop<T> {
|
||||
getDragElements(element: T): T[];
|
||||
}
|
||||
|
||||
export interface IListViewOptions<T> {
|
||||
readonly dnd?: IDragAndDrop<T>;
|
||||
readonly dnd?: IListViewDragAndDrop<T>;
|
||||
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<T> 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<T> 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<T> implements ISpliceable<T>, IDisposable {
|
||||
|
||||
private static currentExternalDragAndDropData: IDragAndDropData | undefined;
|
||||
|
||||
readonly domNode: HTMLElement;
|
||||
|
||||
private items: IItem<T>[];
|
||||
@@ -90,18 +177,26 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
|
||||
private dragOverMouseY: number;
|
||||
private setRowLineHeight: boolean;
|
||||
private supportDynamicHeights: boolean;
|
||||
private readonly dnd: IDragAndDrop<T>;
|
||||
|
||||
private dnd: IListViewDragAndDrop<T>;
|
||||
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<number>();
|
||||
readonly onDidChangeContentHeight: Event<number> = 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<IListDragEvent<T>>;
|
||||
readonly onDragLeave: Event<void>;
|
||||
// readonly onDragOver: Event<IListDragEvent<T>>;
|
||||
// readonly onDragLeave: Event<void>;
|
||||
// readonly onDrop: Event<IListDragEvent<T>>;
|
||||
// readonly onDragEnd: Event<void>;
|
||||
|
||||
constructor(
|
||||
container: HTMLElement,
|
||||
@@ -150,18 +245,14 @@ export class ListView<T> implements ISpliceable<T>, 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<IListViewOptions<T>, IDragAndDrop<T>>(options, o => o.dnd, DefaultOptions.dnd);
|
||||
this.dnd = getOrDefault<IListViewOptions<T>, IListViewDragAndDrop<T>>(options, o => o.dnd, DefaultOptions.dnd);
|
||||
|
||||
this.layout();
|
||||
}
|
||||
@@ -203,6 +294,7 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
|
||||
renderWidth: undefined,
|
||||
row: null,
|
||||
uri: undefined,
|
||||
dropTarget: false,
|
||||
dragStartDisposable: Disposable.None
|
||||
}));
|
||||
|
||||
@@ -385,8 +477,8 @@ export class ListView<T> implements ISpliceable<T>, 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<T> implements ISpliceable<T>, 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<T> implements ISpliceable<T>, 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<T>): 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<T>): 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<T> implements ISpliceable<T>, 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<T> implements ISpliceable<T>, IDisposable {
|
||||
}
|
||||
}
|
||||
|
||||
private _onDragEnd(): void {
|
||||
private teardownDragAndDropScrollTopAnimation(): void {
|
||||
this.dragOverAnimationStopDisposable.dispose();
|
||||
|
||||
if (this.dragOverAnimationDisposable) {
|
||||
|
||||
@@ -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<T> implements ISpliceable<T> {
|
||||
|
||||
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<T> extends IListViewOptions<T>, IListStyles {
|
||||
identityProvider?: IIdentityProvider<T>;
|
||||
dnd?: IDragAndDrop<T>;
|
||||
keyboardNavigationLabelProvider?: IKeyboardNavigationLabelProvider<T>;
|
||||
ariaLabel?: string;
|
||||
mouseSupport?: boolean;
|
||||
keyboardSupport?: boolean;
|
||||
verticalScrollMode?: ScrollbarVisibility;
|
||||
multipleSelectionSupport?: boolean;
|
||||
multipleSelectionController?: IMultipleSelectionController<T>;
|
||||
openController?: IOpenController;
|
||||
styleController?: IStyleController;
|
||||
accessibilityProvider?: IAccessibilityProvider<T>;
|
||||
export interface IListOptions<T> extends IListStyles {
|
||||
readonly identityProvider?: IIdentityProvider<T>;
|
||||
readonly dnd?: IDragAndDrop<T>;
|
||||
readonly keyboardNavigationLabelProvider?: IKeyboardNavigationLabelProvider<T>;
|
||||
readonly ariaLabel?: string;
|
||||
readonly keyboardSupport?: boolean;
|
||||
readonly multipleSelectionSupport?: boolean;
|
||||
readonly multipleSelectionController?: IMultipleSelectionController<T>;
|
||||
readonly openController?: IOpenController;
|
||||
readonly styleController?: IStyleController;
|
||||
readonly accessibilityProvider?: IAccessibilityProvider<T>;
|
||||
|
||||
// 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<any> = {
|
||||
const DefaultOptions = {
|
||||
keyboardSupport: true,
|
||||
mouseSupport: true,
|
||||
multipleSelectionSupport: true,
|
||||
@@ -938,73 +940,8 @@ class AccessibiltyRenderer<T> implements IListRenderer<T, HTMLElement> {
|
||||
}
|
||||
}
|
||||
|
||||
export class ElementsDragAndDropData<T> 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<T> 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<T> implements ISpliceable<T>, IDisposable {
|
||||
|
||||
private static currentExternalDragAndDropData: IDragAndDropData | undefined;
|
||||
|
||||
private static InstanceCount = 0;
|
||||
private idPrefix = `list_id_${++List.InstanceCount}`;
|
||||
|
||||
@@ -1016,9 +953,6 @@ export class List<T> implements ISpliceable<T>, IDisposable {
|
||||
private styleElement: HTMLStyleElement;
|
||||
private styleController: IStyleController;
|
||||
|
||||
private readonly dnd: IDragAndDrop<T>;
|
||||
private currentDragAndDropData: IDragAndDropData | undefined;
|
||||
|
||||
protected disposables: IDisposable[];
|
||||
|
||||
@memoize get onFocusChange(): Event<IListEvent<T>> {
|
||||
@@ -1104,7 +1038,6 @@ export class List<T> implements ISpliceable<T>, IDisposable {
|
||||
) {
|
||||
this.focus = new FocusTrait(i => this.getElementDomId(i));
|
||||
this.selection = new Trait('selected');
|
||||
this.dnd = getOrDefault<IListOptions<T>, IDragAndDrop<T>>(options, o => o.dnd, DefaultOptions.dnd);
|
||||
|
||||
mixin(options, defaultStyles, false);
|
||||
|
||||
@@ -1116,7 +1049,20 @@ export class List<T> implements ISpliceable<T>, 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<T> = {
|
||||
...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<T> implements ISpliceable<T>, 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<T> implements ISpliceable<T>, 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<T>): void {
|
||||
console.log(event);
|
||||
}
|
||||
|
||||
private onDragLeave(): void {
|
||||
console.log('LEAVE');
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this._onDidDispose.fire();
|
||||
this.disposables = dispose(this.disposables);
|
||||
|
||||
@@ -32,11 +32,11 @@ function asListOptions<T, TFilterData>(options?: IAbstractTreeOptions<T, TFilter
|
||||
onDragStart(data, originalEvent) {
|
||||
return options.dnd!.onDragStart(data, originalEvent);
|
||||
},
|
||||
onDragOver(data, targetNode, originalEvent) {
|
||||
return options.dnd!.onDragOver(data, targetNode.element, originalEvent);
|
||||
onDragOver(data, targetNode, targetIndex, originalEvent) {
|
||||
return options.dnd!.onDragOver(data, targetNode && targetNode.element, targetIndex, originalEvent);
|
||||
},
|
||||
drop(data, targetNode, originalEvent) {
|
||||
return options.dnd!.drop(data, targetNode.element, originalEvent);
|
||||
return options.dnd!.drop(data, targetNode && targetNode.element, originalEvent);
|
||||
}
|
||||
},
|
||||
multipleSelectionController: options.multipleSelectionController && {
|
||||
|
||||
@@ -140,11 +140,11 @@ function asObjectTreeOptions<TInput, T, TFilterData>(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 && {
|
||||
|
||||
@@ -162,22 +162,24 @@ class WorkbenchOpenController implements IOpenController {
|
||||
}
|
||||
|
||||
function toWorkbenchListOptions<T>(options: IListOptions<T>, configurationService: IConfigurationService, keybindingService: IKeybindingService): IListOptions<T> {
|
||||
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;
|
||||
|
||||
@@ -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<ISCMResourceGroup | ISCMResource>;
|
||||
|
||||
Reference in New Issue
Block a user