├── .babelrc.json ├── .env.development ├── .env.production ├── .eslintignore ├── .eslintrc.js ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── dependabot.yml ├── pull_request_template.md └── workflows │ ├── audit.yml │ ├── ci.yml │ ├── codeql-analysis.yml │ └── release.yml ├── .gitignore ├── .nvmrc ├── .storybook ├── main.js └── preview.js ├── CHANGELOG.md ├── CODE-OF-CONDUCT.md ├── CODEOWNERS ├── LICENSE ├── README.md ├── SECURITY.md ├── certificates ├── localhost.crt └── localhost.key ├── jest.config.ts ├── jest.setup.ts ├── package-lock.json ├── package.json ├── public ├── example.jpg └── example.ttl ├── resources ├── license-header.js └── whitesource │ ├── wss-pull_request-scan.config │ └── wss-push-scan.config ├── rollup.config.mjs ├── sonar-project.properties ├── src ├── components │ ├── file │ │ ├── __snapshots__ │ │ │ └── index.test.tsx.snap │ │ ├── index.test.tsx │ │ └── index.tsx │ ├── image │ │ ├── __snapshots__ │ │ │ └── index.test.tsx.snap │ │ ├── index.test.tsx │ │ └── index.tsx │ ├── link │ │ ├── __snapshots__ │ │ │ └── index.test.tsx.snap │ │ ├── index.test.tsx │ │ └── index.tsx │ ├── logIn │ │ ├── __snapshots__ │ │ │ └── index.test.tsx.snap │ │ ├── index.test.tsx │ │ └── index.tsx │ ├── logOut │ │ ├── __snapshots__ │ │ │ └── index.test.tsx.snap │ │ ├── index.test.tsx │ │ └── index.tsx │ ├── table │ │ ├── __snapshots__ │ │ │ └── index.test.tsx.snap │ │ ├── index.test.tsx │ │ └── index.tsx │ ├── text │ │ ├── __snapshots__ │ │ │ └── index.test.tsx.snap │ │ ├── index.test.tsx │ │ └── index.tsx │ ├── value │ │ ├── __snapshots__ │ │ │ └── index.test.tsx.snap │ │ ├── boolean │ │ │ ├── index.test.tsx │ │ │ └── index.tsx │ │ ├── datetime │ │ │ ├── index.test.tsx │ │ │ └── index.tsx │ │ ├── decimal │ │ │ ├── index.test.tsx │ │ │ └── index.tsx │ │ ├── index.test.tsx │ │ ├── index.tsx │ │ ├── integer │ │ │ ├── index.test.tsx │ │ │ └── index.tsx │ │ ├── string │ │ │ ├── index.test.tsx │ │ │ └── index.tsx │ │ └── url │ │ │ ├── index.test.tsx │ │ │ └── index.tsx │ └── video │ │ ├── __snapshots__ │ │ └── index.test.tsx.snap │ │ ├── index.test.tsx │ │ └── index.tsx ├── context │ ├── combinedDataContext │ │ ├── __snapshots__ │ │ │ └── index.test.tsx.snap │ │ ├── index.test.tsx │ │ └── index.tsx │ ├── datasetContext │ │ ├── __snapshots__ │ │ │ └── index.test.tsx.snap │ │ ├── index.test.tsx │ │ └── index.tsx │ ├── sessionContext │ │ ├── __snapshots__ │ │ │ └── index.test.tsx.snap │ │ ├── index.test.tsx │ │ └── index.tsx │ └── thingContext │ │ ├── __snapshots__ │ │ └── index.test.tsx.snap │ │ ├── index.test.tsx │ │ └── index.tsx ├── helpers │ ├── index.test.tsx │ └── index.ts ├── hooks │ ├── useDataset │ │ ├── index.test.tsx │ │ └── index.tsx │ ├── useFile │ │ ├── index.test.tsx │ │ └── index.tsx │ ├── useSession │ │ ├── index.test.tsx │ │ └── index.tsx │ └── useThing │ │ ├── index.test.tsx │ │ └── index.tsx ├── index.ts └── types │ └── react-table-config.d.ts ├── stories ├── authentication │ ├── loginButton.stories.tsx │ ├── logoutButton.stories.tsx │ └── sessionProvider.stories.tsx ├── components │ ├── file.stories.tsx │ ├── image.stories.tsx │ ├── link.stories.tsx │ ├── table.stories.tsx │ ├── tableColumn.stories.tsx │ ├── text.stories.tsx │ ├── value.stories.tsx │ └── video.stories.tsx ├── config.tsx ├── gettingStarted.stories.mdx ├── hooks │ ├── useDataset.stories.mdx │ ├── useFile.stories.mdx │ ├── useSession.stories.mdx │ └── useThing.stories.mdx ├── intro.stories.mdx ├── providers │ ├── combinedProvider.stories.tsx │ ├── datasetProvider.stories.tsx │ └── thingProvider.stories.tsx └── usage.stories.mdx └── tsconfig.json /.babelrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "sourceType": "unambiguous", 3 | "presets": [ 4 | [ 5 | "@babel/preset-env", 6 | { 7 | "targets": { 8 | "chrome": 100, 9 | "safari": 15, 10 | "firefox": 91 11 | } 12 | } 13 | ], 14 | "@babel/preset-typescript", 15 | "@babel/preset-react" 16 | ], 17 | "plugins": [] 18 | } 19 | -------------------------------------------------------------------------------- /.env.development: -------------------------------------------------------------------------------- 1 | NEXT_PUBLIC_HOST_URL=${VERCEL_URL} 2 | -------------------------------------------------------------------------------- /.env.production: -------------------------------------------------------------------------------- 1 | NEXT_PUBLIC_HOST_URL=${VERCEL_URL} 2 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | __mocks__ 2 | __snapshots__ 3 | **/styles.ts 4 | .eslintrc.js -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: [ 4 | "@inrupt/eslint-config-react", 5 | "@inrupt/eslint-config-lib", 6 | "plugin:storybook/recommended", 7 | ], 8 | rules: { 9 | "prettier/prettier": [ 10 | "error", 11 | { 12 | endOfLine: "auto", 13 | }, 14 | ], 15 | "react/jsx-filename-extension": [ 16 | 1, 17 | { 18 | extensions: [".tsx"], 19 | }, 20 | ], 21 | "import/no-unresolved": "off", 22 | "no-shadow": ["warn", { allow: ["fetch"] }], 23 | "react/require-default-props": "warn", 24 | "react/default-props-match-prop-types": "warn", 25 | "react/function-component-definition": "off", 26 | noImplicitAny: "off", 27 | // FIXME: re-enable the following 28 | "react/no-unstable-nested-components": "off", 29 | "react/jsx-no-bind": "off", 30 | "react/jsx-no-constructed-context-values": "off" 31 | }, 32 | }; 33 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a bug report to help us improve 4 | title: "" 5 | labels: "bug" 6 | assignees: "" 7 | --- 8 | 9 | 14 | 15 | ### Search terms you've used 16 | 17 | 18 | ### Bug description 19 | 20 | 21 | ### To Reproduce 22 | 1. 23 | 2. 24 | 3. 25 | 4. 26 | 27 | ### Expected result 28 | 29 | 30 | ### Actual result 31 | 32 | 33 | ### Environment 34 | Please run 35 | 36 | npx envinfo --system --npmPackages --binaries --npmGlobalPackages --browsers 37 | 38 | in your project folder and paste the output here: 39 | 40 | ``` 41 | $ npx envinfo --system --npmPackages --binaries --npmGlobalPackages --browsers 42 | ``` 43 | 44 | ## Additional information 45 | 46 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for a new feature 4 | title: "" 5 | labels: "enhancement" 6 | assignees: "" 7 | --- 8 | 9 | 14 | 15 | ### Search terms you've used 16 | 17 | 18 | ### Feature suggestion 19 | 20 | 21 | 22 | ### Expected functionality/enhancement 23 | 24 | 25 | 26 | ### Actual functionality/enhancement 27 | 28 | 29 | 30 | ### Use Cases 31 | 32 | 36 | 37 | ### Additional information 38 | 39 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | # Enable version updates for library itself 4 | - package-ecosystem: "npm" 5 | # Look for `package.json` and `lock` files in the `root` directory 6 | directory: "/" 7 | # Check the npm registry for updates every day (weekdays) 8 | schedule: 9 | interval: "weekly" 10 | ignore: 11 | # Prettier introduces style changes in patch releases, 12 | # (see https://prettier.io/docs/en/install.html) 13 | # so to avoid regular and unpredictable breakage: 14 | - dependency-name: "prettier" 15 | - package-ecosystem: "github-actions" 16 | # Look for `package.json` and `lock` files in the `root` directory 17 | directory: "/" 18 | # Check the npm registry for updates every day (weekdays) 19 | schedule: 20 | interval: "weekly" 21 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | This PR fixes #. 4 | 5 | - [ ] I've added a unit test to test for potential regressions of this bug. 6 | - [ ] The changelog has been updated, if applicable. 7 | - [ ] Commits in this PR are minimal and [have descriptive commit messages](https://chris.beams.io/posts/git-commit/). 8 | 9 | 10 | 11 | # New feature description 12 | 13 | # Checklist 14 | 15 | - [ ] All acceptance criteria are met. 16 | - [ ] Relevant documentation, if any, has been written/updated. 17 | - [ ] The changelog has been updated, if applicable. 18 | - [ ] New functions/types have been exported in `index.ts`, if applicable. 19 | - [ ] New modules (i.e. new `.ts` files) are listed in the `exports` field in `package.json`, if applicable. 20 | - [ ] New modules (i.e. new `.ts` files) are listed in the `typedocOptions.entryPoints` field in `tsconfig.json`, if applicable. 21 | - [ ] Commits in this PR are minimal and [have descriptive commit messages](https://chris.beams.io/posts/git-commit/). 22 | 23 | 24 | 25 | This PR bumps the version to . 26 | 27 | # Checklist 28 | 29 | - [ ] I used `npm version ` to update `package.json`, inspecting the changelog to determine if the release was major, minor or patch. 30 | - [ ] The CHANGELOG has been updated to show version and release date - https://keepachangelog.com/en/1.0.0/. 31 | - [ ] `@since X.Y.Z` annotations have been added to new APIs. 32 | - [ ] The **only** commits in this PR are: 33 | - the CHANGELOG update. 34 | - the version update. 35 | - `@since` annotations. 36 | - [ ] I will make sure **not** to squash these commits, but **rebase** instead. 37 | - [ ] Once this PR is merged, I will push the tag created by `npm version ...` (e.g. `git push origin vX.Y.Z`). 38 | -------------------------------------------------------------------------------- /.github/workflows/audit.yml: -------------------------------------------------------------------------------- 1 | name: Audit 2 | 3 | on: 4 | push: 5 | schedule: 6 | - cron: "40 10 * * *" 7 | 8 | concurrency: 9 | group: ${{ github.workflow }}-${{ github.ref }} 10 | cancel-in-progress: true 11 | jobs: 12 | audit: 13 | uses: inrupt/typescript-sdk-tools/.github/workflows/reusable-audit.yml@v3.2.4 14 | secrets: 15 | WEBHOOK_E2E_FAILURE: ${{ secrets.WEBHOOK_E2E_FAILURE }} 16 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push] 4 | 5 | env: 6 | CI: true 7 | concurrency: 8 | group: ${{ github.workflow }}-${{ github.ref }} 9 | cancel-in-progress: true 10 | jobs: 11 | lint: 12 | uses: inrupt/typescript-sdk-tools/.github/workflows/reusable-lint.yml@v3.2.4 13 | 14 | build: 15 | runs-on: ubuntu-latest 16 | strategy: 17 | matrix: 18 | node-version: ["20.x", "18.x"] 19 | steps: 20 | - uses: actions/checkout@v4 21 | - uses: actions/setup-node@v4 22 | with: 23 | node-version: ${{ matrix.node-version }} 24 | cache: npm 25 | - run: npm ci 26 | - run: npm run build 27 | - run: npm run test 28 | - name: Archive code coverage results 29 | if: ${{ github.actor != 'dependabot[bot]' }} 30 | uses: actions/upload-artifact@v4 31 | with: 32 | name: code-coverage-report 33 | path: coverage 34 | 35 | sonar-scan: 36 | if: ${{ github.actor != 'dependabot[bot]' }} 37 | needs: [build] 38 | runs-on: ubuntu-latest 39 | steps: 40 | - uses: actions/checkout@v4 41 | with: 42 | # Sonar analysis needs the full history for features like automatic assignment of bugs. If the following step 43 | # is not included the project will show a warning about incomplete information. 44 | fetch-depth: 0 45 | - uses: actions/download-artifact@v4 46 | with: 47 | name: code-coverage-report 48 | path: coverage/ 49 | - uses: SonarSource/sonarcloud-github-action@v3.0 50 | env: 51 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 52 | SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} 53 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | name: "Static Application security Testing (CodeQL)" 8 | 9 | on: 10 | push: 11 | branches: ["*"] 12 | pull_request: 13 | # The branches below must be a subset of the branches above 14 | branches: [main] 15 | schedule: 16 | - cron: "0 12 * * 6" 17 | 18 | jobs: 19 | analyze: 20 | name: Analyze 21 | runs-on: ubuntu-latest 22 | permissions: 23 | actions: read 24 | contents: read 25 | security-events: write 26 | 27 | strategy: 28 | fail-fast: false 29 | matrix: 30 | language: ["javascript"] 31 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] 32 | # Learn more: 33 | # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed 34 | 35 | steps: 36 | - name: Checkout repository 37 | uses: actions/checkout@v4 38 | 39 | # Initializes the CodeQL tools for scanning. 40 | - name: Initialize CodeQL 41 | uses: github/codeql-action/init@v3 42 | with: 43 | languages: ${{ matrix.language }} 44 | # If you wish to specify custom queries, you can do so here or in a config file. 45 | # By default, queries listed here will override any specified in a config file. 46 | # Prefix the list here with "+" to use these queries and those in the config file. 47 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 48 | 49 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 50 | # If this step fails, then you should remove it and run the build manually (see below) 51 | - name: Autobuild 52 | uses: github/codeql-action/autobuild@v3 53 | 54 | # ℹ️ Command-line programs to run using the OS shell. 55 | # 📚 https://git.io/JvXDl 56 | 57 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 58 | # and modify them (or add more) to build your code if your project 59 | # uses a compiled language 60 | 61 | #- run: | 62 | # make bootstrap 63 | # make release 64 | 65 | - name: Perform CodeQL Analysis 66 | uses: github/codeql-action/analyze@v3 67 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - v[0-9]+.[0-9]+.[0-9]+ 7 | 8 | env: 9 | CI: true 10 | jobs: 11 | publish-npm: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v4 15 | - name: Prepare for publication to npm 16 | uses: actions/setup-node@v4 17 | with: 18 | node-version-file: ".nvmrc" 19 | registry-url: "https://registry.npmjs.org" 20 | cache: npm 21 | - run: npm ci 22 | - run: npm publish --access public 23 | env: 24 | NODE_AUTH_TOKEN: ${{ secrets.INRUPT_NPM_TOKEN }} 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # Compiled binary addons (https://nodejs.org/api/addons.html) 26 | build/Release 27 | 28 | # Dependency directories 29 | node_modules/ 30 | jspm_packages/ 31 | 32 | # TypeScript v1 declaration files 33 | typings/ 34 | 35 | # TypeScript cache 36 | *.tsbuildinfo 37 | 38 | # Optional npm cache directory 39 | .npm 40 | 41 | # Optional eslint cache 42 | .eslintcache 43 | 44 | # Optional REPL history 45 | .node_repl_history 46 | 47 | # Output of 'npm pack' 48 | *.tgz 49 | 50 | # Yarn Integrity file 51 | .yarn-integrity 52 | 53 | # dotenv environment variables file 54 | .env 55 | .env.test 56 | 57 | # component build 58 | dist/ 59 | 60 | # storybook build 61 | docs/ 62 | 63 | # TernJS port file 64 | .tern-port 65 | 66 | # VSCode settings 67 | .vscode 68 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 20 2 | -------------------------------------------------------------------------------- /.storybook/main.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | framework: { 3 | name: "@storybook/react-webpack5", 4 | options: {} 5 | }, 6 | 7 | staticDirs: ["../public"], 8 | stories: ["../stories/**/*.stories.@(tsx|mdx)"], 9 | 10 | addons: [{ 11 | name: '@storybook/addon-essentials', 12 | // https://github.com/storybookjs/storybook/issues/17996#issuecomment-1134810729 13 | options: { 14 | 'actions': false, 15 | } 16 | }, { 17 | name: "@storybook/addon-storysource", 18 | options: { 19 | sourceLoaderOptions: { 20 | injectStoryParameters: false, 21 | }, 22 | }, 23 | }, "@storybook/addon-mdx-gfm"], 24 | 25 | typescript: { 26 | check: false, 27 | checkOptions: {}, 28 | reactDocgen: "react-docgen-typescript", 29 | reactDocgenTypescriptOptions: { 30 | shouldExtractLiteralValuesFromEnum: true, 31 | propFilter: (prop) => 32 | prop.parent ? !/node_modules/.test(prop.parent.fileName) : true, 33 | }, 34 | }, 35 | 36 | webpackFinal: async (config) => { 37 | return config; 38 | }, 39 | 40 | docs: { 41 | autodocs: true 42 | } 43 | }; 44 | -------------------------------------------------------------------------------- /.storybook/preview.js: -------------------------------------------------------------------------------- 1 | export const parameters = { 2 | options: { 3 | storySort: { 4 | order: [ 5 | "Intro", 6 | "Getting Started", 7 | "Usage", 8 | "Authentication", 9 | "Components", 10 | "Providers", 11 | "Hooks", 12 | ["useSession", "useDataset", "useThing", "useFile"], 13 | ], 14 | }, 15 | }, 16 | }; 17 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @inrupt/engineering 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Inrupt 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Deprecation warning 2 | 3 | **This project is deprecated. It will no longer receive any updates, including security patches, so we recommend to stop using it as a dependency (or to use it at your own risk). The repository will be archived.** 4 | 5 | # Solid React SDK v2 - solid-ui-react 6 | 7 | [![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-2.1-4baaaa.svg)](CODE-OF-CONDUCT.md) 8 | 9 | This project adheres to the Contributor Covenant [code of conduct](CODE-OF-CONDUCT.md). By participating, you are expected to uphold this code. Please report unacceptable behavior to [engineering@inrupt.com](mailto:engineering@inrupt.com). 10 | 11 | solid-ui-react is a library of React components made to make development with [Solid](https://solidproject.org/) simple. 12 | 13 | The goal of this library is to provide highly flexible, simple components that you can use in any browser-based Solid project and customize to match the UI of your application. It uses the [Inrupt Javascript Client Libraries](https://github.com/inrupt/solid-client-js) for authentication and data manipulation. It is built with typescript and exports its types. 14 | 15 | In this library, you will find: 16 | 17 | - **Authentication components**, which can help you log in and log out of an identity provider 18 | - **Data components**, which can help you view and edit individual properties or view a sortable and filterable table of many things 19 | - **Context providers and hooks** to help simplify development when using functional programming 20 | 21 | This project is currently in Beta. Interfaces subject to change. Not for production use. 22 | 23 | **Note:** this project supersedes [Solid React SDK v1 by Inrupt](https://github.com/inrupt/solid-react-sdk) 24 | 25 | ## Installation 26 | 27 | To install into your project, run `npm install -S @inrupt/solid-ui-react`. 28 | 29 | Import components from this package, such as: 30 | 31 | `import { SessionProvider, LoginButton } from "@inrupt/solid-ui-react";` 32 | 33 | ## Development 34 | 35 | All development should follow the [Inrupt Coding Guidelines](https://github.com/inrupt/public-documentation/tree/master/coding-conventions). The existing linting and testing tools should automate most of this. 36 | 37 | 1. Clone the repository 38 | 2. From the directory you cloned to, run `npm install` to install dependencies 39 | 3. Run `npm run storybook` to run a live-reloading Storybook server 40 | 4. Run `npm run lint` and `npm run test` after making changes 41 | 42 | # Solid JavaScript Client - solid-client 43 | 44 | [@inrupt/solid-client](https://github.com/inrupt/solid-client-js) is a JavaScript library for accessing data and managing permissions on data stored in Solid Pods. It provides an abstraction layer on top of both Solid and Resource Description Framework (RDF) principles and is compatible with the RDF/JS specification. You can use solid-client in Node.js using CommonJS modules and in the browser with a bundler like Webpack, Rollup, or Parcel. 45 | 46 | [@inrupt/solid-client](https://github.com/inrupt/solid-client-js) is part of a family open source JavaScript libraries designed to support developers building Solid applications. 47 | 48 | # Issues & Help 49 | 50 | ## Solid Community Forum 51 | 52 | If you have questions about working with Solid or just want to share what you’re working on, visit the [Solid forum](https://forum.solidproject.org/). The Solid forum is a good place to meet the rest of the community. 53 | 54 | ## Bugs and Feature Requests 55 | 56 | - For public feedback, bug reports, and feature requests please file an issue via [GitHub](https://github.com/inrupt/solid-ui-react/issues/). 57 | - For non-public feedback or support inquiries please use the [Inrupt Service Desk](https://inrupt.atlassian.net/servicedesk). 58 | 59 | ## Documentation 60 | 61 | - [Inrupt Solid Javascript React SDK](https://docs.inrupt.com/developer-tools/javascript/react-sdk/) 62 | - [View and play with the live examples on Storybook](https://solid-ui-react.docs.inrupt.com). 63 | - [Homepage](https://docs.inrupt.com/) 64 | 65 | # Changelog 66 | 67 | See [the release notes](https://github.com/inrupt/solid-ui-react/blob/master/CHANGELOG.md). 68 | 69 | # License 70 | 71 | MIT © [Inrupt](https://inrupt.com) 72 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security policy 2 | 3 | This library intends supporting the development of Solid applications reading and 4 | writing data in Solid servers. Data should always be considered sensitive and 5 | be processed with care and regards to access restrictions and personal information. 6 | 7 | This library builds on top of the other Inrupt SDKs, such as 8 | [`@inrupt/solid-client`](https://github.com/inrupt/solid-client-js) which handles the underlying data access logic. For a better separation of concerns, this library does not deal directly with 9 | authentication or the actual fetching of data. 10 | 11 | The security policy for these libraries is available in the associated [GitHub repository](https://github.com/inrupt/solid-ui-react/blob/main/SECURITY.md). 12 | 13 | # Reporting a vulnerability 14 | 15 | If you discover a vulnerability in our code, or experience a bug related to security, 16 | please report it following the instructions provided on [Inrupt’s security page](https://inrupt.com/security/). 17 | -------------------------------------------------------------------------------- /certificates/localhost.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIC5TCCAc2gAwIBAgIJANcHjAkd7z+fMA0GCSqGSIb3DQEBCwUAMBQxEjAQBgNV 3 | BAMMCWxvY2FsaG9zdDAeFw0yMDA1MTkwMDMzMzdaFw0yMDA2MTgwMDMzMzdaMBQx 4 | EjAQBgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC 5 | ggEBAPLyPClF89OdEo+55x5+udM3oOt/V5PxcNELn71Yu4gv8KTrRfyvlZKZN2Hz 6 | Oh5pfTmabflMYTjSnEL+WA27/uXwoGC1ilkVoXaGDHaTJ0kjjA2luT8c3HZMUAHa 7 | Ys8zibuql8kL7d8PqCc9Rv3OzAztnuo/MAMieowDRDCswGQmhH9FkPZPtsVNxgLq 8 | dnxCYWgFrp/IAtlXsPoF0IQKdj/0n37Mj3SFA/+93nABCpOIFfU0pDm9yTLGpaxq 9 | jvy9/2zMm++Mqblac3sBDXPJkNIQPu61/7VwRJcCyiIfhoJzQCHbcMdAiE2nb4lM 10 | eZpB/dhp0N1KS+2SaYSzW3vNaWMCAwEAAaM6MDgwFAYDVR0RBA0wC4IJbG9jYWxo 11 | b3N0MAsGA1UdDwQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDATANBgkqhkiG9w0B 12 | AQsFAAOCAQEAGFfmoYFOXVY33Q2kRLugbhd2R+A4lf9Xfr8bwC0XBk904J/d6EQh 13 | sRD9PKVs1TbPK8nBc2lrbd1Y916KQ3o9FlTgfczL7awWK1Bv9HMUt4RAnmAVdESh 14 | zRb7QWbFrpWz6odWajaaqzSfW3pz+c51cNkd/LxTIpw22pyLh9XO57pmdBbx7dT7 15 | 4dm0WpQ15KlidIEINp/vlgBm4fsmbIR0TNl2o6R5179yeZkAgNvfn3M1Eg5sbgdi 16 | K/u8byks6HyxkC58wMQ43fKktfGNlWmrbs2ky83sEpas2vXqLG9oRiqKV40tiqVN 17 | O1YpGaue5dOGOzlUlgBie0avKn9xNZu2XA== 18 | -----END CERTIFICATE----- 19 | -------------------------------------------------------------------------------- /certificates/localhost.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDy8jwpRfPTnRKP 3 | uecefrnTN6Drf1eT8XDRC5+9WLuIL/Ck60X8r5WSmTdh8zoeaX05mm35TGE40pxC 4 | /lgNu/7l8KBgtYpZFaF2hgx2kydJI4wNpbk/HNx2TFAB2mLPM4m7qpfJC+3fD6gn 5 | PUb9zswM7Z7qPzADInqMA0QwrMBkJoR/RZD2T7bFTcYC6nZ8QmFoBa6fyALZV7D6 6 | BdCECnY/9J9+zI90hQP/vd5wAQqTiBX1NKQ5vckyxqWsao78vf9szJvvjKm5WnN7 7 | AQ1zyZDSED7utf+1cESXAsoiH4aCc0Ah23DHQIhNp2+JTHmaQf3YadDdSkvtkmmE 8 | s1t7zWljAgMBAAECggEAMtwzbBCQ8wQ3Fo+8qs2s6WMD9Uk7dGVfUOQ2FNW2rNQc 9 | dOosRLIdhgs9ZOwehq9e3+hMjZxH3jV5MS0+EeIBgvHVIWngBLZajw2RHmHAH7ep 10 | YlIHbgcEwF9JgJnPhcEsXHViQVyV99S3Rz+01P2uCibU1gVUb9OQBC/K3sXLoStm 11 | thwRSWO2k/vyzgUYQMzbW2t/+oZ8Zo7It13e1oAEOfx+/UsOR6nwterZIiZq2C+Y 12 | Oh3BjJ+0JsooMs4jfbl9hk1f70Lm6hkKU8W+awmQ+4N1yKscSkZt4oBu1o6HTTbB 13 | zi3vl3SRtpAd7esUrtISYF6scKtSmvsNC1zR4FG8gQKBgQD6frUkSUdN6BDrmhVT 14 | 5L8+2yRtR3wp1we+dV91MI8bSfWrt6KSKT43b6cO2XMYe4E0g+jfpoLB9QFW43Rc 15 | IwsrMNa05isdmeYqzo7+rp6vAqbT6HD/DtYA3/kRgOaAcG8FwboAgAlxLfV+ghyP 16 | M941z7Z64vP508Vk2VcMVHFxOwKBgQD4SQ7eUZXrVTReB6ivPpm/IW0PE8858O/6 17 | wziG1Ova2VKGoJ0o+tC391F9/igy51GoY5nErhN0+9MPQI8VhWqroudOi1fg9wCq 18 | Wz2fZjs6FXyAJNp6zakwStkIqG/Yn0NjVT0YZ/oqeHot96TlSg0uiaa1E2tOjMiK 19 | Jbpmyr5l+QKBgEjhrNqN540nRCUIg8QQ/gPPxt10uw+BXONxSwDRzBFoh24a09Yq 20 | 4ctHVRfg9Lfe10zsREkuLUU5f9ZD/4r/NzSt1Qp1HX5TVMCHsNsw+0J4RwnIy3eq 21 | XWGLyO/qzSX5kJPgNboHhrfDY3YXbS/NK44EjVdbyRibIQeipM0VlJwZAoGBAMNq 22 | zTcSQ8tL7sGriYsU3HHg3zOkxc+24UsfaFhHMbO4pQmtVMUyVCDXmd5usjkOc+jz 23 | K83vrQTAR+iTSYsIzWJ1/1SEvBAmmPF8is3w6uDWlH1PhF5vjKCj6Jq16AI5qBKA 24 | vmt9Oxs+Y/vWFIUbebCNSxbuR+E1UYnaFBJftOgRAoGBAK/BHUnC3BR/MGTuIi+K 25 | TLufiVR9jYh3jVNEtITaPfwJ5+orjEBuxBMN5JQ9z5xuS8YMTIN5mePpAOk/ywOl 26 | TczA/OLaFGJlfAAwoELmR7CX5DW9JlXT623jXgUvNuF7LzPpxhYQ+55/9IVPW8Ta 27 | 8IF9saCRm6eF2y8vtcDmhXkv 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /jest.config.ts: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright Inrupt Inc. 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal in 6 | // the Software without restriction, including without limitation the rights to use, 7 | // copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the 8 | // Software, and to permit persons to whom the Software is furnished to do so, 9 | // subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in 12 | // all copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 16 | // PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 17 | // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 18 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 19 | // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | // 21 | export default { 22 | // Automatically clear mock calls and instances between every test 23 | clearMocks: true, 24 | 25 | // The test environment that will be used for testing 26 | testEnvironment: "jsdom", 27 | 28 | testPathIgnorePatterns: ["/node_modules/", "/__testUtils/"], 29 | 30 | // This combination of preset/transformIgnorePatterns enforces that both TS and 31 | // JS files are transformed to CJS, and that the transform also applies to the 32 | // dependencies in the node_modules, so that ESM-only dependencies are supported. 33 | preset: "ts-jest/presets/js-with-ts", 34 | // deliberately set to an empty array to allow including node_modules when transforming code: 35 | transformIgnorePatterns: [], 36 | collectCoverage: true, 37 | coverageReporters: process.env.CI ? ["text", "lcov"] : ["text"], 38 | 39 | coveragePathIgnorePatterns: ["/node_modules/", "/dist"], 40 | 41 | coverageThreshold: { 42 | global: { 43 | branches: 88, 44 | functions: 90, 45 | lines: 94, 46 | statements: 94, 47 | }, 48 | }, 49 | setupFilesAfterEnv: ["/jest.setup.ts"], 50 | }; 51 | -------------------------------------------------------------------------------- /jest.setup.ts: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright Inrupt Inc. 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal in 6 | // the Software without restriction, including without limitation the rights to use, 7 | // copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the 8 | // Software, and to permit persons to whom the Software is furnished to do so, 9 | // subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in 12 | // all copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 16 | // PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 17 | // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 18 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 19 | // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | // 21 | 22 | // import "@inrupt/jest-jsdom-polyfills"; 23 | if ( 24 | typeof globalThis.TextEncoder === "undefined" || 25 | typeof globalThis.TextDecoder === "undefined" 26 | ) { 27 | const utils = require("util"); 28 | globalThis.TextEncoder = utils.TextEncoder; 29 | globalThis.TextDecoder = utils.TextDecoder; 30 | // TextEncoder references a Uint8Array constructor different than the global 31 | // one used by users in tests. The following enforces the same constructor to 32 | // be referenced by both. 33 | // FIXME: currently this doesn't work, and must be set in a custom environment. 34 | globalThis.Uint8Array = Uint8Array; 35 | } 36 | 37 | if ( 38 | typeof globalThis.crypto !== "undefined" && 39 | // jsdom doesn't implement the subtle Web Crypto API 40 | typeof globalThis.crypto.subtle === "undefined" 41 | ) { 42 | // Requires OPENSSL_CONF=/dev/null (see https://github.com/nodejs/node/discussions/43184) 43 | const { 44 | Crypto: WCrypto, 45 | CryptoKey: WCryptoKey, 46 | } = require("@peculiar/webcrypto"); 47 | Object.assign(globalThis.crypto, new WCrypto()); 48 | globalThis.CryptoKey = WCryptoKey; 49 | } 50 | 51 | // Node.js doesn't support Blob or File, so we're polyfilling those with 52 | // https://github.com/web-std/io 53 | if (typeof globalThis.Blob === "undefined") { 54 | const stdBlob = require("@web-std/blob"); 55 | globalThis.Blob = stdBlob.Blob; 56 | } 57 | 58 | if (typeof globalThis.File === "undefined") { 59 | const stdFile = require("@web-std/file"); 60 | globalThis.File = stdFile.File; 61 | } 62 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@inrupt/solid-ui-react", 3 | "version": "3.0.0", 4 | "description": "Set of UI libraries using @solid/core", 5 | "main": "dist/index.js", 6 | "module": "dist/index.mjs", 7 | "types": "dist/src/index.d.ts", 8 | "files": [ 9 | "CHANGELOG.md", 10 | "dist/index.js", 11 | "dist/index.js.map", 12 | "dist/index.mjs", 13 | "dist/index.mjs.map", 14 | "dist/src/*", 15 | "dist/src/**/*" 16 | ], 17 | "scripts": { 18 | "audit": "npm audit --audit-level=moderate", 19 | "audit-licenses": "license-checker --production --failOn \"AGPL-1.0-only; AGPL-1.0-or-later; AGPL-3.0-only; AGPL-3.0-or-later; Beerware; CC-BY-NC-1.0; CC-BY-NC-2.0; CC-BY-NC-2.5; CC-BY-NC-3.0; CC-BY-NC-4.0; CC-BY-NC-ND-1.0; CC-BY-NC-ND-2.0; CC-BY-NC-ND-2.5; CC-BY-NC-ND-3.0; CC-BY-NC-ND-4.0; CC-BY-NC-SA-1.0; CC-BY-NC-SA-2.0; CC-BY-NC-SA-2.5; CC-BY-NC-SA-3.0; CC-BY-NC-SA-4.0; CPAL-1.0; EUPL-1.0; EUPL-1.1; EUPL-1.1; GPL-1.0-only; GPL-1.0-or-later; GPL-2.0-only; GPL-2.0-or-later; GPL-3.0; GPL-3.0-only; GPL-3.0-or-later; SISSL; SISSL-1.2; WTFPL\"", 20 | "build": "rollup --config rollup.config.mjs", 21 | "lint": "eslint src stories --ext .js,.jsx,.ts,.tsx", 22 | "test": "jest", 23 | "test:debug": "node --inspect-brk ./node_modules/.bin/jest -i", 24 | "storybook": "storybook dev -p 3000 --https --ssl-cert ./certificates/localhost.crt --ssl-key ./certificates/localhost.key", 25 | "build-storybook": "storybook build -c .storybook -o docs", 26 | "prepublishOnly": "npm run build" 27 | }, 28 | "peerDependencies": { 29 | "react": ">16.13.0 || ^17.0.0 || ^18.0.0 || ^18.0.0 || ^20.0.0" 30 | }, 31 | "devDependencies": { 32 | "@babel/core": "^7.23.6", 33 | "@babel/preset-env": "^7.23.6", 34 | "@babel/preset-react": "^7.23.3", 35 | "@babel/preset-typescript": "^7.23.3", 36 | "@emotion/styled": "^11.11.0", 37 | "@inrupt/base-rollup-config": "^3.0.2", 38 | "@inrupt/eslint-config-base": "^3.0.2", 39 | "@inrupt/eslint-config-lib": "^3.0.2", 40 | "@inrupt/eslint-config-react": "^3.0.2", 41 | "@inrupt/jest-jsdom-polyfills": "^3.0.2", 42 | "@mui/material": "^5.15.1", 43 | "@storybook/addon-actions": "^7.6.6", 44 | "@storybook/addon-docs": "^7.6.6", 45 | "@storybook/addon-essentials": "^7.6.6", 46 | "@storybook/addon-knobs": "^7.0.2", 47 | "@storybook/addon-links": "^8.0.0", 48 | "@storybook/addon-mdx-gfm": "^8.1.11", 49 | "@storybook/addon-storysource": "^8.0.0", 50 | "@storybook/addons": "^7.6.6", 51 | "@storybook/cli": "^8.0.8", 52 | "@storybook/mdx2-csf": "^1.1.0", 53 | "@storybook/react": "^7.6.6", 54 | "@storybook/react-webpack5": "^7.6.6", 55 | "@testing-library/react": "^14.1.2", 56 | "@testing-library/react-hooks": "^7.0.2", 57 | "@testing-library/user-event": "^14.1.0", 58 | "@types/jest": "^29.5.11", 59 | "@types/node": "^20.10.5", 60 | "@types/react": "^17.0.44", 61 | "@types/react-table": "^7.7.10", 62 | "@typescript-eslint/eslint-plugin": "^6.15.0", 63 | "@typescript-eslint/parser": "^6.15.0", 64 | "babel-eslint": "^10.1.0", 65 | "babel-loader": "^9.1.3", 66 | "core-js": "^3.34.0", 67 | "eslint": "^8.56.0", 68 | "eslint-plugin-storybook": "^0.8.0", 69 | "jest": "^29.7.0", 70 | "jest-environment-jsdom": "^29.7.0", 71 | "license-checker": "^25.0.1", 72 | "postcss": "^8.4.12", 73 | "prettier": "^3.1.1", 74 | "react": "^18.2.0", 75 | "react-docgen-typescript-loader": "^3.7.2", 76 | "react-dom": "^18.2.0", 77 | "react-error-boundary": "^4.0.12", 78 | "react-test-renderer": "^18.2.0", 79 | "rollup": "^4.9.1", 80 | "storybook": "^7.6.6", 81 | "ts-jest": "^29.1.1", 82 | "ts-loader": "^9.5.1", 83 | "ts-node": "^10.9.1", 84 | "typescript": "^5.3.3" 85 | }, 86 | "repository": { 87 | "type": "git", 88 | "url": "git+https://github.com/inrupt/solid-ui-react.git" 89 | }, 90 | "keywords": [ 91 | "Solid", 92 | "ui", 93 | "react" 94 | ], 95 | "author": "Inrupt ", 96 | "license": "MIT", 97 | "bugs": { 98 | "url": "https://github.com/inrupt/solid-ui-react/issues" 99 | }, 100 | "homepage": "https://github.com/inrupt/solid-ui-react#readme", 101 | "dependencies": { 102 | "@inrupt/solid-client": "^2.0.0", 103 | "@inrupt/solid-client-authn-browser": "^2.0.0", 104 | "react-table": "^7.6.3", 105 | "stream": "0.0.3", 106 | "swr": "^2.2.4" 107 | }, 108 | "resolutions": { 109 | "postcss": "8.4.5" 110 | }, 111 | "overrides": { 112 | "glob-parent": "5.1.2", 113 | "trim-newlines": "3.0.1" 114 | }, 115 | "engines": { 116 | "node": "^18.0.0 || ^20.0.0" 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /public/example.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inrupt/solid-ui-react/a77159cacf90e682a03f998db881d5ca8edb8295/public/example.jpg -------------------------------------------------------------------------------- /public/example.ttl: -------------------------------------------------------------------------------- 1 | @prefix : <#>. 2 | @prefix solid: . 3 | @prefix pro: <./>. 4 | @prefix n0: . 5 | @prefix schem: . 6 | @prefix n: . 7 | @prefix ldp: . 8 | @prefix inbox: . 9 | @prefix sp: . 10 | @prefix xsd: . 11 | @prefix doc: . 12 | 13 | pro:card a n0:PersonalProfileDocument; n0:maker :me; n0:primaryTopic :me. 14 | 15 | :id100 a n:Home; n:value . 16 | 17 | :id101 a n:Work; n:value . 18 | 19 | :id102 a n:Work; n:value . 20 | 21 | :me 22 | a schem:Person, n0:Person; 23 | n:fn "Docs Example"; 24 | n:hasTelephone :id100, :id101; 25 | n:note "Sample Account for docs. "; 26 | n:organization-name "Inrupt"; 27 | n:role "Documentation"; 28 | ldp:inbox inbox:; 29 | sp:preferencesFile ; 30 | sp:storage doc:; 31 | solid:account doc:; 32 | solid:privateTypeIndex ; 33 | solid:publicTypeIndex ; 34 | n0:name "Docs Example". 35 | 36 | :alterEgo 37 | a schem:Person, n0:Person; 38 | n:fn "Super Inrupt"; 39 | n:hasTelephone :id102; 40 | n:note "Secret superhero account. "; 41 | n:organization-name "Inrupt"; 42 | n:role "Documentation"; 43 | n0:name "Super Inrupt". 44 | 45 | :exampleImage 46 | schem:author "Snapwire" ; 47 | schem:contentUrl ; 48 | schem:datePublished "2017-12-12T15:31:10-05:00"^^xsd:dateTime; 49 | schem:description "Photo of Gray Cat Looking Up Against Black Background" ; 50 | schem:name "Gray Cat" ; 51 | schem:url ; 52 | schem:copyrightYear 2017 ; 53 | schem:version 1.1 ; 54 | schem:isAccessibleForFree true . 55 | 56 | :exampleVideo 57 | schem:author "Blender Foundation / Netherlands Media Art Institute" ; 58 | schem:contentUrl ; 59 | schem:description "The world's first open movie." ; 60 | schem:name "Elephant's Dream" ; 61 | schem:url ; 62 | schem:isAccessibleForFree true . 63 | -------------------------------------------------------------------------------- /resources/license-header.js: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright Inrupt Inc. 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal in 6 | // the Software without restriction, including without limitation the rights to use, 7 | // copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the 8 | // Software, and to permit persons to whom the Software is furnished to do so, 9 | // subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in 12 | // all copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 16 | // PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 17 | // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 18 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 19 | // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | // 21 | -------------------------------------------------------------------------------- /resources/whitesource/wss-pull_request-scan.config: -------------------------------------------------------------------------------- 1 | ############################################################### 2 | # WhiteSource Unified-Agent configuration file 3 | ############################################################### 4 | # GENERAL SCAN MODE: Files and Package Managers 5 | ############################################################### 6 | 7 | # For more information about this config file, see 8 | # https://whitesource.atlassian.net/wiki/spaces/WD/pages/804814917/Unified+Agent+Configuration+File+and+Parameters 9 | 10 | ####################### 11 | # Organization vitals # 12 | ####################### 13 | 14 | # Required apiKey and userKey are expected to be provided on the CLI 15 | productName=SolidOS 16 | projectName=pod-manager 17 | 18 | wss.url=https://saas.whitesourcesoftware.com/agent 19 | 20 | ############ 21 | # Policies # 22 | ############ 23 | # Set to 'true' to fail the build on license violation 24 | checkPolicies=true 25 | forceCheckAllDependencies=true 26 | forceUpdate=false 27 | forceUpdate.failBuildOnPolicyViolation=true 28 | updateInventory=false 29 | 30 | ########### 31 | # General # 32 | ########### 33 | 34 | ######################################## 35 | # Package Manager Dependency resolvers # 36 | ######################################## 37 | npm.resolveDependencies=true 38 | npm.resolveLockFile=true 39 | npm.includeDevDependencies=false 40 | npm.ignoreNpmLsErrors=true 41 | npm.identifyByNameAndVersion=true 42 | 43 | bower.resolveDependencies=false 44 | 45 | ########################################################################################### 46 | # Includes/Excludes Glob patterns - Please use only one exclude line and one include line # 47 | ########################################################################################### 48 | includes=**/*.js, **/*.ts 49 | 50 | case.sensitive.glob=false 51 | followSymbolicLinks=true 52 | -------------------------------------------------------------------------------- /resources/whitesource/wss-push-scan.config: -------------------------------------------------------------------------------- 1 | ############################################################### 2 | # WhiteSource Unified-Agent configuration file 3 | ############################################################### 4 | # GENERAL SCAN MODE: Files and Package Managers 5 | ############################################################### 6 | 7 | # For more information about this config file, see 8 | # https://whitesource.atlassian.net/wiki/spaces/WD/pages/804814917/Unified+Agent+Configuration+File+and+Parameters 9 | 10 | ####################### 11 | # Organization vitals # 12 | ####################### 13 | 14 | # Required apiKey and userKey are expected to be provided on the CLI 15 | productName=SolidOS 16 | projectName=pod-manager 17 | 18 | wss.url=https://saas.whitesourcesoftware.com/agent 19 | 20 | ############ 21 | # Policies # 22 | ############ 23 | # Set to 'true' to fail the build on license violation 24 | checkPolicies=false 25 | forceCheckAllDependencies=false 26 | forceUpdate=false 27 | forceUpdate.failBuildOnPolicyViolation=false 28 | 29 | ########### 30 | # General # 31 | ########### 32 | resolveAllDependencies=false 33 | 34 | ######################################## 35 | # Package Manager Dependency resolvers # 36 | ######################################## 37 | npm.resolveDependencies=false 38 | npm.resolveLockFile=true 39 | npm.includeDevDependencies=false 40 | npm.ignoreNpmLsErrors=true 41 | npm.identifyByNameAndVersion=true 42 | 43 | bower.resolveDependencies=false 44 | 45 | ########################################################################################### 46 | # Includes/Excludes Glob patterns - Please use only one exclude line and one include line # 47 | ########################################################################################### 48 | 49 | includes=**/*.js, **/*.ts 50 | 51 | case.sensitive.glob=false 52 | followSymbolicLinks=true 53 | -------------------------------------------------------------------------------- /rollup.config.mjs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright Inrupt Inc. 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal in 6 | // the Software without restriction, including without limitation the rights to use, 7 | // copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the 8 | // Software, and to permit persons to whom the Software is furnished to do so, 9 | // subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in 12 | // all copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 16 | // PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 17 | // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 18 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 19 | // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | // 21 | import { createRequire } from "node:module"; 22 | import createConfig from "@inrupt/base-rollup-config"; 23 | const require = createRequire(import.meta.url); 24 | 25 | export default createConfig(require("./package.json")); 26 | -------------------------------------------------------------------------------- /sonar-project.properties: -------------------------------------------------------------------------------- 1 | sonar.projectKey=inrupt_solid-ui-react 2 | sonar.organization=inrupt 3 | 4 | # Path is relative to the sonar-project.properties file. Defaults to . 5 | sonar.sources=src 6 | sonar.exclusions=**/*.test.ts,**/*.test.tsx,**/__snapshots__/* 7 | 8 | sonar.tests=src 9 | sonar.test.inclusions=**/*.test.ts,**/*.test.tsx,**/__snapshots__/* 10 | 11 | sonar.coverage.exclusions=**/*.test.ts,**/*.test.tsx,**/__snapshots__/* 12 | 13 | # Typescript tsconfigPath JSON file 14 | sonar.typescript.tsconfigPath=. 15 | 16 | # Comma-delimited list of paths to LCOV coverage report files. Paths may be absolute or relative to the project root. 17 | sonar.javascript.lcov.reportPaths=./coverage/lcov.info 18 | -------------------------------------------------------------------------------- /src/components/file/__snapshots__/index.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[` component snapshot test matches snapshot 1`] = ` 4 | 5 |
6 | 10 |
11 | 12 | `; 13 | 14 | exports[` component snapshot test matches snapshot with input options 1`] = ` 15 | 16 |
17 | 22 |
23 | 24 | `; 25 | -------------------------------------------------------------------------------- /src/components/file/index.tsx: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright Inrupt Inc. 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal in 6 | // the Software without restriction, including without limitation the rights to use, 7 | // copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the 8 | // Software, and to permit persons to whom the Software is furnished to do so, 9 | // subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in 12 | // all copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 16 | // PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 17 | // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 18 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 19 | // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | // 21 | 22 | import type { ReactElement } from "react"; 23 | import React, { useContext } from "react"; 24 | import type { Url, UrlString, WithResourceInfo } from "@inrupt/solid-client"; 25 | import { saveFileInContainer } from "@inrupt/solid-client"; 26 | import { SessionContext } from "../../context/sessionContext"; 27 | 28 | export type Props = { 29 | saveLocation: Url | UrlString; 30 | inputProps?: Omit, "multiple">; 31 | autosave?: boolean; 32 | onSave?: (savedFile?: File & WithResourceInfo) => void; 33 | onError?: (error: Error) => void; 34 | }; 35 | 36 | export function FileUpload({ 37 | saveLocation, 38 | onError, 39 | onSave, 40 | inputProps, 41 | autosave, 42 | }: Props & React.HTMLAttributes): ReactElement { 43 | const { fetch } = useContext(SessionContext); 44 | 45 | const handleChange = async (e: React.FormEvent) => { 46 | const target = e.target as HTMLInputElement; 47 | 48 | // This is a typescript bug, as target.files should always be a FileList: 49 | if (!target.files) { 50 | return; 51 | } 52 | 53 | if (!autosave) { 54 | return; 55 | } 56 | 57 | try { 58 | const savedFile = await saveFileInContainer( 59 | saveLocation, 60 | target.files[0], 61 | { 62 | slug: target.files[0].name ? target.files[0].name : undefined, 63 | fetch, 64 | }, 65 | ); 66 | if (onSave) { 67 | onSave(savedFile); 68 | } 69 | } catch (saveError) { 70 | if (onError) { 71 | onError(saveError as Error); 72 | } 73 | } 74 | }; 75 | 76 | return ( 77 | handleChange(e)} 82 | data-testid="form-input" 83 | /> 84 | ); 85 | } 86 | -------------------------------------------------------------------------------- /src/components/image/__snapshots__/index.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Image component Image functional tests When getUrl returns null, with an errorComponent, should render the error 1`] = ` 4 | 5 | 6 | Error: No value found for property. 7 | 8 | 9 | `; 10 | 11 | exports[`Image component Image snapshots does not render default loading message if loadingComponent is null 1`] = ``; 12 | 13 | exports[`Image component Image snapshots matches snapshot with additional props for img and input 1`] = ` 14 | 15 | test img 20 | 25 | 26 | `; 27 | 28 | exports[`Image component Image snapshots matches snapshot with standard props 1`] = ` 29 | 30 | 34 | 35 | `; 36 | 37 | exports[`Image component Image snapshots renders a a custom delete button if passed and allowDelete is true 1`] = ` 38 | 39 | 40 | Error: No value found for property. 41 | 42 | 47 | 48 | `; 49 | 50 | exports[`Image component Image snapshots renders a a default delete button if allowDelete is true 1`] = ` 51 | 52 | 53 | Error: No value found for property. 54 | 55 | 60 | 61 | `; 62 | 63 | exports[`Image component Image snapshots renders an error message if an errorComponent is provided 1`] = ` 64 | 65 | 68 | Error: No value found for property. 69 | 70 | 71 | `; 72 | 73 | exports[`Image component Image snapshots renders default error message if there is an error and no errorComponent is passed 1`] = ` 74 | 75 | 76 | Error: No value found for property. 77 | 78 | 79 | `; 80 | 81 | exports[`Image component Image snapshots renders default loading message if thing is undefined and there is no error 1`] = ` 82 | 83 | 84 | fetching data in progress 85 | 86 | 87 | `; 88 | 89 | exports[`Image component Image snapshots renders loading component if passed and thing is undefined and there is no error 1`] = ` 90 | 91 | 94 | loading... 95 | 96 | 97 | `; 98 | -------------------------------------------------------------------------------- /src/components/link/__snapshots__/index.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Link component Link snapshot 1`] = ` 4 | 5 | 9 | http://test.url 10 | 11 | 12 | `; 13 | 14 | exports[`Link component does not render default loading message if loadingComponent is null 1`] = ` 15 | 16 |
17 | 18 | `; 19 | 20 | exports[`Link component matches snapshot while fetching data 1`] = ` 21 | 22 |
23 | 24 | fetching data in progress 25 | 26 |
27 | 28 | `; 29 | 30 | exports[`Link component renders custom error component if passed and there is an error 1`] = ` 31 | 32 |
33 | 36 | Error: URL not found for given property 37 | 38 |
39 | 40 | `; 41 | 42 | exports[`Link component renders default error message if there is an error 1`] = ` 43 | 44 |
45 | 46 | Error: URL not found for given property 47 | 48 |
49 | 50 | `; 51 | 52 | exports[`Link component renders in edit mode 1`] = ` 53 | 54 | 58 | http://test.url 59 | 60 | 61 | `; 62 | 63 | exports[`Link component renders loading component if passed while fetching data 1`] = ` 64 | 65 |
66 | 69 | loading... 70 | 71 |
72 | 73 | `; 74 | -------------------------------------------------------------------------------- /src/components/link/index.tsx: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright Inrupt Inc. 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal in 6 | // the Software without restriction, including without limitation the rights to use, 7 | // copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the 8 | // Software, and to permit persons to whom the Software is furnished to do so, 9 | // subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in 12 | // all copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 16 | // PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 17 | // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 18 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 19 | // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | // 21 | 22 | import type { ReactElement } from "react"; 23 | import React, { useState } from "react"; 24 | 25 | import type { CommonProperties } from "../../helpers"; 26 | import { useProperty } from "../../helpers"; 27 | import { Value } from "../value"; 28 | 29 | export type Props = { 30 | loadingComponent?: React.ComponentType | null; 31 | errorComponent?: React.ComponentType<{ error: Error }>; 32 | } & CommonProperties & 33 | React.AnchorHTMLAttributes; 34 | 35 | /** 36 | * Retrieves a URL from given [Thing](https://docs.inrupt.com/developer-tools/javascript/client-libraries/reference/glossary/#term-Thing)/property, and renders as an anchor tag with the given href. 37 | */ 38 | export function Link({ 39 | children, 40 | property: propProperty, 41 | properties: propProperties, 42 | thing: propThing, 43 | solidDataset, 44 | autosave, 45 | rel, 46 | target, 47 | edit, 48 | loadingComponent: LoadingComponent, 49 | errorComponent: ErrorComponent, 50 | onSave, 51 | onError, 52 | ...linkOptions 53 | }: Props): ReactElement | null { 54 | const { 55 | value: href, 56 | thing, 57 | property, 58 | dataset, 59 | error: thingError, 60 | } = useProperty({ 61 | dataset: solidDataset, 62 | thing: propThing, 63 | property: propProperty, 64 | properties: propProperties, 65 | type: "url", 66 | }); 67 | 68 | let valueError; 69 | if (!edit && !href) { 70 | valueError = new Error("URL not found for given property"); 71 | } 72 | 73 | const isFetchingThing = !thing && !thingError; 74 | 75 | const [error] = useState(thingError ?? valueError); 76 | 77 | if (isFetchingThing) { 78 | let loader: JSX.Element | null = (LoadingComponent && ( 79 | 80 | )) || fetching data in progress; 81 | if (LoadingComponent === null) { 82 | loader = null; 83 | } 84 | return loader; 85 | } 86 | 87 | if (error) { 88 | if (ErrorComponent) { 89 | return ; 90 | } 91 | return {error.toString()}; 92 | } 93 | const adjustedRel = 94 | rel || (target === "_blank" ? "noopener noreferrer" : "nofollow"); 95 | 96 | if (edit) { 97 | return ( 98 | 108 | ); 109 | } 110 | 111 | return ( 112 | // eslint-disable-next-line react/jsx-props-no-spreading 113 | 114 | {children ?? href} 115 | 116 | ); 117 | } 118 | -------------------------------------------------------------------------------- /src/components/logIn/__snapshots__/index.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[` component snapshot test matches snapshot 1`] = ` 4 | 5 |
6 | 11 |
12 | 13 | `; 14 | -------------------------------------------------------------------------------- /src/components/logIn/index.tsx: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright Inrupt Inc. 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal in 6 | // the Software without restriction, including without limitation the rights to use, 7 | // copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the 8 | // Software, and to permit persons to whom the Software is furnished to do so, 9 | // subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in 12 | // all copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 16 | // PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 17 | // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 18 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 19 | // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | // 21 | 22 | import React, { useContext } from "react"; 23 | import type { ILoginInputOptions } from "@inrupt/solid-client-authn-core"; 24 | import { SessionContext } from "../../context/sessionContext"; 25 | 26 | export interface Props { 27 | authOptions?: ILoginInputOptions; 28 | children?: React.ReactNode; 29 | onError?(error: Error): void; 30 | oidcIssuer: string; 31 | redirectUrl: string; 32 | } 33 | 34 | /** 35 | * Displays a button which triggers the login flow on click. Should be used inside a `SessionProvider`. 36 | */ 37 | export const LoginButton: React.FC = ({ 38 | oidcIssuer, 39 | redirectUrl, 40 | children, 41 | authOptions, 42 | onError, 43 | }: Props) => { 44 | const options = { 45 | redirectUrl, 46 | oidcIssuer, 47 | ...authOptions, 48 | }; 49 | 50 | const { login, setSessionRequestInProgress } = useContext(SessionContext); 51 | 52 | async function loginHandler() { 53 | if (setSessionRequestInProgress === undefined) { 54 | return; 55 | } 56 | setSessionRequestInProgress(true); 57 | 58 | try { 59 | await login(options); 60 | setSessionRequestInProgress(false); 61 | } catch (error) { 62 | setSessionRequestInProgress(false); 63 | if (onError) onError(error as Error); 64 | } 65 | } 66 | 67 | function keyDownHandler( 68 | e: React.KeyboardEvent, 69 | ): Promise { 70 | e.preventDefault(); 71 | 72 | return e.key === "Enter" ? loginHandler() : Promise.resolve(); 73 | } 74 | 75 | return children ? ( 76 |
82 | {children} 83 |
84 | ) : ( 85 | 88 | ); 89 | }; 90 | -------------------------------------------------------------------------------- /src/components/logOut/__snapshots__/index.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[` component snapshot test matches snapshot 1`] = ` 4 | 5 |
6 | 11 |
12 | 13 | `; 14 | -------------------------------------------------------------------------------- /src/components/logOut/index.tsx: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright Inrupt Inc. 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal in 6 | // the Software without restriction, including without limitation the rights to use, 7 | // copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the 8 | // Software, and to permit persons to whom the Software is furnished to do so, 9 | // subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in 12 | // all copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 16 | // PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 17 | // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 18 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 19 | // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | // 21 | 22 | import React, { useContext } from "react"; 23 | import { SessionContext } from "../../context/sessionContext"; 24 | 25 | export interface Props { 26 | onLogout?(): void; 27 | onError?(error: Error): void; 28 | children?: React.ReactElement; 29 | } 30 | 31 | /** 32 | * Renders a button which triggers logout on click. Should be used within a `SessionProvider`. 33 | */ 34 | export const LogoutButton: React.FC = ({ 35 | children, 36 | onLogout, 37 | onError, 38 | }: Props) => { 39 | const { logout } = useContext(SessionContext); 40 | 41 | async function logoutHandler() { 42 | try { 43 | await logout(); 44 | if (onLogout) onLogout(); 45 | } catch (error) { 46 | if (onError) onError(error as Error); 47 | } 48 | } 49 | 50 | function keyDownHandler( 51 | e: React.KeyboardEvent, 52 | ): Promise { 53 | e.preventDefault(); 54 | 55 | return e.key === "Enter" ? logoutHandler() : Promise.resolve(); 56 | } 57 | 58 | return children ? ( 59 |
65 | {children} 66 |
67 | ) : ( 68 | 71 | ); 72 | }; 73 | -------------------------------------------------------------------------------- /src/components/table/__snapshots__/index.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[` component snapshot tests matches snapshot 1`] = ` 4 | 5 |
6 |
9 | 10 | 13 | 19 | 25 | 31 | 37 | 38 | 39 | 42 | 45 | 50 | 55 | 60 | 64 | 67 | 72 | 77 | 82 | 86 | 87 |
17 | Name 18 | 23 | http://xmlns.com/foaf/0.1/nick 24 | 29 | http://schema.org/isAccessibleForFree 30 | 35 | http://missing.property 36 |
48 | example name 1 49 | 53 | example nick 1 54 | 58 | true 59 | 63 |
70 | example name 2 71 | 75 | example nick 2 76 | 80 | false 81 | 85 |
88 |
89 | 90 | `; 91 | -------------------------------------------------------------------------------- /src/components/text/__snapshots__/index.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[` component snapshot test does not render default loading message if loadingComponent is null 1`] = ` 4 | 5 |
6 | 7 | `; 8 | 9 | exports[` component snapshot test matches snapshot 1`] = ` 10 | 11 |
12 | 13 | test nick value 14 | 15 |
16 | 17 | `; 18 | 19 | exports[` component snapshot test matches snapshot while fetching data 1`] = ` 20 | 21 |
22 | 23 | fetching data in progress 24 | 25 |
26 | 27 | `; 28 | 29 | exports[` component snapshot test matches snapshot with data from context 1`] = ` 30 | 31 |
32 | 36 |
37 | 38 | `; 39 | 40 | exports[` component snapshot test matches snapshot with edit true and inputOptions 1`] = ` 41 | 42 |
43 | 48 |
49 | 50 | `; 51 | 52 | exports[` component snapshot test renders custom error component if passed and there is an error 1`] = ` 53 | 54 |
55 | 58 | Error: No value found for property. 59 | 60 |
61 | 62 | `; 63 | 64 | exports[` component snapshot test renders default error message if there is an error 1`] = ` 65 | 66 |
67 | 68 | Error: No value found for property. 69 | 70 |
71 | 72 | `; 73 | 74 | exports[` component snapshot test renders loading component if passed while fetching data 1`] = ` 75 | 76 |
77 | 80 | loading... 81 | 82 |
83 | 84 | `; 85 | -------------------------------------------------------------------------------- /src/components/value/__snapshots__/index.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[` component snapshot test does not render default loading message if loadingComponent is null 1`] = ` 4 | 5 |
6 | 7 | `; 8 | 9 | exports[` component snapshot test matches snapshot 1`] = ` 10 | 11 |
12 | 13 | test nick value 14 | 15 |
16 | 17 | `; 18 | 19 | exports[` component snapshot test matches snapshot while fetching data 1`] = ` 20 | 21 |
22 | 23 | fetching data in progress 24 | 25 |
26 | 27 | `; 28 | 29 | exports[` component snapshot test matches snapshot with data from context 1`] = ` 30 | 31 |
32 | 36 |
37 | 38 | `; 39 | 40 | exports[` component snapshot test matches snapshot with dataType NaN 1`] = ` 41 | 42 | 46 | 47 | `; 48 | 49 | exports[` component snapshot test matches snapshot with dataType NaN 2`] = ` 50 | 51 | 55 | 56 | `; 57 | 58 | exports[` component snapshot test matches snapshot with dataType NaN 3`] = ` 59 | 60 | 65 | 66 | `; 67 | 68 | exports[` component snapshot test matches snapshot with dataType NaN 4`] = ` 69 | 70 | 75 | 76 | `; 77 | 78 | exports[` component snapshot test matches snapshot with dataType NaN 5`] = ` 79 | 80 | 84 | 85 | `; 86 | 87 | exports[` component snapshot test matches snapshot with dataType datetime when browser does not support datetime-local 1`] = ` 88 | 89 | 97 | 103 | 104 | `; 105 | 106 | exports[` component snapshot test matches snapshot with dataType datetime when browser supports datetime-local 1`] = ` 107 | 108 | 116 | 117 | `; 118 | 119 | exports[` component snapshot test matches snapshot with edit true and inputOptions 1`] = ` 120 | 121 |
122 | 127 |
128 | 129 | `; 130 | 131 | exports[` component snapshot test renders 'false' if dataType is boolean instead of error if property not found 1`] = ` 132 | 133 |
134 | 135 | false 136 | 137 |
138 | 139 | `; 140 | 141 | exports[` component snapshot test renders custom error component if passed and there is an error 1`] = ` 142 | 143 |
144 | 147 | Error: No value found for property. 148 | 149 |
150 | 151 | `; 152 | 153 | exports[` component snapshot test renders default error message if there is an error 1`] = ` 154 | 155 |
156 | 157 | Error: No value found for property. 158 | 159 |
160 | 161 | `; 162 | 163 | exports[` component snapshot test renders error if dataType is boolean and there is an error fetching the thing 1`] = ` 164 | 165 |
166 | 167 | Error: Thing not found as property or in context 168 | 169 |
170 | 171 | `; 172 | 173 | exports[` component snapshot test renders loading component if passed while fetching data 1`] = ` 174 | 175 |
176 | 179 | loading... 180 | 181 |
182 | 183 | `; 184 | -------------------------------------------------------------------------------- /src/components/value/boolean/index.tsx: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright Inrupt Inc. 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal in 6 | // the Software without restriction, including without limitation the rights to use, 7 | // copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the 8 | // Software, and to permit persons to whom the Software is furnished to do so, 9 | // subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in 12 | // all copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 16 | // PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 17 | // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 18 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 19 | // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | // 21 | 22 | import React, { useState, useEffect, useContext } from "react"; 23 | import { 24 | setThing, 25 | saveSolidDatasetAt, 26 | getSourceUrl, 27 | hasResourceInfo, 28 | setBoolean, 29 | } from "@inrupt/solid-client"; 30 | import { SessionContext } from "../../../context/sessionContext"; 31 | 32 | import { updateDataset, useProperty } from "../../../helpers"; 33 | import type { Props } from ".."; 34 | 35 | type BooleanProps = Omit; 36 | 37 | const BooleanValue = (props: BooleanProps) => { 38 | const { 39 | thing: propThing, 40 | solidDataset: propDataset, 41 | property: propProperty, 42 | properties: propProperties, 43 | saveDatasetTo, 44 | onSave, 45 | onError, 46 | edit, 47 | autosave, 48 | inputProps, 49 | ...other 50 | } = props; 51 | 52 | const { fetch } = useContext(SessionContext); 53 | 54 | const { 55 | value: thingValue, 56 | thing, 57 | property, 58 | dataset, 59 | setDataset, 60 | error, 61 | } = useProperty({ 62 | dataset: propDataset, 63 | thing: propThing, 64 | type: "boolean", 65 | property: propProperty, 66 | properties: propProperties, 67 | }); 68 | 69 | useEffect(() => { 70 | if (error && onError) { 71 | onError(error); 72 | } 73 | }, [error, onError]); 74 | 75 | let formattedValue = thingValue; 76 | let initialBooleanValue = false; 77 | initialBooleanValue = (thingValue as boolean | null) ?? false; 78 | formattedValue = initialBooleanValue.toString(); 79 | 80 | const [value, setValue] = useState(formattedValue); 81 | 82 | const [booleanValue, setBooleanValue] = 83 | useState(initialBooleanValue); 84 | 85 | useEffect(() => { 86 | setValue(booleanValue.toString()); 87 | }, [booleanValue]); 88 | 89 | /* Save Value value in the pod */ 90 | const saveHandler = async (e: React.ChangeEvent) => { 91 | if ( 92 | formattedValue !== value && 93 | thing && 94 | dataset && 95 | e.target.reportValidity() 96 | ) { 97 | const updatedResource = setBoolean(thing, property, booleanValue); 98 | 99 | try { 100 | let savedDataset; 101 | if (saveDatasetTo) { 102 | savedDataset = await saveSolidDatasetAt( 103 | saveDatasetTo, 104 | setThing(dataset, updatedResource), 105 | { fetch }, 106 | ); 107 | await updateDataset(saveDatasetTo, setDataset); 108 | } else if (hasResourceInfo(dataset)) { 109 | savedDataset = await saveSolidDatasetAt( 110 | getSourceUrl(dataset), 111 | setThing(dataset, updatedResource), 112 | { fetch }, 113 | ); 114 | await updateDataset(getSourceUrl(dataset), setDataset); 115 | } else if (onError) { 116 | onError( 117 | new Error("Please provide saveDatasetTo location for new data"), 118 | ); 119 | } 120 | 121 | if (!error && onSave) { 122 | onSave(savedDataset, updatedResource); 123 | } 124 | } catch (saveError) { 125 | if (onError) { 126 | onError(saveError as Error); 127 | } 128 | } 129 | } 130 | }; 131 | 132 | return ( 133 | <> 134 | { 135 | // eslint-disable-next-line react/jsx-props-no-spreading 136 | !edit && dataset && thing && {`${value}`} 137 | } 138 | {edit && dataset && thing && ( 139 | { 145 | setBooleanValue(e.target.checked); 146 | }} 147 | onBlur={(e) => autosave && saveHandler(e)} 148 | value={value} 149 | /> 150 | )} 151 | 152 | ); 153 | }; 154 | 155 | BooleanValue.defaultProps = { 156 | autosave: false, 157 | edit: false, 158 | }; 159 | 160 | export default BooleanValue; 161 | -------------------------------------------------------------------------------- /src/components/value/decimal/index.tsx: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright Inrupt Inc. 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal in 6 | // the Software without restriction, including without limitation the rights to use, 7 | // copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the 8 | // Software, and to permit persons to whom the Software is furnished to do so, 9 | // subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in 12 | // all copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 16 | // PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 17 | // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 18 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 19 | // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | // 21 | 22 | import React, { useState, useEffect, useContext } from "react"; 23 | import { 24 | setThing, 25 | saveSolidDatasetAt, 26 | getSourceUrl, 27 | hasResourceInfo, 28 | setDecimal, 29 | } from "@inrupt/solid-client"; 30 | import { SessionContext } from "../../../context/sessionContext"; 31 | 32 | import { updateDataset, useProperty } from "../../../helpers"; 33 | import type { Props } from ".."; 34 | 35 | type DecimalProps = Omit; 36 | 37 | const DecimalValue: React.FC = (props: DecimalProps) => { 38 | const { 39 | thing: propThing, 40 | solidDataset: propDataset, 41 | property: propProperty, 42 | properties: propProperties, 43 | saveDatasetTo, 44 | onSave, 45 | onError, 46 | edit, 47 | autosave, 48 | inputProps, 49 | ...other 50 | } = props; 51 | 52 | const { fetch } = useContext(SessionContext); 53 | 54 | const { 55 | value: thingValue, 56 | thing, 57 | property, 58 | dataset, 59 | setDataset, 60 | error, 61 | } = useProperty({ 62 | dataset: propDataset, 63 | thing: propThing, 64 | type: "decimal", 65 | property: propProperty, 66 | properties: propProperties, 67 | }); 68 | 69 | useEffect(() => { 70 | if (error && onError) { 71 | onError(error); 72 | } 73 | }, [error, onError]); 74 | 75 | const formattedValue: string = thingValue?.toString() ?? ""; 76 | 77 | const [value, setValue] = useState(formattedValue); 78 | 79 | /* Save Value value in the pod */ 80 | const saveHandler = async (e: React.ChangeEvent) => { 81 | if ( 82 | formattedValue !== value && 83 | thing && 84 | dataset && 85 | e.target.reportValidity() 86 | ) { 87 | const updatedResource = setDecimal( 88 | thing, 89 | property, 90 | parseFloat(value as string), 91 | ); 92 | try { 93 | let savedDataset; 94 | if (saveDatasetTo) { 95 | savedDataset = await saveSolidDatasetAt( 96 | saveDatasetTo, 97 | setThing(dataset, updatedResource), 98 | { fetch }, 99 | ); 100 | await updateDataset(saveDatasetTo, setDataset); 101 | } else if (hasResourceInfo(dataset)) { 102 | savedDataset = await saveSolidDatasetAt( 103 | getSourceUrl(dataset), 104 | setThing(dataset, updatedResource), 105 | { fetch }, 106 | ); 107 | await updateDataset(getSourceUrl(dataset), setDataset); 108 | } else if (onError) { 109 | onError( 110 | new Error("Please provide saveDatasetTo location for new data"), 111 | ); 112 | } 113 | 114 | if (!error && onSave) { 115 | onSave(savedDataset, updatedResource); 116 | } 117 | } catch (saveError) { 118 | if (onError) { 119 | onError(saveError as Error); 120 | } 121 | } 122 | } 123 | }; 124 | 125 | return ( 126 | <> 127 | { 128 | // eslint-disable-next-line react/jsx-props-no-spreading 129 | !edit && dataset && thing && {`${value}`} 130 | } 131 | {edit && dataset && thing && ( 132 | { 138 | setValue(e.target.value); 139 | }} 140 | onBlur={(e) => autosave && saveHandler(e)} 141 | value={value} 142 | /> 143 | )} 144 | 145 | ); 146 | }; 147 | 148 | DecimalValue.defaultProps = { 149 | autosave: false, 150 | edit: false, 151 | }; 152 | 153 | export default DecimalValue; 154 | -------------------------------------------------------------------------------- /src/components/value/index.tsx: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright Inrupt Inc. 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal in 6 | // the Software without restriction, including without limitation the rights to use, 7 | // copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the 8 | // Software, and to permit persons to whom the Software is furnished to do so, 9 | // subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in 12 | // all copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 16 | // PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 17 | // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 18 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 19 | // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | // 21 | 22 | import type { ReactElement } from "react"; 23 | import React, { useState } from "react"; 24 | import type { Url, UrlString } from "@inrupt/solid-client"; 25 | 26 | import type { DataType, CommonProperties } from "../../helpers"; 27 | import { useProperty } from "../../helpers"; 28 | import DatetimeValue from "./datetime"; 29 | import StringValue from "./string"; 30 | import BooleanValue from "./boolean"; 31 | import UrlValue from "./url"; 32 | import IntegerValue from "./integer"; 33 | import DecimalValue from "./decimal"; 34 | 35 | export type Props = { 36 | dataType: DataType; 37 | saveDatasetTo?: Url | UrlString; 38 | inputProps?: React.InputHTMLAttributes; 39 | locale?: string; 40 | loadingComponent?: React.ComponentType | null; 41 | errorComponent?: React.ComponentType<{ error: Error }>; 42 | } & CommonProperties; 43 | 44 | /** 45 | * Retrieves and displays a value of one of a range of types from a given [Dataset](https://docs.inrupt.com/developer-tools/javascript/client-libraries/reference/glossary/#term-SolidDataset)/[Thing](https://docs.inrupt.com/developer-tools/javascript/client-libraries/reference/glossary/#term-Thing)/property. Can also be used to set/update and persist a value. 46 | */ 47 | export function Value(props: Props): ReactElement | null { 48 | const { dataType, ...otherProps } = props as Props; 49 | const { 50 | thing: propThing, 51 | solidDataset: propDataset, 52 | property: propProperty, 53 | properties: propProperties, 54 | edit, 55 | loadingComponent: LoadingComponent, 56 | errorComponent: ErrorComponent, 57 | locale, 58 | } = otherProps; 59 | const { 60 | thing, 61 | value, 62 | error: thingError, 63 | } = useProperty({ 64 | dataset: propDataset, 65 | thing: propThing, 66 | property: propProperty, 67 | properties: propProperties, 68 | type: dataType, 69 | locale, 70 | }); 71 | 72 | let valueError; 73 | if (!edit && !value && dataType !== "boolean") { 74 | valueError = new Error("No value found for property."); 75 | } 76 | 77 | const isFetchingThing = !thing && !thingError; 78 | 79 | const [error] = useState(thingError ?? valueError); 80 | 81 | if (isFetchingThing) { 82 | let loader: JSX.Element | null = (LoadingComponent && ( 83 | 84 | )) || fetching data in progress; 85 | if (LoadingComponent === null) { 86 | loader = null; 87 | } 88 | return loader; 89 | } 90 | 91 | if (error) { 92 | if (ErrorComponent) { 93 | return ; 94 | } 95 | return {error.toString()}; 96 | } 97 | 98 | let Component: React.FC> = StringValue; 99 | 100 | switch (dataType) { 101 | case "boolean": 102 | Component = BooleanValue; 103 | break; 104 | case "datetime": 105 | Component = DatetimeValue; 106 | break; 107 | case "decimal": 108 | Component = DecimalValue; 109 | break; 110 | case "integer": 111 | Component = IntegerValue; 112 | break; 113 | case "url": 114 | Component = UrlValue; 115 | break; 116 | default: 117 | Component = StringValue; 118 | } 119 | 120 | // eslint-disable-next-line react/jsx-props-no-spreading 121 | return ; 122 | } 123 | 124 | Value.defaultProps = { 125 | autosave: false, 126 | edit: false, 127 | }; 128 | -------------------------------------------------------------------------------- /src/components/value/integer/index.tsx: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright Inrupt Inc. 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal in 6 | // the Software without restriction, including without limitation the rights to use, 7 | // copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the 8 | // Software, and to permit persons to whom the Software is furnished to do so, 9 | // subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in 12 | // all copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 16 | // PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 17 | // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 18 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 19 | // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | // 21 | 22 | import React, { useState, useEffect, useContext } from "react"; 23 | import { 24 | setThing, 25 | saveSolidDatasetAt, 26 | getSourceUrl, 27 | hasResourceInfo, 28 | setInteger, 29 | } from "@inrupt/solid-client"; 30 | import { SessionContext } from "../../../context/sessionContext"; 31 | 32 | import { updateDataset, useProperty } from "../../../helpers"; 33 | import type { Props } from ".."; 34 | 35 | type IntegerProps = Omit; 36 | 37 | const IntegerValue: React.FC = (props: IntegerProps) => { 38 | const { 39 | thing: propThing, 40 | solidDataset: propDataset, 41 | property: propProperty, 42 | properties: propProperties, 43 | saveDatasetTo, 44 | onSave, 45 | onError, 46 | edit, 47 | autosave, 48 | inputProps, 49 | ...other 50 | } = props; 51 | 52 | const { fetch } = useContext(SessionContext); 53 | 54 | const { 55 | value: thingValue, 56 | thing, 57 | property, 58 | dataset, 59 | setDataset, 60 | error, 61 | } = useProperty({ 62 | dataset: propDataset, 63 | thing: propThing, 64 | type: "integer", 65 | property: propProperty, 66 | properties: propProperties, 67 | }); 68 | 69 | useEffect(() => { 70 | if (error && onError) { 71 | onError(error); 72 | } 73 | }, [error, onError]); 74 | 75 | const formattedValue: string = thingValue?.toString() ?? ""; 76 | 77 | const [value, setValue] = useState(formattedValue); 78 | 79 | /* Save Value value in the pod */ 80 | const saveHandler = async (e: React.ChangeEvent) => { 81 | if ( 82 | formattedValue !== value && 83 | thing && 84 | dataset && 85 | e.target.reportValidity() 86 | ) { 87 | const updatedResource = setInteger( 88 | thing, 89 | property, 90 | parseInt(value as string, 10), 91 | ); 92 | try { 93 | let savedDataset; 94 | if (saveDatasetTo) { 95 | savedDataset = await saveSolidDatasetAt( 96 | saveDatasetTo, 97 | setThing(dataset, updatedResource), 98 | { fetch }, 99 | ); 100 | await updateDataset(saveDatasetTo, setDataset); 101 | } else if (hasResourceInfo(dataset)) { 102 | savedDataset = await saveSolidDatasetAt( 103 | getSourceUrl(dataset), 104 | setThing(dataset, updatedResource), 105 | { fetch }, 106 | ); 107 | await updateDataset(getSourceUrl(dataset), setDataset); 108 | } else if (onError) { 109 | onError( 110 | new Error("Please provide saveDatasetTo location for new data"), 111 | ); 112 | } 113 | 114 | if (!error && onSave) { 115 | onSave(savedDataset, updatedResource); 116 | } 117 | } catch (saveError) { 118 | if (onError) { 119 | onError(saveError as Error); 120 | } 121 | } 122 | } 123 | }; 124 | 125 | return ( 126 | <> 127 | { 128 | // eslint-disable-next-line react/jsx-props-no-spreading 129 | !edit && dataset && thing && {`${value}`} 130 | } 131 | {edit && dataset && thing && ( 132 | { 138 | setValue(e.target.value); 139 | }} 140 | onBlur={(e) => autosave && saveHandler(e)} 141 | value={value} 142 | /> 143 | )} 144 | 145 | ); 146 | }; 147 | 148 | IntegerValue.defaultProps = { 149 | autosave: false, 150 | edit: false, 151 | }; 152 | 153 | export default IntegerValue; 154 | -------------------------------------------------------------------------------- /src/components/value/string/index.tsx: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright Inrupt Inc. 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal in 6 | // the Software without restriction, including without limitation the rights to use, 7 | // copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the 8 | // Software, and to permit persons to whom the Software is furnished to do so, 9 | // subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in 12 | // all copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 16 | // PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 17 | // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 18 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 19 | // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | // 21 | 22 | import React, { useState, useEffect, useContext } from "react"; 23 | import { 24 | setThing, 25 | saveSolidDatasetAt, 26 | getSourceUrl, 27 | hasResourceInfo, 28 | setStringWithLocale, 29 | setStringNoLocale, 30 | } from "@inrupt/solid-client"; 31 | import { SessionContext } from "../../../context/sessionContext"; 32 | import { updateDataset, useProperty } from "../../../helpers"; 33 | import type { Props } from ".."; 34 | 35 | type StringProps = Omit; 36 | 37 | const StringValue: React.FC = (props: StringProps) => { 38 | const { 39 | thing: propThing, 40 | solidDataset: propDataset, 41 | property: propProperty, 42 | properties: propProperties, 43 | saveDatasetTo, 44 | locale, 45 | onSave, 46 | onError, 47 | edit, 48 | autosave, 49 | inputProps, 50 | ...other 51 | } = props; 52 | 53 | const { fetch } = useContext(SessionContext); 54 | 55 | const { 56 | value: thingValue, 57 | thing, 58 | property, 59 | dataset, 60 | setDataset, 61 | error, 62 | } = useProperty({ 63 | dataset: propDataset, 64 | thing: propThing, 65 | type: "string", 66 | property: propProperty, 67 | properties: propProperties, 68 | locale, 69 | }); 70 | 71 | useEffect(() => { 72 | if (error && onError) { 73 | onError(error); 74 | } 75 | }, [error, onError]); 76 | 77 | const formattedValue: string = thingValue?.toString() ?? ""; 78 | 79 | const [value, setValue] = useState(formattedValue); 80 | 81 | /* Save Value value in the pod */ 82 | const saveHandler = async (e: React.ChangeEvent) => { 83 | if ( 84 | formattedValue !== value && 85 | thing && 86 | dataset && 87 | e.target.reportValidity() 88 | ) { 89 | let updatedResource; 90 | if (locale) { 91 | updatedResource = setStringWithLocale( 92 | thing, 93 | property, 94 | value as string, 95 | locale, 96 | ); 97 | } else { 98 | updatedResource = setStringNoLocale(thing, property, value as string); 99 | } 100 | try { 101 | let savedDataset; 102 | if (saveDatasetTo) { 103 | savedDataset = await saveSolidDatasetAt( 104 | saveDatasetTo, 105 | setThing(dataset, updatedResource), 106 | { fetch }, 107 | ); 108 | await updateDataset(saveDatasetTo, setDataset); 109 | } else if (hasResourceInfo(dataset)) { 110 | savedDataset = await saveSolidDatasetAt( 111 | getSourceUrl(dataset), 112 | setThing(dataset, updatedResource), 113 | { fetch }, 114 | ); 115 | await updateDataset(getSourceUrl(dataset), setDataset); 116 | } else if (onError) { 117 | onError( 118 | new Error("Please provide saveDatasetTo location for new data"), 119 | ); 120 | } 121 | 122 | if (!error && onSave) { 123 | onSave(savedDataset, updatedResource); 124 | } 125 | } catch (saveError) { 126 | if (onError) { 127 | onError(saveError as Error); 128 | } 129 | } 130 | } 131 | }; 132 | 133 | return ( 134 | <> 135 | { 136 | // eslint-disable-next-line react/jsx-props-no-spreading 137 | !edit && dataset && thing && {value} 138 | } 139 | {edit && dataset && thing && ( 140 | { 145 | setValue(e.target.value); 146 | }} 147 | onBlur={(e) => autosave && saveHandler(e)} 148 | value={value} 149 | /> 150 | )} 151 | 152 | ); 153 | }; 154 | 155 | StringValue.defaultProps = { 156 | autosave: false, 157 | edit: false, 158 | }; 159 | 160 | export default StringValue; 161 | -------------------------------------------------------------------------------- /src/components/value/url/index.tsx: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright Inrupt Inc. 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal in 6 | // the Software without restriction, including without limitation the rights to use, 7 | // copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the 8 | // Software, and to permit persons to whom the Software is furnished to do so, 9 | // subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in 12 | // all copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 16 | // PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 17 | // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 18 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 19 | // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | // 21 | 22 | import React, { useState, useEffect, useContext } from "react"; 23 | import { 24 | setThing, 25 | saveSolidDatasetAt, 26 | getSourceUrl, 27 | hasResourceInfo, 28 | setUrl, 29 | } from "@inrupt/solid-client"; 30 | import { SessionContext } from "../../../context/sessionContext"; 31 | 32 | import { updateDataset, useProperty } from "../../../helpers"; 33 | import type { Props } from ".."; 34 | 35 | type UrlProps = Omit; 36 | 37 | const UrlValue: React.FC = (props: UrlProps) => { 38 | const { 39 | thing: propThing, 40 | solidDataset: propDataset, 41 | property: propProperty, 42 | properties: propProperties, 43 | saveDatasetTo, 44 | onSave, 45 | onError, 46 | edit, 47 | autosave, 48 | inputProps, 49 | ...other 50 | } = props; 51 | 52 | const { fetch } = useContext(SessionContext); 53 | 54 | const { 55 | value: thingValue, 56 | thing, 57 | property, 58 | dataset, 59 | setDataset, 60 | error, 61 | } = useProperty({ 62 | dataset: propDataset, 63 | thing: propThing, 64 | type: "url", 65 | property: propProperty, 66 | properties: propProperties, 67 | }); 68 | 69 | useEffect(() => { 70 | if (error && onError) { 71 | onError(error); 72 | } 73 | }, [error, onError]); 74 | 75 | const formattedValue: string = thingValue?.toString() ?? ""; 76 | 77 | const [value, setValue] = useState(formattedValue); 78 | 79 | /* Save Value value in the pod */ 80 | const saveHandler = async (e: React.ChangeEvent) => { 81 | if ( 82 | formattedValue !== value && 83 | thing && 84 | dataset && 85 | e.target.reportValidity() 86 | ) { 87 | const updatedResource = setUrl(thing, property, value as string); 88 | try { 89 | let savedDataset; 90 | if (saveDatasetTo) { 91 | savedDataset = await saveSolidDatasetAt( 92 | saveDatasetTo, 93 | setThing(dataset, updatedResource), 94 | { fetch }, 95 | ); 96 | await updateDataset(saveDatasetTo, setDataset); 97 | } else if (hasResourceInfo(dataset)) { 98 | savedDataset = await saveSolidDatasetAt( 99 | getSourceUrl(dataset), 100 | setThing(dataset, updatedResource), 101 | { fetch }, 102 | ); 103 | await updateDataset(getSourceUrl(dataset), setDataset); 104 | } else if (onError) { 105 | onError( 106 | new Error("Please provide saveDatasetTo location for new data"), 107 | ); 108 | } 109 | 110 | if (!error && onSave) { 111 | onSave(savedDataset, updatedResource); 112 | } 113 | } catch (saveError) { 114 | if (onError) { 115 | onError(saveError as Error); 116 | } 117 | } 118 | } 119 | }; 120 | 121 | return ( 122 | <> 123 | { 124 | // eslint-disable-next-line react/jsx-props-no-spreading 125 | !edit && dataset && thing && {`${value}`} 126 | } 127 | {edit && dataset && thing && ( 128 | { 133 | setValue(e.target.value); 134 | }} 135 | onBlur={(e) => autosave && saveHandler(e)} 136 | value={value} 137 | /> 138 | )} 139 | 140 | ); 141 | }; 142 | 143 | UrlValue.defaultProps = { 144 | autosave: false, 145 | edit: false, 146 | }; 147 | 148 | export default UrlValue; 149 | -------------------------------------------------------------------------------- /src/components/video/__snapshots__/index.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Video component Video functional tests When getUrl returns null, with an errorComponent, should render the error 1`] = ` 4 | 5 | 6 | Error: No value found for property. 7 | 8 | 9 | `; 10 | 11 | exports[`Video component Video snapshots does not render default loading message if loadingComponent is null 1`] = ``; 12 | 13 | exports[`Video component Video snapshots matches snapshot with additional props for video and input 1`] = ` 14 | 15 | 27 | `; 28 | 29 | exports[`Video component Video snapshots matches snapshot with standard props 1`] = ` 30 | 31 | 37 | `; 38 | 39 | exports[`Video component Video snapshots renders an error message if an errorComponent is provided 1`] = ` 40 | 41 | 44 | Error: No value found for property. 45 | 46 | 47 | `; 48 | 49 | exports[`Video component Video snapshots renders default error message if there is an error and no errorComponent is passed 1`] = ` 50 | 51 | 52 | Error: No value found for property. 53 | 54 | 55 | `; 56 | 57 | exports[`Video component Video snapshots renders default loading message if thing is undefined and there is no error 1`] = ` 58 | 59 | 60 | fetching data in progress 61 | 62 | 63 | `; 64 | 65 | exports[`Video component Video snapshots renders loading component if passed and thing is undefined and there is no error 1`] = ` 66 | 67 | 70 | loading... 71 | 72 | 73 | `; 74 | -------------------------------------------------------------------------------- /src/components/video/index.tsx: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright Inrupt Inc. 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal in 6 | // the Software without restriction, including without limitation the rights to use, 7 | // copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the 8 | // Software, and to permit persons to whom the Software is furnished to do so, 9 | // subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in 12 | // all copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 16 | // PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 17 | // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 18 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 19 | // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | // 21 | 22 | import type { ReactElement } from "react"; 23 | import React, { useEffect, useState, useContext } from "react"; 24 | import type { CommonProperties } from "../../helpers"; 25 | import { overwriteFile, useProperty } from "../../helpers"; 26 | import { SessionContext } from "../../context/sessionContext"; 27 | import useFile from "../../hooks/useFile"; 28 | 29 | export type Props = { 30 | maxSize?: number; 31 | inputProps?: React.InputHTMLAttributes; 32 | errorComponent?: React.ComponentType<{ error: Error }>; 33 | loadingComponent?: React.ComponentType | null; 34 | } & CommonProperties & 35 | React.VideoHTMLAttributes; 36 | 37 | /** 38 | * Fetches and displays a video, from a src found at a given property of a given Thing. Can also be used to upload a new/replacement video file. 39 | */ 40 | export function Video({ 41 | property: propProperty, 42 | properties: propProperties, 43 | thing: propThing, 44 | edit, 45 | autosave, 46 | onSave, 47 | onError, 48 | maxSize, 49 | inputProps, 50 | errorComponent: ErrorComponent, 51 | loadingComponent: LoadingComponent, 52 | ...videoOptions 53 | }: Props): ReactElement | null { 54 | const { fetch } = useContext(SessionContext); 55 | 56 | const values = useProperty({ 57 | thing: propThing, 58 | property: propProperty, 59 | properties: propProperties, 60 | type: "url", 61 | }); 62 | 63 | const { value, thing, error: thingError } = values; 64 | let valueError; 65 | if (!edit && !value) { 66 | valueError = new Error("No value found for property."); 67 | } 68 | 69 | const isFetchingThing = !thing && !thingError; 70 | 71 | const [error, setError] = useState( 72 | thingError ?? valueError, 73 | ); 74 | 75 | useEffect(() => { 76 | if (error) { 77 | if (onError) { 78 | onError(error); 79 | } 80 | } 81 | }, [error, onError, ErrorComponent]); 82 | 83 | const [videoObjectUrl, setVideoObjectUrl] = useState(""); 84 | 85 | const { 86 | data, 87 | error: videoError, 88 | inProgress: fetchingVideoInProgress, 89 | } = useFile(value as string); 90 | 91 | useEffect(() => { 92 | if (fetchingVideoInProgress) { 93 | return; 94 | } 95 | if (videoError) { 96 | setError(videoError); 97 | return; 98 | } 99 | const videoObjectURL = data && URL.createObjectURL(data); 100 | if (videoObjectURL) { 101 | setVideoObjectUrl(videoObjectURL); 102 | } 103 | }, [data, fetchingVideoInProgress, videoError]); 104 | 105 | const handleChange = async (input: EventTarget & HTMLInputElement) => { 106 | const fileList = input.files; 107 | if (autosave && fileList && fileList.length > 0 && value) { 108 | const newObjectUrl = await overwriteFile( 109 | value as string, 110 | fileList[0], 111 | input, 112 | fetch, 113 | maxSize, 114 | onSave, 115 | onError, 116 | ); 117 | if (newObjectUrl) { 118 | setVideoObjectUrl(newObjectUrl); 119 | } 120 | } 121 | }; 122 | 123 | let videoComponent = null; 124 | 125 | if (isFetchingThing || fetchingVideoInProgress) { 126 | let loader: JSX.Element | null = (LoadingComponent && ( 127 | 128 | )) || fetching data in progress; 129 | if (LoadingComponent === null) { 130 | loader = null; 131 | } 132 | return loader; 133 | } 134 | 135 | if (error) { 136 | videoComponent = ErrorComponent ? ( 137 | 138 | ) : ( 139 | {error.toString()} 140 | ); 141 | } else if (value) { 142 | videoComponent = ( 143 | /* eslint jsx-a11y/media-has-caption: 0, react/jsx-props-no-spreading: 0 */ 144 |
; 34 | if (inProgress) return
loading...
; 35 | return data; 36 | } 37 | `} 38 | /> 39 | -------------------------------------------------------------------------------- /stories/hooks/useSession.stories.mdx: -------------------------------------------------------------------------------- 1 | import { Meta, Source } from "@storybook/addon-docs/blocks"; 2 | 3 | 4 | 5 | # useSession 6 | 7 | A hook to access the current session from context, along with related values. Should be used within a [`SessionProvider`](/?path=/docs/authentication-session-provider). 8 | 9 | ``` 10 | const { 11 | session, 12 | sessionRequestInProgress, 13 | fetch, 14 | login, 15 | logout, 16 | } = useSession() 17 | ``` 18 | 19 | ## Return Values 20 | 21 | - `session`: the [Session](https://docs.inrupt.com/developer-tools/api/javascript/solid-client-authn-browser/classes/_session_.session.html) retrieved from context 22 | - `sessionRequestInProgress`: a boolean indicating whether a session request (e.g. login) is currently in progress (true) or not (false) 23 | - `fetch()`: the fetch method of the current session 24 | - `login(options)`: function to [login](https://docs.inrupt.com/developer-tools/api/javascript/solid-client-authn-browser/classes/_session_.session.html#login). Takes optional object of options, returns a promise 25 | - `logout()`: function to [logout](https://docs.inrupt.com/developer-tools/api/javascript/solid-client-authn-browser/classes/_session_.session.html#logout), returns a promise 26 | 27 | ## Example Usage 28 | 29 | This example gets the current session, and uses it to render different components depending on whether the user is logged in. 30 | 31 | ; 40 | } 41 | return ; 42 | } 43 | `} 44 | /> 45 | -------------------------------------------------------------------------------- /stories/hooks/useThing.stories.mdx: -------------------------------------------------------------------------------- 1 | import { Meta, Source } from "@storybook/addon-docs/blocks"; 2 | import { useThing } from "../../src"; 3 | 4 | 5 | 6 | # useThing 7 | 8 | A hook to access the nearest [Thing](https://docs.inrupt.com/developer-tools/javascript/client-libraries/reference/glossary/#term-Thing) from context if no arguments are supplied, or to request a [Thing](https://docs.inrupt.com/developer-tools/javascript/client-libraries/reference/glossary/#term-Thing) from a given [IRI](https://docs.inrupt.com/developer-tools/javascript/client-libraries/reference/glossary/#term-IRI). 9 | 10 | Should be located within a [`SessionProvider`](/?path=/docs/authentication-session-provider) if using to fetch. 11 | 12 | ``` 13 | const { thing, error } = useThing(datasetIri, thingIri, options) 14 | ``` 15 | 16 | ## Parameters 17 | 18 | - `datasetIri`: (optional) a [Dataset](https://docs.inrupt.com/developer-tools/javascript/client-libraries/reference/glossary/#term-SolidDataset) [IRI](https://docs.inrupt.com/developer-tools/javascript/client-libraries/reference/glossary/#term-IRI) to fetch the [Thing](https://docs.inrupt.com/developer-tools/javascript/client-libraries/reference/glossary/#term-Thing) from, if both `datasetIri` and `thingIri` are supplied. Uses the [Dataset](https://docs.inrupt.com/developer-tools/javascript/client-libraries/reference/glossary/#term-SolidDataset) from context if omitted. 19 | - `thingIri`: (optional) a [Thing](https://docs.inrupt.com/developer-tools/javascript/client-libraries/reference/glossary/#term-Thing) [IRI](https://docs.inrupt.com/developer-tools/javascript/client-libraries/reference/glossary/#term-IRI) to fetch the [Thing](https://docs.inrupt.com/developer-tools/javascript/client-libraries/reference/glossary/#term-Thing) from. Uses the [Thing](https://docs.inrupt.com/developer-tools/javascript/client-libraries/reference/glossary/#term-Thing) from context if omitted. 20 | - `options`: (optional) additional options to pass to the [dataset request](https://docs.inrupt.com/developer-tools/api/javascript/solid-client/modules/_resource_soliddataset_.html#getsoliddataset), if `datasetIri` is supplied. Unless an alternative id provided, `options.fetch` is included using the current session automatically. 21 | 22 | ## Return Values 23 | 24 | - `thing`: the [Thing](https://docs.inrupt.com/developer-tools/javascript/client-libraries/reference/glossary/#term-Thing) retrieved from context or requests using given [IRI](https://docs.inrupt.com/developer-tools/javascript/client-libraries/reference/glossary/#term-IRI) (undefined if loading/missing) 25 | - `error`: the error thrown by the request (or undefined) 26 | 27 | ## Example Usage 28 | 29 | This example uses the datasetIri and thingIri values to fetch a [Thing](https://docs.inrupt.com/developer-tools/javascript/client-libraries/reference/glossary/#term-Thing), and pass to a child component when loaded. 30 | 31 | failed to load
; 40 | if (!thing) return
loading...
; 41 | return ; 42 | } 43 | `} 44 | /> 45 | -------------------------------------------------------------------------------- /stories/intro.stories.mdx: -------------------------------------------------------------------------------- 1 | import { Meta } from "@storybook/addon-docs/blocks"; 2 | 3 | 4 | 5 | # Solid React SDK - solid-ui-react 6 | 7 | `solid-ui-react` is a library of React components made to make development 8 | with [Solid](https://solidproject.org/) simple. 9 | 10 | The goal of this library is to provide highly flexible, simple components that 11 | you can use in any browser-based Solid project and customize to match the UI of 12 | your application. It uses the 13 | [Inrupt Javascript Client Libraries](https://docs.inrupt.com/developer-tools/javascript/client-libraries/) 14 | for authentication and data manipulation. It is built with typescript and 15 | exports its types. 16 | 17 | In this library, you will find: 18 | 19 | - Authentication components, which can help you log in and log out of an identiy 20 | provider 21 | - Data components, which can help you view and edit individual properties or 22 | view a sortable and filterable table of many things 23 | - Context providers and hooks to help simplify development when using functional 24 | programming 25 | 26 | You can view the project on [GitHub](https://github.com/inrupt/solid-ui-react), or 27 | [view an example application](https://github.com/inrupt/solid-ui-react-example-project). 28 | -------------------------------------------------------------------------------- /stories/providers/thingProvider.stories.tsx: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright Inrupt Inc. 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal in 6 | // the Software without restriction, including without limitation the rights to use, 7 | // copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the 8 | // Software, and to permit persons to whom the Software is furnished to do so, 9 | // subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in 12 | // all copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 16 | // PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 17 | // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 18 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 19 | // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | // 21 | 22 | import type { ReactElement } from "react"; 23 | import React, { useContext, useState, useEffect } from "react"; 24 | import type { SolidDataset, WithResourceInfo } from "@inrupt/solid-client"; 25 | import { 26 | getStringNoLocale, 27 | addStringNoLocale, 28 | createThing, 29 | getSolidDataset, 30 | } from "@inrupt/solid-client"; 31 | import { DatasetProvider } from "../../src/context/datasetContext"; 32 | import ThingContext, { ThingProvider } from "../../src/context/thingContext"; 33 | import config from "../config"; 34 | 35 | const { host } = config(); 36 | 37 | export default { 38 | title: "Providers/Thing Provider", 39 | component: ThingProvider, 40 | parameters: { 41 | docs: { 42 | description: { 43 | component: `Used to provide a [Thing](https://docs.inrupt.com/developer-tools/javascript/client-libraries/reference/glossary/#term-Thing) to child components through context, as used by various provided components and the [useThing](/?path=/docs/hooks-usething) hook.`, 44 | }, 45 | }, 46 | }, 47 | argTypes: { 48 | thing: { 49 | description: `A [Thing](https://docs.inrupt.com/developer-tools/javascript/client-libraries/reference/glossary/#term-Thing) to be used by the provider`, 50 | control: { type: null }, 51 | }, 52 | thingUrl: { 53 | description: `A url to retrieve the [Thing](https://docs.inrupt.com/developer-tools/javascript/client-libraries/reference/glossary/#term-Thing) from, if \`Thing\` is not provided. Uses the Dataset from context.`, 54 | control: { type: null }, 55 | }, 56 | }, 57 | }; 58 | 59 | interface IExampleComponentWithThingUrl { 60 | property?: string; 61 | } 62 | 63 | function ExampleComponentWithThingUrl( 64 | props: IExampleComponentWithThingUrl, 65 | ): ReactElement { 66 | const { property: propertyUrl } = props; 67 | 68 | const [property, setProperty] = useState("fetching in progress"); 69 | 70 | const thingContext = useContext(ThingContext); 71 | const { thing } = thingContext; 72 | 73 | useEffect(() => { 74 | if (thing) { 75 | const fetchedProperty = getStringNoLocale(thing, propertyUrl as string); 76 | 77 | if (fetchedProperty) { 78 | setProperty(fetchedProperty); 79 | } 80 | } 81 | }, [propertyUrl, thing]); 82 | 83 | return ( 84 |
85 |

{property}

86 |
87 | ); 88 | } 89 | 90 | ExampleComponentWithThingUrl.defaultProps = { 91 | property: "http://www.w3.org/2006/vcard/ns#note", 92 | }; 93 | 94 | function ExampleComponentWithThing(): ReactElement { 95 | const [property, setProperty] = useState("fetching in progress"); 96 | const thingContext = useContext(ThingContext); 97 | const { thing } = thingContext; 98 | 99 | useEffect(() => { 100 | if (thing) { 101 | const fetchedProperty = getStringNoLocale( 102 | thing, 103 | "http://xmlns.com/foaf/0.1/name", 104 | ); 105 | if (fetchedProperty) { 106 | setProperty(fetchedProperty); 107 | } 108 | } 109 | }, [thing]); 110 | 111 | return ( 112 |
113 |

{property}

114 |
115 | ); 116 | } 117 | 118 | export function WithLocalThing(): ReactElement { 119 | const property = "http://xmlns.com/foaf/0.1/name"; 120 | const name = "example value"; 121 | 122 | const exampleThing = addStringNoLocale(createThing(), property, name); 123 | 124 | return ( 125 | 126 | 127 | 128 | ); 129 | } 130 | 131 | interface IWithThingUrl { 132 | datasetUrl: string; 133 | thingUrl: string; 134 | property: string; 135 | } 136 | 137 | export function WithThingUrl(props: IWithThingUrl): ReactElement { 138 | const { datasetUrl, thingUrl, property } = props; 139 | const [litDataset, setSolidDataset] = useState< 140 | SolidDataset & WithResourceInfo 141 | >(); 142 | 143 | const setDataset = async (url: string) => { 144 | await getSolidDataset(url).then((result) => { 145 | setSolidDataset(result); 146 | }); 147 | }; 148 | 149 | useEffect(() => { 150 | // eslint-disable-next-line no-void 151 | void setDataset(datasetUrl); 152 | }, [datasetUrl]); 153 | 154 | if (litDataset) { 155 | return ( 156 | 157 | 158 | 159 | 160 | 161 | ); 162 | } 163 | return no dataset; 164 | } 165 | 166 | WithThingUrl.args = { 167 | datasetUrl: `${host}/example.ttl`, 168 | thingUrl: `${host}/example.ttl#me`, 169 | property: "http://www.w3.org/2006/vcard/ns#note", 170 | }; 171 | -------------------------------------------------------------------------------- /stories/usage.stories.mdx: -------------------------------------------------------------------------------- 1 | import { Meta } from "@storybook/addon-docs/blocks"; 2 | 3 | 4 | 5 | # solid-ui-react 6 | 7 | In this guide, we will: 8 | 9 | 1. Set up authentication 10 | 1. View and edit data in your user profile 11 | 12 | You can also view an example application 13 | [on GitHub](https://github.com/inrupt/solid-ui-react-example-project). 14 | 15 | ## Authentication 16 | 17 | Once you have created a react application, allowing users to register or log in 18 | is simple. You will want to install the SDK: 19 | 20 | ``` 21 | npm i -S @inrupt/solid-ui-react 22 | ``` 23 | 24 | And then import two components: 25 | 26 | ``` 27 | import { SessionProvider, LoginButton } from "@inrupt/solid-ui-react"; 28 | ``` 29 | 30 | You can then wrap your application in the SessionProvider that you imported. 31 | Give it a unique sessionId. Finally, anywhere in your component tree, as long 32 | as you have a SessionProvider above it, you can use a 33 | [LoginButton](/?path=/story/authentication-login-button--with-children). 34 | 35 | ``` 36 | 37 | 41 | 42 | ``` 43 | 44 | From there, you can click "login" to be redirected to the OIDC issuer provided. 45 | You can either log in or register a pod once redirected to the identity provider's 46 | login page. 47 | 48 | ## Reading and Writing Data 49 | 50 | Now that you have a pod, you can begin reading data. To do this, we will import 51 | another pair of components: a `CombinedDataProvider`, which will handle fetching 52 | a dataset for you, and a `Text` component to display that data. 53 | 54 | ``` 55 | import { useSession, CombinedDataProvider, Text } from "@inrupt/solid-ui-react"; 56 | ``` 57 | 58 | In your component tree, still within the context of the `SessionProvider`, first 59 | you can read the WebId of your session with the `useSession` hook. 60 | A WebId is a URL that links to the profile document stored in your pod - 61 | which was likely automatically generated for you. A CombinedDataProvider can 62 | then use that WebId to automatically fetch data, and the Text component can display 63 | that data. 64 | 65 | For further information on "datasets" and "things", refer to the 66 | [solid-client-js documentation](https://docs.inrupt.com/developer-tools/api/javascript/solid-client/). 67 | 68 | ``` 69 | function Name() { 70 | const { session, sessionRequestInProgress } = useSession(); 71 | if (sessionRequestInProgress) return "Loading..."; 72 | 73 | const webId = session.info.webId; 74 | 75 | 76 | 77 | 78 | } 79 | ``` 80 | 81 | The `Text` component, like most other data components, will automatically refer 82 | to a DatasetContext or CombinedDataContext if there is one in the heirarchy. This 83 | code will display your full name (assuming a standard profile). 84 | 85 | To edit this data, set the `edit` and `autosave` properties to true, which will 86 | replace the text with an input box. With `autosave`, the data will be automatically 87 | saved back to the server when you click outside of the textbox: 88 | 89 | ``` 90 | function Name() { 91 | const { session, sessionRequestInProgress } = useSession(); 92 | if (sessionRequestInProgress) return "Loading..."; 93 | 94 | const webId = session.info.webId; 95 | 96 | 97 | 98 | 99 | } 100 | ``` 101 | 102 | View the Component documentation for more examples, such as for images, links, 103 | and non-string values such as booleans and datetimes. 104 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "rootDir": ".", 4 | // https://github.com/microsoft/TypeScript/wiki/Performance#controlling-types-inclusion 5 | "types": ["jest", "node"], 6 | "baseUrl": ".", 7 | "outDir": "./dist/", 8 | "target": "es2017", 9 | "lib": [ 10 | "dom", 11 | "dom.iterable", 12 | "esnext" 13 | ], 14 | "allowJs": true, 15 | "skipLibCheck": true, 16 | "strict": true, 17 | "forceConsistentCasingInFileNames": true, 18 | "esModuleInterop": true, 19 | "module": "es6", 20 | "moduleResolution": "node", 21 | "resolveJsonModule": true, 22 | "jsx": "react", 23 | "sourceMap": true, 24 | "declaration": true, 25 | "declarationMap": true 26 | }, 27 | "exclude": [ 28 | "**/node_modules", 29 | "dist", 30 | "**/__mocks__/**", 31 | "**/__snapshots__/**" 32 | ], 33 | "include": [ 34 | "src/**/*.ts", 35 | "src/**/*.tsx", 36 | "stories/**/*.tsx", 37 | "jest.config.ts", 38 | "jest.setup.ts", 39 | ] 40 | } 41 | --------------------------------------------------------------------------------