mirror of
https://github.com/mathuo/dockview
synced 2025-10-22 07:48:21 +00:00
Merge branch 'master' of https://github.com/mathuo/dockview into 397-gready-rendering-mode
This commit is contained in:
commit
43548618ba
1
.github/workflows/deploy-docs.yml
vendored
1
.github/workflows/deploy-docs.yml
vendored
@ -1,6 +1,7 @@
|
|||||||
name: Deploy Docs
|
name: Deploy Docs
|
||||||
|
|
||||||
on:
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
schedule:
|
schedule:
|
||||||
- cron: '0 3 * * *' # every day at 3 am UTC
|
- cron: '0 3 * * *' # every day at 3 am UTC
|
||||||
|
|
||||||
|
41
package.json
41
package.json
@ -29,43 +29,46 @@
|
|||||||
"version": "lerna version"
|
"version": "lerna version"
|
||||||
},
|
},
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
"@types/react": "^18.2.31",
|
"@types/react": "^18.2.46",
|
||||||
"@types/react-dom": "^18.2.14"
|
"@types/react-dom": "^18.2.18"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@rollup/plugin-node-resolve": "^15.2.3",
|
"@rollup/plugin-node-resolve": "^15.2.3",
|
||||||
"@rollup/plugin-terser": "^0.4.4",
|
"@rollup/plugin-terser": "^0.4.4",
|
||||||
"@rollup/plugin-typescript": "^11.1.5",
|
"@rollup/plugin-typescript": "^11.1.5",
|
||||||
"@testing-library/dom": "^9.3.3",
|
"@testing-library/dom": "^9.3.3",
|
||||||
"@testing-library/jest-dom": "^6.1.4",
|
"@testing-library/jest-dom": "^6.1.6",
|
||||||
"@testing-library/react": "^14.0.0",
|
"@testing-library/react": "^14.1.2",
|
||||||
"@total-typescript/shoehorn": "^0.1.1",
|
"@total-typescript/shoehorn": "^0.1.1",
|
||||||
"@types/jest": "^29.5.6",
|
"@types/jest": "^29.5.11",
|
||||||
"@types/react": "^18.2.31",
|
"@types/react": "^18.2.46",
|
||||||
"@types/react-dom": "^18.2.14",
|
"@types/react-dom": "^18.2.18",
|
||||||
"@typescript-eslint/eslint-plugin": "^6.8.0",
|
"@typescript-eslint/eslint-plugin": "^6.17.0",
|
||||||
"@typescript-eslint/parser": "^6.8.0",
|
"@typescript-eslint/parser": "^6.17.0",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"eslint": "^8.52.0",
|
"eslint": "^8.56.0",
|
||||||
"fs-extra": "^11.1.1",
|
"fs-extra": "^11.2.0",
|
||||||
"gulp": "^4.0.2",
|
"gulp": "^4.0.2",
|
||||||
"gulp-concat": "^2.6.1",
|
"gulp-concat": "^2.6.1",
|
||||||
"gulp-dart-sass": "^1.1.0",
|
"gulp-dart-sass": "^1.1.0",
|
||||||
"jest": "^29.7.0",
|
"jest": "^29.7.0",
|
||||||
"jest-environment-jsdom": "^29.7.0",
|
"jest-environment-jsdom": "^29.7.0",
|
||||||
"jest-sonar-reporter": "^2.0.0",
|
"jest-sonar-reporter": "^2.0.0",
|
||||||
"jsdom": "^22.1.0",
|
"jsdom": "^23.0.1",
|
||||||
"lerna": "^7.4.1",
|
"lerna": "^8.0.1",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"rimraf": "^5.0.5",
|
"rimraf": "^5.0.5",
|
||||||
"rollup": "^4.1.4",
|
"rollup": "^4.9.2",
|
||||||
"rollup-plugin-postcss": "^4.0.2",
|
"rollup-plugin-postcss": "^4.0.2",
|
||||||
"ts-jest": "^29.1.1",
|
"ts-jest": "^29.1.1",
|
||||||
"ts-loader": "^9.5.0",
|
"ts-loader": "^9.5.1",
|
||||||
"ts-node": "^10.9.1",
|
"ts-node": "^10.9.2",
|
||||||
"tslib": "^2.6.2",
|
"tslib": "^2.6.2",
|
||||||
"typedoc": "^0.25.2",
|
"typedoc": "^0.25.6",
|
||||||
"typescript": "^5.2.2"
|
"typescript": "^5.3.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -11,7 +11,7 @@ describe('groupDragHandler', () => {
|
|||||||
const groupMock = jest.fn<DockviewGroupPanel, []>(() => {
|
const groupMock = jest.fn<DockviewGroupPanel, []>(() => {
|
||||||
const partial: Partial<DockviewGroupPanel> = {
|
const partial: Partial<DockviewGroupPanel> = {
|
||||||
id: 'test_group_id',
|
id: 'test_group_id',
|
||||||
api: { isFloating: false } as any,
|
api: { location: 'grid' } as any,
|
||||||
};
|
};
|
||||||
return partial as DockviewGroupPanel;
|
return partial as DockviewGroupPanel;
|
||||||
});
|
});
|
||||||
@ -48,12 +48,12 @@ describe('groupDragHandler', () => {
|
|||||||
|
|
||||||
cut.dispose();
|
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 element = document.createElement('div');
|
||||||
|
|
||||||
const groupMock = jest.fn<DockviewGroupPanel, []>(() => {
|
const groupMock = jest.fn<DockviewGroupPanel, []>(() => {
|
||||||
const partial: Partial<DockviewGroupPanel> = {
|
const partial: Partial<DockviewGroupPanel> = {
|
||||||
api: { isFloating: true } as any,
|
api: { location: 'floating' } as any,
|
||||||
};
|
};
|
||||||
return partial as DockviewGroupPanel;
|
return partial as DockviewGroupPanel;
|
||||||
});
|
});
|
||||||
@ -85,7 +85,7 @@ describe('groupDragHandler', () => {
|
|||||||
|
|
||||||
const groupMock = jest.fn<DockviewGroupPanel, []>(() => {
|
const groupMock = jest.fn<DockviewGroupPanel, []>(() => {
|
||||||
const partial: Partial<DockviewGroupPanel> = {
|
const partial: Partial<DockviewGroupPanel> = {
|
||||||
api: { isFloating: false } as any,
|
api: { location: 'grid' } as any,
|
||||||
};
|
};
|
||||||
return partial as DockviewGroupPanel;
|
return partial as DockviewGroupPanel;
|
||||||
});
|
});
|
||||||
|
@ -478,7 +478,7 @@ describe('tabsContainer', () => {
|
|||||||
|
|
||||||
const groupPanelMock = jest.fn<DockviewGroupPanel, []>(() => {
|
const groupPanelMock = jest.fn<DockviewGroupPanel, []>(() => {
|
||||||
return (<Partial<DockviewGroupPanel>>{
|
return (<Partial<DockviewGroupPanel>>{
|
||||||
api: { isFloating: false } as any,
|
api: { location: 'grid' } as any,
|
||||||
}) as DockviewGroupPanel;
|
}) as DockviewGroupPanel;
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -538,7 +538,7 @@ describe('tabsContainer', () => {
|
|||||||
|
|
||||||
const groupPanelMock = jest.fn<DockviewGroupPanel, []>(() => {
|
const groupPanelMock = jest.fn<DockviewGroupPanel, []>(() => {
|
||||||
return (<Partial<DockviewGroupPanel>>{
|
return (<Partial<DockviewGroupPanel>>{
|
||||||
api: { isFloating: true } as any,
|
api: { location: 'floating' } as any,
|
||||||
}) as DockviewGroupPanel;
|
}) as DockviewGroupPanel;
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -591,7 +591,7 @@ describe('tabsContainer', () => {
|
|||||||
|
|
||||||
const groupPanelMock = jest.fn<DockviewGroupPanel, []>(() => {
|
const groupPanelMock = jest.fn<DockviewGroupPanel, []>(() => {
|
||||||
return (<Partial<DockviewGroupPanel>>{
|
return (<Partial<DockviewGroupPanel>>{
|
||||||
api: { isFloating: true } as any,
|
api: { location: 'floating' } as any,
|
||||||
model: {} as any,
|
model: {} as any,
|
||||||
}) as DockviewGroupPanel;
|
}) as DockviewGroupPanel;
|
||||||
});
|
});
|
||||||
@ -653,7 +653,7 @@ describe('tabsContainer', () => {
|
|||||||
|
|
||||||
const groupPanelMock = jest.fn<DockviewGroupPanel, []>(() => {
|
const groupPanelMock = jest.fn<DockviewGroupPanel, []>(() => {
|
||||||
return (<Partial<DockviewGroupPanel>>{
|
return (<Partial<DockviewGroupPanel>>{
|
||||||
api: { isFloating: true } as any,
|
api: { location: 'grid' } as any,
|
||||||
model: {} as any,
|
model: {} as any,
|
||||||
}) as DockviewGroupPanel;
|
}) as DockviewGroupPanel;
|
||||||
});
|
});
|
||||||
@ -723,7 +723,7 @@ describe('tabsContainer', () => {
|
|||||||
|
|
||||||
const groupPanelMock = jest.fn<DockviewGroupPanel, []>(() => {
|
const groupPanelMock = jest.fn<DockviewGroupPanel, []>(() => {
|
||||||
return (<Partial<DockviewGroupPanel>>{
|
return (<Partial<DockviewGroupPanel>>{
|
||||||
api: { isFloating: true } as any,
|
api: { location: 'grid' } as any,
|
||||||
model: {} as any,
|
model: {} as any,
|
||||||
}) as DockviewGroupPanel;
|
}) as DockviewGroupPanel;
|
||||||
});
|
});
|
||||||
@ -793,7 +793,7 @@ describe('tabsContainer', () => {
|
|||||||
|
|
||||||
const groupPanelMock = jest.fn<DockviewGroupPanel, []>(() => {
|
const groupPanelMock = jest.fn<DockviewGroupPanel, []>(() => {
|
||||||
return (<Partial<DockviewGroupPanel>>{
|
return (<Partial<DockviewGroupPanel>>{
|
||||||
api: { isFloating: true } as any,
|
api: { location: 'grid' } as any,
|
||||||
model: {} as any,
|
model: {} as any,
|
||||||
}) as DockviewGroupPanel;
|
}) as DockviewGroupPanel;
|
||||||
});
|
});
|
||||||
|
@ -2862,8 +2862,8 @@ describe('dockviewComponent', () => {
|
|||||||
floating: true,
|
floating: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(panel1.group.api.isFloating).toBeFalsy();
|
expect(panel1.group.api.location).toBe('grid');
|
||||||
expect(panel2.group.api.isFloating).toBeTruthy();
|
expect(panel2.group.api.location).toBe('floating');
|
||||||
expect(dockview.groups.length).toBe(2);
|
expect(dockview.groups.length).toBe(2);
|
||||||
expect(dockview.panels.length).toBe(2);
|
expect(dockview.panels.length).toBe(2);
|
||||||
|
|
||||||
@ -2874,8 +2874,8 @@ describe('dockviewComponent', () => {
|
|||||||
'right'
|
'right'
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(panel1.group.api.isFloating).toBeFalsy();
|
expect(panel1.group.api.location).toBe('grid');
|
||||||
expect(panel2.group.api.isFloating).toBeFalsy();
|
expect(panel2.group.api.location).toBe('grid');
|
||||||
expect(dockview.groups.length).toBe(2);
|
expect(dockview.groups.length).toBe(2);
|
||||||
expect(dockview.panels.length).toBe(2);
|
expect(dockview.panels.length).toBe(2);
|
||||||
});
|
});
|
||||||
@ -2907,8 +2907,8 @@ describe('dockviewComponent', () => {
|
|||||||
floating: true,
|
floating: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(panel1.group.api.isFloating).toBeFalsy();
|
expect(panel1.group.api.location).toBe('grid');
|
||||||
expect(panel2.group.api.isFloating).toBeTruthy();
|
expect(panel2.group.api.location).toBe('floating');
|
||||||
expect(dockview.groups.length).toBe(2);
|
expect(dockview.groups.length).toBe(2);
|
||||||
expect(dockview.panels.length).toBe(2);
|
expect(dockview.panels.length).toBe(2);
|
||||||
|
|
||||||
@ -2919,8 +2919,8 @@ describe('dockviewComponent', () => {
|
|||||||
'center'
|
'center'
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(panel1.group.api.isFloating).toBeFalsy();
|
expect(panel1.group.api.location).toBe('grid');
|
||||||
expect(panel2.group.api.isFloating).toBeFalsy();
|
expect(panel2.group.api.location).toBe('grid');
|
||||||
expect(dockview.groups.length).toBe(1);
|
expect(dockview.groups.length).toBe(1);
|
||||||
expect(dockview.panels.length).toBe(2);
|
expect(dockview.panels.length).toBe(2);
|
||||||
});
|
});
|
||||||
@ -2958,9 +2958,9 @@ describe('dockviewComponent', () => {
|
|||||||
floating: true,
|
floating: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(panel1.group.api.isFloating).toBeFalsy();
|
expect(panel1.group.api.location).toBe('grid');
|
||||||
expect(panel2.group.api.isFloating).toBeTruthy();
|
expect(panel2.group.api.location).toBe('floating');
|
||||||
expect(panel3.group.api.isFloating).toBeTruthy();
|
expect(panel3.group.api.location).toBe('floating');
|
||||||
expect(dockview.groups.length).toBe(3);
|
expect(dockview.groups.length).toBe(3);
|
||||||
expect(dockview.panels.length).toBe(3);
|
expect(dockview.panels.length).toBe(3);
|
||||||
|
|
||||||
@ -2971,9 +2971,9 @@ describe('dockviewComponent', () => {
|
|||||||
'center'
|
'center'
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(panel1.group.api.isFloating).toBeFalsy();
|
expect(panel1.group.api.location).toBe('grid');
|
||||||
expect(panel2.group.api.isFloating).toBeTruthy();
|
expect(panel2.group.api.location).toBe('floating');
|
||||||
expect(panel3.group.api.isFloating).toBeTruthy();
|
expect(panel3.group.api.location).toBe('floating');
|
||||||
expect(dockview.groups.length).toBe(2);
|
expect(dockview.groups.length).toBe(2);
|
||||||
expect(dockview.panels.length).toBe(3);
|
expect(dockview.panels.length).toBe(3);
|
||||||
});
|
});
|
||||||
@ -3011,9 +3011,9 @@ describe('dockviewComponent', () => {
|
|||||||
position: { referencePanel: panel2 },
|
position: { referencePanel: panel2 },
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(panel1.group.api.isFloating).toBeFalsy();
|
expect(panel1.group.api.location).toBe('grid');
|
||||||
expect(panel2.group.api.isFloating).toBeTruthy();
|
expect(panel2.group.api.location).toBe('floating');
|
||||||
expect(panel3.group.api.isFloating).toBeTruthy();
|
expect(panel3.group.api.location).toBe('floating');
|
||||||
expect(dockview.groups.length).toBe(2);
|
expect(dockview.groups.length).toBe(2);
|
||||||
expect(dockview.panels.length).toBe(3);
|
expect(dockview.panels.length).toBe(3);
|
||||||
|
|
||||||
@ -3024,9 +3024,9 @@ describe('dockviewComponent', () => {
|
|||||||
'right'
|
'right'
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(panel1.group.api.isFloating).toBeFalsy();
|
expect(panel1.group.api.location).toBe('grid');
|
||||||
expect(panel2.group.api.isFloating).toBeFalsy();
|
expect(panel2.group.api.location).toBe('grid');
|
||||||
expect(panel3.group.api.isFloating).toBeFalsy();
|
expect(panel3.group.api.location).toBe('grid');
|
||||||
expect(dockview.groups.length).toBe(2);
|
expect(dockview.groups.length).toBe(2);
|
||||||
expect(dockview.panels.length).toBe(3);
|
expect(dockview.panels.length).toBe(3);
|
||||||
});
|
});
|
||||||
@ -3064,9 +3064,9 @@ describe('dockviewComponent', () => {
|
|||||||
position: { referencePanel: panel2 },
|
position: { referencePanel: panel2 },
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(panel1.group.api.isFloating).toBeFalsy();
|
expect(panel1.group.api.location).toBe('grid');
|
||||||
expect(panel2.group.api.isFloating).toBeTruthy();
|
expect(panel2.group.api.location).toBe('floating');
|
||||||
expect(panel3.group.api.isFloating).toBeTruthy();
|
expect(panel3.group.api.location).toBe('floating');
|
||||||
expect(dockview.groups.length).toBe(2);
|
expect(dockview.groups.length).toBe(2);
|
||||||
expect(dockview.panels.length).toBe(3);
|
expect(dockview.panels.length).toBe(3);
|
||||||
|
|
||||||
@ -3077,9 +3077,9 @@ describe('dockviewComponent', () => {
|
|||||||
'center'
|
'center'
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(panel1.group.api.isFloating).toBeFalsy();
|
expect(panel1.group.api.location).toBe('grid');
|
||||||
expect(panel2.group.api.isFloating).toBeFalsy();
|
expect(panel2.group.api.location).toBe('grid');
|
||||||
expect(panel3.group.api.isFloating).toBeFalsy();
|
expect(panel3.group.api.location).toBe('grid');
|
||||||
expect(dockview.groups.length).toBe(1);
|
expect(dockview.groups.length).toBe(1);
|
||||||
expect(dockview.panels.length).toBe(3);
|
expect(dockview.panels.length).toBe(3);
|
||||||
});
|
});
|
||||||
@ -3123,10 +3123,10 @@ describe('dockviewComponent', () => {
|
|||||||
floating: true,
|
floating: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(panel1.group.api.isFloating).toBeFalsy();
|
expect(panel1.group.api.location).toBe('grid');
|
||||||
expect(panel2.group.api.isFloating).toBeTruthy();
|
expect(panel2.group.api.location).toBe('floating');
|
||||||
expect(panel3.group.api.isFloating).toBeTruthy();
|
expect(panel3.group.api.location).toBe('floating');
|
||||||
expect(panel4.group.api.isFloating).toBeTruthy();
|
expect(panel4.group.api.location).toBe('floating');
|
||||||
expect(dockview.groups.length).toBe(3);
|
expect(dockview.groups.length).toBe(3);
|
||||||
expect(dockview.panels.length).toBe(4);
|
expect(dockview.panels.length).toBe(4);
|
||||||
|
|
||||||
@ -3137,10 +3137,10 @@ describe('dockviewComponent', () => {
|
|||||||
'center'
|
'center'
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(panel1.group.api.isFloating).toBeFalsy();
|
expect(panel1.group.api.location).toBe('grid');
|
||||||
expect(panel2.group.api.isFloating).toBeTruthy();
|
expect(panel2.group.api.location).toBe('floating');
|
||||||
expect(panel3.group.api.isFloating).toBeTruthy();
|
expect(panel3.group.api.location).toBe('floating');
|
||||||
expect(panel4.group.api.isFloating).toBeTruthy();
|
expect(panel4.group.api.location).toBe('floating');
|
||||||
expect(dockview.groups.length).toBe(2);
|
expect(dockview.groups.length).toBe(2);
|
||||||
expect(dockview.panels.length).toBe(4);
|
expect(dockview.panels.length).toBe(4);
|
||||||
});
|
});
|
||||||
@ -3172,8 +3172,8 @@ describe('dockviewComponent', () => {
|
|||||||
floating: true,
|
floating: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(panel1.group.api.isFloating).toBeFalsy();
|
expect(panel1.group.api.location).toBe('grid');
|
||||||
expect(panel2.group.api.isFloating).toBeTruthy();
|
expect(panel2.group.api.location).toBe('floating');
|
||||||
expect(dockview.groups.length).toBe(2);
|
expect(dockview.groups.length).toBe(2);
|
||||||
expect(dockview.panels.length).toBe(2);
|
expect(dockview.panels.length).toBe(2);
|
||||||
|
|
||||||
@ -3184,8 +3184,8 @@ describe('dockviewComponent', () => {
|
|||||||
'right'
|
'right'
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(panel1.group.api.isFloating).toBeFalsy();
|
expect(panel1.group.api.location).toBe('grid');
|
||||||
expect(panel2.group.api.isFloating).toBeFalsy();
|
expect(panel2.group.api.location).toBe('grid');
|
||||||
expect(dockview.groups.length).toBe(2);
|
expect(dockview.groups.length).toBe(2);
|
||||||
expect(dockview.panels.length).toBe(2);
|
expect(dockview.panels.length).toBe(2);
|
||||||
});
|
});
|
||||||
@ -3217,8 +3217,8 @@ describe('dockviewComponent', () => {
|
|||||||
floating: true,
|
floating: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(panel1.group.api.isFloating).toBeFalsy();
|
expect(panel1.group.api.location).toBe('grid');
|
||||||
expect(panel2.group.api.isFloating).toBeTruthy();
|
expect(panel2.group.api.location).toBe('floating');
|
||||||
expect(dockview.groups.length).toBe(2);
|
expect(dockview.groups.length).toBe(2);
|
||||||
expect(dockview.panels.length).toBe(2);
|
expect(dockview.panels.length).toBe(2);
|
||||||
|
|
||||||
@ -3229,8 +3229,8 @@ describe('dockviewComponent', () => {
|
|||||||
'center'
|
'center'
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(panel1.group.api.isFloating).toBeFalsy();
|
expect(panel1.group.api.location).toBe('grid');
|
||||||
expect(panel2.group.api.isFloating).toBeFalsy();
|
expect(panel2.group.api.location).toBe('grid');
|
||||||
expect(dockview.groups.length).toBe(1);
|
expect(dockview.groups.length).toBe(1);
|
||||||
expect(dockview.panels.length).toBe(2);
|
expect(dockview.panels.length).toBe(2);
|
||||||
});
|
});
|
||||||
@ -3268,9 +3268,9 @@ describe('dockviewComponent', () => {
|
|||||||
floating: true,
|
floating: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(panel1.group.api.isFloating).toBeFalsy();
|
expect(panel1.group.api.location).toBe('grid');
|
||||||
expect(panel2.group.api.isFloating).toBeTruthy();
|
expect(panel2.group.api.location).toBe('floating');
|
||||||
expect(panel3.group.api.isFloating).toBeTruthy();
|
expect(panel3.group.api.location).toBe('floating');
|
||||||
expect(dockview.groups.length).toBe(3);
|
expect(dockview.groups.length).toBe(3);
|
||||||
expect(dockview.panels.length).toBe(3);
|
expect(dockview.panels.length).toBe(3);
|
||||||
|
|
||||||
@ -3281,9 +3281,9 @@ describe('dockviewComponent', () => {
|
|||||||
'center'
|
'center'
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(panel1.group.api.isFloating).toBeFalsy();
|
expect(panel1.group.api.location).toBe('grid');
|
||||||
expect(panel2.group.api.isFloating).toBeTruthy();
|
expect(panel2.group.api.location).toBe('floating');
|
||||||
expect(panel3.group.api.isFloating).toBeTruthy();
|
expect(panel3.group.api.location).toBe('floating');
|
||||||
expect(dockview.groups.length).toBe(2);
|
expect(dockview.groups.length).toBe(2);
|
||||||
expect(dockview.panels.length).toBe(3);
|
expect(dockview.panels.length).toBe(3);
|
||||||
});
|
});
|
||||||
@ -3321,9 +3321,9 @@ describe('dockviewComponent', () => {
|
|||||||
position: { referencePanel: panel2 },
|
position: { referencePanel: panel2 },
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(panel1.group.api.isFloating).toBeFalsy();
|
expect(panel1.group.api.location).toBe('grid');
|
||||||
expect(panel2.group.api.isFloating).toBeTruthy();
|
expect(panel2.group.api.location).toBe('floating');
|
||||||
expect(panel3.group.api.isFloating).toBeTruthy();
|
expect(panel3.group.api.location).toBe('floating');
|
||||||
expect(dockview.groups.length).toBe(2);
|
expect(dockview.groups.length).toBe(2);
|
||||||
expect(dockview.panels.length).toBe(3);
|
expect(dockview.panels.length).toBe(3);
|
||||||
|
|
||||||
@ -3334,9 +3334,9 @@ describe('dockviewComponent', () => {
|
|||||||
'right'
|
'right'
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(panel1.group.api.isFloating).toBeFalsy();
|
expect(panel1.group.api.location).toBe('grid');
|
||||||
expect(panel2.group.api.isFloating).toBeFalsy();
|
expect(panel2.group.api.location).toBe('grid');
|
||||||
expect(panel3.group.api.isFloating).toBeTruthy();
|
expect(panel3.group.api.location).toBe('floating');
|
||||||
expect(dockview.groups.length).toBe(3);
|
expect(dockview.groups.length).toBe(3);
|
||||||
expect(dockview.panels.length).toBe(3);
|
expect(dockview.panels.length).toBe(3);
|
||||||
});
|
});
|
||||||
@ -3374,9 +3374,9 @@ describe('dockviewComponent', () => {
|
|||||||
position: { referencePanel: panel2 },
|
position: { referencePanel: panel2 },
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(panel1.group.api.isFloating).toBeFalsy();
|
expect(panel1.group.api.location).toBe('grid');
|
||||||
expect(panel2.group.api.isFloating).toBeTruthy();
|
expect(panel2.group.api.location).toBe('floating');
|
||||||
expect(panel3.group.api.isFloating).toBeTruthy();
|
expect(panel3.group.api.location).toBe('floating');
|
||||||
expect(dockview.groups.length).toBe(2);
|
expect(dockview.groups.length).toBe(2);
|
||||||
expect(dockview.panels.length).toBe(3);
|
expect(dockview.panels.length).toBe(3);
|
||||||
|
|
||||||
@ -3387,9 +3387,9 @@ describe('dockviewComponent', () => {
|
|||||||
'center'
|
'center'
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(panel1.group.api.isFloating).toBeFalsy();
|
expect(panel1.group.api.location).toBe('grid');
|
||||||
expect(panel2.group.api.isFloating).toBeFalsy();
|
expect(panel2.group.api.location).toBe('grid');
|
||||||
expect(panel3.group.api.isFloating).toBeTruthy();
|
expect(panel3.group.api.location).toBe('floating');
|
||||||
expect(dockview.groups.length).toBe(2);
|
expect(dockview.groups.length).toBe(2);
|
||||||
expect(dockview.panels.length).toBe(3);
|
expect(dockview.panels.length).toBe(3);
|
||||||
});
|
});
|
||||||
@ -3433,10 +3433,10 @@ describe('dockviewComponent', () => {
|
|||||||
floating: true,
|
floating: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(panel1.group.api.isFloating).toBeFalsy();
|
expect(panel1.group.api.location).toBe('grid');
|
||||||
expect(panel2.group.api.isFloating).toBeTruthy();
|
expect(panel2.group.api.location).toBe('floating');
|
||||||
expect(panel3.group.api.isFloating).toBeTruthy();
|
expect(panel3.group.api.location).toBe('floating');
|
||||||
expect(panel4.group.api.isFloating).toBeTruthy();
|
expect(panel4.group.api.location).toBe('floating');
|
||||||
expect(dockview.groups.length).toBe(3);
|
expect(dockview.groups.length).toBe(3);
|
||||||
expect(dockview.panels.length).toBe(4);
|
expect(dockview.panels.length).toBe(4);
|
||||||
|
|
||||||
@ -3447,10 +3447,10 @@ describe('dockviewComponent', () => {
|
|||||||
'center'
|
'center'
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(panel1.group.api.isFloating).toBeFalsy();
|
expect(panel1.group.api.location).toBe('grid');
|
||||||
expect(panel2.group.api.isFloating).toBeTruthy();
|
expect(panel2.group.api.location).toBe('floating');
|
||||||
expect(panel3.group.api.isFloating).toBeTruthy();
|
expect(panel3.group.api.location).toBe('floating');
|
||||||
expect(panel4.group.api.isFloating).toBeTruthy();
|
expect(panel4.group.api.location).toBe('floating');
|
||||||
expect(dockview.groups.length).toBe(3);
|
expect(dockview.groups.length).toBe(3);
|
||||||
expect(dockview.panels.length).toBe(4);
|
expect(dockview.panels.length).toBe(4);
|
||||||
});
|
});
|
||||||
@ -3488,9 +3488,9 @@ describe('dockviewComponent', () => {
|
|||||||
floating: true,
|
floating: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(panel1.group.api.isFloating).toBeFalsy();
|
expect(panel1.group.api.location).toBe('grid');
|
||||||
expect(panel2.group.api.isFloating).toBeFalsy();
|
expect(panel2.group.api.location).toBe('grid');
|
||||||
expect(panel3.group.api.isFloating).toBeTruthy();
|
expect(panel3.group.api.location).toBe('floating');
|
||||||
expect(dockview.groups.length).toBe(3);
|
expect(dockview.groups.length).toBe(3);
|
||||||
expect(dockview.panels.length).toBe(3);
|
expect(dockview.panels.length).toBe(3);
|
||||||
|
|
||||||
@ -3501,9 +3501,9 @@ describe('dockviewComponent', () => {
|
|||||||
'center'
|
'center'
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(panel1.group.api.isFloating).toBeTruthy();
|
expect(panel1.group.api.location).toBe('floating');
|
||||||
expect(panel2.group.api.isFloating).toBeFalsy();
|
expect(panel2.group.api.location).toBe('grid');
|
||||||
expect(panel3.group.api.isFloating).toBeTruthy();
|
expect(panel3.group.api.location).toBe('floating');
|
||||||
expect(dockview.groups.length).toBe(2);
|
expect(dockview.groups.length).toBe(2);
|
||||||
expect(dockview.panels.length).toBe(3);
|
expect(dockview.panels.length).toBe(3);
|
||||||
});
|
});
|
||||||
@ -3540,9 +3540,9 @@ describe('dockviewComponent', () => {
|
|||||||
floating: true,
|
floating: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(panel1.group.api.isFloating).toBeFalsy();
|
expect(panel1.group.api.location).toBe('grid');
|
||||||
expect(panel2.group.api.isFloating).toBeFalsy();
|
expect(panel2.group.api.location).toBe('grid');
|
||||||
expect(panel3.group.api.isFloating).toBeTruthy();
|
expect(panel3.group.api.location).toBe('floating');
|
||||||
expect(dockview.groups.length).toBe(2);
|
expect(dockview.groups.length).toBe(2);
|
||||||
expect(dockview.panels.length).toBe(3);
|
expect(dockview.panels.length).toBe(3);
|
||||||
|
|
||||||
@ -3553,9 +3553,9 @@ describe('dockviewComponent', () => {
|
|||||||
'center'
|
'center'
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(panel1.group.api.isFloating).toBeTruthy();
|
expect(panel1.group.api.location).toBe('floating');
|
||||||
expect(panel2.group.api.isFloating).toBeFalsy();
|
expect(panel2.group.api.location).toBe('grid');
|
||||||
expect(panel3.group.api.isFloating).toBeTruthy();
|
expect(panel3.group.api.location).toBe('floating');
|
||||||
expect(dockview.groups.length).toBe(2);
|
expect(dockview.groups.length).toBe(2);
|
||||||
expect(dockview.panels.length).toBe(3);
|
expect(dockview.panels.length).toBe(3);
|
||||||
});
|
});
|
||||||
@ -3593,9 +3593,9 @@ describe('dockviewComponent', () => {
|
|||||||
floating: true,
|
floating: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(panel1.group.api.isFloating).toBeFalsy();
|
expect(panel1.group.api.location).toBe('grid');
|
||||||
expect(panel2.group.api.isFloating).toBeFalsy();
|
expect(panel2.group.api.location).toBe('grid');
|
||||||
expect(panel3.group.api.isFloating).toBeTruthy();
|
expect(panel3.group.api.location).toBe('floating');
|
||||||
expect(dockview.groups.length).toBe(3);
|
expect(dockview.groups.length).toBe(3);
|
||||||
expect(dockview.panels.length).toBe(3);
|
expect(dockview.panels.length).toBe(3);
|
||||||
|
|
||||||
@ -3606,9 +3606,9 @@ describe('dockviewComponent', () => {
|
|||||||
'center'
|
'center'
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(panel1.group.api.isFloating).toBeTruthy();
|
expect(panel1.group.api.location).toBe('floating');
|
||||||
expect(panel2.group.api.isFloating).toBeFalsy();
|
expect(panel2.group.api.location).toBe('grid');
|
||||||
expect(panel3.group.api.isFloating).toBeTruthy();
|
expect(panel3.group.api.location).toBe('floating');
|
||||||
expect(dockview.groups.length).toBe(2);
|
expect(dockview.groups.length).toBe(2);
|
||||||
expect(dockview.panels.length).toBe(3);
|
expect(dockview.panels.length).toBe(3);
|
||||||
});
|
});
|
||||||
@ -3645,9 +3645,9 @@ describe('dockviewComponent', () => {
|
|||||||
floating: true,
|
floating: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(panel1.group.api.isFloating).toBeFalsy();
|
expect(panel1.group.api.location).toBe('grid');
|
||||||
expect(panel2.group.api.isFloating).toBeFalsy();
|
expect(panel2.group.api.location).toBe('grid');
|
||||||
expect(panel3.group.api.isFloating).toBeTruthy();
|
expect(panel3.group.api.location).toBe('floating');
|
||||||
expect(dockview.groups.length).toBe(2);
|
expect(dockview.groups.length).toBe(2);
|
||||||
expect(dockview.panels.length).toBe(3);
|
expect(dockview.panels.length).toBe(3);
|
||||||
|
|
||||||
@ -3658,9 +3658,9 @@ describe('dockviewComponent', () => {
|
|||||||
'center'
|
'center'
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(panel1.group.api.isFloating).toBeTruthy();
|
expect(panel1.group.api.location).toBe('floating');
|
||||||
expect(panel2.group.api.isFloating).toBeTruthy();
|
expect(panel2.group.api.location).toBe('floating');
|
||||||
expect(panel3.group.api.isFloating).toBeTruthy();
|
expect(panel3.group.api.location).toBe('floating');
|
||||||
expect(dockview.groups.length).toBe(1);
|
expect(dockview.groups.length).toBe(1);
|
||||||
expect(dockview.panels.length).toBe(3);
|
expect(dockview.panels.length).toBe(3);
|
||||||
});
|
});
|
||||||
@ -3692,15 +3692,15 @@ describe('dockviewComponent', () => {
|
|||||||
position: { direction: 'right' },
|
position: { direction: 'right' },
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(panel1.group.api.isFloating).toBeFalsy();
|
expect(panel1.group.api.location).toBe('grid');
|
||||||
expect(panel2.group.api.isFloating).toBeFalsy();
|
expect(panel2.group.api.location).toBe('grid');
|
||||||
expect(dockview.groups.length).toBe(2);
|
expect(dockview.groups.length).toBe(2);
|
||||||
expect(dockview.panels.length).toBe(2);
|
expect(dockview.panels.length).toBe(2);
|
||||||
|
|
||||||
dockview.addFloatingGroup(panel2);
|
dockview.addFloatingGroup(panel2);
|
||||||
|
|
||||||
expect(panel1.group.api.isFloating).toBeFalsy();
|
expect(panel1.group.api.location).toBe('grid');
|
||||||
expect(panel2.group.api.isFloating).toBeTruthy();
|
expect(panel2.group.api.location).toBe('floating');
|
||||||
expect(dockview.groups.length).toBe(2);
|
expect(dockview.groups.length).toBe(2);
|
||||||
expect(dockview.panels.length).toBe(2);
|
expect(dockview.panels.length).toBe(2);
|
||||||
});
|
});
|
||||||
@ -3731,15 +3731,15 @@ describe('dockviewComponent', () => {
|
|||||||
component: 'default',
|
component: 'default',
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(panel1.group.api.isFloating).toBeFalsy();
|
expect(panel1.group.api.location).toBe('grid');
|
||||||
expect(panel2.group.api.isFloating).toBeFalsy();
|
expect(panel2.group.api.location).toBe('grid');
|
||||||
expect(dockview.groups.length).toBe(1);
|
expect(dockview.groups.length).toBe(1);
|
||||||
expect(dockview.panels.length).toBe(2);
|
expect(dockview.panels.length).toBe(2);
|
||||||
|
|
||||||
dockview.addFloatingGroup(panel2);
|
dockview.addFloatingGroup(panel2);
|
||||||
|
|
||||||
expect(panel1.group.api.isFloating).toBeFalsy();
|
expect(panel1.group.api.location).toBe('grid');
|
||||||
expect(panel2.group.api.isFloating).toBeTruthy();
|
expect(panel2.group.api.location).toBe('floating');
|
||||||
expect(dockview.groups.length).toBe(2);
|
expect(dockview.groups.length).toBe(2);
|
||||||
expect(dockview.panels.length).toBe(2);
|
expect(dockview.panels.length).toBe(2);
|
||||||
});
|
});
|
||||||
@ -3771,15 +3771,15 @@ describe('dockviewComponent', () => {
|
|||||||
position: { direction: 'right' },
|
position: { direction: 'right' },
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(panel1.group.api.isFloating).toBeFalsy();
|
expect(panel1.group.api.location).toBe('grid');
|
||||||
expect(panel2.group.api.isFloating).toBeFalsy();
|
expect(panel2.group.api.location).toBe('grid');
|
||||||
expect(dockview.groups.length).toBe(2);
|
expect(dockview.groups.length).toBe(2);
|
||||||
expect(dockview.panels.length).toBe(2);
|
expect(dockview.panels.length).toBe(2);
|
||||||
|
|
||||||
dockview.addFloatingGroup(panel2.group);
|
dockview.addFloatingGroup(panel2.group);
|
||||||
|
|
||||||
expect(panel1.group.api.isFloating).toBeFalsy();
|
expect(panel1.group.api.location).toBe('grid');
|
||||||
expect(panel2.group.api.isFloating).toBeTruthy();
|
expect(panel2.group.api.location).toBe('floating');
|
||||||
expect(dockview.groups.length).toBe(2);
|
expect(dockview.groups.length).toBe(2);
|
||||||
expect(dockview.panels.length).toBe(2);
|
expect(dockview.panels.length).toBe(2);
|
||||||
});
|
});
|
||||||
@ -3810,15 +3810,15 @@ describe('dockviewComponent', () => {
|
|||||||
component: 'default',
|
component: 'default',
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(panel1.group.api.isFloating).toBeFalsy();
|
expect(panel1.group.api.location).toBe('grid');
|
||||||
expect(panel2.group.api.isFloating).toBeFalsy();
|
expect(panel2.group.api.location).toBe('grid');
|
||||||
expect(dockview.groups.length).toBe(1);
|
expect(dockview.groups.length).toBe(1);
|
||||||
expect(dockview.panels.length).toBe(2);
|
expect(dockview.panels.length).toBe(2);
|
||||||
|
|
||||||
dockview.addFloatingGroup(panel2.group);
|
dockview.addFloatingGroup(panel2.group);
|
||||||
|
|
||||||
expect(panel1.group.api.isFloating).toBeTruthy();
|
expect(panel1.group.api.location).toBe('floating');
|
||||||
expect(panel2.group.api.isFloating).toBeTruthy();
|
expect(panel2.group.api.location).toBe('floating');
|
||||||
expect(dockview.groups.length).toBe(1);
|
expect(dockview.groups.length).toBe(1);
|
||||||
expect(dockview.panels.length).toBe(2);
|
expect(dockview.panels.length).toBe(2);
|
||||||
});
|
});
|
||||||
|
@ -19,13 +19,17 @@ class MockGridview implements IGridView {
|
|||||||
>().event;
|
>().event;
|
||||||
element: HTMLElement = document.createElement('div');
|
element: HTMLElement = document.createElement('div');
|
||||||
|
|
||||||
|
width: number = 0;
|
||||||
|
height: number = 0;
|
||||||
|
|
||||||
constructor(private id?: string) {
|
constructor(private id?: string) {
|
||||||
this.element.className = 'mock-grid-view';
|
this.element.className = 'mock-grid-view';
|
||||||
this.element.id = `${id ?? ''}`;
|
this.element.id = `${id ?? ''}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
layout(width: number, height: number): void {
|
layout(width: number, height: number): void {
|
||||||
//
|
this.width = width;
|
||||||
|
this.height = height;
|
||||||
}
|
}
|
||||||
|
|
||||||
toJSON(): object {
|
toJSON(): object {
|
||||||
@ -760,4 +764,255 @@ describe('gridview', () => {
|
|||||||
el = gridview.element.querySelectorAll('.mock-grid-view');
|
el = gridview.element.querySelectorAll('.mock-grid-view');
|
||||||
expect(el.length).toBe(5);
|
expect(el.length).toBe(5);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('gridview nested proportional layouts', () => {
|
||||||
|
const gridview = new Gridview(
|
||||||
|
true,
|
||||||
|
{ separatorBorder: '' },
|
||||||
|
Orientation.HORIZONTAL
|
||||||
|
);
|
||||||
|
gridview.layout(1000, 1000);
|
||||||
|
|
||||||
|
const view1 = new MockGridview('1');
|
||||||
|
const view2 = new MockGridview('2');
|
||||||
|
const view3 = new MockGridview('3');
|
||||||
|
const view4 = new MockGridview('4');
|
||||||
|
const view5 = new MockGridview('5');
|
||||||
|
const view6 = new MockGridview('6');
|
||||||
|
|
||||||
|
gridview.addView(view1, Sizing.Distribute, [0]);
|
||||||
|
gridview.addView(view2, Sizing.Distribute, [1]);
|
||||||
|
gridview.addView(view3, Sizing.Distribute, [1, 1]);
|
||||||
|
gridview.addView(view4, Sizing.Distribute, [1, 1, 0]);
|
||||||
|
gridview.addView(view5, Sizing.Distribute, [1, 1, 0, 0]);
|
||||||
|
gridview.addView(view6, Sizing.Distribute, [1, 1, 0, 0, 0]);
|
||||||
|
|
||||||
|
const views = [view1, view2, view3, view4, view5, view6];
|
||||||
|
|
||||||
|
const dimensions = [
|
||||||
|
{ width: 500, height: 1000 },
|
||||||
|
{ width: 500, height: 500 },
|
||||||
|
{ width: 250, height: 500 },
|
||||||
|
{ width: 250, height: 250 },
|
||||||
|
{ width: 125, height: 250 },
|
||||||
|
{ width: 125, height: 250 },
|
||||||
|
];
|
||||||
|
|
||||||
|
expect(
|
||||||
|
views.map((view) => ({
|
||||||
|
width: view.width,
|
||||||
|
height: view.height,
|
||||||
|
}))
|
||||||
|
).toEqual(dimensions);
|
||||||
|
|
||||||
|
gridview.layout(2000, 1500);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
views.map((view) => ({
|
||||||
|
width: view.width,
|
||||||
|
height: view.height,
|
||||||
|
}))
|
||||||
|
).toEqual(
|
||||||
|
dimensions.map(({ width, height }) => ({
|
||||||
|
width: width * 2,
|
||||||
|
height: height * 1.5,
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
|
||||||
|
gridview.layout(200, 2000);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
views.map((view) => ({
|
||||||
|
width: view.width,
|
||||||
|
height: view.height,
|
||||||
|
}))
|
||||||
|
).toEqual(
|
||||||
|
dimensions.map(({ width, height }) => ({
|
||||||
|
width: width * 0.2,
|
||||||
|
height: height * 2,
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('that maximizeView retains original dimensions when restored', () => {
|
||||||
|
const gridview = new Gridview(
|
||||||
|
true,
|
||||||
|
{ separatorBorder: '' },
|
||||||
|
Orientation.HORIZONTAL
|
||||||
|
);
|
||||||
|
gridview.layout(1000, 1000);
|
||||||
|
|
||||||
|
let counter = 0;
|
||||||
|
const subscription = gridview.onDidMaxmizedNodeChange(() => {
|
||||||
|
counter++;
|
||||||
|
});
|
||||||
|
|
||||||
|
const view1 = new MockGridview('1');
|
||||||
|
const view2 = new MockGridview('2');
|
||||||
|
const view3 = new MockGridview('3');
|
||||||
|
const view4 = new MockGridview('4');
|
||||||
|
const view5 = new MockGridview('5');
|
||||||
|
const view6 = new MockGridview('6');
|
||||||
|
|
||||||
|
gridview.addView(view1, Sizing.Distribute, [0]);
|
||||||
|
gridview.addView(view2, Sizing.Distribute, [1]);
|
||||||
|
gridview.addView(view3, Sizing.Distribute, [1, 1]);
|
||||||
|
gridview.addView(view4, Sizing.Distribute, [1, 1, 0]);
|
||||||
|
gridview.addView(view5, Sizing.Distribute, [1, 1, 0, 0]);
|
||||||
|
gridview.addView(view6, Sizing.Distribute, [1, 1, 0, 0, 0]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* _____________________________________________
|
||||||
|
* | | |
|
||||||
|
* | | 2 |
|
||||||
|
* | | |
|
||||||
|
* | 1 |_______________________|
|
||||||
|
* | | | 4 |
|
||||||
|
* | | 3 |_____________|
|
||||||
|
* | | | 5 | 6 |
|
||||||
|
* |_____________________|_________|______|______|
|
||||||
|
*/
|
||||||
|
|
||||||
|
const views = [view1, view2, view3, view4, view5, view6];
|
||||||
|
|
||||||
|
const dimensions = [
|
||||||
|
{ width: 500, height: 1000 },
|
||||||
|
{ width: 500, height: 500 },
|
||||||
|
{ width: 250, height: 500 },
|
||||||
|
{ width: 250, height: 250 },
|
||||||
|
{ width: 125, height: 250 },
|
||||||
|
{ width: 125, height: 250 },
|
||||||
|
];
|
||||||
|
|
||||||
|
function assertLayout() {
|
||||||
|
expect(
|
||||||
|
views.map((view) => ({
|
||||||
|
width: view.width,
|
||||||
|
height: view.height,
|
||||||
|
}))
|
||||||
|
).toEqual(dimensions);
|
||||||
|
}
|
||||||
|
|
||||||
|
// base case assertions
|
||||||
|
assertLayout();
|
||||||
|
expect(gridview.hasMaximizedView()).toBeFalsy();
|
||||||
|
expect(counter).toBe(0);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* maximize each view individually and then return to the standard view
|
||||||
|
* checking on each iteration that the original layout dimensions
|
||||||
|
* are restored
|
||||||
|
*/
|
||||||
|
for (let i = 0; i < views.length; i++) {
|
||||||
|
const view = views[i];
|
||||||
|
|
||||||
|
gridview.maximizeView(view);
|
||||||
|
expect(counter).toBe(i * 2 + 1);
|
||||||
|
expect(gridview.hasMaximizedView()).toBeTruthy();
|
||||||
|
gridview.exitMaximizedView();
|
||||||
|
expect(counter).toBe(i * 2 + 2);
|
||||||
|
assertLayout();
|
||||||
|
}
|
||||||
|
|
||||||
|
subscription.dispose();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('that maximizedView is exited when a views visibility is changed', () => {
|
||||||
|
const gridview = new Gridview(
|
||||||
|
true,
|
||||||
|
{ separatorBorder: '' },
|
||||||
|
Orientation.HORIZONTAL
|
||||||
|
);
|
||||||
|
gridview.layout(1000, 1000);
|
||||||
|
|
||||||
|
const view1 = new MockGridview('1');
|
||||||
|
const view2 = new MockGridview('2');
|
||||||
|
const view3 = new MockGridview('3');
|
||||||
|
|
||||||
|
gridview.addView(view1, Sizing.Distribute, [0]);
|
||||||
|
gridview.addView(view2, Sizing.Distribute, [1]);
|
||||||
|
gridview.addView(view3, Sizing.Distribute, [1, 1]);
|
||||||
|
|
||||||
|
expect(gridview.hasMaximizedView()).toBeFalsy();
|
||||||
|
gridview.maximizeView(view2);
|
||||||
|
expect(gridview.hasMaximizedView()).toBeTruthy();
|
||||||
|
|
||||||
|
gridview.setViewVisible([0], true);
|
||||||
|
expect(gridview.hasMaximizedView()).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('that maximizedView is exited when a view is moved', () => {
|
||||||
|
const gridview = new Gridview(
|
||||||
|
true,
|
||||||
|
{ separatorBorder: '' },
|
||||||
|
Orientation.HORIZONTAL
|
||||||
|
);
|
||||||
|
gridview.layout(1000, 1000);
|
||||||
|
|
||||||
|
const view1 = new MockGridview('1');
|
||||||
|
const view2 = new MockGridview('2');
|
||||||
|
const view3 = new MockGridview('3');
|
||||||
|
const view4 = new MockGridview('4');
|
||||||
|
|
||||||
|
gridview.addView(view1, Sizing.Distribute, [0]);
|
||||||
|
gridview.addView(view2, Sizing.Distribute, [1]);
|
||||||
|
gridview.addView(view3, Sizing.Distribute, [1, 1]);
|
||||||
|
gridview.addView(view4, Sizing.Distribute, [1, 1, 0]);
|
||||||
|
|
||||||
|
expect(gridview.hasMaximizedView()).toBeFalsy();
|
||||||
|
gridview.maximizeView(view2);
|
||||||
|
expect(gridview.hasMaximizedView()).toBeTruthy();
|
||||||
|
|
||||||
|
gridview.moveView([1, 1], 0, 1);
|
||||||
|
expect(gridview.hasMaximizedView()).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('that maximizedView is exited when a view is added', () => {
|
||||||
|
const gridview = new Gridview(
|
||||||
|
true,
|
||||||
|
{ separatorBorder: '' },
|
||||||
|
Orientation.HORIZONTAL
|
||||||
|
);
|
||||||
|
gridview.layout(1000, 1000);
|
||||||
|
|
||||||
|
const view1 = new MockGridview('1');
|
||||||
|
const view2 = new MockGridview('2');
|
||||||
|
const view3 = new MockGridview('3');
|
||||||
|
const view4 = new MockGridview('4');
|
||||||
|
|
||||||
|
gridview.addView(view1, Sizing.Distribute, [0]);
|
||||||
|
gridview.addView(view2, Sizing.Distribute, [1]);
|
||||||
|
gridview.addView(view3, Sizing.Distribute, [1, 1]);
|
||||||
|
|
||||||
|
expect(gridview.hasMaximizedView()).toBeFalsy();
|
||||||
|
gridview.maximizeView(view2);
|
||||||
|
expect(gridview.hasMaximizedView()).toBeTruthy();
|
||||||
|
|
||||||
|
gridview.addView(view4, Sizing.Distribute, [1, 1, 0]);
|
||||||
|
expect(gridview.hasMaximizedView()).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('that maximizedView is exited when a view is removed', () => {
|
||||||
|
const gridview = new Gridview(
|
||||||
|
true,
|
||||||
|
{ separatorBorder: '' },
|
||||||
|
Orientation.HORIZONTAL
|
||||||
|
);
|
||||||
|
gridview.layout(1000, 1000);
|
||||||
|
|
||||||
|
const view1 = new MockGridview('1');
|
||||||
|
const view2 = new MockGridview('2');
|
||||||
|
const view3 = new MockGridview('3');
|
||||||
|
|
||||||
|
gridview.addView(view1, Sizing.Distribute, [0]);
|
||||||
|
gridview.addView(view2, Sizing.Distribute, [1]);
|
||||||
|
gridview.addView(view3, Sizing.Distribute, [1, 1]);
|
||||||
|
|
||||||
|
expect(gridview.hasMaximizedView()).toBeFalsy();
|
||||||
|
gridview.maximizeView(view2);
|
||||||
|
expect(gridview.hasMaximizedView()).toBeTruthy();
|
||||||
|
|
||||||
|
gridview.removeView([1, 1]);
|
||||||
|
expect(gridview.hasMaximizedView()).toBeFalsy();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -42,6 +42,7 @@ import {
|
|||||||
GroupDragEvent,
|
GroupDragEvent,
|
||||||
TabDragEvent,
|
TabDragEvent,
|
||||||
} from '../dockview/components/titlebar/tabsContainer';
|
} from '../dockview/components/titlebar/tabsContainer';
|
||||||
|
import { Box } from '../types';
|
||||||
|
|
||||||
export interface CommonApi<T = any> {
|
export interface CommonApi<T = any> {
|
||||||
readonly height: number;
|
readonly height: number;
|
||||||
@ -804,4 +805,33 @@ export class DockviewApi implements CommonApi<SerializedDockview> {
|
|||||||
moveToPrevious(options?: MovementOptions): void {
|
moveToPrevious(options?: MovementOptions): void {
|
||||||
this.component.moveToPrevious(options);
|
this.component.moveToPrevious(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
maximizeGroup(panel: IDockviewPanel): void {
|
||||||
|
this.component.maximizeGroup(panel.group);
|
||||||
|
}
|
||||||
|
|
||||||
|
hasMaximizedGroup(): boolean {
|
||||||
|
return this.component.hasMaximizedGroup();
|
||||||
|
}
|
||||||
|
|
||||||
|
exitMaxmizedGroup(): void {
|
||||||
|
this.component.exitMaximizedGroup();
|
||||||
|
}
|
||||||
|
|
||||||
|
get onDidMaxmizedGroupChange(): Event<void> {
|
||||||
|
return this.component.onDidMaxmizedGroupChange;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a popout group in a new Window
|
||||||
|
*/
|
||||||
|
addPopoutGroup(
|
||||||
|
item: IDockviewPanel | DockviewGroupPanel,
|
||||||
|
options?: {
|
||||||
|
position?: Box;
|
||||||
|
popoutUrl?: string;
|
||||||
|
}
|
||||||
|
): void {
|
||||||
|
this.component.addPopoutGroup(item, options);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,43 +1,50 @@
|
|||||||
import { Position } from '../dnd/droptarget';
|
import { Position } from '../dnd/droptarget';
|
||||||
import { DockviewComponent } from '../dockview/dockviewComponent';
|
import { DockviewComponent } from '../dockview/dockviewComponent';
|
||||||
import { DockviewGroupPanel } from '../dockview/dockviewGroupPanel';
|
import { DockviewGroupPanel } from '../dockview/dockviewGroupPanel';
|
||||||
|
import { DockviewGroupLocation } from '../dockview/dockviewGroupPanelModel';
|
||||||
import { Emitter, Event } from '../events';
|
import { Emitter, Event } from '../events';
|
||||||
import { GridviewPanelApi, GridviewPanelApiImpl } from './gridviewPanelApi';
|
import { GridviewPanelApi, GridviewPanelApiImpl } from './gridviewPanelApi';
|
||||||
|
|
||||||
export interface DockviewGroupPanelApi extends GridviewPanelApi {
|
export interface DockviewGroupPanelApi extends GridviewPanelApi {
|
||||||
readonly onDidFloatingStateChange: Event<DockviewGroupPanelFloatingChangeEvent>;
|
readonly onDidRenderPositionChange: Event<DockviewGroupPanelFloatingChangeEvent>;
|
||||||
readonly isFloating: boolean;
|
readonly location: DockviewGroupLocation;
|
||||||
moveTo(options: { group: DockviewGroupPanel; position?: Position }): void;
|
moveTo(options: { group: DockviewGroupPanel; position?: Position }): void;
|
||||||
|
maximize(): void;
|
||||||
|
isMaximized(): boolean;
|
||||||
|
exitMaximized(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DockviewGroupPanelFloatingChangeEvent {
|
export interface DockviewGroupPanelFloatingChangeEvent {
|
||||||
readonly isFloating: boolean;
|
readonly location: DockviewGroupLocation;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO find a better way to initialize and avoid needing null checks
|
||||||
|
const NOT_INITIALIZED_MESSAGE = 'DockviewGroupPanelApiImpl not initialized';
|
||||||
|
|
||||||
export class DockviewGroupPanelApiImpl extends GridviewPanelApiImpl {
|
export class DockviewGroupPanelApiImpl extends GridviewPanelApiImpl {
|
||||||
private _group: DockviewGroupPanel | undefined;
|
private _group: DockviewGroupPanel | undefined;
|
||||||
|
|
||||||
readonly _onDidFloatingStateChange =
|
readonly _onDidRenderPositionChange =
|
||||||
new Emitter<DockviewGroupPanelFloatingChangeEvent>();
|
new Emitter<DockviewGroupPanelFloatingChangeEvent>();
|
||||||
readonly onDidFloatingStateChange: Event<DockviewGroupPanelFloatingChangeEvent> =
|
readonly onDidRenderPositionChange: Event<DockviewGroupPanelFloatingChangeEvent> =
|
||||||
this._onDidFloatingStateChange.event;
|
this._onDidRenderPositionChange.event;
|
||||||
|
|
||||||
get isFloating() {
|
get location(): DockviewGroupLocation {
|
||||||
if (!this._group) {
|
if (!this._group) {
|
||||||
throw new Error(`DockviewGroupPanelApiImpl not initialized`);
|
throw new Error(NOT_INITIALIZED_MESSAGE);
|
||||||
}
|
}
|
||||||
return this._group.model.isFloating;
|
return this._group.model.location;
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(id: string, private readonly accessor: DockviewComponent) {
|
constructor(id: string, private readonly accessor: DockviewComponent) {
|
||||||
super(id);
|
super(id);
|
||||||
|
|
||||||
this.addDisposables(this._onDidFloatingStateChange);
|
this.addDisposables(this._onDidRenderPositionChange);
|
||||||
}
|
}
|
||||||
|
|
||||||
moveTo(options: { group: DockviewGroupPanel; position?: Position }): void {
|
moveTo(options: { group: DockviewGroupPanel; position?: Position }): void {
|
||||||
if (!this._group) {
|
if (!this._group) {
|
||||||
throw new Error(`DockviewGroupPanelApiImpl not initialized`);
|
throw new Error(NOT_INITIALIZED_MESSAGE);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.accessor.moveGroupOrPanel(
|
this.accessor.moveGroupOrPanel(
|
||||||
@ -48,6 +55,32 @@ export class DockviewGroupPanelApiImpl extends GridviewPanelApiImpl {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
maximize(): void {
|
||||||
|
if (!this._group) {
|
||||||
|
throw new Error(NOT_INITIALIZED_MESSAGE);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.accessor.maximizeGroup(this._group);
|
||||||
|
}
|
||||||
|
|
||||||
|
isMaximized(): boolean {
|
||||||
|
if (!this._group) {
|
||||||
|
throw new Error(NOT_INITIALIZED_MESSAGE);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.accessor.isMaximizedGroup(this._group);
|
||||||
|
}
|
||||||
|
|
||||||
|
exitMaximized(): void {
|
||||||
|
if (!this._group) {
|
||||||
|
throw new Error(NOT_INITIALIZED_MESSAGE);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.isMaximized()) {
|
||||||
|
this.accessor.exitMaximizedGroup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
initialize(group: DockviewGroupPanel): void {
|
initialize(group: DockviewGroupPanel): void {
|
||||||
this._group = group;
|
this._group = group;
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ import { Emitter, Event } from '../events';
|
|||||||
import { GridviewPanelApiImpl, GridviewPanelApi } from './gridviewPanelApi';
|
import { GridviewPanelApiImpl, GridviewPanelApi } from './gridviewPanelApi';
|
||||||
import { DockviewGroupPanel } from '../dockview/dockviewGroupPanel';
|
import { DockviewGroupPanel } from '../dockview/dockviewGroupPanel';
|
||||||
import { MutableDisposable } from '../lifecycle';
|
import { MutableDisposable } from '../lifecycle';
|
||||||
import { DockviewPanel, IDockviewPanel } from '../dockview/dockviewPanel';
|
import { DockviewPanel } from '../dockview/dockviewPanel';
|
||||||
import { DockviewComponent } from '../dockview/dockviewComponent';
|
import { DockviewComponent } from '../dockview/dockviewComponent';
|
||||||
import { Position } from '../dnd/droptarget';
|
import { Position } from '../dnd/droptarget';
|
||||||
import { DockviewPanelRenderer } from '../overlayRenderContainer';
|
import { DockviewPanelRenderer } from '../overlayRenderContainer';
|
||||||
@ -15,13 +15,10 @@ export interface RendererChangedEvent {
|
|||||||
renderer: DockviewPanelRenderer;
|
renderer: DockviewPanelRenderer;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* omit visibility modifiers since the visibility of a single group doesn't make sense
|
|
||||||
* because it belongs to a groupview
|
|
||||||
*/
|
|
||||||
export interface DockviewPanelApi
|
export interface DockviewPanelApi
|
||||||
extends Omit<
|
extends Omit<
|
||||||
GridviewPanelApi,
|
GridviewPanelApi,
|
||||||
|
// omit properties that do not make sense here
|
||||||
'setVisible' | 'onDidConstraintsChange' | 'setConstraints'
|
'setVisible' | 'onDidConstraintsChange' | 'setConstraints'
|
||||||
> {
|
> {
|
||||||
readonly group: DockviewGroupPanel;
|
readonly group: DockviewGroupPanel;
|
||||||
@ -39,6 +36,9 @@ export interface DockviewPanelApi
|
|||||||
position?: Position;
|
position?: Position;
|
||||||
index?: number;
|
index?: number;
|
||||||
}): void;
|
}): void;
|
||||||
|
maximize(): void;
|
||||||
|
isMaximized(): boolean;
|
||||||
|
exitMaximized(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class DockviewPanelApiImpl
|
export class DockviewPanelApiImpl
|
||||||
@ -66,7 +66,7 @@ export class DockviewPanelApiImpl
|
|||||||
}
|
}
|
||||||
|
|
||||||
get isGroupActive(): boolean {
|
get isGroupActive(): boolean {
|
||||||
return !!this.group?.isActive;
|
return this.group.isActive;
|
||||||
}
|
}
|
||||||
|
|
||||||
get renderer(): DockviewPanelRenderer {
|
get renderer(): DockviewPanelRenderer {
|
||||||
@ -140,4 +140,16 @@ export class DockviewPanelApiImpl
|
|||||||
close(): void {
|
close(): void {
|
||||||
this.group.model.closePanel(this.panel);
|
this.group.model.closePanel(this.panel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
maximize(): void {
|
||||||
|
this.group.api.maximize();
|
||||||
|
}
|
||||||
|
|
||||||
|
isMaximized(): boolean {
|
||||||
|
return this.group.api.isMaximized();
|
||||||
|
}
|
||||||
|
|
||||||
|
exitMaximized(): void {
|
||||||
|
this.group.api.exitMaximized();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
3
packages/dockview-core/src/constants.ts
Normal file
3
packages/dockview-core/src/constants.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export const DEFAULT_FLOATING_GROUP_OVERFLOW_SIZE = 100;
|
||||||
|
|
||||||
|
export const DEFAULT_FLOATING_GROUP_POSITION = { left: 100, top: 100 };
|
@ -38,7 +38,7 @@ export class GroupDragHandler extends DragHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override isCancelled(_event: DragEvent): boolean {
|
override isCancelled(_event: DragEvent): boolean {
|
||||||
if (this.group.api.isFloating && !_event.shiftKey) {
|
if (this.group.api.location === 'floating' && !_event.shiftKey) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
@ -11,6 +11,7 @@ import {
|
|||||||
} from '../events';
|
} from '../events';
|
||||||
import { CompositeDisposable, MutableDisposable } from '../lifecycle';
|
import { CompositeDisposable, MutableDisposable } from '../lifecycle';
|
||||||
import { clamp } from '../math';
|
import { clamp } from '../math';
|
||||||
|
import { Box } from '../types';
|
||||||
|
|
||||||
const bringElementToFront = (() => {
|
const bringElementToFront = (() => {
|
||||||
let previous: HTMLElement | null = null;
|
let previous: HTMLElement | null = null;
|
||||||
@ -48,11 +49,7 @@ export class Overlay extends CompositeDisposable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly options: {
|
private readonly options: Box & {
|
||||||
height: number;
|
|
||||||
width: number;
|
|
||||||
left: number;
|
|
||||||
top: number;
|
|
||||||
container: HTMLElement;
|
container: HTMLElement;
|
||||||
content: HTMLElement;
|
content: HTMLElement;
|
||||||
minimumInViewportWidth?: number;
|
minimumInViewportWidth?: number;
|
||||||
@ -86,14 +83,7 @@ export class Overlay extends CompositeDisposable {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
setBounds(
|
setBounds(bounds: Partial<Box> = {}): void {
|
||||||
bounds: Partial<{
|
|
||||||
height: number;
|
|
||||||
width: number;
|
|
||||||
top: number;
|
|
||||||
left: number;
|
|
||||||
}> = {}
|
|
||||||
): void {
|
|
||||||
if (typeof bounds.height === 'number') {
|
if (typeof bounds.height === 'number') {
|
||||||
this._element.style.height = `${bounds.height}px`;
|
this._element.style.height = `${bounds.height}px`;
|
||||||
}
|
}
|
||||||
@ -139,7 +129,7 @@ export class Overlay extends CompositeDisposable {
|
|||||||
this._onDidChange.fire();
|
this._onDidChange.fire();
|
||||||
}
|
}
|
||||||
|
|
||||||
toJSON(): { top: number; left: number; height: number; width: number } {
|
toJSON(): Box {
|
||||||
const container = this.options.container.getBoundingClientRect();
|
const container = this.options.container.getBoundingClientRect();
|
||||||
const element = this._element.getBoundingClientRect();
|
const element = this._element.getBoundingClientRect();
|
||||||
|
|
||||||
|
@ -85,7 +85,7 @@ export class GreadyRenderContainer extends CompositeDisposable {
|
|||||||
toggleClass(
|
toggleClass(
|
||||||
focusContainer,
|
focusContainer,
|
||||||
'dv-render-overlay-float',
|
'dv-render-overlay-float',
|
||||||
panel.group.api.isFloating
|
panel.group.api.location === 'floating'
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
CompositeDisposable,
|
CompositeDisposable,
|
||||||
Disposable,
|
|
||||||
IDisposable,
|
IDisposable,
|
||||||
MutableDisposable,
|
MutableDisposable,
|
||||||
} from '../../../lifecycle';
|
} from '../../../lifecycle';
|
||||||
@ -8,7 +7,6 @@ import { Emitter, Event } from '../../../events';
|
|||||||
import { trackFocus } from '../../../dom';
|
import { trackFocus } from '../../../dom';
|
||||||
import { IDockviewPanel } from '../../dockviewPanel';
|
import { IDockviewPanel } from '../../dockviewPanel';
|
||||||
import { DockviewComponent } from '../../dockviewComponent';
|
import { DockviewComponent } from '../../dockviewComponent';
|
||||||
import { DragAndDropObserver } from '../../../dnd/dnd';
|
|
||||||
import { Droptarget } from '../../../dnd/droptarget';
|
import { Droptarget } from '../../../dnd/droptarget';
|
||||||
import { DockviewGroupPanelModel } from '../../dockviewGroupPanelModel';
|
import { DockviewGroupPanelModel } from '../../dockviewGroupPanelModel';
|
||||||
import { getPanelData } from '../../../dnd/dataTransfer';
|
import { getPanelData } from '../../../dnd/dataTransfer';
|
||||||
@ -70,7 +68,11 @@ export class ContentContainer
|
|||||||
|
|
||||||
const data = getPanelData();
|
const data = getPanelData();
|
||||||
|
|
||||||
if (!data && event.shiftKey && !this.group.isFloating) {
|
if (
|
||||||
|
!data &&
|
||||||
|
event.shiftKey &&
|
||||||
|
this.group.location !== 'floating'
|
||||||
|
) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,6 +9,24 @@
|
|||||||
.tab {
|
.tab {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
&:focus-within,
|
||||||
|
&:focus {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
position: absolute;
|
||||||
|
content: '';
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
top: 0px;
|
||||||
|
left: 0px;
|
||||||
|
pointer-events: none;
|
||||||
|
outline: 1px solid var(--dv-tab-divider-color) !important;
|
||||||
|
outline-offset: -1px;
|
||||||
|
z-index: 5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&.dv-tab-dragging {
|
&.dv-tab-dragging {
|
||||||
.tab-action {
|
.tab-action {
|
||||||
background-color: var(--dv-activegroup-visiblepanel-tab-color);
|
background-color: var(--dv-activegroup-visiblepanel-tab-color);
|
||||||
|
@ -247,7 +247,7 @@ export class TabsContainer
|
|||||||
if (
|
if (
|
||||||
isFloatingGroupsEnabled &&
|
isFloatingGroupsEnabled &&
|
||||||
event.shiftKey &&
|
event.shiftKey &&
|
||||||
!this.group.api.isFloating
|
this.group.api.location !== 'floating'
|
||||||
) {
|
) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
@ -350,7 +350,7 @@ export class TabsContainer
|
|||||||
!this.accessor.options.disableFloatingGroups;
|
!this.accessor.options.disableFloatingGroups;
|
||||||
|
|
||||||
const isFloatingWithOnePanel =
|
const isFloatingWithOnePanel =
|
||||||
this.group.api.isFloating && this.size === 1;
|
this.group.api.location === 'floating' && this.size === 1;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
isFloatingGroupsEnabled &&
|
isFloatingGroupsEnabled &&
|
||||||
|
@ -23,9 +23,6 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 0px 8px;
|
padding: 0px 8px;
|
||||||
// padding: 0px;
|
|
||||||
// margin: 0px;
|
|
||||||
// justify-content: flex-end;
|
|
||||||
|
|
||||||
.close-action {
|
.close-action {
|
||||||
padding: 4px;
|
padding: 4px;
|
||||||
|
@ -47,29 +47,45 @@ import { getPanelData } from '../dnd/dataTransfer';
|
|||||||
import { Parameters } from '../panel/types';
|
import { Parameters } from '../panel/types';
|
||||||
import { Overlay } from '../dnd/overlay';
|
import { Overlay } from '../dnd/overlay';
|
||||||
import { toggleClass, watchElementResize } from '../dom';
|
import { toggleClass, watchElementResize } from '../dom';
|
||||||
import {
|
import { DockviewFloatingGroupPanel } from './dockviewFloatingGroupPanel';
|
||||||
DockviewFloatingGroupPanel,
|
|
||||||
IDockviewFloatingGroupPanel,
|
|
||||||
} from './dockviewFloatingGroupPanel';
|
|
||||||
import {
|
import {
|
||||||
GroupDragEvent,
|
GroupDragEvent,
|
||||||
TabDragEvent,
|
TabDragEvent,
|
||||||
} from './components/titlebar/tabsContainer';
|
} from './components/titlebar/tabsContainer';
|
||||||
import {
|
import { Box } from '../types';
|
||||||
OverlayRenderContainer,
|
|
||||||
DockviewPanelRenderer,
|
|
||||||
<<<<<<< Updated upstream
|
|
||||||
} from './components/greadyRenderContainer';
|
|
||||||
=======
|
|
||||||
} from '../overlayRenderContainer';
|
|
||||||
import { DockviewPopoutGroupPanel } from './dockviewPopoutGroupPanel';
|
import { DockviewPopoutGroupPanel } from './dockviewPopoutGroupPanel';
|
||||||
import {
|
import {
|
||||||
DEFAULT_FLOATING_GROUP_OVERFLOW_SIZE,
|
DEFAULT_FLOATING_GROUP_OVERFLOW_SIZE,
|
||||||
DEFAULT_FLOATING_GROUP_POSITION,
|
DEFAULT_FLOATING_GROUP_POSITION,
|
||||||
} from '../constants';
|
} from '../constants';
|
||||||
>>>>>>> Stashed changes
|
import { DockviewPanelRenderer, OverlayRenderContainer } from '../overlayRenderContainer';
|
||||||
|
|
||||||
const DEFAULT_FLOATING_GROUP_OVERFLOW_SIZE = 100;
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
export interface PanelReference {
|
export interface PanelReference {
|
||||||
update: (event: { params: { [key: string]: any } }) => void;
|
update: (event: { params: { [key: string]: any } }) => void;
|
||||||
@ -78,7 +94,12 @@ export interface PanelReference {
|
|||||||
|
|
||||||
export interface SerializedFloatingGroup {
|
export interface SerializedFloatingGroup {
|
||||||
data: GroupPanelViewState;
|
data: GroupPanelViewState;
|
||||||
position: { height: number; width: number; left: number; top: number };
|
position: Box;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SerializedPopoutGroup {
|
||||||
|
data: GroupPanelViewState;
|
||||||
|
position: Box | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SerializedDockview {
|
export interface SerializedDockview {
|
||||||
@ -91,6 +112,7 @@ export interface SerializedDockview {
|
|||||||
panels: Record<string, GroupviewPanelState>;
|
panels: Record<string, GroupviewPanelState>;
|
||||||
activeGroup?: string;
|
activeGroup?: string;
|
||||||
floatingGroups?: SerializedFloatingGroup[];
|
floatingGroups?: SerializedFloatingGroup[];
|
||||||
|
popoutGroups?: SerializedPopoutGroup[];
|
||||||
}
|
}
|
||||||
|
|
||||||
function typeValidate3(data: GroupPanelViewState, path: string): void {
|
function typeValidate3(data: GroupPanelViewState, path: string): void {
|
||||||
@ -205,7 +227,6 @@ export interface IDockviewComponent extends IBaseGrid<DockviewGroupPanel> {
|
|||||||
readonly activePanel: IDockviewPanel | undefined;
|
readonly activePanel: IDockviewPanel | undefined;
|
||||||
readonly totalPanels: number;
|
readonly totalPanels: number;
|
||||||
readonly panels: IDockviewPanel[];
|
readonly panels: IDockviewPanel[];
|
||||||
readonly floatingGroups: IDockviewFloatingGroupPanel[];
|
|
||||||
readonly onDidDrop: Event<DockviewDropEvent>;
|
readonly onDidDrop: Event<DockviewDropEvent>;
|
||||||
readonly orientation: Orientation;
|
readonly orientation: Orientation;
|
||||||
updateOptions(options: DockviewComponentUpdateOptions): void;
|
updateOptions(options: DockviewComponentUpdateOptions): void;
|
||||||
@ -246,6 +267,13 @@ export interface IDockviewComponent extends IBaseGrid<DockviewGroupPanel> {
|
|||||||
item: IDockviewPanel | DockviewGroupPanel,
|
item: IDockviewPanel | DockviewGroupPanel,
|
||||||
coord?: { x: number; y: number }
|
coord?: { x: number; y: number }
|
||||||
): void;
|
): void;
|
||||||
|
addPopoutGroup(
|
||||||
|
item: IDockviewPanel | DockviewGroupPanel,
|
||||||
|
options?: {
|
||||||
|
position?: Box;
|
||||||
|
popoutUrl?: string;
|
||||||
|
}
|
||||||
|
): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class DockviewComponent
|
export class DockviewComponent
|
||||||
@ -286,7 +314,8 @@ export class DockviewComponent
|
|||||||
readonly onDidActivePanelChange: Event<IDockviewPanel | undefined> =
|
readonly onDidActivePanelChange: Event<IDockviewPanel | undefined> =
|
||||||
this._onDidActivePanelChange.event;
|
this._onDidActivePanelChange.event;
|
||||||
|
|
||||||
readonly floatingGroups: DockviewFloatingGroupPanel[] = [];
|
private readonly _floatingGroups: DockviewFloatingGroupPanel[] = [];
|
||||||
|
private readonly _popoutGroups: DockviewPopoutGroupPanel[] = [];
|
||||||
|
|
||||||
get orientation(): Orientation {
|
get orientation(): Orientation {
|
||||||
return this.gridview.orientation;
|
return this.gridview.orientation;
|
||||||
@ -454,6 +483,73 @@ export class DockviewComponent
|
|||||||
this.updateWatermark();
|
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(
|
addFloatingGroup(
|
||||||
item: DockviewPanel | DockviewGroupPanel,
|
item: DockviewPanel | DockviewGroupPanel,
|
||||||
coord?: { x?: number; y?: number; height?: number; width?: number },
|
coord?: { x?: number; y?: number; height?: number; width?: number },
|
||||||
@ -482,12 +578,16 @@ export class DockviewComponent
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
group.model.isFloating = true;
|
group.model.location = 'floating';
|
||||||
|
|
||||||
const overlayLeft =
|
const overlayLeft =
|
||||||
typeof coord?.x === 'number' ? Math.max(coord.x, 0) : 100;
|
typeof coord?.x === 'number'
|
||||||
|
? Math.max(coord.x, 0)
|
||||||
|
: DEFAULT_FLOATING_GROUP_POSITION.left;
|
||||||
const overlayTop =
|
const overlayTop =
|
||||||
typeof coord?.y === 'number' ? Math.max(coord.y, 0) : 100;
|
typeof coord?.y === 'number'
|
||||||
|
? Math.max(coord.y, 0)
|
||||||
|
: DEFAULT_FLOATING_GROUP_POSITION.top;
|
||||||
|
|
||||||
const overlay = new Overlay({
|
const overlay = new Overlay({
|
||||||
container: this.gridview.element,
|
container: this.gridview.element,
|
||||||
@ -553,14 +653,14 @@ export class DockviewComponent
|
|||||||
dispose: () => {
|
dispose: () => {
|
||||||
disposable.dispose();
|
disposable.dispose();
|
||||||
|
|
||||||
group.model.isFloating = false;
|
group.model.location = 'grid';
|
||||||
remove(this.floatingGroups, floatingGroupPanel);
|
remove(this._floatingGroups, floatingGroupPanel);
|
||||||
this.updateWatermark();
|
this.updateWatermark();
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
this.floatingGroups.push(floatingGroupPanel);
|
this._floatingGroups.push(floatingGroupPanel);
|
||||||
this.updateWatermark();
|
this.updateWatermark();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -614,7 +714,7 @@ export class DockviewComponent
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (hasFloatingGroupOptionsChanged) {
|
if (hasFloatingGroupOptionsChanged) {
|
||||||
for (const group of this.floatingGroups) {
|
for (const group of this._floatingGroups) {
|
||||||
switch (this.options.floatingGroupBounds) {
|
switch (this.options.floatingGroupBounds) {
|
||||||
case 'boundedWithinViewport':
|
case 'boundedWithinViewport':
|
||||||
group.overlay.minimumInViewportHeight = undefined;
|
group.overlay.minimumInViewportHeight = undefined;
|
||||||
@ -647,8 +747,8 @@ export class DockviewComponent
|
|||||||
): void {
|
): void {
|
||||||
super.layout(width, height, forceResize);
|
super.layout(width, height, forceResize);
|
||||||
|
|
||||||
if (this.floatingGroups) {
|
if (this._floatingGroups) {
|
||||||
for (const floating of this.floatingGroups) {
|
for (const floating of this._floatingGroups) {
|
||||||
// ensure floting groups stay within visible boundaries
|
// ensure floting groups stay within visible boundaries
|
||||||
floating.overlay.setBounds();
|
floating.overlay.setBounds();
|
||||||
}
|
}
|
||||||
@ -726,11 +826,20 @@ export class DockviewComponent
|
|||||||
return collection;
|
return collection;
|
||||||
}, {} as { [key: string]: GroupviewPanelState });
|
}, {} as { [key: string]: GroupviewPanelState });
|
||||||
|
|
||||||
const floats: SerializedFloatingGroup[] = this.floatingGroups.map(
|
const floats: SerializedFloatingGroup[] = this._floatingGroups.map(
|
||||||
(floatingGroup) => {
|
(group) => {
|
||||||
return {
|
return {
|
||||||
data: floatingGroup.group.toJSON() as GroupPanelViewState,
|
data: group.group.toJSON() as GroupPanelViewState,
|
||||||
position: floatingGroup.overlay.toJSON(),
|
position: group.overlay.toJSON(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const popoutGroups: SerializedPopoutGroup[] = this._popoutGroups.map(
|
||||||
|
(group) => {
|
||||||
|
return {
|
||||||
|
data: group.group.toJSON() as GroupPanelViewState,
|
||||||
|
position: group.window.dimensions(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@ -745,6 +854,10 @@ export class DockviewComponent
|
|||||||
result.floatingGroups = floats;
|
result.floatingGroups = floats;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (popoutGroups.length > 0) {
|
||||||
|
result.popoutGroups = popoutGroups;
|
||||||
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -850,7 +963,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();
|
floatingGroup.overlay.setBounds();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -884,7 +1010,7 @@ export class DockviewComponent
|
|||||||
}
|
}
|
||||||
|
|
||||||
// iterate over a reassigned array since original array will be modified
|
// 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();
|
floatingGroup.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1008,7 +1134,10 @@ export class DockviewComponent
|
|||||||
panel = this.createPanel(options, group);
|
panel = this.createPanel(options, group);
|
||||||
group.model.openPanel(panel);
|
group.model.openPanel(panel);
|
||||||
this.doSetGroupAndPanelActive(group);
|
this.doSetGroupAndPanelActive(group);
|
||||||
} else if (referenceGroup.api.isFloating || target === 'center') {
|
} else if (
|
||||||
|
referenceGroup.api.location === 'floating' ||
|
||||||
|
target === 'center'
|
||||||
|
) {
|
||||||
panel = this.createPanel(options, referenceGroup);
|
panel = this.createPanel(options, referenceGroup);
|
||||||
referenceGroup.model.openPanel(panel);
|
referenceGroup.model.openPanel(panel);
|
||||||
} else {
|
} else {
|
||||||
@ -1092,7 +1221,7 @@ export class DockviewComponent
|
|||||||
}
|
}
|
||||||
|
|
||||||
private updateWatermark(): void {
|
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) {
|
if (!this.watermark) {
|
||||||
this.watermark = this.createWatermarkComponent();
|
this.watermark = this.createWatermarkComponent();
|
||||||
|
|
||||||
@ -1210,27 +1339,61 @@ export class DockviewComponent
|
|||||||
}
|
}
|
||||||
| undefined
|
| undefined
|
||||||
): DockviewGroupPanel {
|
): DockviewGroupPanel {
|
||||||
const floatingGroup = this.floatingGroups.find(
|
if (group.api.location === 'floating') {
|
||||||
(_) => _.group === group
|
const floatingGroup = this._floatingGroups.find(
|
||||||
);
|
(_) => _.group === group
|
||||||
if (floatingGroup) {
|
);
|
||||||
if (!options?.skipDispose) {
|
|
||||||
floatingGroup.group.dispose();
|
if (floatingGroup) {
|
||||||
this._groups.delete(group.id);
|
if (!options?.skipDispose) {
|
||||||
this._onDidRemoveGroup.fire(group);
|
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) {
|
if (group.api.location === 'popout') {
|
||||||
const groups = Array.from(this._groups.values());
|
const selectedGroup = this._popoutGroups.find(
|
||||||
|
(_) => _.group === group
|
||||||
|
);
|
||||||
|
|
||||||
this.doSetGroupActive(
|
if (selectedGroup) {
|
||||||
groups.length > 0 ? groups[0].value : undefined
|
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);
|
return super.doRemoveGroup(group, options);
|
||||||
@ -1285,11 +1448,7 @@ export class DockviewComponent
|
|||||||
if (sourceGroup && sourceGroup.size < 2) {
|
if (sourceGroup && sourceGroup.size < 2) {
|
||||||
const [targetParentLocation, to] = tail(targetLocation);
|
const [targetParentLocation, to] = tail(targetLocation);
|
||||||
|
|
||||||
const isFloating = this.floatingGroups.find(
|
if (sourceGroup.api.location === 'grid') {
|
||||||
(x) => x.group === sourceGroup
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!isFloating) {
|
|
||||||
const sourceLocation = getGridLocation(sourceGroup.element);
|
const sourceLocation = getGridLocation(sourceGroup.element);
|
||||||
const [sourceParentLocation, from] = tail(sourceLocation);
|
const [sourceParentLocation, from] = tail(sourceLocation);
|
||||||
|
|
||||||
@ -1365,16 +1524,31 @@ export class DockviewComponent
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const floatingGroup = this.floatingGroups.find(
|
switch (sourceGroup.api.location) {
|
||||||
(x) => x.group === sourceGroup
|
case 'grid':
|
||||||
);
|
this.gridview.removeView(
|
||||||
|
getGridLocation(sourceGroup.element)
|
||||||
if (floatingGroup) {
|
);
|
||||||
floatingGroup.dispose();
|
break;
|
||||||
} else {
|
case 'floating': {
|
||||||
this.gridview.removeView(
|
const selectedFloatingGroup = this._floatingGroups.find(
|
||||||
getGridLocation(sourceGroup.element)
|
(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(
|
const referenceLocation = getGridLocation(
|
||||||
|
@ -68,8 +68,8 @@ export class DockviewGroupPanel
|
|||||||
id,
|
id,
|
||||||
'groupview_default',
|
'groupview_default',
|
||||||
{
|
{
|
||||||
minimumHeight: 100,
|
minimumHeight: 0,
|
||||||
minimumWidth: 100,
|
minimumWidth: 0,
|
||||||
},
|
},
|
||||||
new DockviewGroupPanelApiImpl(id, accessor)
|
new DockviewGroupPanelApiImpl(id, accessor)
|
||||||
);
|
);
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { DockviewApi } from '../api/component.api';
|
import { DockviewApi } from '../api/component.api';
|
||||||
import { getPanelData, PanelTransfer } from '../dnd/dataTransfer';
|
import { getPanelData, PanelTransfer } from '../dnd/dataTransfer';
|
||||||
import { Droptarget, Position } from '../dnd/droptarget';
|
import { Position } from '../dnd/droptarget';
|
||||||
import { DockviewComponent } from './dockviewComponent';
|
import { DockviewComponent } from './dockviewComponent';
|
||||||
import { isAncestor, toggleClass } from '../dom';
|
import { isAncestor, toggleClass } from '../dom';
|
||||||
import { addDisposableListener, Emitter, Event } from '../events';
|
import { addDisposableListener, Emitter, Event } from '../events';
|
||||||
@ -130,6 +130,8 @@ export interface IDockviewGroupPanelModel extends IPanel {
|
|||||||
): boolean;
|
): boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type DockviewGroupLocation = 'grid' | 'floating' | 'popout';
|
||||||
|
|
||||||
export class DockviewGroupPanelModel
|
export class DockviewGroupPanelModel
|
||||||
extends CompositeDisposable
|
extends CompositeDisposable
|
||||||
implements IDockviewGroupPanelModel
|
implements IDockviewGroupPanelModel
|
||||||
@ -141,11 +143,12 @@ export class DockviewGroupPanelModel
|
|||||||
private watermark?: IWatermarkRenderer;
|
private watermark?: IWatermarkRenderer;
|
||||||
private _isGroupActive = false;
|
private _isGroupActive = false;
|
||||||
private _locked: DockviewGroupPanelLocked = false;
|
private _locked: DockviewGroupPanelLocked = false;
|
||||||
private _isFloating = false;
|
|
||||||
private _rightHeaderActions: IHeaderActionsRenderer | undefined;
|
private _rightHeaderActions: IHeaderActionsRenderer | undefined;
|
||||||
private _leftHeaderActions: IHeaderActionsRenderer | undefined;
|
private _leftHeaderActions: IHeaderActionsRenderer | undefined;
|
||||||
private _prefixHeaderActions: IHeaderActionsRenderer | undefined;
|
private _prefixHeaderActions: IHeaderActionsRenderer | undefined;
|
||||||
|
|
||||||
|
private _location: DockviewGroupLocation = 'grid';
|
||||||
|
|
||||||
private mostRecentlyUsed: IDockviewPanel[] = [];
|
private mostRecentlyUsed: IDockviewPanel[] = [];
|
||||||
|
|
||||||
private readonly _onDidChange = new Emitter<IViewSize | undefined>();
|
private readonly _onDidChange = new Emitter<IViewSize | undefined>();
|
||||||
@ -241,21 +244,47 @@ export class DockviewGroupPanelModel
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
get isFloating(): boolean {
|
get location(): DockviewGroupLocation {
|
||||||
return this._isFloating;
|
return this._location;
|
||||||
}
|
}
|
||||||
|
|
||||||
set isFloating(value: boolean) {
|
set location(value: DockviewGroupLocation) {
|
||||||
this._isFloating = value;
|
this._location = value;
|
||||||
|
|
||||||
this.contentContainer.dropTarget.setTargetZones(
|
toggleClass(this.container, 'dv-groupview-floating', false);
|
||||||
value ? ['center'] : ['top', 'bottom', 'left', 'right', 'center']
|
toggleClass(this.container, 'dv-groupview-popout', false);
|
||||||
);
|
|
||||||
|
|
||||||
toggleClass(this.container, 'dv-groupview-floating', value);
|
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']
|
||||||
|
);
|
||||||
|
|
||||||
this.groupPanel.api._onDidFloatingStateChange.fire({
|
toggleClass(this.container, 'dv-groupview-floating', true);
|
||||||
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,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -811,7 +840,6 @@ export class DockviewGroupPanelModel
|
|||||||
panel.dispose();
|
panel.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
// this.dropTarget.dispose();
|
|
||||||
this.tabsContainer.dispose();
|
this.tabsContainer.dispose();
|
||||||
this.contentContainer.dispose();
|
this.contentContainer.dispose();
|
||||||
}
|
}
|
||||||
|
@ -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;
|
minimumHeightWithinViewport?: number;
|
||||||
minimumWidthWithinViewport?: number;
|
minimumWidthWithinViewport?: number;
|
||||||
};
|
};
|
||||||
|
popoutUrl?: string;
|
||||||
defaultRenderer?: DockviewPanelRenderer;
|
defaultRenderer?: DockviewPanelRenderer;
|
||||||
debug?: boolean;
|
debug?: boolean;
|
||||||
}
|
}
|
||||||
|
@ -186,6 +186,38 @@ export function quasiDefaultPrevented(event: Event): boolean {
|
|||||||
return (event as any)[QUASI_PREVENT_DEFAULT_KEY];
|
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): {
|
export function getDomNodePagePosition(domNode: Element): {
|
||||||
left: number;
|
left: number;
|
||||||
top: number;
|
top: number;
|
||||||
|
@ -64,6 +64,11 @@ export interface IBaseGrid<T extends IGridPanelView> {
|
|||||||
layout(width: number, height: number, force?: boolean): void;
|
layout(width: number, height: number, force?: boolean): void;
|
||||||
setVisible(panel: T, visible: boolean): void;
|
setVisible(panel: T, visible: boolean): void;
|
||||||
isVisible(panel: T): boolean;
|
isVisible(panel: T): boolean;
|
||||||
|
maximizeGroup(panel: T): void;
|
||||||
|
isMaximizedGroup(panel: T): boolean;
|
||||||
|
exitMaximizedGroup(): void;
|
||||||
|
hasMaximizedGroup(): boolean;
|
||||||
|
readonly onDidMaxmizedGroupChange: Event<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export abstract class BaseGrid<T extends IGridPanelView>
|
export abstract class BaseGrid<T extends IGridPanelView>
|
||||||
@ -174,6 +179,26 @@ export abstract class BaseGrid<T extends IGridPanelView>
|
|||||||
return this.gridview.isViewVisible(getGridLocation(panel.element));
|
return this.gridview.isViewVisible(getGridLocation(panel.element));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
maximizeGroup(panel: T): void {
|
||||||
|
this.gridview.maximizeView(panel);
|
||||||
|
}
|
||||||
|
|
||||||
|
isMaximizedGroup(panel: T): boolean {
|
||||||
|
return this.gridview.maximizedView() === panel;
|
||||||
|
}
|
||||||
|
|
||||||
|
exitMaximizedGroup(): void {
|
||||||
|
this.gridview.exitMaximizedView();
|
||||||
|
}
|
||||||
|
|
||||||
|
hasMaximizedGroup(): boolean {
|
||||||
|
return this.gridview.hasMaximizedView();
|
||||||
|
}
|
||||||
|
|
||||||
|
get onDidMaxmizedGroupChange(): Event<void> {
|
||||||
|
return this.gridview.onDidMaxmizedNodeChange;
|
||||||
|
}
|
||||||
|
|
||||||
protected doAddGroup(
|
protected doAddGroup(
|
||||||
group: T,
|
group: T,
|
||||||
location: number[] = [0],
|
location: number[] = [0],
|
||||||
|
@ -33,6 +33,10 @@ export class BranchNode extends CompositeDisposable implements IView {
|
|||||||
readonly onDidChange: Event<{ size?: number; orthogonalSize?: number }> =
|
readonly onDidChange: Event<{ size?: number; orthogonalSize?: number }> =
|
||||||
this._onDidChange.event;
|
this._onDidChange.event;
|
||||||
|
|
||||||
|
private readonly _onDidVisibilityChange = new Emitter<boolean>();
|
||||||
|
readonly onDidVisibilityChange: Event<boolean> =
|
||||||
|
this._onDidVisibilityChange.event;
|
||||||
|
|
||||||
get width(): number {
|
get width(): number {
|
||||||
return this.orientation === Orientation.HORIZONTAL
|
return this.orientation === Orientation.HORIZONTAL
|
||||||
? this.size
|
? this.size
|
||||||
@ -48,11 +52,23 @@ export class BranchNode extends CompositeDisposable implements IView {
|
|||||||
get minimumSize(): number {
|
get minimumSize(): number {
|
||||||
return this.children.length === 0
|
return this.children.length === 0
|
||||||
? 0
|
? 0
|
||||||
: Math.max(...this.children.map((c) => c.minimumOrthogonalSize));
|
: Math.max(
|
||||||
|
...this.children.map((c, index) =>
|
||||||
|
this.splitview.isViewVisible(index)
|
||||||
|
? c.minimumOrthogonalSize
|
||||||
|
: 0
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
get maximumSize(): number {
|
get maximumSize(): number {
|
||||||
return Math.min(...this.children.map((c) => c.maximumOrthogonalSize));
|
return Math.min(
|
||||||
|
...this.children.map((c, index) =>
|
||||||
|
this.splitview.isViewVisible(index)
|
||||||
|
? c.maximumOrthogonalSize
|
||||||
|
: Number.POSITIVE_INFINITY
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
get minimumOrthogonalSize(): number {
|
get minimumOrthogonalSize(): number {
|
||||||
@ -163,6 +179,7 @@ export class BranchNode extends CompositeDisposable implements IView {
|
|||||||
|
|
||||||
this.addDisposables(
|
this.addDisposables(
|
||||||
this._onDidChange,
|
this._onDidChange,
|
||||||
|
this._onDidVisibilityChange,
|
||||||
this.splitview.onDidSashEnd(() => {
|
this.splitview.onDidSashEnd(() => {
|
||||||
this._onDidChange.fire({});
|
this._onDidChange.fire({});
|
||||||
})
|
})
|
||||||
@ -185,7 +202,7 @@ export class BranchNode extends CompositeDisposable implements IView {
|
|||||||
return this.splitview.isViewVisible(index);
|
return this.splitview.isViewVisible(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
setChildVisible(index: number, visible: boolean): void {
|
setChildVisible(index: number, visible: boolean): void {
|
||||||
if (index < 0 || index >= this.children.length) {
|
if (index < 0 || index >= this.children.length) {
|
||||||
throw new Error('Invalid index');
|
throw new Error('Invalid index');
|
||||||
}
|
}
|
||||||
@ -194,7 +211,18 @@ export class BranchNode extends CompositeDisposable implements IView {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const wereAllChildrenHidden = this.splitview.contentSize === 0;
|
||||||
this.splitview.setViewVisible(index, visible);
|
this.splitview.setViewVisible(index, visible);
|
||||||
|
const areAllChildrenHidden = this.splitview.contentSize === 0;
|
||||||
|
|
||||||
|
// If all children are hidden then the parent should hide the entire splitview
|
||||||
|
// If the entire splitview is hidden then the parent should show the splitview when a child is shown
|
||||||
|
if (
|
||||||
|
(visible && wereAllChildrenHidden) ||
|
||||||
|
(!visible && areAllChildrenHidden)
|
||||||
|
) {
|
||||||
|
this._onDidVisibilityChange.fire(visible);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
moveChild(from: number, to: number): void {
|
moveChild(from: number, to: number): void {
|
||||||
@ -285,15 +313,23 @@ export class BranchNode extends CompositeDisposable implements IView {
|
|||||||
private setupChildrenEvents(): void {
|
private setupChildrenEvents(): void {
|
||||||
this._childrenDisposable.dispose();
|
this._childrenDisposable.dispose();
|
||||||
|
|
||||||
this._childrenDisposable = Event.any(
|
this._childrenDisposable = new CompositeDisposable(
|
||||||
...this.children.map((c) => c.onDidChange)
|
Event.any(...this.children.map((c) => c.onDidChange))((e) => {
|
||||||
)((e) => {
|
/**
|
||||||
/**
|
* indicate a change has occured to allows any re-rendering but don't bubble
|
||||||
* indicate a change has occured to allows any re-rendering but don't bubble
|
* event because that was specific to this branch
|
||||||
* event because that was specific to this branch
|
*/
|
||||||
*/
|
this._onDidChange.fire({ size: e.orthogonalSize });
|
||||||
this._onDidChange.fire({ size: e.orthogonalSize });
|
}),
|
||||||
});
|
...this.children.map((c, i) => {
|
||||||
|
if (c instanceof BranchNode) {
|
||||||
|
return c.onDidVisibilityChange((visible) => {
|
||||||
|
this.setChildVisible(i, visible);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return Disposable.NONE;
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public dispose(): void {
|
public dispose(): void {
|
||||||
|
@ -270,9 +270,11 @@ export interface SerializedGridview<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class Gridview implements IDisposable {
|
export class Gridview implements IDisposable {
|
||||||
|
readonly element: HTMLElement;
|
||||||
|
|
||||||
private _root: BranchNode | undefined;
|
private _root: BranchNode | undefined;
|
||||||
public readonly element: HTMLElement;
|
private _maximizedNode: LeafNode | undefined = undefined;
|
||||||
private disposable: MutableDisposable = new MutableDisposable();
|
private readonly disposable: MutableDisposable = new MutableDisposable();
|
||||||
|
|
||||||
private readonly _onDidChange = new Emitter<{
|
private readonly _onDidChange = new Emitter<{
|
||||||
size?: number;
|
size?: number;
|
||||||
@ -281,6 +283,9 @@ export class Gridview implements IDisposable {
|
|||||||
readonly onDidChange: Event<{ size?: number; orthogonalSize?: number }> =
|
readonly onDidChange: Event<{ size?: number; orthogonalSize?: number }> =
|
||||||
this._onDidChange.event;
|
this._onDidChange.event;
|
||||||
|
|
||||||
|
private readonly _onDidMaxmizedNodeChange = new Emitter<void>();
|
||||||
|
readonly onDidMaxmizedNodeChange = this._onDidMaxmizedNodeChange.event;
|
||||||
|
|
||||||
public get length(): number {
|
public get length(): number {
|
||||||
return this._root ? this._root.children.length : 0;
|
return this._root ? this._root.children.length : 0;
|
||||||
}
|
}
|
||||||
@ -302,6 +307,7 @@ export class Gridview implements IDisposable {
|
|||||||
get width(): number {
|
get width(): number {
|
||||||
return this.root.width;
|
return this.root.width;
|
||||||
}
|
}
|
||||||
|
|
||||||
get height(): number {
|
get height(): number {
|
||||||
return this.root.height;
|
return this.root.height;
|
||||||
}
|
}
|
||||||
@ -309,16 +315,83 @@ export class Gridview implements IDisposable {
|
|||||||
get minimumWidth(): number {
|
get minimumWidth(): number {
|
||||||
return this.root.minimumWidth;
|
return this.root.minimumWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
get minimumHeight(): number {
|
get minimumHeight(): number {
|
||||||
return this.root.minimumHeight;
|
return this.root.minimumHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
get maximumWidth(): number {
|
get maximumWidth(): number {
|
||||||
return this.root.maximumHeight;
|
return this.root.maximumHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
get maximumHeight(): number {
|
get maximumHeight(): number {
|
||||||
return this.root.maximumHeight;
|
return this.root.maximumHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
maximizedView(): IGridView | undefined {
|
||||||
|
return this._maximizedNode?.view;
|
||||||
|
}
|
||||||
|
|
||||||
|
hasMaximizedView(): boolean {
|
||||||
|
return this._maximizedNode !== undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
maximizeView(view: IGridView): void {
|
||||||
|
const location = getGridLocation(view.element);
|
||||||
|
const [_, node] = this.getNode(location);
|
||||||
|
|
||||||
|
if (!(node instanceof LeafNode)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._maximizedNode === node) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.hasMaximizedView()) {
|
||||||
|
this.exitMaximizedView();
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideAllViewsBut(parent: BranchNode, exclude: LeafNode): void {
|
||||||
|
for (let i = 0; i < parent.children.length; i++) {
|
||||||
|
const child = parent.children[i];
|
||||||
|
if (child instanceof LeafNode) {
|
||||||
|
if (child !== exclude) {
|
||||||
|
parent.setChildVisible(i, false);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
hideAllViewsBut(child, exclude);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hideAllViewsBut(this.root, node);
|
||||||
|
this._maximizedNode = node;
|
||||||
|
this._onDidMaxmizedNodeChange.fire();
|
||||||
|
}
|
||||||
|
|
||||||
|
exitMaximizedView(): void {
|
||||||
|
if (!this._maximizedNode) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
function showViewsInReverseOrder(parent: BranchNode): void {
|
||||||
|
for (let index = parent.children.length - 1; index >= 0; index--) {
|
||||||
|
const child = parent.children[index];
|
||||||
|
if (child instanceof LeafNode) {
|
||||||
|
parent.setChildVisible(index, true);
|
||||||
|
} else {
|
||||||
|
showViewsInReverseOrder(child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
showViewsInReverseOrder(this.root);
|
||||||
|
|
||||||
|
this._maximizedNode = undefined;
|
||||||
|
this._onDidMaxmizedNodeChange.fire();
|
||||||
|
}
|
||||||
|
|
||||||
public serialize(): SerializedGridview<any> {
|
public serialize(): SerializedGridview<any> {
|
||||||
const root = serializeBranchNode(this.getView(), this.orientation);
|
const root = serializeBranchNode(this.getView(), this.orientation);
|
||||||
|
|
||||||
@ -333,6 +406,7 @@ export class Gridview implements IDisposable {
|
|||||||
public dispose(): void {
|
public dispose(): void {
|
||||||
this.disposable.dispose();
|
this.disposable.dispose();
|
||||||
this._onDidChange.dispose();
|
this._onDidChange.dispose();
|
||||||
|
this._onDidMaxmizedNodeChange.dispose();
|
||||||
this.root.dispose();
|
this.root.dispose();
|
||||||
|
|
||||||
this.element.remove();
|
this.element.remove();
|
||||||
@ -584,6 +658,10 @@ export class Gridview implements IDisposable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setViewVisible(location: number[], visible: boolean): void {
|
setViewVisible(location: number[], visible: boolean): void {
|
||||||
|
if (this.hasMaximizedView()) {
|
||||||
|
this.exitMaximizedView();
|
||||||
|
}
|
||||||
|
|
||||||
const [rest, index] = tail(location);
|
const [rest, index] = tail(location);
|
||||||
const [, parent] = this.getNode(rest);
|
const [, parent] = this.getNode(rest);
|
||||||
|
|
||||||
@ -595,6 +673,10 @@ export class Gridview implements IDisposable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public moveView(parentLocation: number[], from: number, to: number): void {
|
public moveView(parentLocation: number[], from: number, to: number): void {
|
||||||
|
if (this.hasMaximizedView()) {
|
||||||
|
this.exitMaximizedView();
|
||||||
|
}
|
||||||
|
|
||||||
const [, parent] = this.getNode(parentLocation);
|
const [, parent] = this.getNode(parentLocation);
|
||||||
|
|
||||||
if (!(parent instanceof BranchNode)) {
|
if (!(parent instanceof BranchNode)) {
|
||||||
@ -609,6 +691,10 @@ export class Gridview implements IDisposable {
|
|||||||
size: number | Sizing,
|
size: number | Sizing,
|
||||||
location: number[]
|
location: number[]
|
||||||
): void {
|
): void {
|
||||||
|
if (this.hasMaximizedView()) {
|
||||||
|
this.exitMaximizedView();
|
||||||
|
}
|
||||||
|
|
||||||
const [rest, index] = tail(location);
|
const [rest, index] = tail(location);
|
||||||
|
|
||||||
const [pathToParent, parent] = this.getNode(rest);
|
const [pathToParent, parent] = this.getNode(rest);
|
||||||
@ -670,6 +756,10 @@ export class Gridview implements IDisposable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
removeView(location: number[], sizing?: Sizing): IGridView {
|
removeView(location: number[], sizing?: Sizing): IGridView {
|
||||||
|
if (this.hasMaximizedView()) {
|
||||||
|
this.exitMaximizedView();
|
||||||
|
}
|
||||||
|
|
||||||
const [rest, index] = tail(location);
|
const [rest, index] = tail(location);
|
||||||
const [pathToParent, parent] = this.getNode(rest);
|
const [pathToParent, parent] = this.getNode(rest);
|
||||||
|
|
||||||
|
@ -121,7 +121,6 @@ export class LeafNode implements IView {
|
|||||||
public setVisible(visible: boolean): void {
|
public setVisible(visible: boolean): void {
|
||||||
if (this.view.setVisible) {
|
if (this.view.setVisible) {
|
||||||
this.view.setVisible(visible);
|
this.view.setVisible(visible);
|
||||||
this._onDidChange.fire({});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,11 +107,6 @@
|
|||||||
outline-offset: -1px;
|
outline-offset: -1px;
|
||||||
outline-color: var(--dv-paneview-active-outline-color);
|
outline-color: var(--dv-paneview-active-outline-color);
|
||||||
}
|
}
|
||||||
// outline-width: 1px;
|
|
||||||
// outline-style: solid;
|
|
||||||
// outline-offset: -1px;
|
|
||||||
// opacity: 1 !important;
|
|
||||||
// outline-color: dodgerblue;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -55,7 +55,6 @@
|
|||||||
|
|
||||||
& > .view-container > .view {
|
& > .view-container > .view {
|
||||||
&:not(:first-child) {
|
&:not(:first-child) {
|
||||||
// padding-left: 1px;
|
|
||||||
&::before {
|
&::before {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 1px;
|
width: 1px;
|
||||||
@ -89,7 +88,6 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
&:not(:first-child) {
|
&:not(:first-child) {
|
||||||
// padding-top: 1px;
|
|
||||||
&::before {
|
&::before {
|
||||||
height: 1px;
|
height: 1px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@ -113,12 +111,12 @@
|
|||||||
-ms-user-select: none; // IE 10 and IE 11
|
-ms-user-select: none; // IE 10 and IE 11
|
||||||
touch-action: none;
|
touch-action: none;
|
||||||
|
|
||||||
&:active {
|
&:not(.disabled):active {
|
||||||
transition: background-color 0.1s ease-in-out;
|
transition: background-color 0.1s ease-in-out;
|
||||||
background-color: var(--dv-active-sash-color, transparent);
|
background-color: var(--dv-active-sash-color, transparent);
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:not(.disabled):hover {
|
||||||
background-color: var(--dv-active-sash-color, transparent);
|
background-color: var(--dv-active-sash-color, transparent);
|
||||||
transition: background-color 0.1s ease-in-out;
|
transition: background-color 0.1s ease-in-out;
|
||||||
transition-delay: 0.5s;
|
transition-delay: 0.5s;
|
||||||
|
@ -104,8 +104,8 @@ export class Splitview {
|
|||||||
private _orientation: Orientation;
|
private _orientation: Orientation;
|
||||||
private _size = 0;
|
private _size = 0;
|
||||||
private _orthogonalSize = 0;
|
private _orthogonalSize = 0;
|
||||||
private contentSize = 0;
|
private _contentSize = 0;
|
||||||
private _proportions: number[] | undefined = undefined;
|
private _proportions: (number | undefined)[] | undefined = undefined;
|
||||||
private proportionalLayout: boolean;
|
private proportionalLayout: boolean;
|
||||||
private _startSnappingEnabled = true;
|
private _startSnappingEnabled = true;
|
||||||
private _endSnappingEnabled = true;
|
private _endSnappingEnabled = true;
|
||||||
@ -117,6 +117,10 @@ export class Splitview {
|
|||||||
private readonly _onDidRemoveView = new Emitter<IView>();
|
private readonly _onDidRemoveView = new Emitter<IView>();
|
||||||
readonly onDidRemoveView = this._onDidRemoveView.event;
|
readonly onDidRemoveView = this._onDidRemoveView.event;
|
||||||
|
|
||||||
|
get contentSize(): number {
|
||||||
|
return this._contentSize;
|
||||||
|
}
|
||||||
|
|
||||||
get size(): number {
|
get size(): number {
|
||||||
return this._size;
|
return this._size;
|
||||||
}
|
}
|
||||||
@ -137,7 +141,7 @@ export class Splitview {
|
|||||||
return this.viewItems.length;
|
return this.viewItems.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
public get proportions(): number[] | undefined {
|
public get proportions(): (number | undefined)[] | undefined {
|
||||||
return this._proportions ? [...this._proportions] : undefined;
|
return this._proportions ? [...this._proportions] : undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -242,7 +246,7 @@ export class Splitview {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Initialize content size and proportions for first layout
|
// Initialize content size and proportions for first layout
|
||||||
this.contentSize = this.viewItems.reduce((r, i) => r + i.size, 0);
|
this._contentSize = this.viewItems.reduce((r, i) => r + i.size, 0);
|
||||||
this.saveProportions();
|
this.saveProportions();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -654,7 +658,7 @@ export class Splitview {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public layout(size: number, orthogonalSize: number): void {
|
public layout(size: number, orthogonalSize: number): void {
|
||||||
const previousSize = Math.max(this.size, this.contentSize);
|
const previousSize = Math.max(this.size, this._contentSize);
|
||||||
this.size = size;
|
this.size = size;
|
||||||
this.orthogonalSize = orthogonalSize;
|
this.orthogonalSize = orthogonalSize;
|
||||||
|
|
||||||
@ -675,14 +679,30 @@ export class Splitview {
|
|||||||
highPriorityIndexes
|
highPriorityIndexes
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
let total = 0;
|
||||||
|
|
||||||
for (let i = 0; i < this.viewItems.length; i++) {
|
for (let i = 0; i < this.viewItems.length; i++) {
|
||||||
const item = this.viewItems[i];
|
const item = this.viewItems[i];
|
||||||
|
const proportion = this.proportions[i];
|
||||||
|
|
||||||
item.size = clamp(
|
if (typeof proportion === 'number') {
|
||||||
Math.round(this.proportions[i] * size),
|
total += proportion;
|
||||||
item.minimumSize,
|
} else {
|
||||||
item.maximumSize
|
size -= item.size;
|
||||||
);
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < this.viewItems.length; i++) {
|
||||||
|
const item = this.viewItems[i];
|
||||||
|
const proportion = this.proportions[i];
|
||||||
|
|
||||||
|
if (typeof proportion === 'number' && total > 0) {
|
||||||
|
item.size = clamp(
|
||||||
|
Math.round((proportion * size) / total),
|
||||||
|
item.minimumSize,
|
||||||
|
item.maximumSize
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -747,15 +767,15 @@ export class Splitview {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private saveProportions(): void {
|
private saveProportions(): void {
|
||||||
if (this.proportionalLayout && this.contentSize > 0) {
|
if (this.proportionalLayout && this._contentSize > 0) {
|
||||||
this._proportions = this.viewItems.map(
|
this._proportions = this.viewItems.map((i) =>
|
||||||
(i) => i.size / this.contentSize
|
i.visible ? i.size / this._contentSize : undefined
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private layoutViews(): void {
|
private layoutViews(): void {
|
||||||
this.contentSize = this.viewItems.reduce((r, i) => r + i.size, 0);
|
this._contentSize = this.viewItems.reduce((r, i) => r + i.size, 0);
|
||||||
let sum = 0;
|
let sum = 0;
|
||||||
const x: number[] = [];
|
const x: number[] = [];
|
||||||
|
|
||||||
@ -880,7 +900,7 @@ export class Splitview {
|
|||||||
} else if (
|
} else if (
|
||||||
snappedAfter &&
|
snappedAfter &&
|
||||||
collapsesDown[index] &&
|
collapsesDown[index] &&
|
||||||
(position < this.contentSize || this.endSnappingEnabled)
|
(position < this._contentSize || this.endSnappingEnabled)
|
||||||
) {
|
) {
|
||||||
this.updateSash(sash, SashState.MAXIMUM);
|
this.updateSash(sash, SashState.MAXIMUM);
|
||||||
} else {
|
} else {
|
||||||
|
@ -1,3 +1,10 @@
|
|||||||
export type FunctionOrValue<T> = (() => T) | T;
|
export type FunctionOrValue<T> = (() => T) | T;
|
||||||
|
|
||||||
export type Optional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>;
|
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 DockviewFloating from '@site/sandboxes/floatinggroup-dockview/src/app';
|
||||||
import DockviewLockedGroup from '@site/sandboxes/lockedgroup-dockview/src/app';
|
import DockviewLockedGroup from '@site/sandboxes/lockedgroup-dockview/src/app';
|
||||||
import DockviewKeyboard from '@site/sandboxes/keyboard-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 DockviewRenderMode from '@site/sandboxes/rendermode-dockview/src/app';
|
||||||
|
|
||||||
import { DocRef } from '@site/src/components/ui/reference/docRef';
|
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')} />
|
<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
|
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:
|
You can control the bounding box of floating groups through the optional `floatingGroupBounds` options:
|
||||||
|
|
||||||
@ -370,6 +371,39 @@ You can control the bounding box of floating groups through the optional `floati
|
|||||||
react={DockviewFloating}
|
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);
|
||||||
|
```
|
||||||
|
|
||||||
|
<MultiFrameworkContainer
|
||||||
|
height={600}
|
||||||
|
sandboxId="popoutgroup-dockview"
|
||||||
|
react={DockviewPopoutGroup}
|
||||||
|
/>
|
||||||
|
|
||||||
|
|
||||||
|
## Maximized Groups
|
||||||
|
|
||||||
|
To maximize a group you can all
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
api.maxmimizeGroup(group);
|
||||||
|
```
|
||||||
|
|
||||||
## Panels
|
## Panels
|
||||||
|
|
||||||
### Add Panel
|
### Add Panel
|
||||||
@ -889,4 +923,7 @@ If you wish to interact with the drop event from one dockview instance in anothe
|
|||||||
|
|
||||||
### Window-like mananger with tabs
|
### Window-like mananger with tabs
|
||||||
|
|
||||||
<DockviewNative2 />
|
|
||||||
|
<MultiFrameworkContainer sandboxId="nativeapp-dockview" react={DockviewNative2} />
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
// @ts-check
|
// @ts-check
|
||||||
// Note: type annotations allow type checking and IDEs autocompletion
|
// Note: type annotations allow type checking and IDEs autocompletion
|
||||||
|
|
||||||
const lightCodeTheme = require('prism-react-renderer/themes/nightOwlLight');
|
const { themes } = require('prism-react-renderer');
|
||||||
// const lightCodeTheme = require('prism-react-renderer/themes/dracula');
|
const lightCodeTheme = themes.nightOwlLight;
|
||||||
const darkCodeTheme = require('prism-react-renderer/themes/vsDark');
|
const darkCodeTheme = themes.vsDark;
|
||||||
|
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
|
||||||
|
@ -21,25 +21,25 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@docusaurus/core": "^2.4.3",
|
"@docusaurus/core": "^3.0.1",
|
||||||
"@docusaurus/module-type-aliases": "^2.4.3",
|
"@docusaurus/module-type-aliases": "^3.0.1",
|
||||||
"@docusaurus/preset-classic": "^2.4.3",
|
"@docusaurus/preset-classic": "^3.0.1",
|
||||||
"@mdx-js/react": "^1.6.22",
|
"@mdx-js/react": "^3.0.0",
|
||||||
"@minoru/react-dnd-treeview": "^3.4.3",
|
"@minoru/react-dnd-treeview": "^3.4.4",
|
||||||
"@radix-ui/react-icons": "^1.3.0",
|
"@radix-ui/react-icons": "^1.3.0",
|
||||||
"@radix-ui/react-popover": "^1.0.7",
|
"@radix-ui/react-popover": "^1.0.7",
|
||||||
"axios": "^1.3.3",
|
"axios": "^1.6.3",
|
||||||
"clsx": "^1.2.1",
|
"clsx": "^2.1.0",
|
||||||
"dockview": "^1.8.5",
|
"dockview": "^1.8.5",
|
||||||
"prism-react-renderer": "^1.3.5",
|
"prism-react-renderer": "^2.3.1",
|
||||||
"react-dnd": "^16.0.1",
|
"react-dnd": "^16.0.1",
|
||||||
"recoil": "^0.7.6",
|
"recoil": "^0.7.7",
|
||||||
"source-map-loader": "^4.0.1",
|
"source-map-loader": "^4.0.2",
|
||||||
"uuid": "^9.0.0"
|
"uuid": "^9.0.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tsconfig/docusaurus": "^1.0.6",
|
"@tsconfig/docusaurus": "^2.0.2",
|
||||||
"@types/uuid": "^9.0.0",
|
"@types/uuid": "^9.0.7",
|
||||||
"docusaurus-plugin-sass": "^0.2.3"
|
"docusaurus-plugin-sass": "^0.2.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -109,29 +109,6 @@ const Icon = (props: {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const Button = () => {
|
|
||||||
const [position, setPosition] = React.useState<
|
|
||||||
{ x: number; y: number } | undefined
|
|
||||||
>(undefined);
|
|
||||||
|
|
||||||
const close = () => setPosition(undefined);
|
|
||||||
|
|
||||||
const onClick = (event: React.MouseEvent) => {
|
|
||||||
setPosition({ x: event.pageX, y: event.pageY });
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Icon icon="more_vert" onClick={onClick} />
|
|
||||||
{position && (
|
|
||||||
<Popover position={position} close={close}>
|
|
||||||
<div>hello</div>
|
|
||||||
</Popover>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const groupControlsComponents = {
|
const groupControlsComponents = {
|
||||||
panel_1: () => {
|
panel_1: () => {
|
||||||
return <Icon icon="file_download" />;
|
return <Icon icon="file_download" />;
|
||||||
@ -147,6 +124,34 @@ const RightControls = (props: IDockviewHeaderActionsProps) => {
|
|||||||
return groupControlsComponents[props.activePanel.id];
|
return groupControlsComponents[props.activePanel.id];
|
||||||
}, [props.isGroupActive, props.activePanel]);
|
}, [props.isGroupActive, props.activePanel]);
|
||||||
|
|
||||||
|
const [icon, setIcon] = React.useState<string>(
|
||||||
|
props.containerApi.hasMaximizedGroup()
|
||||||
|
? 'collapse_content'
|
||||||
|
: 'expand_content'
|
||||||
|
);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
const disposable = props.containerApi.onDidMaxmizedGroupChange(() => {
|
||||||
|
setIcon(
|
||||||
|
props.containerApi.hasMaximizedGroup()
|
||||||
|
? 'collapse_content'
|
||||||
|
: 'expand_content'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
disposable.dispose();
|
||||||
|
};
|
||||||
|
}, [props.containerApi]);
|
||||||
|
|
||||||
|
const onClick = () => {
|
||||||
|
if (props.containerApi.hasMaximizedGroup()) {
|
||||||
|
props.containerApi.exitMaxmizedGroup();
|
||||||
|
} else {
|
||||||
|
props.activePanel?.api.maximize();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="group-control"
|
className="group-control"
|
||||||
@ -160,7 +165,7 @@ const RightControls = (props: IDockviewHeaderActionsProps) => {
|
|||||||
>
|
>
|
||||||
{props.isGroupActive && <Icon icon="star" />}
|
{props.isGroupActive && <Icon icon="star" />}
|
||||||
{Component && <Component />}
|
{Component && <Component />}
|
||||||
<Button />
|
<Icon icon={icon} onClick={onClick} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -255,13 +255,15 @@ const LeftComponent = (props: IDockviewHeaderActionsProps) => {
|
|||||||
|
|
||||||
const RightComponent = (props: IDockviewHeaderActionsProps) => {
|
const RightComponent = (props: IDockviewHeaderActionsProps) => {
|
||||||
const [floating, setFloating] = React.useState<boolean>(
|
const [floating, setFloating] = React.useState<boolean>(
|
||||||
props.api.isFloating
|
props.api.location === 'floating'
|
||||||
);
|
);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
const disposable = props.group.api.onDidFloatingStateChange((event) => [
|
const disposable = props.group.api.onDidRenderPositionChange(
|
||||||
setFloating(event.isFloating),
|
(event) => {
|
||||||
]);
|
setFloating(event.location === 'floating');
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
disposable.dispose();
|
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
|
||||||
|
}
|
||||||
|
}
|
@ -39,7 +39,7 @@ For example if n=4 then our split view controls has 4 views. This generic approa
|
|||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
Additional by definition we can known V<sup>min</sup><sub>n</sub> <= V<sub>n</sub> <= V<sup>max</sup><sub>n</sub>
|
Additional by definition we can known V<sup>min</sup><sub>n</sub> \<= V<sub>n</sub> \<= V<sup>max</sup><sub>n</sub>
|
||||||
|
|
||||||
To be able to resize a view we need to be able to drag on the edge of a view to increase or decrease it's size.
|
To be able to resize a view we need to be able to drag on the edge of a view to increase or decrease it's size.
|
||||||
This can be achieved by introducing a narrow component that sits between each view acting as a 'drag handle'.
|
This can be achieved by introducing a narrow component that sits between each view acting as a 'drag handle'.
|
||||||
|
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…
x
Reference in New Issue
Block a user