Compare commits

...

63 Commits

Author SHA1 Message Date
Ola Rubaj
2f56b1bdc8 fix formatting 2024-10-23 18:00:32 +02:00
Ola Rubaj
72f78387ce [feat,LineChart][s]: support for multiple series 2024-10-23 17:55:50 +02:00
Anuar Ustayev (aka Anu)
f86f0541eb Merge pull request #1332 from datopian/site/fix-showcases
[portaljs site][showcases][s] Merge examples into Showcases tab
2024-10-11 09:36:16 +05:00
Lucas Morais Bispo
64bc212384 Update README.md 2024-10-09 11:46:02 -03:00
Lucas Morais Bispo
1e7daf353d Add files via upload 2024-10-09 11:28:42 -03:00
lucasmbispo
cc69dabf80 [site][showcases] update examples 2024-10-03 21:04:06 -03:00
lucasmbispo
a5d87712e0 [site][showcases][s] Merge examples into Showcases tab 2024-10-01 11:07:33 -03:00
Rufus Pollock
86834fd1a6 Merge pull request #1317 from loleg/patch-1
Fix link to Next.js in README.md
2024-09-20 13:30:02 +02:00
Oleg Lavrovsky
8a661b1617 Fix link to Next.js in README.md 2024-09-20 11:23:06 +02:00
Rufus Pollock
1baebc3f3c Merge pull request #1200 from rzmk/patch-1
[#1181, examples/ckan-ssg][xs]: update example generation command
2024-07-05 19:13:43 +02:00
João Demenech
bbac4954f5 Merge pull request #1202 from datopian/changeset-release/main
Version Packages
2024-06-24 17:58:02 -03:00
github-actions[bot]
be6b184884 Version Packages 2024-06-24 20:47:23 +00:00
João Demenech
64103d6488 Merge pull request #1122 from datopian/feature/custom-tile-layer
Custom Tile Layer for Map Component
2024-06-24 17:44:19 -03:00
Demenech
8e3496782c version: add changeset 2024-06-24 17:42:49 -03:00
Mueez Khan
e034503399 [examples/ckan-ssg][xs]: update command to create project 2024-06-22 00:17:49 -04:00
William Lima
93ae498ec2 Code cleanup 2024-06-19 10:10:56 -01:00
William Lima
97e43fdcba add mapbox as default basemap 2024-06-18 22:37:20 -01:00
William Lima
32f29024f8 attr replace fix 2024-06-18 22:05:41 -01:00
William Lima
134f72948c Add TileLayer Presets configuration 2024-06-18 22:01:59 -01:00
Rufus Pollock
c1f2c526a8 [#1181,site][xs]: change portaljs to datahub in github repo references. 2024-06-10 19:31:43 +02:00
João Demenech
8feb87739d Merge pull request #1173 from datopian/changeset-release/main
Version Packages
2024-06-09 08:06:43 -03:00
github-actions[bot]
3a07267e44 Version Packages 2024-06-09 09:25:23 +00:00
Rufus Pollock
3f19ca16ed [#1118,docs/portaljs][s 2024-06-09 11:22:25 +02:00
João Demenech
5deabac5fe Merge pull request #1170 from datopian/fix/iframe-height
[components][iFrame] Change default height
2024-06-04 14:57:24 -03:00
lucasmbispo
96901150c6 [changesets] change major to patch 2024-06-04 09:38:47 -03:00
lucasmbispo
9ff25ed7c4 [components][iFrame] Change iFrame height 2024-06-04 09:38:12 -03:00
lucasmbispo
8f884fceab [components][iFrame] Change default height 2024-06-04 09:26:30 -03:00
Anuar Ustayev (aka Anu)
7094eded50 Merge pull request #1167 from datopian/fix/map-geojson
Fix: autoZoomConfiguration not working properly when the geojson parameter is passed
2024-06-04 14:06:45 +05:00
Rufus Pollock
30e7c6379f Merge pull request #1069 from marcchehab/patch-2 - Add SiteToc to MobileNav.
This PR adds the SiteToc to the MobileNav. It also fixes double type declarations in MobileNav by importing the interfaces from Nav. Adding SiteToc was then just a matter of uncommenting code that was there already.
2024-05-31 17:16:42 +02:00
Ronaldo Campos
feada58932 Fix: autoZoomConfiguration not working properly when the geojson parameter is passed 2024-05-31 11:37:01 -03:00
William Lima
31406d48e3 Update Map.tsx 2024-05-31 10:29:15 -01:00
Daniellappv
d6bf344ca3 Update CONTRIBUTING.md 2024-05-31 10:55:58 +03:00
William Lima
d1a5138c6e include configs on .env vars or pass through props 2024-05-22 11:48:20 -01:00
William Lima
a6047a9341 Implements Custom Tile Layer
#1121 adds default tile layer and allows user to pass a tile object to map
2024-05-13 12:51:28 -01:00
Ola Rubaj
a4e60540ae Merge pull request #1119 from datopian/remark-wiki-link-cleanup
## Changes

- remove unneeded tests
- do not remove "index" from the end of tile path in `getPermalinks` function
2024-05-09 02:20:45 +02:00
Ola Rubaj
e4c456c237 rm changeset file 2024-05-09 02:19:54 +02:00
Ola Rubaj
ce9ebbf41e add changeset file 2024-05-09 02:16:05 +02:00
Ola Rubaj
a8fb176bcc rm test for custom permarlink converter (irrelevant) 2024-05-09 02:12:44 +02:00
Ola Rubaj
2ac82367c5 do not remove "index" from the end of file
- should be treated as a regular file name
- it's up to the app how to interpret those paths/files later
2024-05-09 02:12:38 +02:00
Ola Rubaj
85de6f7878 replace inex.md with README.md in test fixtures 2024-05-09 02:09:52 +02:00
Ola Rubaj
539fffeb55 Merge pull request #1113 from datopian/changeset-release/main
Version Packages
2024-04-18 15:43:31 +02:00
github-actions[bot]
0d276535bd Version Packages 2024-04-18 13:42:23 +00:00
Ola Rubaj
38dd7103a3 Merge pull request #1103 from datopian/feat/portaljs-components-improvements
Components API and docs improvements
Related to: #1089
2024-04-17 16:16:27 +02:00
Ola Rubaj
48cd812a48 add changeset file 2024-04-17 16:14:00 +02:00
Ola Rubaj
7bba10714d refresh package-lock file 2024-04-17 16:13:47 +02:00
Demenech
df9664624f fix(LineChart): remove unused fillWidth prop 2024-04-09 17:45:12 -03:00
Demenech
2ea185b710 feat: Catalog component API and docs improvements 2024-04-09 17:41:01 -03:00
Demenech
b859d48f17 feat: Map component API and docs improvements 2024-04-09 17:30:45 -03:00
Demenech
3d73ac422e feat: Vega and Vega Lite components API and docs improvements 2024-04-09 17:13:05 -03:00
Demenech
059ffe4e34 feat: PlotlyLineChart component API and docs improvements 2024-04-09 17:08:50 -03:00
Demenech
0aed7dce77 feat: Plotly component docs improvements 2024-04-09 16:57:23 -03:00
Demenech
c202d6cfc4 feat: LineChart component API and docs improvements 2024-04-09 16:50:49 -03:00
Demenech
d9c20528c5 feat: PdfViewer component API and docs improvements 2024-04-09 16:20:01 -03:00
Demenech
b7ee5a1869 feat: Iframe component API and docs improvements 2024-04-09 16:07:12 -03:00
Demenech
4b5d549190 feat: comment out the Table component for now 2024-04-09 15:58:33 -03:00
Demenech
e6f0ab4ec8 feat: FlatUiTable component API and docs improvements 2024-04-09 15:54:03 -03:00
Demenech
22038fbd4f feat: Excel component API and docs improvements 2024-04-09 15:44:37 -03:00
Demenech
8b292a9bf2 feat: group stories in different categories 2024-04-09 15:36:48 -03:00
Demenech
cda3d335f1 feat: rename Plotly components stories so that they show up together on the storybook sidebar 2024-04-09 15:25:14 -03:00
Demenech
fe97cc87f4 fix: OpenLayers and BucketViewer were still showing up 2024-04-09 15:22:55 -03:00
Demenech
88f6199d18 feat: implement new Data interface + review PlotlyBarChart API and docs + hide BucketViewer and OpenLayers 2024-04-09 15:21:08 -03:00
luzmediach
1a8e7ac06e NavMobile to use Nav interfaces and add SiteToc to sidebar 2024-01-21 12:48:10 +01:00
marcchehab
4355efe0c4 Update Nav.tsx 2024-01-21 12:36:46 +01:00
68 changed files with 2128 additions and 786 deletions

View File

@@ -0,0 +1,5 @@
---
'@portaljs/components': minor
---
Support for plotting multiple series in LineChart component.

View File

@@ -4,7 +4,7 @@ title: Developer docs for contributors
## Our repository ## Our repository
https://github.com/datopian/portaljs https://github.com/datopian/datahub
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/portaljs/issues). You can start by checking our [issues board](https://github.com/datopian/datahub/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:
@@ -35,7 +35,7 @@ If you'd like to work on one of the issues you can:
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/rTxfCutu) do discuss existing issues and to ask for help. > Join our [Discord channel](https://discord.gg/KZSf3FG4EZ) do discuss existing issues and to ask for help.
## Nx ## Nx

View File

@@ -41,7 +41,7 @@ https://datahub.io/docs
DataHub 🌀 is a platform for rapidly creating rich data portal and publishing systems using a modern frontend approach. Datahub can be used to publish a single dataset or build a full-scale data catalog/portal. DataHub 🌀 is a platform for rapidly creating rich data portal and publishing systems using a modern frontend approach. Datahub can be used to publish a single dataset or build a full-scale data catalog/portal.
DataHub is built in JavaScript and React on top of the popular [Next.js](https://nextjs.com/) framework. DataHub 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. DataHub is built in JavaScript and React on top of the popular [Next.js](https://nextjs.org) framework. DataHub 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 ### Features

View File

@@ -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/portaljs/tree/main/examples/ckan-example npx create-next-app <app-name> --example https://github.com/datopian/datahub/tree/main/examples/ckan-ssg
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`
![](https://media.discordapp.net/attachments/1069718983604977754/1098252297726865408/image.png?width=853&height=461) ![](https://media.discordapp.net/attachments/1069718983604977754/1098252297726865408/image.png?width=853&height=461)
If yo go to any one of those pages by clicking on `More info` you will see something similar to this If you go to any one of those pages by clicking on `More info` you will see something similar to this
![](https://media.discordapp.net/attachments/1069718983604977754/1098252298074988595/image.png?width=853&height=461) ![](https://media.discordapp.net/attachments/1069718983604977754/1098252298074988595/image.png?width=853&height=461)
## Deployment ## Deployment

View File

@@ -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://frictionlessdata.io/data-packages/ [fd]: https://specs.frictionlessdata.io/data-package/
## How to use ## How to use

View File

@@ -1,9 +1,16 @@
import 'tailwindcss/tailwind.css' import 'tailwindcss/tailwind.css'
import '../src/index.css' import '../src/index.css'
import type { Preview } from '@storybook/react'; import type { Preview } from '@storybook/react';
window.process = {
...window.process,
env:{
...window.process?.env,
}
};
const preview: Preview = { const preview: Preview = {
parameters: { parameters: {
actions: { argTypesRegex: '^on[A-Z].*' }, actions: { argTypesRegex: '^on[A-Z].*' },

View File

@@ -1,5 +1,23 @@
# @portaljs/components # @portaljs/components
## 1.1.0
### Minor Changes
- [#1122](https://github.com/datopian/datahub/pull/1122) [`8e349678`](https://github.com/datopian/datahub/commit/8e3496782c022b0653e07f217c6b315ba84e0e61) Thanks [@willy1989cv](https://github.com/willy1989cv)! - Map: allow users to choose a base layer setting
## 1.0.1
### Patch Changes
- [#1170](https://github.com/datopian/datahub/pull/1170) [`9ff25ed7`](https://github.com/datopian/datahub/commit/9ff25ed7c47c8c02cc078c64f76ae35d6754c508) Thanks [@lucasmbispo](https://github.com/lucasmbispo)! - iFrame component: change height
## 1.0.0
### Major Changes
- [#1103](https://github.com/datopian/datahub/pull/1103) [`48cd812a`](https://github.com/datopian/datahub/commit/48cd812a488a069a419d8ecc67f24f94d4d1d1d6) Thanks [@demenech](https://github.com/demenech)! - Components API tidying up and storybook docs improvements.
## 0.6.0 ## 0.6.0
### Minor Changes ### Minor Changes

View File

@@ -1,6 +1,6 @@
{ {
"name": "@portaljs/components", "name": "@portaljs/components",
"version": "0.6.0", "version": "1.1.0",
"type": "module", "type": "module",
"description": "https://portaljs.org", "description": "https://portaljs.org",
"keywords": [ "keywords": [

View File

@@ -7,7 +7,12 @@ export function Catalog({
datasets, datasets,
facets, facets,
}: { }: {
datasets: any[]; datasets: {
_id: string | number;
metadata: { title: string; [k: string]: string | number };
url_path: string;
[k: string]: any;
}[];
facets: string[]; facets: string[];
}) { }) {
const [indexFilter, setIndexFilter] = useState(''); const [indexFilter, setIndexFilter] = useState('');
@@ -56,7 +61,7 @@ export function Catalog({
//Then check if the selectedValue for the given facet is included in the dataset metadata //Then check if the selectedValue for the given facet is included in the dataset metadata
.filter((dataset) => { .filter((dataset) => {
//Avoids a server rendering breakage //Avoids a server rendering breakage
if (!watch() || Object.keys(watch()).length === 0) return true if (!watch() || Object.keys(watch()).length === 0) return true;
//This will filter only the key pairs of the metadata values that were selected as facets //This will filter only the key pairs of the metadata values that were selected as facets
const datasetFacets = Object.entries(dataset.metadata).filter((entry) => const datasetFacets = Object.entries(dataset.metadata).filter((entry) =>
facets.includes(entry[0]) facets.includes(entry[0])
@@ -86,9 +91,7 @@ export function Catalog({
className="p-2 ml-1 text-sm shadow border border-block" className="p-2 ml-1 text-sm shadow border border-block"
{...register(elem[0] + '.selectedValue')} {...register(elem[0] + '.selectedValue')}
> >
<option value=""> <option value="">Filter by {elem[0]}</option>
Filter by {elem[0]}
</option>
{(elem[1] as { possibleValues: string[] }).possibleValues.map( {(elem[1] as { possibleValues: string[] }).possibleValues.map(
(val) => ( (val) => (
<option <option
@@ -102,10 +105,10 @@ export function Catalog({
)} )}
</select> </select>
))} ))}
<ul className='mb-5 pl-6 mt-5 list-disc'> <ul className="mb-5 pl-6 mt-5 list-disc">
{filteredDatasets.map((dataset) => ( {filteredDatasets.map((dataset) => (
<li className='py-2' key={dataset._id}> <li className="py-2" key={dataset._id}>
<a className='font-medium underline' href={dataset.url_path}> <a className="font-medium underline" href={dataset.url_path}>
{dataset.metadata.title {dataset.metadata.title
? dataset.metadata.title ? dataset.metadata.title
: dataset.url_path} : dataset.url_path}
@@ -116,4 +119,3 @@ export function Catalog({
</> </>
); );
} }

View File

@@ -4,12 +4,14 @@ import { read, utils } from 'xlsx';
import { AgGridReact } from 'ag-grid-react'; import { AgGridReact } from 'ag-grid-react';
import 'ag-grid-community/styles/ag-grid.css'; import 'ag-grid-community/styles/ag-grid.css';
import 'ag-grid-community/styles/ag-theme-alpine.css'; import 'ag-grid-community/styles/ag-theme-alpine.css';
import { Data } from '../types/properties';
export type ExcelProps = { export type ExcelProps = {
url: string; data: Required<Pick<Data, 'url'>>;
}; };
export function Excel({ url }: ExcelProps) { export function Excel({ data }: ExcelProps) {
const url = data.url;
const [isLoading, setIsLoading] = useState<boolean>(false); const [isLoading, setIsLoading] = useState<boolean>(false);
const [activeSheetName, setActiveSheetName] = useState<string>(); const [activeSheetName, setActiveSheetName] = useState<string>();
const [workbook, setWorkbook] = useState<any>(); const [workbook, setWorkbook] = useState<any>();

View File

@@ -2,6 +2,7 @@ import { QueryClient, QueryClientProvider, useQuery } from 'react-query';
import Papa from 'papaparse'; import Papa from 'papaparse';
import { Grid } from '@githubocto/flat-ui'; import { Grid } from '@githubocto/flat-ui';
import LoadingSpinner from './LoadingSpinner'; import LoadingSpinner from './LoadingSpinner';
import { Data } from '../types/properties';
const queryClient = new QueryClient(); const queryClient = new QueryClient();
@@ -36,30 +37,25 @@ export async function parseCsv(file: string, parsingConfig): Promise<any> {
} }
export interface FlatUiTableProps { export interface FlatUiTableProps {
url?: string; data: Data;
data?: { [key: string]: number | string }[]; uniqueId?: number;
rawCsv?: string;
randomId?: number;
bytes: number; bytes: number;
parsingConfig: any; parsingConfig: any;
} }
export const FlatUiTable: React.FC<FlatUiTableProps> = ({ export const FlatUiTable: React.FC<FlatUiTableProps> = ({
url,
data, data,
rawCsv, uniqueId,
bytes = 5132288, bytes = 5132288,
parsingConfig = {}, parsingConfig = {},
}) => { }) => {
const randomId = Math.random(); uniqueId = uniqueId ?? Math.random();
return ( return (
// Provide the client to your App // Provide the client to your App
<QueryClientProvider client={queryClient}> <QueryClientProvider client={queryClient}>
<TableInner <TableInner
bytes={bytes} bytes={bytes}
url={url}
data={data} data={data}
rawCsv={rawCsv} uniqueId={uniqueId}
randomId={randomId}
parsingConfig={parsingConfig} parsingConfig={parsingConfig}
/> />
</QueryClientProvider> </QueryClientProvider>
@@ -67,33 +63,32 @@ export const FlatUiTable: React.FC<FlatUiTableProps> = ({
}; };
const TableInner: React.FC<FlatUiTableProps> = ({ const TableInner: React.FC<FlatUiTableProps> = ({
url,
data, data,
rawCsv, uniqueId,
randomId,
bytes, bytes,
parsingConfig, parsingConfig,
}) => { }) => {
if (data) { const url = data.url;
const csv = data.csv;
const values = data.values;
if (values) {
return ( return (
<div className="w-full" style={{ height: '500px' }}> <div className="w-full" style={{ height: '500px' }}>
<Grid data={data} /> <Grid data={values} />
</div> </div>
); );
} }
const { data: csvString, isLoading: isDownloadingCSV } = useQuery( const { data: csvString, isLoading: isDownloadingCSV } = useQuery(
['dataCsv', url, randomId], ['dataCsv', url, uniqueId],
() => getCsv(url as string, bytes), () => getCsv(url as string, bytes),
{ enabled: !!url } { enabled: !!url }
); );
const { data: parsedData, isLoading: isParsing } = useQuery( const { data: parsedData, isLoading: isParsing } = useQuery(
['dataPreview', csvString, randomId], ['dataPreview', csvString, uniqueId],
() => () =>
parseCsv( parseCsv(csv ? (csv as string) : (csvString as string), parsingConfig),
rawCsv ? (rawCsv as string) : (csvString as string), { enabled: csv ? true : !!csvString }
parsingConfig
),
{ enabled: rawCsv ? true : !!csvString }
); );
if (isParsing || isDownloadingCSV) if (isParsing || isDownloadingCSV)
<div className="w-full flex justify-center items-center h-[500px]"> <div className="w-full flex justify-center items-center h-[500px]">

View File

@@ -1,14 +1,17 @@
import { CSSProperties } from "react"; import { CSSProperties } from 'react';
import { Data } from '../types/properties';
export interface IframeProps { export interface IframeProps {
url: string; data: Required<Pick<Data, 'url'>>;
style?: CSSProperties; style?: CSSProperties;
} }
export function Iframe({ export function Iframe({ data, style }: IframeProps) {
url, style const url = data.url;
}: IframeProps) {
return ( return (
<iframe src={url} style={style ?? { width: `100%`, height: `100%` }}></iframe> <iframe
src={url}
style={style ?? { width: `100%`, height: `600px` }}
></iframe>
); );
} }

View File

@@ -2,35 +2,40 @@ import { useEffect, useState } from 'react';
import LoadingSpinner from './LoadingSpinner'; import LoadingSpinner from './LoadingSpinner';
import { VegaLite } from './VegaLite'; import { VegaLite } from './VegaLite';
import loadData from '../lib/loadData'; import loadData from '../lib/loadData';
import { Data } from '../types/properties';
type AxisType = 'quantitative' | 'temporal'; type AxisType = 'quantitative' | 'temporal';
type TimeUnit = 'year' | undefined; // or ... type TimeUnit = 'year' | undefined; // or ...
export type LineChartProps = { export type LineChartProps = {
data: Array<Array<string | number>> | string | { x: string; y: number }[]; data: Omit<Data, 'csv'>;
title?: string; title?: string;
xAxis?: string; xAxis: string;
xAxisType?: AxisType; xAxisType?: AxisType;
xAxisTimeUnit: TimeUnit; xAxisTimeUnit?: TimeUnit;
yAxis?: string; yAxis: string | string[];
yAxisType?: AxisType; yAxisType?: AxisType;
fullWidth?: boolean; fullWidth?: boolean;
symbol?: string;
}; };
export function LineChart({ export function LineChart({
data = [], data,
fullWidth = false,
title = '', title = '',
xAxis = 'x', xAxis,
xAxisType = 'temporal', xAxisType = 'temporal',
xAxisTimeUnit = 'year', // TODO: defaults to undefined would probably work better... keeping it as it's for compatibility purposes xAxisTimeUnit = 'year', // TODO: defaults to undefined would probably work better... keeping it as it's for compatibility purposes
yAxis = 'y', yAxis,
yAxisType = 'quantitative', yAxisType = 'quantitative',
symbol,
}: LineChartProps) { }: LineChartProps) {
const url = data.url;
const values = data.values;
const [isLoading, setIsLoading] = useState<boolean>(false); const [isLoading, setIsLoading] = useState<boolean>(false);
// By default, assumes data is an Array... // By default, assumes data is an Array...
const [specData, setSpecData] = useState<any>({ name: 'table' }); const [specData, setSpecData] = useState<any>({ name: 'table' });
const isMultiYAxis = Array.isArray(yAxis);
const spec = { const spec = {
$schema: 'https://vega.github.io/schema/vega-lite/v5.json', $schema: 'https://vega.github.io/schema/vega-lite/v5.json',
@@ -44,6 +49,11 @@ export function LineChart({
tooltip: true, tooltip: true,
}, },
data: specData, data: specData,
...(isMultiYAxis
? {
transform: [{ fold: yAxis, as: ['key', 'value'] }],
}
: {}),
selection: { selection: {
grid: { grid: {
type: 'interval', type: 'interval',
@@ -57,20 +67,35 @@ export function LineChart({
type: xAxisType, type: xAxisType,
}, },
y: { y: {
field: yAxis, field: isMultiYAxis ? 'value' : yAxis,
type: yAxisType, type: yAxisType,
}, },
...(symbol
? {
color: {
field: symbol,
type: 'nominal',
},
}
: {}),
...(isMultiYAxis
? {
color: {
field: 'key',
type: 'nominal',
},
}
: {}),
}, },
} as any; } as any;
useEffect(() => { useEffect(() => {
// If data is string, assume it's a URL if (url) {
if (typeof data === 'string') {
setIsLoading(true); setIsLoading(true);
// Manualy loading the data allows us to do other kinds // Manualy loading the data allows us to do other kinds
// of stuff later e.g. load a file partially // of stuff later e.g. load a file partially
loadData(data).then((res: any) => { loadData(url).then((res: any) => {
setSpecData({ values: res, format: { type: 'csv' } }); setSpecData({ values: res, format: { type: 'csv' } });
setIsLoading(false); setIsLoading(false);
}); });
@@ -78,12 +103,8 @@ export function LineChart({
}, []); }, []);
var vegaData = {}; var vegaData = {};
if (Array.isArray(data)) { if (values) {
var dataObj; vegaData = { table: values };
dataObj = data.map((r) => {
return { x: r[0], y: r[1] };
});
vegaData = { table: dataObj };
} }
return isLoading ? ( return isLoading ? (
@@ -91,6 +112,6 @@ export function LineChart({
<LoadingSpinner /> <LoadingSpinner />
</div> </div>
) : ( ) : (
<VegaLite fullWidth={fullWidth} data={vegaData} spec={spec} /> <VegaLite data={vegaData} spec={spec} />
); );
} }

View File

@@ -2,6 +2,7 @@ import { CSSProperties, useEffect, useState } from 'react';
import LoadingSpinner from './LoadingSpinner'; import LoadingSpinner from './LoadingSpinner';
import loadData from '../lib/loadData'; import loadData from '../lib/loadData';
import chroma from 'chroma-js'; import chroma from 'chroma-js';
import { GeospatialData } from '../types/properties';
import { import {
MapContainer, MapContainer,
TileLayer, TileLayer,
@@ -11,32 +12,67 @@ import {
import 'leaflet/dist/leaflet.css'; import 'leaflet/dist/leaflet.css';
import * as L from 'leaflet'; import * as L from 'leaflet';
import providers from '../lib/tileLayerPresets';
type VariantKeys<T> = T extends { variants: infer V }
? {
[K in keyof V]: K extends string
? `${K}` | `${K}.${VariantKeys<V[K]>}`
: never;
}[keyof V]
: never;
type ProviderVariantKeys<T> = {
[K in keyof T]: K extends string
? `${K}` | `${K}.${VariantKeys<T[K]>}`
: never;
}[keyof T];
type TileLayerPreset = ProviderVariantKeys<typeof providers> | 'custom';
interface TileLayerSettings extends L.TileLayerOptions {
url?: string;
variant?: string | any;
}
export type MapProps = { export type MapProps = {
tileLayerName: TileLayerPreset;
tileLayerOptions?: TileLayerSettings | undefined;
layers: { layers: {
data: string | GeoJSON.GeoJSON; data: GeospatialData;
name: string; name: string;
colorScale?: { colorScale?: {
starting: string; starting: string;
ending: string; ending: string;
}; };
tooltip?: tooltip?:
| { | {
propNames: string[]; propNames: string[];
} }
| boolean; | boolean;
_id?: number;
}[]; }[];
title?: string; title?: string;
center?: { latitude: number | undefined; longitude: number | undefined }; center?: { latitude: number | undefined; longitude: number | undefined };
zoom?: number; zoom?: number;
style?: CSSProperties; style?: CSSProperties;
autoZoomConfiguration?: { autoZoomConfiguration?: {
layerName: string layerName: string;
} };
}; };
const tileLayerDefaultName = process?.env
.NEXT_PUBLIC_MAP_TILE_LAYER_NAME as TileLayerPreset;
const tileLayerDefaultOptions = Object.keys(process?.env)
.filter((key) => key.startsWith('NEXT_PUBLIC_MAP_TILE_LAYER_OPTION_'))
.reduce((obj, key) => {
obj[key.split('NEXT_PUBLIC_MAP_TILE_LAYER_OPTION_')[1]] = process.env[key];
return obj;
}, {}) as TileLayerSettings;
export function Map({ export function Map({
tileLayerName = tileLayerDefaultName || 'OpenStreetMap',
tileLayerOptions,
layers = [ layers = [
{ {
data: null, data: null,
@@ -54,19 +90,110 @@ export function Map({
const [isLoading, setIsLoading] = useState<boolean>(false); const [isLoading, setIsLoading] = useState<boolean>(false);
const [layersData, setLayersData] = useState<any>([]); const [layersData, setLayersData] = useState<any>([]);
/*
tileLayerDefaultOptions
extract all environment variables thats starts with NEXT_PUBLIC_MAP_TILE_LAYER_OPTION_.
the variables names are the same as the TileLayer object properties:
- NEXT_PUBLIC_MAP_TILE_LAYER_OPTION_url:
- NEXT_PUBLIC_MAP_TILE_LAYER_OPTION_attribution
- NEXT_PUBLIC_MAP_TILE_LAYER_OPTION_accessToken
- NEXT_PUBLIC_MAP_TILE_LAYER_OPTION_id
- NEXT_PUBLIC_MAP_TILE_LAYER_OPTION_ext
- NEXT_PUBLIC_MAP_TILE_LAYER_OPTION_bounds
- NEXT_PUBLIC_MAP_TILE_LAYER_OPTION_maxZoom
- NEXT_PUBLIC_MAP_TILE_LAYER_OPTION_minZoom
see TileLayerOptions inteface
*/
//tileLayerData prioritizes properties passed through component over those passed through .env variables
tileLayerOptions = Object.assign(tileLayerDefaultOptions, tileLayerOptions);
let provider = {
url: tileLayerOptions.url,
options: tileLayerOptions,
};
if (tileLayerName != 'custom') {
var parts = tileLayerName.split('.');
var providerName = parts[0];
var variantName: string = parts[1];
//make sure to declare a variant if url depends on a variant: assume first
if (providers[providerName].url?.includes('{variant}') && !variantName)
variantName = Object.keys(providers[providerName].variants)[0];
if (!providers[providerName]) {
throw 'No such provider (' + providerName + ')';
}
provider = {
url: providers[providerName].url,
options: providers[providerName].options,
};
// overwrite values in provider from variant.
if (variantName && 'variants' in providers[providerName]) {
if (!(variantName in providers[providerName].variants)) {
throw 'No such variant of ' + providerName + ' (' + variantName + ')';
}
var variant = providers[providerName].variants[variantName];
var variantOptions;
if (typeof variant === 'string') {
variantOptions = {
variant: variant,
};
} else {
variantOptions = variant.options;
}
provider = {
url: variant.url || provider.url,
options: L.Util.extend({}, provider.options, variantOptions),
};
}
var attributionReplacer = function (attr) {
if (attr.indexOf('{attribution.') === -1) {
return attr;
}
return attr.replace(
/\{attribution.(\w*)\}/g,
function (match: any, attributionName: string) {
match;
return attributionReplacer(
providers[attributionName].options.attribution
);
}
);
};
provider.options.attribution = attributionReplacer(
provider.options.attribution
);
}
var tileLayerData = L.Util.extend(
{
url: provider.url,
},
provider.options,
tileLayerOptions
);
useEffect(() => { useEffect(() => {
const loadDataPromises = layers.map(async (layer) => { const loadDataPromises = layers.map(async (layer) => {
const url = layer.data.url;
const geojson = layer.data.geojson;
let layerData: any; let layerData: any;
if (typeof layer.data === 'string') { if (url) {
// If "data" is string, assume it's a URL // If "data" is string, assume it's a URL
setIsLoading(true); setIsLoading(true);
layerData = await loadData(layer.data).then((res: any) => { layerData = await loadData(url).then((res: any) => {
return JSON.parse(res); return JSON.parse(res);
}); });
} else { } else {
// Else, expect raw GeoJSON // Else, expect raw GeoJSON
layerData = layer.data; layerData = geojson;
} }
if (layer.colorScale) { if (layer.colorScale) {
@@ -98,6 +225,7 @@ export function Map({
</div> </div>
) : ( ) : (
<MapContainer <MapContainer
key={layersData}
center={[center.latitude, center.longitude]} center={[center.latitude, center.longitude]}
zoom={zoom} zoom={zoom}
scrollWheelZoom={false} scrollWheelZoom={false}
@@ -111,23 +239,23 @@ export function Map({
// Create the title box // Create the title box
var info = new L.Control() as any; var info = new L.Control() as any;
info.onAdd = function() { info.onAdd = function () {
this._div = L.DomUtil.create('div', 'info'); this._div = L.DomUtil.create('div', 'info');
this.update(); this.update();
return this._div; return this._div;
}; };
info.update = function() { info.update = function () {
this._div.innerHTML = `<h4 style="font-weight: 600; background: #f9f9f9; padding: 5px; border-radius: 5px; color: #464646;">${title}</h4>`; this._div.innerHTML = `<h4 style="font-weight: 600; background: #f9f9f9; padding: 5px; border-radius: 5px; color: #464646;">${title}</h4>`;
}; };
if (title) info.addTo(map.target); if (title) info.addTo(map.target);
if(!autoZoomConfiguration) return; if (!autoZoomConfiguration) return;
let layerToZoomBounds = L.latLngBounds(L.latLng(0, 0), L.latLng(0, 0)); let layerToZoomBounds = L.latLngBounds(L.latLng(0, 0), L.latLng(0, 0));
layers.forEach((layer) => { layers.forEach((layer) => {
if(layer.name === autoZoomConfiguration.layerName) { if (layer.name === autoZoomConfiguration.layerName) {
const data = layersData.find( const data = layersData.find(
(layerData) => layerData.name === layer.name (layerData) => layerData.name === layer.name
)?.data; )?.data;
@@ -142,10 +270,8 @@ export function Map({
map.target.fitBounds(layerToZoomBounds); map.target.fitBounds(layerToZoomBounds);
}} }}
> >
<TileLayer {tileLayerData.url && <TileLayer {...tileLayerData} />}
attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
<LayersControl position="bottomright"> <LayersControl position="bottomright">
{layers.map((layer) => { {layers.map((layer) => {
const data = layersData.find( const data = layersData.find(

View File

@@ -1,22 +1,24 @@
// Core viewer // Core viewer
import { Viewer, Worker, SpecialZoomLevel } from '@react-pdf-viewer/core'; import { Viewer, Worker, SpecialZoomLevel } from '@react-pdf-viewer/core';
import { defaultLayoutPlugin } from '@react-pdf-viewer/default-layout'; import { defaultLayoutPlugin } from '@react-pdf-viewer/default-layout';
import { Data } from '../types/properties';
// Import styles // Import styles
import '@react-pdf-viewer/core/lib/styles/index.css'; import '@react-pdf-viewer/core/lib/styles/index.css';
import '@react-pdf-viewer/default-layout/lib/styles/index.css'; import '@react-pdf-viewer/default-layout/lib/styles/index.css';
export interface PdfViewerProps { export interface PdfViewerProps {
url: string; data: Required<Pick<Data, 'url'>>;
layout: boolean; layout: boolean;
parentClassName?: string; parentClassName?: string;
} }
export function PdfViewer({ export function PdfViewer({
url, data,
layout = false, layout = false,
parentClassName, parentClassName = 'h-screen',
}: PdfViewerProps) { }: PdfViewerProps) {
const url = data.url;
const defaultLayoutPluginInstance = defaultLayoutPlugin(); const defaultLayoutPluginInstance = defaultLayoutPlugin();
return ( return (
<Worker workerUrl="https://unpkg.com/pdfjs-dist@2.15.349/build/pdf.worker.js"> <Worker workerUrl="https://unpkg.com/pdfjs-dist@2.15.349/build/pdf.worker.js">

View File

@@ -1,7 +1,8 @@
import { QueryClient, QueryClientProvider, useQuery } from "react-query"; import { QueryClient, QueryClientProvider, useQuery } from 'react-query';
import { Plotly } from "./Plotly"; import { Plotly } from './Plotly';
import Papa, { ParseConfig } from "papaparse"; import Papa, { ParseConfig } from 'papaparse';
import LoadingSpinner from "./LoadingSpinner"; import LoadingSpinner from './LoadingSpinner';
import { Data } from '../types/properties';
const queryClient = new QueryClient(); const queryClient = new QueryClient();
@@ -17,7 +18,7 @@ async function getCsv(url: string, bytes: number) {
async function parseCsv( async function parseCsv(
file: string, file: string,
parsingConfig: ParseConfig, parsingConfig: ParseConfig
): Promise<any> { ): Promise<any> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
Papa.parse(file, { Papa.parse(file, {
@@ -39,43 +40,40 @@ async function parseCsv(
} }
export interface PlotlyBarChartProps { export interface PlotlyBarChartProps {
url?: string; data: Data;
data?: { [key: string]: number | string }[]; uniqueId?: number;
rawCsv?: string;
randomId?: number;
bytes?: number; bytes?: number;
parsingConfig?: ParseConfig; parsingConfig?: ParseConfig;
xAxis: string; xAxis: string;
yAxis: string; yAxis: string;
lineLabel?: string; // TODO: commented out because this doesn't work. I believe
// this would only make any difference on charts with multiple
// traces.
// lineLabel?: string;
title?: string; title?: string;
} }
export const PlotlyBarChart: React.FC<PlotlyBarChartProps> = ({ export const PlotlyBarChart: React.FC<PlotlyBarChartProps> = ({
url,
data, data,
rawCsv,
bytes = 5132288, bytes = 5132288,
parsingConfig = {}, parsingConfig = {},
xAxis, xAxis,
yAxis, yAxis,
lineLabel, // lineLabel,
title = "", title = '',
}) => { }) => {
const randomId = Math.random(); const uniqueId = Math.random();
return ( return (
// Provide the client to your App // Provide the client to your App
<QueryClientProvider client={queryClient}> <QueryClientProvider client={queryClient}>
<PlotlyBarChartInner <PlotlyBarChartInner
url={url}
data={data} data={data}
rawCsv={rawCsv} uniqueId={uniqueId}
randomId={randomId}
bytes={bytes} bytes={bytes}
parsingConfig={parsingConfig} parsingConfig={parsingConfig}
xAxis={xAxis} xAxis={xAxis}
yAxis={yAxis} yAxis={yAxis}
lineLabel={lineLabel ?? yAxis} // lineLabel={lineLabel ?? yAxis}
title={title} title={title}
/> />
</QueryClientProvider> </QueryClientProvider>
@@ -83,30 +81,28 @@ export const PlotlyBarChart: React.FC<PlotlyBarChartProps> = ({
}; };
const PlotlyBarChartInner: React.FC<PlotlyBarChartProps> = ({ const PlotlyBarChartInner: React.FC<PlotlyBarChartProps> = ({
url,
data, data,
rawCsv, uniqueId,
randomId,
bytes, bytes,
parsingConfig, parsingConfig,
xAxis, xAxis,
yAxis, yAxis,
lineLabel, // lineLabel,
title, title,
}) => { }) => {
if (data) { if (data.values) {
return ( return (
<div className="w-full" style={{ height: "500px" }}> <div className="w-full" style={{ height: '500px' }}>
<Plotly <Plotly
layout={{ layout={{
title, title,
}} }}
data={[ data={[
{ {
x: data.map((d) => d[xAxis]), x: data.values.map((d) => d[xAxis]),
y: data.map((d) => d[yAxis]), y: data.values.map((d) => d[yAxis]),
type: "bar", type: 'bar',
name: lineLabel, // name: lineLabel,
}, },
]} ]}
/> />
@@ -114,18 +110,18 @@ const PlotlyBarChartInner: React.FC<PlotlyBarChartProps> = ({
); );
} }
const { data: csvString, isLoading: isDownloadingCSV } = useQuery( const { data: csvString, isLoading: isDownloadingCSV } = useQuery(
["dataCsv", url, randomId], ['dataCsv', data.url, uniqueId],
() => getCsv(url as string, bytes ?? 5132288), () => getCsv(data.url as string, bytes ?? 5132288),
{ enabled: !!url }, { enabled: !!data.url }
); );
const { data: parsedData, isLoading: isParsing } = useQuery( const { data: parsedData, isLoading: isParsing } = useQuery(
["dataPreview", csvString, randomId], ['dataPreview', csvString, uniqueId],
() => () =>
parseCsv( parseCsv(
rawCsv ? (rawCsv as string) : (csvString as string), data.csv ? (data.csv as string) : (csvString as string),
parsingConfig ?? {}, parsingConfig ?? {}
), ),
{ enabled: rawCsv ? true : !!csvString }, { enabled: data.csv ? true : !!csvString }
); );
if (isParsing || isDownloadingCSV) if (isParsing || isDownloadingCSV)
<div className="w-full flex justify-center items-center h-[500px]"> <div className="w-full flex justify-center items-center h-[500px]">
@@ -133,7 +129,7 @@ const PlotlyBarChartInner: React.FC<PlotlyBarChartProps> = ({
</div>; </div>;
if (parsedData) if (parsedData)
return ( return (
<div className="w-full" style={{ height: "500px" }}> <div className="w-full" style={{ height: '500px' }}>
<Plotly <Plotly
layout={{ layout={{
title, title,
@@ -142,8 +138,8 @@ const PlotlyBarChartInner: React.FC<PlotlyBarChartProps> = ({
{ {
x: parsedData.data.map((d: any) => d[xAxis]), x: parsedData.data.map((d: any) => d[xAxis]),
y: parsedData.data.map((d: any) => d[yAxis]), y: parsedData.data.map((d: any) => d[yAxis]),
type: "bar", type: 'bar',
name: lineLabel, // name: lineLabel, TODO: commented out because this doesn't work
}, },
]} ]}
/> />

View File

@@ -1,7 +1,8 @@
import { QueryClient, QueryClientProvider, useQuery } from "react-query"; import { QueryClient, QueryClientProvider, useQuery } from 'react-query';
import { Plotly } from "./Plotly"; import { Plotly } from './Plotly';
import Papa, { ParseConfig } from "papaparse"; import Papa, { ParseConfig } from 'papaparse';
import LoadingSpinner from "./LoadingSpinner"; import LoadingSpinner from './LoadingSpinner';
import { Data } from '../types/properties';
const queryClient = new QueryClient(); const queryClient = new QueryClient();
@@ -17,7 +18,7 @@ async function getCsv(url: string, bytes: number) {
async function parseCsv( async function parseCsv(
file: string, file: string,
parsingConfig: ParseConfig, parsingConfig: ParseConfig
): Promise<any> { ): Promise<any> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
Papa.parse(file, { Papa.parse(file, {
@@ -39,38 +40,33 @@ async function parseCsv(
} }
export interface PlotlyLineChartProps { export interface PlotlyLineChartProps {
url?: string; data: Data;
data?: { [key: string]: number | string }[];
rawCsv?: string;
randomId?: number;
bytes?: number; bytes?: number;
parsingConfig?: ParseConfig; parsingConfig?: ParseConfig;
xAxis: string; xAxis: string;
yAxis: string; yAxis: string;
lineLabel?: string; lineLabel?: string;
title?: string; title?: string;
uniqueId?: number;
} }
export const PlotlyLineChart: React.FC<PlotlyLineChartProps> = ({ export const PlotlyLineChart: React.FC<PlotlyLineChartProps> = ({
url,
data, data,
rawCsv,
bytes = 5132288, bytes = 5132288,
parsingConfig = {}, parsingConfig = {},
xAxis, xAxis,
yAxis, yAxis,
lineLabel, lineLabel,
title = "", title = '',
uniqueId,
}) => { }) => {
const randomId = Math.random(); uniqueId = uniqueId ?? Math.random();
return ( return (
// Provide the client to your App // Provide the client to your App
<QueryClientProvider client={queryClient}> <QueryClientProvider client={queryClient}>
<LineChartInner <LineChartInner
url={url}
data={data} data={data}
rawCsv={rawCsv} uniqueId={uniqueId}
randomId={randomId}
bytes={bytes} bytes={bytes}
parsingConfig={parsingConfig} parsingConfig={parsingConfig}
xAxis={xAxis} xAxis={xAxis}
@@ -83,10 +79,8 @@ export const PlotlyLineChart: React.FC<PlotlyLineChartProps> = ({
}; };
const LineChartInner: React.FC<PlotlyLineChartProps> = ({ const LineChartInner: React.FC<PlotlyLineChartProps> = ({
url,
data, data,
rawCsv, uniqueId,
randomId,
bytes, bytes,
parsingConfig, parsingConfig,
xAxis, xAxis,
@@ -94,18 +88,22 @@ const LineChartInner: React.FC<PlotlyLineChartProps> = ({
lineLabel, lineLabel,
title, title,
}) => { }) => {
if (data) { const values = data.values;
const url = data.url;
const csv = data.csv;
if (values) {
return ( return (
<div className="w-full" style={{ height: "500px" }}> <div className="w-full" style={{ height: '500px' }}>
<Plotly <Plotly
layout={{ layout={{
title, title,
}} }}
data={[ data={[
{ {
x: data.map((d) => d[xAxis]), x: values.map((d) => d[xAxis]),
y: data.map((d) => d[yAxis]), y: values.map((d) => d[yAxis]),
mode: "lines", mode: 'lines',
name: lineLabel, name: lineLabel,
}, },
]} ]}
@@ -114,18 +112,18 @@ const LineChartInner: React.FC<PlotlyLineChartProps> = ({
); );
} }
const { data: csvString, isLoading: isDownloadingCSV } = useQuery( const { data: csvString, isLoading: isDownloadingCSV } = useQuery(
["dataCsv", url, randomId], ['dataCsv', url, uniqueId],
() => getCsv(url as string, bytes ?? 5132288), () => getCsv(url as string, bytes ?? 5132288),
{ enabled: !!url }, { enabled: !!url }
); );
const { data: parsedData, isLoading: isParsing } = useQuery( const { data: parsedData, isLoading: isParsing } = useQuery(
["dataPreview", csvString, randomId], ['dataPreview', csvString, uniqueId],
() => () =>
parseCsv( parseCsv(
rawCsv ? (rawCsv as string) : (csvString as string), csv ? (csv as string) : (csvString as string),
parsingConfig ?? {}, parsingConfig ?? {}
), ),
{ enabled: rawCsv ? true : !!csvString }, { enabled: csv ? true : !!csvString }
); );
if (isParsing || isDownloadingCSV) if (isParsing || isDownloadingCSV)
<div className="w-full flex justify-center items-center h-[500px]"> <div className="w-full flex justify-center items-center h-[500px]">
@@ -133,7 +131,7 @@ const LineChartInner: React.FC<PlotlyLineChartProps> = ({
</div>; </div>;
if (parsedData) if (parsedData)
return ( return (
<div className="w-full" style={{ height: "500px" }}> <div className="w-full" style={{ height: '500px' }}>
<Plotly <Plotly
layout={{ layout={{
title, title,
@@ -142,7 +140,7 @@ const LineChartInner: React.FC<PlotlyLineChartProps> = ({
{ {
x: parsedData.data.map((d: any) => d[xAxis]), x: parsedData.data.map((d: any) => d[xAxis]),
y: parsedData.data.map((d: any) => d[yAxis]), y: parsedData.data.map((d: any) => d[yAxis]),
mode: "lines", mode: 'lines',
name: lineLabel, name: lineLabel,
}, },
]} ]}

View File

@@ -1,6 +1,7 @@
// 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) { export function Vega(props: VegaProps) {
return <VegaOg {...props} />; return <VegaOg {...props} />;
} }

View File

@@ -1,8 +1,9 @@
// Wrapper for the Vega Lite component // Wrapper for the Vega Lite component
import { VegaLite as VegaLiteOg } from "react-vega"; import { VegaLite as VegaLiteOg } from 'react-vega';
import applyFullWidthDirective from "../lib/applyFullWidthDirective"; import { VegaLiteProps } from 'react-vega/lib/VegaLite';
import applyFullWidthDirective from '../lib/applyFullWidthDirective';
export function VegaLite(props) { export function VegaLite(props: VegaLiteProps) {
const Component = applyFullWidthDirective({ Component: VegaLiteOg }); const Component = applyFullWidthDirective({ Component: VegaLiteOg });
return <Component {...props} />; return <Component {...props} />;

View File

@@ -1,15 +1,17 @@
export * from './components/Table';
export * from './components/Catalog'; export * from './components/Catalog';
export * from './components/LineChart'; export * from './components/LineChart';
export * from './components/Vega'; export * from './components/Vega';
export * from './components/VegaLite'; export * from './components/VegaLite';
export * from './components/FlatUiTable'; export * from './components/FlatUiTable';
export * from './components/OpenLayers/OpenLayers';
export * from './components/Map'; export * from './components/Map';
export * from './components/PdfViewer'; export * from './components/PdfViewer';
export * from "./components/Excel"; export * from "./components/Excel";
export * from "./components/BucketViewer";
export * from "./components/Iframe"; export * from "./components/Iframe";
export * from "./components/Plotly"; export * from "./components/Plotly";
export * from "./components/PlotlyLineChart"; export * from "./components/PlotlyLineChart";
export * from "./components/PlotlyBarChart"; export * from "./components/PlotlyBarChart";
// NOTE: components that are hidden for now
// TODO: deprecate those components?
// export * from './components/Table';
// export * from "./components/BucketViewer";
// export * from './components/OpenLayers/OpenLayers';

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,18 @@
/*
* All components should use this interface for
* its data property.
* Based on vega.
*
*/
type URL = string; // Just in case we want to transform it into an object with configurations
export interface Data {
url?: URL;
values?: { [key: string]: number | string }[];
csv?: string;
}
export interface GeospatialData {
url?: URL;
geojson?: GeoJSON.GeoJSON;
}

View File

@@ -1,74 +0,0 @@
import type { Meta, StoryObj } from '@storybook/react';
import { PlotlyBarChart, PlotlyBarChartProps } from '../src/components/PlotlyBarChart';
// More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction
const meta: Meta = {
title: 'Components/PlotlyBarChart',
component: PlotlyBarChart,
tags: ['autodocs'],
argTypes: {
url: {
description:
'CSV Url to be parsed and used as data source',
},
data: {
description:
'Data to be displayed. as an array of key value pairs \n\n E.g.: [{ year: 1850, temperature: -0.41765878 }, { year: 1851, temperature: -0.2333498 }, ...]',
},
rawCsv: {
description:
'Raw csv data to be parsed and used as data source',
},
bytes: {
description:
'How many bytes to read from the url',
},
parsingConfig: {
description: 'If using url or rawCsv, this parsing config will be used to parse the data. Optional, check https://www.papaparse.com/ for more info',
},
title: {
description: 'Title to display on the chart. Optional.',
},
lineLabel: {
description: 'Label to display on the line, Optional, will use yAxis if not provided',
},
xAxis: {
description:
'Name of the X axis on the data. Required when the "data" parameter is an URL.',
},
yAxis: {
description:
'Name of the Y axis on the data. Required when the "data" parameter is an URL.',
},
},
};
export default meta;
type Story = StoryObj<PlotlyBarChartProps>;
export const FromDataPoints: Story = {
name: 'Line chart from array of data points',
args: {
data: [
{year: '1850', temperature: -0.41765878},
{year: '1851', temperature: -0.2333498},
{year: '1852', temperature: -0.22939907},
{year: '1853', temperature: -0.27035445},
{year: '1854', temperature: -0.29163003},
],
xAxis: 'year',
yAxis: 'temperature',
},
};
export const FromURL: Story = {
name: 'Line chart from URL',
args: {
title: 'Apple Stock Prices',
url: 'https://raw.githubusercontent.com/plotly/datasets/master/finance-charts-apple.csv',
xAxis: 'Date',
yAxis: 'AAPL.Open',
},
};

View File

@@ -1,3 +1,6 @@
// NOTE: this component was renamed with .bkp so that it's hidden
// from the Storybook app
import { type Meta, type StoryObj } from '@storybook/react'; import { type Meta, type StoryObj } from '@storybook/react';
import { import {

View File

@@ -10,11 +10,14 @@ const meta: Meta = {
argTypes: { argTypes: {
datasets: { datasets: {
description: description:
'Lists of datasets to be displayed in the list, will usually be automatically available', "Array of items to be displayed on the searchable list. Must have the following properties: \n\n \
`_id`: item's unique id \n\n \
`url_path`: href of the item \n\n \
`metadata`: object with a `title` property, that will be displayed as the title of the item, together with any other custom fields that might or not be faceted.",
}, },
facets: { facets: {
description: description:
'List of frontmatter fields that should be used as filters, needs to match exactly with the field name', "Array of strings, which are name of properties in the datasets' `metadata`, which are going to be faceted.",
}, },
}, },
}; };
@@ -31,99 +34,35 @@ export const WithoutFacets: Story = {
{ {
_id: '07026b22d49916754df1dc8ffb9ccd1c31878aae', _id: '07026b22d49916754df1dc8ffb9ccd1c31878aae',
url_path: 'dataset-4', url_path: 'dataset-4',
file_path: 'content/dataset-4/index.md',
metadata: { metadata: {
title: 'Detecting Abusive Albanian', title: 'Detecting Abusive Albanian',
'link-to-publication': 'https://arxiv.org/abs/2107.13592',
'link-to-data': 'https://doi.org/10.6084/m9.figshare.19333298.v1',
'task-description':
'Hierarchical (offensive/not; untargeted/targeted; person/group/other)',
'details-of-task':
'Detect and categorise abusive language in social media data',
'size-of-dataset': 11874,
'percentage-abusive': 13.2,
language: 'Albanian',
'level-of-annotation': ['Posts'],
platform: ['Instagram', 'Youtube'],
medium: ['Text'],
reference:
'Nurce, E., Keci, J., Derczynski, L., 2021. Detecting Abusive Albanian. arXiv:2107.13592',
}, },
}, },
{ {
_id: '42c86cf3c4fbbab11d91c2a7d6dcb8f750bc4e19', _id: '42c86cf3c4fbbab11d91c2a7d6dcb8f750bc4e19',
url_path: 'dataset-1', url_path: 'dataset-1',
file_path: 'content/dataset-1/index.md',
metadata: { metadata: {
title: 'AbuseEval v1.0', title: 'AbuseEval v1.0',
'link-to-publication':
'http://www.lrec-conf.org/proceedings/lrec2020/pdf/2020.lrec-1.760.pdf',
'link-to-data': 'https://github.com/tommasoc80/AbuseEval',
'task-description':
'Explicitness annotation of offensive and abusive content',
'details-of-task':
'Enriched versions of the OffensEval/OLID dataset with the distinction of explicit/implicit offensive messages and the new dimension for abusive messages. Labels for offensive language: EXPLICIT, IMPLICT, NOT; Labels for abusive language: EXPLICIT, IMPLICT, NOTABU',
'size-of-dataset': 14100,
'percentage-abusive': 20.75,
language: 'English',
'level-of-annotation': ['Tweets'],
platform: ['Twitter'],
medium: ['Text'],
reference:
'Caselli, T., Basile, V., Jelena, M., Inga, K., and Michael, G. 2020. "I feel offended, dont be abusive! implicit/explicit messages in offensive and abusive language". The 12th Language Resources and Evaluation Conference (pp. 6193-6202). European Language Resources Association.',
}, },
}, },
{ {
_id: '80001dd32a752421fdcc64e91fbd237dc31d6bb3', _id: '80001dd32a752421fdcc64e91fbd237dc31d6bb3',
url_path: 'dataset-2', url_path: 'dataset-2',
file_path: 'content/dataset-2/index.md',
metadata: { metadata: {
title: title:
'Abusive Language Detection on Arabic Social Media (Al Jazeera)', 'Abusive Language Detection on Arabic Social Media (Al Jazeera)',
'link-to-publication': 'https://www.aclweb.org/anthology/W17-3008',
'link-to-data':
'http://alt.qcri.org/~hmubarak/offensive/AJCommentsClassification-CF.xlsx',
'task-description':
'Ternary (Obscene, Offensive but not obscene, Clean)',
'details-of-task': 'Incivility',
'size-of-dataset': 32000,
'percentage-abusive': 0.81,
language: 'Arabic',
'level-of-annotation': ['Posts'],
platform: ['AlJazeera'],
medium: ['Text'],
reference:
'Mubarak, H., Darwish, K. and Magdy, W., 2017. Abusive Language Detection on Arabic Social Media. In: Proceedings of the First Workshop on Abusive Language Online. Vancouver, Canada: Association for Computational Linguistics, pp.52-56.',
}, },
}, },
{ {
_id: '96649d05d8193f4333b10015af76c6562971bd8c', _id: '96649d05d8193f4333b10015af76c6562971bd8c',
url_path: 'dataset-3', url_path: 'dataset-3',
file_path: 'content/dataset-3/index.md',
metadata: { metadata: {
title: 'CoRAL: a Context-aware Croatian Abusive Language Dataset', title: 'CoRAL: a Context-aware Croatian Abusive Language Dataset',
'link-to-publication':
'https://aclanthology.org/2022.findings-aacl.21/',
'link-to-data':
'https://github.com/shekharRavi/CoRAL-dataset-Findings-of-the-ACL-AACL-IJCNLP-2022',
'task-description':
'Multi-class based on context dependency categories (CDC)',
'details-of-task': 'Detectioning CDC from abusive comments',
'size-of-dataset': 2240,
'percentage-abusive': 100,
language: 'Croatian',
'level-of-annotation': ['Posts'],
platform: ['Posts'],
medium: ['Newspaper Comments'],
reference:
'Ravi Shekhar, Mladen Karan and Matthew Purver (2022). CoRAL: a Context-aware Croatian Abusive Language Dataset. Findings of the ACL: AACL-IJCNLP.',
}, },
}, },
], ],
}, },
}; };
;
export const WithFacets: Story = { export const WithFacets: Story = {
name: 'Catalog with facets', name: 'Catalog with facets',
args: { args: {
@@ -131,7 +70,6 @@ export const WithFacets: Story = {
{ {
_id: '07026b22d49916754df1dc8ffb9ccd1c31878aae', _id: '07026b22d49916754df1dc8ffb9ccd1c31878aae',
url_path: 'dataset-4', url_path: 'dataset-4',
file_path: 'content/dataset-4/index.md',
metadata: { metadata: {
title: 'Detecting Abusive Albanian', title: 'Detecting Abusive Albanian',
'link-to-publication': 'https://arxiv.org/abs/2107.13592', 'link-to-publication': 'https://arxiv.org/abs/2107.13592',
@@ -220,7 +158,6 @@ export const WithFacets: Story = {
}, },
}, },
], ],
facets: ['language', 'platform'] facets: ['language', 'platform'],
}, },
}; };
;

View File

@@ -4,13 +4,13 @@ import { Excel, ExcelProps } from '../src/components/Excel';
// More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction // More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction
const meta: Meta = { const meta: Meta = {
title: 'Components/Excel', title: 'Components/Tabular/Excel',
component: Excel, component: Excel,
tags: ['autodocs'], tags: ['autodocs'],
argTypes: { argTypes: {
url: { data: {
description: description:
'Url of the file to be displayed e.g.: "https://url.to/data.csv"', 'Object with a `url` property pointing to the Excel file to be displayed, e.g.: `{ url: "https://url.to/data.csv" }`',
}, },
}, },
}; };
@@ -22,13 +22,17 @@ type Story = StoryObj<ExcelProps>;
export const SingleSheet: Story = { export const SingleSheet: Story = {
name: 'Excel file with just one sheet', name: 'Excel file with just one sheet',
args: { args: {
url: 'https://sheetjs.com/pres.xlsx', data: {
url: 'https://sheetjs.com/pres.xlsx',
},
}, },
}; };
export const MultipleSheet: Story = { export const MultipleSheet: Story = {
name: 'Excel file with multiple sheets', name: 'Excel file with multiple sheets',
args: { args: {
url: 'https://storage.portaljs.org/IC-Gantt-Chart-Project-Template-8857.xlsx', data: {
url: 'https://storage.portaljs.org/IC-Gantt-Chart-Project-Template-8857.xlsx',
},
}, },
}; };

View File

@@ -4,29 +4,31 @@ import { FlatUiTable, FlatUiTableProps } from '../src/components/FlatUiTable';
// More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction // More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction
const meta: Meta = { const meta: Meta = {
title: 'Components/FlatUiTable', title: 'Components/Tabular/FlatUiTable',
component: FlatUiTable, component: FlatUiTable,
tags: ['autodocs'], tags: ['autodocs'],
argTypes: { argTypes: {
data: { data: {
description: description:
'Data to be displayed in the table, must be setup as an array of key value pairs', 'Data to be displayed. \n\n \
}, Must be an object with one of the following properties: `url`, `values` or `csv` \n\n \
csv: { `url`: URL pointing to a CSV file. \n\n \
description: 'CSV data as string.', `values`: array of objects. \n\n \
}, `csv`: string with valid CSV. \n\n \
url: { ',
description:
'Fetch the data from a CSV file remotely. only the first 5MB of data will be displayed',
}, },
bytes: { bytes: {
description: description:
'Fetch the data from a CSV file remotely. only the first <bytes> of data will be displayed', 'Fetch the data from a CSV file remotely. Only the first <bytes> of data will be displayed. Defaults to 5MB.',
}, },
parsingConfig: { parsingConfig: {
description: description:
'Configuration for parsing the CSV data. See https://www.papaparse.com/docs#config for more details', 'Configuration for parsing the CSV data. See https://www.papaparse.com/docs#config for more details',
}, },
uniqueId: {
description:
'Provide a unique ID to help with cache revalidation of the fetched data.',
},
}, },
}; };
@@ -36,34 +38,40 @@ type Story = StoryObj<FlatUiTableProps>;
// More on writing stories with args: https://storybook.js.org/docs/react/writing-stories/args // More on writing stories with args: https://storybook.js.org/docs/react/writing-stories/args
export const FromColumnsAndData: Story = { export const FromColumnsAndData: Story = {
name: 'Table data', name: 'Table from array or objects',
args: { args: {
data: [ data: {
{ id: 1, lastName: 'Snow', firstName: 'Jon', age: 35 }, values: [
{ id: 2, lastName: 'Lannister', firstName: 'Cersei', age: 42 }, { id: 1, lastName: 'Snow', firstName: 'Jon', age: 35 },
{ id: 3, lastName: 'Lannister', firstName: 'Jaime', age: 45 }, { id: 2, lastName: 'Lannister', firstName: 'Cersei', age: 42 },
{ id: 4, lastName: 'Stark', firstName: 'Arya', age: 16 }, { id: 3, lastName: 'Lannister', firstName: 'Jaime', age: 45 },
{ id: 7, lastName: 'Clifford', firstName: 'Ferrara', age: 44 }, { id: 4, lastName: 'Stark', firstName: 'Arya', age: 16 },
{ id: 8, lastName: 'Frances', firstName: 'Rossini', age: 36 }, { id: 7, lastName: 'Clifford', firstName: 'Ferrara', age: 44 },
{ id: 9, lastName: 'Roxie', firstName: 'Harvey', age: 65 }, { id: 8, lastName: 'Frances', firstName: 'Rossini', age: 36 },
], { id: 9, lastName: 'Roxie', firstName: 'Harvey', age: 65 },
],
},
}, },
}; };
export const FromRawCSV: Story = { export const FromRawCSV: Story = {
name: 'Table from raw CSV', name: 'Table from inline CSV',
args: { args: {
rawCsv: ` data: {
csv: `
Year,Temp Anomaly Year,Temp Anomaly
1850,-0.418 1850,-0.418
2020,0.923 2020,0.923
`, `,
},
}, },
}; };
export const FromURL: Story = { export const FromURL: Story = {
name: 'Table from URL', name: 'Table from URL',
args: { args: {
url: 'https://storage.openspending.org/alberta-budget/__os_imported__alberta_total.csv', data: {
url: 'https://storage.openspending.org/alberta-budget/__os_imported__alberta_total.csv',
},
}, },
}; };

View File

@@ -3,17 +3,17 @@ import { type Meta, type StoryObj } from '@storybook/react';
import { Iframe, IframeProps } from '../src/components/Iframe'; import { Iframe, IframeProps } from '../src/components/Iframe';
const meta: Meta = { const meta: Meta = {
title: 'Components/Iframe', title: 'Components/Embedding/Iframe',
component: Iframe, component: Iframe,
tags: ['autodocs'], tags: ['autodocs'],
argTypes: { argTypes: {
url: { data: {
description: description:
'Page to display inside of the component', 'Object with a `url` property pointing to the page to be embeded.',
}, },
style: { style: {
description: description:
'Style of the component', 'Style object of the component. See example at https://react.dev/learn#displaying-data. Defaults to `{ width: "100%", height: "100%" }`',
}, },
}, },
}; };
@@ -25,7 +25,9 @@ type Story = StoryObj<IframeProps>;
export const Normal: Story = { export const Normal: Story = {
name: 'Iframe', name: 'Iframe',
args: { args: {
url: 'https://app.powerbi.com/view?r=eyJrIjoiYzBmN2Q2MzYtYzE3MS00ODkxLWE5OWMtZTQ2MjBlMDljMDk4IiwidCI6Ijk1M2IwZjgzLTFjZTYtNDVjMy04MmM5LTFkODQ3ZTM3MjMzOSIsImMiOjh9', data: {
style: {width: `100%`, height: `100%`} url: 'https://app.powerbi.com/view?r=eyJrIjoiYzBmN2Q2MzYtYzE3MS00ODkxLWE5OWMtZTQ2MjBlMDljMDk4IiwidCI6Ijk1M2IwZjgzLTFjZTYtNDVjMy04MmM5LTFkODQ3ZTM3MjMzOSIsImMiOjh9',
},
style: { width: `100%`, height: `600px` },
}, },
}; };

View File

@@ -4,37 +4,40 @@ import { LineChart, LineChartProps } from '../src/components/LineChart';
// More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction // More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction
const meta: Meta = { const meta: Meta = {
title: 'Components/LineChart', title: 'Components/Charts/LineChart',
component: LineChart, component: LineChart,
tags: ['autodocs'], tags: ['autodocs'],
argTypes: { argTypes: {
data: { data: {
description: description:
'Data to be displayed.\n\n E.g.: [["1990", 1], ["1991", 2]] \n\nOR\n\n "https://url.to/data.csv"', 'Data to be displayed. \n\n \
Must be an object with one of the following properties: `url` or `values` \n\n \
`url`: URL pointing to a CSV file. \n\n \
`values`: array of objects \n\n',
}, },
title: { title: {
description: 'Title to display on the chart. Optional.', description: 'Title to display on the chart.',
}, },
xAxis: { xAxis: {
description: description:
'Name of the X axis on the data. Required when the "data" parameter is an URL.', 'Name of the column header or object property that represents the X-axis on the data.',
}, },
xAxisType: { xAxisType: {
description: 'Type of the X axis', description: 'Type of the X-axis.',
}, },
xAxisTimeUnit: { xAxisTimeUnit: {
description: 'Time unit of the X axis (optional)', description: 'Time unit of the X-axis, in case its type is `temporal.`',
}, },
yAxis: { yAxis: {
description: description:
'Name of the Y axis on the data. Required when the "data" parameter is an URL.', 'Name of the column headers or object properties that represent the Y-axis on the data.',
}, },
yAxisType: { yAxisType: {
description: 'Type of the Y axis', description: 'Type of the Y-axis',
}, },
fullWidth: { symbol: {
description: description:
'Whether the component should be rendered as full bleed or not', 'Name of the column header or object property that represents a series for multiple series.',
}, },
}, },
}; };
@@ -47,21 +50,72 @@ type Story = StoryObj<LineChartProps>;
export const FromDataPoints: Story = { export const FromDataPoints: Story = {
name: 'Line chart from array of data points', name: 'Line chart from array of data points',
args: { args: {
data: [ data: {
['1850', -0.41765878], values: [
['1851', -0.2333498], { year: '1850', value: -0.41765878 },
['1852', -0.22939907], { year: '1851', value: -0.2333498 },
['1853', -0.27035445], { year: '1852', value: -0.22939907 },
['1854', -0.29163003], { year: '1853', value: -0.27035445 },
], { year: '1854', value: -0.29163003 },
],
},
xAxis: 'year',
yAxis: 'value',
},
};
export const MultiSeries: Story = {
name: 'Line chart with multiple series (specifying symbol)',
args: {
data: {
values: [
{ year: '1850', value: -0.41765878, z: 'A' },
{ year: '1851', value: -0.2333498, z: 'A' },
{ year: '1852', value: -0.22939907, z: 'A' },
{ year: '1853', value: -0.27035445, z: 'A' },
{ year: '1854', value: -0.29163003, z: 'A' },
{ year: '1850', value: -0.42993882, z: 'B' },
{ year: '1851', value: -0.30365549, z: 'B' },
{ year: '1852', value: -0.27905189, z: 'B' },
{ year: '1853', value: -0.22939704, z: 'B' },
{ year: '1854', value: -0.25688013, z: 'B' },
{ year: '1850', value: -0.4757164, z: 'C' },
{ year: '1851', value: -0.41971018, z: 'C' },
{ year: '1852', value: -0.40724799, z: 'C' },
{ year: '1853', value: -0.45049156, z: 'C' },
{ year: '1854', value: -0.41896583, z: 'C' },
],
},
xAxis: 'year',
yAxis: 'value',
symbol: 'z',
},
};
export const MultiColumns: Story = {
name: 'Line chart with multiple series (with multiple columns)',
args: {
data: {
values: [
{ year: '1850', A: -0.41765878, B: -0.42993882, C: -0.4757164 },
{ year: '1851', A: -0.2333498, B: -0.30365549, C: -0.41971018 },
{ year: '1852', A: -0.22939907, B: -0.27905189, C: -0.40724799 },
{ year: '1853', A: -0.27035445, B: -0.22939704, C: -0.45049156 },
{ year: '1854', A: -0.29163003, B: -0.25688013, C: -0.41896583 },
],
},
xAxis: 'year',
yAxis: ['A', 'B', 'C'],
}, },
}; };
export const FromURL: Story = { export const FromURL: Story = {
name: 'Line chart from URL', name: 'Line chart from URL',
args: { args: {
data: {
url: 'https://raw.githubusercontent.com/datasets/oil-prices/main/data/wti-year.csv',
},
title: 'Oil Price x Year', title: 'Oil Price x Year',
data: 'https://raw.githubusercontent.com/datasets/oil-prices/main/data/wti-year.csv',
xAxis: 'Date', xAxis: 'Date',
yAxis: 'Price', yAxis: 'Price',
}, },

View File

@@ -1,74 +0,0 @@
import type { Meta, StoryObj } from '@storybook/react';
import { PlotlyLineChart, PlotlyLineChartProps } from '../src/components/PlotlyLineChart';
// More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction
const meta: Meta = {
title: 'Components/PlotlyLineChart',
component: PlotlyLineChart,
tags: ['autodocs'],
argTypes: {
url: {
description:
'CSV Url to be parsed and used as data source',
},
data: {
description:
'Data to be displayed. as an array of key value pairs \n\n E.g.: [{ year: 1850, temperature: -0.41765878 }, { year: 1851, temperature: -0.2333498 }, ...]',
},
rawCsv: {
description:
'Raw csv data to be parsed and used as data source',
},
bytes: {
description:
'How many bytes to read from the url',
},
parsingConfig: {
description: 'If using url or rawCsv, this parsing config will be used to parse the data. Optional, check https://www.papaparse.com/ for more info',
},
title: {
description: 'Title to display on the chart. Optional.',
},
lineLabel: {
description: 'Label to display on the line, Optional, will use yAxis if not provided',
},
xAxis: {
description:
'Name of the X axis on the data. Required when the "data" parameter is an URL.',
},
yAxis: {
description:
'Name of the Y axis on the data. Required when the "data" parameter is an URL.',
},
},
};
export default meta;
type Story = StoryObj<PlotlyLineChartProps>;
export const FromDataPoints: Story = {
name: 'Line chart from array of data points',
args: {
data: [
{year: '1850', temperature: -0.41765878},
{year: '1851', temperature: -0.2333498},
{year: '1852', temperature: -0.22939907},
{year: '1853', temperature: -0.27035445},
{year: '1854', temperature: -0.29163003},
],
xAxis: 'year',
yAxis: 'temperature',
},
};
export const FromURL: Story = {
name: 'Line chart from URL',
args: {
title: 'Oil Price x Year',
url: 'https://raw.githubusercontent.com/datasets/oil-prices/main/data/wti-year.csv',
xAxis: 'Date',
yAxis: 'Price',
},
};

View File

@@ -4,29 +4,34 @@ import { Map, MapProps } from '../src/components/Map';
// More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction // More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction
const meta: Meta = { const meta: Meta = {
title: 'Components/Map', title: 'Components/Geospatial/Map',
component: Map, component: Map,
tags: ['autodocs'], tags: ['autodocs'],
argTypes: { argTypes: {
layers: { layers: {
description: description:
'Data to be displayed.\n\n GeoJSON Object \n\nOR\n\n URL to GeoJSON Object', 'Array of layers to be displayed on the map. Should be an object with: \n\n \
`data`: object with either a `url` property pointing to a GeoJSON file or a `geojson` property with a GeoJSON object. \n\n \
`name`: name of the layer. \n\n \
`colorscale`: object with a `starting` and `ending` colors that will be used to create a gradient and color the map. \n\n \
`tooltip`: `true` to show all available features on the tooltip, object with a `propNames` property as an array of strings to choose which features to display. \n\n',
}, },
title: { title: {
description: 'Title to display on the map. Optional.', description: 'Title to display on the map.',
}, },
center: { center: {
description: 'Initial coordinates of the center of the map', description: 'Initial coordinates of the center of the map',
}, },
zoom: { zoom: {
description: 'Zoom level', description: 'Initial zoom level',
}, },
style: { style: {
description: "Styles for the container" description: "CSS styles to be applied to the map's container.",
}, },
autoZoomConfiguration: { autoZoomConfiguration: {
description: "Configuration to auto zoom in the specified layer data" description:
} "Pass a layer's name to automatically zoom to the bounding area of a layer.",
},
}, },
}; };
@@ -38,9 +43,15 @@ type Story = StoryObj<MapProps>;
export const GeoJSONPolygons: Story = { export const GeoJSONPolygons: Story = {
name: 'GeoJSON polygons map', name: 'GeoJSON polygons map',
args: { args: {
tileLayerName:'MapBox',
tileLayerOptions:{
accessToken : 'pk.eyJ1Ijoid2lsbHktcGFsbWFyZWpvIiwiYSI6ImNqNzk5NmRpNDFzb2cyeG9sc2luMHNjajUifQ.lkoVRFSI8hOLH4uJeOzwXw',
},
layers: [ layers: [
{ {
data: 'https://d2ad6b4ur7yvpq.cloudfront.net/naturalearth-3.3.0/ne_10m_geography_marine_polys.geojson', data: {
url: 'https://d2ad6b4ur7yvpq.cloudfront.net/naturalearth-3.3.0/ne_10m_geography_marine_polys.geojson',
},
name: 'Polygons', name: 'Polygons',
tooltip: { propNames: ['name'] }, tooltip: { propNames: ['name'] },
colorScale: { colorScale: {
@@ -60,7 +71,9 @@ export const GeoJSONPoints: Story = {
args: { args: {
layers: [ layers: [
{ {
data: 'https://opendata.arcgis.com/datasets/9c58741995174fbcb017cf46c8a42f4b_25.geojson', data: {
url: 'https://opendata.arcgis.com/datasets/9c58741995174fbcb017cf46c8a42f4b_25.geojson',
},
name: 'Points', name: 'Points',
tooltip: { propNames: ['Location'] }, tooltip: { propNames: ['Location'] },
}, },
@@ -76,12 +89,16 @@ export const GeoJSONMultipleLayers: Story = {
args: { args: {
layers: [ layers: [
{ {
data: 'https://opendata.arcgis.com/datasets/9c58741995174fbcb017cf46c8a42f4b_25.geojson', data: {
url: 'https://opendata.arcgis.com/datasets/9c58741995174fbcb017cf46c8a42f4b_25.geojson',
},
name: 'Points', name: 'Points',
tooltip: true, tooltip: true,
}, },
{ {
data: 'https://d2ad6b4ur7yvpq.cloudfront.net/naturalearth-3.3.0/ne_10m_geography_marine_polys.geojson', data: {
url: 'https://d2ad6b4ur7yvpq.cloudfront.net/naturalearth-3.3.0/ne_10m_geography_marine_polys.geojson',
},
name: 'Polygons', name: 'Polygons',
tooltip: true, tooltip: true,
colorScale: { colorScale: {
@@ -94,19 +111,23 @@ export const GeoJSONMultipleLayers: Story = {
center: { latitude: 45, longitude: 0 }, center: { latitude: 45, longitude: 0 },
zoom: 2, zoom: 2,
}, },
} };
export const GeoJSONMultipleLayersWithAutoZoomInSpecifiedLayer: Story = { export const GeoJSONMultipleLayersWithAutoZoomInSpecifiedLayer: Story = {
name: 'GeoJSON polygons and points map with auto zoom in the points layer', name: 'GeoJSON polygons and points map with auto zoom in the points layer',
args: { args: {
layers: [ layers: [
{ {
data: 'https://opendata.arcgis.com/datasets/9c58741995174fbcb017cf46c8a42f4b_25.geojson', data: {
url: 'https://opendata.arcgis.com/datasets/9c58741995174fbcb017cf46c8a42f4b_25.geojson',
},
name: 'Points', name: 'Points',
tooltip: true, tooltip: true,
}, },
{ {
data: 'https://d2ad6b4ur7yvpq.cloudfront.net/naturalearth-3.3.0/ne_10m_geography_marine_polys.geojson', data: {
url: 'https://d2ad6b4ur7yvpq.cloudfront.net/naturalearth-3.3.0/ne_10m_geography_marine_polys.geojson',
},
name: 'Polygons', name: 'Polygons',
tooltip: true, tooltip: true,
colorScale: { colorScale: {
@@ -119,7 +140,7 @@ export const GeoJSONMultipleLayersWithAutoZoomInSpecifiedLayer: Story = {
center: { latitude: 45, longitude: 0 }, center: { latitude: 45, longitude: 0 },
zoom: 2, zoom: 2,
autoZoomConfiguration: { autoZoomConfiguration: {
layerName: 'Points' layerName: 'Points',
} },
}, },
}; };

View File

@@ -1,3 +1,6 @@
// NOTE: this component was renamed with .bkp so that it's hidden
// from the Storybook app
import type { Meta, StoryObj } from '@storybook/react'; import type { Meta, StoryObj } from '@storybook/react';
import React from 'react'; import React from 'react';
import OpenLayers from '../src/components/OpenLayers/OpenLayers'; import OpenLayers from '../src/components/OpenLayers/OpenLayers';

View File

@@ -3,19 +3,21 @@ import type { Meta, StoryObj } from '@storybook/react';
import { PdfViewer, PdfViewerProps } from '../src/components/PdfViewer'; import { PdfViewer, PdfViewerProps } from '../src/components/PdfViewer';
const meta: Meta = { const meta: Meta = {
title: 'Components/PdfViewer', title: 'Components/Embedding/PdfViewer',
component: PdfViewer, component: PdfViewer,
tags: ['autodocs'], tags: ['autodocs'],
argTypes: { argTypes: {
url: { data: {
description: 'URL to PDF file', description:
'Object with a `url` property pointing to the PDF file to be displayed, e.g.: `{ url: "https://cdn.filestackcontent.com/wcrjf9qPTCKXV3hMXDwK" }`.',
}, },
parentClassName: { parentClassName: {
description: 'Classname for the parent div of the pdf viewer',
},
layour: {
description: description:
'Set to true if you want to have a layout with zoom level, page count, printing button etc', 'HTML classes to be applied to the container of the PDF viewer. [Tailwind](https://tailwindcss.com/) classes, such as `h-96` to define the height of the component, can be used on this field.',
},
layout: {
description:
'Set to `true` if you want to display a layout with zoom level, page count, printing button and other controls.',
defaultValue: false, defaultValue: false,
}, },
}, },
@@ -25,26 +27,23 @@ export default meta;
type Story = StoryObj<PdfViewerProps>; type Story = StoryObj<PdfViewerProps>;
export const PdfViewerStory: Story = { export const PdfViewerStoryWithoutControlsLayout: Story = {
name: 'PdfViewer', name: 'PDF Viewer without controls layout',
args: { args: {
url: 'https://cdn.filestackcontent.com/wcrjf9qPTCKXV3hMXDwK', data: {
}, url: 'https://cdn.filestackcontent.com/wcrjf9qPTCKXV3hMXDwK',
}; },
parentClassName: 'h-96',
export const PdfViewerStoryWithLayout: Story = { },
name: 'PdfViewer with the default layout', };
args: {
url: 'https://cdn.filestackcontent.com/wcrjf9qPTCKXV3hMXDwK', export const PdfViewerStoryWithControlsLayout: Story = {
layout: true, name: 'PdfViewer with controls layout',
}, args: {
}; data: {
url: 'https://cdn.filestackcontent.com/wcrjf9qPTCKXV3hMXDwK',
export const PdfViewerStoryWithHeight: Story = { },
name: 'PdfViewer with a custom height', layout: true,
args: {
url: 'https://cdn.filestackcontent.com/wcrjf9qPTCKXV3hMXDwK',
parentClassName: 'h-96', parentClassName: 'h-96',
layout: true,
}, },
}; };

View File

@@ -4,9 +4,19 @@ import { Plotly } from '../src/components/Plotly';
// More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction // More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction
const meta: Meta = { const meta: Meta = {
title: 'Components/Plotly', title: 'Components/Charts/Plotly',
component: Plotly, component: Plotly,
tags: ['autodocs'], tags: ['autodocs'],
argTypes: {
data: {
description:
"Plotly's `data` prop. You can find references on how to use these props at https://github.com/plotly/react-plotly.js/#basic-props.",
},
layout: {
description:
"Plotly's `layout` prop. You can find references on how to use these props at https://github.com/plotly/react-plotly.js/#basic-props.",
},
},
}; };
export default meta; export default meta;
@@ -15,7 +25,7 @@ type Story = StoryObj<any>;
// More on writing stories with args: https://storybook.js.org/docs/react/writing-stories/args // More on writing stories with args: https://storybook.js.org/docs/react/writing-stories/args
export const Primary: Story = { export const Primary: Story = {
name: 'Chart built with Plotly', name: 'Line chart',
args: { args: {
data: [ data: [
{ {

View File

@@ -0,0 +1,102 @@
import type { Meta, StoryObj } from '@storybook/react';
import {
PlotlyBarChart,
PlotlyBarChartProps,
} from '../src/components/PlotlyBarChart';
// More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction
const meta: Meta = {
title: 'Components/Charts/PlotlyBarChart',
component: PlotlyBarChart,
tags: ['autodocs'],
argTypes: {
data: {
description:
'Data to be displayed. \n\n \
Must be an object with one of the following properties: `url`, `values` or `csv` \n\n \
`url`: URL pointing to a CSV file. \n\n \
`values`: array of objects (check out [this example](/?path=/story/components-plotlybarchart--from-data-points)) \n\n \
`csv`: string with valid CSV (check out [this example](/?path=/story/components-plotlybarchart--from-inline-csv)) \n\n \
',
},
bytes: {
// TODO: likely this should be an extra option on the data parameter,
// specific to URLs
description:
"How many bytes to read from the url so that the entire file doesn's have to be fetched.",
},
parsingConfig: {
description:
'If using URL or CSV, this parsing config will be used to parse the data. Check https://www.papaparse.com/ for more info.',
},
title: {
description: 'Title to display on the chart.',
},
// TODO: commented out because this doesn't work
// lineLabel: {
// description:
// 'Label to display on the line, Optional, will use yAxis if not provided',
// },
xAxis: {
description:
'Name of the column header or object property that represents the X-axis on the data.',
},
yAxis: {
description:
'Name of the column header or object property that represents the Y-axis on the data.',
},
uniqueId: {
description: 'Provide a unique ID to help with cache revalidation of the fetched data.'
}
},
};
export default meta;
type Story = StoryObj<PlotlyBarChartProps>;
export const FromDataPoints: Story = {
name: 'Bar chart from array of data points',
args: {
data: {
values: [
{ year: '1850', temperature: -0.41765878 },
{ year: '1851', temperature: -0.2333498 },
{ year: '1852', temperature: -0.22939907 },
{ year: '1853', temperature: -0.27035445 },
{ year: '1854', temperature: -0.29163003 },
],
},
xAxis: 'year',
yAxis: 'temperature',
},
};
export const FromURL: Story = {
name: 'Bar chart from URL',
args: {
title: 'Apple Stock Prices',
data: {
url: 'https://raw.githubusercontent.com/plotly/datasets/master/finance-charts-apple.csv',
},
xAxis: 'Date',
yAxis: 'AAPL.Open',
},
};
export const FromInlineCSV: Story = {
name: 'Bar chart from inline CSV',
args: {
title: 'Apple Stock Prices',
data: {
csv: `Date,AAPL.Open,AAPL.High,AAPL.Low,AAPL.Close,AAPL.Volume,AAPL.Adjusted,dn,mavg,up,direction
2015-02-17,127.489998,128.880005,126.919998,127.830002,63152400,122.905254,106.7410523,117.9276669,129.1142814,Increasing
2015-02-18,127.629997,128.779999,127.449997,128.720001,44891700,123.760965,107.842423,118.9403335,130.0382439,Increasing
2015-02-19,128.479996,129.029999,128.330002,128.449997,37362400,123.501363,108.8942449,119.8891668,130.8840887,Decreasing
2015-02-20,128.619995,129.5,128.050003,129.5,48948400,124.510914,109.7854494,120.7635001,131.7415509,Increasing`,
},
xAxis: 'Date',
yAxis: 'AAPL.Open',
},
};

View File

@@ -0,0 +1,101 @@
import type { Meta, StoryObj } from '@storybook/react';
import {
PlotlyLineChart,
PlotlyLineChartProps,
} from '../src/components/PlotlyLineChart';
const meta: Meta = {
title: 'Components/Charts/PlotlyLineChart',
component: PlotlyLineChart,
tags: ['autodocs'],
argTypes: {
data: {
description:
'Data to be displayed. \n\n \
Must be an object with one of the following properties: `url`, `values` or `csv` \n\n \
`url`: URL pointing to a CSV file. \n\n \
`values`: array of objects. \n\n \
`csv`: string with valid CSV. \n\n \
',
},
bytes: {
// TODO: likely this should be an extra option on the data parameter,
// specific to URLs
description:
"How many bytes to read from the url so that the entire file doesn's have to be fetched.",
},
parsingConfig: {
description:
'If using URL or CSV, this parsing config will be used to parse the data. Check https://www.papaparse.com/ for more info',
},
title: {
description: 'Title to display on the chart.',
},
lineLabel: {
description:
'Label to display on the line, will use yAxis if not provided',
},
xAxis: {
description:
'Name of the column header or object property that represents the X-axis on the data.',
},
yAxis: {
description:
'Name of the column header or object property that represents the Y-axis on the data.',
},
uniqueId: {
description:
'Provide a unique ID to help with cache revalidation of the fetched data.',
},
},
};
export default meta;
type Story = StoryObj<PlotlyLineChartProps>;
export const FromDataPoints: Story = {
name: 'Line chart from array of data points',
args: {
data: {
values: [
{ year: '1850', temperature: -0.41765878 },
{ year: '1851', temperature: -0.2333498 },
{ year: '1852', temperature: -0.22939907 },
{ year: '1853', temperature: -0.27035445 },
{ year: '1854', temperature: -0.29163003 },
],
},
xAxis: 'year',
yAxis: 'temperature',
},
};
export const FromURL: Story = {
name: 'Line chart from URL',
args: {
title: 'Oil Price x Year',
data: {
url: 'https://raw.githubusercontent.com/datasets/oil-prices/main/data/wti-year.csv',
},
xAxis: 'Date',
yAxis: 'Price',
},
};
export const FromInlineCSV: Story = {
name: 'Bar chart from inline CSV',
args: {
title: 'Apple Stock Prices',
data: {
csv: `Date,AAPL.Open,AAPL.High,AAPL.Low,AAPL.Close,AAPL.Volume,AAPL.Adjusted,dn,mavg,up,direction
2015-02-17,127.489998,128.880005,126.919998,127.830002,63152400,122.905254,106.7410523,117.9276669,129.1142814,Increasing
2015-02-18,127.629997,128.779999,127.449997,128.720001,44891700,123.760965,107.842423,118.9403335,130.0382439,Increasing
2015-02-19,128.479996,129.029999,128.330002,128.449997,37362400,123.501363,108.8942449,119.8891668,130.8840887,Decreasing
2015-02-20,128.619995,129.5,128.050003,129.5,48948400,124.510914,109.7854494,120.7635001,131.7415509,Increasing`,
},
xAxis: 'Date',
yAxis: 'AAPL.Open',
},
};

View File

@@ -1,10 +1,13 @@
// NOTE: this component was renamed with .bkp so that it's hidden
// from the Storybook app
import type { Meta, StoryObj } from '@storybook/react'; import type { Meta, StoryObj } from '@storybook/react';
import { Table, TableProps } from '../src/components/Table'; import { Table, TableProps } from '../src/components/Table';
// More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction // More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction
const meta: Meta = { const meta: Meta = {
title: 'Components/Table', title: 'Components/Tabular/Table',
component: Table, component: Table,
tags: ['autodocs'], tags: ['autodocs'],
argTypes: { argTypes: {

View File

@@ -4,9 +4,19 @@ import { Vega } from '../src/components/Vega';
// More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction // More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction
const meta: Meta = { const meta: Meta = {
title: 'Components/Vega', title: 'Components/Charts/Vega',
component: Vega, component: Vega,
tags: ['autodocs'], tags: ['autodocs'],
argTypes: {
data: {
description:
"Vega's `data` prop. You can find references on how to use this prop at https://vega.github.io/vega/docs/data/",
},
spec: {
description:
"Vega's `spec` prop. You can find references on how to use this prop at https://vega.github.io/vega/docs/specification/",
},
},
}; };
export default meta; export default meta;
@@ -15,7 +25,7 @@ type Story = StoryObj<any>;
// More on writing stories with args: https://storybook.js.org/docs/react/writing-stories/args // More on writing stories with args: https://storybook.js.org/docs/react/writing-stories/args
export const Primary: Story = { export const Primary: Story = {
name: 'Chart built with Vega', name: 'Bar chart',
args: { args: {
data: { data: {
table: [ table: [

View File

@@ -4,7 +4,7 @@ import { VegaLite } from '../src/components/VegaLite';
// More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction // More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction
const meta: Meta = { const meta: Meta = {
title: 'Components/VegaLite', title: 'Components/Charts/VegaLite',
component: VegaLite, component: VegaLite,
tags: ['autodocs'], tags: ['autodocs'],
argTypes: { argTypes: {
@@ -25,7 +25,7 @@ type Story = StoryObj<any>;
// More on writing stories with args: https://storybook.js.org/docs/react/writing-stories/args // More on writing stories with args: https://storybook.js.org/docs/react/writing-stories/args
export const Primary: Story = { export const Primary: Story = {
name: 'Chart built with Vega Lite', name: 'Bar chart',
args: { args: {
data: { data: {
table: [ table: [

View File

@@ -53,7 +53,7 @@ export const Nav: React.FC<Props> = ({
<nav className="flex justify-between"> <nav className="flex justify-between">
{/* Mobile navigation */} {/* Mobile navigation */}
<div className="mr-2 sm:mr-4 flex lg:hidden"> <div className="mr-2 sm:mr-4 flex lg:hidden">
<NavMobile links={links}>{children}</NavMobile> <NavMobile {...{title, links, social, search, defaultTheme, themeToggleIcon}}>{children}</NavMobile>
</div> </div>
{/* Non-mobile navigation */} {/* Non-mobile navigation */}
<div className="flex flex-none items-center"> <div className="flex flex-none items-center">

View File

@@ -4,20 +4,16 @@ import { useRouter } from "next/router.js";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { SearchContext, SearchField } from "../Search"; import { SearchContext, SearchField } from "../Search";
import { MenuIcon, CloseIcon } from "../Icons"; import { MenuIcon, CloseIcon } from "../Icons";
import { NavLink, SearchProviderConfig } from "../types"; import type { NavConfig, ThemeConfig } from "./Nav";
interface Props extends React.PropsWithChildren { interface Props extends NavConfig, ThemeConfig, React.PropsWithChildren {}
author?: string;
links?: Array<NavLink>;
search?: SearchProviderConfig;
}
// TODO why mobile navigation only accepts author and regular nav accepts different things like title, logo, version // TODO: Search doesn't appear
export const NavMobile: React.FC<Props> = ({ export const NavMobile: React.FC<Props> = ({
children, children,
title,
links, links,
search, search,
author,
}) => { }) => {
const router = useRouter(); const router = useRouter();
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
@@ -77,8 +73,8 @@ export const NavMobile: React.FC<Props> = ({
legacyBehavior legacyBehavior
> >
{/* <Logomark className="h-9 w-9" /> */} {/* <Logomark className="h-9 w-9" /> */}
<div className="font-extrabold text-primary dark:text-primary-dark text-2xl ml-6"> <div className="font-extrabold text-primary dark:text-primary-dark text-lg ml-6">
{author} {title}
</div> </div>
</Link> </Link>
</div> </div>
@@ -106,9 +102,7 @@ export const NavMobile: React.FC<Props> = ({
))} ))}
</ul> </ul>
)} )}
{/* <div className="pt-6 border border-t-2"> <div className="pt-6">{children}</div>
{children}
</div> */}
</Dialog.Panel> </Dialog.Panel>
</Dialog> </Dialog>
</> </>

View File

@@ -38,6 +38,5 @@ const defaultPathToPermalinkFunc = (
.replace(markdownFolder, "") // make the permalink relative to the markdown folder .replace(markdownFolder, "") // make the permalink relative to the markdown folder
.replace(/\.(mdx|md)/, "") .replace(/\.(mdx|md)/, "")
.replace(/\\/g, "/") // replace windows backslash with forward slash .replace(/\\/g, "/") // replace windows backslash with forward slash
.replace(/\/index$/, ""); // remove index from the end of the permalink
return permalink.length > 0 ? permalink : "/"; // for home page return permalink.length > 0 ? permalink : "/"; // for home page
}; };

View File

@@ -1,9 +1,6 @@
import * as path from "path"; import * as path from "path";
// import * as url from "url";
import { getPermalinks } from "../src/utils"; import { getPermalinks } from "../src/utils";
// const __dirname = url.fileURLToPath(new URL(".", import.meta.url));
// const markdownFolder = path.join(__dirname, "/fixtures/content");
const markdownFolder = path.join( const markdownFolder = path.join(
".", ".",
"test/fixtures/content" "test/fixtures/content"
@@ -12,12 +9,12 @@ const markdownFolder = path.join(
describe("getPermalinks", () => { describe("getPermalinks", () => {
test("should return an array of permalinks", () => { test("should return an array of permalinks", () => {
const expectedPermalinks = [ const expectedPermalinks = [
"/", // /index.md "/README",
"/abc", "/abc",
"/blog/first-post", "/blog/first-post",
"/blog/Second Post", "/blog/Second Post",
"/blog/third-post", "/blog/third-post",
"/blog", // /blog/index.md "/blog/README",
"/blog/tutorials/first-tutorial", "/blog/tutorials/first-tutorial",
"/assets/Pasted Image 123.png", "/assets/Pasted Image 123.png",
]; ];
@@ -28,35 +25,4 @@ describe("getPermalinks", () => {
expect(expectedPermalinks).toContain(permalink); expect(expectedPermalinks).toContain(permalink);
}); });
}); });
test("should return an array of permalinks with custom path -> permalink converter function", () => {
const expectedPermalinks = [
"/", // /index.md
"/abc",
"/blog/first-post",
"/blog/second-post",
"/blog/third-post",
"/blog", // /blog/index.md
"/blog/tutorials/first-tutorial",
"/assets/pasted-image-123.png",
];
const func = (filePath: string, markdownFolder: string) => {
const permalink = filePath
.replace(markdownFolder, "") // make the permalink relative to the markdown folder
.replace(/\.(mdx|md)/, "")
.replace(/\\/g, "/") // replace windows backslash with forward slash
.replace(/\/index$/, "") // remove index from the end of the permalink
.replace(/ /g, "-") // replace spaces with hyphens
.toLowerCase(); // convert to lowercase
return permalink.length > 0 ? permalink : "/"; // for home page
};
const permalinks = getPermalinks(markdownFolder, [/\.DS_Store/], func);
expect(permalinks).toHaveLength(expectedPermalinks.length);
permalinks.forEach((permalink) => {
expect(expectedPermalinks).toContain(permalink);
});
});
}); });

View File

@@ -159,11 +159,11 @@ describe("micromark-extension-wiki-link", () => {
}); });
expect(serialized).toBe( expect(serialized).toBe(
'<p><img src="My Image.jpg" alt="My Image.jpg" class="internal" width="200" height="200" /></p>' '<p><img src="My Image.jpg" alt="My Image.jpg" class="internal" width="200" height="200" /></p>'
); );
}); });
// TODO: Fix alt attribute // TODO: Fix alt attribute
test("Can identify the dimensions of the image if exists", () => { test("Can identify the dimensions of the image if exists", () => {
const serialized = micromark("![[My Image.jpg|200x200]]", "ascii", { const serialized = micromark("![[My Image.jpg|200x200]]", "ascii", {
extensions: [syntax()], extensions: [syntax()],
htmlExtensions: [html({ permalinks: ["My Image.jpg"] }) as any], // TODO type fix htmlExtensions: [html({ permalinks: ["My Image.jpg"] }) as any], // TODO type fix
@@ -286,56 +286,6 @@ describe("micromark-extension-wiki-link", () => {
}); });
}); });
test("parses wiki links to index files", () => {
const serialized = micromark("[[/some/folder/index]]", "ascii", {
extensions: [syntax()],
htmlExtensions: [html() as any], // TODO type fix
});
expect(serialized).toBe(
'<p><a href="/some/folder" class="internal new">/some/folder/index</a></p>'
);
});
describe("other", () => {
test("parses a wiki link to some index page in a folder with no matching permalink", () => {
const serialized = micromark("[[/some/folder/index]]", "ascii", {
extensions: [syntax()],
htmlExtensions: [html() as any], // TODO type fix
});
expect(serialized).toBe(
'<p><a href="/some/folder" class="internal new">/some/folder/index</a></p>'
);
});
test("parses a wiki link to some index page in a folder with a matching permalink", () => {
const serialized = micromark("[[/some/folder/index]]", "ascii", {
extensions: [syntax()],
htmlExtensions: [html({ permalinks: ["/some/folder"] }) as any], // TODO type fix
});
expect(serialized).toBe(
'<p><a href="/some/folder" class="internal">/some/folder/index</a></p>'
);
});
test("parses a wiki link to home index page with no matching permalink", () => {
const serialized = micromark("[[/index]]", "ascii", {
extensions: [syntax()],
htmlExtensions: [html() as any], // TODO type fix
});
expect(serialized).toBe(
'<p><a href="/" class="internal new">/index</a></p>'
);
});
test("parses a wiki link to home index page with a matching permalink", () => {
const serialized = micromark("[[/index]]", "ascii", {
extensions: [syntax()],
htmlExtensions: [html({ permalinks: ["/"] }) as any], // TODO type fix
});
expect(serialized).toBe('<p><a href="/" class="internal">/index</a></p>');
});
});
describe("transclusions", () => { describe("transclusions", () => {
test("parsers a transclusion as a regular wiki link", () => { test("parsers a transclusion as a regular wiki link", () => {
const serialized = micromark("![[Some Page]]", "ascii", { const serialized = micromark("![[Some Page]]", "ascii", {

View File

@@ -485,109 +485,6 @@ describe("remark-wiki-link", () => {
}); });
}); });
test("parses wiki links to index files", () => {
const processor = unified().use(markdown).use(wikiLinkPlugin);
let ast = processor.parse("[[/some/folder/index]]");
ast = processor.runSync(ast);
expect(select("wikiLink", ast)).not.toEqual(null);
visit(ast, "wikiLink", (node: Node) => {
expect(node.data?.exists).toEqual(false);
expect(node.data?.permalink).toEqual("/some/folder");
expect(node.data?.alias).toEqual(null);
expect(node.data?.hName).toEqual("a");
expect((node.data?.hProperties as any).className).toEqual("internal new");
expect((node.data?.hProperties as any).href).toEqual("/some/folder");
expect((node.data?.hChildren as any)[0].value).toEqual(
"/some/folder/index"
);
});
});
describe("other", () => {
test("parses a wiki link to some index page in a folder with no matching permalink", () => {
const processor = unified().use(markdown).use(wikiLinkPlugin);
let ast = processor.parse("[[/some/folder/index]]");
ast = processor.runSync(ast);
visit(ast, "wikiLink", (node: Node) => {
expect(node.data?.exists).toEqual(false);
expect(node.data?.permalink).toEqual("/some/folder");
expect(node.data?.alias).toEqual(null);
expect(node.data?.hName).toEqual("a");
expect((node.data?.hProperties as any).className).toEqual(
"internal new"
);
expect((node.data?.hProperties as any).href).toEqual("/some/folder");
expect((node.data?.hChildren as any)[0].value).toEqual(
"/some/folder/index"
);
});
});
test("parses a wiki link to some index page in a folder with a matching permalink", () => {
const processor = unified()
.use(markdown)
.use(wikiLinkPlugin, { permalinks: ["/some/folder"] });
let ast = processor.parse("[[/some/folder/index]]");
ast = processor.runSync(ast);
visit(ast, "wikiLink", (node: Node) => {
expect(node.data?.exists).toEqual(true);
expect(node.data?.permalink).toEqual("/some/folder");
expect(node.data?.alias).toEqual(null);
expect(node.data?.hName).toEqual("a");
expect((node.data?.hProperties as any).className).toEqual("internal");
expect((node.data?.hProperties as any).href).toEqual("/some/folder");
expect((node.data?.hChildren as any)[0].value).toEqual(
"/some/folder/index"
);
});
});
test("parses a wiki link to home index page with no matching permalink", () => {
const processor = unified().use(markdown).use(wikiLinkPlugin);
let ast = processor.parse("[[/index]]");
ast = processor.runSync(ast);
visit(ast, "wikiLink", (node: Node) => {
expect(node.data?.exists).toEqual(false);
expect(node.data?.permalink).toEqual("/");
expect(node.data?.alias).toEqual(null);
expect(node.data?.hName).toEqual("a");
expect((node.data?.hProperties as any).className).toEqual(
"internal new"
);
expect((node.data?.hProperties as any).href).toEqual("/");
expect((node.data?.hChildren as any)[0].value).toEqual("/index");
});
});
test("parses a wiki link to home index page with a matching permalink", () => {
const processor = unified()
.use(markdown)
.use(wikiLinkPlugin, { permalinks: ["/"] });
let ast = processor.parse("[[/index]]");
ast = processor.runSync(ast);
visit(ast, "wikiLink", (node: Node) => {
expect(node.data?.exists).toEqual(true);
expect(node.data?.permalink).toEqual("/");
expect(node.data?.alias).toEqual(null);
expect(node.data?.hName).toEqual("a");
expect((node.data?.hProperties as any).className).toEqual("internal");
expect((node.data?.hProperties as any).href).toEqual("/");
expect((node.data?.hChildren as any)[0].value).toEqual("/index");
});
});
});
describe("transclusions", () => { describe("transclusions", () => {
test("replaces a transclusion with a regular wiki link", () => { test("replaces a transclusion with a regular wiki link", () => {
const processor = unified().use(markdown).use(wikiLinkPlugin); const processor = unified().use(markdown).use(wikiLinkPlugin);

View File

@@ -22,11 +22,41 @@ const items = [
sourceUrl: 'https://github.com/FCSCOpendata/frontend', sourceUrl: 'https://github.com/FCSCOpendata/frontend',
}, },
{ {
title: 'Datahub Open Data', title: 'Frictionless Data',
href: 'https://opendata.datahub.io/', href: 'https://datahub.io/core/co2-ppm',
image: '/images/showcases/datahub.webp', repository: 'https://github.com/datopian/datahub/tree/main/examples/dataset-frictionless',
description: 'Demo Data Portal by DataHub', image: '/images/showcases/frictionless-capture.png',
description: 'Progressive open-source framework for building data infrastructure - data management, data integration, data flows, etc. It includes various data standards and provides software to work with data.',
}, },
{
title: "OpenSpending",
image: "/images/showcases/openspending.png",
href: "https://www.openspending.org",
repository: 'https://github.com/datopian/datahub/tree/main/examples/openspending',
description: "OpenSpending is a free, open and global platform to search, visualise and analyse fiscal data in the public sphere."
},
{
title: "FiveThirtyEight",
image: "/images/showcases/fivethirtyeight.png",
href: "https://fivethirtyeight.portaljs.org/",
repository: 'https://github.com/datopian/datahub/tree/main/examples/fivethirtyeight',
description: "This is a replica of data.fivethirtyeight.com using PortalJS."
},
{
title: "Github Datasets",
image: "/images/showcases/github-datasets.png",
href: "https://example.portaljs.org/",
repository: 'https://github.com/datopian/datahub/tree/main/examples/github-backed-catalog',
description: "A simple data catalog that get its data from a list of GitHub repos that serve as datasets."
},
{
title: "Hatespeech Data",
image: "/images/showcases/turing.png",
href: "https://hatespeechdata.com/",
repository: 'https://github.com/datopian/datahub/tree/main/examples/turing',
description: "Datasets annotated for hate speech, online abuse, and offensive language which are useful for training a natural language processing system to detect this online abuse."
},
]; ];
export default function Showcases() { export default function Showcases() {

View File

@@ -1,10 +1,6 @@
export default function ShowcasesItem({ item }) { export default function ShowcasesItem({ item }) {
return ( return (
<a <div className="rounded overflow-hidden group relative border-1 shadow-lg">
className="rounded overflow-hidden group relative border-1 shadow-lg"
target="_blank"
href={item.href}
>
<div <div
className="bg-cover bg-no-repeat bg-top aspect-video w-full group-hover:blur-sm group-hover:scale-105 transition-all duration-200" className="bg-cover bg-no-repeat bg-top aspect-video w-full group-hover:blur-sm group-hover:scale-105 transition-all duration-200"
style={{ backgroundImage: `url(${item.image})` }} style={{ backgroundImage: `url(${item.image})` }}
@@ -16,9 +12,48 @@ export default function ShowcasesItem({ item }) {
<div className="text-center text-primary-dark"> <div className="text-center text-primary-dark">
<span className="text-xl font-semibold">{item.title}</span> <span className="text-xl font-semibold">{item.title}</span>
<p className="text-base font-medium">{item.description}</p> <p className="text-base font-medium">{item.description}</p>
<div className="flex justify-center mt-2 gap-2 ">
{item.href && (
<a
target="_blank"
className=" text-white w-8 h-8 p-1 bg-primary rounded-full hover:scale-110 transition cursor-pointer z-50"
rel="noreferrer"
href={item.href}
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 420 420"
stroke="white"
fill="none"
>
<path stroke-width="26" d="M209,15a195,195 0 1,0 2,0z" />
<path
stroke-width="18"
d="m210,15v390m195-195H15M59,90a260,260 0 0,0 302,0 m0,240 a260,260 0 0,0-302,0M195,20a250,250 0 0,0 0,382 m30,0 a250,250 0 0,0 0-382"
/>
</svg>
</a>
)}
{item.repository && (
<a
target="_blank"
rel="noreferrer"
className="w-8 h-8 bg-black rounded-full p-1 hover:scale-110 transition cursor-pointer z-50"
href={item.repository}
>
<svg
aria-hidden="true"
viewBox="0 0 16 16"
fill="currentColor"
>
<path d="M8 0C3.58 0 0 3.58 0 8C0 11.54 2.29 14.53 5.47 15.59C5.87 15.66 6.02 15.42 6.02 15.21C6.02 15.02 6.01 14.39 6.01 13.72C4 14.09 3.48 13.23 3.32 12.78C3.23 12.55 2.84 11.84 2.5 11.65C2.22 11.5 1.82 11.13 2.49 11.12C3.12 11.11 3.57 11.7 3.72 11.94C4.44 13.15 5.59 12.81 6.05 12.6C6.12 12.08 6.33 11.73 6.56 11.53C4.78 11.33 2.92 10.64 2.92 7.58C2.92 6.71 3.23 5.99 3.74 5.43C3.66 5.23 3.38 4.41 3.82 3.31C3.82 3.31 4.49 3.1 6.02 4.13C6.66 3.95 7.34 3.86 8.02 3.86C8.7 3.86 9.38 3.95 10.02 4.13C11.55 3.09 12.22 3.31 12.22 3.31C12.66 4.41 12.38 5.23 12.3 5.43C12.81 5.99 13.12 6.7 13.12 7.58C13.12 10.65 11.25 11.33 9.47 11.53C9.76 11.78 10.01 12.26 10.01 13.01C10.01 14.08 10 14.94 10 15.21C10 15.42 10.15 15.67 10.55 15.59C13.71 14.53 16 11.53 16 8C16 3.58 12.42 0 8 0Z" />
</svg>
</a>
)}
</div>
</div> </div>
</div> </div>
</div> </div>
</a> </div>
); );
} }

View File

@@ -4,7 +4,7 @@ authors: ['Luccas Mateus']
date: 2021-04-20 date: 2021-04-20
--- ---
We have created a full data portal demo using PortalJS all backed by a CKAN instance storing data and metadata, you can see below a screenshot of the homepage and of an individual dataset page. We have created a full data portal demo using DataHub PortalJS all backed by a CKAN instance storing data and metadata, you can see below a screenshot of the homepage and of an individual dataset page.
![](https://i.imgur.com/ai0VLS4.png) ![](https://i.imgur.com/ai0VLS4.png)
![](https://i.imgur.com/3RhXOW4.png) ![](https://i.imgur.com/3RhXOW4.png)
@@ -14,7 +14,7 @@ We have created a full data portal demo using PortalJS all backed by a CKAN inst
To create a Portal app, run the following command in your terminal: To create a Portal app, run the following command in your terminal:
```console ```console
npx create-next-app -e https://github.com/datopian/portaljs/tree/main/examples/ckan npx create-next-app -e https://github.com/datopian/datahub/tree/main/examples/ckan
``` ```
> NB: Under the hood, this uses the tool called create-next-app, which bootstraps an app for you based on our CKAN example. > NB: Under the hood, this uses the tool called create-next-app, which bootstraps an app for you based on our CKAN example.

View File

@@ -1,7 +1,7 @@
const config = { const config = {
title: 'PortalJS - The JavaScript framework for data portals.', title: 'DataHub PortalJS - The JavaScript framework for data portals.',
description: description:
'PortalJS is a JavaScript framework for rapidly building rich data portal frontends using a modern frontend approach.', 'DataHub PortalJS is a JavaScript framework for rapidly building rich data portal frontends using a modern frontend approach.',
theme: { theme: {
default: 'dark', default: 'dark',
toggleIcon: '/images/theme-button.svg', toggleIcon: '/images/theme-button.svg',
@@ -11,19 +11,18 @@ const config = {
authorUrl: 'https://datopian.com/', authorUrl: 'https://datopian.com/',
navbarTitle: { navbarTitle: {
// logo: "/images/logo.svg", // logo: "/images/logo.svg",
text: '🌀 PortalJS', text: '🌀 DataHub PortalJS',
// version: "Alpha", // version: "Alpha",
}, },
navLinks: [ navLinks: [
{ name: 'Docs', href: '/docs' }, { name: 'Docs', href: '/docs' },
// { name: "Components", href: "/docs/components" }, // { name: "Components", href: "/docs/components" },
{ name: 'Blog', href: '/blog' }, { name: 'Blog', href: '/blog' },
{ name: 'Showcases', href: '/#showcases' },
{ name: 'Howtos', href: '/howtos' }, { name: 'Howtos', href: '/howtos' },
{ name: 'Guide', href: '/guide' }, { name: 'Guide', href: '/guide' },
{ {
name: 'Examples', name: 'Showcases',
href: '/examples/' href: '/showcases/'
}, },
{ {
name: 'Components', name: 'Components',
@@ -68,8 +67,8 @@ const config = {
cardType: 'summary_large_image', cardType: 'summary_large_image',
}, },
}, },
github: 'https://github.com/datopian/portaljs', github: 'https://github.com/datopian/datahub',
discord: 'https://discord.gg/xfFDMPU9dC', discord: 'https://discord.gg/KrRzMKU',
tableOfContents: true, tableOfContents: true,
analytics: 'G-96GWZHMH57', analytics: 'G-96GWZHMH57',
// editLinkShow: true, // editLinkShow: true,

View File

@@ -26,7 +26,7 @@ Below are some screenshots:
- Create a new app with `create-next-app`: - Create a new app with `create-next-app`:
``` ```
npx create-next-app <app-name> --example https://github.com/datopian/portaljs/tree/main/examples/ckan-example npx create-next-app <app-name> --example https://github.com/datopian/datahub/tree/main/examples/ckan-example
cd <app-name> cd <app-name>
``` ```
@@ -49,7 +49,7 @@ If yo go to any one of those pages by clicking on `More info` you will see somet
## Deployment ## Deployment
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fdatopian%2Fportaljs%2Ftree%2Fmain%2Fexamples%2Fckan-example&env=DMS&envDescription=URL%20For%20the%20CKAN%20Backend%20Ex%3A%20https%3A%2F%2Fdemo.dev.datopian.com) [![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fdatopian%2Fdatahub%2Ftree%2Fmain%2Fexamples%2Fckan-example&env=DMS&envDescription=URL%20For%20the%20CKAN%20Backend%20Ex%3A%20https%3A%2F%2Fdemo.dev.datopian.com)
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 allow you to clone the content into your own github/gitlab/bitbucket account and automatically deploy everything.
@@ -70,6 +70,6 @@ npm run start
## Links ## Links
- [Repo](https://github.com/datopian/portaljs/tree/main/examples/ckan-example) - [Repo](https://github.com/datopian/datahub/tree/main/examples/ckan-example)
- [Live Demo](https://ckan-example.portaljs.org) - [Live Demo](https://ckan-example.portaljs.org)

View File

@@ -26,7 +26,7 @@ To get a feel of the project, check out the demo at [live deployment](https://ck
Navigate to the directory in which you want to create the project folder and run the following command: Navigate to the directory in which you want to create the project folder and run the following command:
``` ```
npx create-next-app <app-name> --example https://github.com/datopian/portaljs/tree/main/examples/ckan npx create-next-app <app-name> --example https://github.com/datopian/datahub/tree/main/examples/ckan
cd <app-name> cd <app-name>
``` ```
@@ -56,7 +56,7 @@ If you navigate to any of the dataset pages by clicking on the dataset title you
## Deployment ## Deployment
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fdatopian%2Fportaljs%2Ftree%2Fmain%2Fexamples%2Fckan&env=DMS&envDescription=URL%20For%20the%20CKAN%20Backend%20Ex%3A%20https%3A%2F%2Fdemo.dev.datopian.com) [![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fdatopian%2Fdatahub%2Ftree%2Fmain%2Fexamples%2Fckan&env=DMS&envDescription=URL%20For%20the%20CKAN%20Backend%20Ex%3A%20https%3A%2F%2Fdemo.dev.datopian.com)
By clicking on this button, you will be redirected to a page which allows you to clone the base project into your own GitHub/GitLab/BitBucket account and automatically deploy it. By clicking on this button, you will be redirected to a page which allows you to clone the base project into your own GitHub/GitLab/BitBucket account and automatically deploy it.
@@ -158,6 +158,6 @@ Thanks to TypeScript, you can get a list of all the API methods in `@portaljs/ck
## Links ## Links
- [Repo](https://github.com/datopian/portaljs/tree/main/examples/ckan) - [Repo](https://github.com/datopian/datahub/tree/main/examples/ckan)
- [Live Demo](http://ckan.portaljs.org/) - [Live Demo](http://ckan.portaljs.org/)

View File

@@ -1,48 +0,0 @@
---
title: "Example: showcase for a single Frictionless dataset"
authors: ['Luccas Mateus']
date: 2023-04-20
filetype: blog
---
**See the repo:** https://github.com/datopian/portaljs/tree/main/examples/dataset-frictionless
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://frictionlessdata.io/data-packages/
## How to use
```bash
npx create-next-app -e https://github.com/datopian/portaljs/tree/main/examples/dataset-frictionless
# choose a name for your portal when prompted e.g. your-portal or go with default my-app
# then run it
cd your-portal
yarn #install packages
yarn dev #start app in dev mode
```
You should see the demo portal running with the example dataset provided:
<img src="/assets/examples/frictionless-dataset-demo.gif" />
### Use your own dataset
You can try it out with other [Frictionless datasets](https://datahub.io/search).
In the directory of your portal do:
```bash
export PORTAL_DATASET_PATH=/path/to/my/dataset
```
Then restart the dev server:
```
yarn dev
```
Check the portal page and it should have updated e.g. like:
![](https://i.imgur.com/KSEtNF1.png)

View File

@@ -33,7 +33,7 @@ Run the following commands:
```bash ```bash
npx create-next-app <app-name> --example https://github.com/datopian/portaljs/tree/main/examples/github-backed-catalog npx create-next-app <app-name> --example https://github.com/datopian/datahub/tree/main/examples/github-backed-catalog
cd <app-name> cd <app-name>
``` ```
@@ -61,7 +61,7 @@ Congratulations, your new app is now running at http://localhost:3000.
## Deployment ## Deployment
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fdatopian%2Fportaljs%2Ftree%2Fmain%2Fexamples%2Fgithub-backed-catalog) [![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fdatopian%2Fdatahub%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 example into your own GitHub/GitLab/BitBucket account and automatically deploy it. By clicking on this button, you will be redirected to a page which will allow you to clone the example into your own GitHub/GitLab/BitBucket account and automatically deploy it.
@@ -119,5 +119,5 @@ npm run start
## Links ## Links
- [Repo](https://github.com/datopian/portaljs/tree/main/examples/github-backed-catalog) - [Repo](https://github.com/datopian/datahub/tree/main/examples/github-backed-catalog)
- [Live Demo](https://example.portaljs.org) - [Live Demo](https://example.portaljs.org)

View File

@@ -3,9 +3,9 @@ title: Getting Started
description: 'Getting started guide and tutorial about data portal-building with PortalJS!' description: 'Getting started guide and tutorial about data portal-building with PortalJS!'
--- ---
Welcome to the PortalJS documentation! Welcome to the DataHub PortalJS documentation!
If you have questions about anything related to PortalJS, you're always welcome to ask our community on [GitHub Discussions](https://github.com/datopian/portaljs/discussions) or on [our chat channel on Discord](https://discord.gg/EeyfGrGu4U). If you have questions about anything related to PortalJS, you're always welcome to ask our community on [GitHub Discussions](https://github.com/datopian/datahub/discussions) or on [our chat channel on Discord](https://discord.com/invite/KrRzMKU).
## Setup ## Setup
@@ -16,10 +16,10 @@ If you have questions about anything related to PortalJS, you're always welcome
### Create a PortalJS app ### Create a PortalJS app
To create a PortalJS app, open your terminal, cd into the directory youd like to create the app in, and run the following command: To create a DataHub PortalJS app, open your terminal, cd into the directory youd like to create the app in, and run the following command:
```bash ```bash
npx create-next-app my-data-portal --example https://github.com/datopian/portaljs/tree/main/examples/learn npx create-next-app my-data-portal --example https://github.com/datopian/datahub/tree/main/examples/learn
``` ```
> [!tip] > [!tip]

View File

@@ -1,5 +0,0 @@
# Examples
For now, see the examples folder in github:
https://github.com/datopian/portaljs/tree/main/examples

View File

@@ -11,5 +11,5 @@ description: Learn more about how you can achieve different data portal features
- [[howtos/drd|How to create data-rich documents with charts and tables?]] - [[howtos/drd|How to create data-rich documents with charts and tables?]]
- [[howtos/comments|How to add user comments?]] - [[howtos/comments|How to add user comments?]]
If you have questions about anything related to PortalJS, you're always welcome to ask our community on [GitHub Discussions](https://github.com/datopian/portaljs/discussions) or on [our chat channel on Discord](https://discord.gg/EeyfGrGu4U). If you have questions about anything related to PortalJS, you're always welcome to ask our community on [GitHub Discussions](https://github.com/datopian/datahub/discussions) or on [our chat channel on Discord](https://discord.gg/EeyfGrGu4U).

View File

@@ -50,7 +50,7 @@ function MyApp({ Component, pageProps }) {
<DefaultSeo <DefaultSeo
defaultTitle={siteConfig.title} defaultTitle={siteConfig.title}
description={siteConfig.description} description={siteConfig.description}
titleTemplate="PortalJS - %s" titleTemplate="DataHub PortalJS - %s"
{...siteConfig.nextSeo} {...siteConfig.nextSeo}
/> />

View File

@@ -35,7 +35,7 @@ export default function Home({ sidebarTree }) {
sidebarTree={sidebarTree} sidebarTree={sidebarTree}
> >
<Features /> <Features />
<Showcases />
<Community /> <Community />
</Layout> </Layout>
</> </>

8
site/pages/showcases.tsx Normal file
View File

@@ -0,0 +1,8 @@
import Layout from "@/components/Layout";
import Showcases from "@/components/Showcases";
export default function ShowcasesList() {
return (
<Layout><Showcases/></Layout>
)
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

View File

@@ -1,7 +1,7 @@
#!/bin/bash #!/bin/bash
rm -rf portal rm -rf portal
mkdir -p portal mkdir -p portal
npx create-next-app portal -e https://github.com/datopian/portaljs/tree/main/examples/dataset-frictionless npx create-next-app portal -e https://github.com/datopian/datahub/tree/main/examples/dataset-frictionless
mkdir portal/public/dataset mkdir portal/public/dataset
cp -a ./data portal/public/dataset cp -a ./data portal/public/dataset
@@ -12,7 +12,7 @@ PORTAL_DATASET_PATH=$PWD"/portal/public/dataset"
export PORTAL_DATASET_PATH export PORTAL_DATASET_PATH
mkdir -p .github && mkdir -p .github/workflows && touch .github/workflows/main.yml mkdir -p .github && mkdir -p .github/workflows && touch .github/workflows/main.yml
curl https://raw.githubusercontent.com/datopian/portaljs/main/site/public/scripts/gh-page-builder-action.yml > .github/workflows/main.yml curl https://raw.githubusercontent.com/datopian/datahub/main/site/public/scripts/gh-page-builder-action.yml > .github/workflows/main.yml
cd portal cd portal
assetPrefix='"/'$PORTAL_REPO_NAME'/"' assetPrefix='"/'$PORTAL_REPO_NAME'/"'

View File

@@ -3,7 +3,7 @@ git checkout -b gh-pages
git rm -r --cached . git rm -r --cached .
rm -rf portal rm -rf portal
mkdir -p portal mkdir -p portal
npx create-next-app portal -e https://github.com/datopian/portaljs/tree/main/examples/dataset-frictionless npx create-next-app portal -e https://github.com/datopian/datahub/tree/main/examples/dataset-frictionless
mkdir portal/public/dataset mkdir portal/public/dataset
cp -a ./data portal/public/dataset cp -a ./data portal/public/dataset