diff --git a/public/app/features/dashboard/components/PanelEditor/PanelEditor.tsx b/public/app/features/dashboard/components/PanelEditor/PanelEditor.tsx index 5fa8743c0a1..5806181ce86 100644 --- a/public/app/features/dashboard/components/PanelEditor/PanelEditor.tsx +++ b/public/app/features/dashboard/components/PanelEditor/PanelEditor.tsx @@ -1,6 +1,6 @@ import React, { PureComponent } from 'react'; -import { GrafanaTheme, FieldConfigSource, PanelData, PanelPlugin, SelectableValue } from '@grafana/data'; -import { stylesFactory, Forms, CustomScrollbar, selectThemeVariant, Icon } from '@grafana/ui'; +import { FieldConfigSource, GrafanaTheme, PanelData, PanelPlugin, SelectableValue } from '@grafana/data'; +import { CustomScrollbar, Forms, Icon, selectThemeVariant, stylesFactory } from '@grafana/ui'; import { css, cx } from 'emotion'; import config from 'app/core/config'; import AutoSizer from 'react-virtualized-auto-sizer'; @@ -11,7 +11,7 @@ import { DashboardPanel } from '../../dashgrid/DashboardPanel'; import SplitPane from 'react-split-pane'; import { StoreState } from '../../../../types/store'; -import { connect, MapStateToProps, MapDispatchToProps } from 'react-redux'; +import { connect, MapDispatchToProps, MapStateToProps } from 'react-redux'; import { updateLocation } from '../../../../core/reducers/location'; import { Unsubscribable } from 'rxjs'; import { PanelTitle } from './PanelTitle'; @@ -20,13 +20,18 @@ import { PanelEditorTabs } from './PanelEditorTabs'; import { DashNavTimeControls } from '../DashNav/DashNavTimeControls'; import { LocationState } from 'app/types'; import { calculatePanelSize } from './utils'; -import { initPanelEditor, panelEditorCleanUp } from './state/actions'; -import { setDisplayMode, toggleOptionsView, setDiscardChanges } from './state/reducers'; +import { initPanelEditor, panelEditorCleanUp, updatePanelEditorUIState } from './state/actions'; +import { PanelEditorUIState, setDiscardChanges } from './state/reducers'; import { FieldConfigEditor } from './FieldConfigEditor'; import { OptionsGroup } from './OptionsGroup'; import { getPanelEditorTabs } from './state/selectors'; import { getPanelStateById } from '../../state/selectors'; +enum Pane { + Right, + Top, +} + interface OwnProps { dashboard: DashboardModel; sourcePanel: PanelModel; @@ -37,19 +42,17 @@ interface ConnectedProps { plugin?: PanelPlugin; panel: PanelModel; data: PanelData; - mode: DisplayMode; - isPanelOptionsVisible: boolean; initDone: boolean; tabs: PanelEditorTab[]; + uiState: PanelEditorUIState; } interface DispatchProps { updateLocation: typeof updateLocation; initPanelEditor: typeof initPanelEditor; panelEditorCleanUp: typeof panelEditorCleanUp; - setDisplayMode: typeof setDisplayMode; - toggleOptionsView: typeof toggleOptionsView; setDiscardChanges: typeof setDiscardChanges; + updatePanelEditorUIState: typeof updatePanelEditorUIState; } type Props = OwnProps & ConnectedProps & DispatchProps; @@ -141,8 +144,13 @@ export class PanelEditorUnconnected extends PureComponent { return
No editor (angular?)
; } - onDragFinished = () => { + onDragFinished = (pane: Pane, size: number) => { document.body.style.cursor = 'auto'; + const targetPane = pane === Pane.Top ? 'topPaneSize' : 'rightPaneSize'; + const { updatePanelEditorUIState } = this.props; + updatePanelEditorUIState({ + [targetPane]: size, + }); }; onDragStarted = () => { @@ -155,26 +163,31 @@ export class PanelEditorUnconnected extends PureComponent { }; onDiplayModeChange = (mode: SelectableValue) => { - this.props.setDisplayMode(mode.value); + const { updatePanelEditorUIState } = this.props; + updatePanelEditorUIState({ + mode: mode.value, + }); }; onTogglePanelOptions = () => { - this.props.toggleOptionsView(); + const { uiState, updatePanelEditorUIState } = this.props; + updatePanelEditorUIState({ isPanelOptionsVisible: !uiState.isPanelOptionsVisible }); }; renderHorizontalSplit(styles: any) { - const { dashboard, panel, mode, tabs, data } = this.props; + const { dashboard, panel, tabs, data, uiState } = this.props; return ( this.onDragFinished(Pane.Top, size)} >
@@ -184,7 +197,7 @@ export class PanelEditorUnconnected extends PureComponent { } return (
-
+
{ } render() { - const { dashboard, location, panel, mode, isPanelOptionsVisible, initDone } = this.props; + const { dashboard, location, panel, uiState, initDone } = this.props; const styles = getStyles(config.theme); if (!initDone) { @@ -234,7 +247,7 @@ export class PanelEditorUnconnected extends PureComponent {
v.value === mode)} + value={displayModes.find(v => v.value === uiState.mode)} options={displayModes} onChange={this.onDiplayModeChange} /> @@ -253,15 +266,16 @@ export class PanelEditorUnconnected extends PureComponent {
- {isPanelOptionsVisible ? ( + {uiState.isPanelOptionsVisible ? ( (document.body.style.cursor = 'col-resize')} - onDragFinished={this.onDragFinished} + onDragFinished={size => this.onDragFinished(Pane.Right, size)} > {this.renderHorizontalSplit(styles)}
@@ -292,11 +306,10 @@ const mapStateToProps: MapStateToProps = ( location: state.location, plugin: plugin, panel: state.panelEditorNew.getPanel(), - mode: state.panelEditorNew.mode, - isPanelOptionsVisible: state.panelEditorNew.isPanelOptionsVisible, data: state.panelEditorNew.getData(), initDone: state.panelEditorNew.initDone, tabs: getPanelEditorTabs(state.location, plugin), + uiState: state.panelEditorNew.ui, }; }; @@ -304,9 +317,8 @@ const mapDispatchToProps: MapDispatchToProps = { updateLocation, initPanelEditor, panelEditorCleanUp, - setDisplayMode, - toggleOptionsView, setDiscardChanges, + updatePanelEditorUIState, }; export const PanelEditor = connect(mapStateToProps, mapDispatchToProps)(PanelEditorUnconnected); diff --git a/public/app/features/dashboard/components/PanelEditor/state/actions.ts b/public/app/features/dashboard/components/PanelEditor/state/actions.ts index e62c45bf371..f8cb6bbc890 100644 --- a/public/app/features/dashboard/components/PanelEditor/state/actions.ts +++ b/public/app/features/dashboard/components/PanelEditor/state/actions.ts @@ -1,7 +1,15 @@ import { PanelModel, DashboardModel } from '../../../state'; import { PanelData } from '@grafana/data'; import { ThunkResult } from 'app/types'; -import { setEditorPanelData, updateEditorInitState, closeCompleted } from './reducers'; +import { + setEditorPanelData, + updateEditorInitState, + closeCompleted, + PanelEditorUIState, + setPanelEditorUIState, + PANEL_EDITOR_UI_STATE_STORAGE_KEY, +} from './reducers'; +import store from '../../../../../core/store'; export function initPanelEditor(sourcePanel: PanelModel, dashboard: DashboardModel): ThunkResult { return dispatch => { @@ -45,3 +53,11 @@ export function panelEditorCleanUp(): ThunkResult { dispatch(closeCompleted()); }; } + +export function updatePanelEditorUIState(uiState: Partial): ThunkResult { + return (dispatch, getStore) => { + const nextState = { ...getStore().panelEditorNew.ui, ...uiState }; + dispatch(setPanelEditorUIState(nextState)); + store.setObject(PANEL_EDITOR_UI_STATE_STORAGE_KEY, nextState); + }; +} diff --git a/public/app/features/dashboard/components/PanelEditor/state/reducers.ts b/public/app/features/dashboard/components/PanelEditor/state/reducers.ts index 7a9f11e4a52..2e889eeb212 100644 --- a/public/app/features/dashboard/components/PanelEditor/state/reducers.ts +++ b/public/app/features/dashboard/components/PanelEditor/state/reducers.ts @@ -3,6 +3,25 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit'; import { PanelModel } from '../../../state/PanelModel'; import { PanelData, LoadingState, DefaultTimeRange } from '@grafana/data'; import { DisplayMode } from '../types'; +import store from '../../../../../core/store'; + +export const PANEL_EDITOR_UI_STATE_STORAGE_KEY = 'grafana.dashboard.editor.ui'; + +export const DEFAULT_PANEL_EDITOR_UI_STATE: PanelEditorUIState = { + isPanelOptionsVisible: true, + rightPaneSize: 350, + topPaneSize: '45%', + mode: DisplayMode.Fill, +}; + +export interface PanelEditorUIState { + isPanelOptionsVisible: boolean; + // annotating as number or string since size can be expressed as static value or percentage + rightPaneSize: number | string; + // annotating as number or string since size can be expressed as static value or percentage + topPaneSize: number | string; + mode: DisplayMode; +} export interface PanelEditorStateNew { /* These are functions as they are mutaded later on and redux toolkit will Object.freeze state so @@ -10,12 +29,11 @@ export interface PanelEditorStateNew { getSourcePanel: () => PanelModel; getPanel: () => PanelModel; getData: () => PanelData; - mode: DisplayMode; - isPanelOptionsVisible: boolean; querySubscription?: Unsubscribable; initDone: boolean; shouldDiscardChanges: boolean; isOpen: boolean; + ui: PanelEditorUIState; } export const initialState: PanelEditorStateNew = { @@ -26,11 +44,13 @@ export const initialState: PanelEditorStateNew = { series: [], timeRange: DefaultTimeRange, }), - isPanelOptionsVisible: true, - mode: DisplayMode.Fill, initDone: false, shouldDiscardChanges: false, isOpen: false, + ui: { + ...DEFAULT_PANEL_EDITOR_UI_STATE, + ...store.getObject(PANEL_EDITOR_UI_STATE_STORAGE_KEY, DEFAULT_PANEL_EDITOR_UI_STATE), + }, }; interface InitEditorPayload { @@ -53,15 +73,12 @@ const pluginsSlice = createSlice({ setEditorPanelData: (state, action: PayloadAction) => { state.getData = () => action.payload; }, - toggleOptionsView: state => { - state.isPanelOptionsVisible = !state.isPanelOptionsVisible; - }, - setDisplayMode: (state, action: PayloadAction) => { - state.mode = action.payload; - }, setDiscardChanges: (state, action: PayloadAction) => { state.shouldDiscardChanges = action.payload; }, + setPanelEditorUIState: (state, action: PayloadAction>) => { + state.ui = { ...state.ui, ...action.payload }; + }, closeCompleted: state => { state.isOpen = false; state.initDone = false; @@ -72,10 +89,9 @@ const pluginsSlice = createSlice({ export const { updateEditorInitState, setEditorPanelData, - toggleOptionsView, - setDisplayMode, setDiscardChanges, closeCompleted, + setPanelEditorUIState, } = pluginsSlice.actions; export const panelEditorReducerNew = pluginsSlice.reducer;