PanelEditor: stores option group collapse state (#23781)

* PanelEditor: stores option group collapse state

* Refactor: adds props to OptionsGroup instead
This commit is contained in:
Hugo Häggmark 2020-04-22 19:56:37 +02:00 committed by GitHub
parent 319a0585a5
commit ca385805c9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 82 additions and 14 deletions

View File

@ -45,6 +45,7 @@ export const DynamicConfigValueEditor: React.FC<DynamicConfigValueEditorProps> =
if (isCollapsible) { if (isCollapsible) {
editor = ( editor = (
<OptionsGroup <OptionsGroup
id={item.name}
renderTitle={renderLabel(false, true)} renderTitle={renderLabel(false, true)}
className={css` className={css`
padding-left: 0; padding-left: 0;

View File

@ -195,6 +195,7 @@ export const DefaultFieldConfigEditor: React.FC<Props> = ({ data, onChange, conf
</> </>
); );
}} }}
id={`${k}/${i}`}
key={`${k}/${i}`} key={`${k}/${i}`}
> >
{groupedConfigs[k].map(c => { {groupedConfigs[k].map(c => {

View File

@ -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 { css, cx } from 'emotion';
import { GrafanaTheme } from '@grafana/data'; 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; title?: React.ReactNode;
renderTitle?: (isExpanded: boolean) => React.ReactNode; renderTitle?: (isExpanded: boolean) => React.ReactNode;
defaultToClosed?: boolean; defaultToClosed?: boolean;
className?: string; className?: string;
nested?: boolean; nested?: boolean;
persistMe?: boolean;
onToggle?: (isExpanded: boolean) => void; onToggle?: (isExpanded: boolean) => void;
} }
export const OptionsGroup: FC<Props> = ({ export const OptionsGroup: FC<OptionsGroupProps> = ({
id,
title,
children,
defaultToClosed,
renderTitle,
className,
nested = false,
persistMe = true,
onToggle,
}) => {
if (persistMe) {
return (
<CollapsibleSectionWithPersistence
id={id}
defaultToClosed={defaultToClosed}
className={className}
nested={nested}
renderTitle={renderTitle}
persistMe={persistMe}
title={title}
onToggle={onToggle}
>
{children}
</CollapsibleSectionWithPersistence>
);
}
return (
<CollapsibleSection
defaultToClosed={defaultToClosed}
className={className}
nested={nested}
renderTitle={renderTitle}
title={title}
onToggle={onToggle}
>
{children}
</CollapsibleSection>
);
};
const CollapsibleSectionWithPersistence: FC<OptionsGroupProps> = 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 <CollapsibleSection {...props} defaultToClosed={value.defaultToClosed} onToggle={onToggle} />;
});
const CollapsibleSection: FC<Omit<OptionsGroupProps, 'id' | 'persistMe'>> = ({
title, title,
children, children,
defaultToClosed, 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}]`;

View File

@ -121,7 +121,7 @@ export const OverrideEditor: React.FC<OverrideEditorProps> = ({
}; };
return ( return (
<OptionsGroup renderTitle={renderOverrideTitle}> <OptionsGroup renderTitle={renderOverrideTitle} id={name} key={name}>
<Field label={matcherLabel} description={matcherUi.description}> <Field label={matcherLabel} description={matcherUi.description}>
<matcherUi.component <matcherUi.component
matcher={matcherUi.matcher} matcher={matcherUi.matcher}

View File

@ -1,7 +1,7 @@
import React, { useMemo } from 'react'; import React, { useMemo } from 'react';
import { PanelOptionsEditorItem, PanelPlugin } from '@grafana/data'; import { PanelOptionsEditorItem, PanelPlugin } from '@grafana/data';
import { set as lodashSet, get as lodashGet } from 'lodash'; import { get as lodashGet, set as lodashSet } from 'lodash';
import { Label, Field } from '@grafana/ui'; import { Field, Label } from '@grafana/ui';
import groupBy from 'lodash/groupBy'; import groupBy from 'lodash/groupBy';
import { OptionsGroup } from './OptionsGroup'; import { OptionsGroup } from './OptionsGroup';
@ -50,7 +50,7 @@ export const PanelOptionsEditor: React.FC<PanelOptionsEditorProps<any>> = ({ plu
.filter(e => e !== null); .filter(e => e !== null);
return optionsToShow.length > 0 ? ( return optionsToShow.length > 0 ? (
<OptionsGroup title={c} defaultToClosed key={`${c}/${i}`}> <OptionsGroup title={c} defaultToClosed id={`${c}/${i}`} key={`${c}/${i}`}>
<div>{optionsToShow}</div> <div>{optionsToShow}</div>
</OptionsGroup> </OptionsGroup>
) : null; ) : null;

View File

@ -2,12 +2,12 @@ import React, { FC, useMemo, useRef } from 'react';
import { DashboardModel, PanelModel } from '../../state'; import { DashboardModel, PanelModel } from '../../state';
import { FieldConfigSource, PanelData, PanelPlugin, SelectableValue } from '@grafana/data'; import { FieldConfigSource, PanelData, PanelPlugin, SelectableValue } from '@grafana/data';
import { Counter, DataLinksInlineEditor, Field, Input, RadioButtonGroup, Select, Switch, TextArea } from '@grafana/ui'; import { Counter, DataLinksInlineEditor, Field, Input, RadioButtonGroup, Select, Switch, TextArea } from '@grafana/ui';
import { OptionsGroup } from './OptionsGroup';
import { getPanelLinksVariableSuggestions } from '../../../panel/panellinks/link_srv'; import { getPanelLinksVariableSuggestions } from '../../../panel/panellinks/link_srv';
import { getVariables } from '../../../variables/state/selectors'; import { getVariables } from '../../../variables/state/selectors';
import { PanelOptionsEditor } from './PanelOptionsEditor'; import { PanelOptionsEditor } from './PanelOptionsEditor';
import { AngularPanelOptions } from './AngularPanelOptions'; import { AngularPanelOptions } from './AngularPanelOptions';
import { VisualizationTab } from './VisualizationTab'; import { VisualizationTab } from './VisualizationTab';
import { OptionsGroup } from './OptionsGroup';
interface Props { interface Props {
panel: PanelModel; panel: PanelModel;
@ -48,7 +48,7 @@ export const PanelOptionsTab: FC<Props> = ({
}; };
// Fist common panel settings Title, description // Fist common panel settings Title, description
elements.push( elements.push(
<OptionsGroup title="Panel settings" key="Panel settings"> <OptionsGroup title="Panel settings" id="Panel settings" key="Panel settings">
<Field label="Panel title"> <Field label="Panel title">
<Input defaultValue={panel.title} onBlur={e => onPanelConfigChange('title', e.currentTarget.value)} /> <Input defaultValue={panel.title} onBlur={e => onPanelConfigChange('title', e.currentTarget.value)} />
</Field> </Field>
@ -65,7 +65,7 @@ export const PanelOptionsTab: FC<Props> = ({
); );
elements.push( elements.push(
<OptionsGroup title="Panel type" key="Panel type" defaultToClosed onToggle={focusVisPickerInput}> <OptionsGroup title="Panel type" id="Panel type" key="Panel type" defaultToClosed onToggle={focusVisPickerInput}>
<VisualizationTab panel={panel} ref={visTabInputRef} /> <VisualizationTab panel={panel} ref={visTabInputRef} />
</OptionsGroup> </OptionsGroup>
); );
@ -73,7 +73,7 @@ export const PanelOptionsTab: FC<Props> = ({
// Old legacy react editor // Old legacy react editor
if (plugin.editor && panel && !plugin.optionEditors) { if (plugin.editor && panel && !plugin.optionEditors) {
elements.push( elements.push(
<OptionsGroup title="Display" key="legacy react editor"> <OptionsGroup title="Display" id="legacy react editor" key="legacy react editor">
<plugin.editor <plugin.editor
data={data} data={data}
options={panel.getOptions()} options={panel.getOptions()}
@ -107,8 +107,9 @@ export const PanelOptionsTab: FC<Props> = ({
renderTitle={isExpanded => ( renderTitle={isExpanded => (
<>Panel links {!isExpanded && panelLinksCount > 0 && <Counter value={panelLinksCount} />}</> <>Panel links {!isExpanded && panelLinksCount > 0 && <Counter value={panelLinksCount} />}</>
)} )}
id="panel links"
key="panel links" key="panel links"
defaultToClosed={true} defaultToClosed
> >
<DataLinksInlineEditor <DataLinksInlineEditor
links={panel.links} links={panel.links}
@ -120,7 +121,7 @@ export const PanelOptionsTab: FC<Props> = ({
); );
elements.push( elements.push(
<OptionsGroup title="Panel repeats" key="panel repeats" defaultToClosed={true}> <OptionsGroup title="Panel repeats" id="panel repeats" key="panel repeats" defaultToClosed>
<Field <Field
label="Repeat by variable" label="Repeat by variable"
description="Repeat this panel for each value in the selected variable. description="Repeat this panel for each value in the selected variable.