Compare commits
20 Commits
openspendi
...
drd-storie
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7fc199c438 | ||
|
|
f0042b7730 | ||
|
|
8e4428e2f8 | ||
|
|
1292350aac | ||
|
|
e4da3ed672 | ||
|
|
959fe5a588 | ||
|
|
7a46a6732b | ||
|
|
688db3e6a6 | ||
|
|
fa642d8914 | ||
|
|
c80b283201 | ||
|
|
95fd8e72df | ||
|
|
e50c76090c | ||
|
|
70012d7c03 | ||
|
|
024c06d9cd | ||
|
|
35668c069e | ||
|
|
2500779499 | ||
|
|
fc8eb95dbc | ||
|
|
45089419da | ||
|
|
837a2d3d7a | ||
|
|
37e36539ec |
8
.changeset/README.md
Normal file
8
.changeset/README.md
Normal file
@@ -0,0 +1,8 @@
|
||||
# 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)
|
||||
14
.changeset/config.json
Normal file
14
.changeset/config.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"$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
Normal file
39
.github/workflows/release.yml
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
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.'
|
||||
@@ -13,7 +13,7 @@ Structure:
|
||||
- **dataset-frictionless**: Example utilizing a frictionless dataset as an example
|
||||
- **site**: the website for the project, with a landing page and the docs
|
||||
- **packages**:
|
||||
- **portaljs-components**: the library of components for creating a data portal
|
||||
- **portaljs-components**: the library of components for creating a data portal
|
||||
|
||||
## How to contribute
|
||||
|
||||
@@ -26,10 +26,11 @@ If you'd like to work on one of the issues you can:
|
||||
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).
|
||||
5. Commit your changes to the feature branch.
|
||||
6. Push the feature branch to your forked repository.
|
||||
7. Create a Pull Request against the original repository.
|
||||
6. Add changeset file describing the changes. (See section below)
|
||||
7. Push the feature branch to your forked repository.
|
||||
8. Create a Pull Request against the original repository.
|
||||
- add a short description of the changes included in the PR
|
||||
8. Address review comments if requested by our demanding reviewers 😜.
|
||||
9. 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.
|
||||
|
||||
@@ -62,6 +63,7 @@ or you can use just:
|
||||
nx <target> <project>
|
||||
# e.g. npx nx serve ckan
|
||||
```
|
||||
|
||||
if you have the `nx` binary installed globally in your machine
|
||||
|
||||
#### Running multiple tasks
|
||||
@@ -109,7 +111,7 @@ To check code formatting in selected projects:
|
||||
|
||||
```sh
|
||||
npx nx format:check --projects=<array of projects>
|
||||
# npx nx format:check --projects=ckan,dataset-frictionless
|
||||
# npx nx format:check --projects=ckan,dataset-frictionless
|
||||
```
|
||||
|
||||
To check code formatting in all projects:
|
||||
@@ -124,7 +126,7 @@ To fix code formatting in selected projects:
|
||||
|
||||
```sh
|
||||
npx nx format:write --projects=<array projects>
|
||||
# npx nx format:write --projects=ckan,dataset-frictionless
|
||||
# npx nx format:write --projects=ckan,dataset-frictionless
|
||||
```
|
||||
|
||||
To fix formatting in all projects:
|
||||
@@ -174,3 +176,23 @@ 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).
|
||||
|
||||
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/).
|
||||
|
||||
99
examples/fivethirtyeight/components/Layout.tsx
Normal file
99
examples/fivethirtyeight/components/Layout.tsx
Normal file
@@ -0,0 +1,99 @@
|
||||
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.org"
|
||||
>
|
||||
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.org"
|
||||
>
|
||||
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,33 +1,4 @@
|
||||
[
|
||||
{
|
||||
"url": "https://github.com/fivethirtyeight/data/tree/master/polls",
|
||||
"name": "polls",
|
||||
"displayName": "<span class=\"lastword\">polls</span>",
|
||||
"articles": [
|
||||
{
|
||||
"date": "2023-05-11T14:35:40.000Z",
|
||||
"title": "Latest Polls",
|
||||
"url": "https://projects.fivethirtyeight.com/polls/"
|
||||
}
|
||||
],
|
||||
"files": [
|
||||
"https://projects.fivethirtyeight.com/polls-page/data/president_primary_polls.csv",
|
||||
"https://projects.fivethirtyeight.com/polls-page/data/president_primary_polls_historical.csv",
|
||||
"https://projects.fivethirtyeight.com/polls-page/data/president_polls.csv",
|
||||
"https://projects.fivethirtyeight.com/polls-page/data/president_polls_historical.csv",
|
||||
"https://projects.fivethirtyeight.com/polls-page/data/senate_polls.csv",
|
||||
"https://projects.fivethirtyeight.com/polls-page/data/senate_polls_historical.csv",
|
||||
"https://projects.fivethirtyeight.com/polls-page/data/house_polls.csv",
|
||||
"https://projects.fivethirtyeight.com/polls-page/data/house_polls_historical.csv",
|
||||
"https://projects.fivethirtyeight.com/polls-page/data/governor_polls.csv",
|
||||
"https://projects.fivethirtyeight.com/polls-page/data/governor_polls_historical.csv",
|
||||
"https://projects.fivethirtyeight.com/polls-page/data/president_approval_polls.csv",
|
||||
"https://projects.fivethirtyeight.com/polls-page/data/generic_ballot_polls.csv",
|
||||
"https://projects.fivethirtyeight.com/polls-page/data/generic_ballot_polls_historical.csv",
|
||||
"https://projects.fivethirtyeight.com/2020-primary-data/pres_primary_avgs_2020.csv",
|
||||
"https://projects.fivethirtyeight.com/2020-general-data/presidential_poll_averages_2020.csv"
|
||||
]
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fivethirtyeight/data/tree/master/congress-generic-ballot",
|
||||
"name": "congress-generic-ballot",
|
||||
@@ -195,6 +166,35 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fivethirtyeight/data/tree/master/polls",
|
||||
"name": "polls",
|
||||
"displayName": "<span class=\"lastword\">polls</span>",
|
||||
"articles": [
|
||||
{
|
||||
"date": "2023-05-11T14:35:40.000Z",
|
||||
"title": "Latest Polls",
|
||||
"url": "https://projects.fivethirtyeight.com/polls/"
|
||||
}
|
||||
],
|
||||
"files": [
|
||||
"https://projects.fivethirtyeight.com/polls-page/data/president_primary_polls.csv",
|
||||
"https://projects.fivethirtyeight.com/polls-page/data/president_primary_polls_historical.csv",
|
||||
"https://projects.fivethirtyeight.com/polls-page/data/president_polls.csv",
|
||||
"https://projects.fivethirtyeight.com/polls-page/data/president_polls_historical.csv",
|
||||
"https://projects.fivethirtyeight.com/polls-page/data/senate_polls.csv",
|
||||
"https://projects.fivethirtyeight.com/polls-page/data/senate_polls_historical.csv",
|
||||
"https://projects.fivethirtyeight.com/polls-page/data/house_polls.csv",
|
||||
"https://projects.fivethirtyeight.com/polls-page/data/house_polls_historical.csv",
|
||||
"https://projects.fivethirtyeight.com/polls-page/data/governor_polls.csv",
|
||||
"https://projects.fivethirtyeight.com/polls-page/data/governor_polls_historical.csv",
|
||||
"https://projects.fivethirtyeight.com/polls-page/data/president_approval_polls.csv",
|
||||
"https://projects.fivethirtyeight.com/polls-page/data/generic_ballot_polls.csv",
|
||||
"https://projects.fivethirtyeight.com/polls-page/data/generic_ballot_polls_historical.csv",
|
||||
"https://projects.fivethirtyeight.com/2020-primary-data/pres_primary_avgs_2020.csv",
|
||||
"https://projects.fivethirtyeight.com/2020-general-data/presidential_poll_averages_2020.csv"
|
||||
]
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fivethirtyeight/data/tree/master/nfl-elo",
|
||||
"name": "nfl-elo",
|
||||
@@ -1169,18 +1169,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fivethirtyeight/data/tree/master/undefeated-boxers",
|
||||
"name": "undefeated-boxers",
|
||||
"displayName": "undefeated-<span class=\"lastword\">boxers</span>",
|
||||
"articles": [
|
||||
{
|
||||
"date": "2017-08-18T18:47:32.000Z",
|
||||
"title": "Mayweather Is Defined By The Zero Next To His Name",
|
||||
"url": "https://fivethirtyeight.com/features/mayweather-is-defined-by-the-zero-next-to-his-name/"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fivethirtyeight/data/tree/master/chess-transfers",
|
||||
"name": "chess-transfers",
|
||||
@@ -2139,6 +2127,18 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fivethirtyeight/data/tree/master/undefeated-boxers",
|
||||
"name": "undefeated-boxers",
|
||||
"displayName": "undefeated-<span class=\"lastword\">boxers</span>",
|
||||
"articles": [
|
||||
{
|
||||
"date": "2017-08-18T18:47:32.000Z",
|
||||
"title": "Mayweather Is Defined By The Zero Next To His Name",
|
||||
"url": "https://fivethirtyeight.com/features/mayweather-is-defined-by-the-zero-next-to-his-name/"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fivethirtyeight/data/tree/master/march-madness-predictions",
|
||||
"name": "march-madness-predictions",
|
||||
|
||||
6271
examples/fivethirtyeight/package-lock.json
generated
6271
examples/fivethirtyeight/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -9,7 +9,10 @@
|
||||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@portaljs/components": "^0.1.0",
|
||||
"@flowershow/core": "^0.4.13",
|
||||
"@headlessui/react": "^1.7.14",
|
||||
"@heroicons/react": "^2.0.18",
|
||||
"@portaljs/components": "^0.1.7",
|
||||
"@tailwindcss/typography": "^0.5.9",
|
||||
"@types/node": "20.1.1",
|
||||
"@types/react": "18.2.6",
|
||||
@@ -26,12 +29,15 @@
|
||||
"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,8 +1,49 @@
|
||||
import '@/styles/globals.css'
|
||||
import '@portaljs/components/styles.css'
|
||||
import '@/styles/globals.css';
|
||||
import '@portaljs/components/styles.css';
|
||||
import { useEffect } from 'react';
|
||||
import { pageview } from '@flowershow/core';
|
||||
import Script from 'next/script';
|
||||
import Head from 'next/head';
|
||||
import { useRouter } from 'next/router';
|
||||
|
||||
import type { AppProps } from 'next/app'
|
||||
import type { AppProps } from 'next/app';
|
||||
|
||||
export default function App({ Component, pageProps }: AppProps) {
|
||||
return <Component {...pageProps} />
|
||||
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} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -19,78 +19,9 @@ export default function Document() {
|
||||
/>
|
||||
</Head>
|
||||
<body>
|
||||
<div className="px-2 max-w-5xl mx-auto pb-2">
|
||||
<div className="mt-2 px-2 bg-[#3c3c3c] text-white">
|
||||
<div className="p-2 text-center">
|
||||
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"
|
||||
>
|
||||
Read more here
|
||||
</a>{' '}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<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.org"
|
||||
>
|
||||
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.org"
|
||||
>
|
||||
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>
|
||||
<Main />
|
||||
<NextScript />
|
||||
</body>
|
||||
<NextScript />
|
||||
</Html>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -7,10 +7,19 @@ import remarkGfm from 'remark-gfm';
|
||||
import extract from 'remark-extract-frontmatter';
|
||||
import { Dataset } from '..';
|
||||
import { GetStaticProps } from 'next';
|
||||
import { Table } from '@portaljs/components';
|
||||
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,
|
||||
@@ -22,68 +31,146 @@ export default function DatasetPage({
|
||||
return (
|
||||
<>
|
||||
<NextSeo title={`${dataset.name} page`} />
|
||||
<main className="max-w-5xl px-2 prose mx-auto my-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">
|
||||
<Breadcrumbs links={[{ title: dataset.name, href: '' }]} />
|
||||
<h1 className="uppercase mb-0 mt-16">{dataset.name}</h1>
|
||||
<p className="mb-8">
|
||||
<span className="font-semibold">Repository:</span>{' '}
|
||||
<a target="_blank" href={dataset.url}>
|
||||
{dataset.url}
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<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">
|
||||
<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-light text-xs pb-3"
|
||||
scope="col"
|
||||
>
|
||||
Name
|
||||
<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 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}>{file.split('/').slice(-1)}</a>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
<tbody>
|
||||
<DesktopItem key={dataset.name} dataset={dataset} />
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{dataset.files && dataset.files.length > 0 && (
|
||||
<>
|
||||
<h2 className="mb-0 mt-10">DATA PREVIEWS</h2>
|
||||
{dataset.files?.map((file) => (
|
||||
<div key={file} className="preview-table my-8">
|
||||
<h3>{file.split('/').slice(-1)}</h3>
|
||||
<Table url={file} />
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
{dataset.readme && (
|
||||
<>
|
||||
<h2 className="uppercase font-black">Readme</h2>
|
||||
{dataset.readme && (
|
||||
<ReactMarkdown
|
||||
remarkPlugins={[
|
||||
remarkFrontmatter,
|
||||
remarkGfm,
|
||||
[extract, { remove: true }],
|
||||
]}
|
||||
|
||||
{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}
|
||||
>
|
||||
{dataset.readme}
|
||||
</ReactMarkdown>
|
||||
info
|
||||
</a>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</main>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -101,6 +188,7 @@ export async function getStaticPaths() {
|
||||
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');
|
||||
@@ -110,15 +198,20 @@ export const getStaticProps: GetStaticProps = async ({ params }) => {
|
||||
(_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)
|
||||
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: {
|
||||
|
||||
@@ -4,6 +4,7 @@ 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'] });
|
||||
|
||||
@@ -51,21 +52,12 @@ export function MobileItem({ dataset }: { dataset: Dataset }) {
|
||||
>
|
||||
info
|
||||
</a>
|
||||
{/*
|
||||
<button>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
className="w-12 h-12 text-blue-400 hover:text-blue-300 transition mt-1"
|
||||
>
|
||||
<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>
|
||||
</button> */}
|
||||
<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>
|
||||
);
|
||||
@@ -97,6 +89,16 @@ export function DesktopItem({ dataset }: { dataset: Dataset }) {
|
||||
? 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
|
||||
@@ -107,23 +109,6 @@ export function DesktopItem({ dataset }: { dataset: Dataset }) {
|
||||
</a>
|
||||
)}
|
||||
</td>
|
||||
{/*
|
||||
<td>
|
||||
<button>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
className="w-12 h-12 text-blue-400 hover:text-blue-300 transition mt-1"
|
||||
>
|
||||
<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>
|
||||
</button>
|
||||
</td>*/}
|
||||
</tr>
|
||||
))}
|
||||
</>
|
||||
@@ -143,6 +128,7 @@ 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}`}
|
||||
>
|
||||
@@ -206,6 +192,7 @@ export default function Home({ datasets }: { datasets: Dataset[] }) {
|
||||
.
|
||||
</p>
|
||||
</main>
|
||||
</Layout>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
@tailwind utilities;
|
||||
|
||||
.preview-table > div {
|
||||
overflow-x: scroll;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
.prose h1 {
|
||||
font-size: 1.5em !important;
|
||||
}
|
||||
|
||||
@@ -81,7 +81,7 @@ export default function DatasetPage({ mdxSource, frontMatter }) {
|
||||
<p className="my-0">
|
||||
<span className="font-semibold">Modified: </span>
|
||||
<span className="description my-0">
|
||||
{new Date(frontMatter.modified).toLocaleDateString()}
|
||||
{new Date(frontMatter.modified).toLocaleDateString("en-US")}
|
||||
</span>
|
||||
</p>
|
||||
)}
|
||||
|
||||
21
examples/openspending/components/DataRichDocument.tsx
Normal file
21
examples/openspending/components/DataRichDocument.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('@portaljs/components').then(mod => mod.Table)),
|
||||
Catalog: dynamic(() => import('@portaljs/components').then(mod => mod.Catalog)),
|
||||
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)),
|
||||
FlatUiTable: dynamic(() => import('@portaljs/components').then(mod => mod.FlatUiTable)),
|
||||
} as any;
|
||||
|
||||
export default function DRD({ source }: { source: any }) {
|
||||
return <MDXRemote {...source} components={components} />;
|
||||
}
|
||||
@@ -45,12 +45,12 @@ export default function DatasetCard({ dataset }: { dataset: Project }) {
|
||||
<dt className="text-gray-500">Fiscal Period</dt>
|
||||
<dd className="text-gray-700">
|
||||
{dataset.fiscalPeriod?.start &&
|
||||
new Date(dataset.fiscalPeriod.start).getFullYear()}
|
||||
new Date(dataset.fiscalPeriod.start).getUTCFullYear()}
|
||||
{dataset.fiscalPeriod?.end &&
|
||||
dataset.fiscalPeriod?.start !== dataset.fiscalPeriod?.end && (
|
||||
<>
|
||||
{' - '}
|
||||
{new Date(dataset.fiscalPeriod.end).getFullYear()}
|
||||
{new Date(dataset.fiscalPeriod.end).getUTCFullYear()}
|
||||
</>
|
||||
)}
|
||||
</dd>
|
||||
|
||||
@@ -4,10 +4,13 @@ import { Grid } from '@githubocto/flat-ui';
|
||||
|
||||
const queryClient = new QueryClient();
|
||||
|
||||
export async function getCsv(url: string) {
|
||||
export async function getCsv(url: string, corsProxy?: string, range?: string) {
|
||||
if (corsProxy) {
|
||||
url = corsProxy + url
|
||||
}
|
||||
const response = await fetch(url, {
|
||||
headers: {
|
||||
Range: 'bytes=0-5132288',
|
||||
Range: range ? `bytes=0-${range}` : 'bytes=0-512000',
|
||||
},
|
||||
});
|
||||
const data = await response.text();
|
||||
@@ -61,21 +64,25 @@ export interface FlatUiTableProps {
|
||||
url?: string;
|
||||
data?: { [key: string]: number | string }[];
|
||||
rawCsv?: string;
|
||||
range?: string;
|
||||
corsProxy?: string;
|
||||
}
|
||||
export const FlatUiTable: React.FC<FlatUiTableProps> = ({
|
||||
url,
|
||||
data,
|
||||
rawCsv,
|
||||
corsProxy,
|
||||
range
|
||||
}) => {
|
||||
return (
|
||||
// Provide the client to your App
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<TableInner url={url} data={data} rawCsv={rawCsv} />
|
||||
<TableInner range={range} corsProxy={corsProxy} url={url} data={data} rawCsv={rawCsv} />
|
||||
</QueryClientProvider>
|
||||
);
|
||||
};
|
||||
|
||||
const TableInner: React.FC<FlatUiTableProps> = ({ url, data, rawCsv }) => {
|
||||
const TableInner: React.FC<FlatUiTableProps> = ({ url, data, rawCsv, corsProxy, range }) => {
|
||||
if (data) {
|
||||
return (
|
||||
<div className="w-full" style={{height: '500px'}}>
|
||||
@@ -85,12 +92,12 @@ const TableInner: React.FC<FlatUiTableProps> = ({ url, data, rawCsv }) => {
|
||||
}
|
||||
const { data: csvString, isLoading: isDownloadingCSV } = useQuery(
|
||||
['dataCsv', url],
|
||||
() => getCsv(url),
|
||||
() => getCsv(url as string, corsProxy, range),
|
||||
{ enabled: !!url }
|
||||
);
|
||||
const { data: parsedData, isLoading: isParsing } = useQuery(
|
||||
['dataPreview', csvString],
|
||||
() => parseCsv(rawCsv ? rawCsv : csvString),
|
||||
() => parseCsv(rawCsv ? rawCsv as string : csvString as string),
|
||||
{ enabled: rawCsv ? true : !!csvString }
|
||||
);
|
||||
if (isParsing || isDownloadingCSV)
|
||||
@@ -103,4 +110,6 @@ const TableInner: React.FC<FlatUiTableProps> = ({ url, data, rawCsv }) => {
|
||||
<Grid data={parsedData.data} />
|
||||
</div>
|
||||
);
|
||||
return <Spinning />
|
||||
};
|
||||
|
||||
|
||||
@@ -23,6 +23,10 @@ export function Header() {
|
||||
title: 'Datasets',
|
||||
href: '/#datasets',
|
||||
},
|
||||
{
|
||||
title: 'Data Stories',
|
||||
href: '/stories',
|
||||
},
|
||||
{
|
||||
title: 'Blog',
|
||||
href: '/blog',
|
||||
@@ -157,7 +161,7 @@ function Dropdown({ navItem }: { navItem: any }) {
|
||||
>
|
||||
<div className="py-1">
|
||||
{navItem.children.map((item) => (
|
||||
<Menu.Item>
|
||||
<Menu.Item key={item.href}>
|
||||
{({ active }) => (
|
||||
<a
|
||||
key={item.href}
|
||||
|
||||
@@ -2,23 +2,23 @@ import Image from 'next/image';
|
||||
import Link from 'next/link';
|
||||
export default function Footer() {
|
||||
return (
|
||||
<footer>
|
||||
<footer className="bg-slate-50 border-t border-slate-100 py-10">
|
||||
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8 flex flex-col items-center justify-between md:flex-row">
|
||||
<div className="flex gap-x-2 items-center">
|
||||
<p className="mt-8 text-base text-slate-500 md:mt-0">Maintained by</p>
|
||||
<a href="https://www.datopian.com/">
|
||||
<div className="flex gap-x-2 items-center justify-center mx-auto">
|
||||
<p className="text-base text-slate-500">Maintained by</p>
|
||||
<a href="https://www.datopian.com/" target="_blank">
|
||||
<Image
|
||||
alt="Datopian logo"
|
||||
className="mb-2"
|
||||
className="mb-1"
|
||||
src="/datopian-logotype.png"
|
||||
width={160}
|
||||
height={40}
|
||||
width={120}
|
||||
height={30}
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
<p className="mt-6 text-base text-slate-500 md:mt-0">
|
||||
Copyright © 2023 Datopian, LLC. All rights reserved.
|
||||
</p>
|
||||
<div className="flex gap-x-2 items-center mx-auto h-20">
|
||||
<p className="mt-8 text-base text-slate-500 md:mt-0">Built with <a href="https://portaljs.org" target="_blank" className='text-xl font-medium'>🌀 PortalJS</a></p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
|
||||
@@ -3,7 +3,7 @@ import Footer from './Footer';
|
||||
|
||||
export default function Layout({ children }) {
|
||||
return (
|
||||
<div className="bg-white min-h-screen pb-12">
|
||||
<div className="bg-white min-h-screen">
|
||||
<Header />
|
||||
{children}
|
||||
<Footer />
|
||||
|
||||
34
examples/openspending/content/stories/about.mdx
Normal file
34
examples/openspending/content/stories/about.mdx
Normal file
@@ -0,0 +1,34 @@
|
||||
---
|
||||
title: Sample Data Story
|
||||
date: 06/06/2023
|
||||
---
|
||||
|
||||
This is a sample data story, you can add charts
|
||||
|
||||
<LineChart
|
||||
data="https://raw.githubusercontent.com/datasets/oil-prices/main/data/wti-year.csv"
|
||||
title="Oil Price x Year"
|
||||
xAxis="Date"
|
||||
yAxis="Price"
|
||||
/>
|
||||
|
||||
Or you can add previews
|
||||
|
||||
<FlatUiTable url="https://raw.githubusercontent.com/datasets/oil-prices/main/data/wti-year.csv" />
|
||||
|
||||
And you can of course add markdown
|
||||
|
||||
## Subtitles
|
||||
|
||||
- Lists
|
||||
- Lists
|
||||
|
||||
You can also add mermaid charts
|
||||
|
||||
```mermaid
|
||||
graph TD;
|
||||
A-->B;
|
||||
A-->C;
|
||||
B-->D;
|
||||
C-->D;
|
||||
```
|
||||
943
examples/openspending/package-lock.json
generated
943
examples/openspending/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -12,27 +12,15 @@
|
||||
"test": "vitest"
|
||||
},
|
||||
"dependencies": {
|
||||
"@flowershow/core": "^0.4.13",
|
||||
"@flowershow/markdowndb": "^0.1.5",
|
||||
"@heroicons/react": "^2.0.18",
|
||||
"@octokit/plugin-throttling": "^5.2.2",
|
||||
"@portaljs/components": "0.1.4",
|
||||
"@types/flexsearch": "^0.7.3",
|
||||
"@types/node": "18.16.0",
|
||||
"@types/react": "18.0.38",
|
||||
"@types/react-dom": "18.0.11",
|
||||
"@vitejs/plugin-react": "^4.0.0",
|
||||
"clsx": "^1.2.1",
|
||||
"datapackage": "^1.1.10",
|
||||
"eslint": "8.39.0",
|
||||
"eslint-config-next": "13.3.1",
|
||||
"flexsearch": "0.7.21",
|
||||
"next": "13.3.0",
|
||||
"next-seo": "^6.0.0",
|
||||
"octokit": "^2.0.14",
|
||||
"prettier": "^2.8.8",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"react-hook-form": "^7.43.9",
|
||||
"react-markdown": "^8.0.7",
|
||||
"react-timeago": "^7.1.0",
|
||||
@@ -40,6 +28,38 @@
|
||||
"remark": "^14.0.3",
|
||||
"remark-gfm": "^3.0.1",
|
||||
"strip-markdown": "^5.0.1",
|
||||
"@flowershow/core": "^0.4.13",
|
||||
"@flowershow/markdowndb": "^0.1.5",
|
||||
"@flowershow/remark-callouts": "^1.0.0",
|
||||
"@flowershow/remark-embed": "^1.0.0",
|
||||
"@githubocto/flat-ui": "^0.14.1",
|
||||
"@headlessui/react": "^1.7.14",
|
||||
"@heroicons/react": "^2.0.18",
|
||||
"@portaljs/ckan": "^0.0.2",
|
||||
"@portaljs/components": "0.1.7",
|
||||
"@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",
|
||||
"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"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -184,22 +184,24 @@ export default function ProjectPage({
|
||||
return (
|
||||
<div key={file.name}>
|
||||
{file.path && (
|
||||
<>
|
||||
<div className="lg:ml-[calc(50%-37vw)] lg:w-[74vw]">
|
||||
<h4>
|
||||
{file.name}
|
||||
{file.format ? `.${file.format}` : ''}
|
||||
</h4>
|
||||
{file.bytes >= 5132288 && (
|
||||
<span>Previewing 5MB out of {size}</span>
|
||||
{file.bytes >= 512000 && (
|
||||
<span>Previewing 500KB out of {size}</span>
|
||||
)}
|
||||
<FlatUiTable
|
||||
url={
|
||||
file.path.startsWith('http')
|
||||
? file.path
|
||||
: `https://raw.githubusercontent.com/${project.owner.name}/${project.repo.name}/main/${file.path}`
|
||||
}
|
||||
/>
|
||||
</>
|
||||
<div className="mt-5">
|
||||
<FlatUiTable
|
||||
url={
|
||||
file.path.startsWith('http')
|
||||
? file.path
|
||||
: `https://raw.githubusercontent.com/${project.owner.name}/${project.repo.name}/main/${file.path}`
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -53,6 +53,7 @@ export async function getStaticPaths() {
|
||||
|
||||
const paths = allDocuments
|
||||
.filter((page) => page.metadata?.isDraft !== true)
|
||||
.filter((page) => !page.file_path.startsWith('content/stories/'))
|
||||
.map((page) => {
|
||||
const parts = page.url_path!.split('/');
|
||||
return { params: { slug: parts } };
|
||||
|
||||
@@ -57,7 +57,7 @@ function CustomApp({ Component, pageProps }: AppProps) {
|
||||
<NextSeo title="OpenSpending" />
|
||||
<Script
|
||||
strategy="afterInteractive"
|
||||
src={`https://www.googletagmanager.com/gtag/js?id=${GA_TOKEN}`}
|
||||
src="https://www.googletagmanager.com/gtag/js?id=G-GXZF7NRXX6"
|
||||
/>
|
||||
<Script
|
||||
id="gtag-init"
|
||||
@@ -67,7 +67,7 @@ function CustomApp({ Component, pageProps }: AppProps) {
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
function gtag(){dataLayer.push(arguments);}
|
||||
gtag('js', new Date());
|
||||
gtag('config', ${GA_TOKEN}, {
|
||||
gtag('config', 'G-GXZF7NRXX6', {
|
||||
page_path: window.location.pathname,
|
||||
});
|
||||
`,
|
||||
|
||||
62
examples/openspending/pages/stories/[fileName].tsx
Normal file
62
examples/openspending/pages/stories/[fileName].tsx
Normal file
@@ -0,0 +1,62 @@
|
||||
import fs from 'fs';
|
||||
import clientPromise from '@/lib/mddb';
|
||||
import { GetStaticProps } from 'next';
|
||||
import Layout from '../../components/_shared/Layout';
|
||||
import { formatDate } from '@/utils/formatDate';
|
||||
import parse from '../../lib/markdown';
|
||||
import DataRichDocument from '../../components/DataRichDocument';
|
||||
|
||||
export default function Page({ source, meta }) {
|
||||
return (
|
||||
<Layout>
|
||||
<article className="docs prose-a:text-primary dark:prose-a:text-primary-dark prose-strong:text-primary dark:prose-strong:text-primary-dark prose-code:text-primary dark:prose-code:text-primary-dark prose-headings:text-primary dark:prose-headings:text-primary-dark prose text-primary dark:text-primary-dark prose-headings:font-headings dark:prose-invert prose-a:break-words mx-auto p-6">
|
||||
<header>
|
||||
<div className="mb-4 flex-col items-center">
|
||||
{meta.title && (
|
||||
<h1 className="flex justify-center">{meta.title}</h1>
|
||||
)}
|
||||
{meta.date && (
|
||||
<p className="text-sm text-zinc-400 dark:text-zinc-500 flex justify-center">
|
||||
<time dateTime={meta.date}>{formatDate(meta.date)}</time>
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</header>
|
||||
<section>
|
||||
<DataRichDocument source={source} />
|
||||
</section>
|
||||
</article>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
|
||||
export const getStaticProps: GetStaticProps = async ({ params }) => {
|
||||
const mddb = await clientPromise;
|
||||
const dbFile = await mddb.getFileByUrl('stories/' + params?.fileName);
|
||||
|
||||
let source = fs.readFileSync(dbFile.file_path, { encoding: 'utf-8' });
|
||||
let { mdxSource } = await parse(source, '.mdx', {});
|
||||
return {
|
||||
props: {
|
||||
source: mdxSource,
|
||||
meta: dbFile.metadata,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export async function getStaticPaths() {
|
||||
const mddb = await clientPromise;
|
||||
let allDocuments = await mddb.getFiles({ extensions: ['mdx'], folder: 'stories' });
|
||||
|
||||
const paths = allDocuments
|
||||
.filter((page) => page.metadata?.isDraft !== true)
|
||||
.map((page) => {
|
||||
const parts = page.url_path!.split('/').slice(-1)[0];
|
||||
return { params: { fileName: parts } };
|
||||
});
|
||||
|
||||
return {
|
||||
paths,
|
||||
fallback: false,
|
||||
};
|
||||
}
|
||||
57
examples/openspending/pages/stories/index.tsx
Normal file
57
examples/openspending/pages/stories/index.tsx
Normal file
@@ -0,0 +1,57 @@
|
||||
import fs from 'fs';
|
||||
import React from 'react';
|
||||
import { GetStaticProps } from 'next';
|
||||
import { BlogsList, SimpleLayout } from '@flowershow/core';
|
||||
import clientPromise from '../../lib/mddb';
|
||||
import type { CustomAppProps } from '../_app';
|
||||
import Layout from '@/components/_shared/Layout';
|
||||
|
||||
interface BlogIndexPageProps extends CustomAppProps {
|
||||
blogs: any[]; // TODO types
|
||||
}
|
||||
|
||||
export default function StoriesList({
|
||||
stories,
|
||||
meta: { title, description },
|
||||
}: BlogIndexPageProps) {
|
||||
return (
|
||||
<Layout>
|
||||
<div className="blog-list">
|
||||
<SimpleLayout title={title} description={description}>
|
||||
<BlogsList blogs={stories} />
|
||||
</SimpleLayout>
|
||||
</div>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
|
||||
export const getStaticProps: GetStaticProps = async () => {
|
||||
const mddb = await clientPromise;
|
||||
const storiesFiles = await mddb.getFiles({ folder: 'stories' });
|
||||
|
||||
const stories = storiesFiles.map((item) => ({
|
||||
_id: item._id,
|
||||
file_path: item.file_path,
|
||||
urlPath: item.url_path,
|
||||
date: item.metadata.date,
|
||||
...item.metadata,
|
||||
}));
|
||||
|
||||
return {
|
||||
props: {
|
||||
meta: {
|
||||
title: 'Data Stories',
|
||||
showSidebar: false,
|
||||
showToc: false,
|
||||
showComments: false,
|
||||
showEditLink: false,
|
||||
urlPath: '/stories',
|
||||
},
|
||||
stories: stories.sort((a, b) => {
|
||||
const bDate = new Date(b.date);
|
||||
const aDate = new Date(a.date);
|
||||
return bDate.getTime() - aDate.getTime();
|
||||
}),
|
||||
},
|
||||
};
|
||||
};
|
||||
@@ -82,3 +82,109 @@ pre {
|
||||
.blog-list button {
|
||||
color: black;
|
||||
}
|
||||
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
@import "@flowershow/remark-callouts/styles.css";
|
||||
|
||||
.w-5 {
|
||||
width: 1.25rem
|
||||
}
|
||||
|
||||
.h-5 {
|
||||
height: 1.25rem
|
||||
}
|
||||
|
||||
/* 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;
|
||||
}
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
|
||||
Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
html {
|
||||
color-scheme: dark;
|
||||
}
|
||||
body {
|
||||
color: white;
|
||||
background: black;
|
||||
}
|
||||
}
|
||||
|
||||
26571
package-lock.json
generated
26571
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,11 +1,18 @@
|
||||
{
|
||||
"name": "portaljs",
|
||||
"workspaces": ["./packages/*"],
|
||||
"version": "0.0.0",
|
||||
"license": "MIT",
|
||||
"scripts": {},
|
||||
"scripts": {
|
||||
"changeset": "changeset",
|
||||
"prerelease": "nx affected --targets=lint,test",
|
||||
"release": "changeset publish"
|
||||
},
|
||||
"private": true,
|
||||
"devDependencies": {
|
||||
"@babel/preset-react": "^7.14.5",
|
||||
"@changesets/changelog-github": "^0.4.8",
|
||||
"@changesets/cli": "^2.26.1",
|
||||
"@nrwl/cypress": "15.9.2",
|
||||
"@nrwl/eslint-plugin-nx": "^16.0.2",
|
||||
"@nrwl/jest": "15.9.2",
|
||||
|
||||
7
packages/components/CHANGELOG.md
Normal file
7
packages/components/CHANGELOG.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# @portaljs/components
|
||||
|
||||
## 0.1.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#924](https://github.com/datopian/portaljs/pull/924) [`959fe5a`](https://github.com/datopian/portaljs/commit/959fe5a588b12786e3f2e642c24009d77bceac46) Thanks [@luccasmmg](https://github.com/luccasmmg)! - Fixed redeclaration of formal parameter "x" error after next build
|
||||
633
packages/components/package-lock.json
generated
633
packages/components/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@portaljs/components",
|
||||
"version": "0.1.6",
|
||||
"version": "0.1.7",
|
||||
"type": "module",
|
||||
"description": "https://portaljs.org",
|
||||
"keywords": [
|
||||
@@ -34,8 +34,10 @@
|
||||
"react-hook-form": "^7.43.9",
|
||||
"react-query": "^3.39.3",
|
||||
"react-vega": "^7.6.0",
|
||||
"rollup-plugin-re": "^1.0.7",
|
||||
"vega": "5.25.0",
|
||||
"vega-lite": "5.1.0"
|
||||
"vega-lite": "5.1.0",
|
||||
"vitest": "^0.31.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@storybook/addon-essentials": "^7.0.7",
|
||||
|
||||
@@ -1,30 +1,61 @@
|
||||
import react from '@vitejs/plugin-react';
|
||||
import path from 'node:path';
|
||||
import { defineConfig } from 'vite';
|
||||
import dts from 'vite-plugin-dts';
|
||||
import react from '@vitejs/plugin-react'
|
||||
import path from 'node:path'
|
||||
import { defineConfig } from 'vitest/config'
|
||||
import dts from 'vite-plugin-dts'
|
||||
import tailwindcss from 'tailwindcss'
|
||||
import { UserConfigExport } from 'vite'
|
||||
import replace from "rollup-plugin-re"
|
||||
|
||||
export default defineConfig({
|
||||
const app = async (): Promise<UserConfigExport> => {
|
||||
return defineConfig({
|
||||
plugins: [
|
||||
react(),
|
||||
dts({
|
||||
insertTypesEntry: true,
|
||||
}),
|
||||
],
|
||||
build: {
|
||||
lib: {
|
||||
entry: path.resolve(__dirname, 'src/index.ts'),
|
||||
name: 'components',
|
||||
formats: ['es', 'umd'],
|
||||
fileName: (format) => `components.${format}.js`,
|
||||
},
|
||||
rollupOptions: {
|
||||
external: ['react', 'react-dom', 'styled-components'],
|
||||
output: {
|
||||
globals: {
|
||||
react: 'React',
|
||||
'react-dom': 'ReactDOM'
|
||||
},
|
||||
react(),
|
||||
replace({
|
||||
patterns: [
|
||||
{
|
||||
match: /js-sha256/,
|
||||
test: `eval("require('crypto')")`,
|
||||
replace: `require('crypto')`,
|
||||
},
|
||||
},
|
||||
{
|
||||
match: /js-sha256/,
|
||||
test: `eval("require('buffer').Buffer")`,
|
||||
replace: `require('buffer').Buffer`,
|
||||
},
|
||||
],
|
||||
}),
|
||||
dts({
|
||||
insertTypesEntry: true,
|
||||
}),
|
||||
],
|
||||
css: {
|
||||
postcss: {
|
||||
plugins: [tailwindcss],
|
||||
},
|
||||
},
|
||||
});
|
||||
build: {
|
||||
lib: {
|
||||
entry: path.resolve(__dirname, 'src/index.ts'),
|
||||
name: 'components',
|
||||
formats: ['es', 'umd'],
|
||||
fileName: (format) => `components.${format}.js`,
|
||||
},
|
||||
rollupOptions: {
|
||||
external: ['react', 'react-dom', 'tailwindcss', 'vega-lite', 'vega', 'react-vega'],
|
||||
output: {
|
||||
globals: {
|
||||
react: 'React',
|
||||
'react-dom': 'ReactDOM',
|
||||
tailwindcss: 'tailwindcss',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
test: {
|
||||
globals: true,
|
||||
environment: 'jsdom',
|
||||
},
|
||||
})
|
||||
}
|
||||
// https://vitejs.dev/config/
|
||||
export default app
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { MDXRemote } from 'next-mdx-remote';
|
||||
import layouts from 'layouts';
|
||||
import DocsPagination from './DocsPagination';
|
||||
import { NextSeo } from 'next-seo';
|
||||
|
||||
export default function MDXPage({ source, frontMatter }) {
|
||||
const Layout = ({ children }) => {
|
||||
@@ -10,9 +11,10 @@ export default function MDXPage({ source, frontMatter }) {
|
||||
return <LayoutComponent {...frontMatter}>{children}</LayoutComponent>;
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<MDXRemote {...source} components={{ DocsPagination }} />
|
||||
<MDXRemote {...source} components={{ DocsPagination, NextSeo }} />
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
<NextSeo title="Creating new datasets - PortalJS" />
|
||||
|
||||
# Creating new datasets
|
||||
|
||||
So far, the PortalJS app we created only has a single page displaying a dataset. Data catalogs and data portals generally showcase many different datasets.
|
||||
@@ -63,4 +65,6 @@ From the browser, access http://localhost:3000. You should see the following:
|
||||
|
||||
<img src="/assets/docs/datasets-index-page.png" />
|
||||
|
||||
<DocsPagination prev="/docs" next="/docs/searching-datasets" />
|
||||
At this point, the app has multiple datasets, and users can find and navigate to any dataset they want. In the next lesson, you are going to learn how to improve this experience with search.
|
||||
|
||||
<DocsPagination prev="/docs" next="/docs/searching-datasets" />
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
<NextSeo title="Deploying your PortalJS app - PortalJS" />
|
||||
|
||||
# Deploying your PortalJS app
|
||||
|
||||
Finally, let's learn how to deploy PortalJS apps to Vercel or Cloudflare Pages.
|
||||
@@ -31,7 +33,7 @@ To deploy your PortalJS app:
|
||||
|
||||
When you deploy, your PortalJS app will start building. It should finish in under a minute.
|
||||
|
||||
When it’s done, you’ll get deployment URLs. Click on one of the URLs and you should see your PortaJS app live.
|
||||
When it’s done, you’ll get deployment URLs. Click on one of the URLs and you should see your PortalJS app live.
|
||||
|
||||
>[!tip]
|
||||
> You can find a more in-depth explanation about this process at https://nextjs.org/learn/basics/deploying-nextjs-app/deploy
|
||||
|
||||
@@ -23,33 +23,58 @@ Below are some screenshots:
|
||||
|
||||
## How to use this example as a template
|
||||
|
||||
- Create a new app with `create-next-app`:
|
||||
```
|
||||
> [!tip]
|
||||
> You can also create a new project by clicking on the "Deploy" button below. Vercel will clone the example on a new repo under your user or organization on GitHub and set up a deployment for it.
|
||||
> Then, you can clone the new repo on your machine and start editing it.
|
||||
|
||||
### Create a new app with `create-next-app`
|
||||
|
||||
Run the following commands:
|
||||
|
||||
|
||||
```bash
|
||||
npx create-next-app <app-name> --example https://github.com/datopian/portaljs/tree/main/examples/github-backed-catalog
|
||||
cd <app-name>
|
||||
```
|
||||
|
||||
- This project uses the github api, which for anonymous users will cap at 50 requests per hour, so you might want to get a [Personal Access Token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token) and add it to a `.env` file inside the folder like so
|
||||
### Setup a GitHub token
|
||||
|
||||
```
|
||||
This project uses the GitHub API, which for anonymous users will cap at 50 requests per hour, so you might want to get a [Personal Access Token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token) and add it to a `.env` file inside the folder, like so:
|
||||
|
||||
```bash
|
||||
GITHUB_PAT=<github token>
|
||||
```
|
||||
|
||||
- Edit the file `datasets.json` to your liking, some examples can be found inside this [repo](https://github.com/datasets)
|
||||
- Run the app using:
|
||||
### Setup the datasets list
|
||||
|
||||
```
|
||||
The `datasets.json` file serves as a list of datasets that should be in your data portal. Edit the this file to your liking. Some examples can be found inside this [repo](https://github.com/datasets):
|
||||
|
||||
### Run the app
|
||||
|
||||
Run the app by executing the following command:
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Congratulations, you now have something similar to this running on `http://localhost:3000`
|
||||
Congratulations, your new app is now running at http://localhost:3000.
|
||||
|
||||
## Deployment
|
||||
|
||||
[](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fdatopian%2Fportaljs%2Ftree%2Fmain%2Fexamples%2Fgithub-backed-catalog)
|
||||
|
||||
By clicking on this button, you will be redirected to a page which will allow you to clone the content into your own github/gitlab/bitbucket account and automatically deploy everything.
|
||||
By clicking on this button, you will be redirected to a page which will allows you to clone the example into your own GitHub/GitLab/BitBucket account and automatically deploy it.
|
||||
|
||||
### GitHub token
|
||||
|
||||
You have to set up `GITHUB_PATH` as an environment variable on Vercel. To do that, go to the project's page on Vercel, then click settings, look for "Environment variables" and create a new environment variable. Refer back to the [previous section](#setup-a-github-token) if you are not sure about how to create a GitHub token.
|
||||
|
||||
### Editing the new deployment
|
||||
|
||||
You can now clone the new repo on your machine and start changing it. Simply follow the ["How to use this example as a template" section](#how-to-use-this-example-as-a-template) skipping the first step.
|
||||
|
||||
> [!tip]
|
||||
> Note that whenever you push changes to the new repo these are going to be automatically deployed by Vercel.
|
||||
|
||||
## Structure of `datasets.json`
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
<NextSeo title="Getting Started - PortalJS" />
|
||||
|
||||
# Getting Started
|
||||
|
||||
Welcome to the PortalJS documentation!
|
||||
@@ -51,4 +53,6 @@ After refreshing the page, you should see the new text:
|
||||
|
||||
<img src="/assets/docs/editing-the-page-1.png" />
|
||||
|
||||
<DocsPagination next="/docs/creating-new-datasets" />
|
||||
Congratulations! The app is up and running and you learned how to edit a page. In the next lesson, you are going to learn how to create new datasets.
|
||||
|
||||
<DocsPagination next="/docs/creating-new-datasets" />
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
<NextSeo title="Searching datasets - PortalJS" />
|
||||
|
||||
# Searching datasets
|
||||
|
||||
Typing out every link in the index page will get cumbersome eventually, and as the portal grows, finding the datasets you are looking for on the index page will become harder and harder, for that we will need search functionality.
|
||||
@@ -102,4 +104,6 @@ You now have a filter in your page with all possible values automatically added
|
||||
|
||||

|
||||
|
||||
In the next lesson, you are going to learn how to display metadata on the dataset page.
|
||||
|
||||
<DocsPagination prev="/docs/creating-new-datasets" next="/docs/showing-metadata" />
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
<NextSeo title="Showing metadata - PortalJS" />
|
||||
|
||||
# Showing metadata
|
||||
|
||||
If you go now to `http://localhost:3000/my-awesome-dataset`, you will see that we now have two titles on the page. That's because `title` is one of the default metadata fields supported by PortalJS.
|
||||
@@ -33,4 +35,6 @@ These are the standard metadata fields that will be shown at the top of the page
|
||||
- `author`, `description`, and `modified` which gets displayed below the title
|
||||
- `files` that get displayed as a table with two columns: `File` which is linked directly to the file, and `Format` which show the file format.
|
||||
|
||||
Feel free to experiment with these metadata fields. At this point, you might want to deploy the app, and that's what you are going to learn in the next lesson.
|
||||
|
||||
<DocsPagination prev="/docs/searching-datasets" next="/docs/deploying-your-portaljs-app" />
|
||||
|
||||
Reference in New Issue
Block a user