Skip to content

Commit

Permalink
feat: prefix header actions
Browse files Browse the repository at this point in the history
  • Loading branch information
mathuo committed Sep 30, 2023
1 parent 4ad5b0f commit fa4d1e9
Show file tree
Hide file tree
Showing 6 changed files with 269 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -638,4 +638,214 @@ describe('tabsContainer', () => {
expect(preventDefaultSpy).toBeCalledTimes(1);
expect(accessor.addFloatingGroup).toBeCalledTimes(1);
});

test('pre header actions', () => {
const accessorMock = jest.fn<DockviewComponent, []>(() => {
return (<Partial<DockviewComponent>>{
options: {},
onDidAddPanel: jest.fn(),
onDidRemovePanel: jest.fn(),
element: document.createElement('div'),
addFloatingGroup: jest.fn(),
getGroupPanel: jest.fn(),
}) as DockviewComponent;
});

const groupPanelMock = jest.fn<DockviewGroupPanel, []>(() => {
return (<Partial<DockviewGroupPanel>>{
api: { isFloating: true } as any,
model: {} as any,
}) as DockviewGroupPanel;
});

const accessor = new accessorMock();
const groupPanel = new groupPanelMock();

const cut = new TabsContainer(accessor, groupPanel);

const panelMock = jest.fn<IDockviewPanel, [string]>((id: string) => {
const partial: Partial<IDockviewPanel> = {
id,

view: {
tab: {
element: document.createElement('div'),
} as any,
content: {
element: document.createElement('div'),
} as any,
} as any,
};
return partial as IDockviewPanel;
});

const panel = new panelMock('test_id');
cut.openPanel(panel);

let result = cut.element.querySelector('.pre-actions-container');
expect(result).toBeTruthy();
expect(result!.childNodes.length).toBe(0);

const actions = document.createElement('div');
cut.setPrefixActionsElement(actions);

result = cut.element.querySelector('.pre-actions-container');
expect(result).toBeTruthy();
expect(result!.childNodes.length).toBe(1);
expect(result!.childNodes.item(0)).toBe(actions);

const updatedActions = document.createElement('div');
cut.setPrefixActionsElement(updatedActions);

result = cut.element.querySelector('.pre-actions-container');
expect(result).toBeTruthy();
expect(result!.childNodes.length).toBe(1);
expect(result!.childNodes.item(0)).toBe(updatedActions);

cut.setPrefixActionsElement(undefined);

result = cut.element.querySelector('.pre-actions-container');
expect(result).toBeTruthy();
expect(result!.childNodes.length).toBe(0);
});

test('left header actions', () => {
const accessorMock = jest.fn<DockviewComponent, []>(() => {
return (<Partial<DockviewComponent>>{
options: {},
onDidAddPanel: jest.fn(),
onDidRemovePanel: jest.fn(),
element: document.createElement('div'),
addFloatingGroup: jest.fn(),
getGroupPanel: jest.fn(),
}) as DockviewComponent;
});

const groupPanelMock = jest.fn<DockviewGroupPanel, []>(() => {
return (<Partial<DockviewGroupPanel>>{
api: { isFloating: true } as any,
model: {} as any,
}) as DockviewGroupPanel;
});

const accessor = new accessorMock();
const groupPanel = new groupPanelMock();

const cut = new TabsContainer(accessor, groupPanel);

const panelMock = jest.fn<IDockviewPanel, [string]>((id: string) => {
const partial: Partial<IDockviewPanel> = {
id,

view: {
tab: {
element: document.createElement('div'),
} as any,
content: {
element: document.createElement('div'),
} as any,
} as any,
};
return partial as IDockviewPanel;
});

const panel = new panelMock('test_id');
cut.openPanel(panel);

let result = cut.element.querySelector('.left-actions-container');
expect(result).toBeTruthy();
expect(result!.childNodes.length).toBe(0);

const actions = document.createElement('div');
cut.setLeftActionsElement(actions);

result = cut.element.querySelector('.left-actions-container');
expect(result).toBeTruthy();
expect(result!.childNodes.length).toBe(1);
expect(result!.childNodes.item(0)).toBe(actions);

const updatedActions = document.createElement('div');
cut.setLeftActionsElement(updatedActions);

result = cut.element.querySelector('.left-actions-container');
expect(result).toBeTruthy();
expect(result!.childNodes.length).toBe(1);
expect(result!.childNodes.item(0)).toBe(updatedActions);

cut.setLeftActionsElement(undefined);

result = cut.element.querySelector('.left-actions-container');
expect(result).toBeTruthy();
expect(result!.childNodes.length).toBe(0);
});

test('right header actions', () => {
const accessorMock = jest.fn<DockviewComponent, []>(() => {
return (<Partial<DockviewComponent>>{
options: {},
onDidAddPanel: jest.fn(),
onDidRemovePanel: jest.fn(),
element: document.createElement('div'),
addFloatingGroup: jest.fn(),
getGroupPanel: jest.fn(),
}) as DockviewComponent;
});

const groupPanelMock = jest.fn<DockviewGroupPanel, []>(() => {
return (<Partial<DockviewGroupPanel>>{
api: { isFloating: true } as any,
model: {} as any,
}) as DockviewGroupPanel;
});

const accessor = new accessorMock();
const groupPanel = new groupPanelMock();

const cut = new TabsContainer(accessor, groupPanel);

const panelMock = jest.fn<IDockviewPanel, [string]>((id: string) => {
const partial: Partial<IDockviewPanel> = {
id,

view: {
tab: {
element: document.createElement('div'),
} as any,
content: {
element: document.createElement('div'),
} as any,
} as any,
};
return partial as IDockviewPanel;
});

const panel = new panelMock('test_id');
cut.openPanel(panel);

let result = cut.element.querySelector('.right-actions-container');
expect(result).toBeTruthy();
expect(result!.childNodes.length).toBe(0);

const actions = document.createElement('div');
cut.setRightActionsElement(actions);

result = cut.element.querySelector('.right-actions-container');
expect(result).toBeTruthy();
expect(result!.childNodes.length).toBe(1);
expect(result!.childNodes.item(0)).toBe(actions);

const updatedActions = document.createElement('div');
cut.setRightActionsElement(updatedActions);

result = cut.element.querySelector('.right-actions-container');
expect(result).toBeTruthy();
expect(result!.childNodes.length).toBe(1);
expect(result!.childNodes.item(0)).toBe(updatedActions);

cut.setRightActionsElement(undefined);

result = cut.element.querySelector('.right-actions-container');
expect(result).toBeTruthy();
expect(result!.childNodes.length).toBe(0);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export interface ITabsContainer extends IDisposable {
openPanel: (panel: IDockviewPanel, index?: number) => void;
setRightActionsElement(element: HTMLElement | undefined): void;
setLeftActionsElement(element: HTMLElement | undefined): void;
setPrefixActionsElement(element: HTMLElement | undefined): void;
show(): void;
hide(): void;
}
Expand All @@ -55,12 +56,14 @@ export class TabsContainer
private readonly tabContainer: HTMLElement;
private readonly rightActionsContainer: HTMLElement;
private readonly leftActionsContainer: HTMLElement;
private readonly preActionsContainer: HTMLElement;
private readonly voidContainer: VoidContainer;

private tabs: IValueDisposable<ITab>[] = [];
private selectedIndex = -1;
private rightActions: HTMLElement | undefined;
private leftActions: HTMLElement | undefined;
private preActions: HTMLElement | undefined;

private _hidden = false;

Expand Down Expand Up @@ -129,6 +132,20 @@ export class TabsContainer
}
}

setPrefixActionsElement(element: HTMLElement | undefined): void {
if (this.preActions === element) {
return;
}
if (this.preActions) {
this.preActions.remove();
this.preActions = undefined;
}
if (element) {
this.preActionsContainer.appendChild(element);
this.preActions = element;
}
}

get element(): HTMLElement {
return this._element;
}
Expand Down Expand Up @@ -192,11 +209,15 @@ export class TabsContainer
this.leftActionsContainer = document.createElement('div');
this.leftActionsContainer.className = 'left-actions-container';

this.preActionsContainer = document.createElement('div');
this.preActionsContainer.className = 'pre-actions-container';

this.tabContainer = document.createElement('div');
this.tabContainer.className = 'tabs-container';

this.voidContainer = new VoidContainer(this.accessor, this.group);

this._element.appendChild(this.preActionsContainer);
this._element.appendChild(this.tabContainer);
this._element.appendChild(this.leftActionsContainer);
this._element.appendChild(this.voidContainer.element);
Expand Down
1 change: 1 addition & 0 deletions packages/dockview-core/src/dockview/dockviewComponent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ export type DockviewComponentUpdateOptions = Pick<
| 'defaultTabComponent'
| 'createLeftHeaderActionsElement'
| 'createRightHeaderActionsElement'
| 'createPrefixHeaderActionsElement'
| 'disableFloatingGroups'
| 'floatingGroupBounds'
>;
Expand Down
18 changes: 17 additions & 1 deletion packages/dockview-core/src/dockview/dockviewGroupPanelModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
IContentContainer,
} from './components/panel/content';
import {
GroupDragEvent,
GroupDragEvent,
ITabsContainer,
TabDragEvent,
TabsContainer,
Expand Down Expand Up @@ -144,6 +144,7 @@ export class DockviewGroupPanelModel
private _isFloating = false;
private _rightHeaderActions: IHeaderActionsRenderer | undefined;
private _leftHeaderActions: IHeaderActionsRenderer | undefined;
private _prefixHeaderActions: IHeaderActionsRenderer | undefined;

private mostRecentlyUsed: IDockviewPanel[] = [];

Expand Down Expand Up @@ -398,6 +399,21 @@ export class DockviewGroupPanelModel
this._leftHeaderActions.element
);
}

if (this.accessor.options.createPrefixHeaderActionsElement) {
this._prefixHeaderActions =
this.accessor.options.createPrefixHeaderActionsElement(
this.groupPanel
);
this.addDisposables(this._prefixHeaderActions);
this._prefixHeaderActions.init({
containerApi: new DockviewApi(this.accessor),
api: this.groupPanel.api,
});
this.tabsContainer.setPrefixActionsElement(
this._prefixHeaderActions.element
);
}
}

public indexOf(panel: IDockviewPanel): number {
Expand Down
3 changes: 3 additions & 0 deletions packages/dockview-core/src/dockview/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,9 @@ export interface DockviewComponentOptions extends DockviewRenderFunctions {
createLeftHeaderActionsElement?: (
group: DockviewGroupPanel
) => IHeaderActionsRenderer;
createPrefixHeaderActionsElement?: (
group: DockviewGroupPanel
) => IHeaderActionsRenderer;
singleTabMode?: 'fullwidth' | 'default';
parentElement?: HTMLElement;
disableFloatingGroups?: boolean;
Expand Down
17 changes: 17 additions & 0 deletions packages/dockview/src/dockview/dockview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ export interface IDockviewReactProps {
defaultTabComponent?: React.FunctionComponent<IDockviewPanelHeaderProps>;
rightHeaderActionsComponent?: React.FunctionComponent<IDockviewHeaderActionsProps>;
leftHeaderActionsComponent?: React.FunctionComponent<IDockviewHeaderActionsProps>;
prefixHeaderActionsComponent?: React.FunctionComponent<IDockviewHeaderActionsProps>;
singleTabMode?: 'fullwidth' | 'default';
disableFloatingGroups?: boolean;
floatingGroupBounds?:
Expand Down Expand Up @@ -166,6 +167,10 @@ export const DockviewReact = React.forwardRef(
props.rightHeaderActionsComponent,
{ addPortal }
),
createPrefixHeaderActionsElement: createGroupControlElement(
props.prefixHeaderActionsComponent,
{ addPortal }
),
singleTabMode: props.singleTabMode,
disableFloatingGroups: props.disableFloatingGroups,
floatingGroupBounds: props.floatingGroupBounds,
Expand Down Expand Up @@ -301,6 +306,18 @@ export const DockviewReact = React.forwardRef(
});
}, [props.leftHeaderActionsComponent]);

React.useEffect(() => {
if (!dockviewRef.current) {
return;
}
dockviewRef.current.updateOptions({
createPrefixHeaderActionsElement: createGroupControlElement(
props.prefixHeaderActionsComponent,
{ addPortal }
),
});
}, [props.prefixHeaderActionsComponent]);

return (
<div
className={props.className}
Expand Down

0 comments on commit fa4d1e9

Please sign in to comment.