Finished the development of the BucketViewer component

This commit is contained in:
leonardo.farias
2023-11-23 21:41:28 -03:00
parent 03960c8bac
commit 712f4a3b0f
5 changed files with 54 additions and 110 deletions

View File

@@ -0,0 +1,5 @@
---
'@portaljs/components': minor
---
Creation of BucketViewer component to show the data of public buckets

41
package-lock.json generated
View File

@@ -25726,27 +25726,6 @@
"resolved": "https://registry.npmjs.org/fast-shallow-equal/-/fast-shallow-equal-1.0.0.tgz", "resolved": "https://registry.npmjs.org/fast-shallow-equal/-/fast-shallow-equal-1.0.0.tgz",
"integrity": "sha512-HPtaa38cPgWvaCFmRNhlc6NG7pv6NUHqjPgVAkWGoB9mQMwYB27/K0CvOM5Czy+qpT3e8XJ6Q4aPAnzpNpzNaw==" "integrity": "sha512-HPtaa38cPgWvaCFmRNhlc6NG7pv6NUHqjPgVAkWGoB9mQMwYB27/K0CvOM5Czy+qpT3e8XJ6Q4aPAnzpNpzNaw=="
}, },
"node_modules/fast-xml-parser": {
"version": "4.3.2",
"resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.3.2.tgz",
"integrity": "sha512-rmrXUXwbJedoXkStenj1kkljNF7ugn5ZjR9FJcwmCfcCbtOMDghPajbc+Tck6vE6F5XsDmx+Pr2le9fw8+pXBg==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/NaturalIntelligence"
},
{
"type": "paypal",
"url": "https://paypal.me/naturalintelligence"
}
],
"dependencies": {
"strnum": "^1.0.5"
},
"bin": {
"fxparser": "src/cli/cli.js"
}
},
"node_modules/fastest-stable-stringify": { "node_modules/fastest-stable-stringify": {
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/fastest-stable-stringify/-/fastest-stable-stringify-2.0.2.tgz", "resolved": "https://registry.npmjs.org/fastest-stable-stringify/-/fastest-stable-stringify-2.0.2.tgz",
@@ -43403,11 +43382,6 @@
"url": "https://github.com/sponsors/antfu" "url": "https://github.com/sponsors/antfu"
} }
}, },
"node_modules/strnum": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz",
"integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA=="
},
"node_modules/strong-log-transformer": { "node_modules/strong-log-transformer": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/strong-log-transformer/-/strong-log-transformer-2.1.0.tgz", "resolved": "https://registry.npmjs.org/strong-log-transformer/-/strong-log-transformer-2.1.0.tgz",
@@ -48333,7 +48307,6 @@
"@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",
"fast-xml-parser": "^4.3.2",
"flexsearch": "0.7.21", "flexsearch": "0.7.21",
"leaflet": "^1.9.4", "leaflet": "^1.9.4",
"next-mdx-remote": "^4.4.1", "next-mdx-remote": "^4.4.1",
@@ -57206,7 +57179,6 @@
"eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.3.4", "eslint-plugin-react-refresh": "^0.3.4",
"eslint-plugin-storybook": "^0.6.11", "eslint-plugin-storybook": "^0.6.11",
"fast-xml-parser": "^4.3.2",
"flexsearch": "0.7.21", "flexsearch": "0.7.21",
"json": "^11.0.0", "json": "^11.0.0",
"leaflet": "^1.9.4", "leaflet": "^1.9.4",
@@ -68642,14 +68614,6 @@
"resolved": "https://registry.npmjs.org/fast-shallow-equal/-/fast-shallow-equal-1.0.0.tgz", "resolved": "https://registry.npmjs.org/fast-shallow-equal/-/fast-shallow-equal-1.0.0.tgz",
"integrity": "sha512-HPtaa38cPgWvaCFmRNhlc6NG7pv6NUHqjPgVAkWGoB9mQMwYB27/K0CvOM5Czy+qpT3e8XJ6Q4aPAnzpNpzNaw==" "integrity": "sha512-HPtaa38cPgWvaCFmRNhlc6NG7pv6NUHqjPgVAkWGoB9mQMwYB27/K0CvOM5Czy+qpT3e8XJ6Q4aPAnzpNpzNaw=="
}, },
"fast-xml-parser": {
"version": "4.3.2",
"resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.3.2.tgz",
"integrity": "sha512-rmrXUXwbJedoXkStenj1kkljNF7ugn5ZjR9FJcwmCfcCbtOMDghPajbc+Tck6vE6F5XsDmx+Pr2le9fw8+pXBg==",
"requires": {
"strnum": "^1.0.5"
}
},
"fastest-stable-stringify": { "fastest-stable-stringify": {
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/fastest-stable-stringify/-/fastest-stable-stringify-2.0.2.tgz", "resolved": "https://registry.npmjs.org/fastest-stable-stringify/-/fastest-stable-stringify-2.0.2.tgz",
@@ -81868,11 +81832,6 @@
"acorn": "^8.8.2" "acorn": "^8.8.2"
} }
}, },
"strnum": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz",
"integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA=="
},
"strong-log-transformer": { "strong-log-transformer": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/strong-log-transformer/-/strong-log-transformer-2.1.0.tgz", "resolved": "https://registry.npmjs.org/strong-log-transformer/-/strong-log-transformer-2.1.0.tgz",

View File

@@ -34,7 +34,6 @@
"@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",
"fast-xml-parser": "^4.3.2",
"flexsearch": "0.7.21", "flexsearch": "0.7.21",
"leaflet": "^1.9.4", "leaflet": "^1.9.4",
"next-mdx-remote": "^4.4.1", "next-mdx-remote": "^4.4.1",

View File

@@ -1,78 +1,46 @@
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import LoadingSpinner from './LoadingSpinner'; import LoadingSpinner from './LoadingSpinner';
import { XMLParser } from 'fast-xml-parser';
export interface BucketViewerProps { export interface BucketViewerProps {
domain: string; domain: string;
suffix?: string; suffix?: string;
className?: string;
dataMapperFn: (rawData: Response) => Promise<BucketViewerData[]>;
} }
interface BucketResponse { export interface BucketViewerData {
ListBucketResult: ListBucketResult; fileName: string;
downloadFileUri: string;
dateProps?: {
date: Date;
dateFormatter: (date: Date) => string;
};
} }
interface ListBucketResult { export function BucketViewer({
Name: string; domain,
Prefix: string; suffix,
MaxKeys: number; dataMapperFn,
IsTruncated: boolean; className,
Contents: Content[]; }: BucketViewerProps) {
Marker: string;
NextMarker: string;
}
interface Content {
Key: string;
LastModified: string;
ETag: string;
Size: number;
StorageClass: StorageClass;
Owner?: Owner;
Type: Type;
}
interface Owner {
ID: number;
DisplayName: number;
}
enum StorageClass {
Standard = 'STANDARD',
}
enum Type {
Normal = 'Normal',
}
export function BucketViewer({ domain, suffix }: BucketViewerProps) {
const [isLoading, setIsLoading] = useState<boolean>(false); const [isLoading, setIsLoading] = useState<boolean>(false);
const [bucket, setBucket] = useState<BucketResponse>(); const [bucketFiles, setBucketFiles] = useState<BucketViewerData[]>([]);
suffix = suffix ?? '/'; suffix = suffix ?? '/';
useEffect(() => { useEffect(() => {
setIsLoading(true); setIsLoading(true);
fetch(`${domain}${suffix}`) fetch(`${domain}${suffix}`)
.then((res) => res.text()) .then((res) => dataMapperFn(res))
.then((res) => { .then((data) => setBucketFiles(data))
const parsedXml: BucketResponse = new XMLParser().parse(res);
let {
ListBucketResult: { Contents },
} = parsedXml;
Contents = Contents ?? [];
parsedXml.ListBucketResult.Contents = Array.isArray(Contents)
? Contents
: [Contents];
setBucket(parsedXml);
})
.finally(() => setIsLoading(false)); .finally(() => setIsLoading(false));
}, [domain, suffix]); }, [domain, suffix]);
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>
) : bucket ? ( ) : bucketFiles ? (
<> <>
{...bucket?.ListBucketResult?.Contents?.map((c, i) => ( {...bucketFiles?.map((data, i) => (
<ul <ul
onClick={() => { onClick={() => {
const anchorId = `download_anchor_${i}`; const anchorId = `download_anchor_${i}`;
@@ -83,11 +51,11 @@ export function BucketViewer({ domain, suffix }: BucketViewerProps) {
if (a.download) a.click(); if (a.download) a.click();
else { else {
setIsLoading(true); setIsLoading(true);
fetch(`${domain}${suffix}${c.Key}`) fetch(data.downloadFileUri)
.then((res) => res.blob()) .then((res) => res.blob())
.then((res) => { .then((res) => {
a.href = URL.createObjectURL(res); a.href = URL.createObjectURL(res);
a.download = res.name ?? c.ETag.replace(/\"/g, ''); a.download = res.name ?? data.fileName;
document.body.appendChild(a); document.body.appendChild(a);
a.click(); a.click();
}) })
@@ -95,16 +63,17 @@ export function BucketViewer({ domain, suffix }: BucketViewerProps) {
} }
}} }}
key={i} key={i}
className="mb-2 border-b-[2px] border-b-[red] hover:cursor-pointer" className={`${
className ??
'mb-2 border-b-[2px] border-b-[red] hover:cursor-pointer'
}`}
> >
<li>{c.Key}</li> <li>{data.fileName}</li>
<li>{c.ETag}</li> {data.dateProps ? (
<li>{c.Owner?.DisplayName}</li> <li>{data.dateProps.dateFormatter(data.dateProps.date)}</li>
<li>{c.Owner?.ID}</li> ) : (
<li>{c.Size}</li> <></>
<li>{c.StorageClass}</li> )}
<li>{c.Type}</li>
<li>{c.LastModified}</li>
</ul> </ul>
))} ))}
</> </>

View File

@@ -1,6 +1,6 @@
import type { Meta, StoryObj } from '@storybook/react'; import { raw, type Meta, type StoryObj } from '@storybook/react';
import { BucketViewer, BucketViewerProps } from '../src/components/BucketViewer'; 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 // More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction
const meta: Meta = { const meta: Meta = {
@@ -27,8 +27,20 @@ type Story = StoryObj<BucketViewerProps>;
export const Normal: Story = { export const Normal: Story = {
name: 'Bucket viewer', name: 'Bucket viewer',
args: { args: {
domain: 'https://nwguide.fra1.digitaloceanspaces.com', domain: 'https://ssen-smart-meter.datopian.workers.dev',
suffix: '/' 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()
}
})
)
}
}, },
}; };