diff --git a/package.json b/package.json index 5e3f17f68ab..3ce1aa55bbe 100644 --- a/package.json +++ b/package.json @@ -102,7 +102,6 @@ "@types/angular": "1.8.3", "@types/angular-route": "1.7.0", "@types/classnames": "2.3.0", - "@types/clipboard": "2.0.1", "@types/common-tags": "^1.8.0", "@types/d3": "7.1.0", "@types/d3-force": "^2.1.0", @@ -277,7 +276,6 @@ "calculate-size": "1.1.1", "centrifuge": "2.8.4", "classnames": "2.3.1", - "clipboard": "2.0.4", "comlink": "4.3.1", "common-tags": "^1.8.0", "core-js": "3.20.0", diff --git a/packages/grafana-ui/package.json b/packages/grafana-ui/package.json index 665b6777437..f40d263e108 100644 --- a/packages/grafana-ui/package.json +++ b/packages/grafana-ui/package.json @@ -48,7 +48,6 @@ "ansicolor": "1.1.95", "calculate-size": "1.1.1", "classnames": "2.3.1", - "clipboard": "2.0.4", "core-js": "3.20.0", "d3": "5.15.0", "date-fns": "2.28.0", @@ -119,7 +118,6 @@ "@testing-library/react-hooks": "7.0.2", "@testing-library/user-event": "13.5.0", "@types/classnames": "2.3.0", - "@types/clipboard": "2.0.1", "@types/common-tags": "^1.8.0", "@types/d3": "7.1.0", "@types/enzyme": "3.10.5", diff --git a/packages/grafana-ui/src/components/ClipboardButton/ClipboardButton.tsx b/packages/grafana-ui/src/components/ClipboardButton/ClipboardButton.tsx index 52e8d105c0e..d979d8b6aa2 100644 --- a/packages/grafana-ui/src/components/ClipboardButton/ClipboardButton.tsx +++ b/packages/grafana-ui/src/components/ClipboardButton/ClipboardButton.tsx @@ -1,51 +1,44 @@ -import React, { PureComponent } from 'react'; -import Clipboard from 'clipboard'; +import React, { useCallback, useRef } from 'react'; import { Button, ButtonProps } from '../Button'; +/** @deprecated Will be removed in next major release */ +interface ClipboardEvent { + action: string; + text: string; + trigger: Element; + clearSelection(): void; +} + export interface Props extends ButtonProps { /** A function that returns text to be copied */ getText(): string; /** Callback when the text has been successfully copied */ - onClipboardCopy?(e: Clipboard.Event): void; + onClipboardCopy?(e: ClipboardEvent): void; /** Callback when there was an error copying the text */ - onClipboardError?(e: Clipboard.Event): void; + onClipboardError?(e: ClipboardEvent): void; } -export class ClipboardButton extends PureComponent { - private clipboard!: Clipboard; - private elem!: HTMLButtonElement; +const dummyClearFunc = () => {}; - setRef = (elem: HTMLButtonElement) => { - this.elem = elem; - }; +export function ClipboardButton({ onClipboardCopy, onClipboardError, children, getText, ...buttonProps }: Props) { + // Can be removed in 9.x + const buttonRef = useRef(null); + const copyText = useCallback(() => { + const copiedText = getText(); + const dummyEvent: ClipboardEvent = { + action: 'copy', + clearSelection: dummyClearFunc, + text: copiedText, + trigger: buttonRef.current!, + }; + navigator.clipboard + .writeText(copiedText) + .then(() => (onClipboardCopy?.(dummyEvent), () => onClipboardError?.(dummyEvent))); + }, [getText, onClipboardCopy, onClipboardError]); - componentDidMount() { - const { getText, onClipboardCopy, onClipboardError } = this.props; - - this.clipboard = new Clipboard(this.elem, { - text: () => getText(), - }); - - this.clipboard.on('success', (e: Clipboard.Event) => { - onClipboardCopy && onClipboardCopy(e); - }); - - this.clipboard.on('error', (e: Clipboard.Event) => { - onClipboardError && onClipboardError(e); - }); - } - - componentWillUnmount() { - this.clipboard.destroy(); - } - - render() { - const { getText, onClipboardCopy, onClipboardError, children, ...buttonProps } = this.props; - - return ( - - ); - } + return ( + + ); } diff --git a/public/app/angular/angular_wrappers.ts b/public/app/angular/angular_wrappers.ts index 2ce3911600a..4747c83e977 100644 --- a/public/app/angular/angular_wrappers.ts +++ b/public/app/angular/angular_wrappers.ts @@ -7,6 +7,7 @@ import EmptyListCTA from '../core/components/EmptyListCTA/EmptyListCTA'; import { TagFilter } from '../core/components/TagFilter/TagFilter'; import { MetricSelect } from '../core/components/Select/MetricSelect'; import { + ClipboardButton, ColorPicker, DataLinksInlineEditor, DataSourceHttpSettings, @@ -200,4 +201,8 @@ export function registerAngularDirectives() { ['datasource', { watchDepth: 'reference' }], 'onChange', ]); + + react2AngularDirective('clipboardButton', ClipboardButton, [ + ['getText', { watchDepth: 'reference', wrapApply: true }], + ]); } diff --git a/public/app/angular/misc.ts b/public/app/angular/misc.ts index dec5cd26ff0..bd31ad80fdf 100644 --- a/public/app/angular/misc.ts +++ b/public/app/angular/misc.ts @@ -1,8 +1,5 @@ import angular from 'angular'; -import Clipboard from 'clipboard'; import coreModule from './core_module'; -import { appEvents } from 'app/core/core'; -import { AppEvents } from '@grafana/data'; /** @ngInject */ function tip($compile: any) { @@ -23,31 +20,6 @@ function tip($compile: any) { }; } -function clipboardButton() { - return { - scope: { - getText: '&clipboardButton', - }, - link: (scope: any, elem: any) => { - scope.clipboard = new Clipboard(elem[0], { - text: () => { - return scope.getText(); - }, - }); - - scope.clipboard.on('success', () => { - appEvents.emit(AppEvents.alertSuccess, ['Content copied to clipboard']); - }); - - scope.$on('$destroy', () => { - if (scope.clipboard) { - scope.clipboard.destroy(); - } - }); - }, - }; -} - /** @ngInject */ function compile($compile: any) { return { @@ -209,7 +181,6 @@ function gfDropdown($parse: any, $compile: any, $timeout: any) { } coreModule.directive('tip', tip); -coreModule.directive('clipboardButton', clipboardButton); coreModule.directive('compile', compile); coreModule.directive('watchChange', watchChange); coreModule.directive('editorOptBool', editorOptBool); diff --git a/public/app/core/components/CopyToClipboard/CopyToClipboard.tsx b/public/app/core/components/CopyToClipboard/CopyToClipboard.tsx deleted file mode 100644 index 0b618d09d9f..00000000000 --- a/public/app/core/components/CopyToClipboard/CopyToClipboard.tsx +++ /dev/null @@ -1,78 +0,0 @@ -import React, { PureComponent, ReactNode } from 'react'; -import ClipboardJS from 'clipboard'; - -interface Props { - text: () => string; - elType?: string | React.ForwardRefExoticComponent; - onSuccess?: (evt: any) => void; - onError?: (evt: any) => void; - className?: string; - children?: ReactNode; -} - -export class CopyToClipboard extends PureComponent { - clipboardjs?: ClipboardJS; - myRef: any; - - constructor(props: Props) { - super(props); - this.myRef = React.createRef(); - } - - componentDidMount() { - this.initClipboardJS(); - } - - componentDidUpdate() { - if (this.clipboardjs) { - this.clipboardjs.destroy(); - } - this.initClipboardJS(); - } - - initClipboardJS = () => { - const { text, onSuccess, onError } = this.props; - - this.clipboardjs = new ClipboardJS(this.myRef.current, { - text: text, - }); - - if (onSuccess) { - this.clipboardjs.on('success', (evt) => { - evt.clearSelection(); - onSuccess(evt); - }); - } - - if (onError) { - this.clipboardjs.on('error', (evt) => { - console.error('Action:', evt.action); - console.error('Trigger:', evt.trigger); - onError(evt); - }); - } - }; - - componentWillUnmount() { - if (this.clipboardjs) { - this.clipboardjs.destroy(); - } - } - - getElementType = () => { - return this.props.elType || 'button'; - }; - - render() { - const { elType, text, children, onError, onSuccess, ...restProps } = this.props; - - return React.createElement( - this.getElementType(), - { - ref: this.myRef, - ...restProps, - }, - this.props.children - ); - } -} diff --git a/public/app/features/alerting/TestRuleResult.tsx b/public/app/features/alerting/TestRuleResult.tsx index 4a53b3edb3a..76669a9db90 100644 --- a/public/app/features/alerting/TestRuleResult.tsx +++ b/public/app/features/alerting/TestRuleResult.tsx @@ -1,7 +1,6 @@ import React, { PureComponent } from 'react'; -import { LoadingPlaceholder, JSONFormatter, Icon, HorizontalGroup } from '@grafana/ui'; +import { LoadingPlaceholder, JSONFormatter, Icon, HorizontalGroup, ClipboardButton } from '@grafana/ui'; import appEvents from 'app/core/app_events'; -import { CopyToClipboard } from 'app/core/components/CopyToClipboard/CopyToClipboard'; import { DashboardModel, PanelModel } from '../dashboard/state'; import { getBackendSrv } from '@grafana/runtime'; import { AppEvents } from '@grafana/data'; @@ -107,9 +106,9 @@ export class TestRuleResult extends PureComponent {
{this.renderExpandCollapse()}
- - Copy to Clipboard - + + Copy to Clipboard +
diff --git a/public/app/features/dashboard/components/SaveDashboard/forms/SaveProvisionedDashboardForm.tsx b/public/app/features/dashboard/components/SaveDashboard/forms/SaveProvisionedDashboardForm.tsx index 511518359c7..0bf291f0de0 100644 --- a/public/app/features/dashboard/components/SaveDashboard/forms/SaveProvisionedDashboardForm.tsx +++ b/public/app/features/dashboard/components/SaveDashboard/forms/SaveProvisionedDashboardForm.tsx @@ -1,8 +1,7 @@ import React, { useCallback, useState } from 'react'; import { css } from '@emotion/css'; import { saveAs } from 'file-saver'; -import { Button, Modal, stylesFactory, TextArea, useTheme } from '@grafana/ui'; -import { CopyToClipboard } from 'app/core/components/CopyToClipboard/CopyToClipboard'; +import { Button, ClipboardButton, Modal, stylesFactory, TextArea, useTheme } from '@grafana/ui'; import { SaveDashboardFormProps } from '../types'; import { AppEvents, GrafanaTheme } from '@grafana/data'; import appEvents from '../../../../../core/app_events'; @@ -61,9 +60,9 @@ export const SaveProvisionedDashboardForm: React.FC = ({ - dashboardJSON} elType={Button} onSuccess={onCopyToClipboardSuccess}> + dashboardJSON} onClipboardCopy={onCopyToClipboardSuccess}> Copy JSON to clipboard - + diff --git a/public/app/features/inspector/QueryInspector.tsx b/public/app/features/inspector/QueryInspector.tsx index 226eb1e6941..8bf3ffd05c4 100644 --- a/public/app/features/inspector/QueryInspector.tsx +++ b/public/app/features/inspector/QueryInspector.tsx @@ -1,10 +1,9 @@ import React, { PureComponent } from 'react'; -import { Button, JSONFormatter, LoadingPlaceholder } from '@grafana/ui'; +import { Button, ClipboardButton, JSONFormatter, LoadingPlaceholder } from '@grafana/ui'; import { selectors } from '@grafana/e2e-selectors'; import { AppEvents, DataFrame } from '@grafana/data'; import appEvents from 'app/core/app_events'; -import { CopyToClipboard } from 'app/core/components/CopyToClipboard/CopyToClipboard'; import { PanelModel } from 'app/features/dashboard/state'; import { getPanelInspectorStyles } from './styles'; import { supportsDataQuery } from 'app/features/dashboard/components/PanelEditor/utils'; @@ -294,16 +293,15 @@ export class QueryInspector extends PureComponent { )} {haveData && ( - - - + Copy to clipboard + )}
diff --git a/yarn.lock b/yarn.lock index 03ce28eed80..ea6e156280c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3808,7 +3808,6 @@ __metadata: "@testing-library/react-hooks": 7.0.2 "@testing-library/user-event": 13.5.0 "@types/classnames": 2.3.0 - "@types/clipboard": 2.0.1 "@types/common-tags": ^1.8.0 "@types/d3": 7.1.0 "@types/enzyme": 3.10.5 @@ -3844,7 +3843,6 @@ __metadata: babel-loader: 8.2.2 calculate-size: 1.1.1 classnames: 2.3.1 - clipboard: 2.0.4 common-tags: ^1.8.0 core-js: 3.20.0 css-loader: 6.5.1 @@ -5412,7 +5410,7 @@ __metadata: ramda: ^0.27.1 peerDependencies: "@babel/core": ^7.0.0 - babel-plugin-macros: 2 || 3 + babel-plugin-macros: 2 || 3 typescript: 2 || 3 || 4 bin: lingui: lingui.js @@ -5467,7 +5465,7 @@ __metadata: "@lingui/conf": ^3.12.1 ramda: ^0.27.1 peerDependencies: - babel-plugin-macros: 2 || 3 + babel-plugin-macros: 2 || 3 checksum: 83384f2d796dbc7ecadb74a1c6159ccad82976c194f97b82c2ba72cc8113f95bf0193aa6a4e9596db25b713129a9037a873bf1282bc30e197c2a72855707162f languageName: node linkType: hard @@ -5480,7 +5478,7 @@ __metadata: "@lingui/conf": ^3.13.0 ramda: ^0.27.1 peerDependencies: - babel-plugin-macros: 2 || 3 + babel-plugin-macros: 2 || 3 checksum: 5b80f4fe162e2be76462a124d267d97a7b7c5eacbf966b603e44b06010b016235366ebfa426473262bd72caffc1b03b351743a4cf909c8a77e4319e419abab07 languageName: node linkType: hard @@ -8676,13 +8674,6 @@ __metadata: languageName: node linkType: hard -"@types/clipboard@npm:2.0.1": - version: 2.0.1 - resolution: "@types/clipboard@npm:2.0.1" - checksum: 4ef5af19ac1055693a313194396e00a3f42326b45dd5ae90a2bb47a643957f313ce864c430f387fb16c78c9cd2cde44af84f0f7fd9f2695674a75161a7016937 - languageName: node - linkType: hard - "@types/color-convert@npm:^2.0.0": version: 2.0.0 resolution: "@types/color-convert@npm:2.0.0" @@ -14036,17 +14027,6 @@ __metadata: languageName: node linkType: hard -"clipboard@npm:2.0.4": - version: 2.0.4 - resolution: "clipboard@npm:2.0.4" - dependencies: - good-listener: ^1.2.2 - select: ^1.1.2 - tiny-emitter: ^2.0.0 - checksum: 2f44556f713e940a69a20f95dde9ebc4dc7f6ec121562496a5358eb1893583f6554c64b8a54051cb1d3f637df71efd035afd9baf0b28d8b8799803e8556545db - languageName: node - linkType: hard - "cliui@npm:^6.0.0": version: 6.0.0 resolution: "cliui@npm:6.0.0" @@ -16330,13 +16310,6 @@ __metadata: languageName: node linkType: hard -"delegate@npm:^3.1.2": - version: 3.2.0 - resolution: "delegate@npm:3.2.0" - checksum: d943058fe05897228b158cbd1bab05164df28c8f54127873231d6b03b0a5acc1b3ee1f98ac70ccc9b79cd84aa47118a7de111fee2923753491583905069da27d - languageName: node - linkType: hard - "delegates@npm:^1.0.0": version: 1.0.0 resolution: "delegates@npm:1.0.0" @@ -19486,15 +19459,6 @@ __metadata: languageName: node linkType: hard -"good-listener@npm:^1.2.2": - version: 1.2.2 - resolution: "good-listener@npm:1.2.2" - dependencies: - delegate: ^3.1.2 - checksum: f39fb82c4e41524f56104cfd2d7aef1a88e72f3f75139115fbdf98cc7d844e0c1b39218b2e83438c6188727bf904ed78c7f0f2feff67b32833bc3af7f0202b33 - languageName: node - linkType: hard - "got@npm:^6.7.1": version: 6.7.1 resolution: "got@npm:6.7.1" @@ -19592,7 +19556,6 @@ __metadata: "@types/angular": 1.8.3 "@types/angular-route": 1.7.0 "@types/classnames": 2.3.0 - "@types/clipboard": 2.0.1 "@types/common-tags": ^1.8.0 "@types/d3": 7.1.0 "@types/d3-force": ^2.1.0 @@ -19667,7 +19630,6 @@ __metadata: calculate-size: 1.1.1 centrifuge: 2.8.4 classnames: 2.3.1 - clipboard: 2.0.4 comlink: 4.3.1 common-tags: ^1.8.0 copy-webpack-plugin: 9.0.1 @@ -31573,13 +31535,6 @@ __metadata: languageName: node linkType: hard -"select@npm:^1.1.2": - version: 1.1.2 - resolution: "select@npm:1.1.2" - checksum: 4346151e94f226ea6131e44e68e6d837f3fdee64831b756dd657cc0b02f4cb5107f867cb34a1d1216ab7737d0bf0645d44546afb030bbd8d64e891f5e4c4814e - languageName: node - linkType: hard - "selection-is-backward@npm:^1.0.0": version: 1.0.0 resolution: "selection-is-backward@npm:1.0.0" @@ -33749,13 +33704,6 @@ __metadata: languageName: node linkType: hard -"tiny-emitter@npm:^2.0.0": - version: 2.1.0 - resolution: "tiny-emitter@npm:2.1.0" - checksum: fbcfb5145751a0e3b109507a828eb6d6d4501352ab7bb33eccef46e22e9d9ad3953158870a6966a59e57ab7c3f9cfac7cab8521db4de6a5e757012f4677df2dd - languageName: node - linkType: hard - "tiny-invariant@npm:^1.0.1, tiny-invariant@npm:^1.0.2, tiny-invariant@npm:^1.0.6": version: 1.1.0 resolution: "tiny-invariant@npm:1.1.0"