Compare commits
33 Commits
@portaljs/
...
@portaljs/
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
af11f0cfd5 | ||
|
|
9ae2b31113 | ||
|
|
2bffd130c8 | ||
|
|
058d23678a | ||
|
|
540a08934c | ||
|
|
7d010cfee4 | ||
|
|
dd79da1c6b | ||
|
|
a58e2b81f7 | ||
|
|
6d7acd27ed | ||
|
|
7c30842c7d | ||
|
|
35ca1d6dfd | ||
|
|
a7e90b64af | ||
|
|
26dcffc279 | ||
|
|
d18e3dd486 | ||
|
|
8d7059acb4 | ||
|
|
09d5324d4e | ||
|
|
cf24042a91 | ||
|
|
2c45da679b | ||
|
|
0a476101e7 | ||
|
|
1343a7a6f7 | ||
|
|
27c99adde8 | ||
|
|
96904aef0d | ||
|
|
92a549d6a9 | ||
|
|
1a5bbd4346 | ||
|
|
4985576183 | ||
|
|
7049917ef7 | ||
|
|
dd03a493be | ||
|
|
e5b0a85e48 | ||
|
|
a93b13f448 | ||
|
|
8a4ec39d25 | ||
|
|
38bf06f031 | ||
|
|
8560f165fd | ||
|
|
b13e3ade3c |
2
package-lock.json
generated
2
package-lock.json
generated
@@ -48297,7 +48297,7 @@
|
|||||||
},
|
},
|
||||||
"packages/components": {
|
"packages/components": {
|
||||||
"name": "@portaljs/components",
|
"name": "@portaljs/components",
|
||||||
"version": "0.4.0",
|
"version": "0.5.3",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@githubocto/flat-ui": "^0.14.1",
|
"@githubocto/flat-ui": "^0.14.1",
|
||||||
"@heroicons/react": "^2.0.17",
|
"@heroicons/react": "^2.0.17",
|
||||||
|
|||||||
@@ -1,5 +1,47 @@
|
|||||||
# @portaljs/components
|
# @portaljs/components
|
||||||
|
|
||||||
|
## 0.5.8
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- [#1079](https://github.com/datopian/portaljs/pull/1079) [`058d2367`](https://github.com/datopian/portaljs/commit/058d23678a024890f8a6d909ded9fc8fc11cf145) Thanks [@Gutts-n](https://github.com/Gutts-n)! - Changed the download behaviour of the bucket viewer component and removed loading component while downloading
|
||||||
|
|
||||||
|
## 0.5.7
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- [#1077](https://github.com/datopian/portaljs/pull/1077) [`6d7acd27`](https://github.com/datopian/portaljs/commit/6d7acd27ed9299cbcc14eab906f2f0eb414656b8) Thanks [@Gutts-n](https://github.com/Gutts-n)! - Created property to present a component while is loading the download of the file and fixed download bug on pagination
|
||||||
|
|
||||||
|
## 0.5.6
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- [#1075](https://github.com/datopian/portaljs/pull/1075) [`26dcffc2`](https://github.com/datopian/portaljs/commit/26dcffc279057f80a579134e862085ba042c06c3) Thanks [@Gutts-n](https://github.com/Gutts-n)! - Fixed problem presenting the download component in the first load of the bucket viewer
|
||||||
|
|
||||||
|
## 0.5.5
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- [#1073](https://github.com/datopian/portaljs/pull/1073) [`cf24042a`](https://github.com/datopian/portaljs/commit/cf24042a910567e98eeb75ade42ce0149bdb62d1) Thanks [@Gutts-n](https://github.com/Gutts-n)! - Fixed filter by startDate error
|
||||||
|
|
||||||
|
## 0.5.4
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- [#1071](https://github.com/datopian/portaljs/pull/1071) [`27c99add`](https://github.com/datopian/portaljs/commit/27c99adde8fa36ad2c2e03f227f93aa62454eefa) Thanks [@Gutts-n](https://github.com/Gutts-n)! - Added pagination and filter properties for the BucketViewer component
|
||||||
|
|
||||||
|
## 0.5.3
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- [#1066](https://github.com/datopian/portaljs/pull/1066) [`dd03a493`](https://github.com/datopian/portaljs/commit/dd03a493beca5459d1ef447b2df505609fc64e95) Thanks [@Gutts-n](https://github.com/Gutts-n)! - Created Iframe component
|
||||||
|
|
||||||
|
## 0.5.2
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- [#1063](https://github.com/datopian/portaljs/pull/1063) [`b13e3ade`](https://github.com/datopian/portaljs/commit/b13e3ade3ccefe7dffe84f824bdedd3e512ce499) Thanks [@Gutts-n](https://github.com/Gutts-n)! - Created auto zoom configuration for the map component
|
||||||
|
|
||||||
## 0.5.1
|
## 0.5.1
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@portaljs/components",
|
"name": "@portaljs/components",
|
||||||
"version": "0.5.1",
|
"version": "0.5.8",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "https://portaljs.org",
|
"description": "https://portaljs.org",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
|
|||||||
@@ -1,19 +1,36 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { CSSProperties, ReactNode, useEffect, useState } from 'react';
|
||||||
import LoadingSpinner from './LoadingSpinner';
|
import LoadingSpinner from './LoadingSpinner';
|
||||||
|
|
||||||
|
export interface BucketViewerFilterSearchedDataEvent {
|
||||||
|
startDate?: Date;
|
||||||
|
endDate?: Date;
|
||||||
|
}
|
||||||
|
|
||||||
export interface BucketViewerProps {
|
export interface BucketViewerProps {
|
||||||
|
onLoadTotalNumberOfItems?: (total: number) => void;
|
||||||
domain: string;
|
domain: string;
|
||||||
|
downloadConfig?: {
|
||||||
|
hoverOfTheFileComponent?: ReactNode;
|
||||||
|
};
|
||||||
suffix?: string;
|
suffix?: string;
|
||||||
className?: string;
|
className?: string;
|
||||||
|
paginationConfig?: BucketViewerPaginationConfig;
|
||||||
|
filterState?: BucketViewerFilterSearchedDataEvent;
|
||||||
dataMapperFn: (rawData: Response) => Promise<BucketViewerData[]>;
|
dataMapperFn: (rawData: Response) => Promise<BucketViewerData[]>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface BucketViewerPaginationConfig {
|
||||||
|
containerClassName?: string;
|
||||||
|
containerStyles?: CSSProperties;
|
||||||
|
itemsPerPage: number;
|
||||||
|
}
|
||||||
|
|
||||||
export interface BucketViewerData {
|
export interface BucketViewerData {
|
||||||
fileName: string;
|
fileName: string;
|
||||||
downloadFileUri: string;
|
downloadFileUri: string;
|
||||||
dateProps?: {
|
dateProps?: {
|
||||||
date: Date;
|
date: Date;
|
||||||
dateFormatter: (date: Date) => string;
|
dateFormatter?: (date: Date) => string;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -22,60 +39,184 @@ export function BucketViewer({
|
|||||||
suffix,
|
suffix,
|
||||||
dataMapperFn,
|
dataMapperFn,
|
||||||
className,
|
className,
|
||||||
|
filterState,
|
||||||
|
paginationConfig,
|
||||||
|
downloadConfig,
|
||||||
|
onLoadTotalNumberOfItems,
|
||||||
}: BucketViewerProps) {
|
}: BucketViewerProps) {
|
||||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
|
||||||
const [bucketFiles, setBucketFiles] = useState<BucketViewerData[]>([]);
|
|
||||||
suffix = suffix ?? '/';
|
suffix = suffix ?? '/';
|
||||||
|
|
||||||
|
const { hoverOfTheFileComponent } = downloadConfig ?? {};
|
||||||
|
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||||
|
const [showDownloadComponentOnLine, setShowDownloadComponentOnLine] =
|
||||||
|
useState(-1);
|
||||||
|
const [currentPage, setCurrentPage] = useState<number>(0);
|
||||||
|
const [lastPage, setLastPage] = useState<number>(0);
|
||||||
|
const [bucketFiles, setBucketFiles] = useState<BucketViewerData[]>([]);
|
||||||
|
const [paginatedData, setPaginatedData] = useState<BucketViewerData[]>([]);
|
||||||
|
const [filteredData, setFilteredData] = useState<BucketViewerData[]>([]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
fetch(`${domain}${suffix}`)
|
fetch(`${domain}${suffix}`)
|
||||||
.then((res) => dataMapperFn(res))
|
.then((res) => dataMapperFn(res))
|
||||||
.then((data) => setBucketFiles(data))
|
.then((data) => {
|
||||||
|
setBucketFiles(data);
|
||||||
|
setFilteredData(data);
|
||||||
|
})
|
||||||
.finally(() => setIsLoading(false));
|
.finally(() => setIsLoading(false));
|
||||||
}, [domain, suffix]);
|
}, [domain, suffix]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (paginationConfig) {
|
||||||
|
const startIndex = paginationConfig
|
||||||
|
? currentPage * paginationConfig.itemsPerPage
|
||||||
|
: 0;
|
||||||
|
|
||||||
|
const endIndex = paginationConfig
|
||||||
|
? startIndex + paginationConfig.itemsPerPage
|
||||||
|
: 0;
|
||||||
|
|
||||||
|
setLastPage(
|
||||||
|
Math.ceil(filteredData.length / paginationConfig.itemsPerPage) - 1
|
||||||
|
);
|
||||||
|
setPaginatedData(filteredData.slice(startIndex, endIndex));
|
||||||
|
}
|
||||||
|
}, [currentPage, filteredData]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (onLoadTotalNumberOfItems) onLoadTotalNumberOfItems(filteredData.length);
|
||||||
|
}, [filteredData]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!filterState) return;
|
||||||
|
|
||||||
|
if (filterState.startDate && filterState.endDate) {
|
||||||
|
setFilteredData(
|
||||||
|
bucketFiles.filter(({ dateProps }) =>
|
||||||
|
dateProps
|
||||||
|
? dateProps.date.getTime() >= filterState.startDate.getTime() &&
|
||||||
|
dateProps.date.getTime() <= filterState.endDate.getTime()
|
||||||
|
: true
|
||||||
|
)
|
||||||
|
);
|
||||||
|
} else if (filterState.startDate) {
|
||||||
|
setFilteredData(
|
||||||
|
bucketFiles.filter(({ dateProps }) =>
|
||||||
|
dateProps
|
||||||
|
? dateProps.date.getTime() >= filterState.startDate.getTime()
|
||||||
|
: true
|
||||||
|
)
|
||||||
|
);
|
||||||
|
} else if (filterState.endDate) {
|
||||||
|
setFilteredData(
|
||||||
|
bucketFiles.filter(({ dateProps }) =>
|
||||||
|
dateProps
|
||||||
|
? dateProps.date.getTime() <= filterState.endDate.getTime()
|
||||||
|
: true
|
||||||
|
)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
setFilteredData(bucketFiles);
|
||||||
|
}
|
||||||
|
}, [filterState]);
|
||||||
|
|
||||||
return isLoading ? (
|
return isLoading ? (
|
||||||
<div className="w-full flex items-center justify-center h-[300px]">
|
<div className="w-full flex items-center justify-center h-[300px]">
|
||||||
<LoadingSpinner />
|
<LoadingSpinner />
|
||||||
</div>
|
</div>
|
||||||
) : bucketFiles ? (
|
) : bucketFiles ? (
|
||||||
<>
|
<>
|
||||||
{...bucketFiles?.map((data, i) => (
|
{...(paginationConfig && bucketFiles ? paginatedData : filteredData)?.map(
|
||||||
|
(data, i) => (
|
||||||
|
<ul
|
||||||
|
onClick={() => {
|
||||||
|
const a: HTMLAnchorElement = document.createElement('a');
|
||||||
|
a.href = data.downloadFileUri;
|
||||||
|
a.target = `_blank`;
|
||||||
|
a.download = data.fileName;
|
||||||
|
document.body.appendChild(a);
|
||||||
|
a.click();
|
||||||
|
document.removeChild(a);
|
||||||
|
}}
|
||||||
|
key={i}
|
||||||
|
onMouseEnter={() => setShowDownloadComponentOnLine(i)}
|
||||||
|
onMouseLeave={() => setShowDownloadComponentOnLine(undefined)}
|
||||||
|
className={`${
|
||||||
|
className ??
|
||||||
|
'mb-2 border-b-[2px] border-b-[red] hover:cursor-pointer'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{hoverOfTheFileComponent && showDownloadComponentOnLine === i ? (
|
||||||
|
hoverOfTheFileComponent
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
|
)}
|
||||||
|
<div className="flex justify-between w-full items-center">
|
||||||
|
<div>
|
||||||
|
<li>{data.fileName}</li>
|
||||||
|
{data.dateProps && data.dateProps.dateFormatter ? (
|
||||||
|
<li>{data.dateProps.dateFormatter(data.dateProps.date)}</li>
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ul>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
{paginationConfig ? (
|
||||||
<ul
|
<ul
|
||||||
onClick={() => {
|
className={
|
||||||
const anchorId = `download_anchor_${i}`;
|
paginationConfig.containerClassName
|
||||||
const a: HTMLAnchorElement =
|
? paginationConfig.containerClassName
|
||||||
(document.getElementById(anchorId) as HTMLAnchorElement | null) ??
|
: 'flex justify-end gap-x-[0.5rem] w-full'
|
||||||
document.createElement('a');
|
}
|
||||||
a.id = anchorId;
|
style={paginationConfig.containerStyles ?? {}}
|
||||||
if (a.download) a.click();
|
|
||||||
else {
|
|
||||||
setIsLoading(true);
|
|
||||||
fetch(data.downloadFileUri)
|
|
||||||
.then((res) => res.blob())
|
|
||||||
.then((res) => {
|
|
||||||
a.href = URL.createObjectURL(res);
|
|
||||||
a.download = res.name ?? data.fileName;
|
|
||||||
document.body.appendChild(a);
|
|
||||||
a.click();
|
|
||||||
})
|
|
||||||
.finally(() => setIsLoading(false));
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
key={i}
|
|
||||||
className={`${
|
|
||||||
className ??
|
|
||||||
'mb-2 border-b-[2px] border-b-[red] hover:cursor-pointer'
|
|
||||||
}`}
|
|
||||||
>
|
>
|
||||||
<li>{data.fileName}</li>
|
<li>
|
||||||
{data.dateProps ? (
|
<button
|
||||||
<li>{data.dateProps.dateFormatter(data.dateProps.date)}</li>
|
className="hover:cursor-pointer hover:disabled:cursor-not-allowed"
|
||||||
) : (
|
disabled={currentPage === 0}
|
||||||
<></>
|
onClick={() => setCurrentPage(0)}
|
||||||
)}
|
>
|
||||||
|
First
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<button
|
||||||
|
className="hover:cursor-pointer hover:disabled:cursor-not-allowed"
|
||||||
|
onClick={() => setCurrentPage(currentPage - 1)}
|
||||||
|
disabled={currentPage === 0}
|
||||||
|
>
|
||||||
|
Previous
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<label>{currentPage + 1}</label>
|
||||||
|
|
||||||
|
<li>
|
||||||
|
<button
|
||||||
|
onClick={() => setCurrentPage(currentPage + 1)}
|
||||||
|
disabled={currentPage >= lastPage}
|
||||||
|
className="hover:cursor-pointer hover:disabled:cursor-not-allowed"
|
||||||
|
>
|
||||||
|
Next
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li>
|
||||||
|
<button
|
||||||
|
onClick={() => setCurrentPage(lastPage)}
|
||||||
|
disabled={currentPage >= lastPage}
|
||||||
|
className="hover:cursor-pointer hover:disabled:cursor-not-allowed"
|
||||||
|
>
|
||||||
|
Last
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
))}
|
) : (
|
||||||
|
<></>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
) : null;
|
) : null;
|
||||||
}
|
}
|
||||||
|
|||||||
14
packages/components/src/components/Iframe.tsx
Normal file
14
packages/components/src/components/Iframe.tsx
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { CSSProperties } from "react";
|
||||||
|
|
||||||
|
export interface IframeProps {
|
||||||
|
url: string;
|
||||||
|
style?: CSSProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Iframe({
|
||||||
|
url, style
|
||||||
|
}: IframeProps) {
|
||||||
|
return (
|
||||||
|
<iframe src={url} style={style ?? { width: `100%`, height: `100%` }}></iframe>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { CSSProperties, useEffect, useState } from 'react';
|
||||||
import LoadingSpinner from './LoadingSpinner';
|
import LoadingSpinner from './LoadingSpinner';
|
||||||
import loadData from '../lib/loadData';
|
import loadData from '../lib/loadData';
|
||||||
import chroma from 'chroma-js';
|
import chroma from 'chroma-js';
|
||||||
@@ -30,7 +30,10 @@ export type MapProps = {
|
|||||||
title?: string;
|
title?: string;
|
||||||
center?: { latitude: number | undefined; longitude: number | undefined };
|
center?: { latitude: number | undefined; longitude: number | undefined };
|
||||||
zoom?: number;
|
zoom?: number;
|
||||||
style?: Object;
|
style?: CSSProperties;
|
||||||
|
autoZoomConfiguration?: {
|
||||||
|
layerName: string
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export function Map({
|
export function Map({
|
||||||
@@ -45,7 +48,8 @@ export function Map({
|
|||||||
center = { latitude: 45, longitude: 45 },
|
center = { latitude: 45, longitude: 45 },
|
||||||
zoom = 2,
|
zoom = 2,
|
||||||
title = '',
|
title = '',
|
||||||
style = {}
|
style = {},
|
||||||
|
autoZoomConfiguration = undefined,
|
||||||
}: MapProps) {
|
}: MapProps) {
|
||||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||||
const [layersData, setLayersData] = useState<any>([]);
|
const [layersData, setLayersData] = useState<any>([]);
|
||||||
@@ -118,6 +122,24 @@ export function Map({
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (title) info.addTo(map.target);
|
if (title) info.addTo(map.target);
|
||||||
|
if(!autoZoomConfiguration) return;
|
||||||
|
|
||||||
|
let layerToZoomBounds = L.latLngBounds(L.latLng(0, 0), L.latLng(0, 0));
|
||||||
|
|
||||||
|
layers.forEach((layer) => {
|
||||||
|
if(layer.name === autoZoomConfiguration.layerName) {
|
||||||
|
const data = layersData.find(
|
||||||
|
(layerData) => layerData.name === layer.name
|
||||||
|
)?.data;
|
||||||
|
|
||||||
|
if (data) {
|
||||||
|
layerToZoomBounds = L.geoJSON(data).getBounds();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
map.target.fitBounds(layerToZoomBounds);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<TileLayer
|
<TileLayer
|
||||||
|
|||||||
@@ -9,3 +9,4 @@ export * from './components/Map';
|
|||||||
export * from './components/PdfViewer';
|
export * from './components/PdfViewer';
|
||||||
export * from "./components/Excel";
|
export * from "./components/Excel";
|
||||||
export * from "./components/BucketViewer";
|
export * from "./components/BucketViewer";
|
||||||
|
export * from "./components/Iframe";
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
import { raw, type Meta, type StoryObj } from '@storybook/react';
|
import { type Meta, type StoryObj } from '@storybook/react';
|
||||||
|
|
||||||
import { BucketViewer, BucketViewerData, BucketViewerProps } from '../src/components/BucketViewer';
|
import {
|
||||||
|
BucketViewer,
|
||||||
|
BucketViewerProps,
|
||||||
|
} from '../src/components/BucketViewer';
|
||||||
|
import LoadingSpinner from '../src/components/LoadingSpinner';
|
||||||
|
|
||||||
// More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction
|
// More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction
|
||||||
const meta: Meta = {
|
const meta: Meta = {
|
||||||
@@ -9,12 +13,19 @@ const meta: Meta = {
|
|||||||
tags: ['autodocs'],
|
tags: ['autodocs'],
|
||||||
argTypes: {
|
argTypes: {
|
||||||
domain: {
|
domain: {
|
||||||
description:
|
description: 'Bucket domain URI',
|
||||||
'Bucket domain URI',
|
|
||||||
},
|
},
|
||||||
suffix: {
|
suffix: {
|
||||||
description:
|
description: 'Suffix of bucket domain',
|
||||||
'Suffix of bucket domain',
|
},
|
||||||
|
downloadConfig: {
|
||||||
|
description: `Bucket file download configuration`,
|
||||||
|
},
|
||||||
|
filterState: {
|
||||||
|
description: `State with values used to filter the bucket files`,
|
||||||
|
},
|
||||||
|
paginationConfig: {
|
||||||
|
description: `Configuration to show and stylise the pagination on the component`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -31,16 +42,56 @@ export const Normal: Story = {
|
|||||||
suffix: '/',
|
suffix: '/',
|
||||||
dataMapperFn: async (rawData: Response) => {
|
dataMapperFn: async (rawData: Response) => {
|
||||||
const result = await rawData.json();
|
const result = await rawData.json();
|
||||||
return result.objects.map(
|
return result.objects.map((e) => ({
|
||||||
e => ({
|
downloadFileUri: e.downloadLink,
|
||||||
downloadFileUri: e.downloadLink,
|
fileName: e.key.replace(/^(\w+\/)/g, ''),
|
||||||
fileName: e.key.replace(/^(\w+\/)/g, '') ,
|
dateProps: {
|
||||||
dateProps: {
|
date: new Date(e.uploaded),
|
||||||
date: new Date(e.uploaded),
|
dateFormatter: (date) => date.toLocaleDateString(),
|
||||||
dateFormatter: (date) => date.toLocaleDateString()
|
},
|
||||||
}
|
}));
|
||||||
})
|
},
|
||||||
)
|
},
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export const WithPagination: Story = {
|
||||||
|
name: 'With pagination',
|
||||||
|
args: {
|
||||||
|
domain: 'https://ssen-smart-meter.datopian.workers.dev',
|
||||||
|
suffix: '/',
|
||||||
|
paginationConfig: {
|
||||||
|
itemsPerPage: 3,
|
||||||
|
},
|
||||||
|
dataMapperFn: async (rawData: Response) => {
|
||||||
|
const result = await rawData.json();
|
||||||
|
return result.objects.map((e) => ({
|
||||||
|
downloadFileUri: e.downloadLink,
|
||||||
|
fileName: e.key.replace(/^(\w+\/)/g, ''),
|
||||||
|
dateProps: {
|
||||||
|
date: new Date(e.uploaded),
|
||||||
|
dateFormatter: (date) => date.toLocaleDateString(),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const WithComponentOnHoverOfEachBucketFile: Story = {
|
||||||
|
name: 'With component on hover of each bucket file',
|
||||||
|
args: {
|
||||||
|
domain: 'https://ssen-smart-meter.datopian.workers.dev',
|
||||||
|
suffix: '/',
|
||||||
|
downloadConfig: { hoverOfTheFileComponent: `HOVER COMPONENT` },
|
||||||
|
dataMapperFn: async (rawData: Response) => {
|
||||||
|
const result = await rawData.json();
|
||||||
|
return result.objects.map((e) => ({
|
||||||
|
downloadFileUri: e.downloadLink,
|
||||||
|
fileName: e.key.replace(/^(\w+\/)/g, ''),
|
||||||
|
dateProps: {
|
||||||
|
date: new Date(e.uploaded),
|
||||||
|
dateFormatter: (date) => date.toLocaleDateString(),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
31
packages/components/stories/Iframe.stories.ts
Normal file
31
packages/components/stories/Iframe.stories.ts
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import { type Meta, type StoryObj } from '@storybook/react';
|
||||||
|
|
||||||
|
import { Iframe, IframeProps } from '../src/components/Iframe';
|
||||||
|
|
||||||
|
const meta: Meta = {
|
||||||
|
title: 'Components/Iframe',
|
||||||
|
component: Iframe,
|
||||||
|
tags: ['autodocs'],
|
||||||
|
argTypes: {
|
||||||
|
url: {
|
||||||
|
description:
|
||||||
|
'Page to display inside of the component',
|
||||||
|
},
|
||||||
|
style: {
|
||||||
|
description:
|
||||||
|
'Style of the component',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
|
||||||
|
type Story = StoryObj<IframeProps>;
|
||||||
|
|
||||||
|
export const Normal: Story = {
|
||||||
|
name: 'Iframe',
|
||||||
|
args: {
|
||||||
|
url: 'https://app.powerbi.com/view?r=eyJrIjoiYzBmN2Q2MzYtYzE3MS00ODkxLWE5OWMtZTQ2MjBlMDljMDk4IiwidCI6Ijk1M2IwZjgzLTFjZTYtNDVjMy04MmM5LTFkODQ3ZTM3MjMzOSIsImMiOjh9',
|
||||||
|
style: {width: `100%`, height: `100%`}
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -23,6 +23,9 @@ const meta: Meta = {
|
|||||||
},
|
},
|
||||||
style: {
|
style: {
|
||||||
description: "Styles for the container"
|
description: "Styles for the container"
|
||||||
|
},
|
||||||
|
autoZoomConfiguration: {
|
||||||
|
description: "Configuration to auto zoom in the specified layer data"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -91,4 +94,32 @@ export const GeoJSONMultipleLayers: Story = {
|
|||||||
center: { latitude: 45, longitude: 0 },
|
center: { latitude: 45, longitude: 0 },
|
||||||
zoom: 2,
|
zoom: 2,
|
||||||
},
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export const GeoJSONMultipleLayersWithAutoZoomInSpecifiedLayer: Story = {
|
||||||
|
name: 'GeoJSON polygons and points map with auto zoom in the points layer',
|
||||||
|
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,
|
||||||
|
autoZoomConfiguration: {
|
||||||
|
layerName: 'Points'
|
||||||
|
}
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@portaljs/core",
|
"name": "@portaljs/core",
|
||||||
"version": "1.0.8",
|
"version": "1.0.9",
|
||||||
"description": "Core Portal.JS components, configs and utils.",
|
"description": "Core Portal.JS components, configs and utils.",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|||||||
36
packages/core/src/ui/analytics/GoogleAnalytics.tsx
Normal file
36
packages/core/src/ui/analytics/GoogleAnalytics.tsx
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import Script from 'next/script.js'
|
||||||
|
|
||||||
|
export interface GoogleAnalyticsProps {
|
||||||
|
googleAnalyticsId: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const GA = ({ googleAnalyticsId }: GoogleAnalyticsProps) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Script
|
||||||
|
strategy="afterInteractive"
|
||||||
|
src={`https://www.googletagmanager.com/gtag/js?id=${googleAnalyticsId}`}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Script strategy="afterInteractive" id="ga-script">
|
||||||
|
{`
|
||||||
|
window.dataLayer = window.dataLayer || [];
|
||||||
|
function gtag(){dataLayer.push(arguments);}
|
||||||
|
gtag('js', new Date());
|
||||||
|
gtag('config', '${googleAnalyticsId}');
|
||||||
|
`}
|
||||||
|
</Script>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://developers.google.com/analytics/devguides/collection/gtagjs/events
|
||||||
|
export const logEvent = (action, category, label, value) => {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
window.gtag?.('event', action, {
|
||||||
|
event_category: category,
|
||||||
|
event_label: label,
|
||||||
|
value: value,
|
||||||
|
})
|
||||||
|
}
|
||||||
41
packages/core/src/ui/analytics/Plausible.tsx
Normal file
41
packages/core/src/ui/analytics/Plausible.tsx
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import Script from 'next/script.js'
|
||||||
|
|
||||||
|
export interface PlausibleProps {
|
||||||
|
plausibleDataDomain: string
|
||||||
|
dataApi?: string
|
||||||
|
src?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Plausible analytics component.
|
||||||
|
* To proxy the requests through your own domain, you can use the dataApi and src attribute.
|
||||||
|
* See [Plausible docs](https://plausible.io/docs/proxy/guides/nextjs#step-2-adjust-your-deployed-script)
|
||||||
|
* for more information.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export const Plausible = ({
|
||||||
|
plausibleDataDomain,
|
||||||
|
dataApi = undefined,
|
||||||
|
src = 'https://plausible.io/js/plausible.js',
|
||||||
|
}: PlausibleProps) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Script
|
||||||
|
strategy="lazyOnload"
|
||||||
|
data-domain={plausibleDataDomain}
|
||||||
|
data-api={dataApi}
|
||||||
|
src={src}
|
||||||
|
/>
|
||||||
|
<Script strategy="lazyOnload" id="plausible-script">
|
||||||
|
{`
|
||||||
|
window.plausible = window.plausible || function() { (window.plausible.q = window.plausible.q || []).push(arguments) }
|
||||||
|
`}
|
||||||
|
</Script>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://plausible.io/docs/custom-event-goals
|
||||||
|
export const logEvent = (eventName, ...rest) => {
|
||||||
|
return window.plausible?.(eventName, ...rest)
|
||||||
|
}
|
||||||
25
packages/core/src/ui/analytics/Posthog.tsx
Normal file
25
packages/core/src/ui/analytics/Posthog.tsx
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import Script from 'next/script.js'
|
||||||
|
|
||||||
|
export interface PosthogProps {
|
||||||
|
posthogProjectApiKey: string
|
||||||
|
apiHost?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Posthog analytics component.
|
||||||
|
* See [Posthog docs](https://posthog.com/docs/libraries/js#option-1-add-javascript-snippet-to-your-html-badgerecommendedbadge) for more information.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export const Posthog = ({
|
||||||
|
posthogProjectApiKey,
|
||||||
|
apiHost = 'https://app.posthog.com',
|
||||||
|
}: PosthogProps) => {
|
||||||
|
return (
|
||||||
|
<Script strategy="lazyOnload" id="posthog-script">
|
||||||
|
{`
|
||||||
|
!function(t,e){var o,n,p,r;e.__SV||(window.posthog=e,e._i=[],e.init=function(i,s,a){function g(t,e){var o=e.split(".");2==o.length&&(t=t[o[0]],e=o[1]),t[e]=function(){t.push([e].concat(Array.prototype.slice.call(arguments,0)))}}(p=t.createElement("script")).type="text/javascript",p.async=!0,p.src=s.api_host+"/static/array.js",(r=t.getElementsByTagName("script")[0]).parentNode.insertBefore(p,r);var u=e;for(void 0!==a?u=e[a]=[]:a="posthog",u.people=u.people||[],u.toString=function(t){var e="posthog";return"posthog"!==a&&(e+="."+a),t||(e+=" (stub)"),e},u.people.toString=function(){return u.toString(1)+".people (stub)"},o="capture identify alias people.set people.set_once set_config register register_once unregister opt_out_capturing has_opted_out_capturing opt_in_capturing reset isFeatureEnabled onFeatureFlags".split(" "),n=0;n<o.length;n++)g(u,o[n]);e._i.push([i,s,a])},e.__SV=1)}(document,window.posthog||[]);
|
||||||
|
posthog.init('${posthogProjectApiKey}',{api_host:'${apiHost}'})
|
||||||
|
`}
|
||||||
|
</Script>
|
||||||
|
)
|
||||||
|
}
|
||||||
29
packages/core/src/ui/analytics/SimpleAnalytics.tsx
Normal file
29
packages/core/src/ui/analytics/SimpleAnalytics.tsx
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import Script from 'next/script.js'
|
||||||
|
|
||||||
|
export interface SimpleAnalyticsProps {
|
||||||
|
src?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SimpleAnalytics = ({
|
||||||
|
src = 'https://scripts.simpleanalyticscdn.com/latest.js',
|
||||||
|
}: SimpleAnalyticsProps) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Script strategy="lazyOnload" id="sa-script">
|
||||||
|
{`
|
||||||
|
window.sa_event=window.sa_event||function(){var a=[].slice.call(arguments);window.sa_event.q?window.sa_event.q.push(a):window.sa_event.q=[a]};
|
||||||
|
`}
|
||||||
|
</Script>
|
||||||
|
<Script strategy="lazyOnload" src={src} />
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://docs.simpleanalytics.com/events
|
||||||
|
export const logEvent = (eventName, callback) => {
|
||||||
|
if (callback) {
|
||||||
|
return window.sa_event?.(eventName, callback)
|
||||||
|
} else {
|
||||||
|
return window.sa_event?.(eventName)
|
||||||
|
}
|
||||||
|
}
|
||||||
20
packages/core/src/ui/analytics/Umami.tsx
Normal file
20
packages/core/src/ui/analytics/Umami.tsx
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import Script from 'next/script.js'
|
||||||
|
|
||||||
|
export interface UmamiProps {
|
||||||
|
umamiWebsiteId: string
|
||||||
|
src?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Umami = ({
|
||||||
|
umamiWebsiteId,
|
||||||
|
src = 'https://analytics.umami.is/script.js',
|
||||||
|
}: UmamiProps) => {
|
||||||
|
return (
|
||||||
|
<Script
|
||||||
|
async
|
||||||
|
defer
|
||||||
|
data-website-id={umamiWebsiteId}
|
||||||
|
src={src} // Replace with your umami instance
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
82
packages/core/src/ui/analytics/index.tsx
Normal file
82
packages/core/src/ui/analytics/index.tsx
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
import { GA, GoogleAnalyticsProps } from "./GoogleAnalytics";
|
||||||
|
import { Plausible, PlausibleProps } from "./Plausible";
|
||||||
|
import { SimpleAnalytics, SimpleAnalyticsProps } from "./SimpleAnalytics";
|
||||||
|
import { Umami, UmamiProps } from "./Umami";
|
||||||
|
import { Posthog, PosthogProps } from "./Posthog";
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
gtag?: (...args: any[]) => void;
|
||||||
|
plausible?: (...args: any[]) => void;
|
||||||
|
sa_event?: (...args: any[]) => void;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AnalyticsConfig {
|
||||||
|
googleAnalytics?: GoogleAnalyticsProps;
|
||||||
|
plausibleAnalytics?: PlausibleProps;
|
||||||
|
umamiAnalytics?: UmamiProps;
|
||||||
|
posthogAnalytics?: PosthogProps;
|
||||||
|
simpleAnalytics?: SimpleAnalyticsProps;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @example
|
||||||
|
* const analytics: AnalyticsConfig = {
|
||||||
|
* plausibleDataDomain: '', // e.g. tailwind-nextjs-starter-blog.vercel.app
|
||||||
|
* simpleAnalytics: false, // true or false
|
||||||
|
* umamiWebsiteId: '', // e.g. 123e4567-e89b-12d3-a456-426614174000
|
||||||
|
* posthogProjectApiKey: '', // e.g. AhnJK8392ndPOav87as450xd
|
||||||
|
* googleAnalyticsId: '', // e.g. UA-000000-2 or G-XXXXXXX
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
export interface AnalyticsProps {
|
||||||
|
analyticsConfig: AnalyticsConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isProduction = true || process.env["NODE_ENV"] === "production";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Supports Plausible, Simple Analytics, Umami, Posthog or Google Analytics.
|
||||||
|
* All components default to the hosted service, but can be configured to use a self-hosted
|
||||||
|
* or proxied version of the script by providing the `src` / `apiHost` props.
|
||||||
|
*
|
||||||
|
* Note: If you want to use an analytics provider you have to add it to the
|
||||||
|
* content security policy in the `next.config.js` file.
|
||||||
|
* @param {AnalyticsProps} { analytics }
|
||||||
|
* @return {*}
|
||||||
|
*/
|
||||||
|
export const Analytics = ({ analyticsConfig }: AnalyticsProps) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{isProduction && analyticsConfig.plausibleAnalytics && (
|
||||||
|
<Plausible {...analyticsConfig.plausibleAnalytics} />
|
||||||
|
)}
|
||||||
|
{isProduction && analyticsConfig.simpleAnalytics && (
|
||||||
|
<SimpleAnalytics {...analyticsConfig.simpleAnalytics} />
|
||||||
|
)}
|
||||||
|
{isProduction && analyticsConfig.posthogAnalytics && (
|
||||||
|
<Posthog {...analyticsConfig.posthogAnalytics} />
|
||||||
|
)}
|
||||||
|
{isProduction && analyticsConfig.umamiAnalytics && (
|
||||||
|
<Umami {...analyticsConfig.umamiAnalytics} />
|
||||||
|
)}
|
||||||
|
{isProduction && analyticsConfig.googleAnalytics && (
|
||||||
|
<GA {...analyticsConfig.googleAnalytics} />
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export { GA, Plausible, SimpleAnalytics, Umami, Posthog };
|
||||||
|
|
||||||
|
export type {
|
||||||
|
GoogleAnalyticsProps,
|
||||||
|
PlausibleProps,
|
||||||
|
UmamiProps,
|
||||||
|
PosthogProps,
|
||||||
|
SimpleAnalyticsProps,
|
||||||
|
};
|
||||||
@@ -21,3 +21,4 @@ export { SiteToc, NavItem, NavGroup } from "./SiteToc";
|
|||||||
export { Comments, CommentsConfig } from "./Comments";
|
export { Comments, CommentsConfig } from "./Comments";
|
||||||
export { AuthorConfig } from "./types";
|
export { AuthorConfig } from "./types";
|
||||||
export { Hero } from "./Hero";
|
export { Hero } from "./Hero";
|
||||||
|
export { Analytics, AnalyticsConfig } from "./analytics";
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ export const pageview = ({
|
|||||||
analyticsID: string;
|
analyticsID: string;
|
||||||
}) => {
|
}) => {
|
||||||
if (typeof window.gtag !== undefined) {
|
if (typeof window.gtag !== undefined) {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
window.gtag("config", analyticsID, {
|
window.gtag("config", analyticsID, {
|
||||||
page_path: url,
|
page_path: url,
|
||||||
});
|
});
|
||||||
@@ -16,6 +18,8 @@ export const pageview = ({
|
|||||||
// https://developers.google.com/analytics/devguides/collection/gtagjs/events
|
// https://developers.google.com/analytics/devguides/collection/gtagjs/events
|
||||||
export const event = ({ action, category, label, value }) => {
|
export const event = ({ action, category, label, value }) => {
|
||||||
if (typeof window.gtag !== undefined) {
|
if (typeof window.gtag !== undefined) {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
window.gtag("event", action, {
|
window.gtag("event", action, {
|
||||||
event_category: category,
|
event_category: category,
|
||||||
event_label: label,
|
event_label: label,
|
||||||
|
|||||||
Reference in New Issue
Block a user