Compare commits

..

96 Commits

Author SHA1 Message Date
Ola Rubaj
a580c2dc0a [guide/index.md][xs]: headings adjstmts 2023-07-28 15:35:18 +02:00
Ola Rubaj
a997a956b5 [site/content][xs]: minor fixes 2023-07-28 15:33:26 +02:00
Ola Rubaj
540a84d3f7 [site/content][s]: list howtos on the guide home page 2023-07-28 15:27:17 +02:00
Ola Rubaj
178659c6fe [site/content][s]: mv some files 2023-07-28 15:03:06 +02:00
Ola Rubaj
d8dfa8b682 [site/content][s]: rm duplicate howtos 2023-07-28 15:01:58 +02:00
Ola Rubaj
907ef9cbd3 [site/content][s]: delete duplicate howtos 2023-07-28 14:52:28 +02:00
Ola Rubaj
a8e399c59f [content/blog][s]: mv howto on formatting a perfect blog post from
markdownbased repo
2023-07-28 14:46:52 +02:00
Ola Rubaj
e7b71ec5c4 [content/blog][s]: mv howto on adding images from markdownbased repo 2023-07-28 14:31:04 +02:00
Ola Rubaj
eb987e139e [blog/*][xs]: add authors and dates 2023-07-28 14:11:08 +02:00
Ola Rubaj
845b98cca3 [site/content/blog/quicly-create-a-sandbox-website][xs]: adjust wiki links 2023-07-28 14:01:08 +02:00
Ola Rubaj
365bb3dfd3 [site/content][s]: mv howto on editing a page with code editor to
obsidian from markdownbased repo
2023-07-28 14:01:08 +02:00
Ola Rubaj
59a01f0f6f [site/content][s]: mv howto on adding a simple md page from
markdownbased repo
2023-07-28 14:01:08 +02:00
Ola Rubaj
a491fef5d0 [site/content][s]: mv howto on quickly creating a sandbox website from
markdownbased repo
2023-07-28 14:01:08 +02:00
Ola Rubaj
af0bc9da58 [site/content][s]: mv howto on editing and adding md pages locally from
markdownbased repo
2023-07-28 14:01:08 +02:00
Ola Rubaj
0b123a04ca [site/content][s]: mv howto on publishing obsidian vault to gh from
markdownbased repo
2023-07-28 14:01:08 +02:00
Ola Rubaj
0e2e2221fd [site/content][s]: mv howto on editing text on a single md page to blog
from markdownbased repo
2023-07-27 17:52:24 +02:00
Luccas Mateus de Medeiros Gomes
da226ef205 Merge branch 'main' of github.com:datopian/portaljs 2023-07-21 07:43:38 -03:00
Luccas Mateus de Medeiros Gomes
a37a31f89a [lighthouse][xs] - try ga lazyOnload 2023-07-21 07:42:36 -03:00
Luccas Mateus
06209877ea Lighthouse improvements (#990)
* [site][lighthouse] - increate accessibility

* [site][xs] - fix accessbiility
2023-07-20 11:11:07 -03:00
Luccas Mateus
822a3ce5ec [site][lighthouse] - increate accessibility (#989) 2023-07-20 10:17:24 -03:00
João Demenech
1f06c67d13 [site, blog][xs]: Fix order of authors on a post 2023-07-19 16:17:56 -03:00
João Demenech
9dea140859 SEO Improvements (#983)
* [site,seo][xs]: add custom _document with lang prop, add description to meta tags, make title larger

* [site,seo][xs]: add apple touch icon, add alt props to images

* [site,seo][xs]: add icon to default seo

* [site,seo][s]: implement next-sitemap

* [site,seo][s]: review page titles

* Rebuild package-lock.json files

* Regenerate package-lock
2023-07-19 07:23:06 -03:00
João Demenech
d5899b22ab [site,blog][xs]: Fix typo 2023-07-18 15:23:14 -03:00
João Demenech
dc895ed277 Merge pull request #984 from datopian/feature/maps-blog-post
[site]: Create new blog post about maps
2023-07-18 15:19:01 -03:00
João Demenech
7315df8a86 [site,blog][m]: create new blog post about maps 2023-07-18 14:08:33 -03:00
João Demenech
349f5bea66 Merge pull request #982 from datopian/lighthouse-improvements
[site][m] - improvements to lighthouse score
2023-07-17 15:04:25 -03:00
João Demenech
6aef860a81 Merge pull request #981 from datopian/changeset-release/main
Version Packages
2023-07-17 14:51:08 -03:00
Luccas Mateus de Medeiros Gomes
e908cb9344 [site][m] - improvements to lighthouse score 2023-07-17 14:44:29 -03:00
github-actions[bot]
1a22e54d5b Version Packages 2023-07-17 17:41:23 +00:00
João Demenech
172b4b71d4 Merge pull request #980 from datopian/bugfix/pdf-styles
Bugfix/pdf styles
2023-07-17 14:38:34 -03:00
João Demenech
3873852567 Bump version 2023-07-17 14:32:35 -03:00
João Demenech
5e349855a2 [components,pdf][xs]: add CSS styles for PDF 2023-07-17 14:29:57 -03:00
João Demenech
40bd9e0311 Merge pull request #977 from datopian/changeset-release/main
Version Packages
2023-07-14 14:44:01 -03:00
github-actions[bot]
b437b58d06 Version Packages 2023-07-14 17:31:11 +00:00
João Demenech
c3137ba1cb Regenerate package-lock.json 2023-07-14 14:28:14 -03:00
João Demenech
2e13c1b738 Release new version 2023-07-14 14:22:52 -03:00
João Demenech
122870a23e Merge: PDF viewer component
[components][lg] - pdf viewer
2023-07-13 13:35:35 -03:00
Luccas Mateus
4e282e0d86 Update package.json 2023-07-13 13:06:42 -03:00
Luccas Mateus
6020f76adb Merge branch 'main' into feature/pdfviewer 2023-07-13 13:03:20 -03:00
João Demenech
f3c2a2ffa7 Excel component (#973)
* [components,excel][m]: initial version of the <Excel /> component

* [components,excel][m]: add support for multiple sheets

* [components,map,excel][l]: fix leaflet map markers on prod and implement excel component

* Bump version

* [components,excel][xs]: change data for one of the stories
2023-07-13 13:02:37 -03:00
Luccas Mateus de Medeiros Gomes
11659a838b [components][lg] - pdf viewer 2023-07-12 14:50:30 -03:00
Anuar Ustayev (aka Anu)
58b7b4e753 Merge pull request #962 from datopian/tutorial-2-going-local
Tutorial 2: Edit your Flowershow website locally on your computer
2023-07-12 13:55:24 +02:00
Anuar Ustayev (aka Anu)
7cf8c31e53 Update edit-a-website-locally.md 2023-07-12 13:55:01 +02:00
João Demenech
df000b9e8f [openspending,css][xs]: fix issues with css styles (#972) 2023-07-10 16:24:19 -03:00
Luccas Mateus de Medeiros Gomes
77e9f58899 [examples/openspending][xs] - remove css 2023-07-10 13:38:24 -03:00
github-actions[bot]
0737aaafb2 Version Packages (#971)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-07-10 10:09:37 -03:00
João Demenech
d798f402f6 Leaflet Map: multiple layers (#969)
* [components,maps][l]: leaflet map now supports multiple layers and tooltip props setting

* Remove unused var

* Update vite.config.ts

---------

Co-authored-by: Luccas Mateus <Luccasmmg@gmail.com>
2023-07-10 09:55:37 -03:00
Luccas Mateus de Medeiros Gomes
80c6221a05 [portaljs/components][lg] - map components changeset 2023-07-10 09:56:24 -03:00
Luccas Mateus
f04b86dda4 Update frankfurt-is-investing-in-education-and-your-city-should-too.mdx 2023-07-07 12:44:04 -03:00
Luccas Mateus
0fd3ee9912 Map components - Leaflet and OpenLayers (#968)
* [components,maps][l]: implements Leaflet map component -- #963

* [openspending, maps][m]: fix build for leaflet map

* Feature/openlayers map (#967)

* [maps][xl] - working with swc equals to minify

* [maps][xs] - fixing height

* [openspending][xs] - testing

* [openspending][xs] - testing

* [openspending][xs] - change order drd

* [openspending][xs] - add map

* [maps,tests][xs]: add default export to OpenLayers component

* [@portaljs/components][m] - map components

---------

Co-authored-by: João Demenech <joaommdemenech@gmail.com>
2023-07-07 11:22:34 -03:00
Luccas Mateus
cb0b9b1f14 Fix typos 2023-07-04 11:25:08 -03:00
Luccas Mateus de Medeiros Gomes
9ee4376abf [examples/ckan][sm] - fix responsiveness 2023-06-27 07:23:36 -03:00
Ola Rubaj
5a0ddd91ce [content/guide/index][s]: add overview of tutorial 2 2023-06-26 10:27:28 +02:00
Ola Rubaj
d097bc765b [blog/edit-a-website-locally][s]: improve section on markdown 2023-06-26 10:10:00 +02:00
Ola Rubaj
b283fc1e99 [content/blog/edit-a-website-locally][s]: polishing 2023-06-22 18:46:33 +02:00
Ola Rubaj
0511e00d83 [site/content][xs]: adjust tutorial title 2023-06-22 16:56:26 +02:00
Ola Rubaj
c8afa775d4 [site/content/blog][m]: tutorial on local editing 2023-06-22 16:52:45 +02:00
Ola Rubaj
7ba9b5157e [site/howtos/comments][xs]: rm unneeded link 2023-06-22 11:21:48 +02:00
Ola Rubaj
6c2a1ea125 [site/howtos][m]: mv from datopian/markdownbased 2023-06-22 11:16:12 +02:00
Ola Rubaj
343faf72cf [site/content][s]: mv /howto to /howtos 2023-06-22 11:11:06 +02:00
Ola Rubaj
1eb3f7367b [site/blog][m]: add blog "Create a catalog of anything" 2023-06-22 11:02:23 +02:00
Ola Rubaj
8cdf54397f [package.json][xs]: run mddb before dev 2023-06-22 10:47:38 +02:00
Ola Rubaj
fb94cb9ce9 [site/guide/index.md][s]: adjst tutorials overview 2023-06-22 10:42:34 +02:00
Ola Rubaj
4595cd2231 [site/content/blog][xs]: change title 2023-06-22 10:28:03 +02:00
Ola Rubaj
aa2c8aac04 [site/content/blog][s]: add tutorial on creating a flowershow website
from scratch
2023-06-22 10:20:42 +02:00
Ola Rubaj
f2e5459297 [site/lib/markdown][s]: add support for obsidian style shortened paths 2023-06-22 10:20:11 +02:00
Ola Rubaj
e111adfe73 [site/components/MDXPage][xs]: pass Hero component to mdx 2023-06-22 10:05:22 +02:00
Ola Rubaj
492c21ca4e [site/package.json][xs]: use latest portaljs/core 2023-06-22 10:04:37 +02:00
Ola Rubaj
0581357df8 [site/content][s]: add /guide with home page
- moved from markdownbased repo
2023-06-22 09:57:14 +02:00
João Demenech
15ceeec035 [openspending,stories][xs]: fix typo 2023-06-21 10:10:49 -03:00
João Demenech
1caabcf6b4 [openspending][xs]: bump @portaljs/components version 2023-06-20 19:26:42 -03:00
João Demenech
b548dfd113 Merge pull request #961 from datopian/changeset-release/main
Version Packages
2023-06-20 19:13:57 -03:00
github-actions[bot]
c5ee257d48 Version Packages 2023-06-20 20:32:15 +00:00
João Demenech
8d83f3a900 Merge pull request #960 from datopian/bugfix/linechart-width
[@portaljs/components,line chart][xs]: fix linechart width
2023-06-20 17:29:13 -03:00
João Demenech
add2f6d0f3 [@portaljs/components,line chart][xs]: fix linechart width 2023-06-20 17:12:16 -03:00
github-actions[bot]
dfab6aa318 Version Packages (#959)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-06-20 17:37:03 +02:00
Rufus Pollock
f96fb562fe Merge pull request #958 from datopian/bespoke-hero 2023-06-20 17:32:43 +02:00
Ola Rubaj
c4bf5bd054 [core/ui/Hero][s]: create Hero component 2023-06-20 15:52:20 +02:00
Ola Rubaj
c706575ae4 Replace @flowershow/* with @portaljs/* packages in /examples (#953)
* [examples/ckan][m]: rplc @flowershow with @portaljs packages

* [examples/fivethirtyeight][m]: rplc @flowershow with @portaljs packages

* [examples/turing][m]: rplc @flowershow with @portaljs packages

* [examples/openspending][m]: rplc @flowershow with @portaljs packages

* [examples/learn][m]: rplc @flowershow with @portaljs packages

* [examples/github-backed-catalog][m]: rplc @flowershow with @portaljs packages

* [examples/github-backed-catalog][xs] - fix build

* [examples][m] - fix builds

* [examples/openspending][xs] - fix build

---------

Co-authored-by: Luccas Mateus de Medeiros Gomes <luccasmmg@gmail.com>
2023-06-16 09:13:54 -03:00
João Demenech
ed8de380a9 Merge pull request #950 from datopian/site-deps-update
Replace `@flowershow/*` with `@portaljs/*` packages in /site
2023-06-16 09:04:42 -03:00
João Demenech
33521916d6 [site,blog,#944][xs]: update titles of blog posts 2023-06-15 17:28:12 -03:00
João Demenech
04206457a4 [openspending,contributing][xs]: add link to issue tracker 2023-06-13 19:08:20 -03:00
João Demenech
8a5acb7012 Merge pull request #952 from datopian/openspending/moers-anomaly
OpenSpending: Moers Anomaly story
2023-06-13 19:03:36 -03:00
João Demenech
32493a2014 [openspending,stories][xs]: fix data url 2023-06-13 19:00:00 -03:00
Michael Polidori
b34220cac7 Merge pull request #951 from datopian/openspending/berlin-erdf-data-driven-story
OpenSpending - Berlin ERDF spendings data-driven story
2023-06-13 15:22:15 -04:00
Michael Polidori
44b37e27d9 Add Moers Anomaly story 2023-06-13 15:20:48 -04:00
João Demenech
6d3e571151 [openspending,stories][m]: fix typo 2023-06-13 12:42:01 -03:00
João Demenech
0e997f71e5 [openspending,stories][m]: add new data-driven story about Berlin 2023-06-13 12:39:02 -03:00
olayway
4a41d517ee [package.json][xs]: rm prerelease script
Handling this in projects package.json files.
2023-06-13 17:12:53 +02:00
olayway
c79b69ffe6 [site/package.json][s]: bump @portaljs packages 2023-06-13 12:33:54 +02:00
olayway
2ad6551a44 [site][s]: @flowershow/remark-wiki-link -> @portaljs/remark-wiki-link 2023-06-13 12:33:54 +02:00
olayway
5de9888c02 [site][s]: @flowershow/remark-embed -> @portaljs/remark-embed 2023-06-13 12:33:54 +02:00
olayway
5a517d714a [blog/markdowndb-basics][xs]: typo fix 2023-06-13 12:33:52 +02:00
olayway
746c77de11 [site][s]: @flowershow/markdowndb -> mddb 2023-06-13 12:32:29 +02:00
olayway
9e256b9bf1 [site][s]: @flowershow/remark-callouts -> @portaljs/remark-callouts 2023-06-13 12:32:29 +02:00
olayway
4bfcd4373b [site][s]: @flowershow/core -> @portaljs/core 2023-06-13 12:32:29 +02:00
152 changed files with 14442 additions and 1264 deletions

View File

@@ -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>
);
}

View File

@@ -0,0 +1,5 @@
const nextConfig = {
swcMinify: false
}
module.exports = nextConfig

View File

@@ -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 }) {

View File

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

View File

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

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

View File

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

View File

@@ -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",

View File

@@ -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",

View File

@@ -1,4 +1,5 @@
import { AppProps } from 'next/app';
import '@portaljs/components/styles.css';
import './styles.css';
import { NextSeo } from 'next-seo';

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -1,6 +1,7 @@
import 'tailwindcss/tailwind.css'
import '../src/index.css'
import type { Preview } from '@storybook/react';
const preview: Preview = {

View File

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

View File

@@ -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": {

Binary file not shown.

View File

@@ -1,6 +1,10 @@
console.log('PostCSS');
export default {
plugins: {
'postcss-import': {},
'postcss-url': { url: 'inline' },
tailwindcss: {},
autoprefixer: {},
},
}
};

View 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 });

View 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>
)}
</>
);
}

View File

@@ -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',

View 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='&copy; <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>
);
}

View 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>
);
};

View File

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

View File

@@ -0,0 +1,4 @@
import React from 'react';
export const Layers = ({ children }) => {
return <div>{children}</div>;
};

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

View 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>
);
}

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

View File

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

File diff suppressed because it is too large Load Diff

View 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>
);
}

View 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;
}
}

View File

@@ -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";

View File

@@ -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";

View 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[];
}
}

View 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',
},
};

View 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,
},
};

View 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,
},
],
},
};

View 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,
},
};

View File

@@ -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: [],
};

View File

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

View File

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

View File

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

View File

@@ -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",

View 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">&rarr;</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>
)
}

View File

@@ -0,0 +1 @@
export { Hero } from "./Hero"

View File

@@ -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
View File

@@ -35,3 +35,8 @@ yarn-error.log*
# markdowndb
markdown.db
# seo
robots.txt
sitemap-0.xml
sitemap.xml

View File

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

View File

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

View File

@@ -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>
);
}

View File

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

View File

@@ -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"
/>

View File

@@ -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',
},
];

View File

@@ -1,4 +1,4 @@
import { defaultConfig } from "@flowershow/core";
import { defaultConfig } from "@portaljs/core";
import userConfig from "../content/config";
export const siteConfig: any = {

Binary file not shown.

After

Width:  |  Height:  |  Size: 416 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 187 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 467 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 792 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 218 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 192 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 237 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 233 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 227 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 223 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 276 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 277 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 296 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 213 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 276 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 210 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 260 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 183 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 271 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 236 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 254 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 280 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 205 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 255 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 195 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 578 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 305 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 277 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 444 KiB

View 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.
![Step 2 screenshot](https://images.tango.us/workflows/9bef984b-9dd1-4c07-ae32-88bb426c306e/steps/35cb9df6-d93e-4ce6-9741-54be744cf9e7/55ca0e14-6780-40e3-9ecc-148a7fa7c08e.png?crop=focalpoint&fit=crop&fp-x=0.0945&fp-y=0.4783&fp-z=2.5970&w=1200&border=2%2CF4F2F7&border-radius=8%2C8%2C8%2C8&border-radius-inner=8%2C8%2C8%2C8&blend-align=bottom&blend-mode=normal&blend-x=0&blend-w=1200&blend64=aHR0cHM6Ly9pbWFnZXMudGFuZ28udXMvc3RhdGljL21hZGUtd2l0aC10YW5nby13YXRlcm1hcmstdjIucG5n&mark-x=162&mark-y=440&m64=aHR0cHM6Ly9pbWFnZXMudGFuZ28udXMvc3RhdGljL2JsYW5rLnBuZz9tYXNrPWNvcm5lcnMmYm9yZGVyPTYlMkNGRjc0NDImdz0yNjUmaD03MiZmaXQ9Y3JvcCZjb3JuZXItcmFkaXVzPTEw)
### 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.
![Step 3 screenshot](https://images.tango.us/workflows/9bef984b-9dd1-4c07-ae32-88bb426c306e/steps/bdd61961-e1fe-4282-abec-75ae66bfeaa5/85df39b9-51a8-410a-a83b-93f2d2d6256c.png?crop=focalpoint&fit=crop&fp-x=0.2455&fp-y=0.4226&fp-z=1.4615&w=1200&border=2%2CF4F2F7&border-radius=8%2C8%2C8%2C8&border-radius-inner=8%2C8%2C8%2C8&blend-align=bottom&blend-mode=normal&blend-x=0&blend-w=1200&blend64=aHR0cHM6Ly9pbWFnZXMudGFuZ28udXMvc3RhdGljL21hZGUtd2l0aC10YW5nby13YXRlcm1hcmstdjIucG5n&mark-x=94&mark-y=427&m64=aHR0cHM6Ly9pbWFnZXMudGFuZ28udXMvc3RhdGljL2JsYW5rLnBuZz9tYXNrPWNvcm5lcnMmYm9yZGVyPTYlMkNGRjc0NDImdz02NzQmaD05OSZmaXQ9Y3JvcCZjb3JuZXItcmFkaXVzPTEw)
If your GitHub account is not available in the dropdown list, click on "Add GitHub Account"...
![Step 3 screenshot](https://images.tango.us/workflows/9bef984b-9dd1-4c07-ae32-88bb426c306e/steps/879ebed5-fec7-443e-8be9-66b73a4ec044/583f8a42-fc35-43d8-b791-70e22976ed46.png?crop=focalpoint&fit=crop&fp-x=0.2455&fp-y=0.5030&fp-z=1.5031&w=1200&border=2%2CF4F2F7&border-radius=8%2C8%2C8%2C8&border-radius-inner=8%2C8%2C8%2C8&blend-align=bottom&blend-mode=normal&blend-x=0&blend-w=1200&blend64=aHR0cHM6Ly9pbWFnZXMudGFuZ28udXMvc3RhdGljL21hZGUtd2l0aC10YW5nby13YXRlcm1hcmstdjIucG5n&mark-x=113&mark-y=429&m64=aHR0cHM6Ly9pbWFnZXMudGFuZ28udXMvc3RhdGljL2JsYW5rLnBuZz9tYXNrPWNvcm5lcnMmYm9yZGVyPTYlMkNGRjc0NDImdz02NTkmaD05NCZmaXQ9Y3JvcCZjb3JuZXItcmFkaXVzPTEw)
...and authorize Vercel to access your GitHub repositories by clicking "Install".
![Step 3 screenshot](https://images.tango.us/workflows/9bef984b-9dd1-4c07-ae32-88bb426c306e/steps/61441f7d-ccc4-45ca-b52d-4ccc0573d787/ff97c393-2fff-407e-abe2-bc96c18562cc.png?crop=focalpoint&fit=crop&fp-x=0.2922&fp-y=0.7621&fp-z=2.4427&w=1200&border=2%2CF4F2F7&border-radius=8%2C8%2C8%2C8&border-radius-inner=8%2C8%2C8%2C8&blend-align=bottom&blend-mode=normal&blend-x=0&blend-w=1200&blend64=aHR0cHM6Ly9pbWFnZXMudGFuZ28udXMvc3RhdGljL21hZGUtd2l0aC10YW5nby13YXRlcm1hcmstdjIucG5n&mark-x=440&mark-y=385&m64=aHR0cHM6Ly9pbWFnZXMudGFuZ28udXMvc3RhdGljL2JsYW5rLnBuZz9tYXNrPWNvcm5lcnMmYm9yZGVyPTYlMkNGRjc0NDImdz0zMjEmaD0xMjgmZml0PWNyb3AmY29ybmVyLXJhZGl1cz0xMA%3D%3D)
Now you can select your GitHub account.
![Step 3 screenshot](https://images.tango.us/workflows/9bef984b-9dd1-4c07-ae32-88bb426c306e/steps/d2382391-f4bc-4ffe-b8e8-e22b9f6550ad/f4817109-ae2e-49f9-98ad-2f8dfe9a5b3b.png?crop=focalpoint&fit=crop&fp-x=0.2455&fp-y=0.5833&fp-z=1.5031&w=1200&border=2%2CF4F2F7&border-radius=8%2C8%2C8%2C8&border-radius-inner=8%2C8%2C8%2C8&blend-align=bottom&blend-mode=normal&blend-x=0&blend-w=1200&blend64=aHR0cHM6Ly9pbWFnZXMudGFuZ28udXMvc3RhdGljL21hZGUtd2l0aC10YW5nby13YXRlcm1hcmstdjIucG5n&mark-x=113&mark-y=429&m64=aHR0cHM6Ly9pbWFnZXMudGFuZ28udXMvc3RhdGljL2JsYW5rLnBuZz9tYXNrPWNvcm5lcnMmYm9yZGVyPTYlMkNGRjc0NDImdz02NTkmaD05NCZmaXQ9Y3JvcCZjb3JuZXItcmFkaXVzPTEw)
### 4. Give your repository a name
A good practice is to use lowercase and dashes.
![Step 4 screenshot](https://images.tango.us/workflows/9bef984b-9dd1-4c07-ae32-88bb426c306e/steps/a9b6af4c-4645-4b93-9c28-75d23e7f48a3/55dcaf47-8851-4808-ab9e-6fe6c4d552aa.png?crop=focalpoint&fit=crop&fp-x=0.6919&fp-y=0.4929&fp-z=1.7767&w=1200&border=2%2CF4F2F7&border-radius=8%2C8%2C8%2C8&border-radius-inner=8%2C8%2C8%2C8&blend-align=bottom&blend-mode=normal&blend-x=0&blend-w=1200&blend64=aHR0cHM6Ly9pbWFnZXMudGFuZ28udXMvc3RhdGljL21hZGUtd2l0aC10YW5nby13YXRlcm1hcmstdjIucG5n&mark-x=57&mark-y=416&m64=aHR0cHM6Ly9pbWFnZXMudGFuZ28udXMvc3RhdGljL2JsYW5rLnBuZz9tYXNrPWNvcm5lcnMmYm9yZGVyPTYlMkNGRjc0NDImdz0xMDg2Jmg9MTIxJmZpdD1jcm9wJmNvcm5lci1yYWRpdXM9MTA%3D)
### 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.
![Step 5 screenshot](https://images.tango.us/workflows/9bef984b-9dd1-4c07-ae32-88bb426c306e/steps/ae740310-736d-48e5-9fbf-cf567a2ff966/2bc653a7-0e6c-4eaa-a11b-85a2a4d4c1d3.png?crop=focalpoint&fit=crop&fp-x=0.5007&fp-y=0.5152&fp-z=1.0564&w=1200&border=2%2CF4F2F7&border-radius=8%2C8%2C8%2C8&border-radius-inner=8%2C8%2C8%2C8&blend-align=bottom&blend-mode=normal&blend-x=0&blend-w=1200&blend64=aHR0cHM6Ly9pbWFnZXMudGFuZ28udXMvc3RhdGljL21hZGUtd2l0aC10YW5nby13YXRlcm1hcmstdjIucG5n&mark-x=33&mark-y=379&m64=aHR0cHM6Ly9pbWFnZXMudGFuZ28udXMvc3RhdGljL2JsYW5rLnBuZz9tYXNrPWNvcm5lcnMmYm9yZGVyPTYlMkNGRjc0NDImdz0xMTM0Jmg9MTk1JmZpdD1jcm9wJmNvcm5lci1yYWRpdXM9MTA%3D)
### 6. See your published website!
And voila! Your site is up and running. Once on the "Congratulations" screen, navigate to the project dashboard...
![Step 6 screenshot](https://images.tango.us/workflows/9bef984b-9dd1-4c07-ae32-88bb426c306e/steps/1df8967e-2bcf-43ec-b3d0-a9d638d21ff7/af6d363d-2872-4a45-b5e5-16ca754e4702.png?crop=focalpoint&fit=crop&fp-x=0.5005&fp-y=0.3351&fp-z=1.8197&w=1200&border=2%2CF4F2F7&border-radius=8%2C8%2C8%2C8&border-radius-inner=8%2C8%2C8%2C8&blend-align=bottom&blend-mode=normal&blend-x=0&blend-w=1200&blend64=aHR0cHM6Ly9pbWFnZXMudGFuZ28udXMvc3RhdGljL21hZGUtd2l0aC10YW5nby13YXRlcm1hcmstdjIucG5n&mark-x=328&mark-y=414&m64=aHR0cHM6Ly9pbWFnZXMudGFuZ28udXMvc3RhdGljL2JsYW5rLnBuZz9tYXNrPWNvcm5lcnMmYm9yZGVyPTYlMkNGRjc0NDImdz01NDUmaD0xMjQmZml0PWNyb3AmY29ybmVyLXJhZGl1cz0xMA%3D%3D)
... and click on "Visit" to see your published website!
![Step 6 screenshot](https://images.tango.us/workflows/9bef984b-9dd1-4c07-ae32-88bb426c306e/steps/319f8eef-5007-4885-a3d6-6dc7fd7472ea/80b34413-f7da-4765-90d6-89a93fb4eab5.png?crop=focalpoint&fit=crop&fp-x=0.0697&fp-y=0.3482&fp-z=2.5500&w=1200&border=2%2CF4F2F7&border-radius=8%2C8%2C8%2C8&border-radius-inner=8%2C8%2C8%2C8&blend-align=bottom&blend-mode=normal&blend-x=0&blend-w=1200&blend64=aHR0cHM6Ly9pbWFnZXMudGFuZ28udXMvc3RhdGljL21hZGUtd2l0aC10YW5nby13YXRlcm1hcmstdjIucG5n&mark-x=72&mark-y=390&m64=aHR0cHM6Ly9pbWFnZXMudGFuZ28udXMvc3RhdGljL2JsYW5rLnBuZz9tYXNrPWNvcm5lcnMmYm9yZGVyPTYlMkNGRjc0NDImdz0yODImaD0xNzQmZml0PWNyb3AmY29ybmVyLXJhZGl1cz0xMA%3D%3D)
## 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".
![Step 1 screenshot](https://images.tango.us/workflows/5b5166f4-2965-4a91-9bec-47108fe34234/steps/adecdf18-e46a-4003-984e-1dc1945963df/6792cf00-20fc-4e24-88ca-8d35a41aadab.png?crop=focalpoint&fit=crop&fp-x=0.9589&fp-y=0.0345&fp-z=3.0108&w=1200&border=2%2CF4F2F7&border-radius=8%2C8%2C8%2C8&border-radius-inner=8%2C8%2C8%2C8&blend-align=bottom&blend-mode=normal&blend-x=0&blend-w=1200&blend64=aHR0cHM6Ly9pbWFnZXMudGFuZ28udXMvc3RhdGljL21hZGUtd2l0aC10YW5nby13YXRlcm1hcmstdjIucG5n&mark-x=992&mark-y=53&m64=aHR0cHM6Ly9pbWFnZXMudGFuZ28udXMvc3RhdGljL2JsYW5rLnBuZz9tYXNrPWNvcm5lcnMmYm9yZGVyPTYlMkNGRjc0NDImdz0xMjAmaD05MiZmaXQ9Y3JvcCZjb3JuZXItcmFkaXVzPTEw)
![Step 1 screenshot](https://images.tango.us/workflows/5b5166f4-2965-4a91-9bec-47108fe34234/steps/8578ff07-5dc9-4908-bc81-197293970344/9b01a3fb-47fe-48ed-a7bc-c9910e9e42ef.png?crop=focalpoint&fit=crop&fp-x=0.8963&fp-y=0.1997&fp-z=2.9422&w=1200&border=2%2CF4F2F7&border-radius=8%2C8%2C8%2C8&border-radius-inner=8%2C8%2C8%2C8&blend-align=bottom&blend-mode=normal&blend-x=0&blend-w=1200&blend64=aHR0cHM6Ly9pbWFnZXMudGFuZ28udXMvc3RhdGljL21hZGUtd2l0aC10YW5nby13YXRlcm1hcmstdjIucG5n&mark-x=553&mark-y=420&m64=aHR0cHM6Ly9pbWFnZXMudGFuZ28udXMvc3RhdGljL2JsYW5rLnBuZz9tYXNrPWNvcm5lcnMmYm9yZGVyPTYlMkNGRjc0NDImdz01NjImaD0xMTImZml0PWNyb3AmY29ybmVyLXJhZGl1cz0xMA%3D%3D)
Or, you can navigate to [your Vercel dashboard](https://vercel.com/dashboard), select your project in the "Overview" tab...
![Step 1 screenshot](https://images.tango.us/workflows/4aa9bd80-6829-415e-b3a8-6136bfb176eb/steps/b7f96b7d-28b1-4366-b889-681b327b5f40/a0d2bffd-2e81-4878-854b-0766cf710339.png?crop=focalpoint&fit=crop&fp-x=0.2578&fp-y=0.4408&fp-z=1.3063&w=1200&border=2%2CF4F2F7&border-radius=8%2C8%2C8%2C8&border-radius-inner=8%2C8%2C8%2C8&blend-align=bottom&blend-mode=normal&blend-x=0&blend-w=1200&blend64=aHR0cHM6Ly9pbWFnZXMudGFuZ28udXMvc3RhdGljL21hZGUtd2l0aC10YW5nby13YXRlcm1hcmstdjIucG5n&mark-x=39&mark-y=274&m64=aHR0cHM6Ly9pbWFnZXMudGFuZ28udXMvc3RhdGljL2JsYW5rLnBuZz9tYXNrPWNvcm5lcnMmYm9yZGVyPTYlMkNGRjc0NDImdz03MzAmaD00MDQmZml0PWNyb3AmY29ybmVyLXJhZGl1cz0xMA%3D%3D)
...and click on "Git Repository". You'll be redirected to the repository of your website on GitHub.
![Step 1 screenshot](https://images.tango.us/workflows/4aa9bd80-6829-415e-b3a8-6136bfb176eb/steps/2cfbda16-23dc-45b2-9be9-a4b5b3a255d1/da2be1ca-da13-4257-a830-3ece2b89ff51.png?crop=focalpoint&fit=crop&fp-x=0.4560&fp-y=0.4590&fp-z=1.1707&w=1200&border=2%2CF4F2F7&border-radius=8%2C8%2C8%2C8&border-radius-inner=8%2C8%2C8%2C8&blend-align=bottom&blend-mode=normal&blend-x=0&blend-w=1200&blend64=aHR0cHM6Ly9pbWFnZXMudGFuZ28udXMvc3RhdGljL21hZGUtd2l0aC10YW5nby13YXRlcm1hcmstdjIucG5n&mark-x=321&mark-y=313&m64=aHR0cHM6Ly9pbWFnZXMudGFuZ28udXMvc3RhdGljL2JsYW5rLnBuZz9tYXNrPWNvcm5lcnMmYm9yZGVyPTYlMkNGRjc0NDImdz0yMjYmaD04MCZmaXQ9Y3JvcCZjb3JuZXItcmFkaXVzPTEw)
### 2. Navigate to the "content" folder
This is where all the Markdown-based pages live in a Flowershow-based project.
![Step 2 screenshot](https://images.tango.us/workflows/4aa9bd80-6829-415e-b3a8-6136bfb176eb/steps/4ce5b74e-40e5-4c3f-a3ce-be7fa8959489/25b9ddc8-c1df-44f8-bf3e-7d08960f0592.png?crop=focalpoint&fit=crop&fp-x=0.0900&fp-y=0.4574&fp-z=2.8680&w=1200&border=2%2CF4F2F7&border-radius=8%2C8%2C8%2C8&border-radius-inner=8%2C8%2C8%2C8&blend-align=bottom&blend-mode=normal&blend-x=0&blend-w=1200&blend64=aHR0cHM6Ly9pbWFnZXMudGFuZ28udXMvc3RhdGljL21hZGUtd2l0aC10YW5nby13YXRlcm1hcmstdjIucG5n&mark-x=226&mark-y=441&m64=aHR0cHM6Ly9pbWFnZXMudGFuZ28udXMvc3RhdGljL2JsYW5rLnBuZz9tYXNrPWNvcm5lcnMmYm9yZGVyPTYlMkNGRjc0NDImdz0xNjgmaD03MCZmaXQ9Y3JvcCZjb3JuZXItcmFkaXVzPTEw)
### 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.
![Step 3 screenshot](https://images.tango.us/workflows/4aa9bd80-6829-415e-b3a8-6136bfb176eb/steps/b14c059d-89e4-419b-8ac4-0e219ed195af/4d86a32a-b5f8-48d1-bccd-ddbfd140fe5a.png?crop=focalpoint&fit=crop&fp-x=0.0754&fp-y=0.4711&fp-z=2.7997&w=1200&border=2%2CF4F2F7&border-radius=8%2C8%2C8%2C8&border-radius-inner=8%2C8%2C8%2C8&blend-align=bottom&blend-mode=normal&blend-x=0&blend-w=1200&blend64=aHR0cHM6Ly9pbWFnZXMudGFuZ28udXMvc3RhdGljL21hZGUtd2l0aC10YW5nby13YXRlcm1hcmstdjIucG5n&mark-x=157&mark-y=442&m64=aHR0cHM6Ly9pbWFnZXMudGFuZ28udXMvc3RhdGljL2JsYW5rLnBuZz9tYXNrPWNvcm5lcnMmYm9yZGVyPTYlMkNGRjc0NDImdz0xOTImaD02OCZmaXQ9Y3JvcCZjb3JuZXItcmFkaXVzPTEw)
Then, click on the "Edit this file" icon...
![Step 3 screenshot](https://images.tango.us/workflows/4aa9bd80-6829-415e-b3a8-6136bfb176eb/steps/e4d28cd5-3863-46e3-813d-5304ec0d769f/ce2bf765-9bef-4d96-8354-0eed3aa88c03.png?crop=focalpoint&fit=crop&fp-x=0.9435&fp-y=0.3449&fp-z=2.9525&w=1200&border=2%2CF4F2F7&border-radius=8%2C8%2C8%2C8&border-radius-inner=8%2C8%2C8%2C8&blend-align=bottom&blend-mode=normal&blend-x=0&blend-w=1200&blend64=aHR0cHM6Ly9pbWFnZXMudGFuZ28udXMvc3RhdGljL21hZGUtd2l0aC10YW5nby13YXRlcm1hcmstdjIucG5n&mark-x=945&mark-y=422&m64=aHR0cHM6Ly9pbWFnZXMudGFuZ28udXMvc3RhdGljL2JsYW5rLnBuZz9tYXNrPWNvcm5lcnMmYm9yZGVyPTYlMkNGRjc0NDImdz0xMDkmaD0xMDkmZml0PWNyb3AmY29ybmVyLXJhZGl1cz0xMA%3D%3D)
... and add some content.
![Step 3 screenshot](https://images.tango.us/workflows/4aa9bd80-6829-415e-b3a8-6136bfb176eb/steps/daad6528-db93-426c-8b8b-ae67d0a28189/a5767c01-a16e-4c4b-b4a0-5595663b175d.png?crop=focalpoint&fit=crop&fp-x=0.5170&fp-y=0.3762&fp-z=1.0470&w=1200&border=2%2CF4F2F7&border-radius=8%2C8%2C8%2C8&border-radius-inner=8%2C8%2C8%2C8&blend-align=bottom&blend-mode=normal&blend-x=0&blend-w=1200&blend64=aHR0cHM6Ly9pbWFnZXMudGFuZ28udXMvc3RhdGljL21hZGUtd2l0aC10YW5nby13YXRlcm1hcmstdjIucG5n&mark-x=7&mark-y=300&m64=aHR0cHM6Ly9pbWFnZXMudGFuZ28udXMvc3RhdGljL2JsYW5rLnBuZz9tYXNrPWNvcm5lcnMmYm9yZGVyPTYlMkNGRjc0NDImdz0xMTg2Jmg9MTUwJmZpdD1jcm9wJmNvcm5lci1yYWRpdXM9MTA%3D)
### 4. Save your changes
To see your changes live, you need to "commit" them. Click on "Commit changes..." buttom in the top-tight corner.
![Step 4 screenshot](https://images.tango.us/workflows/4aa9bd80-6829-415e-b3a8-6136bfb176eb/steps/74159a33-2897-4ffb-af3d-ce2fa109e570/0e96f7a4-b4ff-418e-9b44-73573a4354d1.png?crop=focalpoint&fit=crop&fp-x=0.9227&fp-y=0.2208&fp-z=2.9167&w=1200&border=2%2CF4F2F7&border-radius=8%2C8%2C8%2C8&border-radius-inner=8%2C8%2C8%2C8&blend-align=bottom&blend-mode=normal&blend-x=0&blend-w=1200&blend64=aHR0cHM6Ly9pbWFnZXMudGFuZ28udXMvc3RhdGljL21hZGUtd2l0aC10YW5nby13YXRlcm1hcmstdjIucG5n&mark-x=696&mark-y=417&m64=aHR0cHM6Ly9pbWFnZXMudGFuZ28udXMvc3RhdGljL2JsYW5rLnBuZz9tYXNrPWNvcm5lcnMmYm9yZGVyPTYlMkNGRjc0NDImdz00NjgmaD0xMTkmZml0PWNyb3AmY29ybmVyLXJhZGl1cz0xMA%3D%3D)
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.
![Step 4 screenshot](https://images.tango.us/workflows/4aa9bd80-6829-415e-b3a8-6136bfb176eb/steps/09130863-221f-436e-bab0-a46bea2fd5c4/bdbd1458-3b5c-4352-a8fb-5921132ee3e4.png?crop=focalpoint&fit=crop&fp-x=0.4998&fp-y=0.3476&fp-z=1.4555&w=1200&border=2%2CF4F2F7&border-radius=8%2C8%2C8%2C8&border-radius-inner=8%2C8%2C8%2C8&blend-align=bottom&blend-mode=normal&blend-x=0&blend-w=1200&blend64=aHR0cHM6Ly9pbWFnZXMudGFuZ28udXMvc3RhdGljL21hZGUtd2l0aC10YW5nby13YXRlcm1hcmstdjIucG5n&mark-x=262&mark-y=447&m64=aHR0cHM6Ly9pbWFnZXMudGFuZ28udXMvc3RhdGljL2JsYW5rLnBuZz9tYXNrPWNvcm5lcnMmYm9yZGVyPTYlMkNGRjc0NDImdz02NzYmaD01OCZmaXQ9Y3JvcCZjb3JuZXItcmFkaXVzPTEw)
Leave "Commit directly to `main` branch" selected and click on "Commit changes". Doing that will trigger rebuilding of your site on Vercel.
![Step 4 screenshot](https://images.tango.us/workflows/4aa9bd80-6829-415e-b3a8-6136bfb176eb/steps/40bf87e4-a2b1-4a99-97da-5e87530d0622/88a19d05-ce63-4da0-9e85-e427b063245e.png?crop=focalpoint&fit=crop&fp-x=0.5545&fp-y=0.5989&fp-z=1.3207&w=1200&border=2%2CF4F2F7&border-radius=8%2C8%2C8%2C8&border-radius-inner=8%2C8%2C8%2C8&blend-align=bottom&blend-mode=normal&blend-x=0&blend-w=1200&blend64=aHR0cHM6Ly9pbWFnZXMudGFuZ28udXMvc3RhdGljL21hZGUtd2l0aC10YW5nby13YXRlcm1hcmstdjIucG5n&mark-x=612&mark-y=616&m64=aHR0cHM6Ly9pbWFnZXMudGFuZ28udXMvc3RhdGljL2JsYW5rLnBuZz9tYXNrPWNvcm5lcnMmYm9yZGVyPTYlMkNGRjc0NDImdz0yMDcmaD01NSZmaXQ9Y3JvcCZjb3JuZXItcmFkaXVzPTEw)
### 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).
![Step 5 screenshot](https://images.tango.us/workflows/4aa9bd80-6829-415e-b3a8-6136bfb176eb/steps/0d273173-d852-47e4-b41e-418b9196e0fd/fbf1afe6-7af3-4b06-8ab0-d3c41d2923ce.png?crop=focalpoint&fit=crop&fp-x=0.5000&fp-y=0.5000&w=1200&border=2%2CF4F2F7&border-radius=8%2C8%2C8%2C8&border-radius-inner=8%2C8%2C8%2C8&blend-align=bottom&blend-mode=normal&blend-x=0&blend-w=1200&blend64=aHR0cHM6Ly9pbWFnZXMudGFuZ28udXMvc3RhdGljL21hZGUtd2l0aC10YW5nby13YXRlcm1hcmstdjIucG5n)
Click on "Details" to see your project's deployment status on Vercel.
![Step 5 screenshot](https://images.tango.us/workflows/4aa9bd80-6829-415e-b3a8-6136bfb176eb/steps/94b4e4ab-458c-4be9-b7a9-e34c7b4185b9/645687a4-b91d-4870-a935-48403b8ce33c.png?crop=focalpoint&fit=crop&fp-x=0.5000&fp-y=0.5000&w=1200&border=2%2CF4F2F7&border-radius=8%2C8%2C8%2C8&border-radius-inner=8%2C8%2C8%2C8&blend-align=bottom&blend-mode=normal&blend-x=0&blend-w=1200&blend64=aHR0cHM6Ly9pbWFnZXMudGFuZ28udXMvc3RhdGljL21hZGUtd2l0aC10YW5nby13YXRlcm1hcmstdjIucG5n)
### 6. Preview your site after changes
Once the site has been rebuilt, click the preview to see your changes live.
![Step 6 screenshot](https://images.tango.us/workflows/4aa9bd80-6829-415e-b3a8-6136bfb176eb/steps/a1294580-7d0d-492c-a880-32040b4bee48/5e7ddd43-03e3-4545-b1b3-cd631da401d8.png?crop=focalpoint&fit=crop&fp-x=0.2207&fp-y=0.3824&fp-z=1.4465&w=1200&border=2%2CF4F2F7&border-radius=8%2C8%2C8%2C8&border-radius-inner=8%2C8%2C8%2C8&blend-align=bottom&blend-mode=normal&blend-x=0&blend-w=1200&blend64=aHR0cHM6Ly9pbWFnZXMudGFuZ28udXMvc3RhdGljL21hZGUtd2l0aC10YW5nby13YXRlcm1hcmstdjIucG5n&mark-x=43&mark-y=261&m64=aHR0cHM6Ly9pbWFnZXMudGFuZ28udXMvc3RhdGljL2JsYW5rLnBuZz9tYXNrPWNvcm5lcnMmYm9yZGVyPTYlMkNGRjc0NDImdz02NzkmaD00MzEmZml0PWNyb3AmY29ybmVyLXJhZGl1cz0xMA%3D%3D)
## 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"...
![Step 2 screenshot](https://images.tango.us/workflows/c14ae6dc-9e15-49f5-af87-b513daa701ba/steps/1708e4fa-8234-4393-a8e9-cd972afce770/b986ab26-d53f-4b57-8520-fb87782dfb22.png?crop=focalpoint&fit=crop&fp-x=0.9119&fp-y=0.2208&fp-z=2.9167&w=1200&border=2%2CF4F2F7&border-radius=8%2C8%2C8%2C8&border-radius-inner=8%2C8%2C8%2C8&blend-align=bottom&blend-mode=normal&blend-x=0&blend-w=1200&blend64=aHR0cHM6Ly9pbWFnZXMudGFuZ28udXMvc3RhdGljL21hZGUtd2l0aC10YW5nby13YXRlcm1hcmstdjIucG5n&mark-x=737&mark-y=417&m64=aHR0cHM6Ly9pbWFnZXMudGFuZ28udXMvc3RhdGljL2JsYW5rLnBuZz9tYXNrPWNvcm5lcnMmYm9yZGVyPTYlMkNGRjc0NDImdz0zMDkmaD0xMTkmZml0PWNyb3AmY29ybmVyLXJhZGl1cz0xMA%3D%3D)
...and "Create new file".
![Step 2 screenshot](https://images.tango.us/workflows/c14ae6dc-9e15-49f5-af87-b513daa701ba/steps/e79d32ef-9385-4903-8ca1-408f78752633/7006ac1c-796c-4bb6-95ef-dd4d9969c9b6.png?crop=focalpoint&fit=crop&fp-x=0.8712&fp-y=0.2679&fp-z=2.9167&w=1200&border=2%2CF4F2F7&border-radius=8%2C8%2C8%2C8&border-radius-inner=8%2C8%2C8%2C8&blend-align=bottom&blend-mode=normal&blend-x=0&blend-w=1200&blend64=aHR0cHM6Ly9pbWFnZXMudGFuZ28udXMvc3RhdGljL21hZGUtd2l0aC10YW5nby13YXRlcm1hcmstdjIucG5n&mark-x=476&mark-y=417&m64=aHR0cHM6Ly9pbWFnZXMudGFuZ28udXMvc3RhdGljL2JsYW5rLnBuZz9tYXNrPWNvcm5lcnMmYm9yZGVyPTYlMkNGRjc0NDImdz01NDcmaD0xMTkmZml0PWNyb3AmY29ybmVyLXJhZGl1cz0xMA%3D%3D)
### 3. Type the name of the new file you want to create
![Step 3 screenshot](https://images.tango.us/workflows/c14ae6dc-9e15-49f5-af87-b513daa701ba/steps/54c786cb-15d6-4abe-8893-fa95dff3ed0a/c39152b2-f446-4e39-a2f3-9b3cf0e7c193.png?crop=focalpoint&fit=crop&fp-x=0.3476&fp-y=0.2214&fp-z=2.1864&w=1200&border=2%2CF4F2F7&border-radius=8%2C8%2C8%2C8&border-radius-inner=8%2C8%2C8%2C8&blend-align=bottom&blend-mode=normal&blend-x=0&blend-w=1200&blend64=aHR0cHM6Ly9pbWFnZXMudGFuZ28udXMvc3RhdGljL21hZGUtd2l0aC10YW5nby13YXRlcm1hcmstdjIucG5n&mark-x=394&mark-y=418&m64=aHR0cHM6Ly9pbWFnZXMudGFuZ28udXMvc3RhdGljL2JsYW5rLnBuZz9tYXNrPWNvcm5lcnMmYm9yZGVyPTYlMkNGRjc0NDImdz00MTMmaD04NyZmaXQ9Y3JvcCZjb3JuZXItcmFkaXVzPTEw)
### 4. Write the content of the file
![Step 4 screenshot](https://images.tango.us/workflows/c14ae6dc-9e15-49f5-af87-b513daa701ba/steps/8ffe1e8c-45f9-42b7-8053-bc8bc61b098c/56489034-d5a9-495e-8fb7-1fbfff62d7f3.png?crop=focalpoint&fit=crop&fp-x=0.5170&fp-y=0.3440&fp-z=1.0470&w=1200&border=2%2CF4F2F7&border-radius=8%2C8%2C8%2C8&border-radius-inner=8%2C8%2C8%2C8&blend-align=bottom&blend-mode=normal&blend-x=0&blend-w=1200&blend64=aHR0cHM6Ly9pbWFnZXMudGFuZ28udXMvc3RhdGljL21hZGUtd2l0aC10YW5nby13YXRlcm1hcmstdjIucG5n&mark-x=7&mark-y=300&m64=aHR0cHM6Ly9pbWFnZXMudGFuZ28udXMvc3RhdGljL2JsYW5rLnBuZz9tYXNrPWNvcm5lcnMmYm9yZGVyPTYlMkNGRjc0NDImdz0xMTg2Jmg9ODYmZml0PWNyb3AmY29ybmVyLXJhZGl1cz0xMA%3D%3D)
### 5. Save your changes
To see your changes live, you need to "commit" them. Click on "Commit changes..." buttom in the top-tight corner.
![Step 5 screenshot](https://images.tango.us/workflows/c14ae6dc-9e15-49f5-af87-b513daa701ba/steps/382d133d-19f3-4aee-a0fa-535fcb361090/fe7b1fc6-c8f0-4f3f-958a-204c4fc65cf8.png?crop=focalpoint&fit=crop&fp-x=0.9227&fp-y=0.2208&fp-z=2.9167&w=1200&border=2%2CF4F2F7&border-radius=8%2C8%2C8%2C8&border-radius-inner=8%2C8%2C8%2C8&blend-align=bottom&blend-mode=normal&blend-x=0&blend-w=1200&blend64=aHR0cHM6Ly9pbWFnZXMudGFuZ28udXMvc3RhdGljL21hZGUtd2l0aC10YW5nby13YXRlcm1hcmstdjIucG5n&mark-x=696&mark-y=417&m64=aHR0cHM6Ly9pbWFnZXMudGFuZ28udXMvc3RhdGljL2JsYW5rLnBuZz9tYXNrPWNvcm5lcnMmYm9yZGVyPTYlMkNGRjc0NDImdz00NjgmaD0xMTkmZml0PWNyb3AmY29ybmVyLXJhZGl1cz0xMA%3D%3D)
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.
![Step 5 screenshot](https://images.tango.us/workflows/c14ae6dc-9e15-49f5-af87-b513daa701ba/steps/13c5873d-4071-4ae2-b641-d6202631076c/53029856-5543-4681-919a-092ed2dacb02.png?crop=focalpoint&fit=crop&fp-x=0.4998&fp-y=0.3476&fp-z=1.4555&w=1200&border=2%2CF4F2F7&border-radius=8%2C8%2C8%2C8&border-radius-inner=8%2C8%2C8%2C8&blend-align=bottom&blend-mode=normal&blend-x=0&blend-w=1200&blend64=aHR0cHM6Ly9pbWFnZXMudGFuZ28udXMvc3RhdGljL21hZGUtd2l0aC10YW5nby13YXRlcm1hcmstdjIucG5n&mark-x=262&mark-y=447&m64=aHR0cHM6Ly9pbWFnZXMudGFuZ28udXMvc3RhdGljL2JsYW5rLnBuZz9tYXNrPWNvcm5lcnMmYm9yZGVyPTYlMkNGRjc0NDImdz02NzYmaD01OCZmaXQ9Y3JvcCZjb3JuZXItcmFkaXVzPTEw)
Leave "Commit directly to `main` branch" selected and click on "Commit changes". Doing that will trigger rebuilding of your site on Vercel.
![Step 5 screenshot](https://images.tango.us/workflows/c14ae6dc-9e15-49f5-af87-b513daa701ba/steps/68002c21-451a-4536-89b6-8a0d01d327eb/bb71cbb3-7a79-45f3-a71e-2500a380556c.png?crop=focalpoint&fit=crop&fp-x=0.6278&fp-y=0.7315&fp-z=2.3207&w=1200&border=2%2CF4F2F7&border-radius=8%2C8%2C8%2C8&border-radius-inner=8%2C8%2C8%2C8&blend-align=bottom&blend-mode=normal&blend-x=0&blend-w=1200&blend64=aHR0cHM6Ly9pbWFnZXMudGFuZ28udXMvc3RhdGljL21hZGUtd2l0aC10YW5nby13YXRlcm1hcmstdjIucG5n&mark-x=418&mark-y=428&m64=aHR0cHM6Ly9pbWFnZXMudGFuZ28udXMvc3RhdGljL2JsYW5rLnBuZz9tYXNrPWNvcm5lcnMmYm9yZGVyPTYlMkNGRjc0NDImdz0zNjUmaD05NyZmaXQ9Y3JvcCZjb3JuZXItcmFkaXVzPTEw)
### 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!

View File

@@ -0,0 +1,250 @@
---
title: 'Tutorial 2: Edit your Flowershow website locally on your computer'
date: 2023-06-22
authors: ['Ola Rubaj']
filetype: 'blog'
---
In this tutorial, we will walk you through the process of editing your Flowershow website locally on your computer.
By the end of this tutorial, you will:
- Understand what is Markdown and why you should use Obsidian to edit content on your Flowershow websites in most cases.
- Gain a deeper understanding of working with Git and GitHub Desktop.
- Learn how to clone your website's repository to your computer.
- Learn how to edit content using Obsidian and what are the benefits of it.
- Learn how to commit (save) your changes locally and push them back to the GitHub repository.
Below is a screenshot of how the final website will look like:
![[tutorial-2-result.png]]
Let's start by understanding why using GitHub UI as we did in tutorial 1 is not always a good choice.
## What is Markdown and why use Obsidian for editing it in Flowershow-based websites
While editing on the GitHub UI is convenient, there are certain limitations and drawbacks to consider. For example, you can't work offline, can't add or edit multiple files simultaneously, and the GitHub UI's preview doesn't render all the Markdown syntax elements that are supported by Flowershow-based websites.
Ok, but, what exactly is Markdown?
[Markdown](https://en.wikipedia.org/wiki/Markdown) is a simple and intuitive syntax, that allows you to structure your text documents. It's widely used for creating content on the web. As Wikipedia puts it:
> *Markdown is a lightweight [**markup language**](https://en.wikipedia.org/wiki/Markup_language) for creating [**formatted text**](https://en.wikipedia.org/wiki/Formatted_text) using a **plain-text editor**.*
The key term here is "markup language". In simple terms, it is a way to annotate or "mark up" a plain text document with symbols that describe how the document is structured and so, how it should be understood, processed, or displayed. These tags can tell a computer program that supports this syntax (e.g. a website) how to format the rendered text, for example: which words should be bold or italic, where to insert images, when to start new paragraphs, how to create tables, and so forth. It's important to note, that even though Markdown symbols change how the text is displayed when it's rendered by a Markdown-compatible viewer, the underlying document in still just a plain text. For instance, if you want to create a heading in Markdown, you use the "#" symbol before your heading text, like this:
```md
# This is a Heading
```
BUT, there is no single version of Markdown. It comes in different "flavours", with CommonMark and GitHub Flavoured Markdown (GFM) being the most popular ones. And different tools supporting Markdown may use their own specific versions of Markdown. The two tools relevant in the context of this guide are Obsidian and Flowershow.
Obsidian supports an extended version of Markdown that includes majority of elements from CommonMark and GFM, while also introducing its own unique features, like [wiki-links](https://help.obsidian.md/Linking+notes+and+files/Internal+links) or [callouts](https://help.obsidian.md/Editing+and+formatting/Callouts). And Flowershow template is Obsidian-compatible, meaning it supports (or aims to support) all of the Obsidian Markdown features.
To make things a bit clearer, there are:
- CommonMark
- GitHub Flavored Markdown (GFM) - superset of CommonMark
- "Obsidian flavoured Markdown" - majority of the GFM + some extra elements, like wiki-links or callouts
- "Flowershow flavoured Markdown" - majority of Obsidian-supported syntax
As you might have guessed by now, GitHub UI will only preview GitHub Flavoured Markdown. And while it's a majority of Markdown you would write, it won't be able to render Obsidian-only (or Flowershow-only) syntax elements, like callouts, wiki-links or inline table of contents.
Another drawback of GitHub UI, is that it doesn't allow you to make changes (or to add) multiple files at once. Changes to each file will have to be commited (saved) separately, which can introduce a bit of a mess to your GitHub history, and may be cumbersome (e.g. if you want to update a link to some page on 10 other pages...).
Ok, now we have this sorted, let's dive in and start editing your Flowershow website locally!
## Prerequisites
- A [GitHub](https://github.com/) account.
- A [Vercel](https://vercel.com/) account. You can set it up using your GitHub account.
- [Obsidian](https://obsidian.md/) installed
- [GitHub Desktop](https://desktop.github.com/) installed
- [[create-a-website-from-scratch|Tutorial 1: Create a website from scratch using Markdown and PortalJS]] completed - this one is recommended so that you have the same sandbox website we're working on in this tutorial.
## Clone the GitHub repository on your computer
### 1. Open GitHub Desktop app
### 2. Click on "Clone a Repository from the Internet..."
Or click on "File" -> "Clone repository".
![[gh-desktop-starting-screen.png]]
If this is the first time you're using GitHub Desktop, it will prompt you to log in to your GitHub account. Click "Sign In" and follow the prompts.
![[gh-desktop-clone-signin.png]]
Once you're done and you've authorised GitHub Desktop to access your repositories, go back to GitHub Desktop. You should now see a list of all your GitHub repositories.
### 3. Select the repository you want to clone
![[gh-desktop-clone.png]]
### 4. Choose where your repository should be saved
Type the path manually or click "Choose..." to find it using file explorer.
![[gh-desktop-clone-path-select.png]]
### 5. Click "Clone" and wait for the process to complete
You should now see the following screen:
![[gh-desktop-no-local-changes.png]]
Done! You've successfully cloned your website's repository on your computer! 🎉
## Edit your site in Obsidian
### 1. Open Obsidian
### 2. Open your repository's `/content` folder as vault
Click on "Open" in "Open folder as vault" section and select the path to the `/content` of the cloned repository.
![[obsidian-starting.png]]
Now you're ready to edit your site! In the left-hand side panel you should see the two files we created in [[create-a-website-from-scratch|tutorial 1]]: `index.md` and `about.md`.
![[obsidian-content-start.png]]
### 3. Edit your site's content
Now, let's add some more stuff to the home page.
Click on `index.md` to open it and replace the dummy text with "About Me" section, .e.g.:
```md
## About Me
Hey there! I'm Your Name, a passionate learner and explorer of ideas.
```
![[obsidian-content-index-edit.png]]
Now, let's say for more information about you and your site, you want to add link to the about page. You can do so, by creating a wiki-link to that page, like so:
```md
[[about]]
```
When you start typing, after writing empty double brackets `[[]]`, Obsidian will suggest all the available pages you can link to, and after you select one it will create the link automatically for you.
![[obsidian-content-index-add-link.png]]
![[obsidian-content-index-add-link-2.png]]
Now, let's say you want to show people what books you've read and share your reviews and other information on each one. And let's say information on each book should be available at `/books/abc` path on our website. To achieve this, you need to create a folder called `books` in your vault and add all the project-related files in there.
To create a new folder in Obsidian, click on the "New folder" icon, and give your folder a name:
![[obsidian-content-add-folder.png]]
![[obsidian-content-add-folder-2.png]]
Now, let's write some book reviews. You can do this by right-clicking on the `/books` folder, and selecting "New note" option. Rename the newly created `Untitled.md` file and add some review in it. Then add some other reviews.
![[obsidian-content-book.png]]
Ok, now let's make a page that will list all your books reviews - our Bookshelf! It would be nice to have it available under `/books` path on the website, since each of our books will be available under `/books/abc`. To achieve this, we have to create an index page **inside** our `/books` folder, like so:
![[obsidian-content-book-index.png]]
Then, let's list our book reviews with wiki-links to their pages:
![[obsidian-content-book-index-2.png]]
![[obsidian-content-book-index-3.png]]
Now, let's add a link to our Bookshelf page on our home page, so that it's easy to find.
![[obsidian-content-index-bookshelf-link.png]]
Now, if you want to have your link say something different than the raw `books/index`, you can do this by typing `|` after the path and specifying an alternative name, .e.g:
```md
[[books/index|Bookshelf]]
```
Let's also do this for the about page:
```md
[[about|About me]]
```
![[obsidian-content-index-link-aliases.png]]
That's better!
Now, let's maybe add a short info at the bottom, that this site is new and is currently being worked on, in form of an Obsidian callout:
```md
> [!info]
> 🚧 This site it pretty new and I'm enhancing it every day. Stay tuned!
```
![[obsidian-content-index-callout.png]]
Great, our updated site is ready to be published! 🔥
## Save and publish your changes
### 1. Navigate to GitHub Desktop app
In the "Changes" tab, you'll see all the changes that have been made to the repository.
![[github-desktop-all-changed-files.png]]
All the new files will have `[+]` sign next to them, and all the edited files will have `[•]`.
### 2. Commit your changes
Now, to save these changes we need to "commit" them, which is a fancy term for making a checkpoint of the state at which your repository is currently in.
Let's make this checkpoint! In the bottom left corner there is a "Summary (required)" field, which is the place for a commit message - a concise description of the changes you made. The "Description" field is optional, and it's only needed if you need to add some more information about your changes that doesn't fit in the commit message.
![[github-desktop-add-commit-message.png]]
Now, hit the "Commit to main" button, and done! Now GitHub Desktop should say there are no local changes again. And that's correct, as all the changes we made have successfully been saved, and no other changes have been made since then.
You should see the last commit message under the button:
![[github-desktop-commit-message.png]]
You can also inspect the whole history of past commits in the "History tab".
![[github-desktop-history.png]]
The very fist commit on top is the commit we've just made, but you can also see all the commits to the repository we made in [[create-a-website-from-scratch|tutorial 1]], via GitHub UI.
### 3. Push your changes to the remote repository
The commit we've just crated has ↑ sign next to it. It means it hasn't yet been pushed to our remote version of the repository - the changes you made has been saved, but only locally on your computer. In order to push them to the cloud copy of the repository (aka "remote"), click "↑ Push origin (1 ↑)" tab.
When the "push" is complete, the arrow next to the last commit message should disappear, and there should be no `(1 )` indicator next to "Push origin" button.
![[github-desktop-history-after-push.png]]
### 4. See updated site live!
Navigate to your [vercel dashboard](https://vercel.com/dashboard).
![[vercel-dashboard.png]]
Click on the project repository to go to its dashboard.
You may have to wait a bit until the site builds, but once it's ready, you should see the preview with our latest changes.
![[vercel-project-dashboard.png]]
Note, that under "SOURCE" section (next to the preview) there is also our last commit message, indicating that the latest deployment has been triggered by this commit.
Click on the preview to see the updated site live!
![[tutorial-2-result.png]]
![[live-book-home-page.png]]
![[live-book.png]]
Congratulations!
You have successfully completed the tutorial on editing your Flowershow website locally on your computer. By utilising Obsidian and GitHub Desktop, you have unlocked powerful editing capabilities and improved the overall publishing process. Now, you can enjoy the benefits of offline editing, simultaneous file editing, and previewing the extended Markdown syntax provided by Obsidian.

View File

@@ -0,0 +1,64 @@
---
title: 'Enhancing Geospatial Data Visualization with PortalJS'
date: 2023-07-18
authors: ['João Demenech', 'Luccas Mateus', 'Yoana Popova']
filetype: 'blog'
---
Are you keen on building rich and interactive data portals? Do you find value in the power and flexibility of JavaScript, Nextjs, and React? In that case, allow us to introduce you to [PortalJS](https://portaljs.org/), a state-of-the-art framework leveraging these technologies to help you build amazing data portals.
Perhaps you already understand that the effective data visualization lies in the adept utilization of various data components. Within [PortalJS](https://portaljs.org/), we take data visualization a step further. It's not just about displaying data - it's about telling a captivating story through the strategic orchestration of a diverse array of data components.
We are now eager to share our latest enhancement to [PortalJS](https://portaljs.org/): maps, a powerful tool for visualizing geospatial data. In this post, we will to take you on a tour of our experiments and progress in enhancing map functionalities on [PortalJS](https://portaljs.org/). Our journey into this innovative feature is still in its early stages, with new facets being unveiled and refined as we perfect our API. Still, this exciting development opens a new avenue for visualizing data, enhancing your ability to convey complex geospatial information with clarity and precision.
## Exploring Map Formats
Maps play a crucial role in geospatial data visualization. Several formats exist for storing and sharing this type of data, with GeoJSON, KML, and shapefiles being among the most popular. As a prominent figure in the field of open-source data portal platforms, [PortalJS](https://portaljs.org/) strives to support as many map formats as possible.
Taking inspiration from the ckanext-geoview extension, we currently support KML and GeoJSON formats in [PortalJS](https://portaljs.org/). This remarkable extension is a plugin for CKAN, the worlds leading open source data management system, that enables users to visualize geospatial data in diverse formats on an interactive map. Apart from KML and GeoJSON formats support, our roadmap entails extending compatibility to encompass all other formats supported by ckanext-geoview. Rest assured, we are committed to empowering users with a wide array of map format options in the future.
So, what makes these formats special?
- **GeoJSON**: This format uses JSON to depict simple geographic features and their associated attributes. It's often hailed as the most popular choice in the field.
- **KML**: An XML-based format, KML can store details like placemarks, paths, polygons, and styles.
- **Shapefiles**: These are file collections that store vector data—points, lines, and polygons—and their attributes.
## Unveiling the Power of Leaflet and OpenLayers
To display maps in [PortalJS](https://portaljs.org/), we utilize two powerful JavaScript libraries for creating interactive maps based on different layers: Leaflet and OpenLayers. Each offers distinct advantages (and disadvantages), inspiring us to integrate both and give users the flexibility to choose.
Leaflet is the leading open-source JavaScript library known for its mobile-friendly, interactive maps. With its compact size (just 42 KB of JS), it provides all the map features most developers need. Leaflet is designed with simplicity, performance and usability in mind. It works efficiently across all major desktop and mobile platforms.
OpenLayers is a high-performance library packed with features for creating interactive web maps. OpenLayers can display map tiles, vector data, and markers sourced from anywhere on any webpage. It's an excellent tool for leveraging geographic information of all kinds.
## Introducing Map Feature
Both components have some similar features and props, such as:
### Polygons and points
![Map with polygons and points](/assets/blog/2023-07-18-map-polygons-and-points.png)
Our initial version enables the use of both vectors and points to display information. Points are simpler and faster to render than vectors, but they have less detail and interactivity. For example, if you have data that is represented by coordinates or addresses, you can use points to show them on the map. This way, you can avoid time-consuming loading and rendering complex vector shapes that may slow down your map.
### Tooltips
![Map with tooltips](/assets/blog/2023-07-18-map-tooltips.png)
We have implemented an exciting feature that enhances the usability of our map component: tooltips. When you hover over a polygon or point on the map, a small pop-up window, known as a tooltip, appears. This tooltip provides relevant details about the feature under your cursor, according to what features the map creator wants to highlight. For example, when exploring countries, you can effortlessly discover their name, population, and area by hovering over them. Similarly, hovering over cities reveals useful information like their name, temperature, and elevation. To enable this handy tooltip functionality on our map, simply include a tooltip prop when using the map component.
### Focus
![Map with polygons over a region](/assets/blog/2023-07-18-map-polygons-on-region.png)
Users can also choose a region of focus, which will depend on the data, by setting initial center coordinates and zoom level. This is especially helpful for maps that are not global, such as the ones that use data from a specific country, city or region.
## Mapping the Future with PortalJS
Through our ongoing enhancements to the [PortalJS library](https://storybook.portaljs.org/), we aim to empower users to create engaging and informative data portals featuring diverse map formats and data components.
Why not give [PortalJS](https://portaljs.org/) a try today and discover the possibilities for your own data portals? To get started, check out our comprehensive documentation here: [PortalJS Documentation](https://portaljs.org/docs).
Have questions or comments about using [PortalJS](https://portaljs.org/) for your data portals? Feel free to share your thoughts on our [Discord channel](https://discord.com/invite/EeyfGrGu4U). We're here to help you make the most of your data.
Stay tuned for more exciting developments as we continue to enhance [PortalJS](https://portaljs.org/)!

View File

@@ -1,5 +1,5 @@
---
title: "PortalJS example 1: Create a full-featured custom data portal frontend for CKAN with PortalJS"
title: "Setup Guide: How to create a full-featured custom data portal frontend for CKAN with PortalJS"
authors: ['Luccas Mateus']
date: 2021-04-20
---

Some files were not shown because too many files have changed in this diff Show More