Skip to content

Commit

Permalink
Finished the development of the BucketViewer component
Browse files Browse the repository at this point in the history
  • Loading branch information
leonardo.farias committed Nov 24, 2023
1 parent 03960c8 commit 712f4a3
Show file tree
Hide file tree
Showing 5 changed files with 54 additions and 110 deletions.
5 changes: 5 additions & 0 deletions .changeset/dirty-coats-obey.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@portaljs/components': minor
---

Creation of BucketViewer component to show the data of public buckets
41 changes: 0 additions & 41 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion packages/components/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@
"@tanstack/react-table": "^8.8.5",
"ag-grid-react": "^30.0.4",
"chroma-js": "^2.4.2",
"fast-xml-parser": "^4.3.2",
"flexsearch": "0.7.21",
"leaflet": "^1.9.4",
"next-mdx-remote": "^4.4.1",
Expand Down
95 changes: 32 additions & 63 deletions packages/components/src/components/BucketViewer.tsx
Original file line number Diff line number Diff line change
@@ -1,78 +1,46 @@
import { useEffect, useState } from 'react';
import LoadingSpinner from './LoadingSpinner';
import { XMLParser } from 'fast-xml-parser';

export interface BucketViewerProps {
domain: string;
suffix?: string;
className?: string;
dataMapperFn: (rawData: Response) => Promise<BucketViewerData[]>;
}

interface BucketResponse {
ListBucketResult: ListBucketResult;
export interface BucketViewerData {
fileName: string;
downloadFileUri: string;
dateProps?: {
date: Date;
dateFormatter: (date: Date) => string;
};
}

interface ListBucketResult {
Name: string;
Prefix: string;
MaxKeys: number;
IsTruncated: boolean;
Contents: Content[];
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) {
export function BucketViewer({
domain,
suffix,
dataMapperFn,
className,
}: BucketViewerProps) {
const [isLoading, setIsLoading] = useState<boolean>(false);
const [bucket, setBucket] = useState<BucketResponse>();
const [bucketFiles, setBucketFiles] = useState<BucketViewerData[]>([]);
suffix = suffix ?? '/';

useEffect(() => {
setIsLoading(true);
fetch(`${domain}${suffix}`)
.then((res) => res.text())
.then((res) => {
const parsedXml: BucketResponse = new XMLParser().parse(res);
let {
ListBucketResult: { Contents },
} = parsedXml;
Contents = Contents ?? [];
parsedXml.ListBucketResult.Contents = Array.isArray(Contents)
? Contents
: [Contents];
setBucket(parsedXml);
})
.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>
) : bucket ? (
) : bucketFiles ? (
<>
{...bucket?.ListBucketResult?.Contents?.map((c, i) => (
{...bucketFiles?.map((data, i) => (
<ul
onClick={() => {
const anchorId = `download_anchor_${i}`;
Expand All @@ -83,28 +51,29 @@ export function BucketViewer({ domain, suffix }: BucketViewerProps) {
if (a.download) a.click();
else {
setIsLoading(true);
fetch(`${domain}${suffix}${c.Key}`)
fetch(data.downloadFileUri)
.then((res) => res.blob())
.then((res) => {
a.href = URL.createObjectURL(res);
a.download = res.name ?? c.ETag.replace(/\"/g, '');
a.download = res.name ?? data.fileName;
document.body.appendChild(a);
a.click();
})
.finally(() => setIsLoading(false));
}
}}
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>{c.ETag}</li>
<li>{c.Owner?.DisplayName}</li>
<li>{c.Owner?.ID}</li>
<li>{c.Size}</li>
<li>{c.StorageClass}</li>
<li>{c.Type}</li>
<li>{c.LastModified}</li>
<li>{data.fileName}</li>
{data.dateProps ? (
<li>{data.dateProps.dateFormatter(data.dateProps.date)}</li>
) : (
<></>
)}
</ul>
))}
</>
Expand Down
22 changes: 17 additions & 5 deletions packages/components/stories/BucketViewer.stories.ts
Original file line number Diff line number Diff line change
@@ -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
const meta: Meta = {
Expand All @@ -27,8 +27,20 @@ type Story = StoryObj<BucketViewerProps>;
export const Normal: Story = {
name: 'Bucket viewer',
args: {
domain: 'https://nwguide.fra1.digitaloceanspaces.com',
suffix: '/'
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()
}
})
)
}
},
};

0 comments on commit 712f4a3

Please sign in to comment.