diff --git a/public/app/features/dashboard/components/PanelEditor/DynamicConfigValueEditor.tsx b/public/app/features/dashboard/components/PanelEditor/DynamicConfigValueEditor.tsx index 09f6872d136..846062db360 100644 --- a/public/app/features/dashboard/components/PanelEditor/DynamicConfigValueEditor.tsx +++ b/public/app/features/dashboard/components/PanelEditor/DynamicConfigValueEditor.tsx @@ -45,6 +45,7 @@ export const DynamicConfigValueEditor: React.FC = if (isCollapsible) { editor = ( = ({ data, onChange, conf ); }} + id={`${k}/${i}`} key={`${k}/${i}`} > {groupedConfigs[k].map(c => { diff --git a/public/app/features/dashboard/components/PanelEditor/OptionsGroup.tsx b/public/app/features/dashboard/components/PanelEditor/OptionsGroup.tsx index 02206ebcf0b..fa5cc2cd8ca 100644 --- a/public/app/features/dashboard/components/PanelEditor/OptionsGroup.tsx +++ b/public/app/features/dashboard/components/PanelEditor/OptionsGroup.tsx @@ -1,18 +1,81 @@ -import React, { useState, FC, useEffect } from 'react'; +import React, { FC, memo, useCallback, useEffect, useState } from 'react'; import { css, cx } from 'emotion'; import { GrafanaTheme } from '@grafana/data'; -import { useTheme, Icon, stylesFactory } from '@grafana/ui'; +import { Icon, stylesFactory, useTheme } from '@grafana/ui'; +import { PANEL_EDITOR_UI_STATE_STORAGE_KEY } from './state/reducers'; +import { useLocalStorage } from 'react-use'; -interface Props { +export interface OptionsGroupProps { + id: string; title?: React.ReactNode; renderTitle?: (isExpanded: boolean) => React.ReactNode; defaultToClosed?: boolean; className?: string; nested?: boolean; + persistMe?: boolean; onToggle?: (isExpanded: boolean) => void; } -export const OptionsGroup: FC = ({ +export const OptionsGroup: FC = ({ + id, + title, + children, + defaultToClosed, + renderTitle, + className, + nested = false, + persistMe = true, + onToggle, +}) => { + if (persistMe) { + return ( + + {children} + + ); + } + + return ( + + {children} + + ); +}; + +const CollapsibleSectionWithPersistence: FC = memo(props => { + const [value, setValue] = useLocalStorage(getOptionGroupStorageKey(props.id), { + defaultToClosed: props.defaultToClosed, + }); + const onToggle = useCallback( + (isExpanded: boolean) => { + setValue({ defaultToClosed: !isExpanded }); + if (props.onToggle) { + props.onToggle(isExpanded); + } + }, + [setValue, props.onToggle] + ); + + return ; +}); + +const CollapsibleSection: FC> = ({ title, children, defaultToClosed, @@ -106,3 +169,5 @@ const getStyles = stylesFactory((theme: GrafanaTheme, isExpanded: boolean, isNes ), }; }); + +const getOptionGroupStorageKey = (id: string): string => `${PANEL_EDITOR_UI_STATE_STORAGE_KEY}.optionGroup[${id}]`; diff --git a/public/app/features/dashboard/components/PanelEditor/OverrideEditor.tsx b/public/app/features/dashboard/components/PanelEditor/OverrideEditor.tsx index 04c38cc7da4..cd473527cb5 100644 --- a/public/app/features/dashboard/components/PanelEditor/OverrideEditor.tsx +++ b/public/app/features/dashboard/components/PanelEditor/OverrideEditor.tsx @@ -121,7 +121,7 @@ export const OverrideEditor: React.FC = ({ }; return ( - + > = ({ plu .filter(e => e !== null); return optionsToShow.length > 0 ? ( - +
{optionsToShow}
) : null; diff --git a/public/app/features/dashboard/components/PanelEditor/PanelOptionsTab.tsx b/public/app/features/dashboard/components/PanelEditor/PanelOptionsTab.tsx index 67d2fd5e20a..c69be6f0f2c 100644 --- a/public/app/features/dashboard/components/PanelEditor/PanelOptionsTab.tsx +++ b/public/app/features/dashboard/components/PanelEditor/PanelOptionsTab.tsx @@ -2,12 +2,12 @@ import React, { FC, useMemo, useRef } from 'react'; import { DashboardModel, PanelModel } from '../../state'; import { FieldConfigSource, PanelData, PanelPlugin, SelectableValue } from '@grafana/data'; import { Counter, DataLinksInlineEditor, Field, Input, RadioButtonGroup, Select, Switch, TextArea } from '@grafana/ui'; -import { OptionsGroup } from './OptionsGroup'; import { getPanelLinksVariableSuggestions } from '../../../panel/panellinks/link_srv'; import { getVariables } from '../../../variables/state/selectors'; import { PanelOptionsEditor } from './PanelOptionsEditor'; import { AngularPanelOptions } from './AngularPanelOptions'; import { VisualizationTab } from './VisualizationTab'; +import { OptionsGroup } from './OptionsGroup'; interface Props { panel: PanelModel; @@ -48,7 +48,7 @@ export const PanelOptionsTab: FC = ({ }; // Fist common panel settings Title, description elements.push( - + onPanelConfigChange('title', e.currentTarget.value)} /> @@ -65,7 +65,7 @@ export const PanelOptionsTab: FC = ({ ); elements.push( - + ); @@ -73,7 +73,7 @@ export const PanelOptionsTab: FC = ({ // Old legacy react editor if (plugin.editor && panel && !plugin.optionEditors) { elements.push( - + = ({ renderTitle={isExpanded => ( <>Panel links {!isExpanded && panelLinksCount > 0 && } )} + id="panel links" key="panel links" - defaultToClosed={true} + defaultToClosed > = ({ ); elements.push( - +