From 6d21042911b29b4a1d02f5a3c89bae417b4afe09 Mon Sep 17 00:00:00 2001 From: Rising Odegua Date: Mon, 3 May 2021 15:18:20 +0100 Subject: [PATCH 1/8] [Components][s]: Add components for creating blog page --- src/components/blog/Post.js | 41 +++++++++++++++++++++++++++++++++ src/components/blog/PostList.js | 41 +++++++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+) create mode 100644 src/components/blog/Post.js create mode 100644 src/components/blog/PostList.js diff --git a/src/components/blog/Post.js b/src/components/blog/Post.js new file mode 100644 index 00000000..e979d679 --- /dev/null +++ b/src/components/blog/Post.js @@ -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 + * { + * title: + * content: + * modified: { + const { title, content, modified, featured_image } = page; + + return ( + <> +

+ {title} +

+

Edited: {modified}

+ featured_img +
{parse(content)}
+ + ); +}; + + +Post.propTypes = { + page: PropTypes.shape({ + title: PropTypes.string.isRequired, + content: PropTypes.string.isRequired, + modified: PropTypes.string, + featured_image: PropTypes.string, + }) +} + +export default Post; diff --git a/src/components/blog/PostList.js b/src/components/blog/PostList.js new file mode 100644 index 00000000..d641d2ce --- /dev/null +++ b/src/components/blog/PostList.js @@ -0,0 +1,41 @@ +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: + * excerpt: + * } + * } + * @returns + */ +const PostList = ({ posts }) => { + return ( + <> +

+ {posts.length} posts found +

+ {posts.map((post, index) => ( +
+ + {parse(post.title)} + +

{parse(post.excerpt)}

+
+ ))} + + ); +}; + +PostList.propTypes = { + posts: PropTypes.object.isRequired +} + +export default PostList; From acd1c118a1c424c222ed20ca292a771609dcf74f Mon Sep 17 00:00:00 2001 From: Rising Odegua Date: Mon, 3 May 2021 15:18:49 +0100 Subject: [PATCH 2/8] [Components][s]: Add components for creating dataset page --- src/components/dataset/DataExplorer.js | 19 +++++ src/components/dataset/KeyInfo.js | 99 ++++++++++++++++++++++++++ src/components/dataset/Org.js | 45 ++++++++++++ src/components/dataset/Readme.js | 26 +++++++ src/components/dataset/ResourceInfo.js | 67 +++++++++++++++++ 5 files changed, 256 insertions(+) create mode 100644 src/components/dataset/DataExplorer.js create mode 100644 src/components/dataset/KeyInfo.js create mode 100644 src/components/dataset/Org.js create mode 100644 src/components/dataset/Readme.js create mode 100644 src/components/dataset/ResourceInfo.js diff --git a/src/components/dataset/DataExplorer.js b/src/components/dataset/DataExplorer.js new file mode 100644 index 00000000..5ce7db18 --- /dev/null +++ b/src/components/dataset/DataExplorer.js @@ -0,0 +1,19 @@ +import React from 'react' +import PropTypes from 'prop-types'; + +/** + * 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 {object} resource A frictionless Data resource + * @returns React component + */ +const DataExplorer = ({ resource }) => { + // TODO: Add data explorer code + return <>{JSON.stringify(resource)}; +}; + +DataExplorer.propTypes = { + resource: PropTypes.object.isRequired +} +export default DataExplorer; diff --git a/src/components/dataset/KeyInfo.js b/src/components/dataset/KeyInfo.js new file mode 100644 index 00000000..07139469 --- /dev/null +++ b/src/components/dataset/KeyInfo.js @@ -0,0 +1,99 @@ +import React from 'react'; +import filesize from 'filesize' +import PropTypes from 'prop-types'; + +/** + * 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 ( + <> +
+

+ {descriptor.title} +

+

Key info

+
+
+

Files

+
+
+

Size

+
+
+

Format

+
+
+

Created

+
+
+

Updated

+
+
+

Licence

+
+
+

Source

+
+
+
+
+

{resources.length}

+
+
+

{filesize(datasetSize, { bits: true })}

+
+
+

{resources[0].format} zip

+
+
+

{descriptor.created}

+
+
+

{descriptor.updated}

+
+
+

{descriptor.license}

+
+ +
+
+ + + ) +} + +KeyInfo.propTypes = { + descriptor: PropTypes.object.isRequired, + resources: PropTypes.array.isRequired +} +export default KeyInfo \ No newline at end of file diff --git a/src/components/dataset/Org.js b/src/components/dataset/Org.js new file mode 100644 index 00000000..c5b5c414 --- /dev/null +++ b/src/components/dataset/Org.js @@ -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 ? ( + <> + org_img + + + {organization.title || organization.name} + + + + ) : ( + '' + )} + + ); +}; + +Org.propTypes = { + organization: PropTypes.object.isRequired +} + +export default Org; diff --git a/src/components/dataset/Readme.js b/src/components/dataset/Readme.js new file mode 100644 index 00000000..14ae9dbc --- /dev/null +++ b/src/components/dataset/Readme.js @@ -0,0 +1,26 @@ +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 = ({ readmeHtml }) => { + return ( + <> +
+

README

+
+
+
+
+ + ) +} + +ReadMe.propTypes = { + readmeHtml: PropTypes.string.isRequired +} + +export default ReadMe \ No newline at end of file diff --git a/src/components/dataset/ResourceInfo.js b/src/components/dataset/ResourceInfo.js new file mode 100644 index 00000000..e1c9f583 --- /dev/null +++ b/src/components/dataset/ResourceInfo.js @@ -0,0 +1,67 @@ +import React from 'react'; +import filesize from 'filesize' +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 ( + <> +
+

Data Files

+
+
+

File

+
+
+

Description

+
+
+

Size

+
+
+

Last Changed

+
+
+

Download

+
+
+ + {resources.map((resource, index) => { + return ( +
+
+

{resource.name}

+
+
+

{resource.description || "No description"}

+
+
+

{filesize(resource.size, { bits: true })}

+
+
+

{resource.updated}

+
+ +
+ ) + })} +
+ + ) +} + + +ResourcesInfo.propTypes = { + resources: PropTypes.array.isRequired +} +export default ResourcesInfo \ No newline at end of file From 2ffad0d80ba3202eb511c8bb654ce8a44cdeff42 Mon Sep 17 00:00:00 2001 From: Rising Odegua Date: Mon, 3 May 2021 15:19:19 +0100 Subject: [PATCH 3/8] [Components][s]: Re-arrange components folder --- src/components/page/KeyInfo.js | 94 ----------------------------- src/components/page/Readme.js | 21 ------- src/components/page/ResourceInfo.js | 62 ------------------- src/components/ui/Home/Nav.js | 70 --------------------- src/components/ui/Home/Recent.js | 37 ------------ src/components/ui/index.js | 0 6 files changed, 284 deletions(-) delete mode 100644 src/components/page/KeyInfo.js delete mode 100644 src/components/page/Readme.js delete mode 100644 src/components/page/ResourceInfo.js delete mode 100644 src/components/ui/Home/Nav.js delete mode 100644 src/components/ui/Home/Recent.js delete mode 100644 src/components/ui/index.js diff --git a/src/components/page/KeyInfo.js b/src/components/page/KeyInfo.js deleted file mode 100644 index a464a99d..00000000 --- a/src/components/page/KeyInfo.js +++ /dev/null @@ -1,94 +0,0 @@ -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 ( - <> -
-

- {descriptor.title} -

-

Key info

-
-
-

Files

-
-
-

Size

-
-
-

Format

-
-
-

Created

-
-
-

Updated

-
-
-

Licence

-
-
-

Source

-
-
-
-
-

{resources.length}

-
-
-

{filesize(datasetSize, { bits: true })}

-
-
-

{resources[0].format} zip

-
-
-

{descriptor.created}

-
-
-

{descriptor.updated}

-
-
-

{descriptor.license}

-
- -
-
- - - ) -} - -export default KeyInfo \ No newline at end of file diff --git a/src/components/page/Readme.js b/src/components/page/Readme.js deleted file mode 100644 index e3573467..00000000 --- a/src/components/page/Readme.js +++ /dev/null @@ -1,21 +0,0 @@ -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 ( - <> -
-

README

-
-
-
-
- - ) -} - -export default ReadMe \ No newline at end of file diff --git a/src/components/page/ResourceInfo.js b/src/components/page/ResourceInfo.js deleted file mode 100644 index 94df28df..00000000 --- a/src/components/page/ResourceInfo.js +++ /dev/null @@ -1,62 +0,0 @@ -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 ( - <> -
-

Data Files

-
-
-

File

-
-
-

Description

-
-
-

Size

-
-
-

Last Changed

-
-
-

Download

-
-
- - {resources.map((resource, index) => { - return ( -
-
-

{resource.name}

-
-
-

{resource.description || "No description"}

-
-
-

{filesize(resource.size, { bits: true })}

-
-
-

{resource.updated}

-
- -
- ) - })} -
- - ) -} - -export default ResourcesInfo \ No newline at end of file diff --git a/src/components/ui/Home/Nav.js b/src/components/ui/Home/Nav.js deleted file mode 100644 index cde83bcf..00000000 --- a/src/components/ui/Home/Nav.js +++ /dev/null @@ -1,70 +0,0 @@ -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 ( - - ); -}; - -export default Nav; diff --git a/src/components/ui/Home/Recent.js b/src/components/ui/Home/Recent.js deleted file mode 100644 index c1c0b436..00000000 --- a/src/components/ui/Home/Recent.js +++ /dev/null @@ -1,37 +0,0 @@ -import React from 'react'; -import Link from 'next/link'; - -const Recent = (datasets) => { - if (!datasets) { - return <> - } - - return ( -
-

Recent Datasets

-
- {datasets.map((dataset, index) => ( -
-

{dataset.title}

-

- {dataset.organization && dataset.organization.description} -

- - - View Dataset - - -
- ))} -
-
- ); -}; - -export default Recent; diff --git a/src/components/ui/index.js b/src/components/ui/index.js deleted file mode 100644 index e69de29b..00000000 From 44cdced1a6e407a159f49bc423a96bdca7484c7a Mon Sep 17 00:00:00 2001 From: Rising Odegua Date: Mon, 3 May 2021 15:19:33 +0100 Subject: [PATCH 4/8] [Components][s]: Add components for creating search page --- src/components/search/Form.js | 62 ++++++++++++++++++++++++++++++++++ src/components/search/Item.js | 55 ++++++++++++++++++++++++++++++ src/components/search/Total.js | 25 ++++++++++++++ 3 files changed, 142 insertions(+) create mode 100644 src/components/search/Form.js create mode 100644 src/components/search/Item.js create mode 100644 src/components/search/Total.js diff --git a/src/components/search/Form.js b/src/components/search/Form.js new file mode 100644 index 00000000..61b4aeda --- /dev/null +++ b/src/components/search/Form.js @@ -0,0 +1,62 @@ +import React 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 = ({ handleChange, handleSubmit }) => { + + return ( +
+
+ + +
+
+ + +
+
+ ); +}; + +Form.propTypes = { + handleChange: PropTypes.func.isRequired, + handleSubmit: PropTypes.func.isRequired +} + +export default Form; diff --git a/src/components/search/Item.js b/src/components/search/Item.js new file mode 100644 index 00000000..1d47183b --- /dev/null +++ b/src/components/search/Item.js @@ -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: , title: }, + * title: + * name: + * description: + * notes: + * } + * @returns React Component + */ +const Item = ({ dataset }) => { + return ( + + ); +}; + +Item.propTypes = { + dataset: PropTypes.object.isRequired +} + +export default Item; diff --git a/src/components/search/Total.js b/src/components/search/Total.js new file mode 100644 index 00000000..6bf83e17 --- /dev/null +++ b/src/components/search/Total.js @@ -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 ( +

+ {count} results found +

+ ); +}; + +Total.propTypes = { + count: PropTypes.number.isRequired +} + +export default Total; From 98f94303c416acbf56b2b82babad4415c650f87c Mon Sep 17 00:00:00 2001 From: Rising Odegua Date: Mon, 3 May 2021 15:20:17 +0100 Subject: [PATCH 5/8] [Components][s]: Update UI components to include validation --- src/components/ui/Nav.js | 59 +++++++++++++++++++++++++++++++++++++ src/components/ui/Recent.js | 51 ++++++++++++++++++++++++++++++++ 2 files changed, 110 insertions(+) create mode 100644 src/components/ui/Nav.js create mode 100644 src/components/ui/Recent.js diff --git a/src/components/ui/Nav.js b/src/components/ui/Nav.js new file mode 100644 index 00000000..21577d82 --- /dev/null +++ b/src/components/ui/Nav.js @@ -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.propTypes = { + logo: PropTypes.string.isRequired, + navMenu: PropTypes.string +} +export default Nav; diff --git a/src/components/ui/Recent.js b/src/components/ui/Recent.js new file mode 100644 index 00000000..665d0201 --- /dev/null +++ b/src/components/ui/Recent.js @@ -0,0 +1,51 @@ +import React from 'react'; +import Link from 'next/link'; +import PropTypes from 'prop-types'; + +/** + * Displays a list of recent datasets + * @param {object} props + * datasets = { + * organization: {name: , title: }, + * title: + * name: + * description: + * notes: + * } + * @returns React Component + */ +const Recent = ({datasets}) => { + + return ( +
+

Recent Datasets

+
+ {datasets.map((dataset, index) => ( +
+

{dataset.title}

+

+ {dataset.organization && dataset.organization.description} +

+ + + View Dataset + + +
+ ))} +
+
+ ); +}; + +Recent.propTypes = { + datasets: PropTypes.object.isRequired +} + +export default Recent; From fc79f3f7fa695e38da0e547791c9c3b72af3e4dd Mon Sep 17 00:00:00 2001 From: Rising Odegua Date: Mon, 3 May 2021 15:20:30 +0100 Subject: [PATCH 6/8] [Components][s]: Update views components to include validation --- src/components/views/Chart.js | 8 ++++++-- src/components/views/Table.js | 26 ++++++++++---------------- 2 files changed, 16 insertions(+), 18 deletions(-) diff --git a/src/components/views/Chart.js b/src/components/views/Chart.js index 188b8e70..687edb8c 100644 --- a/src/components/views/Chart.js +++ b/src/components/views/Chart.js @@ -1,8 +1,9 @@ import React, { useState, useEffect } from 'react'; +import PropTypes from 'prop-types'; import createPlotlyComponent from "react-plotly.js/factory"; let Plot; -const PlotlyChart = (props) => { +const PlotlyChart = ({spec}) => { const [plotCreated, setPlotCreated] = useState(0) //0: false, 1: true useEffect(() => { @@ -18,7 +19,7 @@ const PlotlyChart = (props) => { return (
- { ) } +PlotlyChart.propTypes = { + spec: PropTypes.object.isRequired +} export { PlotlyChart } \ No newline at end of file diff --git a/src/components/views/Table.js b/src/components/views/Table.js index 2de9afc1..b426473b 100644 --- a/src/components/views/Table.js +++ b/src/components/views/Table.js @@ -1,24 +1,13 @@ import React from 'react'; import { DataGrid } from '@material-ui/data-grid'; +import PropTypes from 'prop-types'; /** - * 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} ] + * 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 = ({ 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 - }) - +const Table = ({ columns, data }) => { return (
@@ -26,4 +15,9 @@ const Table = ({ schema, data }) => { ); } +Table.propTypes = { + columns: PropTypes.array.isRequired, + data: PropTypes.array.isRequired +} + export default Table From bd4e7cf79e4fce25df00ebabe6100cdb01f33aeb Mon Sep 17 00:00:00 2001 From: Rising Odegua Date: Mon, 3 May 2021 15:20:50 +0100 Subject: [PATCH 7/8] [Components][s]: Add misc components --- src/components/misc/CustomLink.js | 27 ++++++++++++++++++++++++++ src/components/misc/Error.js | 32 +++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+) create mode 100644 src/components/misc/CustomLink.js create mode 100644 src/components/misc/Error.js diff --git a/src/components/misc/CustomLink.js b/src/components/misc/CustomLink.js new file mode 100644 index 00000000..686dfdfd --- /dev/null +++ b/src/components/misc/CustomLink.js @@ -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 }) => ( + + {title} + +); + +CustomLink.propTypes = { + url: PropTypes.string.isRequired, + title: PropTypes.string.isRequired +} + +export default CustomLink; diff --git a/src/components/misc/Error.js b/src/components/misc/Error.js new file mode 100644 index 00000000..39eed32b --- /dev/null +++ b/src/components/misc/Error.js @@ -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 ( + + ); +}; + +ErrorMessage.propTypes = { + message: PropTypes.string.isRequired +} + +export default ErrorMessage; From 3faf4f14642edbea911d7e53850c293aa38bb968 Mon Sep 17 00:00:00 2001 From: Rising Odegua Date: Mon, 3 May 2021 15:56:59 +0100 Subject: [PATCH 8/8] [Index][s]: Export new components --- src/index.js | 42 +++++++++++++++++++++++++++++++++++------- 1 file changed, 35 insertions(+), 7 deletions(-) diff --git a/src/index.js b/src/index.js index 7b9f794a..99f31e39 100644 --- a/src/index.js +++ b/src/index.js @@ -1,10 +1,38 @@ -//Components +//View 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 } \ No newline at end of file +//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' + +export { + Table, + PlotlyChart, + KeyInfo, + ResourceInfo, + ReadMe, + Nav, + Recent, + DataExplorer, + Post, + PostList, + Org, + Error, + CustomLink +} \ No newline at end of file