mirror of
https://github.com/grafana/grafana.git
synced 2025-02-16 18:34:52 -06:00
* Chore: Refactor Search/Folder view types into DashboardViewItem * uid is not optional in api * rename queryResultToNestedFolderItem function * Fix error from locationInfo being empty * change queryResultToViewItem to take view instead * Fix sortMeta fields not showing on search cards * Show correct parent for panel search results
166 lines
4.9 KiB
TypeScript
166 lines
4.9 KiB
TypeScript
import { css } from '@emotion/css';
|
|
import classNames from 'classnames';
|
|
import React, { useState } from 'react';
|
|
import SVG from 'react-inlinesvg';
|
|
|
|
import { GrafanaTheme2 } from '@grafana/data';
|
|
import { Icon, Spinner, TagList, useTheme2 } from '@grafana/ui';
|
|
|
|
import { DashboardViewItem } from '../types';
|
|
|
|
import { getThumbnailURL } from './SearchCard';
|
|
|
|
export interface Props {
|
|
className?: string;
|
|
imageHeight: number;
|
|
imageWidth: number;
|
|
item: DashboardViewItem;
|
|
lastUpdated?: string | null;
|
|
onClick?: (event: React.MouseEvent<HTMLAnchorElement>) => void;
|
|
}
|
|
|
|
export function SearchCardExpanded({ className, imageHeight, imageWidth, item, lastUpdated, onClick }: Props) {
|
|
const theme = useTheme2();
|
|
const [hasImage, setHasImage] = useState(true);
|
|
const imageSrc = getThumbnailURL(item.uid!, theme.isLight);
|
|
const styles = getStyles(theme, imageHeight, imageWidth);
|
|
|
|
const folderTitle = item.parentTitle || 'General';
|
|
|
|
return (
|
|
<a className={classNames(className, styles.card)} key={item.uid} href={item.url} onClick={onClick}>
|
|
<div className={styles.imageContainer}>
|
|
{hasImage ? (
|
|
<img
|
|
loading="lazy"
|
|
alt="Dashboard preview"
|
|
className={styles.image}
|
|
src={imageSrc}
|
|
onLoad={() => setHasImage(true)}
|
|
onError={() => setHasImage(false)}
|
|
/>
|
|
) : (
|
|
<div className={styles.imagePlaceholder}>
|
|
{item.icon ? (
|
|
<SVG src={item.icon} width={36} height={36} title={item.title} />
|
|
) : (
|
|
<Icon name="apps" size="xl" />
|
|
)}
|
|
</div>
|
|
)}
|
|
</div>
|
|
<div className={styles.info}>
|
|
<div className={styles.infoHeader}>
|
|
<div className={styles.titleContainer}>
|
|
<div>{item.title}</div>
|
|
<div className={styles.folder}>
|
|
<Icon name={'folder'} />
|
|
{folderTitle}
|
|
</div>
|
|
</div>
|
|
{lastUpdated !== null && (
|
|
<div className={styles.updateContainer}>
|
|
<div>Last updated</div>
|
|
{lastUpdated ? <div className={styles.update}>{lastUpdated}</div> : <Spinner />}
|
|
</div>
|
|
)}
|
|
</div>
|
|
<div>
|
|
<TagList className={styles.tagList} tags={item.tags ?? []} />
|
|
</div>
|
|
</div>
|
|
</a>
|
|
);
|
|
}
|
|
|
|
const getStyles = (theme: GrafanaTheme2, imageHeight: Props['imageHeight'], imageWidth: Props['imageWidth']) => {
|
|
const IMAGE_HORIZONTAL_MARGIN = theme.spacing(4);
|
|
|
|
return {
|
|
card: css`
|
|
background-color: ${theme.colors.background.secondary};
|
|
border: 1px solid ${theme.colors.border.medium};
|
|
border-radius: 4px;
|
|
box-shadow: ${theme.shadows.z3};
|
|
display: flex;
|
|
flex-direction: column;
|
|
height: 100%;
|
|
max-width: calc(${imageWidth}px + (${IMAGE_HORIZONTAL_MARGIN} * 2))};
|
|
width: 100%;
|
|
`,
|
|
folder: css`
|
|
align-items: center;
|
|
color: ${theme.colors.text.secondary};
|
|
display: flex;
|
|
font-size: ${theme.typography.size.sm};
|
|
gap: ${theme.spacing(0.5)};
|
|
`,
|
|
image: css`
|
|
box-shadow: ${theme.shadows.z2};
|
|
height: ${imageHeight}px;
|
|
margin: ${theme.spacing(1)} calc(${IMAGE_HORIZONTAL_MARGIN} - 1px) 0;
|
|
width: ${imageWidth}px;
|
|
`,
|
|
imageContainer: css`
|
|
flex: 1;
|
|
position: relative;
|
|
|
|
&:after {
|
|
background: linear-gradient(180deg, rgba(196, 196, 196, 0) 0%, rgba(127, 127, 127, 0.25) 100%);
|
|
bottom: 0;
|
|
content: '';
|
|
left: 0;
|
|
margin: ${theme.spacing(1)} calc(${IMAGE_HORIZONTAL_MARGIN} - 1px) 0;
|
|
position: absolute;
|
|
right: 0;
|
|
top: 0;
|
|
}
|
|
`,
|
|
imagePlaceholder: css`
|
|
align-items: center;
|
|
color: ${theme.colors.text.secondary};
|
|
display: flex;
|
|
height: ${imageHeight}px;
|
|
justify-content: center;
|
|
margin: ${theme.spacing(1)} ${IMAGE_HORIZONTAL_MARGIN} 0;
|
|
width: ${imageWidth}px;
|
|
`,
|
|
info: css`
|
|
background-color: ${theme.colors.background.canvas};
|
|
border-bottom-left-radius: 4px;
|
|
border-bottom-right-radius: 4px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
min-height: ${theme.spacing(7)};
|
|
gap: ${theme.spacing(1)};
|
|
padding: ${theme.spacing(1)} ${theme.spacing(2)};
|
|
z-index: 1;
|
|
`,
|
|
infoHeader: css`
|
|
display: flex;
|
|
gap: ${theme.spacing(1)};
|
|
justify-content: space-between;
|
|
`,
|
|
tagList: css`
|
|
justify-content: flex-start;
|
|
`,
|
|
titleContainer: css`
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: ${theme.spacing(0.5)};
|
|
`,
|
|
updateContainer: css`
|
|
align-items: flex-end;
|
|
display: flex;
|
|
flex-direction: column;
|
|
flex-shrink: 0;
|
|
font-size: ${theme.typography.bodySmall.fontSize};
|
|
gap: ${theme.spacing(0.5)};
|
|
`,
|
|
update: css`
|
|
color: ${theme.colors.text.secondary};
|
|
text-align: right;
|
|
`,
|
|
};
|
|
};
|