├── .aegir.js ├── .env ├── .gitattributes ├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── config.yml ├── dependabot.yml └── workflows │ ├── generated-pr.yml │ ├── js-test-and-release.yml │ ├── stale.yml │ └── tx-pull.yml ├── .gitignore ├── .npmignore ├── .npmrc ├── .storybook ├── main.ts └── preview.ts ├── .tx └── config ├── CHANGELOG.md ├── LICENSE ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── dev └── devPage.tsx ├── index.html ├── package-lock.json ├── package.json ├── public └── locales │ ├── ar │ └── explore.json │ ├── ca │ └── explore.json │ ├── cs │ └── explore.json │ ├── da │ └── explore.json │ ├── de │ └── explore.json │ ├── el │ └── explore.json │ ├── en │ └── explore.json │ ├── es │ └── explore.json │ ├── fr │ └── explore.json │ ├── id │ └── explore.json │ ├── it │ └── explore.json │ ├── ja-JP │ └── explore.json │ ├── ko-KR │ └── explore.json │ ├── nl │ └── explore.json │ ├── no │ └── explore.json │ ├── pl │ └── explore.json │ ├── pt-BR │ └── explore.json │ ├── pt │ └── explore.json │ ├── ro │ └── explore.json │ ├── ru │ └── explore.json │ ├── sv │ └── explore.json │ ├── tr │ └── explore.json │ ├── zh-CN │ └── explore.json │ └── zh-TW │ └── explore.json ├── src ├── components │ ├── ExplorePage.stories.tsx │ ├── ExplorePage.tsx │ ├── StartExploringPage.stories.tsx │ ├── StartExploringPage.tsx │ ├── about │ │ ├── AboutIpld.tsx │ │ └── ipld.svg │ ├── box │ │ └── Box.tsx │ ├── cid-info │ │ ├── CidInfo.stories.tsx │ │ ├── CidInfo.tsx │ │ └── decode-cid.ts │ ├── cid │ │ ├── Cid.stories.tsx │ │ └── Cid.tsx │ ├── error │ │ └── ErrorBoundary.tsx │ ├── explore │ │ ├── IpldCarExploreForm.stories.tsx │ │ ├── IpldCarExploreForm.tsx │ │ ├── IpldExploreErrorComponent.tsx │ │ ├── IpldExploreForm.stories.tsx │ │ ├── IpldExploreForm.tsx │ │ ├── spinner.svg │ │ └── upload.svg │ ├── graph-crumb │ │ ├── GraphCrumb.stories.tsx │ │ ├── GraphCrumb.tsx │ │ └── graph-crumb.test.tsx │ ├── graph │ │ ├── IpldGraph.stories.tsx │ │ ├── IpldGraph.tsx │ │ └── IpldGraphCytoscape.tsx │ ├── loader │ │ ├── component-loader.stories.tsx │ │ ├── component-loader.tsx │ │ ├── loader.module.css │ │ └── loader.tsx │ └── object-info │ │ ├── ObjectInfo.stories.tsx │ │ ├── ObjectInfo.tsx │ │ ├── fixtures │ │ ├── object-info-0-links.json │ │ ├── object-info-1240-links.json │ │ ├── object-info-36-links.json │ │ └── object-info-8-links.json │ │ ├── links-table.module.css │ │ └── links-table.tsx ├── forms.ts ├── i18n-decorator.tsx ├── i18n.ts ├── icons │ ├── GlyphSmallCancel.tsx │ └── StrokeIpld.tsx ├── index.ts ├── lib │ ├── base-importer.ts │ ├── cid.test.ts │ ├── cid.ts │ ├── codec-importer.ts │ ├── errors.ts │ ├── explore-page-suggestions.ts │ ├── extract-info.ts │ ├── get-codec-for-cid.ts │ ├── get-codec-name-from-code.ts │ ├── get-raw-block.ts │ ├── guards.ts │ ├── hash-importer.ts │ ├── helpers.ts │ ├── import-car.ts │ ├── init-helia.ts │ ├── normalise-dag-node.ts │ ├── normalize-dag-node.test.ts │ ├── parse-ipld-path.ts │ ├── resolve-ipld-path.test.ts │ ├── resolve-ipld-path.ts │ └── tours.tsx ├── pages.ts ├── providers │ ├── explore.tsx │ ├── helia.tsx │ └── index.ts └── types.d.ts ├── test ├── e2e │ ├── edge-cases.test.ts │ ├── fixtures │ │ ├── create-cid.ts │ │ ├── explore │ │ │ └── blocks │ │ │ │ ├── QmR2pm6hPxv7pEgNaPE477rVBNSZnbUgXsSn2R9RqK9tAH │ │ │ │ ├── QmSnuWmxptJZdLJpKRarxBMS2Ju2oANVrgbr2xWbie9b2D │ │ │ │ ├── QmT4hPa6EeeCaTAb4a6ddFf4Lk5da9C1f4nMBmMJgbAW3z │ │ │ │ ├── QmUh6QSTxDKX5qoNU1GoogbhTveQQV9JMeQjfFVchAtd5Q │ │ │ │ ├── QmVFemV13MkWS7xyDzcJbBd5qL31NXAHcsYk4wy4RhwHqh │ │ │ │ ├── QmVmf9vLEdWeBjh74kTibHVkim6iLsRXs5jhHzbSdWjoLt │ │ │ │ ├── QmXPqQjySvisWjE11dkrANGmfvUsmPN5RP9oWVRnR7RQuu │ │ │ │ ├── QmZA6h4vP17Ktw5vyMdSQNTvzsncQKDSifYwJznY461rY2 │ │ │ │ ├── QmawceGscqN4o8Y8Fv26UUmB454kn2bnkXV5tEQYc4jBd6 │ │ │ │ ├── QmbQDovX7wRe9ek7u6QXe9zgCXkTzoUSsTFJEkrYV1HrVR │ │ │ │ ├── Qmd1WaiaEBDe7H3a3U1CToaoaFsxUXEnmhw68ub5u1iY7Q │ │ │ │ ├── QmdmQXB2mzChmMeKY47C43LxUdg1NDJ5MWcKMKxDu7RgQm │ │ │ │ ├── QmeQtZfwuq6aWRarY9P3L9MWhZ6QTonDe9ahWECGBZjyEJ │ │ │ │ ├── README.md │ │ │ │ └── bafyreicds4picvqi46ljgw2eombkoifaftyjgd4abvfvledghftn2xnena │ │ ├── generate-fixtures.sh │ │ ├── load-block-fixtures.ts │ │ └── test-explore-cid.ts │ ├── main.test.ts │ ├── playwright.config.ts │ └── setup │ │ ├── global-setup.ts │ │ ├── global-teardown.ts │ │ └── ipfs-backend.ts └── unit │ ├── helia-mock.ts │ └── setup.ts ├── tsconfig.json ├── vite.config.ts └── vitest.config.js /.aegir.js: -------------------------------------------------------------------------------- 1 | // import copy from 'esbuild-plugin-copy' 2 | import fs from 'node:fs' 3 | import path from 'node:path' 4 | 5 | const copyPlugin = ({ext}) => { 6 | return { 7 | name: `copy-${ext}`, 8 | setup(build) { 9 | const srcDir = 'src' 10 | const destDir = 'dist/src' 11 | build.onEnd(() => { 12 | const copyFile = (src, dest) => { 13 | fs.mkdirSync(path.dirname(dest), { recursive: true }) 14 | fs.copyFileSync(src, dest) 15 | } 16 | 17 | const walkDir = (dir, callback) => { 18 | fs.readdirSync(dir).forEach(f => { 19 | const dirPath = path.join(dir, f) 20 | const isDirectory = fs.statSync(dirPath).isDirectory() 21 | isDirectory ? walkDir(dirPath, callback) : callback(path.join(dir, f)) 22 | }) 23 | } 24 | 25 | walkDir(srcDir, (filePath) => { 26 | if (filePath.endsWith(`.${ext}`)) { 27 | const relativePath = path.relative(srcDir, filePath) 28 | const destPath = path.join(destDir, relativePath) 29 | copyFile(filePath, destPath) 30 | } 31 | }) 32 | }) 33 | } 34 | } 35 | } 36 | /** @type {import('aegir').PartialOptions} */ 37 | export default { 38 | // TODO: fix build and test with aegir 39 | // test: { 40 | // build: false, 41 | // files: [ 42 | // 'dist/test/**/*.spec.{js,ts, jsx, tsx}', 43 | // ], 44 | // }, 45 | build: { 46 | config: { 47 | inject: [ 48 | './src/lib/browser-shims.js' 49 | ], 50 | bundle: true, 51 | loader: { 52 | '.js': 'jsx', 53 | '.ts': 'ts', 54 | '.tsx': 'tsx', 55 | '.jsx': 'jsx', 56 | '.svg': 'text', 57 | // '.css': 'css' 58 | '.woff': 'file', 59 | '.woff2': 'file', 60 | '.eot': 'file', 61 | '.otf': 'file', 62 | }, 63 | platform: 'browser', 64 | target: 'es2022', 65 | format: 'esm', 66 | metafile: true, 67 | plugins: [ 68 | copyPlugin({ext: 'css'}), 69 | copyPlugin({ext: 'svg'}), 70 | ], 71 | } 72 | }, 73 | lint: { 74 | files: [ 75 | 'src/**/*.{js,jsx,ts,tsx}', 76 | 'test/**/*.{js,jsx,ts,tsx}', 77 | 'dev/**/*.{js,jsx,ts,tsx}', 78 | // TODO: re-enable linting of stories. 79 | '!src/**/*.stories.*', 80 | ] 81 | }, 82 | dependencyCheck: { 83 | ignore: [ 84 | // .jsx files aren't checked properly. 85 | 'cytoscape', 86 | 'cytoscape-dagre', 87 | 'filesize', 88 | 'react-inspector', 89 | 'react-joyride', 90 | 'react-helmet', 91 | 92 | // storybook deps 93 | '@chromatic-com/storybook', 94 | '@storybook/addon-actions', 95 | '@storybook/addon-coverage', 96 | '@storybook/addon-interactions', 97 | '@storybook/addon-links', 98 | '@storybook/types', 99 | 100 | // problem with deps 101 | '@typescript-eslint/eslint-plugin', 102 | 103 | // scripts 104 | 'wait-on', 105 | 106 | // playwright deps 107 | 'tsx', 108 | 109 | // vite stuff 110 | 'rollup-plugin-node-polyfills', 111 | '@vitest/coverage-v8', 112 | 113 | // typescript plugins 114 | 'typescript-plugin-css-modules' 115 | ], 116 | productionIgnorePatterns: [ 117 | '.aegir.js', 118 | '.eslintrc.js', 119 | 'vite.config.ts', 120 | 'vitest.config.js', 121 | '/test', 122 | '.storybook', 123 | '**/*.stories.*', 124 | ] 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | SKIP_PREFLIGHT_CHECK=true 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Each line is a file pattern followed by one or more owners. 2 | 3 | # These owners will be the default owners for everything in 4 | # the repo. Unless a later match takes precedence 5 | # these owners will be requested for review when someone 6 | # opens a pull request. 7 | # All GUI Teams: @ipfs-shipyard/ipfs-gui @ipfs-shipyard/gui @ipfs/gui-dev @ipfs/wg-gui-ux 8 | * @ipfs/gui-dev 9 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: need/triage 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: need/triage 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/config.yml: -------------------------------------------------------------------------------- 1 | # Configuration for welcome - https://github.com/behaviorbot/welcome 2 | 3 | # Configuration for new-issue-welcome - https://github.com/behaviorbot/new-issue-welcome 4 | # Comment to be posted to on first time issues 5 | newIssueWelcomeComment: > 6 | Thank you for submitting your first issue to this repository! A maintainer 7 | will be here shortly to triage and review. 8 | 9 | In the meantime, please double-check that you have provided all the 10 | necessary information to make this process easy! Any information that can 11 | help save additional round trips is useful! We currently aim to give 12 | initial feedback within **two business days**. If this does not happen, feel 13 | free to leave a comment. 14 | 15 | Please keep an eye on how this issue will be labeled, as labels give an 16 | overview of priorities, assignments and additional actions requested by the 17 | maintainers: 18 | 19 | - "Priority" labels will show how urgent this is for the team. 20 | - "Status" labels will show if this is ready to be worked on, blocked, or in progress. 21 | - "Need" labels will indicate if additional input or analysis is required. 22 | 23 | Finally, remember to use https://discuss.ipfs.io if you just need general 24 | support. 25 | 26 | # Configuration for new-pr-welcome - https://github.com/behaviorbot/new-pr-welcome 27 | # Comment to be posted to on PRs from first time contributors in your repository 28 | newPRWelcomeComment: > 29 | Thank you for submitting this PR! 30 | 31 | A maintainer will be here shortly to review it. 32 | 33 | We are super grateful, but we are also overloaded! Help us by making sure 34 | that: 35 | 36 | * The context for this PR is clear, with relevant discussion, decisions 37 | and stakeholders linked/mentioned. 38 | 39 | * Your contribution itself is clear (code comments, self-review for the 40 | rest) and in its best form. Follow the [code contribution 41 | guidelines](https://github.com/ipfs/community/blob/master/CONTRIBUTING.md#code-contribution-guidelines) 42 | if they apply. 43 | 44 | Getting other community members to do a review would be great help too on 45 | complex PRs (you can ask in the chats/forums). If you are unsure about 46 | something, just leave us a comment. 47 | 48 | Next steps: 49 | 50 | * A maintainer will triage and assign priority to this PR, commenting on 51 | any missing things and potentially assigning a reviewer for high 52 | priority items. 53 | 54 | * The PR gets reviews, discussed and approvals as needed. 55 | 56 | * The PR is merged by maintainers when it has been approved and comments addressed. 57 | 58 | We currently aim to provide initial feedback/triaging within **two business 59 | days**. Please keep an eye on any labelling actions, as these will indicate 60 | priorities and status of your contribution. 61 | 62 | We are very grateful for your contribution! 63 | 64 | 65 | # Configuration for first-pr-merge - https://github.com/behaviorbot/first-pr-merge 66 | # Comment to be posted to on pull requests merged by a first time user 67 | # Currently disabled 68 | #firstPRMergeComment: "" 69 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | -------------------------------------------------------------------------------- /.github/workflows/generated-pr.yml: -------------------------------------------------------------------------------- 1 | name: Close Generated PRs 2 | 3 | on: 4 | schedule: 5 | - cron: '0 0 * * *' 6 | workflow_dispatch: 7 | 8 | permissions: 9 | issues: write 10 | pull-requests: write 11 | 12 | jobs: 13 | stale: 14 | uses: ipdxco/unified-github-workflows/.github/workflows/reusable-generated-pr.yml@v1 15 | -------------------------------------------------------------------------------- /.github/workflows/js-test-and-release.yml: -------------------------------------------------------------------------------- 1 | name: test & maybe release 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | workflow_dispatch: 9 | 10 | permissions: 11 | contents: write 12 | id-token: write 13 | packages: write 14 | pull-requests: write 15 | 16 | concurrency: 17 | group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.event_name == 'push' && github.sha || github.ref }} 18 | cancel-in-progress: true 19 | 20 | jobs: 21 | js-test-and-release: 22 | uses: ipdxco/unified-github-workflows/.github/workflows/js-test-and-release.yml@v1.0 23 | secrets: 24 | DOCKER_TOKEN: ${{ secrets.DOCKER_TOKEN }} 25 | DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} 26 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 27 | UCI_GITHUB_TOKEN: ${{ secrets.UCI_GITHUB_TOKEN }} 28 | CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} 29 | -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | name: Close Stale Issues 2 | 3 | on: 4 | schedule: 5 | - cron: '0 0 * * *' 6 | workflow_dispatch: 7 | 8 | permissions: 9 | issues: write 10 | pull-requests: write 11 | 12 | jobs: 13 | stale: 14 | uses: ipdxco/unified-github-workflows/.github/workflows/reusable-stale-issue.yml@v1 15 | -------------------------------------------------------------------------------- /.github/workflows/tx-pull.yml: -------------------------------------------------------------------------------- 1 | name: Transifex sync 2 | on: 3 | workflow_dispatch: 4 | schedule: 5 | - cron: '0 0 * * 0' 6 | 7 | jobs: 8 | tx-sync: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v3 12 | - name: Install Transifex client 13 | run: | 14 | curl -o- https://raw.githubusercontent.com/transifex/cli/master/install.sh | bash 15 | chmod +x ./tx 16 | - name: Pull translations from Transifex 17 | run: | 18 | ./tx -t ${{ secrets.TX_TOKEN }} pull -a -f 19 | - uses: stefanzweifel/git-auto-commit-action@8621497c8c39c72f3e2a999a26b4ca1b5058a842 20 | with: 21 | # Optional. Commit message for the created commit. 22 | # Defaults to "Apply automatic changes" 23 | commit_message: "chore: Pull transifex translations" 24 | 25 | # Optional. Local and remote branch name where commit is going to be pushed 26 | # to. Defaults to the current branch. 27 | # You might need to set `create_branch: true` if the branch does not exist. 28 | branch: i18n-sync 29 | 30 | # Optional. Options used by `git-commit`. 31 | # See https://git-scm.com/docs/git-commit#_options 32 | commit_options: '--no-verify --signoff' 33 | 34 | # Optional glob pattern of files which should be added to the commit 35 | # Defaults to all (.) 36 | # See the `pathspec`-documentation for git 37 | # - https://git-scm.com/docs/git-add#Documentation/git-add.txt-ltpathspecgt82308203 38 | # - https://git-scm.com/docs/gitglossary#Documentation/gitglossary.txt-aiddefpathspecapathspec 39 | file_pattern: public/locales 40 | 41 | # Optional. Local file path to the repository. 42 | # Defaults to the root of the repository. 43 | repository: . 44 | 45 | # Optional. Options used by `git-add`. 46 | # See https://git-scm.com/docs/git-add#_options 47 | add_options: '-A' 48 | 49 | # Optional. Options used by `git-push`. 50 | # See https://git-scm.com/docs/git-push#_options 51 | push_options: '--force' 52 | 53 | # Optional. Disable dirty check and always try to create a commit and push 54 | skip_dirty_check: true 55 | 56 | # Optional. Skip internal call to `git fetch` 57 | skip_fetch: true 58 | 59 | # Optional. Skip internal call to `git checkout` 60 | skip_checkout: true 61 | 62 | # Optional. Prevents the shell from expanding filenames. 63 | # Details: https://www.gnu.org/software/bash/manual/html_node/Filename-Expansion.html 64 | disable_globbing: true 65 | 66 | # Optional. Create given branch name in local and remote repository. 67 | create_branch: true 68 | - name: pull-request 69 | uses: repo-sync/pull-request@7e79a9f5dc3ad0ce53138f01df2fad14a04831c5 70 | with: 71 | source_branch: "i18n-sync" 72 | destination_branch: "master" 73 | pr_title: "chore: pull new translations" 74 | pr_body: "Automated PR created by .github/workflows/tx-pull.yml" 75 | pr_label: "area/i18n/translations" 76 | pr_draft: false 77 | pr_allow_empty: false 78 | github_token: ${{ secrets.GITHUB_TOKEN }} 79 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | 4 | # idea 5 | .idea/ 6 | .idea/workspace.xml 7 | .idea/tasks.xml 8 | .idea/dictionaries 9 | .idea/vcs.xml 10 | .idea/jsLibraryMappings.xml 11 | 12 | # dependencies 13 | node_modules 14 | 15 | 16 | # testing 17 | coverage 18 | 19 | # production 20 | build 21 | dist 22 | 23 | # misc 24 | .DS_Store 25 | .env.local 26 | .env.development.local 27 | .env.test.local 28 | .env.production.local 29 | 30 | npm-debug.log* 31 | yarn-debug.log* 32 | yarn-error.log* 33 | storybook-static 34 | .vscode 35 | test/e2e/state.json 36 | test-results 37 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /.storybook 2 | /build 3 | /ci 4 | .editorconfig 5 | .eslintrc 6 | .gitattributes 7 | .gitignore 8 | .travis.yml 9 | Makefile 10 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true 2 | -------------------------------------------------------------------------------- /.storybook/main.ts: -------------------------------------------------------------------------------- 1 | import type { StorybookConfig } from '@storybook/react-vite'; 2 | import { mergeConfig } from 'vite'; 3 | 4 | import viteConfig from '../vite.config'; 5 | 6 | const config: StorybookConfig = { 7 | stories: ['../src/**/*.stories.@(js|jsx|ts|tsx)'], 8 | 9 | addons: [ 10 | '@storybook/addon-links', 11 | '@storybook/addon-interactions', 12 | '@storybook/addon-coverage', 13 | '@chromatic-com/storybook' 14 | ], 15 | 16 | framework: { 17 | name: '@storybook/react-vite', 18 | options: {}, 19 | }, 20 | 21 | // async viteFinal(config) { 22 | // // Merge custom configuration into the default config 23 | // return mergeConfig(config, viteConfig); 24 | // }, 25 | typescript: { 26 | // reactDocgen: 'react-docgen-typescript' 27 | reactDocgen: false 28 | // reactDocgenTypescriptOptions: { 29 | 30 | // } 31 | }, 32 | 33 | // docs: {} 34 | }; 35 | export default config; 36 | -------------------------------------------------------------------------------- /.storybook/preview.ts: -------------------------------------------------------------------------------- 1 | import { type Preview } from '@storybook/react'; 2 | 3 | // import CSS files 4 | import 'ipfs-css' 5 | import 'tachyons' 6 | 7 | const preview: Preview = { 8 | // tags: ['autodocs'] 9 | }; 10 | 11 | export default preview; 12 | -------------------------------------------------------------------------------- /.tx/config: -------------------------------------------------------------------------------- 1 | [main] 2 | host = https://www.transifex.com 3 | lang_map = bn_IN: bn-IN, zh_CN: zh-CN, zh_HK: zh-HK, pt_PT: pt-PT, ja_JP: ja-JP, fa_IR: fa-IR, zh_TW: zh-TW, ko_KR: ko-KR, pt_BR: pt-BR, hi_IN: hi-IN, he_IL: he-IL 4 | 5 | [o:ipfs:p:ipld-explorer:r:explore-json] 6 | file_filter = public/locales//explore.json 7 | source_file = public/locales/en/explore.json 8 | source_lang = en 9 | type = KEYVALUEJSON 10 | minimum_perc = 35 11 | 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This project is dual licensed under MIT and Apache-2.0. 2 | 3 | MIT: https://www.opensource.org/licenses/mit 4 | Apache-2.0: https://www.apache.org/licenses/license-2.0 5 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at 2 | 3 | http://www.apache.org/licenses/LICENSE-2.0 4 | 5 | Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 6 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Dev view for ipld-explorer-components only 10 | 11 | 12 | 15 |
16 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /public/locales/ar/explore.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": "مستكشف IPLD", 3 | "tabName": "استكشف", 4 | "homeLink": "الصفحة الرئيسية", 5 | "UpdateAvailable": { 6 | "paragraph1": "يتوفر إصدار جديد من مستكشف IPLD ؛<1> أعد التحميل من فضلك." 7 | }, 8 | "StartExploringPage": { 9 | "title": "استكشاف - IPLD", 10 | "header": "استكشاف غابة ميركل", 11 | "leadParagraph": "قم بلصق CID في المربع لجلب عقدة IPLD التي تتناولها ، أو اختر مجموعة بيانات مميزة.", 12 | "noDataAvailable": "لا توجد بيانات متاحة" 13 | }, 14 | "AboutIpld": { 15 | "paragraph1":" <0> IPLD هو <1> نموذج البيانات للويب القابل للمحتوى. يسمح لنا بمعالجة جميع تراكيب البيانات المرتبطة بالتجزئة على أنها مجموعات فرعية من مساحة المعلومات الموحدة ، وتوحيد جميع نماذج البيانات التي تربط البيانات بالتجزئة مثل أمثلة IPLD.", 16 | "paragraph2": "<0> أصبح معالجة المحتوى من خلال التجزئة وسيلة شائعة الاستخدام لربط البيانات في الأنظمة الموزعة ، من blockchains التي تعمل على العملات الرقمية المفضلة لديك ، إلى عمليات دعم التعليمات البرمجية ، إلى محتوى الويب بشكل عام. ومع ذلك ، في حين تعتمد كل هذه الأدوات على بعض الأوليات الشائعة ، فإن هياكل البيانات الأساسية الخاصة بها غير قابلة للتشغيل المتبادل. ", 17 | "paragraph3": "<0> أدخل IPLD: مساحة اسم واحدة لجميع البروتوكولات المستوحاة من التجزئة. من خلال IPLD ، يمكن اجتياز الروابط عبر البروتوكولات ، مما يسمح لك باستكشاف البيانات بغض النظر عن البروتوكول الأساسي. " 18 | }, 19 | "ExplorePage": { 20 | "title": "استكشاف - IPLD" 21 | }, 22 | "IpldExploreForm": { 23 | "explore": "استكشف" 24 | }, 25 | "IpldCarExploreForm": { 26 | "imports": " استيراد", 27 | "uploadCarFile": "تحميل ملف CAR" 28 | }, 29 | "CidInfo": { 30 | "header": "معلومات CID", 31 | "hashDigest": "استوعب تجزئة" 32 | }, 33 | "ObjectInfo": { 34 | "publicGateway": "View on Public Gateway", 35 | "privateGateway": "View on Local Gateway" 36 | }, 37 | "base": "أساس", 38 | "version": "الاصدار", 39 | "codec": "برنامج الترميز", 40 | "multihash": "تجزئة متعددة", 41 | "tour": { 42 | "projects": { 43 | "title": "مجموعات البيانات المميزة", 44 | "paragraph1": "استكشف مجموعات البيانات المميزة أو الصق CID لمعرفة كيفية هيكلة هذه البيانات وربطها عبر البروتوكولات." 45 | }, 46 | "explorer": { 47 | "step1": { 48 | "title": "مستكشف IPLD", 49 | "paragraph1": "تبدأ رحلتك مع مستكشف IPLD من هنا. انقر فوق \"التالي\" لمعرفة المزيد." 50 | }, 51 | "step2": { 52 | "title": "فتات ", 53 | "paragraph1": "المسار الذي يؤدي إلى هذه العقدة.", 54 | "paragraph2": "انقر عليها للتنقل إلى والديها أو أطفالها." 55 | }, 56 | "step3": { 57 | "title": "معلومات العقدة", 58 | "paragraph1": "هنا لديك معلومات تفصيلية عن العقدة ويمكنك فتحها في مدخل IPFS.", 59 | "paragraph2": "إذا كانت العقدة عبارة عن دليل ، يمكنك رؤية أطفالها والانتقال إليهم." 60 | }, 61 | "step4": { 62 | "title": "معلومات CID", 63 | "paragraph1": "CID المتحللة حتى تتمكن من معرفة معناها." 64 | }, 65 | "step5": { 66 | "title": "التمثيل البصري", 67 | "paragraph1": "تمثيل مرئي للعقدة وأولادها." 68 | } 69 | } 70 | }, 71 | "errors": { 72 | "BlockFetchTimeoutError": "Failed to fetch content in {timeout}s. Please refresh the page to retry or try a different CID." 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /public/locales/ca/explore.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": "Explorador d'IPLD", 3 | "tabName": "Explora", 4 | "homeLink": "Inici", 5 | "UpdateAvailable": { 6 | "paragraph1": "A new version of IPLD Explorer is available; <1>please reload." 7 | }, 8 | "StartExploringPage": { 9 | "title": "Explora - IPLD", 10 | "header": "Explora el bosc de Merkle", 11 | "leadParagraph": "Enganxa un CID al quadre de text per trobar el node IPLD al que es refereix, o tria un conjunt de dades acceptat.", 12 | "noDataAvailable": "No hi ha dades disponibles" 13 | }, 14 | "AboutIpld": { 15 | "paragraph1":"<0>IPLD is <1>the data model of the content-addressable web. It allows us to treat all hash-linked data structures as subsets of a unified information space, unifying all data models that link data with hashes as instances of IPLD.", 16 | "paragraph2": "<0>Content addressing through hashes has become a widely-used means of connecting data in distributed systems, from the blockchains that run your favorite cryptocurrencies, to the commits that back your code, to the web’s content at large. Yet, whilst all of these tools rely on some common primitives, their specific underlying data structures are not interoperable.", 17 | "paragraph3": "<0>Enter IPLD: a single namespace for all hash-inspired protocols. Through IPLD, links can be traversed across protocols, allowing you to explore data regardless of the underlying protocol." 18 | }, 19 | "ExplorePage": { 20 | "title": "Explorant - IPLD" 21 | }, 22 | "IpldExploreForm": { 23 | "explore": "Explora" 24 | }, 25 | "IpldCarExploreForm": { 26 | "imports": "Importa", 27 | "uploadCarFile": "Upload CAR File" 28 | }, 29 | "CidInfo": { 30 | "header": "Info del CID", 31 | "hashDigest": "Hash digest" 32 | }, 33 | "ObjectInfo": { 34 | "publicGateway": "View on Public Gateway", 35 | "privateGateway": "View on Local Gateway" 36 | }, 37 | "base": "base", 38 | "version": "versió", 39 | "codec": "codec", 40 | "multihash": "multihash", 41 | "tour": { 42 | "projects": { 43 | "title": "Conjunt de dades acceptats", 44 | "paragraph1": "Explore the featured datasets or paste a CID to see how that data is structured and linked across protocols." 45 | }, 46 | "explorer": { 47 | "step1": { 48 | "title": "Explorador d'IPLD", 49 | "paragraph1": "Your journey with IPLD Explorer starts here. Click \"Next\" to learn more." 50 | }, 51 | "step2": { 52 | "title": "Ruta de navegació", 53 | "paragraph1": "The path that leads to this node.", 54 | "paragraph2": "Click on it to traverse to its parents or children." 55 | }, 56 | "step3": { 57 | "title": "Info del node", 58 | "paragraph1": "Aquí tens informació detallada sobre el node i el pots obrir al getaway d'IPFS.", 59 | "paragraph2": "Si el node és un directori pots veure i navegar en els seus fills." 60 | }, 61 | "step4": { 62 | "title": "Info del CID", 63 | "paragraph1": "El CID descomposat per tal que puguins aprendre el seu significat." 64 | }, 65 | "step5": { 66 | "title": "Representació visual", 67 | "paragraph1": "Una representació visual del node i els seus fills." 68 | } 69 | } 70 | }, 71 | "errors": { 72 | "BlockFetchTimeoutError": "Failed to fetch content in {timeout}s. Please refresh the page to retry or try a different CID." 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /public/locales/cs/explore.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": "IPLD Průzkumník", 3 | "tabName": "Prozkoumat", 4 | "homeLink": "Hlavní stránka", 5 | "UpdateAvailable": { 6 | "paragraph1": "K dispozici je nová verze aplikace IPLD Explorer; <1>prosím obnovte." 7 | }, 8 | "StartExploringPage": { 9 | "title": "Průzkumník - IPLD", 10 | "header": "Prozkoumejte Merkelův les", 11 | "leadParagraph": "Pro zobrazeni informací o IPLD uzlu vložte do pole vaše CID nebo si vyberte z několika příkladů.", 12 | "noDataAvailable": "Nejsou k dispozici žádná data" 13 | }, 14 | "AboutIpld": { 15 | "paragraph1":"<0>IPLD je <1>datový model obsahově-adresovaného webu. Umožňuje nám to zacházet se všemi datovými strukturami spojenými s hashem jako s podmnožinami jednotného informačního prostoru a sjednotit všechny datové modely, které propojují data s hashemi, jako instance IPLD.", 16 | "paragraph2": "<0>Obsahové adresování pomocí hashů se stalo široce používaným prostředkem pro připojení dat v distribuovaných systémech, od blockchainů, které provozují vaše oblíbené kryptoměny, až po commity, které váš kód zálohují, až po obsah celého webu. Přestože všechny tyto nástroje spoléhají na některé běžné primitivy, jejich specifické základní datové struktury nejsou interoperabilní.", 17 | "paragraph3": "<0>Zadejte IPLD: jeden jmenný prostor pro všechny protokoly inspirované hashem. Prostřednictvím IPLD lze odkazy procházet přes protokoly, což vám umožní prozkoumávat data bez ohledu na základní protokol." 18 | }, 19 | "ExplorePage": { 20 | "title": "Prozkoumáváme - IPLD" 21 | }, 22 | "IpldExploreForm": { 23 | "explore": "Prozkoumat" 24 | }, 25 | "IpldCarExploreForm": { 26 | "imports": "Import", 27 | "uploadCarFile": "Nahrát soubor CAR" 28 | }, 29 | "CidInfo": { 30 | "header": "CID info", 31 | "hashDigest": "Hash digest" 32 | }, 33 | "ObjectInfo": { 34 | "publicGateway": "Zobrazení na veřejné bráně", 35 | "privateGateway": "Zobrazení na místní bráně" 36 | }, 37 | "base": "základ", 38 | "version": "verze", 39 | "codec": "kodek", 40 | "multihash": "multihash", 41 | "tour": { 42 | "projects": { 43 | "title": "Doporučené soubory dat", 44 | "paragraph1": "Prozkoumejte doporučené soubory dat nebo vložte CID a podívejte se, jak jsou tato data strukturována a propojena napříč protokoly." 45 | }, 46 | "explorer": { 47 | "step1": { 48 | "title": "IPLD Průzkumník", 49 | "paragraph1": "Vaše cesta s IPLD Explorer začíná zde. Klikněte na \"Další\" a dozvíte se více." 50 | }, 51 | "step2": { 52 | "title": "Cesta", 53 | "paragraph1": "Cesta, která vede k tomuto uzlu.", 54 | "paragraph2": "Kliknutím na něj přejdete k rodičům nebo dětem." 55 | }, 56 | "step3": { 57 | "title": "Informace o uzlu", 58 | "paragraph1": "Zde máte podrobné informace o uzlu a můžete je otevřít v bráně IPFS.", 59 | "paragraph2": "Pokud je uzel adresářem, můžete vidět jeho děti a přejít na ně." 60 | }, 61 | "step4": { 62 | "title": "CID info", 63 | "paragraph1": "Rozložený CID, takže se můžete naučit jeho význam." 64 | }, 65 | "step5": { 66 | "title": "Vizuální reprezentace", 67 | "paragraph1": "Vizuální reprezentace uzlu a jeho dětí." 68 | } 69 | } 70 | }, 71 | "errors": { 72 | "BlockFetchTimeoutError": "Nepodařilo se načíst obsah do {timeout} s. Obnovte prosím stránku a zkuste to znovu nebo zkuste jiný CID." 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /public/locales/da/explore.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": "IPLD Udforsker", 3 | "tabName": "Udforsk", 4 | "homeLink": "Hjem", 5 | "UpdateAvailable": { 6 | "paragraph1": "A new version of IPLD Explorer is available; <1>please reload." 7 | }, 8 | "StartExploringPage": { 9 | "title": "Udforsk - IPLD", 10 | "header": "Udforsk Merkle-skoven", 11 | "leadParagraph": "Indsæt et CID i boksen for at fremsøge IPLD-klienten som adresseres, eller vælg fra et udstillet datasæt.", 12 | "noDataAvailable": "Ingen data tilgængelige" 13 | }, 14 | "AboutIpld": { 15 | "paragraph1":"<0>IPLD er <1>datamodellen for det indholds-adresserbare net. Hvilket gør os i stand til at behandle alle hash-linkede data-strukturer som undergrupper hjemhørende under et fælles informations-rum, der kan kombinere alle datamodeller som linker til data via hashes, som instanser af IPLD ", 16 | "paragraph2": "<0>Indholdsadressering gennem hashes er blevet en udbredt måde at forbinde data på tværs af distribuerede systemer. Alt lige fra den blockchain der udgør grundlaget for din ynglings cryptovaluta, til indtjekningen af din kodebase, til udveksling af net-indhold på stor skala. Selvom at alle disse værktøjer er bygget på samme fundamentale byggeklodser, så er de underliggende data strukturer ikke kompatible.", 17 | "paragraph3": "<0>Træd ind IPLD's verden: Et fælles navngivningsrum til brug for alle hash-inspirerede protokoller. Ved at benytte IPLD, kan links forfølges på tværs af protokoller, hvilket giver dig mulighed for at udforske data uanset hvilken underliggende protokol der benyttes." 18 | }, 19 | "ExplorePage": { 20 | "title": "Udforsker - IPLD" 21 | }, 22 | "IpldExploreForm": { 23 | "explore": "Udforsk" 24 | }, 25 | "IpldCarExploreForm": { 26 | "imports": "Import", 27 | "uploadCarFile": "Upload CAR File" 28 | }, 29 | "CidInfo": { 30 | "header": "CID info", 31 | "hashDigest": "Hash sum" 32 | }, 33 | "ObjectInfo": { 34 | "publicGateway": "View on Public Gateway", 35 | "privateGateway": "View on Local Gateway" 36 | }, 37 | "base": "base", 38 | "version": "version", 39 | "codec": "codec", 40 | "multihash": "multihash", 41 | "tour": { 42 | "projects": { 43 | "title": "Featured datasets", 44 | "paragraph1": "Explore the featured datasets or paste a CID to see how that data is structured and linked across protocols." 45 | }, 46 | "explorer": { 47 | "step1": { 48 | "title": "IPLD Udforsker", 49 | "paragraph1": "Your journey with IPLD Explorer starts here. Click \"Next\" to learn more." 50 | }, 51 | "step2": { 52 | "title": "Sti", 53 | "paragraph1": "The path that leads to this node.", 54 | "paragraph2": "Click on it to traverse to its parents or children." 55 | }, 56 | "step3": { 57 | "title": "Node info", 58 | "paragraph1": "Here you have detailed info about the node and you can open it in the IPFS gateway.", 59 | "paragraph2": "If the node is a directory you can see its children and navigate to them." 60 | }, 61 | "step4": { 62 | "title": "CID info", 63 | "paragraph1": "The decomposed CID so you can learn its meaning." 64 | }, 65 | "step5": { 66 | "title": "Visual representation", 67 | "paragraph1": "A visual representation of the node and its children." 68 | } 69 | } 70 | }, 71 | "errors": { 72 | "BlockFetchTimeoutError": "Failed to fetch content in {timeout}s. Please refresh the page to retry or try a different CID." 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /public/locales/de/explore.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": "IPLD-Explorer", 3 | "tabName": "Erkunden", 4 | "homeLink": "Home", 5 | "UpdateAvailable": { 6 | "paragraph1": "Eine neue Version des IPLD Explorer ist verfügbar. <1>Bitte neu laden." 7 | }, 8 | "StartExploringPage": { 9 | "title": "Erkunden – IPLD", 10 | "header": "Merkle-Wald erkunden", 11 | "leadParagraph": "Füge eine CID in das Feld ein, um den von ihr adressierten IPLD-Knoten abzurufen, oder wähle einen vorgestellten Datensatz.", 12 | "noDataAvailable": "Keine Daten verfügbar" 13 | }, 14 | "AboutIpld": { 15 | "paragraph1":"<0>IPLD ist <1>das Datenmodell des inhaltsadressierbaren Webs. Es erlaubt uns, alle Hash-verknüpften Datenstrukturen als Teilmengen eines einheitlichen Informationsraumes zu behandeln und alle Datenmodelle zu vereinheitlichen, die Daten mit Hashes als Instanzen von IPLD verknüpfen.", 16 | "paragraph2": "<0>Die Adressierung von Inhalten durch Hashes ist zu einem weit verbreiteten Mittel geworden, um Daten in verteilten Systemen zu verbinden – von den Blockchains, die Deine bevorzugten Kryptowährungen ausführen, über die Commits, die Deinen Code sichern, bis hin zum gesamten Webinhalt. Obwohl alle diese Werkzeuge auf einigen gemeinsamen Grundelementen aufbauen, sind ihre spezifischen zugrunde liegenden Datenstrukturen trotzdem nicht kompatibel.", 17 | "paragraph3": "<0>Wir präsentieren IPLD: ein einziger Namensraum für alle Hash-inspirierten Protokolle. Über IPLD können Links protokollübergreifend durchlaufen werden, sodass Du die Daten unabhängig vom zugrunde liegenden Protokoll abrufen kannst." 18 | }, 19 | "ExplorePage": { 20 | "title": "Erkundung – IPLD" 21 | }, 22 | "IpldExploreForm": { 23 | "explore": "Erkunden" 24 | }, 25 | "IpldCarExploreForm": { 26 | "imports": "Importieren", 27 | "uploadCarFile": "CAR Datei hochladen" 28 | }, 29 | "CidInfo": { 30 | "header": "CID Informationen", 31 | "hashDigest": "Hash-Extrakt" 32 | }, 33 | "ObjectInfo": { 34 | "publicGateway": "Auf öffentlichem Gateway ansehen", 35 | "privateGateway": "Auf lokalem Gateway ansehen" 36 | }, 37 | "base": "Base", 38 | "version": "Version", 39 | "codec": "Codec", 40 | "multihash": "Multihash", 41 | "tour": { 42 | "projects": { 43 | "title": "Empfohlene Datensätze", 44 | "paragraph1": "Sieh Dir die vorgestellten Datensätze an oder füge eine CID ein, um zu sehen, wie diese Daten protokollübergreifend strukturiert und verknüpft sind." 45 | }, 46 | "explorer": { 47 | "step1": { 48 | "title": "IPLD-Explorer", 49 | "paragraph1": "Deine Reise mit dem IPLD Explorer beginnt hier. Klicke \"Weiter\" um mehr zu erfahren." 50 | }, 51 | "step2": { 52 | "title": "Breadcrumbs", 53 | "paragraph1": "Der Pfad, der zu diesem Knoten führt.", 54 | "paragraph2": "Klicke hier, um zu den Eltern oder Kindern zu gelangen." 55 | }, 56 | "step3": { 57 | "title": "Node informationen", 58 | "paragraph1": "Hier findest Du detaillierte Informationen über den Node. Du kannst es außerdem im IPFS Gateway öffnen.", 59 | "paragraph2": "Wenn der Knoten ein Verzeichnis ist, kannst Du seine Kinder sehen und zu ihnen navigieren." 60 | }, 61 | "step4": { 62 | "title": "CID Informationen", 63 | "paragraph1": "Die zerlegte CID, damit Du seine Bedeutung sehen kannst." 64 | }, 65 | "step5": { 66 | "title": "Visuelle Repräsentation", 67 | "paragraph1": "Eine visuelle Repräsentation des Knotens und seiner Kinder." 68 | } 69 | } 70 | }, 71 | "errors": { 72 | "BlockFetchTimeoutError": "Der Inhalt konnte innerhalb von {timeout}s nicht abgerufen werden. Lade bitte die Seite neu um es erneut zu versuchen oder versuche es mit einer anderen CID." 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /public/locales/el/explore.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": "Εξερευνητής IPLD", 3 | "tabName": "Εξερεύνηση", 4 | "homeLink": "Αρχική", 5 | "UpdateAvailable": { 6 | "paragraph1": "A new version of IPLD Explorer is available; <1>please reload." 7 | }, 8 | "StartExploringPage": { 9 | "title": "Εξερεύνηση - IPLD", 10 | "header": "Explore the Merkle Forest", 11 | "leadParagraph": "Paste a CID into the box to fetch the IPLD node it addresses, or choose a featured dataset.",, 12 | "noDataAvailable": "Δεν υπάρχουν διαθέσιμα δεδομένα" 13 | "noDataAvailable": "No data available" 14 | }, 15 | "AboutIpld": { 16 | "paragraph1":"<0>IPLD is <1>the data model of the content-addressable web. It allows us to treat all hash-linked data structures as subsets of a unified information space, unifying all data models that link data with hashes as instances of IPLD.", 17 | "paragraph2": "<0>Content addressing through hashes has become a widely-used means of connecting data in distributed systems, from the blockchains that run your favorite cryptocurrencies, to the commits that back your code, to the web’s content at large. Yet, whilst all of these tools rely on some common primitives, their specific underlying data structures are not interoperable.", 18 | "paragraph3": "<0>Enter IPLD: a single namespace for all hash-inspired protocols. Through IPLD, links can be traversed across protocols, allowing you to explore data regardless of the underlying protocol." 19 | }, 20 | "ExplorePage": { 21 | "title": "Εξερεύνηση - IPLD" 22 | }, 23 | "IpldExploreForm": { 24 | "explore": "Εξερεύνηση" 25 | }, 26 | "IpldCarExploreForm": { 27 | "imports": "Εισαγωγή", 28 | "uploadCarFile": "Ανέβασμα Αρχείου CAR" 29 | }, 30 | "CidInfo": { 31 | "header": "Στοιχεία CID", 32 | "hashDigest": "Hash digest" 33 | }, 34 | "ObjectInfo": { 35 | "publicGateway": "Προβολή στη Δημόσια Πύλη", 36 | "privateGateway": "Προβολή στην Τοπική Πύλη" 37 | }, 38 | "base": "βάση", 39 | "version": "έκδοση", 40 | "codec": "κωδικοποιητής", 41 | "multihash": "multihash", 42 | "tour": { 43 | "projects": { 44 | "title": "Featured datasets", 45 | "paragraph1": "Explore the featured datasets or paste a CID to see how that data is structured and linked across protocols." 46 | }, 47 | "explorer": { 48 | "step1": { 49 | "title": "Εξερευνητής IPLD", 50 | "paragraph1": "Your journey with IPLD Explorer starts here. Click \"Next\" to learn more." 51 | }, 52 | "step2": { 53 | "title": "Breadcrumbs", 54 | "paragraph1": "Η διαδρομή προς αυτόν τον κόμβο.", 55 | "paragraph2": "Click on it to traverse to its parents or children." 56 | }, 57 | "step3": { 58 | "title": "Πληροφορίες κόμβου", 59 | "paragraph1": "Here you have detailed info about the node and you can open it in the IPFS gateway.", 60 | "paragraph2": "Αν ο κόμβος είναι φάκελος, μπορείτε να δείτε και να περιηγηθείτε στα στοιχεία του." 61 | }, 62 | "step4": { 63 | "title": "Στοιχεία CID", 64 | "paragraph1": "The decomposed CID so you can learn its meaning." 65 | }, 66 | "step5": { 67 | "title": "Οπτική αναπαράσταση", 68 | "paragraph1": "Μια οπτική αναπαράσταση του κόμβου και των στοιχείων του." 69 | } 70 | } 71 | }, 72 | "errors": { 73 | "BlockFetchTimeoutError": "Failed to fetch content in {timeout}s. Please refresh the page to retry or try a different CID." 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /public/locales/en/explore.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": "IPLD Explorer", 3 | "tabName": "Explore", 4 | "homeLink": "Home", 5 | "UpdateAvailable": { 6 | "paragraph1": "A new version of IPLD Explorer is available; <1>please reload." 7 | }, 8 | "StartExploringPage": { 9 | "title": "Explore - IPLD", 10 | "header": "Explore the Merkle Forest", 11 | "leadParagraph": "Paste a CID into the box to fetch the IPLD node it addresses, or choose a featured dataset.", 12 | "noDataAvailable": "No data available" 13 | }, 14 | "AboutIpld": { 15 | "paragraph1":"<0>IPLD is <1>the data model of the content-addressable web. It allows us to treat all hash-linked data structures as subsets of a unified information space, unifying all data models that link data with hashes as instances of IPLD.", 16 | "paragraph2": "<0>Content addressing through hashes has become a widely-used means of connecting data in distributed systems, from the blockchains that run your favorite cryptocurrencies, to the commits that back your code, to the web’s content at large. Yet, whilst all of these tools rely on some common primitives, their specific underlying data structures are not interoperable.", 17 | "paragraph3": "<0>Enter IPLD: a single namespace for all hash-inspired protocols. Through IPLD, links can be traversed across protocols, allowing you to explore data regardless of the underlying protocol." 18 | }, 19 | "ExplorePage": { 20 | "title": "Exploring - IPLD" 21 | }, 22 | "IpldExploreForm": { 23 | "explore": "Explore" 24 | }, 25 | "IpldCarExploreForm": { 26 | "imports": "Import", 27 | "uploadCarFile": "Upload CAR File" 28 | }, 29 | "CidInfo": { 30 | "header": "CID info", 31 | "hashDigest": "Hash digest" 32 | }, 33 | "ObjectInfo": { 34 | "publicGateway": "View on Public Gateway", 35 | "privateGateway": "View on Local Gateway" 36 | }, 37 | "base": "base", 38 | "version": "version", 39 | "codec": "codec", 40 | "multihash": "multihash", 41 | "tour": { 42 | "projects": { 43 | "title": "Featured datasets", 44 | "paragraph1": "Explore the featured datasets or paste a CID to see how that data is structured and linked across protocols." 45 | }, 46 | "explorer": { 47 | "step1": { 48 | "title": "IPLD Explorer", 49 | "paragraph1": "Your journey with IPLD Explorer starts here. Click \"Next\" to learn more." 50 | }, 51 | "step2": { 52 | "title": "Breadcrumbs", 53 | "paragraph1": "The path that leads to this node.", 54 | "paragraph2": "Click on it to traverse to its parents or children." 55 | }, 56 | "step3": { 57 | "title": "Node info", 58 | "paragraph1": "Here you have detailed info about the node and you can open it in the IPFS gateway.", 59 | "paragraph2": "If the node is a directory you can see its children and navigate to them." 60 | }, 61 | "step4": { 62 | "title": "CID info", 63 | "paragraph1": "The decomposed CID so you can learn its meaning." 64 | }, 65 | "step5": { 66 | "title": "Visual representation", 67 | "paragraph1": "A visual representation of the node and its children." 68 | } 69 | } 70 | }, 71 | "errors": { 72 | "BlockFetchTimeoutError": "Failed to fetch content in {timeout}s. Please refresh the page to retry or try a different CID.", 73 | "BlockFetchError": "Failed to fetch content: {error}. This could mean the content is not available on the network or there are connectivity issues.", 74 | "CidSyntaxError": "Invalid CID format: {cid}. Please ensure you are using a valid CID.", 75 | "checkIpfsNetwork": "Check content availability on IPFS Network", 76 | "troubleshootingTips": { 77 | "title": "Troubleshooting Tips", 78 | "refresh": "Try refreshing the page", 79 | "checkConnection": "Check your internet connection", 80 | "tryLater": "Try again later - the content may still be propagating through the network", 81 | "checkCidSyntax": "Verify that your CID is formatted correctly or that your CAR file is valid" 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /public/locales/es/explore.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": "Explorador IPLD", 3 | "tabName": "Explorar", 4 | "homeLink": "Inicio", 5 | "UpdateAvailable": { 6 | "paragraph1": "A new version of IPLD Explorer is available; <1>please reload." 7 | }, 8 | "StartExploringPage": { 9 | "title": "Explorar - IPLD", 10 | "header": "Explorar el Merkle Forest", 11 | "leadParagraph": "Pegue un CID en el cuadro para obtener el nodo de IPLD al que se dirige, o elija un conjunto de datos destacado.", 12 | "noDataAvailable": "No hay datos disponibles" 13 | }, 14 | "AboutIpld": { 15 | "paragraph1":"<0>IPLD es <1>el modelo de datos de la web direccionable por contenido. Nos permite tratar todas las estructuras de datos vinculados con hash como subconjuntos de un espacio de información unificado, unificando todos los modelos de datos que vinculan datos con hashes como instancias de IPLD.", 16 | "paragraph2": "<0>El direccionamiento de contenido a través de hashes se ha convertido en un medio ampliamente utilizado para conectar datos en sistemas distribuidos, desde las cadenas de bloques que ejecutan sus criptomonedas favoritas, hasta las confirmaciones que respaldan su código y el contenido de la web en general. Sin embargo, si bien todas estas herramientas se basan en algunas primitivas comunes, sus estructuras de datos subyacentes específicas no son interoperables.", 17 | "paragraph3": "<0>Ingresar IPLD: un espacio de nombres único para todos los protocolos inspirados en hash. A través de IPLD, los enlaces pueden atravesarse a través de protocolos, lo que le permite explorar datos independientemente del protocolo subyacente." 18 | }, 19 | "ExplorePage": { 20 | "title": "Explorando - IPLD" 21 | }, 22 | "IpldExploreForm": { 23 | "explore": "Explorar" 24 | }, 25 | "IpldCarExploreForm": { 26 | "imports": "Importar", 27 | "uploadCarFile": "Upload CAR File" 28 | }, 29 | "CidInfo": { 30 | "header": "Información del CID", 31 | "hashDigest": "Hash digest" 32 | }, 33 | "ObjectInfo": { 34 | "publicGateway": "View on Public Gateway", 35 | "privateGateway": "View on Local Gateway" 36 | }, 37 | "base": "base", 38 | "version": "versión", 39 | "codec": "codec", 40 | "multihash": "multihash", 41 | "tour": { 42 | "projects": { 43 | "title": "Conjuntos de datos destacados", 44 | "paragraph1": "Explore los conjuntos de datos destacados o pegue un CID para ver cómo esos datos están estructurados y vinculados entre protocolos." 45 | }, 46 | "explorer": { 47 | "step1": { 48 | "title": "Explorador IPLD", 49 | "paragraph1": "Your journey with IPLD Explorer starts here. Click \"Next\" to learn more." 50 | }, 51 | "step2": { 52 | "title": "Migas de Pan", 53 | "paragraph1": "La ruta que conduce a este nodo.", 54 | "paragraph2": "Haga clic en él para recorrer a sus padres o hijos." 55 | }, 56 | "step3": { 57 | "title": "Información del nodo", 58 | "paragraph1": "Aquí tiene información detallada sobre el nodo y puede abrirlo en el portal IPFS.", 59 | "paragraph2": "Si el nodo es un directorio, puede ver sus elementos secundarios y navegar hasta ellos." 60 | }, 61 | "step4": { 62 | "title": "Información del CID", 63 | "paragraph1": "El CID es descompuesto para que pueda aprender su significado." 64 | }, 65 | "step5": { 66 | "title": "Representación visual", 67 | "paragraph1": "Una representación visual del nodo y sus hijos." 68 | } 69 | } 70 | }, 71 | "errors": { 72 | "BlockFetchTimeoutError": "Failed to fetch content in {timeout}s. Please refresh the page to retry or try a different CID." 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /public/locales/fr/explore.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": "Explorateur IPLD", 3 | "tabName": "Explorer", 4 | "homeLink": "Accueil", 5 | "UpdateAvailable": { 6 | "paragraph1": "Une nouvelle version d'IPFS Explorer est disponible. <1>Merci de rafraîchir." 7 | }, 8 | "StartExploringPage": { 9 | "title": "Explorer - IPLD", 10 | "header": "Explorer la forêt de Merkle", 11 | "leadParagraph": "Collez un CID dans la section appropriée pour récupérer le nœud IPLD qu'il adresse, ou choisissez un jeu de données proposé.", 12 | "noDataAvailable": "Aucune donnée disponible" 13 | }, 14 | "AboutIpld": { 15 | "paragraph1":"<0>IPLD est <1>le modèle de données du web \"indexé par contenu\". Il nous permet de traiter toutes les structures de données liées par hash, en tant que sous-ensemble d'un espace d'informations unifié, unifiant tous les modèles de données qui associent les données par hash, en tant qu'instances d'IPLD.", 16 | "paragraph2": "<0>L’indexation du contenu par hash est devenu largement répandu pour la connection de données dans les systèmes distribués, de la blockchain sur laquelle est basée votre cryptomonnaie favorite, aux commits qui constituent votre code, jusqu'au contenu du web au sens large. Mais, alors que tous ces outils se basent sur des fonctionnements identiques, leurs structures de données sous-jacentes sont spécifiques et ne sont pas interopérables.", 17 | "paragraph3": "<0>Entrez dans IPLD : un seul espace de noms pour tous les protocoles fonctionnant avec des hashs. Grâce à IPLD, les liens peuvent êtres accessibles d'un protocole à l'autre, vous permettant d'explorer les données quel que soit le protocole sous-jacent." 18 | }, 19 | "ExplorePage": { 20 | "title": "Exploration - IPLD" 21 | }, 22 | "IpldExploreForm": { 23 | "explore": "Explorer" 24 | }, 25 | "IpldCarExploreForm": { 26 | "imports": "Imports", 27 | "uploadCarFile": "Ajouter un fichier CAR" 28 | }, 29 | "CidInfo": { 30 | "header": "Info CID", 31 | "hashDigest": "Hash" 32 | }, 33 | "ObjectInfo": { 34 | "publicGateway": "Voir sur la passerelle publique", 35 | "privateGateway": "Voir sur la passerelle locale" 36 | }, 37 | "base": "base", 38 | "version": "version", 39 | "codec": "codec", 40 | "multihash": "multihash", 41 | "tour": { 42 | "projects": { 43 | "title": "Jeux de données proposés", 44 | "paragraph1": "Explorez les jeux de données proposés ou copiez un CID pour voir comment les données sont structurées et liées entre les protocoles." 45 | }, 46 | "explorer": { 47 | "step1": { 48 | "title": "Explorateur IPLD", 49 | "paragraph1": "Votre voyage dans l'explorateur commence ici. Cliquez sur \"Suivant\" en apprendre plus." 50 | }, 51 | "step2": { 52 | "title": "Fils d'Ariane", 53 | "paragraph1": "Le chemin qui mène à ce nœud.", 54 | "paragraph2": "Cliquer issue pour naviguer entre les parents ou les enfants." 55 | }, 56 | "step3": { 57 | "title": "Information du pair", 58 | "paragraph1": "Trouvez ici le détail des informations sur votre nœud et ouvrez-le dans la passerelle IPFS.", 59 | "paragraph2": "Si le nœud est un dossier, vous pouvez voir les sous-dossiers et naviguer à l'intérieur. " 60 | }, 61 | "step4": { 62 | "title": "Info CID", 63 | "paragraph1": "Le CID analysé pour apprendre sa signification." 64 | }, 65 | "step5": { 66 | "title": "Représentation visuelle", 67 | "paragraph1": "Une représentation visuelle du nœud et de ses enfants." 68 | } 69 | } 70 | }, 71 | "errors": { 72 | "BlockFetchTimeoutError": "Impossible d'obtenir le contenu après {timeout}s. Merci de rafraîchir la page pour réessayer ou d'essayer avec un autre CID." 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /public/locales/id/explore.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": "Penjelajah IPLD", 3 | "tabName": "Jelajah", 4 | "homeLink": "Rumah", 5 | "UpdateAvailable": { 6 | "paragraph1": "Versi baru Penjelajah IPLD tersedia; <1>tolong muat ulang." 7 | }, 8 | "StartExploringPage": { 9 | "title": "Jelajahi - IPLD", 10 | "header": "Jelajahi Merkle Forest", 11 | "leadParagraph": "Tempelkan CID ke dalam kotak untuk mengambil node IPLD yang dituju, atau pilih kumpulan data unggulan.", 12 | "noDataAvailable": "Data tidak tersedia" 13 | }, 14 | "AboutIpld": { 15 | "paragraph1":"<0>IPLD adalah <1>model data web yang dapat dialamatkan dengan konten. Ini memungkinkan kita untuk memperlakukan semua struktur data yang ditautkan dengan hash sebagai himpunan bagian dari ruang informasi terpadu, menyatukan semua model data yang menautkan data dengan hash sebagai instance dari IPLD.", 16 | "paragraph2": "<0>Pengalamatan konten melalui hash telah menjadi sarana yang digunakan secara luas untuk menghubungkan data dalam sistem distribusi, mulai dari blockchain yang menjalankan mata uang kripto favorit Anda, hingga komitmen yang mendukung kode Anda, hingga konten web secara luas. Namun, sementara semua alat ini bergantung pada beberapa primitif umum, struktur data spesifik yang mendasarinya tidak dapat dioperasikan.", 17 | "paragraph3": "<0>Masukkan IPLD: satu ruang nama untuk semua protokol yang terinspirasi hash. Melalui IPLD, tautan dapat dilintasi lintas protokol, memungkinkan Anda menjelajahi data terlepas dari protokol yang mendasarinya." 18 | }, 19 | "ExplorePage": { 20 | "title": "Menjelajahi - IPLD" 21 | }, 22 | "IpldExploreForm": { 23 | "explore": "Jelajah" 24 | }, 25 | "IpldCarExploreForm": { 26 | "imports": "Impor", 27 | "uploadCarFile": "Unggah Berkas CAR" 28 | }, 29 | "CidInfo": { 30 | "header": "Info CID", 31 | "hashDigest": "Intisari Hash" 32 | }, 33 | "ObjectInfo": { 34 | "publicGateway": "Lihat di Gerbang Umum", 35 | "privateGateway": "Lihat di Gerbang Lokal" 36 | }, 37 | "base": "basis", 38 | "version": "versi", 39 | "codec": "codec", 40 | "multihash": "multihash", 41 | "tour": { 42 | "projects": { 43 | "title": "Kumpulan data unggulan", 44 | "paragraph1": "Jelajahi kumpulan data unggulan atau tempel CID untuk melihat bagaimana data tersebut disusun dan ditautkan di seluruh protokol." 45 | }, 46 | "explorer": { 47 | "step1": { 48 | "title": "Penjelajah IPLD", 49 | "paragraph1": "Perjalanan Anda dengan Penjelajah IPLD dimulai di sini. Klik \"Berikutnya\" untuk mempelajari lebih lanjut." 50 | }, 51 | "step2": { 52 | "title": "Tepung roti", 53 | "paragraph1": "Jalur yang mengarah ke node ini.", 54 | "paragraph2": "Klik untuk melintasi ke orang tua atau anak-anaknya." 55 | }, 56 | "step3": { 57 | "title": "Info Node", 58 | "paragraph1": "Di sini Anda memiliki info rinci tentang node dan Anda dapat membukanya di gerbang IPFS.", 59 | "paragraph2": "Jika node adalah direktori, Anda dapat melihat anak-anaknya dan menavigasi ke mereka." 60 | }, 61 | "step4": { 62 | "title": "Info CID", 63 | "paragraph1": "CID yang didekomposisi sehingga Anda dapat mempelajari artinya." 64 | }, 65 | "step5": { 66 | "title": "Representasi visual", 67 | "paragraph1": "Representasi visual dari node dan anak-anaknya." 68 | } 69 | } 70 | }, 71 | "errors": { 72 | "BlockFetchTimeoutError": "Gagal mengambil konten dalam {timeout}s. Harap segarkan halaman untuk mencoba lagi atau mencoba CID lain." 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /public/locales/it/explore.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": "Esplora risorse IPLD", 3 | "tabName": "Esplora", 4 | "homeLink": "Home", 5 | "UpdateAvailable": { 6 | "paragraph1": "Una nuova versione di IPLD Explorer è disponibile; <1>Per favore, ricarica." 7 | }, 8 | "StartExploringPage": { 9 | "title": "Esplora - IPLD", 10 | "header": "Esplora la Merkle Forest", 11 | "leadParagraph": "Incolla un CID nella casella di testo per recuperare il nodo IPDL, oppure scegli un dataset in primo piano.", 12 | "noDataAvailable": "Nessun dato disponibile" 13 | }, 14 | "AboutIpld": { 15 | "paragraph1":"<0>IPLD è <1>il modello di dati del web content-addressable. Esso ci permette di trattare tutte le strutture di dati hash-linked come sottoinsieme di uno spazio d'informazione unificato, unendo tutti i modelli di dati che collegano dati con gli hash come istanze di IPLD.", 16 | "paragraph2": "<0>L'indirizzamento di contenuti attraverso le hash è diventato un metodo largamente utilizzato per connettere dati in sistemi distribuiti, dalle blockchain che gestiscono le tue criptovalute preferite, ai commit che sostengono il tuo codice, al contenuto web in generale. Nonostante tutti questi strumenti si appoggino su fondamenta comuni, le loro sottostanti strutture di dati non sono interoperabili.", 17 | "paragraph3": "<0>Inserisci IPLD: un singolo namespace per tutti i protocolli ispirati ad hash. Attraverso IPLD, i collegamenti possono attraversare diversi protocolli, permettendoti di esplorare dati indipendentemente dal protocollo sottostante." 18 | }, 19 | "ExplorePage": { 20 | "title": "Esplorando - IPDL" 21 | }, 22 | "IpldExploreForm": { 23 | "explore": "Esplora" 24 | }, 25 | "IpldCarExploreForm": { 26 | "imports": "Importa", 27 | "uploadCarFile": "Upload CAR File" 28 | }, 29 | "CidInfo": { 30 | "header": "Info CID", 31 | "hashDigest": "Hash digest" 32 | }, 33 | "ObjectInfo": { 34 | "publicGateway": "View on Public Gateway", 35 | "privateGateway": "View on Local Gateway" 36 | }, 37 | "base": "base", 38 | "version": "versione", 39 | "codec": "codec", 40 | "multihash": "multihash", 41 | "tour": { 42 | "projects": { 43 | "title": "Dataset in primo piano", 44 | "paragraph1": "Esplora i dataset in primo piano od incolla un CID per vedere come quei dati sono strutturati e collegati tra i vari protocolli." 45 | }, 46 | "explorer": { 47 | "step1": { 48 | "title": "Esplora risorse IPLD", 49 | "paragraph1": "Il tuo percorso con IPLD Explorer inizia qui. Clicca \"Avanti\" per saperne di più." 50 | }, 51 | "step2": { 52 | "title": "Breadcrumb", 53 | "paragraph1": "Il percorso che porta a questo nodo.", 54 | "paragraph2": "Cliccaci per attraversare i suoi percorsi padre o figlio." 55 | }, 56 | "step3": { 57 | "title": "Informazioni nodo", 58 | "paragraph1": "Qui hai tutte le informazioni dettagliate riguardo al nodo e puoi aprirlo nel gateway IPFS.", 59 | "paragraph2": "Se il nodo è una directory, puoi vedere i suoi figli e navigarci in essi." 60 | }, 61 | "step4": { 62 | "title": "Info CID", 63 | "paragraph1": "Il CID scomposto, così puoi imparare il suo significato." 64 | }, 65 | "step5": { 66 | "title": "Rappresentazione visuale", 67 | "paragraph1": "Una rappresentazione visuale del nodo e dei suoi figli." 68 | } 69 | } 70 | }, 71 | "errors": { 72 | "BlockFetchTimeoutError": "Failed to fetch content in {timeout}s. Please refresh the page to retry or try a different CID." 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /public/locales/ja-JP/explore.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": "IPLDエクスプローラー", 3 | "tabName": "探索", 4 | "homeLink": "ホーム", 5 | "UpdateAvailable": { 6 | "paragraph1": "IPLD Explorerの新しいバージョンが利用可能です。<1>再読み込みしてください。" 7 | }, 8 | "StartExploringPage": { 9 | "title": "IPLDを探す", 10 | "header": "マークルフォレストを探す", 11 | "leadParagraph": "CIDをボックスに貼り付けて、アドレス指定するIPLDノードを取得するか、機能データセットを選択します。" 12 | }, 13 | "AboutIpld": { 14 | "paragraph1":"<0>IPLDは<1>Content-Addressable Webのデータモデルです。ハッシュリンクされたすべてのデータ構造を統一された情報スペースのサブセットとして扱い、IPLDのインスタンスとして、データをハッシュとリンクするすべてのデータモデルを統一できます。 ", 15 | "paragraph2": "<0>ハッシュを介したコンテンツアドレッシングは、お気に入りの暗号通貨を実行するブロックチェーンから、コードを裏付けるコミット、ウェブのコンテンツ全般まで、分散システムのデータを接続するための広く使用されている手段になりました。しかし、これらのツールはすべていくつかの一般的なプリミティブに依存していますが、それらの特定の基礎となるデータ構造は相互運用できません。", 16 | "paragraph3": "<0>IPLDを入力してください:すべてのハッシュにヒントを得たプロトコルの単一の名前空間。 IPLDを使用すると、プロトコル間でリンクを横断できるため、基になるプロトコルに関係なくデータを探索できます。" 17 | }, 18 | "ExplorePage": { 19 | "title": "IPLDを探しています" 20 | }, 21 | "IpldExploreForm": { 22 | "explore": "探索" 23 | }, 24 | "IpldCarExploreForm": { 25 | "imports": "インポート", 26 | "uploadCarFile": "Upload CAR File" 27 | }, 28 | "CidInfo": { 29 | "header": "CID情報", 30 | "hashDigest": "ハッシュダイジェスト" 31 | }, 32 | "ObjectInfo": { 33 | "publicGateway": "View on Public Gateway", 34 | "privateGateway": "View on Local Gateway" 35 | }, 36 | "base": "ベース", 37 | "version": "バージョン", 38 | "codec": "コーデック", 39 | "multihash": "マルチハッシュ", 40 | "tour": { 41 | "projects": { 42 | "title": "注目のデータセット", 43 | "paragraph1": "注目のデータセットを調べるか、CIDを貼り付けて、そのデータがプロトコル間でどのように構造化およびリンクされているかを確認します。" 44 | }, 45 | "explorer": { 46 | "step1": { 47 | "title": "IPLDエクスプローラー", 48 | "paragraph1": "あなたとIPLD Explorerの旅はここから始まります。Nextをクリックしてください。" 49 | }, 50 | "step2": { 51 | "title": "パンくずリスト", 52 | "paragraph1": "このノードに至るパス。", 53 | "paragraph2": "それをクリックして、その親または子に移動します。" 54 | }, 55 | "step3": { 56 | "title": "ノード情報", 57 | "paragraph1": "ここにノードに関する詳細情報があり、IPFSゲートウェイで開くことができます。", 58 | "paragraph2": "もしノードがディレクトリであれば、ディレクトリが持っているコンテンツが確認できますし、そこに移動することも可能です。" 59 | }, 60 | "step4": { 61 | "title": "CID情報", 62 | "paragraph1": "分解されたCIDで、その意味を学ぶことができます。" 63 | }, 64 | "step5": { 65 | "title": "視覚的表現", 66 | "paragraph1": "ノードとその子の視覚的表現。" 67 | } 68 | } 69 | }, 70 | "errors": { 71 | "BlockFetchTimeoutError": "Failed to fetch content in {timeout}s. Please refresh the page to retry or try a different CID." 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /public/locales/ko-KR/explore.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": "IPLD 탐색기", 3 | "tabName": "탐색", 4 | "homeLink": "홈", 5 | "UpdateAvailable": { 6 | "paragraph1": "새로운 버전의 IPLD 탐색기가 사용가능 <1>새로고침<1>" 7 | }, 8 | "StartExploringPage": { 9 | "title": "탐색 - IPLD", 10 | "header": "Merkle Forest 탐색", 11 | "leadParagraph": "CID를 붙여 넣고 주소가 지정된 IPLD 노드를 가져오거나, 추천 데이터셋을 선택하세요.", 12 | "noDataAvailable": "데이터가 없습니다" 13 | }, 14 | "AboutIpld": { 15 | "paragraph1":"<0>IPLD는 <1>콘텐츠에 주소를 지정가능한 웹의 데이터 모델입니다.이를 통해서 모든 해시-연결된 데이터 구조는 통합된 정보 공간에서 서브셋으로 존재할 수 있습니다. 해시가 있는 데이터를 IPLD의 인스턴스로 모든 데이터 모델을 통합 할 수 있습니다.", 16 | "paragraph2": "<0>해시를 통한 콘텐츠 주소 지정은 분산 시스템의 데이터를 좋아하는 암호화폐를 실행하는 blockchain에서 코드를 백업하는 커밋에 이르기까지 웹의 콘텐츠에 연결하는 데 널리 사용되는 방법이되었습니다. 하지만 이런 모든 도구는 일부 공통 요소에 의존하지만 고유 한 기본 데이터 구조는 상호 운용 될 수 없었습니다.", 17 | "paragraph3": "<0>Enter IPLD: 모든 해시로 부터 영감을 받은 단일 프로토콜 네임스페이스. IPLD를 통해 링크는 프로토콜을 가로질러 작동하므로 기본 프로토콜과 관계없이 데이터를 탐색 할 수 있습니다." 18 | }, 19 | "ExplorePage": { 20 | "title": "Exploring - IPLD" 21 | }, 22 | "IpldExploreForm": { 23 | "explore": "탐색" 24 | }, 25 | "IpldCarExploreForm": { 26 | "imports": "Import", 27 | "uploadCarFile": "Upload CAR File" 28 | }, 29 | "CidInfo": { 30 | "header": "CID 정보", 31 | "hashDigest": "해시 다이제스트" 32 | }, 33 | "ObjectInfo": { 34 | "publicGateway": "View on Public Gateway", 35 | "privateGateway": "View on Local Gateway" 36 | }, 37 | "base": "base", 38 | "version": "버전", 39 | "codec": "코덱", 40 | "multihash": "멀티해시", 41 | "tour": { 42 | "projects": { 43 | "title": "Featured datasets", 44 | "paragraph1": "주요 데이터 세트를 탐색하거나 CID를 붙여 넣어 해당 데이터가 프로토콜에서 어떻게 구성되고 연결되는지 확인하십시오." 45 | }, 46 | "explorer": { 47 | "step1": { 48 | "title": "IPLD 탐색기", 49 | "paragraph1": "IPLD 익스플로러를 통한 여정은 여기에서 시작됩니다. 자세한 내용을 보려면 \"다음\"을 클릭하십시오." 50 | }, 51 | "step2": { 52 | "title": "이동경로", 53 | "paragraph1": "The path that leads to this node.", 54 | "paragraph2": "Click on it to traverse to its parents or children." 55 | }, 56 | "step3": { 57 | "title": "Node info", 58 | "paragraph1": "Here you have detailed info about the node and you can open it in the IPFS gateway.", 59 | "paragraph2": "If the node is a directory you can see its children and navigate to them." 60 | }, 61 | "step4": { 62 | "title": "CID 정보", 63 | "paragraph1": "The decomposed CID so you can learn its meaning." 64 | }, 65 | "step5": { 66 | "title": "Visual representation", 67 | "paragraph1": "A visual representation of the node and its children." 68 | } 69 | } 70 | }, 71 | "errors": { 72 | "BlockFetchTimeoutError": "Failed to fetch content in {timeout}s. Please refresh the page to retry or try a different CID." 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /public/locales/nl/explore.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": "IPLD Explorer", 3 | "tabName": "Verkennen", 4 | "homeLink": "Thuis", 5 | "UpdateAvailable": { 6 | "paragraph1": "A new version of IPLD Explorer is available; <1>please reload." 7 | }, 8 | "StartExploringPage": { 9 | "title": "Verken - IPLD", 10 | "header": "Verken het Merkle Forest", 11 | "leadParagraph": "Plak een CID in het vak om de IPLD node op te halen waar hij naar verwijst of kies een van de aanbevolen datasets.", 12 | "noDataAvailable": "Geen gegevens beschikbaar" 13 | }, 14 | "AboutIpld": { 15 | "paragraph1":"<0>IPLD is <1>het data model van het inhoud-adresseerbare web.Hiermee kunnen we alle hash-gekoppelde gegevensstructuren behandelen als onderdeel van een uniforme informatieverzameling, waarbij alle gegevens, die met hashes aan elkaar gekoppeld zijn, verenigd worden als onderdelen van IPLD. ", 16 | "paragraph2": "<0>Inhoud-adressering via hashes is een veelgebruikte methode geworden voor het verbinden van gegevens in gedistribueerde systemen, van blockchains die je favoriete cryptocurrencies bevatten, tot commits die uw code ondersteunen, tot de inhoud van het web in het algemeen. Hoewel al deze tools gebruik maken van dezelfde basiscomponenten, zijn hun specifieke datastructuren niet compatibel.", 17 | "paragraph3": "<0>IPLD lost dit op: een enkel adresbereik voor alle op-hash-geïnspireerde protocollen. Met IPLD kunnen koppelingen gevold worden doorheen verschillende protocollen, zodat je gegevens kan verkennen ongeacht het onderliggende protocol." 18 | }, 19 | "ExplorePage": { 20 | "title": "Verkennen - IPLD" 21 | }, 22 | "IpldExploreForm": { 23 | "explore": "Verken" 24 | }, 25 | "IpldCarExploreForm": { 26 | "imports": "Importeren", 27 | "uploadCarFile": "Upload CAR File" 28 | }, 29 | "CidInfo": { 30 | "header": "CID info", 31 | "hashDigest": "Hash digest" 32 | }, 33 | "ObjectInfo": { 34 | "publicGateway": "View on Public Gateway", 35 | "privateGateway": "View on Local Gateway" 36 | }, 37 | "base": "basis", 38 | "version": "versie", 39 | "codec": "codec", 40 | "multihash": "multihash", 41 | "tour": { 42 | "projects": { 43 | "title": "Featured datasets", 44 | "paragraph1": "Explore the featured datasets or paste a CID to see how that data is structured and linked across protocols." 45 | }, 46 | "explorer": { 47 | "step1": { 48 | "title": "IPLD Explorer", 49 | "paragraph1": "Your journey with IPLD Explorer starts here. Click \"Next\" to learn more." 50 | }, 51 | "step2": { 52 | "title": "Navigatiegeschiedenis", 53 | "paragraph1": "The path that leads to this node.", 54 | "paragraph2": "Click on it to traverse to its parents or children." 55 | }, 56 | "step3": { 57 | "title": "Node info", 58 | "paragraph1": "Here you have detailed info about the node and you can open it in the IPFS gateway.", 59 | "paragraph2": "If the node is a directory you can see its children and navigate to them." 60 | }, 61 | "step4": { 62 | "title": "CID info", 63 | "paragraph1": "The decomposed CID so you can learn its meaning." 64 | }, 65 | "step5": { 66 | "title": "Visual representation", 67 | "paragraph1": "A visual representation of the node and its children." 68 | } 69 | } 70 | }, 71 | "errors": { 72 | "BlockFetchTimeoutError": "Failed to fetch content in {timeout}s. Please refresh the page to retry or try a different CID." 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /public/locales/no/explore.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": "IPLD Utforsker", 3 | "tabName": "Utforsk", 4 | "homeLink": "Hjem", 5 | "UpdateAvailable": { 6 | "paragraph1": "A new version of IPLD Explorer is available; <1>please reload." 7 | }, 8 | "StartExploringPage": { 9 | "title": "Utforsk - IPLD", 10 | "header": "Utforsk Merkle Forest", 11 | "leadParagraph": "Lim inn en CID i boksen for å hente IPLD-noden den adresserer, eller velg et utvalgt datasett.", 12 | "noDataAvailable": "Ingen data tilgjengelig" 13 | }, 14 | "AboutIpld": { 15 | "paragraph1":"<0>IPLD er <1>datamodellen til den innholdsadresserbare webben. Den tillater oss å behandle alle hash-linkede datastrukturer som undergrupper av en enhetlig informasjonsplass, og forener alle datamodeller som linker data med hasher som forekomster av IPLD.", 16 | "paragraph2": "<0>Innholdsadressering via hasher har blitt et mye brukt verktøy for å koble data i distribuerte systemer, fra blockchains som driver din favoritt kryptovaluta, til commits som støtter din kode, til nettets innhold som helhet. Mens alle disse verktøyene er avhenging av noen felles primitiver, er deres spesifikke underliggende datastrukturer ikke interoperative.", 17 | "paragraph3": "<0>Her kommer IPLD inn: et enkelt navnerom for alle hash-inspirerte protokoller. Gjennom IPLD, kan koblinger traversere på tvers av protokoller, slik at du kan utforske data uavhengig av den underliggende protokollen." 18 | }, 19 | "ExplorePage": { 20 | "title": "Utforsker - IPLD" 21 | }, 22 | "IpldExploreForm": { 23 | "explore": "Utforsk" 24 | }, 25 | "IpldCarExploreForm": { 26 | "imports": "Import", 27 | "uploadCarFile": "Upload CAR File" 28 | }, 29 | "CidInfo": { 30 | "header": "CID info", 31 | "hashDigest": "Hash digest" 32 | }, 33 | "ObjectInfo": { 34 | "publicGateway": "View on Public Gateway", 35 | "privateGateway": "View on Local Gateway" 36 | }, 37 | "base": "base", 38 | "version": "versjon", 39 | "codec": "codec", 40 | "multihash": "multihash", 41 | "tour": { 42 | "projects": { 43 | "title": "Featured datasets", 44 | "paragraph1": "Explore the featured datasets or paste a CID to see how that data is structured and linked across protocols." 45 | }, 46 | "explorer": { 47 | "step1": { 48 | "title": "IPLD Utforsker", 49 | "paragraph1": "Your journey with IPLD Explorer starts here. Click \"Next\" to learn more." 50 | }, 51 | "step2": { 52 | "title": "Breadcrumbs", 53 | "paragraph1": "The path that leads to this node.", 54 | "paragraph2": "Click on it to traverse to its parents or children." 55 | }, 56 | "step3": { 57 | "title": "Node Info", 58 | "paragraph1": "Here you have detailed info about the node and you can open it in the IPFS gateway.", 59 | "paragraph2": "If the node is a directory you can see its children and navigate to them." 60 | }, 61 | "step4": { 62 | "title": "CID info", 63 | "paragraph1": "The decomposed CID so you can learn its meaning." 64 | }, 65 | "step5": { 66 | "title": "Visual representation", 67 | "paragraph1": "A visual representation of the node and its children." 68 | } 69 | } 70 | }, 71 | "errors": { 72 | "BlockFetchTimeoutError": "Failed to fetch content in {timeout}s. Please refresh the page to retry or try a different CID." 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /public/locales/pl/explore.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": "Przeglądarka IPLD", 3 | "tabName": "Przeglądaj", 4 | "homeLink": "Strona główna", 5 | "UpdateAvailable": { 6 | "paragraph1": "Nowa wersja przeglądarki IPLD jest dostępna; <1>prosimy odświeżyć stronę." 7 | }, 8 | "StartExploringPage": { 9 | "title": "Przeglądaj - IPLD", 10 | "header": "Przeglądaj Las Merkle’a", 11 | "leadParagraph": "Wklej CID w pole, aby pobrać dowolny węzeł IPLD, lub wybierz jeden z wyróżnionych zbiorów danych.", 12 | "noDataAvailable": "Brak dostępnych danych" 13 | }, 14 | "AboutIpld": { 15 | "paragraph1":"<0>IPLD to <1>model danych dla sieci adresowanej treścią (ang. content-addressable web). Umożliwia traktowanie obiektów połączonych skrótem (haszem) jako podzbiorów jednolitej przestrzeni danych, w której modele danych to instancje IPLD.", 16 | "paragraph2": "<0>Adresowanie treścią przy użyciu skrótów funkcji haszujących stało się popularną techniką w rozproszonych systemach takich jak kryptowaluty, systemy kontroli wersji lub systemy dystrybucji treści w internecie. Niestety pomimo podobnej zasady działania, wszystkie te narzędzia używają niekompatybilnych ze sobą struktur danych.", 17 | "paragraph3": "<0>Z pomocą przychodzi IPLD: wspólna przestrzeń nazw dla wszystkich protokołów. Dzięki IPLD, łącza między obiektami mogą przechodzić z protokołu na protokół, umożliwiając dostęp oraz przetwarzanie danych w ujednolicony sposób." 18 | }, 19 | "ExplorePage": { 20 | "title": "Przeglądanie - IPLD" 21 | }, 22 | "IpldExploreForm": { 23 | "explore": "Przeglądaj" 24 | }, 25 | "IpldCarExploreForm": { 26 | "imports": "Zaimportuj", 27 | "uploadCarFile": "Wrzuć plik CAR" 28 | }, 29 | "CidInfo": { 30 | "header": "Informacje CID", 31 | "hashDigest": "wynik funkcji haszującej" 32 | }, 33 | "ObjectInfo": { 34 | "publicGateway": "Oglądaj przez publiczną bramkę", 35 | "privateGateway": "Oglądaj przez lokalną bramkę" 36 | }, 37 | "base": "baza", 38 | "version": "wersja", 39 | "codec": "kodek", 40 | "multihash": "multihash", 41 | "tour": { 42 | "projects": { 43 | "title": "Wyróżnione zbiory danych", 44 | "paragraph1": "Zbadaj wyróżnione zbiory danych lub wklej CID w celu sprawdzenia, w jaki sposób dane te są złożone i połączone między protokołami." 45 | }, 46 | "explorer": { 47 | "step1": { 48 | "title": "Przeglądarka IPLD", 49 | "paragraph1": "Tutaj zaczyna się Twoja przygoda z przeglądarką IPLD. Kliknij \"Dalej\", aby dowiedzieć się więcej." 50 | }, 51 | "step2": { 52 | "title": "Okruszki", 53 | "paragraph1": "Ścieżka prowadząca do tego węzła.", 54 | "paragraph2": "Kliknij, aby przejść do jego węzłów nadrzędnych lub podrzędnych." 55 | }, 56 | "step3": { 57 | "title": "Informacje węzła", 58 | "paragraph1": "Tutaj są widoczne detale dotyczące tego węzła. Można je obejrzeć także w bramie IPFS.", 59 | "paragraph2": "Jeśli węzeł jest katalogiem, możesz zobaczyć jego węzły podrzędne oraz do nich przejść." 60 | }, 61 | "step4": { 62 | "title": "Informacje CID", 63 | "paragraph1": "Identyfikator zawartości (CID) rozłożony w ramach poznania jego składu." 64 | }, 65 | "step5": { 66 | "title": "Reprezentacja graficzna", 67 | "paragraph1": "Graficzna reprezentacja węzłów i ich zależności." 68 | } 69 | } 70 | }, 71 | "errors": { 72 | "BlockFetchTimeoutError": "Failed to fetch content in {timeout}s. Please refresh the page to retry or try a different CID." 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /public/locales/pt-BR/explore.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": "IPLD Explorer", 3 | "tabName": "Explorar", 4 | "homeLink": "Início", 5 | "UpdateAvailable": { 6 | "paragraph1": "Uma nova versão do IPLD Explorer está disponível; <1>favor recarregar." 7 | }, 8 | "StartExploringPage": { 9 | "title": "Explorar - IPLD", 10 | "header": "Explorar a Floresta de Merkle", 11 | "leadParagraph": "Cole um CID na caixa para explorar o nó IPLD a que ele se refere, ou escolha uma das coleções de dados abaixo.", 12 | "noDataAvailable": "Nenhum dado disponível" 13 | }, 14 | "AboutIpld": { 15 | "paragraph1":"<0>IPLD é <1>o modelo de dados endereçável por conteúdos da web. Permite tratarmos todas as estruturas de dados ligadas por hashes como subgrupos de um espaço de informação unificado, unindo assim todos os modelos de dados que relacionam dados com hashes em instâncias do IPLD.", 16 | "paragraph2": "<0>O endereçamento de conteúdo através de hashes tornou-se um sistema de relacionamento de dados bastante utilizado em sistemas distribuídos, desde blockchains na base das suas criptomoedas favoritas, às commits do seu trabalho, a qualquer conteúdo da web. No entanto, apesar de todas estas ferramentas se basearem em ideias-base comuns, as suas estruturas de dados não são interoperáveis.", 17 | "paragraph3": "<0>IPLD: um único espaço para todos os protocolos baseados em hashes. Com o IPLD, os links podem ser relacionados através de diferentes protocolos, permitindo-lhe explorar os dados independentemente do protocolo subjacente." 18 | }, 19 | "ExplorePage": { 20 | "title": "Explorando - IPLD" 21 | }, 22 | "IpldExploreForm": { 23 | "explore": "Explorar" 24 | }, 25 | "IpldCarExploreForm": { 26 | "imports": "Importar", 27 | "uploadCarFile": "Subir Arquivo CAR" 28 | }, 29 | "CidInfo": { 30 | "header": "Informação cid", 31 | "hashDigest": "Resumo do hash" 32 | }, 33 | "ObjectInfo": { 34 | "publicGateway": "Visualizar no Gateway Público", 35 | "privateGateway": "Visualizar no Gateway Local" 36 | }, 37 | "base": "base", 38 | "version": "versão", 39 | "codec": "codec", 40 | "multihash": "multihash", 41 | "tour": { 42 | "projects": { 43 | "title": "Grupos em destaque", 44 | "paragraph1": "Explore os conjuntos de dados apresentados ou cole um CID para ver como esses dados são estruturados e vinculados entre protocolos." 45 | }, 46 | "explorer": { 47 | "step1": { 48 | "title": "Explorador IPLD", 49 | "paragraph1": "Sua jornada com o IPLD Explorer começa aqui. Clique em \"Avançar\" para saber mais." 50 | }, 51 | "step2": { 52 | "title": "Navegação estrutural", 53 | "paragraph1": "O caminho que leva a este nó.", 54 | "paragraph2": "Clique nele para ir aos níveis superiores ou inferiores." 55 | }, 56 | "step3": { 57 | "title": "Informação sobre o nó", 58 | "paragraph1": "Aqui você tem informações detalhadas sobre o nó e pode abri-lo no gateway IPFS.", 59 | "paragraph2": "Se o nó for um diretório, você poderá ver seus subdiretórios e navegar até eles." 60 | }, 61 | "step4": { 62 | "title": "Informação cid", 63 | "paragraph1": "O CID decomposto para que você possa aprender seu significado." 64 | }, 65 | "step5": { 66 | "title": "Representação visual", 67 | "paragraph1": "Uma representação visual do nó e seus níveis inferiores." 68 | } 69 | } 70 | }, 71 | "errors": { 72 | "BlockFetchTimeoutError": "Falha ao buscar conteúdo em {timeout}s. Atualize a página para tentar novamente ou tente um CID diferente." 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /public/locales/pt/explore.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": "Explorador IPLD", 3 | "tabName": "Explorar", 4 | "homeLink": "Início", 5 | "UpdateAvailable": { 6 | "paragraph1": "A new version of IPLD Explorer is available; <1>please reload." 7 | }, 8 | "StartExploringPage": { 9 | "title": "Explorador - IPLD", 10 | "header": "Explore a Floresta de Merkle", 11 | "leadParagraph": "Cole um CID na caixa para explorar o nó IPLD a que se refere, ou escolha uma das coleções de dados abaixo.", 12 | "noDataAvailable": "Nenhum dado disponível" 13 | }, 14 | "AboutIpld": { 15 | "paragraph1":"<0>IPLD é <1>o modelo de dados endereçável por conteúdos da web. Permite-nos tratar todas as estruturas de dados ligadas por hashes como sub-peças de um sistema de informação unificado, unificando assim todos os modelos de dados que relacionam dados com hashes em instâncias do IPLD.", 16 | "paragraph2": "<0>Endereçamento de conteúdo através de hashes tornou-se um sistema de relacionamento de dados bastante utilizado em sistemas distribuídos, desde blockchains na base das suas criptomoedas favoritas, às commits do seu trabalho, a qualquer conteúdo da web. No entanto, apesar de todas estas ferramentas se basearem em primitivas comuns, as suas estruturas de dados não são interoperáveis.", 17 | "paragraph3": "<0>IPLD: um espaço para todos os protocolos inspirados em hashes. Com o IPLD, pode relacionar diferentes protocolos e navegar entre eles, permitindo que explore os dados independentemente do protocolo subjacente." 18 | }, 19 | "ExplorePage": { 20 | "title": "A Explorar - IPLD" 21 | }, 22 | "IpldExploreForm": { 23 | "explore": "Explorar" 24 | }, 25 | "IpldCarExploreForm": { 26 | "imports": "Import", 27 | "uploadCarFile": "Upload CAR File" 28 | }, 29 | "CidInfo": { 30 | "header": "CID info", 31 | "hashDigest": "Hash digest" 32 | }, 33 | "ObjectInfo": { 34 | "publicGateway": "View on Public Gateway", 35 | "privateGateway": "View on Local Gateway" 36 | }, 37 | "base": "base", 38 | "version": "versão", 39 | "codec": "codec", 40 | "multihash": "multihash", 41 | "tour": { 42 | "projects": { 43 | "title": "Featured datasets", 44 | "paragraph1": "Explore the featured datasets or paste a CID to see how that data is structured and linked across protocols." 45 | }, 46 | "explorer": { 47 | "step1": { 48 | "title": "Explorador IPLD", 49 | "paragraph1": "Your journey with IPLD Explorer starts here. Click \"Next\" to learn more." 50 | }, 51 | "step2": { 52 | "title": "Navegação", 53 | "paragraph1": "The path that leads to this node.", 54 | "paragraph2": "Click on it to traverse to its parents or children." 55 | }, 56 | "step3": { 57 | "title": "Node info", 58 | "paragraph1": "Here you have detailed info about the node and you can open it in the IPFS gateway.", 59 | "paragraph2": "If the node is a directory you can see its children and navigate to them." 60 | }, 61 | "step4": { 62 | "title": "CID info", 63 | "paragraph1": "The decomposed CID so you can learn its meaning." 64 | }, 65 | "step5": { 66 | "title": "Visual representation", 67 | "paragraph1": "A visual representation of the node and its children." 68 | } 69 | } 70 | }, 71 | "errors": { 72 | "BlockFetchTimeoutError": "Failed to fetch content in {timeout}s. Please refresh the page to retry or try a different CID." 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /public/locales/ro/explore.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": "Explorator IPLD", 3 | "tabName": "Explorează", 4 | "homeLink": "Home", 5 | "UpdateAvailable": { 6 | "paragraph1": "O nouă versiune de IPLD Explorer este disponibil; <1>reîncarcă." 7 | }, 8 | "StartExploringPage": { 9 | "title": "Explorează - IPDL", 10 | "header": "Explorează Pădurea Merkle", 11 | "leadParagraph": "Introdu un CID pentru a obține un nod IPLD adresat sau alege un set de date reprezentativ.", 12 | "noDataAvailable": "Nu sunt date disponibile" 13 | }, 14 | "AboutIpld": { 15 | "paragraph1":"<0>IPLD este <1>modelul de date a web-ului a cărui conținut este adresabil. Ne permite să tratăm toate structurile de date hash-linked ca subseturi ale unui spațiu informațional unificat, unificând toate modelele de date care leagă datele cu hash-urile ca instanțe ale IPLD.", 16 | "paragraph2": "<0>Adresarea conținutului folosind hash-uri a devenit un mijloc larg folosit pentru a conecta datele în sisteme distribuite, de la blockchains care rulează criptomonedele preferate, la commit-urile folosite pentru salvarea lucrului la cod, până la întreg conținutul web-ului. Cu toate că aceste instrumente se bazează pe niște primitive comune, structurile specifice de la bază nu sunt interoperabile.", 17 | "paragraph3": "<0>Fă cunoștință cu IPLD: un namespace unic pentru toate protocoalele inspirate de hash-uri. Prin intermediul IPLD, link-urile pot fi parcurse folosind diferite protocoale, fiind permisă explorarea datelor indiferent de protocolul de la bază." 18 | }, 19 | "ExplorePage": { 20 | "title": "Explorare - IPLD" 21 | }, 22 | "IpldExploreForm": { 23 | "explore": "Explorează" 24 | }, 25 | "IpldCarExploreForm": { 26 | "imports": "Importă", 27 | "uploadCarFile": "Încarcă un fișier CAR" 28 | }, 29 | "CidInfo": { 30 | "header": "Informații CID", 31 | "hashDigest": "Hash digest" 32 | }, 33 | "ObjectInfo": { 34 | "publicGateway": "View on Public Gateway", 35 | "privateGateway": "View on Local Gateway" 36 | }, 37 | "base": "bază", 38 | "version": "versiune", 39 | "codec": "codec", 40 | "multihash": "multihash", 41 | "tour": { 42 | "projects": { 43 | "title": "Dataseturi prezentate", 44 | "paragraph1": "Explorează seturile de date prezente sau introdu un CID pentru a vedea cum acele date sunt structurate și legate indiferent de protocol." 45 | }, 46 | "explorer": { 47 | "step1": { 48 | "title": "Explorator IPLD", 49 | "paragraph1": "Călătoria ta cu Exploratorul IPLD pornește aici. Apasă pe „Mai departe” pentru a descoperi mai multe." 50 | }, 51 | "step2": { 52 | "title": "Breadcrumbs", 53 | "paragraph1": "Calea care conduce la acest nod.", 54 | "paragraph2": "Dă clic pe el pentru a merge la părinți sau copii." 55 | }, 56 | "step3": { 57 | "title": "Informații nod", 58 | "paragraph1": "Aici ai informații detaliate despre nod și poți să-l deschizi în gateway-ul IPFS.", 59 | "paragraph2": "Dacă nodul este un director poți să-i vezi copiii și poți merge la aceștia." 60 | }, 61 | "step4": { 62 | "title": "Informații CID", 63 | "paragraph1": "CID-ul desfăcut pentru a pătrunde înțelesul." 64 | }, 65 | "step5": { 66 | "title": "Reprezentare vizuală", 67 | "paragraph1": "O reprezentare vizuală a nodului și a copiilor săi." 68 | } 69 | } 70 | }, 71 | "errors": { 72 | "BlockFetchTimeoutError": "Failed to fetch content in {timeout}s. Please refresh the page to retry or try a different CID." 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /public/locales/ru/explore.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": "IPLD Explorer", 3 | "tabName": "Исследовать", 4 | "homeLink": "Домой", 5 | "UpdateAvailable": { 6 | "paragraph1": "Новая версия IPLD Explorer доступна; <1>пожалуйста, перезагрузите." 7 | }, 8 | "StartExploringPage": { 9 | "title": "Обзор - IPLD", 10 | "header": "Обзор Merkle Forest", 11 | "leadParagraph": "Вставьте CID в поле, чтобы получить узел IPLD, на который он ссылается, или выберите отдельный набор данных.", 12 | "noDataAvailable": "Нет доступных данных" 13 | }, 14 | "AboutIpld": { 15 | "paragraph1":"<0>IPLD это <1>модель данных веб с адресацией по содержимому. Она предоставляет возможность обрабатывать все структуры данных с хеш-ссылками как предметы единого иформационного пространства, объединяя все модели данных, которые ссылаются на данные через хеш как экземпляры IPLD.", 16 | "paragraph2": "<0>Адресация по содержимому посредством хешей стала широко используемым инструментом соединения данных в распределенных системах, начиная блокчейнами, оперирующими вашими любимыми криптовалютами, заканчивая фиксацией, обеспечивающей ваш код работоспособностю и веб-контентом вообще. Тем не менее, поскольку все эти инструменты полагаются на некоторые общие базовые элементы, специфические структуры данных, лежащие в их основе, не являются интероперабильными.", 17 | "paragraph3": "<0>Входите в IPLD: единое пространство имен для всех протоколов, движимых хешами. Посредством IPLD ссылки могут быть проведены по протоколам, давая вам возможность изучать данные, независимо от протокола, лежащего в их основе." 18 | }, 19 | "ExplorePage": { 20 | "title": "Обзор - IPLD" 21 | }, 22 | "IpldExploreForm": { 23 | "explore": "Обзор" 24 | }, 25 | "IpldCarExploreForm": { 26 | "imports": "Импорт", 27 | "uploadCarFile": "Upload CAR File" 28 | }, 29 | "CidInfo": { 30 | "header": "Инфо о CID", 31 | "hashDigest": "Справочник хешей" 32 | }, 33 | "ObjectInfo": { 34 | "publicGateway": "View on Public Gateway", 35 | "privateGateway": "View on Local Gateway" 36 | }, 37 | "base": "база", 38 | "version": "версия", 39 | "codec": "кодек", 40 | "multihash": "мультихеш", 41 | "tour": { 42 | "projects": { 43 | "title": "Отдельные наботы данных", 44 | "paragraph1": "Изучайте отдельные наборы данных или вставьте CID, чтобы увидеть, как эти данные структурированы и ссылаемы через протоколы." 45 | }, 46 | "explorer": { 47 | "step1": { 48 | "title": "IPLD Explorer", 49 | "paragraph1": "Ваше путешествие с проводником IPLD начинается здесь. Нажмите \"Далее\" чтобы узнать больше." 50 | }, 51 | "step2": { 52 | "title": "Хлебные крошки", 53 | "paragraph1": "Путь, ведущий к этой ноде.", 54 | "paragraph2": "Нажмите на него, чтобы пройтись по его родителям или детям." 55 | }, 56 | "step3": { 57 | "title": "Информация о ноде", 58 | "paragraph1": "Здесь находится подробная информация об узле и вы можете открыть его в шлюзе IPFS.", 59 | "paragraph2": "Если узел это директория, вы можете увидеть его детей и переходить на них." 60 | }, 61 | "step4": { 62 | "title": "Инфо о CID", 63 | "paragraph1": "Разложенный CID, чтобы вы могли узнать его значение." 64 | }, 65 | "step5": { 66 | "title": "Визуальное представление", 67 | "paragraph1": "Визуальное представление узла и его детей" 68 | } 69 | } 70 | }, 71 | "errors": { 72 | "BlockFetchTimeoutError": "Failed to fetch content in {timeout}s. Please refresh the page to retry or try a different CID." 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /public/locales/sv/explore.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": "IPLD-utforskaren", 3 | "tabName": "Utforska", 4 | "homeLink": "Hem", 5 | "UpdateAvailable": { 6 | "paragraph1": "A new version of IPLD Explorer is available; <1>please reload." 7 | }, 8 | "StartExploringPage": { 9 | "title": "Utforska - IPLD", 10 | "header": "Utforksa Merkle-skogen", 11 | "leadParagraph": "Klistrain en CID i rutan för att hämta IPLD-noden den adresserar, eller välj ett presenterat dataset.", 12 | "noDataAvailable": "Inga data tillgängliga" 13 | }, 14 | "AboutIpld": { 15 | "paragraph1":"<0>IPLD is <1>the data model of the content-addressable web. It allows us to treat all hash-linked data structures as subsets of a unified information space, unifying all data models that link data with hashes as instances of IPLD.", 16 | "paragraph2": "<0>Content addressing through hashes has become a widely-used means of connecting data in distributed systems, from the blockchains that run your favorite cryptocurrencies, to the commits that back your code, to the web’s content at large. Yet, whilst all of these tools rely on some common primitives, their specific underlying data structures are not interoperable.", 17 | "paragraph3": "<0>Enter IPLD: a single namespace for all hash-inspired protocols. Through IPLD, links can be traversed across protocols, allowing you to explore data regardless of the underlying protocol." 18 | }, 19 | "ExplorePage": { 20 | "title": "Utforskar - IPLD" 21 | }, 22 | "IpldExploreForm": { 23 | "explore": "Utforska" 24 | }, 25 | "IpldCarExploreForm": { 26 | "imports": "Importera", 27 | "uploadCarFile": "Upload CAR File" 28 | }, 29 | "CidInfo": { 30 | "header": "CID info", 31 | "hashDigest": "Hash digest" 32 | }, 33 | "ObjectInfo": { 34 | "publicGateway": "View on Public Gateway", 35 | "privateGateway": "View on Local Gateway" 36 | }, 37 | "base": "bas", 38 | "version": "version", 39 | "codec": "codec", 40 | "multihash": "multihash", 41 | "tour": { 42 | "projects": { 43 | "title": "Featured datasets", 44 | "paragraph1": "Explore the featured datasets or paste a CID to see how that data is structured and linked across protocols." 45 | }, 46 | "explorer": { 47 | "step1": { 48 | "title": "IPLD-utforskaren", 49 | "paragraph1": "Your journey with IPLD Explorer starts here. Click \"Next\" to learn more." 50 | }, 51 | "step2": { 52 | "title": "Brödsmulor", 53 | "paragraph1": "The path that leads to this node.", 54 | "paragraph2": "Click on it to traverse to its parents or children." 55 | }, 56 | "step3": { 57 | "title": "Node info", 58 | "paragraph1": "Here you have detailed info about the node and you can open it in the IPFS gateway.", 59 | "paragraph2": "If the node is a directory you can see its children and navigate to them." 60 | }, 61 | "step4": { 62 | "title": "CID info", 63 | "paragraph1": "The decomposed CID so you can learn its meaning." 64 | }, 65 | "step5": { 66 | "title": "Visual representation", 67 | "paragraph1": "A visual representation of the node and its children." 68 | } 69 | } 70 | }, 71 | "errors": { 72 | "BlockFetchTimeoutError": "Failed to fetch content in {timeout}s. Please refresh the page to retry or try a different CID." 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /public/locales/tr/explore.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": "IPLD Explorer", 3 | "tabName": "Keşfet", 4 | "homeLink": "Ana Sayfa", 5 | "UpdateAvailable": { 6 | "paragraph1": "IPLD Explorer'ın yeni bir sürümü mevcut; <1>lütfen yeniden yükleyin." 7 | }, 8 | "StartExploringPage": { 9 | "title": "Keşfet - IPLD", 10 | "header": "Merkle Ormanı'nı keşfedin", 11 | "leadParagraph": "Adres verdiği IPLD düğümünü getirmek için kutuya bir CID yapıştırın veya öne çıkan bir veri kümesi seçin.", 12 | "noDataAvailable": "Veri yok" 13 | }, 14 | "AboutIpld": { 15 | "paragraph1":"<0>IPLD, <1> içerik adresli web'in veri modelidir. Özüt bağlantılı tüm veri yapılarını birleşik bir bilgi alanının alt kümeleri olarak ele almamızı ve verileri IPLD örnekleri olarak özütlerle birbirine bağlayan tüm veri modellerini birleştirmemizi sağlar.", 16 | "paragraph2": "<0>Özütler aracılığıyla içerik adresleme, en sevdiğiniz kripto para birimlerini çalıştıran blok zincirlerinden kodunuzu destekleyen işlemlere ve büyük ölçüde web içeriğine kadar dağıtılmış sistemlerdeki verileri bağlamak için yaygın olarak kullanılan bir araç haline geldi. Yine de, bu araçların tümü bazı ortak ilkellere dayanırken, bunların temelindeki belirli veri yapıları birlikte çalışabilir değildir.", 17 | "paragraph3": "<0>IPLD'yi girin: özüt esinli tüm protokoller için tek bir ad alanı. IPLD aracılığıyla, bağlantılar, temel protokolden bağımsız olarak verileri keşfetmenize olanak tanıyan protokoller arasında gezilebilir." 18 | }, 19 | "ExplorePage": { 20 | "title": "Keşfediliyor - IPLD" 21 | }, 22 | "IpldExploreForm": { 23 | "explore": "Keşfet" 24 | }, 25 | "IpldCarExploreForm": { 26 | "imports": "İçe aktar", 27 | "uploadCarFile": "Upload CAR File" 28 | }, 29 | "CidInfo": { 30 | "header": "CID bilgisi", 31 | "hashDigest": "Özüt özeti" 32 | }, 33 | "ObjectInfo": { 34 | "publicGateway": "View on Public Gateway", 35 | "privateGateway": "View on Local Gateway" 36 | }, 37 | "base": "temel", 38 | "version": "sürüm", 39 | "codec": "kodek", 40 | "multihash": "çoklu özüt", 41 | "tour": { 42 | "projects": { 43 | "title": "Öne çıkan veri kümeleri", 44 | "paragraph1": "Verilerin nasıl yapılandırıldığını ve protokoller arasında nasıl bağlandığını görmek için öne çıkan veri kümelerini keşfedin veya bir CID yapıştırın." 45 | }, 46 | "explorer": { 47 | "step1": { 48 | "title": "IPLD Explorer", 49 | "paragraph1": "IPLD Explorer ile yolculuğunuz burada başlıyor. Daha fazla bilgi edinmek için \"İleri\" yi tıklayın." 50 | }, 51 | "step2": { 52 | "title": "Kırıntılar", 53 | "paragraph1": "Bu düğüme götüren yol.", 54 | "paragraph2": "Ebeveynlerine veya çocuklarına geçmek için üzerine tıklayın." 55 | }, 56 | "step3": { 57 | "title": "Düğüm bilgisi", 58 | "paragraph1": "Burada düğüm hakkında ayrıntılı bilgi var ve onu IPFS ağ geçidinde açabilirsiniz.", 59 | "paragraph2": "Düğüm bir dizinse, alt öğelerini görebilir ve onlara gidebilirsiniz." 60 | }, 61 | "step4": { 62 | "title": "CID bilgisi", 63 | "paragraph1": "Ayrıştırılmış CID, böylece anlamını öğrenebilirsiniz." 64 | }, 65 | "step5": { 66 | "title": "Görsel sunum", 67 | "paragraph1": "Düğümün ve çocuklarının görsel bir temsili." 68 | } 69 | } 70 | }, 71 | "errors": { 72 | "BlockFetchTimeoutError": "Failed to fetch content in {timeout}s. Please refresh the page to retry or try a different CID." 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /public/locales/zh-CN/explore.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": "IPLD 浏览器", 3 | "tabName": "浏览", 4 | "homeLink": "首页", 5 | "UpdateAvailable": { 6 | "paragraph1": "IPLD浏览器有新版本可用;<1>请重新加载。" 7 | }, 8 | "StartExploringPage": { 9 | "title": "浏览 - IPLD", 10 | "header": "浏览 Merkle Forest", 11 | "leadParagraph": "粘贴 CID 到文本框以获取它的寻址 IPLD 节点,或选择一个特色数据集。", 12 | "noDataAvailable": "无可用数据" 13 | }, 14 | "AboutIpld": { 15 | "paragraph1":"<0>IPLD 是<1>内容可寻址网络的数据模型。 它允许我们将所有与哈希链接的数据结构视为统一信息空间的子集,统一所有将数据与哈希作为 IPLD 实例链接的数据模型。", 16 | "paragraph2": "<0>通过哈希进行内容寻址已成为一种广泛使用的方法,用于连接分布式系统中的数据,从运行您中意加密货币的区块链,到保证你的代码提交,再到整个网络的内容。 然而,虽然所有这些工具都依赖于一些常见的基本类型,但它们的特定底层数据结构却无法互操作。", 17 | "paragraph3": "<0>输入 IPLD :所有哈希启发协议的单个命名空间。 通过 IPLD ,可以跨协议遍历链接,无论底层协议如何,都可以浏览数据。" 18 | }, 19 | "ExplorePage": { 20 | "title": "浏览 - IPLD" 21 | }, 22 | "IpldExploreForm": { 23 | "explore": "浏览" 24 | }, 25 | "IpldCarExploreForm": { 26 | "imports": "导入", 27 | "uploadCarFile": "上传CAR文件" 28 | }, 29 | "CidInfo": { 30 | "header": "CID 信息", 31 | "hashDigest": "哈希摘要" 32 | }, 33 | "ObjectInfo": { 34 | "publicGateway": "在公共网关上查看", 35 | "privateGateway": "在本地网关上查看" 36 | }, 37 | "base": "基点", 38 | "version": "版本", 39 | "codec": "编解码", 40 | "multihash": "聚合哈希", 41 | "tour": { 42 | "projects": { 43 | "title": "特征数据集", 44 | "paragraph1": "浏览特色数据集或粘贴 CID 来查看数据是如何通过协议被连接并组织起来的。" 45 | }, 46 | "explorer": { 47 | "step1": { 48 | "title": "IPLD 浏览器", 49 | "paragraph1": "从这里开始您的IPLD浏览器之旅。单击“下一步”了解更多信息。" 50 | }, 51 | "step2": { 52 | "title": "导航栏", 53 | "paragraph1": "通向此节点的路径。", 54 | "paragraph2": "点击它可以查看其上下级节点。" 55 | }, 56 | "step3": { 57 | "title": "节点信息", 58 | "paragraph1": "您可以在此处获得有关节点的详细信息,并在 IPFS 网关中打开它。", 59 | "paragraph2": "如果该节点是一个目录,则可以查看其子节点并跳转。" 60 | }, 61 | "step4": { 62 | "title": "CID 信息", 63 | "paragraph1": "查询分解 CID 使您可以了解其含义。" 64 | }, 65 | "step5": { 66 | "title": "可视化呈现", 67 | "paragraph1": "节点及其子节点的可视化呈现。" 68 | } 69 | } 70 | }, 71 | "errors": { 72 | "BlockFetchTimeoutError": "在{timeout}内抓取内容失败. 请刷新页面或尝试一个不同的CID" 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /public/locales/zh-TW/explore.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": "IPLD 瀏覽器", 3 | "tabName": "瀏覽", 4 | "homeLink": "首頁", 5 | "UpdateAvailable": { 6 | "paragraph1": "A new version of IPLD Explorer is available; <1>please reload." 7 | }, 8 | "StartExploringPage": { 9 | "title": "瀏覽 - IPLD", 10 | "header": "瀏覽Merkle森林", 11 | "leadParagraph": "Paste a CID into the box to fetch the IPLD node it addresses, or choose a featured dataset.", 12 | "noDataAvailable": "沒有可用數據" 13 | }, 14 | "AboutIpld": { 15 | "paragraph1":"<0>IPLD is <1>the data model of the content-addressable web. It allows us to treat all hash-linked data structures as subsets of a unified information space, unifying all data models that link data with hashes as instances of IPLD.", 16 | "paragraph2": "<0>Content addressing through hashes has become a widely-used means of connecting data in distributed systems, from the blockchains that run your favorite cryptocurrencies, to the commits that back your code, to the web’s content at large. Yet, whilst all of these tools rely on some common primitives, their specific underlying data structures are not interoperable.", 17 | "paragraph3": "<0>Enter IPLD: a single namespace for all hash-inspired protocols. Through IPLD, links can be traversed across protocols, allowing you to explore data regardless of the underlying protocol." 18 | }, 19 | "ExplorePage": { 20 | "title": "瀏覽 - IPLD" 21 | }, 22 | "IpldExploreForm": { 23 | "explore": "瀏覽" 24 | }, 25 | "IpldCarExploreForm": { 26 | "imports": "匯入", 27 | "uploadCarFile": "Upload CAR File" 28 | }, 29 | "CidInfo": { 30 | "header": "CID info", 31 | "hashDigest": "雜湊摘要" 32 | }, 33 | "ObjectInfo": { 34 | "publicGateway": "View on Public Gateway", 35 | "privateGateway": "View on Local Gateway" 36 | }, 37 | "base": "基礎", 38 | "version": "版本", 39 | "codec": "編解碼器", 40 | "multihash": "多重雜湊", 41 | "tour": { 42 | "projects": { 43 | "title": "Featured datasets", 44 | "paragraph1": "Explore the featured datasets or paste a CID to see how that data is structured and linked across protocols." 45 | }, 46 | "explorer": { 47 | "step1": { 48 | "title": "IPLD 瀏覽器", 49 | "paragraph1": "Your journey with IPLD Explorer starts here. Click \"Next\" to learn more." 50 | }, 51 | "step2": { 52 | "title": "導覽列", 53 | "paragraph1": "The path that leads to this node.", 54 | "paragraph2": "Click on it to traverse to its parents or children." 55 | }, 56 | "step3": { 57 | "title": "節點信息", 58 | "paragraph1": "Here you have detailed info about the node and you can open it in the IPFS gateway.", 59 | "paragraph2": "If the node is a directory you can see its children and navigate to them." 60 | }, 61 | "step4": { 62 | "title": "CID info", 63 | "paragraph1": "The decomposed CID so you can learn its meaning." 64 | }, 65 | "step5": { 66 | "title": "Visual representation", 67 | "paragraph1": "A visual representation of the node and its children." 68 | } 69 | } 70 | }, 71 | "errors": { 72 | "BlockFetchTimeoutError": "Failed to fetch content in {timeout}s. Please refresh the page to retry or try a different CID." 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/components/ExplorePage.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import i18n from '../i18n-decorator.tsx' 3 | import { ExploreProvider, ExploreState } from '../providers/explore.tsx' 4 | import { HeliaProvider } from '../providers/helia.tsx' 5 | import ExplorePage from './ExplorePage.tsx' 6 | 7 | const defaultState: ExploreState = { 8 | path: 'QmdmQXB2mzChmMeKY47C43LxUdg1NDJ5MWcKMKxDu7RgQm', 9 | canonicalPath: 'QmdmQXB2mzChmMeKY47C43LxUdg1NDJ5MWcKMKxDu7RgQm', 10 | error: null, 11 | targetNode: { 12 | type: 'dag-pb', 13 | format: 'unixfs', 14 | data: { 15 | type: 'directory', 16 | data: new Uint8Array([0]), 17 | blockSizes: [BigInt(0)] 18 | }, 19 | cid: 'QmdmQXB2mzChmMeKY47C43LxUdg1NDJ5MWcKMKxDu7RgQm', 20 | links: [ 21 | { 22 | source: 'QmdmQXB2mzChmMeKY47C43LxUdg1NDJ5MWcKMKxDu7RgQm', 23 | target: 'QmdC5Hav9zdn2iS75reafXBq1PH4EnqUmoxwoxkS5QtuME', 24 | path: '10 - Pi Equals', 25 | size: BigInt(0), 26 | index: 0 27 | } 28 | ] 29 | }, 30 | localPath: '', 31 | nodes: [ 32 | { 33 | type: 'dag-pb', 34 | cid: 'QmdmQXB2mzChmMeKY47C43LxUdg1NDJ5MWcKMKxDu7RgQm', 35 | links: [ 36 | { 37 | source: 'QmdmQXB2mzChmMeKY47C43LxUdg1NDJ5MWcKMKxDu7RgQm', 38 | target: 'QmdC5Hav9zdn2iS75reafXBq1PH4EnqUmoxwoxkS5QtuME', 39 | path: '10 - Pi Equals' 40 | } 41 | ] 42 | } 43 | ], 44 | pathBoundaries: [] 45 | 46 | } 47 | 48 | /** 49 | * @type {import('@storybook/react').Meta} 50 | */ 51 | const meta = { 52 | title: 'Explore page', 53 | component: ExplorePage, 54 | decorators: [i18n], 55 | render: () => ( 56 |
57 | 58 | 59 | 60 | 61 | 62 |
63 | ) 64 | } 65 | export default meta 66 | 67 | export const Default = () => ( 68 |
69 | 70 | 71 | 72 | 73 | 74 |
75 | ) 76 | 77 | Default.story = { 78 | name: 'default' 79 | } 80 | -------------------------------------------------------------------------------- /src/components/StartExploringPage.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import i18n from '../i18n-decorator' 3 | import StartExploringPage from './StartExploringPage' 4 | import { Meta } from '@storybook/react' 5 | 6 | const meta: Meta = { 7 | title: 'Start Exploring page', 8 | component: StartExploringPage, 9 | decorators: [i18n], 10 | render: () => ( 11 | 12 | ) 13 | } 14 | export default meta 15 | 16 | export const Default = () => 17 | 18 | Default.story = { 19 | name: 'default' 20 | } 21 | -------------------------------------------------------------------------------- /src/components/StartExploringPage.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Helmet from 'react-helmet' 3 | import { useTranslation } from 'react-i18next' 4 | import ReactJoyride from 'react-joyride' 5 | import { type ExplorePageLink, explorePageLinks } from '../lib/explore-page-suggestions.js' 6 | import { projectsTour } from '../lib/tours.js' 7 | import { AboutIpld } from './about/AboutIpld.js' 8 | import IpldExploreForm from './explore/IpldExploreForm.js' 9 | import { type NodeStyle, colorForNode, nameForNode, shortNameForNode } from './object-info/ObjectInfo.js' 10 | 11 | const ExploreSuggestion = ({ cid, name, type }: { cid: string, name: string, type: NodeStyle }): JSX.Element => ( 12 | 13 | 14 | {shortNameForNode(type)} 15 | 16 | 17 |

{name}

18 | {cid} 19 |
20 |
21 | ) 22 | 23 | interface StartExploringPageProps { 24 | embed?: any 25 | runTour?: boolean 26 | joyrideCallback?: any 27 | links?: ExplorePageLink[] 28 | } 29 | 30 | export const StartExploringPage: React.FC = ({ embed, runTour = false, joyrideCallback, links }) => { 31 | const { t } = useTranslation('explore') 32 | 33 | return ( 34 |
35 | 36 | {t('StartExploringPage.title')} 37 | 38 |
39 |
40 |
41 |

{t('StartExploringPage.header')}

42 |

{t('StartExploringPage.leadParagraph')}

43 |
44 | {embed != null ? : null} 45 |
    46 | {(links ?? explorePageLinks).map((suggestion) => ( 47 |
  • 48 | 49 |
  • 50 | ))} 51 |
52 |
53 |
54 | 55 |
56 |
57 | 58 | 65 |
66 | ) 67 | } 68 | 69 | export default StartExploringPage 70 | -------------------------------------------------------------------------------- /src/components/about/AboutIpld.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Trans, useTranslation } from 'react-i18next' 3 | import Box from '../box/Box.js' 4 | import ipldLogoSrc from './ipld.svg' 5 | 6 | export const AboutIpld: React.FC = () => { 7 | const { t } = useTranslation('explore') 8 | 9 | return ( 10 | 11 |
12 | 13 | IPLD 14 | 15 |
16 | 17 |

IPLD is the data model of the content-addressable web. It allows us to treat all hash-linked data structures as subsets of a unified information space, unifying all data models that link data with hashes as instances of IPLD.

18 |
19 | 20 |

Content addressing through hashes has become a widely-used means of connecting data in distributed systems, from the blockchains that run your favorite cryptocurrencies, to the commits that back your code, to the web’s content at large. Yet, whilst all of these tools rely on some common primitives, their specific underlying data structures are not interoperable.

21 |
22 | 23 |

Enter IPLD: a single namespace for all hash-inspired protocols. Through IPLD, links can be traversed across protocols, allowing you to explore data regardless of the underlying protocol.

24 |
25 |
26 | ) 27 | } 28 | -------------------------------------------------------------------------------- /src/components/about/ipld.svg: -------------------------------------------------------------------------------- 1 | logotype_IPLD -------------------------------------------------------------------------------- /src/components/box/Box.tsx: -------------------------------------------------------------------------------- 1 | import React, { type CSSProperties, type PropsWithChildren } from 'react' 2 | import ErrorBoundary from '../error/ErrorBoundary.js' 3 | 4 | interface BoxProps { 5 | className?: string 6 | style?: CSSProperties 7 | } 8 | 9 | export const Box: React.FC> = ({ 10 | className = 'pa4', 11 | style, 12 | children 13 | }) => { 14 | return ( 15 |
16 | 17 | {children} 18 | 19 |
20 | ) 21 | } 22 | 23 | export default Box 24 | -------------------------------------------------------------------------------- /src/components/cid-info/CidInfo.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import i18n from '../../i18n-decorator' 3 | import CidInfo from './CidInfo' 4 | 5 | /** 6 | * @type {import('@storybook/react').Meta} 7 | */ 8 | const meta = { 9 | title: 'CID Info', 10 | component: CidInfo, 11 | decorators: [i18n], 12 | // render: () => ( 13 | // 14 | // ) 15 | } 16 | 17 | export default meta 18 | 19 | export const CidV0DagPb = () => 20 | 21 | CidV0DagPb.story = { 22 | name: 'cid v0 dag-pb' 23 | } 24 | 25 | export const CidV1B32Raw = () => 26 | 27 | CidV1B32Raw.story = { 28 | name: 'cid v1 b32 raw' 29 | } 30 | 31 | export const CidV1B32DagPb = () => ( 32 | 33 | ) 34 | 35 | CidV1B32DagPb.story = { 36 | name: 'cid v1 b32 dag-pb' 37 | } 38 | 39 | export const CidV1B36Peerid = () => ( 40 | 41 | ) 42 | 43 | CidV1B36Peerid.story = { 44 | name: 'cid v1 b36 peerid' 45 | } 46 | 47 | export const CidV1B58Sha3 = () => ( 48 | 52 | ) 53 | 54 | CidV1B58Sha3.story = { 55 | name: 'cid v1 b58 sha3' 56 | } 57 | 58 | export const CidV1B58Blake2B512 = () => ( 59 | 63 | ) 64 | 65 | CidV1B58Blake2B512.story = { 66 | name: 'cid v1 b58 blake2b-512' 67 | } 68 | 69 | export const CidError = () => 70 | 71 | CidError.story = { 72 | name: 'cid error' 73 | } 74 | 75 | export const NoCid = () => 76 | 77 | NoCid.story = { 78 | name: 'no cid' 79 | } 80 | -------------------------------------------------------------------------------- /src/components/cid-info/CidInfo.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react' 2 | import { useTranslation } from 'react-i18next' 3 | import extractInfo, { type ExtractedInfo } from '../../lib/extract-info.js' 4 | 5 | export interface CidInfoProps extends React.HTMLAttributes { 6 | cid: string | null 7 | } 8 | 9 | export const CidInfo: React.FC = ({ cid, className, ...props }) => { 10 | const { t } = useTranslation('explore') 11 | const [cidErr, setCidErr] = useState(null) 12 | const [cidInfo, setCidInfo] = useState(null) 13 | useEffect(() => { 14 | const asyncFn = async (): Promise => { 15 | try { 16 | if (cid != null) { 17 | setCidInfo(await extractInfo(cid)) 18 | } 19 | } catch (err: any) { 20 | console.error(err) 21 | setCidErr(err) 22 | } 23 | } 24 | void asyncFn() 25 | }, [cid]) 26 | 27 | if (cid == null) { 28 | return null 29 | } 30 | 31 | return ( 32 |
33 | 38 | {(cidInfo == null) 39 | ? null 40 | : ( 41 |
42 |
43 | {cid.toString()} 44 |
45 |
46 | {cidInfo.humanReadable} 47 |
48 | 51 | 55 | {t('multihash')} 56 | 57 |
58 |
59 | 0x 60 | {cidInfo.hashFnCode} 61 | {cidInfo.hashLengthCode} 62 | 63 | {cidInfo.hashValueIn32CharChunks.map(chunk => ( 64 | {chunk.join('')}
65 | ))} 66 |
67 | 70 |
71 | 72 | 0x 73 | {cidInfo.hashFnCode} = {cidInfo.hashFn} 74 | 75 |
76 | 0x 77 | {cidInfo.hashLengthCode} = {cidInfo.hashLengthInBits} bits 78 |
79 |
80 |
81 |
82 |
83 | )} 84 | {(cidErr == null) 85 | ? null 86 | : ( 87 |
88 |
89 | {cid.toString()} 90 |
91 |
{cidErr.message}
92 |
93 | )} 94 |
95 | ) 96 | } 97 | 98 | export default CidInfo 99 | -------------------------------------------------------------------------------- /src/components/cid-info/decode-cid.ts: -------------------------------------------------------------------------------- 1 | import { type CID } from 'multiformats' 2 | import baseImporter from '../../lib/base-importer' 3 | import { toCidOrNull } from '../../lib/cid.js' 4 | import { getHasherForCode } from '../../lib/hash-importer' 5 | import type { DecodedCidMultihash, DecodedCidMulticodec } from '../../types.js' 6 | 7 | export interface DecodedCid { 8 | cid: CID 9 | multibase: Awaited> 10 | multicodec: DecodedCidMulticodec 11 | multihash: DecodedCidMultihash 12 | } 13 | 14 | export async function decodeCid (value: string | CID): Promise { 15 | const cid = toCidOrNull(value) 16 | try { 17 | if (cid == null) { 18 | throw new Error(`Failed to parse CID from value '${value}'`) 19 | } 20 | const multihasher = await getHasherForCode(cid.multihash.code) 21 | const multibase = await baseImporter(cid.toString().substring(0, 1)) 22 | const multicodecName = cid.code 23 | let multicodecCode 24 | if (cid.version === 0) { 25 | multicodecCode = 'implicit' 26 | } else { 27 | multicodecCode = `0x${cid.code.toString(16)}` 28 | } 29 | 30 | return { 31 | cid, 32 | multibase, 33 | multicodec: { 34 | name: multicodecName, 35 | code: multicodecCode 36 | }, 37 | multihash: { 38 | name: multihasher.name, 39 | ...cid.multihash 40 | } 41 | } 42 | } catch (err) { 43 | console.error('Failed to decode CID', err) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/components/cid/Cid.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Cid from './Cid' 3 | import { CID } from 'multiformats/cid' 4 | import { Meta, StoryObj } from '@storybook/react' 5 | 6 | const meta: Meta = { 7 | title: 'CID', 8 | component: Cid, 9 | args: { 10 | className: 'db ma2 monospace' 11 | } 12 | } 13 | 14 | export default meta 15 | 16 | type Story = StoryObj; 17 | 18 | export const CidV0: Story = { 19 | name: 'CID v0', 20 | args: { 21 | value: CID.parse('QmYPNmahJAvkMTU6tDx5zvhEkoLzEFeTDz6azDCSNqzKkW'), 22 | } 23 | } 24 | 25 | 26 | export const CidV1: Story = { 27 | name: 'CID v1', 28 | args: { 29 | value: CID.parse('zb2rhZMC2PFynWT7oBj7e6BpDpzge367etSQi6ZUA81EVVCxG'), 30 | }, 31 | } 32 | 33 | 34 | export const CidV1Sha3: Story = { 35 | name: 'CID v1 sha3', 36 | args: { 37 | value: CID.parse('zB7NbGN5wyfSbNNNwo3smZczHZutiWERdvWuMcHXTj393RnbhwsHjrP7bPDRPA79YWPbS69cZLWXSANcwUMmk4Rp3hP9Y'), 38 | } 39 | } 40 | 41 | 42 | export const ShortestCid: Story = { 43 | name: 'Shortest CID', 44 | args: { 45 | value: CID.parse('bafkqaaik'), 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/components/cid/Cid.tsx: -------------------------------------------------------------------------------- 1 | import { type CID } from 'multiformats/cid' 2 | import React, { type HTMLProps } from 'react' 3 | 4 | export interface StringValue { 5 | value: string 6 | start: string 7 | end: string 8 | } 9 | export function cidStartAndEnd (value: string): StringValue { 10 | const chars = value.split('') 11 | if (chars.length <= 9) { 12 | return { 13 | value, 14 | start: value, 15 | end: '' 16 | } 17 | } 18 | const start = chars.slice(0, 4).join('') 19 | const end = chars.slice(chars.length - 4).join('') 20 | return { 21 | value, 22 | start, 23 | end 24 | } 25 | } 26 | 27 | export function shortCid (value: any): string { 28 | const { start, end } = cidStartAndEnd(value) 29 | return `${start}…${end}` 30 | } 31 | 32 | export interface CidProps extends Omit, 'value'> { 33 | value: CID 34 | } 35 | 36 | const Cid: React.FC = ({ value, title, style, ...props }) => { 37 | style = Object.assign({}, { 38 | textDecoration: 'none' 39 | }, style) 40 | const cidStr = value.toString() 41 | const { start, end } = cidStartAndEnd(cidStr) 42 | return ( 43 | 44 | {start} 45 | {start === cidStr ? null : } 46 | {end} 47 | 48 | ) 49 | } 50 | 51 | export default Cid 52 | -------------------------------------------------------------------------------- /src/components/error/ErrorBoundary.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ErrorIcon from '../../icons/GlyphSmallCancel' 3 | 4 | class ErrorBoundary extends React.Component { 5 | state = { 6 | hasError: false 7 | } 8 | 9 | static defaultProps = { 10 | fallback: ErrorIcon 11 | } 12 | 13 | componentDidCatch (error: Error, info: React.ErrorInfo): void { 14 | this.setState({ hasError: true }) 15 | console.error(error) 16 | } 17 | 18 | render (): JSX.Element { 19 | const { hasError } = this.state 20 | const { children, fallback: Fallback } = this.props 21 | return hasError ? as React.ReactElement : children 22 | } 23 | } 24 | 25 | export default ErrorBoundary 26 | -------------------------------------------------------------------------------- /src/components/explore/IpldCarExploreForm.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import i18n from '../../i18n-decorator.tsx' 3 | import { ExploreProvider } from '../../providers/explore.tsx' 4 | import { HeliaProvider } from '../../providers/helia.tsx' 5 | import IpldCarExploreForm from './IpldCarExploreForm.tsx' 6 | 7 | /** 8 | * @type {import('@storybook/react').Meta} 9 | */ 10 | const meta = { 11 | title: 'Explore Car form', 12 | component: IpldCarExploreForm, 13 | decorators: [i18n], 14 | render: () => ( 15 | 16 | ) 17 | } 18 | 19 | export default meta 20 | 21 | export const Default = () => ( 22 |
23 | 24 | 25 | 26 | 27 | 28 |
29 | ) 30 | 31 | Default.story = { 32 | name: 'default' 33 | } 34 | -------------------------------------------------------------------------------- /src/components/explore/IpldCarExploreForm.tsx: -------------------------------------------------------------------------------- 1 | import React, { useCallback, type ChangeEvent, type FormEvent } from 'react' 2 | import { useTranslation } from 'react-i18next' 3 | import { useExplore } from '../../providers/explore.js' 4 | import { useHelia } from '../../providers/helia.js' 5 | import spinnerImage from './spinner.svg' 6 | import uploadImage from './upload.svg' 7 | 8 | export const IpldCarExploreForm: React.FC = () => { 9 | const { t } = useTranslation('explore') 10 | // const [file, setFile] = useState({}) 11 | const { doUploadUserProvidedCar } = useExplore() 12 | const { selectHeliaReady } = useHelia() 13 | 14 | const handleOnSubmit = (evt: FormEvent): void => { 15 | evt.preventDefault() 16 | } 17 | 18 | const handleOnChange = useCallback((evt: ChangeEvent): void => { 19 | // Change the state. 20 | const imageFileLoader = document.getElementById('car-loader-image') as HTMLImageElement | null 21 | if (imageFileLoader == null) { 22 | console.error('cannot find element with id "car-loader-image"') 23 | return 24 | } 25 | imageFileLoader.src = spinnerImage 26 | 27 | // Get the file, upload car. 28 | const carFileInputEl = document.getElementById('car-file') as HTMLInputElement | null 29 | if (carFileInputEl == null) { 30 | console.error('cannot find element with id "car-file"') 31 | return 32 | } 33 | const selectedFile = carFileInputEl.files?.[0] 34 | if (selectedFile == null) { 35 | console.error('no file selected') 36 | return 37 | } 38 | void doUploadUserProvidedCar(selectedFile, uploadImage) 39 | }, [doUploadUserProvidedCar]) 40 | 41 | return ( 42 |
43 |
44 |
45 | 55 | {t('IpldCarExploreForm.uploadCarFile')} 56 | placeholder for upload and loader 63 |
64 |
65 |
66 | ) 67 | } 68 | 69 | export default IpldCarExploreForm 70 | -------------------------------------------------------------------------------- /src/components/explore/IpldExploreErrorComponent.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { useTranslation } from 'react-i18next' 3 | import { useExplore } from '../../providers/explore' 4 | import type IpldExploreError from '../../lib/errors' 5 | 6 | export interface IpldExploreErrorComponentProps { 7 | error: IpldExploreError | null 8 | } 9 | 10 | const TroubleshootingTips: React.FC<{ cid: string }> = ({ cid }) => { 11 | const { t } = useTranslation('explore', { keyPrefix: 'errors' }) 12 | if (cid == null) return null 13 | 14 | return ( 15 | <> 16 |
17 |

{t('troubleshootingTips.title')}

18 |
    19 |
  • {t('troubleshootingTips.refresh')}
  • 20 |
  • {t('troubleshootingTips.checkConnection')}
  • 21 |
  • {t('troubleshootingTips.tryLater')}
  • 22 |
  • {t('troubleshootingTips.checkCidSyntax')}
  • 23 |
24 |
25 | 35 | 36 | ) 37 | } 38 | 39 | export function IpldExploreErrorComponent ({ error }: IpldExploreErrorComponentProps): JSX.Element | null { 40 | const { exploreState } = useExplore() 41 | const { path } = exploreState 42 | const [cid] = path?.split('/') ?? [] 43 | const { t } = useTranslation('explore', { keyPrefix: 'errors' }) 44 | if (error == null) return null 45 | 46 | // more self service 47 | return ( 48 |
49 |
50 |
{error.toString(t)}
51 | 52 |
53 |
54 | ) 55 | } 56 | -------------------------------------------------------------------------------- /src/components/explore/IpldExploreForm.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import i18n from '../../i18n-decorator.tsx' 3 | import { ExploreProvider } from '../../providers/explore.tsx' 4 | import { HeliaProvider } from '../../providers/helia.tsx' 5 | import IpldExploreForm from './IpldExploreForm.tsx' 6 | 7 | /** 8 | * @type {import('@storybook/react').Meta} 9 | */ 10 | const meta = { 11 | title: 'Explore form', 12 | component: IpldExploreForm, 13 | decorators: [i18n], 14 | render: () => ( 15 | 16 | ) 17 | } 18 | 19 | export default meta 20 | 21 | export const Default = () => ( 22 |
23 | 24 | 25 | 26 | 27 | 28 |
29 | ) 30 | 31 | Default.story = { 32 | name: 'default' 33 | } 34 | -------------------------------------------------------------------------------- /src/components/explore/IpldExploreForm.tsx: -------------------------------------------------------------------------------- 1 | import React, { type FormEvent, useState, type ChangeEvent } from 'react' 2 | import { useTranslation } from 'react-i18next' 3 | import StrokeIpld from '../../icons/StrokeIpld' 4 | import { useExplore } from '../../providers/explore' 5 | 6 | export const IpldExploreForm: React.FC = () => { 7 | const { t } = useTranslation('explore') 8 | 9 | const [path, setPath] = useState('') 10 | const [showEmptyError, setShowEmptyError] = useState(false) 11 | const { doExploreUserProvidedPath } = useExplore() 12 | 13 | const handleOnSubmit = (evt: FormEvent): void => { 14 | evt.preventDefault() 15 | if (path.trim().length === 0) { 16 | setShowEmptyError(true) 17 | return 18 | } 19 | doExploreUserProvidedPath(path) 20 | } 21 | 22 | const handleOnChange = (evt: ChangeEvent): void => { 23 | const newPath = evt.target.value 24 | if (showEmptyError && newPath.length > 0) { 25 | setShowEmptyError(false) 26 | } 27 | setPath(newPath) 28 | } 29 | 30 | const outline = showEmptyError ? '2px solid red' : '' 31 | const placeholder = showEmptyError ? 'QmHash can not be empty' : 'QmHash' 32 | 33 | return ( 34 |
35 |
36 |
37 | 47 | Paste in a CID or IPFS path 48 |
49 |
50 |
51 | 59 |
60 |
61 | ) 62 | } 63 | 64 | export default IpldExploreForm 65 | -------------------------------------------------------------------------------- /src/components/explore/spinner.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 11 | 12 | 13 | 17 | 21 | 22 | 23 | 27 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /src/components/explore/upload.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/components/graph-crumb/GraphCrumb.stories.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import GraphCrumb, { PathBoundary } from './GraphCrumb' 3 | import { CID } from 'multiformats/cid' 4 | import { Meta, StoryObj } from '@storybook/react/*' 5 | 6 | const meta: Meta = { 7 | title: 'IPLD Graph Crumbs', 8 | component: GraphCrumb, 9 | args: { 10 | className: 'ma3', 11 | pathBoundaries: [] 12 | }, 13 | } 14 | export default meta 15 | 16 | type Story = StoryObj; 17 | 18 | export const SinglePath: Story = { 19 | name: 'single path', 20 | args: { 21 | cid: CID.parse("zdpuAs8sJjcmsPUfB1bUViftCZ8usnvs2cXrPH6MDyT4z1111"), 22 | } 23 | } 24 | 25 | export const DeepPath: Story = { 26 | name: 'deep path', 27 | args: { 28 | ...SinglePath.args, 29 | pathBoundaries: [ 30 | { 31 | source: CID.parse('zdpuAs8sJjcmsPUfB1bUViftCZ8usnvs2cXrPH6MDyT4z1111'), 32 | target: CID.parse('zdpuAs8sJjcmsPUfB1bUViftCZ8usnvs2cXrPH6MDyT4z2222'), 33 | path: 'favourites/0' 34 | }, 35 | { 36 | source: CID.parse('zdpuAs8sJjcmsPUfB1bUViftCZ8usnvs2cXrPH6MDyT4z2222'), 37 | target: CID.parse('zdpuAs8sJjcmsPUfB1bUViftCZ8usnvs2cXrPH6MDyT4z3333'), 38 | path: 'artist' 39 | }, 40 | { 41 | source: CID.parse('zdpuAs8sJjcmsPUfB1bUViftCZ8usnvs2cXrPH6MDyT4z3333'), 42 | target: CID.parse('zdpuAs8sJjcmsPUfB1bUViftCZ8usnvs2cXrPH6MDyT4z4444'), 43 | path: 'bio' 44 | }, 45 | { 46 | source: CID.parse('zdpuAs8sJjcmsPUfB1bUViftCZ8usnvs2cXrPH6MDyT4z4444'), 47 | target: CID.parse('zdpuAs8sJjcmsPUfB1bUViftCZ8usnvs2cXrPH6MDyT4z5555'), 48 | path: 'avatar.jpg' 49 | }, 50 | ] 51 | } 52 | } 53 | 54 | export const DeepPathWithInvalidCid: Story = { 55 | ...DeepPath, 56 | name: 'deep path with invalid CID source', 57 | args: { 58 | ...DeepPath.args, 59 | // @ts-expect-error - invalid CID 60 | pathBoundaries: (DeepPath.args?.pathBoundaries as PathBoundary[]).map((b) => { 61 | if (b.path === 'avatar.jpg') { 62 | return { 63 | ...b, 64 | source: 'QmHash1111', 65 | } 66 | } 67 | return b 68 | }), 69 | } 70 | } 71 | 72 | export const DeepPathWithAllInvalidSourceCids: Story = { 73 | ...DeepPathWithInvalidCid, 74 | name: 'deep path with all invalid source CIDs', 75 | args: { 76 | ...DeepPathWithInvalidCid.args, 77 | // @ts-expect-error - invalid CIDs 78 | pathBoundaries: (DeepPathWithInvalidCid.args?.pathBoundaries as PathBoundary[]).map((b) => ({ 79 | source: 'QmHash1111', 80 | target: b.target, 81 | path: b.path 82 | })) 83 | } 84 | } 85 | 86 | 87 | export const TooDeepPath: Story = { 88 | name: 'too deep path', 89 | render: () => { 90 | const pathBoundaries: PathBoundary[] = [] 91 | for (let i = 1; i < 100; i++) { 92 | const suffix = i.toString().padStart(4, '1').replace(/0/g, 'z') 93 | pathBoundaries.push({ 94 | path: `favourites${suffix}`, 95 | source: CID.parse(`zdpuAs8sJjcmsPUfB1bUViftCZ8usnvs2cXrPH6MDyT4z${suffix}`), 96 | target: CID.parse(`zdpuAs8sJjcmsPUfB1bUViftCZ8usnvs2cXrPH6MDyT4z${suffix}`), 97 | }) 98 | } 99 | return 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/components/graph-crumb/graph-crumb.test.tsx: -------------------------------------------------------------------------------- 1 | // @vitest-environment jsdom 2 | /* global describe expect */ 3 | import { render, screen } from '@testing-library/react' 4 | import { CID } from 'multiformats/cid' 5 | import React from 'react' 6 | import '@testing-library/jest-dom' 7 | import GraphCrumb, { type PathBoundary } from './GraphCrumb' 8 | 9 | // Mock CID 10 | const mockCid = CID.parse('QmYPNmahJAvkMTU6tDx5zvhEkoLzEFeTDz6azDCSNqzKkW') 11 | const mockCidChild1 = CID.parse('zb2rhZMC2PFynWT7oBj7e6BpDpzge367etSQi6ZUA81EVVCxG') 12 | const mockCidChild2 = CID.parse('zB7NbGN5wyfSbNNNwo3smZczHZutiWERdvWuMcHXTj393RnbhwsHjrP7bPDRPA79YWPbS69cZLWXSANcwUMmk4Rp3hP9Y') 13 | const mockCidChild3 = CID.parse('bafkqaaik') 14 | 15 | // Mock PathBoundaries 16 | const mockPathBoundaries: PathBoundary[] = [ 17 | { path: 'path1', source: mockCid, target: mockCidChild1 }, 18 | { path: 'path2', source: mockCidChild1, target: mockCidChild2 }, 19 | { path: 'path3', source: mockCidChild2, target: mockCidChild3 }, 20 | { path: 'path4', source: mockCidChild3, target: mockCid } 21 | ] 22 | 23 | describe('GraphCrumb Component', () => { 24 | test('renders correctly with given props', () => { 25 | render() 26 | expect(screen.getByTitle(mockCid.toString())).toBeInTheDocument() 27 | expect(screen.getByText('path1')).toBeInTheDocument() 28 | expect(screen.getByText('path2')).toBeInTheDocument() 29 | expect(screen.getByText('path3')).toBeInTheDocument() 30 | expect(screen.getByText('path4')).toBeInTheDocument() 31 | }) 32 | }) 33 | -------------------------------------------------------------------------------- /src/components/graph/IpldGraph.stories.tsx: -------------------------------------------------------------------------------- 1 | import { action } from '@storybook/addon-actions' 2 | import React from 'react' 3 | import dagNodeD from '../object-info/fixtures/object-info-0-links.json' 4 | import dagNodeB from '../object-info/fixtures/object-info-36-links.json' 5 | import dagNodeA from '../object-info/fixtures/object-info-8-links.json' 6 | import IpldGraphCytoscape from './IpldGraphCytoscape' 7 | 8 | /** 9 | * @type {import('@storybook/react').Meta} 10 | */ 11 | const meta = { 12 | title: 'IPLD Graph', 13 | component: IpldGraphCytoscape, 14 | // decorators: [i18n], 15 | render: () => ( 16 | 17 | ) 18 | } 19 | 20 | export default meta 21 | 22 | export const Cytoscape8Links = () => ( 23 | 29 | ) 30 | 31 | Cytoscape8Links.story = { 32 | name: 'cytoscape 8 links' 33 | } 34 | 35 | export const Cytoscape36Links = () => ( 36 | 42 | ) 43 | 44 | Cytoscape36Links.story = { 45 | name: 'cytoscape 36 links' 46 | } 47 | 48 | export const Cytoscape0Links = () => ( 49 | 55 | ) 56 | 57 | Cytoscape0Links.story = { 58 | name: 'cytoscape 0 links' 59 | } 60 | -------------------------------------------------------------------------------- /src/components/graph/IpldGraph.tsx: -------------------------------------------------------------------------------- 1 | import React, { type HTMLProps } from 'react' 2 | 3 | export interface IpldGraphProps extends HTMLProps { 4 | root: string 5 | links: string[] 6 | } 7 | 8 | const IpldGraph: React.FC = ({ className, root, links }) => { 9 | const cls = `tc ma4 ${className}` 10 | return ( 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 |
52 | ) 53 | } 54 | 55 | export default IpldGraph 56 | -------------------------------------------------------------------------------- /src/components/loader/component-loader.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ComponentLoader from './component-loader.js' 3 | 4 | /** 5 | * @type {import('@storybook/react').Meta} 6 | */ 7 | const meta = { 8 | title: 'Loader', 9 | component: ComponentLoader, 10 | render: () => ( 11 | 12 | ) 13 | } 14 | export default meta 15 | 16 | export const _ComponentLoader = () => ( 17 |
18 | 19 |
20 | ) 21 | 22 | _ComponentLoader.story = { 23 | name: 'ComponentLoader' 24 | } 25 | -------------------------------------------------------------------------------- /src/components/loader/component-loader.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Box from '../box/Box.js' 3 | import { Loader } from './loader.js' 4 | 5 | const ComponentLoader: React.FC = () => ( 6 | 7 |
8 | 9 |
10 |
11 | ) 12 | 13 | export default ComponentLoader 14 | -------------------------------------------------------------------------------- /src/components/loader/loader.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styles from './loader.module.css' 3 | 4 | export const Loader: React.FC<{ color?: string }> = ({ color = 'light', ...props }) => { 5 | const className = `dib ${styles.laBallTrianglePath} ${color === 'dark' ? styles.laDark : ''} la-sm` 6 | return ( 7 |
8 |
12 |
13 |
14 |
15 |
16 |
17 | ) 18 | } 19 | -------------------------------------------------------------------------------- /src/components/object-info/ObjectInfo.stories.tsx: -------------------------------------------------------------------------------- 1 | import { action } from '@storybook/addon-actions' 2 | import React from 'react' 3 | import i18n from '../../i18n-decorator' 4 | import ObjectInfo from './ObjectInfo' 5 | import dagNodeC from './fixtures/object-info-1240-links.json' 6 | import dagNodeB from './fixtures/object-info-36-links.json' 7 | import dagNodeA from './fixtures/object-info-8-links.json' 8 | 9 | /** 10 | * @type {import('@storybook/react').Meta} 11 | */ 12 | const meta = { 13 | title: 'IPLD Node Info', 14 | component: ObjectInfo, 15 | decorators: [i18n], 16 | // render: () => ( 17 | // 18 | // ) 19 | } 20 | export default meta 21 | 22 | export const CidV0DagPb = () => ( 23 | 35 | ) 36 | 37 | CidV0DagPb.story = { 38 | name: 'cid v0 dag-pb' 39 | } 40 | 41 | export const CidV0DagPb36Links = () => ( 42 | 54 | ) 55 | 56 | CidV0DagPb36Links.story = { 57 | name: 'cid v0 dag-pb 36 links...' 58 | } 59 | 60 | export const CidV0DagPb1240Links = () => ( 61 | 73 | ) 74 | 75 | CidV0DagPb1240Links.story = { 76 | name: 'cid v0 dag-pb 1240 links...' 77 | } 78 | 79 | export const identityCID = () => ( 80 | 93 | ) 94 | identityCID.story = { 95 | name: 'identity CID' 96 | } 97 | 98 | -------------------------------------------------------------------------------- /src/components/object-info/fixtures/object-info-0-links.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { "type": "Buffer", "data": [8, 1] }, 3 | "links": [], 4 | "cid": "QmYPNmahJAvkMTU6tDx5zvhEkoLzEFeTDz6azDCSNqzKkW", 5 | "size": 10016715 6 | } 7 | -------------------------------------------------------------------------------- /src/components/object-info/fixtures/object-info-8-links.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { "type": "Buffer", "data": [8, 1] }, 3 | "links": [ 4 | { 5 | "path": "contact-ipfs", 6 | "size": 5814, 7 | "target": "QmXdUm5xgmmFK5ykH3Yvk2PFtL9eDs4FGJ9wpScXUMVsU1", 8 | "source": "QmYPNmahJAvkMTU6tDx5zvhEkoLzEFeTDz6azDCSNqzKkW" 9 | }, 10 | { 11 | "path": "css", 12 | "size": 42684, 13 | "target": "QmRNaib6Pz2PUVLpEbMkEdETu5cup77Dtkief58o4NRPaM", 14 | "source": "QmYPNmahJAvkMTU6tDx5zvhEkoLzEFeTDz6azDCSNqzKkW" 15 | }, 16 | { 17 | "path": "docs", 18 | "size": 6750964, 19 | "target": "QmSNmHbujerGN3ZmtXdkUY1MoRhULFQztV4tRWpXW8pjkV", 20 | "source": "QmYPNmahJAvkMTU6tDx5zvhEkoLzEFeTDz6azDCSNqzKkW" 21 | }, 22 | { 23 | "path": "fonts", 24 | "size": 914365, 25 | "target": "QmX7GQcyrVaKnS24y6nvU4LEB9hkcK5TkxoxRBaGFUUD6Q", 26 | "source": "QmYPNmahJAvkMTU6tDx5zvhEkoLzEFeTDz6azDCSNqzKkW" 27 | }, 28 | { 29 | "path": "images", 30 | "size": 1509477, 31 | "target": "QmZMWv34NMLbhBxWysqViFdiMkGf4WtfXc5GQDnV8NWo5J", 32 | "source": "QmYPNmahJAvkMTU6tDx5zvhEkoLzEFeTDz6azDCSNqzKkW" 33 | }, 34 | { 35 | "path": "index.html", 36 | "size": 17069, 37 | "target": "Qme8Kh3QbmeBjDZEVTmL6NEf8AWQZr6vyBpoxqC3WwmScc", 38 | "source": "QmYPNmahJAvkMTU6tDx5zvhEkoLzEFeTDz6azDCSNqzKkW" 39 | }, 40 | { 41 | "path": "js", 42 | "size": 755010, 43 | "target": "QmSGnYkR3jJFPfooK7HZbam4418fpSRsCbtLpxMeikciTS", 44 | "source": "QmYPNmahJAvkMTU6tDx5zvhEkoLzEFeTDz6azDCSNqzKkW" 45 | }, 46 | { 47 | "path": "legal", 48 | "size": 5726, 49 | "target": "QmaXsRWbBAxTF7Z4SrScHitj4CvjyCcnCjqXwBLgnkpP5W", 50 | "source": "QmYPNmahJAvkMTU6tDx5zvhEkoLzEFeTDz6azDCSNqzKkW" 51 | }, 52 | { 53 | "path": "media", 54 | "size": 12900, 55 | "target": "QmYMe5tKxTQdyNL7Vs9QtehBTci6F1CDYYkeo9wFdoVSE7", 56 | "source": "QmYPNmahJAvkMTU6tDx5zvhEkoLzEFeTDz6azDCSNqzKkW" 57 | }, 58 | { 59 | "path": "sitemap.xml", 60 | "size": 2202, 61 | "target": "QmbBZGoTh58At3D5uvr5HzCqRtPp39rZeydtPE2isW3gEq", 62 | "source": "QmYPNmahJAvkMTU6tDx5zvhEkoLzEFeTDz6azDCSNqzKkW" 63 | } 64 | ], 65 | "cid": "QmYPNmahJAvkMTU6tDx5zvhEkoLzEFeTDz6azDCSNqzKkW", 66 | "size": 10016715 67 | } 68 | -------------------------------------------------------------------------------- /src/components/object-info/links-table.module.css: -------------------------------------------------------------------------------- 1 | .links-table { 2 | /* background: rgba(255,255,255,0.7); */ 3 | overflow-x: hidden; 4 | } 5 | 6 | .header-grid, 7 | .row-grid { 8 | display: grid; 9 | grid-template-columns: 5% 1fr 3fr; 10 | width: 100%; 11 | column-gap: 1rem; 12 | /* row-gap: 0.5rem; */ 13 | } 14 | 15 | .header-grid { 16 | background: rgba(251,251,251); 17 | 18 | } 19 | 20 | @media (max-width: 860px) { 21 | .header-grid, 22 | .row-grid { 23 | grid-template-columns: 5% 1fr 3fr; 24 | } 25 | } 26 | @media (min-width: 960px) { 27 | .header-grid, 28 | .row-grid { 29 | grid-template-columns: 5% 1fr 3fr; 30 | } 31 | } 32 | @media (min-width: 1200px) { 33 | .header-grid, 34 | .row-grid { 35 | grid-template-columns: 5% 2fr 3fr; 36 | } 37 | } 38 | 39 | .grid-cell-header, 40 | .grid-cell-row { 41 | padding: 0; 42 | } 43 | 44 | .grid-cell-header:nth-child(1), 45 | .grid-cell-row:nth-child(1) { 46 | padding: 0; 47 | } 48 | 49 | .grid-cell-header:nth-child(2), 50 | .grid-cell-row:nth-child(2) { 51 | padding: 0; 52 | text-overflow: ellipsis; 53 | overflow: hidden; 54 | white-space: nowrap; 55 | } 56 | 57 | .grid-cell-header:nth-child(3), 58 | .grid-cell-row:nth-child(3) { 59 | padding: 0; 60 | text-overflow: unset; 61 | overflow: auto; 62 | min-width: 0; 63 | text-align: left; 64 | align-items: left; 65 | vertical-align: middle; 66 | /* margin: 0 auto; */ 67 | } 68 | -------------------------------------------------------------------------------- /src/forms.ts: -------------------------------------------------------------------------------- 1 | export * from './components/explore/IpldCarExploreForm.js' 2 | export * from './components/explore/IpldExploreForm.js' 3 | -------------------------------------------------------------------------------- /src/i18n-decorator.tsx: -------------------------------------------------------------------------------- 1 | import React, { type ReactNode } from 'react' 2 | import { I18nextProvider } from 'react-i18next' 3 | import i18n from './i18n' 4 | 5 | export default function i18nDecorator (fn: () => ReactNode): JSX.Element { 6 | return ( 7 | 8 | {fn()} 9 | 10 | ) 11 | } 12 | -------------------------------------------------------------------------------- /src/i18n.ts: -------------------------------------------------------------------------------- 1 | import i18n from 'i18next' 2 | import LanguageDetector from 'i18next-browser-languagedetector' 3 | import Backend from 'i18next-chained-backend' 4 | import HttpBackend from 'i18next-http-backend' 5 | import ICU from 'i18next-icu' 6 | import LocalStorageBackend from 'i18next-localstorage-backend' 7 | 8 | const chain = i18n 9 | .use(ICU) 10 | .use(Backend) 11 | .use(LanguageDetector) 12 | 13 | await chain 14 | .init({ 15 | backend: { 16 | backends: [ 17 | LocalStorageBackend, 18 | HttpBackend 19 | ], 20 | backendOptions: [ 21 | { // LocalStorageBackend 22 | defaultVersion: 'v1', 23 | expirationTime: (import.meta.env.NODE_ENV == null || import.meta.env.NODE_ENV === 'development') ? 1 : 7 * 24 * 60 * 60 * 1000 24 | }, 25 | { // HttpBackend 26 | // ensure a relative path is used to look up the locales, so it works when loaded from /ipfs/ 27 | loadPath: 'locales/{{lng}}/{{ns}}.json' 28 | } 29 | ] 30 | }, 31 | ns: ['explore'], 32 | defaultNS: 'app', // 'app' is default in ipfs-webui, this ensures we always use explicit explore. ns 33 | fallbackNS: 'explore', 34 | fallbackLng: { 35 | 'zh-Hans': ['zh-CN', 'en'], 36 | 'zh-Hant': ['zh-TW', 'en'], 37 | zh: ['zh-CN', 'en'], 38 | default: ['en'] 39 | }, 40 | debug: import.meta.env.DEBUG, 41 | // react i18next special options (optional) 42 | react: { 43 | useSuspense: false, 44 | bindI18n: 'languageChanged loaded', 45 | // bindStore: 'added removed', 46 | nsMode: 'default' 47 | } 48 | }, () => { 49 | // eslint-disable-next-line no-console 50 | console.log('i18n initialized') 51 | }).catch((err) => { 52 | console.error('i18n init error', err) 53 | }) 54 | 55 | export default i18n 56 | -------------------------------------------------------------------------------- /src/icons/GlyphSmallCancel.tsx: -------------------------------------------------------------------------------- 1 | import React, { type HTMLProps } from 'react' 2 | 3 | const GlyphSmallCancel: React.FC> = props => ( 4 | 5 | 6 | 7 | ) 8 | 9 | export default GlyphSmallCancel 10 | -------------------------------------------------------------------------------- /src/icons/StrokeIpld.tsx: -------------------------------------------------------------------------------- 1 | import React, { type HTMLProps } from 'react' 2 | 3 | const StrokeIpld: React.FC> = (props) => ( 4 | 5 | 6 | 7 | ) 8 | 9 | export default StrokeIpld 10 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import CidInfo from './components/cid-info/CidInfo.js' 2 | import ObjectInfo from './components/object-info/ObjectInfo.js' 3 | import './components/loader/loader.module.css' 4 | import './components/object-info/links-table.module.css' 5 | export * from './providers/index.js' 6 | export * from './pages.js' 7 | export * from './forms.js' 8 | 9 | export { 10 | CidInfo, 11 | ObjectInfo 12 | } 13 | -------------------------------------------------------------------------------- /src/lib/base-importer.ts: -------------------------------------------------------------------------------- 1 | import { bases } from 'multiformats/basics' 2 | 3 | export default async function baseImporter (prefix: T): Promise { 4 | // handle for CIDv0 5 | if (prefix === 'Q') { 6 | return bases.base58btc 7 | } 8 | 9 | const base = Object.values(bases).find((base) => { 10 | if (base.prefix === prefix) { 11 | return true 12 | } 13 | return false 14 | }) 15 | 16 | if (base != null) { 17 | return base 18 | } 19 | 20 | throw new Error(`unknown multibase prefix '${prefix}'`) 21 | } 22 | -------------------------------------------------------------------------------- /src/lib/cid.ts: -------------------------------------------------------------------------------- 1 | import * as dagCbor from '@ipld/dag-cbor' 2 | import * as dagJson from '@ipld/dag-json' 3 | import * as dagPb from '@ipld/dag-pb' 4 | import { CID, type MultibaseDecoder } from 'multiformats/cid' 5 | 6 | /** 7 | * Converts a value to a CID or returns null if it cannot be converted. 8 | */ 9 | export function toCidOrNull (value: CID | string | null | unknown, base?: MultibaseDecoder | undefined): CID | null { 10 | if (value == null) return null 11 | try { 12 | return CID.asCID(value) ?? CID.parse(value as string, base) 13 | } catch (err) { 14 | return null 15 | } 16 | } 17 | 18 | /** 19 | * This function is deprecated, use `getCodeOrNull` instead. 20 | * 21 | * `cid.codec` is deprecated, use integer "code" property instead 22 | * 23 | * @deprecated 24 | */ 25 | export function getCodecOrNull (value: CID | string | null): string | null { 26 | const cid = toCidOrNull(value) 27 | 28 | if (cid == null) return null 29 | switch (cid.code) { 30 | case dagCbor.code: 31 | return dagCbor.name 32 | case dagJson.code: 33 | return dagJson.name 34 | case dagPb.code: 35 | return dagPb.name 36 | default: 37 | return null 38 | } 39 | } 40 | 41 | export function getCodeOrNull (value: CID | string | null): number | null { 42 | const cid = toCidOrNull(value) 43 | if (cid == null) return null 44 | return cid.code 45 | } 46 | 47 | export function toCidStrOrNull (value: CID | string | null): string | null { 48 | const cid = toCidOrNull(value) 49 | if (cid == null) return null 50 | // return cid?.toString() ?? null 51 | return cid.toString() 52 | } 53 | -------------------------------------------------------------------------------- /src/lib/codec-importer.ts: -------------------------------------------------------------------------------- 1 | import { type BlockCodec } from 'multiformats/codecs/interface' 2 | import getCodecNameFromCode from './get-codec-name-from-code' 3 | import type { PBNode } from '@ipld/dag-pb' 4 | 5 | type CodecDataTypes = PBNode | Uint8Array 6 | interface CodecImporterResponse extends Pick, 'decode' | 'encode' | 'code'> { 7 | } 8 | 9 | export default async function codecImporter (codeOrName: number | string): Promise> { 10 | let codecName: string 11 | if (typeof codeOrName === 'string') { 12 | codecName = codeOrName 13 | } else { 14 | codecName = getCodecNameFromCode(codeOrName) 15 | } 16 | 17 | // #WhenAddingNewCodec 18 | switch (codecName) { 19 | case 'dag-cbor': 20 | return import('@ipld/dag-cbor') 21 | case 'dag-pb': 22 | return import('@ipld/dag-pb') 23 | case 'git-raw': 24 | throw new Error('git-raw is unsupported until https://github.com/ipld/js-ipld-git is updated.') 25 | case 'raw': 26 | return import('multiformats/codecs/raw') 27 | case 'json': 28 | return import('multiformats/codecs/json') 29 | case 'dag-json': 30 | return import('@ipld/dag-json') 31 | case 'dag-jose': 32 | return import('dag-jose') 33 | default: 34 | throw new Error(`unsupported codec: ${codeOrName}=${codecName}`) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/lib/errors.ts: -------------------------------------------------------------------------------- 1 | import { type TFunction } from 'i18next' 2 | 3 | export default class IpldExploreError extends Error { 4 | constructor (private readonly options: Record) { 5 | super() 6 | this.name = this.constructor.name 7 | } 8 | 9 | /** 10 | * See IpldExploreError for usage. 11 | * You must pass a t function that is registered with namespace 'explore' and keyPrefix 'errors'. 12 | * 13 | * @param t - the i18next-react t function 14 | * @returns the translated string 15 | * @example 16 | * const {t} = useTranslation('explore', { keyPrefix: 'errors' }) 17 | * t('NameOfErrorClassThatExtendsIpldExploreError') 18 | */ 19 | toString (t: TFunction<'translation', 'translation'>): string { 20 | return t(this.name, this.options) 21 | } 22 | } 23 | 24 | export class BlockFetchTimeoutError extends IpldExploreError {} 25 | -------------------------------------------------------------------------------- /src/lib/explore-page-suggestions.ts: -------------------------------------------------------------------------------- 1 | import type { NodeStyle } from '../components/object-info/ObjectInfo.js' 2 | 3 | /** 4 | * Default Explore page suggestions 5 | */ 6 | export interface ExplorePageLink { 7 | name: string 8 | cid: string 9 | type: NodeStyle 10 | } 11 | 12 | export const explorePageLinks: ExplorePageLink[] = [ 13 | { 14 | name: 'Project Apollo Archives', 15 | cid: 'QmSnuWmxptJZdLJpKRarxBMS2Ju2oANVrgbr2xWbie9b2D', 16 | type: 'dag-pb' 17 | }, 18 | { 19 | name: 'XKCD Archives', 20 | cid: 'QmdmQXB2mzChmMeKY47C43LxUdg1NDJ5MWcKMKxDu7RgQm', 21 | type: 'dag-pb' 22 | }, 23 | { 24 | name: 'HAMT-sharded Wikipedia mirror (>20M files)', 25 | cid: 'bafybeiaysi4s6lnjev27ln5icwm6tueaw2vdykrtjkwiphwekaywqhcjze', 26 | type: 'hamt-sharded-directory' 27 | }, 28 | { 29 | name: 'B-tree search index from ipfs-geoip', 30 | cid: 'bafyreif3tfdpr5n4jdrbielmcapwvbpcthepfkwq2vwonmlhirbjmotedi', 31 | type: 'dag-cbor' 32 | }, 33 | { 34 | name: 'DAG-CBOR Block', 35 | cid: 'bafyreicnokmhmrnlp2wjhyk2haep4tqxiptwfrp2rrs7rzq7uk766chqvq', 36 | type: 'dag-cbor' 37 | }, 38 | { 39 | name: 'dag-cbor hello world (keccak-256)', 40 | cid: 'bafyrwigbexamue2ba3hmtai7hwlcmd6ekiqsduyf5avv7oz6ln3radvjde', 41 | type: 'dag-cbor' 42 | }, 43 | { 44 | name: 'Ceramic LogEntry for sgb.chat Ambassador proposal', 45 | cid: 'bagcqcerarvdwmhvk73mze3e2n6yvpt5h7fh3eae7n6y3hizsflz5grpyeczq', 46 | type: 'dag-jose' 47 | }, 48 | { 49 | name: 'hello world (blake3)', 50 | cid: 'bagaaihraf4oq2kddg6o5ewlu6aol6xab75xkwbgzx2dlot7cdun7iirve23a', 51 | type: 'dag-json' 52 | }, 53 | { 54 | name: 'hello world', 55 | cid: 'baguqeerasords4njcts6vs7qvdjfcvgnume4hqohf65zsfguprqphs3icwea', 56 | type: 'dag-json' 57 | }, 58 | { 59 | name: 'hello world (sha3-512)', 60 | cid: 'bagaaifcavabu6fzheerrmtxbbwv7jjhc3kaldmm7lbnvfopyrthcvod4m6ygpj3unrcggkzhvcwv5wnhc5ufkgzlsji7agnmofovc2g4a3ui7ja', 61 | type: 'json' 62 | }, 63 | { 64 | name: 'Raw Block for "hello"', 65 | cid: 'bafkreibm6jg3ux5qumhcn2b3flc3tyu6dmlb4xa7u5bf44yegnrjhc4yeq', 66 | type: 'raw' 67 | }, 68 | { 69 | name: 'Raw Block for "hello" (blake3)', 70 | cid: 'bafkr4ihkr4ld3m4gqkjf4reryxsy2s5tkbxprqkow6fin2iiyvreuzzab4', 71 | type: 'raw' 72 | } 73 | ] 74 | -------------------------------------------------------------------------------- /src/lib/extract-info.ts: -------------------------------------------------------------------------------- 1 | import { type CID } from 'multiformats' 2 | import { decodeCid } from '../components/cid-info/decode-cid.js' 3 | import getCodecNameFromCode from './get-codec-name-from-code' 4 | 5 | const toHex = (bytes: Uint8Array): string => Array.from(new Uint8Array(bytes.buffer, bytes.byteOffset, bytes.byteLength)) 6 | .map(byte => byte.toString(16).padStart(2, '0')) 7 | .join('') 8 | .toUpperCase() 9 | 10 | export interface ExtractedInfo { 11 | base: string 12 | codecName: string 13 | hashFn: string 14 | hashFnCode: string 15 | hashLengthCode: number 16 | hashLengthInBits: number 17 | hashValue: string 18 | hashValueIn32CharChunks: string[][] 19 | humanReadable: string 20 | } 21 | export default async function extractInfo (cid: CID | string): Promise { 22 | const cidInfo = await decodeCid(cid) 23 | if (cidInfo == null) { 24 | throw new Error(`CID could not be decoded for CID '${cid.toString()}'`) 25 | } 26 | const hashFn = cidInfo.multihash.name 27 | const hashFnCode = cidInfo.multihash.code.toString(16) 28 | const hashLengthCode = cidInfo.multihash.size 29 | const hashLengthInBits = cidInfo.multihash.size * 8 30 | const hashValue = toHex(cidInfo.multihash.digest) 31 | const hashValueIn32CharChunks = hashValue.split('').reduce((resultArray, item, index) => { 32 | const chunkIndex = Math.floor(index / 32) 33 | if (resultArray[chunkIndex] == null) { 34 | resultArray[chunkIndex] = [] // start a new chunk 35 | } 36 | resultArray[chunkIndex].push(item) 37 | return resultArray 38 | }, []) 39 | const codecName = getCodecNameFromCode(cidInfo.cid.code) 40 | 41 | const humanReadable = `${cidInfo.multibase.name} - cidv${cidInfo.cid.version} - ${codecName} - ${hashFn}~${hashLengthInBits}~${hashValue}` 42 | return { 43 | base: cidInfo.multibase.name, 44 | codecName, 45 | hashFn, 46 | hashFnCode, 47 | hashLengthCode, 48 | hashLengthInBits, 49 | hashValue, 50 | hashValueIn32CharChunks, 51 | humanReadable 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/lib/get-codec-name-from-code.ts: -------------------------------------------------------------------------------- 1 | import { type NodeStyle } from '../components/object-info/ObjectInfo.js' 2 | 3 | /** 4 | * Converts supported codec codes from https://github.com/multiformats/multicodec/blob/master/table.csv to their names. 5 | */ 6 | export default function getCodecNameFromCode (code: number): NodeStyle { 7 | // #WhenAddingNewCodec 8 | switch (code) { 9 | case 113: 10 | return 'dag-cbor' 11 | case 112: 12 | return 'dag-pb' 13 | case 120: 14 | // @ts-expect-error - git-raw is not in the NodeStyle type. Leaving for legacy purposes. 15 | return 'git-raw' 16 | case 85: 17 | return 'raw' 18 | case 512: 19 | return 'json' 20 | case 297: 21 | return 'dag-json' 22 | case 133: 23 | return 'dag-jose' 24 | default: 25 | return 'unknown' 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/lib/get-raw-block.ts: -------------------------------------------------------------------------------- 1 | import { type CID } from 'multiformats/cid' 2 | import { BlockFetchTimeoutError } from './errors.js' 3 | import type { Helia } from '@helia/interface' 4 | 5 | /** 6 | * Method for getting a raw block either with helia from trustless gateways or a local Kubo gateway. 7 | */ 8 | export async function getRawBlock (helia: Helia, cid: CID, timeout = 30000): Promise { 9 | const abortController = new AbortController() 10 | 11 | try { 12 | const timeoutId = setTimeout(() => { abortController.abort('Request timed out') }, timeout) 13 | const rawBlock = await helia.blockstore.get(cid, { signal: abortController.signal }) 14 | clearTimeout(timeoutId) 15 | 16 | return rawBlock 17 | } catch (err) { 18 | console.error('unable to get raw block', err) 19 | if (abortController.signal.aborted) { 20 | // if we timed out, we want to throw a timeout error, not a Promise.any error 21 | throw new BlockFetchTimeoutError({ timeout: timeout / 1000 }) 22 | } 23 | throw err 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/lib/guards.ts: -------------------------------------------------------------------------------- 1 | import type { PBLink, PBNode } from '@ipld/dag-pb' 2 | 3 | export function isNotNullish (value: T): value is NonNullable { 4 | return value != null 5 | } 6 | 7 | export function isPBNode (value: unknown): value is PBNode { 8 | return isNotNullish(value) && typeof value === 'object' && (value as PBNode).Data != null && (value as PBNode).Links != null 9 | } 10 | 11 | export function isPBLink (value: unknown): value is PBLink { 12 | return isNotNullish(value) && typeof value === 'object' && (value as PBLink).Hash != null 13 | } 14 | -------------------------------------------------------------------------------- /src/lib/hash-importer.ts: -------------------------------------------------------------------------------- 1 | /* global globalThis */ 2 | import { sha3512, keccak256 } from '@multiformats/sha3' 3 | import { type Hasher, from } from 'multiformats/hashes/hasher' 4 | import { identity } from 'multiformats/hashes/identity' 5 | import * as sha2 from 'multiformats/hashes/sha2' 6 | 7 | // #WhenAddingNewHasher 8 | export type SupportedHashers = typeof sha2.sha256 | 9 | typeof sha2.sha512 | 10 | Hasher<'identity', 0x0> | 11 | Hasher<'keccak-256', 27> | 12 | Hasher<'sha1', 17> | 13 | Hasher<'blake2b-256', 0xb220> | 14 | Hasher<'blake2b-512', 0xb240> | 15 | Hasher<'sha3-512', 20> | 16 | Hasher<'blake3', 30> 17 | 18 | export async function getHashersForCodes (...codes: number[]): Promise { 19 | const hashers: SupportedHashers[] = [] 20 | for (const code of codes) { 21 | try { 22 | hashers.push(await getHasherForCode(code)) 23 | } catch (error) { 24 | console.error(`Failed to get hasher for code ${code}: ${error}`) 25 | } 26 | } 27 | return hashers 28 | } 29 | 30 | /** 31 | * Helper function to prevent `this` from being lost when calling `encode` and `digest` on a hasher. 32 | */ 33 | function getBoundHasher (hasher: T): T { 34 | return { 35 | ...hasher, 36 | encode: hasher.encode.bind(hasher), 37 | digest: hasher.digest.bind(hasher) 38 | } 39 | } 40 | 41 | export async function getHasherForCode (code: number): Promise { 42 | // #WhenAddingNewHasher 43 | switch (code) { 44 | case identity.code: 45 | return getBoundHasher({ 46 | ...identity, 47 | name: 'identity' // multiformats/hashes/identity doesn't export a proper Hasher type. See https://github.com/multiformats/js-multiformats/issues/313 48 | }) 49 | case sha2.sha256.code: 50 | return getBoundHasher(sha2.sha256) 51 | case sha2.sha512.code: 52 | return getBoundHasher(sha2.sha512) 53 | case 17: 54 | // git hasher uses sha1. see ipld-git/src/util.js 55 | return getBoundHasher(from({ 56 | name: 'sha1', 57 | code, 58 | encode: async (data: Uint8Array): Promise => { 59 | const hashBuffer = await globalThis.crypto.subtle.digest('SHA-1', data) 60 | return new Uint8Array(hashBuffer) 61 | } 62 | })) 63 | case sha3512.code: // sha3-512 64 | return getBoundHasher(sha3512) 65 | case keccak256.code: // keccak-256 66 | return getBoundHasher(keccak256) 67 | case 30: 68 | return getBoundHasher(from({ 69 | name: 'blake3', 70 | code, 71 | encode: async (data: Uint8Array): Promise => { 72 | const { createBLAKE3 } = await import('hash-wasm') 73 | const blake3Hasher = await createBLAKE3() 74 | blake3Hasher.init() 75 | blake3Hasher.update(data) 76 | return blake3Hasher.digest('binary') 77 | } 78 | })) 79 | case 0xb220: // blake2b-256 80 | return getBoundHasher(from({ 81 | name: 'blake2b-256', 82 | code, 83 | encode: async (data: Uint8Array): Promise => { 84 | const { createBLAKE2b } = await import('hash-wasm') 85 | const blake2bHasher = await createBLAKE2b(256) 86 | blake2bHasher.init() 87 | blake2bHasher.update(data) 88 | return blake2bHasher.digest('binary') 89 | } 90 | })) 91 | case 0xb240: // blake2b-512 92 | return getBoundHasher(from({ 93 | name: 'blake2b-512', 94 | code, 95 | encode: async (data: Uint8Array): Promise => { 96 | const { createBLAKE2b } = await import('hash-wasm') 97 | const blake2bHasher = await createBLAKE2b(512) 98 | blake2bHasher.init() 99 | blake2bHasher.update(data) 100 | return blake2bHasher.digest('binary') 101 | } 102 | }) 103 | ) 104 | 105 | default: 106 | throw new Error(`unknown multihasher code '${code}'`) 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/lib/helpers.ts: -------------------------------------------------------------------------------- 1 | import { type Helia } from '@helia/interface' 2 | import { CID } from 'multiformats' 3 | import codecImporter from './codec-importer.js' 4 | import { getHasherForCode } from './hash-importer.js' 5 | 6 | export function ensureLeadingSlash (str: string): string { 7 | if (str.startsWith('/')) return str 8 | return `/${str}` 9 | } 10 | 11 | /** 12 | * When migrating to typescript and removing redux-bundler, a lot of old code 13 | * had checks like `path ? path : null` which would fire as "false" when the 14 | * path was an empty string. This function helps the migration path but should 15 | * be removed once all the old code is updated. 16 | */ 17 | export function isFalsy (str: T | null | string | undefined): str is null { 18 | if (str == null || str === '') return true 19 | return false 20 | } 21 | 22 | export function isTruthy (str: T | null | string | undefined): str is string { 23 | return !isFalsy(str) 24 | } 25 | 26 | export async function addDagNodeToHelia (helia: Helia, codecName: string, node: T, hasherCode = 18): Promise { 27 | const codec = await codecImporter(codecName) 28 | const hasher = await getHasherForCode(hasherCode) 29 | const encodedNode = codec.encode(node) 30 | const mhDigest = await hasher.digest(encodedNode) 31 | const cid = CID.createV1(codec.code, mhDigest) 32 | 33 | return helia.blockstore.put(cid, encodedNode) 34 | } 35 | -------------------------------------------------------------------------------- /src/lib/import-car.ts: -------------------------------------------------------------------------------- 1 | import { type Helia } from '@helia/interface' 2 | import { CarBlockIterator } from '@ipld/car' 3 | import { type CID } from 'multiformats' 4 | import { source } from 'stream-to-it' 5 | 6 | /** 7 | * Given a file object representing a CAR archive, import it into the given Helia instance, 8 | * and return the CID for the root block 9 | * 10 | * TODO: Handle multiple roots 11 | */ 12 | export async function importCar (file: File, helia: Helia): Promise { 13 | const inStream = file.stream() 14 | const CarIterator = await CarBlockIterator.fromIterable(source(inStream)) 15 | for await (const { cid, bytes } of CarIterator) { 16 | // add blocks to helia to ensure they are available while navigating children 17 | await helia.blockstore.put(cid, bytes) 18 | } 19 | const cidRoots = await CarIterator.getRoots() 20 | 21 | // @todo: Handle multiple roots 22 | return cidRoots[0] 23 | } 24 | -------------------------------------------------------------------------------- /src/lib/init-helia.ts: -------------------------------------------------------------------------------- 1 | import { trustlessGateway } from '@helia/block-brokers' 2 | import { createHeliaHTTP } from '@helia/http' 3 | import { type Routing, type Helia } from '@helia/interface' 4 | import { delegatedHTTPRouting, httpGatewayRouting } from '@helia/routers' 5 | import { addDagNodeToHelia } from '../lib/helpers.js' 6 | import { getHashersForCodes } from './hash-importer.js' 7 | import type { KuboGatewayOptions } from '../types.d.js' 8 | 9 | const localStorageKey = 'explore.ipld.gatewayEnabled' 10 | console.info( 11 | `🎛️ Customise whether ipld-explorer-components fetches content from gateways by setting an '${localStorageKey}' value to true/false in localStorage. e.g. localStorage.setItem('${localStorageKey}', false) -- NOTE: defaults to true` 12 | ) 13 | 14 | /** 15 | * Whether to enable remote gateways for fetching content. We default to true if the setting is not present. 16 | */ 17 | function areRemoteGatewaysEnabled (): boolean { 18 | const gatewayEnabledSetting = localStorage.getItem(localStorageKey) 19 | 20 | return gatewayEnabledSetting != null ? JSON.parse(gatewayEnabledSetting) : true 21 | } 22 | 23 | export default async function initHelia (kuboGatewayOptions: KuboGatewayOptions): Promise { 24 | const routers: Array> = [] 25 | const kuboGatewayUrlString = `${kuboGatewayOptions.protocol ?? 'http'}://${kuboGatewayOptions.host}:${kuboGatewayOptions.port}` 26 | try { 27 | const kuboGatewayUrl = new URL(kuboGatewayUrlString) 28 | // Always try to add the Kubo gateway if we have a valid URL 29 | routers.push(httpGatewayRouting({ gateways: [kuboGatewayUrl.href] })) 30 | } catch (error) { 31 | // eslint-disable-next-line no-console 32 | console.error('Invalid kuboGateway url string: %s', kuboGatewayUrlString, error) 33 | } 34 | 35 | if (areRemoteGatewaysEnabled()) { 36 | // eslint-disable-next-line no-console 37 | console.log('remote gateways and delegated routing are enabled') 38 | routers.push(delegatedHTTPRouting('https://delegated-ipfs.dev')) 39 | routers.push(httpGatewayRouting()) 40 | } 41 | 42 | const helia = await createHeliaHTTP({ 43 | blockBrokers: [ 44 | trustlessGateway(kuboGatewayOptions.trustlessBlockBrokerConfig?.init) 45 | ], 46 | 47 | routers, 48 | // #WhenAddingNewHasher 49 | hashers: await getHashersForCodes(17, 18, 19, 20, 27, 30, 45600, 45632) 50 | }) 51 | 52 | // add helia-only examples 53 | // consumers may not have the peer-deps installed for these examples, and we don't want to break them if they're not supported. 54 | await Promise.allSettled([ 55 | addDagNodeToHelia(helia, 'dag-json', { hello: 'world' }), // baguqeerasords4njcts6vs7qvdjfcvgnume4hqohf65zsfguprqphs3icwea 56 | addDagNodeToHelia(helia, 'dag-cbor', { hello: 'world' }, 27), // bafyrwigbexamue2ba3hmtai7hwlcmd6ekiqsduyf5avv7oz6ln3radvjde 57 | addDagNodeToHelia(helia, 'json', { hello: 'world' }, 20), // bagaaifcavabu6fzheerrmtxbbwv7jjhc3kaldmm7lbnvfopyrthcvod4m6ygpj3unrcggkzhvcwv5wnhc5ufkgzlsji7agnmofovc2g4a3ui7ja 58 | addDagNodeToHelia(helia, 'json', { hello: 'world' }, 30), // bagaaihraf4oq2kddg6o5ewlu6aol6xab75xkwbgzx2dlot7cdun7iirve23a 59 | addDagNodeToHelia(helia, 'raw', (new TextEncoder()).encode('hello'), 30), // bafkr4ihkr4ld3m4gqkjf4reryxsy2s5tkbxprqkow6fin2iiyvreuzzab4 60 | addDagNodeToHelia(helia, 'dag-pb', { Data: (new TextEncoder()).encode('hello'), Links: [] }, 0xb220), // bafykbzacec3ssfzln7bfcn54t5voa4onlcx63kkx3reucaiwc7eaffmla7gci 61 | addDagNodeToHelia(helia, 'dag-pb', { Data: (new TextEncoder()).encode('hello'), Links: [] }, 0xb240) // bafymbzacia5oqpl3kqdjk6hgisdemv44omuqse33bf3a2gnurnzcmkstjhupcqymbuvsj2qlke4phr5iudjruwbjqsx34psaqsuezr4ivka5ul2y 62 | ]) 63 | 64 | return helia 65 | } 66 | -------------------------------------------------------------------------------- /src/lib/parse-ipld-path.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Capture groups 1 3 | 1: ipns | ipfs | ipld 4 | 2: CID | fqdn 5 | 3: /rest 6 | */ 7 | export const pathRegEx = /(\/(ipns|ipfs|ipld)\/)?([^/]+)(\/.*)?/ 8 | 9 | export interface IpldPath { 10 | namespace: string 11 | cidOrFqdn: string 12 | rest: string 13 | address: string 14 | } 15 | 16 | export function parseIpldPath (str: string): IpldPath | null { 17 | const res = pathRegEx.exec(str) 18 | if (res == null) return null 19 | return { 20 | namespace: res[2], // 'ipfs' 21 | cidOrFqdn: res[3], // 'QmHash' 22 | rest: res[4], // /foo/bar 23 | address: res[0] // /ipfs/QmHash/foo/bar 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/lib/tours.tsx: -------------------------------------------------------------------------------- 1 | import { type TFunction } from 'i18next' 2 | import React from 'react' 3 | import { type Step } from 'react-joyride' 4 | 5 | export const projectsTour = { 6 | getSteps: ({ t }: { t: TFunction<'explore', undefined> }): Step[] => [{ 7 | content: ( 8 |
9 |

{t('tour.projects.title')}

10 |

{t('tour.projects.paragraph1')}

11 |
12 | ), 13 | placement: 'center', 14 | target: 'body' 15 | }], 16 | styles: { 17 | options: { 18 | width: '500px', 19 | primaryColor: '#69c4cd', 20 | textColor: '#34373f', 21 | zIndex: 999 22 | } 23 | } 24 | } 25 | 26 | export const explorerTour = { 27 | getSteps: ({ t }: { t: TFunction<'explore', undefined> }): Step[] => [ 28 | { 29 | content: ( 30 |
31 |

{t('tour.explorer.step1.title')}

32 |

{t('tour.explorer.step1.paragraph1')}

33 |
34 | ), 35 | placement: 'center', 36 | target: 'body' 37 | }, 38 | { 39 | content: ( 40 |
41 |

{t('tour.explorer.step2.title')}

42 |

{t('tour.explorer.step2.paragraph1')}

43 |

{t('tour.explorer.step2.paragraph2')}

44 |
45 | ), 46 | placement: 'bottom', 47 | target: '.joyride-explorer-crumbs' 48 | }, 49 | { 50 | content: ( 51 |
52 |

{t('tour.explorer.step3.title')}

53 |

{t('tour.explorer.step3.paragraph1')}

54 |
55 | ), 56 | placement: 'right', 57 | target: '.joyride-explorer-node' 58 | }, 59 | { 60 | content: ( 61 |
62 |

{t('tour.explorer.step4.title')}

63 |

{t('tour.explorer.step4.paragraph1')}

64 |
65 | ), 66 | placement: 'left', 67 | target: '.joyride-explorer-cid' 68 | }, 69 | { 70 | content: ( 71 |
72 |

{t('tour.explorer.step5.title')}

73 |

{t('tour.explorer.step5.paragraph1')}

74 |
75 | ), 76 | locale: { last: 'Finish' }, 77 | placement: 'left', 78 | target: '.joyride-explorer-graph' 79 | } 80 | ], 81 | styles: { 82 | options: { 83 | width: '500px', 84 | primaryColor: '#69c4cd', 85 | textColor: '#34373f', 86 | zIndex: 999 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/pages.ts: -------------------------------------------------------------------------------- 1 | export * from './components/StartExploringPage' 2 | export * from './components/ExplorePage' 3 | -------------------------------------------------------------------------------- /src/providers/helia.tsx: -------------------------------------------------------------------------------- 1 | import { type Helia } from '@helia/interface' 2 | import React, { createContext, useContext, useState, useCallback } from 'react' 3 | import packageJson from '../../package.json' 4 | import initHelia from '../lib/init-helia.js' 5 | import type { KuboGatewayOptions } from '../types.js' 6 | 7 | interface HeliaContextProps { 8 | kuboGatewayOptions: KuboGatewayOptions 9 | error: Error | null 10 | helia: Helia | null 11 | selectHeliaReady(): boolean 12 | selectHeliaIdentity(): string 13 | doInitHelia(): Promise 14 | setKuboGatewayOptions(kuboGatewayOptions: Partial): void 15 | } 16 | 17 | const defaultKuboGatewayOptions: KuboGatewayOptions = { 18 | host: '127.0.0.1', 19 | port: '8080', 20 | protocol: 'http', 21 | trustlessBlockBrokerConfig: { 22 | init: { 23 | allowLocal: true 24 | } 25 | } 26 | } 27 | 28 | const getDefaultKuboGatewayOptions = (): KuboGatewayOptions => { 29 | const localStorageKuboGatewayOptions = localStorage.getItem('kuboGateway') 30 | if (localStorageKuboGatewayOptions != null) { 31 | try { 32 | const kuboGatewaySettings = JSON.parse(localStorageKuboGatewayOptions) as KuboGatewayOptions 33 | return { ...defaultKuboGatewayOptions, ...kuboGatewaySettings } 34 | } catch (e) { 35 | console.error('getDefaultKuboGatewayOptions error', e) 36 | } 37 | } 38 | return defaultKuboGatewayOptions 39 | } 40 | 41 | const HeliaContext = createContext(undefined) 42 | 43 | export const HeliaProvider = ({ children }: React.ComponentProps): any => { 44 | const [helia, setHelia] = useState(null) 45 | const [kuboGatewayOptions, setKuboGatewayOptions] = useState(getDefaultKuboGatewayOptions()) 46 | const [error, setError] = useState(null) 47 | 48 | const selectHeliaReady = (): boolean => helia !== null 49 | 50 | const selectHeliaIdentity = (): string => { 51 | const heliaHttpVersion = packageJson.dependencies['@helia/http'] 52 | return `@helia/http@${heliaHttpVersion}` 53 | } 54 | 55 | const setKuboGatewayOptionsPublic = (kuboGatewayOptions: Partial): void => { 56 | setKuboGatewayOptions((current) => ({ ...current, ...kuboGatewayOptions })) 57 | } 58 | 59 | const doInitHelia = useCallback(async (): Promise => { 60 | try { 61 | const helia = await initHelia(kuboGatewayOptions) 62 | setError(null) 63 | setHelia(helia) 64 | } catch (error: any) { 65 | console.error('doInitHelia error', error) 66 | setError(error) 67 | setHelia(null) 68 | } 69 | }, [kuboGatewayOptions, setHelia]) 70 | 71 | return ( 72 | 73 | {children} 74 | 75 | ) 76 | } 77 | 78 | export const useHelia = (): HeliaContextProps => { 79 | const context = useContext(HeliaContext) 80 | if (context === undefined) { 81 | throw new Error('useHelia must be used within a HeliaProvider') 82 | } 83 | return context 84 | } 85 | -------------------------------------------------------------------------------- /src/providers/index.ts: -------------------------------------------------------------------------------- 1 | export * from './explore.js' 2 | export * from './helia.js' 3 | -------------------------------------------------------------------------------- /src/types.d.ts: -------------------------------------------------------------------------------- 1 | import { type getHasherForCode } from './lib/hash-importer.js' 2 | import type { TrustlessGatewayBlockBrokerInit } from '@helia/block-brokers' 3 | import type { MultihashDigest } from 'multiformats' 4 | 5 | export type { CID } from 'multiformats/cid' 6 | 7 | export type CodecType = number 8 | export type NodeData = Uint8Array 9 | export type UnixFsNodeTypes = 'raw' | 'directory' | 'file' | 'metadata' | 'symlink' | 'hamt-sharded-directory' 10 | export type UnixFsNodeDirTypes = 'directory' | 'hamt-sharded-directory' 11 | export type NormalizedDagPbNodeFormat = 'unixfs' | 'non-unixfs' 12 | export type NormalizedDagNodeFormat = NormalizedDagPbNodeFormat | 'unknown' 13 | export interface UnixFsNodeData { 14 | type: UnixFsNodeTypes 15 | data: NodeData | undefined 16 | blockSizes: bigint[] 17 | } 18 | 19 | export interface UnixFsNodeDataWithNumbers extends Omit { 20 | blockSizes: number[] 21 | } 22 | 23 | export interface NormalizedDagLink { 24 | path: string 25 | source: string 26 | target: string 27 | size: bigint 28 | index: number 29 | } 30 | 31 | export interface NormalizedDagNode { 32 | cid: string 33 | type: CodecType | string 34 | data: UnixFsNodeData | NodeData | undefined 35 | links: NormalizedDagLink[] 36 | size?: bigint 37 | format: NormalizedDagNodeFormat 38 | } 39 | 40 | export interface ResolveType { 41 | value: DecodedType 42 | remainderPath: string 43 | } 44 | 45 | export interface KuboGatewayOptions { 46 | host: string 47 | port: string 48 | protocol?: string 49 | trustlessBlockBrokerConfig?: { 50 | init?: TrustlessGatewayBlockBrokerInit 51 | } 52 | 53 | } 54 | 55 | export interface DecodedCidMulticodec { 56 | name: number 57 | code: string 58 | } 59 | export interface DecodedCidMultihash extends MultihashDigest { 60 | name: Awaited>['name'] 61 | } 62 | 63 | export interface dagNodeLink { 64 | cid: string 65 | name: string 66 | size: number 67 | } 68 | 69 | export interface dagNodeData { 70 | blockSizes: unknown[] 71 | data: unknown 72 | type: string 73 | } 74 | 75 | export interface dagNode { 76 | data: dagNodeData 77 | links: dagNodeLink[] 78 | size: number 79 | } 80 | -------------------------------------------------------------------------------- /test/e2e/edge-cases.test.ts: -------------------------------------------------------------------------------- 1 | import { test } from '@playwright/test' 2 | import { create, type KuboRPCClient } from 'kubo-rpc-client' 3 | import { loadBlockFixtures } from './fixtures/load-block-fixtures.js' 4 | import { testExploredCid } from './fixtures/test-explore-cid.js' 5 | 6 | test.describe('edge-cases', () => { 7 | let ipfs: KuboRPCClient 8 | test.beforeEach(async () => { 9 | ipfs = create(process.env.IPFS_RPC_ADDR) 10 | }) 11 | 12 | test('links without explicit paths can be navigated to', async ({ page }) => { 13 | const cid = 'Qmd1WaiaEBDe7H3a3U1CToaoaFsxUXEnmhw68ub5u1iY7Q' 14 | await loadBlockFixtures({ 15 | ipfs, 16 | blockCid: [ 17 | 'QmSnuWmxptJZdLJpKRarxBMS2Ju2oANVrgbr2xWbie9b2D', 18 | 'QmUh6QSTxDKX5qoNU1GoogbhTveQQV9JMeQjfFVchAtd5Q', 19 | 'QmXPqQjySvisWjE11dkrANGmfvUsmPN5RP9oWVRnR7RQuu', 20 | 'QmVFemV13MkWS7xyDzcJbBd5qL31NXAHcsYk4wy4RhwHqh', 21 | cid 22 | ] 23 | }) 24 | 25 | await page.goto('/#/explore/QmSnuWmxptJZdLJpKRarxBMS2Ju2oANVrgbr2xWbie9b2D/albums/QXBvbGxvIDE0IE1hZ2F6aW5lIDY0L0xM/21076550124_dfaa21d664_o.jpg') 26 | // wait for the link at index 0 to be visible. it has text of "Qmd1WaiaEBDe7H3a3U1CToaoaFsxUXEnmhw68ub5u1iY7Q" 27 | const link0 = await page.waitForSelector(`"${cid}"`) 28 | // click on the link at index 0 29 | await link0.click() 30 | 31 | await testExploredCid({ 32 | fillOutForm: false, 33 | page, 34 | cid, 35 | humanReadableCID: 'base58btc - cidv0 - dag-pb - sha2-256~256~D9F8142F34B8CC605DBD57745F1221836E616CDA5B5B9B5BE88D364E13CE39B7', 36 | type: 'dag-pb' 37 | }) 38 | }) 39 | 40 | test('identity CIDs render object info properly', async ({ page }) => { 41 | // Test for https://github.com/ipfs/ipld-explorer-components/issues/464 42 | const cid = 'bafkqaddjnzzxazldoqwxizltoq' 43 | 44 | await page.goto('/#/explore/bafkqaddjnzzxazldoqwxizltoq') 45 | 46 | await testExploredCid({ 47 | fillOutForm: false, 48 | page, 49 | cid, 50 | humanReadableCID: 'base32 - cidv1 - raw - identity~96~696E73706563742D74657374', 51 | type: 'raw' 52 | }) 53 | }) 54 | }) 55 | -------------------------------------------------------------------------------- /test/e2e/fixtures/create-cid.ts: -------------------------------------------------------------------------------- 1 | import { CID } from 'multiformats/cid' 2 | import { type BlockEncoder, type MultihashHasher, type Version } from 'multiformats/interface' 3 | 4 | export const createCID = async (value: any, codec: BlockEncoder, hasher: MultihashHasher, version: Version = 1): Promise => { 5 | try { 6 | const digest = await hasher.digest(codec.encode(value)) 7 | return CID.create(version, codec.code, digest) 8 | } catch (err) { 9 | console.error('Failed to create CID', value, err) 10 | throw err 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /test/e2e/fixtures/explore/blocks/QmR2pm6hPxv7pEgNaPE477rVBNSZnbUgXsSn2R9RqK9tAH: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ipfs/ipld-explorer-components/27940a7955a73d2ecd2acb61b379903c23a107b2/test/e2e/fixtures/explore/blocks/QmR2pm6hPxv7pEgNaPE477rVBNSZnbUgXsSn2R9RqK9tAH -------------------------------------------------------------------------------- /test/e2e/fixtures/explore/blocks/QmSnuWmxptJZdLJpKRarxBMS2Ju2oANVrgbr2xWbie9b2D: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ipfs/ipld-explorer-components/27940a7955a73d2ecd2acb61b379903c23a107b2/test/e2e/fixtures/explore/blocks/QmSnuWmxptJZdLJpKRarxBMS2Ju2oANVrgbr2xWbie9b2D -------------------------------------------------------------------------------- /test/e2e/fixtures/explore/blocks/QmT4hPa6EeeCaTAb4a6ddFf4Lk5da9C1f4nMBmMJgbAW3z: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ipfs/ipld-explorer-components/27940a7955a73d2ecd2acb61b379903c23a107b2/test/e2e/fixtures/explore/blocks/QmT4hPa6EeeCaTAb4a6ddFf4Lk5da9C1f4nMBmMJgbAW3z -------------------------------------------------------------------------------- /test/e2e/fixtures/explore/blocks/QmUh6QSTxDKX5qoNU1GoogbhTveQQV9JMeQjfFVchAtd5Q: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ipfs/ipld-explorer-components/27940a7955a73d2ecd2acb61b379903c23a107b2/test/e2e/fixtures/explore/blocks/QmUh6QSTxDKX5qoNU1GoogbhTveQQV9JMeQjfFVchAtd5Q -------------------------------------------------------------------------------- /test/e2e/fixtures/explore/blocks/QmVFemV13MkWS7xyDzcJbBd5qL31NXAHcsYk4wy4RhwHqh: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ipfs/ipld-explorer-components/27940a7955a73d2ecd2acb61b379903c23a107b2/test/e2e/fixtures/explore/blocks/QmVFemV13MkWS7xyDzcJbBd5qL31NXAHcsYk4wy4RhwHqh -------------------------------------------------------------------------------- /test/e2e/fixtures/explore/blocks/QmVmf9vLEdWeBjh74kTibHVkim6iLsRXs5jhHzbSdWjoLt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ipfs/ipld-explorer-components/27940a7955a73d2ecd2acb61b379903c23a107b2/test/e2e/fixtures/explore/blocks/QmVmf9vLEdWeBjh74kTibHVkim6iLsRXs5jhHzbSdWjoLt -------------------------------------------------------------------------------- /test/e2e/fixtures/explore/blocks/QmXPqQjySvisWjE11dkrANGmfvUsmPN5RP9oWVRnR7RQuu: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ipfs/ipld-explorer-components/27940a7955a73d2ecd2acb61b379903c23a107b2/test/e2e/fixtures/explore/blocks/QmXPqQjySvisWjE11dkrANGmfvUsmPN5RP9oWVRnR7RQuu -------------------------------------------------------------------------------- /test/e2e/fixtures/explore/blocks/QmZA6h4vP17Ktw5vyMdSQNTvzsncQKDSifYwJznY461rY2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ipfs/ipld-explorer-components/27940a7955a73d2ecd2acb61b379903c23a107b2/test/e2e/fixtures/explore/blocks/QmZA6h4vP17Ktw5vyMdSQNTvzsncQKDSifYwJznY461rY2 -------------------------------------------------------------------------------- /test/e2e/fixtures/explore/blocks/QmawceGscqN4o8Y8Fv26UUmB454kn2bnkXV5tEQYc4jBd6: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ipfs/ipld-explorer-components/27940a7955a73d2ecd2acb61b379903c23a107b2/test/e2e/fixtures/explore/blocks/QmawceGscqN4o8Y8Fv26UUmB454kn2bnkXV5tEQYc4jBd6 -------------------------------------------------------------------------------- /test/e2e/fixtures/explore/blocks/QmbQDovX7wRe9ek7u6QXe9zgCXkTzoUSsTFJEkrYV1HrVR: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ipfs/ipld-explorer-components/27940a7955a73d2ecd2acb61b379903c23a107b2/test/e2e/fixtures/explore/blocks/QmbQDovX7wRe9ek7u6QXe9zgCXkTzoUSsTFJEkrYV1HrVR -------------------------------------------------------------------------------- /test/e2e/fixtures/explore/blocks/Qmd1WaiaEBDe7H3a3U1CToaoaFsxUXEnmhw68ub5u1iY7Q: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ipfs/ipld-explorer-components/27940a7955a73d2ecd2acb61b379903c23a107b2/test/e2e/fixtures/explore/blocks/Qmd1WaiaEBDe7H3a3U1CToaoaFsxUXEnmhw68ub5u1iY7Q -------------------------------------------------------------------------------- /test/e2e/fixtures/explore/blocks/QmdmQXB2mzChmMeKY47C43LxUdg1NDJ5MWcKMKxDu7RgQm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ipfs/ipld-explorer-components/27940a7955a73d2ecd2acb61b379903c23a107b2/test/e2e/fixtures/explore/blocks/QmdmQXB2mzChmMeKY47C43LxUdg1NDJ5MWcKMKxDu7RgQm -------------------------------------------------------------------------------- /test/e2e/fixtures/explore/blocks/QmeQtZfwuq6aWRarY9P3L9MWhZ6QTonDe9ahWECGBZjyEJ: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ipfs/ipld-explorer-components/27940a7955a73d2ecd2acb61b379903c23a107b2/test/e2e/fixtures/explore/blocks/QmeQtZfwuq6aWRarY9P3L9MWhZ6QTonDe9ahWECGBZjyEJ -------------------------------------------------------------------------------- /test/e2e/fixtures/explore/blocks/README.md: -------------------------------------------------------------------------------- 1 | Generated with test/e2e/fixtures/generate-fixtures.sh 2 | 3 | generated by running the following from the root: 4 | 5 | ### Fixtures for Project apollo archives 6 | ```bash 7 | test/e2e/fixtures/generate-fixtures.sh QmSnuWmxptJZdLJpKRarxBMS2Ju2oANVrgbr2xWbie9b2D explore/blocks 8 | test/e2e/fixtures/generate-fixtures.sh QmeQtZfwuq6aWRarY9P3L9MWhZ6QTonDe9ahWECGBZjyEJ explore/blocks 9 | test/e2e/fixtures/generate-fixtures.sh QmVmf9vLEdWeBjh74kTibHVkim6iLsRXs5jhHzbSdWjoLt explore/blocks 10 | test/e2e/fixtures/generate-fixtures.sh QmT4hPa6EeeCaTAb4a6ddFf4Lk5da9C1f4nMBmMJgbAW3z explore/blocks 11 | test/e2e/fixtures/generate-fixtures.sh QmZA6h4vP17Ktw5vyMdSQNTvzsncQKDSifYwJznY461rY2 explore/blocks 12 | test/e2e/fixtures/generate-fixtures.sh QmR2pm6hPxv7pEgNaPE477rVBNSZnbUgXsSn2R9RqK9tAH explore/blocks 13 | ``` 14 | 15 | ### Fixtures for XKCD archives 16 | ```bash 17 | test/e2e/fixtures/generate-fixtures.sh QmdmQXB2mzChmMeKY47C43LxUdg1NDJ5MWcKMKxDu7RgQm explore/blocks 18 | test/e2e/fixtures/generate-fixtures.sh QmbQDovX7wRe9ek7u6QXe9zgCXkTzoUSsTFJEkrYV1HrVR explore/blocks 19 | test/e2e/fixtures/generate-fixtures.sh QmawceGscqN4o8Y8Fv26UUmB454kn2bnkXV5tEQYc4jBd6 explore/blocks 20 | ``` 21 | -------------------------------------------------------------------------------- /test/e2e/fixtures/explore/blocks/bafyreicds4picvqi46ljgw2eombkoifaftyjgd4abvfvledghftn2xnena: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ipfs/ipld-explorer-components/27940a7955a73d2ecd2acb61b379903c23a107b2/test/e2e/fixtures/explore/blocks/bafyreicds4picvqi46ljgw2eombkoifaftyjgd4abvfvledghftn2xnena -------------------------------------------------------------------------------- /test/e2e/fixtures/generate-fixtures.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | 4 | # Data is loaded by the function `loadBlockFixtures` in ./load-block-fixtures.ts 5 | # Example: const data = await readFile(join(__dirname, '/explore/blocks', blockCid), { encoding: null }) 6 | save_fixture() { 7 | local fixture_cid=$1 8 | shift 9 | local fixture_path="$1/$fixture_cid" 10 | echo -e "\$fixture_path: $fixture_path \n" 11 | npx kubo block get $fixture_cid > $fixture_path 12 | } 13 | 14 | # If a user requests to generate all the fixtures for a full path, we need to loop through the path and save each block 15 | handlePath() { 16 | local path=$1 17 | shift 18 | local save_path=$1 19 | shift 20 | 21 | # while the path contains slashes, we need to find the CID at each part of the path, save the block, and remove the trailing part in the path 22 | while (( $(echo $path | grep -o '/' | wc -l) > 0 )); do 23 | echo "Path=$path" 24 | local cid_for_path_part=$(npx kubo block stat $path | ggrep '^Key:' | awk -F': ' '{print $2}') 25 | 26 | echo "CID=$cid_for_path_part" 27 | # save the block 28 | save_fixture $cid_for_path_part $save_path 29 | # remove the right-most part from the path 30 | path=$(echo $path | rev | cut -d'/' -f2- | rev) 31 | done 32 | 33 | echo "Path=$path" 34 | echo "CID=$path" 35 | # now get the last CID in the path and save it 36 | save_fixture $path $save_path 37 | } 38 | 39 | # Example call: 40 | # test/e2e/fixtures/generate-fixtures.sh QmSnuWmxptJZdLJpKRarxBMS2Ju2oANVrgbr2xWbie9b2D explore/blocks 41 | # saves passed root_cid block to the passed save_path 42 | # Originally intended for helping save fixtures for e2e explore.test.js so we could test files in offline mode. i.e. 43 | # not making network requests. 44 | main () { 45 | # if RUNNING='null', kubo daemon is not running 46 | EXISTING_NODE=$(npx kubo id | jq .Addresses) 47 | if [ "$EXISTING_NODE" == "null" ]; then 48 | echo "Kubo daemon is not running. Starting it." 49 | mkfifo /tmp/kubo_daemon_fifo 50 | npx kubo daemon >& /tmp/kubo_daemon_fifo & 51 | # wait for 'Daemon is ready' message 52 | while read daemon; do 53 | if [[ $daemon == *"Daemon is ready"* ]]; then 54 | echo "Daemon is ready" 55 | break 56 | fi 57 | done < /tmp/kubo_daemon_fifo 58 | fi 59 | local DIR 60 | local FILE 61 | FILE="${BASH_SOURCE[0]:-${(%):-%x}}" 62 | DIR="$(dirname $FILE)" 63 | echo -e "\$DIR: $DIR \n" 64 | local root_cid=$1 65 | shift 66 | local save_path="$DIR/explore/blocks" 67 | 68 | # if instead of an individual CID, we get a path, we need to loop through the path and save each block 69 | if [[ $root_cid == *"/"* ]]; then 70 | handlePath $root_cid $save_path 71 | else 72 | echo -e "\$root_cid: $root_cid \n" 73 | save_fixture $root_cid $save_path 74 | fi 75 | 76 | # Children are not needed for now, but you can get them like so: 77 | # for cid in $(npx kubo ls $root_cid | awk '{ print $1}'); do 78 | # echo -e "\$cid: $cid \n" 79 | # save_fixture $cid $save_path 80 | # done 81 | 82 | if [ "$EXISTING_NODE" == "null" ]; then 83 | echo "Kubo daemon was started by us. Stopping it." 84 | npx kubo shutdown 85 | rm /tmp/kubo_daemon_fifo 86 | fi 87 | } 88 | 89 | main $@ 90 | -------------------------------------------------------------------------------- /test/e2e/fixtures/load-block-fixtures.ts: -------------------------------------------------------------------------------- 1 | import { readFile } from 'node:fs/promises' 2 | import { join, dirname } from 'node:path' 3 | import { fileURLToPath } from 'node:url' 4 | import { expect } from '@playwright/test' 5 | import { type KuboRPCClient } from 'kubo-rpc-client' 6 | 7 | // eslint-disable-next-line @typescript-eslint/naming-convention 8 | const __filename = fileURLToPath(import.meta.url) 9 | // eslint-disable-next-line @typescript-eslint/naming-convention 10 | const __dirname = dirname(__filename) 11 | 12 | interface LoadBlockFixturesOptions { 13 | ipfs: KuboRPCClient 14 | blockCid: string | string[] 15 | blockPutArgs?: Record 16 | } 17 | /** 18 | * Loads saved block fixtures from fixtures/explore/blocks and adds them locally to the ipfs node 19 | */ 20 | export async function loadBlockFixtures ({ ipfs, blockCid, blockPutArgs = { format: 'v0' } }: LoadBlockFixturesOptions): Promise { 21 | try { 22 | if (Array.isArray(blockCid)) { 23 | await Promise.all(blockCid.map(async (cid) => loadBlockFixtures({ ipfs, blockCid: cid, blockPutArgs }))) 24 | return 25 | } 26 | // read the data from the file 27 | const data = await readFile(join(__dirname, '/explore/blocks', blockCid), { encoding: null }) 28 | // add the data to the ipfs node 29 | const result = await ipfs.block.put(data, blockPutArgs) 30 | // check that the CID returned from block.put matches the given block fixture CID 31 | expect(result.toString()).toBe(blockCid) 32 | } catch (e) { 33 | console.error(e) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /test/e2e/fixtures/test-explore-cid.ts: -------------------------------------------------------------------------------- 1 | import { type Page } from '@playwright/test' 2 | import { expect } from '@playwright/test' 3 | 4 | interface TestExploreCidOptions { 5 | page: Page 6 | cid: string 7 | type: string 8 | humanReadableCID?: string 9 | fillOutForm?: boolean 10 | } 11 | /** 12 | * Fills out the explore form (optional), waits for CID of given type to be loaded, and checks if CID details are correct. 13 | */ 14 | export async function testExploredCid ({ cid, type, humanReadableCID, page, fillOutForm = true }: TestExploreCidOptions): Promise { 15 | if (fillOutForm) { 16 | await page.fill('[data-id="IpldExploreForm"] input[id="ipfs-path"]', cid) 17 | await page.press('[data-id="IpldExploreForm"] button[type="submit"]', 'Enter') 18 | } 19 | 20 | await page.waitForSelector(`.joyride-explorer-cid [title="${cid}"]`) // cid is displayed in the CID INFO section. 21 | await page.waitForSelector(`[title="${type}"]`) 22 | 23 | if (humanReadableCID != null) { 24 | // expect cid details 25 | await page.waitForSelector('#CidInfo-human-readable-cid') 26 | const actualHumanReadableCID = await page.$eval('#CidInfo-human-readable-cid', firstRes => firstRes.textContent) 27 | expect(actualHumanReadableCID).toBe(humanReadableCID) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /test/e2e/playwright.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig, type PlaywrightTestConfig } from '@playwright/test' 2 | import getPort from 'aegir/get-port' 3 | 4 | const webHostPort = '5173' 5 | const rpcPort = await getPort(5001, '0.0.0.0') 6 | 7 | const config: PlaywrightTestConfig = { 8 | testDir: './', 9 | timeout: process.env.CI != null ? 90 * 1000 : 30 * 1000, 10 | fullyParallel: true, 11 | forbidOnly: Boolean(process.env.CI), 12 | retries: process.env.CI != null ? 2 : 0, 13 | workers: (process.env.DEBUG != null || process.env.CI != null) ? 1 : undefined, 14 | reporter: 'list', 15 | use: { 16 | headless: process.env.DEBUG == null, 17 | viewport: { width: 1366, height: 768 }, 18 | baseURL: `http://localhost:${webHostPort}/`, 19 | storageState: 'test/e2e/state.json', 20 | trace: 'on-first-retry' 21 | }, 22 | globalSetup: './setup/global-setup.ts', 23 | globalTeardown: './setup/global-teardown.ts', 24 | webServer: [ 25 | { 26 | command: `npx tsx ipfs-backend.ts ${rpcPort}`, 27 | timeout: 5 * 1000, 28 | port: rpcPort, 29 | cwd: './setup', 30 | reuseExistingServer: process.env.CI == null 31 | }, 32 | { 33 | command: 'npm run start', 34 | timeout: 5 * 1000, 35 | url: `http://localhost:${webHostPort}/`, 36 | cwd: '../../', 37 | reuseExistingServer: process.env.CI == null, 38 | env: { 39 | REACT_APP_ENV: 'test', 40 | NODE_ENV: 'test', 41 | PORT: webHostPort 42 | } 43 | } 44 | ] 45 | } 46 | 47 | export default defineConfig(config) 48 | -------------------------------------------------------------------------------- /test/e2e/setup/global-setup.ts: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs' 2 | import path from 'node:path' 3 | import { fileURLToPath } from 'node:url' 4 | import { chromium, type PlaywrightTestConfig } from '@playwright/test' 5 | 6 | // eslint-disable-next-line @typescript-eslint/naming-convention 7 | const __filename = fileURLToPath(import.meta.url) 8 | // eslint-disable-next-line @typescript-eslint/naming-convention 9 | const __dirname = path.dirname(__filename) 10 | 11 | interface ApiOpts { 12 | protocol: string 13 | host: string 14 | port: number 15 | } 16 | // make sure that ipfs-backend is fully running 17 | const ensureKuboDaemon = async (apiOpts: ApiOpts): Promise => { 18 | const backendEndpoint = `${apiOpts.protocol}://${apiOpts.host}:${apiOpts.port}` 19 | const body = new FormData() 20 | body.append('file', new Blob([new Uint8Array([1, 2, 3])]), 'test.txt') 21 | 22 | const fakeFileResult = await fetch(`${backendEndpoint}/api/v0/block/put`, { 23 | body, 24 | method: 'POST' 25 | }) 26 | if (!fakeFileResult.ok) { 27 | console.error('fakeFileResult not okay', await fakeFileResult.text()) 28 | throw new Error(`IPFS backend not running at ${backendEndpoint}`) 29 | } 30 | 31 | const { Key: cidString } = await fakeFileResult.json() 32 | const getContentResult = await fetch(`${backendEndpoint}/api/v0/block/get?arg=${cidString}`, { 33 | method: 'POST' 34 | }) 35 | if (!getContentResult.ok) { 36 | console.error('Could not get fake file', await getContentResult.text()) 37 | throw new Error(`IPFS backend not running at ${backendEndpoint}`) 38 | } 39 | } 40 | 41 | const globalSetup = async (config: PlaywrightTestConfig): Promise => { 42 | // Read and expose backend info in env availables inside of test() blocks 43 | const { rpcAddr, id, agentVersion, apiOpts, kuboGateway } = JSON.parse(fs.readFileSync(path.join(__dirname, 'ipfs-backend.json'), 'utf8')) 44 | process.env.IPFS_RPC_ADDR = rpcAddr 45 | process.env.IPFS_RPC_ID = id 46 | process.env.IPFS_RPC_VERSION = agentVersion 47 | 48 | await ensureKuboDaemon(apiOpts) 49 | 50 | // @ts-expect-error - broken types for projects from playwright config 51 | const { baseURL, storageState } = config.projects[0].use 52 | const browser = await chromium.launch() 53 | const page = await browser.newPage({ 54 | storageState: { 55 | cookies: [], 56 | origins: [ 57 | { 58 | origin: baseURL, 59 | localStorage: [ 60 | { 61 | name: 'kuboGateway', 62 | value: JSON.stringify(kuboGateway) 63 | }, 64 | { 65 | name: 'explore.ipld.gatewayEnabled', 66 | value: 'false' 67 | } 68 | ] 69 | } 70 | ] 71 | } 72 | }) 73 | await page.goto(baseURL) 74 | await page.context().storageState({ path: storageState }) 75 | await browser.close() 76 | } 77 | 78 | export default globalSetup 79 | -------------------------------------------------------------------------------- /test/e2e/setup/global-teardown.ts: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs' 2 | import path from 'node:path' 3 | import { fileURLToPath } from 'node:url' 4 | 5 | // eslint-disable-next-line @typescript-eslint/naming-convention 6 | const __filename = fileURLToPath(import.meta.url) 7 | // eslint-disable-next-line @typescript-eslint/naming-convention 8 | const __dirname = path.dirname(__filename) 9 | 10 | const globalTeardown = async (): Promise => { 11 | fs.rmSync(path.join(__dirname, 'ipfs-backend.json'), { force: true }) 12 | } 13 | 14 | export default globalTeardown 15 | -------------------------------------------------------------------------------- /test/e2e/setup/ipfs-backend.ts: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs' 2 | import path from 'node:path' 3 | import { fileURLToPath } from 'url' 4 | import { createFactory, type KuboNode } from 'ipfsd-ctl' 5 | import { path as kuboPath } from 'kubo' 6 | import { create, multiaddr, type KuboRPCClient } from 'kubo-rpc-client' 7 | 8 | // eslint-disable-next-line @typescript-eslint/naming-convention 9 | const __filename = fileURLToPath(import.meta.url) 10 | // eslint-disable-next-line @typescript-eslint/naming-convention 11 | const __dirname = path.dirname(__filename) 12 | const { console } = globalThis 13 | 14 | let ipfsd: KuboNode 15 | let ipfs: KuboRPCClient 16 | async function run (rpcPort?: string): Promise { 17 | if (ipfsd != null && ipfs != null) { 18 | throw new Error('IPFS backend already running') 19 | } 20 | const endpoint = process.env.E2E_API_URL 21 | if (endpoint != null && endpoint !== '') { 22 | // create http rpc client for endpoint passed via E2E_API_URL= 23 | ipfs = create(endpoint) 24 | } else { 25 | // use ipfds-ctl to spawn daemon to expose http api used for e2e tests 26 | const factory = createFactory({ 27 | rpc: create, 28 | type: 'kubo', 29 | bin: process.env.IPFS_GO_EXEC ?? kuboPath(), 30 | init: { 31 | config: { 32 | Addresses: { 33 | API: `/ip4/127.0.0.1/tcp/${rpcPort}`, 34 | Gateway: '/ip4/127.0.0.1/tcp/0' 35 | }, 36 | Gateway: { 37 | NoFetch: true, 38 | ExposeRoutingAPI: true 39 | }, 40 | Routing: { 41 | Type: 'none' 42 | } 43 | } 44 | }, 45 | // sets up all CORS headers required for accessing HTTP API port of ipfsd node 46 | test: true 47 | }) 48 | 49 | ipfsd = await factory.spawn({ type: 'kubo' }) 50 | ipfs = ipfsd.api 51 | } 52 | const { id, agentVersion } = await ipfs.id() 53 | const { gateway, api: rpcAddr } = await ipfsd.info() 54 | const rpcApiMaddr = multiaddr(rpcAddr) 55 | const { address: apiHost, port: apiPort } = rpcApiMaddr.nodeAddress() 56 | const { hostname: gatewayHost, port: gatewayPort } = new URL(gateway) 57 | 58 | if (String(apiPort) !== rpcPort) { 59 | console.error(`Invalid RPC port returned by IPFS backend: ${apiPort} != ${rpcPort}`) 60 | await ipfsd.stop() 61 | process.exit(1) 62 | } 63 | 64 | // persist details for e2e tests 65 | fs.writeFileSync(path.join(__dirname, 'ipfs-backend.json'), JSON.stringify({ 66 | rpcAddr, 67 | id, 68 | agentVersion, 69 | /** 70 | * Used by ipfs-webui to connect to Kubo via the kubo-rpc-client 71 | */ 72 | apiOpts: { 73 | host: apiHost, 74 | port: apiPort, 75 | protocol: 'http' 76 | }, 77 | /** 78 | * Used by ipld-explorer-components to connect to the kubo gateway 79 | */ 80 | kuboGateway: { 81 | host: gatewayHost, 82 | port: gatewayPort, 83 | protocol: 'http' 84 | } 85 | })) 86 | 87 | console.log(`\nE2E using ${agentVersion} (${endpoint ?? rpcAddr}) 88 | Peer ID: ${id} 89 | rpcAddr: ${rpcAddr} 90 | gatewayAddr: ${gateway}`) 91 | 92 | const teardown = (): void => { 93 | console.log(`Stopping IPFS backend ${id}`) 94 | ipfsd.stop().catch(console.error).finally(() => process.exit(0)) 95 | } 96 | process.stdin.resume() 97 | process.on('SIGINT', teardown) 98 | } 99 | 100 | run(process.argv[2] ?? '5001').catch(console.error) 101 | -------------------------------------------------------------------------------- /test/unit/helia-mock.ts: -------------------------------------------------------------------------------- 1 | import { createHeliaHTTP, type Helia } from '@helia/http' 2 | 3 | export async function createHeliaMock (): Promise { 4 | const helia = await createHeliaHTTP() 5 | 6 | return helia 7 | } 8 | -------------------------------------------------------------------------------- /test/unit/setup.ts: -------------------------------------------------------------------------------- 1 | import '@testing-library/jest-dom/vitest' 2 | import { cleanup } from '@testing-library/react' 3 | import { afterEach } from 'vitest' 4 | 5 | // runs a cleanup after each test case (e.g. clearing jsdom) 6 | afterEach(() => { 7 | cleanup() 8 | }) 9 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // "extends": "aegir/src/config/tsconfig.aegir.json", 3 | "compilerOptions": { 4 | "target": "ESNext", 5 | "lib": [ 6 | "dom", 7 | "dom.iterable", 8 | "es2021", 9 | "esnext" 10 | ], 11 | "types": [ 12 | "vite/client", 13 | "vite-plugin-svgr/client", 14 | "mocha", 15 | "jest", 16 | "@storybook/types", 17 | "vitest/globals", 18 | "@testing-library/jest-dom" 19 | ], 20 | "allowJs": false, 21 | "skipLibCheck": true, 22 | "esModuleInterop": true, 23 | "allowSyntheticDefaultImports": true, 24 | "strict": true, 25 | "forceConsistentCasingInFileNames": true, 26 | "noFallthroughCasesInSwitch": true, 27 | "module": "ES2022", 28 | "moduleResolution": "bundler", 29 | "resolveJsonModule": true, 30 | "isolatedModules": true, 31 | "declaration": true, 32 | "outDir": "dist", 33 | "declarationDir": "dist", 34 | "jsx": "react-jsx", 35 | "rootDir": "." 36 | }, 37 | "include": [ 38 | "src/**/*", 39 | "dev/**/*", 40 | "test/**/*", 41 | "vite.config.ts" 42 | ], 43 | "exclude": [ 44 | "src/**/*.stories.*", 45 | "node_modules", 46 | "storybook-static", 47 | "build", 48 | "dist", 49 | "vite.config.ts", 50 | "package.json" 51 | ], 52 | "plugins": [ 53 | { 54 | "name": "typescript-plugin-css-modules", 55 | "options": { 56 | "classnameTransform": "camelCaseOnly" 57 | } 58 | } 59 | ] 60 | } 61 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { resolve as pathResolve } from 'node:path' 2 | import react from '@vitejs/plugin-react' 3 | import { defineConfig, type UserConfig, type UserConfigExport } from 'vite' 4 | import svgrPlugin from 'vite-plugin-svgr' 5 | import dts from 'vite-plugin-dts'; 6 | 7 | // https://vitejs.dev/config/ 8 | export default defineConfig(({ mode, command }) => { 9 | const vitePlugins: UserConfig['plugins'] = [ 10 | react(), 11 | svgrPlugin(), 12 | dts() 13 | ] 14 | let viteResolve: UserConfig['resolve'] = { 15 | alias: [{ find: '@', replacement: pathResolve(__dirname, '/src') }] 16 | } 17 | 18 | const viteDefine: UserConfig['define'] = {} 19 | if (mode === 'development' && command === 'serve') { 20 | viteDefine.global = 'globalThis' 21 | } 22 | 23 | const viteOptimizeDeps: UserConfig['optimizeDeps'] = { 24 | esbuildOptions: { 25 | loader: { 26 | '.js': 'jsx', 27 | '.ts': 'tsx', 28 | '.jsx': 'jsx', 29 | '.tsx': 'tsx' 30 | } 31 | } 32 | } 33 | const viteEsBuild: UserConfig['esbuild'] = { 34 | loader: 'tsx', 35 | include: /\.(tsx?|jsx?)$/ 36 | } 37 | const viteBuild: UserConfig['build'] = { 38 | lib: { 39 | entry: { 40 | index: pathResolve(__dirname, 'src/index.ts'), 41 | 'providers/index': pathResolve(__dirname, 'src/providers/index.ts'), 42 | pages: pathResolve(__dirname, 'src/pages.ts'), 43 | forms: pathResolve(__dirname, 'src/forms.ts'), 44 | }, 45 | name: 'ipld-explorer-components', 46 | fileName: (format, entryName) => `${format}/${entryName}.js`, 47 | formats: ['es'] 48 | }, 49 | outDir: 'dist', 50 | target: 'esnext', 51 | minify: false, 52 | cssCodeSplit: false, 53 | rollupOptions: { 54 | external: [ 55 | 'react', 56 | 'react-dom', 57 | 'react-i18next', 58 | 'i18next', 59 | 'i18next-browser-languagedetector', 60 | 'i18next-http-backend', 61 | 'i18next-icu', 62 | 'ipfs-css', 63 | 'tachyons', 64 | /\.stories\..+$/, 65 | // all test files (i.e. *.spec.{js,jsx,ts,tsx} or *.test.{js,jsx,ts,tsx}) 66 | /\.test\..+$/, 67 | /\.spec\..+$/, 68 | ], 69 | preserveEntrySignatures: 'strict', 70 | input: { 71 | index: pathResolve(__dirname, 'src/index.ts'), 72 | 'providers/index': pathResolve(__dirname, 'src/providers/index.ts'), 73 | pages: pathResolve(__dirname, 'src/pages.ts'), 74 | forms: pathResolve(__dirname, 'src/forms.ts'), 75 | }, 76 | output: { 77 | preserveModules: true, 78 | preserveModulesRoot: 'src', 79 | entryFileNames: '[name].js', 80 | globals: { 81 | react: 'React', 82 | 'react-dom': 'ReactDOM' 83 | }, 84 | }, 85 | }, 86 | sourcemap: true, 87 | emptyOutDir: true, 88 | } 89 | 90 | viteResolve = { 91 | alias: [ 92 | { find: /^process$/, replacement: 'rollup-plugin-node-polyfills/polyfills/process-es6' }, 93 | { find: /^stream$/, replacement: 'rollup-plugin-node-polyfills/polyfills/stream' }, 94 | { find: /^_stream_duplex$/, replacement: 'rollup-plugin-node-polyfills/polyfills/readable-stream/duplex' }, 95 | { find: /^_stream_transform$/, replacement: 'rollup-plugin-node-polyfills/polyfills/readable-stream/transform' }, 96 | ] 97 | } 98 | viteOptimizeDeps.include = [] 99 | viteOptimizeDeps.esbuildOptions = { 100 | ...viteOptimizeDeps.esbuildOptions, 101 | plugins: [] 102 | } 103 | 104 | const finalConfig: UserConfigExport = { 105 | plugins: vitePlugins, 106 | resolve: viteResolve, 107 | define: viteDefine, 108 | optimizeDeps: viteOptimizeDeps, 109 | esbuild: viteEsBuild, 110 | build: viteBuild, 111 | server: { 112 | open: true 113 | }, 114 | css: { 115 | modules: { 116 | localsConvention: 'camelCaseOnly' 117 | } 118 | } 119 | } 120 | 121 | return finalConfig 122 | }) 123 | -------------------------------------------------------------------------------- /vitest.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig, mergeConfig } from 'vitest/config' 2 | import viteConfig from './vite.config' 3 | 4 | export default defineConfig((configEnv) => mergeConfig( 5 | viteConfig(configEnv), 6 | defineConfig({ 7 | test: { 8 | globals: true, 9 | environment: 'jsdom', 10 | setupFiles: './test/unit/setup.ts', 11 | include: [ 12 | 'src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}' 13 | ], 14 | coverage: { 15 | exclude: [ 16 | 'coverage/**', 17 | 'dist/**', 18 | '**/node_modules/**', 19 | '**/[.]**', 20 | 'packages/*/test?(s)/**', 21 | '**/*.d.ts', 22 | '**/virtual:*', 23 | '**/__x00__*', 24 | '**/\x00*', 25 | 'cypress/**', 26 | 'test?(s)/**', 27 | 'test?(-*).?(c|m)[jt]s?(x)', 28 | '**/*{.,-}{test,spec,bench,benchmark}?(-d).?(c|m)[jt]s?(x)', 29 | '**/__tests__/**', 30 | '**/{karma,rollup,webpack,vite,vitest,jest,ava,babel,nyc,cypress,tsup,build,eslint,prettier}.config.*', 31 | '**/vitest.{workspace,projects}.[jt]s?(on)', 32 | '**/.{eslint,mocha,prettier}rc.{?(c|m)js,yml}', 33 | 'storybook-static/**', 34 | 'build/**', 35 | 'dev/**' 36 | ] 37 | } 38 | } 39 | }) 40 | )) 41 | --------------------------------------------------------------------------------