diff --git a/js_modules/dagster-ui/packages/ui-components/src/components/ConfigEditorWithSchema.tsx b/js_modules/dagster-ui/packages/ui-components/src/components/ConfigEditorWithSchema.tsx index 97baf799c8482..986cc12d607df 100644 --- a/js_modules/dagster-ui/packages/ui-components/src/components/ConfigEditorWithSchema.tsx +++ b/js_modules/dagster-ui/packages/ui-components/src/components/ConfigEditorWithSchema.tsx @@ -4,7 +4,7 @@ import {createGlobalStyle} from 'styled-components'; import {Box} from './Box'; import {ConfigEditorHandle, ConfigSchema, NewConfigEditor} from './NewConfigEditor'; import {Spinner} from './Spinner'; -import {SplitPanelContainer} from './SplitPanelContainer'; +import {SplitPanelContainer, SplitPanelContainerHandle} from './SplitPanelContainer'; import {ConfigEditorHelp} from './configeditor/ConfigEditorHelp'; import {isHelpContextEqual} from './configeditor/isHelpContextEqual'; import {ConfigEditorHelpContext} from './configeditor/types/ConfigEditorHelpContext'; @@ -32,7 +32,7 @@ export const ConfigEditorWithSchema = ({ onConfigChange, configSchema, }: Props) => { - const editorSplitPanelContainer = useRef(null); + const editorSplitPanelContainer = useRef(null); const [editorHelpContext, setEditorHelpContext] = useState(null); const editor = useRef(null); diff --git a/js_modules/dagster-ui/packages/ui-components/src/components/SplitPanelContainer.tsx b/js_modules/dagster-ui/packages/ui-components/src/components/SplitPanelContainer.tsx index 37c27bf55cf93..c0725f9300440 100644 --- a/js_modules/dagster-ui/packages/ui-components/src/components/SplitPanelContainer.tsx +++ b/js_modules/dagster-ui/packages/ui-components/src/components/SplitPanelContainer.tsx @@ -1,9 +1,7 @@ -import * as React from 'react'; +import {forwardRef, useCallback, useImperativeHandle, useRef, useState} from 'react'; import styled from 'styled-components'; -import {Button} from './Button'; import {Colors} from './Color'; -import {Icon} from './Icon'; const DIVIDER_THICKNESS = 2; @@ -13,110 +11,124 @@ interface SplitPanelContainerProps { first: React.ReactNode; firstInitialPercent: number; firstMinSize?: number; + secondMinSize?: number; second: React.ReactNode | null; // Note: pass null to hide / animate away the second panel } -interface SplitPanelContainerState { - size: number; - key: string; - resizing: boolean; -} - -export class SplitPanelContainer extends React.Component< - SplitPanelContainerProps, - SplitPanelContainerState -> { - constructor(props: SplitPanelContainerProps) { - super(props); - - const key = `dagster.panel-width.${this.props.identifier}`; - const value = window.localStorage.getItem(key); - let size = Number(value); - if (value === null || isNaN(size)) { - size = this.props.firstInitialPercent; - } - - this.state = {size, key, resizing: false}; - } +export type SplitPanelContainerHandle = { + getSize: () => number; + changeSize: (value: number) => void; +}; - onChangeSize = (size: number) => { - this.setState({size}); - window.localStorage.setItem(this.state.key, `${size}`); - }; +export const SplitPanelContainer = forwardRef( + (props, ref) => { + const { + axis = 'horizontal', + identifier, + first, + firstInitialPercent, + firstMinSize, + secondMinSize = 0, + second, + } = props; + + const [_, setTrigger] = useState(0); + const [resizing, setResizing] = useState(false); + const key = `dagster.panel-width.${identifier}`; + + const getSize = useCallback(() => { + if (!second) { + return 100; + } + const storedSize = window.localStorage.getItem(key); + const parsed = storedSize === null ? null : parseFloat(storedSize); + return parsed === null || isNaN(parsed) ? firstInitialPercent : parsed; + }, [firstInitialPercent, key, second]); + + const onChangeSize = useCallback( + (newValue: number) => { + window.localStorage.setItem(key, `${newValue}`); + setTrigger((current) => (current ? 0 : 1)); + }, + [key], + ); - getSize = () => { - return this.state.size; - }; + useImperativeHandle(ref, () => ({getSize, changeSize: onChangeSize}), [onChangeSize, getSize]); - render() { - const {firstMinSize, first, second} = this.props; - const {size: _size, resizing} = this.state; - const axis = this.props.axis || 'horizontal'; + const firstSize = getSize(); const firstPaneStyles: React.CSSProperties = {flexShrink: 0}; - const firstSize = second ? _size : 100; // Note: The divider appears after the first panel, so making the first panel 100% wide // hides the divider offscreen. To prevent this, we subtract the divider depth. if (axis === 'horizontal') { firstPaneStyles.minWidth = firstMinSize; - firstPaneStyles.width = `calc(${firstSize}% - ${DIVIDER_THICKNESS}px)`; + firstPaneStyles.width = `calc(${firstSize / 100} * (100% - ${ + DIVIDER_THICKNESS + (second ? secondMinSize : 0) + }px))`; } else { firstPaneStyles.minHeight = firstMinSize; - firstPaneStyles.height = `calc(${firstSize}% - ${DIVIDER_THICKNESS}px)`; + firstPaneStyles.height = `calc(${firstSize / 100} * (100% - ${ + DIVIDER_THICKNESS + (second ? secondMinSize : 0) + }px))`; } return ( - +
{first}
this.setState({resizing})} - onMove={this.onChangeSize} + secondMinSize={secondMinSize} + onSetResizing={setResizing} + onMove={onChangeSize} />
{second}
); - } -} + }, +); interface IDividerProps { axis: 'horizontal' | 'vertical'; resizing: boolean; + secondMinSize: number; onSetResizing: (resizing: boolean) => void; onMove: (vw: number) => void; } -class PanelDivider extends React.Component { - ref = React.createRef(); +const PanelDivider = (props: IDividerProps) => { + const {axis, resizing, secondMinSize, onSetResizing, onMove} = props; + const ref = useRef(null); - onMouseDown = (e: React.MouseEvent) => { + const onMouseDown = (e: React.MouseEvent) => { e.preventDefault(); - this.props.onSetResizing(true); + onSetResizing(true); const onMouseMove = (event: MouseEvent) => { - const parent = this.ref.current?.closest('#split-panel-container'); + const parent = ref.current?.closest('.split-panel-container'); if (!parent) { return; } const parentRect = parent.getBoundingClientRect(); const firstPanelPercent = - this.props.axis === 'horizontal' - ? ((event.clientX - parentRect.left) * 100) / parentRect.width - : ((event.clientY - parentRect.top) * 100) / parentRect.height; + axis === 'horizontal' + ? ((event.clientX - parentRect.left) * 100) / + (parentRect.width - secondMinSize - DIVIDER_THICKNESS) + : ((event.clientY - parentRect.top) * 100) / + (parentRect.height - secondMinSize - DIVIDER_THICKNESS); - this.props.onMove(Math.min(100, Math.max(0, firstPanelPercent))); + onMove(Math.min(100, Math.max(0, firstPanelPercent))); }; const onMouseUp = () => { - this.props.onSetResizing(false); + onSetResizing(false); document.removeEventListener('mousemove', onMouseMove); document.removeEventListener('mouseup', onMouseUp); }; @@ -124,109 +136,60 @@ class PanelDivider extends React.Component { document.addEventListener('mouseup', onMouseUp); }; - render() { - const Wrapper = DividerWrapper[this.props.axis]; - const HitArea = DividerHitArea[this.props.axis]; - return ( - - - + const hitArea = + axis === 'horizontal' ? ( + + ) : ( + ); - } -} -interface PanelToggleProps { - axis: 'horizontal' | 'vertical'; - container: React.RefObject; -} - -// Todo: This component attempts to sync itself with the container, but it can't -// observe the container's width without a React context or adding a listener, etc. -// If we keep making components that manipulate panel state we may want to move it -// all to a context consumed by both. -// -export const SecondPanelToggle = ({container, axis}: PanelToggleProps) => { - const [prevSize, setPrevSize] = React.useState('unknown'); - const initialIsOpen = (container.current?.state.size || 0) < 100; - - const [open, setOpen] = React.useState(initialIsOpen); - React.useEffect(() => setOpen(initialIsOpen), [initialIsOpen]); - - return ( - - + {pipeline.tags.length || tagsFromSession.length ? ( { ) : null} { /> } + secondMinSize={240} /> diff --git a/js_modules/dagster-ui/packages/ui-core/src/runs/Run.tsx b/js_modules/dagster-ui/packages/ui-core/src/runs/Run.tsx index 9d9d0de7b7fd8..e3d3cc0df5ec6 100644 --- a/js_modules/dagster-ui/packages/ui-core/src/runs/Run.tsx +++ b/js_modules/dagster-ui/packages/ui-core/src/runs/Run.tsx @@ -6,6 +6,7 @@ import { Icon, NonIdealState, SplitPanelContainer, + SplitPanelContainerHandle, Tooltip, } from '@dagster-io/ui-components'; import * as React from 'react'; @@ -249,35 +250,33 @@ const RunWithData = ({ onSetSelectionQuery(newSelected.join(', ') || '*'); }; - const [splitPanelContainer, setSplitPanelContainer] = React.useState( - null, - ); + const [expandedPanel, setExpandedPanel] = React.useState(null); + const containerRef = React.useRef(null); + React.useEffect(() => { - const initialSize = splitPanelContainer?.getSize(); - switch (initialSize) { - case 100: + if (containerRef.current) { + const size = containerRef.current.getSize(); + if (size === 100) { setExpandedPanel('top'); - return; - case 0: + } else if (size === 0) { setExpandedPanel('bottom'); - return; + } } - }, [splitPanelContainer]); + }, []); - const [expandedPanel, setExpandedPanel] = React.useState(null); const isTopExpanded = expandedPanel === 'top'; const isBottomExpanded = expandedPanel === 'bottom'; const expandBottomPanel = () => { - splitPanelContainer?.onChangeSize(0); + containerRef.current?.changeSize(0); setExpandedPanel('bottom'); }; const expandTopPanel = () => { - splitPanelContainer?.onChangeSize(100); + containerRef.current?.changeSize(100); setExpandedPanel('top'); }; const resetPanels = () => { - splitPanelContainer?.onChangeSize(50); + containerRef.current?.changeSize(50); setExpandedPanel(null); }; @@ -331,14 +330,13 @@ const RunWithData = ({ return ( <> { - setSplitPanelContainer(container); - }} + ref={containerRef} axis="vertical" identifier="run-gantt" firstInitialPercent={35} firstMinSize={56} first={gantt(metadata)} + secondMinSize={56} second={