diff --git a/packages/dockview-demo/public/index.html b/packages/dockview-demo/public/index.html index 9b54428e6..3e434a577 100644 --- a/packages/dockview-demo/public/index.html +++ b/packages/dockview-demo/public/index.html @@ -1,7 +1,7 @@ - +
diff --git a/packages/dockview-demo/src/layout-grid/activitybar.tsx b/packages/dockview-demo/src/layout-grid/activitybar.tsx index d3bd310f6..37242391d 100644 --- a/packages/dockview-demo/src/layout-grid/activitybar.tsx +++ b/packages/dockview-demo/src/layout-grid/activitybar.tsx @@ -49,18 +49,27 @@ export const Activitybar = (props: IGridviewPanelProps) => { return (
- -
- -
-
+
+ +
+
+ +
+
+ +
); }; diff --git a/packages/dockview-demo/src/layout-grid/application.tsx b/packages/dockview-demo/src/layout-grid/application.tsx index fd265a651..748396699 100644 --- a/packages/dockview-demo/src/layout-grid/application.tsx +++ b/packages/dockview-demo/src/layout-grid/application.tsx @@ -8,12 +8,12 @@ import { SerializedGridview, GridviewApi, } from 'dockview'; -import { Activitybar } from './activitybar'; +import { Activitybar } from '../services/widgets'; import { Footer } from './footer'; import { Panel } from './panel'; import { TestGrid } from './layoutGrid'; import { useLayoutRegistry } from './registry'; -import { Sidebar } from './sidebar'; +import { Sidebar } from '../services/widgets'; const rootcomponents: { [index: string]: React.FunctionComponent; diff --git a/packages/dockview-demo/src/layout-grid/sidebar.layout.json b/packages/dockview-demo/src/layout-grid/sidebar.layout.json index 0625e7e4c..5dd7eb9ba 100644 --- a/packages/dockview-demo/src/layout-grid/sidebar.layout.json +++ b/packages/dockview-demo/src/layout-grid/sidebar.layout.json @@ -5,7 +5,7 @@ "data": { "id": "1", "component": "controlCenter", - "headerComponent": "default", + "props": {}, "title": "Control Center", "state": {} @@ -17,7 +17,7 @@ "data": { "id": "2", "component": "default", - "headerComponent": "default", + "props": {}, "title": "Panel 1", "state": {} @@ -30,7 +30,7 @@ "data": { "id": "3", "component": "default", - "headerComponent": "default", + "props": {}, "title": "Panel 2", "state": {} @@ -43,7 +43,7 @@ "data": { "id": "4", "component": "default", - "headerComponent": "default", + "props": {}, "title": "Panel 3", "state": {} diff --git a/packages/dockview-demo/src/layout-grid/sidebar.tsx b/packages/dockview-demo/src/layout-grid/sidebar.tsx index 6b3b33754..e849abeed 100644 --- a/packages/dockview-demo/src/layout-grid/sidebar.tsx +++ b/packages/dockview-demo/src/layout-grid/sidebar.tsx @@ -12,107 +12,6 @@ import { ControlCenter } from './controlCenter'; import { toggleClass } from '../dom'; import './sidebar.scss'; -const DefaultHeader = (props: IPaneviewPanelProps) => { - const ref = React.useRef(); - const mouseover = React.useRef(); - - const [url, setUrl] = React.useState( - props.api.isExpanded - ? 'https://fonts.gstatic.com/s/i/materialicons/expand_more/v6/24px.svg' - : 'https://fonts.gstatic.com/s/i/materialicons/chevron_right/v7/24px.svg' - ); - - const toggle = () => { - toggleClass( - ref.current, - 'within', - props.api.isExpanded && mouseover.current - ); - }; - - React.useEffect(() => { - const disposable = new CompositeDisposable( - props.api.onDidExpansionChange((event) => { - setUrl( - event.isExpanded - ? 'https://fonts.gstatic.com/s/i/materialicons/expand_more/v6/24px.svg' - : 'https://fonts.gstatic.com/s/i/materialicons/chevron_right/v7/24px.svg' - ); - toggle(); - }), - props.api.onMouseEnter((ev) => { - mouseover.current = true; - toggle(); - }), - props.api.onMouseLeave((ev) => { - mouseover.current = false; - toggle(); - }) - ); - - return () => { - disposable.dispose(); - }; - }); - - const onClick = (event: React.MouseEvent) => { - if (event.defaultPrevented) { - return; - } - props.api.setExpanded(!props.api.isExpanded); - }; - - const onClickAction = (event: React.MouseEvent) => { - event.preventDefault(); - }; - - return ( -
-
- -
- {props.title} - - -
- ); -}; - const components = { default: (props: IPaneviewPanelProps) => { return
This is an example panel
; @@ -120,10 +19,6 @@ const components = { controlCenter: ControlCenter, }; -const headerComponents = { - default: DefaultHeader, -}; - export const Sidebar = (props: IGridviewPanelProps) => { const api = React.useRef(); @@ -139,25 +34,21 @@ export const Sidebar = (props: IGridviewPanelProps) => { event.api.addPanel({ id: '1', component: 'default', - headerComponent: 'default', title: 'Control Center', }); event.api.addPanel({ id: '2', component: 'default', - headerComponent: 'default', title: 'Panel 1', }); event.api.addPanel({ id: '3', component: 'default', - headerComponent: 'default', title: 'Panel 2', }); event.api.addPanel({ id: '4', component: 'default', - headerComponent: 'default', title: 'Panel 3', }); @@ -196,7 +87,6 @@ export const Sidebar = (props: IGridviewPanelProps) => { }} > { + readonly id: string; + readonly views: View[]; + readonly schema: T | any; + readonly icon: string; + readonly onDidAddView: Event; + readonly onDidRemoveView: Event; + addView(view: View, location?: number): void; + layout(schema: T): void; + removeView(view: View): void; + clear(): void; + toJSON(): SerializedViewContainer; +} + +export class PaneviewContainer implements ViewContainer { + private readonly _id: string; + private readonly _views: View[] = []; + + private readonly _onDidAddView = new Emitter(); + readonly onDidAddView = this._onDidAddView.event; + private readonly _onDidRemoveView = new Emitter(); + readonly onDidRemoveView = this._onDidRemoveView.event; + + private _schema: SerializedPaneview | undefined; + + get id() { + return this._id; + } + + get views() { + return this._views; + } + + get schema(): SerializedPaneview | undefined { + if (!this._schema) { + this._schema = JSON.parse( + localStorage.getItem(`viewcontainer_${this.id}`) + ); + } + return this._schema; + } + + get icon(): string { + const defaultIcon = 'search'; + if (this.views.length > 0) { + return this.views.find((v) => !!v.icon)?.icon || defaultIcon; + } + return defaultIcon; + } + + constructor( + id: string, + viewRegistry: IViewRegistry, + views?: SerializedView[] + ) { + this._id = id; + + if (views) { + for (const view of views) { + const registeredView = viewRegistry.getRegisteredView(view.id); + this.addView( + new DefaultView({ + id: view.id, + title: registeredView.title, + isExpanded: view.isExpanded, + isLocationEditable: registeredView.isLocationEditable, + icon: registeredView.icon, + }) + ); + } + } + // this.addDisposables(this._onDidAddView, this._onDidRemoveView); + } + + layout(schema: SerializedPaneview): void { + this._schema = schema; + localStorage.setItem( + `viewcontainer_${this.id}`, + JSON.stringify(schema) + ); + } + + addView(view: View, location = 0): void { + this._views.splice(location, 0, view); + this._onDidAddView.fire(view); + } + + removeView(view: View): void { + const index = this._views.indexOf(view); + if (index < 0) { + throw new Error('invalid'); + } + this._views.splice(index, 1); + + if (this._schema) { + this._schema = { ...this._schema }; + this._schema.views = this._schema.views.filter( + (v) => v.data.id !== view.id + ); + this.layout(this._schema); + } + + this._onDidRemoveView.fire(view); + } + + clear() { + localStorage.removeItem(`viewcontainer_${this.id}`); + } + + toJSON(): SerializedViewContainer { + return { id: this.id, views: this.views.map((v) => v.toJSON()) }; + } +} diff --git a/packages/dockview-demo/src/services/viewRegistry.ts b/packages/dockview-demo/src/services/viewRegistry.ts new file mode 100644 index 000000000..2a77a4ad1 --- /dev/null +++ b/packages/dockview-demo/src/services/viewRegistry.ts @@ -0,0 +1,49 @@ +export interface RegisteredView { + id: string; + icon: string; + title: string; + isLocationEditable: boolean; +} + +export interface IViewRegistry { + getRegisteredView(id: string): RegisteredView | undefined; +} + +export class ViewRegistry { + private readonly _registry = new Map(); + + register(registeredView: RegisteredView): void { + this._registry.set(registeredView.id, registeredView); + } + + getRegisteredView(id: string): RegisteredView | undefined { + return this._registry.get(id); + } +} + +export const VIEW_REGISTRY = new ViewRegistry(); + +VIEW_REGISTRY.register({ + id: 'search_widget', + title: 'search', + icon: 'search', + isLocationEditable: false, +}); +VIEW_REGISTRY.register({ + id: 'home_widget', + title: 'Home', + icon: 'home', + isLocationEditable: true, +}); +VIEW_REGISTRY.register({ + id: 'account_widget', + title: 'Account', + icon: 'account_circle', + isLocationEditable: true, +}); +VIEW_REGISTRY.register({ + id: 'settings_widget', + title: 'Settings', + icon: 'settings', + isLocationEditable: true, +}); diff --git a/packages/dockview-demo/src/services/viewService.ts b/packages/dockview-demo/src/services/viewService.ts new file mode 100644 index 000000000..1c9dc4d77 --- /dev/null +++ b/packages/dockview-demo/src/services/viewService.ts @@ -0,0 +1,200 @@ +import { + CompositeDisposable, + Emitter, + Event, + IDisposable, + IView, +} from 'dockview'; +import { DefaultView, View } from './view'; +import { + PaneviewContainer, + ViewContainer, + SerializedViewContainer, +} from './viewContainer'; +import { IViewRegistry } from './viewRegistry'; + +export interface SerializedViewService { + containers: SerializedViewContainer[]; + activeContainer?: string; +} + +export interface IViewService extends IDisposable { + readonly containers: ViewContainer[]; + readonly onDidActiveContainerChange: Event; + readonly onDidRemoveContainer: Event; + readonly onDidAddContainer: Event; + readonly onDidContainersChange: Event; + readonly activeContainer: ViewContainer | undefined; + addContainer(container: ViewContainer): void; + setActiveViewContainer(id: string): void; + getView(id: string): View | undefined; + moveViewToLocation( + view: View, + targetViewContainer: ViewContainer, + targetLocation: number + ): void; + insertContainerAfter(source: ViewContainer, target: ViewContainer): void; + addViews(view: View, viewContainer: ViewContainer, location?: number): void; + removeViews(removeViews: View[], viewContainer: ViewContainer): void; + getViewContainer(id: string): ViewContainer | undefined; + getViewContainer2(view: View): ViewContainer | undefined; + toJSON(): SerializedViewService; + load(layout: SerializedViewService): void; +} + +export class ViewService implements IViewService { + private _viewContainers: ViewContainer[] = []; + private readonly _onDidActiveContainerChange = new Emitter(); + readonly onDidActiveContainerChange = this._onDidActiveContainerChange + .event; + private readonly _onDidRemoveContainer = new Emitter(); + readonly onDidRemoveContainer = this._onDidRemoveContainer.event; + private readonly _onDidAddContainer = new Emitter(); + readonly onDidAddContainer = this._onDidAddContainer.event; + private readonly _onDidContainersChange = new Emitter(); + readonly onDidContainersChange = this._onDidContainersChange.event; + private _activeViewContainerId: string; + + get containers(): ViewContainer[] { + return this._viewContainers; + } + + get activeContainer(): ViewContainer | undefined { + return this._viewContainers.find( + (c) => c.id === this._activeViewContainerId + ); + } + + constructor(private readonly viewRegistry: IViewRegistry) { + // + } + + load(layout: SerializedViewService): void { + const { containers, activeContainer } = layout; + + for (const container of containers) { + const { id, views } = container; + const viewContainer = new PaneviewContainer( + id, + this.viewRegistry, + views + ); + + this.addContainer(viewContainer); + } + + this.setActiveViewContainer(activeContainer); + } + + insertContainerAfter(source: ViewContainer, target: ViewContainer): void { + const sourceIndex = this._viewContainers.findIndex( + (c) => c.id === source.id + ); + + const view = this._viewContainers.splice(sourceIndex, 1)[0]; + + const targetIndex = this._viewContainers.findIndex( + (c) => c.id === target.id + ); + + this._viewContainers.splice(targetIndex + 1, 0, view); + this._viewContainers = [...this._viewContainers]; + + this._onDidContainersChange.fire(); + } + + addContainer(container: ViewContainer): void { + this._viewContainers = [...this._viewContainers, container]; + this._activeViewContainerId = container.id; + this._onDidAddContainer.fire(); + } + + removeContainer(container: ViewContainer): void { + this._viewContainers = this._viewContainers.filter( + (c) => c.id !== container.id + ); + + if (this._activeViewContainerId === container.id) { + this._activeViewContainerId = + this._viewContainers.length > 0 + ? this._viewContainers[0].id + : undefined; + } + + this._onDidRemoveContainer.fire(); + } + + setActiveViewContainer(id: string): void { + if (!this._viewContainers.find((c) => c.id === id)) { + throw new Error(`invalid container ${id}`); + } + this._activeViewContainerId = id; + this._onDidActiveContainerChange.fire(); + } + + getView(id: string): View | undefined { + for (const container of Array.from(this._viewContainers.values())) { + const view = container.views.find((v) => v.id === id); + if (view) { + return view; + } + } + return undefined; + } + + moveViewToLocation( + view: View, + targetViewContainer: ViewContainer, + targetLocation: number + ): void { + const sourceViewContainer = this.getViewContainer2(view); + this.removeViews([view], sourceViewContainer); + this.addViews(view, targetViewContainer, targetLocation); + } + + addViews( + view: View, + viewContainer: ViewContainer, + location?: number + ): void { + viewContainer.addView(view, location); + } + + removeViews(removeViews: View[], viewContainer: ViewContainer): void { + for (const view of removeViews) { + viewContainer.removeView(view); + + if (viewContainer.views.length === 0) { + viewContainer.clear(); + this.removeContainer(viewContainer); + } + } + } + + getViewContainer(id: string): ViewContainer | undefined { + return this._viewContainers.find((c) => c.id === id); + } + + getViewContainer2(view: View): ViewContainer | undefined { + for (const container of Array.from(this._viewContainers.values())) { + const v = container.views.find((v) => v.id === view.id); + if (v) { + return container; + } + } + return undefined; + } + + toJSON(): SerializedViewService { + return { + containers: this.containers.map((c) => c.toJSON()), + activeContainer: this.activeContainer.id, + }; + } + + dispose(): void { + this._onDidActiveContainerChange.dispose(); + this._onDidAddContainer.dispose(); + this._onDidRemoveContainer.dispose(); + } +} diff --git a/packages/dockview-demo/src/services/widgets.tsx b/packages/dockview-demo/src/services/widgets.tsx new file mode 100644 index 000000000..fb2d17a70 --- /dev/null +++ b/packages/dockview-demo/src/services/widgets.tsx @@ -0,0 +1,357 @@ +import { + CompositeDisposable, + GridviewApi, + IGridviewPanelProps, + IPaneviewPanelProps, + PanelCollection, + PaneviewApi, + PaneviewDropEvent, + PaneviewReact, + PaneviewReadyEvent, + getPaneData, +} from 'dockview'; +import * as React from 'react'; +import { useLayoutRegistry } from '../layout-grid/registry'; +import { PaneviewContainer, ViewContainer } from './viewContainer'; +import { IViewService, ViewService } from './viewService'; +import { DefaultView } from './view'; +import { RegisteredView, VIEW_REGISTRY } from './viewRegistry'; + +class ViewServiceModel { + private readonly viewService: IViewService; + + get model() { + return this.viewService; + } + + constructor() { + this.viewService = new ViewService(VIEW_REGISTRY); + this.init(); + } + + init(): void { + const layout = localStorage.getItem('viewservice'); + if (layout) { + this.viewService.load(JSON.parse(layout)); + } else { + const container1 = new PaneviewContainer( + 'default_container_1', + VIEW_REGISTRY + ); + if (!container1.schema) { + this.addView( + container1, + VIEW_REGISTRY.getRegisteredView('search_widget') + ); + this.addView( + container1, + VIEW_REGISTRY.getRegisteredView('home_widget') + ); + } + const container2 = new PaneviewContainer( + 'default_container_2', + VIEW_REGISTRY + ); + if (!container2.schema) { + this.addView( + container2, + VIEW_REGISTRY.getRegisteredView('account_widget') + ); + this.addView( + container2, + VIEW_REGISTRY.getRegisteredView('settings_widget') + ); + } + this.viewService.addContainer(container1); + this.viewService.addContainer(container2); + } + + const save = () => { + localStorage.setItem( + 'viewservice', + JSON.stringify(this.viewService.toJSON()) + ); + }; + + this.viewService.onDidActiveContainerChange(save); + this.viewService.onDidRemoveContainer(save); + this.viewService.onDidAddContainer(save); + } + + private addView( + container: ViewContainer, + registedView: RegisteredView + ): void { + container.addView( + new DefaultView({ + id: registedView.id, + title: registedView.title, + isExpanded: true, + isLocationEditable: registedView.isLocationEditable, + icon: registedView.icon, + }) + ); + } +} + +const viewService = new ViewServiceModel(); + +const components: PanelCollection> = { + default: (props: IPaneviewPanelProps<{ viewId: string }>) => { + return ( +
+ {props.params.viewId} +
+ ); + }, +}; + +export const Activitybar = (props: IGridviewPanelProps) => { + const [activeContainerid, setActiveContainerId] = React.useState( + viewService.model.activeContainer.id + ); + const [containers, setContainers] = React.useState( + viewService.model.containers + ); + + const registry = useLayoutRegistry(); + + React.useEffect(() => { + const disposable = new CompositeDisposable( + viewService.model.onDidActiveContainerChange(() => { + setActiveContainerId(viewService.model.activeContainer.id); + }), + viewService.model.onDidAddContainer(() => { + setContainers(viewService.model.containers); + }), + viewService.model.onDidRemoveContainer(() => { + setContainers(viewService.model.containers); + }), + viewService.model.onDidContainersChange(() => { + setContainers(viewService.model.containers); + }) + ); + + return () => { + disposable.dispose(); + }; + }, []); + + const onClick = (container: ViewContainer, alwaysOpen = false) => ( + event: React.MouseEvent + ) => { + const api = registry.get('gridview'); + + const selectedActive = container.id === activeContainerid; + + const sidebarPanel = api.getPanel('sidebar'); + if (sidebarPanel.api.isVisible) { + if (!alwaysOpen && selectedActive) { + api.setVisible(sidebarPanel, false); + } + } else { + event.preventDefault(); // prevent focus + api.setVisible(sidebarPanel, true); + sidebarPanel.focus(); + } + + viewService.model.setActiveViewContainer(container.id); + }; + + const onDrop = (targetContainer: ViewContainer) => ( + event: React.DragEvent + ) => { + const data = event.dataTransfer.getData('application/json'); + if (data) { + const { container } = JSON.parse(data); + const sourceContainer = viewService.model.getViewContainer( + container + ); + viewService.model.insertContainerAfter( + sourceContainer, + targetContainer + ); + } + }; + + const onNewContainer = (event: React.DragEvent) => { + const data = getPaneData(); + if (data) { + const { paneId } = data; + const view = viewService.model.getView(paneId); + const viewContainer = viewService.model.getViewContainer2(view); + viewService.model.removeViews([view], viewContainer); + // viewContainer.removeView(view); + const newContainer = new PaneviewContainer( + `t_${Date.now().toString().substr(5)}`, + VIEW_REGISTRY + ); + newContainer.addView(view); + viewService.model.addContainer(newContainer); + } + }; + + return ( +
+ {containers.map((container, i) => { + const isActive = activeContainerid === container.id; + return ( +
{ + e.preventDefault(); + onClick(container, true)(e); + }} + onDragEnter={(e) => { + e.preventDefault(); + }} + draggable={true} + onDragStart={(e) => { + e.dataTransfer.setData( + 'application/json', + JSON.stringify({ container: container.id }) + ); + }} + onDrop={onDrop(container)} + style={{ + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + height: '48px', + boxSizing: 'border-box', + borderLeft: isActive + ? '1px solid white' + : '1px solid transparent', + }} + key={i} + > + {/* {container.id} */} + + {container.icon} + +
+ ); + })} +
{ + e.preventDefault(); + }} + onDragEnter={(e) => { + e.preventDefault(); + }} + onDrop={onNewContainer} + style={{ height: '100%', backgroundColor: 'red' }} + >
+
+ ); +}; + +export const Sidebar = () => { + const [sidebarId, setSidebarId] = React.useState( + viewService.model.activeContainer.id + ); + + React.useEffect(() => { + const disposable = viewService.model.onDidActiveContainerChange(() => { + setSidebarId(viewService.model.activeContainer.id); + }); + + return () => { + disposable.dispose(); + }; + }, []); + + return ; +}; + +export const SidebarPart = (props: { id: string }) => { + const [api, setApi] = React.useState(); + + React.useEffect(() => { + if (!api) { + return () => { + // + }; + } + + const viewContainer = viewService.model.getViewContainer(props.id); + + const disposables = new CompositeDisposable( + api.onDidLayoutChange(() => { + viewContainer.layout(api.toJSON()); + }), + viewContainer.onDidAddView((view) => { + api.addPanel({ + id: view.id, + isExpanded: view.isExpanded, + title: view.title, + component: 'default', + params: { + viewId: view.id, + }, + }); + }), + viewContainer.onDidRemoveView((view) => { + const panel = api.getPanel(view.id); + api.removePanel(panel); + }) + ); + + const schema = viewContainer.schema; + if (schema) { + api.fromJSON(schema); + } else { + api.getPanels().forEach((p) => { + api.removePanel(p); + }); + viewContainer.views.forEach((view) => { + api.addPanel({ + id: view.id, + isExpanded: view.isExpanded, + title: view.title, + component: 'default', + params: { + viewId: view.id, + }, + }); + }); + } + + return () => { + disposables.dispose(); + }; + }, [api, props.id]); + + const onReady = (event: PaneviewReadyEvent) => { + setApi(event.api); + }; + + const onDidDrop = (event: PaneviewDropEvent) => { + const data = getPaneData(); + + if (!data) { + return; + } + + const viewId = data.paneId; + const viewContainer = viewService.model.getViewContainer(props.id); + const view = viewService.model.getView(viewId); + + viewService.model.moveViewToLocation(view, viewContainer, 0); + }; + + if (!props.id) { + return null; + } + + return ( + + ); +}; diff --git a/packages/dockview/src/index.ts b/packages/dockview/src/index.ts index 8ad56d994..b121d7063 100644 --- a/packages/dockview/src/index.ts +++ b/packages/dockview/src/index.ts @@ -16,6 +16,7 @@ export * from './groupview/types'; export * from './dockview/dockviewComponent'; export * from './dockview/options'; export * from './gridview/gridviewComponent'; +export * from './dnd/dataTransfer'; export * from './react'; // TODO: should be conditional on whether user wants the React wrappers diff --git a/packages/dockview/src/paneview/paneview.ts b/packages/dockview/src/paneview/paneview.ts index 9c569c015..8e59de810 100644 --- a/packages/dockview/src/paneview/paneview.ts +++ b/packages/dockview/src/paneview/paneview.ts @@ -96,6 +96,12 @@ export class Paneview extends CompositeDisposable implements IDisposable { this.addDisposables( this.splitview.onDidSashEnd(() => { this._onDidChange.fire(undefined); + }), + this.splitview.onDidAddView(() => { + this._onDidChange.fire(); + }), + this.splitview.onDidRemoveView(() => { + this._onDidChange.fire(); }) ); } diff --git a/packages/dockview/src/paneview/paneviewComponent.ts b/packages/dockview/src/paneview/paneviewComponent.ts index 53503b30a..69e2a96b0 100644 --- a/packages/dockview/src/paneview/paneviewComponent.ts +++ b/packages/dockview/src/paneview/paneviewComponent.ts @@ -128,9 +128,10 @@ export interface IPaneviewComponent extends IDisposable { readonly height: number; readonly minimumSize: number; readonly maximumSize: number; + readonly onDidDrop: Event; + readonly onDidLayoutChange: Event; addPanel(options: AddPaneviewCompponentOptions): IDisposable; layout(width: number, height: number): void; - onDidLayoutChange: Event; toJSON(): SerializedPaneview; fromJSON( serializedPaneview: SerializedPaneview, @@ -142,6 +143,7 @@ export interface IPaneviewComponent extends IDisposable { removePanel(panel: IPaneviewPanel): void; getPanel(id: string): IPaneviewPanel | undefined; movePanel(from: number, to: number): void; + updateOptions(options: Partial): void; } export class PaneviewComponent @@ -199,12 +201,20 @@ export class PaneviewComponent : this.paneview.orthogonalSize; } + private _options: PaneviewComponentOptions; + + get options() { + return this._options; + } + constructor( private element: HTMLElement, - private readonly options: PaneviewComponentOptions + options: PaneviewComponentOptions ) { super(); + this._options = options; + if (!options.components) { options.components = {}; } @@ -224,6 +234,10 @@ export class PaneviewComponent // } + updateOptions(options: Partial): void { + this._options = { ...this.options, ...options }; + } + addPanel(options: AddPaneviewCompponentOptions): IDisposable { const body = createComponent( options.id, @@ -269,17 +283,17 @@ export class PaneviewComponent disableDnd: !!this.options.disableDnd, }); - view.onDidDrop((event) => { - this._onDidDrop.fire(event); - }); + const disposable = new CompositeDisposable( + view.onDidDrop((event) => { + this._onDidDrop.fire(event); + }) + ); const size: Sizing | number = typeof options.size === 'number' ? options.size : Sizing.Distribute; const index = typeof options.index === 'number' ? options.index : undefined; - this.paneview.addPane(view, size, index); - view.init({ params: options.params || {}, minimumBodySize: options.minimumBodySize, @@ -289,13 +303,11 @@ export class PaneviewComponent containerApi: new PaneviewApi(this), }); + this.paneview.addPane(view, size, index); + view.orientation = this.paneview.orientation; - return { - dispose: () => { - // - }, - }; + return disposable; } getPanels(): PaneviewPanel[] { diff --git a/packages/dockview/src/react/paneview/paneview.tsx b/packages/dockview/src/react/paneview/paneview.tsx index d499b5a27..4e6fb48c9 100644 --- a/packages/dockview/src/react/paneview/paneview.tsx +++ b/packages/dockview/src/react/paneview/paneview.tsx @@ -62,6 +62,18 @@ export const PaneviewReact = React.forwardRef( }; }, [props.disableAutoResizing]); + React.useEffect(() => { + paneviewRef.current?.updateOptions({ + frameworkComponents: props.components, + }); + }, [props.components]); + + React.useEffect(() => { + paneviewRef.current?.updateOptions({ + headerframeworkComponents: props.headerComponents, + }); + }, [props.headerComponents]); + React.useEffect(() => { const createComponent = ( id: string, @@ -90,12 +102,6 @@ export const PaneviewReact = React.forwardRef( const api = new PaneviewApi(paneview); - const disposable = paneview.onDidDrop((event) => { - if (props.onDidDrop) { - props.onDidDrop({ event, api }); - } - }); - const { clientWidth, clientHeight } = domRef.current!; paneview.layout(clientWidth, clientHeight); @@ -106,11 +112,30 @@ export const PaneviewReact = React.forwardRef( paneviewRef.current = paneview; return () => { - disposable.dispose(); paneview.dispose(); }; }, []); + React.useEffect(() => { + if (!paneviewRef.current) { + return () => { + // + }; + } + + const paneview = paneviewRef.current; + + const disposable = paneview.onDidDrop((event) => { + if (props.onDidDrop) { + props.onDidDrop({ event, api: new PaneviewApi(paneview) }); + } + }); + + return () => { + disposable.dispose(); + }; + }, [props.onDidDrop]); + return (