mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Core: Move SplitPane layout from PanelEdit. (#29266)
* move split pane logic to its own component * fix update function typing * destruct updateuistate * updates after review * reword the rightpane to work for text panel
This commit is contained in:
parent
5773929953
commit
0b2a6ec7fe
177
public/app/core/components/ThreePaneSplit/SplitPaneWrapper.tsx
Normal file
177
public/app/core/components/ThreePaneSplit/SplitPaneWrapper.tsx
Normal file
@ -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<Props> {
|
||||
rafToken = createRef<number>();
|
||||
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<number>).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 (
|
||||
<SplitPane
|
||||
split="horizontal"
|
||||
maxSize={maxHeight}
|
||||
primary="first"
|
||||
size={topPaneSize < 200 ? 200 : topPaneSize}
|
||||
pane2Style={{ minHeight: 0 }}
|
||||
resizerClassName={styles.resizerH}
|
||||
onDragStarted={this.onDragStarted}
|
||||
onDragFinished={size => this.onDragFinished(Pane.Top, size)}
|
||||
>
|
||||
{leftPaneComponents}
|
||||
</SplitPane>
|
||||
);
|
||||
}
|
||||
|
||||
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 (
|
||||
<SplitPane
|
||||
split="vertical"
|
||||
maxSize={maxWidth}
|
||||
size={rightPaneSize >= 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}
|
||||
</SplitPane>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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};
|
||||
`
|
||||
),
|
||||
};
|
||||
});
|
@ -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<Props> {
|
||||
querySubscription: Unsubscribable;
|
||||
rafToken = createRef<number>();
|
||||
|
||||
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<number>).current = window.requestAnimationFrame(() => {
|
||||
this.forceUpdate();
|
||||
});
|
||||
};
|
||||
|
||||
onPanelExit = () => {
|
||||
this.props.updateLocation({
|
||||
query: { editPanel: null, tab: null },
|
||||
@ -138,30 +124,6 @@ export class PanelEditorUnconnected extends PureComponent<Props> {
|
||||
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<Props> {
|
||||
renderPanel = (styles: EditorStyles) => {
|
||||
const { dashboard, panel, tabs, uiState } = this.props;
|
||||
return (
|
||||
<div className={cx(styles.mainPaneWrapper, tabs.length === 0 && styles.mainPaneWrapperNoTabs)}>
|
||||
<div className={cx(styles.mainPaneWrapper, tabs.length === 0 && styles.mainPaneWrapperNoTabs)} key="panel">
|
||||
{this.renderPanelToolbar(styles)}
|
||||
<div className={styles.panelWrapper}>
|
||||
<AutoSizer>
|
||||
@ -205,38 +167,22 @@ export class PanelEditorUnconnected extends PureComponent<Props> {
|
||||
);
|
||||
};
|
||||
|
||||
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 ? (
|
||||
<SplitPane
|
||||
split="horizontal"
|
||||
minSize={200}
|
||||
maxSize={windowHeight}
|
||||
primary="first"
|
||||
size={size}
|
||||
/* Use persisted state for default size */
|
||||
defaultSize={uiState.topPaneSize}
|
||||
pane2Style={{ minHeight: 0 }}
|
||||
resizerClassName={styles.resizerH}
|
||||
onDragStarted={this.onDragStarted}
|
||||
onDragFinished={size => this.onDragFinished(Pane.Top, size)}
|
||||
>
|
||||
{this.renderPanel(styles)}
|
||||
<div className={styles.tabsWrapper} aria-label={selectors.components.PanelEditor.DataPane.content}>
|
||||
if (tabs.length > 0) {
|
||||
return [
|
||||
this.renderPanel(styles),
|
||||
<div
|
||||
className={styles.tabsWrapper}
|
||||
aria-label={selectors.components.PanelEditor.DataPane.content}
|
||||
key="panel-editor-tabs"
|
||||
>
|
||||
<PanelEditorTabs panel={panel} dashboard={dashboard} tabs={tabs} onChangeTab={this.onChangeTab} />
|
||||
</div>
|
||||
</SplitPane>
|
||||
) : (
|
||||
this.renderPanel(styles)
|
||||
);
|
||||
</div>,
|
||||
];
|
||||
}
|
||||
return this.renderPanel(styles);
|
||||
}
|
||||
|
||||
renderTemplateVariables(styles: EditorStyles) {
|
||||
@ -319,8 +265,13 @@ export class PanelEditorUnconnected extends PureComponent<Props> {
|
||||
);
|
||||
}
|
||||
|
||||
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 <div />;
|
||||
@ -331,7 +282,7 @@ export class PanelEditorUnconnected extends PureComponent<Props> {
|
||||
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<Props> {
|
||||
);
|
||||
}
|
||||
|
||||
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 (
|
||||
<SplitPane
|
||||
split="vertical"
|
||||
minSize={300}
|
||||
maxSize={maxWidth}
|
||||
size={width >= 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)}
|
||||
</SplitPane>
|
||||
);
|
||||
}
|
||||
|
||||
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<Props> {
|
||||
<div className={styles.wrapper} aria-label={selectors.components.PanelEditor.General.content}>
|
||||
{this.editorToolbar(styles)}
|
||||
<div className={styles.verticalSplitPanesWrapper}>
|
||||
{uiState.isPanelOptionsVisible ? this.renderWithOptionsPane(styles) : this.renderHorizontalSplit(styles)}
|
||||
<SplitPaneWrapper
|
||||
leftPaneComponents={this.renderPanelAndEditor(styles)}
|
||||
rightPaneComponents={this.renderOptionsPane()}
|
||||
uiState={uiState}
|
||||
updateUiState={updatePanelEditorUIState}
|
||||
rightPaneVisible={uiState.isPanelOptionsVisible}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@ -416,35 +342,13 @@ const mapDispatchToProps: MapDispatchToProps<DispatchProps, OwnProps> = {
|
||||
|
||||
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%;
|
||||
|
@ -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<void> {
|
||||
return dispatch => {
|
||||
|
@ -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;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user