Browse Source

ref(ui): Switch from clipboard to copy-text-to-clipboard (#24492)

Scott Cooper 4 years ago
parent
commit
83278819ab
3 changed files with 53 additions and 86 deletions
  1. 1 2
      package.json
  2. 47 70
      src/sentry/static/sentry/app/components/clipboard.tsx
  3. 5 14
      yarn.lock

+ 1 - 2
package.json

@@ -28,7 +28,6 @@
     "@sentry/tracing": "6.3.0-beta.4",
     "@sentry/utils": "6.3.0-beta.4",
     "@types/classnames": "2.2.11",
-    "@types/clipboard": "^2.0.1",
     "@types/color": "^3.0.0",
     "@types/create-react-class": "^15.6.2",
     "@types/diff": "4.0.2",
@@ -61,9 +60,9 @@
     "bootstrap": "3.4.1",
     "classnames": "2.2.6",
     "clean-webpack-plugin": "^3.0.0",
-    "clipboard": "^1.7.1",
     "color": "^3.1.3",
     "compression-webpack-plugin": "^6.1.1",
+    "copy-text-to-clipboard": "2.2.0",
     "core-js": "^3.2.1",
     "create-react-class": "^15.6.2",
     "crypto-js": "4.0.0",

+ 47 - 70
src/sentry/static/sentry/app/components/clipboard.tsx

@@ -1,91 +1,68 @@
 import React from 'react';
-import ReactDOM from 'react-dom';
-import Clip from 'clipboard';
+import copy from 'copy-text-to-clipboard';
 
 import {addErrorMessage, addSuccessMessage} from 'app/actionCreators/indicator';
-
-type DefaultProps = {
-  successMessage: string;
-  errorMessage: string;
-  hideMessages: boolean;
-};
+import {t} from 'app/locale';
 
 type Props = {
+  children: React.ReactElement<{onClick: () => void}>;
+  /** Text to be copied on click */
   value: string;
+  successMessage?: string;
+  errorMessage?: string;
+  hideMessages?: boolean;
+  /** Hide children if browser does not support copy */
   hideUnsupported?: boolean;
   onSuccess?: () => void;
   onError?: () => void;
-} & DefaultProps;
-
-class Clipboard extends React.Component<Props> {
-  static defaultProps: DefaultProps = {
-    hideMessages: false,
-    successMessage: 'Copied to clipboard',
-    errorMessage: 'Error copying to clipboard',
-  };
-
-  componentWillUnmount() {
-    if (this.clipboard) {
-      this.clipboard.destroy();
-    }
-  }
-
-  clipboard!: ClipboardJS;
-
-  handleMount = (ref: HTMLElement) => {
-    if (!ref) {
-      return;
-    }
-
-    const {hideMessages, successMessage, errorMessage, onSuccess, onError} = this.props;
-    const hasSuccessCb = typeof onSuccess === 'function';
-    const hasErrorCb = typeof onError === 'function';
-    const bindEventHandlers = !hideMessages || hasSuccessCb || hasErrorCb;
+};
 
-    // eslint-disable-next-line react/no-find-dom-node
-    this.clipboard = new Clip(ReactDOM.findDOMNode(ref) as Element, {
-      text: () => this.props.value,
-    });
+/**
+ * copy-text-to-clipboard relies on `document.execCommand('copy')`
+ */
+function isSupported() {
+  const support = !!document.queryCommandSupported;
+  return support && !!document.queryCommandSupported('copy');
+}
 
-    if (!bindEventHandlers) {
+function Clipboard({
+  children,
+  value,
+  successMessage = t('Copied to clipboard'),
+  errorMessage = t('Error copying to clipboard'),
+  hideMessages = false,
+  hideUnsupported,
+  onSuccess,
+  onError,
+}: Props) {
+  function handleClick() {
+    // Copy returns whether it succeeded to copy the text
+    const success = copy(value);
+    if (!success) {
+      if (!hideMessages) {
+        addErrorMessage(errorMessage);
+      }
+      onError?.();
       return;
     }
 
-    this.clipboard
-      .on('success', () => {
-        if (!hideMessages) {
-          addSuccessMessage(successMessage);
-        }
-        if (onSuccess && hasSuccessCb) {
-          onSuccess();
-        }
-      })
-      .on('error', () => {
-        if (!hideMessages) {
-          addErrorMessage(errorMessage);
-        }
-        if (onError && hasErrorCb) {
-          onError();
-        }
-      });
-  };
-
-  render() {
-    const {children, hideUnsupported} = this.props;
-
-    // Browser doesn't support `execCommand`
-    if (hideUnsupported && !Clip.isSupported()) {
-      return null;
+    if (!hideMessages) {
+      addSuccessMessage(successMessage);
     }
+    onSuccess?.();
+  }
 
-    if (!React.isValidElement(children)) {
-      return null;
-    }
+  if (hideUnsupported && !isSupported()) {
+    return null;
+  }
 
-    return React.cloneElement(children, {
-      ref: this.handleMount,
-    });
+  if (!React.isValidElement(children)) {
+    return null;
   }
+
+  return React.cloneElement(children, {
+    onClick: handleClick,
+  });
 }
 
 export default Clipboard;

+ 5 - 14
yarn.lock

@@ -2644,11 +2644,6 @@
   resolved "https://registry.yarnpkg.com/@types/classnames/-/classnames-2.2.11.tgz#2521cc86f69d15c5b90664e4829d84566052c1cf"
   integrity sha512-2koNhpWm3DgWRp5tpkiJ8JGc1xTn2q0l+jUNUE7oMKXUf5NpI9AIdC4kbjGNFBdHtcxBD18LAksoudAVhFKCjw==
 
-"@types/clipboard@^2.0.1":
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/@types/clipboard/-/clipboard-2.0.1.tgz#75a74086c293d75b12bc93ff13bc7797fef05a40"
-  integrity sha512-gJJX9Jjdt3bIAePQRRjYWG20dIhAgEqonguyHxXuqALxsoDsDLimihqrSg8fXgVTJ4KZCzkfglKtwsh/8dLfbA==
-
 "@types/color-convert@*":
   version "1.9.0"
   resolved "https://registry.yarnpkg.com/@types/color-convert/-/color-convert-1.9.0.tgz#bfa8203e41e7c65471e9841d7e306a7cd8b5172d"
@@ -5018,15 +5013,6 @@ cli-width@^2.0.0:
   resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639"
   integrity sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=
 
-clipboard@^1.7.1:
-  version "1.7.1"
-  resolved "https://registry.yarnpkg.com/clipboard/-/clipboard-1.7.1.tgz#360d6d6946e99a7a1fef395e42ba92b5e9b5a16b"
-  integrity sha1-Ng1taUbpmnof7zleQrqStem1oWs=
-  dependencies:
-    good-listener "^1.2.2"
-    select "^1.1.2"
-    tiny-emitter "^2.0.0"
-
 clipboard@^2.0.0:
   version "2.0.4"
   resolved "https://registry.yarnpkg.com/clipboard/-/clipboard-2.0.4.tgz#836dafd66cf0fea5d71ce5d5b0bf6e958009112d"
@@ -5332,6 +5318,11 @@ copy-descriptor@^0.1.0:
   resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d"
   integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=
 
+copy-text-to-clipboard@2.2.0:
+  version "2.2.0"
+  resolved "https://registry.yarnpkg.com/copy-text-to-clipboard/-/copy-text-to-clipboard-2.2.0.tgz#329dd6daf8c42034c763ace567418401764579ae"
+  integrity sha512-WRvoIdnTs1rgPMkgA2pUOa/M4Enh2uzCwdKsOMYNAJiz/4ZvEJgmbF4OmninPmlFdAWisfeh0tH+Cpf7ni3RqQ==
+
 copy-to-clipboard@^3.0.8:
   version "3.2.0"
   resolved "https://registry.yarnpkg.com/copy-to-clipboard/-/copy-to-clipboard-3.2.0.tgz#d2724a3ccbfed89706fac8a894872c979ac74467"