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"