[site][xl]: move site folder to root

This commit is contained in:
deme
2023-04-11 11:24:01 -03:00
parent ca3eccad86
commit fb86ab4804
72 changed files with 6784 additions and 496 deletions

View File

@@ -0,0 +1,16 @@
import Link from "next/link";
export default function CustomLink({ as, href, ...otherProps }) {
return (
<>
<Link legacyBehavior as={as} href={href}>
<a {...otherProps} />
</Link>
<style jsx>{`
a {
color: tomato;
}
`}</style>
</>
);
}

View File

@@ -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 (
<div>
<DragDropFile handleFile={handleFile}>
<h2>Drag or choose a spreadsheet file</h2>
<div className="">
<DataInput handleFile={handleFile} />
</div>
</DragDropFile>
<div className="mb-6">
<h3>Enter spreadsheet URL</h3>
<UrlInput handleUrl={handleUrl} />
</div>
<div className="row">
<OutTable data={data} cols={cols} />
</div>
</div>
);
}
if(typeof module !== 'undefined') module.exports = SheetJSApp
/* -------------------------------------------------------------------------- */
/*
Simple HTML5 file drag-and-drop wrapper
usage: <DragDropFile handleFile={handleFile}>...</DragDropFile>
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 (
<div
onDrop={handleDrop}
onDragEnter={suppress}
onDragOver={suppress}
>
{children}
</div>
);
}
function UrlInput({ handleUrl }) {
const handleChange = (e) => {
const url = e.target.value;
if(url) handleUrl(url);
};
return (
<form className="form-inline">
<div className="form-group">
<label htmlFor="url">Spreadsheet URL (with CORS enabled!)</label>
<br />
<small>Here is one: http://localhost:3000/_files/eight-centuries-of-global-real-interest-rates-r-g-and-the-suprasecular-decline-1311-2018-data.xlsx</small>
<br />
<input
type="text"
id="url"
className="border w-96"
accept={SheetJSFT}
onChange={handleChange}
/>
</div>
</form>
)
}
/*
Simple HTML5 file input wrapper
usage: <DataInput handleFile={callback} />
handleFile(file:File):void;
*/
function DataInput({ handleFile }) {
const handleChange = (e) => {
const files = e.target.files;
if(files && files[0]) handleFile(files[0]);
};
return (
<form className="form-inline">
<div className="form-group">
<label htmlFor="file">Select spreadsheet file</label>
<br />
<input
type="file"
className="form-control"
id="file"
accept={SheetJSFT}
onChange={handleChange}
/>
</div>
</form>
)
}
/*
Simple HTML Table
usage: <OutTable data={data} cols={cols} />
data:Array<Array<any> >;
cols:Array<{name:string, key:number|string}>;
*/
function OutTable({ data, cols }) {
return (
<div className="table-responsive">
<table className="table table-striped">
<thead>
<tr>{cols.map((c) => <th key={c.key}>{c.name}</th>)}</tr>
</thead>
<tbody>
{data.map((r,i) => <tr key={i}>
{cols.map(c => <td key={c.key}>{ r[c.key] }</td>)}
</tr>)}
</tbody>
</table>
</div>
);
}
/* 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;
};

View File

@@ -0,0 +1,34 @@
import { NextSeo } from "next-seo";
import Nav from "./Nav";
export default function Layout({
children,
title,
}: {
children;
title?: string;
}) {
return (
<>
{title && <NextSeo title={title} />}
<Nav />
<div className="mx-auto p-6">{children}</div>
<footer className="flex items-center justify-center w-full h-24 border-t">
<a
className="flex items-center justify-center"
href="https://datopian.com/"
target="_blank"
rel="noopener noreferrer"
>
Built by{" "}
<img
src="/datopian-logo.png"
alt="Datopian Logo"
className="h-6 ml-2"
/>
</a>
</footer>
</>
);
}

View File

@@ -0,0 +1,40 @@
import { MDXRemote } from "next-mdx-remote";
import layouts from "layouts";
export default function DRD({ source, frontMatter }) {
const Layout = ({ children }) => {
if (frontMatter.layout) {
let LayoutComponent = layouts[frontMatter.layout];
return <LayoutComponent {...frontMatter}>{children}</LayoutComponent>;
}
return <>{children}</>;
};
return (
<div className="prose mx-auto">
<header>
<div className="mb-6">
{/* Default layout */}
{!frontMatter.layout && (
<>
<h1>{frontMatter.title}</h1>
{frontMatter.author && (
<div className="-mt-6">
<p className="opacity-60 pl-1">{frontMatter.author}</p>
</div>
)}
{frontMatter.description && (
<p className="description">{frontMatter.description}</p>
)}
</>
)}
</div>
</header>
<main>
<Layout>
<MDXRemote {...source} />
</Layout>
</main>
</div>
);
}

96
site/components/Nav.js Normal file
View File

@@ -0,0 +1,96 @@
import { Fragment } from "react";
import { Disclosure, Menu, Transition } from "@headlessui/react";
import { BellIcon, MenuIcon, XIcon } from "@heroicons/react/outline";
import { siteConfig } from "config/siteConfig";
import Link from "next/link";
import GitHubButton from "react-next-github-btn";
const navigation = siteConfig.navLinks;
function classNames(...classes) {
return classes.filter(Boolean).join(" ");
}
export default function Nav() {
return (
<Disclosure as="nav" className="bg-gray-800">
{({ open }) => (
<>
<div className="max-w-7xl mx-auto px-2 sm:px-6 lg:px-8">
<div className="relative flex items-center justify-between h-16">
<div className="absolute inset-y-0 left-0 flex items-center sm:hidden">
{/* Mobile menu button*/}
<Disclosure.Button className="inline-flex items-center justify-center p-2 rounded-md text-gray-400 hover:text-white hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-white">
<span className="sr-only">Open main menu</span>
{open ? (
<XIcon className="block h-6 w-6" aria-hidden="true" />
) : (
<MenuIcon className="block h-6 w-6" aria-hidden="true" />
)}
</Disclosure.Button>
</div>
<div className="flex-1 flex items-center justify-center sm:items-stretch sm:justify-start">
<div className="flex-shrink-0 flex items-center">
<Link href="/" className="text-white">
Portal.JS
</Link>
</div>
<div className="hidden sm:block sm:ml-6">
<div className="flex space-x-4">
{navigation.map((item) => (
<Link
href={item.href}
key={item.name}
className={classNames(
item.current
? "bg-gray-900 text-white"
: "text-gray-300 hover:bg-gray-700 hover:text-white",
"px-3 py-2 rounded-md text-sm font-medium"
)}
aria-current={item.current ? "page" : undefined}
>
{item.name}
</Link>
))}
</div>
</div>
</div>
<div className="mt-2 justify-end">
<GitHubButton
href="https://github.com/datopian/portal.js"
data-color-scheme="no-preference: light; light: light; dark: dark;"
data-size="large"
data-show-count="true"
aria-label="Star datopian/portal.js on GitHub"
>
Stars
</GitHubButton>
</div>
</div>
</div>
<Disclosure.Panel className="sm:hidden">
<div className="px-2 pt-2 pb-3 space-y-1">
{navigation.map((item) => (
<a
key={item.name}
href={item.href}
className={classNames(
item.current
? "bg-gray-900 text-white"
: "text-gray-300 hover:bg-gray-700 hover:text-white",
"block px-3 py-2 rounded-md text-base font-medium"
)}
aria-current={item.current ? "page" : undefined}
>
{item.name}
</a>
))}
</div>
</Disclosure.Panel>
</>
)}
</Disclosure>
);
}

View File

@@ -0,0 +1,46 @@
import Layout from '../Layout'
import { MDXRemote } from 'next-mdx-remote'
import dynamic from 'next/dynamic'
import Head from 'next/head'
import CustomLink from '../CustomLink'
import { Vega, VegaLite } from 'react-vega'
// Custom components/renderers to pass to MDX.
// Since the MDX files aren't loaded by webpack, they have no knowledge of how
// to handle import statements. Instead, you must include components in scope
// here.
const components = {
a: CustomLink,
Table: dynamic(() => import('./Table')),
Excel: dynamic(() => import('./Excel')),
// TODO: try and make these dynamic ...
Vega: Vega,
VegaLite: VegaLite,
LineChart: dynamic(() => import('./LineChart')),
Head,
}
export default function DataLiterate({ source, frontMatter }) {
return (
<Layout title={frontMatter.title}>
<div className="prose mx-auto">
<header>
<div className="mb-6">
<h1>{frontMatter.title}</h1>
{frontMatter.author && (
<div className="-mt-6"><p className="opacity-60 pl-1">{frontMatter.author}</p></div>
)}
{frontMatter.description && (
<p className="description">{frontMatter.description}</p>
)}
</div>
</header>
<main>
<MDXRemote {...source} components={components} />
</main>
</div>
</Layout>
)
}

View File

@@ -0,0 +1,74 @@
import axios from 'axios'
import XLSX from 'xlsx'
import React, { useEffect, useState } from 'react'
import Table from './Table'
export default function Excel ({ src='' }) {
const [data, setData] = React.useState([])
const [cols, setCols] = React.useState([])
const [workbook, setWorkbook] = React.useState(null)
const [error, setError] = React.useState('')
const [hasMounted, setHasMounted] = React.useState(0)
// so this is here so we re-render this in the browser
// and not just when we build the page statically in nextjs
useEffect(() => {
if (hasMounted==0) {
handleUrl(src)
}
setHasMounted(1)
})
function handleUrl(url) {
// if url is external may have CORS issue so we proxy it ...
if (url.startsWith('http')) {
const PROXY_URL = window.location.origin + '/api/proxy'
url = PROXY_URL + '?url=' + encodeURIComponent(url)
}
axios.get(url, {
responseType: 'arraybuffer'
}).then((res) => {
let out = new Uint8Array(res.data)
let workbook = XLSX.read(out, {type: "array"})
// Get first worksheet
const wsname = workbook.SheetNames[0]
const ws = workbook.Sheets[wsname]
// Convert array of arrays
const datatmp = XLSX.utils.sheet_to_json(ws, {header:1})
const colstmp = make_cols(ws['!ref'])
setData(datatmp)
setCols(colstmp)
setWorkbook(workbook)
}).catch((e) => {
setError(e.message)
})
}
return (
<>
{error &&
<div>
There was an error loading the excel file at {src}:
<p>{error}</p>
</div>
}
{workbook &&
<ul>
{workbook.SheetNames.map((value, index) => {
return <li key={index}>{value}</li>
})}
</ul>
}
<Table data={data} cols={cols} />
</>
)
}
/* 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
}

View File

@@ -0,0 +1,33 @@
import { Vega, VegaLite } from 'react-vega'
export default function LineChart( { data=[] }) {
var tmp = data
if (Array.isArray(data)) {
tmp = data.map((r,i) => {
return { x: r[0], y: r[1] }
})
}
const vegaData = { "table": tmp }
const spec = {
"$schema": "https://vega.github.io/schema/vega-lite/v5.json",
"mark": "line",
"data": {
"name": "table"
},
"encoding": {
"x": {
"field": "x",
"timeUnit": "year",
"type": "temporal"
},
"y": {
"field": "y",
"type": "quantitative"
}
}
}
return (
<VegaLite data={ vegaData } spec={ spec } />
)
}

View File

@@ -0,0 +1,83 @@
import axios from 'axios'
import React, { useEffect, useState } from 'react'
const papa = require("papaparse")
/*
Simple HTML Table
usage: <OutTable data={data} cols={cols} />
data:Array<Array<any> >;
cols:Array<{name:string, key:number|string}>;
*/
export default function Table({ data=[], cols=[], csv='', url='' }) {
if (csv) {
const out = parseCsv(csv)
data = out.rows
cols = out.cols
}
const [ourdata, setData] = React.useState(data)
const [ourcols, setCols] = React.useState(cols)
const [error, setError] = React.useState('')
useEffect(() => {
if (url) {
loadUrl(url)
}
}, [url])
function loadUrl(path) {
// HACK: duplicate of Excel code - maybe refactor
// if url is external may have CORS issue so we proxy it ...
if (url.startsWith('http')) {
const PROXY_URL = window.location.origin + '/api/proxy'
url = PROXY_URL + '?url=' + encodeURIComponent(url)
}
axios.get(url).then((res) => {
const { rows, fields } = parseCsv(res.data)
setData(rows)
setCols(fields)
})
}
return (
<>
<SimpleTable data={ourdata} cols={ourcols} />
</>
)
}
/*
Simple HTML Table
usage: <OutTable data={data} cols={cols} />
data:Array<Array<any> >;
cols:Array<{name:string, key:number|string}>;
*/
function SimpleTable({ data=[], cols=[] }) {
return (
<div className="table-responsive">
<table className="table table-striped">
<thead>
<tr>{cols.map((c) => <th key={c.key}>{c.name}</th>)}</tr>
</thead>
<tbody>
{data.map((r,i) => <tr key={i}>
{cols.map(c => <td key={c.key}>{ r[c.key] }</td>)}
</tr>)}
</tbody>
</table>
</div>
)
}
function parseCsv(csv) {
csv = csv.trim()
const rawdata = papa.parse(csv, {header: true})
const cols = rawdata.meta.fields.map((r,i) => {
return { key: r, name: r }
})
return {
rows: rawdata.data,
fields: cols
}
}