Finished the development of the BucketViewer 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
|
||||||
41
package-lock.json
generated
41
package-lock.json
generated
@@ -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",
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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>
|
||||||
))}
|
))}
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -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()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user