diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e6719b73..4a742a97 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,6 +25,7 @@ jobs: - helia-nextjs - helia-script-tag - helia-vite + - helia-vue - helia-webpack defaults: run: @@ -78,6 +79,7 @@ jobs: - helia-nextjs - helia-script-tag - helia-vite + - helia-vue - helia-webpack steps: - uses: convictional/trigger-workflow-and-wait@f69fa9eedd3c62a599220f4d5745230e237904be diff --git a/examples/helia-vite/src/App.jsx b/examples/helia-vite/src/App.jsx index 4c962707..f5509f78 100644 --- a/examples/helia-vite/src/App.jsx +++ b/examples/helia-vite/src/App.jsx @@ -1,17 +1,16 @@ -import { React } from 'react' -import { useState } from 'react' +import { React, useState } from 'react' import './App.css' import { useHelia } from '@/hooks/useHelia' import { useCommitText } from '@/hooks/useCommitText' -function App() { - const [text, setText] = useState("") +function App () { + const [text, setText] = useState('') const { error, starting } = useHelia() const { cidString, commitText, fetchCommittedText, - committedText, + committedText } = useCommitText() return ( @@ -20,8 +19,9 @@ function App() { id="heliaStatus" style={{ border: `4px solid ${ - error ? 'red': - starting ? 'yellow' : 'green' + error +? 'red' + : starting ? 'yellow' : 'green' }`, paddingBottom: '4px' }} diff --git a/examples/helia-vite/src/hooks/useCommitText.jsx b/examples/helia-vite/src/hooks/useCommitText.jsx index 401cca6f..035ce927 100644 --- a/examples/helia-vite/src/hooks/useCommitText.jsx +++ b/examples/helia-vite/src/hooks/useCommitText.jsx @@ -5,10 +5,10 @@ const encoder = new TextEncoder() const decoder = new TextDecoder() export const useCommitText = () => { - const {helia, fs, error, starting } = useHelia() + const { helia, fs, error, starting } = useHelia() const [cid, setCid] = useState(null) - const [cidString, setCidString] = useState("") - const [committedText, setCommittedText] = useState("") + const [cidString, setCidString] = useState('') + const [committedText, setCommittedText] = useState('') const commitText = useCallback(async (text) => { if (!error && !starting) { diff --git a/examples/helia-vite/src/hooks/useHelia.jsx b/examples/helia-vite/src/hooks/useHelia.jsx index 2eef1b45..f1f4878e 100644 --- a/examples/helia-vite/src/hooks/useHelia.jsx +++ b/examples/helia-vite/src/hooks/useHelia.jsx @@ -3,7 +3,5 @@ import { HeliaContext } from '@/provider/HeliaProvider' export const useHelia = () => { const { helia, fs, error, starting } = useContext(HeliaContext) - return {helia, fs, error, starting} + return { helia, fs, error, starting } } - - diff --git a/examples/helia-vite/src/main.jsx b/examples/helia-vite/src/main.jsx index 15a45c96..3957671d 100644 --- a/examples/helia-vite/src/main.jsx +++ b/examples/helia-vite/src/main.jsx @@ -9,5 +9,5 @@ ReactDOM.createRoot(document.getElementById('root')).render( - , + ) diff --git a/examples/helia-vite/src/provider/HeliaProvider.jsx b/examples/helia-vite/src/provider/HeliaProvider.jsx index f15bf2ed..4fa4b8c6 100644 --- a/examples/helia-vite/src/provider/HeliaProvider.jsx +++ b/examples/helia-vite/src/provider/HeliaProvider.jsx @@ -1,4 +1,10 @@ -import { React } from 'react' +import { + React, + useEffect, + useState, + useCallback, + createContext +} from 'react' import { createHelia } from 'helia' import { createLibp2p } from 'libp2p' import { noise } from '@chainsafe/libp2p-noise' @@ -8,12 +14,6 @@ import { bootstrap } from '@libp2p/bootstrap' import { unixfs } from '@helia/unixfs' import { MemoryBlockstore } from 'blockstore-core' import { MemoryDatastore } from 'datastore-core' -import { - useEffect, - useState, - useCallback, - createContext -} from 'react' import PropTypes from 'prop-types' export const HeliaContext = createContext({ @@ -23,7 +23,7 @@ export const HeliaContext = createContext({ starting: true }) -export const HeliaProvider = ({children}) => { +export const HeliaProvider = ({ children }) => { const [helia, setHelia] = useState(null) const [fs, setFs] = useState(null) const [starting, setStarting] = useState(true) @@ -31,9 +31,9 @@ export const HeliaProvider = ({children}) => { const startHelia = useCallback(async () => { if (helia) { - console.info("helia already started") + console.info('helia already started') } else if (window.helia) { - console.info("found a windowed instance of helia, populating ...") + console.info('found a windowed instance of helia, populating ...') setHelia(window.helia) setFs(unixfs(helia)) setStarting(false) @@ -61,10 +61,10 @@ export const HeliaProvider = ({children}) => { peerDiscovery: [ bootstrap({ list: [ - "/dnsaddr/bootstrap.libp2p.io/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN", - "/dnsaddr/bootstrap.libp2p.io/p2p/QmQCU2EcMqAqQPR2i9bChDtGNJchTbq5TbXJJ16u19uLTa", - "/dnsaddr/bootstrap.libp2p.io/p2p/QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj75Nb", - "/dnsaddr/bootstrap.libp2p.io/p2p/QmcZf59bWwK5XFi76CZX8cbJ4BhTzzA3gU1ZjYZcYW3dwt" + '/dnsaddr/bootstrap.libp2p.io/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN', + '/dnsaddr/bootstrap.libp2p.io/p2p/QmQCU2EcMqAqQPR2i9bChDtGNJchTbq5TbXJJ16u19uLTa', + '/dnsaddr/bootstrap.libp2p.io/p2p/QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj75Nb', + '/dnsaddr/bootstrap.libp2p.io/p2p/QmcZf59bWwK5XFi76CZX8cbJ4BhTzzA3gU1ZjYZcYW3dwt' ] }) ] diff --git a/examples/helia-vue/.eslintrc.cjs b/examples/helia-vue/.eslintrc.cjs new file mode 100644 index 00000000..b64731a0 --- /dev/null +++ b/examples/helia-vue/.eslintrc.cjs @@ -0,0 +1,14 @@ +/* eslint-env node */ +require('@rushstack/eslint-patch/modern-module-resolution') + +module.exports = { + root: true, + 'extends': [ + 'plugin:vue/vue3-essential', + 'eslint:recommended', + '@vue/eslint-config-prettier/skip-formatting' + ], + parserOptions: { + ecmaVersion: 'latest' + } +} diff --git a/examples/helia-vue/.gitignore b/examples/helia-vue/.gitignore new file mode 100644 index 00000000..719bf30c --- /dev/null +++ b/examples/helia-vue/.gitignore @@ -0,0 +1,31 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +.DS_Store +dist +dist-ssr +coverage +*.local + +/cypress/videos/ +/cypress/screenshots/ + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +test-results/ +playwright-report/ diff --git a/examples/helia-vue/.prettierrc.json b/examples/helia-vue/.prettierrc.json new file mode 100644 index 00000000..66e23359 --- /dev/null +++ b/examples/helia-vue/.prettierrc.json @@ -0,0 +1,8 @@ +{ + "$schema": "https://json.schemastore.org/prettierrc", + "semi": false, + "tabWidth": 2, + "singleQuote": true, + "printWidth": 100, + "trailingComma": "none" +} \ No newline at end of file diff --git a/examples/helia-vue/.vscode/extensions.json b/examples/helia-vue/.vscode/extensions.json new file mode 100644 index 00000000..a7cea0b0 --- /dev/null +++ b/examples/helia-vue/.vscode/extensions.json @@ -0,0 +1,3 @@ +{ + "recommendations": ["Vue.volar"] +} diff --git a/examples/helia-vue/README.md b/examples/helia-vue/README.md new file mode 100644 index 00000000..ad210a6f --- /dev/null +++ b/examples/helia-vue/README.md @@ -0,0 +1,115 @@ +

+ + Helia logo + +

+ +

Helia with Vue

+ +

+ +
+ Explore the docs + · + View Demo + · + Report Bug + · + Request Feature/Example +

+ +## Table of Contents + +- [Table of Contents](#table-of-contents) +- [About The Project](#about-the-project) +- [Getting Started](#getting-started) + - [Prerequisites](#prerequisites) + - [Installation and Running example](#installation-and-running-example) +- [Usage](#usage) +- [Documentation](#documentation) +- [Contributing](#contributing) +- [Want to hack on IPFS?](#want-to-hack-on-ipfs) + +## About The Project + +- Read the [docs](https://ipfs.github.io/helia/modules/helia.html) +- Look into other [examples](https://github.com/ipfs-examples/helia-examples) to learn how to spawn a Helia node in Node.js and in the Browser +- Visit https://dweb-primer.ipfs.io to learn about IPFS and the concepts that underpin it +- Head over to https://proto.school to take interactive tutorials that cover core IPFS APIs +- Check out https://docs.ipfs.io for tips, how-tos and more +- See https://blog.ipfs.io for news and more +- Need help? Please ask 'How do I?' questions on https://discuss.ipfs.io + +## Getting Started + +### Prerequisites + +Make sure you have installed all of the following prerequisites on your development machine: + +- Git - [Download & Install Git](https://git-scm.com/downloads). OSX and Linux machines typically have this already installed. +- Node.js - [Download & Install Node.js](https://nodejs.org/en/download/) and the npm package manager. + +### Installation and Running example + +```console +> npm install +> npm start +or +> npm run dev +``` +To run the test + +```console +npm run test:e2e +``` + +Now open your browser at `http://localhost:5173` + +## Usage + +In this example, you will find a boilerplate you can use to guide yourself into creating a react+vite app with helia, this provides a pattern to reuse the same client across components with the context API and suggests how to integrate it with custom hooks + +You should see the following: + +![](./public/demo-vue.gif) + +_For more examples, please refer to the [Documentation](#documentation)_ + +## Documentation + +- [IPFS Primer](https://dweb-primer.ipfs.io/) +- [IPFS Docs](https://docs.ipfs.io/) +- [Tutorials](https://proto.school) +- [More examples](https://github.com/ipfs-examples/helia-examples) +- [API - Helia](https://ipfs.github.io/helia/modules/helia.html) +- [API - @helia/unixfs](https://ipfs.github.io/helia-unixfs/modules/helia.html) + +## Contributing + +Contributions are what make the open source community such an amazing place to be learn, inspire, and create. Any contributions you make are **greatly appreciated**. + +1. Fork the IPFS Project +2. Create your Feature Branch (`git checkout -b feature/amazing-feature`) +3. Commit your Changes (`git commit -a -m 'feat: add some amazing feature'`) +4. Push to the Branch (`git push origin feature/amazing-feature`) +5. Open a Pull Request + +## Want to hack on IPFS? + +[![](https://cdn.rawgit.com/jbenet/contribute-ipfs-gif/master/img/contribute.gif)](https://github.com/ipfs/community/blob/master/CONTRIBUTING.md) + +The IPFS implementation in JavaScript needs your help! There are a few things you can do right now to help out: + +Read the [Code of Conduct](https://github.com/ipfs/community/blob/master/code-of-conduct.md) and [JavaScript Contributing Guidelines](https://github.com/ipfs/community/blob/master/CONTRIBUTING_JS.md). + +- **Check out existing issues** The [issue list](https://github.com/ipfs/helia/issues) has many that are marked as ['help wanted'](https://github.com/ipfs/helia/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3A%22help+wanted%22) or ['difficulty:easy'](https://github.com/ipfs/helia/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3Adifficulty%3Aeasy) which make great starting points for development, many of which can be tackled with no prior IPFS knowledge +- **Look at the [Helia Roadmap](https://github.com/ipfs/helia/blob/main/ROADMAP.md)** This are the high priority items being worked on right now +- **Perform code reviews** More eyes will help + a. speed the project along + b. ensure quality, and + c. reduce possible future bugs +- **Add tests**. There can never be enough tests + +[cid]: https://docs.ipfs.tech/concepts/content-addressing "Content Identifier" +[Uint8Array]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array +[libp2p]: https://libp2p.io \ No newline at end of file diff --git a/examples/helia-vue/e2e/vue.spec.js b/examples/helia-vue/e2e/vue.spec.js new file mode 100644 index 00000000..d00398aa --- /dev/null +++ b/examples/helia-vue/e2e/vue.spec.js @@ -0,0 +1,92 @@ +import { playwright } from 'test-util-ipfs-example' +const { test, expect } = require('@playwright/test'); + +const play = test.extend({ + ...playwright.servers() +}) + +play.describe("Use Helia with Vue", () => { + play.beforeEach(async ({ servers, page }) => { + await page.goto(`http://localhost:${servers[0].port}/`) + }) + + play('should properly initialize Helia, and add/get a file', async ({ page }) => { + const exampleText = 'Hello Helia' + const exampleDirName = 'newdir' + const exampleFileName = 'test.txt' + + const status = page.locator('#heliaStatus') + await expect(status).toHaveCSS( + 'background-color', + 'rgb(0, 128, 0)', + {timeout: 5000} + ) + + await page.fill('#commitText', exampleText) + await page.click('#commitTextButton') + const cidOutput = page.locator('#commitTextCidOutput') + await expect(cidOutput).toHaveText( + 'cid: bafkreig7i5kbqdnoooievvfextf27eoherluxe2pi3j26hu6zpiauydydy', + {timeout: 2000} + ) + + await page.click('#fetchCommitedTextButton') + const textOutput = page.locator('#fetchedCommitedTextOutput') + await expect(textOutput).toHaveText( + `added text: ${exampleText}`, + {timeout: 2000} + ) + + await page.fill('#newDirInput', exampleDirName) + await page.click('#newDirButton') + const dirOutput = page.locator('#newDirOutput') + await expect(dirOutput).toHaveText( + 'directory Cid: bafybeif5hfzip34o7ocwfg4ge537g7o3nbh3auc3s54l3gsaantucqyyra', + {timeout: 2000} + ) + + await page.click("#statDirButton") + const statDir = page.locator('#statDirOutput') + await expect(statDir).toContainText( + 'bafybeiczsscdsbs7ffqz55asqdf3smv6klcw3gofszvwlyarci47bgf354', + {timeout: 2000} + ) + + await page.click('#getDirButton') + const lsDir = await page.locator('#dirListOutput') + await expect(lsDir).toContainText( + 'bafybeif5hfzip34o7ocwfg4ge537g7o3nbh3auc3s54l3gsaantucqyyra/newdir', + {timeout: 2000} + ) + + await page.fill('#fileNameInput', exampleFileName) + await page.fill('#fileContentInput', exampleText) + await page.click('#newFileButton') + const fileCidOutput = page.locator('#fileCidOutput') + await expect(fileCidOutput).toContainText( + 'bafkreig7i5kbqdnoooievvfextf27eoherluxe2pi3j26hu6zpiauydydy', + {timeout: 2000} + ) + const updatedDirOutput = page.locator('#updatedDirOutput') + await expect(updatedDirOutput).toContainText( + 'bafybeie6lhtnjvea4j7imyx5lp2c7kfgxjkndc4jjpmcetnkq75lapmgwm', + {timeout: 2000} + ) + + await page.click('#dirContentsButton') + const dirContentsOutput = page.locator('#dirContentsOutput') + await expect(dirContentsOutput).toContainText( + 'bafkreig7i5kbqdnoooievvfextf27eoherluxe2pi3j26hu6zpiauydydy', + {timeout: 2000} + ) + await page.click('#fileContentsButton') + const fileContentsOutput = page.locator('#fileContentsOutput') + await expect(fileContentsOutput).toContainText( + exampleText, + {timeout: 2000} + ) + + }) +}) + + diff --git a/examples/helia-vue/index.html b/examples/helia-vue/index.html new file mode 100644 index 00000000..99f583aa --- /dev/null +++ b/examples/helia-vue/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite App + + +
+ + + diff --git a/examples/helia-vue/package.json b/examples/helia-vue/package.json new file mode 100644 index 00000000..023de062 --- /dev/null +++ b/examples/helia-vue/package.json @@ -0,0 +1,40 @@ +{ + "name": "helia-vue", + "version": "0.0.0", + "description": "Using Helia with vue", + "private": true, + "scripts": { + "start": "vite", + "build": "vite build", + "preview": "vite preview", + "test:e2e": "playwright test", + "test": "vite build && playwright test", + "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs --fix --ignore-path .gitignore", + "format": "prettier --write src/" + }, + "dependencies": { + "@chainsafe/libp2p-noise": "^11.0.4", + "@chainsafe/libp2p-yamux": "^3.0.7", + "@helia/unixfs": "^1.2.1", + "@libp2p/bootstrap": "^6.0.3", + "@libp2p/websockets": "^5.0.8", + "blockstore-core": "^4.1.0", + "datastore-core": "^9.1.1", + "events": "^3.3.0", + "helia": "next", + "it-all": "^3.0.1", + "libp2p": "^0.43.3", + "vue": "^3.2.47" + }, + "devDependencies": { + "@playwright/test": "^1.31.1", + "@rushstack/eslint-patch": "^1.2.0", + "@vitejs/plugin-vue": "^4.0.0", + "@vue/eslint-config-prettier": "^7.1.0", + "eslint": "^8.34.0", + "eslint-plugin-vue": "^9.9.0", + "prettier": "^2.8.4", + "test-util-ipfs-example": "^1.0.2", + "vite": "^4.1.4" + } +} diff --git a/examples/helia-vue/playwright.config.js b/examples/helia-vue/playwright.config.js new file mode 100644 index 00000000..ed1ac90a --- /dev/null +++ b/examples/helia-vue/playwright.config.js @@ -0,0 +1,106 @@ +// @ts-check +const { devices } = require('@playwright/test') + +/** + * Read environment variables from file. + * https://github.com/motdotla/dotenv + */ +// require('dotenv').config(); + +/** + * @see https://playwright.dev/docs/test-configuration + * @type {import('@playwright/test').PlaywrightTestConfig} + */ +const config = { + testDir: './e2e', + /* Maximum time one test can run for. */ + timeout: 30 * 1000, + expect: { + /** + * Maximum time expect() should wait for the condition to be met. + * For example in `await expect(locator).toHaveText();` + */ + timeout: 5000 + }, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: Boolean(process.env.CI), + /* Retry on CI only */ + retries: process.env.CI ? 2 : 0, + /* Opt out of parallel tests on CI. */ + workers: process.env.CI ? 1 : undefined, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: 'html', + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + /* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */ + actionTimeout: 0, + /* Base URL to use in actions like `await page.goto('/')`. */ + baseURL: 'http://localhost:5173', + + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: 'on-first-retry', + + /* Only on CI systems run the tests headless */ + headless: Boolean(process.env.CI) + }, + + /* Configure projects for major browsers */ + projects: [ + { + name: 'chromium', + use: { + ...devices['Desktop Chrome'] + } + }, + { + name: 'firefox', + use: { + ...devices['Desktop Firefox'] + } + }, + + /* Test against mobile viewports. */ + // { + // name: 'Mobile Chrome', + // use: { + // ...devices['Pixel 5'], + // }, + // }, + // { + // name: 'Mobile Safari', + // use: { + // ...devices['iPhone 12'], + // }, + // }, + + /* Test against branded browsers. */ + // { + // name: 'Microsoft Edge', + // use: { + // channel: 'msedge', + // }, + // }, + // { + // name: 'Google Chrome', + // use: { + // channel: 'chrome', + // }, + // }, + ], + + /* Folder for test artifacts such as screenshots, videos, traces, etc. */ + // outputDir: 'test-results/', + + /* Run your local dev server before starting the tests */ + webServer: { + /** + * Use the dev server by default for faster feedback loop. + * Use the preview server on CI for more realistic testing. + */ + command: process.env.CI ? 'vite preview --port 5173' : 'vite dev', + port: 5173, + reuseExistingServer: !process.env.CI + } +} + +module.exports = config diff --git a/examples/helia-vue/public/demo-vue.gif b/examples/helia-vue/public/demo-vue.gif new file mode 100644 index 00000000..2e3f2dac Binary files /dev/null and b/examples/helia-vue/public/demo-vue.gif differ diff --git a/examples/helia-vue/public/favicon.ico b/examples/helia-vue/public/favicon.ico new file mode 100644 index 00000000..df36fcfb Binary files /dev/null and b/examples/helia-vue/public/favicon.ico differ diff --git a/examples/helia-vue/src/App.vue b/examples/helia-vue/src/App.vue new file mode 100644 index 00000000..e8c1e888 --- /dev/null +++ b/examples/helia-vue/src/App.vue @@ -0,0 +1,61 @@ + + + + + diff --git a/examples/helia-vue/src/HeliaApi/useCommitText.js b/examples/helia-vue/src/HeliaApi/useCommitText.js new file mode 100644 index 00000000..7c55a507 --- /dev/null +++ b/examples/helia-vue/src/HeliaApi/useCommitText.js @@ -0,0 +1,51 @@ +/* eslint-disable no-console */ + +import { inject, ref } from 'vue' + +const encoder = new TextEncoder() +const decoder = new TextDecoder() + +export const useCommitText = () => { + const cid = ref() + const commitedText = ref() + const { loading, error, helia, fs } = inject("HeliaProvider") + + const commitText = async (text) => { + console.log('text', text) + if (error.value.length === 0 && !loading.value) { + try { + const res = await fs.value.addBytes( + encoder.encode(text), + helia.value.blockstore + ) + cid.value = res + } catch (e) { + console.error(e) + } + } else { + console.log('please wait for helia to start') + } + } + + const fetchCommitedText = async () => { + let text = '' + console.log(cid) + if (error.value.length === 0 && !loading.value && cid.value) { + try { + for await (const chunk of fs.value.cat(cid.value)) { + text += decoder.decode(chunk, { + stream: true + }) + } + commitedText.value = text + } catch (e) { + console.error(e) + } + } else { + console.log('please wait for helia to start') + } + + } + + return {cid, commitText, commitedText, fetchCommitedText} +} \ No newline at end of file diff --git a/examples/helia-vue/src/HeliaApi/useUnixFS.js b/examples/helia-vue/src/HeliaApi/useUnixFS.js new file mode 100644 index 00000000..8bf027a5 --- /dev/null +++ b/examples/helia-vue/src/HeliaApi/useUnixFS.js @@ -0,0 +1,104 @@ +/* eslint-disable no-console */ + +import { inject } from 'vue' + +const encoder = new TextEncoder() +const decoder = new TextDecoder() + +export const useUnixFS = () => { + const { loading, error, fs } = inject("HeliaProvider") + + const getStat = async (dirCid, path) => { + if (error.value.length === 0 && !loading.value) { + try { + const res = await fs.value.stat(dirCid, { + path + }) + const data = { cid: res.cid, blocks: res.blocks } + return { status: 'success', data } + } catch (e) { + console.error(e) + } + } else { + console.log('please wait for helia to start') + } + } + + const addDirectory = async (path) => { + if (error.value.length === 0 && !loading.value) { + try { + const emptyDirCid = await fs.value.addDirectory(path) + const res = await fs.value.mkdir(emptyDirCid, path) + return { status: 'success', data: res } + } catch (e) { + console.error(e) + } + } else { + console.log('please wait for helia to start') + } + } + + const getDirectory = async (dirCid, path) => { + const output = [] + if (error.value.length === 0 && !loading.value) { + try { + if (typeof path !== 'undefined') { + for await (const entry of fs.value.ls(dirCid, { + path + })) { + output.push(entry) + } + } else { + for await (const entry of fs.value.ls(dirCid)) { + output.push(entry) + } + } + return { status: 'success', data: output } + } catch (e) { + console.error(e) + } + } else { + console.log('please wait for helia to start') + } + } + + const addFile = async (name, dirCid, content) => { + if (error.value.length === 0 && !loading.value) { + try { + + const res = await fs.value.addFile({ + content: encoder.encode(content) + }) + const updatedCid = await fs.value.cp(res, dirCid, name) + return { status: 'success', data: { dirCid: updatedCid, fileCid: res } } + } catch (e) { + console.error(e) + } + } else { + console.log('please wait for helia to start') + } + } + + const getFile = async (cid) => { + if (error.value.length === 0 && !loading.value) { + let txt = '' + try { + for await (const buf of fs.value.cat(cid)) { + txt += decoder.decode(buf, { + stream: true + }) + } + return { status: 'success', data: txt } + } catch (e) { + console.error(e) + } + } else { + console.log('please wait for helia to start') + } + } + return { + getStat, + addDirectory, getDirectory, + addFile, getFile + } +} \ No newline at end of file diff --git a/examples/helia-vue/src/assets/base.css b/examples/helia-vue/src/assets/base.css new file mode 100644 index 00000000..71dc55a3 --- /dev/null +++ b/examples/helia-vue/src/assets/base.css @@ -0,0 +1,74 @@ +/* color palette from */ +:root { + --vt-c-white: #ffffff; + --vt-c-white-soft: #f8f8f8; + --vt-c-white-mute: #f2f2f2; + + --vt-c-black: #181818; + --vt-c-black-soft: #222222; + --vt-c-black-mute: #282828; + + --vt-c-indigo: #2c3e50; + + --vt-c-divider-light-1: rgba(60, 60, 60, 0.29); + --vt-c-divider-light-2: rgba(60, 60, 60, 0.12); + --vt-c-divider-dark-1: rgba(84, 84, 84, 0.65); + --vt-c-divider-dark-2: rgba(84, 84, 84, 0.48); + + --vt-c-text-light-1: var(--vt-c-indigo); + --vt-c-text-light-2: rgba(60, 60, 60, 0.66); + --vt-c-text-dark-1: var(--vt-c-white); + --vt-c-text-dark-2: rgba(235, 235, 235, 0.64); +} + +/* semantic color variables for this project */ +:root { + --color-background: var(--vt-c-white); + --color-background-soft: var(--vt-c-white-soft); + --color-background-mute: var(--vt-c-white-mute); + + --color-border: var(--vt-c-divider-light-2); + --color-border-hover: var(--vt-c-divider-light-1); + + --color-heading: var(--vt-c-text-light-1); + --color-text: var(--vt-c-text-light-1); + + --section-gap: 160px; +} + +@media (prefers-color-scheme: dark) { + :root { + --color-background: var(--vt-c-black); + --color-background-soft: var(--vt-c-black-soft); + --color-background-mute: var(--vt-c-black-mute); + + --color-border: var(--vt-c-divider-dark-2); + --color-border-hover: var(--vt-c-divider-dark-1); + + --color-heading: var(--vt-c-text-dark-1); + --color-text: var(--vt-c-text-dark-2); + } +} + +*, +*::before, +*::after { + box-sizing: border-box; + margin: 0; + position: relative; + font-weight: normal; +} + +body { + min-height: 100vh; + color: var(--color-text); + background: var(--color-background); + transition: color 0.5s, background-color 0.5s; + line-height: 1.6; + font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, + Cantarell, 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; + font-size: 15px; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} diff --git a/examples/helia-vue/src/assets/logo.svg b/examples/helia-vue/src/assets/logo.svg new file mode 100644 index 00000000..75656603 --- /dev/null +++ b/examples/helia-vue/src/assets/logo.svg @@ -0,0 +1 @@ + diff --git a/examples/helia-vue/src/assets/main.css b/examples/helia-vue/src/assets/main.css new file mode 100644 index 00000000..100a0841 --- /dev/null +++ b/examples/helia-vue/src/assets/main.css @@ -0,0 +1,23 @@ +@import './base.css'; + +#app { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + + font-weight: normal; +} + +a, +.green { + text-decoration: none; + color: hsla(160, 100%, 37%, 1); + transition: 0.4s; +} + +@media (hover: hover) { + a:hover { + background-color: hsla(160, 100%, 37%, 0.2); + } +} + diff --git a/examples/helia-vue/src/components/TextCommiter.vue b/examples/helia-vue/src/components/TextCommiter.vue new file mode 100644 index 00000000..dfc03023 --- /dev/null +++ b/examples/helia-vue/src/components/TextCommiter.vue @@ -0,0 +1,29 @@ + + + + + \ No newline at end of file diff --git a/examples/helia-vue/src/components/UnixFSManager.vue b/examples/helia-vue/src/components/UnixFSManager.vue new file mode 100644 index 00000000..94597d93 --- /dev/null +++ b/examples/helia-vue/src/components/UnixFSManager.vue @@ -0,0 +1,104 @@ + + + \ No newline at end of file diff --git a/examples/helia-vue/src/main.js b/examples/helia-vue/src/main.js new file mode 100644 index 00000000..7c1a1829 --- /dev/null +++ b/examples/helia-vue/src/main.js @@ -0,0 +1,9 @@ +import { createApp } from 'vue' +import App from '@/App.vue' +import { HeliaProviderPlugin } from './plugins/HeliaProviderPlugin' + +import './assets/main.css' + +const app = createApp(App) +app.use(HeliaProviderPlugin) +app.mount("#app") diff --git a/examples/helia-vue/src/plugins/HeliaProviderPlugin.js b/examples/helia-vue/src/plugins/HeliaProviderPlugin.js new file mode 100644 index 00000000..6029bf1c --- /dev/null +++ b/examples/helia-vue/src/plugins/HeliaProviderPlugin.js @@ -0,0 +1,67 @@ +/* eslint-disable no-console */ + +import { createHelia } from 'helia' +import { createLibp2p } from 'libp2p' +import { noise } from '@chainsafe/libp2p-noise' +import { yamux } from '@chainsafe/libp2p-yamux' +import { webSockets } from '@libp2p/websockets' +import { bootstrap } from '@libp2p/bootstrap' +import { unixfs } from '@helia/unixfs' +import { MemoryBlockstore } from 'blockstore-core' +import { MemoryDatastore } from 'datastore-core' + +import { ref } from 'vue' + +export const HeliaProviderPlugin = { + install: async (app) => { + const loading = ref(true) + const error = ref("") + const helia = ref() + const fs = ref() + app.provide('HeliaProvider', { + loading, + error, + helia, + fs + }) + try { + const blockstore = new MemoryBlockstore() + const datastore = new MemoryDatastore() + const libp2p = await createLibp2p({ + datastore, + transports: [ + webSockets() + ], + connectionEncryption: [ + noise() + ], + streamMuxers: [ + yamux() + ], + peerDiscovery: [ + bootstrap({ + list: [ + "/dnsaddr/bootstrap.libp2p.io/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN", + "/dnsaddr/bootstrap.libp2p.io/p2p/QmQCU2EcMqAqQPR2i9bChDtGNJchTbq5TbXJJ16u19uLTa", + "/dnsaddr/bootstrap.libp2p.io/p2p/QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj75Nb", + "/dnsaddr/bootstrap.libp2p.io/p2p/QmcZf59bWwK5XFi76CZX8cbJ4BhTzzA3gU1ZjYZcYW3dwt" + ] + }) + ] + }) + const instance = await createHelia({ + datastore, + blockstore, + libp2p + }) + loading.value = false + helia.value = instance + fs.value = unixfs(instance) + + } catch (e) { + console.error(e) + error.value = e.toString() + loading.value = false + } + } +} diff --git a/examples/helia-vue/vite.config.js b/examples/helia-vue/vite.config.js new file mode 100644 index 00000000..de5cb31c --- /dev/null +++ b/examples/helia-vue/vite.config.js @@ -0,0 +1,14 @@ +import { fileURLToPath, URL } from 'node:url' + +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [vue()], + resolve: { + alias: { + '@': fileURLToPath(new URL('./src', import.meta.url)) + } + } +})