Compare commits
1 Commits
main
...
basic-exam
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
13d821dd94 |
@ -1,8 +0,0 @@
|
|||||||
# Changesets
|
|
||||||
|
|
||||||
Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
|
|
||||||
with multi-package repos, or single-package repos to help you version and publish your code. You can
|
|
||||||
find the full documentation for it [in our repository](https://github.com/changesets/changesets)
|
|
||||||
|
|
||||||
We have a quick list of common questions to get you started engaging with this project in
|
|
||||||
[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)
|
|
||||||
@ -1,14 +0,0 @@
|
|||||||
{
|
|
||||||
"$schema": "https://unpkg.com/@changesets/config@2.3.0/schema.json",
|
|
||||||
"changelog": [
|
|
||||||
"@changesets/changelog-github",
|
|
||||||
{ "repo": "datopian/portaljs" }
|
|
||||||
],
|
|
||||||
"commit": false,
|
|
||||||
"fixed": [],
|
|
||||||
"linked": [],
|
|
||||||
"access": "restricted",
|
|
||||||
"baseBranch": "main",
|
|
||||||
"updateInternalDependencies": "patch",
|
|
||||||
"ignore": []
|
|
||||||
}
|
|
||||||
39
.github/workflows/release.yml
vendored
@ -1,39 +0,0 @@
|
|||||||
name: Release
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
|
|
||||||
concurrency: release-${{ github.ref }}
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
release:
|
|
||||||
name: Release
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout Repo
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- name: Setup Node.js 16.x
|
|
||||||
uses: actions/setup-node@v3
|
|
||||||
with:
|
|
||||||
node-version: 16.x
|
|
||||||
|
|
||||||
- name: Install Dependencies
|
|
||||||
run: npm ci
|
|
||||||
|
|
||||||
- name: Create Release Pull Request or Publish to npm
|
|
||||||
id: changesets
|
|
||||||
uses: changesets/action@v1
|
|
||||||
with:
|
|
||||||
publish: npm run release
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
||||||
|
|
||||||
# - name: Send a Discord notification if a publish happens
|
|
||||||
# if: steps.changesets.outputs.published == 'true'
|
|
||||||
# uses: Ilshidur/action-discord@0.3.2
|
|
||||||
# with:
|
|
||||||
# args: 'The project {{ EVENT_PAYLOAD.repository.full_name }} has been deployed.'
|
|
||||||
6
.gitignore
vendored
@ -4,7 +4,6 @@
|
|||||||
dist
|
dist
|
||||||
tmp
|
tmp
|
||||||
/out-tsc
|
/out-tsc
|
||||||
**/*.tgz
|
|
||||||
|
|
||||||
# dependencies
|
# dependencies
|
||||||
node_modules
|
node_modules
|
||||||
@ -17,7 +16,6 @@ node_modules
|
|||||||
*.launch
|
*.launch
|
||||||
.settings/
|
.settings/
|
||||||
*.sublime-workspace
|
*.sublime-workspace
|
||||||
.obsidian
|
|
||||||
|
|
||||||
# IDE - VSCode
|
# IDE - VSCode
|
||||||
.vscode/*
|
.vscode/*
|
||||||
@ -46,7 +44,3 @@ Thumbs.db
|
|||||||
# Env
|
# Env
|
||||||
.env
|
.env
|
||||||
**/.env
|
**/.env
|
||||||
|
|
||||||
# MarkdownDB
|
|
||||||
*.db
|
|
||||||
**/*.db
|
|
||||||
|
|||||||
8
.vscode/extensions.json
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"recommendations": [
|
||||||
|
"nrwl.angular-console",
|
||||||
|
"esbenp.prettier-vscode",
|
||||||
|
"firsttris.vscode-jest-runner",
|
||||||
|
"dbaeumer.vscode-eslint"
|
||||||
|
]
|
||||||
|
}
|
||||||
@ -4,7 +4,7 @@ title: Developer docs for contributors
|
|||||||
|
|
||||||
## Our repository
|
## Our repository
|
||||||
|
|
||||||
https://github.com/datopian/datahub
|
https://github.com/datopian/portaljs
|
||||||
|
|
||||||
Structure:
|
Structure:
|
||||||
|
|
||||||
@ -17,7 +17,7 @@ Structure:
|
|||||||
|
|
||||||
## How to contribute
|
## How to contribute
|
||||||
|
|
||||||
You can start by checking our [issues board](https://github.com/datopian/datahub/issues).
|
You can start by checking our [issues board](https://github.com/datopian/portaljs/issues).
|
||||||
|
|
||||||
If you'd like to work on one of the issues you can:
|
If you'd like to work on one of the issues you can:
|
||||||
|
|
||||||
@ -26,16 +26,15 @@ If you'd like to work on one of the issues you can:
|
|||||||
3. Clone the forked repository to your machine.
|
3. Clone the forked repository to your machine.
|
||||||
4. Create a feature branch (e.g. `50-update-readme`, where `50` is the number of the related issue).
|
4. Create a feature branch (e.g. `50-update-readme`, where `50` is the number of the related issue).
|
||||||
5. Commit your changes to the feature branch.
|
5. Commit your changes to the feature branch.
|
||||||
6. Add changeset file describing the changes. (See section below)
|
6. Push the feature branch to your forked repository.
|
||||||
7. Push the feature branch to your forked repository.
|
7. Create a Pull Request against the original repository.
|
||||||
8. Create a Pull Request against the original repository.
|
|
||||||
- add a short description of the changes included in the PR
|
- add a short description of the changes included in the PR
|
||||||
9. Address review comments if requested by our demanding reviewers 😜.
|
8. Address review comments if requested by our demanding reviewers 😜.
|
||||||
|
|
||||||
If you have an idea for improvement, and it doesn't have a corresponding issue yet, simply submit a new one.
|
If you have an idea for improvement, and it doesn't have a corresponding issue yet, simply submit a new one.
|
||||||
|
|
||||||
> [!note]
|
> [!note]
|
||||||
> Join our [Discord channel](https://discord.gg/KZSf3FG4EZ) do discuss existing issues and to ask for help.
|
> Join our [Discord channel](https://discord.gg/rTxfCutu) do discuss existing issues and to ask for help.
|
||||||
|
|
||||||
## Nx
|
## Nx
|
||||||
|
|
||||||
@ -63,7 +62,6 @@ or you can use just:
|
|||||||
nx <target> <project>
|
nx <target> <project>
|
||||||
# e.g. npx nx serve ckan
|
# e.g. npx nx serve ckan
|
||||||
```
|
```
|
||||||
|
|
||||||
if you have the `nx` binary installed globally in your machine
|
if you have the `nx` binary installed globally in your machine
|
||||||
|
|
||||||
#### Running multiple tasks
|
#### Running multiple tasks
|
||||||
@ -176,23 +174,3 @@ To learn more see this [offical docs page](https://nx.dev/reference/nx-json).
|
|||||||
Each project also has it's own configuration file - `project.json`, where you can define and configure it's targets (and more).
|
Each project also has it's own configuration file - `project.json`, where you can define and configure it's targets (and more).
|
||||||
|
|
||||||
To learn more see this [offical docs page](https://nx.dev/reference/project-configuration).
|
To learn more see this [offical docs page](https://nx.dev/reference/project-configuration).
|
||||||
|
|
||||||
## Changesets and publishing packages
|
|
||||||
|
|
||||||
> This monorepo is set up with changesets versioning tool. See their [github repository](https://github.com/changesets/changesets) to learn more.
|
|
||||||
|
|
||||||
### What are Changesets?
|
|
||||||
|
|
||||||
Changesets are files that describe the intention of a contributor to bump a version of the package according to their changes. Changeset file holds two key bits of information: a version type (following semver), and change information to be added to a changelog.
|
|
||||||
|
|
||||||
### Adding changesets
|
|
||||||
|
|
||||||
In the root directory of the repo, run:
|
|
||||||
|
|
||||||
```
|
|
||||||
npx changeset
|
|
||||||
```
|
|
||||||
|
|
||||||
Select the package that has been changed, the semver version that should be bumped with it and a description of your changes. Please make sure to add the most accurate but also concise information.
|
|
||||||
|
|
||||||
To learn about semantic versioning standards see [this semver doc page](https://semver.org/).
|
|
||||||
|
|||||||
74
README.md
@ -1,51 +1,31 @@
|
|||||||
<p align="center">
|
<h1 align="center">
|
||||||
Bugs, issues and suggestions re PortalJS framework
|
🌀 Portal.JS
|
||||||
<br />
|
<br />
|
||||||
<br /><a href="https://discord.gg/xfFDMPU9dC"><img src="https://dcbadge.vercel.app/api/server/xfFDMPU9dC" /></a>
|
Rapidly build rich data portals using a modern frontend framework
|
||||||
</p>
|
</h1>
|
||||||
|
|
||||||
## PortalJS framework
|
* [What is Portal.JS ?](#What-is-Portal.JS)
|
||||||
|
* [Features](#Features)
|
||||||
|
* [For developers](#For-developers)
|
||||||
|
* [Docs](#Docs)
|
||||||
|
* [Community](#Community)
|
||||||
|
* [Appendix](#Appendix)
|
||||||
|
* [What happened to Recline?](#What-happened-to-Recline?)
|
||||||
|
|
||||||
This repo and issue tracker are for
|
# What is Portal.JS
|
||||||
|
|
||||||
- PortalJS 🌀 - https://www.portaljs.com/
|
🌀 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.
|
||||||
- DataHub Cloud ☁️ - https://datahub.io/
|
|
||||||
|
|
||||||
### Issues
|
Built in JavaScript and React on top of the popular [Next.js](https://nextjs.com/) framework. Portal.JS 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/).
|
||||||
|
|
||||||
Found a bug: 👉 https://github.com/datopian/portaljs/issues/new
|
## Features
|
||||||
|
|
||||||
### Discussions
|
|
||||||
|
|
||||||
Got a suggestion, a question, want some support or just want to shoot the breeze 🙂
|
|
||||||
|
|
||||||
Head to the discussion forum: 👉 https://github.com/datopian/portaljs/discussions
|
|
||||||
|
|
||||||
### Chat on Discord
|
|
||||||
|
|
||||||
If you would prefer to get help via live chat check out our discord 👉
|
|
||||||
|
|
||||||
[Discord](https://discord.gg/xfFDMPU9dC)
|
|
||||||
|
|
||||||
### Docs
|
|
||||||
|
|
||||||
- For PortalJS go to https://www.portaljs.com/opensource
|
|
||||||
- For DataHub Cloud – https://datahub.io/docs
|
|
||||||
|
|
||||||
## PortalJS Cloud 🌀
|
|
||||||
|
|
||||||
PortalJS Cloud 🌀 is a platform for rapidly creating rich data portal and publishing systems using a modern frontend approach. PortalJS Cloud can be used to publish a single dataset or build a full-scale data catalog/portal.
|
|
||||||
|
|
||||||
PortalJS Cloud is built in JavaScript and React on top of the popular [Next.js](https://nextjs.org) framework. PortalJS Cloud 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/), GitHub, Frictionless Data Packages and more.
|
|
||||||
|
|
||||||
### 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.
|
- 🗺️ 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, Next.js).
|
- 👩💻 Developer friendly: built with familiar frontend tech (JavaScript, React, Next.js).
|
||||||
- 🔋 Batteries included: full set of portal components out of the box e.g. catalog search, dataset showcase, blog, 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.
|
- 🎨 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
|
- 🧱 Extensible: quickly extend and develop/import your own React components
|
||||||
- 📝 Well documented: full set of documentation plus the documentation of Next.js.
|
- 📝 Well documented: full set of documentation plus the documentation of Next.js and Apollo.
|
||||||
|
|
||||||
### For developers
|
### For developers
|
||||||
|
|
||||||
@ -53,3 +33,25 @@ PortalJS Cloud is built in JavaScript and React on top of the popular [Next.js](
|
|||||||
- 🚀 Next.js framework: so everything in Next.js for free: Server Side Rendering, Static Site Generation, huge number of examples and integrations, etc.
|
- 🚀 Next.js framework: so everything in Next.js for free: Server Side Rendering, Static Site Generation, huge number of examples and integrations, etc.
|
||||||
- Server Side Rendering (SSR) => Unlimited number of pages, SEO and more whilst still using React.
|
- Server Side Rendering (SSR) => Unlimited number of pages, SEO and more whilst still using React.
|
||||||
- Static Site Generation (SSG) => Ultra-simple deployment, great performance, great lighthouse scores and more (good for small sites)
|
- Static Site Generation (SSG) => Ultra-simple deployment, great performance, great lighthouse scores and more (good for small sites)
|
||||||
|
|
||||||
|
#### **Check out the [Portal.JS website](https://portaljs.org/) for a gallery of live portals**
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
# Docs
|
||||||
|
|
||||||
|
Access the Portal.JS documentation at:
|
||||||
|
|
||||||
|
https://portaljs.org/docs
|
||||||
|
|
||||||
|
- [Examples](https://portaljs.org/docs#examples)
|
||||||
|
|
||||||
|
# Community
|
||||||
|
|
||||||
|
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) or on our [Discord server](https://discord.gg/EeyfGrGu4U).
|
||||||
|
|
||||||
|
# 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).
|
||||||
|
|||||||
@ -8,8 +8,6 @@ First, run the development server:
|
|||||||
npm run dev
|
npm run dev
|
||||||
# or
|
# or
|
||||||
yarn dev
|
yarn dev
|
||||||
# or
|
|
||||||
pnpm dev
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
||||||
@ -20,8 +18,6 @@ You can start editing the page by modifying `pages/index.tsx`. The page auto-upd
|
|||||||
|
|
||||||
The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages.
|
The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages.
|
||||||
|
|
||||||
This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
|
|
||||||
|
|
||||||
## Learn More
|
## Learn More
|
||||||
|
|
||||||
To learn more about Next.js, take a look at the following resources:
|
To learn more about Next.js, take a look at the following resources:
|
||||||
21
examples/basic-example/components/DRD.tsx
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { MDXRemote } from 'next-mdx-remote';
|
||||||
|
import dynamic from 'next/dynamic';
|
||||||
|
import { Mermaid } from '@flowershow/core';
|
||||||
|
|
||||||
|
// 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 = {
|
||||||
|
Table: dynamic(() => import('./Table')),
|
||||||
|
mermaid: Mermaid,
|
||||||
|
// Excel: dynamic(() => import('../components/Excel')),
|
||||||
|
// TODO: try and make these dynamic ...
|
||||||
|
Vega: dynamic(() => import('./Vega')),
|
||||||
|
VegaLite: dynamic(() => import('./VegaLite')),
|
||||||
|
LineChart: dynamic(() => import('./LineChart')),
|
||||||
|
} as any;
|
||||||
|
|
||||||
|
export default function DRD({ source }: { source: any }) {
|
||||||
|
return <MDXRemote {...source} components={components} />;
|
||||||
|
}
|
||||||
49
examples/basic-example/components/LineChart.tsx
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
import VegaLite from "./VegaLite";
|
||||||
|
|
||||||
|
export default function LineChart({
|
||||||
|
data = [],
|
||||||
|
fullWidth = false,
|
||||||
|
title = "",
|
||||||
|
}) {
|
||||||
|
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",
|
||||||
|
title,
|
||||||
|
width: 500,
|
||||||
|
height: 300,
|
||||||
|
mark: {
|
||||||
|
type: "line",
|
||||||
|
color: "black",
|
||||||
|
strokeWidth: 1,
|
||||||
|
tooltip: true,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
name: "table",
|
||||||
|
},
|
||||||
|
selection: {
|
||||||
|
grid: {
|
||||||
|
type: "interval",
|
||||||
|
bind: "scales",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
encoding: {
|
||||||
|
x: {
|
||||||
|
field: "x",
|
||||||
|
timeUnit: "year",
|
||||||
|
type: "temporal",
|
||||||
|
},
|
||||||
|
y: {
|
||||||
|
field: "y",
|
||||||
|
type: "quantitative",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return <VegaLite fullWidth={fullWidth} data={vegaData} spec={spec} />;
|
||||||
|
}
|
||||||
189
examples/basic-example/components/Table.tsx
Normal file
@ -0,0 +1,189 @@
|
|||||||
|
import {
|
||||||
|
createColumnHelper,
|
||||||
|
FilterFn,
|
||||||
|
flexRender,
|
||||||
|
getCoreRowModel,
|
||||||
|
getFilteredRowModel,
|
||||||
|
getPaginationRowModel,
|
||||||
|
getSortedRowModel,
|
||||||
|
useReactTable,
|
||||||
|
} from "@tanstack/react-table";
|
||||||
|
|
||||||
|
import {
|
||||||
|
ArrowDownIcon,
|
||||||
|
ArrowUpIcon,
|
||||||
|
ChevronDoubleLeftIcon,
|
||||||
|
ChevronDoubleRightIcon,
|
||||||
|
ChevronLeftIcon,
|
||||||
|
ChevronRightIcon,
|
||||||
|
} from "@heroicons/react/24/solid";
|
||||||
|
|
||||||
|
import React, { useEffect, useMemo, useState } from "react";
|
||||||
|
|
||||||
|
import parseCsv from "../lib/parseCsv";
|
||||||
|
import DebouncedInput from "./DebouncedInput";
|
||||||
|
import loadData from "../lib/loadData";
|
||||||
|
|
||||||
|
const Table = ({
|
||||||
|
data: ogData = [],
|
||||||
|
cols: ogCols = [],
|
||||||
|
csv = "",
|
||||||
|
url = "",
|
||||||
|
fullWidth = false,
|
||||||
|
}) => {
|
||||||
|
if (csv) {
|
||||||
|
const out = parseCsv(csv);
|
||||||
|
ogData = out.rows;
|
||||||
|
ogCols = out.fields;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [data, setData] = React.useState(ogData);
|
||||||
|
const [cols, setCols] = React.useState(ogCols);
|
||||||
|
const [error, setError] = React.useState(""); // TODO: add error handling
|
||||||
|
|
||||||
|
const tableCols = useMemo(() => {
|
||||||
|
const columnHelper = createColumnHelper();
|
||||||
|
return cols.map((c) =>
|
||||||
|
columnHelper.accessor(c.key, {
|
||||||
|
header: () => c.name,
|
||||||
|
cell: (info) => info.getValue(),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}, [data, cols]);
|
||||||
|
|
||||||
|
const [globalFilter, setGlobalFilter] = useState("");
|
||||||
|
|
||||||
|
const table = useReactTable({
|
||||||
|
data,
|
||||||
|
columns: tableCols,
|
||||||
|
getCoreRowModel: getCoreRowModel(),
|
||||||
|
state: {
|
||||||
|
globalFilter,
|
||||||
|
},
|
||||||
|
globalFilterFn: globalFilterFn,
|
||||||
|
onGlobalFilterChange: setGlobalFilter,
|
||||||
|
getFilteredRowModel: getFilteredRowModel(),
|
||||||
|
getPaginationRowModel: getPaginationRowModel(),
|
||||||
|
getSortedRowModel: getSortedRowModel(),
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (url) {
|
||||||
|
loadData(url).then((data) => {
|
||||||
|
const { rows, fields } = parseCsv(data);
|
||||||
|
setData(rows);
|
||||||
|
setCols(fields);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [url]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={`${fullWidth ? "w-[90vw] ml-[calc(50%-45vw)]" : "w-full"}`}>
|
||||||
|
<DebouncedInput
|
||||||
|
value={globalFilter ?? ""}
|
||||||
|
onChange={(value) => setGlobalFilter(String(value))}
|
||||||
|
className="p-2 text-sm shadow border border-block"
|
||||||
|
placeholder="Search all columns..."
|
||||||
|
/>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
{table.getHeaderGroups().map((hg) => (
|
||||||
|
<tr key={hg.id}>
|
||||||
|
{hg.headers.map((h) => (
|
||||||
|
<th key={h.id}>
|
||||||
|
<div
|
||||||
|
{...{
|
||||||
|
className: h.column.getCanSort()
|
||||||
|
? "cursor-pointer select-none"
|
||||||
|
: "",
|
||||||
|
onClick: h.column.getToggleSortingHandler(),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{flexRender(h.column.columnDef.header, h.getContext())}
|
||||||
|
{{
|
||||||
|
asc: (
|
||||||
|
<ArrowUpIcon className="inline-block ml-2 h-4 w-4" />
|
||||||
|
),
|
||||||
|
desc: (
|
||||||
|
<ArrowDownIcon className="inline-block ml-2 h-4 w-4" />
|
||||||
|
),
|
||||||
|
}[h.column.getIsSorted() as string] ?? (
|
||||||
|
<div className="inline-block ml-2 h-4 w-4" />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
))}
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{table.getRowModel().rows.map((r) => (
|
||||||
|
<tr key={r.id}>
|
||||||
|
{r.getVisibleCells().map((c) => (
|
||||||
|
<td key={c.id}>
|
||||||
|
{flexRender(c.column.columnDef.cell, c.getContext())}
|
||||||
|
</td>
|
||||||
|
))}
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<div className="flex gap-2 items-center justify-center">
|
||||||
|
<button
|
||||||
|
className={`w-6 h-6 ${
|
||||||
|
!table.getCanPreviousPage() ? "opacity-25" : "opacity-100"
|
||||||
|
}`}
|
||||||
|
onClick={() => table.setPageIndex(0)}
|
||||||
|
disabled={!table.getCanPreviousPage()}
|
||||||
|
>
|
||||||
|
<ChevronDoubleLeftIcon />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className={`w-6 h-6 ${
|
||||||
|
!table.getCanPreviousPage() ? "opacity-25" : "opacity-100"
|
||||||
|
}`}
|
||||||
|
onClick={() => table.previousPage()}
|
||||||
|
disabled={!table.getCanPreviousPage()}
|
||||||
|
>
|
||||||
|
<ChevronLeftIcon />
|
||||||
|
</button>
|
||||||
|
<span className="flex items-center gap-1">
|
||||||
|
<div>Page</div>
|
||||||
|
<strong>
|
||||||
|
{table.getState().pagination.pageIndex + 1} of{" "}
|
||||||
|
{table.getPageCount()}
|
||||||
|
</strong>
|
||||||
|
</span>
|
||||||
|
<button
|
||||||
|
className={`w-6 h-6 ${
|
||||||
|
!table.getCanNextPage() ? "opacity-25" : "opacity-100"
|
||||||
|
}`}
|
||||||
|
onClick={() => table.nextPage()}
|
||||||
|
disabled={!table.getCanNextPage()}
|
||||||
|
>
|
||||||
|
<ChevronRightIcon />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className={`w-6 h-6 ${
|
||||||
|
!table.getCanNextPage() ? "opacity-25" : "opacity-100"
|
||||||
|
}`}
|
||||||
|
onClick={() => table.setPageIndex(table.getPageCount() - 1)}
|
||||||
|
disabled={!table.getCanNextPage()}
|
||||||
|
>
|
||||||
|
<ChevronDoubleRightIcon />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const globalFilterFn: FilterFn<any> = (row, columnId, filterValue: string) => {
|
||||||
|
const search = filterValue.toLowerCase();
|
||||||
|
|
||||||
|
let value = row.getValue(columnId) as string;
|
||||||
|
if (typeof value === "number") value = String(value);
|
||||||
|
|
||||||
|
return value?.toLowerCase().includes(search);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Table;
|
||||||
@ -1,7 +1,6 @@
|
|||||||
// Wrapper for the Vega component
|
// Wrapper for the Vega component
|
||||||
import { Vega as VegaOg } from "react-vega";
|
import { Vega as VegaOg } from "react-vega";
|
||||||
import { VegaProps } from "react-vega/lib/Vega";
|
|
||||||
|
|
||||||
export function Vega(props: VegaProps) {
|
export default function Vega(props) {
|
||||||
return <VegaOg {...props} />;
|
return <VegaOg {...props} />;
|
||||||
}
|
}
|
||||||
6
examples/basic-example/components/VegaLite.tsx
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
// Wrapper for the Vega Lite component
|
||||||
|
import { VegaLite as VegaLiteOg } from "react-vega";
|
||||||
|
|
||||||
|
export default function VegaLite(props) {
|
||||||
|
return <VegaLiteOg {...props} />;
|
||||||
|
}
|
||||||
@ -4,4 +4,5 @@ Built with PortalJS
|
|||||||
|
|
||||||
## Table
|
## Table
|
||||||
|
|
||||||
<Table url="data.csv" />
|
<Table url="data_1.csv" />
|
||||||
|
|
||||||
11
examples/basic-example/content/my-dataset/README.md
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
# Data
|
||||||
|
|
||||||
|
This is the README.md this project.
|
||||||
|
|
||||||
|
## Table
|
||||||
|
|
||||||
|
<Table url="data_1.csv" />
|
||||||
|
|
||||||
|
## Vega Lite Line Chart from URL
|
||||||
|
|
||||||
|
<VegaLite spec={ { "$schema": "https://vega.github.io/schema/vega-lite/v5.json", "data": {"url": "data_2.csv"}, "width": 550, "height": 250, "mark": "line", "encoding": { "x": {"field": "Time", "type": "temporal"}, "y": {"field": "Anomaly (deg C)", "type": "quantitative"}, "tooltip": {"field": "Anomaly (deg C)", "type": "quantitative"} } } } />
|
||||||
@ -1,13 +1,13 @@
|
|||||||
import matter from "gray-matter";
|
import matter from "gray-matter";
|
||||||
import mdxmermaid from "mdx-mermaid";
|
import mdxmermaid from "mdx-mermaid";
|
||||||
import { h } from "hastscript";
|
import { h } from "hastscript";
|
||||||
import remarkCallouts from "@portaljs/remark-callouts";
|
import remarkCallouts from "@flowershow/remark-callouts";
|
||||||
import remarkEmbed from "@portaljs/remark-embed";
|
import remarkEmbed from "@flowershow/remark-embed";
|
||||||
import remarkGfm from "remark-gfm";
|
import remarkGfm from "remark-gfm";
|
||||||
import remarkMath from "remark-math";
|
import remarkMath from "remark-math";
|
||||||
import remarkSmartypants from "remark-smartypants";
|
import remarkSmartypants from "remark-smartypants";
|
||||||
import remarkToc from "remark-toc";
|
import remarkToc from "remark-toc";
|
||||||
import remarkWikiLink from "@portaljs/remark-wiki-link";
|
import remarkWikiLink from "@flowershow/remark-wiki-link";
|
||||||
import rehypeAutolinkHeadings from "rehype-autolink-headings";
|
import rehypeAutolinkHeadings from "rehype-autolink-headings";
|
||||||
import rehypeKatex from "rehype-katex";
|
import rehypeKatex from "rehype-katex";
|
||||||
import rehypeSlug from "rehype-slug";
|
import rehypeSlug from "rehype-slug";
|
||||||
@ -22,7 +22,7 @@ import { serialize } from "next-mdx-remote/serialize";
|
|||||||
* @format: used to indicate to next-mdx-remote which format to use (md or mdx)
|
* @format: used to indicate to next-mdx-remote which format to use (md or mdx)
|
||||||
* @returns: { mdxSource: mdxSource, frontMatter: ...}
|
* @returns: { mdxSource: mdxSource, frontMatter: ...}
|
||||||
*/
|
*/
|
||||||
const parse = async function (source, format, scope) {
|
const parse = async function (source, format) {
|
||||||
const { content, data, excerpt } = matter(source, {
|
const { content, data, excerpt } = matter(source, {
|
||||||
excerpt: (file, options) => {
|
excerpt: (file, options) => {
|
||||||
// Generate an excerpt for the file
|
// Generate an excerpt for the file
|
||||||
@ -91,7 +91,7 @@ const parse = async function (source, format, scope) {
|
|||||||
],
|
],
|
||||||
format,
|
format,
|
||||||
},
|
},
|
||||||
scope,
|
scope: data,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
16
examples/basic-example/lib/parseCsv.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import papa from "papaparse";
|
||||||
|
|
||||||
|
const 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,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default parseCsv;
|
||||||
@ -1,9 +1,7 @@
|
|||||||
/** @type {import('next').NextConfig} */
|
/** @type {import('next').NextConfig} */
|
||||||
const nextConfig = {
|
const nextConfig = {
|
||||||
reactStrictMode: true,
|
reactStrictMode: true,
|
||||||
serverRuntimeConfig: {
|
swcMinify: true,
|
||||||
github_pat: process.env.GITHUB_PAT ? process.env.GITHUB_PAT : null,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = nextConfig
|
module.exports = nextConfig
|
||||||
@ -6,33 +6,29 @@
|
|||||||
"dev": "next dev",
|
"dev": "next dev",
|
||||||
"build": "next build",
|
"build": "next build",
|
||||||
"start": "next start",
|
"start": "next start",
|
||||||
"lint": "next lint",
|
"lint": "next lint"
|
||||||
"export": "npm run build && next export -o out",
|
|
||||||
"prebuild": "npm run mddb",
|
|
||||||
"mddb": "mddb ./content"
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@githubocto/flat-ui": "^0.14.1",
|
"@flowershow/core": "^0.4.10",
|
||||||
|
"@flowershow/remark-callouts": "^1.0.0",
|
||||||
|
"@flowershow/remark-embed": "^1.0.0",
|
||||||
|
"@flowershow/remark-wiki-link": "^1.1.2",
|
||||||
"@heroicons/react": "^2.0.17",
|
"@heroicons/react": "^2.0.17",
|
||||||
"@opentelemetry/api": "^1.4.0",
|
"@opentelemetry/api": "^1.4.0",
|
||||||
"@portaljs/components": "^0.1.8",
|
|
||||||
"@portaljs/core": "^1.0.5",
|
|
||||||
"@portaljs/remark-callouts": "^1.0.5",
|
|
||||||
"@portaljs/remark-embed": "^1.0.4",
|
|
||||||
"@portaljs/remark-wiki-link": "^1.0.4",
|
|
||||||
"@tanstack/react-table": "^8.8.5",
|
"@tanstack/react-table": "^8.8.5",
|
||||||
"flexsearch": "0.7.21",
|
"@types/node": "18.16.0",
|
||||||
|
"@types/react": "18.2.0",
|
||||||
|
"@types/react-dom": "18.2.0",
|
||||||
|
"eslint": "8.39.0",
|
||||||
|
"eslint-config-next": "13.3.1",
|
||||||
"gray-matter": "^4.0.3",
|
"gray-matter": "^4.0.3",
|
||||||
"hastscript": "^7.2.0",
|
"hastscript": "^7.2.0",
|
||||||
"mddb": "^0.1.9",
|
|
||||||
"mdx-mermaid": "2.0.0-rc7",
|
"mdx-mermaid": "2.0.0-rc7",
|
||||||
"next": "13.2.1",
|
"next": "13.2.1",
|
||||||
"next-mdx-remote": "^4.4.1",
|
"next-mdx-remote": "^4.4.1",
|
||||||
"papaparse": "^5.4.1",
|
"papaparse": "^5.4.1",
|
||||||
"react": "^18.2.0",
|
"react": "18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "18.2.0",
|
||||||
"react-hook-form": "^7.43.9",
|
|
||||||
"react-query": "^3.39.3",
|
|
||||||
"react-vega": "^7.6.0",
|
"react-vega": "^7.6.0",
|
||||||
"rehype-autolink-headings": "^6.1.1",
|
"rehype-autolink-headings": "^6.1.1",
|
||||||
"rehype-katex": "^6.0.3",
|
"rehype-katex": "^6.0.3",
|
||||||
@ -42,19 +38,11 @@
|
|||||||
"remark-math": "^5.1.1",
|
"remark-math": "^5.1.1",
|
||||||
"remark-smartypants": "^2.0.0",
|
"remark-smartypants": "^2.0.0",
|
||||||
"remark-toc": "^8.0.1",
|
"remark-toc": "^8.0.1",
|
||||||
"typescript": "5.0.4",
|
"typescript": "5.0.4"
|
||||||
"vega": "5.25.0",
|
|
||||||
"vega-lite": "5.1.0"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tailwindcss/typography": "^0.5.9",
|
"@tailwindcss/typography": "^0.5.9",
|
||||||
"@types/flexsearch": "^0.7.3",
|
|
||||||
"@types/node": "18.16.0",
|
|
||||||
"@types/react": "18.2.0",
|
|
||||||
"@types/react-dom": "18.2.0",
|
|
||||||
"autoprefixer": "^10.4.14",
|
"autoprefixer": "^10.4.14",
|
||||||
"eslint": "8.39.0",
|
|
||||||
"eslint-config-next": "13.3.1",
|
|
||||||
"postcss": "^8.4.23",
|
"postcss": "^8.4.23",
|
||||||
"tailwindcss": "^3.3.1"
|
"tailwindcss": "^3.3.1"
|
||||||
}
|
}
|
||||||
42
examples/basic-example/pages/[...path].tsx
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import { GetStaticProps } from 'next';
|
||||||
|
import { promises as fs } from 'fs';
|
||||||
|
import path from 'path';
|
||||||
|
import parse from '../lib/markdown';
|
||||||
|
import DRD from '../components/DRD';
|
||||||
|
|
||||||
|
export const getServerSideProps = async (context) => {
|
||||||
|
const indexFile = path.join(process.cwd(), '/content/' + context.params.path.join('/') + '/index.md');
|
||||||
|
const readme = await fs.readFile(indexFile, 'utf8');
|
||||||
|
let { mdxSource, frontMatter } = await parse(readme, '.mdx');
|
||||||
|
return {
|
||||||
|
props: {
|
||||||
|
mdxSource,
|
||||||
|
frontMatter,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function DatasetPage({ mdxSource, frontMatter }) {
|
||||||
|
return (
|
||||||
|
<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>
|
||||||
|
<DRD source={mdxSource} />
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -1,6 +1,4 @@
|
|||||||
import '../styles/globals.css'
|
import '../styles/globals.css'
|
||||||
import '@portaljs/components/styles.css'
|
|
||||||
|
|
||||||
import type { AppProps } from 'next/app'
|
import type { AppProps } from 'next/app'
|
||||||
|
|
||||||
export default function App({ Component, pageProps }: AppProps) {
|
export default function App({ Component, pageProps }: AppProps) {
|
||||||
20
examples/basic-example/pages/api/get-data-file.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
|
||||||
|
import type { NextApiRequest, NextApiResponse } from 'next'
|
||||||
|
import { promises as fs } from 'fs';
|
||||||
|
import path from 'path';
|
||||||
|
|
||||||
|
export default async function handler(
|
||||||
|
req: NextApiRequest,
|
||||||
|
res: NextApiResponse<string>
|
||||||
|
) {
|
||||||
|
const contentDir = path.join(process.cwd(), '/content');
|
||||||
|
const datasets = await fs.readdir(contentDir);
|
||||||
|
const query = req.query;
|
||||||
|
const { fileName } = query;
|
||||||
|
const dataFile = path.join(
|
||||||
|
process.cwd(),
|
||||||
|
'/content/' + datasets[0] + '/' + fileName
|
||||||
|
);
|
||||||
|
const data = await fs.readFile(dataFile, 'utf8');
|
||||||
|
res.status(200).send(data)
|
||||||
|
}
|
||||||
42
examples/basic-example/pages/index.tsx
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import { GetStaticProps } from 'next';
|
||||||
|
import { promises as fs } from 'fs';
|
||||||
|
import path from 'path';
|
||||||
|
import parse from '../lib/markdown';
|
||||||
|
import DRD from '../components/DRD';
|
||||||
|
|
||||||
|
export const getStaticProps = async (context) => {
|
||||||
|
const indexFile = path.join(process.cwd(), '/content/index.md');
|
||||||
|
const readme = await fs.readFile(indexFile, 'utf8');
|
||||||
|
let { mdxSource, frontMatter } = await parse(readme, '.mdx');
|
||||||
|
return {
|
||||||
|
props: {
|
||||||
|
mdxSource,
|
||||||
|
frontMatter,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function DatasetPage({ mdxSource, frontMatter }) {
|
||||||
|
return (
|
||||||
|
<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>
|
||||||
|
<DRD source={mdxSource} />
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
3
examples/basic-example/public/data_1.csv
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
Year,Temp Anomaly
|
||||||
|
1850,-0.418
|
||||||
|
2020,0.923
|
||||||
|
173
examples/basic-example/public/data_2.csv
Normal file
@ -0,0 +1,173 @@
|
|||||||
|
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
|
||||||
|
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 25 KiB |
4
examples/basic-example/public/vercel.svg
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<svg width="283" height="64" viewBox="0 0 283 64" fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M141.04 16c-11.04 0-19 7.2-19 18s8.96 18 20 18c6.67 0 12.55-2.64 16.19-7.09l-7.65-4.42c-2.02 2.21-5.09 3.5-8.54 3.5-4.79 0-8.86-2.5-10.37-6.5h28.02c.22-1.12.35-2.28.35-3.5 0-10.79-7.96-17.99-19-17.99zm-9.46 14.5c1.25-3.99 4.67-6.5 9.45-6.5 4.79 0 8.21 2.51 9.45 6.5h-18.9zM248.72 16c-11.04 0-19 7.2-19 18s8.96 18 20 18c6.67 0 12.55-2.64 16.19-7.09l-7.65-4.42c-2.02 2.21-5.09 3.5-8.54 3.5-4.79 0-8.86-2.5-10.37-6.5h28.02c.22-1.12.35-2.28.35-3.5 0-10.79-7.96-17.99-19-17.99zm-9.45 14.5c1.25-3.99 4.67-6.5 9.45-6.5 4.79 0 8.21 2.51 9.45 6.5h-18.9zM200.24 34c0 6 3.92 10 10 10 4.12 0 7.21-1.87 8.8-4.92l7.68 4.43c-3.18 5.3-9.14 8.49-16.48 8.49-11.05 0-19-7.2-19-18s7.96-18 19-18c7.34 0 13.29 3.19 16.48 8.49l-7.68 4.43c-1.59-3.05-4.68-4.92-8.8-4.92-6.07 0-10 4-10 10zm82.48-29v46h-9V5h9zM36.95 0L73.9 64H0L36.95 0zm92.38 5l-27.71 48L73.91 5H84.3l17.32 30 17.32-30h10.39zm58.91 12v9.69c-1-.29-2.06-.49-3.2-.49-5.81 0-10 4-10 10V51h-9V17h9v9.2c0-5.08 5.91-9.2 13.2-9.2z" fill="#000"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.1 KiB |
129
examples/basic-example/styles/Home.module.css
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
.container {
|
||||||
|
padding: 0 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main {
|
||||||
|
min-height: 100vh;
|
||||||
|
padding: 4rem 0;
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
padding: 2rem 0;
|
||||||
|
border-top: 1px solid #eaeaea;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer a {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title a {
|
||||||
|
color: #0070f3;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title a:hover,
|
||||||
|
.title a:focus,
|
||||||
|
.title a:active {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
margin: 0;
|
||||||
|
line-height: 1.15;
|
||||||
|
font-size: 4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title,
|
||||||
|
.description {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.description {
|
||||||
|
margin: 4rem 0;
|
||||||
|
line-height: 1.5;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code {
|
||||||
|
background: #fafafa;
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 0.75rem;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono,
|
||||||
|
Bitstream Vera Sans Mono, Courier New, monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
max-width: 800px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
margin: 1rem;
|
||||||
|
padding: 1.5rem;
|
||||||
|
text-align: left;
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: none;
|
||||||
|
border: 1px solid #eaeaea;
|
||||||
|
border-radius: 10px;
|
||||||
|
transition: color 0.15s ease, border-color 0.15s ease;
|
||||||
|
max-width: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card:hover,
|
||||||
|
.card:focus,
|
||||||
|
.card:active {
|
||||||
|
color: #0070f3;
|
||||||
|
border-color: #0070f3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card h2 {
|
||||||
|
margin: 0 0 1rem 0;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card p {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 1.25rem;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
height: 1em;
|
||||||
|
margin-left: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
.grid {
|
||||||
|
width: 100%;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
.card,
|
||||||
|
.footer {
|
||||||
|
border-color: #222;
|
||||||
|
}
|
||||||
|
.code {
|
||||||
|
background: #111;
|
||||||
|
}
|
||||||
|
.logo img {
|
||||||
|
filter: invert(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,7 +1,7 @@
|
|||||||
@tailwind base;
|
@tailwind base;
|
||||||
@tailwind components;
|
@tailwind components;
|
||||||
@tailwind utilities;
|
@tailwind utilities;
|
||||||
@import "@portaljs/remark-callouts/styles.css";
|
@import "@flowershow/remark-callouts/styles.css";
|
||||||
|
|
||||||
.w-5 {
|
.w-5 {
|
||||||
width: 1.25rem
|
width: 1.25rem
|
||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "es6",
|
"target": "es5",
|
||||||
"lib": ["dom", "dom.iterable", "esnext"],
|
"lib": ["dom", "dom.iterable", "esnext"],
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
**🚩 UPDATE April 2023: This example is now deprecated - though still works!. Please use the [new CKAN examples](https://github.com/datopian/portaljs/tree/main/examples)**
|
**🚩 UPDATE April 2023: This example is now deprecated - though still works!. Please use the [new CKAN examples](https://github.com/datopian/portaljs/tree/main/examples)**
|
||||||
|
|
||||||
This example shows how you can build a full data portal using a CKAN Backend with a Next.JS Frontend powered by Apollo, a full fledged guide is available as a [blog post](https://portaljs.com/blog/example-ckan-2021)
|
This example shows how you can build a full data portal using a CKAN Backend with a Next.JS Frontend powered by Apollo, a full fledged guide is available as a [blog post](https://portaljs.org/blog/example-ckan-2021)
|
||||||
|
|
||||||
## Developers
|
## Developers
|
||||||
|
|
||||||
|
|||||||
1
examples/ckan-example/.env
Normal file
@ -0,0 +1 @@
|
|||||||
|
DMS=https://demo.dev.datopian.com
|
||||||
@ -1,7 +1,7 @@
|
|||||||
This is a repo intended to serve as an example of a data catalog that get its data from a CKAN Instance.
|
This is a repo intended to serve as an example of a data catalog that get its data from a CKAN Instance.
|
||||||
|
|
||||||
```
|
```
|
||||||
npx create-next-app <app-name> --example https://github.com/datopian/datahub/tree/main/examples/ckan-ssg
|
npx create-next-app <app-name> --example https://github.com/datopian/portaljs/tree/main/examples/ckan-example
|
||||||
cd <app-name>
|
cd <app-name>
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -19,7 +19,7 @@ npm run dev
|
|||||||
|
|
||||||
Congratulations, you now have something similar to this running on `http://localhost:4200`
|
Congratulations, you now have something similar to this running on `http://localhost:4200`
|
||||||

|

|
||||||
If you go to any one of those pages by clicking on `More info` you will see something similar to this
|
If yo go to any one of those pages by clicking on `More info` you will see something similar to this
|
||||||

|

|
||||||
|
|
||||||
## Deployment
|
## Deployment
|
||||||
@ -10,26 +10,24 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@heroicons/react": "^2.0.17",
|
"@heroicons/react": "^2.0.17",
|
||||||
"@portaljs/ckan": "^0.0.2",
|
"@types/node": "18.16.0",
|
||||||
"@portaljs/remark-wiki-link": "^1.0.4",
|
"@types/react": "18.0.38",
|
||||||
|
"@types/react-dom": "18.0.11",
|
||||||
|
"eslint": "8.39.0",
|
||||||
|
"eslint-config-next": "13.3.1",
|
||||||
"next": "13.3.1",
|
"next": "13.3.1",
|
||||||
"next-seo": "^6.0.0",
|
"next-seo": "^6.0.0",
|
||||||
"octokit": "^2.0.14",
|
"octokit": "^2.0.14",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "18.2.0",
|
||||||
"react-markdown": "^8.0.7",
|
"react-markdown": "^8.0.7",
|
||||||
"remark-gfm": "^3.0.1"
|
"remark-gfm": "^3.0.1",
|
||||||
|
"typescript": "5.0.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tailwindcss/typography": "^0.5.9",
|
"@tailwindcss/typography": "^0.5.9",
|
||||||
"@types/node": "18.16.0",
|
|
||||||
"@types/react": "18.0.38",
|
|
||||||
"@types/react-dom": "18.0.11",
|
|
||||||
"autoprefixer": "^10.4.14",
|
"autoprefixer": "^10.4.14",
|
||||||
"eslint": "8.39.0",
|
|
||||||
"eslint-config-next": "13.3.1",
|
|
||||||
"postcss": "^8.4.23",
|
"postcss": "^8.4.23",
|
||||||
"tailwindcss": "^3.3.1",
|
"tailwindcss": "^3.3.1"
|
||||||
"typescript": "5.0.4"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -11,9 +11,8 @@ import {
|
|||||||
ServerIcon,
|
ServerIcon,
|
||||||
UserIcon,
|
UserIcon,
|
||||||
} from '@heroicons/react/20/solid';
|
} from '@heroicons/react/20/solid';
|
||||||
import { CKAN } from '@portaljs/ckan';
|
|
||||||
|
|
||||||
const backend_url = getConfig().publicRuntimeConfig.DMS;
|
const dms = getConfig().publicRuntimeConfig.DMS;
|
||||||
|
|
||||||
const formatter = new Intl.DateTimeFormat('en-US', {
|
const formatter = new Intl.DateTimeFormat('en-US', {
|
||||||
year: 'numeric',
|
year: 'numeric',
|
||||||
@ -26,12 +25,14 @@ const formatter = new Intl.DateTimeFormat('en-US', {
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const getServerSideProps: GetServerSideProps = async (context) => {
|
export const getServerSideProps: GetServerSideProps = async (context) => {
|
||||||
const ckan = new CKAN(backend_url)
|
|
||||||
const { dataset } = context.query;
|
const { dataset } = context.query;
|
||||||
const _dataset = await ckan.getDatasetDetails(dataset as string)
|
const response = await fetch(
|
||||||
|
`${dms}/api/3/action/package_show?id=${dataset}`
|
||||||
|
);
|
||||||
|
const _dataset = await response.json();
|
||||||
return {
|
return {
|
||||||
props: {
|
props: {
|
||||||
dataset: _dataset,
|
dataset: _dataset.result,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@ -1,8 +1,7 @@
|
|||||||
import getConfig from 'next/config';
|
import getConfig from 'next/config';
|
||||||
import styles from './index.module.css';
|
import styles from './index.module.css';
|
||||||
import { CKAN } from '@portaljs/ckan';
|
|
||||||
|
|
||||||
const backend_url = getConfig().publicRuntimeConfig.DMS
|
const dms = getConfig().publicRuntimeConfig.DMS
|
||||||
|
|
||||||
const formatter = new Intl.DateTimeFormat('en-US', {
|
const formatter = new Intl.DateTimeFormat('en-US', {
|
||||||
year: 'numeric',
|
year: 'numeric',
|
||||||
@ -16,11 +15,12 @@ const formatter = new Intl.DateTimeFormat('en-US', {
|
|||||||
|
|
||||||
|
|
||||||
export async function getServerSideProps() {
|
export async function getServerSideProps() {
|
||||||
const ckan = new CKAN(backend_url)
|
const response = await fetch(`${dms}/api/3/action/package_search`)
|
||||||
const { datasets } = await ckan.packageSearch({ limit: 1000, offset: 0, groups:[], orgs: [], tags: []})
|
const datasets = await response.json()
|
||||||
const datasetsWithDetails = await Promise.all(datasets.map(async (dataset) => {
|
const datasetsWithDetails = await Promise.all(datasets.result.results.map(async (dataset) => {
|
||||||
const _dataset = await ckan.getDatasetDetails(dataset.name)
|
const response = await fetch(`${dms}/api/3/action/package_show?id=` + dataset.name)
|
||||||
return _dataset
|
const json = await response.json()
|
||||||
|
return json.result
|
||||||
}))
|
}))
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -79,7 +79,7 @@ export function Index({ datasets }) {
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody className="divide-y divide-gray-200">
|
<tbody className="divide-y divide-gray-200">
|
||||||
{datasets.map((dataset) => (
|
{datasets.map((dataset) => (
|
||||||
<tr key={dataset.name}>
|
<tr>
|
||||||
<td className="px-3 py-4 text-sm text-gray-500">
|
<td className="px-3 py-4 text-sm text-gray-500">
|
||||||
{dataset.title}
|
{dataset.title}
|
||||||
</td>
|
</td>
|
||||||
35
examples/ckan/.gitignore
vendored
@ -1,35 +0,0 @@
|
|||||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
|
||||||
|
|
||||||
# dependencies
|
|
||||||
/node_modules
|
|
||||||
/.pnp
|
|
||||||
.pnp.js
|
|
||||||
|
|
||||||
# testing
|
|
||||||
/coverage
|
|
||||||
|
|
||||||
# next.js
|
|
||||||
/.next/
|
|
||||||
/out/
|
|
||||||
|
|
||||||
# production
|
|
||||||
/build
|
|
||||||
|
|
||||||
# misc
|
|
||||||
.DS_Store
|
|
||||||
*.pem
|
|
||||||
|
|
||||||
# debug
|
|
||||||
npm-debug.log*
|
|
||||||
yarn-debug.log*
|
|
||||||
yarn-error.log*
|
|
||||||
|
|
||||||
# local env files
|
|
||||||
.env*.local
|
|
||||||
|
|
||||||
# vercel
|
|
||||||
.vercel
|
|
||||||
|
|
||||||
# typescript
|
|
||||||
*.tsbuildinfo
|
|
||||||
next-env.d.ts
|
|
||||||
@ -1,21 +0,0 @@
|
|||||||
import { MDXRemote } from 'next-mdx-remote';
|
|
||||||
import dynamic from 'next/dynamic';
|
|
||||||
import { Mermaid } from '@portaljs/core';
|
|
||||||
|
|
||||||
// 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 = {
|
|
||||||
Table: dynamic(() => import('@portaljs/components').then(mod => mod.Table)),
|
|
||||||
Catalog: dynamic(() => import('@portaljs/components').then(mod => mod.Catalog)),
|
|
||||||
FlatUiTable: dynamic(() => import('@portaljs/components').then(mod => mod.FlatUiTable)),
|
|
||||||
mermaid: Mermaid,
|
|
||||||
Vega: dynamic(() => import('@portaljs/components').then(mod => mod.Vega)),
|
|
||||||
VegaLite: dynamic(() => import('@portaljs/components').then(mod => mod.VegaLite)),
|
|
||||||
LineChart: dynamic(() => import('@portaljs/components').then(mod => mod.LineChart)),
|
|
||||||
} as any;
|
|
||||||
|
|
||||||
export default function DRD({ source }: { source: any }) {
|
|
||||||
return <MDXRemote {...source} components={components} />;
|
|
||||||
}
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
# Test
|
|
||||||
|
|
||||||
Test Data Rich Stories
|
|
||||||
@ -1,105 +0,0 @@
|
|||||||
import matter from "gray-matter";
|
|
||||||
import mdxmermaid from "mdx-mermaid";
|
|
||||||
import { h } from "hastscript";
|
|
||||||
import remarkCallouts from "@portaljs/remark-callouts";
|
|
||||||
import remarkEmbed from "@portaljs/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 "@portaljs/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, excerpt } = matter(source, {
|
|
||||||
excerpt: (file, options) => {
|
|
||||||
// Generate an excerpt for the file
|
|
||||||
file.excerpt = file.content.split("\n\n")[0];
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
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,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
mdxSource: mdxSource,
|
|
||||||
frontMatter: data,
|
|
||||||
excerpt,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default parse;
|
|
||||||
@ -1,14 +0,0 @@
|
|||||||
import { MarkdownDB } from "mddb";
|
|
||||||
|
|
||||||
const dbPath = "markdown.db";
|
|
||||||
|
|
||||||
const client = new MarkdownDB({
|
|
||||||
client: "sqlite3",
|
|
||||||
connection: {
|
|
||||||
filename: dbPath,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const clientPromise = client.init();
|
|
||||||
|
|
||||||
export default clientPromise;
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
/** @type {import('next').NextConfig} */
|
|
||||||
const nextConfig = {
|
|
||||||
reactStrictMode: true,
|
|
||||||
publicRuntimeConfig: {
|
|
||||||
DMS: process.env.DMS
|
|
||||||
? process.env.DMS.replace(/\/?$/, '')
|
|
||||||
: 'https://demo.dev.datopian.com/',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = nextConfig;
|
|
||||||
13799
examples/ckan/package-lock.json
generated
@ -1,48 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "ckan",
|
|
||||||
"version": "0.1.0",
|
|
||||||
"private": true,
|
|
||||||
"scripts": {
|
|
||||||
"dev": "next dev",
|
|
||||||
"prebuild": "npm run mddb",
|
|
||||||
"build": "next build",
|
|
||||||
"start": "next start",
|
|
||||||
"lint": "next lint",
|
|
||||||
"mddb": "mddb ./content"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@githubocto/flat-ui": "^0.14.1",
|
|
||||||
"@heroicons/react": "^2.0.18",
|
|
||||||
"@portaljs/ckan": "^0.0.2",
|
|
||||||
"@portaljs/components": "0.1.6",
|
|
||||||
"@portaljs/core": "^1.0.5",
|
|
||||||
"@portaljs/remark-callouts": "^1.0.5",
|
|
||||||
"@portaljs/remark-embed": "^1.0.4",
|
|
||||||
"@portaljs/remark-wiki-link": "^1.0.4",
|
|
||||||
"@tailwindcss/typography": "^0.5.9",
|
|
||||||
"@types/node": "20.2.3",
|
|
||||||
"@types/react": "18.2.6",
|
|
||||||
"@types/react-dom": "18.2.4",
|
|
||||||
"autoprefixer": "10.4.14",
|
|
||||||
"eslint": "8.41.0",
|
|
||||||
"eslint-config-next": "13.4.3",
|
|
||||||
"isomorphic-unfetch": "^4.0.2",
|
|
||||||
"mddb": "^0.1.9",
|
|
||||||
"next": "13.4.3",
|
|
||||||
"next-mdx-remote": "^4.4.1",
|
|
||||||
"papaparse": "^5.4.1",
|
|
||||||
"postcss": "8.4.23",
|
|
||||||
"react": "18.2.0",
|
|
||||||
"react-dom": "18.2.0",
|
|
||||||
"react-query": "^3.39.3",
|
|
||||||
"rehype-autolink-headings": "^6.1.1",
|
|
||||||
"rehype-katex": "^6.0.3",
|
|
||||||
"rehype-prism-plus": "^1.5.1",
|
|
||||||
"rehype-slug": "^5.1.0",
|
|
||||||
"remark-math": "^5.1.1",
|
|
||||||
"remark-smartypants": "^2.0.0",
|
|
||||||
"remark-toc": "^8.0.1",
|
|
||||||
"tailwindcss": "3.3.2",
|
|
||||||
"typescript": "5.0.4"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,179 +0,0 @@
|
|||||||
import Head from "next/head";
|
|
||||||
import { CKAN, Dataset } from "@portaljs/ckan";
|
|
||||||
import {
|
|
||||||
ChevronRightIcon,
|
|
||||||
HomeIcon,
|
|
||||||
PaperClipIcon,
|
|
||||||
} from "@heroicons/react/20/solid";
|
|
||||||
import Link from "next/link";
|
|
||||||
import getConfig from "next/config";
|
|
||||||
|
|
||||||
const backend_url = getConfig().publicRuntimeConfig.DMS
|
|
||||||
|
|
||||||
export const getServerSideProps = async (context: any) => {
|
|
||||||
try {
|
|
||||||
const datasetName = context.params?.dataset;
|
|
||||||
if (!datasetName) {
|
|
||||||
return {
|
|
||||||
notFound: true,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
const ckan = new CKAN(backend_url);
|
|
||||||
const dataset = await ckan.getDatasetDetails(datasetName as string);
|
|
||||||
if (!dataset) {
|
|
||||||
return {
|
|
||||||
notFound: true,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
props: { dataset },
|
|
||||||
};
|
|
||||||
} catch {
|
|
||||||
return {
|
|
||||||
notFound: true,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function DatasetPage({
|
|
||||||
dataset,
|
|
||||||
}: {
|
|
||||||
dataset: Dataset;
|
|
||||||
}): JSX.Element {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Head>
|
|
||||||
<title>{`${dataset.title || dataset.name} - Dataset`}</title>
|
|
||||||
<meta name="description" content="Generated by create next app" />
|
|
||||||
<link rel="icon" href="/favicon.ico" />
|
|
||||||
</Head>
|
|
||||||
<main className="flex min-h-screen flex-col items-center justify-between p-24 bg-zinc-900">
|
|
||||||
<div className="bg-white p-8 my-4 rounded-lg">
|
|
||||||
<nav className="flex px-4 py-8" aria-label="Breadcrumb">
|
|
||||||
<ol role="list" className="flex items-center space-x-4">
|
|
||||||
<li>
|
|
||||||
<div>
|
|
||||||
<Link href="/" className="text-gray-400 hover:text-gray-500">
|
|
||||||
<HomeIcon
|
|
||||||
className="h-5 w-5 flex-shrink-0"
|
|
||||||
aria-hidden="true"
|
|
||||||
/>
|
|
||||||
<span className="sr-only">Home</span>
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<div className="flex items-center">
|
|
||||||
<ChevronRightIcon
|
|
||||||
className="h-5 w-5 flex-shrink-0 text-gray-400"
|
|
||||||
aria-hidden="true"
|
|
||||||
/>
|
|
||||||
<span
|
|
||||||
className="ml-4 text-sm font-medium text-gray-500 hover:text-gray-700"
|
|
||||||
aria-current={"page"}
|
|
||||||
>
|
|
||||||
{dataset.name}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</ol>
|
|
||||||
</nav>
|
|
||||||
{dataset && (
|
|
||||||
<div>
|
|
||||||
<div className="px-4 sm:px-0">
|
|
||||||
<h3 className="text-base font-semibold leading-7 text-gray-900">
|
|
||||||
{dataset.title || dataset.name}
|
|
||||||
</h3>
|
|
||||||
<p className="mt-1 max-w-2xl text-sm leading-6 text-gray-500">
|
|
||||||
Dataset details
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div className="mt-6 border-t border-gray-100">
|
|
||||||
<dl className="divide-y divide-gray-100">
|
|
||||||
<div className="px-4 py-6 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
|
|
||||||
<dt className="text-sm font-medium leading-6 text-gray-900">
|
|
||||||
Title
|
|
||||||
</dt>
|
|
||||||
<dd className="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0">
|
|
||||||
{dataset.title}
|
|
||||||
</dd>
|
|
||||||
</div>
|
|
||||||
{dataset.tags && dataset.tags.length > 0 && (
|
|
||||||
<div className="px-4 py-6 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
|
|
||||||
<dt className="text-sm font-medium leading-6 text-gray-900">
|
|
||||||
Tags
|
|
||||||
</dt>
|
|
||||||
<dd className="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0">
|
|
||||||
{dataset.tags.map((tag) => tag.display_name).join(", ")}
|
|
||||||
</dd>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{dataset.tags && dataset.tags.length > 0 && (
|
|
||||||
<div className="px-4 py-6 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
|
|
||||||
<dt className="text-sm font-medium leading-6 text-gray-900">
|
|
||||||
URL
|
|
||||||
</dt>
|
|
||||||
<dd className="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0">
|
|
||||||
{dataset.url}
|
|
||||||
</dd>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div className="px-4 py-6 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
|
|
||||||
{dataset.notes && (
|
|
||||||
<>
|
|
||||||
<dt className="text-sm font-medium leading-6 text-gray-900">
|
|
||||||
Description
|
|
||||||
</dt>
|
|
||||||
<dd className="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0">
|
|
||||||
{dataset.notes}
|
|
||||||
</dd>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className="px-4 py-6 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
|
|
||||||
<dt className="text-sm font-medium leading-6 text-gray-900">
|
|
||||||
Files
|
|
||||||
</dt>
|
|
||||||
<dd className="mt-2 text-sm text-gray-900 sm:col-span-2 sm:mt-0">
|
|
||||||
<ul
|
|
||||||
role="list"
|
|
||||||
className="divide-y divide-gray-100 rounded-md border border-gray-200"
|
|
||||||
>
|
|
||||||
{dataset.resources.map((resource) => (
|
|
||||||
<li key={resource.id} className="flex items-center justify-between py-4 pl-4 pr-5 text-sm leading-6">
|
|
||||||
<div className="flex w-0 flex-1 items-center">
|
|
||||||
<PaperClipIcon
|
|
||||||
className="h-5 w-5 flex-shrink-0 text-gray-400"
|
|
||||||
aria-hidden="true"
|
|
||||||
/>
|
|
||||||
<div className="ml-4 flex min-w-0 flex-1 gap-2">
|
|
||||||
<span className="truncate font-medium">
|
|
||||||
{resource.name || resource.id}
|
|
||||||
</span>
|
|
||||||
<span className="flex-shrink-0 text-gray-400">
|
|
||||||
{resource.size}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="ml-4 flex-shrink-0">
|
|
||||||
<a
|
|
||||||
href={resource.url}
|
|
||||||
className="font-medium hover:text-indigo-500 mr-4"
|
|
||||||
>
|
|
||||||
Download
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
</dd>
|
|
||||||
</div>
|
|
||||||
</dl>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,7 +0,0 @@
|
|||||||
import '@/styles/globals.css'
|
|
||||||
import '@portaljs/ckan/styles.css'
|
|
||||||
import type { AppProps } from 'next/app'
|
|
||||||
|
|
||||||
export default function App({ Component, pageProps }: AppProps) {
|
|
||||||
return <Component {...pageProps} />
|
|
||||||
}
|
|
||||||
@ -1,13 +0,0 @@
|
|||||||
import { Html, Head, Main, NextScript } from 'next/document'
|
|
||||||
|
|
||||||
export default function Document() {
|
|
||||||
return (
|
|
||||||
<Html lang="en">
|
|
||||||
<Head />
|
|
||||||
<body>
|
|
||||||
<Main />
|
|
||||||
<NextScript />
|
|
||||||
</body>
|
|
||||||
</Html>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@ -1,18 +0,0 @@
|
|||||||
import fetch from 'isomorphic-unfetch';
|
|
||||||
|
|
||||||
const Cors = async (req: any, res: any) => {
|
|
||||||
const { url } = req.query;
|
|
||||||
try {
|
|
||||||
const resProxy = await fetch(url, {
|
|
||||||
headers: {
|
|
||||||
Range: 'bytes=0-5132288',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const data = await resProxy.text();
|
|
||||||
return res.status(200).send(data);
|
|
||||||
} catch (error: any) {
|
|
||||||
res.status(400).send(error.toString());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Cors;
|
|
||||||
@ -1,64 +0,0 @@
|
|||||||
import {
|
|
||||||
CKAN,
|
|
||||||
DatasetSearchForm,
|
|
||||||
ListOfDatasets,
|
|
||||||
PackageSearchOptions,
|
|
||||||
Organization,
|
|
||||||
Group,
|
|
||||||
} from '@portaljs/ckan';
|
|
||||||
import getConfig from 'next/config';
|
|
||||||
import { useState } from 'react';
|
|
||||||
|
|
||||||
const backend_url = getConfig().publicRuntimeConfig.DMS;
|
|
||||||
|
|
||||||
export async function getServerSideProps() {
|
|
||||||
const ckan = new CKAN(backend_url);
|
|
||||||
const groups = await ckan.getGroupsWithDetails();
|
|
||||||
const orgs = await ckan.getOrgsWithDetails();
|
|
||||||
return {
|
|
||||||
props: {
|
|
||||||
groups,
|
|
||||||
orgs,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function Home({
|
|
||||||
orgs,
|
|
||||||
groups,
|
|
||||||
}: {
|
|
||||||
orgs: Organization[];
|
|
||||||
groups: Group[];
|
|
||||||
}) {
|
|
||||||
const ckan = new CKAN(backend_url);
|
|
||||||
const [options, setOptions] = useState<PackageSearchOptions>({
|
|
||||||
offset: 0,
|
|
||||||
limit: 5,
|
|
||||||
tags: [],
|
|
||||||
groups: [],
|
|
||||||
orgs: [],
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<main className="py-12 bg-zinc-900">
|
|
||||||
<DatasetSearchForm
|
|
||||||
options={options}
|
|
||||||
setOptions={setOptions}
|
|
||||||
groups={groups}
|
|
||||||
orgs={orgs}
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
className="bg-white p-8 mx-auto my-4 rounded-lg"
|
|
||||||
style={{ width: 'min(1100px, 95vw)' }}
|
|
||||||
>
|
|
||||||
<ListOfDatasets
|
|
||||||
options={options}
|
|
||||||
setOptions={setOptions}
|
|
||||||
ckan={ckan}
|
|
||||||
/>{' '}
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,129 +0,0 @@
|
|||||||
import { existsSync, promises as fs } from 'fs';
|
|
||||||
import path from 'path';
|
|
||||||
import parse from '../../lib/markdown';
|
|
||||||
|
|
||||||
import DataRichDocument from '../../components/DataRichDocument';
|
|
||||||
import clientPromise from '../../lib/mddb';
|
|
||||||
import getConfig from 'next/config';
|
|
||||||
import { CKAN } from '@portaljs/ckan';
|
|
||||||
|
|
||||||
export const getStaticPaths = async () => {
|
|
||||||
const contentDir = path.join(process.cwd(), '/content/');
|
|
||||||
const contentFolders = await fs.readdir(contentDir, 'utf8');
|
|
||||||
const paths = contentFolders.map((folder: string) => ({
|
|
||||||
params: { path: [folder.split('.')[0]] },
|
|
||||||
}));
|
|
||||||
return {
|
|
||||||
paths,
|
|
||||||
fallback: false,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const backend_url = getConfig().publicRuntimeConfig.DMS;
|
|
||||||
|
|
||||||
export const getStaticProps = async (context) => {
|
|
||||||
const mddb = await clientPromise;
|
|
||||||
const storyFile = await mddb.getFileByUrl(context.params.path);
|
|
||||||
const md = await fs.readFile(
|
|
||||||
`${process.cwd()}/${storyFile.file_path}`,
|
|
||||||
'utf8'
|
|
||||||
);
|
|
||||||
|
|
||||||
const ckan = new CKAN(backend_url);
|
|
||||||
const datasets = storyFile.metadata.datasets ? await Promise.all(
|
|
||||||
storyFile.metadata.datasets.map(
|
|
||||||
async (datasetName: string) => await ckan.getDatasetDetails(datasetName)
|
|
||||||
)
|
|
||||||
) : [];
|
|
||||||
const orgs = storyFile.metadata.orgs ? await Promise.all(
|
|
||||||
storyFile.metadata.orgs.map(
|
|
||||||
async (orgName: string) => await ckan.getOrgDetails(orgName)
|
|
||||||
)
|
|
||||||
) : [];
|
|
||||||
|
|
||||||
let { mdxSource, frontMatter } = await parse(md, '.mdx', { datasets, orgs });
|
|
||||||
|
|
||||||
return {
|
|
||||||
props: {
|
|
||||||
mdxSource,
|
|
||||||
frontMatter: JSON.stringify(frontMatter),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function DatasetPage({ mdxSource, frontMatter }) {
|
|
||||||
frontMatter = JSON.parse(frontMatter);
|
|
||||||
return (
|
|
||||||
<main className="flex min-h-screen flex-col justify-between p-16 bg-zinc-900">
|
|
||||||
<div className="bg-white p-8 my-4 rounded-lg">
|
|
||||||
<div className="prose mx-auto py-8">
|
|
||||||
<header>
|
|
||||||
<div className="mb-6">
|
|
||||||
<>
|
|
||||||
<h1 className="mb-2">{frontMatter.title}</h1>
|
|
||||||
{frontMatter.author && (
|
|
||||||
<p className="my-0">
|
|
||||||
<span className="font-semibold">Author: </span>
|
|
||||||
<span className="my-0">{frontMatter.author}</span>
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
{frontMatter.description && (
|
|
||||||
<p className="my-0">
|
|
||||||
<span className="font-semibold">Description: </span>
|
|
||||||
<span className="description my-0">
|
|
||||||
{frontMatter.description}
|
|
||||||
</span>
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
{frontMatter.modified && (
|
|
||||||
<p className="my-0">
|
|
||||||
<span className="font-semibold">Modified: </span>
|
|
||||||
<span className="description my-0">
|
|
||||||
{new Date(frontMatter.modified).toLocaleDateString()}
|
|
||||||
</span>
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
{frontMatter.files && (
|
|
||||||
<section className="py-6">
|
|
||||||
<h2 className="mt-0">Data files</h2>
|
|
||||||
<table className="table-auto">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>File</th>
|
|
||||||
<th>Format</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{frontMatter.files.map((f) => {
|
|
||||||
const fileName = f.split('/').slice(-1);
|
|
||||||
return (
|
|
||||||
<tr key={`resources-list-${f}`}>
|
|
||||||
<td>
|
|
||||||
<a target="_blank" href={f}>
|
|
||||||
{fileName}
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{fileName[0]
|
|
||||||
.split('.')
|
|
||||||
.slice(-1)[0]
|
|
||||||
.toUpperCase()}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</section>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
<main>
|
|
||||||
<DataRichDocument source={mdxSource} />
|
|
||||||
</main>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 1.3 KiB |
@ -1 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 283 64"><path fill="black" d="M141 16c-11 0-19 7-19 18s9 18 20 18c7 0 13-3 16-7l-7-5c-2 3-6 4-9 4-5 0-9-3-10-7h28v-3c0-11-8-18-19-18zm-9 15c1-4 4-7 9-7s8 3 9 7h-18zm117-15c-11 0-19 7-19 18s9 18 20 18c6 0 12-3 16-7l-8-5c-2 3-5 4-8 4-5 0-9-3-11-7h28l1-3c0-11-8-18-19-18zm-10 15c2-4 5-7 10-7s8 3 9 7h-19zm-39 3c0 6 4 10 10 10 4 0 7-2 9-5l8 5c-3 5-9 8-17 8-11 0-19-7-19-18s8-18 19-18c8 0 14 3 17 8l-8 5c-2-3-5-5-9-5-6 0-10 4-10 10zm83-29v46h-9V5h9zM37 0l37 64H0L37 0zm92 5-27 48L74 5h10l18 30 17-30h10zm59 12v10l-3-1c-6 0-10 4-10 10v15h-9V17h9v9c0-5 6-9 13-9z"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 629 B |
@ -1,71 +0,0 @@
|
|||||||
@tailwind base;
|
|
||||||
@tailwind components;
|
|
||||||
@tailwind utilities;
|
|
||||||
|
|
||||||
@import "@portaljs/remark-callouts/styles.css";
|
|
||||||
|
|
||||||
/* 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,18 +0,0 @@
|
|||||||
/** @type {import('tailwindcss').Config} */
|
|
||||||
module.exports = {
|
|
||||||
content: [
|
|
||||||
'./pages/**/*.{js,ts,jsx,tsx,mdx}',
|
|
||||||
'./components/**/*.{js,ts,jsx,tsx,mdx}',
|
|
||||||
'./app/**/*.{js,ts,jsx,tsx,mdx}',
|
|
||||||
],
|
|
||||||
theme: {
|
|
||||||
extend: {
|
|
||||||
backgroundImage: {
|
|
||||||
'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))',
|
|
||||||
'gradient-conic':
|
|
||||||
'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
plugins: [require('@tailwindcss/typography')],
|
|
||||||
}
|
|
||||||
@ -1,23 +0,0 @@
|
|||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"target": "es5",
|
|
||||||
"lib": ["dom", "dom.iterable", "esnext"],
|
|
||||||
"allowJs": true,
|
|
||||||
"skipLibCheck": true,
|
|
||||||
"strict": false,
|
|
||||||
"forceConsistentCasingInFileNames": true,
|
|
||||||
"noEmit": true,
|
|
||||||
"esModuleInterop": true,
|
|
||||||
"module": "esnext",
|
|
||||||
"moduleResolution": "node",
|
|
||||||
"resolveJsonModule": true,
|
|
||||||
"isolatedModules": true,
|
|
||||||
"jsx": "preserve",
|
|
||||||
"incremental": true,
|
|
||||||
"paths": {
|
|
||||||
"@/*": ["./*"]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
|
|
||||||
"exclude": ["node_modules"]
|
|
||||||
}
|
|
||||||
@ -1,6 +1,6 @@
|
|||||||
This example creates a portal/showcase for a single dataset. The dataset should be a [Frictionless dataset (data package)][fd] i.e. there should be a `datapackage.json`.
|
This example creates a portal/showcase for a single dataset. The dataset should be a [Frictionless dataset (data package)][fd] i.e. there should be a `datapackage.json`.
|
||||||
|
|
||||||
[fd]: https://specs.frictionlessdata.io/data-package/
|
[fd]: https://frictionlessdata.io/data-packages/
|
||||||
|
|
||||||
## How to use
|
## How to use
|
||||||
|
|
||||||
|
|||||||
@ -1,3 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": "next/core-web-vitals"
|
|
||||||
}
|
|
||||||
35
examples/fivethirtyeight/.gitignore
vendored
@ -1,35 +0,0 @@
|
|||||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
|
||||||
|
|
||||||
# dependencies
|
|
||||||
/node_modules
|
|
||||||
/.pnp
|
|
||||||
.pnp.js
|
|
||||||
|
|
||||||
# testing
|
|
||||||
/coverage
|
|
||||||
|
|
||||||
# next.js
|
|
||||||
/.next/
|
|
||||||
/out/
|
|
||||||
|
|
||||||
# production
|
|
||||||
/build
|
|
||||||
|
|
||||||
# misc
|
|
||||||
.DS_Store
|
|
||||||
*.pem
|
|
||||||
|
|
||||||
# debug
|
|
||||||
npm-debug.log*
|
|
||||||
yarn-debug.log*
|
|
||||||
yarn-error.log*
|
|
||||||
|
|
||||||
# local env files
|
|
||||||
.env*.local
|
|
||||||
|
|
||||||
# vercel
|
|
||||||
.vercel
|
|
||||||
|
|
||||||
# typescript
|
|
||||||
*.tsbuildinfo
|
|
||||||
next-env.d.ts
|
|
||||||
@ -1,50 +0,0 @@
|
|||||||
# PortalJS Demo replicating the FiveThirtyEight data portal
|
|
||||||
|
|
||||||
## 👉 https://fivethirtyeight.portaljs.org 👈
|
|
||||||
|
|
||||||
Here's a blog post we wrote about it: https://www.datopian.com/blog/fivethirtyeight-replica
|
|
||||||
|
|
||||||
This is a replica of the awesome data.fivethirtyeight.com using PortalJS.
|
|
||||||
|
|
||||||
You might be asking why we did that, there are three main reasons:
|
|
||||||
|
|
||||||
- The website has a great UI, with multiple datasets being displayed elegantly and with simplicity.
|
|
||||||
- PortalJS allows us to add more functionality to it e.g dataset previews and search functionality.
|
|
||||||
- The project follows our same principles of open sourcing and free data, with every dataset being publicly available on Github.
|
|
||||||
|
|
||||||
## Getting Started
|
|
||||||
|
|
||||||
First, run the development server:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run dev
|
|
||||||
# or
|
|
||||||
yarn dev
|
|
||||||
# or
|
|
||||||
pnpm dev
|
|
||||||
```
|
|
||||||
|
|
||||||
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
|
||||||
|
|
||||||
You can start editing the page by modifying `pages/index.tsx`. The page auto-updates as you edit the file.
|
|
||||||
|
|
||||||
[API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.ts`.
|
|
||||||
|
|
||||||
The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages.
|
|
||||||
|
|
||||||
This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
|
|
||||||
|
|
||||||
## Learn More
|
|
||||||
|
|
||||||
To learn more about Next.js, take a look at the following resources:
|
|
||||||
|
|
||||||
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
|
|
||||||
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
|
|
||||||
|
|
||||||
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
|
|
||||||
|
|
||||||
## Deploy on Vercel
|
|
||||||
|
|
||||||
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
|
|
||||||
|
|
||||||
Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
|
|
||||||
@ -1,23 +0,0 @@
|
|||||||
import Link from "next/link";
|
|
||||||
|
|
||||||
function HomeIcon({ className = "" }) {
|
|
||||||
return <div className={`inline-block w-4 ${className}`}><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> <path d="M 12 2 A 1 1 0 0 0 11.289062 2.296875 L 1.203125 11.097656 A 0.5 0.5 0 0 0 1 11.5 A 0.5 0.5 0 0 0 1.5 12 L 4 12 L 4 20 C 4 20.552 4.448 21 5 21 L 9 21 C 9.552 21 10 20.552 10 20 L 10 14 L 14 14 L 14 20 C 14 20.552 14.448 21 15 21 L 19 21 C 19.552 21 20 20.552 20 20 L 20 12 L 22.5 12 A 0.5 0.5 0 0 0 23 11.5 A 0.5 0.5 0 0 0 22.796875 11.097656 L 12.716797 2.3027344 A 1 1 0 0 0 12.710938 2.296875 A 1 1 0 0 0 12 2 z"/></svg></div>
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function Breadcrumbs({ links }: { links: { title: string, href?: string, target?: string }[] }) {
|
|
||||||
const current = links.at(-1);
|
|
||||||
|
|
||||||
return <div className="flex items-center uppercase font-black text-xs">
|
|
||||||
<Link className="flex items-center" href='/'><HomeIcon /></Link>
|
|
||||||
|
|
||||||
{/* {links.length > 1 && links.slice(0, -1).map((link) => {
|
|
||||||
return <>
|
|
||||||
<span className="mx-4">/</span>
|
|
||||||
<Link href={link.href}>{link.title}</Link>
|
|
||||||
</>
|
|
||||||
})} */}
|
|
||||||
|
|
||||||
<span className="mx-4">/</span>
|
|
||||||
<span>{current?.title}</span>
|
|
||||||
</div >
|
|
||||||
}
|
|
||||||
@ -1,99 +0,0 @@
|
|||||||
import { useState } from 'react';
|
|
||||||
import { XMarkIcon } from '@heroicons/react/20/solid';
|
|
||||||
import { Transition } from '@headlessui/react';
|
|
||||||
|
|
||||||
export default function Layout({ children }: { children: React.ReactNode }) {
|
|
||||||
const [isShowing, setShow] = useState(true);
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Transition
|
|
||||||
show={isShowing}
|
|
||||||
enter="transition-opacity duration-75"
|
|
||||||
enterFrom="opacity-0"
|
|
||||||
enterTo="opacity-100"
|
|
||||||
leave="transition-opacity duration-150"
|
|
||||||
leaveFrom="opacity-100"
|
|
||||||
leaveTo="opacity-0"
|
|
||||||
>
|
|
||||||
<div className="flex items-center gap-x-6 bg-[#3c3c3c] px-6 py-2.5 sm:px-3.5 sm:before:flex-1">
|
|
||||||
<p className="text-sm leading-6 text-white">
|
|
||||||
This is a replica to the awesome{' '}
|
|
||||||
<a
|
|
||||||
className="hover:underline font-bold"
|
|
||||||
href="https://data.fivethirtyeight.com"
|
|
||||||
>
|
|
||||||
data.fivethirtyeight.com
|
|
||||||
</a>{' '}
|
|
||||||
website.{' '}
|
|
||||||
<a
|
|
||||||
className="hover:underline font-bold"
|
|
||||||
href="https://github.com/datopian/portaljs/tree/main/examples/fivethirtyeight#readme"
|
|
||||||
>
|
|
||||||
Read more here
|
|
||||||
</a>{' '}
|
|
||||||
</p>
|
|
||||||
<div className="flex flex-1 justify-end">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => setShow(false)}
|
|
||||||
className="-m-3 p-3 focus-visible:outline-offset-[-4px]"
|
|
||||||
>
|
|
||||||
<span className="sr-only">Dismiss</span>
|
|
||||||
<XMarkIcon className="h-5 w-5 text-white" aria-hidden="true" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Transition>
|
|
||||||
<header className="max-w-5xl mx-auto mt-8 w-full">
|
|
||||||
<div className="border-b-2 pb-2.5 mx-2 border-zinc-800 flex justify-between">
|
|
||||||
<h1 className="flex gap-x-1 items-end">
|
|
||||||
<span className="sr-only">FiveThirtyEight</span>
|
|
||||||
<img
|
|
||||||
width="197"
|
|
||||||
height="25"
|
|
||||||
alt="FiveThirtyEight"
|
|
||||||
src="data:image/svg+xml;base64,PHN2ZyBpZD0iTGF5ZXJfMSIgZGF0YS1uYW1lPSJMYXllciAxIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA0MjEgNTMuNzYiPjxkZWZzPjxzdHlsZT4uY2xzLTF7ZmlsbDojMDEwMTAxO308L3N0eWxlPjwvZGVmcz48dGl0bGU+QXJ0Ym9hcmQgOTU8L3RpdGxlPjxwYXRoIGNsYXNzPSJjbHMtMSIgZD0iTTAgMGgyNXY4SDl2MTBoMTV2OEg5djE3SDBWMHpNMzEgMzZoNVYxOGgtNXYtOGgxM3YyNmg0djdIMzF6bTUtMzZoOHY4aC04ek0xNzkgMzZoNVYxOGgtNXYtOGgxM3YyNmg0djdoLTE3em01LTM2aDh2OGgtOHpNMzE2IDM2aDVWMThoLTV2LThoMTN2MjZoNHY3aC0xN3ptNS0zNmg4djhoLTh6TTU0IDI3VjEwaDh2MTVsNCA5Ljk4aDFMNzEgMjVWMTBoOHYxN2wtNyAxNkg2MWwtNy0xNnpNMTExIDQzSDk3LjQyQzg5LjIzIDQzIDg1IDM5LjE5IDg1IDMxLjE3VjIyYzAtNy41NyA0LjMtMTMgMTMtMTMgOS4zMyAwIDEzIDUuMDcgMTMgMTR2N0g5NHYxLjc0YzAgMi42MiAxIDQuMjYgMy40MiA0LjI2SDExMXpNOTQgMjNoOHYtMS41NWMwLTIuNjItMS4wNi01LjQ1LTQuMTMtNS40NS0yLjc5IDAtMy44NyAyLjItMy44NyA1LjQ1ek0xMjUgOGgtMTBWMGgyOXY4aC0xMHYzNWgtOVY4ek0yMDIgNDNWMTBoOHY0YzEuMTQtMi40NSAzLjc1LTQgNy4yMi00SDIyMHY4aC02Yy0yLjg0IDAtNCAuOTQtNCAzLjlWNDN6TTI0NSA0M2gtNC44NEMyMzMuMDUgNDMgMjMwIDM5LjMxIDIzMCAzMS44NVYxOGgtNnYtOGg2VjNoOHY3aDd2OGgtN2wtLjA3IDEzLjkzYzAgMi4yMi45MyA0LjA3IDMuNjYgNC4wN0gyNDV6TTQyMSA0M2gtNC44NEM0MDkuMDUgNDMgNDA2IDM5LjMxIDQwNiAzMS44NVYxOGgtNnYtOGg2VjNoOHY3aDd2OGgtN2wtLjA3IDEzLjkzYzAgMi4yMi45MyA0LjA3IDMuNjYgNC4wN0g0MjF6TTI1NC4yNiA1My43Nmw0LjYxLTkuNUwyNTEgMjdWMTBoOHYxNWw0IDEwaDFsNC0xMFYxMGg4djE3bC0xMi4zIDI2Ljc2aC05LjQ0ek0yODQgMGgyNXY4aC0xNnY5aDE1djhoLTE1djEwaDE2djhoLTI1VjB6TTMzNyA0OHYtMmgxNi4xYzIgMCAyLjktLjE4IDIuOS0xLjI3di0uMzRjMC0xLjA4LS45MS0xLjM5LTIuOS0xLjM5SDM0MHYtNWw1LTVjLTUuMjktMS40OC04LTUuNDMtOC0xMXYtMWMwLTcuNTYgNC40NC0xMiAxNC0xMmEyMS45MyAyMS45MyAwIDAgMSA1Ljk1IDFMMzYxIDRsNSAzLTQgNmMxLjM3IDEuOTMgMyA0LjkzIDMgOHYxYzAgNy0zLjMgMTAuNjYtMTIgMTFsLTMgNGg2YzUuOTIgMCA5IDIuNjIgOSA3LjY4di4xMWMwIDUuMDYtMi43MSA4LjIxLTguNjIgOC4yMWgtMTNjLTQuMjkgMC02LjM4LTEuODQtNi4zOC01em0xOS0yNXYtM2MwLTMuMy0xLjMzLTQtNS00cy01IC43LTUgNHYzYzAgMy4zIDEuMzkgNCA1IDRzNS0uNyA1LTR6TTM4MCA0M2gtOFYwaDh2MTRjMS4xNC0yLjY3IDMuNC00IDctNCA2LjI2IDAgOSAzLjA4IDkgMTAuNzZWNDNoLThWMjJjMC0zLjEzLTEuMDctNS00LTVzLTQgMS44Ny00IDV6TTE1NyA0M2gtOFYwaDh2MTRjMS4xNC0yLjY3IDMuOTEtNCA3LjQ5LTQgNi4yNiAwIDguNTEgMy4xMyA4LjUxIDEwLjgxVjQzaC04VjIxYzAtMy4xMy0xLjA3LTQuNDQtNC00LjQ0cy00IDIuMjYtNCA1LjM5eiIvPjwvc3ZnPg=="
|
|
||||||
/>{' '}
|
|
||||||
<span className="-mb-0.5 text-[#3c3c3c]">replica</span>
|
|
||||||
</h1>
|
|
||||||
<div className="md:flex items-center gap-x-3 text-[#3c3c3c] -mb-1 hidden">
|
|
||||||
<a
|
|
||||||
className="hover:opacity-75 transition"
|
|
||||||
href="https://portaljs.com"
|
|
||||||
>
|
|
||||||
Built with 🌀PortalJS
|
|
||||||
</a>
|
|
||||||
<hr className="h-[80%] border border-[#3c3c3c] opacity-75 my-2"></hr>
|
|
||||||
<a
|
|
||||||
className="hover:opacity-75 transition"
|
|
||||||
href="https://github.com/datopian/portaljs/tree/main/examples/fivethirtyeight"
|
|
||||||
>
|
|
||||||
Github
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="mx-2 py-1.5 text-[14px] text-[#3c3c3c] md:hidden">
|
|
||||||
<ul className="flex gap-x-4">
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
className="hover:opacity-75 transition"
|
|
||||||
href="https://portaljs.com"
|
|
||||||
>
|
|
||||||
PortalJS
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
className="hover:opacity-75 transition"
|
|
||||||
href="https://github.com/datopian/portaljs/tree/main/examples/fivethirtyeight"
|
|
||||||
>
|
|
||||||
View on Github
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
{children}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,38 +0,0 @@
|
|||||||
import { Octokit } from 'octokit';
|
|
||||||
|
|
||||||
export interface GithubProject {
|
|
||||||
owner: string;
|
|
||||||
repo: string;
|
|
||||||
branch: string;
|
|
||||||
files: string[];
|
|
||||||
readme: string;
|
|
||||||
description?: string;
|
|
||||||
name?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getProjectReadme(
|
|
||||||
owner: string,
|
|
||||||
repo: string,
|
|
||||||
branch: string,
|
|
||||||
readme: string,
|
|
||||||
github_pat?: string
|
|
||||||
) {
|
|
||||||
const octokit = new Octokit({ auth: github_pat });
|
|
||||||
try {
|
|
||||||
const response = await octokit.rest.repos.getContent({
|
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
path: readme,
|
|
||||||
ref: branch,
|
|
||||||
});
|
|
||||||
const data = response.data as { content?: string };
|
|
||||||
const fileContent = data.content ? data.content : '';
|
|
||||||
if (fileContent === '') {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const decodedContent = Buffer.from(fileContent, 'base64').toString();
|
|
||||||
return decodedContent;
|
|
||||||
} catch (error) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
13854
examples/fivethirtyeight/package-lock.json
generated
@ -1,44 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "fiverthirtyeight-example",
|
|
||||||
"version": "0.1.0",
|
|
||||||
"private": true,
|
|
||||||
"scripts": {
|
|
||||||
"dev": "next dev",
|
|
||||||
"build": "next build",
|
|
||||||
"start": "next start",
|
|
||||||
"lint": "next lint"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@headlessui/react": "^1.7.14",
|
|
||||||
"@heroicons/react": "^2.0.18",
|
|
||||||
"@portaljs/components": "^0.1.8",
|
|
||||||
"@portaljs/core": "^1.0.5",
|
|
||||||
"@portaljs/remark-wiki-link": "^1.0.4",
|
|
||||||
"@tailwindcss/typography": "^0.5.9",
|
|
||||||
"@types/node": "20.1.1",
|
|
||||||
"@types/react": "18.2.6",
|
|
||||||
"@types/react-dom": "18.2.4",
|
|
||||||
"autoprefixer": "10.4.14",
|
|
||||||
"eslint": "8.40.0",
|
|
||||||
"eslint-config-next": "13.4.1",
|
|
||||||
"flexsearch": "^0.7.31",
|
|
||||||
"next": "13.4.1",
|
|
||||||
"next-mdx-remote": "^4.4.1",
|
|
||||||
"next-seo": "^6.0.0",
|
|
||||||
"octokit": "^2.0.14",
|
|
||||||
"postcss": "8.4.23",
|
|
||||||
"react": "18.2.0",
|
|
||||||
"react-dom": "18.2.0",
|
|
||||||
"react-markdown": "^8.0.7",
|
|
||||||
"remark": "^14.0.3",
|
|
||||||
"remark-code-frontmatter": "^1.0.0",
|
|
||||||
"remark-excerpt": "^1.0.0-beta.1",
|
|
||||||
"remark-extract-frontmatter": "^3.2.0",
|
|
||||||
"remark-frontmatter": "^4.0.1",
|
|
||||||
"remark-gfm": "^3.0.1",
|
|
||||||
"tailwindcss": "3.3.2",
|
|
||||||
"timeago.js": "^4.0.2",
|
|
||||||
"to-vfile": "^7.2.4",
|
|
||||||
"typescript": "5.0.4"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,49 +0,0 @@
|
|||||||
import '@/styles/globals.css';
|
|
||||||
import '@portaljs/components/styles.css';
|
|
||||||
import { useEffect } from 'react';
|
|
||||||
import { pageview } from '@portaljs/core';
|
|
||||||
import Script from 'next/script';
|
|
||||||
import Head from 'next/head';
|
|
||||||
import { useRouter } from 'next/router';
|
|
||||||
|
|
||||||
import type { AppProps } from 'next/app';
|
|
||||||
|
|
||||||
export default function App({ Component, pageProps }: AppProps) {
|
|
||||||
const router = useRouter();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const handleRouteChange = (url: any) => {
|
|
||||||
pageview(url);
|
|
||||||
};
|
|
||||||
router.events.on('routeChangeComplete', handleRouteChange);
|
|
||||||
return () => {
|
|
||||||
router.events.off('routeChangeComplete', handleRouteChange);
|
|
||||||
};
|
|
||||||
}, [router.events]);
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Head>
|
|
||||||
<link rel="shortcut icon" href="/squared_logo.png" />
|
|
||||||
</Head>
|
|
||||||
<Script
|
|
||||||
strategy="afterInteractive"
|
|
||||||
src="https://www.googletagmanager.com/gtag/js?id=G-3N9SXTC7GS"
|
|
||||||
/>
|
|
||||||
<Script
|
|
||||||
id="gtag-init"
|
|
||||||
strategy="afterInteractive"
|
|
||||||
dangerouslySetInnerHTML={{
|
|
||||||
__html: `
|
|
||||||
window.dataLayer = window.dataLayer || [];
|
|
||||||
function gtag(){dataLayer.push(arguments);}
|
|
||||||
gtag('js', new Date());
|
|
||||||
gtag('config', 'G-3N9SXTC7GS', {
|
|
||||||
page_path: window.location.pathname,
|
|
||||||
});
|
|
||||||
`,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Component {...pageProps} />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,27 +0,0 @@
|
|||||||
import { Html, Head, Main, NextScript } from 'next/document';
|
|
||||||
|
|
||||||
export default function Document() {
|
|
||||||
return (
|
|
||||||
<Html lang="en">
|
|
||||||
<Head>
|
|
||||||
<link
|
|
||||||
rel="icon"
|
|
||||||
type="image/x-icon"
|
|
||||||
href="https://projects.fivethirtyeight.com/shared/favicon.ico"
|
|
||||||
/>
|
|
||||||
<meta
|
|
||||||
property="og:image"
|
|
||||||
content="https://portaljs-fivethirtyeight.vercel.app/share_image.png"
|
|
||||||
/>
|
|
||||||
<meta
|
|
||||||
property="twitter:image"
|
|
||||||
content="https://portaljs-fivethirtyeight.vercel.app/share_image.png"
|
|
||||||
/>
|
|
||||||
</Head>
|
|
||||||
<body>
|
|
||||||
<Main />
|
|
||||||
</body>
|
|
||||||
<NextScript />
|
|
||||||
</Html>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,224 +0,0 @@
|
|||||||
import { NextSeo } from 'next-seo';
|
|
||||||
import { promises as fs } from 'fs';
|
|
||||||
import path from 'path';
|
|
||||||
import getConfig from 'next/config';
|
|
||||||
import { getProjectReadme, GithubProject } from '@/lib/octokit';
|
|
||||||
import remarkGfm from 'remark-gfm';
|
|
||||||
import extract from 'remark-extract-frontmatter';
|
|
||||||
import { Dataset } from '..';
|
|
||||||
import { GetStaticProps } from 'next';
|
|
||||||
import { FlatUiTable } from '@portaljs/components';
|
|
||||||
import Breadcrumbs from '@/components/Breadcrumbs';
|
|
||||||
import { ReactMarkdown } from 'react-markdown/lib/react-markdown';
|
|
||||||
import remarkFrontmatter from 'remark-frontmatter';
|
|
||||||
import Layout from '@/components/Layout';
|
|
||||||
import { format } from 'timeago.js';
|
|
||||||
|
|
||||||
// Request a weekday along with a long date
|
|
||||||
const options = {
|
|
||||||
year: 'numeric',
|
|
||||||
month: 'long',
|
|
||||||
day: 'numeric',
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
export default function DatasetPage({
|
|
||||||
dataset,
|
|
||||||
}: {
|
|
||||||
dataset: Dataset & {
|
|
||||||
readme: string | null;
|
|
||||||
};
|
|
||||||
}) {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<NextSeo title={`${dataset.name} page`} />
|
|
||||||
<Layout>
|
|
||||||
<main className="max-w-5xl px-2 prose mx-auto my-8 pb-8 prose-thead:border-b-4 prose-table:max-w-5xl prose-table:overflow-scroll prose-thead:overflow-scroll prose-tbody:overflow-scroll prose-thead:pb-2 prose-thead:border-zinc-900 prose-th:uppercase prose-th:text-left prose-th:font-light prose-th:text-xs prose-a:no-underline">
|
|
||||||
<Breadcrumbs links={[{ title: dataset.name, href: '' }]} />
|
|
||||||
<h1 className="uppercase mb-0 mt-16">{dataset.name}</h1>
|
|
||||||
<table className="w-full my-10 mb-8 hidden md:table">
|
|
||||||
<thead className="border-b-4 pb-2 border-zinc-900">
|
|
||||||
<tr>
|
|
||||||
<th className="uppercase text-left font-normal text-xs pb-3">
|
|
||||||
related content
|
|
||||||
</th>
|
|
||||||
<th className="uppercase text-left font-normal text-xs pb-3">
|
|
||||||
last updated
|
|
||||||
</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<DesktopItem key={dataset.name} dataset={dataset} />
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
{dataset.readme && (
|
|
||||||
<>
|
|
||||||
{dataset.readme && (
|
|
||||||
<ReactMarkdown
|
|
||||||
remarkPlugins={[
|
|
||||||
remarkFrontmatter,
|
|
||||||
remarkGfm,
|
|
||||||
[extract, { remove: true }],
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
{dataset.readme}
|
|
||||||
</ReactMarkdown>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<h2 className="mb-0 mt-10">Files</h2>
|
|
||||||
<div className="inline-block min-w-full py-2 align-middle">
|
|
||||||
<table className="min-w-full divide-y divide-gray-300">
|
|
||||||
<thead className="border-b-4 pb-2 border-zinc-900">
|
|
||||||
<tr>
|
|
||||||
<th
|
|
||||||
className="uppercase text-left font-light text-xs pb-3"
|
|
||||||
scope="col"
|
|
||||||
>
|
|
||||||
Name
|
|
||||||
</th>
|
|
||||||
<th
|
|
||||||
className="uppercase text-left font-light text-xs pb-3"
|
|
||||||
scope="col"
|
|
||||||
>
|
|
||||||
Download
|
|
||||||
</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody className="divide-y divide-gray-200">
|
|
||||||
{dataset.files?.map((file) => (
|
|
||||||
<tr key={file}>
|
|
||||||
<td className="whitespace-nowrap text-left py-4 text-sm text-gray-500">
|
|
||||||
<a href={`#${file.split('/').slice(-1)}`}>
|
|
||||||
{file.split('/').slice(-1)}
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
<td className="whitespace-nowrap py-4 text-sm text-gray-500">
|
|
||||||
<a href={file}>
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
fill="currentColor"
|
|
||||||
className="w-8 h-8 text-blue-400 hover:text-blue-300 transition mt-1 ml-3"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
fillRule="evenodd"
|
|
||||||
d="M12 2.25c-5.385 0-9.75 4.365-9.75 9.75s4.365 9.75 9.75 9.75 9.75-4.365 9.75-9.75S17.385 2.25 12 2.25zm-.53 14.03a.75.75 0 001.06 0l3-3a.75.75 0 10-1.06-1.06l-1.72 1.72V8.25a.75.75 0 00-1.5 0v5.69l-1.72-1.72a.75.75 0 00-1.06 1.06l3 3z"
|
|
||||||
clipRule="evenodd"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
))}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
{dataset.files && dataset.files.length > 0 && (
|
|
||||||
<>
|
|
||||||
<h2 className="mb-0 mt-8">Data Previews</h2>
|
|
||||||
{dataset.files?.map((file) => (
|
|
||||||
<div
|
|
||||||
key={file}
|
|
||||||
id={file.split('/').slice(-1).join('')}
|
|
||||||
className="preview-table my-8"
|
|
||||||
>
|
|
||||||
<h3>{file.split('/').slice(-1)}</h3>
|
|
||||||
<FlatUiTable url={file} />
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</main>
|
|
||||||
</Layout>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function DesktopItem({ dataset }: { dataset: Dataset }) {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{dataset.articles.map((article, index) => (
|
|
||||||
<tr
|
|
||||||
key={article.url}
|
|
||||||
className={`${
|
|
||||||
index === dataset.articles.length - 1 ? 'border-b' : ''
|
|
||||||
} border-zinc-400`}
|
|
||||||
>
|
|
||||||
<td>
|
|
||||||
<a
|
|
||||||
className="py-8 font-bold hover:underline pr-2"
|
|
||||||
href={article.url}
|
|
||||||
>
|
|
||||||
{article.title}
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
<td className="py-8 font-light text-[14px] min-w-[138px] font-mono text-[#999]">
|
|
||||||
{format(article.date).includes('years')
|
|
||||||
? new Date(article.date).toLocaleString('en-US', options)
|
|
||||||
: format(article.date)}
|
|
||||||
</td>
|
|
||||||
<td className="py-8 text-end">
|
|
||||||
{index === 0 && (
|
|
||||||
<a
|
|
||||||
className="ml-auto border border-zinc-900 font-light px-[25px] py-2.5 text-sm transition hover:bg-zinc-900 hover:text-white"
|
|
||||||
href={dataset.url}
|
|
||||||
>
|
|
||||||
info
|
|
||||||
</a>
|
|
||||||
)}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
))}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getStaticPaths() {
|
|
||||||
const datasetsFile = path.join(process.cwd(), 'datasets.json');
|
|
||||||
const datasets = await fs.readFile(datasetsFile, 'utf8');
|
|
||||||
|
|
||||||
return {
|
|
||||||
paths: JSON.parse(datasets).map((dataset: Dataset) => {
|
|
||||||
return {
|
|
||||||
params: { datasetName: dataset.name },
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
fallback: false, // can also be true or 'blocking'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
// change href base check datahub-next
|
|
||||||
|
|
||||||
export const getStaticProps: GetStaticProps = async ({ params }) => {
|
|
||||||
const datasetsFile = path.join(process.cwd(), 'datasets.json');
|
|
||||||
const datasetsString = await fs.readFile(datasetsFile, 'utf8');
|
|
||||||
const datasets: Dataset[] = JSON.parse(datasetsString);
|
|
||||||
const dataset: Dataset | undefined = datasets.find(
|
|
||||||
(_dataset) => _dataset.name === params?.datasetName
|
|
||||||
);
|
|
||||||
const github_pat = getConfig().serverRuntimeConfig.github_pat;
|
|
||||||
const readmes = await Promise.all(
|
|
||||||
['/README.md', '/readme.md', '/Readme.md'].map(
|
|
||||||
async (readme) =>
|
|
||||||
await getProjectReadme(
|
|
||||||
'fivethirtyeight',
|
|
||||||
'data',
|
|
||||||
'master',
|
|
||||||
dataset?.name + readme,
|
|
||||||
github_pat
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
const readme = readmes.find((item) => item !== null);
|
|
||||||
if (!readme) console.log('Readme not found for ' + dataset?.name);
|
|
||||||
return {
|
|
||||||
props: {
|
|
||||||
dataset: {
|
|
||||||
...dataset,
|
|
||||||
readme,
|
|
||||||
files: dataset && dataset.files ? dataset.files : null,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@ -1,198 +0,0 @@
|
|||||||
import Image from 'next/image';
|
|
||||||
import { Inter } from 'next/font/google';
|
|
||||||
import { format } from 'timeago.js';
|
|
||||||
import { promises as fs } from 'fs';
|
|
||||||
import path from 'path';
|
|
||||||
import { NextSeo } from 'next-seo';
|
|
||||||
import Layout from '@/components/Layout';
|
|
||||||
|
|
||||||
const inter = Inter({ subsets: ['latin'] });
|
|
||||||
|
|
||||||
export interface Article {
|
|
||||||
date: string;
|
|
||||||
title: string;
|
|
||||||
url: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Dataset {
|
|
||||||
url: string;
|
|
||||||
name: string;
|
|
||||||
displayName: string;
|
|
||||||
articles: Article[];
|
|
||||||
files?: string[];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Request a weekday along with a long date
|
|
||||||
const options = {
|
|
||||||
year: 'numeric',
|
|
||||||
month: 'long',
|
|
||||||
day: 'numeric',
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
export function MobileItem({ dataset }: { dataset: Dataset }) {
|
|
||||||
return (
|
|
||||||
<div className="flex gap-x-2 pb-2 py-4 items-center justify-between border-b border-zinc-600">
|
|
||||||
<div className="flex flex-col">
|
|
||||||
<span className="font-mono font-light">{dataset.name}</span>
|
|
||||||
{dataset.articles.map((article) => (
|
|
||||||
<div key={article.title} className="py-1 flex flex-col">
|
|
||||||
<span className="font-bold hover:underline">{article.title}</span>
|
|
||||||
<span className="font-light text-base">
|
|
||||||
{format(article.date).includes('years')
|
|
||||||
? new Date(article.date).toLocaleString('en-US', options)
|
|
||||||
: format(article.date)}
|
|
||||||
</span>{' '}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-col justify-start">
|
|
||||||
<a
|
|
||||||
className="ml-2 border border-zinc-900 font-light px-4 py-1 text-sm transition hover:bg-zinc-900 hover:text-white"
|
|
||||||
href={dataset.url}
|
|
||||||
>
|
|
||||||
info
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
className="ml-2 border border-[#3c3c3c] px-[25px] py-2.5 text-sm transition bg-[#3c3c3c] text-white hover:bg-zinc-900"
|
|
||||||
href={`/datasets/${dataset.name}`}
|
|
||||||
>
|
|
||||||
explore
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function DesktopItem({ dataset }: { dataset: Dataset }) {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{dataset.articles.map((article, index) => (
|
|
||||||
<tr
|
|
||||||
key={article.url}
|
|
||||||
className={`${
|
|
||||||
index === dataset.articles.length - 1 ? 'border-b' : ''
|
|
||||||
} border-zinc-400`}
|
|
||||||
>
|
|
||||||
<td className="py-8 font-light font-mono text-[13px] text-zinc-700">
|
|
||||||
{index === 0 ? dataset.name : ''}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<a
|
|
||||||
className="py-8 font-bold hover:underline pr-2"
|
|
||||||
href={article.url}
|
|
||||||
>
|
|
||||||
{article.title}
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
<td className="py-8 font-light text-[14px] min-w-[138px] font-mono text-[#999]">
|
|
||||||
{format(article.date).includes('years')
|
|
||||||
? new Date(article.date).toLocaleString('en-US', options)
|
|
||||||
: format(article.date)}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{index === 0 && (
|
|
||||||
<a
|
|
||||||
className="ml-2 border border-[#3c3c3c] px-[25px] py-2.5 text-sm transition bg-[#3c3c3c] text-white hover:bg-zinc-900"
|
|
||||||
href={`/datasets/${dataset.name}`}
|
|
||||||
>
|
|
||||||
explore
|
|
||||||
</a>
|
|
||||||
)}
|
|
||||||
</td>
|
|
||||||
<td className="py-8">
|
|
||||||
{index === 0 && (
|
|
||||||
<a
|
|
||||||
className="ml-2 border border-zinc-900 font-light px-[25px] py-2.5 text-sm transition hover:bg-zinc-900 hover:text-white"
|
|
||||||
href={dataset.url}
|
|
||||||
>
|
|
||||||
info
|
|
||||||
</a>
|
|
||||||
)}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
))}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getStaticProps() {
|
|
||||||
const jsonDirectory = path.join(process.cwd(), '/datasets.json');
|
|
||||||
const datasetString = await fs.readFile(jsonDirectory, 'utf8');
|
|
||||||
const datasets = JSON.parse(datasetString);
|
|
||||||
return {
|
|
||||||
props: { datasets },
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function Home({ datasets }: { datasets: Dataset[] }) {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<NextSeo title="FiveThirtyEight tribute by PortalJS" />
|
|
||||||
<Layout>
|
|
||||||
<main
|
|
||||||
className={`flex min-h-screen flex-col items-center max-w-5xl mx-auto pt-20 px-2.5 ${inter.className}`}
|
|
||||||
>
|
|
||||||
<div>
|
|
||||||
<h1 className="text-[40px] font-bold text-zinc-800 text-center">
|
|
||||||
Our Data
|
|
||||||
</h1>
|
|
||||||
<p className="max-w-[600px] text-[17px] text-center text-[#6d6f71]">
|
|
||||||
We’re sharing the data and code behind some of our articles and
|
|
||||||
graphics. We hope you’ll use it to check our work and to create
|
|
||||||
stories and visualizations of your own.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<article className="w-full px-2 md:hidden py-4">
|
|
||||||
{datasets.map((dataset) => (
|
|
||||||
<MobileItem key={dataset.name} dataset={dataset} />
|
|
||||||
))}
|
|
||||||
</article>
|
|
||||||
<table className="w-full mt-10 mb-4 hidden md:table">
|
|
||||||
<thead className="border-b-4 pb-2 border-zinc-900">
|
|
||||||
<tr>
|
|
||||||
<th className="uppercase text-left font-normal text-xs pb-3">
|
|
||||||
data set
|
|
||||||
</th>
|
|
||||||
<th className="uppercase text-left font-normal text-xs pb-3">
|
|
||||||
related content
|
|
||||||
</th>
|
|
||||||
<th className="uppercase text-left font-normal text-xs pb-3">
|
|
||||||
last updated
|
|
||||||
</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{datasets.map((dataset) => (
|
|
||||||
<DesktopItem key={dataset.name} dataset={dataset} />
|
|
||||||
))}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
<p className="text-[13px] py-8">
|
|
||||||
Unless otherwise noted, our data sets are available under the{' '}
|
|
||||||
<a
|
|
||||||
className="text-blue-400 hover:underline"
|
|
||||||
href="http://creativecommons.org/licenses/by/4.0/"
|
|
||||||
>
|
|
||||||
Creative Commons Attribution 4.0 International license
|
|
||||||
</a>
|
|
||||||
, and the code is available under the{' '}
|
|
||||||
<a
|
|
||||||
className="text-blue-400 hover:underline"
|
|
||||||
href="http://opensource.org/licenses/MIT"
|
|
||||||
>
|
|
||||||
MIT license
|
|
||||||
</a>
|
|
||||||
. If you find this information useful, please{' '}
|
|
||||||
<a
|
|
||||||
className="text-blue-400 hover:underline"
|
|
||||||
href="mailto:data@fivethirtyeight.com"
|
|
||||||
>
|
|
||||||
let us know
|
|
||||||
</a>
|
|
||||||
.
|
|
||||||
</p>
|
|
||||||
</main>
|
|
||||||
</Layout>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 25 KiB |
@ -1 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 81 KiB |
@ -1 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 283 64"><path fill="black" d="M141 16c-11 0-19 7-19 18s9 18 20 18c7 0 13-3 16-7l-7-5c-2 3-6 4-9 4-5 0-9-3-10-7h28v-3c0-11-8-18-19-18zm-9 15c1-4 4-7 9-7s8 3 9 7h-18zm117-15c-11 0-19 7-19 18s9 18 20 18c6 0 12-3 16-7l-8-5c-2 3-5 4-8 4-5 0-9-3-11-7h28l1-3c0-11-8-18-19-18zm-10 15c2-4 5-7 10-7s8 3 9 7h-19zm-39 3c0 6 4 10 10 10 4 0 7-2 9-5l8 5c-3 5-9 8-17 8-11 0-19-7-19-18s8-18 19-18c8 0 14 3 17 8l-8 5c-2-3-5-5-9-5-6 0-10 4-10 10zm83-29v46h-9V5h9zM37 0l37 64H0L37 0zm92 5-27 48L74 5h10l18 30 17-30h10zm59 12v10l-3-1c-6 0-10 4-10 10v15h-9V17h9v9c0-5 6-9 13-9z"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 629 B |
@ -1,11 +0,0 @@
|
|||||||
@tailwind base;
|
|
||||||
@tailwind components;
|
|
||||||
@tailwind utilities;
|
|
||||||
|
|
||||||
.preview-table > div {
|
|
||||||
overflow-y: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.prose h1 {
|
|
||||||
font-size: 1.5em !important;
|
|
||||||
}
|
|
||||||
@ -1,18 +0,0 @@
|
|||||||
/** @type {import('tailwindcss').Config} */
|
|
||||||
module.exports = {
|
|
||||||
content: [
|
|
||||||
'./pages/**/*.{js,ts,jsx,tsx,mdx}',
|
|
||||||
'./components/**/*.{js,ts,jsx,tsx,mdx}',
|
|
||||||
'./app/**/*.{js,ts,jsx,tsx,mdx}',
|
|
||||||
],
|
|
||||||
theme: {
|
|
||||||
extend: {
|
|
||||||
backgroundImage: {
|
|
||||||
'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))',
|
|
||||||
'gradient-conic':
|
|
||||||
'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
plugins: [require('@tailwindcss/typography')],
|
|
||||||
};
|
|
||||||
@ -1,23 +0,0 @@
|
|||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"target": "es5",
|
|
||||||
"lib": ["dom", "dom.iterable", "esnext"],
|
|
||||||
"allowJs": true,
|
|
||||||
"skipLibCheck": true,
|
|
||||||
"strict": true,
|
|
||||||
"forceConsistentCasingInFileNames": true,
|
|
||||||
"noEmit": true,
|
|
||||||
"esModuleInterop": true,
|
|
||||||
"module": "esnext",
|
|
||||||
"moduleResolution": "node",
|
|
||||||
"resolveJsonModule": true,
|
|
||||||
"isolatedModules": true,
|
|
||||||
"jsx": "preserve",
|
|
||||||
"incremental": true,
|
|
||||||
"paths": {
|
|
||||||
"@/*": ["./*"]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
|
|
||||||
"exclude": ["node_modules"]
|
|
||||||
}
|
|
||||||
@ -1,29 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": ["next", "next/core-web-vitals"],
|
|
||||||
"ignorePatterns": ["!**/*", ".next/**/*"],
|
|
||||||
"overrides": [
|
|
||||||
{
|
|
||||||
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
|
|
||||||
"rules": {
|
|
||||||
"@next/next/no-html-link-for-pages": [
|
|
||||||
"error",
|
|
||||||
"examples/simple-example/pages"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"files": ["*.ts", "*.tsx"],
|
|
||||||
"rules": {}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"files": ["*.js", "*.jsx"],
|
|
||||||
"rules": {}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"rules": {
|
|
||||||
"@next/next/no-html-link-for-pages": "off"
|
|
||||||
},
|
|
||||||
"env": {
|
|
||||||
"jest": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,7 +0,0 @@
|
|||||||
node_modules
|
|
||||||
**/.next/**
|
|
||||||
**/_next/**
|
|
||||||
**/dist/**
|
|
||||||
**/__tmp__/**
|
|
||||||
lerna.json
|
|
||||||
.github
|
|
||||||