diff --git a/packages/grafana-ui/src/types/icon.ts b/packages/grafana-ui/src/types/icon.ts
index 86bba8a126f..c032c2f95e8 100644
--- a/packages/grafana-ui/src/types/icon.ts
+++ b/packages/grafana-ui/src/types/icon.ts
@@ -65,6 +65,7 @@ export const getAvailableIcons = () =>
'external-link-alt',
'eye',
'eye-slash',
+ 'ellipsis-h',
'fa fa-spinner',
'favorite',
'file-alt',
diff --git a/public/app/features/dimensions/editors/ResourceCards.tsx b/public/app/features/dimensions/editors/ResourceCards.tsx
index 22bbeca39d3..0b328673237 100644
--- a/public/app/features/dimensions/editors/ResourceCards.tsx
+++ b/public/app/features/dimensions/editors/ResourceCards.tsx
@@ -1,21 +1,27 @@
import React, { memo, CSSProperties } from 'react';
import { areEqual, FixedSizeGrid as Grid } from 'react-window';
import AutoSizer from 'react-virtualized-auto-sizer';
-import { GrafanaTheme2, SelectableValue } from '@grafana/data';
+import { GrafanaTheme2 } from '@grafana/data';
import { useTheme2, stylesFactory } from '@grafana/ui';
import SVG from 'react-inlinesvg';
-import { css } from '@emotion/css';
+import { css, cx } from '@emotion/css';
+import { ResourceItem } from './ResourcePicker';
interface CellProps {
columnIndex: number;
rowIndex: number;
style: CSSProperties;
- data: any;
+ data: {
+ cards: ResourceItem[];
+ columnCount: number;
+ onChange: (value: string) => void;
+ selected?: string;
+ };
}
function Cell(props: CellProps) {
const { columnIndex, rowIndex, style, data } = props;
- const { cards, columnCount, onChange, folder } = data;
+ const { cards, columnCount, onChange, selected } = data;
const singleColumnIndex = columnIndex + rowIndex * columnCount;
const card = cards[singleColumnIndex];
const theme = useTheme2();
@@ -24,8 +30,12 @@ function Cell(props: CellProps) {
return (
{card && (
-
onChange(`${folder.value}/${card.value}`)}>
- {folder.value.includes('icons') ? (
+
onChange(card.value)}
+ >
+ {card.imgUrl.endsWith('.svg') ? (
) : (

@@ -41,9 +51,10 @@ const getStyles = stylesFactory((theme: GrafanaTheme2) => {
return {
card: css`
display: inline-block;
- width: 80px;
- height: 80px;
+ width: 90px;
+ height: 90px;
margin: 0.75rem;
+ margin-left: 15px;
text-align: center;
cursor: pointer;
position: relative;
@@ -51,15 +62,20 @@ const getStyles = stylesFactory((theme: GrafanaTheme2) => {
border: 1px solid transparent;
border-radius: 8px;
padding-top: 6px;
-
:hover {
border-color: ${theme.colors.action.hover};
box-shadow: ${theme.shadows.z2};
}
`,
+ selected: css`
+ border: 2px solid ${theme.colors.primary.main};
+ :hover {
+ border-color: ${theme.colors.primary.main};
+ }
+ `,
img: css`
- width: 50px;
- height: 50px;
+ width: 40px;
+ height: 40px;
object-fit: cover;
vertical-align: middle;
fill: ${theme.colors.text.primary};
@@ -72,23 +88,28 @@ const getStyles = stylesFactory((theme: GrafanaTheme2) => {
display: block;
overflow: hidden;
`,
+ grid: css`
+ border: 1px solid ${theme.colors.border.medium};
+ `,
};
});
interface CardProps {
onChange: (value: string) => void;
- cards: SelectableValue[];
- currentFolder: SelectableValue
| undefined;
+ cards: ResourceItem[];
+ value?: string;
}
export const ResourceCards = (props: CardProps) => {
- const { onChange, cards, currentFolder: folder } = props;
+ const { onChange, cards, value } = props;
+ const theme = useTheme2();
+ const styles = getStyles(theme);
return (
-
+
{({ width, height }) => {
- const cardWidth = 80;
- const cardHeight = 80;
+ const cardWidth = 90;
+ const cardHeight = 90;
const columnCount = Math.floor(width / cardWidth);
const rowCount = Math.ceil(cards.length / columnCount);
return (
@@ -99,7 +120,8 @@ export const ResourceCards = (props: CardProps) => {
columnWidth={cardWidth}
rowCount={rowCount}
rowHeight={cardHeight}
- itemData={{ cards, columnCount, onChange, folder }}
+ itemData={{ cards, columnCount, onChange, selected: value }}
+ className={styles.grid}
>
{memo(Cell, areEqual)}
diff --git a/public/app/features/dimensions/editors/ResourceDimensionEditor.tsx b/public/app/features/dimensions/editors/ResourceDimensionEditor.tsx
index 003de0a9133..593e20893a7 100644
--- a/public/app/features/dimensions/editors/ResourceDimensionEditor.tsx
+++ b/public/app/features/dimensions/editors/ResourceDimensionEditor.tsx
@@ -75,7 +75,13 @@ export const ResourceDimensionEditor: FC<
<>
{isOpen && (
setOpen(false)} closeOnEscape>
-
+
)}
{showSourceRadio && (
@@ -106,9 +112,9 @@ export const ResourceDimensionEditor: FC<
readOnly={true}
onClick={openModal}
prefix={srcPath && }
+ suffix={}
/>
-
)}
{mode === ResourceDimensionMode.Mapping && (
@@ -138,5 +144,6 @@ const getStyles = (theme: GrafanaTheme2) => ({
vertical-align: middle;
display: inline-block;
fill: currentColor;
+ max-width: 25px;
`,
});
diff --git a/public/app/features/dimensions/editors/ResourcePicker.tsx b/public/app/features/dimensions/editors/ResourcePicker.tsx
index 4672070bb1d..f34b73cee4a 100644
--- a/public/app/features/dimensions/editors/ResourcePicker.tsx
+++ b/public/app/features/dimensions/editors/ResourcePicker.tsx
@@ -1,16 +1,5 @@
-import React, { useEffect, useState, ChangeEvent } from 'react';
-import {
- TabContent,
- Button,
- Select,
- Input,
- Spinner,
- TabsBar,
- Tab,
- StringValueEditor,
- useTheme2,
- stylesFactory,
-} from '@grafana/ui';
+import React, { useEffect, useState } from 'react';
+import { Button, Select, FilterInput, useTheme2, stylesFactory, Field, Modal, Label, Input } from '@grafana/ui';
import { GrafanaTheme2, SelectableValue } from '@grafana/data';
import { ResourceCards } from './ResourceCards';
import SVG from 'react-inlinesvg';
@@ -25,17 +14,24 @@ interface Props {
onChange: (value?: string) => void;
mediaType: 'icon' | 'image';
folderName: ResourceFolderName;
+ setOpen: (value: boolean) => void;
}
-interface ResourceItem {
+export interface ResourceItem {
label: string;
- value: string;
+ value: string; // includes folder
search: string;
imgUrl: string;
}
+const sourceOptions = [
+ { label: `Folder`, value: 'folder' },
+ { label: 'URL', value: 'url' },
+ { label: 'Upload', value: 'upload' },
+];
+
export function ResourcePicker(props: Props) {
- const { value, onChange, mediaType, folderName } = props;
+ const { value, onChange, mediaType, folderName, setOpen } = props;
const folders = getFolders(mediaType).map((v) => ({
label: v,
value: v,
@@ -43,12 +39,13 @@ export function ResourcePicker(props: Props) {
const folderOfCurrentValue = value || folderName ? folderIfExists(folders, value ?? folderName) : folders[0];
const [currentFolder, setCurrentFolder] = useState>(folderOfCurrentValue);
- const [tabs, setTabs] = useState([
- { label: 'Select', active: true },
- // { label: 'Upload', active: false },
- ]);
const [directoryIndex, setDirectoryIndex] = useState([]);
const [filteredIndex, setFilteredIndex] = useState([]);
+ // select between existing icon folder, url, or upload
+ const [source, setSource] = useState>(sourceOptions[0]);
+ // pass on new value to confirm button and to show in preview
+ const [newValue, setNewValue] = useState(value ?? '');
+ const [searchQuery, setSearchQuery] = useState();
const theme = useTheme2();
const styles = getStyles(theme);
@@ -71,7 +68,7 @@ export function ResourcePicker(props: Props) {
if (filter(item)) {
const idx = item.name.lastIndexOf('.');
cards.push({
- value: item.name,
+ value: `${folder}/${item.name}`,
label: item.name,
search: (idx ? item.name.substr(0, idx) : item.name).toLowerCase(),
imgUrl: `public/${folder}/${item.name}`,
@@ -86,8 +83,7 @@ export function ResourcePicker(props: Props) {
}
}, [mediaType, currentFolder]);
- const onChangeSearch = (e: ChangeEvent) => {
- let query = e.currentTarget.value;
+ const onChangeSearch = (query: string) => {
if (query) {
query = query.toLowerCase();
setFilteredIndex(directoryIndex.filter((card) => card.search.includes(query)));
@@ -95,54 +91,72 @@ export function ResourcePicker(props: Props) {
setFilteredIndex(directoryIndex);
}
};
- const imgSrc = getPublicOrAbsoluteUrl(value!);
-
+ const imgSrc = getPublicOrAbsoluteUrl(newValue!);
+ let shortName = newValue?.substring(newValue.lastIndexOf('/') + 1, newValue.lastIndexOf('.'));
+ if (shortName.length > 20) {
+ shortName = shortName.substring(0, 20) + '...';
+ }
return (
-
- {value && (
- <>
- {mediaType === 'icon' &&
}
- {mediaType === 'image' &&

}
- >
- )}
-
-
+
+
+
+
+
+ {source?.value === 'folder' && (
+ <>
+
+
+
+
+ {
+ onChangeSearch(v);
+ setSearchQuery(v);
+ }}
+ />
+
+ >
+ )}
+ {source?.value === 'url' && (
+
+ setNewValue(e.currentTarget.value)} value={newValue} />
+
+ )}
+
+
+
+
+ {mediaType === 'icon' &&
}
+ {mediaType === 'image' && newValue &&

}
+
+
+
+
-
- {tabs.map((tab, index) => (
- setTabs(tabs.map((tab, idx) => ({ ...tab, active: idx === index })))}
- />
- ))}
-
-
- {tabs[0].active && (
-
-
-
- {filteredIndex ? (
-
-
-
- ) : (
-
- )}
-
- )}
- {/* TODO: add file upload
+ {source?.value === 'folder' && filteredIndex && (
+
+ setNewValue(v)} value={newValue} />
+
+ )}
+
+
+
+
+
+ {/* TODO: add file upload
{tabs[1].active && (
console.log('file', currentTarget?.files && currentTarget.files[0])}
className={styles.tabContent}
/>
)} */}
-
);
}
@@ -150,7 +164,10 @@ export function ResourcePicker(props: Props) {
const getStyles = stylesFactory((theme: GrafanaTheme2) => {
return {
cardsWrapper: css`
- height: calc(100vh - 480px);
+ height: calc(100vh - 550px);
+ min-height: 50px;
+ margin-top: 5px;
+ max-width: 680px;
`,
tabContent: css`
margin-top: 20px;
@@ -158,18 +175,34 @@ const getStyles = stylesFactory((theme: GrafanaTheme2) => {
margin-top: 10px;
},
`,
- currentItem: css`
+ iconPreview: css`
+ width: 95px;
+ height: 79px;
+ border: 1px solid ${theme.colors.border.medium};
display: flex;
- justify-content: space-between;
align-items: center;
- column-gap: 2px;
- margin: -18px 0px 18px 0px;
+ justify-content: center;
+ `,
+ iconContainer: css`
+ display: flex;
+ flex-direction: column;
+ width: 40%;
+ align-items: center;
`,
img: css`
- width: 40px;
- height: 40px;
+ width: 49px;
+ height: 49px;
fill: ${theme.colors.text.primary};
`,
+ child: css`
+ width: 60%;
+ `,
+ upper: css`
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: space-between;
+ align-items: center;
+ `,
};
});