Commit e9c6c040 by Ryan McKinley Committed by GitHub

New Editor: add display modes to fix ratio with actual display (#22032)

* add mode picker

* ratio cleanup

* add siebar toggle

* use secondary button

* use cog icon

* cleanup

* full to fill

* Portaling for Select

* Centering container for panel edit

* Panel editor tabs

Co-authored-by: Dominik Prokop <dominik.prokop@grafana.com>
parent 88922ea4
import React, { FC, ReactNode } from 'react';
import React, { ReactNode } from 'react';
import { stylesFactory, useTheme } from '../../themes';
import { GrafanaTheme } from '@grafana/data';
import { css, cx } from 'emotion';
......@@ -28,13 +28,13 @@ const getTabsBarStyles = stylesFactory((theme: GrafanaTheme, hideBorder = false)
};
});
export const TabsBar: FC<Props> = ({ children, className, hideBorder }) => {
export const TabsBar = React.forwardRef<HTMLDivElement, Props>(({ children, className, hideBorder }, ref) => {
const theme = useTheme();
const tabsStyles = getTabsBarStyles(theme, hideBorder);
return (
<div className={cx(tabsStyles.tabsWrapper, className)}>
<div className={cx(tabsStyles.tabsWrapper, className)} ref={ref}>
<ul className={tabsStyles.tabs}>{children}</ul>
</div>
);
};
});
import React, { PureComponent } from 'react';
import { GrafanaTheme, FieldConfigSource, PanelData, LoadingState, DefaultTimeRange, PanelEvents } from '@grafana/data';
import React, { PureComponent, CSSProperties } from 'react';
import {
stylesFactory,
Forms,
FieldConfigEditor,
CustomScrollbar,
selectThemeVariant,
TabContent,
Tab,
TabsBar,
} from '@grafana/ui';
GrafanaTheme,
FieldConfigSource,
PanelData,
LoadingState,
DefaultTimeRange,
PanelEvents,
SelectableValue,
} from '@grafana/data';
import { stylesFactory, Forms, FieldConfigEditor, CustomScrollbar, selectThemeVariant } from '@grafana/ui';
import { css, cx } from 'emotion';
import config from 'app/core/config';
import AutoSizer from 'react-virtualized-auto-sizer';
import { GRID_CELL_HEIGHT, GRID_CELL_VMARGIN, GRID_COLUMN_COUNT } from 'app/core/constants';
import { PanelModel } from '../../state/PanelModel';
import { DashboardModel } from '../../state/DashboardModel';
import { DashboardPanel } from '../../dashgrid/DashboardPanel';
import { QueriesTab } from '../../panel_editor/QueriesTab';
import SplitPane from 'react-split-pane';
import { StoreState } from '../../../../types/store';
import { connect } from 'react-redux';
import { updateLocation } from '../../../../core/reducers/location';
import { Unsubscribable } from 'rxjs';
import { PanelTitle } from './PanelTitle';
import { DisplayMode, displayModes } from './types';
import { PanelEditorTabs } from './PanelEditorTabs';
const getStyles = stylesFactory((theme: GrafanaTheme) => {
const handleColor = selectThemeVariant(
......@@ -54,7 +57,7 @@ const getStyles = stylesFactory((theme: GrafanaTheme) => {
bottom: 0;
background: ${theme.colors.pageBg};
`,
fill: css`
panelWrapper: css`
width: 100%;
height: 100%;
`,
......@@ -89,21 +92,14 @@ const getStyles = stylesFactory((theme: GrafanaTheme) => {
display: flex;
align-items: center;
`,
centeringContainer: css`
display: flex;
justify-content: center;
align-items: center;
`,
};
});
enum EditorTab {
Query = 'query',
Alerts = 'alerts',
Transform = 'xform',
}
const allTabs = [
{ tab: EditorTab.Query, label: 'Query', show: (panel: PanelModel) => true },
{ tab: EditorTab.Alerts, label: 'Alerts', show: (panel: PanelModel) => true },
{ tab: EditorTab.Transform, label: 'Transform', show: (panel: PanelModel) => true },
];
interface Props {
dashboard: DashboardModel;
sourcePanel: PanelModel;
......@@ -114,7 +110,8 @@ interface State {
pluginLoadedCounter: number;
panel: PanelModel;
data: PanelData;
tab: EditorTab;
mode: DisplayMode;
showPanelOptions: boolean;
}
export class PanelEditor extends PureComponent<Props, State> {
......@@ -128,8 +125,9 @@ export class PanelEditor extends PureComponent<Props, State> {
const panel = props.sourcePanel.getEditClone();
this.state = {
panel,
tab: EditorTab.Query,
pluginLoadedCounter: 0,
mode: DisplayMode.Fill,
showPanelOptions: true,
data: {
state: LoadingState.NotStarted,
series: [],
......@@ -260,40 +258,64 @@ export class PanelEditor extends PureComponent<Props, State> {
this.forceUpdate();
};
renderBottomOptions() {
onDiplayModeChange = (mode: SelectableValue<DisplayMode>) => {
this.setState({
mode: mode.value!,
});
};
onTogglePanelOptions = () => {
this.setState({
showPanelOptions: !this.state.showPanelOptions,
});
};
renderHorizontalSplit(styles: any) {
const { dashboard } = this.props;
const { panel, tab } = this.state;
const { panel, mode } = this.state;
return (
<div>
<TabsBar>
{allTabs.map(t => {
if (t.show(panel)) {
<SplitPane
split="horizontal"
minSize={50}
primary="second"
defaultSize="40%"
resizerClassName={styles.resizerH}
onDragStarted={() => (document.body.style.cursor = 'row-resize')}
onDragFinished={this.onDragFinished}
>
<div className={styles.panelWrapper}>
<AutoSizer>
{({ width, height }) => {
if (width < 3 || height < 3) {
return null;
}
return (
<Tab
label={t.label}
active={tab === t.tab}
onChangeTab={() => {
this.setState({ tab: t.tab });
}}
/>
<div className={styles.centeringContainer} style={{ width, height }}>
<div style={calculatePanelSize(mode, width, height, panel)}>
<DashboardPanel
dashboard={dashboard}
panel={panel}
isEditing={false}
isInEditMode
isFullscreen={false}
isInView={true}
/>
</div>
</div>
);
}
return null;
})}
</TabsBar>
<TabContent>
{tab === EditorTab.Query && <QueriesTab panel={panel} dashboard={dashboard} />}
{tab === EditorTab.Alerts && <div>TODO: Show Alerts</div>}
{tab === EditorTab.Transform && <div>TODO: Show Transform</div>}
</TabContent>
</div>
}}
</AutoSizer>
</div>
<div className={styles.noScrollPaneContent}>
<PanelEditorTabs panel={panel} dashboard={dashboard} />
</div>
</SplitPane>
);
}
render() {
const { dashboard } = this.props;
const { panel } = this.state;
const { panel, mode, showPanelOptions } = this.state;
const styles = getStyles(config.theme);
if (!panel) {
......@@ -309,58 +331,70 @@ export class PanelEditor extends PureComponent<Props, State> {
</button>
<PanelTitle value={panel.title} onChange={this.onPanelTitleChange} />
</div>
<div>
<div className={styles.toolbarLeft}>
<Forms.Select
value={displayModes.find(v => v.value === mode)}
options={displayModes}
onChange={this.onDiplayModeChange}
/>
<Forms.Button icon="fa fa-cog" variant="secondary" onClick={this.onTogglePanelOptions} />
<Forms.Button variant="destructive" onClick={this.onDiscard}>
Discard
</Forms.Button>
</div>
</div>
<div className={styles.panes}>
<SplitPane
split="vertical"
primary="second"
minSize={50}
defaultSize={350}
resizerClassName={styles.resizerV}
onDragStarted={() => (document.body.style.cursor = 'col-resize')}
onDragFinished={this.onDragFinished}
>
{showPanelOptions ? (
<SplitPane
split="horizontal"
split="vertical"
minSize={100}
primary="second"
defaultSize="40%"
resizerClassName={styles.resizerH}
onDragStarted={() => (document.body.style.cursor = 'row-resize')}
defaultSize={350}
resizerClassName={styles.resizerV}
onDragStarted={() => (document.body.style.cursor = 'col-resize')}
onDragFinished={this.onDragFinished}
>
<div className={styles.fill}>
<DashboardPanel
dashboard={dashboard}
panel={panel}
isEditing={false}
isInEditMode
isFullscreen={false}
isInView={true}
/>
{this.renderHorizontalSplit(styles)}
<div className={styles.noScrollPaneContent}>
<CustomScrollbar>
<div style={{ padding: '10px' }}>
{this.renderFieldOptions()}
{this.renderVisSettings()}
</div>
</CustomScrollbar>
</div>
<div className={styles.noScrollPaneContent}>{this.renderBottomOptions()}</div>
</SplitPane>
<div className={styles.noScrollPaneContent}>
<CustomScrollbar>
<div style={{ padding: '10px' }}>
{this.renderFieldOptions()}
{this.renderVisSettings()}
</div>
</CustomScrollbar>
</div>
</SplitPane>
) : (
this.renderHorizontalSplit(styles)
)}
</div>
</div>
);
}
}
function calculatePanelSize(mode: DisplayMode, width: number, height: number, panel: PanelModel): CSSProperties {
if (mode === DisplayMode.Fill) {
return { width, height };
}
const colWidth = (window.innerWidth - GRID_CELL_VMARGIN * 4) / GRID_COLUMN_COUNT;
const pWidth = colWidth * panel.gridPos.w;
const pHeight = GRID_CELL_HEIGHT * panel.gridPos.h;
const scale = Math.min(width / pWidth, height / pHeight);
if (mode === DisplayMode.Exact && pWidth <= width && pHeight <= height) {
return {
width: pWidth,
height: pHeight,
};
}
return {
width: pWidth * scale,
height: pHeight * scale,
};
}
const mapStateToProps = (state: StoreState) => ({
location: state.location,
});
......
import React, { useState } from 'react';
import { css } from 'emotion';
import AutoSizer from 'react-virtualized-auto-sizer';
import useMeasure from 'react-use/lib/useMeasure';
import { TabsBar, Tab, stylesFactory, TabContent } from '@grafana/ui';
import { EditorTab, allTabs } from './types';
import { DashboardModel } from '../../state';
import { QueriesTab } from '../../panel_editor/QueriesTab';
import { PanelModel } from '../../state/PanelModel';
interface PanelEditorTabsProps {
panel: PanelModel;
dashboard: DashboardModel;
}
const getPanelEditorTabsStyles = stylesFactory(() => {
return {
wrapper: css`
display: flex;
flex-direction: column;
height: 100%;
`,
content: css`
flex-grow: 1;
`,
};
});
export const PanelEditorTabs: React.FC<PanelEditorTabsProps> = ({ panel, dashboard }) => {
const [activeTab, setActiveTab] = useState(EditorTab.Query);
const [tabsBarRef, tabsBarMeasurements] = useMeasure();
const styles = getPanelEditorTabsStyles();
return (
<div className={styles.wrapper}>
<div>
<TabsBar ref={tabsBarRef}>
{allTabs.map(t => {
if (t.show(panel)) {
return (
<Tab
label={t.label}
active={activeTab === t.tab}
onChangeTab={() => {
setActiveTab(t.tab);
}}
/>
);
}
return null;
})}
</TabsBar>
</div>
<div style={{ flexGrow: 1 }}>
<TabContent style={{ height: `calc(100% - ${tabsBarMeasurements.height}px)` }}>
<AutoSizer>
{({ width, height }) => {
return (
<div style={{ width, height }}>
{activeTab === EditorTab.Query && <QueriesTab panel={panel} dashboard={dashboard} />}
{activeTab === EditorTab.Alerts && <div>TODO: Show Alerts</div>}
{activeTab === EditorTab.Transform && <div>TODO: Show Transform</div>}
</div>
);
}}
</AutoSizer>
</TabContent>
</div>
</div>
);
};
import { PanelModel } from '../../state/PanelModel';
export enum DisplayMode {
Fill = 0,
Fit = 1,
Exact = 2,
}
export const displayModes = [
{ value: DisplayMode.Fill, label: 'Fill', description: 'Use all avaliable space' },
{ value: DisplayMode.Fit, label: 'Fit', description: 'Fit in the space keeping ratio' },
{ value: DisplayMode.Exact, label: 'Exact', description: 'Same size as the dashboard' },
];
export enum EditorTab {
Query = 'query',
Alerts = 'alerts',
Transform = 'xform',
}
export const allTabs = [
{ tab: EditorTab.Query, label: 'Query', show: (panel: PanelModel) => true },
{ tab: EditorTab.Alerts, label: 'Alerts', show: (panel: PanelModel) => true },
{ tab: EditorTab.Transform, label: 'Transform', show: (panel: PanelModel) => true },
];
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment