From 406af8a87f5afce127dab18891cf2c7b69956bf6 Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Tue, 26 Dec 2023 20:36:49 +0000 Subject: [PATCH] feat: popout windows --- .../dockview-core/src/api/component.api.ts | 5 +- .../dockview/components/tab/defaultTab.scss | 18 ----- .../src/dockview/dockviewComponent.ts | 74 +++++++------------ .../src/dockview/dockviewPopoutGroupPanel.ts | 43 +++++++++++ packages/dockview-core/src/popoutWindow.ts | 2 +- packages/docs/docs/components/dockview.mdx | 18 ++++- .../docs/sandboxes/demo-dockview/src/app.tsx | 5 -- .../popoutgroup-dockview/src/app.tsx | 14 ++-- packages/docs/src/generated/api.output.json | 30 ++++++++ 9 files changed, 128 insertions(+), 81 deletions(-) create mode 100644 packages/dockview-core/src/dockview/dockviewPopoutGroupPanel.ts diff --git a/packages/dockview-core/src/api/component.api.ts b/packages/dockview-core/src/api/component.api.ts index fc8d36334..2067824e4 100644 --- a/packages/dockview-core/src/api/component.api.ts +++ b/packages/dockview-core/src/api/component.api.ts @@ -806,11 +806,14 @@ export class DockviewApi implements CommonApi { 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); diff --git a/packages/dockview-core/src/dockview/components/tab/defaultTab.scss b/packages/dockview-core/src/dockview/components/tab/defaultTab.scss index 2a1e58734..d820534b8 100644 --- a/packages/dockview-core/src/dockview/components/tab/defaultTab.scss +++ b/packages/dockview-core/src/dockview/components/tab/defaultTab.scss @@ -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); diff --git a/packages/dockview-core/src/dockview/dockviewComponent.ts b/packages/dockview-core/src/dockview/dockviewComponent.ts index d9db6d830..6460244b4 100644 --- a/packages/dockview-core/src/dockview/dockviewComponent.ts +++ b/packages/dockview-core/src/dockview/dockviewComponent.ts @@ -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 { readonly activePanel: IDockviewPanel | undefined; readonly totalPanels: number; @@ -277,8 +272,8 @@ export interface IDockviewComponent extends IBaseGrid { 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', - left: box.left, - top: box.top, - width: box.width, - height: box.height, + 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( diff --git a/packages/dockview-core/src/dockview/dockviewPopoutGroupPanel.ts b/packages/dockview-core/src/dockview/dockviewPopoutGroupPanel.ts new file mode 100644 index 000000000..3052cb6ff --- /dev/null +++ b/packages/dockview-core/src/dockview/dockviewPopoutGroupPanel.ts @@ -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); + } +} diff --git a/packages/dockview-core/src/popoutWindow.ts b/packages/dockview-core/src/popoutWindow.ts index 2fb14fa7d..2994e44ec 100644 --- a/packages/dockview-core/src/popoutWindow.ts +++ b/packages/dockview-core/src/popoutWindow.ts @@ -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', () => { diff --git a/packages/docs/docs/components/dockview.mdx b/packages/docs/docs/components/dockview.mdx index db5f14a23..d58ceb062 100644 --- a/packages/docs/docs/components/dockview.mdx +++ b/packages/docs/docs/components/dockview.mdx @@ -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); +``` diff --git a/packages/docs/sandboxes/demo-dockview/src/app.tsx b/packages/docs/sandboxes/demo-dockview/src/app.tsx index e420a0ddc..6ea9ac80c 100644 --- a/packages/docs/sandboxes/demo-dockview/src/app.tsx +++ b/packages/docs/sandboxes/demo-dockview/src/app.tsx @@ -250,11 +250,6 @@ const LeftControls = (props: IDockviewHeaderActionsProps) => { const PrefixHeaderControls = (props: IDockviewHeaderActionsProps) => { return (
{ - if (props.activePanel) { - props.containerApi.addPopoutGroup(props.activePanel.group); - } - }} className="group-control" style={{ display: 'flex', diff --git a/packages/docs/sandboxes/popoutgroup-dockview/src/app.tsx b/packages/docs/sandboxes/popoutgroup-dockview/src/app.tsx index 4deedf4f6..1d16e0029 100644 --- a/packages/docs/sandboxes/popoutgroup-dockview/src/app.tsx +++ b/packages/docs/sandboxes/popoutgroup-dockview/src/app.tsx @@ -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( - props.api.position === 'popout' + const [popout, setPopout] = React.useState( + 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) => {
); diff --git a/packages/docs/src/generated/api.output.json b/packages/docs/src/generated/api.output.json index 4c3fdca77..d5d9a667f 100644 --- a/packages/docs/src/generated/api.output.json +++ b/packages/docs/src/generated/api.output.json @@ -359,6 +359,11 @@ "signature": "(options: AddPanelOptions): 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", "type": "property" }, + { + "name": "onDidRendererChange", + "signature": "Event", + "type": "property" + }, { "name": "onDidVisibilityChange", "signature": "Event", "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>", "type": "property" }, + { + "name": "debug", + "signature": "boolean", + "type": "property" + }, + { + "name": "defaultRenderer", + "signature": "DockviewPanelRenderer", + "type": "property" + }, { "name": "defaultTabComponent", "signature": "FunctionComponent>",