Merge pull request #543 from datopian/ref/portal-reusable

Ref portal to be reusable
This commit is contained in:
Rising Odegua 2021-05-03 15:59:13 +01:00 committed by GitHub
commit 173309adca
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 13932 additions and 0 deletions

3
.babelrc Normal file
View File

@ -0,0 +1,3 @@
{
"presets": ["@babel/env", "@babel/preset-react"]
}

3
.gitignore vendored
View File

@ -28,3 +28,6 @@ yarn-error.log*
.env.development.local
.env.test.local
.env.production.local
# output
dist/*

8
jest.config.js Normal file
View 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
View 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
View 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
View 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 || {}),
};

View 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

View 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

View 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

View 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;

View 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;

View File

30
src/components/views/Chart.js vendored Normal file
View 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 }

View 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
View 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
View 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'),
],
}

13467
yarn.lock Normal file

File diff suppressed because it is too large Load Diff