]> source.dussan.org Git - sonarqube.git/commitdiff
Create a Clipboard button
authorGrégoire Aubert <gregoire.aubert@sonarsource.com>
Wed, 10 Jan 2018 16:28:51 +0000 (17:28 +0100)
committerGrégoire Aubert <gregoire.aubert@sonarsource.com>
Thu, 25 Jan 2018 14:16:50 +0000 (15:16 +0100)
server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/Command.js
server/sonar-web/src/main/js/apps/tutorials/onboarding/commands/__tests__/__snapshots__/Command-test.js.snap
server/sonar-web/src/main/js/apps/users/components/TokensFormNewToken.tsx
server/sonar-web/src/main/js/components/controls/ClipboardButton.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/controls/__tests__/ClipboardButton-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/ClipboardButton-test.tsx.snap [new file with mode: 0644]
sonar-core/src/main/resources/org/sonar/l10n/core.properties

index efa4ee1c2e6d86a41e406e12c36f8824a92745bf..23d728a407ede3fcd69d43837aaab2d25d501cda 100644 (file)
@@ -19,9 +19,8 @@
  */
 // @flow
 import React from 'react';
-import Clipboard from 'clipboard';
 import classNames from 'classnames';
-import Tooltip from '../../../../components/controls/Tooltip';
+import ClipboardButton from '../../../../components/controls/ClipboardButton';
 import { translate } from '../../../../helpers/l10n';
 
 /*::
@@ -31,73 +30,21 @@ type Props = {
 };
 */
 
-/*::
-type State = {
-  tooltipShown: boolean
-};
-*/
-
 const s = ' \\' + '\n  ';
 
 export default class Command extends React.PureComponent {
-  /*:: clipboard: Object; */
-  /*:: copyButton: HTMLButtonElement; */
-  /*:: mounted: boolean; */
   /*:: props: Props; */
-  state /*: State */ = { tooltipShown: false };
-
-  componentDidMount() {
-    this.mounted = true;
-    this.clipboard = new Clipboard(this.copyButton);
-    this.clipboard.on('success', this.showTooltip);
-  }
-
-  componentDidUpdate() {
-    this.clipboard.destroy();
-    this.clipboard = new Clipboard(this.copyButton);
-    this.clipboard.on('success', this.showTooltip);
-  }
-
-  componentWillUnmount() {
-    this.mounted = false;
-    this.clipboard.destroy();
-  }
-
-  showTooltip = () => {
-    if (this.mounted) {
-      this.setState({ tooltipShown: true });
-      setTimeout(this.hideTooltip, 1000);
-    }
-  };
-
-  hideTooltip = () => {
-    if (this.mounted) {
-      this.setState({ tooltipShown: false });
-    }
-  };
 
   render() {
     const { command, isWindows } = this.props;
     const commandArray = Array.isArray(command) ? command.filter(line => line != null) : [command];
     const finalCommand = isWindows ? commandArray.join(' ') : commandArray.join(s);
 
-    const button = (
-      <button data-clipboard-text={finalCommand} ref={node => (this.copyButton = node)}>
-        {translate('copy')}
-      </button>
-    );
-
     return (
       <div
         className={classNames('onboarding-command', { 'onboarding-command-windows': isWindows })}>
         <pre>{finalCommand}</pre>
-        {this.state.tooltipShown ? (
-          <Tooltip defaultVisible={true} placement="top" overlay="Copied!" trigger="manual">
-            {button}
-          </Tooltip>
-        ) : (
-          button
-        )}
+        <ClipboardButton copyValue={finalCommand} tooltipPlacement="top" />
       </div>
     );
   }
index 63e31484c8ddc7c936631eabdf3210b989009b00..2bd0aafd5b8972f87950ddf6692e7b7bc35cf976 100644 (file)
@@ -12,12 +12,19 @@ bar"
       foo
 bar
     </pre>
-    <button
-      data-clipboard-text="foo
+    <ClipboardButton
+      copyValue="foo
 bar"
+      tooltipPlacement="top"
     >
-      copy
-    </button>
+      <button
+        className="js-copy-to-clipboard no-select"
+        data-clipboard-text="foo
+bar"
+      >
+        copy
+      </button>
+    </ClipboardButton>
   </div>
 </Command>
 `;
index e3bb9df6b3219971208c9fec9d92a0d556e634c0..f7b744e9c1fa86aefe6577aef276716a7d415ff9 100644 (file)
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import * as React from 'react';
-import * as Clipboard from 'clipboard';
-import Tooltip from '../../../components/controls/Tooltip';
-import { translate, translateWithParameters } from '../../../helpers/l10n';
+import ClipboardButton from '../../../components/controls/ClipboardButton';
+import { translateWithParameters } from '../../../helpers/l10n';
 
 interface Props {
   token: { name: string; token: string };
 }
 
-interface State {
-  tooltipShown: boolean;
-}
-
-export default class TokensFormNewToken extends React.PureComponent<Props, State> {
-  clipboard: Clipboard;
-  copyButton: HTMLButtonElement | null;
-  mounted: boolean;
-  state: State = { tooltipShown: false };
-
-  componentDidMount() {
-    this.mounted = true;
-    if (this.copyButton) {
-      this.clipboard = new Clipboard(this.copyButton);
-      this.clipboard.on('success', this.showTooltip);
-    }
-  }
-
-  componentDidUpdate() {
-    this.clipboard.destroy();
-    if (this.copyButton) {
-      this.clipboard = new Clipboard(this.copyButton);
-      this.clipboard.on('success', this.showTooltip);
-    }
-  }
-
-  componentWillUnmount() {
-    this.mounted = false;
-    this.clipboard.destroy();
-  }
-
-  showTooltip = () => {
-    if (this.mounted) {
-      this.setState({ tooltipShown: true });
-      setTimeout(() => {
-        if (this.mounted) {
-          this.setState({ tooltipShown: false });
-        }
-      }, 1000);
-    }
-  };
-
-  render() {
-    const { name, token } = this.props.token;
-    const button = (
-      <button
-        className="js-copy-to-clipboard no-select"
-        data-clipboard-text={token}
-        ref={node => (this.copyButton = node)}>
-        {translate('copy')}
-      </button>
-    );
-    return (
-      <div className="panel panel-white big-spacer-top">
-        <p className="alert alert-warning">
-          {translateWithParameters('users.tokens.new_token_created', name)}
-        </p>
-        {this.state.tooltipShown ? (
-          <Tooltip
-            defaultVisible={true}
-            placement="bottom"
-            overlay={translate('users.tokens.copied')}
-            trigger="manual">
-            {button}
-          </Tooltip>
-        ) : (
-          button
-        )}
-        <code className="big-spacer-left text-success">{token}</code>
-      </div>
-    );
-  }
+export default function TokensFormNewToken({ token }: Props) {
+  return (
+    <div className="panel panel-white big-spacer-top">
+      <p className="alert alert-warning">
+        {translateWithParameters('users.tokens.new_token_created', token.name)}
+      </p>
+      <ClipboardButton copyValue={token.token} />
+      <code className="big-spacer-left text-success">{token.token}</code>
+    </div>
+  );
 }
diff --git a/server/sonar-web/src/main/js/components/controls/ClipboardButton.tsx b/server/sonar-web/src/main/js/components/controls/ClipboardButton.tsx
new file mode 100644 (file)
index 0000000..dc9b146
--- /dev/null
@@ -0,0 +1,98 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import * as React from 'react';
+import * as classNames from 'classnames';
+import * as Clipboard from 'clipboard';
+import Tooltip from './Tooltip';
+import { translate } from '../../helpers/l10n';
+
+interface Props {
+  className?: string;
+  copyValue: string;
+  tooltipPlacement?: string;
+}
+
+interface State {
+  tooltipShown: boolean;
+}
+
+export default class ClipboardButton extends React.PureComponent<Props, State> {
+  clipboard: Clipboard;
+  copyButton: HTMLButtonElement | null;
+  mounted: boolean;
+  state: State = { tooltipShown: false };
+
+  componentDidMount() {
+    this.mounted = true;
+    if (this.copyButton) {
+      this.clipboard = new Clipboard(this.copyButton);
+      this.clipboard.on('success', this.showTooltip);
+    }
+  }
+
+  componentDidUpdate() {
+    if (this.clipboard) {
+      this.clipboard.destroy();
+    }
+    if (this.copyButton) {
+      this.clipboard = new Clipboard(this.copyButton);
+      this.clipboard.on('success', this.showTooltip);
+    }
+  }
+
+  componentWillUnmount() {
+    this.mounted = false;
+    this.clipboard.destroy();
+  }
+
+  showTooltip = () => {
+    if (this.mounted) {
+      this.setState({ tooltipShown: true });
+      setTimeout(() => {
+        if (this.mounted) {
+          this.setState({ tooltipShown: false });
+        }
+      }, 1000);
+    }
+  };
+
+  render() {
+    const button = (
+      <button
+        className={classNames('js-copy-to-clipboard no-select', this.props.className)}
+        data-clipboard-text={this.props.copyValue}
+        ref={node => (this.copyButton = node)}>
+        {translate('copy')}
+      </button>
+    );
+    if (this.state.tooltipShown) {
+      return (
+        <Tooltip
+          defaultVisible={true}
+          placement={this.props.tooltipPlacement || 'bottom'}
+          overlay={translate('copied_action')}
+          trigger="manual">
+          {button}
+        </Tooltip>
+      );
+    }
+    return button;
+  }
+}
diff --git a/server/sonar-web/src/main/js/components/controls/__tests__/ClipboardButton-test.tsx b/server/sonar-web/src/main/js/components/controls/__tests__/ClipboardButton-test.tsx
new file mode 100644 (file)
index 0000000..a33520e
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import * as React from 'react';
+import { shallow } from 'enzyme';
+import ClipboardButton from '../ClipboardButton';
+
+jest.useFakeTimers();
+
+it('should display correctly', () => {
+  const wrapper = shallow(<ClipboardButton copyValue="foo" />);
+  expect(wrapper).toMatchSnapshot();
+  (wrapper.instance() as ClipboardButton).showTooltip();
+  wrapper.update();
+  expect(wrapper).toMatchSnapshot();
+  jest.runAllTimers();
+  wrapper.update();
+  expect(wrapper.find('Tooltip')).toHaveLength(0);
+});
diff --git a/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/ClipboardButton-test.tsx.snap b/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/ClipboardButton-test.tsx.snap
new file mode 100644 (file)
index 0000000..61870f7
--- /dev/null
@@ -0,0 +1,26 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should display correctly 1`] = `
+<button
+  className="js-copy-to-clipboard no-select"
+  data-clipboard-text="foo"
+>
+  copy
+</button>
+`;
+
+exports[`should display correctly 2`] = `
+<Tooltip
+  defaultVisible={true}
+  overlay="copied_action"
+  placement="bottom"
+  trigger="manual"
+>
+  <button
+    className="js-copy-to-clipboard no-select"
+    data-clipboard-text="foo"
+  >
+    copy
+  </button>
+</Tooltip>
+`;
index 89c5ededa5704ecd1280092946e33ce0a2df9dbc..ce5c514e171c81dfc770cda9207be65227fb990b 100644 (file)
@@ -198,9 +198,10 @@ are_you_sure=Are you sure?
 assigned_to=Assigned to
 bulk_change=Bulk Change
 bulleted_point=Bulleted point
-coding_rules=Rules
 clear=Clear
 clear_all_filters=Clear All Filters
+coding_rules=Rules
+copied_action=Copied!
 created_by=Created by
 default_error_message=The request cannot be processed. Try again later.
 default_severity=Default severity