From eeb15ecfb0b81f07269bf94617b88492d0671f10 Mon Sep 17 00:00:00 2001 From: Russell Dempsey <1173416+SgtPooki@users.noreply.github.com> Date: Thu, 17 Oct 2024 16:42:29 -0500 Subject: [PATCH] feat!: remove redux-bundler, full typescript (#448) * feat: convert to typescript and remove redux-bundler * feat: fetching is working * fix: build is working * test: fix findLinkPath tests * test: fixing unit tests * fix: navigating through a dag works again * chore: remove tests that will come from #449 * fix: builds and passes tests * fix: storybook and types * fix: deep-link to an explore view * chore: pin storybook, remove viteFinal config * chore: add package-lock.json to attempt to fix CI * test: typed storybook default exports * chore: update storybook * chore: migrate storybook stories to tsx * chore: update aegir * fix: cid stories and rendering shortest cids * fix: storybook dev auto-reload * fix: graphcrumb stories * fix: aegir build is consumable by explore.ipld.io * feat: importable by explore.ipld.io * chore: externalizing more deps * chore: remove unnecessary build plugins * chore: update and move react-helmet to deps * chore: cleanup unused storybook deps * chore: remove rollup direct dev dependency * chore: remove babel * chore: add react-hooks eslint plugin * chore: update Helia deps * chore: update eslint plugins * chore: update aegir * chore: update @types deps * chore: update filesize * chore: update ipld libs * chore: update multiformats * chore: update vite * chore: update storybook deps * chore: remove @rollup/plugin-node-resolve * chore: update testing deps and react * chore: update react peer deps * chore: update the last of the deps * feat: new links-table without react-virtualized * fix: fixing up some issues with new links table * fix: links-table uses grid instead of flex * feat: new links table * docs: update user guide instructions * test: vitest runs again * fix: breadcrumb link clicking * chore: convert tests to typescript * chore: fix import path * chore: update package-lock.json --- .aegir.js | 94 +- .babelrc | 26 - .editorconfig | 16 - .storybook/main.ts | 27 +- .storybook/preview.ts | 15 +- README.md | 195 +- dev/devPage.jsx | 116 - dev/devPage.tsx | 110 + index.html | 6 +- package-lock.json | 52183 +++++++--------- package.json | 202 +- src/bundle-decorator.jsx | 16 - src/bundles/explore.js | 143 - src/bundles/helia.ts | 96 - src/components/ExplorePage.stories.jsx | 59 - src/components/ExplorePage.stories.tsx | 80 + .../{ExplorePage.jsx => ExplorePage.tsx} | 81 +- src/components/LoadableExplorePage.jsx | 9 - src/components/StartExploringPage.jsx | 69 - ...ies.jsx => StartExploringPage.stories.tsx} | 10 +- src/components/StartExploringPage.tsx | 69 + .../about/{AboutIpld.jsx => AboutIpld.tsx} | 10 +- src/components/box/Box.jsx | 19 - src/components/box/Box.tsx | 23 + ...idInfo.stories.jsx => CidInfo.stories.tsx} | 15 +- .../cid-info/{CidInfo.jsx => CidInfo.tsx} | 45 +- src/components/cid-info/decode-cid.js | 31 - src/components/cid-info/decode-cid.ts | 45 + src/components/cid/Cid.jsx | 34 - src/components/cid/Cid.stories.jsx | 45 - src/components/cid/Cid.stories.tsx | 47 + src/components/cid/Cid.tsx | 51 + .../{ErrorBoundary.jsx => ErrorBoundary.tsx} | 8 +- src/components/explore/IpldCarExploreForm.jsx | 50 - .../explore/IpldCarExploreForm.stories.jsx | 30 - .../explore/IpldCarExploreForm.stories.tsx | 33 + src/components/explore/IpldCarExploreForm.tsx | 69 + .../explore/IpldExploreErrorComponent.tsx | 2 +- src/components/explore/IpldExploreForm.jsx | 66 - .../explore/IpldExploreForm.stories.jsx | 27 - .../explore/IpldExploreForm.stories.tsx | 33 + src/components/explore/IpldExploreForm.tsx | 64 + src/components/explore/spinner.svg | 1 - src/components/explore/upload.svg | 1 - .../graph-crumb/GraphCrumb.stories.jsx | 44 - .../graph-crumb/GraphCrumb.stories.tsx | 101 + .../{GraphCrumb.jsx => GraphCrumb.tsx} | 60 +- .../graph-crumb/graph-crumb.test.tsx | 32 + ...raph.stories.jsx => IpldGraph.stories.tsx} | 14 +- .../graph/{IpldGraph.jsx => IpldGraph.tsx} | 9 +- ...phCytoscape.jsx => IpldGraphCytoscape.tsx} | 65 +- src/components/graph/LoadableIpldGraph.jsx | 9 - ...tories.jsx => ComponentLoader.stories.tsx} | 14 +- ...omponentLoader.jsx => ComponentLoader.tsx} | 6 +- .../loader/{Loader.jsx => Loader.tsx} | 5 +- .../loader/{Loader.css => loader.css} | 0 src/components/object-info/LinksTable.css | 7 - src/components/object-info/LinksTable.jsx | 47 - ...nfo.stories.jsx => ObjectInfo.stories.tsx} | 23 +- .../{ObjectInfo.jsx => ObjectInfo.tsx} | 120 +- src/components/object-info/links-table.css | 67 + src/components/object-info/links-table.tsx | 151 + src/forms.ts | 2 + src/global.d.ts | 10 - ...{i18n-decorator.jsx => i18n-decorator.tsx} | 4 +- src/{i18n.js => i18n.ts} | 15 +- ...phSmallCancel.jsx => GlyphSmallCancel.tsx} | 4 +- src/icons/{StrokeIpld.jsx => StrokeIpld.tsx} | 4 +- src/index.js | 21 - src/index.ts | 10 + src/lib/browser-shims.js | 7 + src/lib/cid.js | 54 - src/lib/{cid.test.js => cid.test.ts} | 8 +- src/lib/cid.ts | 52 + ...estions.js => explore-page-suggestions.ts} | 14 +- src/lib/extract-info.ts | 6 +- src/lib/get-codec-for-cid.ts | 8 +- src/lib/get-codec-name-from-code.ts | 7 +- src/lib/hash-importer.ts | 13 +- src/lib/helpers.ts | 15 + src/lib/import-car.ts | 4 +- ...lise-dag-node.js => normalise-dag-node.ts} | 84 +- ...ode.test.js => normalize-dag-node.test.ts} | 12 +- ...{parse-ipld-path.js => parse-ipld-path.ts} | 11 +- ...path.test.js => resolve-ipld-path.test.ts} | 52 +- ...olve-ipld-path.js => resolve-ipld-path.ts} | 102 +- src/lib/{tours.jsx => tours.tsx} | 6 +- src/pages.ts | 2 + src/providers/explore.tsx | 190 + src/providers/helia.tsx | 95 + src/providers/index.ts | 2 + src/types.d.ts | 33 + test/unit/helia-mock.ts | 7 + test/unit/heliaMock.js | 7 - test/unit/setup.js | 11 - test/unit/setup.ts | 8 + tsconfig.eslint.json | 15 - tsconfig.json | 26 +- vite.config.ts | 94 +- vitest.config.js | 14 +- 100 files changed, 24735 insertions(+), 31439 deletions(-) delete mode 100644 .babelrc delete mode 100644 .editorconfig delete mode 100644 dev/devPage.jsx create mode 100644 dev/devPage.tsx delete mode 100644 src/bundle-decorator.jsx delete mode 100644 src/bundles/explore.js delete mode 100644 src/bundles/helia.ts delete mode 100644 src/components/ExplorePage.stories.jsx create mode 100644 src/components/ExplorePage.stories.tsx rename src/components/{ExplorePage.jsx => ExplorePage.tsx} (61%) delete mode 100644 src/components/LoadableExplorePage.jsx delete mode 100644 src/components/StartExploringPage.jsx rename src/components/{StartExploringPage.stories.jsx => StartExploringPage.stories.tsx} (53%) create mode 100644 src/components/StartExploringPage.tsx rename src/components/about/{AboutIpld.jsx => AboutIpld.tsx} (88%) delete mode 100644 src/components/box/Box.jsx create mode 100644 src/components/box/Box.tsx rename src/components/cid-info/{CidInfo.stories.jsx => CidInfo.stories.tsx} (86%) rename src/components/cid-info/{CidInfo.jsx => CidInfo.tsx} (77%) delete mode 100644 src/components/cid-info/decode-cid.js create mode 100644 src/components/cid-info/decode-cid.ts delete mode 100644 src/components/cid/Cid.jsx delete mode 100644 src/components/cid/Cid.stories.jsx create mode 100644 src/components/cid/Cid.stories.tsx create mode 100644 src/components/cid/Cid.tsx rename src/components/error/{ErrorBoundary.jsx => ErrorBoundary.tsx} (61%) delete mode 100644 src/components/explore/IpldCarExploreForm.jsx delete mode 100644 src/components/explore/IpldCarExploreForm.stories.jsx create mode 100644 src/components/explore/IpldCarExploreForm.stories.tsx create mode 100644 src/components/explore/IpldCarExploreForm.tsx delete mode 100644 src/components/explore/IpldExploreForm.jsx delete mode 100644 src/components/explore/IpldExploreForm.stories.jsx create mode 100644 src/components/explore/IpldExploreForm.stories.tsx create mode 100644 src/components/explore/IpldExploreForm.tsx delete mode 100644 src/components/graph-crumb/GraphCrumb.stories.jsx create mode 100644 src/components/graph-crumb/GraphCrumb.stories.tsx rename src/components/graph-crumb/{GraphCrumb.jsx => GraphCrumb.tsx} (54%) create mode 100644 src/components/graph-crumb/graph-crumb.test.tsx rename src/components/graph/{IpldGraph.stories.jsx => IpldGraph.stories.tsx} (82%) rename src/components/graph/{IpldGraph.jsx => IpldGraph.tsx} (90%) rename src/components/graph/{IpldGraphCytoscape.jsx => IpldGraphCytoscape.tsx} (59%) delete mode 100644 src/components/graph/LoadableIpldGraph.jsx rename src/components/loader/{ComponentLoader.stories.jsx => ComponentLoader.stories.tsx} (51%) rename src/components/loader/{ComponentLoader.jsx => ComponentLoader.tsx} (62%) rename src/components/loader/{Loader.jsx => Loader.tsx} (72%) rename src/components/loader/{Loader.css => loader.css} (100%) delete mode 100644 src/components/object-info/LinksTable.css delete mode 100644 src/components/object-info/LinksTable.jsx rename src/components/object-info/{ObjectInfo.stories.jsx => ObjectInfo.stories.tsx} (75%) rename src/components/object-info/{ObjectInfo.jsx => ObjectInfo.tsx} (63%) create mode 100644 src/components/object-info/links-table.css create mode 100644 src/components/object-info/links-table.tsx create mode 100644 src/forms.ts delete mode 100644 src/global.d.ts rename src/{i18n-decorator.jsx => i18n-decorator.tsx} (57%) rename src/{i18n.js => i18n.ts} (75%) rename src/icons/{GlyphSmallCancel.jsx => GlyphSmallCancel.tsx} (65%) rename src/icons/{StrokeIpld.jsx => StrokeIpld.tsx} (96%) delete mode 100644 src/index.js create mode 100644 src/index.ts create mode 100644 src/lib/browser-shims.js delete mode 100644 src/lib/cid.js rename src/lib/{cid.test.js => cid.test.ts} (93%) create mode 100644 src/lib/cid.ts rename src/lib/{explorePageSuggestions.js => explore-page-suggestions.ts} (89%) rename src/lib/{normalise-dag-node.js => normalise-dag-node.ts} (69%) rename src/lib/{normalize-dag-node.test.js => normalize-dag-node.test.ts} (85%) rename src/lib/{parse-ipld-path.js => parse-ipld-path.ts} (62%) rename src/lib/{resolve-ipld-path.test.js => resolve-ipld-path.test.ts} (91%) rename src/lib/{resolve-ipld-path.js => resolve-ipld-path.ts} (54%) rename src/lib/{tours.jsx => tours.tsx} (90%) create mode 100644 src/pages.ts create mode 100644 src/providers/explore.tsx create mode 100644 src/providers/helia.tsx create mode 100644 src/providers/index.ts create mode 100644 test/unit/helia-mock.ts delete mode 100644 test/unit/heliaMock.js delete mode 100644 test/unit/setup.js create mode 100644 test/unit/setup.ts delete mode 100644 tsconfig.eslint.json diff --git a/.aegir.js b/.aegir.js index efda3f3e..89c99fe9 100644 --- a/.aegir.js +++ b/.aegir.js @@ -1,14 +1,82 @@ +// import copy from 'esbuild-plugin-copy' +import fs from 'node:fs' +import path from 'node:path' + +const copyPlugin = ({ext}) => { + return { + name: `copy-${ext}`, + setup(build) { + const srcDir = 'src' + const destDir = 'dist/src' + build.onEnd(() => { + const copyFile = (src, dest) => { + fs.mkdirSync(path.dirname(dest), { recursive: true }) + fs.copyFileSync(src, dest) + } + + const walkDir = (dir, callback) => { + fs.readdirSync(dir).forEach(f => { + const dirPath = path.join(dir, f) + const isDirectory = fs.statSync(dirPath).isDirectory() + isDirectory ? walkDir(dirPath, callback) : callback(path.join(dir, f)) + }) + } + + walkDir(srcDir, (filePath) => { + if (filePath.endsWith(`.${ext}`)) { + const relativePath = path.relative(srcDir, filePath) + const destPath = path.join(destDir, relativePath) + copyFile(filePath, destPath) + } + }) + }) + } + } +} /** @type {import('aegir').PartialOptions} */ -module.exports = { +export default { + // TODO: fix build and test with aegir + // test: { + // build: false, + // files: [ + // 'dist/test/**/*.spec.{js,ts, jsx, tsx}', + // ], + // }, + build: { + config: { + inject: [ + './src/lib/browser-shims.js' + ], + bundle: true, + loader: { + '.js': 'jsx', + '.ts': 'ts', + '.tsx': 'tsx', + '.jsx': 'jsx', + '.svg': 'text', + // '.css': 'css' + '.woff': 'file', + '.woff2': 'file', + '.eot': 'file', + '.otf': 'file', + }, + platform: 'browser', + target: 'es2022', + format: 'esm', + metafile: true, + plugins: [ + copyPlugin({ext: 'css'}), + copyPlugin({ext: 'svg'}), + ], + } + }, lint: { files: [ - // '!node_modules/**', 'src/**/*.{js,jsx,ts,tsx}', 'test/**/*.{js,jsx,ts,tsx}', - // 'src/**/*.tsx', - // 'src/**/*.js', - // 'src/**/*.jsx', 'dev/**/*.{js,jsx,ts,tsx}', + // TODO: re-enable linting of stories. + '!src/**/*.stories.*', ] }, dependencyCheck: { @@ -19,21 +87,14 @@ module.exports = { 'filesize', 'react-inspector', 'react-joyride', + 'react-helmet', // storybook deps + '@chromatic-com/storybook', '@storybook/addon-actions', '@storybook/addon-coverage', - '@storybook/addon-essentials', '@storybook/addon-interactions', '@storybook/addon-links', - '@storybook/channels', - '@storybook/core-common', - '@storybook/core-events', - '@storybook/csf-plugin', - '@storybook/csf-tools', - '@storybook/docs-tools', - '@storybook/node-logger', - '@storybook/react-dom-shim', '@storybook/types', // problem with deps @@ -41,6 +102,9 @@ module.exports = { // scripts 'wait-on', + + // vite stuff + 'rollup-plugin-node-polyfills', ], productionIgnorePatterns: [ '.aegir.js', @@ -49,7 +113,7 @@ module.exports = { 'vitest.config.js', '/test', '.storybook', - 'dist-vite' + '**/*.stories.*', ] } } diff --git a/.babelrc b/.babelrc deleted file mode 100644 index 0432560c..00000000 --- a/.babelrc +++ /dev/null @@ -1,26 +0,0 @@ -{ - "presets": [ - ["@babel/preset-env", { - "modules": false, - "targets": "> 2%, not dead" - }], - ["@babel/preset-typescript", { - "isTSX": true, - "allExtensions": true - }], - "@babel/preset-react" - ], - "plugins": [ - "@babel/plugin-proposal-class-properties", - ], - "ignore": [ - "**/*.test.js", - "**/*.test.jsx", - "**/*.test.ts", - "**/*.test.tsx", - "**/*.stories.js", - "**/*.stories.jsx", - "**/*.stories.ts", - "**/*.stories.tsx" - ], -} diff --git a/.editorconfig b/.editorconfig deleted file mode 100644 index 34e13fd6..00000000 --- a/.editorconfig +++ /dev/null @@ -1,16 +0,0 @@ -# top-most EditorConfig file -root = true - -[*] -charset = utf-8 -end_of_line = lf -indent_size = 2 -indent_style = space -insert_final_newline = true -trim_trailing_whitespace = true - -[Makefile] -indent_style = tab - -[*.md] -trim_trailing_whitespace = false diff --git a/.storybook/main.ts b/.storybook/main.ts index b1a75f7b..c30fc5af 100644 --- a/.storybook/main.ts +++ b/.storybook/main.ts @@ -4,23 +4,32 @@ import { mergeConfig } from 'vite'; import viteConfig from '../vite.config'; const config: StorybookConfig = { - stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'], + stories: ['../src/**/*.stories.@(js|jsx|ts|tsx)'], + addons: [ '@storybook/addon-links', - '@storybook/addon-essentials', '@storybook/addon-interactions', - '@storybook/addon-coverage' + '@storybook/addon-coverage', + '@chromatic-com/storybook' ], + framework: { name: '@storybook/react-vite', options: {}, }, - docs: { - autodocs: 'tag', - }, - async viteFinal(config) { - // Merge custom configuration into the default config - return mergeConfig(config, viteConfig); + + // async viteFinal(config) { + // // Merge custom configuration into the default config + // return mergeConfig(config, viteConfig); + // }, + typescript: { + // reactDocgen: 'react-docgen-typescript' + reactDocgen: false + // reactDocgenTypescriptOptions: { + + // } }, + + // docs: {} }; export default config; diff --git a/.storybook/preview.ts b/.storybook/preview.ts index 14857c73..4503272e 100644 --- a/.storybook/preview.ts +++ b/.storybook/preview.ts @@ -5,21 +5,12 @@ globalThis.Buffer = Buffer // import CSS files import 'ipfs-css' -import 'react-virtualized/styles.css' import 'tachyons' -import '../src/components/loader/Loader.css' -import '../src/components/object-info/LinksTable.css' +import '../src/components/loader/loader.css' +import '../src/components/object-info/links-table.css' const preview: Preview = { - parameters: { - actions: { argTypesRegex: '^on[A-Z].*' }, - controls: { - matchers: { - color: /(background|color)$/i, - date: /Date$/, - }, - }, - }, + // tags: ['autodocs'] }; export default preview; diff --git a/README.md b/README.md index 6979472a..964af217 100644 --- a/README.md +++ b/README.md @@ -17,47 +17,132 @@ This module was extracted from the [explore.ipld.io](https://github.com/ipfs/exp Install it from npm: ```console -npm install ipld-explorer-components +npm install --save ipld-explorer-components ``` -The ES5 friendly version of the `src` dir is generated to the `dist` dir and the -page components are all provided as named exports so you can import them like so: +There are `peerDependencies` so that the consuming app can pick the versions of common deps. You'll need to add relevant deps to your project. + +### Use it in your project + +You can see an example of how to use these components in the [devPage.jsx](./dev/devPage.jsx) file. + +```jsx +// index.tsx +import React from 'react' +import {render} from 'react-dom' +import MyHeader from './app' + +const PageRenderer = (): React.ReactElement => { + /** + * This is a simple example of listening to the hash change event that occurs when the user clicks around in the content rendered by ExplorePage. + */ + const [route, setRoute] = useState(window.location.hash.slice(1) ?? '/') + + useEffect(() => { + const onHashChange = (): void => { setRoute(window.location.hash.slice(1) ?? '/') } + window.addEventListener('hashchange', onHashChange) + return () => { window.removeEventListener('hashchange', onHashChange) } + }, []) + + const RenderPage: React.FC = () => { + switch (true) { + case route.startsWith('/explore'): + return + case route === '/': + default: + return + } + } + + return ( + + ) +} +const App = (): React.ReactElement => { + return ( + + + + + + + ) +} + +const rootEl = document.getElementById('root') +if (rootEl == null) { + throw new Error('No root element found with the id "root"') +} +const root = createRoot(rootEl) +root.render( + + + +) + +``` + +### Exports provided by this library ```js -import {ExplorePage, StartExploringPage} from `ipld-explorer-components` +import { HeliaProvider, ExploreProvider } from 'ipld-explorer-components/providers' +import { StartExploringPage, ExplorePage } from 'ipld-explorer-components/pages' +import { IpldExploreForm, IpldCarExploreForm } from 'ipld-explorer-components/forms' +// or import all components at once +import { HeliaProvider, ExploreProvider, StartExploringPage, ExplorePage, IpldExploreForm, IpldCarExploreForm, CidInfo, ObjectInfo } from 'ipld-explorer-components' ``` The following Components are available: ```js export { + /** + * Helia provider required for IPLD Explorer components + */ + HeliaProvider, + /** + * A hook to gain access to the Helia node + */ + useHelia, + /** + * Explore provider required for IPLD Explorer components. This must be a child (direct or not) of HeliaProvider. + */ + ExploreProvider, + /** + * A hook to gain access to the Explore state. You can programmatically set the CID or path to explore using the provided functions. + */ + useExplore, + /** + * The page to render when you do not have an explicit CID in the URL to explore yet. + */ StartExploringPage, + /** + * When there is a #/explore/CID in the URL, this component will render the ExplorePage + */ ExplorePage, + /** + * The form to use to allow entry of a CID to explore. You can place this anywhere in your app within the ExploreProvider. + */ IpldExploreForm, + /** + * The form to use to allow uploading of a CAR file to explore. You can place this anywhere in your app within the ExploreProvider. + */ IpldCarExploreForm, CidInfo, - IpldGraph ObjectInfo, - exploreBundle, - heliaBundle } ``` -There are `peerDependencies` so that the parent app can pick the versions of common deps. You'll need to add relevant deps to your project. +### Styling And, assuming you are using `create-react-app` or a similar webpack set up, you'll need the following CSS imports: ```js import 'tachyons' import 'ipfs-css' -import 'react-virtualized/styles.css' -import 'ipld-explorer-components/dist/components/object-info/LinksTable.css' -import 'ipld-explorer-components/dist/components/loader/Loader.css' ``` -You can see an example of how to use these components in the [devPage.jsx](./dev/devPage.jsx) file. - ### Customizing the links displayed in the StartExploringPage To customize the links displayed in the start exploring page, you can pass a `links` property to the `StartExploringPage` component. This property should be an array of objects with the following properties: @@ -70,6 +155,76 @@ To customize the links displayed in the start exploring page, you can pass a `li } ``` +### i18n support + +The translations used for this library are provided in `dist/locales`. You can use them in your project by importing them and passing them to the `i18n` instance in your project. + +```ts +import i18n from 'i18next' +import LanguageDetector from 'i18next-browser-languagedetector' +import Backend from 'i18next-chained-backend' +import HttpBackend from 'i18next-http-backend' +import ICU from 'i18next-icu' +import LocalStorageBackend from 'i18next-localstorage-backend' +import { version } from '../package.json' +import locales from './lib/languages.json' + +export const localesList = Object.values(locales) + +await i18n + .use(ICU) + .use(Backend) + .use(LanguageDetector) + .init({ + backend: { + backends: [ + LocalStorageBackend, + HttpBackend + ], + backendOptions: [ + { // LocalStorageBackend + defaultVersion: version, + expirationTime: (!import.meta.env.NODE_ENV || imObjectInfo.publicGatewayport.meta.env.NODE_ENV === 'development') ? 1 : 7 * 24 * 60 * 60 * 1000 + }, + { // HttpBackend + // ensure a relative path is used to look up the locales, so it works when loaded from /ipfs/ + loadPath: (lngs, namespaces) => { + const lang = lngs[0] + const ns = namespaces[0] + if (ns === 'explore') { + // use the ipld-explorer-components locales + return 'node_modules/ipld-explorer-components/dist/locales/{{lng}}/{{ns}}.json' + } + + // you can override keys in the explore namespace with your own translations. If they are not found, the explore translations will be used. + return `locales/${lang}/${ns}.json` + } + } + ] + }, + ns: ['explore', 'app'], + defaultNS: 'app', + fallbackNS: 'explore', // fallback to explore namespace if the key is not found in the app namespace + fallbackLng: { + 'zh-Hans': ['zh-CN', 'en'], + 'zh-Hant': ['zh-TW', 'en'], + zh: ['zh-CN', 'en'], + default: ['en'] + }, + debug: import.meta.env.DEBUG, + // react i18next special options (optional) + react: { + // wait: true, + // useSuspense: false, + bindI18n: 'languageChanged loaded', + bindStore: 'added removed', + nsMode: 'default' + } + }) +``` + +## Development + ### Adding another codec **NOTE:** PRs adding an old IPLDFormat codec would need the old `blockcodec-to-ipld-format` tool, which has many out-of-date deps. We will only accept PRs for adding BlockCodec interface codecs. @@ -96,20 +251,6 @@ To add another hasher, you will need to update all locations containing the comm see https://github.com/ipfs/ipld-explorer-components/pull/395 for an example. -### Redux-bundler requirements - -These components use [redux-bundler](https://reduxbundler.com/), and your app will need to use a redux-bundler provider to propagate the properties and selectors. You can find a basic example in ./dev/devPage.jsx. - -In short, these components export two bundles found in ./src/bundles: `explore` and `heliaBundle`. The explore bundle and components herein have a few redux-bundler selector dependencies that you need to make sure exist and are called properly. - -| Dependent | redux-bundler selector | Notes | -|--------------------|------------------------|---------------------------------------------------------------------------------------------------------------| -| explore bundle | selectHeliaReady | The explore bundle depends on this selector so it knows when the Helia node is available for use | -| explore & other bundles | selectHelia | The explore bundle gets the Helia node via this selector | -| Main page (or any) | doInitHelia | A consuming app needs to call this selector to tell the bundle that provides the Helia node to instantiate it. | - -If you don't want to use the `heliaBundle`, you must adapt the selectors appropriately. - ## Contribute Feel free to dive in! [Open an issue](https://github.com/ipfs/ipld-explorer-components/issues/new) or submit PRs. diff --git a/dev/devPage.jsx b/dev/devPage.jsx deleted file mode 100644 index 58cdb70c..00000000 --- a/dev/devPage.jsx +++ /dev/null @@ -1,116 +0,0 @@ -/* globals globalThis */ -import { Buffer } from 'buffer' -import 'ipfs-css' -import React, { useEffect } from 'react' -import ReactDOM from 'react-dom' -import { I18nextProvider, withTranslation } from 'react-i18next' -import 'react-virtualized/styles.css' -import { composeBundles, createRouteBundle } from 'redux-bundler' -import { Provider as ReduxStoreProvider, connect } from 'redux-bundler-react' -import 'tachyons' -import heliaBundle from '../src/bundles/helia' -import '../src/components/loader/Loader.css' -import '../src/components/object-info/LinksTable.css' -import i18n from '../src/i18n' -import { exploreBundle, ExplorePage, StartExploringPage, IpldExploreForm, IpldCarExploreForm } from '../src/index' - -globalThis.Buffer = Buffer - -const routesBundle = createRouteBundle( - { - '/explore*': ExplorePage, - '/': StartExploringPage, - '': StartExploringPage - }, - { - routeInfoSelector: 'selectHash' - } -) -const getStore = composeBundles( - exploreBundle(), - routesBundle, - heliaBundle -) - -const HeaderComponent = ({ t }) => { - const activeColor = 'navy 0-100' - const inActiveColor = 'navy o-50' - const [exploreFormType, setExploreFormType] = React.useState('cid') - const [cidColor, setCidColor] = React.useState(activeColor) - const [carColor, setCarColor] = React.useState(inActiveColor) - - function handleOnChange (evt) { - setExploreFormType(evt.target.value) - if (evt.target.value === 'cid') { - setCidColor(activeColor) - setCarColor(inActiveColor) - } else { - setCidColor(inActiveColor) - setCarColor(activeColor) - } - } - - return ( - - - {/* */} - - - CID - CAR - - - - {exploreFormType === 'cid' ? : } - - - - { t('appName') } - - - - - - - - - - - ) -} - -const Header = withTranslation('explore')(HeaderComponent) - -const PageRenderer = connect( - 'selectRoute', - 'selectQueryObject', - 'doUpdateUrl', - 'doInitHelia', - (props) => { - const Page = props?.route - const { embed } = props.queryObject - const { doInitHelia } = props - useEffect(() => { - doInitHelia() - }, [doInitHelia]) - - return ( - <> - - - - - > - ) - } -) - -const App = () => ( - - - - - -) - -ReactDOM.render(, document.getElementById('root')) diff --git a/dev/devPage.tsx b/dev/devPage.tsx new file mode 100644 index 00000000..fb0a76d4 --- /dev/null +++ b/dev/devPage.tsx @@ -0,0 +1,110 @@ +/* globals globalThis */ +import 'ipfs-css' +import { Buffer } from 'buffer' +import React, { type MouseEvent, useEffect, useState } from 'react' +import { createRoot } from 'react-dom/client' +import { I18nextProvider, useTranslation } from 'react-i18next' +import 'tachyons' +import i18n from '../src/i18n.js' +import { ExplorePage, StartExploringPage, IpldExploreForm, IpldCarExploreForm, ExploreProvider, HeliaProvider } from '../src/index.js' + +globalThis.Buffer = globalThis.Buffer ?? Buffer + +const HeaderComponent: React.FC = () => { + const activeColor = 'navy 0-100' + const inActiveColor = 'navy o-50' + const [exploreFormType, setExploreFormType] = useState('cid') + const [cidColor, setCidColor] = useState(activeColor) + const [carColor, setCarColor] = useState(inActiveColor) + const { t } = useTranslation('explore') + + function handleOnChange (evt: MouseEvent): void { + const selectedType = evt.currentTarget.getAttribute('data-value') + if (selectedType == null) { + console.error('No data-value attribute found on the button') + return + } + setExploreFormType(selectedType) + + if (selectedType === 'cid') { + setCidColor(activeColor) + setCarColor(inActiveColor) + } else { + setCidColor(inActiveColor) + setCarColor(activeColor) + } + } + + return ( + + + {/* */} + + + CID + CAR + + + + {exploreFormType === 'cid' ? : } + + + + { t('appName') } + + + + {/* SVG content */} + + + + + + ) +} + +const PageRenderer = (): React.ReactElement => { + const [route, setRoute] = useState(window.location.hash.slice(1) ?? '/') + + useEffect(() => { + const onHashChange = (): void => { setRoute(window.location.hash.slice(1) ?? '/') } + window.addEventListener('hashchange', onHashChange) + return () => { window.removeEventListener('hashchange', onHashChange) } + }, []) + + const RenderPage: React.FC = () => { + switch (true) { + case route.startsWith('/explore'): + return + case route === '/': + default: + return + } + } + + return ( + + ) +} + +const App = (): React.ReactElement => { + return ( + + + + + + + ) +} + +const rootEl = document.getElementById('root') +if (rootEl == null) { + throw new Error('No root element found with the id "root"') +} +const root = createRoot(rootEl) +root.render( + + + +) diff --git a/index.html b/index.html index ff872cac..103a5779 100644 --- a/index.html +++ b/index.html @@ -14,11 +14,9 @@ - +