mirror of
https://github.com/mathuo/dockview
synced 2025-03-12 08:52:05 +00:00
Merge pull request #177 from mathuo/176-dnd-to-edge-of-dockview
176 dnd to edge of dockview
This commit is contained in:
commit
213d3ed235
@ -10,7 +10,11 @@ describe('groupPanelApi', () => {
|
||||
title: 'test_title',
|
||||
};
|
||||
|
||||
const accessor: Partial<DockviewComponent> = {};
|
||||
const accessor: Partial<DockviewComponent> = {
|
||||
onDidAddPanel: jest.fn(),
|
||||
onDidRemovePanel: jest.fn(),
|
||||
options: {},
|
||||
};
|
||||
const groupViewPanel = new GroupPanel(
|
||||
<DockviewComponent>accessor,
|
||||
'',
|
||||
@ -44,7 +48,11 @@ describe('groupPanelApi', () => {
|
||||
id: 'test_id',
|
||||
};
|
||||
|
||||
const accessor: Partial<DockviewComponent> = {};
|
||||
const accessor: Partial<DockviewComponent> = {
|
||||
onDidAddPanel: jest.fn(),
|
||||
onDidRemovePanel: jest.fn(),
|
||||
options: {},
|
||||
};
|
||||
const groupViewPanel = new GroupPanel(
|
||||
<DockviewComponent>accessor,
|
||||
'',
|
||||
|
@ -1,16 +1,22 @@
|
||||
import { Droptarget, Position } from '../../dnd/droptarget';
|
||||
import {
|
||||
calculateQuadrantAsPercentage,
|
||||
calculateQuadrantAsPixels,
|
||||
directionToPosition,
|
||||
Droptarget,
|
||||
Position,
|
||||
} from '../../dnd/droptarget';
|
||||
import { fireEvent } from '@testing-library/dom';
|
||||
|
||||
function createOffsetDragOverEvent(params: {
|
||||
offsetX: number;
|
||||
offsetY: number;
|
||||
clientX: number;
|
||||
clientY: number;
|
||||
}): Event {
|
||||
const event = new Event('dragover', {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
});
|
||||
Object.defineProperty(event, 'offsetX', { get: () => params.offsetX });
|
||||
Object.defineProperty(event, 'offsetY', { get: () => params.offsetY });
|
||||
Object.defineProperty(event, 'clientX', { get: () => params.clientX });
|
||||
Object.defineProperty(event, 'clientY', { get: () => params.clientY });
|
||||
return event;
|
||||
}
|
||||
|
||||
@ -27,12 +33,23 @@ describe('droptarget', () => {
|
||||
jest.spyOn(element, 'clientWidth', 'get').mockImplementation(() => 200);
|
||||
});
|
||||
|
||||
test('directionToPosition', () => {
|
||||
expect(directionToPosition('above')).toBe('top');
|
||||
expect(directionToPosition('below')).toBe('bottom');
|
||||
expect(directionToPosition('left')).toBe('left');
|
||||
expect(directionToPosition('right')).toBe('right');
|
||||
expect(directionToPosition('within')).toBe('center');
|
||||
expect(() => directionToPosition('bad_input' as any)).toThrow(
|
||||
"invalid direction 'bad_input'"
|
||||
);
|
||||
});
|
||||
|
||||
test('non-directional', () => {
|
||||
let position: Position | undefined = undefined;
|
||||
|
||||
droptarget = new Droptarget(element, {
|
||||
canDisplayOverlay: () => true,
|
||||
validOverlays: 'none',
|
||||
acceptedTargetZones: ['center'],
|
||||
});
|
||||
|
||||
droptarget.onDrop((event) => {
|
||||
@ -46,7 +63,7 @@ describe('droptarget', () => {
|
||||
'.drop-target-dropzone'
|
||||
) as HTMLElement;
|
||||
fireEvent.drop(target);
|
||||
expect(position).toBe(Position.Center);
|
||||
expect(position).toBe('center');
|
||||
});
|
||||
|
||||
test('drop', () => {
|
||||
@ -54,7 +71,7 @@ describe('droptarget', () => {
|
||||
|
||||
droptarget = new Droptarget(element, {
|
||||
canDisplayOverlay: () => true,
|
||||
validOverlays: 'all',
|
||||
acceptedTargetZones: ['top', 'left', 'right', 'bottom', 'center'],
|
||||
});
|
||||
|
||||
droptarget.onDrop((event) => {
|
||||
@ -73,18 +90,21 @@ describe('droptarget', () => {
|
||||
|
||||
fireEvent(
|
||||
target,
|
||||
createOffsetDragOverEvent({ offsetX: 19, offsetY: 0 })
|
||||
createOffsetDragOverEvent({
|
||||
clientX: 19,
|
||||
clientY: 0,
|
||||
})
|
||||
);
|
||||
|
||||
expect(position).toBeUndefined();
|
||||
fireEvent.drop(target);
|
||||
expect(position).toBe(Position.Left);
|
||||
expect(position).toBe('left');
|
||||
});
|
||||
|
||||
test('default', () => {
|
||||
droptarget = new Droptarget(element, {
|
||||
canDisplayOverlay: () => true,
|
||||
validOverlays: 'all',
|
||||
acceptedTargetZones: ['top', 'left', 'right', 'bottom', 'center'],
|
||||
});
|
||||
|
||||
expect(droptarget.state).toBeUndefined();
|
||||
@ -106,57 +126,204 @@ describe('droptarget', () => {
|
||||
|
||||
fireEvent(
|
||||
target,
|
||||
createOffsetDragOverEvent({ offsetX: 19, offsetY: 0 })
|
||||
createOffsetDragOverEvent({ clientX: 19, clientY: 0 })
|
||||
);
|
||||
|
||||
viewQuery = element.querySelectorAll(
|
||||
'.drop-target > .drop-target-dropzone > .drop-target-selection.left'
|
||||
'.drop-target > .drop-target-dropzone > .drop-target-selection'
|
||||
);
|
||||
expect(viewQuery.length).toBe(1);
|
||||
expect(droptarget.state).toBe(Position.Left);
|
||||
expect(droptarget.state).toBe('left');
|
||||
expect(
|
||||
(
|
||||
element
|
||||
.getElementsByClassName('drop-target-selection')
|
||||
.item(0) as HTMLDivElement
|
||||
).style.transform
|
||||
).toBe('translateX(-25%) scaleX(0.5)');
|
||||
|
||||
fireEvent(
|
||||
target,
|
||||
createOffsetDragOverEvent({ offsetX: 40, offsetY: 19 })
|
||||
createOffsetDragOverEvent({ clientX: 40, clientY: 19 })
|
||||
);
|
||||
|
||||
viewQuery = element.querySelectorAll(
|
||||
'.drop-target > .drop-target-dropzone > .drop-target-selection.top'
|
||||
'.drop-target > .drop-target-dropzone > .drop-target-selection'
|
||||
);
|
||||
expect(viewQuery.length).toBe(1);
|
||||
expect(droptarget.state).toBe(Position.Top);
|
||||
expect(droptarget.state).toBe('top');
|
||||
expect(
|
||||
(
|
||||
element
|
||||
.getElementsByClassName('drop-target-selection')
|
||||
.item(0) as HTMLDivElement
|
||||
).style.transform
|
||||
).toBe('translateY(-25%) scaleY(0.5)');
|
||||
|
||||
fireEvent(
|
||||
target,
|
||||
createOffsetDragOverEvent({ offsetX: 160, offsetY: 81 })
|
||||
createOffsetDragOverEvent({ clientX: 160, clientY: 81 })
|
||||
);
|
||||
|
||||
viewQuery = element.querySelectorAll(
|
||||
'.drop-target > .drop-target-dropzone > .drop-target-selection.bottom'
|
||||
'.drop-target > .drop-target-dropzone > .drop-target-selection'
|
||||
);
|
||||
expect(viewQuery.length).toBe(1);
|
||||
expect(droptarget.state).toBe(Position.Bottom);
|
||||
expect(droptarget.state).toBe('bottom');
|
||||
expect(
|
||||
(
|
||||
element
|
||||
.getElementsByClassName('drop-target-selection')
|
||||
.item(0) as HTMLDivElement
|
||||
).style.transform
|
||||
).toBe('translateY(25%) scaleY(0.5)');
|
||||
|
||||
fireEvent(
|
||||
target,
|
||||
createOffsetDragOverEvent({ offsetX: 161, offsetY: 0 })
|
||||
createOffsetDragOverEvent({ clientX: 161, clientY: 0 })
|
||||
);
|
||||
|
||||
viewQuery = element.querySelectorAll(
|
||||
'.drop-target > .drop-target-dropzone > .drop-target-selection.right'
|
||||
'.drop-target > .drop-target-dropzone > .drop-target-selection'
|
||||
);
|
||||
expect(viewQuery.length).toBe(1);
|
||||
expect(droptarget.state).toBe(Position.Right);
|
||||
expect(droptarget.state).toBe('right');
|
||||
expect(
|
||||
(
|
||||
element
|
||||
.getElementsByClassName('drop-target-selection')
|
||||
.item(0) as HTMLDivElement
|
||||
).style.transform
|
||||
).toBe('translateX(25%) scaleX(0.5)');
|
||||
|
||||
fireEvent(
|
||||
target,
|
||||
createOffsetDragOverEvent({ offsetX: 100, offsetY: 50 })
|
||||
createOffsetDragOverEvent({ clientX: 100, clientY: 50 })
|
||||
);
|
||||
expect(droptarget.state).toBe(Position.Center);
|
||||
expect(droptarget.state).toBe('center');
|
||||
expect(
|
||||
(
|
||||
element
|
||||
.getElementsByClassName('drop-target-selection')
|
||||
.item(0) as HTMLDivElement
|
||||
).style.transform
|
||||
).toBe('');
|
||||
|
||||
fireEvent.dragLeave(target);
|
||||
expect(droptarget.state).toBeUndefined();
|
||||
expect(droptarget.state).toBe('center');
|
||||
viewQuery = element.querySelectorAll('.drop-target');
|
||||
expect(viewQuery.length).toBe(0);
|
||||
});
|
||||
|
||||
describe('calculateQuadrantAsPercentage', () => {
|
||||
test('variety of cases', () => {
|
||||
const inputs: Array<{
|
||||
directions: Position[];
|
||||
x: number;
|
||||
y: number;
|
||||
result: Position | null;
|
||||
}> = [
|
||||
{ directions: ['left', 'right'], x: 19, y: 50, result: 'left' },
|
||||
{
|
||||
directions: ['left', 'right'],
|
||||
x: 81,
|
||||
y: 50,
|
||||
result: 'right',
|
||||
},
|
||||
{
|
||||
directions: ['top', 'bottom'],
|
||||
x: 50,
|
||||
y: 19,
|
||||
result: 'top',
|
||||
},
|
||||
{
|
||||
directions: ['top', 'bottom'],
|
||||
x: 50,
|
||||
y: 81,
|
||||
result: 'bottom',
|
||||
},
|
||||
{
|
||||
directions: ['left', 'right', 'top', 'bottom', 'center'],
|
||||
x: 50,
|
||||
y: 50,
|
||||
result: 'center',
|
||||
},
|
||||
{
|
||||
directions: ['left', 'right', 'top', 'bottom'],
|
||||
x: 50,
|
||||
y: 50,
|
||||
result: null,
|
||||
},
|
||||
];
|
||||
|
||||
for (const input of inputs) {
|
||||
expect(
|
||||
calculateQuadrantAsPercentage(
|
||||
new Set(input.directions),
|
||||
input.x,
|
||||
input.y,
|
||||
100,
|
||||
100,
|
||||
20
|
||||
)
|
||||
).toBe(input.result);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('calculateQuadrantAsPixels', () => {
|
||||
test('variety of cases', () => {
|
||||
const inputs: Array<{
|
||||
directions: Position[];
|
||||
x: number;
|
||||
y: number;
|
||||
result: Position | null;
|
||||
}> = [
|
||||
{ directions: ['left', 'right'], x: 19, y: 50, result: 'left' },
|
||||
{
|
||||
directions: ['left', 'right'],
|
||||
x: 81,
|
||||
y: 50,
|
||||
result: 'right',
|
||||
},
|
||||
{
|
||||
directions: ['top', 'bottom'],
|
||||
x: 50,
|
||||
y: 19,
|
||||
result: 'top',
|
||||
},
|
||||
{
|
||||
directions: ['top', 'bottom'],
|
||||
x: 50,
|
||||
y: 81,
|
||||
result: 'bottom',
|
||||
},
|
||||
{
|
||||
directions: ['left', 'right', 'top', 'bottom', 'center'],
|
||||
x: 50,
|
||||
y: 50,
|
||||
result: 'center',
|
||||
},
|
||||
{
|
||||
directions: ['left', 'right', 'top', 'bottom'],
|
||||
x: 50,
|
||||
y: 50,
|
||||
result: null,
|
||||
},
|
||||
];
|
||||
|
||||
for (const input of inputs) {
|
||||
expect(
|
||||
calculateQuadrantAsPixels(
|
||||
new Set(input.directions),
|
||||
input.x,
|
||||
input.y,
|
||||
100,
|
||||
100,
|
||||
20
|
||||
)
|
||||
).toBe(input.result);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -32,7 +32,7 @@ class PanelContentPartTest implements IContentRenderer {
|
||||
|
||||
isDisposed: boolean = false;
|
||||
|
||||
constructor(public readonly id: string, component: string) {
|
||||
constructor(public readonly id: string, public readonly component: string) {
|
||||
this.element.classList.add(`testpanel-${id}`);
|
||||
}
|
||||
|
||||
@ -53,7 +53,7 @@ class PanelContentPartTest implements IContentRenderer {
|
||||
}
|
||||
|
||||
toJSON(): object {
|
||||
return { id: this.id };
|
||||
return { id: this.component };
|
||||
}
|
||||
|
||||
focus(): void {
|
||||
@ -253,9 +253,9 @@ describe('dockviewComponent', () => {
|
||||
const panel4 = dockview.getGroupPanel('panel4');
|
||||
|
||||
const group1 = panel1!.group;
|
||||
dockview.moveGroupOrPanel(group1, group1.id, 'panel1', Position.Right);
|
||||
dockview.moveGroupOrPanel(group1, group1.id, 'panel1', 'right');
|
||||
const group2 = panel1!.group;
|
||||
dockview.moveGroupOrPanel(group2, group1.id, 'panel3', Position.Center);
|
||||
dockview.moveGroupOrPanel(group2, group1.id, 'panel3', 'center');
|
||||
|
||||
expect(dockview.activeGroup).toBe(group2);
|
||||
expect(dockview.activeGroup!.model.activePanel).toBe(panel3);
|
||||
@ -302,12 +302,12 @@ describe('dockviewComponent', () => {
|
||||
component: 'default',
|
||||
});
|
||||
|
||||
const panel1 = dockview.getGroupPanel('panel1');
|
||||
const panel2 = dockview.getGroupPanel('panel2');
|
||||
const panel1 = dockview.getGroupPanel('panel1')!;
|
||||
const panel2 = dockview.getGroupPanel('panel2')!;
|
||||
const group1 = panel1.group;
|
||||
dockview.moveGroupOrPanel(group1, group1.id, 'panel1', Position.Right);
|
||||
dockview.moveGroupOrPanel(group1, group1.id, 'panel1', 'right');
|
||||
const group2 = panel1.group;
|
||||
dockview.moveGroupOrPanel(group2, group1.id, 'panel3', Position.Center);
|
||||
dockview.moveGroupOrPanel(group2, group1.id, 'panel3', 'center');
|
||||
|
||||
expect(dockview.size).toBe(2);
|
||||
expect(dockview.totalPanels).toBe(4);
|
||||
@ -345,10 +345,10 @@ describe('dockviewComponent', () => {
|
||||
component: 'default',
|
||||
});
|
||||
|
||||
const panel1 = dockview.getGroupPanel('panel1');
|
||||
const panel2 = dockview.getGroupPanel('panel2');
|
||||
const panel3 = dockview.getGroupPanel('panel3');
|
||||
const panel4 = dockview.getGroupPanel('panel4');
|
||||
const panel1 = dockview.getGroupPanel('panel1')!;
|
||||
const panel2 = dockview.getGroupPanel('panel2')!;
|
||||
const panel3 = dockview.getGroupPanel('panel3')!;
|
||||
const panel4 = dockview.getGroupPanel('panel4')!;
|
||||
|
||||
expect(panel1.api.isActive).toBeFalsy();
|
||||
expect(panel2.api.isActive).toBeFalsy();
|
||||
@ -370,9 +370,9 @@ describe('dockviewComponent', () => {
|
||||
expect(panel4.api.isActive).toBeFalsy();
|
||||
|
||||
const group1 = panel1.group;
|
||||
dockview.moveGroupOrPanel(group1, group1.id, 'panel1', Position.Right);
|
||||
dockview.moveGroupOrPanel(group1, group1.id, 'panel1', 'right');
|
||||
const group2 = panel1.group;
|
||||
dockview.moveGroupOrPanel(group2, group1.id, 'panel3', Position.Center);
|
||||
dockview.moveGroupOrPanel(group2, group1.id, 'panel3', 'center');
|
||||
|
||||
expect(dockview.size).toBe(2);
|
||||
expect(panel1.group).toBe(panel3.group);
|
||||
@ -425,9 +425,8 @@ describe('dockviewComponent', () => {
|
||||
expect(dockview.size).toBe(1);
|
||||
expect(dockview.totalPanels).toBe(2);
|
||||
|
||||
const panel1 = dockview.getGroupPanel('panel1');
|
||||
const panel2 = dockview.getGroupPanel('panel2');
|
||||
|
||||
const panel1 = dockview.getGroupPanel('panel1')!;
|
||||
const panel2 = dockview.getGroupPanel('panel2')!;
|
||||
expect(panel1.group).toBe(panel2.group);
|
||||
|
||||
const group = panel1.group;
|
||||
@ -440,7 +439,7 @@ describe('dockviewComponent', () => {
|
||||
expect(group.model.indexOf(panel1)).toBe(0);
|
||||
expect(group.model.indexOf(panel2)).toBe(1);
|
||||
|
||||
dockview.moveGroupOrPanel(group, group.id, 'panel1', Position.Right);
|
||||
dockview.moveGroupOrPanel(group, group.id, 'panel1', 'right');
|
||||
|
||||
expect(dockview.size).toBe(2);
|
||||
expect(dockview.totalPanels).toBe(2);
|
||||
@ -489,8 +488,8 @@ describe('dockviewComponent', () => {
|
||||
|
||||
expect(viewQuery.length).toBe(1);
|
||||
|
||||
const group = dockview.getGroupPanel('panel1').group;
|
||||
dockview.moveGroupOrPanel(group, group.id, 'panel1', Position.Right);
|
||||
const group = dockview.getGroupPanel('panel1')!.group;
|
||||
dockview.moveGroupOrPanel(group, group.id, 'panel1', 'right');
|
||||
|
||||
viewQuery = container.querySelectorAll(
|
||||
'.branch-node > .split-view-container > .view-container > .view'
|
||||
@ -975,7 +974,7 @@ describe('dockviewComponent', () => {
|
||||
panel2.group!,
|
||||
panel5.group!.id,
|
||||
panel5.id,
|
||||
Position.Center
|
||||
'center'
|
||||
);
|
||||
expect(events).toEqual([
|
||||
{ type: 'REMOVE_PANEL', panel: panel5 },
|
||||
@ -994,7 +993,7 @@ describe('dockviewComponent', () => {
|
||||
panel2.group!,
|
||||
panel4.group!.id,
|
||||
panel4.id,
|
||||
Position.Center
|
||||
'center'
|
||||
);
|
||||
|
||||
expect(events).toEqual([
|
||||
@ -1314,7 +1313,7 @@ describe('dockviewComponent', () => {
|
||||
panel1.group,
|
||||
panel2.group.id,
|
||||
'panel2',
|
||||
Position.Left
|
||||
'left'
|
||||
);
|
||||
|
||||
expect(panel1Spy).not.toHaveBeenCalled();
|
||||
@ -1355,7 +1354,7 @@ describe('dockviewComponent', () => {
|
||||
panel1.group,
|
||||
panel2.group.id,
|
||||
'panel2',
|
||||
Position.Center
|
||||
'center'
|
||||
);
|
||||
|
||||
expect(panel1Spy).not.toHaveBeenCalled();
|
||||
@ -1394,7 +1393,7 @@ describe('dockviewComponent', () => {
|
||||
panel1.group,
|
||||
panel1.group.id,
|
||||
'panel1',
|
||||
Position.Center,
|
||||
'center',
|
||||
0
|
||||
);
|
||||
|
||||
@ -1515,6 +1514,53 @@ describe('dockviewComponent', () => {
|
||||
expect(panel2Spy).toBeCalledTimes(1);
|
||||
});
|
||||
|
||||
test('move entire group into another group', () => {
|
||||
const container = document.createElement('div');
|
||||
|
||||
const dockview = new DockviewComponent(container, {
|
||||
components: { default: PanelContentPartTest },
|
||||
});
|
||||
|
||||
dockview.layout(500, 1000);
|
||||
|
||||
const panel1 = dockview.addPanel({
|
||||
id: 'panel1',
|
||||
component: 'default',
|
||||
tabComponent: 'default',
|
||||
});
|
||||
const panel2 = dockview.addPanel({
|
||||
id: 'panel2',
|
||||
component: 'default',
|
||||
tabComponent: 'default',
|
||||
position: {
|
||||
referencePanel: panel1,
|
||||
},
|
||||
});
|
||||
const panel3 = dockview.addPanel({
|
||||
id: 'panel3',
|
||||
component: 'default',
|
||||
tabComponent: 'default',
|
||||
position: {
|
||||
referencePanel: panel1,
|
||||
direction: 'right',
|
||||
},
|
||||
});
|
||||
|
||||
const panel1Spy = jest.spyOn(panel1.group, 'dispose');
|
||||
|
||||
expect(dockview.groups.length).toBe(2);
|
||||
|
||||
dockview.moveGroupOrPanel(
|
||||
panel3.group,
|
||||
panel1.group.id,
|
||||
undefined,
|
||||
'center'
|
||||
);
|
||||
|
||||
expect(dockview.groups.length).toBe(1);
|
||||
expect(panel1Spy).toBeCalledTimes(1);
|
||||
});
|
||||
|
||||
test('fromJSON events should still fire', () => {
|
||||
jest.useFakeTimers();
|
||||
|
||||
@ -1635,8 +1681,6 @@ describe('dockviewComponent', () => {
|
||||
|
||||
jest.runAllTimers();
|
||||
|
||||
console.log(activePanel.map((_) => _?.id).join(' '));
|
||||
|
||||
expect(addGroup.length).toBe(4);
|
||||
expect(removeGroup.length).toBe(0);
|
||||
expect(activeGroup.length).toBe(1);
|
||||
@ -1973,4 +2017,379 @@ describe('dockviewComponent', () => {
|
||||
// load a layout with a default tab identifier when react default is present
|
||||
|
||||
// load a layout with invialid panel identifier
|
||||
|
||||
test('orthogonal realigment #1', () => {
|
||||
const container = document.createElement('div');
|
||||
|
||||
const dockview = new DockviewComponent(container, {
|
||||
components: {
|
||||
default: PanelContentPartTest,
|
||||
},
|
||||
tabComponents: {
|
||||
test_tab_id: PanelTabPartTest,
|
||||
},
|
||||
orientation: Orientation.HORIZONTAL,
|
||||
});
|
||||
dockview.deserializer = new ReactPanelDeserialzier(dockview);
|
||||
|
||||
expect(dockview.orientation).toBe(Orientation.HORIZONTAL);
|
||||
|
||||
dockview.fromJSON({
|
||||
activeGroup: 'group-1',
|
||||
grid: {
|
||||
root: {
|
||||
type: 'branch',
|
||||
data: [
|
||||
{
|
||||
type: 'leaf',
|
||||
data: {
|
||||
views: ['panel1'],
|
||||
id: 'group-1',
|
||||
activeView: 'panel1',
|
||||
},
|
||||
size: 500,
|
||||
},
|
||||
],
|
||||
size: 1000,
|
||||
},
|
||||
height: 1000,
|
||||
width: 1000,
|
||||
orientation: Orientation.VERTICAL,
|
||||
},
|
||||
panels: {
|
||||
panel1: {
|
||||
id: 'panel1',
|
||||
view: { content: { id: 'default' } },
|
||||
title: 'panel1',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(dockview.orientation).toBe(Orientation.VERTICAL);
|
||||
|
||||
dockview.addPanel({
|
||||
id: 'panel2',
|
||||
component: 'default',
|
||||
position: {
|
||||
direction: 'left',
|
||||
},
|
||||
});
|
||||
|
||||
expect(dockview.orientation).toBe(Orientation.HORIZONTAL);
|
||||
|
||||
expect(JSON.parse(JSON.stringify(dockview.toJSON()))).toEqual({
|
||||
activeGroup: '1',
|
||||
grid: {
|
||||
root: {
|
||||
type: 'branch',
|
||||
data: [
|
||||
{
|
||||
type: 'leaf',
|
||||
data: {
|
||||
views: ['panel2'],
|
||||
id: '1',
|
||||
activeView: 'panel2',
|
||||
},
|
||||
size: 500,
|
||||
},
|
||||
{
|
||||
type: 'leaf',
|
||||
data: {
|
||||
views: ['panel1'],
|
||||
id: 'group-1',
|
||||
activeView: 'panel1',
|
||||
},
|
||||
size: 1000,
|
||||
},
|
||||
],
|
||||
size: 1000,
|
||||
},
|
||||
height: 1000,
|
||||
width: 1000,
|
||||
orientation: Orientation.HORIZONTAL,
|
||||
},
|
||||
panels: {
|
||||
panel1: {
|
||||
id: 'panel1',
|
||||
view: { content: { id: 'default' } },
|
||||
title: 'panel1',
|
||||
},
|
||||
panel2: {
|
||||
id: 'panel2',
|
||||
view: { content: { id: 'default' } },
|
||||
title: 'panel2',
|
||||
},
|
||||
},
|
||||
options: {},
|
||||
});
|
||||
});
|
||||
|
||||
test('orthogonal realigment #2', () => {
|
||||
const container = document.createElement('div');
|
||||
|
||||
const dockview = new DockviewComponent(container, {
|
||||
components: {
|
||||
default: PanelContentPartTest,
|
||||
},
|
||||
tabComponents: {
|
||||
test_tab_id: PanelTabPartTest,
|
||||
},
|
||||
orientation: Orientation.HORIZONTAL,
|
||||
});
|
||||
dockview.deserializer = new ReactPanelDeserialzier(dockview);
|
||||
|
||||
expect(dockview.orientation).toBe(Orientation.HORIZONTAL);
|
||||
|
||||
dockview.fromJSON({
|
||||
activeGroup: 'group-1',
|
||||
grid: {
|
||||
root: {
|
||||
type: 'branch',
|
||||
data: [
|
||||
{
|
||||
type: 'leaf',
|
||||
data: {
|
||||
views: ['panel1'],
|
||||
id: 'group-1',
|
||||
activeView: 'panel1',
|
||||
},
|
||||
size: 500,
|
||||
},
|
||||
{
|
||||
type: 'leaf',
|
||||
data: {
|
||||
views: ['panel2'],
|
||||
id: 'group-2',
|
||||
activeView: 'panel2',
|
||||
},
|
||||
size: 500,
|
||||
},
|
||||
],
|
||||
size: 1000,
|
||||
},
|
||||
height: 1000,
|
||||
width: 1000,
|
||||
orientation: Orientation.VERTICAL,
|
||||
},
|
||||
panels: {
|
||||
panel1: {
|
||||
id: 'panel1',
|
||||
view: { content: { id: 'default' } },
|
||||
title: 'panel1',
|
||||
},
|
||||
panel2: {
|
||||
id: 'panel2',
|
||||
view: { content: { id: 'default' } },
|
||||
title: 'panel2',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(dockview.orientation).toBe(Orientation.VERTICAL);
|
||||
|
||||
dockview.addPanel({
|
||||
id: 'panel3',
|
||||
component: 'default',
|
||||
position: {
|
||||
direction: 'left',
|
||||
},
|
||||
});
|
||||
|
||||
expect(dockview.orientation).toBe(Orientation.HORIZONTAL);
|
||||
|
||||
expect(JSON.parse(JSON.stringify(dockview.toJSON()))).toEqual({
|
||||
activeGroup: '1',
|
||||
grid: {
|
||||
root: {
|
||||
type: 'branch',
|
||||
data: [
|
||||
{
|
||||
type: 'leaf',
|
||||
data: {
|
||||
views: ['panel3'],
|
||||
id: '1',
|
||||
activeView: 'panel3',
|
||||
},
|
||||
size: 500,
|
||||
},
|
||||
{
|
||||
type: 'branch',
|
||||
data: [
|
||||
{
|
||||
type: 'leaf',
|
||||
data: {
|
||||
views: ['panel1'],
|
||||
id: 'group-1',
|
||||
activeView: 'panel1',
|
||||
},
|
||||
size: 500,
|
||||
},
|
||||
{
|
||||
type: 'leaf',
|
||||
data: {
|
||||
views: ['panel2'],
|
||||
id: 'group-2',
|
||||
activeView: 'panel2',
|
||||
},
|
||||
size: 500,
|
||||
},
|
||||
],
|
||||
size: 500,
|
||||
},
|
||||
],
|
||||
size: 1000,
|
||||
},
|
||||
height: 1000,
|
||||
width: 1000,
|
||||
orientation: Orientation.HORIZONTAL,
|
||||
},
|
||||
panels: {
|
||||
panel1: {
|
||||
id: 'panel1',
|
||||
view: { content: { id: 'default' } },
|
||||
title: 'panel1',
|
||||
},
|
||||
|
||||
panel2: {
|
||||
id: 'panel2',
|
||||
view: { content: { id: 'default' } },
|
||||
title: 'panel2',
|
||||
},
|
||||
panel3: {
|
||||
id: 'panel3',
|
||||
view: { content: { id: 'default' } },
|
||||
title: 'panel3',
|
||||
},
|
||||
},
|
||||
options: {},
|
||||
});
|
||||
});
|
||||
|
||||
test('orthogonal realigment #3', () => {
|
||||
const container = document.createElement('div');
|
||||
|
||||
const dockview = new DockviewComponent(container, {
|
||||
components: {
|
||||
default: PanelContentPartTest,
|
||||
},
|
||||
tabComponents: {
|
||||
test_tab_id: PanelTabPartTest,
|
||||
},
|
||||
orientation: Orientation.HORIZONTAL,
|
||||
});
|
||||
dockview.deserializer = new ReactPanelDeserialzier(dockview);
|
||||
|
||||
expect(dockview.orientation).toBe(Orientation.HORIZONTAL);
|
||||
|
||||
dockview.fromJSON({
|
||||
activeGroup: 'group-1',
|
||||
grid: {
|
||||
root: {
|
||||
type: 'branch',
|
||||
data: [
|
||||
{
|
||||
type: 'leaf',
|
||||
data: {
|
||||
views: ['panel1'],
|
||||
id: 'group-1',
|
||||
activeView: 'panel1',
|
||||
},
|
||||
size: 500,
|
||||
},
|
||||
],
|
||||
size: 1000,
|
||||
},
|
||||
height: 1000,
|
||||
width: 1000,
|
||||
orientation: Orientation.VERTICAL,
|
||||
},
|
||||
panels: {
|
||||
panel1: {
|
||||
id: 'panel1',
|
||||
view: { content: { id: 'default' } },
|
||||
title: 'panel1',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(dockview.orientation).toBe(Orientation.VERTICAL);
|
||||
|
||||
dockview.addPanel({
|
||||
id: 'panel2',
|
||||
component: 'default',
|
||||
position: {
|
||||
direction: 'above',
|
||||
},
|
||||
});
|
||||
|
||||
dockview.addPanel({
|
||||
id: 'panel3',
|
||||
component: 'default',
|
||||
position: {
|
||||
direction: 'below',
|
||||
},
|
||||
});
|
||||
|
||||
expect(dockview.orientation).toBe(Orientation.VERTICAL);
|
||||
|
||||
expect(JSON.parse(JSON.stringify(dockview.toJSON()))).toEqual({
|
||||
activeGroup: '2',
|
||||
grid: {
|
||||
root: {
|
||||
type: 'branch',
|
||||
data: [
|
||||
{
|
||||
type: 'leaf',
|
||||
data: {
|
||||
views: ['panel2'],
|
||||
id: '1',
|
||||
activeView: 'panel2',
|
||||
},
|
||||
size: 333,
|
||||
},
|
||||
{
|
||||
type: 'leaf',
|
||||
data: {
|
||||
views: ['panel1'],
|
||||
id: 'group-1',
|
||||
activeView: 'panel1',
|
||||
},
|
||||
size: 333,
|
||||
},
|
||||
{
|
||||
type: 'leaf',
|
||||
data: {
|
||||
views: ['panel3'],
|
||||
id: '2',
|
||||
activeView: 'panel3',
|
||||
},
|
||||
size: 334,
|
||||
},
|
||||
],
|
||||
size: 1000,
|
||||
},
|
||||
height: 1000,
|
||||
width: 1000,
|
||||
orientation: Orientation.VERTICAL,
|
||||
},
|
||||
panels: {
|
||||
panel1: {
|
||||
id: 'panel1',
|
||||
view: { content: { id: 'default' } },
|
||||
title: 'panel1',
|
||||
},
|
||||
panel2: {
|
||||
id: 'panel2',
|
||||
view: { content: { id: 'default' } },
|
||||
title: 'panel2',
|
||||
},
|
||||
panel3: {
|
||||
id: 'panel3',
|
||||
view: { content: { id: 'default' } },
|
||||
title: 'panel3',
|
||||
},
|
||||
},
|
||||
options: {},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { DockviewComponent } from '../../dockview/dockviewComponent';
|
||||
import { DockviewApi } from '../../api/component.api';
|
||||
import { IGroupPanelView } from '../../dockview/defaultGroupPanelView';
|
||||
import { DockviewGroupPanel } from '../../dockview/dockviewGroupPanel';
|
||||
import { DockviewPanel } from '../../dockview/dockviewPanel';
|
||||
import { GroupPanel } from '../../groupview/groupviewPanel';
|
||||
|
||||
describe('dockviewGroupPanel', () => {
|
||||
@ -20,7 +20,7 @@ describe('dockviewGroupPanel', () => {
|
||||
const api = new dockviewApiMock();
|
||||
const accessor = new accessorMock();
|
||||
const group = new groupMock();
|
||||
const cut = new DockviewGroupPanel('fake-id', accessor, api, group);
|
||||
const cut = new DockviewPanel('fake-id', accessor, api, group);
|
||||
|
||||
let latestTitle: string | undefined = undefined;
|
||||
|
||||
@ -55,7 +55,7 @@ describe('dockviewGroupPanel', () => {
|
||||
const accessor = new accessorMock();
|
||||
const group = new groupMock();
|
||||
|
||||
const cut = new DockviewGroupPanel('fake-id', accessor, api, group);
|
||||
const cut = new DockviewPanel('fake-id', accessor, api, group);
|
||||
|
||||
const viewMock = jest.fn<IGroupPanelView, []>(() => {
|
||||
return {
|
||||
@ -86,7 +86,7 @@ describe('dockviewGroupPanel', () => {
|
||||
const api = new dockviewApiMock();
|
||||
const accessor = new accessorMock();
|
||||
const group = new groupMock();
|
||||
const cut = new DockviewGroupPanel('fake-id', accessor, api, group);
|
||||
const cut = new DockviewPanel('fake-id', accessor, api, group);
|
||||
|
||||
expect(cut.params).toEqual(undefined);
|
||||
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { GridviewPanelApiImpl } from '../../api/gridviewPanelApi';
|
||||
import { GridviewComponent } from '../../gridview/gridviewComponent';
|
||||
import { GridviewPanel } from '../../gridview/gridviewPanel';
|
||||
import { CompositeDisposable } from '../../lifecycle';
|
||||
@ -6,7 +7,9 @@ import { Orientation } from '../../splitview/core/splitview';
|
||||
|
||||
class TestGridview extends GridviewPanel {
|
||||
constructor(id: string, componentName: string) {
|
||||
super(id, componentName);
|
||||
super(id, componentName, new GridviewPanelApiImpl(id));
|
||||
|
||||
this.api.initialize(this);
|
||||
|
||||
this.element.id = id;
|
||||
}
|
||||
@ -65,7 +68,7 @@ describe('gridview', () => {
|
||||
|
||||
expect(gridview.size).toBe(1);
|
||||
|
||||
const panel1 = gridview.getPanel('panel1');
|
||||
const panel1 = gridview.getPanel('panel1')!;
|
||||
|
||||
gridview.removePanel(panel1);
|
||||
|
||||
@ -101,9 +104,9 @@ describe('gridview', () => {
|
||||
component: 'default',
|
||||
});
|
||||
|
||||
const panel1 = gridview.getPanel('panel1');
|
||||
const panel2 = gridview.getPanel('panel2');
|
||||
const panel3 = gridview.getPanel('panel3');
|
||||
const panel1 = gridview.getPanel('panel1')!;
|
||||
const panel2 = gridview.getPanel('panel2')!;
|
||||
const panel3 = gridview.getPanel('panel3')!;
|
||||
|
||||
expect(panel1.api.isActive).toBeFalsy();
|
||||
expect(panel2.api.isActive).toBeFalsy();
|
||||
@ -192,9 +195,9 @@ describe('gridview', () => {
|
||||
});
|
||||
gridview.layout(800, 400, true);
|
||||
|
||||
const panel1 = gridview.getPanel('panel_1');
|
||||
const panel2 = gridview.getPanel('panel_2');
|
||||
const panel3 = gridview.getPanel('panel_3');
|
||||
const panel1 = gridview.getPanel('panel_1')!;
|
||||
const panel2 = gridview.getPanel('panel_2')!;
|
||||
const panel3 = gridview.getPanel('panel_3')!;
|
||||
|
||||
expect(panel1?.api.isVisible).toBeTruthy();
|
||||
expect(panel1?.api.id).toBe('panel_1');
|
||||
@ -330,7 +333,7 @@ describe('gridview', () => {
|
||||
component: 'default',
|
||||
});
|
||||
|
||||
const panel1 = gridview.getPanel('panel_1');
|
||||
const panel1 = gridview.getPanel('panel_1')!;
|
||||
|
||||
expect(events).toEqual([
|
||||
{
|
||||
@ -349,7 +352,7 @@ describe('gridview', () => {
|
||||
component: 'default',
|
||||
});
|
||||
|
||||
const panel2 = gridview.getPanel('panel_2');
|
||||
const panel2 = gridview.getPanel('panel_2')!;
|
||||
|
||||
expect(events).toEqual([
|
||||
{
|
||||
@ -368,7 +371,7 @@ describe('gridview', () => {
|
||||
component: 'default',
|
||||
});
|
||||
|
||||
const panel3 = gridview.getPanel('panel_3');
|
||||
const panel3 = gridview.getPanel('panel_3')!;
|
||||
|
||||
expect(events).toEqual([
|
||||
{
|
||||
@ -1685,8 +1688,8 @@ describe('gridview', () => {
|
||||
component: 'default',
|
||||
});
|
||||
|
||||
const panel1 = gridview.getPanel('panel1');
|
||||
const panel2 = gridview.getPanel('panel2');
|
||||
const panel1 = gridview.getPanel('panel1')!;
|
||||
const panel2 = gridview.getPanel('panel2')!;
|
||||
|
||||
const panel1Spy = jest.spyOn(panel1, 'dispose');
|
||||
const panel2Spy = jest.spyOn(panel2, 'dispose');
|
||||
@ -1714,8 +1717,8 @@ describe('gridview', () => {
|
||||
component: 'default',
|
||||
});
|
||||
|
||||
const panel1 = gridview.getPanel('panel1');
|
||||
const panel2 = gridview.getPanel('panel2');
|
||||
const panel1 = gridview.getPanel('panel1')!;
|
||||
const panel2 = gridview.getPanel('panel2')!;
|
||||
|
||||
const panel1Spy = jest.spyOn(panel1, 'dispose');
|
||||
const panel2Spy = jest.spyOn(panel2, 'dispose');
|
||||
@ -1743,8 +1746,8 @@ describe('gridview', () => {
|
||||
component: 'default',
|
||||
});
|
||||
|
||||
const panel1 = gridview.getPanel('panel1');
|
||||
const panel2 = gridview.getPanel('panel2');
|
||||
const panel1 = gridview.getPanel('panel1')!;
|
||||
const panel2 = gridview.getPanel('panel2')!;
|
||||
|
||||
const panel1Spy = jest.spyOn(panel1, 'dispose');
|
||||
const panel2Spy = jest.spyOn(panel2, 'dispose');
|
||||
|
@ -4,7 +4,11 @@ import { GroupPanel } from '../../groupview/groupviewPanel';
|
||||
describe('gridviewPanel', () => {
|
||||
test('get panel', () => {
|
||||
const accessorMock = jest.fn<DockviewComponent, []>(() => {
|
||||
return {} as any;
|
||||
return {
|
||||
onDidAddPanel: jest.fn(),
|
||||
onDidRemovePanel: jest.fn(),
|
||||
options: {},
|
||||
} as any;
|
||||
});
|
||||
|
||||
const accessor = new accessorMock();
|
||||
|
@ -225,6 +225,8 @@ describe('groupview', () => {
|
||||
id: 'dockview-1',
|
||||
removePanel: removePanelMock,
|
||||
removeGroup: removeGroupMock,
|
||||
onDidAddPanel: jest.fn(),
|
||||
onDidRemovePanel: jest.fn(),
|
||||
}) as DockviewComponent;
|
||||
|
||||
options = {
|
||||
@ -616,6 +618,8 @@ describe('groupview', () => {
|
||||
showDndOverlay: jest.fn(),
|
||||
},
|
||||
getPanel: jest.fn(),
|
||||
onDidAddPanel: jest.fn(),
|
||||
onDidRemovePanel: jest.fn(),
|
||||
};
|
||||
});
|
||||
const accessor = new accessorMock() as DockviewComponent;
|
||||
@ -671,6 +675,8 @@ describe('groupview', () => {
|
||||
},
|
||||
getPanel: jest.fn(),
|
||||
doSetGroupActive: jest.fn(),
|
||||
onDidAddPanel: jest.fn(),
|
||||
onDidRemovePanel: jest.fn(),
|
||||
};
|
||||
});
|
||||
const accessor = new accessorMock() as DockviewComponent;
|
||||
@ -724,7 +730,7 @@ describe('groupview', () => {
|
||||
).toBe(0);
|
||||
});
|
||||
|
||||
test('that should allow drop when not dropping on self for same component id', () => {
|
||||
test('that should not allow drop when dropping on self for same component id', () => {
|
||||
const accessorMock = jest.fn<Partial<DockviewComponent>, []>(() => {
|
||||
return {
|
||||
id: 'testcomponentid',
|
||||
@ -733,6 +739,8 @@ describe('groupview', () => {
|
||||
},
|
||||
getPanel: jest.fn(),
|
||||
doSetGroupActive: jest.fn(),
|
||||
onDidAddPanel: jest.fn(),
|
||||
onDidRemovePanel: jest.fn(),
|
||||
};
|
||||
});
|
||||
const accessor = new accessorMock() as DockviewComponent;
|
||||
@ -784,7 +792,7 @@ describe('groupview', () => {
|
||||
|
||||
expect(
|
||||
element.getElementsByClassName('drop-target-dropzone').length
|
||||
).toBe(1);
|
||||
).toBe(0);
|
||||
});
|
||||
|
||||
test('that should not allow drop when not dropping for different component id', () => {
|
||||
@ -796,6 +804,8 @@ describe('groupview', () => {
|
||||
},
|
||||
getPanel: jest.fn(),
|
||||
doSetGroupActive: jest.fn(),
|
||||
onDidAddPanel: jest.fn(),
|
||||
onDidRemovePanel: jest.fn(),
|
||||
};
|
||||
});
|
||||
const accessor = new accessorMock() as DockviewComponent;
|
||||
|
@ -12,7 +12,11 @@ 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 {};
|
||||
return {
|
||||
onDidAddPanel: jest.fn(),
|
||||
onDidRemovePanel: jest.fn(),
|
||||
options: {},
|
||||
};
|
||||
});
|
||||
const groupviewMock = jest.fn<Partial<Groupview>, []>(() => {
|
||||
return {
|
||||
@ -31,7 +35,7 @@ describe('tabsContainer', () => {
|
||||
const accessor = new accessorMock() as DockviewComponent;
|
||||
const groupPanel = new groupPanelMock() as GroupPanel;
|
||||
|
||||
const cut = new TabsContainer(accessor, groupPanel, {});
|
||||
const cut = new TabsContainer(accessor, groupPanel);
|
||||
|
||||
const emptySpace = cut.element
|
||||
.getElementsByClassName('void-container')
|
||||
@ -62,6 +66,9 @@ describe('tabsContainer', () => {
|
||||
const accessorMock = jest.fn<Partial<DockviewComponent>, []>(() => {
|
||||
return {
|
||||
id: 'testcomponentid',
|
||||
onDidAddPanel: jest.fn(),
|
||||
onDidRemovePanel: jest.fn(),
|
||||
options: {},
|
||||
};
|
||||
});
|
||||
const groupviewMock = jest.fn<Partial<Groupview>, []>(() => {
|
||||
@ -83,7 +90,7 @@ describe('tabsContainer', () => {
|
||||
const accessor = new accessorMock() as DockviewComponent;
|
||||
const groupPanel = new groupPanelMock() as GroupPanel;
|
||||
|
||||
const cut = new TabsContainer(accessor, groupPanel, {});
|
||||
const cut = new TabsContainer(accessor, groupPanel);
|
||||
|
||||
const emptySpace = cut.element
|
||||
.getElementsByClassName('void-container')
|
||||
@ -125,6 +132,9 @@ describe('tabsContainer', () => {
|
||||
const accessorMock = jest.fn<Partial<DockviewComponent>, []>(() => {
|
||||
return {
|
||||
id: 'testcomponentid',
|
||||
onDidAddPanel: jest.fn(),
|
||||
onDidRemovePanel: jest.fn(),
|
||||
options: {},
|
||||
};
|
||||
});
|
||||
const groupviewMock = jest.fn<Partial<Groupview>, []>(() => {
|
||||
@ -146,7 +156,7 @@ describe('tabsContainer', () => {
|
||||
const accessor = new accessorMock() as DockviewComponent;
|
||||
const groupPanel = new groupPanelMock() as GroupPanel;
|
||||
|
||||
const cut = new TabsContainer(accessor, 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));
|
||||
@ -185,6 +195,9 @@ describe('tabsContainer', () => {
|
||||
const accessorMock = jest.fn<Partial<DockviewComponent>, []>(() => {
|
||||
return {
|
||||
id: 'testcomponentid',
|
||||
onDidAddPanel: jest.fn(),
|
||||
onDidRemovePanel: jest.fn(),
|
||||
options: {},
|
||||
};
|
||||
});
|
||||
const groupviewMock = jest.fn<Partial<Groupview>, []>(() => {
|
||||
@ -206,7 +219,7 @@ describe('tabsContainer', () => {
|
||||
const accessor = new accessorMock() as DockviewComponent;
|
||||
const groupPanel = new groupPanelMock() as GroupPanel;
|
||||
|
||||
const cut = new TabsContainer(accessor, 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));
|
||||
@ -245,6 +258,9 @@ describe('tabsContainer', () => {
|
||||
const accessorMock = jest.fn<Partial<DockviewComponent>, []>(() => {
|
||||
return {
|
||||
id: 'testcomponentid',
|
||||
onDidAddPanel: jest.fn(),
|
||||
onDidRemovePanel: jest.fn(),
|
||||
options: {},
|
||||
};
|
||||
});
|
||||
const groupviewMock = jest.fn<Partial<Groupview>, []>(() => {
|
||||
@ -265,7 +281,7 @@ describe('tabsContainer', () => {
|
||||
const accessor = new accessorMock() as DockviewComponent;
|
||||
const groupPanel = new groupPanelMock() as GroupPanel;
|
||||
|
||||
const cut = new TabsContainer(accessor, 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));
|
||||
|
@ -325,6 +325,10 @@ export class GridviewApi implements CommonApi<SerializedGridview> {
|
||||
}
|
||||
|
||||
export class DockviewApi implements CommonApi<SerializedDockview> {
|
||||
get id(): string {
|
||||
return this.component.id;
|
||||
}
|
||||
|
||||
get width(): number {
|
||||
return this.component.width;
|
||||
}
|
||||
@ -435,8 +439,8 @@ export class DockviewApi implements CommonApi<SerializedDockview> {
|
||||
return this.component.addPanel(options);
|
||||
}
|
||||
|
||||
addEmptyGroup(options?: AddGroupOptions): void {
|
||||
this.component.addEmptyGroup(options);
|
||||
addGroup(options?: AddGroupOptions): IGroupviewPanel {
|
||||
return this.component.addGroup(options);
|
||||
}
|
||||
|
||||
moveToNext(options?: MovementOptions): void {
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { Emitter, Event } from '../events';
|
||||
import { IPanel } from '../panel/types';
|
||||
import { FunctionOrValue } from '../types';
|
||||
import { PanelApiImpl, PanelApi } from './panelApi';
|
||||
|
||||
@ -48,7 +49,7 @@ export class GridviewPanelApiImpl
|
||||
readonly onDidSizeChange: Event<SizeEvent> = this._onDidSizeChange.event;
|
||||
//
|
||||
|
||||
constructor(id: string) {
|
||||
constructor(id: string, panel?: IPanel) {
|
||||
super(id);
|
||||
|
||||
this.addDisposables(
|
||||
@ -56,6 +57,10 @@ export class GridviewPanelApiImpl
|
||||
this._onDidConstraintsChange,
|
||||
this._onDidSizeChange
|
||||
);
|
||||
|
||||
if (panel) {
|
||||
this.initialize(panel);
|
||||
}
|
||||
}
|
||||
|
||||
public setConstraints(value: GridConstraintChangeEvent) {
|
||||
|
@ -71,6 +71,9 @@ export class DockviewPanelApiImpl
|
||||
|
||||
constructor(private panel: IDockviewPanel, group: GroupPanel) {
|
||||
super(panel.id);
|
||||
|
||||
this.initialize(panel);
|
||||
|
||||
this._group = group;
|
||||
|
||||
this.addDisposables(
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { Emitter, Event } from '../events';
|
||||
import { CompositeDisposable } from '../lifecycle';
|
||||
import { CompositeDisposable, MutableDisposable } from '../lifecycle';
|
||||
import { IPanel, Parameters } from '../panel/types';
|
||||
|
||||
export interface FocusEvent {
|
||||
readonly isFocused: boolean;
|
||||
@ -25,6 +26,7 @@ export interface PanelApi {
|
||||
readonly onDidActiveChange: Event<ActiveEvent>;
|
||||
setVisible(isVisible: boolean): void;
|
||||
setActive(): void;
|
||||
updateParameters(parameters: Parameters): void;
|
||||
/**
|
||||
* The id of the panel that would have been assigned when the panel was created
|
||||
*/
|
||||
@ -61,6 +63,8 @@ export class PanelApiImpl extends CompositeDisposable implements PanelApi {
|
||||
private _width = 0;
|
||||
private _height = 0;
|
||||
|
||||
private readonly panelUpdatesDisposable = new MutableDisposable();
|
||||
|
||||
readonly _onDidDimensionChange = new Emitter<PanelDimensionChangeEvent>({
|
||||
replay: true,
|
||||
});
|
||||
@ -94,6 +98,10 @@ export class PanelApiImpl extends CompositeDisposable implements PanelApi {
|
||||
readonly _onActiveChange = new Emitter<void>();
|
||||
readonly onActiveChange: Event<void> = this._onActiveChange.event;
|
||||
//
|
||||
readonly _onUpdateParameters = new Emitter<Parameters>();
|
||||
readonly onUpdateParameters: Event<Parameters> =
|
||||
this._onUpdateParameters.event;
|
||||
//
|
||||
|
||||
get isFocused() {
|
||||
return this._isFocused;
|
||||
@ -118,6 +126,7 @@ export class PanelApiImpl extends CompositeDisposable implements PanelApi {
|
||||
super();
|
||||
|
||||
this.addDisposables(
|
||||
this.panelUpdatesDisposable,
|
||||
this._onDidDimensionChange,
|
||||
this._onDidChangeFocus,
|
||||
this._onDidVisibilityChange,
|
||||
@ -125,6 +134,7 @@ export class PanelApiImpl extends CompositeDisposable implements PanelApi {
|
||||
this._onFocusEvent,
|
||||
this._onActiveChange,
|
||||
this._onVisibilityChange,
|
||||
this._onUpdateParameters,
|
||||
this.onDidFocusChange((event) => {
|
||||
this._isFocused = event.isFocused;
|
||||
}),
|
||||
@ -141,6 +151,18 @@ export class PanelApiImpl extends CompositeDisposable implements PanelApi {
|
||||
);
|
||||
}
|
||||
|
||||
public initialize(panel: IPanel): void {
|
||||
this.panelUpdatesDisposable.value = this._onUpdateParameters.event(
|
||||
(parameters) => {
|
||||
panel.update({
|
||||
params: {
|
||||
params: parameters,
|
||||
},
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
setVisible(isVisible: boolean) {
|
||||
this._onVisibilityChange.fire({ isVisible });
|
||||
}
|
||||
@ -149,6 +171,10 @@ export class PanelApiImpl extends CompositeDisposable implements PanelApi {
|
||||
this._onActiveChange.fire();
|
||||
}
|
||||
|
||||
updateParameters(parameters: Parameters): void {
|
||||
this._onUpdateParameters.fire(parameters);
|
||||
}
|
||||
|
||||
dispose() {
|
||||
super.dispose();
|
||||
}
|
||||
|
@ -6,16 +6,11 @@ export interface IDragAndDropObserverCallbacks {
|
||||
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;
|
||||
private target: EventTarget | null = null;
|
||||
|
||||
constructor(
|
||||
private element: HTMLElement,
|
||||
@ -28,28 +23,37 @@ export class DragAndDropObserver extends CompositeDisposable {
|
||||
|
||||
private registerListeners(): void {
|
||||
this.addDisposables(
|
||||
addDisposableListener(this.element, 'dragenter', (e: DragEvent) => {
|
||||
this.counter++;
|
||||
|
||||
this.callbacks.onDragEnter(e);
|
||||
})
|
||||
addDisposableListener(
|
||||
this.element,
|
||||
'dragenter',
|
||||
(e: DragEvent) => {
|
||||
this.target = e.target;
|
||||
this.callbacks.onDragEnter(e);
|
||||
},
|
||||
true
|
||||
)
|
||||
);
|
||||
|
||||
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)
|
||||
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);
|
||||
}
|
||||
})
|
||||
if (this.callbacks.onDragOver) {
|
||||
this.callbacks.onDragOver(e);
|
||||
}
|
||||
},
|
||||
true
|
||||
)
|
||||
);
|
||||
|
||||
this.addDisposables(
|
||||
addDisposableListener(this.element, 'dragleave', (e: DragEvent) => {
|
||||
this.counter--;
|
||||
if (this.target === e.target) {
|
||||
this.target = null;
|
||||
|
||||
if (this.counter === 0) {
|
||||
this.callbacks.onDragLeave(e);
|
||||
}
|
||||
})
|
||||
@ -57,14 +61,13 @@ export class DragAndDropObserver extends CompositeDisposable {
|
||||
|
||||
this.addDisposables(
|
||||
addDisposableListener(this.element, 'dragend', (e: DragEvent) => {
|
||||
this.counter = 0;
|
||||
this.target = null;
|
||||
this.callbacks.onDragEnd(e);
|
||||
})
|
||||
);
|
||||
|
||||
this.addDisposables(
|
||||
addDisposableListener(this.element, 'drop', (e: DragEvent) => {
|
||||
this.counter = 0;
|
||||
this.callbacks.onDrop(e);
|
||||
})
|
||||
);
|
||||
|
@ -19,22 +19,6 @@
|
||||
will-change: transform;
|
||||
pointer-events: none;
|
||||
|
||||
&.left {
|
||||
transform: translateX(-25%) scaleX(0.5)
|
||||
}
|
||||
|
||||
&.right {
|
||||
transform: translateX(25%) scaleX(0.5)
|
||||
}
|
||||
|
||||
&.top {
|
||||
transform: translateY(-25%) scaleY(0.5);
|
||||
}
|
||||
|
||||
&.bottom {
|
||||
transform: translateY(25%) scaleY(0.5);
|
||||
}
|
||||
|
||||
&.small-top {
|
||||
border-top: 1px solid var(--dv-drag-over-border-color);
|
||||
}
|
||||
|
@ -2,23 +2,36 @@ import { toggleClass } from '../dom';
|
||||
import { Emitter, Event } from '../events';
|
||||
import { CompositeDisposable } from '../lifecycle';
|
||||
import { DragAndDropObserver } from './dnd';
|
||||
import { clamp } from '../math';
|
||||
import { Direction } from '../gridview/baseComponentGridview';
|
||||
|
||||
export enum Position {
|
||||
Top = 'Top',
|
||||
Left = 'Left',
|
||||
Bottom = 'Bottom',
|
||||
Right = 'Right',
|
||||
Center = 'Center',
|
||||
function numberOrFallback(maybeNumber: any, fallback: number): number {
|
||||
return typeof maybeNumber === 'number' ? maybeNumber : fallback;
|
||||
}
|
||||
|
||||
export type Quadrant = 'top' | 'bottom' | 'left' | 'right';
|
||||
export function directionToPosition(direction: Direction): Position {
|
||||
switch (direction) {
|
||||
case 'above':
|
||||
return 'top';
|
||||
case 'below':
|
||||
return 'bottom';
|
||||
case 'left':
|
||||
return 'left';
|
||||
case 'right':
|
||||
return 'right';
|
||||
case 'within':
|
||||
return 'center';
|
||||
default:
|
||||
throw new Error(`invalid direction '${direction}'`);
|
||||
}
|
||||
}
|
||||
|
||||
export interface DroptargetEvent {
|
||||
position: Position;
|
||||
nativeEvent: DragEvent;
|
||||
readonly position: Position;
|
||||
readonly nativeEvent: DragEvent;
|
||||
}
|
||||
|
||||
export type DropTargetDirections = 'vertical' | 'horizontal' | 'all' | 'none';
|
||||
export type Position = 'top' | 'bottom' | 'left' | 'right' | 'center';
|
||||
|
||||
function isBooleanValue(
|
||||
canDisplayOverlay: CanDisplayOverlay
|
||||
@ -28,7 +41,7 @@ function isBooleanValue(
|
||||
|
||||
export type CanDisplayOverlay =
|
||||
| boolean
|
||||
| ((dragEvent: DragEvent, state: Quadrant | null) => boolean);
|
||||
| ((dragEvent: DragEvent, state: Position) => boolean);
|
||||
|
||||
export class Droptarget extends CompositeDisposable {
|
||||
private target: HTMLElement | undefined;
|
||||
@ -38,27 +51,31 @@ export class Droptarget extends CompositeDisposable {
|
||||
private readonly _onDrop = new Emitter<DroptargetEvent>();
|
||||
readonly onDrop: Event<DroptargetEvent> = this._onDrop.event;
|
||||
|
||||
get state() {
|
||||
get state(): Position | undefined {
|
||||
return this._state;
|
||||
}
|
||||
|
||||
set validOverlays(value: DropTargetDirections) {
|
||||
this.options.validOverlays = value;
|
||||
}
|
||||
|
||||
set canDisplayOverlay(value: CanDisplayOverlay) {
|
||||
this.options.canDisplayOverlay = value;
|
||||
}
|
||||
|
||||
constructor(
|
||||
private readonly element: HTMLElement,
|
||||
private readonly options: {
|
||||
canDisplayOverlay: CanDisplayOverlay;
|
||||
validOverlays: DropTargetDirections;
|
||||
acceptedTargetZones: Position[];
|
||||
overlayModel?: {
|
||||
size?: { value: number; type: 'pixels' | 'percentage' };
|
||||
activationSize?: {
|
||||
value: number;
|
||||
type: 'pixels' | 'percentage';
|
||||
};
|
||||
};
|
||||
}
|
||||
) {
|
||||
super();
|
||||
|
||||
// use a set to take advantage of #<set>.has
|
||||
const acceptedTargetZonesSet = new Set(
|
||||
this.options.acceptedTargetZones
|
||||
);
|
||||
|
||||
this.addDisposables(
|
||||
this._onDrop,
|
||||
new DragAndDropObserver(this.element, {
|
||||
@ -71,17 +88,25 @@ export class Droptarget extends CompositeDisposable {
|
||||
return; // avoid div!0
|
||||
}
|
||||
|
||||
const x = e.offsetX;
|
||||
const y = e.offsetY;
|
||||
const xp = (100 * x) / width;
|
||||
const yp = (100 * y) / height;
|
||||
const rect = (
|
||||
e.currentTarget as HTMLElement
|
||||
).getBoundingClientRect();
|
||||
const x = e.clientX - rect.left;
|
||||
const y = e.clientY - rect.top;
|
||||
|
||||
const quadrant = this.calculateQuadrant(
|
||||
this.options.validOverlays,
|
||||
xp,
|
||||
yp
|
||||
acceptedTargetZonesSet,
|
||||
x,
|
||||
y,
|
||||
width,
|
||||
height
|
||||
);
|
||||
|
||||
if (quadrant === null) {
|
||||
this.removeDropTarget();
|
||||
return;
|
||||
}
|
||||
|
||||
if (isBooleanValue(this.options.canDisplayOverlay)) {
|
||||
if (!this.options.canDisplayOverlay) {
|
||||
return;
|
||||
@ -95,14 +120,14 @@ export class Droptarget extends CompositeDisposable {
|
||||
this.target.className = 'drop-target-dropzone';
|
||||
this.overlay = document.createElement('div');
|
||||
this.overlay.className = 'drop-target-selection';
|
||||
this._state = Position.Center;
|
||||
this._state = 'center';
|
||||
this.target.appendChild(this.overlay);
|
||||
|
||||
this.element.classList.add('drop-target');
|
||||
this.element.append(this.target);
|
||||
}
|
||||
|
||||
if (this.options.validOverlays === 'none') {
|
||||
if (this.options.acceptedTargetZones.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -110,10 +135,7 @@ export class Droptarget extends CompositeDisposable {
|
||||
return;
|
||||
}
|
||||
|
||||
const isSmallX = width < 100;
|
||||
const isSmallY = height < 100;
|
||||
|
||||
this.toggleClasses(quadrant, isSmallX, isSmallY);
|
||||
this.toggleClasses(quadrant, width, height);
|
||||
|
||||
this.setState(quadrant);
|
||||
},
|
||||
@ -139,28 +161,69 @@ export class Droptarget extends CompositeDisposable {
|
||||
);
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
public dispose(): void {
|
||||
this.removeDropTarget();
|
||||
}
|
||||
|
||||
private toggleClasses(
|
||||
quadrant: Quadrant | null,
|
||||
isSmallX: boolean,
|
||||
isSmallY: boolean
|
||||
) {
|
||||
quadrant: Position,
|
||||
width: number,
|
||||
height: number
|
||||
): void {
|
||||
if (!this.overlay) {
|
||||
return;
|
||||
}
|
||||
|
||||
const isSmallX = width < 100;
|
||||
const isSmallY = height < 100;
|
||||
|
||||
const isLeft = quadrant === 'left';
|
||||
const isRight = quadrant === 'right';
|
||||
const isTop = quadrant === 'top';
|
||||
const isBottom = quadrant === 'bottom';
|
||||
|
||||
toggleClass(this.overlay, 'right', !isSmallX && isRight);
|
||||
toggleClass(this.overlay, 'left', !isSmallX && isLeft);
|
||||
toggleClass(this.overlay, 'top', !isSmallY && isTop);
|
||||
toggleClass(this.overlay, 'bottom', !isSmallY && isBottom);
|
||||
const rightClass = !isSmallX && isRight;
|
||||
const leftClass = !isSmallX && isLeft;
|
||||
const topClass = !isSmallY && isTop;
|
||||
const bottomClass = !isSmallY && isBottom;
|
||||
|
||||
let size = 0.5;
|
||||
|
||||
if (this.options.overlayModel?.size?.type === 'percentage') {
|
||||
size = clamp(this.options.overlayModel.size.value, 0, 100) / 100;
|
||||
}
|
||||
|
||||
if (this.options.overlayModel?.size?.type === 'pixels') {
|
||||
if (rightClass || leftClass) {
|
||||
size =
|
||||
clamp(0, this.options.overlayModel.size.value, width) /
|
||||
width;
|
||||
}
|
||||
if (topClass || bottomClass) {
|
||||
size =
|
||||
clamp(0, this.options.overlayModel.size.value, height) /
|
||||
height;
|
||||
}
|
||||
}
|
||||
|
||||
const translate = (1 - size) / 2;
|
||||
const scale = size;
|
||||
|
||||
let transform: string;
|
||||
|
||||
if (rightClass) {
|
||||
transform = `translateX(${100 * translate}%) scaleX(${scale})`;
|
||||
} else if (leftClass) {
|
||||
transform = `translateX(-${100 * translate}%) scaleX(${scale})`;
|
||||
} else if (topClass) {
|
||||
transform = `translateY(-${100 * translate}%) scaleY(${scale})`;
|
||||
} else if (bottomClass) {
|
||||
transform = `translateY(${100 * translate}%) scaleY(${scale})`;
|
||||
} else {
|
||||
transform = '';
|
||||
}
|
||||
|
||||
this.overlay.style.transform = transform;
|
||||
|
||||
toggleClass(this.overlay, 'small-right', isSmallX && isRight);
|
||||
toggleClass(this.overlay, 'small-left', isSmallX && isLeft);
|
||||
@ -168,60 +231,61 @@ export class Droptarget extends CompositeDisposable {
|
||||
toggleClass(this.overlay, 'small-bottom', isSmallY && isBottom);
|
||||
}
|
||||
|
||||
private setState(quadrant: Quadrant | null) {
|
||||
private setState(quadrant: Position): void {
|
||||
switch (quadrant) {
|
||||
case 'top':
|
||||
this._state = Position.Top;
|
||||
this._state = 'top';
|
||||
break;
|
||||
case 'left':
|
||||
this._state = Position.Left;
|
||||
this._state = 'left';
|
||||
break;
|
||||
case 'bottom':
|
||||
this._state = Position.Bottom;
|
||||
this._state = 'bottom';
|
||||
break;
|
||||
case 'right':
|
||||
this._state = Position.Right;
|
||||
this._state = 'right';
|
||||
break;
|
||||
default:
|
||||
this._state = Position.Center;
|
||||
case 'center':
|
||||
this._state = 'center';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private calculateQuadrant(
|
||||
overlayType: DropTargetDirections,
|
||||
xp: number,
|
||||
yp: number
|
||||
): Quadrant | null {
|
||||
switch (overlayType) {
|
||||
case 'all':
|
||||
if (xp < 20) {
|
||||
return 'left';
|
||||
}
|
||||
if (xp > 80) {
|
||||
return 'right';
|
||||
}
|
||||
if (yp < 20) {
|
||||
return 'top';
|
||||
}
|
||||
if (yp > 80) {
|
||||
return 'bottom';
|
||||
}
|
||||
break;
|
||||
case 'vertical':
|
||||
if (yp < 50) {
|
||||
return 'top';
|
||||
}
|
||||
return 'bottom';
|
||||
overlayType: Set<Position>,
|
||||
x: number,
|
||||
y: number,
|
||||
width: number,
|
||||
height: number
|
||||
): Position | null {
|
||||
const isPercentage =
|
||||
this.options.overlayModel?.activationSize === undefined ||
|
||||
this.options.overlayModel?.activationSize?.type === 'percentage';
|
||||
|
||||
case 'horizontal':
|
||||
if (xp < 50) {
|
||||
return 'left';
|
||||
}
|
||||
return 'right';
|
||||
const value = numberOrFallback(
|
||||
this.options?.overlayModel?.activationSize?.value,
|
||||
20
|
||||
);
|
||||
|
||||
if (isPercentage) {
|
||||
return calculateQuadrantAsPercentage(
|
||||
overlayType,
|
||||
x,
|
||||
y,
|
||||
width,
|
||||
height,
|
||||
value
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
return calculateQuadrantAsPixels(
|
||||
overlayType,
|
||||
x,
|
||||
y,
|
||||
width,
|
||||
height,
|
||||
value
|
||||
);
|
||||
}
|
||||
|
||||
private removeDropTarget() {
|
||||
@ -229,7 +293,67 @@ export class Droptarget extends CompositeDisposable {
|
||||
this._state = undefined;
|
||||
this.element.removeChild(this.target);
|
||||
this.target = undefined;
|
||||
this.overlay = undefined;
|
||||
this.element.classList.remove('drop-target');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function calculateQuadrantAsPercentage(
|
||||
overlayType: Set<Position>,
|
||||
x: number,
|
||||
y: number,
|
||||
width: number,
|
||||
height: number,
|
||||
threshold: number
|
||||
): Position | null {
|
||||
const xp = (100 * x) / width;
|
||||
const yp = (100 * y) / height;
|
||||
|
||||
if (overlayType.has('left') && xp < threshold) {
|
||||
return 'left';
|
||||
}
|
||||
if (overlayType.has('right') && xp > 100 - threshold) {
|
||||
return 'right';
|
||||
}
|
||||
if (overlayType.has('top') && yp < threshold) {
|
||||
return 'top';
|
||||
}
|
||||
if (overlayType.has('bottom') && yp > 100 - threshold) {
|
||||
return 'bottom';
|
||||
}
|
||||
|
||||
if (!overlayType.has('center')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return 'center';
|
||||
}
|
||||
|
||||
export function calculateQuadrantAsPixels(
|
||||
overlayType: Set<Position>,
|
||||
x: number,
|
||||
y: number,
|
||||
width: number,
|
||||
height: number,
|
||||
threshold: number
|
||||
): Position | null {
|
||||
if (overlayType.has('left') && x < threshold) {
|
||||
return 'left';
|
||||
}
|
||||
if (overlayType.has('right') && x > width - threshold) {
|
||||
return 'right';
|
||||
}
|
||||
if (overlayType.has('top') && y < threshold) {
|
||||
return 'top';
|
||||
}
|
||||
if (overlayType.has('bottom') && y > height - threshold) {
|
||||
return 'bottom';
|
||||
}
|
||||
|
||||
if (!overlayType.has('center')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return 'center';
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -12,7 +12,7 @@ import { Parameters } from '../panel/types';
|
||||
import { IGroupPanelView } from './defaultGroupPanelView';
|
||||
import { DockviewComponent } from './dockviewComponent';
|
||||
|
||||
export class DockviewGroupPanel
|
||||
export class DockviewPanel
|
||||
extends CompositeDisposable
|
||||
implements IDockviewPanel
|
||||
{
|
||||
@ -38,7 +38,7 @@ export class DockviewGroupPanel
|
||||
return this._group;
|
||||
}
|
||||
|
||||
get view() {
|
||||
get view(): IGroupPanelView | undefined {
|
||||
return this._view;
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ import { FrameworkFactory } from '../types';
|
||||
import { DockviewDropTargets } from '../groupview/dnd';
|
||||
import { PanelTransfer } from '../dnd/dataTransfer';
|
||||
import { IGroupControlRenderer } from '../react/dockview/groupControlsRenderer';
|
||||
import { Position } from '../dnd/droptarget';
|
||||
|
||||
export interface GroupPanelFrameworkComponentFactory {
|
||||
content: FrameworkFactory<IContentRenderer>;
|
||||
@ -54,7 +55,8 @@ export interface ViewFactoryData {
|
||||
export interface DockviewDndOverlayEvent {
|
||||
nativeEvent: DragEvent;
|
||||
target: DockviewDropTargets;
|
||||
group: GroupPanel;
|
||||
position: Position;
|
||||
group?: GroupPanel;
|
||||
getData: () => PanelTransfer | undefined;
|
||||
}
|
||||
|
||||
@ -68,6 +70,7 @@ export interface DockviewComponentOptions extends DockviewRenderFunctions {
|
||||
defaultTabComponent?: string;
|
||||
showDndOverlay?: (event: DockviewDndOverlayEvent) => boolean;
|
||||
createGroupControlElement?: (group: GroupPanel) => IGroupControlRenderer;
|
||||
singleTabMode?: 'fullwidth' | 'default';
|
||||
}
|
||||
|
||||
export interface PanelOptions {
|
||||
@ -78,19 +81,81 @@ export interface PanelOptions {
|
||||
title?: string;
|
||||
}
|
||||
|
||||
type RelativePanel = {
|
||||
direction?: Direction;
|
||||
referencePanel: string | IDockviewPanel;
|
||||
};
|
||||
|
||||
type RelativeGroup = {
|
||||
direction?: Direction;
|
||||
referenceGroup: string | GroupPanel;
|
||||
};
|
||||
|
||||
type AbsolutePosition = {
|
||||
direction: Omit<Direction, 'within'>;
|
||||
};
|
||||
|
||||
export type AddPanelPositionOptions =
|
||||
| RelativePanel
|
||||
| RelativeGroup
|
||||
| AbsolutePosition;
|
||||
|
||||
export function isPanelOptionsWithPanel(
|
||||
data: AddPanelPositionOptions
|
||||
): data is RelativePanel {
|
||||
if ((data as RelativePanel).referencePanel) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function isPanelOptionsWithGroup(
|
||||
data: AddPanelPositionOptions
|
||||
): data is RelativeGroup {
|
||||
if ((data as RelativeGroup).referenceGroup) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export interface AddPanelOptions
|
||||
extends Omit<PanelOptions, 'component' | 'tabComponent'> {
|
||||
component: string;
|
||||
tabComponent?: string;
|
||||
position?: {
|
||||
direction?: Direction;
|
||||
referencePanel?: string;
|
||||
};
|
||||
position?: AddPanelPositionOptions;
|
||||
}
|
||||
|
||||
export interface AddGroupOptions {
|
||||
direction?: 'left' | 'right' | 'above' | 'below';
|
||||
referencePanel: string;
|
||||
type AddGroupOptionsWithPanel = {
|
||||
referencePanel: string | IDockviewPanel;
|
||||
direction?: Omit<Direction, 'within'>;
|
||||
};
|
||||
|
||||
type AddGroupOptionsWithGroup = {
|
||||
referenceGroup: string | GroupPanel;
|
||||
direction?: Omit<Direction, 'within'>;
|
||||
};
|
||||
|
||||
export type AddGroupOptions =
|
||||
| AddGroupOptionsWithGroup
|
||||
| AddGroupOptionsWithPanel
|
||||
| AbsolutePosition;
|
||||
|
||||
export function isGroupOptionsWithPanel(
|
||||
data: AddGroupOptions
|
||||
): data is AddGroupOptionsWithPanel {
|
||||
if ((data as AddGroupOptionsWithPanel).referencePanel) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function isGroupOptionsWithGroup(
|
||||
data: AddGroupOptions
|
||||
): data is AddGroupOptionsWithGroup {
|
||||
if ((data as AddGroupOptionsWithGroup).referenceGroup) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export interface MovementOptions2 {
|
||||
|
@ -15,19 +15,19 @@ const nextLayoutId = sequentialNumberGenerator();
|
||||
|
||||
export type Direction = 'left' | 'right' | 'above' | 'below' | 'within';
|
||||
|
||||
export function toTarget(direction: Direction) {
|
||||
export function toTarget(direction: Direction): Position {
|
||||
switch (direction) {
|
||||
case 'left':
|
||||
return Position.Left;
|
||||
return 'left';
|
||||
case 'right':
|
||||
return Position.Right;
|
||||
return 'right';
|
||||
case 'above':
|
||||
return Position.Top;
|
||||
return 'top';
|
||||
case 'below':
|
||||
return Position.Bottom;
|
||||
return 'bottom';
|
||||
case 'within':
|
||||
default:
|
||||
return Position.Center;
|
||||
return 'center';
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6,7 +6,7 @@ import {
|
||||
PanelInitParameters,
|
||||
IPanel,
|
||||
} from '../panel/types';
|
||||
import { PanelApiImpl } from '../api/panelApi';
|
||||
import { PanelApi, PanelApiImpl } from '../api/panelApi';
|
||||
|
||||
export interface BasePanelViewState {
|
||||
id: string;
|
||||
@ -14,7 +14,7 @@ export interface BasePanelViewState {
|
||||
params?: Record<string, any>;
|
||||
}
|
||||
|
||||
export interface BasePanelViewExported<T extends PanelApiImpl> {
|
||||
export interface BasePanelViewExported<T extends PanelApi> {
|
||||
readonly id: string;
|
||||
readonly api: T;
|
||||
readonly width: number;
|
||||
|
@ -9,13 +9,13 @@ import {
|
||||
Orientation,
|
||||
Sizing,
|
||||
} from '../splitview/core/splitview';
|
||||
import { Position } from '../dnd/droptarget';
|
||||
import { tail } from '../array';
|
||||
import { LeafNode } from './leafNode';
|
||||
import { BranchNode } from './branchNode';
|
||||
import { Node } from './types';
|
||||
import { Emitter, Event } from '../events';
|
||||
import { IDisposable, MutableDisposable } from '../lifecycle';
|
||||
import { Position } from '../dnd/droptarget';
|
||||
|
||||
function findLeaf(candiateNode: Node, last: boolean): LeafNode {
|
||||
if (candiateNode instanceof LeafNode) {
|
||||
@ -132,22 +132,19 @@ export function getRelativeLocation(
|
||||
const [rest, _index] = tail(location);
|
||||
let index = _index;
|
||||
|
||||
if (direction === Position.Right || direction === Position.Bottom) {
|
||||
if (direction === 'right' || direction === 'bottom') {
|
||||
index += 1;
|
||||
}
|
||||
|
||||
return [...rest, index];
|
||||
} else {
|
||||
const index =
|
||||
direction === Position.Right || direction === Position.Bottom
|
||||
? 1
|
||||
: 0;
|
||||
const index = direction === 'right' || direction === 'bottom' ? 1 : 0;
|
||||
return [...location, index];
|
||||
}
|
||||
}
|
||||
|
||||
export function getDirectionOrientation(direction: Position): Orientation {
|
||||
return direction === Position.Top || direction === Position.Bottom
|
||||
return direction === 'top' || direction === 'bottom'
|
||||
? Orientation.VERTICAL
|
||||
: Orientation.HORIZONTAL;
|
||||
}
|
||||
@ -276,6 +273,10 @@ export class Gridview implements IDisposable {
|
||||
readonly onDidChange: Event<{ size?: number; orthogonalSize?: number }> =
|
||||
this._onDidChange.event;
|
||||
|
||||
public get length(): number {
|
||||
return this._root ? this._root.children.length : 0;
|
||||
}
|
||||
|
||||
public serialize() {
|
||||
const root = serializeBranchNode(this.getView(), this.orientation);
|
||||
|
||||
@ -410,6 +411,43 @@ export class Gridview implements IDisposable {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* If the root is orientated as a VERTICAL node then nest the existing root within a new HORIZIONTAL root node
|
||||
* If the root is orientated as a HORIZONTAL node then nest the existing root within a new VERITCAL root node
|
||||
*/
|
||||
public insertOrthogonalSplitviewAtRoot(): void {
|
||||
if (!this._root) {
|
||||
return;
|
||||
}
|
||||
|
||||
const oldRoot = this.root;
|
||||
oldRoot.element.remove();
|
||||
|
||||
this._root = new BranchNode(
|
||||
orthogonal(oldRoot.orientation),
|
||||
this.proportionalLayout,
|
||||
this.styles,
|
||||
this.root.orthogonalSize,
|
||||
this.root.size
|
||||
);
|
||||
|
||||
if (oldRoot.children.length === 1) {
|
||||
// can remove one level of redundant branching if there is only a single child
|
||||
const childReference = oldRoot.children[0];
|
||||
oldRoot.removeChild(0); // remove to prevent disposal when disposing of unwanted root
|
||||
oldRoot.dispose();
|
||||
this._root.addChild(childReference, Sizing.Distribute, 0);
|
||||
} else {
|
||||
this._root.addChild(oldRoot, Sizing.Distribute, 0);
|
||||
}
|
||||
|
||||
this.element.appendChild(this._root.element);
|
||||
|
||||
this.disposable.value = this._root.onDidChange((e) => {
|
||||
this._onDidChange.fire(e);
|
||||
});
|
||||
}
|
||||
|
||||
public next(location: number[]) {
|
||||
return this.progmaticSelect(location);
|
||||
}
|
||||
|
@ -3,7 +3,6 @@ import {
|
||||
SerializedGridObject,
|
||||
getGridLocation,
|
||||
} from './gridview';
|
||||
import { Position } from '../dnd/droptarget';
|
||||
import { tail, sequenceEquals } from '../array';
|
||||
import { CompositeDisposable } from '../lifecycle';
|
||||
import { IPanelDeserializer } from '../dockview/deserializer';
|
||||
@ -25,6 +24,7 @@ import { BaseComponentOptions } from '../panel/types';
|
||||
import { Orientation, Sizing } from '../splitview/core/splitview';
|
||||
import { createComponent } from '../panel/componentFactory';
|
||||
import { Emitter, Event } from '../events';
|
||||
import { Position } from '../dnd/droptarget';
|
||||
|
||||
export interface SerializedGridview {
|
||||
grid: {
|
||||
@ -265,7 +265,7 @@ export class GridviewComponent
|
||||
}
|
||||
|
||||
const target = toTarget(options.direction);
|
||||
if (target === Position.Center) {
|
||||
if (target === 'center') {
|
||||
throw new Error(`${target} not supported as an option`);
|
||||
} else {
|
||||
const location = getGridLocation(referenceGroup.element);
|
||||
@ -294,7 +294,7 @@ export class GridviewComponent
|
||||
}
|
||||
|
||||
const target = toTarget(options.position.direction);
|
||||
if (target === Position.Center) {
|
||||
if (target === 'center') {
|
||||
throw new Error(`${target} not supported as an option`);
|
||||
} else {
|
||||
const location = getGridLocation(referenceGroup.element);
|
||||
|
@ -9,7 +9,10 @@ import {
|
||||
BasePanelViewExported,
|
||||
BasePanelViewState,
|
||||
} from './basePanelView';
|
||||
import { GridviewPanelApiImpl } from '../api/gridviewPanelApi';
|
||||
import {
|
||||
GridviewPanelApi,
|
||||
GridviewPanelApiImpl,
|
||||
} from '../api/gridviewPanelApi';
|
||||
import { LayoutPriority } from '../splitview/core/splitview';
|
||||
import { Emitter, Event } from '../events';
|
||||
import { IViewSize } from './gridview';
|
||||
@ -26,7 +29,7 @@ export interface GridviewInitParameters extends PanelInitParameters {
|
||||
}
|
||||
|
||||
export interface IGridviewPanel
|
||||
extends BasePanelViewExported<GridviewPanelApiImpl> {
|
||||
extends BasePanelViewExported<GridviewPanelApi> {
|
||||
readonly minimumWidth: number;
|
||||
readonly maximumWidth: number;
|
||||
readonly minimumHeight: number;
|
||||
@ -123,13 +126,11 @@ export abstract class GridviewPanel
|
||||
return this.api.isActive;
|
||||
}
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
component: string,
|
||||
api = new GridviewPanelApiImpl(id)
|
||||
) {
|
||||
constructor(id: string, component: string, api: GridviewPanelApiImpl) {
|
||||
super(id, component, api);
|
||||
|
||||
this.api.initialize(this); // TODO: required to by-pass 'super before this' requirement
|
||||
|
||||
this.addDisposables(
|
||||
this._onDidChange,
|
||||
this.api.onVisibilityChange((event) => {
|
||||
|
@ -2,4 +2,5 @@ export enum DockviewDropTargets {
|
||||
Tab,
|
||||
Panel,
|
||||
TabContainer,
|
||||
Edge,
|
||||
}
|
||||
|
@ -110,7 +110,11 @@ export interface IGroupview extends IDisposable, IGridPanelView {
|
||||
panel?: IDockviewPanel;
|
||||
suppressRoll?: boolean;
|
||||
}): void;
|
||||
canDisplayOverlay(event: DragEvent, target: DockviewDropTargets): boolean;
|
||||
canDisplayOverlay(
|
||||
event: DragEvent,
|
||||
position: Position,
|
||||
target: DockviewDropTargets
|
||||
): boolean;
|
||||
}
|
||||
|
||||
export class Groupview extends CompositeDisposable implements IGroupview {
|
||||
@ -167,6 +171,8 @@ export class Groupview extends CompositeDisposable implements IGroupview {
|
||||
|
||||
set locked(value: boolean) {
|
||||
this._locked = value;
|
||||
|
||||
toggleClass(this.container, 'locked-groupview', value);
|
||||
}
|
||||
|
||||
get isActive(): boolean {
|
||||
@ -226,45 +232,48 @@ export class Groupview extends CompositeDisposable implements IGroupview {
|
||||
private accessor: DockviewComponent,
|
||||
public id: string,
|
||||
private readonly options: GroupOptions,
|
||||
private readonly parent: GroupPanel
|
||||
private readonly groupPanel: GroupPanel
|
||||
) {
|
||||
super();
|
||||
|
||||
this.container.classList.add('groupview');
|
||||
|
||||
this.tabsContainer = new TabsContainer(this.accessor, this.parent, {
|
||||
tabHeight: options.tabHeight,
|
||||
});
|
||||
this.tabsContainer = new TabsContainer(this.accessor, this.groupPanel);
|
||||
|
||||
this.contentContainer = new ContentContainer();
|
||||
|
||||
this.dropTarget = new Droptarget(this.contentContainer.element, {
|
||||
validOverlays: 'all',
|
||||
canDisplayOverlay: (event, quadrant) => {
|
||||
if (this.locked && !quadrant) {
|
||||
acceptedTargetZones: ['top', 'bottom', 'left', 'right', 'center'],
|
||||
canDisplayOverlay: (event, position) => {
|
||||
if (this.locked && position === 'center') {
|
||||
return false;
|
||||
}
|
||||
|
||||
const data = getPanelData();
|
||||
|
||||
if (
|
||||
data &&
|
||||
data.panelId === null &&
|
||||
data.viewId === this.accessor.id &&
|
||||
data.groupId !== this.id
|
||||
) {
|
||||
// prevent dropping on self for group dnd
|
||||
return true;
|
||||
}
|
||||
|
||||
if (data && data.viewId === this.accessor.id) {
|
||||
if (data.groupId === this.id) {
|
||||
if (position === 'center') {
|
||||
// don't allow to drop on self for center position
|
||||
return false;
|
||||
}
|
||||
if (data.panelId === null) {
|
||||
// don't allow group move to drop anywhere on self
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const groupHasOnePanelAndIsActiveDragElement =
|
||||
this._panels.length === 1 && data.groupId === this.id;
|
||||
|
||||
return !groupHasOnePanelAndIsActiveDragElement;
|
||||
}
|
||||
|
||||
return this.canDisplayOverlay(event, DockviewDropTargets.Panel);
|
||||
return this.canDisplayOverlay(
|
||||
event,
|
||||
position,
|
||||
DockviewDropTargets.Panel
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
@ -284,10 +293,10 @@ export class Groupview extends CompositeDisposable implements IGroupview {
|
||||
this._onDidRemovePanel,
|
||||
this._onDidActivePanelChange,
|
||||
this.tabsContainer.onDrop((event) => {
|
||||
this.handleDropEvent(event.event, Position.Center, event.index);
|
||||
this.handleDropEvent(event.event, 'center', event.index);
|
||||
}),
|
||||
this.contentContainer.onDidFocus(() => {
|
||||
this.accessor.doSetGroupActive(this.parent, true);
|
||||
this.accessor.doSetGroupActive(this.groupPanel, true);
|
||||
}),
|
||||
this.contentContainer.onDidBlur(() => {
|
||||
// noop
|
||||
@ -316,12 +325,12 @@ export class Groupview extends CompositeDisposable implements IGroupview {
|
||||
|
||||
if (this.accessor.options.createGroupControlElement) {
|
||||
this._control = this.accessor.options.createGroupControlElement(
|
||||
this.parent
|
||||
this.groupPanel
|
||||
);
|
||||
this.addDisposables(this._control);
|
||||
this._control.init({
|
||||
containerApi: new DockviewApi(this.accessor),
|
||||
api: this.parent.api,
|
||||
api: this.groupPanel.api,
|
||||
});
|
||||
this.tabsContainer.setActionElement(this._control.element);
|
||||
}
|
||||
@ -441,11 +450,11 @@ export class Groupview extends CompositeDisposable implements IGroupview {
|
||||
const skipSetGroupActive = !!options.skipSetGroupActive;
|
||||
|
||||
// ensure the group is updated before we fire any events
|
||||
panel.updateParentGroup(this.parent, true);
|
||||
panel.updateParentGroup(this.groupPanel, true);
|
||||
|
||||
if (this._activePanel === panel) {
|
||||
if (!skipSetGroupActive) {
|
||||
this.accessor.doSetGroupActive(this.parent);
|
||||
this.accessor.doSetGroupActive(this.groupPanel);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@ -457,7 +466,10 @@ export class Groupview extends CompositeDisposable implements IGroupview {
|
||||
}
|
||||
|
||||
if (!skipSetGroupActive) {
|
||||
this.accessor.doSetGroupActive(this.parent, !!options.skipFocus);
|
||||
this.accessor.doSetGroupActive(
|
||||
this.groupPanel,
|
||||
!!options.skipFocus
|
||||
);
|
||||
}
|
||||
|
||||
this.updateContainer();
|
||||
@ -486,7 +498,7 @@ export class Groupview extends CompositeDisposable implements IGroupview {
|
||||
this.doClose(panel);
|
||||
}
|
||||
} else {
|
||||
this.accessor.removeGroup(this.parent);
|
||||
this.accessor.removeGroup(this.groupPanel);
|
||||
}
|
||||
}
|
||||
|
||||
@ -643,7 +655,7 @@ export class Groupview extends CompositeDisposable implements IGroupview {
|
||||
toggleClass(this.container, 'empty', this.isEmpty);
|
||||
|
||||
this.panels.forEach((panel) =>
|
||||
panel.updateParentGroup(this.parent, this.isActive)
|
||||
panel.updateParentGroup(this.groupPanel, this.isActive)
|
||||
);
|
||||
|
||||
if (this.isEmpty && !this.watermark) {
|
||||
@ -658,14 +670,14 @@ export class Groupview extends CompositeDisposable implements IGroupview {
|
||||
|
||||
addDisposableListener(this.watermark.element, 'click', () => {
|
||||
if (!this.isActive) {
|
||||
this.accessor.doSetGroupActive(this.parent);
|
||||
this.accessor.doSetGroupActive(this.groupPanel);
|
||||
}
|
||||
});
|
||||
|
||||
this.tabsContainer.hide();
|
||||
this.contentContainer.element.appendChild(this.watermark.element);
|
||||
|
||||
this.watermark.updateParentGroup(this.parent, true);
|
||||
this.watermark.updateParentGroup(this.groupPanel, true);
|
||||
}
|
||||
if (!this.isEmpty && this.watermark) {
|
||||
this.watermark.element.remove();
|
||||
@ -675,13 +687,18 @@ export class Groupview extends CompositeDisposable implements IGroupview {
|
||||
}
|
||||
}
|
||||
|
||||
canDisplayOverlay(event: DragEvent, target: DockviewDropTargets): boolean {
|
||||
canDisplayOverlay(
|
||||
event: DragEvent,
|
||||
position: Position,
|
||||
target: DockviewDropTargets
|
||||
): boolean {
|
||||
// custom overlay handler
|
||||
if (this.accessor.options.showDndOverlay) {
|
||||
return this.accessor.options.showDndOverlay({
|
||||
nativeEvent: event,
|
||||
target,
|
||||
group: this.accessor.getPanel(this.id)!,
|
||||
position,
|
||||
getData: getPanelData,
|
||||
});
|
||||
}
|
||||
|
@ -8,28 +8,16 @@ import {
|
||||
import { toggleClass } from '../dom';
|
||||
import { IDockviewComponent } from '../dockview/dockviewComponent';
|
||||
import { ITabRenderer } from './types';
|
||||
import { IDockviewPanel } from './groupPanel';
|
||||
import { GroupPanel } from './groupviewPanel';
|
||||
import { DroptargetEvent, Droptarget } from '../dnd/droptarget';
|
||||
import { DockviewDropTargets } from './dnd';
|
||||
import { DragHandler } from '../dnd/abstractDragHandler';
|
||||
|
||||
export enum MouseEventKind {
|
||||
CLICK = 'CLICK',
|
||||
}
|
||||
|
||||
export interface LayoutMouseEvent {
|
||||
readonly kind: MouseEventKind;
|
||||
readonly event: MouseEvent;
|
||||
readonly panel?: IDockviewPanel;
|
||||
readonly tab?: boolean;
|
||||
}
|
||||
|
||||
export interface ITab {
|
||||
readonly panelId: string;
|
||||
readonly element: HTMLElement;
|
||||
setContent: (element: ITabRenderer) => void;
|
||||
onChanged: Event<LayoutMouseEvent>;
|
||||
onChanged: Event<MouseEvent>;
|
||||
onDrop: Event<DroptargetEvent>;
|
||||
setActive(isActive: boolean): void;
|
||||
}
|
||||
@ -39,13 +27,13 @@ export class Tab extends CompositeDisposable implements ITab {
|
||||
private readonly droptarget: Droptarget;
|
||||
private content?: ITabRenderer;
|
||||
|
||||
private readonly _onChanged = new Emitter<LayoutMouseEvent>();
|
||||
readonly onChanged: Event<LayoutMouseEvent> = this._onChanged.event;
|
||||
private readonly _onChanged = new Emitter<MouseEvent>();
|
||||
readonly onChanged: Event<MouseEvent> = this._onChanged.event;
|
||||
|
||||
private readonly _onDropped = new Emitter<DroptargetEvent>();
|
||||
readonly onDrop: Event<DroptargetEvent> = this._onDropped.event;
|
||||
|
||||
public get element() {
|
||||
public get element(): HTMLElement {
|
||||
return this._element;
|
||||
}
|
||||
|
||||
@ -104,20 +92,34 @@ export class Tab extends CompositeDisposable implements ITab {
|
||||
*/
|
||||
event.stopPropagation();
|
||||
|
||||
this._onChanged.fire({ kind: MouseEventKind.CLICK, event });
|
||||
this._onChanged.fire(event);
|
||||
})
|
||||
);
|
||||
|
||||
this.droptarget = new Droptarget(this._element, {
|
||||
validOverlays: 'none',
|
||||
canDisplayOverlay: (event) => {
|
||||
acceptedTargetZones: ['center'],
|
||||
canDisplayOverlay: (event, position) => {
|
||||
if (this.group.locked) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const data = getPanelData();
|
||||
|
||||
if (data && this.accessor.id === data.viewId) {
|
||||
if (
|
||||
data.panelId === null &&
|
||||
data.groupId === this.group.id
|
||||
) {
|
||||
// don't allow group move to drop on self
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.panelId !== data.panelId;
|
||||
}
|
||||
|
||||
return this.group.model.canDisplayOverlay(
|
||||
event,
|
||||
position,
|
||||
DockviewDropTargets.Tab
|
||||
);
|
||||
},
|
||||
@ -130,12 +132,12 @@ export class Tab extends CompositeDisposable implements ITab {
|
||||
);
|
||||
}
|
||||
|
||||
public setActive(isActive: boolean) {
|
||||
public setActive(isActive: boolean): void {
|
||||
toggleClass(this.element, 'active-tab', isActive);
|
||||
toggleClass(this.element, 'inactive-tab', !isActive);
|
||||
}
|
||||
|
||||
public setContent(part: ITabRenderer) {
|
||||
public setContent(part: ITabRenderer): void {
|
||||
if (this.content) {
|
||||
this._element.removeChild(this.content.element);
|
||||
}
|
||||
@ -143,7 +145,7 @@ export class Tab extends CompositeDisposable implements ITab {
|
||||
this._element.appendChild(this.content.element);
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
public dispose(): void {
|
||||
super.dispose();
|
||||
this.droptarget.dispose();
|
||||
}
|
||||
|
@ -10,6 +10,20 @@
|
||||
display: none;
|
||||
}
|
||||
|
||||
&.dv-single-tab.dv-full-width-single-tab {
|
||||
.tabs-container {
|
||||
flex-grow: 1;
|
||||
|
||||
.tab {
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.void-container {
|
||||
flex-grow: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.void-container {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
|
@ -4,11 +4,12 @@ import {
|
||||
IValueDisposable,
|
||||
} from '../../lifecycle';
|
||||
import { addDisposableListener, Emitter, Event } from '../../events';
|
||||
import { ITab, MouseEventKind, Tab } from '../tab';
|
||||
import { ITab, Tab } from '../tab';
|
||||
import { IDockviewPanel } from '../groupPanel';
|
||||
import { DockviewComponent } from '../../dockview/dockviewComponent';
|
||||
import { GroupPanel } from '../groupviewPanel';
|
||||
import { VoidContainer } from './voidContainer';
|
||||
import { toggleClass } from '../../dom';
|
||||
|
||||
export interface TabDropIndexEvent {
|
||||
event: DragEvent;
|
||||
@ -134,8 +135,7 @@ export class TabsContainer
|
||||
|
||||
constructor(
|
||||
private readonly accessor: DockviewComponent,
|
||||
private readonly group: GroupPanel,
|
||||
readonly options: { tabHeight?: number }
|
||||
private readonly group: GroupPanel
|
||||
) {
|
||||
super();
|
||||
|
||||
@ -144,7 +144,34 @@ export class TabsContainer
|
||||
this._element = document.createElement('div');
|
||||
this._element.className = 'tabs-and-actions-container';
|
||||
|
||||
this.height = options.tabHeight;
|
||||
this.height = accessor.options.tabHeight;
|
||||
|
||||
toggleClass(
|
||||
this._element,
|
||||
'dv-full-width-single-tab',
|
||||
this.accessor.options.singleTabMode === 'fullwidth'
|
||||
);
|
||||
|
||||
this.addDisposables(
|
||||
this.accessor.onDidAddPanel((e) => {
|
||||
if (e.api.group === this.group) {
|
||||
toggleClass(
|
||||
this._element,
|
||||
'dv-single-tab',
|
||||
this.size === 1
|
||||
);
|
||||
}
|
||||
}),
|
||||
this.accessor.onDidRemovePanel((e) => {
|
||||
if (e.api.group === this.group) {
|
||||
toggleClass(
|
||||
this._element,
|
||||
'dv-single-tab',
|
||||
this.size === 1
|
||||
);
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
this.actionContainer = document.createElement('div');
|
||||
this.actionContainer.className = 'action-container';
|
||||
@ -242,17 +269,15 @@ export class TabsContainer
|
||||
panel.id === this.group.model.activePanel?.id &&
|
||||
this.group.model.isContentFocused;
|
||||
|
||||
const isLeftClick = event.event.button === 0;
|
||||
const isLeftClick = event.button === 0;
|
||||
|
||||
if (!isLeftClick || event.event.defaultPrevented) {
|
||||
if (!isLeftClick || event.defaultPrevented) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.kind === MouseEventKind.CLICK) {
|
||||
this.group.model.openPanel(panel, {
|
||||
skipFocus: alreadyFocused,
|
||||
});
|
||||
}
|
||||
this.group.model.openPanel(panel, {
|
||||
skipFocus: alreadyFocused,
|
||||
});
|
||||
}),
|
||||
tabToAdd.onDrop((event) => {
|
||||
this._onDrop.fire({
|
||||
|
@ -41,17 +41,26 @@ export class VoidContainer extends CompositeDisposable {
|
||||
const handler = new GroupDragHandler(this._element, accessor.id, group);
|
||||
|
||||
this.voidDropTarget = new Droptarget(this._element, {
|
||||
validOverlays: 'none',
|
||||
canDisplayOverlay: (event) => {
|
||||
acceptedTargetZones: ['center'],
|
||||
canDisplayOverlay: (event, position) => {
|
||||
const data = getPanelData();
|
||||
|
||||
if (data && this.accessor.id === data.viewId) {
|
||||
if (
|
||||
data.panelId === null &&
|
||||
data.groupId === this.group.id
|
||||
) {
|
||||
// don't allow group move to drop on self
|
||||
return false;
|
||||
}
|
||||
|
||||
// don't show the overlay if the tab being dragged is the last panel of this group
|
||||
return last(this.group.panels)?.id !== data.panelId;
|
||||
}
|
||||
|
||||
return group.model.canDisplayOverlay(
|
||||
event,
|
||||
position,
|
||||
DockviewDropTargets.Panel
|
||||
);
|
||||
},
|
||||
|
@ -27,7 +27,7 @@ export * from './react'; // TODO: should be conditional on whether user wants th
|
||||
|
||||
export { Event } from './events';
|
||||
export { IDisposable } from './lifecycle';
|
||||
export { Position } from './dnd/droptarget';
|
||||
export { Position as DropTargetDirections } from './dnd/droptarget';
|
||||
export {
|
||||
FocusEvent,
|
||||
PanelDimensionChangeEvent,
|
||||
|
@ -4,7 +4,7 @@ import {
|
||||
LocalSelectionTransfer,
|
||||
PaneTransfer,
|
||||
} from '../dnd/dataTransfer';
|
||||
import { Droptarget, DroptargetEvent, Position } from '../dnd/droptarget';
|
||||
import { Droptarget, DroptargetEvent } from '../dnd/droptarget';
|
||||
import { Emitter } from '../events';
|
||||
import { IDisposable } from '../lifecycle';
|
||||
import { Orientation } from '../splitview/core/splitview';
|
||||
@ -70,7 +70,10 @@ export abstract class DraggablePaneviewPanel extends PaneviewPanel {
|
||||
})(this.header);
|
||||
|
||||
this.target = new Droptarget(this.element, {
|
||||
validOverlays: 'vertical',
|
||||
acceptedTargetZones: ['top', 'bottom'],
|
||||
overlayModel: {
|
||||
activationSize: { type: 'percentage', value: 50 },
|
||||
},
|
||||
canDisplayOverlay: (event) => {
|
||||
const data = getPaneData();
|
||||
|
||||
@ -139,16 +142,10 @@ export abstract class DraggablePaneviewPanel extends PaneviewPanel {
|
||||
const fromIndex = allPanels.indexOf(existingPanel);
|
||||
let toIndex = containerApi.panels.indexOf(this);
|
||||
|
||||
if (
|
||||
event.position === Position.Left ||
|
||||
event.position === Position.Top
|
||||
) {
|
||||
if (event.position === 'left' || event.position === 'top') {
|
||||
toIndex = Math.max(0, toIndex - 1);
|
||||
}
|
||||
if (
|
||||
event.position === Position.Right ||
|
||||
event.position === Position.Bottom
|
||||
) {
|
||||
if (event.position === 'right' || event.position === 'bottom') {
|
||||
if (fromIndex > toIndex) {
|
||||
toIndex++;
|
||||
}
|
||||
|
@ -164,6 +164,8 @@ export abstract class PaneviewPanel
|
||||
) {
|
||||
super(id, component, new PaneviewPanelApiImpl(id));
|
||||
this.api.pane = this; // TODO cannot use 'this' before 'super'
|
||||
this.api.initialize(this);
|
||||
|
||||
this._isExpanded = isExpanded;
|
||||
this._headerVisible = isHeaderVisible;
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { DockviewComponent } from '../dockview/dockviewComponent';
|
||||
import { GroupviewPanelState, IDockviewPanel } from '../groupview/groupPanel';
|
||||
import { DockviewGroupPanel } from '../dockview/dockviewGroupPanel';
|
||||
import { DockviewPanel } from '../dockview/dockviewPanel';
|
||||
import { IPanelDeserializer } from '../dockview/deserializer';
|
||||
import { createComponent } from '../panel/componentFactory';
|
||||
import { DockviewApi } from '../api/component.api';
|
||||
@ -56,7 +56,7 @@ export class ReactPanelDeserialzier implements IPanelDeserializer {
|
||||
tab,
|
||||
});
|
||||
|
||||
const panel = new DockviewGroupPanel(
|
||||
const panel = new DockviewPanel(
|
||||
panelId,
|
||||
this.layout,
|
||||
new DockviewApi(this.layout),
|
||||
|
@ -69,6 +69,7 @@ export interface IDockviewReactProps {
|
||||
disableAutoResizing?: boolean;
|
||||
defaultTabComponent?: React.FunctionComponent<IDockviewPanelHeaderProps>;
|
||||
groupControlComponent?: React.FunctionComponent<IDockviewGroupControlProps>;
|
||||
singleTabMode?: 'fullwidth' | 'default';
|
||||
}
|
||||
|
||||
export const DockviewReact = React.forwardRef(
|
||||
@ -161,6 +162,7 @@ export const DockviewReact = React.forwardRef(
|
||||
props.groupControlComponent,
|
||||
{ addPortal }
|
||||
),
|
||||
singleTabMode: props.singleTabMode,
|
||||
});
|
||||
|
||||
domRef.current?.appendChild(dockview.element);
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { GridviewApi } from '../../api/component.api';
|
||||
import { GridviewPanelApiImpl } from '../../api/gridviewPanelApi';
|
||||
import {
|
||||
GridviewPanel,
|
||||
GridviewInitParameters,
|
||||
@ -14,7 +15,7 @@ export class ReactGridPanelView extends GridviewPanel {
|
||||
private readonly reactComponent: React.FunctionComponent<IGridviewPanelProps>,
|
||||
private readonly reactPortalStore: ReactPortalStore
|
||||
) {
|
||||
super(id, component);
|
||||
super(id, component, new GridviewPanelApiImpl(id));
|
||||
}
|
||||
|
||||
getComponent(): IFrameworkPart {
|
||||
|
@ -85,6 +85,8 @@ export abstract class SplitviewPanel
|
||||
constructor(id: string, componentName: string) {
|
||||
super(id, componentName, new SplitviewPanelApiImpl(id));
|
||||
|
||||
this.api.initialize(this);
|
||||
|
||||
this.addDisposables(
|
||||
this._onDidChange,
|
||||
this.api.onVisibilityChange((event) => {
|
||||
|
Loading…
Reference in New Issue
Block a user