diff --git a/packages/splitview/src/api/panePanelApi.ts b/packages/splitview/src/api/panePanelApi.ts new file mode 100644 index 000000000..bb67c39e5 --- /dev/null +++ b/packages/splitview/src/api/panePanelApi.ts @@ -0,0 +1,22 @@ +import { Emitter, Event } from '../events'; +import { IPanelApi, PanelApi } from './panelApi'; + +interface ExpansionEvent { + isExpanded: boolean; +} + +export interface IPanePanelApi extends IPanelApi { + onDidExpansionChange: Event; +} + +export class PanePanelApi extends PanelApi implements IPanePanelApi { + readonly _onDidExpansionChange = new Emitter({ + emitLastValue: true, + }); + readonly onDidExpansionChange: Event = this + ._onDidExpansionChange.event; + + constructor() { + super(); + } +} diff --git a/packages/splitview/src/paneview/componentPaneView.ts b/packages/splitview/src/paneview/componentPaneView.ts index 3d12b8ca9..9726528e4 100644 --- a/packages/splitview/src/paneview/componentPaneView.ts +++ b/packages/splitview/src/paneview/componentPaneView.ts @@ -76,6 +76,17 @@ export class ComponentPaneView implements IComponentPaneView { this.paneview.layout(size, orthogonalSize); } + /** + * Resize the layout to fit the parent container + */ + public resizeToFit(): void { + const { + width, + height, + } = this.element.parentElement.getBoundingClientRect(); + this.layout(width, height); + } + public dispose() { this.paneview.dispose(); } diff --git a/packages/splitview/src/paneview/paneview.ts b/packages/splitview/src/paneview/paneview.ts index 49ae38efb..e946f8def 100644 --- a/packages/splitview/src/paneview/paneview.ts +++ b/packages/splitview/src/paneview/paneview.ts @@ -1,5 +1,5 @@ import { SplitView, IView, Orientation } from '../splitview/splitview'; -import { IDisposable } from '../lifecycle'; +import { CompositeDisposable, IDisposable } from '../lifecycle'; import { Emitter, Event } from '../events'; import { addClasses, removeClasses } from '../dom'; @@ -13,7 +13,7 @@ export interface IPaneview extends IView { onDidChangeExpansionState: Event; } -export abstract class Pane implements IPaneview { +export abstract class Pane extends CompositeDisposable implements IPaneview { private _element: HTMLElement; private _minimumBodySize: number; private _maximumBodySize: number; @@ -81,6 +81,10 @@ export abstract class Pane implements IPaneview { } constructor(options: IPaneOptions) { + super(); + + this.addDisposables(this._onDidChange, this._onDidChangeExpansionState); + this._element = document.createElement('div'); this._element.className = 'pane'; diff --git a/packages/splitview/src/react/dockview/dockview.tsx b/packages/splitview/src/react/dockview/dockview.tsx index 36842380d..c95528756 100644 --- a/packages/splitview/src/react/dockview/dockview.tsx +++ b/packages/splitview/src/react/dockview/dockview.tsx @@ -9,6 +9,7 @@ import { TabContextMenuEvent, } from '../../dockview/options'; import { IGroupPanelApi } from '../../api/groupPanelApi'; +import { usePortalsLifecycle } from '../react'; export interface IGroupPanelProps { api: IGroupPanelApi; @@ -51,23 +52,12 @@ export interface IDockviewComponentProps { export const DockviewComponent: React.FunctionComponent = ( props: IDockviewComponentProps ) => { - const domReference = React.useRef(); - const layoutReference = React.useRef(); + const domRef = React.useRef(); + const dockviewRef = React.useRef(); - const [portals, setPortals] = React.useState([]); + const [portals, addPortal] = usePortalsLifecycle(); React.useEffect(() => { - const addPortal = (p: React.ReactPortal) => { - setPortals((portals) => [...portals, p]); - return { - dispose: () => { - setPortals((portals) => - portals.filter((portal) => portal !== p) - ); - }, - }; - }; - const factory: GroupPanelFrameworkComponentFactory = { content: { createComponent: ( @@ -95,7 +85,7 @@ export const DockviewComponent: React.FunctionComponent const element = document.createElement('div'); - const layout = new ComponentDockview(element, { + const dockview = new ComponentDockview(element, { frameworkComponentFactory: factory, frameworkComponents: props.components, frameworkTabComponents: props.tabComponents, @@ -105,28 +95,28 @@ export const DockviewComponent: React.FunctionComponent // orientation: props.orientation, }); - layoutReference.current = layout; - domReference.current.appendChild(layoutReference.current.element); - - layout.deserializer = new ReactPanelDeserialzier(layout); - - layout.resizeToFit(); + domRef.current.appendChild(dockview.element); + dockview.deserializer = new ReactPanelDeserialzier(dockview); if (props.serializedLayout) { - layout.deserialize(props.serializedLayout); + dockview.deserialize(props.serializedLayout); } + dockview.resizeToFit(); + if (props.onReady) { - props.onReady({ api: layout }); + props.onReady({ api: dockview }); } + dockviewRef.current = dockview; + return () => { - layout.dispose(); + dockview.dispose(); }; }, []); React.useEffect(() => { - const disposable = layoutReference.current.onTabContextMenu((event) => { + const disposable = dockviewRef.current.onTabContextMenu((event) => { props.onTabContextMenu(event); }); @@ -136,9 +126,7 @@ export const DockviewComponent: React.FunctionComponent }, [props.onTabContextMenu]); React.useEffect(() => { - layoutReference.current.setAutoResizeToFit( - props.autoSizeToFitContainer - ); + dockviewRef.current.setAutoResizeToFit(props.autoSizeToFitContainer); }, [props.autoSizeToFitContainer]); return ( @@ -147,7 +135,7 @@ export const DockviewComponent: React.FunctionComponent // height: '100%', width: '100%', }} - ref={domReference} + ref={domRef} > {portals} diff --git a/packages/splitview/src/react/dockview/reactContentPart.ts b/packages/splitview/src/react/dockview/reactContentPart.ts index fdf17eb60..107e6e8ae 100644 --- a/packages/splitview/src/react/dockview/reactContentPart.ts +++ b/packages/splitview/src/react/dockview/reactContentPart.ts @@ -54,9 +54,6 @@ export class ReactPanelContentPart implements PanelContentPart { return Promise.resolve(ClosePanelResult.CLOSE); } - public focus(): void {} - public onHide(): void {} - public dispose() { this.part?.dispose(); } diff --git a/packages/splitview/src/react/dockview/reactHeaderPart.ts b/packages/splitview/src/react/dockview/reactHeaderPart.ts index f5d89ff96..8d4b55b69 100644 --- a/packages/splitview/src/react/dockview/reactHeaderPart.ts +++ b/packages/splitview/src/react/dockview/reactHeaderPart.ts @@ -1,5 +1,4 @@ import * as React from 'react'; -import { IGroupPanelApi } from '../../api/groupPanelApi'; import { PanelHeaderPart, GroupPanelPartInitParameters, diff --git a/packages/splitview/src/react/gridview/gridview.tsx b/packages/splitview/src/react/gridview/gridview.tsx index c9c4d78d5..76f895554 100644 --- a/packages/splitview/src/react/gridview/gridview.tsx +++ b/packages/splitview/src/react/gridview/gridview.tsx @@ -6,6 +6,7 @@ import { import { IGridPanelApi } from '../../api/gridPanelApi'; import { Orientation } from '../../splitview/splitview'; import { ReactComponentGridView } from './reactComponentGridView'; +import { usePortalsLifecycle } from '../react'; export interface GridviewReadyEvent { api: IComponentGridview; @@ -26,23 +27,12 @@ export interface IGridviewComponentProps { export const GridviewComponent: React.FunctionComponent = ( props: IGridviewComponentProps ) => { - const domReference = React.useRef(); - const gridview = React.useRef(); - const [portals, setPortals] = React.useState([]); - - const addPortal = React.useCallback((p: React.ReactPortal) => { - setPortals((portals) => [...portals, p]); - return { - dispose: () => { - setPortals((portals) => - portals.filter((portal) => portal !== p) - ); - }, - }; - }, []); + const domRef = React.useRef(); + const gridviewRef = React.useRef(); + const [portals, addPortal] = usePortalsLifecycle(); React.useEffect(() => { - gridview.current = new ComponentGridview(domReference.current, { + const gridview = new ComponentGridview(domRef.current, { orientation: props.orientation, frameworkComponents: props.components, frameworkComponentFactory: { @@ -59,9 +49,17 @@ export const GridviewComponent: React.FunctionComponent }, }); + // gridview.resizeToFit(); + if (props.onReady) { - props.onReady({ api: gridview.current }); + props.onReady({ api: gridview }); } + + gridviewRef.current = gridview; + + return () => { + gridview.dispose(); + }; }, []); return ( @@ -70,7 +68,7 @@ export const GridviewComponent: React.FunctionComponent height: '100%', width: '100%', }} - ref={domReference} + ref={domRef} > {portals} diff --git a/packages/splitview/src/react/paneview/paneview.tsx b/packages/splitview/src/react/paneview/paneview.tsx index b8ee736fe..2728e6e06 100644 --- a/packages/splitview/src/react/paneview/paneview.tsx +++ b/packages/splitview/src/react/paneview/paneview.tsx @@ -1,17 +1,18 @@ import * as React from 'react'; -import { IPanelApi } from '../../api/panelApi'; +import { IPanePanelApi } from '../../api/panePanelApi'; import { ComponentPaneView, IComponentPaneView, } from '../../paneview/componentPaneView'; import { PaneReact } from './reactPane'; +import { usePortalsLifecycle } from '../react'; export interface PaneviewReadyEvent { api: IComponentPaneView; } export interface IPaneviewPanelProps { - api: IPanelApi; + api: IPanePanelApi; } export interface IPaneviewComponentProps { @@ -24,23 +25,12 @@ export interface IPaneviewComponentProps { export const PaneViewComponent: React.FunctionComponent = ( props: IPaneviewComponentProps ) => { - const domReference = React.useRef(); - const splitpanel = React.useRef(); - const [portals, setPortals] = React.useState([]); - - const addPortal = React.useCallback((p: React.ReactPortal) => { - setPortals((portals) => [...portals, p]); - return { - dispose: () => { - setPortals((portals) => - portals.filter((portal) => portal !== p) - ); - }, - }; - }, []); + const domRef = React.useRef(); + const paneviewRef = React.useRef(); + const [portals, addPortal] = usePortalsLifecycle(); React.useEffect(() => { - splitpanel.current = new ComponentPaneView(domReference.current, { + const paneview = new ComponentPaneView(domRef.current, { frameworkComponents: props.components, components: {}, frameworkWrapper: { @@ -56,16 +46,20 @@ export const PaneViewComponent: React.FunctionComponent }, }); - const { width, height } = domReference.current.getBoundingClientRect(); + const { width, height } = domRef.current.getBoundingClientRect(); const [size, orthogonalSize] = [height, width]; - splitpanel.current.layout(size, orthogonalSize); + paneview.layout(size, orthogonalSize); if (props.onReady) { - props.onReady({ api: splitpanel.current }); + props.onReady({ api: paneview }); } + paneview.resizeToFit(); + + paneviewRef.current = paneview; + return () => { - splitpanel.current.dispose(); + paneview.dispose(); }; }, []); @@ -75,7 +69,7 @@ export const PaneViewComponent: React.FunctionComponent height: '100%', width: '100%', }} - ref={domReference} + ref={domRef} > {portals} diff --git a/packages/splitview/src/react/paneview/reactPane.tsx b/packages/splitview/src/react/paneview/reactPane.tsx index 160d1b386..f94389c9d 100644 --- a/packages/splitview/src/react/paneview/reactPane.tsx +++ b/packages/splitview/src/react/paneview/reactPane.tsx @@ -1,12 +1,12 @@ import * as React from 'react'; -import { BaseViewApi, IBaseViewApi } from '../../api/api'; +import { IPanePanelApi, PanePanelApi } from '../../api/panePanelApi'; import { Pane } from '../../paneview/paneview'; import { ReactLayout } from '../dockview/dockview'; import { ReactPart } from '../react'; export class PaneReact extends Pane { private params: {}; - private api: IBaseViewApi; + private api: PanePanelApi; private contentPart: ReactPart; private headerPart: ReactPart; @@ -27,7 +27,14 @@ export class PaneReact extends Pane { // options.isExpanded }); - this.api = new BaseViewApi(); + this.api = new PanePanelApi(); + + this.addDisposables( + this.onDidChangeExpansionState((isExpanded) => { + this.api._onDidExpansionChange.fire({ isExpanded }); + }) + ); + this.render(); } @@ -62,6 +69,7 @@ export class PaneReact extends Pane { } public dispose() { + super.dispose(); this.headerPart.dispose(); this.contentPart.dispose(); this.api.dispose(); diff --git a/packages/splitview/src/react/react.tsx b/packages/splitview/src/react/react.tsx index 2032d24fb..268cd46dd 100644 --- a/packages/splitview/src/react/react.tsx +++ b/packages/splitview/src/react/react.tsx @@ -118,3 +118,28 @@ export class ReactPart implements IDisposable { this.disposed = true; } } + +/** + * A React Hook that returns an array of portals to be rendered by the user of this hook + * and a disposable function to add a portal. Calling dispose remove this portal from the + * portal array + */ +export const usePortalsLifecycle = () => { + const [portals, setPortals] = React.useState([]); + + const addPortal = React.useCallback((p: React.ReactPortal) => { + setPortals((portals) => [...portals, p]); + return { + dispose: () => { + setPortals((portals) => + portals.filter((portal) => portal !== p) + ); + }, + }; + }, []); + + return [portals, addPortal] as [ + React.ReactPortal[], + (portal: React.ReactPortal) => IDisposable + ]; +}; diff --git a/packages/splitview/src/react/splitview/splitview.tsx b/packages/splitview/src/react/splitview/splitview.tsx index 6f0e69f29..099c29263 100644 --- a/packages/splitview/src/react/splitview/splitview.tsx +++ b/packages/splitview/src/react/splitview/splitview.tsx @@ -5,6 +5,7 @@ import { ComponentSplitview, } from '../../splitview/componentSplitview'; import { Orientation } from '../../splitview/splitview'; +import { usePortalsLifecycle } from '../react'; import { ReactComponentView } from './reactComponentView'; export interface SplitviewReadyEvent { @@ -26,23 +27,12 @@ export interface ISplitviewComponentProps { export const SplitviewComponent: React.FunctionComponent = ( props: ISplitviewComponentProps ) => { - const domReference = React.useRef(); - const splitpanel = React.useRef(); - const [portals, setPortals] = React.useState([]); - - const addPortal = React.useCallback((p: React.ReactPortal) => { - setPortals((portals) => [...portals, p]); - return { - dispose: () => { - setPortals((portals) => - portals.filter((portal) => portal !== p) - ); - }, - }; - }, []); + const domRef = React.useRef(); + const splitviewRef = React.useRef(); + const [portals, addPortal] = usePortalsLifecycle(); React.useEffect(() => { - splitpanel.current = new ComponentSplitview(domReference.current, { + const splitview = new ComponentSplitview(domRef.current, { orientation: props.orientation, frameworkComponents: props.components, frameworkWrapper: { @@ -55,19 +45,16 @@ export const SplitviewComponent: React.FunctionComponent { - splitpanel.current.dispose(); + splitview.dispose(); }; }, []); @@ -77,7 +64,7 @@ export const SplitviewComponent: React.FunctionComponent {portals} diff --git a/packages/splitview/src/splitview/componentSplitview.ts b/packages/splitview/src/splitview/componentSplitview.ts index 4fddb962f..b8422cfcd 100644 --- a/packages/splitview/src/splitview/componentSplitview.ts +++ b/packages/splitview/src/splitview/componentSplitview.ts @@ -79,6 +79,17 @@ export class ComponentSplitview implements IComponentSplitview { }; } + /** + * Resize the layout to fit the parent container + */ + public resizeToFit(): void { + const { + width, + height, + } = this.element.parentElement.getBoundingClientRect(); + this.layout(width, height); + } + private registerView(view: ISerializableView) { // } diff --git a/vscode.code-workspace b/vscode.code-workspace index d6c7df5fe..dfc97e8cf 100644 --- a/vscode.code-workspace +++ b/vscode.code-workspace @@ -4,5 +4,7 @@ "path": "." } ], - "settings": {} +"settings": { + "editor.formatOnSave": true +} }