Files
grafana/packages/grafana-ui/src/components/TransformersUI/OrganizeFieldsTransformerEditor.tsx
Torkel Ödegaard 8b7decf57b Styles & Theme: Inline form styles polish & tweaks (#23521)
* Styles: Refactoring and tweaks to inline form styles

* Minor change

* Minor fix

* Updated snapshot

* tweaks

* minor tweak

* Updated modal style

* Updated snapshot

* Updated more snapshots
2020-04-13 09:58:40 +02:00

227 lines
6.3 KiB
TypeScript

import React, { useCallback, useMemo } from 'react';
import { css, cx } from 'emotion';
import { DragDropContext, Draggable, Droppable, DropResult } from 'react-beautiful-dnd';
import {
createOrderFieldsComparer,
DataFrame,
DataTransformerID,
GrafanaTheme,
OrganizeFieldsTransformerOptions,
standardTransformers,
TransformerRegistyItem,
TransformerUIProps,
} from '@grafana/data';
import { stylesFactory, useTheme } from '../../themes';
import { Button } from '../Button/Button';
import { VerticalGroup } from '../Layout/Layout';
import { Input } from '../Input/Input';
interface OrganizeFieldsTransformerEditorProps extends TransformerUIProps<OrganizeFieldsTransformerOptions> {}
const OrganizeFieldsTransformerEditor: React.FC<OrganizeFieldsTransformerEditorProps> = props => {
const { options, input, onChange } = props;
const { indexByName, excludeByName, renameByName } = options;
const fieldNames = useMemo(() => fieldNamesFromInput(input), [input]);
const orderedFieldNames = useMemo(() => orderFieldNamesByIndex(fieldNames, indexByName), [fieldNames, indexByName]);
const onToggleVisibility = useCallback(
(field: string, shouldExclude: boolean) => {
onChange({
...options,
excludeByName: {
...excludeByName,
[field]: shouldExclude,
},
});
},
[onChange, excludeByName, indexByName]
);
const onDragEnd = useCallback(
(result: DropResult) => {
if (!result || !result.destination) {
return;
}
const startIndex = result.source.index;
const endIndex = result.destination.index;
if (startIndex === endIndex) {
return;
}
onChange({
...options,
indexByName: reorderToIndex(fieldNames, startIndex, endIndex),
});
},
[onChange, indexByName, excludeByName, fieldNames]
);
const onRenameField = useCallback(
(from: string, to: string) => {
onChange({
...options,
renameByName: {
...options.renameByName,
[from]: to,
},
});
},
[onChange, fieldNames, renameByName]
);
return (
<VerticalGroup>
<DragDropContext onDragEnd={onDragEnd}>
<Droppable droppableId="sortable-fields-transformer" direction="vertical">
{provided => (
<div ref={provided.innerRef} {...provided.droppableProps}>
{orderedFieldNames.map((fieldName, index) => {
return (
<DraggableFieldName
fieldName={fieldName}
index={index}
onToggleVisibility={onToggleVisibility}
onRenameField={onRenameField}
visible={!excludeByName[fieldName]}
key={fieldName}
/>
);
})}
{provided.placeholder}
</div>
)}
</Droppable>
</DragDropContext>
</VerticalGroup>
);
};
interface DraggableFieldProps {
fieldName: string;
index: number;
visible: boolean;
onToggleVisibility: (fieldName: string, isVisible: boolean) => void;
onRenameField: (from: string, to: string) => void;
}
const DraggableFieldName: React.FC<DraggableFieldProps> = ({
fieldName,
index,
visible,
onToggleVisibility,
onRenameField,
}) => {
const theme = useTheme();
const styles = getFieldNameStyles(theme);
return (
<Draggable draggableId={fieldName} index={index}>
{provided => (
<div
className={styles.container}
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
>
<div className={styles.left}>
<i className={cx('fa fa-ellipsis-v', styles.draggable)} />
<Button
className={styles.toggle}
variant="link"
size="md"
icon={visible ? 'eye' : 'eye-slash'}
onClick={() => onToggleVisibility(fieldName, visible)}
/>
<span className={styles.name}>{fieldName}</span>
</div>
<div className={styles.right}>
<Input
placeholder={`Rename ${fieldName}`}
onChange={event => onRenameField(fieldName, event.currentTarget.value)}
/>
</div>
</div>
)}
</Draggable>
);
};
const getFieldNameStyles = stylesFactory((theme: GrafanaTheme) => ({
container: css`
display: flex;
align-items: center;
margin-top: 8px;
`,
left: css`
width: 35%;
padding: 0 8px;
border-radius: 3px;
background-color: ${theme.colors.bg2};
`,
right: css`
width: 65%;
margin-left: 8px;
`,
toggle: css`
padding: 5px;
margin: 0 5px;
`,
draggable: css`
font-size: ${theme.typography.size.md};
opacity: 0.4;
`,
name: css`
font-size: ${theme.typography.size.sm};
font-weight: ${theme.typography.weight.semibold};
`,
}));
const reorderToIndex = (fieldNames: string[], startIndex: number, endIndex: number) => {
const result = Array.from(fieldNames);
const [removed] = result.splice(startIndex, 1);
result.splice(endIndex, 0, removed);
return result.reduce((nameByIndex, fieldName, index) => {
nameByIndex[fieldName] = index;
return nameByIndex;
}, {} as Record<string, number>);
};
const orderFieldNamesByIndex = (fieldNames: string[], indexByName: Record<string, number> = {}): string[] => {
if (!indexByName || Object.keys(indexByName).length === 0) {
return fieldNames;
}
const comparer = createOrderFieldsComparer(indexByName);
return fieldNames.sort(comparer);
};
const fieldNamesFromInput = (input: DataFrame[]): string[] => {
if (!Array.isArray(input)) {
return [] as string[];
}
return Object.keys(
input.reduce((names, frame) => {
if (!frame || !Array.isArray(frame.fields)) {
return names;
}
return frame.fields.reduce((names, field) => {
names[field.name] = null;
return names;
}, names);
}, {} as Record<string, null>)
);
};
export const organizeFieldsTransformRegistryItem: TransformerRegistyItem<OrganizeFieldsTransformerOptions> = {
id: DataTransformerID.organize,
editor: OrganizeFieldsTransformerEditor,
transformation: standardTransformers.organizeFieldsTransformer,
name: 'Organize fields',
description: 'Order, filter and rename fields',
};