Răsfoiți Sursa

Merge pull request #45 from smiclea/CORWEB-59

Implemented the ability to copy the execution ID to clipboard CORWEB-59
Dorin Paslaru 8 ani în urmă
părinte
comite
0188c3fda4

+ 9 - 0
src/components/App/App.scss

@@ -178,6 +178,15 @@ button {
     margin-top: 16px;
     cursor: pointer;
   }
+  .copyButton {
+    opacity: 0;
+    width: 16px;
+    height: 16px;
+    display: inline-block;
+    margin-left: 8px;
+    background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48c3ZnIHdpZHRoPSIxNnB4IiBoZWlnaHQ9IjE2cHgiIHZpZXdCb3g9IjAgMCAxNiAxNiIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIj4gICAgICAgIDx0aXRsZT5pY29uLWNvcHktbGluZTwvdGl0bGU+ICAgIDxkZXNjPkNyZWF0ZWQgd2l0aCBTa2V0Y2guPC9kZXNjPiAgICA8ZGVmcz48L2RlZnM+ICAgIDxnIGlkPSJTeW1ib2xzIiBzdHJva2U9Im5vbmUiIHN0cm9rZS13aWR0aD0iMSIgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIj4gICAgICAgIDxnIGlkPSJpY29uLWNvcHktbGluZSIgZmlsbC1ydWxlPSJub256ZXJvIiBmaWxsPSIjMDA0NENBIj4gICAgICAgICAgICA8ZyBpZD0iR3JvdXAiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDAuMDAwMDAwLCA1LjAwMDAwMCkiPiAgICAgICAgICAgICAgICA8cGF0aCBkPSJNMyw0LjIgQzIuMDA1ODg3NDUsNC4yIDEuMiw1LjAwNTg4NzQ1IDEuMiw2IEwxLjIsOCBDMS4yLDguOTk0MTEyNTUgMi4wMDU4ODc0NSw5LjggMyw5LjggTDksOS44IEM5Ljk5NDExMjU1LDkuOCAxMC44LDguOTk0MTEyNTUgMTAuOCw4IEwxMC44LDYgQzEwLjgsNS4wMDU4ODc0NSA5Ljk5NDExMjU1LDQuMiA5LDQuMiBMMyw0LjIgWiBNMywzIEw5LDMgQzEwLjY1Njg1NDIsMyAxMiw0LjM0MzE0NTc1IDEyLDYgTDEyLDggQzEyLDkuNjU2ODU0MjUgMTAuNjU2ODU0MiwxMSA5LDExIEwzLDExIEMxLjM0MzE0NTc1LDExIDIuMDI5MDYxMjVlLTE2LDkuNjU2ODU0MjUgMCw4IEwwLDYgQy0yLjAyOTA2MTI1ZS0xNiw0LjM0MzE0NTc1IDEuMzQzMTQ1NzUsMyAzLDMgWiIgaWQ9IlJlY3RhbmdsZS1Db3B5Ij48L3BhdGg+ICAgICAgICAgICAgICAgIDxwYXRoIGQ9Ik0xMyw3IEMxNC42NTY4NTQyLDcgMTYsNS42NTY4NTQyNSAxNiw0IEwxNiwyIEMxNiwwLjM0MzE0NTc1MSAxNC42NTY4NTQyLC0xIDEzLC0xIEw3LC0xIEM1LjM0MzE0NTc1LC0xIDQsMC4zNDMxNDU3NTEgNCwyIEw1LjIsMiBDNS4yLDEuMDA1ODg3NDUgNi4wMDU4ODc0NSwwLjIgNywwLjIgTDEzLDAuMiBDMTMuOTk0MTEyNSwwLjIgMTQuOCwxLjAwNTg4NzQ1IDE0LjgsMiBMMTQuOCw0IEMxNC44LDQuOTk0MTEyNTUgMTMuOTk0MTEyNSw1LjggMTMsNS44IEwxMyw3IFoiIGlkPSJSZWN0YW5nbGUtQ29weS0yIj48L3BhdGg+ICAgICAgICAgICAgPC9nPiAgICAgICAgPC9nPiAgICA8L2c+PC9zdmc+);
+    transition: opacity $animation-swift-out;
+  }
 }
 :global(.checkbox-normal) {
   /* Base for label styling */

+ 34 - 0
src/components/Helper/Helper.js

@@ -49,6 +49,40 @@ class Helper extends Component {
   static convertCloudLabel(label) {
     return cloudLabels[label] || label
   }
+
+  /**
+   * Copies specified text to clipboard
+   * @param {string} text The text to copy
+   * @return True if successful, false otherwise
+   */
+  static copyTextToClipboard(text) {
+    let textArea = document.createElement("textarea");
+    textArea.style.position = 'fixed';
+    textArea.style.top = 0;
+    textArea.style.left = 0;
+    textArea.style.width = '2em';
+    textArea.style.height = '2em';
+    textArea.style.padding = 0;
+    textArea.style.border = 'none';
+    textArea.style.outline = 'none';
+    textArea.style.boxShadow = 'none';
+    textArea.style.background = 'transparent';
+
+    textArea.value = text;
+
+    document.body.appendChild(textArea);
+
+    textArea.select();
+
+    let successful;
+
+    try {
+      successful = document.execCommand('copy');
+    } finally {
+      document.body.removeChild(textArea);
+      return successful
+    }
+  }
 }
 
 export default Helper;

+ 18 - 1
src/components/MigrationDetail/MigrationDetail.js

@@ -19,6 +19,8 @@ import React, { Component, PropTypes } from 'react';
 import withStyles from 'isomorphic-style-loader/lib/withStyles';
 import s from './MigrationDetail.scss';
 import Moment from 'react-moment';
+import Helper from "../Helper"
+import NotificationActions from '../../actions/NotificationActions'
 import Location from '../../core/Location';
 import EndpointLink from '../EndpointLink';
 import ConfirmationDialog from '../ConfirmationDialog'
@@ -72,6 +74,16 @@ class MigrationDetail extends Component {
     })
   }
 
+  copyIdClick(item) {
+    let succesful = Helper.copyTextToClipboard(item.id)
+
+    if (succesful) {
+      NotificationActions.notify('The ID has been copied to clipboard.')
+    } else {
+      NotificationActions.notify('The ID couldn\'t be copied', 'error')
+    }
+  }
+
   render() {
     let item = this.props.migration
     let output = null
@@ -138,8 +150,13 @@ class MigrationDetail extends Component {
                 <div className={s.titleIp}>
                   Id
                 </div>
-                <div className={s.value}>
+                <div className={s.value + ' ' + s.idValue}
+                  onClick={() => this.copyIdClick(item)}
+                  onMouseDown={e => e.stopPropagation()}
+                  onMouseUp={e => e.stopPropagation()}
+                >
                   <a>{item.id}</a>
+                  <span className="copyButton"></span>
                 </div>
               </div>
             </div>

+ 7 - 0
src/components/MigrationDetail/MigrationDetail.scss

@@ -70,6 +70,13 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
     a {
       color: $blue;
     }
+     &.idValue {
+      cursor: pointer;
+      display: inline-block;
+    }
+    &:hover :global(.copyButton) {
+      opacity: 1;
+    }
   }
 }
 .cloudImg {

+ 17 - 1
src/components/ReplicaDetail/ReplicaDetail.js

@@ -20,6 +20,7 @@ import withStyles from 'isomorphic-style-loader/lib/withStyles';
 import s from './ReplicaDetail.scss';
 import Moment from 'react-moment';
 import Helper from "../Helper";
+import NotificationActions from '../../actions/NotificationActions'
 import Location from '../../core/Location';
 import EndpointLink from '../EndpointLink';
 import ConfirmationDialog from '../ConfirmationDialog'
@@ -73,6 +74,16 @@ class MigrationDetail extends Component {
     })
   }
 
+  copyIdClick(item) {
+    let succesful = Helper.copyTextToClipboard(item.id)
+
+    if (succesful) {
+      NotificationActions.notify('The ID has been copied to clipboard.')
+    } else {
+      NotificationActions.notify('The ID couldn\'t be copied', 'error')
+    }
+  }
+
   render() {
     let item = this.props.replica
     let output = null
@@ -141,8 +152,13 @@ class MigrationDetail extends Component {
                 <div className={s.titleIp}>
                   Id
                 </div>
-                <div className={s.value}>
+                <div className={s.value + ' ' + s.idValue}
+                  onClick={() => this.copyIdClick(item)}
+                  onMouseDown={e => e.stopPropagation()}
+                  onMouseUp={e => e.stopPropagation()}
+                >
                   <a>{item.id}</a>
+                  <span className="copyButton"></span>
                 </div>
               </div>
             </div>

+ 7 - 0
src/components/ReplicaDetail/ReplicaDetail.scss

@@ -67,6 +67,13 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
     font-weight: $weight-regular;
     font-size: 14px;
     color: $black;
+    &.idValue {
+      cursor: pointer;
+      display: inline-block;
+    }
+    &:hover :global(.copyButton) {
+      opacity: 1;
+    }
     a {
       color: $blue;
     }

+ 2 - 1
src/components/Table/Table.js

@@ -50,7 +50,8 @@ class Table extends Component {
     for (let i in newProps.listItems) {
       // Use the previous open state if the table's parent ID is the same
       // i.e. don't close the collapser if new props arrive
-      let prevOpenState = newProps.parentId === this.state.parentId && this.state.openState[i]
+      let isSameParent = this.state.parentId === null || newProps.parentId === this.state.parentId
+      let prevOpenState = isSameParent && this.state.openState[i]
       openState.push(newProps.listItems[i].openState || prevOpenState)
     }
     this.setState({ openState: openState, parentId: newProps.parentId })

+ 18 - 1
src/components/Tasks/Tasks.js

@@ -21,6 +21,7 @@ import moment from 'moment';
 import Table from '../Table';
 import s from './Tasks.scss';
 import TextTruncate from 'react-text-truncate';
+import NotificationActions from '../../actions/NotificationActions'
 import LoadingIcon from "../LoadingIcon/LoadingIcon";
 import ProgressBar from '../ProgressBar';
 import Helper from '../Helper';
@@ -113,7 +114,13 @@ class Tasks extends Component {
           </div>
           <div className={s.group}>
             <div className={s.detailTitle}>ID</div>
-            <div className={s.detailValue}>{item.id}</div>
+            <div className={s.detailValue + ' ' + s.detailIdValue}
+              onClick={() => this.copyIdClick(item)}
+              onMouseDown={e => e.stopPropagation()}
+              onMouseUp={e => e.stopPropagation()}
+            >{item.id}
+               <span className="copyButton"></span>
+            </div>
           </div>
           <div className={s.group}>
             <div className={s.detailTitle}>Exception details</div>
@@ -153,6 +160,16 @@ class Tasks extends Component {
     this.setState({ listItems: listItems, execution: newProps.execution })
   }
 
+  copyIdClick(item) {
+    let succesful = Helper.copyTextToClipboard(item.id)
+
+    if (succesful) {
+      NotificationActions.notify('The ID has been copied to clipboard.')
+    } else {
+      NotificationActions.notify('The ID couldn\'t be copied', 'error')
+    }
+  }
+
   render() {
     return (
       <div className={s.root}>

+ 6 - 4
src/components/Tasks/Tasks.scss

@@ -17,10 +17,6 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 @import '../variables.scss';
 
-.root {
-
-}
-
 .container {
   margin: 0 auto;
   padding: 0 0 40px;
@@ -75,6 +71,12 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
         font-weight: $weight-regular;
         font-size: 14px;
         color: $gray-dark;
+        &.detailIdValue {
+          display: inline-block;
+        }
+        &:hover :global(.copyButton) {
+          opacity: 1;
+        }
       }
       &:nth-child(2n) {
         width: 500px;