mirror of
https://github.com/mathuo/dockview
synced 2025-02-13 11:55:45 +00:00
Merge pull request #364 from mathuo/360-investigate-opening-tabs-in-new-browser-window
work-in-progress: popout windows
This commit is contained in:
commit
eb0172f06c
@ -11,7 +11,7 @@ describe('groupDragHandler', () => {
|
||||
const groupMock = jest.fn<DockviewGroupPanel, []>(() => {
|
||||
const partial: Partial<DockviewGroupPanel> = {
|
||||
id: 'test_group_id',
|
||||
api: { isFloating: false } as any,
|
||||
api: { location: 'grid' } as any,
|
||||
};
|
||||
return partial as DockviewGroupPanel;
|
||||
});
|
||||
@ -48,12 +48,12 @@ describe('groupDragHandler', () => {
|
||||
|
||||
cut.dispose();
|
||||
});
|
||||
test('that the event is cancelled when isFloating and shiftKey=true', () => {
|
||||
test('that the event is cancelled when floating and shiftKey=true', () => {
|
||||
const element = document.createElement('div');
|
||||
|
||||
const groupMock = jest.fn<DockviewGroupPanel, []>(() => {
|
||||
const partial: Partial<DockviewGroupPanel> = {
|
||||
api: { isFloating: true } as any,
|
||||
api: { location: 'floating' } as any,
|
||||
};
|
||||
return partial as DockviewGroupPanel;
|
||||
});
|
||||
@ -85,7 +85,7 @@ describe('groupDragHandler', () => {
|
||||
|
||||
const groupMock = jest.fn<DockviewGroupPanel, []>(() => {
|
||||
const partial: Partial<DockviewGroupPanel> = {
|
||||
api: { isFloating: false } as any,
|
||||
api: { location: 'grid' } as any,
|
||||
};
|
||||
return partial as DockviewGroupPanel;
|
||||
});
|
||||
|
@ -478,7 +478,7 @@ describe('tabsContainer', () => {
|
||||
|
||||
const groupPanelMock = jest.fn<DockviewGroupPanel, []>(() => {
|
||||
return (<Partial<DockviewGroupPanel>>{
|
||||
api: { isFloating: false } as any,
|
||||
api: { location: 'grid' } as any,
|
||||
}) as DockviewGroupPanel;
|
||||
});
|
||||
|
||||
@ -538,7 +538,7 @@ describe('tabsContainer', () => {
|
||||
|
||||
const groupPanelMock = jest.fn<DockviewGroupPanel, []>(() => {
|
||||
return (<Partial<DockviewGroupPanel>>{
|
||||
api: { isFloating: true } as any,
|
||||
api: { location: 'floating' } as any,
|
||||
}) as DockviewGroupPanel;
|
||||
});
|
||||
|
||||
@ -591,7 +591,7 @@ describe('tabsContainer', () => {
|
||||
|
||||
const groupPanelMock = jest.fn<DockviewGroupPanel, []>(() => {
|
||||
return (<Partial<DockviewGroupPanel>>{
|
||||
api: { isFloating: true } as any,
|
||||
api: { location: 'floating' } as any,
|
||||
model: {} as any,
|
||||
}) as DockviewGroupPanel;
|
||||
});
|
||||
@ -653,7 +653,7 @@ describe('tabsContainer', () => {
|
||||
|
||||
const groupPanelMock = jest.fn<DockviewGroupPanel, []>(() => {
|
||||
return (<Partial<DockviewGroupPanel>>{
|
||||
api: { isFloating: true } as any,
|
||||
api: { location: 'grid' } as any,
|
||||
model: {} as any,
|
||||
}) as DockviewGroupPanel;
|
||||
});
|
||||
@ -723,7 +723,7 @@ describe('tabsContainer', () => {
|
||||
|
||||
const groupPanelMock = jest.fn<DockviewGroupPanel, []>(() => {
|
||||
return (<Partial<DockviewGroupPanel>>{
|
||||
api: { isFloating: true } as any,
|
||||
api: { location: 'grid' } as any,
|
||||
model: {} as any,
|
||||
}) as DockviewGroupPanel;
|
||||
});
|
||||
@ -793,7 +793,7 @@ describe('tabsContainer', () => {
|
||||
|
||||
const groupPanelMock = jest.fn<DockviewGroupPanel, []>(() => {
|
||||
return (<Partial<DockviewGroupPanel>>{
|
||||
api: { isFloating: true } as any,
|
||||
api: { location: 'grid' } as any,
|
||||
model: {} as any,
|
||||
}) as DockviewGroupPanel;
|
||||
});
|
||||
|
@ -2862,8 +2862,8 @@ describe('dockviewComponent', () => {
|
||||
floating: true,
|
||||
});
|
||||
|
||||
expect(panel1.group.api.isFloating).toBeFalsy();
|
||||
expect(panel2.group.api.isFloating).toBeTruthy();
|
||||
expect(panel1.group.api.location).toBe('grid');
|
||||
expect(panel2.group.api.location).toBe('floating');
|
||||
expect(dockview.groups.length).toBe(2);
|
||||
expect(dockview.panels.length).toBe(2);
|
||||
|
||||
@ -2874,8 +2874,8 @@ describe('dockviewComponent', () => {
|
||||
'right'
|
||||
);
|
||||
|
||||
expect(panel1.group.api.isFloating).toBeFalsy();
|
||||
expect(panel2.group.api.isFloating).toBeFalsy();
|
||||
expect(panel1.group.api.location).toBe('grid');
|
||||
expect(panel2.group.api.location).toBe('grid');
|
||||
expect(dockview.groups.length).toBe(2);
|
||||
expect(dockview.panels.length).toBe(2);
|
||||
});
|
||||
@ -2907,8 +2907,8 @@ describe('dockviewComponent', () => {
|
||||
floating: true,
|
||||
});
|
||||
|
||||
expect(panel1.group.api.isFloating).toBeFalsy();
|
||||
expect(panel2.group.api.isFloating).toBeTruthy();
|
||||
expect(panel1.group.api.location).toBe('grid');
|
||||
expect(panel2.group.api.location).toBe('floating');
|
||||
expect(dockview.groups.length).toBe(2);
|
||||
expect(dockview.panels.length).toBe(2);
|
||||
|
||||
@ -2919,8 +2919,8 @@ describe('dockviewComponent', () => {
|
||||
'center'
|
||||
);
|
||||
|
||||
expect(panel1.group.api.isFloating).toBeFalsy();
|
||||
expect(panel2.group.api.isFloating).toBeFalsy();
|
||||
expect(panel1.group.api.location).toBe('grid');
|
||||
expect(panel2.group.api.location).toBe('grid');
|
||||
expect(dockview.groups.length).toBe(1);
|
||||
expect(dockview.panels.length).toBe(2);
|
||||
});
|
||||
@ -2958,9 +2958,9 @@ describe('dockviewComponent', () => {
|
||||
floating: true,
|
||||
});
|
||||
|
||||
expect(panel1.group.api.isFloating).toBeFalsy();
|
||||
expect(panel2.group.api.isFloating).toBeTruthy();
|
||||
expect(panel3.group.api.isFloating).toBeTruthy();
|
||||
expect(panel1.group.api.location).toBe('grid');
|
||||
expect(panel2.group.api.location).toBe('floating');
|
||||
expect(panel3.group.api.location).toBe('floating');
|
||||
expect(dockview.groups.length).toBe(3);
|
||||
expect(dockview.panels.length).toBe(3);
|
||||
|
||||
@ -2971,9 +2971,9 @@ describe('dockviewComponent', () => {
|
||||
'center'
|
||||
);
|
||||
|
||||
expect(panel1.group.api.isFloating).toBeFalsy();
|
||||
expect(panel2.group.api.isFloating).toBeTruthy();
|
||||
expect(panel3.group.api.isFloating).toBeTruthy();
|
||||
expect(panel1.group.api.location).toBe('grid');
|
||||
expect(panel2.group.api.location).toBe('floating');
|
||||
expect(panel3.group.api.location).toBe('floating');
|
||||
expect(dockview.groups.length).toBe(2);
|
||||
expect(dockview.panels.length).toBe(3);
|
||||
});
|
||||
@ -3011,9 +3011,9 @@ describe('dockviewComponent', () => {
|
||||
position: { referencePanel: panel2 },
|
||||
});
|
||||
|
||||
expect(panel1.group.api.isFloating).toBeFalsy();
|
||||
expect(panel2.group.api.isFloating).toBeTruthy();
|
||||
expect(panel3.group.api.isFloating).toBeTruthy();
|
||||
expect(panel1.group.api.location).toBe('grid');
|
||||
expect(panel2.group.api.location).toBe('floating');
|
||||
expect(panel3.group.api.location).toBe('floating');
|
||||
expect(dockview.groups.length).toBe(2);
|
||||
expect(dockview.panels.length).toBe(3);
|
||||
|
||||
@ -3024,9 +3024,9 @@ describe('dockviewComponent', () => {
|
||||
'right'
|
||||
);
|
||||
|
||||
expect(panel1.group.api.isFloating).toBeFalsy();
|
||||
expect(panel2.group.api.isFloating).toBeFalsy();
|
||||
expect(panel3.group.api.isFloating).toBeFalsy();
|
||||
expect(panel1.group.api.location).toBe('grid');
|
||||
expect(panel2.group.api.location).toBe('grid');
|
||||
expect(panel3.group.api.location).toBe('grid');
|
||||
expect(dockview.groups.length).toBe(2);
|
||||
expect(dockview.panels.length).toBe(3);
|
||||
});
|
||||
@ -3064,9 +3064,9 @@ describe('dockviewComponent', () => {
|
||||
position: { referencePanel: panel2 },
|
||||
});
|
||||
|
||||
expect(panel1.group.api.isFloating).toBeFalsy();
|
||||
expect(panel2.group.api.isFloating).toBeTruthy();
|
||||
expect(panel3.group.api.isFloating).toBeTruthy();
|
||||
expect(panel1.group.api.location).toBe('grid');
|
||||
expect(panel2.group.api.location).toBe('floating');
|
||||
expect(panel3.group.api.location).toBe('floating');
|
||||
expect(dockview.groups.length).toBe(2);
|
||||
expect(dockview.panels.length).toBe(3);
|
||||
|
||||
@ -3077,9 +3077,9 @@ describe('dockviewComponent', () => {
|
||||
'center'
|
||||
);
|
||||
|
||||
expect(panel1.group.api.isFloating).toBeFalsy();
|
||||
expect(panel2.group.api.isFloating).toBeFalsy();
|
||||
expect(panel3.group.api.isFloating).toBeFalsy();
|
||||
expect(panel1.group.api.location).toBe('grid');
|
||||
expect(panel2.group.api.location).toBe('grid');
|
||||
expect(panel3.group.api.location).toBe('grid');
|
||||
expect(dockview.groups.length).toBe(1);
|
||||
expect(dockview.panels.length).toBe(3);
|
||||
});
|
||||
@ -3123,10 +3123,10 @@ describe('dockviewComponent', () => {
|
||||
floating: true,
|
||||
});
|
||||
|
||||
expect(panel1.group.api.isFloating).toBeFalsy();
|
||||
expect(panel2.group.api.isFloating).toBeTruthy();
|
||||
expect(panel3.group.api.isFloating).toBeTruthy();
|
||||
expect(panel4.group.api.isFloating).toBeTruthy();
|
||||
expect(panel1.group.api.location).toBe('grid');
|
||||
expect(panel2.group.api.location).toBe('floating');
|
||||
expect(panel3.group.api.location).toBe('floating');
|
||||
expect(panel4.group.api.location).toBe('floating');
|
||||
expect(dockview.groups.length).toBe(3);
|
||||
expect(dockview.panels.length).toBe(4);
|
||||
|
||||
@ -3137,10 +3137,10 @@ describe('dockviewComponent', () => {
|
||||
'center'
|
||||
);
|
||||
|
||||
expect(panel1.group.api.isFloating).toBeFalsy();
|
||||
expect(panel2.group.api.isFloating).toBeTruthy();
|
||||
expect(panel3.group.api.isFloating).toBeTruthy();
|
||||
expect(panel4.group.api.isFloating).toBeTruthy();
|
||||
expect(panel1.group.api.location).toBe('grid');
|
||||
expect(panel2.group.api.location).toBe('floating');
|
||||
expect(panel3.group.api.location).toBe('floating');
|
||||
expect(panel4.group.api.location).toBe('floating');
|
||||
expect(dockview.groups.length).toBe(2);
|
||||
expect(dockview.panels.length).toBe(4);
|
||||
});
|
||||
@ -3172,8 +3172,8 @@ describe('dockviewComponent', () => {
|
||||
floating: true,
|
||||
});
|
||||
|
||||
expect(panel1.group.api.isFloating).toBeFalsy();
|
||||
expect(panel2.group.api.isFloating).toBeTruthy();
|
||||
expect(panel1.group.api.location).toBe('grid');
|
||||
expect(panel2.group.api.location).toBe('floating');
|
||||
expect(dockview.groups.length).toBe(2);
|
||||
expect(dockview.panels.length).toBe(2);
|
||||
|
||||
@ -3184,8 +3184,8 @@ describe('dockviewComponent', () => {
|
||||
'right'
|
||||
);
|
||||
|
||||
expect(panel1.group.api.isFloating).toBeFalsy();
|
||||
expect(panel2.group.api.isFloating).toBeFalsy();
|
||||
expect(panel1.group.api.location).toBe('grid');
|
||||
expect(panel2.group.api.location).toBe('grid');
|
||||
expect(dockview.groups.length).toBe(2);
|
||||
expect(dockview.panels.length).toBe(2);
|
||||
});
|
||||
@ -3217,8 +3217,8 @@ describe('dockviewComponent', () => {
|
||||
floating: true,
|
||||
});
|
||||
|
||||
expect(panel1.group.api.isFloating).toBeFalsy();
|
||||
expect(panel2.group.api.isFloating).toBeTruthy();
|
||||
expect(panel1.group.api.location).toBe('grid');
|
||||
expect(panel2.group.api.location).toBe('floating');
|
||||
expect(dockview.groups.length).toBe(2);
|
||||
expect(dockview.panels.length).toBe(2);
|
||||
|
||||
@ -3229,8 +3229,8 @@ describe('dockviewComponent', () => {
|
||||
'center'
|
||||
);
|
||||
|
||||
expect(panel1.group.api.isFloating).toBeFalsy();
|
||||
expect(panel2.group.api.isFloating).toBeFalsy();
|
||||
expect(panel1.group.api.location).toBe('grid');
|
||||
expect(panel2.group.api.location).toBe('grid');
|
||||
expect(dockview.groups.length).toBe(1);
|
||||
expect(dockview.panels.length).toBe(2);
|
||||
});
|
||||
@ -3268,9 +3268,9 @@ describe('dockviewComponent', () => {
|
||||
floating: true,
|
||||
});
|
||||
|
||||
expect(panel1.group.api.isFloating).toBeFalsy();
|
||||
expect(panel2.group.api.isFloating).toBeTruthy();
|
||||
expect(panel3.group.api.isFloating).toBeTruthy();
|
||||
expect(panel1.group.api.location).toBe('grid');
|
||||
expect(panel2.group.api.location).toBe('floating');
|
||||
expect(panel3.group.api.location).toBe('floating');
|
||||
expect(dockview.groups.length).toBe(3);
|
||||
expect(dockview.panels.length).toBe(3);
|
||||
|
||||
@ -3281,9 +3281,9 @@ describe('dockviewComponent', () => {
|
||||
'center'
|
||||
);
|
||||
|
||||
expect(panel1.group.api.isFloating).toBeFalsy();
|
||||
expect(panel2.group.api.isFloating).toBeTruthy();
|
||||
expect(panel3.group.api.isFloating).toBeTruthy();
|
||||
expect(panel1.group.api.location).toBe('grid');
|
||||
expect(panel2.group.api.location).toBe('floating');
|
||||
expect(panel3.group.api.location).toBe('floating');
|
||||
expect(dockview.groups.length).toBe(2);
|
||||
expect(dockview.panels.length).toBe(3);
|
||||
});
|
||||
@ -3321,9 +3321,9 @@ describe('dockviewComponent', () => {
|
||||
position: { referencePanel: panel2 },
|
||||
});
|
||||
|
||||
expect(panel1.group.api.isFloating).toBeFalsy();
|
||||
expect(panel2.group.api.isFloating).toBeTruthy();
|
||||
expect(panel3.group.api.isFloating).toBeTruthy();
|
||||
expect(panel1.group.api.location).toBe('grid');
|
||||
expect(panel2.group.api.location).toBe('floating');
|
||||
expect(panel3.group.api.location).toBe('floating');
|
||||
expect(dockview.groups.length).toBe(2);
|
||||
expect(dockview.panels.length).toBe(3);
|
||||
|
||||
@ -3334,9 +3334,9 @@ describe('dockviewComponent', () => {
|
||||
'right'
|
||||
);
|
||||
|
||||
expect(panel1.group.api.isFloating).toBeFalsy();
|
||||
expect(panel2.group.api.isFloating).toBeFalsy();
|
||||
expect(panel3.group.api.isFloating).toBeTruthy();
|
||||
expect(panel1.group.api.location).toBe('grid');
|
||||
expect(panel2.group.api.location).toBe('grid');
|
||||
expect(panel3.group.api.location).toBe('floating');
|
||||
expect(dockview.groups.length).toBe(3);
|
||||
expect(dockview.panels.length).toBe(3);
|
||||
});
|
||||
@ -3374,9 +3374,9 @@ describe('dockviewComponent', () => {
|
||||
position: { referencePanel: panel2 },
|
||||
});
|
||||
|
||||
expect(panel1.group.api.isFloating).toBeFalsy();
|
||||
expect(panel2.group.api.isFloating).toBeTruthy();
|
||||
expect(panel3.group.api.isFloating).toBeTruthy();
|
||||
expect(panel1.group.api.location).toBe('grid');
|
||||
expect(panel2.group.api.location).toBe('floating');
|
||||
expect(panel3.group.api.location).toBe('floating');
|
||||
expect(dockview.groups.length).toBe(2);
|
||||
expect(dockview.panels.length).toBe(3);
|
||||
|
||||
@ -3387,9 +3387,9 @@ describe('dockviewComponent', () => {
|
||||
'center'
|
||||
);
|
||||
|
||||
expect(panel1.group.api.isFloating).toBeFalsy();
|
||||
expect(panel2.group.api.isFloating).toBeFalsy();
|
||||
expect(panel3.group.api.isFloating).toBeTruthy();
|
||||
expect(panel1.group.api.location).toBe('grid');
|
||||
expect(panel2.group.api.location).toBe('grid');
|
||||
expect(panel3.group.api.location).toBe('floating');
|
||||
expect(dockview.groups.length).toBe(2);
|
||||
expect(dockview.panels.length).toBe(3);
|
||||
});
|
||||
@ -3433,10 +3433,10 @@ describe('dockviewComponent', () => {
|
||||
floating: true,
|
||||
});
|
||||
|
||||
expect(panel1.group.api.isFloating).toBeFalsy();
|
||||
expect(panel2.group.api.isFloating).toBeTruthy();
|
||||
expect(panel3.group.api.isFloating).toBeTruthy();
|
||||
expect(panel4.group.api.isFloating).toBeTruthy();
|
||||
expect(panel1.group.api.location).toBe('grid');
|
||||
expect(panel2.group.api.location).toBe('floating');
|
||||
expect(panel3.group.api.location).toBe('floating');
|
||||
expect(panel4.group.api.location).toBe('floating');
|
||||
expect(dockview.groups.length).toBe(3);
|
||||
expect(dockview.panels.length).toBe(4);
|
||||
|
||||
@ -3447,10 +3447,10 @@ describe('dockviewComponent', () => {
|
||||
'center'
|
||||
);
|
||||
|
||||
expect(panel1.group.api.isFloating).toBeFalsy();
|
||||
expect(panel2.group.api.isFloating).toBeTruthy();
|
||||
expect(panel3.group.api.isFloating).toBeTruthy();
|
||||
expect(panel4.group.api.isFloating).toBeTruthy();
|
||||
expect(panel1.group.api.location).toBe('grid');
|
||||
expect(panel2.group.api.location).toBe('floating');
|
||||
expect(panel3.group.api.location).toBe('floating');
|
||||
expect(panel4.group.api.location).toBe('floating');
|
||||
expect(dockview.groups.length).toBe(3);
|
||||
expect(dockview.panels.length).toBe(4);
|
||||
});
|
||||
@ -3488,9 +3488,9 @@ describe('dockviewComponent', () => {
|
||||
floating: true,
|
||||
});
|
||||
|
||||
expect(panel1.group.api.isFloating).toBeFalsy();
|
||||
expect(panel2.group.api.isFloating).toBeFalsy();
|
||||
expect(panel3.group.api.isFloating).toBeTruthy();
|
||||
expect(panel1.group.api.location).toBe('grid');
|
||||
expect(panel2.group.api.location).toBe('grid');
|
||||
expect(panel3.group.api.location).toBe('floating');
|
||||
expect(dockview.groups.length).toBe(3);
|
||||
expect(dockview.panels.length).toBe(3);
|
||||
|
||||
@ -3501,9 +3501,9 @@ describe('dockviewComponent', () => {
|
||||
'center'
|
||||
);
|
||||
|
||||
expect(panel1.group.api.isFloating).toBeTruthy();
|
||||
expect(panel2.group.api.isFloating).toBeFalsy();
|
||||
expect(panel3.group.api.isFloating).toBeTruthy();
|
||||
expect(panel1.group.api.location).toBe('floating');
|
||||
expect(panel2.group.api.location).toBe('grid');
|
||||
expect(panel3.group.api.location).toBe('floating');
|
||||
expect(dockview.groups.length).toBe(2);
|
||||
expect(dockview.panels.length).toBe(3);
|
||||
});
|
||||
@ -3540,9 +3540,9 @@ describe('dockviewComponent', () => {
|
||||
floating: true,
|
||||
});
|
||||
|
||||
expect(panel1.group.api.isFloating).toBeFalsy();
|
||||
expect(panel2.group.api.isFloating).toBeFalsy();
|
||||
expect(panel3.group.api.isFloating).toBeTruthy();
|
||||
expect(panel1.group.api.location).toBe('grid');
|
||||
expect(panel2.group.api.location).toBe('grid');
|
||||
expect(panel3.group.api.location).toBe('floating');
|
||||
expect(dockview.groups.length).toBe(2);
|
||||
expect(dockview.panels.length).toBe(3);
|
||||
|
||||
@ -3553,9 +3553,9 @@ describe('dockviewComponent', () => {
|
||||
'center'
|
||||
);
|
||||
|
||||
expect(panel1.group.api.isFloating).toBeTruthy();
|
||||
expect(panel2.group.api.isFloating).toBeFalsy();
|
||||
expect(panel3.group.api.isFloating).toBeTruthy();
|
||||
expect(panel1.group.api.location).toBe('floating');
|
||||
expect(panel2.group.api.location).toBe('grid');
|
||||
expect(panel3.group.api.location).toBe('floating');
|
||||
expect(dockview.groups.length).toBe(2);
|
||||
expect(dockview.panels.length).toBe(3);
|
||||
});
|
||||
@ -3593,9 +3593,9 @@ describe('dockviewComponent', () => {
|
||||
floating: true,
|
||||
});
|
||||
|
||||
expect(panel1.group.api.isFloating).toBeFalsy();
|
||||
expect(panel2.group.api.isFloating).toBeFalsy();
|
||||
expect(panel3.group.api.isFloating).toBeTruthy();
|
||||
expect(panel1.group.api.location).toBe('grid');
|
||||
expect(panel2.group.api.location).toBe('grid');
|
||||
expect(panel3.group.api.location).toBe('floating');
|
||||
expect(dockview.groups.length).toBe(3);
|
||||
expect(dockview.panels.length).toBe(3);
|
||||
|
||||
@ -3606,9 +3606,9 @@ describe('dockviewComponent', () => {
|
||||
'center'
|
||||
);
|
||||
|
||||
expect(panel1.group.api.isFloating).toBeTruthy();
|
||||
expect(panel2.group.api.isFloating).toBeFalsy();
|
||||
expect(panel3.group.api.isFloating).toBeTruthy();
|
||||
expect(panel1.group.api.location).toBe('floating');
|
||||
expect(panel2.group.api.location).toBe('grid');
|
||||
expect(panel3.group.api.location).toBe('floating');
|
||||
expect(dockview.groups.length).toBe(2);
|
||||
expect(dockview.panels.length).toBe(3);
|
||||
});
|
||||
@ -3645,9 +3645,9 @@ describe('dockviewComponent', () => {
|
||||
floating: true,
|
||||
});
|
||||
|
||||
expect(panel1.group.api.isFloating).toBeFalsy();
|
||||
expect(panel2.group.api.isFloating).toBeFalsy();
|
||||
expect(panel3.group.api.isFloating).toBeTruthy();
|
||||
expect(panel1.group.api.location).toBe('grid');
|
||||
expect(panel2.group.api.location).toBe('grid');
|
||||
expect(panel3.group.api.location).toBe('floating');
|
||||
expect(dockview.groups.length).toBe(2);
|
||||
expect(dockview.panels.length).toBe(3);
|
||||
|
||||
@ -3658,9 +3658,9 @@ describe('dockviewComponent', () => {
|
||||
'center'
|
||||
);
|
||||
|
||||
expect(panel1.group.api.isFloating).toBeTruthy();
|
||||
expect(panel2.group.api.isFloating).toBeTruthy();
|
||||
expect(panel3.group.api.isFloating).toBeTruthy();
|
||||
expect(panel1.group.api.location).toBe('floating');
|
||||
expect(panel2.group.api.location).toBe('floating');
|
||||
expect(panel3.group.api.location).toBe('floating');
|
||||
expect(dockview.groups.length).toBe(1);
|
||||
expect(dockview.panels.length).toBe(3);
|
||||
});
|
||||
@ -3692,15 +3692,15 @@ describe('dockviewComponent', () => {
|
||||
position: { direction: 'right' },
|
||||
});
|
||||
|
||||
expect(panel1.group.api.isFloating).toBeFalsy();
|
||||
expect(panel2.group.api.isFloating).toBeFalsy();
|
||||
expect(panel1.group.api.location).toBe('grid');
|
||||
expect(panel2.group.api.location).toBe('grid');
|
||||
expect(dockview.groups.length).toBe(2);
|
||||
expect(dockview.panels.length).toBe(2);
|
||||
|
||||
dockview.addFloatingGroup(panel2);
|
||||
|
||||
expect(panel1.group.api.isFloating).toBeFalsy();
|
||||
expect(panel2.group.api.isFloating).toBeTruthy();
|
||||
expect(panel1.group.api.location).toBe('grid');
|
||||
expect(panel2.group.api.location).toBe('floating');
|
||||
expect(dockview.groups.length).toBe(2);
|
||||
expect(dockview.panels.length).toBe(2);
|
||||
});
|
||||
@ -3731,15 +3731,15 @@ describe('dockviewComponent', () => {
|
||||
component: 'default',
|
||||
});
|
||||
|
||||
expect(panel1.group.api.isFloating).toBeFalsy();
|
||||
expect(panel2.group.api.isFloating).toBeFalsy();
|
||||
expect(panel1.group.api.location).toBe('grid');
|
||||
expect(panel2.group.api.location).toBe('grid');
|
||||
expect(dockview.groups.length).toBe(1);
|
||||
expect(dockview.panels.length).toBe(2);
|
||||
|
||||
dockview.addFloatingGroup(panel2);
|
||||
|
||||
expect(panel1.group.api.isFloating).toBeFalsy();
|
||||
expect(panel2.group.api.isFloating).toBeTruthy();
|
||||
expect(panel1.group.api.location).toBe('grid');
|
||||
expect(panel2.group.api.location).toBe('floating');
|
||||
expect(dockview.groups.length).toBe(2);
|
||||
expect(dockview.panels.length).toBe(2);
|
||||
});
|
||||
@ -3771,15 +3771,15 @@ describe('dockviewComponent', () => {
|
||||
position: { direction: 'right' },
|
||||
});
|
||||
|
||||
expect(panel1.group.api.isFloating).toBeFalsy();
|
||||
expect(panel2.group.api.isFloating).toBeFalsy();
|
||||
expect(panel1.group.api.location).toBe('grid');
|
||||
expect(panel2.group.api.location).toBe('grid');
|
||||
expect(dockview.groups.length).toBe(2);
|
||||
expect(dockview.panels.length).toBe(2);
|
||||
|
||||
dockview.addFloatingGroup(panel2.group);
|
||||
|
||||
expect(panel1.group.api.isFloating).toBeFalsy();
|
||||
expect(panel2.group.api.isFloating).toBeTruthy();
|
||||
expect(panel1.group.api.location).toBe('grid');
|
||||
expect(panel2.group.api.location).toBe('floating');
|
||||
expect(dockview.groups.length).toBe(2);
|
||||
expect(dockview.panels.length).toBe(2);
|
||||
});
|
||||
@ -3810,15 +3810,15 @@ describe('dockviewComponent', () => {
|
||||
component: 'default',
|
||||
});
|
||||
|
||||
expect(panel1.group.api.isFloating).toBeFalsy();
|
||||
expect(panel2.group.api.isFloating).toBeFalsy();
|
||||
expect(panel1.group.api.location).toBe('grid');
|
||||
expect(panel2.group.api.location).toBe('grid');
|
||||
expect(dockview.groups.length).toBe(1);
|
||||
expect(dockview.panels.length).toBe(2);
|
||||
|
||||
dockview.addFloatingGroup(panel2.group);
|
||||
|
||||
expect(panel1.group.api.isFloating).toBeTruthy();
|
||||
expect(panel2.group.api.isFloating).toBeTruthy();
|
||||
expect(panel1.group.api.location).toBe('floating');
|
||||
expect(panel2.group.api.location).toBe('floating');
|
||||
expect(dockview.groups.length).toBe(1);
|
||||
expect(dockview.panels.length).toBe(2);
|
||||
});
|
||||
|
@ -42,6 +42,7 @@ import {
|
||||
GroupDragEvent,
|
||||
TabDragEvent,
|
||||
} from '../dockview/components/titlebar/tabsContainer';
|
||||
import { Box } from '../types';
|
||||
|
||||
export interface CommonApi<T = any> {
|
||||
readonly height: number;
|
||||
@ -804,4 +805,17 @@ export class DockviewApi implements CommonApi<SerializedDockview> {
|
||||
moveToPrevious(options?: MovementOptions): void {
|
||||
this.component.moveToPrevious(options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a popout group in a new Window
|
||||
*/
|
||||
addPopoutGroup(
|
||||
item: IDockviewPanel | DockviewGroupPanel,
|
||||
options?: {
|
||||
position?: Box;
|
||||
popoutUrl?: string;
|
||||
}
|
||||
): void {
|
||||
this.component.addPopoutGroup(item, options);
|
||||
}
|
||||
}
|
||||
|
@ -1,38 +1,39 @@
|
||||
import { Position } from '../dnd/droptarget';
|
||||
import { DockviewComponent } from '../dockview/dockviewComponent';
|
||||
import { DockviewGroupPanel } from '../dockview/dockviewGroupPanel';
|
||||
import { DockviewGroupLocation } from '../dockview/dockviewGroupPanelModel';
|
||||
import { Emitter, Event } from '../events';
|
||||
import { GridviewPanelApi, GridviewPanelApiImpl } from './gridviewPanelApi';
|
||||
|
||||
export interface DockviewGroupPanelApi extends GridviewPanelApi {
|
||||
readonly onDidFloatingStateChange: Event<DockviewGroupPanelFloatingChangeEvent>;
|
||||
readonly isFloating: boolean;
|
||||
readonly onDidRenderPositionChange: Event<DockviewGroupPanelFloatingChangeEvent>;
|
||||
readonly location: DockviewGroupLocation;
|
||||
moveTo(options: { group: DockviewGroupPanel; position?: Position }): void;
|
||||
}
|
||||
|
||||
export interface DockviewGroupPanelFloatingChangeEvent {
|
||||
readonly isFloating: boolean;
|
||||
readonly location: DockviewGroupLocation;
|
||||
}
|
||||
|
||||
export class DockviewGroupPanelApiImpl extends GridviewPanelApiImpl {
|
||||
private _group: DockviewGroupPanel | undefined;
|
||||
|
||||
readonly _onDidFloatingStateChange =
|
||||
readonly _onDidRenderPositionChange =
|
||||
new Emitter<DockviewGroupPanelFloatingChangeEvent>();
|
||||
readonly onDidFloatingStateChange: Event<DockviewGroupPanelFloatingChangeEvent> =
|
||||
this._onDidFloatingStateChange.event;
|
||||
readonly onDidRenderPositionChange: Event<DockviewGroupPanelFloatingChangeEvent> =
|
||||
this._onDidRenderPositionChange.event;
|
||||
|
||||
get isFloating() {
|
||||
get location(): DockviewGroupLocation {
|
||||
if (!this._group) {
|
||||
throw new Error(`DockviewGroupPanelApiImpl not initialized`);
|
||||
}
|
||||
return this._group.model.isFloating;
|
||||
return this._group.model.location;
|
||||
}
|
||||
|
||||
constructor(id: string, private readonly accessor: DockviewComponent) {
|
||||
super(id);
|
||||
|
||||
this.addDisposables(this._onDidFloatingStateChange);
|
||||
this.addDisposables(this._onDidRenderPositionChange);
|
||||
}
|
||||
|
||||
moveTo(options: { group: DockviewGroupPanel; position?: Position }): void {
|
||||
|
@ -38,7 +38,7 @@ export class GroupDragHandler extends DragHandler {
|
||||
}
|
||||
|
||||
override isCancelled(_event: DragEvent): boolean {
|
||||
if (this.group.api.isFloating && !_event.shiftKey) {
|
||||
if (this.group.api.location === 'floating' && !_event.shiftKey) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
@ -11,6 +11,7 @@ import {
|
||||
} from '../events';
|
||||
import { CompositeDisposable, MutableDisposable } from '../lifecycle';
|
||||
import { clamp } from '../math';
|
||||
import { Box } from '../types';
|
||||
|
||||
const bringElementToFront = (() => {
|
||||
let previous: HTMLElement | null = null;
|
||||
@ -48,11 +49,7 @@ export class Overlay extends CompositeDisposable {
|
||||
}
|
||||
|
||||
constructor(
|
||||
private readonly options: {
|
||||
height: number;
|
||||
width: number;
|
||||
left: number;
|
||||
top: number;
|
||||
private readonly options: Box & {
|
||||
container: HTMLElement;
|
||||
content: HTMLElement;
|
||||
minimumInViewportWidth?: number;
|
||||
@ -86,14 +83,7 @@ export class Overlay extends CompositeDisposable {
|
||||
});
|
||||
}
|
||||
|
||||
setBounds(
|
||||
bounds: Partial<{
|
||||
height: number;
|
||||
width: number;
|
||||
top: number;
|
||||
left: number;
|
||||
}> = {}
|
||||
): void {
|
||||
setBounds(bounds: Partial<Box> = {}): void {
|
||||
if (typeof bounds.height === 'number') {
|
||||
this._element.style.height = `${bounds.height}px`;
|
||||
}
|
||||
@ -139,7 +129,7 @@ export class Overlay extends CompositeDisposable {
|
||||
this._onDidChange.fire();
|
||||
}
|
||||
|
||||
toJSON(): { top: number; left: number; height: number; width: number } {
|
||||
toJSON(): Box {
|
||||
const container = this.options.container.getBoundingClientRect();
|
||||
const element = this._element.getBoundingClientRect();
|
||||
|
||||
|
@ -85,7 +85,7 @@ export class GreadyRenderContainer extends CompositeDisposable {
|
||||
toggleClass(
|
||||
focusContainer,
|
||||
'dv-render-overlay-float',
|
||||
panel.group.api.isFloating
|
||||
panel.group.api.location === 'floating'
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -76,7 +76,11 @@ export class ContentContainer
|
||||
|
||||
const data = getPanelData();
|
||||
|
||||
if (!data && event.shiftKey && !this.group.isFloating) {
|
||||
if (
|
||||
!data &&
|
||||
event.shiftKey &&
|
||||
this.group.location !== 'floating'
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -247,7 +247,7 @@ export class TabsContainer
|
||||
if (
|
||||
isFloatingGroupsEnabled &&
|
||||
event.shiftKey &&
|
||||
!this.group.api.isFloating
|
||||
this.group.api.location !== 'floating'
|
||||
) {
|
||||
event.preventDefault();
|
||||
|
||||
@ -350,7 +350,7 @@ export class TabsContainer
|
||||
!this.accessor.options.disableFloatingGroups;
|
||||
|
||||
const isFloatingWithOnePanel =
|
||||
this.group.api.isFloating && this.size === 1;
|
||||
this.group.api.location === 'floating' && this.size === 1;
|
||||
|
||||
if (
|
||||
isFloatingGroupsEnabled &&
|
||||
|
@ -7,7 +7,7 @@ import {
|
||||
import { directionToPosition, Droptarget, Position } from '../dnd/droptarget';
|
||||
import { tail, sequenceEquals, remove } from '../array';
|
||||
import { DockviewPanel, IDockviewPanel } from './dockviewPanel';
|
||||
import { CompositeDisposable } from '../lifecycle';
|
||||
import { CompositeDisposable, IDisposable } from '../lifecycle';
|
||||
import { Event, Emitter } from '../events';
|
||||
import { Watermark } from './components/watermark/watermark';
|
||||
import {
|
||||
@ -47,18 +47,45 @@ import { getPanelData } from '../dnd/dataTransfer';
|
||||
import { Parameters } from '../panel/types';
|
||||
import { Overlay } from '../dnd/overlay';
|
||||
import { toggleClass, watchElementResize } from '../dom';
|
||||
import {
|
||||
DockviewFloatingGroupPanel,
|
||||
IDockviewFloatingGroupPanel,
|
||||
} from './dockviewFloatingGroupPanel';
|
||||
import { DockviewFloatingGroupPanel } from './dockviewFloatingGroupPanel';
|
||||
import {
|
||||
GroupDragEvent,
|
||||
TabDragEvent,
|
||||
} from './components/titlebar/tabsContainer';
|
||||
import { PopoutWindow } from '../popoutWindow';
|
||||
import { Box } from '../types';
|
||||
import {
|
||||
GreadyRenderContainer,
|
||||
DockviewPanelRenderer,
|
||||
} from './components/greadyRenderContainer';
|
||||
import { DockviewPopoutGroupPanel } from './dockviewPopoutGroupPanel';
|
||||
|
||||
function getTheme(element: HTMLElement): string | undefined {
|
||||
function toClassList(element: HTMLElement) {
|
||||
const list: string[] = [];
|
||||
|
||||
for (let i = 0; i < element.classList.length; i++) {
|
||||
list.push(element.classList.item(i)!);
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
let theme: string | undefined = undefined;
|
||||
let parent: HTMLElement | null = element;
|
||||
|
||||
while (parent !== null) {
|
||||
theme = toClassList(parent).find((cls) =>
|
||||
cls.startsWith('dockview-theme-')
|
||||
);
|
||||
if (typeof theme === 'string') {
|
||||
break;
|
||||
}
|
||||
parent = parent.parentElement;
|
||||
}
|
||||
|
||||
return theme;
|
||||
}
|
||||
|
||||
const DEFAULT_FLOATING_GROUP_OVERFLOW_SIZE = 100;
|
||||
|
||||
@ -69,7 +96,12 @@ export interface PanelReference {
|
||||
|
||||
export interface SerializedFloatingGroup {
|
||||
data: GroupPanelViewState;
|
||||
position: { height: number; width: number; left: number; top: number };
|
||||
position: Box;
|
||||
}
|
||||
|
||||
export interface SerializedPopoutGroup {
|
||||
data: GroupPanelViewState;
|
||||
position: Box | null;
|
||||
}
|
||||
|
||||
export interface SerializedDockview {
|
||||
@ -82,6 +114,7 @@ export interface SerializedDockview {
|
||||
panels: Record<string, GroupviewPanelState>;
|
||||
activeGroup?: string;
|
||||
floatingGroups?: SerializedFloatingGroup[];
|
||||
popoutGroups?: SerializedPopoutGroup[];
|
||||
}
|
||||
|
||||
function typeValidate3(data: GroupPanelViewState, path: string): void {
|
||||
@ -196,7 +229,6 @@ export interface IDockviewComponent extends IBaseGrid<DockviewGroupPanel> {
|
||||
readonly activePanel: IDockviewPanel | undefined;
|
||||
readonly totalPanels: number;
|
||||
readonly panels: IDockviewPanel[];
|
||||
readonly floatingGroups: IDockviewFloatingGroupPanel[];
|
||||
readonly onDidDrop: Event<DockviewDropEvent>;
|
||||
readonly orientation: Orientation;
|
||||
updateOptions(options: DockviewComponentUpdateOptions): void;
|
||||
@ -237,6 +269,13 @@ export interface IDockviewComponent extends IBaseGrid<DockviewGroupPanel> {
|
||||
item: IDockviewPanel | DockviewGroupPanel,
|
||||
coord?: { x: number; y: number }
|
||||
): void;
|
||||
addPopoutGroup(
|
||||
item: IDockviewPanel | DockviewGroupPanel,
|
||||
options?: {
|
||||
position?: Box;
|
||||
popoutUrl?: string;
|
||||
}
|
||||
): void;
|
||||
}
|
||||
|
||||
export class DockviewComponent
|
||||
@ -277,7 +316,8 @@ export class DockviewComponent
|
||||
readonly onDidActivePanelChange: Event<IDockviewPanel | undefined> =
|
||||
this._onDidActivePanelChange.event;
|
||||
|
||||
readonly floatingGroups: DockviewFloatingGroupPanel[] = [];
|
||||
private readonly _floatingGroups: DockviewFloatingGroupPanel[] = [];
|
||||
private readonly _popoutGroups: DockviewPopoutGroupPanel[] = [];
|
||||
|
||||
get orientation(): Orientation {
|
||||
return this.gridview.orientation;
|
||||
@ -445,6 +485,73 @@ export class DockviewComponent
|
||||
this.updateWatermark();
|
||||
}
|
||||
|
||||
addPopoutGroup(
|
||||
item: DockviewPanel | DockviewGroupPanel,
|
||||
options?: {
|
||||
skipRemoveGroup?: boolean;
|
||||
position?: Box;
|
||||
popoutUrl?: string;
|
||||
}
|
||||
): void {
|
||||
let group: DockviewGroupPanel;
|
||||
let box: Box | undefined = options?.position;
|
||||
|
||||
if (item instanceof DockviewPanel) {
|
||||
group = this.createGroup();
|
||||
|
||||
this.removePanel(item, {
|
||||
removeEmptyGroup: true,
|
||||
skipDispose: true,
|
||||
});
|
||||
|
||||
group.model.openPanel(item);
|
||||
|
||||
if (!box) {
|
||||
box = this.element.getBoundingClientRect();
|
||||
}
|
||||
} else {
|
||||
group = item;
|
||||
|
||||
if (!box) {
|
||||
box = group.element.getBoundingClientRect();
|
||||
}
|
||||
|
||||
const skip =
|
||||
typeof options?.skipRemoveGroup === 'boolean' &&
|
||||
options.skipRemoveGroup;
|
||||
|
||||
if (!skip) {
|
||||
this.doRemoveGroup(item, { skipDispose: true });
|
||||
}
|
||||
}
|
||||
|
||||
const theme = getTheme(this.gridview.element);
|
||||
|
||||
const popoutWindow = new DockviewPopoutGroupPanel(group, {
|
||||
className: theme ?? '',
|
||||
popoutUrl: options?.popoutUrl ?? '/popout.html',
|
||||
box: {
|
||||
left: box.left,
|
||||
top: box.top,
|
||||
width: box.width,
|
||||
height: box.height,
|
||||
},
|
||||
});
|
||||
|
||||
popoutWindow.addDisposables(
|
||||
{
|
||||
dispose: () => {
|
||||
remove(this._popoutGroups, popoutWindow);
|
||||
},
|
||||
},
|
||||
popoutWindow.window.onDidClose(() => {
|
||||
this.doAddGroup(group, [0]);
|
||||
})
|
||||
);
|
||||
|
||||
this._popoutGroups.push(popoutWindow);
|
||||
}
|
||||
|
||||
addFloatingGroup(
|
||||
item: DockviewPanel | DockviewGroupPanel,
|
||||
coord?: { x?: number; y?: number; height?: number; width?: number },
|
||||
@ -473,7 +580,7 @@ export class DockviewComponent
|
||||
}
|
||||
}
|
||||
|
||||
group.model.isFloating = true;
|
||||
group.model.location = 'floating';
|
||||
|
||||
const overlayLeft =
|
||||
typeof coord?.x === 'number' ? Math.max(coord.x, 0) : 100;
|
||||
@ -544,14 +651,14 @@ export class DockviewComponent
|
||||
dispose: () => {
|
||||
disposable.dispose();
|
||||
|
||||
group.model.isFloating = false;
|
||||
remove(this.floatingGroups, floatingGroupPanel);
|
||||
group.model.location = 'grid';
|
||||
remove(this._floatingGroups, floatingGroupPanel);
|
||||
this.updateWatermark();
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
this.floatingGroups.push(floatingGroupPanel);
|
||||
this._floatingGroups.push(floatingGroupPanel);
|
||||
this.updateWatermark();
|
||||
}
|
||||
|
||||
@ -605,7 +712,7 @@ export class DockviewComponent
|
||||
}
|
||||
|
||||
if (hasFloatingGroupOptionsChanged) {
|
||||
for (const group of this.floatingGroups) {
|
||||
for (const group of this._floatingGroups) {
|
||||
switch (this.options.floatingGroupBounds) {
|
||||
case 'boundedWithinViewport':
|
||||
group.overlay.minimumInViewportHeight = undefined;
|
||||
@ -638,8 +745,8 @@ export class DockviewComponent
|
||||
): void {
|
||||
super.layout(width, height, forceResize);
|
||||
|
||||
if (this.floatingGroups) {
|
||||
for (const floating of this.floatingGroups) {
|
||||
if (this._floatingGroups) {
|
||||
for (const floating of this._floatingGroups) {
|
||||
// ensure floting groups stay within visible boundaries
|
||||
floating.overlay.setBounds();
|
||||
}
|
||||
@ -717,11 +824,20 @@ export class DockviewComponent
|
||||
return collection;
|
||||
}, {} as { [key: string]: GroupviewPanelState });
|
||||
|
||||
const floats: SerializedFloatingGroup[] = this.floatingGroups.map(
|
||||
(floatingGroup) => {
|
||||
const floats: SerializedFloatingGroup[] = this._floatingGroups.map(
|
||||
(group) => {
|
||||
return {
|
||||
data: floatingGroup.group.toJSON() as GroupPanelViewState,
|
||||
position: floatingGroup.overlay.toJSON(),
|
||||
data: group.group.toJSON() as GroupPanelViewState,
|
||||
position: group.overlay.toJSON(),
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
const popoutGroups: SerializedPopoutGroup[] = this._popoutGroups.map(
|
||||
(group) => {
|
||||
return {
|
||||
data: group.group.toJSON() as GroupPanelViewState,
|
||||
position: group.window.dimensions(),
|
||||
};
|
||||
}
|
||||
);
|
||||
@ -736,6 +852,10 @@ export class DockviewComponent
|
||||
result.floatingGroups = floats;
|
||||
}
|
||||
|
||||
if (popoutGroups.length > 0) {
|
||||
result.popoutGroups = popoutGroups;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -841,7 +961,20 @@ export class DockviewComponent
|
||||
);
|
||||
}
|
||||
|
||||
for (const floatingGroup of this.floatingGroups) {
|
||||
const serializedPopoutGroups = data.popoutGroups ?? [];
|
||||
|
||||
for (const serializedPopoutGroup of serializedPopoutGroups) {
|
||||
const { data, position } = serializedPopoutGroup;
|
||||
|
||||
const group = createGroupFromSerializedState(data);
|
||||
|
||||
this.addPopoutGroup(group, {
|
||||
skipRemoveGroup: true,
|
||||
position: position ?? undefined,
|
||||
});
|
||||
}
|
||||
|
||||
for (const floatingGroup of this._floatingGroups) {
|
||||
floatingGroup.overlay.setBounds();
|
||||
}
|
||||
|
||||
@ -875,7 +1008,7 @@ export class DockviewComponent
|
||||
}
|
||||
|
||||
// iterate over a reassigned array since original array will be modified
|
||||
for (const floatingGroup of [...this.floatingGroups]) {
|
||||
for (const floatingGroup of [...this._floatingGroups]) {
|
||||
floatingGroup.dispose();
|
||||
}
|
||||
|
||||
@ -999,7 +1132,10 @@ export class DockviewComponent
|
||||
panel = this.createPanel(options, group);
|
||||
group.model.openPanel(panel);
|
||||
this.doSetGroupAndPanelActive(group);
|
||||
} else if (referenceGroup.api.isFloating || target === 'center') {
|
||||
} else if (
|
||||
referenceGroup.api.location === 'floating' ||
|
||||
target === 'center'
|
||||
) {
|
||||
panel = this.createPanel(options, referenceGroup);
|
||||
referenceGroup.model.openPanel(panel);
|
||||
} else {
|
||||
@ -1083,7 +1219,7 @@ export class DockviewComponent
|
||||
}
|
||||
|
||||
private updateWatermark(): void {
|
||||
if (this.groups.filter((x) => !x.api.isFloating).length === 0) {
|
||||
if (this.groups.filter((x) => x.api.location === 'grid').length === 0) {
|
||||
if (!this.watermark) {
|
||||
this.watermark = this.createWatermarkComponent();
|
||||
|
||||
@ -1201,27 +1337,61 @@ export class DockviewComponent
|
||||
}
|
||||
| undefined
|
||||
): DockviewGroupPanel {
|
||||
const floatingGroup = this.floatingGroups.find(
|
||||
(_) => _.group === group
|
||||
);
|
||||
if (floatingGroup) {
|
||||
if (!options?.skipDispose) {
|
||||
floatingGroup.group.dispose();
|
||||
this._groups.delete(group.id);
|
||||
this._onDidRemoveGroup.fire(group);
|
||||
if (group.api.location === 'floating') {
|
||||
const floatingGroup = this._floatingGroups.find(
|
||||
(_) => _.group === group
|
||||
);
|
||||
|
||||
if (floatingGroup) {
|
||||
if (!options?.skipDispose) {
|
||||
floatingGroup.group.dispose();
|
||||
this._groups.delete(group.id);
|
||||
this._onDidRemoveGroup.fire(group);
|
||||
}
|
||||
|
||||
remove(this._floatingGroups, floatingGroup);
|
||||
floatingGroup.dispose();
|
||||
|
||||
if (!options?.skipActive && this._activeGroup === group) {
|
||||
const groups = Array.from(this._groups.values());
|
||||
|
||||
this.doSetGroupActive(
|
||||
groups.length > 0 ? groups[0].value : undefined
|
||||
);
|
||||
}
|
||||
|
||||
return floatingGroup.group;
|
||||
}
|
||||
|
||||
floatingGroup.dispose();
|
||||
throw new Error('failed to find floating group');
|
||||
}
|
||||
|
||||
if (!options?.skipActive && this._activeGroup === group) {
|
||||
const groups = Array.from(this._groups.values());
|
||||
if (group.api.location === 'popout') {
|
||||
const selectedGroup = this._popoutGroups.find(
|
||||
(_) => _.group === group
|
||||
);
|
||||
|
||||
this.doSetGroupActive(
|
||||
groups.length > 0 ? groups[0].value : undefined
|
||||
);
|
||||
if (selectedGroup) {
|
||||
if (!options?.skipDispose) {
|
||||
selectedGroup.group.dispose();
|
||||
this._groups.delete(group.id);
|
||||
this._onDidRemoveGroup.fire(group);
|
||||
}
|
||||
|
||||
selectedGroup.dispose();
|
||||
|
||||
if (!options?.skipActive && this._activeGroup === group) {
|
||||
const groups = Array.from(this._groups.values());
|
||||
|
||||
this.doSetGroupActive(
|
||||
groups.length > 0 ? groups[0].value : undefined
|
||||
);
|
||||
}
|
||||
|
||||
return selectedGroup.group;
|
||||
}
|
||||
|
||||
return floatingGroup.group;
|
||||
throw new Error('failed to find popout group');
|
||||
}
|
||||
|
||||
return super.doRemoveGroup(group, options);
|
||||
@ -1276,11 +1446,7 @@ export class DockviewComponent
|
||||
if (sourceGroup && sourceGroup.size < 2) {
|
||||
const [targetParentLocation, to] = tail(targetLocation);
|
||||
|
||||
const isFloating = this.floatingGroups.find(
|
||||
(x) => x.group === sourceGroup
|
||||
);
|
||||
|
||||
if (!isFloating) {
|
||||
if (sourceGroup.api.location === 'grid') {
|
||||
const sourceLocation = getGridLocation(sourceGroup.element);
|
||||
const [sourceParentLocation, from] = tail(sourceLocation);
|
||||
|
||||
@ -1356,16 +1522,29 @@ export class DockviewComponent
|
||||
});
|
||||
}
|
||||
} else {
|
||||
const floatingGroup = this.floatingGroups.find(
|
||||
(x) => x.group === sourceGroup
|
||||
);
|
||||
|
||||
if (floatingGroup) {
|
||||
floatingGroup.dispose();
|
||||
} else {
|
||||
this.gridview.removeView(
|
||||
getGridLocation(sourceGroup.element)
|
||||
);
|
||||
switch (sourceGroup.api.location) {
|
||||
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.group === sourceGroup
|
||||
);
|
||||
if (!selectedPopoutGroup) {
|
||||
throw new Error('failed to find popout group');
|
||||
}
|
||||
selectedPopoutGroup.dispose();
|
||||
}
|
||||
|
||||
const referenceLocation = getGridLocation(
|
||||
|
@ -130,6 +130,8 @@ export interface IDockviewGroupPanelModel extends IPanel {
|
||||
): boolean;
|
||||
}
|
||||
|
||||
export type DockviewGroupLocation = 'grid' | 'floating' | 'popout';
|
||||
|
||||
export class DockviewGroupPanelModel
|
||||
extends CompositeDisposable
|
||||
implements IDockviewGroupPanelModel
|
||||
@ -141,11 +143,12 @@ export class DockviewGroupPanelModel
|
||||
private watermark?: IWatermarkRenderer;
|
||||
private _isGroupActive = false;
|
||||
private _locked: DockviewGroupPanelLocked = false;
|
||||
private _isFloating = false;
|
||||
private _rightHeaderActions: IHeaderActionsRenderer | undefined;
|
||||
private _leftHeaderActions: IHeaderActionsRenderer | undefined;
|
||||
private _prefixHeaderActions: IHeaderActionsRenderer | undefined;
|
||||
|
||||
private _location: DockviewGroupLocation = 'grid';
|
||||
|
||||
private mostRecentlyUsed: IDockviewPanel[] = [];
|
||||
|
||||
private readonly _onDidChange = new Emitter<IViewSize | undefined>();
|
||||
@ -241,21 +244,45 @@ export class DockviewGroupPanelModel
|
||||
);
|
||||
}
|
||||
|
||||
get isFloating(): boolean {
|
||||
return this._isFloating;
|
||||
get location(): DockviewGroupLocation {
|
||||
return this._location;
|
||||
}
|
||||
|
||||
set isFloating(value: boolean) {
|
||||
this._isFloating = value;
|
||||
set location(value: DockviewGroupLocation) {
|
||||
this._location = value;
|
||||
|
||||
toggleClass(this.container, 'dv-groupview-floating', false);
|
||||
toggleClass(this.container, 'dv-groupview-popout', false);
|
||||
|
||||
switch (value) {
|
||||
case 'grid':
|
||||
this.contentContainer.dropTarget.setTargetZones([
|
||||
'top',
|
||||
'bottom',
|
||||
'left',
|
||||
'right',
|
||||
'center',
|
||||
]);
|
||||
break;
|
||||
case 'floating':
|
||||
this.contentContainer.dropTarget.setTargetZones(['center']);
|
||||
this.contentContainer.dropTarget.setTargetZones(
|
||||
value ? ['center'] : ['top', 'bottom', 'left', 'right', 'center']
|
||||
);
|
||||
|
||||
toggleClass(this.container, 'dv-groupview-floating', value);
|
||||
toggleClass(this.container, 'dv-groupview-floating', true);
|
||||
|
||||
this.groupPanel.api._onDidFloatingStateChange.fire({
|
||||
isFloating: this.isFloating,
|
||||
break;
|
||||
case 'popout':
|
||||
this.contentContainer.dropTarget.setTargetZones(['center']);
|
||||
|
||||
toggleClass(this.container, 'dv-groupview-popout', true);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
this.groupPanel.api._onDidRenderPositionChange.fire({
|
||||
location: this.location,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,43 @@
|
||||
import { CompositeDisposable } from '../lifecycle';
|
||||
import { PopoutWindow } from '../popoutWindow';
|
||||
import { Box } from '../types';
|
||||
import { DockviewGroupPanel } from './dockviewGroupPanel';
|
||||
|
||||
export class DockviewPopoutGroupPanel extends CompositeDisposable {
|
||||
readonly window: PopoutWindow;
|
||||
|
||||
constructor(
|
||||
readonly group: DockviewGroupPanel,
|
||||
private readonly options: {
|
||||
className: string;
|
||||
popoutUrl: string;
|
||||
box: Box;
|
||||
}
|
||||
) {
|
||||
super();
|
||||
|
||||
this.window = new PopoutWindow('test', options.className ?? '', {
|
||||
url: this.options.popoutUrl,
|
||||
left: this.options.box.left,
|
||||
top: this.options.box.top,
|
||||
width: this.options.box.width,
|
||||
height: this.options.box.height,
|
||||
});
|
||||
|
||||
group.model.location = 'popout';
|
||||
|
||||
this.addDisposables(
|
||||
this.window,
|
||||
{
|
||||
dispose: () => {
|
||||
group.model.location = 'grid';
|
||||
},
|
||||
},
|
||||
this.window.onDidClose(() => {
|
||||
this.dispose();
|
||||
})
|
||||
);
|
||||
|
||||
this.window.open(group.element);
|
||||
}
|
||||
}
|
@ -97,6 +97,7 @@ export interface DockviewComponentOptions extends DockviewRenderFunctions {
|
||||
minimumHeightWithinViewport?: number;
|
||||
minimumWidthWithinViewport?: number;
|
||||
};
|
||||
popoutUrl?: string;
|
||||
defaultRenderer?: DockviewPanelRenderer;
|
||||
debug?: boolean;
|
||||
}
|
||||
|
@ -186,6 +186,38 @@ export function quasiDefaultPrevented(event: Event): boolean {
|
||||
return (event as any)[QUASI_PREVENT_DEFAULT_KEY];
|
||||
}
|
||||
|
||||
export function addStyles(document: Document, styleSheetList: StyleSheetList) {
|
||||
const styleSheets = Array.from(styleSheetList);
|
||||
|
||||
for (const styleSheet of styleSheets) {
|
||||
if (styleSheet.href) {
|
||||
const link = document.createElement('link');
|
||||
link.href = styleSheet.href;
|
||||
link.type = styleSheet.type;
|
||||
link.rel = 'stylesheet';
|
||||
document.head.appendChild(link);
|
||||
}
|
||||
|
||||
let cssTexts: string[] = [];
|
||||
|
||||
try {
|
||||
if (styleSheet.cssRules) {
|
||||
cssTexts = Array.from(styleSheet.cssRules).map(
|
||||
(rule) => rule.cssText
|
||||
);
|
||||
}
|
||||
} catch (err) {
|
||||
// security errors (lack of permissions), ignore
|
||||
}
|
||||
|
||||
for (const rule of cssTexts) {
|
||||
const style = document.createElement('style');
|
||||
style.appendChild(document.createTextNode(rule));
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function getDomNodePagePosition(domNode: Element): {
|
||||
left: number;
|
||||
top: number;
|
||||
|
121
packages/dockview-core/src/popoutWindow.ts
Normal file
121
packages/dockview-core/src/popoutWindow.ts
Normal file
@ -0,0 +1,121 @@
|
||||
import { addStyles } from './dom';
|
||||
import { Emitter, addDisposableWindowListener } from './events';
|
||||
import { CompositeDisposable, IDisposable } from './lifecycle';
|
||||
import { Box } from './types';
|
||||
|
||||
export type PopoutWindowOptions = {
|
||||
url: string;
|
||||
} & Box;
|
||||
|
||||
export class PopoutWindow extends CompositeDisposable {
|
||||
private readonly _onDidClose = new Emitter<void>();
|
||||
readonly onDidClose = this._onDidClose.event;
|
||||
|
||||
private _window: { value: Window; disposable: IDisposable } | null = null;
|
||||
|
||||
constructor(
|
||||
private readonly id: string,
|
||||
private readonly className: string,
|
||||
private readonly options: PopoutWindowOptions
|
||||
) {
|
||||
super();
|
||||
|
||||
this.addDisposables(this._onDidClose, {
|
||||
dispose: () => {
|
||||
this.close();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
dimensions(): Box | null {
|
||||
if (!this._window) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const left = this._window.value.screenX;
|
||||
const top = this._window.value.screenY;
|
||||
const width = this._window.value.innerWidth;
|
||||
const height = this._window.value.innerHeight;
|
||||
|
||||
return { top, left, width, height };
|
||||
}
|
||||
|
||||
close(): void {
|
||||
if (this._window) {
|
||||
this._window.disposable.dispose();
|
||||
this._window.value.close();
|
||||
this._window = null;
|
||||
}
|
||||
}
|
||||
|
||||
open(content: HTMLElement): void {
|
||||
if (this._window) {
|
||||
throw new Error('instance of popout window is already open');
|
||||
}
|
||||
|
||||
const url = `${this.options.url}`;
|
||||
|
||||
const features = Object.entries({
|
||||
top: this.options.top,
|
||||
left: this.options.left,
|
||||
width: this.options.width,
|
||||
height: this.options.height,
|
||||
})
|
||||
.map(([key, value]) => `${key}=${value}`)
|
||||
.join(',');
|
||||
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/Window/open
|
||||
const externalWindow = window.open(url, this.id, features);
|
||||
|
||||
if (!externalWindow) {
|
||||
return;
|
||||
}
|
||||
|
||||
const disposable = new CompositeDisposable();
|
||||
|
||||
this._window = { value: externalWindow, disposable };
|
||||
|
||||
const grievingParent = content.parentElement;
|
||||
|
||||
const cleanUp = () => {
|
||||
grievingParent?.appendChild(content);
|
||||
this._onDidClose.fire();
|
||||
this._window = null;
|
||||
};
|
||||
|
||||
// prevent any default content from loading
|
||||
// externalWindow.document.body.replaceWith(document.createElement('div'));
|
||||
|
||||
disposable.addDisposables(
|
||||
addDisposableWindowListener(window, 'beforeunload', () => {
|
||||
cleanUp();
|
||||
this.close();
|
||||
})
|
||||
);
|
||||
|
||||
externalWindow.addEventListener('load', () => {
|
||||
const externalDocument = externalWindow.document;
|
||||
externalDocument.title = document.title;
|
||||
|
||||
const div = document.createElement('div');
|
||||
div.classList.add('dv-popout-window');
|
||||
div.style.position = 'absolute';
|
||||
div.style.width = '100%';
|
||||
div.style.height = '100%';
|
||||
div.style.top = '0px';
|
||||
div.style.left = '0px';
|
||||
div.classList.add(this.className);
|
||||
div.appendChild(content);
|
||||
|
||||
externalDocument.body.replaceChildren(div);
|
||||
externalDocument.body.classList.add(this.className);
|
||||
|
||||
addStyles(externalDocument, window.document.styleSheets);
|
||||
|
||||
externalWindow.addEventListener('beforeunload', () => {
|
||||
// TODO: indicate external window is closing
|
||||
cleanUp();
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
@ -1,3 +1,10 @@
|
||||
export type FunctionOrValue<T> = (() => T) | T;
|
||||
|
||||
export type Optional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>;
|
||||
|
||||
export interface Box {
|
||||
left: number;
|
||||
top: number;
|
||||
height: number;
|
||||
width: number;
|
||||
}
|
||||
|
@ -28,6 +28,7 @@ import DockviewWithIFrames from '@site/sandboxes/iframe-dockview/src/app';
|
||||
import DockviewFloating from '@site/sandboxes/floatinggroup-dockview/src/app';
|
||||
import DockviewLockedGroup from '@site/sandboxes/lockedgroup-dockview/src/app';
|
||||
import DockviewKeyboard from '@site/sandboxes/keyboard-dockview/src/app';
|
||||
import DockviewPopoutGroup from '@site/sandboxes/popoutgroup-dockview/src/app';
|
||||
import DockviewRenderMode from '@site/sandboxes/rendermode-dockview/src/app';
|
||||
|
||||
import { DocRef } from '@site/src/components/ui/reference/docRef';
|
||||
@ -356,7 +357,7 @@ Floating groups can be interacted with whilst holding the `shift` key activating
|
||||
<img style={{ width: '60%' }} src={useBaseUrl('/img/float_group.svg')} />
|
||||
|
||||
Floating groups can be programatically added through the dockview `api` method `api.addFloatingGroup(...)` and you can check whether
|
||||
a group is floating via the `group.api.isFloating` property. See examples for full code.
|
||||
a group is floating via the `group.api.location` property. See examples for full code.
|
||||
|
||||
You can control the bounding box of floating groups through the optional `floatingGroupBounds` options:
|
||||
|
||||
@ -370,6 +371,27 @@ You can control the bounding box of floating groups through the optional `floati
|
||||
react={DockviewFloating}
|
||||
/>
|
||||
|
||||
## Popout Groups
|
||||
|
||||
Dockview has built-in support for opening groups in new Windows.
|
||||
Each popout window can contain a single group with many panels and you can have as many popout
|
||||
windows as needed. You cannot dock multiple groups together in the same window.
|
||||
|
||||
To open an existing group in a new window
|
||||
|
||||
```tsx
|
||||
api.addPopoutGroup(group);
|
||||
```
|
||||
|
||||
From within a panel you may say
|
||||
|
||||
```tsx
|
||||
props.containerApi.addPopoutGroup(props.api.group);
|
||||
```
|
||||
|
||||
<DockviewPopoutGroup/>
|
||||
|
||||
|
||||
## Panels
|
||||
|
||||
### Add Panel
|
||||
|
@ -255,13 +255,15 @@ const LeftComponent = (props: IDockviewHeaderActionsProps) => {
|
||||
|
||||
const RightComponent = (props: IDockviewHeaderActionsProps) => {
|
||||
const [floating, setFloating] = React.useState<boolean>(
|
||||
props.api.isFloating
|
||||
props.api.location === 'floating'
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
const disposable = props.group.api.onDidFloatingStateChange((event) => [
|
||||
setFloating(event.isFloating),
|
||||
]);
|
||||
const disposable = props.group.api.onDidRenderPositionChange(
|
||||
(event) => {
|
||||
setFloating(event.location === 'floating');
|
||||
}
|
||||
);
|
||||
|
||||
return () => {
|
||||
disposable.dispose();
|
||||
|
32
packages/docs/sandboxes/popoutgroup-dockview/package.json
Normal file
32
packages/docs/sandboxes/popoutgroup-dockview/package.json
Normal file
@ -0,0 +1,32 @@
|
||||
{
|
||||
"name": "popout-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"
|
||||
]
|
||||
}
|
@ -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>
|
253
packages/docs/sandboxes/popoutgroup-dockview/src/app.tsx
Normal file
253
packages/docs/sandboxes/popoutgroup-dockview/src/app.tsx
Normal file
@ -0,0 +1,253 @@
|
||||
import {
|
||||
DockviewApi,
|
||||
DockviewGroupPanel,
|
||||
DockviewReact,
|
||||
DockviewReadyEvent,
|
||||
IDockviewHeaderActionsProps,
|
||||
IDockviewPanelProps,
|
||||
SerializedDockview,
|
||||
} from 'dockview';
|
||||
import * as React from 'react';
|
||||
import { Icon } from './utils';
|
||||
|
||||
const components = {
|
||||
default: (props: IDockviewPanelProps<{ title: string }>) => {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
height: '100%',
|
||||
padding: '20px',
|
||||
background: 'var(--dv-group-view-background-color)',
|
||||
}}
|
||||
>
|
||||
{props.params.title}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
const counter = (() => {
|
||||
let i = 0;
|
||||
|
||||
return {
|
||||
next: () => ++i,
|
||||
};
|
||||
})();
|
||||
|
||||
function loadDefaultLayout(api: DockviewApi) {
|
||||
api.addPanel({
|
||||
id: 'panel_1',
|
||||
component: 'default',
|
||||
});
|
||||
|
||||
api.addPanel({
|
||||
id: 'panel_2',
|
||||
component: 'default',
|
||||
});
|
||||
|
||||
api.addPanel({
|
||||
id: 'panel_3',
|
||||
component: 'default',
|
||||
});
|
||||
|
||||
api.addPanel({
|
||||
id: 'panel_4',
|
||||
component: 'default',
|
||||
});
|
||||
|
||||
api.addPanel({
|
||||
id: 'panel_5',
|
||||
component: 'default',
|
||||
position: { direction: 'right' },
|
||||
});
|
||||
|
||||
api.addPanel({
|
||||
id: 'panel_6',
|
||||
component: 'default',
|
||||
});
|
||||
}
|
||||
|
||||
let panelCount = 0;
|
||||
|
||||
function safeParse<T>(value: any): T | null {
|
||||
try {
|
||||
return JSON.parse(value) as T;
|
||||
} catch (err) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
const useLocalStorage = <T,>(
|
||||
key: string
|
||||
): [T | null, (setter: T | null) => void] => {
|
||||
const [state, setState] = React.useState<T | null>(
|
||||
safeParse(localStorage.getItem(key))
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
const _state = localStorage.getItem('key');
|
||||
try {
|
||||
if (_state !== null) {
|
||||
setState(JSON.parse(_state));
|
||||
}
|
||||
} catch (err) {
|
||||
//
|
||||
}
|
||||
}, [key]);
|
||||
|
||||
return [
|
||||
state,
|
||||
(_state: T | null) => {
|
||||
if (_state === null) {
|
||||
localStorage.removeItem(key);
|
||||
} else {
|
||||
localStorage.setItem(key, JSON.stringify(_state));
|
||||
setState(_state);
|
||||
}
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
export const DockviewPersistance = (props: { theme?: string }) => {
|
||||
const [api, setApi] = React.useState<DockviewApi>();
|
||||
const [layout, setLayout] =
|
||||
useLocalStorage<SerializedDockview>('floating.layout');
|
||||
|
||||
const [disableFloatingGroups, setDisableFloatingGroups] =
|
||||
React.useState<boolean>(false);
|
||||
|
||||
const load = (api: DockviewApi) => {
|
||||
api.clear();
|
||||
if (layout) {
|
||||
try {
|
||||
api.fromJSON(layout);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
api.clear();
|
||||
loadDefaultLayout(api);
|
||||
}
|
||||
} else {
|
||||
loadDefaultLayout(api);
|
||||
}
|
||||
};
|
||||
|
||||
const onReady = (event: DockviewReadyEvent) => {
|
||||
load(event.api);
|
||||
setApi(event.api);
|
||||
};
|
||||
|
||||
const [options, setOptions] = React.useState<
|
||||
'boundedWithinViewport' | undefined
|
||||
>(undefined);
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
height: '400px',
|
||||
}}
|
||||
>
|
||||
<div style={{ height: '25px' }}>
|
||||
<button
|
||||
onClick={() => {
|
||||
if (api) {
|
||||
setLayout(api.toJSON());
|
||||
}
|
||||
}}
|
||||
>
|
||||
Save
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
if (api) {
|
||||
load(api);
|
||||
}
|
||||
}}
|
||||
>
|
||||
Load
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
api!.clear();
|
||||
setLayout(null);
|
||||
}}
|
||||
>
|
||||
Clear
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
flexGrow: 1,
|
||||
}}
|
||||
>
|
||||
<DockviewReact
|
||||
onReady={onReady}
|
||||
components={components}
|
||||
watermarkComponent={Watermark}
|
||||
leftHeaderActionsComponent={LeftComponent}
|
||||
rightHeaderActionsComponent={RightComponent}
|
||||
disableFloatingGroups={disableFloatingGroups}
|
||||
floatingGroupBounds={options}
|
||||
className={`${props.theme || 'dockview-theme-abyss'}`}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const LeftComponent = (props: IDockviewHeaderActionsProps) => {
|
||||
const onClick = () => {
|
||||
props.containerApi.addPanel({
|
||||
id: (++panelCount).toString(),
|
||||
title: `Tab ${panelCount}`,
|
||||
component: 'default',
|
||||
position: { referenceGroup: props.group },
|
||||
});
|
||||
};
|
||||
return (
|
||||
<div style={{ height: '100%', color: 'white', padding: '0px 4px' }}>
|
||||
<Icon onClick={onClick} icon={'add'} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const RightComponent = (props: IDockviewHeaderActionsProps) => {
|
||||
const [popout, setPopout] = React.useState<boolean>(
|
||||
props.api.location === 'popout'
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
const disposable = props.group.api.onDidRenderPositionChange(
|
||||
(event) => [setPopout(event.location === 'popout')]
|
||||
);
|
||||
|
||||
return () => {
|
||||
disposable.dispose();
|
||||
};
|
||||
}, [props.group.api]);
|
||||
|
||||
const onClick = () => {
|
||||
if (popout) {
|
||||
const group = props.containerApi.addGroup();
|
||||
props.group.api.moveTo({ group });
|
||||
} else {
|
||||
props.containerApi.addPopoutGroup(props.group);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{ height: '100%', color: 'white', padding: '0px 4px' }}>
|
||||
<Icon
|
||||
onClick={onClick}
|
||||
icon={popout ? 'jump_to_element' : 'back_to_tab'}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default DockviewPersistance;
|
||||
|
||||
const Watermark = () => {
|
||||
return <div style={{ color: 'white', padding: '8px' }}>watermark</div>;
|
||||
};
|
20
packages/docs/sandboxes/popoutgroup-dockview/src/index.tsx
Normal file
20
packages/docs/sandboxes/popoutgroup-dockview/src/index.tsx
Normal 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>
|
||||
);
|
||||
}
|
16
packages/docs/sandboxes/popoutgroup-dockview/src/styles.css
Normal file
16
packages/docs/sandboxes/popoutgroup-dockview/src/styles.css
Normal 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%;
|
||||
|
||||
}
|
30
packages/docs/sandboxes/popoutgroup-dockview/src/utils.tsx
Normal file
30
packages/docs/sandboxes/popoutgroup-dockview/src/utils.tsx
Normal file
@ -0,0 +1,30 @@
|
||||
import * as React from 'react';
|
||||
|
||||
export const Icon = (props: {
|
||||
icon: string;
|
||||
title?: string;
|
||||
onClick?: (event: React.MouseEvent) => void;
|
||||
}) => {
|
||||
return (
|
||||
<div
|
||||
title={props.title}
|
||||
style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
width: '30px',
|
||||
height: '100%',
|
||||
|
||||
fontSize: '18px',
|
||||
}}
|
||||
onClick={props.onClick}
|
||||
>
|
||||
<span
|
||||
style={{ fontSize: 'inherit', cursor: 'pointer' }}
|
||||
className="material-symbols-outlined"
|
||||
>
|
||||
{props.icon}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
};
|
18
packages/docs/sandboxes/popoutgroup-dockview/tsconfig.json
Normal file
18
packages/docs/sandboxes/popoutgroup-dockview/tsconfig.json
Normal 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
|
||||
}
|
||||
}
|
@ -359,6 +359,11 @@
|
||||
"signature": "<T extends object = Parameters>(options: AddPanelOptions<T>): IDockviewPanel",
|
||||
"type": "method"
|
||||
},
|
||||
{
|
||||
"name": "addPopoutGroup",
|
||||
"signature": "(item: IDockviewPanel | DockviewGroupPanel, options?: { position: Box, skipRemoveGroup: boolean }): void",
|
||||
"type": "method"
|
||||
},
|
||||
{
|
||||
"name": "clear",
|
||||
"comment": {
|
||||
@ -1525,11 +1530,21 @@
|
||||
"signature": "Event<void>",
|
||||
"type": "property"
|
||||
},
|
||||
{
|
||||
"name": "onDidRendererChange",
|
||||
"signature": "Event<RendererChangedEvent>",
|
||||
"type": "property"
|
||||
},
|
||||
{
|
||||
"name": "onDidVisibilityChange",
|
||||
"signature": "Event<VisibilityEvent>",
|
||||
"type": "property"
|
||||
},
|
||||
{
|
||||
"name": "renderer",
|
||||
"signature": "DockviewPanelRenderer",
|
||||
"type": "property"
|
||||
},
|
||||
{
|
||||
"name": "title",
|
||||
"signature": "string | undefined",
|
||||
@ -1563,6 +1578,11 @@
|
||||
"signature": "(): void",
|
||||
"type": "method"
|
||||
},
|
||||
{
|
||||
"name": "setRenderer",
|
||||
"signature": "(renderer: DockviewPanelRenderer): void",
|
||||
"type": "method"
|
||||
},
|
||||
{
|
||||
"name": "setSize",
|
||||
"signature": "(event: SizeEvent): void",
|
||||
@ -2005,6 +2025,16 @@
|
||||
"signature": "PanelCollection<IDockviewPanelProps<any>>",
|
||||
"type": "property"
|
||||
},
|
||||
{
|
||||
"name": "debug",
|
||||
"signature": "boolean",
|
||||
"type": "property"
|
||||
},
|
||||
{
|
||||
"name": "defaultRenderer",
|
||||
"signature": "DockviewPanelRenderer",
|
||||
"type": "property"
|
||||
},
|
||||
{
|
||||
"name": "defaultTabComponent",
|
||||
"signature": "FunctionComponent<IDockviewPanelHeaderProps<any>>",
|
||||
|
5
packages/docs/src/pages/popout.tsx
Normal file
5
packages/docs/src/pages/popout.tsx
Normal file
@ -0,0 +1,5 @@
|
||||
import React from 'react';
|
||||
|
||||
export default function Popout() {
|
||||
return <div className="popout-anchor" />;
|
||||
}
|
Loading…
Reference in New Issue
Block a user