diff --git a/package.json b/package.json index 2e7b35c0..f3aec65b 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "bootstrap": "^4.1.1", "chart.js": "^2.7.2", "classnames": "^2.2.6", + "jsonexport": "^2.2.1", "moment": "^2.22.2", "promise": "8.0.1", "prop-types": "^15.6.2", diff --git a/src/index.scss b/src/index.scss index fc7cd163..dfeabf0a 100644 --- a/src/index.scss +++ b/src/index.scss @@ -21,7 +21,8 @@ body, .dropdown-item { cursor: pointer; } -.dropdown-item.active { +.dropdown-item.active, +.dropdown-item:active { @extend .bg-main; } diff --git a/src/servers/ServersDropdown.js b/src/servers/ServersDropdown.js index 5f59d204..cd345cc7 100644 --- a/src/servers/ServersDropdown.js +++ b/src/servers/ServersDropdown.js @@ -1,4 +1,4 @@ -import { isEmpty, pick } from 'ramda'; +import { isEmpty, pick, values } from 'ramda'; import React from 'react'; import { connect } from 'react-redux'; import { Link } from 'react-router-dom'; @@ -6,27 +6,39 @@ import { DropdownItem, DropdownMenu, DropdownToggle, UncontrolledDropdown } from import { listServers } from './reducers/server'; import { selectServer } from '../servers/reducers/selectedServer'; +import serversExporter from '../servers/services/ServersExporter'; + +const defaultProps = { + serversExporter, +}; export class ServersDropdown extends React.Component { renderServers = () => { - const { servers, selectedServer, selectServer } = this.props; + const { servers, selectedServer, selectServer, serversExporter } = this.props; if (isEmpty(servers)) { return Add a server first... } - return Object.values(servers).map(({ name, id }) => ( - - selectServer(id)} // FIXME This should be implicit - > - {name} + return ( + + {values(servers).map(({ name, id }) => ( + selectServer(id)} // FIXME This should be implicit + > + {name} + + ))} + + + Export servers - - )); + + ); }; componentDidMount() { @@ -35,12 +47,17 @@ export class ServersDropdown extends React.Component { render() { return ( - + Servers - {this.renderServers()} + {this.renderServers()} ); } } -export default connect(pick(['servers', 'selectedServer']), { listServers, selectServer })(ServersDropdown); +ServersDropdown.defaultProps = defaultProps; + +export default connect( + pick(['servers', 'selectedServer']), + { listServers, selectServer } +)(ServersDropdown); diff --git a/src/servers/services/ServersExporter.js b/src/servers/services/ServersExporter.js new file mode 100644 index 00000000..ce04f8c5 --- /dev/null +++ b/src/servers/services/ServersExporter.js @@ -0,0 +1,54 @@ +import Storage from '../../utils/Storage'; +import jsonexport from 'jsonexport/dist'; +import { dissoc, values } from 'ramda'; + +const SERVERS_STORAGE_KEY = 'servers'; + +const saveCsv = (window, csv) => { + const { navigator, document } = window; + const filename = 'shlink-servers.csv'; + const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' }); + + // IE10 and IE11 + if (navigator.msSaveBlob) { + navigator.msSaveBlob(blob, filename); + return; + } + + // Modern browsers + const link = document.createElement('a'); + const url = URL.createObjectURL(blob); + link.setAttribute('href', url); + link.setAttribute('download', filename); + link.style.visibility = 'hidden'; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); +}; + +export class ServersExporter { + constructor(storage, window, jsonexport) { + this.storage = storage; + this.window = window; + this.jsonexport = jsonexport; + } + + exportServers = async () => { + const servers = values(this.storage.get(SERVERS_STORAGE_KEY) || {}).map( + dissoc('id') + ); + + try { + const csv = await new Promise((resolve, reject) => { + this.jsonexport(servers, (err, csv) => err ? reject(err) : resolve(csv)); + }); + + saveCsv(this.window, csv); + } catch (e) { + // FIXME Handle error + console.error(e); + } + }; +} + +export default new ServersExporter(Storage, global.window, jsonexport); diff --git a/yarn.lock b/yarn.lock index eb2340f8..f9afa9c7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4436,6 +4436,10 @@ json5@^0.5.0, json5@^0.5.1: version "0.5.1" resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821" +jsonexport@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/jsonexport/-/jsonexport-2.2.1.tgz#b25a8a6e805e7179ef20460715ba2b92ebc32753" + jsonfile@^2.1.0: version "2.4.0" resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-2.4.0.tgz#3736a2b428b87bbda0cc83b53fa3d633a35c2ae8"