diff --git a/packages/grafana-ui/src/components/Tabs/TabsBar.tsx b/packages/grafana-ui/src/components/Tabs/TabsBar.tsx index a953c2615c6..40abf161d6c 100644 --- a/packages/grafana-ui/src/components/Tabs/TabsBar.tsx +++ b/packages/grafana-ui/src/components/Tabs/TabsBar.tsx @@ -1,4 +1,4 @@ -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 = ({ children, className, hideBorder }) => { +export const TabsBar = React.forwardRef(({ children, className, hideBorder }, ref) => { const theme = useTheme(); const tabsStyles = getTabsBarStyles(theme, hideBorder); return ( -
+
    {children}
); -}; +}); diff --git a/public/app/features/dashboard/components/PanelEditor/PanelEditor.tsx b/public/app/features/dashboard/components/PanelEditor/PanelEditor.tsx index 062ff1fa83f..4ab226e1cb4 100644 --- a/public/app/features/dashboard/components/PanelEditor/PanelEditor.tsx +++ b/public/app/features/dashboard/components/PanelEditor/PanelEditor.tsx @@ -1,28 +1,31 @@ -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 { @@ -128,8 +125,9 @@ export class PanelEditor extends PureComponent { 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 { this.forceUpdate(); }; - renderBottomOptions() { + onDiplayModeChange = (mode: SelectableValue) => { + 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 ( -
- - {allTabs.map(t => { - if (t.show(panel)) { + (document.body.style.cursor = 'row-resize')} + onDragFinished={this.onDragFinished} + > +
+ + {({ width, height }) => { + if (width < 3 || height < 3) { + return null; + } return ( - { - this.setState({ tab: t.tab }); - }} - /> +
+
+ +
+
); - } - return null; - })} - - - {tab === EditorTab.Query && } - {tab === EditorTab.Alerts &&
TODO: Show Alerts
} - {tab === EditorTab.Transform &&
TODO: Show Transform
} -
-
+ }} + +
+
+ +
+ ); } 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 {
-
+
+ v.value === mode)} + options={displayModes} + onChange={this.onDiplayModeChange} + /> + Discard
- (document.body.style.cursor = 'col-resize')} - onDragFinished={this.onDragFinished} - > + {showPanelOptions ? ( (document.body.style.cursor = 'row-resize')} + defaultSize={350} + resizerClassName={styles.resizerV} + onDragStarted={() => (document.body.style.cursor = 'col-resize')} onDragFinished={this.onDragFinished} > -
- + {this.renderHorizontalSplit(styles)} +
+ +
+ {this.renderFieldOptions()} + {this.renderVisSettings()} +
+
-
{this.renderBottomOptions()}
-
- -
- {this.renderFieldOptions()} - {this.renderVisSettings()} -
-
-
- + ) : ( + this.renderHorizontalSplit(styles) + )}
); } } +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, }); diff --git a/public/app/features/dashboard/components/PanelEditor/PanelEditorTabs.tsx b/public/app/features/dashboard/components/PanelEditor/PanelEditorTabs.tsx new file mode 100644 index 00000000000..39df46f30be --- /dev/null +++ b/public/app/features/dashboard/components/PanelEditor/PanelEditorTabs.tsx @@ -0,0 +1,70 @@ +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 = ({ panel, dashboard }) => { + const [activeTab, setActiveTab] = useState(EditorTab.Query); + const [tabsBarRef, tabsBarMeasurements] = useMeasure(); + const styles = getPanelEditorTabsStyles(); + + return ( +
+
+ + {allTabs.map(t => { + if (t.show(panel)) { + return ( + { + setActiveTab(t.tab); + }} + /> + ); + } + return null; + })} + +
+
+ + + {({ width, height }) => { + return ( +
+ {activeTab === EditorTab.Query && } + {activeTab === EditorTab.Alerts &&
TODO: Show Alerts
} + {activeTab === EditorTab.Transform &&
TODO: Show Transform
} +
+ ); + }} +
+
+
+
+ ); +}; diff --git a/public/app/features/dashboard/components/PanelEditor/types.ts b/public/app/features/dashboard/components/PanelEditor/types.ts new file mode 100644 index 00000000000..426ca71bafb --- /dev/null +++ b/public/app/features/dashboard/components/PanelEditor/types.ts @@ -0,0 +1,25 @@ +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 }, +];