[#812,package][xl]: initial versioning of the package
This commit is contained in:
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
|
||||
Reference in New Issue
Block a user