Compare commits
48 Commits
@portaljs/
...
@portaljs/
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ad5a176e85 | ||
|
|
eeb480e8cf | ||
|
|
30fcb256b2 | ||
|
|
a4f8c0ed76 | ||
|
|
829f3b1f13 | ||
|
|
836b143a31 | ||
|
|
be38086794 | ||
|
|
63d9e3b754 | ||
|
|
f86f0541eb | ||
|
|
64bc212384 | ||
|
|
1e7daf353d | ||
|
|
cc69dabf80 | ||
|
|
a5d87712e0 | ||
|
|
86834fd1a6 | ||
|
|
8a661b1617 | ||
|
|
1baebc3f3c | ||
|
|
bbac4954f5 | ||
|
|
be6b184884 | ||
|
|
64103d6488 | ||
|
|
8e3496782c | ||
|
|
e034503399 | ||
|
|
93ae498ec2 | ||
|
|
97e43fdcba | ||
|
|
32f29024f8 | ||
|
|
134f72948c | ||
|
|
c1f2c526a8 | ||
|
|
8feb87739d | ||
|
|
3a07267e44 | ||
|
|
3f19ca16ed | ||
|
|
5deabac5fe | ||
|
|
96901150c6 | ||
|
|
9ff25ed7c4 | ||
|
|
8f884fceab | ||
|
|
7094eded50 | ||
|
|
30e7c6379f | ||
|
|
feada58932 | ||
|
|
31406d48e3 | ||
|
|
d6bf344ca3 | ||
|
|
d1a5138c6e | ||
|
|
a6047a9341 | ||
|
|
a4e60540ae | ||
|
|
e4c456c237 | ||
|
|
ce9ebbf41e | ||
|
|
a8fb176bcc | ||
|
|
2ac82367c5 | ||
|
|
85de6f7878 | ||
|
|
1a8e7ac06e | ||
|
|
4355efe0c4 |
@@ -4,7 +4,7 @@ title: Developer docs for contributors
|
||||
|
||||
## Our repository
|
||||
|
||||
https://github.com/datopian/portaljs
|
||||
https://github.com/datopian/datahub
|
||||
|
||||
Structure:
|
||||
|
||||
@@ -17,7 +17,7 @@ Structure:
|
||||
|
||||
## 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:
|
||||
|
||||
@@ -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.
|
||||
|
||||
> [!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
|
||||
|
||||
|
||||
@@ -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 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
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
```
|
||||
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>
|
||||
```
|
||||
|
||||
@@ -19,7 +19,7 @@ npm run dev
|
||||
|
||||
Congratulations, you now have something similar to this running on `http://localhost:4200`
|
||||

|
||||
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
|
||||

|
||||
|
||||
## Deployment
|
||||
|
||||
@@ -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`.
|
||||
|
||||
[fd]: https://frictionlessdata.io/data-packages/
|
||||
[fd]: https://specs.frictionlessdata.io/data-package/
|
||||
|
||||
## How to use
|
||||
|
||||
|
||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -49897,7 +49897,7 @@
|
||||
},
|
||||
"packages/components": {
|
||||
"name": "@portaljs/components",
|
||||
"version": "0.6.0",
|
||||
"version": "1.2.0",
|
||||
"dependencies": {
|
||||
"@githubocto/flat-ui": "^0.14.1",
|
||||
"@heroicons/react": "^2.0.17",
|
||||
@@ -50383,7 +50383,7 @@
|
||||
},
|
||||
"packages/remark-wiki-link": {
|
||||
"name": "@portaljs/remark-wiki-link",
|
||||
"version": "1.1.3",
|
||||
"version": "1.2.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mdast-util-to-markdown": "^1.5.0",
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
import 'tailwindcss/tailwind.css'
|
||||
import '../src/index.css'
|
||||
|
||||
|
||||
import type { Preview } from '@storybook/react';
|
||||
|
||||
window.process = {
|
||||
...window.process,
|
||||
env:{
|
||||
...window.process?.env,
|
||||
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
const preview: Preview = {
|
||||
parameters: {
|
||||
actions: { argTypesRegex: '^on[A-Z].*' },
|
||||
|
||||
@@ -1,5 +1,35 @@
|
||||
# @portaljs/components
|
||||
|
||||
## 1.2.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [`eeb480e8`](https://github.com/datopian/datahub/commit/eeb480e8cff2d11072ace55ad683a65f54f5d07a) Thanks [@olayway](https://github.com/olayway)! - Adjust `xAxisTimeUnit` property in LineChart to allow for passing `yearmonth`.
|
||||
|
||||
## 1.2.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [`836b143a`](https://github.com/datopian/datahub/commit/836b143a3178b893b1aae3fb511d795dd3a63545) Thanks [@olayway](https://github.com/olayway)! - Fix: make tileLayerName in Map optional.
|
||||
|
||||
## 1.2.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- [#1338](https://github.com/datopian/datahub/pull/1338) [`63d9e3b7`](https://github.com/datopian/datahub/commit/63d9e3b7543c38154e6989ef1cc1d694ae9fc4f8) Thanks [@olayway](https://github.com/olayway)! - Support for plotting multiple series in LineChart component.
|
||||
|
||||
## 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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@portaljs/components",
|
||||
"version": "1.0.0",
|
||||
"version": "1.2.2",
|
||||
"type": "module",
|
||||
"description": "https://portaljs.org",
|
||||
"keywords": [
|
||||
|
||||
@@ -11,7 +11,7 @@ export function Iframe({ data, style }: IframeProps) {
|
||||
return (
|
||||
<iframe
|
||||
src={url}
|
||||
style={style ?? { width: `100%`, height: `100%` }}
|
||||
style={style ?? { width: `100%`, height: `600px` }}
|
||||
></iframe>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import loadData from '../lib/loadData';
|
||||
import { Data } from '../types/properties';
|
||||
|
||||
type AxisType = 'quantitative' | 'temporal';
|
||||
type TimeUnit = 'year' | undefined; // or ...
|
||||
type TimeUnit = 'year' | 'yearmonth' | undefined; // or ...
|
||||
|
||||
export type LineChartProps = {
|
||||
data: Omit<Data, 'csv'>;
|
||||
@@ -13,9 +13,10 @@ export type LineChartProps = {
|
||||
xAxis: string;
|
||||
xAxisType?: AxisType;
|
||||
xAxisTimeUnit?: TimeUnit;
|
||||
yAxis: string;
|
||||
yAxis: string | string[];
|
||||
yAxisType?: AxisType;
|
||||
fullWidth?: boolean;
|
||||
symbol?: string;
|
||||
};
|
||||
|
||||
export function LineChart({
|
||||
@@ -26,6 +27,7 @@ export function LineChart({
|
||||
xAxisTimeUnit = 'year', // TODO: defaults to undefined would probably work better... keeping it as it's for compatibility purposes
|
||||
yAxis,
|
||||
yAxisType = 'quantitative',
|
||||
symbol,
|
||||
}: LineChartProps) {
|
||||
const url = data.url;
|
||||
const values = data.values;
|
||||
@@ -33,6 +35,7 @@ export function LineChart({
|
||||
|
||||
// By default, assumes data is an Array...
|
||||
const [specData, setSpecData] = useState<any>({ name: 'table' });
|
||||
const isMultiYAxis = Array.isArray(yAxis);
|
||||
|
||||
const spec = {
|
||||
$schema: 'https://vega.github.io/schema/vega-lite/v5.json',
|
||||
@@ -46,6 +49,11 @@ export function LineChart({
|
||||
tooltip: true,
|
||||
},
|
||||
data: specData,
|
||||
...(isMultiYAxis
|
||||
? {
|
||||
transform: [{ fold: yAxis, as: ['key', 'value'] }],
|
||||
}
|
||||
: {}),
|
||||
selection: {
|
||||
grid: {
|
||||
type: 'interval',
|
||||
@@ -59,9 +67,25 @@ export function LineChart({
|
||||
type: xAxisType,
|
||||
},
|
||||
y: {
|
||||
field: yAxis,
|
||||
field: isMultiYAxis ? 'value' : yAxis,
|
||||
type: yAxisType,
|
||||
},
|
||||
...(symbol
|
||||
? {
|
||||
color: {
|
||||
field: symbol,
|
||||
type: 'nominal',
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
...(isMultiYAxis
|
||||
? {
|
||||
color: {
|
||||
field: 'key',
|
||||
type: 'nominal',
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
},
|
||||
} as any;
|
||||
|
||||
|
||||
@@ -12,8 +12,32 @@ import {
|
||||
|
||||
import 'leaflet/dist/leaflet.css';
|
||||
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 = {
|
||||
tileLayerName?: TileLayerPreset;
|
||||
tileLayerOptions?: TileLayerSettings | undefined;
|
||||
layers: {
|
||||
data: GeospatialData;
|
||||
name: string;
|
||||
@@ -36,7 +60,19 @@ export type MapProps = {
|
||||
};
|
||||
};
|
||||
|
||||
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({
|
||||
tileLayerName = tileLayerDefaultName || 'OpenStreetMap',
|
||||
tileLayerOptions,
|
||||
layers = [
|
||||
{
|
||||
data: null,
|
||||
@@ -54,6 +90,95 @@ export function Map({
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
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(() => {
|
||||
const loadDataPromises = layers.map(async (layer) => {
|
||||
const url = layer.data.url;
|
||||
@@ -100,6 +225,7 @@ export function Map({
|
||||
</div>
|
||||
) : (
|
||||
<MapContainer
|
||||
key={layersData}
|
||||
center={[center.latitude, center.longitude]}
|
||||
zoom={zoom}
|
||||
scrollWheelZoom={false}
|
||||
@@ -144,10 +270,8 @@ export function Map({
|
||||
map.target.fitBounds(layerToZoomBounds);
|
||||
}}
|
||||
>
|
||||
<TileLayer
|
||||
attribution='© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
|
||||
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
|
||||
/>
|
||||
{tileLayerData.url && <TileLayer {...tileLayerData} />}
|
||||
|
||||
<LayersControl position="bottomright">
|
||||
{layers.map((layer) => {
|
||||
const data = layersData.find(
|
||||
|
||||
1211
packages/components/src/lib/tileLayerPresets.tsx
Normal file
1211
packages/components/src/lib/tileLayerPresets.tsx
Normal file
File diff suppressed because it is too large
Load Diff
@@ -28,6 +28,6 @@ export const Normal: Story = {
|
||||
data: {
|
||||
url: 'https://app.powerbi.com/view?r=eyJrIjoiYzBmN2Q2MzYtYzE3MS00ODkxLWE5OWMtZTQ2MjBlMDljMDk4IiwidCI6Ijk1M2IwZjgzLTFjZTYtNDVjMy04MmM5LTFkODQ3ZTM3MjMzOSIsImMiOjh9',
|
||||
},
|
||||
style: { width: `100%`, height: `100%` },
|
||||
style: { width: `100%`, height: `600px` },
|
||||
},
|
||||
};
|
||||
|
||||
@@ -30,11 +30,15 @@ Must be an object with one of the following properties: `url` or `values` \n\n \
|
||||
},
|
||||
yAxis: {
|
||||
description:
|
||||
'Name of the column header or object property that represents the Y-axis on the data.',
|
||||
'Name of the column headers or object properties that represent the Y-axis on the data.',
|
||||
},
|
||||
yAxisType: {
|
||||
description: 'Type of the Y-axis',
|
||||
},
|
||||
symbol: {
|
||||
description:
|
||||
'Name of the column header or object property that represents a series for multiple series.',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -60,6 +64,51 @@ export const FromDataPoints: Story = {
|
||||
},
|
||||
};
|
||||
|
||||
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 = {
|
||||
name: 'Line chart from URL',
|
||||
args: {
|
||||
|
||||
@@ -43,6 +43,10 @@ type Story = StoryObj<MapProps>;
|
||||
export const GeoJSONPolygons: Story = {
|
||||
name: 'GeoJSON polygons map',
|
||||
args: {
|
||||
tileLayerName:'MapBox',
|
||||
tileLayerOptions:{
|
||||
accessToken : 'pk.eyJ1Ijoid2lsbHktcGFsbWFyZWpvIiwiYSI6ImNqNzk5NmRpNDFzb2cyeG9sc2luMHNjajUifQ.lkoVRFSI8hOLH4uJeOzwXw',
|
||||
},
|
||||
layers: [
|
||||
{
|
||||
data: {
|
||||
|
||||
@@ -53,7 +53,7 @@ export const Nav: React.FC<Props> = ({
|
||||
<nav className="flex justify-between">
|
||||
{/* Mobile navigation */}
|
||||
<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>
|
||||
{/* Non-mobile navigation */}
|
||||
<div className="flex flex-none items-center">
|
||||
|
||||
@@ -4,20 +4,16 @@ import { useRouter } from "next/router.js";
|
||||
import { useEffect, useState } from "react";
|
||||
import { SearchContext, SearchField } from "../Search";
|
||||
import { MenuIcon, CloseIcon } from "../Icons";
|
||||
import { NavLink, SearchProviderConfig } from "../types";
|
||||
import type { NavConfig, ThemeConfig } from "./Nav";
|
||||
|
||||
interface Props extends React.PropsWithChildren {
|
||||
author?: string;
|
||||
links?: Array<NavLink>;
|
||||
search?: SearchProviderConfig;
|
||||
}
|
||||
interface Props extends NavConfig, ThemeConfig, React.PropsWithChildren {}
|
||||
|
||||
// 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> = ({
|
||||
children,
|
||||
title,
|
||||
links,
|
||||
search,
|
||||
author,
|
||||
}) => {
|
||||
const router = useRouter();
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
@@ -77,8 +73,8 @@ export const NavMobile: React.FC<Props> = ({
|
||||
legacyBehavior
|
||||
>
|
||||
{/* <Logomark className="h-9 w-9" /> */}
|
||||
<div className="font-extrabold text-primary dark:text-primary-dark text-2xl ml-6">
|
||||
{author}
|
||||
<div className="font-extrabold text-primary dark:text-primary-dark text-lg ml-6">
|
||||
{title}
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
@@ -106,9 +102,7 @@ export const NavMobile: React.FC<Props> = ({
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
{/* <div className="pt-6 border border-t-2">
|
||||
{children}
|
||||
</div> */}
|
||||
<div className="pt-6">{children}</div>
|
||||
</Dialog.Panel>
|
||||
</Dialog>
|
||||
</>
|
||||
|
||||
@@ -38,6 +38,5 @@ const defaultPathToPermalinkFunc = (
|
||||
.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
|
||||
return permalink.length > 0 ? permalink : "/"; // for home page
|
||||
};
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
import * as path from "path";
|
||||
// import * as url from "url";
|
||||
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(
|
||||
".",
|
||||
"test/fixtures/content"
|
||||
@@ -12,12 +9,12 @@ const markdownFolder = path.join(
|
||||
describe("getPermalinks", () => {
|
||||
test("should return an array of permalinks", () => {
|
||||
const expectedPermalinks = [
|
||||
"/", // /index.md
|
||||
"/README",
|
||||
"/abc",
|
||||
"/blog/first-post",
|
||||
"/blog/Second Post",
|
||||
"/blog/third-post",
|
||||
"/blog", // /blog/index.md
|
||||
"/blog/README",
|
||||
"/blog/tutorials/first-tutorial",
|
||||
"/assets/Pasted Image 123.png",
|
||||
];
|
||||
@@ -28,35 +25,4 @@ describe("getPermalinks", () => {
|
||||
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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -159,11 +159,11 @@ describe("micromark-extension-wiki-link", () => {
|
||||
});
|
||||
expect(serialized).toBe(
|
||||
'<p><img src="My Image.jpg" alt="My Image.jpg" class="internal" width="200" height="200" /></p>'
|
||||
);
|
||||
});
|
||||
|
||||
);
|
||||
});
|
||||
|
||||
// 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", {
|
||||
extensions: [syntax()],
|
||||
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", () => {
|
||||
test("parsers a transclusion as a regular wiki link", () => {
|
||||
const serialized = micromark("![[Some Page]]", "ascii", {
|
||||
|
||||
@@ -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", () => {
|
||||
test("replaces a transclusion with a regular wiki link", () => {
|
||||
const processor = unified().use(markdown).use(wikiLinkPlugin);
|
||||
|
||||
@@ -22,11 +22,41 @@ const items = [
|
||||
sourceUrl: 'https://github.com/FCSCOpendata/frontend',
|
||||
},
|
||||
{
|
||||
title: 'Datahub Open Data',
|
||||
href: 'https://opendata.datahub.io/',
|
||||
image: '/images/showcases/datahub.webp',
|
||||
description: 'Demo Data Portal by DataHub',
|
||||
title: 'Frictionless Data',
|
||||
href: 'https://datahub.io/core/co2-ppm',
|
||||
repository: 'https://github.com/datopian/datahub/tree/main/examples/dataset-frictionless',
|
||||
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() {
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
export default function ShowcasesItem({ item }) {
|
||||
return (
|
||||
<a
|
||||
className="rounded overflow-hidden group relative border-1 shadow-lg"
|
||||
target="_blank"
|
||||
href={item.href}
|
||||
>
|
||||
<div className="rounded overflow-hidden group relative border-1 shadow-lg">
|
||||
<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"
|
||||
style={{ backgroundImage: `url(${item.image})` }}
|
||||
@@ -16,9 +12,48 @@ export default function ShowcasesItem({ item }) {
|
||||
<div className="text-center text-primary-dark">
|
||||
<span className="text-xl font-semibold">{item.title}</span>
|
||||
<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>
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ authors: ['Luccas Mateus']
|
||||
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.
|
||||
|
||||

|
||||

|
||||
@@ -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:
|
||||
|
||||
```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.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
const config = {
|
||||
title: 'PortalJS - The JavaScript framework for data portals.',
|
||||
title: 'DataHub PortalJS - The JavaScript framework for data portals.',
|
||||
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: {
|
||||
default: 'dark',
|
||||
toggleIcon: '/images/theme-button.svg',
|
||||
@@ -11,19 +11,18 @@ const config = {
|
||||
authorUrl: 'https://datopian.com/',
|
||||
navbarTitle: {
|
||||
// logo: "/images/logo.svg",
|
||||
text: '🌀 PortalJS',
|
||||
text: '🌀 DataHub PortalJS',
|
||||
// version: "Alpha",
|
||||
},
|
||||
navLinks: [
|
||||
{ name: 'Docs', href: '/docs' },
|
||||
// { name: "Components", href: "/docs/components" },
|
||||
{ name: 'Blog', href: '/blog' },
|
||||
{ name: 'Showcases', href: '/#showcases' },
|
||||
{ name: 'Howtos', href: '/howtos' },
|
||||
{ name: 'Guide', href: '/guide' },
|
||||
{
|
||||
name: 'Examples',
|
||||
href: '/examples/'
|
||||
name: 'Showcases',
|
||||
href: '/showcases/'
|
||||
},
|
||||
{
|
||||
name: 'Components',
|
||||
@@ -68,8 +67,8 @@ const config = {
|
||||
cardType: 'summary_large_image',
|
||||
},
|
||||
},
|
||||
github: 'https://github.com/datopian/portaljs',
|
||||
discord: 'https://discord.gg/xfFDMPU9dC',
|
||||
github: 'https://github.com/datopian/datahub',
|
||||
discord: 'https://discord.gg/KrRzMKU',
|
||||
tableOfContents: true,
|
||||
analytics: 'G-96GWZHMH57',
|
||||
// editLinkShow: true,
|
||||
|
||||
@@ -26,7 +26,7 @@ Below are some screenshots:
|
||||
- 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>
|
||||
```
|
||||
|
||||
@@ -49,7 +49,7 @@ If yo go to any one of those pages by clicking on `More info` you will see somet
|
||||
|
||||
## Deployment
|
||||
|
||||
[](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)
|
||||
[](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.
|
||||
|
||||
@@ -70,6 +70,6 @@ npm run start
|
||||
|
||||
## 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)
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
```
|
||||
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>
|
||||
```
|
||||
|
||||
@@ -56,7 +56,7 @@ If you navigate to any of the dataset pages by clicking on the dataset title you
|
||||
|
||||
## Deployment
|
||||
|
||||
[](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)
|
||||
[](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.
|
||||
|
||||
@@ -158,6 +158,6 @@ Thanks to TypeScript, you can get a list of all the API methods in `@portaljs/ck
|
||||
|
||||
## 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/)
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||

|
||||
@@ -33,7 +33,7 @@ Run the following commands:
|
||||
|
||||
|
||||
```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>
|
||||
```
|
||||
|
||||
@@ -61,7 +61,7 @@ Congratulations, your new app is now running at http://localhost:3000.
|
||||
|
||||
## Deployment
|
||||
|
||||
[](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fdatopian%2Fportaljs%2Ftree%2Fmain%2Fexamples%2Fgithub-backed-catalog)
|
||||
[](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.
|
||||
|
||||
@@ -119,5 +119,5 @@ npm run start
|
||||
|
||||
## 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)
|
||||
|
||||
@@ -3,9 +3,9 @@ title: Getting Started
|
||||
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
|
||||
|
||||
@@ -16,10 +16,10 @@ If you have questions about anything related to PortalJS, you're always welcome
|
||||
|
||||
### Create a PortalJS app
|
||||
|
||||
To create a PortalJS app, open your terminal, cd into the directory you’d like to create the app in, and run the following command:
|
||||
To create a DataHub PortalJS app, open your terminal, cd into the directory you’d like to create the app in, and run the following command:
|
||||
|
||||
```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]
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
# Examples
|
||||
|
||||
For now, see the examples folder in github:
|
||||
|
||||
https://github.com/datopian/portaljs/tree/main/examples
|
||||
@@ -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/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).
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ function MyApp({ Component, pageProps }) {
|
||||
<DefaultSeo
|
||||
defaultTitle={siteConfig.title}
|
||||
description={siteConfig.description}
|
||||
titleTemplate="PortalJS - %s"
|
||||
titleTemplate="DataHub PortalJS - %s"
|
||||
{...siteConfig.nextSeo}
|
||||
/>
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ export default function Home({ sidebarTree }) {
|
||||
sidebarTree={sidebarTree}
|
||||
>
|
||||
<Features />
|
||||
<Showcases />
|
||||
|
||||
<Community />
|
||||
</Layout>
|
||||
</>
|
||||
|
||||
8
site/pages/showcases.tsx
Normal file
8
site/pages/showcases.tsx
Normal file
@@ -0,0 +1,8 @@
|
||||
import Layout from "@/components/Layout";
|
||||
import Showcases from "@/components/Showcases";
|
||||
|
||||
export default function ShowcasesList() {
|
||||
return (
|
||||
<Layout><Showcases/></Layout>
|
||||
)
|
||||
}
|
||||
BIN
site/public/images/showcases/fivethirtyeight.png
Normal file
BIN
site/public/images/showcases/fivethirtyeight.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 66 KiB |
BIN
site/public/images/showcases/frictionless-capture.png
Normal file
BIN
site/public/images/showcases/frictionless-capture.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 70 KiB |
BIN
site/public/images/showcases/github-datasets.png
Normal file
BIN
site/public/images/showcases/github-datasets.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 41 KiB |
BIN
site/public/images/showcases/openspending.png
Normal file
BIN
site/public/images/showcases/openspending.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 62 KiB |
BIN
site/public/images/showcases/turing.png
Normal file
BIN
site/public/images/showcases/turing.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 88 KiB |
@@ -1,7 +1,7 @@
|
||||
#!/bin/bash
|
||||
rm -rf 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
|
||||
|
||||
cp -a ./data portal/public/dataset
|
||||
@@ -12,7 +12,7 @@ PORTAL_DATASET_PATH=$PWD"/portal/public/dataset"
|
||||
export PORTAL_DATASET_PATH
|
||||
|
||||
mkdir -p .github && mkdir -p .github/workflows && touch .github/workflows/main.yml
|
||||
curl https://raw.githubusercontent.com/datopian/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
|
||||
assetPrefix='"/'$PORTAL_REPO_NAME'/"'
|
||||
|
||||
@@ -3,7 +3,7 @@ git checkout -b gh-pages
|
||||
git rm -r --cached .
|
||||
rm -rf portal
|
||||
mkdir -p portal
|
||||
npx create-next-app portal -e https://github.com/datopian/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
|
||||
|
||||
cp -a ./data portal/public/dataset
|
||||
|
||||
Reference in New Issue
Block a user