diff --git a/public/app/core/components/ThreePaneSplit/SplitPaneWrapper.tsx b/public/app/core/components/ThreePaneSplit/SplitPaneWrapper.tsx new file mode 100644 index 00000000000..8f5fa8cedb0 --- /dev/null +++ b/public/app/core/components/ThreePaneSplit/SplitPaneWrapper.tsx @@ -0,0 +1,177 @@ +import React, { createRef, MutableRefObject, PureComponent, ReactNode } from 'react'; +import SplitPane from 'react-split-pane'; +import { css, cx } from 'emotion'; +import { GrafanaTheme } from '@grafana/data'; +import { stylesFactory } from '@grafana/ui'; +import { config } from 'app/core/config'; + +enum Pane { + Right, + Top, +} + +interface Props { + leftPaneComponents: ReactNode[] | ReactNode; + rightPaneComponents: ReactNode; + uiState: { topPaneSize: number; rightPaneSize: number }; + rightPaneVisible?: boolean; + updateUiState: (uiState: { topPaneSize?: number; rightPaneSize?: number }) => void; +} + +export class SplitPaneWrapper extends PureComponent { + rafToken = createRef(); + static defaultProps = { + rightPaneVisible: true, + }; + + componentDidMount() { + window.addEventListener('resize', this.updateSplitPaneSize); + } + + componentWillUnmount() { + window.removeEventListener('resize', this.updateSplitPaneSize); + } + + updateSplitPaneSize = () => { + if (this.rafToken.current !== undefined) { + window.cancelAnimationFrame(this.rafToken.current!); + } + (this.rafToken as MutableRefObject).current = window.requestAnimationFrame(() => { + this.forceUpdate(); + }); + }; + + onDragFinished = (pane: Pane, size?: number) => { + document.body.style.cursor = 'auto'; + + // When the drag handle is just clicked size is undefined + if (!size) { + return; + } + + const { updateUiState } = this.props; + if (pane === Pane.Top) { + updateUiState({ + topPaneSize: size / window.innerHeight, + }); + } else { + updateUiState({ + rightPaneSize: size / window.innerWidth, + }); + } + }; + + onDragStarted = () => { + document.body.style.cursor = 'row-resize'; + }; + + renderHorizontalSplit() { + const { leftPaneComponents, uiState } = this.props; + const styles = getStyles(config.theme); + const topPaneSize = + uiState.topPaneSize >= 1 ? (uiState.topPaneSize as number) : (uiState.topPaneSize as number) * window.innerHeight; + + /* + Guesstimate the height of the browser window minus + panel toolbar and editor toolbar (~120px). This is to prevent resizing + the preview window beyond the browser window. + */ + const maxHeight = window.innerHeight - 120; + + if (Array.isArray(leftPaneComponents)) { + return ( + this.onDragFinished(Pane.Top, size)} + > + {leftPaneComponents} + + ); + } + + return leftPaneComponents; + } + + render() { + const { rightPaneVisible, rightPaneComponents, uiState } = this.props; + // Limit options pane width to 90% of screen. + const maxWidth = window.innerWidth * 0.9; + const styles = getStyles(config.theme); + + // Need to handle when width is relative. ie a percentage of the viewport + const rightPaneSize = + uiState.rightPaneSize <= 1 + ? (uiState.rightPaneSize as number) * window.innerWidth + : (uiState.rightPaneSize as number); + + if (!rightPaneVisible) { + return this.renderHorizontalSplit(); + } + + return ( + = 300 ? rightPaneSize : 300} + primary="second" + resizerClassName={styles.resizerV} + onDragStarted={() => (document.body.style.cursor = 'col-resize')} + onDragFinished={size => this.onDragFinished(Pane.Right, size)} + > + {this.renderHorizontalSplit()} + {rightPaneComponents} + + ); + } +} + +const getStyles = stylesFactory((theme: GrafanaTheme) => { + const handleColor = theme.palette.blue95; + const paneSpacing = theme.spacing.md; + + const resizer = css` + font-style: italic; + background: transparent; + border-top: 0; + border-right: 0; + border-bottom: 0; + border-left: 0; + border-color: transparent; + border-style: solid; + transition: 0.2s border-color ease-in-out; + + &:hover { + border-color: ${handleColor}; + } + `; + + return { + resizerV: cx( + resizer, + css` + cursor: col-resize; + width: ${paneSpacing}; + border-right-width: 1px; + margin-top: 18px; + ` + ), + resizerH: cx( + resizer, + css` + height: ${paneSpacing}; + cursor: row-resize; + position: relative; + top: 0px; + z-index: 1; + border-top-width: 1px; + margin-left: ${paneSpacing}; + ` + ), + }; +}); diff --git a/public/app/features/dashboard/components/PanelEditor/PanelEditor.tsx b/public/app/features/dashboard/components/PanelEditor/PanelEditor.tsx index ad9fc90b77e..fb767f63134 100644 --- a/public/app/features/dashboard/components/PanelEditor/PanelEditor.tsx +++ b/public/app/features/dashboard/components/PanelEditor/PanelEditor.tsx @@ -1,7 +1,6 @@ -import React, { createRef, MutableRefObject, PureComponent } from 'react'; +import React, { PureComponent } from 'react'; import { connect, MapDispatchToProps, MapStateToProps } from 'react-redux'; import AutoSizer from 'react-virtualized-auto-sizer'; -import SplitPane from 'react-split-pane'; import { css, cx } from 'emotion'; import { Unsubscribable } from 'rxjs'; @@ -19,6 +18,7 @@ import { OptionsPaneContent } from './OptionsPaneContent'; import { DashNavButton } from 'app/features/dashboard/components/DashNav/DashNavButton'; import { SubMenuItems } from 'app/features/dashboard/components/SubMenu/SubMenuItems'; import { BackButton } from 'app/core/components/BackButton/BackButton'; +import { SplitPaneWrapper } from 'app/core/components/ThreePaneSplit/SplitPaneWrapper'; import { SaveDashboardModalProxy } from '../SaveDashboard/SaveDashboardModalProxy'; import { DashboardPanel } from '../../dashgrid/DashboardPanel'; @@ -65,28 +65,14 @@ type Props = OwnProps & ConnectedProps & DispatchProps; export class PanelEditorUnconnected extends PureComponent { querySubscription: Unsubscribable; - rafToken = createRef(); componentDidMount() { this.props.initPanelEditor(this.props.sourcePanel, this.props.dashboard); - - window.addEventListener('resize', this.updateSplitPaneSize); } - componentWillUnmount() { this.props.panelEditorCleanUp(); - window.removeEventListener('resize', this.updateSplitPaneSize); } - updateSplitPaneSize = () => { - if (this.rafToken.current !== undefined) { - window.cancelAnimationFrame(this.rafToken.current!); - } - (this.rafToken as MutableRefObject).current = window.requestAnimationFrame(() => { - this.forceUpdate(); - }); - }; - onPanelExit = () => { this.props.updateLocation({ query: { editPanel: null, tab: null }, @@ -138,30 +124,6 @@ export class PanelEditorUnconnected extends PureComponent { this.forceUpdate(); }; - onDragFinished = (pane: Pane, size?: number) => { - document.body.style.cursor = 'auto'; - - // When the drag handle is just clicked size is undefined - if (!size) { - return; - } - - const { updatePanelEditorUIState } = this.props; - if (pane === Pane.Top) { - updatePanelEditorUIState({ - topPaneSize: size / window.innerHeight, - }); - } else { - updatePanelEditorUIState({ - rightPaneSize: size / window.innerWidth, - }); - } - }; - - onDragStarted = () => { - document.body.style.cursor = 'row-resize'; - }; - onDisplayModeChange = (mode: DisplayMode) => { const { updatePanelEditorUIState } = this.props; updatePanelEditorUIState({ @@ -177,7 +139,7 @@ export class PanelEditorUnconnected extends PureComponent { renderPanel = (styles: EditorStyles) => { const { dashboard, panel, tabs, uiState } = this.props; return ( -
+
{this.renderPanelToolbar(styles)}
@@ -205,38 +167,22 @@ export class PanelEditorUnconnected extends PureComponent { ); }; - renderHorizontalSplit(styles: EditorStyles) { - const { dashboard, panel, tabs, uiState } = this.props; - /* - Guesstimate the height of the browser window minus - panel toolbar and editor toolbar (~120px). This is to prevent resizing - the preview window beyond the browser window. - */ - const windowHeight = window.innerHeight - 120; - const size = uiState.topPaneSize >= 1 ? uiState.topPaneSize : (uiState.topPaneSize as number) * window.innerHeight; + renderPanelAndEditor(styles: EditorStyles) { + const { panel, dashboard, tabs } = this.props; - return tabs.length > 0 ? ( - this.onDragFinished(Pane.Top, size)} - > - {this.renderPanel(styles)} -
+ if (tabs.length > 0) { + return [ + this.renderPanel(styles), +
-
- - ) : ( - this.renderPanel(styles) - ); +
, + ]; + } + return this.renderPanel(styles); } renderTemplateVariables(styles: EditorStyles) { @@ -319,8 +265,13 @@ export class PanelEditorUnconnected extends PureComponent { ); } - renderOptionsPane(width: number) { - const { plugin, dashboard, panel } = this.props; + renderOptionsPane() { + const { plugin, dashboard, panel, uiState } = this.props; + + const rightPaneSize = + uiState.rightPaneSize <= 1 + ? (uiState.rightPaneSize as number) * window.innerWidth + : (uiState.rightPaneSize as number); if (!plugin) { return
; @@ -331,7 +282,7 @@ export class PanelEditorUnconnected extends PureComponent { plugin={plugin} dashboard={dashboard} panel={panel} - width={width} + width={rightPaneSize} onClose={this.onTogglePanelOptions} onFieldConfigsChange={this.onFieldConfigChange} onPanelOptionsChanged={this.onPanelOptionsChanged} @@ -340,39 +291,8 @@ export class PanelEditorUnconnected extends PureComponent { ); } - renderWithOptionsPane(styles: EditorStyles) { - const { uiState } = this.props; - - // Limit options pane width to 90% of screen. - const maxWidth = window.innerWidth * 0.9; - - // Need to handle when width is relative. ie a percentage of the viewport - const width = - uiState.rightPaneSize <= 1 - ? (uiState.rightPaneSize as number) * window.innerWidth - : (uiState.rightPaneSize as number); - - return ( - = 300 ? width : 300} - primary="second" - /* Use persisted state for default size */ - defaultSize={uiState.rightPaneSize} - resizerClassName={styles.resizerV} - onDragStarted={() => (document.body.style.cursor = 'col-resize')} - onDragFinished={size => this.onDragFinished(Pane.Right, size)} - > - {this.renderHorizontalSplit(styles)} - {this.renderOptionsPane(width)} - - ); - } - render() { - const { initDone, uiState } = this.props; + const { initDone, updatePanelEditorUIState, uiState } = this.props; const styles = getStyles(config.theme, this.props); if (!initDone) { @@ -383,7 +303,13 @@ export class PanelEditorUnconnected extends PureComponent {
{this.editorToolbar(styles)}
- {uiState.isPanelOptionsVisible ? this.renderWithOptionsPane(styles) : this.renderHorizontalSplit(styles)} +
); @@ -416,35 +342,13 @@ const mapDispatchToProps: MapDispatchToProps = { export const PanelEditor = connect(mapStateToProps, mapDispatchToProps)(PanelEditorUnconnected); -enum Pane { - Right, - Top, -} - /* * Styles */ export const getStyles = stylesFactory((theme: GrafanaTheme, props: Props) => { const { uiState } = props; - const handleColor = theme.palette.blue95; const paneSpacing = theme.spacing.md; - const resizer = css` - font-style: italic; - background: transparent; - border-top: 0; - border-right: 0; - border-bottom: 0; - border-left: 0; - border-color: transparent; - border-style: solid; - transition: 0.2s border-color ease-in-out; - - &:hover { - border-color: ${handleColor}; - } - `; - return { wrapper: css` width: 100%; @@ -488,27 +392,6 @@ export const getStyles = stylesFactory((theme: GrafanaTheme, props: Props) => { width: 100%; padding-left: ${paneSpacing}; `, - resizerV: cx( - resizer, - css` - cursor: col-resize; - width: ${paneSpacing}; - border-right-width: 1px; - margin-top: 18px; - ` - ), - resizerH: cx( - resizer, - css` - height: ${paneSpacing}; - cursor: row-resize; - position: relative; - top: 0px; - z-index: 1; - border-top-width: 1px; - margin-left: ${paneSpacing}; - ` - ), tabsWrapper: css` height: 100%; width: 100%; diff --git a/public/app/features/dashboard/components/PanelEditor/state/actions.ts b/public/app/features/dashboard/components/PanelEditor/state/actions.ts index 52b26aeda17..2e17682d384 100644 --- a/public/app/features/dashboard/components/PanelEditor/state/actions.ts +++ b/public/app/features/dashboard/components/PanelEditor/state/actions.ts @@ -8,7 +8,7 @@ import { updateEditorInitState, } from './reducers'; import { cleanUpEditPanel, panelModelAndPluginReady } from '../../../state/reducers'; -import store from '../../../../../core/store'; +import store from 'app/core/store'; export function initPanelEditor(sourcePanel: PanelModel, dashboard: DashboardModel): ThunkResult { return dispatch => { diff --git a/public/app/features/dashboard/components/PanelEditor/state/reducers.ts b/public/app/features/dashboard/components/PanelEditor/state/reducers.ts index 68de6746aa0..da757ecca4f 100644 --- a/public/app/features/dashboard/components/PanelEditor/state/reducers.ts +++ b/public/app/features/dashboard/components/PanelEditor/state/reducers.ts @@ -17,9 +17,9 @@ export interface PanelEditorUIState { /* Visualization options pane visibility */ isPanelOptionsVisible: boolean; /* Pixels or percentage */ - rightPaneSize: number | string; + rightPaneSize: number; /* Pixels or percentage */ - topPaneSize: number | string; + topPaneSize: number; /* Visualization size mode */ mode: DisplayMode; }