diff --git a/package-lock.json b/package-lock.json index bf21c403..9bc6806f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25726,6 +25726,27 @@ "resolved": "https://registry.npmjs.org/fast-shallow-equal/-/fast-shallow-equal-1.0.0.tgz", "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": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/fastest-stable-stringify/-/fastest-stable-stringify-2.0.2.tgz", @@ -43382,6 +43403,11 @@ "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": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/strong-log-transformer/-/strong-log-transformer-2.1.0.tgz", @@ -48307,6 +48333,7 @@ "@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", @@ -48734,7 +48761,7 @@ }, "packages/core": { "name": "@portaljs/core", - "version": "1.0.6", + "version": "1.0.8", "license": "MIT", "dependencies": { "@docsearch/react": "^3.3.3", @@ -48773,7 +48800,7 @@ }, "packages/remark-embed": { "name": "@portaljs/remark-embed", - "version": "1.0.4", + "version": "1.0.5", "license": "MIT", "dependencies": { "unist-util-visit": "^4.1.1" @@ -48781,7 +48808,7 @@ }, "packages/remark-wiki-link": { "name": "@portaljs/remark-wiki-link", - "version": "1.1.0", + "version": "1.1.2", "license": "MIT", "dependencies": { "mdast-util-to-markdown": "^1.5.0", @@ -57179,6 +57206,7 @@ "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-refresh": "^0.3.4", "eslint-plugin-storybook": "^0.6.11", + "fast-xml-parser": "^4.3.2", "flexsearch": "0.7.21", "json": "^11.0.0", "leaflet": "^1.9.4", @@ -68614,6 +68642,14 @@ "resolved": "https://registry.npmjs.org/fast-shallow-equal/-/fast-shallow-equal-1.0.0.tgz", "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": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/fastest-stable-stringify/-/fastest-stable-stringify-2.0.2.tgz", @@ -81832,6 +81868,11 @@ "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": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/strong-log-transformer/-/strong-log-transformer-2.1.0.tgz", diff --git a/packages/components/package.json b/packages/components/package.json index f7ccb13e..c30a9826 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -29,14 +29,18 @@ "@githubocto/flat-ui": "^0.14.1", "@heroicons/react": "^2.0.17", "@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", "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", "ol": "^7.4.0", "papaparse": "^5.4.1", + "pdfjs-dist": "2.15.349", "postcss-url": "^10.1.3", "react": "^18.2.0", "react-dom": "^18.2.0", @@ -47,9 +51,6 @@ "vega": "5.25.0", "vega-lite": "5.1.0", "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" }, "devDependencies": { diff --git a/packages/components/src/components/BucketViewer.tsx b/packages/components/src/components/BucketViewer.tsx new file mode 100644 index 00000000..9c8aeefa --- /dev/null +++ b/packages/components/src/components/BucketViewer.tsx @@ -0,0 +1,112 @@ +import { useEffect, useState } from 'react'; +import LoadingSpinner from './LoadingSpinner'; +import { XMLParser } from 'fast-xml-parser'; + +export interface BucketViewerProps { + domain: string; + suffix?: string; +} + +interface BucketResponse { + ListBucketResult: ListBucketResult; +} + +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) { + const [isLoading, setIsLoading] = useState(false); + const [bucket, setBucket] = useState(); + 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); + }) + .finally(() => setIsLoading(false)); + }, [domain, suffix]); + return isLoading ? ( +
+ +
+ ) : bucket ? ( + <> + {...bucket?.ListBucketResult?.Contents?.map((c, i) => ( + + ))} + + ) : null; +} diff --git a/packages/components/stories/BucketViewer.stories.ts b/packages/components/stories/BucketViewer.stories.ts new file mode 100644 index 00000000..7f73cd03 --- /dev/null +++ b/packages/components/stories/BucketViewer.stories.ts @@ -0,0 +1,34 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import { BucketViewer, 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; + +// 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://nwguide.fra1.digitaloceanspaces.com', + suffix: '/' + }, +}; +