Files
grafana/public/app/plugins/panel/xychart/ManualEditor.tsx
Ashley Harrison 47f8717149 React: Use new JSX transform (#88802)
* update eslint, tsconfig + esbuild to handle new jsx transform

* remove thing that breaks the new jsx transform

* remove react imports

* adjust grafana-icons build

* is this the correct syntax?

* try this

* well this was much easier than expected...

* change grafana-plugin-configs webpack config

* fixes

* fix lockfile

* fix 2 more violations

* use path.resolve instead of require.resolve

* remove react import

* fix react imports

* more fixes

* remove React import

* remove import React from docs

* remove another react import
2024-06-25 12:43:47 +01:00

200 lines
5.6 KiB
TypeScript

import { css, cx } from '@emotion/css';
import { useState, useEffect, useMemo } from 'react';
import {
GrafanaTheme2,
StandardEditorProps,
FieldNamePickerBaseNameMode,
StandardEditorsRegistryItem,
getFrameDisplayName,
} from '@grafana/data';
import { Button, Field, IconButton, Select, useStyles2 } from '@grafana/ui';
import { LayerName } from 'app/core/components/Layers/LayerName';
import { ScatterSeriesEditor } from './ScatterSeriesEditor';
import { Options, ScatterSeriesConfig, defaultFieldConfig } from './panelcfg.gen';
export const ManualEditor = ({
value,
onChange,
context,
}: StandardEditorProps<ScatterSeriesConfig[], unknown, Options>) => {
const frameNames = useMemo(() => {
if (context?.data?.length) {
return context.data.map((frame, index) => ({
value: index,
label: `${getFrameDisplayName(frame, index)} (index: ${index}, rows: ${frame.length})`,
}));
}
return [{ value: 0, label: 'First result' }];
}, [context.data]);
const [selected, setSelected] = useState(0);
const style = useStyles2(getStyles);
const onFieldChange = (val: unknown | undefined, index: number, field: string) => {
onChange(
value.map((obj, i) => {
if (i === index) {
return { ...obj, [field]: val };
}
return obj;
})
);
};
const createNewSeries = () => {
onChange([
...value,
{
pointColor: undefined,
pointSize: defaultFieldConfig.pointSize,
},
]);
setSelected(value.length);
};
// Component-did-mount callback to check if a new series should be created
useEffect(() => {
if (!value?.length) {
createNewSeries(); // adds a new series
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const onSeriesDelete = (index: number) => {
onChange(value.filter((_, i) => i !== index));
};
// const { options } = context;
const getRowStyle = (index: number) => {
return index === selected ? `${style.row} ${style.sel}` : style.row;
};
return (
<>
<Button icon="plus" size="sm" variant="secondary" onClick={createNewSeries} className={style.marginBot}>
Add series
</Button>
<div className={style.marginBot}>
{value.map((series, index) => {
return (
<div
key={`series/${index}`}
className={getRowStyle(index)}
onClick={() => setSelected(index)}
role="button"
aria-label={`Select series ${index + 1}`}
tabIndex={0}
onKeyPress={(e) => {
if (e.key === 'Enter') {
setSelected(index);
}
}}
>
<LayerName
name={series.name ?? `Series ${index + 1}`}
onChange={(v) => onFieldChange(v, index, 'name')}
/>
<IconButton
name="trash-alt"
title={'remove'}
className={cx(style.actionIcon)}
onClick={() => onSeriesDelete(index)}
tooltip="Delete series"
/>
</div>
);
})}
</div>
{selected >= 0 && value[selected] && (
<>
{frameNames.length > 1 && (
<Field label={'Data'}>
<Select
isClearable={false}
options={frameNames}
placeholder={'Change filter'}
value={
frameNames.find((v) => {
return v.value === value[selected].frame;
}) ?? 0
}
onChange={(val) => {
onChange(
value.map((obj, i) => {
if (i === selected) {
if (val === null) {
return { ...value[i], frame: undefined };
}
return { ...value[i], frame: val?.value!, x: undefined, y: undefined };
}
return obj;
})
);
}}
/>
</Field>
)}
<ScatterSeriesEditor
key={`series/${selected}`}
baseNameMode={FieldNamePickerBaseNameMode.ExcludeBaseNames}
item={{} as StandardEditorsRegistryItem}
context={context}
value={value[selected]}
onChange={(val) => {
onChange(
value.map((obj, i) => {
if (i === selected) {
return val!;
}
return obj;
})
);
}}
frameFilter={value[selected].frame ?? undefined}
/>
</>
)}
</>
);
};
const getStyles = (theme: GrafanaTheme2) => ({
marginBot: css({
marginBottom: '20px',
}),
row: css({
padding: `${theme.spacing(0.5, 1)}`,
borderRadius: `${theme.shape.radius.default}`,
background: `${theme.colors.background.secondary}`,
minHeight: `${theme.spacing(4)}`,
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
marginBottom: '3px',
cursor: 'pointer',
border: `1px solid ${theme.components.input.borderColor}`,
'&:hover': {
border: `1px solid ${theme.components.input.borderHover}`,
},
}),
sel: css({
border: `1px solid ${theme.colors.primary.border}`,
'&:hover': {
border: `1px solid ${theme.colors.primary.border}`,
},
}),
actionIcon: css({
color: `${theme.colors.text.secondary}`,
'&:hover': {
color: `${theme.colors.text}`,
},
}),
});