This commit is contained in:
mathuo 2020-09-24 23:01:48 +01:00
parent 9e26f3d47c
commit 9e34faafea
13 changed files with 148 additions and 102 deletions

View File

@ -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<ExpansionEvent>;
}
export class PanePanelApi extends PanelApi implements IPanePanelApi {
readonly _onDidExpansionChange = new Emitter<ExpansionEvent>({
emitLastValue: true,
});
readonly onDidExpansionChange: Event<ExpansionEvent> = this
._onDidExpansionChange.event;
constructor() {
super();
}
}

View File

@ -76,6 +76,17 @@ export class ComponentPaneView implements IComponentPaneView {
this.paneview.layout(size, orthogonalSize); 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() { public dispose() {
this.paneview.dispose(); this.paneview.dispose();
} }

View File

@ -1,5 +1,5 @@
import { SplitView, IView, Orientation } from '../splitview/splitview'; import { SplitView, IView, Orientation } from '../splitview/splitview';
import { IDisposable } from '../lifecycle'; import { CompositeDisposable, IDisposable } from '../lifecycle';
import { Emitter, Event } from '../events'; import { Emitter, Event } from '../events';
import { addClasses, removeClasses } from '../dom'; import { addClasses, removeClasses } from '../dom';
@ -13,7 +13,7 @@ export interface IPaneview extends IView {
onDidChangeExpansionState: Event<boolean>; onDidChangeExpansionState: Event<boolean>;
} }
export abstract class Pane implements IPaneview { export abstract class Pane extends CompositeDisposable implements IPaneview {
private _element: HTMLElement; private _element: HTMLElement;
private _minimumBodySize: number; private _minimumBodySize: number;
private _maximumBodySize: number; private _maximumBodySize: number;
@ -81,6 +81,10 @@ export abstract class Pane implements IPaneview {
} }
constructor(options: IPaneOptions) { constructor(options: IPaneOptions) {
super();
this.addDisposables(this._onDidChange, this._onDidChangeExpansionState);
this._element = document.createElement('div'); this._element = document.createElement('div');
this._element.className = 'pane'; this._element.className = 'pane';

View File

@ -9,6 +9,7 @@ import {
TabContextMenuEvent, TabContextMenuEvent,
} from '../../dockview/options'; } from '../../dockview/options';
import { IGroupPanelApi } from '../../api/groupPanelApi'; import { IGroupPanelApi } from '../../api/groupPanelApi';
import { usePortalsLifecycle } from '../react';
export interface IGroupPanelProps { export interface IGroupPanelProps {
api: IGroupPanelApi; api: IGroupPanelApi;
@ -51,23 +52,12 @@ export interface IDockviewComponentProps {
export const DockviewComponent: React.FunctionComponent<IDockviewComponentProps> = ( export const DockviewComponent: React.FunctionComponent<IDockviewComponentProps> = (
props: IDockviewComponentProps props: IDockviewComponentProps
) => { ) => {
const domReference = React.useRef<HTMLDivElement>(); const domRef = React.useRef<HTMLDivElement>();
const layoutReference = React.useRef<ComponentDockview>(); const dockviewRef = React.useRef<ComponentDockview>();
const [portals, setPortals] = React.useState<React.ReactPortal[]>([]); const [portals, addPortal] = usePortalsLifecycle();
React.useEffect(() => { React.useEffect(() => {
const addPortal = (p: React.ReactPortal) => {
setPortals((portals) => [...portals, p]);
return {
dispose: () => {
setPortals((portals) =>
portals.filter((portal) => portal !== p)
);
},
};
};
const factory: GroupPanelFrameworkComponentFactory = { const factory: GroupPanelFrameworkComponentFactory = {
content: { content: {
createComponent: ( createComponent: (
@ -95,7 +85,7 @@ export const DockviewComponent: React.FunctionComponent<IDockviewComponentProps>
const element = document.createElement('div'); const element = document.createElement('div');
const layout = new ComponentDockview(element, { const dockview = new ComponentDockview(element, {
frameworkComponentFactory: factory, frameworkComponentFactory: factory,
frameworkComponents: props.components, frameworkComponents: props.components,
frameworkTabComponents: props.tabComponents, frameworkTabComponents: props.tabComponents,
@ -105,28 +95,28 @@ export const DockviewComponent: React.FunctionComponent<IDockviewComponentProps>
// orientation: props.orientation, // orientation: props.orientation,
}); });
layoutReference.current = layout; domRef.current.appendChild(dockview.element);
domReference.current.appendChild(layoutReference.current.element); dockview.deserializer = new ReactPanelDeserialzier(dockview);
layout.deserializer = new ReactPanelDeserialzier(layout);
layout.resizeToFit();
if (props.serializedLayout) { if (props.serializedLayout) {
layout.deserialize(props.serializedLayout); dockview.deserialize(props.serializedLayout);
} }
dockview.resizeToFit();
if (props.onReady) { if (props.onReady) {
props.onReady({ api: layout }); props.onReady({ api: dockview });
} }
dockviewRef.current = dockview;
return () => { return () => {
layout.dispose(); dockview.dispose();
}; };
}, []); }, []);
React.useEffect(() => { React.useEffect(() => {
const disposable = layoutReference.current.onTabContextMenu((event) => { const disposable = dockviewRef.current.onTabContextMenu((event) => {
props.onTabContextMenu(event); props.onTabContextMenu(event);
}); });
@ -136,9 +126,7 @@ export const DockviewComponent: React.FunctionComponent<IDockviewComponentProps>
}, [props.onTabContextMenu]); }, [props.onTabContextMenu]);
React.useEffect(() => { React.useEffect(() => {
layoutReference.current.setAutoResizeToFit( dockviewRef.current.setAutoResizeToFit(props.autoSizeToFitContainer);
props.autoSizeToFitContainer
);
}, [props.autoSizeToFitContainer]); }, [props.autoSizeToFitContainer]);
return ( return (
@ -147,7 +135,7 @@ export const DockviewComponent: React.FunctionComponent<IDockviewComponentProps>
// height: '100%', // height: '100%',
width: '100%', width: '100%',
}} }}
ref={domReference} ref={domRef}
> >
{portals} {portals}
</div> </div>

View File

@ -54,9 +54,6 @@ export class ReactPanelContentPart implements PanelContentPart {
return Promise.resolve(ClosePanelResult.CLOSE); return Promise.resolve(ClosePanelResult.CLOSE);
} }
public focus(): void {}
public onHide(): void {}
public dispose() { public dispose() {
this.part?.dispose(); this.part?.dispose();
} }

View File

@ -1,5 +1,4 @@
import * as React from 'react'; import * as React from 'react';
import { IGroupPanelApi } from '../../api/groupPanelApi';
import { import {
PanelHeaderPart, PanelHeaderPart,
GroupPanelPartInitParameters, GroupPanelPartInitParameters,

View File

@ -6,6 +6,7 @@ import {
import { IGridPanelApi } from '../../api/gridPanelApi'; import { IGridPanelApi } from '../../api/gridPanelApi';
import { Orientation } from '../../splitview/splitview'; import { Orientation } from '../../splitview/splitview';
import { ReactComponentGridView } from './reactComponentGridView'; import { ReactComponentGridView } from './reactComponentGridView';
import { usePortalsLifecycle } from '../react';
export interface GridviewReadyEvent { export interface GridviewReadyEvent {
api: IComponentGridview; api: IComponentGridview;
@ -26,23 +27,12 @@ export interface IGridviewComponentProps {
export const GridviewComponent: React.FunctionComponent<IGridviewComponentProps> = ( export const GridviewComponent: React.FunctionComponent<IGridviewComponentProps> = (
props: IGridviewComponentProps props: IGridviewComponentProps
) => { ) => {
const domReference = React.useRef<HTMLDivElement>(); const domRef = React.useRef<HTMLDivElement>();
const gridview = React.useRef<IComponentGridview>(); const gridviewRef = React.useRef<IComponentGridview>();
const [portals, setPortals] = React.useState<React.ReactPortal[]>([]); const [portals, addPortal] = usePortalsLifecycle();
const addPortal = React.useCallback((p: React.ReactPortal) => {
setPortals((portals) => [...portals, p]);
return {
dispose: () => {
setPortals((portals) =>
portals.filter((portal) => portal !== p)
);
},
};
}, []);
React.useEffect(() => { React.useEffect(() => {
gridview.current = new ComponentGridview(domReference.current, { const gridview = new ComponentGridview(domRef.current, {
orientation: props.orientation, orientation: props.orientation,
frameworkComponents: props.components, frameworkComponents: props.components,
frameworkComponentFactory: { frameworkComponentFactory: {
@ -59,9 +49,17 @@ export const GridviewComponent: React.FunctionComponent<IGridviewComponentProps>
}, },
}); });
// gridview.resizeToFit();
if (props.onReady) { if (props.onReady) {
props.onReady({ api: gridview.current }); props.onReady({ api: gridview });
} }
gridviewRef.current = gridview;
return () => {
gridview.dispose();
};
}, []); }, []);
return ( return (
@ -70,7 +68,7 @@ export const GridviewComponent: React.FunctionComponent<IGridviewComponentProps>
height: '100%', height: '100%',
width: '100%', width: '100%',
}} }}
ref={domReference} ref={domRef}
> >
{portals} {portals}
</div> </div>

View File

@ -1,17 +1,18 @@
import * as React from 'react'; import * as React from 'react';
import { IPanelApi } from '../../api/panelApi'; import { IPanePanelApi } from '../../api/panePanelApi';
import { import {
ComponentPaneView, ComponentPaneView,
IComponentPaneView, IComponentPaneView,
} from '../../paneview/componentPaneView'; } from '../../paneview/componentPaneView';
import { PaneReact } from './reactPane'; import { PaneReact } from './reactPane';
import { usePortalsLifecycle } from '../react';
export interface PaneviewReadyEvent { export interface PaneviewReadyEvent {
api: IComponentPaneView; api: IComponentPaneView;
} }
export interface IPaneviewPanelProps { export interface IPaneviewPanelProps {
api: IPanelApi; api: IPanePanelApi;
} }
export interface IPaneviewComponentProps { export interface IPaneviewComponentProps {
@ -24,23 +25,12 @@ export interface IPaneviewComponentProps {
export const PaneViewComponent: React.FunctionComponent<IPaneviewComponentProps> = ( export const PaneViewComponent: React.FunctionComponent<IPaneviewComponentProps> = (
props: IPaneviewComponentProps props: IPaneviewComponentProps
) => { ) => {
const domReference = React.useRef<HTMLDivElement>(); const domRef = React.useRef<HTMLDivElement>();
const splitpanel = React.useRef<IComponentPaneView>(); const paneviewRef = React.useRef<IComponentPaneView>();
const [portals, setPortals] = React.useState<React.ReactPortal[]>([]); const [portals, addPortal] = usePortalsLifecycle();
const addPortal = React.useCallback((p: React.ReactPortal) => {
setPortals((portals) => [...portals, p]);
return {
dispose: () => {
setPortals((portals) =>
portals.filter((portal) => portal !== p)
);
},
};
}, []);
React.useEffect(() => { React.useEffect(() => {
splitpanel.current = new ComponentPaneView(domReference.current, { const paneview = new ComponentPaneView(domRef.current, {
frameworkComponents: props.components, frameworkComponents: props.components,
components: {}, components: {},
frameworkWrapper: { frameworkWrapper: {
@ -56,16 +46,20 @@ export const PaneViewComponent: React.FunctionComponent<IPaneviewComponentProps>
}, },
}); });
const { width, height } = domReference.current.getBoundingClientRect(); const { width, height } = domRef.current.getBoundingClientRect();
const [size, orthogonalSize] = [height, width]; const [size, orthogonalSize] = [height, width];
splitpanel.current.layout(size, orthogonalSize); paneview.layout(size, orthogonalSize);
if (props.onReady) { if (props.onReady) {
props.onReady({ api: splitpanel.current }); props.onReady({ api: paneview });
} }
paneview.resizeToFit();
paneviewRef.current = paneview;
return () => { return () => {
splitpanel.current.dispose(); paneview.dispose();
}; };
}, []); }, []);
@ -75,7 +69,7 @@ export const PaneViewComponent: React.FunctionComponent<IPaneviewComponentProps>
height: '100%', height: '100%',
width: '100%', width: '100%',
}} }}
ref={domReference} ref={domRef}
> >
{portals} {portals}
</div> </div>

View File

@ -1,12 +1,12 @@
import * as React from 'react'; import * as React from 'react';
import { BaseViewApi, IBaseViewApi } from '../../api/api'; import { IPanePanelApi, PanePanelApi } from '../../api/panePanelApi';
import { Pane } from '../../paneview/paneview'; import { Pane } from '../../paneview/paneview';
import { ReactLayout } from '../dockview/dockview'; import { ReactLayout } from '../dockview/dockview';
import { ReactPart } from '../react'; import { ReactPart } from '../react';
export class PaneReact extends Pane { export class PaneReact extends Pane {
private params: {}; private params: {};
private api: IBaseViewApi; private api: PanePanelApi;
private contentPart: ReactPart; private contentPart: ReactPart;
private headerPart: ReactPart; private headerPart: ReactPart;
@ -27,7 +27,14 @@ export class PaneReact extends Pane {
// options.isExpanded // options.isExpanded
}); });
this.api = new BaseViewApi(); this.api = new PanePanelApi();
this.addDisposables(
this.onDidChangeExpansionState((isExpanded) => {
this.api._onDidExpansionChange.fire({ isExpanded });
})
);
this.render(); this.render();
} }
@ -62,6 +69,7 @@ export class PaneReact extends Pane {
} }
public dispose() { public dispose() {
super.dispose();
this.headerPart.dispose(); this.headerPart.dispose();
this.contentPart.dispose(); this.contentPart.dispose();
this.api.dispose(); this.api.dispose();

View File

@ -118,3 +118,28 @@ export class ReactPart implements IDisposable {
this.disposed = true; 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<React.ReactPortal[]>([]);
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
];
};

View File

@ -5,6 +5,7 @@ import {
ComponentSplitview, ComponentSplitview,
} from '../../splitview/componentSplitview'; } from '../../splitview/componentSplitview';
import { Orientation } from '../../splitview/splitview'; import { Orientation } from '../../splitview/splitview';
import { usePortalsLifecycle } from '../react';
import { ReactComponentView } from './reactComponentView'; import { ReactComponentView } from './reactComponentView';
export interface SplitviewReadyEvent { export interface SplitviewReadyEvent {
@ -26,23 +27,12 @@ export interface ISplitviewComponentProps {
export const SplitviewComponent: React.FunctionComponent<ISplitviewComponentProps> = ( export const SplitviewComponent: React.FunctionComponent<ISplitviewComponentProps> = (
props: ISplitviewComponentProps props: ISplitviewComponentProps
) => { ) => {
const domReference = React.useRef<HTMLDivElement>(); const domRef = React.useRef<HTMLDivElement>();
const splitpanel = React.useRef<IComponentSplitview>(); const splitviewRef = React.useRef<IComponentSplitview>();
const [portals, setPortals] = React.useState<React.ReactPortal[]>([]); const [portals, addPortal] = usePortalsLifecycle();
const addPortal = React.useCallback((p: React.ReactPortal) => {
setPortals((portals) => [...portals, p]);
return {
dispose: () => {
setPortals((portals) =>
portals.filter((portal) => portal !== p)
);
},
};
}, []);
React.useEffect(() => { React.useEffect(() => {
splitpanel.current = new ComponentSplitview(domReference.current, { const splitview = new ComponentSplitview(domRef.current, {
orientation: props.orientation, orientation: props.orientation,
frameworkComponents: props.components, frameworkComponents: props.components,
frameworkWrapper: { frameworkWrapper: {
@ -55,19 +45,16 @@ export const SplitviewComponent: React.FunctionComponent<ISplitviewComponentProp
proportionalLayout: false, proportionalLayout: false,
}); });
const { width, height } = domReference.current.getBoundingClientRect(); splitview.resizeToFit();
const [size, orthogonalSize] =
props.orientation === Orientation.HORIZONTAL
? [width, height]
: [height, width];
splitpanel.current.layout(size, orthogonalSize);
if (props.onReady) { if (props.onReady) {
props.onReady({ api: splitpanel.current }); props.onReady({ api: splitview });
} }
splitviewRef.current = splitview;
return () => { return () => {
splitpanel.current.dispose(); splitview.dispose();
}; };
}, []); }, []);
@ -77,7 +64,7 @@ export const SplitviewComponent: React.FunctionComponent<ISplitviewComponentProp
height: '100%', height: '100%',
width: '100%', width: '100%',
}} }}
ref={domReference} ref={domRef}
> >
{portals} {portals}
</div> </div>

View File

@ -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) { private registerView(view: ISerializableView) {
// //
} }

View File

@ -4,5 +4,7 @@
"path": "." "path": "."
} }
], ],
"settings": {} "settings": {
"editor.formatOnSave": true
}
} }