Merge pull request #1055 from datopian/feature/bucket-viewer-component
feature: Created bucket viewer component
This commit is contained in:
5
.changeset/dirty-coats-obey.md
Normal file
5
.changeset/dirty-coats-obey.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
'@portaljs/components': minor
|
||||||
|
---
|
||||||
|
|
||||||
|
Creation of BucketViewer component to show the data of public buckets
|
||||||
6
package-lock.json
generated
6
package-lock.json
generated
@@ -48734,7 +48734,7 @@
|
|||||||
},
|
},
|
||||||
"packages/core": {
|
"packages/core": {
|
||||||
"name": "@portaljs/core",
|
"name": "@portaljs/core",
|
||||||
"version": "1.0.6",
|
"version": "1.0.8",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@docsearch/react": "^3.3.3",
|
"@docsearch/react": "^3.3.3",
|
||||||
@@ -48773,7 +48773,7 @@
|
|||||||
},
|
},
|
||||||
"packages/remark-embed": {
|
"packages/remark-embed": {
|
||||||
"name": "@portaljs/remark-embed",
|
"name": "@portaljs/remark-embed",
|
||||||
"version": "1.0.4",
|
"version": "1.0.5",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"unist-util-visit": "^4.1.1"
|
"unist-util-visit": "^4.1.1"
|
||||||
@@ -48781,7 +48781,7 @@
|
|||||||
},
|
},
|
||||||
"packages/remark-wiki-link": {
|
"packages/remark-wiki-link": {
|
||||||
"name": "@portaljs/remark-wiki-link",
|
"name": "@portaljs/remark-wiki-link",
|
||||||
"version": "1.1.0",
|
"version": "1.1.2",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"mdast-util-to-markdown": "^1.5.0",
|
"mdast-util-to-markdown": "^1.5.0",
|
||||||
|
|||||||
@@ -29,6 +29,8 @@
|
|||||||
"@githubocto/flat-ui": "^0.14.1",
|
"@githubocto/flat-ui": "^0.14.1",
|
||||||
"@heroicons/react": "^2.0.17",
|
"@heroicons/react": "^2.0.17",
|
||||||
"@planet/maps": "^8.1.0",
|
"@planet/maps": "^8.1.0",
|
||||||
|
"@react-pdf-viewer/core": "3.6.0",
|
||||||
|
"@react-pdf-viewer/default-layout": "3.6.0",
|
||||||
"@tanstack/react-table": "^8.8.5",
|
"@tanstack/react-table": "^8.8.5",
|
||||||
"ag-grid-react": "^30.0.4",
|
"ag-grid-react": "^30.0.4",
|
||||||
"chroma-js": "^2.4.2",
|
"chroma-js": "^2.4.2",
|
||||||
@@ -37,6 +39,7 @@
|
|||||||
"next-mdx-remote": "^4.4.1",
|
"next-mdx-remote": "^4.4.1",
|
||||||
"ol": "^7.4.0",
|
"ol": "^7.4.0",
|
||||||
"papaparse": "^5.4.1",
|
"papaparse": "^5.4.1",
|
||||||
|
"pdfjs-dist": "2.15.349",
|
||||||
"postcss-url": "^10.1.3",
|
"postcss-url": "^10.1.3",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
@@ -47,9 +50,6 @@
|
|||||||
"vega": "5.25.0",
|
"vega": "5.25.0",
|
||||||
"vega-lite": "5.1.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"
|
"xlsx": "^0.18.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
81
packages/components/src/components/BucketViewer.tsx
Normal file
81
packages/components/src/components/BucketViewer.tsx
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import LoadingSpinner from './LoadingSpinner';
|
||||||
|
|
||||||
|
export interface BucketViewerProps {
|
||||||
|
domain: string;
|
||||||
|
suffix?: string;
|
||||||
|
className?: string;
|
||||||
|
dataMapperFn: (rawData: Response) => Promise<BucketViewerData[]>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BucketViewerData {
|
||||||
|
fileName: string;
|
||||||
|
downloadFileUri: string;
|
||||||
|
dateProps?: {
|
||||||
|
date: Date;
|
||||||
|
dateFormatter: (date: Date) => string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function BucketViewer({
|
||||||
|
domain,
|
||||||
|
suffix,
|
||||||
|
dataMapperFn,
|
||||||
|
className,
|
||||||
|
}: BucketViewerProps) {
|
||||||
|
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||||
|
const [bucketFiles, setBucketFiles] = useState<BucketViewerData[]>([]);
|
||||||
|
suffix = suffix ?? '/';
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setIsLoading(true);
|
||||||
|
fetch(`${domain}${suffix}`)
|
||||||
|
.then((res) => dataMapperFn(res))
|
||||||
|
.then((data) => setBucketFiles(data))
|
||||||
|
.finally(() => setIsLoading(false));
|
||||||
|
}, [domain, suffix]);
|
||||||
|
return isLoading ? (
|
||||||
|
<div className="w-full flex items-center justify-center h-[300px]">
|
||||||
|
<LoadingSpinner />
|
||||||
|
</div>
|
||||||
|
) : bucketFiles ? (
|
||||||
|
<>
|
||||||
|
{...bucketFiles?.map((data, i) => (
|
||||||
|
<ul
|
||||||
|
onClick={() => {
|
||||||
|
const anchorId = `download_anchor_${i}`;
|
||||||
|
const a: HTMLAnchorElement =
|
||||||
|
(document.getElementById(anchorId) as HTMLAnchorElement | null) ??
|
||||||
|
document.createElement('a');
|
||||||
|
a.id = anchorId;
|
||||||
|
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>
|
||||||
|
{data.dateProps ? (
|
||||||
|
<li>{data.dateProps.dateFormatter(data.dateProps.date)}</li>
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
|
)}
|
||||||
|
</ul>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
) : null;
|
||||||
|
}
|
||||||
46
packages/components/stories/BucketViewer.stories.ts
Normal file
46
packages/components/stories/BucketViewer.stories.ts
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import { raw, type Meta, type StoryObj } from '@storybook/react';
|
||||||
|
|
||||||
|
import { BucketViewer, BucketViewerData, BucketViewerProps } from '../src/components/BucketViewer';
|
||||||
|
|
||||||
|
// More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction
|
||||||
|
const meta: Meta = {
|
||||||
|
title: 'Components/BucketViewer',
|
||||||
|
component: BucketViewer,
|
||||||
|
tags: ['autodocs'],
|
||||||
|
argTypes: {
|
||||||
|
domain: {
|
||||||
|
description:
|
||||||
|
'Bucket domain URI',
|
||||||
|
},
|
||||||
|
suffix: {
|
||||||
|
description:
|
||||||
|
'Suffix of bucket domain',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
|
||||||
|
type Story = StoryObj<BucketViewerProps>;
|
||||||
|
|
||||||
|
// More on writing stories with args: https://storybook.js.org/docs/react/writing-stories/args
|
||||||
|
export const Normal: Story = {
|
||||||
|
name: 'Bucket viewer',
|
||||||
|
args: {
|
||||||
|
domain: 'https://ssen-smart-meter.datopian.workers.dev',
|
||||||
|
suffix: '/',
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user