Merge pull request #136 from mathuo/133-dnd-enhancements

feat: dnd changes
This commit is contained in:
mathuo 2022-06-10 21:09:45 +01:00 committed by GitHub
commit a92fb3f554
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 878 additions and 23 deletions

View File

@ -16,16 +16,14 @@ import {
GroupOptions,
Groupview,
} from '../../groupview/groupview';
import {
DockviewPanelApi,
DockviewPanelApiImpl,
} from '../../api/groupPanelApi';
import { DockviewPanelApi } from '../../api/groupPanelApi';
import {
DefaultGroupPanelView,
IGroupPanelView,
} from '../../dockview/defaultGroupPanelView';
import { GroupPanel } from '../../groupview/groupviewPanel';
import { DockviewApi } from '../../api/component.api';
import { fireEvent } from '@testing-library/dom';
import { LocalSelectionTransfer, PanelTransfer } from '../../dnd/dataTransfer';
class Watermark implements IWatermarkRenderer {
public readonly element = document.createElement('div');
@ -134,7 +132,7 @@ class TestHeaderPart implements ITabRenderer {
}
}
class TestPanel implements IDockviewPanel {
export class TestPanel implements IDockviewPanel {
private _view: IGroupPanelView | undefined;
private _group: GroupPanel | undefined;
private _params: IGroupPanelInitParameters;
@ -148,7 +146,7 @@ class TestPanel implements IDockviewPanel {
}
get group() {
return this._group;
return this._group!;
}
get view() {
@ -544,7 +542,7 @@ describe('groupview', () => {
);
const contentContainer = groupviewContainer
.getElementsByClassName('content-container')
.item(0).childNodes;
.item(0)!.childNodes;
const panel1 = new TestPanel('id_1', null);
@ -568,4 +566,246 @@ describe('groupview', () => {
expect(contentContainer.length).toBe(1);
expect(contentContainer.item(0)).toBe(panel3.view.content.element);
});
test('that should not show drop target is external event', () => {
const accessorMock = jest.fn<Partial<DockviewComponent>, []>(() => {
return {
id: 'testcomponentid',
options: {
showDndOverlay: jest.fn(),
},
getPanel: jest.fn(),
};
});
const accessor = new accessorMock() as DockviewComponent;
const groupviewMock = jest.fn<Partial<Groupview>, []>(() => {
return {
canDisplayOverlay: jest.fn(),
};
});
const groupView = new groupviewMock() as Groupview;
const groupPanelMock = jest.fn<Partial<GroupPanel>, []>(() => {
return {
id: 'testgroupid',
model: groupView,
};
});
const container = document.createElement('div');
const cut = new Groupview(
container,
accessor,
'groupviewid',
{},
new groupPanelMock() as GroupPanel
);
const element = container
.getElementsByClassName('content-container')
.item(0)!;
jest.spyOn(element, 'clientHeight', 'get').mockImplementation(
() => 100
);
jest.spyOn(element, 'clientWidth', 'get').mockImplementation(() => 100);
fireEvent.dragEnter(element);
fireEvent.dragOver(element);
expect(accessor.options.showDndOverlay).toBeCalledTimes(1);
expect(
element.getElementsByClassName('drop-target-dropzone').length
).toBe(0);
});
test('that should not show drop target if dropping on self', () => {
const accessorMock = jest.fn<Partial<DockviewComponent>, []>(() => {
return {
id: 'testcomponentid',
options: {
showDndOverlay: jest.fn(),
},
getPanel: jest.fn(),
doSetGroupActive: jest.fn(),
};
});
const accessor = new accessorMock() as DockviewComponent;
const groupviewMock = jest.fn<Partial<Groupview>, []>(() => {
return {
canDisplayOverlay: jest.fn(),
};
});
const groupView = new groupviewMock() as Groupview;
const groupPanelMock = jest.fn<Partial<GroupPanel>, []>(() => {
return {
id: 'testgroupid',
model: groupView,
};
});
const container = document.createElement('div');
const cut = new Groupview(
container,
accessor,
'groupviewid',
{},
new groupPanelMock() as GroupPanel
);
cut.openPanel(new TestPanel('panel1', jest.fn() as any));
const element = container
.getElementsByClassName('content-container')
.item(0)!;
jest.spyOn(element, 'clientHeight', 'get').mockImplementation(
() => 100
);
jest.spyOn(element, 'clientWidth', 'get').mockImplementation(() => 100);
LocalSelectionTransfer.getInstance().setData(
[new PanelTransfer('testcomponentid', 'groupviewid', 'panel1')],
PanelTransfer.prototype
);
fireEvent.dragEnter(element);
fireEvent.dragOver(element);
expect(accessor.options.showDndOverlay).toBeCalledTimes(0);
expect(
element.getElementsByClassName('drop-target-dropzone').length
).toBe(0);
});
test('that should allow drop when not dropping on self for same component id', () => {
const accessorMock = jest.fn<Partial<DockviewComponent>, []>(() => {
return {
id: 'testcomponentid',
options: {
showDndOverlay: jest.fn(),
},
getPanel: jest.fn(),
doSetGroupActive: jest.fn(),
};
});
const accessor = new accessorMock() as DockviewComponent;
const groupviewMock = jest.fn<Partial<Groupview>, []>(() => {
return {
canDisplayOverlay: jest.fn(),
};
});
const groupView = new groupviewMock() as Groupview;
const groupPanelMock = jest.fn<Partial<GroupPanel>, []>(() => {
return {
id: 'testgroupid',
model: groupView,
};
});
const container = document.createElement('div');
const cut = new Groupview(
container,
accessor,
'groupviewid',
{},
new groupPanelMock() as GroupPanel
);
cut.openPanel(new TestPanel('panel1', jest.fn() as any));
cut.openPanel(new TestPanel('panel2', jest.fn() as any));
const element = container
.getElementsByClassName('content-container')
.item(0)!;
jest.spyOn(element, 'clientHeight', 'get').mockImplementation(
() => 100
);
jest.spyOn(element, 'clientWidth', 'get').mockImplementation(() => 100);
LocalSelectionTransfer.getInstance().setData(
[new PanelTransfer('testcomponentid', 'groupviewid', 'panel1')],
PanelTransfer.prototype
);
fireEvent.dragEnter(element);
fireEvent.dragOver(element);
expect(accessor.options.showDndOverlay).toBeCalledTimes(0);
expect(
element.getElementsByClassName('drop-target-dropzone').length
).toBe(1);
});
test('that should not allow drop when not dropping for different component id', () => {
const accessorMock = jest.fn<Partial<DockviewComponent>, []>(() => {
return {
id: 'testcomponentid',
options: {
showDndOverlay: jest.fn(),
},
getPanel: jest.fn(),
doSetGroupActive: jest.fn(),
};
});
const accessor = new accessorMock() as DockviewComponent;
const groupviewMock = jest.fn<Partial<Groupview>, []>(() => {
return {
canDisplayOverlay: jest.fn(),
};
});
const groupView = new groupviewMock() as Groupview;
const groupPanelMock = jest.fn<Partial<GroupPanel>, []>(() => {
return {
id: 'testgroupid',
model: groupView,
};
});
const container = document.createElement('div');
const cut = new Groupview(
container,
accessor,
'groupviewid',
{},
new groupPanelMock() as GroupPanel
);
cut.openPanel(new TestPanel('panel1', jest.fn() as any));
cut.openPanel(new TestPanel('panel2', jest.fn() as any));
const element = container
.getElementsByClassName('content-container')
.item(0)!;
jest.spyOn(element, 'clientHeight', 'get').mockImplementation(
() => 100
);
jest.spyOn(element, 'clientWidth', 'get').mockImplementation(() => 100);
LocalSelectionTransfer.getInstance().setData(
[new PanelTransfer('anothercomponentid', 'groupviewid', 'panel1')],
PanelTransfer.prototype
);
fireEvent.dragEnter(element);
fireEvent.dragOver(element);
expect(accessor.options.showDndOverlay).toBeCalledTimes(1);
expect(
element.getElementsByClassName('drop-target-dropzone').length
).toBe(0);
});
});

View File

@ -1,3 +1,8 @@
import { fireEvent } from '@testing-library/dom';
import { LocalSelectionTransfer, PanelTransfer } from '../../dnd/dataTransfer';
import { DockviewComponent } from '../../dockview/dockviewComponent';
import { Groupview } from '../../groupview/groupview';
import { GroupPanel } from '../../groupview/groupviewPanel';
import { Tab } from '../../groupview/tab';
describe('tab', () => {
@ -22,4 +27,251 @@ describe('tab', () => {
cut.setActive(false);
expect(cut.element.className).toBe('tab inactive-tab');
});
test('that an external event does not render a drop target and calls through to the group model', () => {
const accessorMock = jest.fn<Partial<DockviewComponent>, []>(() => {
return {
id: 'testcomponentid',
};
});
const groupviewMock = jest.fn<Partial<Groupview>, []>(() => {
return {
canDisplayOverlay: jest.fn(),
};
});
const groupView = new groupviewMock() as Groupview;
const groupPanelMock = jest.fn<Partial<GroupPanel>, []>(() => {
return {
id: 'testgroupid',
model: groupView,
};
});
const accessor = new accessorMock() as DockviewComponent;
const groupPanel = new groupPanelMock() as GroupPanel;
const cut = new Tab('panelId', accessor, groupPanel);
jest.spyOn(cut.element, 'clientHeight', 'get').mockImplementation(
() => 100
);
jest.spyOn(cut.element, 'clientWidth', 'get').mockImplementation(
() => 100
);
fireEvent.dragEnter(cut.element);
fireEvent.dragOver(cut.element);
expect(groupView.canDisplayOverlay).toBeCalled();
expect(
cut.element.getElementsByClassName('drop-target-dropzone').length
).toBe(0);
});
test('that if you drag over yourself no drop target is shown', () => {
const accessorMock = jest.fn<Partial<DockviewComponent>, []>(() => {
return {
id: 'testcomponentid',
};
});
const groupviewMock = jest.fn<Partial<Groupview>, []>(() => {
return {
canDisplayOverlay: jest.fn(),
};
});
const groupView = new groupviewMock() as Groupview;
const groupPanelMock = jest.fn<Partial<GroupPanel>, []>(() => {
return {
id: 'testgroupid',
model: groupView,
};
});
const accessor = new accessorMock() as DockviewComponent;
const groupPanel = new groupPanelMock() as GroupPanel;
const cut = new Tab('panel1', accessor, groupPanel);
jest.spyOn(cut.element, 'clientHeight', 'get').mockImplementation(
() => 100
);
jest.spyOn(cut.element, 'clientWidth', 'get').mockImplementation(
() => 100
);
LocalSelectionTransfer.getInstance().setData(
[new PanelTransfer('testcomponentid', 'anothergroupid', 'panel1')],
PanelTransfer.prototype
);
fireEvent.dragEnter(cut.element);
fireEvent.dragOver(cut.element);
expect(groupView.canDisplayOverlay).toBeCalledTimes(0);
expect(
cut.element.getElementsByClassName('drop-target-dropzone').length
).toBe(0);
});
test('that if you drag over another tab a drop target is shown', () => {
const accessorMock = jest.fn<Partial<DockviewComponent>, []>(() => {
return {
id: 'testcomponentid',
};
});
const groupviewMock = jest.fn<Partial<Groupview>, []>(() => {
return {
canDisplayOverlay: jest.fn(),
};
});
const groupView = new groupviewMock() as Groupview;
const groupPanelMock = jest.fn<Partial<GroupPanel>, []>(() => {
return {
id: 'testgroupid',
model: groupView,
};
});
const accessor = new accessorMock() as DockviewComponent;
const groupPanel = new groupPanelMock() as GroupPanel;
const cut = new Tab('panel1', accessor, groupPanel);
jest.spyOn(cut.element, 'clientHeight', 'get').mockImplementation(
() => 100
);
jest.spyOn(cut.element, 'clientWidth', 'get').mockImplementation(
() => 100
);
LocalSelectionTransfer.getInstance().setData(
[new PanelTransfer('testcomponentid', 'anothergroupid', 'panel2')],
PanelTransfer.prototype
);
fireEvent.dragEnter(cut.element);
fireEvent.dragOver(cut.element);
expect(groupView.canDisplayOverlay).toBeCalledTimes(0);
expect(
cut.element.getElementsByClassName('drop-target-dropzone').length
).toBe(1);
});
test('that dropping on a tab with the same id but from a different component should not render a drop over and call through to the group model', () => {
const accessorMock = jest.fn<Partial<DockviewComponent>, []>(() => {
return {
id: 'testcomponentid',
};
});
const groupviewMock = jest.fn<Partial<Groupview>, []>(() => {
return {
canDisplayOverlay: jest.fn(),
};
});
const groupView = new groupviewMock() as Groupview;
const groupPanelMock = jest.fn<Partial<GroupPanel>, []>(() => {
return {
id: 'testgroupid',
model: groupView,
};
});
const accessor = new accessorMock() as DockviewComponent;
const groupPanel = new groupPanelMock() as GroupPanel;
const cut = new Tab('panel1', accessor, groupPanel);
jest.spyOn(cut.element, 'clientHeight', 'get').mockImplementation(
() => 100
);
jest.spyOn(cut.element, 'clientWidth', 'get').mockImplementation(
() => 100
);
LocalSelectionTransfer.getInstance().setData(
[
new PanelTransfer(
'anothercomponentid',
'anothergroupid',
'panel1'
),
],
PanelTransfer.prototype
);
fireEvent.dragEnter(cut.element);
fireEvent.dragOver(cut.element);
expect(groupView.canDisplayOverlay).toBeCalledTimes(1);
expect(
cut.element.getElementsByClassName('drop-target-dropzone').length
).toBe(0);
});
test('that dropping on a tab from a different component should not render a drop over and call through to the group model', () => {
const accessorMock = jest.fn<Partial<DockviewComponent>, []>(() => {
return {
id: 'testcomponentid',
};
});
const groupviewMock = jest.fn<Partial<Groupview>, []>(() => {
return {
canDisplayOverlay: jest.fn(),
};
});
const groupView = new groupviewMock() as Groupview;
const groupPanelMock = jest.fn<Partial<GroupPanel>, []>(() => {
return {
id: 'testgroupid',
model: groupView,
};
});
const accessor = new accessorMock() as DockviewComponent;
const groupPanel = new groupPanelMock() as GroupPanel;
const cut = new Tab('panel1', accessor, groupPanel);
jest.spyOn(cut.element, 'clientHeight', 'get').mockImplementation(
() => 100
);
jest.spyOn(cut.element, 'clientWidth', 'get').mockImplementation(
() => 100
);
LocalSelectionTransfer.getInstance().setData(
[
new PanelTransfer(
'anothercomponentid',
'anothergroupid',
'panel2'
),
],
PanelTransfer.prototype
);
fireEvent.dragEnter(cut.element);
fireEvent.dragOver(cut.element);
expect(groupView.canDisplayOverlay).toBeCalledTimes(1);
expect(
cut.element.getElementsByClassName('drop-target-dropzone').length
).toBe(0);
});
});

View File

@ -0,0 +1,305 @@
import { DockviewComponent } from '../../../dockview/dockviewComponent';
import { GroupPanel } from '../../../groupview/groupviewPanel';
import { TabsContainer } from '../../../groupview/titlebar/tabsContainer';
import { fireEvent } from '@testing-library/dom';
import { Groupview } from '../../../groupview/groupview';
import {
LocalSelectionTransfer,
PanelTransfer,
} from '../../../dnd/dataTransfer';
import { TestPanel } from '../groupview.spec';
describe('tabsContainer', () => {
test('that an external event does not render a drop target and calls through to the group mode', () => {
const accessorMock = jest.fn<Partial<DockviewComponent>, []>(() => {
return {};
});
const groupviewMock = jest.fn<Partial<Groupview>, []>(() => {
return {
canDisplayOverlay: jest.fn(),
};
});
const groupView = new groupviewMock() as Groupview;
const groupPanelMock = jest.fn<Partial<GroupPanel>, []>(() => {
return {
model: groupView,
};
});
const accessor = new accessorMock() as DockviewComponent;
const groupPanel = new groupPanelMock() as GroupPanel;
const cut = new TabsContainer(accessor, groupPanel, {});
const emptySpace = cut.element
.getElementsByClassName('void-container')
.item(0);
if (!emptySpace) {
fail('element not found');
}
jest.spyOn(emptySpace, 'clientHeight', 'get').mockImplementation(
() => 100
);
jest.spyOn(emptySpace, 'clientWidth', 'get').mockImplementation(
() => 100
);
fireEvent.dragEnter(emptySpace);
fireEvent.dragOver(emptySpace);
expect(groupView.canDisplayOverlay).toBeCalled();
expect(
cut.element.getElementsByClassName('drop-target-dropzone').length
).toBe(0);
});
test('that a drag over event from another tab should render a drop target', () => {
const accessorMock = jest.fn<Partial<DockviewComponent>, []>(() => {
return {
id: 'testcomponentid',
};
});
const groupviewMock = jest.fn<Partial<Groupview>, []>(() => {
return {
canDisplayOverlay: jest.fn(),
};
});
const groupView = new groupviewMock() as Groupview;
const groupPanelMock = jest.fn<Partial<GroupPanel>, []>(() => {
return {
id: 'testgroupid',
model: groupView,
};
});
const accessor = new accessorMock() as DockviewComponent;
const groupPanel = new groupPanelMock() as GroupPanel;
const cut = new TabsContainer(accessor, groupPanel, {});
const emptySpace = cut.element
.getElementsByClassName('void-container')
.item(0);
if (!emptySpace) {
fail('element not found');
}
jest.spyOn(emptySpace, 'clientHeight', 'get').mockImplementation(
() => 100
);
jest.spyOn(emptySpace, 'clientWidth', 'get').mockImplementation(
() => 100
);
LocalSelectionTransfer.getInstance().setData(
[
new PanelTransfer(
'testcomponentid',
'anothergroupid',
'anotherpanelid'
),
],
PanelTransfer.prototype
);
fireEvent.dragEnter(emptySpace);
fireEvent.dragOver(emptySpace);
expect(groupView.canDisplayOverlay).toBeCalledTimes(0);
expect(
cut.element.getElementsByClassName('drop-target-dropzone').length
).toBe(1);
});
test('that dropping the last tab should render no drop target', () => {
const accessorMock = jest.fn<Partial<DockviewComponent>, []>(() => {
return {
id: 'testcomponentid',
};
});
const groupviewMock = jest.fn<Partial<Groupview>, []>(() => {
return {
canDisplayOverlay: jest.fn(),
};
});
const groupView = new groupviewMock() as Groupview;
const groupPanelMock = jest.fn<Partial<GroupPanel>, []>(() => {
return {
id: 'testgroupid',
model: groupView,
};
});
const accessor = new accessorMock() as DockviewComponent;
const groupPanel = new groupPanelMock() as GroupPanel;
const cut = new TabsContainer(accessor, groupPanel, {});
cut.openPanel(new TestPanel('panel1', jest.fn() as any));
cut.openPanel(new TestPanel('panel2', jest.fn() as any));
const emptySpace = cut.element
.getElementsByClassName('void-container')
.item(0);
if (!emptySpace) {
fail('element not found');
}
jest.spyOn(emptySpace, 'clientHeight', 'get').mockImplementation(
() => 100
);
jest.spyOn(emptySpace, 'clientWidth', 'get').mockImplementation(
() => 100
);
LocalSelectionTransfer.getInstance().setData(
[new PanelTransfer('testcomponentid', 'anothergroupid', 'panel2')],
PanelTransfer.prototype
);
fireEvent.dragEnter(emptySpace);
fireEvent.dragOver(emptySpace);
expect(groupView.canDisplayOverlay).toBeCalledTimes(0);
expect(
cut.element.getElementsByClassName('drop-target-dropzone').length
).toBe(0);
});
test('that dropping the first tab should render a drop target', () => {
const accessorMock = jest.fn<Partial<DockviewComponent>, []>(() => {
return {
id: 'testcomponentid',
};
});
const groupviewMock = jest.fn<Partial<Groupview>, []>(() => {
return {
canDisplayOverlay: jest.fn(),
};
});
const groupView = new groupviewMock() as Groupview;
const groupPanelMock = jest.fn<Partial<GroupPanel>, []>(() => {
return {
id: 'testgroupid',
model: groupView,
};
});
const accessor = new accessorMock() as DockviewComponent;
const groupPanel = new groupPanelMock() as GroupPanel;
const cut = new TabsContainer(accessor, groupPanel, {});
cut.openPanel(new TestPanel('panel1', jest.fn() as any));
cut.openPanel(new TestPanel('panel2', jest.fn() as any));
const emptySpace = cut.element
.getElementsByClassName('void-container')
.item(0);
if (!emptySpace) {
fail('element not found');
}
jest.spyOn(emptySpace, 'clientHeight', 'get').mockImplementation(
() => 100
);
jest.spyOn(emptySpace, 'clientWidth', 'get').mockImplementation(
() => 100
);
LocalSelectionTransfer.getInstance().setData(
[new PanelTransfer('testcomponentid', 'anothergroupid', 'panel1')],
PanelTransfer.prototype
);
fireEvent.dragEnter(emptySpace);
fireEvent.dragOver(emptySpace);
expect(groupView.canDisplayOverlay).toBeCalledTimes(0);
expect(
cut.element.getElementsByClassName('drop-target-dropzone').length
).toBe(1);
});
test('that dropping a tab from another component should not render a drop target', () => {
const accessorMock = jest.fn<Partial<DockviewComponent>, []>(() => {
return {
id: 'testcomponentid',
};
});
const groupviewMock = jest.fn<Partial<Groupview>, []>(() => {
return {
canDisplayOverlay: jest.fn(),
};
});
const groupView = new groupviewMock() as Groupview;
const groupPanelMock = jest.fn<Partial<GroupPanel>, []>(() => {
return {
id: 'testgroupid',
model: groupView,
};
});
const accessor = new accessorMock() as DockviewComponent;
const groupPanel = new groupPanelMock() as GroupPanel;
const cut = new TabsContainer(accessor, groupPanel, {});
cut.openPanel(new TestPanel('panel1', jest.fn() as any));
cut.openPanel(new TestPanel('panel2', jest.fn() as any));
const emptySpace = cut.element
.getElementsByClassName('void-container')
.item(0);
if (!emptySpace) {
fail('element not found');
}
jest.spyOn(emptySpace, 'clientHeight', 'get').mockImplementation(
() => 100
);
jest.spyOn(emptySpace, 'clientWidth', 'get').mockImplementation(
() => 100
);
LocalSelectionTransfer.getInstance().setData(
[
new PanelTransfer(
'anothercomponentid',
'anothergroupid',
'panel1'
),
],
PanelTransfer.prototype
);
fireEvent.dragEnter(emptySpace);
fireEvent.dragOver(emptySpace);
expect(groupView.canDisplayOverlay).toBeCalledTimes(1);
expect(
cut.element.getElementsByClassName('drop-target-dropzone').length
).toBe(0);
});
});

View File

@ -12,6 +12,7 @@ import { GroupPanel } from '../groupview/groupviewPanel';
import { ISplitviewStyles, Orientation } from '../splitview/core/splitview';
import { FrameworkFactory } from '../types';
import { DockviewDropTargets } from '../groupview/dnd';
import { PanelTransfer } from '../dnd/dataTransfer';
export interface GroupPanelFrameworkComponentFactory {
content: FrameworkFactory<IContentRenderer>;
@ -53,6 +54,7 @@ export interface DockviewDndOverlayEvent {
nativeEvent: DragEvent;
target: DockviewDropTargets;
group: GroupPanel;
getData: () => PanelTransfer | undefined;
}
export interface DockviewComponentOptions extends DockviewRenderFunctions {

View File

@ -247,7 +247,7 @@ export class Groupview extends CompositeDisposable implements IGroupview {
const data = getPanelData();
if (data) {
if (data && data.viewId === this.accessor.id) {
const groupHasOnePanelAndIsActiveDragElement =
this._panels.length === 1 && data.groupId === this.id;
@ -670,6 +670,7 @@ export class Groupview extends CompositeDisposable implements IGroupview {
nativeEvent: event,
target,
group: this.accessor.getPanel(this.id)!,
getData: getPanelData,
});
}
return false;

View File

@ -52,7 +52,7 @@ export class Tab extends CompositeDisposable implements ITab {
constructor(
public readonly panelId: string,
accessor: IDockviewComponent,
private readonly accessor: IDockviewComponent,
private readonly group: GroupPanel
) {
super();
@ -119,7 +119,7 @@ export class Tab extends CompositeDisposable implements ITab {
validOverlays: 'none',
canDisplayOverlay: (event) => {
const data = getPanelData();
if (data) {
if (data && this.accessor.id === data.viewId) {
return this.panelId !== data.panelId;
}

View File

@ -169,7 +169,7 @@ export class TabsContainer
canDisplayOverlay: (event) => {
const data = getPanelData();
if (data) {
if (data && this.accessor.id === data.viewId) {
// 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;
}

View File

@ -16,6 +16,7 @@ export * from './dockview/dockviewComponent';
export * from './gridview/gridviewComponent';
export * from './splitview/splitviewComponent';
export * from './paneview/paneviewComponent';
export { PaneviewComponentOptions } from './paneview/options';
export * from './gridview/gridviewPanel';
export * from './splitview/splitviewPanel';

View File

@ -8,6 +8,7 @@ import { Droptarget, DroptargetEvent, Position } from '../dnd/droptarget';
import { Emitter } from '../events';
import { IDisposable } from '../lifecycle';
import { Orientation } from '../splitview/core/splitview';
import { IPaneviewComponent } from './paneviewComponent';
import {
IPaneviewPanel,
PanePanelInitParameter,
@ -27,6 +28,7 @@ export abstract class DraggablePaneviewPanel extends PaneviewPanel {
readonly onDidDrop = this._onDidDrop.event;
constructor(
private readonly accessor: IPaneviewComponent,
id: string,
component: string,
headerComponent: string | undefined,
@ -47,12 +49,13 @@ export abstract class DraggablePaneviewPanel extends PaneviewPanel {
}
const id = this.id;
const accessorId = this.accessor.id;
this.header.draggable = true;
this.handler = new (class PaneDragHandler extends DragHandler {
getData(): IDisposable {
LocalSelectionTransfer.getInstance().setData(
[new PaneTransfer('paneview', id)],
[new PaneTransfer(accessorId, id)],
PaneTransfer.prototype
);
@ -68,14 +71,27 @@ export abstract class DraggablePaneviewPanel extends PaneviewPanel {
this.target = new Droptarget(this.element, {
validOverlays: 'vertical',
canDisplayOverlay: () => {
canDisplayOverlay: (event) => {
const data = getPaneData();
if (!data) {
return true;
if (data) {
if (
data.paneId !== this.id &&
data.viewId === this.accessor.id
) {
return true;
}
}
return data.paneId !== this.id;
if (this.accessor.options.showDndOverlay) {
return this.accessor.options.showDndOverlay({
nativeEvent: event,
getData: getPaneData,
panel: this,
});
}
return false;
},
});
@ -92,11 +108,13 @@ export abstract class DraggablePaneviewPanel extends PaneviewPanel {
private onDrop(event: DroptargetEvent) {
const data = getPaneData();
if (!data) {
if (!data || data.viewId !== this.accessor.id) {
// if there is no local drag event for this panel
// or if the drag event was creating by another Paneview instance
this._onDidDrop.fire({
...event,
panel: this,
getData: () => getPaneData(),
getData: getPaneData,
});
return;
}
@ -107,10 +125,11 @@ export abstract class DraggablePaneviewPanel extends PaneviewPanel {
const existingPanel = containerApi.getPanel(panelId);
if (!existingPanel) {
// if the panel doesn't exist
this._onDidDrop.fire({
...event,
panel: this,
getData: () => getPaneData(),
getData: getPaneData,
});
return;
}

View File

@ -1,4 +1,5 @@
import { FrameworkFactory } from '../types';
import { PaneviewDndOverlayEvent } from './paneviewComponent';
import { IPaneBodyPart, IPaneHeaderPart, PaneviewPanel } from './paneviewPanel';
export interface PaneviewComponentOptions {
@ -23,4 +24,5 @@ export interface PaneviewComponentOptions {
body: FrameworkFactory<IPaneBodyPart>;
};
disableDnd?: boolean;
showDndOverlay?: (event: PaneviewDndOverlayEvent) => boolean;
}

View File

@ -24,6 +24,16 @@ import {
PaneviewDropEvent2,
} from './draggablePaneviewPanel';
import { DefaultHeader } from './defaultPaneviewHeader';
import { sequentialNumberGenerator } from '../math';
import { PaneTransfer } from '../dnd/dataTransfer';
const nextLayoutId = sequentialNumberGenerator();
export interface PaneviewDndOverlayEvent {
nativeEvent: DragEvent;
panel: IPaneviewPanel;
getData: () => PaneTransfer | undefined;
}
export interface SerializedPaneviewPanel {
snap?: boolean;
@ -57,9 +67,11 @@ export class PaneFramework extends DraggablePaneviewPanel {
orientation: Orientation;
isExpanded: boolean;
disableDnd: boolean;
accessor: IPaneviewComponent;
}
) {
super(
options.accessor,
options.id,
options.component,
options.headerComponent,
@ -94,11 +106,13 @@ export interface AddPaneviewComponentOptions {
}
export interface IPaneviewComponent extends IDisposable {
readonly id: string;
readonly width: number;
readonly height: number;
readonly minimumSize: number;
readonly maximumSize: number;
readonly panels: IPaneviewPanel[];
readonly options: PaneviewComponentOptions;
readonly onDidAddView: Event<PaneviewPanel>;
readonly onDidRemoveView: Event<PaneviewPanel>;
readonly onDidDrop: Event<PaneviewDropEvent2>;
@ -120,6 +134,8 @@ export class PaneviewComponent
extends CompositeDisposable
implements IPaneviewComponent
{
private readonly _id = nextLayoutId.next();
private _options: PaneviewComponentOptions;
private _disposable = new MutableDisposable();
private _viewDisposables = new Map<string, IDisposable>();
private _paneview!: Paneview;
@ -139,6 +155,10 @@ export class PaneviewComponent
private readonly _onDidRemoveView = new Emitter<PaneviewPanel>();
readonly onDidRemoveView = this._onDidRemoveView.event;
get id(): string {
return this._id;
}
get panels(): PaneviewPanel[] {
return this.paneview.getPanes();
}
@ -179,9 +199,7 @@ export class PaneviewComponent
: this.paneview.orthogonalSize;
}
private _options: PaneviewComponentOptions;
get options() {
get options(): PaneviewComponentOptions {
return this._options;
}
@ -267,6 +285,7 @@ export class PaneviewComponent
orientation: Orientation.VERTICAL,
isExpanded: !!options.isExpanded,
disableDnd: !!this.options.disableDnd,
accessor: this,
});
this.doAddPanel(view);
@ -400,6 +419,7 @@ export class PaneviewComponent
orientation: Orientation.VERTICAL,
isExpanded: !!view.expanded,
disableDnd: !!this.options.disableDnd,
accessor: this,
});
this.doAddPanel(panel);

View File

@ -137,6 +137,7 @@ export const DockviewReact = React.forwardRef(
styles: props.hideBorders
? { separatorBorder: 'transparent' }
: undefined,
showDndOverlay: props.showDndOverlay,
});
domRef.current?.appendChild(dockview.element);

View File

@ -3,6 +3,7 @@ import { PaneviewPanelApi } from '../../api/paneviewPanelApi';
import {
PaneviewComponent,
IPaneviewComponent,
PaneviewDndOverlayEvent,
} from '../../paneview/paneviewComponent';
import { usePortalsLifecycle } from '../react';
import { PaneviewApi } from '../../api/component.api';
@ -33,6 +34,7 @@ export interface IPaneviewReactProps {
className?: string;
disableAutoResizing?: boolean;
disableDnd?: boolean;
showDndOverlay?: (event: PaneviewDndOverlayEvent) => boolean;
onDidDrop?(event: PaneviewDropEvent): void;
}
@ -85,6 +87,7 @@ export const PaneviewReact = React.forwardRef(
createComponent,
},
},
showDndOverlay: props.showDndOverlay,
});
const api = new PaneviewApi(paneview);
@ -144,6 +147,15 @@ export const PaneviewReact = React.forwardRef(
};
}, [props.onDidDrop]);
React.useEffect(() => {
if (!paneviewRef.current) {
return;
}
paneviewRef.current.updateOptions({
showDndOverlay: props.showDndOverlay,
});
}, [props.showDndOverlay]);
return (
<div
className={props.className}