feat: popout windows

This commit is contained in:
mathuo 2023-12-26 20:36:49 +00:00
parent 584795b099
commit 406af8a87f
No known key found for this signature in database
GPG Key ID: C6EEDEFD6CA07281
9 changed files with 128 additions and 81 deletions

View File

@ -806,11 +806,14 @@ export class DockviewApi implements CommonApi<SerializedDockview> {
this.component.moveToPrevious(options);
}
/**
* Add a popout group in a new Window
*/
addPopoutGroup(
item: IDockviewPanel | DockviewGroupPanel,
options?: {
skipRemoveGroup?: boolean;
position?: Box;
popoutUrl?: string;
}
): void {
this.component.addPopoutGroup(item, options);

View File

@ -9,24 +9,6 @@
.tab {
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 {
.tab-action {
background-color: var(--dv-activegroup-visiblepanel-tab-color);

View File

@ -58,6 +58,7 @@ import {
GreadyRenderContainer,
DockviewPanelRenderer,
} from './components/greadyRenderContainer';
import { DockviewPopoutGroupPanel } from './dockviewPopoutGroupPanel';
function getTheme(element: HTMLElement): string | undefined {
function toClassList(element: HTMLElement) {
@ -224,12 +225,6 @@ export interface DockviewDropEvent extends GroupviewDropEvent {
group: DockviewGroupPanel | null;
}
export interface DockviewPopoutGroupPanel {
window: PopoutWindow;
disposable: IDisposable;
group: DockviewGroupPanel;
}
export interface IDockviewComponent extends IBaseGrid<DockviewGroupPanel> {
readonly activePanel: IDockviewPanel | undefined;
readonly totalPanels: number;
@ -277,8 +272,8 @@ export interface IDockviewComponent extends IBaseGrid<DockviewGroupPanel> {
addPopoutGroup(
item: IDockviewPanel | DockviewGroupPanel,
options?: {
skipRemoveGroup?: boolean;
position?: Box;
popoutUrl?: string;
}
): void;
}
@ -495,11 +490,10 @@ export class DockviewComponent
options?: {
skipRemoveGroup?: boolean;
position?: Box;
popoutUrl?: string;
}
): void {
let group: DockviewGroupPanel;
const theme = getTheme(this.gridview.element);
let box: Box | undefined = options?.position;
if (item instanceof DockviewPanel) {
@ -531,42 +525,31 @@ export class DockviewComponent
}
}
// const { top: boundingTop, left: boundingLeft } =
// this.element.getBoundingClientRect();
const theme = getTheme(this.gridview.element);
const window = new PopoutWindow('test', theme ?? '', {
url: this.options.popoutUrl ?? 'popout.html',
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,
},
});
const disposable = new CompositeDisposable();
const wrappedWindow = { window, disposable, group };
disposable.addDisposables(
window.onDidClose(() => {
group.model.location = 'grid';
remove(this._popoutGroups, wrappedWindow);
this.doAddGroup(group, [0]);
}),
popoutWindow.addDisposables(
{
dispose: () => {
group.model.location = 'grid';
remove(this._popoutGroups, wrappedWindow);
remove(this._popoutGroups, popoutWindow);
},
},
window
popoutWindow.window.onDidClose(() => {
this.doAddGroup(group, [0]);
})
);
group.model.location = 'popout';
this._popoutGroups.push(wrappedWindow);
window.open(group.element);
this._popoutGroups.push(popoutWindow);
}
addFloatingGroup(
@ -1395,7 +1378,7 @@ export class DockviewComponent
this._onDidRemoveGroup.fire(group);
}
selectedGroup.disposable.dispose();
selectedGroup.dispose();
if (!options?.skipActive && this._activeGroup === group) {
const groups = Array.from(this._groups.values());
@ -1539,10 +1522,6 @@ export class DockviewComponent
});
}
} else {
const floatingGroup = this._floatingGroups.find(
(x) => x.group === sourceGroup
);
switch (sourceGroup.api.location) {
case 'grid':
this.gridview.removeView(
@ -1550,23 +1529,22 @@ export class DockviewComponent
);
break;
case 'floating':
const floatingGroup = this._floatingGroups.find(
const selectedFloatingGroup = this._floatingGroups.find(
(x) => x.group === sourceGroup
);
if (!floatingGroup) {
if (!selectedFloatingGroup) {
throw new Error('failed to find floating group');
}
floatingGroup.dispose();
selectedFloatingGroup.dispose();
break;
case 'popout':
const selectedGroup = this._popoutGroups.find(
const selectedPopoutGroup = this._popoutGroups.find(
(x) => x.group === sourceGroup
);
if (!selectedGroup) {
if (!selectedPopoutGroup) {
throw new Error('failed to find popout group');
}
selectedGroup.disposable.dispose();
selectedGroup.window.dispose();
selectedPopoutGroup.dispose();
}
const referenceLocation = getGridLocation(

View File

@ -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);
}
}

View File

@ -84,7 +84,7 @@ export class PopoutWindow extends CompositeDisposable {
};
// prevent any default content from loading
externalWindow.document.body.replaceWith(document.createElement('div'));
// externalWindow.document.body.replaceWith(document.createElement('div'));
disposable.addDisposables(
addDisposableWindowListener(window, 'beforeunload', () => {

View File

@ -371,7 +371,23 @@ You can control the bounding box of floating groups through the optional `floati
react={DockviewFloating}
/>
## Popout Window
## Popout Groups
Dockview has built-in support for opening groups in new Windows.
Each popout window can contain a single group with many panels and you can have as many popout
windows as needed. You cannot dock multiple groups together in the same window.
To open an existing group in a new window
```tsx
api.addPopoutGroup(group);
```
From within a panel you may say
```tsx
props.containerApi.addPopoutGroup(props.api.group);
```
<DockviewPopoutGroup/>

View File

@ -250,11 +250,6 @@ const LeftControls = (props: IDockviewHeaderActionsProps) => {
const PrefixHeaderControls = (props: IDockviewHeaderActionsProps) => {
return (
<div
onClick={() => {
if (props.activePanel) {
props.containerApi.addPopoutGroup(props.activePanel.group);
}
}}
className="group-control"
style={{
display: 'flex',

View File

@ -50,7 +50,7 @@ function loadDefaultLayout(api: DockviewApi) {
component: 'default',
});
const panel4 = api.addPanel({
api.addPanel({
id: 'panel_4',
component: 'default',
});
@ -58,7 +58,7 @@ function loadDefaultLayout(api: DockviewApi) {
api.addPanel({
id: 'panel_5',
component: 'default',
position: { referencePanel: panel4 },
position: { direction: 'right' },
});
api.addPanel({
@ -213,13 +213,13 @@ const LeftComponent = (props: IDockviewHeaderActionsProps) => {
};
const RightComponent = (props: IDockviewHeaderActionsProps) => {
const [floating, setFloating] = React.useState<boolean>(
props.api.position === 'popout'
const [popout, setPopout] = React.useState<boolean>(
props.api.location === 'popout'
);
React.useEffect(() => {
const disposable = props.group.api.onDidRenderPositionChange(
(event) => [setFloating(event.position === 'popout')]
(event) => [setPopout(event.location === 'popout')]
);
return () => {
@ -228,7 +228,7 @@ const RightComponent = (props: IDockviewHeaderActionsProps) => {
}, [props.group.api]);
const onClick = () => {
if (floating) {
if (popout) {
const group = props.containerApi.addGroup();
props.group.api.moveTo({ group });
} else {
@ -240,7 +240,7 @@ const RightComponent = (props: IDockviewHeaderActionsProps) => {
<div style={{ height: '100%', color: 'white', padding: '0px 4px' }}>
<Icon
onClick={onClick}
icon={floating ? 'jump_to_element' : 'back_to_tab'}
icon={popout ? 'jump_to_element' : 'back_to_tab'}
/>
</div>
);

View File

@ -359,6 +359,11 @@
"signature": "<T extends object = Parameters>(options: AddPanelOptions<T>): IDockviewPanel",
"type": "method"
},
{
"name": "addPopoutGroup",
"signature": "(item: IDockviewPanel | DockviewGroupPanel, options?: { position: Box, skipRemoveGroup: boolean }): void",
"type": "method"
},
{
"name": "clear",
"comment": {
@ -1525,11 +1530,21 @@
"signature": "Event<void>",
"type": "property"
},
{
"name": "onDidRendererChange",
"signature": "Event<RendererChangedEvent>",
"type": "property"
},
{
"name": "onDidVisibilityChange",
"signature": "Event<VisibilityEvent>",
"type": "property"
},
{
"name": "renderer",
"signature": "DockviewPanelRenderer",
"type": "property"
},
{
"name": "title",
"signature": "string | undefined",
@ -1563,6 +1578,11 @@
"signature": "(): void",
"type": "method"
},
{
"name": "setRenderer",
"signature": "(renderer: DockviewPanelRenderer): void",
"type": "method"
},
{
"name": "setSize",
"signature": "(event: SizeEvent): void",
@ -2005,6 +2025,16 @@
"signature": "PanelCollection<IDockviewPanelProps<any>>",
"type": "property"
},
{
"name": "debug",
"signature": "boolean",
"type": "property"
},
{
"name": "defaultRenderer",
"signature": "DockviewPanelRenderer",
"type": "property"
},
{
"name": "defaultTabComponent",
"signature": "FunctionComponent<IDockviewPanelHeaderProps<any>>",