From 0bca63b550dd66fd7922afaabb4b39d5d300752f Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Tue, 30 Jan 2024 17:41:04 +0000 Subject: [PATCH] feat: popout group enhancements --- .../dockview/components/panel/content.spec.ts | 39 ++++++----- .../dockview/dockviewComponent.spec.ts | 67 ++++++++++--------- .../src/dockview/components/panel/content.ts | 4 +- .../src/dockview/dockviewComponent.ts | 45 +++++++++++-- .../src/dockview/dockviewGroupPanelModel.ts | 22 ++++++ 5 files changed, 121 insertions(+), 56 deletions(-) diff --git a/packages/dockview-core/src/__tests__/dockview/components/panel/content.spec.ts b/packages/dockview-core/src/__tests__/dockview/components/panel/content.spec.ts index eacad7b9a..670c981d8 100644 --- a/packages/dockview-core/src/__tests__/dockview/components/panel/content.spec.ts +++ b/packages/dockview-core/src/__tests__/dockview/components/panel/content.spec.ts @@ -62,16 +62,19 @@ describe('contentContainer', () => { const disposable = new CompositeDisposable(); - const dockviewComponent = jest.fn(() => { - return { - renderer: 'onlyWhenVisibile', - overlayRenderContainer: new OverlayRenderContainer( - document.createElement('div') - ), - } as DockviewComponent; - }); + const overlayRenderContainer = new OverlayRenderContainer( + document.createElement('div') + ); - const cut = new ContentContainer(dockviewComponent(), jest.fn() as any); + const cut = new ContentContainer( + fromPartial({ + renderer: 'onlyWhenVisibile', + overlayRenderContainer, + }), + fromPartial({ + renderContainer: overlayRenderContainer, + }) + ); disposable.addDisposables( cut.onDidFocus(() => { @@ -84,12 +87,12 @@ describe('contentContainer', () => { const contentRenderer = new TestContentRenderer('id-1'); - const panel = { + const panel = fromPartial({ view: { content: contentRenderer, - } as Partial, + }, api: { renderer: 'onlyWhenVisibile' }, - } as Partial; + }); cut.openPanel(panel as IDockviewPanel); @@ -151,13 +154,17 @@ describe('contentContainer', () => { }); test("that panels renderered as 'onlyWhenVisibile' are removed when closed", () => { + const overlayRenderContainer = fromPartial({ + detatch: jest.fn(), + }); + const cut = new ContentContainer( fromPartial({ - overlayRenderContainer: { - detatch: jest.fn(), - }, + overlayRenderContainer, }), - fromPartial({}) + fromPartial({ + renderContainer: overlayRenderContainer, + }) ); const panel1 = fromPartial({ diff --git a/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts b/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts index 49c5ff221..51482f0ed 100644 --- a/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts +++ b/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts @@ -4434,44 +4434,12 @@ describe('dockviewComponent', () => { cb(); } }), + removeEventListener: jest.fn(), close: jest.fn(), }) ); }); - test('that can remove a popout group', async () => { - const container = document.createElement('div'); - - const dockview = new DockviewComponent({ - parentElement: container, - components: { - default: PanelContentPartTest, - }, - tabComponents: { - test_tab_id: PanelTabPartTest, - }, - orientation: Orientation.HORIZONTAL, - }); - - dockview.layout(1000, 500); - - const panel1 = dockview.addPanel({ - id: 'panel_1', - component: 'default', - }); - - await dockview.addPopoutGroup(panel1); - - expect(dockview.panels.length).toBe(1); - expect(dockview.groups.length).toBe(2); - expect(panel1.api.group.api.location.type).toBe('popout'); - - dockview.removePanel(panel1); - - expect(dockview.panels.length).toBe(0); - expect(dockview.groups.length).toBe(0); - }); - test('add a popout group', async () => { const container = document.createElement('div'); @@ -4511,6 +4479,39 @@ describe('dockviewComponent', () => { expect(dockview.panels.length).toBe(2); }); + test('that can remove a popout group', async () => { + const container = document.createElement('div'); + + const dockview = new DockviewComponent({ + parentElement: container, + components: { + default: PanelContentPartTest, + }, + tabComponents: { + test_tab_id: PanelTabPartTest, + }, + orientation: Orientation.HORIZONTAL, + }); + + dockview.layout(1000, 500); + + const panel1 = dockview.addPanel({ + id: 'panel_1', + component: 'default', + }); + + await dockview.addPopoutGroup(panel1); + + expect(dockview.panels.length).toBe(1); + expect(dockview.groups.length).toBe(2); + expect(panel1.api.group.api.location.type).toBe('popout'); + + dockview.removePanel(panel1); + + expect(dockview.panels.length).toBe(0); + expect(dockview.groups.length).toBe(0); + }); + test('move from fixed to popout group and back', async () => { const container = document.createElement('div'); diff --git a/packages/dockview-core/src/dockview/components/panel/content.ts b/packages/dockview-core/src/dockview/components/panel/content.ts index 9d9e3f2ca..933415283 100644 --- a/packages/dockview-core/src/dockview/components/panel/content.ts +++ b/packages/dockview-core/src/dockview/components/panel/content.ts @@ -133,7 +133,7 @@ export class ContentContainer switch (panel.api.renderer) { case 'onlyWhenVisibile': - this.accessor.overlayRenderContainer.detatch(panel); + this.group.renderContainer.detatch(panel); if (this.panel) { if (doRender) { this._element.appendChild( @@ -149,7 +149,7 @@ export class ContentContainer ) { this._element.removeChild(panel.view.content.element); } - container = this.accessor.overlayRenderContainer.attach({ + container = this.group.renderContainer.attach({ panel, referenceContainer: this, }); diff --git a/packages/dockview-core/src/dockview/dockviewComponent.ts b/packages/dockview-core/src/dockview/dockviewComponent.ts index 400205cc6..926abd40c 100644 --- a/packages/dockview-core/src/dockview/dockviewComponent.ts +++ b/packages/dockview-core/src/dockview/dockviewComponent.ts @@ -13,7 +13,7 @@ import { import { tail, sequenceEquals, remove } from '../array'; import { DockviewPanel, IDockviewPanel } from './dockviewPanel'; import { CompositeDisposable, Disposable, IDisposable } from '../lifecycle'; -import { Event, Emitter } from '../events'; +import { Event, Emitter, addDisposableWindowListener } from '../events'; import { Watermark } from './components/watermark/watermark'; import { IWatermarkRenderer, GroupviewPanelState } from './types'; import { sequentialNumberGenerator } from '../math'; @@ -561,12 +561,15 @@ export class DockviewComponent from: DockviewGroupPanel; to: DockviewGroupPanel; }) { + const activePanel = options.from.activePanel; const panels = [...options.from.panels].map((panel) => options.from.model.removePanel(panel) ); panels.forEach((panel) => { - options.to.model.openPanel(panel); + options.to.model.openPanel(panel, { + skipSetPanelActive: activePanel !== panel, + }); }); } @@ -624,10 +627,18 @@ export class DockviewComponent return; } + const gready = document.createElement('div'); + gready.className = 'dv-overlay-render-container'; + + const overlayRenderContainer = new OverlayRenderContainer( + gready + ); + const referenceGroup = item instanceof DockviewPanel ? item.group : item; const group = this.createGroup({ id: groupId }); + group.model.renderContainer = overlayRenderContainer; if (item instanceof DockviewPanel) { const panel = referenceGroup.model.removePanel(item); @@ -637,9 +648,13 @@ export class DockviewComponent from: referenceGroup, to: group, }); - referenceGroup.api.setHidden(false); + referenceGroup.api.setHidden(true); } + popoutContainer.classList.add('dv-dockview'); + popoutContainer.style.overflow = 'hidden'; + popoutContainer.appendChild(gready); + popoutContainer.appendChild(group.element); group.model.location = { @@ -655,6 +670,19 @@ export class DockviewComponent }; popoutWindowDisposable.addDisposables( + /** + * ResizeObserver seems slow here, I do not know why but we don't need it + * since we can reply on the window resize event as we will occupy the full + * window dimensions + */ + addDisposableWindowListener( + _window.window!, + 'resize', + () => { + group.layout(window.innerWidth, window.innerHeight); + } + ), + overlayRenderContainer, Disposable.from(() => { if (this.getPanel(referenceGroup.id)) { moveGroupWithoutDestroying({ @@ -666,12 +694,16 @@ export class DockviewComponent referenceGroup.api.setHidden(false); } - this.doRemoveGroup(group); + this.doRemoveGroup(group, { + skipPopoutAssociated: true, + }); } else { const removedGroup = this.doRemoveGroup(group, { skipDispose: true, skipActive: true, }); + removedGroup.model.renderContainer = + this.overlayRenderContainer; removedGroup.model.location = { type: 'grid' }; this.doAddGroup(removedGroup, [0]); } @@ -1484,6 +1516,7 @@ export class DockviewComponent | { skipActive?: boolean; skipDispose?: boolean; + skipPopoutAssociated?: boolean; } | undefined ): DockviewGroupPanel { @@ -1523,7 +1556,9 @@ export class DockviewComponent if (selectedGroup) { if (!options?.skipDispose) { - this.doRemoveGroup(selectedGroup.referenceGroup); + if (!options?.skipPopoutAssociated) { + this.removeGroup(selectedGroup.referenceGroup); + } selectedGroup.popoutGroup.dispose(); this._groups.delete(group.id); diff --git a/packages/dockview-core/src/dockview/dockviewGroupPanelModel.ts b/packages/dockview-core/src/dockview/dockviewGroupPanelModel.ts index 44a912c55..12d548681 100644 --- a/packages/dockview-core/src/dockview/dockviewGroupPanelModel.ts +++ b/packages/dockview-core/src/dockview/dockviewGroupPanelModel.ts @@ -26,6 +26,7 @@ import { DockviewDropTargets, IWatermarkRenderer } from './types'; import { DockviewGroupPanel } from './dockviewGroupPanel'; import { IDockviewPanel } from './dockviewPanel'; import { IHeaderActionsRenderer } from './options'; +import { OverlayRenderContainer } from '../overlayRenderContainer'; interface GroupMoveEvent { groupId: string; @@ -421,6 +422,27 @@ export class DockviewGroupPanelModel ); } + private _overwriteRenderContainer: OverlayRenderContainer | null = null; + + set renderContainer(value: OverlayRenderContainer | null) { + this.panels.forEach((panel) => { + this.renderContainer.detatch(panel); + }); + + this._overwriteRenderContainer = value; + + this.panels.forEach((panel) => { + this.rerender(panel); + }); + } + + get renderContainer(): OverlayRenderContainer { + return ( + this._overwriteRenderContainer ?? + this.accessor.overlayRenderContainer + ); + } + initialize(): void { if (this.options.panels) { this.options.panels.forEach((panel) => {