Compare commits
5 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
8444c7ab44 | ||
|
6b2f5c5362 | ||
|
ace3670339 | ||
|
54f9250e53 | ||
|
2e0f6036b9 |
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,5 +1,6 @@
|
||||
.now
|
||||
node_modules
|
||||
build
|
||||
|
||||
# Environment Variables
|
||||
.env
|
||||
|
16
api/file/index.js
Normal file
16
api/file/index.js
Normal file
@ -0,0 +1,16 @@
|
||||
const URL = require('url');
|
||||
const do_wb = require('../../src/util');
|
||||
fs = require('fs');
|
||||
|
||||
module.exports = function (req, res) {
|
||||
res.setHeader('Access-Control-Allow-Origin', '*');
|
||||
const url = URL.parse(req.url, true);
|
||||
|
||||
if (!url.query.filename) return res.status(400).send("Must specify filename");
|
||||
if (url.query.filename){
|
||||
fs.readFile(url.query.filename, (err, body) => {
|
||||
if (err) return res.status(500).send(err.message || err);
|
||||
do_wb(req, body, url, res);
|
||||
});
|
||||
}
|
||||
};
|
44
api/save/index.js
Normal file
44
api/save/index.js
Normal file
@ -0,0 +1,44 @@
|
||||
const URL = require('url'), fs = require('fs');
|
||||
const do_wb = require('../../src/util');
|
||||
const formidable = require('formidable-serverless');
|
||||
const tmp = require('tmp');
|
||||
|
||||
module.exports = function (req, res) {
|
||||
res.setHeader('Access-Control-Allow-Origin', '*');
|
||||
const url = URL.parse(req.url, true);
|
||||
|
||||
/* parse form data */
|
||||
const form = formidable({ multiples: true, maxFileSize: 2 * 1024 * 1024 });
|
||||
|
||||
/*create temp file*/
|
||||
const newFile = () => {
|
||||
const tmpobj = tmp.fileSync({postfix: '.xlsx'});
|
||||
return tmpobj.name;
|
||||
}
|
||||
|
||||
const writeFile = (fileName, file) => {
|
||||
fs.writeFile(fileName, file, (err) => {
|
||||
if (err) return res.status(500).send(err.message || err);
|
||||
res.status(201).send(fileName);
|
||||
});
|
||||
}
|
||||
form.parse(req, (err, fields, files) => {
|
||||
if (err) return res.status(400).send(err.message || err);
|
||||
if (!url.query) url.query = fields;
|
||||
["N", "t"].forEach(k => { if (!url.query[k] && fields[k] != null) url.query[k] = fields[k]; });
|
||||
|
||||
/* look for first uploaded file entry */
|
||||
if (!files) return res.status(400).send("Missing file");
|
||||
const fentries = Object.entries(files);
|
||||
if (fentries.length == 0) return res.status(400).send("Missing file");
|
||||
|
||||
/* read file */
|
||||
const file = fentries[0][1];
|
||||
|
||||
fs.readFile(file.path, (err, body) => {
|
||||
if (err) return res.status(500).send(err.message || err);
|
||||
const tmpFile = newFile();
|
||||
writeFile(tmpFile, body);
|
||||
});
|
||||
});
|
||||
};
|
@ -2,16 +2,22 @@ const express = require('express');
|
||||
const app = express();
|
||||
const data = require('./data/index');
|
||||
const upload = require('./upload/index');
|
||||
const save = require('./save/index');
|
||||
const file = require('./file/index');
|
||||
const bodyParser = require('body-parser');
|
||||
const port = 3000;
|
||||
const port = 50001;
|
||||
|
||||
app.use(bodyParser.urlencoded({ extended: true }));
|
||||
app.use(bodyParser.json());
|
||||
|
||||
app.use(express.static('public'));
|
||||
// app.use(express.static('public'));
|
||||
//serve static files from build folder instead of public folder
|
||||
app.use('/', express.static('build'));
|
||||
|
||||
app.use('/api/data', data);
|
||||
app.use('/api/upload', upload);
|
||||
app.use('/api/save', save);
|
||||
app.use('/api/file', file);
|
||||
|
||||
app.listen(port, () => {
|
||||
console.log(`Listening on port: ${port}`);
|
||||
|
39116
package-lock.json
generated
39116
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
71
package.json
71
package.json
@ -1,26 +1,49 @@
|
||||
{
|
||||
"name": "sheetaki",
|
||||
"version": "0.0.1",
|
||||
"author": "sheetjs",
|
||||
"description": "Spreadsheet CSV conversion microservice",
|
||||
"keywords": [
|
||||
"csv",
|
||||
"spreadsheet",
|
||||
"data",
|
||||
"conversion",
|
||||
"excel"
|
||||
],
|
||||
"dependencies": {
|
||||
"body-parser": "^1.19.0",
|
||||
"express": "^4.17.1",
|
||||
"formidable-serverless": "",
|
||||
"request": "",
|
||||
"xlsx": ""
|
||||
},
|
||||
"scripts": {
|
||||
"build": "echo hai",
|
||||
"start": "node api/server.js"
|
||||
},
|
||||
"homepage": "https://sheetaki.now.sh",
|
||||
"license": "Apache-2.0"
|
||||
"name": "sheetaki",
|
||||
"version": "0.0.1",
|
||||
"author": "sheetjs",
|
||||
"description": "Spreadsheet CSV conversion microservice",
|
||||
"keywords": [
|
||||
"csv",
|
||||
"spreadsheet",
|
||||
"data",
|
||||
"conversion",
|
||||
"excel"
|
||||
],
|
||||
"proxy": "http://localhost:5000/",
|
||||
"dependencies": {
|
||||
"axios": "^0.19.0",
|
||||
"body-parser": "^1.19.0",
|
||||
"express": "^4.17.1",
|
||||
"formidable-serverless": "",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-scripts": "4.0.3",
|
||||
"request": "",
|
||||
"tmp": "^0.2.1",
|
||||
"xlsx": ""
|
||||
},
|
||||
"scripts": {
|
||||
"build": "react-scripts build",
|
||||
"start": "node api/server.js",
|
||||
"client": "react-scripts start",
|
||||
"server": "nodemon --watch server api/server.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"nodemon": "^2.0.4"
|
||||
},
|
||||
"homepage": "https://sheetaki.now.sh",
|
||||
"license": "Apache-2.0",
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
BIN
public/assets/logo.png
Normal file
BIN
public/assets/logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 36 KiB |
41
public/client.js
Normal file
41
public/client.js
Normal file
@ -0,0 +1,41 @@
|
||||
$(document).ready(readyNow);
|
||||
|
||||
|
||||
var $content = $('.demoContent');
|
||||
|
||||
function readyNow() {
|
||||
$('.barSection').on('click', barClick);
|
||||
$('.demoContent').hide();
|
||||
$('.proxy').show();
|
||||
$('#proxy').toggleClass('clicked');
|
||||
}
|
||||
|
||||
// function showContent(type){
|
||||
// alert(type);
|
||||
// $content.hide().filter('.' + type).show();
|
||||
// }
|
||||
|
||||
function barClick(e){
|
||||
$('.barSection').removeClass('clicked');
|
||||
$(this).toggleClass('clicked');
|
||||
$('.demoContent').hide();
|
||||
switch (e.currentTarget.id) {
|
||||
case 'proxy':
|
||||
$('.proxy').show();
|
||||
break;
|
||||
case 'upload':
|
||||
$('.upload').show();
|
||||
break;
|
||||
case 'save':
|
||||
$('.save').show();
|
||||
break;
|
||||
default:
|
||||
// code block
|
||||
}
|
||||
|
||||
// showContent(e.currentTarget.hash.slice(1));
|
||||
e.preventDefault();
|
||||
}
|
||||
// $('.barSection').click(function () {
|
||||
// $(this).toggleClass('clicked');
|
||||
// });
|
@ -1,92 +1,117 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>SheetJS Spreadsheet Conversion Service</title>
|
||||
<link rel="icon" type="image/png" href="//sheetjs.com/logo.png"/>
|
||||
</head>
|
||||
<body>
|
||||
<style> a { text-decoration: none } </style>
|
||||
<pre>
|
||||
<b><a href="http://sheetjs.com">SheetJS</a> Spreadsheet Conversion Service</b>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<!-- <script type="text/javascript" src="jquery.js"></script>
|
||||
<script type="text/javascript" src="client.js"></script> -->
|
||||
<title>SheetJS Spreadsheet Conversion Service</title>
|
||||
<link rel="icon" type="image/png" href="//sheetjs.com/logo.png" />
|
||||
<link rel="stylesheet" type="text/css" href="style.css" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Roboto&display=swap" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<style>
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
</style>
|
||||
<div id="root">
|
||||
<!-- <div class="headerBar">
|
||||
<img src="assets/logo.png" class="logo" alt="logo" />
|
||||
<h2 class="title">SheetJS Spreadsheet Conversion Service</h2>
|
||||
</div>
|
||||
<div class="container">
|
||||
<div class="bar">
|
||||
<div id="proxy" class="barSection" style="border-right: solid #B5B5B5 1px;">
|
||||
<h2>Proxy</h2>
|
||||
</div>
|
||||
|
||||
|
||||
<div id="upload" class="barSection">
|
||||
<h2>Upload</h2>
|
||||
</div>
|
||||
|
||||
<div id="save" class="barSection" style="border-left: solid #B5B5B5 1px;">
|
||||
<h2>Save</h2>
|
||||
</div>
|
||||
</div>
|
||||
<div class="demo">
|
||||
<div class="demoContent proxy">
|
||||
<h2>Proxy</h2>
|
||||
<p>
|
||||
|
||||
</p>
|
||||
<div class="testButtons">
|
||||
<p>
|
||||
Send a request to /api/data to convert the spreadsheet at
|
||||
`url` to a simpler format
|
||||
</p>
|
||||
<b>Parameters:</b>
|
||||
<p>
|
||||
- url=<url> the url to request
|
||||
</p>
|
||||
<p>
|
||||
- N=<idx> the sheet index to use (-1 returns a list of sheet names in the workbook)
|
||||
</p>
|
||||
<p>
|
||||
- t=<type> export type: "json" for json, "html" for html, default is CSV
|
||||
</p>
|
||||
<br/>
|
||||
|
||||
<b>Proxy:</b> send a request to /api/data to convert the spreadsheet at `url` to a simpler format
|
||||
<b>Examples:</b>
|
||||
</div>
|
||||
</div>
|
||||
<div class="demoContent upload">
|
||||
<h2>
|
||||
Read a file
|
||||
</h2>
|
||||
<p>
|
||||
using /api/upload
|
||||
</p>
|
||||
<form method="POST" action="api/upload" enctype="multipart/form-data">
|
||||
<input type="hidden" name="N" value="0" />
|
||||
<label class="button1" for="file">
|
||||
<input
|
||||
name="file"
|
||||
type="file"
|
||||
onChange={handleFileSelect}
|
||||
/>
|
||||
Choose file
|
||||
</label>
|
||||
<label for="t">Export type</label>
|
||||
<select name="t" id="t">
|
||||
<option value="csv" selected>CSV</option>
|
||||
<option value="json">Array of arrays</option>
|
||||
<option value="html">HTML TABLE</option>
|
||||
</select>
|
||||
|
||||
<b>parameters:</b>
|
||||
- url=<url> the url to request
|
||||
- N=<idx> the sheet index to use (-1 returns a list of sheet names in the workbook)
|
||||
- t=<type> export type: "json" for json, "html" for html, default is CSV
|
||||
<input class="button1" type="submit" value="Click here to upload!" />
|
||||
</form>
|
||||
</div>
|
||||
<div class="demoContent save">
|
||||
Save
|
||||
</div>
|
||||
</div>
|
||||
</div> -->
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
var _gaq = _gaq || [];
|
||||
_gaq.push(["_setAccount", "UA-36810333-1"]);
|
||||
_gaq.push(["_setDomainName", "sheetjs.com"]);
|
||||
_gaq.push(["_setAllowLinker", true]);
|
||||
_gaq.push(["_trackPageview"]);
|
||||
|
||||
<b>examples:</b>
|
||||
</pre>
|
||||
<pre id="examples"></pre>
|
||||
<script>
|
||||
var URL1 = "https://obamawhitehouse.archives.gov/sites/default/files/omb/budget/fy2014/assets/receipts.xls";
|
||||
var URL2 = "https://www.ers.usda.gov/webdocs/DataFiles/50048/Feed%20Grains%20Yearbook%20Tables-All%20Years.xls?v=42839"
|
||||
var URLS = [
|
||||
["[CSV] ", {url:URL1}],
|
||||
["[JSON]", {url:URL1, t:"json"}],
|
||||
["[HTML]", {url:URL1, t:"html"}],
|
||||
[],
|
||||
["[LIST]", {url:URL2,N:-1}],
|
||||
["[N=1] ", {url:URL2,N:1}],
|
||||
// ["[N=2] ", {url:URL2,N:2}],
|
||||
// ["[N=3] ", {url:URL2,N:3}],
|
||||
["[N=4] ", {url:URL2,N:4}],
|
||||
// ["[N=5] ", {url:URL2,N:5}],
|
||||
// ["[N=6] ", {url:URL2,N:6}],
|
||||
// ["[N=7] ", {url:URL2,N:7}],
|
||||
["[N=8] ", {url:URL2,N:8}]
|
||||
];
|
||||
var parms = ["N", "t"];
|
||||
for(var i = 0; i < URLS.length; ++i) {
|
||||
if(URLS[i].length < 2) { document.getElementById("examples").innerHTML += (URLS[i][0]||"") + "\n"; continue; }
|
||||
var path = '/api/data?url=' + URLS[i][1].url;
|
||||
for(var j = 0; j < parms.length; ++j) if(URLS[i][1][parms[j]]) path += '&' + parms[j] + '=' + URLS[i][1][parms[j]];
|
||||
document.getElementById("examples").innerHTML += '- ' + URLS[i][0] + ' <a href="' + path + '">' + path + '</a>\n';
|
||||
}
|
||||
</script>
|
||||
<pre>
|
||||
|
||||
<b>Upload:</b> upload a file to /api/upload to convert the spreadsheet at `url` to a simpler format
|
||||
|
||||
<b>parameters:</b>
|
||||
- N=<idx> the sheet index to use (-1 returns a list of sheet names in the workbook)
|
||||
- t=<type> export type: "json" for json, "html" for html, default is CSV
|
||||
|
||||
<b>example:</b>
|
||||
<form method="POST" action="api/upload" enctype="multipart/form-data"><input type="hidden" name="N" value="0">
|
||||
<label for="file">Upload a file</label>
|
||||
<input type="file" name="file" id="file">
|
||||
|
||||
<label for="t">Export type</label>
|
||||
<select name="t" id="t">
|
||||
<option value="csv" selected>CSV</option>
|
||||
<option value="json">Array of arrays</option>
|
||||
<option value="html">HTML TABLE</option>
|
||||
</select>
|
||||
|
||||
<input type="submit" value="Click here to upload!">
|
||||
</form>
|
||||
|
||||
<b>This service is powered by <a href="http://sheetjs.com">SheetJS</a></b>
|
||||
|
||||
<b>Source Code:</b>
|
||||
|
||||
- <a href="https://github.com/SheetJS/sheetaki">Source code for this service</a>
|
||||
|
||||
- <a href="https://github.com/SheetJS/sheetjs">Source code for the SheetJS spreadsheet library</a>
|
||||
</pre>
|
||||
<script type="text/javascript">
|
||||
var _gaq = _gaq || [];
|
||||
_gaq.push(['_setAccount', 'UA-36810333-1']);
|
||||
_gaq.push(['_setDomainName', 'sheetjs.com']);
|
||||
_gaq.push(['_setAllowLinker', true]);
|
||||
_gaq.push(['_trackPageview']);
|
||||
|
||||
(function() {
|
||||
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
|
||||
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
|
||||
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
(function () {
|
||||
var ga = document.createElement("script");
|
||||
ga.type = "text/javascript";
|
||||
ga.async = true;
|
||||
ga.src =
|
||||
("https:" == document.location.protocol
|
||||
? "https://ssl"
|
||||
: "http://www") + ".google-analytics.com/ga.js";
|
||||
var s = document.getElementsByTagName("script")[0];
|
||||
s.parentNode.insertBefore(ga, s);
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
2
public/jquery.js
vendored
Normal file
2
public/jquery.js
vendored
Normal file
File diff suppressed because one or more lines are too long
112
public/style.css
Normal file
112
public/style.css
Normal file
@ -0,0 +1,112 @@
|
||||
body {
|
||||
background-color: #E5E5E5;
|
||||
font-family: Roboto, Helvetica, Arial, FreeSans, sans-serif;
|
||||
color: #454545;
|
||||
font-size: 14px;
|
||||
/* margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
||||
sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale; */
|
||||
|
||||
}
|
||||
|
||||
.App {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.headerBar{
|
||||
width: 800px;
|
||||
height: 100px;
|
||||
margin: auto;
|
||||
margin-top: 30px;
|
||||
background-color: #FFFFFF;
|
||||
border: solid #B5B5B5 1px;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.title{
|
||||
padding-top: 15px;
|
||||
position: absolute;
|
||||
left: 200px;
|
||||
}
|
||||
|
||||
.logo{
|
||||
float:left;
|
||||
width: 60px;
|
||||
padding-left: 20px;
|
||||
padding-top: 20px;
|
||||
padding-right: 0px;
|
||||
/* position: absolute; */
|
||||
}
|
||||
.container{
|
||||
text-align: center;
|
||||
margin: auto;
|
||||
width: 85%;
|
||||
background-color: #FFFFFF;
|
||||
margin-top: 30px;
|
||||
/* display: flex; */
|
||||
border: solid #B5B5B5 1px;
|
||||
}
|
||||
|
||||
.bar{
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.exampleForms{
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.formContainer{
|
||||
width: 50%;
|
||||
display: block;
|
||||
padding-top: 20px;
|
||||
/* background-color: #454545; */
|
||||
/* border: solid #000; */
|
||||
border-width: 1px;
|
||||
}
|
||||
|
||||
.barSection{
|
||||
margin: auto;
|
||||
padding: 10px;
|
||||
width: 33%;
|
||||
cursor:pointer;
|
||||
border-bottom: solid #B5B5B5 1px;
|
||||
}
|
||||
|
||||
input[type="file"]{
|
||||
display: table-cell;
|
||||
position: absolute;
|
||||
/* left: 0; */
|
||||
opacity: 0;
|
||||
/* top: 0; */
|
||||
/* bottom: 0; */
|
||||
width: 40px;
|
||||
height: 30px;
|
||||
}
|
||||
.button1{
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
padding:0.35em 1.2em;
|
||||
border:0.1em solid #E18874;
|
||||
margin:0 0.3em 0.3em 0;
|
||||
border-radius:0.12em;
|
||||
box-sizing: border-box;
|
||||
text-decoration:none;
|
||||
font-family:'Roboto',sans-serif;
|
||||
font-weight:300;
|
||||
color:#454545;
|
||||
background-color: Transparent;
|
||||
text-align:center;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.button1:hover{
|
||||
color:#FFFFFF;
|
||||
background-color:#E18874;
|
||||
}
|
||||
|
||||
.clicked {
|
||||
background: #77E2B6;
|
||||
}
|
264
src/App.css
Normal file
264
src/App.css
Normal file
@ -0,0 +1,264 @@
|
||||
.App {
|
||||
text-align: center;
|
||||
font-family: 'Roboto', sans-serif;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Roboto';
|
||||
src: local('Roboto'), url(./fonts/Roboto/Roboto-Regular.ttf) format('truetype');
|
||||
}
|
||||
|
||||
html { font-family: Roboto, sans-serif; }
|
||||
|
||||
body {
|
||||
background-color: #E5E5E5;
|
||||
color: #282c34;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
hr{
|
||||
border: 0;
|
||||
height: 0;
|
||||
border-top: 1px solid rgba(0, 0, 0, 0.5);
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
||||
monospace;
|
||||
}
|
||||
|
||||
::selection {
|
||||
background: #E18874;
|
||||
}
|
||||
.headerBar{
|
||||
border-radius: 2px;
|
||||
width: 800px;
|
||||
height: 100px;
|
||||
margin: auto;
|
||||
margin-top: 30px;
|
||||
background-color: #FFFFFF;
|
||||
border: solid #B5B5B5 1px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.title{
|
||||
padding-top: 15px;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.logo{
|
||||
float:left;
|
||||
width: 60px;
|
||||
padding-left: 20px;
|
||||
padding-top: 20px;
|
||||
padding-right: 0px;
|
||||
}
|
||||
.container{
|
||||
border-radius: 2px;
|
||||
text-align: center;
|
||||
margin: auto;
|
||||
width: 85%;
|
||||
background-color: #FFFFFF;
|
||||
margin-top: 30px;
|
||||
margin-bottom: 10px;
|
||||
border: solid #B5B5B5 1px;
|
||||
}
|
||||
|
||||
.bar{
|
||||
border-top-left-radius: 2px;
|
||||
border-top-right-radius: 2px;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.exampleForms{
|
||||
display: flex;
|
||||
}
|
||||
.descriptionContainer{
|
||||
width: 40%;
|
||||
margin: auto;
|
||||
}
|
||||
.formContainer{
|
||||
width: 50%;
|
||||
display: block;
|
||||
padding-top: 20px;
|
||||
border-width: 1px;
|
||||
}
|
||||
|
||||
.barSection{
|
||||
margin: auto;
|
||||
padding: 10px;
|
||||
width: 33.333333333333%;
|
||||
cursor:pointer;
|
||||
border-bottom: solid #B5B5B5 1px;
|
||||
}
|
||||
|
||||
.fileInputWrapper{
|
||||
width: 80%;
|
||||
margin: auto;
|
||||
}
|
||||
input[type="file"]{
|
||||
margin: auto;
|
||||
padding: 0;
|
||||
width: 500px;
|
||||
height: 200px;
|
||||
outline: none;
|
||||
opacity: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
input[type=text] {
|
||||
width: 80%;
|
||||
text-align: center;
|
||||
padding: 12px 0px;
|
||||
margin: 8px 0;
|
||||
font-size: 14px;
|
||||
-webkit-border-radius: 20px;
|
||||
-moz-border-radius: 20px;
|
||||
border-radius: 20px;
|
||||
border: 2px solid #707070;
|
||||
outline: none;
|
||||
-webkit-transition: all 0.1s ease-in-out;
|
||||
-moz-transition: all 0.1s ease-in-out;
|
||||
-ms-transition: all 0.1s ease-in-out;
|
||||
-o-transition: all 0.1s ease-in-out;
|
||||
}
|
||||
input[type=text]:hover {
|
||||
border: 2px solid #E18874;
|
||||
}
|
||||
input[type=text]:focus, textarea:focus {
|
||||
border: 2px solid #E18874;
|
||||
}
|
||||
|
||||
.button1{
|
||||
width: 40%;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
padding:0.35em 1.2em;
|
||||
margin: auto;
|
||||
margin-top: 20px;
|
||||
margin-bottom: 20px;
|
||||
border-radius:2px;
|
||||
border: 2px solid #707070;
|
||||
box-sizing: border-box;
|
||||
text-decoration:none;
|
||||
font-family:'Roboto',sans-serif;
|
||||
font-weight:300;
|
||||
color:#454545;
|
||||
background-color: #e2b5aa;
|
||||
text-align:center;
|
||||
padding: 10px;
|
||||
font-size: 15px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 5px;
|
||||
transition: 150ms ease-in;
|
||||
}
|
||||
.button1:hover{
|
||||
color:#FFFFFF;
|
||||
background-color:#E18874;
|
||||
border: 2px solid #707070;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.button2{
|
||||
overflow: hidden;
|
||||
width: 5%;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
margin: auto;
|
||||
margin-right: 5px;
|
||||
border-radius:2px;
|
||||
border: 2px solid #707070;
|
||||
box-sizing: border-box;
|
||||
text-decoration:none;
|
||||
font-family:'Roboto',sans-serif;
|
||||
font-weight:300;
|
||||
color:#454545;
|
||||
background-color: #e2b5aa;
|
||||
text-align:center;
|
||||
transition: 150ms ease-in;
|
||||
}
|
||||
.button2:hover{
|
||||
color:#FFFFFF;
|
||||
background-color:#E18874;
|
||||
border: 2px solid #707070;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.scrollableDiv{
|
||||
width: 100%;
|
||||
max-height: 800px;
|
||||
overflow-y: scroll;
|
||||
overflow-x: scroll;
|
||||
border-top: solid #B5B5B5 1px;
|
||||
}
|
||||
|
||||
.demoForm{
|
||||
width: 80%;
|
||||
margin: auto;
|
||||
border: solid #B5B5B5 1px;
|
||||
margin-bottom: 20px;
|
||||
border-radius: 2px;
|
||||
box-shadow: 0 0 2px #B5B5B5;
|
||||
background: #A2C5F0;
|
||||
}
|
||||
|
||||
select{
|
||||
width: 50%;
|
||||
height:40px;
|
||||
border:0px;
|
||||
outline:none;
|
||||
text-align-last:center;
|
||||
}
|
||||
|
||||
.selectWrapper{
|
||||
margin: 8px 0;
|
||||
background: #FFFFFF;
|
||||
text-align: center;
|
||||
width: 20%;
|
||||
border-radius:36px;
|
||||
display:inline-block;
|
||||
overflow:hidden;
|
||||
border:2px solid #707070;
|
||||
}
|
||||
.selectWrapper:hover{
|
||||
border: 2px solid #E18874;
|
||||
}
|
||||
|
||||
label{
|
||||
display: block;
|
||||
}
|
||||
|
||||
ol li{
|
||||
list-style-type: none;
|
||||
border-bottom: 1px solid #B5B5B5;
|
||||
|
||||
}
|
||||
|
||||
ul li{
|
||||
color: #6d6d6d;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.centering{
|
||||
width: 100%;
|
||||
margin: auto;
|
||||
}
|
||||
.fileInputContainer{
|
||||
margin: auto;
|
||||
width: 500px;
|
||||
height: 200px;
|
||||
border: 4px dashed #fff;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.fileInputContainer:hover{
|
||||
border: 4px dashed #E18874;
|
||||
}
|
||||
|
||||
.fileInputText{
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
text-align: center;
|
||||
line-height: 170px;
|
||||
color: #ffffff;
|
||||
font-family: Arial;
|
||||
}
|
78
src/App.js
Normal file
78
src/App.js
Normal file
@ -0,0 +1,78 @@
|
||||
import './App.css';
|
||||
import React, { useState } from 'react';
|
||||
import logo from './assets/logo.png'
|
||||
import Proxy from './Features/Data/Data';
|
||||
import Upload from './Features/Upload/Upload';
|
||||
import Save from './Features/Save/Save'
|
||||
import ReadWorkbook from './Features/ReadWorkbook/ReadWorkbook';
|
||||
|
||||
function App() {
|
||||
const [active, setActive] = useState('proxy');
|
||||
|
||||
return (
|
||||
<div className="App">
|
||||
<div className="headerBar">
|
||||
<a href="https://sheetjs.com/"><img src={logo} className="logo" alt="logo" /></a>
|
||||
<h2 className="title" style={{paddingTop:"15px"}}>SheetJS Spreadsheet Conversion Service</h2>
|
||||
</div>
|
||||
<div className="container">
|
||||
<div className="bar">
|
||||
{active == 'proxy'?
|
||||
<div className="barSection" style={{ borderRight: "solid #B5B5B5 1px", background:'#77E2B6' }} onClick={(() => setActive('proxy'))}>
|
||||
<h2>Read</h2>
|
||||
</div>
|
||||
:
|
||||
<div className="barSection" style={{ borderRight: "solid #B5B5B5 1px" }} onClick={(() => setActive('proxy'))}>
|
||||
<h2>Read</h2>
|
||||
</div>
|
||||
}
|
||||
{active == 'upload'?
|
||||
<div className="barSection" style={{ background: '#77E2B6' }} onClick={(() => setActive('upload'))}>
|
||||
<h2>Upload</h2>
|
||||
</div>
|
||||
:
|
||||
<div className="barSection" onClick={(() => setActive('upload'))}>
|
||||
<h2>Upload</h2>
|
||||
</div>
|
||||
}
|
||||
{active == 'save'?
|
||||
<div className="barSection" onClick={(() => setActive('save'))} style={{ borderLeft: "solid #B5B5B5 1px", background: '#77E2B6' }}>
|
||||
<h2>Save</h2>
|
||||
</div>
|
||||
:
|
||||
<div className="barSection" onClick={(() => setActive('save'))} style={{ borderLeft: "solid #B5B5B5 1px" }}>
|
||||
<h2>Save</h2>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div className="demo">
|
||||
{active == 'proxy' &&
|
||||
<Proxy />
|
||||
}
|
||||
{active == 'workbook' &&
|
||||
<ReadWorkbook/>
|
||||
}
|
||||
{active == 'upload'&&
|
||||
<Upload/>
|
||||
}
|
||||
{active == 'save' &&
|
||||
<Save/>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<footer style={{justifyContent:'left', width:'80%', margin:'auto'}}>
|
||||
<b style={{ textAlign: 'left', width: '5018%', margin: 'auto' }}>Source Code:</b>
|
||||
<ol style={{ textAlign: 'left', width: '18%', margin:'auto' }}>
|
||||
<li style={{padding:'0px'}}>
|
||||
<a href="https://github.com/SheetJS/sheetaki">Source code for this service</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://github.com/SheetJS/sheetjs">Source code for the SheetJS spreadsheet library</a>
|
||||
</li>
|
||||
</ol>
|
||||
</footer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
8
src/App.test.js
Normal file
8
src/App.test.js
Normal file
@ -0,0 +1,8 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import App from './App';
|
||||
|
||||
test('renders learn react link', () => {
|
||||
render(<App />);
|
||||
const linkElement = screen.getByText(/learn react/i);
|
||||
expect(linkElement).toBeInTheDocument();
|
||||
});
|
56
src/Features/Data/Data.js
Normal file
56
src/Features/Data/Data.js
Normal file
@ -0,0 +1,56 @@
|
||||
import React, { useState } from 'react';
|
||||
import ResponseDisplay from '../ResponseDisplay/ResponseDisplay';
|
||||
import DataForm from './DataForm/DataForm';
|
||||
|
||||
const Proxy = () => {
|
||||
const [type, setType] = useState('csv');
|
||||
const [apiResponse, setApiResponse] = useState();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div>
|
||||
<div className="descriptionContainer">
|
||||
<h2>/api/data</h2>
|
||||
<p style={{ color:'#6d6d6d'}}>
|
||||
Send a request to /api/data to convert the spreadsheet at
|
||||
`url` to a simpler format
|
||||
</p>
|
||||
<b>Parameters:</b>
|
||||
<ul style={{textAlign:'left'}}>
|
||||
<li>
|
||||
url=<url> the url to request
|
||||
</li>
|
||||
<li>
|
||||
N=<idx> the sheet index to use (-1 returns a list of sheet names in the workbook)
|
||||
</li>
|
||||
<li>
|
||||
t=<type> export type: default is csv, "json" for json, "html" for html, "file" is to download the file
|
||||
</li>
|
||||
<li>
|
||||
addr=<cell address> the address of the cell that you want to change the value of
|
||||
</li>
|
||||
<li>
|
||||
val=<value> the value you want in that cell
|
||||
</li>
|
||||
<li>
|
||||
name=<name for file> the name you want to give to a file for downloading
|
||||
</li>
|
||||
</ul>
|
||||
<br/>
|
||||
</div>
|
||||
<DataForm
|
||||
type={type}
|
||||
setType={setType}
|
||||
setApiResponse={setApiResponse}
|
||||
/>
|
||||
</div>
|
||||
<ResponseDisplay
|
||||
apiResponse={apiResponse}
|
||||
type={type}
|
||||
endpoint='/api/data'
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default (Proxy);
|
147
src/Features/Data/DataForm/DataForm.js
Normal file
147
src/Features/Data/DataForm/DataForm.js
Normal file
@ -0,0 +1,147 @@
|
||||
import React, { useState } from 'react';
|
||||
import axios from 'axios';
|
||||
import Loader from '../../Loader/Loader';
|
||||
const downloadFile = require('../../../downloadFile');
|
||||
|
||||
const DataForm = ({type, setType, setApiResponse}) => {
|
||||
const [addr, setAddr] = useState('');
|
||||
const [val, setVal] = useState('');
|
||||
const [N, setN] = useState('');
|
||||
const [url, setUrl] = useState('');
|
||||
const [downloadName, setDownloadName] = useState('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
|
||||
//send request to /api/data
|
||||
const handleSubmit = () => {
|
||||
setLoading(true);
|
||||
event.preventDefault();
|
||||
axios({
|
||||
method: 'GET',
|
||||
url: `/api/data?url=${url}&N=${N}&t=${type}&name=${downloadName}&addr=${addr}&val=${val}`,
|
||||
responseType: 'text',
|
||||
headers: { 'Content-Type': 'application/octet-stream' }
|
||||
}).then((response) => {
|
||||
if(type=='file'){
|
||||
var workbook = response.data.workbook;
|
||||
var filename = response.data.newName;
|
||||
var byteCharacters = atob(workbook);
|
||||
var byteNumbers = new Array(byteCharacters.length);
|
||||
for (var i = 0; i < byteCharacters.length; i++) {
|
||||
byteNumbers[i] = byteCharacters.charCodeAt(i);
|
||||
}
|
||||
var byteArray = new Uint8Array(byteNumbers);
|
||||
var blob = new Blob([byteArray], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
|
||||
var url = URL.createObjectURL(blob);
|
||||
downloadFile(url, filename);
|
||||
}else{
|
||||
setApiResponse(response.data);
|
||||
}
|
||||
setLoading(false);
|
||||
}).catch((error) => {
|
||||
if(error.response.data){
|
||||
setApiResponse(error.response.data);
|
||||
} else {
|
||||
alert(error);
|
||||
}
|
||||
setLoading(false);
|
||||
})
|
||||
}
|
||||
return (
|
||||
<div className="demoForm">
|
||||
<h2>Demo</h2>
|
||||
<p>
|
||||
Sample single worksheet url:
|
||||
<div>
|
||||
<button className="button2" onClick={(() => setUrl('https://obamawhitehouse.archives.gov/sites/default/files/omb/budget/fy2014/assets/receipts.xls'))}>select</button>
|
||||
<a href="https://obamawhitehouse.archives.gov/sites/default/files/omb/budget/fy2014/assets/receipts.xls">https://obamawhitehouse.archives.gov/sites/default/files/omb/budget/fy2014/assets/receipts.xls</a>
|
||||
</div>
|
||||
</p>
|
||||
<p>
|
||||
Sample workbook url:
|
||||
<div>
|
||||
<button className="button2" onClick={(() => setUrl('https://www.ers.usda.gov/webdocs/DataFiles/50048/Feed%20Grains%20Yearbook%20Tables-All%20Years.xls?v=42839'))}>select</button>
|
||||
<a href="https://www.ers.usda.gov/webdocs/DataFiles/50048/Feed%20Grains%20Yearbook%20Tables-All%20Years.xls?v=42839">https://www.ers.usda.gov/webdocs/DataFiles/50048/Feed%20Grains%20Yearbook%20Tables-All%20Years.xls</a>
|
||||
</div>
|
||||
</p>
|
||||
<form onSubmit={(e) => handleSubmit(e)}>
|
||||
<label for="t">Type:</label>
|
||||
<div className="selectWrapper">
|
||||
<select name="t" id="t" onChange={(e) => setType(e.target.value)} value={type}>
|
||||
<option value="csv" selected>CSV</option>
|
||||
<option value="json">Array of arrays</option>
|
||||
<option value="html">HTML TABLE</option>
|
||||
<option value="file">Download file</option>
|
||||
</select>
|
||||
</div>
|
||||
{type == 'file' &&
|
||||
<div style={{ width: "25%", margin:'auto' }}>
|
||||
<label for="name">Name to give file:</label>
|
||||
<input name="name" type="text" onChange={((e) => setDownloadName(e.target.value))} placeholder="name" value={downloadName} />
|
||||
</div>
|
||||
}
|
||||
<div style={{ display: "flex", margin:'auto', width: '100%'}}>
|
||||
<div style={{width: "25%"}}>
|
||||
<label for="url">url:</label>
|
||||
<input name="url" type="text" onChange={((e) => setUrl(e.target.value))} placeholder="url" value={url}/>
|
||||
</div>
|
||||
<div style={{ width: "25%" }}>
|
||||
<label for="N">Worksheet:</label>
|
||||
<input name="N" type="text" onChange={((e) => setN(e.target.value))} placeholder="N" />
|
||||
</div>
|
||||
<div style={{ width: "25%" }}>
|
||||
<label for="addr">Cell address:</label>
|
||||
<input name="addr" type="text" onChange={((e) => setAddr(e.target.value))} placeholder="addr"/>
|
||||
</div>
|
||||
<div style={{ width: "25%" }}>
|
||||
<label for="val">New value:</label>
|
||||
<input name="val" type="text" onChange={((e) => setVal(encodeURIComponent(e.target.value)))} placeholder="val"/>
|
||||
</div>
|
||||
</div>
|
||||
<p>request url:</p>
|
||||
<div className="urlWrapper">
|
||||
<p style={{overflowWrap: 'break-word', wordWrap:'break-word', width:'90%', margin:'auto'}}>
|
||||
/api/data?url=
|
||||
{url &&
|
||||
<>
|
||||
{url}
|
||||
</>
|
||||
}
|
||||
{N &&
|
||||
<>
|
||||
&N={N}
|
||||
</>
|
||||
}
|
||||
{type != 'csv' &&
|
||||
<>
|
||||
&t={type}
|
||||
</>
|
||||
}
|
||||
{downloadName &&
|
||||
<>
|
||||
&name={encodeURIComponent(downloadName)}
|
||||
</>
|
||||
}
|
||||
{addr &&
|
||||
<>
|
||||
&addr={addr}
|
||||
</>
|
||||
}
|
||||
{val &&
|
||||
<>
|
||||
&val={val}
|
||||
</>
|
||||
}
|
||||
</p>
|
||||
</div>
|
||||
{loading?
|
||||
<Loader/>
|
||||
:
|
||||
<button className="button1">Send Request</button>
|
||||
}
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default (DataForm);
|
17
src/Features/Loader/Loader.css
Normal file
17
src/Features/Loader/Loader.css
Normal file
@ -0,0 +1,17 @@
|
||||
/* --------loader modal css--------- */
|
||||
.loader {
|
||||
/* background-color: #b90e0a; */
|
||||
border: 16px solid #efefef; /* Light grey */
|
||||
border-top: 16px solid #E18874; /* Blue */
|
||||
border-radius: 50%;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
margin: auto;
|
||||
margin-bottom: 10px;
|
||||
animation: spin .7s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
11
src/Features/Loader/Loader.js
Normal file
11
src/Features/Loader/Loader.js
Normal file
@ -0,0 +1,11 @@
|
||||
import React from 'react';
|
||||
import './Loader.css';
|
||||
|
||||
const Loader = () => {
|
||||
// const loader = useSelector((state) => state.loader)
|
||||
return (
|
||||
<div className="loader"></div>
|
||||
);
|
||||
}
|
||||
|
||||
export default (Loader);
|
37
src/Features/ReadWorkbook/ReadWorkbook.js
Normal file
37
src/Features/ReadWorkbook/ReadWorkbook.js
Normal file
@ -0,0 +1,37 @@
|
||||
import React, { useState } from 'react';
|
||||
import ResponseDisplay from '../ResponseDisplay/ResponseDisplay';
|
||||
import ReadWorkbookForm from './ReadWorkbookForm/ReadWorkbookForm';
|
||||
|
||||
const ReadWorkbook = ({ activeItem }) => {
|
||||
const [apiResponse, setApiResponse] = useState();
|
||||
|
||||
return (
|
||||
<div>
|
||||
|
||||
|
||||
<div>
|
||||
<div style={{ width: '40%', margin: 'auto' }}>
|
||||
<h2>Read Workbook</h2>
|
||||
<p>
|
||||
Send a request to /api/data to convert the spreadsheet at
|
||||
`url` to a simpler format
|
||||
</p>
|
||||
<b>Parameters:</b>
|
||||
<ol style={{ textAlign: 'left' }}>
|
||||
<li>
|
||||
N=<idx> the sheet index to use (-1 returns a list of sheet names in the workbook)
|
||||
</li>
|
||||
</ol>
|
||||
<br />
|
||||
</div>
|
||||
{/* <hr style={{width:'40%'}}/> */}
|
||||
<ReadWorkbookForm
|
||||
setApiResponse={setApiResponse}
|
||||
/>
|
||||
</div>
|
||||
<ResponseDisplay apiResponse={apiResponse}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default (ReadWorkbook);
|
@ -0,0 +1,67 @@
|
||||
import React, { useState } from 'react';
|
||||
import axios from 'axios';
|
||||
import Loader from '../../Loader/Loader';
|
||||
|
||||
const ReadWorkbookForm = ({ setApiResponse }) => {
|
||||
const [N, setN] = useState('');
|
||||
const [url, setUrl] = useState();
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const handleSubmit = () => {
|
||||
setLoading(true);
|
||||
event.preventDefault();
|
||||
axios({
|
||||
method: 'GET',
|
||||
url: `/api/data?url=https://www.ers.usda.gov/webdocs/DataFiles/50048/Feed%20Grains%20Yearbook%20Tables-All%20Years.xls?v=42839&N=${N}`
|
||||
}).then((response) => {
|
||||
setApiResponse(response.data);
|
||||
setLoading(false);
|
||||
}).catch((error) => {
|
||||
setApiResponse(error.response.data);
|
||||
// alert(error);
|
||||
setLoading(false);
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="demoForm">
|
||||
<h2>Demo</h2>
|
||||
<p>
|
||||
Sample url to use:
|
||||
<div>
|
||||
<a href="https://www.ers.usda.gov/webdocs/DataFiles/50048/Feed%20Grains%20Yearbook%20Tables-All%20Years.xls?v=42839">https://obamawhitehouse.archives.gov/sites/default/files/omb/budget/fy2014/assets/receipts.xls</a>
|
||||
</div>
|
||||
</p>
|
||||
<form onSubmit={(e) => handleSubmit(e)}>
|
||||
|
||||
<div style={{ display: "flex", margin: 'auto', width: '100%' }}>
|
||||
<div style={{ width: "33.3333%" }}>
|
||||
<label for="N">N:</label>
|
||||
<input name="N" type="text" onChange={((e) => setN(e.target.value))} placeholder="N" />
|
||||
</div>
|
||||
</div>
|
||||
<p>request url:</p>
|
||||
<p>
|
||||
/api/data?url=
|
||||
{url &&
|
||||
<>
|
||||
{url}
|
||||
</>
|
||||
}
|
||||
{N &&
|
||||
<>
|
||||
&N={N}
|
||||
</>
|
||||
}
|
||||
</p>
|
||||
{loading ?
|
||||
<Loader />
|
||||
:
|
||||
<button style={{ marginBottom: '20px' }} className="button1">Send Request</button>
|
||||
}
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default (ReadWorkbookForm);
|
36
src/Features/ResponseDisplay/ResponseDisplay.js
Normal file
36
src/Features/ResponseDisplay/ResponseDisplay.js
Normal file
@ -0,0 +1,36 @@
|
||||
import React, {useEffect} from 'react';
|
||||
|
||||
//renders a response from an api request
|
||||
const ResponseDisplay = ({ apiResponse, type, endpoint }) => {
|
||||
useEffect(() => {
|
||||
//render the table html
|
||||
if(type=='html'){
|
||||
var tag_id = document.getElementById('apiResponse');
|
||||
tag_id.innerHTML = apiResponse;
|
||||
}
|
||||
}, [apiResponse])
|
||||
return (
|
||||
<>
|
||||
{apiResponse &&
|
||||
<div style={{ borderTop: 'solid #77E2B6 5px' }}>
|
||||
<h2 >
|
||||
Response from {endpoint}:
|
||||
</h2>
|
||||
<div className="scrollableDiv" id="apiResponse">
|
||||
{type == 'json' &&
|
||||
JSON.stringify(apiResponse)
|
||||
}
|
||||
{type == 'csv' &&
|
||||
apiResponse
|
||||
}
|
||||
{type == 'file' &&
|
||||
apiResponse
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default (ResponseDisplay);
|
49
src/Features/Save/Save.js
Normal file
49
src/Features/Save/Save.js
Normal file
@ -0,0 +1,49 @@
|
||||
import React, { useState } from 'react';
|
||||
import SaveForm from './SaveForm/SaveForm';
|
||||
import ResponseDisplay from '../ResponseDisplay/ResponseDisplay';
|
||||
|
||||
const Save = () => {
|
||||
const [type, setType] = useState('csv');
|
||||
const [apiResponse, setApiResponse] = useState();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="descriptionContainer">
|
||||
<h2>
|
||||
/api/save
|
||||
</h2>
|
||||
<div style={{ color: '#6d6d6d' }}>
|
||||
<p>
|
||||
Use /api/save to save a file to an AWS S3 Bucket
|
||||
</p>
|
||||
</div>
|
||||
<h2>
|
||||
/api/file
|
||||
</h2>
|
||||
<div style={{ color: '#6d6d6d' }}>
|
||||
<p>
|
||||
Then use /api/file?filename=
|
||||
to read the saved file. Choose t=file to download the file.
|
||||
</p>
|
||||
</div>
|
||||
<b>Parameters:</b>
|
||||
<ul style={{ textAlign: 'left' }}>
|
||||
<li>
|
||||
t=<type> export type: default is csv, "json" for json, "html" for html, "file" is to download the file
|
||||
</li>
|
||||
<li>
|
||||
name=<name for file> the name you want to give to a file for downloading
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<SaveForm type={type} setType={setType} setApiResponse={setApiResponse}/>
|
||||
<ResponseDisplay
|
||||
apiResponse={apiResponse}
|
||||
type={type}
|
||||
endpoint='/api/file'
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default (Save);
|
164
src/Features/Save/SaveForm/SaveForm.js
Normal file
164
src/Features/Save/SaveForm/SaveForm.js
Normal file
@ -0,0 +1,164 @@
|
||||
import React, { useState } from 'react';
|
||||
import axios from 'axios';
|
||||
import Loader from '../../Loader/Loader';
|
||||
const downloadFile = require('../../../downloadFile');
|
||||
|
||||
const SaveForm = ({ type, setType, setApiResponse }) => {
|
||||
const [selectedFile, setSelectedFile] = useState();
|
||||
const [filename, setFilename] = useState();
|
||||
const [loadingSave, setLoadingSave] = useState(false);
|
||||
const [loadingGetFile, setLoadingGetFile] = useState(false);
|
||||
const [filenameInput, setFilenameInput] = useState();
|
||||
const [downloadName, setDownloadName] = useState('');
|
||||
|
||||
|
||||
//post file to /api/save
|
||||
const handleSubmitFile = (event) => {
|
||||
setLoadingSave(true);
|
||||
event.preventDefault();
|
||||
let formData = new FormData();
|
||||
formData.append("file", selectedFile);
|
||||
let url = '/api/save';
|
||||
axios.post(url, formData, {
|
||||
headers: {
|
||||
"Content-Type": "multipart/form-data"
|
||||
}
|
||||
}).then((response) => {
|
||||
setFilename(response.data);
|
||||
setLoadingSave(false);
|
||||
}).catch((error) => {
|
||||
if (error.response.data) {
|
||||
setApiResponse(error.response.data);
|
||||
} else {
|
||||
alert(error);
|
||||
}
|
||||
setLoadingSave(false);
|
||||
});
|
||||
}
|
||||
|
||||
//read file using /api/file
|
||||
const handleGetFile = (event) =>{
|
||||
setLoadingGetFile(true);
|
||||
event.preventDefault();
|
||||
let formData = new FormData();
|
||||
formData.append("file", selectedFile);
|
||||
axios({
|
||||
method: 'GET',
|
||||
url: `/api/file?filename=${filenameInput}&t=${type}&name=${downloadName}`
|
||||
}).then((response) => {
|
||||
if (type == 'file') {
|
||||
var workbook = response.data.workbook;
|
||||
var filename = response.data.newName;
|
||||
var byteCharacters = atob(workbook);
|
||||
var byteNumbers = new Array(byteCharacters.length);
|
||||
for (var i = 0; i < byteCharacters.length; i++) {
|
||||
byteNumbers[i] = byteCharacters.charCodeAt(i);
|
||||
}
|
||||
var byteArray = new Uint8Array(byteNumbers);
|
||||
var blob = new Blob([byteArray], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
|
||||
var url = URL.createObjectURL(blob);
|
||||
downloadFile(url, filename);
|
||||
} else {
|
||||
setApiResponse(response.data);
|
||||
}
|
||||
setLoadingGetFile(false);
|
||||
}).catch((error) => {
|
||||
if (error.response.data){
|
||||
setApiResponse(error.response.data);
|
||||
} else {
|
||||
alert(error);
|
||||
}
|
||||
setLoadingGetFile(false);
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="demoForm">
|
||||
<h2>Demo</h2>
|
||||
<form onSubmit={(e) => handleSubmitFile(e)}>
|
||||
<input type="hidden" name="N" value="0" />
|
||||
<div className="centering">
|
||||
<div className="fileInputContainer">
|
||||
<input
|
||||
name="file"
|
||||
type="file"
|
||||
onChange={((e) => setSelectedFile(e.target.files[0]))}
|
||||
/>
|
||||
{selectedFile ?
|
||||
<p className="fileInputText">
|
||||
{selectedFile.name}
|
||||
</p>
|
||||
:
|
||||
<p className="fileInputText">
|
||||
Drag file or click here.
|
||||
</p>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
{loadingSave?
|
||||
<Loader/>
|
||||
:
|
||||
<div>
|
||||
<button style={{ marginTop:'10px', marginBottom: '0px' }} type="submit" className="button1">Click here to save file!</button>
|
||||
</div>
|
||||
}
|
||||
</form>
|
||||
<br />
|
||||
{filename &&
|
||||
<>
|
||||
<p>Your saved file:</p>
|
||||
<b>{filename}</b>
|
||||
</>
|
||||
}
|
||||
<form onSubmit={(e) => handleGetFile(e)} style={{marginTop:'20px'}}>
|
||||
<div style={{ width: "25%", margin:'auto' }}>
|
||||
<label for="filename">Paste your saved file filename here:</label>
|
||||
<input type="text" id="filename" name="filename" onChange={((e) => setFilenameInput(e.target.value))} placeholder="filename"/>
|
||||
</div>
|
||||
<div>
|
||||
<label for="t">Type:</label>
|
||||
<div className="selectWrapper">
|
||||
<select name="t" id="t" onChange={(e) => setType(e.target.value)} value={type}>
|
||||
<option value="csv" selected>CSV</option>
|
||||
<option value="json">Array of arrays</option>
|
||||
<option value="html">HTML TABLE</option>
|
||||
<option value="file">Download file</option>
|
||||
</select>
|
||||
</div>
|
||||
{type == 'file' &&
|
||||
<div style={{ width: "25%", margin: 'auto' }}>
|
||||
<label for="name">Name to give file:</label>
|
||||
<input name="name" type="text" onChange={((e) => setDownloadName(e.target.value))} placeholder="name" value={downloadName} />
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<p>request url:</p>
|
||||
<p style={{ overflowWrap: 'break-word', wordWrap: 'break-word', width: '90%', margin: 'auto' }}>
|
||||
/api/file?filename=
|
||||
{filenameInput&&
|
||||
<>
|
||||
{encodeURIComponent(filenameInput)}
|
||||
</>
|
||||
}
|
||||
{type != 'csv' &&
|
||||
<>
|
||||
&t={type}
|
||||
</>
|
||||
}
|
||||
{downloadName &&
|
||||
<>
|
||||
&name={encodeURIComponent(downloadName)}
|
||||
</>
|
||||
}
|
||||
</p>
|
||||
{loadingGetFile?
|
||||
<Loader />
|
||||
:
|
||||
<button style={{ marginBottom: '20px' }} className="button1" type="submit">Read file</button>
|
||||
}
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default (SaveForm);
|
50
src/Features/Upload/Upload.js
Normal file
50
src/Features/Upload/Upload.js
Normal file
@ -0,0 +1,50 @@
|
||||
import React, { useState } from 'react';
|
||||
import ResponseDisplay from '../ResponseDisplay/ResponseDisplay';
|
||||
import UploadForm from './UploadForm/UploadForm';
|
||||
const Upload = () => {
|
||||
const [type, setType] = useState('csv');
|
||||
const [selectedFile, setSelectedFile] = useState();
|
||||
const [apiResponse, setApiResponse] = useState();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="descriptionContainer">
|
||||
<h2>/api/upload</h2>
|
||||
<p style={{ color: '#6d6d6d' }}>
|
||||
Upload a file to /api/upload to convert the spreadsheet to a simpler format
|
||||
</p>
|
||||
<b>Parameters:</b>
|
||||
<ul style={{ textAlign: 'left' }}>
|
||||
<li>
|
||||
t=<type> export type: default is csv, "json" for json, "html" for html, "file" is to download the file
|
||||
</li>
|
||||
<li>
|
||||
addr=<cell address> the address of the cell that you want to change the value of
|
||||
</li>
|
||||
<li>
|
||||
val=<value> the value you want in that cell
|
||||
</li>
|
||||
<li>
|
||||
name=<name for file> the name you want to give to a file for downloading
|
||||
</li>
|
||||
</ul>
|
||||
<br />
|
||||
</div>
|
||||
<UploadForm
|
||||
setApiResponse={setApiResponse}
|
||||
setType={setType}
|
||||
type={type}
|
||||
setSelectedFile={setSelectedFile}
|
||||
selectedFile={selectedFile}
|
||||
|
||||
/>
|
||||
<ResponseDisplay
|
||||
apiResponse={apiResponse}
|
||||
type={type}
|
||||
endpoint='/api/upload'
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default (Upload);
|
123
src/Features/Upload/UploadForm/UploadForm.js
Normal file
123
src/Features/Upload/UploadForm/UploadForm.js
Normal file
@ -0,0 +1,123 @@
|
||||
import React, { useState } from 'react';
|
||||
import axios from 'axios';
|
||||
import Loader from '../../Loader/Loader';
|
||||
const downloadFile = require('../../../downloadFile');
|
||||
|
||||
const UploadForm = ({ type, setType, setApiResponse, selectedFile, setSelectedFile }) => {
|
||||
const [addr, setAddr] = useState('');
|
||||
const [val, setVal] = useState('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [downloadName, setDownloadName] = useState('');
|
||||
|
||||
|
||||
//post file to /api/upload
|
||||
const handleSubmit = (event) => {
|
||||
setLoading(true);
|
||||
event.preventDefault();
|
||||
let formData = new FormData();
|
||||
formData.append("file", selectedFile);
|
||||
let url = `/api/upload?t=${type}&addr=${addr}&val=${val}&name=${downloadName}`;
|
||||
axios.post(url, formData, {
|
||||
headers: {
|
||||
"Content-Type": "multipart/form-data"
|
||||
}
|
||||
}).then((response) => {
|
||||
if (type == 'file') {
|
||||
var workbook = response.data.workbook;
|
||||
var filename = response.data.newName;
|
||||
var byteCharacters = atob(workbook);
|
||||
var byteNumbers = new Array(byteCharacters.length);
|
||||
for (var i = 0; i < byteCharacters.length; i++) {
|
||||
byteNumbers[i] = byteCharacters.charCodeAt(i);
|
||||
}
|
||||
var byteArray = new Uint8Array(byteNumbers);
|
||||
var blob = new Blob([byteArray], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
|
||||
var url = URL.createObjectURL(blob);
|
||||
downloadFile(url, filename);
|
||||
}else{
|
||||
setApiResponse(response.data);
|
||||
}
|
||||
setLoading(false);
|
||||
}).catch((error) => {
|
||||
if (error.response.data){
|
||||
setApiResponse(error.response.data);
|
||||
}else{
|
||||
alert(error);
|
||||
}
|
||||
setLoading(false);
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="demoForm">
|
||||
<h2>Demo</h2>
|
||||
<form encType="multipart/form-data" onSubmit={(e) => handleSubmit(e)}>
|
||||
<input type="hidden" value="0" />
|
||||
<div className="centering">
|
||||
<div className="fileInputContainer">
|
||||
<input
|
||||
name="file"
|
||||
type="file"
|
||||
onChange={((e) => setSelectedFile(e.target.files[0]))}
|
||||
/>
|
||||
{selectedFile ?
|
||||
<p className="fileInputText">
|
||||
{selectedFile.name}
|
||||
</p>
|
||||
:
|
||||
<p className="fileInputText">
|
||||
Drag file or click here.
|
||||
</p>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<label for="t">Export Type:</label>
|
||||
<div className="selectWrapper">
|
||||
<select name="t" id="t" onChange={(e) => setType(e.target.value)} value={type}>
|
||||
<option value="csv" selected>CSV</option>
|
||||
<option value="json">Array of arrays</option>
|
||||
<option value="html">HTML TABLE</option>
|
||||
<option value="file">Download file</option>
|
||||
</select>
|
||||
</div>
|
||||
{type == 'file' &&
|
||||
<div style={{ width: "25%", margin: 'auto' }}>
|
||||
<label for="name">Name to give file:</label>
|
||||
<input name="name" type="text" onChange={((e) => setDownloadName(e.target.value))} placeholder="name" value={downloadName} />
|
||||
</div>
|
||||
}
|
||||
<div style={{ display: "flex", margin: 'auto', width: '80%' }}>
|
||||
<div style={{ width: "50%" }}>
|
||||
<label for="addr">Cell address to change:</label>
|
||||
<input name="addr" type="text" onChange={((e) => setAddr(e.target.value))} placeholder="addr" />
|
||||
</div>
|
||||
<div style={{ width: "50%" }}>
|
||||
<label for="val">New value:</label>
|
||||
<input name="val" type="text" onChange={((e) => setVal(encodeURIComponent(e.target.value)))} placeholder="val" />
|
||||
</div>
|
||||
</div>
|
||||
<p>request url:</p>
|
||||
<p style={{ overflowWrap: 'break-word', wordWrap: 'break-word', width: '90%', margin: 'auto' }}>
|
||||
/api/upload
|
||||
{type != 'csv' &&
|
||||
<>
|
||||
?t={type}
|
||||
</>
|
||||
}
|
||||
{downloadName &&
|
||||
<>
|
||||
&name={encodeURIComponent(downloadName)}
|
||||
</>
|
||||
}
|
||||
</p>
|
||||
{loading ?
|
||||
<Loader />
|
||||
:
|
||||
<button style={{ marginBottom: '20px' }} className="button1">Click here to upload!</button>
|
||||
}
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default (UploadForm);
|
BIN
src/assets/logo.png
Normal file
BIN
src/assets/logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 36 KiB |
10
src/downloadFile.js
Normal file
10
src/downloadFile.js
Normal file
@ -0,0 +1,10 @@
|
||||
//creates link to download a file. then clicks link automatically. then removes link
|
||||
module.exports = function (url, filename) {
|
||||
var a = document.createElement("a");
|
||||
a.download = filename;
|
||||
a.href = url;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
if (URL.revokeObjectURL && typeof setTimeout !== 'undefined') setTimeout(function () { URL.revokeObjectURL(url); }, 60000);
|
||||
}
|
202
src/fonts/Roboto/LICENSE.txt
Executable file
202
src/fonts/Roboto/LICENSE.txt
Executable file
@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
BIN
src/fonts/Roboto/Roboto-Black.ttf
Executable file
BIN
src/fonts/Roboto/Roboto-Black.ttf
Executable file
Binary file not shown.
BIN
src/fonts/Roboto/Roboto-BlackItalic.ttf
Executable file
BIN
src/fonts/Roboto/Roboto-BlackItalic.ttf
Executable file
Binary file not shown.
BIN
src/fonts/Roboto/Roboto-Bold.ttf
Executable file
BIN
src/fonts/Roboto/Roboto-Bold.ttf
Executable file
Binary file not shown.
BIN
src/fonts/Roboto/Roboto-BoldItalic.ttf
Executable file
BIN
src/fonts/Roboto/Roboto-BoldItalic.ttf
Executable file
Binary file not shown.
BIN
src/fonts/Roboto/Roboto-Italic.ttf
Executable file
BIN
src/fonts/Roboto/Roboto-Italic.ttf
Executable file
Binary file not shown.
BIN
src/fonts/Roboto/Roboto-Light.ttf
Executable file
BIN
src/fonts/Roboto/Roboto-Light.ttf
Executable file
Binary file not shown.
BIN
src/fonts/Roboto/Roboto-LightItalic.ttf
Executable file
BIN
src/fonts/Roboto/Roboto-LightItalic.ttf
Executable file
Binary file not shown.
BIN
src/fonts/Roboto/Roboto-Medium.ttf
Executable file
BIN
src/fonts/Roboto/Roboto-Medium.ttf
Executable file
Binary file not shown.
BIN
src/fonts/Roboto/Roboto-MediumItalic.ttf
Executable file
BIN
src/fonts/Roboto/Roboto-MediumItalic.ttf
Executable file
Binary file not shown.
BIN
src/fonts/Roboto/Roboto-Regular.ttf
Executable file
BIN
src/fonts/Roboto/Roboto-Regular.ttf
Executable file
Binary file not shown.
BIN
src/fonts/Roboto/Roboto-Thin.ttf
Executable file
BIN
src/fonts/Roboto/Roboto-Thin.ttf
Executable file
Binary file not shown.
BIN
src/fonts/Roboto/Roboto-ThinItalic.ttf
Executable file
BIN
src/fonts/Roboto/Roboto-ThinItalic.ttf
Executable file
Binary file not shown.
0
src/index.css
Normal file
0
src/index.css
Normal file
11
src/index.js
Normal file
11
src/index.js
Normal file
@ -0,0 +1,11 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import './index.css';
|
||||
import App from './App';
|
||||
|
||||
ReactDOM.render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>,
|
||||
document.getElementById('root')
|
||||
);
|
27
src/util.js
27
src/util.js
@ -9,6 +9,14 @@ module.exports = function(req, body, url, res) {
|
||||
|
||||
/* N parameter specifies worksheet index */
|
||||
const N = url.query.N ? parseInt(url.query.N,10) : 0;
|
||||
|
||||
const addr = url.query.addr || null;
|
||||
const val = url.query.val || null;
|
||||
const name = url.query.name || null;
|
||||
|
||||
if (addr != null && val != null) {
|
||||
XLSX.utils.sheet_add_aoa(wb.Sheets[wb.SheetNames[0]], [[val]], { origin: addr });
|
||||
}
|
||||
|
||||
/* -1 -> return sheet names */
|
||||
if(N < 0) switch(url.query.t || "csv") {
|
||||
@ -28,6 +36,25 @@ module.exports = function(req, body, url, res) {
|
||||
switch(url.query.t) {
|
||||
case "json": return res.status(200).json(XLSX.utils.sheet_to_json(ws, {header:1, raw:true}));
|
||||
case "html": return XLSX.stream.to_html(ws).pipe(res);
|
||||
case "file":
|
||||
var newName;
|
||||
if(name!=null){
|
||||
newName = name + '.xlsx';
|
||||
}else{
|
||||
newName = 'yourFile.xlsx'
|
||||
}
|
||||
// const buf = XLSX.write(wb, { type: "buffer", bookType: "xlsx" });
|
||||
// res.header("Content-Type", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
|
||||
// res.header("Content-Disposition", `attachment; filename="${newName}"`);
|
||||
// return res.status(200).end(buf);
|
||||
res.setHeader('Content-Type', 'application/octet-stream');
|
||||
var wbopts = {
|
||||
type: 'base64',
|
||||
bookType: "xlsx",
|
||||
bookSST: false
|
||||
}
|
||||
var wbout = XLSX.write(wb, wbopts);
|
||||
return res.send({workbook: wbout, newName: newName});
|
||||
default: return XLSX.stream.to_csv(ws).pipe(res);
|
||||
}
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user