diff --git a/public/app/features/dashboard/components/PanelEditor/PanelEditor.tsx b/public/app/features/dashboard/components/PanelEditor/PanelEditor.tsx index 0a1b02d37a1..ad9fc90b77e 100644 --- a/public/app/features/dashboard/components/PanelEditor/PanelEditor.tsx +++ b/public/app/features/dashboard/components/PanelEditor/PanelEditor.tsx @@ -1,38 +1,41 @@ -import React, { PureComponent } from 'react'; -import { FieldConfigSource, GrafanaTheme, PanelPlugin } from '@grafana/data'; -import { Button, HorizontalGroup, Icon, RadioButtonGroup, stylesFactory } from '@grafana/ui'; -import { css, cx } from 'emotion'; -import config from 'app/core/config'; -import AutoSizer from 'react-virtualized-auto-sizer'; - -import { PanelModel } from '../../state/PanelModel'; -import { DashboardModel } from '../../state/DashboardModel'; -import { DashboardPanel } from '../../dashgrid/DashboardPanel'; - -import SplitPane from 'react-split-pane'; -import { StoreState } from '../../../../types/store'; +import React, { createRef, MutableRefObject, PureComponent } from 'react'; import { connect, MapDispatchToProps, MapStateToProps } from 'react-redux'; -import { updateLocation } from '../../../../core/reducers/location'; +import AutoSizer from 'react-virtualized-auto-sizer'; +import SplitPane from 'react-split-pane'; +import { css, cx } from 'emotion'; import { Unsubscribable } from 'rxjs'; -import { DisplayMode, displayModes, PanelEditorTab } from './types'; + +import { FieldConfigSource, GrafanaTheme, PanelPlugin } from '@grafana/data'; +import { selectors } from '@grafana/e2e-selectors'; +import { Button, HorizontalGroup, Icon, RadioButtonGroup, stylesFactory } from '@grafana/ui'; + +import config from 'app/core/config'; +import { appEvents } from 'app/core/core'; +import { calculatePanelSize } from './utils'; + import { PanelEditorTabs } from './PanelEditorTabs'; import { DashNavTimeControls } from '../DashNav/DashNavTimeControls'; -import { CoreEvents, LocationState } from 'app/types'; -import { calculatePanelSize } from './utils'; -import { initPanelEditor, panelEditorCleanUp, updatePanelEditorUIState } from './state/actions'; -import { PanelEditorUIState, setDiscardChanges } from './state/reducers'; -import { getPanelEditorTabs } from './state/selectors'; -import { getPanelStateById } from '../../state/selectors'; import { OptionsPaneContent } from './OptionsPaneContent'; -import { updateTimeZoneForSession } from 'app/features/profile/state/reducers'; import { DashNavButton } from 'app/features/dashboard/components/DashNav/DashNavButton'; -import { VariableModel } from 'app/features/variables/types'; -import { getVariables } from 'app/features/variables/state/selectors'; import { SubMenuItems } from 'app/features/dashboard/components/SubMenu/SubMenuItems'; import { BackButton } from 'app/core/components/BackButton/BackButton'; -import { appEvents } from 'app/core/core'; import { SaveDashboardModalProxy } from '../SaveDashboard/SaveDashboardModalProxy'; -import { selectors } from '@grafana/e2e-selectors'; +import { DashboardPanel } from '../../dashgrid/DashboardPanel'; + +import { initPanelEditor, panelEditorCleanUp, updatePanelEditorUIState } from './state/actions'; + +import { updateTimeZoneForSession } from 'app/features/profile/state/reducers'; +import { updateLocation } from 'app/core/reducers/location'; +import { PanelEditorUIState, setDiscardChanges } from './state/reducers'; + +import { getPanelEditorTabs } from './state/selectors'; +import { getPanelStateById } from '../../state/selectors'; +import { getVariables } from 'app/features/variables/state/selectors'; + +import { CoreEvents, LocationState, StoreState } from 'app/types'; +import { DisplayMode, displayModes, PanelEditorTab } from './types'; +import { VariableModel } from 'app/features/variables/types'; +import { DashboardModel, PanelModel } from '../../state'; interface OwnProps { dashboard: DashboardModel; @@ -62,15 +65,28 @@ 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 }, @@ -130,11 +146,16 @@ export class PanelEditorUnconnected extends PureComponent { return; } - const targetPane = pane === Pane.Top ? 'topPaneSize' : 'rightPaneSize'; const { updatePanelEditorUIState } = this.props; - updatePanelEditorUIState({ - [targetPane]: size, - }); + if (pane === Pane.Top) { + updatePanelEditorUIState({ + topPaneSize: size / window.innerHeight, + }); + } else { + updatePanelEditorUIState({ + rightPaneSize: size / window.innerWidth, + }); + } }; onDragStarted = () => { @@ -186,11 +207,21 @@ 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; + return tabs.length > 0 ? ( { ); } - renderOptionsPane() { - const { plugin, dashboard, panel, uiState } = this.props; + renderOptionsPane(width: number) { + const { plugin, dashboard, panel } = this.props; if (!plugin) { return
; @@ -300,7 +331,7 @@ export class PanelEditorUnconnected extends PureComponent { plugin={plugin} dashboard={dashboard} panel={panel} - width={uiState.rightPaneSize as number} + width={width} onClose={this.onTogglePanelOptions} onFieldConfigsChange={this.onFieldConfigChange} onPanelOptionsChanged={this.onPanelOptionsChanged} @@ -312,10 +343,21 @@ 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} @@ -324,7 +366,7 @@ export class PanelEditorUnconnected extends PureComponent { onDragFinished={size => this.onDragFinished(Pane.Right, size)} > {this.renderHorizontalSplit(styles)} - {this.renderOptionsPane()} + {this.renderOptionsPane(width)} ); } diff --git a/public/app/features/dashboard/components/PanelEditor/state/reducers.ts b/public/app/features/dashboard/components/PanelEditor/state/reducers.ts index 9503099a219..68de6746aa0 100644 --- a/public/app/features/dashboard/components/PanelEditor/state/reducers.ts +++ b/public/app/features/dashboard/components/PanelEditor/state/reducers.ts @@ -9,7 +9,7 @@ export const PANEL_EDITOR_UI_STATE_STORAGE_KEY = 'grafana.dashboard.editor.ui'; export const DEFAULT_PANEL_EDITOR_UI_STATE: PanelEditorUIState = { isPanelOptionsVisible: true, rightPaneSize: 400, - topPaneSize: '45%', + topPaneSize: 0.45, mode: DisplayMode.Fill, }; @@ -25,7 +25,7 @@ export interface PanelEditorUIState { } export interface PanelEditorState { - /* These are functions as they are mutaded later on and redux toolkit will Object.freeze state so + /* These are functions as they are mutated later on and redux toolkit will Object.freeze state so * we need to store these using functions instead */ getSourcePanel: () => PanelModel; getPanel: () => PanelModel;