mirror of
https://github.com/mathuo/dockview
synced 2025-02-13 20:05:46 +00:00
feat: popout windows
This commit is contained in:
parent
584795b099
commit
406af8a87f
@ -806,11 +806,14 @@ export class DockviewApi implements CommonApi<SerializedDockview> {
|
|||||||
this.component.moveToPrevious(options);
|
this.component.moveToPrevious(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a popout group in a new Window
|
||||||
|
*/
|
||||||
addPopoutGroup(
|
addPopoutGroup(
|
||||||
item: IDockviewPanel | DockviewGroupPanel,
|
item: IDockviewPanel | DockviewGroupPanel,
|
||||||
options?: {
|
options?: {
|
||||||
skipRemoveGroup?: boolean;
|
|
||||||
position?: Box;
|
position?: Box;
|
||||||
|
popoutUrl?: string;
|
||||||
}
|
}
|
||||||
): void {
|
): void {
|
||||||
this.component.addPopoutGroup(item, options);
|
this.component.addPopoutGroup(item, options);
|
||||||
|
@ -9,24 +9,6 @@
|
|||||||
.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);
|
||||||
|
@ -58,6 +58,7 @@ import {
|
|||||||
GreadyRenderContainer,
|
GreadyRenderContainer,
|
||||||
DockviewPanelRenderer,
|
DockviewPanelRenderer,
|
||||||
} from './components/greadyRenderContainer';
|
} from './components/greadyRenderContainer';
|
||||||
|
import { DockviewPopoutGroupPanel } from './dockviewPopoutGroupPanel';
|
||||||
|
|
||||||
function getTheme(element: HTMLElement): string | undefined {
|
function getTheme(element: HTMLElement): string | undefined {
|
||||||
function toClassList(element: HTMLElement) {
|
function toClassList(element: HTMLElement) {
|
||||||
@ -224,12 +225,6 @@ export interface DockviewDropEvent extends GroupviewDropEvent {
|
|||||||
group: DockviewGroupPanel | null;
|
group: DockviewGroupPanel | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DockviewPopoutGroupPanel {
|
|
||||||
window: PopoutWindow;
|
|
||||||
disposable: IDisposable;
|
|
||||||
group: DockviewGroupPanel;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IDockviewComponent extends IBaseGrid<DockviewGroupPanel> {
|
export interface IDockviewComponent extends IBaseGrid<DockviewGroupPanel> {
|
||||||
readonly activePanel: IDockviewPanel | undefined;
|
readonly activePanel: IDockviewPanel | undefined;
|
||||||
readonly totalPanels: number;
|
readonly totalPanels: number;
|
||||||
@ -277,8 +272,8 @@ export interface IDockviewComponent extends IBaseGrid<DockviewGroupPanel> {
|
|||||||
addPopoutGroup(
|
addPopoutGroup(
|
||||||
item: IDockviewPanel | DockviewGroupPanel,
|
item: IDockviewPanel | DockviewGroupPanel,
|
||||||
options?: {
|
options?: {
|
||||||
skipRemoveGroup?: boolean;
|
|
||||||
position?: Box;
|
position?: Box;
|
||||||
|
popoutUrl?: string;
|
||||||
}
|
}
|
||||||
): void;
|
): void;
|
||||||
}
|
}
|
||||||
@ -495,11 +490,10 @@ export class DockviewComponent
|
|||||||
options?: {
|
options?: {
|
||||||
skipRemoveGroup?: boolean;
|
skipRemoveGroup?: boolean;
|
||||||
position?: Box;
|
position?: Box;
|
||||||
|
popoutUrl?: string;
|
||||||
}
|
}
|
||||||
): void {
|
): void {
|
||||||
let group: DockviewGroupPanel;
|
let group: DockviewGroupPanel;
|
||||||
const theme = getTheme(this.gridview.element);
|
|
||||||
|
|
||||||
let box: Box | undefined = options?.position;
|
let box: Box | undefined = options?.position;
|
||||||
|
|
||||||
if (item instanceof DockviewPanel) {
|
if (item instanceof DockviewPanel) {
|
||||||
@ -531,42 +525,31 @@ export class DockviewComponent
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// const { top: boundingTop, left: boundingLeft } =
|
const theme = getTheme(this.gridview.element);
|
||||||
// this.element.getBoundingClientRect();
|
|
||||||
|
|
||||||
const window = new PopoutWindow('test', theme ?? '', {
|
const popoutWindow = new DockviewPopoutGroupPanel(group, {
|
||||||
url: this.options.popoutUrl ?? 'popout.html',
|
className: theme ?? '',
|
||||||
left: box.left,
|
popoutUrl: options?.popoutUrl ?? '/popout.html',
|
||||||
top: box.top,
|
box: {
|
||||||
width: box.width,
|
left: box.left,
|
||||||
height: box.height,
|
top: box.top,
|
||||||
|
width: box.width,
|
||||||
|
height: box.height,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const disposable = new CompositeDisposable();
|
popoutWindow.addDisposables(
|
||||||
const wrappedWindow = { window, disposable, group };
|
|
||||||
|
|
||||||
disposable.addDisposables(
|
|
||||||
window.onDidClose(() => {
|
|
||||||
group.model.location = 'grid';
|
|
||||||
|
|
||||||
remove(this._popoutGroups, wrappedWindow);
|
|
||||||
|
|
||||||
this.doAddGroup(group, [0]);
|
|
||||||
}),
|
|
||||||
{
|
{
|
||||||
dispose: () => {
|
dispose: () => {
|
||||||
group.model.location = 'grid';
|
remove(this._popoutGroups, popoutWindow);
|
||||||
remove(this._popoutGroups, wrappedWindow);
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
window
|
popoutWindow.window.onDidClose(() => {
|
||||||
|
this.doAddGroup(group, [0]);
|
||||||
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
group.model.location = 'popout';
|
this._popoutGroups.push(popoutWindow);
|
||||||
|
|
||||||
this._popoutGroups.push(wrappedWindow);
|
|
||||||
|
|
||||||
window.open(group.element);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
addFloatingGroup(
|
addFloatingGroup(
|
||||||
@ -1395,7 +1378,7 @@ export class DockviewComponent
|
|||||||
this._onDidRemoveGroup.fire(group);
|
this._onDidRemoveGroup.fire(group);
|
||||||
}
|
}
|
||||||
|
|
||||||
selectedGroup.disposable.dispose();
|
selectedGroup.dispose();
|
||||||
|
|
||||||
if (!options?.skipActive && this._activeGroup === group) {
|
if (!options?.skipActive && this._activeGroup === group) {
|
||||||
const groups = Array.from(this._groups.values());
|
const groups = Array.from(this._groups.values());
|
||||||
@ -1539,10 +1522,6 @@ export class DockviewComponent
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const floatingGroup = this._floatingGroups.find(
|
|
||||||
(x) => x.group === sourceGroup
|
|
||||||
);
|
|
||||||
|
|
||||||
switch (sourceGroup.api.location) {
|
switch (sourceGroup.api.location) {
|
||||||
case 'grid':
|
case 'grid':
|
||||||
this.gridview.removeView(
|
this.gridview.removeView(
|
||||||
@ -1550,23 +1529,22 @@ export class DockviewComponent
|
|||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case 'floating':
|
case 'floating':
|
||||||
const floatingGroup = this._floatingGroups.find(
|
const selectedFloatingGroup = this._floatingGroups.find(
|
||||||
(x) => x.group === sourceGroup
|
(x) => x.group === sourceGroup
|
||||||
);
|
);
|
||||||
if (!floatingGroup) {
|
if (!selectedFloatingGroup) {
|
||||||
throw new Error('failed to find floating group');
|
throw new Error('failed to find floating group');
|
||||||
}
|
}
|
||||||
floatingGroup.dispose();
|
selectedFloatingGroup.dispose();
|
||||||
break;
|
break;
|
||||||
case 'popout':
|
case 'popout':
|
||||||
const selectedGroup = this._popoutGroups.find(
|
const selectedPopoutGroup = this._popoutGroups.find(
|
||||||
(x) => x.group === sourceGroup
|
(x) => x.group === sourceGroup
|
||||||
);
|
);
|
||||||
if (!selectedGroup) {
|
if (!selectedPopoutGroup) {
|
||||||
throw new Error('failed to find popout group');
|
throw new Error('failed to find popout group');
|
||||||
}
|
}
|
||||||
selectedGroup.disposable.dispose();
|
selectedPopoutGroup.dispose();
|
||||||
selectedGroup.window.dispose();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const referenceLocation = getGridLocation(
|
const referenceLocation = getGridLocation(
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -84,7 +84,7 @@ export class PopoutWindow extends CompositeDisposable {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// prevent any default content from loading
|
// prevent any default content from loading
|
||||||
externalWindow.document.body.replaceWith(document.createElement('div'));
|
// externalWindow.document.body.replaceWith(document.createElement('div'));
|
||||||
|
|
||||||
disposable.addDisposables(
|
disposable.addDisposables(
|
||||||
addDisposableWindowListener(window, 'beforeunload', () => {
|
addDisposableWindowListener(window, 'beforeunload', () => {
|
||||||
|
@ -371,7 +371,23 @@ You can control the bounding box of floating groups through the optional `floati
|
|||||||
react={DockviewFloating}
|
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/>
|
<DockviewPopoutGroup/>
|
||||||
|
|
||||||
|
@ -250,11 +250,6 @@ const LeftControls = (props: IDockviewHeaderActionsProps) => {
|
|||||||
const PrefixHeaderControls = (props: IDockviewHeaderActionsProps) => {
|
const PrefixHeaderControls = (props: IDockviewHeaderActionsProps) => {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
onClick={() => {
|
|
||||||
if (props.activePanel) {
|
|
||||||
props.containerApi.addPopoutGroup(props.activePanel.group);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
className="group-control"
|
className="group-control"
|
||||||
style={{
|
style={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
|
@ -50,7 +50,7 @@ function loadDefaultLayout(api: DockviewApi) {
|
|||||||
component: 'default',
|
component: 'default',
|
||||||
});
|
});
|
||||||
|
|
||||||
const panel4 = api.addPanel({
|
api.addPanel({
|
||||||
id: 'panel_4',
|
id: 'panel_4',
|
||||||
component: 'default',
|
component: 'default',
|
||||||
});
|
});
|
||||||
@ -58,7 +58,7 @@ function loadDefaultLayout(api: DockviewApi) {
|
|||||||
api.addPanel({
|
api.addPanel({
|
||||||
id: 'panel_5',
|
id: 'panel_5',
|
||||||
component: 'default',
|
component: 'default',
|
||||||
position: { referencePanel: panel4 },
|
position: { direction: 'right' },
|
||||||
});
|
});
|
||||||
|
|
||||||
api.addPanel({
|
api.addPanel({
|
||||||
@ -213,13 +213,13 @@ const LeftComponent = (props: IDockviewHeaderActionsProps) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const RightComponent = (props: IDockviewHeaderActionsProps) => {
|
const RightComponent = (props: IDockviewHeaderActionsProps) => {
|
||||||
const [floating, setFloating] = React.useState<boolean>(
|
const [popout, setPopout] = React.useState<boolean>(
|
||||||
props.api.position === 'popout'
|
props.api.location === 'popout'
|
||||||
);
|
);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
const disposable = props.group.api.onDidRenderPositionChange(
|
const disposable = props.group.api.onDidRenderPositionChange(
|
||||||
(event) => [setFloating(event.position === 'popout')]
|
(event) => [setPopout(event.location === 'popout')]
|
||||||
);
|
);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
@ -228,7 +228,7 @@ const RightComponent = (props: IDockviewHeaderActionsProps) => {
|
|||||||
}, [props.group.api]);
|
}, [props.group.api]);
|
||||||
|
|
||||||
const onClick = () => {
|
const onClick = () => {
|
||||||
if (floating) {
|
if (popout) {
|
||||||
const group = props.containerApi.addGroup();
|
const group = props.containerApi.addGroup();
|
||||||
props.group.api.moveTo({ group });
|
props.group.api.moveTo({ group });
|
||||||
} else {
|
} else {
|
||||||
@ -240,7 +240,7 @@ const RightComponent = (props: IDockviewHeaderActionsProps) => {
|
|||||||
<div style={{ height: '100%', color: 'white', padding: '0px 4px' }}>
|
<div style={{ height: '100%', color: 'white', padding: '0px 4px' }}>
|
||||||
<Icon
|
<Icon
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
icon={floating ? 'jump_to_element' : 'back_to_tab'}
|
icon={popout ? 'jump_to_element' : 'back_to_tab'}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -359,6 +359,11 @@
|
|||||||
"signature": "<T extends object = Parameters>(options: AddPanelOptions<T>): IDockviewPanel",
|
"signature": "<T extends object = Parameters>(options: AddPanelOptions<T>): IDockviewPanel",
|
||||||
"type": "method"
|
"type": "method"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "addPopoutGroup",
|
||||||
|
"signature": "(item: IDockviewPanel | DockviewGroupPanel, options?: { position: Box, skipRemoveGroup: boolean }): void",
|
||||||
|
"type": "method"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "clear",
|
"name": "clear",
|
||||||
"comment": {
|
"comment": {
|
||||||
@ -1525,11 +1530,21 @@
|
|||||||
"signature": "Event<void>",
|
"signature": "Event<void>",
|
||||||
"type": "property"
|
"type": "property"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "onDidRendererChange",
|
||||||
|
"signature": "Event<RendererChangedEvent>",
|
||||||
|
"type": "property"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "onDidVisibilityChange",
|
"name": "onDidVisibilityChange",
|
||||||
"signature": "Event<VisibilityEvent>",
|
"signature": "Event<VisibilityEvent>",
|
||||||
"type": "property"
|
"type": "property"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "renderer",
|
||||||
|
"signature": "DockviewPanelRenderer",
|
||||||
|
"type": "property"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "title",
|
"name": "title",
|
||||||
"signature": "string | undefined",
|
"signature": "string | undefined",
|
||||||
@ -1563,6 +1578,11 @@
|
|||||||
"signature": "(): void",
|
"signature": "(): void",
|
||||||
"type": "method"
|
"type": "method"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "setRenderer",
|
||||||
|
"signature": "(renderer: DockviewPanelRenderer): void",
|
||||||
|
"type": "method"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "setSize",
|
"name": "setSize",
|
||||||
"signature": "(event: SizeEvent): void",
|
"signature": "(event: SizeEvent): void",
|
||||||
@ -2005,6 +2025,16 @@
|
|||||||
"signature": "PanelCollection<IDockviewPanelProps<any>>",
|
"signature": "PanelCollection<IDockviewPanelProps<any>>",
|
||||||
"type": "property"
|
"type": "property"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "debug",
|
||||||
|
"signature": "boolean",
|
||||||
|
"type": "property"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "defaultRenderer",
|
||||||
|
"signature": "DockviewPanelRenderer",
|
||||||
|
"type": "property"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "defaultTabComponent",
|
"name": "defaultTabComponent",
|
||||||
"signature": "FunctionComponent<IDockviewPanelHeaderProps<any>>",
|
"signature": "FunctionComponent<IDockviewPanelHeaderProps<any>>",
|
||||||
|
Loading…
Reference in New Issue
Block a user