New panel edit: data links tweaks (#22304)

* Add basic layout components

* Add some love to data links in new panel edit

* fix prettier
This commit is contained in:
Dominik Prokop
2020-02-19 18:55:44 +01:00
committed by GitHub
parent b0b46991ec
commit f82a6aa0d0
5 changed files with 299 additions and 203 deletions

View File

@ -0,0 +1,51 @@
import { DataFrame, DataLink, VariableSuggestion } from '@grafana/data';
import React, { FC, useState } from 'react';
import { DataLinkEditor } from '../DataLinkEditor';
import { HorizontalGroup } from '../../Layout/Layout';
import Forms from '../../Forms';
interface DataLinkEditorModalContentProps {
link: DataLink;
index: number;
data: DataFrame[];
suggestions: VariableSuggestion[];
onChange: (index: number, ink: DataLink) => void;
onClose: () => void;
}
export const DataLinkEditorModalContent: FC<DataLinkEditorModalContentProps> = ({
link,
index,
suggestions,
onChange,
onClose,
}) => {
const [dirtyLink, setDirtyLink] = useState(link);
return (
<>
<DataLinkEditor
value={dirtyLink}
index={index}
isLast={false}
suggestions={suggestions}
onChange={(index, link) => {
setDirtyLink(link);
}}
onRemove={() => {}}
/>
<HorizontalGroup>
<Forms.Button
onClick={() => {
onChange(index, dirtyLink);
onClose();
}}
>
Save
</Forms.Button>
<Forms.Button variant="secondary" onClick={() => onClose()}>
Cancel
</Forms.Button>
</HorizontalGroup>
</>
);
};

View File

@ -0,0 +1,134 @@
import { DataFrame, DataLink, GrafanaTheme, VariableSuggestion } from '@grafana/data';
import React, { useState } from 'react';
import { css } from 'emotion';
import Forms from '../../Forms';
import cloneDeep from 'lodash/cloneDeep';
import { Modal } from '../../Modal/Modal';
import { FullWidthButtonContainer } from '../../Button/FullWidthButtonContainer';
import { selectThemeVariant, stylesFactory, useTheme } from '../../../themes';
import { DataLinksListItem } from './DataLinksListItem';
import { DataLinkEditorModalContent } from './DataLinkEditorModalContent';
interface DataLinksInlineEditorProps {
links: DataLink[];
onChange: (links: DataLink[]) => void;
suggestions: VariableSuggestion[];
data: DataFrame[];
}
export const DataLinksInlineEditor: React.FC<DataLinksInlineEditorProps> = ({ links, onChange, suggestions, data }) => {
const theme = useTheme();
const [editIndex, setEditIndex] = useState();
const isEditing = editIndex !== null && editIndex !== undefined;
const styles = getDataLinksInlineEditorStyles(theme);
const onDataLinkChange = (index: number, link: DataLink) => {
const update = cloneDeep(links);
update[index] = link;
onChange(update);
};
const onDataLinkAdd = () => {
let update = cloneDeep(links);
if (update) {
update.push({
title: '',
url: '',
});
} else {
update = [
{
title: '',
url: '',
},
];
}
setEditIndex(update.length - 1);
onChange(update);
};
const onDataLinkRemove = (index: number) => {
const update = cloneDeep(links);
update.splice(index, 1);
onChange(update);
};
return (
<>
{links && (
<div className={styles.wrapper}>
{links.map((l, i) => {
return (
<DataLinksListItem
key={`${l.title}/${i}`}
index={i}
link={l}
onChange={onDataLinkChange}
onEdit={() => setEditIndex(i)}
onRemove={() => onDataLinkRemove(i)}
data={data}
suggestions={suggestions}
/>
);
})}
</div>
)}
{isEditing && (
<Modal
title="Edit data link"
isOpen={isEditing}
onDismiss={() => {
setEditIndex(null);
}}
>
<DataLinkEditorModalContent
index={editIndex}
link={links[editIndex]}
data={data}
onChange={onDataLinkChange}
onClose={() => setEditIndex(null)}
suggestions={suggestions}
/>
</Modal>
)}
<FullWidthButtonContainer>
<Forms.Button size="sm" icon="fa fa-plus" onClick={onDataLinkAdd}>
Add data link
</Forms.Button>
</FullWidthButtonContainer>
</>
);
};
const getDataLinksInlineEditorStyles = stylesFactory((theme: GrafanaTheme) => {
const borderColor = selectThemeVariant(
{
light: theme.colors.gray85,
dark: theme.colors.dark9,
},
theme.type
);
const shadow = selectThemeVariant(
{
light: theme.colors.gray85,
dark: theme.colors.black,
},
theme.type
);
return {
wrapper: css`
border: 1px dashed ${borderColor};
margin-bottom: ${theme.spacing.md};
transition: box-shadow 0.5s cubic-bezier(0.19, 1, 0.22, 1);
box-shadow: none;
&:hover {
box-shadow: 0 0 10px ${shadow};
}
`,
};
});

View File

@ -0,0 +1,98 @@
import React, { FC } from 'react';
import { css, cx } from 'emotion';
import { DataFrame, DataLink, GrafanaTheme, VariableSuggestion } from '@grafana/data';
import { selectThemeVariant, stylesFactory, useTheme } from '../../../themes';
import { HorizontalGroup } from '../../Layout/Layout';
import { Icon } from '../../Icon/Icon';
interface DataLinksListItemProps {
index: number;
link: DataLink;
data: DataFrame[];
onChange: (index: number, link: DataLink) => void;
onEdit: () => void;
onRemove: () => void;
suggestions: VariableSuggestion[];
isEditing?: boolean;
}
export const DataLinksListItem: FC<DataLinksListItemProps> = ({
index,
link,
data,
onChange,
suggestions,
isEditing,
onEdit,
onRemove,
}) => {
const theme = useTheme();
const styles = getDataLinkListItemStyles(theme);
const hasTitle = link.title.trim() !== '';
const hasUrl = link.url.trim() !== '';
return (
<div className={styles.wrapper}>
<HorizontalGroup justify="space-between">
<div>
<div className={cx(!hasTitle && styles.notConfigured)}>{hasTitle ? link.title : 'No data link provided'}</div>
<div className={cx(!hasUrl && styles.notConfigured, styles.url)}>{hasUrl ? link.url : 'No url provided'}</div>
</div>
<HorizontalGroup>
<div onClick={onEdit} className={styles.action}>
<Icon name="pencil" />
</div>
<div onClick={onRemove} className={cx(styles.action, styles.remove)}>
<Icon name="trash" />
</div>
</HorizontalGroup>
</HorizontalGroup>
</div>
);
};
const getDataLinkListItemStyles = stylesFactory((theme: GrafanaTheme) => {
const borderColor = selectThemeVariant(
{
light: theme.colors.gray85,
dark: theme.colors.dark9,
},
theme.type
);
const bg = selectThemeVariant(
{
light: theme.colors.white,
dark: theme.colors.dark1,
},
theme.type
);
return {
wrapper: css`
border-bottom: 1px dashed ${borderColor};
padding: ${theme.spacing.sm};
transition: background 0.5s cubic-bezier(0.19, 1, 0.22, 1);
&:last-child {
border-bottom: 0;
}
&:hover {
background: ${bg};
}
`,
action: css`
cursor: pointer;
`,
notConfigured: css`
font-style: italic;
`,
url: css`
font-size: ${theme.typography.size.sm};
`,
remove: css`
color: ${theme.colors.red88};
`,
};
});

View File

@ -1,18 +1,6 @@
import {
FieldOverrideContext,
FieldConfigEditorProps,
DataLink,
FieldOverrideEditorProps,
DataFrame,
} from '@grafana/data';
import React, { FC, useState } from 'react';
import { css, cx } from 'emotion';
import Forms from '../Forms';
import { Modal } from '../Modal/Modal';
import { DataLinkEditor } from '../DataLinks/DataLinkEditor';
import cloneDeep from 'lodash/cloneDeep';
import { VariableSuggestion } from '@grafana/data';
import { FullWidthButtonContainer } from '../Button/FullWidthButtonContainer';
import { FieldOverrideContext, FieldConfigEditorProps, DataLink, FieldOverrideEditorProps } from '@grafana/data';
import React from 'react';
import { DataLinksInlineEditor } from '../DataLinks/DataLinksInlineEditor/DataLinksInlineEditor';
export interface DataLinksFieldConfigSettings {}
@ -29,45 +17,13 @@ export const DataLinksValueEditor: React.FC<FieldConfigEditorProps<DataLink[], D
onChange,
context,
}) => {
const onDataLinkChange = (index: number, link: DataLink) => {
const links = cloneDeep(value);
links[index] = link;
onChange(links);
};
const onDataLinkAdd = () => {
const links = cloneDeep(value) || [];
links.push({
title: '',
url: '',
});
onChange(links);
};
return (
<>
{value &&
value.map((l, i) => {
return (
<DataLinksListItem
key={`${l.title}/${i}`}
index={i}
link={l}
onChange={onDataLinkChange}
data={context.data}
suggestions={context.getSuggestions ? context.getSuggestions() : []}
/>
);
})}
<FullWidthButtonContainer>
<Forms.Button size="sm" icon="fa fa-plus" onClick={onDataLinkAdd}>
Add data link
</Forms.Button>
</FullWidthButtonContainer>
</>
<DataLinksInlineEditor
links={value}
onChange={onChange}
data={context.data}
suggestions={context.getSuggestions ? context.getSuggestions() : []}
/>
);
};
@ -75,156 +31,13 @@ export const DataLinksOverrideEditor: React.FC<FieldOverrideEditorProps<DataLink
value,
onChange,
context,
item,
}) => {
const onDataLinkChange = (index: number, link: DataLink) => {
const links = cloneDeep(value);
links[index] = link;
onChange(links);
};
const onDataLinkAdd = () => {
let links = cloneDeep(value);
if (links) {
links.push({
title: '',
url: '',
});
} else {
links = [
{
title: '',
url: '',
},
];
}
onChange(links);
};
return (
<>
{value &&
value.map((l, i) => {
return (
<DataLinksListItem
key={`${l.title}/${i}`}
index={i}
link={l}
onChange={onDataLinkChange}
data={context.data}
suggestions={context.getSuggestions ? context.getSuggestions() : []}
/>
);
})}
<Forms.Button size="sm" icon="fa fa-plus" onClick={onDataLinkAdd}>
Create data link
</Forms.Button>
</>
);
};
interface DataLinksListItemProps {
index: number;
link: DataLink;
data: DataFrame[];
onChange: (index: number, link: DataLink) => void;
suggestions: VariableSuggestion[];
}
const DataLinksListItem: FC<DataLinksListItemProps> = ({ index, link, data, onChange, suggestions }) => {
const [isEditing, setIsEditing] = useState(false);
const style = () => {
return {
wrapper: css`
display: flex;
justify-content: space-between;
`,
action: css`
flex-shrink: 0;
flex-grow: 0;
`,
noTitle: css`
font-style: italic;
`,
};
};
const styles = style();
const hasTitle = link.title.trim() !== '';
return (
<>
<div className={styles.wrapper}>
<div className={cx(!hasTitle && styles.noTitle)}>{hasTitle ? link.title : 'Edit data link'}</div>
<div>
<Forms.Button size="sm" icon="fa fa-pencil" variant="link" onClick={() => setIsEditing(true)} />
</div>
</div>
{isEditing && (
<Modal
title="Edit data link"
isOpen={isEditing}
onDismiss={() => {
setIsEditing(false);
}}
>
<DataLinkEditorModalContent
index={index}
link={link}
data={data}
onChange={onChange}
onClose={() => setIsEditing(false)}
suggestions={suggestions}
/>
</Modal>
)}
</>
);
};
interface DataLinkEditorModalContentProps {
link: DataLink;
index: number;
data: DataFrame[];
suggestions: VariableSuggestion[];
onChange: (index: number, ink: DataLink) => void;
onClose: () => void;
}
const DataLinkEditorModalContent: FC<DataLinkEditorModalContentProps> = ({
link,
index,
data,
suggestions,
onChange,
onClose,
}) => {
const [dirtyLink, setDirtyLink] = useState(link);
return (
<>
<DataLinkEditor
value={dirtyLink}
index={index}
isLast={false}
suggestions={suggestions}
onChange={(index, link) => {
setDirtyLink(link);
}}
onRemove={() => {}}
/>
<Forms.Button
onClick={() => {
onChange(index, dirtyLink);
onClose();
}}
>
Save
</Forms.Button>
<Forms.Button onClick={() => onClose()}>Cancel</Forms.Button>
</>
<DataLinksInlineEditor
links={value}
onChange={onChange}
data={context.data}
suggestions={context.getSuggestions ? context.getSuggestions() : []}
/>
);
};

View File

@ -1,7 +1,7 @@
import React from 'react';
import { css } from 'emotion';
import { stylesFactory, useTheme } from '../../themes';
import { GrafanaTheme } from '@grafana/data';
import { stylesFactory, useTheme } from '../../themes';
enum Orientation {
Horizontal,