Merge pull request #543 from datopian/ref/portal-reusable
Ref portal to be reusable
This commit is contained in:
commit
173309adca
3
.babelrc
Normal file
3
.babelrc
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"presets": ["@babel/env", "@babel/preset-react"]
|
||||
}
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@ -28,3 +28,6 @@ yarn-error.log*
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
# output
|
||||
dist/*
|
||||
8
jest.config.js
Normal file
8
jest.config.js
Normal file
@ -0,0 +1,8 @@
|
||||
module.exports = {
|
||||
testPathIgnorePatterns: ["<rootDir>/.next/", "<rootDir>/node_modules/", "<rootDir>/examples/catalog/*"],
|
||||
setupFilesAfterEnv: ["<rootDir>/tests/setupTests.js"],
|
||||
transform: {
|
||||
"^.+\\.(js|jsx|ts|tsx)$": "<rootDir>/node_modules/babel-jest",
|
||||
"\\.(css|less|scss|sass)$": "identity-obj-proxy"
|
||||
}
|
||||
};
|
||||
48
package.json
Normal file
48
package.json
Normal file
@ -0,0 +1,48 @@
|
||||
{
|
||||
"name": "portal",
|
||||
"description": "The data presentation framework",
|
||||
"author": "Datopian",
|
||||
"license": "MIT",
|
||||
"version": "0.1.0",
|
||||
"main": "dist/index.cjs.js",
|
||||
"module": "dist/index.esm.js",
|
||||
"source": "src/index.js",
|
||||
"scripts": {
|
||||
"build": "rollup -c",
|
||||
"build-watch": "rollup -c -w",
|
||||
"test": "jest --coverage"
|
||||
},
|
||||
"dependencies": {
|
||||
"@material-ui/core": "^4.11.3",
|
||||
"@material-ui/data-grid": "^4.0.0-alpha.20",
|
||||
"filesize": "^6.3.0",
|
||||
"next": "latest",
|
||||
"plotly.js-basic-dist": "^1.58.4",
|
||||
"postcss": "^8.1.10",
|
||||
"react": "^17.0.1",
|
||||
"react-dom": "^17.0.1",
|
||||
"react-plotly.js": "^2.5.1",
|
||||
"react-scripts": "3.4.3",
|
||||
"tailwindcss": "^2.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/cli": "^7.13.16",
|
||||
"@babel/core": "^7.13.16",
|
||||
"@babel/plugin-proposal-class-properties": "^7.13.0",
|
||||
"@babel/preset-env": "^7.13.15",
|
||||
"@babel/preset-react": "^7.13.13",
|
||||
"@rollup/plugin-babel": "^5.3.0",
|
||||
"@testing-library/dom": "^7.29.6",
|
||||
"@testing-library/jest-dom": "^5.11.9",
|
||||
"@testing-library/react": "^11.2.5",
|
||||
"babel-jest": "^26.6.3",
|
||||
"identity-obj-proxy": "^3.0.0",
|
||||
"jest": "^26.6.3",
|
||||
"jest-canvas-mock": "^2.3.1",
|
||||
"jest-dom": "^4.0.0",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"rollup": "^2.45.2",
|
||||
"rollup-plugin-delete": "^2.0.0",
|
||||
"rollup-plugin-peer-deps-external": "^2.2.4"
|
||||
}
|
||||
}
|
||||
8
postcss.config.js
Normal file
8
postcss.config.js
Normal file
@ -0,0 +1,8 @@
|
||||
// If you want to use other PostCSS plugins, see the following:
|
||||
// https://tailwindcss.com/docs/using-with-preprocessors
|
||||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
||||
20
rollup.config.js
Normal file
20
rollup.config.js
Normal file
@ -0,0 +1,20 @@
|
||||
import babel from '@rollup/plugin-babel';
|
||||
import external from 'rollup-plugin-peer-deps-external';
|
||||
import del from 'rollup-plugin-delete';
|
||||
import pkg from './package.json';
|
||||
|
||||
export default {
|
||||
input: pkg.source,
|
||||
output: [
|
||||
{ file: pkg.main, format: 'cjs' },
|
||||
{ file: pkg.module, format: 'esm' }
|
||||
],
|
||||
plugins: [
|
||||
external(),
|
||||
babel({
|
||||
exclude: 'node_modules/**'
|
||||
}),
|
||||
del({ targets: ['dist/*'] }),
|
||||
],
|
||||
external: Object.keys(pkg.peerDependencies || {}),
|
||||
};
|
||||
94
src/components/page/KeyInfo.js
Normal file
94
src/components/page/KeyInfo.js
Normal file
@ -0,0 +1,94 @@
|
||||
import React from 'react';
|
||||
import filesize from 'filesize'
|
||||
|
||||
/**
|
||||
* KeyInfo component receives two arguments.
|
||||
* @param {Object} descriptor A Frictionless datapackage descriptor object with the following fields:
|
||||
* {
|
||||
* title: "Title of the data package",
|
||||
* length: "The number of resources present in the data package"
|
||||
* datasetSize: The combined size of the data package resources
|
||||
* format: The format of resources in the dataset. e.g csv, json, excel
|
||||
* created: The date the dataset was created
|
||||
* updated: The date the dataset was last updated
|
||||
* licence: The licence of the dataset
|
||||
* sources: An array of the data set sources
|
||||
* }
|
||||
* @param {Array} resources A Frictionless datapackage resource array
|
||||
* @returns React Component
|
||||
*/
|
||||
const KeyInfo = ({ descriptor, resources }) => {
|
||||
let datasetSize = 0
|
||||
if (resources) {
|
||||
datasetSize = resources.length == 1 ?
|
||||
resources[0].size :
|
||||
resources.reduce((accumulator, currentValue) => {
|
||||
return accumulator.size + currentValue.size
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<section className="m-8" name="key-info" id="key-info">
|
||||
<h1 data-testid="datasetTitle" className="text-3xl font-bold mb-8">
|
||||
{descriptor.title}
|
||||
</h1>
|
||||
<h1 className="text-2xl font-bold mb-4">Key info</h1>
|
||||
<div className="grid grid-cols-7 gap-4">
|
||||
<div>
|
||||
<h3 className="text-1xl font-bold mb-2">Files</h3>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-1xl font-bold mb-2">Size</h3>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-1xl font-bold mb-2">Format</h3>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-1xl font-bold mb-2">Created</h3>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-1xl font-bold mb-2">Updated</h3>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-1xl font-bold mb-2">Licence</h3>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-1xl font-bold mb-2">Source</h3>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-7 gap-4">
|
||||
<div>
|
||||
<h3 className="text-1xl">{resources.length}</h3>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-1xl">{filesize(datasetSize, { bits: true })}</h3>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-1xl">{resources[0].format} zip</h3>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-1xl">{descriptor.created}</h3>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-1xl">{descriptor.updated}</h3>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-1xl">{descriptor.license}</h3>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-1xl">
|
||||
<a className="text-yellow-600"
|
||||
href={descriptor.sources[0].web}>
|
||||
{descriptor.sources[0].title}
|
||||
</a>
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default KeyInfo
|
||||
21
src/components/page/Readme.js
Normal file
21
src/components/page/Readme.js
Normal file
@ -0,0 +1,21 @@
|
||||
import React from 'react';
|
||||
|
||||
/**
|
||||
* ReadMe component displays the markdown description of a datapackage
|
||||
* @param {string} readme parsed html of data package readme
|
||||
* @returns React Component
|
||||
*/
|
||||
const ReadMe = ({ readmeHtml }) => {
|
||||
return (
|
||||
<>
|
||||
<section className="m-8" name="sample-table">
|
||||
<h1 className="text-2xl font-bold mb-4">README</h1>
|
||||
<div className="prose">
|
||||
<div dangerouslySetInnerHTML={{ __html: readmeHtml }} />
|
||||
</div>
|
||||
</section>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default ReadMe
|
||||
62
src/components/page/ResourceInfo.js
Normal file
62
src/components/page/ResourceInfo.js
Normal file
@ -0,0 +1,62 @@
|
||||
import React from 'react';
|
||||
import filesize from 'filesize'
|
||||
|
||||
/**
|
||||
* ResourceInfo component displays all resources in a data package
|
||||
* @param {Array} resources A Frictionless datapackage resource object
|
||||
* @returns React Component
|
||||
*/
|
||||
const ResourcesInfo = ({ resources }) => {
|
||||
return (
|
||||
<>
|
||||
<section className="m-8" name="file-list">
|
||||
<h1 className="text-2xl font-bold mb-4">Data Files</h1>
|
||||
<div className="grid grid-cols-7 gap-4">
|
||||
<div>
|
||||
<h3 className="text-1xl font-bold mb-2">File</h3>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-1xl font-bold mb-2">Description</h3>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-1xl font-bold mb-2">Size</h3>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-1xl font-bold mb-2">Last Changed</h3>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-1xl font-bold mb-2">Download</h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{resources.map((resource, index) => {
|
||||
return (
|
||||
<div key={`${index}_${resource.name}`} className="grid grid-cols-7 gap-4">
|
||||
<div>
|
||||
<h3 className="text-1xl">{resource.name}</h3>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-1xl">{resource.description || "No description"}</h3>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-1xl">{filesize(resource.size, { bits: true })}</h3>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-1xl">{resource.updated}</h3>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-1xl">
|
||||
<a className="text-yellow-600" href={`/dataset/${resource.path}`}>
|
||||
{resource.format} ({filesize(resource.size, { bits: true })})
|
||||
</a>
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</section>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default ResourcesInfo
|
||||
70
src/components/ui/Home/Nav.js
Normal file
70
src/components/ui/Home/Nav.js
Normal file
@ -0,0 +1,70 @@
|
||||
import Link from 'next/link';
|
||||
import React, { useState } from 'react';
|
||||
|
||||
const Nav = ({ logo, navMenu }) => {
|
||||
const [open, setOpen] = useState(false);
|
||||
const handleClick = (event) => {
|
||||
event.preventDefault();
|
||||
setOpen(!open);
|
||||
};
|
||||
|
||||
return (
|
||||
<nav className="flex items-center justify-between flex-wrap bg-white p-4 border-b border-gray-200">
|
||||
<div className="flex items-center flex-shrink-0 text-gray-700 mr-6">
|
||||
<img src={logo} alt="portal logo" width="40" />
|
||||
</div>
|
||||
<div className="block lg:hidden mx-4">
|
||||
<button
|
||||
onClick={handleClick}
|
||||
className="flex items-center px-3 py-2 border rounded text-gray-700 border-orange-400 hover:text-black hover:border-black"
|
||||
>
|
||||
<svg
|
||||
className="fill-current h-3 w-3"
|
||||
viewBox="0 0 20 20"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<title>Menu</title>
|
||||
<path d="M0 3h20v2H0V3zm0 6h20v2H0V9zm0 6h20v2H0v-2z" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div className={`${open ? `block` : `hidden`} lg:block`}>
|
||||
{navMenu.map((menu, index) => {
|
||||
<Link href={menu.path} key={index}>
|
||||
<a className="block mt-4 lg:inline-block lg:mt-0 active:bg-primary-background text-gray-700 hover:text-black mr-6">
|
||||
{menu.title}
|
||||
</a>
|
||||
</Link>
|
||||
})}
|
||||
<Link href="/blog">
|
||||
<a className="block mt-4 lg:inline-block lg:mt-0 active:bg-primary-background text-gray-700 hover:text-black mr-6">
|
||||
Blog
|
||||
</a>
|
||||
</Link>
|
||||
<Link href="/search">
|
||||
<a className="block mt-4 lg:inline-block lg:mt-0 text-gray-700 hover:text-black mr-6">
|
||||
Search
|
||||
</a>
|
||||
</Link>
|
||||
<a
|
||||
href="http://tech.datopian.com/frontend/"
|
||||
className="block mt-4 lg:inline-block lg:mt-0 text-gray-700 hover:text-black mr-6"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
Docs
|
||||
</a>
|
||||
<a
|
||||
href="https://github.com/datopian/portal"
|
||||
className="inline-block text-tiny px-4 py-2 leading-none border rounded text-primary bg-primary-background border-black hover:border-gray-700 hover:text-gray-700 hover:bg-white mt-4 lg:mt-0"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
GitHub
|
||||
</a>
|
||||
</div>
|
||||
</nav>
|
||||
);
|
||||
};
|
||||
|
||||
export default Nav;
|
||||
37
src/components/ui/Home/Recent.js
Normal file
37
src/components/ui/Home/Recent.js
Normal file
@ -0,0 +1,37 @@
|
||||
import React from 'react';
|
||||
import Link from 'next/link';
|
||||
|
||||
const Recent = (datasets) => {
|
||||
if (!datasets) {
|
||||
return <></>
|
||||
}
|
||||
|
||||
return (
|
||||
<section className="my-10 mx-4 lg:my-20">
|
||||
<h1 className="text-2xl font-thin mb-4">Recent Datasets</h1>
|
||||
<div className="recent flex flex-col lg:flex-row">
|
||||
{datasets.map((dataset, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="border px-4 mb-4 mr-3 border-gray-100 w-5/6 shadow-sm"
|
||||
>
|
||||
<h1 className="text-2xl font-thin">{dataset.title}</h1>
|
||||
<p className="text-gray-500">
|
||||
{dataset.organization && dataset.organization.description}
|
||||
</p>
|
||||
<Link
|
||||
href={`/@${dataset.organization ? dataset.organization.name : 'dataset'
|
||||
}/${dataset.name}`}
|
||||
>
|
||||
<a className="pt-3 flex justify-end text-orange-500">
|
||||
View Dataset
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default Recent;
|
||||
0
src/components/ui/index.js
Normal file
0
src/components/ui/index.js
Normal file
30
src/components/views/Chart.js
vendored
Normal file
30
src/components/views/Chart.js
vendored
Normal file
@ -0,0 +1,30 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import createPlotlyComponent from "react-plotly.js/factory";
|
||||
let Plot;
|
||||
|
||||
const PlotlyChart = (props) => {
|
||||
const [plotCreated, setPlotCreated] = useState(0) //0: false, 1: true
|
||||
|
||||
useEffect(() => {
|
||||
import(`plotly.js-basic-dist`).then(Plotly => { //import Plotly dist when Page has been generated
|
||||
Plot = createPlotlyComponent(Plotly);
|
||||
setPlotCreated(1)
|
||||
});
|
||||
}, [])
|
||||
|
||||
if (!plotCreated) {
|
||||
return (<div>Loading...</div>)
|
||||
}
|
||||
|
||||
return (
|
||||
<div data-testid="plotlyChart">
|
||||
<Plot {...props.spec}
|
||||
layout={{ autosize: true }}
|
||||
style={{ width: "100%", height: "100%" }}
|
||||
useResizeHandler={true}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export { PlotlyChart }
|
||||
29
src/components/views/Table.js
Normal file
29
src/components/views/Table.js
Normal file
@ -0,0 +1,29 @@
|
||||
import React from 'react';
|
||||
import { DataGrid } from '@material-ui/data-grid';
|
||||
|
||||
/**
|
||||
* Displays a table from a Frictionless dataset
|
||||
* @param schema: Frictionless Table Schema
|
||||
* @param data: an array of data objects e.g. [ {a: 1, b: 2}, {a: 5, b: 7} ]
|
||||
*/
|
||||
const Table = ({ schema, data }) => {
|
||||
const columns = schema.fields.map((field) => (
|
||||
{
|
||||
field: field.title || field.name,
|
||||
headerName: field.name,
|
||||
width: 300
|
||||
}
|
||||
))
|
||||
data = data.map((item, index)=>{
|
||||
item.id = index //Datagrid requires every row to have an ID
|
||||
return item
|
||||
})
|
||||
|
||||
return (
|
||||
<div data-testid="tableGrid" style={{ height: 400, width: '100%' }}>
|
||||
<DataGrid rows={data} columns={columns} pageSize={5} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Table
|
||||
10
src/index.js
Normal file
10
src/index.js
Normal file
@ -0,0 +1,10 @@
|
||||
//Components
|
||||
import Table from './components/views/Table'
|
||||
import { PlotlyChart } from './components/views/Chart'
|
||||
import KeyInfo from './components/page/KeyInfo'
|
||||
import ResourceInfo from './components/page/ResourceInfo'
|
||||
import ReadMe from './components/page/Readme'
|
||||
import Nav from './components/ui/Home/Nav'
|
||||
import Recent from './components/ui/Home/Recent'
|
||||
|
||||
export { Table, PlotlyChart, KeyInfo, ResourceInfo, ReadMe, Nav, Recent }
|
||||
22
tailwind.config.js
Normal file
22
tailwind.config.js
Normal file
@ -0,0 +1,22 @@
|
||||
const defaultTheme = require("tailwindcss/defaultTheme");
|
||||
|
||||
module.exports = {
|
||||
purge: ['./pages/**/*.{js,ts,jsx,tsx}', './components/**/*.{js,ts,jsx,tsx}'],
|
||||
darkMode: false, // or 'media' or 'class'
|
||||
theme: {
|
||||
container: {
|
||||
center: true,
|
||||
},
|
||||
extend: {
|
||||
fontFamily: {
|
||||
mono: ["Inconsolata", ...defaultTheme.fontFamily.mono]
|
||||
}
|
||||
},
|
||||
},
|
||||
variants: {
|
||||
extend: {},
|
||||
},
|
||||
plugins: [
|
||||
require('@tailwindcss/typography'),
|
||||
],
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user