|
|
|
@ -1,9 +1,10 @@ |
|
|
|
|
import { css, cx } from '@emotion/css'; |
|
|
|
|
import React, { useState } from 'react'; |
|
|
|
|
import React, { useEffect, useRef, useState } from 'react'; |
|
|
|
|
|
|
|
|
|
import { GrafanaTheme2 } from '@grafana/data'; |
|
|
|
|
|
|
|
|
|
import { useStyles2 } from '../../themes'; |
|
|
|
|
import { InlineToast } from '../InlineToast/InlineToast'; |
|
|
|
|
import { Tooltip } from '../Tooltip'; |
|
|
|
|
|
|
|
|
|
import { ColorIndicatorPosition, VizTooltipColorIndicator } from './VizTooltipColorIndicator'; |
|
|
|
@ -16,6 +17,14 @@ interface Props extends LabelValue { |
|
|
|
|
isPinned: boolean; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
enum LabelValueTypes { |
|
|
|
|
label = 'label', |
|
|
|
|
value = 'value', |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const SUCCESSFULLY_COPIED_TEXT = 'Copied to clipboard'; |
|
|
|
|
const SHOW_SUCCESS_DURATION = 2 * 1000; |
|
|
|
|
|
|
|
|
|
export const VizTooltipRow = ({ |
|
|
|
|
label, |
|
|
|
|
value, |
|
|
|
@ -32,6 +41,61 @@ export const VizTooltipRow = ({ |
|
|
|
|
const [showLabelTooltip, setShowLabelTooltip] = useState(false); |
|
|
|
|
const [showValueTooltip, setShowValueTooltip] = useState(false); |
|
|
|
|
|
|
|
|
|
const [copiedText, setCopiedText] = useState<Record<string, string> | null>(null); |
|
|
|
|
const [showCopySuccess, setShowCopySuccess] = useState(false); |
|
|
|
|
|
|
|
|
|
const labelRef = useRef<null | HTMLDivElement>(null); |
|
|
|
|
const valueRef = useRef<null | HTMLDivElement>(null); |
|
|
|
|
|
|
|
|
|
useEffect(() => { |
|
|
|
|
let timeoutId: ReturnType<typeof setTimeout>; |
|
|
|
|
|
|
|
|
|
if (showCopySuccess) { |
|
|
|
|
timeoutId = setTimeout(() => { |
|
|
|
|
setShowCopySuccess(false); |
|
|
|
|
}, SHOW_SUCCESS_DURATION); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return () => { |
|
|
|
|
window.clearTimeout(timeoutId); |
|
|
|
|
}; |
|
|
|
|
}, [showCopySuccess]); |
|
|
|
|
|
|
|
|
|
const copyToClipboard = async (text: string, type: LabelValueTypes) => { |
|
|
|
|
if (!(navigator?.clipboard && window.isSecureContext)) { |
|
|
|
|
fallbackCopyToClipboard(text, type); |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
try { |
|
|
|
|
await navigator.clipboard.writeText(text); |
|
|
|
|
setCopiedText({ [`${type}`]: text }); |
|
|
|
|
setShowCopySuccess(true); |
|
|
|
|
} catch (error) { |
|
|
|
|
setCopiedText(null); |
|
|
|
|
} |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
const fallbackCopyToClipboard = (text: string, type: LabelValueTypes) => { |
|
|
|
|
// Use a fallback method for browsers/contexts that don't support the Clipboard API.
|
|
|
|
|
const textarea = document.createElement('textarea'); |
|
|
|
|
labelRef.current?.appendChild(textarea); |
|
|
|
|
textarea.value = text; |
|
|
|
|
textarea.focus(); |
|
|
|
|
textarea.select(); |
|
|
|
|
try { |
|
|
|
|
const successful = document.execCommand('copy'); |
|
|
|
|
if (successful) { |
|
|
|
|
setCopiedText({ [`${type}`]: text }); |
|
|
|
|
setShowCopySuccess(true); |
|
|
|
|
} |
|
|
|
|
} catch (err) { |
|
|
|
|
console.error('Unable to copy to clipboard', err); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
textarea.remove(); |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
const onMouseEnterLabel = (event: React.MouseEvent<HTMLDivElement>) => { |
|
|
|
|
if (event.currentTarget.offsetWidth < event.currentTarget.scrollWidth) { |
|
|
|
|
setShowLabelTooltip(true); |
|
|
|
@ -58,15 +122,27 @@ export const VizTooltipRow = ({ |
|
|
|
|
{!isPinned ? ( |
|
|
|
|
<div className={cx(styles.label, isActive && styles.activeSeries)}>{label}</div> |
|
|
|
|
) : ( |
|
|
|
|
<Tooltip content={label} interactive={false} show={showLabelTooltip}> |
|
|
|
|
<div |
|
|
|
|
className={cx(styles.label, isActive && styles.activeSeries)} |
|
|
|
|
onMouseEnter={onMouseEnterLabel} |
|
|
|
|
onMouseLeave={onMouseLeaveLabel} |
|
|
|
|
> |
|
|
|
|
{label} |
|
|
|
|
</div> |
|
|
|
|
</Tooltip> |
|
|
|
|
<> |
|
|
|
|
<Tooltip content={label} interactive={false} show={showLabelTooltip}> |
|
|
|
|
<> |
|
|
|
|
{showCopySuccess && copiedText?.label && ( |
|
|
|
|
<InlineToast placement="top" referenceElement={labelRef.current}> |
|
|
|
|
{SUCCESSFULLY_COPIED_TEXT} |
|
|
|
|
</InlineToast> |
|
|
|
|
)} |
|
|
|
|
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions */} |
|
|
|
|
<div |
|
|
|
|
className={cx(styles.label, isActive && styles.activeSeries, navigator?.clipboard && styles.copy)} |
|
|
|
|
onMouseEnter={onMouseEnterLabel} |
|
|
|
|
onMouseLeave={onMouseLeaveLabel} |
|
|
|
|
onClick={() => copyToClipboard(label, LabelValueTypes.label)} |
|
|
|
|
ref={labelRef} |
|
|
|
|
> |
|
|
|
|
{label} |
|
|
|
|
</div> |
|
|
|
|
</> |
|
|
|
|
</Tooltip> |
|
|
|
|
</> |
|
|
|
|
)} |
|
|
|
|
</div> |
|
|
|
|
)} |
|
|
|
@ -84,13 +160,23 @@ export const VizTooltipRow = ({ |
|
|
|
|
<div className={cx(styles.value, isActive)}>{value}</div> |
|
|
|
|
) : ( |
|
|
|
|
<Tooltip content={value ? value.toString() : ''} interactive={false} show={showValueTooltip}> |
|
|
|
|
<div |
|
|
|
|
className={cx(styles.value, isActive)} |
|
|
|
|
onMouseEnter={onMouseEnterValue} |
|
|
|
|
onMouseLeave={onMouseLeaveValue} |
|
|
|
|
> |
|
|
|
|
{value} |
|
|
|
|
</div> |
|
|
|
|
<> |
|
|
|
|
{showCopySuccess && copiedText?.value && ( |
|
|
|
|
<InlineToast placement="top" referenceElement={valueRef.current}> |
|
|
|
|
{SUCCESSFULLY_COPIED_TEXT} |
|
|
|
|
</InlineToast> |
|
|
|
|
)} |
|
|
|
|
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions */} |
|
|
|
|
<div |
|
|
|
|
className={cx(styles.value, isActive, navigator?.clipboard && styles.copy)} |
|
|
|
|
onMouseEnter={onMouseEnterValue} |
|
|
|
|
onMouseLeave={onMouseLeaveValue} |
|
|
|
|
onClick={() => copyToClipboard(value ? value.toString() : '', LabelValueTypes.value)} |
|
|
|
|
ref={valueRef} |
|
|
|
|
> |
|
|
|
|
{value} |
|
|
|
|
</div> |
|
|
|
|
</> |
|
|
|
|
</Tooltip> |
|
|
|
|
)} |
|
|
|
|
|
|
|
|
@ -135,4 +221,7 @@ const getStyles = (theme: GrafanaTheme2, justify: string, marginRight: string) = |
|
|
|
|
fontWeight: theme.typography.fontWeightBold, |
|
|
|
|
color: theme.colors.text.maxContrast, |
|
|
|
|
}), |
|
|
|
|
copy: css({ |
|
|
|
|
cursor: 'pointer', |
|
|
|
|
}), |
|
|
|
|
}); |
|
|
|
|