Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 17 additions & 12 deletions packages/dashboard/src/DashboardLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import type {
import Log from '@deephaven/log';
import { usePrevious } from '@deephaven/react-hooks';
import { RootState } from '@deephaven/redux';
import { Provider, useDispatch, useSelector, useStore } from 'react-redux';
import { useDispatch, useSelector } from 'react-redux';
import PanelManager, { ClosedPanels } from './PanelManager';
import PanelErrorBoundary from './PanelErrorBoundary';
import LayoutUtils from './layout/LayoutUtils';
Expand Down Expand Up @@ -91,10 +91,12 @@ export function DashboardLayout({
(data as DashboardData)?.closed ?? []
);
const [isDashboardInitialized, setIsDashboardInitialized] = useState(false);
const [layoutChildren, setLayoutChildren] = useState(
layout.getReactChildren()
);

const hydrateMap = useMemo(() => new Map(), []);
const dehydrateMap = useMemo(() => new Map(), []);
const store = useStore();
const registerComponent = useCallback(
(
name: string,
Expand Down Expand Up @@ -123,15 +125,10 @@ export function DashboardLayout({
// eslint-disable-next-line react/prop-types
const { glContainer, glEventHub } = props;
return (
<Provider store={store}>
<PanelErrorBoundary
glContainer={glContainer}
glEventHub={glEventHub}
>
{/* eslint-disable-next-line react/jsx-props-no-spreading */}
<CType {...props} ref={ref} />
</PanelErrorBoundary>
</Provider>
<PanelErrorBoundary glContainer={glContainer} glEventHub={glEventHub}>
{/* eslint-disable-next-line react/jsx-props-no-spreading */}
<CType {...props} ref={ref} />
</PanelErrorBoundary>
);
}

Expand All @@ -141,7 +138,7 @@ export function DashboardLayout({
dehydrateMap.set(name, componentDehydrate);
return cleanup;
},
[hydrate, dehydrate, hydrateMap, dehydrateMap, layout, store]
[hydrate, dehydrate, hydrateMap, dehydrateMap, layout]
);
const hydrateComponent = useCallback(
(name, props) => (hydrateMap.get(name) ?? FALLBACK_CALLBACK)(props, id),
Expand Down Expand Up @@ -206,6 +203,8 @@ export function DashboardLayout({
setLastConfig(dehydratedLayoutConfig);

onLayoutChange(dehydratedLayoutConfig);

setLayoutChildren(layout.getReactChildren());
}
}, [
dehydrateComponent,
Expand Down Expand Up @@ -254,6 +253,10 @@ export function DashboardLayout({
item.element.addClass(cssClass);
}, []);

const handleReactChildrenChange = useCallback(() => {
setLayoutChildren(layout.getReactChildren());
}, [layout]);

useListener(layout, 'stateChanged', handleLayoutStateChanged);
useListener(layout, 'itemPickedUp', handleLayoutItemPickedUp);
useListener(layout, 'itemDropped', handleLayoutItemDropped);
Expand All @@ -263,6 +266,7 @@ export function DashboardLayout({
PanelEvent.TITLE_CHANGED,
handleLayoutStateChanged
);
useListener(layout, 'reactChildrenChanged', handleReactChildrenChange);

const previousLayoutConfig = usePrevious(layoutConfig);
useEffect(
Expand Down Expand Up @@ -302,6 +306,7 @@ export function DashboardLayout({
return (
<>
{isDashboardEmpty && emptyDashboard}
{layoutChildren}
{React.Children.map(children, child =>
child != null
? React.cloneElement(child as ReactElement, {
Expand Down
14 changes: 2 additions & 12 deletions packages/dashboard/src/layout/LayoutUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -518,12 +518,7 @@ class LayoutUtils {
}
if (replaceExisting && oldContentItem) {
const index = stack.contentItems.indexOf(oldContentItem);

// Using remove/add here instead of replaceChild because I was getting errors with replaceChild... should be the same.
// Add first so that the stack doesn't get screwed up
stack.addChild(config, index + 1);
stack.removeChild(oldContentItem);

stack.replaceChild(oldContentItem, config);
stack.setActiveContentItem(stack.contentItems[index]);
} else {
stack.addChild(config);
Expand Down Expand Up @@ -561,12 +556,7 @@ class LayoutUtils {

if (replaceExisting && oldContentItem && stack) {
const index = stack?.contentItems.indexOf(oldContentItem);

// Using remove/add here instead of replaceChild because I was getting errors with replaceChild... should be the same.
// Add first so that the stack doesn't get screwed up
stack.addChild(config, index + 1);
stack.removeChild(oldContentItem);

stack.replaceChild(oldContentItem, config);
stack.setActiveContentItem(stack.contentItems[index]);
} else {
stack?.addChild(config);
Expand Down
35 changes: 35 additions & 0 deletions packages/golden-layout/src/LayoutManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ export default class LayoutManager extends EventEmitter {
private _dragSources: DragSource[] = [];
private _updatingColumnsResponsive = false;
private _firstLoad = true;
private _reactChildMap = new Map<string, React.ReactNode>();
private _reactChildren: React.ReactNode = null;

width: number | null = null;
height: number | null = null;
Expand Down Expand Up @@ -369,6 +371,39 @@ export default class LayoutManager extends EventEmitter {
this.emit('initialised');
}

/**
* Adds a react child to the layout manager
* @param id Unique panel id
* @param element The React element
*/
addReactChild(id: string, element: React.ReactNode) {
this._reactChildMap.set(id, element);
this._reactChildren = [...this._reactChildMap.values()];
this.emit('reactChildrenChanged');
}

/**
* Removes a react child from the layout manager
* @param id Unique panel id
*/
removeReactChild(id: string) {
this._reactChildMap.delete(id);
this._reactChildren = [...this._reactChildMap.values()];
this.emit('reactChildrenChanged');
}

/**
* Gets the react children in the layout
*
* Used in @deephaven/dashboard to mount the react elements
* inside the app's React tree
*
* @returns The react children to mount for this layout manager
*/
getReactChildren() {
return this._reactChildren;
}

Comment thread
mattrunyon marked this conversation as resolved.
/**
* Updates the layout managers size
* @param width width in pixels
Expand Down
2 changes: 1 addition & 1 deletion packages/golden-layout/src/items/AbstractContentItem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ export default abstract class AbstractContentItem extends EventEmitter {
*/
replaceChild(
oldChild: AbstractContentItem,
newChild: AbstractContentItem,
newChild: AbstractContentItem | ItemConfigType,
_$destroyOldChild = false
) {
newChild = this.layoutManager._$normalizeContentItem(newChild);
Expand Down
6 changes: 5 additions & 1 deletion packages/golden-layout/src/items/RowOrColumn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,11 @@ export default class RowOrColumn extends AbstractContentItem {
* @param oldChild
* @param newChild
*/
replaceChild(oldChild: AbstractContentItem, newChild: AbstractContentItem) {
replaceChild(
oldChild: AbstractContentItem,
newChild: AbstractContentItem | ItemConfigType
) {
newChild = this.layoutManager._$normalizeContentItem(newChild, this);
var size = oldChild.config[this._dimension];
super.replaceChild(oldChild, newChild);
newChild.config[this._dimension] = size;
Expand Down
34 changes: 32 additions & 2 deletions packages/golden-layout/src/utils/ReactComponentHandler.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,44 @@ export default class ReactComponentHandler {
this._container.on('destroy', this._destroy, this);
}

/**
* Gets the unique key to use for the react component
* @returns Unique key for the component
*/
_key(): string {
const id = this._container._config.id;
if (!id) {
throw new Error('Cannot mount panel without id');
}

// If addId is called multiple times, an element can have multiple IDs in golden-layout
// We don't use it, but changing the type requires many changes and a separate PR
if (Array.isArray(id)) {
return id.join(',');
}
Comment thread
mattrunyon marked this conversation as resolved.

return id;
}

/**
* Creates the react class and component and hydrates it with
* the initial state - if one is present
*
* By default, react's getInitialState will be used
*
* Creates a portal so the non-react golden-layout code still works,
* but also allows us to mount the React components in the app's tree
* instead of separate sibling root trees
*/
_render() {
ReactDOM.render(this._getReactComponent(), this._container.getElement()[0]);
this._container.layoutManager.addReactChild(
this._key(),
Comment thread
mattrunyon marked this conversation as resolved.
Outdated
ReactDOM.createPortal(
this._getReactComponent(),
this._container.getElement()[0],
this._key()
Comment thread
mattrunyon marked this conversation as resolved.
Outdated
)
);
}

/**
Expand Down Expand Up @@ -67,7 +97,7 @@ export default class ReactComponentHandler {
* Removes the component from the DOM and thus invokes React's unmount lifecycle
*/
_destroy() {
ReactDOM.unmountComponentAtNode(this._container.getElement()[0]);
this._container.layoutManager.removeReactChild(this._key());
this._container.off('open', this._render, this);
this._container.off('destroy', this._destroy, this);
}
Expand Down