FlexLayout
FlexLayout copied to clipboard
RFC: New system for popout windows
The current system for popout windows is a bit flawed for a few reasons:
- You can't control the lifecycle of the windows or how they are opened
- You can't render custom content in the window
- You don't have any access to the window from outside
- You can't control what is displayed in a panel while a popout exists
I am opening this issue to propose a new solution that will solve all of these issues while preserving backwards compatibility with existing consumers of flexlayout. Most importantly, I am open to implementing this all in a pull request if you approve of the design. So you will not have to worry :wink:
The layout will get a new prop, popupFactory
, that will default to flexlayout's current implementation. Consumers can override this prop to use their own implementation for popout windows. When a panel is popped out, the component factory will be called with these arguments:
-
node
for the TabNode whose content is being rendered; -
content
for the actual content of the panel; -
close
, a callback for closing the popout window early (to be called when someone clicks the close button of the window, or when someone clicks "dock window" in the panel). This will unmount the custom component immediately as the panel will no longer be popped out
Crucially, the returned component will not be rendered in addition to the frame, but inside of it, completely replacing the panel's content just like flexlayout currently does with "This panel is show in a floating window" (that will become the default implementation). Flexlayout will not use window.open
at all, but instead rely on the component to do so itself using the contents
that were passed to the popout factory. Flexlayout's default implementation will use window.open
in conjunction with the existing props on layout that control how said window is opened.
Backwards compatibility is maintained because the default behavior will be identical to the existing behavior. There will just be an opportunity to replace that default behavior. The existing props like popoutURL
will still be used to control the default implementation.
I am proposing this solution because I want to support popouts for an application I'm working on, however:
- It is not running in a browser with chrome, it is running in a borderless Electron context, which means that I need to add a custom titlebar to the window, which is impossible with the current implementation;
- I need to control where the panel content is rendered within the window, which is also currently impossible;
- I want to customize the content that shows inside a panel while it is popped out;
- Panels in this application require support from CSS - since the new window is a different document, things like dark mode classes applied to the body don't work
I could bundle all supporting machinery in the panel, but that would be kind of weird if the panel suddenly gained a titlebar, a component that modified body
, and so on all of a sudden just from being in a different document. I would feel much better if the popout window itself contained these.
The proposed solution will solve all of those issues by giving the user full control over:
- How windows are opened
- What is rendered inside of them
- The quality of the implementation (my window implementation handles hot reloading of styles)
- What shows inside the panel whose content is in a popup window
It doesn't even need to be implemented with native window.open
- since flexlayout does not worry about the implementation details, a lot more things become possible.
Additionally, implementation in flexlayout will become much simpler, as whether or not a panel is popped out can be represented using a reference to the node returned by the popoutFactory
. Flexlayout will not need to worry about windows at all, only where and when this node is mounted/unmounted.
I will say that it is already possible to implement this, given enough effort. You would add a custom button using onRenderTabSet (rather than using flexlayout's existing popout system), and wrap every single component in your custom popout creater that renders the contents into a new window if it is popped out.
However, that requires turning off tabEnableRenderOnDemand
to support opening all popouts when a layout is first loaded. Either that, or adding a new API to control demand of tabs. (Perhaps that should be added anyway? Let me know if you would like this.)
Please let me know what you think and if you would be interested in having this functionality added to flexlayout.
A variation on this is to have a callback, say onRenderPopoutTab, with the same args, that simply wraps the tab content in a container with the toolbar and returns that wrapped object to be rendered in the window.
But with your solution you can also control how to open the window if you need to, which may be needed with Electron apps.
I assume the default implementation would be available so you could just wrap the tab content like that anyway.
e.g
function popoutFactory (node, content, onClose) {
return (
<DefaultPopoutWindow node={node} onClose={onClose} >
<PanelWithToobar>
{content}
</PanelWithToobar>
</DefaultPopoutWindow>
);
}
Also note there is an existing callback (onRenderFloatingTabPlaceholder, added recently) for rendering the popout placeholder
A variation on this is to have a callback, say onRenderPopoutTab, with the same args, that simply wraps the tab content in a container with the toolbar and returns that wrapped object to be rendered in the window.
But with your solution you can also control how to open the window if you need to, which may be needed with Electron apps.
Yeah, I think in this case it is better to control the whole thing rather than only specific elements of it.
I assume the default implementation would be available so you could just wrap the tab content like that anyway.
e.g
function popoutFactory (node, content, onClose) { return ( <DefaultPopoutWindow node={node} onClose={onClose} > <PanelWithToobar> {content} </PanelWithToobar> </DefaultPopoutWindow> ); }
It would be more like:
function popoutFactory(node: TabNode, contents: any, onClose: () => void): any {
return <>
<YourCustomPlaceholder onClose={onClose} />
<YourCustomWindow node={node} contents={contents} onClose={onClose} />
</>
}
since popoutFactory
would be expected to render the placeholder inside the panel as well. YourCustomWindow
wouldn't render anything to the tab but instead open a new window to render contents
inside.
Also note there is an existing callback (onRenderFloatingTabPlaceholder, added recently) for rendering the popout placeholder
This could control the default implementation, like popoutURL
in the original proposal. It would be relevant if you aren't making a custom popout factory, but if you are, you're more likely to want all popout stuff in the same place.
Additionally, with this method you can hook YourCustomPlaceholder
and YourCustomWindow
up (i.e. provide link to focus the window like what flexlayout does now). For onRenderFloatingTabPlaceholder
I'm not sure how flexlayout would extract that info from popoutFactory
(showWindow
namely), so it would likely only affect the default implementation.