mirror of
https://github.com/grafana/grafana.git
synced 2025-02-12 08:35:43 -06:00
* Add split resize to Explore without keeping width in state * debug commit * ugly hack around split lib only supporting one child * Use SplitView to accomodate one or two elements, remove debug code, fix test * More cleanup, fix state action * Fix even split from manual size scenario * cleanup * Add new state elements to test * Handle scrollable on internal element for virtualized lists * Left align overflow button for explore * Change min/max buttons * Apply suggestions from code review Co-authored-by: Giordano Ricci <me@giordanoricci.com> * Add more suggestions from the code review * Fix problems tests found * commit broken test with debug info * Add test, remove debug code * Remove second get of panes * Remove second get of panes Co-authored-by: Elfo404 <me@giordanoricci.com>
205 lines
4.9 KiB
TypeScript
205 lines
4.9 KiB
TypeScript
import { css, cx } from '@emotion/css';
|
|
import React, { createRef, MutableRefObject, PureComponent, ReactNode } from 'react';
|
|
import SplitPane from 'react-split-pane';
|
|
|
|
import { GrafanaTheme } from '@grafana/data';
|
|
import { stylesFactory } from '@grafana/ui';
|
|
import { config } from 'app/core/config';
|
|
|
|
import { SplitView } from './SplitView';
|
|
|
|
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: MutableRefObject<number | null> = 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.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 : uiState.topPaneSize * 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.
|
|
*/
|
|
|
|
if (Array.isArray(leftPaneComponents)) {
|
|
return (
|
|
<SplitPane
|
|
split="horizontal"
|
|
maxSize={-200}
|
|
primary="first"
|
|
size={topPaneSize}
|
|
pane2Style={{ minHeight: 0 }}
|
|
resizerClassName={styles.resizerH}
|
|
onDragStarted={this.onDragStarted}
|
|
onDragFinished={(size) => this.onDragFinished(Pane.Top, size)}
|
|
>
|
|
{leftPaneComponents}
|
|
</SplitPane>
|
|
);
|
|
}
|
|
|
|
return <div className={styles.singleLeftPane}>{leftPaneComponents}</div>;
|
|
}
|
|
|
|
render() {
|
|
const { rightPaneVisible, rightPaneComponents, uiState } = this.props;
|
|
// Limit options pane width to 90% of screen.
|
|
// Need to handle when width is relative. ie a percentage of the viewport
|
|
const rightPaneSize =
|
|
uiState.rightPaneSize <= 1 ? uiState.rightPaneSize * window.innerWidth : uiState.rightPaneSize;
|
|
|
|
if (!rightPaneVisible) {
|
|
return this.renderHorizontalSplit();
|
|
}
|
|
|
|
return (
|
|
<SplitView uiState={{ rightPaneSize }}>
|
|
{this.renderHorizontalSplit()}
|
|
{rightPaneComponents}
|
|
</SplitView>
|
|
);
|
|
}
|
|
}
|
|
|
|
const getStyles = stylesFactory((theme: GrafanaTheme) => {
|
|
const handleColor = theme.palette.blue95;
|
|
const paneSpacing = theme.spacing.md;
|
|
|
|
const resizer = css`
|
|
position: relative;
|
|
|
|
&::before {
|
|
content: '';
|
|
position: absolute;
|
|
transition: 0.2s border-color ease-in-out;
|
|
}
|
|
|
|
&::after {
|
|
background: ${theme.colors.panelBorder};
|
|
content: '';
|
|
position: absolute;
|
|
left: 50%;
|
|
top: 50%;
|
|
transition: 0.2s background ease-in-out;
|
|
transform: translate(-50%, -50%);
|
|
border-radius: 4px;
|
|
}
|
|
|
|
&:hover {
|
|
&::before {
|
|
border-color: ${handleColor};
|
|
}
|
|
|
|
&::after {
|
|
background: ${handleColor};
|
|
}
|
|
}
|
|
`;
|
|
|
|
return {
|
|
singleLeftPane: css`
|
|
height: 100%;
|
|
position: absolute;
|
|
overflow: hidden;
|
|
width: 100%;
|
|
`,
|
|
resizerV: cx(
|
|
resizer,
|
|
css`
|
|
cursor: col-resize;
|
|
width: ${paneSpacing};
|
|
|
|
&::before {
|
|
border-right: 1px solid transparent;
|
|
height: 100%;
|
|
left: 50%;
|
|
transform: translateX(-50%);
|
|
}
|
|
|
|
&::after {
|
|
height: 200px;
|
|
width: 4px;
|
|
}
|
|
`
|
|
),
|
|
resizerH: cx(
|
|
resizer,
|
|
css`
|
|
height: ${paneSpacing};
|
|
cursor: row-resize;
|
|
margin-left: ${paneSpacing};
|
|
|
|
&::before {
|
|
border-top: 1px solid transparent;
|
|
top: 50%;
|
|
transform: translateY(-50%);
|
|
width: 100%;
|
|
}
|
|
|
|
&::after {
|
|
height: 4px;
|
|
width: 200px;
|
|
}
|
|
`
|
|
),
|
|
};
|
|
});
|