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, GroupOptions,
Groupview, Groupview,
} from '../../groupview/groupview'; } from '../../groupview/groupview';
import { import { DockviewPanelApi } from '../../api/groupPanelApi';
DockviewPanelApi,
DockviewPanelApiImpl,
} from '../../api/groupPanelApi';
import { import {
DefaultGroupPanelView, DefaultGroupPanelView,
IGroupPanelView, IGroupPanelView,
} from '../../dockview/defaultGroupPanelView'; } from '../../dockview/defaultGroupPanelView';
import { GroupPanel } from '../../groupview/groupviewPanel'; 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 { class Watermark implements IWatermarkRenderer {
public readonly element = document.createElement('div'); 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 _view: IGroupPanelView | undefined;
private _group: GroupPanel | undefined; private _group: GroupPanel | undefined;
private _params: IGroupPanelInitParameters; private _params: IGroupPanelInitParameters;
@ -148,7 +146,7 @@ class TestPanel implements IDockviewPanel {
} }
get group() { get group() {
return this._group; return this._group!;
} }
get view() { get view() {
@ -544,7 +542,7 @@ describe('groupview', () => {
); );
const contentContainer = groupviewContainer const contentContainer = groupviewContainer
.getElementsByClassName('content-container') .getElementsByClassName('content-container')
.item(0).childNodes; .item(0)!.childNodes;
const panel1 = new TestPanel('id_1', null); const panel1 = new TestPanel('id_1', null);
@ -568,4 +566,246 @@ describe('groupview', () => {
expect(contentContainer.length).toBe(1); expect(contentContainer.length).toBe(1);
expect(contentContainer.item(0)).toBe(panel3.view.content.element); 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'; import { Tab } from '../../groupview/tab';
describe('tab', () => { describe('tab', () => {
@ -22,4 +27,251 @@ describe('tab', () => {
cut.setActive(false); cut.setActive(false);
expect(cut.element.className).toBe('tab inactive-tab'); 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 { ISplitviewStyles, Orientation } from '../splitview/core/splitview';
import { FrameworkFactory } from '../types'; import { FrameworkFactory } from '../types';
import { DockviewDropTargets } from '../groupview/dnd'; import { DockviewDropTargets } from '../groupview/dnd';
import { PanelTransfer } from '../dnd/dataTransfer';
export interface GroupPanelFrameworkComponentFactory { export interface GroupPanelFrameworkComponentFactory {
content: FrameworkFactory<IContentRenderer>; content: FrameworkFactory<IContentRenderer>;
@ -53,6 +54,7 @@ export interface DockviewDndOverlayEvent {
nativeEvent: DragEvent; nativeEvent: DragEvent;
target: DockviewDropTargets; target: DockviewDropTargets;
group: GroupPanel; group: GroupPanel;
getData: () => PanelTransfer | undefined;
} }
export interface DockviewComponentOptions extends DockviewRenderFunctions { export interface DockviewComponentOptions extends DockviewRenderFunctions {

View File

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

View File

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

View File

@ -169,7 +169,7 @@ export class TabsContainer
canDisplayOverlay: (event) => { canDisplayOverlay: (event) => {
const data = getPanelData(); 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 // 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 last(this.tabs)?.value.panelId !== data.panelId;
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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