New panel editor: Persist panel editor ui state (#22210)

This commit is contained in:
Dominik Prokop 2020-02-15 20:31:11 +01:00 committed by GitHub
parent 21b53b7d79
commit 131a3248ba
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 82 additions and 38 deletions

View File

@ -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<Props> {
return <div>No editor (angular?)</div>;
}
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<Props> {
};
onDiplayModeChange = (mode: SelectableValue<DisplayMode>) => {
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 (
<SplitPane
split="horizontal"
minSize={50}
primary="first"
defaultSize="45%"
/* Use persisted state for default size */
defaultSize={uiState.topPaneSize}
pane2Style={{ minHeight: 0 }}
resizerClassName={styles.resizerH}
onDragStarted={this.onDragStarted}
onDragFinished={this.onDragFinished}
onDragFinished={size => this.onDragFinished(Pane.Top, size)}
>
<div className={styles.panelWrapper}>
<AutoSizer>
@ -184,7 +197,7 @@ export class PanelEditorUnconnected extends PureComponent<Props> {
}
return (
<div className={styles.centeringContainer} style={{ width, height }}>
<div style={calculatePanelSize(mode, width, height, panel)}>
<div style={calculatePanelSize(uiState.mode, width, height, panel)}>
<DashboardPanel
dashboard={dashboard}
panel={panel}
@ -207,7 +220,7 @@ export class PanelEditorUnconnected extends PureComponent<Props> {
}
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<Props> {
</div>
<div className={styles.toolbarItem}>
<Forms.Select
value={displayModes.find(v => 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<Props> {
</div>
</div>
<div className={styles.editorBody}>
{isPanelOptionsVisible ? (
{uiState.isPanelOptionsVisible ? (
<SplitPane
split="vertical"
minSize={100}
primary="second"
defaultSize={350}
/* Use persisted state for default size */
defaultSize={uiState.rightPaneSize}
resizerClassName={styles.resizerV}
onDragStarted={() => (document.body.style.cursor = 'col-resize')}
onDragFinished={this.onDragFinished}
onDragFinished={size => this.onDragFinished(Pane.Right, size)}
>
{this.renderHorizontalSplit(styles)}
<div className={styles.panelOptionsPane}>
@ -292,11 +306,10 @@ const mapStateToProps: MapStateToProps<ConnectedProps, OwnProps, StoreState> = (
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<DispatchProps, OwnProps> = {
updateLocation,
initPanelEditor,
panelEditorCleanUp,
setDisplayMode,
toggleOptionsView,
setDiscardChanges,
updatePanelEditorUIState,
};
export const PanelEditor = connect(mapStateToProps, mapDispatchToProps)(PanelEditorUnconnected);

View File

@ -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<void> {
return dispatch => {
@ -45,3 +53,11 @@ export function panelEditorCleanUp(): ThunkResult<void> {
dispatch(closeCompleted());
};
}
export function updatePanelEditorUIState(uiState: Partial<PanelEditorUIState>): ThunkResult<void> {
return (dispatch, getStore) => {
const nextState = { ...getStore().panelEditorNew.ui, ...uiState };
dispatch(setPanelEditorUIState(nextState));
store.setObject(PANEL_EDITOR_UI_STATE_STORAGE_KEY, nextState);
};
}

View File

@ -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<PanelData>) => {
state.getData = () => action.payload;
},
toggleOptionsView: state => {
state.isPanelOptionsVisible = !state.isPanelOptionsVisible;
},
setDisplayMode: (state, action: PayloadAction<DisplayMode>) => {
state.mode = action.payload;
},
setDiscardChanges: (state, action: PayloadAction<boolean>) => {
state.shouldDiscardChanges = action.payload;
},
setPanelEditorUIState: (state, action: PayloadAction<Partial<PanelEditorUIState>>) => {
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;