Compare commits
84 Commits
examples-d
...
feature/se
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d367deaea3 | ||
|
|
3e9eadcc69 | ||
|
|
da226ef205 | ||
|
|
a37a31f89a | ||
|
|
03c27df800 | ||
|
|
d198130038 | ||
|
|
06209877ea | ||
|
|
822a3ce5ec | ||
|
|
1f06c67d13 | ||
|
|
9dea140859 | ||
|
|
d5899b22ab | ||
|
|
dc895ed277 | ||
|
|
7315df8a86 | ||
|
|
349f5bea66 | ||
|
|
6aef860a81 | ||
|
|
e908cb9344 | ||
|
|
1a22e54d5b | ||
|
|
172b4b71d4 | ||
|
|
3873852567 | ||
|
|
5e349855a2 | ||
|
|
40bd9e0311 | ||
|
|
b437b58d06 | ||
|
|
c3137ba1cb | ||
|
|
2e13c1b738 | ||
|
|
122870a23e | ||
|
|
4e282e0d86 | ||
|
|
6020f76adb | ||
|
|
f3c2a2ffa7 | ||
|
|
11659a838b | ||
|
|
58b7b4e753 | ||
|
|
7cf8c31e53 | ||
|
|
df000b9e8f | ||
|
|
77e9f58899 | ||
|
|
0737aaafb2 | ||
|
|
d798f402f6 | ||
|
|
80c6221a05 | ||
|
|
f04b86dda4 | ||
|
|
0fd3ee9912 | ||
|
|
cb0b9b1f14 | ||
|
|
9ee4376abf | ||
|
|
5a0ddd91ce | ||
|
|
d097bc765b | ||
|
|
b283fc1e99 | ||
|
|
0511e00d83 | ||
|
|
c8afa775d4 | ||
|
|
7ba9b5157e | ||
|
|
6c2a1ea125 | ||
|
|
343faf72cf | ||
|
|
1eb3f7367b | ||
|
|
8cdf54397f | ||
|
|
fb94cb9ce9 | ||
|
|
4595cd2231 | ||
|
|
aa2c8aac04 | ||
|
|
f2e5459297 | ||
|
|
e111adfe73 | ||
|
|
492c21ca4e | ||
|
|
0581357df8 | ||
|
|
15ceeec035 | ||
|
|
1caabcf6b4 | ||
|
|
b548dfd113 | ||
|
|
c5ee257d48 | ||
|
|
8d83f3a900 | ||
|
|
add2f6d0f3 | ||
|
|
dfab6aa318 | ||
|
|
f96fb562fe | ||
|
|
c4bf5bd054 | ||
|
|
c706575ae4 | ||
|
|
ed8de380a9 | ||
|
|
33521916d6 | ||
|
|
04206457a4 | ||
|
|
8a5acb7012 | ||
|
|
32493a2014 | ||
|
|
b34220cac7 | ||
|
|
44b37e27d9 | ||
|
|
6d3e571151 | ||
|
|
0e997f71e5 | ||
|
|
4a41d517ee | ||
|
|
c79b69ffe6 | ||
|
|
2ad6551a44 | ||
|
|
5de9888c02 | ||
|
|
5a517d714a | ||
|
|
746c77de11 | ||
|
|
9e256b9bf1 | ||
|
|
4bfcd4373b |
@@ -40,16 +40,25 @@ export default function Home({
|
||||
});
|
||||
|
||||
return (
|
||||
<main className="flex min-h-screen flex-col items-center p-24 bg-zinc-900">
|
||||
<DatasetSearchForm
|
||||
options={options}
|
||||
setOptions={setOptions}
|
||||
groups={groups}
|
||||
orgs={orgs}
|
||||
/>
|
||||
<div className="bg-white p-8 my-4 rounded-lg">
|
||||
<ListOfDatasets options={options} setOptions={setOptions} ckan={ckan} />{' '}
|
||||
</div>
|
||||
</main>
|
||||
<div>
|
||||
<main className="py-12 bg-zinc-900">
|
||||
<DatasetSearchForm
|
||||
options={options}
|
||||
setOptions={setOptions}
|
||||
groups={groups}
|
||||
orgs={orgs}
|
||||
/>
|
||||
<div
|
||||
className="bg-white p-8 mx-auto my-4 rounded-lg"
|
||||
style={{ width: 'min(1100px, 95vw)' }}
|
||||
>
|
||||
<ListOfDatasets
|
||||
options={options}
|
||||
setOptions={setOptions}
|
||||
ckan={ckan}
|
||||
/>{' '}
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
5
examples/learn/next.config.js
Normal file
@@ -0,0 +1,5 @@
|
||||
const nextConfig = {
|
||||
swcMinify: false
|
||||
}
|
||||
|
||||
module.exports = nextConfig
|
||||
@@ -7,13 +7,21 @@ import { Mermaid } from '@portaljs/core';
|
||||
// to handle import statements. Instead, you must include components in scope
|
||||
// here.
|
||||
const components = {
|
||||
Table: dynamic(() => import('@portaljs/components').then(mod => mod.Table)),
|
||||
Catalog: dynamic(() => import('@portaljs/components').then(mod => mod.Catalog)),
|
||||
Table: dynamic(() => import('@portaljs/components').then((mod) => mod.Table)),
|
||||
Catalog: dynamic(() =>
|
||||
import('@portaljs/components').then((mod) => mod.Catalog)
|
||||
),
|
||||
mermaid: Mermaid,
|
||||
Vega: dynamic(() => import('@portaljs/components').then(mod => mod.Vega)),
|
||||
VegaLite: dynamic(() => import('@portaljs/components').then(mod => mod.VegaLite)),
|
||||
LineChart: dynamic(() => import('@portaljs/components').then(mod => mod.LineChart)),
|
||||
FlatUiTable: dynamic(() => import('@portaljs/components').then(mod => mod.FlatUiTable)),
|
||||
Vega: dynamic(() => import('@portaljs/components').then((mod) => mod.Vega)),
|
||||
VegaLite: dynamic(() =>
|
||||
import('@portaljs/components').then((mod) => mod.VegaLite)
|
||||
),
|
||||
LineChart: dynamic(() =>
|
||||
import('@portaljs/components').then((mod) => mod.LineChart)
|
||||
),
|
||||
FlatUiTable: dynamic(() =>
|
||||
import('@portaljs/components').then((mod) => mod.FlatUiTable)
|
||||
),
|
||||
} as any;
|
||||
|
||||
export default function DRD({ source }: { source: any }) {
|
||||
|
||||
@@ -16,6 +16,8 @@ We welcome contributions from anyone who is interested in improving OpenSpending
|
||||
|
||||
The main platform for communication and collaboration for OpenSpending is Github, if you want to interact with us you can o so by submitting an issue.
|
||||
|
||||
**Repo for issues:** [https://github.com/os-data/registry/issues](https://github.com/os-data/registry/issues)
|
||||
|
||||
If you want to submit a dataset for evaluation and inclusion in OpenSpending, you will need to create an issue on Github using this template:
|
||||
|
||||
```markdown
|
||||
|
||||
@@ -156,8 +156,8 @@ the key to a prosperous future.
|
||||
title: '',
|
||||
field: 'label',
|
||||
scale: {
|
||||
domain: ["Education"],
|
||||
range: ['#64b5f6']
|
||||
domain: ['Education'],
|
||||
range: ['#64b5f6'],
|
||||
},
|
||||
},
|
||||
tooltip: [
|
||||
@@ -189,4 +189,3 @@ Frankfurt is a city that's recognized the power of education, and it's using tha
|
||||
One can only hope that more cities follow suit.
|
||||
|
||||
[^1]: https://worldpopulationreview.com/world-cities/frankfurt-population
|
||||
|
||||
|
||||
284
examples/openspending/content/stories/moers-anomaly-2016.mdx
Normal file
@@ -0,0 +1,284 @@
|
||||
---
|
||||
title: An Exploration of Moers' 2016 Budget Anomaly
|
||||
date: 06/13/2023
|
||||
---
|
||||
|
||||
_Note: The currency in this post has been converted to USD using an average exchange rate of 1 EUR = 1.20 USD.
|
||||
Additionally, the text values for account groups have been loosely translated to English. You can find the
|
||||
original dataset here: [Moers budget dataset](https://www.openspending.org/@os-data/moers-all)_
|
||||
|
||||
In the fiscal landscape of Moers, Germany, 2016 stands out as an anomaly. An examination of the budget allocations
|
||||
for this particular year shows a significant surge in spending across various areas. After this anomalous
|
||||
spike, spending patterns then seemed to resume their previous trajectory of a slow, steady climb in the
|
||||
following years. While it's tempting to dismiss this as an irregularity, it's worth exploring the potential
|
||||
underlying causes of this fiscal fluctuation.
|
||||
|
||||
<VegaLite
|
||||
spec={{
|
||||
$schema: 'https://vega.github.io/schema/vega-lite/v5.json',
|
||||
title: 'Spending by Account Group',
|
||||
description: 'Spending by Account Group',
|
||||
data: {
|
||||
url: 'https://storage.openspending.org/moers-all/moers-sums.csv',
|
||||
},
|
||||
transform: [
|
||||
{
|
||||
calculate: "format(datum.amount / 1e6 * 1.20, '$,.1f') + 'M'",
|
||||
as: 'formattedAmountMillion',
|
||||
},
|
||||
{ calculate: 'datum.amount / 1e9 * 1.20', as: 'amountBillion' },
|
||||
],
|
||||
mark: 'bar',
|
||||
height: 400,
|
||||
width: 350,
|
||||
encoding: {
|
||||
x: {
|
||||
field: 'date',
|
||||
type: 'temporal',
|
||||
timeUnit: 'utcyear',
|
||||
title: 'Year',
|
||||
},
|
||||
y: {
|
||||
field: 'amount',
|
||||
type: 'quantitative',
|
||||
title: 'Spending (USD in millions)',
|
||||
axis: {
|
||||
format: '~s',
|
||||
labelExpr: "format(datum.value / 1e6 * 1.20, '$,.0f') + 'M'",
|
||||
},
|
||||
},
|
||||
color: {
|
||||
field: 'account group',
|
||||
type: 'nominal',
|
||||
title: 'Account Group',
|
||||
scale: {
|
||||
domain: [
|
||||
'Activated Own Work',
|
||||
'Balance Expenses',
|
||||
'Care Expenses',
|
||||
'Donations And General Levies',
|
||||
'Expenses For Material And Services',
|
||||
'Extraordinary Expenses',
|
||||
'Extraordinary Income',
|
||||
'Other Ordinary Expenses',
|
||||
'Other Ordinary Income',
|
||||
'Other Transfer Income',
|
||||
'Personnel Expenses',
|
||||
'Private Law Performance Fees',
|
||||
'Public-Service Performance Fees',
|
||||
'Taxes And Similar Charges',
|
||||
'Transfer Expenses',
|
||||
],
|
||||
range: [
|
||||
'#EF5350',
|
||||
'#F06292',
|
||||
'#BA68C8',
|
||||
'#9575CD',
|
||||
'#7986CB',
|
||||
'#64B5F6',
|
||||
'#4FC3F7',
|
||||
'#4DD0E1',
|
||||
'#4DB6AC',
|
||||
'#81C784',
|
||||
'#AED581',
|
||||
'#DCE775',
|
||||
'#FFF176',
|
||||
'#FFD54F',
|
||||
'#FFB74D',
|
||||
],
|
||||
},
|
||||
},
|
||||
tooltip: [
|
||||
{ field: 'account group', type: 'nominal', title: 'Account Group' },
|
||||
{
|
||||
field: 'formattedAmountMillion',
|
||||
type: 'ordinal',
|
||||
title: 'Spending (USD)',
|
||||
},
|
||||
{ field: 'date', type: 'temporal', title: 'Year', format: '%Y' }
|
||||
],
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
## Why?
|
||||
|
||||
One possible explanation could be the global refugee crisis that was unfolding around this time, particularly
|
||||
in Germany. While we can't assert this connection definitively due to the lack of specific data, it's an angle
|
||||
worth examining to better understand the budget anomaly of 2016.
|
||||
|
||||
For instance, the "Donations and General Levies" account group saw a distinct increase in 2016. This could be
|
||||
reflective of Moers' attempts to provide immediate support to incoming refugees, although we can only speculate
|
||||
at this point.
|
||||
|
||||
<VegaLite
|
||||
spec={{
|
||||
$schema: 'https://vega.github.io/schema/vega-lite/v5.json',
|
||||
title: 'Donations and General Levies',
|
||||
description: 'Donations and General Levies',
|
||||
data: {
|
||||
url: 'https://storage.openspending.org/moers-all/moers-sums.csv',
|
||||
},
|
||||
transform: [
|
||||
{ filter: "datum['account group']==='Donations And General Levies'" },
|
||||
{
|
||||
calculate: "format(datum.amount / 1e6 * 1.20, '$,.1f') + 'M'",
|
||||
as: 'formattedAmountMillion',
|
||||
},
|
||||
],
|
||||
mark: {
|
||||
type: 'area',
|
||||
line: true,
|
||||
point: true,
|
||||
},
|
||||
width: 400,
|
||||
height: 400,
|
||||
encoding: {
|
||||
x: {
|
||||
field: 'date',
|
||||
type: 'temporal',
|
||||
timeUnit: 'utcyear',
|
||||
title: 'Year',
|
||||
},
|
||||
y: {
|
||||
field: 'amount',
|
||||
type: 'quantitative',
|
||||
title: 'Spending (USD in millions)',
|
||||
axis: {
|
||||
format: '~s',
|
||||
labelExpr: "format(datum.value / 1e6 * 1.20, '$,.0f') + 'M'",
|
||||
},
|
||||
},
|
||||
color: {
|
||||
title: 'Account Group',
|
||||
field: 'account group',
|
||||
scale: {
|
||||
domain: ['Donations And General Levies'],
|
||||
range: ['#9575CD'],
|
||||
},
|
||||
},
|
||||
tooltip: [
|
||||
{ field: 'account group', type: 'nominal', title: 'Area' },
|
||||
{
|
||||
field: 'formattedAmountMillion',
|
||||
type: 'ordinal',
|
||||
title: 'Spending (USD in millions)',
|
||||
},
|
||||
{ field: 'date', type: 'temporal', title: 'Year', format: '%Y' }
|
||||
],
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
Similarly, "Expenses for Material and Services" also experienced an upswing. This could suggest
|
||||
that the town was working to establish necessary infrastructure and resources for the newcomers. It's plausible
|
||||
that procurement of essential supplies and services to aid the refugees was a factor contributing to this budget
|
||||
increase.
|
||||
|
||||
The rise in "Personnel Expenses" may suggest an expansion of the town's workforce during this period. Additional
|
||||
staffing in areas like interpretation, social work, healthcare, and administration would be necessary to handle
|
||||
the influx of refugees.
|
||||
|
||||
The two account groups that experienced the most substantial raw monetary increases were "Taxes and Similar Charges"
|
||||
and "Transfer Expenses". Both these categories more than doubled in size during the year, a significant surge
|
||||
especially considering their already high baseline spending. This could be for a variety of reasons.
|
||||
|
||||
For "Taxes and Similar Charges", the hike could indicate the town adjusting local taxation to meet the additional
|
||||
expenditures, or it could reflect an increase in income from other sources falling under this group. The influx
|
||||
of refugees and the resulting uptick in economic activity may have also led to a rise in the collection of certain
|
||||
local taxes or fees.
|
||||
|
||||
Concurrently, the "Transfer Expenses" spike could be an indication of heightened social welfare payments, costs
|
||||
associated with refugee integration, and funds transferred to other entities managing the crisis. These financial
|
||||
escalations are a testament to the extensive and multifaceted financial impact the refugee crisis had on the town's
|
||||
budget.
|
||||
|
||||
<VegaLite
|
||||
spec={{
|
||||
$schema: 'https://vega.github.io/schema/vega-lite/v5.json',
|
||||
title: 'Largest Spending by Account Group',
|
||||
description: 'Largest Spending by Account Group',
|
||||
data: {
|
||||
url: 'https://storage.openspending.org/moers-all/moers-sums.csv',
|
||||
},
|
||||
transform: [
|
||||
{
|
||||
filter: {
|
||||
field: 'account group',
|
||||
oneOf: [
|
||||
'Donations And General Levies',
|
||||
'Expenses For Material And Services',
|
||||
'Personnel Expenses',
|
||||
'Taxes And Similar Charges',
|
||||
'Transfer Expenses'
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
calculate: "format(datum.amount / 1e6 * 1.20, '$,.1f') + 'M'",
|
||||
as: 'formattedAmountMillion',
|
||||
}
|
||||
],
|
||||
mark: {
|
||||
type: 'line',
|
||||
point: true,
|
||||
},
|
||||
width: 400,
|
||||
height: 400,
|
||||
encoding: {
|
||||
x: {
|
||||
field: 'date',
|
||||
type: 'temporal',
|
||||
timeUnit: 'utcyear',
|
||||
title: 'Year'
|
||||
},
|
||||
y: {
|
||||
field: 'amount',
|
||||
type: 'quantitative',
|
||||
title: 'Spending (USD in millions)',
|
||||
axis: {
|
||||
format: '~s',
|
||||
labelExpr: "format(datum.value / 1e6 * 1.20, '$,.0f') + 'M'",
|
||||
}
|
||||
},
|
||||
color: {
|
||||
field: 'account group',
|
||||
type: 'nominal',
|
||||
title: 'Account Group',
|
||||
scale: {
|
||||
domain: [
|
||||
'Donations And General Levies',
|
||||
'Expenses For Material And Services',
|
||||
'Personnel Expenses',
|
||||
'Taxes And Similar Charges',
|
||||
'Transfer Expenses'
|
||||
],
|
||||
range: [
|
||||
'#9575CD',
|
||||
'#7986CB',
|
||||
'#AED581',
|
||||
'#FFD54F',
|
||||
'#FFB74D'
|
||||
],
|
||||
},
|
||||
},
|
||||
tooltip: [
|
||||
{ field: 'account group', type: 'nominal', title: 'Area' },
|
||||
{
|
||||
field: 'formattedAmountMillion',
|
||||
type: 'ordinal',
|
||||
title: 'Spending (USD in millions)',
|
||||
},
|
||||
{ field: 'date', type: 'temporal', title: 'Year', format: '%Y' }
|
||||
],
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
By 2017, these budget items returned to their pre-2016 levels, which may suggest that the town had successfully
|
||||
adapted to the new circumstances, perhaps by implementing more cost-effective strategies.
|
||||
|
||||
While this exploration offers a compelling perspective, it's important to note that these are theories derived
|
||||
from the available budget data and the concurrent global events of the time. It underscores how global crises,
|
||||
like the refugee crisis, may potentially impact not just national budgets, but also the fiscal management of
|
||||
smaller towns like Moers.
|
||||
@@ -0,0 +1,105 @@
|
||||
---
|
||||
title: Where does Berlin spend its ERDF benefits?
|
||||
date: 2023-06-12
|
||||
authors: ['João Demenech']
|
||||
---
|
||||
|
||||
In this data-driven story, let's analyze how the city of Berlin benefited from ERDF from 2008 to 2015.
|
||||
|
||||
## What is ERDF?
|
||||
|
||||
If you're not familiar with ERDF, it's the European Regional Development Fund. According to its [official website](https://ec.europa.eu/regional_policy/funding/erdf_en):
|
||||
|
||||
> The European Regional Development Fund (ERDF) aims to strengthen economic, social, and territorial cohesion in the European Union by correcting imbalances between its regions. In 2021-2027 it will enable investments in a smarter, greener, more connected, and more social Europe that is closer to its citizens.
|
||||
|
||||
## A look into the data
|
||||
|
||||
The dataset that will be used is this one: https://www.openspending.org/@os-data/de3-berlin-2007-2013-erdf (if you're looking for ERDF data for other cities/regions, take a look at the data catalog).
|
||||
|
||||
Here's a sample of this data:
|
||||
|
||||
<FlatUiTable url="https://storage.openspending.org/de3-berlin-2007-2013-erdf/concat.csv" />
|
||||
|
||||
Note that we will only use the rows where the "amount_kind" column is set to "total_amount".
|
||||
|
||||
## How has the total amount changed over the years?
|
||||
|
||||
The line chart below shows the sum of the total amount of EUR that was approved for each year:
|
||||
|
||||
<LineChart data="https://storage.openspending.org/de3-berlin-2007-2013-erdf/berlin-erdf-2007-2013-total-amount-apporved-per-year.csv" xAxis="approval_year" yAxis="total_amount" />
|
||||
|
||||
As you can see, the total increased significantly from 2007 to 2010, later fluctuating between €7 million and €9 million per year.
|
||||
|
||||
## Who were the top beneficiaries for each year?
|
||||
|
||||
Now, let's try to understand who were the top beneficiaries each year. To do that, let's filter the data to show only the two top beneficiaries for each year:
|
||||
|
||||
<FlatUiTable url="https://storage.openspending.org/de3-berlin-2007-2013-erdf/berlin-erdf-2007-2013-total-amount-approved-per-year-per-beneficiary--top-2.csv" />
|
||||
|
||||
Since there are many years in this range, let's split this analysis into two ranges: one for 2008-2010 and the other for 2011-2015.
|
||||
|
||||
<VegaLite spec={{
|
||||
"data": { "url": "https://storage.openspending.org/de3-berlin-2007-2013-erdf/berlin-erdf-2007-2013-total-amount-approved-per-year-per-beneficiary--top-2.csv"},
|
||||
"transform": [
|
||||
{"filter": "datum.approval_year <= 2010"},
|
||||
],
|
||||
"title": "Total Amount (EUR) x Approval Year (2007-2010)",
|
||||
"width": "container",
|
||||
"mark": {"type": "bar", "tooltip": true },
|
||||
"encoding": {
|
||||
"y": {
|
||||
"aggregate": "sum",
|
||||
"field": "total_amount",
|
||||
"title": "Total Amount (EUR)",
|
||||
"stack": null
|
||||
},
|
||||
"x": {"field": "approval_year", "title": "Approval Year"},
|
||||
"color": {
|
||||
"type": "nominal",
|
||||
"field": "beneficiary_name",
|
||||
}
|
||||
}
|
||||
}} />
|
||||
|
||||
<VegaLite spec={{
|
||||
"title": "Total Amount (EUR) x Approval Year (2011-2013)",
|
||||
"data": { "url": "https://storage.openspending.org/de3-berlin-2007-2013-erdf/berlin-erdf-2007-2013-total-amount-approved-per-year-per-beneficiary--top-2.csv"},
|
||||
"transform": [
|
||||
{"filter": "datum.approval_year > 2010"},
|
||||
],
|
||||
"width": "container",
|
||||
"mark": {"type": "bar", "tooltip": true },
|
||||
"encoding": {
|
||||
"y": {
|
||||
"aggregate": "sum",
|
||||
"field": "total_amount",
|
||||
"title": "Total Amount (EUR)",
|
||||
"stack": null
|
||||
},
|
||||
"x": {"field": "approval_year", "title": "Approval Year"},
|
||||
"color": {
|
||||
"type": "nominal",
|
||||
"field": "beneficiary_name",
|
||||
}
|
||||
}
|
||||
}} />
|
||||
|
||||
It's easy to spot some repeating colors. Now, let's see to which sector the top beneficiaries belong to:
|
||||
|
||||
<Table csv="
|
||||
Beneficiary,Sector
|
||||
D & B,Education
|
||||
Stiftung Sozialpädagogisches Institut Berlin (SPI),Education/Social work
|
||||
IB e.V.,Social work
|
||||
gsub-Projektegesellschaft mbH,Consulting
|
||||
BBW Berufsvorbereitungs-u.Ausbildungsgesellschaft,Education
|
||||
Arbeit und Bildung e.V.,Consulting
|
||||
Gesellschaft für soziale Unternehmensberatung GSUB,Consulting
|
||||
SPI Consult GmbH,Consulting
|
||||
Beuth Hochschule für Technik Berlin,Education
|
||||
TU Berlin, Fakultät II,Education
|
||||
Hochschule für Technik und Wirtschaft Berlin,Education
|
||||
WeTeK gGmbH,Education
|
||||
" />
|
||||
|
||||
Based on the sectors of the top beneficiaries, it's clear that education and professional training have been a major focus of ERDF investment in Berlin during that period of time.
|
||||
313
examples/openspending/package-lock.json
generated
@@ -14,7 +14,7 @@
|
||||
"@heroicons/react": "^2.0.18",
|
||||
"@octokit/plugin-throttling": "^5.2.2",
|
||||
"@portaljs/ckan": "^0.0.2",
|
||||
"@portaljs/components": "^0.1.8",
|
||||
"@portaljs/components": "0.1.12",
|
||||
"@portaljs/core": "^1.0.5",
|
||||
"@portaljs/remark-callouts": "^1.0.5",
|
||||
"@portaljs/remark-embed": "^1.0.4",
|
||||
@@ -1971,6 +1971,40 @@
|
||||
"@lit-labs/ssr-dom-shim": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@mapbox/jsonlint-lines-primitives": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@mapbox/jsonlint-lines-primitives/-/jsonlint-lines-primitives-2.0.2.tgz",
|
||||
"integrity": "sha512-rY0o9A5ECsTQRVhv7tL/OyDpGAoUB4tTvLiW1DSzQGq4bvTPhNw1VpSNjDJc5GFZ2XuyOtSWSVN05qOtcD71qQ==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/@mapbox/mapbox-gl-style-spec": {
|
||||
"version": "13.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@mapbox/mapbox-gl-style-spec/-/mapbox-gl-style-spec-13.28.0.tgz",
|
||||
"integrity": "sha512-B8xM7Fp1nh5kejfIl4SWeY0gtIeewbuRencqO3cJDrCHZpaPg7uY+V8abuR+esMeuOjRl5cLhVTP40v+1ywxbg==",
|
||||
"dependencies": {
|
||||
"@mapbox/jsonlint-lines-primitives": "~2.0.2",
|
||||
"@mapbox/point-geometry": "^0.1.0",
|
||||
"@mapbox/unitbezier": "^0.0.0",
|
||||
"csscolorparser": "~1.0.2",
|
||||
"json-stringify-pretty-compact": "^2.0.0",
|
||||
"minimist": "^1.2.6",
|
||||
"rw": "^1.3.3",
|
||||
"sort-object": "^0.3.2"
|
||||
},
|
||||
"bin": {
|
||||
"gl-style-composite": "bin/gl-style-composite.js",
|
||||
"gl-style-format": "bin/gl-style-format.js",
|
||||
"gl-style-migrate": "bin/gl-style-migrate.js",
|
||||
"gl-style-validate": "bin/gl-style-validate.js"
|
||||
}
|
||||
},
|
||||
"node_modules/@mapbox/mapbox-gl-style-spec/node_modules/json-stringify-pretty-compact": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/json-stringify-pretty-compact/-/json-stringify-pretty-compact-2.0.0.tgz",
|
||||
"integrity": "sha512-WRitRfs6BGq4q8gTgOy4ek7iPFXjbra0H3PmDLKm2xnZ+Gh1HUhiKGgCZkSPNULlP7mvfu6FV/mOLhCarspADQ=="
|
||||
},
|
||||
"node_modules/@mapbox/node-pre-gyp": {
|
||||
"version": "1.0.10",
|
||||
"resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.10.tgz",
|
||||
@@ -2020,6 +2054,16 @@
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
|
||||
},
|
||||
"node_modules/@mapbox/point-geometry": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@mapbox/point-geometry/-/point-geometry-0.1.0.tgz",
|
||||
"integrity": "sha512-6j56HdLTwWGO0fJPlrZtdU/B13q8Uwmo18Ck2GnGgN9PCFyKTZ3UbXeEdRFh18i9XQ92eH2VdtpJHpBD3aripQ=="
|
||||
},
|
||||
"node_modules/@mapbox/unitbezier": {
|
||||
"version": "0.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@mapbox/unitbezier/-/unitbezier-0.0.0.tgz",
|
||||
"integrity": "sha512-HPnRdYO0WjFjRTSwO3frz1wKaU649OBFPX3Zo/2WZvuRi6zMiRGui8SnPQiQABgqCf8YikDe5t3HViTVw1WUzA=="
|
||||
},
|
||||
"node_modules/@mdx-js/mdx": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@mdx-js/mdx/-/mdx-2.3.0.tgz",
|
||||
@@ -2657,6 +2701,11 @@
|
||||
"resolved": "https://registry.npmjs.org/@octokit/webhooks-types/-/webhooks-types-6.11.0.tgz",
|
||||
"integrity": "sha512-AanzbulOHljrku1NGfafxdpTCfw2ENaWzH01N2vqQM+cUFbk868Cgh0xylz0JIM9BoKbfI++bdD6EYX0Q/UTEw=="
|
||||
},
|
||||
"node_modules/@petamoriken/float16": {
|
||||
"version": "3.8.1",
|
||||
"resolved": "https://registry.npmjs.org/@petamoriken/float16/-/float16-3.8.1.tgz",
|
||||
"integrity": "sha512-oj3dU9kuMy8AqrreIboVh3KCJGSQO5T+dJ8JQFl369961jTWvPLP1GIlLy0FVoWehXLoI9BXygu/yzuNiIHBlg=="
|
||||
},
|
||||
"node_modules/@pkgr/utils": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@pkgr/utils/-/utils-2.4.1.tgz",
|
||||
@@ -2676,6 +2725,14 @@
|
||||
"url": "https://opencollective.com/unts"
|
||||
}
|
||||
},
|
||||
"node_modules/@planet/maps": {
|
||||
"version": "8.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@planet/maps/-/maps-8.1.0.tgz",
|
||||
"integrity": "sha512-THvbooWXFZYyjSdoqi6MFNqnjrfnO5Oev1tKh6ORjxFru0N69gZwUfEduRjg99VNklmaAk2BPaCatgKs5qr0XA==",
|
||||
"dependencies": {
|
||||
"react-reconciler": "^0.29.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@portaljs/ckan": {
|
||||
"version": "0.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@portaljs/ckan/-/ckan-0.0.2.tgz",
|
||||
@@ -2691,22 +2748,26 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@portaljs/components": {
|
||||
"version": "0.1.11",
|
||||
"resolved": "https://registry.npmjs.org/@portaljs/components/-/components-0.1.11.tgz",
|
||||
"integrity": "sha512-ojiZ2dtnORAlo2YycpwK+mxrseqVxrD/Z/FMejHrZ9V6pAdlLLpjMU724WTvGBbjwO/RkoVFc4x6H6ykyvMOig==",
|
||||
"version": "0.1.12",
|
||||
"resolved": "file:../../packages/components/portaljs-components-0.1.12.tgz",
|
||||
"integrity": "sha512-Cr+RQ7tkbIqtBNq5D8zeZEB2dOejxD0V78l/I4AbdjYI8jvQ4Evx6APEfhPs3im6LDEGrT28LslVTZJ6luslnw==",
|
||||
"dependencies": {
|
||||
"@githubocto/flat-ui": "^0.14.1",
|
||||
"@heroicons/react": "^2.0.17",
|
||||
"@planet/maps": "^8.1.0",
|
||||
"@tanstack/react-table": "^8.8.5",
|
||||
"chroma-js": "^2.4.2",
|
||||
"flexsearch": "0.7.21",
|
||||
"leaflet": "^1.9.4",
|
||||
"next-mdx-remote": "^4.4.1",
|
||||
"ol": "^7.4.0",
|
||||
"papaparse": "^5.4.1",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-hook-form": "^7.43.9",
|
||||
"react-leaflet": "^4.2.1",
|
||||
"react-query": "^3.39.3",
|
||||
"react-vega": "^7.6.0",
|
||||
"rollup-plugin-re": "^1.0.7",
|
||||
"vega": "5.25.0",
|
||||
"vega-lite": "5.1.0",
|
||||
"vitest": "^0.31.4"
|
||||
@@ -2909,6 +2970,16 @@
|
||||
"resolved": "https://registry.npmjs.org/@reach/observe-rect/-/observe-rect-1.2.0.tgz",
|
||||
"integrity": "sha512-Ba7HmkFgfQxZqqaeIWWkNK0rEhpxVQHIoVyW1YDSkGsGIXzcaW4deC8B0pZrNSSyLTdIk7y+5olKt5+g0GmFIQ=="
|
||||
},
|
||||
"node_modules/@react-leaflet/core": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@react-leaflet/core/-/core-2.1.0.tgz",
|
||||
"integrity": "sha512-Qk7Pfu8BSarKGqILj4x7bCSZ1pjuAPZ+qmRwH5S7mDS91VSbVVsJSrW4qA+GPrro8t69gFYVMWb1Zc4yFmPiVg==",
|
||||
"peerDependencies": {
|
||||
"leaflet": "^1.9.0",
|
||||
"react": "^18.0.0",
|
||||
"react-dom": "^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@rushstack/eslint-patch": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.3.2.tgz",
|
||||
@@ -4424,6 +4495,11 @@
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/chroma-js": {
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/chroma-js/-/chroma-js-2.4.2.tgz",
|
||||
"integrity": "sha512-U9eDw6+wt7V8z5NncY2jJfZa+hUH8XEj8FQHgFJTrUFnJfXYf4Ml4adI2vXZOjqRDpFWtYVWypDfZwnJ+HIR4A=="
|
||||
},
|
||||
"node_modules/clean-set": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/clean-set/-/clean-set-1.1.2.tgz",
|
||||
@@ -4735,6 +4811,11 @@
|
||||
"resolved": "https://registry.npmjs.org/css-unit-converter/-/css-unit-converter-1.1.2.tgz",
|
||||
"integrity": "sha512-IiJwMC8rdZE0+xiEZHeru6YoONC4rfPMqGm2W85jMIbkFvv5nFTwJVFHam2eFrN6txmoUYFAFXiv8ICVeTO0MA=="
|
||||
},
|
||||
"node_modules/csscolorparser": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/csscolorparser/-/csscolorparser-1.0.3.tgz",
|
||||
"integrity": "sha512-umPSgYwZkdFoUrH5hIq5kf0wPSXiro51nPw0j2K/c83KflkPSTBGMz6NJvMB+07VlL0y7VPo6QJcDjcgKTTm3w=="
|
||||
},
|
||||
"node_modules/cssesc": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
|
||||
@@ -5858,6 +5939,11 @@
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/earcut": {
|
||||
"version": "2.2.4",
|
||||
"resolved": "https://registry.npmjs.org/earcut/-/earcut-2.2.4.tgz",
|
||||
"integrity": "sha512-/pjZsA1b4RPHbeWZQn66SWS8nZZWLQQ23oE3Eam7aroEFGEvwKAsJfZ9ytiEMycfzXWpca4FA9QIOehf7PocBQ=="
|
||||
},
|
||||
"node_modules/ecdsa-sig-formatter": {
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
|
||||
@@ -7155,6 +7241,39 @@
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/geotiff": {
|
||||
"version": "2.0.7",
|
||||
"resolved": "https://registry.npmjs.org/geotiff/-/geotiff-2.0.7.tgz",
|
||||
"integrity": "sha512-FKvFTNowMU5K6lHYY2f83d4lS2rsCNdpUC28AX61x9ZzzqPNaWFElWv93xj0eJFaNyOYA63ic5OzJ88dHpoA5Q==",
|
||||
"dependencies": {
|
||||
"@petamoriken/float16": "^3.4.7",
|
||||
"lerc": "^3.0.0",
|
||||
"pako": "^2.0.4",
|
||||
"parse-headers": "^2.0.2",
|
||||
"quick-lru": "^6.1.1",
|
||||
"web-worker": "^1.2.0",
|
||||
"xml-utils": "^1.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.19"
|
||||
}
|
||||
},
|
||||
"node_modules/geotiff/node_modules/pako": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz",
|
||||
"integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug=="
|
||||
},
|
||||
"node_modules/geotiff/node_modules/quick-lru": {
|
||||
"version": "6.1.1",
|
||||
"resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-6.1.1.tgz",
|
||||
"integrity": "sha512-S27GBT+F0NTRiehtbrgaSE1idUAJ5bX8dPAQTdylEyNlrdcH5X4Lz7Edz3DYzecbsCluD5zO8ZNEe04z3D3u6Q==",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/get-caller-file": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
|
||||
@@ -7831,9 +7950,7 @@
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"optional": true,
|
||||
"peer": true
|
||||
]
|
||||
},
|
||||
"node_modules/ignore": {
|
||||
"version": "5.2.4",
|
||||
@@ -8784,6 +8901,16 @@
|
||||
"resolved": "https://registry.npmjs.org/layout-base/-/layout-base-1.0.2.tgz",
|
||||
"integrity": "sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg=="
|
||||
},
|
||||
"node_modules/leaflet": {
|
||||
"version": "1.9.4",
|
||||
"resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz",
|
||||
"integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA=="
|
||||
},
|
||||
"node_modules/lerc": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/lerc/-/lerc-3.0.0.tgz",
|
||||
"integrity": "sha512-Rm4J/WaHhRa93nCN2mwWDZFoRVF18G1f47C+kvQWyHGEZxFpTUi73p7lMVSAndyxGt6lJ2/CFbOcf9ra5p8aww=="
|
||||
},
|
||||
"node_modules/levn": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
|
||||
@@ -8956,14 +9083,6 @@
|
||||
"yallist": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/magic-string": {
|
||||
"version": "0.16.0",
|
||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.16.0.tgz",
|
||||
"integrity": "sha512-c4BEos3y6G2qO0B9X7K0FVLOPT9uGrjYwYRLFmDqyl5YMboUviyecnXWp94fJTSMwPw2/sf+CEYt5AGpmklkkQ==",
|
||||
"dependencies": {
|
||||
"vlq": "^0.2.1"
|
||||
}
|
||||
},
|
||||
"node_modules/make-dir": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
|
||||
@@ -9023,6 +9142,11 @@
|
||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/mapbox-to-css-font": {
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/mapbox-to-css-font/-/mapbox-to-css-font-2.4.2.tgz",
|
||||
"integrity": "sha512-f+NBjJJY4T3dHtlEz1wCG7YFlkODEjFIYlxDdLIDMNpkSksqTt+l/d4rjuwItxuzkuMFvPyrjzV2lxRM4ePcIA=="
|
||||
},
|
||||
"node_modules/markdown-extensions": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/markdown-extensions/-/markdown-extensions-1.1.1.tgz",
|
||||
@@ -11492,6 +11616,32 @@
|
||||
"node": ">= 14"
|
||||
}
|
||||
},
|
||||
"node_modules/ol": {
|
||||
"version": "7.4.0",
|
||||
"resolved": "https://registry.npmjs.org/ol/-/ol-7.4.0.tgz",
|
||||
"integrity": "sha512-bgBbiah694HhC0jt8ptEFNRXwgO8d6xWH3G97PCg4bmn9Li5nLLbi59oSrvqUI6VPVwonPQF1YcqJymxxyMC6A==",
|
||||
"dependencies": {
|
||||
"earcut": "^2.2.3",
|
||||
"geotiff": "^2.0.7",
|
||||
"ol-mapbox-style": "^10.1.0",
|
||||
"pbf": "3.2.1",
|
||||
"rbush": "^3.0.1"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/openlayers"
|
||||
}
|
||||
},
|
||||
"node_modules/ol-mapbox-style": {
|
||||
"version": "10.6.0",
|
||||
"resolved": "https://registry.npmjs.org/ol-mapbox-style/-/ol-mapbox-style-10.6.0.tgz",
|
||||
"integrity": "sha512-s86QhCoyyKVRsYkvPzzdWd///bhYh3onWrBq4lNXnCd9G/hS6AoK023kn4zlDESVlTBDTWLz8vhOistp0M3TXA==",
|
||||
"dependencies": {
|
||||
"@mapbox/mapbox-gl-style-spec": "^13.23.1",
|
||||
"mapbox-to-css-font": "^2.4.1",
|
||||
"ol": "^7.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
@@ -11638,6 +11788,11 @@
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/parse-headers": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/parse-headers/-/parse-headers-2.0.5.tgz",
|
||||
"integrity": "sha512-ft3iAoLOB/MlwbNXgzy43SWGP6sQki2jQvAyBg/zDFAgr9bfNWZIUj42Kw2eJIl8kEi4PbgE6U1Zau/HwI75HA=="
|
||||
},
|
||||
"node_modules/parse-json": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
|
||||
@@ -11735,6 +11890,18 @@
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/pbf": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/pbf/-/pbf-3.2.1.tgz",
|
||||
"integrity": "sha512-ClrV7pNOn7rtmoQVF4TS1vyU0WhYRnP92fzbfF75jAIwpnzdJXf8iTd4CMEqO4yUenH6NDqLiwjqlh6QgZzgLQ==",
|
||||
"dependencies": {
|
||||
"ieee754": "^1.1.12",
|
||||
"resolve-protobuf-schema": "^2.1.0"
|
||||
},
|
||||
"bin": {
|
||||
"pbf": "bin/pbf"
|
||||
}
|
||||
},
|
||||
"node_modules/pend": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
|
||||
@@ -12055,6 +12222,11 @@
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/protocol-buffers-schema": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/protocol-buffers-schema/-/protocol-buffers-schema-3.6.0.tgz",
|
||||
"integrity": "sha512-TdDRD+/QNdrCGCE7v8340QyuXd4kIWIgapsE2+n/SaGiSSbomYl4TjHlvIoCWRpE7wFt02EpB35VVA2ImcBVqw=="
|
||||
},
|
||||
"node_modules/proxy-from-env": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||
@@ -12173,6 +12345,19 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/quickselect": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/quickselect/-/quickselect-2.0.0.tgz",
|
||||
"integrity": "sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw=="
|
||||
},
|
||||
"node_modules/rbush": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/rbush/-/rbush-3.0.1.tgz",
|
||||
"integrity": "sha512-XRaVO0YecOpEuIvbhbpTrZgoiI6xBlz6hnlr6EHhd+0x9ase6EmeN+hdwwUaJvLcsFFQ8iWVF1GAK1yB0BWi0w==",
|
||||
"dependencies": {
|
||||
"quickselect": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react": {
|
||||
"version": "18.2.0",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz",
|
||||
@@ -12221,6 +12406,19 @@
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
|
||||
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="
|
||||
},
|
||||
"node_modules/react-leaflet": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/react-leaflet/-/react-leaflet-4.2.1.tgz",
|
||||
"integrity": "sha512-p9chkvhcKrWn/H/1FFeVSqLdReGwn2qmiobOQGO3BifX+/vV/39qhY8dGqbdcPh1e6jxh/QHriLXr7a4eLFK4Q==",
|
||||
"dependencies": {
|
||||
"@react-leaflet/core": "^2.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"leaflet": "^1.9.0",
|
||||
"react": "^18.0.0",
|
||||
"react-dom": "^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-markdown": {
|
||||
"version": "8.0.7",
|
||||
"resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-8.0.7.tgz",
|
||||
@@ -12290,6 +12488,21 @@
|
||||
"react-dom": "^16.8.0-0 || ^17.0.0-0 || ^18.0.0-0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-reconciler": {
|
||||
"version": "0.29.0",
|
||||
"resolved": "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.29.0.tgz",
|
||||
"integrity": "sha512-wa0fGj7Zht1EYMRhKWwoo1H9GApxYLBuhoAuXN0TlltESAjDssB+Apf0T/DngVqaMyPypDmabL37vw/2aRM98Q==",
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0",
|
||||
"scheduler": "^0.23.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^18.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-refresh": {
|
||||
"version": "0.14.0",
|
||||
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz",
|
||||
@@ -12828,6 +13041,14 @@
|
||||
"url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/resolve-protobuf-schema": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/resolve-protobuf-schema/-/resolve-protobuf-schema-2.1.0.tgz",
|
||||
"integrity": "sha512-kI5ffTiZWmJaS/huM8wZfEMer1eRd7oJQhDuxeCLe3t7N7mX3z94CN0xPxBQxFYQTSNz9T0i+v6inKqSdK8xrQ==",
|
||||
"dependencies": {
|
||||
"protocol-buffers-schema": "^3.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/retext": {
|
||||
"version": "8.1.0",
|
||||
"resolved": "https://registry.npmjs.org/retext/-/retext-8.1.0.tgz",
|
||||
@@ -12949,28 +13170,6 @@
|
||||
"fsevents": "~2.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/rollup-plugin-re": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/rollup-plugin-re/-/rollup-plugin-re-1.0.7.tgz",
|
||||
"integrity": "sha512-TyFf3QaV/eJ/50k4wp5BM0SodGy0Idq0uOgvA1q3gHRwgXLPVX5y3CRKkBuHzKTZPC9CTZX7igKw5UvgjDls8w==",
|
||||
"dependencies": {
|
||||
"magic-string": "^0.16.0",
|
||||
"rollup-pluginutils": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/rollup-pluginutils": {
|
||||
"version": "2.8.2",
|
||||
"resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz",
|
||||
"integrity": "sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==",
|
||||
"dependencies": {
|
||||
"estree-walker": "^0.6.1"
|
||||
}
|
||||
},
|
||||
"node_modules/rollup-pluginutils/node_modules/estree-walker": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz",
|
||||
"integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w=="
|
||||
},
|
||||
"node_modules/rtl-css-js": {
|
||||
"version": "1.16.1",
|
||||
"resolved": "https://registry.npmjs.org/rtl-css-js/-/rtl-css-js-1.16.1.tgz",
|
||||
@@ -13314,6 +13513,34 @@
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/sort-asc": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/sort-asc/-/sort-asc-0.1.0.tgz",
|
||||
"integrity": "sha512-jBgdDd+rQ+HkZF2/OHCmace5dvpos/aWQpcxuyRs9QUbPRnkEJmYVo81PIGpjIdpOcsnJ4rGjStfDHsbn+UVyw==",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/sort-desc": {
|
||||
"version": "0.1.1",
|
||||
"resolved": "https://registry.npmjs.org/sort-desc/-/sort-desc-0.1.1.tgz",
|
||||
"integrity": "sha512-jfZacW5SKOP97BF5rX5kQfJmRVZP5/adDUTY8fCSPvNcXDVpUEe2pr/iKGlcyZzchRJZrswnp68fgk3qBXgkJw==",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/sort-object": {
|
||||
"version": "0.3.2",
|
||||
"resolved": "https://registry.npmjs.org/sort-object/-/sort-object-0.3.2.tgz",
|
||||
"integrity": "sha512-aAQiEdqFTTdsvUFxXm3umdo04J7MRljoVGbBlkH7BgNsMvVNAJyGj7C/wV1A8wHWAJj/YikeZbfuCKqhggNWGA==",
|
||||
"dependencies": {
|
||||
"sort-asc": "^0.1.0",
|
||||
"sort-desc": "^0.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/source-map": {
|
||||
"version": "0.5.7",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
|
||||
@@ -15964,11 +16191,6 @@
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/vlq": {
|
||||
"version": "0.2.3",
|
||||
"resolved": "https://registry.npmjs.org/vlq/-/vlq-0.2.3.tgz",
|
||||
"integrity": "sha512-DRibZL6DsNhIgYQ+wNdWDL2SL3bKPlVrRiBqV5yuMm++op8W4kGFtaQfCs4KEJn0wBZcHVHJ3eoywX8983k1ow=="
|
||||
},
|
||||
"node_modules/warning": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz",
|
||||
@@ -16173,6 +16395,11 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/xml-utils": {
|
||||
"version": "1.7.0",
|
||||
"resolved": "https://registry.npmjs.org/xml-utils/-/xml-utils-1.7.0.tgz",
|
||||
"integrity": "sha512-bWB489+RQQclC7A9OW8e5BzbT8Tu//jtAOvkYwewFr+Q9T9KDGvfzC1lp0pYPEQPEoPQLDkmxkepSC/2gIAZGw=="
|
||||
},
|
||||
"node_modules/xtend": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
"@heroicons/react": "^2.0.18",
|
||||
"@octokit/plugin-throttling": "^5.2.2",
|
||||
"@portaljs/ckan": "^0.0.2",
|
||||
"@portaljs/components": "^0.1.8",
|
||||
"@portaljs/components": "0.1.12",
|
||||
"@portaljs/core": "^1.0.5",
|
||||
"@portaljs/remark-callouts": "^1.0.5",
|
||||
"@portaljs/remark-embed": "^1.0.4",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { AppProps } from 'next/app';
|
||||
import '@portaljs/components/styles.css';
|
||||
import './styles.css';
|
||||
import { NextSeo } from 'next-seo';
|
||||
|
||||
|
||||
@@ -4,7 +4,12 @@ import { GetStaticProps } from 'next';
|
||||
import Layout from '../../components/_shared/Layout';
|
||||
import { formatDate } from '@/utils/formatDate';
|
||||
import parse from '../../lib/markdown';
|
||||
import DataRichDocument from '../../components/DataRichDocument';
|
||||
import dynamic from 'next/dynamic';
|
||||
|
||||
const DataRichDocument = dynamic(
|
||||
() => import('../../components/DataRichDocument'),
|
||||
{ ssr: false }
|
||||
);
|
||||
|
||||
export default function Page({ source, meta }) {
|
||||
return (
|
||||
@@ -46,7 +51,10 @@ export const getStaticProps: GetStaticProps = async ({ params }) => {
|
||||
|
||||
export async function getStaticPaths() {
|
||||
const mddb = await clientPromise;
|
||||
let allDocuments = await mddb.getFiles({ extensions: ['mdx'], folder: 'stories' });
|
||||
let allDocuments = await mddb.getFiles({
|
||||
extensions: ['mdx'],
|
||||
folder: 'stories',
|
||||
});
|
||||
|
||||
const paths = allDocuments
|
||||
.filter((page) => page.metadata?.isDraft !== true)
|
||||
|
||||
1720
package-lock.json
generated
@@ -1,11 +1,12 @@
|
||||
{
|
||||
"name": "portaljs",
|
||||
"workspaces": ["./packages/*"],
|
||||
"workspaces": [
|
||||
"./packages/*"
|
||||
],
|
||||
"version": "0.0.0",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"changeset": "changeset",
|
||||
"prerelease": "nx run-many --targets=lint,test,build --projects=tag:test",
|
||||
"release": "changeset publish"
|
||||
},
|
||||
"private": true,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'tailwindcss/tailwind.css'
|
||||
import '../src/index.css'
|
||||
|
||||
|
||||
import type { Preview } from '@storybook/react';
|
||||
|
||||
const preview: Preview = {
|
||||
|
||||
@@ -1,5 +1,31 @@
|
||||
# @portaljs/components
|
||||
|
||||
## 0.3.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#980](https://github.com/datopian/portaljs/pull/980) [`38738525`](https://github.com/datopian/portaljs/commit/3873852567b1aab4827a716bd588bd5de3223e2b) Thanks [@demenech](https://github.com/demenech)! - Fix missing CSS styles for PDF component
|
||||
|
||||
## 0.3.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- [`2e13c1b7`](https://github.com/datopian/portaljs/commit/2e13c1b738ddac91a9419f5c0484406328bd1cd3) Thanks [@demenech](https://github.com/demenech)! - PDF and Excel components
|
||||
|
||||
- [#973](https://github.com/datopian/portaljs/pull/973) [`f3c2a2ff`](https://github.com/datopian/portaljs/commit/f3c2a2ffa7dcf9693bd25318c719ce58d27070b8) Thanks [@demenech](https://github.com/demenech)! - Implement <Excel /> component
|
||||
|
||||
## 0.2.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- [`80c6221a`](https://github.com/datopian/portaljs/commit/80c6221a05733f8c1dd0431bed4d72b1f9d7d636) Thanks [@luccasmmg](https://github.com/luccasmmg)! - Added the OpenLayers and the Map(Leaflet based) component
|
||||
|
||||
## 0.1.12
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#960](https://github.com/datopian/portaljs/pull/960) [`add2f6d0`](https://github.com/datopian/portaljs/commit/add2f6d0f372434eb996d59d6faf5cd06530c932) Thanks [@demenech](https://github.com/demenech)! - Fix width of the <LineChart /> component
|
||||
|
||||
## 0.1.11
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@portaljs/components",
|
||||
"version": "0.1.11",
|
||||
"version": "0.3.1",
|
||||
"type": "module",
|
||||
"description": "https://portaljs.org",
|
||||
"keywords": [
|
||||
@@ -12,12 +12,14 @@
|
||||
],
|
||||
"scripts": {
|
||||
"dev": "npm run storybook",
|
||||
"build": "tsc && vite build && npm run build-tailwind",
|
||||
"example": "vite",
|
||||
"build": "tsc && vite build && npm run build-tailwind && npm run fix-leaflet",
|
||||
"lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
|
||||
"storybook": "storybook dev -p 6006",
|
||||
"build-storybook": "storybook build",
|
||||
"build-tailwind": "NODE_ENV=production npx tailwindcss -o ./dist/styles.css --minify",
|
||||
"prepare": "npm run build"
|
||||
"build-tailwind": "NODE_ENV=production npx tailwindcss --postcss -c tailwind.config.js -i src/index.css -o ./dist/style.css --minify",
|
||||
"prepare": "npm run build",
|
||||
"fix-leaflet": "node ./scripts/fix-leaflet.cjs"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^18.2.0",
|
||||
@@ -26,19 +28,29 @@
|
||||
"dependencies": {
|
||||
"@githubocto/flat-ui": "^0.14.1",
|
||||
"@heroicons/react": "^2.0.17",
|
||||
"@planet/maps": "^8.1.0",
|
||||
"@tanstack/react-table": "^8.8.5",
|
||||
"ag-grid-react": "^30.0.4",
|
||||
"chroma-js": "^2.4.2",
|
||||
"flexsearch": "0.7.21",
|
||||
"leaflet": "^1.9.4",
|
||||
"next-mdx-remote": "^4.4.1",
|
||||
"ol": "^7.4.0",
|
||||
"papaparse": "^5.4.1",
|
||||
"postcss-url": "^10.1.3",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-hook-form": "^7.43.9",
|
||||
"react-leaflet": "^4.2.1",
|
||||
"react-query": "^3.39.3",
|
||||
"react-vega": "^7.6.0",
|
||||
"rollup-plugin-re": "^1.0.7",
|
||||
"vega": "5.25.0",
|
||||
"vega-lite": "5.1.0",
|
||||
"vitest": "^0.31.4"
|
||||
"vitest": "^0.31.4",
|
||||
"@react-pdf-viewer/core": "3.6.0",
|
||||
"@react-pdf-viewer/default-layout": "3.6.0",
|
||||
"pdfjs-dist": "2.15.349",
|
||||
"xlsx": "^0.18.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@storybook/addon-essentials": "^7.0.7",
|
||||
@@ -48,13 +60,16 @@
|
||||
"@storybook/react": "^7.0.7",
|
||||
"@storybook/react-vite": "^7.0.7",
|
||||
"@storybook/testing-library": "^0.0.14-next.2",
|
||||
"@swc/core": "^1.3.68",
|
||||
"@types/flexsearch": "^0.7.3",
|
||||
"@types/leaflet": "^1.9.3",
|
||||
"@types/papaparse": "^5.3.7",
|
||||
"@types/react": "^18.0.28",
|
||||
"@types/react-dom": "^18.0.11",
|
||||
"@typescript-eslint/eslint-plugin": "^5.57.1",
|
||||
"@typescript-eslint/parser": "^5.57.1",
|
||||
"@vitejs/plugin-react": "^4.0.0",
|
||||
"@vitejs/plugin-react-swc": "^3.3.2",
|
||||
"autoprefixer": "^10.4.14",
|
||||
"eslint": "^8.38.0",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
@@ -62,12 +77,15 @@
|
||||
"eslint-plugin-storybook": "^0.6.11",
|
||||
"json": "^11.0.0",
|
||||
"postcss": "^8.4.23",
|
||||
"postcss-import": "^15.1.0",
|
||||
"postcss-import-url": "^7.2.0",
|
||||
"prop-types": "^15.8.1",
|
||||
"storybook": "^7.0.7",
|
||||
"tailwindcss": "^3.3.2",
|
||||
"typescript": "^5.0.2",
|
||||
"vite": "^4.3.2",
|
||||
"vite-plugin-dts": "^2.3.0"
|
||||
"vite-plugin-dts": "^2.3.0",
|
||||
"vite-plugin-swc-only": "^0.1.18"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
@@ -81,7 +99,7 @@
|
||||
"require": "./dist/components.umd.js"
|
||||
},
|
||||
"./styles.css": {
|
||||
"import": "./dist/styles.css"
|
||||
"import": "./dist/style.css"
|
||||
}
|
||||
},
|
||||
"publishConfig": {
|
||||
|
||||
BIN
packages/components/portaljs-components-0.1.12.tgz
Normal file
@@ -1,6 +1,10 @@
|
||||
console.log('PostCSS');
|
||||
|
||||
export default {
|
||||
plugins: {
|
||||
'postcss-import': {},
|
||||
'postcss-url': { url: 'inline' },
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
6
packages/components/scripts/fix-leaflet.cjs
Normal file
@@ -0,0 +1,6 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const leafletPath = path.join(require.resolve('leaflet'), '../')
|
||||
|
||||
fs.cpSync(`${leafletPath}images`,'./dist/images', { recursive: true });
|
||||
99
packages/components/src/components/Excel.tsx
Normal file
@@ -0,0 +1,99 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import LoadingSpinner from './LoadingSpinner';
|
||||
import { read, utils } from 'xlsx';
|
||||
import { AgGridReact } from 'ag-grid-react';
|
||||
import 'ag-grid-community/styles/ag-grid.css';
|
||||
import 'ag-grid-community/styles/ag-theme-alpine.css';
|
||||
|
||||
export type ExcelProps = {
|
||||
url: string;
|
||||
};
|
||||
|
||||
export function Excel({ url }: ExcelProps) {
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
const [activeSheetName, setActiveSheetName] = useState<string>();
|
||||
const [workbook, setWorkbook] = useState<any>();
|
||||
const [rows, setRows] = useState<any>();
|
||||
const [cols, setCols] = useState<any>();
|
||||
|
||||
const loadSpreadsheet = (wb: any, name: string) => {
|
||||
setActiveSheetName(name);
|
||||
const ws = wb.Sheets[name];
|
||||
|
||||
const range = utils.decode_range(ws['!ref'] || 'A1');
|
||||
const columns = Array.from({ length: range.e.c + 1 }, (_, i) => ({
|
||||
field: utils.encode_col(i),
|
||||
}));
|
||||
|
||||
const rowsAr = utils.sheet_to_json(ws, { header: 1 });
|
||||
const rows = rowsAr.map((row) => {
|
||||
const obj = {};
|
||||
columns.forEach((col, i) => {
|
||||
obj[col.field] = row[i];
|
||||
});
|
||||
return obj;
|
||||
});
|
||||
|
||||
setRows(rows);
|
||||
setCols(columns);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setIsLoading(true);
|
||||
|
||||
fetch(url)
|
||||
.then((res) => res.arrayBuffer())
|
||||
.then((f) => {
|
||||
const wb = read(f);
|
||||
setWorkbook(wb);
|
||||
loadSpreadsheet(wb, wb.SheetNames[0]);
|
||||
setIsLoading(false);
|
||||
});
|
||||
}, [url]);
|
||||
|
||||
return isLoading ? (
|
||||
<div className="w-full flex items-center justify-center w-[600px] h-[300px]">
|
||||
<LoadingSpinner />
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{cols && rows && (
|
||||
<div>
|
||||
<div
|
||||
className="ag-theme-alpine"
|
||||
style={{ height: 400, width: '100%' }}
|
||||
>
|
||||
<AgGridReact
|
||||
rowData={rows}
|
||||
columnDefs={cols}
|
||||
defaultColDef={{
|
||||
resizable: true,
|
||||
minWidth: 200,
|
||||
flex: 1,
|
||||
sortable: true,
|
||||
filter: true,
|
||||
}}
|
||||
></AgGridReact>
|
||||
</div>
|
||||
<div className="border-t">
|
||||
{workbook.SheetNames.map((name: string, idx: number) => {
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
key={idx}
|
||||
className={`text-sm px-3 pb-2 pt-4 border-b border-l border-r ${
|
||||
name == activeSheetName ? 'font-semibold' : ''
|
||||
}`}
|
||||
onClick={() => loadSpreadsheet(workbook, name)}
|
||||
>
|
||||
{name}
|
||||
</button>
|
||||
</>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -35,7 +35,7 @@ export function LineChart({
|
||||
const spec = {
|
||||
$schema: 'https://vega.github.io/schema/vega-lite/v5.json',
|
||||
title,
|
||||
width: 600,
|
||||
width: 'container',
|
||||
height: 300,
|
||||
mark: {
|
||||
type: 'line',
|
||||
|
||||
233
packages/components/src/components/Map.tsx
Normal file
@@ -0,0 +1,233 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import LoadingSpinner from './LoadingSpinner';
|
||||
import loadData from '../lib/loadData';
|
||||
import chroma from 'chroma-js';
|
||||
import {
|
||||
MapContainer,
|
||||
TileLayer,
|
||||
GeoJSON as GeoJSONLayer,
|
||||
LayersControl,
|
||||
} from 'react-leaflet';
|
||||
|
||||
import 'leaflet/dist/leaflet.css';
|
||||
import * as L from 'leaflet';
|
||||
|
||||
export type MapProps = {
|
||||
layers: {
|
||||
data: string | GeoJSON.GeoJSON;
|
||||
name: string;
|
||||
colorScale?: {
|
||||
starting: string;
|
||||
ending: string;
|
||||
};
|
||||
tooltip?:
|
||||
| {
|
||||
propNames: string[];
|
||||
}
|
||||
| boolean;
|
||||
_id?: number;
|
||||
}[];
|
||||
title?: string;
|
||||
center?: { latitude: number | undefined; longitude: number | undefined };
|
||||
zoom?: number;
|
||||
};
|
||||
|
||||
export function Map({
|
||||
layers = [
|
||||
{
|
||||
data: null,
|
||||
name: null,
|
||||
colorScale: { starting: 'blue', ending: 'red' },
|
||||
tooltip: true,
|
||||
},
|
||||
],
|
||||
center = { latitude: 45, longitude: 45 },
|
||||
zoom = 2,
|
||||
title = '',
|
||||
}: MapProps) {
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
const [layersData, setLayersData] = useState<any>([]);
|
||||
|
||||
useEffect(() => {
|
||||
const loadDataPromises = layers.map(async (layer) => {
|
||||
let layerData: any;
|
||||
|
||||
if (typeof layer.data === 'string') {
|
||||
// If "data" is string, assume it's a URL
|
||||
setIsLoading(true);
|
||||
layerData = await loadData(layer.data).then((res: any) => {
|
||||
return JSON.parse(res);
|
||||
});
|
||||
} else {
|
||||
// Else, expect raw GeoJSON
|
||||
layerData = layer.data;
|
||||
}
|
||||
|
||||
if (layer.colorScale) {
|
||||
const colorScaleAr = chroma
|
||||
.scale([layer.colorScale.starting, layer.colorScale.ending])
|
||||
.mode('lch')
|
||||
.colors(layerData.features.length);
|
||||
|
||||
layerData.features.forEach((feature, i) => {
|
||||
// Only style if the feature doesn't have a color prop
|
||||
if (feature.color === undefined) {
|
||||
feature.color = colorScaleAr[i];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return { name: layer.name, data: layerData };
|
||||
});
|
||||
|
||||
Promise.all(loadDataPromises).then((values) => {
|
||||
setLayersData(values);
|
||||
setIsLoading(false);
|
||||
});
|
||||
}, []);
|
||||
|
||||
return isLoading ? (
|
||||
<div className="w-full flex items-center justify-center w-[600px] h-[300px]">
|
||||
<LoadingSpinner />
|
||||
</div>
|
||||
) : (
|
||||
<MapContainer
|
||||
center={[center.latitude, center.longitude]}
|
||||
zoom={zoom}
|
||||
scrollWheelZoom={false}
|
||||
className="h-80 w-full"
|
||||
// @ts-ignore
|
||||
whenReady={(map: any) => {
|
||||
// Enable zoom using scroll wheel
|
||||
map.target.scrollWheelZoom.enable();
|
||||
|
||||
// Create the title box
|
||||
var info = new L.Control() as any;
|
||||
|
||||
info.onAdd = function () {
|
||||
this._div = L.DomUtil.create('div', 'info');
|
||||
this.update();
|
||||
return this._div;
|
||||
};
|
||||
|
||||
info.update = function () {
|
||||
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);
|
||||
}}
|
||||
>
|
||||
<TileLayer
|
||||
attribution='© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
|
||||
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
|
||||
/>
|
||||
<LayersControl position="bottomright">
|
||||
{layers.map((layer) => {
|
||||
const data = layersData.find(
|
||||
(layerData) => layerData.name === layer.name
|
||||
)?.data;
|
||||
|
||||
return (
|
||||
data && (
|
||||
<LayersControl.Overlay key={layer.name} checked name={layer.name}>
|
||||
<GeoJSONLayer
|
||||
data={data}
|
||||
// @ts-ignore
|
||||
pointToLayer={(feature, latlng) => {
|
||||
// This resolver an issue in which the bundled map was
|
||||
// not finding the images
|
||||
const leafletBase =
|
||||
'https://unpkg.com/leaflet@1.9.4/dist/images/';
|
||||
|
||||
const icon = new L.Icon({
|
||||
iconUrl: leafletBase + 'marker-icon.png',
|
||||
iconRetinaUrl: leafletBase + 'marker-icon-2x.png',
|
||||
shadowUrl: leafletBase + 'marker-shadow.png',
|
||||
iconSize: [25, 41],
|
||||
iconAnchor: [12, 41],
|
||||
popupAnchor: [1, -34],
|
||||
tooltipAnchor: [16, -28],
|
||||
shadowSize: [41, 41],
|
||||
});
|
||||
|
||||
const iconMarker = L.marker(latlng, { icon });
|
||||
return iconMarker;
|
||||
}}
|
||||
style={(geoJsonFeature: any) => {
|
||||
// Set the fill color of each feature when appliable
|
||||
if (
|
||||
!['Point', 'MultiPoint'].includes(geoJsonFeature.type)
|
||||
) {
|
||||
return { color: geoJsonFeature?.color };
|
||||
}
|
||||
}}
|
||||
eventHandlers={{
|
||||
add: (e) => {
|
||||
const featureGroup = e.target;
|
||||
const tooltip = layer.tooltip;
|
||||
|
||||
featureGroup.eachLayer((featureLayer) => {
|
||||
const feature = featureLayer.feature;
|
||||
const geometryType = feature.geometry.type;
|
||||
|
||||
if (tooltip) {
|
||||
const featurePropNames = Object.keys(
|
||||
feature.properties
|
||||
);
|
||||
let includedFeaturePropNames;
|
||||
|
||||
if (tooltip === true) {
|
||||
includedFeaturePropNames = featurePropNames;
|
||||
} else {
|
||||
includedFeaturePropNames = tooltip.propNames.filter(
|
||||
(name) => featurePropNames.includes(name)
|
||||
);
|
||||
}
|
||||
|
||||
if (includedFeaturePropNames) {
|
||||
const tooltipContent = includedFeaturePropNames
|
||||
.map(
|
||||
(name) =>
|
||||
`<b>${name}:</b> ${feature.properties[name]}`
|
||||
)
|
||||
.join('<br />');
|
||||
|
||||
featureLayer.bindTooltip(tooltipContent, {
|
||||
direction: 'center',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
featureLayer.on({
|
||||
mouseover: (event) => {
|
||||
if (
|
||||
['Polygon', 'MultiPolygon'].includes(geometryType)
|
||||
) {
|
||||
event.target.setStyle({
|
||||
fillOpacity: 0.5,
|
||||
});
|
||||
}
|
||||
},
|
||||
mouseout: (event) => {
|
||||
if (
|
||||
['Polygon', 'MultiPolygon'].includes(geometryType)
|
||||
) {
|
||||
event.target.setStyle({
|
||||
fillOpacity: 0.2,
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
});
|
||||
},
|
||||
}}
|
||||
/>
|
||||
;
|
||||
</LayersControl.Overlay>
|
||||
)
|
||||
);
|
||||
})}
|
||||
</LayersControl>
|
||||
</MapContainer>
|
||||
);
|
||||
}
|
||||
84
packages/components/src/components/OpenLayers/Controls.jsx
Normal file
@@ -0,0 +1,84 @@
|
||||
import React, { useContext, useEffect, useState } from 'react';
|
||||
export const Controls = ({ children }) => {
|
||||
return <div>{children}</div>;
|
||||
};
|
||||
|
||||
import { FullScreen, Zoom } from 'ol/control';
|
||||
import { MapContext } from './Map';
|
||||
|
||||
export const FullScreenControl = () => {
|
||||
const { map } = useContext(MapContext);
|
||||
useEffect(() => {
|
||||
if (!map) return;
|
||||
let fullScreenControl = new FullScreen({
|
||||
className: 'ml-1 flex flex-col w-8 items-center mt-2',
|
||||
activeClassName:
|
||||
'w-full inline-flex justify-center items-center rounded-t-md bg-white px-2 py-2 text-gray-400 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:z-10 text-sm',
|
||||
inactiveClassName:
|
||||
'inline-flex w-full justify-center items-center rounded-t-md bg-white px-2 py-2 text-gray-400 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:z-10 text-sm',
|
||||
});
|
||||
let zoomControl = new Zoom({
|
||||
className: 'ml-1 flex flex-col w-8 items-center',
|
||||
zoomInClassName:
|
||||
'inline-flex w-full justify-center items-center bg-white px-2 py-2 text-gray-400 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:z-10 text-sm',
|
||||
zoomOutClassName:
|
||||
'inline-flex w-full justify-center items-center rounded-b-md bg-white px-2 py-2 text-gray-400 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:z-10 text-sm',
|
||||
});
|
||||
map.controls.push(fullScreenControl);
|
||||
map.controls.push(zoomControl);
|
||||
|
||||
return () => {
|
||||
map.controls.remove(zoomControl);
|
||||
map.controls.remove(fullScreenControl);
|
||||
};
|
||||
}, [map]);
|
||||
return null;
|
||||
};
|
||||
|
||||
//build a list of checkboxes in react
|
||||
|
||||
export const ListOfCheckboxes = ({ layers, shownLayers, setShownLayers }) => {
|
||||
//layers is an array of url and name
|
||||
function addLayer(layer) {
|
||||
setShownLayers([...shownLayers, layer.url]);
|
||||
}
|
||||
|
||||
function removeLayer(layer) {
|
||||
setShownLayers(shownLayers.filter((l) => l !== layer.url));
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h3 className="mb-4 font-semibold text-gray-900 ">Layers</h3>
|
||||
<ul className="w-48 text-sm font-medium text-gray-900 bg-white border border-gray-200 rounded-lg ">
|
||||
{layers.map((layer, index) => (
|
||||
<li
|
||||
key={index}
|
||||
className="w-full border-b border-gray-200 rounded-t-lg "
|
||||
>
|
||||
<div className="flex items-center pl-3">
|
||||
<input
|
||||
id={layer.name}
|
||||
type="checkbox"
|
||||
defaultChecked={shownLayers.includes(layer.url)}
|
||||
onClick={() =>
|
||||
shownLayers.includes(layer.url)
|
||||
? removeLayer(layer)
|
||||
: addLayer(layer)
|
||||
}
|
||||
value={true}
|
||||
className="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 focus:ring-2 "
|
||||
></input>
|
||||
<label
|
||||
htmlFor={layer.name}
|
||||
className="w-full py-3 ml-2 text-sm font-medium text-gray-90"
|
||||
>
|
||||
{layer.name}
|
||||
</label>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,29 @@
|
||||
import { useContext, useEffect, useState } from 'react';
|
||||
import HeatMap from 'ol/layer/Heatmap';
|
||||
import { MapContext } from './Map';
|
||||
const HeatMapLayer = ({ source, style, zIndex = 0 }) => {
|
||||
const { map } = useContext(MapContext);
|
||||
const [heatMapLayer, setHeatMapLayer] = useState(null);
|
||||
useEffect(() => {
|
||||
if (!map) return;
|
||||
let heatMapLayer = new HeatMap({
|
||||
source,
|
||||
style,
|
||||
blur: parseInt(5, 10),
|
||||
radius: parseInt(5, 10),
|
||||
});
|
||||
map.addLayer(heatMapLayer);
|
||||
setHeatMapLayer(heatMapLayer);
|
||||
heatMapLayer.setZIndex(zIndex);
|
||||
return () => {
|
||||
if (map) {
|
||||
map.removeLayer(heatMapLayer);
|
||||
}
|
||||
};
|
||||
}, [map]);
|
||||
useEffect(() => {
|
||||
heatMapLayer && heatMapLayer.setZIndex(zIndex);
|
||||
}, [zIndex]);
|
||||
return null;
|
||||
};
|
||||
export default HeatMapLayer;
|
||||
4
packages/components/src/components/OpenLayers/Layers.jsx
Normal file
@@ -0,0 +1,4 @@
|
||||
import React from 'react';
|
||||
export const Layers = ({ children }) => {
|
||||
return <div>{children}</div>;
|
||||
};
|
||||
50
packages/components/src/components/OpenLayers/Map.jsx
Normal file
@@ -0,0 +1,50 @@
|
||||
import React, { useRef, useState, useEffect } from 'react';
|
||||
import * as ol from 'ol';
|
||||
|
||||
export const MapContext = new React.createContext();
|
||||
|
||||
const Map = ({ children, zoom, center, setSelected }) => {
|
||||
const mapRef = useRef();
|
||||
const [map, setMap] = useState(null);
|
||||
// on component mount
|
||||
useEffect(() => {
|
||||
let options = {
|
||||
view: new ol.View({ zoom, center }),
|
||||
layers: [],
|
||||
controls: [],
|
||||
overlays: [],
|
||||
};
|
||||
let mapObject = new ol.Map(options);
|
||||
mapObject.setTarget(mapRef.current);
|
||||
setMap(mapObject);
|
||||
return () => mapObject.setTarget(undefined);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (map) {
|
||||
if (setSelected !== null) {
|
||||
let selected = null;
|
||||
map.on('pointermove', function (e) {
|
||||
map.forEachFeatureAtPixel(e.pixel, function (f) {
|
||||
selected = f;
|
||||
return true;
|
||||
});
|
||||
if (selected) {
|
||||
setSelected(selected);
|
||||
} else {
|
||||
setSelected(null);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}, [map]);
|
||||
return (
|
||||
<MapContext.Provider value={{ map }}>
|
||||
<div ref={mapRef} className="w-full" style={{height: '500px'}}>
|
||||
{children}
|
||||
</div>
|
||||
</MapContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export default Map;
|
||||
136
packages/components/src/components/OpenLayers/OpenLayers.tsx
Normal file
@@ -0,0 +1,136 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import Map from './Map';
|
||||
import { Layers } from './Layers';
|
||||
import { Fill, Icon, Style } from 'ol/style';
|
||||
import * as olSource from 'ol/source';
|
||||
import TileLayer from './TileLayer';
|
||||
import { fromLonLat } from 'ol/proj';
|
||||
import VectorLayer from './VectorLayer';
|
||||
import { Vector as VectorSource } from 'ol/source';
|
||||
import GeoJSON from 'ol/format/GeoJSON';
|
||||
import KML from 'ol/format/KML';
|
||||
import { colors } from './colors';
|
||||
import { FullScreenControl, Controls, ListOfCheckboxes } from './Controls';
|
||||
import HeatMapLayer from './HeatMapLayer';
|
||||
|
||||
function osm() {
|
||||
return new olSource.OSM();
|
||||
}
|
||||
|
||||
const formats = {
|
||||
geojson: new GeoJSON(),
|
||||
kml: new KML(),
|
||||
};
|
||||
|
||||
interface OpenLayersProps {
|
||||
layers: {
|
||||
url: string;
|
||||
name?: string;
|
||||
format?: string;
|
||||
heatmap?: boolean;
|
||||
}[];
|
||||
center?: [number, number];
|
||||
zoom?: number;
|
||||
popup?: (selected: any) => JSX.Element;
|
||||
}
|
||||
|
||||
export default function OpenLayers({
|
||||
layers,
|
||||
center = [0, 0],
|
||||
zoom = 1,
|
||||
popup,
|
||||
}: OpenLayersProps) {
|
||||
const [shownLayers, setShownLayers] = useState(
|
||||
layers.map((layer) => layer.url)
|
||||
);
|
||||
const [selected, setSelected] = useState(null);
|
||||
const [style, setStyle] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
const style = new Style({
|
||||
fill: new Fill({
|
||||
color: '#eeeeee',
|
||||
}),
|
||||
image: new Icon({
|
||||
anchor: [0.5, 46],
|
||||
anchorXUnits: 'fraction',
|
||||
anchorYUnits: 'pixels',
|
||||
width: 18,
|
||||
height: 28,
|
||||
src: 'https://upload.wikimedia.org/wikipedia/commons/thumb/a/aa/Google_Maps_icon_%282020%29.svg/418px-Google_Maps_icon_%282020%29.svg.png?20200218211225',
|
||||
}),
|
||||
});
|
||||
setStyle(style);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
<Map
|
||||
center={fromLonLat(center)}
|
||||
zoom={zoom}
|
||||
setSelected={popup ? setSelected : null}
|
||||
>
|
||||
<Layers>
|
||||
<TileLayer source={osm()} zIndex={0} />
|
||||
{layers.map((layer, index) =>
|
||||
!layer.heatmap ? (
|
||||
<VectorLayer
|
||||
key={index}
|
||||
zIndex={shownLayers.includes(layer.url) ? 1 : -1}
|
||||
source={
|
||||
new VectorSource({
|
||||
url: layer.url,
|
||||
format: layer.format
|
||||
? formats[layer.format]
|
||||
: new GeoJSON(),
|
||||
})
|
||||
}
|
||||
style={function (feature) {
|
||||
const id = feature.getId();
|
||||
const color = feature.get('COLOR') || colors[id % 1302].hex;
|
||||
style.getFill().setColor(color);
|
||||
return style;
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<HeatMapLayer
|
||||
key={index}
|
||||
zIndex={shownLayers.includes(layer.url) ? 1 : -1}
|
||||
source={
|
||||
new VectorSource({
|
||||
url: layer.url,
|
||||
format: layer.format
|
||||
? formats[layer.format]
|
||||
: new GeoJSON(),
|
||||
})
|
||||
}
|
||||
style={function (feature) {
|
||||
const color =
|
||||
feature.get('COLOR') || colors[feature.ol_uid % 1302].hex;
|
||||
style.getFill().setColor(color);
|
||||
return style;
|
||||
}}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
</Layers>
|
||||
{/* add a floating pane that will output the ListOfCheckboxes component using tailwind*/}
|
||||
<div className="absolute bottom-0 right-0 m-4 p-4 z-50 bg-white rounded-lg shadow-xl">
|
||||
<ListOfCheckboxes
|
||||
layers={layers}
|
||||
shownLayers={shownLayers}
|
||||
setShownLayers={setShownLayers}
|
||||
/>
|
||||
</div>
|
||||
{popup && selected && (
|
||||
<div className="absolute bottom-0 left-0 m-4 p-4 z-50 bg-white rounded-lg shadow-xl">
|
||||
{popup(selected)}
|
||||
</div>
|
||||
)}
|
||||
<Controls>
|
||||
<FullScreenControl />
|
||||
</Controls>
|
||||
</Map>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
23
packages/components/src/components/OpenLayers/TileLayer.jsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import { useContext, useEffect } from 'react';
|
||||
import OLTileLayer from 'ol/layer/Tile';
|
||||
import { MapContext } from './Map';
|
||||
const TileLayer = ({ source, zIndex = 0 }) => {
|
||||
const { map } = useContext(MapContext);
|
||||
useEffect(() => {
|
||||
if (!map) return;
|
||||
|
||||
let tileLayer = new OLTileLayer({
|
||||
source,
|
||||
zIndex,
|
||||
});
|
||||
map.addLayer(tileLayer);
|
||||
tileLayer.setZIndex(zIndex);
|
||||
return () => {
|
||||
if (map) {
|
||||
map.removeLayer(tileLayer);
|
||||
}
|
||||
};
|
||||
}, [map]);
|
||||
return null;
|
||||
};
|
||||
export default TileLayer;
|
||||
@@ -0,0 +1,33 @@
|
||||
import { useContext, useEffect, useState } from 'react';
|
||||
import OLVectorLayer from 'ol/layer/Vector';
|
||||
import { MapContext } from './Map';
|
||||
const VectorLayer = ({ source, style, zIndex = 0 }) => {
|
||||
const { map } = useContext(MapContext);
|
||||
const [vectorLayer, setVectorLayer] = useState(null);
|
||||
useEffect(() => {
|
||||
if (!map) return;
|
||||
let vectorLayer = new OLVectorLayer({
|
||||
source,
|
||||
style,
|
||||
});
|
||||
const vectorSource = vectorLayer.getSource();
|
||||
vectorSource.on('featuresloadend', function () {
|
||||
vectorSource.getFeatures().forEach((feature, index) => {
|
||||
feature.setId(index);
|
||||
});
|
||||
});
|
||||
map.addLayer(vectorLayer);
|
||||
setVectorLayer(vectorLayer);
|
||||
vectorLayer.setZIndex(zIndex);
|
||||
return () => {
|
||||
if (map) {
|
||||
map.removeLayer(vectorLayer);
|
||||
}
|
||||
};
|
||||
}, [map]);
|
||||
useEffect(() => {
|
||||
vectorLayer && vectorLayer.setZIndex(zIndex);
|
||||
}, [zIndex]);
|
||||
return null;
|
||||
};
|
||||
export default VectorLayer;
|
||||
5210
packages/components/src/components/OpenLayers/colors.js
Normal file
32
packages/components/src/components/PdfViewer.tsx
Normal file
@@ -0,0 +1,32 @@
|
||||
// Core viewer
|
||||
import { Viewer, Worker, SpecialZoomLevel } from '@react-pdf-viewer/core';
|
||||
import { defaultLayoutPlugin } from '@react-pdf-viewer/default-layout';
|
||||
|
||||
// Import styles
|
||||
import '@react-pdf-viewer/core/lib/styles/index.css';
|
||||
import '@react-pdf-viewer/default-layout/lib/styles/index.css';
|
||||
|
||||
export interface PdfViewerProps {
|
||||
url: string;
|
||||
layout: boolean;
|
||||
parentClassName?: string;
|
||||
}
|
||||
|
||||
export function PdfViewer({
|
||||
url,
|
||||
layout = false,
|
||||
parentClassName,
|
||||
}: PdfViewerProps) {
|
||||
const defaultLayoutPluginInstance = defaultLayoutPlugin();
|
||||
return (
|
||||
<Worker workerUrl="https://unpkg.com/pdfjs-dist@2.15.349/build/pdf.worker.js">
|
||||
<div className={parentClassName}>
|
||||
<Viewer
|
||||
defaultScale={SpecialZoomLevel.PageWidth}
|
||||
fileUrl={url}
|
||||
plugins={layout ? [defaultLayoutPluginInstance] : []}
|
||||
/>
|
||||
</div>
|
||||
</Worker>
|
||||
);
|
||||
}
|
||||
6
packages/components/src/include.css
Normal file
@@ -0,0 +1,6 @@
|
||||
/* Temporary fix for a size issue with FlatUiTable loading indicator on Firefox */
|
||||
@layer base {
|
||||
svg[tw^='animate-pulse w-12'] {
|
||||
max-width: 100px;
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
/* Temporary fix for a size issue with FlatUiTable loading indicator on Firefox */
|
||||
@layer base {
|
||||
svg[tw^='animate-pulse w-12'] {
|
||||
max-width: 100px;
|
||||
}
|
||||
}
|
||||
@import "tailwindcss/base";
|
||||
@import "tailwindcss/components";
|
||||
@import "include";
|
||||
@import "leaflet";
|
||||
@import '@react-pdf-viewer/core/lib/styles/index.css';
|
||||
@import '@react-pdf-viewer/default-layout/lib/styles/index.css';
|
||||
@import 'ag-grid-community/styles/ag-grid.css';
|
||||
@import 'ag-grid-community/styles/ag-theme-alpine.css';
|
||||
@import "tailwindcss/utilities";
|
||||
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
export * from "./components/Table";
|
||||
export * from "./components/Catalog";
|
||||
export * from "./components/LineChart";
|
||||
export * from "./components/Vega";
|
||||
export * from "./components/VegaLite";
|
||||
export * from "./components/FlatUiTable";
|
||||
export * from './components/Table';
|
||||
export * from './components/Catalog';
|
||||
export * from './components/LineChart';
|
||||
export * from './components/Vega';
|
||||
export * from './components/VegaLite';
|
||||
export * from './components/FlatUiTable';
|
||||
export * from './components/OpenLayers/OpenLayers';
|
||||
export * from './components/Map';
|
||||
export * from './components/PdfViewer';
|
||||
export * from "./components/Excel";
|
||||
|
||||
171
packages/components/src/types/GeoJSON.tsx
Normal file
@@ -0,0 +1,171 @@
|
||||
/**
|
||||
* Typescript types for the GeoJSON RFC7946 specification. This is not fully RFC-compliant due to lack of support for
|
||||
* ranged number data types.
|
||||
*
|
||||
* See https://tools.ietf.org/html/rfc7946
|
||||
*/
|
||||
export declare namespace GeoJSON {
|
||||
/**
|
||||
* Inside this document, the term "geometry type" refers to seven case-sensitive strings: "Point", "MultiPoint",
|
||||
* "LineString", "MultiLineString", "Polygon", "MultiPolygon", and "GeometryCollection".
|
||||
*/
|
||||
export type Geometry = Point | MultiPoint | LineString | MultiLineString | Polygon | MultiPolygon
|
||||
| GeometryCollection;
|
||||
export type GeometryType = Geometry["type"];
|
||||
|
||||
/**
|
||||
* ...the term "GeoJSON types" refers to nine case-sensitive strings: "Feature", "FeatureCollection", and the
|
||||
* geometry types listed above.
|
||||
*/
|
||||
export type GeoJson = Geometry | Feature | FeatureCollection;
|
||||
export type GeoJsonType = GeoJson["type"];
|
||||
|
||||
// types
|
||||
|
||||
/**
|
||||
* A position is an array of numbers. There MUST be two or more elements. The first two elements are longitude and
|
||||
* latitude, or easting and northing, precisely in that order and using decimal numbers. Altitude or elevation MAY
|
||||
* be included as an optional third element.
|
||||
*
|
||||
* Implementations SHOULD NOT extend positions beyond three elements because the semantics of extra elements are
|
||||
* unspecified and ambiguous.
|
||||
*/
|
||||
export type Position = [longitude: number, latitude: number, elevation?: number]
|
||||
|
||||
export type Record = { [key in string | number]: unknown };
|
||||
|
||||
/**
|
||||
* Properties inherit to all GeoJSON types
|
||||
*/
|
||||
export interface GeometryBase extends Record {
|
||||
/**
|
||||
* A GeoJSON object MAY have a member named "bbox" to include information on the coordinate range for its
|
||||
* Geometries, Features, or FeatureCollections. The value of the bbox member MUST be an array of length 2*n
|
||||
* where n is the number of dimensions represented in the contained geometries, with all axes of the most
|
||||
* southwesterly point followed by all axes of the more northeasterly point. The axes order of a bbox follows
|
||||
* the axes order of geometries.
|
||||
*/
|
||||
bbox?: number[];
|
||||
|
||||
/**
|
||||
* A GeoJSON object MAY have other members.
|
||||
*
|
||||
* Members not described in this specification ("foreign members") MAY be used in a GeoJSON document. Note that
|
||||
* support for foreign members can vary across implementations, and no normative processing model for foreign
|
||||
* members is defined.
|
||||
*/
|
||||
}
|
||||
|
||||
// geometry types
|
||||
|
||||
export interface Point extends GeometryBase {
|
||||
type: "Point";
|
||||
/**
|
||||
* For type "Point", the "coordinates" member is a single position.
|
||||
*/
|
||||
coordinates: Position;
|
||||
}
|
||||
|
||||
export interface MultiPoint extends GeometryBase {
|
||||
type: "MultiPoint";
|
||||
/**
|
||||
* For type "MultiPoint", the "coordinates" member is an array of positions.
|
||||
*/
|
||||
coordinates: Position[];
|
||||
}
|
||||
|
||||
export interface LineString extends GeometryBase {
|
||||
type: "LineString";
|
||||
/**
|
||||
* For type "LineString", the "coordinates" member is an array of two or more positions.
|
||||
*/
|
||||
coordinates: { 0: Position, 1: Position } & Position[]
|
||||
}
|
||||
|
||||
export interface MultiLineString extends GeometryBase {
|
||||
type: "MultiLineString";
|
||||
/**
|
||||
* For type "MultiLineString", the "coordinates" member is an array of LineString coordinate arrays.
|
||||
*/
|
||||
coordinates: LineString["coordinates"][];
|
||||
}
|
||||
|
||||
/**
|
||||
* To specify a constraint specific to Polygons, it is useful to introduce the concept of a linear ring:
|
||||
* - A linear ring is a closed LineString with four or more positions.
|
||||
* - The first and last positions are equivalent, and they MUST contain identical values; their representation
|
||||
* SHOULD also be identical.
|
||||
* - A linear ring is the boundary of a surface or the boundary of a hole in a surface.
|
||||
* - A linear ring MUST follow the right-hand rule with respect to the area it bounds, i.e., exterior rings are
|
||||
* counterclockwise, and holes are clockwise.
|
||||
*/
|
||||
export type LinearRing = { 0: Position, 1: Position, 2: Position, 3: Position } & Position[];
|
||||
|
||||
export interface Polygon extends GeometryBase {
|
||||
type: "Polygon";
|
||||
/**
|
||||
* For type "Polygon", the "coordinates" member MUST be an array of linear ring coordinate arrays.
|
||||
*
|
||||
* For Polygons with more than one of these rings, the first MUST be the exterior ring, and any others MUST be
|
||||
* interior rings. The exterior ring bounds the surface, and the interior rings (if present) bound holes within
|
||||
* the surface.
|
||||
*/
|
||||
coordinates: LinearRing[];
|
||||
}
|
||||
|
||||
export interface MultiPolygon extends GeometryBase {
|
||||
type: "MultiPolygon";
|
||||
/**
|
||||
* For type "MultiPolygon", the "coordinates" member is an array of Polygon coordinate arrays.
|
||||
*/
|
||||
coordinates: Polygon["coordinates"][];
|
||||
}
|
||||
|
||||
export interface GeometryCollection {
|
||||
/**
|
||||
* A GeoJSON object with type "GeometryCollection" is a Geometry object.
|
||||
*/
|
||||
type: "GeometryCollection";
|
||||
/**
|
||||
* A GeometryCollection has a member with the name "geometries". The value of "geometries" is an array. Each
|
||||
* element of this array is a GeoJSON Geometry object. It is possible for this array to be empty.
|
||||
*/
|
||||
geometries: Geometry[];
|
||||
}
|
||||
|
||||
// GeoJSON types
|
||||
|
||||
export interface Feature {
|
||||
/**
|
||||
* A Feature object has a "type" member with the value "Feature".
|
||||
*/
|
||||
type: "Feature";
|
||||
/**
|
||||
* If a Feature has a commonly used identifier, that identifier SHOULD be included as a member of the Feature object
|
||||
* with the name "id", and the value of this member is either a JSON string or number.
|
||||
*/
|
||||
id?: string | number;
|
||||
/**
|
||||
* A Feature object has a member with the name "geometry". The value of the geometry member SHALL be either a
|
||||
* Geometry object as defined above or, in the case that the Feature is unlocated, a JSON null value.
|
||||
*/
|
||||
geometry: Geometry | null;
|
||||
/**
|
||||
* A Feature object has a member with the name "properties". The value of the properties member is an object
|
||||
* (any JSON object or a JSON null value).
|
||||
*/
|
||||
properties: Record | null;
|
||||
}
|
||||
|
||||
export interface FeatureCollection {
|
||||
/**
|
||||
* A GeoJSON object with the type "FeatureCollection" is a FeatureCollection object.
|
||||
*/
|
||||
type: "FeatureCollection";
|
||||
/**
|
||||
* A FeatureCollection object has a member with the name "features". The value of "features" is a JSON array. Each
|
||||
* element of the array is a Feature object as defined above. It is possible for this array to be empty.
|
||||
*/
|
||||
features: Feature[];
|
||||
}
|
||||
}
|
||||
34
packages/components/stories/Excel.stories.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import { Excel, ExcelProps } from '../src/components/Excel';
|
||||
|
||||
// More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction
|
||||
const meta: Meta = {
|
||||
title: 'Components/Excel',
|
||||
component: Excel,
|
||||
tags: ['autodocs'],
|
||||
argTypes: {
|
||||
url: {
|
||||
description:
|
||||
'Url of the file to be displayed e.g.: "https://url.to/data.csv"',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<ExcelProps>;
|
||||
|
||||
export const SingleSheet: Story = {
|
||||
name: 'Excel file with just one sheet',
|
||||
args: {
|
||||
url: 'https://sheetjs.com/pres.xlsx',
|
||||
},
|
||||
};
|
||||
|
||||
export const MultipleSheet: Story = {
|
||||
name: 'Excel file with multiple sheets',
|
||||
args: {
|
||||
url: 'https://storage.portaljs.org/IC-Gantt-Chart-Project-Template-8857.xlsx',
|
||||
},
|
||||
};
|
||||
91
packages/components/stories/Map.stories.ts
Normal file
@@ -0,0 +1,91 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import { Map, MapProps } from '../src/components/Map';
|
||||
|
||||
// More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction
|
||||
const meta: Meta = {
|
||||
title: 'Components/Map',
|
||||
component: Map,
|
||||
tags: ['autodocs'],
|
||||
argTypes: {
|
||||
layers: {
|
||||
description:
|
||||
'Data to be displayed.\n\n GeoJSON Object \n\nOR\n\n URL to GeoJSON Object',
|
||||
},
|
||||
title: {
|
||||
description: 'Title to display on the map. Optional.',
|
||||
},
|
||||
center: {
|
||||
description: 'Initial coordinates of the center of the map',
|
||||
},
|
||||
zoom: {
|
||||
description: 'Zoom level',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<MapProps>;
|
||||
|
||||
// More on writing stories with args: https://storybook.js.org/docs/react/writing-stories/args
|
||||
export const GeoJSONPolygons: Story = {
|
||||
name: 'GeoJSON polygons map',
|
||||
args: {
|
||||
layers: [
|
||||
{
|
||||
data: 'https://d2ad6b4ur7yvpq.cloudfront.net/naturalearth-3.3.0/ne_10m_geography_marine_polys.geojson',
|
||||
name: 'Polygons',
|
||||
tooltip: { propNames: ['name'] },
|
||||
colorScale: {
|
||||
starting: '#ff0000',
|
||||
ending: '#00ff00',
|
||||
},
|
||||
},
|
||||
],
|
||||
title: 'Seas and Oceans Map',
|
||||
center: { latitude: 45, longitude: 0 },
|
||||
zoom: 2,
|
||||
},
|
||||
};
|
||||
|
||||
export const GeoJSONPoints: Story = {
|
||||
name: 'GeoJSON points map',
|
||||
args: {
|
||||
layers: [
|
||||
{
|
||||
data: 'https://opendata.arcgis.com/datasets/9c58741995174fbcb017cf46c8a42f4b_25.geojson',
|
||||
name: 'Points',
|
||||
tooltip: { propNames: ['Location'] },
|
||||
},
|
||||
],
|
||||
title: 'Roads in York',
|
||||
center: { latitude: 53.9614, longitude: -1.0739 },
|
||||
zoom: 12,
|
||||
},
|
||||
};
|
||||
|
||||
export const GeoJSONMultipleLayers: Story = {
|
||||
name: 'GeoJSON polygons and points map',
|
||||
args: {
|
||||
layers: [
|
||||
{
|
||||
data: 'https://opendata.arcgis.com/datasets/9c58741995174fbcb017cf46c8a42f4b_25.geojson',
|
||||
name: 'Points',
|
||||
tooltip: true,
|
||||
},
|
||||
{
|
||||
data: 'https://d2ad6b4ur7yvpq.cloudfront.net/naturalearth-3.3.0/ne_10m_geography_marine_polys.geojson',
|
||||
name: 'Polygons',
|
||||
tooltip: true,
|
||||
colorScale: {
|
||||
starting: '#ff0000',
|
||||
ending: '#00ff00',
|
||||
},
|
||||
},
|
||||
],
|
||||
title: 'Polygons and points',
|
||||
center: { latitude: 45, longitude: 0 },
|
||||
zoom: 2,
|
||||
},
|
||||
};
|
||||
136
packages/components/stories/OpenLayers.stories.tsx
Normal file
@@ -0,0 +1,136 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import React from 'react';
|
||||
import OpenLayers from '../src/components/OpenLayers/OpenLayers';
|
||||
|
||||
const meta: Meta = {
|
||||
title: 'Components/OpenLayers',
|
||||
component: OpenLayers,
|
||||
argTypes: {
|
||||
layers: {
|
||||
description: 'Layers to be added to the map',
|
||||
control: {
|
||||
type: 'array',
|
||||
},
|
||||
},
|
||||
center: {
|
||||
description: 'Center of the map',
|
||||
defaultValue: [0, 0],
|
||||
control: {
|
||||
type: 'array',
|
||||
},
|
||||
},
|
||||
zoom: {
|
||||
description: 'Zoom level of the map',
|
||||
defaultValue: 1,
|
||||
control: {
|
||||
type: 'number',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<any>;
|
||||
|
||||
export const Secondary: Story = {
|
||||
name: 'Map with OpenLayers',
|
||||
args: {
|
||||
layers: [
|
||||
{
|
||||
url: 'https://openlayers.org/data/vector/ecoregions.json',
|
||||
name: 'Ecoregions',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
export const Primary: Story = {
|
||||
name: 'Map with OpenLayers 2',
|
||||
args: {
|
||||
layers: [
|
||||
{
|
||||
url: 'https://d2ad6b4ur7yvpq.cloudfront.net/naturalearth-3.3.0/ne_10m_geography_marine_polys.geojson',
|
||||
name: 'Marine regions',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
export const MapWithPopover: Story = {
|
||||
name: 'Map with popup',
|
||||
args: {
|
||||
layers: [
|
||||
{
|
||||
url: 'https://openlayers.org/data/vector/ecoregions.json',
|
||||
name: 'Ecoregions',
|
||||
},
|
||||
],
|
||||
popup: (feature: any) => {
|
||||
return (
|
||||
<div className="flex flex-col gap-y-1" style={{ color: 'red' }}>
|
||||
<span className="font-bold">Biome name</span>
|
||||
<span className="text-sm">{feature.values_.BIOME_NAME}</span>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const Third: Story = {
|
||||
name: 'Map with two layers',
|
||||
args: {
|
||||
layers: [
|
||||
{
|
||||
url: 'https://d2ad6b4ur7yvpq.cloudfront.net/naturalearth-3.3.0/ne_10m_geography_marine_polys.geojson',
|
||||
name: 'Marine regions',
|
||||
},
|
||||
{
|
||||
url: 'https://openlayers.org/data/vector/ecoregions.json',
|
||||
name: 'Ecoregions',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
export const CustomCenter: Story = {
|
||||
name: 'Map with custom center and zoom',
|
||||
args: {
|
||||
layers: [
|
||||
{
|
||||
url: 'https://openlayers.org/data/vector/ecoregions.json',
|
||||
name: 'Ecoregions',
|
||||
},
|
||||
],
|
||||
center: [-15, 20],
|
||||
zoom: 4,
|
||||
},
|
||||
};
|
||||
|
||||
export const PointsOnMap: Story = {
|
||||
name: 'Map with points on',
|
||||
args: {
|
||||
layers: [
|
||||
{
|
||||
url: 'https://opendata.arcgis.com/datasets/9c58741995174fbcb017cf46c8a42f4b_25.geojson',
|
||||
name: 'E-Scooter Parking Bays',
|
||||
},
|
||||
],
|
||||
center: [-1.055429957881787, 53.963900188025301],
|
||||
zoom: 12,
|
||||
},
|
||||
};
|
||||
|
||||
export const KMLFile: Story = {
|
||||
name: 'Map with KML File',
|
||||
args: {
|
||||
layers: [
|
||||
{
|
||||
url: 'https://openlayers.org/en/latest/examples/data/kml/2012_Earthquakes_Mag5.kml',
|
||||
name: '2012 Earthquakes M5+',
|
||||
format: 'kml',
|
||||
heatmap: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
50
packages/components/stories/PdfViewer.stories.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import { PdfViewer, PdfViewerProps } from '../src/components/PdfViewer';
|
||||
|
||||
const meta: Meta = {
|
||||
title: 'Components/PdfViewer',
|
||||
component: PdfViewer,
|
||||
tags: ['autodocs'],
|
||||
argTypes: {
|
||||
url: {
|
||||
description: 'URL to PDF file',
|
||||
},
|
||||
parentClassName: {
|
||||
description: 'Classname for the parent div of the pdf viewer',
|
||||
},
|
||||
layour: {
|
||||
description:
|
||||
'Set to true if you want to have a layout with zoom level, page count, printing button etc',
|
||||
defaultValue: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<PdfViewerProps>;
|
||||
|
||||
export const PdfViewerStory: Story = {
|
||||
name: 'PdfViewer',
|
||||
args: {
|
||||
url: 'https://cdn.filestackcontent.com/wcrjf9qPTCKXV3hMXDwK',
|
||||
},
|
||||
};
|
||||
|
||||
export const PdfViewerStoryWithLayout: Story = {
|
||||
name: 'PdfViewer with the default layout',
|
||||
args: {
|
||||
url: 'https://cdn.filestackcontent.com/wcrjf9qPTCKXV3hMXDwK',
|
||||
layout: true,
|
||||
},
|
||||
};
|
||||
|
||||
export const PdfViewerStoryWithHeight: Story = {
|
||||
name: 'PdfViewer with a custom height',
|
||||
args: {
|
||||
url: 'https://cdn.filestackcontent.com/wcrjf9qPTCKXV3hMXDwK',
|
||||
parentClassName: 'h-96',
|
||||
layout: true,
|
||||
},
|
||||
};
|
||||
@@ -1,8 +1,10 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
export default {
|
||||
content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
|
||||
theme: {
|
||||
extend: {},
|
||||
},
|
||||
content: [
|
||||
'./index.html',
|
||||
'./src/**/*.{js,ts,jsx,tsx}',
|
||||
'./stories/*.{js,ts,jsx,tsx}',
|
||||
],
|
||||
theme: {},
|
||||
plugins: [],
|
||||
};
|
||||
|
||||
@@ -1,29 +1,14 @@
|
||||
import react from '@vitejs/plugin-react'
|
||||
import path from 'node:path'
|
||||
import { defineConfig } from 'vitest/config'
|
||||
import dts from 'vite-plugin-dts'
|
||||
import tailwindcss from 'tailwindcss'
|
||||
import { UserConfigExport } from 'vite'
|
||||
import replace from "rollup-plugin-re"
|
||||
import react from '@vitejs/plugin-react';
|
||||
import path from 'node:path';
|
||||
import { defineConfig } from 'vitest/config';
|
||||
import dts from 'vite-plugin-dts';
|
||||
import tailwindcss from 'tailwindcss';
|
||||
import { UserConfigExport } from 'vite';
|
||||
|
||||
const app = async (): Promise<UserConfigExport> => {
|
||||
return defineConfig({
|
||||
plugins: [
|
||||
react(),
|
||||
replace({
|
||||
patterns: [
|
||||
{
|
||||
match: /js-sha256/,
|
||||
test: `eval("require('crypto')")`,
|
||||
replace: `require('crypto')`,
|
||||
},
|
||||
{
|
||||
match: /js-sha256/,
|
||||
test: `eval("require('buffer').Buffer")`,
|
||||
replace: `require('buffer').Buffer`,
|
||||
},
|
||||
],
|
||||
}),
|
||||
dts({
|
||||
insertTypesEntry: true,
|
||||
}),
|
||||
@@ -34,6 +19,7 @@ const app = async (): Promise<UserConfigExport> => {
|
||||
},
|
||||
},
|
||||
build: {
|
||||
target: 'es2020',
|
||||
lib: {
|
||||
entry: path.resolve(__dirname, 'src/index.ts'),
|
||||
name: 'components',
|
||||
@@ -41,12 +27,27 @@ const app = async (): Promise<UserConfigExport> => {
|
||||
fileName: (format) => `components.${format}.js`,
|
||||
},
|
||||
rollupOptions: {
|
||||
external: ['react', 'react-dom', 'tailwindcss', 'vega-lite', 'vega', 'react-vega'],
|
||||
external: [
|
||||
'react',
|
||||
'ol-mapbox-style',
|
||||
'react-dom',
|
||||
'tailwindcss',
|
||||
'vega-lite',
|
||||
'vega',
|
||||
'react-vega',
|
||||
'ol',
|
||||
'leaflet',
|
||||
],
|
||||
output: {
|
||||
manualChunks: undefined,
|
||||
globals: {
|
||||
react: 'React',
|
||||
ol: 'ol',
|
||||
'ol-mapbox-style': 'ol-mapbox-style',
|
||||
'react-vega': 'react-vega',
|
||||
'react-dom': 'ReactDOM',
|
||||
tailwindcss: 'tailwindcss',
|
||||
leaflet: 'leaflet',
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -55,7 +56,7 @@ const app = async (): Promise<UserConfigExport> => {
|
||||
globals: true,
|
||||
environment: 'jsdom',
|
||||
},
|
||||
})
|
||||
}
|
||||
});
|
||||
};
|
||||
// https://vitejs.dev/config/
|
||||
export default app
|
||||
export default app;
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
// vite.config.ts
|
||||
import react from "file:///home/urutu-branco/Projetos/portaljs/node_modules/@vitejs/plugin-react-swc/index.mjs";
|
||||
import path from "node:path";
|
||||
import { defineConfig } from "file:///home/urutu-branco/Projetos/portaljs/node_modules/vitest/dist/config.js";
|
||||
import dts from "file:///home/urutu-branco/Projetos/portaljs/node_modules/vite-plugin-dts/dist/index.mjs";
|
||||
import tailwindcss from "file:///home/urutu-branco/Projetos/portaljs/node_modules/tailwindcss/lib/index.js";
|
||||
var __vite_injected_original_dirname = "/home/urutu-branco/Projetos/portaljs/packages/components";
|
||||
var app = async () => {
|
||||
return defineConfig({
|
||||
plugins: [
|
||||
react(),
|
||||
dts({
|
||||
insertTypesEntry: true
|
||||
})
|
||||
],
|
||||
css: {
|
||||
postcss: {
|
||||
plugins: [tailwindcss]
|
||||
}
|
||||
},
|
||||
build: {
|
||||
target: "es2020",
|
||||
lib: {
|
||||
entry: path.resolve(__vite_injected_original_dirname, "src/index.ts"),
|
||||
name: "components",
|
||||
formats: ["es", "umd"],
|
||||
fileName: (format) => `components.${format}.js`
|
||||
},
|
||||
rollupOptions: {
|
||||
external: [
|
||||
"react",
|
||||
"ol-mapbox-style",
|
||||
"react-dom",
|
||||
"tailwindcss",
|
||||
"vega-lite",
|
||||
"vega",
|
||||
"react-vega",
|
||||
"ol",
|
||||
"ol/dom.js"
|
||||
],
|
||||
output: {
|
||||
manualChunks: void 0,
|
||||
globals: {
|
||||
react: "React",
|
||||
ol: "ol",
|
||||
"ol/dom.js": "ol/dom.js",
|
||||
"react-vega": "react-vega",
|
||||
"react-dom": "ReactDOM",
|
||||
tailwindcss: "tailwindcss"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
test: {
|
||||
globals: true,
|
||||
environment: "jsdom"
|
||||
}
|
||||
});
|
||||
};
|
||||
var vite_config_default = app;
|
||||
export {
|
||||
vite_config_default as default
|
||||
};
|
||||
//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsidml0ZS5jb25maWcudHMiXSwKICAic291cmNlc0NvbnRlbnQiOiBbImNvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9kaXJuYW1lID0gXCIvaG9tZS91cnV0dS1icmFuY28vUHJvamV0b3MvcG9ydGFsanMvcGFja2FnZXMvY29tcG9uZW50c1wiO2NvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9maWxlbmFtZSA9IFwiL2hvbWUvdXJ1dHUtYnJhbmNvL1Byb2pldG9zL3BvcnRhbGpzL3BhY2thZ2VzL2NvbXBvbmVudHMvdml0ZS5jb25maWcudHNcIjtjb25zdCBfX3ZpdGVfaW5qZWN0ZWRfb3JpZ2luYWxfaW1wb3J0X21ldGFfdXJsID0gXCJmaWxlOi8vL2hvbWUvdXJ1dHUtYnJhbmNvL1Byb2pldG9zL3BvcnRhbGpzL3BhY2thZ2VzL2NvbXBvbmVudHMvdml0ZS5jb25maWcudHNcIjtpbXBvcnQgcmVhY3QgZnJvbSAnQHZpdGVqcy9wbHVnaW4tcmVhY3Qtc3djJztcbmltcG9ydCBwYXRoIGZyb20gJ25vZGU6cGF0aCc7XG5pbXBvcnQgeyBkZWZpbmVDb25maWcgfSBmcm9tICd2aXRlc3QvY29uZmlnJztcbmltcG9ydCBkdHMgZnJvbSAndml0ZS1wbHVnaW4tZHRzJztcbmltcG9ydCB0YWlsd2luZGNzcyBmcm9tICd0YWlsd2luZGNzcyc7XG5pbXBvcnQgeyBVc2VyQ29uZmlnRXhwb3J0IH0gZnJvbSAndml0ZSc7XG5pbXBvcnQgcmVwbGFjZSBmcm9tICdyb2xsdXAtcGx1Z2luLXJlJztcblxuY29uc3QgYXBwID0gYXN5bmMgKCk6IFByb21pc2U8VXNlckNvbmZpZ0V4cG9ydD4gPT4ge1xuICByZXR1cm4gZGVmaW5lQ29uZmlnKHtcbiAgICBwbHVnaW5zOiBbXG4gICAgICByZWFjdCgpLFxuICAgICAgZHRzKHtcbiAgICAgICAgaW5zZXJ0VHlwZXNFbnRyeTogdHJ1ZSxcbiAgICAgIH0pLFxuICAgIF0sXG4gICAgY3NzOiB7XG4gICAgICBwb3N0Y3NzOiB7XG4gICAgICAgIHBsdWdpbnM6IFt0YWlsd2luZGNzc10sXG4gICAgICB9LFxuICAgIH0sXG4gICAgYnVpbGQ6IHtcbiAgICAgIHRhcmdldDogJ2VzMjAyMCcsXG4gICAgICBsaWI6IHtcbiAgICAgICAgZW50cnk6IHBhdGgucmVzb2x2ZShfX2Rpcm5hbWUsICdzcmMvaW5kZXgudHMnKSxcbiAgICAgICAgbmFtZTogJ2NvbXBvbmVudHMnLFxuICAgICAgICBmb3JtYXRzOiBbJ2VzJywgJ3VtZCddLFxuICAgICAgICBmaWxlTmFtZTogKGZvcm1hdCkgPT4gYGNvbXBvbmVudHMuJHtmb3JtYXR9LmpzYCxcbiAgICAgIH0sXG4gICAgICByb2xsdXBPcHRpb25zOiB7XG4gICAgICAgIGV4dGVybmFsOiBbXG4gICAgICAgICAgJ3JlYWN0JyxcbiAgICAgICAgICAnb2wtbWFwYm94LXN0eWxlJyxcbiAgICAgICAgICAncmVhY3QtZG9tJyxcbiAgICAgICAgICAndGFpbHdpbmRjc3MnLFxuICAgICAgICAgICd2ZWdhLWxpdGUnLFxuICAgICAgICAgICd2ZWdhJyxcbiAgICAgICAgICAncmVhY3QtdmVnYScsXG4gICAgICAgICAgJ29sJyxcbiAgICAgICAgICAnb2wvZG9tLmpzJyxcbiAgICAgICAgXSxcbiAgICAgICAgb3V0cHV0OiB7XG4gICAgICAgICAgbWFudWFsQ2h1bmtzOiB1bmRlZmluZWQsXG4gICAgICAgICAgZ2xvYmFsczoge1xuICAgICAgICAgICAgcmVhY3Q6ICdSZWFjdCcsXG4gICAgICAgICAgICBvbDogJ29sJyxcbiAgICAgICAgICAgICdvbC9kb20uanMnOiAnb2wvZG9tLmpzJyxcbiAgICAgICAgICAgICdyZWFjdC12ZWdhJzogJ3JlYWN0LXZlZ2EnLFxuICAgICAgICAgICAgJ3JlYWN0LWRvbSc6ICdSZWFjdERPTScsXG4gICAgICAgICAgICB0YWlsd2luZGNzczogJ3RhaWx3aW5kY3NzJyxcbiAgICAgICAgICB9LFxuICAgICAgICB9LFxuICAgICAgfSxcbiAgICB9LFxuICAgIHRlc3Q6IHtcbiAgICAgIGdsb2JhbHM6IHRydWUsXG4gICAgICBlbnZpcm9ubWVudDogJ2pzZG9tJyxcbiAgICB9LFxuICB9KTtcbn07XG4vLyBodHRwczovL3ZpdGVqcy5kZXYvY29uZmlnL1xuZXhwb3J0IGRlZmF1bHQgYXBwO1xuIl0sCiAgIm1hcHBpbmdzIjogIjtBQUEwVixPQUFPLFdBQVc7QUFDNVcsT0FBTyxVQUFVO0FBQ2pCLFNBQVMsb0JBQW9CO0FBQzdCLE9BQU8sU0FBUztBQUNoQixPQUFPLGlCQUFpQjtBQUp4QixJQUFNLG1DQUFtQztBQVF6QyxJQUFNLE1BQU0sWUFBdUM7QUFDakQsU0FBTyxhQUFhO0FBQUEsSUFDbEIsU0FBUztBQUFBLE1BQ1AsTUFBTTtBQUFBLE1BQ04sSUFBSTtBQUFBLFFBQ0Ysa0JBQWtCO0FBQUEsTUFDcEIsQ0FBQztBQUFBLElBQ0g7QUFBQSxJQUNBLEtBQUs7QUFBQSxNQUNILFNBQVM7QUFBQSxRQUNQLFNBQVMsQ0FBQyxXQUFXO0FBQUEsTUFDdkI7QUFBQSxJQUNGO0FBQUEsSUFDQSxPQUFPO0FBQUEsTUFDTCxRQUFRO0FBQUEsTUFDUixLQUFLO0FBQUEsUUFDSCxPQUFPLEtBQUssUUFBUSxrQ0FBVyxjQUFjO0FBQUEsUUFDN0MsTUFBTTtBQUFBLFFBQ04sU0FBUyxDQUFDLE1BQU0sS0FBSztBQUFBLFFBQ3JCLFVBQVUsQ0FBQyxXQUFXLGNBQWM7QUFBQSxNQUN0QztBQUFBLE1BQ0EsZUFBZTtBQUFBLFFBQ2IsVUFBVTtBQUFBLFVBQ1I7QUFBQSxVQUNBO0FBQUEsVUFDQTtBQUFBLFVBQ0E7QUFBQSxVQUNBO0FBQUEsVUFDQTtBQUFBLFVBQ0E7QUFBQSxVQUNBO0FBQUEsVUFDQTtBQUFBLFFBQ0Y7QUFBQSxRQUNBLFFBQVE7QUFBQSxVQUNOLGNBQWM7QUFBQSxVQUNkLFNBQVM7QUFBQSxZQUNQLE9BQU87QUFBQSxZQUNQLElBQUk7QUFBQSxZQUNKLGFBQWE7QUFBQSxZQUNiLGNBQWM7QUFBQSxZQUNkLGFBQWE7QUFBQSxZQUNiLGFBQWE7QUFBQSxVQUNmO0FBQUEsUUFDRjtBQUFBLE1BQ0Y7QUFBQSxJQUNGO0FBQUEsSUFDQSxNQUFNO0FBQUEsTUFDSixTQUFTO0FBQUEsTUFDVCxhQUFhO0FBQUEsSUFDZjtBQUFBLEVBQ0YsQ0FBQztBQUNIO0FBRUEsSUFBTyxzQkFBUTsiLAogICJuYW1lcyI6IFtdCn0K
|
||||
@@ -1,5 +1,11 @@
|
||||
# @portaljs/core
|
||||
|
||||
## 1.0.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#958](https://github.com/datopian/portaljs/pull/958) [`c4bf5bd0`](https://github.com/datopian/portaljs/commit/c4bf5bd05401982125750e61a27ed31f9bb14a4d) Thanks [@olayway](https://github.com/olayway)! - Add basic Hero component.
|
||||
|
||||
## 1.0.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@portaljs/core",
|
||||
"version": "1.0.5",
|
||||
"version": "1.0.6",
|
||||
"description": "Core Portal.JS components, configs and utils.",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
90
packages/core/src/ui/Hero/Hero.tsx
Normal file
@@ -0,0 +1,90 @@
|
||||
interface Button {
|
||||
title: string;
|
||||
href: string;
|
||||
type: 'filled' | 'text'
|
||||
}
|
||||
|
||||
export interface HeroProps {
|
||||
title: string;
|
||||
subtitle?: string;
|
||||
announcement?: {
|
||||
title: string;
|
||||
href?: string;
|
||||
};
|
||||
buttons?: Array<Button>
|
||||
}
|
||||
|
||||
const buttonStyle = {
|
||||
'filled': "rounded-md bg-secondary px-3.5 py-2.5 text-sm font-semibold text-white shadow-sm hover:bg-secondary/90 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600",
|
||||
'text': "text-sm font-semibold leading-6"
|
||||
}
|
||||
|
||||
|
||||
export const Hero: React.FC<HeroProps> = ({ title, subtitle, announcement, buttons }) => {
|
||||
|
||||
return (
|
||||
<div className="text-primary dark:text-primary-dark">
|
||||
<div className="relative isolate px-6 pt-14 lg:px-8">
|
||||
{/* <div
|
||||
className="absolute inset-x-0 -top-40 -z-10 transform-gpu overflow-hidden blur-3xl sm:-top-80"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div
|
||||
className="relative left-[calc(50%-11rem)] aspect-[1155/678] w-[36.125rem] -translate-x-1/2 rotate-[30deg] bg-gradient-to-tr from-[#38bdf8] to-[#89a8fc] opacity-30 sm:left-[calc(50%-30rem)] sm:w-[72.1875rem]"
|
||||
style={{
|
||||
clipPath:
|
||||
'polygon(74.1% 44.1%, 100% 61.6%, 97.5% 26.9%, 85.5% 0.1%, 80.7% 2%, 72.5% 32.5%, 60.2% 62.4%, 52.4% 68.1%, 47.5% 58.3%, 45.2% 34.5%, 27.5% 76.7%, 0.1% 64.9%, 17.9% 100%, 27.6% 76.8%, 76.1% 97.7%, 74.1% 44.1%)',
|
||||
}}
|
||||
/>
|
||||
</div> */}
|
||||
<div className="mx-auto max-w-2xl py-16 sm:py-32 lg:py-40">
|
||||
{announcement && (
|
||||
<div className="hidden sm:mb-8 sm:flex sm:justify-center">
|
||||
<div className="relative rounded-full px-3 py-1 text-sm leading-6 ring-1 ring-primary/20 hover:ring-primary/30 dark:ring-primary-dark/30 dark:hover:ring-primary-dark/40">
|
||||
{announcement.title}{' '}
|
||||
<a href="#" className="font-semibold text-secondary">
|
||||
<span className="absolute inset-0" aria-hidden="true" />
|
||||
Read more <span aria-hidden="true">→</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="text-center">
|
||||
<h1 className="text-4xl font-bold tracking-tight sm:text-6xl">
|
||||
{title}
|
||||
</h1>
|
||||
{subtitle && (
|
||||
<p className="mt-6 text-lg leading-8 text-primary/90 dark:text-primary-dark/80">
|
||||
{subtitle}
|
||||
</p>
|
||||
)}
|
||||
{buttons && buttons.length && (
|
||||
<div className="mt-10 flex items-center justify-center gap-x-6">
|
||||
{buttons.map((b) => (
|
||||
<a
|
||||
href={b.href}
|
||||
className={buttonStyle[b.type]}
|
||||
>
|
||||
{b.title}
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{/* <div
|
||||
className="absolute inset-x-0 top-[calc(100%-13rem)] -z-10 transform-gpu overflow-hidden blur-3xl sm:top-[calc(100%-30rem)]"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div
|
||||
className="relative left-[calc(50%+3rem)] aspect-[1155/678] w-[36.125rem] -translate-x-1/2 bg-gradient-to-tr from-[#89a8fc] to-[#38bdf8] opacity-30 sm:left-[calc(50%+36rem)] sm:w-[72.1875rem]"
|
||||
style={{
|
||||
clipPath:
|
||||
'polygon(74.1% 44.1%, 100% 61.6%, 97.5% 26.9%, 85.5% 0.1%, 80.7% 2%, 72.5% 32.5%, 60.2% 62.4%, 52.4% 68.1%, 47.5% 58.3%, 45.2% 34.5%, 27.5% 76.7%, 0.1% 64.9%, 17.9% 100%, 27.6% 76.8%, 76.1% 97.7%, 74.1% 44.1%)',
|
||||
}}
|
||||
/>
|
||||
</div> */}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
1
packages/core/src/ui/Hero/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { Hero } from "./Hero"
|
||||
@@ -18,5 +18,5 @@ export { BlogLayout } from "./BlogLayout";
|
||||
export { Mermaid } from "./Mermaid";
|
||||
export { SiteToc, NavItem, NavGroup } from "./SiteToc";
|
||||
export { Comments, CommentsConfig } from "./Comments";
|
||||
|
||||
export { AuthorConfig } from "./types";
|
||||
export { Hero } from "./Hero";
|
||||
|
||||
5
site/.gitignore
vendored
@@ -35,3 +35,8 @@ yarn-error.log*
|
||||
|
||||
# markdowndb
|
||||
markdown.db
|
||||
|
||||
# seo
|
||||
robots.txt
|
||||
sitemap-0.xml
|
||||
sitemap.xml
|
||||
|
||||
@@ -58,7 +58,7 @@ export default function Features() {
|
||||
>
|
||||
<div className="absolute -inset-px rounded-xl border-2 border-transparent opacity-0 [background:linear-gradient(var(--quick-links-hover-bg,theme(colors.sky.50)),var(--quick-links-hover-bg,theme(colors.sky.50)))_padding-box,linear-gradient(to_top,theme(colors.blue.300),theme(colors.blue.400),theme(colors.blue.500))_border-box] group-hover:opacity-100 dark:[--quick-links-hover-bg:theme(colors.slate.800)]" />
|
||||
<div className="relative overflow-hidden rounded-xl p-6">
|
||||
<img src={feature.icon} alt="" className="h-24 w-auto" />
|
||||
<img src={feature.icon} alt={feature.title} className="h-24 w-auto" />
|
||||
<h2 className="mt-4 font-display text-base text-primary dark:text-primary-dark">
|
||||
<span className="absolute -inset-px rounded-xl" />
|
||||
{feature.title}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useRef } from 'react';
|
||||
import ButtonLink from './ButtonLink';
|
||||
import NewsletterForm from './NewsletterForm';
|
||||
import Image from 'next/image';
|
||||
import DatahubExampleImg from "@/public/images/showcases/datahub.png"
|
||||
import DatahubExampleImg from '@/public/images/showcases/datahub.webp';
|
||||
|
||||
const codeLanguage = 'javascript';
|
||||
const code = `export default {
|
||||
@@ -41,7 +41,7 @@ export function Hero() {
|
||||
{/* Commented code on line 37, 39 and 113 will reenable the two columns hero */}
|
||||
<div className="mx-auto grid max-w-2xl grid-cols-1 gap-y-16 gap-x-8 px-4 lg:max-w-8xl lg:grid-cols-2 lg:px-8 xl:gap-x-16 xl:px-12">
|
||||
<div className="relative mb-10 lg:mb-0 md:text-center lg:text-left">
|
||||
<div role="heading">
|
||||
<div>
|
||||
<h1 className="inline bg-gradient-to-r from-blue-500 via-blue-300 to-blue-500 bg-clip-text text-5xl tracking-tight text-transparent">
|
||||
The JavaScript framework for data portals
|
||||
</h1>
|
||||
@@ -72,9 +72,11 @@ export function Hero() {
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<img
|
||||
<Image
|
||||
src="/images/datopian_logo.png"
|
||||
alt="Datopian"
|
||||
width={24}
|
||||
height={20}
|
||||
className="mx-2 mb-1 h-6 inline bg-black rounded-full"
|
||||
/>
|
||||
<span>Datopian</span>
|
||||
@@ -85,7 +87,12 @@ export function Hero() {
|
||||
<div className="relative rounded-2xl bg-[#0A101F]/80 ring-1 ring-white/10 backdrop-blur">
|
||||
<div className="absolute -top-px left-20 right-11 h-px bg-gradient-to-r from-sky-300/0 via-sky-300/70 to-sky-300/0" />
|
||||
<div className="absolute -bottom-px left-11 right-20 h-px bg-gradient-to-r from-blue-400/0 via-blue-400 to-blue-400/0" />
|
||||
<Image src={DatahubExampleImg} alt="opendata.datahub.io" />
|
||||
<Image
|
||||
height={400}
|
||||
width={600}
|
||||
src={DatahubExampleImg}
|
||||
alt="opendata.datahub.io"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
65
site/components/JSONLD.tsx
Normal file
@@ -0,0 +1,65 @@
|
||||
import { ArticleJsonLd } from 'next-seo';
|
||||
import { useRouter } from 'next/router';
|
||||
|
||||
export default function JSONLD({
|
||||
meta,
|
||||
source,
|
||||
}: {
|
||||
meta: any;
|
||||
source: string;
|
||||
}): JSX.Element {
|
||||
if (!source) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
const baseUrl = process.env.NEXT_PUBLIC_SITE_URL || 'https://portaljs.org';
|
||||
const pageUrl = `${baseUrl}/${meta.urlPath}`;
|
||||
|
||||
const imageMatches = source.match(
|
||||
/(?<=src: ")(.*)\.((png)|(jpg)|(jpeg))(?=")/g
|
||||
);
|
||||
let images = [];
|
||||
if (imageMatches) {
|
||||
images = [...imageMatches];
|
||||
images = images.map((img) =>
|
||||
img.startsWith('http')
|
||||
? img
|
||||
: `${baseUrl}${img.startsWith('/') ? '' : '/'}${img}`
|
||||
);
|
||||
}
|
||||
|
||||
let Component: JSX.Element;
|
||||
|
||||
const isBlog: boolean =
|
||||
/^blog\/.*/.test(meta.urlPath) || meta.filetype === 'blog';
|
||||
const isDoc: boolean = /^((docs)|(howtos\/)|(guide\/)).*/.test(meta.urlPath);
|
||||
|
||||
if (isBlog) {
|
||||
Component = (
|
||||
<ArticleJsonLd
|
||||
type="BlogPosting"
|
||||
url={pageUrl}
|
||||
title={meta.title}
|
||||
datePublished={meta.date}
|
||||
dateModified={meta.date}
|
||||
authorName={meta.authors.length ? meta.authors[0].name : 'PortalJS'}
|
||||
description={meta.description}
|
||||
images={images}
|
||||
/>
|
||||
);
|
||||
} else if (isDoc) {
|
||||
Component = (
|
||||
<ArticleJsonLd
|
||||
url={pageUrl}
|
||||
title={meta.title}
|
||||
images={images}
|
||||
datePublished={meta.date}
|
||||
dateModified={meta.date}
|
||||
authorName={meta.authors.length ? meta.authors[0].name : 'PortalJS'}
|
||||
description={meta.description}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return Component;
|
||||
}
|
||||
@@ -54,12 +54,14 @@ function useTableOfContents(tableOfContents) {
|
||||
export default function Layout({
|
||||
children,
|
||||
title,
|
||||
description,
|
||||
tableOfContents = [],
|
||||
isHomePage = false,
|
||||
sidebarTree = [],
|
||||
}: {
|
||||
children;
|
||||
title?: string;
|
||||
description?: string;
|
||||
tableOfContents?;
|
||||
urlPath?: string;
|
||||
sidebarTree?: [];
|
||||
@@ -82,7 +84,7 @@ export default function Layout({
|
||||
|
||||
return (
|
||||
<>
|
||||
{title && <NextSeo title={title} />}
|
||||
{title && <NextSeo title={title} description={description} />}
|
||||
<Nav />
|
||||
<div className="mx-auto p-6 bg-background dark:bg-background-dark">
|
||||
{isHomePage && <Hero />}
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
import { MDXRemote } from 'next-mdx-remote';
|
||||
import { NextSeo } from 'next-seo';
|
||||
import layouts from 'layouts';
|
||||
import DocsPagination from './DocsPagination';
|
||||
import { NextSeo } from 'next-seo';
|
||||
import { Hero } from "@portaljs/core";
|
||||
|
||||
export default function MDXPage({ source, frontMatter }) {
|
||||
const Layout = ({ children }) => {
|
||||
const layoutName = frontMatter?.layout || 'default';
|
||||
const LayoutComponent = layouts[layoutName];
|
||||
const Layout = ({ children }) => {
|
||||
const layoutName = frontMatter?.layout || 'default';
|
||||
const LayoutComponent = layouts[layoutName];
|
||||
|
||||
return <LayoutComponent {...frontMatter}>{children}</LayoutComponent>;
|
||||
};
|
||||
return <LayoutComponent {...frontMatter}>{children}</LayoutComponent>;
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<MDXRemote {...source} components={{ DocsPagination, NextSeo }} />
|
||||
</Layout>
|
||||
);
|
||||
return (
|
||||
<Layout>
|
||||
<MDXRemote {...source} components={{ DocsPagination, NextSeo, Hero }} />
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ export default function NavItem({ item }) {
|
||||
};
|
||||
|
||||
return (
|
||||
<Menu as="div" className="relative">
|
||||
<Menu as="div" role="menu" className="relative">
|
||||
<Menu.Item>
|
||||
{Object.prototype.hasOwnProperty.call(item, 'href') ? (
|
||||
<Link
|
||||
|
||||
@@ -3,10 +3,6 @@ import Script from 'next/script';
|
||||
export default function NewsletterForm() {
|
||||
return (
|
||||
<div>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://sibforms.com/forms/end-form/build/sib-styles.css"
|
||||
/>
|
||||
<div
|
||||
id="sib-form-container"
|
||||
className="mt-8 sm:mx-auto sm:text-center lg:text-left lg:mx-0"
|
||||
@@ -119,6 +115,7 @@ export default function NewsletterForm() {
|
||||
}}
|
||||
/>
|
||||
<Script
|
||||
strategy="worker"
|
||||
id="newsletter-submit-form"
|
||||
src="https://sibforms.com/forms/end-form/build/main.js"
|
||||
/>
|
||||
|
||||
@@ -5,26 +5,26 @@ const items = [
|
||||
{
|
||||
title: 'Open Data Northern Ireland',
|
||||
href: 'https://www.opendatani.gov.uk/',
|
||||
image: '/images/showcases/odni.png',
|
||||
image: '/images/showcases/odni.webp',
|
||||
description: 'Government Open Data Portal',
|
||||
},
|
||||
{
|
||||
title: 'Birmingham City Observatory',
|
||||
href: 'https://www.cityobservatory.birmingham.gov.uk/',
|
||||
image: '/images/showcases/birmingham.png',
|
||||
image: '/images/showcases/birmingham.webp',
|
||||
description: 'Government Open Data Portal',
|
||||
},
|
||||
{
|
||||
title: 'UAE Open Data',
|
||||
href: 'https://opendata.fcsc.gov.ae/',
|
||||
image: '/images/showcases/uae.png',
|
||||
image: '/images/showcases/uae.webp',
|
||||
description: 'Government Open Data Portal',
|
||||
sourceUrl: 'https://github.com/FCSCOpendata/frontend',
|
||||
},
|
||||
{
|
||||
title: 'Datahub Open Data',
|
||||
href: 'https://opendata.datahub.io/',
|
||||
image: '/images/showcases/datahub.png',
|
||||
image: '/images/showcases/datahub.webp',
|
||||
description: 'Demo Data Portal by DataHub',
|
||||
},
|
||||
];
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { defaultConfig } from "@flowershow/core";
|
||||
import { defaultConfig } from "@portaljs/core";
|
||||
import userConfig from "../content/config";
|
||||
|
||||
export const siteConfig: any = {
|
||||
|
||||
BIN
site/content/assets/blog/2023-07-18-map-polygons-and-points.png
Normal file
|
After Width: | Height: | Size: 416 KiB |
BIN
site/content/assets/blog/2023-07-18-map-polygons-on-region.png
Normal file
|
After Width: | Height: | Size: 187 KiB |
BIN
site/content/assets/blog/2023-07-18-map-tooltips.png
Normal file
|
After Width: | Height: | Size: 467 KiB |
BIN
site/content/assets/blog/dataview.gif
Normal file
|
After Width: | Height: | Size: 2.0 MiB |
BIN
site/content/assets/blog/gh-authorize.png
Normal file
|
After Width: | Height: | Size: 792 KiB |
BIN
site/content/assets/blog/gh-desktop-clone-path-select.png
Normal file
|
After Width: | Height: | Size: 218 KiB |
BIN
site/content/assets/blog/gh-desktop-clone-signin.png
Normal file
|
After Width: | Height: | Size: 192 KiB |
BIN
site/content/assets/blog/gh-desktop-clone.png
Normal file
|
After Width: | Height: | Size: 212 KiB |
BIN
site/content/assets/blog/gh-desktop-no-local-changes.png
Normal file
|
After Width: | Height: | Size: 237 KiB |
BIN
site/content/assets/blog/gh-desktop-signin-using-browser.png
Normal file
|
After Width: | Height: | Size: 233 KiB |
BIN
site/content/assets/blog/gh-desktop-starting-screen.png
Normal file
|
After Width: | Height: | Size: 168 KiB |
BIN
site/content/assets/blog/github-desktop-add-commit-message.png
Normal file
|
After Width: | Height: | Size: 227 KiB |
BIN
site/content/assets/blog/github-desktop-all-changed-files.png
Normal file
|
After Width: | Height: | Size: 223 KiB |
BIN
site/content/assets/blog/github-desktop-commit-message.png
Normal file
|
After Width: | Height: | Size: 23 KiB |
BIN
site/content/assets/blog/github-desktop-history-after-push.png
Normal file
|
After Width: | Height: | Size: 276 KiB |
BIN
site/content/assets/blog/github-desktop-history.png
Normal file
|
After Width: | Height: | Size: 277 KiB |
BIN
site/content/assets/blog/harry.png
Normal file
|
After Width: | Height: | Size: 2.2 MiB |
BIN
site/content/assets/blog/harry_original.png
Normal file
|
After Width: | Height: | Size: 1.4 MiB |
BIN
site/content/assets/blog/link-preview.png
Normal file
|
After Width: | Height: | Size: 296 KiB |
BIN
site/content/assets/blog/live-book-home-page.png
Normal file
|
After Width: | Height: | Size: 213 KiB |
BIN
site/content/assets/blog/live-book.png
Normal file
|
After Width: | Height: | Size: 276 KiB |
BIN
site/content/assets/blog/obsidian-content-add-folder-2.png
Normal file
|
After Width: | Height: | Size: 210 KiB |
BIN
site/content/assets/blog/obsidian-content-add-folder.png
Normal file
|
After Width: | Height: | Size: 212 KiB |
BIN
site/content/assets/blog/obsidian-content-book-index-2.png
Normal file
|
After Width: | Height: | Size: 260 KiB |
BIN
site/content/assets/blog/obsidian-content-book-index-3.png
Normal file
|
After Width: | Height: | Size: 208 KiB |
BIN
site/content/assets/blog/obsidian-content-book-index.png
Normal file
|
After Width: | Height: | Size: 183 KiB |
BIN
site/content/assets/blog/obsidian-content-book.png
Normal file
|
After Width: | Height: | Size: 271 KiB |
BIN
site/content/assets/blog/obsidian-content-index-add-link-2.png
Normal file
|
After Width: | Height: | Size: 208 KiB |
BIN
site/content/assets/blog/obsidian-content-index-add-link.png
Normal file
|
After Width: | Height: | Size: 236 KiB |
|
After Width: | Height: | Size: 254 KiB |
BIN
site/content/assets/blog/obsidian-content-index-callout.png
Normal file
|
After Width: | Height: | Size: 280 KiB |
BIN
site/content/assets/blog/obsidian-content-index-edit.png
Normal file
|
After Width: | Height: | Size: 205 KiB |
BIN
site/content/assets/blog/obsidian-content-index-link-aliases.png
Normal file
|
After Width: | Height: | Size: 255 KiB |
BIN
site/content/assets/blog/obsidian-content-start.png
Normal file
|
After Width: | Height: | Size: 195 KiB |
BIN
site/content/assets/blog/obsidian-starting.png
Normal file
|
After Width: | Height: | Size: 105 KiB |
BIN
site/content/assets/blog/table1.png
Normal file
|
After Width: | Height: | Size: 57 KiB |
BIN
site/content/assets/blog/table2.png
Normal file
|
After Width: | Height: | Size: 64 KiB |
BIN
site/content/assets/blog/tutorial-1-result.png
Normal file
|
After Width: | Height: | Size: 578 KiB |
BIN
site/content/assets/blog/tutorial-2-result.png
Normal file
|
After Width: | Height: | Size: 305 KiB |
BIN
site/content/assets/blog/vercel-dashboard.png
Normal file
|
After Width: | Height: | Size: 277 KiB |
BIN
site/content/assets/blog/vercel-project-dashboard.png
Normal file
|
After Width: | Height: | Size: 444 KiB |
201
site/content/blog/create-a-simple-catalog-of-anything.md
Normal file
@@ -0,0 +1,201 @@
|
||||
---
|
||||
title: Create a catalog of anything using Markdown files in Obsidian
|
||||
date: 2023-05-30
|
||||
authors: ['Ola Rubaj']
|
||||
filetype: 'blog'
|
||||
---
|
||||
|
||||
![[dataview.gif]]
|
||||
|
||||
Have you ever wanted to create a catalog of stuff? Maybe it's a list of personal projects, maybe favourite books or movies, or perhaps the options for the next smartphone you'll buy. But you've found yourself deterred by expensive software, lack of flexibility in capturing and modifying the structure of the data, or lack of control over it?
|
||||
|
||||
Markdown files, with their unique combination of 1) rich content, including text, images, links, and more, with 2) structured metadata allowing for data retrieval, are an excellent tool for this task. These features, coupled with the user-friendly interface of Obsidian and the analytical power of the Dataview plugin, make creating a data catalog an easy task. And it's free!
|
||||
|
||||
This tutorial will provide you with an easy yet extendable approach to creating your personal data catalogs using Markdown, in Obsidian.
|
||||
|
||||
## What are we going to build?
|
||||
|
||||
In this tutorial, we're going to create a catalog of characters from the Harry Potter series. Each character will have their own markdown file containing some unstructured data like character's quotes, as well as structured metadata, such as the character's name, house, creature they own, and status (alive or dead).
|
||||
|
||||
## Step 1: Setup
|
||||
|
||||
Here are the steps to get you started:
|
||||
|
||||
1. Download and install Obsidian from their [official website](https://obsidian.md/).
|
||||
2. Create a new Obsidian vault.
|
||||
1. Open the app.
|
||||
2. Click the "New" button to create a new vault.
|
||||
3. Choose a name and a location for your vault. For this tutorial, you might name it "Harry Potter Characters".
|
||||
4. Click "Create" to create the vault.
|
||||
3. Install the ["Dataview" plugin](https://github.com/blacksmithgu/obsidian-dataview) from Obsidian's community plugins.
|
||||
1. Open the newly created vault in Obsidian.
|
||||
2. Click on the settings icon ⚙️ in the left-hand pane to open the Settings view.
|
||||
3. In the Settings view, find the "Community plugins" section and click on it.
|
||||
4. Click on the button "Turn on community plugins".
|
||||
5. Click on "Browse" and search for "Dataview" in the plugin browser.
|
||||
6. Click on "Install" to install the Dataview plugin.
|
||||
7. After the plugin is installed, click on "Enable" to enable the plugin in your vault.
|
||||
|
||||
## Step 2: Add some data about the characters
|
||||
|
||||
Let's start by creating a subfolder in our Obsidian vault, that will store all the markdown files with our characters data. Let's name it e.g. `/characters`. Then, we're going to create three markdown files in it for data about Harry Potter, Hermione, and Malfoy.
|
||||
|
||||
Here's an example file for Harry Potter:
|
||||
|
||||
```md
|
||||
---
|
||||
name: Harry Potter
|
||||
house: Gryffindor
|
||||
status: Alive
|
||||
---
|
||||
|
||||
## Quotes
|
||||
|
||||
"I solemnly swear I am up to no good."
|
||||
```
|
||||
|
||||
Here's an example file for Hermione:
|
||||
|
||||
```md
|
||||
---
|
||||
name: Hermione Granger
|
||||
house: Gryffindor
|
||||
status: Alive
|
||||
---
|
||||
|
||||
## Quotes
|
||||
|
||||
"Books! And cleverness! There are more important things - friendship and bravery."
|
||||
```
|
||||
|
||||
Here's an example file for Malfoy:
|
||||
|
||||
```md
|
||||
---
|
||||
name: Draco Malfoy
|
||||
house: Slytherin
|
||||
status: Alive
|
||||
---
|
||||
|
||||
Draco Malfoy is Harry's rival and a member of Slytherin House.
|
||||
|
||||
## Quotes
|
||||
|
||||
"My father will hear about this!"
|
||||
```
|
||||
|
||||
By the end of this step you should end up with the following folder structure:
|
||||
|
||||
```
|
||||
Harry Potter Characters
|
||||
└── characters
|
||||
├── Harry Potter.md
|
||||
├── Hermione.md
|
||||
└── Malfoy.md
|
||||
```
|
||||
|
||||
## Step 3: Create a data catalog
|
||||
|
||||
Now let's create a data catalog of our Harry Potter characters!
|
||||
|
||||
In the root of our vault, let's create a file called `Catalog` (but you can name it whatever you want) that's going to display a table with all our characters and their metadata. Inside it, we're going to write the following code block with a simple Dataview query:
|
||||
|
||||
````md
|
||||
```dataview
|
||||
table name, house, status
|
||||
from "characters"
|
||||
sort name asc
|
||||
```
|
||||
````
|
||||
|
||||
Let's break it down:
|
||||
|
||||
- `table name, house, status`: This line instructs Dataview to create a table that includes columns for "name", "house", and "status". These are fields that you should have defined in your markdown files' frontmatter, but you don't have to. For any missing field Dataview will just display `-`.
|
||||
- `from "characters"`: This tells Dataview to pull the data from all markdown files located in the `/characters` folder in your Obsidian vault.
|
||||
- `sort name asc`: This command sorts the data based on the "name" field, in ascending order.
|
||||
|
||||
After you click somewhere outside of this code block, you should see the following table:
|
||||
|
||||
![[table1.png]]
|
||||
|
||||
## Step 4: Add more metadata about characters
|
||||
|
||||
We can continue to add more information about our characters. For example, let's add creatures each character owns:
|
||||
|
||||
```md=
|
||||
---
|
||||
name: Harry Potter
|
||||
...
|
||||
creature: Hedwig (Owl)
|
||||
---
|
||||
|
||||
...
|
||||
|
||||
---
|
||||
|
||||
name: Hermione Granger
|
||||
...
|
||||
creature: Crookshanks (Cat)
|
||||
---
|
||||
|
||||
...
|
||||
|
||||
```
|
||||
|
||||
Now, if we want to show this field in our Dataview table, we need to add it to the first line of our dataview code block:
|
||||
|
||||
````md
|
||||
```dataview
|
||||
table name, house, status, creature
|
||||
from "characters"
|
||||
sort name asc
|
||||
```
|
||||
````
|
||||
|
||||
After clicking somewhere outside the code block, you should see an updated table that includes the column `creature`.
|
||||
|
||||
![[table2.png]]
|
||||
|
||||
## Step 5: Enrich content with images and links
|
||||
|
||||
To enhance your catalog, you can add images of characters, and links to other characters in your markdown content.
|
||||
|
||||
#### Add images
|
||||
|
||||
To add an image of a character, right-click on the image you want to embed and copy it to clipboard - you can use the image below - and paste it directly in your markdown file.
|
||||
|
||||
![[harry_original.png]]
|
||||
|
||||
Obsidian will automatically save the file to the root of your vault and create a link to it in your content, so you'll end up with a link similar to this one:
|
||||
|
||||
```md
|
||||
![[Pasted image 20230525212302.png]]
|
||||
```
|
||||
|
||||
Which will render as:
|
||||
|
||||
![[harry.png]]
|
||||
|
||||
#### Add links to other pages
|
||||
|
||||
Obsidian allows you to create links between different notes (or in this case, characters). To add a link to another character, use the double bracket `[[]]` syntax.
|
||||
|
||||
For example, if you're writing about Harry Potter and want to mention that he is friends with Hermione, you can link to Hermione's markdown file like this:
|
||||
|
||||
```md
|
||||
He is a friend of [[Hermione]].
|
||||
```
|
||||
|
||||
When you click on Hermione's name in the rendered link, Obsidian will take you to Hermione's file in your vault. If you hover over it, you'll see a preview of Hermione's file:
|
||||
|
||||
![[link-preview.png]]
|
||||
|
||||
## Summary
|
||||
|
||||
And there you have it! We've walked through the process of creating a personal data catalog using markdown files, the Obsidian application, and its Dataview plugin. We've transformed scattered data into a well-organized, easy-to-navigate, and visually pleasing catalog. Whether you've used our example of a Harry Potter character catalog or applied these steps to a different topic of your choosing, we hope you've found this tutorial helpful and empowering.
|
||||
|
||||
Happy data cataloging!
|
||||
|
||||
---
|
||||
|
||||
You can find the [vault created in this tutorial here](https://github.com/datopian/markdowndb/tree/main/examples/obsidian-dataview).
|
||||
190
site/content/blog/create-a-website-from-scratch.md
Normal file
@@ -0,0 +1,190 @@
|
||||
---
|
||||
title: 'Tutorial 1: Create a website from scratch using Markdown and Flowershow'
|
||||
date: 2023-06-20
|
||||
authors: ['Ola Rubaj']
|
||||
filetype: 'blog'
|
||||
---
|
||||
|
||||
In this tutorial we will walk you through creating an elegant, fully functional website written in simple markdown and published with Flowershow.
|
||||
|
||||
By the end of this tutorial you will:
|
||||
|
||||
- have a working markdown-based website powered by Flowershow.
|
||||
- be able to edit the text and add pages, all from an online interface without installing anything.
|
||||
|
||||
Below is a screenshot of how the final website will look like:
|
||||
![[tutorial-1-result.png]]
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- A [GitHub](https://github.com/) account.
|
||||
- A [Vercel](https://vercel.com/) account. You can set it up using your GitHub account.
|
||||
|
||||
## Setting up a sandbox website
|
||||
|
||||
### 1. Navigate to the [datopian/flowershow repository](https://github.com/datopian/flowershow).
|
||||
|
||||
### 2. Scroll down and click on the "Deploy" button
|
||||
|
||||
After clicking on it, you'll be redirected to Vercel's "Create Git Repository" page.
|
||||
|
||||

|
||||
|
||||
### 3. Select your GitHub account in "Git Scope"
|
||||
|
||||
Click on "Select Git Scope" dropdown and select your GitHub account name from the list if it's there.
|
||||
|
||||

|
||||
|
||||
If your GitHub account is not available in the dropdown list, click on "Add GitHub Account"...
|
||||
|
||||

|
||||
|
||||
...and authorize Vercel to access your GitHub repositories by clicking "Install".
|
||||
|
||||

|
||||
|
||||
Now you can select your GitHub account.
|
||||
|
||||

|
||||
|
||||
### 4. Give your repository a name
|
||||
|
||||
A good practice is to use lowercase and dashes.
|
||||
|
||||

|
||||
|
||||
### 5. Click on "Create" and wait until the site deploys
|
||||
|
||||
After you click "Create", Vercel will create a new repository on your GitHub account, using the `datopian/flowershow` repository as a template. Then, it will immediately start buidling the initial version of your website. This may take about 1-2 minutes.
|
||||
|
||||

|
||||
|
||||
### 6. See your published website!
|
||||
|
||||
And voila! Your site is up and running. Once on the "Congratulations" screen, navigate to the project dashboard...
|
||||
|
||||

|
||||
|
||||
... and click on "Visit" to see your published website!
|
||||
|
||||

|
||||
|
||||
## Editing a page on your website
|
||||
|
||||
Once your site is up and running, the next step is to customize it to your liking. Let's start by editing our home page.
|
||||
|
||||
### 1. Navigate to the repository of your website on GitHub
|
||||
|
||||
You can get there by going to GitHub, clicking on your profile icon, and going to "Your repositories".
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
Or, you can navigate to [your Vercel dashboard](https://vercel.com/dashboard), select your project in the "Overview" tab...
|
||||
|
||||

|
||||
|
||||
...and click on "Git Repository". You'll be redirected to the repository of your website on GitHub.
|
||||
|
||||

|
||||
|
||||
### 2. Navigate to the "content" folder
|
||||
|
||||
This is where all the Markdown-based pages live in a Flowershow-based project.
|
||||
|
||||

|
||||
|
||||
### 3. Edit the "index.md" file
|
||||
|
||||
The homepage on your website is built with the "index.md" file in the root of the "content" folder. Click on it to open.
|
||||
|
||||

|
||||
|
||||
Then, click on the "Edit this file" icon...
|
||||
|
||||

|
||||
|
||||
... and add some content.
|
||||
|
||||

|
||||
|
||||
### 4. Save your changes
|
||||
|
||||
To see your changes live, you need to "commit" them. Click on "Commit changes..." buttom in the top-tight corner.
|
||||
|
||||

|
||||
|
||||
In the "Commit message" field add a concise description of your changes. Optionally, if the commit message is not enough, you can add more info in the "Extended description" field.
|
||||
|
||||

|
||||
|
||||
Leave "Commit directly to `main` branch" selected and click on "Commit changes". Doing that will trigger rebuilding of your site on Vercel.
|
||||
|
||||

|
||||
|
||||
### 5. See your site getting rebuild
|
||||
|
||||
If you want to see the current progress of rebuilding your website after you've commited the changes, click on the dot next to your commit message.
|
||||
|
||||
> [!note]
|
||||
> It will be either a dot (if the site is currently being rebuilt after your changes), a check mark (if the site has finished building) or a cross (if something went wrong when rebuilding it).
|
||||
|
||||

|
||||
|
||||
Click on "Details" to see your project's deployment status on Vercel.
|
||||
|
||||

|
||||
|
||||
### 6. Preview your site after changes
|
||||
|
||||
Once the site has been rebuilt, click the preview to see your changes live.
|
||||
|
||||

|
||||
|
||||
## Add a single Markdown-based page
|
||||
|
||||
### 1. Navigate to the "content" folder in your website's repository
|
||||
|
||||
See how to find it in the previous section.
|
||||
|
||||
### 2. Create new file
|
||||
|
||||
Click on "Add file"...
|
||||
|
||||

|
||||
|
||||
...and "Create new file".
|
||||
|
||||

|
||||
|
||||
### 3. Type the name of the new file you want to create
|
||||
|
||||

|
||||
|
||||
### 4. Write the content of the file
|
||||
|
||||

|
||||
|
||||
### 5. Save your changes
|
||||
|
||||
To see your changes live, you need to "commit" them. Click on "Commit changes..." buttom in the top-tight corner.
|
||||
|
||||

|
||||
|
||||
In the "Commit message" field add a concise description of your changes. Optionally, if the commit message is not enough, you can add more info in the "Extended description" field.
|
||||
|
||||

|
||||
|
||||
Leave "Commit directly to `main` branch" selected and click on "Commit changes". Doing that will trigger rebuilding of your site on Vercel.
|
||||
|
||||

|
||||
|
||||
### 6. Preview your site after changes
|
||||
|
||||
As you already know, Vercel will now start rebuilding your website. When it's done, you can navigate to `/about` url on your website to see the new file we've just added.
|
||||
|
||||
## What's next?
|
||||
|
||||
While editing on GitHub UI is acceptable, it has its limitations – it doesn't support working offline, adding multiple files simultaneously, or previewing many markdown syntax elements supported by Flowershow-based websites. We'll delve into these issues and solutions to overcome them in our next tutorial. Stay tuned!
|
||||