[monorepo][m] - restructuring

- renamed apps to examples
- renamed libs to packages
- fixed data-literate build
This commit is contained in:
Luccas Mateus de Medeiros Gomes 2023-04-11 13:23:53 -03:00
parent ca3eccad86
commit b2438e655f
161 changed files with 6997 additions and 4524 deletions

83
DESIGN.md Normal file
View File

@ -0,0 +1,83 @@
# Design Notes
## Roadmap
General comment: let's do "README" (docs) driven development here.
* [x] [show] Local functionality for Frictionless datasets with CSV #528
* [x] Move in new work (portal-experiment) into portal.js and refactor https://github.com/datopian/portal.js.bak/issues/59
* [ ] [show] Uber Epic covering all functionality **See below**
* [ ] [show] README only + data datasets (dont have to be frictionless)
* (?) Graphs direct in README with say visdown …
* [ ] [show] SQL interface to the data (alasql or sql.js … https://github.com/agershun/alasql/wiki/Performance-Tests)
* [ ] file/resource subpages ... (for datasets with lots of resources)
* [ ] Docs **80% analysed** #
* [ ] Create portal components and library i.e. have a Table, Graph, Dataset component
* [ ] publish to @datopian/portal
* [ ] Examples
* [ ] Catalog functionality **20% analysed**
## [uber][epic] Show functionality for single datasets
### Features
* Elegant
* Description (README/Description)
* Data preview and exploration (for tablular)
* Basic: some sample data shown
* Data exploration v1: filterable
* Data Exploration v2: can do sql etc ...
* Graphs / visualization
* Validation: this row does not match schema in column X
* Summarization e.g. this columns has this range of values, this average value, this number of nulls
### Dataset structure support (in rough order of priority / like implementation)
* Frictionless
* Plain README (with frontmatter)
* README (no frontmatter) and LICENSE file (?)
Data has roughly two dimensions that are relevant
* Format
* CSV
* xlsx
* JSON
* ...
* Size
* Small: < 5mb (can just load inline ...)
* Medium < 100mb
* Large < 5Gb
* xlarge > 5Gb
* TODO: How does show/build work with remote files e.g. a resource ...
```
path: abc.csv
remote_storage_url: s3://.../.../.../
```
Options:
* We clone the data into path locally ...
* Possible problem if data is big ...
* Load data direct from remote_storage_url (as long as supports CORs)
## Architecture
Portal.js is a React and NextJS based framework for building dataset/resources pages and catalogs. It consists of:
* React components for data portal functionality e.g. data tables, graphs, dataset pages etc
* Tooling to load data (based on Frictionless)
* Template sites you can reuse using `create-next-app`
* Single dataset micro-site
* Github backed catalog
* CKAN backed catalog
* ...
* Local development environment
* Deployment integration with DataHub.io
In summary, technically PortalJS is: NextJS + data specific react components + data loading glue (mostly using frictionless-js).

831
README.md
View File

@ -1,21 +1,830 @@
# Portaljs <h1 align="center">
🌀 Portal.JS
<br />
Create a gateway to your data
</h1>
<a alt="Nx logo" href="https://nx.dev" target="_blank" rel="noreferrer"><img src="https://raw.githubusercontent.com/nrwl/nx/master/images/nx-logo.png" width="45"></a> * [What is Portal.JS ?](#What-is-Portal.JS)
* [Features](#Features)
* [For developers](#For-developers)
* [Installation and setup](#Installation-and-setup)
* [Getting Started](#Getting-Started)
* [Tutorial](#Tutorial)
* [Build a single Frictionless dataset portal](#Build-a-single-Frictionless-dataset-portal)
* [Build a CKAN powered dataset portal](#Build-a-CKAN-powered-dataset-portal)
* [Architecture / Reference](#Architecture--Reference)
* [Component List](#Component-List)
* [UI Components](#UI-Components)
* [Dataset Components](#Dataset-Components)
* [View Components](#View-Components)
* [Search Components](#Search-Components)
* [Blog Components](#Blog-Components)
* [Misc Components](#Misc-Components)
* [Concepts and Terms](#Concepts-and-Terms)
* [Dataset](#Dataset)
* [Resource](#Resource)
* [View Spec](#view-spec)
* [Appendix](#Appendix)
* [What happened to Recline?](#What-happened-to-Recline?)
**This workspace has been generated by [Nx, a Smart, fast and extensible build system.](https://nx.dev)** # What is Portal.JS
🌀 `portal.js` is a framework for rapidly building rich data portal frontends using a modern frontend approach. `portal.js` can be used to present a single dataset or build a full-scale data catalog/portal.
## Development server `portal.js` is built in Javascript and React on top of the popular [Next.js](https://nextjs.com/) framework. `portal` assumes a "decoupled" approach where the frontend is a separate service from the backend and interacts with backend(s) via an API. It can be used with any backend and has out of the box support for [CKAN](https://ckan.org/).
Run `nx serve portaljs` for a dev server. Navigate to http://localhost:4200/. The app will automatically reload if you change any of the source files. ## Features
## Understand this workspace - 🗺️ Unified sites: present data and content in one seamless site, pulling datasets from a DMS (e.g. CKAN) and content from a CMS (e.g. wordpress) with a common internal API.
- 👩‍💻 Developer friendly: built with familiar frontend tech Javascript, React etc
- 🔋 Batteries included: Full set of portal components out of the box e.g. catalog search, dataset showcase, blog etc.
- 🎨 Easy to theme and customize: installable themes, use standard CSS and React+CSS tooling. Add new routes quickly.
- 🧱 Extensible: quickly extend and develop/import your own React components
- 📝 Well documented: full set of documentation plus the documentation of NextJS and Apollo.
Run `nx graph` to see a diagram of the dependencies of the projects. ### For developers
## Remote caching - 🏗 Build with modern, familiar frontend tech such as Javascript and React.
- 🚀 NextJS framework: so everything in NextJS for free React, SSR, static site generation, huge number of examples and integrations etc.
- SSR => unlimited number of pages, SEO etc whilst still using React.
- Static Site Generation (SSG) (good for small sites) => ultra-simple deployment, great performance and lighthouse scores etc
Run `npx nx connect-to-nx-cloud` to enable [remote caching](https://nx.app) and make CI faster. # Installation and setup
Before installation, ensure your system satisfies the following requirements:
## Further help - Node.js 10.13 or later
- Nextjs 10.0.3
- MacOS, Windows (including WSL), and Linux are supported
Visit the [Nx Documentation](https://nx.dev) to learn more. > Note: We also recommend instead of npm using `yarn` instead of `npm`.
>
Portal.js is built with React on top of Nextjs framework, so for a quick setup, you can bootstrap a Nextjs app and install portal.js as demonstrated in the code below:
```bash=
## Create a react app
npx create-next-app
# or
yarn create next-app
```
After the installation is complete, follow the instructions to start the development server. Try editing pages/index.js and see the result on your browser.
> For more information on how to use create-next-app, you can review the [create-next-app](https://nextjs.org/docs/api-reference/create-next-app) documentation.
Once you have Nextjs created, you can install portal.js:
```bash=
yarn add https://github.com/datopian/portal.js.git
```
You're now ready to use portal.js in your next app. To test portal.js, open your `index.js` file in the pages folder. By default you should have some autogenerated code in the `index.js` file:
Which outputs a page with the following content:
![](https://i.imgur.com/GVh0P6p.png)
Now, we are going to do some clean up and add a table component. In the `index.js` file, import a [Table]() component from portal as shown below:
```javascript
import Head from 'next/head'
import { Table } from 'portal' //import Table component
import styles from '../styles/Home.module.css'
export default function Home() {
const columns = [
{ field: 'id', headerName: 'ID' },
{ field: 'firstName', headerName: 'First name' },
{ field: 'lastName', headerName: 'Last name' },
{ field: 'age', headerName: 'Age' }
];
const rows = [
{ id: 1, lastName: 'Snow', firstName: 'Jon', age: 35 },
{ id: 2, lastName: 'Lannister', firstName: 'Cersei', age: 42 },
{ id: 3, lastName: 'Lannister', firstName: 'Jaime', age: 45 },
{ id: 4, lastName: 'Stark', firstName: 'Arya', age: 16 },
{ id: 7, lastName: 'Clifford', firstName: 'Ferrara', age: 44 },
{ id: 8, lastName: 'Frances', firstName: 'Rossini', age: 36 },
{ id: 9, lastName: 'Roxie', firstName: 'Harvey', age: 65 },
];
return (
<div className={styles.container}>
<Head>
<title>Create Portal App</title>
<link rel="icon" href="/favicon.ico" />
</Head>
<h1 className={styles.title}>
Welcome to <a href="https://nextjs.org">Portal.JS</a>
</h1>
{/* Use table component */}
<Table data={rows} columns={columns} />
</div>
)
}
```
Now, your page should look like the following:
![](https://i.imgur.com/n0vSjY4.png)
> **Note**: You can learn more about individual portal components, as well as their prop types in the [components reference](#Component-List).
___
# Getting Started
If you're new to Portal.js we recommend that you start with the step-by-step guide below. You can also check out the following examples of projects built with portal.js.
* [A portal for a single Frictionless dataset](#Build-a-single-Frictionless-dataset-portal)
* [A portal with a CKAN backend](#Build-a-CKAN-powered-dataset-portal)
> The [`examples` directory](https://github.com/datopian/portal.js/tree/main/examples) is regularly updated with different portal examples.
If you have questions about anything related to Portal.js, you're always welcome to ask our community on [GitHub Discussions](https://github.com/datopian/portal.js/discussions).
___
# Tutorial
## Build a single Frictionless dataset portal
This tutorial will guide you through building a portal for a single Frictionless dataset.
[Heres](https://portal-js.vercel.app/) an example of the final result.
### Setup
The dataset should be a Frictionless Dataset i.e. it should have a [datapackage.json](https://specs.frictionlessdata.io/data-package/).
Create a frictionless dataset portal app from the template:
```
npx create-next-app -e https://github.com/datopian/portal.js/tree/main/examples/dataset-frictionless
#choose a name for your portal when prompted e.g. your-portal
```
Go into your portal's directory and set the path to your dataset directory that contains the `datapackage.json`:
```
cd <your-portal>
export PORTAL_DATASET_PATH=<path/to/your/dataset>
```
Start the server:
```
yarn dev
```
Visit the Page to view your dataset portal.
## Build a CKAN powered dataset portal
See [the CKAN Portal.JS example](./examples/ckan).
___
# Architecture / Reference
## Component List
Portal.js supports many components that can help you build amazing data portals similar to [this](https://catalog-portal-js.vercel.app/) and [this](https://portal-js.vercel.app/).
In this section, we'll cover all supported components in depth, and help you understand their use as well as the expected properties.
Components are grouped under the following sections:
* [UI](https://github.com/datopian/portal.js/tree/main/src/components/ui): Components like Nav bar, Footer, e.t.c
* [Dataset](https://github.com/datopian/portal.js/tree/main/src/components/dataset): Components used for displaying a Frictionless dataset and resources
* [Search](https://github.com/datopian/portal.js/tree/main/src/components/search): Components used for building a search interface for datasets
* [Blog](https://github.com/datopian/portal.js/tree/main/src/components/blog): Components for building a simple blog for datasets
* [Views](https://github.com/datopian/portal.js/tree/main/src/components/views): Components like charts, tables, maps for generating data views
* [Misc](https://github.com/datopian/portal.js/tree/main/src/components/misc): Miscellaneous components like errors, custom links, etc used for extra design.
### UI Components
In the UI we group all components that can be used for building generic page sections. These are components for building sections like the Navigation bar, Footer, Side pane, Recent datasets, e.t.c.
#### [Nav Component](https://github.com/datopian/portal.js/blob/main/src/components/ui/Nav.js)
To build a navigation bar, you can use the `Nav` component as demonstrated below:
```javascript
import { Nav } from 'portal'
export default function Home(){
const navMenu = [{ title: 'Blog', path: '/blog' },
{ title: 'Search', path: '/search' }]
return (
<>
<Nav logo="/images/logo.png" navMenu={navMenu}/>
...
</>
)
}
```
#### Nav Component Prop Types
Nav component accepts two properties:
* **logo**: A string to an image path. Can be relative or absolute.
* **navMenu**: An array of objects with title and path. E.g : [{ title: 'Blog', path: '/blog' },{ title: 'Search', path: '/search' }]
#### [Recent Component](https://github.com/datopian/portal.js/blob/main/src/components/ui/Recent.js)
The `Recent` component is used to display a list of recent [datasets](#Dataset) in the home page. This useful if you want to display the most recent dataset users have interacted with in your home page.
To build a recent dataset section, you can use the `Recent` component as demonstrated below:
```javascript
import { Recent } from 'portal'
export default function Home() {
const datasets = [
{
organization: {
name: "Org1",
title: "This is the first org",
description: "A description of the organization 1"
},
title: "Data package title",
name: "dataset1",
description: "description of data package",
resources: [],
},
{
organization: {
name: "Org2",
title: "This is the second org",
description: "A description of the organization 2"
},
title: "Data package title",
name: "dataset2",
description: "description of data package",
resources: [],
},
]
return (
<div>
{/* Use Recent component */}
<Recent datasets={datasets} />
</div>
)
}
```
Note: The `Recent` component is hyperlinked with the dataset name of the organization and the dataset name in the following format:
> `/@<org name>/<dataset name>`
For instance, using the example dataset above, the first component will be link to page:
> `/@org1/dataset1`
and the second will be linked to:
> `/@org2/dataset2`
This is useful to know when generating dynamic pages for each dataset.
#### Recent Component Prop Types
The `Recent` component accepts the following properties:
* **datasets**: An array of [datasets](#Dataset)
### Dataset Components
The dataset component groups together components that can be used for building a dataset UI. These includes components for displaying info about a dataset, resources in a dataset as well as dataset ReadMe.
#### [KeyInfo Component](https://github.com/datopian/portal.js/blob/main/src/components/dataset/KeyInfo.js)
The `KeyInfo` components displays key properties like the number of resources, size, format, licences of in a dataset in tabular form. See example in the `Key Info` section [here](https://portal-js.vercel.app/). To use it, you can import the `KeyInfo` component as demonstrated below:
```javascript
import { KeyInfo } from 'portal'
export default function Home() {
const datapackage = {
"name": "finance-vix",
"title": "VIX - CBOE Volatility Index",
"homepage": "http://www.cboe.com/micro/VIX/",
"version": "0.1.0",
"license": "PDDL-1.0",
"sources": [
{
"title": "CBOE VIX Page",
"name": "CBOE VIX Page",
"web": "http://www.cboe.com/micro/vix/historical.aspx"
}
],
"resources": [
{
"name": "vix-daily",
"path": "vix-daily.csv",
"format": "csv",
"size": 20982,
"mediatype": "text/csv",
}
]
}
return (
<div>
{/* Use KeyInfo component */}
<KeyInfo descriptor={datapackage} resources={datapackage.resources} />
</div>
)
}
```
#### KeyInfo Component Prop Types
KeyInfo component accepts two properties:
* **descriptor**: A [Frictionless data package descriptor](https://specs.frictionlessdata.io/data-package/#descriptor)
* **resources**: An [Frictionless data package resource](https://specs.frictionlessdata.io/data-resource/#introduction)
#### [ResourceInfo Component](https://github.com/datopian/portal.js/blob/main/src/components/dataset/ResourceInfo.js)
The `ResourceInfo` components displays key properties like the name, size, format, modification dates, as well as a download link in a resource object. See an example of a `ResourceInfo` component in the `Data Files` section [here](https://portal-js.vercel.app/).
You can import and use the `ResourceInfo` component as demonstrated below:
```javascript
import { ResourceInfo } from 'portal'
export default function Home() {
const resources = [
{
"name": "vix-daily",
"path": "vix-daily.csv",
"format": "csv",
"size": 20982,
"mediatype": "text/csv",
},
{
"name": "vix-daily 2",
"path": "vix-daily2.csv",
"format": "csv",
"size": 2082,
"mediatype": "text/csv",
}
]
return (
<div>
{/* Use Recent component */}
<ResourceInfo resources={resources} />
</div>
)
}
```
#### ResourceInfo Component Prop Types
ResourceInfo component accepts a single property:
* **resources**: An [Frictionless data package resource](https://specs.frictionlessdata.io/data-resource/#introduction)
#### [ReadMe Component](https://github.com/datopian/portal.js/blob/main/src/components/dataset/Readme.js)
The `ReadMe` component is used for displaying a compiled dataset Readme in a readable format. See example in the `README` section [here](https://portal-js.vercel.app/).
> Note: By compiled ReadMe, we mean ReadMe that has been converted to plain string using a package like [remark](https://www.npmjs.com/package/remark).
You can import and use the `ReadMe` component as demonstrated below:
```javascript
import { ReadMe } from 'portal'
import remark from 'remark'
import html from 'remark-html'
import { useEffect, useState } from 'react'
const readMeMarkdown = `
CBOE Volatility Index (VIX) time-series dataset including daily open, close,
high and low. The CBOE Volatility Index (VIX) is a key measure of market
expectations of near-term volatility conveyed by S&P 500 stock index option
prices introduced in 1993.
## Data
From the [VIX FAQ][faq]:
> In 1993, the Chicago Board Options Exchange® (CBOE®) introduced the CBOE
> Volatility Index®, VIX®, and it quickly became the benchmark for stock market
> volatility. It is widely followed and has been cited in hundreds of news
> articles in the Wall Street Journal, Barron's and other leading financial
> publications. Since volatility often signifies financial turmoil, VIX is
> often referred to as the "investor fear gauge".
[faq]: http://www.cboe.com/micro/vix/faq.aspx
## License
No obvious statement on [historical data page][historical]. Given size and
factual nature of the data and its source from a US company would imagine this
was public domain and as such have licensed the Data Package under the Public
Domain Dedication and License (PDDL).
[historical]: http://www.cboe.com/micro/vix/historical.aspx
`
export default function Home() {
const [readMe, setreadMe] = useState("")
useEffect(() => {
async function processReadMe() {
const processed = await remark()
.use(html)
.process(readMeMarkdown)
setreadMe(processed.toString())
}
processReadMe()
}, [])
return (
<div>
<ReadMe readme={readMe} />
</div>
)
}
```
#### ReadMe Component Prop Types
The `ReadMe` component accepts a single property:
* **readme**: A string of a compiled ReadMe in html format.
### [View Components](https://github.com/datopian/portal.js/tree/main/src/components/views)
View components is a set of components that can be used for displaying dataset views like charts, tables, maps, e.t.c.
#### [Chart Component](https://github.com/datopian/portal.js/blob/main/src/components/views/Chart.js)
The `Chart` components exposes different chart components like Plotly Chart, Vega charts, which can be used for showing graphs. See example in the `Graph` section [here](https://portal-js.vercel.app/).
To use a chart component, you need to compile and pass a view spec as props to the chart component.
Each Chart type have their specific spec, as explained in this [doc](https://specs.frictionlessdata.io/views/#graph-spec).
In the example below, we assume there's a compiled Plotly spec:
```javascript
import { PlotlyChart } from 'portal'
export default function Home({plotlySpec}) {
return (
< div >
<PlotlyChart spec={plotlySpec} />
</div>
)
}
```
> Note: You can compile views using the [datapackage-render](https://github.com/datopian/datapackage-views-js) library, as demonstrated in [this example](https://github.com/datopian/portal.js/blob/main/examples/dataset-frictionless/lib/utils.js).
#### Chart Component Prop Types
KeyInfo component accepts two properties:
* **spec**: A compiled view spec depending on the chart type.
#### [Table Component](https://github.com/datopian/portal.js/blob/main/examples/dataset-frictionless/components/Table.js)
The `Table` component is used for displaying dataset resources as a tabular grid. See example in the `Data Preview` section [here](https://portal-js.vercel.app/).
To use a Table component, you have to pass an array of data and columns as demonstrated below:
```javascript
import { Table } from 'portal' //import Table component
export default function Home() {
const columns = [
{ field: 'id', headerName: 'ID' },
{ field: 'firstName', headerName: 'First name' },
{ field: 'lastName', headerName: 'Last name' },
{ field: 'age', headerName: 'Age' }
];
const data = [
{ id: 1, lastName: 'Snow', firstName: 'Jon', age: 35 },
{ id: 2, lastName: 'Lannister', firstName: 'Cersei', age: 42 },
{ id: 3, lastName: 'Lannister', firstName: 'Jaime', age: 45 },
{ id: 4, lastName: 'Stark', firstName: 'Arya', age: 16 },
{ id: 7, lastName: 'Clifford', firstName: 'Ferrara', age: 44 },
{ id: 8, lastName: 'Frances', firstName: 'Rossini', age: 36 },
{ id: 9, lastName: 'Roxie', firstName: 'Harvey', age: 65 },
];
return (
<Table data={data} columns={columns} />
)
}
```
> Note: Under the hood, Table component uses the [DataGrid Material UI table](https://material-ui.com/components/data-grid/), and as such all supported params in data and columns are supported.
#### Table Component Prop Types
Table component accepts two properties:
* **data**: An array of column names with properties: e.g [{field: "col1", headerName: "col1"}, {field: "col2", headerName: "col2"}]
* **columns**: An array of data objects e.g. [ {col1: 1, col2: 2}, {col1: 5, col2: 7} ]
### [Search Components](https://github.com/datopian/portal.js/tree/main/src/components/search)
Search components groups together components that can be used for creating a search interface. This includes search forms, search item as well as search result list.
#### [Form Component](https://github.com/datopian/portal.js/blob/main/src/components/search/Form.js)
The search`Form` component is a simple search input and submit button. See example of a search form [here](https://catalog-portal-js.vercel.app/search).
The search `form` requires a submit handler (`handleSubmit`). This handler function receives the search term, and handles actual search.
In the example below, we demonstrate how to use the `Form` component.
```javascript
import { Form } from 'portal'
export default function Home() {
const handleSearchSubmit = (searchQuery) => {
// Write your custom code to perform search in db
console.log(searchQuery);
}
return (
<Form
handleSubmit={handleSearchSubmit} />
)
}
```
#### Form Component Prop Types
The `Form` component accepts a single property:
* **handleSubmit**: A function that receives the search text, and can be customize to perform the actual search.
#### [Item Component](https://github.com/datopian/portal.js/blob/main/src/components/search/Item.js)
The search`Item` component can be used to display a single search result.
In the example below, we demonstrate how to use the `Item` component.
```javascript
import { Item } from 'portal'
export default function Home() {
const datapackage = {
"name": "finance-vix",
"title": "VIX - CBOE Volatility Index",
"homepage": "http://www.cboe.com/micro/VIX/",
"version": "0.1.0",
"description": "This is a test organization description",
"resources": [
{
"name": "vix-daily",
"path": "vix-daily.csv",
"format": "csv",
"size": 20982,
"mediatype": "text/csv",
}
]
}
return (
<Item dataset={datapackage} />
)
}
```
#### Item Component Prop Types
The `Item` component accepts a single property:
* **dataset**: A [Frictionless data package descriptor](https://specs.frictionlessdata.io/data-package/#descriptor)
#### [ItemTotal Component](https://github.com/datopian/portal.js/blob/main/src/components/search/Item.js)
The search`ItemTotal` is a simple component for displaying the total search result
In the example below, we demonstrate how to use the `ItemTotal` component.
```javascript
import { ItemTotal } from 'portal'
export default function Home() {
//do some custom search to get results
const search = (text) => {
return [{ name: "data1" }, { name: "data2" }]
}
//get the total result count
const searchTotal = search("some text").length
return (
<ItemTotal count={searchTotal} />
)
}
```
#### ItemTotal Component Prop Types
The `ItemTotal` component accepts a single property:
* **count**: An integer of the total number of results.
### [Blog Components](https://github.com/datopian/portal.js/tree/main/src/components/blog)
These are group of components for building a portal blog. See example of portal blog [here](https://catalog-portal-js.vercel.app/blog)
#### [PostList Components](https://github.com/datopian/portal.js/tree/main/src/components/misc)
The `PostList` component is used to display a list of blog posts with the title and a short excerpts from the content.
In the example below, we demonstrate how to use the `PostList` component.
```javascript
import { PostList } from 'portal'
export default function Home() {
const posts = [
{ title: "Blog post 1", excerpt: "This is the first blog excerpts in this list." },
{ title: "Blog post 2", excerpt: "This is the second blog excerpts in this list." },
{ title: "Blog post 3", excerpt: "This is the third blog excerpts in this list." },
]
return (
<PostList posts={posts} />
)
}
```
#### PostList Component Prop Types
The `PostList` component accepts a single property:
* **posts**: An array of post list objects with the following properties:
```javascript
[
{
title: "The title of the blog post",
excerpt: "A short excerpt from the post content",
},
]
```
#### [Post Components](https://github.com/datopian/portal.js/tree/main/src/components/misc)
The `Post` component is used to display a blog post. See an example of a blog post [here](https://catalog-portal-js.vercel.app/blog/nyt-pa-platformen-opdateringsfrekvens-og-andres-data)
In the example below, we demonstrate how to use the `Post` component.
```javascript
import { Post } from 'portal'
import * as dayjs from 'dayjs' //For converting UTC time to relative format
import relativeTime from 'dayjs/plugin/relativeTime'
dayjs.extend(relativeTime)
export default function Home() {
const post = {
title: "This is a sample blog post",
content: `<h1>A simple header</h1>
The PostList component is used to display a list of blog posts
with the title and a short excerpts from the content.
In the example below, we demonstrate how to use the PostList component.`,
createdAt: dayjs().to(dayjs(1620649596902)),
featuredImage: "https://pixabay.com/get/ge9a766d1f7b5fe0eccbf0f439501a2cf2b191997290e7ab15e6a402574acc2fdba48a82d278dca3547030e0202b7906d_640.jpg"
}
return (
<Post post={post} />
)
}
```
#### Post Component Prop Types
The `Post` component accepts a single property:
* **post**: An object with the following properties:
```javascript
{
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>
}
```
### [Misc Components](https://github.com/datopian/portal.js/tree/main/src/components/misc)
These are group of miscellaneous/extra components for extending your portal. They include components like Errors, custom links, etc.
#### [Error Component](https://github.com/datopian/portal.js/blob/main/src/components/misc/Error.js)
The `Error` component is used to display a custom error message.
In the example below, we demonstrate how to use the `Error` component.
```javascript
import { Error } from 'portal'
export default function Home() {
return (
<Error message="An error occured when loading the file!" />
)
}
```
#### Error Component Prop Types
The `Error` component accepts a single property:
* **message**: A string with the error message to display.
#### [Custom Component](https://github.com/datopian/portal.js/blob/main/src/components/misc/Error.js)
The `CustomLink` component is used to create a link with a consistent style to other portal components.
In the example below, we demonstrate how to use the `CustomLink` component.
```javascript
import { CustomLink } from 'portal'
export default function Home() {
return (
<CustomLink url="/blog" title="Goto Blog" />
)
}
```
#### CustomLink Component Prop Types
The `CustomLink` component accepts the following properties:
* **url**: A string. The relative or absolute url of the link.
* **title**: A string. The title of the link
___
## Concepts and Terms
In this section, we explain some of the terms and concepts used throughtout the portal.js documentation.
> Some of these concepts are part of official specs, and when appropriate, we'll link to the sources where you can get more details.
### Dataset
A dataset extends the [Frictionless data package](https://specs.frictionlessdata.io/data-package/#metadata) to add an extra organization property. The organization property describes the organization the dataset belongs to, and it should have the following properties:
```javascript
organization = {
name: "some org name",
title: "Some optional org title",
description: "A description of the organization"
}
```
An example of dataset with organization properties is given below:
```javascript
datasets = [{
organization: {
name: "some org name",
title: "Some optional org title",
description: "A description of the organization"
},
title: "Data package title",
name: "Data package name",
description: "description of data package",
resources: [...],
licences: [...],
sources: [...]
}
]
```
### Resource
TODO
### view spec
---
## Deploying portal build to github pages
[Deploying single frictionless dataset to Github](https://portaljs.org/publish)
## Showcases
### Single Dataset with Default Theme
![Single Dataset Example](./examples/dataset-frictionless/assets/demo.gif)
---
# Appendix
## What happened to Recline?
Portal.JS used to be Recline(JS). If you are looking for the old Recline codebase it still exists: see the [`recline` branch](https://github.com/datopian/portal.js/tree/recline). If you want context for the rename see [this issue](https://github.com/datopian/portal.js/issues/520).

5
SECURITY.md Normal file
View File

@ -0,0 +1,5 @@
# Security Policy
## Reporting a Vulnerability
Please report security issues to `security@datopian.com`

View File

@ -1,10 +0,0 @@
{
"extends": ["plugin:cypress/recommended", "../../.eslintrc.json"],
"ignorePatterns": ["!**/*"],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {}
}
]
}

View File

@ -1,6 +0,0 @@
import { defineConfig } from 'cypress';
import { nxE2EPreset } from '@nrwl/cypress/plugins/cypress-preset';
export default defineConfig({
e2e: nxE2EPreset(__dirname),
});

View File

@ -1,30 +0,0 @@
{
"name": "data-literate-e2e",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "apps/data-literate-e2e/src",
"projectType": "application",
"targets": {
"e2e": {
"executor": "@nrwl/cypress:cypress",
"options": {
"cypressConfig": "apps/data-literate-e2e/cypress.config.ts",
"devServerTarget": "data-literate:serve:development",
"testingType": "e2e"
},
"configurations": {
"production": {
"devServerTarget": "data-literate:serve:production"
}
}
},
"lint": {
"executor": "@nrwl/linter:eslint",
"outputs": ["{options.outputFile}"],
"options": {
"lintFilePatterns": ["apps/data-literate-e2e/**/*.{js,ts}"]
}
}
},
"tags": [],
"implicitDependencies": ["data-literate"]
}

View File

@ -1,13 +0,0 @@
import { getGreeting } from '../support/app.po';
describe('data-literate', () => {
beforeEach(() => cy.visit('/'));
it('should display welcome message', () => {
// Custom command example, see `../support/commands.ts` file
cy.login('my-email@something.com', 'myPassword');
// Function helper example, see `../support/app.po.ts` file
getGreeting().contains('Welcome data-literate');
});
});

View File

@ -1,4 +0,0 @@
{
"name": "Using fixtures to represent data",
"email": "hello@cypress.io"
}

View File

@ -1 +0,0 @@
export const getGreeting = () => cy.get('h1');

View File

@ -1,33 +0,0 @@
// ***********************************************
// This example commands.js shows you how to
// create various custom commands and overwrite
// existing commands.
//
// For more comprehensive examples of custom
// commands please read more here:
// https://on.cypress.io/custom-commands
// ***********************************************
// eslint-disable-next-line @typescript-eslint/no-namespace
declare namespace Cypress {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
interface Chainable<Subject> {
login(email: string, password: string): void;
}
}
//
// -- This is a parent command --
Cypress.Commands.add('login', (email, password) => {
console.log('Custom command example: Login', email, password);
});
//
// -- This is a child command --
// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
//
//
// -- This is a dual command --
// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
//
//
// -- This will overwrite an existing command --
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })

View File

@ -1,17 +0,0 @@
// ***********************************************************
// This example support/index.js is processed and
// loaded automatically before your test files.
//
// This is a great place to put global configuration and
// behavior that modifies Cypress.
//
// You can change the location of this file or turn off
// automatically serving support files with the
// 'supportFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/configuration
// ***********************************************************
// Import commands.js using ES2015 syntax:
import './commands';

View File

@ -1,10 +0,0 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"sourceMap": false,
"outDir": "../../dist/out-tsc",
"allowJs": true,
"types": ["cypress", "node"]
},
"include": ["src/**/*.ts", "src/**/*.js", "cypress.config.ts"]
}

View File

@ -1,10 +0,0 @@
{
"extends": ["plugin:cypress/recommended", "../../.eslintrc.json"],
"ignorePatterns": ["!**/*"],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {}
}
]
}

View File

@ -1,6 +0,0 @@
import { defineConfig } from 'cypress';
import { nxE2EPreset } from '@nrwl/cypress/plugins/cypress-preset';
export default defineConfig({
e2e: nxE2EPreset(__dirname),
});

View File

@ -1,30 +0,0 @@
{
"name": "site-e2e",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "apps/site-e2e/src",
"projectType": "application",
"targets": {
"e2e": {
"executor": "@nrwl/cypress:cypress",
"options": {
"cypressConfig": "apps/site-e2e/cypress.config.ts",
"devServerTarget": "site:serve:development",
"testingType": "e2e"
},
"configurations": {
"production": {
"devServerTarget": "site:serve:production"
}
}
},
"lint": {
"executor": "@nrwl/linter:eslint",
"outputs": ["{options.outputFile}"],
"options": {
"lintFilePatterns": ["apps/site-e2e/**/*.{js,ts}"]
}
}
},
"tags": [],
"implicitDependencies": ["site"]
}

View File

@ -1,13 +0,0 @@
import { getGreeting } from '../support/app.po';
describe('site', () => {
beforeEach(() => cy.visit('/'));
it('should display welcome message', () => {
// Custom command example, see `../support/commands.ts` file
cy.login('my-email@something.com', 'myPassword');
// Function helper example, see `../support/app.po.ts` file
getGreeting().contains('Welcome site');
});
});

View File

@ -1,4 +0,0 @@
{
"name": "Using fixtures to represent data",
"email": "hello@cypress.io"
}

View File

@ -1 +0,0 @@
export const getGreeting = () => cy.get('h1');

View File

@ -1,33 +0,0 @@
// ***********************************************
// This example commands.js shows you how to
// create various custom commands and overwrite
// existing commands.
//
// For more comprehensive examples of custom
// commands please read more here:
// https://on.cypress.io/custom-commands
// ***********************************************
// eslint-disable-next-line @typescript-eslint/no-namespace
declare namespace Cypress {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
interface Chainable<Subject> {
login(email: string, password: string): void;
}
}
//
// -- This is a parent command --
Cypress.Commands.add('login', (email, password) => {
console.log('Custom command example: Login', email, password);
});
//
// -- This is a child command --
// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
//
//
// -- This is a dual command --
// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
//
//
// -- This will overwrite an existing command --
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })

View File

@ -1,17 +0,0 @@
// ***********************************************************
// This example support/index.js is processed and
// loaded automatically before your test files.
//
// This is a great place to put global configuration and
// behavior that modifies Cypress.
//
// You can change the location of this file or turn off
// automatically serving support files with the
// 'supportFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/configuration
// ***********************************************************
// Import commands.js using ES2015 syntax:
import './commands';

View File

@ -1,10 +0,0 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"sourceMap": false,
"outDir": "../../dist/out-tsc",
"allowJs": true,
"types": ["cypress", "node"]
},
"include": ["src/**/*.ts", "src/**/*.js", "cypress.config.ts"]
}

View File

@ -1,31 +0,0 @@
{
"extends": [
"plugin:@nrwl/nx/react-typescript",
"next",
"next/core-web-vitals",
"../../.eslintrc.json"
],
"ignorePatterns": ["!**/*", ".next/**/*"],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {
"@next/next/no-html-link-for-pages": ["error", "apps/site/pages"]
}
},
{
"files": ["*.ts", "*.tsx"],
"rules": {}
},
{
"files": ["*.js", "*.jsx"],
"rules": {}
}
],
"rules": {
"@next/next/no-html-link-for-pages": "off"
},
"env": {
"jest": true
}
}

View File

@ -1,19 +0,0 @@
This the Portal.JS website.
It is built on [Next.js](https://nextjs.org/).
## Getting Started
First, run the development server:
```bash
npm run dev
# or
yarn dev
```
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
## Deployment
We currently deploy on Vercel.

View File

@ -1,16 +0,0 @@
import Link from "next/link";
export default function CustomLink({ as, href, ...otherProps }) {
return (
<>
<Link legacyBehavior as={as} href={href}>
<a {...otherProps} />
</Link>
<style jsx>{`
a {
color: tomato;
}
`}</style>
</>
);
}

View File

@ -1,47 +0,0 @@
import Layout from '../components/Layout'
import { MDXRemote } from 'next-mdx-remote'
import dynamic from 'next/dynamic'
import Head from 'next/head'
import Link from 'next/link'
import CustomLink from '../components/CustomLink'
import { Vega, VegaLite } from 'react-vega'
// Custom components/renderers to pass to MDX.
// Since the MDX files aren't loaded by webpack, they have no knowledge of how
// to handle import statements. Instead, you must include components in scope
// here.
const components = {
a: CustomLink,
Table: dynamic(() => import('../components/Table')),
Excel: dynamic(() => import('../components/Excel')),
// TODO: try and make these dynamic ...
Vega: Vega,
VegaLite: VegaLite,
LineChart: dynamic(() => import('../components/LineChart')),
Head,
}
export default function DataLiterate({ children, source, frontMatter }) {
return (
<Layout title={frontMatter.title}>
<div className="prose mx-auto">
<header>
<div className="mb-6">
<h1>{frontMatter.title}</h1>
{frontMatter.author && (
<div className="-mt-6"><p className="opacity-60 pl-1">{frontMatter.author}</p></div>
)}
{frontMatter.description && (
<p className="description">{frontMatter.description}</p>
)}
</div>
</header>
<main>
<MDXRemote {...source} components={components} />
</main>
</div>
</Layout>
)
}

View File

@ -1,74 +0,0 @@
import axios from 'axios'
import * as XLSX from 'xlsx'
import React, { useEffect, useState } from 'react'
import Table from './Table'
export default function Excel ({ src='' }) {
const [data, setData] = React.useState([])
const [cols, setCols] = React.useState([])
const [workbook, setWorkbook] = React.useState(null)
const [error, setError] = React.useState('')
const [hasMounted, setHasMounted] = React.useState(0)
// so this is here so we re-render this in the browser
// and not just when we build the page statically in nextjs
useEffect(() => {
if (hasMounted==0) {
handleUrl(src)
}
setHasMounted(1)
})
function handleUrl(url) {
// if url is external may have CORS issue so we proxy it ...
if (url.startsWith('http')) {
const PROXY_URL = window.location.origin + '/api/proxy'
url = PROXY_URL + '?url=' + encodeURIComponent(url)
}
axios.get(url, {
responseType: 'arraybuffer'
}).then((res) => {
let out = new Uint8Array(res.data)
let workbook = XLSX.read(out, {type: "array"})
// Get first worksheet
const wsname = workbook.SheetNames[0]
const ws = workbook.Sheets[wsname]
// Convert array of arrays
const datatmp = XLSX.utils.sheet_to_json(ws, {header:1})
const colstmp = make_cols(ws['!ref'])
setData(datatmp)
setCols(colstmp)
setWorkbook(workbook)
}).catch((e) => {
setError(e.message)
})
}
return (
<>
{error &&
<div>
There was an error loading the excel file at {src}:
<p>{error}</p>
</div>
}
{workbook &&
<ul>
{workbook.SheetNames.map((value, index) => {
return <li key={index}>{value}</li>
})}
</ul>
}
<Table data={data} cols={cols} />
</>
)
}
/* generate an array of column objects */
const make_cols = refstr => {
let o = [], C = XLSX.utils.decode_range(refstr).e.c + 1
for(var i = 0; i < C; ++i) o[i] = {name:XLSX.utils.encode_col(i), key:i}
return o
}

View File

@ -1,186 +0,0 @@
import XLSX from 'xlsx';
import React from 'react';
function SheetJSApp() {
const [data, setData] = React.useState([]);
const [cols, setCols] = React.useState([]);
const handleFile = (file) => {
const reader = new FileReader();
const rABS = !!reader.readAsBinaryString;
reader.onload = (e) => {
/* Parse data */
const bstr = e.target.result;
const wb = XLSX.read(bstr, {type:rABS ? 'binary' : 'array'});
displayWorkbook(wb);
};
if(rABS) reader.readAsBinaryString(file); else reader.readAsArrayBuffer(file);
}
const handleUrl = (url) => {
let oReq = new XMLHttpRequest();
oReq.open("GET", url, true);
oReq.responseType = "arraybuffer";
oReq.onload = function (e) {
let arraybuffer = oReq.response;
/* not responseText!! */
/* convert data to binary string */
let data = new Uint8Array(arraybuffer);
let arr = new Array();
for (let i = 0; i != data.length; ++i) arr[i] = String.fromCharCode(data[i]);
let bstr = arr.join("");
/* Call XLSX */
let workbook = XLSX.read(bstr, {type: "binary"});
displayWorkbook(workbook);
};
oReq.send();
}
const displayWorkbook = (wb) => {
/* Get first worksheet */
const wsname = wb.SheetNames[0];
const ws = wb.Sheets[wsname];
/t Convert array of arrays */
const data = XLSX.utils.sheet_to_json(ws, {header:1});
/* Update state */
setData(data);
setCols(make_cols(ws['!ref']));
}
return (
<div>
<DragDropFile handleFile={handleFile}>
<h2>Drag or choose a spreadsheet file</h2>
<div className="">
<DataInput handleFile={handleFile} />
</div>
</DragDropFile>
<div className="mb-6">
<h3>Enter spreadsheet URL</h3>
<UrlInput handleUrl={handleUrl} />
</div>
<div className="row">
<OutTable data={data} cols={cols} />
</div>
</div>
);
}
if(typeof module !== 'undefined') module.exports = SheetJSApp
/* -------------------------------------------------------------------------- */
/*
Simple HTML5 file drag-and-drop wrapper
usage: <DragDropFile handleFile={handleFile}>...</DragDropFile>
handleFile(file:File):void;
*/
function DragDropFile({ handleFile, children }) {
const suppress = (e) => { e.stopPropagation(); e.preventDefault(); };
const handleDrop = (e) => { e.stopPropagation(); e.preventDefault();
const files = e.dataTransfer.files;
if(files && files[0]) handleFile(files[0]);
};
return (
<div
onDrop={handleDrop}
onDragEnter={suppress}
onDragOver={suppress}
>
{children}
</div>
);
}
function UrlInput({ handleUrl }) {
const handleChange = (e) => {
const url = e.target.value;
if(url) handleUrl(url);
};
return (
<form className="form-inline">
<div className="form-group">
<label htmlFor="url">Spreadsheet URL (with CORS enabled!)</label>
<br />
<small>Here is one: http://localhost:3000/_files/eight-centuries-of-global-real-interest-rates-r-g-and-the-suprasecular-decline-1311-2018-data.xlsx</small>
<br />
<input
type="text"
id="url"
className="border w-96"
accept={SheetJSFT}
onChange={handleChange}
/>
</div>
</form>
)
}
/*
Simple HTML5 file input wrapper
usage: <DataInput handleFile={callback} />
handleFile(file:File):void;
*/
function DataInput({ handleFile }) {
const handleChange = (e) => {
const files = e.target.files;
if(files && files[0]) handleFile(files[0]);
};
return (
<form className="form-inline">
<div className="form-group">
<label htmlFor="file">Select spreadsheet file</label>
<br />
<input
type="file"
className="form-control"
id="file"
accept={SheetJSFT}
onChange={handleChange}
/>
</div>
</form>
)
}
/*
Simple HTML Table
usage: <OutTable data={data} cols={cols} />
data:Array<Array<any> >;
cols:Array<{name:string, key:number|string}>;
*/
function OutTable({ data, cols }) {
return (
<div className="table-responsive">
<table className="table table-striped">
<thead>
<tr>{cols.map((c) => <th key={c.key}>{c.name}</th>)}</tr>
</thead>
<tbody>
{data.map((r,i) => <tr key={i}>
{cols.map(c => <td key={c.key}>{ r[c.key] }</td>)}
</tr>)}
</tbody>
</table>
</div>
);
}
/* list of supported file types */
const SheetJSFT = [
"xlsx", "xlsb", "xlsm", "xls", "xml", "csv", "txt", "ods", "fods", "uos", "sylk", "dif", "dbf", "prn", "qpw", "123", "wb*", "wq*", "html", "htm"
].map(x => `.${x}`).join(",");
/* generate an array of column objects */
const make_cols = refstr => {
let o = [], C = XLSX.utils.decode_range(refstr).e.c + 1;
for(var i = 0; i < C; ++i) o[i] = {name:XLSX.utils.encode_col(i), key:i}
return o;
};

View File

@ -1,32 +0,0 @@
import Link from 'next/link'
import Head from 'next/head'
import Nav from '../components/Nav'
export default function Layout({ children, title = 'Home' }) {
return (
<>
<Head>
<title>Portal.JS - {title}</title>
<link rel="icon" href="/favicon.ico" />
<meta charSet="utf-8" />
<meta name="viewport" content="initial-scale=1.0, width=device-width" />
</Head>
<Nav />
<div className="mx-auto p-6">
{children}
</div>
<footer className="flex items-center justify-center w-full h-24 border-t">
<a
className="flex items-center justify-center"
href="https://datopian.com/"
target="_blank"
rel="noopener noreferrer"
>
Built by{' '}
<img src="/datopian-logo.png" alt="Datopian Logo" className="h-6 ml-2" />
</a>
</footer>
</>
)
}

View File

@ -1,34 +0,0 @@
import { NextSeo } from "next-seo";
import Nav from "./Nav";
export default function Layout({
children,
title,
}: {
children;
title?: string;
}) {
return (
<>
{title && <NextSeo title={title} />}
<Nav />
<div className="mx-auto p-6">{children}</div>
<footer className="flex items-center justify-center w-full h-24 border-t">
<a
className="flex items-center justify-center"
href="https://datopian.com/"
target="_blank"
rel="noopener noreferrer"
>
Built by{" "}
<img
src="/datopian-logo.png"
alt="Datopian Logo"
className="h-6 ml-2"
/>
</a>
</footer>
</>
);
}

View File

@ -1,33 +0,0 @@
import { Vega, VegaLite } from 'react-vega'
export default function LineChart( { data=[] }) {
var tmp = data
if (Array.isArray(data)) {
tmp = data.map((r,i) => {
return { x: r[0], y: r[1] }
})
}
const vegaData = { "table": tmp }
const spec = {
"$schema": "https://vega.github.io/schema/vega-lite/v5.json",
"mark": "line",
"data": {
"name": "table"
},
"encoding": {
"x": {
"field": "x",
"timeUnit": "year",
"type": "temporal"
},
"y": {
"field": "y",
"type": "quantitative"
}
}
}
return (
<VegaLite data={ vegaData } spec={ spec } />
)
}

View File

@ -1,40 +0,0 @@
import { MDXRemote } from "next-mdx-remote";
import layouts from "layouts";
export default function DRD({ source, frontMatter }) {
const Layout = ({ children }) => {
if (frontMatter.layout) {
let LayoutComponent = layouts[frontMatter.layout];
return <LayoutComponent {...frontMatter}>{children}</LayoutComponent>;
}
return <>{children}</>;
};
return (
<div className="prose mx-auto">
<header>
<div className="mb-6">
{/* Default layout */}
{!frontMatter.layout && (
<>
<h1>{frontMatter.title}</h1>
{frontMatter.author && (
<div className="-mt-6">
<p className="opacity-60 pl-1">{frontMatter.author}</p>
</div>
)}
{frontMatter.description && (
<p className="description">{frontMatter.description}</p>
)}
</>
)}
</div>
</header>
<main>
<Layout>
<MDXRemote {...source} />
</Layout>
</main>
</div>
);
}

View File

@ -1,96 +0,0 @@
import { Fragment } from "react";
import { Disclosure, Menu, Transition } from "@headlessui/react";
import { siteConfig } from "config/siteConfig";
import { Bars3Icon, XMarkIcon } from '@heroicons/react/24/outline'
import Link from "next/link";
import GitHubButton from "react-next-github-btn";
const navigation = siteConfig.navLinks;
function classNames(...classes) {
return classes.filter(Boolean).join(" ");
}
export default function Nav() {
return (
<Disclosure as="nav" className="bg-gray-800">
{({ open }) => (
<>
<div className="max-w-7xl mx-auto px-2 sm:px-6 lg:px-8">
<div className="relative flex items-center justify-between h-16">
<div className="absolute inset-y-0 left-0 flex items-center sm:hidden">
{/* Mobile menu button*/}
<Disclosure.Button className="inline-flex items-center justify-center p-2 rounded-md text-gray-400 hover:text-white hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-white">
<span className="sr-only">Open main menu</span>
{open ? (
<Bars3Icon className="block h-6 w-6" aria-hidden="true" />
) : (
<XMarkIcon className="block h-6 w-6" aria-hidden="true" />
)}
</Disclosure.Button>
</div>
<div className="flex-1 flex items-center justify-center sm:items-stretch sm:justify-start">
<div className="flex-shrink-0 flex items-center">
<Link href="/" className="text-white">
Portal.JS
</Link>
</div>
<div className="hidden sm:block sm:ml-6">
<div className="flex space-x-4">
{navigation.map((item) => (
<Link
href={item.href}
key={item.name}
className={classNames(
item.current
? "bg-gray-900 text-white"
: "text-gray-300 hover:bg-gray-700 hover:text-white",
"px-3 py-2 rounded-md text-sm font-medium"
)}
aria-current={item.current ? "page" : undefined}
>
{item.name}
</Link>
))}
</div>
</div>
</div>
<div className="mt-2 justify-end">
<GitHubButton
href="https://github.com/datopian/portal.js"
data-color-scheme="no-preference: light; light: light; dark: dark;"
data-size="large"
data-show-count="true"
aria-label="Star datopian/portal.js on GitHub"
>
Stars
</GitHubButton>
</div>
</div>
</div>
<Disclosure.Panel className="sm:hidden">
<div className="px-2 pt-2 pb-3 space-y-1">
{navigation.map((item) => (
<a
key={item.name}
href={item.href}
className={classNames(
item.current
? "bg-gray-900 text-white"
: "text-gray-300 hover:bg-gray-700 hover:text-white",
"block px-3 py-2 rounded-md text-base font-medium"
)}
aria-current={item.current ? "page" : undefined}
>
{item.name}
</a>
))}
</div>
</Disclosure.Panel>
</>
)}
</Disclosure>
);
}

View File

@ -1,83 +0,0 @@
import axios from 'axios'
import React, { useEffect, useState } from 'react'
const papa = require("papaparse")
/*
Simple HTML Table
usage: <OutTable data={data} cols={cols} />
data:Array<Array<any> >;
cols:Array<{name:string, key:number|string}>;
*/
export default function Table({ data=[], cols=[], csv='', url='' }) {
if (csv) {
const out = parseCsv(csv)
data = out.rows
cols = out.cols
}
const [ourdata, setData] = React.useState(data)
const [ourcols, setCols] = React.useState(cols)
const [error, setError] = React.useState('')
useEffect(() => {
if (url) {
loadUrl(url)
}
}, [url])
function loadUrl(path) {
// HACK: duplicate of Excel code - maybe refactor
// if url is external may have CORS issue so we proxy it ...
if (url.startsWith('http')) {
const PROXY_URL = window.location.origin + '/api/proxy'
url = PROXY_URL + '?url=' + encodeURIComponent(url)
}
axios.get(url).then((res) => {
const { rows, fields } = parseCsv(res.data)
setData(rows)
setCols(fields)
})
}
return (
<>
<SimpleTable data={ourdata} cols={ourcols} />
</>
)
}
/*
Simple HTML Table
usage: <OutTable data={data} cols={cols} />
data:Array<Array<any> >;
cols:Array<{name:string, key:number|string}>;
*/
function SimpleTable({ data=[], cols=[] }) {
return (
<div className="table-responsive">
<table className="table table-striped">
<thead>
<tr>{cols.map((c) => <th key={c.key}>{c.name}</th>)}</tr>
</thead>
<tbody>
{data.map((r,i) => <tr key={i}>
{cols.map(c => <td key={c.key}>{ r[c.key] }</td>)}
</tr>)}
</tbody>
</table>
</div>
)
}
function parseCsv(csv) {
csv = csv.trim()
const rawdata = papa.parse(csv, {header: true})
const cols = rawdata.meta.fields.map((r,i) => {
return { key: r, name: r }
})
return {
rows: rawdata.data,
fields: cols
}
}

View File

@ -1,46 +0,0 @@
import Layout from '../Layout'
import { MDXRemote } from 'next-mdx-remote'
import dynamic from 'next/dynamic'
import Head from 'next/head'
import CustomLink from '../CustomLink'
import { Vega, VegaLite } from 'react-vega'
// Custom components/renderers to pass to MDX.
// Since the MDX files aren't loaded by webpack, they have no knowledge of how
// to handle import statements. Instead, you must include components in scope
// here.
const components = {
a: CustomLink,
Table: dynamic(() => import('./Table')),
Excel: dynamic(() => import('./Excel')),
// TODO: try and make these dynamic ...
Vega: Vega,
VegaLite: VegaLite,
LineChart: dynamic(() => import('./LineChart')),
Head,
}
export default function DataLiterate({ source, frontMatter }) {
return (
<Layout title={frontMatter.title}>
<div className="prose mx-auto">
<header>
<div className="mb-6">
<h1>{frontMatter.title}</h1>
{frontMatter.author && (
<div className="-mt-6"><p className="opacity-60 pl-1">{frontMatter.author}</p></div>
)}
{frontMatter.description && (
<p className="description">{frontMatter.description}</p>
)}
</div>
</header>
<main>
<MDXRemote {...source} components={components} />
</main>
</div>
</Layout>
)
}

View File

@ -1,74 +0,0 @@
import axios from 'axios'
import XLSX from 'xlsx'
import React, { useEffect, useState } from 'react'
import Table from './Table'
export default function Excel ({ src='' }) {
const [data, setData] = React.useState([])
const [cols, setCols] = React.useState([])
const [workbook, setWorkbook] = React.useState(null)
const [error, setError] = React.useState('')
const [hasMounted, setHasMounted] = React.useState(0)
// so this is here so we re-render this in the browser
// and not just when we build the page statically in nextjs
useEffect(() => {
if (hasMounted==0) {
handleUrl(src)
}
setHasMounted(1)
})
function handleUrl(url) {
// if url is external may have CORS issue so we proxy it ...
if (url.startsWith('http')) {
const PROXY_URL = window.location.origin + '/api/proxy'
url = PROXY_URL + '?url=' + encodeURIComponent(url)
}
axios.get(url, {
responseType: 'arraybuffer'
}).then((res) => {
let out = new Uint8Array(res.data)
let workbook = XLSX.read(out, {type: "array"})
// Get first worksheet
const wsname = workbook.SheetNames[0]
const ws = workbook.Sheets[wsname]
// Convert array of arrays
const datatmp = XLSX.utils.sheet_to_json(ws, {header:1})
const colstmp = make_cols(ws['!ref'])
setData(datatmp)
setCols(colstmp)
setWorkbook(workbook)
}).catch((e) => {
setError(e.message)
})
}
return (
<>
{error &&
<div>
There was an error loading the excel file at {src}:
<p>{error}</p>
</div>
}
{workbook &&
<ul>
{workbook.SheetNames.map((value, index) => {
return <li key={index}>{value}</li>
})}
</ul>
}
<Table data={data} cols={cols} />
</>
)
}
/* generate an array of column objects */
const make_cols = refstr => {
let o = [], C = XLSX.utils.decode_range(refstr).e.c + 1
for(var i = 0; i < C; ++i) o[i] = {name:XLSX.utils.encode_col(i), key:i}
return o
}

View File

@ -1,33 +0,0 @@
import { Vega, VegaLite } from 'react-vega'
export default function LineChart( { data=[] }) {
var tmp = data
if (Array.isArray(data)) {
tmp = data.map((r,i) => {
return { x: r[0], y: r[1] }
})
}
const vegaData = { "table": tmp }
const spec = {
"$schema": "https://vega.github.io/schema/vega-lite/v5.json",
"mark": "line",
"data": {
"name": "table"
},
"encoding": {
"x": {
"field": "x",
"timeUnit": "year",
"type": "temporal"
},
"y": {
"field": "y",
"type": "quantitative"
}
}
}
return (
<VegaLite data={ vegaData } spec={ spec } />
)
}

View File

@ -1,83 +0,0 @@
import axios from 'axios'
import React, { useEffect, useState } from 'react'
const papa = require("papaparse")
/*
Simple HTML Table
usage: <OutTable data={data} cols={cols} />
data:Array<Array<any> >;
cols:Array<{name:string, key:number|string}>;
*/
export default function Table({ data=[], cols=[], csv='', url='' }) {
if (csv) {
const out = parseCsv(csv)
data = out.rows
cols = out.cols
}
const [ourdata, setData] = React.useState(data)
const [ourcols, setCols] = React.useState(cols)
const [error, setError] = React.useState('')
useEffect(() => {
if (url) {
loadUrl(url)
}
}, [url])
function loadUrl(path) {
// HACK: duplicate of Excel code - maybe refactor
// if url is external may have CORS issue so we proxy it ...
if (url.startsWith('http')) {
const PROXY_URL = window.location.origin + '/api/proxy'
url = PROXY_URL + '?url=' + encodeURIComponent(url)
}
axios.get(url).then((res) => {
const { rows, fields } = parseCsv(res.data)
setData(rows)
setCols(fields)
})
}
return (
<>
<SimpleTable data={ourdata} cols={ourcols} />
</>
)
}
/*
Simple HTML Table
usage: <OutTable data={data} cols={cols} />
data:Array<Array<any> >;
cols:Array<{name:string, key:number|string}>;
*/
function SimpleTable({ data=[], cols=[] }) {
return (
<div className="table-responsive">
<table className="table table-striped">
<thead>
<tr>{cols.map((c) => <th key={c.key}>{c.name}</th>)}</tr>
</thead>
<tbody>
{data.map((r,i) => <tr key={i}>
{cols.map(c => <td key={c.key}>{ r[c.key] }</td>)}
</tr>)}
</tbody>
</table>
</div>
)
}
function parseCsv(csv) {
csv = csv.trim()
const rawdata = papa.parse(csv, {header: true})
const cols = rawdata.meta.fields.map((r,i) => {
return { key: r, name: r }
})
return {
rows: rawdata.data,
fields: cols
}
}

View File

@ -1,13 +0,0 @@
import { defaultConfig } from "@flowershow/core";
import userConfig from "../content/config";
export const siteConfig: any = {
...defaultConfig,
...userConfig,
// prevent theme object overrides for
// values not provided in userConfig
theme: {
...defaultConfig.theme,
...userConfig?.theme,
},
};

View File

@ -1,51 +0,0 @@
const config = {
title:
"Portal.JS - Rapidly build rich data portals using a modern frontend framework",
description:
"Portal.JS is a framework for rapidly building rich data portal frontends using a modern frontend approach. portal.js can be used to present a single dataset or build a full-scale data catalog/portal.",
theme: {
default: "",
},
author: "Datopian",
authorLogo: "/datopian-logo.png",
authorUrl: "https://datopian.com/",
navLinks: [
{ name: "Docs", href: "/docs" },
{ name: "Components", href: "/docs/components" },
{ name: "Learn", href: "/learn" },
{ name: "Gallery", href: "/gallery" },
{ name: "Data Literate", href: "/data-literate" },
{ name: "DL Demo", href: "/data-literate/demo" },
{ name: "Excel Viewer", href: "/excel-viewer" },
{ name: "GitHub", href: "https://github.com/datopian/portal.js" },
],
footerLinks: [],
nextSeo: {
openGraph: {
type: "website",
title:
"Portal.JS - Rapidly build rich data portals using a modern frontend framework",
description:
"Portal.JS is a framework for rapidly building rich data portal frontends using a modern frontend approach. portal.js can be used to present a single dataset or build a full-scale data catalog/portal.",
locale: "en_US",
images: [
{
url: "https://datahub.io/static/img/opendata/product.png", // TODO
alt: "Portal.JS - Rapidly build rich data portals using a modern frontend framework",
width: 1200,
height: 627,
type: "image/jpg",
},
],
},
twitter: {
handle: "@datopian",
site: "https://datopian.com/",
cardType: "summary_large_image",
},
},
// tableOfContents: true,
// analytics: "xxxxxx",
// editLinkShow: true,
};
export default config;

View File

@ -1,21 +0,0 @@
---
title: Data Literate Documents
author: Rufus Pollock and Friends
---
**What?** An experiment in simple, lightweight approach to creating, displaying and sharing datasets and data-driven stories.
**Why?** a simple, fast, extensible way to present data(sets) and author data-driven content. I want to work with markdown for content and quickly add data in the simplest way possible e.g. dropping in links, pasting tables or adding links to the metadata.
**How?** Technically the essence is Markdown+React (MDX) + a curated toolkit of components for data-presentation + NextJS for framework and deployment.
Check out the [demo](/data-literate/demo).
## Background
I have observed two converging data-rich use cases:
* **Data Publishing**: quickly presenting data whether a single file or a full dataset.
* **Data Stories**: creating data-driven content from the simplest of a blog post with a graph to high end there is sophisticated data journalism and visualization.
Both of these can now be well served by a simple markdown-plus approach. Taking data publishing first. I've long been a fan of ultra-simple `README + metadata + csv` datasets. With the evolution of frontmatter we can merge the metadata into the README. However, we still need to "present" the dataset and the key thing for a dataset is the data and this is not something markdown ever supported well ... But now with MDX and the richness of the javascript ecosystem it's quite easy to enhance our markdown and build a rendering pipeleine.

View File

@ -1,268 +0,0 @@
---
title: Demo
---
This demos and documents Data Literate features live.
You can see the raw source of this page here: https://raw.githubusercontent.com/datopian/data-literate/main/content/demo.mdx
## Table of Contents
## GFM
We can have github-flavored markdown including markdown tables, auto-linked links and checklists:
```
https://github.com/datopian/portal.js
| a | b |
|---|---|
| 1 | 2 |
* [x] one thing to do
* [ ] a second thing to do
```
https://github.com/datopian/portal.js
| a | b |
|---|---|
| 1 | 2 |
* [x] one thing to do
* [ ] a second thing to do
## Footnotes
```
here is a footnote reference[^1]
[^1]: a very interesting footnote.
```
here is a footnote reference[^1]
[^1]: a very interesting footnote.
## Frontmatter
Posts can have frontmatter like:
```
---
title: Hello World
author: Rufus Pollock
---
```
The title and description are pulled from the MDX file and processed using `gray-matter`. Additionally, links are rendered using a custom component passed to `next-mdx-remote`.
## A Table of Contents
You can create a table of contents by having a markdown heading named `Table of Contents`. You can see an example at the start of this post.
## A Table
You can create tables ...
```
<Table cols={[
{ key: 'id', name: 'ID' },
{ key: 'firstName', name: 'First name' },
{ key: 'lastName', name: 'Last name' },
{ key: 'age', name: 'Age' }
]} data={[
{ id: 1, lastName: 'Snow', firstName: 'Jon', age: 35 },
{ id: 2, lastName: 'Lannister', firstName: 'Cersei', age: 42 },
{ id: 3, lastName: 'Lannister', firstName: 'Jaime', age: 45 },
{ id: 4, lastName: 'Stark', firstName: 'Arya', age: 16 },
{ id: 7, lastName: 'Clifford', firstName: 'Ferrara', age: 44 },
{ id: 8, lastName: 'Frances', firstName: 'Rossini', age: 36 },
{ id: 9, lastName: 'Roxie', firstName: 'Harvey', age: 65 },
]}
/>
```
<Table cols={[
{ key: 'id', name: 'ID' },
{ key: 'firstName', name: 'First name' },
{ key: 'lastName', name: 'Last name' },
{ key: 'age', name: 'Age' }
]} data={[
{ id: 1, lastName: 'Snow', firstName: 'Jon', age: 35 },
{ id: 2, lastName: 'Lannister', firstName: 'Cersei', age: 42 },
{ id: 3, lastName: 'Lannister', firstName: 'Jaime', age: 45 },
{ id: 4, lastName: 'Stark', firstName: 'Arya', age: 16 },
{ id: 7, lastName: 'Clifford', firstName: 'Ferrara', age: 44 },
{ id: 8, lastName: 'Frances', firstName: 'Rossini', age: 36 },
{ id: 9, lastName: 'Roxie', firstName: 'Harvey', age: 65 },
]}
/>
### Table from Raw CSV
You can also pass raw CSV as the content ...
```
<Table csv={`
Year,Temp Anomaly
1850,-0.418
2020,0.923
`} />
```
<Table csv={`
Year,Temp Anomaly,
1850,-0.418
2020,0.923
`} />
### Table from a URL
<Table url='https://raw.githubusercontent.com/datopian/data-literate/main/public/_files/HadCRUT.5.0.1.0.analysis.summary_series.global.annual.csv' />
```
<Table url='https://raw.githubusercontent.com/datopian/data-literate/main/public/_files/HadCRUT.5.0.1.0.analysis.summary_series.global.annual.csv' />
```
## Charts
You can create charts using a simple syntax.
### Line Chart
<LineChart data={
[
["1850",-0.41765878],
["1851",-0.2333498],
["1852",-0.22939907],
["1853",-0.27035445],
["1854",-0.29163003]
]
}
/>
```
<LineChart data={
[
["1850",-0.41765878],
["1851",-0.2333498],
["1852",-0.22939907],
["1853",-0.27035445],
["1854",-0.29163003]
]
}
/>
```
NB: we have quoted years as otherwise not interpreted as dates but as integers ...
### Vega and Vega Lite
You can using vega or vega-lite. Here's an example using vega-lite:
<VegaLite data={ { "table": [
{
"y": -0.418,
"x": 1850
},
{
"y": 0.923,
"x": 2020
}
]
}
} spec={
{
"$schema": "https://vega.github.io/schema/vega-lite/v4.json",
"mark": "bar",
"data": {
"name": "table"
},
"encoding": {
"x": {
"field": "x",
"type": "ordinal"
},
"y": {
"field": "y",
"type": "quantitative"
}
}
}
} />
```jsx
<VegaLite data={ { "table": [
{
"y": -0.418,
"x": 1850
},
{
"y": 0.923,
"x": 2020
}
]
}
} spec={
{
"$schema": "https://vega.github.io/schema/vega-lite/v4.json",
"mark": "bar",
"data": {
"name": "table"
},
"encoding": {
"x": {
"field": "x",
"type": "ordinal"
},
"y": {
"field": "y",
"type": "quantitative"
}
}
}
} />
```
#### Line Chart from URL with Tooltip
https://vega.github.io/vega-lite/examples/interactive_multi_line_pivot_tooltip.html
<VegaLite spec={
{
"$schema": "https://vega.github.io/schema/vega-lite/v5.json",
"data": {"url": "/_files/HadCRUT.5.0.1.0.analysis.summary_series.global.annual.csv"},
"width": 600,
"height": 250,
"mark": "line",
"encoding": {
"x": {"field": "Time", "type": "temporal"},
"y": {"field": "Anomaly (deg C)", "type": "quantitative"},
"tooltip": {"field": "Anomaly (deg C)", "type": "quantitative"}
}
}
} />
## Display Excel Files
Local file ...
```
<Excel src='/_files/eight-centuries-of-global-real-interest-rates-r-g-and-the-suprasecular-decline-1311-2018-data.xlsx' />
```
<Excel src='/_files/eight-centuries-of-global-real-interest-rates-r-g-and-the-suprasecular-decline-1311-2018-data.xlsx' />
Remote files work too (even without CORS) thanks to proxying:
```
<Excel src='https://github.com/datasets/awesome-data/files/6604635/eight-centuries-of-global-real-interest-rates-r-g-and-the-suprasecular-decline-1311-2018-data.xlsx' />
```
<Excel src='https://github.com/datasets/awesome-data/files/6604635/eight-centuries-of-global-real-interest-rates-r-g-and-the-suprasecular-decline-1311-2018-data.xlsx' />

View File

@ -1,132 +0,0 @@
# 🌀 Portal.JS: The JavaScript framework for data portals
🌀 `portal.js` is a framework for rapidly building rich data portal frontends using a modern frontend approach. `portal.js` can be used to present a single dataset or build a full-scale data catalog/portal.
`portal.js` is built in Javascript and React on top of the popular [Next.js](https://nextjs.com/) framework. `portal` assumes a "decoupled" approach where the frontend is a separate service from the backend and interacts with backend(s) via an API. It can be used with any backend and has out of the box support for [CKAN](https://ckan.org/).
## Features
- 🗺️ Unified sites: present data and content in one seamless site, pulling datasets from a DMS (e.g. CKAN) and content from a CMS (e.g. wordpress) with a common internal API.
- 👩‍💻 Developer friendly: built with familiar frontend tech Javascript, React etc
- 🔋 Batteries included: Full set of portal components out of the box e.g. catalog search, dataset showcase, blog etc.
- 🎨 Easy to theme and customize: installable themes, use standard CSS and React+CSS tooling. Add new routes quickly.
- 🧱 Extensible: quickly extend and develop/import your own React components
- 📝 Well documented: full set of documentation plus the documentation of NextJS and Apollo.
### For developers
- 🏗 Build with modern, familiar frontend tech such as Javascript and React.
- 🚀 NextJS framework: so everything in NextJS for free React, SSR, static site generation, huge number of examples and integrations etc.
- SSR => unlimited number of pages, SEO etc whilst still using React.
- Static Site Generation (SSG) (good for small sites) => ultra-simple deployment, great performance and lighthouse scores etc
## Installation and setup
Before installation, ensure your system satisfies the following requirements:
- Node.js 10.13 or later
- Nextjs 10.0.3
- MacOS, Windows (including WSL), and Linux are supported
> Note: We also recommend instead of npm using `yarn` instead of `npm`.
>
Portal.js is built with React on top of Nextjs framework, so for a quick setup, you can bootstrap a Nextjs app and install portal.js as demonstrated in the code below:
```bash=
## Create a react app
npx create-next-app
# or
yarn create next-app
```
After the installation is complete, follow the instructions to start the development server. Try editing pages/index.js and see the result on your browser.
> For more information on how to use create-next-app, you can review the [create-next-app](https://nextjs.org/docs/api-reference/create-next-app) documentation.
Once you have Nextjs created, you can install portal.js:
```bash=
yarn add https://github.com/datopian/portal.js.git
```
You're now ready to use portal.js in your next app. To test portal.js, open your `index.js` file in the pages folder. By default you should have some autogenerated code in the `index.js` file:
Which outputs a page with the following content:
![](https://i.imgur.com/GVh0P6p.png)
Now, we are going to do some clean up and add a table component. In the `index.js` file, import a [Table]() component from portal as shown below:
```javascript
import Head from 'next/head'
import { Table } from 'portal' //import Table component
import styles from '../styles/Home.module.css'
export default function Home() {
const columns = [
{ field: 'id', headerName: 'ID' },
{ field: 'firstName', headerName: 'First name' },
{ field: 'lastName', headerName: 'Last name' },
{ field: 'age', headerName: 'Age' }
];
const rows = [
{ id: 1, lastName: 'Snow', firstName: 'Jon', age: 35 },
{ id: 2, lastName: 'Lannister', firstName: 'Cersei', age: 42 },
{ id: 3, lastName: 'Lannister', firstName: 'Jaime', age: 45 },
{ id: 4, lastName: 'Stark', firstName: 'Arya', age: 16 },
{ id: 7, lastName: 'Clifford', firstName: 'Ferrara', age: 44 },
{ id: 8, lastName: 'Frances', firstName: 'Rossini', age: 36 },
{ id: 9, lastName: 'Roxie', firstName: 'Harvey', age: 65 },
];
return (
<div className={styles.container}>
<Head>
<title>Create Portal App</title>
<link rel="icon" href="/favicon.ico" />
</Head>
<h1 className={styles.title}>
Welcome to <a href="https://nextjs.org">Portal.JS</a>
</h1>
{/* Use table component */}
<Table data={rows} columns={columns} />
</div>
)
}
```
Now, your page should look like the following:
![](https://i.imgur.com/n0vSjY4.png)
> **Note**: You can learn more about individual portal components, as well as their prop types in the [components reference](/docs/components).
## Next Steps
You can check out the following examples built with Portal.js.
* [A portal for a single Frictionless dataset](/learn/ckan)
* [A portal with a CKAN backend](/learn/single-frictionless-dataset)
> The [`examples` directory](https://github.com/datopian/portal.js/tree/main/examples) is regularly updated with different portal examples.
You can also look at the full list of the available components that are provided by Portal.JS in [Components](/docs/components).
## Reference Information
* [Full list of the available components that are provided by Portal.JS](/docs/components)
* [Reference](/docs/references)
## Getting Help
If you have questions about anything related to Portal.js, you're always welcome to ask our community on [GitHub Discussions](https://github.com/datopian/portal.js/discussions).

View File

@ -1,589 +0,0 @@
# Components Reference
Portal.js supports many components that can help you build amazing data portals similar to [this](https://catalog-portal-js.vercel.app/) and [this](https://portal-js.vercel.app/).
In this section, we'll cover all supported components in depth, and help you understand their use as well as the expected properties.
Components are grouped under the following sections:
* [UI](https://github.com/datopian/portal.js/tree/main/src/components/ui): Components like Nav bar, Footer, e.t.c
* [Dataset](https://github.com/datopian/portal.js/tree/main/src/components/dataset): Components used for displaying a Frictionless dataset and resources
* [Search](https://github.com/datopian/portal.js/tree/main/src/components/search): Components used for building a search interface for datasets
* [Blog](https://github.com/datopian/portal.js/tree/main/src/components/blog): Components for building a simple blog for datasets
* [Views](https://github.com/datopian/portal.js/tree/main/src/components/views): Components like charts, tables, maps for generating data views
* [Misc](https://github.com/datopian/portal.js/tree/main/src/components/misc): Miscellaneos components like errors, custom links, etc used for extra design.
### UI Components
In the UI we group all components that can be used for building generic page sections. These are components for building sections like the Navigation bar, Footer, Side pane, Recent datasets, e.t.c.
#### [Nav Component](https://github.com/datopian/portal.js/blob/main/src/components/ui/Nav.js)
To build a navigation bar, you can use the `Nav` component as demonstrated below:
```javascript
import { Nav } from 'portal'
export default function Home(){
const navMenu = [{ title: 'Blog', path: '/blog' },
{ title: 'Search', path: '/search' }]
return (
<>
<Nav logo="/images/logo.png" navMenu={navMenu}/>
...
</>
)
}
```
#### Nav Component Prop Types
Nav component accepts two properties:
* **logo**: A string to an image path. Can be relative or absolute.
* **navMenu**: An array of objects with title and path. E.g : {"[{ title: 'Blog', path: '/blog' },{ title: 'Search', path: '/search' }]"}
#### [Recent Component](https://github.com/datopian/portal.js/blob/main/src/components/ui/Recent.js)
The `Recent` component is used to display a list of recent [datasets](#Dataset) in the home page. This useful if you want to display the most recent dataset users have interacted with in your home page.
To build a recent dataset section, you can use the `Recent` component as demonstrated below:
```javascript
import { Recent } from 'portal'
export default function Home() {
const datasets = [
{
organization: {
name: "Org1",
title: "This is the first org",
description: "A description of the organization 1"
},
title: "Data package title",
name: "dataset1",
description: "description of data package",
resources: [],
},
{
organization: {
name: "Org2",
title: "This is the second org",
description: "A description of the organization 2"
},
title: "Data package title",
name: "dataset2",
description: "description of data package",
resources: [],
},
]
return (
<div>
{/* Use Recent component */}
<Recent datasets={datasets} />
</div>
)
}
```
Note: The `Recent` component is hyperlinked with the dataset name of the organization and the dataset name in the following format:
> `/@<org name>/<dataset name>`
For instance, using the example dataset above, the first component will be link to page:
> `/@org1/dataset1`
and the second will be linked to:
> `/@org2/dataset2`
This is useful to know when generating dynamic pages for each dataset.
#### Recent Component Prop Types
The `Recent` component accepts the following properties:
* **datasets**: An array of [datasets](#Dataset)
### Dataset Components
The dataset component groups together components that can be used for building a dataset UI. These includes components for displaying info about a dataset, resources in a dataset as well as dataset ReadMe.
#### [KeyInfo Component](https://github.com/datopian/portal.js/blob/main/src/components/dataset/KeyInfo.js)
The `KeyInfo` components displays key properties like the number of resources, size, format, licences of in a dataset in tabular form. See example in the `Key Info` section [here](https://portal-js.vercel.app/). To use it, you can import the `KeyInfo` component as demonstrated below:
```javascript
import { KeyInfo } from 'portal'
export default function Home() {
const datapackage = {
"name": "finance-vix",
"title": "VIX - CBOE Volatility Index",
"homepage": "http://www.cboe.com/micro/VIX/",
"version": "0.1.0",
"license": "PDDL-1.0",
"sources": [
{
"title": "CBOE VIX Page",
"name": "CBOE VIX Page",
"web": "http://www.cboe.com/micro/vix/historical.aspx"
}
],
"resources": [
{
"name": "vix-daily",
"path": "vix-daily.csv",
"format": "csv",
"size": 20982,
"mediatype": "text/csv",
}
]
}
return (
<div>
{/* Use KeyInfo component */}
<KeyInfo descriptor={datapackage} resources={datapackage.resources} />
</div>
)
}
```
#### KeyInfo Component Prop Types
KeyInfo component accepts two properties:
* **descriptor**: A [Frictionless data package descriptor](https://specs.frictionlessdata.io/data-package/#descriptor)
* **resources**: An [Frictionless data package resource](https://specs.frictionlessdata.io/data-resource/#introduction)
#### [ResourceInfo Component](https://github.com/datopian/portal.js/blob/main/src/components/dataset/ResourceInfo.js)
The `ResourceInfo` components displays key properties like the name, size, format, modification dates, as well as a download link in a resource object. See an example of a `ResourceInfo` component in the `Data Files` section [here](https://portal-js.vercel.app/).
You can import and use the`ResourceInfo` component as demonstrated below:
```javascript
import { ResourceInfo } from 'portal'
export default function Home() {
const resources = [
{
"name": "vix-daily",
"path": "vix-daily.csv",
"format": "csv",
"size": 20982,
"mediatype": "text/csv",
},
{
"name": "vix-daily 2",
"path": "vix-daily2.csv",
"format": "csv",
"size": 2082,
"mediatype": "text/csv",
}
]
return (
<div>
{/* Use Recent component */}
<ResourceInfo resources={resources} />
</div>
)
}
```
#### ResourceInfo Component Prop Types
ResourceInfo component accepts a single property:
* **resources**: An [Frictionless data package resource](https://specs.frictionlessdata.io/data-resource/#introduction)
#### [ReadMe Component](https://github.com/datopian/portal.js/blob/main/src/components/dataset/Readme.js)
The `ReadMe` component is used for displaying a compiled dataset Readme in a readable format. See example in the `README` section [here](https://portal-js.vercel.app/).
> Note: By compiled ReadMe, we mean ReadMe that has been converted to plain string using a package like [remark](https://www.npmjs.com/package/remark).
You can import and use the`ReadMe` component as demonstrated below:
```javascript
import { ReadMe } from 'portal'
import remark from 'remark'
import html from 'remark-html'
import { useEffect, useState } from 'react'
const readMeMarkdown = `
CBOE Volatility Index (VIX) time-series dataset including daily open, close,
high and low. The CBOE Volatility Index (VIX) is a key measure of market
expectations of near-term volatility conveyed by S&P 500 stock index option
prices introduced in 1993.
## Data
From the [VIX FAQ][faq]:
> In 1993, the Chicago Board Options Exchange® (CBOE®) introduced the CBOE
> Volatility Index®, VIX®, and it quickly became the benchmark for stock market
> volatility. It is widely followed and has been cited in hundreds of news
> articles in the Wall Street Journal, Barron's and other leading financial
> publications. Since volatility often signifies financial turmoil, VIX is
> often referred to as the "investor fear gauge".
[faq]: http://www.cboe.com/micro/vix/faq.aspx
## License
No obvious statement on [historical data page][historical]. Given size and
factual nature of the data and its source from a US company would imagine this
was public domain and as such have licensed the Data Package under the Public
Domain Dedication and License (PDDL).
[historical]: http://www.cboe.com/micro/vix/historical.aspx
`
export default function Home() {
const [readMe, setreadMe] = useState("")
useEffect(() => {
async function processReadMe() {
const processed = await remark()
.use(html)
.process(readMeMarkdown)
setreadMe(processed.toString())
}
processReadMe()
}, [])
return (
<div>
<ReadMe readme={readMe} />
</div>
)
}
```
#### ReadMe Component Prop Types
The `ReadMe` component accepts a single property:
* **readme**: A string of a compiled ReadMe in html format.
### [View Components](https://github.com/datopian/portal.js/tree/main/src/components/views)
View components is a set of components that can be used for displaying dataset views like charts, tables, maps, e.t.c.
#### [Chart Component](https://github.com/datopian/portal.js/blob/main/src/components/views/Chart.js)
The `Chart` components exposes different chart components like Plotly Chart, Vega charts, which can be used for showing graphs. See example in the `Graph` section [here](https://portal-js.vercel.app/).
To use a chart component, you need to compile and pass a view spec as props to the chart component.
Each Chart type have their specific spec, as explained in this [doc](https://specs.frictionlessdata.io/views/#graph-spec).
In the example below, we assume there's a compiled Plotly spec:
```javascript
import { PlotlyChart } from 'portal'
export default function Home({plotlySpec}) {
return (
< div >
<PlotlyChart spec={plotlySpec} />
</div>
)
}
```
> Note: You can compile views using the [datapackage-render](https://github.com/datopian/datapackage-views-js) library, as demonstrated in [this example](https://github.com/datopian/portal.js/blob/main/examples/dataset-frictionless/lib/utils.js).
#### Chart Component Prop Types
KeyInfo component accepts two properties:
* **spec**: A compiled view spec depending on the chart type.
#### [Table Component](https://github.com/datopian/portal.js/blob/main/examples/dataset-frictionless/components/Table.js)
The `Table` component is used for displaying dataset resources as a tabular grid. See example in the `Data Preview` section [here](https://portal-js.vercel.app/).
To use a Table component, you have to pass an array of data and columns as demonstrated below:
```javascript
import { Table } from 'portal' //import Table component
export default function Home() {
const columns = [
{ field: 'id', headerName: 'ID' },
{ field: 'firstName', headerName: 'First name' },
{ field: 'lastName', headerName: 'Last name' },
{ field: 'age', headerName: 'Age' }
];
const data = [
{ id: 1, lastName: 'Snow', firstName: 'Jon', age: 35 },
{ id: 2, lastName: 'Lannister', firstName: 'Cersei', age: 42 },
{ id: 3, lastName: 'Lannister', firstName: 'Jaime', age: 45 },
{ id: 4, lastName: 'Stark', firstName: 'Arya', age: 16 },
{ id: 7, lastName: 'Clifford', firstName: 'Ferrara', age: 44 },
{ id: 8, lastName: 'Frances', firstName: 'Rossini', age: 36 },
{ id: 9, lastName: 'Roxie', firstName: 'Harvey', age: 65 },
];
return (
<Table data={data} columns={columns} />
)
}
```
> Note: Under the hood, Table component uses the [DataGrid Material UI table](https://material-ui.com/components/data-grid/), and as such all supported params in data and columns are supported.
#### Table Component Prop Types
Table component accepts two properties:
* **data**: An array of column names with properties: e.g {'[{field: "col1", headerName: "col1"}, {field: "col2", headerName: "col2"}]'}
* **columns**: An array of data objects e.g. {'[ {col1: 1, col2: 2}, {col1: 5, col2: 7} ]'}
### [Search Components](https://github.com/datopian/portal.js/tree/main/src/components/search)
Search components groups together components that can be used for creating a search interface. This includes search forms, search item as well as search result list.
#### [Form Component](https://github.com/datopian/portal.js/blob/main/src/components/search/Form.js)
The search`Form` component is a simple search input and submit button. See example of a search form [here](https://catalog-portal-js.vercel.app/search).
The search `form` requires a submit handler (`handleSubmit`). This handler function receives the search term, and handles actual search.
In the example below, we demonstrate how to use the `Form` component.
```javascript
import { Form } from 'portal'
export default function Home() {
const handleSearchSubmit = (searchQuery) => {
// Write your custom code to perform search in db
console.log(searchQuery);
}
return (
<Form
handleSubmit={handleSearchSubmit} />
)
}
```
#### Form Component Prop Types
The `Form` component accepts a single property:
* **handleSubmit**: A function that receives the search text, and can be customize to perform the actual search.
#### [Item Component](https://github.com/datopian/portal.js/blob/main/src/components/search/Item.js)
The search`Item` component can be used to display a single search result.
In the example below, we demonstrate how to use the `Item` component.
```javascript
import { Item } from 'portal'
export default function Home() {
const datapackage = {
"name": "finance-vix",
"title": "VIX - CBOE Volatility Index",
"homepage": "http://www.cboe.com/micro/VIX/",
"version": "0.1.0",
"description": "This is a test organization description",
"resources": [
{
"name": "vix-daily",
"path": "vix-daily.csv",
"format": "csv",
"size": 20982,
"mediatype": "text/csv",
}
]
}
return (
<Item dataset={datapackage} />
)
}
```
#### Item Component Prop Types
The `Item` component accepts a single property:
* **dataset**: A [Frictionless data package descriptor](https://specs.frictionlessdata.io/data-package/#descriptor)
#### [ItemTotal Component](https://github.com/datopian/portal.js/blob/main/src/components/search/Item.js)
The search`ItemTotal` is a simple component for displaying the total search result
In the example below, we demonstrate how to use the `ItemTotal` component.
```javascript
import { ItemTotal } from 'portal'
export default function Home() {
//do some custom search to get results
const search = (text) => {
return [{ name: "data1" }, { name: "data2" }]
}
//get the total result count
const searchTotal = search("some text").length
return (
<ItemTotal count={searchTotal} />
)
}
```
#### ItemTotal Component Prop Types
The `ItemTotal` component accepts a single property:
* **count**: An integer of the total number of results.
### [Blog Components](https://github.com/datopian/portal.js/tree/main/src/components/blog)
These are group of components for building a portal blog. See example of portal blog [here](https://catalog-portal-js.vercel.app/blog)
#### [PostList Components](https://github.com/datopian/portal.js/tree/main/src/components/misc)
The `PostList` component is used to display a list of blog posts with the title and a short excerpts from the content.
In the example below, we demonstrate how to use the `PostList` component.
```javascript
import { PostList } from 'portal'
export default function Home() {
const posts = [
{ title: "Blog post 1", excerpt: "This is the first blog excerpts in this list." },
{ title: "Blog post 2", excerpt: "This is the second blog excerpts in this list." },
{ title: "Blog post 3", excerpt: "This is the third blog excerpts in this list." },
]
return (
<PostList posts={posts} />
)
}
```
#### PostList Component Prop Types
The `PostList` component accepts a single property:
* **posts**: An array of post list objects with the following properties:
```javascript
[
{
title: "The title of the blog post",
excerpt: "A short excerpt from the post content",
},
]
```
#### [Post Components](https://github.com/datopian/portal.js/tree/main/src/components/misc)
The `Post` component is used to display a blog post. See an example of a blog post [here](https://catalog-portal-js.vercel.app/blog/nyt-pa-platformen-opdateringsfrekvens-og-andres-data)
In the example below, we demonstrate how to use the `Post` component.
```javascript
import { Post } from 'portal'
import * as dayjs from 'dayjs' //For converting UTC time to relative format
import relativeTime from 'dayjs/plugin/relativeTime'
dayjs.extend(relativeTime)
export default function Home() {
const post = {
title: "This is a sample blog post",
content: `<h1>A simple header</h1>
The PostList component is used to display a list of blog posts
with the title and a short excerpts from the content.
In the example below, we demonstrate how to use the PostList component.`,
createdAt: dayjs().to(dayjs(1620649596902)),
featuredImage: "https://pixabay.com/get/ge9a766d1f7b5fe0eccbf0f439501a2cf2b191997290e7ab15e6a402574acc2fdba48a82d278dca3547030e0202b7906d_640.jpg"
}
return (
<Post post={post} />
)
}
```
#### Post Component Prop Types
The `Post` component accepts a single property:
* **post**: An object with the following properties:
```javascript
{
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>
}
```
### [Misc Components](https://github.com/datopian/portal.js/tree/main/src/components/misc)
These are group of miscellaneous/extra components for extending your portal. They include components like Errors, custom links, etc.
#### [Error Component](https://github.com/datopian/portal.js/blob/main/src/components/misc/Error.js)
The `Error` component is used to display a custom error message.
In the example below, we demonstrate how to use the `Error` component.
```javascript
import { Error } from 'portal'
export default function Home() {
return (
<Error message="An error occured when loading the file!" />
)
}
```
#### Error Component Prop Types
The `Error` component accepts a single property:
* **message**: A string with the error message to display.
#### [Custom Component](https://github.com/datopian/portal.js/blob/main/src/components/misc/Error.js)
The `CustomLink` component is used to create a link with a consistent style to other portal components.
In the example below, we demonstrate how to use the `CustomLink` component.
```javascript
import { CustomLink } from 'portal'
export default function Home() {
return (
<CustomLink url="/blog" title="Goto Blog" />
)
}
```
#### CustomLink Component Prop Types
The `CustomLink` component accepts the following properties:
* **url**: A string. The relative or absolute url of the link.
* **title**: A string. The title of the link

View File

@ -1,54 +0,0 @@
# Concepts and Terms
In this section, we explain some of the terms and concepts used throughtout the portal.js documentation.
> Some of these concepts are part of official specs, and when appropriate, we'll link to the sources where you can get more details.
### Dataset
A dataset extends the [Frictionless data package](https://specs.frictionlessdata.io/data-package/#metadata) to add an extra organization property. The organization property describes the organization the dataset belongs to, and it should have the following properties:
```javascript
organization = {
name: "some org name",
title: "Some optional org title",
description: "A description of the organization"
}
```
An example of dataset with organization properties is given below:
```javascript
datasets = [{
organization: {
name: "some org name",
title: "Some optional org title",
description: "A description of the organization"
},
title: "Data package title",
name: "Data package name",
description: "description of data package",
resources: [...],
licences: [...],
sources: [...]
}
]
```
### Resource
TODO
### View spec
---
## Deploying portal build to github pages
* [Deploying single frictionless dataset to Github](./scripts/README.md)
## Showcases
### Single Dataset with Default Theme
![Single Dataset Example](./examples/dataset-frictionless/assets/demo.gif)

View File

@ -1,5 +0,0 @@
---
title: Gallery
---
Come back soon!

View File

@ -1,95 +0,0 @@
# Getting Started
It's no secret that creating data portals and data-driven applications can be quite complex nowadays. Fortunately, there are some projects available which simplify things and help you build platforms faster.
[CKAN](https://ckan.org/), [Jupyter](https://jupyter.org/) and other tools are very good examples of that.
Even still, there's a high learning curve before you can build a proper application. That's because you need to learn about Python, templating, data loading and so on. If you'd like to integrate content or rich visualizations things are even more complex.
**So, we need something simple but customizable.**
Think about how apps are created as a frontend developer. You create some files, write some code, load some data and then simply deploy it. We don't have to worry about Docker, Kubernetes, data storage, Postgres etc.
That's exactly what we do with Portal.js. Built in pure Javascript and React on top of the awesome Next.js framework. Here are some the cool features Portal.js brings to the table:
- 🗺️ Unified sites: present data and content in one seamless site, pulling datasets from a DMS (e.g. CKAN) and content from a CMS (e.g. wordpress)
- 👩‍💻 Developer friendly: built with familiar frontend tech Javascript, React etc
- 🔋 Batteries included: Full set of presentation and portal components out of the box e.g. data tables, graphs, maps plus catalog search, dataset showcase, blog etc.
- 🎨 Easy to theme and customize: installable themes, use standard CSS and React+CSS tooling. Add new routes quickly.
- 🧱 Extensible: quickly extend and develop/import your own React components
- 📝 Well documented: full set of documentation plus the documentation of NextJS and Apollo.
- 🚀 Built on NextJS framework: so everything in NextJS for free React, SSR, static site generation, huge number of examples and integrations etc.
- SSR => unlimited number of pages, SEO etc whilst still using React.
- Static Site Generation (SSG) (good for small sites) => ultra-simple deployment, great performance and lighthouse scores etc
Sounds great, right? Let's give it a try.
> This tutorial assumes basic knowledge of JavaScript, React and Nextjs. If you are not familiar with React or Nextjs, it is advisable to learn them first. We provide some links below to get you started:
>
> * [Learn NextJS](https://nextjs.org/docs/getting-started)
> * [Getting started with React](https://reactjs.org/docs/getting-started.html#learn-react)
## Create a Portal.JS app
### Setup
First, lets make sure that your development environment is ready.
* If you dont have Node.js installed, [install it from here](https://nodejs.org/en/). Youll need Node.js version 10.13 or later.
* Youll be using your own text editor and terminal app for this tutorial.
If you are on Windows, we recommend downloading Git for Windows and use Git Bash that comes with it, which supports the UNIX-specific commands in this tutorial. Windows Subsystem for Linux (WSL) is another option.
### Create a Portal.js App
To create a Portal.js app, open your terminal, cd into the directory youd like to create the app in, and run the following command:
```
npx create-next-app portaljs-dataset --use-npm --example "https://github.com/datopian/portal.js/tree/main/examples/default"
```
### Run the development server
You now have a new directory called portaljs-dataset. Lets cd into it:
```
cd portaljs-dataset
```
Then, run the following command:
```
npm run dev
```
This starts your Portal.js apps "development server" (more on this later) on port 3000.
Lets check to see if its working. Open http://localhost:3000 from your browser and you should see the following page:
![Portal.js screen](/portaljs-screen.png)
### Edit the page
Portal.js app is a Next.js/React.js based project. To edit the page follow these steps:
1. Open the project in your text editor.
2. Go to `/pages/index.js` file.
3. Find the `h2` tag with text that says **"Yay, the portal is open 🌀"** and change it to **"Hello World!"**.
4. Save the file.
Once you've changed the file and saved it, the page on `localhost:3000` should update:
![Portal.js screen update](/portaljs-screen-update.png)
We won't dive into details of how to edit the pages as our focus is presenting data. To learn more about how to use Next.js and/or React, please visit the following sites:
* [Learn NextJS](https://nextjs.org/docs/getting-started)
* [Getting started with React](https://reactjs.org/docs/getting-started.html#learn-react)
## Next steps
* [Presenting a dataset]()
* [Putting your portal online]()
* Deploy to GitHub Pages - [learn/deploy-to-gh-pages](/learn/deploy-to-gh-pages).
* Learn how to build a portal for a single frictionless dataset - [learn/single-frictionless-dataset](/learn/single-frictionless-dataset).
* Learn how to use Portal.js as a frontend for CKAN - [learn/ckan](/learn/ckan).

View File

@ -1,218 +0,0 @@
Live DEMOs:
- https://catalog-portal-js.vercel.app
- https://ckan-enterprise-frontend.vercel.app/
## Create a Portal app for CKAN
To create a Portal app, run the following command in your terminal:
```console
npx create-next-app -e https://github.com/datopian/portal.js/tree/main/examples/ckan
```
> NB: Under the hood, this uses the tool called create-next-app, which bootstraps an app for you based on our CKAN example.
## Guide
### Styling 🎨
We use Tailwind as a CSS framework. Take a look at `/styles/globals.css` to see what we're importing from Tailwind bundle. You can also configure Tailwind using `tailwind.config.js` file.
Have a look at Next.js support of CSS and ways of writing CSS:
https://nextjs.org/docs/basic-features/built-in-css-support
### Backend
So far the app is running with mocked data behind. You can connect CMS and DMS backends easily via environment variables:
```console
$ export DMS=http://ckan:5000
$ export CMS=http://myblog.wordpress.com
```
> Note that we don't yet have implementations for the following CKAN features:
>
> - Activities
> - Auth
> - Groups
> - Facets
### Routes
These are the default routes set up in the "starter" app.
- Home `/`
- Search `/search`
- Dataset `/@org/dataset`
- Resource `/@org/dataset/r/resource`
- Organization `/@org`
- Collection (aka group in CKAN) (?) - suggest to merge into org
- Static pages, eg, `/about` etc. from CMS or can do it without external CMS, e.g., in Next.js.
### New Routes
You can create new routes in `/pages` directory where each file is associated with a route based on its name. We suggest using [Next.JS docs][] for more detailed information.
[next.js docs]: https://nextjs.org/docs/basic-features/pages
### Data fetching
We use Apollo client which allows us to query data with GraphQL. We have setup CKAN API for the demo (it uses demo.ckan.org as DMS):
http://portal.datopian1.now.sh/
Note that we don't have Apollo Server but we connect CKAN API using [`apollo-link-rest`](https://www.apollographql.com/docs/link/links/rest/) module. You can see how it works in [lib/apolloClient.ts](https://github.com/datopian/portal/blob/master/lib/apolloClient.ts) and then have a look at [pages/\_app.tsx](https://github.com/datopian/portal/blob/master/pages/_app.tsx).
For development/debugging purposes, we suggest installing the Chrome extension - https://chrome.google.com/webstore/detail/apollo-client-developer-t/jdkknkkbebbapilgoeccciglkfbmbnfm.
### I18n configuration
Portal.js is configured by default to support both `English` and `French` subpath for language translation. But for subsequent users, this following steps can be used to configure i18n for other languages;
1. Update `next.config.js`, to add more languages to the i18n locales
```js
i18n: {
locales: ['en', 'fr', 'nl-NL'], // add more language to the list
defaultLocale: 'en', // set the default language to use
},
```
2. Create a folder for the language in `locales` --> `locales/en-Us`
3. In the language folder, different namespace files (json) can be created for each translation. For the `index.js` use-case, I named it `common.json`
```json
// locales/en/common.json
{
"title" : "Portal js in English",
}
// locales/fr/common.json
{
"title" : "Portal js in French",
}
```
4. To use on pages using Server-side Props.
```js
import { loadNamespaces } from './_app';
import useTranslation from 'next-translate/useTranslation';
const Home: React.FC = ()=> {
const { t } = useTranslation();
return (
<div>{t(`common:title`)}</div> // we use common and title base on the common.json data
);
};
export const getServerSideProps: GetServerSideProps = async ({ locale }) => {
........ ........
return {
props : {
_ns: await loadNamespaces(['common'], locale),
}
};
};
```
5. Go to the browser and view the changes using language subpath like this `http://localhost:3000` and `http://localhost:3000/fr`. **Note** The subpath also activate chrome language Translator
### Pre-fetch data in the server-side
When visiting a dataset page, you may want to fetch the dataset metadata in the server-side. To do so, you can use `getServerSideProps` function from NextJS:
```javascript
import { GetServerSideProps } from 'next';
import { initializeApollo } from '../lib/apolloClient';
import gql from 'graphql-tag';
const QUERY = gql`
query dataset($id: String) {
dataset(id: $id) @rest(type: "Response", path: "package_show?{args}") {
result
}
}
`;
...
export const getServerSideProps: GetServerSideProps = async (context) => {
const apolloClient = initializeApollo();
await apolloClient.query({
query: QUERY,
variables: {
id: 'my-dataset'
},
});
return {
props: {
initialApolloState: apolloClient.cache.extract(),
},
};
};
```
This would fetch the data from DMS and save it in the Apollo cache so that we can query it again from the components.
### Access data from a component
Consider situation when rendering a component for org info on the dataset page. We already have pre-fetched dataset metadata that includes `organization` property with attributes such as `name`, `title` etc. We can now query only organization part for our `Org` component:
```javascript
import { useQuery } from '@apollo/react-hooks';
import gql from 'graphql-tag';
export const GET_ORG_QUERY = gql`
query dataset($id: String) {
dataset(id: $id) @rest(type: "Response", path: "package_show?{args}") {
result {
organization {
name
title
image_url
}
}
}
}
`;
export default function Org({ variables }) {
const { loading, error, data } = useQuery(
GET_ORG_QUERY,
{
variables: { id: 'my-dataset' }
}
);
...
const { organization } = data.dataset.result;
return (
<>
{organization ? (
<>
<img
src={
organization.image_url
}
className="h-5 w-5 mr-2 inline-block"
/>
<Link href={`/@${organization.name}`} className="font-semibold text-primary underline">
{organization.title || organization.name}
</Link>
</>
) : (
''
)}
</>
);
}
```

View File

@ -1,251 +0,0 @@
# Deploying data on Github using Portal.js and Github pages
---
**Use Case:**
---
You have some data in a Github repo and you'd like to deploy it online using "portal" so that it is easy for others to view, explore and use.
---
Here we show how you can use portal.js plus github actions to deploy your dataset in minutes and keep it updated as you make changes.
The example focuses on the case of a [Frictionless dataset][fd] but it works for any dataset type supported by portal.js.
We provide three options on how to do this and recommend using the first one unless you really want to get hands on:
* Deploying datasets automatically by setting up a github actions script.
* Deploying datasets from a local bash script with portal code commits
* Deploying datasets from a local bash script without portal code commits
[fd]: https://frictionlessdata.io/data-packages/
## Deploy datasets automatically by setting up a github actions script
The github actions below will automatically build and deploy a single page, Frictionless dataset to `gh-pages` branch. Follow the steps below to achieve this:
1. Create a secret so we can automatically commit to gh-pages branch (see below)
2. Set up the github action to build portal to your dataset and deploy it (see below)
3. Wait for your page to build and then setup github pages (see below)
4. View the results: visit `https://<your github username>/github.io/<dataset repo name>/`
### Step 1
In the dataset repository you want to deploy, create a github secret with the name `PORTAL_REPO_NAME` and the value should be the name of the repository.
See steps on creating a secret [here](https://docs.github.com/en/actions/reference/encrypted-secrets)
<img src="/scripts/assets/secrets.png" />
### Step 2
In the dataset repository you want deploy create a `.github/workflow` directory and add a `main.yml` file with the following content (you can also view/download this [action file here](scripts/actions/single-dataset-ssg.yml):
```bash
name: github pages
on:
push:
branches:
- master
- main
jobs:
run:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup Node
uses: actions/setup-node@v2.1.2
with:
node-version: '12.x'
- name: Build datasets
env:
PORTAL_REPO_NAME: ${{ secrets.PORTAL_REPO_NAME }}
run: |
curl https://raw.githubusercontent.com/datopian/portal.js/main/site/public/scripts/single-dataset-no-commit.sh > portal.sh
git config --local user.email "$(git log --format='%ae' HEAD^!)"
git config --local user.name "$(git log --format='%an' HEAD^!)"
source ./portal.sh
```
Then, commit and push your code.
```bash
git add .
git commit -m "Build dataset page"
git push
```
### Step 3
Wait for a while as your page builds, and once you see the green check mark, navigate to your repository's github `pages` in settings, set the `source` to `gh-pages` and folder to `/root`:
<img src='/scripts/assets/sdnocommit.png' />
## Deploy single dataset without commiting portal.js code
Users who want to deploy datasets from a local bash script without saving/commiting the portal.js code, can use the script shown below.
Using this script means you do not have access to the portal.js code used to generate the dataset page, and as such cannot modify/extend it.
This script creates and commit only the build/output files to the gh-pages branch. Follow the steps below to achieve this.
### Step 1
Clone/Pull the dataset repository you want deploy. For example:
```bash
git clone https://github.com/datasets/finance-vix
cd finance-vix
```
### Step 2
In a terminal, export an env variable with the name of your dataset github repo. For example if deploying https://github.com/datasets/finance-vix, then export the name as:
```bash
export PORTAL_REPO_NAME=finance-vix
```
### Step 3
In the dataset repository's root folder, create a file called `portal.sh` and paste the following [content](/scripts/single-dataset-no-commit.sh):
```bash
#!/bin/bash
git checkout -b gh-pages
git rm -r --cached .
rm -rf portal
mkdir -p portal
npx create-next-app portal -e https://github.com/datopian/portal.js/tree/main/examples/dataset-frictionless
mkdir portal/public/dataset
cp -a ./data portal/public/dataset
cp -a ./datapackage.json portal/public/dataset
cp -a ./README.md portal/public/dataset
PORTAL_DATASET_PATH=$PWD"/portal/public/dataset"
export PORTAL_DATASET_PATH
cd portal
assetPrefix='"/'$PORTAL_REPO_NAME'/"'
basePath='"/'$PORTAL_REPO_NAME'"'
echo 'module.exports = {assetPrefix:' ${assetPrefix}', basePath: '${basePath}' }' > next.config.js ## This ensures css and public folder works
yarn export
cd ..
cp -R -a portal/out/* ./
touch .nojekyll
git add $PWD'/_next' $PWD'/index.html' $PWD'/dataset' $PWD'/404.html' $PWD'/.nojekyll' $PWD'/favicon.ico'
git commit -m "Build new dataset page"
git push origin gh-pages
```
### Step 4
Run the bash script in a terminal with:
```bash
source portal.sh
```
> Note: Use `source` instead of `bash` so that the script can work well with environment variables.
### Step 5
Go to your repository's github `pages` in setting and set the Branch to gh-pages and folder to root:
<img src='/scripts/assets/sdnocommit.png' />
### Step 6
Open your deployed site at `https://<your github username>/github.io/<dataset repo name>`
## Deploy single dataset with portal commit
Users who want access to the portal.js code used for generating the dataset page can use the script shown in the following section.
This script creates and commits the portal.js code to the root branch and also adds an automated script to deploy to gh-page. Follow the steps below to use this script.
### Step 1
Create a Github Personal Access Token (PAT). See steps [here](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token)
### Step 2
In the dataset repository you want to deploy, create a github secret with the name `PORTAL_NEXT_TOKEN`. The value should be the PAT created in step 1. See steps on creating a secret [here](https://docs.github.com/en/actions/reference/encrypted-secrets)
> Note: Without the PAT and the secret configured, the automatic build will fail.
### Step 3
Clone/Pull the dataset repository you want deploy. For example:
```bash
git clone https://github.com/datasets/finance-vix
cd finance-vix
```
### Step 4
In your computer's terminal/command prompt, export an environment variable with the name of your dataset's github repo.
For example if you want to deploy the dataset at https://github.com/datasets/finance-vix, then export the name using the command:
```bash
export PORTAL_REPO_NAME=finance-vix
```
### Step 5
Create a file called `portal.sh` and paste the following [content](/scripts/single-dataset-commit.sh):
```bash
#!/bin/bash
rm -rf portal
mkdir -p portal
npx create-next-app portal -e https://github.com/datopian/portal.js/tree/main/examples/dataset-frictionless
mkdir portal/public/dataset
cp -a ./data portal/public/dataset
cp -a ./datapackage.json portal/public/dataset
cp -a ./README.md portal/public/dataset
PORTAL_DATASET_PATH=$PWD"/portal/public/dataset"
export PORTAL_DATASET_PATH
mkdir -p .github && mkdir -p .github/workflows && touch .github/workflows/main.yml
curl https://raw.githubusercontent.com/datopian/portal.js/main/site/public/scripts/gh-page-builder-action.yml > .github/workflows/main.yml
cd portal
assetPrefix='"/'$PORTAL_REPO_NAME'/"'
basePath='"/'$PORTAL_REPO_NAME'"'
echo 'module.exports = {assetPrefix:' ${assetPrefix}', basePath: '${basePath}' }' > next.config.js ## This ensures css and public folder works
cd ..
git add .
git commit -m "Add dataset build feature"
git push
echo "Portal generated, please push your code to github"
```
### Step 6
Run the bash script with:
```bash
source portal.sh
```
> Note: Use `source` instead of `bash` so that the script can work well with environment variables.
### Step 7
Go to your repository's github `pages` in setting and set the Branch to gh-pages and folder to root:
<img src='/scripts/assets/sdnocommit.png' />
### Step 8
Open your deployed site at `https://<your github username>/github.io/<dataset repo name>`

View File

@ -1,32 +0,0 @@
Live Demo:
- https://portal-js.vercel.app/
## Create a single frictionless dataset portal
The dataset should be a frictionless dataset i.e. it should have a [datapackage.json](https://specs.frictionlessdata.io/data-package/)
Create a frictionless dataset portal app from the default template by executing the following command in your terminal:
```
$ npx create-next-app -e https://github.com/datopian/portal.js/tree/main/examples/dataset-frictionless
```
> Choose a name for your portal when prompted e.g. your-portal
Next, connect the frictionless dataset to `your-portal` by declaring the path to the directory level that contains the `datapackage.json` via an environment variable by executing the following command in your terminal:
```
$ cd your-portal
$ export PORTAL_DATASET_PATH=path/to/your/dataset
```
In `your-portal` directory, run the command below in your terminal to start the portal:
```
$ yarn dev
```
Open the page in your browser via the localhost url(usually http://localhost:3000) returned in the terminal to see your frictionless dataset portal.
### Styling 🎨
We use Tailwind as a CSS framework. Take a look at `/styles/tailwind.css` to see what we're importing from Tailwind bundle. You can also configure Tailwind using `tailwind.config.js` file.
Have a look at Next.js support of CSS and ways of writing CSS:
https://nextjs.org/docs/basic-features/built-in-css-support

View File

@ -1,6 +0,0 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
declare module '*.svg' {
const content: any;
export const ReactComponent: any;
export default content;
}

View File

@ -1,11 +0,0 @@
/* eslint-disable */
export default {
displayName: 'site',
preset: '../../jest.preset.js',
transform: {
'^(?!.*\\.(js|jsx|ts|tsx|css|json)$)': '@nrwl/react/plugins/jest',
'^.+\\.[tj]sx?$': ['babel-jest', { presets: ['@nrwl/next/babel'] }],
},
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
coverageDirectory: '../../coverage/apps/site',
};

View File

@ -1,13 +0,0 @@
import {
SimpleLayout,
DocsLayout,
UnstyledLayout,
BlogLayout,
} from "@flowershow/core";
export default {
simple: SimpleLayout,
docs: DocsLayout,
unstyled: UnstyledLayout,
blog: BlogLayout,
};

View File

@ -1,35 +0,0 @@
// Used by Data Literate
import matter from 'gray-matter'
import toc from 'remark-toc'
import slug from 'remark-slug'
import gfm from 'remark-gfm'
import footnotes from 'remark-footnotes'
import { serialize } from 'next-mdx-remote/serialize'
/**
* Parse a markdown or MDX file to an MDX source form + front matter data
*
* @source: the contents of a markdown or mdx file
* @returns: { mdxSource: mdxSource, frontMatter: ...}
*/
const parse = async function(source) {
const { content, data } = matter(source)
const mdxSource = await serialize(content, {
// Optionally pass remark/rehype plugins
mdxOptions: {
remarkPlugins: [gfm, toc, slug, footnotes],
rehypePlugins: [],
},
scope: data,
})
return {
mdxSource: mdxSource,
frontMatter: data
}
}
export default parse

View File

@ -1,26 +0,0 @@
import { siteConfig } from "config/siteConfig";
import clientPromise from "lib/mddb";
export const getAuthorsDetails = async (authors: string[]) => {
const mddb = await clientPromise;
const allPeople = await mddb.getFiles({ folder: "people" });
// Temporary, flowershow UI component expects contentlayer obj props
const allPeopleMetadata = allPeople.map((p) => p.metadata);
let blogAuthors = [];
if (authors) {
blogAuthors = authors;
} else if (siteConfig.defaultAuthor) {
blogAuthors = [siteConfig.defaultAuthor];
}
return blogAuthors.map((author) => {
const person = allPeopleMetadata.find(
({ id, name }) => id === author || name === author
);
return person ?? { name: author, avatar: siteConfig.avatarPlaceholder };
});
};

View File

@ -1,33 +0,0 @@
import matter from 'gray-matter'
import toc from 'remark-toc'
import slug from 'remark-slug'
import gfm from 'remark-gfm'
import footnotes from 'remark-footnotes'
import { serialize } from 'next-mdx-remote/serialize'
/**
* Parse a markdown or MDX file to an MDX source form + front matter data
*
* @source: the contents of a markdown or mdx file
* @returns: { mdxSource: mdxSource, frontMatter: ...}
*/
const parse = async function(source) {
const { content, data } = matter(source)
const mdxSource = await serialize(content, {
// Optionally pass remark/rehype plugins
mdxOptions: {
remarkPlugins: [gfm, toc, slug, footnotes],
rehypePlugins: [],
},
scope: data,
})
return {
mdxSource: mdxSource,
frontMatter: data
}
}
export default parse

View File

@ -1,99 +0,0 @@
import matter from "gray-matter";
import mdxmermaid from "mdx-mermaid";
import { h } from "hastscript";
import remarkCallouts from "@flowershow/remark-callouts";
import remarkEmbed from "@flowershow/remark-embed";
import remarkGfm from "remark-gfm";
import remarkMath from "remark-math";
import remarkSmartypants from "remark-smartypants";
import remarkToc from "remark-toc";
import remarkWikiLink from "@flowershow/remark-wiki-link";
import rehypeAutolinkHeadings from "rehype-autolink-headings";
import rehypeKatex from "rehype-katex";
import rehypeSlug from "rehype-slug";
import rehypePrismPlus from "rehype-prism-plus";
import { serialize } from "next-mdx-remote/serialize";
/**
* Parse a markdown or MDX file to an MDX source form + front matter data
*
* @source: the contents of a markdown or mdx file
* @format: used to indicate to next-mdx-remote which format to use (md or mdx)
* @returns: { mdxSource: mdxSource, frontMatter: ...}
*/
const parse = async function (source, format, scope) {
const { content, data } = matter(source);
const mdxSource = await serialize(
{ value: content, path: format },
{
// Optionally pass remark/rehype plugins
mdxOptions: {
remarkPlugins: [
remarkEmbed,
remarkGfm,
[remarkSmartypants, { quotes: false, dashes: "oldschool" }],
remarkMath,
remarkCallouts,
remarkWikiLink,
[
remarkToc,
{
heading: "Table of contents",
tight: true,
},
],
[mdxmermaid, {}],
],
rehypePlugins: [
rehypeSlug,
[
rehypeAutolinkHeadings,
{
properties: { className: "heading-link" },
test(element) {
return (
["h2", "h3", "h4", "h5", "h6"].includes(element.tagName) &&
element.properties?.id !== "table-of-contents" &&
element.properties?.className !== "blockquote-heading"
);
},
content() {
return [
h(
"svg",
{
xmlns: "http:www.w3.org/2000/svg",
fill: "#ab2b65",
viewBox: "0 0 20 20",
className: "w-5 h-5",
},
[
h("path", {
fillRule: "evenodd",
clipRule: "evenodd",
d: "M9.493 2.853a.75.75 0 00-1.486-.205L7.545 6H4.198a.75.75 0 000 1.5h3.14l-.69 5H3.302a.75.75 0 000 1.5h3.14l-.435 3.148a.75.75 0 001.486.205L7.955 14h2.986l-.434 3.148a.75.75 0 001.486.205L12.456 14h3.346a.75.75 0 000-1.5h-3.14l.69-5h3.346a.75.75 0 000-1.5h-3.14l.435-3.147a.75.75 0 00-1.486-.205L12.045 6H9.059l.434-3.147zM8.852 7.5l-.69 5h2.986l.69-5H8.852z",
}),
]
),
];
},
},
],
[rehypeKatex, { output: "mathml" }],
[rehypePrismPlus, { ignoreMissing: true }],
],
format,
},
scope: { ...scope, ...data },
}
);
return {
mdxSource: mdxSource,
frontMatter: data,
};
};
export default parse;

View File

@ -1,24 +0,0 @@
import { MarkdownDB } from "@flowershow/markdowndb";
// import config from "./markdowndb.config.js";
// TODO get this path from markdowndb.config.js or something
const dbPath = "markdown.db";
//
// if (!config.dbPath)
// throw new Error("Invalid/Missing path in markdowndb.config.js");
// }
//
// const dbPath = config.dbPath;
// OR
// const dbOptions = config.dbOptions;
const client = new MarkdownDB({
client: "sqlite3",
connection: {
filename: dbPath,
},
});
const clientPromise = client.init();
export default clientPromise;

View File

@ -1,23 +0,0 @@
import fs from 'fs'
import glob from 'glob'
import path from 'path'
// POSTS_PATH is useful when you want to get the path to a specific file
export const POSTS_PATH = path.join(process.cwd(), 'content')
const walkSync = (dir, filelist = []) => {
fs.readdirSync(dir).forEach(file => {
filelist = fs.statSync(path.join(dir, file)).isDirectory()
? walkSync(path.join(dir, file), filelist)
: filelist.concat(path.join(dir, file))
})
return filelist
}
// postFilePaths is the list of all mdx files inside the POSTS_PATH directory
export const postFilePaths = walkSync(POSTS_PATH)
.map((file) => { return file.slice(POSTS_PATH.length) })
// Only include md(x) files
.filter((path) => /\.mdx?$/.test(path))

View File

@ -1,13 +0,0 @@
import remark from 'remark'
import html from 'remark-html'
import fs from 'fs'
export async function formatMD(mdFilePath) {
const mdFile = fs.readFileSync(mdFilePath, "utf-8")
const processed = await remark()
.use(html)
.process(mdFile)
const readmeHtml = processed.toString()
return readmeHtml
}

Binary file not shown.

View File

@ -1,5 +0,0 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.

View File

@ -1,17 +0,0 @@
//@ts-check
// eslint-disable-next-line @typescript-eslint/no-var-requires
const { withNx } = require('@nrwl/next/plugins/with-nx');
/**
* @type {import('@nrwl/next/plugins/with-nx').WithNxOptions}
**/
const nextConfig = {
nx: {
// Set this to true if you would like to use SVGR
// See: https://github.com/gregberge/svgr
svgr: false,
},
};
module.exports = withNx(nextConfig);

View File

@ -1,76 +0,0 @@
import fs from "fs";
import parse from "../lib/markdown.mjs";
import MDXPage from "../components/MDXPage";
import clientPromise from "@/lib/mddb";
import { getAuthorsDetails } from "lib/getAuthorsDetails";
import Layout from "components/Layout";
export default function DRDPage({ source, frontMatter }) {
source = JSON.parse(source);
frontMatter = JSON.parse(frontMatter);
return (
<Layout title={frontMatter.title}>
<MDXPage source={source} frontMatter={frontMatter} />
</Layout>
);
}
export const getStaticProps = async ({ params }) => {
const urlPath = params.slug ? params.slug.join("/") : "";
const mddb = await clientPromise;
const dbFile = await mddb.getFileByUrl(urlPath);
const dbBacklinks = await mddb.getLinks({
fileId: dbFile._id,
direction: "backward",
});
// TODO temporary solution, we will have a method on MddbFile to get these links
const dbBacklinkFilesPromises = dbBacklinks.map((link) =>
mddb.getFileById(link.from)
);
const dbBacklinkFiles = await Promise.all(dbBacklinkFilesPromises);
const dbBacklinkUrls = dbBacklinkFiles.map(
(file) => file.toObject().url_path
);
// TODO we can already get frontmatter from dbFile.metadata
// so parse could only return mdxSource
const source = fs.readFileSync(dbFile.file_path, { encoding: "utf-8" });
const { mdxSource, frontMatter } = await parse(source, "mdx", {
backlinks: dbBacklinkUrls,
});
// Temporary, so that blogs work properly
if (dbFile.url_path.startsWith("blog/")) {
frontMatter.layout = "blog";
frontMatter.authorsDetails = await getAuthorsDetails(
dbFile.metadata.authors
);
}
return {
props: {
source: JSON.stringify(mdxSource),
frontMatter: JSON.stringify(frontMatter),
},
};
};
export async function getStaticPaths() {
const mddb = await clientPromise;
const allDocuments = await mddb.getFiles({ extensions: ["md", "mdx"] });
const paths = allDocuments.map((page) => {
const parts = page.url_path.split("/");
return { params: { slug: parts } };
});
return {
paths,
fallback: false,
};
}

View File

@ -1,64 +0,0 @@
import "../styles/globals.css";
import "../styles/tailwind.css";
import Script from "next/script";
import { DefaultSeo } from "next-seo";
import { pageview, ThemeProvider } from "@flowershow/core";
import { siteConfig } from "../config/siteConfig";
import { useEffect } from "react";
import { useRouter } from "next/dist/client/router";
function MyApp({ Component, pageProps }) {
const router = useRouter();
useEffect(() => {
if (siteConfig.analytics) {
const handleRouteChange = (url) => {
pageview(url);
};
router.events.on("routeChangeComplete", handleRouteChange);
return () => {
router.events.off("routeChangeComplete", handleRouteChange);
};
}
}, [router.events]);
return (
<ThemeProvider
disableTransitionOnChange
attribute="class"
defaultTheme={siteConfig.theme.default}
forcedTheme={siteConfig.theme.default ? null : "light"}
>
<DefaultSeo defaultTitle={siteConfig.title} {...siteConfig.nextSeo} />
{/* Global Site Tag (gtag.js) - Google Analytics */}
{siteConfig.analytics && (
<>
<Script
strategy="afterInteractive"
src={`https://www.googletagmanager.com/gtag/js?id=${siteConfig.analytics}`}
/>
<Script
id="gtag-init"
strategy="afterInteractive"
dangerouslySetInnerHTML={{
__html: `
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', '${siteConfig.analytics}', {
page_path: window.location.pathname,
});
`,
}}
/>
</>
)}
<Component {...pageProps} />
</ThemeProvider>
);
}
export default MyApp;

View File

@ -1,26 +0,0 @@
import axios from 'axios'
export default function handler(req, res) {
if (!req.query.url) {
res.status(200).send({
error: true,
info: 'No url to proxy in query string i.e. ?url=...'
})
return
}
axios({
method: 'get',
url: req.query.url,
responseType:'stream'
})
.then(resp => {
resp.data.pipe(res)
})
.catch(err => {
res.status(400).send({
error: true,
info: err.message,
detailed: err
})
})
}

View File

@ -1,23 +0,0 @@
import fs from "fs";
import parse from "../../lib/data-literate/markdown";
import DataLiterate from "../../components/data-literate/DataLiterate";
export default function PostPage({ source, frontMatter }) {
return <DataLiterate source={source} frontMatter={frontMatter} />;
}
export const getStaticProps = async ({ params }) => {
const mdxPath = "content/data-literate/demo.mdx";
const source = fs.readFileSync(mdxPath);
const { mdxSource, frontMatter } = await parse(source);
return {
props: {
source: mdxSource,
frontMatter: frontMatter,
},
};
};

View File

@ -1,12 +0,0 @@
import Head from 'next/head'
import SheetJSApp from '../components/ExcelViewerApp.js'
import Layout from '../components/Layout'
export default function Index() {
return (
<Layout title='Excel Viewer'>
<h1>Excel Viewer</h1>
<SheetJSApp />
</Layout>
)
}

View File

@ -1,63 +0,0 @@
import Layout from '../components/Layout'
export default function Home() {
return (
<Layout>
<main className="flex flex-col items-center justify-center w-full flex-1 px-20 text-center py-10">
<h1 className="text-4xl sm:text-8xl font-bold">
<a href="https://portaljs.com/">
P🌀RTAL.<small>JS</small>
</a>
</h1>
<h2 className="mt-6 text-2xl sm:text-4xl font-normal leading-snug">
Rapidly build rich data portals using a modern frontend framework
</h2>
<div className="flex flex-wrap items-center justify-around max-w-4xl mt-6 sm:w-full">
<a
href="/docs/"
className="p-6 mt-6 text-left border w-96 rounded-xl hover:text-blue-600 focus:text-blue-600"
>
<h3 className="text-2xl font-semibold"> Documentation</h3>
<p className="mt-4 text-xl">
Find in-depth information about Portal.js features and API.
</p>
</a>
<a
href="/learn/"
className="p-6 mt-6 text-left border w-96 rounded-xl hover:text-blue-600 focus:text-blue-600"
>
<h3 className="text-2xl font-semibold"> Learn</h3>
<p className="mt-4 text-xl">
Learn about Portal.js in an interactive course.
</p>
</a>
<a
href="/gallery/"
className="p-6 mt-6 text-left border w-96 rounded-xl hover:text-blue-600 focus:text-blue-600"
>
<h3 className="text-2xl font-semibold"> Gallery</h3>
<p className="mt-4 text-xl">
Discover examples of Portal.js projects.
</p>
</a>
<a
href="https://github.com/datopian/portal.js"
className="p-6 mt-6 text-left border w-96 rounded-xl hover:text-blue-600 focus:text-blue-600"
>
<h3 className="text-2xl font-semibold"> Contribute</h3>
<p className="mt-4 text-xl">
Checkout the Portal.js repository on Github.
</p>
</a>
</div>
</main>
</Layout>
)
}

View File

@ -1,15 +0,0 @@
const { join } = require('path');
// Note: If you use library-specific PostCSS/Tailwind configuration then you should remove the `postcssConfig` build
// option from your application's configuration (i.e. project.json).
//
// See: https://nx.dev/guides/using-tailwind-css-in-react#step-4:-applying-configuration-to-libraries
module.exports = {
plugins: {
tailwindcss: {
config: join(__dirname, 'tailwind.config.js'),
},
autoprefixer: {},
},
};

View File

@ -1,69 +0,0 @@
{
"name": "site",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "apps/site",
"projectType": "application",
"targets": {
"build": {
"executor": "@nrwl/next:build",
"outputs": ["{options.outputPath}"],
"defaultConfiguration": "production",
"options": {
"root": "apps/site",
"outputPath": "dist/apps/site"
},
"configurations": {
"development": {
"outputPath": "apps/site"
},
"production": {}
}
},
"serve": {
"executor": "@nrwl/next:server",
"defaultConfiguration": "development",
"options": {
"buildTarget": "site:build",
"dev": true
},
"configurations": {
"development": {
"buildTarget": "site:build:development",
"dev": true
},
"production": {
"buildTarget": "site:build:production",
"dev": false
}
}
},
"export": {
"executor": "@nrwl/next:export",
"options": {
"buildTarget": "site:build:production"
}
},
"test": {
"executor": "@nrwl/jest:jest",
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
"options": {
"jestConfig": "apps/site/jest.config.ts",
"passWithNoTests": true
},
"configurations": {
"ci": {
"ci": true,
"codeCoverage": true
}
}
},
"lint": {
"executor": "@nrwl/linter:eslint",
"outputs": ["{options.outputFile}"],
"options": {
"lintFilePatterns": ["apps/site/**/*.{ts,tsx,js,jsx}"]
}
}
},
"tags": []
}

View File

@ -1,173 +0,0 @@
Time,Anomaly (deg C),Lower confidence limit (2.5%),Upper confidence limit (97.5%)
1850,-0.41765878,-0.589203,-0.24611452
1851,-0.2333498,-0.41186792,-0.054831687
1852,-0.22939907,-0.40938243,-0.04941572
1853,-0.27035445,-0.43000934,-0.110699534
1854,-0.29163003,-0.43282393,-0.15043613
1855,-0.2969512,-0.43935776,-0.15454465
1856,-0.32035372,-0.46809322,-0.1726142
1857,-0.46723005,-0.61632216,-0.31813794
1858,-0.3887657,-0.53688604,-0.24064532
1859,-0.28119546,-0.42384982,-0.13854107
1860,-0.39016518,-0.5389766,-0.24135375
1861,-0.42927712,-0.5972301,-0.26132414
1862,-0.53639776,-0.7037096,-0.36908585
1863,-0.3443432,-0.5341645,-0.1545219
1864,-0.4654367,-0.6480974,-0.282776
1865,-0.33258784,-0.5246526,-0.14052312
1866,-0.34126064,-0.52183825,-0.16068307
1867,-0.35696334,-0.55306214,-0.16086453
1868,-0.35196072,-0.52965826,-0.17426313
1869,-0.31657043,-0.47642276,-0.15671812
1870,-0.32789087,-0.46867347,-0.18710826
1871,-0.3685807,-0.5141493,-0.22301209
1872,-0.32804197,-0.4630833,-0.19300064
1873,-0.34133235,-0.4725396,-0.21012507
1874,-0.3732512,-0.5071426,-0.2393598
1875,-0.37562594,-0.514041,-0.23721085
1876,-0.42410994,-0.56287116,-0.28534868
1877,-0.101108834,-0.22982001,0.027602348
1878,-0.011315193,-0.13121258,0.10858219
1879,-0.30363432,-0.43406433,-0.1732043
1880,-0.31583208,-0.44015095,-0.19151321
1881,-0.23224552,-0.35793498,-0.10655605
1882,-0.29553008,-0.4201501,-0.17091006
1883,-0.3464744,-0.4608177,-0.23213111
1884,-0.49232006,-0.6026686,-0.38197154
1885,-0.47112358,-0.5830682,-0.35917896
1886,-0.42090362,-0.5225382,-0.31926903
1887,-0.49878576,-0.61655986,-0.3810117
1888,-0.37937889,-0.49332377,-0.265434
1889,-0.24989556,-0.37222093,-0.12757017
1890,-0.50685817,-0.6324095,-0.3813068
1891,-0.40131494,-0.5373699,-0.26525995
1892,-0.5075585,-0.64432853,-0.3707885
1893,-0.49461925,-0.6315314,-0.35770702
1894,-0.48376393,-0.6255681,-0.34195974
1895,-0.4487516,-0.58202064,-0.3154826
1896,-0.28400728,-0.4174015,-0.15061308
1897,-0.25980017,-0.39852425,-0.12107607
1898,-0.48579213,-0.6176492,-0.35393503
1899,-0.35543364,-0.48639694,-0.22447036
1900,-0.23447904,-0.3669676,-0.10199049
1901,-0.29342857,-0.42967388,-0.15718324
1902,-0.43898427,-0.5754281,-0.30254042
1903,-0.5333264,-0.66081935,-0.40583345
1904,-0.5975614,-0.7288325,-0.46629035
1905,-0.40775132,-0.5350291,-0.28047356
1906,-0.3191393,-0.45052385,-0.18775477
1907,-0.5041577,-0.6262818,-0.38203365
1908,-0.5138707,-0.63748026,-0.3902612
1909,-0.5357649,-0.6526296,-0.41890016
1910,-0.5310242,-0.6556868,-0.40636164
1911,-0.5392051,-0.66223973,-0.4161705
1912,-0.47567302,-0.5893311,-0.36201498
1913,-0.46715254,-0.5893755,-0.34492958
1914,-0.2625924,-0.38276345,-0.1424214
1915,-0.19184391,-0.32196194,-0.06172589
1916,-0.42020997,-0.5588941,-0.28152588
1917,-0.54301953,-0.6921192,-0.3939199
1918,-0.42458433,-0.58198184,-0.26718682
1919,-0.32551822,-0.48145813,-0.1695783
1920,-0.2985808,-0.44860035,-0.14856121
1921,-0.24067703,-0.38175339,-0.09960067
1922,-0.33922812,-0.46610323,-0.21235302
1923,-0.31793055,-0.444173,-0.1916881
1924,-0.3120622,-0.4388317,-0.18529275
1925,-0.28242525,-0.4147755,-0.15007503
1926,-0.12283547,-0.25264767,0.006976739
1927,-0.22940508,-0.35135695,-0.10745319
1928,-0.20676155,-0.33881804,-0.074705064
1929,-0.39275664,-0.52656746,-0.25894582
1930,-0.1768054,-0.29041144,-0.06319936
1931,-0.10339768,-0.2126916,0.0058962475
1932,-0.14546166,-0.25195515,-0.0389682
1933,-0.32234442,-0.4271004,-0.21758842
1934,-0.17433685,-0.27400395,-0.07466974
1935,-0.20605922,-0.30349734,-0.10862111
1936,-0.16952093,-0.26351926,-0.07552261
1937,-0.01919893,-0.11975875,0.08136089
1938,-0.012200732,-0.11030374,0.08590227
1939,-0.040797167,-0.14670466,0.065110326
1940,0.07593584,-0.04194966,0.19382134
1941,0.038129337,-0.16225387,0.23851255
1942,0.0014060909,-0.1952124,0.19802457
1943,0.0064140745,-0.19959097,0.21241911
1944,0.14410514,-0.054494828,0.3427051
1945,0.043088365,-0.15728289,0.24345961
1946,-0.1188128,-0.2659574,0.028331792
1947,-0.091205545,-0.23179041,0.04937931
1948,-0.12466127,-0.25913337,0.009810844
1949,-0.14380224,-0.2540775,-0.033526987
1950,-0.22662179,-0.33265698,-0.12058662
1951,-0.06115397,-0.15035024,0.028042298
1952,0.015354565,-0.08293597,0.11364509
1953,0.07763074,-0.020529618,0.1757911
1954,-0.11675021,-0.20850271,-0.024997713
1955,-0.19730993,-0.28442997,-0.1101899
1956,-0.2631656,-0.33912563,-0.18720557
1957,-0.035334926,-0.10056862,0.029898768
1958,-0.017632553,-0.083074555,0.04780945
1959,-0.048004825,-0.11036375,0.0143540995
1960,-0.115487024,-0.17416587,-0.056808177
1961,-0.019997388,-0.07078052,0.030785747
1962,-0.06405444,-0.11731443,-0.010794453
1963,-0.03680589,-0.09057008,0.016958294
1964,-0.30586675,-0.34949213,-0.26224136
1965,-0.2043879,-0.25357357,-0.15520222
1966,-0.14888458,-0.19839221,-0.09937696
1967,-0.11751631,-0.16062479,-0.07440783
1968,-0.1686323,-0.21325313,-0.124011464
1969,-0.031366713,-0.07186544,0.009132013
1970,-0.08510657,-0.12608096,-0.04413217
1971,-0.20593274,-0.24450706,-0.16735843
1972,-0.0938271,-0.13171694,-0.05593726
1973,0.04993336,0.013468528,0.086398184
1974,-0.17253734,-0.21022376,-0.1348509
1975,-0.11075424,-0.15130512,-0.07020335
1976,-0.21586166,-0.25588378,-0.17583954
1977,0.10308852,0.060056705,0.14612034
1978,0.0052557723,-0.034576867,0.04508841
1979,0.09085813,0.062358618,0.119357646
1980,0.19607207,0.162804,0.22934014
1981,0.25001204,0.21939126,0.28063282
1982,0.034263328,-0.005104665,0.07363132
1983,0.22383861,0.18807402,0.2596032
1984,0.04800471,0.011560736,0.08444869
1985,0.04972978,0.015663471,0.08379609
1986,0.09568697,0.064408,0.12696595
1987,0.2430264,0.21218552,0.27386728
1988,0.28215173,0.2470353,0.31726816
1989,0.17925027,0.14449838,0.21400215
1990,0.36056247,0.32455227,0.39657268
1991,0.33889654,0.30403617,0.3737569
1992,0.124896795,0.09088206,0.15891153
1993,0.16565846,0.12817313,0.2031438
1994,0.23354977,0.19841294,0.2686866
1995,0.37686616,0.34365577,0.41007656
1996,0.2766894,0.24318004,0.31019878
1997,0.4223085,0.39009082,0.4545262
1998,0.57731646,0.54304415,0.6115888
1999,0.32448497,0.29283476,0.35613516
2000,0.3310848,0.29822788,0.36394167
2001,0.48928034,0.4580683,0.5204924
2002,0.5434665,0.51278186,0.57415116
2003,0.5441702,0.5112426,0.5770977
2004,0.46737072,0.43433833,0.5004031
2005,0.60686255,0.5757053,0.6380198
2006,0.5725527,0.541973,0.60313237
2007,0.5917013,0.56135315,0.6220495
2008,0.46564984,0.43265733,0.49864236
2009,0.5967817,0.56525564,0.6283077
2010,0.68037146,0.649076,0.7116669
2011,0.53769773,0.5060012,0.5693943
2012,0.5776071,0.5448553,0.6103589
2013,0.6235754,0.5884838,0.6586669
2014,0.67287165,0.63890487,0.7068384
2015,0.82511437,0.79128706,0.8589417
2016,0.93292713,0.90176356,0.96409065
2017,0.84517425,0.81477475,0.87557375
2018,0.762654,0.731052,0.79425603
2019,0.8910726,0.85678726,0.92535794
2020,0.9227938,0.8882121,0.9573755
2021,0.6640137,0.5372486,0.79077876
1 Time Anomaly (deg C) Lower confidence limit (2.5%) Upper confidence limit (97.5%)
2 1850 -0.41765878 -0.589203 -0.24611452
3 1851 -0.2333498 -0.41186792 -0.054831687
4 1852 -0.22939907 -0.40938243 -0.04941572
5 1853 -0.27035445 -0.43000934 -0.110699534
6 1854 -0.29163003 -0.43282393 -0.15043613
7 1855 -0.2969512 -0.43935776 -0.15454465
8 1856 -0.32035372 -0.46809322 -0.1726142
9 1857 -0.46723005 -0.61632216 -0.31813794
10 1858 -0.3887657 -0.53688604 -0.24064532
11 1859 -0.28119546 -0.42384982 -0.13854107
12 1860 -0.39016518 -0.5389766 -0.24135375
13 1861 -0.42927712 -0.5972301 -0.26132414
14 1862 -0.53639776 -0.7037096 -0.36908585
15 1863 -0.3443432 -0.5341645 -0.1545219
16 1864 -0.4654367 -0.6480974 -0.282776
17 1865 -0.33258784 -0.5246526 -0.14052312
18 1866 -0.34126064 -0.52183825 -0.16068307
19 1867 -0.35696334 -0.55306214 -0.16086453
20 1868 -0.35196072 -0.52965826 -0.17426313
21 1869 -0.31657043 -0.47642276 -0.15671812
22 1870 -0.32789087 -0.46867347 -0.18710826
23 1871 -0.3685807 -0.5141493 -0.22301209
24 1872 -0.32804197 -0.4630833 -0.19300064
25 1873 -0.34133235 -0.4725396 -0.21012507
26 1874 -0.3732512 -0.5071426 -0.2393598
27 1875 -0.37562594 -0.514041 -0.23721085
28 1876 -0.42410994 -0.56287116 -0.28534868
29 1877 -0.101108834 -0.22982001 0.027602348
30 1878 -0.011315193 -0.13121258 0.10858219
31 1879 -0.30363432 -0.43406433 -0.1732043
32 1880 -0.31583208 -0.44015095 -0.19151321
33 1881 -0.23224552 -0.35793498 -0.10655605
34 1882 -0.29553008 -0.4201501 -0.17091006
35 1883 -0.3464744 -0.4608177 -0.23213111
36 1884 -0.49232006 -0.6026686 -0.38197154
37 1885 -0.47112358 -0.5830682 -0.35917896
38 1886 -0.42090362 -0.5225382 -0.31926903
39 1887 -0.49878576 -0.61655986 -0.3810117
40 1888 -0.37937889 -0.49332377 -0.265434
41 1889 -0.24989556 -0.37222093 -0.12757017
42 1890 -0.50685817 -0.6324095 -0.3813068
43 1891 -0.40131494 -0.5373699 -0.26525995
44 1892 -0.5075585 -0.64432853 -0.3707885
45 1893 -0.49461925 -0.6315314 -0.35770702
46 1894 -0.48376393 -0.6255681 -0.34195974
47 1895 -0.4487516 -0.58202064 -0.3154826
48 1896 -0.28400728 -0.4174015 -0.15061308
49 1897 -0.25980017 -0.39852425 -0.12107607
50 1898 -0.48579213 -0.6176492 -0.35393503
51 1899 -0.35543364 -0.48639694 -0.22447036
52 1900 -0.23447904 -0.3669676 -0.10199049
53 1901 -0.29342857 -0.42967388 -0.15718324
54 1902 -0.43898427 -0.5754281 -0.30254042
55 1903 -0.5333264 -0.66081935 -0.40583345
56 1904 -0.5975614 -0.7288325 -0.46629035
57 1905 -0.40775132 -0.5350291 -0.28047356
58 1906 -0.3191393 -0.45052385 -0.18775477
59 1907 -0.5041577 -0.6262818 -0.38203365
60 1908 -0.5138707 -0.63748026 -0.3902612
61 1909 -0.5357649 -0.6526296 -0.41890016
62 1910 -0.5310242 -0.6556868 -0.40636164
63 1911 -0.5392051 -0.66223973 -0.4161705
64 1912 -0.47567302 -0.5893311 -0.36201498
65 1913 -0.46715254 -0.5893755 -0.34492958
66 1914 -0.2625924 -0.38276345 -0.1424214
67 1915 -0.19184391 -0.32196194 -0.06172589
68 1916 -0.42020997 -0.5588941 -0.28152588
69 1917 -0.54301953 -0.6921192 -0.3939199
70 1918 -0.42458433 -0.58198184 -0.26718682
71 1919 -0.32551822 -0.48145813 -0.1695783
72 1920 -0.2985808 -0.44860035 -0.14856121
73 1921 -0.24067703 -0.38175339 -0.09960067
74 1922 -0.33922812 -0.46610323 -0.21235302
75 1923 -0.31793055 -0.444173 -0.1916881
76 1924 -0.3120622 -0.4388317 -0.18529275
77 1925 -0.28242525 -0.4147755 -0.15007503
78 1926 -0.12283547 -0.25264767 0.006976739
79 1927 -0.22940508 -0.35135695 -0.10745319
80 1928 -0.20676155 -0.33881804 -0.074705064
81 1929 -0.39275664 -0.52656746 -0.25894582
82 1930 -0.1768054 -0.29041144 -0.06319936
83 1931 -0.10339768 -0.2126916 0.0058962475
84 1932 -0.14546166 -0.25195515 -0.0389682
85 1933 -0.32234442 -0.4271004 -0.21758842
86 1934 -0.17433685 -0.27400395 -0.07466974
87 1935 -0.20605922 -0.30349734 -0.10862111
88 1936 -0.16952093 -0.26351926 -0.07552261
89 1937 -0.01919893 -0.11975875 0.08136089
90 1938 -0.012200732 -0.11030374 0.08590227
91 1939 -0.040797167 -0.14670466 0.065110326
92 1940 0.07593584 -0.04194966 0.19382134
93 1941 0.038129337 -0.16225387 0.23851255
94 1942 0.0014060909 -0.1952124 0.19802457
95 1943 0.0064140745 -0.19959097 0.21241911
96 1944 0.14410514 -0.054494828 0.3427051
97 1945 0.043088365 -0.15728289 0.24345961
98 1946 -0.1188128 -0.2659574 0.028331792
99 1947 -0.091205545 -0.23179041 0.04937931
100 1948 -0.12466127 -0.25913337 0.009810844
101 1949 -0.14380224 -0.2540775 -0.033526987
102 1950 -0.22662179 -0.33265698 -0.12058662
103 1951 -0.06115397 -0.15035024 0.028042298
104 1952 0.015354565 -0.08293597 0.11364509
105 1953 0.07763074 -0.020529618 0.1757911
106 1954 -0.11675021 -0.20850271 -0.024997713
107 1955 -0.19730993 -0.28442997 -0.1101899
108 1956 -0.2631656 -0.33912563 -0.18720557
109 1957 -0.035334926 -0.10056862 0.029898768
110 1958 -0.017632553 -0.083074555 0.04780945
111 1959 -0.048004825 -0.11036375 0.0143540995
112 1960 -0.115487024 -0.17416587 -0.056808177
113 1961 -0.019997388 -0.07078052 0.030785747
114 1962 -0.06405444 -0.11731443 -0.010794453
115 1963 -0.03680589 -0.09057008 0.016958294
116 1964 -0.30586675 -0.34949213 -0.26224136
117 1965 -0.2043879 -0.25357357 -0.15520222
118 1966 -0.14888458 -0.19839221 -0.09937696
119 1967 -0.11751631 -0.16062479 -0.07440783
120 1968 -0.1686323 -0.21325313 -0.124011464
121 1969 -0.031366713 -0.07186544 0.009132013
122 1970 -0.08510657 -0.12608096 -0.04413217
123 1971 -0.20593274 -0.24450706 -0.16735843
124 1972 -0.0938271 -0.13171694 -0.05593726
125 1973 0.04993336 0.013468528 0.086398184
126 1974 -0.17253734 -0.21022376 -0.1348509
127 1975 -0.11075424 -0.15130512 -0.07020335
128 1976 -0.21586166 -0.25588378 -0.17583954
129 1977 0.10308852 0.060056705 0.14612034
130 1978 0.0052557723 -0.034576867 0.04508841
131 1979 0.09085813 0.062358618 0.119357646
132 1980 0.19607207 0.162804 0.22934014
133 1981 0.25001204 0.21939126 0.28063282
134 1982 0.034263328 -0.005104665 0.07363132
135 1983 0.22383861 0.18807402 0.2596032
136 1984 0.04800471 0.011560736 0.08444869
137 1985 0.04972978 0.015663471 0.08379609
138 1986 0.09568697 0.064408 0.12696595
139 1987 0.2430264 0.21218552 0.27386728
140 1988 0.28215173 0.2470353 0.31726816
141 1989 0.17925027 0.14449838 0.21400215
142 1990 0.36056247 0.32455227 0.39657268
143 1991 0.33889654 0.30403617 0.3737569
144 1992 0.124896795 0.09088206 0.15891153
145 1993 0.16565846 0.12817313 0.2031438
146 1994 0.23354977 0.19841294 0.2686866
147 1995 0.37686616 0.34365577 0.41007656
148 1996 0.2766894 0.24318004 0.31019878
149 1997 0.4223085 0.39009082 0.4545262
150 1998 0.57731646 0.54304415 0.6115888
151 1999 0.32448497 0.29283476 0.35613516
152 2000 0.3310848 0.29822788 0.36394167
153 2001 0.48928034 0.4580683 0.5204924
154 2002 0.5434665 0.51278186 0.57415116
155 2003 0.5441702 0.5112426 0.5770977
156 2004 0.46737072 0.43433833 0.5004031
157 2005 0.60686255 0.5757053 0.6380198
158 2006 0.5725527 0.541973 0.60313237
159 2007 0.5917013 0.56135315 0.6220495
160 2008 0.46564984 0.43265733 0.49864236
161 2009 0.5967817 0.56525564 0.6283077
162 2010 0.68037146 0.649076 0.7116669
163 2011 0.53769773 0.5060012 0.5693943
164 2012 0.5776071 0.5448553 0.6103589
165 2013 0.6235754 0.5884838 0.6586669
166 2014 0.67287165 0.63890487 0.7068384
167 2015 0.82511437 0.79128706 0.8589417
168 2016 0.93292713 0.90176356 0.96409065
169 2017 0.84517425 0.81477475 0.87557375
170 2018 0.762654 0.731052 0.79425603
171 2019 0.8910726 0.85678726 0.92535794
172 2020 0.9227938 0.8882121 0.9573755
173 2021 0.6640137 0.5372486 0.79077876

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 188 KiB

View File

@ -1,26 +0,0 @@
name: github pages
on:
push:
branches:
- master
- main
jobs:
run:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup Node
uses: actions/setup-node@v2.1.2
with:
node-version: '12.x'
- name: Build datasets
env:
PORTAL_REPO_NAME: ${{ secrets.PORTAL_REPO_NAME }}
run: |
curl https://raw.githubusercontent.com/datopian/portal.js/main/site/public/scripts/single-dataset-no-commit.sh > portal.sh
git config --local user.email "$(git log --format='%ae' HEAD^!)"
git config --local user.name "$(git log --format='%an' HEAD^!)"
source ./portal.sh

Binary file not shown.

Before

Width:  |  Height:  |  Size: 261 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 166 KiB

View File

@ -1,40 +0,0 @@
name: github pages
on:
push:
branches:
- master
- main
jobs:
deploy:
runs-on: ubuntu-18.04
steps:
- uses: actions/checkout@v2
- name: Setup Node
uses: actions/setup-node@v2.1.2
with:
node-version: '12.x'
- name: Get yarn cache
id: yarn-cache
run: echo "::set-output name=dir::$(yarn cache dir)"
- name: Cache dependencies
uses: actions/cache@v2
with:
path: ${{ steps.yarn-cache.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- run: cd portal && yarn install --frozen-lockfile
- run: cd portal && yarn build
- run: cd portal && yarn export
- name: Deploy
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.PORTAL_NEXT_TOKEN }}
publish_dir: ./portal/out

View File

@ -1,26 +0,0 @@
#!/bin/bash
rm -rf portal
mkdir -p portal
npx create-next-app portal -e https://github.com/datopian/portal.js/tree/main/examples/dataset-frictionless
mkdir portal/public/dataset
cp -a ./data portal/public/dataset
cp -a ./datapackage.json portal/public/dataset
cp -a ./README.md portal/public/dataset
PORTAL_DATASET_PATH=$PWD"/portal/public/dataset"
export PORTAL_DATASET_PATH
mkdir -p .github && mkdir -p .github/workflows && touch .github/workflows/main.yml
curl https://raw.githubusercontent.com/datopian/portal.js/main/site/public/scripts/gh-page-builder-action.yml > .github/workflows/main.yml
cd portal
assetPrefix='"/'$PORTAL_REPO_NAME'/"'
basePath='"/'$PORTAL_REPO_NAME'"'
echo 'module.exports = {assetPrefix:' ${assetPrefix}', basePath: '${basePath}' }' > next.config.js ## This ensures css and public folder works
cd ..
git add .
git commit -m "Add dataset build feature"
git push
echo "Portal generated, please push your code to github"

View File

@ -1,27 +0,0 @@
#!/bin/bash
git checkout -b gh-pages
git rm -r --cached .
rm -rf portal
mkdir -p portal
npx create-next-app portal -e https://github.com/datopian/portal.js/tree/main/examples/dataset-frictionless
mkdir portal/public/dataset
cp -a ./data portal/public/dataset
cp -a ./datapackage.json portal/public/dataset
cp -a ./README.md portal/public/dataset
PORTAL_DATASET_PATH=$PWD"/portal/public/dataset"
export PORTAL_DATASET_PATH
cd portal
assetPrefix='"/'$PORTAL_REPO_NAME'/"'
basePath='"/'$PORTAL_REPO_NAME'"'
echo 'module.exports = {assetPrefix:' ${assetPrefix}', basePath: '${basePath}' }' > next.config.js ## This ensures css and public folder works
yarn export
cd ..
cp -R -a portal/out/* ./
touch .nojekyll
git add $PWD'/_next' $PWD'/index.html' $PWD'/dataset' $PWD'/404.html' $PWD'/.nojekyll' $PWD'/favicon.ico'
git commit -m "Build new dataset page"
git push origin gh-pages

View File

@ -1,11 +0,0 @@
import React from 'react';
import { render } from '@testing-library/react';
import Index from '../pages/index';
describe('Index', () => {
it('should render successfully', () => {
const { baseElement } = render(<Index />);
expect(baseElement).toBeTruthy();
});
});

View File

@ -1,84 +0,0 @@
@import "@flowershow/remark-callouts/styles.css";
html,
body {
padding: 0;
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
}
a {
color: inherit;
text-decoration: none;
}
* {
box-sizing: border-box;
}
/* mathjax */
.math-inline > mjx-container > svg {
display: inline;
align-items: center;
}
/* smooth scrolling in modern browsers */
html {
scroll-behavior: smooth !important;
}
/* tooltip fade-out clip */
.tooltip-body::after {
content: "";
position: absolute;
right: 0;
top: 3.6rem; /* multiple of $line-height used on the tooltip body (defined in tooltipBodyStyle) */
height: 1.2rem; /* ($top + $height)/$line-height is the number of lines we want to clip tooltip text at*/
width: 10rem;
background: linear-gradient(
to right,
rgba(255, 255, 255, 0),
rgba(255, 255, 255, 1) 100%
);
}
:is(h2, h3, h4, h5, h6):not(.blogitem-title) {
margin-left: -2rem !important;
padding-left: 2rem !important;
scroll-margin-top: 4.5rem;
position: relative;
}
.heading-link {
padding: 1px;
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
margin: auto 0;
border-radius: 5px;
background: #1e293b;
opacity: 0;
transition: opacity 0.2s;
}
.light .heading-link {
/* border: 1px solid #ab2b65; */
/* background: none; */
background: #e2e8f0;
}
:is(h2, h3, h4, h5, h6):not(.blogitem-title):hover .heading-link {
opacity: 100;
}
.heading-link svg {
transform: scale(0.75);
}
@media screen and (max-width: 640px) {
.heading-link {
visibility: hidden;
}
}

View File

@ -1,3 +0,0 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

View File

@ -1,31 +0,0 @@
const defaultTheme = require("tailwindcss/defaultTheme");
const { createGlobPatternsForDependencies } = require('@nrwl/react/tailwind');
const { join } = require('path');
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
join(
__dirname,
'{src,pages,components}/**/*!(*.stories|*.spec).{ts,tsx,html,js,jsx}'
),
...createGlobPatternsForDependencies(__dirname),
],
darkMode: false, // or 'media' or 'class'
theme: {
container: {
center: true,
},
extend: {
fontFamily: {
mono: ["Inconsolata", ...defaultTheme.fontFamily.mono]
}
},
},
variants: {
extend: {},
},
plugins: [
require('@tailwindcss/typography'),
],
};

View File

@ -1,31 +0,0 @@
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@flowershow/core": ["node_modules/@flowershow/core/dist/src"],
"@flowershow/remark-callouts": [
"node_modules/@flowershow/remark-callouts/dist/src"
],
"@flowershow/remark-embed": [
"node_modules/@flowershow/remark-embed/dist/src"
],
"@/*": ["./*"]
},
"target": "es2020",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": false,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"incremental": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve"
},
"include": ["types.d.ts", "next-env.d.ts", "**/*.tsx", "**/*.ts"],
"exclude": ["node_modules"]
}

View File

@ -1,21 +0,0 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"module": "commonjs",
"types": ["jest", "node"],
"jsx": "react"
},
"include": [
"jest.config.ts",
"src/**/*.test.ts",
"src/**/*.spec.ts",
"src/**/*.test.tsx",
"src/**/*.spec.tsx",
"src/**/*.test.js",
"src/**/*.spec.js",
"src/**/*.test.jsx",
"src/**/*.spec.jsx",
"src/**/*.d.ts"
]
}

View File

@ -1,6 +1,6 @@
import axios from 'axios' import axios from 'axios'
import React, { useEffect } from 'react' import React, { useEffect } from 'react'
import { Table } from '@portaljs/components' import { Table } from '@portaljs/portaljs-components'
const papa = require("papaparse") const papa = require("papaparse")

Some files were not shown because too many files have changed in this diff Show More