[#812,package][xl]: initial versioning of the package
This commit is contained in:
3
packages/components-old/src/assets/download.svg
Normal file
3
packages/components-old/src/assets/download.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="16" height="19" viewBox="0 0 16 19" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.66055e-08 9.49999C3.66055e-08 10.4283 0.368749 11.3185 1.02513 11.9749C1.6815 12.6312 2.57174 13 3.5 13H7V15.586L5.707 14.293C5.5184 14.1108 5.2658 14.01 5.0036 14.0123C4.7414 14.0146 4.49059 14.1198 4.30518 14.3052C4.11977 14.4906 4.0146 14.7414 4.01233 15.0036C4.01005 15.2658 4.11084 15.5184 4.293 15.707L7.293 18.707C7.48053 18.8945 7.73484 18.9998 8 18.9998C8.26516 18.9998 8.51947 18.8945 8.707 18.707L11.707 15.707C11.8892 15.5184 11.99 15.2658 11.9877 15.0036C11.9854 14.7414 11.8802 14.4906 11.6948 14.3052C11.5094 14.1198 11.2586 14.0146 10.9964 14.0123C10.7342 14.01 10.4816 14.1108 10.293 14.293L9 15.586V13H11.5C12.1167 12.9996 12.7268 12.8725 13.2924 12.6265C13.8579 12.3805 14.3668 12.0209 14.7876 11.5699C15.2083 11.119 15.5319 10.5864 15.7382 10.0052C15.9445 9.42397 16.0291 8.80656 15.9868 8.19128C15.9445 7.576 15.7761 6.97598 15.4922 6.42848C15.2083 5.88097 14.8149 5.39767 14.3364 5.00858C13.8579 4.61949 13.3045 4.33291 12.7106 4.16663C12.1167 4.00035 11.495 3.95793 10.884 4.04199C10.7659 3.52058 10.5445 3.02811 10.2329 2.59367C9.92137 2.15923 9.5259 1.79162 9.0699 1.51255C8.61389 1.23347 8.10659 1.0486 7.57795 0.968834C7.04931 0.88907 6.51005 0.916034 5.99201 1.04813C5.47396 1.18024 4.98763 1.41479 4.56174 1.73796C4.13585 2.06113 3.77903 2.46636 3.51234 2.92971C3.24565 3.39307 3.07451 3.90516 3.00901 4.43576C2.94351 4.96635 2.98499 5.50469 3.131 6.01899C2.27136 6.11013 1.47586 6.51618 0.897764 7.1589C0.319667 7.80162 -0.00012492 8.63554 3.66055e-08 9.49999ZM9 13H7V7.99999C7 7.73478 7.10536 7.48042 7.29289 7.29289C7.48043 7.10535 7.73478 6.99999 8 6.99999C8.26522 6.99999 8.51957 7.10535 8.70711 7.29289C8.89464 7.48042 9 7.73478 9 7.99999V13Z" fill="#3F3F46"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
3
packages/components-old/src/assets/file.svg
Normal file
3
packages/components-old/src/assets/file.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="8" height="10" viewBox="0 0 8 10" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M0.400024 1.40001C0.400024 1.08175 0.526453 0.776528 0.751496 0.551484C0.97654 0.32644 1.28176 0.200012 1.60002 0.200012H4.35162C4.66986 0.20008 4.97503 0.326553 5.20002 0.551612L7.24842 2.60001C7.47348 2.825 7.59996 3.13018 7.60002 3.44841V8.60001C7.60002 8.91827 7.4736 9.2235 7.24855 9.44854C7.02351 9.67358 6.71828 9.80001 6.40002 9.80001H1.60002C1.28176 9.80001 0.97654 9.67358 0.751496 9.44854C0.526453 9.2235 0.400024 8.91827 0.400024 8.60001V1.40001Z" fill="#828282"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 627 B |
3
packages/components-old/src/assets/files.svg
Normal file
3
packages/components-old/src/assets/files.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="16" height="14" viewBox="0 0 16 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M5 0C4.73478 0 4.48043 0.105357 4.29289 0.292893C4.10536 0.48043 4 0.734784 4 1C4 1.26522 4.10536 1.51957 4.29289 1.70711C4.48043 1.89464 4.73478 2 5 2H11C11.2652 2 11.5196 1.89464 11.7071 1.70711C11.8946 1.51957 12 1.26522 12 1C12 0.734784 11.8946 0.48043 11.7071 0.292893C11.5196 0.105357 11.2652 0 11 0H5ZM2 4C2 3.73478 2.10536 3.48043 2.29289 3.29289C2.48043 3.10536 2.73478 3 3 3H13C13.2652 3 13.5196 3.10536 13.7071 3.29289C13.8946 3.48043 14 3.73478 14 4C14 4.26522 13.8946 4.51957 13.7071 4.70711C13.5196 4.89464 13.2652 5 13 5H3C2.73478 5 2.48043 4.89464 2.29289 4.70711C2.10536 4.51957 2 4.26522 2 4ZM0 8C0 7.46957 0.210714 6.96086 0.585786 6.58579C0.960859 6.21071 1.46957 6 2 6H14C14.5304 6 15.0391 6.21071 15.4142 6.58579C15.7893 6.96086 16 7.46957 16 8V12C16 12.5304 15.7893 13.0391 15.4142 13.4142C15.0391 13.7893 14.5304 14 14 14H2C1.46957 14 0.960859 13.7893 0.585786 13.4142C0.210714 13.0391 0 12.5304 0 12V8Z" fill="#3F3F46"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
41
packages/components-old/src/components/blog/Post.js
Normal file
41
packages/components-old/src/components/blog/Post.js
Normal file
@@ -0,0 +1,41 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import parse from 'html-react-parser';
|
||||
|
||||
/**
|
||||
* Displays a blog post page
|
||||
* @param {object} props
|
||||
* post = {
|
||||
* title: <The title of the blog post>
|
||||
* content: <The body of the blog post. Can be plain text or html>
|
||||
* createdAt: <The utc date when the post was last modified>.
|
||||
* featuredImage: <Url/relative url to post cover image>
|
||||
* }
|
||||
* @returns
|
||||
*/
|
||||
const Post = ({ post }) => {
|
||||
const { title, content, createdAt, featuredImage } = post;
|
||||
|
||||
return (
|
||||
<>
|
||||
<h1 className="text-3xl font-semibold text-primary my-6 inline-block">
|
||||
{title}
|
||||
</h1>
|
||||
<p className="mb-6">Posted: {createdAt}</p>
|
||||
<img src={featuredImage} className="mb-6" alt="featured_img" />
|
||||
<div>{parse(content)}</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
Post.propTypes = {
|
||||
page: PropTypes.shape({
|
||||
title: PropTypes.string.isRequired,
|
||||
content: PropTypes.string.isRequired,
|
||||
createdAt: PropTypes.number,
|
||||
featuredImage: PropTypes.string,
|
||||
})
|
||||
}
|
||||
|
||||
export default Post;
|
||||
38
packages/components-old/src/components/blog/PostList.js
Normal file
38
packages/components-old/src/components/blog/PostList.js
Normal file
@@ -0,0 +1,38 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import parse from 'html-react-parser';
|
||||
|
||||
/**
|
||||
* Displays a list of blog posts with the title and a short excerp from the content.
|
||||
* @param {object} props
|
||||
* {
|
||||
* posts: {
|
||||
* title: <The title of the blog post>
|
||||
* excerpt: <A short excerpt from the post content>
|
||||
* }
|
||||
* }
|
||||
* @returns
|
||||
*/
|
||||
const PostList = ({ posts }) => {
|
||||
return (
|
||||
<>
|
||||
{posts.map((post, index) => (
|
||||
<div key={index}>
|
||||
<a
|
||||
href={`/blog/${post.slug}`}
|
||||
className="text-2xl font-semibold text-primary my-6 inline-block"
|
||||
>
|
||||
{parse(post.title)}
|
||||
</a>
|
||||
<p>{parse(post.excerpt)}</p>
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
PostList.propTypes = {
|
||||
posts: PropTypes.object.isRequired
|
||||
}
|
||||
|
||||
export default PostList;
|
||||
227
packages/components-old/src/components/dataset/DataExplorer.js
Normal file
227
packages/components-old/src/components/dataset/DataExplorer.js
Normal file
@@ -0,0 +1,227 @@
|
||||
import { DataGrid } from '@mui/x-data-grid';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useState } from 'react';
|
||||
import FileDownloadIcon from '@mui/icons-material/FileDownload';
|
||||
import InsertDriveFileIcon from '@mui/icons-material/InsertDriveFile';
|
||||
import FolderIcon from '@mui/icons-material/Folder';
|
||||
|
||||
/**
|
||||
* Opens a frictionless resource in data explorer. Data explorer gives you
|
||||
* an interface to interact with a resource. That means you can do things like
|
||||
* data filtering, sorting, e.t.c
|
||||
* @param resources: A array of frictionless datapackage resource
|
||||
*/
|
||||
const DataExplorer = ({ resources, columnHeaderStyle }) => {
|
||||
const [activeTable, setActiveTable] = useState(0);
|
||||
const [previewMode, setPreviewMode] = useState(true);
|
||||
|
||||
const handleTableNameClick = (index) => {
|
||||
setActiveTable(index);
|
||||
}
|
||||
|
||||
const getDataGridTable = (resource, columnHeaderStyle) => {
|
||||
return (
|
||||
<DataGrid
|
||||
sx={{
|
||||
'& .table-column-header-style-class': {
|
||||
backgroundColor: '#f5f5f5',
|
||||
color: 'black',
|
||||
...columnHeaderStyle,
|
||||
},
|
||||
}}
|
||||
key={resource.name}
|
||||
columns={generateColumns(resource)}
|
||||
rows={prepareRows(resource)}
|
||||
pageSize={5}
|
||||
rowsPerPageOptions={[5]}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const getDataGridSchema = (resource, columnHeaderStyle) => {
|
||||
return (
|
||||
<DataGrid
|
||||
sx={{
|
||||
'& .table-column-header-style-class': {
|
||||
backgroundColor: '#f5f5f5',
|
||||
color: 'black',
|
||||
...columnHeaderStyle,
|
||||
},
|
||||
}}
|
||||
key={resource.name}
|
||||
columns={generateSchemaColumns(resource)}
|
||||
rows={prepareSchemaRows(resource)}
|
||||
pageSize={5}
|
||||
rowsPerPageOptions={[5]}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='grid grid-cols-12' >
|
||||
<div className='col-span-3'>
|
||||
<div className='flex'>
|
||||
<FolderIcon />
|
||||
<h1 className="font-bold ml-3">
|
||||
Files
|
||||
</h1>
|
||||
</div>
|
||||
<div className='flex-col'>
|
||||
{
|
||||
resources.map((resource, i) => {
|
||||
return (
|
||||
<div key={`res@${i}`} className='flex'>
|
||||
<InsertDriveFileIcon className='ml-2' />
|
||||
<button className='ml-3 focus:outline-none' id={i} onClick={() => handleTableNameClick(i)}>
|
||||
{
|
||||
i === activeTable ? (
|
||||
<h3>{resource.name}.{resource.format}</h3>
|
||||
) : (
|
||||
<h3 className='text-gray-400'>{resource.name}.{resource.format}</h3>
|
||||
)
|
||||
}
|
||||
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div className='col-span-9 border-2'>
|
||||
<h1 className='font-bold ml-3 mb-2 capitalize'>{resources[activeTable].name}</h1>
|
||||
<div className='flex'>
|
||||
<div className='flex mr-3'>
|
||||
<a href={resources[activeTable].path} >
|
||||
<FileDownloadIcon className='ml-2' />
|
||||
</a>
|
||||
<span>
|
||||
{resources[activeTable].size ? (formatResourceSize(resources[activeTable].size)) : 'N/A'}
|
||||
</span>
|
||||
</div>
|
||||
<div className='mr-3 text-gray-500'>
|
||||
|
|
||||
</div>
|
||||
<div className='flex mr-3'>
|
||||
<span>
|
||||
{resources[activeTable].sample.length} rows
|
||||
</span>
|
||||
</div>
|
||||
<div className='mr-3 text-gray-500'>
|
||||
|
|
||||
</div>
|
||||
<div className='flex mr-3'>
|
||||
<span>
|
||||
{resources[activeTable].schema.fields.length} columns
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex mt-5 mb-4'>
|
||||
<button
|
||||
className={`${previewMode && 'font-bold underline'} ml-3 mr-5 focus:outline-none`}
|
||||
onClick={() => setPreviewMode(!previewMode)}
|
||||
>
|
||||
Preview
|
||||
</button>
|
||||
<button
|
||||
className={`${!previewMode && 'font-bold underline'} ml-3 mr-5 focus:outline-none`}
|
||||
onClick={() => setPreviewMode(!previewMode)}
|
||||
>
|
||||
Table Schema
|
||||
</button>
|
||||
</div>
|
||||
{
|
||||
previewMode && (
|
||||
<div className='ml-3' style={{ height: "370px" }}>
|
||||
{getDataGridTable(resources[activeTable], columnHeaderStyle)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
{
|
||||
!previewMode && (
|
||||
<div className='ml-3' style={{ height: "370px" }}>
|
||||
{getDataGridSchema(resources[activeTable], columnHeaderStyle)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
const generateColumns = (resource) => {
|
||||
return resource.schema?.fields.map((field) => {
|
||||
return {
|
||||
field: field.name,
|
||||
headerName: field.name,
|
||||
width: 150,
|
||||
description: field.description,
|
||||
headerClassName: 'table-column-header-style-class',
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const prepareRows = (resource) => {
|
||||
return resource.sample.map((row, i) => {
|
||||
row['id'] = i
|
||||
return row
|
||||
})
|
||||
}
|
||||
|
||||
const generateSchemaColumns = () => {
|
||||
return [
|
||||
{
|
||||
field: "name",
|
||||
headerName: "Field",
|
||||
flex: 0.5,
|
||||
description: "Field name",
|
||||
headerClassName: 'table-column-header-style-class',
|
||||
},
|
||||
{
|
||||
field: "type",
|
||||
headerName: "Type",
|
||||
width: 150,
|
||||
description: "Field type",
|
||||
headerClassName: 'table-column-header-style-class',
|
||||
},
|
||||
{
|
||||
field: "description",
|
||||
headerName: "Description",
|
||||
flex: 1,
|
||||
description: "Field description",
|
||||
headerClassName: 'table-column-header-style-class',
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
const prepareSchemaRows = (resource) => {
|
||||
return resource.schema?.fields.map((field, i) => {
|
||||
field['id'] = i
|
||||
return field
|
||||
});
|
||||
}
|
||||
|
||||
const formatResourceSize = (bytes) => {
|
||||
if (bytes < 1024) {
|
||||
return bytes + ' b';
|
||||
} else if (bytes < 1048576) {
|
||||
return (bytes / 1024).toFixed(2) + ' kb';
|
||||
} else if (bytes < 1073741824) {
|
||||
return (bytes / 1048576).toFixed(2) + ' mb';
|
||||
} else {
|
||||
return bytes
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
DataExplorer.propTypes = {
|
||||
resources: PropTypes.array.isRequired,
|
||||
}
|
||||
|
||||
export default DataExplorer
|
||||
|
||||
|
||||
|
||||
89
packages/components-old/src/components/dataset/KeyInfo.js
Normal file
89
packages/components-old/src/components/dataset/KeyInfo.js
Normal file
@@ -0,0 +1,89 @@
|
||||
import React from 'react';
|
||||
import filesize from 'filesize'
|
||||
import * as timeago from 'timeago.js';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
/**
|
||||
* KeyInfo component receives two arguments.
|
||||
* @param {Object} descriptor A Frictionless datapackage descriptor object with the following fields
|
||||
* @param {Array} resources A Frictionless datapackage resource array
|
||||
* @returns React Component
|
||||
*/
|
||||
const KeyInfo = ({ descriptor, resources }) => {
|
||||
const formats = resources.map(item => item.format).join(', ');
|
||||
|
||||
return (
|
||||
<>
|
||||
<section className="m-8" name="key-info" id="key-info">
|
||||
<h2 className="text-xl font-bold mb-4">Key info</h2>
|
||||
<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">Licenses</h3>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-1xl font-bold mb-2">Sources</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">{descriptor.size || 'N/A'}</h3>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-1xl">{formats}</h3>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-1xl">{descriptor.created && timeago.format(descriptor.created)}</h3>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-1xl">{descriptor.updated && timeago.format(descriptor.updated)}</h3>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-1xl">{
|
||||
descriptor.licenses?.length && (descriptor.licenses.map((item, index) => (
|
||||
<a className="text-yellow-600"
|
||||
href={item.path || '#'} title={item.title || ''}
|
||||
key={index}>
|
||||
{item.name}
|
||||
</a>
|
||||
)))
|
||||
}</h3>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-1xl">{
|
||||
descriptor.sources?.length && (descriptor.sources.map((item, index) => (
|
||||
<a className="text-yellow-600" href={item.path} key={index}>
|
||||
{item.title}
|
||||
</a>
|
||||
)))
|
||||
}</h3>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
KeyInfo.propTypes = {
|
||||
descriptor: PropTypes.object.isRequired,
|
||||
resources: PropTypes.array.isRequired
|
||||
}
|
||||
export default KeyInfo
|
||||
45
packages/components-old/src/components/dataset/Org.js
Normal file
45
packages/components-old/src/components/dataset/Org.js
Normal file
@@ -0,0 +1,45 @@
|
||||
import Link from 'next/link';
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
/**
|
||||
* Displays information about an organization in a dataset page
|
||||
* @param {Object} props object describing the dataset organization.
|
||||
* organization: {
|
||||
* image_url: The image url of the organization
|
||||
* name: The name of the organization
|
||||
* title: The title of the organization
|
||||
* }
|
||||
* @returns
|
||||
*/
|
||||
const Org = ({ organization }) => {
|
||||
return (
|
||||
<>
|
||||
{organization ? (
|
||||
<>
|
||||
<img
|
||||
src={
|
||||
organization.image_url ||
|
||||
'https://datahub.io/static/img/datahub-cube-edited.svg'
|
||||
}
|
||||
className="h-5 w-5 mr-2 inline-block"
|
||||
alt="org_img"
|
||||
/>
|
||||
<Link href={`/@${organization.name}`}>
|
||||
<a className="font-semibold text-primary underline">
|
||||
{organization.title || organization.name}
|
||||
</a>
|
||||
</Link>
|
||||
</>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
Org.propTypes = {
|
||||
organization: PropTypes.object.isRequired
|
||||
}
|
||||
|
||||
export default Org;
|
||||
25
packages/components-old/src/components/dataset/Readme.js
Normal file
25
packages/components-old/src/components/dataset/Readme.js
Normal file
@@ -0,0 +1,25 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
/**
|
||||
* ReadMe component displays the markdown description of a datapackage
|
||||
* @param {string} readme parsed html of data package readme
|
||||
* @returns React Component
|
||||
*/
|
||||
const ReadMe = ({ readme }) => {
|
||||
return (
|
||||
<>
|
||||
<section className="m-8" name="sample-table">
|
||||
<div className="prose">
|
||||
<div dangerouslySetInnerHTML={{ __html: readme }} />
|
||||
</div>
|
||||
</section>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
ReadMe.propTypes = {
|
||||
readme: PropTypes.string.isRequired
|
||||
}
|
||||
|
||||
export default ReadMe
|
||||
@@ -0,0 +1,74 @@
|
||||
import React from 'react';
|
||||
import filesize from 'filesize'
|
||||
import * as timeago from 'timeago.js';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
/**
|
||||
* 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">
|
||||
<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">Created</h3>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-1xl font-bold mb-2">Updated</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.title || resource.name}</h3>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-1xl">{resource.description || "No description"}</h3>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-1xl">{resource.size ? filesize(resource.size, { bits: true }) : 0}</h3>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-1xl">{resource.created && timeago.format(resource.created)}</h3>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-1xl">{resource.updated && timeago.format(resource.updated)}</h3>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-1xl">
|
||||
{/* We assume that resource.path is a URL but not relative path. */}
|
||||
<a className="text-yellow-600" href={resource.path}>
|
||||
{resource.format}
|
||||
</a>
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</section>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
ResourcesInfo.propTypes = {
|
||||
resources: PropTypes.array.isRequired
|
||||
}
|
||||
export default ResourcesInfo
|
||||
27
packages/components-old/src/components/misc/CustomLink.js
Normal file
27
packages/components-old/src/components/misc/CustomLink.js
Normal file
@@ -0,0 +1,27 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
/**
|
||||
* Creates a custom link with title
|
||||
* @param {object} props
|
||||
* {
|
||||
* url: The url of the custom link
|
||||
* title: The title for the custom link
|
||||
* }
|
||||
* @returns React Component
|
||||
*/
|
||||
const CustomLink = ({ url, title }) => (
|
||||
<a
|
||||
href={url}
|
||||
className="bg-white hover:bg-gray-200 border text-black font-semibold py-2 px-4 rounded"
|
||||
>
|
||||
{title}
|
||||
</a>
|
||||
);
|
||||
|
||||
CustomLink.propTypes = {
|
||||
url: PropTypes.string.isRequired,
|
||||
title: PropTypes.string.isRequired
|
||||
}
|
||||
|
||||
export default CustomLink;
|
||||
32
packages/components-old/src/components/misc/Error.js
Normal file
32
packages/components-old/src/components/misc/Error.js
Normal file
@@ -0,0 +1,32 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
/**
|
||||
* Error message component with consistent portal style
|
||||
* @param {object} props
|
||||
* {
|
||||
* message: The error message to display
|
||||
* }
|
||||
* @returns
|
||||
*/
|
||||
const ErrorMessage = ({ message }) => {
|
||||
return (
|
||||
<aside>
|
||||
{message}
|
||||
<style jsx>{`
|
||||
aside {
|
||||
padding: 1.5em;
|
||||
font-size: 14px;
|
||||
color: white;
|
||||
background-color: red;
|
||||
}
|
||||
`}</style>
|
||||
</aside>
|
||||
);
|
||||
};
|
||||
|
||||
ErrorMessage.propTypes = {
|
||||
message: PropTypes.string.isRequired
|
||||
}
|
||||
|
||||
export default ErrorMessage;
|
||||
45
packages/components-old/src/components/search/Form.js
Normal file
45
packages/components-old/src/components/search/Form.js
Normal file
@@ -0,0 +1,45 @@
|
||||
import React, { useState } from 'react'
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
/**
|
||||
* Search component form that can be customized with change and submit handlers
|
||||
* @param {object} props
|
||||
* {
|
||||
* handleChange: A form input change event handler. This function is executed when the
|
||||
* search input or order by input changes.
|
||||
* handleSubmit: A form submit event handler. This function is executed when the
|
||||
* search form is submitted.
|
||||
* }
|
||||
* @returns
|
||||
*/
|
||||
const Form = ({ handleSubmit }) => {
|
||||
const [searchQuery, setSearchQuery] = useState("")
|
||||
return (
|
||||
<form onSubmit={(e) => e.preventDefault()} className="items-center">
|
||||
<div className="flex">
|
||||
<input
|
||||
type="text"
|
||||
name="search#q"
|
||||
value={searchQuery}
|
||||
onChange={(e) => { setSearchQuery(e.target.value) }}
|
||||
placeholder="Search"
|
||||
aria-label="Search"
|
||||
className="bg-white focus:outline-none focus:shadow-outline border border-gray-300 w-1/2 rounded-lg py-2 px-4 block appearance-none leading-normal"
|
||||
/>
|
||||
<button
|
||||
onClick={() => handleSubmit(searchQuery)}
|
||||
type="button"
|
||||
className="inline-block text-sm px-4 py-3 mx-3 leading-none border rounded text-white bg-black border-black lg:mt-0"
|
||||
>
|
||||
Search
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
Form.propTypes = {
|
||||
handleSubmit: PropTypes.func.isRequired
|
||||
}
|
||||
|
||||
export default Form;
|
||||
55
packages/components-old/src/components/search/Item.js
Normal file
55
packages/components-old/src/components/search/Item.js
Normal file
@@ -0,0 +1,55 @@
|
||||
import React from 'react'
|
||||
import Link from 'next/link';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
/**
|
||||
* Single item from a search result showing info about a dataset.
|
||||
* @param {object} props data package with the following format:
|
||||
* {
|
||||
* organization: {name: <some name>, title: <some title> },
|
||||
* title: <Data package title>
|
||||
* name: <Data package name>
|
||||
* description: <description of data package>
|
||||
* notes: <Notes associated with the data package>
|
||||
* }
|
||||
* @returns React Component
|
||||
*/
|
||||
const Item = ({ dataset }) => {
|
||||
return (
|
||||
<div className="mb-6">
|
||||
<h3 className="text-xl font-semibold">
|
||||
<Link
|
||||
href={`/@${
|
||||
dataset.organization
|
||||
? dataset.organization.name
|
||||
: 'dataset'
|
||||
}/${dataset.name}`}
|
||||
>
|
||||
<a className="text-primary">
|
||||
{dataset.title || dataset.name}
|
||||
</a>
|
||||
</Link>
|
||||
</h3>
|
||||
<Link
|
||||
href={`/@${
|
||||
dataset.organization ? dataset.organization.name : 'dataset'
|
||||
}`}
|
||||
>
|
||||
<a className="text-gray-500 block mt-1">
|
||||
{dataset.organization
|
||||
? dataset.organization.title
|
||||
: 'dataset'}
|
||||
</a>
|
||||
</Link>
|
||||
<div className="leading-relaxed mt-2">
|
||||
{dataset.description || dataset.notes}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Item.propTypes = {
|
||||
dataset: PropTypes.object.isRequired
|
||||
}
|
||||
|
||||
export default Item;
|
||||
25
packages/components-old/src/components/search/Total.js
Normal file
25
packages/components-old/src/components/search/Total.js
Normal file
@@ -0,0 +1,25 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
/**
|
||||
* Displays the total search result
|
||||
* @param {object} props
|
||||
* {
|
||||
* count: The total number of search results
|
||||
* }
|
||||
* @returns React Component
|
||||
*/
|
||||
const Total = ({ count }) => {
|
||||
|
||||
return (
|
||||
<h1 className="text-3xl font-semibold text-primary my-6 inline-block">
|
||||
{count} results found
|
||||
</h1>
|
||||
);
|
||||
};
|
||||
|
||||
Total.propTypes = {
|
||||
count: PropTypes.number.isRequired
|
||||
}
|
||||
|
||||
export default Total;
|
||||
59
packages/components-old/src/components/ui/Nav.js
Normal file
59
packages/components-old/src/components/ui/Nav.js
Normal file
@@ -0,0 +1,59 @@
|
||||
import React from 'react'
|
||||
import Link from 'next/link';
|
||||
import { useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
/**
|
||||
* Displays a navigation bar with logo and menu links
|
||||
* @param {Object} props object with the following properties:
|
||||
* {
|
||||
* logo: The relative url to the logo image
|
||||
* navMenu: An array of objects with menu items. E.g : [{ title: 'Blog', path: '/blog' },{ title: 'Search', path: '/search' }]
|
||||
* }
|
||||
* @returns React Component
|
||||
*/
|
||||
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">
|
||||
<Link href="/"><img src={logo} alt="portal logo" width="40" /></Link>
|
||||
</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) => {
|
||||
return (<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>)
|
||||
})}
|
||||
</div>
|
||||
</nav>
|
||||
);
|
||||
};
|
||||
|
||||
Nav.propTypes = {
|
||||
logo: PropTypes.string.isRequired,
|
||||
navMenu: PropTypes.array.isRequired
|
||||
}
|
||||
export default Nav;
|
||||
49
packages/components-old/src/components/ui/Recent.js
Normal file
49
packages/components-old/src/components/ui/Recent.js
Normal file
@@ -0,0 +1,49 @@
|
||||
import React from 'react';
|
||||
import Link from 'next/link';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
/**
|
||||
* Displays a list of recent datasets
|
||||
* @param {array} props An array of datasets
|
||||
* { datasets = [{
|
||||
* organization: {name: <some name>, title: <some title> },
|
||||
* title: <Data package title>
|
||||
* name: <Data package name>
|
||||
* description: <description of data package>
|
||||
* }]
|
||||
* }
|
||||
* @returns React Component
|
||||
*/
|
||||
const Recent = ({datasets}) => {
|
||||
|
||||
return (
|
||||
<section className="my-10 mx-4 lg:my-20">
|
||||
<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>
|
||||
);
|
||||
};
|
||||
|
||||
Recent.propTypes = {
|
||||
datasets: PropTypes.array.isRequired
|
||||
}
|
||||
|
||||
export default Recent;
|
||||
34
packages/components-old/src/components/views/Chart.js
vendored
Normal file
34
packages/components-old/src/components/views/Chart.js
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import createPlotlyComponent from "react-plotly.js/factory";
|
||||
let Plot;
|
||||
|
||||
const PlotlyChart = ({spec}) => {
|
||||
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 {...spec}
|
||||
layout={{ autosize: true }}
|
||||
style={{ width: "100%", height: "100%" }}
|
||||
useResizeHandler={true}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
PlotlyChart.propTypes = {
|
||||
spec: PropTypes.object.isRequired
|
||||
}
|
||||
export { PlotlyChart }
|
||||
28
packages/components-old/src/components/views/Table.js
Normal file
28
packages/components-old/src/components/views/Table.js
Normal file
@@ -0,0 +1,28 @@
|
||||
import React from 'react';
|
||||
import { DataGrid } from '@mui/x-data-grid';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
/**
|
||||
* Displays dataset in tabular form using data grid
|
||||
* @param columns: An array of column names with properties: e.g [{field: "col1", headerName: "col1"}, {field: "col2", headerName: "col2"}]
|
||||
* @param data: an array of data objects e.g. [ {col1: 1, col2: 2}, {col1: 5, col2: 7} ]
|
||||
*/
|
||||
const Table = ({ columns, data, height, width }) => {
|
||||
let rows = [...data,]
|
||||
rows = rows.map((row, i) => {
|
||||
row['id'] = i
|
||||
return row
|
||||
})
|
||||
return (
|
||||
<div style={{ height, width }} data-testid="tableGrid">
|
||||
<DataGrid rows={rows} columns={columns} pageSize={5} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Table.propTypes = {
|
||||
columns: PropTypes.array.isRequired,
|
||||
data: PropTypes.array.isRequired
|
||||
}
|
||||
|
||||
export default Table
|
||||
46
packages/components-old/src/index.ts
Normal file
46
packages/components-old/src/index.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
//View components
|
||||
import Table from './components/views/Table'
|
||||
import { PlotlyChart } from './components/views/Chart'
|
||||
|
||||
//Dataset components
|
||||
import KeyInfo from './components/dataset/KeyInfo'
|
||||
import ResourceInfo from './components/dataset/ResourceInfo'
|
||||
import ReadMe from './components/dataset/Readme'
|
||||
import DataExplorer from './components/dataset/DataExplorer'
|
||||
import Org from './components/dataset/Org'
|
||||
|
||||
//Blog components
|
||||
import Post from './components/blog/Post'
|
||||
import PostList from './components/blog/PostList'
|
||||
|
||||
//Misc components
|
||||
import Error from './components/misc/Error'
|
||||
import CustomLink from './components/misc/CustomLink'
|
||||
|
||||
//UI components
|
||||
import Nav from './components/ui/Nav'
|
||||
import Recent from './components/ui/Recent'
|
||||
|
||||
//UI components
|
||||
import Form from './components/search/Form'
|
||||
import Item from './components/search/Item'
|
||||
import ItemTotal from './components/search/Total'
|
||||
|
||||
export {
|
||||
Table,
|
||||
PlotlyChart,
|
||||
KeyInfo,
|
||||
ResourceInfo,
|
||||
ReadMe,
|
||||
Nav,
|
||||
Recent,
|
||||
DataExplorer,
|
||||
Post,
|
||||
PostList,
|
||||
Org,
|
||||
Error,
|
||||
CustomLink,
|
||||
Form,
|
||||
Item,
|
||||
ItemTotal
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
/*
|
||||
* Replace this with your own classes
|
||||
*
|
||||
* e.g.
|
||||
* .container {
|
||||
* }
|
||||
*/
|
||||
10
packages/components-old/src/lib/portaljs-components.spec.tsx
Normal file
10
packages/components-old/src/lib/portaljs-components.spec.tsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import { render } from '@testing-library/react';
|
||||
|
||||
import PortaljsComponents from './portaljs-components';
|
||||
|
||||
describe('PortaljsComponents', () => {
|
||||
it('should render successfully', () => {
|
||||
const { baseElement } = render(<PortaljsComponents />);
|
||||
expect(baseElement).toBeTruthy();
|
||||
});
|
||||
});
|
||||
14
packages/components-old/src/lib/portaljs-components.tsx
Normal file
14
packages/components-old/src/lib/portaljs-components.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import styles from './portaljs-components.module.css';
|
||||
|
||||
/* eslint-disable-next-line */
|
||||
export interface PortaljsComponentsProps {}
|
||||
|
||||
export function PortaljsComponents(props: PortaljsComponentsProps) {
|
||||
return (
|
||||
<div className={styles['container']}>
|
||||
<h1>Welcome to PortaljsComponents!</h1>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default PortaljsComponents;
|
||||
Reference in New Issue
Block a user