mirror of
https://github.com/mathuo/dockview
synced 2025-05-04 10:38:24 +00:00
Merge pull request #138 from mathuo/137-group-controls-section
feat: group controls renderer
This commit is contained in:
commit
fbc8fce942
@ -1,9 +1,5 @@
|
||||
import { DefaultGroupPanelView } from '../../dockview/defaultGroupPanelView';
|
||||
import {
|
||||
IActionsRenderer,
|
||||
IContentRenderer,
|
||||
ITabRenderer,
|
||||
} from '../../groupview/types';
|
||||
import { IContentRenderer, ITabRenderer } from '../../groupview/types';
|
||||
|
||||
describe('defaultGroupPanelView', () => {
|
||||
test('dispose cleanup', () => {
|
||||
@ -23,24 +19,14 @@ describe('defaultGroupPanelView', () => {
|
||||
return partial as IContentRenderer;
|
||||
});
|
||||
|
||||
const actionsMock = jest.fn<IActionsRenderer, []>(() => {
|
||||
const partial: Partial<IContentRenderer> = {
|
||||
element: document.createElement('div'),
|
||||
dispose: jest.fn(),
|
||||
};
|
||||
return partial as IContentRenderer;
|
||||
});
|
||||
|
||||
const content = new contentMock();
|
||||
const tab = new tabMock();
|
||||
const actions = new actionsMock();
|
||||
|
||||
const cut = new DefaultGroupPanelView({ content, tab, actions });
|
||||
const cut = new DefaultGroupPanelView({ content, tab });
|
||||
|
||||
cut.dispose();
|
||||
|
||||
expect(content.dispose).toHaveBeenCalled();
|
||||
expect(tab.dispose).toHaveBeenCalled();
|
||||
expect(actions.dispose).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
@ -152,7 +152,7 @@ class TestGroupPanel implements IDockviewPanel {
|
||||
public readonly title: string,
|
||||
accessor: DockviewComponent
|
||||
) {
|
||||
this.api = new DockviewPanelApiImpl(this, this._group);
|
||||
this.api = new DockviewPanelApiImpl(this, this._group!);
|
||||
this._group = new GroupPanel(accessor, id, {});
|
||||
this.view = new TestGroupPanelView(
|
||||
new PanelContentPartTest(id, 'component')
|
||||
@ -162,9 +162,8 @@ class TestGroupPanel implements IDockviewPanel {
|
||||
get params(): Record<string, any> {
|
||||
return {};
|
||||
}
|
||||
|
||||
get group(): GroupPanel | undefined {
|
||||
return this._group;
|
||||
get group(): GroupPanel {
|
||||
return this._group!;
|
||||
}
|
||||
|
||||
updateParentGroup(group: GroupPanel, isGroupActive: boolean): void {
|
||||
|
@ -11,11 +11,7 @@ import {
|
||||
IWatermarkRenderer,
|
||||
} from '../../groupview/types';
|
||||
import { PanelUpdateEvent } from '../../panel/types';
|
||||
import {
|
||||
GroupChangeKind2,
|
||||
GroupOptions,
|
||||
Groupview,
|
||||
} from '../../groupview/groupview';
|
||||
import { GroupOptions, Groupview } from '../../groupview/groupview';
|
||||
import { DockviewPanelApi } from '../../api/groupPanelApi';
|
||||
import {
|
||||
DefaultGroupPanelView,
|
||||
@ -24,6 +20,13 @@ import {
|
||||
import { GroupPanel } from '../../groupview/groupviewPanel';
|
||||
import { fireEvent } from '@testing-library/dom';
|
||||
import { LocalSelectionTransfer, PanelTransfer } from '../../dnd/dataTransfer';
|
||||
import { CompositeDisposable } from '../../lifecycle';
|
||||
|
||||
enum GroupChangeKind2 {
|
||||
ADD_PANEL,
|
||||
REMOVE_PANEL,
|
||||
PANEL_ACTIVE,
|
||||
}
|
||||
|
||||
class Watermark implements IWatermarkRenderer {
|
||||
public readonly element = document.createElement('div');
|
||||
@ -193,7 +196,7 @@ export class TestPanel implements IDockviewPanel {
|
||||
toJSON(): GroupviewPanelState {
|
||||
return {
|
||||
id: this.id,
|
||||
view: this._view.toJSON(),
|
||||
view: this._view?.toJSON(),
|
||||
title: this._params?.title,
|
||||
};
|
||||
}
|
||||
@ -269,10 +272,29 @@ describe('groupview', () => {
|
||||
|
||||
const events: Array<{
|
||||
kind: GroupChangeKind2;
|
||||
panel?: IDockviewPanel;
|
||||
}> = [];
|
||||
const disposable = groupview2.model.onDidGroupChange((e) => {
|
||||
events.push(e);
|
||||
});
|
||||
|
||||
const disposable = new CompositeDisposable(
|
||||
groupview2.model.onDidAddPanel((e) => {
|
||||
events.push({
|
||||
kind: GroupChangeKind2.ADD_PANEL,
|
||||
panel: e.panel,
|
||||
});
|
||||
}),
|
||||
groupview2.model.onDidRemovePanel((e) => {
|
||||
events.push({
|
||||
kind: GroupChangeKind2.REMOVE_PANEL,
|
||||
panel: e.panel,
|
||||
});
|
||||
}),
|
||||
groupview2.model.onDidActivePanelChange((e) => {
|
||||
events.push({
|
||||
kind: GroupChangeKind2.PANEL_ACTIVE,
|
||||
panel: e.panel,
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
groupview2.initialize();
|
||||
|
||||
@ -301,10 +323,29 @@ describe('groupview', () => {
|
||||
test('panel events flow', () => {
|
||||
let events: Array<{
|
||||
kind: GroupChangeKind2;
|
||||
panel?: IDockviewPanel;
|
||||
}> = [];
|
||||
const disposable = groupview.model.onDidGroupChange((e) => {
|
||||
events.push(e);
|
||||
});
|
||||
|
||||
const disposable = new CompositeDisposable(
|
||||
groupview.model.onDidAddPanel((e) => {
|
||||
events.push({
|
||||
kind: GroupChangeKind2.ADD_PANEL,
|
||||
panel: e.panel,
|
||||
});
|
||||
}),
|
||||
groupview.model.onDidRemovePanel((e) => {
|
||||
events.push({
|
||||
kind: GroupChangeKind2.REMOVE_PANEL,
|
||||
panel: e.panel,
|
||||
});
|
||||
}),
|
||||
groupview.model.onDidActivePanelChange((e) => {
|
||||
events.push({
|
||||
kind: GroupChangeKind2.PANEL_ACTIVE,
|
||||
panel: e.panel,
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
const panel1 = new TestPanel('panel1', jest.fn() as any);
|
||||
const panel2 = new TestPanel('panel2', jest.fn() as any);
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { DefaultTab } from './components/tab/defaultTab';
|
||||
import {
|
||||
GroupPanelPartInitParameters,
|
||||
IActionsRenderer,
|
||||
IContentRenderer,
|
||||
ITabRenderer,
|
||||
} from '../groupview/types';
|
||||
@ -12,7 +11,6 @@ import { GroupPanelUpdateEvent } from '../groupview/groupPanel';
|
||||
export interface IGroupPanelView extends IDisposable {
|
||||
readonly content: IContentRenderer;
|
||||
readonly tab?: ITabRenderer;
|
||||
readonly actions?: IActionsRenderer;
|
||||
update(event: GroupPanelUpdateEvent): void;
|
||||
layout(width: number, height: number): void;
|
||||
init(params: GroupPanelPartInitParameters): void;
|
||||
@ -23,37 +21,18 @@ export interface IGroupPanelView extends IDisposable {
|
||||
export class DefaultGroupPanelView implements IGroupPanelView {
|
||||
private readonly _content: IContentRenderer;
|
||||
private readonly _tab: ITabRenderer;
|
||||
private readonly _actions: IActionsRenderer | undefined;
|
||||
|
||||
get content() {
|
||||
get content(): IContentRenderer {
|
||||
return this._content;
|
||||
}
|
||||
|
||||
get tab() {
|
||||
get tab(): ITabRenderer {
|
||||
return this._tab;
|
||||
}
|
||||
|
||||
get actions() {
|
||||
return this._actions;
|
||||
}
|
||||
|
||||
constructor(renderers: {
|
||||
content: IContentRenderer;
|
||||
tab?: ITabRenderer;
|
||||
actions?: IActionsRenderer;
|
||||
}) {
|
||||
constructor(renderers: { content: IContentRenderer; tab?: ITabRenderer }) {
|
||||
this._content = renderers.content;
|
||||
this._tab = renderers.tab ?? new DefaultTab();
|
||||
this._actions =
|
||||
renderers.actions ||
|
||||
(this.content.actions
|
||||
? {
|
||||
element: this.content.actions,
|
||||
dispose: () => {
|
||||
//
|
||||
},
|
||||
}
|
||||
: undefined);
|
||||
}
|
||||
|
||||
init(params: GroupPanelPartInitParameters): void {
|
||||
@ -85,6 +64,5 @@ export class DefaultGroupPanelView implements IGroupPanelView {
|
||||
dispose(): void {
|
||||
this.content.dispose();
|
||||
this.tab.dispose();
|
||||
this.actions?.dispose();
|
||||
}
|
||||
}
|
||||
|
@ -36,7 +36,6 @@ import { LayoutMouseEvent, MouseEventKind } from '../groupview/tab';
|
||||
import { Orientation } from '../splitview/core/splitview';
|
||||
import { DefaultTab } from './components/tab/defaultTab';
|
||||
import {
|
||||
GroupChangeKind2,
|
||||
GroupOptions,
|
||||
GroupPanelViewState,
|
||||
GroupviewDropEvent,
|
||||
@ -73,6 +72,7 @@ export type DockviewComponentUpdateOptions = Pick<
|
||||
| 'showDndOverlay'
|
||||
| 'watermarkFrameworkComponent'
|
||||
| 'defaultTabComponent'
|
||||
| 'createGroupControlElement'
|
||||
>;
|
||||
|
||||
export interface DockviewDropEvent extends GroupviewDropEvent {
|
||||
@ -716,22 +716,14 @@ export class DockviewComponent
|
||||
view.model.onDidDrop((event) => {
|
||||
this._onDidDrop.fire({ ...event, api: this._api, group: view });
|
||||
}),
|
||||
view.model.onDidGroupChange((event) => {
|
||||
switch (event.kind) {
|
||||
case GroupChangeKind2.ADD_PANEL:
|
||||
if (event.panel) {
|
||||
this._onDidAddPanel.fire(event.panel);
|
||||
}
|
||||
break;
|
||||
case GroupChangeKind2.REMOVE_PANEL:
|
||||
if (event.panel) {
|
||||
this._onDidRemovePanel.fire(event.panel);
|
||||
}
|
||||
break;
|
||||
case GroupChangeKind2.PANEL_ACTIVE:
|
||||
this._onDidActivePanelChange.fire(event.panel);
|
||||
break;
|
||||
}
|
||||
view.model.onDidAddPanel((event) => {
|
||||
this._onDidAddPanel.fire(event.panel);
|
||||
}),
|
||||
view.model.onDidRemovePanel((event) => {
|
||||
this._onDidRemovePanel.fire(event.panel);
|
||||
}),
|
||||
view.model.onDidActivePanelChange((event) => {
|
||||
this._onDidActivePanelChange.fire(event.panel);
|
||||
})
|
||||
);
|
||||
|
||||
|
@ -13,6 +13,7 @@ import { ISplitviewStyles, Orientation } from '../splitview/core/splitview';
|
||||
import { FrameworkFactory } from '../types';
|
||||
import { DockviewDropTargets } from '../groupview/dnd';
|
||||
import { PanelTransfer } from '../dnd/dataTransfer';
|
||||
import { IGroupControlRenderer } from '../react/dockview/groupControlsRenderer';
|
||||
|
||||
export interface GroupPanelFrameworkComponentFactory {
|
||||
content: FrameworkFactory<IContentRenderer>;
|
||||
@ -66,6 +67,7 @@ export interface DockviewComponentOptions extends DockviewRenderFunctions {
|
||||
styles?: ISplitviewStyles;
|
||||
defaultTabComponent?: string;
|
||||
showDndOverlay?: (event: DockviewDndOverlayEvent) => boolean;
|
||||
createGroupControlElement?: (group: GroupPanel) => IGroupControlRenderer;
|
||||
}
|
||||
|
||||
export interface PanelOptions {
|
||||
|
@ -14,12 +14,7 @@ import { ITabsContainer, TabsContainer } from './titlebar/tabsContainer';
|
||||
import { IWatermarkRenderer } from './types';
|
||||
import { GroupPanel } from './groupviewPanel';
|
||||
import { DockviewDropTargets } from './dnd';
|
||||
|
||||
export enum GroupChangeKind2 {
|
||||
ADD_PANEL = 'ADD_PANEL',
|
||||
REMOVE_PANEL = 'REMOVE_PANEL',
|
||||
PANEL_ACTIVE = 'PANEL_ACTIVE',
|
||||
}
|
||||
import { IGroupControlRenderer } from '../react/dockview/groupControlsRenderer';
|
||||
|
||||
export interface DndService {
|
||||
canDisplayOverlay(
|
||||
@ -67,15 +62,14 @@ export interface GroupPanelViewState extends CoreGroupOptions {
|
||||
}
|
||||
|
||||
export interface GroupviewChangeEvent {
|
||||
readonly kind: GroupChangeKind2;
|
||||
readonly panel?: IDockviewPanel;
|
||||
readonly panel: IDockviewPanel;
|
||||
}
|
||||
|
||||
export interface GroupviewDropEvent {
|
||||
nativeEvent: DragEvent;
|
||||
position: Position;
|
||||
readonly nativeEvent: DragEvent;
|
||||
readonly position: Position;
|
||||
readonly index?: number;
|
||||
getData(): PanelTransfer | undefined;
|
||||
index?: number;
|
||||
}
|
||||
|
||||
export interface IHeader {
|
||||
@ -91,7 +85,9 @@ export interface IGroupview extends IDisposable, IGridPanelView {
|
||||
readonly header: IHeader;
|
||||
readonly isContentFocused: boolean;
|
||||
readonly onDidDrop: Event<GroupviewDropEvent>;
|
||||
readonly onDidGroupChange: Event<GroupviewChangeEvent>;
|
||||
readonly onDidAddPanel: Event<GroupviewChangeEvent>;
|
||||
readonly onDidRemovePanel: Event<GroupviewChangeEvent>;
|
||||
readonly onDidActivePanelChange: Event<GroupviewChangeEvent>;
|
||||
readonly onMove: Event<GroupMoveEvent>;
|
||||
locked: boolean;
|
||||
// state
|
||||
@ -121,10 +117,11 @@ export class Groupview extends CompositeDisposable implements IGroupview {
|
||||
private readonly tabsContainer: ITabsContainer;
|
||||
private readonly contentContainer: IContentContainer;
|
||||
private readonly dropTarget: Droptarget;
|
||||
private _activePanel?: IDockviewPanel;
|
||||
private _activePanel: IDockviewPanel | undefined;
|
||||
private watermark?: IWatermarkRenderer;
|
||||
private _isGroupActive = false;
|
||||
private _locked = false;
|
||||
private _control: IGroupControlRenderer | undefined;
|
||||
|
||||
private mostRecentlyUsed: IDockviewPanel[] = [];
|
||||
|
||||
@ -140,13 +137,22 @@ export class Groupview extends CompositeDisposable implements IGroupview {
|
||||
private readonly _onMove = new Emitter<GroupMoveEvent>();
|
||||
readonly onMove: Event<GroupMoveEvent> = this._onMove.event;
|
||||
|
||||
private readonly _onDidGroupChange = new Emitter<GroupviewChangeEvent>();
|
||||
readonly onDidGroupChange: Event<GroupviewChangeEvent> =
|
||||
this._onDidGroupChange.event;
|
||||
|
||||
private readonly _onDidDrop = new Emitter<GroupviewDropEvent>();
|
||||
readonly onDidDrop: Event<GroupviewDropEvent> = this._onDidDrop.event;
|
||||
|
||||
private readonly _onDidAddPanel = new Emitter<GroupviewChangeEvent>();
|
||||
readonly onDidAddPanel: Event<GroupviewChangeEvent> =
|
||||
this._onDidAddPanel.event;
|
||||
|
||||
private readonly _onDidRemovePanel = new Emitter<GroupviewChangeEvent>();
|
||||
readonly onDidRemovePanel: Event<GroupviewChangeEvent> =
|
||||
this._onDidRemovePanel.event;
|
||||
|
||||
private readonly _onDidActivePanelChange =
|
||||
new Emitter<GroupviewChangeEvent>();
|
||||
readonly onDidActivePanelChange: Event<GroupviewChangeEvent> =
|
||||
this._onDidActivePanelChange.event;
|
||||
|
||||
get element(): HTMLElement {
|
||||
throw new Error('not supported');
|
||||
}
|
||||
@ -226,16 +232,10 @@ export class Groupview extends CompositeDisposable implements IGroupview {
|
||||
|
||||
this.container.classList.add('groupview');
|
||||
|
||||
this.addDisposables(
|
||||
this._onMove,
|
||||
this._onDidGroupChange,
|
||||
this._onDidChange,
|
||||
this._onDidDrop
|
||||
);
|
||||
|
||||
this.tabsContainer = new TabsContainer(this.accessor, this.parent, {
|
||||
tabHeight: options.tabHeight,
|
||||
});
|
||||
|
||||
this.contentContainer = new ContentContainer();
|
||||
|
||||
this.dropTarget = new Droptarget(this.contentContainer.element, {
|
||||
@ -268,7 +268,11 @@ export class Groupview extends CompositeDisposable implements IGroupview {
|
||||
|
||||
this.addDisposables(
|
||||
this._onMove,
|
||||
this._onDidGroupChange,
|
||||
this._onDidChange,
|
||||
this._onDidDrop,
|
||||
this._onDidAddPanel,
|
||||
this._onDidRemovePanel,
|
||||
this._onDidActivePanelChange,
|
||||
this.tabsContainer.onDrop((event) => {
|
||||
this.handleDropEvent(event.event, Position.Center, event.index);
|
||||
}),
|
||||
@ -284,7 +288,7 @@ export class Groupview extends CompositeDisposable implements IGroupview {
|
||||
);
|
||||
}
|
||||
|
||||
initialize() {
|
||||
initialize(): void {
|
||||
if (this.options?.panels) {
|
||||
this.options.panels.forEach((panel) => {
|
||||
this.doAddPanel(panel);
|
||||
@ -299,9 +303,21 @@ export class Groupview extends CompositeDisposable implements IGroupview {
|
||||
// correctly initialized
|
||||
this.setActive(this.isActive, true, true);
|
||||
this.updateContainer();
|
||||
|
||||
if (this.accessor.options.createGroupControlElement) {
|
||||
this._control = this.accessor.options.createGroupControlElement(
|
||||
this.parent
|
||||
);
|
||||
this.addDisposables(this._control);
|
||||
this._control.init({
|
||||
containerApi: new DockviewApi(this.accessor),
|
||||
api: this.parent.api,
|
||||
});
|
||||
this.tabsContainer.setActionElement(this._control.element);
|
||||
}
|
||||
}
|
||||
|
||||
public indexOf(panel: IDockviewPanel) {
|
||||
public indexOf(panel: IDockviewPanel): number {
|
||||
return this.tabsContainer.indexOf(panel.id);
|
||||
}
|
||||
|
||||
@ -326,7 +342,7 @@ export class Groupview extends CompositeDisposable implements IGroupview {
|
||||
public moveToNext(options?: {
|
||||
panel?: IDockviewPanel;
|
||||
suppressRoll?: boolean;
|
||||
}) {
|
||||
}): void {
|
||||
if (!options) {
|
||||
options = {};
|
||||
}
|
||||
@ -352,7 +368,7 @@ export class Groupview extends CompositeDisposable implements IGroupview {
|
||||
public moveToPrevious(options?: {
|
||||
panel?: IDockviewPanel;
|
||||
suppressRoll?: boolean;
|
||||
}) {
|
||||
}): void {
|
||||
if (!options) {
|
||||
options = {};
|
||||
}
|
||||
@ -379,19 +395,19 @@ export class Groupview extends CompositeDisposable implements IGroupview {
|
||||
this.openPanel(this.panels[normalizedIndex]);
|
||||
}
|
||||
|
||||
public containsPanel(panel: IDockviewPanel) {
|
||||
public containsPanel(panel: IDockviewPanel): boolean {
|
||||
return this.panels.includes(panel);
|
||||
}
|
||||
|
||||
init(_params: PanelInitParameters) {
|
||||
init(_params: PanelInitParameters): void {
|
||||
//noop
|
||||
}
|
||||
|
||||
update(_params: PanelUpdateEvent) {
|
||||
update(_params: PanelUpdateEvent): void {
|
||||
//noop
|
||||
}
|
||||
|
||||
focus() {
|
||||
focus(): void {
|
||||
this._activePanel?.focus();
|
||||
}
|
||||
|
||||
@ -403,7 +419,7 @@ export class Groupview extends CompositeDisposable implements IGroupview {
|
||||
skipSetPanelActive?: boolean;
|
||||
skipSetGroupActive?: boolean;
|
||||
} = {}
|
||||
) {
|
||||
): void {
|
||||
if (
|
||||
typeof options.index !== 'number' ||
|
||||
options.index > this.panels.length
|
||||
@ -452,7 +468,7 @@ export class Groupview extends CompositeDisposable implements IGroupview {
|
||||
return this._removePanel(panelToRemove);
|
||||
}
|
||||
|
||||
public closeAllPanels() {
|
||||
public closeAllPanels(): void {
|
||||
if (this.panels.length > 0) {
|
||||
// take a copy since we will be edting the array as we iterate through
|
||||
const arrPanelCpy = [...this.panels];
|
||||
@ -468,25 +484,23 @@ export class Groupview extends CompositeDisposable implements IGroupview {
|
||||
this.doClose(panel);
|
||||
}
|
||||
|
||||
private doClose(panel: IDockviewPanel) {
|
||||
private doClose(panel: IDockviewPanel): void {
|
||||
this.accessor.removePanel(panel);
|
||||
}
|
||||
|
||||
public isPanelActive(panel: IDockviewPanel) {
|
||||
public isPanelActive(panel: IDockviewPanel): boolean {
|
||||
return this._activePanel === panel;
|
||||
}
|
||||
|
||||
updateActions() {
|
||||
if (this.isActive && this._activePanel?.view?.actions) {
|
||||
this.tabsContainer.setActionElement(
|
||||
this._activePanel.view.actions.element
|
||||
);
|
||||
} else {
|
||||
this.tabsContainer.setActionElement(undefined);
|
||||
}
|
||||
updateActions(element: HTMLElement | undefined): void {
|
||||
this.tabsContainer.setActionElement(element);
|
||||
}
|
||||
|
||||
public setActive(isGroupActive: boolean, skipFocus = false, force = false) {
|
||||
public setActive(
|
||||
isGroupActive: boolean,
|
||||
skipFocus = false,
|
||||
force = false
|
||||
): void {
|
||||
if (!force && this.isActive === isGroupActive) {
|
||||
if (!skipFocus) {
|
||||
this._activePanel?.focus();
|
||||
@ -514,7 +528,7 @@ export class Groupview extends CompositeDisposable implements IGroupview {
|
||||
}
|
||||
}
|
||||
|
||||
public layout(width: number, height: number) {
|
||||
public layout(width: number, height: number): void {
|
||||
this._width = width;
|
||||
this._height = height;
|
||||
|
||||
@ -525,7 +539,7 @@ export class Groupview extends CompositeDisposable implements IGroupview {
|
||||
}
|
||||
}
|
||||
|
||||
private _removePanel(panel: IDockviewPanel) {
|
||||
private _removePanel(panel: IDockviewPanel): IDockviewPanel {
|
||||
const isActivePanel = this._activePanel === panel;
|
||||
|
||||
this.doRemovePanel(panel);
|
||||
@ -543,7 +557,7 @@ export class Groupview extends CompositeDisposable implements IGroupview {
|
||||
return panel;
|
||||
}
|
||||
|
||||
private doRemovePanel(panel: IDockviewPanel) {
|
||||
private doRemovePanel(panel: IDockviewPanel): void {
|
||||
const index = this.panels.indexOf(panel);
|
||||
|
||||
if (this._activePanel === panel) {
|
||||
@ -560,17 +574,14 @@ export class Groupview extends CompositeDisposable implements IGroupview {
|
||||
);
|
||||
}
|
||||
|
||||
this._onDidGroupChange.fire({
|
||||
kind: GroupChangeKind2.REMOVE_PANEL,
|
||||
panel,
|
||||
});
|
||||
this._onDidRemovePanel.fire({ panel });
|
||||
}
|
||||
|
||||
private doAddPanel(
|
||||
panel: IDockviewPanel,
|
||||
index: number = this.panels.length,
|
||||
skipSetActive = false
|
||||
) {
|
||||
): void {
|
||||
const existingPanel = this._panels.indexOf(panel);
|
||||
const hasExistingPanel = existingPanel > -1;
|
||||
|
||||
@ -591,13 +602,10 @@ export class Groupview extends CompositeDisposable implements IGroupview {
|
||||
this.updateMru(panel);
|
||||
this.panels.splice(index, 0, panel);
|
||||
|
||||
this._onDidGroupChange.fire({
|
||||
kind: GroupChangeKind2.ADD_PANEL,
|
||||
panel,
|
||||
});
|
||||
this._onDidAddPanel.fire({ panel });
|
||||
}
|
||||
|
||||
private doSetActivePanel(panel: IDockviewPanel | undefined) {
|
||||
private doSetActivePanel(panel: IDockviewPanel | undefined): void {
|
||||
this._activePanel = panel;
|
||||
|
||||
if (panel) {
|
||||
@ -607,14 +615,11 @@ export class Groupview extends CompositeDisposable implements IGroupview {
|
||||
|
||||
this.updateMru(panel);
|
||||
|
||||
this._onDidGroupChange.fire({
|
||||
kind: GroupChangeKind2.PANEL_ACTIVE,
|
||||
panel,
|
||||
});
|
||||
this._onDidActivePanelChange.fire({ panel });
|
||||
}
|
||||
}
|
||||
|
||||
private updateMru(panel: IDockviewPanel) {
|
||||
private updateMru(panel: IDockviewPanel): void {
|
||||
if (this.mostRecentlyUsed.includes(panel)) {
|
||||
this.mostRecentlyUsed.splice(
|
||||
this.mostRecentlyUsed.indexOf(panel),
|
||||
@ -624,8 +629,7 @@ export class Groupview extends CompositeDisposable implements IGroupview {
|
||||
this.mostRecentlyUsed = [panel, ...this.mostRecentlyUsed];
|
||||
}
|
||||
|
||||
private updateContainer() {
|
||||
this.updateActions();
|
||||
private updateContainer(): void {
|
||||
toggleClass(this.container, 'empty', this.isEmpty);
|
||||
|
||||
this.panels.forEach((panel) =>
|
||||
@ -680,7 +684,7 @@ export class Groupview extends CompositeDisposable implements IGroupview {
|
||||
event: DragEvent,
|
||||
position: Position,
|
||||
index?: number
|
||||
) {
|
||||
): void {
|
||||
const data = getPanelData();
|
||||
|
||||
if (data) {
|
||||
@ -716,7 +720,7 @@ export class Groupview extends CompositeDisposable implements IGroupview {
|
||||
}
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
public dispose(): void {
|
||||
super.dispose();
|
||||
|
||||
this.watermark?.dispose();
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { IDisposable } from '../lifecycle';
|
||||
import { IDockviewComponent } from '../dockview/dockviewComponent';
|
||||
import { DockviewPanelApi } from '../api/groupPanelApi';
|
||||
import { PanelInitParameters, IPanel } from '../panel/types';
|
||||
@ -42,10 +41,6 @@ export interface ITabRenderer extends IPanel {
|
||||
updateParentGroup(group: GroupPanel, isPanelVisible: boolean): void;
|
||||
}
|
||||
|
||||
export interface IActionsRenderer extends IDisposable {
|
||||
readonly element: HTMLElement;
|
||||
}
|
||||
|
||||
export interface IContentRenderer extends IPanel {
|
||||
readonly element: HTMLElement;
|
||||
readonly actions?: HTMLElement;
|
||||
|
@ -12,15 +12,36 @@ import {
|
||||
TabContextMenuEvent,
|
||||
} from '../../dockview/options';
|
||||
import { DockviewPanelApi } from '../../api/groupPanelApi';
|
||||
import { usePortalsLifecycle } from '../react';
|
||||
import { ReactPortalStore, usePortalsLifecycle } from '../react';
|
||||
import { DockviewApi } from '../../api/component.api';
|
||||
import { IWatermarkPanelProps, ReactWatermarkPart } from './reactWatermarkPart';
|
||||
import { PanelCollection, PanelParameters } from '../types';
|
||||
import { watchElementResize } from '../../dom';
|
||||
import { IContentRenderer, ITabRenderer } from '../../groupview/types';
|
||||
import {
|
||||
IDockviewGroupControlProps,
|
||||
IGroupControlRenderer,
|
||||
ReactGroupControlsRendererPart,
|
||||
} from './groupControlsRenderer';
|
||||
import { GroupPanel } from '../../groupview/groupviewPanel';
|
||||
|
||||
export const DEFAULT_TAB_IDENTIFIER = '__default__tab__';
|
||||
|
||||
function createGroupControlElement(
|
||||
component: React.FunctionComponent<IDockviewGroupControlProps> | undefined,
|
||||
store: ReactPortalStore
|
||||
): ((groupPanel: GroupPanel) => IGroupControlRenderer) | undefined {
|
||||
return component
|
||||
? (groupPanel: GroupPanel) => {
|
||||
return new ReactGroupControlsRendererPart(
|
||||
component,
|
||||
store,
|
||||
groupPanel
|
||||
);
|
||||
}
|
||||
: undefined;
|
||||
}
|
||||
|
||||
export interface IGroupPanelBaseProps<T extends {} = Record<string, any>>
|
||||
extends PanelParameters<T> {
|
||||
api: DockviewPanelApi;
|
||||
@ -50,6 +71,7 @@ export interface IDockviewReactProps {
|
||||
className?: string;
|
||||
disableAutoResizing?: boolean;
|
||||
defaultTabComponent?: React.FunctionComponent<IDockviewPanelHeaderProps>;
|
||||
groupControlComponent?: React.FunctionComponent<IDockviewGroupControlProps>;
|
||||
}
|
||||
|
||||
export const DockviewReact = React.forwardRef(
|
||||
@ -138,6 +160,10 @@ export const DockviewReact = React.forwardRef(
|
||||
? { separatorBorder: 'transparent' }
|
||||
: undefined,
|
||||
showDndOverlay: props.showDndOverlay,
|
||||
createGroupControlElement: createGroupControlElement(
|
||||
props.groupControlComponent,
|
||||
{ addPortal }
|
||||
),
|
||||
});
|
||||
|
||||
domRef.current?.appendChild(dockview.element);
|
||||
@ -243,6 +269,18 @@ export const DockviewReact = React.forwardRef(
|
||||
});
|
||||
}, [props.defaultTabComponent]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!dockviewRef.current) {
|
||||
return;
|
||||
}
|
||||
dockviewRef.current.updateOptions({
|
||||
createGroupControlElement: createGroupControlElement(
|
||||
props.groupControlComponent,
|
||||
{ addPortal }
|
||||
),
|
||||
});
|
||||
}, [props.groupControlComponent]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={props.className}
|
||||
|
117
packages/dockview/src/react/dockview/groupControlsRenderer.ts
Normal file
117
packages/dockview/src/react/dockview/groupControlsRenderer.ts
Normal file
@ -0,0 +1,117 @@
|
||||
import * as React from 'react';
|
||||
import { ReactPart, ReactPortalStore } from '../react';
|
||||
import { PanelUpdateEvent } from '../../panel/types';
|
||||
import { GroupPanel, GroupviewPanelApi } from '../../groupview/groupviewPanel';
|
||||
import { DockviewApi } from '../../api/component.api';
|
||||
import {
|
||||
CompositeDisposable,
|
||||
IDisposable,
|
||||
MutableDisposable,
|
||||
} from '../../lifecycle';
|
||||
import { IDockviewPanel } from '../../groupview/groupPanel';
|
||||
|
||||
export interface IDockviewGroupControlProps {
|
||||
api: GroupviewPanelApi;
|
||||
containerApi: DockviewApi;
|
||||
panels: IDockviewPanel[];
|
||||
activePanel: IDockviewPanel | undefined;
|
||||
isGroupActive: boolean;
|
||||
}
|
||||
|
||||
export interface IGroupControlRenderer extends IDisposable {
|
||||
readonly element: HTMLElement;
|
||||
init(params: { containerApi: DockviewApi; api: GroupviewPanelApi }): void;
|
||||
}
|
||||
|
||||
export class ReactGroupControlsRendererPart {
|
||||
private mutableDisposable = new MutableDisposable();
|
||||
private _element: HTMLElement;
|
||||
private _part?: ReactPart<IDockviewGroupControlProps>;
|
||||
|
||||
get element(): HTMLElement {
|
||||
return this._element;
|
||||
}
|
||||
|
||||
get part(): ReactPart<IDockviewGroupControlProps> | undefined {
|
||||
return this._part;
|
||||
}
|
||||
|
||||
get group(): GroupPanel {
|
||||
return this._group;
|
||||
}
|
||||
|
||||
constructor(
|
||||
private readonly component: React.FunctionComponent<IDockviewGroupControlProps>,
|
||||
private readonly reactPortalStore: ReactPortalStore,
|
||||
private readonly _group: GroupPanel
|
||||
) {
|
||||
this._element = document.createElement('div');
|
||||
this._element.className = 'dockview-react-part';
|
||||
}
|
||||
|
||||
focus() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
public init(parameters: {
|
||||
containerApi: DockviewApi;
|
||||
api: GroupviewPanelApi;
|
||||
}): void {
|
||||
this.mutableDisposable.value = new CompositeDisposable(
|
||||
this._group.model.onDidAddPanel(() => {
|
||||
this.updatePanels();
|
||||
}),
|
||||
this._group.model.onDidRemovePanel(() => {
|
||||
this.updatePanels();
|
||||
}),
|
||||
this._group.model.onDidActivePanelChange(() => {
|
||||
this.updateActivePanel();
|
||||
}),
|
||||
parameters.api.onDidActiveChange(() => {
|
||||
this.updateGroupActive();
|
||||
})
|
||||
);
|
||||
|
||||
this._part = new ReactPart(
|
||||
this.element,
|
||||
this.reactPortalStore,
|
||||
this.component,
|
||||
{
|
||||
api: parameters.api,
|
||||
containerApi: parameters.containerApi,
|
||||
panels: this._group.model.panels,
|
||||
activePanel: this._group.model.activePanel,
|
||||
isGroupActive: this._group.api.isActive,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public update(event: PanelUpdateEvent) {
|
||||
this._part?.update(event.params);
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
this.mutableDisposable.dispose();
|
||||
this._part?.dispose();
|
||||
}
|
||||
|
||||
private updatePanels() {
|
||||
this.update({ params: { panels: this._group.model.panels } });
|
||||
}
|
||||
|
||||
private updateActivePanel() {
|
||||
this.update({
|
||||
params: {
|
||||
activePanel: this._group.model.activePanel,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private updateGroupActive() {
|
||||
this.update({
|
||||
params: {
|
||||
isGroupActive: this._group.api.isActive,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
@ -2,34 +2,17 @@ import * as React from 'react';
|
||||
import {
|
||||
IContentRenderer,
|
||||
GroupPanelContentPartInitParameters,
|
||||
ITabRenderer,
|
||||
} from '../../groupview/types';
|
||||
import { ReactPart, ReactPortalStore } from '../react';
|
||||
import { IDockviewPanelProps } from '../dockview/dockview';
|
||||
import { PanelUpdateEvent } from '../../panel/types';
|
||||
import { DockviewPanelApi } from '../../api/groupPanelApi';
|
||||
import { DockviewApi } from '../../api/component.api';
|
||||
import { GroupPanel } from '../../groupview/groupviewPanel';
|
||||
import { Emitter, Event } from '../../events';
|
||||
|
||||
export interface IGroupPanelActionbarProps {
|
||||
api: DockviewPanelApi;
|
||||
containerApi: DockviewApi;
|
||||
}
|
||||
|
||||
export interface ReactContentPartContext {
|
||||
api: DockviewPanelApi;
|
||||
containerApi: DockviewApi;
|
||||
actionsPortalElement: HTMLElement;
|
||||
tabPortalElement: ITabRenderer;
|
||||
}
|
||||
|
||||
export class ReactPanelContentPart implements IContentRenderer {
|
||||
private _element: HTMLElement;
|
||||
private part?: ReactPart<IDockviewPanelProps>;
|
||||
//
|
||||
private _actionsElement: HTMLElement;
|
||||
private actionsPart?: ReactPart<any>;
|
||||
private _group: GroupPanel | undefined;
|
||||
|
||||
private readonly _onDidFocus = new Emitter<void>();
|
||||
@ -42,10 +25,6 @@ export class ReactPanelContentPart implements IContentRenderer {
|
||||
return this._element;
|
||||
}
|
||||
|
||||
get actions(): HTMLElement {
|
||||
return this._actionsElement;
|
||||
}
|
||||
|
||||
constructor(
|
||||
public readonly id: string,
|
||||
private readonly component: React.FunctionComponent<IDockviewPanelProps>,
|
||||
@ -53,9 +32,6 @@ export class ReactPanelContentPart implements IContentRenderer {
|
||||
) {
|
||||
this._element = document.createElement('div');
|
||||
this._element.className = 'dockview-react-part';
|
||||
|
||||
this._actionsElement = document.createElement('div');
|
||||
this._actionsElement.className = 'dockview-react-part';
|
||||
}
|
||||
|
||||
focus() {
|
||||
@ -63,13 +39,6 @@ export class ReactPanelContentPart implements IContentRenderer {
|
||||
}
|
||||
|
||||
public init(parameters: GroupPanelContentPartInitParameters): void {
|
||||
const context: ReactContentPartContext = {
|
||||
api: parameters.api,
|
||||
containerApi: parameters.containerApi,
|
||||
actionsPortalElement: this._actionsElement,
|
||||
tabPortalElement: parameters.tab,
|
||||
};
|
||||
|
||||
this.part = new ReactPart(
|
||||
this.element,
|
||||
this.reactPortalStore,
|
||||
@ -78,8 +47,7 @@ export class ReactPanelContentPart implements IContentRenderer {
|
||||
params: parameters.params,
|
||||
api: parameters.api,
|
||||
containerApi: parameters.containerApi,
|
||||
},
|
||||
context
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@ -108,6 +76,5 @@ export class ReactPanelContentPart implements IContentRenderer {
|
||||
this._onDidFocus.dispose();
|
||||
this._onDidBlur.dispose();
|
||||
this.part?.dispose();
|
||||
this.actionsPart?.dispose();
|
||||
}
|
||||
}
|
||||
|
@ -2,8 +2,7 @@ export * from './dockview/dockview';
|
||||
export * from './dockview/defaultTab';
|
||||
export * from './splitview/splitview';
|
||||
export * from './gridview/gridview';
|
||||
export * from './dockview/reactContentPart';
|
||||
export * from './dockview/reactHeaderPart';
|
||||
export { IDockviewGroupControlProps } from './dockview/groupControlsRenderer';
|
||||
export { IWatermarkPanelProps } from './dockview/reactWatermarkPart';
|
||||
export * from './paneview/paneview';
|
||||
export * from './types';
|
||||
|
@ -65,6 +65,7 @@ export const ReactPartContext = React.createContext<{}>({});
|
||||
export class ReactPart<P extends object, C extends object = {}>
|
||||
implements IFrameworkPart
|
||||
{
|
||||
private _initialProps: Record<string, any> = {};
|
||||
private componentInstance?: IPanelWrapperRef;
|
||||
private ref?: { portal: React.ReactPortal; disposable: IDisposable };
|
||||
private disposed = false;
|
||||
@ -84,7 +85,12 @@ export class ReactPart<P extends object, C extends object = {}>
|
||||
throw new Error('invalid operation: resource is already disposed');
|
||||
}
|
||||
|
||||
this.componentInstance?.update(props);
|
||||
if (!this.componentInstance) {
|
||||
// if the component is yet to be mounted store the props
|
||||
this._initialProps = { ...this._initialProps, ...props };
|
||||
} else {
|
||||
this.componentInstance.update(props);
|
||||
}
|
||||
}
|
||||
|
||||
private createPortal() {
|
||||
@ -111,6 +117,11 @@ export class ReactPart<P extends object, C extends object = {}>
|
||||
componentProps: this.parameters as unknown as {},
|
||||
ref: (element: IPanelWrapperRef) => {
|
||||
this.componentInstance = element;
|
||||
|
||||
if (Object.keys(this._initialProps).length > 0) {
|
||||
this.componentInstance.update(this._initialProps);
|
||||
this._initialProps = {}; // don't keep a reference to the users object once no longer required
|
||||
}
|
||||
},
|
||||
}
|
||||
);
|
||||
|
Loading…
Reference in New Issue
Block a user