mirror of
https://github.com/shlinkio/shlink-web-client.git
synced 2024-12-23 09:30:31 +03:00
Added feature to export servers list
This commit is contained in:
parent
d84bf01937
commit
2b373cc4af
5 changed files with 94 additions and 17 deletions
|
@ -17,6 +17,7 @@
|
||||||
"bootstrap": "^4.1.1",
|
"bootstrap": "^4.1.1",
|
||||||
"chart.js": "^2.7.2",
|
"chart.js": "^2.7.2",
|
||||||
"classnames": "^2.2.6",
|
"classnames": "^2.2.6",
|
||||||
|
"jsonexport": "^2.2.1",
|
||||||
"moment": "^2.22.2",
|
"moment": "^2.22.2",
|
||||||
"promise": "8.0.1",
|
"promise": "8.0.1",
|
||||||
"prop-types": "^15.6.2",
|
"prop-types": "^15.6.2",
|
||||||
|
|
|
@ -21,7 +21,8 @@ body,
|
||||||
.dropdown-item {
|
.dropdown-item {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
.dropdown-item.active {
|
.dropdown-item.active,
|
||||||
|
.dropdown-item:active {
|
||||||
@extend .bg-main;
|
@extend .bg-main;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { isEmpty, pick } from 'ramda';
|
import { isEmpty, pick, values } from 'ramda';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
|
@ -6,27 +6,39 @@ import { DropdownItem, DropdownMenu, DropdownToggle, UncontrolledDropdown } from
|
||||||
|
|
||||||
import { listServers } from './reducers/server';
|
import { listServers } from './reducers/server';
|
||||||
import { selectServer } from '../servers/reducers/selectedServer';
|
import { selectServer } from '../servers/reducers/selectedServer';
|
||||||
|
import serversExporter from '../servers/services/ServersExporter';
|
||||||
|
|
||||||
|
const defaultProps = {
|
||||||
|
serversExporter,
|
||||||
|
};
|
||||||
|
|
||||||
export class ServersDropdown extends React.Component {
|
export class ServersDropdown extends React.Component {
|
||||||
renderServers = () => {
|
renderServers = () => {
|
||||||
const { servers, selectedServer, selectServer } = this.props;
|
const { servers, selectedServer, selectServer, serversExporter } = this.props;
|
||||||
|
|
||||||
if (isEmpty(servers)) {
|
if (isEmpty(servers)) {
|
||||||
return <DropdownItem disabled><i>Add a server first...</i></DropdownItem>
|
return <DropdownItem disabled><i>Add a server first...</i></DropdownItem>
|
||||||
}
|
}
|
||||||
|
|
||||||
return Object.values(servers).map(({ name, id }) => (
|
return (
|
||||||
<span key={id}>
|
<React.Fragment>
|
||||||
<DropdownItem
|
{values(servers).map(({ name, id }) => (
|
||||||
tag={Link}
|
<DropdownItem
|
||||||
to={`/server/${id}/list-short-urls/1`}
|
key={id}
|
||||||
active={selectedServer && selectedServer.id === id}
|
tag={Link}
|
||||||
onClick={() => selectServer(id)} // FIXME This should be implicit
|
to={`/server/${id}/list-short-urls/1`}
|
||||||
>
|
active={selectedServer && selectedServer.id === id}
|
||||||
{name}
|
onClick={() => selectServer(id)} // FIXME This should be implicit
|
||||||
|
>
|
||||||
|
{name}
|
||||||
|
</DropdownItem>
|
||||||
|
))}
|
||||||
|
<DropdownItem divider />
|
||||||
|
<DropdownItem onClick={serversExporter.exportServers}>
|
||||||
|
Export servers
|
||||||
</DropdownItem>
|
</DropdownItem>
|
||||||
</span>
|
</React.Fragment>
|
||||||
));
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
@ -35,12 +47,17 @@ export class ServersDropdown extends React.Component {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<UncontrolledDropdown nav>
|
<UncontrolledDropdown nav inNavbar>
|
||||||
<DropdownToggle nav caret>Servers</DropdownToggle>
|
<DropdownToggle nav caret>Servers</DropdownToggle>
|
||||||
<DropdownMenu>{this.renderServers()}</DropdownMenu>
|
<DropdownMenu right>{this.renderServers()}</DropdownMenu>
|
||||||
</UncontrolledDropdown>
|
</UncontrolledDropdown>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(pick(['servers', 'selectedServer']), { listServers, selectServer })(ServersDropdown);
|
ServersDropdown.defaultProps = defaultProps;
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
pick(['servers', 'selectedServer']),
|
||||||
|
{ listServers, selectServer }
|
||||||
|
)(ServersDropdown);
|
||||||
|
|
54
src/servers/services/ServersExporter.js
Normal file
54
src/servers/services/ServersExporter.js
Normal file
|
@ -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);
|
|
@ -4436,6 +4436,10 @@ json5@^0.5.0, json5@^0.5.1:
|
||||||
version "0.5.1"
|
version "0.5.1"
|
||||||
resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821"
|
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:
|
jsonfile@^2.1.0:
|
||||||
version "2.4.0"
|
version "2.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-2.4.0.tgz#3736a2b428b87bbda0cc83b53fa3d633a35c2ae8"
|
resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-2.4.0.tgz#3736a2b428b87bbda0cc83b53fa3d633a35c2ae8"
|
||||||
|
|
Loading…
Reference in a new issue