feat: events cleanup

This commit is contained in:
mathuo 2024-02-03 22:56:38 +00:00
parent 89fe866ac5
commit 807ccf80de
No known key found for this signature in database
GPG Key ID: C6EEDEFD6CA07281
24 changed files with 1506 additions and 567 deletions

View File

@ -257,9 +257,17 @@ describe('dockviewComponent', () => {
const panel4 = dockview.getGroupPanel('panel4');
const group1 = panel1!.group;
dockview.moveGroupOrPanel(group1, group1.id, 'panel1', 'right');
dockview.moveGroupOrPanel({
from: { groupId: group1.id, panelId: 'panel1' },
to: { group: group1, position: 'right' },
});
const group2 = panel1!.group;
dockview.moveGroupOrPanel(group2, group1.id, 'panel3', 'center');
dockview.moveGroupOrPanel({
from: { groupId: group1.id, panelId: 'panel3' },
to: { group: group2, position: 'center' },
});
expect(dockview.activeGroup).toBe(group2);
expect(dockview.activeGroup!.model.activePanel).toBe(panel3);
@ -309,9 +317,16 @@ describe('dockviewComponent', () => {
const panel1 = dockview.getGroupPanel('panel1')!;
const panel2 = dockview.getGroupPanel('panel2')!;
const group1 = panel1.group;
dockview.moveGroupOrPanel(group1, group1.id, 'panel1', 'right');
dockview.moveGroupOrPanel({
from: { groupId: group1.id, panelId: 'panel1' },
to: { group: group1, position: 'right' },
});
const group2 = panel1.group;
dockview.moveGroupOrPanel(group2, group1.id, 'panel3', 'center');
dockview.moveGroupOrPanel({
from: { groupId: group1.id, panelId: 'panel3' },
to: { group: group2, position: 'center' },
});
expect(dockview.size).toBe(2);
expect(dockview.totalPanels).toBe(4);
@ -374,9 +389,16 @@ describe('dockviewComponent', () => {
expect(panel4.api.isActive).toBeFalsy();
const group1 = panel1.group;
dockview.moveGroupOrPanel(group1, group1.id, 'panel1', 'right');
dockview.moveGroupOrPanel({
from: { groupId: group1.id, panelId: 'panel1' },
to: { group: group1, position: 'right' },
});
const group2 = panel1.group;
dockview.moveGroupOrPanel(group2, group1.id, 'panel3', 'center');
dockview.moveGroupOrPanel({
from: { groupId: group1.id, panelId: 'panel3' },
to: { group: group2, position: 'center' },
});
expect(dockview.size).toBe(2);
expect(panel1.group).toBe(panel3.group);
@ -443,7 +465,10 @@ describe('dockviewComponent', () => {
expect(group.model.indexOf(panel1)).toBe(0);
expect(group.model.indexOf(panel2)).toBe(1);
dockview.moveGroupOrPanel(group, group.id, 'panel1', 'right');
dockview.moveGroupOrPanel({
from: { groupId: group.id, panelId: 'panel1' },
to: { group, position: 'right' },
});
expect(dockview.size).toBe(2);
expect(dockview.totalPanels).toBe(2);
@ -493,7 +518,10 @@ describe('dockviewComponent', () => {
expect(viewQuery.length).toBe(1);
const group = dockview.getGroupPanel('panel1')!.group;
dockview.moveGroupOrPanel(group, group.id, 'panel1', 'right');
dockview.moveGroupOrPanel({
from: { groupId: group.id, panelId: 'panel1' },
to: { group, position: 'right' },
});
viewQuery = container.querySelectorAll(
'.branch-node > .split-view-container > .view-container > .view'
@ -908,8 +936,8 @@ describe('dockviewComponent', () => {
expect(events).toEqual([
{ type: 'ADD_GROUP', group: panel1.group },
{ type: 'ACTIVE_GROUP', group: panel1.group },
{ type: 'ADD_PANEL', panel: panel1 },
{ type: 'ACTIVE_GROUP', group: panel1.group },
{ type: 'ACTIVE_PANEL', panel: panel1 },
]);
@ -956,8 +984,8 @@ describe('dockviewComponent', () => {
expect(events).toEqual([
{ type: 'ADD_GROUP', group: panel4.group },
{ type: 'ACTIVE_GROUP', group: panel4.group },
{ type: 'ADD_PANEL', panel: panel4 },
{ type: 'ACTIVE_GROUP', group: panel4.group },
{ type: 'ACTIVE_PANEL', panel: panel4 },
]);
@ -973,36 +1001,24 @@ describe('dockviewComponent', () => {
]);
events = [];
dockview.moveGroupOrPanel(
panel2.group!,
panel5.group!.id,
panel5.id,
'center'
);
dockview.moveGroupOrPanel({
from: { groupId: panel5.group.id, panelId: panel5.id },
to: { group: panel2.group, position: 'center' },
});
expect(events).toEqual([
{ type: 'REMOVE_PANEL', panel: panel5 },
{ type: 'ACTIVE_PANEL', panel: panel4 },
{ type: 'ADD_PANEL', panel: panel5 },
{ type: 'ACTIVE_PANEL', panel: panel5 },
{ type: 'ACTIVE_GROUP', group: panel2.group },
]);
expect(events).toEqual([{ type: 'ACTIVE_GROUP', group: panel2.group }]);
events = [];
const groupReferenceBeforeMove = panel4.group;
dockview.moveGroupOrPanel(
panel2.group!,
panel4.group!.id,
panel4.id,
'center'
);
dockview.moveGroupOrPanel({
from: { groupId: panel4.group.id, panelId: panel4.id },
to: { group: panel2.group, position: 'center' },
});
expect(events).toEqual([
{ type: 'REMOVE_PANEL', panel: panel4 },
{ type: 'REMOVE_GROUP', group: groupReferenceBeforeMove },
{ type: 'ADD_PANEL', panel: panel4 },
{ type: 'ACTIVE_PANEL', panel: panel4 },
]);
@ -1022,8 +1038,8 @@ describe('dockviewComponent', () => {
expect(events).toEqual([
{ type: 'ADD_GROUP', group: panel6.group },
{ type: 'ADD_PANEL', panel: panel6 },
{ type: 'ACTIVE_PANEL', panel: panel6 },
{ type: 'ACTIVE_GROUP', group: panel6.group },
{ type: 'ACTIVE_PANEL', panel: panel6 },
]);
events = [];
@ -1038,8 +1054,8 @@ describe('dockviewComponent', () => {
expect(events).toEqual([
{ type: 'ADD_GROUP', group: panel7.group },
{ type: 'ADD_PANEL', panel: panel7 },
{ type: 'ACTIVE_PANEL', panel: panel7 },
{ type: 'ACTIVE_GROUP', group: panel7.group },
{ type: 'ACTIVE_PANEL', panel: panel7 },
]);
expect(dockview.activePanel === panel7).toBeTruthy();
@ -1061,6 +1077,7 @@ describe('dockviewComponent', () => {
{ type: 'REMOVE_PANEL', panel: panel6 },
{ type: 'REMOVE_GROUP', group: panel6Group },
{ type: 'ACTIVE_GROUP', group: undefined },
{ type: 'ACTIVE_PANEL', group: undefined },
]);
expect(dockview.size).toBe(0);
@ -1317,12 +1334,10 @@ describe('dockviewComponent', () => {
const panel1Spy = jest.spyOn(panel1, 'dispose');
const panel2Spy = jest.spyOn(panel2, 'dispose');
dockview.moveGroupOrPanel(
panel1.group,
panel2.group.id,
'panel2',
'left'
);
dockview.moveGroupOrPanel({
from: { groupId: panel2.group.id, panelId: 'panel2' },
to: { group: panel1.group, position: 'left' },
});
expect(panel1Spy).not.toHaveBeenCalled();
expect(panel2Spy).not.toHaveBeenCalled();
@ -1359,12 +1374,10 @@ describe('dockviewComponent', () => {
const panel1Spy = jest.spyOn(panel1, 'dispose');
const panel2Spy = jest.spyOn(panel2, 'dispose');
dockview.moveGroupOrPanel(
panel1.group,
panel2.group.id,
'panel2',
'center'
);
dockview.moveGroupOrPanel({
from: { groupId: panel2.group.id, panelId: 'panel2' },
to: { group: panel1.group, position: 'center' },
});
expect(panel1Spy).not.toHaveBeenCalled();
expect(panel2Spy).not.toHaveBeenCalled();
@ -1399,13 +1412,10 @@ describe('dockviewComponent', () => {
const panel1Spy = jest.spyOn(panel1, 'dispose');
const panel2Spy = jest.spyOn(panel2, 'dispose');
dockview.moveGroupOrPanel(
panel1.group,
panel1.group.id,
'panel1',
'center',
0
);
dockview.moveGroupOrPanel({
from: { groupId: panel1.group.id, panelId: 'panel1' },
to: { group: panel1.group, position: 'center', index: 0 },
});
expect(panel1Spy).not.toHaveBeenCalled();
expect(panel2Spy).not.toHaveBeenCalled();
@ -1563,12 +1573,10 @@ describe('dockviewComponent', () => {
expect(dockview.groups.length).toBe(2);
dockview.moveGroupOrPanel(
panel3.group,
panel1.group.id,
undefined,
'center'
);
dockview.moveGroupOrPanel({
from: { groupId: panel1.group.id },
to: { group: panel3.group, position: 'center' },
});
expect(dockview.groups.length).toBe(1);
expect(panel1Spy).toBeCalledTimes(1);
@ -1697,7 +1705,7 @@ describe('dockviewComponent', () => {
expect(activeGroup.length).toBe(1);
expect(addPanel.length).toBe(5);
expect(removePanel.length).toBe(0);
expect(activePanel.length).toBe(5);
expect(activePanel.length).toBe(1);
expect(layoutChange).toBe(1);
expect(layoutChangeFromJson).toBe(1);
@ -2728,32 +2736,26 @@ describe('dockviewComponent', () => {
expect(dockview.element.querySelectorAll('.view').length).toBe(1);
dockview.moveGroupOrPanel(
panel3.group,
panel3.group.id,
panel3.id,
'right'
);
dockview.moveGroupOrPanel({
from: { groupId: panel3.group.id, panelId: panel3.id },
to: { group: panel3.group, position: 'right' },
});
expect(dockview.groups.length).toBe(2);
expect(dockview.element.querySelectorAll('.view').length).toBe(2);
dockview.moveGroupOrPanel(
panel3.group,
panel2.group.id,
panel2.id,
'bottom'
);
dockview.moveGroupOrPanel({
from: { groupId: panel2.group.id, panelId: panel2.id },
to: { group: panel3.group, position: 'bottom' },
});
expect(dockview.groups.length).toBe(3);
expect(dockview.element.querySelectorAll('.view').length).toBe(4);
dockview.moveGroupOrPanel(
panel2.group,
panel1.group.id,
panel1.id,
'center'
);
dockview.moveGroupOrPanel({
from: { groupId: panel1.group.id, panelId: panel1.id },
to: { group: panel2.group, position: 'center' },
});
expect(dockview.groups.length).toBe(2);
@ -3463,12 +3465,10 @@ describe('dockviewComponent', () => {
expect(dockview.groups.length).toBe(2);
expect(dockview.panels.length).toBe(2);
dockview.moveGroupOrPanel(
panel1.group,
panel2.group.id,
undefined,
'right'
);
dockview.moveGroupOrPanel({
from: { groupId: panel2.group.id },
to: { group: panel1.group, position: 'right' },
});
expect(panel1.group.api.location.type).toBe('grid');
expect(panel2.group.api.location.type).toBe('grid');
@ -3508,12 +3508,10 @@ describe('dockviewComponent', () => {
expect(dockview.groups.length).toBe(2);
expect(dockview.panels.length).toBe(2);
dockview.moveGroupOrPanel(
panel1.group,
panel2.group.id,
undefined,
'center'
);
dockview.moveGroupOrPanel({
from: { groupId: panel2.group.id },
to: { group: panel1.group, position: 'center' },
});
expect(panel1.group.api.location.type).toBe('grid');
expect(panel2.group.api.location.type).toBe('grid');
@ -3560,12 +3558,10 @@ describe('dockviewComponent', () => {
expect(dockview.groups.length).toBe(3);
expect(dockview.panels.length).toBe(3);
dockview.moveGroupOrPanel(
panel2.group,
panel3.group.id,
undefined,
'center'
);
dockview.moveGroupOrPanel({
from: { groupId: panel3.group.id },
to: { group: panel2.group, position: 'center' },
});
expect(panel1.group.api.location.type).toBe('grid');
expect(panel2.group.api.location.type).toBe('floating');
@ -3613,12 +3609,10 @@ describe('dockviewComponent', () => {
expect(dockview.groups.length).toBe(2);
expect(dockview.panels.length).toBe(3);
dockview.moveGroupOrPanel(
panel1.group,
panel2.group.id,
undefined,
'right'
);
dockview.moveGroupOrPanel({
from: { groupId: panel2.group.id },
to: { group: panel1.group, position: 'right' },
});
expect(panel1.group.api.location.type).toBe('grid');
expect(panel2.group.api.location.type).toBe('grid');
@ -3666,12 +3660,10 @@ describe('dockviewComponent', () => {
expect(dockview.groups.length).toBe(2);
expect(dockview.panels.length).toBe(3);
dockview.moveGroupOrPanel(
panel1.group,
panel2.group.id,
undefined,
'center'
);
dockview.moveGroupOrPanel({
from: { groupId: panel2.group.id },
to: { group: panel1.group, position: 'center' },
});
expect(panel1.group.api.location.type).toBe('grid');
expect(panel2.group.api.location.type).toBe('grid');
@ -3726,12 +3718,10 @@ describe('dockviewComponent', () => {
expect(dockview.groups.length).toBe(3);
expect(dockview.panels.length).toBe(4);
dockview.moveGroupOrPanel(
panel4.group,
panel2.group.id,
undefined,
'center'
);
dockview.moveGroupOrPanel({
from: { groupId: panel2.group.id },
to: { group: panel4.group, position: 'center' },
});
expect(panel1.group.api.location.type).toBe('grid');
expect(panel2.group.api.location.type).toBe('floating');
@ -3773,12 +3763,10 @@ describe('dockviewComponent', () => {
expect(dockview.groups.length).toBe(2);
expect(dockview.panels.length).toBe(2);
dockview.moveGroupOrPanel(
panel1.group,
panel2.group.id,
panel2.id,
'right'
);
dockview.moveGroupOrPanel({
from: { groupId: panel2.group.id, panelId: panel2.id },
to: { group: panel1.group, position: 'right' },
});
expect(panel1.group.api.location.type).toBe('grid');
expect(panel2.group.api.location.type).toBe('grid');
@ -3818,12 +3806,10 @@ describe('dockviewComponent', () => {
expect(dockview.groups.length).toBe(2);
expect(dockview.panels.length).toBe(2);
dockview.moveGroupOrPanel(
panel1.group,
panel2.group.id,
panel2.id,
'center'
);
dockview.moveGroupOrPanel({
from: { groupId: panel2.group.id, panelId: panel2.id },
to: { group: panel1.group, position: 'center' },
});
expect(panel1.group.api.location.type).toBe('grid');
expect(panel2.group.api.location.type).toBe('grid');
@ -3870,12 +3856,10 @@ describe('dockviewComponent', () => {
expect(dockview.groups.length).toBe(3);
expect(dockview.panels.length).toBe(3);
dockview.moveGroupOrPanel(
panel2.group,
panel3.group.id,
panel3.id,
'center'
);
dockview.moveGroupOrPanel({
from: { groupId: panel3.group.id, panelId: panel3.id },
to: { group: panel2.group, position: 'center' },
});
expect(panel1.group.api.location.type).toBe('grid');
expect(panel2.group.api.location.type).toBe('floating');
@ -3923,12 +3907,10 @@ describe('dockviewComponent', () => {
expect(dockview.groups.length).toBe(2);
expect(dockview.panels.length).toBe(3);
dockview.moveGroupOrPanel(
panel1.group,
panel2.group.id,
panel2.id,
'right'
);
dockview.moveGroupOrPanel({
from: { groupId: panel2.group.id, panelId: panel2.id },
to: { group: panel1.group, position: 'right' },
});
expect(panel1.group.api.location.type).toBe('grid');
expect(panel2.group.api.location.type).toBe('grid');
@ -3976,12 +3958,10 @@ describe('dockviewComponent', () => {
expect(dockview.groups.length).toBe(2);
expect(dockview.panels.length).toBe(3);
dockview.moveGroupOrPanel(
panel1.group,
panel2.group.id,
panel2.id,
'center'
);
dockview.moveGroupOrPanel({
from: { groupId: panel2.group.id, panelId: panel2.id },
to: { group: panel1.group, position: 'center' },
});
expect(panel1.group.api.location.type).toBe('grid');
expect(panel2.group.api.location.type).toBe('grid');
@ -4036,12 +4016,10 @@ describe('dockviewComponent', () => {
expect(dockview.groups.length).toBe(3);
expect(dockview.panels.length).toBe(4);
dockview.moveGroupOrPanel(
panel4.group,
panel2.group.id,
panel2.id,
'center'
);
dockview.moveGroupOrPanel({
from: { groupId: panel2.group.id, panelId: panel2.id },
to: { group: panel4.group, position: 'center' },
});
expect(panel1.group.api.location.type).toBe('grid');
expect(panel2.group.api.location.type).toBe('floating');
@ -4090,12 +4068,10 @@ describe('dockviewComponent', () => {
expect(dockview.groups.length).toBe(3);
expect(dockview.panels.length).toBe(3);
dockview.moveGroupOrPanel(
panel3.group,
panel1.group.id,
panel1.id,
'center'
);
dockview.moveGroupOrPanel({
from: { groupId: panel1.group.id, panelId: panel1.id },
to: { group: panel3.group, position: 'center' },
});
expect(panel1.group.api.location.type).toBe('floating');
expect(panel2.group.api.location.type).toBe('grid');
@ -4142,12 +4118,10 @@ describe('dockviewComponent', () => {
expect(dockview.groups.length).toBe(2);
expect(dockview.panels.length).toBe(3);
dockview.moveGroupOrPanel(
panel3.group,
panel1.group.id,
panel1.id,
'center'
);
dockview.moveGroupOrPanel({
from: { groupId: panel1.group.id, panelId: panel1.id },
to: { group: panel3.group, position: 'center' },
});
expect(panel1.group.api.location.type).toBe('floating');
expect(panel2.group.api.location.type).toBe('grid');
@ -4195,12 +4169,10 @@ describe('dockviewComponent', () => {
expect(dockview.groups.length).toBe(3);
expect(dockview.panels.length).toBe(3);
dockview.moveGroupOrPanel(
panel3.group,
panel1.group.id,
undefined,
'center'
);
dockview.moveGroupOrPanel({
from: { groupId: panel1.group.id },
to: { group: panel3.group, position: 'center' },
});
expect(panel1.group.api.location.type).toBe('floating');
expect(panel2.group.api.location.type).toBe('grid');
@ -4247,12 +4219,10 @@ describe('dockviewComponent', () => {
expect(dockview.groups.length).toBe(2);
expect(dockview.panels.length).toBe(3);
dockview.moveGroupOrPanel(
panel3.group,
panel1.group.id,
undefined,
'center'
);
dockview.moveGroupOrPanel({
from: { groupId: panel1.group.id },
to: { group: panel3.group, position: 'center' },
});
expect(panel1.group.api.location.type).toBe('floating');
expect(panel2.group.api.location.type).toBe('floating');
@ -4427,6 +4397,7 @@ describe('dockviewComponent', () => {
document: fromPartial<Document>({
body: document.createElement('body'),
}),
focus: jest.fn(),
addEventListener: jest
.fn()
.mockImplementation((name, cb) => {
@ -4560,12 +4531,10 @@ describe('dockviewComponent', () => {
expect(dockview.groups.length).toBe(3);
expect(dockview.panels.length).toBe(3);
dockview.moveGroupOrPanel(
panel3.api.group,
panel2.api.group.id,
panel2.api.id,
'right'
);
dockview.moveGroupOrPanel({
from: { groupId: panel2.group.id, panelId: panel2.id },
to: { group: panel3.group, position: 'right' },
});
expect(panel1.group.api.location.type).toBe('popout');
expect(panel2.group.api.location.type).toBe('grid');
@ -4573,12 +4542,10 @@ describe('dockviewComponent', () => {
expect(dockview.groups.length).toBe(4);
expect(dockview.panels.length).toBe(3);
dockview.moveGroupOrPanel(
panel3.api.group,
panel1.api.group.id,
panel1.api.id,
'center'
);
dockview.moveGroupOrPanel({
from: { groupId: panel1.group.id, panelId: panel1.id },
to: { group: panel3.group, position: 'center' },
});
expect(panel1.group.api.location.type).toBe('grid');
expect(panel2.group.api.location.type).toBe('grid');

View File

@ -196,7 +196,14 @@ export class TestPanel implements IDockviewPanel {
this._params = params;
}
updateParentGroup(group: DockviewGroupPanel, isGroupActive: boolean): void {
updateParentGroup(
group: DockviewGroupPanel,
options: { isGroupActive: boolean }
): void {
//
}
runEvents(): void {
//
}
@ -624,7 +631,7 @@ describe('dockviewGroupPanelModel', () => {
renderer: 'onlyWhenVisibile',
} as any);
cut.openPanel(panel3, { skipSetPanelActive: true });
cut.openPanel(panel3, { skipRender: true });
expect(contentContainer.length).toBe(1);
expect(contentContainer.item(0)).toBe(panel2.view.content.element);

View File

@ -18,8 +18,8 @@ describe('dockviewGroupPanel', () => {
element: document.createElement('div'),
dispose: jest.fn(),
update: jest.fn(),
onGroupChange: jest.fn(),
onPanelVisibleChange: jest.fn(),
// onGroupChange: jest.fn(),
// onPanelVisibleChange: jest.fn(),
};
return partial as IContentRenderer;
});
@ -29,8 +29,8 @@ describe('dockviewGroupPanel', () => {
element: document.createElement('div'),
dispose: jest.fn(),
update: jest.fn(),
onGroupChange: jest.fn(),
onPanelVisibleChange: jest.fn(),
// onGroupChange: jest.fn(),
// onPanelVisibleChange: jest.fn(),
};
return partial as IContentRenderer;
});
@ -82,51 +82,51 @@ describe('dockviewGroupPanel', () => {
expect(cut.tab.update).toHaveBeenCalled();
});
test('that events are fired', () => {
const cut = new DockviewPanelModel(
<IDockviewComponent>new accessorMock(),
'id',
'contentComponent',
'tabComponent'
);
// test('that events are fired', () => {
// const cut = new DockviewPanelModel(
// <IDockviewComponent>new accessorMock(),
// 'id',
// 'contentComponent',
// 'tabComponent'
// );
const group1 = jest.fn() as any;
const group2 = jest.fn() as any;
cut.updateParentGroup(group1, false);
// const group1 = jest.fn() as any;
// const group2 = jest.fn() as any;
// cut.updateParentGroup(group1, false);
expect(cut.content.onGroupChange).toHaveBeenNthCalledWith(1, group1);
expect(cut.tab.onGroupChange).toHaveBeenNthCalledWith(1, group1);
expect(cut.content.onPanelVisibleChange).toHaveBeenNthCalledWith(
1,
false
);
expect(cut.tab.onPanelVisibleChange).toHaveBeenNthCalledWith(1, false);
expect(cut.content.onGroupChange).toHaveBeenCalledTimes(1);
expect(cut.tab.onGroupChange).toHaveBeenCalledTimes(1);
expect(cut.content.onPanelVisibleChange).toHaveBeenCalledTimes(1);
expect(cut.tab.onPanelVisibleChange).toHaveBeenCalledTimes(1);
// expect(cut.content.onGroupChange).toHaveBeenNthCalledWith(1, group1);
// expect(cut.tab.onGroupChange).toHaveBeenNthCalledWith(1, group1);
// expect(cut.content.onPanelVisibleChange).toHaveBeenNthCalledWith(
// 1,
// false
// );
// expect(cut.tab.onPanelVisibleChange).toHaveBeenNthCalledWith(1, false);
// expect(cut.content.onGroupChange).toHaveBeenCalledTimes(1);
// expect(cut.tab.onGroupChange).toHaveBeenCalledTimes(1);
// expect(cut.content.onPanelVisibleChange).toHaveBeenCalledTimes(1);
// expect(cut.tab.onPanelVisibleChange).toHaveBeenCalledTimes(1);
cut.updateParentGroup(group1, true);
// cut.updateParentGroup(group1, true);
expect(cut.content.onPanelVisibleChange).toHaveBeenNthCalledWith(
2,
true
);
expect(cut.tab.onPanelVisibleChange).toHaveBeenNthCalledWith(2, true);
expect(cut.content.onGroupChange).toHaveBeenCalledTimes(1);
expect(cut.tab.onGroupChange).toHaveBeenCalledTimes(1);
expect(cut.content.onPanelVisibleChange).toHaveBeenCalledTimes(2);
expect(cut.tab.onPanelVisibleChange).toHaveBeenCalledTimes(2);
// expect(cut.content.onPanelVisibleChange).toHaveBeenNthCalledWith(
// 2,
// true
// );
// expect(cut.tab.onPanelVisibleChange).toHaveBeenNthCalledWith(2, true);
// expect(cut.content.onGroupChange).toHaveBeenCalledTimes(1);
// expect(cut.tab.onGroupChange).toHaveBeenCalledTimes(1);
// expect(cut.content.onPanelVisibleChange).toHaveBeenCalledTimes(2);
// expect(cut.tab.onPanelVisibleChange).toHaveBeenCalledTimes(2);
cut.updateParentGroup(group2, true);
// cut.updateParentGroup(group2, true);
expect(cut.content.onGroupChange).toHaveBeenNthCalledWith(2, group2);
expect(cut.tab.onGroupChange).toHaveBeenNthCalledWith(2, group2);
expect(cut.content.onGroupChange).toHaveBeenCalledTimes(2);
expect(cut.tab.onGroupChange).toHaveBeenCalledTimes(2);
expect(cut.content.onPanelVisibleChange).toHaveBeenCalledTimes(2);
expect(cut.tab.onPanelVisibleChange).toHaveBeenCalledTimes(2);
});
// expect(cut.content.onGroupChange).toHaveBeenNthCalledWith(2, group2);
// expect(cut.tab.onGroupChange).toHaveBeenNthCalledWith(2, group2);
// expect(cut.content.onGroupChange).toHaveBeenCalledTimes(2);
// expect(cut.tab.onGroupChange).toHaveBeenCalledTimes(2);
// expect(cut.content.onPanelVisibleChange).toHaveBeenCalledTimes(2);
// expect(cut.tab.onPanelVisibleChange).toHaveBeenCalledTimes(2);
// });
test('that the default tab is created', () => {
accessorMock = jest.fn<DockviewComponent, []>(() => {

View File

@ -114,17 +114,17 @@ describe('baseComponentGridview', () => {
proportionalLayout: true,
});
const events: (TestPanel | undefined)[] = [];
const events: { type: string; panel: TestPanel | undefined }[] = [];
const disposable = new CompositeDisposable(
cut.onDidAddGroup((event) => {
events.push(event);
cut.onDidAdd((event) => {
events.push({ type: 'add', panel: event });
}),
cut.onDidRemoveGroup((event) => {
events.push(event);
cut.onDidRemove((event) => {
events.push({ type: 'remove', panel: event });
}),
cut.onDidActiveGroupChange((event) => {
events.push(event);
cut.onDidActiveChange((event) => {
events.push({ type: 'active', panel: event });
})
);
@ -141,9 +141,8 @@ describe('baseComponentGridview', () => {
cut.doAddGroup(panel1);
expect(events.length).toBe(2);
expect(events[0]).toBe(panel1);
expect(events[1]).toBe(panel1);
expect(events.length).toBe(1);
expect(events[0]).toEqual({ type: 'add', panel: panel1 });
const panel2 = new TestPanel(
'id',
@ -158,12 +157,12 @@ describe('baseComponentGridview', () => {
cut.doAddGroup(panel2);
expect(events.length).toBe(4);
expect(events[2]).toBe(panel2);
expect(events.length).toBe(2);
expect(events[1]).toEqual({ type: 'add', panel: panel2 });
cut.doRemoveGroup(panel1);
expect(events.length).toBe(5);
expect(events[4]).toBe(panel1);
expect(events.length).toBe(3);
expect(events[2]).toEqual({ type: 'remove', panel: panel1 });
disposable.dispose();
cut.dispose();

View File

@ -63,12 +63,15 @@ export class DockviewGroupPanelApiImpl extends GridviewPanelApiImpl {
direction: positionToDirection(options.position ?? 'right'),
});
this.accessor.moveGroupOrPanel(
group,
this._group.id,
undefined,
options.group ? options.position ?? 'center' : 'center'
);
this.accessor.moveGroupOrPanel({
from: { groupId: this._group.id },
to: {
group,
position: options.group
? options.position ?? 'center'
: 'center',
},
});
}
maximize(): void {

View File

@ -14,7 +14,15 @@ export interface TitleEvent {
}
export interface RendererChangedEvent {
renderer: DockviewPanelRenderer;
readonly renderer: DockviewPanelRenderer;
}
export interface ActiveGroupEvent {
readonly isActive: boolean;
}
export interface GroupChangedEvent {
// empty
}
export interface DockviewPanelApi
@ -27,8 +35,8 @@ export interface DockviewPanelApi
readonly isGroupActive: boolean;
readonly renderer: DockviewPanelRenderer;
readonly title: string | undefined;
readonly onDidActiveGroupChange: Event<void>;
readonly onDidGroupChange: Event<void>;
readonly onDidActiveGroupChange: Event<ActiveGroupEvent>;
readonly onDidGroupChange: Event<GroupChangedEvent>;
readonly onDidRendererChange: Event<RendererChangedEvent>;
readonly location: DockviewGroupLocation;
readonly onDidLocationChange: Event<DockviewGroupPanelFloatingChangeEvent>;
@ -58,10 +66,10 @@ export class DockviewPanelApiImpl
readonly _onDidTitleChange = new Emitter<TitleEvent>();
readonly onDidTitleChange = this._onDidTitleChange.event;
private readonly _onDidActiveGroupChange = new Emitter<void>();
private readonly _onDidActiveGroupChange = new Emitter<ActiveGroupEvent>();
readonly onDidActiveGroupChange = this._onDidActiveGroupChange.event;
private readonly _onDidGroupChange = new Emitter<void>();
private readonly _onDidGroupChange = new Emitter<GroupChangedEvent>();
readonly onDidGroupChange = this._onDidGroupChange.event;
readonly _onDidRendererChange = new Emitter<RendererChangedEvent>();
@ -93,23 +101,39 @@ export class DockviewPanelApiImpl
set group(value: DockviewGroupPanel) {
const isOldGroupActive = this.isGroupActive;
this._group = value;
if (this._group !== value) {
this._group = value;
this._onDidGroupChange.fire();
this._onDidGroupChange.fire({});
let _trackGroupActive = isOldGroupActive; // prevent duplicate events with same state
if (this._group) {
this.groupEventsDisposable.value = new CompositeDisposable(
this.group.api.onDidLocationChange((event) => {
if (this.group !== this.panel.group) {
return;
}
this._onDidLocationChange.fire(event);
}),
this.group.api.onDidActiveChange(() => {
this._onDidActiveGroupChange.fire();
if (this.group !== this.panel.group) {
return;
}
if (_trackGroupActive !== this.isGroupActive) {
_trackGroupActive = this.isGroupActive;
this._onDidActiveGroupChange.fire({
isActive: this.isGroupActive,
});
}
})
);
if (this.isGroupActive !== isOldGroupActive) {
this._onDidActiveGroupChange.fire();
}
// if (this.isGroupActive !== isOldGroupActive) {
// this._onDidActiveGroupChange.fire({
// isActive: this.isGroupActive,
// });
// }
this._onDidLocationChange.fire({
location: this.group.api.location,
@ -132,8 +156,6 @@ export class DockviewPanelApiImpl
this._group = group;
this.addDisposables(
this.groupEventsDisposable,
this._onDidRendererChange,
@ -153,13 +175,14 @@ export class DockviewPanelApiImpl
position?: Position;
index?: number;
}): void {
this.accessor.moveGroupOrPanel(
options.group,
this._group.id,
this.panel.id,
options.position ?? 'center',
options.index
);
this.accessor.moveGroupOrPanel({
from: { groupId: this._group.id, panelId: this.panel.id },
to: {
group: options.group,
position: options.position ?? 'center',
index: options.index,
},
});
}
setTitle(title: string): void {

View File

@ -390,17 +390,15 @@ export class TabsContainer
return;
}
const alreadyFocused =
panel.id === this.group.model.activePanel?.id &&
this.group.model.isContentFocused;
const isLeftClick = event.button === 0;
if (!isLeftClick || event.defaultPrevented) {
return;
}
this.group.model.openPanel(panel);
if (this.group.activePanel !== panel) {
this.group.model.openPanel(panel);
}
}),
tab.onDrop((event) => {
this._onDrop.fire({

View File

@ -232,6 +232,23 @@ export type DockviewComponentUpdateOptions = Pick<
| 'disableDnd'
>;
type MoveGroupOptions = {
from: { group: DockviewGroupPanel };
to: { group: DockviewGroupPanel; position: Position };
};
type MoveGroupOrPanelOptions = {
from: {
groupId: string;
panelId?: string;
};
to: {
group: DockviewGroupPanel;
position: Position;
index?: number;
};
};
export interface IDockviewComponent extends IBaseGrid<DockviewGroupPanel> {
readonly activePanel: IDockviewPanel | undefined;
readonly totalPanels: number;
@ -241,13 +258,8 @@ export interface IDockviewComponent extends IBaseGrid<DockviewGroupPanel> {
readonly onWillShowOverlay: Event<WillShowOverlayLocationEvent>;
readonly orientation: Orientation;
updateOptions(options: DockviewComponentUpdateOptions): void;
moveGroupOrPanel(
referenceGroup: DockviewGroupPanel,
groupId: string,
itemId: string,
target: Position,
index?: number
): void;
moveGroupOrPanel(options: MoveGroupOrPanelOptions): void;
moveGroup(options: MoveGroupOptions): void;
doSetGroupActive: (group: DockviewGroupPanel, skipFocus?: boolean) => void;
removeGroup: (group: DockviewGroupPanel) => void;
options: DockviewComponentOptions;
@ -287,6 +299,9 @@ export interface IDockviewComponent extends IBaseGrid<DockviewGroupPanel> {
onWillClose?: (event: { id: string; window: Window }) => void;
}
): Promise<void>;
readonly onDidRemoveGroup: Event<DockviewGroupPanel>;
readonly onDidAddGroup: Event<DockviewGroupPanel>;
readonly onDidActiveGroupChange: Event<DockviewGroupPanel | undefined>;
}
export class DockviewComponent
@ -335,6 +350,10 @@ export class DockviewComponent
readonly onDidActivePanelChange: Event<IDockviewPanel | undefined> =
this._onDidActivePanelChange.event;
private readonly _onDidMovePanel = new Emitter<{
panel: IDockviewPanel;
}>();
private readonly _floatingGroups: DockviewFloatingGroupPanel[] = [];
private readonly _popoutGroups: {
window: PopoutWindow;
@ -344,6 +363,22 @@ export class DockviewComponent
}[] = [];
private readonly _rootDropTarget: Droptarget;
private _ignoreEvents = 0;
private readonly _onDidRemoveGroup = new Emitter<DockviewGroupPanel>();
readonly onDidRemoveGroup: Event<DockviewGroupPanel> =
this._onDidRemoveGroup.event;
protected readonly _onDidAddGroup = new Emitter<DockviewGroupPanel>();
readonly onDidAddGroup: Event<DockviewGroupPanel> =
this._onDidAddGroup.event;
private readonly _onDidActiveGroupChange = new Emitter<
DockviewGroupPanel | undefined
>();
readonly onDidActiveGroupChange: Event<DockviewGroupPanel | undefined> =
this._onDidActiveGroupChange.event;
get orientation(): Orientation {
return this.gridview.orientation;
}
@ -404,9 +439,28 @@ export class DockviewComponent
this._onDidLayoutFromJSON,
this._onDidDrop,
this._onWillDrop,
this._onDidMovePanel,
this._onDidAddGroup,
this._onDidRemoveGroup,
this._onDidActiveGroupChange,
this.onDidAdd((event) => {
if (!this._moving) {
this._onDidAddGroup.fire(event);
}
}),
this.onDidRemove((event) => {
if (!this._moving) {
this._onDidRemoveGroup.fire(event);
}
}),
this.onDidActiveChange((event) => {
if (!this._moving) {
this._onDidActiveGroupChange.fire(event);
}
}),
Event.any(
this.onDidAddGroup,
this.onDidRemoveGroup
this.onDidAdd,
this.onDidRemove
)(() => {
this.updateWatermark();
}),
@ -515,12 +569,16 @@ export class DockviewComponent
const data = getPanelData();
if (data) {
this.moveGroupOrPanel(
this.orthogonalize(event.position),
data.groupId,
data.panelId ?? undefined,
'center'
);
this.moveGroupOrPanel({
from: {
groupId: data.groupId,
panelId: data.panelId ?? undefined,
},
to: {
group: this.orthogonalize(event.position),
position: 'center',
},
});
} else {
this._onDidDrop.fire(
new DockviewDidDropEvent({
@ -574,7 +632,7 @@ export class DockviewComponent
panels.forEach((panel) => {
options.to.model.openPanel(panel, {
skipSetPanelActive: activePanel !== panel,
skipRender: activePanel !== panel,
});
});
}
@ -651,16 +709,27 @@ export class DockviewComponent
this.createGroup({ id: groupId });
group.model.renderContainer = overlayRenderContainer;
if (itemToPopout instanceof DockviewPanel) {
const panel =
referenceGroup.model.removePanel(itemToPopout);
group.model.openPanel(panel);
} else {
moveGroupWithoutDestroying({
from: referenceGroup,
to: group,
});
referenceGroup.api.setHidden(true);
if (!options?.overridePopoutGroup) {
this._onDidAddGroup.fire(group);
}
const isMoving = this._moving;
try {
this._moving = true;
if (itemToPopout instanceof DockviewPanel) {
const panel =
referenceGroup.model.removePanel(itemToPopout);
group.model.openPanel(panel);
} else {
moveGroupWithoutDestroying({
from: referenceGroup,
to: group,
});
referenceGroup.api.setHidden(true);
}
} finally {
this._moving = isMoving;
}
popoutContainer.classList.add('dv-dockview');
@ -674,6 +743,17 @@ export class DockviewComponent
getWindow: () => _window.window!,
};
popoutWindowDisposable.addDisposables(
group.api.onDidActiveChange((event) => {
if (event.isActive) {
_window.window?.focus();
}
}),
group.api.onWillFocus(() => {
_window.window?.focus();
})
);
const value = {
window: _window,
popoutGroup: group,
@ -697,10 +777,15 @@ export class DockviewComponent
overlayRenderContainer,
Disposable.from(() => {
if (this.getPanel(referenceGroup.id)) {
moveGroupWithoutDestroying({
from: group,
to: referenceGroup,
});
try {
this._moving = true;
moveGroupWithoutDestroying({
from: group,
to: referenceGroup,
});
} finally {
this._moving = isMoving;
}
if (referenceGroup.api.isHidden) {
referenceGroup.api.setHidden(false);
@ -718,6 +803,7 @@ export class DockviewComponent
this.overlayRenderContainer;
removedGroup.model.location = { type: 'grid' };
this.doAddGroup(removedGroup, [0]);
this.doSetGroupAndPanelActive(removedGroup);
}
})
);
@ -733,19 +819,27 @@ export class DockviewComponent
addFloatingGroup(
item: DockviewPanel | DockviewGroupPanel,
coord?: { x?: number; y?: number; height?: number; width?: number },
options?: { skipRemoveGroup?: boolean; inDragMode: boolean }
options?: {
skipRemoveGroup?: boolean;
inDragMode: boolean;
skipActiveGroup?: boolean;
}
): void {
let group: DockviewGroupPanel;
if (item instanceof DockviewPanel) {
group = this.createGroup();
this._onDidAddGroup.fire(group);
this.removePanel(item, {
removeEmptyGroup: true,
skipDispose: true,
});
this.movingLock(() =>
this.removePanel(item, {
removeEmptyGroup: true,
skipDispose: true,
skipSetActiveGroup: true,
})
);
group.model.openPanel(item);
group.model.openPanel(item, { skipSetGroupActive: true });
} else {
group = item;
@ -841,6 +935,11 @@ export class DockviewComponent
);
this._floatingGroups.push(floatingGroupPanel);
if (!options?.skipActiveGroup) {
this.doSetGroupAndPanelActive(group);
}
this.updateWatermark();
}
@ -952,8 +1051,8 @@ export class DockviewComponent
}
setActivePanel(panel: IDockviewPanel): void {
this.doSetGroupActive(panel.group);
panel.group.model.openPanel(panel);
this.doSetGroupAndPanelActive(panel.group);
}
moveToNext(options: MovementOptions = {}): void {
@ -1108,7 +1207,7 @@ export class DockviewComponent
activeView === panel.id;
group.model.openPanel(panel, {
skipSetPanelActive: !isActive,
skipRender: !isActive,
skipSetGroupActive: true,
});
}
@ -1241,10 +1340,6 @@ export class DockviewComponent
this.doSetGroupAndPanelActive(undefined);
}
if (hasActivePanel) {
this._onDidActivePanelChange.fire(undefined);
}
this.gridview.clear();
}
@ -1301,8 +1396,10 @@ export class DockviewComponent
const group = this.orthogonalize(
directionToPosition(<Direction>options.position.direction)
);
const panel = this.createPanel(options, group);
group.model.openPanel(panel);
this.doSetGroupAndPanelActive(group);
return panel;
}
} else {
@ -1318,26 +1415,30 @@ export class DockviewComponent
if (options.floating) {
const group = this.createGroup();
this._onDidAddGroup.fire(group);
const o =
typeof options.floating === 'object' &&
options.floating !== null
? options.floating
: {};
this.addFloatingGroup(group, o, {
inDragMode: false,
skipRemoveGroup: true,
skipActiveGroup: true,
});
this._onDidAddGroup.fire(group);
panel = this.createPanel(options, group);
group.model.openPanel(panel);
this.doSetGroupAndPanelActive(group);
} else if (
referenceGroup.api.location.type === 'floating' ||
target === 'center'
) {
panel = this.createPanel(options, referenceGroup);
referenceGroup.model.openPanel(panel);
this.doSetGroupAndPanelActive(referenceGroup);
} else {
const location = getGridLocation(referenceGroup.element);
const relativeLocation = getRelativeLocation(
@ -1348,30 +1449,31 @@ export class DockviewComponent
const group = this.createGroupAtLocation(relativeLocation);
panel = this.createPanel(options, group);
group.model.openPanel(panel);
this.doSetGroupAndPanelActive(group);
}
} else if (options.floating) {
const group = this.createGroup();
this._onDidAddGroup.fire(group);
const o =
typeof options.floating === 'object' &&
options.floating !== null
? options.floating
: {};
this.addFloatingGroup(group, o, {
inDragMode: false,
skipRemoveGroup: true,
skipActiveGroup: true,
});
this._onDidAddGroup.fire(group);
panel = this.createPanel(options, group);
group.model.openPanel(panel);
} else {
const group = this.createGroupAtLocation();
panel = this.createPanel(options, group);
group.model.openPanel(panel);
this.doSetGroupAndPanelActive(group);
} else {
const group = this.createGroupAtLocation();
panel = this.createPanel(options, group);
group.model.openPanel(panel);
}
return panel;
@ -1379,7 +1481,11 @@ export class DockviewComponent
removePanel(
panel: IDockviewPanel,
options: { removeEmptyGroup: boolean; skipDispose: boolean } = {
options: {
removeEmptyGroup: boolean;
skipDispose: boolean;
skipSetActiveGroup?: boolean;
} = {
removeEmptyGroup: true,
skipDispose: false,
}
@ -1392,7 +1498,9 @@ export class DockviewComponent
);
}
group.model.removePanel(panel);
group.model.removePanel(panel, {
skipSetActiveGroup: options.skipSetActiveGroup,
});
if (!options.skipDispose) {
this.overlayRenderContainer.detatch(panel);
@ -1400,7 +1508,7 @@ export class DockviewComponent
}
if (group.size === 0 && options.removeEmptyGroup) {
this.removeGroup(group);
this.removeGroup(group, { skipActive: options.skipSetActiveGroup });
}
}
@ -1486,6 +1594,7 @@ export class DockviewComponent
const group = this.orthogonalize(
directionToPosition(<Direction>options.direction)
);
this.doSetGroupAndPanelActive(group);
return group;
}
@ -1498,9 +1607,11 @@ export class DockviewComponent
target
);
this.doAddGroup(group, relativeLocation);
this.doSetGroupAndPanelActive(group);
return group;
} else {
this.doAddGroup(group);
this.doSetGroupAndPanelActive(group);
return group;
}
}
@ -1514,22 +1625,7 @@ export class DockviewComponent
}
| undefined
): void {
const panels = [...group.panels]; // reassign since group panels will mutate
for (const panel of panels) {
this.removePanel(panel, {
removeEmptyGroup: false,
skipDispose: options?.skipDispose ?? false,
});
}
const activePanel = this.activePanel;
this.doRemoveGroup(group, options);
if (this.activePanel !== activePanel) {
this._onDidActivePanelChange.fire(this.activePanel);
}
}
protected override doRemoveGroup(
@ -1542,6 +1638,19 @@ export class DockviewComponent
}
| undefined
): DockviewGroupPanel {
const panels = [...group.panels]; // reassign since group panels will mutate
if (!options?.skipDispose) {
for (const panel of panels) {
this.removePanel(panel, {
removeEmptyGroup: false,
skipDispose: options?.skipDispose ?? false,
});
}
}
const activePanel = this.activePanel;
if (group.api.location.type === 'floating') {
const floatingGroup = this._floatingGroups.find(
(_) => _.group === group
@ -1560,7 +1669,7 @@ export class DockviewComponent
if (!options?.skipActive && this._activeGroup === group) {
const groups = Array.from(this._groups.values());
this.doSetGroupActive(
this.doSetGroupAndPanelActive(
groups.length > 0 ? groups[0].value : undefined
);
}
@ -1597,7 +1706,7 @@ export class DockviewComponent
if (!options?.skipActive && this._activeGroup === group) {
const groups = Array.from(this._groups.values());
this.doSetGroupActive(
this.doSetGroupAndPanelActive(
groups.length > 0 ? groups[0].value : undefined
);
}
@ -1609,48 +1718,100 @@ export class DockviewComponent
throw new Error('failed to find popout group');
}
return super.doRemoveGroup(group, options);
const re = super.doRemoveGroup(group, options);
if (!options?.skipActive) {
if (this.activePanel !== activePanel) {
this._onDidActivePanelChange.fire(this.activePanel);
}
}
return re;
}
moveGroupOrPanel(
destinationGroup: DockviewGroupPanel,
sourceGroupId: string,
sourceItemId: string | undefined,
destinationTarget: Position,
destinationIndex?: number
): void {
private _moving = false;
movingLock<T>(func: () => T): T {
const isMoving = this._moving;
try {
this._moving = true;
return func();
} finally {
this._moving = isMoving;
}
}
moveGroupOrPanel(options: MoveGroupOrPanelOptions): void {
const destinationGroup = options.to.group;
const sourceGroupId = options.from.groupId;
const sourceItemId = options.from.panelId;
const destinationTarget = options.to.position;
const destinationIndex = options.to.index;
const sourceGroup = sourceGroupId
? this._groups.get(sourceGroupId)?.value
: undefined;
if (!sourceGroup) {
throw new Error(`Failed to find group id ${sourceGroupId}`);
}
if (sourceItemId === undefined) {
if (sourceGroup) {
this.moveGroup(
sourceGroup,
destinationGroup,
destinationTarget
);
}
/**
* Moving an entire group into another group
*/
this.moveGroup({
from: { group: sourceGroup },
to: {
group: destinationGroup,
position: destinationTarget,
},
});
return;
}
if (!destinationTarget || destinationTarget === 'center') {
const groupItem: IDockviewPanel | undefined =
sourceGroup?.model.removePanel(sourceItemId) ??
this.panels.find((panel) => panel.id === sourceItemId);
/**
* Dropping a panel within another group
*/
if (!groupItem) {
const removedPanel: IDockviewPanel | undefined = this.movingLock(
() =>
sourceGroup.model.removePanel(sourceItemId, {
skipEvents: true,
skipActive: true,
skipSetActiveGroup: true,
})
);
if (!removedPanel) {
throw new Error(`No panel with id ${sourceItemId}`);
}
if (sourceGroup?.model.size === 0) {
this.doRemoveGroup(sourceGroup);
if (sourceGroup.model.size === 0) {
// remove the group and do not set a new group as active
this.doRemoveGroup(sourceGroup, { skipActive: true });
}
destinationGroup.model.openPanel(groupItem, {
index: destinationIndex,
this.movingLock(() =>
destinationGroup.model.openPanel(removedPanel, {
index: destinationIndex,
skipSetGroupActive: true,
})
);
this.doSetGroupAndPanelActive(destinationGroup);
this._onDidMovePanel.fire({
panel: removedPanel,
});
} else {
/**
* Dropping a panel to the extremities of a group which will place that panel
* into an adjacent group
*/
const referenceLocation = getGridLocation(destinationGroup.element);
const targetLocation = getRelativeLocation(
this.gridview.orientation,
@ -1658,7 +1819,12 @@ export class DockviewComponent
destinationTarget
);
if (sourceGroup && sourceGroup.size < 2) {
if (sourceGroup.size < 2) {
/**
* If we are moving from a group which only has one panel left we will consider
* moving the group itself rather than moving the panel into a newly created group
*/
const [targetParentLocation, to] = tail(targetLocation);
if (sourceGroup.api.location.type === 'grid') {
@ -1675,31 +1841,45 @@ export class DockviewComponent
// if a group has one tab - we are essentially moving the 'group'
// which is equivalent to swapping two views in this case
this.gridview.moveView(sourceParentLocation, from, to);
return;
}
}
// source group will become empty so delete the group
const targetGroup = this.doRemoveGroup(sourceGroup, {
skipActive: true,
skipDispose: true,
});
const targetGroup = this.movingLock(() =>
this.doRemoveGroup(sourceGroup, {
skipActive: true,
skipDispose: true,
})
);
// after deleting the group we need to re-evaulate the ref location
const updatedReferenceLocation = getGridLocation(
destinationGroup.element
);
const location = getRelativeLocation(
this.gridview.orientation,
updatedReferenceLocation,
destinationTarget
);
this.doAddGroup(targetGroup, location);
this.movingLock(() => this.doAddGroup(targetGroup, location));
this.doSetGroupAndPanelActive(targetGroup);
} else {
const groupItem: IDockviewPanel | undefined =
sourceGroup?.model.removePanel(sourceItemId) ??
this.panels.find((panel) => panel.id === sourceItemId);
/**
* The group we are removing from has many panels, we need to remove the panels we are moving,
* create a new group, add the panels to that new group and add the new group in an appropiate position
*/
const removedPanel: IDockviewPanel | undefined =
this.movingLock(() =>
sourceGroup.model.removePanel(sourceItemId, {
skipEvents: true,
skipActive: true,
skipSetActiveGroup: true,
})
);
if (!groupItem) {
if (!removedPanel) {
throw new Error(`No panel with id ${sourceItemId}`);
}
@ -1710,86 +1890,121 @@ export class DockviewComponent
);
const group = this.createGroupAtLocation(dropLocation);
group.model.openPanel(groupItem);
this.movingLock(() =>
group.model.openPanel(removedPanel, {
skipEvents: true,
skipSetGroupActive: true,
})
);
this.doSetGroupAndPanelActive(group);
}
}
}
private moveGroup(
sourceGroup: DockviewGroupPanel,
referenceGroup: DockviewGroupPanel,
target: Position
): void {
if (sourceGroup) {
if (!target || target === 'center') {
const activePanel = sourceGroup.activePanel;
const panels = [...sourceGroup.panels].map((p) =>
sourceGroup.model.removePanel(p.id)
);
moveGroup(options: MoveGroupOptions): void {
const from = options.from.group;
const to = options.to.group;
const target = options.to.position;
if (sourceGroup?.model.size === 0) {
this.doRemoveGroup(sourceGroup);
}
if (target === 'center') {
const activePanel = from.activePanel;
const panels = this.movingLock(() =>
[...from.panels].map((p) =>
from.model.removePanel(p.id, {
skipRender: true,
skipEvents: true,
})
)
);
if (from?.model.size === 0) {
this.doRemoveGroup(from, { skipActive: true });
}
this.movingLock(() => {
for (const panel of panels) {
referenceGroup.model.openPanel(panel, {
skipSetPanelActive: panel !== activePanel,
to.model.openPanel(panel, {
skipRender: panel !== activePanel,
skipSetGroupActive: panel !== activePanel,
});
}
} else {
switch (sourceGroup.api.location.type) {
case 'grid':
this.gridview.removeView(
getGridLocation(sourceGroup.element)
);
break;
case 'floating': {
const selectedFloatingGroup = this._floatingGroups.find(
(x) => x.group === sourceGroup
);
if (!selectedFloatingGroup) {
throw new Error('failed to find floating group');
}
selectedFloatingGroup.dispose();
break;
}
case 'popout': {
const selectedPopoutGroup = this._popoutGroups.find(
(x) => x.popoutGroup === sourceGroup
);
if (!selectedPopoutGroup) {
throw new Error('failed to find popout group');
}
selectedPopoutGroup.disposable.dispose();
});
panels.forEach((panel) => {
this._onDidMovePanel.fire({ panel });
});
} else {
switch (from.api.location.type) {
case 'grid':
this.gridview.removeView(getGridLocation(from.element));
break;
case 'floating': {
const selectedFloatingGroup = this._floatingGroups.find(
(x) => x.group === from
);
if (!selectedFloatingGroup) {
throw new Error('failed to find floating group');
}
selectedFloatingGroup.dispose();
break;
}
case 'popout': {
const selectedPopoutGroup = this._popoutGroups.find(
(x) => x.popoutGroup === from
);
if (!selectedPopoutGroup) {
throw new Error('failed to find popout group');
}
selectedPopoutGroup.disposable.dispose();
}
const referenceLocation = getGridLocation(
referenceGroup.element
);
const dropLocation = getRelativeLocation(
this.gridview.orientation,
referenceLocation,
target
);
this.gridview.addView(
sourceGroup,
Sizing.Distribute,
dropLocation
);
}
const referenceLocation = getGridLocation(to.element);
const dropLocation = getRelativeLocation(
this.gridview.orientation,
referenceLocation,
target
);
this.gridview.addView(from, Sizing.Distribute, dropLocation);
from.panels.forEach((panel) => {
this._onDidMovePanel.fire({ panel });
});
}
}
doSetGroupAndPanelActive(
group: DockviewGroupPanel | undefined,
): void {
const activePanel = this.activePanel;
override doSetGroupActive(group: DockviewGroupPanel | undefined): void {
super.doSetGroupActive(group);
if (this._activeGroup?.activePanel !== activePanel) {
this._onDidActivePanelChange.fire(this._activeGroup?.activePanel);
const activePanel = this.activePanel;
if (
!this._moving &&
activePanel !== this._onDidActivePanelChange.value
) {
this._onDidActivePanelChange.fire(activePanel);
}
}
doSetGroupAndPanelActive(group: DockviewGroupPanel | undefined): void {
// if (
// this.activeGroup === group &&
// this._onDidActiveGroupChange.value !== group
// ) {
// this._onDidActiveGroupChange.fire(group);
// }
super.doSetGroupActive(group);
const activePanel = this.activePanel;
if (
!this._moving &&
activePanel !== this._onDidActivePanelChange.value
) {
this._onDidActivePanelChange.fire(activePanel);
}
}
@ -1836,7 +2051,14 @@ export class DockviewComponent
}),
view.model.onMove((event) => {
const { groupId, itemId, target, index } = event;
this.moveGroupOrPanel(view, groupId, itemId, target, index);
this.moveGroupOrPanel({
from: { groupId: groupId, panelId: itemId },
to: {
group: view,
position: target,
index,
},
});
}),
view.model.onDidDrop((event) => {
this._onDidDrop.fire(event);
@ -1853,13 +2075,27 @@ export class DockviewComponent
this._onWillShowOverlay.fire(event);
}),
view.model.onDidAddPanel((event) => {
if (this._moving) {
return;
}
this._onDidAddPanel.fire(event.panel);
}),
view.model.onDidRemovePanel((event) => {
if (this._moving) {
return;
}
this._onDidRemovePanel.fire(event.panel);
}),
view.model.onDidActivePanelChange((event) => {
this._onDidActivePanelChange.fire(event.panel);
if (this._moving) {
return;
}
if (event.panel !== this.activePanel) {
return;
}
if (this._onDidActivePanelChange.value !== event.panel) {
this._onDidActivePanelChange.fire(event.panel);
}
})
);

View File

@ -88,6 +88,13 @@ export class DockviewGroupPanel
);
}
override focus(): void {
if (!this.api.isActive) {
this.api.setActive();
}
super.focus();
}
initialize(): void {
this._model.initialize();
}

View File

@ -460,7 +460,7 @@ export class DockviewGroupPanelModel
// must be run after the constructor otherwise this.parent may not be
// correctly initialized
this.setActive(this.isActive, true, true);
this.setActive(this.isActive, true);
this.updateContainer();
if (this.accessor.options.createRightHeaderActionsElement) {
@ -604,17 +604,25 @@ export class DockviewGroupPanelModel
}
focus(): void {
this._activePanel?.focus?.();
this._activePanel?.focus();
}
public openPanel(
panel: IDockviewPanel,
options: {
index?: number;
skipSetPanelActive?: boolean;
skipRender?: boolean;
skipEvents?: boolean;
skipSetGroupActive?: boolean;
} = {}
): void {
/**
* set the panel group
* add the panel
* check if group active
* check if panel active
*/
if (
typeof options.index !== 'number' ||
options.index > this.panels.length
@ -622,34 +630,47 @@ export class DockviewGroupPanelModel
options.index = this.panels.length;
}
const skipSetPanelActive = !!options.skipSetPanelActive;
const skipSetGroupActive = !!options.skipSetGroupActive;
const skipRender = !!options.skipRender;
// ensure the group is updated before we fire any events
panel.updateParentGroup(this.groupPanel, true);
panel.updateParentGroup(this.groupPanel, { isGroupActive: true });
this.doAddPanel(panel, options.index, {
skipRender,
});
if (this._activePanel === panel) {
if (!skipSetGroupActive) {
this.accessor.doSetGroupActive(this.groupPanel);
}
this.contentContainer.renderPanel(panel, { asActive: true });
return;
}
this.doAddPanel(panel, options.index, skipSetPanelActive);
if (!skipSetPanelActive) {
if (!skipRender) {
this.doSetActivePanel(panel);
}
if (!skipSetGroupActive) {
if (!options.skipSetGroupActive) {
this.accessor.doSetGroupActive(this.groupPanel);
}
this.updateContainer();
if (!options.skipEvents) {
panel.runEvents();
this.updateContainer();
}
}
public removePanel(groupItemOrId: IDockviewPanel | string): IDockviewPanel {
public removePanel(
groupItemOrId: IDockviewPanel | string,
options: {
skipRender?: boolean;
skipEvents?: boolean;
skipActive?: boolean;
skipSetActiveGroup?: boolean;
} = {
skipRender: false,
skipEvents: false,
skipActive: false,
}
): IDockviewPanel {
const id =
typeof groupItemOrId === 'string'
? groupItemOrId
@ -661,7 +682,7 @@ export class DockviewGroupPanelModel
throw new Error('invalid operation');
}
return this._removePanel(panelToRemove);
return this._removePanel(panelToRemove, options);
}
public closeAllPanels(): void {
@ -692,15 +713,8 @@ export class DockviewGroupPanelModel
this.tabsContainer.setRightActionsElement(element);
}
public setActive(
isGroupActive: boolean,
skipFocus = false,
force = false
): void {
public setActive(isGroupActive: boolean, force = false): void {
if (!force && this.isActive === isGroupActive) {
if (!skipFocus) {
this._activePanel?.focus?.();
}
return;
}
@ -716,12 +730,6 @@ export class DockviewGroupPanelModel
}
this.updateContainer();
if (isGroupActive) {
if (!skipFocus) {
this._activePanel?.focus?.();
}
}
}
public layout(width: number, height: number): void {
@ -735,21 +743,35 @@ export class DockviewGroupPanelModel
}
}
private _removePanel(panel: IDockviewPanel): IDockviewPanel {
private _removePanel(
panel: IDockviewPanel,
options: {
skipRender?: boolean;
skipEvents?: boolean;
skipSetActiveGroup?: boolean;
}
): IDockviewPanel {
const isActivePanel = this._activePanel === panel;
this.doRemovePanel(panel);
if (isActivePanel && this.panels.length > 0) {
const nextPanel = this.mostRecentlyUsed[0];
this.openPanel(nextPanel);
this.openPanel(nextPanel, {
skipRender: options.skipRender,
skipEvents: options.skipEvents,
skipSetGroupActive: options.skipSetActiveGroup,
});
}
if (this._activePanel && this.panels.length === 0) {
this.doSetActivePanel(undefined);
}
this.updateContainer();
if (!options.skipEvents) {
this.updateContainer();
}
return panel;
}
@ -776,7 +798,9 @@ export class DockviewGroupPanelModel
private doAddPanel(
panel: IDockviewPanel,
index: number = this.panels.length,
skipSetActive = false
options: {
skipRender: boolean;
} = { skipRender: false }
): void {
const existingPanel = this._panels.indexOf(panel);
const hasExistingPanel = existingPanel > -1;
@ -786,7 +810,7 @@ export class DockviewGroupPanelModel
this.tabsContainer.openPanel(panel, index);
if (!skipSetActive) {
if (!options.skipRender) {
this.contentContainer.openPanel(panel);
}
@ -802,6 +826,10 @@ export class DockviewGroupPanelModel
}
private doSetActivePanel(panel: IDockviewPanel | undefined): void {
if (this._activePanel === panel) {
return;
}
this._activePanel = panel;
if (panel) {
@ -811,7 +839,9 @@ export class DockviewGroupPanelModel
this.updateMru(panel);
this._onDidActivePanelChange.fire({ panel });
this._onDidActivePanelChange.fire({
panel,
});
}
}
@ -829,7 +859,10 @@ export class DockviewGroupPanelModel
toggleClass(this.container, 'empty', this.isEmpty);
this.panels.forEach((panel) =>
panel.updateParentGroup(this.groupPanel, this.isActive)
// panel.updateParentGroup(this.groupPanel, {
// isGroupActive: this.isActive,
// })
panel.runEvents()
);
if (this.isEmpty && !this.watermark) {

View File

@ -18,11 +18,15 @@ export interface IDockviewPanel extends IDisposable, IPanel {
readonly api: DockviewPanelApi;
readonly title: string | undefined;
readonly params: Parameters | undefined;
updateParentGroup(group: DockviewGroupPanel, isGroupActive: boolean): void;
updateParentGroup(
group: DockviewGroupPanel,
options: { isGroupActive: boolean }
): void;
init(params: IGroupPanelInitParameters): void;
toJSON(): GroupviewPanelState;
setTitle(title: string): void;
update(event: PanelUpdateEvent): void;
runEvents(): void;
}
export class DockviewPanel
@ -94,16 +98,6 @@ export class DockviewPanel
}
focus(): void {
/**
* This is a progmatic request of focus -
* We need to tell the active panel that it can choose it's focus
* If the panel doesn't choose the panels container for it
*/
if (!this.api.isActive) {
this.api.setActive();
}
const event = new WillFocusEvent();
this.api._onWillFocus.fire(event);
@ -111,7 +105,9 @@ export class DockviewPanel
return;
}
this.group.model.focusContent();
if (!this.api.isActive) {
this.api.setActive();
}
}
public toJSON(): GroupviewPanelState {
@ -183,24 +179,49 @@ export class DockviewPanel
public updateParentGroup(
group: DockviewGroupPanel,
isGroupActive: boolean
options: { isGroupActive: boolean }
): void {
this._group = group;
this.api.group = group;
// const isPanelVisible = this._group.model.isPanelActive(this);
// const isActive = options.isGroupActive && isPanelVisible;
// if (this.api.isActive !== isActive) {
// this.api._onDidActiveChange.fire({
// isActive: options.isGroupActive && isPanelVisible,
// });
// }
// if (this.api.isVisible !== isPanelVisible) {
// this.api._onDidVisibilityChange.fire({
// isVisible: isPanelVisible,
// });
// }
// this.view.updateParentGroup(
// this._group,
// this._group.model.isPanelActive(this)
// );
}
runEvents(): void {
this.api.group = this._group;
const isPanelVisible = this._group.model.isPanelActive(this);
this.api._onDidActiveChange.fire({
isActive: isGroupActive && isPanelVisible,
});
this.api._onDidVisibilityChange.fire({
isVisible: isPanelVisible,
});
const isActive = this.group.api.isActive && isPanelVisible;
this.view.updateParentGroup(
this._group,
this._group.model.isPanelActive(this)
);
if (this.api.isActive !== isActive) {
this.api._onDidActiveChange.fire({
isActive: this.group.api.isActive && isPanelVisible,
});
}
if (this.api.isVisible !== isPanelVisible) {
this.api._onDidVisibilityChange.fire({
isVisible: isPanelVisible,
});
}
}
public layout(width: number, height: number): void {

View File

@ -25,9 +25,6 @@ export class DockviewPanelModel implements IDockviewPanelModel {
private readonly _content: IContentRenderer;
private readonly _tab: ITabRenderer;
private _group: DockviewGroupPanel | null = null;
private _isPanelVisible: boolean | null = null;
get content(): IContentRenderer {
return this._content;
}
@ -52,28 +49,10 @@ export class DockviewPanelModel implements IDockviewPanelModel {
}
updateParentGroup(
group: DockviewGroupPanel,
isPanelVisible: boolean
_group: DockviewGroupPanel,
_isPanelVisible: boolean
): void {
if (group !== this._group) {
this._group = group;
if (this._content.onGroupChange) {
this._content.onGroupChange(group);
}
if (this._tab.onGroupChange) {
this._tab.onGroupChange(group);
}
}
if (isPanelVisible !== this._isPanelVisible) {
this._isPanelVisible = isPanelVisible;
if (this._content.onPanelVisibleChange) {
this._content.onPanelVisibleChange(isPanelVisible);
}
if (this._tab.onPanelVisibleChange) {
this._tab.onPanelVisibleChange(isPanelVisible);
}
}
// noop
}
layout(width: number, height: number): void {

View File

@ -47,8 +47,6 @@ export interface ITabRenderer
> {
readonly element: HTMLElement;
init(parameters: GroupPanelPartInitParameters): void;
onGroupChange?(group: DockviewGroupPanel): void;
onPanelVisibleChange?(isPanelVisible: boolean): void;
}
export interface IContentRenderer
@ -60,8 +58,6 @@ export interface IContentRenderer
readonly onDidFocus?: Event<void>;
readonly onDidBlur?: Event<void>;
init(parameters: GroupPanelContentPartInitParameters): void;
onGroupChange?(group: DockviewGroupPanel): void;
onPanelVisibleChange?(isPanelVisible: boolean): void;
}
// watermark component

View File

@ -93,6 +93,10 @@ export class Emitter<T> implements IDisposable {
Emitter.ENABLE_TRACKING = isEnabled;
}
get value(): T | undefined {
return this._last;
}
constructor(private readonly options?: EmitterOptions) {}
get event(): Event<T> {

View File

@ -54,10 +54,6 @@ export interface IBaseGrid<T extends IGridPanelView> {
readonly activeGroup: T | undefined;
readonly size: number;
readonly groups: T[];
readonly onDidLayoutChange: Event<void>;
readonly onDidRemoveGroup: Event<T>;
readonly onDidAddGroup: Event<T>;
readonly onDidActiveGroupChange: Event<T | undefined>;
getPanel(id: string): T | undefined;
toJSON(): object;
fromJSON(data: any): void;
@ -70,6 +66,7 @@ export interface IBaseGrid<T extends IGridPanelView> {
exitMaximizedGroup(): void;
hasMaximizedGroup(): boolean;
readonly onDidMaxmizedGroupChange: Event<void>;
readonly onDidLayoutChange: Event<void>;
}
export abstract class BaseGrid<T extends IGridPanelView>
@ -85,15 +82,15 @@ export abstract class BaseGrid<T extends IGridPanelView>
private _onDidLayoutChange = new Emitter<void>();
readonly onDidLayoutChange = this._onDidLayoutChange.event;
protected readonly _onDidRemoveGroup = new Emitter<T>();
readonly onDidRemoveGroup: Event<T> = this._onDidRemoveGroup.event;
private readonly _onDidRemove = new Emitter<T>();
readonly onDidRemove: Event<T> = this._onDidRemove.event;
protected readonly _onDidAddGroup = new Emitter<T>();
readonly onDidAddGroup: Event<T> = this._onDidAddGroup.event;
private readonly _onDidAdd = new Emitter<T>();
readonly onDidAdd: Event<T> = this._onDidAdd.event;
private readonly _onDidActiveGroupChange = new Emitter<T | undefined>();
readonly onDidActiveGroupChange: Event<T | undefined> =
this._onDidActiveGroupChange.event;
private readonly _onDidActiveChange = new Emitter<T | undefined>();
readonly onDidActiveChange: Event<T | undefined> =
this._onDidActiveChange.event;
protected readonly _bufferOnDidLayoutChange = new TickDelayedEvent();
@ -167,9 +164,9 @@ export abstract class BaseGrid<T extends IGridPanelView>
this._bufferOnDidLayoutChange.fire();
}),
Event.any(
this.onDidAddGroup,
this.onDidRemoveGroup,
this.onDidActiveGroupChange
this.onDidAdd,
this.onDidRemove,
this.onDidActiveChange
)(() => {
this._bufferOnDidLayoutChange.fire();
}),
@ -222,9 +219,9 @@ export abstract class BaseGrid<T extends IGridPanelView>
): void {
this.gridview.addView(group, size ?? Sizing.Distribute, location);
this._onDidAddGroup.fire(group);
this._onDidAdd.fire(group);
this.doSetGroupActive(group);
// this.doSetGroupActive(group);
}
protected doRemoveGroup(
@ -243,10 +240,9 @@ export abstract class BaseGrid<T extends IGridPanelView>
item.disposable.dispose();
item.value.dispose();
this._groups.delete(group.id);
this._onDidRemove.fire(group);
}
this._onDidRemoveGroup.fire(group);
if (!options?.skipActive && this._activeGroup === group) {
const groups = Array.from(this._groups.values());
@ -276,7 +272,7 @@ export abstract class BaseGrid<T extends IGridPanelView>
this._activeGroup = group;
this._onDidActiveGroupChange.fire(group);
this._onDidActiveChange.fire(group);
}
public removeGroup(group: T): void {
@ -330,9 +326,9 @@ export abstract class BaseGrid<T extends IGridPanelView>
}
public dispose(): void {
this._onDidActiveGroupChange.dispose();
this._onDidAddGroup.dispose();
this._onDidRemoveGroup.dispose();
this._onDidActiveChange.dispose();
this._onDidAdd.dispose();
this._onDidRemove.dispose();
this._onDidLayoutChange.dispose();
for (const group of this.groups) {

View File

@ -71,6 +71,9 @@ export interface IGridviewComponent extends IBaseGrid<GridviewPanel> {
): void;
setVisible(panel: IGridviewPanel, visible: boolean): void;
setActive(panel: IGridviewPanel): void;
readonly onDidRemoveGroup: Event<GridviewPanel>;
readonly onDidAddGroup: Event<GridviewPanel>;
readonly onDidActiveGroupChange: Event<GridviewPanel | undefined>;
}
export class GridviewComponent
@ -83,6 +86,19 @@ export class GridviewComponent
private readonly _onDidLayoutfromJSON = new Emitter<void>();
readonly onDidLayoutFromJSON: Event<void> = this._onDidLayoutfromJSON.event;
private readonly _onDidRemoveGroup = new Emitter<GridviewPanel>();
readonly onDidRemoveGroup: Event<GridviewPanel> =
this._onDidRemoveGroup.event;
protected readonly _onDidAddGroup = new Emitter<GridviewPanel>();
readonly onDidAddGroup: Event<GridviewPanel> = this._onDidAddGroup.event;
private readonly _onDidActiveGroupChange = new Emitter<
GridviewPanel | undefined
>();
readonly onDidActiveGroupChange: Event<GridviewPanel | undefined> =
this._onDidActiveGroupChange.event;
get orientation(): Orientation {
return this.gridview.orientation;
}
@ -114,6 +130,21 @@ export class GridviewComponent
this._options = options;
this.addDisposables(
this._onDidAddGroup,
this._onDidRemoveGroup,
this._onDidActiveGroupChange,
this.onDidAdd((event) => {
this._onDidAddGroup.fire(event);
}),
this.onDidRemove((event) => {
this._onDidRemoveGroup.fire(event);
}),
this.onDidActiveChange((event) => {
this._onDidActiveGroupChange.fire(event);
})
);
if (!this.options.components) {
this.options.components = {};
}
@ -364,6 +395,7 @@ export class GridviewComponent
this.registerPanel(view);
this.doAddGroup(view, relativeLocation, options.size);
this.doSetGroupActive(view);
return view;
}

View File

@ -33,6 +33,8 @@ import DockviewMaximizeGroup from '@site/sandboxes/maximizegroup-dockview/src/ap
import DockviewRenderMode from '@site/sandboxes/rendermode-dockview/src/app';
import DockviewScrollbars from '@site/sandboxes/scrollbars-dockview/src/app';
import DockviewFocus from '@site/sandboxes/focus-dockview/src/app';
import { DocRef } from '@site/src/components/ui/reference/docRef';
import { attach as attachDockviewVanilla } from '@site/sandboxes/javascript/vanilla-dockview/src/app';
@ -42,6 +44,11 @@ import { attach as attachNativeDockview } from '@site/sandboxes/javascript/fullw
# Dockview
<MultiFrameworkContainer
sandboxId="focus-dockview"
react={DockviewFocus}
/>
## Introduction
Dockview is an abstraction built on top of [Gridviews](./gridview) where each view is a container of many tabbed panels.

View File

@ -5,14 +5,169 @@ import {
IDockviewPanelHeaderProps,
IDockviewPanelProps,
IDockviewHeaderActionsProps,
DockviewPanelApi,
DockviewPanelRenderer,
DockviewGroupLocation,
DockviewApi,
} from 'dockview';
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { v4 } from 'uuid';
import './app.scss';
interface PanelApiMetadata {
isActive: {
value: boolean;
count: number;
};
isVisible: {
value: boolean;
count: number;
};
isHidden: {
value: boolean;
count: number;
};
renderer: {
value: DockviewPanelRenderer;
count: number;
};
isGroupActive: {
value: boolean;
count: number;
};
groupChanged: {
count: number;
};
location: {
value: DockviewGroupLocation;
count: number;
};
didFocus: {
count: number;
};
dimensions: {
count: number;
height: number;
width: number;
};
}
function usePanelApiMetadata(api: DockviewPanelApi): PanelApiMetadata {
const [state, setState] = React.useState<PanelApiMetadata>({
isActive: { value: api.isActive, count: 0 },
isVisible: { value: api.isVisible, count: 0 },
isHidden: { value: api.isHidden, count: 0 },
renderer: { value: api.renderer, count: 0 },
isGroupActive: { value: api.isGroupActive, count: 0 },
groupChanged: { count: 0 },
location: { value: api.location, count: 0 },
didFocus: { count: 0 },
dimensions: { count: 0, height: api.height, width: api.width },
});
React.useEffect(() => {
const d1 = api.onDidActiveChange((event) => {
setState((_) => ({
..._,
isActive: {
value: event.isActive,
count: _.isActive.count + 1,
},
}));
});
const d2 = api.onDidActiveGroupChange((event) => {
setState((_) => ({
..._,
isGroupActive: {
value: event.isActive,
count: _.isGroupActive.count + 1,
},
}));
});
const d3 = api.onDidDimensionsChange((event) => {
setState((_) => ({
..._,
dimensions: {
count: _.dimensions.count + 1,
height: event.height,
width: event.width,
},
}));
});
const d4 = api.onDidFocusChange((event) => {
setState((_) => ({
..._,
didFocus: {
count: _.didFocus.count + 1,
},
}));
});
const d5 = api.onDidGroupChange((event) => {
setState((_) => ({
..._,
groupChanged: {
count: _.groupChanged.count + 1,
},
}));
});
const d6 = api.onDidHiddenChange((event) => {
setState((_) => ({
..._,
isHidden: {
value: event.isHidden,
count: _.isHidden.count + 1,
},
}));
});
const d7 = api.onDidLocationChange((event) => {
setState((_) => ({
..._,
location: {
value: event.location,
count: _.location.count + 1,
},
}));
});
const d8 = api.onDidRendererChange((event) => {
setState((_) => ({
..._,
renderer: {
value: event.renderer,
count: _.renderer.count + 1,
},
}));
});
const d9 = api.onDidVisibilityChange((event) => {
setState((_) => ({
..._,
isVisible: {
value: event.isVisible,
count: _.isVisible.count + 1,
},
}));
});
return () => {
d1.dispose();
d2.dispose();
d3.dispose();
d4.dispose();
d5.dispose();
d6.dispose();
d7.dispose();
d8.dispose();
d9.dispose();
};
}, [api]);
return state;
}
const components = {
default: (props: IDockviewPanelProps<{ title: string }>) => {
default: (props: IDockviewPanelProps) => {
const metadata = usePanelApiMetadata(props.api);
return (
<div
style={{
@ -22,6 +177,9 @@ const components = {
position: 'relative',
}}
>
<pre style={{ fontSize: '11px' }}>
{JSON.stringify(metadata, null, 4)}
</pre>
<span
style={{
position: 'absolute',
@ -198,7 +356,62 @@ const PrefixHeaderControls = (props: IDockviewHeaderActionsProps) => {
};
const DockviewDemo = (props: { theme?: string }) => {
const [logLines, setLogLines] = React.useState<any[]>([]);
const [panels, setPanels] = React.useState<string[]>([]);
const [groups, setGroups] = React.useState<string[]>([]);
const [api, setApi] = React.useState<DockviewApi>();
const [activePanel, setActivePanel] = React.useState<string>();
const [activeGroup, setActiveGroup] = React.useState<string>();
const onReady = (event: DockviewReadyEvent) => {
setApi(event.api);
event.api.onDidAddPanel((event) => {
setPanels((_) => [..._, event.id]);
setLogLines((line) => [`Panel Added ${event.id}`, ...line]);
});
event.api.onDidActivePanelChange((event) => {
setActivePanel(event?.id);
setLogLines((line) => [`Panel Activated ${event?.id}`, ...line]);
});
event.api.onDidRemovePanel((event) => {
setPanels((_) => {
const next = [..._];
next.splice(
next.findIndex((x) => x === event.id),
1
);
return next;
});
setLogLines((line) => [`Panel Removed ${event.id}`, ...line]);
});
event.api.onDidAddGroup((event) => {
setGroups((_) => [..._, event.id]);
setLogLines((line) => [`Group Added ${event.id}`, ...line]);
});
event.api.onDidRemoveGroup((event) => {
setGroups((_) => {
const next = [..._];
next.splice(
next.findIndex((x) => x === event.id),
1
);
return next;
});
setLogLines((line) => [`Group Removed ${event.id}`, ...line]);
});
event.api.onDidActiveGroupChange((event) => {
setActiveGroup(event?.id);
setLogLines((line) => [`Group Activated ${event?.id}`, ...line]);
});
const panel1 = event.api.addPanel({
id: 'panel_1',
component: 'default',
@ -257,16 +470,179 @@ const DockviewDemo = (props: { theme?: string }) => {
panel1.api.setActive();
};
const onAddPanel = () => {
api?.addPanel({
id: `id_${Date.now().toString()}`,
component: 'default',
title: `Tab ${counter++}`,
});
};
const onAddGroup = () => {
api?.addGroup();
};
return (
<DockviewReact
components={components}
defaultTabComponent={headerComponents.default}
rightHeaderActionsComponent={RightControls}
leftHeaderActionsComponent={LeftControls}
prefixHeaderActionsComponent={PrefixHeaderControls}
onReady={onReady}
className={props.theme || 'dockview-theme-abyss'}
/>
<div
style={{ height: '100%', display: 'flex', flexDirection: 'column' }}
>
<div>
<div
style={{
display: 'flex',
height: '25px',
padding: '2px 0px',
}}
>
<button onClick={onAddPanel}>Add Panel</button>
<button onClick={onAddGroup}>Add Group</button>
</div>
<div
style={{
display: 'flex',
height: '25px',
padding: '2px 0px',
}}
>
{panels.map((x) => {
const onClick = () => {
api?.getPanel(x)?.focus();
};
return (
<>
<button
onClick={onClick}
style={{
minWidth: '50px',
border: 'none',
margin: '0px 2px',
padding: '0px 2px',
backgroundColor:
activePanel === x
? 'blueviolet'
: 'dodgerblue',
borderRadius: '2px',
}}
>
{x}
</button>
<button
onClick={() => {
const panel = api?.getPanel(x);
if (panel) {
api?.addFloatingGroup(panel);
}
}}
>
float
</button>
<button
onClick={() => {
const panel = api?.getPanel(x);
if (panel) {
api?.addPopoutGroup(panel);
}
}}
>
pop
</button>
</>
);
})}
</div>
<div
style={{
display: 'flex',
height: '25px',
padding: '2px 0px',
}}
>
{groups.map((x) => {
const onClick = () => {
api?.getGroup(x)?.focus();
};
return (
<>
<button
onClick={onClick}
style={{
minWidth: '50px',
border: 'none',
margin: '0px 2px',
padding: '0px 2px',
backgroundColor:
activeGroup === x
? 'blueviolet'
: 'dodgerblue',
borderRadius: '2px',
}}
>
{x}
</button>
<button
onClick={() => {
const panel = api?.getGroup(x);
if (panel) {
api?.addFloatingGroup(panel);
}
}}
>
float
</button>
<button
onClick={() => {
const panel = api?.getGroup(x);
if (panel) {
api?.addPopoutGroup(panel);
}
}}
>
pop
</button>
</>
);
})}
</div>
</div>
<div style={{ flexGrow: 1 }}>
<DockviewReact
components={components}
defaultTabComponent={headerComponents.default}
rightHeaderActionsComponent={RightControls}
leftHeaderActionsComponent={LeftControls}
prefixHeaderActionsComponent={PrefixHeaderControls}
onReady={onReady}
className={props.theme || 'dockview-theme-abyss'}
/>
</div>
<div
style={{
height: '200px',
backgroundColor: 'black',
color: 'white',
overflow: 'auto',
}}
>
{logLines.map((line, i) => {
return (
<div key={i}>
<span
style={{
display: 'inline-block',
width: '30px',
color: 'gray',
borderRight: '1px solid gray',
marginRight: '4px',
}}
>
{logLines.length - i}
</span>
<span>{line}</span>
</div>
);
})}
</div>
</div>
);
};

View File

@ -0,0 +1,32 @@
{
"name": "focus-dockview",
"description": "",
"keywords": [
"dockview"
],
"version": "1.0.0",
"main": "src/index.tsx",
"dependencies": {
"dockview": "*",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@types/react": "^18.0.28",
"@types/react-dom": "^18.0.11",
"typescript": "^4.9.5",
"react-scripts": "*"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test --env=jsdom",
"eject": "react-scripts eject"
},
"browserslist": [
">0.2%",
"not dead",
"not ie <= 11",
"not op_mini all"
]
}

View File

@ -0,0 +1,44 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="theme-color" content="#000000">
<!--
manifest.json provides metadata used when your web app is added to the
homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json">
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
</head>
<body>
<noscript>
You need to enable JavaScript to run this app.
</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

View File

@ -0,0 +1,125 @@
import {
DockviewApi,
DockviewReact,
DockviewReadyEvent,
IDockviewPanelProps,
} from 'dockview';
import * as React from 'react';
const components = {
default: (props: IDockviewPanelProps) => {
React.useEffect(() => {
const d1 = props.api.onWillFocus((event) => {
console.log('willFocus');
});
const d2 = props.api.onDidActiveChange((event) => {
console.log(props.api.title, event, 'active');
});
const d3 = props.api.onDidActiveGroupChange((event) => {
console.log(
props.api.title,
props.api.group.api.isActive,
'active-group'
);
});
const d4 = props.api.onDidGroupChange((event) => {
console.log(
props.api.title,
props.api.group.id,
'group-change'
);
});
return () => {
d1.dispose();
d2.dispose();
d3.dispose();
};
}, [props.api]);
return (
<div style={{ padding: '20px', color: 'white' }}>
{props.api.title}
</div>
);
},
};
export const App: React.FC = (props: { theme?: string }) => {
const [api, setApi] = React.useState<DockviewApi>();
const onReady = (event: DockviewReadyEvent) => {
setApi(event.api);
event.api.addPanel({
id: 'panel_1',
title: 'Panel 1',
component: 'default',
});
event.api.addPanel({
id: 'panel_2',
title: 'Panel 2',
component: 'default',
});
// event.api.onDidAddPanel((event) => {
// console.log('add panel', event);
// });
// event.api.onDidActivePanelChange((event) => {
// console.log('active panel', event);
// });
// event.api.onDidRemovePanel((event) => {
// console.log('remove panel', event);
// });
};
return (
<div
style={{ height: '100%', display: 'flex', flexDirection: 'column' }}
>
<div>
<button
onClick={() => {
api?.getPanel('panel_1')?.focus();
}}
>
{'Focus Panel 1'}
</button>
<button
onClick={() => {
api?.getPanel('panel_2')?.focus();
}}
>
{'Focus Panel 2'}
</button>
<button
onClick={() => {
api?.getPanel('panel_1')?.api.setActive();
}}
>
{'Active Panel 1'}
</button>
<button
onClick={() => {
api?.getPanel('panel_2')?.api.setActive();
}}
>
{'Active Panel 2'}
</button>
</div>
<div style={{ flexGrow: 1 }}>
<DockviewReact
components={components}
onReady={onReady}
className={props.theme || 'dockview-theme-abyss'}
/>
</div>
</div>
);
};
export default App;

View File

@ -0,0 +1,20 @@
import { StrictMode } from 'react';
import * as ReactDOMClient from 'react-dom/client';
import './styles.css';
import 'dockview/dist/styles/dockview.css';
import App from './app';
const rootElement = document.getElementById('root');
if (rootElement) {
const root = ReactDOMClient.createRoot(rootElement);
root.render(
<StrictMode>
<div className="app">
<App />
</div>
</StrictMode>
);
}

View File

@ -0,0 +1,16 @@
body {
margin: 0px;
color: white;
font-family: sans-serif;
text-align: center;
}
#root {
height: 100vh;
width: 100vw;
}
.app {
height: 100%;
}

View File

@ -0,0 +1,18 @@
{
"compilerOptions": {
"outDir": "build/dist",
"module": "esnext",
"target": "es5",
"lib": ["es6", "dom"],
"sourceMap": true,
"allowJs": true,
"jsx": "react-jsx",
"moduleResolution": "node",
"rootDir": "src",
"forceConsistentCasingInFileNames": true,
"noImplicitReturns": true,
"noImplicitThis": true,
"noImplicitAny": true,
"strictNullChecks": true
}
}