mirror of
https://github.com/grafana/grafana.git
synced 2025-07-30 05:32:32 +08:00
GrafanaUI: Add success state to ClipboardButton (#52069)
* User Experience: apply the same pattern feedback for all copy to clipboard buttons * add copy icon to all ClipboardButton use cases * Change primary color for copy to clipboard in create token * Add success button variant * Remove copy confirmation from TableCellInspectModal because it's in the base component now * Design tweaks to copy confirmation - Only change the icon to tick to avoid the button changing size - Change button to success green - Only show copy confirmation state for 2 seconds * revert TabelCellInspectModal text button back * revert accidental change to ShareLink Co-authored-by: joshhunt <josh@trtr.co>
This commit is contained in:

committed by
GitHub

parent
ba76be174f
commit
0633840777
@ -11,7 +11,7 @@ import { getPropertiesForButtonSize } from '../Forms/commonStyles';
|
|||||||
import { Icon } from '../Icon/Icon';
|
import { Icon } from '../Icon/Icon';
|
||||||
import { PopoverContent, Tooltip, TooltipPlacement } from '../Tooltip';
|
import { PopoverContent, Tooltip, TooltipPlacement } from '../Tooltip';
|
||||||
|
|
||||||
export type ButtonVariant = 'primary' | 'secondary' | 'destructive';
|
export type ButtonVariant = 'primary' | 'secondary' | 'destructive' | 'success';
|
||||||
export const allButtonVariants: ButtonVariant[] = ['primary', 'secondary', 'destructive'];
|
export const allButtonVariants: ButtonVariant[] = ['primary', 'secondary', 'destructive'];
|
||||||
export type ButtonFill = 'solid' | 'outline' | 'text';
|
export type ButtonFill = 'solid' | 'outline' | 'text';
|
||||||
export const allButtonFills: ButtonFill[] = ['solid', 'outline', 'text'];
|
export const allButtonFills: ButtonFill[] = ['solid', 'outline', 'text'];
|
||||||
@ -294,6 +294,9 @@ export function getPropertiesForVariant(theme: GrafanaTheme2, variant: ButtonVar
|
|||||||
case 'destructive':
|
case 'destructive':
|
||||||
return getButtonVariantStyles(theme, theme.colors.error, fill);
|
return getButtonVariantStyles(theme, theme.colors.error, fill);
|
||||||
|
|
||||||
|
case 'success':
|
||||||
|
return getButtonVariantStyles(theme, theme.colors.success, fill);
|
||||||
|
|
||||||
case 'primary':
|
case 'primary':
|
||||||
default:
|
default:
|
||||||
return getButtonVariantStyles(theme, theme.colors.primary, fill);
|
return getButtonVariantStyles(theme, theme.colors.primary, fill);
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, { useCallback, useRef } from 'react';
|
import React, { useCallback, useRef, useState, useEffect } from 'react';
|
||||||
|
|
||||||
import { Button, ButtonProps } from '../Button';
|
import { Button, ButtonProps } from '../Button';
|
||||||
|
|
||||||
@ -11,13 +11,40 @@ export interface Props extends ButtonProps {
|
|||||||
onClipboardError?(copiedText: string, error: unknown): void;
|
onClipboardError?(copiedText: string, error: unknown): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ClipboardButton({ onClipboardCopy, onClipboardError, children, getText, ...buttonProps }: Props) {
|
const SHOW_SUCCESS_DURATION = 2 * 1000;
|
||||||
|
|
||||||
|
export function ClipboardButton({
|
||||||
|
onClipboardCopy,
|
||||||
|
onClipboardError,
|
||||||
|
children,
|
||||||
|
getText,
|
||||||
|
icon,
|
||||||
|
variant,
|
||||||
|
...buttonProps
|
||||||
|
}: Props) {
|
||||||
|
const [showCopySuccess, setShowCopySuccess] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let timeoutId: NodeJS.Timeout;
|
||||||
|
|
||||||
|
if (showCopySuccess) {
|
||||||
|
timeoutId = setTimeout(() => {
|
||||||
|
setShowCopySuccess(false);
|
||||||
|
}, SHOW_SUCCESS_DURATION);
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.clearTimeout(timeoutId);
|
||||||
|
};
|
||||||
|
}, [showCopySuccess]);
|
||||||
|
|
||||||
const buttonRef = useRef<null | HTMLButtonElement>(null);
|
const buttonRef = useRef<null | HTMLButtonElement>(null);
|
||||||
const copyTextCallback = useCallback(async () => {
|
const copyTextCallback = useCallback(async () => {
|
||||||
const textToCopy = getText();
|
const textToCopy = getText();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await copyText(textToCopy, buttonRef);
|
await copyText(textToCopy, buttonRef);
|
||||||
|
setShowCopySuccess(true);
|
||||||
onClipboardCopy?.(textToCopy);
|
onClipboardCopy?.(textToCopy);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
onClipboardError?.(textToCopy, e);
|
onClipboardError?.(textToCopy, e);
|
||||||
@ -25,7 +52,14 @@ export function ClipboardButton({ onClipboardCopy, onClipboardError, children, g
|
|||||||
}, [getText, onClipboardCopy, onClipboardError]);
|
}, [getText, onClipboardCopy, onClipboardError]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button onClick={copyTextCallback} {...buttonProps} ref={buttonRef}>
|
<Button
|
||||||
|
onClick={copyTextCallback}
|
||||||
|
icon={showCopySuccess ? 'check' : icon}
|
||||||
|
variant={showCopySuccess ? 'success' : variant}
|
||||||
|
aria-label={showCopySuccess ? 'Copied' : undefined}
|
||||||
|
{...buttonProps}
|
||||||
|
ref={buttonRef}
|
||||||
|
>
|
||||||
{children}
|
{children}
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
import { isString } from 'lodash';
|
import { isString } from 'lodash';
|
||||||
import React, { useEffect, useState } from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { ClipboardButton } from '../ClipboardButton/ClipboardButton';
|
import { ClipboardButton } from '../ClipboardButton/ClipboardButton';
|
||||||
import { Icon } from '../Icon/Icon';
|
|
||||||
import { Modal } from '../Modal/Modal';
|
import { Modal } from '../Modal/Modal';
|
||||||
import { CodeEditor } from '../Monaco/CodeEditor';
|
import { CodeEditor } from '../Monaco/CodeEditor';
|
||||||
|
|
||||||
@ -13,23 +12,6 @@ interface TableCellInspectModalProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function TableCellInspectModal({ value, onDismiss, mode }: TableCellInspectModalProps) {
|
export function TableCellInspectModal({ value, onDismiss, mode }: TableCellInspectModalProps) {
|
||||||
const [isInClipboard, setIsInClipboard] = useState(false);
|
|
||||||
const timeoutRef = React.useRef<number>();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (isInClipboard) {
|
|
||||||
timeoutRef.current = window.setTimeout(() => {
|
|
||||||
setIsInClipboard(false);
|
|
||||||
}, 2000);
|
|
||||||
}
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
if (timeoutRef.current) {
|
|
||||||
window.clearTimeout(timeoutRef.current);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}, [isInClipboard]);
|
|
||||||
|
|
||||||
let displayValue = value;
|
let displayValue = value;
|
||||||
if (isString(value)) {
|
if (isString(value)) {
|
||||||
try {
|
try {
|
||||||
@ -60,15 +42,8 @@ export function TableCellInspectModal({ value, onDismiss, mode }: TableCellInspe
|
|||||||
<pre>{text}</pre>
|
<pre>{text}</pre>
|
||||||
)}
|
)}
|
||||||
<Modal.ButtonRow>
|
<Modal.ButtonRow>
|
||||||
<ClipboardButton getText={() => text} onClipboardCopy={() => setIsInClipboard(true)}>
|
<ClipboardButton icon="copy" getText={() => text}>
|
||||||
{!isInClipboard ? (
|
Copy to Clipboard
|
||||||
'Copy to Clipboard'
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<Icon name="check" />
|
|
||||||
Copied to clipboard
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</ClipboardButton>
|
</ClipboardButton>
|
||||||
</Modal.ButtonRow>
|
</Modal.ButtonRow>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
import React, { PureComponent } from 'react';
|
import React, { PureComponent } from 'react';
|
||||||
|
|
||||||
import { AppEvents } from '@grafana/data';
|
|
||||||
import { getBackendSrv } from '@grafana/runtime';
|
import { getBackendSrv } from '@grafana/runtime';
|
||||||
import { LoadingPlaceholder, JSONFormatter, Icon, HorizontalGroup, ClipboardButton } from '@grafana/ui';
|
import { LoadingPlaceholder, JSONFormatter, Icon, HorizontalGroup, ClipboardButton } from '@grafana/ui';
|
||||||
import appEvents from 'app/core/app_events';
|
|
||||||
|
|
||||||
import { DashboardModel, PanelModel } from '../dashboard/state';
|
import { DashboardModel, PanelModel } from '../dashboard/state';
|
||||||
|
|
||||||
@ -58,10 +56,6 @@ export class TestRuleResult extends PureComponent<Props, State> {
|
|||||||
return JSON.stringify(this.formattedJson, null, 2);
|
return JSON.stringify(this.formattedJson, null, 2);
|
||||||
};
|
};
|
||||||
|
|
||||||
onClipboardSuccess = () => {
|
|
||||||
appEvents.emit(AppEvents.alertSuccess, ['Content copied to clipboard']);
|
|
||||||
};
|
|
||||||
|
|
||||||
onToggleExpand = () => {
|
onToggleExpand = () => {
|
||||||
this.setState((prevState) => ({
|
this.setState((prevState) => ({
|
||||||
...prevState,
|
...prevState,
|
||||||
@ -108,7 +102,7 @@ export class TestRuleResult extends PureComponent<Props, State> {
|
|||||||
<div className="pull-right">
|
<div className="pull-right">
|
||||||
<HorizontalGroup spacing="md">
|
<HorizontalGroup spacing="md">
|
||||||
<div onClick={this.onToggleExpand}>{this.renderExpandCollapse()}</div>
|
<div onClick={this.onToggleExpand}>{this.renderExpandCollapse()}</div>
|
||||||
<ClipboardButton getText={this.getTextForClipboard} onClipboardCopy={this.onClipboardSuccess} icon="copy">
|
<ClipboardButton getText={this.getTextForClipboard} icon="copy">
|
||||||
Copy to Clipboard
|
Copy to Clipboard
|
||||||
</ClipboardButton>
|
</ClipboardButton>
|
||||||
</HorizontalGroup>
|
</HorizontalGroup>
|
||||||
|
@ -200,9 +200,7 @@ export const RuleDetailsActionButtons: FC<Props> = ({ rule, rulesSource }) => {
|
|||||||
rightButtons.push(
|
rightButtons.push(
|
||||||
<ClipboardButton
|
<ClipboardButton
|
||||||
key="copy"
|
key="copy"
|
||||||
onClipboardCopy={() => {
|
icon="copy"
|
||||||
notifyApp.success('URL copied!');
|
|
||||||
}}
|
|
||||||
onClipboardError={(copiedText) => {
|
onClipboardError={(copiedText) => {
|
||||||
notifyApp.error('Error while copying URL', copiedText);
|
notifyApp.error('Error while copying URL', copiedText);
|
||||||
}}
|
}}
|
||||||
|
@ -2,11 +2,7 @@
|
|||||||
import React, { useCallback } from 'react';
|
import React, { useCallback } from 'react';
|
||||||
|
|
||||||
import { GrafanaTheme2 } from '@grafana/data';
|
import { GrafanaTheme2 } from '@grafana/data';
|
||||||
import { Alert, Field, Modal, useStyles2, Input, Icon, ClipboardButton } from '@grafana/ui';
|
import { Alert, Field, Modal, useStyles2, Input, ClipboardButton } from '@grafana/ui';
|
||||||
|
|
||||||
import { notifyApp } from '../../core/actions';
|
|
||||||
import { createSuccessNotification } from '../../core/copy/appNotification';
|
|
||||||
import { dispatch } from '../../store/store';
|
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
onDismiss: () => void;
|
onDismiss: () => void;
|
||||||
@ -17,9 +13,7 @@ export interface Props {
|
|||||||
export function ApiKeysAddedModal({ onDismiss, apiKey, rootPath }: Props): JSX.Element {
|
export function ApiKeysAddedModal({ onDismiss, apiKey, rootPath }: Props): JSX.Element {
|
||||||
const styles = useStyles2(getStyles);
|
const styles = useStyles2(getStyles);
|
||||||
const getClipboardText = useCallback(() => apiKey, [apiKey]);
|
const getClipboardText = useCallback(() => apiKey, [apiKey]);
|
||||||
const onClipboardCopy = () => {
|
|
||||||
dispatch(notifyApp(createSuccessNotification('Content copied to clipboard')));
|
|
||||||
};
|
|
||||||
return (
|
return (
|
||||||
<Modal title="API Key Created" onDismiss={onDismiss} onClickBackdrop={onDismiss} isOpen>
|
<Modal title="API Key Created" onDismiss={onDismiss} onClickBackdrop={onDismiss} isOpen>
|
||||||
<Field label="Key">
|
<Field label="Key">
|
||||||
@ -28,8 +22,8 @@ export function ApiKeysAddedModal({ onDismiss, apiKey, rootPath }: Props): JSX.E
|
|||||||
value={apiKey}
|
value={apiKey}
|
||||||
readOnly
|
readOnly
|
||||||
addonAfter={
|
addonAfter={
|
||||||
<ClipboardButton variant="primary" getText={getClipboardText} onClipboardCopy={onClipboardCopy}>
|
<ClipboardButton icon="copy" variant="primary" getText={getClipboardText}>
|
||||||
<Icon name="copy" /> Copy
|
Copy
|
||||||
</ClipboardButton>
|
</ClipboardButton>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
@ -5,13 +5,11 @@ import React, { useCallback, useState } from 'react';
|
|||||||
import { GrafanaTheme } from '@grafana/data';
|
import { GrafanaTheme } from '@grafana/data';
|
||||||
import { Stack } from '@grafana/experimental';
|
import { Stack } from '@grafana/experimental';
|
||||||
import { Button, ClipboardButton, HorizontalGroup, stylesFactory, TextArea, useTheme } from '@grafana/ui';
|
import { Button, ClipboardButton, HorizontalGroup, stylesFactory, TextArea, useTheme } from '@grafana/ui';
|
||||||
import { useAppNotification } from 'app/core/copy/appNotification';
|
|
||||||
|
|
||||||
import { SaveDashboardFormProps } from '../types';
|
import { SaveDashboardFormProps } from '../types';
|
||||||
|
|
||||||
export const SaveProvisionedDashboardForm: React.FC<SaveDashboardFormProps> = ({ dashboard, onCancel }) => {
|
export const SaveProvisionedDashboardForm: React.FC<SaveDashboardFormProps> = ({ dashboard, onCancel }) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const notifyApp = useAppNotification();
|
|
||||||
const [dashboardJSON, setDashboardJson] = useState(() => {
|
const [dashboardJSON, setDashboardJson] = useState(() => {
|
||||||
const clone = dashboard.getSaveModelClone();
|
const clone = dashboard.getSaveModelClone();
|
||||||
delete clone.id;
|
delete clone.id;
|
||||||
@ -25,10 +23,6 @@ export const SaveProvisionedDashboardForm: React.FC<SaveDashboardFormProps> = ({
|
|||||||
saveAs(blob, dashboard.title + '-' + new Date().getTime() + '.json');
|
saveAs(blob, dashboard.title + '-' + new Date().getTime() + '.json');
|
||||||
}, [dashboard.title, dashboardJSON]);
|
}, [dashboard.title, dashboardJSON]);
|
||||||
|
|
||||||
const onCopyToClipboardSuccess = useCallback(() => {
|
|
||||||
notifyApp.success('Dashboard JSON copied to clipboard');
|
|
||||||
}, [notifyApp]);
|
|
||||||
|
|
||||||
const styles = getStyles(theme);
|
const styles = getStyles(theme);
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -64,7 +58,7 @@ export const SaveProvisionedDashboardForm: React.FC<SaveDashboardFormProps> = ({
|
|||||||
<Button variant="secondary" onClick={onCancel} fill="outline">
|
<Button variant="secondary" onClick={onCancel} fill="outline">
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
<ClipboardButton getText={() => dashboardJSON} onClipboardCopy={onCopyToClipboardSuccess}>
|
<ClipboardButton icon="copy" getText={() => dashboardJSON}>
|
||||||
Copy JSON to clipboard
|
Copy JSON to clipboard
|
||||||
</ClipboardButton>
|
</ClipboardButton>
|
||||||
<Button type="submit" onClick={saveToFile}>
|
<Button type="submit" onClick={saveToFile}>
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
import React, { FormEvent, PureComponent } from 'react';
|
import React, { FormEvent, PureComponent } from 'react';
|
||||||
|
|
||||||
import { AppEvents, SelectableValue } from '@grafana/data';
|
import { SelectableValue } from '@grafana/data';
|
||||||
import { reportInteraction } from '@grafana/runtime/src';
|
import { reportInteraction } from '@grafana/runtime/src';
|
||||||
import { ClipboardButton, Field, Modal, RadioButtonGroup, Switch, TextArea } from '@grafana/ui';
|
import { ClipboardButton, Field, Modal, RadioButtonGroup, Switch, TextArea } from '@grafana/ui';
|
||||||
import { appEvents } from 'app/core/core';
|
|
||||||
|
|
||||||
import { ShareModalTabProps } from './types';
|
import { ShareModalTabProps } from './types';
|
||||||
import { buildIframeHtml } from './utils';
|
import { buildIframeHtml } from './utils';
|
||||||
@ -62,10 +61,6 @@ export class ShareEmbed extends PureComponent<Props, State> {
|
|||||||
this.setState({ selectedTheme: value }, this.buildIframeHtml);
|
this.setState({ selectedTheme: value }, this.buildIframeHtml);
|
||||||
};
|
};
|
||||||
|
|
||||||
onIframeHtmlCopy = () => {
|
|
||||||
appEvents.emit(AppEvents.alertSuccess, ['Content copied to clipboard']);
|
|
||||||
};
|
|
||||||
|
|
||||||
getIframeHtml = () => {
|
getIframeHtml = () => {
|
||||||
return this.state.iframeHtml;
|
return this.state.iframeHtml;
|
||||||
};
|
};
|
||||||
@ -104,7 +99,7 @@ export class ShareEmbed extends PureComponent<Props, State> {
|
|||||||
/>
|
/>
|
||||||
</Field>
|
</Field>
|
||||||
<Modal.ButtonRow>
|
<Modal.ButtonRow>
|
||||||
<ClipboardButton variant="primary" getText={this.getIframeHtml} onClipboardCopy={this.onIframeHtmlCopy}>
|
<ClipboardButton icon="copy" variant="primary" getText={this.getIframeHtml}>
|
||||||
Copy to clipboard
|
Copy to clipboard
|
||||||
</ClipboardButton>
|
</ClipboardButton>
|
||||||
</Modal.ButtonRow>
|
</Modal.ButtonRow>
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
import React, { PureComponent } from 'react';
|
import React, { PureComponent } from 'react';
|
||||||
|
|
||||||
import { AppEvents, SelectableValue } from '@grafana/data';
|
import { SelectableValue } from '@grafana/data';
|
||||||
import { selectors as e2eSelectors } from '@grafana/e2e-selectors';
|
import { selectors as e2eSelectors } from '@grafana/e2e-selectors';
|
||||||
import { reportInteraction } from '@grafana/runtime/src';
|
import { reportInteraction } from '@grafana/runtime/src';
|
||||||
import { Alert, ClipboardButton, Field, FieldSet, Icon, Input, RadioButtonGroup, Switch } from '@grafana/ui';
|
import { Alert, ClipboardButton, Field, FieldSet, Icon, Input, RadioButtonGroup, Switch } from '@grafana/ui';
|
||||||
import config from 'app/core/config';
|
import config from 'app/core/config';
|
||||||
import { appEvents } from 'app/core/core';
|
|
||||||
|
|
||||||
import { ShareModalTabProps } from './types';
|
import { ShareModalTabProps } from './types';
|
||||||
import { buildImageUrl, buildShareUrl } from './utils';
|
import { buildImageUrl, buildShareUrl } from './utils';
|
||||||
@ -76,10 +75,6 @@ export class ShareLink extends PureComponent<Props, State> {
|
|||||||
this.setState({ selectedTheme: value });
|
this.setState({ selectedTheme: value });
|
||||||
};
|
};
|
||||||
|
|
||||||
onShareUrlCopy = () => {
|
|
||||||
appEvents.emit(AppEvents.alertSuccess, ['Content copied to clipboard']);
|
|
||||||
};
|
|
||||||
|
|
||||||
getShareUrl = () => {
|
getShareUrl = () => {
|
||||||
return this.state.shareUrl;
|
return this.state.shareUrl;
|
||||||
};
|
};
|
||||||
@ -120,8 +115,8 @@ export class ShareLink extends PureComponent<Props, State> {
|
|||||||
value={shareUrl}
|
value={shareUrl}
|
||||||
readOnly
|
readOnly
|
||||||
addonAfter={
|
addonAfter={
|
||||||
<ClipboardButton variant="primary" getText={this.getShareUrl} onClipboardCopy={this.onShareUrlCopy}>
|
<ClipboardButton icon="copy" variant="primary" getText={this.getShareUrl}>
|
||||||
<Icon name="copy" /> Copy
|
Copy
|
||||||
</ClipboardButton>
|
</ClipboardButton>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import React, { useCallback, useEffect, useState } from 'react';
|
import React, { useCallback, useEffect, useState } from 'react';
|
||||||
|
|
||||||
import { AppEvents } from '@grafana/data';
|
|
||||||
import { reportInteraction } from '@grafana/runtime/src';
|
import { reportInteraction } from '@grafana/runtime/src';
|
||||||
import {
|
import {
|
||||||
Alert,
|
Alert,
|
||||||
@ -16,7 +15,6 @@ import {
|
|||||||
} from '@grafana/ui';
|
} from '@grafana/ui';
|
||||||
import { notifyApp } from 'app/core/actions';
|
import { notifyApp } from 'app/core/actions';
|
||||||
import { createErrorNotification } from 'app/core/copy/appNotification';
|
import { createErrorNotification } from 'app/core/copy/appNotification';
|
||||||
import { appEvents } from 'app/core/core';
|
|
||||||
import { dispatch } from 'app/store/store';
|
import { dispatch } from 'app/store/store';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -79,10 +77,6 @@ export const SharePublicDashboard = (props: Props) => {
|
|||||||
savePublicDashboardConfig(props.dashboard.uid, publicDashboard, setPublicDashboardConfig).catch();
|
savePublicDashboardConfig(props.dashboard.uid, publicDashboard, setPublicDashboardConfig).catch();
|
||||||
};
|
};
|
||||||
|
|
||||||
const onShareUrlCopy = () => {
|
|
||||||
appEvents.emit(AppEvents.alertSuccess, ['Content copied to clipboard']);
|
|
||||||
};
|
|
||||||
|
|
||||||
const onAcknowledge = useCallback(
|
const onAcknowledge = useCallback(
|
||||||
(field: string, checked: boolean) => {
|
(field: string, checked: boolean) => {
|
||||||
setAcknowledgements({ ...acknowledgements, [field]: checked });
|
setAcknowledgements({ ...acknowledgements, [field]: checked });
|
||||||
@ -219,7 +213,6 @@ export const SharePublicDashboard = (props: Props) => {
|
|||||||
getText={() => {
|
getText={() => {
|
||||||
return generatePublicDashboardUrl(publicDashboard);
|
return generatePublicDashboardUrl(publicDashboard);
|
||||||
}}
|
}}
|
||||||
onClipboardCopy={onShareUrlCopy}
|
|
||||||
>
|
>
|
||||||
Copy
|
Copy
|
||||||
</ClipboardButton>
|
</ClipboardButton>
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
import React, { PureComponent } from 'react';
|
import React, { PureComponent } from 'react';
|
||||||
|
|
||||||
import { AppEvents, SelectableValue } from '@grafana/data';
|
import { SelectableValue } from '@grafana/data';
|
||||||
import { getBackendSrv, reportInteraction } from '@grafana/runtime';
|
import { getBackendSrv, reportInteraction } from '@grafana/runtime';
|
||||||
import { Button, ClipboardButton, Field, Icon, Input, LinkButton, Modal, Select, Spinner } from '@grafana/ui';
|
import { Button, ClipboardButton, Field, Input, LinkButton, Modal, Select, Spinner } from '@grafana/ui';
|
||||||
import { appEvents } from 'app/core/core';
|
|
||||||
import { getTimeSrv } from 'app/features/dashboard/services/TimeSrv';
|
import { getTimeSrv } from 'app/features/dashboard/services/TimeSrv';
|
||||||
import { DashboardModel, PanelModel } from 'app/features/dashboard/state';
|
import { DashboardModel, PanelModel } from 'app/features/dashboard/state';
|
||||||
|
|
||||||
@ -198,10 +197,6 @@ export class ShareSnapshot extends PureComponent<Props, State> {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
onSnapshotUrlCopy = () => {
|
|
||||||
appEvents.emit(AppEvents.alertSuccess, ['Content copied to clipboard']);
|
|
||||||
};
|
|
||||||
|
|
||||||
renderStep1() {
|
renderStep1() {
|
||||||
const { onDismiss } = this.props;
|
const { onDismiss } = this.props;
|
||||||
const { snapshotName, selectedExpireOption, timeoutSeconds, isLoading, sharingButtonText, externalEnabled } =
|
const { snapshotName, selectedExpireOption, timeoutSeconds, isLoading, sharingButtonText, externalEnabled } =
|
||||||
@ -262,17 +257,18 @@ export class ShareSnapshot extends PureComponent<Props, State> {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="gf-form" style={{ marginTop: '40px' }}>
|
<Field label="Snapshot URL">
|
||||||
<div className="gf-form-row">
|
<Input
|
||||||
<a href={snapshotUrl} className="large share-modal-link" target="_blank" rel="noreferrer">
|
id="snapshot-url-input"
|
||||||
<Icon name="external-link-alt" /> {snapshotUrl}
|
value={snapshotUrl}
|
||||||
</a>
|
readOnly
|
||||||
<br />
|
addonAfter={
|
||||||
<ClipboardButton variant="secondary" getText={this.getSnapshotUrl} onClipboardCopy={this.onSnapshotUrlCopy}>
|
<ClipboardButton icon="copy" variant="primary" getText={this.getSnapshotUrl}>
|
||||||
Copy Link
|
Copy
|
||||||
</ClipboardButton>
|
</ClipboardButton>
|
||||||
</div>
|
}
|
||||||
</div>
|
/>
|
||||||
|
</Field>
|
||||||
|
|
||||||
<div className="pull-right" style={{ padding: '5px' }}>
|
<div className="pull-right" style={{ padding: '5px' }}>
|
||||||
Did you make a mistake?{' '}
|
Did you make a mistake?{' '}
|
||||||
|
@ -3,10 +3,6 @@ import AutoSizer from 'react-virtualized-auto-sizer';
|
|||||||
|
|
||||||
import { ClipboardButton, CodeEditor, Modal } from '@grafana/ui';
|
import { ClipboardButton, CodeEditor, Modal } from '@grafana/ui';
|
||||||
|
|
||||||
import { notifyApp } from '../../../../core/actions';
|
|
||||||
import { createSuccessNotification } from '../../../../core/copy/appNotification';
|
|
||||||
import { dispatch } from '../../../../store/store';
|
|
||||||
|
|
||||||
export interface ViewJsonModalProps {
|
export interface ViewJsonModalProps {
|
||||||
json: string;
|
json: string;
|
||||||
onDismiss: () => void;
|
onDismiss: () => void;
|
||||||
@ -14,16 +10,13 @@ export interface ViewJsonModalProps {
|
|||||||
|
|
||||||
export function ViewJsonModal({ json, onDismiss }: ViewJsonModalProps): JSX.Element {
|
export function ViewJsonModal({ json, onDismiss }: ViewJsonModalProps): JSX.Element {
|
||||||
const getClipboardText = useCallback(() => json, [json]);
|
const getClipboardText = useCallback(() => json, [json]);
|
||||||
const onClipboardCopy = () => {
|
|
||||||
dispatch(notifyApp(createSuccessNotification('Content copied to clipboard')));
|
|
||||||
};
|
|
||||||
return (
|
return (
|
||||||
<Modal title="JSON" onDismiss={onDismiss} onClickBackdrop={onDismiss} isOpen>
|
<Modal title="JSON" onDismiss={onDismiss} onClickBackdrop={onDismiss} isOpen>
|
||||||
<AutoSizer disableHeight>
|
<AutoSizer disableHeight>
|
||||||
{({ width }) => <CodeEditor value={json} language="json" showMiniMap={false} height="500px" width={width} />}
|
{({ width }) => <CodeEditor value={json} language="json" showMiniMap={false} height="500px" width={width} />}
|
||||||
</AutoSizer>
|
</AutoSizer>
|
||||||
<Modal.ButtonRow>
|
<Modal.ButtonRow>
|
||||||
<ClipboardButton getText={getClipboardText} onClipboardCopy={onClipboardCopy}>
|
<ClipboardButton icon="copy" getText={getClipboardText}>
|
||||||
Copy to Clipboard
|
Copy to Clipboard
|
||||||
</ClipboardButton>
|
</ClipboardButton>
|
||||||
</Modal.ButtonRow>
|
</Modal.ButtonRow>
|
||||||
|
@ -2,12 +2,11 @@ import { css } from '@emotion/css';
|
|||||||
import React, { PureComponent } from 'react';
|
import React, { PureComponent } from 'react';
|
||||||
import { Subscription } from 'rxjs';
|
import { Subscription } from 'rxjs';
|
||||||
|
|
||||||
import { AppEvents, DataFrame } from '@grafana/data';
|
import { DataFrame } from '@grafana/data';
|
||||||
import { selectors } from '@grafana/e2e-selectors';
|
import { selectors } from '@grafana/e2e-selectors';
|
||||||
import { Stack } from '@grafana/experimental';
|
import { Stack } from '@grafana/experimental';
|
||||||
import { config, RefreshEvent } from '@grafana/runtime';
|
import { config, RefreshEvent } from '@grafana/runtime';
|
||||||
import { Button, ClipboardButton, JSONFormatter, LoadingPlaceholder } from '@grafana/ui';
|
import { Button, ClipboardButton, JSONFormatter, LoadingPlaceholder } from '@grafana/ui';
|
||||||
import appEvents from 'app/core/app_events';
|
|
||||||
import { backendSrv } from 'app/core/services/backend_srv';
|
import { backendSrv } from 'app/core/services/backend_srv';
|
||||||
import { supportsDataQuery } from 'app/features/dashboard/components/PanelEditor/utils';
|
import { supportsDataQuery } from 'app/features/dashboard/components/PanelEditor/utils';
|
||||||
import { PanelModel } from 'app/features/dashboard/state';
|
import { PanelModel } from 'app/features/dashboard/state';
|
||||||
@ -186,10 +185,6 @@ export class QueryInspector extends PureComponent<Props, State> {
|
|||||||
return JSON.stringify(this.formattedJson, null, 2);
|
return JSON.stringify(this.formattedJson, null, 2);
|
||||||
};
|
};
|
||||||
|
|
||||||
onClipboardSuccess = () => {
|
|
||||||
appEvents.emit(AppEvents.alertSuccess, ['Content copied to clipboard']);
|
|
||||||
};
|
|
||||||
|
|
||||||
onToggleExpand = () => {
|
onToggleExpand = () => {
|
||||||
this.setState((prevState) => ({
|
this.setState((prevState) => ({
|
||||||
...prevState,
|
...prevState,
|
||||||
@ -297,7 +292,6 @@ export class QueryInspector extends PureComponent<Props, State> {
|
|||||||
{haveData && (
|
{haveData && (
|
||||||
<ClipboardButton
|
<ClipboardButton
|
||||||
getText={this.getTextForClipboard}
|
getText={this.getTextForClipboard}
|
||||||
onClipboardCopy={this.onClipboardSuccess}
|
|
||||||
className={styles.toolbarItem}
|
className={styles.toolbarItem}
|
||||||
icon="copy"
|
icon="copy"
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
|
@ -26,7 +26,7 @@ class InviteeRow extends PureComponent<Props> {
|
|||||||
<td>{invitee.email}</td>
|
<td>{invitee.email}</td>
|
||||||
<td>{invitee.name}</td>
|
<td>{invitee.name}</td>
|
||||||
<td className="text-right">
|
<td className="text-right">
|
||||||
<ClipboardButton variant="secondary" size="sm" getText={() => invitee.url}>
|
<ClipboardButton icon="copy" variant="secondary" size="sm" getText={() => invitee.url}>
|
||||||
Copy Invite
|
Copy Invite
|
||||||
</ClipboardButton>
|
</ClipboardButton>
|
||||||
|
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
|
|
||||||
import { AppEvents, SelectableValue, UrlQueryMap, urlUtil } from '@grafana/data';
|
import { SelectableValue, UrlQueryMap, urlUtil } from '@grafana/data';
|
||||||
import { Checkbox, ClipboardButton, Field, FieldSet, Icon, Input, Modal, RadioButtonGroup } from '@grafana/ui';
|
import { Checkbox, ClipboardButton, Field, FieldSet, Input, Modal, RadioButtonGroup } from '@grafana/ui';
|
||||||
import appEvents from 'app/core/app_events';
|
|
||||||
|
|
||||||
import { buildBaseUrl } from '../dashboard/components/ShareModal/utils';
|
import { buildBaseUrl } from '../dashboard/components/ShareModal/utils';
|
||||||
|
|
||||||
@ -23,10 +22,6 @@ export const ShareModal = ({ playlistUid, onDismiss }: ShareModalProps) => {
|
|||||||
{ label: 'Kiosk', value: true },
|
{ label: 'Kiosk', value: true },
|
||||||
];
|
];
|
||||||
|
|
||||||
const onShareUrlCopy = () => {
|
|
||||||
appEvents.emit(AppEvents.alertSuccess, ['Content copied to clipboard']);
|
|
||||||
};
|
|
||||||
|
|
||||||
const params: UrlQueryMap = {};
|
const params: UrlQueryMap = {};
|
||||||
if (mode) {
|
if (mode) {
|
||||||
params.kiosk = mode;
|
params.kiosk = mode;
|
||||||
@ -59,8 +54,8 @@ export const ShareModal = ({ playlistUid, onDismiss }: ShareModalProps) => {
|
|||||||
value={shareUrl}
|
value={shareUrl}
|
||||||
readOnly
|
readOnly
|
||||||
addonAfter={
|
addonAfter={
|
||||||
<ClipboardButton variant="primary" getText={() => shareUrl} onClipboardCopy={onShareUrlCopy}>
|
<ClipboardButton icon="copy" variant="primary" getText={() => shareUrl}>
|
||||||
<Icon name="copy" /> Copy
|
Copy
|
||||||
</ClipboardButton>
|
</ClipboardButton>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
@ -134,7 +134,7 @@ export const CreateTokenModal = ({ isOpen, token, serviceAccountLogin, onCreateT
|
|||||||
<Input name="tokenValue" value={token} readOnly />
|
<Input name="tokenValue" value={token} readOnly />
|
||||||
<ClipboardButton
|
<ClipboardButton
|
||||||
className={styles.modalCopyToClipboardButton}
|
className={styles.modalCopyToClipboardButton}
|
||||||
variant="secondary"
|
variant="primary"
|
||||||
size="md"
|
size="md"
|
||||||
icon="copy"
|
icon="copy"
|
||||||
getText={() => token}
|
getText={() => token}
|
||||||
|
Reference in New Issue
Block a user