mirror of
https://github.com/mathuo/dockview
synced 2025-02-08 17:35:44 +00:00
work in progress
This commit is contained in:
parent
c94987d88c
commit
adf09a1a25
@ -3,6 +3,7 @@ import {
|
||||
CompositeDisposable,
|
||||
GridviewApi,
|
||||
IGridviewPanelProps,
|
||||
DockviewDropTarget,
|
||||
} from 'dockview';
|
||||
import './activitybar.scss';
|
||||
import { useLayoutRegistry } from './registry';
|
||||
@ -48,13 +49,18 @@ export const Activitybar = (props: IGridviewPanelProps) => {
|
||||
|
||||
return (
|
||||
<div className="activity-bar" onClick={onOpenSidebar}>
|
||||
<div className="activity-bar-item">
|
||||
<ActivitybarImage
|
||||
url={
|
||||
'https://fonts.gstatic.com/s/i/materialicons/search/v7/24px.svg'
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<DockviewDropTarget
|
||||
validOverlays={'vertical'}
|
||||
canDisplayOverlay={true}
|
||||
>
|
||||
<div className="activity-bar-item">
|
||||
<ActivitybarImage
|
||||
url={
|
||||
'https://fonts.gstatic.com/s/i/materialicons/search/v7/24px.svg'
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</DockviewDropTarget>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -8,20 +8,20 @@ export const ControlCenter = () => {
|
||||
|
||||
const dragRef = React.useRef<HTMLDivElement>();
|
||||
|
||||
React.useEffect(() => {
|
||||
const api = registry.get<DockviewApi>('dockview');
|
||||
const target = api.createDragTarget(
|
||||
{ element: dragRef.current, content: 'drag me' },
|
||||
() => ({
|
||||
id: 'yellow',
|
||||
component: 'test_component',
|
||||
})
|
||||
);
|
||||
// React.useEffect(() => {
|
||||
// const api = registry.get<DockviewApi>('dockview');
|
||||
// const target = api.createDragTarget(
|
||||
// { element: dragRef.current, content: 'drag me' },
|
||||
// () => ({
|
||||
// id: 'yellow',
|
||||
// component: 'test_component',
|
||||
// })
|
||||
// );
|
||||
|
||||
return () => {
|
||||
target.dispose();
|
||||
};
|
||||
}, []);
|
||||
// return () => {
|
||||
// target.dispose();
|
||||
// };
|
||||
// }, []);
|
||||
|
||||
const onDragStart = (event: React.DragEvent) => {
|
||||
event.dataTransfer.setData('text/plain', 'Panel2');
|
||||
|
@ -304,26 +304,26 @@ export const TestGrid = (props: IGridviewPanelProps) => {
|
||||
_api.current = event.api;
|
||||
registry.register('dockview', api);
|
||||
|
||||
api.addDndHandle('text/plain', (ev) => {
|
||||
const { event } = ev;
|
||||
// api.addDndHandle('text/plain', (ev) => {
|
||||
// const { event } = ev;
|
||||
|
||||
return {
|
||||
id: 'yellow',
|
||||
component: 'test_component',
|
||||
};
|
||||
});
|
||||
// return {
|
||||
// id: 'yellow',
|
||||
// component: 'test_component',
|
||||
// };
|
||||
// });
|
||||
|
||||
api.addDndHandle('Files', (ev) => {
|
||||
const { event } = ev;
|
||||
// api.addDndHandle('Files', (ev) => {
|
||||
// const { event } = ev;
|
||||
|
||||
ev.event.event.preventDefault();
|
||||
// ev.event.event.preventDefault();
|
||||
|
||||
return {
|
||||
id: Date.now().toString(),
|
||||
title: event.event.dataTransfer.files[0].name,
|
||||
component: 'test_component',
|
||||
};
|
||||
});
|
||||
// return {
|
||||
// id: Date.now().toString(),
|
||||
// title: event.event.dataTransfer.files[0].name,
|
||||
// component: 'test_component',
|
||||
// };
|
||||
// });
|
||||
|
||||
const state = localStorage.getItem('dockview');
|
||||
if (state) {
|
||||
|
@ -6,6 +6,7 @@ import {
|
||||
IPaneviewPanelProps,
|
||||
CompositeDisposable,
|
||||
PaneviewApi,
|
||||
PaneviewDropEvent,
|
||||
} from 'dockview';
|
||||
import { ControlCenter } from './controlCenter';
|
||||
import { toggleClass } from '../dom';
|
||||
@ -183,6 +184,10 @@ export const Sidebar = (props: IGridviewPanelProps) => {
|
||||
};
|
||||
}, []);
|
||||
|
||||
const onDidDrop = React.useCallback((event: PaneviewDropEvent) => {
|
||||
console.log('drop', event);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
@ -194,6 +199,7 @@ export const Sidebar = (props: IGridviewPanelProps) => {
|
||||
headerComponents={headerComponents}
|
||||
components={components}
|
||||
onReady={onReady}
|
||||
onDidDrop={onDidDrop}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
@ -26,17 +26,16 @@ describe('droptarget', () => {
|
||||
let position: Position | undefined = undefined;
|
||||
|
||||
droptarget = new Droptarget(element, {
|
||||
isDisabled: () => false,
|
||||
isDirectional: false,
|
||||
id: 'test-dnd',
|
||||
enableExternalDragEvents: true,
|
||||
canDisplayOverlay: () => true,
|
||||
validOverlays: 'none',
|
||||
});
|
||||
|
||||
droptarget.onDidChange((event) => {
|
||||
droptarget.onDrop((event) => {
|
||||
position = event.position;
|
||||
});
|
||||
|
||||
fireEvent.dragEnter(element);
|
||||
fireEvent.dragOver(element);
|
||||
|
||||
const target = element.querySelector(
|
||||
'.drop-target-dropzone'
|
||||
@ -49,17 +48,16 @@ describe('droptarget', () => {
|
||||
let position: Position | undefined = undefined;
|
||||
|
||||
droptarget = new Droptarget(element, {
|
||||
isDisabled: () => false,
|
||||
isDirectional: true,
|
||||
id: 'test-dnd',
|
||||
enableExternalDragEvents: true,
|
||||
canDisplayOverlay: () => true,
|
||||
validOverlays: 'all',
|
||||
});
|
||||
|
||||
droptarget.onDidChange((event) => {
|
||||
droptarget.onDrop((event) => {
|
||||
position = event.position;
|
||||
});
|
||||
|
||||
fireEvent.dragEnter(element);
|
||||
fireEvent.dragOver(element);
|
||||
|
||||
const target = element.querySelector(
|
||||
'.drop-target-dropzone'
|
||||
@ -80,15 +78,14 @@ describe('droptarget', () => {
|
||||
|
||||
test('default', () => {
|
||||
droptarget = new Droptarget(element, {
|
||||
isDisabled: () => false,
|
||||
isDirectional: true,
|
||||
id: 'test-dnd',
|
||||
enableExternalDragEvents: true,
|
||||
canDisplayOverlay: () => true,
|
||||
validOverlays: 'all',
|
||||
});
|
||||
|
||||
expect(droptarget.state).toBeUndefined();
|
||||
|
||||
fireEvent.dragEnter(element);
|
||||
fireEvent.dragOver(element);
|
||||
|
||||
let viewQuery = element.querySelectorAll(
|
||||
'.drop-target > .drop-target-dropzone > .drop-target-selection'
|
||||
|
@ -9,6 +9,7 @@ import { Orientation } from '../../splitview/core/splitview';
|
||||
import { ReactPanelDeserialzier } from '../../react/deserializer';
|
||||
import { Position } from '../../dnd/droptarget';
|
||||
import { GroupviewPanel } from '../../groupview/groupviewPanel';
|
||||
import { IGroupPanel } from '../../groupview/groupPanel';
|
||||
class PanelContentPartTest implements IContentRenderer {
|
||||
element: HTMLElement = document.createElement('div');
|
||||
|
||||
@ -357,6 +358,7 @@ describe('dockviewComponent', () => {
|
||||
data: {
|
||||
views: ['panel1'],
|
||||
id: 'group-1',
|
||||
activeView: 'panel1',
|
||||
},
|
||||
size: 500,
|
||||
},
|
||||
|
@ -16,7 +16,7 @@ import { fireEvent } from '@testing-library/dom';
|
||||
import { LocalSelectionTransfer } from '../../dnd/dataTransfer';
|
||||
import { Position } from '../../dnd/droptarget';
|
||||
import { GroupviewPanel } from '../../groupview/groupviewPanel';
|
||||
import { GroupOptions, GroupDropEvent } from '../../groupview/groupview';
|
||||
import { GroupOptions } from '../../groupview/groupview';
|
||||
import { DockviewPanelApi } from '../../api/groupPanelApi';
|
||||
import {
|
||||
DefaultGroupPanelView,
|
||||
@ -294,44 +294,44 @@ describe('groupview', () => {
|
||||
expect(viewQuery).toBeTruthy();
|
||||
});
|
||||
|
||||
test('dnd', () => {
|
||||
const panel1 = new TestPanel('panel1', jest.fn() as any);
|
||||
const panel2 = new TestPanel('panel2', jest.fn() as any);
|
||||
// test('dnd', () => {
|
||||
// const panel1 = new TestPanel('panel1', jest.fn() as any);
|
||||
// const panel2 = new TestPanel('panel2', jest.fn() as any);
|
||||
|
||||
groupview.model.openPanel(panel1);
|
||||
groupview.model.openPanel(panel2);
|
||||
// groupview.model.openPanel(panel1);
|
||||
// groupview.model.openPanel(panel2);
|
||||
|
||||
const events: GroupDropEvent[] = [];
|
||||
// const events: GroupDropEvent[] = [];
|
||||
|
||||
groupview.model.onDrop((event) => {
|
||||
events.push(event);
|
||||
});
|
||||
// groupview.model.onDrop((event) => {
|
||||
// events.push(event);
|
||||
// });
|
||||
|
||||
const viewQuery = groupview.element.querySelectorAll(
|
||||
'.groupview > .tabs-and-actions-container > .tabs-container > .tab'
|
||||
);
|
||||
expect(viewQuery.length).toBe(2);
|
||||
// const viewQuery = groupview.element.querySelectorAll(
|
||||
// '.groupview > .tabs-and-actions-container > .tabs-container > .tab'
|
||||
// );
|
||||
// expect(viewQuery.length).toBe(2);
|
||||
|
||||
LocalSelectionTransfer.getInstance().setData([], 'dockview-1');
|
||||
// LocalSelectionTransfer.getInstance().setData([], 'dockview-1');
|
||||
|
||||
fireEvent.dragEnter(viewQuery[0]);
|
||||
// fireEvent.dragEnter(viewQuery[0]);
|
||||
|
||||
let dropTarget = viewQuery[0].querySelector('.drop-target-dropzone');
|
||||
fireEvent.dragOver(dropTarget);
|
||||
fireEvent.drop(dropTarget);
|
||||
// let dropTarget = viewQuery[0].querySelector('.drop-target-dropzone');
|
||||
// fireEvent.dragOver(dropTarget);
|
||||
// fireEvent.drop(dropTarget);
|
||||
|
||||
expect(events.length).toBe(1);
|
||||
expect(events[0].target).toBe(Position.Center);
|
||||
expect(events[0].index).toBe(0);
|
||||
// expect(events.length).toBe(1);
|
||||
// expect(events[0].target).toBe(Position.Center);
|
||||
// expect(events[0].index).toBe(0);
|
||||
|
||||
fireEvent.dragEnter(viewQuery[1]);
|
||||
// fireEvent.dragEnter(viewQuery[1]);
|
||||
|
||||
dropTarget = viewQuery[1].querySelector('.drop-target-dropzone');
|
||||
fireEvent.dragOver(dropTarget);
|
||||
fireEvent.drop(dropTarget);
|
||||
// dropTarget = viewQuery[1].querySelector('.drop-target-dropzone');
|
||||
// fireEvent.dragOver(dropTarget);
|
||||
// fireEvent.drop(dropTarget);
|
||||
|
||||
expect(events.length).toBe(2);
|
||||
expect(events[1].target).toBe(Position.Center);
|
||||
expect(events[1].index).toBe(1);
|
||||
});
|
||||
// expect(events.length).toBe(2);
|
||||
// expect(events[1].target).toBe(Position.Center);
|
||||
// expect(events[1].index).toBe(1);
|
||||
// });
|
||||
});
|
||||
|
@ -1,6 +1,5 @@
|
||||
import {
|
||||
IDockviewComponent,
|
||||
LayoutDropEvent,
|
||||
SerializedDockview,
|
||||
} from '../dockview/dockviewComponent';
|
||||
import {
|
||||
@ -339,19 +338,19 @@ export class DockviewApi {
|
||||
return this.component.addPanel(options);
|
||||
}
|
||||
|
||||
addDndHandle(type: string, cb: (event: LayoutDropEvent) => PanelOptions) {
|
||||
return this.component.addDndHandle(type, cb);
|
||||
}
|
||||
// addDndHandle(type: string, cb: (event: LayoutDropEvent) => PanelOptions) {
|
||||
// return this.component.addDndHandle(type, cb);
|
||||
// }
|
||||
|
||||
createDragTarget(
|
||||
target: {
|
||||
element: HTMLElement;
|
||||
content: string;
|
||||
},
|
||||
options: (() => PanelOptions) | PanelOptions
|
||||
) {
|
||||
return this.component.createDragTarget(target, options);
|
||||
}
|
||||
// createDragTarget(
|
||||
// target: {
|
||||
// element: HTMLElement;
|
||||
// content: string;
|
||||
// },
|
||||
// options: (() => PanelOptions) | PanelOptions
|
||||
// ) {
|
||||
// return this.component.createDragTarget(target, options);
|
||||
// }
|
||||
|
||||
addEmptyGroup(options?: AddGroupOptions) {
|
||||
return this.component.addEmptyGroup(options);
|
||||
|
100
packages/dockview/src/dnd/abstractDragHandler.ts
Normal file
100
packages/dockview/src/dnd/abstractDragHandler.ts
Normal file
@ -0,0 +1,100 @@
|
||||
import { getElementsByTagName } from '../dom';
|
||||
import { addDisposableListener, Emitter } from '../events';
|
||||
import { focusedElement } from '../focusedElement';
|
||||
import { CompositeDisposable, IDisposable } from '../lifecycle';
|
||||
import { DATA_KEY, LocalSelectionTransfer } from './dataTransfer';
|
||||
|
||||
export abstract class DragHandler extends CompositeDisposable {
|
||||
private iframes: HTMLElement[] = [];
|
||||
|
||||
private readonly _onDragStart = new Emitter<void>();
|
||||
readonly onDragStart = this._onDragStart.event;
|
||||
|
||||
// private activeDrag: { id: string } | undefined;
|
||||
|
||||
// get isDragging() {
|
||||
// return !!this.activeDrag;
|
||||
// }
|
||||
|
||||
private disposable: IDisposable | undefined;
|
||||
|
||||
constructor(private readonly el: HTMLElement) {
|
||||
super();
|
||||
this.configure();
|
||||
}
|
||||
|
||||
abstract getData(): IDisposable;
|
||||
|
||||
private configure() {
|
||||
this.addDisposables(
|
||||
addDisposableListener(this.el, 'dragstart', (event) => {
|
||||
this.iframes = [
|
||||
...getElementsByTagName('iframe'),
|
||||
...getElementsByTagName('webview'),
|
||||
];
|
||||
|
||||
for (const iframe of this.iframes) {
|
||||
iframe.style.pointerEvents = 'none';
|
||||
}
|
||||
|
||||
this.el.classList.add('dragged');
|
||||
setTimeout(() => this.el.classList.remove('dragged'), 0);
|
||||
|
||||
// this.activeDrag = this.getData();
|
||||
this.disposable?.dispose();
|
||||
this.disposable = this.getData();
|
||||
|
||||
// if (event.dataTransfer) {
|
||||
// event.dataTransfer.setData(DATA_KEY, stringifiedData);
|
||||
// event.dataTransfer.effectAllowed = 'move';
|
||||
// }
|
||||
}),
|
||||
addDisposableListener(this.el, 'dragend', (ev) => {
|
||||
for (const iframe of this.iframes) {
|
||||
iframe.style.pointerEvents = 'auto';
|
||||
}
|
||||
this.iframes = [];
|
||||
|
||||
this.disposable?.dispose();
|
||||
this.disposable = undefined;
|
||||
|
||||
// drop events fire before dragend so we can remove this safely
|
||||
// LocalSelectionTransfer.getInstance().clearData(this.activeDrag);
|
||||
// this.activeDrag = undefined;
|
||||
}),
|
||||
addDisposableListener(this.el, 'mousedown', (event) => {
|
||||
if (event.defaultPrevented) {
|
||||
return;
|
||||
}
|
||||
/**
|
||||
* TODO: alternative to stopPropagation
|
||||
*
|
||||
* I need to stop the event propagation here since otherwise it'll be intercepted by event handlers
|
||||
* on the tabs-container. I cannot use event.preventDefault() since I need the on DragStart event to occur
|
||||
*/
|
||||
event.stopPropagation();
|
||||
|
||||
/**
|
||||
* //TODO mousedown focusing with draggable element (is there a better approach?)
|
||||
*
|
||||
* this mousedown event wants to focus the tab itself but if we call preventDefault()
|
||||
* this would also prevent the dragStart event from firing. To get around this we propagate
|
||||
* the onChanged event during the next tick of the event-loop, allowing the tab element to become
|
||||
* focused on this tick and ensuring the dragstart event is not interrupted
|
||||
*/
|
||||
|
||||
const oldFocus = focusedElement.element as HTMLElement;
|
||||
setTimeout(() => {
|
||||
oldFocus.focus();
|
||||
// this._onChanged.fire({ kind: MouseEventKind.CLICK, event });
|
||||
}, 0);
|
||||
}),
|
||||
addDisposableListener(this.el, 'contextmenu', (event) => {
|
||||
// this._onChanged.fire({
|
||||
// kind: MouseEventKind.CONTEXT_MENU,
|
||||
// event,
|
||||
// });
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
import { PanelOptions } from '../dockview/options';
|
||||
import { tryParseJSON } from '../json';
|
||||
import { PanelTransfer, PaneTransfer } from './droptarget';
|
||||
|
||||
export const DATA_KEY = 'splitview/transfer';
|
||||
|
||||
@ -12,7 +13,7 @@ export const isPanelTransferEvent = (event: DragEvent) => {
|
||||
};
|
||||
|
||||
export enum DragType {
|
||||
ITEM = 'group_drag',
|
||||
DOCKVIEW_TAB = 'dockview_tab',
|
||||
EXTERNAL = 'external_group_drag',
|
||||
}
|
||||
|
||||
@ -30,7 +31,7 @@ export type DataObject = DragItem | ExternalDragItem;
|
||||
* dragging a tab component
|
||||
*/
|
||||
export const isTabDragEvent = (data: any): data is DragItem => {
|
||||
return data.type === DragType.ITEM;
|
||||
return data.type === DragType.DOCKVIEW_TAB;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -102,3 +103,25 @@ export class LocalSelectionTransfer<T> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function getPanelData(): PanelTransfer | undefined {
|
||||
const panelTransfer = LocalSelectionTransfer.getInstance<PanelTransfer>();
|
||||
const isPanelEvent = panelTransfer.hasData(PanelTransfer.prototype);
|
||||
|
||||
if (!isPanelEvent) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return panelTransfer.getData(PanelTransfer.prototype)![0];
|
||||
}
|
||||
|
||||
export function getPaneData(): PaneTransfer | undefined {
|
||||
const paneTransfer = LocalSelectionTransfer.getInstance<PaneTransfer>();
|
||||
const isPanelEvent = paneTransfer.hasData(PaneTransfer.prototype);
|
||||
|
||||
if (!isPanelEvent) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return paneTransfer.getData(PaneTransfer.prototype)![0];
|
||||
}
|
||||
|
181
packages/dockview/src/dnd/dnd.ts
Normal file
181
packages/dockview/src/dnd/dnd.ts
Normal file
@ -0,0 +1,181 @@
|
||||
import { addDisposableListener, Emitter } from '../events';
|
||||
import { CompositeDisposable, IDisposable } from '../lifecycle';
|
||||
import { LocalSelectionTransfer } from './dataTransfer';
|
||||
|
||||
export interface IDragAndDropObserverCallbacks {
|
||||
onDragEnter: (e: DragEvent) => void;
|
||||
onDragLeave: (e: DragEvent) => void;
|
||||
onDrop: (e: DragEvent) => void;
|
||||
onDragEnd: (e: DragEvent) => void;
|
||||
|
||||
onDragOver?: (e: DragEvent) => void;
|
||||
}
|
||||
|
||||
export class DragAndDropObserver extends CompositeDisposable {
|
||||
// A helper to fix issues with repeated DRAG_ENTER / DRAG_LEAVE
|
||||
// calls see https://github.com/microsoft/vscode/issues/14470
|
||||
// when the element has child elements where the events are fired
|
||||
// repeadedly.
|
||||
private counter = 0;
|
||||
|
||||
constructor(
|
||||
private element: HTMLElement,
|
||||
private callbacks: IDragAndDropObserverCallbacks
|
||||
) {
|
||||
super();
|
||||
|
||||
this.registerListeners();
|
||||
}
|
||||
|
||||
private registerListeners(): void {
|
||||
this.addDisposables(
|
||||
addDisposableListener(this.element, 'dragenter', (e: DragEvent) => {
|
||||
this.counter++;
|
||||
|
||||
this.callbacks.onDragEnter(e);
|
||||
})
|
||||
);
|
||||
|
||||
this.addDisposables(
|
||||
addDisposableListener(this.element, 'dragover', (e: DragEvent) => {
|
||||
e.preventDefault(); // needed so that the drop event fires (https://stackoverflow.com/questions/21339924/drop-event-not-firing-in-chrome)
|
||||
|
||||
if (this.callbacks.onDragOver) {
|
||||
this.callbacks.onDragOver(e);
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
this.addDisposables(
|
||||
addDisposableListener(this.element, 'dragleave', (e: DragEvent) => {
|
||||
this.counter--;
|
||||
|
||||
if (this.counter === 0) {
|
||||
this.callbacks.onDragLeave(e);
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
this.addDisposables(
|
||||
addDisposableListener(this.element, 'dragend', (e: DragEvent) => {
|
||||
this.counter = 0;
|
||||
this.callbacks.onDragEnd(e);
|
||||
})
|
||||
);
|
||||
|
||||
this.addDisposables(
|
||||
addDisposableListener(this.element, 'drop', (e: DragEvent) => {
|
||||
this.counter = 0;
|
||||
this.callbacks.onDrop(e);
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export interface IDraggedCompositeData {
|
||||
eventData: DragEvent;
|
||||
dragAndDropData: any;
|
||||
}
|
||||
|
||||
export interface ICompositeDragAndDropObserverCallbacks {
|
||||
onDragEnter?: (e: IDraggedCompositeData) => void;
|
||||
onDragLeave?: (e: IDraggedCompositeData) => void;
|
||||
onDrop?: (e: IDraggedCompositeData) => void;
|
||||
onDragOver?: (e: IDraggedCompositeData) => void;
|
||||
onDragStart?: (e: IDraggedCompositeData) => void;
|
||||
onDragEnd?: (e: IDraggedCompositeData) => void;
|
||||
}
|
||||
|
||||
class DockviewIdentifier<T = {}> {
|
||||
constructor(private readonly data: T) {
|
||||
//
|
||||
}
|
||||
}
|
||||
|
||||
export class DragAndDrop extends CompositeDisposable {
|
||||
private _onDragStart = new Emitter<any>();
|
||||
private _onDragEnd = new Emitter<any>();
|
||||
private static _instance: DragAndDrop | undefined;
|
||||
static get INSTANCE(): DragAndDrop {
|
||||
if (!DragAndDrop._instance) {
|
||||
DragAndDrop._instance = new DragAndDrop();
|
||||
}
|
||||
return DragAndDrop._instance;
|
||||
}
|
||||
|
||||
private transferData =
|
||||
LocalSelectionTransfer.getInstance<DockviewIdentifier>();
|
||||
|
||||
private constructor() {
|
||||
super();
|
||||
|
||||
this.addDisposables(this._onDragStart, this._onDragEnd);
|
||||
}
|
||||
|
||||
registerTarget(
|
||||
element: HTMLElement,
|
||||
callbacks: ICompositeDragAndDropObserverCallbacks
|
||||
): IDisposable {
|
||||
const disposables = new CompositeDisposable();
|
||||
|
||||
disposables.addDisposables(
|
||||
new DragAndDropObserver(element, {
|
||||
onDragEnd: (e) => {
|
||||
// no-op
|
||||
},
|
||||
onDragEnter: (e) => {
|
||||
e.preventDefault();
|
||||
},
|
||||
onDragLeave: (e) => {
|
||||
//
|
||||
},
|
||||
onDrop: (e) => {
|
||||
//
|
||||
},
|
||||
onDragOver: (e) => {
|
||||
//
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
return disposables;
|
||||
}
|
||||
|
||||
registerDraggable(
|
||||
element: HTMLElement,
|
||||
draggedItemProvider: () => { type: string; id: string },
|
||||
callbacks: ICompositeDragAndDropObserverCallbacks
|
||||
): IDisposable {
|
||||
element.draggable = true;
|
||||
|
||||
const disposables = new CompositeDisposable();
|
||||
|
||||
disposables.addDisposables(
|
||||
addDisposableListener(element, 'dragstart', (e) => {
|
||||
this._onDragStart.fire({ event: e });
|
||||
})
|
||||
);
|
||||
|
||||
disposables.addDisposables(
|
||||
new DragAndDropObserver(element, {
|
||||
onDragEnd: (e) => {
|
||||
// no-op
|
||||
},
|
||||
onDragEnter: (e) => {
|
||||
//
|
||||
},
|
||||
onDragLeave: (e) => {
|
||||
//
|
||||
},
|
||||
onDrop: (e) => {
|
||||
//
|
||||
},
|
||||
onDragOver: (e) => {
|
||||
//
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
return disposables;
|
||||
}
|
||||
}
|
@ -12,6 +12,7 @@
|
||||
> .drop-target-selection {
|
||||
position: relative;
|
||||
pointer-events: none;
|
||||
box-sizing: border-box;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background-color: var(--dv-drag-over-background-color);
|
||||
@ -33,6 +34,19 @@
|
||||
&.bottom {
|
||||
height: 50%;
|
||||
}
|
||||
|
||||
&.small-top {
|
||||
border-top: 1px solid var(--dv-drag-over-border-color);
|
||||
}
|
||||
&.small-bottom {
|
||||
border-bottom: 1px solid var(--dv-drag-over-border-color);
|
||||
}
|
||||
&.small-left {
|
||||
border-left: 1px solid var(--dv-drag-over-border-color);
|
||||
}
|
||||
&.small-right {
|
||||
border-right: 1px solid var(--dv-drag-over-border-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,37 @@
|
||||
import { toggleClass } from '../dom';
|
||||
import { Emitter, Event } from '../events';
|
||||
import { LocalSelectionTransfer } from './dataTransfer';
|
||||
import { CompositeDisposable } from '../lifecycle';
|
||||
import { DragAndDropObserver } from './dnd';
|
||||
|
||||
export interface DroptargetEvent {
|
||||
position: Position;
|
||||
event: DragEvent;
|
||||
}
|
||||
|
||||
class TransferObject {
|
||||
constructor() {
|
||||
//
|
||||
}
|
||||
}
|
||||
|
||||
export class PanelTransfer extends TransferObject {
|
||||
constructor(
|
||||
public readonly viewId: string,
|
||||
public readonly groupId: string,
|
||||
public readonly panelId: string
|
||||
) {
|
||||
super();
|
||||
}
|
||||
}
|
||||
|
||||
export class PaneTransfer extends TransferObject {
|
||||
constructor(
|
||||
public readonly viewId: string,
|
||||
public readonly paneId: string
|
||||
) {
|
||||
super();
|
||||
}
|
||||
}
|
||||
|
||||
export enum Position {
|
||||
Top = 'Top',
|
||||
@ -15,146 +46,176 @@ export interface DroptargetEvent {
|
||||
event: DragEvent;
|
||||
}
|
||||
|
||||
export class Droptarget {
|
||||
export type DropTargetDirections = 'vertical' | 'horizontal' | 'all' | 'none';
|
||||
|
||||
function isBooleanValue(
|
||||
canDisplayOverlay: CanDisplayOverlay
|
||||
): canDisplayOverlay is boolean {
|
||||
return typeof canDisplayOverlay === 'boolean';
|
||||
}
|
||||
|
||||
export type CanDisplayOverlay = boolean | ((dragEvent: DragEvent) => boolean);
|
||||
|
||||
export class Droptarget extends CompositeDisposable {
|
||||
private target: HTMLElement | undefined;
|
||||
private overlay: HTMLElement | undefined;
|
||||
private _state: Position | undefined;
|
||||
|
||||
private readonly _onDidChange = new Emitter<DroptargetEvent>();
|
||||
readonly onDidChange: Event<DroptargetEvent> = this._onDidChange.event;
|
||||
private readonly _onDrop = new Emitter<DroptargetEvent>();
|
||||
readonly onDrop: Event<DroptargetEvent> = this._onDrop.event;
|
||||
|
||||
get state() {
|
||||
return this._state;
|
||||
}
|
||||
|
||||
set validOverlays(value: DropTargetDirections) {
|
||||
this.options.validOverlays = value;
|
||||
}
|
||||
|
||||
set canDisplayOverlay(value: CanDisplayOverlay) {
|
||||
this.options.canDisplayOverlay = value;
|
||||
}
|
||||
|
||||
constructor(
|
||||
private element: HTMLElement,
|
||||
private options: {
|
||||
isDisabled: () => boolean;
|
||||
isDirectional: boolean;
|
||||
id: string;
|
||||
enableExternalDragEvents?: boolean;
|
||||
private readonly element: HTMLElement,
|
||||
private readonly options: {
|
||||
canDisplayOverlay: CanDisplayOverlay;
|
||||
validOverlays: DropTargetDirections;
|
||||
}
|
||||
) {
|
||||
this.element.addEventListener('dragenter', this.onDragEnter);
|
||||
super();
|
||||
|
||||
this.addDisposables(
|
||||
new DragAndDropObserver(this.element, {
|
||||
onDragEnter: (e) => undefined,
|
||||
onDragOver: (e) => {
|
||||
if (isBooleanValue(this.options.canDisplayOverlay)) {
|
||||
if (!this.options.canDisplayOverlay) {
|
||||
return;
|
||||
}
|
||||
} else if (!this.options.canDisplayOverlay(e)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.target) {
|
||||
console.debug('[droptarget] created');
|
||||
this.target = document.createElement('div');
|
||||
this.target.className = 'drop-target-dropzone';
|
||||
this.overlay = document.createElement('div');
|
||||
this.overlay.className = 'drop-target-selection';
|
||||
this._state = Position.Center;
|
||||
this.target.appendChild(this.overlay);
|
||||
|
||||
this.element.classList.add('drop-target');
|
||||
this.element.append(this.target);
|
||||
}
|
||||
|
||||
if (this.options.validOverlays === 'none') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.target || !this.overlay) {
|
||||
return;
|
||||
}
|
||||
|
||||
const width = this.target.clientWidth;
|
||||
const height = this.target.clientHeight;
|
||||
|
||||
if (width === 0 || height === 0) {
|
||||
return; // avoid div!0
|
||||
}
|
||||
|
||||
const x = e.offsetX;
|
||||
const y = e.offsetY;
|
||||
const xp = (100 * x) / width;
|
||||
const yp = (100 * y) / height;
|
||||
|
||||
let isRight = false;
|
||||
let isLeft = false;
|
||||
let isTop = false;
|
||||
let isBottom = false;
|
||||
|
||||
switch (this.options.validOverlays) {
|
||||
case 'all':
|
||||
isRight = xp > 80;
|
||||
isLeft = xp < 20;
|
||||
isTop = !isRight && !isLeft && yp < 20;
|
||||
isBottom = !isRight && !isLeft && yp > 80;
|
||||
break;
|
||||
case 'vertical':
|
||||
isTop = yp < 50;
|
||||
isBottom = yp >= 50;
|
||||
break;
|
||||
case 'horizontal':
|
||||
isLeft = xp < 50;
|
||||
isRight = xp >= 50;
|
||||
break;
|
||||
}
|
||||
|
||||
const isSmallX = width < 100;
|
||||
const isSmallY = height < 100;
|
||||
|
||||
toggleClass(this.overlay, 'right', !isSmallX && isRight);
|
||||
toggleClass(this.overlay, 'left', !isSmallX && isLeft);
|
||||
toggleClass(this.overlay, 'top', !isSmallY && isTop);
|
||||
toggleClass(this.overlay, 'bottom', !isSmallY && isBottom);
|
||||
|
||||
toggleClass(
|
||||
this.overlay,
|
||||
'small-right',
|
||||
isSmallX && isRight
|
||||
);
|
||||
toggleClass(this.overlay, 'small-left', isSmallX && isLeft);
|
||||
toggleClass(this.overlay, 'small-top', isSmallY && isTop);
|
||||
toggleClass(
|
||||
this.overlay,
|
||||
'small-bottom',
|
||||
isSmallY && isBottom
|
||||
);
|
||||
|
||||
if (isRight) {
|
||||
this._state = Position.Right;
|
||||
} else if (isLeft) {
|
||||
this._state = Position.Left;
|
||||
} else if (isTop) {
|
||||
this._state = Position.Top;
|
||||
} else if (isBottom) {
|
||||
this._state = Position.Bottom;
|
||||
} else {
|
||||
this._state = Position.Center;
|
||||
}
|
||||
},
|
||||
onDragLeave: (e) => {
|
||||
this.removeDropTarget();
|
||||
},
|
||||
onDragEnd: (e) => {
|
||||
this.removeDropTarget();
|
||||
},
|
||||
onDrop: (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
const state = this._state;
|
||||
|
||||
console.debug('[dragtarget] drop');
|
||||
this.removeDropTarget();
|
||||
|
||||
if (state) {
|
||||
this._onDrop.fire({ position: state, event: e });
|
||||
}
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
this._onDidChange.dispose();
|
||||
this._onDrop.dispose();
|
||||
this.removeDropTarget();
|
||||
this.element.removeEventListener('dragenter', this.onDragEnter);
|
||||
}
|
||||
|
||||
private onDragEnter = (event: DragEvent) => {
|
||||
if (
|
||||
!this.options.enableExternalDragEvents &&
|
||||
!LocalSelectionTransfer.getInstance().hasData(this.options.id)
|
||||
) {
|
||||
console.debug('[droptarget] invalid event');
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.options.isDisabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
if (!this.target) {
|
||||
console.debug('[droptarget] created');
|
||||
this.target = document.createElement('div');
|
||||
this.target.className = 'drop-target-dropzone';
|
||||
this.overlay = document.createElement('div');
|
||||
this.overlay.className = 'drop-target-selection';
|
||||
//
|
||||
this._state = Position.Center;
|
||||
this.target.addEventListener('dragover', this.onDragOver);
|
||||
this.target.addEventListener('dragleave', this.onDragLeave);
|
||||
this.target.addEventListener('drop', this.onDrop);
|
||||
this.target.appendChild(this.overlay);
|
||||
|
||||
this.element.classList.add('drop-target');
|
||||
this.element.append(this.target);
|
||||
}
|
||||
};
|
||||
|
||||
private onDrop = (event: DragEvent) => {
|
||||
if (
|
||||
!this.options.enableExternalDragEvents &&
|
||||
!LocalSelectionTransfer.getInstance().hasData(this.options.id)
|
||||
) {
|
||||
console.debug('[dragtarget] invalid');
|
||||
return;
|
||||
}
|
||||
|
||||
const state = this._state;
|
||||
|
||||
console.debug('[dragtarget] drop');
|
||||
this.removeDropTarget();
|
||||
|
||||
if (event.defaultPrevented) {
|
||||
console.debug('[dragtarget] defaultPrevented');
|
||||
} else if (state) {
|
||||
this._onDidChange.fire({ position: state, event });
|
||||
}
|
||||
};
|
||||
|
||||
private onDragOver = (event: DragEvent) => {
|
||||
event.preventDefault();
|
||||
|
||||
if (!this.options.isDirectional) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.target || !this.overlay) {
|
||||
return;
|
||||
}
|
||||
|
||||
const width = this.target.clientWidth;
|
||||
const height = this.target.clientHeight;
|
||||
|
||||
if (width === 0 || height === 0) {
|
||||
return; // avoid div!0
|
||||
}
|
||||
|
||||
const x = event.offsetX;
|
||||
const y = event.offsetY;
|
||||
const xp = (100 * x) / width;
|
||||
const yp = (100 * y) / height;
|
||||
|
||||
const isRight = xp > 80;
|
||||
const isLeft = xp < 20;
|
||||
const isTop = !isRight && !isLeft && yp < 20;
|
||||
const isBottom = !isRight && !isLeft && yp > 80;
|
||||
|
||||
toggleClass(this.overlay, 'right', isRight);
|
||||
toggleClass(this.overlay, 'left', isLeft);
|
||||
toggleClass(this.overlay, 'top', isTop);
|
||||
toggleClass(this.overlay, 'bottom', isBottom);
|
||||
|
||||
if (isRight) {
|
||||
this._state = Position.Right;
|
||||
} else if (isLeft) {
|
||||
this._state = Position.Left;
|
||||
} else if (isTop) {
|
||||
this._state = Position.Top;
|
||||
} else if (isBottom) {
|
||||
this._state = Position.Bottom;
|
||||
} else {
|
||||
this._state = Position.Center;
|
||||
}
|
||||
};
|
||||
|
||||
private onDragLeave = (event: DragEvent) => {
|
||||
console.debug('[droptarget] leave');
|
||||
this.removeDropTarget();
|
||||
};
|
||||
|
||||
private removeDropTarget() {
|
||||
if (this.target) {
|
||||
this._state = undefined;
|
||||
this.target.removeEventListener('dragover', this.onDragOver);
|
||||
this.target.removeEventListener('dragleave', this.onDragLeave);
|
||||
this.target.removeEventListener('drop', this.onDrop);
|
||||
this.element.removeChild(this.target);
|
||||
this.target = undefined;
|
||||
this.element.classList.remove('drop-target');
|
||||
|
@ -1,13 +1,21 @@
|
||||
.dragged {
|
||||
transform: translate3d(
|
||||
0px,
|
||||
0px,
|
||||
0px
|
||||
); /* forces tab to be drawn on a separate layer (see https://github.com/microsoft/vscode/issues/18733) */
|
||||
}
|
||||
|
||||
.tab {
|
||||
flex-shrink: 0;
|
||||
|
||||
&.dragged {
|
||||
transform: translate3d(
|
||||
0px,
|
||||
0px,
|
||||
0px
|
||||
); /* forces tab to be drawn on a separate layer (see https://github.com/microsoft/vscode/issues/18733) */
|
||||
}
|
||||
// &.dragged {
|
||||
// transform: translate3d(
|
||||
// 0px,
|
||||
// 0px,
|
||||
// 0px
|
||||
// ); /* forces tab to be drawn on a separate layer (see https://github.com/microsoft/vscode/issues/18733) */
|
||||
// }
|
||||
|
||||
&.dragging {
|
||||
.tab-action {
|
||||
|
@ -7,13 +7,8 @@ import { Position } from '../dnd/droptarget';
|
||||
import { tail, sequenceEquals } from '../array';
|
||||
import { GroupviewPanelState, IGroupPanel } from '../groupview/groupPanel';
|
||||
import { DockviewGroupPanel } from './dockviewGroupPanel';
|
||||
import {
|
||||
CompositeDisposable,
|
||||
IDisposable,
|
||||
IValueDisposable,
|
||||
MutableDisposable,
|
||||
} from '../lifecycle';
|
||||
import { Event, Emitter, addDisposableListener } from '../events';
|
||||
import { CompositeDisposable, IValueDisposable } from '../lifecycle';
|
||||
import { Event, Emitter } from '../events';
|
||||
import { Watermark } from './components/watermark/watermark';
|
||||
import { timeoutAsPromise } from '../async';
|
||||
import {
|
||||
@ -28,16 +23,10 @@ import { createComponent } from '../panel/componentFactory';
|
||||
import {
|
||||
AddGroupOptions,
|
||||
AddPanelOptions,
|
||||
PanelOptions,
|
||||
DockviewOptions as DockviewComponentOptions,
|
||||
MovementOptions,
|
||||
TabContextMenuEvent,
|
||||
} from './options';
|
||||
import {
|
||||
DATA_KEY,
|
||||
DragType,
|
||||
LocalSelectionTransfer,
|
||||
} from '../dnd/dataTransfer';
|
||||
import {
|
||||
BaseGrid,
|
||||
IBaseGrid,
|
||||
@ -50,7 +39,6 @@ import { Orientation } from '../splitview/core/splitview';
|
||||
import { DefaultTab } from './components/tab/defaultTab';
|
||||
import {
|
||||
GroupChangeKind,
|
||||
GroupDropEvent,
|
||||
GroupOptions,
|
||||
GroupPanelViewState,
|
||||
} from '../groupview/groupview';
|
||||
@ -113,17 +101,17 @@ export interface IDockviewComponent extends IBaseGrid<GroupviewPanel> {
|
||||
onTabContextMenu: Event<TabContextMenuEvent>;
|
||||
moveToNext(options?: MovementOptions): void;
|
||||
moveToPrevious(options?: MovementOptions): void;
|
||||
createDragTarget(
|
||||
target: {
|
||||
element: HTMLElement;
|
||||
content: string;
|
||||
},
|
||||
options: (() => PanelOptions) | PanelOptions
|
||||
): IDisposable;
|
||||
addDndHandle(
|
||||
type: string,
|
||||
cb: (event: LayoutDropEvent) => PanelOptions
|
||||
): void;
|
||||
// createDragTarget(
|
||||
// target: {
|
||||
// element: HTMLElement;
|
||||
// content: string;
|
||||
// },
|
||||
// options: (() => PanelOptions) | PanelOptions
|
||||
// ): IDisposable;
|
||||
// addDndHandle(
|
||||
// type: string,
|
||||
// cb: (event: LayoutDropEvent) => PanelOptions
|
||||
// ): void;
|
||||
setActivePanel(panel: IGroupPanel): void;
|
||||
focus(): void;
|
||||
toJSON(): SerializedDockview;
|
||||
@ -131,10 +119,6 @@ export interface IDockviewComponent extends IBaseGrid<GroupviewPanel> {
|
||||
onDidLayoutChange: Event<void>;
|
||||
}
|
||||
|
||||
export interface LayoutDropEvent {
|
||||
event: GroupDropEvent;
|
||||
}
|
||||
|
||||
export class DockviewComponent
|
||||
extends BaseGrid<GroupviewPanel>
|
||||
implements IDockviewComponent
|
||||
@ -153,13 +137,13 @@ export class DockviewComponent
|
||||
readonly onTabContextMenu: Event<TabContextMenuEvent> =
|
||||
this._onTabContextMenu.event;
|
||||
// everything else
|
||||
private drag = new MutableDisposable();
|
||||
// private drag = new MutableDisposable();
|
||||
private _deserializer: IPanelDeserializer | undefined;
|
||||
private panelState: State = {};
|
||||
private registry = new Map<
|
||||
string,
|
||||
(event: LayoutDropEvent) => PanelOptions
|
||||
>();
|
||||
// private registry = new Map<
|
||||
// string,
|
||||
// (event: LayoutDropEvent) => PanelOptions
|
||||
// >();
|
||||
private _api: DockviewApi;
|
||||
private _options: DockviewComponentOptions;
|
||||
|
||||
@ -274,12 +258,12 @@ export class DockviewComponent
|
||||
this.layout(this.gridview.width, this.gridview.height, true);
|
||||
}
|
||||
|
||||
addDndHandle(
|
||||
type: string,
|
||||
cb: (event: LayoutDropEvent) => PanelOptions
|
||||
): void {
|
||||
this.registry.set(type, cb);
|
||||
}
|
||||
// addDndHandle(
|
||||
// type: string,
|
||||
// cb: (event: LayoutDropEvent) => PanelOptions
|
||||
// ): void {
|
||||
// this.registry.set(type, cb);
|
||||
// }
|
||||
|
||||
focus(): void {
|
||||
this.activeGroup?.focus();
|
||||
@ -289,57 +273,57 @@ export class DockviewComponent
|
||||
return this.panels.get(id)?.value;
|
||||
}
|
||||
|
||||
createDragTarget(
|
||||
target: {
|
||||
element: HTMLElement;
|
||||
content: string;
|
||||
},
|
||||
options: (() => PanelOptions) | PanelOptions
|
||||
): IDisposable {
|
||||
return new CompositeDisposable(
|
||||
addDisposableListener(target.element, 'dragstart', (event) => {
|
||||
if (!event.dataTransfer) {
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
// createDragTarget(
|
||||
// target: {
|
||||
// element: HTMLElement;
|
||||
// content: string;
|
||||
// },
|
||||
// options: (() => PanelOptions) | PanelOptions
|
||||
// ): IDisposable {
|
||||
// return new CompositeDisposable(
|
||||
// addDisposableListener(target.element, 'dragstart', (event) => {
|
||||
// if (!event.dataTransfer) {
|
||||
// throw new Error('unsupported');
|
||||
// }
|
||||
|
||||
const panelOptions =
|
||||
typeof options === 'function' ? options() : options;
|
||||
// const panelOptions =
|
||||
// typeof options === 'function' ? options() : options;
|
||||
|
||||
const panel = this.panels.get(panelOptions.id)?.value;
|
||||
if (panel) {
|
||||
this.drag.value = panel.group!.model.startActiveDrag(panel);
|
||||
}
|
||||
// const panel = this.panels.get(panelOptions.id)?.value;
|
||||
// if (panel) {
|
||||
// this.drag.value = panel.group!.model.startActiveDrag(panel);
|
||||
// }
|
||||
|
||||
const data = JSON.stringify({
|
||||
type: DragType.EXTERNAL,
|
||||
...panelOptions,
|
||||
});
|
||||
// const data = JSON.stringify({
|
||||
// type: DragType.EXTERNAL,
|
||||
// ...panelOptions,
|
||||
// });
|
||||
|
||||
LocalSelectionTransfer.getInstance().setData([data], this.id);
|
||||
// LocalSelectionTransfer.getInstance().setData([data], this.id);
|
||||
|
||||
event.dataTransfer.effectAllowed = 'move';
|
||||
// event.dataTransfer.effectAllowed = 'move';
|
||||
|
||||
const dragImage = document.createElement('div');
|
||||
dragImage.textContent = target.content;
|
||||
dragImage.classList.add('custom-dragging');
|
||||
// const dragImage = document.createElement('div');
|
||||
// dragImage.textContent = target.content;
|
||||
// dragImage.classList.add('custom-dragging');
|
||||
|
||||
document.body.appendChild(dragImage);
|
||||
event.dataTransfer.setDragImage(
|
||||
dragImage,
|
||||
event.offsetX,
|
||||
event.offsetY
|
||||
);
|
||||
setTimeout(() => document.body.removeChild(dragImage), 0);
|
||||
// document.body.appendChild(dragImage);
|
||||
// event.dataTransfer.setDragImage(
|
||||
// dragImage,
|
||||
// event.offsetX,
|
||||
// event.offsetY
|
||||
// );
|
||||
// setTimeout(() => document.body.removeChild(dragImage), 0);
|
||||
|
||||
event.dataTransfer.setData(DATA_KEY, data);
|
||||
}),
|
||||
addDisposableListener(this.element, 'dragend', (ev) => {
|
||||
// drop events fire before dragend so we can remove this safely
|
||||
LocalSelectionTransfer.getInstance().clearData(this.id);
|
||||
this.drag.dispose();
|
||||
})
|
||||
);
|
||||
}
|
||||
// event.dataTransfer.setData(DATA_KEY, data);
|
||||
// }),
|
||||
// addDisposableListener(this.element, 'dragend', (ev) => {
|
||||
// // drop events fire before dragend so we can remove this safely
|
||||
// LocalSelectionTransfer.getInstance().clearData(this.id);
|
||||
// this.drag.dispose();
|
||||
// })
|
||||
// );
|
||||
// }
|
||||
|
||||
setActivePanel(panel: IGroupPanel): void {
|
||||
if (!panel.group) {
|
||||
@ -759,47 +743,6 @@ export class DockviewComponent
|
||||
}),
|
||||
view.model.onDidGroupChange((event) => {
|
||||
this._onGridEvent.fire(event);
|
||||
}),
|
||||
view.model.onDrop((event) => {
|
||||
const dragEvent = event.event;
|
||||
const dataTransfer = dragEvent.dataTransfer;
|
||||
|
||||
if (!dataTransfer) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (dataTransfer.types.length === 0) {
|
||||
return;
|
||||
}
|
||||
const cb = this.registry.get(dataTransfer.types[0]);
|
||||
|
||||
if (!cb) {
|
||||
return;
|
||||
}
|
||||
|
||||
const panelOptions = cb({ event });
|
||||
|
||||
let panel = this.getGroupPanel(panelOptions.id);
|
||||
|
||||
if (!panel) {
|
||||
panel = this._addPanel(panelOptions);
|
||||
}
|
||||
|
||||
const groupId = panel.group?.id;
|
||||
|
||||
if (!groupId) {
|
||||
throw new Error(
|
||||
`Panel ${panel.id} has no associated group`
|
||||
);
|
||||
}
|
||||
|
||||
this.moveGroupOrPanel(
|
||||
view,
|
||||
groupId,
|
||||
panel.id,
|
||||
event.target,
|
||||
event.index
|
||||
);
|
||||
})
|
||||
);
|
||||
|
||||
|
@ -555,9 +555,8 @@ export class Gridview implements IDisposable {
|
||||
|
||||
let newSiblingSize: number | Sizing = 0;
|
||||
|
||||
const newSiblingCachedVisibleSize = grandParent.getChildCachedVisibleSize(
|
||||
parentIndex
|
||||
);
|
||||
const newSiblingCachedVisibleSize =
|
||||
grandParent.getChildCachedVisibleSize(parentIndex);
|
||||
if (typeof newSiblingCachedVisibleSize === 'number') {
|
||||
newSiblingSize = Sizing.Invisible(newSiblingCachedVisibleSize);
|
||||
}
|
||||
|
@ -1,12 +1,8 @@
|
||||
import { DockviewApi } from '../api/component.api';
|
||||
import { timeoutAsPromise } from '../async';
|
||||
import {
|
||||
extractData,
|
||||
isCustomDragEvent,
|
||||
isPanelTransferEvent,
|
||||
isTabDragEvent,
|
||||
} from '../dnd/dataTransfer';
|
||||
import { Droptarget, DroptargetEvent, Position } from '../dnd/droptarget';
|
||||
import { getPanelData } from '../dnd/dataTransfer';
|
||||
import { Position } from '../dnd/droptarget';
|
||||
import { Droptarget } from '../dnd/droptarget';
|
||||
import {
|
||||
DockviewComponent,
|
||||
IDockviewComponent,
|
||||
@ -15,7 +11,7 @@ import { isAncestor, toggleClass } from '../dom';
|
||||
import { addDisposableListener, Emitter, Event } from '../events';
|
||||
import { IGridPanelView } from '../gridview/baseComponentGridview';
|
||||
import { IViewSize } from '../gridview/gridview';
|
||||
import { CompositeDisposable, Disposable, IDisposable } from '../lifecycle';
|
||||
import { CompositeDisposable, IDisposable } from '../lifecycle';
|
||||
import { PanelInitParameters, PanelUpdateEvent } from '../panel/types';
|
||||
import { IGroupPanel } from './groupPanel';
|
||||
import { ContentContainer, IContentContainer } from './panel/content';
|
||||
@ -46,6 +42,20 @@ export enum GroupChangeKind {
|
||||
LAYOUT_CONFIG_UPDATED = 'LAYOUT_CONFIG_UPDATED',
|
||||
}
|
||||
|
||||
export interface DndService {
|
||||
canDisplayOverlay(
|
||||
group: IGroupview,
|
||||
event: DragEvent,
|
||||
target: DockviewDropTargets
|
||||
): boolean;
|
||||
onDrop(
|
||||
group: IGroupview,
|
||||
event: DragEvent,
|
||||
position: Position,
|
||||
index?: number
|
||||
): void;
|
||||
}
|
||||
|
||||
export interface IGroupItem {
|
||||
id: string;
|
||||
header: { element: HTMLElement };
|
||||
@ -77,6 +87,12 @@ export interface GroupPanelViewState {
|
||||
id: string;
|
||||
}
|
||||
|
||||
export enum DockviewDropTargets {
|
||||
Tab,
|
||||
Panel,
|
||||
TabContainer,
|
||||
}
|
||||
|
||||
export interface IGroupview extends IDisposable, IGridPanelView {
|
||||
readonly isActive: boolean;
|
||||
readonly size: number;
|
||||
@ -99,7 +115,7 @@ export interface IGroupview extends IDisposable, IGridPanelView {
|
||||
onDidGroupChange: Event<{ kind: GroupChangeKind }>;
|
||||
onMove: Event<GroupMoveEvent>;
|
||||
//
|
||||
startActiveDrag(panel: IGroupPanel): IDisposable;
|
||||
// startActiveDrag(panel: IGroupPanel): IDisposable;
|
||||
//
|
||||
moveToNext(options?: { panel?: IGroupPanel; suppressRoll?: boolean }): void;
|
||||
moveToPrevious(options?: {
|
||||
@ -108,12 +124,7 @@ export interface IGroupview extends IDisposable, IGridPanelView {
|
||||
}): void;
|
||||
isContentFocused(): boolean;
|
||||
updateActions(): void;
|
||||
}
|
||||
|
||||
export interface GroupDropEvent {
|
||||
event: DragEvent;
|
||||
target: Position;
|
||||
index?: number;
|
||||
canDisplayOverlay(event: DragEvent, target: DockviewDropTargets): boolean;
|
||||
}
|
||||
|
||||
export class Groupview extends CompositeDisposable implements IGroupview {
|
||||
@ -138,9 +149,6 @@ export class Groupview extends CompositeDisposable implements IGroupview {
|
||||
private readonly _onMove = new Emitter<GroupMoveEvent>();
|
||||
readonly onMove: Event<GroupMoveEvent> = this._onMove.event;
|
||||
|
||||
private readonly _onDrop = new Emitter<GroupDropEvent>();
|
||||
readonly onDrop: Event<GroupDropEvent> = this._onDrop.event;
|
||||
|
||||
private readonly _onDidGroupChange = new Emitter<GroupChangeEvent>();
|
||||
readonly onDidGroupChange: Event<{ kind: GroupChangeKind }> =
|
||||
this._onDidGroupChange.event;
|
||||
@ -205,24 +213,26 @@ export class Groupview extends CompositeDisposable implements IGroupview {
|
||||
|
||||
this.container.classList.add('groupview');
|
||||
|
||||
this.addDisposables(this._onMove, this._onDidGroupChange, this._onDrop);
|
||||
this.addDisposables(this._onMove, this._onDidGroupChange);
|
||||
|
||||
this.tabsContainer = new TabsContainer(this.accessor, this.parent, {
|
||||
tabHeight: options.tabHeight,
|
||||
});
|
||||
this.contentContainer = new ContentContainer();
|
||||
this.dropTarget = new Droptarget(this.contentContainer.element, {
|
||||
isDirectional: true,
|
||||
id: this.accessor.id,
|
||||
isDisabled: () => {
|
||||
// disable the drop target if we only have one tab, and that is also the tab we are moving
|
||||
return (
|
||||
this._panels.length === 1 &&
|
||||
this.tabsContainer.hasActiveDragEvent
|
||||
);
|
||||
validOverlays: 'all',
|
||||
canDisplayOverlay: (event) => {
|
||||
const data = getPanelData();
|
||||
|
||||
if (data) {
|
||||
const groupHasOnePanelAndIsActiveDragElement =
|
||||
this._panels.length === 1 && data.groupId === this.id;
|
||||
|
||||
return !groupHasOnePanelAndIsActiveDragElement;
|
||||
}
|
||||
|
||||
return this.canDisplayOverlay(event, DockviewDropTargets.Panel);
|
||||
},
|
||||
enableExternalDragEvents:
|
||||
this.accessor.options.enableExternalDragEvents,
|
||||
});
|
||||
|
||||
container.append(
|
||||
@ -233,28 +243,22 @@ export class Groupview extends CompositeDisposable implements IGroupview {
|
||||
this.addDisposables(
|
||||
this._onMove,
|
||||
this._onDidGroupChange,
|
||||
this.tabsContainer.onDropEvent((event) =>
|
||||
this.handleDropEvent(event.event, event.index)
|
||||
),
|
||||
this.tabsContainer.onDrop((event) => {
|
||||
this.handleDropEvent(event.event, Position.Center, event.index);
|
||||
}),
|
||||
this.contentContainer.onDidFocus(() => {
|
||||
this.accessor.doSetGroupActive(this.parent, true);
|
||||
}),
|
||||
this.contentContainer.onDidBlur(() => {
|
||||
// this._activePanel?.api._ondid
|
||||
}),
|
||||
this.dropTarget.onDidChange((event) => {
|
||||
// if we've center dropped on ourself then ignore
|
||||
if (
|
||||
event.position === Position.Center &&
|
||||
this.tabsContainer.hasActiveDragEvent
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.handleDropEvent(event);
|
||||
this.dropTarget.onDrop((event) => {
|
||||
this.handleDropEvent(event.event, event.position);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
initialize() {
|
||||
if (this.options?.panels) {
|
||||
this.options.panels.forEach((panel) => {
|
||||
this.doAddPanel(panel);
|
||||
@ -264,9 +268,7 @@ export class Groupview extends CompositeDisposable implements IGroupview {
|
||||
if (this.options?.activePanel) {
|
||||
this.openPanel(this.options.activePanel);
|
||||
}
|
||||
}
|
||||
|
||||
initialize() {
|
||||
// must be run after the constructor otherwise this.parent may not be
|
||||
// correctly initialized
|
||||
this.setActive(this.isActive, true, true);
|
||||
@ -295,19 +297,19 @@ export class Groupview extends CompositeDisposable implements IGroupview {
|
||||
};
|
||||
}
|
||||
|
||||
public startActiveDrag(panel: IGroupPanel): IDisposable {
|
||||
const index = this.tabsContainer.indexOf(panel.id);
|
||||
if (index > -1) {
|
||||
const tab = this.tabsContainer.at(index);
|
||||
tab.startDragEvent();
|
||||
return {
|
||||
dispose: () => {
|
||||
tab.stopDragEvent();
|
||||
},
|
||||
};
|
||||
}
|
||||
return Disposable.NONE;
|
||||
}
|
||||
// public startActiveDrag(panel: IGroupPanel): IDisposable {
|
||||
// const index = this.tabsContainer.indexOf(panel.id);
|
||||
// if (index > -1) {
|
||||
// const tab = this.tabsContainer.at(index);
|
||||
// tab.startDragEvent();
|
||||
// return {
|
||||
// dispose: () => {
|
||||
// tab.stopDragEvent();
|
||||
// },
|
||||
// };
|
||||
// }
|
||||
// return Disposable.NONE;
|
||||
// }
|
||||
|
||||
public moveToNext(options?: {
|
||||
panel?: IGroupPanel;
|
||||
@ -385,7 +387,10 @@ export class Groupview extends CompositeDisposable implements IGroupview {
|
||||
panel: IGroupPanel,
|
||||
options: { index?: number; skipFocus?: boolean } = {}
|
||||
) {
|
||||
if (typeof options.index !== 'number') {
|
||||
if (
|
||||
typeof options.index !== 'number' ||
|
||||
options.index > this.panels.length
|
||||
) {
|
||||
options.index = this.panels.length;
|
||||
}
|
||||
if (this._activePanel === panel) {
|
||||
@ -668,33 +673,34 @@ export class Groupview extends CompositeDisposable implements IGroupview {
|
||||
}
|
||||
}
|
||||
|
||||
private handleDropEvent(event: DroptargetEvent, index?: number) {
|
||||
if (isPanelTransferEvent(event.event)) {
|
||||
this.handlePanelDropEvent(event.event, event.position, index);
|
||||
return;
|
||||
}
|
||||
|
||||
this._onDrop.fire({
|
||||
event: event.event,
|
||||
target: event.position,
|
||||
index,
|
||||
});
|
||||
|
||||
console.debug('[customDropEvent]');
|
||||
canDisplayOverlay(
|
||||
dragOverEvent: DragEvent,
|
||||
target: DockviewDropTargets
|
||||
): boolean {
|
||||
// custom overlay handler
|
||||
return false;
|
||||
}
|
||||
|
||||
private handlePanelDropEvent(
|
||||
private handleDropEvent(
|
||||
event: DragEvent,
|
||||
target: Position,
|
||||
position: Position,
|
||||
index?: number
|
||||
) {
|
||||
const dataObject = extractData(event);
|
||||
const data = getPanelData();
|
||||
|
||||
if (isTabDragEvent(dataObject)) {
|
||||
const { groupId, itemId } = dataObject;
|
||||
if (data) {
|
||||
const fromSameGroup =
|
||||
this.tabsContainer.indexOf(data.panelId) !== -1;
|
||||
|
||||
if (fromSameGroup && this.tabsContainer.size === 1) {
|
||||
console.debug('[tabs] ignore event');
|
||||
return;
|
||||
}
|
||||
|
||||
const { groupId, panelId } = data;
|
||||
const isSameGroup = this.id === groupId;
|
||||
if (isSameGroup && !target) {
|
||||
const oldIndex = this.tabsContainer.indexOf(itemId);
|
||||
if (isSameGroup && !position) {
|
||||
const oldIndex = this.tabsContainer.indexOf(panelId);
|
||||
if (oldIndex === index) {
|
||||
console.debug(
|
||||
'[tabs] drop indicates no change in position'
|
||||
@ -704,30 +710,13 @@ export class Groupview extends CompositeDisposable implements IGroupview {
|
||||
}
|
||||
|
||||
this._onMove.fire({
|
||||
target,
|
||||
groupId: dataObject.groupId,
|
||||
itemId: dataObject.itemId,
|
||||
index,
|
||||
});
|
||||
}
|
||||
|
||||
if (isCustomDragEvent(dataObject)) {
|
||||
let panel = this.accessor.getGroupPanel(dataObject.id);
|
||||
|
||||
if (!panel) {
|
||||
panel = this.accessor.addPanel(dataObject);
|
||||
}
|
||||
|
||||
if (!panel.group) {
|
||||
throw new Error(`panel ${panel.id} has no associated group`);
|
||||
}
|
||||
|
||||
this._onMove.fire({
|
||||
target,
|
||||
groupId: panel.group.id,
|
||||
itemId: panel.id,
|
||||
target: position,
|
||||
groupId: data.groupId,
|
||||
itemId: data.panelId,
|
||||
index,
|
||||
});
|
||||
} else {
|
||||
// custom drop handler
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,17 +1,14 @@
|
||||
import { addDisposableListener, Emitter, Event } from '../events';
|
||||
import { Droptarget, DroptargetEvent } from '../dnd/droptarget';
|
||||
import { CompositeDisposable } from '../lifecycle';
|
||||
import {
|
||||
DATA_KEY,
|
||||
DragType,
|
||||
LocalSelectionTransfer,
|
||||
} from '../dnd/dataTransfer';
|
||||
import { getPanelData, LocalSelectionTransfer } from '../dnd/dataTransfer';
|
||||
import { getElementsByTagName, toggleClass } from '../dom';
|
||||
import { IDockviewComponent } from '../dockview/dockviewComponent';
|
||||
import { ITabRenderer } from './types';
|
||||
import { focusedElement } from '../focusedElement';
|
||||
import { IGroupPanel } from './groupPanel';
|
||||
import { GroupviewPanel } from './groupviewPanel';
|
||||
import { DroptargetEvent, Droptarget, PanelTransfer } from '../dnd/droptarget';
|
||||
import { DockviewDropTargets } from './groupview';
|
||||
|
||||
export enum MouseEventKind {
|
||||
CLICK = 'CLICK',
|
||||
@ -26,22 +23,16 @@ export interface LayoutMouseEvent {
|
||||
}
|
||||
|
||||
export interface ITab {
|
||||
id: string;
|
||||
panelId: string;
|
||||
element: HTMLElement;
|
||||
hasActiveDragEvent: boolean;
|
||||
setContent: (element: ITabRenderer) => void;
|
||||
onChanged: Event<LayoutMouseEvent>;
|
||||
onDropped: Event<DroptargetEvent>;
|
||||
onDrop: Event<DroptargetEvent>;
|
||||
setActive(isActive: boolean): void;
|
||||
startDragEvent(): void;
|
||||
stopDragEvent(): void;
|
||||
}
|
||||
|
||||
export class Tab extends CompositeDisposable implements ITab {
|
||||
private _element: HTMLElement;
|
||||
private dragInPlayDetails: { id?: string; isDragging: boolean } = {
|
||||
isDragging: false,
|
||||
};
|
||||
private droptarget: Droptarget;
|
||||
private content?: ITabRenderer;
|
||||
|
||||
@ -49,28 +40,19 @@ export class Tab extends CompositeDisposable implements ITab {
|
||||
readonly onChanged: Event<LayoutMouseEvent> = this._onChanged.event;
|
||||
|
||||
private readonly _onDropped = new Emitter<DroptargetEvent>();
|
||||
readonly onDropped: Event<DroptargetEvent> = this._onDropped.event;
|
||||
readonly onDrop: Event<DroptargetEvent> = this._onDropped.event;
|
||||
|
||||
private readonly panelTransfer =
|
||||
LocalSelectionTransfer.getInstance<PanelTransfer>();
|
||||
|
||||
public get element() {
|
||||
return this._element;
|
||||
}
|
||||
|
||||
public get hasActiveDragEvent() {
|
||||
return this.dragInPlayDetails?.isDragging;
|
||||
}
|
||||
|
||||
public startDragEvent() {
|
||||
this.dragInPlayDetails = { isDragging: true, id: this.accessor.id };
|
||||
}
|
||||
|
||||
public stopDragEvent() {
|
||||
this.dragInPlayDetails = { isDragging: false, id: undefined };
|
||||
}
|
||||
|
||||
private iframes: HTMLElement[] = [];
|
||||
|
||||
constructor(
|
||||
public id: string,
|
||||
public panelId: string,
|
||||
private readonly accessor: IDockviewComponent,
|
||||
private group: GroupviewPanel
|
||||
) {
|
||||
@ -85,11 +67,6 @@ export class Tab extends CompositeDisposable implements ITab {
|
||||
|
||||
this.addDisposables(
|
||||
addDisposableListener(this._element, 'dragstart', (event) => {
|
||||
this.dragInPlayDetails = {
|
||||
isDragging: true,
|
||||
id: this.accessor.id,
|
||||
};
|
||||
|
||||
this.iframes = [
|
||||
...getElementsByTagName('iframe'),
|
||||
...getElementsByTagName('webview'),
|
||||
@ -102,18 +79,18 @@ export class Tab extends CompositeDisposable implements ITab {
|
||||
this.element.classList.add('dragged');
|
||||
setTimeout(() => this.element.classList.remove('dragged'), 0);
|
||||
|
||||
const data = JSON.stringify({
|
||||
type: DragType.ITEM,
|
||||
itemId: this.id,
|
||||
groupId: this.group.id,
|
||||
});
|
||||
LocalSelectionTransfer.getInstance().setData(
|
||||
[data],
|
||||
this.dragInPlayDetails.id
|
||||
this.panelTransfer.setData(
|
||||
[
|
||||
new PanelTransfer(
|
||||
this.accessor.id,
|
||||
this.group.id,
|
||||
this.panelId
|
||||
),
|
||||
],
|
||||
PanelTransfer.prototype
|
||||
);
|
||||
|
||||
if (event.dataTransfer) {
|
||||
event.dataTransfer.setData(DATA_KEY, data);
|
||||
event.dataTransfer.effectAllowed = 'move';
|
||||
}
|
||||
}),
|
||||
@ -123,14 +100,7 @@ export class Tab extends CompositeDisposable implements ITab {
|
||||
}
|
||||
this.iframes = [];
|
||||
|
||||
// drop events fire before dragend so we can remove this safely
|
||||
LocalSelectionTransfer.getInstance().clearData(
|
||||
this.dragInPlayDetails.id
|
||||
);
|
||||
this.dragInPlayDetails = {
|
||||
isDragging: false,
|
||||
id: undefined,
|
||||
};
|
||||
this.panelTransfer.clearData(PanelTransfer.prototype);
|
||||
}),
|
||||
addDisposableListener(this._element, 'mousedown', (event) => {
|
||||
if (event.defaultPrevented) {
|
||||
@ -168,16 +138,22 @@ export class Tab extends CompositeDisposable implements ITab {
|
||||
);
|
||||
|
||||
this.droptarget = new Droptarget(this._element, {
|
||||
isDirectional: false,
|
||||
isDisabled: () => this.dragInPlayDetails.isDragging,
|
||||
id: this.accessor.id,
|
||||
enableExternalDragEvents: this.accessor.options
|
||||
.enableExternalDragEvents,
|
||||
validOverlays: 'none',
|
||||
canDisplayOverlay: (event) => {
|
||||
const data = getPanelData();
|
||||
if (data) {
|
||||
return this.panelId !== data.panelId;
|
||||
}
|
||||
|
||||
return this.group.model.canDisplayOverlay(
|
||||
event,
|
||||
DockviewDropTargets.Tab
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
this.addDisposables(
|
||||
this.droptarget.onDidChange((event) => {
|
||||
event.event.preventDefault();
|
||||
this.droptarget.onDrop((event) => {
|
||||
this._onDropped.fire(event);
|
||||
})
|
||||
);
|
||||
|
@ -10,8 +10,12 @@
|
||||
display: none;
|
||||
}
|
||||
|
||||
.tabs-container {
|
||||
.void-container {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.tabs-container {
|
||||
display: flex;
|
||||
overflow-x: overlay;
|
||||
overflow-y: hidden;
|
||||
@ -32,10 +36,6 @@
|
||||
background: var(--dv-tabs-container-scrollbar-color);
|
||||
}
|
||||
|
||||
&.drag-over-target {
|
||||
background-color: var(--dv-drag-over-background-color);
|
||||
}
|
||||
|
||||
.tab {
|
||||
-webkit-user-drag: element;
|
||||
outline: none;
|
||||
|
@ -5,28 +5,28 @@ import {
|
||||
} from '../../lifecycle';
|
||||
import { addDisposableListener, Emitter, Event } from '../../events';
|
||||
import { ITab, MouseEventKind, Tab } from '../tab';
|
||||
import { removeClasses, addClasses } from '../../dom';
|
||||
import { DroptargetEvent, Position } from '../../dnd/droptarget';
|
||||
import { last } from '../../array';
|
||||
import { IGroupPanel } from '../groupPanel';
|
||||
import { IDockviewComponent } from '../../dockview/dockviewComponent';
|
||||
import { LocalSelectionTransfer } from '../../dnd/dataTransfer';
|
||||
import { getPanelData } from '../../dnd/dataTransfer';
|
||||
import { GroupviewPanel } from '../groupviewPanel';
|
||||
import { Droptarget } from '../../dnd/droptarget';
|
||||
import { DockviewDropTargets } from '../groupview';
|
||||
|
||||
export interface TabDropEvent {
|
||||
readonly event: DroptargetEvent;
|
||||
readonly index?: number;
|
||||
export interface TabDropIndexEvent {
|
||||
event: DragEvent;
|
||||
readonly index: number;
|
||||
}
|
||||
|
||||
export interface ITabsContainer extends IDisposable {
|
||||
readonly element: HTMLElement;
|
||||
readonly panels: string[];
|
||||
readonly hasActiveDragEvent: boolean;
|
||||
readonly size: number;
|
||||
height: number | undefined;
|
||||
delete: (id: string) => void;
|
||||
indexOf: (id: string) => number;
|
||||
at: (index: number) => ITab;
|
||||
onDropEvent: Event<TabDropEvent>;
|
||||
onDrop: Event<TabDropIndexEvent>;
|
||||
setActive: (isGroupActive: boolean) => void;
|
||||
setActivePanel: (panel: IGroupPanel) => void;
|
||||
isActive: (tab: ITab) => boolean;
|
||||
@ -43,8 +43,11 @@ export class TabsContainer
|
||||
{
|
||||
private readonly _element: HTMLElement;
|
||||
private readonly tabContainer: HTMLElement;
|
||||
private readonly voidContainer: HTMLElement;
|
||||
private readonly actionContainer: HTMLElement;
|
||||
|
||||
private readonly voidDropTarget: Droptarget;
|
||||
|
||||
private tabs: IValueDisposable<ITab>[] = [];
|
||||
private selectedIndex = -1;
|
||||
private active = false;
|
||||
@ -53,11 +56,15 @@ export class TabsContainer
|
||||
|
||||
private _height: number | undefined;
|
||||
|
||||
private readonly _onDropped = new Emitter<TabDropEvent>();
|
||||
readonly onDropEvent: Event<TabDropEvent> = this._onDropped.event;
|
||||
private readonly _onDrop = new Emitter<TabDropIndexEvent>();
|
||||
readonly onDrop: Event<TabDropIndexEvent> = this._onDrop.event;
|
||||
|
||||
get panels() {
|
||||
return this.tabs.map((_) => _.value.id);
|
||||
return this.tabs.map((_) => _.value.panelId);
|
||||
}
|
||||
|
||||
get size() {
|
||||
return this.tabs.length;
|
||||
}
|
||||
|
||||
get height(): number | undefined {
|
||||
@ -67,20 +74,15 @@ export class TabsContainer
|
||||
set height(value: number | undefined) {
|
||||
this._height = value;
|
||||
if (typeof value !== 'number') {
|
||||
// removeClasses(this.element, 'separator-border');
|
||||
this.element.style.removeProperty(
|
||||
'--dv-tabs-and-actions-container-height'
|
||||
);
|
||||
} else {
|
||||
// addClasses(this.element, 'separator-border');
|
||||
// if (styles?.separatorBorder) {
|
||||
this.element.style.setProperty(
|
||||
'--dv-tabs-and-actions-container-height',
|
||||
`${value}px`
|
||||
);
|
||||
// }
|
||||
}
|
||||
// this._element.style.height = `${this.height}px`;
|
||||
}
|
||||
|
||||
show() {
|
||||
@ -116,16 +118,12 @@ export class TabsContainer
|
||||
);
|
||||
}
|
||||
|
||||
public get hasActiveDragEvent() {
|
||||
return !!this.tabs.find((tab) => tab.value.hasActiveDragEvent);
|
||||
}
|
||||
|
||||
public at(index: number) {
|
||||
return this.tabs[index]?.value;
|
||||
}
|
||||
|
||||
public indexOf(id: string): number {
|
||||
return this.tabs.findIndex((tab) => tab.value.id === id);
|
||||
return this.tabs.findIndex((tab) => tab.value.panelId === id);
|
||||
}
|
||||
|
||||
constructor(
|
||||
@ -135,7 +133,7 @@ export class TabsContainer
|
||||
) {
|
||||
super();
|
||||
|
||||
this.addDisposables(this._onDropped);
|
||||
this.addDisposables(this._onDrop);
|
||||
|
||||
this._element = document.createElement('div');
|
||||
this._element.className = 'tabs-and-actions-container';
|
||||
@ -148,10 +146,38 @@ export class TabsContainer
|
||||
this.tabContainer = document.createElement('div');
|
||||
this.tabContainer.className = 'tabs-container';
|
||||
|
||||
this.voidContainer = document.createElement('div');
|
||||
this.voidContainer.className = 'void-container';
|
||||
|
||||
this._element.appendChild(this.tabContainer);
|
||||
this._element.appendChild(this.voidContainer);
|
||||
this._element.appendChild(this.actionContainer);
|
||||
|
||||
this.voidDropTarget = new Droptarget(this.voidContainer, {
|
||||
validOverlays: 'none',
|
||||
canDisplayOverlay: (event) => {
|
||||
const data = getPanelData();
|
||||
|
||||
if (data) {
|
||||
// don't show the overlay if the tab being dragged is the last panel of this group
|
||||
return last(this.tabs)?.value.panelId !== data.panelId;
|
||||
}
|
||||
|
||||
return group.model.canDisplayOverlay(
|
||||
event,
|
||||
DockviewDropTargets.Panel
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
this.addDisposables(
|
||||
this.voidDropTarget.onDrop((event) => {
|
||||
this._onDrop.fire({
|
||||
event: event.event,
|
||||
index: this.tabs.length,
|
||||
});
|
||||
}),
|
||||
this.voidDropTarget,
|
||||
addDisposableListener(this.tabContainer, 'mousedown', (event) => {
|
||||
if (event.defaultPrevented) {
|
||||
return;
|
||||
@ -162,62 +188,6 @@ export class TabsContainer
|
||||
if (isLeftClick) {
|
||||
this.accessor.doSetGroupActive(this.group);
|
||||
}
|
||||
}),
|
||||
addDisposableListener(this.tabContainer, 'dragenter', (event) => {
|
||||
if (
|
||||
!LocalSelectionTransfer.getInstance().hasData(
|
||||
this.accessor.id
|
||||
)
|
||||
) {
|
||||
console.debug('[tabs] invalid drop event');
|
||||
return;
|
||||
}
|
||||
if (!last(this.tabs)?.value.hasActiveDragEvent) {
|
||||
addClasses(this.tabContainer, 'drag-over-target');
|
||||
}
|
||||
}),
|
||||
addDisposableListener(this.tabContainer, 'dragover', (event) => {
|
||||
event.preventDefault();
|
||||
}),
|
||||
addDisposableListener(this.tabContainer, 'dragleave', (event) => {
|
||||
removeClasses(this.tabContainer, 'drag-over-target');
|
||||
}),
|
||||
addDisposableListener(this.tabContainer, 'drop', (event) => {
|
||||
if (
|
||||
!LocalSelectionTransfer.getInstance().hasData(
|
||||
this.accessor.id
|
||||
)
|
||||
) {
|
||||
console.debug('[tabs] invalid drop event');
|
||||
return;
|
||||
}
|
||||
if (event.defaultPrevented) {
|
||||
console.debug('[tab] drop event defaultprevented');
|
||||
return;
|
||||
}
|
||||
|
||||
removeClasses(this.tabContainer, 'drag-over-target');
|
||||
|
||||
const activetab = this.tabs.find(
|
||||
(tab) => tab.value.hasActiveDragEvent
|
||||
);
|
||||
|
||||
const ignore = !!(
|
||||
activetab &&
|
||||
event
|
||||
.composedPath()
|
||||
.find((x) => activetab.value.element === x)
|
||||
);
|
||||
|
||||
if (ignore) {
|
||||
console.debug('[tabs] ignore event');
|
||||
return;
|
||||
}
|
||||
|
||||
this._onDropped.fire({
|
||||
event: { event, position: Position.Center },
|
||||
index: this.tabs.length - (activetab ? 1 : 0),
|
||||
});
|
||||
})
|
||||
);
|
||||
}
|
||||
@ -251,7 +221,7 @@ export class TabsContainer
|
||||
}
|
||||
|
||||
public delete(id: string) {
|
||||
const index = this.tabs.findIndex((tab) => tab.value.id === id);
|
||||
const index = this.tabs.findIndex((tab) => tab.value.panelId === id);
|
||||
|
||||
const tabToRemove = this.tabs.splice(index, 1)[0];
|
||||
|
||||
@ -263,13 +233,13 @@ export class TabsContainer
|
||||
|
||||
public setActivePanel(panel: IGroupPanel) {
|
||||
this.tabs.forEach((tab) => {
|
||||
const isActivePanel = panel.id === tab.value.id;
|
||||
const isActivePanel = panel.id === tab.value.panelId;
|
||||
tab.value.setActive(isActivePanel);
|
||||
});
|
||||
}
|
||||
|
||||
public openPanel(panel: IGroupPanel, index: number = this.tabs.length) {
|
||||
if (this.tabs.find((tab) => tab.value.id === panel.id)) {
|
||||
if (this.tabs.find((tab) => tab.value.panelId === panel.id)) {
|
||||
return;
|
||||
}
|
||||
const tabToAdd = new Tab(panel.id, this.accessor, this.group);
|
||||
@ -299,9 +269,9 @@ export class TabsContainer
|
||||
break;
|
||||
}
|
||||
}),
|
||||
tabToAdd.onDropped((event) => {
|
||||
this._onDropped.fire({
|
||||
event,
|
||||
tabToAdd.onDrop((event) => {
|
||||
this._onDrop.fire({
|
||||
event: event.event,
|
||||
index: this.tabs.findIndex((x) => x.value === tabToAdd),
|
||||
});
|
||||
})
|
||||
|
126
packages/dockview/src/paneview/draggablePaneviewPanel.ts
Normal file
126
packages/dockview/src/paneview/draggablePaneviewPanel.ts
Normal file
@ -0,0 +1,126 @@
|
||||
import { DragHandler } from '../dnd/abstractDragHandler';
|
||||
import { getPaneData, LocalSelectionTransfer } from '../dnd/dataTransfer';
|
||||
import {
|
||||
Droptarget,
|
||||
DroptargetEvent,
|
||||
PaneTransfer,
|
||||
Position,
|
||||
} from '../dnd/droptarget';
|
||||
import { Emitter, Event } from '../events';
|
||||
import { IDisposable } from '../lifecycle';
|
||||
import { Orientation } from '../splitview/core/splitview';
|
||||
import { PanePanelInitParameter, PaneviewPanel } from './paneviewPanel';
|
||||
|
||||
interface ViewContainer {
|
||||
readonly title: string;
|
||||
readonly icon: string;
|
||||
}
|
||||
|
||||
interface ViewContainerModel {
|
||||
readonly title: string;
|
||||
readonly icon: string;
|
||||
readonly onDidAdd: Event<void>;
|
||||
readonly onDidRemove: Event<void>;
|
||||
}
|
||||
|
||||
interface IViewContainerService {
|
||||
getViewContainerById(id: string): ViewContainer;
|
||||
getViewContainerModel(container: ViewContainer): ViewContainerModel;
|
||||
}
|
||||
|
||||
export abstract class DraggablePaneviewPanel extends PaneviewPanel {
|
||||
private handler: DragHandler | undefined;
|
||||
private target: Droptarget | undefined;
|
||||
|
||||
private readonly _onDidDrop = new Emitter<DroptargetEvent>();
|
||||
readonly onDidDrop = this._onDidDrop.event;
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
component: string,
|
||||
headerComponent: string | undefined,
|
||||
orientation: Orientation,
|
||||
isExpanded: boolean,
|
||||
disableDnd: boolean
|
||||
) {
|
||||
super(id, component, headerComponent, orientation, isExpanded);
|
||||
|
||||
if (!disableDnd) {
|
||||
this.initDragFeatures();
|
||||
}
|
||||
}
|
||||
|
||||
private initDragFeatures() {
|
||||
const id = this.id;
|
||||
this.header!.draggable = true;
|
||||
this.header!.tabIndex = 0;
|
||||
|
||||
this.handler = new (class PaneDragHandler extends DragHandler {
|
||||
getData(): IDisposable {
|
||||
LocalSelectionTransfer.getInstance().setData(
|
||||
[new PaneTransfer('paneview', id)],
|
||||
PaneTransfer.prototype
|
||||
);
|
||||
|
||||
return {
|
||||
dispose: () => {
|
||||
LocalSelectionTransfer.getInstance().clearData(
|
||||
PaneTransfer.prototype
|
||||
);
|
||||
},
|
||||
};
|
||||
}
|
||||
})(this.header!);
|
||||
|
||||
this.target = new Droptarget(this.element, {
|
||||
validOverlays: 'vertical',
|
||||
canDisplayOverlay: (event: DragEvent) => {
|
||||
const data = getPaneData();
|
||||
|
||||
if (!data) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return data.paneId !== this.id;
|
||||
},
|
||||
});
|
||||
|
||||
this.addDisposables(
|
||||
this._onDidDrop,
|
||||
this.handler,
|
||||
this.target,
|
||||
this.target.onDrop((event) => {
|
||||
const data = getPaneData();
|
||||
|
||||
if (!data) {
|
||||
this._onDidDrop.fire(event);
|
||||
return;
|
||||
}
|
||||
|
||||
const containerApi = (this.params! as PanePanelInitParameter)
|
||||
.containerApi;
|
||||
const id = data.paneId;
|
||||
|
||||
const existingPanel = containerApi.getPanel(id);
|
||||
if (!existingPanel) {
|
||||
this._onDidDrop.fire(event);
|
||||
return;
|
||||
}
|
||||
|
||||
const fromIndex = containerApi
|
||||
.getPanels()
|
||||
.indexOf(existingPanel);
|
||||
let toIndex = containerApi.getPanels().indexOf(this);
|
||||
|
||||
if (
|
||||
event.position === Position.Right ||
|
||||
event.position === Position.Bottom
|
||||
) {
|
||||
toIndex = Math.max(0, toIndex + 1);
|
||||
}
|
||||
|
||||
containerApi.movePanel(fromIndex, toIndex);
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
@ -22,4 +22,5 @@ export interface PaneviewComponentOptions {
|
||||
header: FrameworkFactory<IPaneHeaderPart>;
|
||||
body: FrameworkFactory<IPaneBodyPart>;
|
||||
};
|
||||
disableDnd?: boolean;
|
||||
}
|
||||
|
@ -58,11 +58,6 @@
|
||||
outline-offset: -1px;
|
||||
outline-color: var(--dv-paneview-active-outline-color);
|
||||
}
|
||||
// outline-width: 1px;
|
||||
// outline-style: solid;
|
||||
// outline-offset: -1px;
|
||||
// opacity: 1 !important;
|
||||
// outline-color: dodgerblue;
|
||||
}
|
||||
}
|
||||
.pane-body {
|
||||
|
@ -24,6 +24,13 @@ export class Paneview extends CompositeDisposable implements IDisposable {
|
||||
private readonly _onDidChange = new Emitter<void>();
|
||||
readonly onDidChange: Event<void> = this._onDidChange.event;
|
||||
|
||||
get onDidAddView() {
|
||||
return <Event<PaneviewPanel>>this.splitview.onDidAddView;
|
||||
}
|
||||
get onDidRemoveView() {
|
||||
return <Event<PaneviewPanel>>this.splitview.onDidRemoveView;
|
||||
}
|
||||
|
||||
get minimumSize() {
|
||||
return this.splitview.minimumSize;
|
||||
}
|
||||
@ -66,10 +73,12 @@ export class Paneview extends CompositeDisposable implements IDisposable {
|
||||
// if we've added views from the descriptor we need to
|
||||
// add the panes to our Pane array and setup animation
|
||||
this.getPanes().forEach((pane, index) => {
|
||||
const disposable = pane.onDidChangeExpansionState(() => {
|
||||
this.setupAnimation();
|
||||
this._onDidChange.fire(undefined);
|
||||
});
|
||||
const disposable = new CompositeDisposable(
|
||||
pane.onDidChangeExpansionState(() => {
|
||||
this.setupAnimation();
|
||||
this._onDidChange.fire(undefined);
|
||||
})
|
||||
);
|
||||
|
||||
const paneItem: PaneItem = {
|
||||
pane,
|
||||
@ -112,6 +121,7 @@ export class Paneview extends CompositeDisposable implements IDisposable {
|
||||
};
|
||||
|
||||
this.paneItems.splice(index, 0, paneItem);
|
||||
|
||||
pane.orthogonalSize = this.splitview.orthogonalSize;
|
||||
this.splitview.addView(pane, size, index, skipLayout);
|
||||
}
|
||||
@ -131,9 +141,21 @@ export class Paneview extends CompositeDisposable implements IDisposable {
|
||||
return paneItem;
|
||||
}
|
||||
|
||||
private skipAnimation = false;
|
||||
|
||||
public moveView(from: number, to: number) {
|
||||
if (from === to) {
|
||||
return;
|
||||
}
|
||||
|
||||
const view = this.removePane(from);
|
||||
this.addPane(view.pane, to);
|
||||
|
||||
this.skipAnimation = true;
|
||||
try {
|
||||
this.addPane(view.pane, view.pane.size, to, false);
|
||||
} finally {
|
||||
this.skipAnimation = false;
|
||||
}
|
||||
}
|
||||
|
||||
public layout(size: number, orthogonalSize: number): void {
|
||||
@ -145,6 +167,10 @@ export class Paneview extends CompositeDisposable implements IDisposable {
|
||||
}
|
||||
|
||||
private setupAnimation() {
|
||||
if (this.skipAnimation) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.animationTimer) {
|
||||
clearTimeout(this.animationTimer);
|
||||
this.animationTimer = undefined;
|
||||
|
@ -22,6 +22,8 @@ import {
|
||||
PanePanelInitParameter,
|
||||
IPaneviewPanel,
|
||||
} from './paneviewPanel';
|
||||
import { DraggablePaneviewPanel } from './draggablePaneviewPanel';
|
||||
import { DroptargetEvent } from '../dnd/droptarget';
|
||||
|
||||
export interface SerializedPaneviewPanel {
|
||||
snap?: boolean;
|
||||
@ -74,7 +76,7 @@ class DefaultHeader extends CompositeDisposable implements IPaneHeaderPart {
|
||||
}
|
||||
}
|
||||
|
||||
export class PaneFramework extends PaneviewPanel {
|
||||
export class PaneFramework extends DraggablePaneviewPanel {
|
||||
constructor(
|
||||
private readonly options: {
|
||||
id: string;
|
||||
@ -84,6 +86,7 @@ export class PaneFramework extends PaneviewPanel {
|
||||
header: IPaneHeaderPart;
|
||||
orientation: Orientation;
|
||||
isExpanded: boolean;
|
||||
disableDnd: boolean;
|
||||
}
|
||||
) {
|
||||
super(
|
||||
@ -91,7 +94,8 @@ export class PaneFramework extends PaneviewPanel {
|
||||
options.component,
|
||||
options.headerComponent,
|
||||
options.orientation,
|
||||
options.isExpanded
|
||||
options.isExpanded,
|
||||
options.disableDnd
|
||||
);
|
||||
}
|
||||
|
||||
@ -142,13 +146,25 @@ export interface IPaneviewComponent extends IDisposable {
|
||||
|
||||
export class PaneviewComponent
|
||||
extends CompositeDisposable
|
||||
implements IPaneviewComponent {
|
||||
implements IPaneviewComponent
|
||||
{
|
||||
private _disposable = new MutableDisposable();
|
||||
private _paneview!: Paneview;
|
||||
|
||||
private readonly _onDidLayoutChange = new Emitter<void>();
|
||||
readonly onDidLayoutChange: Event<void> = this._onDidLayoutChange.event;
|
||||
|
||||
private readonly _onDidDrop = new Emitter<DroptargetEvent>();
|
||||
readonly onDidDrop: Event<DroptargetEvent> = this._onDidDrop.event;
|
||||
|
||||
get onDidAddView() {
|
||||
return this._paneview.onDidAddView;
|
||||
}
|
||||
|
||||
get onDidRemoveView() {
|
||||
return this._paneview.onDidRemoveView;
|
||||
}
|
||||
|
||||
set paneview(value: Paneview) {
|
||||
this._paneview = value;
|
||||
|
||||
@ -216,8 +232,8 @@ export class PaneviewComponent
|
||||
this.options.frameworkComponents || {},
|
||||
this.options.frameworkWrapper
|
||||
? {
|
||||
createComponent: this.options.frameworkWrapper.body
|
||||
.createComponent,
|
||||
createComponent:
|
||||
this.options.frameworkWrapper.body.createComponent,
|
||||
}
|
||||
: undefined
|
||||
);
|
||||
@ -232,8 +248,9 @@ export class PaneviewComponent
|
||||
this.options.headerframeworkComponents,
|
||||
this.options.frameworkWrapper
|
||||
? {
|
||||
createComponent: this.options.frameworkWrapper.header
|
||||
.createComponent,
|
||||
createComponent:
|
||||
this.options.frameworkWrapper.header
|
||||
.createComponent,
|
||||
}
|
||||
: undefined
|
||||
);
|
||||
@ -249,6 +266,11 @@ export class PaneviewComponent
|
||||
body,
|
||||
orientation: Orientation.VERTICAL,
|
||||
isExpanded: !!options.isExpanded,
|
||||
disableDnd: !!this.options.disableDnd,
|
||||
});
|
||||
|
||||
view.onDidDrop((event) => {
|
||||
this._onDidDrop.fire(event);
|
||||
});
|
||||
|
||||
const size: Sizing | number =
|
||||
@ -309,10 +331,8 @@ export class PaneviewComponent
|
||||
if (!this.element.parentElement) {
|
||||
return;
|
||||
}
|
||||
const {
|
||||
width,
|
||||
height,
|
||||
} = this.element.parentElement.getBoundingClientRect();
|
||||
const { width, height } =
|
||||
this.element.parentElement.getBoundingClientRect();
|
||||
this.layout(width, height);
|
||||
}
|
||||
|
||||
@ -367,8 +387,9 @@ export class PaneviewComponent
|
||||
this.options.frameworkComponents || {},
|
||||
this.options.frameworkWrapper
|
||||
? {
|
||||
createComponent: this.options.frameworkWrapper
|
||||
.body.createComponent,
|
||||
createComponent:
|
||||
this.options.frameworkWrapper.body
|
||||
.createComponent,
|
||||
}
|
||||
: undefined
|
||||
);
|
||||
@ -383,9 +404,9 @@ export class PaneviewComponent
|
||||
this.options.headerframeworkComponents || {},
|
||||
this.options.frameworkWrapper
|
||||
? {
|
||||
createComponent: this.options
|
||||
.frameworkWrapper.header
|
||||
.createComponent,
|
||||
createComponent:
|
||||
this.options.frameworkWrapper.header
|
||||
.createComponent,
|
||||
}
|
||||
: undefined
|
||||
);
|
||||
@ -401,6 +422,11 @@ export class PaneviewComponent
|
||||
body,
|
||||
orientation: Orientation.VERTICAL,
|
||||
isExpanded: !!view.expanded,
|
||||
disableDnd: !!this.options.disableDnd,
|
||||
});
|
||||
|
||||
panel.onDidDrop((event) => {
|
||||
this._onDidDrop.fire(event);
|
||||
});
|
||||
|
||||
queue.push(() => {
|
||||
|
@ -61,7 +61,8 @@ export interface IPaneviewPanel
|
||||
|
||||
export abstract class PaneviewPanel
|
||||
extends BasePanelView<PaneviewPanelApiImpl>
|
||||
implements IPaneview, IPaneviewPanel {
|
||||
implements IPaneview, IPaneviewPanel
|
||||
{
|
||||
private _onDidChangeExpansionState: Emitter<boolean> = new Emitter<boolean>(
|
||||
{ replay: true }
|
||||
);
|
||||
@ -71,6 +72,7 @@ export abstract class PaneviewPanel
|
||||
|
||||
private headerSize = 22;
|
||||
private _orthogonalSize = 0;
|
||||
private _size = 0;
|
||||
private _minimumBodySize = 0;
|
||||
private _maximumBodySize: number = Number.POSITIVE_INFINITY;
|
||||
private _isExpanded = false;
|
||||
@ -80,7 +82,6 @@ export abstract class PaneviewPanel
|
||||
private headerPart?: IPaneBodyPart;
|
||||
private expandedSize = 0;
|
||||
private animationTimer: any | undefined;
|
||||
|
||||
private _orientation: Orientation;
|
||||
|
||||
set orientation(value: Orientation) {
|
||||
@ -107,6 +108,10 @@ export abstract class PaneviewPanel
|
||||
return headerSize + maximumBodySize;
|
||||
}
|
||||
|
||||
get size() {
|
||||
return this._size;
|
||||
}
|
||||
|
||||
get orthogonalSize() {
|
||||
return this._orthogonalSize;
|
||||
}
|
||||
@ -176,7 +181,7 @@ export abstract class PaneviewPanel
|
||||
})
|
||||
);
|
||||
|
||||
this.render();
|
||||
this.renderOnce();
|
||||
}
|
||||
|
||||
setVisible(isVisible: boolean) {
|
||||
@ -216,6 +221,8 @@ export abstract class PaneviewPanel
|
||||
}
|
||||
|
||||
layout(size: number, orthogonalSize: number) {
|
||||
this._size = size;
|
||||
this._orthogonalSize = orthogonalSize;
|
||||
const [width, height] =
|
||||
this.orientation === Orientation.HORIZONTAL
|
||||
? [size, orthogonalSize]
|
||||
@ -259,7 +266,7 @@ export abstract class PaneviewPanel
|
||||
};
|
||||
}
|
||||
|
||||
private render() {
|
||||
private renderOnce() {
|
||||
this.header = document.createElement('div');
|
||||
this.header.tabIndex = -1;
|
||||
|
||||
|
44
packages/dockview/src/react/dropTarget.tsx
Normal file
44
packages/dockview/src/react/dropTarget.tsx
Normal file
@ -0,0 +1,44 @@
|
||||
import * as React from 'react';
|
||||
import {
|
||||
CanDisplayOverlay,
|
||||
Droptarget,
|
||||
DropTargetDirections,
|
||||
} from '../dnd/droptarget';
|
||||
|
||||
export interface IDragTragetProps {
|
||||
canDisplayOverlay: CanDisplayOverlay;
|
||||
validOverlays: DropTargetDirections;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export const DockviewDropTarget = React.forwardRef(
|
||||
(props: IDragTragetProps, ref: React.ForwardedRef<HTMLDivElement>) => {
|
||||
const domRef = React.useRef<HTMLDivElement>(null);
|
||||
const dropTargetRef = React.useRef<Droptarget>();
|
||||
|
||||
React.useImperativeHandle(ref, () => domRef.current!, []);
|
||||
|
||||
React.useEffect(() => {
|
||||
dropTargetRef.current = new Droptarget(domRef.current!, {
|
||||
canDisplayOverlay: props.canDisplayOverlay,
|
||||
validOverlays: props.validOverlays,
|
||||
});
|
||||
|
||||
return () => {
|
||||
dropTargetRef.current?.dispose();
|
||||
};
|
||||
}, []);
|
||||
|
||||
React.useEffect(() => {
|
||||
dropTargetRef.current!.validOverlays = props.validOverlays;
|
||||
}, [props.validOverlays]);
|
||||
|
||||
React.useEffect(() => {
|
||||
dropTargetRef.current!.canDisplayOverlay = props.canDisplayOverlay;
|
||||
}, [props.canDisplayOverlay]);
|
||||
|
||||
return <div ref={domRef}>{props.children}</div>;
|
||||
}
|
||||
);
|
||||
|
||||
DockviewDropTarget.displayName = 'DockviewDropTarget';
|
@ -8,3 +8,4 @@ export * from '../gridview/gridviewPanel';
|
||||
export * from './paneview/paneview';
|
||||
export * from './types';
|
||||
export * from './react';
|
||||
export * from './dropTarget';
|
||||
|
@ -9,6 +9,7 @@ import { PaneviewApi } from '../../api/component.api';
|
||||
import { PanePanelSection } from './view';
|
||||
import { PanelCollection, PanelParameters } from '../types';
|
||||
import { watchElementResize } from '../../dom';
|
||||
import { DroptargetEvent } from '../../dnd/droptarget';
|
||||
|
||||
export interface PaneviewReadyEvent {
|
||||
api: PaneviewApi;
|
||||
@ -21,12 +22,19 @@ export interface IPaneviewPanelProps<T extends {} = Record<string, any>>
|
||||
title: string;
|
||||
}
|
||||
|
||||
export interface PaneviewDropEvent {
|
||||
api: PaneviewApi;
|
||||
event: DroptargetEvent;
|
||||
}
|
||||
|
||||
export interface IPaneviewReactProps {
|
||||
onReady?: (event: PaneviewReadyEvent) => void;
|
||||
components?: PanelCollection<IPaneviewPanelProps>;
|
||||
headerComponents?: PanelCollection<IPaneviewPanelProps>;
|
||||
className?: string;
|
||||
disableAutoResizing?: boolean;
|
||||
disableDnd?: boolean;
|
||||
onDidDrop?(event: PaneviewDropEvent): void;
|
||||
}
|
||||
|
||||
export const PaneviewReact = React.forwardRef(
|
||||
@ -68,6 +76,7 @@ export const PaneviewReact = React.forwardRef(
|
||||
frameworkComponents: props.components,
|
||||
components: {},
|
||||
headerComponents: {},
|
||||
disableDnd: props.disableDnd,
|
||||
headerframeworkComponents: props.headerComponents,
|
||||
frameworkWrapper: {
|
||||
header: {
|
||||
@ -79,16 +88,25 @@ export const PaneviewReact = React.forwardRef(
|
||||
},
|
||||
});
|
||||
|
||||
const api = new PaneviewApi(paneview);
|
||||
|
||||
const disposable = paneview.onDidDrop((event) => {
|
||||
if (props.onDidDrop) {
|
||||
props.onDidDrop({ event, api });
|
||||
}
|
||||
});
|
||||
|
||||
const { clientWidth, clientHeight } = domRef.current!;
|
||||
paneview.layout(clientWidth, clientHeight);
|
||||
|
||||
if (props.onReady) {
|
||||
props.onReady({ api: new PaneviewApi(paneview) });
|
||||
props.onReady({ api });
|
||||
}
|
||||
|
||||
paneviewRef.current = paneview;
|
||||
|
||||
return () => {
|
||||
disposable.dispose();
|
||||
paneview.dispose();
|
||||
};
|
||||
}, []);
|
||||
|
@ -106,8 +106,12 @@ export class Splitview {
|
||||
private _proportions: number[] | undefined = undefined;
|
||||
private proportionalLayout: boolean;
|
||||
|
||||
private _onDidSashEnd = new Emitter<void>();
|
||||
public onDidSashEnd = this._onDidSashEnd.event;
|
||||
private readonly _onDidSashEnd = new Emitter<void>();
|
||||
readonly onDidSashEnd = this._onDidSashEnd.event;
|
||||
private readonly _onDidAddView = new Emitter<IView>();
|
||||
readonly onDidAddView = this._onDidAddView.event;
|
||||
private readonly _onDidRemoveView = new Emitter<IView>();
|
||||
readonly onDidRemoveView = this._onDidAddView.event;
|
||||
|
||||
get size() {
|
||||
return this._size;
|
||||
@ -548,6 +552,8 @@ export class Splitview {
|
||||
) {
|
||||
this.distributeViewSizes();
|
||||
}
|
||||
|
||||
this._onDidAddView.fire(view);
|
||||
}
|
||||
|
||||
distributeViewSizes(): void {
|
||||
@ -602,6 +608,8 @@ export class Splitview {
|
||||
this.distributeViewSizes();
|
||||
}
|
||||
|
||||
this._onDidRemoveView.fire(viewItem.view);
|
||||
|
||||
return viewItem.view;
|
||||
}
|
||||
|
||||
@ -1033,6 +1041,10 @@ export class Splitview {
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
this._onDidSashEnd.dispose();
|
||||
this._onDidAddView.dispose();
|
||||
this._onDidRemoveView.dispose();
|
||||
|
||||
this.element.remove();
|
||||
for (let i = 0; i < this.element.children.length; i++) {
|
||||
if (this.element.children.item(i) === this.element) {
|
||||
|
@ -7,6 +7,7 @@
|
||||
--dv-tab-close-icon: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M0 0h24v24H0z" fill="none"/><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/></svg>');
|
||||
--dv-tab-dirty-icon: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M0 0h24v24H0z" fill="none"/><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2z"/></svg>');
|
||||
--dv-drag-over-background-color: rgba(83, 89, 93, 0.5);
|
||||
--dv-drag-over-border-color: white;
|
||||
--dv-tabs-container-scrollbar-color: #888;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user