diff --git a/site/.gitignore b/site/.gitignore new file mode 100644 index 00000000..47575e0f --- /dev/null +++ b/site/.gitignore @@ -0,0 +1,37 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# local env files +.env.local +.env.development.local +.env.test.local +.env.production.local + +# vercel +.vercel + +# markdowndb +markdown.db diff --git a/site/README.md b/site/README.md new file mode 100644 index 00000000..2b444666 --- /dev/null +++ b/site/README.md @@ -0,0 +1,19 @@ +This the Portal.JS website. + +It is built on [Next.js](https://nextjs.org/). + +## Getting Started + +First, run the development server: + +```bash +npm run dev +# or +yarn dev +``` + +Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. + +## Deployment + +We currently deploy on Vercel. diff --git a/site/components/CustomLink.js b/site/components/CustomLink.js new file mode 100644 index 00000000..c311089d --- /dev/null +++ b/site/components/CustomLink.js @@ -0,0 +1,16 @@ +import Link from "next/link"; + +export default function CustomLink({ as, href, ...otherProps }) { + return ( + <> + + + + + + ); +} diff --git a/site/components/ExcelViewerApp.js b/site/components/ExcelViewerApp.js new file mode 100644 index 00000000..d1505afe --- /dev/null +++ b/site/components/ExcelViewerApp.js @@ -0,0 +1,186 @@ +import XLSX from 'xlsx'; +import React from 'react'; + +function SheetJSApp() { + const [data, setData] = React.useState([]); + const [cols, setCols] = React.useState([]); + + const handleFile = (file) => { + const reader = new FileReader(); + const rABS = !!reader.readAsBinaryString; + reader.onload = (e) => { + /* Parse data */ + const bstr = e.target.result; + const wb = XLSX.read(bstr, {type:rABS ? 'binary' : 'array'}); + displayWorkbook(wb); + }; + if(rABS) reader.readAsBinaryString(file); else reader.readAsArrayBuffer(file); + } + + const handleUrl = (url) => { + let oReq = new XMLHttpRequest(); + oReq.open("GET", url, true); + oReq.responseType = "arraybuffer"; + oReq.onload = function (e) { + let arraybuffer = oReq.response; + /* not responseText!! */ + + /* convert data to binary string */ + let data = new Uint8Array(arraybuffer); + let arr = new Array(); + for (let i = 0; i != data.length; ++i) arr[i] = String.fromCharCode(data[i]); + let bstr = arr.join(""); + /* Call XLSX */ + let workbook = XLSX.read(bstr, {type: "binary"}); + displayWorkbook(workbook); + }; + + oReq.send(); + } + + const displayWorkbook = (wb) => { + /* Get first worksheet */ + const wsname = wb.SheetNames[0]; + const ws = wb.Sheets[wsname]; + /t Convert array of arrays */ + const data = XLSX.utils.sheet_to_json(ws, {header:1}); + /* Update state */ + setData(data); + setCols(make_cols(ws['!ref'])); + } + + return ( +
+ +

Drag or choose a spreadsheet file

+
+ +
+
+
+

Enter spreadsheet URL

+ +
+
+ +
+
+ ); +} + +if(typeof module !== 'undefined') module.exports = SheetJSApp + +/* -------------------------------------------------------------------------- */ + +/* + Simple HTML5 file drag-and-drop wrapper + usage: ... + handleFile(file:File):void; +*/ + +function DragDropFile({ handleFile, children }) { + const suppress = (e) => { e.stopPropagation(); e.preventDefault(); }; + const handleDrop = (e) => { e.stopPropagation(); e.preventDefault(); + const files = e.dataTransfer.files; + if(files && files[0]) handleFile(files[0]); + }; + + return ( +
+ {children} +
+ ); +} + +function UrlInput({ handleUrl }) { + const handleChange = (e) => { + const url = e.target.value; + if(url) handleUrl(url); + }; + + return ( +
+
+ +
+ Here is one: http://localhost:3000/_files/eight-centuries-of-global-real-interest-rates-r-g-and-the-suprasecular-decline-1311-2018-data.xlsx +
+ +
+
+ ) +} + +/* + Simple HTML5 file input wrapper + usage: + handleFile(file:File):void; +*/ + +function DataInput({ handleFile }) { + const handleChange = (e) => { + const files = e.target.files; + if(files && files[0]) handleFile(files[0]); + }; + + return ( +
+
+ +
+ +
+
+ ) +} + +/* + Simple HTML Table + usage: + data:Array >; + cols:Array<{name:string, key:number|string}>; +*/ +function OutTable({ data, cols }) { + return ( +
+ + + {cols.map((c) => )} + + + {data.map((r,i) => + {cols.map(c => )} + )} + +
{c.name}
{ r[c.key] }
+
+ ); +} + +/* list of supported file types */ +const SheetJSFT = [ + "xlsx", "xlsb", "xlsm", "xls", "xml", "csv", "txt", "ods", "fods", "uos", "sylk", "dif", "dbf", "prn", "qpw", "123", "wb*", "wq*", "html", "htm" +].map(x => `.${x}`).join(","); + +/* generate an array of column objects */ +const make_cols = refstr => { + let o = [], C = XLSX.utils.decode_range(refstr).e.c + 1; + for(var i = 0; i < C; ++i) o[i] = {name:XLSX.utils.encode_col(i), key:i} + return o; +}; diff --git a/site/components/Layout.tsx b/site/components/Layout.tsx new file mode 100644 index 00000000..626cd44a --- /dev/null +++ b/site/components/Layout.tsx @@ -0,0 +1,34 @@ +import { NextSeo } from "next-seo"; + +import Nav from "./Nav"; + +export default function Layout({ + children, + title, +}: { + children; + title?: string; +}) { + return ( + <> + {title && } +