diff --git a/.env b/.env new file mode 100644 index 0000000000..ff9f481697 --- /dev/null +++ b/.env @@ -0,0 +1,17 @@ +# Disable all mentioning of fiat values and token prices. +# VITE_DISABLE_FIAT=1 + +# Display an organisation label in the network bar. +# VITE_ORGANISATION="© Parity Technologies" + +# Provide a privacy policy url in the network bar. +# VITE_PRIVACY_URL=https://www.parity.io/privacy/ + +# Toggle i18n to be in debug mode. Not debug by default. +# VITE_DEBUG_I18N=1 + +# Provide a disclaimer url in the network bar. +# VITE_DISCLAIMER_URL=https://parity.io/disclaimer/ + +# Provide a legal disclosure url in the network bar. +# VITE_LEGAL_DISCLOSURES_URL=https://polkadot.network/legal-disclosures/ \ No newline at end of file diff --git a/.eslintignore b/.eslintignore index 51a618d157..6567f86360 100644 --- a/.eslintignore +++ b/.eslintignore @@ -12,4 +12,7 @@ *.html *.webmanifest LICENSE -build \ No newline at end of file +dist +build +vite.config.ts +public/lottie/player.js diff --git a/.eslintrc.json b/.eslintrc.json index 489d2bbd7c..0a2c0477a4 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -5,6 +5,7 @@ }, "extends": [ "eslint:recommended", + "plugin:import/recommended", "plugin:react/recommended", "plugin:@typescript-eslint/recommended", "airbnb", @@ -21,9 +22,15 @@ "sourceType": "module", "project": "./tsconfig.json" }, - "plugins": ["react", "@typescript-eslint", "prefer-arrow-functions"], + "plugins": [ + "react", + "@typescript-eslint", + "prefer-arrow-functions", + "unused-imports" + ], "rules": { - "import/no-webpack-loader-syntax": "off", + // NOTE: These rules are being reviewed and comments justifying their deactivation will be + // added. "react/require-default-props": "off", "react/no-access-state-in-setstate": "off", "react/destructuring-assignment": "off", @@ -36,21 +43,29 @@ "react/static-property-placement": "off", "no-unused-vars": "off", "no-param-reassign": "off", - "no-plusplus": "off", - "no-continue": "off", - "no-underscore-dangle": "off", "no-restricted-syntax": "off", "@typescript-eslint/no-empty-function": "off", "no-use-before-define": "off", "@typescript-eslint/no-explicit-any": "off", - "@typescript-eslint/no-use-before-define": "off", - "import/prefer-default-export": "off", - "consistent-return": "off", "no-promise-executor-return": "off", "prefer-destructuring": "off", - "@typescript-eslint/ban-ts-comment": "off", "no-nested-ternary": "off", - "no-shadow": "off", + // `continue` statements cut down on conditional nesting and improve readability where it is + // used in this project. Conditionals would further bloat the code. + "no-continue": "off", + // Unary operators are not impacting code as semi-colons are currently enforced. + "no-plusplus": "off", + // Default imports cause naming inconsistencies to imports when component names are changed. + "import/prefer-default-export": "off", + + "unused-imports/no-unused-imports": "error", + "@typescript-eslint/consistent-type-imports": [ + "error", + { + "prefer": "type-imports", + "fixStyle": "separate-type-imports" + } + ], "semi": [2, "always"], "react/jsx-filename-extension": [ "warn", diff --git a/.github/dependabot.yml b/.github/dependabot.yml index b72ed23708..c7d3172c1e 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -3,14 +3,10 @@ version: 2 updates: - package-ecosystem: npm directory: '/' - labels: - - 'automerge' schedule: interval: daily - open-pull-requests-limit: 4 + open-pull-requests-limit: 10 - package-ecosystem: github-actions directory: '/' - labels: - - 'automerge' schedule: interval: daily diff --git a/.github/workflows/auto-merge.yml b/.github/workflows/auto-merge.yml new file mode 100644 index 0000000000..0fd2326124 --- /dev/null +++ b/.github/workflows/auto-merge.yml @@ -0,0 +1,17 @@ +name: Dependabot Auto Merge + +on: pull_request + +permissions: + contents: write + pull-requests: write + +jobs: + auto-merge: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: ahmadnassri/action-dependabot-auto-merge@v2 + with: + target: minor + github-token: ${{ secrets.DEPENDABOT_TOKEN }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 80ac0f86b9..49aa13e4a2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,10 +1,10 @@ -name: Staking Dashboard CI +name: CI on: push: - branches: [master] + branches: [main] pull_request: - branches: [master] + branches: [main] jobs: check-license-lines: @@ -13,27 +13,34 @@ jobs: - uses: actions/checkout@master - name: Check License Lines uses: kt3k/license_checker@v1.0.6 + validate-locales: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Use Node.js 16.x + uses: actions/setup-node@v4 + with: + node-version: 16.x + - run: yarn run locale:validate build: runs-on: ubuntu-latest - strategy: matrix: - node-version: [14.x, 16.x, 18.x] - + node-version: [18.x, 19.x, 20.x] steps: - - uses: actions/checkout@v3.1.0 + - uses: actions/checkout@v4 - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} - run: yarn install - run: yarn build - run: yarn lint - # - run: yarn test # No reason to check for tests now since they do not exist + - run: yarn test all: # This job ensures that all jobs above (now we have just build) are successful. - needs: [check-license-lines, build] + needs: [check-license-lines, build, validate-locales] runs-on: ubuntu-latest steps: - run: echo Success diff --git a/.github/workflows/gh-publish.yml b/.github/workflows/gh-publish.yml new file mode 100644 index 0000000000..97d2397d4f --- /dev/null +++ b/.github/workflows/gh-publish.yml @@ -0,0 +1,23 @@ +name: GitHub Pages Publish + +on: + push: + branches: [main] + +jobs: + gh-deploy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 18 + registry-url: https://registry.npmjs.org + - run: yarn install + - name: Build + working-directory: '.' + run: yarn build:pages + - name: Deploy + uses: JamesIves/github-pages-deploy-action@v4 + with: + folder: build diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml new file mode 100644 index 0000000000..9e93ec92d7 --- /dev/null +++ b/.github/workflows/release-please.yml @@ -0,0 +1,21 @@ +name: Release Automation + +on: + push: + branches: + - main + +permissions: + contents: write + pull-requests: write + +jobs: + release-please: + runs-on: ubuntu-latest + steps: + - uses: google-github-actions/release-please-action@v3 + with: + # Commit to start releases from. This can be removed after the first release is merged. + last-release-sha: 99cfade027ce6ca81d0a14657d6bdd1b05406ad8 + release-type: node + package-name: staking-dashboard diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 0000000000..a472af1ab9 --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,17 @@ +name: 'Close Stale PRs' + +on: + schedule: + - cron: '30 1 * * *' + +jobs: + stale: + runs-on: ubuntu-latest + steps: + - uses: actions/stale@v8 + with: + stale-pr-message: 'This PR is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 5 days.' + close-pr-message: 'This PR was closed because it has been stale for 5 days with no activity.' + days-before-pr-stale: 30 + days-before-pr-close: 5 + repo-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index 09a3774b03..bd98e77ade 100644 --- a/.gitignore +++ b/.gitignore @@ -1,27 +1,25 @@ -# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. - -# environment -.env -.vscode - -# dependencies -/node_modules -/.pnp -.pnp.js - -# testing -/coverage - -# production -/build - -# misc -.DS_Store -.env.local -.env.development.local -.env.test.local -.env.production.local - +# Logs +logs +*.log npm-debug.log* yarn-debug.log* yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +build +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/.licenserc.json b/.licenserc.json index 3b06971ff2..c502a50c86 100644 --- a/.licenserc.json +++ b/.licenserc.json @@ -1,4 +1,7 @@ { - "**/*.{ts, tsx}": "// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors", - "ignore": ["testdata", "npm"] + "**/*.{js, ts, tsx}": [ + "// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors", + "// SPDX-License-Identifier: GPL-3.0-only" + ], + "ignore": ["testdata", "npm", "public/", "Dockerfile"] } diff --git a/.prettierignore b/.prettierignore index d2859c15da..69abff9550 100644 --- a/.prettierignore +++ b/.prettierignore @@ -5,5 +5,6 @@ **/package-lock.json **/.eslintrc.js **/tsconfig.json -**/webpack.config.js +dist src/img/**/* +public/lottie/player.js diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000000..bfa6c96ee7 --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,7 @@ +{ + "arrowParens": "always", + "trailingComma": "es5", + "tabWidth": 2, + "semi": true, + "singleQuote": true +} diff --git a/.scripts/localeOrderKeys.cjs b/.scripts/localeOrderKeys.cjs new file mode 100644 index 0000000000..62b1009a2a --- /dev/null +++ b/.scripts/localeOrderKeys.cjs @@ -0,0 +1,42 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +const fs = require('fs'); +const path = require('path'); +const prettier = require('prettier'); +const { getDirectories, localeDir, orderJsonByKeys } = require('./utils.cjs'); + +// Get all language paths to re-order. +const languages = getDirectories(localeDir, []); + +// Gor each language path. +for (const lng of languages) { + const pathToLanguage = path.join(localeDir, `/${lng}`); + + fs.readdir(pathToLanguage, (error, files) => { + if (error) return; + + files.forEach(async (file) => { + const pathToFile = path.join(pathToLanguage, file); + const json = JSON.parse(fs.readFileSync(pathToFile).toString()); + + // order json object alphabetically. + const orderedJson = orderJsonByKeys(json); + + // format json object. + const formatted = await prettier.format(JSON.stringify(orderedJson), { + parser: 'json', + }); + + fs.writeFile(pathToFile, formatted, (err) => { + if (err) { + console.err(err); + } else { + console.log( + `----------Keys In ${pathToLanguage}/${file} Are Ordered Alphabetically-------------` + ); + } + }); + }); + }); +} diff --git a/.scripts/localeValidate.cjs b/.scripts/localeValidate.cjs new file mode 100644 index 0000000000..0a71147e74 --- /dev/null +++ b/.scripts/localeValidate.cjs @@ -0,0 +1,78 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +const fs = require('fs'); +const { join } = require('path'); +const { + getDeepKeys, + getDirectories, + localeDir, + orderJsonByKeys, +} = require('./utils.cjs'); + +// Missing key validation function. +const validateMissingKeys = () => { + const defaultPath = join(localeDir, 'en'); + const languages = getDirectories(localeDir, ['en']); + + fs.readdir(defaultPath, (error, files) => { + if (error) console.log(error); + + files.forEach((file) => { + const defaultJson = JSON.parse( + fs.readFileSync(join(defaultPath, file)).toString() + ); + + for (const lng of languages) { + const otherPath = join(localeDir, lng); + const otherJson = JSON.parse( + fs.readFileSync(join(otherPath, file)).toString() + ); + + const a = getDeepKeys(defaultJson); + const b = getDeepKeys(otherJson); + + if (a.sort().length !== b.sort().length) { + const missing = a.filter((item) => b.indexOf(item) < 0); + if (missing.join('').trim().length > 0) { + throw new Error( + `Missing the following keys from locale "${lng}", file: "${file}":\n"${missing}".` + ); + } + } + } + }); + }); +}; + +// Key order validation function. +const validateKeyOrder = () => { + // get all language paths to re-order. + const languages = getDirectories(localeDir, []); + + for (const lng of languages) { + const pathToLanguage = join(localeDir, `/${lng}`); + + fs.readdir(pathToLanguage, (error, files) => { + if (error) return; + + files.forEach((file) => { + const pathToFile = join(pathToLanguage, file); + const json = JSON.parse(fs.readFileSync(pathToFile).toString()); + + // order json object alphabetically. + const orderedJson = orderJsonByKeys(json); + if (JSON.stringify(json) !== JSON.stringify(orderedJson)) { + throw new Error( + `Keys are in the incorrect order from locale "${lng}", file: "${file}".` + ); + } + }); + }); + } +}; + +// validate missing keys +validateMissingKeys(); +// validate key order +validateKeyOrder(); diff --git a/.scripts/utils.cjs b/.scripts/utils.cjs new file mode 100644 index 0000000000..83eb0209e5 --- /dev/null +++ b/.scripts/utils.cjs @@ -0,0 +1,96 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +const fs = require('fs'); +const { join } = require('path'); + +// Project locale directory. +const localeDir = join(__dirname, '..', 'src', 'locale'); + +// The suffixes of keys related to i18n functionality that should be ignored. +const ignoreSuffixes = ['_one', '_two', '_few', '_many', '_other']; + +// Check if value is an object. Do not count arrays as objects. +const isObject = (o) => (Array.isArray(o) ? false : typeof o === 'object'); + +// Checks whether a key contains an ingore suffix. +const endsWithIgnoreSuffix = (key) => + ignoreSuffixes.some((i) => key.endsWith(i)); + +// Locale directories, ommitting `en` - the langauge to check missing keys against. +const getDirectories = (source, omit) => + fs + .readdirSync(source, { withFileTypes: true }) + .filter((dirent) => dirent.isDirectory()) + .filter((v) => !omit.includes(v.name)) + .map((dirent) => dirent.name); + +// Order keys of a json object. +const orderKeysAlphabetically = (o) => + Object.keys(o) + .sort() + .reduce((obj, key) => { + obj[key] = o[key]; + return obj; + }, {}); + +// Order json object by its keys. +const orderJsonByKeys = (json) => { + // order top level keys + json = orderKeysAlphabetically(json); + // order child objects if they are values. + const jsonOrdered = {}; + Object.entries(json).forEach(([k, v]) => { + if (isObject(v)) { + jsonOrdered[k] = orderJsonByKeys(v); + } else { + jsonOrdered[k] = v; + } + }); + return jsonOrdered; +}; + +// Recursive function to get all keys of a locale object. +const getDeepKeys = (obj) => { + let keys = []; + for (const key in obj) { + let isSubstring = false; + + // not number + if (isNaN(key)) { + // check if key includes any special substrings + if (endsWithIgnoreSuffix(key)) { + isSubstring = true; + // get the substring up to the last underscore + const rawKey = key.substring(0, key.lastIndexOf('_')); + // add the key to `keys` if it does not already exist + if (!keys.includes(rawKey)) { + keys.push(rawKey); + } + } + } + // full string, if not already added, go ahead and add + if (!isSubstring) { + if (!keys.includes(key)) { + keys.push(key); + } + } + // if object, recursively get keys + if (typeof obj[key] === 'object') { + const subkeys = getDeepKeys(obj[key]); + keys = keys.concat(subkeys.map((subkey) => `${key}.${subkey}`)); + } + } + return keys; +}; + +module.exports = { + endsWithIgnoreSuffix, + getDeepKeys, + getDirectories, + ignoreSuffixes, + isObject, + localeDir, + orderJsonByKeys, + orderKeysAlphabetically, +}; diff --git a/.shell/build-container.sh b/.shell/build-container.sh new file mode 100755 index 0000000000..5a3cde22b1 --- /dev/null +++ b/.shell/build-container.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash + +podman build \ + -t polkadot-staking-dashboard \ + -t psd \ + . diff --git a/CHANGELOG.md b/CHANGELOG.md index 85106ca70a..320888625e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,18 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.7.0] - 2023-02-23 + +## Added + +- Support for substrate node v1.0.0 + +## [1.6.0] - 2024-01-18 + +## Added + +- Support for substrate node v0.9.31-0.9.37 + ## [1.5.0] - 2023-11-27 ## Added diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000000..bed439733f --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,90 @@ +# Contribution Guide + +This section aims to familiarise developers to the Polkadot Staking Dashboard [[GitHub](https://github.com/paritytech/polkadot-staking-dashboard), [Demo](https://paritytech.github.io/polkadot-staking-dashboard/#/overview)] for the purpose of contributing to the project. + +Reach out to ross@parity.io for clarification of any content within this document. + +## Submitting Pull Requests + +This project follows the Conventional Commits specification. Pull requests are merged and squashed, with the pull request title being used as the commit message. Commit messages should adhere to the following structure: + +``` +(): +``` + +Some example PR titles: + +- `feat: implement help overlay` +- `feat(auth): implement login API` +- `fix(button): resolve issue with button alignment` +- `docs(readme): add installation section` +- `chore(tests): refactor user tests` + +The `(scope)` could be anything specifying the place of the commit change. For example, api, app, cli, etc. + +If you would like to know more about the Conventional Commits specification, please visit the [Conventional Commits website](https://www.conventionalcommits.org/). + +## Releases + +[Release Please](https://github.com/googleapis/release-please) is used for automating staking dashboard's changelog and release generation. + +Release Please is a GitHub action maintained by Google that automates CHANGELOG generation, the creation of GitHub releases, and version bumps. [[Gtihub docs](https://github.com/googleapis/release-please), [Action](https://github.com/marketplace/actions/release-please-action)] + +## Major Packages Used + +- React 18 +- Polkadot JS API [[docs](https://polkadot.js.org/docs/api)] +- React Chart JS 2 for graphing. [[docs](https://www.chartjs.org/docs/latest/), [React docs](https://react-chartjs-2.js.org/)] +- Framer Motion. [[docs](https://www.framer.com/docs/animation/)] +- [Font Awesome](https://fontawesome.com/v5/search) for the majority of icons. [Ionicons](https://ionic.io/ionicons) for side menu footer icons +- SCSS for theme configuration and Styled Components [[docs](https://styled-components.com/docs)] for component styling. + +## Environment Variables + +Optionally apply envrionment variables in an environment file such as `.env` or with `yarn build` to customise the build of staking dashboard. + +Refer to the [`.env`](https://github.com/paritytech/polkadot-staking-dashboard/blob/main/.env) file in the root of the repository for all supported variables. + +## Config Files + +There are some ad-hoc files defining app configuration where needed. These just provide a means of bootstrapping app data, and further abstraction could be explored in the future. + +- [`config/pages.ts`](https://github.com/paritytech/polkadot-staking-dashboard/blob/main/src/config/pages.ts): provides the pages and page categories of the app. +- [`config/help.ts`](https://github.com/paritytech/polkadot-staking-dashboard/blob/main/src/config/help.ts): provides the help content. +- [`Utils.ts`](https://github.com/paritytech/polkadot-staking-dashboard/blob/main/src/Utils.ts): Various general helper functions used throughout the app, such as formatting utilities. + +## Folders + +Folders are structured in the [`src/`](https://github.com/paritytech/polkadot-staking-dashboard/tree/main/src) directory to separate functional, presentational and context components: + +- [`contexts`](https://github.com/paritytech/polkadot-staking-dashboard/tree/main/src/contexts): context providers for the app. All Polkadot JS API interaction happens in these files. +- [`img`](https://github.com/paritytech/polkadot-staking-dashboard/tree/main/src/img): app SVGs. +- [`library`](https://github.com/paritytech/polkadot-staking-dashboard/tree/main/src/library): reusable components that could eventually be abstracted into a separate UI library. +- [`modals`](https://github.com/paritytech/polkadot-staking-dashboard/tree/main/src/modals): the various modal pop-ups used in the app. +- [`pages`](https://github.com/paritytech/polkadot-staking-dashboard/tree/main/src/pages): similar to modals, page components and components that comprise pages. +- [`workers`](https://github.com/paritytech/polkadot-staking-dashboard/tree/main/src/workers): web workers that crunch process-heavy scripts. Only one exists right now, that iterates `erasStakers` and calculates active nominators and minimum nomination bond. + +## App Entry + +Going from the top-most component, the component hierarchy is set up as follows: + +- [`index.tsx`](https://github.com/paritytech/polkadot-staking-dashboard/blob/main/src/index.tsx): DOM render, of little interest. +- [`App.tsx`](https://github.com/paritytech/polkadot-staking-dashboard/blob/main/src/App.tsx): wraps `` in the theme provider context and determines the active network from local storage. +- [`Providers.tsx`](https://github.com/paritytech/polkadot-staking-dashboard/blob/main/src/Providers.tsx): imports and wraps `` with all the contexts using a withProviders hook. We also wrap styled component's theme provider context here to make the theme configuration work. +- [`Router.tsx`](https://github.com/paritytech/polkadot-staking-dashboard/blob/main/src/Router.tsx): contains react router ``'s, in addition to the major app presentational components. + +## Development Patterns + +Documenting some of the development patterns used: + +- We use the **"Wrapper" terminology for styled components** that wrap a functional component. +- **Styles are defined on a per-component basis**, being defined in the same folder as the component markup itself. Where unavoidable (such as global styles, interface layout), styled components should reside in [`src/Wrappers.ts`](https://github.com/paritytech/polkadot-staking-dashboard/blob/main/src/Wrappers.tsx). +- **Theme values** are configured in [`styles/theme.scss`](https://github.com/paritytech/polkadot-staking-dashboard/blob/main/src/styles/theme.scss), and can be included in any styled component as custom properties. Graph colors are configurable from [`styles/graphs.ts`](https://github.com/paritytech/polkadot-staking-dashboard/blob/main/src/styles/graphs.ts). +- **Constants or default values** (such as those waiting for Polkadot API) are defined in [`src/constants.ts`](https://github.com/paritytech/polkadot-staking-dashboard/blob/main/src/constants.ts). +- Packages with **missing TypeScript definitions** can be declared in [`src/react-app-env.d.ts`](https://github.com/paritytech/polkadot-staking-dashboard/blob/main/src/react-app-env.d.ts). + +## TypeScript Support + +The majority of components have types. Type additions are welcome for data that makes sense to type (e.g. data that is unlikely to change as we continue development). + +Strict mode is used in development, so types are always required for objects. diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000000..928422436e --- /dev/null +++ b/Dockerfile @@ -0,0 +1,13 @@ +FROM node:buster as BUILDER + +WORKDIR /app +COPY . . +RUN apt update && apt install -y git +RUN yarn install && yarn build + +# -------------------------------------------------- +FROM nginx + +COPY --from=BUILDER /app/build /usr/share/nginx/html + +EXPOSE 80 diff --git a/LICENSE b/LICENSE index 261eeb9e9f..bdfbac5031 100644 --- a/LICENSE +++ b/LICENSE @@ -1,201 +1,674 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Substrate Connect Copyright (C) 2020-2022 Parity Technologies Ltd + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/index.html b/index.html new file mode 100644 index 0000000000..09726b6ee2 --- /dev/null +++ b/index.html @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Polkadot Staking Dashboard | Polkadot Staking (DOT) + + +
+ + + diff --git a/package.json b/package.json index 1475fb8d5a..112f08cc98 100644 --- a/package.json +++ b/package.json @@ -1,111 +1,103 @@ { "name": "polkadot-staking-dashboard", - "version": "0.1.0", - "license": "apache-2.0", + "version": "1.7.0", + "type": "module", + "license": "GPL-3.0-only", "private": false, + "scripts": { + "build": "tsc && vite build --base './'", + "build:pages": "tsc && vite build --base '/polkadot-staking-dashboard/'", + "clear": "rm -rf node_modules build tsconfig.tsbuildinfo", + "deploy:pages": "yarn build:pages && gh-pages -d build", + "dev": "vite --base './'", + "lint": "eslint . --fix && npx prettier --write . && npx prettier --write ./.scripts && node ./.scripts/localeOrderKeys.cjs", + "locale:order": "node ./.scripts/localeOrderKeys.cjs", + "locale:validate": "node ./.scripts/localeValidate.cjs", + "preview": "vite preview", + "test": "vitest", + "visualizer": "vite-bundle-visualizer" + }, "dependencies": { "@apollo/client": "^3.7.14", - "@fortawesome/fontawesome-svg-core": "^6.2.1", - "@fortawesome/free-brands-svg-icons": "^6.2.1", - "@fortawesome/free-regular-svg-icons": "^6.2.1", - "@fortawesome/free-solid-svg-icons": "^6.2.1", + "@dotlottie/player-component": "^2.7.0", + "@fortawesome/fontawesome-svg-core": "^6.4.2", + "@fortawesome/free-brands-svg-icons": "^6.4.2", + "@fortawesome/free-regular-svg-icons": "^6.4.2", + "@fortawesome/free-solid-svg-icons": "^6.4.2", "@fortawesome/react-fontawesome": "^0.2.0", - "@polkadot/api": "^9.9.4", - "@polkadot/keyring": "^10.1.12", - "@polkadot/react-identicon": "^2.9.13", - "@polkadot/rpc-provider": "^9.9.1", - "@polkadot/types": "^9.9.1", - "@polkadot/types-codec": "^9.9.1", - "@polkadot/util": "^10.1.13", - "@rossbulat/polkadot-dashboard-ui": "0.1.0-alpha.23", - "@testing-library/jest-dom": "^5.16.5", - "@testing-library/react": "^13.4.0", - "@testing-library/user-event": "^14.4.3", + "@ledgerhq/hw-transport-webhid": "^6.27.19", + "@polkadot-cloud/assets": "^0.1.32", + "@polkadot-cloud/core": "^1.0.30", + "@polkadot-cloud/react": "^0.1.101", + "@polkadot-cloud/utils": "^0.0.23", + "@polkadot/api": "^10.10.1", + "@polkadot/keyring": "^12.1.1", + "@polkadot/rpc-provider": "^10.9.1", + "@polkadot/util": "^12.4.2", + "@polkadot/util-crypto": "12.5.1", + "@polkawatch/ddp-client": "^2.0.8", + "@substrate/connect": "^0.7.34", + "@zondax/ledger-substrate": "^0.41.3", + "bignumber.js": "^9.1.2", "bn.js": "^5.2.1", - "chart.js": "^4.0.1", - "chartjs-plugin-datalabels": "^2.1.0", - "che-react-number-easing": "^0.1.4", + "buffer": "^6.0.3", + "chart.js": "^4.4.0", + "chroma-js": "^2.4.2", "date-fns": "^2.29.3", - "downshift": "^6.1.7", - "framer-motion": "^7.6.7", + "framer-motion": "^10.16.3", "graphql": "^16.6.0", - "i18next": "^22.0.6", - "i18next-browser-languagedetector": "^7.0.1", + "i18next": "^23.6.0", + "i18next-browser-languagedetector": "^7.1.0", "lodash.throttle": "^4.1.1", + "qrcode-generator": "1.4.4", + "rc-slider": "^10.3.1", "react": "^18.2.0", - "react-chartjs-2": "^5.0.1", - "react-content-loader": "^6.2.0", - "react-dom": "^18.1.0", - "react-error-boundary": "^3.1.4", + "react-chartjs-2": "^5.2.0", + "react-dom": "^18.2.0", + "react-error-boundary": "^4.0.11", "react-helmet": "^6.1.0", - "react-i18next": "^12.0.0", - "react-lottie": "^1.2.3", - "react-router-dom": "^6.4.3", - "react-scripts": "5.0.1", - "react-scroll": "^1.8.6", - "styled-components": "^5.3.3", - "styled-theming": "^2.2.0", - "subscriptions-transport-ws": "^0.11.0", - "typescript": "^4.9.3", - "web-vitals": "^3.1.0", - "yarn": "^1.22.18" + "react-i18next": "^13.3.1", + "react-qr-reader": "^2.2.1", + "react-router-dom": "^6.17.0", + "react-scroll": "^1.9.0", + "styled-components": "^6.1.0", + "subscriptions-transport-ws": "^0.11.0" }, "devDependencies": { - "@types/jest": "^27.4.0", - "@types/lodash.throttle": "^4.1.6", - "@types/node": "^18.11.9", - "@types/react": "^18.0.25", - "@types/react-dom": "^18.0.9", - "@types/react-helmet": "^6.1.5", - "@types/react-lottie": "^1.2.6", - "@types/react-scroll": "^1.8.5", - "@types/styled-components": "^5.1.21", - "@types/styled-theming": "^2.2.5", - "@typescript-eslint/eslint-plugin": "^5.44.0", - "@typescript-eslint/parser": "^5.44.0", - "eslint": "^8.27.0", + "@ledgerhq/logs": "^6.10.1", + "@types/chroma-js": "^2.4.0", + "@types/lodash.throttle": "^4.1.7", + "@types/react": "^18.2.33", + "@types/react-dom": "^18.2.14", + "@types/react-helmet": "^6.1.8", + "@types/react-qr-reader": "^2.1.6", + "@types/react-scroll": "^1.8.9", + "@typescript-eslint/eslint-plugin": "^6.9.0", + "@typescript-eslint/parser": "^6.9.0", + "@vitejs/plugin-react-swc": "^3.4.0", + "eslint": "^8.52.0", "eslint-config-airbnb": "^19.0.4", - "eslint-config-airbnb-typescript": "^17.0.0", - "eslint-config-prettier": "^8.5.0", - "eslint-import-resolver-typescript": "^3.5.2", - "eslint-plugin-import": "^2.25.3", - "eslint-plugin-jsx-a11y": "^6.5.1", + "eslint-config-airbnb-typescript": "^17.1.0", + "eslint-config-prettier": "^9.0.0", + "eslint-import-resolver-typescript": "^3.6.1", + "eslint-plugin-import": "^2.29.0", + "eslint-plugin-jsx-a11y": "^6.7.1", "eslint-plugin-prefer-arrow": "^1.2.3", - "eslint-plugin-prefer-arrow-functions": "^3.1.4", - "eslint-plugin-prettier": "^4.0.0", - "eslint-plugin-react": "^7.31.11", - "eslint-plugin-react-hooks": "^4.3.0", - "gh-pages": "^4.0.0", - "prettier": "^2.8.0", - "prettier-plugin-organize-imports": "^3.2.0", - "terser-webpack-plugin": "^5.3.3", - "worker-loader": "^3.0.8" - }, - "scripts": { - "start": "react-scripts start", - "predeploy": "yarn run build", - "deploy": "gh-pages -d build", - "build": "react-scripts build", - "test": "react-scripts test", - "eject": "react-scripts eject", - "lint": "eslint ./src", - "lint:fix": "eslint ./src --fix", - "prettier": "npx prettier --check ./src", - "prettier:write": "npx prettier --write ./src" - }, - "browserslist": { - "production": [ - "chrome >= 67", - "edge >= 79", - "firefox >= 68", - "opera >= 54", - "safari >= 14" - ], - "development": [ - "last 1 chrome version", - "last 1 firefox version", - "last 1 safari version" - ] - }, - "homepage": "/" + "eslint-plugin-prefer-arrow-functions": "^3.2.4", + "eslint-plugin-prettier": "^5.0.1", + "eslint-plugin-react": "^7.33.2", + "eslint-plugin-unused-imports": "^3.0.0", + "gh-pages": "^6.0.0", + "prettier": "^3.0.3", + "prettier-plugin-organize-imports": "^3.2.3", + "sass": "^1.69.5", + "typescript": "^5.2.2", + "vite": "^4.4.11", + "vite-bundle-visualizer": "^0.10.0", + "vite-plugin-checker": "^0.6.2", + "vite-plugin-eslint": "^1.8.1", + "vite-plugin-svgr": "^4.1.0", + "vite-tsconfig-paths": "^4.2.1", + "vitest": "^0.34.5" + } } diff --git a/public/favicons/kusama/android-chrome-192x192.png b/public/favicons/kusama/android-chrome-192x192.png new file mode 100644 index 0000000000..18c3e3aa6b Binary files /dev/null and b/public/favicons/kusama/android-chrome-192x192.png differ diff --git a/public/favicons/kusama/android-chrome-512x512.png b/public/favicons/kusama/android-chrome-512x512.png new file mode 100644 index 0000000000..7169b62060 Binary files /dev/null and b/public/favicons/kusama/android-chrome-512x512.png differ diff --git a/public/favicons/kusama/apple-touch-icon.png b/public/favicons/kusama/apple-touch-icon.png new file mode 100644 index 0000000000..ae7adbfed0 Binary files /dev/null and b/public/favicons/kusama/apple-touch-icon.png differ diff --git a/public/favicons/kusama/browserconfig.xml b/public/favicons/kusama/browserconfig.xml new file mode 100644 index 0000000000..890e5d861b --- /dev/null +++ b/public/favicons/kusama/browserconfig.xml @@ -0,0 +1,9 @@ + + + + + + #111 + + + diff --git a/public/favicons/kusama/favicon-16x16.png b/public/favicons/kusama/favicon-16x16.png new file mode 100644 index 0000000000..1a7034f64c Binary files /dev/null and b/public/favicons/kusama/favicon-16x16.png differ diff --git a/public/favicons/kusama/favicon-32x32.png b/public/favicons/kusama/favicon-32x32.png new file mode 100644 index 0000000000..e87aaaef73 Binary files /dev/null and b/public/favicons/kusama/favicon-32x32.png differ diff --git a/public/favicons/kusama/favicon.ico b/public/favicons/kusama/favicon.ico new file mode 100644 index 0000000000..0bbe2a2db2 Binary files /dev/null and b/public/favicons/kusama/favicon.ico differ diff --git a/public/favicons/kusama/mstile-150x150.png b/public/favicons/kusama/mstile-150x150.png new file mode 100644 index 0000000000..e93f838d5c Binary files /dev/null and b/public/favicons/kusama/mstile-150x150.png differ diff --git a/public/favicons/kusama/safari-pinned-tab.svg b/public/favicons/kusama/safari-pinned-tab.svg new file mode 100644 index 0000000000..1668066ec4 --- /dev/null +++ b/public/favicons/kusama/safari-pinned-tab.svg @@ -0,0 +1,32 @@ + + + + + + + diff --git a/public/favicons/kusama/site.webmanifest b/public/favicons/kusama/site.webmanifest new file mode 100644 index 0000000000..45a14840bb --- /dev/null +++ b/public/favicons/kusama/site.webmanifest @@ -0,0 +1,19 @@ +{ + "name": "Kusama", + "short_name": "Kusama", + "icons": [ + { + "src": "/favicons/kusama/android-chrome-192x192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "/favicons/kusama/android-chrome-512x512.png", + "sizes": "512x512", + "type": "image/png" + } + ], + "theme_color": "#ffffff", + "background_color": "#ffffff", + "display": "standalone" +} diff --git a/public/favicons/polkadot/android-chrome-192x192.png b/public/favicons/polkadot/android-chrome-192x192.png new file mode 100644 index 0000000000..c6b3cb673a Binary files /dev/null and b/public/favicons/polkadot/android-chrome-192x192.png differ diff --git a/public/favicons/polkadot/android-chrome-512x512.png b/public/favicons/polkadot/android-chrome-512x512.png new file mode 100644 index 0000000000..6aa07a0158 Binary files /dev/null and b/public/favicons/polkadot/android-chrome-512x512.png differ diff --git a/public/favicons/polkadot/apple-touch-icon.png b/public/favicons/polkadot/apple-touch-icon.png new file mode 100644 index 0000000000..af04946776 Binary files /dev/null and b/public/favicons/polkadot/apple-touch-icon.png differ diff --git a/public/favicons/polkadot/browserconfig.xml b/public/favicons/polkadot/browserconfig.xml new file mode 100644 index 0000000000..e20f0764df --- /dev/null +++ b/public/favicons/polkadot/browserconfig.xml @@ -0,0 +1,9 @@ + + + + + + #d33079 + + + diff --git a/public/favicons/polkadot/favicon-16x16.png b/public/favicons/polkadot/favicon-16x16.png new file mode 100644 index 0000000000..1a7034f64c Binary files /dev/null and b/public/favicons/polkadot/favicon-16x16.png differ diff --git a/public/favicons/polkadot/favicon-32x32.png b/public/favicons/polkadot/favicon-32x32.png new file mode 100644 index 0000000000..e87aaaef73 Binary files /dev/null and b/public/favicons/polkadot/favicon-32x32.png differ diff --git a/public/favicons/polkadot/favicon.ico b/public/favicons/polkadot/favicon.ico new file mode 100644 index 0000000000..83ccfcbd19 Binary files /dev/null and b/public/favicons/polkadot/favicon.ico differ diff --git a/public/favicons/polkadot/mstile-150x150.png b/public/favicons/polkadot/mstile-150x150.png new file mode 100644 index 0000000000..44839aea96 Binary files /dev/null and b/public/favicons/polkadot/mstile-150x150.png differ diff --git a/public/favicons/polkadot/safari-pinned-tab.svg b/public/favicons/polkadot/safari-pinned-tab.svg new file mode 100644 index 0000000000..8ef3afcf55 --- /dev/null +++ b/public/favicons/polkadot/safari-pinned-tab.svg @@ -0,0 +1,67 @@ + + + + + + + + + + + + diff --git a/public/favicons/polkadot/site.webmanifest b/public/favicons/polkadot/site.webmanifest new file mode 100644 index 0000000000..ef09a5b848 --- /dev/null +++ b/public/favicons/polkadot/site.webmanifest @@ -0,0 +1,19 @@ +{ + "name": "", + "short_name": "", + "icons": [ + { + "src": "/favicons/polkadot/android-chrome-192x192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "/favicons/polkadot/android-chrome-512x512.png", + "sizes": "512x512", + "type": "image/png" + } + ], + "theme_color": "#ffffff", + "background_color": "#ffffff", + "display": "standalone" +} diff --git a/public/favicons/westend/android-chrome-192x192.png b/public/favicons/westend/android-chrome-192x192.png new file mode 100644 index 0000000000..ff25849158 Binary files /dev/null and b/public/favicons/westend/android-chrome-192x192.png differ diff --git a/public/favicons/westend/android-chrome-512x512.png b/public/favicons/westend/android-chrome-512x512.png new file mode 100644 index 0000000000..060c349060 Binary files /dev/null and b/public/favicons/westend/android-chrome-512x512.png differ diff --git a/public/favicons/westend/apple-touch-icon.png b/public/favicons/westend/apple-touch-icon.png new file mode 100644 index 0000000000..4fdcc49021 Binary files /dev/null and b/public/favicons/westend/apple-touch-icon.png differ diff --git a/public/favicons/westend/browserconfig.xml b/public/favicons/westend/browserconfig.xml new file mode 100644 index 0000000000..97fc6607be --- /dev/null +++ b/public/favicons/westend/browserconfig.xml @@ -0,0 +1,9 @@ + + + + + + #e86c5b + + + diff --git a/public/favicons/westend/favicon-16x16.png b/public/favicons/westend/favicon-16x16.png new file mode 100644 index 0000000000..1a7034f64c Binary files /dev/null and b/public/favicons/westend/favicon-16x16.png differ diff --git a/public/favicons/westend/favicon-32x32.png b/public/favicons/westend/favicon-32x32.png new file mode 100644 index 0000000000..e87aaaef73 Binary files /dev/null and b/public/favicons/westend/favicon-32x32.png differ diff --git a/public/favicons/westend/favicon.ico b/public/favicons/westend/favicon.ico new file mode 100644 index 0000000000..a23ff135fa Binary files /dev/null and b/public/favicons/westend/favicon.ico differ diff --git a/public/favicons/westend/mstile-150x150.png b/public/favicons/westend/mstile-150x150.png new file mode 100644 index 0000000000..d37b459b57 Binary files /dev/null and b/public/favicons/westend/mstile-150x150.png differ diff --git a/public/favicons/westend/safari-pinned-tab.svg b/public/favicons/westend/safari-pinned-tab.svg new file mode 100644 index 0000000000..62f23b2eff --- /dev/null +++ b/public/favicons/westend/safari-pinned-tab.svg @@ -0,0 +1,235 @@ + + + + +Created by potrace 1.14, written by Peter Selinger 2001-2017 + + + + + diff --git a/public/favicons/westend/site.webmanifest b/public/favicons/westend/site.webmanifest new file mode 100644 index 0000000000..7ad920e785 --- /dev/null +++ b/public/favicons/westend/site.webmanifest @@ -0,0 +1,19 @@ +{ + "name": "Kusama", + "short_name": "Kusama", + "icons": [ + { + "src": "/favicons/westend/android-chrome-192x192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "/favicons/westend/android-chrome-512x512.png", + "sizes": "512x512", + "type": "image/png" + } + ], + "theme_color": "#ffffff", + "background_color": "#ffffff", + "display": "standalone" +} diff --git a/public/index.html b/public/index.html deleted file mode 100644 index 2db5a71a35..0000000000 --- a/public/index.html +++ /dev/null @@ -1,95 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Cere Staking Dashboard - - - - - - - - - - - -
- - - - - - - - diff --git a/public/lottie/analytics-dark.lottie b/public/lottie/analytics-dark.lottie new file mode 100644 index 0000000000..f82081b35f Binary files /dev/null and b/public/lottie/analytics-dark.lottie differ diff --git a/public/lottie/analytics-light.lottie b/public/lottie/analytics-light.lottie new file mode 100644 index 0000000000..a348e5ae9b Binary files /dev/null and b/public/lottie/analytics-light.lottie differ diff --git a/public/lottie/globe-dark.lottie b/public/lottie/globe-dark.lottie new file mode 100644 index 0000000000..b92bc8a85c Binary files /dev/null and b/public/lottie/globe-dark.lottie differ diff --git a/public/lottie/globe-light.lottie b/public/lottie/globe-light.lottie new file mode 100644 index 0000000000..60f9b14d09 Binary files /dev/null and b/public/lottie/globe-light.lottie differ diff --git a/public/lottie/groups-dark.lottie b/public/lottie/groups-dark.lottie new file mode 100644 index 0000000000..fad73c0e27 Binary files /dev/null and b/public/lottie/groups-dark.lottie differ diff --git a/public/lottie/groups-light.lottie b/public/lottie/groups-light.lottie new file mode 100644 index 0000000000..31d1777420 Binary files /dev/null and b/public/lottie/groups-light.lottie differ diff --git a/public/lottie/label-dark.lottie b/public/lottie/label-dark.lottie new file mode 100644 index 0000000000..e985d619f4 Binary files /dev/null and b/public/lottie/label-dark.lottie differ diff --git a/public/lottie/label-light.lottie b/public/lottie/label-light.lottie new file mode 100644 index 0000000000..6393602bb0 Binary files /dev/null and b/public/lottie/label-light.lottie differ diff --git a/public/lottie/player.js b/public/lottie/player.js new file mode 100644 index 0000000000..1a2d57c7d6 --- /dev/null +++ b/public/lottie/player.js @@ -0,0 +1,2 @@ +!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self)["dotlottie-player"]={})}(this,function(exports){"use strict";function _taggedTemplateLiteral(t,e){return e=e||t.slice(0),Object.freeze(Object.defineProperties(t,{raw:{value:Object.freeze(e)}}))}function __decorate(t,e,r,i){var s,n=arguments.length,a=n<3?e:null===i?i=Object.getOwnPropertyDescriptor(e,r):i;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)a=Reflect.decorate(t,e,r,i);else for(var o=t.length-1;0<=o;o--)(s=t[o])&&(a=(n<3?s(a):3{for(;e!==r;){const r=e.nextSibling;t.removeChild(e),e=r}},marker=`{{lit-${String(Math.random()).slice(2)}}}`,nodeMarker=``,markerRegex=new RegExp(marker+"|"+nodeMarker),boundAttributeSuffix="$lit$";class Template{constructor(i,r){this.parts=[],this.element=r;const s=[],n=[],a=document.createTreeWalker(r.content,133,null,!1);let t=0,o=-1,h=0;for(var{strings:l,values:{length:e}}=i;h{var r=t.length-e.length;return 0<=r&&t.slice(r)===e},isTemplatePartActive=t=>-1!==t.index,createMarker=()=>document.createComment(""),lastAttributeNameRegex=/([ \x09\x0a\x0c\x0d])([^\0-\x1F\x7F-\x9F "'>=/]+)([ \x09\x0a\x0c\x0d]*=[ \x09\x0a\x0c\x0d]*(?:[^ \x09\x0a\x0c\x0d"'`<>=]*|"[^"]*|'[^']*))$/,walkerNodeFilter=133;function removeNodesFromTemplate(t,e){var{element:{content:t},parts:r}=t,i=document.createTreeWalker(t,walkerNodeFilter,null,!1);let s=nextActiveIndexInTemplateParts(r),n=r[s],a=-1,o=0;var h=[];let l=null;for(;i.nextNode();){a++;const t=i.currentNode;for(t.previousSibling===l&&(l=null),null!==(l=e.has(t)&&(h.push(t),null===l)?t:l)&&o++;void 0!==n&&n.index===a;)n.index=null!==l?-1:n.index-o,s=nextActiveIndexInTemplateParts(r,s),n=r[s]}h.forEach(t=>t.parentNode.removeChild(t))}const countNodes=t=>{let e=11===t.nodeType?0:1;for(var r=document.createTreeWalker(t,walkerNodeFilter,null,!1);r.nextNode();)e++;return e},nextActiveIndexInTemplateParts=(e,r=-1)=>{for(let t=r+1;t"function"==typeof t&&directives.has(t),noChange={},nothing={};class TemplateInstance{constructor(t,e,r){this.__parts=[],this.template=t,this.processor=e,this.options=r}update(t){let e=0;for(const r of this.__parts)void 0!==r&&r.setValue(t[e]),e++;for(const t of this.__parts)void 0!==t&&t.commit()}_clone(){const t=isCEPolyfill?this.template.element.content.cloneNode(!0):document.importNode(this.template.element.content,!0),e=[],r=this.template.parts,i=document.createTreeWalker(t,133,null,!1);let s,n=0,a=0,o=i.nextNode();for(;nnull===t||!("object"==typeof t||"function"==typeof t),isIterable=t=>Array.isArray(t)||!(!t||!t[Symbol.iterator]);class AttributeCommitter{constructor(t,e,r){this.dirty=!0,this.element=t,this.name=e,this.strings=r,this.parts=[];for(let t=0;t{try{var t={get capture(){return!(eventOptionsSupported=!0)}};window.addEventListener("test",t,t),window.removeEventListener("test",t,t)}catch(t){}})();class EventPart{constructor(t,e,r){this.value=void 0,this.__pendingValue=void 0,this.element=t,this.eventName=e,this.eventContext=r,this.__boundHandleEvent=t=>this.handleEvent(t)}setValue(t){this.__pendingValue=t}commit(){for(;isDirective(this.__pendingValue);){const t=this.__pendingValue;this.__pendingValue=noChange,t(this)}if(this.__pendingValue!==noChange){const t=this.__pendingValue,e=this.value,r=null==t||null!=e&&(t.capture!==e.capture||t.once!==e.once||t.passive!==e.passive),i=null!=t&&(null==e||r);r&&this.element.removeEventListener(this.eventName,this.__boundHandleEvent,this.__options),i&&(this.__options=getOptions(t),this.element.addEventListener(this.eventName,this.__boundHandleEvent,this.__options)),this.value=t,this.__pendingValue=noChange}}handleEvent(t){"function"==typeof this.value?this.value.call(this.eventContext||this.element,t):this.value.handleEvent(t)}}const getOptions=t=>t&&(eventOptionsSupported?{capture:t.capture,passive:t.passive,once:t.once}:t.capture);function templateFactory(t){let e=templateCaches.get(t.type),r=(void 0===e&&(e={stringsArray:new WeakMap,keyString:new Map},templateCaches.set(t.type,e)),e.stringsArray.get(t.strings));var i;return void 0===r&&(i=t.strings.join(marker),void 0===(r=e.keyString.get(i))&&(r=new Template(t,t.getTemplateElement()),e.keyString.set(i,r)),e.stringsArray.set(t.strings,r)),r}const templateCaches=new Map,parts=new WeakMap,render=(t,e,r)=>{let i=parts.get(e);void 0===i&&(removeNodes(e,e.firstChild),parts.set(e,i=new NodePart(Object.assign({templateFactory:templateFactory},r))),i.appendInto(e)),i.setValue(t),i.commit()};class DefaultTemplateProcessor{handleAttributeExpressions(t,e,r,i){var s=e[0];return"."===s?new PropertyCommitter(t,e.slice(1),r).parts:"@"===s?[new EventPart(t,e.slice(1),i.eventContext)]:"?"===s?[new BooleanAttributePart(t,e.slice(1),r)]:new AttributeCommitter(t,e,r).parts}handleTextExpression(t){return new NodePart(t)}}const defaultTemplateProcessor=new DefaultTemplateProcessor,html=("undefined"!=typeof window&&(window.litHtmlVersions||(window.litHtmlVersions=[])).push("1.2.1"),(t,...e)=>new TemplateResult(t,e,"html",defaultTemplateProcessor)),getTemplateCacheKey=(t,e)=>t+"--"+e;let compatibleShadyCSSVersion=!0;void 0===window.ShadyCSS?compatibleShadyCSSVersion=!1:void 0===window.ShadyCSS.prepareTemplateDom&&(console.warn("Incompatible ShadyCSS version detected. Please update to at least @webcomponents/webcomponentsjs@2.0.2 and @webcomponents/shadycss@1.3.1."),compatibleShadyCSSVersion=!1);const shadyTemplateFactory=n=>t=>{const e=getTemplateCacheKey(t.type,n);let r=templateCaches.get(e),i=(void 0===r&&(r={stringsArray:new WeakMap,keyString:new Map},templateCaches.set(e,r)),r.stringsArray.get(t.strings));if(void 0===i){var s=t.strings.join(marker);if(void 0===(i=r.keyString.get(s))){const e=t.getTemplateElement();compatibleShadyCSSVersion&&window.ShadyCSS.prepareTemplateDom(e,n),i=new Template(t,e),r.keyString.set(s,i)}r.stringsArray.set(t.strings,i)}return i},TEMPLATE_TYPES=["html","svg"],removeStylesFromLitTemplates=e=>{TEMPLATE_TYPES.forEach(t=>{t=templateCaches.get(getTemplateCacheKey(t,e));void 0!==t&&t.keyString.forEach(t=>{const{content:e}=t["element"],r=new Set;Array.from(e.querySelectorAll("style")).forEach(t=>{r.add(t)}),removeNodesFromTemplate(t,r)})})},shadyRenderSet=new Set,prepareTemplateStyles=(t,e,r)=>{shadyRenderSet.add(t);var i=r?r.element:document.createElement("template"),s=e.querySelectorAll("style"),n=s["length"];if(0===n)window.ShadyCSS.prepareTemplateStyles(i,t);else{var a=document.createElement("style");for(let t=0;t{if(!r||"object"!=typeof r||!r.scopeName)throw new Error("The `scopeName` option is required.");var i=r.scopeName,s=parts.has(e),n=compatibleShadyCSSVersion&&11===e.nodeType&&!!e.host,a=n&&!shadyRenderSet.has(i),o=a?document.createDocumentFragment():e;if(render(t,o,Object.assign({templateFactory:shadyTemplateFactory(i)},r)),a){const t=parts.get(o),r=(parts.delete(o),t.value instanceof TemplateInstance?t.value.template:void 0);prepareTemplateStyles(i,o,r),removeNodes(e,e.firstChild),e.appendChild(o),parts.set(e,t)}!s&&n&&window.ShadyCSS.styleElement(e.host)};var _a;window.JSCompiler_renameProperty=(t,e)=>t;const defaultConverter={toAttribute(t,e){switch(e){case Boolean:return t?"":null;case Object:case Array:return null==t?t:JSON.stringify(t)}return t},fromAttribute(t,e){switch(e){case Boolean:return null!==t;case Number:return null===t?null:Number(t);case Object:case Array:return JSON.parse(t)}return t}},notEqual=(t,e)=>e!==t&&(e==e||t==t),defaultPropertyDeclaration={attribute:!0,type:String,converter:defaultConverter,reflect:!1,hasChanged:notEqual},STATE_HAS_UPDATED=1,STATE_UPDATE_REQUESTED=4,STATE_IS_REFLECTING_TO_ATTRIBUTE=8,STATE_IS_REFLECTING_TO_PROPERTY=16,finalized="finalized";class UpdatingElement extends HTMLElement{constructor(){super(),this._updateState=0,this._instanceProperties=void 0,this._updatePromise=new Promise(t=>this._enableUpdatingResolver=t),this._changedProperties=new Map,this._reflectingProperties=void 0,this.initialize()}static get observedAttributes(){this.finalize();const r=[];return this._classProperties.forEach((t,e)=>{t=this._attributeNameForProperty(e,t);void 0!==t&&(this._attributeToPropertyMap.set(t,e),r.push(t))}),r}static _ensureClassProperties(){var t;this.hasOwnProperty(JSCompiler_renameProperty("_classProperties",this))||(this._classProperties=new Map,void 0!==(t=Object.getPrototypeOf(this)._classProperties)&&t.forEach((t,e)=>this._classProperties.set(e,t)))}static createProperty(t,e=defaultPropertyDeclaration){var r;this._ensureClassProperties(),this._classProperties.set(t,e),e.noAccessor||this.prototype.hasOwnProperty(t)||(r="symbol"==typeof t?Symbol():"__"+t,void 0!==(r=this.getPropertyDescriptor(t,r,e))&&Object.defineProperty(this.prototype,t,r))}static getPropertyDescriptor(r,i,t){return{get(){return this[i]},set(t){var e=this[r];this[i]=t,this._requestUpdate(r,e)},configurable:!0,enumerable:!0}}static getPropertyOptions(t){return this._classProperties&&this._classProperties.get(t)||defaultPropertyDeclaration}static finalize(){const t=Object.getPrototypeOf(this);if(t.hasOwnProperty(finalized)||t.finalize(),this[finalized]=!0,this._ensureClassProperties(),this._attributeToPropertyMap=new Map,this.hasOwnProperty(JSCompiler_renameProperty("properties",this))){const t=this.properties,e=[...Object.getOwnPropertyNames(t),..."function"==typeof Object.getOwnPropertySymbols?Object.getOwnPropertySymbols(t):[]];for(const r of e)this.createProperty(r,t[r])}}static _attributeNameForProperty(t,e){e=e.attribute;return!1===e?void 0:"string"==typeof e?e:"string"==typeof t?t.toLowerCase():void 0}static _valueHasChanged(t,e,r=notEqual){return r(t,e)}static _propertyValueFromAttribute(t,e){var r=e.type,e=e.converter||defaultConverter,e="function"==typeof e?e:e.fromAttribute;return e?e(t,r):t}static _propertyValueToAttribute(t,e){var r;if(void 0!==e.reflect)return r=e.type,((e=e.converter)&&e.toAttribute||defaultConverter.toAttribute)(t,r)}initialize(){this._saveInstanceProperties(),this._requestUpdate()}_saveInstanceProperties(){this.constructor._classProperties.forEach((t,e)=>{if(this.hasOwnProperty(e)){const t=this[e];delete this[e],this._instanceProperties||(this._instanceProperties=new Map),this._instanceProperties.set(e,t)}})}_applyInstanceProperties(){this._instanceProperties.forEach((t,e)=>this[e]=t),this._instanceProperties=void 0}connectedCallback(){this.enableUpdating()}enableUpdating(){void 0!==this._enableUpdatingResolver&&(this._enableUpdatingResolver(),this._enableUpdatingResolver=void 0)}disconnectedCallback(){}attributeChangedCallback(t,e,r){e!==r&&this._attributeToProperty(t,r)}_propertyToAttribute(t,e,r=defaultPropertyDeclaration){var i=this.constructor,s=i._attributeNameForProperty(t,r);if(void 0!==s){const t=i._propertyValueToAttribute(e,r);void 0!==t&&(this._updateState=this._updateState|STATE_IS_REFLECTING_TO_ATTRIBUTE,null==t?this.removeAttribute(s):this.setAttribute(s,t),this._updateState=this._updateState&~STATE_IS_REFLECTING_TO_ATTRIBUTE)}}_attributeToProperty(t,e){if(!(this._updateState&STATE_IS_REFLECTING_TO_ATTRIBUTE)){var r=this.constructor,i=r._attributeToPropertyMap.get(t);if(void 0!==i){const t=r.getPropertyOptions(i);this._updateState=this._updateState|STATE_IS_REFLECTING_TO_PROPERTY,this[i]=r._propertyValueFromAttribute(e,t),this._updateState=this._updateState&~STATE_IS_REFLECTING_TO_PROPERTY}}}_requestUpdate(t,e){let r=!0;var i,s;void 0!==t&&(s=(i=this.constructor).getPropertyOptions(t),i._valueHasChanged(this[t],e,s.hasChanged)?(this._changedProperties.has(t)||this._changedProperties.set(t,e),!0!==s.reflect||this._updateState&STATE_IS_REFLECTING_TO_PROPERTY||(void 0===this._reflectingProperties&&(this._reflectingProperties=new Map),this._reflectingProperties.set(t,s))):r=!1),!this._hasRequestedUpdate&&r&&(this._updatePromise=this._enqueueUpdate())}requestUpdate(t,e){return this._requestUpdate(t,e),this.updateComplete}async _enqueueUpdate(){this._updateState=this._updateState|STATE_UPDATE_REQUESTED;try{await this._updatePromise}catch(t){}var t=this.performUpdate();return null!=t&&await t,!this._hasRequestedUpdate}get _hasRequestedUpdate(){return this._updateState&STATE_UPDATE_REQUESTED}get hasUpdated(){return this._updateState&STATE_HAS_UPDATED}performUpdate(){this._instanceProperties&&this._applyInstanceProperties();let t=!1;var e=this._changedProperties;try{(t=this.shouldUpdate(e))?this.update(e):this._markUpdated()}catch(e){throw t=!1,this._markUpdated(),e}t&&(this._updateState&STATE_HAS_UPDATED||(this._updateState=this._updateState|STATE_HAS_UPDATED,this.firstUpdated(e)),this.updated(e))}_markUpdated(){this._changedProperties=new Map,this._updateState=this._updateState&~STATE_UPDATE_REQUESTED}get updateComplete(){return this._getUpdateComplete()}_getUpdateComplete(){return this._updatePromise}shouldUpdate(t){return!0}update(t){void 0!==this._reflectingProperties&&0this._propertyToAttribute(e,this[e],t)),this._reflectingProperties=void 0),this._markUpdated()}updated(t){}firstUpdated(t){}}_a=finalized,UpdatingElement[_a]=!0;const legacyCustomElement=(t,e)=>(window.customElements.define(t,e),e),standardCustomElement=(e,t)=>{var{kind:t,elements:r}=t;return{kind:t,elements:r,finisher(t){window.customElements.define(e,t)}}},customElement=e=>t=>("function"==typeof t?legacyCustomElement:standardCustomElement)(e,t),standardProperty=(e,r)=>"method"!==r.kind||!r.descriptor||"value"in r.descriptor?{kind:"field",key:Symbol(),placement:"own",descriptor:{},initializer(){"function"==typeof r.initializer&&(this[r.key]=r.initializer.call(this))},finisher(t){t.createProperty(r.key,e)}}:Object.assign(Object.assign({},r),{finisher(t){t.createProperty(r.key,e)}}),legacyProperty=(t,e,r)=>{e.constructor.createProperty(r,t)};function property(r){return(t,e)=>void 0!==e?legacyProperty(r,t,e):standardProperty(r,t)}function query(i){return(t,e)=>{var r={get(){return this.renderRoot.querySelector(i)},enumerable:!0,configurable:!0};return void 0!==e?legacyQuery(r,t,e):standardQuery(r,t)}}const legacyQuery=(t,e,r)=>{Object.defineProperty(e,r,t)},standardQuery=(t,e)=>({kind:"method",placement:"prototype",key:e.key,descriptor:t}),supportsAdoptingStyleSheets="adoptedStyleSheets"in Document.prototype&&"replace"in CSSStyleSheet.prototype,constructionToken=Symbol();class CSSResult{constructor(t,e){if(e!==constructionToken)throw new Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead.");this.cssText=t}get styleSheet(){return void 0===this._styleSheet&&(supportsAdoptingStyleSheets?(this._styleSheet=new CSSStyleSheet,this._styleSheet.replaceSync(this.cssText)):this._styleSheet=null),this._styleSheet}toString(){return this.cssText}}const textFromCSSResult=t=>{if(t instanceof CSSResult)return t.cssText;if("number"==typeof t)return t;throw new Error(`Value passed to 'css' function must be a 'css' function result: ${t}. Use 'unsafeCSS' to pass non-literal values, but + take care to ensure page security.`)},css=(i,...t)=>{t=t.reduce((t,e,r)=>t+textFromCSSResult(e)+i[r+1],i[0]);return new CSSResult(t,constructionToken)},renderNotImplemented=((window.litElementVersions||(window.litElementVersions=[])).push("2.3.1"),{});class LitElement extends UpdatingElement{static getStyles(){return this.styles}static _getUniqueStyles(){if(!this.hasOwnProperty(JSCompiler_renameProperty("_styles",this))){var t=this.getStyles();if(void 0===t)this._styles=[];else if(Array.isArray(t)){const r=(t,e)=>t.reduceRight((t,e)=>Array.isArray(e)?r(e,t):(t.add(e),t),e),e=r(t,new Set),i=[];e.forEach(t=>i.unshift(t)),this._styles=i}else this._styles=[t]}}initialize(){super.initialize(),this.constructor._getUniqueStyles(),this.renderRoot=this.createRenderRoot(),window.ShadowRoot&&this.renderRoot instanceof window.ShadowRoot&&this.adoptStyles()}createRenderRoot(){return this.attachShadow({mode:"open"})}adoptStyles(){var t=this.constructor._styles;0!==t.length&&(void 0===window.ShadyCSS||window.ShadyCSS.nativeShadow?supportsAdoptingStyleSheets?this.renderRoot.adoptedStyleSheets=t.map(t=>t.styleSheet):this._needsShimAdoptedStyleSheets=!0:window.ShadyCSS.ScopingShim.prepareAdoptedCssText(t.map(t=>t.cssText),this.localName))}connectedCallback(){super.connectedCallback(),this.hasUpdated&&void 0!==window.ShadyCSS&&window.ShadyCSS.styleElement(this)}update(t){var e=this.render();super.update(t),e!==renderNotImplemented&&this.constructor.render(e,this.renderRoot,{scopeName:this.localName,eventContext:this}),this._needsShimAdoptedStyleSheets&&(this._needsShimAdoptedStyleSheets=!1,this.constructor._styles.forEach(t=>{var e=document.createElement("style");e.textContent=t.cssText,this.renderRoot.appendChild(e)}))}render(){return renderNotImplemented}}LitElement.finalized=!0,LitElement.render=render$1;var commonjsGlobal="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:{};function createCommonjsModule(t,e,r){return t(r={path:e,exports:{},require:function(t,e){return commonjsRequire(t,null==e?r.path:e)}},r.exports),r.exports}function commonjsRequire(){throw new Error("Dynamic requires are not currently supported by @rollup/plugin-commonjs")}var lottie_svg=createCommonjsModule(function(module){"undefined"!=typeof navigator&&function(t,e){module.exports?module.exports=e(t):(t.lottie=e(t),t.bodymovin=t.lottie)}(window||{},function(window){var svgNS="http://www.w3.org/2000/svg",locationHref="",initialDefaultFrame=-999999,subframeEnabled=!0,expressionsPlugin,isSafari=/^((?!chrome|android).)*safari/i.test(navigator.userAgent),bm_pow=Math.pow,bm_sqrt=Math.sqrt,bm_floor=Math.floor,bm_min=Math.min,BMMath={};function ProjectInterface(){return{}}!function(){for(var t=["abs","acos","acosh","asin","asinh","atan","atanh","atan2","ceil","cbrt","expm1","clz32","cos","cosh","exp","floor","fround","hypot","imul","log","log1p","log2","log10","max","min","pow","random","round","sign","sin","sinh","sqrt","tan","tanh","trunc","E","LN10","LN2","LOG10E","LOG2E","PI","SQRT1_2","SQRT2"],e=t.length,r=0;r>>=1;return(t+r)/e}var s=[],t=u(function t(e,r){var i,s=[],n=typeof e;if(r&&"object"==n)for(i in e)try{s.push(t(e[i],r-1))}catch(t){}return s.length?s:"string"==n?e:e+"\0"}((e=!0===e?{entropy:!0}:e||{}).entropy?[t,m(a)]:null===t?function(){try{var t=new Uint8Array(256);return(h.crypto||h.msCrypto).getRandomValues(t),m(t)}catch(t){var e=h.navigator,e=e&&e.plugins;return[+new Date,h,e,h.screen,m(a)]}}():t,3),s),n=new f(s);return i.int32=function(){return 0|n.g(4)},i.quick=function(){return n.g(4)/4294967296},i.double=i,u(m(n.S),a),(e.pass||r||function(t,e,r,i){return i&&(i.S&&d(i,n),t.state=function(){return d(n,{})}),r?(o.random=t,e):t})(i,t,"global"in e?e.global:this==o,e.state)},u(o.random(),a)}([],BMMath),function(){var t={getBezierEasing:function(t,e,r,i,s){s=s||("bez_"+t+"_"+e+"_"+r+"_"+i).replace(/\./g,"p");return n[s]||(t=new a([t,e,r,i]),n[s]=t)}},n={},e="function"==typeof Float32Array;function i(t,e){return 1-3*e+3*t}function P(t,e,r){return((i(e,r)*t+(3*r-6*e))*t+3*e)*t}function x(t,e,r){return 3*i(e,r)*t*t+2*(3*r-6*e)*t+3*e}function a(t){this._p=t,this._mSampleValues=new(e?Float32Array:Array)(11),this._precomputed=!1,this.get=this.get.bind(this)}return a.prototype={get:function(t){var e=this._p[0],r=this._p[1],i=this._p[2],s=this._p[3];return this._precomputed||this._precompute(),e===r&&i===s?t:0===t?0:1===t?1:P(this._getTForX(t),r,s)},_precompute:function(){var t=this._p[0],e=this._p[1],r=this._p[2],i=this._p[3];this._precomputed=!0,t===e&&r===i||this._calcSampleValues()},_calcSampleValues:function(){for(var t=this._p[0],e=this._p[2],r=0;r<11;++r)this._mSampleValues[r]=P(.1*r,t,e)},_getTForX:function(t){for(var e=this._p[0],r=this._p[2],i=this._mSampleValues,s=0,n=1;10!==n&&i[n]<=t;++n)s+=.1;var a=s+(t-i[--n])/(i[n+1]-i[n])*.1,o=x(a,e,r);if(.001<=o){for(var h=t,l=a,p=e,c=r,f=0;f<4;++f){var d=x(l,p,c);if(0===d)return l;l-=(P(l,p,c)-h)/d}return l}if(0===o)return a;for(var u,m,y=t,g=s,v=s+.1,_=e,b=r,S=0;0<(u=P(m=g+(v-g)/2,_,b)-y)?v=m:g=m,1e-7a?-1:1,l=!0;l;)if(i[n]<=a&&i[n+1]>a?(o=(a-i[n])/(i[n+1]-i[n]),l=!1):n+=h,n<0||s-1<=n){if(n===s-1)return r[n];l=!1}return r[n]+(r[n+1]-r[n])*o}var A=createTypedArray("float32",8);return{getSegmentsLength:function(t){for(var e=segments_length_pool.newElement(),r=t.c,i=t.v,s=t.o,n=t.i,a=t._length,o=e.lengths,h=0,l=0;le[0]||!(e[0]>t[0])&&(t[1]>e[1]||!(e[1]>t[1])&&(t[2]>e[2]||(e[2],t[2],0)))}function r(t){if(t.chars&&!o(h,t.v))for(var e,r,i,s,n=t.chars.length,a=0;a=n.t-i){s.h&&(s=n),o=0;break}if(n.t-i>t){o=h;break}h=r&&r<=t||this._caching.lastFrame=t&&(this._caching._lastKeyframeIndex=-1,this._caching.lastIndex=0),r=this.interpolateValue(t,this._caching),this.pv=r),this._caching.lastFrame=t,this.pv}function u(t){var e;if("unidimensional"===this.propType)e=t*this.mult,1e-5=this.p.keyframes[this.p.keyframes.length-1].t?(e=this.p.getValueAtTime(this.p.keyframes[this.p.keyframes.length-1].t/t,0),this.p.getValueAtTime((this.p.keyframes[this.p.keyframes.length-1].t-.05)/t,0)):(e=this.p.pv,this.p.getValueAtTime((this.p._caching.lastFrame+this.p.offsetTime-.01)/t,this.p.offsetTime)):this.px&&this.px.keyframes&&this.py.keyframes&&this.px.getValueAtTime&&this.py.getValueAtTime?(e=[],r=[],i=this.px,s=this.py,i._caching.lastFrame+i.offsetTime<=i.keyframes[0].t?(e[0]=i.getValueAtTime((i.keyframes[0].t+.01)/t,0),e[1]=s.getValueAtTime((s.keyframes[0].t+.01)/t,0),r[0]=i.getValueAtTime(i.keyframes[0].t/t,0),r[1]=s.getValueAtTime(s.keyframes[0].t/t,0)):i._caching.lastFrame+i.offsetTime>=i.keyframes[i.keyframes.length-1].t?(e[0]=i.getValueAtTime(i.keyframes[i.keyframes.length-1].t/t,0),e[1]=s.getValueAtTime(s.keyframes[s.keyframes.length-1].t/t,0),r[0]=i.getValueAtTime((i.keyframes[i.keyframes.length-1].t-.01)/t,0),r[1]=s.getValueAtTime((s.keyframes[s.keyframes.length-1].t-.01)/t,0)):(e=[i.pv,s.pv],r[0]=i.getValueAtTime((i._caching.lastFrame+i.offsetTime-.01)/t,i.offsetTime),r[1]=s.getValueAtTime((s._caching.lastFrame+s.offsetTime-.01)/t,s.offsetTime))):e=r=n,this.v.rotate(-Math.atan2(e[1]-r[1],e[0]-r[0]))),this.data.p&&this.data.p.s?this.data.p.z?this.v.translate(this.px.v,this.py.v,-this.pz.v):this.v.translate(this.px.v,this.py.v,0):this.v.translate(this.p.v[0],this.p.v[1],-this.p.v[2])),this.frameId=this.elem.globalData.frameId)},precalculateMatrix:function(){if(!this.a.k&&(this.pre.translate(-this.a.v[0],-this.a.v[1],this.a.v[2]),this.appliedTransformations=1,!this.s.effectsSequence.length)){if(this.pre.scale(this.s.v[0],this.s.v[1],this.s.v[2]),this.appliedTransformations=2,this.sk){if(this.sk.effectsSequence.length||this.sa.effectsSequence.length)return;this.pre.skewFromAxis(-this.sk.v,this.sa.v),this.appliedTransformations=3}this.r?this.r.effectsSequence.length||(this.pre.rotate(-this.r.v),this.appliedTransformations=4):this.rz.effectsSequence.length||this.ry.effectsSequence.length||this.rx.effectsSequence.length||this.or.effectsSequence.length||(this.pre.rotateZ(-this.rz.v).rotateY(this.ry.v).rotateX(this.rx.v).rotateZ(-this.or.v[2]).rotateY(this.or.v[1]).rotateX(this.or.v[0]),this.appliedTransformations=4)}},autoOrient:function(){}},extendPrototype([DynamicPropertyContainer],i),i.prototype.addDynamicProperty=function(t){this._addDynamicProperty(t),this.elem.addDynamicProperty(t),this._isDirty=!0},i.prototype._addDynamicProperty=DynamicPropertyContainer.prototype.addDynamicProperty,{getTransformProperty:function(t,e,r){return new i(t,e,r)}}}();function ShapePath(){this.c=!1,this._length=0,this._maxLength=8,this.v=createSizedArray(this._maxLength),this.o=createSizedArray(this._maxLength),this.i=createSizedArray(this._maxLength)}ShapePath.prototype.setPathData=function(t,e){this.c=t,this.setLength(e);for(var r=0;r=this._maxLength&&this.doubleArrayLength(),r){case"v":n=this.v;break;case"i":n=this.i;break;case"o":n=this.o}n[i]&&(!n[i]||s)||(n[i]=point_pool.newElement()),n[i][0]=t,n[i][1]=e},ShapePath.prototype.setTripleAt=function(t,e,r,i,s,n,a,o){this.setXYAt(t,e,"v",a,o),this.setXYAt(r,i,"o",a,o),this.setXYAt(s,n,"i",a,o)},ShapePath.prototype.reverse=function(){var t=new ShapePath,e=(t.setPathData(this.c,this._length),this.v),r=this.o,i=this.i,s=0;this.c&&(t.setTripleAt(e[0][0],e[0][1],i[0][0],i[0][1],r[0][0],r[0][1],0,!1),s=1);for(var n=this._length-1,a=this._length,o=s;o=c[c.length-1].t-this.offsetTime)i=(c[c.length-1].s?c[c.length-1].s:c[c.length-2].e)[0],s=!0;else{for(var f,d,u=p,m=c.length-1,y=!0;y&&(f=c[u],!((d=c[u+1]).t-this.offsetTime>t));)u=d.t-this.offsetTime?1:ti+r||(a=o.s*s<=i?0:(o.s*s-i)/r,o=o.e*s>=i+r?1:(o.e*s-i)/r,h.push([a,o]));return h.length||h.push([0,0]),h},TrimModifier.prototype.releasePathsData=function(t){for(var e=t.length,r=0;r(s=(1e.e){r.c=!1;break}e.s<=u&&e.e>=u+p.addedLength?(this.addSegment(f[i].v[s-1],f[i].o[s-1],f[i].i[s],f[i].v[s],r,a,y),y=!1):(h=bez.getNewSegment(f[i].v[s-1],f[i].v[s],f[i].o[s-1],f[i].i[s],(e.s-u)/p.addedLength,(e.e-u)/p.addedLength,o[s-1]),this.addSegmentFromArray(h,r,a,y),r.c=y=!1),u+=p.addedLength,a+=1}if(f[i].c&&o.length&&(p=o[s-1],u<=e.e?(l=o[s-1].addedLength,e.s<=u&&e.e>=u+l?(this.addSegment(f[i].v[s-1],f[i].o[s-1],f[i].i[0],f[i].v[0],r,a,y),y=!1):(h=bez.getNewSegment(f[i].v[s-1],f[i].v[0],f[i].o[s-1],f[i].i[0],(e.s-u)/l,(e.e-u)/l,o[s-1]),this.addSegmentFromArray(h,r,a,y),r.c=y=!1)):r.c=!1,u+=p.addedLength,a+=1),r._length&&(r.setXYAt(r.v[g][0],r.v[g][1],"i",g),r.setXYAt(r.v[r._length-1][0],r.v[r._length-1][1],"o",r._length-1)),u>e.e)break;i=o.length&&(s=0,o=h[n+=1]?h[n].points:p.v.c?h[n=s=0].points:(i-=r.partialLength,null)),o)&&(a=r,N=(r=o[s]).partialLength);x=m[b].an/2-m[b].add,u.translate(-x,0,0)}else x=m[b].an/2-m[b].add,u.translate(-x,0,0),u.translate(-c[0]*m[b].an/200,-c[1]*K/100,0);for(m[b].l,z=0;ze));)r+=1;return this.keysIndex!==r&&(this.keysIndex=r),this.data.d.k[this.keysIndex].s},TextProperty.prototype.buildFinalText=function(t){for(var e,r=FontManager.getCombinedCharacterCodes(),i=[],s=0,n=t.length;sthis.minimumFontSize&&O=p(n)&&(s=h(0,l(t-n<0?l(a,1)-(n-t):a-t,1))),i(s)))*this.a.v},getValue:function(t){this.iterateDynamicProperties(),this._mdf=t||this._mdf,this._currentTextLength=this.elem.textProperty.currentData.l.length||0,t&&2===this.data.r&&(this.e.v=this._currentTextLength);var t=2===this.data.r?1:100/this.data.totalChars,e=this.o.v/t,r=this.s.v/t+e,t=this.e.v/t+e;tt-this.layers[e].st&&this.buildItem(e),this.completeLayers=!!this.elements[e]&&this.completeLayers;this.checkPendingElements()},BaseRenderer.prototype.createItem=function(t){switch(t.ty){case 2:return this.createImage(t);case 0:return this.createComp(t);case 1:return this.createSolid(t);case 3:return this.createNull(t);case 4:return this.createShape(t);case 5:return this.createText(t);case 13:return this.createCamera(t)}return this.createNull(t)},BaseRenderer.prototype.createCamera=function(){throw new Error("You're using a 3d camera. Try the html renderer.")},BaseRenderer.prototype.buildAllItems=function(){for(var t=this.layers.length,e=0;et?!0!==this.isInRange&&(this.globalData._mdf=!0,this._mdf=!0,this.isInRange=!0,this.show()):!1!==this.isInRange&&(this.globalData._mdf=!0,this.isInRange=!1,this.hide())},renderRenderable:function(){for(var t=this.renderableComponents.length,e=0;ethis.animationData.op&&(this.animationData.op=t.op,this.totalFrames=Math.floor(t.op-this.animationData.ip));for(var e,r=this.animationData.layers,i=r.length,s=t.layers,n=s.length,a=0;athis.timeCompleted&&(this.currentFrame=this.timeCompleted),this.trigger("enterFrame"),this.renderFrame()},AnimationItem.prototype.renderFrame=function(){if(!1!==this.isLoaded)try{this.renderer.renderFrame(this.currentFrame+this.firstFrame)}catch(t){this.triggerRenderFrameError(t)}},AnimationItem.prototype.play=function(t){t&&this.name!=t||!0===this.isPaused&&(this.isPaused=!1,this._idle)&&(this._idle=!1,this.trigger("_active"))},AnimationItem.prototype.pause=function(t){t&&this.name!=t||!1===this.isPaused&&(this.isPaused=!0,this._idle=!0,this.trigger("_idle"))},AnimationItem.prototype.togglePause=function(t){t&&this.name!=t||(!0===this.isPaused?this.play():this.pause())},AnimationItem.prototype.stop=function(t){t&&this.name!=t||(this.pause(),this.playCount=0,this._completedLoop=!1,this.setCurrentRawFrameValue(0))},AnimationItem.prototype.goToAndStop=function(t,e,r){r&&this.name!=r||(e?this.setCurrentRawFrameValue(t):this.setCurrentRawFrameValue(t*this.frameModifier),this.pause())},AnimationItem.prototype.goToAndPlay=function(t,e,r){this.goToAndStop(t,e,r),this.play()},AnimationItem.prototype.advanceTime=function(t){var e;!0!==this.isPaused&&!1!==this.isLoaded&&(e=!1,(t=this.currentRawFrame+t*this.frameModifier)>=this.totalFrames-1&&0=this.totalFrames?(this.playCount+=1,this.checkSegments(t%this.totalFrames)||(this.setCurrentRawFrameValue(t%this.totalFrames),this._completedLoop=!0,this.trigger("loopComplete"))):this.setCurrentRawFrameValue(t):this.checkSegments(t>this.totalFrames?t%this.totalFrames:0)||(e=!0,t=this.totalFrames-1):t<0?this.checkSegments(t%this.totalFrames)||(!this.loop||this.playCount--<=0&&!0!==this.loop?(e=!0,t=0):(this.setCurrentRawFrameValue(this.totalFrames+t%this.totalFrames),this._completedLoop?this.trigger("loopComplete"):this._completedLoop=!0)):this.setCurrentRawFrameValue(t),e)&&(this.setCurrentRawFrameValue(t),this.pause(),this.trigger("complete"))},AnimationItem.prototype.adjustSegment=function(t,e){this.playCount=0,t[1]t[0]&&(this.frameModifier<0&&(this.playSpeed<0?this.setSpeed(-this.playSpeed):this.setDirection(1)),this.timeCompleted=this.totalFrames=t[1]-t[0],this.firstFrame=t[0],this.setCurrentRawFrameValue(.001+e)),this.trigger("segmentStart")},AnimationItem.prototype.setSegment=function(t,e){var r=-1;this.isPaused&&(this.currentRawFrame+this.firstFramee&&(r=e-t)),this.firstFrame=t,this.timeCompleted=this.totalFrames=e-t,-1!==r&&this.goToAndStop(r,!0)},AnimationItem.prototype.playSegments=function(t,e){if(e&&(this.segments.length=0),"object"==typeof t[0])for(var r=t.length,i=0;il.length-1)&&(e=l.length-1),i=p-(s=l[l.length-1-e].t)),"pingpong"===t){if(Math.floor((h-s)/i)%2!=0)return this.getValueAtTime((i-(h-s)%i+s)/this.comp.globalData.frameRate,0)}else{if("offset"===t){var c=this.getValueAtTime(s/this.comp.globalData.frameRate,0),f=this.getValueAtTime(p/this.comp.globalData.frameRate,0),d=this.getValueAtTime(((h-s)%i+s)/this.comp.globalData.frameRate,0),u=Math.floor((h-s)/i);if(this.pv.length){for(a=(o=new Array(c.length)).length,n=0;nl.length-1?l.length-1:e].t)-p,"pingpong"===t){if(Math.floor((p-h)/i)%2==0)return this.getValueAtTime(((p-h)%i+p)/this.comp.globalData.frameRate,0)}else{if("offset"===t){var c=this.getValueAtTime(p/this.comp.globalData.frameRate,0),f=this.getValueAtTime(s/this.comp.globalData.frameRate,0),d=this.getValueAtTime((i-(p-h)%i+p)/this.comp.globalData.frameRate,0),u=Math.floor((p-h)/i)+1;if(this.pv.length){for(a=(o=new Array(c.length)).length,n=0;ns){var h=n,l=r.c&&n===a-1?0:n+1,p=(s-o)/i[n].addedLength,c=bez.getPointInSegment(r.v[h],r.v[l],r.o[h],r.i[l],p,i[n]);break}o+=i[n].addedLength,n+=1}return c=c||(r.c?[r.v[0][0],r.v[0][1]]:[r.v[r._length-1][0],r.v[r._length-1][1]])},vectorOnPath:function(t,e,r){t=1==t?this.v.c?0:.999:t;var i=this.pointOnPath(t,e),t=this.pointOnPath(t+.001,e),e=t[0]-i[0],t=t[1]-i[1],i=Math.sqrt(Math.pow(e,2)+Math.pow(t,2));return 0===i?[0,0]:"tangent"===r?[e/i,t/i]:[-t/i,e/i]},tangentOnPath:function(t,e){return this.vectorOnPath(t,e,"tangent")},normalOnPath:function(t,e){return this.vectorOnPath(t,e,"normal")},setGroupProperty:expressionHelpers.setGroupProperty,getValueAtTime:expressionHelpers.getStaticValueAtTime},extendPrototype([r],t),extendPrototype([r],e),e.prototype.getValueAtTime=function(t){return this._cachingAtTime||(this._cachingAtTime={shapeValue:shape_pool.clone(this.pv),lastIndex:0,lastTime:initialDefaultFrame}),(t=(t*=this.elem.globalData.frameRate)-this.offsetTime)!==this._cachingAtTime.lastTime&&(this._cachingAtTime.lastIndex=this._cachingAtTime.lastTime>4,n=1>6:64,a=2>2)+f.charAt(s)+f.charAt(n)+f.charAt(a));return o.join("")},r.decode=function(t){var e,r,i,s,n,a=0,o=0;if("data:"===t.substr(0,"data:".length))throw new Error("Invalid base64 input, it looks like a data url.");var h,l=3*(t=t.replace(/[^A-Za-z0-9\+\/\=]/g,"")).length/4;if(t.charAt(t.length-1)===f.charAt(64)&&l--,t.charAt(t.length-2)===f.charAt(64)&&l--,l%1!=0)throw new Error("Invalid base64 input, bad content length.");for(h=new(p.uint8array?Uint8Array:Array)(0|l);a>4,r=(15&s)<<4|(s=f.indexOf(t.charAt(a++)))>>2,i=(3&s)<<6|(n=f.indexOf(t.charAt(a++))),h[o++]=e,64!==s&&(h[o++]=r),64!==n&&(h[o++]=i);return h}},{"./support":30,"./utils":32}],2:[function(t,e,r){var i=t("./external"),s=t("./stream/DataWorker"),n=t("./stream/DataLengthProbe"),a=t("./stream/Crc32Probe");function o(t,e,r,i,s){this.compressedSize=t,this.uncompressedSize=e,this.crc32=r,this.compression=i,this.compressedContent=s}n=t("./stream/DataLengthProbe"),o.prototype={getContentWorker:function(){var t=new s(i.Promise.resolve(this.compressedContent)).pipe(this.compression.uncompressWorker()).pipe(new n("data_length")),e=this;return t.on("end",function(){if(this.streamInfo.data_length!==e.uncompressedSize)throw new Error("Bug : uncompressed data size mismatch")}),t},getCompressedWorker:function(){return new s(i.Promise.resolve(this.compressedContent)).withStreamInfo("compressedSize",this.compressedSize).withStreamInfo("uncompressedSize",this.uncompressedSize).withStreamInfo("crc32",this.crc32).withStreamInfo("compression",this.compression)}},o.createWorkerFrom=function(t,e,r){return t.pipe(new a).pipe(new n("uncompressedSize")).pipe(e.compressWorker(r)).pipe(new n("compressedSize")).withStreamInfo("compression",e)},e.exports=o},{"./external":6,"./stream/Crc32Probe":25,"./stream/DataLengthProbe":26,"./stream/DataWorker":27}],3:[function(t,e,r){var i=t("./stream/GenericWorker");r.STORE={magic:"\0\0",compressWorker:function(t){return new i("STORE compression")},uncompressWorker:function(){return new i("STORE decompression")}},r.DEFLATE=t("./flate")},{"./flate":7,"./stream/GenericWorker":28}],4:[function(t,e,r){var i=t("./utils"),a=function(){for(var t=[],e=0;e<256;e++){for(var r=e,i=0;i<8;i++)r=1&r?3988292384^r>>>1:r>>>1;t[e]=r}return t}();e.exports=function(t,e){return void 0!==t&&t.length?("string"!==i.getTypeOf(t)?function(t,e,r){var i=a,s=0+r;t^=-1;for(var n=0;n>>8^i[255&(t^e[n])];return-1^t}:function(t,e,r){var i=a,s=0+r;t^=-1;for(var n=0;n>>8^i[255&(t^e.charCodeAt(n))];return-1^t})(0|e,t,t.length):0}},{"./utils":32}],5:[function(t,e,r){r.base64=!1,r.binary=!1,r.dir=!1,r.createFolders=!0,r.date=null,r.compression=null,r.compressionOptions=null,r.comment=null,r.unixPermissions=null,r.dosPermissions=null},{}],6:[function(t,e,r){t="undefined"!=typeof Promise?Promise:t("lie");e.exports={Promise:t}},{lie:37}],7:[function(t,e,r){var i="undefined"!=typeof Uint8Array&&"undefined"!=typeof Uint16Array&&"undefined"!=typeof Uint32Array,s=t("pako"),n=t("./utils"),a=t("./stream/GenericWorker"),o=i?"uint8array":"array";function h(t,e){a.call(this,"FlateWorker/"+t),this._pako=null,this._pakoAction=t,this._pakoOptions=e,this.meta={}}r.magic="\b\0",n.inherits(h,a),h.prototype.processChunk=function(t){this.meta=t.meta,null===this._pako&&this._createPako(),this._pako.push(n.transformTo(o,t.data),!1)},h.prototype.flush=function(){a.prototype.flush.call(this),null===this._pako&&this._createPako(),this._pako.push([],!0)},h.prototype.cleanUp=function(){a.prototype.cleanUp.call(this),this._pako=null},h.prototype._createPako=function(){this._pako=new s[this._pakoAction]({raw:!0,level:this._pakoOptions.level||-1});var e=this;this._pako.onData=function(t){e.push({data:t,meta:e.meta})}},r.compressWorker=function(t){return new h("Deflate",t)},r.uncompressWorker=function(){return new h("Inflate",{})}},{"./stream/GenericWorker":28,"./utils":32,pako:38}],8:[function(t,e,r){function v(t,e){for(var r="",i=0;i>>=8;return r}function i(t,e,r,i,s,n){var a=t.file,o=t.compression,h=n!==b.utf8encode,l=_.transformTo("string",n(a.name)),p=_.transformTo("string",b.utf8encode(a.name)),c=a.comment,n=_.transformTo("string",n(c)),f=_.transformTo("string",b.utf8encode(c)),d=p.length!==a.name.length,c=f.length!==c.length,u="",m=a.dir,y=a.date,g={crc32:0,compressedSize:0,uncompressedSize:0},r=(e&&!r||(g.crc32=t.crc32,g.compressedSize=t.compressedSize,g.uncompressedSize=t.uncompressedSize),0);return e&&(r|=8),h||!d&&!c||(r|=2048),e=t=0,m&&(t|=16),"UNIX"===s?(e=798,t|=(65535&((h=a.unixPermissions)?h:m?16893:33204))<<16):(e=20,t|=63&(a.dosPermissions||0)),s=y.getUTCHours(),s=(s=((s<<=6)|y.getUTCMinutes())<<5)|y.getUTCSeconds()/2,h=y.getUTCFullYear()-1980,h=(h=((h<<=4)|y.getUTCMonth()+1)<<5)|y.getUTCDate(),d&&(m=v(1,1)+v(S(l),4)+p,u+="up"+v(m.length,2)+m),c&&(a=v(1,1)+v(S(n),4)+f,u+="uc"+v(a.length,2)+a),y="",y=(y=(y=(y=(y=(y=(y=(y=(y=(y+="\n\0")+v(r,2))+o.magic)+v(s,2))+v(h,2))+v(g.crc32,4))+v(g.compressedSize,4))+v(g.uncompressedSize,4))+v(l.length,2))+v(u.length,2),{fileRecord:P.LOCAL_FILE_HEADER+y+l+u,dirRecord:P.CENTRAL_FILE_HEADER+v(e,2)+y+v(n.length,2)+"\0\0\0\0"+v(t,4)+v(i,4)+l+u+n}}var _=t("../utils"),s=t("../stream/GenericWorker"),b=t("../utf8"),S=t("../crc32"),P=t("../signature");function n(t,e,r,i){s.call(this,"ZipFileWorker"),this.bytesWritten=0,this.zipComment=e,this.zipPlatform=r,this.encodeFileName=i,this.streamFiles=t,this.accumulate=!1,this.contentBuffer=[],this.dirRecords=[],this.currentSourceOffset=0,this.entriesCount=0,this.currentFile=null,this._sources=[]}_.inherits(n,s),n.prototype.push=function(t){var e=t.meta.percent||0,r=this.entriesCount,i=this._sources.length;this.accumulate?this.contentBuffer.push(t):(this.bytesWritten+=t.data.length,s.prototype.push.call(this,{data:t.data,meta:{currentFile:this.currentFile,percent:r?(e+100*(r-i-1))/r:100}}))},n.prototype.openedSource=function(t){this.currentSourceOffset=this.bytesWritten,this.currentFile=t.file.name;var e=this.streamFiles&&!t.file.dir;e?(t=i(t,e,!1,this.currentSourceOffset,this.zipPlatform,this.encodeFileName),this.push({data:t.fileRecord,meta:{percent:0}})):this.accumulate=!0},n.prototype.closedSource=function(t){this.accumulate=!1;var e=this.streamFiles&&!t.file.dir,r=i(t,e,!0,this.currentSourceOffset,this.zipPlatform,this.encodeFileName);if(this.dirRecords.push(r.dirRecord),e)this.push({data:(e=t,P.DATA_DESCRIPTOR+v(e.crc32,4)+v(e.compressedSize,4)+v(e.uncompressedSize,4)),meta:{percent:100}});else for(this.push({data:r.fileRecord,meta:{percent:0}});this.contentBuffer.length;)this.push(this.contentBuffer.shift());this.currentFile=null},n.prototype.flush=function(){for(var t=this.bytesWritten,e=0;e=this.index;e--)r=(r<<8)+this.byteAt(e);return this.index+=t,r},readString:function(t){return i.transformTo("string",this.readData(t))},readData:function(t){},lastIndexOfSignature:function(t){},readAndCheckSignature:function(t){},readDate:function(){var t=this.readInt(4);return new Date(Date.UTC(1980+(t>>25&127),(t>>21&15)-1,t>>16&31,t>>11&31,t>>5&63,(31&t)<<1))}},e.exports=s},{"../utils":32}],19:[function(t,e,r){var i=t("./Uint8ArrayReader");function s(t){i.call(this,t)}t("../utils").inherits(s,i),s.prototype.readData=function(t){this.checkOffset(t);var e=this.data.slice(this.zero+this.index,this.zero+this.index+t);return this.index+=t,e},e.exports=s},{"../utils":32,"./Uint8ArrayReader":21}],20:[function(t,e,r){var i=t("./DataReader");function s(t){i.call(this,t)}t("../utils").inherits(s,i),s.prototype.byteAt=function(t){return this.data.charCodeAt(this.zero+t)},s.prototype.lastIndexOfSignature=function(t){return this.data.lastIndexOf(t)-this.zero},s.prototype.readAndCheckSignature=function(t){return t===this.readData(4)},s.prototype.readData=function(t){this.checkOffset(t);var e=this.data.slice(this.zero+this.index,this.zero+this.index+t);return this.index+=t,e},e.exports=s},{"../utils":32,"./DataReader":18}],21:[function(t,e,r){var i=t("./ArrayReader");function s(t){i.call(this,t)}t("../utils").inherits(s,i),s.prototype.readData=function(t){var e;return this.checkOffset(t),0===t?new Uint8Array(0):(e=this.data.subarray(this.zero+this.index,this.zero+this.index+t),this.index+=t,e)},e.exports=s},{"../utils":32,"./ArrayReader":17}],22:[function(t,e,r){var i=t("../utils"),s=t("../support"),n=t("./ArrayReader"),a=t("./StringReader"),o=t("./NodeBufferReader"),h=t("./Uint8ArrayReader");e.exports=function(t){var e=i.getTypeOf(t);return i.checkSupport(e),"string"!==e||s.uint8array?"nodebuffer"===e?new o(t):s.uint8array?new h(i.transformTo("uint8array",t)):new n(i.transformTo("array",t)):new a(t)}},{"../support":30,"../utils":32,"./ArrayReader":17,"./NodeBufferReader":19,"./StringReader":20,"./Uint8ArrayReader":21}],23:[function(t,e,r){r.LOCAL_FILE_HEADER="PK",r.CENTRAL_FILE_HEADER="PK",r.CENTRAL_DIRECTORY_END="PK",r.ZIP64_CENTRAL_DIRECTORY_LOCATOR="PK",r.ZIP64_CENTRAL_DIRECTORY_END="PK",r.DATA_DESCRIPTOR="PK\b"},{}],24:[function(t,e,r){var i=t("./GenericWorker"),s=t("../utils");function n(t){i.call(this,"ConvertWorker to "+t),this.destType=t}s.inherits(n,i),n.prototype.processChunk=function(t){this.push({data:s.transformTo(this.destType,t.data),meta:t.meta})},e.exports=n},{"../utils":32,"./GenericWorker":28}],25:[function(t,e,r){var i=t("./GenericWorker"),s=t("../crc32");function n(){i.call(this,"Crc32Probe"),this.withStreamInfo("crc32",0)}t("../utils").inherits(n,i),n.prototype.processChunk=function(t){this.streamInfo.crc32=s(t.data,this.streamInfo.crc32||0),this.push(t)},e.exports=n},{"../crc32":4,"../utils":32,"./GenericWorker":28}],26:[function(t,e,r){var i=t("../utils"),s=t("./GenericWorker");function n(t){s.call(this,"DataLengthProbe for "+t),this.propName=t,this.withStreamInfo(t,0)}i.inherits(n,s),n.prototype.processChunk=function(t){var e;t&&(e=this.streamInfo[this.propName]||0,this.streamInfo[this.propName]=e+t.data.length),s.prototype.processChunk.call(this,t)},e.exports=n},{"../utils":32,"./GenericWorker":28}],27:[function(t,e,r){var i=t("../utils"),s=t("./GenericWorker");function n(t){s.call(this,"DataWorker");var e=this;this.dataIsReady=!1,this.index=0,this.max=0,this.data=null,this.type="",this._tickScheduled=!1,t.then(function(t){e.dataIsReady=!0,e.data=t,e.max=t&&t.length||0,e.type=i.getTypeOf(t),e.isPaused||e._tickAndRepeat()},function(t){e.error(t)})}i.inherits(n,s),n.prototype.cleanUp=function(){s.prototype.cleanUp.call(this),this.data=null},n.prototype.resume=function(){return!!s.prototype.resume.call(this)&&(!this._tickScheduled&&this.dataIsReady&&(this._tickScheduled=!0,i.delay(this._tickAndRepeat,[],this)),!0)},n.prototype._tickAndRepeat=function(){this._tickScheduled=!1,this.isPaused||this.isFinished||(this._tick(),this.isFinished)||(i.delay(this._tickAndRepeat,[],this),this._tickScheduled=!0)},n.prototype._tick=function(){if(this.isPaused||this.isFinished)return!1;var t=null,e=Math.min(this.max,this.index+16384);if(this.index>=this.max)return this.end();switch(this.type){case"string":t=this.data.substring(this.index,e);break;case"uint8array":t=this.data.subarray(this.index,e);break;case"array":case"nodebuffer":t=this.data.slice(this.index,e)}return this.index=e,this.push({data:t,meta:{percent:this.max?this.index/this.max*100:0}})},e.exports=n},{"../utils":32,"./GenericWorker":28}],28:[function(t,e,r){function i(t){this.name=t||"default",this.streamInfo={},this.generatedError=null,this.extraStreamInfo={},this.isPaused=!0,this.isFinished=!1,this.isLocked=!1,this._listeners={data:[],end:[],error:[]},this.previous=null}i.prototype={push:function(t){this.emit("data",t)},end:function(){if(this.isFinished)return!1;this.flush();try{this.emit("end"),this.cleanUp(),this.isFinished=!0}catch(t){this.emit("error",t)}return!0},error:function(t){return!this.isFinished&&(this.isPaused?this.generatedError=t:(this.isFinished=!0,this.emit("error",t),this.previous&&this.previous.error(t),this.cleanUp()),!0)},on:function(t,e){return this._listeners[t].push(e),this},cleanUp:function(){this.streamInfo=this.generatedError=this.extraStreamInfo=null,this._listeners=[]},emit:function(t,e){if(this._listeners[t])for(var r=0;r "+t:t}},e.exports=i},{}],29:[function(t,e,r){var l=t("../utils"),s=t("./ConvertWorker"),n=t("./GenericWorker"),p=t("../base64"),i=t("../support"),a=t("../external"),o=null;if(i.nodestream)try{o=t("../nodejs/NodejsStreamOutputAdapter")}catch(t){}function h(t,e,r){var i=e;switch(e){case"blob":case"arraybuffer":i="uint8array";break;case"base64":i="string"}try{this._internalType=i,this._outputType=e,this._mimeType=r,l.checkSupport(i),this._worker=t.pipe(new s(i)),t.lock()}catch(t){this._worker=new n("error"),this._worker.error(t)}}h.prototype={accumulate:function(t){return o=this,h=t,new a.Promise(function(e,r){var i=[],s=o._internalType,n=o._outputType,a=o._mimeType;o.on("data",function(t,e){i.push(t),h&&h(e)}).on("error",function(t){i=[],r(t)}).on("end",function(){try{var t=function(t,e,r){switch(t){case"blob":return l.newBlob(l.transformTo("arraybuffer",e),r);case"base64":return p.encode(e);default:return l.transformTo(t,e)}}(n,function(t,e){for(var r=0,i=null,s=0,n=0;n>>6:(r<65536?e[s++]=224|r>>>12:(e[s++]=240|r>>>18,e[s++]=128|r>>>12&63),e[s++]=128|r>>>6&63),e[s++]=128|63&r);return e},s.utf8decode=function(t){if(l.nodebuffer)return h.transformTo("nodebuffer",t).toString("utf-8");for(var e,r,i=t=h.transformTo(l.uint8array?"uint8array":"array",t),s=i.length,n=new Array(2*s),a=0,o=0;o>10&1023,n[a++]=56320|1023&e)}return n.length!==a&&(n.subarray?n=n.subarray(0,a):n.length=a),h.applyFromCharCode(n)},h.inherits(n,r),n.prototype.processChunk=function(t){var e=h.transformTo(l.uint8array?"uint8array":"array",t.data),r=(this.leftOver&&this.leftOver.length&&(l.uint8array?(r=e,(e=new Uint8Array(r.length+this.leftOver.length)).set(this.leftOver,0),e.set(r,this.leftOver.length)):e=this.leftOver.concat(e),this.leftOver=null),function(t,e){for(var r=(e=(e=e||t.length)>t.length?t.length:e)-1;0<=r&&128==(192&t[r]);)r--;return!(r<0||0===r)&&r+c[t[r]]>e?r:e}(e)),i=e;r!==e.length&&(l.uint8array?(i=e.subarray(0,r),this.leftOver=e.subarray(r,e.length)):(i=e.slice(0,r),this.leftOver=e.slice(r,e.length))),this.push({data:s.utf8decode(i),meta:t.meta})},n.prototype.flush=function(){this.leftOver&&this.leftOver.length&&(this.push({data:s.utf8decode(this.leftOver),meta:{}}),this.leftOver=null)},s.Utf8DecodeWorker=n,h.inherits(a,r),a.prototype.processChunk=function(t){this.push({data:s.utf8encode(t.data),meta:t.meta})},s.Utf8EncodeWorker=a},{"./nodejsUtils":14,"./stream/GenericWorker":28,"./support":30,"./utils":32}],32:[function(t,e,a){var o=t("./support"),h=t("./base64"),r=t("./nodejsUtils"),i=t("set-immediate-shim"),l=t("./external");function s(t){return t}function p(t,e){for(var r=0;r>8;this.dir=!!(16&this.externalFileAttributes),0==t&&(this.dosPermissions=63&this.externalFileAttributes),3==t&&(this.unixPermissions=this.externalFileAttributes>>16&65535),this.dir||"/"!==this.fileNameStr.slice(-1)||(this.dir=!0)},parseZIP64ExtraField:function(t){var e;this.extraFields[1]&&(e=i(this.extraFields[1].value),this.uncompressedSize===s.MAX_VALUE_32BITS&&(this.uncompressedSize=e.readInt(8)),this.compressedSize===s.MAX_VALUE_32BITS&&(this.compressedSize=e.readInt(8)),this.localHeaderOffset===s.MAX_VALUE_32BITS&&(this.localHeaderOffset=e.readInt(8)),this.diskNumberStart===s.MAX_VALUE_32BITS)&&(this.diskNumberStart=e.readInt(4))},readExtraFields:function(t){var e,r,i,s=t.index+this.extraFieldsLength;for(this.extraFields||(this.extraFields={});t.index+4>>6:(r<65536?e[s++]=224|r>>>12:(e[s++]=240|r>>>18,e[s++]=128|r>>>12&63),e[s++]=128|r>>>6&63),e[s++]=128|63&r);return e},r.buf2binstring=function(t){return p(t,t.length)},r.binstring2buf=function(t){for(var e=new h.Buf8(t.length),r=0,i=e.length;r>10&1023,n[a++]=56320|1023&r)}return p(n,a)},r.utf8border=function(t,e){for(var r=(e=(e=e||t.length)>t.length?t.length:e)-1;0<=r&&128==(192&t[r]);)r--;return!(r<0||0===r)&&r+l[t[r]]>e?r:e}},{"./common":41}],43:[function(t,e,r){e.exports=function(t,e,r,i){for(var s=65535&t|0,n=t>>>16&65535|0,a=0;0!==r;){for(r-=a=2e3>>1:r>>>1;t[e]=r}return t}();e.exports=function(t,e,r,i){var s=o,n=i+r;t^=-1;for(var a=i;a>>8^s[255&(t^e[a])];return-1^t}},{}],46:[function(t,e,r){var o,c=t("../utils/common"),h=t("./trees"),f=t("./adler32"),d=t("./crc32"),i=t("./messages");function l(t,e){return t.msg=i[e],e}function p(t){return(t<<1)-(4t.avail_out?t.avail_out:r)&&(c.arraySet(t.output,e.pending_buf,e.pending_out,r,t.next_out),t.next_out+=r,e.pending_out+=r,t.total_out+=r,t.avail_out-=r,e.pending-=r,0===e.pending)&&(e.pending_out=0)}function y(t,e){h._tr_flush_block(t,0<=t.block_start?t.block_start:-1,t.strstart-t.block_start,e),t.block_start=t.strstart,m(t.strm)}function g(t,e){t.pending_buf[t.pending++]=e}function v(t,e){t.pending_buf[t.pending++]=e>>>8&255,t.pending_buf[t.pending++]=255&e}function n(t,e){var r,i,s=t.max_chain_length,n=t.strstart,a=t.prev_length,o=t.nice_match,h=t.strstart>t.w_size-262?t.strstart-(t.w_size-262):0,l=t.window,p=t.w_mask,c=t.prev,f=t.strstart+258,d=l[n+a-1],u=l[n+a];t.prev_length>=t.good_match&&(s>>=2),o>t.lookahead&&(o=t.lookahead);do{if(l[(r=e)+a]===u&&l[r+a-1]===d&&l[r]===l[n]&&l[++r]===l[n+1]){for(n+=2,r++;l[++n]===l[++r]&&l[++n]===l[++r]&&l[++n]===l[++r]&&l[++n]===l[++r]&&l[++n]===l[++r]&&l[++n]===l[++r]&&l[++n]===l[++r]&&l[++n]===l[++r]&&nh&&0!=--s);return a<=t.lookahead?a:t.lookahead}function _(t){var e,r,i,s,n,a,o,h,l,p=t.w_size;do{if(h=t.window_size-t.lookahead-t.strstart,t.strstart>=p+(p-262)){for(c.arraySet(t.window,t.window,p,p,0),t.match_start-=p,t.strstart-=p,t.block_start-=p,e=r=t.hash_size;i=t.head[--e],t.head[e]=p<=i?i-p:0,--r;);for(e=r=p;i=t.prev[--e],t.prev[e]=p<=i?i-p:0,--r;);h+=p}if(0===t.strm.avail_in)break;if(n=t.strm,a=t.window,o=t.strstart+t.lookahead,h=h,l=void 0,r=0===(l=(l=n.avail_in)>h?h:l)?0:(n.avail_in-=l,c.arraySet(a,n.input,n.next_in,l,o),1===n.state.wrap?n.adler=f(n.adler,a,l,o):2===n.state.wrap&&(n.adler=d(n.adler,a,l,o)),n.next_in+=l,n.total_in+=l,l),t.lookahead+=r,3<=t.lookahead+t.insert)for(s=t.strstart-t.insert,t.ins_h=t.window[s],t.ins_h=(t.ins_h<t.pending_buf_size-5&&(r=t.pending_buf_size-5);;){if(t.lookahead<=1){if(_(t),0===t.lookahead&&0===e)return 1;if(0===t.lookahead)break}t.strstart+=t.lookahead,t.lookahead=0;var i=t.block_start+r;if((0===t.strstart||t.strstart>=i)&&(t.lookahead=t.strstart-i,t.strstart=i,y(t,!1),0===t.strm.avail_out))return 1;if(t.strstart-t.block_start>=t.w_size-262&&(y(t,!1),0===t.strm.avail_out))return 1}return t.insert=0,4===e?(y(t,!0),0===t.strm.avail_out?3:4):(t.strstart>t.block_start&&(y(t,!1),t.strm.avail_out),1)}),new b(4,4,8,4,s),new b(4,5,16,8,s),new b(4,6,32,32,s),new b(4,4,16,16,a),new b(8,16,32,32,a),new b(8,16,128,128,a),new b(8,32,128,256,a),new b(32,128,258,1024,a),new b(32,258,258,4096,a)],r.deflateInit=function(t,e){return k(t,e,8,15,8,0)},r.deflateInit2=k,r.deflateReset=x,r.deflateResetKeep=P,r.deflateSetHeader=function(t,e){return!t||!t.state||2!==t.state.wrap?-2:(t.state.gzhead=e,0)},r.deflate=function(t,e){var r,i,s,n;if(!t||!t.state||5>8&255),g(i,i.gzhead.time>>16&255),g(i,i.gzhead.time>>24&255),g(i,9===i.level?2:2<=i.strategy||i.level<2?4:0),g(i,255&i.gzhead.os),i.gzhead.extra&&i.gzhead.extra.length&&(g(i,255&i.gzhead.extra.length),g(i,i.gzhead.extra.length>>8&255)),i.gzhead.hcrc&&(t.adler=d(t.adler,i.pending_buf,i.pending,0)),i.gzindex=0,i.status=69):(g(i,0),g(i,0),g(i,0),g(i,0),g(i,0),g(i,9===i.level?2:2<=i.strategy||i.level<2?4:0),g(i,3),i.status=113)):(a=8+(i.w_bits-8<<4)<<8,a|=(2<=i.strategy||i.level<2?0:i.level<6?1:6===i.level?2:3)<<6,0!==i.strstart&&(a|=32),a+=31-a%31,i.status=113,v(i,a),0!==i.strstart&&(v(i,t.adler>>>16),v(i,65535&t.adler)),t.adler=1)),69===i.status)if(i.gzhead.extra){for(s=i.pending;i.gzindex<(65535&i.gzhead.extra.length)&&(i.pending!==i.pending_buf_size||(i.gzhead.hcrc&&i.pending>s&&(t.adler=d(t.adler,i.pending_buf,i.pending-s,s)),m(t),s=i.pending,i.pending!==i.pending_buf_size));)g(i,255&i.gzhead.extra[i.gzindex]),i.gzindex++;i.gzhead.hcrc&&i.pending>s&&(t.adler=d(t.adler,i.pending_buf,i.pending-s,s)),i.gzindex===i.gzhead.extra.length&&(i.gzindex=0,i.status=73)}else i.status=73;if(73===i.status)if(i.gzhead.name){s=i.pending;do{if(i.pending===i.pending_buf_size&&(i.gzhead.hcrc&&i.pending>s&&(t.adler=d(t.adler,i.pending_buf,i.pending-s,s)),m(t),s=i.pending,i.pending===i.pending_buf_size)){n=1;break}}while(n=i.gzindexs&&(t.adler=d(t.adler,i.pending_buf,i.pending-s,s)),0===n&&(i.gzindex=0,i.status=91)}else i.status=91;if(91===i.status)if(i.gzhead.comment){s=i.pending;do{if(i.pending===i.pending_buf_size&&(i.gzhead.hcrc&&i.pending>s&&(t.adler=d(t.adler,i.pending_buf,i.pending-s,s)),m(t),s=i.pending,i.pending===i.pending_buf_size)){n=1;break}}while(n=i.gzindexs&&(t.adler=d(t.adler,i.pending_buf,i.pending-s,s)),0===n&&(i.status=103)}else i.status=103;if(103===i.status&&(i.gzhead.hcrc?(i.pending+2>i.pending_buf_size&&m(t),i.pending+2<=i.pending_buf_size&&(g(i,255&t.adler),g(i,t.adler>>8&255),t.adler=0,i.status=113)):i.status=113),0!==i.pending){if(m(t),0===t.avail_out)return i.last_flush=-1,0}else if(0===t.avail_in&&p(e)<=p(r)&&4!==e)return l(t,-5);if(666===i.status&&0!==t.avail_in)return l(t,-5);if(0!==t.avail_in||0!==i.lookahead||0!==e&&666!==i.status){var a=2===i.strategy?function(t,e){for(var r;;){if(0===t.lookahead&&(_(t),0===t.lookahead)){if(0===e)return 1;break}if(t.match_length=0,r=h._tr_tally(t,0,t.window[t.strstart]),t.lookahead--,t.strstart++,r&&(y(t,!1),0===t.strm.avail_out))return 1}return t.insert=0,4===e?(y(t,!0),0===t.strm.avail_out?3:4):t.last_lit&&(y(t,!1),0===t.strm.avail_out)?1:2}(i,e):3===i.strategy?function(t,e){for(var r,i,s,n,a=t.window;;){if(t.lookahead<=258){if(_(t),t.lookahead<=258&&0===e)return 1;if(0===t.lookahead)break}if(t.match_length=0,3<=t.lookahead&&0t.lookahead&&(t.match_length=t.lookahead)}if(3<=t.match_length?(r=h._tr_tally(t,1,t.match_length-3),t.lookahead-=t.match_length,t.strstart+=t.match_length,t.match_length=0):(r=h._tr_tally(t,0,t.window[t.strstart]),t.lookahead--,t.strstart++),r&&(y(t,!1),0===t.strm.avail_out))return 1}return t.insert=0,4===e?(y(t,!0),0===t.strm.avail_out?3:4):t.last_lit&&(y(t,!1),0===t.strm.avail_out)?1:2}(i,e):o[i.level].func(i,e);if(3!==a&&4!==a||(i.status=666),1===a||3===a)return 0===t.avail_out&&(i.last_flush=-1),0;if(2===a&&(1===e?h._tr_align(i):5!==e&&(h._tr_stored_block(i,0,0,!1),3===e)&&(u(i.head),0===i.lookahead)&&(i.strstart=0,i.block_start=0,i.insert=0),m(t),0===t.avail_out))return i.last_flush=-1,0}return 4!==e||!(i.wrap<=0)&&(2===i.wrap?(g(i,255&t.adler),g(i,t.adler>>8&255),g(i,t.adler>>16&255),g(i,t.adler>>24&255),g(i,255&t.total_in),g(i,t.total_in>>8&255),g(i,t.total_in>>16&255),g(i,t.total_in>>24&255)):(v(i,t.adler>>>16),v(i,65535&t.adler)),m(t),0=r.w_size&&(0===n&&(u(r.head),r.strstart=0,r.block_start=0,r.insert=0),h=new c.Buf8(r.w_size),c.arraySet(h,e,l-r.w_size,r.w_size,0),e=h,l=r.w_size),h=t.avail_in,a=t.next_in,o=t.input,t.avail_in=l,t.next_in=0,t.input=e,_(r);3<=r.lookahead;){for(i=r.strstart,s=r.lookahead-2;r.ins_h=(r.ins_h<>>=i=r>>>24,P-=i,0==(i=r>>>16&255))d[f++]=65535&r;else{if(!(16&i)){if(0==(64&i)){r=x[(65535&r)+(S&(1<>>=i,P-=i),P<15&&(S+=p[l++]<>>=i=r>>>24,P-=i,!(16&(i=r>>>16&255))){if(0==(64&i)){r=k[(65535&r)+(S&(1<y){t.msg="invalid distance too far back",h.mode=30;break t}if(S>>>=i,P-=i,n>(i=f-u)){if((i=n-i)>v&&h.sane){t.msg="invalid distance too far back",h.mode=30;break t}if(o=b,(a=0)===_){if(a+=g-i,i>3,S&=(1<<(P-=s<<3))-1,t.next_in=l,t.next_out=f,t.avail_in=l>>24&255)+(t>>>8&65280)+((65280&t)<<8)+((255&t)<<24)}function i(){this.mode=0,this.last=!1,this.wrap=0,this.havedict=!1,this.flags=0,this.dmax=0,this.check=0,this.total=0,this.head=null,this.wbits=0,this.wsize=0,this.whave=0,this.wnext=0,this.window=null,this.hold=0,this.bits=0,this.length=0,this.offset=0,this.extra=0,this.lencode=null,this.distcode=null,this.lenbits=0,this.distbits=0,this.ncode=0,this.nlen=0,this.ndist=0,this.have=0,this.next=null,this.lens=new F.Buf16(320),this.work=new F.Buf16(288),this.lendyn=null,this.distdyn=null,this.sane=0,this.back=0,this.was=0}function s(t){var e;return t&&t.state?(e=t.state,t.total_in=t.total_out=e.total=0,t.msg="",e.wrap&&(t.adler=1&e.wrap),e.mode=1,e.last=0,e.havedict=0,e.dmax=32768,e.head=null,e.hold=0,e.bits=0,e.lencode=e.lendyn=new F.Buf32(852),e.distcode=e.distdyn=new F.Buf32(592),e.sane=1,e.back=-1,0):-2}function n(t){var e;return t&&t.state?((e=t.state).wsize=0,e.whave=0,e.wnext=0,s(t)):-2}function a(t,e){var r,i;return!t||!t.state||(i=t.state,e<0?(r=0,e=-e):(r=1+(e>>4),e<48&&(e&=15)),e&&(e<8||15=t.wsize?(F.arraySet(t.window,e,r-t.wsize,t.wsize,0),t.wnext=0,t.whave=t.wsize):((s=t.wsize-t.wnext)>i&&(s=i),F.arraySet(t.window,e,r-i,s,t.wnext),(i-=s)?(F.arraySet(t.window,e,r-i,i,0),t.wnext=i,t.whave=t.wsize):(t.wnext+=s,t.wnext===t.wsize&&(t.wnext=0),t.whave>>8&255,r.check=D(r.check,A,2,0),p=l=0,r.mode=2;else if(r.flags=0,r.head&&(r.head.done=!1),!(1&r.wrap)||(((255&l)<<8)+(l>>8))%31)t.msg="incorrect header check",r.mode=30;else if(8!=(15&l))t.msg="unknown compression method",r.mode=30;else{if(p-=4,P=8+(15&(l>>>=4)),0===r.wbits)r.wbits=P;else if(P>r.wbits){t.msg="invalid window size",r.mode=30;break}r.dmax=1<>8&1),512&r.flags&&(A[0]=255&l,A[1]=l>>>8&255,r.check=D(r.check,A,2,0)),p=l=0,r.mode=3;case 3:for(;p<32;){if(0===o)break t;o--,l+=i[n++]<>>8&255,A[2]=l>>>16&255,A[3]=l>>>24&255,r.check=D(r.check,A,4,0)),p=l=0,r.mode=4;case 4:for(;p<16;){if(0===o)break t;o--,l+=i[n++]<>8),512&r.flags&&(A[0]=255&l,A[1]=l>>>8&255,r.check=D(r.check,A,2,0)),p=l=0,r.mode=5;case 5:if(1024&r.flags){for(;p<16;){if(0===o)break t;o--,l+=i[n++]<>>8&255,r.check=D(r.check,A,2,0)),p=l=0}else r.head&&(r.head.extra=null);r.mode=6;case 6:if(1024&r.flags&&((d=(d=r.length)>o?o:d)&&(r.head&&(P=r.head.extra_len-r.length,r.head.extra||(r.head.extra=new Array(r.head.extra_len)),F.arraySet(r.head.extra,i,n,d,P)),512&r.flags&&(r.check=D(r.check,i,d,n)),o-=d,n+=d,r.length-=d),r.length))break t;r.length=0,r.mode=7;case 7:if(2048&r.flags){if(0===o)break t;for(d=0;P=i[n+d++],r.head&&P&&r.length<65536&&(r.head.name+=String.fromCharCode(P)),P&&d>9&1,r.head.done=!0),t.adler=r.check=0,r.mode=12;break;case 10:for(;p<32;){if(0===o)break t;o--,l+=i[n++]<>>=7&p,p-=7&p,r.mode=27;else{for(;p<3;){if(0===o)break t;o--,l+=i[n++]<>>=1)){case 0:r.mode=14;break;case 1:C=I=void 0;var C,I=r;if(B){for(O=new F.Buf32(512),N=new F.Buf32(32),C=0;C<144;)I.lens[C++]=8;for(;C<256;)I.lens[C++]=9;for(;C<280;)I.lens[C++]=7;for(;C<288;)I.lens[C++]=8;for(z(1,I.lens,0,288,O,0,I.work,{bits:9}),C=0;C<32;)I.lens[C++]=5;z(2,I.lens,0,32,N,0,I.work,{bits:5}),B=!1}if(I.lencode=O,I.lenbits=9,I.distcode=N,I.distbits=5,r.mode=20,6!==e)break;l>>>=2,p-=2;break t;case 2:r.mode=17;break;case 3:t.msg="invalid block type",r.mode=30}l>>>=2,p-=2}break;case 14:for(l>>>=7&p,p-=7&p;p<32;){if(0===o)break t;o--,l+=i[n++]<>>16^65535)){t.msg="invalid stored block lengths",r.mode=30;break}if(r.length=65535&l,p=l=0,r.mode=15,6===e)break t;case 15:r.mode=16;case 16:if(d=r.length){if(0===(d=h<(d=o>>=5,p-=5,r.ndist=1+(31&l),l>>>=5,p-=5,r.ncode=4+(15&l),l>>>=4,p-=4,286>>=3,p-=3}for(;r.have<19;)r.lens[T[r.have++]]=0;if(r.lencode=r.lendyn,r.lenbits=7,k={bits:r.lenbits},x=z(0,r.lens,0,19,r.lencode,0,r.work,k),r.lenbits=k.bits,x){t.msg="invalid code lengths set",r.mode=30;break}r.have=0,r.mode=19;case 19:for(;r.have>>16&255,v=65535&w,!((y=w>>>24)<=p);){if(0===o)break t;o--,l+=i[n++]<>>=y,p-=y,r.lens[r.have++]=v;else{if(16===v){for(E=y+2;p>>=y,p-=y,0===r.have){t.msg="invalid bit length repeat",r.mode=30;break}P=r.lens[r.have-1],d=3+(3&l),l>>>=2,p-=2}else if(17===v){for(E=y+3;p>>=y)),l>>>=3,p=p-y-3}else{for(E=y+7;p>>=y)),l>>>=7,p=p-y-7}if(r.have+d>r.nlen+r.ndist){t.msg="invalid bit length repeat",r.mode=30;break}for(;d--;)r.lens[r.have++]=P}}if(30===r.mode)break;if(0===r.lens[256]){t.msg="invalid code -- missing end-of-block",r.mode=30;break}if(r.lenbits=9,k={bits:r.lenbits},x=z(1,r.lens,0,r.nlen,r.lencode,0,r.work,k),r.lenbits=k.bits,x){t.msg="invalid literal/lengths set",r.mode=30;break}if(r.distbits=6,r.distcode=r.distdyn,k={bits:r.distbits},x=z(2,r.lens,r.nlen,r.ndist,r.distcode,0,r.work,k),r.distbits=k.bits,x){t.msg="invalid distances set",r.mode=30;break}if(r.mode=20,6===e)break t;case 20:r.mode=21;case 21:if(6<=o&&258<=h){t.next_out=a,t.avail_out=h,t.next_in=n,t.avail_in=o,r.hold=l,r.bits=p,R(t,f),a=t.next_out,s=t.output,h=t.avail_out,n=t.next_in,i=t.input,o=t.avail_in,l=r.hold,p=r.bits,12===r.mode&&(r.back=-1);break}for(r.back=0;g=(w=r.lencode[l&(1<>>16&255,v=65535&w,!((y=w>>>24)<=p);){if(0===o)break t;o--,l+=i[n++]<>_)])>>>16&255,v=65535&w,!(_+(y=w>>>24)<=p);){if(0===o)break t;o--,l+=i[n++]<>>=_,p-=_,r.back+=_}if(l>>>=y,p-=y,r.back+=y,r.length=v,0===g){r.mode=26;break}if(32&g){r.back=-1,r.mode=12;break}if(64&g){t.msg="invalid literal/length code",r.mode=30;break}r.extra=15&g,r.mode=22;case 22:if(r.extra){for(E=r.extra;p>>=r.extra,p-=r.extra,r.back+=r.extra}r.was=r.length,r.mode=23;case 23:for(;g=(w=r.distcode[l&(1<>>16&255,v=65535&w,!((y=w>>>24)<=p);){if(0===o)break t;o--,l+=i[n++]<>_)])>>>16&255,v=65535&w,!(_+(y=w>>>24)<=p);){if(0===o)break t;o--,l+=i[n++]<>>=_,p-=_,r.back+=_}if(l>>>=y,p-=y,r.back+=y,64&g){t.msg="invalid distance code",r.mode=30;break}r.offset=v,r.extra=15&g,r.mode=24;case 24:if(r.extra){for(E=r.extra;p>>=r.extra,p-=r.extra,r.back+=r.extra}if(r.offset>r.dmax){t.msg="invalid distance too far back",r.mode=30;break}r.mode=25;case 25:if(0===h)break t;if(r.offset>(d=f-h)){if((d=r.offset-d)>r.whave&&r.sane){t.msg="invalid distance too far back",r.mode=30;break}u=d>r.wnext?(d-=r.wnext,r.wsize-d):r.wnext-d,d>r.length&&(d=r.length),m=r.window}else m=s,u=a-r.offset,d=r.length;for(h-=d=hd?(m=M[D+a[_]],T[C+a[_]]):(m=96,0),h=1<<(u=v-k),b=l=1<>k)+(l-=h)]=u<<24|m<<16|y|0,0!==l;);for(h=1<>=1;if(0!==h?A=(A&h-1)+h:A=0,_++,0==--I[v]){if(v===S)break;v=e[r+a[_]]}if(P>>7)]}function n(t,e){t.pending_buf[t.pending++]=255&e,t.pending_buf[t.pending++]=e>>>8&255}function x(t,e,r){t.bi_valid>16-r?(t.bi_buf|=e<>16-t.bi_valid,t.bi_valid+=r-16):(t.bi_buf|=e<>>=1,r<<=1,0<--e;);return r>>>1}function w(t,e,r){for(var i,s=new Array(16),n=0,a=1;a<=15;a++)s[a]=n=n+r[a-1]<<1;for(i=0;i<=e;i++){var o=t[2*i+1];0!==o&&(t[2*i]=E(s[o]++,o))}}function A(t){for(var e=0;e<286;e++)t.dyn_ltree[2*e]=0;for(e=0;e<30;e++)t.dyn_dtree[2*e]=0;for(e=0;e<19;e++)t.bl_tree[2*e]=0;t.dyn_ltree[512]=1,t.opt_len=t.static_len=0,t.last_lit=t.matches=0}function T(t){8>1;1<=r;r--)C(t,n,r);for(s=h;r=t.heap[1],t.heap[1]=t.heap[t.heap_len--],C(t,n,1),i=t.heap[1],t.heap[--t.heap_max]=r,t.heap[--t.heap_max]=i,n[2*s]=n[2*r]+n[2*i],t.depth[s]=(t.depth[r]>=t.depth[i]?t.depth[r]:t.depth[i])+1,n[2*r+1]=n[2*i+1]=s,t.heap[1]=s++,C(t,n,1),2<=t.heap_len;);t.heap[--t.heap_max]=t.heap[1];for(var p,c,f,d,u,m=t,y=e.dyn_tree,g=e.max_code,v=e.stat_desc.static_tree,_=e.stat_desc.has_stree,b=e.stat_desc.extra_bits,S=e.stat_desc.extra_base,P=e.stat_desc.max_length,x=0,k=0;k<=15;k++)m.bl_count[k]=0;for(y[2*m.heap[m.heap_max]+1]=0,p=m.heap_max+1;p<573;p++)(k=y[2*y[2*(c=m.heap[p])+1]+1]+1)>P&&(k=P,x++),y[2*c+1]=k,gg||(y[2*f+1]!==k&&(m.opt_len+=(k-y[2*f+1])*y[2*f],y[2*f+1]=k),c--)}w(n,l,t.bl_count)}function M(t,e,r){var i,s,n=-1,a=e[1],o=0,h=7,l=4;for(0===a&&(h=138,l=3),e[2*(r+1)+1]=65535,i=0;i<=r;i++)s=a,a=e[2*(i+1)+1],++o>=7;a<30;a++)for(_[a]=i<<7,e=0;e<1<>>=1)if(1&e&&0!==t.dyn_ltree[2*r])return 0;if(0!==t.dyn_ltree[18]||0!==t.dyn_ltree[20]||0!==t.dyn_ltree[26])return 1;for(r=32;r<256;r++)if(0!==t.dyn_ltree[2*r])return 1;return 0}(t)),F(t,t.l_desc),F(t,t.d_desc),o=function(t){var e;for(M(t,t.dyn_ltree,t.l_desc.max_code),M(t,t.dyn_dtree,t.d_desc.max_code),F(t,t.bl_desc),e=18;3<=e&&0===t.bl_tree[2*p[e]+1];e--);return t.opt_len+=3*(e+1)+5+5+4,e}(t),s=t.opt_len+3+7>>>3,(n=t.static_len+3+7>>>3)<=s&&(s=n)):s=n=r+5,r+4<=s&&-1!==e)z(t,e,r,i);else if(4===t.strategy||n===s)x(t,2+(i?1:0),3),I(t,c,f);else{x(t,4+(i?1:0),3);var h=t,l=(e=t.l_desc.max_code+1,r=t.d_desc.max_code+1,o+1);for(x(h,e-257,5),x(h,r-1,5),x(h,l-4,4),a=0;a>>8&255,t.pending_buf[t.d_buf+2*t.last_lit+1]=255&e,t.pending_buf[t.l_buf+t.last_lit]=255&r,t.last_lit++,0===e?t.dyn_ltree[2*r]++:(t.matches++,e--,t.dyn_ltree[2*(u[r]+256+1)]++,t.dyn_dtree[2*P(e)]++),t.last_lit===t.lit_bufsize-1},e._tr_align=function(t){x(t,2,3),k(t,256,c),16===(t=t).bi_valid?(n(t,t.bi_buf),t.bi_buf=0,t.bi_valid=0):8<=t.bi_valid&&(t.pending_buf[t.pending++]=255&t.bi_buf,t.bi_buf>>=8,t.bi_valid-=8)}},{"../utils/common":41}],53:[function(t,e,r){e.exports=function(){this.input=null,this.next_in=0,this.avail_in=0,this.total_in=0,this.output=null,this.next_out=0,this.avail_out=0,this.total_out=0,this.msg="",this.state=null,this.data_type=2,this.adler=0}},{}],54:[function(t,e,r){e.exports="function"==typeof setImmediate?setImmediate:function(){var t=[].slice.apply(arguments);t.splice(1,0,0),setTimeout.apply(null,t)}},{}]},{},[10])(10)});function _templateObject(){const t=_taggedTemplateLiteral(["\n* {\n box-sizing: border-box;\n padding: 0;\n margin: 0;\n}\n\n:host {\n --lottie-player-toolbar-height: 35px;\n --lottie-player-toolbar-background-color: transparent;\n --lottie-player-toolbar-icon-color: #999;\n --lottie-player-toolbar-icon-hover-color: #222;\n --lottie-player-toolbar-icon-active-color: #555;\n --lottie-player-seeker-track-color: #CCC;\n --lottie-player-seeker-thumb-color: rgba(0, 107, 120, 0.8);\n\n display: block;\n}\n\n.main {\n display: grid;\n grid-auto-columns: auto;\n grid-template-rows: auto;\n height: inherit;\n width: inherit;\n}\n\n.main.controls {\n grid-template-rows: 1fr var(--lottie-player-toolbar-height);\n}\n\n.animation {\n display: flex;\n justify-content: center;\n align-items: center;\n width: inherit;\n height: inherit;\n}\n\n.toolbar {\n display: grid;\n grid-template-columns: 32px 32px 1fr 32px;\n align-items: center;\n justify-items: center;\n background-color: var(--lottie-player-toolbar-background-color);\n}\n\n.toolbar button {\n cursor: pointer;\n fill: var(--lottie-player-toolbar-icon-color);\n display: flex;\n background: none;\n border: 0;\n padding: 0;\n outline: none;\n height: 100%;\n}\n\n.toolbar button:hover {\n fill: var(--lottie-player-toolbar-icon-hover-color);\n}\n\n.toolbar button.active {\n fill: var(--lottie-player-toolbar-icon-active-color);\n}\n\n.toolbar button svg {\n}\n\n.toolbar button.disabled svg {\n display: none;\n}\n\n.toolbar a {\n filter: grayscale(100%);\n display: flex;\n transition: filter .5s, opacity 0.5s;\n opacity: 0.4;\n height: 100%;\n align-items: center;\n}\n\n.toolbar a:hover {\n filter: none;\n display: flex;\n opacity: 1;\n}\n\n.toolbar a svg {\n}\n\n.seeker {\n -webkit-appearance: none;\n width: 95%;\n outline: none;\n}\n\n.seeker::-webkit-slider-runnable-track {\n width: 100%;\n height: 5px;\n cursor: pointer;\n background: var(--lottie-player-seeker-track-color);\n border-radius: 3px;\n}\n.seeker::-webkit-slider-thumb {\n height: 15px;\n width: 15px;\n border-radius: 50%;\n background: var(--lottie-player-seeker-thumb-color);\n cursor: pointer;\n -webkit-appearance: none;\n margin-top: -5px;\n}\n.seeker:focus::-webkit-slider-runnable-track {\n background: #999;\n}\n.seeker::-moz-range-track {\n width: 100%;\n height: 5px;\n cursor: pointer;\n background: var(--lottie-player-seeker-track-color);\n border-radius: 3px;\n}\n.seeker::-moz-range-thumb {\n height: 15px;\n width: 15px;\n border-radius: 50%;\n background: var(--lottie-player-seeker-thumb-color);\n cursor: pointer;\n}\n.seeker::-ms-track {\n width: 100%;\n height: 5px;\n cursor: pointer;\n background: transparent;\n border-color: transparent;\n color: transparent;\n}\n.seeker::-ms-fill-lower {\n background: var(--lottie-player-seeker-track-color);\n border-radius: 3px;\n}\n.seeker::-ms-fill-upper {\n background: var(--lottie-player-seeker-track-color);\n border-radius: 3px;\n}\n.seeker::-ms-thumb {\n border: 0;\n height: 15px;\n width: 15px;\n border-radius: 50%;\n background: var(--lottie-player-seeker-thumb-color);\n cursor: pointer;\n}\n.seeker:focus::-ms-fill-lower {\n background: var(--lottie-player-seeker-track-color);\n}\n.seeker:focus::-ms-fill-upper {\n background: var(--lottie-player-seeker-track-color);\n}\n\n.error {\n display: flex;\n justify-content: center;\n height: 100%;\n align-items: center;\n}\n"]);return _templateObject=function(){return t},t}var styles=css(_templateObject()),PlayerState,PlayMode,PlayerEvents;function _templateObject5(){const t=_taggedTemplateLiteral(['\n
\n ']);return _templateObject5=function(){return t},t}function _templateObject4(){const t=_taggedTemplateLiteral(["\n
\n ']);return _templateObject3=function(){return t},t}function _templateObject2(){const t=_taggedTemplateLiteral(['\n \n \n \n ']);return _templateObject2=function(){return t},t}function _templateObject$1(){const t=_taggedTemplateLiteral(['\n
\n \n \n \n \n \n \n
\n ']);return _templateObject$1=function(){return t},t}function fetchPath(i){return new Promise((r,e)=>{const t=new XMLHttpRequest;t.open("GET",i,!0),t.responseType="arraybuffer",t.send(),t.onreadystatechange=function(){4==t.readyState&&200==t.status&&jszip.loadAsync(t.response).then(i=>{i.file("manifest.json").async("string").then(t=>{t=JSON.parse(t);if(!("animations"in t))throw new Error("Manifest not found");if(0===t.animations.length)throw new Error("No animations listed in the manifest");t=t.animations[0];i.file("animations/".concat(t.id,".json")).async("string").then(t=>{const e=JSON.parse(t);"assets"in e&&Promise.all(e.assets.map(r=>{if(r.p&&null!=i.file("images/".concat(r.p)))return new Promise(e=>{i.file("images/".concat(r.p)).async("base64").then(t=>{r.p="data:;base64,"+t,r.e=1,e()})})})).then(()=>{r(e)})})})}).catch(t=>{e(t)})}})}PlayerState=exports.PlayerState||(exports.PlayerState={}),PlayerState.Loading="loading",PlayerState.Playing="playing",PlayerState.Paused="paused",PlayerState.Stopped="stopped",PlayerState.Frozen="frozen",PlayerState.Error="error",PlayMode=exports.PlayMode||(exports.PlayMode={}),PlayMode.Normal="normal",PlayMode.Bounce="bounce",PlayerEvents=exports.PlayerEvents||(exports.PlayerEvents={}),PlayerEvents.Load="load",PlayerEvents.Error="error",PlayerEvents.Ready="ready",PlayerEvents.Play="play",PlayerEvents.Pause="pause",PlayerEvents.Stop="stop",PlayerEvents.Freeze="freeze",PlayerEvents.Loop="loop",PlayerEvents.Complete="complete",PlayerEvents.Frame="frame",exports.DotLottiePlayer=class extends LitElement{constructor(){super(...arguments),this.mode=exports.PlayMode.Normal,this.autoplay=!1,this.background="transparent",this.controls=!1,this.direction=1,this.hover=!1,this.loop=!1,this.renderer="svg",this.speed=1,this.currentState=exports.PlayerState.Loading,this.intermission=1,this._counter=0}_onVisibilityChange(){document.hidden&&this.currentState===exports.PlayerState.Playing?this.freeze():this.currentState===exports.PlayerState.Frozen&&this.play()}_handleSeekChange(t){this._lottie&&!isNaN(t.target.value)&&(t=t.target.value/100*this._lottie.totalFrames,this.seek(t))}async load(t){if(this.shadowRoot){var e={container:this.container,loop:!1,autoplay:!1,renderer:this.renderer,rendererSettings:{scaleMode:"noScale",clearCanvas:!1,progressiveLoad:!0,hideOnTransparent:!0}};try{var r=await fetchPath(t);this._lottie&&this._lottie.destroy(),this._lottie=lottie_svg.loadAnimation(Object.assign(Object.assign({},e),{animationData:r}))}catch(t){return this.currentState=exports.PlayerState.Error,void this.dispatchEvent(new CustomEvent(exports.PlayerEvents.Error))}this._lottie&&(this._lottie.addEventListener("enterFrame",()=>{this.seeker=this._lottie.currentFrame/this._lottie.totalFrames*100,this.dispatchEvent(new CustomEvent(exports.PlayerEvents.Frame,{detail:{frame:this._lottie.currentFrame,seeker:this.seeker}}))}),this._lottie.addEventListener("complete",()=>{this.currentState!==exports.PlayerState.Playing||!this.loop||this.count&&this._counter>=this.count?this.dispatchEvent(new CustomEvent(exports.PlayerEvents.Complete)):this.mode===exports.PlayMode.Bounce?(this.count&&(this._counter+=.5),setTimeout(()=>{this.dispatchEvent(new CustomEvent(exports.PlayerEvents.Loop)),this.currentState===exports.PlayerState.Playing&&(this._lottie.setDirection(-1*this._lottie.playDirection),this._lottie.play())},this.intermission)):(this.count&&(this._counter+=1),window.setTimeout(()=>{this.dispatchEvent(new CustomEvent(exports.PlayerEvents.Loop)),this.currentState===exports.PlayerState.Playing&&(this._lottie.stop(),this._lottie.play())},this.intermission))}),this._lottie.addEventListener("DOMLoaded",()=>{this.dispatchEvent(new CustomEvent(exports.PlayerEvents.Ready))}),this._lottie.addEventListener("data_ready",()=>{this.dispatchEvent(new CustomEvent(exports.PlayerEvents.Load))}),this._lottie.addEventListener("data_failed",()=>{this.currentState=exports.PlayerState.Error,this.dispatchEvent(new CustomEvent(exports.PlayerEvents.Error))}),this.container.addEventListener("mouseenter",()=>{this.hover&&this.currentState!==exports.PlayerState.Playing&&this.play()}),this.container.addEventListener("mouseleave",()=>{this.hover&&this.currentState===exports.PlayerState.Playing&&this.stop()}),this.setSpeed(this.speed),this.setDirection(this.direction),this.autoplay)&&this.play()}}getLottie(){return this._lottie}play(){this._lottie&&(this._lottie.play(),this.currentState=exports.PlayerState.Playing,this.dispatchEvent(new CustomEvent(exports.PlayerEvents.Play)))}pause(){this._lottie&&(this._lottie.pause(),this.currentState=exports.PlayerState.Paused,this.dispatchEvent(new CustomEvent(exports.PlayerEvents.Pause)))}stop(){this._lottie&&(this._counter=0,this._lottie.stop(),this.currentState=exports.PlayerState.Stopped,this.dispatchEvent(new CustomEvent(exports.PlayerEvents.Stop)))}seek(t){this._lottie&&(t=t.toString().match(/^([0-9]+)(%?)$/))&&(t="%"===t[2]?this._lottie.totalFrames*Number(t[1])/100:t[1],this.seeker=t,this.currentState===exports.PlayerState.Playing?this._lottie.goToAndPlay(t,!0):(this._lottie.goToAndStop(t,!0),this._lottie.pause()))}snapshot(){let t=!(0{t[0].isIntersecting?this.currentState===exports.PlayerState.Frozen&&this.play():this.currentState===exports.PlayerState.Playing&&this.freeze()}),this._io.observe(this.container)),void 0!==document.hidden&&document.addEventListener("visibilitychange",()=>this._onVisibilityChange()),this.src&&await this.load(this.src)}disconnectedCallback(){this._io&&(this._io.disconnect(),this._io=void 0),document.removeEventListener("visibilitychange",()=>this._onVisibilityChange())}renderControls(){var t=this.currentState===exports.PlayerState.Playing,e=this.currentState===exports.PlayerState.Paused,r=this.currentState===exports.PlayerState.Stopped;return html(_templateObject$1(),this.togglePlay,t||e?"active":"",html((t?_templateObject2:_templateObject3)()),this.stop,r?"active":"",this.seeker,this._handleSeekChange,()=>{this._prevState=this.currentState,this.freeze()},()=>{this._prevState===exports.PlayerState.Playing&&this.play()},this.toggleLooping,this.loop?"active":"")}render(){var t=this.controls?"controls":"";return html(_templateObject4(),"main "+t,"background:"+this.background,this.currentState===exports.PlayerState.Error?html(_templateObject5()):void 0,this.controls?this.renderControls():void 0)}},__decorate([query(".animation")],exports.DotLottiePlayer.prototype,"container",void 0),__decorate([property()],exports.DotLottiePlayer.prototype,"mode",void 0),__decorate([property({type:Boolean})],exports.DotLottiePlayer.prototype,"autoplay",void 0),__decorate([property({type:String,reflect:!0})],exports.DotLottiePlayer.prototype,"background",void 0),__decorate([property({type:Boolean})],exports.DotLottiePlayer.prototype,"controls",void 0),__decorate([property({type:Number})],exports.DotLottiePlayer.prototype,"count",void 0),__decorate([property({type:Number})],exports.DotLottiePlayer.prototype,"direction",void 0),__decorate([property({type:Boolean})],exports.DotLottiePlayer.prototype,"hover",void 0),__decorate([property({type:Boolean,reflect:!0})],exports.DotLottiePlayer.prototype,"loop",void 0),__decorate([property({type:String})],exports.DotLottiePlayer.prototype,"renderer",void 0),__decorate([property({type:Number})],exports.DotLottiePlayer.prototype,"speed",void 0),__decorate([property({type:String})],exports.DotLottiePlayer.prototype,"src",void 0),__decorate([property({type:String})],exports.DotLottiePlayer.prototype,"currentState",void 0),__decorate([property()],exports.DotLottiePlayer.prototype,"seeker",void 0),__decorate([property()],exports.DotLottiePlayer.prototype,"intermission",void 0),exports.DotLottiePlayer=__decorate([customElement("dotlottie-player")],exports.DotLottiePlayer),exports.fetchPath=fetchPath,Object.defineProperty(exports,"__esModule",{value:!0})}); \ No newline at end of file diff --git a/public/lottie/refresh-dark.lottie b/public/lottie/refresh-dark.lottie new file mode 100644 index 0000000000..a029a1f114 Binary files /dev/null and b/public/lottie/refresh-dark.lottie differ diff --git a/public/lottie/refresh-light.lottie b/public/lottie/refresh-light.lottie new file mode 100644 index 0000000000..cd14ae8917 Binary files /dev/null and b/public/lottie/refresh-light.lottie differ diff --git a/public/lottie/trending-dark.lottie b/public/lottie/trending-dark.lottie new file mode 100644 index 0000000000..8cf537a92f Binary files /dev/null and b/public/lottie/trending-dark.lottie differ diff --git a/public/lottie/trending-light.lottie b/public/lottie/trending-light.lottie new file mode 100644 index 0000000000..785b0274c2 Binary files /dev/null and b/public/lottie/trending-light.lottie differ diff --git a/public/lottie/view-dark.lottie b/public/lottie/view-dark.lottie new file mode 100644 index 0000000000..5a99974e51 Binary files /dev/null and b/public/lottie/view-dark.lottie differ diff --git a/public/lottie/view-light.lottie b/public/lottie/view-light.lottie new file mode 100644 index 0000000000..df45c2de03 Binary files /dev/null and b/public/lottie/view-light.lottie differ diff --git a/src/App.tsx b/src/App.tsx index 49cd81b2e3..0dbdddda34 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,14 +1,16 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only +import React from 'react'; +import { I18nextProvider } from 'react-i18next'; import { DefaultNetwork } from 'consts'; import { ThemesProvider } from 'contexts/Themes'; import { i18next } from 'locale'; import { Providers } from 'Providers'; -import React from 'react'; -import { I18nextProvider } from 'react-i18next'; +import { NetworkProvider } from 'contexts/Network'; +import { ActiveAccountsProvider } from 'contexts/ActiveAccounts'; -const App: React.FC = () => { +export const App: React.FC = () => { let network = localStorage.getItem('network'); if (network === null) { @@ -19,10 +21,12 @@ const App: React.FC = () => { return ( - + + + + + ); }; - -export default App; diff --git a/src/Providers.tsx b/src/Providers.tsx index ed8b715b0b..51bca58a5d 100644 --- a/src/Providers.tsx +++ b/src/Providers.tsx @@ -1,91 +1,112 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import { AccountProvider } from 'contexts/Account'; -import { APIProvider, useApi } from 'contexts/Api'; import { BalancesProvider } from 'contexts/Balances'; -import { ConnectProvider } from 'contexts/Connect'; -import { ExtensionsProvider } from 'contexts/Extensions'; +import { BondedProvider } from 'contexts/Bonded'; +import { + ExtensionsProvider, + ExtensionAccountsProvider, + OverlayProvider, +} from '@polkadot-cloud/react/providers'; import { ExtrinsicsProvider } from 'contexts/Extrinsics'; +import { FastUnstakeProvider } from 'contexts/FastUnstake'; import { FiltersProvider } from 'contexts/Filters'; +import { LedgerHardwareProvider } from 'contexts/Hardware/Ledger'; +import { VaultHardwareProvider } from 'contexts/Hardware/Vault'; import { HelpProvider } from 'contexts/Help'; +import { IdentitiesProvider } from 'contexts/Identities'; import { MenuProvider } from 'contexts/Menu'; -import { ModalProvider } from 'contexts/Modal'; -import { NetworkMetricsProvider } from 'contexts/Network'; +import { MigrateProvider } from 'contexts/Migrate'; +import { NetworkMetricsProvider } from 'contexts/NetworkMetrics'; import { NotificationsProvider } from 'contexts/Notifications'; -import { OverlayProvider } from 'contexts/Overlay'; +import { PromptProvider } from 'contexts/Prompt'; +import { PluginsProvider } from 'contexts/Plugins'; import { ActivePoolsProvider } from 'contexts/Pools/ActivePools'; import { BondedPoolsProvider } from 'contexts/Pools/BondedPools'; import { PoolMembersProvider } from 'contexts/Pools/PoolMembers'; import { PoolMembershipsProvider } from 'contexts/Pools/PoolMemberships'; import { PoolsConfigProvider } from 'contexts/Pools/PoolsConfig'; -import { SessionEraProvider } from 'contexts/SessionEra'; +import { ProxiesProvider } from 'contexts/Proxies'; +import { SetupProvider } from 'contexts/Setup'; import { StakingProvider } from 'contexts/Staking'; -import { useTheme } from 'contexts/Themes'; +import { SubscanProvider } from 'contexts/Plugins/Subscan'; import { TooltipProvider } from 'contexts/Tooltip'; import { TransferOptionsProvider } from 'contexts/TransferOptions'; -import { TxFeesProvider } from 'contexts/TxFees'; +import { TxMetaProvider } from 'contexts/TxMeta'; import { UIProvider } from 'contexts/UI'; -import { ValidatorsProvider } from 'contexts/Validators'; +import { ValidatorsProvider } from 'contexts/Validators/ValidatorEntries'; +import { FavoriteValidatorsProvider } from 'contexts/Validators/FavoriteValidators'; +import { PayoutsProvider } from 'contexts/Payouts'; +import { PolkawatchProvider } from 'contexts/Plugins/Polkawatch'; +import { useNetwork } from 'contexts/Network'; +import { APIProvider } from 'contexts/Api'; +import { ThemedRouter } from 'Themes'; +import type { AnyJson } from 'types'; +import type { FC } from 'react'; import { withProviders } from 'library/Hooks'; -import Router from 'Router'; -import { ThemeProvider } from 'styled-components'; -import { EntryWrapper as Wrapper } from 'Wrappers'; +import { OtherAccountsProvider } from 'contexts/Connect/OtherAccounts'; +import { useActiveAccounts } from 'contexts/ActiveAccounts'; +import { DappName } from 'consts'; +import { ImportedAccountsProvider } from 'contexts/Connect/ImportedAccounts'; +import { PoolPerformanceProvider } from 'contexts/Pools/PoolPerformance'; import { CereStatsProvider } from './contexts/CereStats'; -// `polkadot-dashboard-ui` theme classes are inserted here. -export const WrappedRouter = () => { - const { mode } = useTheme(); - const { network } = useApi(); - - return ( - - - - ); -}; +// Embed providers from hook. +export const Providers = () => { + const networkAllData = useNetwork(); + console.warn(networkAllData); + const { + network, + networkData: { ss58 }, + } = useNetwork(); + const { activeAccount, setActiveAccount } = useActiveAccounts(); -// App-specific theme classes are inserted here. -export const ThemedRouter = () => { - const { mode } = useTheme(); - const { network } = useApi(); + // !! Provider order matters + const providers: Array | [FC, AnyJson]> = [ + [APIProvider, { network }], + FiltersProvider, + NotificationsProvider, + PluginsProvider, + VaultHardwareProvider, + LedgerHardwareProvider, + ExtensionsProvider, + [ + ExtensionAccountsProvider, + { dappName: DappName, network, ss58, activeAccount, setActiveAccount }, + ], + OtherAccountsProvider, + ImportedAccountsProvider, + HelpProvider, + NetworkMetricsProvider, + SubscanProvider, + PolkawatchProvider, + IdentitiesProvider, + ProxiesProvider, + BalancesProvider, + BondedProvider, + StakingProvider, + PoolsConfigProvider, + BondedPoolsProvider, + PoolMembershipsProvider, + PoolMembersProvider, + ActivePoolsProvider, + TransferOptionsProvider, + ValidatorsProvider, + FavoriteValidatorsProvider, + FastUnstakeProvider, + PayoutsProvider, + PoolPerformanceProvider, + UIProvider, + SetupProvider, + MenuProvider, + TooltipProvider, + TxMetaProvider, + ExtrinsicsProvider, + OverlayProvider, + PromptProvider, + MigrateProvider, + CereStatsProvider, + ]; - return ( - - - - ); + return <>{withProviders(providers, ThemedRouter)}; }; - -export const Providers = withProviders( - FiltersProvider, - APIProvider, - ExtensionsProvider, - ConnectProvider, - HelpProvider, - NetworkMetricsProvider, - AccountProvider, - BalancesProvider, - StakingProvider, - PoolsConfigProvider, - BondedPoolsProvider, - PoolMembershipsProvider, - PoolMembersProvider, - ActivePoolsProvider, - TransferOptionsProvider, - ValidatorsProvider, - UIProvider, - CereStatsProvider, - MenuProvider, - TooltipProvider, - NotificationsProvider, - TxFeesProvider, - ExtrinsicsProvider, - ModalProvider, - SessionEraProvider, - OverlayProvider -)(ThemedRouter); - -export default Providers; diff --git a/src/Router.tsx b/src/Router.tsx index c9d21762bb..ab504685f1 100644 --- a/src/Router.tsx +++ b/src/Router.tsx @@ -1,21 +1,9 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import { PAGES_CONFIG } from 'config/pages'; -import { TitleDefault } from 'consts'; -import { useApi } from 'contexts/Api'; -import { useUi } from 'contexts/UI'; +import { Body, Main, Page, Side } from '@polkadot-cloud/react'; +import { extractUrlValue } from '@polkadot-cloud/utils'; import { AnimatePresence } from 'framer-motion'; -import { ErrorFallbackApp, ErrorFallbackRoutes } from 'library/ErrorBoundary'; -import { Headers } from 'library/Headers'; -import { Help } from 'library/Help'; -import { Menu } from 'library/Menu'; -import { NetworkBar } from 'library/NetworkBar'; -import Notifications from 'library/Notifications'; -import { Overlay } from 'library/Overlay'; -import SideMenu from 'library/SideMenu'; -import { Tooltip } from 'library/Tooltip'; -import { Modal } from 'modals'; import { useEffect, useRef } from 'react'; import { ErrorBoundary } from 'react-error-boundary'; import { Helmet } from 'react-helmet'; @@ -27,65 +15,105 @@ import { Routes, useLocation, } from 'react-router-dom'; -import { - BodyInterfaceWrapper, - MainInterfaceWrapper, - PageWrapper, - SideInterfaceWrapper, -} from 'Wrappers'; +import { Prompt } from 'library/Prompt'; +import { PagesConfig } from 'config/pages'; +import { useNotifications } from 'contexts/Notifications'; +import { useUi } from 'contexts/UI'; +import { ErrorFallbackApp, ErrorFallbackRoutes } from 'library/ErrorBoundary'; +import { Headers } from 'library/Headers'; +import { Help } from 'library/Help'; +import { Menu } from 'library/Menu'; +import { NetworkBar } from 'library/NetworkBar'; +import { Notifications } from 'library/Notifications'; +import { SideMenu } from 'library/SideMenu'; +import { Tooltip } from 'library/Tooltip'; +import { Overlays } from 'overlay'; +import { useNetwork } from 'contexts/Network'; +import { useActiveAccounts } from 'contexts/ActiveAccounts'; +import { useOtherAccounts } from 'contexts/Connect/OtherAccounts'; +import { useImportedAccounts } from 'contexts/Connect/ImportedAccounts'; +import { SideMenuMaximisedWidth } from 'consts'; export const RouterInner = () => { - const { network } = useApi(); + const { t } = useTranslation(); + const { network } = useNetwork(); const { pathname } = useLocation(); + const { accounts } = useImportedAccounts(); + const { addNotification } = useNotifications(); + const { accountsInitialised } = useOtherAccounts(); + const { activeAccount, setActiveAccount } = useActiveAccounts(); const { sideMenuOpen, sideMenuMinimised, setContainerRefs } = useUi(); - const { t } = useTranslation('base'); - // scroll to top of the window on every page change or network change + // Scroll to top of the window on every page change or network change. useEffect(() => { window.scrollTo(0, 0); }, [pathname, network]); - // set references to UI context and make available throughout app + // Set references to UI context and make available throughout app. useEffect(() => { setContainerRefs({ mainInterface: mainInterfaceRef, }); }, []); - // references to outer containers + // Open default account modal if url var present and accounts initialised. + useEffect(() => { + if (accountsInitialised) { + const aUrl = extractUrlValue('a'); + if (aUrl) { + const account = accounts.find((a) => a.address === aUrl); + if (account && aUrl !== activeAccount) { + setActiveAccount(account?.address || null); + addNotification({ + title: t('accountConnected', { ns: 'library' }), + subtitle: `${t('connectedTo', { ns: 'library' })} ${ + account?.name || aUrl + }.`, + }); + } + } + } + }, [accountsInitialised]); + + // References to outer containers const mainInterfaceRef = useRef(null); return ( - - {/* Modal: closed by default */} - + {/* Help: closed by default */} + {/* Overlays: modal and canvas. Closed by default */} + + {/* Menu: closed by default */} {/* Tooltip: invisible by default */} - {/* Overlay: closed by default */} - + {/* Prompt: closed by default */} + {/* Left side menu */} - + - + {/* Main content window */} - +
{/* Fixed headers */} - {PAGES_CONFIG.map((page, i) => { + {PagesConfig.map((page, i) => { const { Entry, hash, key } = page; return ( @@ -93,18 +121,15 @@ export const RouterInner = () => { key={`main_interface_page_${i}`} path={hash} element={ - + - {`${t(key)} : ${TitleDefault}`} + {`${t(key, { ns: 'base' })} : ${t('title', { + context: `${network}`, + ns: 'base', + })}`} - + } /> ); @@ -117,8 +142,8 @@ export const RouterInner = () => { - - +
+ {/* Network status and network details */} @@ -129,11 +154,8 @@ export const RouterInner = () => { ); }; -export const Router = () => { - return ( - - - - ); -}; -export default Router; +export const Router = () => ( + + + +); diff --git a/src/Themes.tsx b/src/Themes.tsx new file mode 100644 index 0000000000..1aa9738b58 --- /dev/null +++ b/src/Themes.tsx @@ -0,0 +1,23 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { ThemeProvider } from 'styled-components'; +import { Entry } from '@polkadot-cloud/react'; +import { Router } from 'Router'; +import { useTheme } from 'contexts/Themes'; +import { useNetwork } from 'contexts/Network'; + +// light / dark `mode` added to styled-components provider +// `@polkadot-cloud/react` themes are added to `Entry`. +export const ThemedRouter = () => { + const { mode } = useTheme(); + const { network } = useNetwork(); + + return ( + + + + + + ); +}; diff --git a/src/Utils.ts b/src/Utils.ts index 059ce67f22..6309e02861 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -4,8 +4,8 @@ import { decodeAddress, encodeAddress } from '@polkadot/keyring'; import { hexToU8a, isHex, u8aToString, u8aUnwrapBytes } from '@polkadot/util'; import BN from 'bn.js'; -import { MutableRefObject } from 'react'; -import { AnyMetaBatch } from 'types/index'; +import type { MutableRefObject } from 'react'; +import type { AnyMetaBatch } from 'types/index'; export const clipAddress = (val: string) => { if (typeof val !== 'string') { diff --git a/src/Wrappers.tsx b/src/Wrappers.tsx deleted file mode 100644 index ed186553b6..0000000000 --- a/src/Wrappers.tsx +++ /dev/null @@ -1,458 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { - InterfaceMaximumWidth, - ShowAccountsButtonWidthThreshold, - SideMenuMaximisedWidth, - SideMenuMinimisedWidth, - SideMenuStickyThreshold, -} from 'consts'; -import { motion } from 'framer-motion'; -import styled from 'styled-components'; -import { - backgroundGradient, - backgroundPrimary, - borderPrimary, - buttonSecondaryBackground, - textPrimary, - textSecondary, -} from 'theme'; -import { - InterfaceLayoutProps, - PageRowWrapperProps, - PageTitleWrapperProps, - SideInterfaceWrapperProps, -} from 'types/styles'; - -/* EntryWrapper - * - * Highest level app component. - * Provides global styling for headers and other global - * classes used throughout the app and possibly the library. - */ -export const EntryWrapper = styled.div` - background: ${backgroundGradient}; - width: 100%; - background-attachment: fixed; - display: flex; - flex-flow: column nowrap; - min-height: 100vh; - flex-grow: 1; - - h1 { - color: ${textPrimary}; - } - h2 { - color: ${textPrimary}; - } - h3 { - color: ${textPrimary}; - } - h4 { - color: ${textPrimary}; - } - h5 { - color: ${textPrimary}; - } - p { - color: ${textSecondary}; - } - a { - color: ${textSecondary}; - } - input { - color: ${textPrimary}; - } - - path.primary { - fill: ${textPrimary}; - } - - ellipse.primary { - fill: ${textPrimary}; - } - - input:focus, - textarea:focus, - select:focus { - outline: none; - } - - input { - border: none; - padding: 0.7rem 0rem; - font-size: 1.1rem; - background: none; - transition: all 0.1s; - } - - input::placeholder { - color: #aaa; - } - - .textbox, - .textbox:focus { - border-bottom: 1px solid #ddd; - } - - .searchbox, - .searchbox:focus { - border: 1px solid #ddd; - } - - .page-padding { - padding-left: 1.25rem; - padding-right: 1.25rem; - - @media (min-width: ${ShowAccountsButtonWidthThreshold + 1}px) { - padding-left: 2.25rem; - padding-right: 2.25rem; - } - @media (min-width: ${SideMenuStickyThreshold + 1}px) { - padding: 0 5rem 0 2.5rem; - } - @media (min-width: 1500px) { - padding: 0 5rem 0 2.5rem; - } - } -`; - -/* BodyInterfaceWrapper - * - * An element that houses SideInterface and MainInterface. - * Used once in Router. - */ -export const BodyInterfaceWrapper = styled.div` - display: flex; - flex-flow: row nowrap; - position: relative; - flex-grow: 1; -`; - -/* SideInterfaceWrapper - * - * An element that houses the side menu and handles resizing - * on smaller screens. - * Used once in Router. - */ -export const SideInterfaceWrapper = styled.div` - height: 100vh; - display: flex; - flex-flow: column nowrap; - position: sticky; - top: 0px; - z-index: 7; - flex: 0; - overflow: hidden; - min-width: ${(props) => - props.minimised - ? `${SideMenuMinimisedWidth}px` - : `${SideMenuMaximisedWidth}px`}; - max-width: ${(props) => - props.minimised - ? `${SideMenuMinimisedWidth}px` - : `${SideMenuMaximisedWidth}px`}; - transition: all 0.5s cubic-bezier(0.1, 1, 0.2, 1); - - @media (max-width: ${SideMenuStickyThreshold}px) { - position: fixed; - top: 0; - left: ${(props) => (props.open ? 0 : `-${SideMenuMaximisedWidth}px`)}; - } -`; - -/* MainInterfaceWrapper - * - * A column flex wrapper that hosts the main page content. - * Used once in Router. - */ -export const MainInterfaceWrapper = styled.div` - flex: 1; - display: flex; - flex-flow: column nowrap; - position: relative; -`; - -/* PageWrapper - * - * A motion.div that wraps every page. - * Transitions can be applied to this wrapper that will - * affect the entire page. - */ -export const PageWrapper = styled(motion.div)` - max-width: ${InterfaceMaximumWidth}px; - display: flex; - flex-flow: column nowrap; - padding-bottom: 4.5rem; - width: 100%; - margin: 0 auto; -`; - -/* PageTitleWrapper - * - * The element that wraps a page title. Determines the padding - * and position relative to top of screen when the element - * is stuck. - */ -export const PageTitleWrapper = styled.header` - background: ${backgroundPrimary}; - position: sticky; - top: 0px; - padding-top: ${(props) => (props.sticky ? '1.5rem' : '0.5rem')}; - margin-top: 4rem; - margin-bottom: 0.25rem; - padding-bottom: ${(props) => (props.sticky ? '0.25rem' : 0)}; - width: 100%; - z-index: 5; - display: flex; - flex-flow: column wrap; - justify-content: flex-end; - transition: padding 0.3s ease-out; - - @media (max-width: ${SideMenuStickyThreshold}px) { - top: 4rem; - padding-top: 0.75rem; - padding-bottom: 0.5rem; - } - - .title { - display: flex; - flex-flow: row wrap; - align-items: center; - width: 100%; - margin-bottom: ${(props) => (props.sticky ? '0.75rem ' : 0)}; - - > div { - &:last-child { - padding-left: 1rem; - flex-grow: 1; - } - } - - button { - color: ${textSecondary}; - border: 1px solid ${borderPrimary}; - padding: 0.5rem 0.75rem; - margin: 0; - border-radius: 0.75rem; - font-size: 1.1rem; - - &:hover { - background: ${buttonSecondaryBackground}; - } - - .icon { - margin-left: 0.75rem; - } - } - } - - h1 { - font-family: 'Unbounded', 'sans-serif', sans-serif; - font-size: ${(props) => (props.sticky ? '1.4rem ' : '1.75rem')}; - @media (max-width: ${SideMenuStickyThreshold}px) { - font-size: 1.5rem; - } - transition: font 0.5s; - margin: 0; - } - - .tabs { - overflow: hidden; - max-width: ${InterfaceMaximumWidth}px; - transition: margin 0.2s; - height: 3.6rem; - border-bottom: ${(props) => (props.sticky ? '0px' : '1px solid')}; - border-bottom-color: ${borderPrimary}; - - margin-top: ${(props) => (props.sticky ? '0.5rem' : '0.9rem')}; - @media (max-width: ${SideMenuStickyThreshold}px) { - margin-top: 0.5rem; - } - - > .scroll { - width: 100%; - height: 4.5rem; - overflow-x: auto; - overflow-y: hidden; - } - - .inner { - display: flex; - flex-flow: row nowrap; - - > button { - padding: 0.65rem 1rem; - margin-bottom: 0.5rem; - margin-right: 0.75rem; - font-size: ${(props) => (props.sticky ? '1.05rem' : '1.15rem')}; - color: ${textSecondary}; - transition: opacity 0.1s, font-size 0.1s; - border-radius: 0.5rem; - - &.active { - background: ${buttonSecondaryBackground}; - } - &:last-child { - margin-right: 0; - } - &:hover { - opacity: 0.8; - } - } - } - } -`; - -/* MenuPaddingWrapper - * - * A fixed block that is used to hide scrollable content - * on smaller screens when a PageTitle is fixed. - * Purely cosmetic. Applied in Pagetitle. - */ -export const MenuPaddingWrapper = styled.div` - background: ${backgroundPrimary}; - position: fixed; - top: 0px; - width: 100%; - height: 4rem; - z-index: 4; - display: none; - @media (max-width: ${SideMenuStickyThreshold}px) { - display: block; - } -`; - -/* PageRowWrapper - * - * Used to separate page content based on rows. - * Commonly used with RowPrimaryWrapper and RowSecondaryWrapper. - */ -export const PageRowWrapper = styled.div` - margin-top: ${(props) => (props.noVerticalSpacer === true ? '0' : '1rem')}; - margin-bottom: ${(props) => (props.noVerticalSpacer === true ? '0' : '1rem')}; - display: flex; - flex-shrink: 0; - flex-flow: row wrap; - width: 100%; - /* kill heading padding, already applied to wrapper */ - h1, - h2, - h3, - h4 { - margin-top: 0; - } -`; - -/* RowPrimaryWrapper - * - * The primary module in a PageRow. - */ -export const RowPrimaryWrapper = styled.div` - order: ${(props) => props.vOrder}; - flex: 1; - flex-basis: 100%; - max-width: 100%; - - @media (min-width: ${(props) => props.thresholdStickyMenu + 1}px) { - ${(props) => props.hOrder === 0 && ' padding-right: 0.75rem;'} - ${(props) => props.hOrder === 1 && 'padding-left: 0.75rem;'} - order: ${(props) => props.hOrder}; - flex: 1; - flex-basis: 56%; - width: 56%; - max-width: ${(props) => (props.maxWidth ? props.maxWidth : 'none')}; - } - - @media (min-width: ${(props) => props.thresholdFullWidth + 400}px) { - flex-basis: 62%; - width: 62%; - max-width: ${(props) => (props.maxWidth ? props.maxWidth : 'none')}; - } -`; - -/* RowSecondaryWrapper - * - * The secondary module in a PageRow. - */ -export const RowSecondaryWrapper = styled.div` - order: ${(props) => props.vOrder}; - flex-basis: 100%; - width: 100%; - border-radius: 1rem; - - @media (min-width: ${(props) => props.thresholdStickyMenu + 1}px) { - ${(props) => props.hOrder === 1 && ' padding-left: 0.75rem;'} - ${(props) => props.hOrder === 0 && 'padding-right: 0.75rem;'} - order: ${(props) => props.hOrder}; - flex: 1; - flex-basis: 44%; - width: 44%; - max-width: ${(props) => (props.maxWidth ? props.maxWidth : 'none')}; - } - - @media (min-width: ${(props) => props.thresholdFullWidth + 400}px) { - flex-basis: 38%; - max-width: ${(props) => (props.maxWidth ? props.maxWidth : '38%')}; - } -`; - -/* Separator - * - * A horizontal spacer with a bottom border. - * General spacer for separating content by row. - */ -export const Separator = styled.div` - border-bottom: 1px solid ${borderPrimary}; - width: 100%; - margin: 0.75rem 0; -`; - -/* TopBarWrapper - * - * Positioned under titles for a Go Back button and other page header info. - */ -export const TopBarWrapper = styled.div` - display: flex; - flex-flow: row wrap; - align-items: center; - border-bottom: 1px solid ${borderPrimary}; - padding-top: 0.75rem; - padding-bottom: 0.75rem; - width: 100%; - margin-bottom: 0.25rem; - - > span { - margin-right: 1rem; - } - - h3 { - color: ${textSecondary}; - font-size: 1.15rem; - margin: 0.25rem 0; - min-height: 2rem; - } - - .right { - flex: 1 1 0%; - display: flex; - flex-flow: row wrap; - justify-content: flex-end; - - button { - margin: 0 0 0 1rem; - } - } -`; - -/* ButtonRowWrapper - * - * A flex container for a row of buttons - */ -export const ButtonRowWrapper = styled.div<{ verticalSpacing?: boolean }>` - display: flex; - align-items: center; - justify-content: flex-start; - margin-top: ${(props) => (props.verticalSpacing ? '1rem' : 0)}; -`; diff --git a/src/canvas/ManageNominations/Prompts/FavoritesPrompt.tsx b/src/canvas/ManageNominations/Prompts/FavoritesPrompt.tsx new file mode 100644 index 0000000000..032af6190c --- /dev/null +++ b/src/canvas/ManageNominations/Prompts/FavoritesPrompt.tsx @@ -0,0 +1,104 @@ +import { faCheck } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { ButtonPrimary } from '@polkadot-cloud/react'; +import { useApi } from 'contexts/Api'; +import { useNotifications } from 'contexts/Notifications'; +import { useFavoriteValidators } from 'contexts/Validators/FavoriteValidators'; +import type { Validator } from 'contexts/Validators/types'; +import { Identity } from 'library/ListItem/Labels/Identity'; +import { SelectWrapper } from 'library/ListItem/Wrappers'; +import { Title } from 'library/Prompt/Title'; +import { useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { FooterWrapper, PromptListItem } from 'library/Prompt/Wrappers'; +import type { FavoritesPromptProps } from '../types'; + +export const FavoritesPrompt = ({ + callback, + nominations, +}: FavoritesPromptProps) => { + const { t } = useTranslation('modals'); + const { consts } = useApi(); + const { addNotification } = useNotifications(); + const { favoritesList } = useFavoriteValidators(); + const { maxNominations } = consts; + + // Store the total number of selected favorites. + const [selected, setSelected] = useState([]); + + const addToSelected = (item: Validator) => + setSelected([...selected].concat(item)); + + const removeFromSelected = (items: Validator[]) => + setSelected([...selected].filter((item) => !items.includes(item))); + + const remaining = maxNominations + .minus(nominations.length) + .minus(selected.length); + + const canAdd = remaining.isGreaterThan(0); + + return ( + <> + + <div className="padded"> + {remaining.isZero() ? ( + <h4 className="subheading"> + {t('moreFavoritesSurpassLimit', { + max: maxNominations.toString(), + })} + </h4> + ) : ( + <h4 className="subheading"> + {t('addUpToFavorites', { count: remaining.toNumber() })}. + </h4> + )} + + {favoritesList?.map((favorite: Validator, i) => { + const inInitial = !!nominations.find( + ({ address }: Validator) => address === favorite.address + ); + const isDisabled = + selected.includes(favorite) || !canAdd || inInitial; + + return ( + <PromptListItem + key={`favorite_${i}`} + className={isDisabled && inInitial ? 'inactive' : undefined} + > + <SelectWrapper + disabled={inInitial} + onClick={() => { + if (selected.includes(favorite)) + removeFromSelected([favorite]); + else addToSelected(favorite); + }} + > + {(inInitial || selected.includes(favorite)) && ( + <FontAwesomeIcon icon={faCheck} transform="shrink-2" /> + )} + </SelectWrapper> + <Identity key={`favorite_${i}`} address={favorite.address} /> + </PromptListItem> + ); + })} + + <FooterWrapper> + <ButtonPrimary + text={t('addToNominations')} + onClick={() => { + callback(nominations.concat(selected)); + addNotification({ + title: t('favoritesAddedTitle', { count: selected.length }), + subtitle: t('favoritesAddedSubtitle', { + count: selected.length, + }), + }); + }} + disabled={selected.length === 0} + /> + </FooterWrapper> + </div> + </> + ); +}; diff --git a/src/canvas/ManageNominations/Prompts/RevertPrompt.tsx b/src/canvas/ManageNominations/Prompts/RevertPrompt.tsx new file mode 100644 index 0000000000..06a44dddc8 --- /dev/null +++ b/src/canvas/ManageNominations/Prompts/RevertPrompt.tsx @@ -0,0 +1,25 @@ +import { ButtonPrimary } from '@polkadot-cloud/react'; +import { Title } from 'library/Prompt/Title'; +import { useTranslation } from 'react-i18next'; +import { FooterWrapper } from 'library/Prompt/Wrappers'; +import type { RevertPromptProps } from '../types'; + +export const RevertPrompt = ({ onRevert }: RevertPromptProps) => { + const { t } = useTranslation('modals'); + + return ( + <> + <Title title={t('revertNominations')} closeText={t('cancel')} /> + <div className="body"> + <h4 className="subheading">{t('revertNominationChanges')}</h4> + <FooterWrapper> + <ButtonPrimary + marginRight + text={t('revertChanges')} + onClick={() => onRevert()} + /> + </FooterWrapper> + </div> + </> + ); +}; diff --git a/src/canvas/ManageNominations/Wrappers.tsx b/src/canvas/ManageNominations/Wrappers.tsx new file mode 100644 index 0000000000..483a89fb90 --- /dev/null +++ b/src/canvas/ManageNominations/Wrappers.tsx @@ -0,0 +1,27 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import styled from 'styled-components'; + +export const ManageNominationsWrapper = styled.div` + padding-top: 3rem; + min-height: calc(100vh - 12rem); + padding-bottom: 2rem; + + > .head { + display: flex; + align-items: center; + justify-content: flex-end; + } + + > h1 { + margin-top: 1.5rem; + margin-bottom: 1.25rem; + } +`; + +export const CanvasSubmitTxFooter = styled.div` + border-radius: 1rem; + overflow: hidden; + margin-bottom: 2rem; +`; diff --git a/src/canvas/ManageNominations/index.tsx b/src/canvas/ManageNominations/index.tsx new file mode 100644 index 0000000000..9a04d337e3 --- /dev/null +++ b/src/canvas/ManageNominations/index.tsx @@ -0,0 +1,208 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { + ButtonHelp, + ButtonPrimary, + ButtonPrimaryInvert, +} from '@polkadot-cloud/react'; +import { useOverlay } from '@polkadot-cloud/react/hooks'; +import { GenerateNominations } from 'library/GenerateNominations'; +import { useEffect, useState } from 'react'; +import { Subheading } from 'pages/Nominate/Wrappers'; +import { useTranslation } from 'react-i18next'; +import { useApi } from 'contexts/Api'; +import { faTimes } from '@fortawesome/free-solid-svg-icons'; +import { usePrompt } from 'contexts/Prompt'; +import { useHelp } from 'contexts/Help'; +import { useNotifications } from 'contexts/Notifications'; +import { useSubmitExtrinsic } from 'library/Hooks/useSubmitExtrinsic'; +import { useActiveAccounts } from 'contexts/ActiveAccounts'; +import { useBonded } from 'contexts/Bonded'; +import { useActivePools } from 'contexts/Pools/ActivePools'; +import { SubmitTx } from 'library/SubmitTx'; +import type { + NominationSelection, + NominationSelectionWithResetCounter, +} from 'library/GenerateNominations/types'; +import { RevertPrompt } from './Prompts/RevertPrompt'; +import { CanvasSubmitTxFooter, ManageNominationsWrapper } from './Wrappers'; + +export const ManageNominations = () => { + const { t } = useTranslation('library'); + const { + closeCanvas, + setCanvasStatus, + config: { options }, + } = useOverlay().canvas; + const { openHelp } = useHelp(); + const { consts, api } = useApi(); + const { getBondedAccount } = useBonded(); + const { activeAccount } = useActiveAccounts(); + const { addNotification } = useNotifications(); + const { selectedActivePool } = useActivePools(); + const { openPromptWith, closePrompt } = usePrompt(); + const controller = getBondedAccount(activeAccount); + const { maxNominations } = consts; + const bondFor = options?.bondFor || 'nominator'; + const isPool = bondFor === 'pool'; + const signingAccount = isPool ? activeAccount : controller; + + // Valid to submit transaction. + const [valid, setValid] = useState<boolean>(false); + + // Default nominators, from canvas options. + const [defaultNominations, setDefaultNominations] = + useState<NominationSelectionWithResetCounter>({ + nominations: options?.nominated || [], + reset: 0, + }); + + // Current nominator selection, defaults to defaultNominations. + const [newNominations, setNewNominations] = useState<NominationSelection>({ + nominations: options?.nominated || [], + }); + + // Handler for updating setup. + const handleSetupUpdate = (value: NominationSelection) => { + setNewNominations(value); + }; + + // Handler for reverting nomination updates. + const handleRevertChanges = () => { + setNewNominations({ nominations: defaultNominations.nominations }); + setDefaultNominations({ + nominations: defaultNominations.nominations, + reset: defaultNominations.reset + 1, + }); + addNotification({ + title: t('nominationsReverted'), + subtitle: t('revertedToActiveSelection'), + }); + closePrompt(); + }; + + // Check if default nominations match new ones. + const nominationsMatch = () => { + return ( + newNominations.nominations.every((n) => + defaultNominations.nominations.find((d) => d.address === n.address) + ) && + newNominations.nominations.length > 0 && + newNominations.nominations.length === + defaultNominations.nominations.length + ); + }; + + // Tx to submit. + const getTx = () => { + let tx = null; + if (!valid || !api) { + return tx; + } + + // Note: `targets` structure differs between staking and pools. + const targetsToSubmit = newNominations.nominations.map((nominee) => + isPool + ? nominee.address + : { + Id: nominee.address, + } + ); + + if (isPool) { + tx = api.tx.nominationPools.nominate( + selectedActivePool?.id || 0, + targetsToSubmit + ); + } else { + tx = api.tx.staking.nominate(targetsToSubmit); + } + return tx; + }; + + const submitExtrinsic = useSubmitExtrinsic({ + tx: getTx(), + from: signingAccount, + shouldSubmit: valid, + callbackSubmit: () => { + setCanvasStatus('closing'); + }, + callbackInBlock: () => {}, + }); + + // Valid if there are between 1 and `maxNominations` nominations. + useEffect(() => { + setValid( + maxNominations.isGreaterThanOrEqualTo( + newNominations.nominations.length + ) && + newNominations.nominations.length > 0 && + !nominationsMatch() + ); + }, [newNominations]); + + return ( + <> + <ManageNominationsWrapper> + <div className="head"> + <ButtonPrimaryInvert + text={t('revertChanges', { ns: 'modals' })} + lg + onClick={() => { + openPromptWith(<RevertPrompt onRevert={handleRevertChanges} />); + }} + disabled={ + newNominations.nominations === defaultNominations.nominations + } + /> + <ButtonPrimary + text={t('cancel', { ns: 'library' })} + lg + onClick={() => closeCanvas()} + iconLeft={faTimes} + style={{ marginLeft: '1.1rem' }} + /> + </div> + <h1>{t('manageNominations', { ns: 'modals' })}</h1> + + <Subheading> + <h3 style={{ marginBottom: '1.5rem' }}> + {t('chooseValidators', { + ns: 'library', + maxNominations: maxNominations.toString(), + })} + <ButtonHelp + onClick={() => openHelp('Nominations')} + background="none" + outline + /> + </h3> + </Subheading> + + <GenerateNominations + displayFor="canvas" + setters={[ + { + current: { + callable: true, + fn: () => newNominations, + }, + set: handleSetupUpdate, + }, + ]} + nominations={defaultNominations} + /> + </ManageNominationsWrapper> + <CanvasSubmitTxFooter> + <SubmitTx + noMargin + fromController={!isPool} + valid={valid} + displayFor="canvas" + {...submitExtrinsic} + /> + </CanvasSubmitTxFooter> + </> + ); +}; diff --git a/src/canvas/ManageNominations/types.ts b/src/canvas/ManageNominations/types.ts new file mode 100644 index 0000000000..05581f16f7 --- /dev/null +++ b/src/canvas/ManageNominations/types.ts @@ -0,0 +1,13 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import type { Validator } from 'contexts/Validators/types'; + +export interface FavoritesPromptProps { + callback: (newNominations: Validator[]) => void; + nominations: Validator[]; +} + +export interface RevertPromptProps { + onRevert: () => void; +} diff --git a/src/config/extensions/icons/dot_icon.svg b/src/config/extensions/icons/dot_icon.svg deleted file mode 100644 index be7a14a54d..0000000000 --- a/src/config/extensions/icons/dot_icon.svg +++ /dev/null @@ -1 +0,0 @@ -<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 372.78 498.62"><defs><style>.cls-2{fill:#e6007a;}</style></defs><path class="dark" d="M260.4,6.38C156.25,6.8,71.92,91.12,71.51,195.27a193.86,193.86,0,0,0,10,61.36,27,27,0,0,0,33.23,16.55,26.88,26.88,0,0,0,16-33.11,124.53,124.53,0,0,1-7.35-48.15A136.26,136.26,0,1,1,267.87,332s-26.51,1.61-39.7,3.23a134.73,134.73,0,0,0-14.45,2.87,1.75,1.75,0,0,1-2.47,0l0,0a1.77,1.77,0,0,1,0-2.12l4.11-22.39,24.9-112a25.89,25.89,0,0,0-50.64-10.84s-59.23,274.13-59.23,276.63a24.9,24.9,0,0,0,18,30.26,3.48,3.48,0,0,0,.44.1h1.38a24.89,24.89,0,0,0,30.32-17.87,6.77,6.77,0,0,0,.16-.68,6.42,6.42,0,0,1,0-1.24c.73-3.22,8.2-39.7,8.2-39.7a67.19,67.19,0,0,1,55.62-52.89c5.72-.88,29.75-2.49,29.75-2.49C378,372.57,453.7,280.08,443.34,176.33A188.78,188.78,0,0,0,260.4,6.38Z" transform="translate(-71.51 -6.38)"/><path class="cls-2" d="M271.85,441.92a31.47,31.47,0,0,0-37.23,24.4c0,.16-.06.32-.1.48a31.36,31.36,0,0,0,24,37.31.07.07,0,0,1,.06,0h.87a30.86,30.86,0,0,0,37.24-22.75c0-.12.06-.26.1-.38v-1.73A32.64,32.64,0,0,0,271.85,441.92Z" transform="translate(-71.51 -6.38)"/></svg> \ No newline at end of file diff --git a/src/config/extensions/icons/enkrypt_icon.svg b/src/config/extensions/icons/enkrypt_icon.svg deleted file mode 100644 index f114a68d83..0000000000 --- a/src/config/extensions/icons/enkrypt_icon.svg +++ /dev/null @@ -1,9 +0,0 @@ -<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg"> -<path fill-rule="evenodd" clip-rule="evenodd" d="M0 6.15778C0 2.75671 2.75698 0 6.15778 0H32V4.72485C32 7.16928 30.0186 9.15072 27.5741 9.15072H15.4242C12.0231 9.15072 9.26641 11.9077 9.26641 15.3088V16.8756C9.26641 20.2767 12.0231 23.0333 15.4242 23.0333H27.5741C30.0186 23.0333 32 25.015 32 27.4595V32.0003H6.15778C2.75698 32.0003 0 29.243 0 25.8421V6.15778ZM15.6901 11.7259H28.1513C30.277 11.7259 32 13.4491 32 15.5746V16.6097C32 18.7352 30.277 20.4585 28.1513 20.4585H15.6901C13.5644 20.4585 11.8412 18.7352 11.8412 16.6097V15.5746C11.8412 13.4491 13.5644 11.7259 15.6901 11.7259Z" fill="url(#paint0_radial_102_36)"/> -<defs> -<radialGradient id="paint0_radial_102_36" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(0.888265 -0.888331) rotate(55.378) scale(45.3387 181.167)"> -<stop offset="0.051483" stop-color="#C549FF"/> -<stop offset="0.815643" stop-color="#704BFF"/> -</radialGradient> -</defs> -</svg> diff --git a/src/config/extensions/icons/nova_wallet.svg b/src/config/extensions/icons/nova_wallet.svg deleted file mode 100644 index f806807c85..0000000000 --- a/src/config/extensions/icons/nova_wallet.svg +++ /dev/null @@ -1,13 +0,0 @@ -<svg width="145" height="145" viewBox="0 0 145 145" fill="none" xmlns="http://www.w3.org/2000/svg"> - <path d="M0 40C0 17.9086 17.9086 0 40 0H105C127.091 0 145 17.9086 145 40V105C145 127.091 127.091 145 105 145H40C17.9086 145 0 127.091 0 105V40Z" fill="url(#paint0_linear_1014_14373)" /> - <path d="M71.515 24.6219C71.7083 23.5186 73.2917 23.5186 73.485 24.6219L79.265 57.6109C79.9918 61.7596 83.2404 65.0082 87.3891 65.7351L120.378 71.515C121.481 71.7083 121.481 73.2917 120.378 73.485L87.3891 79.265C83.2404 79.9918 79.9918 83.2404 79.265 87.3891L73.485 120.378C73.2917 121.481 71.7083 121.481 71.515 120.378L65.7351 87.3891C65.0082 83.2404 61.7596 79.9918 57.6109 79.265L24.6219 73.485C23.5186 73.2917 23.5186 71.7083 24.6219 71.515L57.6109 65.7351C61.7596 65.0082 65.0082 61.7596 65.7351 57.6109L71.515 24.6219Z" fill="white" /> - <defs> - <linearGradient id="paint0_linear_1014_14373" x1="138.302" y1="-63.9403" x2="-1.11118e-05" y2="205.228" gradientUnits="userSpaceOnUse"> - <stop offset="0.205836" /> - <stop offset="0.369792" stop-color="#541E7E" /> - <stop offset="0.47142" stop-color="#3F51D1" /> - <stop offset="0.609119" stop-color="#73AFE3" /> - <stop offset="0.801091" stop-color="#90D7FF" /> - </linearGradient> - </defs> -</svg> \ No newline at end of file diff --git a/src/config/extensions/icons/polkadot_js.svg b/src/config/extensions/icons/polkadot_js.svg deleted file mode 100644 index f2d53e64ea..0000000000 --- a/src/config/extensions/icons/polkadot_js.svg +++ /dev/null @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 106.2 106.2"><defs><style>.cls-2{fill:#fff}</style></defs><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><circle cx="53.1" cy="53.1" r="53.1" fill="#f29235"/><path class="cls-2" d="M54.47 13.76a28.85 28.85 0 00-28.73 28.73 29.34 29.34 0 001.52 9.34 4 4 0 107.49-2.52A18.67 18.67 0 0133.63 42a20.72 20.72 0 1122 21.31s-4 .25-6 .49c-.74.11-1.48.26-2.2.44a.28.28 0 01-.38 0 .27.27 0 010-.32l.63-3.41 3.79-17a3.94 3.94 0 10-7.71-1.65s-9 41.7-9 42.08a3.79 3.79 0 002.74 4.6h.28a3.78 3.78 0 004.61-2.71.43.43 0 000-.11v-.19c.11-.49 1.25-6 1.25-6a10.23 10.23 0 018.46-8c.87-.13 4.53-.38 4.53-.38a28.71 28.71 0 00-2.11-57.27z"/><path class="cls-2" d="M56.21 80a4.78 4.78 0 00-5.66 3.71.24.24 0 010 .08 4.77 4.77 0 003.65 5.67h.14A4.7 4.7 0 0060 86v-.32A5 5 0 0056.21 80z"/></g></g></svg> \ No newline at end of file diff --git a/src/config/extensions/icons/signer_icon.svg b/src/config/extensions/icons/signer_icon.svg deleted file mode 100644 index 8bdc41c91b..0000000000 --- a/src/config/extensions/icons/signer_icon.svg +++ /dev/null @@ -1,3 +0,0 @@ -<svg width="128" height="128" viewBox="0 0 128 128" fill="none" xmlns="http://www.w3.org/2000/svg"> -<path d="M78.7744 0L0 49.2384L10.352 65.7824L32.1856 52.2784V41.6384H49.2832L89.3152 16.8192L78.7744 0ZM92.0672 21.2736L59.1712 41.6992H96.016L102.349 37.8208L92.0672 21.2768V21.2736ZM105.158 42.16L96.7712 47.2768V56.8384H81.3504L59.952 70.1472H94.8576L114.848 57.7472L105.158 42.16V42.16ZM34.6432 56.8832L13.12 70.2624L23.3984 86.7264L32.272 81.232V70.1472H50.0896L71.488 56.8832H34.6432V56.8832ZM117.603 62.1568L96.8896 75.04V85.344H80.2048L38.6624 111.098L49.2224 128L128 78.7616L117.6 62.1536L117.603 62.1568ZM35.4464 85.344L26.1728 91.104L35.9328 106.701L70.3264 85.344H35.4464V85.344Z" className="primary" /> -</svg> diff --git a/src/config/extensions/icons/subwallet_icon.svg b/src/config/extensions/icons/subwallet_icon.svg deleted file mode 100644 index 69d0334c9f..0000000000 --- a/src/config/extensions/icons/subwallet_icon.svg +++ /dev/null @@ -1,82 +0,0 @@ -<svg width="134" height="134" viewBox="0 0 134 134" fill="none" xmlns="http://www.w3.org/2000/svg"> -<mask id="mask0_699_5101" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="134" height="134"> -<rect width="134" height="134" fill="#C4C4C4"/> -</mask> -<g mask="url(#mask0_699_5101)"> -<path d="M87.9615 64.3201L87.9456 47.7455L27.1191 16.2236V64.3041L66.0589 85.106L80.2884 78.8367L37.4403 56.1046L37.4722 37.887L87.9615 64.3201Z" fill="url(#paint0_linear_699_5101)"/> -<path d="M50.7607 44.8421V50.5052L37.3926 56.2321L37.4883 37.6636L50.7607 44.8421Z" fill="url(#paint1_linear_699_5101)"/> -<path d="M50.8095 91.822L80.2895 78.8368L37.4414 56.2163L50.6819 50.5054L105.765 79.2835L50.9212 103.212L50.8095 91.822Z" fill="url(#paint2_linear_699_5101)"/> -<path d="M37.4886 87.9773L50.6493 82.2982L50.9365 103.196L105.765 79.2832V97.118L37.377 127.077L37.4886 87.9773Z" fill="url(#paint3_linear_699_5101)"/> -<path d="M27.1191 82.5857L37.4403 87.9776L37.3765 127.013L27.1191 121.86V82.5857Z" fill="url(#paint4_linear_699_5101)"/> -<path d="M40.1522 76.7791L50.6489 82.2986L37.4403 87.9776L27.1191 82.5857L40.1522 76.7791Z" fill="url(#paint5_linear_699_5101)"/> -<path d="M105.765 56.5993L105.702 39.9131L87.9785 47.7457V64.3362L105.765 56.5993Z" fill="url(#paint6_linear_699_5101)"/> -<path d="M27.1191 16.2237L45.0337 7.97632L105.732 39.8811L87.9775 47.7456L27.1191 16.2237Z" fill="url(#paint7_linear_699_5101)"/> -</g> -<defs> -<linearGradient id="paint0_linear_699_5101" x1="11.9006" y1="50.6648" x2="119.372" y2="50.6648" gradientUnits="userSpaceOnUse"> -<stop stop-color="#FFD4B2"/> -<stop offset="0.36" stop-color="#9ACEB7"/> -<stop offset="0.67" stop-color="#47C8BB"/> -<stop offset="0.89" stop-color="#14C5BE"/> -<stop offset="1" stop-color="#00C4BF"/> -</linearGradient> -<linearGradient id="paint1_linear_699_5101" x1="44.0766" y1="62.8524" x2="44.0766" y2="21.2167" gradientUnits="userSpaceOnUse"> -<stop stop-color="#00FECF"/> -<stop offset="0.08" stop-color="#00E5D0"/> -<stop offset="0.24" stop-color="#00A5D1"/> -<stop offset="0.48" stop-color="#0040D4"/> -<stop offset="0.54" stop-color="#0025D5"/> -<stop offset="1"/> -</linearGradient> -<linearGradient id="paint2_linear_699_5101" x1="37.4414" y1="76.8587" x2="146.891" y2="76.8587" gradientUnits="userSpaceOnUse"> -<stop stop-color="#FDEC9F"/> -<stop offset="0.08" stop-color="#E4D8A4"/> -<stop offset="0.24" stop-color="#A4A6B2"/> -<stop offset="0.47" stop-color="#3F57C8"/> -<stop offset="0.61" stop-color="#0025D5"/> -<stop offset="1"/> -</linearGradient> -<linearGradient id="paint3_linear_699_5101" x1="15.0596" y1="103.18" x2="155.01" y2="103.18" gradientUnits="userSpaceOnUse"> -<stop offset="0.05" stop-color="#62A5FF"/> -<stop offset="0.45" stop-color="#1032D1"/> -<stop offset="1"/> -</linearGradient> -<linearGradient id="paint4_linear_699_5101" x1="628.741" y1="3244.93" x2="797.782" y2="3247.12" gradientUnits="userSpaceOnUse"> -<stop stop-color="#FFD4B2"/> -<stop offset="0.36" stop-color="#9ACEB7"/> -<stop offset="0.67" stop-color="#47C8BB"/> -<stop offset="0.89" stop-color="#14C5BE"/> -<stop offset="1" stop-color="#00C4BF"/> -</linearGradient> -<linearGradient id="paint5_linear_699_5101" x1="24.5987" y1="82.3783" x2="72.5834" y2="82.3783" gradientUnits="userSpaceOnUse"> -<stop stop-color="#00FECF"/> -<stop offset="0.08" stop-color="#00E5D0"/> -<stop offset="0.25" stop-color="#00A5D1"/> -<stop offset="0.49" stop-color="#0040D4"/> -<stop offset="0.56" stop-color="#0025D5"/> -</linearGradient> -<linearGradient id="paint6_linear_699_5101" x1="70.9573" y1="52.5952" x2="189.069" y2="50.4576" gradientUnits="userSpaceOnUse"> -<stop stop-color="#00FECF"/> -<stop offset="0.05" stop-color="#00E5D0"/> -<stop offset="0.15" stop-color="#00A5D1"/> -<stop offset="0.29" stop-color="#0040D4"/> -<stop offset="0.33" stop-color="#0025D5"/> -</linearGradient> -<linearGradient id="paint7_linear_699_5101" x1="27.1191" y1="27.8689" x2="173.642" y2="27.8689" gradientUnits="userSpaceOnUse"> -<stop stop-color="#FFD4AF"/> -<stop offset="0.1" stop-color="#E6D5BA"/> -<stop offset="0.31" stop-color="#A7D6D5"/> -<stop offset="0.61" stop-color="#43D9FF"/> -<stop offset="0.63" stop-color="#37B1D0"/> -<stop offset="0.65" stop-color="#2B8CA5"/> -<stop offset="0.67" stop-color="#216B7D"/> -<stop offset="0.7" stop-color="#184E5B"/> -<stop offset="0.72" stop-color="#10353F"/> -<stop offset="0.75" stop-color="#0A2228"/> -<stop offset="0.78" stop-color="#061316"/> -<stop offset="0.82" stop-color="#020809"/> -<stop offset="0.88" stop-color="#010202"/> -<stop offset="1"/> -</linearGradient> -</defs> -</svg> diff --git a/src/config/extensions/icons/talisman_icon.svg b/src/config/extensions/icons/talisman_icon.svg deleted file mode 100644 index c8893a2a2a..0000000000 --- a/src/config/extensions/icons/talisman_icon.svg +++ /dev/null @@ -1 +0,0 @@ -<svg viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg" class="logo"><path fill-rule="evenodd" clip-rule="evenodd" d="M25.058 17.832c.304.662 1.2.896 1.714.38l.945-.944a2.5 2.5 0 0 1 3.536 3.535l-7.636 7.636A9.979 9.979 0 0 1 15.965 32a9.983 9.983 0 0 1-7.883-3.847l-7.35-7.35a2.5 2.5 0 1 1 3.536-3.535l.93.93c.504.504 1.379.277 1.677-.37a.97.97 0 0 0 .09-.406V6a2.5 2.5 0 0 1 5 0v5.778c0 .497.51.835.984.685a.727.727 0 0 0 .516-.682V2.5a2.5 2.5 0 0 1 5 0v9.28c0 .315.217.588.517.683.474.15.983-.188.983-.685V6a2.5 2.5 0 0 1 5 0v11.418a.99.99 0 0 0 .093.414Z" className='dark'></path><path className='light' d="M23.965 23s-3.581 5-8 5c-4.418 0-8-5-8-5s3.582-5 8-5c4.419 0 8 5 8 5Z"></path><path d="M18.731 23a2.766 2.766 0 1 1-5.531 0 2.766 2.766 0 0 1 5.531 0Z" className='dark' stroke="currentColor" stroke-width="0.469"></path></svg> \ No newline at end of file diff --git a/src/config/extensions/index.ts b/src/config/extensions/index.ts deleted file mode 100644 index 55f9a0454b..0000000000 --- a/src/config/extensions/index.ts +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { FunctionComponent, SVGProps } from 'react'; -import { ReactComponent as EnkryptSVG } from './icons/enkrypt_icon.svg'; -import { ReactComponent as NovaWalletSVG } from './icons/nova_wallet.svg'; -import { ReactComponent as PolkadotJSSVG } from './icons/polkadot_js.svg'; -import { ReactComponent as SignerSVG } from './icons/signer_icon.svg'; -import { ReactComponent as SubwalletSVG } from './icons/subwallet_icon.svg'; -import { ReactComponent as TalismanSVG } from './icons/talisman_icon.svg'; - -export interface ExtensionConfig { - id: string; - title: string; - icon: FunctionComponent< - SVGProps<SVGSVGElement> & { title?: string | undefined } - >; -} -export const EXTENSIONS: ExtensionConfig[] = [ - { - id: 'enkrypt', - title: 'Enkrypt', - icon: EnkryptSVG, - }, - { - id: 'polkadot-js', - title: (window as any)?.walletExtension?.isNovaWallet - ? 'Nova Wallet' - : 'Polkadot JS', - icon: (window as any)?.walletExtension?.isNovaWallet - ? NovaWalletSVG - : PolkadotJSSVG, - }, - { - id: 'subwallet-js', - title: 'SubWallet', - icon: SubwalletSVG, - }, - { - id: 'talisman', - title: 'Talisman', - icon: TalismanSVG, - }, - { - id: 'parity-signer-companion', - title: 'Parity Signer Companion', - icon: SignerSVG, - }, -]; diff --git a/src/config/help.ts b/src/config/help.ts index 60bbcf8a6a..6384c1cdac 100644 --- a/src/config/help.ts +++ b/src/config/help.ts @@ -1,37 +1,43 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -// ToDo: update -import { HelpItems } from 'contexts/Help/types'; +import type { HelpItems } from 'contexts/Help/types'; -export const HELP_CONFIG: HelpItems = [ +export const HelpConfig: HelpItems = [ + { + key: 'vault', + definitions: ['Polkadot Vault'], + }, { key: 'overview', definitions: [ - 'Dashboard Tips', 'Total Nominators', 'Active Nominators', 'Your Balance', 'Reserve Balance', - 'Network Stats', - 'Inflation', + 'Locked Balance', 'Historical Rewards Rate', + 'Adjusted Rewards Rate', + 'Inflation', 'Ideal Staked', 'Supply Staked', + 'Read Only Accounts', + 'Proxy Accounts', + 'Reserve Balance For Existential Deposit', ], external: [ [ - 'connect_your_accounts', + 'connectAccounts', 'https://support.polkadot.network/support/solutions/articles/65000182121-how-to-use-the-staking-dashboard-connecting-your-account', 'polkadot.network', ], [ - 'how_to_use', + 'howToUse', 'https://support.polkadot.network/support/solutions/articles/65000182104-how-to-use-the-staking-dashboard-overview', 'polkadot.network', ], [ - 'stake_cere', + 'stakeCere', 'https://support.polkadot.network/support/solutions/articles/65000182104-how-to-use-the-staking-dashboard-overview', 'polkadot.network', ], @@ -41,28 +47,26 @@ export const HELP_CONFIG: HelpItems = [ key: 'nominate', definitions: [ 'Nomination Status', - 'Stash and Controller Accounts', - 'Controller Account Eligibility', 'Bonding', - 'Active Bond Threshold', - 'Reward Destination', + 'Active Stake Threshold', + 'Payout Destination', 'Nominating', 'Nominations', 'Inactive Nominations', ], external: [ [ - 'change_destination', + 'changeDestination', 'https://support.polkadot.network/support/solutions/articles/65000182220-how-to-use-the-staking-dashboard-changing-reward-destination', 'polkadot.network', ], [ - 'bond_more', + 'bondMore', 'https://support.polkadot.network/support/solutions/articles/65000182207-how-to-use-the-staking-dashboard-bond-more-tokens-to-your-existing-stake', 'polkadot.network', ], [ - 'unbonding_tokens', + 'unbondingTokens', 'https://support.polkadot.network/support/solutions/articles/65000182201-how-to-use-the-staking-dashboard-unbonding-your-tokens', 'polkadot.network', ], @@ -72,12 +76,12 @@ export const HELP_CONFIG: HelpItems = [ 'polkadot.network', ], [ - 'change_account', + 'changeAccount', 'https://support.polkadot.network/support/solutions/articles/65000182218-how-to-use-the-staking-dashboard-changing-your-controller-account', 'polkadot.network', ], [ - 'change_nominations', + 'changeNominations', 'https://support.polkadot.network/support/solutions/articles/65000182518-how-to-use-the-staking-dashboard-changing-your-nominations', 'polkadot.network', ], @@ -88,21 +92,24 @@ export const HELP_CONFIG: HelpItems = [ definitions: [ 'Nomination Pools', 'Active Pools', - 'Minimum Join Bond', - 'Minimum Create Bond', + 'Minimum To Join Pool', + 'Minimum To Create Pool', 'Pool Membership', 'Bonded in Pool', 'Pool Rewards', 'Pool Roles', + 'Pool Commission Rate', + 'Pool Max Commission', + 'Pool Commission Change Rate', ], external: [ [ - 'create_pools', + 'createPools', 'https://support.polkadot.network/support/solutions/articles/65000182388-how-to-use-the-staking-dashboard-creating-nomination-pools', 'polkadot.network', ], [ - 'claim_rewards', + 'claimRewards', 'https://support.polkadot.network/support/solutions/articles/65000182399-how-to-use-staking-dashboard-claiming-nomination-pool-rewards', 'polkadot.network', ], @@ -122,10 +129,11 @@ export const HELP_CONFIG: HelpItems = [ 'Commission', 'Over Subscribed', 'Blocked Nominations', + 'Rewards By Country And Network', ], external: [ [ - 'choose_validators', + 'chooseValidators', 'https://support.polkadot.network/support/solutions/articles/65000150130-how-do-i-know-which-validators-to-choose-', 'polkadot.network', ], @@ -141,4 +149,15 @@ export const HELP_CONFIG: HelpItems = [ definitions: [], external: [], }, + { + key: 'ledger', + definitions: [ + 'Ledger Hardware Wallets', + 'Ledger Rejected Transaction', + 'Ledger Request Timeout', + 'Open App On Ledger', + 'Wrong Transaction', + ], + external: [], + }, ]; diff --git a/src/config/ledger.ts b/src/config/ledger.ts new file mode 100644 index 0000000000..0651a01126 --- /dev/null +++ b/src/config/ledger.ts @@ -0,0 +1,13 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import type { LedgerApp } from 'contexts/Hardware/types'; +import PolkadotSVG from 'img/appIcons/polkadot.svg?react'; + +export const LedgerApps: LedgerApp[] = [ + { + network: 'cereMainnet', + appName: 'Polkadot', + Icon: PolkadotSVG, + }, +]; diff --git a/src/config/networks.ts b/src/config/networks.ts index cf8dfd3c8e..f8f048bbb6 100644 --- a/src/config/networks.ts +++ b/src/config/networks.ts @@ -1,12 +1,18 @@ // SPDX-License-Identifier: Apache-2.0 -import { WellKnownChain } from '@polkadot/rpc-provider/substrate-connect'; import { DefaultParams } from 'consts'; -import { ReactComponent as CereIconSvg } from 'img/cere_icon.svg'; -import { ReactComponent as CereLogoSvg } from 'img/cere_logo.svg'; -import { Networks } from 'types'; +import CereIconSvg from 'img/cere_icon.svg?react'; +import CereLogoSvg from 'img/cere_logo.svg?react'; +import type { NetworkName, Networks } from 'types'; +import BigNumber from 'bignumber.js'; + +const CereMainnet: NetworkName = 'cereMainnet'; +const CereTestnet: NetworkName = 'cereTestnet'; +const CereDevnet: NetworkName = 'cereDevnet'; +const CereQanet: NetworkName = 'cereQanet'; const cereMainnet = { - name: 'Cere', + name: CereMainnet, + namespace: '91b171bb158e2d3848fa23a9f1c25182', colors: { primary: { light: '#1D1B20', @@ -22,12 +28,20 @@ const cereMainnet = { }, transparent: { light: 'rgb(236,110,121,0.05)', - dark: 'rgb(236,110,121, 0.05)', + dark: 'rgb(236,110,121,0.05)', + }, + pending: { + light: 'rgb(236,110,121,0.33)', + dark: 'rgb(236,110,121,0.33))', }, }, endpoints: { - rpc: 'wss://archive.mainnet.cere.network/ws', - lightClient: WellKnownChain.polkadot, + lightClient: 'Cere', + defaultRpcEndpoint: 'Cere', + rpcEndpoints: { + Cere: 'wss://archive.mainnet.cere.network/ws', + // Cere: 'ws://127.0.0.1:9944', + }, }, subscanEndpoint: '', cereStatsEndpoint: 'wss://hasura.stats.cere.network/v1/graphql', @@ -37,6 +51,7 @@ const cereMainnet = { // It's a draft icons set brand: { icon: CereIconSvg, + token: CereIconSvg, logo: { svg: CereLogoSvg, width: '8.5rem', @@ -53,47 +68,62 @@ const cereMainnet = { features: { pools: false, }, - params: DefaultParams, + params: { + ...DefaultParams, + stakeTarget: 0.75, + }, + defaultFeeReserve: 0.1, + maxExposurePageSize: new BigNumber(512), }; const cereTestnet = { ...cereMainnet, - name: 'Cere Testnet', + name: CereTestnet, endpoints: { - rpc: 'wss://archive.testnet.cere.network/ws', - lightClient: WellKnownChain.polkadot, + lightClient: 'Cere', + defaultRpcEndpoint: 'Cere', + rpcEndpoints: { + Cere: 'wss://archive.testnet.cere.network/ws', + }, }, cereStatsEndpoint: 'wss://stats-hasura.network-dev.aws.cere.io/v1/graphql', }; const cereDevnet = { ...cereMainnet, - name: 'Cere Devnet', + name: CereDevnet, endpoints: { - rpc: 'wss://archive.devnet.cere.network/ws', - lightClient: WellKnownChain.polkadot, + lightClient: 'Cere', + defaultRpcEndpoint: 'Cere', + rpcEndpoints: { + Cere: 'wss://archive.devnet.cere.network/ws', + }, }, cereStatsEndpoint: 'wss://stats-hasura.network-dev.aws.cere.io/v1/graphql', }; const cereQAnet = { ...cereMainnet, - name: 'Cere Qanet', + name: CereQanet, endpoints: { - rpc: 'wss://archive.qanet.cere.network/ws', - lightClient: WellKnownChain.polkadot, + lightClient: 'Cere', + defaultRpcEndpoint: 'Cere', + rpcEndpoints: { + Cere: 'wss://archive.qanet.cere.network/ws', + }, }, cereStatsEndpoint: 'wss://stats-hasura.network-dev.aws.cere.io/v1/graphql', }; // Determine if the testnet should be included based on the REACT_APP_INCLUDE_TESTNET environment variable // By default, includeTestnet is true or undefined unless REACT_APP_INCLUDE_TESTNET is explicitly set to 'false' -const includeTestnet = process.env.REACT_APP_INCLUDE_TESTNET !== 'false'; +// const includeTestnet = process.env.REACT_APP_INCLUDE_TESTNET !== 'false'; +const includeTestnet = true; /* * Network Configuration */ -export const NETWORKS: Networks = { +export const NetworkList: Networks = { cereMainnet, ...(includeTestnet ? { cereTestnet } : {}), cereDevnet, diff --git a/src/config/pages.ts b/src/config/pages.ts index cb65dc69a8..25e1c12d97 100644 --- a/src/config/pages.ts +++ b/src/config/pages.ts @@ -1,24 +1,16 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import { faHashtag } from '@fortawesome/free-solid-svg-icons'; -import { UriPrefix } from 'consts'; -import * as analyticsJson from 'img/json/analytics-solid.json'; -import * as favoriteHeartJson from 'img/json/favorite-heart-solid.json'; -import * as viewGroupJson from 'img/json/groups-solid.json'; -import * as viewTrendingUpJson from 'img/json/trending-up-solid.json'; -import * as view1SolidJson from 'img/json/view-1-solid.json'; -import * as viewAgendaJson from 'img/json/view-agenda-solid.json'; -import Community from 'pages/Community'; -import Favorites from 'pages/Favorites'; -import Nominate from 'pages/Nominate'; -import Overview from 'pages/Overview'; -import Payouts from 'pages/Payouts'; -import Pools from 'pages/Pools'; -import Browse from 'pages/Validators'; -import { PageCategories, PagesConfig } from 'types'; +import { Community } from 'pages/Community'; +import { Nominate } from 'pages/Nominate'; +import { Overview } from 'pages/Overview'; +import { Payouts } from 'pages/Payouts'; +import { Pools } from 'pages/Pools'; +import { Validators } from 'pages/Validators'; +import type { PageCategoryItems, PagesConfigItems } from 'types'; -export const PAGE_CATEGORIES: PageCategories = [ +const BASE_URL = import.meta.env.BASE_URL; +export const PageCategories: PageCategoryItems = [ { id: 1, key: 'default', @@ -33,63 +25,53 @@ export const PAGE_CATEGORIES: PageCategories = [ }, ]; -export const PAGES_CONFIG: PagesConfig = [ +export const PagesConfig: PagesConfigItems = [ { category: 1, key: 'overview', - uri: `${UriPrefix}/`, + uri: `${BASE_URL}`, hash: '/overview', Entry: Overview, - animate: view1SolidJson, - }, - { - category: 2, - key: 'nominate', - uri: `${UriPrefix}/nominate`, - hash: '/nominate', - Entry: Nominate, - animate: viewTrendingUpJson, + lottie: 'globe', }, { category: 2, key: 'pools', - uri: `${UriPrefix}/pools`, + uri: `${BASE_URL}pools`, hash: '/pools', Entry: Pools, - animate: viewGroupJson, + lottie: 'groups', + }, + { + category: 2, + key: 'nominate', + uri: `${BASE_URL}nominate`, + hash: '/nominate', + Entry: Nominate, + lottie: 'trending', }, { category: 2, key: 'payouts', - uri: `${UriPrefix}/payouts`, + uri: `${BASE_URL}payouts`, hash: '/payouts', Entry: Payouts, - animate: analyticsJson, + lottie: 'analytics', }, { category: 3, key: 'validators', - uri: `${UriPrefix}/validators`, + uri: `${BASE_URL}validators`, hash: '/validators', - Entry: Browse, - animate: viewAgendaJson, + Entry: Validators, + lottie: 'view', }, { category: 3, key: 'community', - uri: `${UriPrefix}/community`, + uri: `${BASE_URL}community`, hash: '/community', Entry: Community, - icon: faHashtag, - }, - { - category: 3, - key: 'favorites', - uri: `${UriPrefix}/favorites`, - hash: '/favorites', - Entry: Favorites, - animate: favoriteHeartJson, + lottie: 'label', }, ]; - -export default PAGES_CONFIG; diff --git a/src/config/proxies.ts b/src/config/proxies.ts new file mode 100644 index 0000000000..618e46571f --- /dev/null +++ b/src/config/proxies.ts @@ -0,0 +1,60 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +export const SupportedProxies: Record<string, string[]> = { + Any: ['*'], + Staking: [ + 'staking.bond', + 'staking.bondExtra', + 'staking.chill', + 'staking.nominate', + 'staking.rebond', + 'staking.setController', + 'staking.setPayee', + 'staking.unbond', + 'staking.withdrawUnbonded', + 'nominationPools.create', + 'nominationPools.nominate', + 'nominationPools.bondExtra', + 'nominationPools.chill', + 'nominationPools.claimPayout', + 'nominationPools.join', + 'nominationPools.setClaimPermission', + 'nominationPools.claimCommission', + 'nominationPools.setCommission', + 'nominationPools.setCommissionMax', + 'nominationPools.setCommissionChangeRate', + 'nominationPools.unbond', + 'nominationPools.setMetadata', + 'nominationPools.setState', + 'nominationPools.withdrawUnbonded', + 'fastUnstake.registerFastUnstake', + 'fastUnstake.deregister', + ], +}; + +export const UnsupportedIfUniqueController: string[] = [ + 'staking.chill', + 'staking.nominate', + 'staking.rebond', + 'staking.unbond', + 'staking.setPayee', + 'staking.withdrawUnbonded', +]; + +export const isSupportedProxy = (proxy: string) => + Object.keys(SupportedProxies).includes(proxy) || proxy === 'Any'; + +export const isSupportedProxyCall = ( + proxy: string, + pallet: string, + method: string +) => { + if ([method, pallet].includes('undefined')) { + return false; + } + + const call = `${pallet}.${method}`; + const calls = SupportedProxies[proxy]; + return (calls || []).find((c) => ['*', call].includes(c)) !== undefined; +}; diff --git a/src/config/tips.ts b/src/config/tips.ts index e6652a84a5..282ede798d 100644 --- a/src/config/tips.ts +++ b/src/config/tips.ts @@ -1,58 +1,53 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import * as helpCenterJson from 'img/json/help-center-outline.json'; -import * as infoJson from 'img/json/info-outline.json'; - -export const TIPS_CONFIG = [ +export const TipsConfig = [ { - id: 'connect_extensions', + id: 'connectExtensions', s: 1, - icon: infoJson, }, { - id: 'recommended_nominator', + id: 'recommendedNominator', s: 2, - icon: infoJson, + page: 'nominate', }, { - id: 'recommended_join_pool', + id: 'recommendedJoinPool', s: 3, - icon: infoJson, + page: 'pools', }, { - id: 'how_to_stake', + id: 'howToStake', s: 4, - icon: helpCenterJson, }, { - id: 'managing_nominations', + id: 'managingNominations', s: 5, - icon: infoJson, + page: 'nominate', }, { - id: 'monitoring_pool', + id: 'monitoringPool', s: 6, - icon: infoJson, + page: 'pools', }, { - id: 'join_another_pool', + id: 'joinAnotherPool', s: 6, - icon: infoJson, + page: 'pools', }, { - id: 'keep_pool_nominating', + id: 'keepPoolNominating', s: 7, - icon: infoJson, + page: 'pools', }, { - id: 'reviewing_payouts', + id: 'reviewingPayouts', s: 8, - icon: infoJson, + page: 'payouts', }, { - id: 'understanding_validator_performance', + id: 'understandingValidatorPerformance', s: 8, - icon: infoJson, + page: 'validators', }, ]; diff --git a/src/config/tokens/svg/DOT.svg b/src/config/tokens/svg/DOT.svg new file mode 100644 index 0000000000..d9badbeba9 --- /dev/null +++ b/src/config/tokens/svg/DOT.svg @@ -0,0 +1 @@ +<svg version="1.1" width="100%" height="100%" viewBox="0 0 310 310" fill="none" xmlns="http://www.w3.org/2000/svg"><g><circle cx="155" cy="151" r="146" fill="#eee" stroke="#ccc" stroke-width="10"/><path d="M154.962 89.56c21.165 0 38.322-9.975 38.322-22.28 0-12.305-17.157-22.28-38.322-22.28-21.165 0-38.322 9.975-38.322 22.28 0 12.305 17.157 22.28 38.322 22.28zm0 168.44c21.165 0 38.322-9.975 38.322-22.28 0-12.305-17.157-22.281-38.322-22.281-21.165 0-38.322 9.976-38.322 22.281 0 12.305 17.157 22.28 38.322 22.28zm-53.497-137.409c10.597-18.369 10.528-38.263-.154-44.435-10.683-6.172-27.934 3.715-38.53 22.084-10.598 18.369-10.53 38.263.153 44.435 10.683 6.172 27.934-3.715 38.531-22.084zm145.627 84.14c10.598-18.369 10.534-38.26-.141-44.428-10.675-6.168-27.92 3.723-38.517 22.091-10.598 18.369-10.535 38.26.141 44.428 10.675 6.168 27.92-3.723 38.517-22.091zm-145.776 22.086c10.683-6.172 10.752-26.067.154-44.435-10.597-18.369-27.848-28.257-38.53-22.084-10.683 6.172-10.752 26.066-.154 44.435 10.597 18.369 27.848 28.256 38.53 22.084zM246.96 142.68c10.676-6.168 10.739-26.059.141-44.428-10.597-18.369-27.842-28.26-38.517-22.091-10.675 6.168-10.739 26.059-.141 44.427 10.597 18.369 27.842 28.26 38.517 22.092z" fill="#E6007A"/></g></svg> \ No newline at end of file diff --git a/src/config/tokens/svg/KSM.svg b/src/config/tokens/svg/KSM.svg new file mode 100644 index 0000000000..98ed7699fd --- /dev/null +++ b/src/config/tokens/svg/KSM.svg @@ -0,0 +1,4 @@ +<svg version="1.1" width="100%" height="100%" viewBox="0 0 302 302" fill="none" xmlns="http://www.w3.org/2000/svg"> +<circle cx="151" cy="151" r="151" fill="black"/> +<path d="M227.655 94.7713C224.991 92.6735 221.816 89.8082 216.029 89.0919C210.6 88.3756 205.069 92.0083 201.33 94.4131C197.591 96.8179 190.523 103.879 187.604 106.028C184.685 108.177 177.207 110.172 165.172 117.386C153.136 124.601 105.916 154.891 105.916 154.891L118.207 155.044L63.4071 183.236H68.8872L61 189.223C61 189.223 67.9653 191.065 73.8038 187.381V189.069C73.8038 189.069 139.052 163.384 151.651 170.036L143.969 172.287C144.635 172.287 157.029 173.106 157.029 173.106C157.029 173.106 157.438 180.832 164.916 185.795C172.393 190.707 172.547 193.418 172.547 193.418C172.547 193.418 168.655 195.005 168.655 197C168.655 197 174.391 195.26 179.717 195.414C185.043 195.567 189.704 197 189.704 197C189.704 197 189.294 194.851 184.122 193.418C178.898 191.935 173.827 186.358 171.318 183.288C168.808 180.218 167.067 174.743 169.218 169.268C171.01 164.612 177.259 162.054 190.165 155.402C205.376 147.523 208.859 141.69 211.01 137.136C213.161 132.583 216.336 123.526 218.128 119.28C220.382 113.805 223.148 110.888 225.452 109.149C227.706 107.409 238 103.572 238 103.572C238 103.572 230.164 96.7667 227.655 94.7713Z" fill="#EEEEEE"/> +</svg> diff --git a/src/config/tokens/svg/WND.svg b/src/config/tokens/svg/WND.svg new file mode 100644 index 0000000000..83d722fb81 --- /dev/null +++ b/src/config/tokens/svg/WND.svg @@ -0,0 +1 @@ +<svg version="1.1" width="100%" height="100%" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1671.16 1671.66"><defs><linearGradient id="b" x1="40.98" y1="835.47" x2="1774.13" y2="836.99" gradientTransform="matrix(1 0 0 -1 -.84 1672)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#f79420" stop-opacity=".92"/><stop offset="1" stop-color="#c4c4c4" stop-opacity="0"/></linearGradient><mask id="a" x="0" y="0" width="1671.16" height="1671.66" maskUnits="userSpaceOnUse"><circle cx="836.27" cy="836.22" r="835.44" fill="#c4c4c4" transform="translate(-.84)"/></mask></defs><circle cx="835.44" cy="836.22" r="835.44" fill="#e6007a"/><g mask="url(#a)"><path fill="url(#b)" d="M.66 0h1670.5v1671.65H.66z"/></g><path d="M533.46 1196.48H735.4L799.78 963c8.87-32.19 54.14-32.17 63 0l64.15 233.43h201.55l154.53-597h-189.23l-48.32 237c-7 34.4-55.41 35.35-63.75 1.25l-58.25-238.16h-184.6l-56.7 239.64c-8.11 34.22-56.6 33.55-63.78-.87l-17-81.7c-19.22-91.53-99.22-157.07-192-157.07h-30.06z" fill="#fff"/></svg> \ No newline at end of file diff --git a/src/config/validators/Amforc.tsx b/src/config/validators/Amforc.tsx new file mode 100644 index 0000000000..3f2afb9f3b --- /dev/null +++ b/src/config/validators/Amforc.tsx @@ -0,0 +1,52 @@ +const Amforc = () => ( + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 94.12 94.24"> + <defs> + <linearGradient + id="a" + x1="46.57" + y1="97.04" + x2="47.53" + y2="2.32" + gradientTransform="matrix(1 0 0 -1 0 96.38)" + gradientUnits="userSpaceOnUse" + > + <stop offset="0" stopColor="#507e99" /> + <stop offset=".78" stopColor="#8fd698" /> + <stop offset="1" stopColor="#aad4a0" /> + </linearGradient> + </defs> + <path + d="M77.22 94.11H16.89A16.89 16.89 0 010 77.22V16.89A16.89 16.89 0 0116.89 0h60.33a16.89 16.89 0 0116.89 16.89v60.33a16.89 16.89 0 01-16.89 16.89z" + fill="#1d5c81" + /> + <path + d="M91.09 86.85L60 10.16l-34 84h4.91l29-72.41 27.25 69.11a17 17 0 003.93-4.01z" + fill="#8fd698" + /> + <path fill="#3a8286" d="M20.53 39.49h41.9v4.27h-41.9z" /> + <path fill="#8fd698" d="M38.94 63.06h41.93v4.33H38.94z" /> + <path + fill="#3a8286" + d="M31.6 51.41h41.89v4.36H31.6zM63.12 57.09h30.99v4.45H63.12z" + /> + <path fill="#8fd698" d="M5.72 45.46h35.6v4.36H5.72z" /> + <path + d="M77.22 94.11H16.89A16.89 16.89 0 010 77.22V16.89A16.89 16.89 0 0116.89 0h60.33a16.89 16.89 0 0116.89 16.89v60.33a16.89 16.89 0 01-16.89 16.89z" + fill="url(#a)" + /> + <path + d="M60.24 10.74h-.39l-33.8 83.5H31l29-72.41L87.18 91a16.81 16.81 0 003.93-4z" + fill="#fff" + /> + <path + fill="#3a8286" + d="M12.85 39.49h41.9v4.27h-41.9zM23.93 51.41h41.89v4.36H23.93zM55.45 57.09h38.67v4.45H55.45z" + /> + <path + fill="#114a62" + d="M0 45.46h33.64v4.36H0zM31.26 63.06h41.93v4.33H31.26z" + /> + </svg> +); + +export default Amforc; diff --git a/src/config/validators/ApertureMining.tsx b/src/config/validators/ApertureMining.tsx new file mode 100644 index 0000000000..c9199eeaec --- /dev/null +++ b/src/config/validators/ApertureMining.tsx @@ -0,0 +1,97 @@ +const ApertureMining = () => ( + <svg + version="1.0" + xmlns="http://www.w3.org/2000/svg" + width="376.000000pt" + height="376.000000pt" + viewBox="0 0 376.000000 376.000000" + preserveAspectRatio="xMidYMid meet" + > + <g + transform="translate(0.000000,376.000000) scale(0.100000,-0.100000)" + fill="#000000" + stroke="none" + > + <path + d="M1683 3713 c-27 -4 -58 -32 -185 -169 -84 -90 -154 -169 -156 -174 + -2 -7 287 -10 807 -10 446 0 811 3 811 8 0 14 -166 114 -285 172 -259 126 + -513 182 -808 178 -84 0 -167 -3 -184 -5z" + /> + <path + d="M1422 3665 c-146 -38 -206 -61 -367 -141 -193 -95 -309 -178 -472 + -339 l-133 -131 0 -249 0 -250 311 310 c378 377 798 825 772 825 -10 -1 -60 + -12 -111 -25z" + /> + <path + d="M3125 2730 c314 -314 571 -566 573 -561 5 16 -49 230 -80 321 -95 + 270 -268 536 -474 729 l-87 81 -251 0 -251 0 570 -570z" + /> + <path + d="M1810 3230 c-19 -4 -26 -14 -30 -40 -4 -27 -10 -36 -27 -38 -16 -2 + -23 2 -23 12 0 23 -14 20 -45 -11 -35 -36 -53 -71 -61 -122 -4 -24 -12 -41 + -20 -41 -16 0 -29 -39 -17 -51 5 -5 129 -8 289 -7 l279 3 3 23 c3 17 -2 25 + -17 29 -15 4 -21 14 -21 32 0 33 -31 97 -64 136 -25 29 -27 29 -40 12 -22 -29 + -50 -17 -56 22 -4 27 -11 36 -32 42 -26 7 -27 5 -30 -35 -3 -36 -6 -41 -28 + -41 -22 0 -26 5 -30 41 -5 37 -7 40 -30 34z" + /> + <path + d="M326 2874 c-117 -181 -213 -418 -255 -630 -28 -140 -43 -339 -35 + -449 l7 -93 173 -173 174 -174 0 803 c0 441 -2 802 -4 802 -2 0 -29 -39 -60 + -86z" + /> + <path + d="M1632 2841 c10 -146 161 -248 303 -206 59 17 126 68 150 116 20 37 + 36 114 29 133 -5 14 -38 16 -246 16 l-240 0 4 -59z" + /> + <path + d="M2482 2758 c112 -111 248 -307 248 -358 0 -22 -12 -32 -97 -75 -54 + -28 -120 -62 -146 -78 -44 -25 -50 -26 -75 -13 -43 22 -136 126 -197 220 -36 + 56 -65 90 -82 97 -16 6 -122 9 -267 7 l-241 -3 -60 -29 c-35 -17 -86 -56 -123 + -92 -103 -103 -152 -237 -152 -419 0 -161 49 -393 90 -430 32 -29 88 -25 116 + 9 28 32 30 54 8 125 -63 215 -56 433 19 546 57 86 57 85 57 -214 0 -149 2 + -271 4 -271 15 0 563 295 574 309 7 9 12 38 12 66 -1 28 -1 54 -1 59 1 5 22 + -12 47 -38 25 -26 44 -49 42 -49 -51 -25 -63 -38 -66 -70 l-3 -35 64 34 63 33 + 43 -21 c93 -46 203 -20 276 64 34 39 36 47 32 91 l-4 48 48 24 c26 14 55 22 + 64 19 46 -18 164 -353 165 -469 l1 -40 17 30 c13 23 17 53 16 125 -1 104 -19 + 191 -66 308 -17 40 -27 80 -24 89 11 28 -13 81 -42 94 -15 7 -44 37 -64 68 + -95 147 -237 273 -332 295 -25 6 -21 0 36 -56z" + /> + <path + d="M3360 1594 c0 -512 3 -803 10 -799 19 12 125 192 175 300 125 263 + 180 524 173 817 l-3 148 -170 170 c-93 93 -173 170 -177 170 -5 0 -8 -363 -8 + -806z" + /> + <path + d="M1872 1851 c-156 -82 -287 -153 -292 -157 -4 -5 -15 -88 -24 -184 -9 + -96 -30 -317 -46 -490 -40 -419 -40 -413 9 -461 34 -34 44 -39 86 -39 54 0 95 + 21 119 61 15 26 33 186 99 924 l12 130 35 3 36 3 38 -413 c64 -698 53 -627 96 + -670 33 -34 43 -38 86 -38 54 0 99 23 120 62 20 36 17 94 -31 606 -29 317 -45 + 533 -46 645 0 92 -4 167 -7 167 -4 -1 -135 -68 -290 -149z" + /> + <path + d="M1530 1733 c0 -10 5 -25 10 -33 7 -11 10 -7 10 18 0 17 -4 32 -10 32 + -5 0 -10 -8 -10 -17z" + /> + <path + d="M1194 1569 c-96 -51 -111 -63 -107 -80 3 -11 11 -23 18 -25 12 -5 + 220 100 230 117 7 10 -12 49 -23 48 -4 0 -57 -27 -118 -60z" + /> + <path + d="M60 1580 c0 -14 9 -63 20 -110 64 -267 164 -483 325 -698 87 -116 + 274 -309 308 -317 12 -3 126 -4 253 -3 l231 3 -569 575 c-452 457 -568 569 + -568 550z" + /> + <path + d="M2725 628 c-357 -359 -554 -564 -544 -566 26 -5 223 45 326 84 275 + 102 497 247 701 457 l92 96 0 246 c0 135 -3 245 -7 245 -5 0 -260 -253 -568 + -562z" + /> + <path + d="M805 380 c11 -18 190 -123 284 -167 263 -124 490 -173 795 -173 l171 + 0 175 175 175 175 -803 0 c-511 0 -801 -3 -797 -10z" + /> + </g> + </svg> +); + +export default ApertureMining; diff --git a/src/config/validators/Blockseeker.tsx b/src/config/validators/Blockseeker.tsx new file mode 100644 index 0000000000..3f1e98bbba --- /dev/null +++ b/src/config/validators/Blockseeker.tsx @@ -0,0 +1,30 @@ +const Blockseeker = () => ( + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 389.83 450.04"> + <defs> + <linearGradient + id="a" + x1="194.91" + y1="448.54" + x2="194.91" + y2="1.5" + gradientUnits="userSpaceOnUse" + > + <stop offset="0" stopColor="#010101" /> + <stop offset="1" stopColor="#010101" /> + </linearGradient> + </defs> + <path + d="M68.7 335.07a2 2 0 00-3.16-1.53l-22.1 17.58a24.44 24.44 0 01-27.5 2A29.05 29.05 0 011.5 328V131.5a38.7 38.7 0 0118.62-33.08L171 6.91a37.29 37.29 0 0137.89-.47 13 13 0 01.29 22.38l-77.18 47a13.84 13.84 0 00-5.88 7.33 1.84 1.84 0 001.14 2.34 1.82 1.82 0 001.54-.16l86.84-51.87a38.68 38.68 0 0139.08-.35l114.17 65.47a38.69 38.69 0 0119.44 33.56v196a27.14 27.14 0 01-14.72 24.13 10.13 10.13 0 01-14.77-9l-.18-53.84a1.45 1.45 0 00-2.73-.68 23 23 0 00-2.71 10.83v42.25a38.68 38.68 0 01-20.11 33.93L209 443.78a38.67 38.67 0 01-38.11-.54l-116.46-68.1A14.12 14.12 0 0152.72 352l12.83-10.31a8.44 8.44 0 003.15-6.62z" + stroke="#fff" + strokeMiterlimit="10" + strokeWidth="1" + fill="#010101" + /> + <path + d="M91 216v-56.31h20.28A44.26 44.26 0 01122 160.9a15.39 15.39 0 017.71 4.25q2.87 3 2.87 8.58a15.31 15.31 0 01-1.88 7.27 10.06 10.06 0 01-5.2 4.9v.35a13.91 13.91 0 016.88 4.33q2.84 3.19 2.83 8.84a14.46 14.46 0 01-3 9.49 17.6 17.6 0 01-8.06 5.42A33.73 33.73 0 01113 216zm14.91-34.32h5c2.42 0 4.22-.52 5.37-1.56a5.29 5.29 0 001.74-4.16 4.1 4.1 0 00-1.74-3.73 10.2 10.2 0 00-5.38-1.12h-5v10.59zm0 22.88H112q8.65 0 8.66-6.24a4.73 4.73 0 00-2.13-4.42c-1.41-.87-3.58-1.3-6.53-1.3h-6.07zm50.26 12.47a13.62 13.62 0 01-7.93-2 11.2 11.2 0 01-4.16-5.72 26.56 26.56 0 01-1.25-8.53V155.7h14.91v45.58a4.22 4.22 0 00.82 3 2.35 2.35 0 001.6.82 4.6 4.6 0 00.74 0l.82-.12 1.73 10.92a13.85 13.85 0 01-2.95.87 23.27 23.27 0 01-4.33.28zm31.72 0a21.36 21.36 0 01-18.41-10.44 25.94 25.94 0 010-24.52 21.45 21.45 0 0136.83 0 26 26 0 010 24.53 21.38 21.38 0 01-18.42 10.44zm0-12a5 5 0 004.81-2.9 17.66 17.66 0 001.43-7.84 17.87 17.87 0 00-1.43-7.85 5.43 5.43 0 00-9.61 0 22.09 22.09 0 000 15.69 4.94 4.94 0 004.8 2.97zm48.88 12a22.94 22.94 0 01-11.09-2.67 19.9 19.9 0 01-7.84-7.76 24.39 24.39 0 01-2.9-12.26 22.23 22.23 0 013.24-12.27 21.69 21.69 0 018.54-7.76 25 25 0 0111.44-2.68 18.93 18.93 0 0112.65 4.51l-6.93 9.52a9 9 0 00-2.52-1.64 6.48 6.48 0 00-2.34-.44 7.93 7.93 0 00-6.5 2.9 12.25 12.25 0 00-2.34 7.86 11.79 11.79 0 002.43 7.83 7.42 7.42 0 005.9 2.9 8.56 8.56 0 003.59-.81 19.63 19.63 0 003.33-1.95l5.73 9.71a17.73 17.73 0 01-7.11 3.93 27 27 0 01-7.28 1.1zm21.32-1V155.7h14.56v32.06h.35l12-15.07h16.12l-15.43 18L301.94 216h-16.11l-8.68-15.42-4.5 5V216zM83.47 294.09a33.6 33.6 0 01-11-2A30.65 30.65 0 0162.14 286l8.5-10.24a28.64 28.64 0 006.54 3.95 16.78 16.78 0 006.63 1.52 8.85 8.85 0 004.9-1 3.36 3.36 0 001.52-2.94 3.27 3.27 0 00-2-3q-2.75-1.35-5.59-2.43l-7-2.95a20.8 20.8 0 01-7.93-5.72 14.49 14.49 0 01-3.43-10 15.29 15.29 0 012.69-8.8 19.11 19.11 0 017.41-6.32 23.68 23.68 0 0110.88-2.39 27.77 27.77 0 019.74 1.87 25 25 0 018.89 5.67l-7.45 9.36a27.2 27.2 0 00-5.52-3 15.66 15.66 0 00-5.75-1.05 8.32 8.32 0 00-4.29 1 3.11 3.11 0 00-1.61 2.86 3.38 3.38 0 002.25 3.08c1.51.72 3.53 1.58 6.08 2.55l6.84 2.69a18.61 18.61 0 018.15 5.8 15.43 15.43 0 012.86 9.62 16.47 16.47 0 01-2.56 8.89 18.34 18.34 0 01-7.5 6.63 26.15 26.15 0 01-11.92 2.44zm49.91 0A23.8 23.8 0 01122 291.4a19.94 19.94 0 01-8-7.75 24.1 24.1 0 01-3-12.27 23.28 23.28 0 013-12.08 21.28 21.28 0 017.75-7.84 19.69 19.69 0 0110.06-2.78 18.65 18.65 0 0110.61 2.82 16.94 16.94 0 016.24 7.58 26.81 26.81 0 012 10.57 31.27 31.27 0 01-.21 3.68c-.15 1.19-.28 2-.39 2.56h-24.7a8.28 8.28 0 003.72 5.42 12.7 12.7 0 006.33 1.51 18.41 18.41 0 008.85-2.6l4.84 8.84a26.93 26.93 0 01-7.79 3.74 27.8 27.8 0 01-7.93 1.29zm-8.14-27.9h12.82a7.59 7.59 0 00-1.26-4.38c-.83-1.24-2.38-1.87-4.64-1.87a6.9 6.9 0 00-4.32 1.48 7.76 7.76 0 00-2.6 4.77zm53.76 27.9a23.86 23.86 0 01-11.4-2.69 19.88 19.88 0 01-8-7.75 24.1 24.1 0 01-2.94-12.27 23.19 23.19 0 013-12.08 21.26 21.26 0 017.74-7.84 19.69 19.69 0 0110.06-2.78A18.7 18.7 0 01188 251.5a17 17 0 016.24 7.58 26.81 26.81 0 012 10.57 29.23 29.23 0 01-.22 3.68c-.14 1.19-.27 2-.38 2.56H171a8.21 8.21 0 003.72 5.42 12.67 12.67 0 006.33 1.51 18.28 18.28 0 008.83-2.6l4.86 8.84a26.86 26.86 0 01-7.8 3.74 27.84 27.84 0 01-7.94 1.29zm-8.15-27.9h12.83a7.74 7.74 0 00-1.26-4.38c-.84-1.24-2.38-1.87-4.64-1.87a6.94 6.94 0 00-4.33 1.48 7.91 7.91 0 00-2.61 4.77zm33.63 26.87v-60.34H219v32.07h.35l12-15.09h16.12l-15.43 18 16.3 25.31h-16.17l-8.66-15.44-4.51 5v10.41zm66.55 1a23.82 23.82 0 01-11.39-2.69 19.9 19.9 0 01-8-7.75 24.2 24.2 0 01-2.94-12.27 23.28 23.28 0 013-12.08 21.28 21.28 0 017.75-7.84 19.65 19.65 0 0110.05-2.78 18.66 18.66 0 0110.62 2.82 16.88 16.88 0 016.24 7.58 26.81 26.81 0 012 10.57 31.27 31.27 0 01-.21 3.68c-.15 1.19-.27 2-.39 2.56H263a8.25 8.25 0 003.73 5.42 12.63 12.63 0 006.32 1.51 18.28 18.28 0 008.84-2.6l4.85 8.84a26.93 26.93 0 01-7.74 3.77 27.85 27.85 0 01-8 1.29zm-8.14-27.9h12.82a7.65 7.65 0 00-1.25-4.38c-.85-1.24-2.38-1.87-4.65-1.87a6.92 6.92 0 00-4.32 1.48 7.81 7.81 0 00-2.61 4.77zm33.62 26.87v-43.31h12.13l1.05 7.45h.31a16.69 16.69 0 015.77-6.45 12.46 12.46 0 016.53-2 19.14 19.14 0 013.25.21 7.77 7.77 0 012.13.65l-2.43 12.82c-.74-.17-1.5-.33-2.25-.47a13.36 13.36 0 00-2.6-.22 9.27 9.27 0 00-4.89 1.6 11 11 0 00-4.12 5.68v24.1z" + fill="#fff" + /> + </svg> +); + +export default Blockseeker; diff --git a/src/config/validators/Blockshard.tsx b/src/config/validators/Blockshard.tsx new file mode 100644 index 0000000000..20c560c6d3 --- /dev/null +++ b/src/config/validators/Blockshard.tsx @@ -0,0 +1,17 @@ +const Blockshard = () => ( + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 800"> + <path fill="#151c2c" d="M-6.87-11.4h813.73v822.81H-6.87z" /> + <path + fill="#12e1ff" + d="M325.29 401.94l74.86 43.22 74.86-43.22-74.86-43.22z" + /> + <path fill="#e7168b" d="M409.88 465.93v86.45l74.86-43.23v-86.44z" /> + <path fill="#6300f2" d="M390.32 465.93v86.45l-74.86-43.23v-86.44z" /> + <path + fill="#fff" + d="M586.77 350.12l-.13.02.02-.01-93.27-53.86L400 242.36l-39.84 24.7-44.75 27.47v73.28l85.15-51.03 60.74 35.07 61.19 35.32.21-.11-.1.11v141.64l-61.3 35.4-61.36 35.42-.06.14v-.23h.01l-61.19-35.33-61.3-35.4V126.27l-64.18 37.05v98.1l.01 76.8h-.01l.01 78.22-.01 149.42 93.39 53.92 93.28 53.85v-.11l.05.21 93.45-53.95 93.39-53.92V458.03z" + /> + </svg> +); + +export default Blockshard; diff --git a/src/config/validators/CoinbaseCloud.tsx b/src/config/validators/CoinbaseCloud.tsx new file mode 100644 index 0000000000..d53ed4fb9d --- /dev/null +++ b/src/config/validators/CoinbaseCloud.tsx @@ -0,0 +1,18 @@ +const CoinbaseCloud = () => ( + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"> + <path + style={{ + fill: '#45e1e5', + }} + d="M0 0h64v64H0z" + /> + <path + d="M32 43a11 11 0 1 1 10.83-12.83h11.09a22 22 0 1 0 0 3.66H42.83A11 11 0 0 1 32 43Z" + style={{ + fill: '#0a0b0d', + }} + /> + </svg> +); + +export default CoinbaseCloud; diff --git a/src/config/validators/Crifferent.tsx b/src/config/validators/Crifferent.tsx new file mode 100644 index 0000000000..50138ffdaa --- /dev/null +++ b/src/config/validators/Crifferent.tsx @@ -0,0 +1,206 @@ +const Crifferent = () => ( + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 500"> + <path + style={{ + fill: '#2d2d2d', + }} + d="M0 0h500v500H0z" + /> + <path + d="M66.5 162 122 94m-55.5 68v79.5l75 16m-75-95.5 136.5-5.5M122 94l66.5-32.5M122 94l19.5 163.5m47-196H303m-114.5 0L322 162M303 61.5l87.5 40m-87.5-40-100 95m187.5-55 55.5 68m-55.5-68L322 162m124 7.5V264m0-94.5-148.5 80M446 264 322 162m124 102-80.5 67m80.5-67-148.5-14.5M322 162l-180.5 95.5m0 0L169 325l94 27m-121.5-94.5 224 73.5M263 352v-38m0 38v87.5m0-125.5-60-157.5M263 314l34.5-64.5m-94.5-93 94.5 93m68 81.5v108.5" + style={{ + fill: 'none', + stroke: '#f8f8f8', + strokeWidth: 8, + }} + /> + <path + style={{ + fill: '#f8f8f8', + }} + d="M49 55h12v12H49zM61 67h12v12H61zM61 43h12v12H61zM73 55h12v12H73zM427 83h12v12h-12zM439 95h12v12h-12zM439 71h12v12h-12zM451 83h12v12h-12zM387 365h12v12h-12zM399 377h12v12h-12zM399 353h12v12h-12zM411 365h12v12h-12zM116 379h12v12h-12zM128 391h12v12h-12zM128 367h12v12h-12zM140 379h12v12h-12zM78 305h12v12H78zM90 317h12v12H90zM90 293h12v12H90zM102 305h12v12h-12z" + /> + <circle + cx={390} + cy={103} + r={14} + style={{ + fill: '#2d2d2d', + stroke: '#f8f8f8', + strokeWidth: 8, + }} + /> + <circle + cx={303} + cy={63.5} + r={14} + style={{ + fill: '#2d2d2d', + stroke: '#f8f8f8', + strokeWidth: 8, + }} + /> + <circle + cx={189} + cy={63.5} + r={14} + style={{ + fill: '#2d2d2d', + stroke: '#f8f8f8', + strokeWidth: 8, + }} + /> + <circle + cx={122} + cy={96} + r={14} + style={{ + fill: '#2d2d2d', + stroke: '#f8f8f8', + strokeWidth: 8, + }} + /> + <circle + cx={203} + cy={158} + r={14} + style={{ + fill: '#2d2d2d', + stroke: '#f8f8f8', + strokeWidth: 8, + }} + /> + <circle + cx={298} + cy={251.5} + r={14} + style={{ + fill: '#2d2d2d', + stroke: '#f8f8f8', + strokeWidth: 8, + }} + /> + <circle + cx={263} + cy={315} + r={14} + style={{ + fill: '#2d2d2d', + stroke: '#f8f8f8', + strokeWidth: 8, + }} + /> + <circle + cx={365} + cy={333} + r={14} + style={{ + fill: '#2d2d2d', + stroke: '#f8f8f8', + strokeWidth: 8, + }} + /> + <circle + cx={263} + cy={354} + r={14} + style={{ + fill: '#2d2d2d', + stroke: '#f8f8f8', + strokeWidth: 8, + }} + /> + <circle + cx={263} + cy={441.5} + r={14} + style={{ + fill: '#2d2d2d', + stroke: '#f8f8f8', + strokeWidth: 8, + }} + /> + <circle + cx={365} + cy={441.5} + r={14} + style={{ + fill: '#2d2d2d', + stroke: '#f8f8f8', + strokeWidth: 8, + }} + /> + <circle + cx={169} + cy={327} + r={14} + style={{ + fill: '#2d2d2d', + stroke: '#f8f8f8', + strokeWidth: 8, + }} + /> + <circle + cx={142} + cy={259} + r={14} + style={{ + fill: '#2d2d2d', + stroke: '#f8f8f8', + strokeWidth: 8, + }} + /> + <circle + cx={66.5} + cy={243} + r={14} + style={{ + fill: '#2d2d2d', + stroke: '#f8f8f8', + strokeWidth: 8, + }} + /> + <circle + cx={66.5} + cy={164} + r={14} + style={{ + fill: '#2d2d2d', + stroke: '#f8f8f8', + strokeWidth: 8, + }} + /> + <circle + cx={446} + cy={172} + r={14} + style={{ + fill: '#2d2d2d', + stroke: '#f8f8f8', + strokeWidth: 8, + }} + /> + <circle + cx={446} + cy={266} + r={14} + style={{ + fill: '#2d2d2d', + stroke: '#f8f8f8', + strokeWidth: 8, + }} + /> + <circle + cx={322} + cy={164} + r={14} + style={{ + fill: '#2d2d2d', + stroke: '#f8f8f8', + strokeWidth: 8, + }} + /> + </svg> +); + +export default Crifferent; diff --git a/src/config/validators/Decentradot.tsx b/src/config/validators/Decentradot.tsx new file mode 100644 index 0000000000..3a6209e098 --- /dev/null +++ b/src/config/validators/Decentradot.tsx @@ -0,0 +1,11 @@ +const Decentradot = () => ( + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"> + <path fill="#fff" d="M0 0h512v512H0z" /> + <path + d="M141.22 94.56a37.39 37.39 0 0 1 32.36-18.68h164.84a37.39 37.39 0 0 1 32.36 18.68l82.42 142.76a37.38 37.38 0 0 1 0 37.36l-82.42 142.76a37.39 37.39 0 0 1-32.36 18.68H173.58a37.39 37.39 0 0 1-32.36-18.68L58.81 274.68a37.33 37.33 0 0 1 0-37.36z" + fill="#ed1382" + /> + </svg> +); + +export default Decentradot; diff --git a/src/config/validators/Dionysus.tsx b/src/config/validators/Dionysus.tsx new file mode 100644 index 0000000000..3f4c068ca1 --- /dev/null +++ b/src/config/validators/Dionysus.tsx @@ -0,0 +1,43 @@ +const Dionysus = () => ( + <svg + xmlns="http://www.w3.org/2000/svg" + x={0} + y={0} + viewBox="0 0 646.47 652.6" + xmlSpace="preserve" + > + <style>{'.dion0{fill:#e3187d}'}</style> + <circle fill="#e3187d" cx={158.39} cy={300.66} r={27.75} /> + <circle fill="#e3187d" cx={159.01} cy={232.74} r={23.93} /> + <circle fill="#e3187d" cx={169.83} cy={124.95} r={14.77} /> + <circle fill="#e3187d" cx={100.74} cy={206.94} r={14.77} /> + <circle fill="#e3187d" cx={112.82} cy={134.87} r={9.92} /> + <circle fill="#e3187d" cx={137.68} cy={74.69} r={9.92} /> + <path + fill="#e3187d" + d="m505.72 243.01-29.35 40.11-48.3 63.09-83.47 109.18-101.84-109.18-1.16-1.24 18.13-16.93 16.97 18.17 65.96 70.82 54.24-70.82 96.21-128.78c-28.03-46.51-77.19-78.78-134.22-83.47-4.74-.43-9.59-.62-14.44-.62-11.53 0-22.79 1.13-33.62 3.26.27.39.43.85.66 1.24 0 .04 0 .08.04.08 0 .04.04.08.04.08 2.29 4.39 3.57 9.28 3.57 14.6 0 17.55-14.25 31.8-31.84 31.8-11.14 0-20.85-5.71-26.52-14.33-.04 0-.04 0-.04-.04l-.04-.04a31.448 31.448 0 0 1-4.39-10.17c-23.3 14.6-42.86 34.63-56.92 58.24 2.21.97 4.27 2.14 6.21 3.49 0 0 0 .04.04.04 8.04 5.78 13.24 15.18 13.24 25.82 0 17.63-14.25 31.88-31.8 31.88-2.29 0-4.5-.23-6.64-.74-.66-.12-1.28-.27-1.94-.47h-.04c-.35-.12-.7-.19-1.05-.31-1.59 9.43-2.41 19.1-2.41 28.96 0 6.1.35 12.07.93 17.98v.16c9.05 87.28 82.81 155.3 172.5 155.3 95.78 0 173.39-77.65 173.39-173.43.01-22.49-4.26-44-12.1-63.73zM307.91 343.42c-9.47-5.09-13.01-16.89-7.92-26.36 5.09-9.47 16.89-13.01 26.36-7.92 9.47 5.09 13.01 16.89 7.92 26.36-5.09 9.47-16.89 13-26.36 7.92zm14.48-67.79c5.09-9.47 16.89-13.01 26.36-7.92 9.47 5.09 13.01 16.89 7.92 26.36-5.09 9.47-16.89 13.01-26.36 7.92s-13.01-16.89-7.92-26.36zm34.28 102.54c-5.09 9.47-16.89 13.01-26.36 7.92s-13.01-16.89-7.92-26.36 16.89-13.01 26.36-7.92 13.01 16.88 7.92 26.36zm25.2-42.67c-5.12 9.47-16.93 13.01-26.36 7.92-9.47-5.09-13.05-16.89-7.92-26.36 5.09-9.47 16.89-13.01 26.32-7.92 9.47 5.08 13.04 16.88 7.96 26.36zm-12.54-59.87c5.09-9.47 16.89-13.01 26.36-7.92 9.47 5.09 13.01 16.89 7.92 26.36-5.09 9.47-16.89 13.01-26.36 7.92-9.48-5.09-13.01-16.89-7.92-26.36zm-53.89-60.76c9.09.16 17.28 5.44 21.24 13.63l3.3 6.99.5-.7c3.22-4.74 8.39-7.77 14.09-8.35l12.19-1.13-2.6 11.69c-1.48 6.79-6.52 12.23-13.12 14.29l-13.55 4.19-.08.04v-.04l-19.61-6.06a24.05 24.05 0 0 1-16.35-17.74l-3.8-17.16 17.79.35zm-41.55 60.76c5.09-9.47 16.89-13.01 26.36-7.92 9.47 5.09 13.01 16.89 7.92 26.36-5.09 9.47-16.89 13.01-26.36 7.92s-13-16.89-7.92-26.36z" + /> + <circle fill="#e3187d" cx={215.27} cy={180.92} r={42.87} /> + <circle fill="#e3187d" cx={334.02} cy={121.69} r={27.75} /> + <circle fill="#e3187d" cx={273.46} cy={134.1} r={23.93} /> + <circle fill="#e3187d" cx={323.82} cy={61.69} r={14.77} /> + <circle fill="#e3187d" cx={251.82} cy={64.77} r={9.92} /> + <circle fill="#e3187d" cx={200.42} cy={74.69} r={9.92} /> + <circle fill="#e3187d" cx={275.13} cy={22.15} r={9.92} /> + <circle fill="#e3187d" cx={210.34} cy={24.23} r={9.92} /> + <path + fill="#e3187d" + d="m534.45 203.8-14.44 19.76c11.84 24.93 18.44 52.76 18.44 82.15 0 106.23-86.43 192.65-192.65 192.65-98.73 0-180.27-74.54-191.37-170.29v-.04c-.04-.19-.04-.35-.08-.54-.78-6.87-1.2-13.86-1.24-20.93h-21.82c.43 117.91 96.44 213.66 214.51 213.66 118.26 0 214.51-96.25 214.51-214.51 0-36.84-9.36-71.59-25.86-101.91z" + /> + <path + d="M70.17 601.31v27.17c0 10-8.11 18.11-18.11 18.11H6.77v-63.4h45.29c10 .01 18.11 8.12 18.11 18.12zm-9.06 0c0-5-4.06-9.06-9.06-9.06H15.83v45.29h36.23c5 0 9.06-4.05 9.06-9.06v-27.17zm63.22-9.06v45.29h27.17v9.06H88.1v-9.06h27.17v-45.29H88.1v-9.06h63.4v9.06h-27.17zm90.38 54.35h-27.17c-10 0-18.11-8.11-18.11-18.11V583.2h45.29c10 0 18.11 8.11 18.11 18.12v27.17c0 10-8.11 18.11-18.12 18.11zm0-54.35h-36.23v36.23c0 5 4.05 9.06 9.06 9.06h27.17c5 0 9.06-4.05 9.06-9.06v-27.17c0-5-4.05-9.06-9.06-9.06zm99.45 9.06v45.29h-9.06v-45.29c0-5-4.05-9.06-9.06-9.06h-36.23v54.34h-9.06v-63.4h45.29c10.01.01 18.12 8.12 18.12 18.12zm81.33-18.11v45.29c0 10-8.11 18.11-18.11 18.11H350.2c-10 0-18.11-8.11-18.11-18.11h9.06c0 5 4.05 9.06 9.06 9.06h27.17c5 0 9.06-4.05 9.06-9.06v-9.06H350.2c-10 0-18.11-8.11-18.11-18.11V583.2h9.06v18.12c0 5 4.05 9.06 9.06 9.06h36.23V583.2h9.05zm26.98 18.11c0 5 4.05 9.06 9.06 9.06h27.17c10 0 18.11 8.11 18.11 18.11s-8.11 18.12-18.11 18.12h-45.29v-9.06h45.29c5 0 9.06-4.05 9.06-9.06 0-5-4.05-9.06-9.06-9.06h-27.17c-10 0-18.11-8.11-18.11-18.11V583.2h63.4v9.06h-54.34v9.05zm135.67 27.17c0 10-8.11 18.11-18.11 18.11h-45.29v-63.4h9.06v54.34h36.23c5 0 9.06-4.05 9.06-9.06V583.2h9.06v45.28zm26.99-27.17c0 5 4.05 9.06 9.06 9.06h27.17c10 0 18.11 8.11 18.11 18.11s-8.11 18.12-18.11 18.12h-45.29v-9.06h45.29c5 0 9.06-4.05 9.06-9.06 0-5-4.05-9.06-9.06-9.06h-27.17c-10 0-18.11-8.11-18.11-18.11V583.2h63.4v9.06h-54.34v9.05z" + style={{ + stroke: '#e3187d', + strokeMiterlimit: 10, + fill: '#e3187d', + }} + /> + </svg> +); + +export default Dionysus; diff --git a/src/config/validators/Dozenodes.tsx b/src/config/validators/Dozenodes.tsx new file mode 100644 index 0000000000..0e9c481aae --- /dev/null +++ b/src/config/validators/Dozenodes.tsx @@ -0,0 +1,8 @@ +const Dozenodes = () => ( + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 192 192"> + <path d="M113.8 8c-5.1.9-11.1 5.1-13.9 9.7C98 20.8 97.5 23 97.4 29l-.1 7.5-10.8 7.9-10.7 7.8-4.4-5C65 39.8 57.6 36.5 48 36.5c-12.9 0-22.2 6.2-27.8 18.3-2.1 4.7-2.4 6.4-2 13.6.5 9.4 2.5 14.1 8.8 20.3 2.2 2.3 6.5 5.2 9.5 6.4 6.2 2.6 6.1 2.1 4.4 14.4l-1.1 8-5.8 2.7c-20.7 9.8-14.3 40.3 8.5 40.3 9.6 0 15.3-4.2 21-15.2 0-.1 7.5.9 16.5 2.2 9.1 1.4 16.8 2.5 17.1 2.5.4 0 1.2 2 1.9 4.5 2.9 10.3 10.6 17.9 21.4 21 14.1 4.1 29.5-3.3 35.9-17.4 1.6-3.4 2.2-6.5 2.1-12.1 0-8.8-2.2-14.6-7.8-20.7l-3.6-4 5.8-7.6c5.7-7.6 5.7-7.6 9.7-7 12.3 1.6 23.4-8.5 23.5-21.2 0-13.5-13.2-24.1-25.7-20.6-2.7.7-5.5 1.5-6.3 1.7-.9.2-5-3.8-10.5-10.3-4.9-5.8-9.1-11-9.3-11.4-.2-.5.9-3.4 2.3-6.4 8-16.3-5-33.7-22.7-30.5zm9.6 9.2c.9 1.2 1.6 3.2 1.6 4.3 0 1.1.9 2.8 2 3.8 1.5 1.2 2 2.9 2 6.5 0 7.6-1 8.3-11.5 8l-9-.3-.3-6.5c-.2-5.2 0-6.7 1.4-7.7 1-.7 1.9-2.4 2-3.9.7-6.1 8.5-8.9 11.8-4.2zm-20.3 26.4c5.3 5.8 15.2 8 22.6 4.9 1.9-.8 3.7-1.5 3.9-1.5.2 0 4.8 5.2 10.3 11.6l9.9 11.7-2.5 3.6c-1.3 1.9-2.6 3.7-2.8 3.9-.2.2-15-2.2-32.9-5.4l-32.5-5.8-.8-4.5c-.4-2.5-.8-4.9-.8-5.3 0-.9 20.7-15.7 22.1-15.8.6 0 2.1 1.2 3.5 2.6zm-50.4 5.9c2.5 1.8 4.8 8.4 3.3 9.9s-4-.2-4-2.7c0-3.2-2.2-4.9-5.7-4.5-2.2.2-3 1-3.9 3.8-1.6 5-4.4 5.3-4.4.5 0-7.1 8.7-11.2 14.7-7zm7.7 13.1c2.3 2.3 2.3 16.6-.1 18.5-1.2 1-4.9 1.4-13.5 1.4C32.5 82.5 32 82.2 32 72c0-10.5.7-11 15-11 9.4 0 12.2.3 13.4 1.6zM170 73c2.4 2.4 2.7 7 .5 7-.8 0-1.5-.8-1.5-1.9 0-1-.7-2.4-1.6-3.2-2-1.6-4.8-.1-5.2 3-.2 1.1-1 2.1-1.8 2.1-2.1 0-1.7-4.7.6-7 1.1-1.1 3.1-2 4.5-2s3.4.9 4.5 2zm-56 8.4 29.5 5.2.3 3 .2 2.9-41.2 16.8C80.1 118.5 61.2 126 60.9 126c-.4 0-3-1.5-5.8-3.4-2.7-1.9-5.5-3.6-6.1-3.8-1.2-.4 1.3-19.6 2.7-20.8.4-.4 2.8-1.2 5.3-1.9 8.5-2.5 15.9-9.3 19.8-18.4 1.1-2.5 1.6-2.7 4.4-2.2 1.8.3 16.6 3 32.8 5.9zm60.8.8c.7.7 1.2 3.7 1.2 6.8 0 3.1-.5 6.1-1.2 6.8-1.6 1.6-17 1.6-18.6 0-1.3-1.3-1.7-13-.5-14.1 1.2-1.2 17.8-.8 19.1.5zM149 99.5l2.3 2.5-5.7 7.5-5.8 7.5-4.2-.8c-6.8-1.2-14-.5-19.5 1.8-6.7 2.9-14.3 11.2-16.6 18-2 5.9-.8 5.8-21.4 2.7-14.3-2.2-14.5-2.2-15.2-5-.7-2.5-.4-3 2.4-4.3 8.2-3.8 78.4-32.3 79.8-32.3.8-.1 2.6 1 3.9 2.4zM46.4 127.1c1.6 1.3 2.6 3 2.6 4.7 0 1.6.8 3.2 2 4 1.6 1 2 2.3 2 7 0 3.2-.5 6.3-1.2 7-1.6 1.6-17 1.6-18.6 0-.7-.7-1.2-4-1.2-7.6 0-5.5.3-6.5 2-6.9 1.4-.4 2-1.4 2-3.2 0-3.1.8-4.4 3.5-5.9 2.8-1.6 3.9-1.5 6.9.9zm88.6 2.4c2.5 2.7 3.3 9 1.1 9-.7 0-1.8-1.6-2.5-3.5-.9-2.8-1.7-3.6-3.9-3.8-3.5-.4-5.4 1.1-6.2 4.9-1 4.3-4.1 3.5-3.8-1 .3-7.9 9.9-11.4 15.3-5.6zm6.8 11.7c.8.8 1.2 4.5 1.2 10 0 8.6-.1 8.9-2.6 9.8-3.7 1.4-20.4 1.2-23.4-.3-2.3-1.1-2.5-1.9-2.8-9-.4-11.7-.5-11.7 14.1-11.7 8.2 0 12.7.4 13.5 1.2z" /> + <path d="M115 20.6c-.6 1.5-.9 2.9-.6 3.1.2.2 2.2.3 4.5.1 4-.3 4-.4 2.8-2.8-1.8-3.6-5.4-3.9-6.7-.4zM116.8 30.6c-.8.8.3 4.4 1.3 4.4.5 0 .9-.4.9-.9s.3-1.6.6-2.5c.6-1.5-1.6-2.3-2.8-1zM45.5 69c-.7 1.2.4 8 1.4 8 .9 0 2.8-6.4 2.3-7.8-.5-1.5-2.9-1.6-3.7-.2zM164 88.4c0 1.4.5 2.8 1 3.1.6.3 1 .3 1-.2 0-.4.3-1.8.6-3.1.5-1.6.2-2.2-1-2.2-1 0-1.6.9-1.6 2.4zM39.5 130.4c-.3.9-.7 2.3-1 3.1-.3 1.2.5 1.5 3.6 1.5 3.8 0 4-.2 3.7-2.8-.2-2.1-.9-2.8-3.1-3-1.8-.2-2.9.2-3.2 1.2zM41.7 140.6c-.4.4-.7 1.8-.7 3.1 0 2.8 2.3 2.7 2.8-.1.4-2-1.1-4-2.1-3zM126.4 148.5c-.4.9-.2 2.1.5 2.8.6.6 1.1 2 1.1 3.1 0 3.3 1.9.4 2.2-3.4.2-2.6-.1-3.5-1.5-3.8-.9-.2-2 .4-2.3 1.3z" /> + </svg> +); + +export default Dozenodes; diff --git a/src/config/validators/DragonStake.tsx b/src/config/validators/DragonStake.tsx new file mode 100644 index 0000000000..039209ed92 --- /dev/null +++ b/src/config/validators/DragonStake.tsx @@ -0,0 +1,14 @@ +const DragonStake = () => ( + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 258.16 232.53"> + <path + d="M230.54 96.72c-13.81-11.26-15.72-19.46-15.72-19.46C223.25 56.7 181.5 1.68 136.89.19c7.39 10.37 9.23 16.81 9.23 16.81l-1-.14c-.28-.24-.53-.51-.82-.74a69.77 69.77 0 00-14.95-9.69 66.75 66.75 0 00-8-3.27 64.68 64.68 0 00-7.83-2.08 57.06 57.06 0 00-7.22-.94c-1.14-.05-2.21-.12-3.24-.14s-2 .05-2.94.11a33.48 33.48 0 00-4.75.54 40.41 40.41 0 00-4.11 1s1.27 1 3.43 2.39l3.83 2.5c1.47.9 3.09 1.95 4.83 3.07s3.6 2.31 5.56 3.49l4.79 3-2.35.44c-.23-.06-.45-.14-.68-.21a74.38 74.38 0 00-8.75-1.74 67.42 67.42 0 00-8.63-.62 65.61 65.61 0 00-8.09.44 57.77 57.77 0 00-7.16 1.35c-1.09.3-2.13.57-3.11.87s-1.91.67-2.76 1a32.16 32.16 0 00-4.36 2 39.69 39.69 0 00-3.61 2.19s1.51.54 4 1.21l4.42 1.19c1.68.39 3.54.89 5.54 1.42 1.56.34 3.23.8 4.93 1.23a103.06 103.06 0 00-53.7 69.05H15.86v22.05h11a102.68 102.68 0 006.11 35.57H17.21v45.36h41.78v21.43h30.84l1.22.63a81.46 81.46 0 009.4 4c1.58.6 3.14 1.23 4.72 1.8l4.83 1.38a71.54 71.54 0 009.63 2.28l4.81.82a37.29 37.29 0 004.78.64c3.2.27 6.36.46 9.48.65 6.26-.17 12.36-.14 18.2-1.2a67.87 67.87 0 008.7-1.55c2.88-.69 5.7-1.37 8.44-2.1l7.12-2.59 3.45-1.26h.11s.75-.24.47-.16l.22-.09.44-.2.88-.39 1.74-.79a117.64 117.64 0 0012.72-6.89 114.71 114.71 0 0010.49-7.57 101.5 101.5 0 0014.67-14.72 97.82 97.82 0 007.56-10.78c.79-1.27 1.3-2.31 1.69-3s.57-1 .57-1l-3.4 3.1c-2.2 2-5.56 4.76-9.95 8a124.86 124.86 0 01-16.56 10.2c-1.93 1-4 1.94-6.11 2.87-1.43-6.16-4.39-15.88-9.84-22.62-25.66-31.72-47.79-18.65-66.86-40-11-12.33-8.25-24.15-6-31.22a39 39 0 0114.35-19.4c.26 38.18 42.77 30.8 59.68 40.21 27 15 24.16 26.15 24.16 26.15l31.71 4.51s9.56-19.34 10.73-32.78c1.68-19.65-16.16-21.26-27.54-30.54zM57.73 135.66h4.83v4.88h-4.83zm-19.09-4.83h9.71v9.71h-9.71zm12.09 25.12h-4.84v-4.83h4.84zm24.91 14.18H53.59v-22.05h22.05zm15.22 33.59h-9.67v-9.67h9.67zm51.66-9.67h9.67v9.67h-9.67zm-12.17 16.72h4.84v4.83h-4.84zM113.04 17.06l.61-.1zm1.74-.29l-.6-.38-.75.44.75-.44.6.38zm50.13 37.93s25.77 11.84 25.44 26.84c0 0-23-3.34-25.44-26.84z" + fill="#006938" + /> + <path + fill="#006938" + d="M4.83 124.64h22.05v22.05H4.83zM0 106.97h9.67v9.67H0zM0 165.29h9.67v9.67H0z" + /> + </svg> +); + +export default DragonStake; diff --git a/src/config/validators/Gatotech.tsx b/src/config/validators/Gatotech.tsx new file mode 100644 index 0000000000..b9363514e2 --- /dev/null +++ b/src/config/validators/Gatotech.tsx @@ -0,0 +1,83 @@ +const Gatotech = () => ( + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 521.94 521.94"> + <g> + <path + d="m170.29 270.91-15.09-31.62-12.23-76.18 31.63-58.22 14.91 50.81 41.34 2.64 26.4-50.57 25.87 59.65 18.37 82.55-8.31 29.2 59.93 158.8 44.3-108.88 53.9 55.34-43.89-7.95-9.21 98.1-87.77-8.22-120.74 9z" + strokeWidth={35} + stroke="#010101" + fill="none" + /> + <rect + strokeWidth={10} + stroke="#010101" + fill="#010101" + x={201.02} + y={201.79} + width={12.88} + height={9.04} + rx={3.46} + /> + <rect + strokeWidth={10} + stroke="#010101" + fill="#010101" + x={237.97} + y={202.75} + width={12.88} + height={9.04} + rx={3.46} + /> + <path + strokeWidth={15} + stroke="#010101" + d="m191.69 231.52 61.38 9.82" + transform="translate(4.97 4.97)" + /> + <rect + x={4.97} + y={4.97} + width={512} + height={512} + rx={3.44} + fill="#c9d8ef" + strokeOpacity={0} + strokeWidth={9.94} + stroke="#010101" + /> + </g> + <g id="g843"> + <path + d="m170.29 270.91-15.09-31.62-12.23-76.18 31.63-58.22 14.91 50.81 41.34 2.64 26.4-50.57 25.87 59.65 18.37 82.55-8.31 29.2 59.93 158.8 44.3-108.88 53.9 55.34-43.89-7.95-9.21 98.1-87.77-8.22-120.74 9z" + fill="#fff" + strokeWidth={35} + stroke="#010101" + /> + <rect + strokeWidth={10} + fill="#010101" + x={201.02} + y={201.79} + width={12.88} + height={9.04} + rx={3.46} + /> + <rect + strokeWidth={10} + fill="#010101" + x={237.97} + y={202.75} + width={12.88} + height={9.04} + rx={3.46} + /> + <path + strokeWidth={15} + stroke="#010101" + d="m191.69 231.52 61.38 9.82" + transform="translate(4.97 4.97)" + /> + </g> + </svg> +); + +export default Gatotech; diff --git a/src/config/validators/Gdot.tsx b/src/config/validators/Gdot.tsx new file mode 100644 index 0000000000..613d6823f0 --- /dev/null +++ b/src/config/validators/Gdot.tsx @@ -0,0 +1,20 @@ +const Gdot = () => ( + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"> + <g> + <g> + <path + style={{ + fill: '#161719', + }} + d="M0 0h512v512H0z" + /> + <path + fill="#eee" + d="M90.39 308.09c-18.78.59-35.46-12.1-34.94-31.5 1.23-12.16-3.46-51 4.37-61 16-25.71 67.2-17.15 65.51 16.61h-21.48c1.24-12.75-15.59-16.41-23.41-9.6q-3.51 3.3-3.51 9.6c1.33 7.27-3.39 49.14 3.51 53.91 4.21 4.49 15.69 4.49 19.9 0 4.57-3.56 3.39-13.12 3.51-18.54H88.39v-18.19h36.94c1.1 28.25 2.49 58.46-34.94 58.71ZM149.38 269.43v-20.05h45.82v20.05ZM245.89 308.09c-16.65 0-25.73-14.19-25.49-30.79.2-12.5-2-33.52 7.09-42.45 10.52-13.34 34.6-10.44 39.59 6.65h.43c-.6-9.66-.86-29.62-.72-39.37h21.48v104.53h-20.76v-13.61h-.43c-3.41 9.53-10.97 15.1-21.19 15.04Zm8.31-18.62c8.5 0 12.62-5.17 12.59-13.6-.57-10.41 3.93-31.77-12.6-30.79-8 0-12.36 4.78-12.31 12.89.74 10.2-4.32 32.51 12.32 31.5ZM337.24 308.09c-18.82.63-34.92-12.21-34.36-31.5-2.06-26.38 2.5-50 34.36-50.12 18.93-.66 34.87 12.2 34.37 31.5 2.2 26.03-2.8 50.3-34.37 50.12Zm0-18.62c17.18 1.26 12.23-21 12.89-31.5 0-8.38-4.51-12.91-12.89-12.89s-12.93 4.58-12.88 12.89c.64 10.52-4.22 32.7 12.88 31.5ZM428.31 306.66c-32.95 1.47-25-38.3-25.77-59.43h-20.77V227.9h20.77v-22.19H424v22.19h29.36v19.33H424c2.5 38.71-11.45 41.64 27.93 40.1v19.33Z" + /> + </g> + </g> + </svg> +); + +export default Gdot; diff --git a/src/config/validators/GenericChain.tsx b/src/config/validators/GenericChain.tsx new file mode 100644 index 0000000000..425ef3f474 --- /dev/null +++ b/src/config/validators/GenericChain.tsx @@ -0,0 +1,46 @@ +const GenericChain = () => ( + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"> + <defs> + <linearGradient + id="gnrcc-linear-gradient" + x1={259.06} + y1={-669.92} + x2={256.91} + y2={-434.66} + gradientTransform="matrix(1 0 0 -1 1.99 -210.71)" + gradientUnits="userSpaceOnUse" + > + <stop offset={0} stopColor="#fff" /> + <stop offset={0.08} stopColor="#e0e1e0" /> + <stop offset={0.28} stopColor="#9c9c9d" /> + <stop offset={0.46} stopColor="#666" /> + <stop offset={0.63} stopColor="#3a3a39" /> + <stop offset={0.78} stopColor="#1a1a1a" /> + <stop offset={0.91} stopColor="#090909" /> + <stop offset={1} stopColor="#010101" /> + </linearGradient> + <style>{'.gnrcc-2{fill:#010101}'}</style> + </defs> + <path fill="#fff" d="M0 0h512v512H0z" /> + <g id="Layer_1" data-name="Layer 1"> + <path + fill="#010101" + d="M274.69 189.05c-.86 39.64-32.74 71.52-72.38 70.66s-71.53-32.71-70.67-72.38 32.75-70.67 71.53-70.67c39.64 0 71.52 31.89 71.52 72.39zm72.39 0c0-76.7-64.63-138.74-143.91-138.74s-144.78 62-144.78 138.74S123 327.79 203.17 327.79s143.91-62.04 143.91-138.74z" + /> + <path + fill="#010101" + d="M164.39 422.58h294.72c1.72-11.2 3.45-23.26 3.45-35.33 0-106.85-90.49-193-202.51-193s-202.52 86.15-202.52 193A187.84 187.84 0 0 0 61 425.17z" + /> + <path + fill="#fff" + d="M164.39 351.92c19.82-52.57 77.56-79.28 130.13-59.46 27.57 10.34 50 31.88 59.46 59.46zm-117.2 0h415.37v104.27H47.19z" + /> + <path + d="M166.11 345.89 61 347.61c18.1 62.05 102.55 109.44 202.51 109.44 91.35 0 173.21-34.47 195.62-106H354.84c-19 31-58.6 42.22-91.35 42.22-44.81-.86-81.86-19.82-97.38-47.39" + fill="url(#gnrcc-linear-gradient)" + /> + </g> + </svg> +); + +export default GenericChain; diff --git a/src/config/validators/GoOpen.tsx b/src/config/validators/GoOpen.tsx new file mode 100644 index 0000000000..74ff9dba74 --- /dev/null +++ b/src/config/validators/GoOpen.tsx @@ -0,0 +1,17 @@ +const GoOpen = () => ( + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 266.33 196.67"> + <path d="m224.78 60.19 41.55 55.23-13.26 49.68-37.49 24.03-45.57-7.73-19.74-30.61 3.22-8.92 21.05 32.63 39.42 6.7 32.92-21.08 10.82-43.13-35.96-48.8-59.99 7.55-10.36.12 1.9-6.64 71.49-9.03z" /> + <path + d="m221.75 68.19 36 48.8-10.82 43.13-32.96 21.08-39.42-6.7-21.05-32.64 23.91-66.28-15.65.16zm-5 68.55 4.88-20-21.16-12.89-21.58 8-2.8 20 14.65 9.38 13.62 5.12z" + fill="#ffc300" + /> + <path d="m221.59 116.78-4.88 20-12.4 9.61-13.62-5.12-14.65-9.38 2.8-20 21.58-8zm-11.58 15.6 2.92-12-13.31-8.13-13.94 5.14-1.51 10.71 10.22 6.57 8.58 3.14z" /> + <path + fill="#fd6412" + d="m157.32 82.61-7.95.27-46.27 1.62-13.46 36.18 32.53-3.24-10.09 30.24-42.98-5.59-21.18-35.12 9.63-39.61 37.42-22.63 21.49 17.93 45.93-12.54-26.45-40.33-69.19-.98-54.97 38.24-3.52 88.75 30.85 52.02 90.61-9.97 14.21-36.89 3.36-8.7 19.26-49.98-9.23.33z" + /> + <path d="m161.76 75.74-10.37.11-54.22.57-20.07 53.92 33-3.35-3.81 11.51-32.12-4.16-17.32-28.76 8-32.91 29.32-17.7 20.3 16.86 61.22-16.7-34.75-52.92L64.87 0 3.17 42.78l-3.2 95.09 34 58.8 103.76-11.09 12.54-34.8 3.22-8.92 23.92-66.28zm-14.47 56.51-3.35 8.71-14.22 36.88-90.61 10-30.85-52 3.52-88.75 55-38.24 69.18 1 26.45 40.33-45.95 12.47-21.49-17.92-37.42 22.63-9.63 39.61 21.19 35.12 43 5.59 10.06-30.24-32.54 3.24 13.46-36.18 46.27-1.61 8-.27 9.24-.33z" /> + </svg> +); + +export default GoOpen; diff --git a/src/config/validators/Helikon.tsx b/src/config/validators/Helikon.tsx new file mode 100644 index 0000000000..3e8027b4d6 --- /dev/null +++ b/src/config/validators/Helikon.tsx @@ -0,0 +1,15 @@ +const Helikon = () => ( + <svg + xmlns="http://www.w3.org/2000/svg" + viewBox="0 0 300 300" + style={{ + backgroundColor: '#000', + padding: '5.5px', + }} + fill="#fff" + > + <path d="M260.4 110.4C260.4 49.5 210.9 0 150 0S39.6 49.6 39.6 110.5 89.1 220.9 150 220.9s110.4-49.6 110.4-110.5zM150 5c58.1 0 105.4 47.3 105.4 105.4 0 14.2-2.8 27.7-7.9 40l-83.8-83.8C160 63 155.2 61 150 61s-10 2-13.7 5.6l-83.8 83.8c-5.1-12.3-7.9-25.9-7.9-40C44.6 52.3 91.9 5 150 5zm-47.4 102.5 8.3 8.4c2.1 2.1 4.9 3.2 7.7 3.2s5.6-1 7.9-3.1l8.4-8.4c1.1-1.1 2.6-1.8 4.1-1.8 1.5-.1 2.9.5 3.9 1.5 4.1 4.1 10.9 3.9 15.1-.3 1.3-1.3 2.8-2.2 4.1-2.6.9-.2 1.5-.1 1.6 0 2.9 2.9 8.4 1.8 12.8-2.6l7.6-7.6 51.7 51.7-11.7 11.7c-2.2 2.2-6.1 2.2-8.3 0l-14.2-14.1c-4.2-4.3-11.1-4.3-15.4 0l-16.9 16.9c-2.3 2.3-6 2.3-8.3 0l-22.3-22.3c-2-2.1-4.8-3.2-7.7-3.2s-5.6 1.1-7.7 3.2l-3.4 3.4c-2.3 2.3-6 2.3-8.3 0L90.1 120l12.5-12.5zm3.6-3.5L140 70.2c2.7-2.7 6.3-4.2 10.1-4.2s7.4 1.5 10.1 4.2l20.4 20.4-7.6 7.6c-2.7 2.7-5.3 3-5.7 2.6-1.6-1.6-4-2-6.6-1.3-2.2.6-4.4 1.9-6.3 3.8-2.3 2.3-5.9 2.4-8 .3-4.1-4.1-10.8-4-15.1.3l-8.4 8.4c-2.3 2.3-6 2.3-8.3 0l-8.4-8.3zM150 215.8c-42 0-78.4-24.7-95.3-60.4l31.8-31.8 21.5 21.5c4.2 4.3 11.1 4.3 15.4 0l3.4-3.4c2.2-2.2 6.1-2.2 8.3 0l22.3 22.3c2.1 2.1 4.9 3.2 7.7 3.2s5.6-1.1 7.7-3.2l17-16.9c2.3-2.3 6-2.3 8.3 0l14.2 14.1c2 2.1 4.8 3.2 7.7 3.2 2.9 0 5.6-1.1 7.7-3.2l11.7-11.7 5.9 5.9c-16.9 35.7-53.3 60.4-95.3 60.4zM41.5 259.5c0-1.2 0-1.6-.4-2.2-.4-.6-1.1-1-1.9-1s-1.5.4-1.9 1c-.3.6-.4 1-.4 2.2v16.4H16.6v-16.4c0-1.2 0-1.6-.4-2.2-.4-.6-1.1-1-1.9-1s-1.5.4-1.9 1c-.3.6-.4 1-.4 2.2v37.2c0 1.2 0 1.6.4 2.2.4.6 1.1 1 1.9 1s1.5-.4 1.9-1c.3-.6.4-1 .4-2.2V280H37v16.7c0 1.2 0 1.6.4 2.2.4.6 1.1 1 1.9 1s1.5-.4 1.9-1c.3-.6.4-1 .3-2.2v-37.2zm86.8 36.4c-.5-.3-.9-.4-2-.4H107v-36c0-1.2 0-1.6-.4-2.2-.4-.6-1.1-1-1.9-1s-1.5.4-1.9 1c-.3.6-.4 1-.4 2.2v37.3c0 .7 0 1.6.6 2.2.7.6 1.5.6 2.2.7h21.1c1.2 0 1.5-.1 2-.4.6-.4.9-1 .9-1.7s-.4-1.3-.9-1.7zm17.1-39.6c-.8 0-1.5.4-1.9 1-.3.6-.4 1-.4 2.2v37.2c0 1.2 0 1.6.4 2.2.4.6 1.1 1 1.9 1s1.5-.4 1.9-1c.3-.6.4-1 .4-2.2v-37.2c0-1.2 0-1.6-.4-2.2-.4-.6-1.1-1-1.9-1zM183 273l10.3-12.5c.7-.9 1-1.4 1-2 0-.7-.3-1.2-.8-1.7-.5-.3-1-.5-1.5-.5-.8 0-1.5.6-2.1 1.4l-19.1 23.2h-.1v-21.4c0-1.2 0-1.6-.4-2.2-.4-.6-1.1-1-1.9-1s-1.5.4-1.9 1c-.3.6-.4 1-.4 2.2v37.2c0 1.2 0 1.6.4 2.2.4.6 1.1 1 1.9 1s1.5-.4 1.9-1c.3-.6.4-1 .4-2.2v-9.4l9-10.8 12.1 21.5c.6 1 1.2 1.9 2.4 1.9.4 0 .8-.1 1.2-.3.8-.4 1.2-1.1 1.2-1.8 0-.6-.2-1.1-.7-2.1L183 273zm41.2-16.7c-4.5 0-8.1 1.6-10.8 4.2-4.1 4.1-4.4 7.8-4.4 17.7s.3 13.6 4.4 17.7c2.7 2.6 6.3 4.2 10.8 4.2s8.1-1.6 10.8-4.2c4.1-4.1 4.4-7.8 4.4-17.7 0-9.9-.3-13.6-4.4-17.7-2.7-2.7-6.3-4.2-10.8-4.2zm7.4 36.5c-1.9 2-4.5 3.1-7.4 3.1-2.9 0-5.4-1.1-7.4-3.1-2.7-2.8-3-5.8-3-14.7 0-8.9.3-11.9 3-14.7 1.9-2 4.5-3.1 7.4-3.1 2.9 0 5.4 1.1 7.4 3.1 2.7 2.8 3 5.8 3 14.7s-.3 11.9-3 14.7zm56-35.5c-.4-.6-1.1-1-1.9-1s-1.5.4-1.9 1c-.3.6-.4 1-.4 2.2v31.3l-22.1-32.6c-.8-1.1-1.5-1.8-2.6-1.8-.7 0-1.4.2-1.8.7-.5.5-.7 1.2-.7 2.3v37.3c0 1.2 0 1.6.4 2.2.4.6 1.1 1 1.9 1s1.5-.4 1.9-1c.3-.6.4-1 .4-2.2v-31.3l22.1 32.6c.8 1.1 1.5 1.8 2.6 1.8.7 0 1.3-.2 1.8-.7s.7-1.2.7-2.3v-37.3c0-1.2 0-1.6-.4-2.2zm-200.7 1.3c0-1.2-.9-2.1-2.1-2.1H62.3c-.8 0-1.5.4-1.9 1.1-.3.7-.3 1.6.2 2.2L74 278l-13.4 18.6c-.5.7-.6 1.5-.2 2.2.4.7 1.1 1.1 1.9 1.1h22.4c1.2 0 2-.9 2-2.1s-.9-2.1-2.1-2.1H66.3l11.9-16.4c.5-.8.5-1.8 0-2.5l-11.8-16.1h18.4c1.2 0 2.1-.9 2.1-2.1z" /> + </svg> +); + +export default Helikon; diff --git a/src/config/validators/Highstake.tsx b/src/config/validators/Highstake.tsx new file mode 100644 index 0000000000..1264ed0f3c --- /dev/null +++ b/src/config/validators/Highstake.tsx @@ -0,0 +1,143 @@ +const HighStake = () => ( + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576.72 454.33"> + <defs> + <clipPath id="a" transform="translate(-15.35 -23.13)"> + <path + style={{ + fill: 'none', + }} + d="M143.54 105.83h369.07V474.9H143.54z" + /> + </clipPath> + <clipPath id="b" transform="translate(-15.35 -23.13)"> + <path + style={{ + fill: 'none', + }} + d="M143.54 105.83h369.07V474.9H143.54z" + /> + </clipPath> + </defs> + <g + style={{ + clipPath: 'url(#a)', + }} + > + <path + d="M374.21 246.8c-115.33 0-217.85 44.35-217.85 115.33 0 53.23 51.26 97.59 128.15 97.59 44.26 0 91.12-26.72 124.65-26.72 1.17 0 2.34 0 3.49.1 50.1 3 51.26 44.36 115.34 44.36 30 0 64.07-26.61 64.07-88.71 0-79.85-89.7-141.95-217.85-141.95" + transform="translate(-15.35 -23.13)" + style={{ + fill: '#ffd983', + }} + /> + </g> + <g + style={{ + clipPath: 'url(#a)', + }} + > + <path + d="M566.43 336.5h25.63v51.26h-25.63Z" + transform="translate(-15.35 -23.13)" + style={{ + fill: '#ffd983', + }} + /> + </g> + <g + style={{ + clipPath: 'url(#a)', + }} + > + <path + d="M156.36 310.87H182v51.26h-25.64Z" + transform="translate(-15.35 -23.13)" + style={{ + fill: '#ffd983', + }} + /> + </g> + <g + style={{ + clipPath: 'url(#a)', + }} + > + <path + d="M528 417.33c-29.09 0-42.06-9.67-57.09-20.86-13.7-10.21-29.23-21.77-57.13-23.46-1.53-.09-3-.15-4.61-.15-18.37 0-38.17 6.21-59.13 12.77-21.94 6.85-44.61 13.95-65.52 13.95-66.83 0-115.34-37.31-115.34-88.71 0-69.15 105.65-106.46 205-106.46 118.81 0 205 56 205 133.09 0 58.57-30.67 79.83-51.26 79.83" + transform="translate(-15.35 -23.13)" + style={{ + fill: '#a01d22', + }} + /> + </g> + <g + style={{ + clipPath: 'url(#a)', + }} + > + <path + d="M374.21 195.54c-115.33 0-217.85 44.35-217.85 115.33 0 53.23 51.26 97.59 128.15 97.59 44.26 0 91.12-26.72 124.65-26.72 1.17 0 2.34 0 3.49.1 50.1 3 51.26 44.37 115.34 44.37 30 0 64.07-26.62 64.07-88.72 0-79.85-89.7-142-217.85-142m0 17.75c111.37 0 192.22 52.23 192.22 124.2 0 53.1-26.41 71-38.44 71-23.5 0-33.13-7.17-47.71-18s-32.59-24.28-65.41-26.26c-1.88-.12-3.78-.18-5.71-.18-21.12 0-42.15 6.59-64.41 13.54-20.7 6.49-42.1 13.18-60.24 13.18-51 0-102.52-27.43-102.52-79.84 0-63.38 99-97.58 192.22-97.58" + transform="translate(-15.35 -23.13)" + style={{ + fill: '#ffe8b6', + }} + /> + </g> + <g + style={{ + clipPath: 'url(#a)', + }} + > + <path + d="M350.64 214s36.38-.75 62 17c12.81 8.88 4.29 28.83 4.29 28.83l21.34 6.67s-.78-10.72 38.44-17.75c28.85-5.18 58.21 17.75 58.21 17.75s-32.58-44.37-96.65-53.24-87.64.75-87.64.75" + transform="translate(-15.35 -23.13)" + style={{ + fill: '#ffe8b6', + }} + /> + </g> + <g + style={{ + clipPath: 'url(#b)', + }} + > + <path + d="M463.91 284.25c0 19.6-23 35.5-51.26 35.5s-51.26-15.9-51.26-35.5 23-35.48 51.26-35.48 51.26 15.89 51.26 35.48" + transform="translate(-15.35 -23.13)" + style={{ + fill: '#e6e7e7', + }} + /> + </g> + <g + style={{ + clipPath: 'url(#a)', + }} + > + <path + d="M438.28 284.25c0 9.81-11.47 17.75-25.63 17.75S387 294.06 387 284.25s11.49-17.73 25.63-17.73 25.63 7.93 25.63 17.73" + transform="translate(-15.35 -23.13)" + style={{ + fill: '#a8aaad', + }} + /> + </g> + <path + d="M187.91 381.05c-.74-.43-.82-2.44-.87-20.8 0-11.17-.15-20.6-.26-21s-.35-8.6-.52-18.33-.4-18.43-.5-19.35-.33-7.4-.52-14.43-.54-12.91-.78-13.05-.46.13-.48.62c-.17 4.43-1 6.57-2.25 5.56-.81-.67-.78-.75-1.95 5.26-1 5.15-1.51 6.39-2.5 6-.38-.15-.7-.06-.7.2s-.55.61-1.22.78c-.93.23-1.81 1.44-3.7 5.12-2.3 4.48-2.62 4.88-4.52 5.51-2.19.72-5.55 4.43-7.27 8-.5 1.05-1.14 1.89-1.43 1.9-.57 0-4.23 6.56-6.1 10.92-1.47 3.43-1.43 3.39-2.08 1.68s-.65-13.69.08-17.12c.49-2.28.46-2.67-.29-3.22-1.06-.77-.78-1.77 1.79-6.32 2.15-3.8 2.22-4.07 1.17-4.47a1.18 1.18 0 0 1-.74-.95c0-.77 4.26-6.68 5.63-7.82.84-.7.85-.83.12-1.43-1.25-1-1-1.75 2-5.22 1.52-1.79 2.58-3.26 2.36-3.26a2.71 2.71 0 0 1-1.08-.44c-.55-.34-.56-.63 0-1.6.61-1.15.59-1.14-1.16.19a6.53 6.53 0 0 1-2.93 1.37c-1.1 0-2.25.52-7.35 3.23a10.79 10.79 0 0 1-3.47 1.22 8.32 8.32 0 0 0-3.23 1.25 37.06 37.06 0 0 1-3.72 2c-1 .42-2.13 1-2.6 1.25a1.61 1.61 0 0 1-1.8-.09c-.77-.48-1.65-.29-4.91 1.06-4.23 1.74-6.56 1.79-6.57.13 0-2.12-.7-2.17-4.65-.35-2.48 1.15-4.4 1.73-5.42 1.63-1.38-.14-1.6-.36-1.74-1.77-.18-1.83-.1-1.78-1.79-1.13a37.58 37.58 0 0 1-5.43 1c-4.82.6-6.76.15-6.76-1.53 0-1-.17-1-2.74-.72-3.75.47-5.7 0-5.7-1.38 0-1-.24-1.08-4.57-1.17-3.53-.08-4.7-.27-5.15-.88s-2.14-.79-12.17-.79c-6.38 0-11.88-.18-12.23-.39s.46-.91 2.73-1.94c5.38-2.44 16.06-6.13 18.61-6.43 2.16-.26 2.33-.37 2.06-1.43-.35-1.43 1-1.85 5.93-1.9h3.4l-.26-1.6c-.25-1.49-.16-1.61 1.47-1.91a17.78 17.78 0 0 1 7 .56c1 .3 1.08.19.89-1.32-.26-2 .28-2.41 3.47-2.41a23.09 23.09 0 0 1 6.68 1c.55.14.69-.22.57-1.5a2.39 2.39 0 0 1 .52-2.1c1-.66 5.42-.46 7.61.35 2.94 1.09 3 1.08 2.79-.66-.14-1.57-.1-1.61 1.84-1.61a21.23 21.23 0 0 1 5 .94l3 .94v-3l3.5.33a23.49 23.49 0 0 1 5.27 1.06c2.3 1 2.3 1 1.92-.56l-.33-1.31 4.12.35a22 22 0 0 1 5.56 1.06c1.84.9 1.91.89 1.55-.25-.29-.9-.09-.94 3.56-.71a33.64 33.64 0 0 1 5.31.71 9.22 9.22 0 0 0 2.16.46c.63 0 .63-.12.06-.82s-.31-.79 4-.46c2.59.2 5.27.45 6 .56 3.22.54-3.86-1.42-8.74-2.42-3.27-.66-5.85-1.46-6.36-2a3.34 3.34 0 0 0-2.08-.85c-.68 0-3.6-.37-6.48-.82-4.2-.66-5.29-1-5.47-1.7-.31-1.16-2.63-1.94-5.79-2-5 0-9.6-1.93-8.92-3.7.18-.46-.63-.64-3.11-.7a21.38 21.38 0 0 1-8.38-1.64c-.94-.43-1.12-.8-.9-1.88l.27-1.34-3.94-.07c-3.79-.08-8.64-.75-9.59-1.35-.28-.17-.27-.65 0-1.21a5.47 5.47 0 0 0 .49-1.14 20.18 20.18 0 0 0-4.12-.47c-3.79-.23-7.31-1.33-7.27-2.25a4.07 4.07 0 0 1 .74-1.32 3.94 3.94 0 0 0 .73-1.29c0-.19-1.77-.47-3.94-.64-4.07-.31-7.47-1.37-7.47-2.31a5.08 5.08 0 0 1 .81-1.78c.45-.68.73-1.3.62-1.37s-1.86-.53-3.91-1c-4.1-1-7.44-2.5-7.44-3.33A5.1 5.1 0 0 1 72 214c1-1.25 1-1.33.12-1.49-2.2-.43-9.33-3.12-9.66-3.65-.21-.34.28-1.32 1.16-2.32l1.52-1.74-1.43-.53a44.41 44.41 0 0 1-4.06-1.91c-2.16-1.13-2.58-1.56-2.32-2.36s0-1.12-1.38-1.68c-2.44-1-3.2-2.08-2.41-3.34.34-.55.55-1 .46-1.1s-1.73-.88-3.59-1.88a27.63 27.63 0 0 1-4.41-2.59c-.94-.88-.95-1-.14-1.87.47-.52.77-1 .66-1.11s-1.94-1.23-4.05-2.53c-3.21-2-3.8-2.54-3.6-3.43s-.32-1.46-2.59-3.05c-6.07-4.25-11.19-7.56-16-10.33-2.75-1.59-5-3.06-5-3.28 0-.52 8.13 1.46 13.64 3.33 2.45.83 5.24 1.67 6.2 1.86a13 13 0 0 1 3.35 1.26c.89.51 1.61.77 1.62.57 0-1.5.93-1.51 4.48-.07a21.9 21.9 0 0 1 4.72 2.47c1.37 1.29 1.67 1.22 2.27-.5.45-1.29.64-1.41 1.73-1 3.29 1.25 6.83 3.29 7.88 4.54a7.08 7.08 0 0 0 1.38 1.39 7.36 7.36 0 0 0 1.08-1.95c.65-1.43 1.09-1.86 1.69-1.63a23.09 23.09 0 0 1 6.44 4.82l1.52 1.73 1.18-1.73 1.17-1.72 2.44 1.51a22.33 22.33 0 0 1 4.27 3.55l1.82 2 1.08-1.81 1.07-1.82 2.23 1.53a16.68 16.68 0 0 1 3.55 3.45 6.69 6.69 0 0 0 1.7 1.93c.21 0 .8-.66 1.3-1.5l.93-1.52 3 2.53a32.67 32.67 0 0 1 4.12 4c1.36 1.84 1.63 1.83 2.4 0 .33-.82.9-1.49 1.26-1.49.86 0 5.55 4.77 6.51 6.63l.77 1.49.93-1.21a5.08 5.08 0 0 0 .93-1.57c0-1.49 4.07 1.94 6.53 5.57a10.4 10.4 0 0 0 2 2.47c.19 0 .48-.45.67-1 .23-.75 0-1.43-1-2.44l-1.34-1.4 1-.66c1.18-.74 1.4-1.9.35-1.9s-9.52-5.71-9.87-6.62c-.21-.54.17-1.07 1.16-1.66.81-.48 1.47-1 1.47-1.21a6.87 6.87 0 0 0-2.11-1.6 45.21 45.21 0 0 1-5.34-4.22l-3.22-2.94 1.42-1.13 1.42-1.12-1.42-.72c-1.55-.78-6.91-5.8-7.67-7.17-.38-.7-.09-1 1.65-1.93 2-1 2-1.12 1.13-1.63a47.76 47.76 0 0 1-5-4.41c-4.31-4.18-4.27-4-1.24-5.85.66-.4.65-.53 0-1.08a35.28 35.28 0 0 1-5.41-5.23c-2.59-2.93-2.94-4.27-1.24-4.72 2.58-.69 2.61-1.09.29-3.36s-4.87-5.75-4.87-6.65c0-.29.81-.92 1.81-1.4 1.76-.84 1.78-.88.77-1.59-1.43-1-5.72-8-5.41-8.82a2.78 2.78 0 0 1 1.83-1l1.57-.31-1.72-2.15c-1.88-2.32-3.82-5.71-3.82-6.7 0-.34.55-.63 1.22-.63 1.59 0 2-.84 1.06-1.93s-3.76-6.72-3.76-7.5c0-.27.56-.49 1.24-.49 1.41 0 1.52-.35.58-1.75-1.38-2-2.4-5.33-1.78-5.72.44-.27.29-1.09-.57-3.18s-1-2.89-.52-3.21.53-.64.08-1.47c-.53-1-3-12.09-3.71-16.67a27 27 0 0 0-.58-2.85c-.15-.48 0-.87.26-.87.69 0 5.61 9.82 5.23 10.44-.16.26-.06.48.23.48s1.41 1.78 2.51 4a51.67 51.67 0 0 0 4 6.57 14.42 14.42 0 0 1 2.27 4c.31 1.38.34 1.39 1.31.51s1-.86 1.74.09c1 1.29 3.12 5.78 3.12 6.57s.65.77 1.84-.34c1-1 1-1 2 .13 1.32 1.4 3.56 6.21 3.56 7.65s.27 1.4 2.1-.14c1.17-1 1.64-1.13 2.18-.68 1.13.94 4.66 8.4 4.66 9.85 0 2 .45 2.13 2.11.73.82-.68 1.73-1.16 2-1.06.9.3 3.43 5.91 3.69 8.17s.61 2.52 2.57 1.12c1.08-.76 1.5-.85 1.89-.37.89 1.09 3 6.66 3.38 8.7l.31 2 1.69-1c1.24-.74 1.86-.86 2.33-.46 1 .78 3.47 8.34 3.17 9.5-.41 1.57.34 1.56 2.51 0l2-1.48 1.23 2.62a20.56 20.56 0 0 1 2.33 8.19c0 .89.12 1.62.27 1.62a15 15 0 0 0 2.21-1c1.3-.67 2-.82 2.26-.45.83 1.33 1.7 5.94 1.71 9a10.65 10.65 0 0 0 .27 3.35 11.54 11.54 0 0 0 2.1-1.24c1.33-.9 1.9-1.07 2.15-.62.53 1 1.43 7.48 1.43 10.28v2.53l1.89-1.29a10.76 10.76 0 0 1 2.18-1.26c.86 0 1.5 3.36 1.58 8.33l.1 5.51 1.69-1c1.19-.7 1.79-.83 2-.43.35.55 1.17 10.08 1.06 12.26-.07 1.36.45 1.41 2 .19 1-.81 1.29-.85 2.17-.27a3 3 0 0 0 2.13.38l1.1-.29-1.39-1.8c-3-3.89-6.05-9.33-6.05-10.69 0-.45.68-.87 1.75-1.07l1.75-.33L167 180a58.76 58.76 0 0 1-5.31-10.33c-.37-1.25-.28-1.34 1.55-1.66a4.6 4.6 0 0 0 1.94-.56 16.59 16.59 0 0 0-1.2-2.31c-1.47-2.56-4.76-10.5-4.76-11.51 0-.53.57-.74 2-.74 2.23 0 2.28-.07 1.24-1.66s-3.23-8.18-3.23-9.79c0-1.3.09-1.34 2-1.08s2 .24 1.68-.91a18.41 18.41 0 0 0-1-2.45 35.94 35.94 0 0 1-2.21-8.18c0-.72.33-.82 2-.6 2.47.33 2.71.06 1.5-1.63-1-1.42-3.11-9.27-2.68-10a4.58 4.58 0 0 1 2.54-.09c2.57.35 2.52.59.87-3.81a18.43 18.43 0 0 1-.9-5c-.07-3-.15-2.91 3.22-2.25l1.8.35-.91-2.65a21.41 21.41 0 0 1-.94-6.52v-3.84l2.31.34c2.14.32 2.28.28 1.93-.59-1.25-3.08-2-10-1.18-10.85.16-.17 1.15 0 2.17.46a4.83 4.83 0 0 0 2.07.58 6.37 6.37 0 0 0-.38-2.36 19.59 19.59 0 0 1 .3-8.51c.29-.74.46-.75 1.73-.09 2.35 1.22 2.94 1 2.39-.88s1-10.59 1.85-10.47a13.71 13.71 0 0 1 1.54.39c1 .31 1.07.2.84-1.3a18.12 18.12 0 0 1 .43-4.86l.68-3.23 1.64.16c1.19.12 1.57 0 1.4-.49s.88-3.79 2.24-7.61 2.89-8.28 3.41-9.92a49.51 49.51 0 0 1 1.78-4.71 16.54 16.54 0 0 0 1.13-3.72l.31-2 .66 2.1c.57 1.79.55 3.09-.17 8.93-1 7.8-1 15-.12 15 .64 0 .31 7.23-.38 8.35-.29.46 0 .59 1 .48l1.41-.15-.06 3.75a15.91 15.91 0 0 1-.66 4.88c-.82 1.53-.45 1.73 1.61.87l1.74-.73.27 1.57a28.56 28.56 0 0 1-.93 9c-.36.84-.25.9 1.05.56 3.05-.8 2.94-1 2.75 4.7a28.74 28.74 0 0 1-.87 6.89 4.58 4.58 0 0 0-.51 1.88 6.92 6.92 0 0 0 2.33-.29 6 6 0 0 1 2.61-.2c.73.45-.1 5.12-1.43 8l-1.21 2.67 2.48-.33 2.48-.33v1.8a28.85 28.85 0 0 1-2.52 9.72c-1.12 2.2-1.15 2.43-.33 2.18.34-.1 1.57-.32 2.73-.49 2-.27 2.11-.22 2.11.94 0 1.52-1.71 5.93-3 7.75a7.07 7.07 0 0 0-1 1.64c0 .16 1 .15 2.23 0 2.24-.3 2.24-.3 2.24 1.23 0 1.76-1.79 6.9-2.8 8.07s-.79 1.59 1.51 1.32l2.19-.24-.65 2c-1.28 4-2.23 6.35-3.22 7.73-1.56 2.2-1.33 2.66 1.23 2.38 1.38-.16 2.23-.06 2.23.26 0 1.1-3.19 8.62-4.3 10.12a8.88 8.88 0 0 0-1.16 1.86c0 .15.9.28 2 .28 1.44 0 2 .21 2 .75 0 1.13-3.86 10.23-5 11.82s-1.1 1.81 1 2c1 .07 1.56.38 1.56.81 0 .83-4.28 9.37-5.27 10.5s-.84 1.45 1.06 1.45c1.41 0 1.73.18 1.73 1a6.71 6.71 0 0 1-.73 2.43c-.69 1.34-.67 1.45.48 2.2a8.74 8.74 0 0 0 1.35.8 36.68 36.68 0 0 0 .2-4.59c.06-4.43.84-8.81 1.57-8.81a9.1 9.1 0 0 1 2 1l1.61 1v-3.3c0-4.18.89-9.38 1.73-10.07.47-.39 1-.21 2.13.72a7.56 7.56 0 0 0 1.8 1.26c.17 0 .27-.95.23-2.11-.1-3 1.09-9.76 1.85-10.52.36-.36.49-.83.3-1s.32.06 1.14.61 1.72 1.13 2 1.28.53-.77.57-2.53c.07-2.84.79-6.19 1.61-7.46.36-.57.71-.49 2 .4 1.75 1.25 2.49 1.07 2.18-.57-.23-1.25 1.94-8.57 3-10 .63-.87.73-.86 2.49.18l1.84 1.09v-1.74a22.42 22.42 0 0 1 1-5c1.07-3.48 1.77-4 3.67-2.52 1.49 1.13 2.33.92 2-.48-.48-1.91 1.11-6.67 3.59-10.83.12-.2 1 .19 1.86.87a8.37 8.37 0 0 0 1.95 1.24c.17 0 .32-.89.32-2 0-2 2-6.94 3.22-7.94.47-.4 1-.21 2.13.72 1.86 1.56 2.09 1.56 2.09 0a14.83 14.83 0 0 1 1.55-4.34c2.16-4.31 2.31-4.46 3.58-3.57s1.82.93 1.83-.14 1.81-5.07 2.82-6.19c.76-.84.83-.83 1.49.12.95 1.36 1.55 1.24 1.93-.38s3.09-6.07 3.7-6.07a1 1 0 0 1 .72.49c.48.78 1.14.59 1.46-.41.54-1.69 4.3-6.28 5-6.14.39.08.71-.06.71-.3 0-.4 2.61-3.47 5.95-7 .68-.72 2.33-2.25 3.66-3.4 2.24-2 3.29-2.28 3.29-1.06 0 .27-.26.33-.57.14s-.44-.14-.22.22-.29 1.52-1.09 2.68a50.25 50.25 0 0 0-6.33 12 30.56 30.56 0 0 1-1.79 4.44c-.67.75-.48 1.76.32 1.76.41 0 .75.23.75.51 0 .77-2.88 6.32-3.47 6.68-.75.46-.11 1.24 1 1.24a.88.88 0 0 1 1 .77c0 1-1.77 4.5-3.31 6.56-1.41 1.88-1.17 2.6.86 2.6.8 0 1.46.19 1.46.43 0 .75-4.84 7.87-5.5 8.09-1 .34 0 1.4 1.28 1.4a2.09 2.09 0 0 1 1.42.39c.44.71-1.47 4.29-3.83 7.18a27 27 0 0 0-2.3 3.06 3.91 3.91 0 0 0 1.73.56c1 .18 1.74.57 1.74.86 0 1-3.88 7-4.92 7.67-1.54.93-1.26 1.49.95 1.86 1.42.24 2 .58 2 1.19 0 .95-3.6 5.42-6.3 7.81-1.69 1.5-1.86 2.66-.4 2.66 1 0 3.22 1.07 3.22 1.54s-2.84 3.39-6.2 6.33c-2.26 2-2.4 2.22-1.57 2.73 2.77 1.7 2.8 1.52-.91 5a26 26 0 0 1-4.34 3.53c-1.23.4-1.08.86.62 1.87.82.49 1.49 1.07 1.49 1.31 0 .74-5 4.8-8 6.53a14.63 14.63 0 0 0-2.92 1.95c0 .16.54.59 1.23 1 1.39.75 1.45.92.6 2-.65.84-6 4.49-8.45 5.75l-1.5.77 1.32.8a2.63 2.63 0 0 1 1.33 1.65c0 1.47.85 1.45 1.47 0s6.47-8 7.26-8c.28 0 .91.84 1.4 1.86l.88 1.86.9-1.73a29.5 29.5 0 0 1 4-4.9c3.27-3.35 3.15-3.33 4.69-.77.47.77.65.67 1.57-.9s5.69-5.84 6.56-5.84c.21 0 .64.9 1 2 .54 1.79 1.55 2.75 1.55 1.47 0-.72 4.9-5.38 6.56-6.24 1.35-.7 1.4-.67 2.22 1a4.41 4.41 0 0 0 1.14 1.75 7.33 7.33 0 0 0 1.77-1.53c2.79-3 8.16-5.88 8.16-4.45a8 8 0 0 0 .49 1.77l.5 1.31 1.19-1.4a20 20 0 0 1 4-3.11c2.7-1.62 2.87-1.66 3.5-.8a7.59 7.59 0 0 1 .83 1.39c.08.26.87-.19 1.74-1a18.39 18.39 0 0 1 4.94-2.74c3.08-1.15 3.42-1.19 4-.45s.73.74 2 0a12.63 12.63 0 0 1 3.84-1.13c1.37-.19 5.16-.79 8.42-1.34 6.79-1.14 12.78-.94 11.46.38a2.49 2.49 0 0 1-1.44.7c-1.18 0-10.75 3.45-13.9 5a35.81 35.81 0 0 0-5.36 3.4 20.1 20.1 0 0 1-3 2.14c-.45.15-.43.42.09 1s.56 1-.22 1.83a19.6 19.6 0 0 1-3.66 2.56c-1.52.85-2.85 1.62-3 1.71s.17.57.62 1.07a3.58 3.58 0 0 1 .83 1.25c0 .45-6 4.8-6.68 4.81a3.11 3.11 0 0 0-1.25.46c-.62.4-.55.66.42 1.7l1.16 1.22-2.41 1.75a41.62 41.62 0 0 1-5.23 3.14 9.54 9.54 0 0 0-2.86 1.79 4.16 4.16 0 0 0 1.33 1.55c1.19 1 1.26 1.26.65 2-.82 1-5.36 3.32-8.2 4.22s-2.88.9-1.25 2.88c1.18 1.43 1.36 1.93.87 2.4-.78.73-7.55 3.16-8.83 3.16s-1.22.41.08 1.79l1 1.11-1.92 1a45.82 45.82 0 0 1-6.13 2.11c-2.32.65-4.31 1.24-4.44 1.34s.15.79.61 1.56c.79 1.35.78 1.42-.27 2a37.37 37.37 0 0 1-9.66 2.55c-1.81 0-1.95.29-.87 1.84.7 1 .65 1.13-.63 2-1.5 1.07-6 2.07-9.32 2.07-1.91 0-2.05.09-1.84 1.17.29 1.54-1.35 2.13-7.19 2.6-3.63.29-4.5.53-5.33 1.47-1.13 1.27-3.5 2-8.19 2.43-5 .49-6 .77-5.76 1.54.38 1 .06 1.07-5.59 1.92a29.33 29.33 0 0 0-5.91 1.3c-.74.48-.73.51.12.23a27.18 27.18 0 0 1 3.1-.6c1.77-.24 2.14-.15 2.14.52s.19.71.87.32c1.66-1 7-2.35 7.59-2a1 1 0 0 1 .31 1.17c-.37 1-.37 1 1.73.09a22.12 22.12 0 0 1 4.84-1.06l3.15-.36-.23 1.56a6.27 6.27 0 0 0-.13 1.56c.06 0 1.05-.34 2.21-.74a12.44 12.44 0 0 1 6.87-.42c1 .27 1.14.53.89 2s-.19 1.69.82 1.4c3.65-1 6.44-1.31 8.09-.76s1.71.72 1.45 1.91l-.3 1.34 3.23-.21a25.9 25.9 0 0 1 5 .14c1.66.31 1.78.45 1.59 1.79l-.21 1.46h3.5c4.29 0 5 .36 4.54 2.32-.31 1.41-.26 1.46 1.2 1.25a17.31 17.31 0 0 1 5 .67c3.12.81 3.42 1 3.18 2s0 1.15 1.09 1.43a35 35 0 0 1 4.38 1.73c1.65.77 5.8 2.34 9.21 3.5s6.87 2.45 7.68 2.86l1.48.76-1.23.46c-.68.25-5.59.47-10.91.48-15.94 0-19.53.21-19.75.87-.46 1.38-4.92 1.52-9.6.29-1.13-.3-1.35-.18-1.49.81-.24 1.69-3.2 1.78-8 .24-4.13-1.31-3.82-1.39-4.58 1.11-.3 1-3.08.44-6.9-1.36-3.25-1.52-3.71-1.51-3.71 0s-3 1.15-6.68-.61c-2.49-1.18-3.59-1.44-4.93-1.19-1.73.33-2.73 0-8.86-3.27a14.06 14.06 0 0 0-2.72-1.14c-1.59-.31-3.32-1.21-5.83-3-3.74-2.69-4.29-2.14-1.1 1.09a15.26 15.26 0 0 1 2.83 3.41c0 .3-.48.68-1.07.83-1 .27-1 .39.87 2.42 2.58 2.82 5.32 7.17 4.93 7.81a1.32 1.32 0 0 1-1 .52c-.8 0-1 .78-.24 1.23s4 6.74 4 7.51a1.14 1.14 0 0 1-.79.88c-.71.28-.72.44-.07 1.42 1 1.49 2.33 6.24 1.84 6.43-.21.08.26 2.6 1 5.6 1.45 5.49 1.82 8.68 1 8.68-.24 0-2.66-3-5.38-6.57-6-8-7.67-9.8-9-9.8-1.17 0-2.83-2.37-5.15-7.31l-1.45-3.11-1.3 1c-1.23 1-1.33 1-1.94.15a3.25 3.25 0 0 1-.63-1.31c0-1-2.68-7.12-3.42-7.82a2.1 2.1 0 0 0-1.62-.59c-.81.16-1.34-1-2.13-4.5-.27-1.22-.39-1.3-1-.66-.9.9-1 13.76-.24 34.29.27 7.37.71 20.09 1 28.28.49 14.67.66 19 1.28 31.75.3 6.12.26 6.52-.76 7.81s-2.17 1.67-3.57.85Zm-6.85-130.43a26.92 26.92 0 0 0-3.36-4.54l-4.81-5.26a67.91 67.91 0 0 0-7.44-6.42c-5.06-3.79-5.9-4-4.75-.93.5 1.33.56 1.35 1.22.46.38-.52.77-.83.88-.7s1.23 1.7 2.48 3.47c2.55 3.6 8.4 9.48 13.26 13.34 1.76 1.4 3.27 2.54 3.36 2.54a8 8 0 0 0-.84-2Zm4.44-1.62c0-7.67-3-22-5.28-25.58-.49-.76-.57-1.35-.25-1.74.48-.59-1.61-4.48-4.44-8.27a10.73 10.73 0 0 1-1.63-3.1c-.28-1.22-.18-1.37 1-1.37.72 0 1.22-.19 1.13-.41-.57-1.44-5.64-8.41-6-8.21s-.4 2.27-.4 4.74c0 3.39.15 4.42.62 4.23 2.33-1 2.18-1.25 2.58 5.21.2 3.36.33 7 .3 8-.06 1.74 0 1.85.91 1.37 1.41-.76 1.55-.68 1.55.84a103.48 103.48 0 0 0 3 13.91c1.62 4.31 6.09 14 6.46 14s.42-1.62.42-3.6Zm22.92 3.26a1 1 0 0 0-.95 0c-.16.17.13.28.65.26s.7-.14.3-.3ZM190.93 247a80 80 0 0 0 3.73-9.8c.75-2.59 1.53-5 1.75-5.48a43.4 43.4 0 0 0 .75-6.86l.37-6.09 1.18.29 1.18.3v-5c0-5.7-.43-6.17-2.91-3.15-1.54 1.85-1.6 2.64-.23 2.64.54 0 .66.27.41.87a19.3 19.3 0 0 0-.61 1.86 5.87 5.87 0 0 1-.85 1.74c-1.15 1.48-3.9 9.62-3.31 9.82.31.1.47.35.34.55-.28.46-3.6 15.51-3.95 17.91-.14 1-.38 2.57-.54 3.59s-.16 1.86 0 1.86a31.26 31.26 0 0 0 2.68-5.08Zm7.47-.62c4.65-4.07 7.9-7.9 7.27-8.53s-8.75 6.66-8.75 7.84a.47.47 0 0 1-.48.44 10.21 10.21 0 0 0-2.48 2.71c-2 2.71-2.59 3.88-1.33 2.66.37-.36 3-2.67 5.77-5.12Zm-45.31-22.2c-.14-.34-.25-.07-.25.62s.11 1 .25.62a1.88 1.88 0 0 0 0-1.24Zm-9.75-8.31a.5.5 0 1 0-1 0 .5.5 0 0 0 1 0Zm-9.43-9.37c0-.43-2-1.67-2.29-1.4s0 .81.37 1.52c.46 1 .74 1.18 1.26.75a1.75 1.75 0 0 0 .66-.87Zm104.18.23a5.64 5.64 0 0 1 .33-1c.26-.7.12-.78-.87-.47-1.51.46-1.61.56-1.21 1.2s1.75.77 1.75.32Zm-35.46-3.12c-.32-1.65-.42-1.75-.77-.81a3.33 3.33 0 0 1-1 1.43c-.41.25-.23.57.59 1 1.55.82 1.61.66 1.18-1.62Zm.24-5.38c0-1.21-.14-2.21-.31-2.21-.51 0-3.15 3.19-3.15 3.81s2.3 1.14 3.09.77a4.68 4.68 0 0 0 .37-2.37Z" + transform="translate(-15.35 -23.13)" + style={{ + fill: '#33502e', + }} + /> + <path + d="M154 317.38c0-.43.22-.78.5-.78a.48.48 0 0 1 .49.47 1.05 1.05 0 0 1-.49.77c-.24.16-.5-.04-.5-.46Zm65.27-4.1c-.27-.71-.22-.77.28-.28.33.33.49.73.33.89s-.38-.13-.57-.61ZM157 311.42a2.1 2.1 0 0 1 .49-1.52c.28-.16.5 0 .5.35s.54 0 1.19-.84c1.28-1.59 1.31-2.29.06-1.25-.52.43-.75.45-.75.07a.53.53 0 0 1 .49-.56c.27 0 .5-.33.5-.74s.22-.75.49-.75.5-.55.5-1.24-.28-1.24-.77-1.24-.64-.22-.47-.5a.54.54 0 0 1 .77-.2c.23.14.55-.3.72-1s.67-1.24 1.11-1.24c.62 0 .73-.33.49-1.54-.29-1.45-.23-1.52.93-1.24.71.19 1.24.1 1.24-.19s.53-.51 1.17-.51a2.67 2.67 0 0 0 1.81-.74c.42-.51.44-.74.05-.74s-.55-.56-.55-1.25.28-1.24.74-1.24a.75.75 0 0 0 .75-.74c0-.41-.19-.74-.41-.74s-.28-.94-.12-2.09c.25-1.88.44-2.12 1.92-2.42 2.09-.43 2.54-.9 1.45-1.51-.7-.39-.76-.68-.32-1.5s.38-1.18 0-1.54a2.68 2.68 0 0 1-.22-2c.23-1.13.13-1.43-.4-1.25a2 2 0 0 0-.85 1.56c-.09.72-.33 1.32-.55 1.32s-.8 1-1.31 2.23a6.1 6.1 0 0 1-1.24 2.24 7.57 7.57 0 0 0-1.51 2.35 32.75 32.75 0 0 1-2.48 4.09 15.93 15.93 0 0 0-1.57 2.48c-.19.5-.3.38-.32-.37 0-1.06-1-1.65-1-.62a.5.5 0 0 1-.5.5c-.92 0-.51-.82.74-1.48a3.51 3.51 0 0 0 1.52-1.58 1.25 1.25 0 0 1 1-.91c.39 0 .71-.19.71-.43s-.51-.4-1.12-.37-1.1.28-1.09.55c0 .6-3.15 3-4.61 3.44-.57.17-1.18.09-1.36-.19s.05-.52.51-.52a4.31 4.31 0 0 0 2-1c1.22-.95 1.22-1 .22-1.59s-.94-.67.74-1.49a4.44 4.44 0 0 1 2.59-.55c.7.25.67.11-.15-.84l-1-1.15 1.07.56a2.57 2.57 0 0 0 2.23.11c.64-.26.94-.49.66-.51s.13-.57.9-1.19a4.59 4.59 0 0 0 1.53-2 2 2 0 0 1 .82-1.29c.38-.24.53-.59.34-.79s-.77 0-1.27.47a3.13 3.13 0 0 1-1.36.83 6 6 0 0 0-1.65.62c-1 .53-1.64.54-3.22.05-1.09-.34-1.59-.63-1.12-.66s.75-.32.54-.88-.08-.76 1-.49 1.24.18 1-.66a2.3 2.3 0 0 1 1.81-2.49.53.53 0 0 0 .53-.5c0-.27.44-.49 1-.49s1-.23 1-.5c0-1.53-4.49 1.24-5 3.12l-.33 1.1-.05-1.1a4.41 4.41 0 0 1 1.57-2.61c1.49-1.43 1.54-1.59.74-2.2-1.19-.89-1.1-1.42.5-2.82 3.57-3.15 4.09-3.41 6.29-3.11 1.69.24 2.17.13 2.43-.54.18-.46.55-.69.82-.52s.5 0 .5-.44a1.73 1.73 0 0 1 .87-1.26c1.59-.92.11-.55-1.92.49a5.38 5.38 0 0 1-3.33.73 5.35 5.35 0 0 0-3.29.72 10.41 10.41 0 0 1-2.5 1 8.29 8.29 0 0 0-2.48 2 6.06 6.06 0 0 1-3.05 2c-1.46 0-3.21.78-7 3.11a12.12 12.12 0 0 1-4 1.83c-1.84-.2-2.52.09-6.61 2.74s-4.22 2.67-4.65 1.7c-.56-1.2-1.69-1-5.5.81a21.54 21.54 0 0 1-4.09 1.52c-1.5.3-1.64.21-1.92-1.23s-1.42-2.05-2.45-1.13a38 38 0 0 1-7.89 3c-.43 0-.85-.71-1-1.74-.36-1.87-.61-2-3.13-1.25-2.7.77-8.7 1.49-9.88 1.18a1.34 1.34 0 0 1-1.1-1.39c0-1.57-.27-1.69-2.79-1.15-3.23.69-5.47.17-5.77-1.35-.12-.67-.09-1.29.07-1.4s0-.05-.44.11a25.83 25.83 0 0 1-5 .16c-3.09-.1-4.31-.33-4.61-.87s-1.27-.73-3.36-.73c-1.63 0-2.95-.17-2.95-.38 0-.42-9.91-.34-11.87.1-1.09.25-1.15.21-.39-.25a16.49 16.49 0 0 1 4.59-.82c2-.16 3.7-.47 3.7-.7s.67-.28 1.49-.11 1.48.1 1.48-.28.79-.48 2.61-.27 2.41.17 1.86-.23-.34-.46.92-.21c.93.17 1.8.1 2-.18.41-.64 4-1.32 4-.76 0 .23-.94.84-2.09 1.35s-1.83 1-1.51 1.12a10.18 10.18 0 0 0 3.6-1.32c3.48-1.77 3.48-1.77 3.48-.79a.74.74 0 0 0 .74.75.76.76 0 0 0 .75-.75c0-.62.48-.69 2.72-.43 2 .24 2.73.16 2.73-.27s.37-.48.95-.3 1.1.07 1.27-.22a1.35 1.35 0 0 1 1-.51c.52 0 .45.26-.28 1a3.4 3.4 0 0 1-1.49 1 .51.51 0 0 0-.5.5c0 .27-.44.49-1 .49s-1 .23-1 .5-.17.47-.37.44c-.8-.11-2.61.7-2.61 1.16 0 .27.36.36.79.19a20.16 20.16 0 0 1 2-.6c.66-.17 1.08-.47 1-.69s.34-.54 1-.71a9.51 9.51 0 0 0 2.6-1.25c1.58-1.1 3.88-1.69 4.25-1.07.29.45 2.64-.23 4.67-1.37.64-.35 1.26-.54 1.38-.43.34.35-3.93 4.33-4.64 4.33a.56.56 0 0 0-.62.5c0 .27.33.49.74.49s.74-.2.74-.44.33-.56.72-.72.63 0 .44.45c-.42 1.11 1.13.35 3.23-1.59A6.41 6.41 0 0 1 111 273c.48 0 .87-.23.87-.5 0-1 1-.44 1 .62 0 .61.15.84.29.49a1.38 1.38 0 0 1 1.2-.62c.52 0 1-.24 1-.55s.23-.37.75.06.74.44.74.06.61-.57 1.37-.59c1.2 0 1.25-.09.37-.46s-.45-.44 1.26-.47a5.68 5.68 0 0 0 2.8-.57 2.32 2.32 0 0 1 1.7-.37c1.11.16 1.13.2.26.91-.5.41-1.4 1.19-2 1.74-1 .92-2.19 1.73-6.86 4.75a5.81 5.81 0 0 0-1.86 1.57c0 .21-.33.37-.72.37a1.37 1.37 0 0 0-1.07.58c-.22.35-.13.44.22.22s.57-.14.57.13a.74.74 0 0 1-.48.65c-.27.09-.35.67-.19 1.3l.3 1.13.67-1.29a4 4 0 0 0 .56-1.7c-.11-.39 4.75-4 5.37-4a2.46 2.46 0 0 0 1-.62 24.89 24.89 0 0 1 3.41-2.62l1.42-.93-.37 1.22c-.34 1.12-.3 1.15.47.42a4.38 4.38 0 0 0 1.22-2.93 10 10 0 0 1 3-.26c2.12 0 3.14-.25 3.59-.82.64-.82 2.86-1.72 2.86-1.16s-3.08 3.28-9.08 8.19c-2.17 1.78-3.87 3.59-3.62 3.84s.65 0 1.11-.41a2.57 2.57 0 0 1 1.82-.53c.71.12.9 0 .69-.59s-.07-.68.37-.51 1-.25 1.47-.88.8-.86.8-.53a.72.72 0 0 0 .79.61c.57 0 .73-.31.54-1s-.08-.95.69-.71 1 0 1.27-1.23.48-1.48 1.36-1.22.94.2.43-.33c-1-1-.75-1.62.87-2.54s2-.59.89.5c-.43.42-.44.59 0 .59s.69-.38.85-.87a4.56 4.56 0 0 1 4.17-3.39l2.07-.17-.76 1.45a12.64 12.64 0 0 1-2 2.69 4.21 4.21 0 0 0-1.23 1.88c0 .36.52 0 1.16-.8.77-1 1.37-1.31 1.81-1s.53.22.29-.19.2-1.34 1-2.35c1.08-1.4 1.58-1.69 2.53-1.45s1.18.08 1.18-.41.35-.62 1-.45 1 0 1-.58.28-.75.62-.63a2.65 2.65 0 0 0 1.81-.71c.66-.51 1.29-.84 1.4-.72.29.3-.94 2.37-2 3.35-.6.56-.71.94-.34 1.17s3-2 3-2.85c0-.2.51-.31 1.12-.25.82.09 1.08-.11 1-.75s.23-.87 1.86-.87c2.08 0 2.36.39 1.49 2-.35.66-.35 1 0 1s.52-.17.52-.38c0-.61 2.86-3.59 3.44-3.59.29 0 .53-.38.53-.87 0-.74.08-.75.49-.12.27.41.48.52.49.23 0-.81 4.36-2.27 4.64-1.56.14.37.52.18 1-.5s1.81-1.3 3.66-1.7a48.2 48.2 0 0 0 5.25-1.31c1.44-.43 2.14-.42 2.85 0a1.48 1.48 0 0 0 1.88 0c.8-.5 1-.44 1.2.37.26 1 1.34 1.37 1.34.49 0-.27.31-.36.69-.22a.85.85 0 0 0 1-.21c.42-.68 7.46-.3 10.81.59 1.56.41 3.86.9 5.13 1.09s2.41.65 2.56 1a1.28 1.28 0 0 0 1.14.68c1 0 2.15 1.16 1.66 1.65-.18.19.17.33.78.33s1 .22.77.53 0 .46.42.38c.89-.19 3.42 2.57 3.11 3.39s1.53 2.33 2.46 2c.43-.17.64-.55.46-.84a.9.9 0 0 0-1.06-.25 1.63 1.63 0 0 1-1.29-.05c-.34-.21-.21-.45.36-.65.77-.27.51-.74-1.77-3.2-3-3.22-3.53-4.13-2-3.31a6.38 6.38 0 0 0 2.41.58c.77 0 1.12.16.78.3-1.21.48-.6 1.1 1.24 1.25 1.25.1 1.86.39 1.86.89 0 .91 1.56 2.16 2.2 1.76a.64.64 0 0 1 .82.3c.22.36.14.44-.22.23s-.57 0-.57.72c0 .9.2 1 1.29.75s1.26-.24.95 1.15c-.25 1.18-.11 1.59.7 2 .56.3 1 .71 1 .9-.16 1 .12 1.55.69 1.33.37-.14.87.38 1.2 1.25.42 1.13.42 1.52 0 1.55s0 .2.62.39a2.22 2.22 0 0 1 1.41.94c.33 1-.51.7-3.37-1.17-3.31-2.17-6.64-3.68-8.12-3.68-1.17 0-4.27-2.43-4.27-3.35 0-.29-.44-.41-1-.27s-1 .06-1-.19-.89-.48-2-.52-2 .11-2 .35a17.42 17.42 0 0 0 3 3.27 16.29 16.29 0 0 1 3 3.35c0 .28-.14.35-.32.18s-.68.07-1.11.54a3.12 3.12 0 0 1-3.9.51c-.61-.31-1.28-.38-1.49-.17s-.09.51.25.62a1.29 1.29 0 0 1 .62 1.18.87.87 0 0 0 .75.95c.4 0 .74.2.74.45s.57.39 1.27.31c1.11-.12 1.24 0 1 1s-.13 1.09.83.85a2.94 2.94 0 0 0 1.48-.82c.2-.3.37.19.37 1.07s.23 1.61.51 1.61c.58 0 .44 1.1-.19 1.56-.25.17.54.49 1.73.69 1.76.3 2 .45 1.18.76-1.83.73-3.4.48-5.31-.85-1.05-.72-2-1.17-2.21-1-.46.46.83 1.82 1.76 1.83.71 0 .7.1-.06.68-.95.72-1.18 1.79-.38 1.79.28 0 .5.38.5.84 0 .73.09.71.71-.13s.73-.82 1.05 1.12.41 2 1.55 1.61c1.55-.59 2.11-.58 1.16 0-.61.38-.54.65.45 1.71.65.69 1.37 1.13 1.6 1s.43 0 .43.26a.5.5 0 0 0 .48.52c.26 0 .35-.38.19-.86-.23-.73-.19-.75.26-.13a2.81 2.81 0 0 1 .54 1.12c0 .75-2.37.34-3.6-.62-1.49-1.17-3.3-1.41-2.39-.3s2.91 5.26 2.5 5.26c-.22 0-.71-.61-1.11-1.36-2.77-5.33-3.6-6.71-4.17-6.93a1 1 0 0 1-.64-.86 8.71 8.71 0 0 0-1.49-2.68 10 10 0 0 1-1.49-2.52 1.06 1.06 0 0 0-.5-.78c-1-.6-.48 1.62.75 3.43a6.86 6.86 0 0 1 1.22 2.67c0 .51-.48.09-1.19-1.07-1.11-1.81-1.21-1.86-1.72-.9-.67 1.25-.7 1.73-.07 1.34s1.29 1 1.71 3.33c.26 1.42.18 1.86-.35 1.86-.37 0-.95-.83-1.26-1.86-.94-3-1.27-3.36-2.62-2.42s-2.14 1.06-1.63.24c.21-.34.14-.44-.17-.25a.55.55 0 0 1-.81-.29c-.15-.34-.16.05 0 .86.24 1.47.23 1.47-.32.25-.31-.68-.9-2.13-1.32-3.22s-1.06-2.69-1.43-3.55c-.94-2.15-.91-3 .08-2.15.41.35.89.49 1.07.31s.06-.32-.26-.32-.58-.47-.58-1.05c0-.89-.11-.95-.75-.42s-.7.46-.22-.8a4.49 4.49 0 0 1 1.11-1.78c.32-.2.41-.53.19-.74s-.56-.1-.78.26a1.12 1.12 0 0 1-1.09.5c-1.14-.22-3 .59-2.6 1.17.2.33.1.39-.28.16s-.48-.9-.28-1.81.12-1.44-.22-1.44a.51.51 0 0 1-.53-.48c0-.26.3-.36.66-.23a1.22 1.22 0 0 0 1.25-.45c.47-.56.34-.86-.63-1.49A4.32 4.32 0 0 1 191 273c-.37-1.17-1.24-1.36-1.24-.26 0 .41-.22.74-.5.74s-.49-.33-.49-.74a.73.73 0 0 0-.71-.75c-.38 0-.85-.66-1-1.48-.4-1.81-1.14-2.37-1.55-1.15-.16.49-.72 1.84-1.23 3a12.43 12.43 0 0 0-.94 4.25 6.44 6.44 0 0 1-.66 3c-.62.85-.72.83-1.73-.46-.74-.94-1.14-1.15-1.28-.68s-.46 1.43-.75 2.36a10.85 10.85 0 0 0-.53 2.73c0 .95.06 1 .53.17a1.6 1.6 0 0 0 .22-1.33.56.56 0 0 1 .22-.77c.68-.42.65.11-.32 5-.67 3.43-1 4.18-1.73 4.18a2.11 2.11 0 0 1-1.53-1c-.88-1.41-1.37-1.23-1.37.5 0 .82-.19 1.49-.42 1.49s-.71.89-1.07 2c-.57 1.74-2 3.69-2 2.75 0-.18-.25-.18-.55 0s-.37.46-.07.65c.72.47-1.17 4.53-2.11 4.53a1.22 1.22 0 0 1-1.06-.75c-.42-1.09-.92-.91-1.7.62a21.58 21.58 0 0 1-2.22 3.2 41.09 41.09 0 0 0-3.23 4.83c-1.8 3.13-2.95 3.84-2.95 1.8Zm5.21-7.72a1.15 1.15 0 0 0-.9-.48c-.36 0-.29.19.15.48.97.62 1.14.62.77 0Zm1.93-.78c.63-1.64.28-2.43-.45-1-.86 1.66-.89 1.8-.3 1.8.26-.02.61-.37.77-.8Zm8.38-10.16c.22-.83.14-.91-.43-.44-.38.32-.57.78-.42 1 .4.66.56.55.85-.58Zm1.31-4.34c0-.64-.18-1-.46-.84a3.33 3.33 0 0 0-.46 2.14c0 1.48.11 1.63.46.84a7.23 7.23 0 0 0 .48-2.14ZM200 289c-.16-.16-.28.13-.26.66s.14.69.3.3a1 1 0 0 0 0-1Zm-1.62-2.26c-.16-1-.49-1.84-.73-1.84-.57 0-.54.52.12 2.43s.97 1.67.59-.64Zm-21.87-1.1c.06-.27-.43-.57-1.1-.66-1.07-.15-1.13-.08-.54.64s1.41.81 1.62-.03Zm23-.61c-.16-.16-.28.13-.25.66s.13.69.29.3a1 1 0 0 0 0-1Zm-21.83-2c-.16-.16-.28.13-.25.66s.14.69.29.29a1 1 0 0 0 0-.95Zm-1.29.36a.48.48 0 0 0-.46-.49 1.07 1.07 0 0 0-.78.49c-.16.28 0 .5.47.5s.74-.25.74-.53Zm31.35.16a1 1 0 0 0-1 0c-.16.16.13.28.65.26s.7-.14.3-.3Zm-82.34-1.58a1.11 1.11 0 0 1-.3-1.35c.31-.89.29-.89-.47 0-1 1.32-1 1.52.28 1.62.7.06.87-.05.46-.3Zm77.47-.82c.17-.43-.32-1.61-1.09-2.68-1.18-1.62-1.47-1.8-1.89-1.13s-.25 1 .41 1.68a3 3 0 0 0 1.45.9c.31 0 .44.44.3 1-.3 1.16.39 1.36.82.24Zm-71.62-.52c0-.11-.22-.21-.5-.21a.5.5 0 0 0-.49.52c0 .29.22.39.49.22s.47-.43.47-.56Zm-31.75-.21a.5.5 0 1 0-.5.49.5.5 0 0 0 .47-.52Zm100.71-.5c0-.27-.39-.49-.87-.49-.74 0-.76.08-.12.49.9.58.96.58.96-.03Zm-71.06-.9c.12-.36-.11-.47-.59-.29s-.78.53-.78.79c-.03.62 1.09.21 1.34-.52Zm46.25.4a.5.5 0 1 0-.49.5.49.49 0 0 0 .46-.53Zm-72.4-1.64c-.94-.94-1.18-1-2.31-.38-1.79 1-1.57 1.24 1.06 1.34l2.3.09Zm68.87.34c0-.42.16-.67.37-.54s.4-.42.43-1.23l.05-1.45-.92 1.52c-1 1.69-1.17 2.48-.43 2.48.28 0 .5-.35.5-.78Zm-33.64-2.82c.36-.13.47-.43.25-.65-.38-.42-3.32 2.33-3.32 3.1 0 .22.54-.2 1.21-.92a7.2 7.2 0 0 1 1.89-1.53Zm-53.09.28a1 1 0 0 0-1 0c-.16.17.13.29.65.26s.7-.14.3-.3Zm114.86-.95c-.18-.46-.44-.72-.57-.58s-.11.62.07 1.08.43.72.57.59.08-.63-.07-1.09Zm4.66.58c0-.95-.71-1.35-1.7-1-.62.24-.67.47-.24 1 .73.92 1.92.9 1.92 0Zm20.84-.06c0-.42-.22-.64-.5-.47a1.07 1.07 0 0 0-.49.77.48.48 0 0 0 .49.47c.26 0 .48-.35.48-.77Zm-125.3-.48c.73-.55.78-.73.22-.73a3.49 3.49 0 0 0-1.7.73c-.73.55-.79.73-.22.73a3.62 3.62 0 0 0 1.68-.73Zm77.18 0c0-.41-.22-.74-.48-.74s-.36.33-.2.74.37.74.48.74.2-.33.2-.74Zm2.77-.74a.73.73 0 0 0 .7-.75c0-.4-.22-.74-.48-.74-.5 0-1.53 1.63-1.46 2.33 0 .22.14.12.28-.22a1.11 1.11 0 0 1 .94-.61Zm-4.71-.15c-.95-.94-.63-1.87.57-1.67.61.1.84.09.5 0s-.62-.69-.62-1.28c0-1-.05-1-1.24.26-1.65 1.78-1.63 3.09.05 3.19.92.03 1.12-.1.72-.49Zm31.21-.07a1.29 1.29 0 0 0-.46-1c-.59-.37-1.19.56-.78 1.22s1.22.61 1.22-.21Zm-59-.56c0-.42-.22-.63-.5-.46a1.05 1.05 0 0 0-.49.77.48.48 0 0 0 .49.47c.22.01.44-.34.44-.77Zm-3.22-.22a1.09 1.09 0 0 0-.81-.49c-.27 0-.35.22-.19.49a1.09 1.09 0 0 0 .81.5c.21.01.3-.21.13-.49Zm-15.46-.76c-.16-.42-.4-.66-.53-.53s-.09.48.09.77c.49.79.78.63.44-.24Zm37.33-.59c-.29-.3-1.78.47-1.78.92 0 .24.43.17 1-.16s.9-.68.81-.76Zm30.22.36a1.42 1.42 0 0 0-1.15-.46c-.68 0-.65.11.15.46 1.27.58 1.29.58.94.06Zm24.77-.23c-1.26-1-2-1-1.21 0a2.11 2.11 0 0 0 1.4.75c.52-.01.52-.16-.25-.74Zm-90.38-.1a2 2 0 0 0-1.24 0c-.34.14-.07.25.62.25s.89-.1.56-.24Zm18.08-.44c.45-.45.6-1 .34-1.22s-.68.07-1 .78c-.66 1.4-.44 1.54.67.44Zm19.92-.27c-.89-.57-1.91-.56-1.91 0s1.52 1 2.16.63c.18-.1.06-.39-.31-.62Zm20.3-2.13c-.06-.68-.34-1.08-.68-1-.65.25-.34 2.15.35 2.15.18.08.33-.48.27-1.14Zm-13-.66c-.17-.17-.29.13-.26.65s.14.7.3.3a1 1 0 0 0 0-.95Zm-21.39-.63a1 1 0 0 0-.77-.5.48.48 0 0 0-.47.5c0 .27.35.49.78.49s.64-.21.42-.48Zm14-2.74c-.14-.15-.42.12-.61.61-.27.7-.21.76.28.28.33-.33.51-.73.33-.88Zm11.48-1c.31.19.4 0 .22-.45-.37-1-.35-1-1.48.34-.72.84-.74.95-.1.45a1.45 1.45 0 0 1 1.36-.34Zm35.75.23c0-.28-.22-.38-.49-.21s-.5.4-.5.52.22.22.5.22a.51.51 0 0 0 .49-.5Zm-28.44-1.58c-.14-.34-.26-.06-.26.62s.12 1 .26.62a1.88 1.88 0 0 0 0-1.23Zm16.74.8c.48-1.23 0-1.4-.68-.22-.3.57-.35 1-.09 1s.6-.36.77-.81Zm-35.2-.71c-.43-.68-.72-.55-.72.31 0 .43.22.64.5.47a.57.57 0 0 0 .22-.77Zm2 .53c.16-.27 0-.49-.47-.49s-.77.22-.77.49a.48.48 0 0 0 .46.5 1.06 1.06 0 0 0 .79-.51Zm22.07 0a.51.51 0 0 0-.52-.49c-.29 0-.39.22-.22.49s.4.5.52.5.23-.21.23-.51Zm-19.65-.54a1.28 1.28 0 0 0 1.39.23c.45-.27.27-.49-.59-.71l-1.24-.33 1.2-.48c.72-.29 1-.62.66-.82s-2.2.22-3.49.95c-.07 0 0 .46.2.93.24.65.42.7.72.23s.53-.51 1.15 0Zm24.48-.58c.34-.26.5-.67.35-.92-.4-.63-1.21-.54-1.21.13a2.15 2.15 0 0 1-.87 1.24c-.81.63-.8.65.13.35a6.63 6.63 0 0 0 1.61-.79Zm-16.24-1.45c0-.26-.34-.14-.74.27a2.63 2.63 0 0 0-.75 1.09c0 .19.34.07.75-.28a2 2 0 0 0 .75-1.07Zm21.33 1.11a1.07 1.07 0 0 0-.49-.77c-.27-.17-.5 0-.5.47s.23.77.5.77a.48.48 0 0 0 .5-.46Zm-2-1.76c-.34-.41-.7-.75-.81-.75s-.18.34-.18.75a.77.77 0 0 0 .8.74c.69.01.73-.12.22-.73Zm-24.31-.25a.49.49 0 0 0-.47-.5 1.09 1.09 0 0 0-.77.5c-.17.27 0 .5.47.5s.8-.22.8-.49Zm3.64-.29a1.55 1.55 0 0 1 .39-1c.27-.32.3-.69.06-.83-.52-.33-3.67 1.62-3.38 2.1s3 .29 2.93-.24Zm15.46.29a1.71 1.71 0 0 0-1.3-.5c-.54 0-.85.23-.68.5a1.71 1.71 0 0 0 1.3.5c.57.01.88-.22.75-.49Zm-12.76-.9c.11-.32-.16-.59-.59-.59a.79.79 0 0 0-.79.79c.03.88 1.1.73 1.41-.19Zm7.55-.37c0-.12-.22-.22-.49-.22a.51.51 0 0 0-.5.53c0 .29.22.38.5.22s.52-.4.52-.52ZM156 309.41c0-.41.22-.75.49-.75s.35.34.19.75-.37.74-.48.74-.2-.33-.2-.74Zm59.34-1.65c-.14-.23.06-.42.44-.42s.59.19.45.42-.34.41-.45.41-.28-.17-.42-.41Zm-1-1.58c-.17-.27-.07-.49.21-.49a.51.51 0 0 1 .53.49c0 .28-.1.5-.22.5s-.37-.22-.54-.5Zm-60.48-3.1a13.94 13.94 0 0 1 1.12-1.52c.58-.67.63-1 .18-1.26s.17-.39 1.14-.42c1.17 0 1.73.18 1.73.65s-.19.69-.42.69a6.06 6.06 0 0 0-1.86 1.24c-1.55 1.33-2.47 1.63-1.89.62Zm57.07-2.17c-1.66-1.79-1.23-2.22.49-.5.73.73 1.2 1.45 1.06 1.6s-.87-.35-1.57-1.1Zm-3.48 0a7.3 7.3 0 0 1-.31-.9 2.51 2.51 0 0 1 1-.18c1.15-.09 1.28.49.3 1.31-.55.47-.78.42-1.01-.14Zm-51.9-2c0-.4.2-.72.46-.72a5 5 0 0 0 1.3-.33c.58-.22.78-.12.63.33-.33 1-2.39 1.63-2.39.72Zm2.48.27a.5.5 0 1 1 .5.49.5.5 0 0 1-.53-.43Zm-4-.22c0-.12.22-.36.5-.53s.49-.07.49.22a.51.51 0 0 1-.49.53c-.27.06-.53-.04-.53-.18Zm5.05-1.3a1.55 1.55 0 0 1 .87-.87c.37-.12.56.07.44.44a1.61 1.61 0 0 1-.87.87c-.36.18-.52-.01-.43-.38Zm48.53-.71c0-.41.37-.56 1-.4a2.5 2.5 0 0 1 1 .4 2.57 2.57 0 0 1-1 .39c-.61.22-.99.07-.99-.34Zm-50.11-.19a1.1 1.1 0 0 1 .5-.81c.27-.17.5-.08.5.19a1.12 1.12 0 0 1-.5.81c-.26.22-.49.14-.49-.13Zm48.62-.06a.5.5 0 1 1 .5.49.5.5 0 0 1-.49-.43Zm-42.46-.63c-.38-.42-.34-.94.13-1.84a6.17 6.17 0 0 0 .65-2.37c0-.62.31-1.12.7-1.12.84 0 1.37-1.21 1-2.23-.16-.41 0-.74.26-.74.78 0 .68 3-.18 5.4a5.86 5.86 0 0 0-.44 2.79c.33.96-1.31 1.04-2.11.17Zm39.79-2.07c-.18-.46-.21-1-.07-1.08s.39.13.57.58.21.95.07 1.09-.39-.15-.57-.59Zm-16.1-4.17c0-.68.12-1 .26-.62a1.88 1.88 0 0 1 0 1.24c-.14.32-.26.05-.26-.64Zm26 .5c-.16-.27-.07-.5.22-.5a.52.52 0 0 1 .53.5c0 .27-.1.49-.22.49s-.39-.24-.56-.51Zm-49-6.28a1.88 1.88 0 0 1 1.24 0c.34.14.06.25-.62.25s-.97-.13-.64-.3Zm82.68-3.4a27.54 27.54 0 0 1-4-1.65c-2.55-1.31-3.12-1.34-3.12-.18s-1 2.08-1.93 1.63l-4-2a36 36 0 0 0-4.68-2 2.67 2.67 0 0 1-1.68-1.05c-.12-.37 0-.52.31-.33s.77-.07 1.06-.55c.5-.84.55-.84 1.05 0 .29.48.74.75 1 .59.45-.28-6.85-7.65-7.6-7.66-.21 0-.37-.25-.37-.55a7.14 7.14 0 0 0-1.79-2.27c-1-.95-1.66-1.85-1.51-2a5.35 5.35 0 0 1 2.48.48 5 5 0 0 0 2.53.45c.17-.17.44.19.6.79s.54 1.09.85 1.09a12.75 12.75 0 0 1 2.92 1.26 8 8 0 0 0 2.35 1 15.87 15.87 0 0 0-2.11-2.24l-2.1-2 1.57.44a3.47 3.47 0 0 0 1.81.21c.14-.13.4.26.59.86s.49.88.67.62a3.67 3.67 0 0 1 2.36-.27c1.73.17 2 .36 1.81 1.15-.14.54 0 .95.33.95s.45-.23.28-.5 0-.49.47-.49a.76.76 0 0 1 .77.74.68.68 0 0 1-.61.74c-.33 0 .16.7 1.1 1.56s1.78 1.31 1.89 1c.27-.82 1.57-.69 1.91.18a1.55 1.55 0 0 0 1.34.79c.59 0 .79.15.45.29a1.48 1.48 0 0 0-.62 1.32c0 .88.1.94.49.33.29-.44.49-.51.49-.15s.58.49 1.75.3c1.67-.27 2.48.46 1.11 1-.37.15-.27.27.25.29.89 0 1.13-.8.49-1.71a17.24 17.24 0 0 0-2.85-2.09 19 19 0 0 1-3.27-2.51 13 13 0 0 0-2-1.75c-.63-.44-1-1-.81-1.15s1 .13 1.8.72a3.67 3.67 0 0 0 3.1.81l1.64-.26-1.47.67c-.81.37-1.26.8-1 1a7.2 7.2 0 0 0 3.2.4 13.62 13.62 0 0 1 1.87-.25c1.7-.14 2 .17 1 1.14-.46.46-.29.59.78.59.75 0 1.24-.2 1.08-.46a.56.56 0 0 1 .22-.77c.27-.17.5 0 .5.4 0 1.13 1.2 1.92 1.89 1.23.31-.32.75-.41 1-.2s-.06.73-.61 1.14c-.95.74-.93.76.38.45.75-.17 1.36-.12 1.36.11 0 .42 1 1 4.44 2.43 1.54.66 1.65.66 1.08 0a51.49 51.49 0 0 0-4.62-3.78c-4.07-3.08-4.32-4.09-.38-1.6 3.77 2.37 3.65 2.32 3.31 1.43a1 1 0 0 1 .24-1.15c.33-.2.41-.11.2.22s.19.69 1 .9c1.23.31 1.27.38.4.72-.64.25-.72.39-.23.41 1.5.06 2.9-.56 2.66-1.18-.17-.44.14-.56 1-.39.7.13 1.27.44 1.27.68s.33.44.74.44.75-.23.75-.51c0-.65 2.47-.07 3.51.82a2.54 2.54 0 0 0 1.24.67c.25 0 .12-.22-.29-.49-.6-.38-.53-.48.37-.48.7 0 1.12.29 1.12.78s.27.69.62.55c.91-.38 2.17-.72 1.74-.48-1 .54-.22 1.07 1.11.8.82-.17 1.49-.1 1.49.15s1.14.5 2.54.56a13.26 13.26 0 0 1 4.19.9 5.24 5.24 0 0 0 3.29.46c1.22-.24 1.45-.19.9.2s-.52.52.49.53 1.07.13.52.47a9.49 9.49 0 0 1-3.38.45 7.33 7.33 0 0 0-3.42.64c-.8.66-5.53.74-8.22.15-1.18-.26-1.49-.13-1.71.7-.35 1.35-1.19 1.45-5.8.67-4.33-.73-5.27-.58-5.27.82 0 1-1.13 1.75-1.71 1.18a3.91 3.91 0 0 0-1.66-.48 36.18 36.18 0 0 1-4.71-1.2 11.28 11.28 0 0 0-3.64-.78 6.1 6.1 0 0 0-.65 2.39c-.06.44-1.18.34-3.31-.3Zm3.24-4.37c0-.21-.19-.26-.46-.09a1.75 1.75 0 0 0-.46 1.39c0 1.05 0 1.05.46.1a7 7 0 0 0 .44-1.42Zm-17.06-.13c-1.46-1.07-2.18-.82-.78.27.68.52 1.35.83 1.49.69s-.18-.57-.71-1Zm39.72-.08c-.17-.16-.29.13-.26.66s.14.69.3.3a1 1 0 0 0 0-1Zm-33.13-.46c-.24-1.23-1.36-1.41-1.27-.19 0 .56.38 1 .75 1s.61-.37.52-.83Zm-2.31 0a1 1 0 0 0-.94 0c-.17.17.13.29.65.26s.69-.14.29-.3Zm34.82-1.26c-.43-.69-1.15.1-.83.92.23.6.38.63.69.13a1.15 1.15 0 0 0 .12-1.08Zm-36.24-.4c-.05-.67 0-.94.16-.6.3.71 1.67.84 1.67.17a.7.7 0 0 0-.46-.62 26.51 26.51 0 0 1-3-2.56c-1.38-1.32-2.51-2.14-2.51-1.84 0 .66 3 3.48 3.67 3.48.27 0 .05.33-.48.73-.7.53-.77.74-.24.75s.59.14.12.61c-.86.86-.73 1.49.28 1.28.57-.12.83-.59.77-1.4Zm33.79.71c1.07-.42 1-1.15-.29-2.25-1.06-.92-2.18-1.27-1.72-.53.13.22-.53.3-1.46.19-1.87-.22-2.16.2-.83 1.21 1 .78 2.6.9 2.6.19 0-.27.34-.5.75-.5.9 0 .95.58.15 1.39s-.4.76.8.3Zm-27.74-2.18c-.17-.27-.41-.5-.53-.5s-.22.23-.22.5a.51.51 0 0 0 .53.49c.27-.03.37-.25.2-.52Zm-9.06-1c-.48-.46-.94-.64-1-.41a.4.4 0 0 1-.62.15c-.55-.33-1.55.21-1.25.68a3.83 3.83 0 0 0 2 .38c1.67 0 1.72 0 .91-.8Zm-20.36 8.35c-.33-.33-.6-.43-.6-.23 0 .43-2.79-1-3.18-1.59-.15-.24.31-.44 1-.44 1 0 1.12.13.61.48s-.38.42.41.24 1-.05.78.54-.09.64.23.45.8 0 1.14.41c.73.88.44 1-.4.14Zm82.38-3.23a11.33 11.33 0 0 1 2.73 0c.74.12.13.2-1.37.2s-2.14-.12-1.38-.24Zm-3.47-.44a1 1 0 0 1 1 0c.4.16.28.28-.3.3s-.81-.1-.65-.26Zm-228.34-.85c.57-.57 5.9-1.68 6.45-1.33s-.29.6-4.71 1.28c-1.09.17-1.87.19-1.74 0Zm67.47-.34a.5.5 0 0 1 1 0 .5.5 0 0 1-1 0Zm160.49-.73c-.27-.08-3.79-.77-7.81-1.54-7.22-1.37-9.09-2.14-5.46-2.25 2.9-.09 3.76.09 4.37.92.31.43.79.64 1.06.46a4 4 0 0 1 2.19.22c.92.28 3.25.93 5.16 1.43s2.91.91 2.23.91a7.86 7.86 0 0 1-1.74-.15Zm-218-1.3a11.63 11.63 0 0 1 4.64-.75c2.81.32 3.66-.13 2.13-1.14-1.23-.81-1.23-.81 1.33-1 1.41-.1 4.4-.19 6.66-.21a23.41 23.41 0 0 0 4.09-.2 4.65 4.65 0 0 0-1.54-.71c-1.17-.41-1.37-.65-.87-1s.4-.41-.35-.25-1-.05-1.11-1c-.06-.69.11-1.06.41-.88s.4.46.29.65.68.4 1.76.47 1.85-.06 1.7-.32.07-.59.49-.75c1.18-.46 1.54-.35.71.19-.56.36-.26.49 1.15.49a4.56 4.56 0 0 1 2.64.75 2.47 2.47 0 0 0 1.57.71c.68 0 .65-.12-.15-.5s-.86-.45.24-.28a12 12 0 0 1 2.24.6c.87.36.9.32.24-.33a13.05 13.05 0 0 0-3.22-1.7c-2.25-.88-2.35-1-1.11-1.14.76-.08 1.24-.37 1.08-.63s.8-.34 2.55-.13c2.09.24 2.75.18 2.48-.24-.38-.63 1.17-.77 3-.28.75.2 1 .57.81 1.24-.28 1.09.75 1.31 1.17.23.22-.58.51-.61 1.54-.14l1.28.59-1.19.36c-.77.24-.48.28.79.13a6 6 0 0 1 3.4.51c1.61.85 3.52 1 3 .26a1.75 1.75 0 0 0-1.35-.5 35 35 0 0 1-7.81-3.31c-.13-.11.09-.43.49-.68.64-.42.64-.53 0-.94-1.23-.8-.4-.93 3-.46 1.93.26 3.45.76 3.8 1.22a5 5 0 0 0 3 1 23.87 23.87 0 0 1 4.25.78c3 .91 2.58.26-1-1.61-1.81-1-3.38-2.07-3.49-2.47-.15-.58-.1-.59.27-.06s.53.43.74-.37c.34-1.32 1.92-1.35 3.09-.05.92 1 3.23 1.38 3.23.5 0-.7.83-.62 2.74.29a5.6 5.6 0 0 0 2.55.62c.51-.1-.33-.59-1.95-1.14-1.87-.63-2.84-1.23-2.81-1.73s.12-.56.29-.15a1 1 0 0 0 .73.62c.27 0 .35-.22.18-.49s-.07-.5.22-.5a.5.5 0 0 1 .53.45c0 .52.93.45 1.87-.13a3.61 3.61 0 0 1 2.23.1 28.73 28.73 0 0 0 4 .77 8.54 8.54 0 0 1 3.08.84c.69.57 1.87.16 1.45-.51a3.68 3.68 0 0 0-1.74-.7c-.82-.17-1.5-.55-1.5-.86s.46-.45 1.17-.27a2.08 2.08 0 0 0 1.49 0c.84-.85 3-.54 4.06.56.74.81 1.29 1 1.77.72.8-.51 2.77-.2 5.85.93 1.78.65 2 .86 1.24 1.17s-.29.37 1.17.4a5.28 5.28 0 0 0 2.1-.18 10.58 10.58 0 0 0-2.35-1.37 10.19 10.19 0 0 1-2.53-1.52c-.09-.2-.64-.37-1.24-.38a3.63 3.63 0 0 1-1.81-.53c-.58-.4-.48-.44.43-.19s1.07.16.84-.2 0-.44.49-.23.76.11.49-.75c-.34-1.09 0-1.3 1.39-.77a.73.73 0 0 1 .54.86 1.77 1.77 0 0 0 .26 1.31c.38.6.83.63 2.47.19 1.11-.29 2-.39 2-.21s.83.65 1.86 1c2.75 1.06 2.92 1.1 2.11.47-.61-.47-.52-.52.49-.28.68.17 1.91.44 2.73.59l1.49.28-1.7-1.08a10.57 10.57 0 0 0-3.34-1.35c-1.55-.25-3.45-1.63-2.24-1.63a1.13 1.13 0 0 1 .83.49 2.58 2.58 0 0 0 1.8.5c1.37 0 1.43-.06.69-.8-.93-.93-.61-1.6.37-.78a2.19 2.19 0 0 0 1.7.28 1.31 1.31 0 0 1 1.36.27c.19.29.1.53-.19.53a.48.48 0 0 0-.52.43c0 .24 1.17.57 2.6.74l4.84.6c3.17.4 1.47-.58-2.58-1.5l-3.13-.7 3.73.26c2 .14 4.5.37 5.45.5a33.61 33.61 0 0 0 3.72.23c2.86 0 1.18.57-6 2.15a65.39 65.39 0 0 0-6.89 1.78 28 28 0 0 1-5.95 1.8 5.45 5.45 0 0 0-2 .67 19.06 19.06 0 0 1-4 1.13c-1.91.4-6.94 1.52-11.16 2.48-10.22 2.31-16.71 3.47-19 3.38a37.43 37.43 0 0 0-6 .67 161.55 161.55 0 0 1-20.2 2c-7.5.27-19.69 1.22-22.52 1.76-4.23.79-5.35.5-2.28-.59Zm76.15-13.46c.27-.2-.15-.37-.94-.38-1.22 0-1.33.1-.79.75.34.42.76.59.93.38a4.49 4.49 0 0 1 .8-.75Zm-21.09 10.44c0-.22.67-.41 1.49-.41s1.49.09 1.49.2a3.63 3.63 0 0 1-1.49.41c-.87.08-1.54-.01-1.54-.24Zm141.14.09c-1.36-.22-4.6-.75-7.19-1.19L251.52 267c-3.27-.55-7.07-1.25-8.43-1.55s-3.6-.72-5-.92c-3.7-.56-8.5-1.43-12.4-2.24-1.91-.4-6.49-1.2-10.17-1.78a41.65 41.65 0 0 1-7.3-1.53 1 1 0 0 0-1-.22 23.35 23.35 0 0 1-5.11-.7l-7.11-1.49a15.93 15.93 0 0 1-2.61-.67 13.3 13.3 0 0 1 3.19-.78l4.1-.74a35.64 35.64 0 0 0 5.09-1.38c1-.38 1.55-.36 2.07.08a1.34 1.34 0 0 0 1.71.06c1.21-.65 1.55-.67 1.55-.07 0 .25-.51.6-1.12.77-1.87.52-4.82 2-4.83 2.36 0 .21-.38.37-.83.37s-.71.12-.56.27c.4.41 4.37-.34 4.11-.77-.13-.21.1-.51.53-.68s.64-.09.48.17a.56.56 0 0 0 .2.75c.26.16.72-.16 1-.71a1.86 1.86 0 0 1 1.29-1c.45 0 .61-.23.4-.57s0-.49.69-.3a1.88 1.88 0 0 0 1.85-.63c.75-.83.75-.91-.1-1.11-.57-.12-.19-.35 1-.6s2-.21 2.25.16 1.06.46 1.93.35 1.59 0 1.59.26-.34.45-.75.45c-.88 0-2.51.89-3.91 2.11a3.61 3.61 0 0 1-1.39.87.46.46 0 0 0-.4.5c0 .75 1.29.59 2-.25a2.35 2.35 0 0 1 1.61-.75 2.53 2.53 0 0 0 1.71-.86c.49-.6.78-.69 1-.28s.54.47 1.45.05 1.08-.7.65-1.21-.32-.67.55-.67a1.32 1.32 0 0 0 1.35-1c.31-1.2 1.64-.78 1.64.52a.88.88 0 0 0 .87 1c.82 0 .82 0 0 .67-.48.37-.87.78-.87.93 0 .44 2.43.75 2.58.32a3.66 3.66 0 0 1 1.88-.7 9.13 9.13 0 0 0 2.87-1c.62-.41 1.3-.64 1.5-.51.81.5.22 2.79-.93 3.65a58.91 58.91 0 0 1-8.4 4.23c-.41.13.82.12 2.73 0 3.38-.24 5.21-1.14 5.21-2.57 0-.29.53-.52 1.19-.52a1.63 1.63 0 0 0 1.47-.74 1.14 1.14 0 0 1 .92-.74c.48 0 .47.11-.05.44-.89.56-.28 1.54 1 1.54s1.16-.2.49-1.45c-.41-.76-.3-.86.74-.66.67.13 1.09.43.94.68s-.05.43.22.43a4.88 4.88 0 0 1 1.35.34c.48.18.73.13.58-.13-.38-.61.58-1.88 2-2.62 1-.51 1.07-.48.79.29a3.77 3.77 0 0 1-1.07 1.5c-.56.45-.59.62-.12.62 1.27 0-.43 1.38-2.09 1.69a23.43 23.43 0 0 0-4.51 1.79 14.49 14.49 0 0 1-3.85 1.48c-.51 0-.92.21-.92.45s.78.25 1.86 0 2.75-.6 3.84-.77a4.52 4.52 0 0 0 2.35-.85c.25-.37.37-.35.37.08s.46.62 1 .62 1-.22 1-.48.63-.36 1.41-.21a6.41 6.41 0 0 0 3.59-.71 7.21 7.21 0 0 1 3.49-.74c.72.13 1.45 0 1.62-.26s.86-.35 1.8-.08 1.48.32 1.48.18.28-.06.62.15 0 .51-1.12.72a83.35 83.35 0 0 0-11.85 3.3l-2.36 1 2.48-.34c1.36-.2 3-.36 3.59-.37s1.12-.22 1.12-.47.78-.3 1.74-.15 1.73.07 1.73-.18a.5.5 0 0 1 .53-.47c.29 0 .39.21.24.46-.47.76.7 1.16 1.48.52a2.27 2.27 0 0 1 1.79-.32 3.5 3.5 0 0 0 2.73-1c1.29-1 1.82-1.15 2.35-.71a2.36 2.36 0 0 0 3-.66c.25-.32.3-.19.14.33s.07.94 1.07 1.16c2.23.49 2.41.63.91.67a22.84 22.84 0 0 0-4.87 1.28 24.6 24.6 0 0 1-4.52 1.27c-1 0-1 .06-.09.42a5.14 5.14 0 0 0 2.72-.11 38.93 38.93 0 0 1 7-.55c3.88 0 5.17-.2 5.08-.66a.47.47 0 0 1 .43-.62c.3 0 .41.35.24.78a4.57 4.57 0 0 0-.29 1.21c0 .23-.73.57-1.62.76a7 7 0 0 0-2.1.73 5.82 5.82 0 0 1-1.49.67c-.55.15 0 .19 1.26.08a19.44 19.44 0 0 0 3.22-.55c.94-.33.94-.32 0 .45s-1 .77.25.49a6.08 6.08 0 0 0 1.82-.75c.35-.27.7-.25.89.07.47.75 1.88.66 1.5-.1-.18-.33 0-.2.44.31.74.91 1.36.91 4.4 0 .62-.18.78 0 .58.49-.56 1.46-7.07 2.49-11.37 1.8ZM235.9 258c0-.29-.23-.39-.5-.22s-.5.41-.5.53.23.22.5.22a.52.52 0 0 0 .5-.53Zm-35.77-2.26a.28.28 0 0 0 0-.42c-.37-.37-2.41.32-2.41.82s1.85.14 2.41-.37Zm25.55-.43c-1.18-.89-1.52-.91-1.82-.13-.16.4.29.66 1.26.73 1.41.1 1.44.07.56-.6Zm13 13.19c-.15-.24 0-.56.42-.71.72-.28 1.28 0 1.28.74s-1.38.56-1.72 0Zm-146-4.32a1 1 0 0 1 1 0c.39.16.28.28-.3.3s-.82-.09-.65-.26Zm67.09-.67c-1-.41-1-.41 0-.79.54-.2 1.21-.5 1.48-.64s.22.07-.12.49-.47.91-.29 1.09c.4.4.2.37-1.07-.15Zm-53.7-1.31a1 1 0 0 1 1 0c.4.16.28.28-.3.3s-.81-.1-.65-.26Zm-2-.5a1 1 0 0 1 1 0c.39.16.28.28-.3.3s-.82-.09-.65-.26Zm151.49-.09c-.14-.22.17-.39.68-.39s.81.17.67.39a.78.78 0 0 1-1.35 0Zm-131.78-2.73a2 2 0 0 1 .67-1.29c.54-.44.67-.34.62.52-.07 1.23-1.29 1.96-1.29.8Zm105.8.34a1 1 0 0 1 .95 0c.4.16.28.28-.3.3s-.81-.09-.65-.26Zm-100.84-2.68a.5.5 0 1 1 .5.5.49.49 0 0 1-.5-.47Zm106.66-2.45c0-.83.88-1 1.17-.27.18.45 0 .74-.44.74s-.73-.21-.73-.47Zm-48.56-.77c-.06-.73-.18-.82-.48-.34s-.56-1.85-.76-5.36c-.19-3.3-.67-7.89-1.07-10.19a35.94 35.94 0 0 1-.62-4.57 2.79 2.79 0 0 0-.44-1.37 26.9 26.9 0 0 1-1.36-4.19 13.64 13.64 0 0 0-1.55-4c-.81-.9-1-1.69-.26-1.25a.56.56 0 0 0 .76-.19.92.92 0 0 1 1.09-.18c.55.21.72.08.56-.39a1.11 1.11 0 0 0-1.21-.57c-.69.1-1.21-.36-1.81-1.61-1-2.19-.66-2.27.65-.14 1.19 2 1.29 1.42.17-.94-.48-1-1.07-1.61-1.46-1.46-.67.26-2.85-2.78-2.85-4 0-.36-.23-.61-.5-.55s-.79-.46-1.15-1.15c-.62-1.19-.58-1.24 1-1.4s3.2-1.08 1.91-1.08a1.94 1.94 0 0 1-1.21-.63 15.91 15.91 0 0 1-2.82-4.27.75.75 0 0 0-.37-.64c-.6-.26-3.32-4.51-3.68-5.74s0-1.45 1-.53c.46.47.6.47.6 0a.53.53 0 0 1 .62-.53 18 18 0 0 0 2.11 0c1.26-.06 1.35-.15.61-.58a1.66 1.66 0 0 0-1.34-.22c-.47.29-4.48-4.74-4.48-5.63a.75.75 0 0 0-.37-.64 26.11 26.11 0 0 1-3.11-5.56c0-.17.73-.33 1.62-.36 1.55 0 1.56-.06.37-.49-1-.36-.92-.4.62-.23 1 .12 1.86 0 1.86-.25a.47.47 0 0 0-.47-.47c-.61 0-3.48-4.13-4.62-6.63-1.06-2.34-1.06-2.34 0-1.94.76.29.78.24.13-.46-.4-.44-.91-.76-1.15-.72s-.62-.44-.86-1.07c-.4-1-.32-1.13.72-1a4.94 4.94 0 0 0 2.36-.42c.65-.31 1.63-.73 2.17-.93.86-.33.88-.4.15-.57a.75.75 0 0 1-.57-1c.22-.7.18-.72-.25-.1s-.7.56-1.73-.41c-1.34-1.26-3.79-6.34-4-8.28a3.15 3.15 0 0 0-.54-1.6c-.23-.2-.23 0 0 .38s.24.58-.11.25a4.69 4.69 0 0 1-.85-1.74c-.26-1-.22-1.06.18-.5.29.41.52.54.53.28s.28-.21.6.12c.72.72 2.07.8 1.64.1s.92-.62 1.63.09c.32.33.74.43.92.25s-.18-.78-.81-1.33c-1.15-1-.9-1.22 1.11-1 .58.06.77-.16.58-.65s-.83-.7-1.59-.65c-1 .07-1.49-.27-2.17-1.65a31.85 31.85 0 0 1-2.61-6.72c-.31-1.49-.28-1.52 1.39-1.32 1.16.13 1.79.5 2 1.14.36 1.35 1 1.17 2-.58 1.1-1.94 1.08-2.25-.16-3.58-.77-.82-.94-.88-.69-.23.54 1.42-.55 1-1.27-.49a5.05 5.05 0 0 1-.58-1.74c.05-.2-.13-.37-.4-.37s-.5-.45-.5-1 .23-1 .5-1 .49-.44.49-1 .23-1 .5-1a.48.48 0 0 1 .5.45c0 .58 3.16.11 3.38-.5a1 1 0 0 1 .87-.45c.39 0 .71-.22.71-.49s-.67-.5-1.49-.5-1.49-.22-1.49-.5.45-.49 1-.49c1.1 0 1.22-.37.49-1.49-.38-.58-.58-.61-.89-.12-.88 1.35-4.33-3.87-4.57-6.92-.11-1.45.49-3.38 1.06-3.38a.43.43 0 0 1 .45.37 10.49 10.49 0 0 0 .33 1.62c.3 1.11.24 1.17-.5.56-.54-.45-.83-.49-.83-.13s.19.56.42.56.33.61.21 1.36c-.18 1.11-.11 1.23.36.62s.52-.47.27.64c-.37 1.73.66 3 1.6 2 .47-.51.43-.81-.17-1.41-1.27-1.27-1.2-5.74.1-5.74.66 0 .69.13.18.74s-.44.75-.06.75.56.5.57 1.11c0 .87.1 1 .46.41s.58-.6 1.22-.07.85.44 1.3-.74c.62-1.64.26-2.91-.73-2.53a1.26 1.26 0 0 1-1.29-.45c-.49-.58-.4-.71.45-.71.58 0 1.06-.23 1.06-.53s-.2-.39-.45-.24c-.68.42-2-.23-2-1a1.07 1.07 0 0 0-.62-.9c-.34-.13 0-.26.74-.29s1.37-.22 1.37-.43-.64-.36-1.41-.31-1.42-.16-1.45-.47a13.73 13.73 0 0 0-.38-1.8c-.22-.88-.18-1 .16-.56.79 1.12 1.62.2 1.08-1.2-.47-1.25-.2-1.46 1.18-.93a.76.76 0 0 1 .5 1c-.17.46 0 .6.65.45 1-.24 3.14 2.55 3.14 4 0 .78 1.31 1.58 1.8 1.09.15-.14 0-.73-.29-1.3s-.38-1 0-1 .52-.36.52-.8c0-.63-.15-.68-.7-.22-.95.78-1.59-.25-.78-1.23a1.19 1.19 0 0 0-1.29-1.88c-.39.15-.71 0-.71-.28s.45-.55 1-.55 1-.22 1-.49-.34-.5-.75-.5-.74-.22-.74-.5a.47.47 0 0 1 .46-.49c.25 0 0-1.24-.51-2.75a31.64 31.64 0 0 1-1.57-5.01c-.24-1.78-.4-2.09-.69-1.34s-.39.63-.42-.65c0-.88.07-1.61.19-1.61a6.67 6.67 0 0 1 1.47 1.24 4.16 4.16 0 0 0 2 1.24c.45 0 .64.29.48.72a2.41 2.41 0 0 0 .51 1.85c.69 1 .79 1 .79.2a2.26 2.26 0 0 1 .86-1.57c.7-.51.75-.76.25-1.27-.72-.73-.84-3.4-.15-3.4.26 0 .7.39 1 .86.65 1.12.68.08 0-1.57-.26-.7-.7-1.28-1-1.28s-.37.3-.24.67a1.32 1.32 0 0 1-.53 1.32c-.67.56-.77.55-.65 0 .09-.39-.13-.65-.49-.57-.65.13-2.08-2.69-2.08-4.13 0-.72.07-.72.77 0 .43.43.64 1 .47 1.28s0 .5.46.5c.89 0 .81-1.66-.12-2.59s-.73-3.2.2-3.56c1.09-.42 1.3-.37 1 .2-.17.27-.06.49.25.49s.41.23.24.5c-.39.63.06.63 1.24 0 .62-.33 1-.33 1.24 0s0 .49-.46.49c-1.09 0-1 .86.22 1.49s1.3 1.49.17 1.49c-.61 0-.46.33.58 1.3 1.23 1.13 1.3 1.35.59 1.75a1 1 0 0 0-.53 1.18c.15.4.5.59.76.43.78-.48 1.87 1.41 1.36 2.37-.62 1.15-.57 1.41.26 1.41s1.44-1.68.86-2.82a21.64 21.64 0 0 1-2.36-7.09 6.57 6.57 0 0 0-.78-2.79.54.54 0 0 1-.15-.73c.16-.26-.06-.61-.5-.77-.61-.24-.66-.46-.22-1s.38-.69.07-.69c-.7 0-1.05-4.62-.39-5.08.35-.24.31-.36-.1-.37a.59.59 0 0 1-.62-.54c0-.29.61-.46 1.37-.37a3.09 3.09 0 0 1 2.58 3.23 1.5 1.5 0 0 0 1 1.41c1.4.44 1.66 0 .87-1.34a6.1 6.1 0 0 1-.66-2.65c0-1.24.18-1.46 1.12-1.36 1.15.12 1.51-.56.59-1.13-.65-.41.35-1.72 1.33-1.72a.75.75 0 0 0 .68-.81c0-.56-.18-.66-.62-.33-1.24.93-3.29.68-3.91-.48-.54-1-.52-1 .25-.42s.79.59.5-.17a1 1 0 0 1 .26-1.23c.43-.27.45-.73.05-1.77s-.38-1.5 0-1.76a1.26 1.26 0 0 0 .23-1.4c-.23-.71-.12-1 .32-1 1.14 0 2 .86 1.75 1.64a.78.78 0 0 0 .43 1c.5.19.63.67.41 1.53-.35 1.39.33 1.66 1.29.51.33-.41.39-.75.12-.75-.8 0-1.26-4.06-.52-4.54.49-.3.44-.41-.17-.41a1.34 1.34 0 0 1-1.12-.79c-.46-1.21-.33-1.55.24-.58.28.47.76.87 1.07.87s.42-.16 0-.59-.45-.75 0-1.28.42-.8.06-1a3.53 3.53 0 0 1-.39-2.31c.17-2.71.13-2.59 1.1-3 .69-.26.74-.2.25.3a2 2 0 0 0-.62 1.14c0 .29.22.19.48-.22.4-.63.57-.6 1.22.25s.75.81.76.24a2.2 2.2 0 0 1 1.08-1.41c1-.6 1-.58.71.25-.25.66-.2.77.2.38.8-.8.09-2.37-.76-1.67s-.71-.54.21-3.85c.45-1.61.77-2 1.4-1.78s.74.1.45-.88c-.57-1.91-.58-2.19-.05-2.32.74-.18 2 3.82 1.36 4.43s-.24 2.39.42 2.39c.23 0 .3-.32.14-.72s.16-.83 1-1 1.29-.67 1.29-1.09.14-.64.31-.46 0 1.08-.5 2a10.08 10.08 0 0 0-.8 2.11 7.15 7.15 0 0 1-1.51 2c-.93 1-1.39 1.85-1.21 2.33.33.85 2.22 1.56 2.22.83a.47.47 0 0 0-.5-.45.53.53 0 0 1-.49-.55c0-.3.31-.43.69-.28a1.76 1.76 0 0 0 2.15-1.93c-.23-.87-.25-.86-.31.1-.06 1.22-1.54 2.06-1.54.88 0-.42.22-.62.5-.45s.49-.21.49-.85a2.43 2.43 0 0 1 1-1.8c1-.59 1-.55.69.46a15.66 15.66 0 0 0-.34 3.51c0 1.87-.22 2.54-.94 2.92a1.72 1.72 0 0 0-.93 1.21 10.25 10.25 0 0 1-1.11 2.84 15.18 15.18 0 0 1-2.77 4.12c-.56.62-.51.73.33.73 1.12 0 2.74-2 3.22-4.06.42-1.7 1.74-3 2.66-2.66a4.32 4.32 0 0 0 1.21.27c.27 0 .36.21.2.45s-.83.4-1.52.32-1.23.12-1.23.47.47.52 1.36.35c1.12-.21 1.27-.14.81.44-.77 1-1.83 5.74-1.54 6.88.17.66 0 .86-.44.68s-.69.13-.69 1c0 .71-.22 1.29-.49 1.29s-.5.5-.5 1.12a7 7 0 0 1-1 2.78c-.56.92-.9 1.78-.77 1.92s-.4.84-1.21 1.54c-1.58 1.39-1.85 2.06-.81 2.06s3.73-2.92 3.46-4c-.16-.62.21-1.38 1-2.14a12 12 0 0 0 2.25-3.46c.84-1.94 1.15-2.24 2.13-2s1 .14.41-.28c-.41-.29-.51-.54-.23-.54a1.5 1.5 0 0 1 1.37 1.79c-.14.37-.46.55-.7.4s-.44.88-.44 2.29c0 1.85-.18 2.5-.62 2.33-2.08-.78-2.86-.89-2.86-.39 0 .3.67.68 1.49.84s1.5.61 1.51 1.11.47-.17 1-1.35 1.11-2 1.25-1.92c.41.42-1.23 3.83-2 4.12a1.29 1.29 0 0 0-.74 1.11 11.64 11.64 0 0 1-.34 2.2c-.28 1.15-.2 1.32.51 1s.71-.41.19-.74-.47-.42-.07-.43a1.19 1.19 0 0 1 .9.49 1.8 1.8 0 0 0 1.39.47c1 0 1-.06-.15-.55s-1.2-.54-.25-.93c.54-.23.77-.43.49-.45s-.17-.25.22-.49c1-.63 2.75.32 2.09 1.12a2.24 2.24 0 0 0-.51 1.08 24.07 24.07 0 0 1-1.35 6.11c-.54 1.32-.88 1.59-1.68 1.33-.58-.18-1-.09-1 .22s.5.52 1.12.53c.9 0 1.12.29 1.16 1.49 0 .92-.19 1.48-.6 1.48s-.54.31-.37.75a2.73 2.73 0 0 1-.51 2 6.18 6.18 0 0 0-.8 1.48c0 .55 1.41.24 1.65-.36.17-.39.27-.32.3.19a2.33 2.33 0 0 0 .81 1.45c.68.57.73.49.46-.63-.23-.88-.09-1.41.41-1.7a2.48 2.48 0 0 0 .91-1.66c.12-.91.48-1.24 1.31-1.25a2.1 2.1 0 0 0 1.61-.75c.35-.56.46-.44.47.53a3.15 3.15 0 0 1-.43 1.74c-.7.75-1.24 2.88-.64 2.51 1.48-.91.92 1.91-.69 3.52a4.34 4.34 0 0 0-.85 1.25c-.13.37-.41.45-.67.2s-.17-.54.19-.67a1.21 1.21 0 0 0 .62-1.11 1.36 1.36 0 0 1 .77-1.2c.43-.16.63-.53.45-.82s-.79 0-1.52.77-1.08 1.51-.92 1.76.07.46-.2.46-.37.4-.25.88a2.59 2.59 0 0 1-.79 2c-1 1.13-1.33 2.12-.6 2.12.23 0 .71-.61 1.06-1.36.47-1 .71-1.16.9-.62.56 1.63 2.59 2 2.59.51 0-.66-.24-.88-.75-.69s-.74 0-.74-.26.51-.55 1.14-.55a2.76 2.76 0 0 0 1.68-.54c.38-.38.83-.39 1.52 0 .54.29 1.1.41 1.23.28.38-.38-.42 3.33-1 4.74l-.52 1.24v-1.26a2.9 2.9 0 0 1 .56-1.86c.44-.44.45-.6 0-.6s-.45-.33-.3-.74 0-.75-.7-.75c-.91 0-1.91 1.84-1.33 2.43.12.12.23 0 .23-.36a.57.57 0 0 1 .55-.58c.38 0 .49.83.33 2.47-.13 1.35-.05 2.34.19 2.2s.42-.13.42 0a19.2 19.2 0 0 1-2.2 4.22c-.34 0 0-1.2.69-2.48s.63-1.2-.22-.49-.88.68-.09-.85a7 7 0 0 0 .81-2.23c0-.35-.5.36-1.11 1.59a12 12 0 0 1-1.85 2.84c-.5.4-.55.61-.14.62s.48.35.32.75 0 .76.88.79c1 0 1 .12-.44.75-1.89.82-2.06 1.49-.5 2a3.23 3.23 0 0 1 1.44.85c.17.27.57 0 .88-.57.48-.89.43-1.06-.32-1.06s-.76-.09-.15-.47.85-.27 1.06.08a.94.94 0 0 0 1.1.24.79.79 0 0 1 1 .31c.18.44.35.42.7-.07.67-1 1.1.32.57 1.65-.39 1-.39 1-.43 0s-.12-.85-.67.25a7.19 7.19 0 0 1-2.21 2.23c-.86.55-1.58 1.19-1.59 1.43s.29.17.68-.16c.58-.48.66-.4.44.44-.15.56-.46 1-.71 1a.46.46 0 0 0-.43.48c0 .79 1.43 0 1.73-1 .43-1.38 2.73-3.7 2.73-2.76 0 .42-.17.77-.37.78a6.12 6.12 0 0 0-1.57 1.95 5.48 5.48 0 0 1-2.11 2.17 1.21 1.21 0 0 0-.91 1c0 .67.18.71 1 .26.57-.31 1.17-.43 1.33-.27s0 .29-.42.3-.14.34.54.73c1.58.9 2 .91 2 0a1.11 1.11 0 0 1 .68-1c.56-.22.54.13-.11 1.81a9.62 9.62 0 0 1-1.62 2.81 8.22 8.22 0 0 0-1.47 2.48 9 9 0 0 1-1.31 2.39c-.9.92-.8 1.74.36 3.07.89 1 1.12 1.09 2.22.5a3.38 3.38 0 0 0 1.48-1.45 3.09 3.09 0 0 1 1.18-1.37c.83-.52.94-.44.93.74 0 .73-.11 1.05-.23.71-.28-.8-1.12-.29-1.12.68 0 .41.21.61.48.45.67-.42-.87 3.84-1.61 4.47a6 6 0 0 0-1.16 1.48 1.88 1.88 0 0 1-1.37 1c-.45 0-1.14.62-1.53 1.37s-.5 1.36-.24 1.36c.73 0 .54 2.57-.26 3.46-.67.73-.56.78 1.11.58 1-.12 1.57-.32 1.23-.43-1.2-.42-.61-1.57 1.12-2.19 1-.34 1.73-1 1.74-1.4 0-.67.06-.68.49 0s.55.66 1 0 .48-.55.19.25c-1.31 3.58-1.68 4.36-1.7 3.59 0-.47-.24-.86-.51-.86-.72 0-.61 1.86.12 2.1s-1.78 4.14-3.14 4.87c-.72.37-.82.27-.62-.65s.2-1-.54.13a28 28 0 0 0-1.39 2.6c-.33.75-.78 1.36-1 1.36s-.39.24-.39.53a2.46 2.46 0 0 1-.75 1.28c-.85.85-.56 1.67.58 1.67.49 0 .67-.28.47-.77-.36-1 1.42-3 1.92-2.22a1.22 1.22 0 0 1-.26 1.28c-1 1.16.82.91 2-.28s1.23-1.41-.08-2.54c-.89-.78-.93-.93-.23-.93a1.27 1.27 0 0 1 1.11.72c.2.51.71.64 1.72.44 1.54-.31 1.89.37 1 1.82-.41.63-.48.64-.49 0s-.13-.59-.62-.1-.48.7 0 1 .28 1.12-.58 3.21c-1.55 3.82-2.54 5.25-4.33 6.23-2.67 1.46-10.3 10.21-9.53 10.93.17.17.79-.35 1.37-1.16 1.45-2 5.91-6.5 6.32-6.37.18.06.3-.27.27-.74-.05-.66.31-.83 1.67-.78 4.48.15 4.39 0 2.55 3.56-2.4 4.61-3.15 5.83-4 6.59s-.82 1.38.79 2.27c.85.47 1.2.43 1.86-.23s.81-.74.81.18a4.29 4.29 0 0 1-.71 2 21.46 21.46 0 0 0-1.7 3.6c-1.3 3.43-3.93 6.84-5.77 7.5-1.2.43-1.35.63-.75 1a.86.86 0 0 0 1.31-.28c.55-.73 3.87-1.17 4.46-.58.17.17-.5 1.41-1.49 2.76a11.53 11.53 0 0 0-1.8 3c0 1.12-2.68 5.24-4.44 6.82-1.6 1.45-1.66 1.6-.67 1.62a1.85 1.85 0 0 0 1.49-.66c.26-.45.73-.55 1.39-.28s.7.41.34.43-.5.27-.32.56 0 .9-.41 1.37a18.56 18.56 0 0 0-1.88 4.4c-.62 2-1.3 3.47-1.49 3.35s-.26.17-.14.65a2.53 2.53 0 0 1-.77 1.94c-.86.92-.89 1.11-.22 1.36s.69.66.43 1.66c-.19.75-.44 1.81-.54 2.35-2.48 12.54-3.21 16.38-3.59 18.85-.25 1.64-.49 2.53-.53 2Zm.44-9.18c.12-3.88 0-11.57-.19-11.39s-.34 4.14-.48 8.91c-.17 6.07-.11 7.86.2 5.95.25-1.46.46-3.03.47-3.44Zm2.48-13.14c0-.4-.22-.6-.5-.43a1.35 1.35 0 0 0-.49 1.05c0 .41.22.6.49.44a1.33 1.33 0 0 0 .5-1.06Zm-2.49-4.8c0-.49-.21-.76-.49-.59a2.3 2.3 0 0 0-.49 1.64c0 1 .12 1.17.49.59a3.64 3.64 0 0 0 .49-1.68Zm-2.68-2.2a2.47 2.47 0 0 1-.78-1.32c0-.3-.13-.41-.29-.25-.4.4.7 2.35 1.33 2.35.28 0 .16-.34-.26-.78Zm-2.27-1.2c0-.27-.35-.5-.78-.5s-.63.23-.46.5a1.05 1.05 0 0 0 .77.49.49.49 0 0 0 .47-.45Zm9.21-5.18c.78-.77.91-1.27.33-1.27-.21 0-.65.45-1 1-.71 1.19-.36 1.34.67.31Zm-12.93-2.72c-.17-.27-.41-.5-.53-.5s-.22.23-.22.5a.52.52 0 0 0 .53.5c.29 0 .39-.23.22-.5Zm1.91-4.22c-.31-.8-1.17-1-1.17-.28 0 .37.35.63 1.35 1 .06 0 0-.29-.18-.71Zm11.73-2.93a4.1 4.1 0 0 1 1.08-1.62c.59-.63.92-1.3.74-1.48s-.4-.24-.48-.12-.86 1.05-1.73 2.08c-1.81 2.12-2.08 3.15-.6 2.22.54-.34 1-.83 1-1.08Zm-5.46.21a.5.5 0 1 0-.49.49.49.49 0 0 0 .49-.45Zm-1.73-1.49c-.17-.28-.41-.5-.53-.5s-.22.22-.22.5a.51.51 0 0 0 .53.49c.29.04.39-.22.22-.49Zm12.65 0a.5.5 0 1 0-.5.49.5.5 0 0 0 .5-.49Zm-13.91-2c-1.17-.73-1.76-.54-.7.23.53.39 1.07.6 1.2.47s-.1-.45-.5-.71Zm11.43-1a.5.5 0 1 0-.5.5.51.51 0 0 0 .5-.48Zm-13.15-.5a1.07 1.07 0 0 0-.77-.49.48.48 0 0 0-.47.49c0 .28.35.5.77.5s.64-.2.47-.47Zm8-4.18c.78-.78.91-1.27.33-1.27-.2 0-.65.44-1 1-.74 1.17-.4 1.32.6.29Zm-3.75-.28c0-.27-.45-.5-1-.5s-1 .23-1 .5.45.5 1 .5.97-.21.97-.48Zm-9.75-1.24c-.33-.86-.3-.85-1.5-.39-.8.3-.76.38.33.69l1.35.39c.06 0 0-.28-.18-.69Zm16.09-.41c-.08-.36.53-1.24 1.35-1.94s1.31-1.46 1.09-1.67-1.33.57-2.48 1.76-1.7 2-1.22 1.85.87-.09.87.18.12.48.27.48.17-.28.09-.64Zm-9.1-1c-1-.76-1.51-.67-1 .18a1.44 1.44 0 0 0 1.13.53c.68 0 .65-.12-.15-.71Zm-2.7-2.71c0-.24-.56-.8-1.24-1.25-1.09-.71-1.24-.72-1.24-.06 0 .43.43.76 1 .76s1 .22 1 .49.11.5.25.5.2-.22.2-.46Zm15.56-.8c-.16-.4-.51-.6-.78-.43s-.24.47.17.73c.88.56.9.54.58-.32Zm-8.07-1.39c-.63-.64-1.28-.14-.81.62a.62.62 0 0 0 .84.23c.34-.22.33-.49 0-.85Zm-12.53-3.66c-1.86-2.49-2.18-2.73-2.31-1.77-.09.61-.37 1.11-.62 1.11s-.46.34-.46.75a.68.68 0 0 1-.58.74c-.32 0-.44.15-.26.33s.8-.1 1.37-.61c1-.89 1.08-.89 1.67-.08.35.47.83.86 1.08.86s.74.47 1.1 1 .76.92.9.78-.71-1.55-1.89-3.15Zm14.47 1.82a1.79 1.79 0 0 1 1.25-1c.4 0 .73-.16.72-.37 0-.57-.91-1.86-1.1-1.6l-1.54 2.1c-1 1.39-1.18 1.86-.62 1.86a1.84 1.84 0 0 0 1.26-1.01Zm-9.43-.79c0-.11-.34-.2-.74-.2s-.75.22-.75.48.33.35.75.2.74-.38.74-.48Zm20.59.3a1.16 1.16 0 0 0-.9-.49c-.36 0-.29.2.15.49.93.59 1.09.59.72-.02Zm-16.94-1.74c-.3-.78-1.17-1-1.17-.33a1.18 1.18 0 0 0 1.06 1.08c.22 0 .27-.34.11-.75Zm-2.16-2.23c-1-.63-1.63-.63-1.24 0a1.47 1.47 0 0 0 1.14.49c.69-.03.75-.09.07-.51Zm18.85-.28c0-.12-.22-.22-.5-.22a.51.51 0 0 0-.49.53c0 .29.22.39.49.22s.47-.43.47-.55Zm-32.25-.22a3 3 0 0 0-1.24-.48c-.27 0-.16.22.25.48a2.92 2.92 0 0 0 1.24.48c.24-.02.13-.23-.28-.5Zm3.57-.6c-.36-.35-.59-.38-.59-.07 0 .66.59 1.24.92.92.15-.17-.04-.55-.36-.87Zm-1.09-1.19c0-1-.65-.85-1.34.25-.51.81-.46.91.38.69.53-.16.93-.58.93-.96Zm8.92.43a4.08 4.08 0 0 0-.75-1.34c-.71-.94-.72-.94-.72 0a2.38 2.38 0 0 0 .32 1.34c.42.46 1.13.44 1.12-.02Zm16.19-1c-.17-.17-.29.12-.26.65s.14.7.3.3a1 1 0 0 0 0-.95Zm3.4-1.6c1.15-2.21.91-2.76-.48-1.11s-1.59 2.57-.76 2.57c.27 0 .83-.65 1.24-1.46Zm-11.88.07c-.15-.15-.55 0-.88.34-.49.49-.43.55.27.28.46-.19.74-.47.55-.62Zm-9.68-.1a.5.5 0 1 0-.5.5.49.49 0 0 0 .47-.5Zm15.73-5.95c.94-1.22 1.86-2.12 2-2s.4-.12.49-.56c.31-1.61-1.34-1.45-2.2.2a15.37 15.37 0 0 1-1.86 2.75l-2 2.24c-2.25 2.47-2.35 2.92-.3 1.3a26.85 26.85 0 0 0 3.88-3.95Zm-25.16 4.56a3.2 3.2 0 0 0-.64-1.27c-.59-.82-.63-.81-.63.11 0 .55.17.89.39.75s.39 0 .39.29.11.52.25.52.24-.18.24-.4Zm7.77-.51a3 3 0 0 1-.82-1.48.55.55 0 0 0-.5-.59.57.57 0 0 1-.49-.63c0-.6-5.1-5.81-5.69-5.81-.76 0-.12 1.4.89 1.94a6.78 6.78 0 0 1 2.08 2.19c1.06 1.8 4.23 5.21 4.9 5.26.25 0 .08-.38-.37-.88Zm17.05-.34c.86-1 .22-1.53-.74-.58a2.9 2.9 0 0 0-.76 1.05c0 .55.88.28 1.5-.47Zm-22.25-.84c-.35-.35-.58-.38-.58-.07 0 .66.58 1.24.92.92.14-.15-.04-.52-.37-.85Zm24.72.57c0-.28-.22-.38-.5-.21s-.49.4-.49.52.22.22.49.22a.51.51 0 0 0 .47-.53Zm6.7-1a1.19 1.19 0 0 0-.9-.49c-.36 0-.29.2.15.49.92.66 1.09.66.72.04Zm-18-.36a.9.9 0 0 1-.62-.71 2.91 2.91 0 0 0-1.89-1.91c-.26 0-.58.43-.72.94-.19.73 0 .87.77.66s.94-.08.73.5 0 .76 1 .75c.73 0 1-.11.7-.23Zm-3.12-3.48a10.86 10.86 0 0 0-2-2.35 11.69 11.69 0 0 1-2.26-2.87c-.2-.65-.59-.82-1.36-.6s-.88.17-.41-.17c1-.66.31-2.31-1.17-3-1.08-.49-1.23-.44-1.23.39a1.66 1.66 0 0 1-.5 1.26c-.91.56-.5 2 .71 2.54.85.4 1.36.41 1.74 0s.53-.34.53.1.21.49.47.33.73.2 1 .8c1.06 2 4.47 4.7 4.41 3.53Zm-8.66-6.08c-.17-.27-.07-.5.22-.5a.51.51 0 0 1 .52.5c0 .27-.09.5-.21.5s-.38-.19-.55-.46Zm26.05 3.47a.5.5 0 0 0-1 0 .5.5 0 0 0 1 0ZM188.68 169a15.74 15.74 0 0 0 1.49-1.48c.45-.53 1.74-2 2.85-3.23 2.35-2.62 3-4 1.93-4-.4 0-.73.17-.73.37a5.88 5.88 0 0 1-1.11 1.74c-.62.75-1.68 2.07-2.36 2.94s-1.85 2.23-2.6 3.05c-1.9 2-1.65 2.33.53.61Zm9.61-1.45c-.21-.8-.06-.92.85-.68s1 .17.5-.43-.72-.6-1.52.12a2.68 2.68 0 0 0-.92 1.5c0 .37-.22.66-.5.66a.48.48 0 0 0-.49.45c0 .25.52.2 1.16-.1.82-.38 1.1-.84.92-1.52Zm8.32-1.56c0-.27-.22-.15-.48.25a2.92 2.92 0 0 0-.48 1.24c0 .28.21.17.48-.24a3 3 0 0 0 .48-1.24Zm-21.81-.72c0-.65-3.91-4.71-4.23-4.39-.48.48.34 1.85 1.3 2.15 1.18.38 1.22 1.08.08 1.38-.49.13-.15.26.74.29s1.61.28 1.61.55.11.5.25.5.25-.22.25-.48Zm11.41-1c0-.62-.53-.62-1.49 0-.6.39-.53.48.37.49.61.03 1.12-.19 1.12-.46Zm-23.81-.71a4.78 4.78 0 0 1 .3-1c.22-.58.09-.71-.5-.48-.87.33-1.11 1.68-.3 1.68.27 0 .5-.09.5-.21Zm15.1-1.32a.55.55 0 0 0-.76-.17.54.54 0 0 0-.18.75.55.55 0 0 0 .94-.58Zm10.2 0c0-.27-.34-.49-.75-.49s-.74.22-.74.49.33.5.74.5.75-.15.75-.43Zm-33.55-.76c.17-.45.09-.64-.19-.47a1 1 0 0 0-.49.77c0 .78.35.62.68-.23Zm7.75 0a10.39 10.39 0 0 0-1.47-2.15c-1.23-1.56-1.7-1.82-3-1.63-1.67.25-1.58 1.09.13 1.12 1.11 0 2.82 1 2.82 1.69 0 .2-.67.21-1.49 0-1.36-.3-2.22.42-1 .83 1 .32 4 .4 4 .11Zm6.45-.2a.51.51 0 0 0-.53-.49c-.28 0-.38.22-.21.49s.4.5.52.5.22-.19.22-.46Zm20.6-.89c-.15-.15-.55 0-.89.34-.49.49-.43.54.28.27.49-.14.76-.42.66-.57Zm-23 .06a1 1 0 0 0-1 0c-.16.16.13.28.65.26s.69-.14.3-.3Zm2.86-1.76a6.21 6.21 0 0 1-1.46-2.12c0-.29-.45-.84-1-1.24a2.29 2.29 0 0 1-1-1.35.7.7 0 0 0-.74-.64c-.41 0-.75.23-.75.5a.53.53 0 0 0 .55.5c.3 0 .42.33.27.74s-.06.74.22.74a9.44 9.44 0 0 1 2.54 2.24 11.14 11.14 0 0 0 2.42 2.23c.22 0-.26-.72-1.06-1.6Zm19.37-.65c0-.13-.44-.23-1-.23s-1 .33-1 .76c0 .61.2.66 1 .23.57-.25 1.01-.59 1.01-.72Zm-23.31 0c-.69-.37-1.35-.58-1.48-.45-.33.33 1.08 1.16 2 1.15.41.07.21-.22-.51-.62Zm14.88-.55a7.93 7.93 0 0 1 1.91-2.69c1.06-1.1 1.72-2.14 1.48-2.29-.68-.42-5.37 4.76-5.37 5.92 0 .13.44.12 1 0s1-.56 1-.92Zm11.11-.77c.74-1.48.73-1.53-.21-1.28-.63.16-1 0-1-.44s.47-.7 1-.7c.74 0 1-.23.76-.76a2 2 0 0 1 .69-1.73 3.28 3.28 0 0 0 1-1.54c0-.31-.89.41-2 1.61a10.91 10.91 0 0 0-2 2.62 3.32 3.32 0 0 1-.86 1.41c-.82.91-.81.93.46.61s1.28-.2 1.05.71c-.41 1.53.15 1.26 1-.51Zm6.45-2.38c-.33-.34-2.18 1.83-2.18 2.54 0 .35.54 0 1.19-.85s1.12-1.5 1-1.61Zm-38.71 1.69c-.31-.78-1.18-1-1.18-.32a1.17 1.17 0 0 0 1.06 1.07c.22 0 .27-.34.12-.75Zm14.29.41a1 1 0 0 0-.94 0c-.17.16.12.28.65.26s.69-.14.29-.3Zm-9-.6c0-.24-.44-.55-1-.7-1-.25-1.31.16-.66.81.48.56 1.65.49 1.65-.03Zm9.38-2c-.66-.79-2.2-2.68-3.42-4.19a16.12 16.12 0 0 0-2.59-2.78c-.59 0 .23 1.63 1.45 2.95a4.21 4.21 0 0 1 1.14 2.11c0 .48.28.87.63.87a4.75 4.75 0 0 1 2 1.56c1.7 1.94 2.41 1.47.79-.52Zm13 .45a.51.51 0 0 0-.53-.5c-.28 0-.38.22-.22.5s.41.49.53.49.16-.13.16-.4Zm-24.06-1.49a1.09 1.09 0 0 0-.78-.5.49.49 0 0 0-.46.5c0 .27.34.49.77.49s.58-.13.41-.4Zm.79-1.8c-.76-1-2.06-2.6-2.88-3.51a12.55 12.55 0 0 1-1.82-2.49c-.23-.55-.35-.28-.37.79 0 .89.12 1.56.33 1.49.55-.19 2.74 2.14 2.37 2.51-.17.17-.53 0-.78-.42-.42-.64-.53-.63-1 0a2.84 2.84 0 0 0-.44 1.48c0 .53.12.49.28-.12.28-1.06 1.15-1.15 1.15-.12 0 .41.22.74.49.74s.5-.33.5-.74c0-1.22.7-.85 1.41.74.36.82 1 1.49 1.38 1.49s.34-.53-.66-1.86Zm21.74 1.41a.75.75 0 0 1-.34-1c.48-.78 1.27-.68 1.27.17a1.34 1.34 0 0 0 .49 1c.34.2.34-.13 0-1-.58-1.54-.62-2.24-.12-2.16 1.19.18 2.61-.14 2.61-.59s-.43-.4-1-.2c-1 .32-1 .3-.14-1 .48-.74 1-1.1 1.07-.79a1.06 1.06 0 0 0 1 .56c.63 0 .67-.13.21-.59-.9-.9-.75-2.88.22-2.88a2.06 2.06 0 0 0 1.42-.84c.5-.68.48-1-.09-1.55-.83-.83-1.36-.29-3.24 3.34a24.07 24.07 0 0 1-3 4.21c-1.95 2.14-2.27 3-1.25 3.44s1.65.37.93-.09Zm-30-2.26c-.33-.86-1.81-1.05-1.81-.24 0 .29.22.39.5.22s.64.05.8.49.42.68.56.55a1.26 1.26 0 0 0-.05-1Zm25.72-.58c-1-.73-1-.75 0-1.45.72-.54.78-.72.23-.72-1.73 0-2.12 1.66-.79 3.37.37.47.65.5 1 .09s.27-.75-.45-1.29Zm-7.44-.68c.17-.27 0-.5-.46-.5s-.78.23-.78.5a.49.49 0 0 0 .47.5 1.07 1.07 0 0 0 .81-.47Zm-19.1-.53c0-.28-.2-.4-.44-.24s-.8-.62-1.24-1.71-1-2-1.24-2c-.55 0-.12 1.89.5 2.28a1.64 1.64 0 0 1 .5 1.31.87.87 0 0 0 1 1c.52-.08 1-.31 1-.61Zm25.24-1.57c2-2 2-2.08 2.74-1.12s.75.93 1.06.12c.22-.57.87-.87 1.89-.87h1.55l-1.6-1.12c-1.84-1.28-2-1.86-.62-1.86.55 0 1-.22 1-.49a.51.51 0 0 0-.5-.5.5.5 0 0 1-.5-.49c0-.28.32-.5.7-.5a1.11 1.11 0 0 0 1-.62c.19-.48.26-.47.29 0s.26.51.53.34c.71-.43.62-2.23-.12-2.24-.42 0-.37-.16.12-.49s.56-.48.11-.48-.78.38-1 .86a5.48 5.48 0 0 1-1.48 1.77 2.92 2.92 0 0 0-1.14 1.5 19.45 19.45 0 0 1-3.21 4.06c-2.88 3.1-3.67 4.2-3 4.2a16.9 16.9 0 0 0 2.21-2.1Zm2.6-2.3c.21-.63.94-.77.94-.19 0 .21-.26.46-.56.56s-.44-.03-.34-.34Zm-27.34 1.42a.51.51 0 0 0-.53-.49c-.29 0-.38.22-.22.49s.41.5.53.5.26-.19.26-.47Zm20.59 0c-.17-.27-.41-.49-.53-.49s-.22.22-.22.49a.51.51 0 0 0 .53.5c.33.03.43-.19.26-.47ZM166 145.2c0-.41-.23-.75-.5-.75s-.5.34-.5.75.23.74.5.74.5-.33.5-.74Zm10.41 0c0-.42-.22-.63-.5-.46a.56.56 0 0 0-.22.77c.38.66.67.49.67-.34Zm5.72-.19a.85.85 0 0 0-1-.2c-1 .39-.9.67.3.67.49-.03.81-.25.65-.48Zm24.55-.74a2.17 2.17 0 0 1 1-1.39c1.09-.76 1.94-3.36 1.1-3.36-.3 0-.41.34-.25.75s.06.67-.4.49-.94.36-1.42 1.61c-.4 1-.8 2-.89 2.24s.07.37.37.37a.65.65 0 0 0 .54-.71Zm-31.46-1.14c-.5-.5-.91.26-.5.93.31.5.43.5.59 0a1 1 0 0 0-.09-.93Zm9.13.86a.52.52 0 0 0-.52-.5c-.29 0-.39.23-.22.5s.4.49.52.49.17-.25.17-.48Zm-.87-1.32a1 1 0 0 1-.62-.76 5.5 5.5 0 0 0-1.19-1.93 9.36 9.36 0 0 1-1.4-2c-.14-.42-.57-.27-1.38.48s-1 1.09-.39 1.09c1.07 0 3.39 1.94 2.38 2-.46 0-.27.31.5.74a5 5 0 0 0 2 .69q.75 0 .12-.27Zm7.52-5.33c1.44-1.62 1.66-2.39.55-2-.42.15-.75.48-.75.73a3.33 3.33 0 0 1-.87 1.38c-2.7 2.93-3.1 3.52-3.1 4.56s.25.77 1.44-1.06a35.68 35.68 0 0 1 2.73-3.64ZM172.4 142a.5.5 0 1 0-.5.5.5.5 0 0 0 .5-.5Zm.93-1.61c-1.29-2.56-1.69-3-1.23-1.36a24.11 24.11 0 0 1 .75 3.1c0 .21.38.37.8.37.62-.03.57-.39-.32-2.14Zm16.83.36c-.15-.15-.43.12-.62.61-.27.71-.21.77.28.28s.49-.77.34-.92Zm15.47.52a3.85 3.85 0 0 0-.81-1c-.66-.66-.9-.69-1.23-.16-.62 1-.53 1.3.43 1.52s1.61-.01 1.61-.39Zm-34-.75a1 1 0 0 0-.77-.5.48.48 0 0 0-.47.5c0 .27.35.49.78.49s.65-.25.48-.52Zm31.5-1.52a.51.51 0 0 0 .52-.5c0-.73-1-.6-1.82.26a1.55 1.55 0 0 0-.41 1.62c.3.75.39.72.78-.27.23-.61.66-1.11.93-1.11Zm-11.39.22c0-.43-.22-.64-.49-.47a1.06 1.06 0 0 0-.5.77.48.48 0 0 0 .5.47c.27.01.49-.35.49-.77ZM177.36 138a.5.5 0 1 0-.5.49.5.5 0 0 0 .5-.49Zm-6-.5a.5.5 0 1 0-.49.5.5.5 0 0 0 .53-.49Zm-7.26-.38c.33-.54-1.1-2.34-1.52-1.91-.21.2-.07.55.3.78a.72.72 0 0 1 .33 1c-.19.3-.12.55.16.55a1 1 0 0 0 .77-.41Zm15.2-.12c.52-.33.55-.48.11-.48s-.85-.59-1.12-1.3c-.34-.89-.74-1.2-1.27-1a4.31 4.31 0 0 1-1.23.3.49.49 0 0 0-.46.51c0 .29.53.38 1.24.19 1.19-.3 1.56.26 1.3 1.91-.08.51.53.45 1.43-.13Zm14.26-.78c-.47-.47-1.42.29-1.07.86.22.34.49.33.85 0s.43-.68.26-.85Zm-17 .29c.17-.28.09-.5-.19-.5a1.07 1.07 0 0 0-.8.5c-.17.27-.08.49.19.49a1.09 1.09 0 0 0 .85-.48Zm23.57-3.87c0-.59.14-.6.75-.09 1.13.94 1.29.39.26-1l-.89-1.19-.35 1.08c-.18.59-.55 1.07-.8 1.07s-.46.34-.46.74-.22.75-.5.75-.49.37-.49.83c0 .68.21.63 1.24-.34a3.87 3.87 0 0 0 1.29-1.84Zm-17.37 1.08c0-.55.31-.72 1-.55a1.66 1.66 0 0 0 1.58-.47c.52-.64.35-.69-1.35-.36a16.09 16.09 0 0 1-2.79.32c-.78 0-.78 0 0 .87 1.09 1.16 1.61 1.23 1.61.2Zm-13.8-.79c-.35-.36-.58-.39-.58-.07 0 .66.58 1.24.91.91.19-.13.05-.51-.29-.83Zm6.86.1a.51.51 0 0 0-.53-.49c-.28 0-.38.22-.22.49s.41.5.53.5.27-.21.27-.53Zm1.31-.24a11.52 11.52 0 0 1-1.62-2.73c-.89-2-1.37-2.39-1.92-1.5-.17.28.26 1.06 1 1.76a12.32 12.32 0 0 1 1.78 2.23c.29.54.75 1 1 1s.22-.34-.19-.75Zm13.82-.75a1.07 1.07 0 0 0-.77-.49.48.48 0 0 0-.47.49c0 .27.35.5.77.5s.69-.22.52-.49Zm-8-.49a1.34 1.34 0 0 0 1.41-1c.19-.77 0-1-.83-1s-1.43-.57-2-1.59-1-1.32-1.15-1a1.65 1.65 0 0 0 .38 1.32 1.18 1.18 0 0 1 .27 1.23c-.18.28.19.73.82 1 1.13.45 1.12.46-.21.49-.76 0-1.37.28-1.37.58s.36.41.79.25a6.39 6.39 0 0 1 1.92-.3Zm11.68-.56c0-.27-.22-.36-.5-.19a1.09 1.09 0 0 0-.49.81c0 .27.22.35.49.18a1.06 1.06 0 0 0 .59-.79Zm1.47-2.15a1.59 1.59 0 0 0 .37-1.37q-.24-.63-.63.36a3.47 3.47 0 0 1-.93 1.35c-.37.26-.36.37 0 .38a2 2 0 0 0 1.28-.71Zm9.52-.57c-.62-.43-1.28-.65-1.46-.46-.4.39.86 1.24 1.87 1.25.56.02.41-.27-.32-.78Zm-32.7-2.27c-.4-1-.46-.91-.48.82 0 1.52.1 1.79.47 1.16a2.31 2.31 0 0 0 .01-1.98Zm30.23 2.06a.5.5 0 0 0-.52-.49c-.29 0-.39.22-.22.49s.4.5.53.5.21-.2.21-.48Zm-37-1a1.05 1.05 0 0 0-.77-.5.49.49 0 0 0-.47.5c0 .27.35.5.78.5s.67-.2.5-.47Zm21.59.29a5 5 0 0 1 .29-1c.44-1.14-.66-1-1.29.2-.42.8-.37 1 .23 1 .46.04.81-.06.81-.17Zm-10.19-.76c.16-.26-.14-.7-.67-1-.82-.44-.92-.37-.7.47.32 1.1.93 1.33 1.41.56Zm7.67.1a7.62 7.62 0 0 0-.46-1.37c-.38-.87-.43-.83-.46.38 0 .75.19 1.36.46 1.36s.52-.13.5-.34Zm-4.93-1a2 2 0 0 0-.74-1.06c-.61-.5-.74-.48-.74.1a1.76 1.76 0 0 0 .32 1.06c.51.45 1.2.39 1.2-.12Zm17.86-.62c0-.28-.22-.36-.49-.19a1.07 1.07 0 0 0-.5.8c0 .28.22.36.5.19a1.09 1.09 0 0 0 .53-.82Zm1.86 0a1.89 1.89 0 0 1-.86-1.48c0-.53-.23-1-.5-1a.5.5 0 0 0-.5.5c0 .94 1.23 2.47 2 2.46.61.03.59-.08-.1-.48Zm-28.15-.13c0-.62-1.65-3.31-2-3.31-.7 0-.55.8.28 1.49a2.08 2.08 0 0 1 .75 1.31c0 .37.22.68.49.68s.52-.11.52-.17Zm19.47-2.61a9 9 0 0 0 1.33-3.08c0-.63-.14-.57-.46.24a10.76 10.76 0 0 1-1.58 2.39c-1.4 1.6-2.11 3.29-1.25 2.93a8.85 8.85 0 0 0 2-2.48Zm-13.46 0c-.43-.74-.53-.76-.54-.12 0 .84.51 1.59.86 1.24.15-.16.01-.66-.28-1.16Zm-.92-1.82c-.14-.24-.45-.13-.68.25-.66 1-.52 1.52.25.81.42-.39.61-.87.48-1.1Zm6.32 1.16a.51.51 0 0 0-.53-.49c-.28 0-.38.22-.21.49s.4.5.52.5.26-.26.26-.54Zm17.81-1.52c.63 0 .65-.1.09-.32-.71-.28-2.05 1.11-2 2.06 0 .29.27 0 .57-.59a2 2 0 0 1 1.38-1.19Zm1.13 1.19a1 1 0 0 0-.95 0c-.16.16.13.28.65.25s.74-.13.34-.29Zm-17.57-2.07a4.64 4.64 0 0 0-1.48-3.09 16.72 16.72 0 0 1-2.37-3.54c-.46-1-1-1.73-1.15-1.55-.51.5.21 2.71 1.22 3.71a3 3 0 0 1 .92 1.55c0 .34.5.84 1.12 1.12a2 2 0 0 1 1.16 1.48c.12 2.2.52 2.43.58.32Zm-2.61-1.07c-.17-.27-.41-.49-.53-.49s-.21.22-.21.49a.51.51 0 0 0 .52.5c.33-.04.43-.27.26-.54Zm12.15-.49a.49.49 0 0 0-.49-.5.5.5 0 1 0 0 1 .49.49 0 0 0 .53-.54Zm3.16-.18c.71-.37 1.18-.78 1-.93-.34-.34-2.69.73-2.7 1.23s.26.44 1.74-.34Zm9.74.18a.5.5 0 1 0-.49.49.49.49 0 0 0 .53-.53Zm-31.75-.47a1.07 1.07 0 0 0-.49-.78c-.28-.16-.5 0-.5.47s.22.78.5.78a.48.48 0 0 0 .53-.51Zm-2.48-.53a.5.5 0 1 0-.49.5.49.49 0 0 0 .53-.54Zm-5.45-1.27c0-.12-.35-.22-.78-.22s-.63.23-.46.51c.32.42 1.28.2 1.28-.33Zm28.6-.69c0-1.27-.08-1.36-.6-.66a1.46 1.46 0 0 0 .56 2.11 11 11 0 0 0 0-1.45Zm-21.66.44c0-.79-1.48-2.12-1.88-1.69-.2.21-.07.47.27.59a1.05 1.05 0 0 1 .62.93c0 .39.22.7.49.7a.51.51 0 0 0 .54-.57Zm24.81-1.73c0-.12-.35-.22-.78-.22s-.63.23-.46.51c.32.41 1.28.19 1.28-.33Zm-9.7-1.08a4.82 4.82 0 0 0 1.26-2 2.48 2.48 0 0 1 .75-1.41 2.5 2.5 0 0 0 .74-1.28c0-.29-.56.11-1.24.91a5.25 5.25 0 0 0-1.24 2.08 4.1 4.1 0 0 1-1.32 1.86c-.71.67-1.06 1.22-.76 1.22a4.46 4.46 0 0 0 1.85-1.42Zm-23.54.12c0-.41-.23-.75-.5-.75s-.5.34-.5.75.23.74.5.74.59-.37.59-.78Zm12.65.24a1.07 1.07 0 0 0-.78-.49.48.48 0 0 0-.46.49c0 .28.34.5.77.5s.68-.24.51-.54Zm14.69-1.54c1.17-1.17 1.19-1.93.06-1.93s-2.11 1.61-1.74 3c.19.71.4.9.53.49a5.57 5.57 0 0 1 1.19-1.6Zm7.72-1c-.87-.5-.87-.54 0-1.19s.86-.71 0-1.55-.87-.77-.87-.1c0 .42-.51 1-1.12 1.31-.81.39-.89.55-.3.57a1.34 1.34 0 0 1 1.1.78 1.28 1.28 0 0 0 1.17.72c.91-.05.91-.08.05-.58Zm-24.61-1.2c0-.4-.08-.74-.19-.74s-.33.34-.48.74-.07.75.19.75.48-.33.48-.75Zm2.49-.24a.5.5 0 1 0-.5.49.5.5 0 0 0 .5-.51Zm-2-4.09c0-.21-.22-.38-.5-.38a.49.49 0 0 0-.49.5.49.49 0 0 1-.5.49c-.27 0-.49-.37-.49-.84 0-.62-.15-.7-.56-.29-.9.9.54 2.14 1.66 1.44.49-.32.89-.73.89-.94Zm7.94.36c-.35-.42-.75-.65-.89-.52s0 .6.39 1 .75.66.89.52-.03-.59-.38-1.03Zm2.73-.24c-.17-.28-.41-.5-.53-.5s-.22.22-.22.5a.51.51 0 0 0 .53.49c.3-.02.4-.28.23-.51Zm7.19-1.24c0-.41-.22-.75-.5-.75s-.49.34-.49.75.22.74.49.74.51-.35.51-.76Zm2.48.27c0-.58-1-1.21-1.32-.85s.27 1.32.85 1.32a.47.47 0 0 0 .48-.49Zm-13.89-.52a.5.5 0 1 0-1 0 .5.5 0 0 0 1 0Zm-7.44-1.77c0-.12-.22-.22-.5-.22a.51.51 0 0 0-.49.53c0 .29.22.39.49.22s.51-.42.51-.55Zm19.84 0c0-.4-.22-.74-.49-.74s-.5.34-.5.74.22.75.5.75.5-.28.5-.74Zm-25.29-2.22c-.62-1.48-1-1.79-1.65-1.18-.32.32 1.74 3.15 2.08 2.86a3.66 3.66 0 0 0-.42-1.67Zm4.46.73a.75.75 0 0 0-.75-.74c-1.29 0-.87 1 .62 1.45.08.03.14-.28.14-.7Zm6-.8c0-.27-.22-.36-.5-.19a1.09 1.09 0 0 0-.49.81c0 .27.22.35.49.18a1.07 1.07 0 0 0 .46-.79Zm16.62-.19c0-1.26-1.06-1.49-1.38-.3a1.86 1.86 0 0 0 0 1.28c.53.54 1.34-.03 1.34-.97Zm-18.11-.25a.51.51 0 0 0-.52-.49c-.29 0-.39.22-.22.49s.4.5.52.5.18-.21.18-.49Zm10.85-3.22a8.25 8.25 0 0 0 1.73-2.85c.19-.76.68-1.37 1.11-1.37s.69-.26.58-.58c-.35-1.06-1.28-1-1.63.11-.63 2-3.19 5.59-4.52 6.33-.5.28-.67.71-.42 1.1s.55.41 1.07-.31c.38-.52 1.31-1.61 2.08-2.43Zm-4.4-2.12A4.8 4.8 0 0 0 185 92c-.6-.86-.95-1-1.44-.59s-.76.39-1 0c-.48-.77-1.25-.63-1.25.22a.86.86 0 0 0 .95.74c1.4 0 2.55.54 2.24 1-.15.24.07.44.5.44s.77-.17.77-.38Zm9.38-.43c.12-.1-.49-.18-1.36-.18s-1.57.23-1.57.51c-.04.45 2.3.19 2.89-.36Zm-11.74-3.36c.07-.25-.15-.55-.49-.66s-.62.16-.62.62c-.04.85.85.88 1.07.05Zm11.29-1.28c0-.55-.1-1-.23-1s-.47.45-.76 1c-.43.79-.38 1 .23 1 .39 0 .72-.39.72-.99Zm-6 0c.4-.26.56-.63.36-.83s-.74 0-1.21.46c-.91.98-.37 1.16.84.4Zm-7.1-.8c.41-1.05-.74-4.79-1.34-4.42s-.58 3.21.15 3.22c.44 0 .43.12-.07.44a.75.75 0 0 0-.34 1c.48.8 1.25.69 1.59-.21Zm4.14-1.21a.52.52 0 0 0-.53-.5c-.28 0-.38.23-.21.5s.4.5.52.5.21-.2.21-.47Zm10.91-.26c-.92-1.09.76-2.88 2.34-2.48.62.15 1.14.08 1.14-.17 0-.64-2.63-1.06-3.27-.52a.76.76 0 0 1-.95.08c-.22-.2-.27 0-.09.5s.06.87-.42.87-.73.44-.73 1 .24.89.74.69.75 0 .75.27a.55.55 0 0 0 .55.55c.39 0 .37-.24-.07-.76Zm-11.23-1.48c.16-.4.1-.74-.14-.74s-.56.34-.71.74-.09.75.14.75.54-.3.7-.72Zm6.07-2.1c-.39-.39-2.28 1.12-2.28 1.82 0 .49.38.35 1.24-.45.67-.61 1.14-1.23 1.03-1.34Zm-11.61-1a1 1 0 0 0-1 0c-.16.16.13.28.65.26s.73-.03.34-.19Zm6.45 0a1 1 0 0 0-.95 0c-.17.16.13.28.65.26s.68-.03.29-.19Zm-.09-2.89c0-.68-.19-1.24-.42-1.24s-.4 1.53-.12 2.36c.18.66.53-.05.53-1.05Zm-10.75-.45a.74.74 0 0 0-.39-1c-.45-.17-.58 0-.41.71.25 1.25.43 1.29.79.36Zm8.85-1.39c-.36-.36-.58-.39-.58-.07 0 .66.58 1.24.91.91.13-.07-.02-.45-.34-.77Zm11 .27a1.88 1.88 0 0 0-1.24 0c-.34.14-.06.25.62.25s.9-.04.56-.18Zm-8.83-2.27a23.27 23.27 0 0 0-1-2.66 5 5 0 0 1-.58-2c.22-.75-1.09-2.73-1.59-2.42-.88.54-.54 1.72.39 1.37.74-.28.78-.21.23.47s-.49.89-.1 1.13.38.53.05.92c-.88 1.05.13 3.88 1.23 3.45.38-.14.7 0 .7.29a.54.54 0 0 0 .53.54c.32 0 .39-.44.19-1.11Zm-2.2-1.49c0-.9.1-1 .49-.37.62 1 .62 1.48 0 1.48-.33.07-.56-.43-.55-1.04Zm4.87-.3c1.27-1.1 1.35-1.85.31-2.89s-.7-1.8.8-2.15c.81-.19 1-.4.58-.67a2.24 2.24 0 0 0-1.57-.08c-.78.25-1 .87-1.11 3.51-.09 1.76-.15 3.2-.12 3.2s.47-.35 1.05-.85Zm8-.57c0-.54-.09-1-.19-1s-.31.45-.45 1-.06 1 .19 1 .41-.38.41-.93Zm-9.94-3.72c0-.27-.21-.16-.48.25a3.06 3.06 0 0 0-.48 1.24c0 .27.22.16.48-.25a2.92 2.92 0 0 0 .44-1.17Zm.43-5.06c-.47-.56-.46-.8 0-1.11.78-.48.53-3.6-.32-3.88s-.83 1.46-.08 2.2c.39.39.26.74-.46 1.24s-.86.94-.62 1.33a3.55 3.55 0 0 1 .19 2l-.21 1.34 1-1.18c.83-1 .92-1.33.44-1.9Zm5.68 1.49c-.23-.61-.38-.66-.54-.19s-.31.5-.58 0-.45-.37-.67.2 0 .78.91.78 1.13-.19.88-.84Zm-2.14-6.91a1.79 1.79 0 0 0-.7-1.15c-.58-.49-.8-.27-1.22 1.26a4.68 4.68 0 0 0-.21 2.33c.29.6 2.08-1.45 2.09-2.37Zm-4.46-2.08c0-.63-.19-1-.42-.87-.53.32-1 2.63-.65 3s1.06-.87 1.07-2.14Zm-2.17-.45c-.51-.51-.92.25-.51.92.32.51.44.51.6 0a1 1 0 0 0-.09-.92Zm9.46-.76c-.14-.34-.26-.06-.26.62s.12 1 .26.62a2 2 0 0 0-.04-1.2Zm-5.54-5.33c-.17-.27-.41-.5-.53-.5s-.21.23-.21.5a.51.51 0 0 0 .52.5c.25.07.35-.13.18-.43Zm1.24-2a.51.51 0 0 0-.53-.5c-.28 0-.38.22-.21.5s.4.49.52.49.18-.12.18-.4Zm4.2 203.28c1.91-1.8 4.77-4.43 6.37-5.84a36.58 36.58 0 0 0 8-10.2c1.4-2.58 2.25-3.28 2.25-1.86 0 1.23.94.84 1.29-.52.41-1.65.13-1.86-1.6-1.15a11.49 11.49 0 0 1-3.41.64c-1.5 0-2 .26-2.15 1s0 .8.37.58a.6.6 0 0 0 .26-.8c-.15-.26.29-.46 1-.46s1.27.24 1.27.55-.33.42-.74.26-.68-.11-.62.08c.14.45-.82 1.63-3.06 3.72-3.6 3.38-4.75 4.32-5.32 4.32a1 1 0 0 0-.81.62c-.16.48-.38.47-1-.06s-.86-.51-1.14.86c-.16.86-.5 1.55-.73 1.55-.62 0-1.93 2.75-1.94 4.07 0 .75-.14.93-.4.53s-.62 0-1.08 1.24a7.68 7.68 0 0 0-.6 2.22c.06.21-.12.38-.39.38-.88 0-.54-1.4 1.29-5.33 1.91-4.11 4.66-12.12 4.66-13.58a1.51 1.51 0 0 1 .46-1.16 10 10 0 0 0 .79-3.63c.18-1.83.4-3.79.5-4.33a29.38 29.38 0 0 0 .2-3.14c0-1.5.23-2.09.66-1.94a.94.94 0 0 1 .47 1 .63.63 0 0 0 .62.81c.42 0 .77.22.77.5s.34.49.77.49c.58 0 .69-.28.43-1.11a3.17 3.17 0 0 0-.74-1.37 13.48 13.48 0 0 1-.42-4.22c0-2.18-.16-5.36-.29-7.06-.14-1.88-.05-3.1.25-3.1s.51.39.53.86.14.62.29.25a1 1 0 0 1 .76-.62c.28 0 .38.34.22.75s0 .74.72.74.87.22.7.5 0 .49.42.49c.85 0 .58-1.57-.56-3.35-1-1.6-.38-11.74.73-11.32a4.61 4.61 0 0 0 1.42.29.45.45 0 0 1 .43.66 1.19 1.19 0 0 0 .41 1.22c.55.45 1.12.48 2.79.14.1 0-.07-.35-.39-.74s-.45-.78 0-1.07.15-.59-.72-1.05c-1.2-.62-1.31-.94-1.31-3.64 0-1.63.2-3 .44-3s.91-.71 1.46-1.59c.8-1.27.89-1.75.42-2.32s-.06-.42 1 .21c.88.52 1.61 1.14 1.61 1.39s-.34.18-.75-.17-.74-.4-.74-.11.33.63.74.79c1 .38 2.31-.15 1.93-.76-.16-.26.1-.9.58-1.43.77-.86.78-1 .07-1a1.65 1.65 0 0 0-1.24.81c-.31.55-.57.63-.8.25s-.09-.56.22-.56.48-.67.29-2-.12-2 .33-1.82.6.81.59 1.53.16 1.12.42.87.36-2.44.27-4.89c-.14-3.56-.06-4.28.43-3.64a4 4 0 0 1 .62 2 4.71 4.71 0 0 0 .49 2c.81 1.28 1.23.06.68-2-.67-2.54-.07-2.53 1.08 0a5.29 5.29 0 0 0 1.22 2c.53 0 2-3.5 1.73-4-.16-.26.07-.83.51-1.26 1.09-1.09.5-1.55-.65-.52-1 .87-1.57.65-1.57-.56 0-.54.16-.58.65-.18s.65 0 .67-1.93c0-2.9.35-3.39 1.69-2.55 1.16.73 2.06.05 3.51-2.66.66-1.22.73-1.71.28-1.93s-.11-.2.49-.09a2.82 2.82 0 0 0 2-.51c.82-.66.77-.7-.59-.45-1.67.31-2.4 0-2.89-1.38-.25-.67-.07-1 .68-1.18s.92-.54.75-1c-.48-1.24.22-1.65 1.44-.85s1.19.7 1.34-.1.3-.59.78.46c.54 1.21.51 1.34-.37 1.34-.55 0-1 .2-1 .45s.42.34.94.2a1.23 1.23 0 0 1 1.27.3 1.14 1.14 0 0 0 .8.54c.66 0 .57-1.67-.13-2.38-.47-.47-.47-.73 0-1.2.92-.91.73-2-.3-1.76-.63.16-.91-.07-1-.8s0-.86.34-.36.66.57 1.94 0c1-.46 1.36-.86 1.05-1.23s.14-1 1.36-2q2.81-2.24 2.52-.95a.59.59 0 0 1-.74.35c-.82-.19-.83.73 0 1.06.37.15.27.27-.24.29s-.87.28-.87.57.5.46 1.11.37 1.12-.49 1.13-.9.42-.71 1.24-.63 1 0 .61-.17a1.12 1.12 0 0 1-.62-.94 1.1 1.1 0 0 1 .62-.92c.42-.17.22-.6-.62-1.34l-1.24-1.08 1.12-.75a2.57 2.57 0 0 0 1.11-1.92c0-.65.23-1.18.5-1.18s.5-.34.5-.75-.23-.74-.5-.74a.48.48 0 0 1-.5-.45c0-.25.48-.32 1.08-.17.85.23 1 .11.76-.54s-.07-.83.41-.83a.74.74 0 0 1 .73.75.74.74 0 0 0 .74.74.81.81 0 0 0 .75-.87c0-.74.08-.75.5-.12s.48.57.41-.25v-2.9c.07-1.77.18-1.9 1.32-1.69.83.16 1.24 0 1.24-.44s-.44-.67-1-.67a1.67 1.67 0 0 1-1.27-.5c-.18-.29.41-.49 1.46-.49 2.1 0 2.3-.71.4-1.41-1.27-.47-1.28-.5-.23-.54a4.61 4.61 0 0 0 3.77-4c.06-1.53.55-2 .91-1 .29.88 2.29.85 2.62 0 .17-.43.07-.85-.22-1a.79.79 0 0 1-.53-.69c0-.31.29-.28.74.1.89.73 1.36-.15 1.44-2.69a4.16 4.16 0 0 1 .92-2.46c.83-.83.87-.82.87.18s.12.93.68.41a1.43 1.43 0 0 0 .42-1.39c-.14-.41-.08-.58.14-.37.51.46 1.9-1.45 1.51-2.08-.18-.29.29-.3 1.21 0s1.5.24 1.5 0-.28-.46-.62-.47-.28-.22.21-.5a3.35 3.35 0 0 0 1.14-1.92c.39-1.75 2.07-1.82 1.87-.08-.06.59.09 1 .35.8a3.7 3.7 0 0 0 .43-2.22c0-2.1.41-3.18 1.09-2.76s3.74-1.08 5.14-2.75c1.11-1.31 1.47-1.5 1.83-.94.25.39.45.48.45.21a2.94 2.94 0 0 0-.52-1.24c-.4-.56-.59-.59-.78-.12-.41 1-1 .71-1.28-.6-.21-1.09-.13-1.16.76-.69s1.16.33 1.9-.64a4.26 4.26 0 0 0 .91-2 1.64 1.64 0 0 1 2.07-1.48c.45.07 1.29-.47 1.87-1.21 1.16-1.47 2.1-1.21 1.14.32a4.25 4.25 0 0 0-.61 1.84c0 1.06-2.17 3.6-3.06 3.61-.51 0-.51.09 0 .4s.39.83-.37 2.31a4.76 4.76 0 0 0-.74 2.16c.29.29 2.23-1.88 2.23-2.5 0-.25.22-.32.5-.15s.49-.36.49-1.3c0-1.48.06-1.54.67-.7s.71.83 1.33-.57a4.28 4.28 0 0 1 2-2c1.08-.4 1.32-.77 1.15-1.69-.12-.64-.09-.9.07-.57.3.63 1.71.3 2.35-.56.23-.31.32-.12.22.46s-.47.92-.83.84-.54.15-.34.68c.45 1.16-1.51 4.12-3 4.48-.66.17-1.21.56-1.22.87 0 .66-3.6 3.39-6.1 4.66a6 6 0 0 0-2.5 2.58c-1 2.15-.56 2.64.85 1a2.73 2.73 0 0 1 1.94-1.08 1.06 1.06 0 0 0 1.17-.61c.17-.43.54-.78.83-.78.94 0 4.33-1.67 4.33-2.14 0-.24-.56-.31-1.24-.14s-1.24.11-1.24-.13c0-.64 2.66-1.08 3-.5.17.27.58.13.95-.3.54-.66.52-.74-.12-.49a1 1 0 0 1-1.14-.32c-.28-.44 0-.52 1.12-.3a2.56 2.56 0 0 0 2.1-.3c.47-.46.47-.3 0 .76-.34.74-.84 1.21-1.12 1s-.38 0-.18.54.14.73-.25.5-.47-.08-.23.52.14.81-.46.58-2.4.84-2.09 1.41-3.54 4.77-6.49 7c-1.75 1.33-3.57 2.79-4.05 3.24a6.3 6.3 0 0 1-2 1.18 4.41 4.41 0 0 0-3.13 2.82c-.71 1.41-.69 1.48.2 1.24a3.2 3.2 0 0 1 2.1.48l1.15.73-1.11-1c-1.48-1.3-1.43-1.49.62-2.34 1-.41 1.73-.95 1.73-1.22 0-.9 2-.53 2 .38 0 .69.09.72.32.12a2.48 2.48 0 0 1 .83-1.1c.33-.23.19-.37-.37-.38-1.84 0-.43-1.25 1.69-1.5 1.37-.15 2-.39 1.58-.64-1.24-.8-.58-1.24 1.49-1 1.15.14 1.95.09 1.75-.12-.44-.47 2.44-3.23 3.38-3.23 1.23 0 .74 1.85-1.13 4.31-2.19 2.88-3.06 3.6-4.39 3.62a2.52 2.52 0 0 0-1.72 1c-.4.55-.89 1-1.1 1-.9 0-2.78 2.15-2.54 2.91.19.58.13.63-.19.19s-.65-.47-1.41.11-.87.73-.11.74a2.68 2.68 0 0 1 1.66 1.24c.67 1 1 1.12 1.71.64s.89-.6-.05-1.26-.85-1.9.11-1.3a4.74 4.74 0 0 0 2.19 0c1.51-.28 1.63-.42 1.11-1.25s-.81-.82-1.45-.48-1 .28-1.14 0 .22-.57.81-.72c1.41-.37 4.36 1 4.37 2.09 0 .7.07.71.49.06.59-.9.64-1.63.1-1.3a1.12 1.12 0 0 1-1-.15c-.47-.28-.36-.47.39-.66.59-.15 1 0 1 .27s.34.11.77-.42c.61-.77.59-.61-.08.77-1.6 3.31-5 7.45-6.18 7.45-.31 0-.45.32-.29.72.22.6.06.63-.91.18s-1.54-.28-3.82 1.38c-1.45 1-3.2 2.42-3.88 3a7.54 7.54 0 0 1-2.11 1.4c-1 .33-1.13 1-.24 1.39.33.15.05.16-.65 0a7.05 7.05 0 0 0-3.79 1 12.89 12.89 0 0 1-3.29 1.27c-1.37 0-2.32.59-2.32 1.45a2.37 2.37 0 0 1-.59 1.43c-.32.32-.46.71-.29.87.42.43 2.37-.9 2.37-1.63 0-.35.16-.59.37-.55a15 15 0 0 0 3.26-1.29 8.86 8.86 0 0 1 4.34-1.15c1.09.15 1.45 0 1.45-.58 0-1.11 1.94-2.26 2.64-1.56a1.56 1.56 0 0 0 2.52-.25c.43-1.11.39-1.2-.44-1.2-.41 0-.75-.22-.75-.5a.47.47 0 0 1 .44-.49c.24 0 .56-.45.7-1a1.32 1.32 0 0 1 1.15-1c.69 0 .75.14.29.6-.89.89-.72 2.24.33 2.51 1.3.34 2.57-.49 2.2-1.44-.23-.61-.07-.72.71-.47s1.07.07 1.25-.6a1.29 1.29 0 0 1 1.17-.93c2.23 0 1.68 2-1.58 5.7-1.44 1.64-2.12 2.07-3 1.87-1.16-.3-3 1.22-2.34 1.93.21.21-.24.28-1 .15s-1.37-.11-1.37.07-5.51 4-6.94 4.69c-.42.18-2.09 1.12-3.72 2.09-3.86 2.26-8.06 4.2-8.78 4.05-.31-.07-1.07.77-1.69 1.86a20.68 20.68 0 0 1-1.65 2.58 2.1 2.1 0 0 0-.54 1.12.51.51 0 0 1-.49.52.5.5 0 0 0-.5.49c0 .27.45.5 1 .5s1-.33 1-.75.46-.74 1.22-.74c1.11 0 1.2-.15.92-1.54-.33-1.65-.26-1.66 2.82-.53.63.23.64.15.06-.56a2.25 2.25 0 0 0-1.49-.84c-.47 0-.13-.42.84-1s1.87-.85 2.16-.67.32.43-.07.68-.36.46.24.68.91 0 1.1-.7.51-.92 1.07-.69a13.46 13.46 0 0 0 1.79.56c.73.17.64 0-.37-.66-1.61-1.06-1.8-1.89-.35-1.51.65.17 1 .05.83-.31-.21-.63 2.7-1.81 4.51-1.82.69 0 1-.22.82-.54a.86.86 0 0 0-1-.27c-.42.16-.57 0-.4-.45s.42-.68.6-.66c.85.11 1.46-.15 2.48-1.1l1.1-1-.31 1.28c-.22.83-.12 1.27.27 1.27s.71-.45.86-1c.27-1 1.17-1.1 1.51-.07.11.32-.15.59-.59.59s-.78.22-.78.49c0 1 1.87.48 2.15-.59.16-.61.44-.95.62-.76s1-.11 1.72-.65a5.26 5.26 0 0 1 2.21-1 3.11 3.11 0 0 0 1.82-1.24c1.14-1.44 2.66-1.7 2.2-.37-.26.73-.18.71.44-.09.86-1.11 1-.67.23.75a1.76 1.76 0 0 1-1.27.95c-.42 0-.65.18-.51.39.3.5-2.47 3.55-4.58 5a8.06 8.06 0 0 1-3.61 1.28 5.45 5.45 0 0 0-2.84.92 3.31 3.31 0 0 1-1.21.81 18.48 18.48 0 0 0-2.94 1.67c-5.48 3.5-15.26 7.74-17.87 7.75-.48 0-.87.21-.87.47 0 .67 3 2.06 3.62 1.67.32-.19.14-.46-.43-.68-.82-.3-.75-.39.53-.68a11.36 11.36 0 0 0 2.35-.81c1.17-.63 1.37-.59 1.37.28 0 1.05 1.67.93 3-.24.8-.67 1-1.1.6-1.36s-.39-.54.07-1 .85-.42 1.56.15c.87.7.85.72-.32.41s-1.19-.28-.39.55 1 .77 1.58.26a1.28 1.28 0 0 1 1.24-.32.64.64 0 0 0 .84-.28c.2-.32.16-.47-.09-.34s-.84-.16-1.32-.67-.81-.93-.74-1 .77-.14 1.56-.23c2.21-.25 2.91-.65 2.6-1.46s.73-1 1.57-.14c.33.32.6.41.6.19s.35-.12.77.24.91.47 1.12.15c.5-.75 4.72-2.67 5.85-2.67a2.55 2.55 0 0 0 1.56-.57c.43-.43.77-.41 1.38.09s.88.56 1.31 0 .44-.53.2.19a7 7 0 0 0-.29 1.12c0 .11-.23.21-.51.21a6 6 0 0 0-2 1.71c-2.14 2.44-4.79 4-6.7 3.83a10.46 10.46 0 0 0-4.49 1.34c-2.87 1.43-8.71 4.18-12.07 5.67-2.52 1.12-10.09 3.55-10.67 3.44-.27-.06-1.09 1-1.81 2.37s-1.11 2.47-.87 2.47a.47.47 0 0 0 .45-.49c0-.28.33-.5.74-.5a.74.74 0 0 0 .74-.75c0-.41.45-.74 1-.74a2.21 2.21 0 0 0 1.61-1c.65-1 2.1-1.33 2.61-.5.41.67 2.24.64 2.24 0 0-.3-.43-.39-1-.22-1.5.49-1.2-.68.33-1.27 1-.36 1.43-.34 1.69.09a1 1 0 0 0 1.28.21c.88-.36.88-.39-.07-.82-.7-.31-.39-.36 1-.15a4.62 4.62 0 0 1 2.35.87c.24.38.36.28.37-.29 0-1.55 1.72-2.7 4.41-3 3-.29 3.55-.65 2.29-1.57-.88-.64-.88-.69.17-1.25s1.08-.46 1.08.65c0 1.76.88 2.06 1.58.54.46-1 .46-1.39 0-1.69s-.21-.55.65-.94c1.31-.59 1.67-.21.69.77a1.12 1.12 0 0 0 1.18 1.87 11.55 11.55 0 0 0 3.87-1.85c.15-.2.92-.19 1.71 0 1.43.38 1.43.39.47 1.34a25.64 25.64 0 0 1-8.31 5.75c-1.41.49-2.29.9-1.95.91a.72.72 0 0 1 .62.76c0 1-.3.94-1.48-.24s-2.84-1-7.45 1.13a104.22 104.22 0 0 1-11.41 4.4 9.24 9.24 0 0 0-2.26.95 5.42 5.42 0 0 1-2.24.56c-2.88.24-3.79 1.88-1 1.88a5 5 0 0 0 2.46-.78c1-.7 1.18-.7 1.91 0a1.6 1.6 0 0 0 1.61.48 14.94 14.94 0 0 1 1.67-.58c.47-.14.72-.48.55-.75s.26-.37 1.2-.16c1.4.31 2.61-.36 1.91-1.06-.25-.26-3.06.56-5.39 1.56-.54.23-.87.16-.87-.19 0-.6.94-1 4.37-2.06 1.7-.51 2.23-.51 2.53 0a.93.93 0 0 0 .83.51 2.84 2.84 0 0 1 1.35.29 1.38 1.38 0 0 0 1.4-.1c.33-.33 0-.6-1-.8-2.17-.43-2.08-1.89.1-1.52 1 .18 1.38.12 1-.16s-.12-.68 1.14-1.28c2-.94 3.43-1.11 3-.35-.31.5 1.52.7 4.43.5 1.86-.12 3.88-1.43 3.55-2.31-.16-.41.1-.67.69-.67s.89.29.76 1 .07 1 .49 1c1.66 0 1.08 1.11-1.6 3.08-4.53 3.33-7.4 4.71-8.44 4.06s-3.86.1-14 3.26c-4.06 1.26-10.55 3-11.2 3-.11 0-.21.31-.21.69a2.09 2.09 0 0 1-.77 1.32 10.23 10.23 0 0 0-1.75 2.79c-.53 1.18-1.31 2.15-1.71 2.15a.75.75 0 0 0-.73.76c0 .67.17.67 1.5 0 .83-.44 1.38-1 1.23-1.24s.45-.67 1.34-.93c1.47-.42 1.51-.47.4-.55-.69-.05-1-.2-.62-.33a1.28 1.28 0 0 0 .62-1.11 2.09 2.09 0 0 1 2.08-2c.49 0 .89-.23.89-.53s.23-.38.5-.21.5.12.5-.11 1.28-.7 2.85-1.06a34.53 34.53 0 0 0 7.54-2.41c.93-.46 1.31-.47 1.58 0 .53.85-.11 1.6-.67.81-.38-.53-.43-.5-.23.13a1.48 1.48 0 0 1-.3 1.37c-.83.82 0 .67 1.67-.3 1.47-.86 1.95-2.11.83-2.15-.35 0 .27-.37 1.36-.79s2.43-.95 3-1.18c2.15-.91 3.1-1 3.37-.33.33.87-.14 1.56-1.39 2-.8.3-.87.24-.37-.26a2.07 2.07 0 0 0 .61-1.15c0-.29-.17-.26-.39.08a3.6 3.6 0 0 1-2.22.72c-1.5.11-1.84.31-1.81 1.14 0 .56.15.74.29.4.3-.74 2.65-.85 2.65-.13 0 .27-.37.5-.83.5s-.71.11-.58.25a12.24 12.24 0 0 0 3.52-.56c2.62-.65 3.21-1 3-1.61s-.05-.58.73.18.94 1.07.24 1.87c-1.43 1.62-8.76 5.82-10.28 5.88-.83 0-1.1.16-.62.28a1.49 1.49 0 0 1 .53 2.32c-.51.51-.63 1.81-.16 1.81.21 0 .55-.39.73-.87.31-.82.37-.81.85.06s.61.8 1 .24.43-.54.43 0 .24.52.51.35.5-.07.5.22.67.52 1.51.52 1.39-.21 1.21-.49 0-.37.42-.21c1 .37 1.62-.78.76-1.34-.57-.36-.56-.52.06-.93 1.14-.74 1.42-.6 1.12.57-.19.73-.1 1 .29.71a1.26 1.26 0 0 0 .58-.95c0-.33.46-.89 1-1.24.88-.55 1.18-.52 2 .23s.94.79.64 0c-.17-.46-.5-.84-.72-.84s-.26-.48-.11-1.07c.27-1 .3-1 1-.12s.71.87.44-.3-.25-1.2.45-.26a2.08 2.08 0 0 1 .48 1.61c-.13.35 0 .63.24.63.45 0 .67-.88.61-2.38 0-.37.09-.54.23-.4s.82-.16 1.5-.7a2.8 2.8 0 0 1 2.42-.67c.74.18 1.18.07 1.18-.29s-.22-.44-.49-.27-.37 0-.22-.42 1.26-.87 2.47-1 2.21-.51 2.21-.73.46-.53 1-.68.91-.48.75-.74.16-1 .7-1.6 1-.88 1-.58a2.09 2.09 0 0 1-.67 1.18c-.56.52-.46.73.55 1.26 1.54.79 1.44.83 1.4-.49a1.06 1.06 0 0 1 1.08-1.27c.81-.11 1.11-.49 1.11-1.36 0-1.44.77-1.61 1.13-.25.18.7.54.89 1.3.69.57-.15 1.05-.53 1.05-.84 0-.65 1.74-1.87 2.12-1.49.13.13-.51 1-1.43 1.88a6.25 6.25 0 0 0-1.69 2.2c0 1 1.36.62 1.64-.42.15-.61.53-.89 1-.73a1.33 1.33 0 0 0 1.35-.49 2.36 2.36 0 0 1 1.57-.77c.51 0 .93-.28.94-.62s.17-.37.49.13c.53.83.63.48.5-1.86 0-.89.13-1.62.4-1.62s.44.39.41.86.31.88 1.05.93 1.11-.22 1.11-.58a1.14 1.14 0 0 1 .41-.9c.22-.14.31.4.19 1.21-.21 1.44.56 2.06 1.05.84.21-.5.26-.5.3 0a.62.62 0 0 0 .56.62c.29 0 .4-.42.23-.93-.24-.77.11-1.05 1.94-1.59s2.29-.93 2.52-2 .6-1.36 2.25-1.52c1.89-.18 2.51.18 1.46.84s.17 1.17 1.49.49a6.69 6.69 0 0 1 2.34-.75 6.14 6.14 0 0 0 2.69-1.92c1.89-2 3.73-3.43 2.56-2a2.27 2.27 0 0 0-.32 1.8c.23.91.43 1 1.45.52.65-.29 1-.76.86-1s0-.39.48-.2a2.57 2.57 0 0 0 2-.64 4.08 4.08 0 0 1 2.14-.91 15.09 15.09 0 0 0 5.16-1.64 11.19 11.19 0 0 1 1.49-.71 11.46 11.46 0 0 0 1.49-.74 12.55 12.55 0 0 1 4.45-1.64c2.46-.47 1.39.22-3.71 2.41a32.08 32.08 0 0 0-5.7 3c-1.55 1.34-7.32 5.21-7.77 5.21a8.37 8.37 0 0 0-1.95 1.24 8.86 8.86 0 0 1-1.9 1.24 9 9 0 0 0-1.81.94c-2.16 1.31-1.13 1.67 1.27.44a11.32 11.32 0 0 1 3.23-1.12l2.73-.21c1.43-.11 1.45-.13.37-.65-1.31-.63-1.54-1.67-.25-1.11 1.66.7 3.76.89 3.39.29-.2-.31-.13-.56.13-.56a.89.89 0 0 1 .7.37c.11.21.16.15.11-.12s-.69-.44-1.41-.36a4.31 4.31 0 0 1-2.19-.35c-.62-.36.13-.48 2.76-.44 2 0 3.72.24 3.86.46.38.62 1.73-.09 1.38-.73-.18-.31 0-.27.37.09.59.56.47.84-.79 1.89-2.69 2.22-5.88 3.63-6.92 3-.68-.37-1.43-.34-2.71.14a21.54 21.54 0 0 1-5.28.72 17.76 17.76 0 0 0-4 .36c-.26.15-1.7.95-3.2 1.76-4.72 2.56-5.94 3.33-6 3.71 0 .82 1.35.29 2.9-1.12.9-.83 1.86-1.36 2.12-1.2.54.34 3.69-1 4.17-1.79a2.16 2.16 0 0 1 1.63-.44c.73 0 2.67-.08 4.31-.23 2.8-.26 2.76-.17-.25.51l-1.24.29 1.24.49c1.55.62 4.38.58 5.17-.08a2.66 2.66 0 0 1 2-.1l1.44.38-2.21 1.51c-3.25 2.23-5.16 3.11-7.27 3.39-1 .13-2 .46-2.05.74a.42.42 0 0 1-.65.21c-.62-.38 1-1.65 2.18-1.68a4.29 4.29 0 0 0 1.74-.48 9.73 9.73 0 0 1 2.1-.72 6.77 6.77 0 0 0 2.23-1c.86-.63.72-.65-1.3-.13a10.92 10.92 0 0 0-3 1.12c-1.76 1.38-14.52 2.27-15.7 1.1-.44-.44-1.25-.14-3.37 1.27-3.24 2.16-3.28 2.8-.08 1.34a7.61 7.61 0 0 1 4.81-.7c1.61.19 2.71.11 2.91-.22.4-.64 4.35 0 5.17.81.41.41.33.72-.32 1.2-.81.59-.78.64.35.62a3 3 0 0 0 1.62-.4 1.9 1.9 0 0 1 1.15-.36c.69 0 .7.06.05.49-.85.54-.47.64.71.19.61-.23.67-.47.25-1a1.19 1.19 0 0 1-.18-1.27c.25-.41.5-.25.79.53a1.18 1.18 0 0 0 1.6.91c1.69-.32 1.35.84-.7 2.35a8 8 0 0 1-2 1.23 9.68 9.68 0 0 0-2.21 1.24 7.11 7.11 0 0 1-3.52 1.24c-.93 0-1.57.21-1.42.46s-.14.33-.67.2a3.29 3.29 0 0 0-2 .34c-.8.42-1.25.44-1.64.05s-.3-.55.33-.55A21.67 21.67 0 0 0 297 201c1.93-.82 3.7-1.37 3.95-1.22s.44-.07.44-.5c0-1-.63-1-2.95.21-4.15 2.12-6.13 2.5-14.54 2.83-6.14.23-8.3.16-8.3-.26s.45-.57 1-.57a3.66 3.66 0 0 0 2.11-1.12c1.3-1.33 4.81-3 4.81-2.35a.49.49 0 0 1-.49.49.5.5 0 0 0-.5.5c0 .27.33.49.74.49a.71.71 0 0 0 .75-.66c0-1 2.43-2 3.26-1.29a1.47 1.47 0 0 0 1.31.31c.4-.17.32-.28-.23-.3s-.87-.23-.87-.47-.81-.56-1.79-.72c-2.12-.34-5.61 1.33-10.36 5a21.47 21.47 0 0 1-2.24 1.26 51.36 51.36 0 0 0-5.77 3.39c-6 3.94-7.92 5.17-8.36 5.35a48.77 48.77 0 0 0-6.7 4.38c0 .5 3.17-.38 3.57-1s.48-.47.78 0c.49.76 1.86 0 1.39-.79-.22-.34.11-.55.9-.55a2.1 2.1 0 0 0 1.54-.49c.17-.27 0-.5-.34-.5s-.51-.1 0-.44a2.49 2.49 0 0 1 1.69-.16c.78.2.95.08.72-.51s0-.86.67-1 .86-.09.74.1 1.28.3 3.1.25 2.65 0 1.83.13c-2.53.42-2.46 1.07.08.79a5.23 5.23 0 0 0 2.56-.69.39.39 0 0 1 .6-.16c.24.14.29.64.12 1.08-.24.63-.11.74.59.47a1.92 1.92 0 0 1 1.91.53c1 .85 1 .85.63-.11-.66-1.79-.56-2 1.12-2.14 1.09-.11 1.55 0 1.34.37s.09.53.69.53 1-.22 1-.5.46-.49 1-.49.89.21.73.46.33.55 1.09.67-.42.26-2.6.32c-4 .12-4.14.19-1.65.74a5.07 5.07 0 0 1 2.12 1.08c.53.53 1.87.83 4.17.91a15.58 15.58 0 0 1 4 .48c.32.2.58.11.58-.18s.35-.35 1.12.07a3.07 3.07 0 0 0 1.52.5c.22-.06.28.09.13.34a18 18 0 0 1-4.16 1.74 15.3 15.3 0 0 1-7.94 1c-2.91-.22-4.06-.15-4.06.26s-1.19.58-3.35.6c-2.69 0-3.11.13-2.15.51a8.1 8.1 0 0 0 3.43.15c1.82-.27 2.17-.2 2 .38s.31.74 2.56.8a17.64 17.64 0 0 1 4 .52c1.17.43 1.16.47-.78 1.45-4.2 2.12-11.15 3.41-11.15 2.06 0-.3-.31-.43-.69-.29a1.23 1.23 0 0 1-1.21-.26c-.37-.38-.45-.19-.27.65s.11 1-.17.58-.78-.49-2 0c-2.12.8-2 1.36.28 1.42 4.41.12 4.84.22 5.36 1.19s.34 1.11-.59 1.62c-1.36.75-6.77 2-9.73 2.32-1.94.18-2.15.09-2.16-.87a1.92 1.92 0 0 1 .39-1.36c.22-.15-3.34-.57-7.91-.93-9.62-.75-10.36-.81-14-1.31-2.25-.29-2.51-.42-1.49-.72a6.28 6.28 0 0 1 2.48-.11c1.22.24 1.22.23.25-.59s-.94-.83.74-.52a34.27 34.27 0 0 0 3.56.47c1.5.11 1.72 0 1.18-.51a2 2 0 0 0-1.82-.34c-1.13.3-1.15.27-.31-.58a5.79 5.79 0 0 1 3-1.07c1.52-.14 1.76-.08.86.19s-1 .52-.66.74a2.79 2.79 0 0 0 1.94-.16c1.18-.45 1.6-.32 3.18.95 1.21 1 2.05 1.32 2.5 1a1.74 1.74 0 0 1 1.54 0 8.78 8.78 0 0 0 3.12.46c1.68 0 2.27-.18 2.27-.74s-.58-.74-2.18-.74a9.49 9.49 0 0 1-3.59-.74c-1.26-.66-1.3-.75-.35-.75a6.6 6.6 0 0 0 1.87-.3c.57-.23.7-.09.48.49s.06.8 1.69.8c1.1 0 2.07-.2 2.16-.45.22-.59 3.33-.82 4.75-.35a2.38 2.38 0 0 0 2.12-.34c.89-.62-.17-.73-9.05-1a115.07 115.07 0 0 1-11.79-.75l-1.74-.44 1.49-.36c2.92-.71 3-.77 2.22-1.29s-.37-.65 1.07-.51a1.2 1.2 0 0 0 1.13-.78c.29-.76.4-.73 1.08.23.42.6 1 1.08 1.22 1.08.7 0 .57-1.37-.15-1.63-.34-.12-.46-.39-.26-.6s.65-.16 1 .14.86.35 1.42-.06c.93-.68 7.55-.48 7.12.21-.16.25.19.45.77.45s.91-.22.74-.5c-.28-.46 1.32-.58 10.92-.79 5.42-.12 6.2-.19 6.2-.57 0-.2-4.07-.31-9.05-.23s-11.4-.06-14.27-.31c-4.73-.42-5.33-.37-6.55.48a5.5 5.5 0 0 1-1.86.93.49.49 0 0 0-.52.44c0 .24-.39.57-.86.74a30.71 30.71 0 0 0-6.32 3.73 18.49 18.49 0 0 1-2.72 1.84 18.79 18.79 0 0 0-2.7 1.8 6.4 6.4 0 0 1-2 1.18 19.6 19.6 0 0 0-3.38 2 12.07 12.07 0 0 1-2.88 1.67.52.52 0 0 0-.5.54c0 .3-.28.45-.62.33a1.56 1.56 0 0 0-1.36.63A34.59 34.59 0 0 1 220 236a43.77 43.77 0 0 0-4 2.72 10.76 10.76 0 0 1-2 1.29 16.8 16.8 0 0 0-2.4 1.58 2.24 2.24 0 0 1-1.17.62 7.79 7.79 0 0 0-2.33 1.42 25.67 25.67 0 0 1-4 2.47 16.13 16.13 0 0 0-4.48 2.65 5.3 5.3 0 0 1-1.49.88 32.76 32.76 0 0 0-3.46 2c-4.13 2.71-4.23 2.61-.74-.69Zm12.65-10.52c0-.4-.23-.6-.5-.43a1.34 1.34 0 0 0-.5 1c0 .41.23.6.5.43a1.36 1.36 0 0 0 .48-1Zm-7.94-2.23c0-.24-.34-.44-.74-.44s-.75.45-.75 1.06c0 .88.12.95.75.43a2 2 0 0 0 .72-1.05Zm2.54-1.78c-.47-.19-.19-.68 1-1.67.9-.78 1.47-1.59 1.25-1.8s-.68 0-1 .48a1.27 1.27 0 0 1-1.5.61c-.58-.23-.8 0-.8.74 0 .57-.29 1.05-.62 1.07-.7 0 1.09.9 1.86.89.27 0 .19-.13-.19-.28Zm11.35-1.14a.51.51 0 0 0-.53-.49c-.28 0-.38.22-.21.49s.4.5.52.5.2-.22.2-.5Zm-14.53-7.56c-.1-.89-.19-.16-.19 1.61s.09 2.5.19 1.61a15.75 15.75 0 0 0-.02-3.22Zm10.19-1.79c.15-.6.88-1 2-1.26a4.78 4.78 0 0 0 2-.72c.27-.45-4.79.47-5.17.93a3.28 3.28 0 0 1-1.11.62c-1.37.51-1 1.35.56 1.35 1 0 1.51-.29 1.67-.92Zm7.56-1.56c.17-.27 0-.36-.47-.19-.85.33-1 .68-.3.68a1 1 0 0 0 .75-.49Zm55.41-.84a1 1 0 0 0-.95 0c-.16.17.13.29.65.26s.7-.14.3-.3Zm2.64-.15c.65-.28.74-.44.25-.46a3 3 0 0 0-1.49.46c-.93.58-.15.58 1.22 0Zm-55.32-1.77c0-.12-.22-.22-.49-.22a.51.51 0 0 0-.5.53c0 .29.22.38.5.22s.47-.41.47-.53Zm-8.43-1.3c0-.78 1.16-1.71 2.61-2.07.74-.19 1.36-.56 1.36-.83s.61-.5 1.36-.52a7.61 7.61 0 0 0 2.36-.47c.67-.3.27-.35-1.24-.16a16.84 16.84 0 0 0-4.25 1.22 11.75 11.75 0 0 1-2.53.93c-.41 0-1.65 2-1.65 2.71 0 .1.44.06 1-.08s1-.47 1-.73Zm25-.65c-.16-.41 0-.75.27-.75a.52.52 0 0 1 .55.48c0 .27.33.35.74.2 1.26-.49.82-1.18-.74-1.18-1.16 0-1.49.23-1.49 1 0 .54.21 1 .48 1s.35-.33.19-.74Zm24.95.13c-.75-.48-4.17-.49-4.46 0-.13.21.95.37 2.4.37s2.34-.12 1.99-.37Zm6.75.26a7 7 0 0 0-2.23 0c-.62.12-.12.22 1.11.22s1.69-.1 1.08-.23Zm-45.62-2.33c-.49-.66-1-1-1.07-.71s.24.79.75 1.17c1.25.98 1.3.86.28-.46Zm47.57.85a1 1 0 0 0-1 0c-.16.17.13.29.65.26s.7-.14.3-.3Zm5.74 0a5.46 5.46 0 0 0-2 0c-.62.12-.23.22.86.23s1.59-.09 1.12-.22Zm-42.68-.5a2.82 2.82 0 0 0-1.48 0c-.34.14.06.24.87.23s1.06-.1.57-.26Zm4.11-.65a.5.5 0 1 0-.5.5.5.5 0 0 0 .46-.5Zm-24.81-.77c0-.12-.22-.22-.49-.22a.51.51 0 0 0-.5.53c0 .28.22.38.5.21s.45-.4.45-.52Zm28.28.28c0-.28-.34-.5-.74-.5s-.75.22-.75.5.33.49.75.49.7-.22.7-.49Zm45.76.17c-.34-.14-.5-.46-.34-.71s-.17-.46-.71-.46c-1.43 0-1.77.89-.44 1.15 1.59.3 2.22.31 1.49 0Zm-53.7-.67a.49.49 0 0 0-.46-.5 1.09 1.09 0 0 0-.78.5c-.17.27 0 .5.47.5s.73-.23.73-.5Zm-3.47-1.27c0-.42-.23-.63-.51-.46a.58.58 0 0 0-.22.77c.39.69.73.57.73-.31Zm10.17.28c.17-.28-.24-.5-1-.5s-1.27.23-1.27.5.43.49 1 .49a1.67 1.67 0 0 0 1.23-.49ZM225 213.88c0-.25-.33-.47-.74-.47-.77 0-1 .59-.43 1.15.38.44 1.17-.05 1.17-.68Zm2 .06a1.21 1.21 0 0 0-.59-.83c-.41-.25-.29-.48.37-.74.89-.35.88-.37-.13-.41-1.28 0-1.76.56-1.34 1.66.29.84 1.69 1.1 1.69.32Zm-12.9-.93c0-1.47 1.81-3 4.55-3.79a19.64 19.64 0 0 1 3.38-.77 2.28 2.28 0 0 0 1.27-.4 11.31 11.31 0 0 1 2.58-1.08c1.16-.36 2.11-.85 2.11-1.08 0-.53-.3-.52-1.37 0a46.88 46.88 0 0 1-4.58 1.54l-5.46 1.59c-2.22.65-3 1.27-3 2.43a.85.85 0 0 1-.81.93c-.65 0-.69.14-.18.75.82 1.04 1.48.97 1.48-.13Zm32-1.08c-.17-.28-.41-.5-.53-.5s-.22.22-.22.5a.51.51 0 0 0 .53.49c.26 0 .36-.22.19-.49Zm-2-1a1.14 1.14 0 0 0-.9-.48c-.35 0-.29.2.16.48.93.62 1.1.62.72 0Zm-19.59-1.54c0-.28-.23-.36-.5-.19a1.1 1.1 0 0 0-.5.8c0 .27.23.36.5.19a1.1 1.1 0 0 0 .48-.8Zm1.73.55a.57.57 0 0 0-.78-.22c-.69.42-.56.72.32.72.4 0 .61-.23.44-.5Zm21.09-.22a1.33 1.33 0 0 0-.5-1c-.27-.17-.5.15-.5.72s.23 1 .5 1 .48-.33.48-.72Zm-18.09-.3c.18-.3.24-.62.13-.73-.34-.34-2.38.36-2.38.81 0 .62 1.86.55 2.25-.08Zm5.33-1.65c1-.42 1.76-.57 1.61-.33-.69 1.12 1.78-.25 2.52-1.39a6.59 6.59 0 0 1 1.52-1.72.69.69 0 0 0 .37-.9c-.19-.3-.53-.22-.88.22-.8 1-3.69 1.9-4.07 1.28-.17-.27-.74-.35-1.28-.18a1.89 1.89 0 0 1-1.57-.15 2.38 2.38 0 0 0-1.83 0c-1.21.41-1.21.41.13.24 1-.12 1.54.11 1.87.72.66 1.26.6 1.61-.27 1.28-.45-.18-.74 0-.74.44s-.45.73-1 .73c-1.11 0-1.35-.77-.38-1.16.45-.18.46-.26 0-.29a1.06 1.06 0 0 0-.89.45 2.36 2.36 0 0 1-1.65.55c-.9 0-.54.23 1.13.62 1.36.31 2.42.74 2.36.94-.26.9-.06 1 .51.26a7.49 7.49 0 0 1 2.5-1.56Zm6.31 2c0-.11-.34-.32-.75-.48s-.74-.07-.74.19.33.48.74.48.73-.1.73-.21Zm.49-2.29c0-.54-.22-1-.49-1s-.5.45-.5 1 .22 1 .5 1 .47-.48.47-1.02ZM256 208a.56.56 0 0 0-.77-.22c-.69.43-.56.72.31.72.4-.05.61-.27.46-.5Zm-7.68-.77c0-.12-.22-.22-.5-.22a.51.51 0 0 0-.49.53c0 .29.22.39.49.22s.48-.45.48-.58Zm-31-.22a1.05 1.05 0 0 0-.77-.49.48.48 0 0 0-.47.49c0 .28.35.5.78.5s.6-.27.43-.51Zm-6.69-1.48a.5.5 0 1 0-.5.49.5.5 0 0 0 .47-.54Zm7.93 0c0-.28-.33-.5-.74-.5s-.74.22-.74.5.33.49.74.49.71-.27.71-.54Zm39-1.17c.93.36.93-.36 0-1.59-.71-.93-.73-.93-.74.18 0 .64-.24 1-.51.84s-.5-.2-.5-.07a6.1 6.1 0 0 1-.35 1.15c-.32.85-.29.86.51.06a1.79 1.79 0 0 1 1.52-.62Zm-6.17-2c.24.15.45-.07.45-.49 0-1.3-1.42-.86-2.22.68-1.06 2.05-.94 2.39.28.84.56-.73 1.23-1.19 1.49-1Zm-31.59 1.28c.41-.66 5.13-2.76 8.48-3.76 3-.9 4.56-1.77 3.24-1.77a108.79 108.79 0 0 0-12 4.05 1.59 1.59 0 0 0-.87 1.18c0 .8.78 1 1.2.3Zm-15.29-1.42c-.16-.17-.28.13-.25.65s.14.7.29.3a1 1 0 0 0 0-1Zm43.54.12c-.15-.41-.48-.62-.73-.46-.52.32-.11 1.2.56 1.2.25 0 .33-.34.17-.74ZM213.57 201a.5.5 0 0 0-1 0 .5.5 0 1 0 1 0Zm45.64 0a.5.5 0 0 0-1 0 .5.5 0 1 0 1 0Zm-5-1.76c0-.4-.22-.72-.49-.72s-.5.46-.5 1 .22.89.5.72a1.32 1.32 0 0 0 .53-.99Zm-44.88-.75a1.56 1.56 0 0 0-.35-1.26c-.23-.14-.41.47-.41 1.35 0 1.69.63 1.62.76-.09Zm2.22 1a.51.51 0 0 0-.53-.49c-.29 0-.38.22-.22.49s.41.5.53.5.26-.19.26-.47Zm28.45-.25c-.19-.5-.06-.66.39-.49s1-.19 1.3-.74c.52-.82.5-1-.1-1a1.31 1.31 0 0 0-1 .48.86.86 0 0 1-1 .19c-.4-.15-.73 0-.73.28s-.33.54-.74.54c-1.31 0-.8.92.62 1.14a14.25 14.25 0 0 1 1.47.27c.06.05 0-.26-.18-.67Zm-28.45-1.76c0-.29-.23-.39-.5-.22s-.49.41-.49.53.22.21.49.21a.51.51 0 0 0 .54-.49Zm23.31 0c0-.26-.33-.35-.74-.19s-.74.37-.74.48.33.19.74.19.78-.14.78-.41Zm-28.49-.48a.56.56 0 0 0-.77-.22c-.69.42-.56.72.31.72.42.04.63-.18.46-.5Zm21.58-.87c0-.49-.16-.65-.38-.37a5.24 5.24 0 0 1-.82.87c-.24.21-.08.37.38.37a.84.84 0 0 0 .87-.83Zm9.68.38c.66-.28.74-.44.25-.45a3 3 0 0 0-1.49.45c-.9.62-.12.62 1.24.04Zm61.52-.61c.27-.2-.27-.37-1.21-.38-1.6 0-1.65 0-.75.7.53.39 1.08.55 1.22.38a4.22 4.22 0 0 1 .74-.66Zm7.28.77a1 1 0 0 0-.95 0c-.16.17.13.29.65.26s.7-.14.3-.29ZM226 195.8c0-.4-.08-.74-.19-.74s-.33.34-.48.74-.07.75.19.75.48-.34.48-.75Zm-22-1.1c-.17-.17-.28.13-.26.65s.14.7.3.3a1 1 0 0 0 0-1Zm58.13 0c.15-.46-.14-.61-.91-.5a1.76 1.76 0 0 0-1.36.83c-.14.45.15.6.92.49a1.76 1.76 0 0 0 1.3-.87Zm5.57.13c0-.42-.23-.63-.5-.47a1.09 1.09 0 0 0-.5.78.48.48 0 0 0 .5.46c.22-.05.45-.39.45-.82Zm31.13-.06a3.93 3.93 0 0 0-1.74 0c-.48.12-.09.22.87.22s1.29-.15.82-.27Zm-62.39-3.23c.51-.32.22-.38-.93-.18-.92.15-1.87.28-2.11.28a.46.46 0 0 0-.43.5c-.05.5 2.19.12 3.42-.65Zm-28.68-1c-.36-.36-.59-.39-.59-.08 0 .66.59 1.25.92.92.09-.19-.09-.57-.38-.89Zm62.42.6a.5.5 0 0 0-.47-.5 1.07 1.07 0 0 0-.77.5c-.17.27 0 .5.46.5s.73-.28.73-.55Zm-56.67-.9c.11-.32-.16-.59-.59-.59a.8.8 0 0 0-.79.79c-.05.82 1.02.67 1.33-.25Zm14.18.16a2.67 2.67 0 0 1 1.58-.75c.67 0 .72-.14.26-.7s-.44-.78 0-1.06a.78.78 0 0 1 1 .33c.37.58.53.57.93-.06.81-1.22.59-1.49-1.23-1.49-1.41 0-1.73.19-1.73 1 0 .57-.34 1-.78 1s-.63.23-.42.57-.06.87-.56 1.24-.67.67-.36.67a2.47 2.47 0 0 0 1.3-.74Zm73.74 0a3.16 3.16 0 0 0-1.19-.36c-.81-.16-1.12 0-1 .38s2.14.42 2.14-.07Zm-14.8-2.91c-.19-.75 0-.87.8-.6.56.18 1.16.09 1.34-.2s0-.51-.42-.51a1 1 0 0 1-.91-.87c-.11-.61-.42-.32-1.05 1-.49 1-1.07 1.86-1.31 1.86s-.31.43-.18.95c.24.88.31.89 1.1.12a2.1 2.1 0 0 0 .63-1.74Zm19.76 1.62c0-.25-.58-.45-1.3-.45-1.07 0-1.2.12-.71.71s1.96.49 1.96-.31Zm-26.37.11c.54-.34.52-.47-.08-.7-.77-.3-1.33 0-1.33.72 0 .53.56.52 1.41 0Zm22.49-.9a1 1 0 0 0-.94 0c-.17.16.12.28.65.26s.69-.14.29-.3Zm-89.09-2c-.5-.5-.91.26-.5.93.31.51.43.51.59 0a1 1 0 0 0-.09-.93Zm10.21-.24c-.35-.35-.58-.38-.58-.07 0 .66.58 1.25.92.92.08-.21-.06-.58-.39-.93Zm-12.12-.23a3.89 3.89 0 0 0-1.73 0c-.48.12-.09.22.87.22s1.29-.16.81-.28ZM249.8 181c0-1 .06-1 .68-.18.8 1 1.29 1.11 1.29.14 0-.43-.7-.85-1.76-1.05-1.87-.35-2.87.07-1.77.75.44.27.54.8.29 1.58-.33 1.05-.29 1.1.44.5a2.65 2.65 0 0 0 .83-1.74Zm5.94 1.07c0-.27-.35-.36-.78-.19a4.64 4.64 0 0 1-1 .3c-.11 0-.2.2-.2.45s.44.33 1 .19.98-.5.98-.77Zm-35.72.08c0-.29-.22-.39-.49-.22s-.5.41-.5.53.22.22.5.22a.51.51 0 0 0 .47-.55Zm78.39-.53c0-.27-.23-.35-.5-.18a1.09 1.09 0 0 0-.5.8c0 .27.23.36.5.19a1.1 1.1 0 0 0 .5-.83Zm-76.09-.18a2.62 2.62 0 0 0-1.08-.75c-.2 0-.07.34.27.75a2 2 0 0 0 1.09.74c.25-.02.13-.35-.28-.76Zm-4.78-.9c1.07-.22 1-.24-.21-.18-.82 0-1.54.41-1.65.83s-.06.55.21.17a3.22 3.22 0 0 1 1.65-.84Zm8.43.18c0-.78-.94-1.11-2.11-.72l-1.11.37 1.24.36c1.78.5 2.01.49 2.01-.03Zm71-1a.5.5 0 0 0-.47-.5 1.07 1.07 0 0 0-.77.5c-.17.27 0 .5.46.5s.73-.22.73-.54Zm-77.61-.76c-1.11-.83-1.77-.93-1.77-.27 0 .26.5.56 1.12.68l1.36.26c.09-.01-.24-.31-.76-.71Zm8.63-1.35a5.45 5.45 0 0 0-1.27-2c-.91-1-1.41-1.28-1.8-.89s-.36.76.09 1.31.48.76-.34.76-1-.27-.8-1.36c.18-1.31.16-1.32-.39-.27a2.79 2.79 0 0 1-1.36 1.31c-.54.13-.43.23.31.27s1.11.29 1.11.58.18.42.4.28a1.28 1.28 0 0 1 1.11.21c1.06.67 3 .57 2.94-.15Zm-9-.71a2.75 2.75 0 0 0-1.49 0c-.34.13.06.23.88.22s1-.12.52-.26Zm25.93-.44c0-.11-.35-.21-.78-.21s-.63.22-.46.5c.19.42 1.15.25 1.15-.33Zm11.74-.81c1.5-.54 2.25-1.39 1.23-1.39a10.62 10.62 0 0 0-3.55 1.78c-.09.3.48.21 2.23-.43Zm-35.06-1.39a.51.51 0 0 0-.53-.49c-.28 0-.38.22-.21.49s.4.5.52.5.13-.27.13-.54Zm6.45 0a.51.51 0 0 0-.53-.49c-.28 0-.38.22-.21.49s.4.5.52.5.17-.27.17-.54ZM239 172c1.46-2.3 2.17-2.66 3.21-1.64.32.33.59.39.58.13 0-.84-.71-1.57-1.83-1.92-.92-.29-1.21-.07-1.78 1.3a7.44 7.44 0 0 1-1.51 2.32c-.84.69-1.13 2-.45 2A7.66 7.66 0 0 0 239 172Zm12.73 1.74c0-.28-.34-.5-.74-.5s-.75.22-.75.5.34.49.75.49.78-.23.78-.5Zm3 0c0-.28-.34-.5-.75-.5s-.74.22-.74.5.33.49.74.49.77-.23.77-.5Zm5.31-.76c.68 0 2.4-1.2 1.78-1.21-1.2 0-3.12 1.16-3.12 1.92s.07.7.48 0c.28-.37.67-.69.88-.69Zm-37.47-.08a1 1 0 0 0-1 0c-.16.17.13.28.65.26s.7-.14.3-.3Zm2.53-2.22c-.61-.71-.61-.88 0-1.24s.52-.58-.48-1.33l-1.23-.91.25 2.89c.13 1.6.26 3 .28 3s.44-.27.94-.75c.8-.75.83-1 .22-1.69Zm6.38 1.62c-.47-.3-.5-.52-.12-.76.84-.52.65-1.27-.32-1.3-.73 0-.69-.15.25-.81 1.13-.79 1.43-2.63.8-5-.31-1.16-2.31-1.59-2.91-.62-.17.28.1.5.6.5.77 0 .88.32.72 2s-.42 2.1-1.87 2.7c-1.89.79-2.23 1.75-.64 1.75.71 0 1 .23.77.71-.28.72.5 1.11 2.4 1.21.74 0 .82-.06.32-.38Zm-8.75-1.54c-.17-.27-.41-.5-.53-.5s-.21.23-.21.5a.51.51 0 0 0 .52.5c.31-.01.41-.26.24-.51Zm41.42 0c0-.29-.22-.39-.49-.22s-.5.41-.5.53.23.22.5.22a.51.51 0 0 0 .51-.57ZM246.81 168c0-.42-.23-.63-.51-.46a.57.57 0 0 0-.21.77c.42.69.72.55.72-.31Zm-19.35-1.71c0-.81-.22-1.48-.49-1.48-.56 0-.69 2.12-.17 2.65s.66.37.66-1.18Zm-2.35.21a1.14 1.14 0 0 0 0-1c-.24-.6-.37-.61-.71-.07-.71 1.1-.18 1.95.71 1.06Zm18-1.69c.17-.28.08-.5-.19-.5a1.07 1.07 0 0 0-.8.5c-.17.27-.09.49.19.49a1.09 1.09 0 0 0 .78-.5Zm20.09-.5a.5.5 0 1 0-.49.5.5.5 0 0 0 .47-.51Zm-35.2-.74c0-.46-.29-.61-.79-.43s-.65.39-.12.73c.84.55.91.54.91-.3Zm37.6-.53c1.06-1 1.13-1.19.35-.9-1 .38-2.23 1.39-2.23 1.88s.51.27 1.84-1.02Zm-31.65-.2a1.05 1.05 0 0 0-.49-.77c-.28-.17-.5 0-.5.46s.22.78.5.78a.48.48 0 0 0 .45-.47Zm6-.52c0-.22-.32-.52-.71-.66a2 2 0 0 1-.93-1.42c-.15-.78-.23-.58-.28.63a5 5 0 0 0 .22 2.08c.39.37 1.7-.11 1.7-.63Zm-7.67-1.62a3.91 3.91 0 0 1 .9-2c.41-.25.4-.51 0-.94s-.66-.29-.91.73-.65 1.31-1.52 1.31c-1.37 0-1.59.81-.31 1.14.63.17.56.24-.28.29-1.49.07-.9 1 .64 1 .92 0 1.24-.35 1.52-1.62Zm17.09.93c0-.11-.34-.33-.74-.49s-.75-.06-.75.2.34.48.75.48.66-.08.66-.19Zm20.09-.8c.17-.28 0-.5-.46-.5s-.78.22-.78.5a.48.48 0 0 0 .47.49 1.05 1.05 0 0 0 .69-.49Zm-32.81-4.45c-.33-.34-1.16 1.08-1.14 2 0 .43.29.22.69-.5s.5-1.37.37-1.5Zm-10.35 1.47a1.71 1.71 0 0 0-1.3-.5c-.54 0-.85.23-.68.5a1.71 1.71 0 0 0 1.3.5c.46 0 .77-.23.6-.5Zm8.37-.25c-.16-.4-.37-.74-.48-.74s-.2.34-.2.74.22.75.48.75.28-.35.12-.75Zm-2.12-4.86c-.39-.39-.54-.25-.54.51a8.2 8.2 0 0 1-.28 2c-.16.5.08.27.54-.51.51-1.07.59-1.61.2-2Zm26.47 2.67c.33-.54-.72-1-1.59-.65-.34.12-.62.42-.62.66 0 .59 1.85.58 2.21 0Zm-20.81-1.28c0-.27-.51-.57-1.12-.65-.77-.11-1.11.09-1.11.65s.34.76 1.11.65c.53-.08 1.04-.38 1.04-.65Zm22.57.28a1.07 1.07 0 0 0-.49-.78c-.28-.16-.5.05-.5.47s.22.77.5.77a.47.47 0 0 0 .41-.46Zm-15.87-.53a.5.5 0 0 0-1 0 .5.5 0 0 0 1 0Zm7.16-2.31c.47.18.63-.2.55-1.32s-.32-1.52-.81-1.41c-.79.18-1.27.9-2.56 3.8l-.88 2 1.52-1.66c.84-.9 1.82-1.54 2.18-1.4Zm16 1.91c.12-.36-.11-.47-.59-.29s-.78.52-.78.79c-.04.65 1.08.24 1.3-.5Zm-32.88-1.09c.17-.27 0-.49-.46-.49s-.78.22-.78.49a.48.48 0 0 0 .47.5 1 1 0 0 0 .73-.5Zm35 0c.86-.46 1.38-2 .68-2s-2.2 1.12-2.2 1.76c-.03.85.21.91 1.49.23Zm-24.34-1.23c0-.4-.22-.74-.5-.74s-.49.34-.49.74.22.75.49.75.47-.34.47-.76Zm-2-1c0-.42-.22-.63-.5-.46a.56.56 0 0 0-.22.77c.41.66.7.53.7-.34Zm29.28-.24c.51-.93.47-1-.47-.37s-1.36 1.39-.55 1.39c.25-.03.71-.49 1-1.05Zm-18.77-.58c-.36-.35-.58-.38-.58-.07 0 .66.58 1.24.91.92.12-.19-.03-.55-.35-.88Zm9.34-5.37c0-.15-.9-.26-2-.25-2 0-2 0-1 .88.86.69 1.23.73 2 .25.55-.36.98-.76.98-.91Zm-19.85 0c0-.47-1.53-1.11-1.87-.77-.12.12-.1.57.06 1 .26.73 1.79.54 1.79-.2Zm.62-2.71c-.13-.69-.46-1.26-.73-1.26s-.35.35-.19.78a5.05 5.05 0 0 1 .3 1.52c0 .41.2.62.43.47s.32-.83.19-1.51Zm2.36.7c0-.29-.22-.39-.5-.22s-.49.4-.49.52.22.22.49.22a.51.51 0 0 0 .48-.48Zm22.57 0a1.07 1.07 0 0 0-.8-.5c-.27 0-.36.22-.19.5a1.09 1.09 0 0 0 .81.49c.25.02.33-.2.16-.45Zm3.6.2c-.39-.39 1.63-2.69 2.37-2.69s1.82-2.35 1.19-2.74c-.27-.17-.42-.12-.35.12s-.92 1.48-2.22 2.77c-2.61 2.61-2.73 2.83-1.53 2.83.44.02.68-.11.54-.27Zm-20.91-1.56c-.16-.16-.28.13-.26.66s.14.69.3.29a1 1 0 0 0 0-.95Zm14.59.86a.5.5 0 1 0-.5.5.51.51 0 0 0 .48-.48Zm-8.84-1.83a4.39 4.39 0 0 0 1.39-2.22c0-.75.2-.81 1.31-.39 1.39.53 3.16.22 3.16-.56 0-.27-.85-.33-2-.15-1.29.2-2 .13-2-.22 0-1 2.4-3 4.33-3.62a13.72 13.72 0 0 0 2.47-1.08 1.11 1.11 0 0 1 1.08-.14.56.56 0 0 0 .77-.16.57.57 0 0 0-.21-.79c-.8-.49-.59-1.24.34-1.24.67 0 .71-.13.23-.62s-.74-.44-1-.1c-.7 1-3.87 3.09-5.65 3.66-1.57.5-2.09 1.09-3.67 4.16a18.58 18.58 0 0 1-2.26 3.73c-.51.2-.58 1.07-.09 1.07a6.25 6.25 0 0 0 1.74-1.33Zm13.8.84a.49.49 0 0 0-.47-.5 1 1 0 0 0-.77.5c-.17.27 0 .49.46.49s.76-.2.76-.47Zm-19.85-2a.5.5 0 1 0-.49.5.49.49 0 0 0 .47-.47Zm0-3a.5.5 0 1 0-.49.49.49.49 0 0 0 .47-.43Zm11.89-1.47c0-.27-.35-.5-.78-.5s-.63.23-.46.5a1.05 1.05 0 0 0 .77.49.48.48 0 0 0 .47-.49Zm10.42-1.48c0-.27.86-1.07 1.93-1.77a5.92 5.92 0 0 0 2.18-2.24c.36-1.33-.32-1.2-1.43.28-.52.68-1.16 1.25-1.44 1.27s-1 .68-1.66 1.49c-.92 1.17-1 1.46-.37 1.46.43.04.79-.18.79-.45Zm-9.67-6.37c-.39-.39-.64 0-1 1.65l-.34 1.48.8-1.43c.44-.79.68-1.56.54-1.7Zm-5.71-1.58a.49.49 0 0 0-.47-.49 1.05 1.05 0 0 0-.77.49c-.17.27 0 .5.46.5s.78-.19.78-.46Zm-1-2.26c0-.12-.23-.22-.5-.22a.52.52 0 0 0-.5.53c0 .29.23.38.5.22s.51-.37.51-.49Zm19.84-1.24c0-.29-.22-.39-.5-.22s-.49.41-.49.53.22.22.49.22a.51.51 0 0 0 .51-.49Zm-7.13-4.44a17.22 17.22 0 0 1 .38-2.23c.23-1-.13-.55-1 1.24a21.83 21.83 0 0 1-2 3.39c-1.23 1.3-.58 1.15 1-.24a5.09 5.09 0 0 0 1.66-2.16Zm8.89 3.1c.86-.61 2.7-3.45 2.7-4.16 0-.37-.55.22-1.23 1.31s-1.22 1.8-1.23 1.58c0-.45-1.5.9-1.5 1.36s.64.39 1.27-.05Zm-18.42-3.43c.38-1-.76-1.86-1.3-1s-.46 1.83.29 1.83a1.24 1.24 0 0 0 1.02-.79Zm2.77-.91c0-1.61-.73-1.61-.82 0 0 .69.13 1.24.37 1.24s.46-.51.46-1.2Zm8.93-2c0-.12-.22-.22-.5-.22a.51.51 0 0 0-.49.53c0 .29.22.38.49.22s.51-.38.51-.5Zm2.38-.32a2 2 0 0 0 .59-1.24c0-.41-.35-.29-1 .35a4.76 4.76 0 0 0-1 1.24c.03.49.81.29 1.43-.32Zm-7.33-3.07c.5-.61.47-.75-.19-.75a.78.78 0 0 0-.8.75c0 .41.08.74.18.74a2.73 2.73 0 0 0 .81-.74Zm10.42 0c0-.12-.35-.22-.78-.22s-.63.23-.46.51c.28.4 1.24.18 1.24-.29Zm-7.45-.5c0-.12-.34-.22-.77-.22s-.64.23-.46.51c.28.41 1.23.21 1.23-.34Zm-.49-1.71a.5.5 0 0 0-.5-.49.5.5 0 0 0 0 1 .5.5 0 0 0 .5-.56Zm14.07-8.19c.94-1.44 1-1.78.31-1.78-.27 0-.49.34-.49.75s-.22.74-.5.74a.54.54 0 0 0-.49.57 2.31 2.31 0 0 1-.87 1.27c-.8.64-.78.66.18.19a5.72 5.72 0 0 0 1.86-1.74ZM172 252.8a1 1 0 0 1 .95 0c.4.16.28.28-.3.3s-.81-.09-.65-.26Zm7.26-.62a5 5 0 0 0-1.44-.91 5.47 5.47 0 0 1-1.49-.72c-3.19-2-6.39-3.8-8.43-4.86a23.83 23.83 0 0 1-3-1.78 6.71 6.71 0 0 0-1.91-1 13.36 13.36 0 0 1-2.79-1.42c-.9-.59-2.37-1.52-3.28-2.06a35.73 35.73 0 0 1-4-2.56c-.59-.47-5.3-3-12.32-6.69a19.73 19.73 0 0 1-2.48-1.43c-.14-.15-2.26-1.41-4.72-2.81l-4.4-2.53-4.71 1a56.74 56.74 0 0 1-5.82 1c-.62 0-1.12.25-1.12.56s.42.42 1.12.21a43.36 43.36 0 0 1 5.86-.62c4.61-.26 4.84-.23 7.69 1.28a17.19 17.19 0 0 0 3.56 1.55.57.57 0 0 1 .62.48c0 .26.39.34.87.17s.7-.14.37.2-4.13.78-8.56 1.13-8.06.82-8.06 1.06.28.43.62.44.37.17-.12.49c-1 .67-.54.61 2-.23 2-.65 5.9-1.07 12.61-1.34 1.69-.07 2.44.09 2.36.48a.44.44 0 0 0 .41.59 2.52 2.52 0 0 1 1.27.74 4.49 4.49 0 0 0 2.69.75c3.14 0 2.25.74-1.64 1.39-8.36 1.38-10.74 1.85-11.1 2.21-.53.55 4.92.09 6.06-.5a21.19 21.19 0 0 1 4.85-.85c3.07-.29 3.82-.23 3.52.26s.05.55 1.43.33c1-.17 1.81-.09 1.81.17s-.39.47-.87.48c-.82 0-.82 0 0 .52a2.66 2.66 0 0 0 1.86.2c.56-.17 1-.08 1 .22s-.73.56-1.62.6c-1.21.05-1.33.12-.47.29 2 .38.86 1.37-1.92 1.62-2.17.21-2.45.33-1.45.62a14.7 14.7 0 0 0 3.31.37c1.75 0 2.11.18 2.29 1.12l.22 1.12.37-1.12c.21-.61.66-1.11 1-1.11a1 1 0 0 0 .89-.62c.2-.5.26-.5.29 0s.74.62 1.75.62c1.64 0 4.21.84 4.74 1.55a8.09 8.09 0 0 0 2.48.82l2.23.5-2.6.05c-1.43 0-2.6.27-2.6.52s-.84.53-1.86.62c-1.3.11-1.91.42-2 1s.07.87.36.87a.63.63 0 0 0 .54-.69c0-.46.89-.79 2.68-1a15 15 0 0 1 8.45 1.26 5.08 5.08 0 0 0 2.36.64c.22-.14.4 0 .4.24s.39.59.87.71-.25.28-1.61.34l-2.49.11 1.74.49a24.52 24.52 0 0 1 2.73 1c.78.37.65.42-.6.22-.9-.14-1.75 0-1.94.32s-.12.42.18.23a2.75 2.75 0 0 1 1.93.44c1.41.75 1.4.75-1.06.5-4.78-.51-6.69-.89-6.45-1.27.13-.21-.48-.51-1.37-.66a41.1 41.1 0 0 1-7.31-1.81 7.14 7.14 0 0 0-2.48-.31c-3.46-.06-7-.7-6.68-1.22s-1.83-1.46-3.52-1.46c-.67 0-1.21-.22-1.21-.49s-1.18-.51-2.61-.53c-3.21 0-9.09-1.59-8.93-2.34.06-.33-.22-.44-.68-.26-.67.26-.71.14-.22-.77s.48-1.06-2.3-1.09c-6.8-.08-10.67-1.2-10.2-3 .13-.53.46-1 .71-1s.33-.24.14-.53-1-.41-2.15-.22a52.91 52.91 0 0 1-11.54-1c-1.17-.35-.56-2.17.91-2.73.67-.25 1.07-.68.9-1s-.07-.49.22-.49a.52.52 0 0 0 .53-.5c0-.91-1.42-.51-1.74.5s-.72 1-3.88.93c-5.39-.09-6.54-.44-6.52-2 0-.72-.16-1.2-.38-1.07a1.24 1.24 0 0 0-.38.95c0 .4-.23.72-.5.72-.68 0-.63-.25.41-2 .5-.82 1-1.38 1.17-1.24s.55-.09.9-.51c.91-1.09.22-1.52-1.22-.76-2.65 1.42-13.44-.6-11.5-2.15a3.52 3.52 0 0 0 1-1.31c.2-.53.29-.46.32.25s.23.63.85-.62c.51-1 1.16-1.61 1.82-1.61s.86-.18.56-.47-1.25-.28-2.59.06c-2.1.53-5.52-.06-9.66-1.66-3.07-1.19-.71-3.84 3.55-4l2.77-.08-2.35-.19c-1.3-.1-2.36-.34-2.35-.53 0-1 1-1.66 2.28-1.55 2.28.18 3.66 0 3.66-.49 0-.25-1.55-.45-3.45-.45a15.72 15.72 0 0 1-4-.32c-.28-.18-.51-.09-.51.2s.37.53.83.53c.73 0 .72.09-.12.73-1.33 1-2.06.94-6.61-.52-4.38-1.42-5.92-2.55-4.11-3 .58-.15.94-.48.8-.71s.07-.31.5-.14.77 0 .77-.45c0-.69 1.29-1.39 3-1.61a8 8 0 0 0 1.63-.47c.49-.21 1-.24 1.2-.07s0 .3-.49.3c-.81 0-1.69 1.82-1.14 2.37s3-1.67 2.89-2.32c-.08-.34 1.17-.58 3.47-.64 2.13-.06.76-.2-3.35-.37-3.82-.15-7-.11-7.11.1-.42.95-4-.33-4-1.43 0-.37-.32-.53-.75-.36a2.85 2.85 0 0 1-2-.55l-1.27-.82 1.29-1c1.15-.9 1.54-.95 3.48-.46 2.66.66 3.23.68 3.23.08 0-.25-.51-.53-1.12-.62s2.4-.18 6.7-.22c4.56 0 7.81.14 7.81.41s-.43.33-.95.16a1.2 1.2 0 0 0-1.31.29c-.28.44.15.51 1.69.26 1.7-.27 2.06-.18 2.06.48 0 .49.27.7.71.53a1.28 1.28 0 0 1 1.3.44 2.6 2.6 0 0 0 1.8.72c2.1 0 .76.88-2 1.28l-2.6.39 2.48.39a11.09 11.09 0 0 0 3.36.12 6.25 6.25 0 0 1 2.48.08c1.4.32 1.48.41.61.75s-.7.4.62.43c1.76 0 2.1.69.74 1.48-.78.46-.79.5 0 .52a1.13 1.13 0 0 0 1.05-.61c.18-.49.43-.49 1.27 0a4.86 4.86 0 0 0 2.12.62c1.78 0 .26-1.25-3.95-3.24a63.72 63.72 0 0 1-9-4.94c-3-1.82-4-2.19-5-1.88a67.56 67.56 0 0 1-9.14.39c-5.87 0-7.94.16-8.1.62-.28.83-1.63.78-3.47-.13s-3.1-2.26-1.7-1.89a1.25 1.25 0 0 0 1.29-.27.89.89 0 0 1 1-.24c.41.15.73.06.73-.23a.74.74 0 0 0-.46-.67c-.25-.09-.17-.56.18-1 .57-.77.72-.79 1.27-.12.43.51.45.75.07.75a.53.53 0 0 0-.56.49c0 .27.67.5 1.49.5s1.49-.23 1.49-.5a.5.5 0 0 0-.5-.49.51.51 0 0 1-.5-.5c0-.27.45-.5 1-.5s1 .17 1 .38 2 .37 4.46.37c4.19 0 5.31.28 3.85 1-.34.15-.12.15.5 0s1.11-.07 1.11.19.8.62 1.77.81a20.22 20.22 0 0 1 2.56.63c1.21.47.26-.82-1-1.38-.58-.26-2.63-1.35-4.54-2.43l-5.71-3.19c-1.22-.68-3.57-2.15-5.2-3.25a16.82 16.82 0 0 0-3.51-2 3.51 3.51 0 0 1-1.51-.87 22.29 22.29 0 0 0-3.2-2.05l-3.69-2c-1.48-.79-6.2-1.14-6.2-.45s2 1.43 3 1.12a2.2 2.2 0 0 1 1.56.06c.37.23-.15.61-1.36 1l-1.95.62 2.38-.23c2-.19 2.52-.06 3.27.87a3.61 3.61 0 0 0 2.83 1.2c2.28.13 6 1.45 5.29 1.88s-4 .31-6.19-.23a3.55 3.55 0 0 0-2.74.1c-.71.45-1.28.33-2.78-.55S42.5 183.39 43 183s.42-.39-.24-.18c-1 .34-3.82-.94-3.82-1.76 0-.33.32-.28.84.14s.72.48.47.06 0-.63 1-.66c.8 0 1.08-.16.65-.32a2.58 2.58 0 0 1-1.14-.95 1.72 1.72 0 0 0-1.37-.67c-.53 0-1-.23-1-.5s-.32-.5-.72-.5a5 5 0 0 1-2.11-1.22l-1.39-1.23 1.74.18 2.38.24c.36 0 .5.29.32.59s0 .49.59.42a6.87 6.87 0 0 1 2.66.66 4 4 0 0 0 2 .55c.47-.47-2.88-2.67-7.84-5.15-2.81-1.41-5-2.69-4.85-2.84s.56-.06.91.19a13.66 13.66 0 0 0 2.62 1.13c1.09.36 3.41 1.32 5.15 2.12a17.38 17.38 0 0 0 5 1.55 4.75 4.75 0 0 1 3.37 1.67c1 1 1.66 1.36 1.79 1s.92.17 1.93 1.26c2.44 2.65 2.71 2.31.68-.87-1-1.5-1.58-2.73-1.37-2.73s.69.43 1.07.95.93.86 1.24.74 1.21.45 2 1.28a4.77 4.77 0 0 0 2.34 1.5c.49 0 .9.22.9.5 0 1 3 .52 4-.62.59-.7.94-.87.95-.44s.38.58 1 .42c1-.27 1.27.09.61.91-.21.26-.29.41-.17.35s.55.16 1 .5c.6.49.79.49 1 0 .23-.67 1.62-.87 1.62-.22a2 2 0 0 1-.59 1c-.32.32-.42.75-.2 1s.64 0 1-.44c.55-.75.71-.71 1.75.37 1.5 1.56 2.44 1.51 2.85-.14l.34-1.34.44 1.21c.33.89.55 1 .85.53s.51-.5.75-.1.51.46.69.34c.48-.29 2.38 1.16 2.07 1.59-.14.19.36.41 1.09.5a1.9 1.9 0 0 1 1.65 1.11c.17.54.62 1 1 1 1 0 .42-1.14-1-2-.88-.53-1.08-.93-.75-1.47s.75-.44 2.15.92c1.21 1.17 1.78 1.46 2 1s.27-.5.3-.05.46.62 1 .62a4.39 4.39 0 0 1 2.1.92 2.62 2.62 0 0 0 1.86.69c.49-.17.58.06.34.82s-.09 1.15.57 1.38a3.12 3.12 0 0 1 1.24.86.46.46 0 0 0 .75.09c.22-.23.12-.52-.22-.65-.74-.26-.85-1.62-.13-1.62a.49.49 0 0 1 .5.49c0 .73 1.48.61 1.5-.12 0-.38.28-.28.68.25a2.15 2.15 0 0 0 1.82.74c1.61-.17 1.94 1.45.37 1.76-.74.14-.5.24.72.28s1.86-.14 1.86-.68a.69.69 0 0 0-.59-.74c-.31 0-.46-.11-.31-.26.45-.45 1.89.34 1.89 1 0 .43.31.56.87.37s.72-.15.54 0c-.55.62 2.66 3.36 3.24 2.78.83-.83 1.39-.58 1 .48-.22.69-.09 1 .41 1a1.25 1.25 0 0 1 1 .82c.17.44.46.66.64.48.36-.36 3.12 2.1 3.12 2.79 0 .24-.44.3-1 .12s-1-.12-1 .12c0 .66 1.11 1.62 1.88 1.62a3 3 0 0 1 1.61 1.12c.52.61 1 .86 1 .56a1.94 1.94 0 0 0-.72-1.18 3.33 3.33 0 0 1-.69-2.42c0-1 .16-1.4.29-.92s.4.76.63.62.66.07 1 .46a1.28 1.28 0 0 0 1.31.43 1.83 1.83 0 0 1 1.58.47 6.42 6.42 0 0 0 2 1.09c1 .29 1.17.12 1.42-1.19.17-.84.5-1.52.74-1.52.64 0 1.93 3.39 1.61 4.23-.58 1.52.67.66 1.46-1s1.83-2.34 1.83-1.24a.5.5 0 0 0 .5.5c.73 0 .61-1.77-.16-2.41-.43-.36-.83-.37-1.15-.05s-.87.08-1.61-.7l-1.11-1.19 1.27-1c1.36-1.07 1.6-1.61.74-1.61s-6.93-3.7-8.89-5.37c-1.48-1.26-1.64-1.59-1-2.15.4-.38.56-.41.36-.07s0 .56.68.52 1-.43 1-1.22c0-.62.24-1.13.52-1.13s.4.43.22 1-.09 1 .22 1 .53-.47.53-1 .23-1 .7-.78a.79.79 0 0 0 1-.49c.2-.52.49-.62.91-.31a17 17 0 0 0 3 1.34c1.29.48 2.35 1.09 2.35 1.36s.51.34 1.12.17.86-.13.55.1-.35.6.22 1.13.74.63.45-.13-.12-.81 1-.53a3.52 3.52 0 0 1 1.66.79c.17.26.84.37 1.49.26 1.11-.19 1.12-.23.19-.59l-6.55-2.59c-4.45-1.76-6-2.15-7.63-2a6.3 6.3 0 0 1-3.62-.55c-2.36-1.21-5-3.72-4.87-4.59.1-.58 0-.62-.29-.14s-.73.4-1.78-.42l-1.34-1.06 1.08-.94a3.83 3.83 0 0 1 2-.94c.5 0 .92-.23.92-.5 0-.64-.78-.63-2.15 0-1 .48-1 .46-.21-.18 1.17-.95 1.1-1.16-.5-1.49-.78-.15-3-1.8-5.09-3.85s-3.33-3.3-2.84-2.95.86.45.86.07.56-.56 1.24-.56 1.24-.28 1.24-.74.41-.69 1.12-.62 1.07-.12 1-.5.21-.62.62-.62c1 0 .94-.49-.12-1.29-1.28-1-1.72-.83-1.37.42.17.62.13.88-.09.58a3.52 3.52 0 0 0-1.7-.87c-1.6-.39-5.64-4-5.22-4.68.17-.27-.08-.47-.56-.43s-.81-.2-.74-.53-.2-.48-.62-.32c-.89.34-1-.61-.12-1 .34-.15.11-.16-.5 0s-1.11.09-1.11-.14c0-.5 3-1.73 3.4-1.37.13.13 0 .23-.35.23a.55.55 0 0 0-.57.5c0 .93 2.43.52 3-.5.38-.71.37-1 0-1s-.46-.28-.34-.62.74-.54 1.38-.44c1 .14 1.08.06.53-.6s-.84-.65-1.51-.23c-1.61 1-3.66-.06-7.35-3.83a23.6 23.6 0 0 1-3.6-4.18c0-.28.66-.52 1.46-.52a2.59 2.59 0 0 0 1.77-.49c.48-.79 2.23.38 2.23 1.5 0 1.6 2.26 1.83 3.72.37l1.22-1.22-2.1-1.48c-2.93-2.07-3.57-2.42-3.14-1.73.21.35 0 .57-.47.57s-.72-.27-.6-.62a2.61 2.61 0 0 0-.2-1.61c-.4-.91-.42-.9-.23.12.3 1.55-.64 1.41-2.59-.37-.9-.82-1.53-1.49-1.4-1.49s-.42-.68-1.21-1.53c-2.44-2.62-1.6-3.88 1.7-2.56 1.15.46 1.79 1 1.64 1.39-.31.79 1 1.38 1.47.65.22-.36.48-.31.77.14s.34.14.12-.81c-.42-1.91-.64-2.13-1.2-1.23-.4.63-.46.61-.47-.08 0-1.09 1.44-1.62 1.8-.67a1.12 1.12 0 0 0 .92.74c.34 0 .79.5 1 1.11.32 1 .37 1 .6.18a1.23 1.23 0 0 0-.6-1.42 10.17 10.17 0 0 1-1.79-1.42 3.26 3.26 0 0 0-1.37-.93 2 2 0 0 1-1.06-.76 1.5 1.5 0 0 0-1.62-.44c-.71.22-.87.14-.59-.31s.18-.51-.2-.28c-.85.54-2.61-1.58-6.18-7.48-.25-.42.27-.57 1.85-.52 1.21 0 2.1.25 2 .46-.44.72 5 6 7.37 7.08 1.29.61 2.18 1.29 2 1.51s0 .22.32 0a.77.77 0 0 1 1.09.28c.3.48.4.36.34-.43 0-.61-.37-1.11-.72-1.11a5.55 5.55 0 0 1-2.52-1.56 8 8 0 0 0-1.85-1.61c-.17 0-.24-.48-.17-1.07s-.55-1.73-2-3.1a11.79 11.79 0 0 0-2.51-2 .46.46 0 0 1-.4-.5 12.66 12.66 0 0 0-2.48-3c-1.36-1.35-2.48-2.28-2.48-2.05a10.79 10.79 0 0 0 2.05 2.49c2.13 2.18 2.43 3.45.33 1.45-1.7-1.62-3.4-4.17-3-4.55.18-.17 0-.31-.29-.31s-.47-.23-.3-.5 0-.52-.34-.54.1-.26 1-.52c1.32-.38 1.84-.32 2.55.32s.93.66 1.12.15.4-.49.61-.29.14.66-.17 1-.38.92.14 1.63c.38.52.87.78 1.07.57s.12-.64-.2-1c-1.17-1.17-.56-1.52.66-.38.84.79 1.08 1.34.74 1.67-.81.82-.61 1.1 1.5 2.05 1.45.66 2.15.76 2.54.36.86-.85.15-2-1.91-3a14.08 14.08 0 0 1-3.57-3c-.93-1.11-1.91-1.88-2.19-1.71s-.34 0-.13-.29c.36-.58-.69-2.58-2.09-4-.54-.55-.78-.57-1.08-.09s-.46.42-.7-.2c-.48-1.27-.36-2.09.18-1.24.29.45.48.51.49.15s-.48-.93-1.08-1.35c-1.25-.88-1-1.39.9-1.59.71-.07 1.16 0 1 .16s.15.9.69 1.65a5.39 5.39 0 0 1 1 2c0 .33.28.49.62.37a1 1 0 0 0 .55-1c-.08-.92-.06-.93 1-.52s1 1-.21 1.33c-1 .26-1.44 2.34-.49 2.34a.53.53 0 0 0 .49-.55c0-.37.72-.45 2.19-.23 1.31.2 2.31.13 2.5-.17.34-.54-1.14-2.19-2.16-2.41s-6-4.13-6-4.64c0-.24-.35-.44-.77-.44s-.71-.27-.64-.62c.19-1-.61-2.88-1.11-2.57-.25.15-.68-.31-1-1-.68-1.78-.67-1.79.74-1.15.68.31 1.24.87 1.24 1.23s.21.66.46.66.61.45.78 1a2.36 2.36 0 0 0 1.57 1.31c.93.23 1.08.43.59.74s-.4.42.36.43a2.25 2.25 0 0 1 1.74 1.37c.67 1.3 2 2.68 2.33 2.38.15-.14-1.3-2.93-3.16-6.1-.52-.88-1.11-1.61-1.32-1.61s-.37-.38-.37-.84a18.55 18.55 0 0 0-1.55-5.23c-.85-1.67-.42-2.1.74-.76A4.13 4.13 0 0 0 85 89.39c.3 0 .61.84.69 1.86.15 1.84.17 1.86 2.26 1.8 1.18 0 2 .13 1.82.39a1 1 0 0 0 .19 1.06 6.72 6.72 0 0 1 .83 2.21c.19.88.55 1.61.8 1.61s.46.56.46 1.24c0 1.54 1 1.66 1 .12 0-1 .08-1 .54.25.51 1.41 2.12 1.89 2.54.75.18-.49.36-.5.87 0 .87.82-.42 1.31-2.76 1-1.08-.13-1.68 0-1.57.31s1.22.6 2.5.68 2.21.35 2 .64c-.45.72.74 1.93 1.88 1.93.83 0 .89-.12.38-.74-.67-.81-.51-.91.7-.44 1.61.61.85 1.68-1.2 1.68s-2.85 1-.86 1c.61 0 .83.16.49.29-.92.37-.24 1.16 1 1.16h1.06l-1.09 1c-1.35 1.26-1.37 1.81-.09 2.22s1.24.77.37 1.7c-.5.53-.47.61.13.43.4-.13.81-.93.89-1.8.15-1.51.25-1.57 2.37-1.57 2.68 0 3.37 1 1.68 2.5-.63.54-1 1.14-.74 1.34s.49.09.62-.25a1.06 1.06 0 0 1 .95-.62c.4 0 .59.24.41.53s0 1 .51 1.46.71 1.14.55 1.4.14.58.65.71a.86.86 0 0 0 1.2-.73c.22-.84.37-.89 1-.32.39.37.57.79.38.92a8.82 8.82 0 0 0-1.16 2c-.85 1.78-.42 2.56.82 1.53.49-.4.8-.36 1.24.16a1.64 1.64 0 0 0 1.55.44c.57-.15 1 0 1 .35a4.66 4.66 0 0 0 1 1.85c.54.69.86 1.44.71 1.69-.4.66.5.52 1.21-.19.5-.49.55-.42.26.38s-.29.92.5.28.94-.62 1.35 0 .67.46 1.59-.66c1.39-1.69 2.13-1.79 1.05-.14-.93 1.42-.52 2.63.58 1.72.42-.35.7-.38.7-.08s-.4.66-.87.85c-.79.3-.77.42.25 1.25a3 3 0 0 1 1.11 1.47.51.51 0 0 0 .62.48c1.3-.17 1.86.06 1.86.75a1.3 1.3 0 0 1-.49 1c-.3.18-.36-.05-.15-.59.3-.79.24-.82-.53-.2-1.1.9-.84 2.28.43 2.28.74 0 1 .28.83 1.12-.08.61.09 1.11.37 1.11s.53-.44.53-1c0-.75.3-1 1.23-.89 1.31.11 2.22 1.38 1 1.38-.81 0-1 .95-.23 1.05l.95.13c1 .14 1.12.8.12.8-1.44 0-1.36.71.17 1.4 1.81.82 1.52 1.57-.62 1.62-1.84 0-1.84.05-.43.47a7 7 0 0 0 2.85.15c.92-.19 1.42-.08 1.42.3s-.2.46-.46.3a.56.56 0 0 0-.77.22 2 2 0 0 1-1.4.55c-.81 0-.67.16.53.51s1.61.73 1.61 1.72c0 .72.21 1.12.5.94a.6.6 0 0 0 .19-.82c-.21-.33 0-.4.68-.2a1.77 1.77 0 0 0 1.76-.44c1-1 1.33-1 1.33 0a.74.74 0 0 1-.74.74c-.58 0-.51.24.27 1a2 2 0 0 1 .71 2c-.17.55-.07 1 .22 1s.55-.61.58-1.36l.06-1.37.54 1.38c.31.79.82 1.28 1.18 1.14a.83.83 0 0 1 1 .31c.2.32.12.41-.2.22a.61.61 0 0 0-.85.17c-.17.28-.85.37-1.52.21-1.63-.42-1.53.39.16 1.18 1.17.54 1.44.51 2-.23.34-.46.61-.64.6-.38 0 .55-1.74 2.7-2.17 2.7-.16 0-.29-.33-.29-.74 0-.85-.43-.93-1.46-.28-.58.36-.44.58.65 1.08a5.33 5.33 0 0 1 2.07 1.89c.48.9.69 1 .71.44 0-1 2.1-1.82 2.39-1a1 1 0 0 0 .89.56.74.74 0 0 1 .7.78c0 .42.2.65.45.5s.59.32.77 1 .1 1.45-.24 1.67c-.89.54-.34 2.2.77 2.3.77.08.79.05.11-.15-.48-.14-.86-.5-.86-.8s.14-.39.32-.21.63.07 1-.24c.95-.79 1.18-.36.71 1.34-.27 1-.67 1.42-1.24 1.31s-.77.11-.5 1.2.18 1.24-.28.95-.49-.14 0 .78l.62 1.16.39-1.06a1.19 1.19 0 0 1 2.35.27c.11.76.51 1.07 1.36 1.07 1.23 0 1.49.75 1.33 3.81a1.41 1.41 0 0 0 .71 1.36c.54.21.68.07.48-.45s-.05-.75.25-.75.32-.27-.09-.76-.49-.92-.09-1.31.62-.23.82.64c.42 1.83.11 4.92-.44 4.38a2 2 0 0 0-1.24-.46c-.65 0-.63.13.1.68 1.06.81 1.15 1.87.12 1.47-.41-.15-.74 0-.74.32s.78.54 2.48.42c1.36-.09 2.48 0 2.48.2s-.45.37-1 .37c-1.38 0-1.25.83.17 1.1 1.08.21 1.16.09 1-1.65s.43-2.93 1.06-1.9a9.78 9.78 0 0 1 .31 3.05c0 2.52.63 2.77 1.06.39.12-.67.25.22.28 2 .08 4.16-.2 5.09-1.82 6.09l-1.33.83h2.14c1.49 0 2-.16 1.78-.56s-.13-.43.22-.21c.81.49.76 2.94 0 2.47-.35-.19-.46-.17-.26.05a.77.77 0 0 1 0 .95 2 2 0 0 0 .24 1.78l.65 1.24v-1.49c-.06-2.84.87-5.17 2.07-5.2.43 0 1.63 10.25 1.29 11.12a1.71 1.71 0 0 1-1.43.84c-.91 0-1 .1-.22.29s.84.54.61 1.55c-.28 1.26-.26 1.28.45.36.42-.53.76-.72.76-.43s.37.68.81.86c.69.26.72.19.18-.46-.87-1-.46-1.3 1.43-.93 1.11.23 1.52.54 1.33 1s-.08.58.16.43.55.8.69 2.09.07 2.42-.24 2.53-.3.31.06.53a1.06 1.06 0 0 1 .22 1.27c-.34.88-.37.88-.73 0s-.37-.86-.4-.07a1.26 1.26 0 0 0 .67 1.16c.77.3.52 1.49-.56 2.58-.76.77-.87 2.57-.15 2.57.27 0 .5-.36.5-.81 0-.66.13-.69.74-.18s.75.48.75-.07a2.11 2.11 0 0 1 1.06-1.35c1-.65 1.05-.61.74 1.34-.18 1.1-.13 2.12.11 2.27.76.46 1.22 9.17.53 10-.93 1.12-.2 1.38 1.23.44 1.23-.81 1.29-.8 1.29.14A104.22 104.22 0 0 0 177.4 236c.3 1.09 1.1 3.39 1.79 5.12s1.1 3.27.94 3.43c-.47.47-1.94-1.37-3-3.73-.64-1.45-1.1-2-1.45-1.62s-.19.94.49 1.88c2 2.75 1.62 3.15-.8.83-1.37-1.31-2.49-2.82-2.49-3.36s-.13-.85-.29-.69-1-.24-2.86-1.86c-3.71-3.26-8.84-6.2-10.8-6.2-.71 0-.49 1.09.41 2.09a5 5 0 0 1 .86 3.14c0 1.21.18 2.21.39 2.21s.39-.35.39-.78.23-.62.57-.41.44.13.24-.18a1.83 1.83 0 0 1 0-1.33c.24-.62.47-.67 1-.23a1.87 1.87 0 0 1 .68 1.13c0 .31-.25.22-.53-.19-.48-.66-.51-.66-.33 0 .11.41.42.75.7.75a13.73 13.73 0 0 1 3 3c1.38 1.64 3.75 4.19 5.27 5.67s2.65 2.87 2.51 3.09.1.28.52.11.77-.05.77.25.61.87 1.37 1.28c1.93 1.06 4.35 3.15 4 3.48-.15.15-.81-.14-1.47-.66ZM149.53 245a14.69 14.69 0 0 0-2.68-.52c-1.76-.23-2.27-.18-1.74.18s4.92.79 4.42.34Zm-1.94-2.3c0-.27-.32-.49-.71-.49a1.33 1.33 0 0 0-1 .49c-.16.28.16.5.72.5s.99-.2.99-.52Zm19-2c-.25-.95-1.14-1.41-1.14-.6a3.27 3.27 0 0 0 1.29 1.59 2.39 2.39 0 0 0-.15-.99Zm-1.59-2.23c-.34-.4-.82-.61-1.06-.46-.64.39 0 1.2.9 1.2.63 0 .66-.14.16-.74Zm-28-.28c.54-.87-.07-1.05-.75-.23-.39.47-.43.76-.1.76a1.14 1.14 0 0 0 .79-.53Zm37.94.05A9.66 9.66 0 0 0 174 236c-.67-1.4-1.12-1.76-2.23-1.76-1.51 0-1.55 0-1.1 1.19.23.62.51.67 1.31.23s1-.41.73.22a.77.77 0 0 0 .42 1 1.2 1.2 0 0 1 .71 1c0 .41.23.75.5.75a.49.49 0 0 0 .5-.47Zm-34.47-.91c-.15-.16-.55 0-.89.33-.49.49-.43.55.28.28.43-.19.7-.47.55-.61Zm-17.62-3.11c0-.29-.23-.39-.5-.22s-.5.41-.5.53.23.22.5.22a.52.52 0 0 0 .44-.53Zm35-.83c-.16-.16-.28.13-.26.66s.14.69.3.29a1 1 0 0 0 0-1Zm-21.88-.13c.83-.35.72-.43-.65-.46-1 0-1.52.17-1.33.46.32.58.59.58 1.94 0Zm32.5-.25a2 2 0 0 0-1.12-.74c-.28 0-.22.33.13.74a1.91 1.91 0 0 0 1.11.74c.23 0 .18-.32-.16-.75Zm-41.68-.55a3.25 3.25 0 0 0-1.51-.19c-.84 0-1.4.19-1.26.43.25.39 2.77.18 2.77-.24Zm-7.07-.54a2.86 2.86 0 0 0-1.49 0c-.34.14.06.24.88.23s1.05-.1.57-.23Zm14.52-.43c0-.12-.23-.22-.5-.22a.52.52 0 0 0-.5.53c0 .29.23.39.5.22s.46-.4.46-.53Zm35.52-2.07c-.17-.16-.29.13-.26.66s.14.69.3.3a1 1 0 0 0 0-1Zm-56.3-.48c-.57-.22-.57-.33 0-.69s.24-.43-.77-.44c-1.39 0-1.42 0-.5.72a3.21 3.21 0 0 0 1.46.7c.27 0 .19-.14-.19-.29Zm45-.95a2 2 0 0 0-1.26-.69c-.56 0-.57.19-.1.95.32.52.89.83 1.26.69s.53-.42.1-.94Zm-11.19.06c-.15-.4-.5-.6-.78-.43s-.23.47.18.73c.9.58.93.56.6-.3Zm27.41-.61c-.17-.17-.28.13-.26.65s.14.7.3.3a1 1 0 0 0 0-.95Zm-74.09.53a1.88 1.88 0 0 0-1.24 0c-.35.14-.07.25.62.25s.95-.11.62-.22Zm13.27-.39c1.77 0 2.67-.06 2-.17l-1.23-.2 1.23-.68 1.22-.68h-1.24a64.19 64.19 0 0 0-6.94 1.21c-5.43 1.14-5.53 1.18-2 .84 2.1-.18 5.18-.33 6.96-.32Zm6 .38a1 1 0 0 0-1 0c-.16.16.13.28.65.26s.7-.14.3-.3Zm33-.37c.7.23.76.19.25-.18s-.53-.71.25-1.77c1.13-1.55 1.48-1.6 2.17-.32s1.49 1.32 1.49.28c0-.39-.45-.85-1-1s-1-.52-1-.78-.43-.46-1-.46a1.67 1.67 0 0 1-1.27-.5 1.71 1.71 0 0 0-1.3-.5c-.92 0-.94.08-.22 1.11a4.7 4.7 0 0 1 .76 2.36c0 1.2 0 1.21-.74.28s-.73-.86-.73.68c0 1.34.12 1.55.68 1.09a1.85 1.85 0 0 1 1.61-.27Zm15.46-.23c.12-.34-.09-.57-.46-.51a4.59 4.59 0 0 1-1.91-.38 20.34 20.34 0 0 0-2.73-.84c-1.06-.24-.7.09 1.24 1.12 2.87 1.53 3.54 1.63 3.86.61Zm6.06-1.11c0-.27-.22-.35-.5-.19a1.11 1.11 0 0 0-.49.81c0 .27.22.36.49.19a1.07 1.07 0 0 0 .5-.81Zm-24.51-.92c0-.82-.27-1.42-.54-1.33s-.44.31-.37.5a6 6 0 0 1 .12 1.32c0 .54.19 1 .42 1s.4-.68.37-1.49Zm8.88.49c-.17-.28-.4-.5-.52-.5s-.22.22-.22.5a.5.5 0 0 0 .52.49c.29 0 .39-.22.22-.49Zm-57.3-1c.58-.4.5-.45-.37-.2s-1 .15-.81-.47 0-.77-.44-.77a1.24 1.24 0 0 0-1 1c-.2.77 0 1 .81 1a3.77 3.77 0 0 0 1.81-.54Zm8.28-2c-.31-1.21 3.41-2.71 5.63-2.27.84.17 1.42.14 1.29-.06s.43-.41 1.22-.47 1.44.13 1.44.57c0 .81 1.36 1.42 1.59.71s2.41.17 3.54 1.3a2.84 2.84 0 0 0 2.19.65l1.37-.16-1.3-.74c-2-1.17-28.38-14.24-29.28-14.54a6.17 6.17 0 0 0-2.73.38 55.13 55.13 0 0 1-7.49 1.27c-3.07.34-5.48.77-5.37 1s2.61.13 5.56-.1c3.85-.3 5.41-.26 5.55.13s.37.31.75-.15c.78-1 4.12-1.43 4.91-.66a8.63 8.63 0 0 0 2.73 1.23c1.16.34 2.11.81 2.11 1.05s.83.64 1.86.9l1.86.47-1.42.58c-1.76.71-15.74 2.54-19.44 2.54-2.11 0-2.71.17-2.71.76s.22.65.89.29a4.71 4.71 0 0 1 2.6-.12c1.84.38 8.5-.3 8.92-.91a21.69 21.69 0 0 1 5-1c2.59-.32 5.55-.72 6.58-.88 1.67-.26 1.84-.19 1.56.64s-.2.79.22.2.67-.58 1.55.37a3.43 3.43 0 0 0 1.89 1.11 6.47 6.47 0 0 1 2.27.75 6.67 6.67 0 0 0 2.38.73c.53 0 .85.2.7.44a10.8 10.8 0 0 1-3.69.77c-5.06.49-20.4 3.09-20.8 3.51s4.81 0 8.08-.73c2.61-.58 7.2-.24 7.2.53 0 .24-.62.48-1.37.51h-1.36l1.48.35c1.86.44 2.39.18 2.08-1Zm-2.19-1.26a1 1 0 0 1 .95 0c.4.16.28.28-.3.3s-.81-.1-.65-.26Zm-5.5 1.94a1 1 0 0 0-1 0c-.16.17.13.29.65.26s.7-.14.3-.3Zm11.32-.65a.5.5 0 1 0-.5.5.5.5 0 0 0 .5-.51Zm51.15-1.13c-.4-.41-.55-.35-.55.21 0 1 .67 1.65.92.91a1.25 1.25 0 0 0-.37-1.13Zm-58.5.79a1 1 0 0 0-1 0c-.16.17.13.28.65.26s.69-.14.3-.3Zm11.57-.16c-.17-.27-.41-.49-.53-.49s-.22.22-.22.49a.51.51 0 0 0 .53.5c.29-.01.39-.23.22-.51Zm45.11-3.5a3.17 3.17 0 0 0-2.4-.91h-1.54l1.62.52c.88.28 1.61.77 1.61 1.09a2.17 2.17 0 0 0 .62 1.2c1 1 1.1-.83.09-1.95Zm-21.48.36c-.31-.37-.44-.79-.29-.93s-.19-.38-.73-.52c-.93-.25-.95-.2-.21.93.43.65 1 1.18 1.28 1.18s.29-.25 0-.66Zm-45.42-.33c2 0 1.32-.77-.79-.86-1.49-.06-2 .11-2 .69s.27.67.78.47a7.07 7.07 0 0 1 2-.3Zm18.69-.82a1.88 1.88 0 0 0-1.24 0c-.35.14-.07.25.62.25s.95-.12.62-.26Zm-3.23-.51a3.89 3.89 0 0 0-1.73 0c-.49.12-.09.22.86.22s1.35-.11.87-.23Zm32.26-1.34c.48-1.06.41-1.22-.67-1.43s-1.2 0-1.2.86c0 2 1.06 2.34 1.87.57Zm13.75.69a1.31 1.31 0 0 0-1-.5c-.41 0-.6.22-.43.5a1.35 1.35 0 0 0 1.05.49c.36-.01.55-.23.38-.5Zm-76.15-.5c0-.27-.73-.47-1.61-.44l-1.61.06 1.49.38c.82.21 1.54.41 1.61.44s.12-.18.12-.45Zm21.83-.3c0-.11-.44-.2-1-.2s-1 .21-1 .46.44.33 1 .18 1-.35 1-.45Zm58.63.46a1 1 0 0 0-.95 0c-.16.16.13.28.65.26s.7-.14.3-.3Zm-54.07-1a1 1 0 0 0-1 0c-.17.16.13.28.65.25s.69-.13.3-.29Zm44.8-.16a6.07 6.07 0 0 0-2-.43c-1 0-1 0 0 .43a5.91 5.91 0 0 0 2 .42c1 0 1 0 0-.42Zm-19.41-.75c-.16-.4-.38-.74-.48-.74s-.2.34-.2.74.22.75.48.75.36-.33.2-.75Zm34.93.16c-.31-.92-1.38-1.07-1.38-.2a.79.79 0 0 0 .79.79c.43 0 .7-.26.59-.59Zm-22.71-1.43c0-.56-.22-.88-.5-.71a1.32 1.32 0 0 0-.49 1c0 .39.22.72.49.72s.5-.39.5-1.01Zm-56.55-.49a.5.5 0 0 0-.5-.47c-.27 0-.5.35-.5.78s.23.63.5.46a1.07 1.07 0 0 0 .5-.77Zm70.44.52a.5.5 0 1 0-.49.5.49.49 0 0 0 .49-.5Zm-77.67-.95c-.58-.56-4.51-.87-5-.39-.2.19.8.35 2.21.35 1.62 0 2.56.22 2.56.58s.15.43.34.24a.56.56 0 0 0-.12-.78Zm5-.15c.29-.21.13-.37-.37-.37a.82.82 0 0 0-.87.81c0 .46.17.63.37.38a6.79 6.79 0 0 1 .86-.82Zm-2 .11c-.17-.27-.41-.49-.53-.49s-.22.22-.22.49a.52.52 0 0 0 .53.5c.3 0 .4-.23.23-.5Zm47-.49c-.14-.55-.34-1-.45-1s-.19.45-.19 1 .2 1 .45 1 .36-.46.22-1Zm26.65-.22a1.28 1.28 0 0 0-.5-1 1.67 1.67 0 0 1-.49-1.27c0-.78-.36-1-1.87-1a14.9 14.9 0 0 1-3.6-.52 102.28 102.28 0 0 0-10.82-2.43c-1.19.07 3.65 1.56 7.89 2.44 5.78 1.2 6.45 1.49 7.59 3.22.91 1.39 1.8 1.66 1.8.55Zm-23.81-.78a.49.49 0 0 0-.47-.49 1 1 0 0 0-.77.49c-.17.28 0 .5.46.5s.81-.22.81-.5Zm-36.13-.33a1 1 0 0 0-.95 0c-.16.16.13.28.65.25s.7-.14.3-.29Zm-26.63-1.15A15.05 15.05 0 0 0 69 209c-.82-.05-.86 0-.25.43a5.52 5.52 0 0 0 2.48.43h1.74Zm-6.45-1a.58.58 0 0 0-.78-.22c-.69.42-.56.72.31.72.51.05.66-.22.51-.5Zm65.69.36-1.2-.87c-.91-.67-.94-.66-.31.14a2.15 2.15 0 0 0 1.2.87c.31.05.45-.06.34-.14Zm6.29-1.59c-.18-.71-.53-1.16-.77-1a.56.56 0 0 1-.76-.23 1.84 1.84 0 0 0-1.4-.5 6.68 6.68 0 0 1-2.71-1c-2-1.18-2.09-1.2-2.09-.24 0 .41.24.69.53.62s1 .65 1.49 1.6c.76 1.39 1.27 1.74 2.56 1.8a4.07 4.07 0 0 0 2.24-.42c.45-.36.62-.26.62.39 0 .49.14.75.3.58a2.35 2.35 0 0 0 0-1.59Zm-3.29 0c.6-.72 2-.53 2 .28s-1 .52-1-.18c0-.5-.09-.5-.29 0a1 1 0 0 1-.76.62c-.33.06-.3-.25.08-.7Zm-2.46.22a.52.52 0 0 0-.53-.5c-.28 0-.38.23-.21.5s.4.5.52.5.25-.21.25-.48Zm-7.94-.53c0-.28-.22-.38-.49-.21s-.5.4-.5.52.22.22.5.22a.51.51 0 0 0 .52-.51Zm32.25.09c0-.24-.32-.57-.72-.72a4.55 4.55 0 0 1-1.55-1.89c-1.15-2.21-1.61-2.58-4.18-3.35-1.23-.37-3.63-1.1-5.33-1.64a14 14 0 0 0-3.91-.8c-.45.09 1.91 1 5.25 2.1s6.18 2.25 6.35 2.67c.36.94 3.22 4.07 3.71 4.07a.42.42 0 0 0 .38-.44Zm-33.35-2.81c-.17-.17-.57.21-.9.85-.57 1.11-.56 1.13.3.31.52-.45.79-.97.63-1.14Zm6.8 1.26a.56.56 0 0 0-.77-.22c-.69.42-.56.72.31.72.45.05.66-.2.51-.48Zm-28.53-1.24c-.35-.42-.76-.63-.92-.48s0 .5.45.75c1.05.69 1.22.61.5-.25Zm7.7.25a.57.57 0 0 0-.78-.22c-.69.42-.56.72.31.72.46.04.66-.2.5-.48Zm30.6.16a1 1 0 0 0-.95 0c-.16.17.13.29.65.26s.7-.14.3-.3ZM122.56 202c-.43-1-.74-1.32-.75-.78 0 1 .94 3 1.27 2.69a4 4 0 0 0-.52-1.91Zm3.5.68c-.16-.16-.28.13-.26.66s.14.69.3.3a1 1 0 0 0-.04-1.01Zm33.44-1.12a.5.5 0 0 0-.5-.5.5.5 0 0 0 0 1 .5.5 0 0 0 .5-.55Zm-53.91-1.26c-.16-.42-.4-.66-.53-.53s-.09.47.09.77c.49.78.78.63.44-.24Zm-36.38-.51c0-.42-.23-.63-.51-.46a.56.56 0 0 0-.22.77c.43.64.73.51.73-.36Zm3.59.45a1.88 1.88 0 0 0-1.24 0c-.34.14-.06.25.62.25s.96-.16.62-.3Zm3 0a1.88 1.88 0 0 0-1.24 0c-.34.14-.06.25.62.25s.94-.16.6-.3ZM73.67 199c.51-.33.11-.38-1.24-.18-2.45.38-2.87.67-1 .66a5.31 5.31 0 0 0 2.24-.48Zm74.39-2.71c-1.65-2.72-3.21-3.75-5.65-3.75a2.68 2.68 0 0 1-1.54-.45c-.61-.58-5.78-2.1-6.09-1.79-.13.12.19.37.71.54s1.39.48 1.93.7c4.89 2 6.38 2.44 6.8 2.17s.36.14.14.88c-.29 1-.24 1.06.29.55a2.27 2.27 0 0 1 1.06-.62c.64 0 .45 1.39-.23 1.66-.34.14 0 .27.68.29 1.36 0 3.19 1.52 1.89 1.52-.39 0-.27.29.31.71 1.54 1.13 1.46.5-.3-2.41Zm-59.19.3a12.18 12.18 0 0 1-1.52-2.58c-.41-1-.75-1.43-.76-1.09 0 1.66 2.49 6.06 2.88 5 .1-.22-.17-.82-.6-1.29Zm49 1.38c-.57-.57-5.71-2-6-1.73-.11.11.66.44 1.72.73a12.66 12.66 0 0 1 2.65 1c1 .68 2.34.66 1.66 0Zm-41.1-.47a1.09 1.09 0 0 0-.77-.5.49.49 0 0 0-.47.5c0 .27.35.49.77.49s.61-.18.44-.45ZM56.8 197a3.34 3.34 0 0 0-1.49-.48c-.66 0-.66.06 0 .48a3.22 3.22 0 0 0 1.49.48c.66.04.66-.01 0-.48Zm45.9-1c.32-.51-.57-2-1.19-1.95-.82 0-1.63.93-1.3 1.47s.47.45.8-.07.43-.48.43.2c.01.99.76 1.19 1.26.35Zm25.3-.48c.95-.41.95-.42-.1-.46a1.78 1.78 0 0 0-1.39.46c-.37.62.12.62 1.49.03Zm12.65-.49a.5.5 0 0 0-1 0 .5.5 0 0 0 1 0Zm-3-.2c0-.1-.35-.32-.77-.49s-.64-.08-.47.2c.29.47 1.24.7 1.24.29Zm1.49-.82c0-.29-.23-.39-.5-.22s-.49.4-.49.52.22.22.49.22a.51.51 0 0 0 .52-.53Zm-44.93-.91c.15-.24 0-.57-.45-.72s-.62 0-.43.44c.33.86.52.92.9.31Zm64.86-.39a1 1 0 0 0-1 0c-.16.16.13.28.65.25s.7-.13.3-.29Zm4.27-.36c-.35-.38-.72-.62-.81-.53-.3.28.45 1.22 1 1.22.25.03.18-.28-.17-.66Zm-28.17-.58c0-.12-.22-.21-.5-.21a.5.5 0 0 0-.49.52c0 .29.22.39.49.22s.52-.37.52-.5Zm8.82-.91c-.35-.38-.72-.62-.81-.52-.3.27.45 1.22 1 1.22.25.03.19-.29-.18-.67Zm18.76-1.65c-.16-.16-.28.13-.26.65s.14.7.3.3a1 1 0 0 0 0-.95Zm-35.52.42c0-.5-1.2-1-2.27-1-.39 0-.71.24-.71.52s.23.39.5.22.5-.07.5.22.44.53 1 .53 1-.22 1-.46Zm-6.94-1.58c0-.28-.23-.38-.5-.22s-.5.41-.5.53.23.22.5.22a.51.51 0 0 0 .52-.5Zm38.2-.22c.42-.51.44-.74.05-.74a.52.52 0 0 0-.55.46c0 .25-.39.56-.87.69-.73.19-.74.23-.06.28a2 2 0 0 0 1.43-.69Zm-33.65-.08a1 1 0 0 0-1 0c-.16.16.13.28.65.25s.7-.14.3-.29Zm17.77 0c0-.33-.2-.57-2.57-3.17a7.79 7.79 0 0 0-4.13-1.9 86.22 86.22 0 0 1-18.22-7.05c-1.14-.6-2.37-.58-2.37 0 0 .27.46.5 1 .5.84 0 .93.16.5 1-.7 1.29-.65 1.54.23 1.2.41-.15.75 0 .75.27s.33.54.74.54c.9 0 1-.8.12-1.17-.34-.15 0-.14.87 0a5.3 5.3 0 0 1 2 .72 2.33 2.33 0 0 0 1.37.43c.49 0 .89.2.89.43s.89.58 2 .76 2 .5 2 .74.72.57 1.61.74c2.5.49 7 1.91 7.54 2.37a2.39 2.39 0 0 0 1.36.42c.5 0 .9.21.9.48 0 .61 2.38 3 3 3 .27 0 .48-.15.48-.32Zm-19.35-.67c0-.27-.61-.49-1.36-.49-1.07 0-1.2.12-.62.49.99.67 2 .67 2 .04Zm39.69 0a.5.5 0 1 0-.5.5.5.5 0 0 0 .55-.46Zm-11.19-1.52c-.42-.69-.72-.56-.72.31 0 .43.23.64.5.46a.56.56 0 0 0 .24-.73Zm-17.58.05c0-.27-.34-.36-.75-.2s-.74.38-.74.48.33.2.74.2.77-.17.77-.44Zm15.87-.05c0-.29-.22-.38-.49-.22s-.5.41-.5.53.22.22.5.22a.51.51 0 0 0 .51-.49Zm-76.4-.46c0-.28-.39-.49-.87-.49-.74 0-.76.07-.12.49.95.64 1.01.64 1.01.04Zm73.42 0a.51.51 0 0 0-.52-.5c-.29 0-.39.22-.22.5s.4.49.53.49.23-.18.23-.45Zm-101.89-1.24c-.45-.46-1-.68-1.14-.51s.12.63.65 1c1.23.91 1.56.58.49-.49Zm85-.38c-.69-.36-1.35-.57-1.48-.47-.35.29 1.12 1.12 2 1.12.43 0 .21-.29-.47-.65ZM64 183c-.16-.34-.43-1-.6-1.49-.37-1.06-.87-1.1-1.68-.12-.5.61-.47.74.21.74a1.27 1.27 0 0 1 1.1.74c.16.42.51.75.77.75s.4-.25.2-.62Zm52.54-.88a.57.57 0 0 0-.77-.21c-.69.42-.56.72.31.72.46.03.67-.2.5-.48Zm7.45-.49c-.92-1-2.24-1.34-2.24-.57 0 .42 1.83 1.47 2.68 1.54.29.04.09-.39-.43-.94Zm2-.46a1.17 1.17 0 0 0-.85-.53c-.34 0-.3.29.1.76.76.85 1.32.67.76-.2Zm33 .46c0-.27-.34-.49-.74-.49s-.75.22-.75.49.34.5.75.5.75-.19.75-.47Zm-21.65-1.48a5.5 5.5 0 0 0-.93-1.49c-.11 0 0 .67.33 1.49s.7 1.48.93 1.48.08-.63-.32-1.45Zm-15.56-.5a.49.49 0 0 0-.46-.5 1.09 1.09 0 0 0-.78.5c-.17.27 0 .5.47.5s.78-.15.78-.47Zm27.79 0c0-.26-1-.54-2.11-.62s-2.14-.41-2.18-.78c-.14-1.45-.2-1.61-.66-1.61-.26 0-.71.43-1 1-.81 1.51.58 2.52 3.49 2.52 1.36.02 2.47-.16 2.47-.45Zm-1.86-2.22a.64.64 0 0 0-.5-.66c-.34-.12-.62.17-.62.62.01.9.9.93 1.13.1Zm-35.85-.57c0-.12-.22-.22-.49-.22a.51.51 0 0 0-.5.53c0 .29.22.39.5.22s.5-.39.5-.47Zm2 .58c0-.11-.35-.33-.77-.49s-.65-.08-.47.19c.29.47 1.24.7 1.24.3Zm20.83-1.22c0-.81-2.44-3.05-3.33-3.05-1.18 0-11.75-3.62-12-4.09a.66.66 0 0 0-.56-.38 51.23 51.23 0 0 1-5.18-2.27c-2.62-1.25-5-2.16-5.38-2s-.28.27.24.29.74.31.59.72.55.87 2.62 1.37 2.83.92 2.73 1.37.19.58.94.39a3.34 3.34 0 0 1 2.63.86c.85.64 1.8 1.06 2.11.94a5.25 5.25 0 0 1 2.48.81 9.12 9.12 0 0 0 2.52 1c.35 0 .63.2.63.43s.73.58 1.61.75c2.59.51 5.83 2 5.83 2.66a.7.7 0 0 0 .75.62c.41 0 .74-.19.74-.42Zm10.64-1.6c-.42-.68-.72-.55-.72.32 0 .42.23.63.51.46a.58.58 0 0 0 .21-.72Zm-31.33-.18c-1.13-.86-1.64-.88-1.64-.09 0 .34.57.65 1.26.69 1.18.07 1.21.03.38-.6Zm6-1.47c-2.48-.89-2.74-.89-1.52 0a4.21 4.21 0 0 0 2.23.71c1.11.01.99-.12-.65-.68Zm20.09.26a.5.5 0 1 0-.5.5.5.5 0 0 0 .56-.5Zm-22.87-2.73c0-.27-.18-.16-.45.25a3 3 0 0 0-.45 1.49c.09.76.91-.8.96-1.74Zm14.63 1.61a13.21 13.21 0 0 0-1.14-1.53 27.3 27.3 0 0 1-2-3c-3.11-5.2-3.05-5.15-5.53-5.47a14.26 14.26 0 0 1-3.74-1 7 7 0 0 0-2.64-.73c-1.19 0-1.18 0 .38 1a6.94 6.94 0 0 0 2.78 1 10.45 10.45 0 0 1 2.77.56c1.17.41 1.43.68.94 1s-.37.43.43.44 1.83 1 3.59 3.35c2.42 3.27 4 5.08 4.36 5.08.1 0 0-.27-.17-.62Zm15.77.29a1 1 0 0 0-1 0c-.16.16.13.28.65.25s.7-.13.3-.29Zm-26.38-.66c-1-.63-2.13-.63-1.74 0a1.84 1.84 0 0 0 1.4.49c.94 0 1-.1.4-.49Zm-14-1c-2.38-1.28-3-1.28-1.11 0a5.8 5.8 0 0 0 2.22 1c.45 0-.06-.44-1.07-.99Zm38.42.65a1 1 0 0 0-.95 0c-.17.17.13.29.65.26s.69-.14.3-.3Zm-31.62-.62a.53.53 0 0 0-.17-.74c-.59-.36-2 .18-2 .76s1.85.58 2.21-.01Zm33.76-1.52a1.1 1.1 0 0 0-.8-.5c-.28 0-.36.23-.19.5a1.1 1.1 0 0 0 .8.5c.31.01.39-.22.23-.49Zm-35.6-1.36c.14-.41-.32-.62-1.32-.62-1.53 0-2.07.69-.91 1.16 1.03.41 2.03.15 2.27-.53Zm31.38.37c-1-.62-1.49-.62-1.49 0 0 .27.51.49 1.12.49.94 0 1.01-.09.41-.48Zm-2-1a1.09 1.09 0 0 0-.5-.78c-.27-.17-.49.05-.49.47s.22.77.49.77a.49.49 0 0 0 .56-.41Zm6.74-1.39c-.16-.16-.28.13-.25.66s.14.69.29.29a1 1 0 0 0 0-1Zm-22.62.39a1.07 1.07 0 0 0-.49-.77c-.28-.17-.5 0-.5.47s.22.77.5.77a.48.48 0 0 0 .55-.42Zm-7.44-3.5a.48.48 0 0 0-.47-.49 1.07 1.07 0 0 0-.77.49c-.17.27 0 .5.47.5s.83-.18.83-.45Zm-14.88-2.09c0-.55-1.37-.48-1.72.08-.16.26.16.43.72.39s1.06-.2 1.06-.42Zm6.71-.29c-.15-.15-.56 0-.89.34-.49.49-.43.54.28.28.52-.14.82-.42.67-.57Zm14.3.15c-.16-.41-.43-.74-.61-.74s-.59-.62-.92-1.37a11.52 11.52 0 0 0-1.77-2.6c-1.14-1.22-1.34-1.59-1.34-2.39 0-.22-1.29-.81-2.85-1.3a65.92 65.92 0 0 1-7.19-3c-2.5-1.24-4.46-1.94-4.63-1.65-.49.79.8 1.93 2.2 1.94a4.51 4.51 0 0 1 2.27.75 3.74 3.74 0 0 0 1.85.73c1.11 0 4.37 1.68 5.75 3a5.15 5.15 0 0 0 1.31 1c.47 0 3 2.73 2.89 3.11s2.38 3.34 2.93 3.34c.28-.03.33-.35.17-.77Zm-9.48-.83c.21-.56-12.56-6.82-13.32-6.52-1.32.5-.69 1.09 1.54 1.43 1.23.18 2.23.49 2.23.68s-1 .29-2.23.22-2.23.08-2.23.34.61.48 1.36.5c1.21 0 1.25.08.37.46s-.73.43.62.46c.89 0 1.62-.18 1.62-.45s.37-.33.82-.16.71.13.47-.26c-.45-.72 2 .23 3.21 1.25 1.07.88 4.58 2.62 5 2.49a.85.85 0 0 0 .63-.39Zm17.92-2.15c.17-.44 0-.74-.43-.74s-.6.3-.43.74.35.75.43.75a1.77 1.77 0 0 0 .49-.7Zm11.36-1.24c-1-1.16-1.9-1.27-2.28-.28-.19.5.14.78 1.06.92l1.7.28c.26.09.05-.32-.42-.87Zm-35-3.81a2.76 2.76 0 0 0-1.49 0c-.34.13.06.24.88.23s1.16-.06.68-.18Zm-1.84-1.41c1-1.15.37-1.27-1-.21-.91.7-1 .95-.42 1a2.19 2.19 0 0 0 1.49-.74ZM118 151a2.78 2.78 0 0 1-.83-1.21 1.27 1.27 0 0 0-1-.86c-.45 0-.41.15.11.49s.54.48.29.49 0 .45.45 1 1 .87 1.18.71.11-.43-.2-.62Zm-18.55-.09c0-.28-.35-.5-.77-.5s-.64.22-.47.5a1.07 1.07 0 0 0 .77.49.48.48 0 0 0 .49-.49Zm6.22-.53c.31-.5-2.09-2.94-2.89-2.95s-1 1.55-.42 2.12c.42.42.56.41.56 0a.71.71 0 0 1 .78-.6c.42 0 .63.22.46.5a1 1 0 0 0 .75 1.49 1.11 1.11 0 0 0 .76-.53Zm-8.64-1.09c-.49-.49-.55-.43-.28.28.19.48.47.76.62.61s.02-.56-.32-.89Zm27.72.13a.51.51 0 0 0-.53-.5c-.28 0-.38.22-.21.5s.4.49.52.49.24-.22.24-.49Zm8.93-.78c0-.12-.22-.22-.5-.22a.51.51 0 0 0-.49.53c0 .29.22.39.49.22s.52-.4.52-.53Zm-38.2-.46a2 2 0 0 0-1.12-.75c-.27 0-.22.34.13.75a2 2 0 0 0 1.11.74c.3 0 .24-.33-.1-.74Zm20.34-.52c0-1.63-4-6.18-5.36-6.18a16.34 16.34 0 0 1-3.56-1.28l-2.85-1.27-1.68 1-1.68 1 1.55-.3a2.28 2.28 0 0 1 2.21.42 1.74 1.74 0 0 0 1.51.5 3.61 3.61 0 0 1 2.22.69c.77.5 1.4.83 1.41.73.07-.57 3.25.63 3.25 1.22 0 .38.23.7.5.7s.5.4.5.9a2.36 2.36 0 0 0 .59 1.48c.64.73 1.41.86 1.41.39Zm-11.16-7.17c-.18-.28 0-.36.47-.19.86.33 1 .68.3.68a1 1 0 0 1-.75-.49Zm31.39 6.85c-.31-.92-1.38-1.07-1.38-.2a.79.79 0 0 0 .79.79c.45 0 .72-.26.61-.59Zm-34.93-.65c-.36-.92.19-.92 1.8 0 1.43.81 2 .91 2 .35s-4.36-2.59-5.09-2.31-2 2.21-1.41 2.21a.91.91 0 0 0 .69-.62c.17-.46.47-.39 1.14.24 1.12 1.07 1.25 1.09.89.13Zm-8.62-.25a.51.51 0 0 0-.52-.5c-.29 0-.39.23-.22.5s.4.5.53.5.23-.23.23-.5Zm19.76-1.08c-.2-1.36-1.4-1.82-1.4-.53 0 1 .4 1.61 1.11 1.61.26 0 .39-.44.31-1.08Zm-3.09-.77c-.16-.16-.28.13-.26.65s.14.7.3.3a1 1 0 0 0-.02-.95Zm-1.83-1.31c-.58-.53-2.43-.81-2.43-.38 0 .27.5.58 1.11.67 1.14.18 1.73.05 1.34-.29Zm21.38-.81c0-.27-.35-.49-.77-.49s-.64.22-.47.49a1.07 1.07 0 0 0 .77.5.49.49 0 0 0 .49-.5ZM101 139.06a2 2 0 0 0-.76-1.06c-.42-.35-.65-.74-.52-.87s-.11-.58-.53-1c-.62-.61-1-.66-1.7-.2a2.18 2.18 0 0 1-1.68.28c-.44-.17-.77 0-.77.32s.57.52 2 .3c1.63-.26 1.92-.19 1.69.43a1.51 1.51 0 0 0 1.27 2.24c.55 0 1-.2 1-.44Zm8.65-1.34c-.42-.8-1.2-2.12-1.72-2.94s-1.3-2.11-1.73-2.87a5 5 0 0 0-4-2.34c-.41 0-.59.25-.39.55a.82.82 0 0 1-.35 1c-.46.31-.16.37.92.15a4.24 4.24 0 0 1 2.11 0c.77.49-.59 1.63-2.27 1.91-.8.13-.25.21 1.2.16l2.64-.08-.28 1.43c-.24 1.21-.18 1.33.43.83.82-.68 1.76-.3 1.76.71 0 .64 2.08 3.31 2.35 3a4.14 4.14 0 0 0-.71-1.51Zm-6.18.91c0-.21-.56-.63-1.24-.95-1.31-.61-1.62-.26-.64.72.62.66 1.85.81 1.84.23Zm22.83-1.72c0-.27-.32-.37-.72-.22a.69.69 0 0 1-.92-.32c-.12-.33-.38-.44-.58-.25-.52.48.85 1.81 1.59 1.52a.92.92 0 0 0 .59-.73Zm-35 .1a.57.57 0 0 0-.78-.22c-.68.42-.56.72.32.72.4 0 .61-.22.44-.51Zm-2.73-3.1c0-.86-.11-.95-.47-.39a1.3 1.3 0 0 0-.21 1.12c.41.69.67.42.66-.73Zm34.54-2.42c-.35-1.78-2.78-4.6-2.78-3.22 0 .44.22.81.49.81a.59.59 0 0 1 .5.64c0 1 1.18 3.32 1.67 3.32.22.01.27-.69.1-1.55Zm-12.21-.71c0-.42-.23-.63-.51-.46a.58.58 0 0 0-.21.78c.4.69.7.56.7-.32Zm-1.24-1.7c.17-.28.08-.5-.19-.5a1.07 1.07 0 0 0-.8.5c-.17.27-.09.49.19.49a1.09 1.09 0 0 0 .78-.49Zm-9.43-.37a2.52 2.52 0 0 1-1.34-.79.46.46 0 0 0-.73-.16c-.51.51.82 1.32 2.07 1.25.98-.01.98-.01-.02-.3Zm19.1-.61c.57-.36.6-.6.14-1.06s-.76-.46-1.35 0c-1.31 1.1-.27 1.96 1.19 1.06Zm-31.13-1.24c-1-1.4-1.63-1.59-1.63-.5 0 .6 1.17 1.44 2.38 1.69a4.51 4.51 0 0 0-.75-1.19Zm14.14-.64a10.1 10.1 0 0 0-1-1.83 2 2 0 0 1-.4-1.09 4.79 4.79 0 0 0-1.24-2 4.69 4.69 0 0 1-1.24-1.93c0-.86-1.48-2-3.39-2.65-2.21-.75-1.91.29.49 1.65 1 .55 1.65 1.28 1.54 1.61s.13.62.54.62c1.18 0 1.5 1 .54 1.7-.61.44-.7.74-.29 1a1.12 1.12 0 0 0 1.08 0c.75-.46 2 .71 2 1.88a2.34 2.34 0 0 0 .87 1.66c1.17.89 1.17.88.51-.68Zm10.77 0c-.1-.74-.2-1.58-.23-1.86s-.27-.11-.54.36a1.63 1.63 0 0 0 .13 1.86c.82 1.3.86 1.28.64-.36Zm-4.09 0a1.88 1.88 0 0 0-1.24 0c-.34.14-.07.25.62.25s.96-.06.6-.2Zm-13-.56c0-.21-.67-.78-1.49-1.27-1.73-1-2-.33-.37.83 1.23.92 1.84 1.07 1.84.49Zm-10.92-2a4.45 4.45 0 0 0-1-1.61l-1-1.17.89 1.61c.84 1.69 1.09 1.94 1.09 1.18Zm24.53 0c.4-.64-.23-2.07-.77-1.74s-.57 2.2 0 2.2a1 1 0 0 0 .75-.46Zm2.06-1.39c-.16-.17-.28.12-.25.65s.13.69.29.3a1 1 0 0 0-.06-.95Zm-16.61-8.18a2.19 2.19 0 0 0-1.1-1.27c-.44-.12-.5 0-.16.22a3.45 3.45 0 0 1 .85 1.28c.18.48.43.87.57.87s.05-.5-.18-1.1Zm8.26-1.47c-.3-.93-1.22-1-1.54-.18-.21.53 0 .76.72.76s.91-.26.8-.58Zm-10-2.71c-.43-.51-1.11-1.37-1.52-1.92-.72-1-.73-1-.75.14a1.72 1.72 0 0 1-1.28 1.61c-.7.27-1.14.71-1 1 .4.65 2.24.65 2.24 0 0-1 .87-.48 1.16.62s.35 1 1.1.31.71-.91.03-1.76Zm-4.27-.66c.5-.32.57-.63.23-1s-.7-.19-1.12.47c-.67 1.08-.32 1.28.87.53Zm7-5c0-.27-.45-.49-1-.49s-1 .22-1 .49.45.5 1 .5.93-.21.93-.48ZM89.18 95c-.13-.88-.43-1.61-.65-1.61-.8 0-1 2.08-.43 3.57.55 1.33.67 1.4 1 .59a6.46 6.46 0 0 0 .08-2.55Zm94.35 157.46a19.79 19.79 0 0 1-1.65-2.91c-.62-1.29-1.36-2.3-1.65-2.23s-.63-.2-.77-.58 0-.55.27-.36.62-.08.73-.56.56-.06 1.27 1.61c.58 1.36 1.41 3.12 1.83 3.91.83 1.54.82 2.05 0 1.12Zm14.8.37a1 1 0 0 1 .94 0c.4.16.29.28-.29.3s-.82-.09-.65-.26Zm2.47-.52c-1.29-.52-.55-1.53 1.49-2.06l2.1-.53-2.31.09c-1.27.06-2.2-.07-2.08-.27a1 1 0 0 1 .73-.38 14.72 14.72 0 0 0 2.8-1c1.9-.87 3-1 6.15-.83 4.48.26 4.78-.35.42-.86s-3.95-1.4.74-1.31c1.44 0 1.45 0 .25-.38s-1.18-.42-.25-.76c.55-.19 1.51-.56 2.13-.81 1.82-.72 5.24-.53 6.53.38.65.45 1 .56.82.25s.16-.6 1.28-.64c2.16-.06-4-1.35-6.79-1.43h-2l2.18-.77a11.66 11.66 0 0 1 4.09-.58c1.85.17 2.56-.25 1.29-.76-.34-.14.22-.26 1.24-.29 1.21 0 1.82.17 1.74.56-.15.75 6.24 1.61 6.49.87.09-.26.82-.48 1.61-.48s1.45-.22 1.45-.49.45-.5 1-.5 1-.18 1-.41c0-.51-7.77-1.56-12-1.61-3 0-3.1-.07-1.71-.68 2-.87 9.48-.92 10.33-.07.53.52.75.52 1.27 0 .34-.37.45-.69.23-.7s.17-.28.85-.58l1.24-.55-1.73-.34-1.74-.35 1.68-.07a14.54 14.54 0 0 1 3.47.39c4.86 1.23 6.9 1.54 7.77 1.2 1.32-.51-.41-.93-10.81-2.68-2.12-.35-3.85-.85-3.85-1.1s.56-.32 1.24-.15a3.19 3.19 0 0 0 2.46-.48c1.16-.76 6.72-1.83 7.2-1.38s-1.31 1.14-3.05 1.15a3.84 3.84 0 0 0-2.07.39c-.13.21.66.3 1.76.19 1.31-.13 2.15 0 2.42.47s.58.5.79.28.06-.59-.34-.85c-.58-.36-.49-.46.41-.46.64 0 1-.23.84-.51-.37-.59 5.34-.59 7.2 0 1.17.38 1.15.4-.38.45-.88 0-1.61.3-1.61.6s.34.41.76.26A38.79 38.79 0 0 1 253 231c4-.28 4.7-.22 4.7.44 0 .94-1.06 1.39-2.44 1s-1.35.63 0 1.35c.76.41 1.21.4 1.74 0a.91.91 0 0 1 1.39 0 1.74 1.74 0 0 0 1.55.24c.71-.28.8-.14.53.73s-.18 1 .56.76.75-.2-.09.41c-2 1.45-4.51 2.09-8.25 2.11h-3.83l.27 1.33c.24 1.21.08 1.38-1.65 1.82-3.57.9-8.72 1.22-9.13.55-.26-.43-.46-.33-.67.34-.48 1.51-2.83 2.58-6.42 3-1.82.18-3.31.54-3.31.79s-.2.33-.45.18a16.57 16.57 0 0 0-4.42.57c-3.37.7-4.08 1-4.56 2.08s-.67 1.06-1 .59-.5-.49-.75-.09-.5.46-.69.34a33.55 33.55 0 0 0-5.8 1 57.94 57.94 0 0 1-7.08 1.2c-1 0-1.52.22-1.34.51.33.53-.08.59-1.11.17Zm5-4a1.88 1.88 0 0 0-1.24 0c-.34.14-.07.25.62.25s.92-.11.58-.25Zm19.2-3.05a2.49 2.49 0 0 0-1.54-1.58c-.19 0-.23.44-.09 1 .24.94 1.63 1.44 1.63.59Zm-10.73-.84c.17-.45 0-.74-.44-.74a.73.73 0 0 0-.73.74c0 .41.19.74.44.74s.55-.33.71-.74Zm14.95-1.24a3.91 3.91 0 0 0-2.39-.43l-2.08.07 1.74.36c2.6.55 3.07.55 2.73 0Zm-6.2-1.74c0-.4-.22-.74-.47-.74-.62 0-1.07.77-.68 1.17.54.56 1.13.34 1.13-.43Zm16.87-5.7c0-.27-.35-.5-.78-.5s-.63.23-.46.5a1 1 0 0 0 .77.49.49.49 0 0 0 .45-.49Zm11.11-3.49c.69-.32.38-.37-1-.15a10.11 10.11 0 0 0-2.2.48c-.42.38 2.24.1 3.19-.33Zm-6.82-.79c.61-.17 1.11-.5 1.11-.74 0-.68-1.63-.48-2.7.32s-.4.96 1.62.42Zm-33.48 20.85a1 1 0 0 1 1 0c.39.16.28.28-.3.3s-.81-.1-.65-.26Zm-36.85-1.19a.48.48 0 0 1 .47-.5 1 1 0 0 1 .77.5c.17.27 0 .49-.46.49s-.75-.22-.75-.49Zm47.27.2a1 1 0 0 1 .95 0c.39.15.28.27-.3.29s-.82-.09-.65-.25Zm-27.1-5.41c.15-.41.51-.75.78-.75s.24.32-.11.75a2.63 2.63 0 0 1-.79.74c-.06 0-.01-.33.15-.74Zm31.68-6.2a1.07 1.07 0 0 1 .8-.5c.27 0 .36.22.19.5a1.09 1.09 0 0 1-.8.49c-.25 0-.33-.2-.16-.49Zm-76-.76c.44-.32.64-.73.47-.9s-.08-.33.22-.33a1.14 1.14 0 0 1 .86.5 2.51 2.51 0 0 0 1.74.49 2.49 2.49 0 0 1 1.73.5c.19.3-.85.45-2.75.4-2.41-.07-2.85-.2-2.21-.61Zm79.69.26-1.74-.36 2.11-.07c1.16 0 2.11.16 2.11.43s-.17.47-.37.43-1.16-.23-2.11-.43Zm-8.06-.85a13.48 13.48 0 0 1 3 0c.75.12 0 .21-1.62.2s-2.21-.09-1.32-.2Zm7.44-3.46a7 7 0 0 1 2.23 0c.61.11.11.21-1.12.21s-1.67-.11-1.05-.21Zm5.08-5.61a.51.51 0 0 1 .5-.5.5.5 0 0 1 0 1 .51.51 0 0 1-.44-.5Zm46.5-9c.34-.34.74-.49.89-.34s-.12.43-.61.62c-.63.28-.7.22-.22-.27Zm.88-6.89a1.35 1.35 0 0 1 1-.49c.41 0 .61.22.44.49a1.34 1.34 0 0 1-1 .5c-.36.01-.55-.21-.38-.49Zm5.21-2.48-3.23-.24 2.86-.41c1.56-.22 2.85-.63 2.85-.9s-.41-.37-.9-.21c-1.52.48-21.1.43-22.32 0s-1.08-.48.62-1.15c1-.38 1.76-.87 1.76-1.07s.78-.53 1.74-.71 1.74-.52 1.74-.75c0-.52 3.42-1.47 5.29-1.47.77 0 4.27-.22 7.77-.48 5.31-.4 6.48-.37 7.07.22.94.94.89 1.45-.11 1.07-1.21-.47-4 .2-4.36 1-.16.42-.06.59.24.41a1.13 1.13 0 0 1 1.17.22c1 .81 2.47.14 1.73-.76-.44-.54-.4-.69.19-.69a1 1 0 0 1 .95.57c.15.42.46.42 1.25 0 1.34-.72 2.28-.73 1.84 0-.21.36.32.49 1.6.4a5.86 5.86 0 0 1 2.6.27.79.79 0 0 0 1.13-.28c.4-.56.45-.55.3.06-.23.9-6.37 4.24-8.82 4.81a21.12 21.12 0 0 1-5 .15Zm5.7-2.48c.63-.27.72-.42.25-.42a5.48 5.48 0 0 0-1.73.42c-.63.27-.72.43-.25.43a5.32 5.32 0 0 0 1.79-.42Zm2.82-.83a1 1 0 0 0-.95 0c-.16.16.13.28.65.26s.7-.14.3-.3Zm2.89-.69c0-.28-.22-.38-.5-.21s-.49.4-.49.52.22.22.49.22a.51.51 0 0 0 .56-.52Zm-12.68-1c-.42-.69-.72-.56-.72.31 0 .43.23.64.51.46a.57.57 0 0 0 .27-.75Zm-7.16 0a.51.51 0 0 0-.53-.49c-.29 0-.39.22-.22.49s.4.5.53.5.28-.17.28-.42Zm17.49-1.78a1 1 0 0 1 .95 0c.4.16.28.27-.3.29s-.81-.09-.65-.25ZM118 200.47c-.23-.39 0-.44.84-.19 1.14.35 1.15.33.31-.6-1.26-1.42-1.07-1.8.25-.51.7.68.93 1.24.62 1.46a1.37 1.37 0 0 1-2-.16Zm-6.88-3c-.62-.73-.71-1.17-.32-1.55s.54-.31.54.3a.85.85 0 0 0 .93.84c1.2 0 1.73 1.27.58 1.35a2.52 2.52 0 0 1-1.69-.96Zm56 .58a1.05 1.05 0 0 1 .77-.49.49.49 0 0 1 .47.49c0 .28-.35.5-.78.5s-.58-.24-.39-.55Zm39.44-2.48a.5.5 0 1 1 .49.5.49.49 0 0 1-.42-.52Zm-155-3.33c-2.72-1.11-6.63-3.59-5.68-3.6a2.29 2.29 0 0 0 1.37-.62 5.37 5.37 0 0 1 2.71-.7 43 43 0 0 0 4.71-.56c1.44-.25 2.72-.3 2.85-.1a8 8 0 0 0 2 1.22c1.36.64 1.64 1 1.13 1.29s-.43.44.38.45a13.94 13.94 0 0 1 4 1.52l2.9 1.51-6.7-.23c-3.89-.14-6.28-.06-5.71.18.85.36.78.42-.49.43a11.15 11.15 0 0 1-3.47-.79Zm.49-1.39a12 12 0 0 0-2.48-.7c-.9 0 2.46 1.38 3.47 1.4.59-.01.14-.33-.95-.72Zm-3.47-1.23a1.1 1.1 0 0 0-.8-.5c-.28 0-.36.23-.19.5a1.1 1.1 0 0 0 .8.5c.31-.02.4-.25.23-.52Zm7.07-.62c-.24-.74-2.36-1.15-2.36-.45 0 .53.82 1 1.9 1a.42.42 0 0 0 .5-.55Zm.74-1.71a7 7 0 0 0-2.23 0c-.61.12-.11.21 1.12.21s1.77-.11 1.15-.23Zm204.27 5c0-.56 2-2.35 2.28-2.05.11.11-.36.72-1 1.36s-1.24.94-1.24.68Zm-170.03-4.46a1.65 1.65 0 0 1-.48-1.29c.1-.3.57 0 1 .76.96 1.4.57 1.77-.52.53Zm159.65 0c0-.42.23-.63.5-.46a1.07 1.07 0 0 1 .5.77.49.49 0 0 1-.5.47c-.27 0-.5-.35-.5-.78Zm-158.44-1c-.48-1.24-.34-1.56.25-.56.29.5.42 1 .29 1.14s-.38-.11-.54-.56Zm-43-.31c0-.23.67-.41 1.48-.41a3.17 3.17 0 0 1 1.49.19 3.31 3.31 0 0 1-1.49.41c-.78.14-1.45.05-1.45-.17Zm40.18-.88a.51.51 0 0 1 .5-.52c.27 0 .49.1.49.21s-.22.36-.49.53-.47.14-.47-.2Zm192.74-1.52a1 1 0 0 1 .77-.49.49.49 0 0 1 .47.49c0 .28-.35.5-.78.5s-.6-.2-.43-.48Zm26.79-1.48a1 1 0 0 1 .77-.5.49.49 0 0 1 .47.5c0 .27-.35.49-.78.49s-.6-.2-.43-.47Zm-259.71-.5c-.45-.29-.51-.48-.16-.49a1.17 1.17 0 0 1 .9.49c.42.63.24.63-.71.02Zm158.25 0a1.05 1.05 0 0 1 .5-.77c.27-.17.5 0 .5.46s-.23.78-.5.78a.49.49 0 0 1-.47-.42Zm79.17 0c-.21-.35-.25-.76-.09-.93.44-.45.93.21.69.93-.14.6-.26.6-.57.09Zm-215.91-1.39c-.85-.82-1.44-1.61-1.29-1.75s.58.05 1 .45a8.31 8.31 0 0 0 1.49 1.14 1 1 0 0 1 .58 1c-.13.41-.8.09-1.78-.84Zm219 .55c-.18-.29.23-.88.89-1.32 1.23-.8 2.74-1.11 2-.42a1.34 1.34 0 0 1-.8.37c-.24 0-.74.42-1.13.94s-.81.74-1 .43Zm24.47-2.13c0-.23.51-.57 1.12-.76a4 4 0 0 0 1.37-.66 10.79 10.79 0 0 1 4.16-2c.44 0 .8-.25.8-.55s.38-.46.86-.33a4.75 4.75 0 0 0 2.36-.42l1.49-.64-1.49-.05c-1.43-.05-1.45-.07-.33-.52a2.43 2.43 0 0 1 1.58-.21.54.54 0 0 0 .72-.23.81.81 0 0 1 .94-.23 1.3 1.3 0 0 0 1.29-.52 2.36 2.36 0 0 1 1.69-.72c.73 0 .83.12.31.32a2.87 2.87 0 0 0-1 .6 9.88 9.88 0 0 1-2.1 1.19 21.83 21.83 0 0 0-4.13 2.83c-2.1 1.82-2.8 2.19-3.85 2a19.73 19.73 0 0 0-3.1.64c-1.51.36-2.73.49-2.73.26Zm-61-1c0-.27.34-.5.75-.5s.74.23.74.5-.33.5-.74.5-.81-.21-.81-.48Zm46.64-.2a1.19 1.19 0 0 1 .87-1 11.17 11.17 0 0 0 2.1-1.11c.69-.45.86-.49.38-.09a2.39 2.39 0 0 0-.87 1.22c0 .27-.56.74-1.24 1-1.05.56-1.3.54-1.3.01Zm-241.39-1.53c-1.23-.94-.77-.93 1 0 .78.41 1.08.74.67.73a3.75 3.75 0 0 1-1.67-.73Zm96-1.23c0-.27.5-.49 1.12-.48.9 0 1 .1.37.48-.99.63-1.56.63-1.56 0Zm-122.84-1.74c-.91-.71-.88-.73.52-.41.82.18 2 .5 2.73.71 1.11.33 1.06.37-.53.41a5.06 5.06 0 0 1-2.72-.71Zm14.16.29a.52.52 0 0 1 .5-.53c.27 0 .5.1.5.22s-.23.36-.5.52-.5.03-.5-.21Zm200.92 0c0-.28.34-.5.75-.5s.74.22.74.5-.33.49-.74.49-.75-.25-.75-.52Zm-205.88-1c0-.27.1-.49.22-.49s.36.22.53.49.07.5-.22.5a.51.51 0 0 1-.53-.53Zm-11.9-1c-.64-.41-.62-.48.12-.48.48 0 .87.21.87.48 0 .59-.06.59-.99-.02Zm303.11-1c0-.27.61-.48 1.37-.46 1.2 0 1.25.08.37.46-1.38.57-1.74.57-1.74-.01ZM29 169.26c-.41-.26-.52-.48-.25-.48a3 3 0 0 1 1.24.48c.41.27.53.48.25.48a2.92 2.92 0 0 1-1.24-.48Zm123.81-2.83c-.27-.71-.21-.76.28-.27.33.33.49.73.33.88s-.42-.12-.59-.61Zm-2.6-1.43a1 1 0 0 1 .94 0c.4.16.29.28-.3.3s-.81-.09-.64-.26Zm69.44-1.54c-1-.35-.73-1.13.37-1.13.55 0 1 .21 1 .46-.02.63-.62.93-1.37.66Zm3.6-2.62c-.17-.28-.07-.5.21-.5a.51.51 0 0 1 .53.5c0 .27-.1.49-.22.49s-.36-.23-.52-.5Zm36.83-.35c-1.5-.39-1-1.28.57-1 1.28.26 1.54.09 2.26-1.42.59-1.25 1.14-1.72 2-1.72a2.05 2.05 0 0 1 1.53.55c.22.37 0 .44-.72.22s-1.05-.13-1.05.44-.22.78-.5.78-.49.33-.49.74a.76.76 0 0 1-.75.75c-.41 0-.74.22-.74.49 0 .51-.59.56-2.11.15Zm-113.48-2.64a.5.5 0 1 1 .49.5.5.5 0 0 1-.49-.5Zm-1.17-3.22c-.16-.41-.07-.75.19-.75s.48.34.48.75-.09.74-.2.74-.32-.33-.47-.74Zm85.51-5.91c0-.71.22-1.29.49-1.29s.5-.61.5-1.36c0-1 .13-1.18.47-.64s.64.61 1.68.06a2.83 2.83 0 0 1 1.89-.38c.48.19.45.27-.14.3a8.09 8.09 0 0 0-2.85 2.32c-1.95 2.18-2 2.22-2 1Zm-1.49-.54c0-.68.08-1.24.19-1.24s.29.56.42 1.24.05 1.24-.19 1.24-.42-.56-.42-1.24Zm35.22.62a12.24 12.24 0 0 1 5.1-2.3c1 .05 1 .08.11.3a4.66 4.66 0 0 0-1.74.9c-.7.61-4 1.66-3.47 1.1Zm6.45-3.35c-.41-.26-.52-.48-.25-.48a3.06 3.06 0 0 1 1.24.48c.41.27.52.48.25.48a2.92 2.92 0 0 1-1.24-.48Zm-175.72-2c-1.54-1.25-.93-1.69 1-.73 1.78.89 1.85 1 .85 1.38a2.41 2.41 0 0 1-1.85-.62Zm38.31.12c-.65-.73-.63-.78.17-.48a5.91 5.91 0 0 0 1.1.34c.11 0 .21.23.21.5 0 .76-.65.61-1.48-.33Zm98.5.42c-.34-.55 1.54-1.08 2.49-.71.38.14.57.46.42.7-.35.6-2.56.61-2.91.02Zm-94.5-.76c0-.53.09-.82.26-.65a1 1 0 0 1 0 .95c-.12.47-.24.31-.26-.27Zm96.45-1.23c1.35-.58 1.74-.58 1.74 0 0 .27-.62.48-1.37.46-1.21-.05-1.25-.11-.37-.46Zm32.49-3.45a1.07 1.07 0 0 1 .5-.77c.27-.17.5 0 .5.46s-.23.78-.5.78a.49.49 0 0 1-.5-.49ZM239.1 137c-.18-.29-.22-.63-.09-.76s.37.1.53.53c.34.85.05 1-.44.23Zm1.76-1.7c0-.68.22-1.24.49-1.24s.5.56.5 1.24-.22 1.24-.5 1.24-.49-.54-.49-1.26Zm30.26 0c0-.52.95-.75 1.23-.28.17.27 0 .5-.46.5s-.77-.09-.77-.21Zm-31.71-1c0-.53.09-.82.26-.65a1 1 0 0 1 0 .95c-.16.4-.28.28-.3-.3Zm-33.28-.15a5.48 5.48 0 0 1 1.24-1.73 6 6 0 0 0 1.26-1.74 12.86 12.86 0 0 1 1-2.38c1.3-2.58.88-.36-.55 2.88-.87 2.04-2.95 4.12-2.95 3Zm36.71-1.63c0-.27.1-.5.22-.5s.36.23.53.5.07.5-.22.5a.51.51 0 0 1-.53-.47Zm-81.36-1.49a.5.5 0 0 1 1 0 .5.5 0 1 1-1 0Zm85.1-3.93c-.26-.42 1.9-3 2.52-3 .42 0-.39 2.13-1.08 2.83s-1.02.82-1.44.2Zm-82.89-2c-.18-.29-.22-.64-.09-.77s.37.11.53.53c.34.85.05 1.01-.44.22Zm83.66-1.17c0-.52.09-.82.25-.65a1 1 0 0 1 0 .95c-.15.4-.27.28-.29-.3Zm-157.63-1c-.47-1.2.18-1.42.79-.27.39.71.4 1 0 1s-.63-.38-.79-.8Zm-2.41-2.22c-.39-.62.27-.62 1.24 0 .64.42.63.48-.09.49a1.43 1.43 0 0 1-1.15-.56Zm79.13-.51c0-.64 1.42-1.21 1.87-.76.18.19 0 .54-.39.8-.92.54-1.48.52-1.48-.11Zm84.38-.19c0-.52.09-.81.26-.65a1 1 0 0 1 0 .95c-.16.4-.28.28-.3-.3Zm-160.65-3.8c-1.23-1-1.42-1.52-.58-1.52s3 1.8 2.59 2.17-.79.37-2.01-.65Zm22.71.48c-.4-.25-.58-.61-.4-.79.46-.46 1.87.12 1.87.76s-.51.64-1.47.03ZM259.21 112c0-.52 1-.74 1.24-.28.17.28 0 .5-.46.5s-.78-.11-.78-.22Zm21.35-.61a2.35 2.35 0 0 1 .7-1.31c.4-.4.59-.86.42-1s-.05-.3.27-.3a.53.53 0 0 1 .58.44c0 .64-1.95 2.78-1.97 2.16Zm-111.64-1.65c0-.27.34-.5.75-.5s.74.23.74.5-.34.5-.74.5-.75-.24-.75-.51Zm-68-1.49c0-.27.33-.5.74-.5s.75.23.75.5-.34.5-.75.5-.66-.24-.66-.51Zm-18.37-.75c-.93-1.12-.39-1.25.72-.18.52.51.72.93.43.93a2 2 0 0 1-1.11-.76Zm20.6-.24a1.1 1.1 0 0 1 .8-.5c.28 0 .36.23.19.5a1.09 1.09 0 0 1-.8.49c-.23-.01-.34-.23-.15-.5ZM99.6 107a1 1 0 0 1 1 0c.4.16.28.28-.3.3s-.86-.19-.7-.3Zm162.59-.69c0-.28.1-.5.22-.5s.36.22.52.5.07.49-.21.49a.51.51 0 0 1-.53-.54Zm-157.83-1.12c.37-.37.73-.4 1.06-.08s.12.52-.57.57-.93-.11-.49-.54Zm63.86-.3c-.21-.35-.25-.77-.09-.93.44-.45.93.21.68.93-.16.45-.28.45-.59-.05Zm100.78-1.4c-.3-1.54.75-2.14 1.85-1s1 1.38 0 1c-.47-.18-.74 0-.74.51 0 1.28-.86.92-1.12-.48Zm-102.07-1c0-.43.22-.64.49-.47a1.06 1.06 0 0 1 .5.77.48.48 0 0 1-.5.47c-.26.02-.48-.33-.48-.75Zm-.4-2.45c0-.82.09-1.22.22-.88a2.75 2.75 0 0 1 0 1.49c-.09.5-.2.22-.21-.59Zm102.26.47c0-.4.19-.72.41-.72a.45.45 0 0 1 .42.46 1 1 0 0 1-.42.73c-.2.16-.4-.05-.4-.45Zm12-4.32c2.6-3.07 3-3.41 3.37-2.57s-1.41 2.65-2.55 2.68a.59.59 0 0 0-.62.51.55.55 0 0 1-.57.5c-.28.02-.11-.47.42-1.1Zm-75.63-.37c0-.68.12-1 .26-.62a2 2 0 0 1 0 1.24c-.09.36-.21.08-.21-.6Zm-5.45-1c0-.68.11-1 .25-.62a1.88 1.88 0 0 1 0 1.24c-.09.37-.2.09-.2-.59Zm75.16.66a1.37 1.37 0 0 1-.33-.84c0-.32.23-.29.59.07s.47.7.33.85a.45.45 0 0 1-.54-.05ZM81.15 93.05c-1.3-1.66-1.32-2.36-.05-1.68a4.54 4.54 0 0 1 2 2.7c0 .7-1.01.17-1.95-1.02Zm198.79.18c-.7-.55-.61-.79.91-2.17 1.8-1.64 1.83-1.54.63 1.68-.48 1.3-.5 1.31-1.54.49Zm4.57-.86c0-.82.22-1.49.48-1.49s.43.67.37 1.49c-.14 1.93-.85 1.94-.85 0ZM200 90.17a2.46 2.46 0 0 1 .63-1.87 6.85 6.85 0 0 0 1.15-2.14c.19-.74.27-.65.31.4.05 1.21-1.12 4.32-1.64 4.32-.09 0-.3-.32-.45-.71Zm-120.55-.53a1.16 1.16 0 0 1 1-.75.73.73 0 0 1 .69.75.88.88 0 0 1-1 .74c-.67 0-.89-.24-.69-.74Zm203.85-1.39c.19-.49.47-.76.62-.61s0 .55-.34.89-.58.47-.28-.28Zm-205.39-.31a.57.57 0 0 1 .22-.78c.27-.17.5 0 .5.46 0 .88-.29 1-.72.32Zm91-2.52a.5.5 0 1 1 .5.5.51.51 0 0 1-.49-.5Zm-90.78-1c0-.27.1-.5.21-.5s.36.23.53.5.07.49-.22.49a.5.5 0 0 1-.51-.48Zm123.69-.28a1.37 1.37 0 0 1 .26-1.34c.42-.42.57-.22.57.77.01 1.51-.36 1.76-.82.58Zm-121.2-.21a.5.5 0 0 1 1 0 .5.5 0 1 1-1 0Zm93.11-2.35c-.38-.69-.92-3.35-.62-3s1.27 3.42 1 3.42-.28-.17-.4-.37ZM79.13 80c0-.27.1-.49.22-.49s.35.22.52.49.07.5-.22.5a.51.51 0 0 1-.52-.5Zm99.59-7.38c-1.2-1.22-1.09-1.62.29-1 .63.29 1.15.85 1.15 1.23 0 .91-.37.85-1.44-.23Zm16-5.07a1.86 1.86 0 0 1 .37-1.15 1.55 1.55 0 0 0 .42-.7 9.33 9.33 0 0 1 .62-1.57c.53-1.15.55-1.11.26.5-.39 1.9-1.67 4.21-1.67 2.88Zm-2.23-23c0-1.5.22-2.81.49-2.91s.48.67.45 1.85c-.09 3.24-.17 3.78-.57 3.78-.21-.05-.37-1.27-.37-2.77ZM190.55 42c-.45-1.17-.34-1.54.2-.7.39.6.48.54.49-.34a8.15 8.15 0 0 0-.46-2.32c-.42-1.1-.36-1.22.54-1s1 0 .79-.72a2.29 2.29 0 0 1 .18-1.59c.29-.46.43-.18.44.9a2.8 2.8 0 0 1-.49 1.89 4.39 4.39 0 0 0-.5 2.51c0 2.11-.61 2.84-1.19 1.37Z" + transform="translate(-15.35 -23.13)" + style={{ + fill: '#303030', + fillOpacity: 0.38999998569488525, + }} + /> + </svg> +); + +export default HighStake; diff --git a/src/config/validators/Metaspan.tsx b/src/config/validators/Metaspan.tsx new file mode 100644 index 0000000000..f56a3b4e7b --- /dev/null +++ b/src/config/validators/Metaspan.tsx @@ -0,0 +1,28 @@ +const Metaspan = () => ( + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"> + <path + style={{ + fill: '#fff', + }} + d="M0 0h512v512H0z" + /> + <path + d="M251.47 64.72c-107.7.55-194.55 88.3-194 196s88.3 194.55 196 194 194.55-88.3 194-196c-.54-106.91-87.08-193.45-194-194Z" + style={{ + strokeWidth: 26, + fill: 'none', + stroke: '#010101', + }} + /> + <path + d="M252.47 64.72v390m195-195h-390m44-120a260 260 0 0 0 302 0m0 240a260 260 0 0 0-302 0m136-310C132 158.8 118.68 316.53 207.77 422a250.18 250.18 0 0 0 29.7 29.7m30 0C373 362.63 386.25 204.9 297.16 99.41a250 250 0 0 0-29.69-29.69" + style={{ + strokeWidth: 18, + fill: 'none', + stroke: '#010101', + }} + /> + </svg> +); + +export default Metaspan; diff --git a/src/config/validators/PDP.tsx b/src/config/validators/PDP.tsx new file mode 100644 index 0000000000..551d823215 --- /dev/null +++ b/src/config/validators/PDP.tsx @@ -0,0 +1,16 @@ +const PDP = () => ( + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"> + <path + d="M0 0h512m0 512H0M0 0h512v512H0z" + style={{ + fill: '#fff', + }} + /> + <path + d="M0 256v256h512V0H0zm293.4-134.15c8.9 7.6 12 15.88 11.13 31.53l-.67 13.19-62.77 1.12c-61.67 1.11-63 1.11-67.9 6.26-5.12 4.91-5.12 6.26-6.23 68.19l-1.12 63.05L150 305c-11.35 0-17.81-1.11-22-4-11.8-7.83-12.47-12.52-11.8-94.13l.67-74.45 5.56-6c3.12-3.35 7.57-6.93 10-7.82 2.45-1.12 38.29-2 79.47-2l74.8-.23zm90.38 89.43c11.8 7.83 12.46 12.3 12.46 90.55 0 78.93-.44 82.51-13.13 90.11-5.79 3.35-14.69 3.8-81.48 3.8-82.36 0-84.59-.23-91.26-13.64-2.45-4.47-3.34-11.4-2.9-21.69l.67-15 63.44-1.12c34.95-.67 63.67-1.56 64.11-1.78.23-.23 2.45-3.81 4.9-7.83 4.23-6.71 4.45-11.18 4.45-66.63 0-32.64.67-60.14 1.78-61 2.68-2.92 30.95.43 36.96 4.23zM305 246.83c0 36.67-.22 39.13-5.12 46.28-7.56 11.41-16.47 13.42-57 12.75l-34.73-.67-.67-38c-.67-40.69.67-47.18 10.24-54.78 4.9-4 9.13-4.47 46.3-4.47h41z" + fill="#010101" + /> + </svg> +); + +export default PDP; diff --git a/src/config/validators/Paranodes.tsx b/src/config/validators/Paranodes.tsx new file mode 100644 index 0000000000..f62ee2d88e --- /dev/null +++ b/src/config/validators/Paranodes.tsx @@ -0,0 +1,18 @@ +const Paranodes = () => ( + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"> + <path + d="M0 0h512m0 512H0M0 0h512v512H0z" + style={{ + fill: '#fff', + }} + /> + <path + d="M34.71 289.6v-43.53h35.65a7.68 7.68 0 0 1 4 1.09 8.16 8.16 0 0 1 2.87 2.84 7.47 7.47 0 0 1 1.06 3.9v12.23a7.61 7.61 0 0 1-1.06 4 7.93 7.93 0 0 1-6.87 3.9l-29.11.06v15.5zm8-22.16h27.53a1.4 1.4 0 0 0 1-.42 1.41 1.41 0 0 0 .42-1v-11.89a1.5 1.5 0 0 0-.42-1.06 1.33 1.33 0 0 0-1-.46H42.76a1.56 1.56 0 0 0-1.51 1.52V266a1.36 1.36 0 0 0 .45 1 1.51 1.51 0 0 0 1.06.44zm40.36-13.56a7.67 7.67 0 0 1 1.06-4 8.1 8.1 0 0 1 2.87-2.81 7.67 7.67 0 0 1 4-1.06h27.79a7.83 7.83 0 0 1 4 1.06 8 8 0 0 1 2.88 2.85 7.67 7.67 0 0 1 1.06 4v35.68h-6.6v-15.26H89.61v15.26h-6.54zm37 13.93v-13.75a1.49 1.49 0 0 0-.42-1.05 1.35 1.35 0 0 0-1-.46H91.12a1.57 1.57 0 0 0-1.51 1.51v13.75zm13.49 21.79v-43.53h35.65a7.7 7.7 0 0 1 4 1.09 8.24 8.24 0 0 1 2.87 2.88 7.47 7.47 0 0 1 1.06 3.9v12.23a7.61 7.61 0 0 1-1.06 4 7.89 7.89 0 0 1-2.87 2.84 7.77 7.77 0 0 1-4 1.06l-29.12.06v15.5zm8.05-22.16h27.48a1.43 1.43 0 0 0 1.45-1.45v-11.86a1.5 1.5 0 0 0-.42-1.06 1.34 1.34 0 0 0-1-.46h-27.51a1.42 1.42 0 0 0-1.06.46 1.45 1.45 0 0 0-.46 1.06V266a1.37 1.37 0 0 0 .46 1 1.5 1.5 0 0 0 1.06.44zm27.66 22.16-13.62-16.29h8.54l12.71 15.08v1.21zm14.35-35.72a7.58 7.58 0 0 1 1.06-4 8 8 0 0 1 2.84-2.85 7.68 7.68 0 0 1 4-1.06h27.78a7.9 7.9 0 0 1 4 1.06 8 8 0 0 1 2.87 2.85 7.58 7.58 0 0 1 1.06 4v35.72h-6.6v-15.26h-30.48v15.26h-6.53zm37 13.93v-13.75a1.49 1.49 0 0 0-.42-1.05 1.34 1.34 0 0 0-1-.46h-27.53a1.42 1.42 0 0 0-1.06.46 1.45 1.45 0 0 0-.46 1.05v13.75zm13.48 21.79V246h7.81l29.18 34.75V246h6.59v43.6h-7.8l-29.24-34.81v34.81zm58.11 0a7.58 7.58 0 0 1-4-1.06 8 8 0 0 1-2.85-2.85 7.58 7.58 0 0 1-1.06-4v-27.81a7.58 7.58 0 0 1 1.06-4 8 8 0 0 1 2.85-2.85 7.67 7.67 0 0 1 4-1.06h27.84a7.68 7.68 0 0 1 4 1.06 8 8 0 0 1 2.84 2.85 7.58 7.58 0 0 1 1.06 4v27.85a7.58 7.58 0 0 1-1.06 4 8 8 0 0 1-2.84 2.85 7.59 7.59 0 0 1-4 1.06zm.18-6.54h27.48a1.35 1.35 0 0 0 1-.45 1.54 1.54 0 0 0 .43-1.06v-27.49a1.5 1.5 0 0 0-.43-1.05 1.33 1.33 0 0 0-1-.46h-27.48a1.56 1.56 0 0 0-1.51 1.51v27.49a1.48 1.48 0 0 0 .45 1.06 1.46 1.46 0 0 0 1.06.45zm42.31 6.54V246h35.66a7.83 7.83 0 0 1 4 1.06 8 8 0 0 1 2.88 2.85 7.67 7.67 0 0 1 1.06 4v27.85a7.67 7.67 0 0 1-1.06 4 8 8 0 0 1-2.88 2.85 7.74 7.74 0 0 1-4 1.06zm8-6.54h27.54a1.35 1.35 0 0 0 1-.45 1.54 1.54 0 0 0 .43-1.06v-27.49a1.5 1.5 0 0 0-.43-1.05 1.33 1.33 0 0 0-1-.46h-27.55a1.35 1.35 0 0 0-1 .46 1.49 1.49 0 0 0-.42 1.05v27.49a1.53 1.53 0 0 0 .42 1.06 1.38 1.38 0 0 0 1 .45zm42.49 6.54V246h39.95v6.54h-33.36v12h26.82v6.53h-26.82v12h33.36v6.54zm53.87 0a7.61 7.61 0 0 1-4-1.06 8 8 0 0 1-2.84-2.85 7.58 7.58 0 0 1-1.06-4v-2.54h6.54v2.36a1.48 1.48 0 0 0 .45 1.06 1.46 1.46 0 0 0 1.06.45h27.48a1.36 1.36 0 0 0 1-.45 1.53 1.53 0 0 0 .42-1.06v-9a1.47 1.47 0 0 0-.42-1.09 1.42 1.42 0 0 0-1-.43h-27.63a7.7 7.7 0 0 1-4-1.05 8 8 0 0 1-2.84-2.85 7.58 7.58 0 0 1-1.06-4v-9.33a7.58 7.58 0 0 1 1.06-4 8 8 0 0 1 2.84-2.85 7.71 7.71 0 0 1 4-1.06h27.84a7.68 7.68 0 0 1 4 1.06 8 8 0 0 1 2.84 2.85 7.58 7.58 0 0 1 1.06 4v2.55h-6.6v-2.37a1.49 1.49 0 0 0-.42-1.05 1.34 1.34 0 0 0-1-.46h-27.54a1.56 1.56 0 0 0-1.51 1.51v9a1.42 1.42 0 0 0 .45 1.09 1.48 1.48 0 0 0 1.06.43h27.66a7.59 7.59 0 0 1 4 1.06 7.89 7.89 0 0 1 2.84 2.84 7.59 7.59 0 0 1 1.06 4v9.32a7.58 7.58 0 0 1-1.06 4 8 8 0 0 1-2.84 2.85 7.59 7.59 0 0 1-4 1.06z" + style={{ + fill: '#231f20', + }} + /> + </svg> +); + +export default Paranodes; diff --git a/src/config/validators/PioneerStake.tsx b/src/config/validators/PioneerStake.tsx new file mode 100644 index 0000000000..cb8ab2035e --- /dev/null +++ b/src/config/validators/PioneerStake.tsx @@ -0,0 +1,67 @@ +const PionerStake = () => ( + <svg + xmlns="http://www.w3.org/2000/svg" + xmlnsXlink="http://www.w3.org/1999/xlink" + viewBox="0 0 375 375" + > + <defs> + <symbol id="glyph0-1" data-name="glyph0-1" viewBox="0 0 52.92 63.22"> + <path + fill="#222" + d="M26 63.22a34.93 34.93 0 0 0 14.33-2.72 20.74 20.74 0 0 0 9.34-7.77 21.63 21.63 0 0 0 3.25-12 21.82 21.82 0 0 0-3.25-12A20.68 20.68 0 0 0 40.33 21 35.1 35.1 0 0 0 26 18.25H11.75V0H0v63.22zm-.55-35q7.68 0 11.65 3.25t4 9.31c0 4-1.32 7.13-4 9.3s-6.53 3.25-11.65 3.25h-13.7V28.17z" + /> + </symbol> + <symbol id="glyph0-2" data-name="glyph0-2" viewBox="0 0 54.02 63.22"> + <path + fill="#222" + d="M41.38 0 28.45 18.52a20.83 20.83 0 0 0-2.43-.1H11.75V0H0v63.22h26a34.93 34.93 0 0 0 14.33-2.72 20.74 20.74 0 0 0 9.34-7.77 21.63 21.63 0 0 0 3.25-12 21.44 21.44 0 0 0-3.48-12.29 20.66 20.66 0 0 0-10-7.67L54 0zm-.29 40.73c0 4-1.32 7.13-4 9.3s-6.53 3.25-11.65 3.25H11.75V28.09h13.72q7.68 0 11.65 3.3t3.97 9.34z" + /> + </symbol> + <symbol id="glyph0-3" data-name="glyph0-3" viewBox="0 0 59.61 65.03"> + <path + fill="#222" + d="M47.69 33.23H58.8V8.05a35.07 35.07 0 0 0-11.38-6A43.79 43.79 0 0 0 34.05 0a36.06 36.06 0 0 0-17.43 4.2 31.41 31.41 0 0 0-12.2 11.61A32 32 0 0 0 0 32.52a32 32 0 0 0 4.42 16.7 31.27 31.27 0 0 0 12.25 11.61A36.49 36.49 0 0 0 34.23 65a38.3 38.3 0 0 0 14.46-2.62 28.32 28.32 0 0 0 10.92-7.68l-7.41-7.2a23.72 23.72 0 0 1-17.42 7.23 24.76 24.76 0 0 1-11.89-2.79 20.4 20.4 0 0 1-8.12-7.86 22.51 22.51 0 0 1-2.94-11.56 22.21 22.21 0 0 1 2.94-11.4 21 21 0 0 1 8.12-7.93 23.85 23.85 0 0 1 11.8-2.89 25.4 25.4 0 0 1 13 3.25z" + /> + </symbol> + <clipPath id="clip-path"> + <path fill="none" d="M27.05 114.83h76.77v76.94H27.05z" /> + </clipPath> + <clipPath id="clip-path-2"> + <path fill="none" d="M271.17 183.22h76.52v76.95h-76.52z" /> + </clipPath> + </defs> + <g id="surface1"> + <path fill="#fff" d="M0 0h375v375H0z" /> + <g clipPath="url(#clip-path)"> + <path fill="#ff66c4" d="M30.21 190.89h-3.16v-76.35h76.42v3.15H30.21z" /> + </g> + <g clipPath="url(#clip-path-2)"> + <path fill="#ff66c4" d="M347.7 260.16h-76.42V257h73.27v-73.2h3.15z" /> + </g> + <use + width={52.92} + height={63.22} + transform="matrix(1.22 0 0 -1.22 61.91 233.73)" + xlinkHref="#glyph0-1" + /> + <use + width={54.02} + height={63.22} + transform="matrix(1.22 0 0 -1.22 156.32 233.73)" + xlinkHref="#glyph0-2" + /> + <use + width={59.61} + height={65.03} + transform="matrix(1.22 0 0 -1.22 245.76 234.83)" + xlinkHref="#glyph0-3" + /> + <use + transform="matrix(1.22 0 0 -1.22 340.39 233.73)" + xlinkHref="#glyph0-4" + /> + </g> + </svg> +); + +export default PionerStake; diff --git a/src/config/validators/Polkachu.tsx b/src/config/validators/Polkachu.tsx new file mode 100644 index 0000000000..8385fa8719 --- /dev/null +++ b/src/config/validators/Polkachu.tsx @@ -0,0 +1,18 @@ +const Polkachu = () => ( + <svg + viewBox="0 0 44.426 44.424" + xmlSpace="preserve" + xmlns="http://www.w3.org/2000/svg" + > + <path + fill="#7c3aed" + d="M28.272 25.817c1.006-.551 1.754-1.306 2.24-2.268.486-.96.73-2.065.73-3.314 0-1.247-.244-2.346-.73-3.296-.486-.949-1.236-1.691-2.25-2.223-1.013-.533-2.314-.799-3.905-.799h-5.839V26.64h5.873c1.58 0 2.873-.274 3.881-.823z" + /> + <path + fill="#7c3aed" + d="M22.213 0C9.945 0 0 9.945 0 22.213c0 7.366 3.588 13.892 9.109 17.933.264.103.544.171.844.171a2.364 2.364 0 0 0 2.363-2.363l.004-2.257V8.594h13.014c2.702 0 4.972.504 6.809 1.511 1.838 1.008 3.229 2.39 4.173 4.146.944 1.757 1.417 3.752 1.417 5.983 0 2.256-.476 4.259-1.426 6.011-.95 1.75-2.354 3.127-4.207 4.128-1.853 1.001-4.138 1.502-6.851 1.502h-6.606l-.014 9.086a3.59 3.59 0 0 0 2.559 3.437c.34.016.682.026 1.025.026 12.269 0 22.213-9.944 22.213-22.213C44.426 9.945 34.482 0 22.213 0Z" + /> + </svg> +); + +export default Polkachu; diff --git a/src/config/validators/Polkadotters.tsx b/src/config/validators/Polkadotters.tsx new file mode 100644 index 0000000000..492ed7d8d3 --- /dev/null +++ b/src/config/validators/Polkadotters.tsx @@ -0,0 +1,59 @@ +const Polkadotters = () => ( + <svg + xmlns="http://www.w3.org/2000/svg" + x={0} + y={0} + viewBox="0 0 512 512" + xmlSpace="preserve" + > + <g transform="translate(-1200)"> + <circle fill="#fdebf3" fillOpacity={0.33} cx={1286.7} cy={105.8} r={20} /> + <circle fill="#fdebf3" fillOpacity={0.33} cx={1261.7} cy={155.8} r={20} /> + <circle fill="#fdebf3" fillOpacity={0.33} cx={1248.7} cy={205.8} r={20} /> + <circle fill="#fdebf3" fillOpacity={0.33} cx={1261.7} cy={255.8} r={20} /> + <circle fill="#fdebf3" fillOpacity={0.33} cx={1274.7} cy={305.8} r={20} /> + <circle fill="#fdebf3" fillOpacity={0.33} cx={1261.7} cy={355.8} r={20} /> + <circle fill="#fdebf3" fillOpacity={0.33} cx={1236.7} cy={405.8} r={20} /> + </g> + <g transform="translate(-1200)"> + <circle fill="#fdebf3" fillOpacity={0.66} cx={1336.7} cy={105.8} r={20} /> + <circle fill="#fdebf3" fillOpacity={0.66} cx={1311.7} cy={155.8} r={20} /> + <circle fill="#fdebf3" fillOpacity={0.66} cx={1298.7} cy={205.8} r={20} /> + <circle fill="#fdebf3" fillOpacity={0.66} cx={1311.7} cy={255.8} r={20} /> + <circle fill="#fdebf3" fillOpacity={0.66} cx={1324.7} cy={305.8} r={20} /> + <circle fill="#fdebf3" fillOpacity={0.66} cx={1311.7} cy={355.8} r={20} /> + <circle fill="#fdebf3" fillOpacity={0.66} cx={1286.7} cy={405.8} r={20} /> + </g> + <g transform="translate(-1200)"> + <circle fill="#ee4699" cx={1386.7} cy={105.8} r={20} /> + <circle fill="#ee4699" cx={1361.7} cy={155.8} r={20} /> + <circle fill="#ee4699" cx={1348.7} cy={205.8} r={20} /> + <circle fill="#ee4699" cx={1361.7} cy={255.8} r={20} /> + <circle fill="#ee4699" cx={1374.7} cy={305.8} r={20} /> + <circle fill="#ee4699" cx={1361.7} cy={355.8} r={20} /> + <circle fill="#ee4699" cx={1336.7} cy={405.8} r={20} /> + </g> + <g transform="translate(-1200)"> + <circle fill="#bbb" cx={1491.7} cy={120.8} r={20} /> + <circle fill="#bbb" cx={1536.7} cy={105.8} r={20} /> + <circle fill="#bbb" cx={1586.7} cy={105.8} r={20} /> + <circle fill="#bbb" cx={1631.7} cy={121.8} r={20} /> + <circle fill="#bbb" cx={1461.7} cy={155.8} r={20} /> + <circle fill="#bbb" cx={1661.7} cy={155.8} r={20} /> + <circle fill="#bbb" cx={1446.7} cy={205.8} r={20} /> + <circle fill="#bbb" cx={1536.7} cy={205.8} r={20} /> + <circle fill="#bbb" cx={1675.7} cy={205.8} r={20} /> + <circle fill="#bbb" cx={1511.7} cy={255.8} r={20} /> + <circle fill="#bbb" cx={1661.7} cy={255.8} r={20} /> + <circle fill="#bbb" cx={1486.7} cy={305.8} r={20} /> + <circle fill="#bbb" cx={1536.7} cy={305.8} r={20} /> + <circle fill="#bbb" cx={1586.7} cy={305.8} r={20} /> + <circle fill="#bbb" cx={1631.7} cy={290.8} r={20} /> + <circle fill="#bbb" cx={1461.7} cy={355.8} r={20} /> + <circle fill="#bbb" cx={1436.7} cy={405.8} r={20} /> + </g> + <circle cx={336.7} cy={405.8} r={20} fill="#ed1e7f" /> + </svg> +); + +export default Polkadotters; diff --git a/src/config/validators/PythagorasCapitalInvestment.tsx b/src/config/validators/PythagorasCapitalInvestment.tsx new file mode 100644 index 0000000000..5d5f7ea44c --- /dev/null +++ b/src/config/validators/PythagorasCapitalInvestment.tsx @@ -0,0 +1,34 @@ +const PythagorasCapitalInvestment = () => ( + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 481 481"> + <path + style={{ + fill: '#e5d3b0', + stroke: '#000', + }} + d="M.5.5h480v480H.5z" + /> + <path fill="none" stroke="#ff7f00" strokeWidth={12} d="M50.5 240.5h120" /> + <path fill="none" stroke="#ff7f00" strokeWidth={12} d="M310.5 240.5h120" /> + <g + style={{ + isolation: 'isolate', + }} + > + <path + fill="#ff7f00" + d="M119.2 299.41a3.56 3.56 0 0 0 2.28-.73 4.4 4.4 0 0 0 .46-2.52V282.3a3.35 3.35 0 0 0-.51-2.21 3.73 3.73 0 0 0-2.23-.67v-.59h8.44a10.37 10.37 0 0 1 6.22 1.58 4.9 4.9 0 0 1 2.16 4.15 5.14 5.14 0 0 1-2.4 4.74 11.21 11.21 0 0 1-6 1.47h-2.52v5.81c0 1.2.19 2 .56 2.27a4.94 4.94 0 0 0 2.48.61v.54h-9Zm10.8-18.8a8.57 8.57 0 0 0-3.44-.59 2.27 2.27 0 0 0-1.21.19 1.18 1.18 0 0 0-.23.87v8.34l1.13.1h.52a8 8 0 0 0 3.46-.62c1.54-.76 2.3-2.19 2.3-4.27a4 4 0 0 0-2.53-4.02ZM136.92 285.64h6.6v.44a4.3 4.3 0 0 0-1.16.14c-.48.14-.72.41-.72.82a1.42 1.42 0 0 0 .08.45 6.61 6.61 0 0 0 .28.7l3.62 7.82 3-7.89q0-.12.15-.57a3.14 3.14 0 0 0 .1-.61.68.68 0 0 0-.28-.61 1.46 1.46 0 0 0-.72-.22h-.48v-.44h4.3v.44a1.41 1.41 0 0 0-1 .45 3.2 3.2 0 0 0-.56 1l-4.92 13a20.19 20.19 0 0 1-2.45 4.89 3.63 3.63 0 0 1-3 1.52 3.25 3.25 0 0 1-1.64-.44 1.48 1.48 0 0 1-.77-1.39 1.26 1.26 0 0 1 .46-1 1.65 1.65 0 0 1 1.15-.4 3.81 3.81 0 0 1 1.19.3 4 4 0 0 0 1.14.29c.57 0 1.18-.7 1.82-2.12a9.07 9.07 0 0 0 .95-2.8 1.55 1.55 0 0 0 0-.34c0-.13-.07-.25-.1-.35L139 288.08a5.12 5.12 0 0 0-.93-1.53 2.14 2.14 0 0 0-1.18-.47ZM160.62 285.59v1.13h-3.18v9a5.94 5.94 0 0 0 .2 1.8 1.45 1.45 0 0 0 1.47 1.07 1.72 1.72 0 0 0 1-.26 4.89 4.89 0 0 0 .95-.85l.41.35-.35.47a4.88 4.88 0 0 1-1.72 1.54 3.78 3.78 0 0 1-1.75.46 2.47 2.47 0 0 1-2.5-1.64 6.82 6.82 0 0 1-.36-2.47v-9.47H153a.4.4 0 0 1-.12-.1.21.21 0 0 1 0-.12.36.36 0 0 1 .07-.24 3.49 3.49 0 0 1 .44-.4 13.29 13.29 0 0 0 1.56-1.45c.31-.37 1.06-1.34 2.24-2.93a.51.51 0 0 1 .24 0s0 .1 0 .23v3.84ZM161.66 299.56a2.72 2.72 0 0 0 1.68-.63 3.85 3.85 0 0 0 .39-2.18v-15a2.85 2.85 0 0 0-.22-1.29c-.15-.27-.52-.41-1.1-.41a1.88 1.88 0 0 0-.34 0l-.37.05v-.56l1.7-.49 1.4-.41 1.5-.47v9.7a10.29 10.29 0 0 1 1.75-1.66 5.17 5.17 0 0 1 3-.93 3.43 3.43 0 0 1 3.45 2.12 7.44 7.44 0 0 1 .5 3v6.37a4.08 4.08 0 0 0 .38 2.16 2.33 2.33 0 0 0 1.57.65v.42h-6.76v-.44a2.92 2.92 0 0 0 1.75-.64 3.79 3.79 0 0 0 .39-2.17v-6.33a5.1 5.1 0 0 0-.51-2.45 2 2 0 0 0-1.91-.94 3.79 3.79 0 0 0-2.36.88q-1.14.87-1.14 1.14v7.7a3.7 3.7 0 0 0 .4 2.19 3 3 0 0 0 1.74.62v.44h-6.89ZM180.81 293.11a34.27 34.27 0 0 1 5.74-2.55v-1.33a5.35 5.35 0 0 0-.32-2.21 2.21 2.21 0 0 0-2.2-1 2.91 2.91 0 0 0-1.51.41 1.28 1.28 0 0 0-.72 1.15 3.29 3.29 0 0 0 .08.63 4.41 4.41 0 0 1 .07.57 1.3 1.3 0 0 1-.57 1.22 1.43 1.43 0 0 1-.79.21 1.31 1.31 0 0 1-1.07-.46 1.6 1.6 0 0 1-.38-1 3.26 3.26 0 0 1 1.35-2.29 5.84 5.84 0 0 1 4-1.19c2 0 3.39.65 4.11 2a6.72 6.72 0 0 1 .58 3.14v6.26a3.92 3.92 0 0 0 .13 1.25.8.8 0 0 0 .84.61 1.27 1.27 0 0 0 .59-.11 5.84 5.84 0 0 0 .82-.53v.81a5.28 5.28 0 0 1-1.08 1 3.14 3.14 0 0 1-1.78.6 1.73 1.73 0 0 1-1.54-.69 3 3 0 0 1-.53-1.64 14.28 14.28 0 0 1-2 1.53 5.35 5.35 0 0 1-2.7.84 3.25 3.25 0 0 1-2.33-.94 3.2 3.2 0 0 1-1-2.4 4.44 4.44 0 0 1 2.21-3.89Zm5.74-1.77a14.4 14.4 0 0 0-3 1.32c-1.5.92-2.25 2-2.25 3.15a2.4 2.4 0 0 0 .94 2.1 2.24 2.24 0 0 0 1.35.43 3.78 3.78 0 0 0 2-.57 1.71 1.71 0 0 0 1-1.47ZM194.54 293.08a4.78 4.78 0 0 1-.79-2.7 5.29 5.29 0 0 1 5.56-5.11 7.17 7.17 0 0 1 2.52.53 7.16 7.16 0 0 0 2.64.54h2.19v1.26h-2.7c.18.44.33.82.43 1.14a6.07 6.07 0 0 1 .27 1.72 4.58 4.58 0 0 1-1.48 3.28 5.35 5.35 0 0 1-4 1.49 13.22 13.22 0 0 1-1.41-.14c-.3 0-.7.25-1.2.76a2.11 2.11 0 0 0-.75 1.25c0 .34.36.58 1.09.74a7.84 7.84 0 0 0 1.6.15 21 21 0 0 1 5.46.46 3 3 0 0 1 2.36 3.15q0 2.47-2.76 3.94A11.69 11.69 0 0 1 198 307a7.06 7.06 0 0 1-4.06-1 2.82 2.82 0 0 1-1.5-2.18 1.82 1.82 0 0 1 .4-1.1 11.85 11.85 0 0 1 1.56-1.6l1-.93.19-.18a4.51 4.51 0 0 1-1.07-.55 1.37 1.37 0 0 1-.62-1.11 2.15 2.15 0 0 1 .54-1.27 18.77 18.77 0 0 1 2.3-2.26 4.06 4.06 0 0 1-2.2-1.74Zm1.66 11.62a10.32 10.32 0 0 0 3.38.5 8.69 8.69 0 0 0 4.22-.9 2.57 2.57 0 0 0 1.62-2.21c0-.69-.44-1.16-1.31-1.39a16.7 16.7 0 0 0-3.09-.23h-1.22l-1.17-.05c-.23 0-.6-.06-1.11-.13s-.9-.14-1.15-.19-.43.33-.91 1a3.19 3.19 0 0 0-.74 1.84 1.84 1.84 0 0 0 1.48 1.76Zm5-10.73a3.18 3.18 0 0 0 .93-2.61 9 9 0 0 0-.75-3.34 2.61 2.61 0 0 0-2.53-1.86 2.11 2.11 0 0 0-2.11 1.45 5.22 5.22 0 0 0-.29 1.89 6.4 6.4 0 0 0 .92 3.42 2.73 2.73 0 0 0 2.39 1.52 2.24 2.24 0 0 0 1.4-.44ZM210.37 287.49a6.59 6.59 0 0 1 5.08-2.15 6.94 6.94 0 0 1 5.13 2 7.3 7.3 0 0 1 2 5.4 8.07 8.07 0 0 1-2 5.41 6.39 6.39 0 0 1-5.09 2.29 6.7 6.7 0 0 1-5.08-2.2 7.72 7.72 0 0 1-2.08-5.53 7.48 7.48 0 0 1 2.04-5.22Zm2.49-.4q-1.56 1.43-1.56 4.91a11.06 11.06 0 0 0 1.26 5.19 3.85 3.85 0 0 0 3.5 2.4 3 3 0 0 0 2.7-1.61 8.23 8.23 0 0 0 .94-4.21 11.22 11.22 0 0 0-1.2-5.1 3.79 3.79 0 0 0-3.49-2.39 3.13 3.13 0 0 0-2.15.81ZM223.72 299.48a3.77 3.77 0 0 0 1.9-.5 2.08 2.08 0 0 0 .47-1.64v-7a5.86 5.86 0 0 0-.29-2.21 1 1 0 0 0-1.05-.67h-.41l-.54.1v-.51l1.82-.64 1.29-.47c.52-.2 1.06-.42 1.61-.67.07 0 .11 0 .13.07a2.16 2.16 0 0 1 0 .33v2.56a12.27 12.27 0 0 1 1.94-2.17 3 3 0 0 1 1.93-.78 1.8 1.8 0 0 1 1.3.48A1.69 1.69 0 0 1 234 288a1.21 1.21 0 0 1-1 .44 1.8 1.8 0 0 1-1.2-.55 1.68 1.68 0 0 0-.94-.54c-.37 0-.81.29-1.34.88a2.67 2.67 0 0 0-.8 1.82v7a2.36 2.36 0 0 0 .62 1.87 3.09 3.09 0 0 0 2.08.49v.59h-7.7ZM237.67 293.11a34.27 34.27 0 0 1 5.74-2.55v-1.33a5.35 5.35 0 0 0-.32-2.21 2.22 2.22 0 0 0-2.2-1 2.91 2.91 0 0 0-1.51.41 1.28 1.28 0 0 0-.72 1.15 4 4 0 0 0 .07.63 3.5 3.5 0 0 1 .08.57 1.29 1.29 0 0 1-.58 1.22 1.4 1.4 0 0 1-.78.21 1.3 1.3 0 0 1-1.07-.46 1.55 1.55 0 0 1-.38-1 3.26 3.26 0 0 1 1.35-2.29 5.84 5.84 0 0 1 4-1.19c2 0 3.39.65 4.11 2a6.72 6.72 0 0 1 .58 3.14v6.26a4.34 4.34 0 0 0 .12 1.25.82.82 0 0 0 .85.61 1.27 1.27 0 0 0 .59-.11 5.84 5.84 0 0 0 .82-.53v.81a5.28 5.28 0 0 1-1.08 1 3.14 3.14 0 0 1-1.78.6 1.73 1.73 0 0 1-1.54-.69 3 3 0 0 1-.53-1.64 14.28 14.28 0 0 1-2 1.53 5.35 5.35 0 0 1-2.7.84 3.25 3.25 0 0 1-2.33-.94 3.17 3.17 0 0 1-1-2.4 4.44 4.44 0 0 1 2.21-3.89Zm5.74-1.77a14.4 14.4 0 0 0-3 1.32c-1.5.92-2.25 2-2.25 3.15a2.42 2.42 0 0 0 .93 2.1 2.29 2.29 0 0 0 1.36.43 3.78 3.78 0 0 0 2-.57 1.71 1.71 0 0 0 .95-1.47ZM250.11 295.08h.51a8.21 8.21 0 0 0 1 2.73 3.53 3.53 0 0 0 3.2 1.75 2.51 2.51 0 0 0 1.84-.65 2.19 2.19 0 0 0 .68-1.68 2.33 2.33 0 0 0-.39-1.26 4.05 4.05 0 0 0-1.37-1.19l-1.75-1a10 10 0 0 1-2.83-2.08 3.66 3.66 0 0 1-.91-2.47 3.71 3.71 0 0 1 1.25-2.87 4.53 4.53 0 0 1 3.14-1.13 6.08 6.08 0 0 1 1.82.32 8 8 0 0 0 1.12.31.84.84 0 0 0 .41-.08.69.69 0 0 0 .22-.25h.37l.11 4.36H258a7.11 7.11 0 0 0-.85-2.36 3.09 3.09 0 0 0-2.79-1.56 2.22 2.22 0 0 0-1.72.67 2.25 2.25 0 0 0-.63 1.58c0 1 .72 1.81 2.16 2.56l2.06 1.11q3.33 1.82 3.33 4.22a3.76 3.76 0 0 1-1.38 3 5.36 5.36 0 0 1-3.61 1.18 8.47 8.47 0 0 1-2.13-.32 10.59 10.59 0 0 0-1.4-.31.53.53 0 0 0-.33.13 1 1 0 0 0-.21.32h-.41ZM284.3 279a18.19 18.19 0 0 0 2 .51 1.57 1.57 0 0 0 .86-.25 1.3 1.3 0 0 0 .57-.78h.67l.29 7.19H288a10.28 10.28 0 0 0-1.67-3.45 6.41 6.41 0 0 0-5.3-2.47 6.7 6.7 0 0 0-5.49 2.66 11.36 11.36 0 0 0-2.13 7.31 10 10 0 0 0 2.24 6.83 7.21 7.21 0 0 0 5.68 2.56 9.18 9.18 0 0 0 4.58-1.19 13.77 13.77 0 0 0 2.64-2l.59.59a11.28 11.28 0 0 1-16.66.78 10.83 10.83 0 0 1-2.79-7.61 11.07 11.07 0 0 1 2.95-7.84 10.56 10.56 0 0 1 8.09-3.33 12.9 12.9 0 0 1 3.57.49ZM293.67 293.11a34.27 34.27 0 0 1 5.74-2.55v-1.33a5.35 5.35 0 0 0-.32-2.21 2.22 2.22 0 0 0-2.2-1 2.91 2.91 0 0 0-1.51.41 1.28 1.28 0 0 0-.72 1.15 4 4 0 0 0 .07.63 3.5 3.5 0 0 1 .08.57 1.29 1.29 0 0 1-.58 1.22 1.4 1.4 0 0 1-.78.21 1.3 1.3 0 0 1-1.07-.46 1.55 1.55 0 0 1-.38-1 3.26 3.26 0 0 1 1.35-2.29 5.84 5.84 0 0 1 4-1.19c2 0 3.39.65 4.11 2a6.72 6.72 0 0 1 .58 3.14v6.26a4.34 4.34 0 0 0 .12 1.25.82.82 0 0 0 .85.61 1.27 1.27 0 0 0 .59-.11 5.84 5.84 0 0 0 .82-.53v.81a5.28 5.28 0 0 1-1.08 1 3.14 3.14 0 0 1-1.78.6 1.73 1.73 0 0 1-1.54-.69 3 3 0 0 1-.53-1.64 14.28 14.28 0 0 1-2 1.53 5.35 5.35 0 0 1-2.7.84 3.25 3.25 0 0 1-2.33-.94 3.17 3.17 0 0 1-1-2.4 4.44 4.44 0 0 1 2.21-3.89Zm5.74-1.77a14.4 14.4 0 0 0-3 1.32c-1.5.92-2.25 2-2.25 3.15a2.42 2.42 0 0 0 .93 2.1 2.29 2.29 0 0 0 1.36.43 3.78 3.78 0 0 0 2-.57 1.71 1.71 0 0 0 1-1.47ZM304.59 306.45a2.54 2.54 0 0 0 1.86-.62 2.86 2.86 0 0 0 .39-1.67v-15a2.59 2.59 0 0 0-.29-1.53 1.39 1.39 0 0 0-1.08-.33h-.32l-.43.07v-.52l1.48-.48 3.14-1.08a.12.12 0 0 1 .12.07.27.27 0 0 1 0 .16v2.11a11.22 11.22 0 0 1 1.81-1.53 5.44 5.44 0 0 1 2.89-.88 4.72 4.72 0 0 1 3.71 1.84 7.49 7.49 0 0 1 1.56 5 9.29 9.29 0 0 1-1.87 5.66 5.61 5.61 0 0 1-4.69 2.55 4.88 4.88 0 0 1-1.52-.22 4.51 4.51 0 0 1-1.84-1.2V304c0 1.05.17 1.7.51 1.94a5.2 5.2 0 0 0 2.25.48v.58h-7.72Zm6.17-7.75a3.44 3.44 0 0 0 4.78-1.16 7.91 7.91 0 0 0 1.19-4.67 6.33 6.33 0 0 0-1.24-4.29 3.73 3.73 0 0 0-2.88-1.42 3.4 3.4 0 0 0-2.11.71 1.86 1.86 0 0 0-.95 1.39v7.85a3.15 3.15 0 0 0 1.21 1.59ZM321.06 299.56a3.89 3.89 0 0 0 2.11-.57c.29-.27.44-1 .44-2.24v-7.45a4.89 4.89 0 0 0-.14-1.41 1 1 0 0 0-1-.64h-.33l-.95.25v-.48l.68-.22c1.82-.6 3.09-1 3.81-1.35a2.11 2.11 0 0 1 .56-.18.85.85 0 0 1 0 .23v11.22a3.61 3.61 0 0 0 .43 2.23 3.16 3.16 0 0 0 2 .58v.47h-7.6Zm2.49-20.89a1.53 1.53 0 0 1 1.15-.48 1.59 1.59 0 0 1 1.15.47 1.57 1.57 0 0 1 .48 1.16 1.53 1.53 0 0 1-.48 1.14 1.56 1.56 0 0 1-1.15.48 1.53 1.53 0 0 1-1.15-.48 1.52 1.52 0 0 1-.47-1.14 1.57 1.57 0 0 1 .47-1.15ZM337.47 285.59v1.13h-3.19v9a6.28 6.28 0 0 0 .2 1.8 1.45 1.45 0 0 0 1.47 1.07 1.75 1.75 0 0 0 1-.26 5.19 5.19 0 0 0 .94-.85l.41.35-.34.47a5.06 5.06 0 0 1-1.72 1.54 3.84 3.84 0 0 1-1.75.46 2.47 2.47 0 0 1-2.5-1.64 6.82 6.82 0 0 1-.36-2.47v-9.47h-1.7a.25.25 0 0 1-.12-.1.16.16 0 0 1 0-.12.36.36 0 0 1 .07-.24 4.52 4.52 0 0 1 .44-.4 13.29 13.29 0 0 0 1.56-1.45c.32-.37 1.06-1.34 2.24-2.93a.45.45 0 0 1 .24 0s0 .1 0 .23v3.84ZM341.66 293.11a34 34 0 0 1 5.73-2.55v-1.33a5.39 5.39 0 0 0-.31-2.21 2.23 2.23 0 0 0-2.2-1 2.94 2.94 0 0 0-1.52.41 1.28 1.28 0 0 0-.72 1.15 4.4 4.4 0 0 0 .08.63 5.34 5.34 0 0 1 .08.57 1.31 1.31 0 0 1-.58 1.22 1.42 1.42 0 0 1-.78.21 1.33 1.33 0 0 1-1.08-.46 1.6 1.6 0 0 1-.38-1 3.24 3.24 0 0 1 1.36-2.29 5.84 5.84 0 0 1 4-1.19c2 0 3.39.65 4.11 2a6.87 6.87 0 0 1 .57 3.14v6.26a4.24 4.24 0 0 0 .13 1.25.81.81 0 0 0 .84.61 1.34 1.34 0 0 0 .6-.11 6.63 6.63 0 0 0 .81-.53v.81a4.81 4.81 0 0 1-1.08 1 3.12 3.12 0 0 1-1.78.6 1.74 1.74 0 0 1-1.54-.69 3 3 0 0 1-.52-1.64 14.28 14.28 0 0 1-2 1.53 5.35 5.35 0 0 1-2.71.84 3.23 3.23 0 0 1-2.32-.94 3.17 3.17 0 0 1-1-2.4 4.45 4.45 0 0 1 2.21-3.89Zm5.73-1.77a14.4 14.4 0 0 0-3 1.32c-1.5.92-2.25 2-2.25 3.15a2.4 2.4 0 0 0 .94 2.1 2.29 2.29 0 0 0 1.36.43 3.8 3.8 0 0 0 2-.57 1.7 1.7 0 0 0 .94-1.47ZM353.08 299.56a3.76 3.76 0 0 0 2-.58 2.3 2.3 0 0 0 .51-1.75V282a4.08 4.08 0 0 0-.17-1.42 1.26 1.26 0 0 0-1.27-.66 2.79 2.79 0 0 0-.47.05l-.65.14v-.52q2.12-.56 5.08-1.5a.11.11 0 0 1 .13.1 1.8 1.8 0 0 1 0 .4v18.71a2.33 2.33 0 0 0 .47 1.75 3.61 3.61 0 0 0 1.94.51v.44h-7.56Z" + transform="translate(.5 .5)" + /> + </g> + <path + style={{ + fillOpacity: 0, + stroke: '#e87f6a', + strokeWidth: 10, + }} + d="m239.67 170.92 20.58 35.65 20.57 35.64h-82.31l20.58-35.64 20.58-35.65 20.58 35.65-20.58-35.65z" + /> + </svg> +); + +export default PythagorasCapitalInvestment; diff --git a/src/config/validators/SekoyaLabs.tsx b/src/config/validators/SekoyaLabs.tsx new file mode 100644 index 0000000000..4d03cfade2 --- /dev/null +++ b/src/config/validators/SekoyaLabs.tsx @@ -0,0 +1,30 @@ +const SekoyaLabs = () => ( + <svg + xmlns="http://www.w3.org/2000/svg" + viewBox="0 0 473 479" + xmlSpace="preserve" + > + <path + fill="#040401" + d="M474 164v315.959H1.104V1.166H474V164m-70-84.5c-5.67-5.334-11.258-10.757-17.025-15.985-12.167-11.03-25.88-19.796-40.284-27.618-18.138-9.85-37.18-17.544-57.348-21.522-16.17-3.19-32.698-5.039-49.157-6.12-10.606-.696-21.489.313-32.04 1.919-14.282 2.173-28.6 4.819-42.479 8.757-23.21 6.586-44.259 17.866-64.087 31.695C82.452 63.968 65.904 79.92 51.587 98.113c-13.241 16.826-23.646 35.438-31.729 55.33-10.5 25.843-15.955 52.733-16.77 80.544-.24 8.248.049 16.59 1.03 24.778 1.628 13.602 3.007 27.365 6.313 40.606 4.892 19.586 12.429 38.301 22.555 55.944 13.71 23.888 30.692 44.936 51.624 62.893 16.088 13.801 33.763 24.885 52.853 34.075 27.802 13.382 57.269 20.064 87.755 21.408 16.38.722 33.058-.63 49.321-2.984 23.069-3.34 45.02-10.742 66-21.377 28.109-14.25 52.208-33.211 72.736-57.02 14.174-16.438 25.516-34.568 34.752-54.166 12.55-26.629 19.03-54.827 20.705-83.962.85-14.774-.081-29.837-1.994-44.537-2.685-20.642-7.876-40.762-16.336-60.028C439.037 123.736 424.325 100.224 404 79.5z" + /> + <path + fill="#CFAA48" + d="M404.25 79.75c20.075 20.474 34.787 43.986 46.152 69.867 8.46 19.266 13.65 39.386 16.336 60.028 1.913 14.7 2.844 29.763 1.994 44.537-1.676 29.135-8.155 57.333-20.705 83.962-9.236 19.598-20.578 37.728-34.752 54.167-20.528 23.808-44.627 42.768-72.737 57.019-20.978 10.635-42.93 18.037-65.999 21.377-16.263 2.355-32.94 3.706-49.321 2.984-30.486-1.344-59.953-8.026-87.755-21.408-19.09-9.19-36.765-20.274-52.853-34.075-20.932-17.957-37.914-39.005-51.624-62.893-10.126-17.643-17.663-36.358-22.555-55.944-3.306-13.24-4.685-27.004-6.314-40.606-.98-8.188-1.27-16.53-1.028-24.778.814-27.81 6.27-54.7 16.77-80.544 8.082-19.892 18.487-38.504 31.728-55.33C65.904 79.92 82.452 63.968 101.58 50.626c19.828-13.83 40.876-25.109 64.087-31.695 13.878-3.938 28.197-6.584 42.478-8.757 10.552-1.606 21.435-2.615 32.041-1.919 16.459 1.081 32.987 2.93 49.157 6.12 20.167 3.978 39.21 11.672 57.348 21.522 14.403 7.822 28.117 16.588 40.284 27.618 5.767 5.228 11.356 10.65 17.275 16.235m18.25 31.228c-11.568-17.25-25.656-32.312-41.645-45.385-16.17-13.221-33.85-24.097-53.027-32.837-28.002-12.76-57.411-18.113-87.758-19.665-5.899-.301-11.897.25-17.78.975-13.127 1.62-26.406 2.727-39.266 5.627-20.36 4.593-39.673 12.346-57.905 22.686-16.887 9.577-32.58 20.76-46.338 34.462-7.01 6.98-13.61 14.408-20.007 21.962-16.294 19.236-28.2 41.088-36.69 64.708-10.273 28.584-15.48 58.085-12.993 88.546 1.144 14.013 2.034 28.237 5.23 41.851 6.138 26.135 16.922 50.537 31.984 72.913 11.744 17.446 25.598 32.966 41.592 46.667 13.712 11.748 28.405 22.026 44.476 30.153 16.86 8.527 34.494 15.208 53 19.311 17.348 3.846 34.944 5.722 52.696 5.989 4.258.064 8.537-.52 12.79-.959 9.455-.973 19.053-1.302 28.32-3.216 23.679-4.89 46.269-13.033 67.391-24.966 23.37-13.203 43.965-29.75 61.449-50.22 15.698-18.382 28.32-38.476 37.812-60.792 11.823-27.795 17.158-56.822 17.954-86.704.313-11.754-1.126-23.675-2.96-35.332-1.915-12.168-4.361-24.406-8.185-36.087-6.91-21.105-16.608-41.014-30.14-59.687z" + /> + <path + fill="#040401" + d="M422.754 111.244c13.278 18.407 22.977 38.316 29.886 59.421 3.824 11.68 6.27 23.919 8.185 36.087 1.834 11.657 3.273 23.578 2.96 35.332-.796 29.882-6.13 58.909-17.954 86.704-9.492 22.316-22.114 42.41-37.812 60.791-17.484 20.471-38.08 37.018-61.449 50.22-21.122 11.934-43.712 20.077-67.392 24.967-9.266 1.914-18.864 2.243-28.318 3.216-4.254.438-8.533 1.023-12.791.959-17.752-.267-35.348-2.143-52.695-5.989-18.507-4.103-36.14-10.784-53.001-19.311-16.071-8.127-30.764-18.405-44.476-30.153-15.994-13.701-29.848-29.22-41.592-46.667-15.062-22.376-25.846-46.778-31.984-72.913-3.196-13.614-4.086-27.838-5.23-41.85-2.487-30.462 2.72-59.963 12.993-88.547 8.49-23.62 20.396-45.472 36.69-64.708C65.172 91.249 71.77 83.822 78.78 76.84c13.757-13.701 29.451-24.885 46.338-34.462 18.232-10.34 37.545-18.093 57.905-22.686 12.86-2.9 26.14-4.008 39.266-5.627 5.883-.725 11.881-1.276 17.78-.975 30.347 1.552 59.756 6.904 87.758 19.665 19.178 8.74 36.857 19.616 53.027 32.837 15.99 13.073 30.077 28.135 41.899 45.651m-193.82 245.597c-1.316-3.495-2.497-7.048-3.97-10.475-8.491-19.745-22.172-35.032-40.457-46.14-15.273-9.28-32.385-12.621-49.868-14.156-14.929-1.31-29.988-1.417-44.836-3.28-11.376-1.428-22.644-4.3-33.706-7.43-6.454-1.826-12.408-5.421-19.153-8.476.404 2.09.733 3.763 1.051 5.44.334 1.758.687 3.513.984 5.277 4.129 24.526 13.25 47.187 26.062 68.364 13.961 23.078 32.119 42.462 53.87 58.315 19.514 14.221 41.2 24.23 64.474 30.614 7.823 2.147 15.855 3.611 23.864 4.958 3.428.577 6.639.703 9.238-3.51 15.159-24.565 19.254-50.708 12.447-79.501m59.992-24.42c-3.987 8.151-8.793 16.013-11.786 24.514-5.815 16.518-6.76 33.658-3.03 50.84 1.929 8.888 4.14 17.826 10.281 24.984.935 1.09 3.25 1.978 4.56 1.62 22.462-6.145 43.306-15.71 62.43-29.155 15.938-11.202 29.974-24.453 42.1-39.494 16.823-20.87 28.83-44.523 35.707-70.48 3.033-11.453 4.962-23.243 6.655-34.986.88-6.108.155-12.446.155-18.624-.328.297-.915.66-1.276 1.182-6.736 9.699-15.56 17.222-24.922 24.236-15.75 11.801-34.201 16.877-52.744 22.098-11.489 3.235-22.904 7.234-33.71 12.269-14.216 6.623-24.914 17.843-34.42 30.996m-75.924-152.917c-9.725-8.052-19.362-16.216-29.265-24.043-1.575-1.246-4.518-1.747-6.536-1.292-17.29 3.891-34.696 3.633-52.142 1.811-18.119-1.891-35.408-6.814-51.753-14.925-9.408-4.668-9.216-4.578-14.39 5.173-11.01 20.751-18.266 42.756-21.651 65.93-.705 4.824-2.819 10.038.733 15.229 5.784 8.454 13.776 14.158 22.65 18.307 17.205 8.042 36.003 8.81 54.555 10.275 10.678.843 21.473.717 32.053 2.19 18.633 2.597 36.727 7.389 52.727 17.88 10.541 6.912 21.046 13.894 28.826 24.187 5.633 7.454 11.613 14.646 17.264 21.737 15.517-41.404 8.17-99.83-33.07-142.46m57.508 32.492c-.052 1.602-.638 3.425-.078 4.773 10.415 25.06 12.96 51.234 11.121 79.52 21.882-21.264 48.113-31.513 76.06-37.29.211-.93.387-1.262.34-1.56-2.222-14.232-3.314-28.552-.86-42.805 2.146-12.468 4.543-24.954 7.896-37.138 5.568-20.236 16.802-37.622 29.218-54.292.822-1.104 1.667-3.311 1.141-4.107-2.335-3.536-4.812-7.12-7.88-10.004-11.065-10.395-22.014-20.975-33.752-30.573-7.57-6.189-16.442-10.785-25.202-16.398-.58 6.253-.912 11.316-1.542 16.341-1.402 11.182-2.218 22.505-4.6 33.483-4.601 21.207-12.24 41.414-23.074 60.292-8.065 14.05-16.538 27.842-28.788 39.758M157 79.311c3.103 13.534 10.558 24.395 21.038 33.167 12.31 10.304 25.287 19.841 37.237 30.536 11.003 9.848 21.157 20.665 31.409 31.321 3.316 3.447 5.758 7.735 8.768 11.87 1.154-1.623 1.944-2.853 2.85-3.99 13.865-17.403 23.577-36.99 30.817-57.933.927-2.68.266-4.012-1.801-5.146-2.548-1.397-5.207-2.592-7.768-3.968-16.73-8.986-28.976-22.383-37.462-39.156-5.172-10.222-8.342-21.158-9.205-32.699-.08-1.06-1.763-2.9-2.627-2.858-5.716.279-11.401 1.133-17.116 1.503-15.427.998-30.088 5.282-44.677 10.008-5.303 1.718-10.405 3.898-11.233 9.956-.74 5.415-.205 11.003-.23 17.389m260.929 76.421-4.907-9.359c-21.692 30.635-30.791 63.754-27.152 101.745 5.482-4.108 9.976-7.06 13.991-10.561 13.289-11.585 22.14-26.219 27.585-42.794 1.327-4.039 1.302-9.171.086-13.26-2.536-8.524-6.283-16.687-9.603-25.77M102.522 90.984c-6.693 7.3-14.734 13.39-20.695 21.92 22.083 11.283 44.665 16.018 67.891 14.991-5.311-8.777-11.397-17.002-15.492-26.121-4.036-8.984-6.054-18.874-8.994-28.489-7.002 5.418-14.607 11.301-22.71 17.699m196.477-34.112c-.179-4.345-1.437-7.705-6.434-8.669-9.251-1.784-18.44-3.896-27.696-5.656-.608-.116-2.36 1.73-2.236 2.424 2.626 14.79 9.231 27.542 20.515 37.55 3.911 3.468 8.555 6.11 12.864 9.13l1.445-.834a8936.44 8936.44 0 0 0 1.542-33.945z" + /> + <path + fill="#CFAA48" + d="M228.963 357.259c6.778 28.375 2.683 54.518-12.476 79.084-2.6 4.212-5.81 4.086-9.238 3.51-8.009-1.348-16.041-2.812-23.864-4.959-23.273-6.385-44.96-16.393-64.474-30.614-21.751-15.853-39.909-35.237-53.87-58.315-12.812-21.177-21.933-43.838-26.062-68.364-.297-1.764-.65-3.52-.984-5.278-.318-1.676-.647-3.35-1.05-5.44 6.744 3.056 12.698 6.65 19.152 8.477 11.062 3.13 22.33 6.002 33.706 7.43 14.848 1.863 29.907 1.97 44.836 3.28 17.483 1.535 34.595 4.876 49.868 14.156 18.285 11.108 31.966 26.395 40.456 46.14 1.474 3.427 2.655 6.98 4 10.893zM288.992 332.06c9.44-12.792 20.138-24.012 34.354-30.635 10.806-5.035 22.221-9.034 33.71-12.269 18.543-5.221 36.994-10.297 52.744-22.098 9.362-7.014 18.186-14.537 24.922-24.236.361-.521.948-.885 1.276-1.182 0 6.178.725 12.516-.155 18.624-1.693 11.743-3.622 23.533-6.655 34.985-6.876 25.958-18.884 49.611-35.708 70.481-12.125 15.041-26.161 28.292-42.098 39.494-19.125 13.444-39.97 23.01-62.431 29.155-1.31.358-3.625-.53-4.56-1.62-6.14-7.158-8.352-16.096-10.282-24.985-3.73-17.181-2.784-34.321 3.031-50.84 2.993-8.5 7.8-16.362 11.852-24.874zM213.256 179.755c40.987 42.378 48.334 100.804 32.817 142.208-5.651-7.09-11.63-14.283-17.264-21.737-7.78-10.293-18.285-17.275-28.826-24.187-16-10.491-34.094-15.283-52.727-17.88-10.58-1.473-21.375-1.347-32.053-2.19-18.552-1.464-37.35-2.233-54.554-10.275-8.875-4.15-16.867-9.853-22.651-18.307-3.552-5.191-1.438-10.405-.733-15.23 3.385-23.173 10.64-45.178 21.651-65.929 5.174-9.751 4.982-9.841 14.39-5.173 16.345 8.11 33.634 13.034 51.753 14.925 17.446 1.822 34.852 2.08 52.142-1.811 2.018-.455 4.96.046 6.536 1.292 9.903 7.827 19.54 15.99 29.519 24.294m-111.332.654c-14.504 24.855-.358 51.798 25.62 58.283 21.217-29.918 3.978-58.033-21.905-64.678-1.162 1.835-2.368 3.738-3.715 6.395m67.488 66.663 4.815 2.962c22.969-21.081 17.956-52.038-9.683-65.748-14.28 12.125-20.628 34.882-7.001 51.656 3.271 4.027 7.409 7.35 11.869 11.13M86 212.591c1.228-14.104-13.355-28.992-26.877-31.313-.451.764-.912 1.61-1.433 2.416-13.65 21.12-5.588 42.798 16.073 52.606 2.946 1.334 5.109 1.227 6.784-2.24 3.155-6.532 6.29-12.974 5.453-21.47m151.654 39.24c2.631-17.008-6.622-30.874-23.436-37.908-1.472 2.207-3.004 4.487-4.517 6.778-12.28 18.591-4.232 40.209 14.708 49.245 1.242.593 4.29.024 4.81-.927 2.917-5.343 5.317-10.969 8.435-17.189zM270.76 211.741c12.001-11.662 20.474-25.453 28.539-39.504 10.835-18.878 18.473-39.085 23.074-60.292 2.382-10.978 3.198-22.3 4.6-33.483.63-5.025.963-10.088 1.542-16.34 8.76 5.612 17.633 10.208 25.202 16.397 11.738 9.598 22.687 20.178 33.752 30.573 3.068 2.883 5.545 6.468 7.88 10.004.526.796-.319 3.003-1.141 4.107-12.416 16.67-23.65 34.056-29.218 54.292-3.353 12.184-5.75 24.67-7.896 37.138-2.454 14.253-1.362 28.573.86 42.805.047.298-.129.63-.34 1.56-27.947 5.777-54.178 16.026-76.06 37.29 1.84-28.286-.706-54.46-11.12-79.52-.56-1.348.025-3.171.326-5.027m70.805 17.324c-11.646-20.064-33-23.744-51.697-14.308-1.342.678-2.915 3.726-2.43 4.742 3.26 6.84 5.999 14.348 10.875 19.914 11.381 12.991 32.31 14.342 48.712 4.13-1.659-4.514-3.37-9.167-5.46-14.478m-1.51-123.345c-1.351 3.97-3.46 7.857-3.917 11.928-1.671 14.935 5.466 25.772 17.263 33.967.96.668 3.279.234 4.469-.497 1.454-.894 2.432-2.605 3.532-4.028 7.138-9.231 9.999-19.385 6.683-30.897-2.937-10.2-10.13-16.652-19.287-22.184-2.851 3.77-5.606 7.412-8.744 11.71m-20.596 56.325c-.486 4.538-1.66 9.128-1.328 13.605.974 13.11 12.575 22.662 24.775 23.243 1.205.057 3.498-1.3 3.562-2.134.468-6.1 1.623-12.465.428-18.31-2.47-12.08-13.083-17.493-23.686-19.314-.945-.162-2.19 1.412-3.75 2.91zM157 78.874c.026-5.949-.509-11.537.231-16.952.828-6.058 5.93-8.238 11.233-9.956 14.59-4.726 29.25-9.01 44.677-10.008 5.715-.37 11.4-1.224 17.116-1.503.864-.042 2.548 1.797 2.627 2.858.863 11.54 4.033 22.477 9.205 32.699 8.486 16.773 20.732 30.17 37.462 39.156 2.56 1.376 5.22 2.57 7.768 3.968 2.067 1.134 2.728 2.467 1.801 5.146-7.24 20.944-16.952 40.53-30.816 57.932-.907 1.138-1.697 2.368-2.85 3.99-3.011-4.134-5.453-8.422-8.77-11.869-10.251-10.656-20.405-21.473-31.408-31.32-11.95-10.696-24.928-20.233-37.237-30.537-10.48-8.772-17.935-19.633-21.04-33.604m53.108 20.592c7.89-17.829.32-35.552-16.111-44.758-3.123 4.032-6.496 8.036-9.484 12.308-11.688 16.709-3.018 36.622 11.038 45.87.954.629 3.393.535 4.067-.204 3.633-3.98 6.926-8.27 10.49-13.216m14.9 15.517c-.002 2.164.1 4.334-.024 6.49-.786 13.698 11.174 27.523 24.448 31.184 1.052.29 3.254-.311 3.54-1.04 2.521-6.424 6.268-12.862 6.737-19.502 1.352-19.125-9.71-27.832-23.434-34-2.207-.992-4.253-.857-5.368 2.104-1.768 4.695-3.89 9.258-5.9 14.764zM417.965 156.119c3.285 8.697 7.032 16.86 9.568 25.383 1.216 4.09 1.241 9.222-.086 13.26-5.446 16.576-14.296 31.21-27.585 42.795-4.015 3.5-8.51 6.453-13.99 10.561-3.64-37.991 5.46-71.11 27.151-101.745 1.812 3.456 3.36 6.408 4.942 9.746m-5.013 50.41c6.509-6.7 9.226-16.813 1.404-27.39-11.278 3.711-16.41 10.838-15.19 22.312.336 3.166 2.11 6.657 4.34 8.884.782.782 4.902-1.774 7.496-2.812.45-.18.886-.39 1.95-.995zM102.771 90.728c7.854-6.14 15.459-12.024 22.46-17.442 2.941 9.615 4.96 19.505 8.995 28.489 4.095 9.119 10.181 17.344 15.492 26.121-23.226 1.027-45.808-3.708-67.89-14.992 5.96-8.529 14.001-14.618 20.943-22.176m7.065 8.926-4.575.704c-1.607 6.892 4.077 14.253 12.351 14.558 1.986.074 5.331-.385 5.73-1.46.732-1.974.32-4.87-.65-6.88-2.383-4.929-7.137-6.6-12.856-6.922zM299 57.336c-.515 11.47-1.029 22.476-1.543 33.482l-1.445.834c-4.309-3.02-8.953-5.662-12.864-9.13-11.284-10.008-17.889-22.76-20.515-37.55-.124-.695 1.628-2.54 2.236-2.424 9.256 1.76 18.445 3.872 27.696 5.656 4.997.964 6.255 4.324 6.434 9.132M289.953 63c-1.647-4.501-4.36-7.875-10.633-9.104-.255 3.845-1.612 7.943-.43 11.085 1.179 3.135 4.902 5.314 7.51 7.912 1.198-3.025 2.395-6.05 3.553-9.893z" + /> + <path + fill="#040401" + d="M101.995 180.033c1.276-2.28 2.482-4.184 3.644-6.02 25.883 6.646 43.122 34.76 21.905 64.68-25.978-6.486-40.124-33.429-25.55-58.66zM169.051 247.006c-4.1-3.713-8.237-7.037-11.508-11.064-13.627-16.774-7.28-39.53 7.001-51.656 27.639 13.71 32.652 44.667 9.683 65.748-1.554-.956-3.185-1.96-5.176-3.028zM86 213.062c.838 8.024-2.298 14.466-5.453 20.999-1.675 3.466-3.838 3.573-6.784 2.239-21.661-9.808-29.722-31.487-16.073-52.606.52-.806.982-1.652 1.433-2.416 13.522 2.32 28.105 17.209 26.877 31.784zM237.383 252.175c-2.847 5.874-5.247 11.5-8.164 16.843-.52.95-3.568 1.52-4.81.927-18.94-9.036-26.987-30.654-14.708-49.245 1.513-2.291 3.045-4.57 4.517-6.778 16.814 7.034 26.067 20.9 23.165 38.253zM341.755 229.394c1.9 4.982 3.611 9.635 5.27 14.15-16.402 10.211-37.331 8.86-48.712-4.13-4.876-5.567-7.615-13.075-10.875-19.915-.485-1.016 1.088-4.064 2.43-4.742 18.697-9.436 40.05-5.756 51.887 14.637zM340.245 105.392c2.947-3.97 5.702-7.614 8.553-11.383 9.156 5.532 16.35 11.985 19.287 22.184 3.316 11.512.455 21.666-6.683 30.897-1.1 1.423-2.078 3.134-3.532 4.028-1.19.731-3.508 1.165-4.47.497-11.796-8.195-18.933-19.032-17.262-33.967.456-4.071 2.566-7.957 4.107-12.256zM319.687 161.679c1.333-1.133 2.577-2.707 3.522-2.545 10.603 1.821 21.217 7.234 23.686 19.314 1.195 5.845.04 12.21-.428 18.31-.064.834-2.357 2.191-3.562 2.134-12.2-.581-23.8-10.133-24.775-23.243-.332-4.477.842-9.067 1.557-13.97zM210.03 99.847c-3.487 4.565-6.78 8.855-10.413 12.835-.674.739-3.113.833-4.067.205-14.056-9.25-22.726-29.162-11.038-45.87 2.988-4.273 6.361-8.277 9.484-12.309 16.43 9.206 24 26.93 16.034 45.14zM225.025 114.54c1.991-5.063 4.113-9.626 5.88-14.32 1.116-2.962 3.162-3.097 5.37-2.105 13.724 6.168 24.785 14.875 23.433 34-.469 6.64-4.216 13.078-6.736 19.501-.287.73-2.49 1.331-3.541 1.041-13.274-3.661-25.234-17.486-24.448-31.183.124-2.157.022-4.327.042-6.935zM412.641 206.732c-.753.4-1.19.61-1.639.79-2.594 1.039-6.714 3.595-7.496 2.813-2.23-2.227-4.004-5.718-4.34-8.884-1.22-11.474 3.912-18.6 15.19-22.312 7.822 10.577 5.105 20.69-1.715 27.593zM110.156 99.392c5.4.584 10.153 2.255 12.535 7.185.971 2.01 1.383 4.905.651 6.879-.399 1.075-3.744 1.534-5.73 1.46-8.274-.305-13.958-7.666-12.351-14.558 1.44-.222 3.008-.463 4.895-.966zM289.973 63.409c-1.178 3.434-2.375 6.459-3.573 9.484-2.608-2.598-6.331-4.777-7.51-7.912-1.182-3.142.175-7.24.43-11.085 6.272 1.23 8.986 4.603 10.653 9.513z" + /> + </svg> +); + +export default SekoyaLabs; diff --git a/src/config/validators/StakeWorld.tsx b/src/config/validators/StakeWorld.tsx new file mode 100644 index 0000000000..1f62d47037 --- /dev/null +++ b/src/config/validators/StakeWorld.tsx @@ -0,0 +1,238 @@ +const StakeWorld = () => ( + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"> + <path + style={{ + fill: '#fff', + }} + d="M0 0h512v512H0z" + /> + <path + fill="#184891" + d="M273.84 462.49c-15.14 0-30.15 0-45.7-.41a7.68 7.68 0 0 0-1.93-1.65c-27.81-4-54-12.12-77.78-28.09-17.76-11.7-34.83-23.95-48.87-40-4-4.41-7.16-9.92-10.18-15a50.74 50.74 0 0 1 7.57-1c4.54-.42 9.36-.83 11.15-6.06 2.2-5.51 5.78-7.57 11.84-6.61 3.71 2.07 7 4.82 10.73 5.92a33 33 0 0 0 13.22 1c1.38-.13 1.93-4.81 3.58-6.74a31.6 31.6 0 0 1 7.57-7.85c2.89-1.79 6.47-2.61 9.77-3.3a25.25 25.25 0 0 1 9.09 0 77.12 77.12 0 0 1 12.53 4c.41 1.1.55 2.48 1.1 2.75 8.53 3.31 10.6 11.29 13.63 18.73 1 2.2 2.06 5.36 3.85 6.33 4.41 2.34 6.06 5.92 7 10.19 1.24 4.4 2.89 8.67 4 12.94a10.84 10.84 0 0 1-.41 6.33c-4 8.4-8.4 16.66-12.8 25.33 3.44 1.51 7 2.89 10.46 4.68 3.3 1.51 7.43 2.61 9.91 5.23a14.09 14.09 0 0 0 9.78 3.72c5.64.27 11.56.27 17.2 1.51 8 1.65 15-2.06 16.25-10.05.55-4.4.55-8.95 1.1-13.35.14-1.24.27-2.89 1.24-3.58a116.25 116.25 0 0 1 12.8-10.6c3.17-1.93 6.2-3.44 7-7.57.14-1.38 2.34-2.62 3.86-3.17 5.92-1.65 11.7-2.89 17.75-4.4 1.24-.42 2.9-.83 3.58-1.79 5.79-8.13 6.47-17.49 4.41-26.85-.83-2.89-3.3-5.78-5.64-8.12-3.17-3.3-7.58-5.78-10.6-9.36-9.5-11.7-9.37-11.84-24-9.5a1.32 1.32 0 0 0-.41.14c-4.41.41-7-1.38-7.85-5.78-1.24-6.89-1.93-14-3.3-20.93-.41-2.06-2.07-3.71-3-6-2.89-15 2.75-24.1 17.48-27.4a3.15 3.15 0 0 0 1.38-.69c2.89-1.79 6.33-3.3 8.53-5.78 3.86-4 7-8.67 10.47-13.22 6.47-8.53 14.59-14 25.6-15.14 1.38-.14 3.31-2.48 3.58-4 1.1-5.64 1.52-11.29 2.2-17.35a54.71 54.71 0 0 1 4.55-6.88c4.95 3.17 9.91 5.92 14.31 9.09 2.76 1.79 4.82 1.51 6.89-.83 2.75-3.17 5.37-6.33 8.26-9.22 6.05-6.33 6.19-6.2 13.62-1.65a54.28 54.28 0 0 1 5.1 3c.69 6.47-3.17 12.81.55 18.86 1.93 3.17 1.93 6.34 1 9.78-.82 2.48-1.79 5.92-.82 7.43a44.06 44.06 0 0 1 9.5 24.92c3.3-.55 5.91-.69 8.67-1.52 5.5-1.65 11-3.71 16.65-5.64 4.27-1.51 7.16.28 8.13 4.13a156.73 156.73 0 0 0 5.37 16.79c.68 1.52 2.89 2.62 4.68 4a1.78 1.78 0 0 0 1.37.27 5.24 5.24 0 0 1 .59-.48 2 2 0 0 1 .69.41c3.72 3.86 7.16 7.44 10.88 11.29a87.59 87.59 0 0 1-2.34 10.74 189.45 189.45 0 0 1-27.54 52.45 213.59 213.59 0 0 1-42.95 44.05c-19.41 14.73-40.74 26-64.28 32.76-12.53 3.44-25.06 7.57-38.27 6.75-1.11-.14-2.21 1.24-3.31 2.06Z" + /> + <path + d="M49.18 229.71c4.4-2.07.82-5.65 2.48-8.4 7.29 4.27 17.89 5 17.75 16.24h25.88a37.6 37.6 0 0 1 2.9 2.62c-1.38 10.05-3.31 19-14.46 21.61-5.37 1.38-6.47 5-4.27 9.64 2.48 4.82.55 8.26-3.44 11-3.85 2.48-7.43 5.09-11.56 7.71-4.68-1.51-8.81-2.89-13.08-4.27a8.83 8.83 0 0 0-1.79-3.16c-.41-17.49-.41-35.11-.41-53Z" + style={{ + fill: '#1163ae', + }} + /> + <path + fill="#1773ba" + d="M261.73 49.51c1.24 4.4 1.93 8.81 3 13.08.82 3.16 0 5.36-3 7.57-5.37.68-10.33 1.1-15.15 1.37-6.05-3.71-7.84-8.81-5.64-15.41.55-1.79.28-3.86.28-6.2 6.74-.41 13.35-.41 20.51-.41Z" + /> + <path + d="M460.79 232.32c0 14.73 0 29.46-.42 44.6a15 15 0 0 1-5.5-1.79c-1.38-2.2-2.89-4-3.72-6.19a14.48 14.48 0 0 1-.83-5.92c.42-3.44 1.66-6.74 1.93-10.19s-.69-7.15-1.1-11.15c.41-8.26 1.38-9.36 9.64-9.36Z" + style={{ + fill: '#174994', + }} + /> + <path + fill="#273779" + d="M455 275.41c1.66.55 3.31 1.38 5.37 2.07a17.14 17.14 0 0 1 .42 4.81c-1.1 3.44-2.34 6.34-3 9.36-1.79 7-3.17 14.05-5 21.48a105 105 0 0 1-11.15-11c4.41-9.36 9-18.17 13.35-26.71Z" + /> + <path + fill="#1e4485" + d="M460.79 231.91c-8.26.41-9.23 1.51-9.91 9.64-4.55-3.17-8.68-6.47-12.95-9.92a12.24 12.24 0 0 1-2.34-15.69c.42 1.24.42 2.75 1.11 3 7.43 2.06 13.07 9.5 21.75 8.12a5 5 0 0 0 1.92 1.1 9.75 9.75 0 0 1 .42 3.72Z" + /> + <path + d="M263.38 313.4a129.47 129.47 0 0 1-15.83 3.72 9.9 9.9 0 0 1-3.44-.41c-6.61-1.79-13.22-3.72-20.24-6.06 1-9.63 2.34-18.72 3.72-27.67 8.53 1.38 13.35-2.48 14.87-10.74a8.65 8.65 0 0 1 1.37-2.89c3.17-2.2 6.75-5.78 8.54-5.09 8 3 15.55-.28 23.4.55 3.3.41 5.78-1.1 6.88-4.82 2.07-6.88 4.68-13.63 7.3-20.37 1.93-.28 3.44-.69 5.37-.83 3 0 6.19.28 8.81-.41 9.63-2.48 19-5.51 28.49-8.12-.68 5.64-1.1 11.29-2.2 16.93-.27 1.51-2.2 3.85-3.58 4-11 1.1-19.13 6.61-25.6 15.14-3.44 4.55-6.61 9.23-10.47 13.22-2.2 2.48-5.64 4-8.53 5.78a3.26 3.26 0 0 1-1.38.69c-14.73 3.3-20.37 12.39-17.48 27.39Z" + style={{ + fill: '#124b96', + }} + /> + <path + fill="#0f4e99" + d="M89.38 377.28a138.87 138.87 0 0 1-10.6-13.77c-.83-1.24-.28-3.58 0-5.78 8.94-5.09 17.75-9.36 22.16-19.27 7.16-3.58 13.9-7 20.92-10.74a6.88 6.88 0 0 0 2.07-2.61c1.51-5.65 5.37-6.34 10.19-5.1a7.63 7.63 0 0 0 2.47.42c8.26-.28 16.66-.55 24.92-.42.83 11.29-6.06 15-15 15.28a77.73 77.73 0 0 0-9.77.14 4.91 4.91 0 0 0-3 1.79c1.65 4.41-2.2 6.33-3.85 9.09-3.31 5.92-6.89 11.56-10.33 17.34-5.64-1-9.22 1.1-11.42 6.61-1.79 5.23-6.61 5.64-11.15 6.06a50.74 50.74 0 0 0-7.57 1Z" + /> + <path + fill="#124b97" + d="M119.94 363.65c3-5.78 6.6-11.42 9.91-17.34 1.65-2.76 5.5-4.68 3.85-9.09a4.91 4.91 0 0 1 3-1.79 77.73 77.73 0 0 1 9.77-.14c8.95-.27 15.84-4 15.29-15.55a52.57 52.57 0 0 1 5.36-7.57c7.58 3.58 14.73 7.15 21.89 10.6-5.37 10.46-4.82 21.61-2.61 33.17-4.27-1-8.4-2.48-12.53-3.3a25.25 25.25 0 0 0-9.09 0c-3.3.69-6.88 1.51-9.77 3.3a31.6 31.6 0 0 0-7.57 7.85c-1.65 1.93-2.2 6.61-3.58 6.74a33 33 0 0 1-13.22-1c-3.71-1.1-7-3.85-10.73-5.92Z" + /> + <path + fill="#124b97" + d="M384.11 192.4c2.75 1.93 6.88 3.44 8 6.2 1.93 5.23 2.34 11.15 2.89 16.93.14 1-2.06 2.34-3.44 3.3a58 58 0 0 1-6.05 3.86 26.58 26.58 0 0 1-5.24-2.34c-7.43-4.55-7.57-4.68-13.62 1.65-2.89 2.89-5.51 6.05-8.26 9.22-2.07 2.34-4.13 2.62-6.89.83-4.4-3.17-9.36-5.92-14.31-9.36a2.24 2.24 0 0 1-.28-1.38c.41-2.48.83-4.41 1.1-6.61.69-4 2.48-5.78 7-5.64a64.08 64.08 0 0 0 14.46-1.1c4.54-.83 9.91.13 11.42-7.3 1.93-10.32 2.21-8 11.15-8.26Z" + /> + <path + fill="#0f4e99" + d="M336.89 221.86v.69c-1.38 2.48-2.75 4.68-4.27 7.29-9.5 3-18.86 6.06-28.49 8.54-2.62.69-5.78.41-8.81.14-.55-.42-.55-1-.14-1.1a33.6 33.6 0 0 0 1.93-5c1.1-2.75 1.65-7.16 3.44-7.71 6.74-2.34 13.35-6.06 21.06-4.4 4.96 1 10.19 1 15.28 1.55Z" + /> + <path + fill="#273779" + d="M439 302.12c.09-.19.46-.1 1.1.27-.1.19-.51.09-1.1-.27Z" + /> + <path + fill="#105ca8" + d="M383 133.62a10.09 10.09 0 0 1-1.38 2.75 69.21 69.21 0 0 1-12.11-4.4c-3.72-1.66-5.65-1.24-7.85 2.75-1.79 3.17-5 7.16-8.12 7.71-8.67 1.51-12.25 5.92-12.53 14.18a6.58 6.58 0 0 1-.14 1.65c-.55 2.75-1.79 4.13-5.09 3.17-4.27-1.24-8.54-1.79-12.67-2.89-7.15-1.66-8-1.66-10.18 5.5-1.38 4.68-4.54 6.2-8.81 6.2-3.72.13-7.44 0-11 0-3.85 0-6.05 1.37-5.64 5.64a15.66 15.66 0 0 1-.14 6.2c-.41 1.79-1.37 4.26-2.75 4.68-8 3.58-13.35 8.53-13.63 17.75a15.4 15.4 0 0 1-.27 1.79c-3.58 4.55-5.92 10.6-12.53 10a32.89 32.89 0 0 0-15.57 2.7 9.76 9.76 0 0 1-4 .69c-5.64-.28-11-.83-16.66-1.1-1.92-7.44-3.71-15-5.5-22.44-.14-1 1-2.21 1.79-2.89a6.76 6.76 0 0 1 2.47-1c13.08-1.92 19.69-10 22.72-22.85a33.58 33.58 0 0 1 8.67 3.3c6.06 3.72 13.49.56 14.32-6.74.55-5 .82-9.91 1.1-14.87 5.23-1.24 10.19-2.61 15.28-3.85 1.79-.55 3.58-1.79 6.19-2.89 5.37-.28 10-.28 14.73-.28 5.24 0 11.57-5.78 12.12-11s.69-10.6 1.1-16c.14-4.54 1.51-5.51 5.37-3.44 5.09 2.89 9.64 6.19 14.87 8.67 2.06 1.1 5.92 1.51 7.43.41 4.54-3.16 9.09-4.13 14.73-4 1.24.55 2.75.55 3 1.24 1.93 5.09 6.47 6.33 10.74 8.12a77 77 0 0 1 9.91 5.51Z" + /> + <path + d="M57.44 195.57c.69-3 1-6.47 2.34-9.23 4.68-10.6 9.77-20.92 14.73-31.25 2.61-5.23 4.54-10.87 7.57-15.83 2.48-4 6.19-7.16 9.36-10.73 6.19-7.16 11.84-14.73 18.45-21.48 5.78-5.64 12.39-10.46 18.86-15.42A192.39 192.39 0 0 1 175 64c13.49-5.64 27.26-10.46 42-12 .14 0 .27.13.69.68a47.81 47.81 0 0 1 .55 7.44c0 6-1.24 7.16-7.44 7.16-5.5 0-11.15-.55-16.79 3.16-1.93 0-3.17.41-3.86-.14-8.12-5.23-12.94 0-17.75 5.37a98.45 98.45 0 0 1-7.71 7.44c-.69 1.65-1.93 3.3-2.07 5.09-.69 7.16-4 13.08-9.22 18.45-3.72-1.66-7.71-2.76-11.15-5.1-7.71-5.23-13.36-.55-19.69 3-5.92 3.31-5.64 9.36-7.29 14.46a11.88 11.88 0 0 1-3.17 4.54 35.68 35.68 0 0 1-7.57 4.54c-4.68 2.48-7.57 5.23-7.57 11.43.13 3.58-3 7.57-5.65 10.6-3.3 4-3.3 7.29 0 11.15 1.52 1.79 2.62 3.72 4.13 5.78-3.58 5.92-6.88 12.25-11 18-1.65 2.07-4.82 3-7.3 4.41-1.92 1.24-4 2.06-6.47 3.58-4.81 1.1-9.08 1.79-13.21 2.48Z" + style={{ + fill: '#1972b9', + }} + /> + <path + d="M221.81 218.69c5.78.14 11.15.69 16.79 1a9.76 9.76 0 0 0 4-.69 32.89 32.89 0 0 1 15.56-2.62c6.61.55 9-5.5 12.8-10a47.31 47.31 0 0 0 3.86-8.12c1.92-7 11.56-10.87 17.48-7.16 3.44 2.21 7.29 3.72 10.87 5.92-.27 1.93-.27 3.72-1.1 5-4.68 7.71-6.88 15.83-5.5 25 .55 3.31-1.11 7-1.79 10.6v.69a15.13 15.13 0 0 1-5.23 1.38c-5.51-1.1-10.19-2.34-15-3.17-1.79-.41-4.68-.41-5.64.69-3.72 4.41-8.95 5-13.91 6.47-2.75.69-4.95 3-7.57 4.68-1.51 1.1-3 2.62-4.54 2.89-6.47.55-12.8.83-19.27 1.1-9.23-10.46-8.4-21.61-1.79-33.59Z" + style={{ + fill: '#0f54a0', + }} + /> + <path + d="M358.92 118.75a22.52 22.52 0 0 0-14.32 4c-1.51 1.1-5.37.69-7.43-.41-5.23-2.48-9.78-5.78-14.87-8.67-3.86-2.07-5.23-1.1-5.37 3.44-.41 5.37-.69 10.74-1.1 16s-6.88 11-12.12 11h-14.59a75.48 75.48 0 0 1-4.54-11.56c1.38-5.92 2.75-11 4-16.38 8.4-4.13 17.07-7 26.57-7.16a4.63 4.63 0 0 0 1.93-.28c10.05-6.33 18.86-13.76 23.13-25.47.41-1 .55-2.2 1.1-4 4.81-1.79 9.22-3.16 13.49-4.68a77.51 77.51 0 0 1 6.88 4.68 7.47 7.47 0 0 1 1.38 2.34c3.58 7.85 7 15.7 10.05 23.54-7.17 2.32-12.82 5.76-14.19 13.61Z" + style={{ + fill: '#1262ae', + }} + /> + <path + d="M166.74 312.17c-1.65 2.34-3.17 4.68-5 7.15-8.54.56-16.94.83-25.2 1.11a7.63 7.63 0 0 1-2.47-.42c-4.82-1.24-8.68-.55-10.19 5.1a4 4 0 0 1-2.07 2.2c.42-5.92 1.1-11.56 1.93-17.62 4.82-5.23 9.09-10.33 9.5-17.76a4.12 4.12 0 0 1 1.38-2.48c5.64-6.74 11.56-13.63 17.34-20.37a1.7 1.7 0 0 1 1.1.14c6.34 3.85 12.25 7.7 18.72 11.83 1.24-3.16 2.34-5.64 3.31-8.12 2.06-5.5 4.13-11 6.61-16.65 2.89-1.38 5.5-2.76 8.12-4 4.54 7 9.08 14.18 13.35 21.89-1.24 1.1-2.06 2.07-3.16 2.48-7 2.75-14.32 5.23-21.2 8.26-2.07.82-4.27 2.75-4.82 4.68-2.71 7.41-4.91 15-7.25 22.58Z" + style={{ + fill: '#0e54a1', + }} + /> + <path + d="M167.15 312.17a221.94 221.94 0 0 1 6.85-22.58c.55-1.93 2.75-3.86 4.82-4.68 6.88-3 14.17-5.51 21.2-8.26 1.1-.41 1.92-1.38 3.16-2.2 2.2.27 4.41 1.51 5.78.82 5.65-2.34 9.5.42 13.36 3.86 1.51 1.24 3 2.34 4.95 3.71-1.1 9.09-2.48 18.18-3.85 27.81-11 4.82-21.48 10.88-34 12.12-7.58-3.45-14.73-7-22.31-10.6Z" + style={{ + fill: '#0f519c', + }} + /> + <path + d="M64.87 290.14c3.72-2.62 7.3-5.23 11.15-7.71 4-2.75 5.92-6.19 3.44-11-2.2-4.68-1.1-8.26 4.27-9.64 11.15-2.61 13.08-11.56 14.59-21.47a81.48 81.48 0 0 1 11-1.52 9.58 9.58 0 0 1 4.81 1.24c1.79 1.1 3.17 2.89 5 3.86 5.78 2.89 7.71 7.57 6.61 14.45-4.41 7-6.47 14.73-16 16.38-5.51.83-10.33 5.23-15.28 8.26a7.35 7.35 0 0 0-3.31 4.13c-.55 6.2-.82 12.53-1.23 18.86-.55.28-.83.69-1.38 1-6.2-.68-11.7-1.37-17.35-2.47-2.06-5.1-4.26-9.78-6.33-14.32Z" + style={{ + fill: '#0f5faa', + }} + /> + <path + d="M90.06 305.83c.28-6.19.55-12.52 1.1-18.72a7.35 7.35 0 0 1 3.31-4.11c5-3 9.77-7.43 15.28-8.26 9.5-1.65 11.56-9.36 16-15.83 8.67 2.89 17.34 6.33 26.15 9.91-5.64 7-11.56 13.91-17.2 20.65a4.12 4.12 0 0 0-1.38 2.48c-.41 7.43-4.68 12.53-9.77 17.76-7-2.2-13.22-4.68-20.38-4-4.3.54-8.7.12-13.11.12Z" + style={{ + fill: '#105ba6', + }} + /> + <path + fill="#1773ba" + d="M196.06 95.76c7.57.41 15.15.69 23.13 1.1-1.38 9.09-2.75 17.9-4.27 26.85a5.35 5.35 0 0 1-2.75 3 28.51 28.51 0 0 1-7.3 2.34c-3.85.68-6.05 3-7.84 6.19-.69 1.38-2.21 3.3-3.58 3.44a161.4 161.4 0 0 1-16.38.83 6.31 6.31 0 0 1-4.27-2.07c-3.17-4.26-6.2-8.81-9.91-14.18 8-4.4 15.69-8.53 23.26-12.94 1.38-.82 2.2-2.89 2.89-4.4.83-2.34 1.38-5 2.34-7.71a19.5 19.5 0 0 1 4.68-2.45Z" + /> + <path + d="M223.46 252.7c6.61-.69 12.94-1 19.41-1.52 1.51-.27 3-1.79 4.54-2.89 2.62-1.65 4.82-4 7.57-4.68 5-1.51 10.19-2.06 13.91-6.47 1-1.1 3.85-1.1 5.64-.69 4.82.83 9.5 2.07 14.59 3.17-1.79 6.74-4.4 13.49-6.47 20.37-1.1 3.72-3.58 5.23-6.88 4.82-7.85-.83-15.42 2.48-23.4-.55-1.79-.69-5.37 2.89-8.54 4.68-5.64-2.75-11-5.78-16.66-8-4-1.79-5.36-3.86-3.71-8.26Z" + style={{ + fill: '#0e4f9a', + }} + /> + <path + d="M57.44 196c4.13-1.1 8.4-1.79 12.94-2.62a12.82 12.82 0 0 1 2.62 4.25c2.76 8.4 7.16 14.87 16.94 16.25a4.34 4.34 0 0 1 3 3.3c1.11 6.47 1.66 12.94 2.34 20-8.53.41-16.93.41-25.88.41.14-11.28-10.46-12-17.62-16.79 1.66-8.12 3.58-16.25 5.65-24.78Z" + style={{ + fill: '#146db6', + }} + /> + <path + d="M189.87 251.87a55.36 55.36 0 0 1-8.54 4.41 58.45 58.45 0 0 1-10.87-1.66c-4.82-1.37-9.36-1.79-13.77.83-3.58-3.86-7-7.85-10.6-11.7-4.13-4.54-4.27-7.3-.41-12.12 3.17-3.85 14-5.5 17.21-1.65 5.37 6.75 13.35 8.26 20.65 10.88s7.7 3.14 6.33 11.01Z" + style={{ + fill: '#0e5da8', + }} + /> + <path + d="M246.58 72c4.82-.69 9.78-1.11 15-1.52 4 9.91 7.43 19.83 10.46 30.15a12.09 12.09 0 0 1-3.3 1.38c-9.09-3.72-18.17-7.57-27.81-11.57 1.93-6.19 3.86-12.11 5.65-18.44Z" + style={{ + fill: '#207bc0', + }} + /> + <path + d="M268.75 102.37a10.51 10.51 0 0 1 3.3-1.38c5.51 5 10.74 9.78 16.11 15a135.5 135.5 0 0 1-3.72 16.1c-9.63-2.47-19.13-5.5-29.46-8.81 4.82-7.28 9.23-13.89 13.77-20.91Z" + style={{ + fill: '#146ab2', + }} + /> + <path + d="M196.06 95.21a23 23 0 0 1-4.82 2.62C184 94 177.07 90 170.32 86.13c-1.65-1-3.44-1.79-5.37-2.9 2.34-2.61 5.1-4.81 7.44-7.57 4.81-5.37 9.63-10.6 17.75-5.37.69.55 2.07.14 3.45.14 1.1 8.26 1.65 16.38 2.47 24.78Z" + style={{ + fill: '#1e76bd', + }} + /> + <path + d="M267.1 151a109.88 109.88 0 0 1-.69 15c-.83 7.3-8.26 10.46-14.32 6.74a45.31 45.31 0 0 0-8.26-3.44c0-5.09-1.24-10.05 1.38-15.14 2.89-5.64 7.16-8.26 13.21-6.06 2.76 1 5.51 1.79 8.68 2.89Z" + style={{ + fill: '#0d62ab', + }} + /> + <path + d="M156.55 255.86c4.55-3 9.09-2.61 13.91-1.24a72.78 72.78 0 0 0 10.6 1.79c-1.79 5.51-3.86 11-5.92 16.52-1 2.48-2.07 5-3.31 8.12-6.47-4.13-12.38-8-18.58-11.83a128.72 128.72 0 0 1 3.3-13.36Z" + style={{ + fill: '#1256a7', + }} + /> + <path + d="M71.2 304.87c5.65.69 11.15 1.38 17.35 2.48 1.51 8 2.61 15.42 3.58 22.85-2.48.83-4.41 1.24-6.61 1.93a15.46 15.46 0 0 1-3.85-.28 9.08 9.08 0 0 0-3.58-.82c-5 .82-9.5 1.37-14 2.2-1.93-5.37-3.72-10.6-5.24-16.52 4.23-4.27 8.36-8.12 12.35-11.84Z" + style={{ + fill: '#0c5aa5', + }} + /> + <path + d="M81.67 332.13a23.55 23.55 0 0 1 4 .27c4 1.65 7.16 2.89 10.46 4.13l4.41 1.93c-4 9.91-12.81 14.18-21.75 18.86-8.14-5.92-10.79-15.14-14.74-23.82a89.24 89.24 0 0 1 14.31-2.06 24.65 24.65 0 0 0 3.31.69Z" + style={{ + fill: '#1154a0', + }} + /> + <path + d="M71.2 304.46c-4 4.13-8.12 8-12.39 11.84-2.75-9.78-5.23-19.69-7.43-30 4.27 1 8.4 2.34 13.08 3.85a135.08 135.08 0 0 1 6.74 14.31Z" + style={{ + fill: '#1655a6', + }} + /> + <path + d="M414.81 179.74q-1.8-9.65-3.44-19.28c-.55-4 0-7.43 3.44-10.05s3.44-6.33 1.92-10.32c-2.61-6.61-5-13.63-7.29-20.79 8 10 17.07 20 23.95 31 5.65 8.94 8.81 19.13 12.8 28.9.69 1.79.56 4 .42 6.34-10.88-1.65-21.34-3.72-31.8-5.78Z" + style={{ + fill: '#194790', + }} + /> + <path + d="M414.67 180.15c10.6 1.65 21.06 3.72 31.94 5.78a39.78 39.78 0 0 1 4.13 9.22c-3.44-.13-5.37 1-6.89 3.86-2.34 4.4-5.64 8.4-8.39 12.53-5.65-4.69-11.57-9.09-16.66-14.18-4.54-4.36-4.54-10.6-4.13-17.21Z" + style={{ + fill: '#184787', + }} + /> + <path + d="M373.65 105.12q-5.37-11.7-10.6-23.54a20.75 20.75 0 0 0-1-2.2c.83-.41 2.07-.83 2.62-.41 13.77 11.84 27.53 23.81 41.3 36.2a96.3 96.3 0 0 1-14.32 4.54c-2.75-2.34-5-4.54-7.43-6.47-3.58-2.89-7.16-5.36-10.6-8.12Z" + style={{ + fill: '#0c5e9c', + }} + /> + <path + fill="#184891" + d="M435.46 212c2.75-4.54 6.05-8.54 8.39-12.94 1.52-2.89 3.45-4 6.89-3.44 2.48 7.57 4.68 15.28 6.88 23a40.39 40.39 0 0 1 .83 5.09c-.28.55-.42.69-.28.83s.14.27.41.41v1.79c-8.81 1.79-14.45-5.65-21.88-7.71-.69-.28-.69-1.79-1.11-3 0-1.6-.13-2.71-.13-4.03Z" + /> + <path + d="M354.51 74.29a99.41 99.41 0 0 1-13.21 4.54c-5.37-2.89-10.19-6.2-15.28-9-4.13-2.2-7.3-4.95-7.85-10.19 5.09 1 10.46 1.66 15.14 3.58 7.3 3.17 14.18 7.16 21.2 11Z" + style={{ + fill: '#0d5f9d', + }} + /> + <path + fill="#1e4485" + d="M458.58 224.61c-.27.14-.41 0-.41-.13s0-.28.28-.42.13.14.13.55Z" + /> + <path + d="M295.18 237.42c.27-3.45 1.93-7.16 1.38-10.47-1.38-9.22.82-17.34 5.5-25 .83-1.24.83-3 1.65-5 6.89-3 13.08-6.74 20.65-5.78 1.38.14 3.45-1.38 4.41-2.61q6.33-8.4 12.11-16.94c1.52-2.47 3-3.58 5.92-2.47 3.72 1.23 7.71 1.79 11.57 2.89 4.13 1.51 7.57.82 10.6-2.62 1.51-1.79 4-2.34 6-3.44 6.2 3.85 12.39 7.85 19.14 12.11-3.58 4.82-6.88 9.09-10.05 14a5.65 5.65 0 0 1-2.07.28c-8.94.28-9.22-2.06-11.15 8.26-1.51 7.43-6.88 6.47-11.42 7.3a64.08 64.08 0 0 1-14.46 1.1c-4.54-.14-6.33 1.65-7 5.64-.27 2.2-.69 4.13-1.1 6.61a81 81 0 0 1-15.28-1c-7.71-1.66-14.32 2.06-21.06 4.4-1.79.55-2.34 5-3.44 7.71a33.6 33.6 0 0 1-1.93 5Z" + style={{ + fill: '#11519d', + }} + /> + <path + d="M374.89 165.56c-1.93 1.51-4.41 2.06-5.92 3.85-3 3.44-6.47 4.13-10.6 2.62-3.86-1.1-7.85-1.66-11.57-2.89-2.89-1.11-4.4 0-5.92 2.47q-5.78 8.53-12.11 16.94c-1 1.23-3 2.75-4.41 2.61-7.57-1-13.76 2.75-20.65 5.37-4.13-1.79-8-3.3-11.42-5.51-5.92-3.71-15.56.14-17.48 7.16a39.7 39.7 0 0 1-3.72 7.71c-.28-.41-.14-1-.14-1.38.28-9.22 5.65-14.17 13.63-17.75 1.38-.42 2.34-2.89 2.75-4.68a15.66 15.66 0 0 0 .14-6.2c-.41-4.27 1.79-5.64 5.64-5.64 3.58 0 7.3.13 11 0 4.27 0 7.43-1.52 8.81-6.2 2.2-7.16 3-7.16 10.18-5.5 4.13 1.1 8.4 1.65 12.67 2.89 3.3 1 4.54-.42 5.09-3.17a6.58 6.58 0 0 0 .14-1.65c.28-8.26 3.86-12.67 12.53-14.18 3.16-.55 6.33-4.54 8.12-7.71 2.2-4 4.13-4.41 7.85-2.75a109.51 109.51 0 0 0 12 4.68c2.34 7.43 5 15-2.34 21.33-2.06 1.79-2.89 4.82-4.26 7.58Z" + style={{ + fill: '#0c58a3', + }} + /> + <path + fill="#105ca8" + d="M373.1 105.12c4 2.76 7.57 5.23 11.15 8.12 2.47 1.93 4.68 4.13 7.16 6.47a156.55 156.55 0 0 1-8.13 13.77c-3.44-1.79-6.6-3.85-10.18-5.37-4.27-1.79-8.81-3-10.74-8.12-.28-.69-1.79-.69-3-1.24 1-7.85 6.61-11.29 13.77-13.63Z" + /> + <path + fill="#0f56a2" + d="M100.94 338.46c-1.79-.69-3.44-1.24-4.82-1.93-3.3-1.24-6.47-2.48-10.05-4.13 1.65-1 3.58-1.37 6.06-2.2-1-7.43-2.07-14.87-3.17-22.85a1.76 1.76 0 0 1 1-1.38c4.54-.14 8.94.28 13.21-.27 7.16-.69 13.35 1.79 20.1 4.4-.28 5.65-1 11.29-1.65 17.35-6.78 3.99-13.52 7.43-20.68 11.01Z" + /> + <path + fill="#0f56a2" + d="M81.67 331.85a5.48 5.48 0 0 1-2.89-.41 3.75 3.75 0 0 1 2.89.41Z" + /> + </svg> +); + +export default StakeWorld; diff --git a/src/config/validators/Stakely.tsx b/src/config/validators/Stakely.tsx new file mode 100644 index 0000000000..4f47984501 --- /dev/null +++ b/src/config/validators/Stakely.tsx @@ -0,0 +1,20 @@ +const Stakely = () => ( + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 87.02 87.02"> + <path + d="m63.29 15.51-4.18 11.43c-8.27-4.5-19-5-21.46-1.33-2 2.95-1.64 7.67 6.27 10.19a1.49 1.49 0 0 1 .64 2.43l-5.49 6a5.93 5.93 0 0 1-7.17 1.23c-4.06-2.16-9.49-6.67-9.9-15.24-.77-18.12 21.44-23.28 41.29-14.71Z" + style={{ + fillRule: 'evenodd', + fill: '#f79367', + }} + /> + <path + d="M41.32 46.79a1.56 1.56 0 0 0 .6 2.53c4.27 1.57 10.59 4 9.27 9.68-1.56 6.75-17.3 4.88-26.26.73L21 71.08a49.74 49.74 0 0 0 31.74 3.19C70.46 70.11 65.86 52 64.29 48.69c-1.39-2.9-4.88-7.12-11.6-9.85a4.4 4.4 0 0 0-4.83 1Z" + style={{ + fill: '#415876', + fillRule: 'evenodd', + }} + /> + </svg> +); + +export default Stakely; diff --git a/src/config/validators/Stakenode.tsx b/src/config/validators/Stakenode.tsx new file mode 100644 index 0000000000..7e10712965 --- /dev/null +++ b/src/config/validators/Stakenode.tsx @@ -0,0 +1,29 @@ +const Stakenode = () => ( + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"> + <defs> + <linearGradient + id="a" + x1={-285.62} + y1={1459.68} + x2={-284.99} + y2={1459.68} + gradientTransform="scale(-647.12 647.12) rotate(2.588 32160.098 7037.231)" + gradientUnits="userSpaceOnUse" + > + <stop offset={0} stopColor="#33c4f2" /> + <stop offset={1} stopColor="#2e3690" /> + </linearGradient> + </defs> + <path fill="#fff" d="M0 0h512v512H0z" /> + <path + d="m206 104.43-82.28 33.76a8.55 8.55 0 0 0-4.78 5 8.37 8.37 0 0 0 .42 6.89l6.4 12.45a4 4 0 0 1-.49 4.43l-24.83 29.6a.63.63 0 0 1-.77.14.63.63 0 0 1-.35-.7l8.16-33.33a8.33 8.33 0 0 0 0-4.15L104 145.15a8.48 8.48 0 0 0-5.7-6.05 8.39 8.39 0 0 0-8.15 1.62l-28.41 23.77a8.56 8.56 0 0 0-3.1 6.61v12.65a8.67 8.67 0 0 0 2.67 6.19l9.36 9a4 4 0 0 1 1.19 3.59l-5.2 30.24a8.74 8.74 0 0 0 2.18 7.31l29 31.29a8.55 8.55 0 0 0 6.12 2.75 8.52 8.52 0 0 0 6.26-2.46l33-32.56a3.88 3.88 0 0 1 4.36-.85 4 4 0 0 1 2.53 3.66l.14 6.54a8.63 8.63 0 0 0 8.58 8.44h57.1a4.17 4.17 0 0 1 2.81 1.12l30.66 29.68a4 4 0 0 1 1.27 2.74L251 302a4.42 4.42 0 0 1-.63 2.32l-3.59 5.63a8.56 8.56 0 0 0-.28 8.79 8.51 8.51 0 0 0 7.52 4.5h9.71a8.63 8.63 0 0 0 7.17-3.8l7.39-10.9a8.35 8.35 0 0 0 1.47-4.78V248.1a3.77 3.77 0 0 1 .78-2.32l1.68-2.32a3.84 3.84 0 0 1 4.5-1.48 4 4 0 0 1 2.82 3.87V257a8.57 8.57 0 0 0 8.58 8.58h75.66a4.06 4.06 0 0 1 2.53.84l34.25 27.08a4 4 0 0 1 1.55 3.16v11.11a3.89 3.89 0 0 1-.64 2.18 8.45 8.45 0 0 0-.28 8.79 8.52 8.52 0 0 0 7.53 4.5h5.62a8.55 8.55 0 0 0 8-5.48l2.32-6a9.34 9.34 0 0 0 .56-3.1v-34a8.79 8.79 0 0 0-2.88-6.47l-5-4.5a6.93 6.93 0 0 0-5-6.33v-51.74l23.77 14.49v15.68a6.86 6.86 0 0 0-4.64 6.54 6.93 6.93 0 0 0 7 7 7 7 0 0 0 7-7 7 7 0 0 0-4.64-6.54v-18.21l-32-19.41a9.85 9.85 0 0 0-2.82-1.48l-54.07-15.4a3.67 3.67 0 0 1-.78-.28l-10.68-5.48v-.36a6.94 6.94 0 0 0-7-7 7.09 7.09 0 0 0-4.29 1.48L308.6 153.9v-.28a7 7 0 0 0-7-7 6.89 6.89 0 0 0-4.22 1.48l-84.31-43.46a8.86 8.86 0 0 0-3.94-1 10.71 10.71 0 0 0-3.23.7m1.75 4.22a4.07 4.07 0 0 1 3.38.14l43.39 22.36a7 7 0 0 0-4.15 4.22h-23.21l-7.38 7.95h-30.24l-11.25-11.25h32.49a6.91 6.91 0 0 0 6.54 4.64 6.93 6.93 0 0 0 7-7 7 7 0 0 0-7-7 7 7 0 0 0-6.54 4.64h-48.81zm7.35 21.82v-1.27a2.4 2.4 0 0 1 2.25-1.75 2.37 2.37 0 0 1 2.39 2.39 2.38 2.38 0 0 1-2.39 2.39 2.31 2.31 0 0 1-2.25-1.76M123.41 148a4.3 4.3 0 0 1-.21-3.23 4 4 0 0 1 2.25-2.32l25.31-10.41h21l15.75 15.82h34.18L229 140h21.38a6.9 6.9 0 0 0 6.54 4.65 6.94 6.94 0 0 0 7-7 8.19 8.19 0 0 0-.28-1.82L295.06 152v.07h-54.43l-20.81 20.81h-93.53l2.46-2.95a8.56 8.56 0 0 0 1.06-9.49l-2-3.8h20.67l8.72 9.07h46.48a6.93 6.93 0 0 0 6.54 4.64 7 7 0 0 0 7-7 7 7 0 0 0-7-7 7 7 0 0 0-6.54 4.64h-24a6.94 6.94 0 0 0 5.41-6.75 6.93 6.93 0 0 0-7-7 7 7 0 0 0-6.54 4.64h-46.2zm131.15-10.34a2.37 2.37 0 0 1 2.44-2.36 2.38 2.38 0 0 1 2.39 2.39 2.37 2.37 0 0 1-2.39 2.39 2.41 2.41 0 0 1-2.39-2.39M63.21 171.1a4.11 4.11 0 0 1 1.48-3.1l28.41-23.77a4 4 0 0 1 3.87-.77 4 4 0 0 1 2.67 2.88l3.45 13.36a4.67 4.67 0 0 1 0 2l-8.23 33.41a5.1 5.1 0 0 0 2.67 5.83 4.3 4.3 0 0 0 1.62.49l-.07.15h39.17a6.91 6.91 0 0 0 6.54 4.64 6.94 6.94 0 0 0 7-7 7 7 0 0 0-7-7 7 7 0 0 0-6.54 4.64h-8.93l3.58-6h16.32a6.91 6.91 0 0 0 6.54 4.64 6.94 6.94 0 0 0 7-7 6.94 6.94 0 0 0-7-7 7 7 0 0 0-6.54 4.65H130.3l-6.4 10.47h-17.79l16.31-19.48h29.4L182 195.85h141.54a6.93 6.93 0 0 0 6.54 4.64 6.93 6.93 0 0 0 7-7 6.93 6.93 0 0 0-7-7 7 7 0 0 0-6.54 4.64h-33.4l11.32-14.07h35.65a6.92 6.92 0 0 0 6.54 4.65 6.78 6.78 0 0 0 5.21-2.4l10.34 5.35a6.42 6.42 0 0 0 1.61.63l47.33 13.5a6.88 6.88 0 0 0-4.78 4.43H348.3l-19.2 20.89h-26.3l-15.19-11.25h21.09a6.93 6.93 0 0 0 6.54 4.64 7 7 0 0 0 7-7 6.94 6.94 0 0 0-7-7 7 7 0 0 0-6.54 4.64H75.45l.84-5.07a8.61 8.61 0 0 0-2.53-7.66l-9.35-9a3.06 3.06 0 0 1-.42-.64h16.66a6.92 6.92 0 0 0 6.54 4.65 6.94 6.94 0 0 0 7-7 7 7 0 0 0-7-7 7 7 0 0 0-6.54 4.64H63.21zm112.59-16.67a2.36 2.36 0 0 1 2.39-2.39 2.37 2.37 0 0 1 2.39 2.39 2.37 2.37 0 0 1-2.39 2.39 2.46 2.46 0 0 1-2.39-2.39m66.66 2.25h53a7 7 0 0 0 6.26 3.94 6.87 6.87 0 0 0 5.21-2.46l28.55 14.69h-36.27l-15 18.57h-101l-22.78-14.07h102.85a6.9 6.9 0 0 0 6.54 4.65 7 7 0 0 0 7-7 6.94 6.94 0 0 0-7-7 7 7 0 0 0-6.54 4.64h-37.13zm-87.62 0h16.81a7 7 0 0 0 5 4.5h-17.52zm112.66 18.57a2.37 2.37 0 0 1 2.39-2.4 2.38 2.38 0 0 1 2.39 2.4 2.38 2.38 0 0 1-2.39 2.39 2.42 2.42 0 0 1-2.39-2.39m75.59 2.25h1.13a1.65 1.65 0 0 1-.57.14 1.32 1.32 0 0 1-.56-.14m-15.4 16.31a2.37 2.37 0 0 1 2.39-2.39 2.36 2.36 0 0 1 2.39 2.39 2.36 2.36 0 0 1-2.39 2.39 2.42 2.42 0 0 1-2.39-2.39m-184.87 6.61v-2.32a2.5 2.5 0 0 1 2-1.27 2.38 2.38 0 0 1 2.39 2.39 2.38 2.38 0 0 1-2.39 2.4 2.43 2.43 0 0 1-2-1.2m274 5.49a6.65 6.65 0 0 0-1.9-4.72 2 2 0 0 1 .7.36v.07l.71.42a3.94 3.94 0 0 1 1.47 3v52.6a6.83 6.83 0 0 0-4.15 5h-6.25l-33.83-33.82h6a6.86 6.86 0 0 0 6.68 5.2 7 7 0 0 0 7-7 7 7 0 0 0-7-7 6.88 6.88 0 0 0-6.33 4.15h-44.56l14.9-16h53.1a6.84 6.84 0 0 0 6.54 4.64 6.92 6.92 0 0 0 6.89-7m-9.35 0a2.37 2.37 0 0 1 2.39-2.4 2.38 2.38 0 0 1 2.39 2.4 2.38 2.38 0 0 1-2.39 2.39 2.42 2.42 0 0 1-2.39-2.39m-94.59 4.78a2.37 2.37 0 0 1 2.39-2.39 2.37 2.37 0 0 1 2.4 2.39 2.38 2.38 0 0 1-2.4 2.39 2.42 2.42 0 0 1-2.39-2.39m-111.24 2.25H280l21.31 15.82h13.43l19.41 23.84h30.38a6.92 6.92 0 0 0 6.54 4.64 7 7 0 0 0 7-7 7 7 0 0 0-7-7 7 7 0 0 0-6.54 4.64h-28.2l-15.75-19.27H367l38.4 38.39h8.79a6.84 6.84 0 0 0 6.18 3.8 6.71 6.71 0 0 0 5.35-2.6l3.59 3.16a4.18 4.18 0 0 1 1.33 3v34a3.67 3.67 0 0 1-.28 1.48L428 316a4.12 4.12 0 0 1-3.8 2.6h-5.62a4.1 4.1 0 0 1-3.59-2.11 4.18 4.18 0 0 1 .14-4.15 8.51 8.51 0 0 0 1.34-4.64v-11.15a8.58 8.58 0 0 0-3.24-6.75L379 262.72a8.82 8.82 0 0 0-5.35-1.82h-75.56a4.08 4.08 0 0 1-4.08-4.08v-11.11a8.52 8.52 0 0 0-6-8.23 9.21 9.21 0 0 0-2.67-.42 8.38 8.38 0 0 0-7 3.65l-1.63 2.29a8.46 8.46 0 0 0-1.62 5v4.22h-20.74l-10.06-10.06h17.93a6.86 6.86 0 0 0 6.54 4.64 6.93 6.93 0 0 0 7-7 7 7 0 0 0-7-7 7 7 0 0 0-6.54 4.64h-29.81zM72.14 237.06a4.08 4.08 0 0 1-1-3.45l3.52-20.67h119.63l36.64 29.32h7l14.63 14.63h22.64v46.83a3.71 3.71 0 0 1-.7 2.25l-7.39 10.9a4 4 0 0 1-3.37 1.76h-9.71a4.09 4.09 0 0 1-3.58-2.11 4 4 0 0 1 .14-4.15l3.59-5.62a8.72 8.72 0 0 0 1.33-4.93l-.42-11.53a8.49 8.49 0 0 0-2.6-5.91L224 256.89h9.28l27.92 25.24a6.77 6.77 0 0 0-.92 3.38 6.94 6.94 0 0 0 7 7 7 7 0 0 0 7-7 7 7 0 0 0-7-7 7.86 7.86 0 0 0-2.61.49l-29.53-26.79h-76.3a4.1 4.1 0 0 1-4.08-3.94l-.14-6.54a8.49 8.49 0 0 0-5.34-7.81 3.77 3.77 0 0 0-.85-.21l5.07-5.06h10l12 15.47h36.14v-4.57h-33.87l-8.44-10.9h23.13a6.87 6.87 0 0 0 6.68 5.2 6.94 6.94 0 0 0 7-7 7 7 0 0 0-7-7 6.87 6.87 0 0 0-6.32 4.15h-41.14l-31.79 31.64-12.8 12.66a4.6 4.6 0 0 1-3 1.2 4.06 4.06 0 0 1-2.88-1.27l-10.78-11.62 17.93-20.26h15.19a7 7 0 0 0 6.61 4.86 7 7 0 0 0 7-7 7 7 0 0 0-7-7 7 7 0 0 0-6.47 4.5h-17.58l-19 21.45zM383.74 227a2.37 2.37 0 0 1 2.39-2.39 2.38 2.38 0 0 1 2.39 2.39 2.37 2.37 0 0 1-2.39 2.39 2.36 2.36 0 0 1-2.39-2.39m-187.13 0a2.38 2.38 0 0 1 2.39-2.39 2.37 2.37 0 0 1 2.39 2.39 2.36 2.36 0 0 1-2.39 2.39 2.37 2.37 0 0 1-2.39-2.39m-68.77 8.37v-2.32a2.4 2.4 0 0 1 2-1.27 2.38 2.38 0 0 1 2.39 2.39 2.38 2.38 0 0 1-2.39 2.4 2.16 2.16 0 0 1-2-1.2M446 242.26a2.35 2.35 0 0 1 2-2.32h.84a2.34 2.34 0 0 1 2 2.32 2.36 2.36 0 0 1-2.39 2.39 2.42 2.42 0 0 1-2.39-2.39M418 264a2.42 2.42 0 0 1 1.13-2 11.52 11.52 0 0 0 1.48 1.83l1.68 1.48a2.3 2.3 0 0 1-1.9 1.05A2.42 2.42 0 0 1 418 264m-153.11 21.58a2.37 2.37 0 0 1 2.4-2.39 2.37 2.37 0 0 1 2.39 2.39 2.37 2.37 0 0 1-2.39 2.39 2.37 2.37 0 0 1-2.4-2.39m-75.59-22.22a8.51 8.51 0 0 0-8.09 5.62 8.5 8.5 0 0 0 2.46 9.5l9.22 7.87a7.87 7.87 0 0 0 1.82 1.2l8.44 4.15a4 4 0 0 1 2.18 2.81 4.13 4.13 0 0 1-.84 3.45l-9.28 11a8.51 8.51 0 0 0-1.2 9.14 8.5 8.5 0 0 0 7.81 5h7.66a8.62 8.62 0 0 0 6.89-3.45l18.5-24.47a8.61 8.61 0 0 0-1.2-11.67l-20.88-18.11a8.71 8.71 0 0 0-5.63-2.11H189.3zm8.79 52.95a3.85 3.85 0 0 1 .56-4.29l9.28-11a8.57 8.57 0 0 0 1.83-7.31 8.68 8.68 0 0 0-4.64-6l-8.44-4.15a5.84 5.84 0 0 1-.84-.56l-9.21-7.88a4 4 0 0 1-1.13-4.5 4 4 0 0 1 3.8-2.67H207a4.19 4.19 0 0 1 2.67 1l20.89 18.15a4 4 0 0 1 .56 5.48L212.58 317a4.17 4.17 0 0 1-3.24 1.62h-7.59a3.92 3.92 0 0 1-3.66-2.32m159.77-43.74a8.4 8.4 0 0 0-7.95 5.34 8.47 8.47 0 0 0 2 9.43l6.12 6a8.45 8.45 0 0 0 2.32 1.62l6.54 3.1a4.1 4.1 0 0 1 2.18 2.53 3.93 3.93 0 0 1-.49 3.3l-5.28 8.37a8.48 8.48 0 0 0-.21 8.72 8.45 8.45 0 0 0 7.53 4.43h1.68a8.61 8.61 0 0 0 6.9-3.52l15.05-20.39a8.55 8.55 0 0 0-1.16-11.49l-16.74-15.19a8.51 8.51 0 0 0-5.83-2.25h-12.66zm9.14 46.2a4 4 0 0 1 .14-4.15l5.28-8.44a8.58 8.58 0 0 0 1-7 8.63 8.63 0 0 0-4.57-5.34l-6.54-3.1a4.2 4.2 0 0 1-1.13-.77l-6.11-6a4 4 0 0 1-.92-4.43 4 4 0 0 1 3.73-2.53h12.73a4.07 4.07 0 0 1 2.74 1.05l16.74 15.26a4.12 4.12 0 0 1 .56 5.42l-15.05 20.39a4 4 0 0 1-3.23 1.62h-1.83a3.56 3.56 0 0 1-3.52-2" + fill="url(#a)" + /> + <path + d="M31.34 392c0-2.22.83-3.36 2.47-3.36s2.4 1 2.4 3.1a7.37 7.37 0 0 0 1.08 4.49c.69.89 2.09 1.33 4.05 1.33h23.21c3.66 0 5.5-2.28 5.5-6.89a9.76 9.76 0 0 0-1.52-5.67 4.6 4.6 0 0 0-4-2.15H43.36c-4.3 0-7.34-1.14-9.23-3.36a13.14 13.14 0 0 1-2.85-8.85c0-3.73.95-6.51 2.91-8.47s5.31-2.91 10.12-2.91h21.06a12.57 12.57 0 0 1 5 .88 5.66 5.66 0 0 1 2.85 2.53 12 12 0 0 1 1.2 3.23 21.72 21.72 0 0 1 .32 3.79c0 2.22-.83 3.29-2.41 3.29s-2.46-1-2.46-3.1c0-2.21-.38-3.73-1.14-4.55s-2.09-1.2-4-1.2h-23q-5.51 0-5.51 6.45a8.59 8.59 0 0 0 1.51 5.42 4.86 4.86 0 0 0 4 1.89H63a14.43 14.43 0 0 1 4.87.76 9.44 9.44 0 0 1 3.42 2 10 10 0 0 1 2.15 2.91A14.08 14.08 0 0 1 74.6 387a22.58 22.58 0 0 1 .32 3.67c0 3.86-1 6.76-3 8.79s-5.24 3-10 3H40.7a12.24 12.24 0 0 1-4.23-.63 7.34 7.34 0 0 1-2.72-1.58 7 7 0 0 1-1.52-2.4 13.3 13.3 0 0 1-.76-2.79c-.06-.94-.13-1.96-.13-3.06zM85 361.42c0-1.39 1-2.15 2.91-2.28v-9.67c0-2.21.76-3.35 2.22-3.35 1.77 0 2.65 1.14 2.65 3.35v9.74h12.9c2.22 0 3.36.88 3.36 2.65 0 1.52-1.14 2.28-3.36 2.28H92.81v21.69a23.73 23.73 0 0 0 .38 4.55 9.45 9.45 0 0 0 1.27 3.23 7.76 7.76 0 0 0 2 2.09 9 9 0 0 0 3 1.2 26.2 26.2 0 0 0 3.73.5c1.27.07 2.85.13 4.74.13 2.22 0 3.29.76 3.29 2.21 0 1.78-1.07 2.66-3.29 2.66a52.64 52.64 0 0 1-6.76-.38 20.59 20.59 0 0 1-5.5-1.45 10.86 10.86 0 0 1-4.24-2.85 12 12 0 0 1-2.6-4.72 23.22 23.22 0 0 1-1-7.09V364c-1.83-.11-2.83-1-2.83-2.58zm33.4 29.41a23.2 23.2 0 0 1 .63-5.88 11.54 11.54 0 0 1 2-4.3 13.23 13.23 0 0 1 3-2.91 14.26 14.26 0 0 1 4.18-1.84 34.21 34.21 0 0 1 5.06-.95 58.17 58.17 0 0 1 6.07-.25h13.78v-5.31a6.32 6.32 0 0 0-1.07-4.11c-.7-.82-2.09-1.2-4-1.2H134.8c-7.65 0-11.44 1.89-11.44 5.63a5.07 5.07 0 0 1-.51 2.46 2.19 2.19 0 0 1-2 .82c-1.58 0-2.34-1.07-2.34-3.28a8.64 8.64 0 0 1 4.3-8c2.84-1.71 6.64-2.53 11.32-2.53h14.54q5.5 0 7.4 2.65c1.27 1.77 2 4.81 2 9.17v28.84c0 2.15-.82 3.16-2.46 3.16s-2.41-1.07-2.41-3.29v-3.28a16.56 16.56 0 0 1-6.83 4.42 28 28 0 0 1-9.1 1.46h-8.73a9.37 9.37 0 0 1-7.21-3.16c-1.96-2-2.9-4.84-2.9-8.32zm4.86.5a6.37 6.37 0 0 0 1.65 4.3 4.65 4.65 0 0 0 3.54 1.9h8.73c5.25 0 9.23-1.39 11.89-4.24s4-6.89 4-12.26v-1.4h-13.71c-1.84 0-3.29 0-4.49.07a34 34 0 0 0-3.61.38 12.43 12.43 0 0 0-3 .76 11 11 0 0 0-2.15 1.32 5.71 5.71 0 0 0-1.58 2.09 15.47 15.47 0 0 0-.89 3 20 20 0 0 0-.35 4.08zm48.45 8.79v-54.89c0-2.09.82-3.1 2.4-3.1s2.47 1 2.47 3.1v31.94l18.15-17.33a3.53 3.53 0 0 1 2.27-1.14 2.36 2.36 0 0 1 1.58.7 2.19 2.19 0 0 1 .76 1.77 3.16 3.16 0 0 1-1.14 2.15l-15.56 14.23 22.36 20.8a3.32 3.32 0 0 1 1 2.22 2.31 2.31 0 0 1-.63 1.58 2 2 0 0 1-1.58.76 4 4 0 0 1-2.34-1.14l-22.2-21.44-2.72 2.72V400c0 2-.82 3-2.53 3a2.24 2.24 0 0 1-1.64-.63 3.19 3.19 0 0 1-.62-2.25zm39.71-14.23v-10.18a23.47 23.47 0 0 1 1-7.08 13 13 0 0 1 2.59-4.74 11 11 0 0 1 4.37-2.85 24.78 24.78 0 0 1 5.75-1.39 59.68 59.68 0 0 1 7.21-.38H235a58 58 0 0 1 7.15.38 23.58 23.58 0 0 1 5.69 1.39 11 11 0 0 1 4.36 2.85 12.9 12.9 0 0 1 2.59 4.74 23.16 23.16 0 0 1 .95 7.08v3.73h-39.48v6.39a25.67 25.67 0 0 0 .31 4.05 11.71 11.71 0 0 0 .89 3 5.71 5.71 0 0 0 1.58 2.09 8.77 8.77 0 0 0 2.15 1.33 8.91 8.91 0 0 0 3 .76c1.2.18 2.47.31 3.61.37s2.65.13 4.49.13h12.58c2 0 3.35-.44 4-1.33a7.45 7.45 0 0 0 1.07-4.49c0-2.08.83-3.1 2.47-3.1s2.4 1.14 2.4 3.36a21.08 21.08 0 0 1-.19 3 17.59 17.59 0 0 1-.69 2.78 7.24 7.24 0 0 1-1.52 2.41 7.87 7.87 0 0 1-2.72 1.58 12.26 12.26 0 0 1-4.24.63h-13.13a59.68 59.68 0 0 1-7.21-.38 24.29 24.29 0 0 1-5.75-1.39 10.9 10.9 0 0 1-4.37-2.85 12.85 12.85 0 0 1-2.59-4.8 23.86 23.86 0 0 1-.95-7.09zm4.87-11.32H251a14.05 14.05 0 0 0-.7-4.67 7.81 7.81 0 0 0-1.77-3 6.92 6.92 0 0 0-3.16-1.77 24.46 24.46 0 0 0-4.37-.76c-1.51-.13-3.47-.19-5.94-.19h-2.72c-2.47 0-4.43.06-5.94.19a24.46 24.46 0 0 0-4.37.76 6.63 6.63 0 0 0-3.22 1.77 7.81 7.81 0 0 0-1.77 3 13.56 13.56 0 0 0-.72 4.67zM268 399.81v-38c0-2.21.76-3.29 2.21-3.29 1.77 0 2.66 1.14 2.66 3.35V365c3.66-3.93 9-5.89 16-5.89h2.72a57.76 57.76 0 0 1 7.14.38 23.59 23.59 0 0 1 5.69 1.4 10.89 10.89 0 0 1 4.37 2.84 13 13 0 0 1 2.59 4.74 23.54 23.54 0 0 1 .95 7.09v24.15c0 2.15-.76 3.17-2.34 3.17-1.71 0-2.53-1.08-2.53-3.17v-24.06a24.15 24.15 0 0 0-.38-4.62 9.2 9.2 0 0 0-1.27-3.22 7.93 7.93 0 0 0-2-2.09 9.66 9.66 0 0 0-3.16-1.2 30.24 30.24 0 0 0-4-.51c-1.32-.06-3-.12-5.12-.12h-2.72c-5.31 0-9.29 1.39-12 4.23s-4 7-4 12.4v19.1c0 2.15-.82 3.16-2.4 3.16-1.6.22-2.41-.78-2.41-2.97zm56.66-13.92v-10.18a23.47 23.47 0 0 1 1-7.08 13 13 0 0 1 2.59-4.74 11 11 0 0 1 4.36-2.85 25 25 0 0 1 5.76-1.39 59.68 59.68 0 0 1 7.21-.38h2.72a57.76 57.76 0 0 1 7.14.38 23.66 23.66 0 0 1 5.7 1.39 11 11 0 0 1 4.36 2.85 13 13 0 0 1 2.59 4.74 23.47 23.47 0 0 1 1 7.08v10.18a23.54 23.54 0 0 1-1 7.09 13.34 13.34 0 0 1-2.59 4.8 10.93 10.93 0 0 1-4.36 2.85 22 22 0 0 1-5.7 1.39 58.45 58.45 0 0 1-7.14.38h-2.72a59.68 59.68 0 0 1-7.21-.38 24.49 24.49 0 0 1-5.76-1.39 10.93 10.93 0 0 1-4.36-2.85 12.85 12.85 0 0 1-2.59-4.8 23.86 23.86 0 0 1-1.01-7.09zm4.87-10.11v10.05a25.59 25.59 0 0 0 .32 4.05 11.33 11.33 0 0 0 .88 3 5.82 5.82 0 0 0 1.58 2.09 8.77 8.77 0 0 0 2.15 1.33 8.91 8.91 0 0 0 3 .76c1.21.18 2.47.31 3.61.37s2.65.13 4.49.13h2.72c2.08 0 3.79-.06 5.12-.13a28.3 28.3 0 0 0 4-.5 7.67 7.67 0 0 0 3.17-1.2 7.09 7.09 0 0 0 2-2.09 8.22 8.22 0 0 0 1.26-3.23 23.19 23.19 0 0 0 .38-4.61v-10.09a23.19 23.19 0 0 0-.38-4.61 9.24 9.24 0 0 0-1.26-3.23 8.08 8.08 0 0 0-2-2.09 9.64 9.64 0 0 0-3.17-1.2 28.3 28.3 0 0 0-4-.5c-1.33-.07-3-.13-5.12-.13h-2.72c-1.84 0-3.29 0-4.49.06s-2.4.19-3.61.38a12.93 12.93 0 0 0-3 .76 15 15 0 0 0-2.15 1.33 5.82 5.82 0 0 0-1.58 2.09 14.82 14.82 0 0 0-.88 3 29.59 29.59 0 0 0-.32 4.24zm51.16 10.11v-10.18a23.47 23.47 0 0 1 .95-7.08 13 13 0 0 1 2.59-4.74 11 11 0 0 1 4.36-2.85 25.25 25.25 0 0 1 5.7-1.39 58.45 58.45 0 0 1 7.14-.38h18.72v-13.66c0-2.28.89-3.35 2.66-3.35 1.52 0 2.21 1.14 2.21 3.35v54.32c0 2.15-.82 3.17-2.4 3.17s-2.47-1.08-2.47-3.29v-3.29c-3.67 3.92-9 5.88-16 5.88h-2.72a57.76 57.76 0 0 1-7.14-.38 23.2 23.2 0 0 1-5.7-1.39 11.37 11.37 0 0 1-4.36-2.85 12.85 12.85 0 0 1-2.59-4.8 23.86 23.86 0 0 1-.96-7.09zm4.87-10.11v10.05a24.07 24.07 0 0 0 .38 4.62 9.19 9.19 0 0 0 1.26 3.22 8.11 8.11 0 0 0 2 2.09 9.23 9.23 0 0 0 3.16 1.2 30.31 30.31 0 0 0 4 .51c1.33.06 3 .12 5.13.12h2.71c5.32 0 9.3-1.39 12-4.23s4-6.9 4-12.27v-16.95h-18.65c-2.09 0-3.8.06-5.12.13a28.63 28.63 0 0 0-4 .5 7.91 7.91 0 0 0-3.16 1.2 8.06 8.06 0 0 0-2 2.09 7.29 7.29 0 0 0-1.27 3.23 17.81 17.81 0 0 0-.44 4.49zm52.3 10.11v-10.18a23.16 23.16 0 0 1 .95-7.08 12.9 12.9 0 0 1 2.59-4.74 11 11 0 0 1 4.36-2.85 24.87 24.87 0 0 1 5.76-1.39 59.57 59.57 0 0 1 7.21-.38h2.72a57.76 57.76 0 0 1 7.14.38 23.46 23.46 0 0 1 5.69 1.39 11 11 0 0 1 4.37 2.85 13 13 0 0 1 2.59 4.74 23.47 23.47 0 0 1 .95 7.08v3.73h-39.54v6.39a25.59 25.59 0 0 0 .32 4.05 11.71 11.71 0 0 0 .89 3 5.71 5.71 0 0 0 1.58 2.09 8.58 8.58 0 0 0 2.15 1.33 8.91 8.91 0 0 0 3 .76c1.26.18 2.47.31 3.6.37s2.66.13 4.49.13h12.59c2 0 3.35-.44 4.05-1.33a7.45 7.45 0 0 0 1.07-4.49c0-2.08.82-3.1 2.4-3.1s2.47 1.14 2.47 3.36a21.08 21.08 0 0 1-.19 3 18.75 18.75 0 0 1-.69 2.78 7.24 7.24 0 0 1-1.52 2.41 7.87 7.87 0 0 1-2.72 1.58 12.26 12.26 0 0 1-4.24.63h-13.25a59.51 59.51 0 0 1-7.2-.38 24.25 24.25 0 0 1-5.76-1.39 10.86 10.86 0 0 1-4.36-2.85 12.86 12.86 0 0 1-2.6-4.8 26.16 26.16 0 0 1-.88-7.09zm4.87-11.32h34.65a15.83 15.83 0 0 0-.69-4.67 7.93 7.93 0 0 0-1.77-3 6.91 6.91 0 0 0-3.17-1.77 24.35 24.35 0 0 0-4.36-.76c-1.52-.13-3.48-.19-5.94-.19h-2.72c-2.47 0-4.43.06-6 .19a24.51 24.51 0 0 0-4.36.76 6.67 6.67 0 0 0-3.23 1.77 8.06 8.06 0 0 0-1.77 3 15.83 15.83 0 0 0-.65 4.67z" + fill="#2f3b96" + /> + </svg> +); + +export default Stakenode; diff --git a/src/config/validators/Stakepile.tsx b/src/config/validators/Stakepile.tsx new file mode 100644 index 0000000000..9816035c8c --- /dev/null +++ b/src/config/validators/Stakepile.tsx @@ -0,0 +1,112 @@ +const Stakepile = () => ( + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 560 560"> + <path + style={{ + fill: '#fff', + }} + d="M0 0h560v560H0z" + /> + <path + style={{ + fill: 'none', + stroke: '#f5980c', + strokeWidth: 9, + }} + d="M27.5 388.3v33.03M467.93 388.3v33.03" + /> + <ellipse + cx={247.8} + cy={421.33} + rx={224.8} + ry={64.23} + style={{ + fill: '#f5980c', + }} + /> + <path + d="M465.11 386.47c0 5.06-3.66 11.55-14.37 18.78-10.45 7.06-26.07 13.72-46 19.42-39.84 11.38-95.31 18.53-156.9 18.53s-117.06-7.15-156.9-18.53c-20-5.7-35.58-12.36-46-19.42C34.15 398 30.5 391.53 30.5 386.47s3.65-11.55 14.36-18.79c10.46-7.06 26.07-13.71 46-19.42 39.84-11.38 95.32-18.52 156.9-18.52s117.06 7.14 156.9 18.52c20 5.71 35.59 12.36 46 19.42 10.79 7.24 14.45 13.72 14.45 18.79Z" + style={{ + fill: '#f2c94c', + stroke: '#f5980c', + strokeWidth: 15, + }} + /> + <path + style={{ + fill: 'none', + stroke: '#f5980c', + strokeWidth: 9, + }} + d="M89.89 307.56v33.03M530.32 307.56v33.03" + /> + <ellipse + cx={310.2} + cy={340.59} + rx={224.8} + ry={64.23} + style={{ + fill: '#f5980c', + }} + /> + <path + d="M527.5 305.72c0 5.06-3.65 11.55-14.36 18.79-10.46 7.06-26.07 13.71-46 19.42-39.84 11.38-95.32 18.52-156.9 18.52s-117.06-7.14-156.9-18.52c-20-5.71-35.59-12.36-46-19.42-10.71-7.24-14.37-13.73-14.37-18.79s3.66-11.55 14.37-18.79c10.45-7.06 26.07-13.71 46-19.41C193.14 256.13 248.61 249 310.2 249s117.06 7.14 156.9 18.53c20 5.7 35.58 12.35 46 19.41 10.75 7.23 14.4 13.72 14.4 18.78Z" + style={{ + fill: '#f2c94c', + stroke: '#f5980c', + strokeWidth: 15, + }} + /> + <path + style={{ + fill: 'none', + stroke: '#f5980c', + strokeWidth: 9, + }} + d="M27.5 226.81v33.03M467.93 226.81v33.03" + /> + <ellipse + cx={247.8} + cy={259.84} + rx={224.8} + ry={64.23} + style={{ + fill: '#f5980c', + }} + /> + <path + d="M465.11 225c0 5.06-3.66 11.54-14.37 18.78-10.45 7.06-26.07 13.71-46 19.42-39.84 11.38-95.31 18.52-156.9 18.52s-117.06-7.14-156.9-18.52c-20-5.71-35.58-12.36-46-19.42C34.15 236.52 30.5 230 30.5 225s3.65-11.55 14.36-18.79c10.46-7.06 26.07-13.71 46-19.42 39.84-11.38 95.32-18.53 156.9-18.53s117.06 7.15 156.9 18.53c20 5.71 35.59 12.36 46 19.42 10.79 7.22 14.45 13.7 14.45 18.79Z" + style={{ + fill: '#f2c94c', + stroke: '#f5980c', + strokeWidth: 15, + }} + /> + <path + style={{ + fill: 'none', + stroke: '#f5980c', + strokeWidth: 9, + }} + d="M89.89 146.06v33.04M530.32 146.06v33.04" + /> + <ellipse + cx={310.2} + cy={179.1} + rx={224.8} + ry={64.23} + style={{ + fill: '#f5980c', + }} + /> + <path + d="M527.5 144.23c0 5.06-3.65 11.55-14.36 18.78-10.46 7.07-26.07 13.72-46 19.42C427.26 193.82 371.78 201 310.2 201s-117.06-7.14-156.9-18.53c-20-5.7-35.59-12.35-46-19.42-10.71-7.23-14.37-13.72-14.37-18.78s3.66-11.55 14.37-18.79c10.45-7.06 26.07-13.71 46-19.42 39.84-11.42 95.31-18.56 156.9-18.56s117.06 7.14 156.9 18.5c20 5.71 35.58 12.36 46 19.42 10.75 7.26 14.4 13.75 14.4 18.81Z" + style={{ + fill: '#f2c94c', + stroke: '#f5980c', + strokeWidth: 15, + }} + /> + </svg> +); + +export default Stakepile; diff --git a/src/config/validators/Stakeplus.tsx b/src/config/validators/Stakeplus.tsx new file mode 100644 index 0000000000..c7777d3e8a --- /dev/null +++ b/src/config/validators/Stakeplus.tsx @@ -0,0 +1,41 @@ +const Stakeplus = () => ( + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 400"> + <path + d="M169.48 176.66a7.35 7.35 0 0 0-.28 2.59v2.31l1.5.24c.82.14 1.91.33 2.4.43l.91.19-.11 18.28-.1 18.3-1.41.23c-2.2.35-2.39.61-2.39 3.2v2.38h16.8v-2.38c0-2.59-.19-2.85-2.39-3.2-1.5-.24-2-1.85-1.43-4.27.2-.83 4.47-4.5 4.75-4.11l2.79 3.63c2.91 3.77 3.28 5 1.68 5.37-.93.23-1 .42-1 2.6v2.36h16.4v-5.19l-1.67-.27c-2.07-.33-5.67-4.08-8.93-9.3a5.79 5.79 0 0 0-.61-.84c-2.55-2.42-3.1-4.51-1.44-5.48.29-.18 1.84-1.49 3.43-2.92 3.12-2.81 4.36-3.6 6.22-4 1.18-.25 1.2-.29 1.2-2.84v-2.57h-16.4l-.12 2.36c-.13 2.52.17 3 1.82 3 .5 0 .9.26.9.56 0 .64-7.88 8.05-8.56 8.05-.31 0-.5-4.26-.64-14.4l-.2-14.4-6.42-.11a43.42 43.42 0 0 0-6.7.17m-85.08 3.15c-13.75 3.93-14.34 18.08-1 23.61a47.27 47.27 0 0 0 5.4 2c6.81 2.13 9.29 4.2 9.29 7.73 0 5.76-8.8 8-15.88 4.09-1.22-.67-1.21-.64-2.15-5.6l-.27-1.4-3.3-.12-3.29-.09v10.66l3 1.51c6.7 3.36 17.62 4.12 23 1.61 12.14-5.66 9.63-19.27-4.41-23.94-10.34-3.44-12.21-4.73-12.18-8.4 0-4.57 7.58-7.5 12.91-5l1.67.79c.68.33 1.92 3.67 2 5.32v1.1h6.4v-4.73c0-5.48-.07-5.6-4.17-7.45-4.34-2-13-2.8-17-1.64m30.31 7.31-.13 3.86-2.2.2-2.2.2v5.6l1.93.22c2.66.31 2.65.28 2.69 10.38 0 12.45.24 13.34 3.66 16.11 1.61 1.3 6 1.86 9.52 1.2 2-.37 2.31-1 1.81-4.39l-.26-1.72h-1.76c-4.31 0-4.59-.74-4.59-12 0-9.9-.24-9.27 3.6-9.56l2.6-.2.12-2.44c.14-3-.14-3.28-3.53-3.44l-2.59-.16-.2-3.8-.2-3.8-4.08-.11-4.09-.12-.12 3.92m107.69 4a10 10 0 0 0-3.06.7 8.17 8.17 0 0 1-1.5.58c-.88 0-5.06 4.27-5.89 6-4.59 9.62-1.61 21.33 6.44 25.29 2.86 1.41 11.79 1.92 14.06.8.52-.25 1.67-.79 2.55-1.18 2.7-1.2 2.81-1.45 1.79-4-1.08-2.67-1.27-2.8-2.84-2-5.75 2.87-11.54 1.91-14-2.31-1-1.69-1.54-4-1.09-4.34.06-.05 4.51-.18 9.9-.3l9.8-.22.12-3c.45-11.2-5.26-16.84-16.32-16.12m-81.05.88c-1.12.39-2.7 1-3.51 1.41l-1.47.69.12 4.05.11 4h6l.24-1.77c.36-2.69 1.75-3.63 5.45-3.63 4.4 0 6.71 2.88 5.83 7.28l-.22 1.12h-4.05c-10.76 0-15.88 4.3-14.92 12.47.86 7.24 14.36 10.58 18 4.46.57-1 1.84-.63 2.11.56.13.6.31 1.32.39 1.6s1.59.5 5.57.5h5.41v-4.73l-1.7-.57-1.7-.57-.2-10c-.24-12.17-.91-14-5.8-16.34l-2.29-1.09c-1.33-.65-11.12-.31-13.36.46m86.21 5.58c2.19 1.12 3.82 5 2.85 6.8-.6 1.13-11.06 1.28-11.49.16-1.52-3.95 4.71-9 8.64-7m-73.41 13.67c.75 3.73.13 5.45-2.46 6.86-4.85 2.64-10.07-.12-8.29-4.38 1.07-2.55 3.34-3.65 7.65-3.7h2.85l.25 1.25" + style={{ + fill: '#f6f6f6', + fillRule: 'evenodd', + }} + /> + <path + d="M0 200v200h400V0H0v200m183.1-9.9c.06 7.86.25 14.3.43 14.3a6.68 6.68 0 0 0 1.68-1.41 44.89 44.89 0 0 1 3.48-3.1c2.31-1.84 2.66-2.69 1.11-2.69-1 0-1 0-1-3.21v-3.2l9.1.1 9.1.11v5.87l-2.35.81a13.54 13.54 0 0 0-3.38 1.67c-1.42 1.18-4.4 3.84-5.69 5.08l-1.07 1 1.15 1.56c.64.87 1.39 1.85 1.67 2.18s1.31 1.69 2.29 3 2.18 2.84 2.68 3.39a3.54 3.54 0 0 1 .9 1.44c0 .51 2.53 1.68 4 1.84 1 .11 1 .25 1.12 3.22l.11 3.1H190.8v-2.78c0-2.29.14-2.87.77-3.31.86-.6.41-1.5-2.74-5.47-1.32-1.67-1.32-1.67-2.38-1-3.92 2.56-4.4 5.35-1 6.05l1.55.35.12 3.09.11 3.09-9.11-.1L169 225l-.12-2.54c-.14-2.91.28-3.66 2-3.66 2.76 0 2.68.57 2.68-18.28 0-15.79 0-17-.7-17.17-.39-.12-1.06-.38-1.5-.57a8 8 0 0 0-1.6-.46c-.7-.11-.81-.48-.92-2.88-.18-4.14-.7-3.87 7.21-3.75l6.91.11.1 14.3M59.9 176.38c.39.22 1.78.86 3.1 1.44s3.35 1.49 4.51 2a11.66 11.66 0 0 0 2.71 1c1.32 0 1 1.44-1.68 6.7-3.66 7.35-3.36 7.13-7.54 5.3l-3-1.32c-5.18-2.28-7.85-2-9.18 1-.54 1.21-.07 4.57.79 5.7 1.27 1.66 2 9.45 1.14 11.68-1.76 4.44-2.81 5.63-6.37 7.25a27.77 27.77 0 0 1-7.13 2.07c-.62 0-1.14.64-2.06 2.5-1.9 3.84-1.93 3.85-4.88 2.52l-8-3.57a8 8 0 0 0-1.9-.65c-1 0 3.76-11.16 5.53-12.93.37-.37 2.08.2 7 2.33.66.28 1.65.69 2.2.9s1.47.62 2 .89c1.3.61 3.33 0 5.25-1.62s2-4.89.49-9.37c-3-9 .32-17.21 7.1-17.37 3.23-.08 4.48-.82 5.69-3.39 1.73-3.64 2.3-4.06 4.12-3.06M297.9 197c.12 22.42.05 21.84 2.33 21.84 1.74 0 2.25.94 2.09 3.91l-.12 2.25-9 .11c-10 .12-9.61.21-9.62-2.58s.52-3.73 2.37-3.73c2.15 0 2.06.92 1.93-18.8l-.11-17-1.88-.46c-2.65-.66-2.72-.74-2.72-3.13 0-3.64-.46-3.44 7.55-3.32l7.05.11.1 20.76M98 179.27c2.82.85 6.52 2.62 7.64 3.66.71.66.77 1.19.67 6l-.11 5.27-3.69.12-3.68.11-.59-2.11a29.23 29.23 0 0 1-.72-3.11c-.24-2-9.09-4.08-10.12-2.41a1.28 1.28 0 0 1-1 .4 4.1 4.1 0 0 0-2 1.41c-2.4 2.74-1.4 5.53 2.64 7.4 1.06.49 2.23 1 2.61 1.24a3.9 3.9 0 0 0 1.43.35 2.93 2.93 0 0 1 1.5.59 3.46 3.46 0 0 0 1.69.6 4.26 4.26 0 0 1 1.59.36l3.1 1.5c10.84 5.21 11.64 18.55 1.4 23.4a24.8 24.8 0 0 1-15.73 1.75 19.15 19.15 0 0 0-3.1-.6 3.91 3.91 0 0 1-1.47-.35L76.2 223l-3.2-1.6V209l3.3-.12 3.29-.11.8 1.31a6.41 6.41 0 0 1 .81 3.29c0 2.08.78 3.43 2 3.43a1.89 1.89 0 0 1 1.15.49c.85.86 9.63.67 11-.23a5.88 5.88 0 0 0 2.31-3.93c0-1.36-2.44-4.73-3.43-4.73a1.61 1.61 0 0 1-1-.6 2.06 2.06 0 0 0-1.42-.6 1.58 1.58 0 0 1-1.18-.4 1.91 1.91 0 0 0-1.35-.4 3.54 3.54 0 0 1-1.87-.59 3.19 3.19 0 0 0-1.49-.6 3.77 3.77 0 0 1-1.39-.36l-3.1-1.5c-6.4-3.08-9.81-8.85-8.21-13.92.21-.68.48-1.71.6-2.3.48-2.44 4.74-6 9.61-8 1.9-.78 11.88-.7 14.6.12m171.24.34a19.51 19.51 0 0 1 7.34 3.27 2.94 2.94 0 0 0 1.06.72c.5 0 3.56 5.14 3.56 6a13.91 13.91 0 0 0 .47 2.58c.71 2.65-.43 7.47-2.43 10.24-3.32 4.61-6.76 6-15.64 6.36l-6.2.24-.11 4.57c-.13 5.05-.06 5.21 2.33 5.22 2 0 2.38.58 2.38 3.69v2.72H242.8v-3c0-3.22.16-3.44 2.49-3.44s2.28 0 2.3-16.89v-15.5l-1.1-.24c-3.77-.83-3.7-.76-3.7-3.73a9.76 9.76 0 0 1 .27-3c.43-.43 23.81-.31 26.17.14M123.77 183c.48.48.63 1.48.63 4.18v3.55l2.9.12 2.9.12.12 3.14c.15 4-.08 4.26-3.38 4.26h-2.55l.1 9.5.11 9.5 2.39.12c3.33.17 3.55.51 3.92 6 .25 3.73-10.27 3.72-14.09 0-2.62-2.56-3-4.29-3.14-15.42l-.14-9.7H112c-2.39 0-2.85-.74-2.72-4.35l.12-3.05 2.1-.12 2.1-.12V187c0-3.16.11-3.82.7-4.17 1.2-.7 8.73-.54 9.47.2m133.63 4.23a65 65 0 0 0-.11 7.15l.11 6.59h4.6c5.3 0 6.22-.21 7.62-1.46a7.47 7.47 0 0 0-.83-11.52c-1.55-1.1-11-1.71-11.39-.73M154 190.6a13.82 13.82 0 0 1 8.76 6.57c.43.78.63 3.48.81 11.23l.23 10.2 1.6.2 1.6.2.12 3.1.11 3.1H161c-5.82 0-6.22 0-6.22-.75 0-1.62-1.07-1.82-2.68-.49a5.61 5.61 0 0 1-2.1 1.24 1.7 1.7 0 0 0-1.05.42c-2.48 2.48-14.14-1.7-14.14-5.07a5.34 5.34 0 0 0-.6-1.6c-1.74-3.35 1.6-12.13 4.62-12.15a1.79 1.79 0 0 0 1.09-.47c1-1 3.66-1.53 8.59-1.79l5.1-.26v-1.48a5.41 5.41 0 0 0-7.88-4.94 3.73 3.73 0 0 0-1.52 1.1c-.07.24-.37 1.3-.66 2.35l-.52 1.92-3.61-.12-3.62-.11-.11-4.3c-.14-5 0-5.46 2.58-6.44l2.73-1c2.61-1.06 8.93-1.35 13-.61m74.69-.14c4.79 1.15 9.71 5.55 9.71 8.68a1.39 1.39 0 0 0 .4 1.06c.24.15.4 2.47.4 5.62v5.38h-19.69l.28 1.1c1.34 5.39 7.62 7.41 13.61 4.38 2.28-1.16 2.36-1.12 4 1.84s1.07 4.41-2.19 5.71l-1.4.58a12 12 0 0 1-1.8.53c-.55.11-1.81.41-2.8.67-2.11.54-7.44.31-8.1-.35a1.91 1.91 0 0 0-1.17-.46c-2.1 0-7.41-4.33-8.71-7.1-.28-.61-.71-1.46-.94-1.9-1.59-3-1.61-13.15 0-15.69a3.83 3.83 0 0 0 .57-1.4c.05-1.15 3.12-4.93 5.18-6.38a16 16 0 0 1 12.69-2.27m133.91 0a23.9 23.9 0 0 1 7.91 2.06l1.51.74-.11 4.68-.11 4.69h-3.16c-3.38 0-3.56-.1-4-2.33-.92-4.33-9.41-4.13-9.41.22 0 1.3 1.5 2.55 3.75 3.13.91.23 2.1.57 2.65.75s1.81.57 2.8.86c6.22 1.79 8.81 4.75 8.79 10.06 0 8.05-8.89 12.72-19.48 10.27-8-1.85-8.1-1.94-8.1-7.16 0-5.7-.06-5.6 3.31-5.6s4.28.6 4.28 2.68c0 2.49 1.36 3.32 5.4 3.32 3.11 0 3.53-.09 4.42-1 2.21-2.22.39-4.61-4.42-5.78l-3-.76c-11.17-2.89-13.58-13.95-4.21-19.32a9.07 9.07 0 0 1 3.41-1.18 7.4 7.4 0 0 0 2.2-.38c1.1-.47 1.92-.47 5.6 0M317.2 202.6c.25 14.78.47 15.4 5.31 15.4a5.26 5.26 0 0 0 3.83-1.2l1.66-1.2v-8.55c0-9.31.05-9.11-2.4-9.74l-1.4-.36V191l6.37-.11c4.15-.07 6.52 0 6.8.32s.5 5.08.63 13.9l.2 13.48 1.54.25c2 .32 2.29.84 2.16 3.76l-.1 2.4H329l-.12-1.5c-.15-1.71-.44-1.85-1.52-.66-4.34 4.79-14.26 3.89-17.51-1.59-1.83-3.1-2-3.89-2.14-13.72l-.18-9.46-1.07-.4c-.58-.22-1.38-.5-1.76-.62-.72-.23-1.08-5.34-.42-6a39.61 39.61 0 0 1 6.5-.17l6.22.12.2 11.6m-94.7-4c-.71.36-1.3.89-1.3 1.18a.74.74 0 0 1-.35.65 3.6 3.6 0 0 0-.64 1.83l-.28 1.7h9.67v-1.7c0-3.47-3.72-5.39-7.1-3.66m-77 13.66c-3.24 3.52-.91 6.9 3.95 5.73 3.12-.75 4.12-1.86 4.12-4.58v-2.21h-3.53c-3.33 0-3.58.06-4.54 1.1" + style={{ + fill: '#040404', + fillRule: 'evenodd', + }} + /> + <path + d="M284.24 177a11 11 0 0 0-.24 2.58v1.94l1.9.28c3.11.44 2.9-.91 2.9 19v17.95l-1.1.23c-3.1.68-3.3.89-3.3 3.43v2.35h17.24l-.12-2.5-.12-2.5-1.6-.4a10.75 10.75 0 0 1-2.1-.72c-.39-.26-.5-4.83-.5-20.67 0-11.19-.11-20.63-.24-21-.21-.54-1.15-.63-6.36-.63s-6.15.09-6.36.63m-226.51.27a20.1 20.1 0 0 0-1.15 2.1c-1.69 3.48-2 3.8-4.22 3.8-8.11 0-12.26 7.89-8.73 16.6 2.67 6.56 1.24 10.44-4.54 12.35-.61.2-5.13-1.33-7.39-2.5a23.91 23.91 0 0 0-4.7-1.74c-.65-.09-1.25.78-3.28 4.76-2.7 5.27-3.3 6.93-2.52 6.93.47 0 1.3.34 5.6 2.3 6.55 3 5.94 3.13 8.6-2 .8-1.55 1-1.65 4.9-2.13a5.45 5.45 0 0 0 2.32-.62 1.12 1.12 0 0 1 .87-.36 4.92 4.92 0 0 0 1.95-1 6 6 0 0 1 1.64-1c.74 0 2.51-3.44 3.09-6 .74-3.25.1-7.46-1.8-11.81-2.17-5 2.4-9.57 7.23-7.24a16.19 16.19 0 0 0 2.46 1 49.17 49.17 0 0 1 5.68 2.46c.94.5.69.86 3.68-5.06 3.44-6.79 3.58-6.23-2.12-8.62l-1.8-.76-2.78-1.2c-2.19-1-2.5-1-3-.32m31.37 1.78a5.44 5.44 0 0 0 1.8 0c.49-.09.09-.17-.9-.17s-1.39.08-.9.17m6.3.52a4.36 4.36 0 0 0 1.4.34c.38 0 .3-.12-.2-.34a4.36 4.36 0 0 0-1.4-.34c-.38 0-.3.12.2.34m147.8 2.78c0 2.32 0 2.42 1 2.42a7.81 7.81 0 0 1 2.41.58l1.4.74-.11 16.52-.09 16.48-1.2.12c-2.91.28-3.4.76-3.4 3.34v2.36l9.1-.11 9.1-.11.12-2c.14-2.41-.38-3-2.85-3.34L257 219v-10.8l6.6-.25a40.66 40.66 0 0 0 8-.8c4-1.55 7.8-4.91 8.28-7.26a2.59 2.59 0 0 1 .57-1.32 14.34 14.34 0 0 0 .35-4.21c0-6.61-2.22-10.2-8-12.93l-2.61-1.23-13.5-.12-13.49-.08v2.42m-125.89.72a25.38 25.38 0 0 0 3.6 0c.93-.08.07-.15-1.91-.15s-2.74.07-1.69.16m-2.91 4c-.11 2.25-.06 3.94.12 3.77a15.13 15.13 0 0 0 .19-4.09l-.11-3.83-.2 4.08m-8.72 1.8-.08 4.69-3.3.13-3.3.14 3.31.08c4 .1 3.76.4 3.59-5.35l-.13-4.37-.09 4.68m-16.37-3a7 7 0 0 0 2 0c.49-.09 0-.17-1.11-.17s-1.5.08-.89.18m177.75.61q5.34 1.42 5.34 7.57c0 5.83-3.29 8.09-11.4 7.85l-4-.12-.11-7.39c-.06-4.06 0-7.62.09-7.9.26-.68 7.53-.69 10.08 0M73.69 191.6c0 .77.09 1.08.19.7a3.3 3.3 0 0 0 0-1.4c-.1-.39-.19-.07-.19.7m8.8 0c0 .77.08 1.08.19.7a3.3 3.3 0 0 0 0-1.4c-.11-.39-.19-.07-.19.7m64-.5a37.9 37.9 0 0 0 4.4 0c1.15-.08.11-.14-2.31-.14s-3.36.07-2.09.15m75.59 0a3.3 3.3 0 0 0 1.4 0c.38-.1.07-.19-.7-.19s-1.09.09-.7.19m3.61 0a7 7 0 0 0 2 0c.49-.09 0-.17-1.11-.17s-1.5.08-.89.18M205.8 194c0 1.5.13 2.64.3 2.54a10.9 10.9 0 0 0 0-5.08c-.17-.1-.3 1-.3 2.54m99.09-2.33c-1.24 1.24-.31 5.12 1.22 5.14 2.05 0 2.07.1 2.34 10.28.29 11.41.75 13.38 3.7 15.82 4.14 3.42 12.8 2.64 15.64-1.42.73-1 1.09-.71 1.61 1.44l.4 1.67H341l.12-1.94c.15-2.3-.65-3.46-2.36-3.46s-1.75-.17-2-14.4l-.2-13.4-5.55-.11c-6.26-.13-6.65 0-6.65 2.39 0 2.17.59 3.12 1.93 3.12 2 0 2.07.28 2.07 10.08 0 10.86-.55 11.93-6.09 11.91-5 0-5.27-.84-5.51-15.32l-.2-12.07-5.61-.11c-4-.08-5.75 0-6.1.38m47.27.7c-7.2 3.33-7.7 13.47-.82 16.61a20.52 20.52 0 0 0 5 1.82c4.17 0 8.89 3.29 8.26 5.76-1.06 4.25-11.77 4.12-11.77-.14 0-2.1-1-2.82-3.82-2.82h-2.57v9l1.1.46c3.78 1.55 5.61 1.85 11.34 1.9s6.56-.1 8.76-1.41c.33-.2.86-.48 1.17-.62 2.11-1 4-4.84 4-8.15 0-3.68-4.89-8.78-8.45-8.78a1.22 1.22 0 0 1-.94-.4 1.38 1.38 0 0 0-1-.4c-3.65 0-7.55-2.58-7.55-5 0-4.86 10.12-5 10.48-.1l.12 1.69H371v-8.37l-1.8-.67c-3.73-1.39-4.78-1.56-9.63-1.54-4.59 0-5.16.1-7.41 1.15m-118.56.95a9.18 9.18 0 0 0 1.3 1.3l1.3 1.18-1.2-1.29c-1.09-1.21-1.42-1.48-1.42-1.18m-97.51 2.28c0 .77.09 1.08.19.7a3.3 3.3 0 0 0 0-1.4c-.1-.39-.19-.07-.19.7m-25.72 1.73a3.57 3.57 0 0 0 1.88.19l1.55-.14-1.88-.19c-1-.11-1.74 0-1.55.14m16.43-.13-2.6.26 2.48.07a5.63 5.63 0 0 0 2.72-.33c.14-.22.19-.37.12-.33s-1.29.19-2.72.33m97.7-.12a3.3 3.3 0 0 0 1.4 0c.39-.1.07-.19-.7-.19s-1.08.09-.7.19M114.57 207c0 4.84.06 6.76.13 4.28s.07-6.45 0-8.8-.13-.32-.13 4.52m81.43-4.12c-1.21 1.13-1.66 1.67-1 1.22 1.14-.78 3.75-3.32 3.38-3.28a25.22 25.22 0 0 0-2.38 2.06m-33.12 7.2a67.92 67.92 0 0 0 .22 8.86c.16.1.25-2.45.2-5.68-.19-10.82-.34-11.92-.42-3.18m-26.22-7.58a14.56 14.56 0 0 0 5.88 0c.1-.16-1.22-.3-2.94-.3s-3 .14-2.94.3M103 204.4a11.21 11.21 0 0 0 1.76 1.6 6.87 6.87 0 0 0-1.36-1.6 11.21 11.21 0 0 0-1.76-1.6 6.87 6.87 0 0 0 1.36 1.6m82-.39c-.71.74-1.4 1.12-1.78 1s-.51-.14-.1.27.79.23 2-1c.82-.82 1.39-1.49 1.27-1.49A6.52 6.52 0 0 0 185 204m34 0v1l5.37.11a28.55 28.55 0 0 0 5.68-.2c.17-.17-2.19-.26-5.24-.19-5.52.12-5.55.12-5.72-.8s-.16-.83-.13.08m-70.05 1.1a25.38 25.38 0 0 0 3.6 0c.93-.08.07-.15-1.91-.15s-2.74.07-1.69.16m60.81 3.5c0 1.32.08 1.81.17 1.09a10.69 10.69 0 0 0 0-2.4c-.1-.6-.17 0-.17 1.31M74.4 209.85c2.35.3 5.51.33 5.34.05a10.19 10.19 0 0 0-3.26-.24c-1.7 0-2.63.12-2.08.19m75.11.44a19 19 0 0 0 3.2 0c.82-.08 0-.15-1.71-.15s-2.43.07-1.49.15m79.49.11-9.6.22 9.48.09c6 .06 9.56-.06 9.72-.31s.19-.36.12-.31-4.44.19-9.72.31m-10.11 1.6c0 .77.09 1.09.19.7a3.3 3.3 0 0 0 0-1.4c-.1-.39-.19-.07-.19.7m-64.77 1.6c0 1.21.08 1.7.17 1.1a9 9 0 0 0 0-2.2c-.09-.6-.17-.11-.17 1.1m-19.6 2.2c0 1.32.08 1.81.17 1.09a10.69 10.69 0 0 0 0-2.4c-.09-.6-.17 0-.17 1.31m-8 2.89a9 9 0 0 0 2.2 0c.6-.09.11-.17-1.1-.17s-1.71.08-1.1.17m-37.8.4a12.55 12.55 0 0 0 2.6 0c.71-.09.13-.16-1.3-.16s-2 .07-1.3.16m58.2 0a9 9 0 0 0 2.2 0c.61-.1.11-.17-1.1-.17s-1.71.07-1.1.17m79.81 0a4 4 0 0 0 1.59 0c.39-.09 0-.17-.9-.17s-1.19.09-.69.18m-61.41.58c1 .38 1.1.58 1.18 2.87l.07 2.47.15-2.39c.15-2.49-.38-3.45-1.88-3.38-.34 0-.12.21.48.43m4.73.11a5.52 5.52 0 0 0-.33 2.9l.15 2.33.07-2.48c.06-1.89.25-2.58.78-2.89s.5-.41.25-.42a1.52 1.52 0 0 0-.92.56m66.8-.37a4.51 4.51 0 0 0 .57 1.4c.31.55.57.82.57.6a4.51 4.51 0 0 0-.57-1.4c-.31-.55-.57-.82-.57-.6m-29.1 3c0 1.43.07 2 .16 1.3a12.55 12.55 0 0 0 0-2.6c-.09-.72-.16-.13-.16 1.3m5.47-2.28a10.29 10.29 0 0 0 1.3 1.31l1.3 1.17-1.18-1.3c-1.09-1.21-1.42-1.48-1.42-1.18M116 221.73a10.08 10.08 0 0 0 1.3 1.29l1.3 1.18-1.17-1.3c-1.1-1.21-1.43-1.48-1.43-1.17m37 .26c-.14.24 0 .3.44.14a1.12 1.12 0 0 1 1.27.5c.55.71.58.71.34 0a5.46 5.46 0 0 1-.25-.9c0-.35-1.56-.15-1.8.22m-68.9 3.09a3.3 3.3 0 0 0 1.4 0c.39-.1.07-.19-.7-.19s-1.09.09-.7.19m4 .41a26.77 26.77 0 0 0 3.8 0c1-.08.19-.15-1.9-.15s-2.94.07-1.9.15m35.2 0a9 9 0 0 0 2.2 0c.61-.09.11-.17-1.1-.17s-1.71.08-1.1.17m20.8 0a9 9 0 0 0 2.2 0c.6-.09.11-.17-1.1-.17s-1.71.08-1.1.17m80.19 0a14.54 14.54 0 0 0 2.8 0c.83-.09.25-.16-1.29-.16s-2.22.07-1.51.16" + style={{ + fill: '#53b96a', + fillRule: 'evenodd', + }} + /> + <path + d="M168.8 179c0 3 0 3 1.1 3.25.61.13 1.69.42 2.4.64l1.3.39v17.29c0 18.73.06 18.25-2.23 18.25-1.81 0-2.23.73-2.09 3.66l.12 2.54H187l.12-2.54c.14-2.86-.29-3.68-1.87-3.61-1 0-1 .05.15.3s1.2.31 1.32 2.95l.12 2.7H169.6v-2.32c0-2.52.44-3.28 1.91-3.28 2.63 0 2.49 1 2.48-18.31s.15-18.32-2.6-18.77l-2-.34-.12-2.69-.12-2.69h13.63l.11 14.3c.07 10.25.23 14.3.55 14.3s3.16-2.35 7.66-6.48c1.15-1.06.87-1.72-.73-1.72h-1.17v-5.62l8.5.11 8.5.11.12 2.51c.11 2.29 0 2.54-.8 2.89l-.92.39 1-.09c1-.1 1-.19 1-3.1v-3H189v6l1.35.2 1.35.2-4 3.7a54.52 54.52 0 0 1-4.25 3.7c-.12 0-.28-6.43-.34-14.3l-.1-14.3-7.1-.11-7.1-.11v3m-85.4.47-1.2.58 1.2-.24c.66-.14 2.1-.39 3.2-.57l2-.31h-2a8.78 8.78 0 0 0-3.2.56m10.6-.24a21 21 0 0 1 8.13 2.38c3.75 1.82 3.92 2.15 3.78 7.66l-.11 4.55-3.16.12c-2.25.08-3.28 0-3.54-.4s-.32-.38-.2.08c.21.77 6.08 1.08 6.94.37.66-.55.81-8.47.19-10.09-.76-2-7.39-4.7-12-4.9l-2.6-.1 2.6.33m-14 1.72c-2.78 1.69-6 5-6 6.2 0 .32.47-.12 1-1a13.39 13.39 0 0 1 5.23-4.95 6.27 6.27 0 0 0 1.73-1c0-.36-.58-.15-2 .73m34.3 2.14a16.67 16.67 0 0 0-.27 4v3.71l-2.1.12-2.1.12v6.8l2 .2 2 .2.2 10.07c.11 5.53.3 10.16.42 10.28s0-19.67-.22-20.43c-.09-.26-1-.53-2.08-.6l-1.92-.12-.12-3.08-.11-3.08 2.11-.12 2.12-.12.11-3.87c.1-3.27.23-3.9.8-4.06s.58-.19.06-.22a1.3 1.3 0 0 0-.9.22m8.43 0c.34.16.5 1.31.5 3.71 0 4.26.16 4.45 3.7 4.45h2.73l-.11 3.1-.12 3.1-2.58.12c-3.77.18-3.62-.17-3.6 8.63 0 11.39.32 12.25 4.22 12.25 1.3 0 2.17.19 2.3.5s.21.22.23-.2c0-.59-.34-.7-2.34-.7-3.76 0-3.66.29-3.54-10.54l.11-9.26 2.8-.2 2.8-.2V191l-2.89-.12-2.88-.11-.12-3.88c-.1-3.54-.18-3.89-.91-4-.44 0-.57 0-.3.13M85.46 187a6.13 6.13 0 0 0-2 1.67c-.31.58-.13.52.74-.27a9.11 9.11 0 0 1 4.56-2.09c.72-.12.61-.17-.4-.19a6.81 6.81 0 0 0-2.94.88m6.74-.54c1.1.18 2.54.43 3.2.57 1.19.25 1.19.25-.15-.37a8.38 8.38 0 0 0-3.2-.56l-1.85.05 2 .31m-18.81 3.74a12.43 12.43 0 0 0 1.47 7.59c.92 1.37 4 4.32 4.25 4.08A7.71 7.71 0 0 0 77.4 200c-2.83-2.6-3.53-4.27-3.53-8.45s-.11-4.34-.48-1.34m24.66 0a3.89 3.89 0 0 0 .35 1.8c.49.76.49-.47 0-2-.31-1-.32-1-.35.2m-15.18 1.22q-.12 3.24 3.53 5c.95.45 1 .44.2-.14-3.26-2.48-3.35-2.62-3.51-4.75L83 189.4l-.08 2m62.93-1c-.52.22.54.34 3 .33s3.41-.12 2.6-.33a12.5 12.5 0 0 0-5.6 0m75.8.16a13.83 13.83 0 0 0-9.5 6.58c-1.17 2-2.29 4.8-2 5.09.09.1.41-.59.71-1.53 1.94-6.06 6.55-9.45 13.39-9.82 2.62-.14 3.65-.32 3-.51a11.51 11.51 0 0 0-5.6.19m-80.4.74-3 1.34c-1.9.84-2.56 1.54-2.54 2.69 0 .26.23 0 .51-.53.56-1.12 2.22-2 5.48-3l2.6-.79c.22-.07-.14-.13-.8-.13a6.34 6.34 0 0 0-2.2.44m12.6-.12c5 1.24 8.22 4.5 9 9l.28 1.6.05-1.52c.13-3.92-5.51-9.58-9.41-9.44h-1.14l1.2.3m75.67 0c6.13 1.46 9.56 7.33 9.26 15.82l-.13 3.6-9.8.21-9.88.23h20l.12-4.4c.23-8.37-4.49-16-9.81-15.76-1.1 0-1.09 0 .16.34M88.55 197.4a5.24 5.24 0 0 0 1.6.59c.65 0-1.43-1.16-2.15-1.17-.33 0-.08.25.55.58m47.61 2.4v3.2h3.4c3.33 0 3.4 0 3.5-1l.09-1-.39.92c-.36.85-.59.91-3.29.8l-2.91-.12-.24-3-.23-3v3.2m12-2.64c3.94.47 5 1.5 5.41 5.44l.27 2.4.06-2.34c.1-3.81-2-5.92-5.82-5.79l-1.92.06 2 .23m-4.13.86a2.65 2.65 0 0 0-.83 1.9c0 1.05 0 1 .41-.21a3.4 3.4 0 0 1 1.46-1.89c.65-.35.84-.59.46-.6a2.87 2.87 0 0 0-1.5.8m58.24-.02c-2.1 1.32-8 6.94-8 7.61a27.85 27.85 0 0 0 3.2 4.8c5.08 6.65 6.34 8.16 7 8.36.33.11.15-.24-.4-.76s-2.35-2.76-4-5-3.1-4.09-3.22-4.2-.74-.89-1.38-1.74l-1.15-1.54 2.57-2.47c2.88-2.77 5.28-4.66 6.78-5.34.55-.25.73-.47.4-.48a4.44 4.44 0 0 0-1.8.72m20.4 0a7 7 0 0 0-3.4 5.93v.95l5.5-.11 5.5-.11.07-1.4c.07-1.36.06-1.37-.2-.2l-.28 1.2-5.14.11-5.14.12.28-1.5c.66-3.53 2.35-5.1 5.45-5.05 1.07 0 1.85-.13 1.73-.33-.38-.62-3.13-.37-4.37.39m-131.2.46c1.1.47 1.49.47 1.2 0a1.42 1.42 0 0 0-1.12-.37c-.85 0-.85 0-.08.37m137.09.7a5.56 5.56 0 0 1 1.22 1.9c.19.76.2.76.24-.05s-1.35-2.95-2-2.95c-.25 0 0 .49.59 1.1m-134.14.32a7.22 7.22 0 0 0 1.8.57c.79 0-1.51-1.16-2.35-1.16-.55 0-.38.18.55.59m3.85 1.48c.88.5 2.12 1.17 2.75 1.5a16.43 16.43 0 0 1 3.15 2.8c1.1 1.21 1.8 1.83 1.57 1.38-1.06-2.08-7-6.59-8.59-6.57-.26 0 .24.4 1.12.89m-18.6 1.25c0 .33 5.09 2.58 7.73 3.43s2 .2-.84-.76c-1.48-.5-3.58-1.35-4.67-1.87-2-1-2.22-1-2.22-.8m129.79 4.09a27.93 27.93 0 0 0 .06 5.5c.29 1.7.36 1.09.39-3.44 0-6.38-.06-6.82-.45-2.06m-66.59-1.06c-.33.18-1.56.75-2.75 1.26-2.24 1-5.28 3.95-5.22 5.12 0 .54.07.54.25 0 1-2.92 6.16-5.92 11.12-6.53.67-.08.31-.17-.8-.19a7.66 7.66 0 0 0-2.6.3m-52.3 1.44a4.9 4.9 0 0 0 1.8.58c.36 0 0-.27-.7-.6-1.76-.76-2.58-.75-1.1 0m2.65.75a5.87 5.87 0 0 0 1.4 1c6.31 3.72 3.08 9.87-5.16 9.81-2.77 0-3.67.09-3 .34 6.87 2.59 14.39-3.68 10.32-8.62-1.05-1.27-3.56-3.06-3.56-2.53m12.82.73a1.15 1.15 0 0 0 .38.92 3.43 3.43 0 0 1 .47 1.92l.06 1.68.16-1.48a5 5 0 0 0-.45-2.6c-.41-.75-.61-.9-.62-.44m-32.94 1.37a33.29 33.29 0 0 0-.28 5.76c0 6.27-.18 5.92 4 7.88 3.88 1.82 4.73 2.11 5.92 2.07s.71-.2-2.65-1a29.4 29.4 0 0 1-3.8-1.72l-2.87-1.48V209.8h6.4l.6 3c.76 3.8 1.95 5.21 4.36 5.2.35 0-.28-.44-1.41-1-2-.95-2-1-2.22-3.1-.34-4-.83-4.52-4.55-4.65a12.39 12.39 0 0 0-3.5.16m73.58 1.36c-3.47 1-4.58 6.19-1.63 7.53 1.52.69 2.14.52.77-.21-1.57-.85-1.79-1.22-1.79-3.15 0-2.78 1.44-3.77 5.94-4.06l3.65-.24v2.59c0 3-.74 4-3.44 4.82-1.07.31-1.33.5-.82.62a5.48 5.48 0 0 0 2.59-1l1.87-1.15.12-3.09.11-3.08h-3.11a22.15 22.15 0 0 0-4.26.38m38.22 2.31c-1.87 1.74-2.07 2.08-2 3.5l.07 1.58.16-1.4c.17-1.42 1.38-3 3.4-4.46l1.09-.78 2.56 3.5 2.57 3.5-1 .76c-.87.69-1 1-.86 3.22L191 225h17.2v-4.6l-.16 2.2-.15 2.2H191.2v-2.44c0-2.07.12-2.46.77-2.64 1.44-.37 1.19-1.25-1.43-4.89-3-4.23-3-4.2-5.66-1.71m34.35-.6c-.07 1.73 2.83 5.49 4.23 5.48.19 0-.17-.34-.78-.74a7.1 7.1 0 0 1-2.87-4c-.3-.82-.57-1.18-.58-.78m-85 .15c-.46.46-.28 5.06.25 6.6.88 2.52 4.5 6 6.24 5.91.35 0-.11-.36-1-.79a9.57 9.57 0 0 1-5.22-9.8c.23-2.24.21-2.38-.25-1.92M106.74 215c-.14.88-.37 2.05-.52 2.6-.25 1-.23.94.38-.12a5 5 0 0 0 .52-2.6l-.12-1.48-.25 1.6m103.25-.32a1.42 1.42 0 0 0 .37 1.12c.47.29.47-.1 0-1.2-.33-.77-.35-.77-.37.08m23.37 2.24c-1.2.62-2.6.87-5.6 1-2.54.11-3.71.3-3.2.5 2.19.86 7.46-.07 10.69-1.9.76-.43 2.78 4.73 2.13 5.45a16.92 16.92 0 0 1-6 3c-.5.11-.35.18.4.19 1.33 0 5.74-2.17 6.32-3.14.43-.71-2-6-2.68-5.94a10.91 10.91 0 0 0-2 .88m-22.6-.13c0 .93 4.48 6.41 5.25 6.41a9.45 9.45 0 0 0-1.89-2.22 10.86 10.86 0 0 1-2.17-2.7c0-.69-1.19-2.17-1.19-1.49m-47.37 2.07a1.43 1.43 0 0 0 1 .34c1.85 0 2.35.69 2.35 3.21v2.39H155.3l-.24-1.21c-.35-1.74-1.23-1.87-2.86-.44a11.59 11.59 0 0 1-8.37 2.3c-1.64-.16-2.23-.12-1.83.15 2.16 1.4 8.44-.09 11-2.61.79-.8 1.72-.25 1.92 1.14.12.85.26.87 6.12.87h6l.1-2.4a8 8 0 0 0-.25-2.95c-.38-.58-3.77-1.37-3.42-.79M104 220.72c-3.43 3.58-9 5.2-16.32 4.78-2.92-.17-3.54-.13-2.5.17 1.4.4 8.64.23 11.22-.27a16.59 16.59 0 0 0 8.78-5.36c1.18-1.67.95-1.53-1.18.68m102-1.64a4.27 4.27 0 0 1 1.57.66c.44.36.52.33.36-.14s-.74-.62-1.56-.66-1.07 0-.37.14m-91.15.6c-.13 1.71 3.35 5.53 5 5.51.47 0 .26-.24-.59-.66-1.55-.76-4-3.45-4.2-4.68-.12-.57-.19-.63-.23-.17m15.33 2.25c.14 2.28.07 2.56-.72 2.87l-.86.35h.91c1.23 0 1.42-.66.93-3.43l-.42-2.32.15 2.53m86.75 1.54c.26.42 2.47 1.49 4.3 2.07s7.94.6 8.77.06c.4-.26-.39-.31-2.4-.15a18.38 18.38 0 0 1-9.34-1.53c-.85-.43-1.45-.63-1.33-.45m-95.59 2.22c.61.39 5.93.38 6.54 0 .27-.17-1.2-.3-3.26-.29s-3.54.14-3.28.3" + style={{ + fill: '#5f5f5f', + fillRule: 'evenodd', + }} + /> + <path + d="M171.4 175.87c5.86.29 11.71.31 11.54 0a43.54 43.54 0 0 0-7.06-.22c-3.79 0-5.8.13-4.48.19m-114.08.63a29.66 29.66 0 0 0-1.54 2.94c-1.21 2.57-2.46 3.31-5.69 3.39-6.78.2-10.09 8.41-7.09 17.4 1.51 4.48 1.34 7.85-.49 9.37s-4 2.23-5.25 1.62c-.58-.27-1.5-.67-2-.89s-1.54-.62-2.2-.9c-4.95-2.13-6.66-2.7-7-2.33-1.86 1.77-6.63 12.93-5.62 12.93a8 8 0 0 1 1.9.65l8 3.57c2.95 1.33 3 1.32 4.88-2.52.92-1.86 1.44-2.5 2.06-2.5a27.77 27.77 0 0 0 7.13-2.07c3.56-1.62 4.61-2.81 6.37-7.25.89-2.23.13-10-1.14-11.68-.86-1.13-1.33-4.49-.79-5.7 1.33-3 4-3.3 9.18-1 7.71 3.39 6.67 3.78 10.54-4 2.64-5.26 3-6.7 1.68-6.7a11.66 11.66 0 0 1-2.71-1c-1.16-.53-3.19-1.44-4.51-2s-2.71-1.22-3.1-1.44a2.12 2.12 0 0 0-2.58.12m226.13.12c-.41 1.06-.28 4.91.17 5.19a13.31 13.31 0 0 0 2.3.73l1.88.46.11 17c.13 19.72.22 18.8-1.93 18.8-1.85 0-2.38.84-2.37 3.73s-.33 2.7 9.62 2.58l9-.11.12-2.29c.16-3-.35-3.91-2.09-3.91-2.28 0-2.21.58-2.33-21.84l-.1-20.76-7.05-.11c-6.17-.09-7.09 0-7.3.53m13.49.4c.13.35.24 9.79.24 21 0 15.84.11 20.41.5 20.67a10.75 10.75 0 0 0 2.1.72l1.6.4.12 2.5.12 2.5H284.4v-2.35c0-2.54.2-2.75 3.3-3.43l1.1-.23v-17.94c0-19.92.21-18.57-2.9-19l-1.9-.28v-1.94c0-3.35-.29-3.21 6.6-3.21 5.21 0 6.15.09 6.36.63m-236.24.59 2.78 1.2 1.8.76c5.7 2.39 5.56 1.83 2.12 8.62-3 5.92-2.74 5.56-3.68 5.06a49.17 49.17 0 0 0-5.68-2.46 16.19 16.19 0 0 1-2.46-1c-4.83-2.33-9.4 2.25-7.23 7.24 1.9 4.35 2.54 8.56 1.8 11.81-.58 2.55-2.35 6-3.09 6a6 6 0 0 0-1.64 1 4.92 4.92 0 0 1-1.95 1 1.12 1.12 0 0 0-.87.36 5.45 5.45 0 0 1-2.32.62c-3.88.48-4.1.58-4.9 2.13-2.66 5.12-2 5-8.6 2-4.3-2-5.13-2.3-5.6-2.3-.78 0-.18-1.66 2.52-6.93 2-4 2.63-4.85 3.28-4.76a23.91 23.91 0 0 1 4.7 1.74c2.26 1.17 6.78 2.7 7.39 2.5 5.78-1.91 7.21-5.79 4.54-12.35-3.53-8.71.62-16.6 8.73-16.6 2.17 0 2.53-.32 4.22-3.8 1.46-3 1.41-3 4.14-1.78m28.38 1.07a12.55 12.55 0 0 0 2.6 0c.72-.09.13-.16-1.3-.16s-2 .07-1.3.16m154 .78a9.76 9.76 0 0 0-.27 3c0 3-.07 2.9 3.7 3.73l1.1.24v15.5c0 16.93 0 16.89-2.3 16.89s-2.49.22-2.49 3.44v3H262v-2.72c0-3.11-.36-3.68-2.38-3.69-2.39 0-2.46-.17-2.33-5.22l.11-4.57 6.2-.24c8.88-.36 12.32-1.75 15.64-6.36 2-2.77 3.14-7.59 2.43-10.24a13.91 13.91 0 0 1-.47-2.58c0-.84-3.06-6-3.56-6a2.94 2.94 0 0 1-1.06-.72 19.51 19.51 0 0 0-7.34-3.27c-2.36-.45-25.74-.57-26.17-.14m29.74 2c5.77 2.73 8 6.32 8 12.93a14.34 14.34 0 0 1-.35 4.21 2.59 2.59 0 0 0-.57 1.32c-.48 2.35-4.3 5.71-8.28 7.26a40.66 40.66 0 0 1-8 .8l-6.6.25V219l1.67.22c2.47.33 3 .93 2.85 3.34l-.12 2-9.1.11-9.1.11v-2.36c0-2.58.49-3.06 3.4-3.34l1.2-.12.1-16.52L248 186l-1.39-.58a7.81 7.81 0 0 0-2.41-.58c-1 0-1-.1-1-2.42V180l13.5.12 13.5.12 2.61 1.23m-155.89 1.23a40.28 40.28 0 0 0 4.4 0c1.14-.08.1-.14-2.32-.14s-3.36.07-2.08.15m-3.15.63a24 24 0 0 0-.08 4.1l.12 3.57.19-4.1c.2-4.07.16-4.59-.23-3.57m10.37 3.87c0 2.09.07 3 .15 1.9a26.77 26.77 0 0 0 0-3.8c-.08-1-.15-.19-.15 1.9m132.86-.7c-.11.28-.15 3.84-.09 7.9l.11 7.39 4 .12c8.11.24 11.4-2 11.4-7.85s-3-8.07-11.22-8.07c-2.89 0-4.06.14-4.2.51m11.79 1.49a7.47 7.47 0 0 1 .83 11.52c-1.4 1.25-2.32 1.43-7.62 1.46h-4.6l-.11-6.59a65 65 0 0 1 .11-7.15c.38-1 9.84-.37 11.39.73m-184.59.43c-.63.65-1.06 1.26-1 1.33s.68-.46 1.31-1.19c1.41-1.66 1.21-1.75-.34-.14M73 189.83a7.89 7.89 0 0 0 .07 4c.16.26.29-.69.29-2.1 0-2.81 0-2.84-.36-1.93m284 .57a7.4 7.4 0 0 1-2.2.38 9.07 9.07 0 0 0-3.41 1.18c-9.37 5.37-7 16.43 4.21 19.32l3 .76c4.81 1.17 6.63 3.56 4.42 5.78-2.64 2.64-9.82.93-9.82-2.34 0-2.08-1-2.68-4.28-2.68s-3.31-.1-3.31 5.6c0 5.22.11 5.31 8.1 7.16 10.59 2.45 19.45-2.22 19.48-10.27 0-5.31-2.57-8.27-8.79-10.06-1-.29-2.25-.67-2.8-.86s-1.74-.52-2.65-.75c-2.25-.58-3.75-1.83-3.75-3.13 0-4.35 8.49-4.55 9.41-.22.47 2.23.65 2.33 4 2.33h3.16l.11-4.69.11-4.68-1.51-.74c-3.59-1.77-11.44-3-13.51-2.09m-247.63.94a15.33 15.33 0 0 0-.08 3.3l.12 2.76.2-3.3c.19-3.3.15-3.78-.24-2.76m20.43 3v3.46l-2.8.2-2.8.2v18.4l.12-9.1.12-9.1h2.51a6.41 6.41 0 0 0 3-.48c.53-.53.68-6.55.18-6.86-.17-.1-.3 1.37-.3 3.28m76.8-.34c0 1.72.14 3 .3 2.94a14.56 14.56 0 0 0 0-5.88c-.16-.1-.3 1.22-.3 2.94m97.68-2.94c-.66.65-.3 5.76.42 6 .38.12 1.18.4 1.76.62l1.07.4.18 9.46c.18 9.83.31 10.62 2.14 13.72 3.25 5.48 13.17 6.38 17.51 1.59 1.08-1.19 1.37-1.05 1.52.66L329 225h12.8l.1-2.4c.13-2.92-.17-3.44-2.16-3.76l-1.54-.25-.2-13.48c-.13-8.82-.35-13.62-.63-13.9s-2.65-.39-6.8-.32l-6.37.11v6l1.4.36c2.45.63 2.4.43 2.4 9.74v8.55l-1.66 1.2a5.26 5.26 0 0 1-3.83 1.2c-4.84 0-5.06-.62-5.31-15.4L317 191l-6.22-.11a39.61 39.61 0 0 0-6.5.17m12.52 12.41c.24 14.48.54 15.31 5.51 15.32 5.54 0 6.09-1 6.09-11.91 0-9.8-.06-10.08-2.07-10.08-1.34 0-1.93-1-1.93-3.12 0-2.38.39-2.52 6.65-2.39l5.55.11.2 13.4c.21 14.23.24 14.4 2 14.4s2.51 1.16 2.36 3.46l-.16 1.94h-11.2l-.4-1.67c-.52-2.15-.88-2.48-1.61-1.44-2.84 4.06-11.5 4.84-15.64 1.42-2.95-2.44-3.41-4.41-3.7-15.82-.27-10.18-.29-10.27-2.34-10.28-1.11 0-1.71-1.1-1.71-3.1 0-2.44.29-2.54 6.59-2.42l5.61.11.2 12.07m49.2-11.83c.79.25 2.24.75 3.23 1.12l1.8.67v8.37h-5.6l-.12-1.69c-.36-4.86-10.48-4.76-10.48.1 0 2.41 3.9 5 7.55 5a1.38 1.38 0 0 1 1 .4 1.22 1.22 0 0 0 .94.4c3.56 0 8.47 5.1 8.45 8.78 0 3.31-1.91 7.16-4 8.15-.31.14-.84.42-1.17.62-2.2 1.31-3.08 1.45-8.76 1.41s-7.56-.35-11.34-1.9l-1.1-.46v-9h2.6c2.85 0 3.82.72 3.82 2.82 0 4.26 10.71 4.39 11.77.14.63-2.47-4.09-5.76-8.26-5.76a20.52 20.52 0 0 1-5-1.82c-6.88-3.14-6.38-13.28.82-16.61 2.71-1.26 10.74-1.68 13.81-.73m-132 1.29a11.43 11.43 0 0 0 1.3 1.3l1.3 1.17-1.17-1.3c-1.1-1.21-1.43-1.48-1.43-1.17m-98.34 6.15a14 14 0 0 0 .24 3.86c.17.1.25-1 .19-2.48-.19-4.64-.36-5.18-.43-1.38m-23.67-.8 1.59.17.12 9 .12 9V198.2h-3.41l1.6.16m31.66 1.39a7.71 7.71 0 0 1-.28 1.4c-.22.69-.21.69.23 0a2 2 0 0 0 .27-1.4c-.16-.62-.18-.62-.22 0m66.37 1.07a2.59 2.59 0 0 0-.31 1.43c.06.33.32 0 .56-.82.5-1.61.4-1.83-.25-.61m-46.71 8.66a73.2 73.2 0 0 0 .22 9.06c.16.1.25-2.54.2-5.88-.19-11-.34-12.17-.42-3.18m22.05-6.67c-1.14 1.09-1.94 2-1.79 2a12.47 12.47 0 0 0 2.26-2c2.54-2.58 2.22-2.58-.47 0m44.32-.11-.05 1.3h-4.77c-4.21 0-4.79-.08-5-.7s-.2-.48-.23.2c-.05.89 0 .9 5 .9 5.37 0 5.65-.11 5.28-2.07-.14-.71-.19-.62-.24.37m-124.8 2.68a14.7 14.7 0 0 1 1.08 1.62c.25.44.45.58.46.31a4 4 0 0 0-1.09-1.62c-.61-.62-.81-.76-.45-.31m43.66-.68a25.38 25.38 0 0 0 3.6 0c.93-.08.07-.15-1.91-.15s-2.74.07-1.69.16m60.54 1c-.34 1.7-.31 5.64.06 6.21.16.25.29-1.32.29-3.5 0-4.42 0-4.46-.35-2.71m-121.67.12a2.83 2.83 0 0 0 1.6.6c.66 0 .61-.1-.25-.36a12.41 12.41 0 0 1-1.6-.6c-.29-.15-.18 0 .25.36m-14.32 3.27c-.1.16 1.25.24 3 .17 2.21-.09 3.39 0 3.86.4s.62.46.13-.17-6.57-1.07-7-.4m76.43 2a14.54 14.54 0 0 0 2.8 0c.83-.08.25-.16-1.29-.16s-2.22.07-1.51.16m-42.17 1.9c0 1.1.08 1.5.18.9a7 7 0 0 0 0-2c-.09-.5-.17 0-.17 1.1m37.72-.63c-.62.64-1 1.4-.91 1.7s.22.4.25 0a4.15 4.15 0 0 1 1.11-1.7c.59-.61 1-1.12.87-1.12a6.54 6.54 0 0 0-1.32 1.17m-11.31 3.43c0 1.32.08 1.81.17 1.09a10.69 10.69 0 0 0 0-2.4c-.1-.6-.17 0-.17 1.31m-7.41 2.09a10.69 10.69 0 0 0 2.4 0c.6-.1 0-.17-1.31-.17s-1.81.08-1.09.17m-37.41.4a9 9 0 0 0 2.2 0c.61-.09.11-.17-1.1-.17s-1.71.08-1.1.17m58.9.11c-1.11.28-1.12.31-.2.35a3.32 3.32 0 0 0 1.6-.35c.68-.44.33-.44-1.4 0m21.48.88c-.52.52-.68 5.35-.18 5.66.16.1.24-1 .16-2.39a6.17 6.17 0 0 1 .35-3.17c.59-.7.34-.77-.33-.1M214 221.8a8.83 8.83 0 0 0 1.56 1.4c.11 0-.41-.63-1.16-1.4a8.83 8.83 0 0 0-1.56-1.4c-.11 0 .41.63 1.16 1.4m-83.41 1.58c0 1 .15 1.66.31 1.56.39-.24.38-2.2 0-2.82-.16-.26-.29.31-.29 1.26m-42.51 2.91a26.77 26.77 0 0 0 3.8 0c1-.08.19-.15-1.9-.15s-2.94.07-1.9.15m35 0a10.69 10.69 0 0 0 2.4 0c.6-.09 0-.17-1.31-.17s-1.81.08-1.09.17m20.8 0a10.69 10.69 0 0 0 2.4 0c.6-.09 0-.17-1.31-.17s-1.81.08-1.09.17m80.19 0a16.7 16.7 0 0 0 3 0c.83-.08.15-.16-1.5-.16s-2.33.08-1.5.16" + style={{ + fill: '#31683c', + fillRule: 'evenodd', + }} + /> + </svg> +); + +export default Stakeplus; diff --git a/src/config/validators/StakerSpace.tsx b/src/config/validators/StakerSpace.tsx new file mode 100644 index 0000000000..6808a408a3 --- /dev/null +++ b/src/config/validators/StakerSpace.tsx @@ -0,0 +1,15 @@ +const StakerSpace = () => ( + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"> + <rect width={512} height={512} rx={26} fill="#fff" /> + <path + fill="#010101" + d="M226.59 61a134.31 134.31 0 0 0-99.4 75.13c-8.43 17.63-11.62 30.91-12.39 51-.77 18.78.64 31.43 5 45.49 1.92 6.38 2.94 7.92 4.6 7.41 1.15-.39 3.84-1 5.75-1.54 2.69-.64 3.32-1.4 2.81-3.06-1-3.2 3.33-4.6 5.37-1.92 1.41 2 3.83 1.4 27.47-5.75 29.13-8.94 69-22.61 112.81-38.71 35.14-12.91 89.31-33.73 94.16-36.16l3.45-1.79L372 141c-23.78-56.33-85.23-90.06-145.41-80zm14.31 13.57c0 1.92-.89 2.43-4.47 2.43s-4.43-.51-4.43-2.43 1-2.68 3.58-2.93c4.42-.64 5.32-.13 5.32 2.93zm15.33-.76c0 2.68-.63 3.19-3.7 3.19-2.17 0-4.22-.64-4.6-1.28-1.66-2.68.51-5.11 4.47-5.11 3.2 0 3.83.51 3.83 3.2zm13.29-1.15a3.62 3.62 0 0 1 2.05 3.34c0 3.83-6.9 2.68-7.41-1.28-.39-2.96 1.4-3.6 5.36-2.06zm-44.72 2.68c1 2.56-.25 3.83-4.34 4.47-2.43.26-3.32-.25-3.58-2.43s.39-2.93 2.56-3.32a34.2 34.2 0 0 0 3.7-.76c.51 0 1.28.89 1.66 2.04zm61.84 4.09c.38 2.68.13 2.94-2.55 2.3a19 19 0 0 0-3.84-.9c-.64 0-1-1.15-1-2.68-.02-4.09 6.88-2.94 7.39 1.28zm-76.4 2.17c0 2.3-6.52 3.58-7.79 1.53-1.79-2.68-1.41-3.32 2.42-4.6 3.45-1.15 5.37-.12 5.37 3.07zm89.56.51c2.68 1.53 2.94 2.3 1.28 4.86-.77 1.27-1.66 1.4-4.35.12-3.32-1.53-4.34-3.7-2.55-5.36 1.15-1.28 2.68-1.15 5.62.38zm-106.17 7.67c-4 3.06-4.6 3.06-5.62.38-.64-1.4.13-2.94 2-4.47 2.56-1.92 3.2-1.92 4.86-.26s1.57 2.18-1.24 4.35zm120.1 0c2.81 2.17 2.94 2.68 1.27 4.34s-2.3 1.66-4.85-.25c-1.92-1.54-2.68-3.07-2-4.48.98-2.68 1.62-2.68 5.58.39zM182 94.12c1.28 1.53.9 2.3-1.66 4.09-2.81 1.79-3.7 1.79-5.36.38-1.92-1.53-1.79-1.91.76-4 3.45-2.77 4.35-2.77 6.26-.47zm81 8 4.08.89-4.08 1c-3.2.89-4.48 2-5.24 5.24-1.28 4.08-1.41 4-3.45-2.3-.26-1-2.43-2.3-4.73-2.94l-4.22-1 4.22-.89c2.3-.51 4.47-1.66 4.73-2.81 2-6.26 2.17-6.39 3.45-2.17.9 3.37 1.92 4.39 5.24 5.03zm66.05-.77c0 .51-.9 1.54-2.05 2.43-1.66 1.41-2.42 1.28-4.6-1s-2.42-3.07-1.15-4.6c1.54-1.79 2.05-1.79 4.73.26 1.67 1.18 3.08 2.56 3.08 2.96zm-160.6 6.52c-2.43 2.3-3.06 2.43-4.85 1s-1.66-1.91.89-4.73 2.94-2.81 4.73-1 1.67 2.22-.76 4.78zm170.18 6c-1.66 1.79-2.17 1.67-4.73-.76-2.3-2.43-2.42-3.07-1-4.86s1.92-1.66 4.73.9 2.8 2.99 1.01 4.77zM158 119c-1.54 2.82-4.6 3.58-5.88 1.41-.51-.77.13-2.81 1.28-4.73 1.91-2.81 2.42-3.06 4.08-1.27 1.52 1.43 1.65 2.45.52 4.59zm190.23 3.45c1.54 2.82 1.54 3.45 0 4.35-1.28.77-2.68.38-4.09-.9-2.68-2.42-2.93-5-.63-5.87 2.67-1.12 2.92-1.03 4.71 2.45zm-199.43 4.86c1.91 2.3-.26 7.15-3.07 7.15s-3.32-2-1.79-5.62c1.78-3.84 2.8-4.06 4.85-1.5zm207.74 8.43c1.79 3.58 1.79 4-.13 4.73-1.41.51-3.07-.26-4.6-2.17-2.43-2.94-2.43-3.2-.26-4.73 2.93-2.14 2.68-2.27 4.98 2.2zm-215.41 5c1.53 1 1.66 1.79.38 4.48-1.53 3.57-2.42 4-5.11 2.17-1.27-.77-1.4-1.66-.12-4.35 1.65-3.69 2.16-3.95 4.84-2.29zm221.41 13.93c-2.43 1.79-3.58.89-4.73-3.83s3.2-5.75 5.37-1.15c1.27 2.82 1.14 3.59-.65 4.99zm-226 1c.38.39.13 2.3-.51 4.35-1 2.68-1.92 3.45-3.83 2.94-2.69-.77-2.69-1-1.66-5.24.74-2.53 4.19-3.81 5.98-2.02zm-4 17.76c-.64 4.6-1.4 5.75-3.45 5-1.15-.51-1.92-2.3-1.92-4.6 0-3.19.52-3.7 2.94-3.32 2.07.27 2.71 1.16 2.45 2.95zm52.13 1.15a5.72 5.72 0 0 0 4.09 3.58c2.93.51 2.93.51-.39 1.92a8.67 8.67 0 0 0-4.34 4.47l-1 2.94-.51-2.94a7 7 0 0 0-4-4.35l-3.41-1.2 2.93-1.15a6.59 6.59 0 0 0 3.71-3.71c.89-3.36 1.53-3.23 2.94.47zM131 190.07c0 3.58-.51 4.47-2.56 4.47s-2.55-.89-2.55-4.47.51-4.47 2.55-4.47 2.56.89 2.56 4.47zm.51 13.67c1.28 5 .89 6.13-2.43 6.13-2.68 0-3.19-.64-3.19-3.57.03-5.88 4.24-7.8 5.65-2.56zm2.55 15.33c1.28 5.11.9 6.39-2 5.88-4.86-.64-4.34-8.56.38-8.69.5 0 1.14 1.28 1.65 2.81z" + /> + <path + fill="#010101" + d="M450.82 110.35c-5.12.64-12 1.53-15.34 2.17l-6.13 1 10.22.9c11.37 1.15 18.53 2.93 18.53 5 0 5.62-35.65 24.91-77.3 41.78C249.46 214.73 106.75 261 62.16 264.43c-10.09.76-10.22.76-11.75-2.56-1.28-2.81-.89-4.47 2.43-11.62 3.7-7.8 3.7-8.31 1.27-7C45.17 248 22.43 275.16 22.43 281c-.13 11.88 47 4.47 128.4-19.93C285.75 220.61 454.52 150.72 484 123.25c7-6.64 7.92-11.63 2.18-13.16-4.32-1.15-24.12-1.02-35.36.26zM376.71 191c-5 2.18-5.49 2.82-5.49 6.9s-.38 4.48-2.81 4.09-2.81-1-2.56-3.45c.39-2.17 0-2.81-1.27-2.3-70 28.49-135.81 52-216.69 77.43l-6.64 2 7.66 8.56c10.61 11.76 26.32 23.25 41.53 30.54 65.92 31.43 145 4.72 177.59-60.18 9.58-19.16 15.45-43.18 14.43-59.54l-.38-6.38zm-7.41 19c1.66 2.43-.51 7.54-3.19 7.54s-3.07-1.28-1.79-6.13c.89-2.94 3.58-3.84 4.98-1.41zm-4.09 14.69c2.82 1 .77 7.54-2.42 7.54-1.28 0-2.43-.76-2.3-1.66s.25-2.68.38-4.09c.13-2.68 1.13-3.06 4.34-1.79zM327.52 244a10.63 10.63 0 0 0 5.24 1.78c2.69 0 2.3.39-1.79 1.67-4.34 1.27-5.23 2.17-6.51 6.51s-1.66 4.6-1.66 1.53a9.34 9.34 0 0 0-8.69-8.68c-3.32-.13-3.19-.26.89-1.15 5-1.15 7.67-4.22 7.8-8.82.13-2.81.25-2.68 1.4 1.15.64 2.42 2.18 5.23 3.32 6.01zm33.86-4c.39.51-.13 2.55-1.15 4.47-1.53 3.19-2.3 3.57-4.34 2.42s-2.3-1.78-1-4.47c1.25-2.91 4.7-4.18 6.49-2.42zm-6.77 15.33a16.47 16.47 0 0 1-2 3.7c-1.66 2.56-2.43 2.81-4.35 1.66-2.3-1.4-2.3-1.79-.38-5 1.66-2.81 2.55-3.32 4.47-2.3 1.24.69 2.26 1.61 2.26 1.97zM345 267.49c1 1.54-2.94 6.26-5.24 6.26-2.81 0-3.45-2.68-1-5.23s4.99-3.2 6.24-1.03zm-181.39 11.25c-1.66 1.79-2.18 1.66-4.73-.77-2.3-2.43-2.43-3.07-1-4.85s1.91-1.67 4.72.89 2.8 2.99 1.01 4.73zm170.43.51c1.79 2.3-2.68 6.64-5.62 5.62-2.56-1-2.43-4.34.38-5.88 2.94-1.79 3.58-1.66 5.2.26zM175 285.13c1.79 2 1.91 2.55.25 3.83-3.19 2.68-9.2-2.56-6.26-5.37 1.39-1.53 3.69-1.02 6.01 1.54zm145.26 7.92c-2.81 2.3-3.32 2.3-5 .64s-1.66-2.18 1.15-4.48 3.32-2.3 5-.64 1.52 2.18-1.17 4.48zm-106.57 2.55c1.79 0 1.66.26-.51 1.15a4.8 4.8 0 0 0-3.07 3.45c0 1.79-.25 1.66-1.15-.64a4.88 4.88 0 0 0-3.45-2.94c-1.79 0-1.66-.25.64-1.15a4.86 4.86 0 0 0 2.94-3.45c0-1.78.26-1.66 1.15.52a4.78 4.78 0 0 0 3.45 3.06zm-26.83-2c2.81 2 2.68 5.75-.13 5.75-3.06 0-6-3.2-5.11-5.5 1.02-2.72 1.79-2.72 5.24-.29zM308 301.22c-3.07 2.3-6.26 1.92-6.77-.76-.52-2.94 5-5.24 7.41-2.94 1.51 1.53 1.51 2.04-.64 3.7zm-107.58-.51c2.17 1 2.94 2.17 2.56 3.84-.77 2.81-2.81 3.06-6.14.63-1.4-1-2-2.42-1.53-3.83 1-2.43 1.13-2.43 5.09-.64zM295 307.1a12.73 12.73 0 0 1-4.48 1.79c-2.93.77-3.57.51-3.57-1.53 0-3.58 5.23-5.88 7.41-3.2.71 1.15 1.1 2.43.64 2.94zm-78.45-.26a2.7 2.7 0 0 1 .89 3.33c-.63 1.66-1.66 1.91-4.34 1.27-4.34-1-4.34-1.15-3.32-3.7.84-2.17 3.91-2.68 6.72-.9zm63.11 2.82c.64 2.55-.12 3.19-4.85 4-4 .64-4.34-5-.38-5.75a33.39 33.39 0 0 0 3.7-.77c.46-.04 1.1.98 1.48 2.52zM233 312.59c-.25 2.18-1.15 2.56-4.21 2.3-4.73-.63-4.86-.76-4-3.7 1.21-3.71 8.7-2.43 8.21 1.4zm31.3-.63c.77 3.19 0 4-4.47 4s-5.24-4.6-.64-5.5c1.54-.25 3.2-.64 3.58-.76s1.13.98 1.51 2.3zm-15.71 1.27c0 3.32-1.66 4.22-5.5 3.32-4.6-1.15-3.19-5.74 1.79-5.74 2.79 0 3.69.63 3.69 2.42zm-96.48 35.27c-5.24 1.91-9.46 6.26-9.58 9.83 0 3.32 3.19 5.75 10.22 7.41 7.41 1.79 9.07 4 5.75 7.67-2.81 3.06-12.91 3.45-16.74.77-2.17-1.67-2.68-1.54-4.6.12-3.07 2.69-2.94 3.58.89 5.24 4.86 2.17 16.74 1.92 22-.64s9.33-9.58 7.28-12.9c-.76-1.15-4.47-3.2-8.3-4.47-9.46-3.2-10.73-4.47-7.92-7.67 1.91-2 3.45-2.43 10.22-1.79 5.62.39 8.43.13 9.32-1S171 349 168 348c-5.42-1.8-9.63-1.68-15.89.5zm23.12 1.27c-.51 1.54.51 1.92 5 1.92 6.89 0 6.77-.64 2.68 16.48l-3.07 12.9h3.58c4 0 4-.12 7.79-20.82l1.54-8.56H199c4.6 0 6.13-.51 6.13-1.92s-2.43-1.91-14.57-1.91c-11.75 0-14.69.38-15.33 1.91zm34.12 14.7-10.48 16.6h3.58c2.93 0 4.34-1 6.38-4.47l2.56-4.47h19.42l1.92 4.47c1.53 3.71 2.55 4.47 5.49 4.47h3.7l-7.41-16.35c-6.77-14.82-7.79-16.22-11.11-16.61s-4.34.89-14.05 16.36zm16.22-5.63c3.71 8.44 3.45 8.82-4.09 8.69-3.7-.13-6.64-.76-6.39-1.28.64-1.66 7.29-13.28 7.54-13.28s1.37 2.68 2.94 5.87zm21.72 5.63v16.6h3.83c3.58 0 3.84-.25 3.84-4.85 0-3.45.89-5.75 2.93-7.67l2.94-2.81 7.54 7.67c5.88 6 8.43 7.66 11.37 7.66 3.83 0 3.71-.12-5.62-9.58l-9.45-9.58 6.9-7 6.89-7h-3.83c-2.94 0-5.49 1.66-11.37 7.66-4.22 4.22-8 7.67-8.56 7.67s-1-3.45-1-7.67c0-7.28-.13-7.66-3.2-7.66h-3.19zm39.71-1.03c1.28 8.69 2.43 16.1 2.43 16.61s6.65 1 14.82 1c13.42 0 14.69-.25 14.31-2.3-.38-1.78-2.17-2.17-11.24-2.55-11.5-.38-12.78-1-12.78-7 0-3.45 0-3.45 9.07-3.45 7.8 0 8.95-.25 8.31-2a18.88 18.88 0 0 1-.77-2.56c0-.25-4-.51-8.81-.51-9.2 0-10.35-.76-10.35-6.51 0-2.18 1-2.43 9.58-2.43 7.66 0 9.58-.38 9.58-1.92s-2.3-1.91-13.16-1.91h-13.27zm33.75-9.58c.9 3.45 2.94 10.86 4.48 16.61 2.68 10.09 2.93 10.6 6.51 10.6h3.58l-1.79-5.07c-1.92-5.49-1.79-5.75 4.09-6.26 3.32-.25 5.24.77 10.35 5.5 4.47 4.21 7.28 5.87 10 5.87 3.71-.12 3.71-.12-2.68-5.74-6.13-5.37-6.26-5.63-4.09-8.05 2.94-3.2 2.81-8.18-.51-12.27-4.34-5.62-8.94-7.15-20.83-7.15H319zm20.19-.77a11.3 11.3 0 0 1 4.73 4.73c1.27 2.94 1.15 3.71-1.28 5.62-1.79 1.41-5 2.3-8.43 2.3-5.37 0-5.62-.12-6.9-5.24-.77-2.81-1.79-6-2.17-7-.51-1.41.63-1.79 5.11-1.79a26.85 26.85 0 0 1 8.94 1.38zm-204.16 50.09c-9.71 5.49-13.93 17-8.31 22.61 1.28 1.28 5.75 3.45 9.84 4.6 10.35 3.07 12 4.22 11.63 8.69C149 447.51 135 450.83 125 445l-4.72-2.82-3.07 3.45c-3.58 4.22-3.2 5.63 2.43 9 3.32 1.91 6.64 2.55 13.92 2.55 8.18 0 10.48-.51 15.72-3.57 6.77-4 11.11-11 11.11-17.76s-2.94-9.59-12.9-12.65c-8.31-2.56-12.65-5.11-12.65-7.41 0-.64 1.15-2.56 2.56-4.35 3.44-4.34 10.6-5.49 18-2.93 5.37 1.91 5.75 1.91 7.92-.64 3.07-3.71 2.81-4.48-1.79-6.14-6.99-2.51-19-1.73-24.75 1.45zm41.01-.77c-1 3.58-9.71 50.34-9.71 52.38 0 1.28 1.28 1.66 4.85 1.41l4.73-.38 1.28-8.31 1.28-8.3 10.86-.64c18-1.28 25.68-7.67 25.8-21.59 0-12.4-6.77-16.74-26.44-16.74-10.35 0-12.01.25-12.65 2.17zm28.11 8.69c3.06 4.21 2.17 10.6-1.92 14.82-3.45 3.45-4.47 3.7-12.78 3.7-5 0-9.07-.38-9.07-.89 0-1.53 2.68-17.63 3.32-19.42.9-2.81 18.15-1.41 20.45 1.79zm30.02-.38c-2.68 5.87-8.43 18.14-12.78 27.46s-7.79 17.12-7.79 17.51 2.17.76 4.86.76c4.72 0 5-.25 7.79-7l2.94-7.16 14.05.39 14.18.38 2.56 6.64c2.43 6.65 2.55 6.77 7.92 6.77h5.49l-12.9-28.1-12.91-28.11h-4.21c-4.22-.02-4.47.36-9.2 10.46zm12 5.11c1.15 3.06 3.32 8.17 4.6 11.49l2.43 6.14h-20.18l4.6-11.5c2.55-6.26 5.11-11.5 5.49-11.5s1.92 2.43 3.07 5.37zm41.53-14.19c-8.56 3.45-13.29 11.5-13.29 22.87 0 17 12 30.92 28.37 32.84 8.05.89 15.58-1.28 20.06-6l3.7-3.83-3.19-3.32-3.32-3.2-4.86 3.2c-13.67 9.33-31.3-2.17-31.3-20.19 0-6.77.38-7.79 4.34-11.75 3.71-3.71 5.24-4.35 10.61-4.35a26.71 26.71 0 0 1 10.86 2.43c4.6 2.56 4.6 2.56 6.9 0s2.3-2.68-.13-4.6c-6.9-5.25-20.83-7.16-28.75-4.1zm39.61.26c0 2 11.5 53.66 12 54.17.25.26 9.58.26 20.82.13l20.32-.38-1-4.22-1.2-4.09h-31.52l-1.92-7.92c-1.15-4.34-1.79-8.43-1.4-8.94s6-1 12.64-1h12l-1.27-3.83-1.4-3.82h-24.91l-.77-3.71c-.38-2-1.15-5.11-1.66-7l-.79-3.39h13.55c7.41 0 13.54-.25 13.54-.51a17.59 17.59 0 0 0-1.28-3.83l-1.28-3.32h-17.22c-13.42 0-17.25.38-17.25 1.66z" + /> + </svg> +); + +export default StakerSpace; diff --git a/src/config/validators/Staking4All.tsx b/src/config/validators/Staking4All.tsx new file mode 100644 index 0000000000..231781c58d --- /dev/null +++ b/src/config/validators/Staking4All.tsx @@ -0,0 +1,28 @@ +const Staking4All = () => ( + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 400"> + <g fillRule="evenodd"> + <path + d="M6.41 224.1c0 4 2 21.33 2.79 24.3.2.77.58 2.39.85 3.6 9.84 45 43.72 85.79 91 109.52 3.92 2 10.11 4.88 10.38 4.88a21.16 21.16 0 0 1 2.31 1c6 2.75 24.2 8.8 29.91 10 .55.11 2.05.49 3.33.84s3.81.92 5.61 1.25 4.07.78 5.06 1 3 .56 4.4.76l7 .95a220.12 220.12 0 0 0 50 1c7.07-.66 17.57-2.07 20.6-2.75 1-.23 3.26-.68 5-1 10.06-1.85 30.24-8.1 38.87-12.05a22.72 22.72 0 0 1 2.29-1c.61 0 11.08-5.11 16.8-8.2a197.5 197.5 0 0 0 19.22-12c.89-.66 2.13-1.57 2.76-2 38.48-27 64.73-73.68 65.91-117.2l.11-4-11.9-.11-11.9-.1v.88c0 31.79-16.56 67.65-42.64 92.34a173.94 173.94 0 0 1-70.56 40c-1 .28-2.25.67-2.8.85s-2.62.72-4.6 1.17-4.23 1-5 1.21c-17.32 4.8-50.67 5.9-72.4 2.38-6.4-1-10.63-1.83-12.8-2.39-.77-.2-3-.75-5-1.22-3.84-.92-3.89-.93-10-2.82-5.94-1.84-10.9-3.56-13.6-4.73l-4.4-1.86c-47.1-19.9-82.4-60.69-90.17-104.2a120.3 120.3 0 0 1-2.1-15.5l-.49-6.1H6.4v1.3" + fill="#060606" + /> + <path + d="M189.17 11.37c-.1.09-3.2.36-6.9.6-6 .39-13.53 1.26-20.87 2.39-7.54 1.15-26.74 5.79-30 7.23a21.93 21.93 0 0 1-2.2.78c-3.52 1.07-5.2 1.63-7.8 2.63-1.54.59-3.11 1.24-3.5 1.44a3.17 3.17 0 0 1-1.09.37 17.53 17.53 0 0 0-2.88 1.2 18.87 18.87 0 0 1-2.81 1.2c-.45 0-14.29 6.79-17.52 8.6a202.5 202.5 0 0 0-24.17 15.91c-1.43 1.11-5.56 4.38-5.83 4.66-.11.12-1.55 1.38-3.2 2.81C42.07 77 26.28 98.75 17.43 120.36c-2.8 6.85-6.42 18.08-7.17 22.24-.11.66-.41 2-.66 3a140.64 140.64 0 0 0-2.39 16.8c-.2 2.31-.49 5.5-.64 7.1l-.26 2.9H30.4v-2.1c0-26.83 13.92-60 35-83.08C73.64 78.18 89 65 94.8 62a5.68 5.68 0 0 0 1.2-.78 10.22 10.22 0 0 1 1.6-1.08l4.17-2.53a156.18 156.18 0 0 1 18-9.41c6.35-2.83 7.13-3.16 9.4-4 1-.37 2.16-.83 2.6-1s3-1 5.6-1.91c14.55-4.74 23.67-6.67 42-8.89 24.62-3 64.43 1.78 84.1 10 1.63.68 2.68 1.09 4.1 1.59.77.27 1.94.72 2.6 1l2.8 1.17c24.1 9.89 47.77 27.1 63 45.82 1 1.21 1.89 2.29 2 2.4s1.31 1.65 2.63 3.42c12.43 16.63 21 36.16 24.35 55.38a150.06 150.06 0 0 1 1.8 16.9v2.3h23.6v-3.9c0-13.3-4.54-34.45-10-46.8a11.71 11.71 0 0 1-.76-2c0-2.19-12-23.7-16.28-29.3l-2.4-3.22c-2.41-3.26-2.22-3-6.16-7.8a224.73 224.73 0 0 0-20.35-20.31c-2.09-1.73-4.25-3.53-4.8-4-2.75-2.36-15.7-11.33-19.49-13.52-8.38-4.84-19.18-10.45-22.91-11.92-.77-.3-2.53-1.06-3.91-1.68a19 19 0 0 0-2.9-1.13 3.27 3.27 0 0 1-1.09-.35c-7.05-3.59-30.9-10.22-43.5-12.09-7.16-1.07-10.91-1.53-15.6-1.94l-9.62-.83c-4.33-.38-21.08-.55-21.41-.22m-3.37 97.09c-36.26 4-57.4 23.27-57.4 52.39 0 25.58 12.45 42.12 37 49.16a85.94 85.94 0 0 0 9.6 2.39l4.2.76c4.87.88 11.58 2 20.2 3.25 17.47 2.61 23.54 6.27 23.48 14.18-.07 9.06-8.28 13.41-25.31 13.41-13.45 0-21.76-3.89-27.27-12.75-3.86-6.21-6.06-7.23-12.1-5.57a79.64 79.64 0 0 0-9.2 3.13c-.72.31-4.12 1.51-11.95 4.24-11.38 4-12.66 7.53-6.87 19.24 4.8 9.7 12.67 17.32 23.7 22.91a31.25 31.25 0 0 0 4.42 2 3.48 3.48 0 0 1 1.21.35 51.68 51.68 0 0 0 9.49 3l3 .65c11.21 2.49 32.84 3.06 43.2 1.14 12-2.22 17.76-3.95 25.6-7.68 7.67-3.65 15.24-9.48 19.6-15.09a12.6 12.6 0 0 1 1.7-1.93.63.63 0 0 0 .3-.53 8.15 8.15 0 0 1 1.16-2.09c9.62-14 9.12-40.22-1.07-55.75-8.69-13.25-23.49-20.21-52.09-24.5-1.65-.25-4-.61-5.2-.81s-3.73-.56-5.6-.8c-17.44-2.26-24.24-6.65-23.46-15.14.48-5.19 4.07-8.68 10.26-10a43.51 43.51 0 0 1 11.72-1.27c9.35 0 15.35 1.84 20.19 6.19 1.95 1.76 2.36 2.3 5.93 7.89s5.7 5.9 15.16 1.75c3.12-1.37 4-1.74 8.3-3.58l3.15-1.38c1-.45 3.62-1.56 5.8-2.46 10.21-4.24 11.79-7.13 8.09-14.78-8.19-16.93-23.34-27.62-43.74-30.85-9.58-1.52-26.61-2.06-35.2-1.12" + fill="#fc443c" + /> + <path + d="M0 200v200h400V0H0v200M218 11.54c6.11.55 11.7 1.19 14.4 1.65l4.6.81c11 1.8 29.15 6.55 36.8 9.64l5.2 2c2.42.94 5.48 2.19 6.8 2.78s3.2 1.41 4.19 1.83c23.19 9.89 50.32 30.47 66.9 50.76 13 15.86 24.14 36.83 28.8 54l1.14 4.21c2.3 8.55 4.33 22.92 4.36 30.9v2.7h-24.65l-.49-5.7a128.25 128.25 0 0 0-4.44-25.9c-1.56-5.55-1.75-6.2-2-6.6a15.21 15.21 0 0 1-.8-2.2 39.5 39.5 0 0 0-1.43-3.8L355 123.2a129.17 129.17 0 0 0-7-13.4c-.57-.88-1.27-2-1.55-2.6-4.19-8.34-21.8-27.85-32.87-36.39-1-.76-1.89-1.49-2-1.61-.6-.66-11.26-8.06-13-9-.44-.25-2.3-1.38-4.13-2.51s-3.36-2.09-3.45-2.09-1.42-.73-3-1.6-5.47-2.8-8.55-4.23A124.64 124.64 0 0 0 267 44.41c-4.17-1.57-6.18-2.26-9.4-3.24-.77-.23-1.76-.57-2.2-.75-2.46-1-15.75-4.19-21.8-5.21L226.4 34c-13.2-2.32-42.41-2.32-55.6 0l-6 1c-5.26.89-9.61 1.76-10.8 2.16-.55.18-2 .55-3.2.82a103 103 0 0 0-10 2.83c-11.9 3.63-31.24 12.19-37.22 16.4a5.45 5.45 0 0 1-1.33.79c-.46 0-9.72 6.19-14 9.35a144.65 144.65 0 0 0-21.93 19.77c-2.26 2.53-4.31 4.79-4.55 5-5.5 5.72-16 22.64-20.35 32.85-.38.88-.85 1.91-1.05 2.29a3.24 3.24 0 0 0-.37 1.14 10 10 0 0 1-.78 2.1 125.92 125.92 0 0 0-4 12.1 130.73 130.73 0 0 0-4 24.92L31 172.6l-12.31.11c-11 .09-12.34 0-12.56-.54-.31-.8.56-12.68 1.28-17.57.29-2 .65-4.41.79-5.4a22.47 22.47 0 0 1 .59-2.8c.18-.55.55-2.08.83-3.4s.73-3.3 1-4.4.64-2.45.77-3a16.59 16.59 0 0 1 .58-1.8c.17-.44.77-2.24 1.32-4 7.61-24.2 25.94-51.31 46.69-69 1.65-1.41 3.09-2.65 3.2-2.76C70.73 50.45 95 35.53 111.6 28.28A231.39 231.39 0 0 1 142 18c3.41-.86 6.83-1.73 7.6-1.95 2-.55 11.26-2.22 17.6-3.18a127.13 127.13 0 0 1 12.8-1.3 30 30 0 0 0 4-.36c2.39-.45 28.15-.19 34 .33M214.2 108c4.55.55 8.57 1.17 10.2 1.57l3.4.82a59.1 59.1 0 0 1 14.4 5.47c12.8 6.46 26.37 24.14 24.8 32.28-.44 2.36-8.74 8.26-11.61 8.26a15.72 15.72 0 0 0-2.13.85c-1.92.85-3.7 1.62-5.9 2.55-.66.28-2.1.91-3.19 1.4-3.05 1.36-10.22 4.24-11.94 4.79-3.53 1.13-5.56-.25-9.68-6.59-6.4-9.85-14-13-28.79-11.77-12.07 1-17 4.28-17 11.45 0 7.68 5.55 10.93 23.17 13.57 8.61 1.29 10.8 1.61 14.43 2.11a83 83 0 0 1 8.6 1.65c.88.19 2.5.54 3.6.76s2.63.58 3.4.8l2.8.76a30.22 30.22 0 0 1 3.8 1.27c.44.18 1.88.73 3.2 1.23 17.06 6.45 27.53 19.18 30.45 37 6.13 37.42-18.91 62.48-65.68 65.76-38.81 2.71-68.49-11.55-77.39-37.21-2.84-8.19-.27-11.1 14-15.85 1-.33 2.16-.76 2.6-1s1.61-.63 2.6-.95 3.33-1.17 5.2-1.86c11.41-4.25 14.66-3.68 18.83 3.29 5.84 9.77 16.52 14 31.92 12.72 14.33-1.21 20-4.83 20.05-12.71 0-7.68-5.36-10.67-24.6-13.67s-27.39-4.55-31.8-6c-.55-.18-1.81-.56-2.8-.84a9.52 9.52 0 0 1-2.18-.85 1.87 1.87 0 0 0-1-.34c-2.34 0-13.62-6.7-18-10.66-13.27-12-18.08-36.31-11-55.65 4.19-11.48 14.44-21.79 27.3-27.47 2.36-1.05 4.6-2.06 5-2.25a3.72 3.72 0 0 1 1.42-.36 2 2 0 0 0 1.11-.33c.58-.5 4.83-1.6 12.38-3.21 7.05-1.5 26.71-2 36-.83M31.46 229c3.63 50.62 37.54 95.07 90.14 118.3.77.34 2.44 1.08 3.71 1.66a20.82 20.82 0 0 0 2.58 1c.15 0 1.9.62 3.89 1.37a204.71 204.71 0 0 0 27 7.82c15.67 3 24.2 3.78 39.8 3.78 14.18 0 25-.91 35.6-3l4.2-.81c1-.18 2.56-.52 3.5-.76l3.2-.79c1.37-.34 6.17-1.61 9.1-2.42 1.29-.35 10-3.36 14-4.81 1.32-.48 3.35-1.31 4.51-1.84a18.8 18.8 0 0 1 2.35-1c.24 0 8-3.67 9.54-4.5l3.4-1.81c5.08-2.68 17.85-10.74 20.7-13.06.38-.32 1.6-1.25 2.7-2.06 17.82-13.23 35.39-35.13 43.78-54.57.91-2.09 1.8-4.16 2-4.6s.57-1.43.83-2.2.67-1.85.89-2.4a38.72 38.72 0 0 0 1.88-5.6c.46-1.65 1-3.54 1.19-4.2s.54-2 .77-3l.83-3.6c.66-2.82 2-12.25 2.23-16 .12-1.87.34-4.35.47-5.5l.23-2.1h12.33c6.78 0 12.42.14 12.53.32.24.39-.83 14.6-1.33 17.68-.19 1.21-.54 3.37-.78 4.8-.46 2.89-1.78 9-2.39 11.2-.22.77-.74 2.66-1.16 4.21-2.3 8.46-6.92 19.42-12.82 30.39a177 177 0 0 1-12.77 19.4l-3 3.8a184.38 184.38 0 0 1-25.12 25l-3.34 2.67c-.43.36-2.05 1.56-3.6 2.68s-2.91 2.15-3 2.28c-.3.37-10.77 7.16-11 7.16a5.48 5.48 0 0 0-1.3.75c-3.8 2.7-18.61 10.23-26.06 13.25-1.21.49-2.74 1.13-3.41 1.42s-2.56 1-4.2 1.57a39.18 39.18 0 0 0-3.69 1.43 3.24 3.24 0 0 1-1.3.38 3.24 3.24 0 0 0-1.3.38 31 31 0 0 1-3.7 1.24c-1.65.47-3.36 1-3.8 1.19a16.59 16.59 0 0 1-1.8.58l-4 1.07c-3 .8-4.49 1.18-7.7 1.94-25.28 6-59 7.13-85.3 2.8-8.3-1.37-14.45-2.6-18-3.6-.88-.25-2.32-.62-3.2-.82s-2.23-.54-3-.75l-2.4-.64a16.59 16.59 0 0 1-1.8-.58c-.44-.17-2.15-.72-3.8-1.2a37.17 37.17 0 0 1-3.7-1.25 3.36 3.36 0 0 0-1.3-.36 3.48 3.48 0 0 1-1.31-.37c-.38-.2-1.77-.76-3.09-1.24s-2.85-1.05-3.4-1.28l-1.8-.73a233 233 0 0 1-22.8-10.8c-1.54-.87-3.07-1.7-3.4-1.85a3.19 3.19 0 0 1-.8-.5 6.74 6.74 0 0 0-1.16-.77L86.25 354a213.71 213.71 0 0 1-19.46-13.6c-.38-.33-1.39-1.14-2.23-1.8-12.69-9.93-30.07-29.82-37.29-42.67-.44-.78-1-1.85-1.33-2.38a8.1 8.1 0 0 0-.75-1.15c-.49-.47-4.7-8.76-6.71-13.2-1.58-3.48-4.22-10.35-5.3-13.8-.49-1.54-1-3.16-1.2-3.6s-.43-1.25-.57-1.8-.43-1.63-.64-2.4a138 138 0 0 1-4.62-29.47c-.37-6.41-1.81-5.76 12.54-5.64l12.31.11.46 6.4" + fill="#fbfbfb" + /> + <path + d="M6.27 222.66c-.92.92.9 19.85 2.56 26.64.89 3.62 1.12 4.63 1.57 6.7a100.69 100.69 0 0 0 3 10A152.45 152.45 0 0 0 24 290.1l1.7 3c.46.82 1.18 2 1.6 2.56A5.26 5.26 0 0 1 28 297c0 1 10.5 15.33 14.91 20.4a187.57 187.57 0 0 0 31.92 29 198.52 198.52 0 0 0 43 23.17c2.42.94 4.76 1.86 5.2 2.05 1.1.47 7.37 2.52 11 3.59 1.65.49 3.81 1.16 4.8 1.47s2.61.75 3.6 1 2.43.55 3.2.74l3.8.94c10.17 2.52 27.16 4.65 38.8 4.87l8 .14-6.4-.33c-7.62-.41-14.78-1.05-20.8-1.88l-7-.95c-1.43-.2-3.41-.54-4.4-.76s-3.27-.68-5.06-1-4.32-.89-5.61-1.25-2.78-.73-3.33-.84c-5.71-1.16-23.91-7.21-29.91-10a21.16 21.16 0 0 0-2.31-1c-.27 0-6.46-2.91-10.38-4.88C53.77 337.79 19.89 297 10.05 252c-.27-1.21-.65-2.83-.85-3.6-.79-3-2.78-20.3-2.79-24.3v-1.3h23.84l.49 6.1a120.3 120.3 0 0 0 2.1 15.5c7.77 43.51 43.07 84.3 90.16 104.17l4.4 1.86c2.7 1.17 7.66 2.89 13.6 4.73 6.11 1.89 6.16 1.9 10 2.82 2 .47 4.23 1 5 1.22 2.17.56 6.4 1.35 12.8 2.39a205.75 205.75 0 0 0 51.8 1 190.12 190.12 0 0 0 20.6-3.37c.77-.21 3-.76 5-1.21s4.05-1 4.6-1.17 1.81-.57 2.8-.85c58.57-16.53 102.78-62.25 111.56-115.38.67-4.09 1.64-14 1.64-16.93v-.88l11.9.1 11.9.11-.11 4c-1.18 43.52-27.43 90.19-65.91 117.2-.63.44-1.87 1.35-2.76 2a197.5 197.5 0 0 1-19.22 12c-5.72 3.09-16.19 8.2-16.8 8.2a22.72 22.72 0 0 0-2.29 1c-8.63 3.95-28.81 10.2-38.87 12.05-1.78.33-4 .78-5 1-5.67 1.28-21.11 3-32.2 3.59l-6.4.33 8-.14c13.08-.25 31.3-2.7 42.2-5.68 1.21-.32 2.92-.75 3.8-.94s2.86-.73 4.4-1.2 3.34-1 4-1.16c1.55-.42 9.75-3.14 10.8-3.6.44-.19 1.88-.74 3.2-1.22s2.76-1 3.2-1.22l4.69-2a216.69 216.69 0 0 0 22.85-11.55c1.51-.92 2.81-1.67 2.89-1.67.37 0 12-7.87 13.77-9.32.33-.27 1.23-.93 2-1.47a21.72 21.72 0 0 0 1.93-1.5c.3-.29 1.2-1 2-1.62 5.73-4.29 16-14 21.88-20.73 3.3-3.76 3.48-4 5.8-7 .76-1 1.49-1.89 1.6-2 1.54-1.45 9.64-13.65 12.61-19 1.77-3.17 5.55-10.67 6.16-12.2.31-.77 1-2.3 1.45-3.4a147.22 147.22 0 0 0 9-31c1.22-7.3 2.05-16.09 1.85-19.8l-.12-2.4-12.3-.11c-11.52-.09-12.3-.06-12.31.6a196.31 196.31 0 0 1-3.19 24.81c-.66 2.8-1.12 4.61-1.57 6.2-.24.83-.59 2.08-.78 2.8a26.49 26.49 0 0 1-1.27 3.7c-.19.44-.63 1.7-1 2.8s-.89 2.54-1.18 3.2-.87 2-1.27 3c-8.31 20.23-24.05 40.19-44.55 56.5a177.1 177.1 0 0 1-34 20.4l-3.38 1.5a197.12 197.12 0 0 1-25.82 8.86 113.57 113.57 0 0 1-15.4 3.31c-2.2.37-4.9.82-6 1-15.39 2.76-40.48 2.6-59.2-.37-15.14-2.4-32.5-7.31-43.31-12.25a18 18 0 0 0-2.35-1c-.47 0-10.36-4.82-13.54-6.59A189.12 189.12 0 0 1 85 325.6c-8.66-6.72-20.15-18.21-26.24-26.23-1-1.3-1.9-2.46-2-2.57-2.2-1.86-12.83-20.34-15.66-27.2-.55-1.34-1.13-2.81-1.55-3.9-.19-.5-.52-1.31-.74-1.8-3.68-8.62-7.19-26.37-7.72-39.1l-.09-2.2-12.23-.11c-6.72-.05-12.35 0-12.5.17m191.43 162a9 9 0 0 0 2.2 0c.61-.09.11-.17-1.1-.17s-1.7.08-1.1.17" + fill="#959595" + /> + <path + d="M184 11.21a30 30 0 0 1-4 .36 127.13 127.13 0 0 0-12.8 1.27c-6.34 1-15.58 2.63-17.6 3.18-.77.22-4.19 1.09-7.6 1.95C117.41 24.16 94 35.23 72 51c-2.15 1.56-8.08 6.24-8.8 7-.11.11-1.55 1.35-3.2 2.76-20.75 17.73-39.08 44.84-46.69 69-.55 1.76-1.15 3.56-1.32 4a16.59 16.59 0 0 0-.58 1.8c-.13.55-.48 1.9-.77 3s-.75 3.08-1 4.4-.65 2.85-.83 3.4a22.47 22.47 0 0 0-.59 2.8c-.14 1-.5 3.42-.79 5.4-.72 4.89-1.59 16.77-1.28 17.57.22.57 1.54.63 12.56.54L31 172.6l.26-5.08a130.73 130.73 0 0 1 4-24.92 125.92 125.92 0 0 1 4-12.1 10 10 0 0 0 .78-2.1 3.24 3.24 0 0 1 .37-1.11c.2-.38.67-1.41 1.05-2.29 4.37-10.21 14.85-27.13 20.35-32.85.24-.24 2.29-2.5 4.55-5a144.65 144.65 0 0 1 21.9-19.8c4.27-3.16 13.53-9.35 14-9.35a5.45 5.45 0 0 0 1.33-.79c6-4.25 25.32-12.77 37.22-16.39a103 103 0 0 1 10-2.83c1.21-.27 2.65-.64 3.2-.82 1.19-.4 5.54-1.27 10.8-2.16l6-1c13.19-2.32 42.4-2.32 55.6 0l7.2 1.22c6.05 1 19.34 4.19 21.8 5.21.44.18 1.43.52 2.2.75a147.52 147.52 0 0 1 21.8 8.57c3.08 1.43 6.93 3.34 8.55 4.23s3 1.63 3.08 1.63 1.61.93 3.44 2.06 3.69 2.26 4.13 2.51c1.74 1 12.4 8.37 13 9 .11.12 1 .85 2 1.61 11.07 8.54 28.68 28.05 32.87 36.39.28.55 1 1.72 1.55 2.6a129.17 129.17 0 0 1 7 13.4l2.35 5.4a39.5 39.5 0 0 1 1.43 3.8 15.21 15.21 0 0 0 .8 2.2c.26.4.45 1.05 2 6.6a92.08 92.08 0 0 1 3.16 14.8c.43 3 1 8 1.28 11.1l.49 5.7h24.66v-2.7c0-8-2.06-22.35-4.36-30.9l-1.15-4.19c-4.66-17.16-15.84-38.13-28.8-54-16.58-20.29-43.71-40.87-66.89-50.76-1-.42-2.87-1.24-4.19-1.83s-4.38-1.84-6.8-2.78l-5.2-2C266.15 20.51 248 15.76 237 14l-4.6-.77c-2.7-.46-8.29-1.1-14.4-1.65-5.85-.52-31.61-.78-34-.33m26.58.38 9.62.83c4.69.41 8.44.87 15.6 1.94 12.6 1.87 36.45 8.5 43.5 12.09a3.27 3.27 0 0 0 1.09.35 19 19 0 0 1 2.9 1.13c1.38.62 3.14 1.38 3.91 1.68 3.73 1.47 14.53 7.08 22.91 11.92 3.79 2.19 16.74 11.16 19.49 13.52.55.47 2.71 2.27 4.8 4a224.73 224.73 0 0 1 20.36 20.31c3.94 4.78 3.75 4.54 6.16 7.8l2.4 3.22c4.33 5.6 16.28 27.11 16.28 29.3a11.71 11.71 0 0 0 .76 2c5.5 12.35 10 33.5 10 46.8v3.9H366.8v-2.3c0-22.66-10.27-51-26.15-72.28-1.32-1.77-2.5-3.31-2.63-3.42s-1-1.19-2-2.4c-13-16-31.3-30.3-51.81-40.59-6.07-3-7.43-3.68-11.2-5.23L270.2 45c-.66-.28-1.83-.73-2.6-1-1.42-.5-2.47-.91-4.1-1.59-19.67-8.26-59.48-13-84.1-10-18.33 2.22-27.45 4.15-42 8.89-2.64.86-5.16 1.72-5.6 1.91s-1.61.65-2.6 1c-2.27.84-3 1.17-9.4 4a156.18 156.18 0 0 0-18 9.41l-4.2 2.52a10.22 10.22 0 0 0-1.6 1.08 5.68 5.68 0 0 1-1.2.78c-1.42.74-5.3 3.58-10.94 8-21.69 17-37.92 39.3-46.5 63.75-3.73 10.63-7 27.68-7 36.53v2.1H6.31l.26-2.9c.15-1.6.44-4.79.64-7.1A140.64 140.64 0 0 1 9.6 145.6c.25-1 .55-2.34.66-3 .75-4.16 4.37-15.39 7.17-22.24C26.28 98.75 42.07 77 60.4 61.19c1.65-1.43 3.09-2.69 3.2-2.81.27-.28 4.35-3.55 5.83-4.66A202.5 202.5 0 0 1 93.6 37.8c3.23-1.81 17.07-8.6 17.52-8.6a18.87 18.87 0 0 0 2.81-1.2 17.53 17.53 0 0 1 2.88-1.2 3.17 3.17 0 0 0 1.09-.37c.39-.2 2-.85 3.5-1.44 2.6-1 4.28-1.55 7.8-2.62a21.93 21.93 0 0 0 2.2-.78c3.26-1.44 22.46-6.08 30-7.23 7.34-1.13 14.92-2 20.87-2.39 3.7-.24 6.8-.51 6.9-.6.33-.33 17.08-.16 21.41.22m-19 95.86a132.26 132.26 0 0 0-13.4 1.41c-7.55 1.61-11.8 2.71-12.38 3.21a2 2 0 0 1-1.11.33 3.72 3.72 0 0 0-1.42.36c-.38.19-2.62 1.2-5 2.25a52.33 52.33 0 0 0-24.52 21.53c-9.16 16.09-7.68 41.27 3.3 56.07 4.69 6.33 18.6 16.14 22.93 16.18a1.87 1.87 0 0 1 1 .34 9.52 9.52 0 0 0 2.18.85c1 .28 2.25.66 2.8.84 4.41 1.45 12.65 3 31.8 6s24.61 6 24.6 13.67c0 7.88-5.72 11.5-20.05 12.71-15.4 1.3-26.08-2.95-31.92-12.72-4.17-7-7.42-7.54-18.83-3.29-1.87.69-4.21 1.53-5.2 1.86s-2.16.75-2.6.95-1.61.63-2.6 1c-14.29 4.75-16.86 7.66-14 15.85 8.9 25.66 38.58 39.92 77.39 37.21 46.77-3.28 71.81-28.34 65.68-65.76-2.92-17.85-13.39-30.58-30.45-37-1.32-.5-2.76-1-3.2-1.23a30.22 30.22 0 0 0-3.8-1.27L230 178c-.77-.22-2.3-.57-3.4-.8s-2.72-.57-3.6-.76a83 83 0 0 0-8.6-1.65c-3.63-.5-5.82-.82-14.43-2.11-17.62-2.64-23.17-5.89-23.17-13.57 0-7.17 4.93-10.49 17-11.45 14.8-1.18 22.39 1.92 28.79 11.77 4.12 6.34 6.15 7.72 9.68 6.59 1.72-.55 8.89-3.43 11.94-4.79 1.09-.49 2.53-1.12 3.19-1.4 2.2-.93 4-1.7 5.9-2.55a15.72 15.72 0 0 1 2.13-.85c.12 0 2.06-.87 4.3-1.93 8-3.82 9.22-7 5.43-14.56q-9.24-18.4-27.36-26.11a47.53 47.53 0 0 0-10-3.38l-3.4-.82c-1.63-.4-5.65-1-10.2-1.57-3.46-.42-18.38-.8-22.6-.58m21.6 1.15c26.59 3 42.25 12.63 51.54 31.83 3.7 7.65 2.12 10.54-8.09 14.78-2.18.9-4.79 2-5.8 2.46l-3.15 1.38a564.85 564.85 0 0 0-8.3 3.58c-9.46 4.15-11.56 3.91-15.16-1.75s-4-6.13-5.93-7.89c-4.84-4.35-10.84-6.19-20.19-6.19a43.51 43.51 0 0 0-11.72 1.27c-6.19 1.3-9.78 4.79-10.26 10-.78 8.49 6 12.88 23.46 15.14 1.87.24 4.39.6 5.6.8s3.55.56 5.2.81c28.6 4.29 43.4 11.25 52.09 24.5 10.19 15.53 10.69 41.77 1.07 55.75a8.15 8.15 0 0 0-1.16 2.09.63.63 0 0 1-.3.53 12.6 12.6 0 0 0-1.7 1.93c-4.36 5.61-11.93 11.44-19.6 15.09-7.84 3.73-13.6 5.46-25.6 7.68-10.36 1.92-32 1.35-43.2-1.14l-3-.65a51.68 51.68 0 0 1-9.49-3 3.48 3.48 0 0 0-1.21-.35 31.25 31.25 0 0 1-4.42-2c-11-5.59-18.9-13.21-23.7-22.91-5.79-11.71-4.51-15.28 6.87-19.24 7.83-2.73 11.23-3.93 11.95-4.24a79.64 79.64 0 0 1 9.2-3.13c6-1.66 8.24-.64 12.1 5.57 5.51 8.86 13.82 12.75 27.27 12.75 17 0 25.24-4.35 25.31-13.41.06-7.91-6-11.57-23.48-14.18-8.62-1.29-15.33-2.37-20.2-3.25l-4.2-.76a104.4 104.4 0 0 1-12-3.12c-27-8.71-39.93-32.88-32.9-61.68 6.85-28.06 40.29-43.76 83.1-39m178 115.09c.19 1.11.24 1.16.37.36a1.81 1.81 0 0 0-.22-1.31c-.22-.22-.28.15-.15.95m-198.46 161a14.54 14.54 0 0 0 2.8 0c.83-.08.25-.16-1.29-.16s-2.22.07-1.51.16m9.21 0a12.55 12.55 0 0 0 2.6 0c.71-.09.13-.16-1.3-.16s-2 .07-1.3.16" + fill="#fc9f9b" + /> + </g> + </svg> +); + +export default Staking4All; diff --git a/src/config/validators/StakingFacilities.tsx b/src/config/validators/StakingFacilities.tsx new file mode 100644 index 0000000000..16cdbd3cc6 --- /dev/null +++ b/src/config/validators/StakingFacilities.tsx @@ -0,0 +1,46 @@ +const StakingFacilities = () => ( + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 600 600"> + <defs> + <linearGradient + id="linear-gradient" + y1={300} + x2={600} + y2={300} + gradientUnits="userSpaceOnUse" + > + <stop offset={0} stopColor="#141b30" /> + <stop offset={1} stopColor="#1c3d6f" /> + </linearGradient> + </defs> + <path + style={{ + stroke: '#010101', + strokeMiterlimit: 10, + fill: 'url(#linear-gradient)', + }} + d="M0 0h600v600H0z" + /> + <path + fill="#fff" + d="M498.16 210.2v-21.48l-142.45-68.44-253.87 52.52v23.87l251.48-39.79 144.84 53.32z" + /> + <path + fill="#fff" + d="M498.16 219.75v22.29L334.22 210.2l-232.38 26.27V210.2l238.75-36.6 157.57 46.15z" + /> + <path + fill="#fff" + d="M498.16 252.38v23.08L319.1 263.92l-217.26 11.54v-27.85l219.65-19.9 176.67 24.67zM498.16 287.4v23.87l-198.47 6.37-197.85-6.37V287.4l202.14-5.57 194.18 5.57zM498.16 324v24.15l-217.45 25.42-178.87-27.96V324l180.78 13.34L498.16 324z" + /> + <path + fill="#fff" + d="M498.16 362.77v24.79l-235.89 38.77-160.43-46.4v-22.88l162.34 34.64 233.98-28.92z" + /> + <path + fill="#fff" + d="M101.84 388.19v21.61l143.28 69.92 253.04-53.39v-24.79l-251.14 41.95-145.18-55.3z" + /> + </svg> +); + +export default StakingFacilities; diff --git a/src/config/validators/TurboFlakes.tsx b/src/config/validators/TurboFlakes.tsx new file mode 100644 index 0000000000..1d905e56ff --- /dev/null +++ b/src/config/validators/TurboFlakes.tsx @@ -0,0 +1,29 @@ +const TurboFlakes = () => ( + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 156.84 156.84"> + <circle + cx={78.42} + cy={78.42} + r={78.42} + style={{ + fill: '#ed1c24', + }} + /> + <circle + cx={53.96} + cy={108.04} + r={13.17} + style={{ + fill: '#fff', + }} + /> + <path + d="M98.23 78.64a13.17 13.17 0 0 1-13.17 13.17c-7.27 0-37.64-13.17-37.64-13.17s30.37-13.17 37.64-13.17a13.17 13.17 0 0 1 13.17 13.17ZM116.59 49a13.17 13.17 0 0 1-13.17 13.17C96.15 62.18 53.54 49 53.54 49s42.6-13.17 49.88-13.17A13.17 13.17 0 0 1 116.59 49Z" + transform="translate(-.52 -.22)" + style={{ + fill: '#fff', + }} + /> + </svg> +); + +export default TurboFlakes; diff --git a/src/config/validators/VFValidierung.tsx b/src/config/validators/VFValidierung.tsx new file mode 100644 index 0000000000..8a1b8f4796 --- /dev/null +++ b/src/config/validators/VFValidierung.tsx @@ -0,0 +1,40 @@ +const VFValidierung = () => ( + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 513 513"> + <path + strokeMiterlimit={10} + fill="none" + stroke="#fff" + d="M.5.5h512v512H.5z" + /> + <g> + <path + d="M98.01 432.1c-8.17-7.77-14.62-15.05-17-17.82-20.73-23.72-37.61-54.18-48.2-87a261.4 261.4 0 0 1-10.12-45.52c-1.29-9.35-1.8-35.83-.88-46.56 3.29-38.71 17.73-79 40-111.76a232.51 232.51 0 0 1 46.34-49.94c11.36-9.07 29.79-20.75 44.07-27.9 19.66-9.86 34.16-14.75 56.39-19 19.69-3.78 26.86-4.48 46-4.48 49.81 0 91.83 12.8 133.71 40.73a157.89 157.89 0 0 1 22.07 17.6c8.06 6.15 17.88 16.67 17.88 16.67 16.51 15.39 31 39.48 41.05 61.69a236.86 236.86 0 0 1 14.18 155.54c-13.62 54.39-46.64 103-91.45 134.47-32 22.48-68.33 36.32-108.21 41.19-7.58.92-36.17 1.65-43.18 1.09-40.26-3.2-76-14.38-107-33.46-7.6-4.68-22.56-15.27-22.56-15.27" + fill="#ffcd05" + /> + <path + d="M27.4 307.85c-1.9-8.2-3.46-16.78-4.75-26.1s-1.8-35.83-.88-46.56c3.29-38.71 17.73-79 40-111.76a232.51 232.51 0 0 1 46.38-49.93c11.36-9.07 29.79-20.75 44.07-27.9 19.66-9.86 34.16-14.75 56.39-19 19.69-3.78 26.86-4.48 46-4.48 49.81 0 91.83 12.8 133.71 40.73a157.89 157.89 0 0 1 22.07 17.6c8.06 6.15 17.88 16.67 17.88 16.67 16.51 15.39 31 39.48 41.05 61.69a235.05 235.05 0 0 1 15.49 149.07" + fill="#ed2224" + /> + <path + d="M24.34 215.66a241.6 241.6 0 0 1 37.47-92.23 232.51 232.51 0 0 1 46.34-49.93c11.36-9.07 29.79-20.75 44.07-27.9 19.66-9.86 34.16-14.75 56.39-19 19.69-3.78 26.86-4.48 46-4.48 49.81 0 91.83 12.8 133.71 40.73a157.89 157.89 0 0 1 22.07 17.6c8.06 6.15 17.88 16.67 17.88 16.67 16.51 15.39 31 39.48 41.05 61.69a231.88 231.88 0 0 1 17.71 56.88" + fill="#010101" + /> + <path + d="M98.01 432.1C252.38 245.68 327.32 164.5 418.83 87.64c5.22 4.88 9.49 9.08 9.49 9.08 16.51 15.4 30.76 38.28 41.49 61.69 22.29 48.68 26.69 103.9 13.69 155.94-13.62 54.39-46.64 103-91.45 134.47-32 22.48-68.33 36.32-108.21 41.19-7.58.92-36.17 1.65-43.18 1.09-40.26-3.2-76-14.38-107-33.46-7.6-4.68-22.56-15.27-22.56-15.27" + fill="#c0c0cc" + /> + <path + d="M118.85 439.75c3.65-1.22 10.07-3.45 14.27-5a69.46 69.46 0 0 1 15.57-3.87c7.64-1.09 13.56-2.73 16.27-4.5a6 6 0 0 0 1.93-2.78c.56-1.7.4-2.45-1.38-6.35-.79-2.79-3.76-4.17-.21-4.69 4.54-.67 8 1 18.89 9.21 15.16 11.4 26.34 16.2 37.79 16.2a35.39 35.39 0 0 0 16.69-3.74c4.49-2.24 5.1-3.55 5.15-11 .06-8.21-3-16.09-8.89-23.23a43.17 43.17 0 0 0-12.68-10.13c-3.29-1.52-3.56-2-2.18-4 1.24-1.77 4.54-2.71 9.37-2.65 5.16.07 9.88 1.51 20.56 6.29a83.91 83.91 0 0 0 27.05 7.4c15.13 1.57 27.81-1.36 35-8.11l2.86-2.68v-6.53c0-9.55-1.7-15.62-5.63-20.29a54.09 54.09 0 0 0-6.34-5.5c-5.7-4.33-8.85-7.41-11.39-11.1-3.26-4.73-4-4.57 19.16-4.18 43.13.73 64-3.06 74.46-13.57 5.06-5.07 6.37-8.65 5.9-16.21-.41-6.7-1-8.87-3.27-12.48-2.59-4.09-6.64-6.42-17.07-9.85-8.82-2.9-13.12-4.86-14.63-6.67-1.1-1.34 1.15-5.19 4.64-8 5.64-4.46 13.77-7.51 32.9-12.37 19.41-4.93 22.43-6.23 29.09-12.45 12.32-11.53 14.93-25.31 6.37-33.64-3.27-3.19-6.76-4.76-16.53-7.45-13.27-3.64-14.25-4.21-15.12-8.87-.48-2.56 1-6.1 5.18-12.58 2.82-4.34 6-7.39 14.5-13.74 7.11-5.33 11.3-9.79 13.6-14.48 1.84-3.72 2-4.37 1.92-9.59-.08-7.89-.31-8.26-6.95-11.43-7.82-3.73-7.9-3.89-5.41-11.84 3.34-10.69 7.42-15.82 4.16-23.05l-5.71-12.67s-7.95-6-15.07-6.13c-10.36-.26-16.19.61-20.1 3-7.08 4.33-8.59 5.1-10.65 5.45-2.66.45-4.18-.65-6.58-4.75-1.6-2.72-6.69-6.52-9.48-7.07-2.51-.5-8.42 1.1-14.13 3.82-4.14 2-5.72 3.22-11.75 9.28-7.25 7.29-10.73 9.57-15.83 10.41-2.23.37-2.54.25-4.07-1.55a52.51 52.51 0 0 1-4.53-8.11c-6.56-14-8.71-18.09-11-20.68q-9.22-10.45-24.26.27a26.73 26.73 0 0 0-5.3 4.93c-2.79 4-6.95 13.74-8.4 19.59-.77 3.14-3.62 11.89-6.33 19.45s-5.62 16.78-6.49 20.5c-2.31 9.91-4.91 13.68-8.77 12.71-2.6-.65-5.18-3.42-11.71-12.53-9.16-12.8-13.07-16.46-19.33-18.13-9.37-2.49-14.39-.18-21.47 9.93-5.52 7.88-7.82 17-7.82 30.87 0 9.39.5 13.92 3.25 29.46 1.48 8.37 2.6 12.89 4.13 16.67 3.2 7.94 5.1 15.77 5.16 21.19.05 4.74-.81 8.49-2.19 9.64-1.57 1.3-6.44-2.07-13.89-9.61-4.19-4.23-8.41-7.94-10.13-8.87-5.43-3-15-2.54-22.55 1-6 2.8-8.94 7.15-10.2 14.92-1.52 9.69 1.25 22.26 10.25 46.74 3.52 9.57 3.57 9.77 3.57 15.86 0 4.61-.29 7-1.14 9.4-1.12 3.17-1.19 3.23-3.43 3.23-2.51 0-2.33.22-5-5.75a37.66 37.66 0 0 0-18.1-18.2c-3.49-1.65-4.61-1.89-8.8-1.89-4.43 0-4.94.12-6.47 1.59a27.6 27.6 0 0 0-6.34 11.28c-1.17 4.49-1.18 14.9 0 24.09 1.24 10 1.61 10.65 13.87 24.64 4.73 5.4 7.37 10.2 8.48 15.46 1.67 7.87.82 8.4-5.41 3.4-4-3.21-6.3-4.21-8.25-3.58-1.6.51-4.74 5.54-6.48 10.37-1.46 4.08-1.56 5-1.59 14.64v10.28l-4 11.75c-2.21 6.46 7.68 25.52 7.68 25.52s17.2 8.87 20.84 7.65z" + fill="#fff" + /> + <path + d="M80.97 414.28c-20.73-23.72-37.61-54.18-48.2-87a261.4 261.4 0 0 1-10.12-45.52c-1.29-9.35-1.8-35.83-.88-46.56 3.29-38.71 17.73-79 40-111.76a232.51 232.51 0 0 1 46.38-49.94c11.36-9.07 29.79-20.75 44.07-27.9 19.66-9.86 34.16-14.75 56.39-19 19.69-3.78 26.86-4.48 46-4.48 49.81 0 91.83 12.8 133.71 40.73a157.89 157.89 0 0 1 22.07 17.6c8.06 6.15 17.88 16.67 17.88 16.67 16.51 15.39 31 39.48 41.05 61.69a236.86 236.86 0 0 1 14.18 155.54c-13.62 54.39-46.64 103-91.45 134.47-32 22.48-68.33 36.32-108.21 41.19-7.58.92-36.17 1.65-43.18 1.09-40.26-3.2-76-14.38-107-33.46-7.6-4.68-29.06-20.41-29.06-20.41-14.68-12.9-21.13-20.18-23.55-23z" + strokeWidth={18.9} + fill="none" + stroke="#fff" + /> + </g> + </svg> +); + +export default VFValidierung; diff --git a/src/config/validators/Wojdot.tsx b/src/config/validators/Wojdot.tsx new file mode 100644 index 0000000000..c0bdd6df32 --- /dev/null +++ b/src/config/validators/Wojdot.tsx @@ -0,0 +1,59 @@ +const Wojdot = () => ( + <svg + xmlns="http://www.w3.org/2000/svg" + viewBox="0 0 512 512" + xmlSpace="preserve" + > + <path d="M338 513H1.041V1.104h511.793V513H338m-79.418-211.033c.667.044 1.334.088 2.167.961-.393 2.951.823 4.222 3.844 3.904 1.444-.153 2.924.039 4.667.53.606-.352 1.212-.703 2.338-1.473 1.182-2.391 3.022-4.685 3.393-7.195.574-3.89.234-7.929.123-11.9-.15-5.343-2.609-7.76-8.03-7.947-1.976-.068-3.962.146-6.803.162-.86.057-1.721.114-3.395-.032-2.865-.679-3.856.535-3.784 3.365.13 5.11.02 10.225.038 15.338.009 2.38.117 4.629-3.14 5.224-.797.145-1.317 1.806-1.964 2.77 1.081.495 2.592 1.706 3.169 1.36 2.39-1.433 4.496-3.338 7.377-5.067m21.209-62.478c10.039 6.962 26.772 7.908 36.043 1.862l-1.924-9.653c-10.763 2.432-21.305 6.135-30.155-4.126 3.194-4.236 6.13-8.245 9.186-12.16 5.26-6.74 8.575-14.2 8.95-22.851.444-10.207-3.172-18.605-12.328-23.33-10.13-5.227-20.81-5.13-30.396 1.42-8.691 5.94-11.186 14.905-9.594 25.117 1.742 11.173 8.05 19.98 15.107 28.243 2.818 3.3 1.926 5.048-.995 7.14-5.501 3.939-11.599 3.593-17.77 2.575-2.889-.477-5.728-1.25-8.658-1.904-.406 1.524-.771 2.632-.993 3.77-1.368 7.032-1.363 7.062 5.499 8.51 12.508 2.643 24.216 1.747 33.642-8.25a372.457 372.457 0 0 0 4.386 3.637m-164.928-24.91v-9.557c-3.161 0-5.473.042-7.783-.007-8.709-.187-14.148-3.8-17.017-12.038-4.124-11.836-4.282-23.897-.187-35.744 3.137-9.074 10.027-13.14 19.827-12.62 8.676.46 14.18 5.237 16.716 14.468.212.772.615 1.492.834 2.01h12.249c0-.995.074-1.646-.011-2.275-1.634-12.028-9.83-21.22-21.53-24.177-14.49-3.661-28.87.823-36.396 11.265-6.672 9.257-8.128 19.77-7.95 30.826.343 21.4 7.435 31.843 27.661 39.457v27.235h13.587V214.58m270.29-73.875c-5.832 4.763-9.916 10.442-10.277 18.754 3.76 0 6.928.206 10.042-.111 1.092-.112 2.548-1.301 2.993-2.355 3.552-8.413 9.37-12.35 18.553-12.258 9.072.092 15.44 4.19 18.27 12.753 3.637 11.01 3.673 22.325.403 33.411-2.843 9.636-9.226 13.96-19.331 14.118-2.443.038-4.886.006-7.498.006v38.51h13.842c0-8.116.12-15.917-.06-23.71-.07-2.989.735-4.367 3.869-5.068 11.232-2.513 18.134-9.99 22.005-20.478 3.732-10.113 3.8-20.579 2.054-31.03-4.515-27.04-31.625-38.45-54.865-22.542m-193.561 55.818c3.346 7.875 9.945 11.041 18.627 8.939 7.243-1.754 11.005-7.723 10.365-16.446-.653-8.915-6.564-14.199-15.51-13.865-8.825.328-13.982 5.655-14.08 14.635-.022 1.982.21 3.967.598 6.737m164.51 5.05c4.38-5.304 4.39-11.368 2.544-17.464-1.81-5.976-7.545-9.253-14.58-8.955-7.096.3-12.057 4.033-13.647 10.266-1.88 7.37.201 14.573 5.214 18.05 5.58 3.869 13.696 3.313 20.47-1.897M208.336 298.64c.282 1.642.283 3.407.934 4.887.552 1.257 1.854 2.184 2.824 3.257.933-1.11 2.195-2.084 2.72-3.362.722-1.752.887-3.735 1.29-5.618 1.529 2.362 1.754 4.677 2.51 6.802.33.927 1.689 1.897 2.694 2.037.715.1 2.207-1.048 2.348-1.822 1.541-8.452 2.884-16.941 4.268-25.34-7.083-2.486-5.308 3.528-6.512 6.268l-5.015-6.929-4.807 6.577c-2.978-6.652-2.978-6.652-6.623-5.727a4260.21 4260.21 0 0 0 3.369 18.97m36.91-9.84c0-.664.022-1.328-.006-1.99-.22-5.252-2.852-8.284-7.196-8.305-4.357-.021-7.173 3.1-7.262 8.238-.077 4.473-.146 8.954.04 13.421.18 4.295 2.7 6.686 6.939 6.98 4.064.281 7.2-2.665 7.43-6.984.185-3.47.049-6.958.055-11.36m42.299-9.87c-6.595-.817-9.586 1.547-9.721 7.842-.093 4.319-.124 8.645.016 12.962.145 4.502 2.971 7.36 7.049 7.442 4.282.086 7.231-2.863 7.367-7.626.123-4.317.506-8.715-.146-12.935-.403-2.611-2.526-4.957-4.565-7.685m16.738 25.748v-19.499l4.723-4.208c-1.712-.719-3.368-1.74-5.153-2.074-1.925-.361-4.017-.335-5.946.017-1.119.205-2.06 1.384-3.08 2.124.982.753 1.943 1.535 2.957 2.243.382.267.885.36 1.743.69 0 6.367.012 12.82-.004 19.272-.01 4.064 1.486 4.907 4.76 1.435z" /> + <path + fill="#F7F7F7" + d="M279.492 239.258a606.687 606.687 0 0 1-4.087-3.407c-9.426 9.998-21.134 10.894-33.642 8.252-6.862-1.45-6.867-1.48-5.499-8.512.222-1.137.587-2.245.993-3.77 2.93.655 5.77 1.428 8.658 1.905 6.171 1.018 12.269 1.364 17.77-2.575 2.92-2.092 3.813-3.84.995-7.14-7.057-8.263-13.365-17.07-15.107-28.243-1.592-10.212.903-19.177 9.594-25.116 9.586-6.551 20.266-6.648 30.396-1.42 9.156 4.724 12.772 13.122 12.329 23.329-.376 8.652-3.692 16.112-8.95 22.85-3.057 3.916-5.993 7.925-9.187 12.16 8.85 10.262 19.392 6.56 30.155 4.127l1.924 9.653c-9.271 6.046-26.004 5.1-36.342-2.093z" + /> + <path + fill="#F3F3F3" + d="M114.863 215.068v28.354h-13.587v-27.235c-20.226-7.614-27.318-18.057-27.662-39.457-.177-11.055 1.279-21.57 7.95-30.826 7.527-10.442 21.907-14.926 36.397-11.265 11.7 2.957 19.896 12.15 21.53 24.177.085.63.01 1.28.01 2.275h-12.248c-.22-.518-.622-1.238-.834-2.01-2.536-9.23-8.04-14.007-16.716-14.468-9.8-.52-16.69 3.546-19.827 12.62-4.095 11.847-3.937 23.908.187 35.744 2.87 8.238 8.308 11.85 17.017 12.038 2.31.05 4.622.007 7.783.007v10.046zM385.432 140.485c22.961-15.689 50.07-4.279 54.586 22.76 1.745 10.452 1.678 20.918-2.054 31.03-3.871 10.49-10.773 17.966-22.005 20.479-3.134.701-3.939 2.08-3.87 5.067.18 7.794.06 15.595.06 23.712h-13.841v-38.511c2.612 0 5.055.032 7.498-.006 10.105-.158 16.488-4.482 19.33-14.118 3.271-11.086 3.235-22.401-.403-33.41-2.829-8.563-9.197-12.662-18.27-12.754-9.182-.093-15 3.845-18.552 12.258-.445 1.054-1.901 2.243-2.993 2.355-3.114.317-6.282.111-10.042.111.361-8.312 4.445-13.991 10.556-18.973z" + /> + <path + fill="#F6F6F6" + d="M191.455 196.128c-.251-2.376-.483-4.36-.461-6.343.098-8.98 5.255-14.307 14.08-14.635 8.946-.334 14.857 4.95 15.51 13.865.64 8.723-3.122 14.692-10.365 16.446-8.682 2.102-15.281-1.064-18.764-9.333zM355.854 201.837c-6.525 4.945-14.642 5.5-20.22 1.632-5.014-3.477-7.095-10.68-5.215-18.05 1.59-6.233 6.551-9.965 13.647-10.266 7.035-.298 12.77 2.979 14.58 8.955 1.846 6.096 1.835 12.16-2.792 17.729z" + /> + <path + fill="#E4E4E4" + d="M208.248 298.227a7977.639 7977.639 0 0 1-3.281-18.558c3.645-.925 3.645-.925 6.623 5.727l4.807-6.577 5.015 6.929c1.204-2.74-.571-8.754 6.512-6.267-1.384 8.398-2.727 16.887-4.268 25.34-.141.773-1.633 1.92-2.348 1.821-1.005-.14-2.364-1.11-2.694-2.037-.756-2.125-.981-4.44-2.51-6.802-.403 1.883-.568 3.866-1.29 5.618-.525 1.278-1.787 2.253-2.72 3.362-.97-1.073-2.272-2-2.824-3.257-.65-1.48-.652-3.245-1.022-5.3z" + /> + <path + fill="#E0E0E0" + d="M245.246 289.26c-.006 3.941.13 7.429-.054 10.9-.23 4.318-3.367 7.264-7.431 6.983-4.239-.294-6.76-2.685-6.939-6.98-.186-4.467-.117-8.948-.04-13.42.089-5.139 2.905-8.26 7.262-8.24 4.344.022 6.976 3.054 7.196 8.307.028.661.005 1.325.006 2.45m-9.902-.382c.008 3.47-.18 6.96.139 10.401.102 1.098 1.546 2.072 2.375 3.102.825-1.033 2.275-2.017 2.361-3.108.299-3.778.349-7.613.024-11.385-.142-1.642-1.426-3.185-2.19-4.772-.903 1.622-1.805 3.244-2.71 5.762z" + /> + <path + fill="#E1E1E1" + d="M287.889 279.06c1.695 2.597 3.818 4.943 4.221 7.554.652 4.22.27 8.618.146 12.935-.136 4.763-3.085 7.712-7.367 7.626-4.078-.081-6.904-2.94-7.05-7.442-.139-4.317-.108-8.643-.015-12.962.135-6.295 3.126-8.66 10.065-7.71m-1.052 5.017c-3.042-2.192-4.262-.557-4.393 2.338-.186 4.148-.248 8.326.06 12.46.093 1.239 1.597 2.373 2.455 3.555.825-1.213 2.302-2.395 2.36-3.643.213-4.636-.04-9.293-.482-14.71z" + /> + <path + fill="#E6E6E6" + d="M261.141 279.078c1.981-.085 3.967-.299 5.942-.231 5.422.186 7.881 2.604 8.03 7.947.112 3.971.452 8.01-.122 11.9-.371 2.51-2.211 4.804-3.937 7.355a26.844 26.844 0 0 0-2.073.858c-1.464-.036-2.944-.228-4.388-.075-3.02.318-4.237-.953-3.762-4.768.157-8.238.234-15.612.31-22.986m4.173 9.746v13.219c3.625.904 5.354-.026 5.354-3.39 0-3.796.005-7.592-.002-11.387-.006-3.22-1.6-4.396-5.08-3.722-.094 1.512-.183 2.946-.272 5.28z" + /> + <path + fill="#E5E5E5" + d="M304.246 305.086c-3.237 3.063-4.734 2.22-4.723-1.844.016-6.452.004-12.905.004-19.272-.858-.33-1.36-.423-1.743-.69-1.014-.708-1.975-1.49-2.958-2.243 1.021-.74 1.962-1.92 3.081-2.124 1.929-.352 4.021-.378 5.946-.017 1.785.335 3.44 1.355 5.153 2.074l-4.723 4.208c0 5.613 0 12.556-.037 19.908z" + /> + <path + fill="#D5D5D5" + d="M257.912 301.965c-2.21 1.731-4.318 3.636-6.707 5.07-.577.345-2.088-.866-3.17-1.361.648-.964 1.168-2.625 1.964-2.77 3.258-.595 3.15-2.844 3.141-5.224-.019-5.113.092-10.228-.038-15.338-.072-2.83.919-4.044 4.199-2.807.48 7.849.545 15.14.61 22.43z" + /> + <path + fill="#303030" + d="M258.247 301.966c-.4-7.292-.466-14.582-.54-22.33.853-.513 1.713-.57 3.004-.592.354 7.408.277 14.782.036 22.605-.831.406-1.498.362-2.5.317zM269.12 307.134c.37-.46.88-.693 1.674-.876-.322.4-.928.752-1.674.876z" + /> + <path + fill="#202020" + d="M235.345 288.43c.903-2.07 1.805-3.692 2.708-5.314.764 1.587 2.048 3.13 2.19 4.772.325 3.772.275 7.607-.024 11.385-.086 1.091-1.536 2.075-2.36 3.108-.83-1.03-2.274-2.004-2.376-3.102-.318-3.442-.13-6.93-.138-10.849zM287.02 284.462c.26 5.033.512 9.69.298 14.326-.057 1.248-1.534 2.43-2.359 3.643-.858-1.182-2.362-2.316-2.454-3.556-.309-4.133-.247-8.31-.06-12.46.13-2.894 1.35-4.529 4.575-1.953z" + /> + <path + fill="#2E2E2E" + d="M265.314 288.374c.09-1.884.178-3.318.271-4.83 3.481-.674 5.075.502 5.08 3.722.008 3.795.003 7.59.003 11.387 0 3.364-1.73 4.294-5.354 3.39v-13.67z" + /> + </svg> +); + +export default Wojdot; diff --git a/src/config/validators/bLdNodes.tsx b/src/config/validators/bLdNodes.tsx new file mode 100644 index 0000000000..669b51e886 --- /dev/null +++ b/src/config/validators/bLdNodes.tsx @@ -0,0 +1,21 @@ +const IconBldnodes = () => ( + <svg + xmlns="http://www.w3.org/2000/svg" + width="1em" + height="1em" + viewBox="0 0 285.000000 222.000000" + preserveAspectRatio="xMidYMid meet" + > + <g + transform="translate(0.000000,222.000000) scale(0.100000,-0.100000)" + fill="#000000" + stroke="none" + > + <path d="M2320 1993 c-36 -11 -62 -29 -73 -48 -8 -13 -14 -105 -17 -266 l-5 -246 -239 269 c-132 149 -251 274 -265 280 -38 14 -99 3 -127 -23 -29 -27 -41 -81 -25 -115 21 -46 712 -814 739 -821 44 -11 94 9 120 47 l24 35 -4 407 c-5 452 -4 445 -73 474 -19 8 -36 14 -37 13 -2 0 -10 -3 -18 -6z" /> + <path d="M755 1951 c-11 -5 -31 -20 -44 -35 -30 -32 -32 -94 -5 -128 11 -13 306 -346 657 -740 674 -756 666 -749 736 -733 61 13 103 83 82 138 -9 23 -1271 1450 -1314 1485 -20 17 -86 25 -112 13z" /> + <path d="M480 1252 c-58 -29 -60 -41 -53 -493 l6 -402 28 -31 c23 -26 35 -31 73 -31 36 0 51 6 73 28 l28 27 5 253 5 252 238 -268 c131 -147 247 -272 257 -277 25 -13 83 -13 113 1 47 21 71 94 46 141 -18 33 -677 771 -711 796 -37 27 -62 28 -108 4z" /> + </g> + </svg> +); + +export default IconBldnodes; diff --git a/src/config/validators/index.ts b/src/config/validators/index.ts index 1c650c09f5..3aa8dba661 100644 --- a/src/config/validators/index.ts +++ b/src/config/validators/index.ts @@ -3,25 +3,25 @@ /* Import your SVG Here. * Use upper camel-case for your SVG import, lower camel case for the svg. - * import { ReactComponent as ValidatorEntityName } from './thumbnails/validatorEntityName.svg'; + * import ValidatorEntityName from './thumbnails/validatorEntityName.svg'; */ -import { ReactComponent as Thumbnail4T2CAPITAL } from './thumbnails/4t2.svg'; -import { ReactComponent as AnyValid } from './thumbnails/anyvalid.svg'; -import { ReactComponent as Brightlystake } from './thumbnails/Brightlystake-logo.svg'; -import { ReactComponent as Cere } from './thumbnails/cere.svg'; -import { ReactComponent as EdgeServices } from './thumbnails/edgeservices.svg'; -import { ReactComponent as garm99 } from './thumbnails/garm99.svg'; -import { ReactComponent as Jinogami } from './thumbnails/Jinogami.svg'; -import { ReactComponent as medium } from './thumbnails/medium.svg'; -import { ReactComponent as SerGo } from './thumbnails/SerGo.svg'; -import { ReactComponent as StakeAngle } from './thumbnails/stakeangle.svg'; -import { ReactComponent as Testnetrun } from './thumbnails/Testnetrun.svg'; -import { ReactComponent as Tokem } from './thumbnails/tomek.svg'; -import { ReactComponent as TRK } from './thumbnails/TRK.svg'; -import { ReactComponent as wombat } from './thumbnails/wombat.svg'; -import { ReactComponent as XameyzIdentity } from './thumbnails/xameyz.svg'; +import Thumbnail4T2CAPITAL from './thumbnails/4t2.svg'; +import AnyValid from './thumbnails/anyvalid.svg'; +import Brightlystake from './thumbnails/Brightlystake-logo.svg'; +import Cere from './thumbnails/cere.svg'; +import EdgeServices from './thumbnails/edgeservices.svg'; +import garm99 from './thumbnails/garm99.svg'; +import Jinogami from './thumbnails/Jinogami.svg'; +import medium from './thumbnails/medium.svg'; +import SerGo from './thumbnails/SerGo.svg'; +import StakeAngle from './thumbnails/stakeangle.svg'; +import Testnetrun from './thumbnails/Testnetrun.svg'; +import Tokem from './thumbnails/tomek.svg'; +import TRK from './thumbnails/TRK.svg'; +import wombat from './thumbnails/wombat.svg'; +import XameyzIdentity from './thumbnails/xameyz.svg'; -export const VALIDATOR_COMMUNITY = [ +export const ValidatorCommunity = [ { name: 'CERE', Thumbnail: Cere, diff --git a/src/consts.ts b/src/consts.ts index 4c12839d3e..2d045a6da9 100644 --- a/src/consts.ts +++ b/src/consts.ts @@ -1,19 +1,18 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only import { stringToU8a } from '@polkadot/util'; +import BigNumber from 'bignumber.js'; +import type { Plugin } from 'types'; /* * Global Constants */ -export const AppVersion = '1.0.1'; -export const UriPrefix = ''; -export const TitleDefault = 'Cere Staking Dashboard'; -export const DappName = 'cere_staking_dashboard'; +export const AppVersion = '1.0.8'; +export const DappName = 'Cere Staking Dashboard'; export const CereUrl = 'https://cere.network'; - export const DefaultNetwork = 'cereMainnet'; - +export const ManualSigners = ['ledger', 'vault']; /* * Data Structure Helpers */ @@ -21,55 +20,46 @@ export const EmptyH256 = new Uint8Array(32); export const ModPrefix = stringToU8a('modl'); export const U32Opts = { bitLength: 32, isLe: true }; -export const PayeeStatus = [ - { - key: 'Staked', - name: 'Back to Staking', - }, - { - key: 'Stash', - name: 'To Stash Account', - }, - { - key: 'Controller', - name: 'To Controller Account', - }, -]; - -export const InterfaceMaximumWidth = 1550; export const SideMenuMaximisedWidth = 185; export const SideMenuMinimisedWidth = 75; -export const SideMenuStickyThreshold = 1175; -export const SectionFullWidthThreshold = 1050; -export const ShowAccountsButtonWidthThreshold = 850; +export const SideMenuStickyThreshold = 1150; +export const SectionFullWidthThreshold = 1000; +export const ShowAccountsButtonWidthThreshold = 825; export const FloatingMenuWidth = 250; export const SmallFontSizeMaxWidth = 600; -export const MediumFontSizeMaxWidth = 1600; export const TipsThresholdSmall = 750; export const TipsThresholdMedium = 1200; /* - * Toggle-able services + * Available plugins */ -export const ServiceList = ['cereStats']; +export const PluginsList: Plugin[] = [ + 'subscan', + 'binance_spot', + 'tips', + 'polkawatch', + 'cereStats', +]; /* * Fallback config values */ -export const FallbackMaxNominations = 16; -export const FallbackBondingDuration = 3; -export const FallbackSessionsPerEra = 6; -export const FallbackNominatorRewardedPerValidator = 256; -export const FallbackMaxElectingVoters = 10000; -export const FallbackExpectedBlockTime = 6000; +export const FallbackMaxNominations = new BigNumber(16); +export const FallbackBondingDuration = new BigNumber(28); +export const FallbackSessionsPerEra = new BigNumber(6); +export const FallbackNominatorRewardedPerValidator = new BigNumber(512); +export const FallbackMaxElectingVoters = new BigNumber(22500); +export const FallbackExpectedBlockTime = new BigNumber(6000); +export const FallbackEpochDuration = new BigNumber(2400); /* * Misc values */ -export const ListItemsPerPage = 50; -export const ListItemsPerBatch = 30; +export const ListItemsPerPage = 25; +export const ListItemsPerBatch = 25; export const MinBondPrecision = 3; export const MaxPayoutDays = 60; +export const MaxEraRewardPointsEras = 14; /* * Third party API keys and endpoints @@ -81,6 +71,8 @@ export const ApiEndpoints = { subscanRewardSlash: '/api/v2/scan/account/reward_slash', subscanPoolRewards: '/api/scan/nomination_pool/rewards', subscanEraStat: '/api/scan/staking/era_stat', + subscanPoolMembers: '/api/scan/nomination_pool/pool/members', + subscanPoolDetails: '/api/scan/nomination_pool/pool', }; /* @@ -90,9 +82,9 @@ export const DefaultParams = { auctionAdjust: 0, auctionMax: 0, falloff: 0.05, - stakeTarget: 0.2, maxInflation: 0.05, minInflation: 0.0001, + stakeTarget: 0.2, }; /* diff --git a/src/contexts/Account/defaults.ts b/src/contexts/Account/defaults.ts index 168934ea61..b09d389c6c 100644 --- a/src/contexts/Account/defaults.ts +++ b/src/contexts/Account/defaults.ts @@ -1,7 +1,7 @@ // Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors // SPDX-License-Identifier: Apache-2.0 -import { AccountContextInterface } from './types'; +import type { AccountContextInterface } from './types'; export const defaultAccountContext: AccountContextInterface = { // eslint-disable-next-line diff --git a/src/contexts/Account/index.tsx b/src/contexts/Account/index.tsx index b2d9384ab8..cd714a52e7 100644 --- a/src/contexts/Account/index.tsx +++ b/src/contexts/Account/index.tsx @@ -2,11 +2,11 @@ // SPDX-License-Identifier: Apache-2.0 import React, { useEffect, useRef, useState } from 'react'; -import { AnyApi, AnyMetaBatch } from 'types'; +import type { AnyApi, AnyMetaBatch } from 'types'; import { setStateWithRef } from 'Utils'; import { useApi } from '../Api'; import { defaultAccountContext } from './defaults'; -import { AccountContextInterface } from './types'; +import type { AccountContextInterface } from './types'; // context definition export const AccountContext = React.createContext<AccountContextInterface>( diff --git a/src/contexts/Account/types.ts b/src/contexts/Account/types.ts index caa779dab1..51e77dfb09 100644 --- a/src/contexts/Account/types.ts +++ b/src/contexts/Account/types.ts @@ -1,7 +1,7 @@ // Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors // SPDX-License-Identifier: Apache-2.0 -import { AnyMetaBatch } from 'types'; +import type { AnyMetaBatch } from 'types'; export interface AccountContextInterface { fetchAccountMetaBatch: (k: string, v: string[], r?: boolean) => void; diff --git a/src/contexts/ActiveAccounts/defaults.ts b/src/contexts/ActiveAccounts/defaults.ts new file mode 100644 index 0000000000..3335436090 --- /dev/null +++ b/src/contexts/ActiveAccounts/defaults.ts @@ -0,0 +1,14 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only +/* eslint-disable @typescript-eslint/no-unused-vars */ + +import type { ActiveAccountsContextInterface } from './types'; + +export const defaultActiveAccountsContext: ActiveAccountsContextInterface = { + activeAccount: null, + activeProxy: null, + activeProxyType: null, + getActiveAccount: () => null, + setActiveAccount: (address, updateLocal) => {}, + setActiveProxy: (address, updateLocal) => {}, +}; diff --git a/src/contexts/ActiveAccounts/index.tsx b/src/contexts/ActiveAccounts/index.tsx new file mode 100644 index 0000000000..c3f5ff907c --- /dev/null +++ b/src/contexts/ActiveAccounts/index.tsx @@ -0,0 +1,79 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import type { ReactNode } from 'react'; +import { createContext, useContext, useEffect, useRef, useState } from 'react'; +import type { MaybeAddress } from 'types'; +import { setStateWithRef } from '@polkadot-cloud/utils'; +import { useNetwork } from 'contexts/Network'; +import type { ActiveAccountsContextInterface, ActiveProxy } from './types'; +import { defaultActiveAccountsContext } from './defaults'; + +export const ActiveAccountsContext = + createContext<ActiveAccountsContextInterface>(defaultActiveAccountsContext); + +export const ActiveAccountsProvider = ({ + children, +}: { + children: ReactNode; +}) => { + const { network } = useNetwork(); + + // Store the currently active account. + const [activeAccount, setActiveAccountState] = useState<MaybeAddress>(null); + const activeAccountRef = useRef<string | null>(activeAccount); + + // Store the active proxy account. + const [activeProxy, setActiveProxyState] = useState<ActiveProxy>(null); + const activeProxyRef = useRef(activeProxy); + + // Setter for the active proxy account. + const setActiveProxy = (newActiveProxy: ActiveProxy, updateLocal = true) => { + if (updateLocal) + if (newActiveProxy) { + localStorage.setItem( + `${network}_active_proxy`, + JSON.stringify(newActiveProxy) + ); + } else { + localStorage.removeItem(`${network}_active_proxy`); + } + setStateWithRef(newActiveProxy, setActiveProxyState, activeProxyRef); + }; + + // Setter for the active account. + const setActiveAccount = ( + newActiveAccount: MaybeAddress, + updateLocalStorage: boolean = true + ) => { + if (updateLocalStorage) + if (newActiveAccount === null) + localStorage.removeItem(`${network}_active_account`); + else localStorage.setItem(`${network}_active_account`, newActiveAccount); + + setStateWithRef(newActiveAccount, setActiveAccountState, activeAccountRef); + }; + + // Getter for the active account. + const getActiveAccount = () => activeAccountRef.current; + + // Disconnect from the active account on network change, but don't remove local record. + useEffect(() => setActiveAccount(null, false), [network]); + + return ( + <ActiveAccountsContext.Provider + value={{ + activeAccount: activeAccountRef.current, + activeProxy: activeProxyRef.current?.address ?? null, + activeProxyType: activeProxyRef.current?.proxyType ?? null, + setActiveAccount, + getActiveAccount, + setActiveProxy, + }} + > + {children} + </ActiveAccountsContext.Provider> + ); +}; + +export const useActiveAccounts = () => useContext(ActiveAccountsContext); diff --git a/src/contexts/ActiveAccounts/types.ts b/src/contexts/ActiveAccounts/types.ts new file mode 100644 index 0000000000..dc5f98cc2a --- /dev/null +++ b/src/contexts/ActiveAccounts/types.ts @@ -0,0 +1,21 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import type { MaybeAddress } from 'types'; + +export interface ActiveAccountsContextInterface { + activeAccount: MaybeAddress; + activeProxy: MaybeAddress; + activeProxyType: string | null; + getActiveAccount: () => string | null; + setActiveAccount: ( + address: MaybeAddress, + updateLocalStorage?: boolean + ) => void; + setActiveProxy: (address: ActiveProxy, updateLocalStorage?: boolean) => void; +} + +export type ActiveProxy = { + address: MaybeAddress; + proxyType: string; +} | null; diff --git a/src/contexts/Api/defaults.ts b/src/contexts/Api/defaults.ts index 948b0a058c..2e7afaa837 100644 --- a/src/contexts/Api/defaults.ts +++ b/src/contexts/Api/defaults.ts @@ -1,41 +1,33 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only +/* eslint-disable @typescript-eslint/no-unused-vars */ import { stringToU8a } from '@polkadot/util'; -import BN from 'bn.js'; -import { NETWORKS } from 'config/networks'; -import { - APIConstants, - APIContextInterface, - ConnectionStatus, -} from 'contexts/Api/types'; +import BigNumber from 'bignumber.js'; +import type { APIConstants, APIContextInterface } from 'contexts/Api/types'; export const consts: APIConstants = { - bondDuration: 0, - maxNominations: 0, - sessionsPerEra: 0, - maxNominatorRewardedPerValidator: 0, - historyDepth: new BN(0), - maxElectingVoters: 0, - expectedBlockTime: 0, - existentialDeposit: new BN(0), + bondDuration: new BigNumber(0), + maxNominations: new BigNumber(0), + sessionsPerEra: new BigNumber(0), + maxNominatorRewardedPerValidator: new BigNumber(0), + historyDepth: new BigNumber(0), + maxElectingVoters: new BigNumber(0), + expectedBlockTime: new BigNumber(0), + epochDuration: new BigNumber(0), + existentialDeposit: new BigNumber(0), + fastUnstakeDeposit: new BigNumber(0), poolsPalletId: stringToU8a('0'), }; export const defaultApiContext: APIContextInterface = { - // eslint-disable-next-line - connect: async () => { - await new Promise((resolve) => resolve(null)); - }, - fetchDotPrice: () => {}, - // eslint-disable-next-line - switchNetwork: async (_network, _isLightClient) => { - await new Promise((resolve) => resolve(null)); - }, api: null, consts, - isLightClient: false, + chainState: undefined, isReady: false, - status: ConnectionStatus.Disconnected, - network: NETWORKS.cereMainnet, + apiStatus: 'disconnected', + isLightClient: false, + setIsLightClient: () => {}, + rpcEndpoint: '', + setRpcEndpoint: (key) => {}, }; diff --git a/src/contexts/Api/index.tsx b/src/contexts/Api/index.tsx index 45a014b3cc..2bb80f79a9 100644 --- a/src/contexts/Api/index.tsx +++ b/src/contexts/Api/index.tsx @@ -1,151 +1,226 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only import { ApiPromise, WsProvider } from '@polkadot/api'; import { ScProvider } from '@polkadot/rpc-provider/substrate-connect'; -import BN from 'bn.js'; -import { NETWORKS } from 'config/networks'; +import { makeCancelable, rmCommas } from '@polkadot-cloud/utils'; +import BigNumber from 'bignumber.js'; +import { createContext, useContext, useEffect, useState } from 'react'; +import { NetworkList } from 'config/networks'; import { - ApiEndpoints, FallbackBondingDuration, + FallbackEpochDuration, FallbackExpectedBlockTime, FallbackMaxElectingVoters, FallbackMaxNominations, FallbackNominatorRewardedPerValidator, FallbackSessionsPerEra, } from 'consts'; -import { +import type { + APIChainState, APIConstants, APIContextInterface, - ConnectionStatus, - NetworkState, + APIProviderProps, + ApiStatus, } from 'contexts/Api/types'; -import React, { useEffect, useState } from 'react'; -import { AnyApi, Network, NetworkName } from 'types'; +import type { AnyApi } from 'types'; +import { useEffectIgnoreInitial } from '@polkadot-cloud/react/hooks'; import * as defaults from './defaults'; -export const APIContext = React.createContext<APIContextInterface>( - defaults.defaultApiContext -); - -export const useApi = () => React.useContext(APIContext); - -export const APIProvider = ({ children }: { children: React.ReactNode }) => { - // provider instance state +export const APIProvider = ({ children, network }: APIProviderProps) => { + // Store povider instance. const [provider, setProvider] = useState<WsProvider | ScProvider | null>( null ); - // api instance state - const [api, setApi] = useState<ApiPromise | null>(null); - - // network state - const _name: NetworkName = - (localStorage.getItem('network') as NetworkName) ?? NetworkName.Polkadot; + // Store chain state. + const [chainState, setchainState] = useState<APIChainState>(undefined); - const [network, setNetwork] = useState<NetworkState>({ - name: _name, - meta: NETWORKS[localStorage.getItem('network') as NetworkName], - }); - - // constants state - const [consts, setConsts] = useState<APIConstants>(defaults.consts); + // Store the active RPC provider. + const initialRpcEndpoint = () => { + const local = localStorage.getItem(`${network}_rpc_endpoint`); + if (local) + if (NetworkList[network].endpoints.rpcEndpoints[local]) { + return local; + } else { + localStorage.removeItem(`${network}_rpc_endpoint`); + } - // connection status state - const [connectionStatus, setConnectionStatus] = useState<ConnectionStatus>( - ConnectionStatus.Disconnected + return NetworkList[network].endpoints.defaultRpcEndpoint; + }; + const [rpcEndpoint, setRpcEndpointState] = useState<string>( + initialRpcEndpoint() ); + // Store whether in light client mode. const [isLightClient, setIsLightClient] = useState<boolean>( - !!localStorage.getItem('isLightClient') + !!localStorage.getItem('light_client') ); - // initial connection - useEffect(() => { - if (!provider) { - const _network: NetworkName = localStorage.getItem( - 'network' - ) as NetworkName; - connect(_network, isLightClient); + // API instance state. + const [api, setApi] = useState<ApiPromise | null>(null); + + // Store network constants. + const [consts, setConsts] = useState<APIConstants>(defaults.consts); + + // Store API connection status. + const [apiStatus, setApiStatus] = useState<ApiStatus>('disconnected'); + + // Set RPC provider with local storage and validity checks. + const setRpcEndpoint = (key: string) => { + if (!NetworkList[network].endpoints.rpcEndpoints[key]) return; + localStorage.setItem(`${network}_rpc_endpoint`, key); + + setRpcEndpointState(key); + }; + + // Handle light client connection. + const handleLightClientConnection = async (Sc: AnyApi) => { + const newProvider = new ScProvider( + Sc, + NetworkList[network].endpoints.lightClient + ); + connectProvider(newProvider); + }; + + // Handle a switch in API. + let cancelFn: () => void | undefined; + + const handleApiSwitch = () => { + setApi(null); + setConsts(defaults.consts); + setchainState(undefined); + }; + + // Handle connect to API. + // Dynamically load `Sc` when user opts to use light client. + const handleConnectApi = async () => { + if (api) { + await api.disconnect(); + setApi(null); + } + // handle local light client flag. + if (isLightClient) { + localStorage.setItem('light_client', isLightClient ? 'true' : ''); + } else { + localStorage.removeItem('light_client'); } - }); - // provider event handlers - useEffect(() => { - if (provider !== null) { - provider.on('connected', () => { - setConnectionStatus(ConnectionStatus.Connected); - }); - provider.on('error', () => { - setConnectionStatus(ConnectionStatus.Disconnected); + if (isLightClient) { + handleApiSwitch(); + setApiStatus('connecting'); + + const ScPromise = makeCancelable(import('@substrate/connect')); + cancelFn = ScPromise.cancel; + ScPromise.promise.then((Sc) => { + handleLightClientConnection(Sc); }); - connectedCallback(provider); + } else { + // if not light client, directly connect. + setApiStatus('connecting'); + connectProvider(); } - }, [provider]); + }; - // connection callback - const connectedCallback = async (_provider: WsProvider | ScProvider) => { - const _api = new ApiPromise({ provider: _provider }); - await _api.isReady; - - localStorage.setItem('network', String(network.name)); - - // constants - const promises = [ - _api.consts.staking.bondingDuration, - _api.consts.staking.maxNominations, - _api.consts.staking.sessionsPerEra, - _api.consts.staking.maxNominatorRewardedPerValidator, - _api.consts.electionProviderMultiPhase.maxElectingVoters, - _api.consts.babe.expectedBlockTime, - _api.consts.balances.existentialDeposit, - _api.consts.staking.historyDepth, - _api.consts.nominationPools.palletId, - ]; - - // fetch constants - const _consts: AnyApi = await Promise.all(promises); - - // format constants - const bondDuration = _consts[0] - ? Number(_consts[0].toString()) + // Fetch chain state. Called once `provider` has been initialised. + const getChainState = async () => { + if (!provider) return; + + // initiate new api and set connected. + const newApi = await ApiPromise.create({ provider }); + + // set connected here in case event listeners have not yet initialised. + setApiStatus('connected'); + + const newChainState = await Promise.all([ + newApi.rpc.system.chain(), + newApi.rpc.system.version(), + newApi.consts.system.ss58Prefix, + ]); + + // check that chain values have been fetched before committing to state. + // could be expanded to check supported chains. + if ( + newChainState.every((c) => { + return !!c?.toHuman(); + }) + ) { + const chain = newChainState[0]?.toString(); + const version = newChainState[1]?.toString(); + const ss58Prefix = Number(newChainState[2]?.toString()); + + // set fetched chain state in storage. + setchainState({ chain, version, ss58Prefix }); + } + + // store active network in localStorage. + // NOTE: this should ideally refer to above `chain` value. + localStorage.setItem('network', String(network)); + + // Assume chain state is correct and bootstrap network consts. + connectedCallback(newApi); + }; + + // Connection callback. Called once `provider` and `api` have been initialised. + const connectedCallback = async (newApi: ApiPromise) => { + // fetch constants. + const result = await Promise.all([ + newApi.consts.staking.bondingDuration, + newApi.consts.staking.maxNominations, + newApi.consts.staking.sessionsPerEra, + newApi.consts.staking.maxNominatorRewardedPerValidator, + newApi.consts.electionProviderMultiPhase.maxElectingVoters, + newApi.consts.babe.expectedBlockTime, + newApi.consts.babe.epochDuration, + newApi.consts.balances.existentialDeposit, + newApi.consts.staking.historyDepth, + newApi.consts.fastUnstake.deposit, + newApi.consts.nominationPools.palletId, + ]); + + // format constants. + const bondDuration = result[0] + ? new BigNumber(rmCommas(result[0].toString())) : FallbackBondingDuration; - const maxNominations = _consts[1] - ? Number(_consts[1].toString()) + const maxNominations = result[1] + ? new BigNumber(rmCommas(result[1].toString())) : FallbackMaxNominations; - const sessionsPerEra = _consts[2] - ? Number(_consts[2].toString()) + const sessionsPerEra = result[2] + ? new BigNumber(rmCommas(result[2].toString())) : FallbackSessionsPerEra; - const maxNominatorRewardedPerValidator = _consts[3] - ? Number(_consts[3].toString()) + const maxNominatorRewardedPerValidator = result[3] + ? new BigNumber(rmCommas(result[3].toString())) : FallbackNominatorRewardedPerValidator; - const maxElectingVoters = _consts[4] - ? Number(_consts[4].toString()) + const maxElectingVoters = result[4] + ? new BigNumber(rmCommas(result[4].toString())) : FallbackMaxElectingVoters; - const expectedBlockTime = _consts[5] - ? Number(_consts[5].toString()) + const expectedBlockTime = result[5] + ? new BigNumber(rmCommas(result[5].toString())) : FallbackExpectedBlockTime; - const existentialDeposit = _consts[6] - ? new BN(_consts[6].toString()) - : new BN(0); + const epochDuration = result[6] + ? new BigNumber(rmCommas(result[6].toString())) + : FallbackEpochDuration; - let historyDepth; - if (_consts[7] !== undefined) { - historyDepth = new BN(_consts[7].toString()); - } else { - historyDepth = await _api.query.staking.historyDepth(); - historyDepth = new BN(historyDepth.toString()); - } + const existentialDeposit = result[7] + ? new BigNumber(rmCommas(result[7].toString())) + : new BigNumber(0); + + const historyDepth = result[8] + ? new BigNumber(rmCommas(result[8].toString())) + : new BigNumber(0); - const poolsPalletId = _consts[8] ? _consts[8].toU8a() : new Uint8Array(0); + const fastUnstakeDeposit = result[9] + ? new BigNumber(rmCommas(result[9].toString())) + : new BigNumber(0); + + const poolsPalletId = result[10] ? result[10].toU8a() : new Uint8Array(0); - setApi(_api); setConsts({ bondDuration, maxNominations, @@ -153,93 +228,81 @@ export const APIProvider = ({ children }: { children: React.ReactNode }) => { maxNominatorRewardedPerValidator, historyDepth, maxElectingVoters, + epochDuration, expectedBlockTime, poolsPalletId, existentialDeposit, + fastUnstakeDeposit, }); + setApi(newApi); }; - // connect function sets provider and updates active network. - const connect = async (_network: NetworkName, _isLightClient?: boolean) => { - const nodeEndpoint: Network = NETWORKS[_network]; - const { endpoints } = nodeEndpoint; - - let _provider: WsProvider | ScProvider; - if (_isLightClient) { - _provider = new ScProvider(endpoints.lightClient); - await _provider.connect(); - } else { - _provider = new WsProvider(endpoints.rpc); + // Connect function sets provider and updates active network. + const connectProvider = async (lc?: ScProvider) => { + const newProvider = + lc || + new WsProvider(NetworkList[network].endpoints.rpcEndpoints[rpcEndpoint]); + if (lc) { + await newProvider.connect(); } - setNetwork({ - name: _network, - meta: NETWORKS[_network], - }); - setProvider(_provider); + setProvider(newProvider); }; - // handle network switching - const switchNetwork = async ( - _network: NetworkName, - _isLightClient: boolean - ) => { - localStorage.setItem('isLightClient', _isLightClient ? 'true' : ''); - setIsLightClient(_isLightClient); - // disconnect api if not null - if (api) { - await api.disconnect(); + // Handle an initial RPC connection. + useEffect(() => { + if (!provider && !isLightClient) { + connectProvider(); } - setApi(null); - setConnectionStatus(ConnectionStatus.Connecting); - connect(_network, _isLightClient); - }; + }); - // handles fetching of DOT price and updates context state. - const fetchDotPrice = async () => { - const urls = [ - `${ApiEndpoints.priceChange}${NETWORKS[network.name].api.priceTicker}`, - ]; - const responses = await Promise.all( - urls.map((u) => fetch(u, { method: 'GET' })) - ); - const texts = await Promise.all(responses.map((res) => res.json())); - const _change = texts[0]; + // If RPC endpoint changes, and not on light client, re-connect. + useEffectIgnoreInitial(() => { + if (!isLightClient) handleConnectApi(); + }, [rpcEndpoint]); - if ( - _change.lastPrice !== undefined && - _change.priceChangePercent !== undefined - ) { - const price: string = (Math.ceil(_change.lastPrice * 100) / 100).toFixed( - 2 - ); - const change: string = ( - Math.round(_change.priceChangePercent * 100) / 100 - ).toFixed(2); - - return { - lastPrice: price, - change, - }; + // Trigger API connection handler on network or light client change. + useEffect(() => { + setRpcEndpoint(initialRpcEndpoint()); + handleConnectApi(); + return () => { + cancelFn?.(); + }; + }, [isLightClient, network]); + + // Initialise provider event handlers when provider is set. + useEffectIgnoreInitial(() => { + if (provider) { + provider.on('connected', () => { + setApiStatus('connected'); + }); + provider.on('error', () => { + setApiStatus('disconnected'); + }); + getChainState(); } - return null; - }; + }, [provider]); return ( <APIContext.Provider value={{ - connect, - fetchDotPrice, - switchNetwork, api, consts, - isReady: - connectionStatus === ConnectionStatus.Connected && api !== null, - network: network.meta, - status: connectionStatus, + chainState, + apiStatus, isLightClient, + setIsLightClient, + rpcEndpoint, + setRpcEndpoint, + isReady: apiStatus === 'connected' && api !== null, }} > {children} </APIContext.Provider> ); }; + +export const APIContext = createContext<APIContextInterface>( + defaults.defaultApiContext +); + +export const useApi = () => useContext(APIContext); diff --git a/src/contexts/Api/types.ts b/src/contexts/Api/types.ts index 7eabaf5206..e5e23c4792 100644 --- a/src/contexts/Api/types.ts +++ b/src/contexts/Api/types.ts @@ -1,15 +1,17 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import { ApiPromise } from '@polkadot/api'; -import { U8aLike } from '@polkadot/util/types'; -import BN from 'bn.js'; -import { Network, NetworkName } from '../../types'; +import type { ApiPromise } from '@polkadot/api'; +import type { U8aLike } from '@polkadot/util/types'; +import type BigNumber from 'bignumber.js'; +import type { ReactNode } from 'react'; +import type { Network, NetworkName } from '../../types'; -export enum ConnectionStatus { - Connecting = 'connecting', - Connected = 'connected', - Disconnected = 'disconnected', +export type ApiStatus = 'connecting' | 'connected' | 'disconnected'; + +export interface APIProviderProps { + children: ReactNode; + network: NetworkName; } export interface NetworkState { @@ -17,28 +19,35 @@ export interface NetworkState { meta: Network; } export interface APIConstants { - bondDuration: number; - maxNominations: number; - sessionsPerEra: number; - maxNominatorRewardedPerValidator: number; - historyDepth: BN; - maxElectingVoters: number; - expectedBlockTime: number; - existentialDeposit: BN; + bondDuration: BigNumber; + maxNominations: BigNumber; + sessionsPerEra: BigNumber; + maxNominatorRewardedPerValidator: BigNumber; + historyDepth: BigNumber; + maxElectingVoters: BigNumber; + expectedBlockTime: BigNumber; + epochDuration: BigNumber; + existentialDeposit: BigNumber; + fastUnstakeDeposit: BigNumber; poolsPalletId: U8aLike; } +export type APIChainState = + | { + chain: string; + version: string; + ss58Prefix: number; + } + | undefined; + export interface APIContextInterface { - connect: (_network: NetworkName) => Promise<void>; - fetchDotPrice: () => void; - switchNetwork: ( - _network: NetworkName, - _isLightClient: boolean - ) => Promise<void>; api: ApiPromise | null; consts: APIConstants; + chainState: APIChainState; isReady: boolean; + apiStatus: ApiStatus; isLightClient: boolean; - status: ConnectionStatus; - network: Network; + setIsLightClient: (isLightClient: boolean) => void; + rpcEndpoint: string; + setRpcEndpoint: (key: string) => void; } diff --git a/src/contexts/Balances/Utils.ts b/src/contexts/Balances/Utils.ts new file mode 100644 index 0000000000..50a8008ddb --- /dev/null +++ b/src/contexts/Balances/Utils.ts @@ -0,0 +1,20 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import type { MaybeAddress } from 'types'; +import { defaultLedger } from './defaults'; +import type { Ledger } from './types'; + +/** + * @name getLedger + * @summary Get an account's ledger record according to a key. + * @param {Ledger} ledgers + * @param {string} key + * @param { MaybeAddress } address + * @returns Ledger + */ +export const getLedger = ( + ledgers: Ledger[], + key: 'stash' | 'address', + address: MaybeAddress +): Ledger => ledgers.find((l) => l[key] === address) || defaultLedger; diff --git a/src/contexts/Balances/defaults.ts b/src/contexts/Balances/defaults.ts index 8da6f24d07..c8c19b5a47 100644 --- a/src/contexts/Balances/defaults.ts +++ b/src/contexts/Balances/defaults.ts @@ -1,53 +1,29 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only +/* eslint-disable @typescript-eslint/no-unused-vars */ -import BN from 'bn.js'; -import { - Balance, - BalanceLedger, - BalancesContextInterface, - Nominations, -} from 'contexts/Balances/types'; +import BigNumber from 'bignumber.js'; +import type { Balance, BalancesContextInterface, Ledger } from './types'; -export const balance: Balance = { - free: new BN(0), - reserved: new BN(0), - miscFrozen: new BN(0), - feeFrozen: new BN(0), - freeAfterReserve: new BN(0), +export const defaultBalancesContext: BalancesContextInterface = { + ledgers: [], + balances: [], + getStashLedger: (address) => defaultLedger, + getBalance: (address) => defaultBalance, + getLocks: (address) => [], + getNonce: (address) => 0, }; -export const ledger: BalanceLedger = { +export const defaultLedger: Ledger = { address: null, stash: null, - active: new BN(0), - total: new BN(0), + active: new BigNumber(0), + total: new BigNumber(0), unlocking: [], }; -export const nominations: Nominations = { - targets: [], - submittedIn: 0, -}; - -export const defaultBalancesContext: BalancesContextInterface = { - // eslint-disable-next-line - getAccount: (address) => null, - // eslint-disable-next-line - getAccountBalance: (address) => balance, - // eslint-disable-next-line - getLedgerForStash: (address) => ledger, - // eslint-disable-next-line - getLedgerForController: (address) => null, - // eslint-disable-next-line - getAccountLocks: (address) => [], - // eslint-disable-next-line - getBondedAccount: (address) => null, - // eslint-disable-next-line - getAccountNominations: (address) => [], - // eslint-disable-next-line - isController: (address) => false, - accounts: [], - existentialAmount: new BN(0), - ledgers: [], +export const defaultBalance: Balance = { + free: new BigNumber(0), + reserved: new BigNumber(0), + frozen: new BigNumber(0), }; diff --git a/src/contexts/Balances/index.tsx b/src/contexts/Balances/index.tsx index 1568f5c68a..21a7181feb 100644 --- a/src/contexts/Balances/index.tsx +++ b/src/contexts/Balances/index.tsx @@ -1,440 +1,232 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import { Option } from '@polkadot/types-codec'; -import BN from 'bn.js'; +import type { VoidFn } from '@polkadot/api/types'; import { - BalanceLedger, - BalancesAccount, - BalancesContextInterface, -} from 'contexts/Balances/types'; -import { ImportedAccount } from 'contexts/Connect/types'; -import React, { useEffect, useRef, useState } from 'react'; -import { AnyApi, MaybeAccount } from 'types'; -import { rmCommas, setStateWithRef } from 'Utils'; -import { useApi } from '../Api'; -import { useConnect } from '../Connect'; + addedTo, + matchedProperties, + removedFrom, + rmCommas, + setStateWithRef, +} from '@polkadot-cloud/utils'; +import BigNumber from 'bignumber.js'; +import React, { useRef, useState } from 'react'; +import { useApi } from 'contexts/Api'; +import type { AnyApi, MaybeAddress } from 'types'; +import { useEffectIgnoreInitial } from '@polkadot-cloud/react/hooks'; +import { useNetwork } from 'contexts/Network'; +import { useImportedAccounts } from 'contexts/Connect/ImportedAccounts'; +import { useOtherAccounts } from 'contexts/Connect/OtherAccounts'; +import { getLedger } from './Utils'; import * as defaults from './defaults'; - -export const BalancesContext = React.createContext<BalancesContextInterface>( - defaults.defaultBalancesContext -); - -export const useBalances = () => React.useContext(BalancesContext); - +import type { + Balances, + BalancesContextInterface, + Ledger, + UnlockChunkRaw, +} from './types'; + +/** + * @name useBalances + * @summary A provider that subscribes to an account's balances and wrap app children. + */ export const BalancesProvider = ({ children, }: { children: React.ReactNode; }) => { - const { api, isReady, network, consts } = useApi(); - const { accounts: connectAccounts, addExternalAccount } = useConnect(); - - // existential amount of unit for an account - const existentialAmount = consts.existentialDeposit; + const { api, isReady } = useApi(); + const { network } = useNetwork(); + const { accounts } = useImportedAccounts(); + const { getAccount } = useImportedAccounts(); + const { addExternalAccount } = useOtherAccounts(); - // balance accounts state - const [accounts, setAccounts] = useState<Array<BalancesAccount>>([]); - const accountsRef = useRef(accounts); + const [balances, setBalances] = useState<Balances[]>([]); + const balancesRef = useRef(balances); - // balance subscriptions state - const [unsubsBalances, setUnsubsBalances] = useState<AnyApi>([]); - const unsubsBalancesRef = useRef<AnyApi>(unsubsBalances); - - // account ledgers to separate storage - const [ledgers, setLedgers] = useState<Array<BalanceLedger>>([]); + const [ledgers, setLedgers] = useState<Ledger[]>([]); const ledgersRef = useRef(ledgers); - // ledger subscriptions state - const [unsubsLedgers, setUnsubsLedgers] = useState<AnyApi>([]); - const unsubsLedgersRef = useRef<AnyApi>(unsubsLedgers); - - // fetch account balances & ledgers. Remove or add subscriptions - useEffect(() => { - if (isReady) { - // local updated values - let _accounts = accountsRef.current; - let _ledgers = ledgersRef.current; - const _unsubsBalances = unsubsBalancesRef.current; - const _unsubsLedgers = unsubsLedgersRef.current; - - // get accounts removed: use these to unsubscribe - const accountsRemoved = accountsRef.current.filter( - (a: BalancesAccount) => - !connectAccounts.find((c: ImportedAccount) => c.address === a.address) + const unsubs = useRef<Record<string, VoidFn>>({}); + + // Handle the syncing of accounts on accounts change. + const handleSyncAccounts = () => { + // Sync removed accounts. + const handleRemovedAccounts = () => { + const removed = removedFrom(accounts, ledgersRef.current, [ + 'address', + ]).map(({ address }) => address); + + removed?.forEach((address) => { + const unsub = unsubs.current[address]; + if (unsub) unsub(); + }); + unsubs.current = Object.fromEntries( + Object.entries(unsubs.current).filter(([key]) => !removed.includes(key)) ); - // get accounts added: use these to subscribe - const accountsAdded = connectAccounts.filter( - (c: ImportedAccount) => - !accountsRef.current.find( - (a: BalancesAccount) => a.address === c.address - ) - ); - // update accounts state for removal - _accounts = accountsRef.current.filter((a: BalancesAccount) => - connectAccounts.find((c: ImportedAccount) => c.address === a.address) + }; + // Sync added accounts. + const handleAddedAccounts = () => { + addedTo(accounts, ledgersRef.current, ['address'])?.map(({ address }) => + handleSubscriptions(address) ); - // update ledgers state for removal - _ledgers = ledgersRef.current.filter((l: BalanceLedger) => - connectAccounts.find((c: ImportedAccount) => c.address === l.address) + }; + // Sync existing accounts. + const handleExistingAccounts = () => { + setStateWithRef( + matchedProperties(accounts, ledgersRef.current, ['address']), + setLedgers, + ledgersRef ); - - // update accounts state and unsubscribe if accounts have been removed - if (_accounts.length < accountsRef.current.length) { - // unsubscribe from removed balances - accountsRemoved.forEach((a: BalancesAccount) => { - const unsub = unsubsBalancesRef.current.find( - (u: AnyApi) => u.key === a.address - ); - if (unsub) { - unsub.unsub(); - // remove unsub from balances - _unsubsBalances.filter((u: AnyApi) => u.key !== a.address); - } - }); - // commit state updates - setStateWithRef(_unsubsBalances, setUnsubsBalances, unsubsBalancesRef); - setStateWithRef(_accounts, setAccounts, accountsRef); - } - - // update ledgers state and unsubscribe if accounts have been removed - if (_ledgers.length < ledgersRef.current.length) { - // unsubscribe from removed ledgers if it exists - accountsRemoved.forEach((a: BalancesAccount) => { - const unsub = unsubsLedgersRef.current.find( - (u: AnyApi) => u.key === a.address - ); - if (unsub) { - unsub.unsub(); - // remove unsub from balances - _unsubsLedgers.filter((u: AnyApi) => u.key !== a.address); - } - }); - // commit state updates - setStateWithRef(_unsubsLedgers, setUnsubsLedgers, unsubsLedgersRef); - setStateWithRef(_ledgers, setLedgers, ledgersRef); - } - - // if accounts have changed, update state with new unsubs / accounts - if (accountsAdded.length) { - // subscribe to account balances and ledgers - handleSubscribe(accountsAdded); - } - } - }, [connectAccounts, network, isReady]); - - // unsubscribe from everything on unmount - useEffect(() => { - return () => { - unsubscribeAll(); }; - }, []); - - // subscribe to added accounts - const handleSubscribe = async (accountsAdded: Array<ImportedAccount>) => { - // subscribe to balances - Promise.all( - accountsAdded.map((a: ImportedAccount) => subscribeToBalances(a.address)) - ); - // subscribe to ledgers - Promise.all( - accountsAdded.map((a: ImportedAccount) => subscribeToLedger(a.address)) - ); + handleRemovedAccounts(); + handleAddedAccounts(); + handleExistingAccounts(); }; - /* - * Unsubscrbe all balance subscriptions - */ - const unsubscribeAll = () => { - Object.values(unsubsBalancesRef.current).forEach(({ unsub }: AnyApi) => { - unsub(); - }); - Object.values(unsubsLedgersRef.current).forEach(({ unsub }: AnyApi) => { - unsub(); - }); - }; + const handleSubscriptions = async (address: string) => { + if (!api) return undefined; - // subscribe to account balances, ledger, bonded and nominators - const subscribeToBalances = async (address: string) => { - if (!api) return; - - const unsub: () => void = await api.queryMulti< - [AnyApi, AnyApi, Option<AnyApi>, Option<AnyApi>] - >( + const unsub = await api.queryMulti<AnyApi>( [ + [api.query.staking.ledger, address], [api.query.system.account, address], [api.query.balances.locks, address], - [api.query.staking.bonded, address], - [api.query.staking.nominators, address], ], - async ([{ data }, locks, bonded, nominations]): Promise<void> => { - const _account: BalancesAccount = { - address, - }; - - // get account balances - const { free, reserved, miscFrozen, feeFrozen } = data; - - // calculate free balance after app reserve - let freeAfterReserve = new BN(free).sub(existentialAmount); - freeAfterReserve = freeAfterReserve.lt(new BN(0)) - ? new BN(0) - : freeAfterReserve; - - // set account balances to context - _account.balance = { - free: free.toBn(), - reserved: reserved.toBn(), - miscFrozen: miscFrozen.toBn(), - feeFrozen: feeFrozen.toBn(), - freeAfterReserve, - }; - - // get account locks - const _locks = locks.toHuman(); - for (let i = 0; i < _locks.length; i++) { - _locks[i].amount = new BN(rmCommas(_locks[i].amount)); - } - _account.locks = _locks; - - // set account bonded (controller) or null - let _bonded = bonded.unwrapOr(null); - _bonded = - _bonded === null ? null : (_bonded.toHuman() as string | null); - _account.bonded = _bonded; - - // add bonded (controller) account as external account if not presently imported - if (_bonded) { - if ( - connectAccounts.find( - (s: ImportedAccount) => s.address === _bonded - ) === undefined - ) { - addExternalAccount(_bonded, 'system'); - } - } - - // set account nominations - let _nominations = nominations.unwrapOr(null); - if (_nominations === null) { - _nominations = defaults.nominations; - } else { - _nominations = { - targets: _nominations.targets.toHuman(), - submittedIn: _nominations.submittedIn.toHuman(), - }; - } - - _account.nominations = _nominations; - - // update account in context state - let _accounts = Object.values(accountsRef.current); - // remove stale account if it's already in list - _accounts = _accounts - .filter((a: BalancesAccount) => a.address !== address) - .concat(_account); - - setStateWithRef(_accounts, setAccounts, accountsRef); - } - ); - - const _unsubs = unsubsBalancesRef.current.concat({ - key: address, - unsub, - }); - setStateWithRef(_unsubs, setUnsubsBalances, unsubsBalancesRef); - return unsub; - }; - - const subscribeToLedger = async (address: string) => { - if (!api) return; - - const unsub: () => void = await api.queryMulti<[AnyApi]>( - [[api.query.staking.ledger, address]], - async ([l]): Promise<void> => { - let ledger: BalanceLedger; - - const _ledger = l.unwrapOr(null); - // fallback to default ledger if not present - if (_ledger !== null) { - const { stash, total, active, unlocking } = _ledger; - - // format unlocking chunks - const _unlocking = []; - for (const u of unlocking.toHuman()) { - const era = rmCommas(u.era); - const value = rmCommas(u.value); - _unlocking.push({ - era: Number(era), - value: new BN(value), - }); - } - - // add stash as external account if not present - if ( - connectAccounts.find( - (s: ImportedAccount) => s.address === stash.toHuman() - ) === undefined - ) { - addExternalAccount(stash.toHuman(), 'system'); + async ([ledger, { data: accountData, nonce }, locks]) => { + const handleLedger = () => { + const newLedger = ledger.unwrapOr(null); + + if (newLedger !== null) { + const { stash, total, active, unlocking } = newLedger; + + // add stash as external account if not present + if (!getAccount(stash.toString())) { + addExternalAccount(stash.toString(), 'system'); + } + + setStateWithRef( + Object.values([...ledgersRef.current]) + // remove stale account if it's already in list + .filter((l) => l.stash !== stash.toString()) + // add new ledger record to list. + .concat({ + address, + stash: stash.toString(), + active: new BigNumber(rmCommas(active.toString())), + total: new BigNumber(rmCommas(total.toString())), + unlocking: unlocking + .toHuman() + .map(({ era, value }: UnlockChunkRaw) => ({ + era: Number(rmCommas(era)), + value: new BigNumber(rmCommas(value)), + })), + }), + setLedgers, + ledgersRef + ); + } else { + // no ledger: remove account if it's already in list. + setStateWithRef( + Object.values([...ledgersRef.current]).filter( + (l) => l.address !== address + ), + setLedgers, + ledgersRef + ); } + }; - ledger = { + const handleAccount = () => { + const free = new BigNumber(accountData.free.toString()); + const newBalances: Balances = { address, - stash: stash.toHuman(), - active: active.toBn(), - total: total.toBn(), - unlocking: _unlocking, + nonce: nonce.toNumber(), + balance: { + free, + reserved: new BigNumber(accountData.reserved.toString()), + frozen: new BigNumber(accountData.frozen.toString()), + }, + locks: locks.toHuman().map((l: AnyApi) => ({ + ...l, + id: l.id.trim(), + amount: new BigNumber(rmCommas(l.amount)), + })), }; - // remove stale account if it's already in list, and concat. - let _ledgers = Object.values(ledgersRef.current); - _ledgers = _ledgers - .filter((_l: BalanceLedger) => _l.stash !== ledger.stash) - .concat(ledger); - - setStateWithRef(_ledgers, setLedgers, ledgersRef); - } else { - // no ledger: remove stale account if it's already in list. - let _ledgers = Object.values(ledgersRef.current); - _ledgers = _ledgers.filter( - (_l: BalanceLedger) => _l.address !== address + setStateWithRef( + Object.values(balancesRef.current) + .filter((a) => a.address !== address) + .concat(newBalances), + setBalances, + balancesRef ); - setStateWithRef(_ledgers, setLedgers, ledgersRef); - } + }; + + handleLedger(); + handleAccount(); } ); - - const _unsubs = unsubsLedgersRef.current.concat({ - key: address, - unsub, - }); - setStateWithRef(_unsubs, setUnsubsLedgers, unsubsLedgersRef); + unsubs.current[address] = unsub; return unsub; }; - // get an account's balance metadata - const getAccountBalance = (address: MaybeAccount) => { - const account = accountsRef.current.find( - (a: BalancesAccount) => a.address === address - ); - if (account === undefined) { - return defaults.balance; - } - const { balance } = account; - if (balance?.free === undefined) { - return defaults.balance; - } - return balance; - }; - - // get a stash account's ledger metadata - const getLedgerForStash = (address: MaybeAccount) => { - const ledger = ledgersRef.current.find( - (l: BalanceLedger) => l.stash === address - ); - if (ledger === undefined) { - return defaults.ledger; - } - if (ledger.stash === undefined) { - return defaults.ledger; - } - return ledger; - }; - - // get a controler account's ledger - // returns null if ledger does not exist. - const getLedgerForController = (address: MaybeAccount) => { - const ledger = ledgersRef.current.find( - (l: BalanceLedger) => l.address === address - ); - if (ledger === undefined) { - return null; - } - if (ledger.address === undefined) { - return null; + const unsubAll = () => { + for (const unsub of Object.values(unsubs.current)) { + unsub(); } - return ledger; + unsubs.current = {}; }; - // get an account's locks metadata - const getAccountLocks = (address: MaybeAccount) => { - const account = accountsRef.current.find( - (a: BalancesAccount) => a.address === address - ); - if (account === undefined) { - return []; + // fetch account balances & ledgers. Remove or add subscriptions + useEffectIgnoreInitial(() => { + if (isReady) { + handleSyncAccounts(); } + }, [accounts, network, isReady]); - const locks = account.locks ?? []; - return locks; - }; + // Unsubscribe from subscriptions on network change & unmount. + useEffectIgnoreInitial(() => { + unsubAll(); + return () => unsubAll(); + }, [network]); - // get an account's bonded (controller) account) - const getBondedAccount = (address: MaybeAccount) => { - const account = accountsRef.current.find( - (a: BalancesAccount) => a.address === address - ); - if (account === undefined) { - return null; - } - const bonded = account.bonded ?? null; - return bonded; + // Gets a ledger for a stash address. + const getStashLedger = (address: MaybeAddress) => { + return getLedger(ledgersRef.current, 'stash', address); }; - // get an account's nominations - const getAccountNominations = (address: MaybeAccount) => { - const account = accountsRef.current.find( - (a: BalancesAccount) => a.address === address - ); - if (account === undefined) { - return []; - } - const nominations = account.nominations; - if (nominations === undefined) { - return []; - } + // Gets an account's balance metadata. + const getBalance = (address: MaybeAddress) => + balancesRef.current.find((a) => a.address === address)?.balance || + defaults.defaultBalance; - const targets = nominations.targets ?? []; - return targets; - }; + // Gets an account's locks. + const getLocks = (address: MaybeAddress) => + balancesRef.current.find((a) => a.address === address)?.locks ?? []; - // get an account - const getAccount = (address: MaybeAccount) => { - const account = accountsRef.current.find( - (a: BalancesAccount) => a.address === address - ); - if (account === undefined) { - return null; - } - return account; - }; - - // check if an account is a controller account - const isController = (address: MaybeAccount) => { - const existsAsController = accountsRef.current.filter( - (a: BalancesAccount) => (a?.bonded || '') === address - ); - return existsAsController.length > 0; - }; + // Gets an account's nonce. + const getNonce = (address: MaybeAddress) => + balancesRef.current.find((a) => a.address === address)?.nonce ?? 0; return ( <BalancesContext.Provider value={{ - getAccount, - getAccountBalance, - getLedgerForStash, - getLedgerForController, - getAccountLocks, - getBondedAccount, - getAccountNominations, - isController, - existentialAmount, - accounts: accountsRef.current, ledgers: ledgersRef.current, + balances: balancesRef.current, + getStashLedger, + getBalance, + getLocks, + getNonce, }} > {children} </BalancesContext.Provider> ); }; + +export const BalancesContext = React.createContext<BalancesContextInterface>( + defaults.defaultBalancesContext +); + +export const useBalances = () => React.useContext(BalancesContext); diff --git a/src/contexts/Balances/types.ts b/src/contexts/Balances/types.ts index 9d100aedd7..99ad4a820b 100644 --- a/src/contexts/Balances/types.ts +++ b/src/contexts/Balances/types.ts @@ -1,66 +1,50 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import BN from 'bn.js'; -import { AnyApi, MaybeAccount } from 'types'; +import type BigNumber from 'bignumber.js'; +import type { MaybeAddress } from 'types'; -export interface UnlockChunk { - era: number; - value: BN; -} - -export interface BalanceLedger { - address: MaybeAccount; - stash: string | null; - active: BN; - total: BN; - unlocking: Array<UnlockChunk>; +export interface BalancesContextInterface { + ledgers: Ledger[]; + balances: Balances[]; + getStashLedger: (a: MaybeAddress) => Ledger; + getBalance: (address: MaybeAddress) => Balance; + getLocks: (address: MaybeAddress) => BalanceLock[]; + getNonce: (address: MaybeAddress) => number; } -export interface BondedAccount { - address: string; - unsub: { (): void } | null; +export interface Balances { + address?: string; + nonce?: number; + balance?: Balance; + locks?: BalanceLock[]; } -export interface Lock { - id: string; - amount: BN; - reasons: string; -} export interface Balance { - free: BN; - reserved: BN; - miscFrozen: BN; - feeFrozen: BN; - freeAfterReserve: BN; + free: BigNumber; + reserved: BigNumber; + frozen: BigNumber; } -export interface BalancesAccount { - address?: string; - balance?: Balance; - bonded?: string; - ledger?: BalanceLedger; - locks?: Array<Lock>; - nominations?: Nominations; +export interface UnlockChunkRaw { + era: string; + value: string; } - -export interface Nominations { - targets: Targets; - submittedIn: string | number; +export interface UnlockChunk { + era: number; + value: BigNumber; } -export type Targets = string[]; +export interface BalanceLock { + id: string; + amount: BigNumber; + reasons: string; +} -export interface BalancesContextInterface { - getAccount: (address: MaybeAccount) => BalancesAccount | null; - getAccountBalance: (address: MaybeAccount) => Balance; - getLedgerForStash: (address: MaybeAccount) => BalanceLedger; - getLedgerForController: (address: MaybeAccount) => BalanceLedger | null; - getAccountLocks: (address: MaybeAccount) => Array<Lock>; - getBondedAccount: (address: MaybeAccount) => string | null; - getAccountNominations: (address: MaybeAccount) => Targets; - isController: (address: MaybeAccount) => boolean; - accounts: Array<BalancesAccount>; - existentialAmount: BN; - ledgers: AnyApi; +export interface Ledger { + address: MaybeAddress; + stash: string | null; + active: BigNumber; + total: BigNumber; + unlocking: UnlockChunk[]; } diff --git a/src/contexts/Bonded/defaults.ts b/src/contexts/Bonded/defaults.ts new file mode 100644 index 0000000000..1b8d622d71 --- /dev/null +++ b/src/contexts/Bonded/defaults.ts @@ -0,0 +1,21 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only +/* eslint-disable @typescript-eslint/no-unused-vars */ + +import type { + BondedContextInterface, + Nominations, +} from 'contexts/Bonded/types'; + +export const nominations: Nominations = { + targets: [], + submittedIn: 0, +}; + +export const defaultBondedContext: BondedContextInterface = { + getAccount: (address) => null, + getBondedAccount: (address) => null, + getAccountNominations: (address) => [], + isController: (address) => false, + bondedAccounts: [], +}; diff --git a/src/contexts/Bonded/index.tsx b/src/contexts/Bonded/index.tsx new file mode 100644 index 0000000000..212d0ecc65 --- /dev/null +++ b/src/contexts/Bonded/index.tsx @@ -0,0 +1,171 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import type { VoidFn } from '@polkadot/api/types'; +import { + addedTo, + matchedProperties, + removedFrom, + setStateWithRef, +} from '@polkadot-cloud/utils'; +import React, { useEffect, useRef, useState } from 'react'; +import { useApi } from 'contexts/Api'; +import type { AnyApi, MaybeAddress } from 'types'; +import { useEffectIgnoreInitial } from '@polkadot-cloud/react/hooks'; +import { useNetwork } from 'contexts/Network'; +import { useImportedAccounts } from 'contexts/Connect/ImportedAccounts'; +import { useOtherAccounts } from 'contexts/Connect/OtherAccounts'; +import * as defaults from './defaults'; +import type { BondedAccount, BondedContextInterface } from './types'; + +export const BondedProvider = ({ children }: { children: React.ReactNode }) => { + const { network } = useNetwork(); + const { api, isReady } = useApi(); + const { accounts } = useImportedAccounts(); + const { addExternalAccount } = useOtherAccounts(); + + // Balance accounts state. + const [bondedAccounts, setBondedAccounts] = useState<BondedAccount[]>([]); + const bondedAccountsRef = useRef(bondedAccounts); + + const unsubs = useRef<Record<string, VoidFn>>({}); + + // Handle the syncing of accounts on accounts change. + const handleSyncAccounts = () => { + // Sync removed accounts. + const handleRemovedAccounts = () => { + const removed = removedFrom(accounts, bondedAccountsRef.current, [ + 'address', + ]).map(({ address }) => address); + + removed?.forEach((address) => { + const unsub = unsubs.current[address]; + if (unsub) unsub(); + }); + + unsubs.current = Object.fromEntries( + Object.entries(unsubs.current).filter(([key]) => !removed.includes(key)) + ); + }; + // Sync added accounts. + const handleAddedAccounts = () => { + addedTo(accounts, bondedAccountsRef.current, ['address'])?.map( + ({ address }) => subscribeToBondedAccount(address) + ); + }; + // Sync existing accounts. + const handleExistingAccounts = () => { + setStateWithRef( + matchedProperties(accounts, bondedAccountsRef.current, ['address']), + setBondedAccounts, + bondedAccountsRef + ); + }; + handleRemovedAccounts(); + handleAddedAccounts(); + handleExistingAccounts(); + }; + + // Handle accounts sync on connected accounts change. + useEffectIgnoreInitial(() => { + if (isReady) { + handleSyncAccounts(); + } + }, [accounts, network, isReady]); + + // Unsubscribe from subscriptions on unmount. + useEffect( + () => () => + Object.values(unsubs.current).forEach((unsub) => { + unsub(); + }), + [] + ); + + // Subscribe to account, get controller and nominations. + const subscribeToBondedAccount = async (address: string) => { + if (!api) return undefined; + + const unsub = await api.queryMulti<AnyApi>( + [ + [api.query.staking.bonded, address], + [api.query.staking.nominators, address], + ], + async ([controller, nominations]) => { + const newAccount: BondedAccount = { + address, + }; + + // set account bonded (controller) or null + let newController = controller.unwrapOr(null); + newController = + newController === null + ? null + : (newController.toHuman() as string | null); + newAccount.bonded = newController; + + // add bonded (controller) account as external account if not presently imported + if (newController) { + if (accounts.find((s) => s.address === newController) === undefined) { + addExternalAccount(newController, 'system'); + } + } + + // set account nominations. + const newNominations = nominations.unwrapOr(null); + newAccount.nominations = + newNominations === null + ? defaults.nominations + : { + targets: newNominations.targets.toHuman(), + submittedIn: newNominations.submittedIn.toHuman(), + }; + + // remove stale account if it's already in list. + const newBonded = Object.values(bondedAccountsRef.current) + .filter((a) => a.address !== address) + .concat(newAccount); + + setStateWithRef(newBonded, setBondedAccounts, bondedAccountsRef); + } + ); + + unsubs.current[address] = unsub; + return unsub; + }; + + const getBondedAccount = (address: MaybeAddress) => + bondedAccountsRef.current.find((a) => a.address === address)?.bonded || + null; + + const getAccountNominations = (address: MaybeAddress) => + bondedAccountsRef.current.find((a) => a.address === address)?.nominations + ?.targets || []; + + const getAccount = (address: MaybeAddress) => + bondedAccountsRef.current.find((a) => a.address === address) || null; + + const isController = (address: MaybeAddress) => + bondedAccountsRef.current.filter((a) => (a?.bonded || '') === address) + ?.length > 0 || false; + + return ( + <BondedContext.Provider + value={{ + getAccount, + getBondedAccount, + getAccountNominations, + isController, + bondedAccounts: bondedAccountsRef.current, + }} + > + {children} + </BondedContext.Provider> + ); +}; + +export const BondedContext = React.createContext<BondedContextInterface>( + defaults.defaultBondedContext +); + +export const useBonded = () => React.useContext(BondedContext); diff --git a/src/contexts/Bonded/types.ts b/src/contexts/Bonded/types.ts new file mode 100644 index 0000000000..7ff494e281 --- /dev/null +++ b/src/contexts/Bonded/types.ts @@ -0,0 +1,25 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import type { MaybeAddress } from 'types'; + +export interface BondedAccount { + address?: string; + bonded?: string; + nominations?: Nominations; +} + +export interface Nominations { + targets: Targets; + submittedIn: string | number; +} + +export type Targets = string[]; + +export interface BondedContextInterface { + getAccount: (address: MaybeAddress) => BondedAccount | null; + getBondedAccount: (address: MaybeAddress) => string | null; + getAccountNominations: (address: MaybeAddress) => Targets; + isController: (address: MaybeAddress) => boolean; + bondedAccounts: BondedAccount[]; +} diff --git a/src/contexts/CereStats/defaults.ts b/src/contexts/CereStats/defaults.ts index c1ec0d6005..0600df3cf7 100644 --- a/src/contexts/CereStats/defaults.ts +++ b/src/contexts/CereStats/defaults.ts @@ -1,8 +1,9 @@ -import { CereStatsContextInterface } from './types'; +import type { CereStatsContextInterface } from './types'; export const defaultCereStatsContext: CereStatsContextInterface = { // eslint-disable-next-line - fetchEraPoints: (v, e) => {}, + fetchEraPoints: (v, e) => Promise.resolve([]), payouts: [], poolClaims: [], + unclaimedPayouts: [], }; diff --git a/src/contexts/CereStats/index.tsx b/src/contexts/CereStats/index.tsx index 28072eec43..d360bb536b 100644 --- a/src/contexts/CereStats/index.tsx +++ b/src/contexts/CereStats/index.tsx @@ -1,16 +1,12 @@ -import { - ApolloClient, - gql, - InMemoryCache, - NormalizedCacheObject, -} from '@apollo/client'; +import type { NormalizedCacheObject } from '@apollo/client'; +import { ApolloClient, gql, InMemoryCache } from '@apollo/client'; import { WebSocketLink } from '@apollo/client/link/ws'; import React, { createContext, useEffect, useState } from 'react'; -import { Network } from '../../types'; -import { useApi } from '../Api'; -import { useConnect } from '../Connect'; +import { useActiveAccounts } from 'contexts/ActiveAccounts'; +import { useNetwork } from 'contexts/Network'; import { defaultCereStatsContext } from './defaults'; -import { CereStatsContextInterface } from './types'; +import type { CereStatsContextInterface } from './types'; +import type { Network, AnyJson } from '../../types'; const useApolloClient = (endpoint: Network['cereStatsEndpoint']) => { const [client, setClient] = @@ -24,12 +20,12 @@ const useApolloClient = (endpoint: Network['cereStatsEndpoint']) => { }, }); - const _client = new ApolloClient({ + const clientSetter = new ApolloClient({ link: wsLink, cache: new InMemoryCache(), }); - setClient(_client); + setClient(clientSetter); }, [endpoint]); return client; @@ -82,7 +78,7 @@ const usePayouts = ( client: ApolloClient<NormalizedCacheObject> | null, activeAccount: string | null ) => { - const [payouts, setPayouts] = useState([]); + const [payouts, setPayouts] = useState<AnyJson[]>([]); const normalizePayouts = ( payoutData: { blockNumber: number; data: string; timestamp: number }[] @@ -133,7 +129,6 @@ const usePayouts = ( }, }); - // @ts-ignore setPayouts(normalizePayouts(data.event)); }; @@ -155,10 +150,10 @@ export const CereStatsProvider = ({ }: { children: React.ReactNode; }) => { - const { network } = useApi(); - const { activeAccount } = useConnect(); + const { networkData } = useNetwork(); + const { activeAccount } = useActiveAccounts(); - const client = useApolloClient(network.cereStatsEndpoint); + const client = useApolloClient(networkData.cereStatsEndpoint); const fetchEraPoints = useFetchEraPoints(client); const payouts = usePayouts(client, activeAccount); @@ -172,6 +167,7 @@ export const CereStatsProvider = ({ fetchEraPoints, payouts, poolClaims: [], + unclaimedPayouts: [], }} > {children} diff --git a/src/contexts/CereStats/types.ts b/src/contexts/CereStats/types.ts index e6b0632d38..a16a3b9cf0 100644 --- a/src/contexts/CereStats/types.ts +++ b/src/contexts/CereStats/types.ts @@ -1,7 +1,17 @@ +export interface ActiveEraData { + era: number; + reward_point: RewardPoint; // You might want to change `any` to a more specific type if possible +} + +export interface RewardPoint { + era: number; +} + export interface CereStatsContextInterface { - fetchEraPoints: (v: string, e: number) => void; + fetchEraPoints: (v: string, e: number) => Promise<ActiveEraData[]>; payouts: any; // The Cere Stats does not currently support `poolClaims`. // We need it to maintain consistency with the `useSubscan` hook and for possible future support of `poolClaims`. poolClaims: []; + unclaimedPayouts: []; } diff --git a/src/contexts/Connect/Hooks/useImportExtension.tsx b/src/contexts/Connect/Hooks/useImportExtension.tsx deleted file mode 100644 index a06a6959f8..0000000000 --- a/src/contexts/Connect/Hooks/useImportExtension.tsx +++ /dev/null @@ -1,115 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import Keyring from '@polkadot/keyring'; -import { useApi } from 'contexts/Api'; -import { useExtensions } from 'contexts/Extensions'; -import { ExtensionInteface } from 'contexts/Extensions/types'; -import { AnyFunction } from 'types'; -import { isValidAddress } from 'Utils'; -import { ExtensionAccount, ExternalAccount, ImportedAccount } from '../types'; -import { - addToLocalExtensions, - getActiveAccountLocal, - getInExternalAccounts, -} from '../Utils'; - -export const useImportExtension = () => { - const { network } = useApi(); - const { setExtensionStatus } = useExtensions(); - - // Handles importing of an extension. - // - // Adds extension metadata to state and updates local storage with - // connected extensions. Calls separate method to handle account importing. - const handleImportExtension = ( - id: string, - accounts: Array<ExtensionAccount>, - extension: ExtensionInteface, - injected: Array<ExtensionAccount>, - forget: (a: Array<ExternalAccount>) => void - ) => { - // update extensions status to connected. - setExtensionStatus(id, 'connected'); - // update local active extensions - addToLocalExtensions(id); - - if (injected.length) { - return handleInjectedAccounts(id, accounts, extension, injected, forget); - } - return []; - }; - - // Handles importing of extension accounts. - // - // Gets accounts to be imported and commits them to state. - const handleInjectedAccounts = ( - id: string, - accounts: Array<ExtensionAccount>, - extension: ExtensionInteface, - injected: Array<ExtensionAccount>, - forget: (a: Array<ExternalAccount>) => void - ) => { - // set network ss58 format - const keyring = new Keyring(); - keyring.setSS58Format(network.ss58); - - // remove accounts that do not contain correctly formatted addresses. - injected = injected.filter((i: ExtensionAccount) => { - return isValidAddress(i.address); - }); - - // reformat addresses to ensure correct ss58 format - injected.forEach(async (account: ExtensionAccount) => { - const { address } = keyring.addFromAddress(account.address); - account.address = address; - return account; - }); - - // remove injected if they exist in local external accounts - forget(getInExternalAccounts(injected, network)); - - // remove accounts that have already been injected via another extension. - injected = injected.filter( - (i: ExtensionAccount) => - !accounts.map((j: ImportedAccount) => j.address).includes(i.address) - ); - - // format account properties. - injected = injected.map((a: ExtensionAccount) => { - return { - address: a.address, - source: id, - name: a.name, - signer: extension.signer, - }; - }); - return injected; - }; - - // Get active extension account. - // - // checks if the local active account is in the extension. - const getActiveExtensionAccount = (injected: Array<ImportedAccount>) => - injected.find( - (a: ExtensionAccount) => a.address === getActiveAccountLocal(network) - ) ?? null; - - // Connect active extension account. - // - // Connects to active account if it is provided. - const connectActiveExtensionAccount = ( - account: ImportedAccount | null, - callback: AnyFunction - ) => { - if (account !== null) { - callback(account); - } - }; - - return { - handleImportExtension, - getActiveExtensionAccount, - connectActiveExtensionAccount, - }; -}; diff --git a/src/contexts/Connect/ImportedAccounts/defaults.ts b/src/contexts/Connect/ImportedAccounts/defaults.ts new file mode 100644 index 0000000000..3d8bebec5d --- /dev/null +++ b/src/contexts/Connect/ImportedAccounts/defaults.ts @@ -0,0 +1,14 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only +/* eslint-disable @typescript-eslint/no-unused-vars */ + +import type { ImportedAccountsContextInterface } from './types'; + +export const defaultImportedAccountsContext: ImportedAccountsContextInterface = + { + accounts: [], + getAccount: (address) => null, + isReadOnlyAccount: (address) => false, + accountHasSigner: (address) => false, + requiresManualSign: (address) => false, + }; diff --git a/src/contexts/Connect/ImportedAccounts/index.tsx b/src/contexts/Connect/ImportedAccounts/index.tsx new file mode 100644 index 0000000000..139c8594db --- /dev/null +++ b/src/contexts/Connect/ImportedAccounts/index.tsx @@ -0,0 +1,71 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import type { ReactNode } from 'react'; +import { createContext, useContext } from 'react'; +import type { MaybeAddress } from 'types'; +import type { ExternalAccount } from '@polkadot-cloud/react/types'; +import { ManualSigners } from 'consts'; +import { useExtensionAccounts } from '@polkadot-cloud/react/hooks'; +import { defaultImportedAccountsContext } from './defaults'; +import type { ImportedAccountsContextInterface } from './types'; +import { useOtherAccounts } from '../OtherAccounts'; + +export const ImportedAccountsContext = + createContext<ImportedAccountsContextInterface>( + defaultImportedAccountsContext + ); + +export const ImportedAccountsProvider = ({ + children, +}: { + children: ReactNode; +}) => { + const { otherAccounts } = useOtherAccounts(); + const { extensionAccounts } = useExtensionAccounts(); + + const allAccounts = extensionAccounts.concat(otherAccounts); + + const getAccount = (who: MaybeAddress) => + allAccounts.find(({ address }) => address === who) || null; + + const isReadOnlyAccount = (address: MaybeAddress) => { + const account = getAccount(address) ?? {}; + + if (Object.prototype.hasOwnProperty.call(account, 'addedBy')) { + const { addedBy } = account as ExternalAccount; + return addedBy === 'user'; + } + return false; + }; + + // Checks whether an account can sign transactions + const accountHasSigner = (address: MaybeAddress) => + allAccounts.find( + (a) => a.address === address && a.source !== 'external' + ) !== undefined; + + // Checks whether an account needs manual signing. This is the case for Ledger accounts, + // transactions of which cannot be automatically signed by a provided `signer` as is the case with + // extensions. + const requiresManualSign = (address: MaybeAddress) => + allAccounts.find( + (a) => a.address === address && ManualSigners.includes(a.source) + ) !== undefined; + + return ( + <ImportedAccountsContext.Provider + value={{ + accounts: allAccounts, + getAccount, + isReadOnlyAccount, + accountHasSigner, + requiresManualSign, + }} + > + {children} + </ImportedAccountsContext.Provider> + ); +}; + +export const useImportedAccounts = () => useContext(ImportedAccountsContext); diff --git a/src/contexts/Connect/ImportedAccounts/types.ts b/src/contexts/Connect/ImportedAccounts/types.ts new file mode 100644 index 0000000000..1743dd74c5 --- /dev/null +++ b/src/contexts/Connect/ImportedAccounts/types.ts @@ -0,0 +1,16 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import type { + ExtensionAccount, + ImportedAccount, +} from '@polkadot-cloud/react/types'; +import type { MaybeAddress } from 'types'; + +export interface ImportedAccountsContextInterface { + accounts: ImportedAccount[]; + getAccount: (address: MaybeAddress) => ExtensionAccount | null; + isReadOnlyAccount: (address: MaybeAddress) => boolean; + accountHasSigner: (address: MaybeAddress) => boolean; + requiresManualSign: (address: MaybeAddress) => boolean; +} diff --git a/src/contexts/Connect/OtherAccounts/defaults.ts b/src/contexts/Connect/OtherAccounts/defaults.ts new file mode 100644 index 0000000000..7629215a2b --- /dev/null +++ b/src/contexts/Connect/OtherAccounts/defaults.ts @@ -0,0 +1,16 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only +/* eslint-disable @typescript-eslint/no-unused-vars */ + +import type { OtherAccountsContextInterface } from './types'; + +export const defaultOtherAccountsContext: OtherAccountsContextInterface = { + addExternalAccount: (a, b) => {}, + addOtherAccounts: (a) => {}, + renameOtherAccount: (a, n) => {}, + importLocalOtherAccounts: (n) => {}, + forgetOtherAccounts: (a) => {}, + forgetExternalAccounts: (a) => {}, + otherAccounts: [], + accountsInitialised: false, +}; diff --git a/src/contexts/Connect/OtherAccounts/index.tsx b/src/contexts/Connect/OtherAccounts/index.tsx new file mode 100644 index 0000000000..b4ce6519e5 --- /dev/null +++ b/src/contexts/Connect/OtherAccounts/index.tsx @@ -0,0 +1,277 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import type { ReactNode } from 'react'; +import { createContext, useContext, useEffect, useRef, useState } from 'react'; +import { + useEffectIgnoreInitial, + useExtensions, + useExtensionAccounts, +} from '@polkadot-cloud/react/hooks'; +import { + getLocalLedgerAccounts, + getLocalVaultAccounts, +} from 'contexts/Hardware/Utils'; +import type { AnyFunction, MaybeAddress, NetworkName } from 'types'; +import { ellipsisFn, setStateWithRef } from '@polkadot-cloud/utils'; +import { useNetwork } from 'contexts/Network'; +import { useActiveAccounts } from 'contexts/ActiveAccounts'; +import Keyring from '@polkadot/keyring'; +import type { + ExternalAccount, + ImportedAccount, +} from '@polkadot-cloud/react/types'; +import { + getActiveAccountLocal, + getLocalExternalAccounts, + removeLocalExternalAccounts, +} from '../Utils'; +import type { OtherAccountsContextInterface } from './types'; +import { defaultOtherAccountsContext } from './defaults'; + +export const OtherAccountsContext = + createContext<OtherAccountsContextInterface>(defaultOtherAccountsContext); + +export const OtherAccountsProvider = ({ + children, +}: { + children: ReactNode; +}) => { + const { + network, + networkData: { ss58 }, + } = useNetwork(); + const { checkingInjectedWeb3 } = useExtensions(); + const { extensionAccountsSynced } = useExtensionAccounts(); + const { activeAccount, setActiveAccount } = useActiveAccounts(); + + // Store whether other (non-extension) accounts have been initialised. + const [otherAccountsSynced, setOtherAccountsSynced] = + useState<boolean>(false); + + // Store other (non-extension) accounts list. + const [otherAccounts, setOtherAccounts] = useState<ImportedAccount[]>([]); + const otherAccountsRef = useRef(otherAccounts); + + // Store unsubscribe handlers for connected extensions. + const unsubs = useRef<Record<string, AnyFunction>>({}); + + // Store whether all accounts have been synced. + const [accountsInitialised, setAccountsInitialised] = + useState<boolean>(false); + + // Handle forgetting of an imported other account. + const forgetOtherAccounts = (forget: ImportedAccount[]) => { + // Unsubscribe and remove unsub from context ref. + if (forget.length) { + for (const { address } of forget) { + if (otherAccountsRef.current.find((a) => a.address === address)) { + const unsub = unsubs.current[address]; + if (unsub) { + unsub(); + delete unsubs.current[address]; + } + } + } + // Remove forgotten accounts from context state. + setStateWithRef( + [...otherAccountsRef.current].filter( + (a) => + forget.find(({ address }) => address === a.address) === undefined + ), + setOtherAccounts, + otherAccountsRef + ); + // If the currently active account is being forgotten, disconnect. + if (forget.find(({ address }) => address === activeAccount) !== undefined) + setActiveAccount(null); + } + }; + + // Checks `localStorage` for previously added accounts from the provided source, and adds them to + // `accounts` state. if local active account is present, it will also be assigned as active. + // Accounts are ignored if they are already imported through an extension. + const importLocalOtherAccounts = ( + getter: (n: NetworkName) => ImportedAccount[] + ) => { + // Get accounts from provided `getter` function. The resulting array of accounts must contain an + // `address` field. + let localAccounts = getter(network); + + if (localAccounts.length) { + const activeAccountInSet = + localAccounts.find( + ({ address }) => address === getActiveAccountLocal(network, ss58) + ) ?? null; + + // remove already-imported accounts. + localAccounts = localAccounts.filter( + (l) => + otherAccountsRef.current.find( + ({ address }) => address === l.address + ) === undefined + ); + + // set active account for networkData. + if (activeAccountInSet) { + setActiveAccount(activeAccountInSet?.address || null); + } + // add accounts to imported. + addOtherAccounts(localAccounts); + } + }; + + // Renames an other account. + const renameOtherAccount = (address: MaybeAddress, newName: string) => { + setStateWithRef( + [...otherAccountsRef.current].map((a) => + a.address !== address + ? a + : { + ...a, + name: newName, + } + ), + setOtherAccounts, + otherAccountsRef + ); + }; + + // Adds an external account (non-wallet) to accounts. + const addExternalAccount = (address: string, addedBy: string) => { + // ensure account is formatted correctly + const keyring = new Keyring(); + keyring.setSS58Format(ss58); + const formatted = keyring.addFromAddress(address).address; + + const newAccount = { + address: formatted, + network, + name: ellipsisFn(address), + source: 'external', + addedBy, + }; + + // get all external accounts from localStorage. + const localExternalAccounts = getLocalExternalAccounts(); + const existsLocal = localExternalAccounts.find( + (l) => l.address === address && l.network === network + ); + + // check that address is not sitting in imported accounts (currently cannot check which + // network). + const existsImported = otherAccountsRef.current.find( + (a) => a.address === address + ); + + // add external account if not there already. + if (!existsLocal && !existsImported) { + localStorage.setItem( + 'external_accounts', + JSON.stringify(localExternalAccounts.concat(newAccount)) + ); + + // add external account to imported accounts + addOtherAccounts([newAccount]); + } else if (existsLocal && existsLocal.addedBy !== 'system') { + // the external account needs to change to `system` so it cannot be removed. This will replace + // the whole entry. + localStorage.setItem( + 'external_accounts', + JSON.stringify( + localExternalAccounts.map((item) => + item.address !== address ? item : newAccount + ) + ) + ); + // re-sync account state. + setStateWithRef( + [...otherAccountsRef.current].map((item) => + item.address !== newAccount.address ? item : newAccount + ), + setOtherAccounts, + otherAccountsRef + ); + } + }; + + // Get any external accounts and remove from localStorage. + const forgetExternalAccounts = (forget: ImportedAccount[]) => { + if (!forget.length) return; + removeLocalExternalAccounts( + network, + forget.filter((i) => 'network' in i) as ExternalAccount[] + ); + + // If the currently active account is being forgotten, disconnect. + if (forget.find((a) => a.address === activeAccount) !== undefined) + setActiveAccount(null); + }; + + // Re-sync other accounts on network switch. Waits for `injectedWeb3` to be injected. + useEffect(() => { + if (!checkingInjectedWeb3) { + // unsubscribe from all accounts and reset state. + unsubscribe(); + setStateWithRef([], setOtherAccounts, otherAccountsRef); + } + return () => unsubscribe(); + }, [network, checkingInjectedWeb3]); + + // Unsubscrbe all account subscriptions. + const unsubscribe = () => { + Object.values(unsubs.current).forEach((unsub) => { + unsub(); + }); + }; + + // Add other accounts to context state. + const addOtherAccounts = (a: ImportedAccount[]) => { + setStateWithRef( + [...otherAccountsRef.current].concat(a), + setOtherAccounts, + otherAccountsRef + ); + }; + + // Once extensions are fully initialised, fetch accounts from other sources. + useEffectIgnoreInitial(() => { + if (extensionAccountsSynced) { + // Fetch accounts from supported hardware wallets. + importLocalOtherAccounts(getLocalVaultAccounts); + importLocalOtherAccounts(getLocalLedgerAccounts); + + // Mark hardware wallets as initialised. + setOtherAccountsSynced(true); + + // Finally, fetch any read-only accounts that have been added by `system` or `user`. + importLocalOtherAccounts(getLocalExternalAccounts); + } + }, [extensionAccountsSynced]); + + // Account fetching complete, mark accounts as initialised. Does not include read only accounts. + useEffectIgnoreInitial(() => { + if (extensionAccountsSynced && otherAccountsSynced === true) { + setAccountsInitialised(true); + } + }, [extensionAccountsSynced, otherAccountsSynced]); + + return ( + <OtherAccountsContext.Provider + value={{ + addExternalAccount, + addOtherAccounts, + renameOtherAccount, + importLocalOtherAccounts, + forgetOtherAccounts, + forgetExternalAccounts, + accountsInitialised, + otherAccounts: otherAccountsRef.current, + }} + > + {children} + </OtherAccountsContext.Provider> + ); +}; + +export const useOtherAccounts = () => useContext(OtherAccountsContext); diff --git a/src/contexts/Connect/OtherAccounts/types.ts b/src/contexts/Connect/OtherAccounts/types.ts new file mode 100644 index 0000000000..58a1a3aaca --- /dev/null +++ b/src/contexts/Connect/OtherAccounts/types.ts @@ -0,0 +1,16 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import type { ImportedAccount } from '@polkadot-cloud/react/types'; +import type { MaybeAddress, NetworkName } from 'types'; + +export interface OtherAccountsContextInterface { + addExternalAccount: (a: string, addedBy: string) => void; + addOtherAccounts: (a: ImportedAccount[]) => void; + renameOtherAccount: (a: MaybeAddress, n: string) => void; + importLocalOtherAccounts: (g: (n: NetworkName) => ImportedAccount[]) => void; + forgetOtherAccounts: (a: ImportedAccount[]) => void; + forgetExternalAccounts: (a: ImportedAccount[]) => void; + accountsInitialised: boolean; + otherAccounts: ImportedAccount[]; +} diff --git a/src/contexts/Connect/Utils.ts b/src/contexts/Connect/Utils.ts index a5a0bedf4a..70b293fc8b 100644 --- a/src/contexts/Connect/Utils.ts +++ b/src/contexts/Connect/Utils.ts @@ -1,12 +1,13 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only import Keyring from '@polkadot/keyring'; -import { Network } from 'types'; -import { localStorageOrDefault } from 'Utils'; -import { ExtensionAccount, ExternalAccount, ImportedAccount } from './types'; - -// extension utils +import { localStorageOrDefault } from '@polkadot-cloud/utils'; +import type { + ExtensionAccount, + ExternalAccount, +} from '@polkadot-cloud/react/types'; +import type { NetworkName } from 'types'; // adds an extension to local `active_extensions` export const addToLocalExtensions = (id: string) => { @@ -26,101 +27,76 @@ export const addToLocalExtensions = (id: string) => { } }; -// removes extension from local `active_extensions` -export const removeFromLocalExtensions = (id: string) => { - let localExtensions = localStorageOrDefault<string[]>( - `active_extensions`, - [], - true - ); - if (Array.isArray(localExtensions)) { - localExtensions = localExtensions.filter((l: string) => l !== id); - localStorage.setItem('active_extensions', JSON.stringify(localExtensions)); - } -}; - -// check if an extension exists in local `active_extensions`. -export const extensionIsLocal = (id: string) => { - // connect if extension has been connected to previously - const localExtensions = localStorageOrDefault<string[]>( - `active_extensions`, - [], - true - ); - let foundExtensionLocally = false; - if (Array.isArray(localExtensions)) { - foundExtensionLocally = - localExtensions.find((l: string) => l === id) !== undefined; - } - return foundExtensionLocally; -}; - // account utils // gets local `activeAccount` for a network -export const getActiveAccountLocal = (network: Network) => { +export const getActiveAccountLocal = (network: NetworkName, ss58: number) => { const keyring = new Keyring(); - keyring.setSS58Format(network.ss58); - let _activeAccount = localStorageOrDefault( - `${network.name.toLowerCase()}_active_account`, - null - ); - if (_activeAccount !== null) { - _activeAccount = keyring.addFromAddress(_activeAccount).address; + keyring.setSS58Format(ss58); + let account = localStorageOrDefault(`${network}_active_account`, null); + if (account !== null) { + account = keyring.addFromAddress(account).address; } - return _activeAccount; + return account; }; // gets local external accounts, formatting their addresses // using active network ss58 format. -export const getLocalExternalAccounts = ( - network: Network, - activeNetworkOnly = false -) => { - let localExternalAccounts = localStorageOrDefault<Array<ExternalAccount>>( +export const getLocalExternalAccounts = (network?: NetworkName) => { + let localAccounts = localStorageOrDefault<ExternalAccount[]>( 'external_accounts', [], true - ) as Array<ExternalAccount>; - if (activeNetworkOnly) { - localExternalAccounts = localExternalAccounts.filter( - (l: ExternalAccount) => l.network === network.name - ); + ) as ExternalAccount[]; + if (network) { + localAccounts = localAccounts.filter((l) => l.network === network); } - return localExternalAccounts; + return localAccounts; }; // gets accounts that exist in local `external_accounts` export const getInExternalAccounts = ( - accounts: Array<ExtensionAccount>, - network: Network + accounts: ExtensionAccount[], + network: NetworkName ) => { - const localExternalAccounts = getLocalExternalAccounts(network, true); + const localExternalAccounts = getLocalExternalAccounts(network); + return ( localExternalAccounts.filter( - (l: ExternalAccount) => - (accounts || []).find( - (a: ExtensionAccount) => a.address === l.address - ) !== undefined && l.addedBy === 'system' + (a) => (accounts || []).find((b) => b.address === a.address) !== undefined ) || [] ); }; // removes supplied accounts from local `external_accounts`. export const removeLocalExternalAccounts = ( - network: Network, - accounts: Array<ExternalAccount> + network: NetworkName, + accounts: ExternalAccount[] ) => { - let localExternalAccounts = getLocalExternalAccounts(network, true); + if (!accounts.length) return; + + let localExternalAccounts = getLocalExternalAccounts(network); localExternalAccounts = localExternalAccounts.filter( - (l: ExternalAccount) => - accounts.find( - (a: ImportedAccount) => - a.address === l.address && l.network === network.name - ) === undefined + (a) => + accounts.find((b) => b.address === a.address && a.network === network) === + undefined ); localStorage.setItem( 'external_accounts', JSON.stringify(localExternalAccounts) ); }; + +export const formatAccountSs58 = (address: string, ss58: number) => { + try { + const keyring = new Keyring(); + keyring.setSS58Format(ss58); + const formatted = keyring.addFromAddress(address).address; + if (formatted !== address) { + return formatted; + } + return null; + } catch (e) { + return null; + } +}; diff --git a/src/contexts/Connect/defaults.ts b/src/contexts/Connect/defaults.ts deleted file mode 100644 index 0fb2dba0d3..0000000000 --- a/src/contexts/Connect/defaults.ts +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { ConnectContextInterface } from 'contexts/Connect/types'; - -export const defaultConnectContext: ConnectContextInterface = { - // eslint-disable-next-line - formatAccountSs58: (a: string) => null, - // eslint-disable-next-line - connectExtensionAccounts: (e) => {}, - // eslint-disable-next-line - getAccount: (a) => null, - // eslint-disable-next-line - connectToAccount: (a) => {}, - disconnectFromAccount: () => {}, - // eslint-disable-next-line - addExternalAccount: (a, b) => {}, - getActiveAccount: () => null, - // eslint-disable-next-line - accountHasSigner: (a) => false, - // eslint-disable-next-line - isReadOnlyAccount: (a) => false, - // eslint-disable-next-line - forgetAccounts: (a) => {}, - accounts: [], - activeAccount: null, - activeAccountMeta: null, -}; diff --git a/src/contexts/Connect/index.tsx b/src/contexts/Connect/index.tsx deleted file mode 100644 index d476498ef1..0000000000 --- a/src/contexts/Connect/index.tsx +++ /dev/null @@ -1,490 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import Keyring from '@polkadot/keyring'; -import { DappName } from 'consts'; -import { useApi } from 'contexts/Api'; -import { - ConnectContextInterface, - ExtensionAccount, - ExternalAccount, - ImportedAccount, -} from 'contexts/Connect/types'; -import { useExtensions } from 'contexts/Extensions'; -import { Extension, ExtensionInteface } from 'contexts/Extensions/types'; -import React, { useEffect, useRef, useState } from 'react'; -import { AnyApi, MaybeAccount } from 'types'; -import { clipAddress, localStorageOrDefault, setStateWithRef } from 'Utils'; -import { defaultConnectContext } from './defaults'; -import { useImportExtension } from './Hooks/useImportExtension'; -import { - extensionIsLocal, - getActiveAccountLocal, - getLocalExternalAccounts, - removeFromLocalExtensions, - removeLocalExternalAccounts, -} from './Utils'; - -export const ConnectContext = React.createContext<ConnectContextInterface>( - defaultConnectContext -); - -export const useConnect = () => React.useContext(ConnectContext); - -export const ConnectProvider = ({ - children, -}: { - children: React.ReactNode; -}) => { - const { network } = useApi(); - const { - setExtensionStatus, - extensionsFetched, - setExtensionsFetched, - extensions, - } = useExtensions(); - const { - handleImportExtension, - getActiveExtensionAccount, - connectActiveExtensionAccount, - } = useImportExtension(); - - // store accounts list - const [accounts, setAccounts] = useState<Array<ImportedAccount>>([]); - const accountsRef = useRef(accounts); - - // store the currently active account - const [activeAccount, _setActiveAccount] = useState<string | null>(null); - const activeAccountRef = useRef<string | null>(activeAccount); - - // store the currently active account metadata - const [activeAccountMeta, setActiveAccountMeta] = - useState<ImportedAccount | null>(null); - const activeAccountMetaRef = useRef(activeAccountMeta); - - // store unsubscribe handler for connected extensions - const [unsubscribe, setUnsubscribe] = useState<AnyApi>([]); - const unsubscribeRef = useRef(unsubscribe); - - /* re-sync extensions accounts on network switch - * do this if activeAccount is present. - * if activeAccount is present, and extensions have for some - * reason forgot the site, then all pop-ups will be summoned - * here. - */ - useEffect(() => { - // unsubscribe from all accounts and reset state - unsubscribeAll(); - setStateWithRef(null, _setActiveAccount, activeAccountRef); - setStateWithRef([], setAccounts, accountsRef); - setStateWithRef(null, setActiveAccountMeta, activeAccountMetaRef); - setExtensionsFetched(false); - - // get active extensions - const localExtensions = localStorageOrDefault( - `active_extensions`, - [], - true - ); - // if extensions have been fetched, - // get accounts if extensions exist and - // local extensions exist (previously connected). - if (extensions) { - if (extensions.length && localExtensions.length) { - connectActiveExtensions(); - } else { - setExtensionsFetched(true); - } - } - return () => { - unsubscribeAll(); - }; - }, [extensions?.length, network]); - - // once extension accounts are synced, fetch - // any external accounts present in localStorage. - useEffect(() => { - if (extensionsFetched) importExternalAccounts(); - }, [extensionsFetched]); - - /* - * Unsubscrbe all account subscriptions - */ - const unsubscribeAll = () => { - unsubscribeRef.current.forEach(({ unsub }: AnyApi) => unsub()); - }; - - /* - * Unsubscrbe from some account subscriptions and update the resulting state. - */ - const forgetAccounts = (forget: Array<ExternalAccount>) => { - if (!forget.length) return; - const addresses = forget.map((a: ExternalAccount) => a.address); - - // unsubscribe from provided addresses - Object.values( - unsubscribeRef.current.filter((f: AnyApi) => addresses.includes(f.key)) - ).forEach(({ unsub }: AnyApi) => unsub()); - - // filter addresses from current unsubs - const unsubsNew = unsubscribeRef.current.filter( - (f: AnyApi) => !addresses.includes(f.key) - ); - - // if active account is being forgotten, disconnect - const activeAccountUnsub = forget.find( - (a: ExternalAccount) => a.address === activeAccount - ); - if (activeAccountUnsub !== undefined) { - setStateWithRef(null, setActiveAccount, activeAccountRef); - setStateWithRef(null, setActiveAccountMeta, activeAccountMetaRef); - } - - // remove forgotten external accounts from localStorage - removeLocalExternalAccounts(network, forget); - - // update accounts - const accountsNew = accountsRef.current.filter( - (a: ImportedAccount) => - forget.find((e: ExternalAccount) => e.address === a.address) === - undefined - ); - - // update accounts and corresponding unsubs - setStateWithRef(accountsNew, setAccounts, accountsRef); - setStateWithRef(unsubsNew, setUnsubscribe, unsubscribeRef); - }; - - /* importExternalAccounts - * checks previously imported read-only accounts from - * localStorage and adds them to `accounts` state. - * if local active account is present, it will also be - * assigned as active. - * Should be called AFTER extension accounts are imported, as - * to not replace an extension account by an external account. - */ - const importExternalAccounts = () => { - // import any local external accounts - let localExternalAccounts = getLocalExternalAccounts(network, true); - - if (localExternalAccounts.length) { - // get and format active account if present - const activeAccountLocal = getActiveAccountLocal(network); - - const activeAccountIsExternal = - localExternalAccounts.find( - (a: ImportedAccount) => a.address === activeAccountLocal - ) ?? null; - - // remove already-imported accounts (extensions may have already imported) - localExternalAccounts = localExternalAccounts.filter( - (l: ExternalAccount) => - accountsRef.current.find( - (a: ImportedAccount) => a.address === l.address - ) === undefined - ); - - // set active account for network - if (activeAccountIsExternal) { - connectToAccount(activeAccountIsExternal); - } - // add external accounts to imported - setStateWithRef( - [...accountsRef.current].concat(localExternalAccounts), - setAccounts, - accountsRef - ); - } - }; - - /* connectActiveExtensions - * Connects to extensions that already have been connected - * to and stored in localStorage. - * Loop through extensions and connect to accounts. - * If `activeAccount` exists locally, we wait until all - * extensions are looped before connecting to it; there is - * no guarantee it still exists - must explicitly find it. - */ - const connectActiveExtensions = async () => { - const keyring = new Keyring(); - keyring.setSS58Format(network.ss58); - - // iterate extensions and add accounts to state - const total = extensions?.length ?? 0; - let activeWalletAccount: ImportedAccount | null = null; - - if (!extensions) { - return; - } - - let i = 0; - extensions.forEach(async (e: Extension) => { - i++; - const { id, enable } = e; - - // if extension is found locally, subscribe to accounts - if (extensionIsLocal(id)) { - try { - // summons extension popup - const extension: ExtensionInteface = await enable(DappName); - - if (extension !== undefined) { - const unsub = (await extension.accounts.subscribe( - (injected: ExtensionAccount[]) => { - if (injected) { - injected = handleImportExtension( - id, - accountsRef.current, - extension, - injected, - forgetAccounts - ); - // store active wallet account if found in this extension - if (!activeWalletAccount) { - activeWalletAccount = getActiveExtensionAccount(injected); - } - // set active account for network on final extension - if (i === total && activeAccountRef.current === null) { - connectActiveExtensionAccount( - activeWalletAccount, - connectToAccount - ); - } - // concat accounts and store - if (injected.length) { - setStateWithRef( - [...accountsRef.current].concat(injected), - setAccounts, - accountsRef - ); - } - } - } - )) as () => void; - - // update context state - setStateWithRef( - [...unsubscribeRef.current].concat({ - key: id, - unsub, - }), - setUnsubscribe, - unsubscribeRef - ); - } - } catch (err) { - handleExtensionError(id, String(err)); - } - } - - // set extension fetched to allow external accounts - // to be imported. - if (i === total) { - setExtensionsFetched(true); - } - }); - }; - - /* connectExtensionAccounts - * Similar to the above but only connects to a single extension. - * This is invoked by the user by clicking on an extension. - * If activeAccount is not found here, it is simply ignored. - */ - const connectExtensionAccounts = async (e: Extension) => { - const keyring = new Keyring(); - keyring.setSS58Format(network.ss58); - const { id, enable } = e; - - try { - // summons extension popup - const extension: ExtensionInteface = await enable(DappName); - - if (extension !== undefined) { - // subscribe to accounts - const unsub = (await extension.accounts.subscribe( - (injected: ExtensionAccount[]) => { - if (injected) { - injected = handleImportExtension( - id, - accountsRef.current, - extension, - injected, - forgetAccounts - ); - // set active account for network if not yet set - if (activeAccountRef.current === null) { - connectActiveExtensionAccount( - getActiveExtensionAccount(injected), - connectToAccount - ); - } - // concat accounts and store - setStateWithRef( - [...accountsRef.current].concat(injected), - setAccounts, - accountsRef - ); - } - } - )) as () => void; - - // update context state - setStateWithRef( - [...unsubscribeRef.current].concat({ - key: id, - unsub, - }), - setUnsubscribe, - unsubscribeRef - ); - } - } catch (err) { - handleExtensionError(id, String(err)); - } - }; - - const handleExtensionError = (id: string, err: string) => { - // authentication error (extension not enabled) - if (err.substring(0, 9) === 'AuthError') { - removeFromLocalExtensions(id); - setExtensionStatus(id, 'not_authenticated'); - } - - // extension not found (does not exist) - if (err.substring(0, 17) === 'NotInstalledError') { - removeFromLocalExtensions(id); - setExtensionStatus(id, 'not_found'); - } - - // general error (maybe enabled but no accounts trust app) - if (err.substring(0, 5) === 'Error') { - setExtensionStatus(id, 'no_accounts'); - } - }; - - const setActiveAccount = (address: string | null) => { - if (address === null) { - localStorage.removeItem(`${network.name.toLowerCase()}_active_account`); - } else { - localStorage.setItem( - `${network.name.toLowerCase()}_active_account`, - address - ); - } - setStateWithRef(address, _setActiveAccount, activeAccountRef); - }; - - const connectToAccount = (account: ImportedAccount | null) => { - setActiveAccount(account?.address ?? null); - setStateWithRef(account, setActiveAccountMeta, activeAccountMetaRef); - }; - - const disconnectFromAccount = () => { - localStorage.removeItem(`${network.name.toLowerCase()}_active_account`); - setActiveAccount(null); - setStateWithRef(null, setActiveAccountMeta, activeAccountMetaRef); - }; - - const getAccount = (addr: MaybeAccount) => { - const acc = - accountsRef.current.find((a: ImportedAccount) => a?.address === addr) || - null; - return acc; - }; - - const getActiveAccount = () => { - return activeAccountRef.current; - }; - - // adds an external account (non-wallet) to accounts - const addExternalAccount = (address: string, addedBy: string) => { - // ensure account is formatted correctly - const keyring = new Keyring(); - keyring.setSS58Format(network.ss58); - const formatted = keyring.addFromAddress(address).address; - - const externalAccount = { - address: formatted, - network: network.name, - name: clipAddress(address), - source: 'external', - addedBy, - }; - - // get all external accounts from localStorage - const localExternalAccounts = getLocalExternalAccounts(network, false); - const exists = localExternalAccounts.find( - (l: ExternalAccount) => - l.address === address && l.network === network.name - ); - - // add external account to localStorage if not there already - if (!exists) { - const localExternal = localExternalAccounts.concat(externalAccount); - localStorage.setItem('external_accounts', JSON.stringify(localExternal)); - } - - // add external account to imported accounts - setStateWithRef( - [...accountsRef.current].concat(externalAccount), - setAccounts, - accountsRef - ); - }; - - // checks whether an account can sign transactions - const accountHasSigner = (address: MaybeAccount) => { - const exists = - accountsRef.current.find( - (a: ImportedAccount) => a.address === address && a.source !== 'external' - ) !== undefined; - return exists; - }; - - const isReadOnlyAccount = (address: MaybeAccount) => { - const account = getAccount(address) ?? {}; - - if (Object.prototype.hasOwnProperty.call(account, 'addedBy')) { - const { addedBy } = account as ExternalAccount; - return addedBy === 'user'; - } - return false; - }; - - // check an account balance exists on-chain - const formatAccountSs58 = (address: string) => { - try { - const keyring = new Keyring(); - keyring.setSS58Format(network.ss58); - const formatted = keyring.addFromAddress(address).address; - if (formatted !== address) { - return formatted; - } - return null; - } catch (e) { - return null; - } - }; - - return ( - <ConnectContext.Provider - value={{ - formatAccountSs58, - connectExtensionAccounts, - getAccount, - connectToAccount, - disconnectFromAccount, - addExternalAccount, - getActiveAccount, - accountHasSigner, - isReadOnlyAccount, - forgetAccounts, - accounts: accountsRef.current, - activeAccount: activeAccountRef.current, - activeAccountMeta: activeAccountMetaRef.current, - }} - > - {children} - </ConnectContext.Provider> - ); -}; diff --git a/src/contexts/Connect/types.ts b/src/contexts/Connect/types.ts deleted file mode 100644 index f241fa32f9..0000000000 --- a/src/contexts/Connect/types.ts +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { Extension } from 'contexts/Extensions/types'; -import { MaybeAccount } from 'types'; - -export interface ConnectContextInterface { - formatAccountSs58: (a: string) => string | null; - connectExtensionAccounts: (e: Extension) => void; - getAccount: (account: MaybeAccount) => ExtensionAccount | null; - connectToAccount: (a: ExtensionAccount) => void; - disconnectFromAccount: () => void; - addExternalAccount: (a: string, addedBy: string) => void; - getActiveAccount: () => string | null; - accountHasSigner: (a: MaybeAccount) => boolean; - isReadOnlyAccount: (a: MaybeAccount) => boolean; - forgetAccounts: (a: Array<ExternalAccount>) => void; - accounts: Array<ExtensionAccount>; - activeAccount: string | null; - activeAccountMeta: ExtensionAccount | null; -} -export interface ExtensionAccount { - addedBy?: string; - address: string; - source: string; - name?: string; - signer?: unknown; -} - -export type ImportedAccount = ExtensionAccount | ExternalAccount; - -export interface ExternalAccount { - address: string; - network: string; - name: string; - source: string; - addedBy: string; -} diff --git a/src/contexts/Extensions/defaults.ts b/src/contexts/Extensions/defaults.ts deleted file mode 100644 index 47c61958f6..0000000000 --- a/src/contexts/Extensions/defaults.ts +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { ExtensionsContextInterface } from './types'; - -export const defaultExtensionsContext: ExtensionsContextInterface = { - extensions: [], - extensionsStatus: {}, - extensionsFetched: false, - // eslint-disable-next-line - setExtensionStatus: (id, s) => {}, - // eslint-disable-next-line - setExtensionsFetched: (s) => {}, - // eslint-disable-next-line - setExtensions: (s) => {}, -}; diff --git a/src/contexts/Extensions/index.tsx b/src/contexts/Extensions/index.tsx deleted file mode 100644 index 695d49fba3..0000000000 --- a/src/contexts/Extensions/index.tsx +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { ExtensionConfig, EXTENSIONS } from 'config/extensions'; -import { - Extension, - ExtensionsContextInterface, -} from 'contexts/Extensions/types'; -import React, { useEffect, useRef, useState } from 'react'; -import { AnyApi } from 'types'; -import { setStateWithRef } from 'Utils'; -import { defaultExtensionsContext } from './defaults'; - -export const ExtensionsContext = - React.createContext<ExtensionsContextInterface>(defaultExtensionsContext); - -export const useExtensions = () => React.useContext(ExtensionsContext); - -export const ExtensionsProvider = ({ - children, -}: { - children: React.ReactNode; -}) => { - // store the installed extensions in state - const [extensions, setExtensions] = useState<Array<Extension> | null>(null); - - // store whether extensions have been fetched - const [extensionsFetched, setExtensionsFetched] = useState(false); - - // store each extension's status in state. - const [extensionsStatus, setExtensionsStatus] = useState<{ - [key: string]: string; - }>({}); - const extensionsStatusRef = useRef(extensionsStatus); - - // initialise extensions. - useEffect(() => { - if (!extensions) { - // timeout for initialising injectedWeb3 - setTimeout(() => setExtensions(getInstalledExtensions()), 200); - } - }); - - const setExtensionStatus = (id: string, status: string) => { - setStateWithRef( - Object.assign(extensionsStatusRef.current, { - [id]: status, - }), - setExtensionsStatus, - extensionsStatusRef - ); - }; - - const getInstalledExtensions = () => { - const { injectedWeb3 }: AnyApi = window; - const _exts: Extension[] = []; - EXTENSIONS.forEach((e: ExtensionConfig) => { - if (injectedWeb3[e.id] !== undefined) { - _exts.push({ - ...e, - ...injectedWeb3[e.id], - }); - } - }); - return _exts; - }; - - return ( - <ExtensionsContext.Provider - value={{ - extensions: extensions ?? [], - setExtensionStatus, - extensionsStatus: extensionsStatusRef.current, - extensionsFetched, - setExtensionsFetched, - setExtensions, - }} - > - {children} - </ExtensionsContext.Provider> - ); -}; diff --git a/src/contexts/Extensions/types.ts b/src/contexts/Extensions/types.ts deleted file mode 100644 index 51145bec34..0000000000 --- a/src/contexts/Extensions/types.ts +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { FunctionComponent, SVGProps } from 'react'; -import { AnyApi } from 'types'; - -export interface ExtensionsContextInterface { - extensions: Array<Extension>; - extensionsStatus: { [key: string]: string }; - extensionsFetched: boolean; - setExtensionStatus: (id: string, s: string) => void; - setExtensionsFetched: (s: boolean) => void; - setExtensions: (s: Array<Extension>) => void; -} - -export interface ExtensionInteface { - accounts: AnyApi; - metadata: AnyApi; - provider: AnyApi; - signer: AnyApi; -} - -export interface Extension { - id: string; - title: string; - icon: FunctionComponent< - SVGProps<SVGSVGElement> & { title?: string | undefined } - >; - enable: (n: string) => Promise<ExtensionInteface>; - version: string; -} diff --git a/src/contexts/Extrinsics/defaults.ts b/src/contexts/Extrinsics/defaults.ts index 01cd1233f0..a1202dd7b0 100644 --- a/src/contexts/Extrinsics/defaults.ts +++ b/src/contexts/Extrinsics/defaults.ts @@ -1,12 +1,11 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only +/* eslint-disable @typescript-eslint/no-unused-vars */ -import { ExtrinsicsContextInterface } from './types'; +import type { ExtrinsicsContextInterface } from './types'; export const defaultExtrinsicsContext: ExtrinsicsContextInterface = { - // eslint-disable-next-line addPending: (t) => {}, - // eslint-disable-next-line removePending: (t) => {}, pending: [], }; diff --git a/src/contexts/Extrinsics/index.tsx b/src/contexts/Extrinsics/index.tsx index c903049f86..86c5ed285f 100644 --- a/src/contexts/Extrinsics/index.tsx +++ b/src/contexts/Extrinsics/index.tsx @@ -1,15 +1,10 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only +import { setStateWithRef } from '@polkadot-cloud/utils'; import React, { useRef, useState } from 'react'; -import { setStateWithRef } from 'Utils'; import { defaultExtrinsicsContext } from './defaults'; -import { ExtrinsicsContextInterface } from './types'; - -export const ExtrinsicsContext = - React.createContext<ExtrinsicsContextInterface>(defaultExtrinsicsContext); - -export const useExtrinsics = () => React.useContext(ExtrinsicsContext); +import type { ExtrinsicsContextInterface } from './types'; export const ExtrinsicsProvider = ({ children, @@ -20,14 +15,19 @@ export const ExtrinsicsProvider = ({ const pendingRef = useRef(pending); const addPending = (nonce: string) => { - const _pending: string[] = [...pendingRef.current]; - _pending.push(nonce); - setStateWithRef(_pending, setPending, pendingRef); + setStateWithRef( + [...pendingRef.current].concat(nonce), + setPending, + pendingRef + ); }; const removePending = (nonce: string) => { - const _pending = pendingRef.current.filter((n: string) => n !== nonce); - setStateWithRef(_pending, setPending, pendingRef); + setStateWithRef( + pendingRef.current.filter((n: string) => n !== nonce), + setPending, + pendingRef + ); }; return ( @@ -42,3 +42,8 @@ export const ExtrinsicsProvider = ({ </ExtrinsicsContext.Provider> ); }; + +export const ExtrinsicsContext = + React.createContext<ExtrinsicsContextInterface>(defaultExtrinsicsContext); + +export const useExtrinsics = () => React.useContext(ExtrinsicsContext); diff --git a/src/contexts/Extrinsics/types.ts b/src/contexts/Extrinsics/types.ts index 732fbc1fc8..5113f5bca9 100644 --- a/src/contexts/Extrinsics/types.ts +++ b/src/contexts/Extrinsics/types.ts @@ -1,5 +1,5 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only export interface ExtrinsicsContextInterface { addPending: (n: string) => void; diff --git a/src/contexts/FastUnstake/defaults.ts b/src/contexts/FastUnstake/defaults.ts new file mode 100644 index 0000000000..0f1cc87e0f --- /dev/null +++ b/src/contexts/FastUnstake/defaults.ts @@ -0,0 +1,19 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only +/* eslint-disable @typescript-eslint/no-unused-vars */ + +import type { FastUnstakeContextInterface, MetaInterface } from './types'; + +export const defaultMeta: MetaInterface = { + checked: [], +}; + +export const defaultFastUnstakeContext: FastUnstakeContextInterface = { + getLocalkey: (a) => '', + checking: false, + meta: defaultMeta, + isExposed: null, + queueDeposit: null, + head: null, + counterForQueue: null, +}; diff --git a/src/contexts/FastUnstake/index.tsx b/src/contexts/FastUnstake/index.tsx new file mode 100644 index 0000000000..6c7712af60 --- /dev/null +++ b/src/contexts/FastUnstake/index.tsx @@ -0,0 +1,335 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { + greaterThanZero, + isNotZero, + rmCommas, + setStateWithRef, +} from '@polkadot-cloud/utils'; +import BigNumber from 'bignumber.js'; +import React, { useRef, useState } from 'react'; +import { useApi } from 'contexts/Api'; +import { useNetworkMetrics } from 'contexts/NetworkMetrics'; +import { useStaking } from 'contexts/Staking'; +import type { AnyApi, AnyJson, MaybeAddress } from 'types'; +import Worker from 'workers/stakers?worker'; +import { useEffectIgnoreInitial } from '@polkadot-cloud/react/hooks'; +import { useNominationStatus } from 'library/Hooks/useNominationStatus'; +import { validateLocalExposure } from 'contexts/Validators/Utils'; +import { useNetwork } from 'contexts/Network'; +import { useActiveAccounts } from 'contexts/ActiveAccounts'; +import { defaultFastUnstakeContext, defaultMeta } from './defaults'; +import type { + FastUnstakeContextInterface, + LocalMeta, + MetaInterface, +} from './types'; + +const worker = new Worker(); + +export const FastUnstakeProvider = ({ + children, +}: { + children: React.ReactNode; +}) => { + const { network } = useNetwork(); + const { api, isReady, consts } = useApi(); + const { activeAccount } = useActiveAccounts(); + const { inSetup, fetchEraStakers } = useStaking(); + const { metrics, activeEra } = useNetworkMetrics(); + const { getNominationStatus } = useNominationStatus(); + const { fastUnstakeErasToCheckPerBlock } = metrics; + const { bondDuration } = consts; + const { nominees } = getNominationStatus(activeAccount, 'nominator'); + + // store whether a fast unstake check is in progress. + const [checking, setChecking] = useState<boolean>(false); + const checkingRef = useRef(checking); + + // store whether the account is exposed for fast unstake + const [isExposed, setIsExposed] = useState<boolean | null>(null); + const isExposedRef = useRef(isExposed); + + // store state of elibigility checking. + const [meta, setMeta] = useState<MetaInterface>(defaultMeta); + const metaRef = useRef(meta); + + // store fastUnstake queue deposit for user. + const [queueDeposit, setqueueDeposit] = useState<AnyApi>(null); + const queueDepositRef = useRef(queueDeposit); + + // store fastUnstake head. + const [head, setHead] = useState<AnyApi>(null); + const headRef = useRef(head); + + // store fastUnstake counter for queue. + const [counterForQueue, setCounterForQueue] = useState<number | null>(null); + const counterForQueueRef = useRef(counterForQueue); + + // store fastUnstake subscription unsub. + const unsubs = useRef<AnyApi[]>([]); + + // localStorage key to fetch local metadata. + const getLocalkey = (a: MaybeAddress) => `${network}_fast_unstake_${a}`; + + // check until bond duration eras surpasssed. + const checkToEra = activeEra.index.minus(bondDuration); + + // initiate fast unstake check for accounts that are nominating but not active. + useEffectIgnoreInitial(() => { + if ( + isReady && + activeAccount && + isNotZero(activeEra.index) && + fastUnstakeErasToCheckPerBlock > 0 + ) { + // cancel fast unstake check on network change or account change. + for (const unsub of unsubs.current) unsub(); + + setStateWithRef(false, setChecking, checkingRef); + setStateWithRef(null, setqueueDeposit, queueDepositRef); + setStateWithRef(null, setCounterForQueue, counterForQueueRef); + unsubs.current = []; + + // get any existing localStorage records for account. + const localMeta: LocalMeta | null = getLocalMeta(); + + const initialMeta = localMeta + ? { checked: localMeta.checked } + : defaultMeta; + + // even if localMeta.isExposed is false, we don't assume a final value until current era + + // bondDuration is checked. + let initialIsExposed = null; + if (localMeta) { + if (bondDuration.plus(1).isEqualTo(localMeta.checked.length)) { + initialIsExposed = localMeta.isExposed; + } else if (localMeta.isExposed === true) { + initialIsExposed = true; + } else { + initialIsExposed = null; + } + } + + // checkpoint: initial local meta: localMeta + + setStateWithRef(initialMeta, setMeta, metaRef); + setStateWithRef(initialIsExposed, setIsExposed, isExposedRef); + + // start process if account is inactively nominating & local fast unstake data is not + // complete. + if ( + activeAccount && + !inSetup() && + !nominees.active.length && + initialIsExposed === null + ) { + // if localMeta existed, start checking from the next era. + const nextEra = localMeta?.checked.at(-1) || 0; + const maybeNextEra = localMeta + ? new BigNumber(nextEra - 1) + : activeEra.index; + + // checkpoint: check from the possible next era `maybeNextEra`. + + processEligibility(activeAccount, maybeNextEra); + } + + // subscribe to fast unstake queue immediately if synced in localStorage and still up to date. + if (initialIsExposed === false) { + subscribeToFastUnstakeQueue(); + } + } + + return () => { + for (const unsub of unsubs.current) unsub(); + }; + }, [ + inSetup(), + isReady, + network, + activeAccount, + activeEra.index, + fastUnstakeErasToCheckPerBlock, + ]); + + // handle worker message on completed exposure check. + worker.onmessage = (message: MessageEvent) => { + if (message) { + // ensure correct task received. + const { data } = message; + const { task } = data; + if (task !== 'processEraForExposure') return; + + // ensure still same conditions. + const { networkName, who } = data; + if (networkName !== network || who !== activeAccount) return; + + const { era, exposed } = data; + + // ensure checked eras are in order highest first. + const checked = metaRef.current.checked + .concat(Number(era)) + .sort((a: number, b: number) => b - a); + + if (!metaRef.current.checked.includes(Number(era))) { + // update localStorage with updated changes. + localStorage.setItem( + getLocalkey(who), + JSON.stringify({ + isExposed: exposed, + checked, + }) + ); + + // update check metadata. + setStateWithRef( + { + checked, + }, + setMeta, + metaRef + ); + } + + if (exposed) { + // checkpoint: 'exposed! Stop checking. + + // cancel checking and update exposed state. + setStateWithRef(false, setChecking, checkingRef); + setStateWithRef(true, setIsExposed, isExposedRef); + } else if (bondDuration.plus(1).isEqualTo(checked.length)) { + // successfully checked current era - bondDuration eras. + setStateWithRef(false, setChecking, checkingRef); + setStateWithRef(false, setIsExposed, isExposedRef); + + // checkpoint: check finished, not exposed. + + // subscribe to fast unstake queue for user and queue counter. + subscribeToFastUnstakeQueue(); + } else { + // continue checking the next era. + checkEra(new BigNumber(era).minus(1)); + } + } + }; + + // initiate fast unstake eligibility check. + const processEligibility = async (a: MaybeAddress, era: BigNumber) => { + // ensure current era has synced + if ( + era.isLessThan(0) || + !greaterThanZero(bondDuration) || + !api || + !a || + checkingRef.current || + !activeAccount + ) + return; + + setStateWithRef(true, setChecking, checkingRef); + checkEra(era); + }; + + // calls service worker to check exppsures for given era. + const checkEra = async (era: BigNumber) => { + if (!api) return; + + const exposures = await fetchEraStakers(era.toString()); + + worker.postMessage({ + task: 'processEraForExposure', + era: era.toString(), + who: activeAccount, + networkName: network, + exitOnExposed: true, + exposures, + }); + }; + + // subscribe to fastUnstake queue + const subscribeToFastUnstakeQueue = async () => { + if (!api || !activeAccount) return; + const subscribeQueue = async (a: MaybeAddress) => { + const u = await api.query.fastUnstake.queue(a, (q: AnyApi) => + setStateWithRef( + new BigNumber(rmCommas(q.unwrapOrDefault(0).toString())), + setqueueDeposit, + queueDepositRef + ) + ); + return u; + }; + const subscribeHead = async () => { + const u = await api.query.fastUnstake.head((result: AnyApi) => { + const h = result.unwrapOrDefault(null).toHuman(); + setStateWithRef(h, setHead, headRef); + }); + return u; + }; + const subscribeCounterForQueue = async () => { + const u = await api.query.fastUnstake.counterForQueue( + (result: AnyApi) => { + const c = result.toHuman(); + setStateWithRef(c, setCounterForQueue, counterForQueueRef); + } + ); + return u; + }; + + // checkpoint: subscribing to queue + head. + + // initiate subscription, add to unsubs. + await Promise.all([ + subscribeQueue(activeAccount), + subscribeHead(), + subscribeCounterForQueue(), + ]).then((u: any) => { + unsubs.current = u; + }); + }; + + // gets any existing fast unstake metadata for an account. + const getLocalMeta = (): LocalMeta | null => { + const localMeta: AnyJson = localStorage.getItem(getLocalkey(activeAccount)); + if (!localMeta) return null; + + const localMetaValidated = validateLocalExposure( + JSON.parse(localMeta), + checkToEra + ); + if (!localMetaValidated) { + // remove if not valid. + localStorage.removeItem(getLocalkey(activeAccount)); + return null; + } + // set validated localStorage. + localStorage.setItem( + getLocalkey(activeAccount), + JSON.stringify(localMetaValidated) + ); + return localMetaValidated; + }; + + return ( + <FastUnstakeContext.Provider + value={{ + getLocalkey, + checking: checkingRef.current, + meta: metaRef.current, + isExposed: isExposedRef.current, + queueDeposit: queueDepositRef.current, + head: headRef.current, + counterForQueue: counterForQueueRef.current, + }} + > + {children} + </FastUnstakeContext.Provider> + ); +}; + +export const FastUnstakeContext = + React.createContext<FastUnstakeContextInterface>(defaultFastUnstakeContext); + +export const useFastUnstake = () => React.useContext(FastUnstakeContext); diff --git a/src/contexts/FastUnstake/types.ts b/src/contexts/FastUnstake/types.ts new file mode 100644 index 0000000000..1c6dfc2c1b --- /dev/null +++ b/src/contexts/FastUnstake/types.ts @@ -0,0 +1,23 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import type BigNumber from 'bignumber.js'; +import type { AnyApi, MaybeAddress } from 'types'; + +export interface LocalMeta { + isExposed: boolean; + checked: number[]; +} +export interface MetaInterface { + checked: number[]; +} + +export interface FastUnstakeContextInterface { + getLocalkey: (a: MaybeAddress) => string; + checking: boolean; + meta: MetaInterface; + isExposed: boolean | null; + queueDeposit: BigNumber | null; + head: AnyApi; + counterForQueue: number | null; +} diff --git a/src/contexts/Filters/defaults.ts b/src/contexts/Filters/defaults.ts index 7c9f7c4f98..009b13a09a 100644 --- a/src/contexts/Filters/defaults.ts +++ b/src/contexts/Filters/defaults.ts @@ -1,32 +1,20 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only +/* eslint-disable @typescript-eslint/no-unused-vars */ -import { AnyFunction, AnyJson } from 'types'; -import { FilterType } from './types'; +import type { FiltersContextInterface } from './types'; -export const defaultFiltersInterface = { - // eslint-disable-next-line - getFilters: (t: FilterType, g: string) => [], - // eslint-disable-next-line - toggleFilter: (t: FilterType, g: string, f: string) => {}, - // eslint-disable-next-line - setMultiFilters: (t: FilterType, g: string, fs: Array<string>) => {}, - // eslint-disable-next-line - getOrder: (g: string) => 'default', - // eslint-disable-next-line - setOrder: (g: string, o: string) => {}, - // eslint-disable-next-line - getSearchTerm: (g: string) => null, - // eslint-disable-next-line - setSearchTerm: (g: string, t: string) => {}, - // eslint-disable-next-line - resetFilters: (t: FilterType, g: string) => {}, - // eslint-disable-next-line - resetOrder: (g: string) => {}, - // eslint-disable-next-line - clearSearchTerm: (g: string) => {}, - // eslint-disable-next-line - applyFilters: (t: FilterType, g: string, l: AnyJson, f: AnyFunction) => { }, - // eslint-disable-next-line - applyOrder: (g: string, l: AnyJson, f: AnyFunction) => { } +export const defaultFiltersInterface: FiltersContextInterface = { + getFilters: (t, g) => [], + toggleFilter: (t, g, f) => {}, + setMultiFilters: (t, g, fs, r) => {}, + getOrder: (g) => 'default', + setOrder: (g, o) => {}, + getSearchTerm: (g) => null, + setSearchTerm: (g, t) => {}, + resetFilters: (t, g) => {}, + resetOrder: (g) => {}, + clearSearchTerm: (g) => {}, + applyFilters: (t, g, l, f) => {}, + applyOrder: (g, l, f) => {}, }; diff --git a/src/contexts/Filters/index.tsx b/src/contexts/Filters/index.tsx index 4b43f48679..c87e6f45d9 100644 --- a/src/contexts/Filters/index.tsx +++ b/src/contexts/Filters/index.tsx @@ -1,26 +1,20 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only import React, { useState } from 'react'; -import { AnyFunction, AnyJson } from 'types'; +import type { AnyFunction, AnyJson } from 'types'; import { defaultFiltersInterface } from './defaults'; -import { +import type { FilterItem, FilterItems, FilterOrder, FilterOrders, - FiltersContextInterface, FilterSearch, FilterSearches, FilterType, + FiltersContextInterface, } from './types'; -export const FiltersContext = React.createContext<FiltersContextInterface>( - defaultFiltersInterface -); - -export const useFilters = () => React.useContext(FiltersContext); - export const FiltersProvider = ({ children, }: { @@ -39,13 +33,13 @@ export const FiltersProvider = ({ const [searchTerms, setSearchTerms] = useState<FilterSearches>([]); // Get stored includes or excludes for a group. - const getFilters = (t: FilterType, g: string): Array<string> | null => { - const current = t === FilterType.Exclude ? excludes : includes; - return current.find((e: FilterItem) => e.key === g)?.filters || null; + const getFilters = (t: FilterType, g: string): string[] | null => { + const current = t === 'exclude' ? excludes : includes; + return current.find((e) => e.key === g)?.filters || null; }; const setFilters = (t: FilterType, n: FilterItems) => { - if (t === FilterType.Exclude) { + if (t === 'exclude') { setExcludes(n); } else { setIncludes(n); @@ -55,7 +49,7 @@ export const FiltersProvider = ({ // Toggle a filter for a group. // Adds the group to `excludes` or `includes` if it does not already exist. const toggleFilter = (t: FilterType, g: string, f: string) => { - const current = t === FilterType.Exclude ? excludes : includes; + const current = t === 'exclude' ? excludes : includes; const exists = getFilters(t, g); if (!exists) { @@ -64,7 +58,7 @@ export const FiltersProvider = ({ return; } const newFilters = [...current] - .map((e: FilterItem) => { + .map((e) => { if (e.key !== g) return e; let { filters } = e; @@ -78,13 +72,20 @@ export const FiltersProvider = ({ filters, }; }) - .filter((e: FilterItem) => e.filters.length !== 0); + .filter((e) => e.filters.length !== 0); setFilters(t, newFilters); }; // Sets an array of filters to a group. - const setMultiFilters = (t: FilterType, g: string, fs: Array<string>) => { - const current = t === FilterType.Exclude ? excludes : includes; + const setMultiFilters = ( + t: FilterType, + g: string, + fs: string[], + reset: boolean + ) => { + // get the current filters from the group. + const current = reset ? [] : t === 'exclude' ? excludes : includes; + // check if filters currently exist in the group. const exists = getFilters(t, g); if (!exists) { @@ -92,31 +93,37 @@ export const FiltersProvider = ({ setFilters(t, newFilters); return; } - const newFilters = [...current].map((e: FilterItem) => { - if (e.key !== g) return e; - let { filters } = e; - filters = filters.filter((f: string) => !fs.includes(f)).concat(fs); - - return { - key: e.key, - filters, - }; - }); + + let newFilters: FilterItems; + if (current.length) { + newFilters = [...current].map((e) => { + // return groups we are not manipulating. + if (e.key !== g) return e; + + let { filters } = e; + filters = filters.filter((f: string) => !fs.includes(f)).concat(fs); + return { + key: e.key, + filters, + }; + }); + } else { + newFilters = [{ key: g, filters: fs }]; + } setFilters(t, newFilters); }; // Get the current order of a list or null. - const getOrder = (g: string) => { - return orders.find((o: FilterOrder) => o.key === g)?.order || 'default'; - }; + const getOrder = (g: string) => + orders.find((o) => o.key === g)?.order || 'default'; // Sets an order key for a group. const setOrder = (g: string, o: string) => { let newOrders = []; if (o === 'default') { - newOrders = [...orders].filter((order: FilterOrder) => order.key !== g); + newOrders = [...orders].filter((order) => order.key !== g); } else if (orders.length) { - newOrders = [...orders].map((order: FilterOrder) => + newOrders = [...orders].map((order) => order.key !== g ? order : { ...order, order: o } ); } else { @@ -126,17 +133,14 @@ export const FiltersProvider = ({ }; // Get the current search term of a list or null. - const getSearchTerm = (g: string) => { - return ( - searchTerms.find((o: FilterSearch) => o.key === g)?.searchTerm || null - ); - }; + const getSearchTerm = (g: string) => + searchTerms.find((o) => o.key === g)?.searchTerm || null; // Sets an order key for a group. const setSearchTerm = (g: string, t: string) => { let newSearchTerms = []; if (orders.length) { - newSearchTerms = [...searchTerms].map((term: FilterSearch) => + newSearchTerms = [...searchTerms].map((term) => term.key !== g ? term : { ...term, searchTerm: t } ); } else { @@ -147,7 +151,7 @@ export const FiltersProvider = ({ // resets excludes for a given group const resetFilters = (t: FilterType, g: string) => { - const current = t === FilterType.Exclude ? excludes : includes; + const current = t === 'exclude' ? excludes : includes; setFilters( t, [...current].filter((e: FilterItem) => e.key !== g) @@ -209,3 +213,9 @@ export const FiltersProvider = ({ </FiltersContext.Provider> ); }; + +export const FiltersContext = React.createContext<FiltersContextInterface>( + defaultFiltersInterface +); + +export const useFilters = () => React.useContext(FiltersContext); diff --git a/src/contexts/Filters/types.ts b/src/contexts/Filters/types.ts index 7c9a3deb1f..fa9044c718 100644 --- a/src/contexts/Filters/types.ts +++ b/src/contexts/Filters/types.ts @@ -1,17 +1,14 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import { AnyFunction, AnyJson } from 'types'; +import type { AnyFunction, AnyJson } from 'types'; -export enum FilterType { - Exclude, - Include, -} +export type FilterType = 'exclude' | 'include'; export interface FiltersContextInterface { - getFilters: (t: FilterType, g: string) => Array<string> | null; + getFilters: (t: FilterType, g: string) => string[] | null; toggleFilter: (t: FilterType, g: string, f: string) => void; - setMultiFilters: (t: FilterType, g: string, fs: Array<string>) => void; + setMultiFilters: (t: FilterType, g: string, fs: string[], r: boolean) => void; getOrder: (g: string) => string; setOrder: (g: string, o: string) => void; getSearchTerm: (g: string) => string | null; @@ -28,11 +25,11 @@ export interface FiltersContextInterface { applyOrder: (g: string, list: AnyJson, fn: AnyFunction) => void; } -export type FilterItems = Array<FilterItem>; -export type FilterItem = { key: string; filters: Array<string> }; +export type FilterItems = FilterItem[]; +export type FilterItem = { key: string; filters: string[] }; -export type FilterOrders = Array<FilterOrder>; +export type FilterOrders = FilterOrder[]; export type FilterOrder = { key: string; order: string }; -export type FilterSearches = Array<FilterSearch>; +export type FilterSearches = FilterSearch[]; export type FilterSearch = { key: string; searchTerm: string }; diff --git a/src/contexts/Hardware/Ledger.tsx b/src/contexts/Hardware/Ledger.tsx new file mode 100644 index 0000000000..32628b4b77 --- /dev/null +++ b/src/contexts/Hardware/Ledger.tsx @@ -0,0 +1,556 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import TransportWebHID from '@ledgerhq/hw-transport-webhid'; +import { u8aToBuffer } from '@polkadot/util'; +import { localStorageOrDefault, setStateWithRef } from '@polkadot-cloud/utils'; +import { newSubstrateApp } from '@zondax/ledger-substrate'; +import React, { useEffect, useRef, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import type { LedgerAccount } from '@polkadot-cloud/react/types'; +import type { AnyFunction, AnyJson, MaybeString } from 'types'; +import { useNetwork } from 'contexts/Network'; +import { + getLocalLedgerAccounts, + getLocalLedgerAddresses, + isLocalNetworkAddress, +} from './Utils'; +import { + LEDGER_DEFAULT_ACCOUNT, + LEDGER_DEFAULT_CHANGE, + LEDGER_DEFAULT_INDEX, + TOTAL_ALLOWED_STATUS_CODES, + defaultFeedback, + defaultLedgerHardwareContext, +} from './defaults'; +import type { + FeedbackMessage, + LedgerAddress, + LedgerHardwareContextInterface, + LedgerResponse, + LedgerStatusCode, + LedgerTask, + PairingStatus, +} from './types'; + +export const LedgerHardwareProvider = ({ + children, +}: { + children: React.ReactNode; +}) => { + const { t } = useTranslation('modals'); + const { network } = useNetwork(); + + const [ledgerAccounts, setLedgerAccountsState] = useState<LedgerAccount[]>( + getLocalLedgerAccounts(network) + ); + const ledgerAccountsRef = useRef(ledgerAccounts); + + // Store whether the device has been paired. + const [isPaired, setIsPairedState] = useState<PairingStatus>('unknown'); + const isPairedRef = useRef(isPaired); + + // Store whether an import is in process. + const [isExecuting, setIsExecutingState] = useState(false); + const isExecutingRef = useRef(isExecuting); + + // Store status codes received from Ledger device. + const [statusCodes, setStatusCodes] = useState<LedgerResponse[]>([]); + const statusCodesRef = useRef(statusCodes); + + // Get the default message to display, set when a failed loop has happened. + const [feedback, setFeedbackState] = + useState<FeedbackMessage>(defaultFeedback); + + const feedbackRef = useRef(feedback); + + // Store the latest successful response from an attempted `executeLedgerLoop`. + const [transportResponse, setTransportResponse] = useState<AnyJson>(null); + + // Whether pairing is in progress: protects against re-renders & duplicate attempts. + const pairInProgress = useRef(false); + + // Whether a ledger-loop is in progress: protects against re-renders & duplicate attempts. + const ledgerLoopInProgress = useRef(false); + + // The ledger transport interface. + const ledgerTransport = useRef<any>(null); + + // Refresh imported ledger accounts on network change. + useEffect(() => { + setStateWithRef( + getLocalLedgerAccounts(network), + setLedgerAccountsState, + ledgerAccountsRef + ); + }, [network]); + + // Handles errors that occur during `executeLedgerLoop` and `pairDevice` calls. + const handleErrors = (appName: string, err: AnyJson) => { + // reset any in-progress calls. + ledgerLoopInProgress.current = false; + pairInProgress.current = false; + + // execution failed - no longer executing. + setIsExecuting(false); + + // close any open device connections. + if (ledgerTransport.current?.device?.opened) { + ledgerTransport.current?.device?.close(); + } + + // format error message. + err = String(err); + if (err === 'Error: Timeout') { + // only set default message here - maintain previous status code. + setFeedback(t('ledgerRequestTimeout'), 'Ledger Request Timeout'); + handleNewStatusCode('failure', 'DeviceTimeout'); + } else if (err.startsWith('Error: Call nesting not supported')) { + setFeedback(t('missingNesting')); + handleNewStatusCode('failure', 'NestingNotSupported'); + } else if ( + err.startsWith('Error: TransportError: Invalid channel') || + err.startsWith('Error: InvalidStateError') + ) { + // occurs when tx was approved outside of active channel. + setFeedback(t('queuedTransactionRejected'), 'Wrong Transaction'); + handleNewStatusCode('failure', 'WrongTransaction'); + } else if ( + err.startsWith('TransportOpenUserCancelled') || + err.startsWith('Error: Ledger Device is busy') + ) { + // occurs when the device is not connected. + setFeedback(t('connectLedgerToContinue')); + handleNewStatusCode('failure', 'DeviceNotConnected'); + } else if (err.startsWith('Error: LockedDeviceError')) { + // occurs when the device is connected but not unlocked. + setFeedback(t('unlockLedgerToContinue')); + handleNewStatusCode('failure', 'DeviceLocked'); + } else if (err.startsWith('Error: Transaction rejected')) { + // occurs when user rejects a transaction. + setFeedback( + t('transactionRejectedPending'), + 'Ledger Rejected Transaction' + ); + handleNewStatusCode('failure', 'TransactionRejected'); + } else if (err.startsWith('Error: Unknown Status Code: 28161')) { + // occurs when the required app is not open. + handleNewStatusCode('failure', 'AppNotOpenContinue'); + setFeedback(t('openAppOnLedger', { appName }), 'Open App On Ledger'); + } else { + // miscellanous errors - assume app is not open or ready. + setFeedback(t('openAppOnLedger', { appName }), 'Open App On Ledger'); + handleNewStatusCode('failure', 'AppNotOpen'); + } + }; + + // Timeout function for hanging tasks. Used for tasks that require no input from the device, such + // as getting an address that does not require confirmation. + const withTimeout = (millis: AnyFunction, promise: AnyFunction) => { + const timeout = new Promise((_, reject) => + setTimeout(async () => { + ledgerTransport.current?.device?.close(); + reject(Error('Timeout')); + }, millis) + ); + return Promise.race([promise, timeout]); + }; + + // Attempt to pair a device. + // + // Trigger a one-time connection to the device to determine if it is available. If the device + // needs to be paired, a browser prompt will open. If cancelled, an error will be thrown. + const pairDevice = async () => { + try { + // return `paired` if pairing is already in progress. + if (pairInProgress.current) { + return isPairedRef.current === 'paired'; + } + // set pairing in progress. + pairInProgress.current = true; + + // remove any previously stored status codes. + resetStatusCodes(); + + // close any open connections. + if (ledgerTransport.current?.device?.opened) { + await ledgerTransport.current?.device?.close(); + } + // establish a new connection with device. + ledgerTransport.current = await TransportWebHID.create(); + setIsPaired('paired'); + pairInProgress.current = false; + return true; + } catch (err) { + pairInProgress.current = false; + handleErrors('', err); + return false; + } + }; + + // Connects to a Ledger device to perform a task. This is the main execute function that handles + // all Ledger tasks, along with errors that occur during the process. + const executeLedgerLoop = async ( + appName: string, + tasks: LedgerTask[], + options?: AnyJson + ) => { + try { + // do not execute again if already in progress. + if (ledgerLoopInProgress.current) { + return; + } + + // set ledger loop in progress. + ledgerLoopInProgress.current = true; + + // test for tasks and execute them. This is designed such that `result` will only store the + // result of one task. This will have to be refactored if we ever need to execute multiple + // tasks at once. + let result = null; + if (tasks.includes('get_address')) { + result = await handleGetAddress(appName, options?.accountIndex || 0); + } else if (tasks.includes('sign_tx')) { + const uid = options?.uid || 0; + const index = options?.accountIndex || 0; + const payload = options?.payload || ''; + + result = await handleSignTx(appName, uid, index, payload); + } + + // a populated result indicates a successful execution. Set the transport response state for + // other components to respond to via useEffect. + if (result) { + setTransportResponse({ + ack: 'success', + options, + ...result, + }); + } + ledgerLoopInProgress.current = false; + } catch (err) { + handleErrors(appName, err); + } + }; + + // Gets an app address on device. + const handleGetAddress = async (appName: string, index: number) => { + const substrateApp = newSubstrateApp(ledgerTransport.current, appName); + const { deviceModel } = ledgerTransport.current; + const { id, productName } = deviceModel; + + setTransportResponse({ + ack: 'success', + statusCode: 'GettingAddress', + body: null, + }); + setFeedback(t('gettingAddress')); + + if (!ledgerTransport.current?.device?.opened) { + await ledgerTransport.current?.device?.open(); + } + const result: AnyJson = await withTimeout( + 3000, + substrateApp.getAddress( + LEDGER_DEFAULT_ACCOUNT + index, + LEDGER_DEFAULT_CHANGE, + LEDGER_DEFAULT_INDEX + 0, + false + ) + ); + + await ledgerTransport.current?.device?.close(); + + const error = result?.error_message; + if (error) { + if (!error.startsWith('No errors')) { + throw new Error(error); + } + } + + if (!(result instanceof Error)) { + setFeedback(t('successfullyFetchedAddress')); + + return { + statusCode: 'ReceivedAddress', + device: { id, productName }, + body: [result], + }; + } + return undefined; + }; + + // Signs a payload on device. + const handleSignTx = async ( + appName: string, + uid: number, + index: number, + payload: AnyJson + ) => { + const substrateApp = newSubstrateApp(ledgerTransport.current, appName); + const { deviceModel } = ledgerTransport.current; + const { id, productName } = deviceModel; + + setTransportResponse({ + ack: 'success', + statusCode: 'SigningPayload', + body: null, + }); + + setFeedback(t('approveTransactionLedger')); + + if (!ledgerTransport.current?.device?.opened) { + await ledgerTransport.current?.device?.open(); + } + const result = await substrateApp.sign( + LEDGER_DEFAULT_ACCOUNT + index, + LEDGER_DEFAULT_CHANGE, + LEDGER_DEFAULT_INDEX + 0, + u8aToBuffer(payload.toU8a(true)) + ); + + setFeedback(t('signedTransactionSuccessfully')); + await ledgerTransport.current?.device?.close(); + + const error = result?.error_message; + if (error) { + if (!error.startsWith('No errors')) { + throw new Error(error); + } + } + + if (!(result instanceof Error)) { + return { + statusCode: 'SignedPayload', + device: { id, productName }, + body: { + uid, + sig: result.signature, + }, + }; + } + return undefined; + }; + + // Handle an incoming new status code and persist to state. + const handleNewStatusCode = (ack: string, statusCode: LedgerStatusCode) => { + const newStatusCodes = [{ ack, statusCode }, ...statusCodes]; + + // Remove last status code if there are more than allowed number of status codes. + if (newStatusCodes.length > TOTAL_ALLOWED_STATUS_CODES) { + newStatusCodes.pop(); + } + setStateWithRef(newStatusCodes, setStatusCodes, statusCodesRef); + }; + + // Check if a Ledger address exists in imported addresses. + const ledgerAccountExists = (address: string) => + !!getLocalLedgerAccounts().find((a) => + isLocalNetworkAddress(network, a, address) + ); + + const addLedgerAccount = (address: string, index: number) => { + let newLedgerAccounts = getLocalLedgerAccounts(); + + const ledgerAddress = getLocalLedgerAddresses().find((a) => + isLocalNetworkAddress(network, a, address) + ); + + if ( + ledgerAddress && + !newLedgerAccounts.find((a) => isLocalNetworkAddress(network, a, address)) + ) { + const account = { + address, + network, + name: ledgerAddress.name, + source: 'ledger', + index, + }; + + // update the full list of local ledger accounts with new entry. + newLedgerAccounts = [...newLedgerAccounts].concat(account); + localStorage.setItem( + 'ledger_accounts', + JSON.stringify(newLedgerAccounts) + ); + + // store only those accounts on the current network in state. + setStateWithRef( + newLedgerAccounts.filter((a) => a.network === network), + setLedgerAccountsState, + ledgerAccountsRef + ); + + return account; + } + return null; + }; + + const removeLedgerAccount = (address: string) => { + let newLedgerAccounts = getLocalLedgerAccounts(); + + newLedgerAccounts = newLedgerAccounts.filter((a) => { + if (a.address !== address) { + return true; + } + if (a.network !== network) { + return true; + } + return false; + }); + if (!newLedgerAccounts.length) { + localStorage.removeItem('ledger_accounts'); + } else { + localStorage.setItem( + 'ledger_accounts', + JSON.stringify(newLedgerAccounts) + ); + } + setStateWithRef( + newLedgerAccounts.filter((a) => a.network === network), + setLedgerAccountsState, + ledgerAccountsRef + ); + }; + + // Gets an imported address along with its Ledger metadata. + const getLedgerAccount = (address: string) => { + const localLedgerAccounts = getLocalLedgerAccounts(); + + if (!localLedgerAccounts) { + return null; + } + return ( + localLedgerAccounts.find((a) => + isLocalNetworkAddress(network, a, address) + ) ?? null + ); + }; + + // Renames an imported ledger account. + const renameLedgerAccount = (address: string, newName: string) => { + let newLedgerAccounts = getLocalLedgerAccounts(); + + newLedgerAccounts = newLedgerAccounts.map((a) => + isLocalNetworkAddress(network, a, address) + ? { + ...a, + name: newName, + } + : a + ); + renameLocalLedgerAddress(address, newName); + localStorage.setItem('ledger_accounts', JSON.stringify(newLedgerAccounts)); + setStateWithRef( + newLedgerAccounts.filter((a) => a.network === network), + setLedgerAccountsState, + ledgerAccountsRef + ); + }; + + // Renames a record from local ledger addresses. + const renameLocalLedgerAddress = (address: string, name: string) => { + const localLedger = ( + localStorageOrDefault('ledger_addresses', [], true) as LedgerAddress[] + )?.map((i) => + !(i.address === address && i.network === network) + ? i + : { + ...i, + name, + } + ); + + if (localLedger) { + localStorage.setItem('ledger_addresses', JSON.stringify(localLedger)); + } + }; + + const getTransport = () => { + return ledgerTransport.current; + }; + + const getIsExecuting = () => { + return isExecutingRef.current; + }; + + const getStatusCodes = () => { + return statusCodesRef.current; + }; + + const getFeedback = () => { + return feedbackRef.current; + }; + + const setFeedback = (message: MaybeString, helpKey: MaybeString = null) => { + setStateWithRef({ message, helpKey }, setFeedbackState, feedbackRef); + }; + + const resetFeedback = () => { + setStateWithRef(defaultFeedback, setFeedbackState, feedbackRef); + }; + + const setIsPaired = (p: PairingStatus) => { + setStateWithRef(p, setIsPairedState, isPairedRef); + }; + + const setIsExecuting = (val: boolean) => { + setStateWithRef(val, setIsExecutingState, isExecutingRef); + }; + + const resetStatusCodes = () => { + setStateWithRef([], setStatusCodes, statusCodesRef); + }; + + const handleUnmount = () => { + // reset refs + ledgerLoopInProgress.current = false; + pairInProgress.current = false; + // reset state + resetStatusCodes(); + setIsExecuting(false); + resetFeedback(); + // close transport + if (getTransport()?.device?.opened) { + getTransport().device.close(); + } + }; + + return ( + <LedgerHardwareContext.Provider + value={{ + pairDevice, + transportResponse, + executeLedgerLoop, + setIsPaired, + setIsExecuting, + handleNewStatusCode, + resetStatusCodes, + getIsExecuting, + getStatusCodes, + getTransport, + ledgerAccountExists, + addLedgerAccount, + removeLedgerAccount, + renameLedgerAccount, + getLedgerAccount, + getFeedback, + setFeedback, + resetFeedback, + handleUnmount, + isPaired: isPairedRef.current, + ledgerAccounts: ledgerAccountsRef.current, + }} + > + {children} + </LedgerHardwareContext.Provider> + ); +}; + +export const LedgerHardwareContext = + React.createContext<LedgerHardwareContextInterface>( + defaultLedgerHardwareContext + ); + +export const useLedgerHardware = () => React.useContext(LedgerHardwareContext); diff --git a/src/contexts/Hardware/Utils.tsx b/src/contexts/Hardware/Utils.tsx new file mode 100644 index 0000000000..c78de28dfc --- /dev/null +++ b/src/contexts/Hardware/Utils.tsx @@ -0,0 +1,59 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { localStorageOrDefault } from '@polkadot-cloud/utils'; +import { LedgerApps } from 'config/ledger'; +import type { MaybeString } from 'types'; +import type { LedgerAccount, VaultAccount } from '@polkadot-cloud/react/types'; +import type { LedgerAddress } from './types'; + +// Gets ledger app from local storage, fallback to first entry. +export const getLedgerApp = (network: string) => { + return LedgerApps.find((a) => a.network === network) || LedgerApps[0]; +}; + +// Gets saved ledger addresses from local storage. +export const getLocalLedgerAddresses = (network?: string) => { + const localAddresses = localStorageOrDefault( + 'ledger_addresses', + [], + true + ) as LedgerAddress[]; + + return network + ? localAddresses.filter((a) => a.network === network) + : localAddresses; +}; + +// Gets imported Ledger accounts from local storage. +export const getLocalLedgerAccounts = (network?: string) => { + const localAddresses = localStorageOrDefault( + 'ledger_accounts', + [], + true + ) as LedgerAccount[]; + + return network + ? localAddresses.filter((a) => a.network === network) + : localAddresses; +}; + +// Gets imported Vault accounts from local storage. +export const getLocalVaultAccounts = (network?: string) => { + const localAddresses = localStorageOrDefault( + 'polkadot_vault_accounts', + [], + true + ) as VaultAccount[]; + + return network + ? localAddresses.filter((a) => a.network === network) + : localAddresses; +}; + +// Gets whether an address is a local network address. +export const isLocalNetworkAddress = ( + chain: string, + a: { address: MaybeString; network: string }, + address: string +) => a.address === address && a.network === chain; diff --git a/src/contexts/Hardware/Vault.tsx b/src/contexts/Hardware/Vault.tsx new file mode 100644 index 0000000000..35acc77792 --- /dev/null +++ b/src/contexts/Hardware/Vault.tsx @@ -0,0 +1,154 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { ellipsisFn, setStateWithRef } from '@polkadot-cloud/utils'; +import React, { useEffect, useRef, useState } from 'react'; +import type { VaultAccount } from '@polkadot-cloud/react/types'; +import { useNetwork } from 'contexts/Network'; +import { getLocalVaultAccounts, isLocalNetworkAddress } from './Utils'; +import { defaultVaultHardwareContext } from './defaults'; +import type { VaultHardwareContextInterface } from './types'; + +export const VaultHardwareProvider = ({ + children, +}: { + children: React.ReactNode; +}) => { + const { network } = useNetwork(); + + const [vaultAccounts, seVaultAccountsState] = useState<VaultAccount[]>( + getLocalVaultAccounts(network) + ); + const vaultAccountsRef = useRef(vaultAccounts); + + // Check if a Vault address exists in imported addresses. + const vaultAccountExists = (address: string) => + !!getLocalVaultAccounts().find((a) => + isLocalNetworkAddress(network, a, address) + ); + + // Adds a vault account to state and local storage. + const addVaultAccount = (address: string, index: number) => { + let newVaultAccounts = getLocalVaultAccounts(); + + if ( + !newVaultAccounts.find((a) => isLocalNetworkAddress(network, a, address)) + ) { + const account = { + address, + network, + name: ellipsisFn(address), + source: 'vault', + index, + }; + + newVaultAccounts = [...newVaultAccounts].concat(account); + localStorage.setItem( + 'polkadot_vault_accounts', + JSON.stringify(newVaultAccounts) + ); + + // store only those accounts on the current network in state. + setStateWithRef( + newVaultAccounts.filter((a) => a.network === network), + seVaultAccountsState, + vaultAccountsRef + ); + return account; + } + return null; + }; + + const removeVaultAccount = (address: string) => { + let newVaultAccounts = getLocalVaultAccounts(); + + newVaultAccounts = newVaultAccounts.filter((a) => { + if (a.address !== address) { + return true; + } + if (a.network !== network) { + return true; + } + return false; + }); + + if (!newVaultAccounts.length) { + localStorage.removeItem('polkadot_vault_accounts'); + } else { + localStorage.setItem( + 'polkadot_vault_accounts', + JSON.stringify(newVaultAccounts) + ); + } + setStateWithRef( + newVaultAccounts.filter((a) => a.network === network), + seVaultAccountsState, + vaultAccountsRef + ); + }; + + const getVaultAccount = (address: string) => { + const localVaultAccounts = getLocalVaultAccounts(); + if (!localVaultAccounts) { + return null; + } + return ( + localVaultAccounts.find((a) => + isLocalNetworkAddress(network, a, address) + ) ?? null + ); + }; + + const renameVaultAccount = (address: string, newName: string) => { + let newVaultAccounts = getLocalVaultAccounts(); + + newVaultAccounts = newVaultAccounts.map((a) => + isLocalNetworkAddress(network, a, address) + ? { + ...a, + name: newName, + } + : a + ); + localStorage.setItem( + 'polkadot_vault_accounts', + JSON.stringify(newVaultAccounts) + ); + setStateWithRef( + newVaultAccounts.filter((a) => a.network === network), + seVaultAccountsState, + vaultAccountsRef + ); + }; + + // Refresh imported vault accounts on network change. + useEffect(() => { + setStateWithRef( + getLocalVaultAccounts(network), + seVaultAccountsState, + vaultAccountsRef + ); + }, [network]); + + return ( + <VaultHardwareContext.Provider + value={{ + vaultAccountExists, + addVaultAccount, + removeVaultAccount, + renameVaultAccount, + getVaultAccount, + vaultAccounts: vaultAccountsRef.current, + }} + > + {children} + </VaultHardwareContext.Provider> + ); +}; + +export const VaultHardwareContext = + React.createContext<VaultHardwareContextInterface>( + defaultVaultHardwareContext + ); + +export const useVaultHardware = () => React.useContext(VaultHardwareContext); diff --git a/src/contexts/Hardware/defaults.ts b/src/contexts/Hardware/defaults.ts new file mode 100644 index 0000000000..af4bab8bef --- /dev/null +++ b/src/contexts/Hardware/defaults.ts @@ -0,0 +1,51 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only +/* eslint-disable @typescript-eslint/no-unused-vars */ + +import type { + LedgerHardwareContextInterface, + VaultHardwareContextInterface, +} from './types'; + +export const TOTAL_ALLOWED_STATUS_CODES = 50; +export const LEDGER_DEFAULT_ACCOUNT = 0x80000000; +export const LEDGER_DEFAULT_CHANGE = 0x80000000; +export const LEDGER_DEFAULT_INDEX = 0x80000000; + +export const defaultFeedback = { + message: null, + helpKey: null, +}; + +export const defaultLedgerHardwareContext: LedgerHardwareContextInterface = { + transportResponse: null, + pairDevice: async () => new Promise((resolve) => resolve(false)), + executeLedgerLoop: async (a, s, o) => new Promise((resolve) => resolve()), + setIsPaired: (v) => {}, + handleNewStatusCode: (a, s) => {}, + setIsExecuting: (b) => {}, + resetStatusCodes: () => {}, + getIsExecuting: () => false, + getStatusCodes: () => [], + getTransport: () => null, + ledgerAccountExists: (a) => false, + addLedgerAccount: (a, i) => null, + removeLedgerAccount: (a) => {}, + renameLedgerAccount: (a, n) => {}, + getLedgerAccount: (a) => null, + isPaired: 'unknown', + ledgerAccounts: [], + getFeedback: () => defaultFeedback, + setFeedback: (s, h) => {}, + resetFeedback: () => {}, + handleUnmount: () => {}, +}; + +export const defaultVaultHardwareContext: VaultHardwareContextInterface = { + vaultAccountExists: (a) => false, + addVaultAccount: (a, i) => null, + removeVaultAccount: (a) => {}, + renameVaultAccount: (a, n) => {}, + getVaultAccount: (a) => null, + vaultAccounts: [], +}; diff --git a/src/contexts/Hardware/types.ts b/src/contexts/Hardware/types.ts new file mode 100644 index 0000000000..0785085953 --- /dev/null +++ b/src/contexts/Hardware/types.ts @@ -0,0 +1,87 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import type { LedgerAccount, VaultAccount } from '@polkadot-cloud/react/types'; +import type { FunctionComponent, SVGProps } from 'react'; +import type { AnyJson, MaybeString, NetworkName } from 'types'; + +export type LedgerHardwareContextInterface = { + pairDevice: () => Promise<boolean>; + setIsPaired: (v: PairingStatus) => void; + transportResponse: AnyJson; + executeLedgerLoop: ( + appName: string, + tasks: LedgerTask[], + options?: AnyJson + ) => Promise<void>; + handleNewStatusCode: (ack: string, statusCode: LedgerStatusCode) => void; + setIsExecuting: (v: boolean) => void; + resetStatusCodes: () => void; + getIsExecuting: () => boolean; + getStatusCodes: () => LedgerResponse[]; + getTransport: () => AnyJson; + ledgerAccountExists: (a: string) => boolean; + addLedgerAccount: (a: string, i: number) => LedgerAccount | null; + removeLedgerAccount: (a: string) => void; + renameLedgerAccount: (a: string, name: string) => void; + getLedgerAccount: (a: string) => LedgerAccount | null; + isPaired: PairingStatus; + ledgerAccounts: LedgerAccount[]; + getFeedback: () => FeedbackMessage; + setFeedback: (s: MaybeString, helpKey?: MaybeString) => void; + resetFeedback: () => void; + handleUnmount: () => void; +}; + +export interface FeedbackMessage { + message: MaybeString; + helpKey?: MaybeString; +} + +export type LedgerStatusCode = + | 'GettingAddress' + | 'ReceivedAddress' + | 'SigningPayload' + | 'SignedPayload' + | 'DeviceTimeout' + | 'NestingNotSupported' + | 'WrongTransaction' + | 'DeviceNotConnected' + | 'DeviceLocked' + | 'TransactionRejected' + | 'AppNotOpenContinue' + | 'AppNotOpen'; + +export interface LedgerResponse { + ack: string; + statusCode: LedgerStatusCode; + body?: AnyJson; + options?: AnyJson; +} + +export type LedgerTask = 'get_address' | 'sign_tx'; + +export type PairingStatus = 'paired' | 'unpaired' | 'unknown'; + +export interface LedgerAddress { + address: string; + index: number; + name: string; + network: NetworkName; + pubKey: string; +} + +export type LedgerApp = { + network: NetworkName; + appName: string; + Icon: FunctionComponent<SVGProps<SVGSVGElement>>; +}; + +export type VaultHardwareContextInterface = { + vaultAccountExists: (a: string) => boolean; + addVaultAccount: (a: string, i: number) => LedgerAccount | null; + removeVaultAccount: (a: string) => void; + renameVaultAccount: (a: string, name: string) => void; + getVaultAccount: (a: string) => LedgerAccount | null; + vaultAccounts: VaultAccount[]; +}; diff --git a/src/contexts/Help/defaults.ts b/src/contexts/Help/defaults.ts index e85b5e477d..4c2411813d 100644 --- a/src/contexts/Help/defaults.ts +++ b/src/contexts/Help/defaults.ts @@ -1,16 +1,14 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only +/* eslint-disable @typescript-eslint/no-unused-vars */ -import { HelpContextInterface } from './types'; +import type { HelpContextInterface } from './types'; export const defaultHelpContext: HelpContextInterface = { - // eslint-disable-next-line - openHelpWith: (d, c) => {}, + openHelp: (key) => {}, closeHelp: () => {}, - // eslint-disable-next-line - setStatus: (s) => {}, - // eslint-disable-next-line - setDefinition: (d) => {}, - status: 0, + setStatus: (status) => {}, + setDefinition: (definition) => {}, + status: 'closed', definition: null, }; diff --git a/src/contexts/Help/index.tsx b/src/contexts/Help/index.tsx index cf27c43301..270a1efb92 100644 --- a/src/contexts/Help/index.tsx +++ b/src/contexts/Help/index.tsx @@ -1,33 +1,27 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import React, { useEffect, useState } from 'react'; -import { MaybeString } from 'types'; +import React, { useState } from 'react'; +import type { MaybeString } from 'types'; +import { useEffectIgnoreInitial } from '@polkadot-cloud/react/hooks'; import * as defaults from './defaults'; -import { - HelpConfig, +import type { HelpContextInterface, HelpContextProps, HelpContextState, + HelpStatus, } from './types'; -export const HelpContext = React.createContext<HelpContextInterface>( - defaults.defaultHelpContext -); - -export const useHelp = () => React.useContext(HelpContext); - -export const HelpProvider = (props: HelpContextProps) => { +export const HelpProvider = ({ children }: HelpContextProps) => { // help module state const [state, setState] = useState<HelpContextState>({ - status: 0, + status: 'closed', definition: null, - config: {}, }); // when fade out completes, reset active definiton - useEffect(() => { - if (state.status === 0) { + useEffectIgnoreInitial(() => { + if (state.status === 'closed') { setState({ ...state, definition: null, @@ -36,41 +30,38 @@ export const HelpProvider = (props: HelpContextProps) => { }, [state.status]); const setDefinition = (definition: MaybeString) => { - const _state = { + setState({ ...state, definition, - }; - setState(_state); + }); }; - const setStatus = (newStatus: number) => { - const _state = { + const setStatus = (newStatus: HelpStatus) => { + setState({ ...state, status: newStatus, - }; - setState(_state); + }); }; - const openHelpWith = (definition: MaybeString, _config: HelpConfig = {}) => { + const openHelp = (definition: MaybeString) => { setState({ ...state, definition, - status: 1, - config: _config, + status: 'open', }); }; const closeHelp = () => { setState({ ...state, - status: 2, + status: 'closing', }); }; return ( <HelpContext.Provider value={{ - openHelpWith, + openHelp, closeHelp, setStatus, setDefinition, @@ -78,7 +69,13 @@ export const HelpProvider = (props: HelpContextProps) => { definition: state.definition, }} > - {props.children} + {children} </HelpContext.Provider> ); }; + +export const HelpContext = React.createContext<HelpContextInterface>( + defaults.defaultHelpContext +); + +export const useHelp = () => React.useContext(HelpContext); diff --git a/src/contexts/Help/types.ts b/src/contexts/Help/types.ts index 124de4ec24..d51c07ee21 100644 --- a/src/contexts/Help/types.ts +++ b/src/contexts/Help/types.ts @@ -1,18 +1,18 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import { ReactNode } from 'react'; -import { MaybeString } from 'types'; +import type { ReactNode } from 'react'; +import type { MaybeString } from 'types'; -export type HelpItems = Array<HelpItem>; +export type HelpItems = HelpItem[]; export interface HelpItem { key?: string; - definitions?: Array<string>; + definitions?: string[]; external?: ExternalItems; } -export type ExternalItems = Array<ExternalItem>; +export type ExternalItems = ExternalItem[]; export type ExternalItem = [string, string, string]; export type DefinitionWithKeys = { @@ -26,19 +26,20 @@ export interface ExternalWithKeys { website?: string; } +export type HelpStatus = 'closed' | 'open' | 'closing'; + export interface HelpContextInterface { - openHelpWith: (d: MaybeString, c: HelpConfig) => void; + openHelp: (d: MaybeString) => void; closeHelp: () => void; - setStatus: (s: number) => void; + setStatus: (s: HelpStatus) => void; setDefinition: (d: MaybeString) => void; - status: number; + status: HelpStatus; definition: MaybeString; } export interface HelpContextState { - status: number; + status: HelpStatus; definition: MaybeString; - config: HelpConfig; } export interface HelpContextProps { diff --git a/src/contexts/Identities/defaults.ts b/src/contexts/Identities/defaults.ts new file mode 100644 index 0000000000..6a1ed169a3 --- /dev/null +++ b/src/contexts/Identities/defaults.ts @@ -0,0 +1,10 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only +/* eslint-disable @typescript-eslint/no-unused-vars */ + +import type { IdentitiesContextInterface } from './types'; + +export const defaultIdentitiesContext: IdentitiesContextInterface = { + fetchIdentitiesMetaBatch: (k, v, r) => {}, + meta: {}, +}; diff --git a/src/contexts/Identities/index.tsx b/src/contexts/Identities/index.tsx new file mode 100644 index 0000000000..856c12707a --- /dev/null +++ b/src/contexts/Identities/index.tsx @@ -0,0 +1,198 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { setStateWithRef } from '@polkadot-cloud/utils'; +import React, { useEffect, useRef, useState } from 'react'; +import type { AnyApi, AnyMetaBatch } from 'types'; +import { useApi } from '../Api'; +import { defaultIdentitiesContext } from './defaults'; +import type { IdentitiesContextInterface } from './types'; + +export const IdentitiesProvider = ({ + children, +}: { + children: React.ReactNode; +}) => { + const { isReady, api } = useApi(); + + // stores the meta data batches for validator lists + const [identitiesMetaBatches, setIdentitiesMetaBatch] = + useState<AnyMetaBatch>({}); + const identitiesMetaBatchesRef = useRef(identitiesMetaBatches); + + // stores the meta batch subscriptions for validator lists + const identitiesSubsRef = useRef<AnyApi>({}); + + // unsubscribe from any validator meta batches + useEffect( + () => () => { + Object.values(identitiesSubsRef.current).map((batch: AnyMetaBatch) => + Object.entries(batch).map(([, v]: AnyApi) => v()) + ); + }, + [] + ); + + /* + Fetches a new batch of subscribed accounts metadata. Stores the returning + metadata alongside the unsubscribe function in state. + structure: + { + key: { + [ + { + addresses [], + identities: [], + } + ] + }, + }; + */ + const fetchIdentitiesMetaBatch = async ( + key: string, + addresses: string[], + refetch = false + ) => { + if (!isReady || !api) { + return; + } + + if (!addresses.length) { + return; + } + + if (!refetch) { + // if already exists, do not re-fetch + if (identitiesMetaBatchesRef.current[key] !== undefined) { + return; + } + } else { + // tidy up if existing batch exists + const updatedMetaBatches: AnyMetaBatch = { + ...identitiesMetaBatchesRef.current, + }; + delete updatedMetaBatches[key]; + setStateWithRef( + updatedMetaBatches, + setIdentitiesMetaBatch, + identitiesMetaBatchesRef + ); + if (identitiesSubsRef.current[key] !== undefined) { + for (const unsub of identitiesSubsRef.current[key]) { + unsub(); + } + } + } + + // store batch addresses + const batchesUpdated = Object.assign(identitiesMetaBatchesRef.current); + batchesUpdated[key] = {}; + batchesUpdated[key].addresses = addresses; + setStateWithRef( + { ...batchesUpdated }, + setIdentitiesMetaBatch, + identitiesMetaBatchesRef + ); + + const subscribeToIdentities = async (addr: string[]) => { + const unsub = await api.query.identity.identityOf.multi<AnyApi>( + addr, + (_identities) => { + const identities = []; + for (let i = 0; i < _identities.length; i++) { + identities.push(_identities[i].toHuman()); + } + const updated = Object.assign(identitiesMetaBatchesRef.current); + updated[key].identities = identities; + setStateWithRef( + { ...updated }, + setIdentitiesMetaBatch, + identitiesMetaBatchesRef + ); + } + ); + return unsub; + }; + + const subscribeToSuperIdentities = async (addr: string[]) => { + const unsub = await api.query.identity.superOf.multi<AnyApi>( + addr, + async (result) => { + // determine where supers exist + const supers: AnyApi = []; + const supersWithIdentity: AnyApi = []; + + for (let i = 0; i < result.length; i++) { + const item = result[i].toHuman(); + supers.push(item); + if (item !== null) { + supersWithIdentity.push(i); + } + } + + // get supers one-off multi query + const query = supers + .filter((s: AnyApi) => s !== null) + .map((s: AnyApi) => s[0]); + + ( + await api.query.identity.identityOf.multi<AnyApi>( + query, + (_identities) => { + for (let j = 0; j < _identities.length; j++) { + const identity = _identities[j].toHuman(); + // inject identity into super array + supers[supersWithIdentity[j]].identity = identity; + } + } + ) + )(); + + const updated = Object.assign(identitiesMetaBatchesRef.current); + updated[key].supers = supers; + setStateWithRef( + { ...updated }, + setIdentitiesMetaBatch, + identitiesMetaBatchesRef + ); + } + ); + return unsub; + }; + + await Promise.all([ + subscribeToIdentities(addresses), + subscribeToSuperIdentities(addresses), + ]).then((unsubs: AnyApi) => { + addMetaBatchUnsubs(key, unsubs); + }); + }; + + /* + * Helper function to add mataBatch unsubs by key. + */ + const addMetaBatchUnsubs = (key: string, unsubs: AnyApi) => { + const identityUnsubs = identitiesSubsRef.current; + const keyUnsubs = identityUnsubs[key] ?? []; + + keyUnsubs.push(...unsubs); + identityUnsubs[key] = keyUnsubs; + identitiesSubsRef.current = identityUnsubs; + }; + + return ( + <IdentitiesContext.Provider + value={{ + fetchIdentitiesMetaBatch, + meta: identitiesMetaBatchesRef.current, + }} + > + {children} + </IdentitiesContext.Provider> + ); +}; + +export const IdentitiesContext = + React.createContext<IdentitiesContextInterface>(defaultIdentitiesContext); + +export const useIdentities = () => React.useContext(IdentitiesContext); diff --git a/src/contexts/Identities/types.ts b/src/contexts/Identities/types.ts new file mode 100644 index 0000000000..4af71a55d9 --- /dev/null +++ b/src/contexts/Identities/types.ts @@ -0,0 +1,9 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import type { AnyMetaBatch } from 'types'; + +export interface IdentitiesContextInterface { + fetchIdentitiesMetaBatch: (k: string, v: string[], r?: boolean) => void; + meta: AnyMetaBatch; +} diff --git a/src/contexts/Menu/defaults.ts b/src/contexts/Menu/defaults.ts index 701005e8f4..a0b4c25659 100644 --- a/src/contexts/Menu/defaults.ts +++ b/src/contexts/Menu/defaults.ts @@ -1,16 +1,14 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only +/* eslint-disable @typescript-eslint/no-unused-vars */ -import { MenuContextInterface } from './types'; +import type { MenuContextInterface } from './types'; export const defaultMenuContext: MenuContextInterface = { openMenu: () => {}, closeMenu: () => {}, - // eslint-disable-next-line setMenuPosition: (r) => {}, - // eslint-disable-next-line checkMenuPosition: (r) => {}, - // eslint-disable-next-line setMenuItems: (items) => {}, open: 0, show: 0, diff --git a/src/contexts/Menu/index.tsx b/src/contexts/Menu/index.tsx index e42ae51104..6c93cd6417 100644 --- a/src/contexts/Menu/index.tsx +++ b/src/contexts/Menu/index.tsx @@ -1,14 +1,10 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import React, { RefObject, useState } from 'react'; +import type { RefObject } from 'react'; +import React, { useState } from 'react'; import { defaultMenuContext } from './defaults'; -import { MenuContextInterface } from './types'; - -export const MenuContext = - React.createContext<MenuContextInterface>(defaultMenuContext); - -export const useMenu = () => React.useContext(MenuContext); +import type { MenuContextInterface } from './types'; export const MenuProvider = ({ children }: { children: React.ReactNode }) => { const [open, setOpen] = useState(0); @@ -89,3 +85,8 @@ export const MenuProvider = ({ children }: { children: React.ReactNode }) => { </MenuContext.Provider> ); }; + +export const MenuContext = + React.createContext<MenuContextInterface>(defaultMenuContext); + +export const useMenu = () => React.useContext(MenuContext); diff --git a/src/contexts/Menu/types.ts b/src/contexts/Menu/types.ts index 517218d6ba..229e917b20 100644 --- a/src/contexts/Menu/types.ts +++ b/src/contexts/Menu/types.ts @@ -1,7 +1,8 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import React, { RefObject } from 'react'; +import type React from 'react'; +import type { RefObject } from 'react'; export interface MenuContextInterface { openMenu: () => void; diff --git a/src/contexts/Migrate/index.tsx b/src/contexts/Migrate/index.tsx new file mode 100644 index 0000000000..4f5713ff4e --- /dev/null +++ b/src/contexts/Migrate/index.tsx @@ -0,0 +1,81 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import React, { useState } from 'react'; +import { NetworkList } from 'config/networks'; +import { AppVersion } from 'consts'; +import { useApi } from 'contexts/Api'; +import { useUi } from 'contexts/UI'; +import { useEffectIgnoreInitial } from '@polkadot-cloud/react/hooks'; +import { useImportedAccounts } from 'contexts/Connect/ImportedAccounts'; + +export const MigrateProvider = ({ + children, +}: { + children: React.ReactNode; +}) => { + const { isReady } = useApi(); + const { isNetworkSyncing } = useUi(); + const { accounts } = useImportedAccounts(); + + // The local app version of the current user. + const localAppVersion = localStorage.getItem('app_version'); + + // Store whether the migration check has taken place. + const [done, setDone] = useState<boolean>(localAppVersion === AppVersion); + + // Removes the previous nominator setup objects from local storage. + const removeDeprecatedNominatorSetups = () => + Object.values(NetworkList).forEach((n: any) => { + for (const a of accounts) + localStorage.removeItem(`${n.name}_stake_setup_${a.address}`); + }); + + // Removes the previous pool setup objects from local storage. + const removeDeprecatedPoolSetups = () => + Object.values(NetworkList).forEach((n: any) => { + for (const a of accounts) + localStorage.removeItem(`${n.name}_pool_setup_${a.address}`); + }); + + // Removes the previous active proxies from local storage. + const removeDeprecatedActiveProxies = () => + Object.values(NetworkList).forEach((n: any) => { + localStorage.removeItem(`${n.name}_active_proxy`); + }); + + useEffectIgnoreInitial(() => { + if (isReady && !isNetworkSyncing && !done) { + // Carry out migrations if local version is different to current version. + if (localAppVersion !== AppVersion) { + // Added in 1.0.2. + // + // Remove local language resources. No expiry. + localStorage.removeItem('lng_resources'); + + // Added in 1.0.4. + // + // Remove legacy local nominator setup and pool setup items. + removeDeprecatedNominatorSetups(); + removeDeprecatedPoolSetups(); + + // Added in 1.0.8. + // + // Remove legacy local active proxy records. + removeDeprecatedActiveProxies(); + + // Finally, + // + // Update local version to current app version. + localStorage.setItem('app_version', AppVersion); + setDone(true); + } + } + }, [isNetworkSyncing]); + + return ( + <MigrateContext.Provider value={{}}>{children}</MigrateContext.Provider> + ); +}; + +export const MigrateContext = React.createContext<any>(null); diff --git a/src/contexts/Modal/defaults.ts b/src/contexts/Modal/defaults.ts deleted file mode 100644 index ca64ff8e9a..0000000000 --- a/src/contexts/Modal/defaults.ts +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { ModalContextInterface } from './types'; - -export const defaultModalContext: ModalContextInterface = { - status: 0, - // eslint-disable-next-line - setStatus: (status) => {}, - // eslint-disable-next-line - openModalWith: (m, c, s) => {}, - // eslint-disable-next-line - setModalHeight: (v) => {}, - setResize: () => {}, - modal: 'ConnectAccounts', - config: {}, - size: 'large', - height: 0, - resize: 0, -}; diff --git a/src/contexts/Modal/index.tsx b/src/contexts/Modal/index.tsx deleted file mode 100644 index 6676ebd152..0000000000 --- a/src/contexts/Modal/index.tsx +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { useTxFees } from 'contexts/TxFees'; -import React, { useEffect, useState } from 'react'; -import { defaultModalContext } from './defaults'; -import { ModalConfig, ModalContextInterface, ModalContextState } from './types'; - -// default modal content -const DEFAULT_MODAL_COMPONENT = 'ConnectAccounts'; - -export const ModalContext = - React.createContext<ModalContextInterface>(defaultModalContext); - -export const useModal = () => React.useContext(ModalContext); - -// wrapper component to provide components with context -export const ModalProvider = ({ children }: { children: React.ReactNode }) => { - const { notEnoughFunds } = useTxFees(); - - const [state, setState] = useState<ModalContextState>({ - status: 0, - modal: DEFAULT_MODAL_COMPONENT, - config: {}, - size: 'large', - height: 0, - resize: 0, - }); - - useEffect(() => { - setResize(); - }, [state.status, notEnoughFunds]); - - const setStatus = (newStatus: number) => { - const _state = { - ...state, - status: newStatus, - resize: state.resize + 1, - height: newStatus === 0 ? 0 : state.height, - }; - setState(_state); - }; - - const openModalWith = ( - modal: string, - _config: ModalConfig = {}, - size = 'large' - ) => { - setState({ - ...state, - modal, - status: 1, - config: _config, - size, - resize: state.resize + 1, - }); - }; - - const setModalHeight = (h: number) => { - if (state.status === 0) return; - // set maximum height to 80% of window height - const maxHeight = window.innerHeight * 0.8; - h = h > maxHeight ? maxHeight : h; - setState({ - ...state, - height: h, - }); - }; - - const setResize = () => { - // increments resize to trigger a height transition - setState({ - ...state, - resize: state.resize + 1, - }); - }; - - return ( - <ModalContext.Provider - value={{ - status: state.status, - setStatus, - openModalWith, - setModalHeight, - setResize, - modal: state.modal, - config: state.config, - size: state.size, - height: state.height, - resize: state.resize, - }} - > - {children} - </ModalContext.Provider> - ); -}; diff --git a/src/contexts/Modal/types.ts b/src/contexts/Modal/types.ts deleted file mode 100644 index ed5b252db3..0000000000 --- a/src/contexts/Modal/types.ts +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -export interface ModalContextInterface extends ModalContextState { - setStatus: (status: number) => void; - openModalWith: (modal: string, config?: ModalConfig, size?: string) => void; - setModalHeight: (v: number) => void; - setResize: () => void; -} - -export interface ModalContextState { - status: number; - modal: string; - config: ModalConfig; - size: string; - height: number; - resize: number; -} - -export type ModalConfig = Record<string, string | any>; diff --git a/src/contexts/Network/defaults.ts b/src/contexts/Network/defaults.ts index 0fc2bbf64a..19514ffb5b 100644 --- a/src/contexts/Network/defaults.ts +++ b/src/contexts/Network/defaults.ts @@ -1,19 +1,10 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import BN from 'bn.js'; -import { NetworkMetrics, NetworkMetricsContextInterface } from './types'; +import { NetworkList } from 'config/networks'; -export const metrics: NetworkMetrics = { - activeEra: { - index: 0, - start: 0, - }, - totalIssuance: new BN(0), - // auctionCounter: new BN(0), - // earliestStoredSession: new BN(0), -}; - -export const defaultNetworkContext: NetworkMetricsContextInterface = { - metrics, +export const defaultNetworkContext = { + network: NetworkList.cereMainnet.name, + networkData: NetworkList.cereMainnet, + switchNetwork: () => {}, }; diff --git a/src/contexts/Network/index.tsx b/src/contexts/Network/index.tsx index 8a4530893d..70a72e2e91 100644 --- a/src/contexts/Network/index.tsx +++ b/src/contexts/Network/index.tsx @@ -1,99 +1,86 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import React, { useEffect, useState } from 'react'; -import { AnyApi } from 'types'; -import { useApi } from '../Api'; -import * as defaults from './defaults'; -import { NetworkMetrics, NetworkMetricsContextInterface } from './types'; +import { extractUrlValue, varToUrlHash } from '@polkadot-cloud/utils'; +import React, { createContext, useContext, useState } from 'react'; +import { NetworkList } from 'config/networks'; +import { DefaultNetwork } from 'consts'; +import type { NetworkName } from 'types'; +import type { NetworkState } from 'contexts/Api/types'; +import type { NetworkContextInterface } from './types'; +import { defaultNetworkContext } from './defaults'; -export const NetworkMetricsContext = - React.createContext<NetworkMetricsContextInterface>( - defaults.defaultNetworkContext - ); - -export const useNetworkMetrics = () => React.useContext(NetworkMetricsContext); - -export const NetworkMetricsProvider = ({ +console.log('Default network context'); +console.log(defaultNetworkContext); +export const NetworkProvider = ({ children, }: { children: React.ReactNode; }) => { - const { isReady, api, status } = useApi(); + // Get the initial network and prepare meta tags if necessary. + const getInitialNetwork = () => { + const urlNetworkRaw = extractUrlValue('n'); - useEffect(() => { - if (status === 'connecting') { - setMetrics(defaults.metrics); - } - }, [status]); - - // store network metrics in state - const [metrics, setMetrics] = useState<NetworkMetrics>(defaults.metrics); + const urlNetworkValid = !!Object.values(NetworkList).find( + (n) => n.name === urlNetworkRaw + ); - // store network metrics unsubscribe - const [unsub, setUnsub] = useState<AnyApi>(undefined); + // use network from url if valid. + if (urlNetworkValid) { + const urlNetwork = urlNetworkRaw as NetworkName; - // manage unsubscribe - useEffect(() => { - subscribeToNetworkMetrics(); - return () => { - if (unsub) { - unsub(); + if (urlNetworkValid) { + return urlNetwork; } - }; - }, [isReady]); - - // active subscription - const subscribeToNetworkMetrics = async () => { - if (!api) return; + } + // fallback to localStorage network if there. + const localNetwork: NetworkName = localStorage.getItem( + 'network' + ) as NetworkName; + const localNetworkValid = !!Object.values(NetworkList).find( + (n) => n.name === localNetwork + ); + return localNetworkValid ? localNetwork : DefaultNetwork; + }; - if (isReady) { - const _unsub = await api.queryMulti( - [ - api.query.staking.activeEra, - api.query.balances.totalIssuance, - // api.query.auctions.auctionCounter, - // api.query.paraSessionInfo.earliestStoredSession, - ], - ([activeEra, _totalIssuance]: // _auctionCounter, - // _earliestStoredSession, - AnyApi) => { - // determine activeEra: toString used as alternative to `toHuman`, that puts commas in numbers - let _activeEra = activeEra - .unwrapOrDefault({ - index: 0, - start: 0, - }) - .toString(); + // handle network switching + const switchNetwork = (name: NetworkName) => { + console.warn(`Switching network to name: ${name}`); + console.warn(NetworkList); - // convert JSON string to object - _activeEra = JSON.parse(_activeEra); + setNetwork({ + name, + meta: NetworkList.cereMainnet, + }); - const _metrics = { - activeEra: _activeEra, - totalIssuance: _totalIssuance.toBn(), - // auctionCounter: new BN(_auctionCounter.toString()), - // earliestStoredSession: new BN(_earliestStoredSession.toString()), - }; - setMetrics(_metrics); - } - ); - setUnsub(_unsub); - } + // update url `n` if needed. + varToUrlHash('n', name, false); }; + // Store the initial active network. + const initialNetwork = getInitialNetwork(); + console.warn('Initial network'); + console.warn(NetworkList); + const [network, setNetwork] = useState<NetworkState>({ + name: initialNetwork, + meta: NetworkList.cereMainnet, + }); + return ( - <NetworkMetricsContext.Provider + <NetworkContext.Provider value={{ - metrics: { - activeEra: metrics.activeEra, - totalIssuance: metrics.totalIssuance, - // auctionCounter: metrics.auctionCounter, - // earliestStoredSession: metrics.earliestStoredSession, - }, + network: network.name, + networkData: network.meta, + switchNetwork, }} > {children} - </NetworkMetricsContext.Provider> + </NetworkContext.Provider> ); }; + +export const NetworkContext = createContext<NetworkContextInterface>( + defaultNetworkContext +); + +export const useNetwork = () => useContext(NetworkContext); diff --git a/src/contexts/Network/types.ts b/src/contexts/Network/types.ts index 58b28da6b4..1589c7cab1 100644 --- a/src/contexts/Network/types.ts +++ b/src/contexts/Network/types.ts @@ -1,18 +1,10 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import BN from 'bn.js'; +import type { Network, NetworkName } from 'types'; -export interface NetworkMetricsContextInterface { - metrics: NetworkMetrics; -} - -export interface NetworkMetrics { - activeEra: { - index: number; - start: number; - }; - totalIssuance: BN; - // auctionCounter: BN; - // earliestStoredSession: BN; +export interface NetworkContextInterface { + network: NetworkName; + networkData: Network; + switchNetwork: (network: NetworkName) => void; } diff --git a/src/contexts/NetworkMetrics/defaults.ts b/src/contexts/NetworkMetrics/defaults.ts new file mode 100644 index 0000000000..cc036ba693 --- /dev/null +++ b/src/contexts/NetworkMetrics/defaults.ts @@ -0,0 +1,26 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import BigNumber from 'bignumber.js'; +import type { + ActiveEra, + NetworkMetrics, + NetworkMetricsContextInterface, +} from './types'; + +export const activeEra: ActiveEra = { + index: new BigNumber(0), + start: new BigNumber(0), +}; +export const metrics: NetworkMetrics = { + totalIssuance: new BigNumber(0), + // auctionCounter: new BigNumber(0), + earliestStoredSession: new BigNumber(0), + fastUnstakeErasToCheckPerBlock: 0, + minimumActiveStake: new BigNumber(0), +}; + +export const defaultNetworkContext: NetworkMetricsContextInterface = { + activeEra, + metrics, +}; diff --git a/src/contexts/NetworkMetrics/index.tsx b/src/contexts/NetworkMetrics/index.tsx new file mode 100644 index 0000000000..58ea7cdc58 --- /dev/null +++ b/src/contexts/NetworkMetrics/index.tsx @@ -0,0 +1,155 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { setStateWithRef } from '@polkadot-cloud/utils'; +import BigNumber from 'bignumber.js'; +import React, { useRef, useState } from 'react'; +import type { AnyApi, AnyJson } from 'types'; +import { useEffectIgnoreInitial } from '@polkadot-cloud/react/hooks'; +import { useNetwork } from 'contexts/Network'; +import { useApi } from '../Api'; +import * as defaults from './defaults'; +import type { + ActiveEra, + NetworkMetrics, + NetworkMetricsContextInterface, +} from './types'; + +export const NetworkMetricsProvider = ({ + children, +}: { + children: React.ReactNode; +}) => { + const { network } = useNetwork(); + const { isReady, api } = useApi(); + + // Store active era in state. + const [activeEra, setActiveEra] = useState<ActiveEra>(defaults.activeEra); + const activeEraRef = useRef(activeEra); + + // Store network metrics in state. + const [metrics, setMetrics] = useState<NetworkMetrics>(defaults.metrics); + const metricsRef = useRef(metrics); + + // Store unsubscribe objects. + const unsubsRef = useRef<AnyApi[]>([]); + + // active subscription + const initialiseSubscriptions = async () => { + if (!api) return; + + if (isReady) { + const subscribeToMetrics = async () => { + const unsub = await api.queryMulti( + [ + api.query.balances.totalIssuance, + // api.query.auctions.auctionCounter, + api.query.paraSessionInfo.earliestStoredSession, + api.query.fastUnstake.erasToCheckPerBlock, + api.query.staking.minimumActiveStake, + ], + ([ + totalIssuance, + earliestStoredSession, + erasToCheckPerBlock, + minimumActiveStake, + ]: AnyApi) => { + setStateWithRef( + { + totalIssuance: new BigNumber(totalIssuance.toString()), + // auctionCounter: new BigNumber('0'.toString()), + earliestStoredSession: new BigNumber( + earliestStoredSession.toString() + ), + fastUnstakeErasToCheckPerBlock: erasToCheckPerBlock.toNumber(), + minimumActiveStake: new BigNumber( + minimumActiveStake.toString() + ), + }, + setMetrics, + metricsRef + ); + } + ); + return unsub; + }; + + const subscribeToActiveEra = async () => { + const unsub = await api.query.staking.activeEra((result: AnyApi) => { + // determine activeEra: toString used as alternative to `toHuman`, that puts commas in + // numbers + let newActiveEra = result + .unwrapOrDefault({ + index: 0, + start: 0, + }) + .toString(); + + newActiveEra = JSON.parse(newActiveEra); + setStateWithRef( + { + index: new BigNumber(newActiveEra.index), + start: new BigNumber(newActiveEra.start), + }, + setActiveEra, + activeEraRef + ); + }); + return unsub; + }; + + // initiate subscription, add to unsubs. + await Promise.all([subscribeToMetrics(), subscribeToActiveEra()]).then( + (u: any) => { + unsubsRef.current = unsubsRef.current.concat(u); + } + ); + } + }; + + // Unsubscribe from unsubs + const unsubscribe = () => { + Object.values(unsubsRef.current).forEach((unsub: AnyJson) => { + unsub(); + }); + }; + + // Set defaults for all metrics. + const handleResetMetrics = () => { + unsubscribe(); + unsubsRef.current = []; + setStateWithRef(defaults.activeEra, setActiveEra, activeEraRef); + setStateWithRef(defaults.metrics, setMetrics, metricsRef); + }; + + // manage unsubscribe + useEffectIgnoreInitial(() => { + initialiseSubscriptions(); + return () => { + unsubscribe(); + }; + }, [isReady]); + + // Reset active era and metrics on network change. + useEffectIgnoreInitial(() => { + handleResetMetrics(); + }, [network]); + + return ( + <NetworkMetricsContext.Provider + value={{ + activeEra: activeEraRef.current, + metrics: metricsRef.current, + }} + > + {children} + </NetworkMetricsContext.Provider> + ); +}; + +export const NetworkMetricsContext = + React.createContext<NetworkMetricsContextInterface>( + defaults.defaultNetworkContext + ); + +export const useNetworkMetrics = () => React.useContext(NetworkMetricsContext); diff --git a/src/contexts/NetworkMetrics/types.ts b/src/contexts/NetworkMetrics/types.ts new file mode 100644 index 0000000000..6cb64fe838 --- /dev/null +++ b/src/contexts/NetworkMetrics/types.ts @@ -0,0 +1,22 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import type BigNumber from 'bignumber.js'; + +export interface NetworkMetricsContextInterface { + activeEra: ActiveEra; + metrics: NetworkMetrics; +} + +export interface NetworkMetrics { + totalIssuance: BigNumber; + // auctionCounter: BigNumber; + earliestStoredSession: BigNumber; + fastUnstakeErasToCheckPerBlock: number; + minimumActiveStake: BigNumber; +} + +export interface ActiveEra { + index: BigNumber; + start: BigNumber; +} diff --git a/src/contexts/Notifications/defaults.ts b/src/contexts/Notifications/defaults.ts index 89f7559a79..4493a040f6 100644 --- a/src/contexts/Notifications/defaults.ts +++ b/src/contexts/Notifications/defaults.ts @@ -1,12 +1,11 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only +/* eslint-disable @typescript-eslint/no-unused-vars */ -import { NotificationsContextInterface } from './types'; +import type { NotificationsContextInterface } from './types'; export const defaultNotificationsContext: NotificationsContextInterface = { - // eslint-disable-next-line addNotification: (n) => {}, - // eslint-disable-next-line removeNotification: (n) => {}, notifications: [], }; diff --git a/src/contexts/Notifications/index.tsx b/src/contexts/Notifications/index.tsx index 99a8fbcb1d..297c6c9d5f 100644 --- a/src/contexts/Notifications/index.tsx +++ b/src/contexts/Notifications/index.tsx @@ -1,28 +1,22 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only +import { setStateWithRef } from '@polkadot-cloud/utils'; +import type { ReactNode } from 'react'; import React, { useRef, useState } from 'react'; -import { setStateWithRef } from 'Utils'; import { defaultNotificationsContext } from './defaults'; -import { +import type { NotificationInterface, NotificationItem, NotificationsContextInterface, } from './types'; -export const NotificationsContext = - React.createContext<NotificationsContextInterface>( - defaultNotificationsContext - ); - -export const useNotifications = () => React.useContext(NotificationsContext); - export const NotificationsProvider = ({ children, }: { - children: React.ReactNode; + children: ReactNode; }) => { - const [index, _setIndex] = useState(0); + const [index, setIndexState] = useState<number>(0); const [notifications, setNotifications] = useState<NotificationInterface[]>( [] ); @@ -30,28 +24,28 @@ export const NotificationsProvider = ({ const indexRef = useRef(index); const notificationsRef = useRef(notifications); - const setIndex = (_index: number) => { - indexRef.current = _index; - _setIndex(_index); + const setIndex = (i: number) => { + indexRef.current = i; + setIndexState(i); }; - const addNotification = (_n: NotificationItem) => { - const _notifications: NotificationInterface[] = [ + const addNotification = (newNotification: NotificationItem) => { + const newNotifications: NotificationInterface[] = [ ...notificationsRef.current, ]; const newIndex: number = indexRef.current + 1; - _notifications.push({ + newNotifications.push({ index: newIndex, item: { - ..._n, + ...newNotification, index: newIndex, }, }); setIndex(newIndex); - setStateWithRef(_notifications, setNotifications, notificationsRef); + setStateWithRef(newNotifications, setNotifications, notificationsRef); setTimeout(() => { removeNotification(newIndex); }, 3000); @@ -59,11 +53,11 @@ export const NotificationsProvider = ({ return newIndex; }; - const removeNotification = (_index: number) => { - const _notifications = notificationsRef.current.filter( - (item: NotificationInterface) => item.index !== _index + const removeNotification = (i: number) => { + const newNotifications = notificationsRef.current.filter( + (item: NotificationInterface) => item.index !== i ); - setStateWithRef(_notifications, setNotifications, notificationsRef); + setStateWithRef(newNotifications, setNotifications, notificationsRef); }; return ( @@ -78,3 +72,10 @@ export const NotificationsProvider = ({ </NotificationsContext.Provider> ); }; + +export const NotificationsContext = + React.createContext<NotificationsContextInterface>( + defaultNotificationsContext + ); + +export const useNotifications = () => React.useContext(NotificationsContext); diff --git a/src/contexts/Notifications/types.ts b/src/contexts/Notifications/types.ts index 90b0983519..8936af26cc 100644 --- a/src/contexts/Notifications/types.ts +++ b/src/contexts/Notifications/types.ts @@ -1,5 +1,5 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only export interface NotificationsContextInterface { addNotification: (n: NotificationItem) => void; diff --git a/src/contexts/Overlay/defaults.tsx b/src/contexts/Overlay/defaults.tsx index c666f3f0e9..70c979ffd3 100644 --- a/src/contexts/Overlay/defaults.tsx +++ b/src/contexts/Overlay/defaults.tsx @@ -1,7 +1,7 @@ // Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors // SPDX-License-Identifier: Apache-2.0 -import { OverlayContextInterface } from './types'; +import type { OverlayContextInterface } from './types'; export const defaultOverlayContext: OverlayContextInterface = { // eslint-disable-next-line diff --git a/src/contexts/Overlay/index.tsx b/src/contexts/Overlay/index.tsx index 5625d3653a..6a24a6841b 100644 --- a/src/contexts/Overlay/index.tsx +++ b/src/contexts/Overlay/index.tsx @@ -3,7 +3,7 @@ import React, { useState } from 'react'; import { defaultOverlayContext } from './defaults'; -import { OverlayContextInterface } from './types'; +import type { OverlayContextInterface } from './types'; export const OverlayContext = React.createContext<OverlayContextInterface>( defaultOverlayContext diff --git a/src/contexts/Overlay/types.ts b/src/contexts/Overlay/types.ts index 2ffe9ea121..0bc5cf0d85 100644 --- a/src/contexts/Overlay/types.ts +++ b/src/contexts/Overlay/types.ts @@ -1,8 +1,8 @@ // Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors // SPDX-License-Identifier: Apache-2.0 -import React from 'react'; -import { MaybeString } from 'types'; +import type React from 'react'; +import type { MaybeString } from 'types'; export interface OverlayContextInterface { openOverlayWith: (o: React.ReactNode | null, s?: string) => void; diff --git a/src/contexts/Payouts/Utils.ts b/src/contexts/Payouts/Utils.ts new file mode 100644 index 0000000000..28bb1c53d4 --- /dev/null +++ b/src/contexts/Payouts/Utils.ts @@ -0,0 +1,98 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import BigNumber from 'bignumber.js'; +import type { AnyJson, NetworkName } from 'types'; +import type { LocalValidatorExposure } from './types'; + +// Check if local exposure entry exists for an era. +export const hasLocalEraExposure = ( + network: NetworkName, + era: string, + who: string +) => { + const current = JSON.parse( + localStorage.getItem(`${network}_era_exposures`) || '{}' + ); + return !!current?.[who]?.[era]; +}; + +// Get local exposure entry for an era. +export const getLocalEraExposure = ( + network: NetworkName, + era: string, + who: string +) => { + const current = JSON.parse( + localStorage.getItem(`${network}_era_exposures`) || '{}' + ); + return current?.[who]?.[era] || []; +}; + +// Set local exposure entry for an era. +export const setLocalEraExposure = ( + network: NetworkName, + era: string, + who: string, + exposedValidators: Record<string, LocalValidatorExposure> | null, + endEra: string +) => { + const current = JSON.parse( + localStorage.getItem(`${network}_era_exposures`) || '{}' + ); + + const whoRemoveStaleEras = Object.fromEntries( + Object.entries(current[who] || {}).filter(([k]: AnyJson) => + new BigNumber(k).isGreaterThanOrEqualTo(endEra) + ) + ); + + localStorage.setItem( + `${network}_era_exposures`, + JSON.stringify({ + ...current, + [who]: { + ...whoRemoveStaleEras, + [era]: exposedValidators, + }, + }) + ); +}; + +// Get unclaimed payouts for an account. +export const getLocalUnclaimedPayouts = (network: NetworkName, who: string) => { + const current = JSON.parse( + localStorage.getItem(`${network}_unclaimed_payouts`) || '{}' + ); + return current?.[who] || {}; +}; + +// Set local unclaimed payouts for an account. +export const setLocalUnclaimedPayouts = ( + network: NetworkName, + era: string, + who: string, + unclaimdPayouts: Record<string, string>, + endEra: string +) => { + const current = JSON.parse( + localStorage.getItem(`${network}_unclaimed_payouts`) || '{}' + ); + + const whoRemoveStaleEras = Object.fromEntries( + Object.entries(current[who] || {}).filter(([k]: AnyJson) => + new BigNumber(k).isGreaterThanOrEqualTo(endEra) + ) + ); + + localStorage.setItem( + `${network}_unclaimed_payouts`, + JSON.stringify({ + ...current, + [who]: { + ...whoRemoveStaleEras, + [era]: unclaimdPayouts, + }, + }) + ); +}; diff --git a/src/contexts/Payouts/defaults.ts b/src/contexts/Payouts/defaults.ts new file mode 100644 index 0000000000..f9d101e66a --- /dev/null +++ b/src/contexts/Payouts/defaults.ts @@ -0,0 +1,13 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only +/* eslint-disable @typescript-eslint/no-unused-vars */ + +import type { PayoutsContextInterface } from './types'; + +export const MaxSupportedPayoutEras = 7; + +export const defaultPayoutsContext: PayoutsContextInterface = { + payoutsSynced: 'unsynced', + unclaimedPayouts: null, + removeEraPayout: (era, validator) => {}, +}; diff --git a/src/contexts/Payouts/index.tsx b/src/contexts/Payouts/index.tsx new file mode 100644 index 0000000000..150f8d72c5 --- /dev/null +++ b/src/contexts/Payouts/index.tsx @@ -0,0 +1,348 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import React, { useState, useEffect, useRef } from 'react'; +import { useStaking } from 'contexts/Staking'; +import { useApi } from 'contexts/Api'; +import type { AnyApi, AnyJson, Sync } from 'types'; +import { useNetworkMetrics } from 'contexts/NetworkMetrics'; +import Worker from 'workers/stakers?worker'; +import { rmCommas, setStateWithRef } from '@polkadot-cloud/utils'; +import BigNumber from 'bignumber.js'; +import { useNetwork } from 'contexts/Network'; +import { useActiveAccounts } from 'contexts/ActiveAccounts'; +import { MaxSupportedPayoutEras, defaultPayoutsContext } from './defaults'; +import type { + LocalValidatorExposure, + PayoutsContextInterface, + UnclaimedPayouts, +} from './types'; +import { + getLocalEraExposure, + hasLocalEraExposure, + setLocalEraExposure, + setLocalUnclaimedPayouts, +} from './Utils'; + +const worker = new Worker(); + +export const PayoutsProvider = ({ + children, +}: { + children: React.ReactNode; +}) => { + const { api } = useApi(); + const { network } = useNetwork(); + const { activeEra } = useNetworkMetrics(); + const { activeAccount } = useActiveAccounts(); + const { isNominating, fetchEraStakers } = useStaking(); + + // Store active accont's payout state. + const [unclaimedPayouts, setUnclaimedPayouts] = + useState<UnclaimedPayouts>(null); + + // Track whether payouts have been fetched. + const [payoutsSynced, setPayoutsSynced] = useState<Sync>('unsynced'); + const payoutsSyncedRef = useRef(payoutsSynced); + + // Calculate eras to check for pending payouts. + const getErasInterval = () => { + const startEra = activeEra?.index.minus(1) || new BigNumber(1); + const endEra = BigNumber.max( + startEra.minus(MaxSupportedPayoutEras).plus(1), + 1 + ); + return { + startEra, + endEra, + }; + }; + + // Determine whether to keep processing a next era, or move onto checking for pending payouts. + const shouldContinueProcessing = async ( + era: BigNumber, + endEra: BigNumber + ) => { + // If there are more exposures to process, check next era. + if (new BigNumber(era).isGreaterThan(endEra)) + checkEra(new BigNumber(era).minus(1)); + // If all exposures have been processed, check for pending payouts. + else if (new BigNumber(era).isEqualTo(endEra)) { + await getUnclaimedPayouts(); + setStateWithRef('synced', setPayoutsSynced, payoutsSyncedRef); + } + }; + + // Fetch exposure data for an era, and pass the data to the worker to determine the validator the + // active account was backing in that era. + const checkEra = async (era: BigNumber) => { + if (!activeAccount) return; + + // Bypass worker if local exposure data is available. + if (hasLocalEraExposure(network, era.toString(), activeAccount)) { + // Continue processing eras, or move onto reward processing. + shouldContinueProcessing(era, getErasInterval().endEra); + } else { + const exposures = await fetchEraStakers(era.toString()); + worker.postMessage({ + task: 'processEraForExposure', + era: String(era), + who: activeAccount, + networkName: network, + exposures, + }); + } + }; + + // Handle worker message on completed exposure check. + worker.onmessage = (message: MessageEvent) => { + if (message) { + // ensure correct task received. + const { data } = message; + const { task } = data; + if (task !== 'processEraForExposure') return; + + // Exit early if network or account conditions have changed. + const { networkName, who } = data; + if (networkName !== network || who !== activeAccount) return; + const { era, exposedValidators } = data; + const { endEra } = getErasInterval(); + + // Store received era exposure data results in local storage. + setLocalEraExposure( + networkName, + era, + who, + exposedValidators, + endEra.toString() + ); + + // Continue processing eras, or move onto reward processing. + shouldContinueProcessing(era, endEra); + } + }; + + // Start pending payout process once exposure data is fetched. + const getUnclaimedPayouts = async () => { + if (!api || !activeAccount) return; + + // Accumulate eras to check, and determine all validator ledgers to fetch from exposures. + const erasValidators = []; + const { startEra, endEra } = getErasInterval(); + let erasToCheck: string[] = []; + let currentEra = startEra; + while (currentEra.isGreaterThanOrEqualTo(endEra)) { + const validators = Object.keys( + getLocalEraExposure(network, currentEra.toString(), activeAccount) + ); + erasValidators.push(...validators); + erasToCheck.push(currentEra.toString()); + currentEra = currentEra.minus(1); + } + + // Ensure no validator duplicates. + const uniqueValidators = [...new Set(erasValidators)]; + + // Ensure `erasToCheck` is in order, highest first. + erasToCheck = erasToCheck.sort((a: string, b: string) => + new BigNumber(b).minus(a).toNumber() + ); + + // Helper function to check which eras a validator was exposed in. + const validatorExposedEras = (validator: string) => { + const exposedEras: string[] = []; + for (const era of erasToCheck) + if ( + Object.values( + Object.keys(getLocalEraExposure(network, era, activeAccount)) + )?.[0] === validator + ) + exposedEras.push(era); + return exposedEras; + }; + + // Fetch controllers in order to query ledgers. + const bondedResults = + await api.query.staking.bonded.multi<AnyApi>(uniqueValidators); + const validatorControllers: Record<string, string> = {}; + for (let i = 0; i < bondedResults.length; i++) { + const ctlr = bondedResults[i].unwrapOr(null); + if (ctlr) validatorControllers[uniqueValidators[i]] = ctlr; + } + + // Fetch ledgers to determine which eras have not yet been claimed per validator. Only includes + // eras that are in `erasToCheck`. + const ledgerResults = await api.query.staking.ledger.multi<AnyApi>( + Object.values(validatorControllers) + ); + const unclaimedRewards: Record<string, string[]> = {}; + for (const ledgerResult of ledgerResults) { + const ledger = ledgerResult.unwrapOr(null)?.toHuman(); + if (ledger) { + // get claimed eras within `erasToCheck`. + const erasClaimed = ledger.claimedRewards + .map((e: string) => rmCommas(e)) + .filter( + (e: string) => + new BigNumber(e).isLessThanOrEqualTo(startEra) && + new BigNumber(e).isGreaterThanOrEqualTo(endEra) + ); + + // filter eras yet to be claimed + unclaimedRewards[ledger.stash] = erasToCheck + .map((e) => e.toString()) + .filter((r: string) => validatorExposedEras(ledger.stash).includes(r)) + .filter((r: string) => !erasClaimed.includes(r)); + } + } + + // Reformat unclaimed rewards to be { era: validators[] }. + const unclaimedByEra: Record<string, string[]> = {}; + erasToCheck.forEach((era) => { + const eraValidators: string[] = []; + Object.entries(unclaimedRewards).forEach(([validator, eras]) => { + if (eras.includes(era)) eraValidators.push(validator); + }); + if (eraValidators.length > 0) unclaimedByEra[era] = eraValidators; + }); + + // Accumulate calls needed to fetch data to calculate rewards. + const calls: AnyApi[] = []; + Object.entries(unclaimedByEra).forEach(([era, validators]) => { + if (validators.length > 0) + calls.push( + Promise.all([ + api.query.staking.erasValidatorReward<AnyApi>(era), + api.query.staking.erasRewardPoints<AnyApi>(era), + ...validators.map((validator: AnyJson) => + api.query.staking.erasValidatorPrefs<AnyApi>(era, validator) + ), + ]) + ); + }); + + // Iterate calls and determine unclaimed payouts. + const unclaimed: UnclaimedPayouts = {}; + let i = 0; + for (const [reward, points, ...prefs] of await Promise.all(calls)) { + const era = Object.keys(unclaimedByEra)[i]; + const eraTotalPayout = new BigNumber(rmCommas(reward.toHuman())); + const eraRewardPoints = points.toHuman(); + const unclaimedValidators = unclaimedByEra[era]; + + let j = 0; + for (const pref of prefs) { + const eraValidatorPrefs = pref.toHuman(); + const commission = new BigNumber( + eraValidatorPrefs.commission.replace(/%/g, '') + ).multipliedBy(0.01); + + // Get validator from era exposure data. Falls back no null if it cannot be found. + const validator = unclaimedValidators?.[j] || ''; + + const localExposed: LocalValidatorExposure | null = getLocalEraExposure( + network, + era, + activeAccount + )?.[validator]; + + const staked = new BigNumber(localExposed?.staked || '0'); + const total = new BigNumber(localExposed?.total || '0'); + const isValidator = localExposed?.isValidator || false; + + // Calculate the validator's share of total era payout. + const totalRewardPoints = new BigNumber( + rmCommas(eraRewardPoints.total) + ); + const validatorRewardPoints = new BigNumber( + rmCommas(eraRewardPoints.individual?.[validator] || '0') + ); + const avail = eraTotalPayout + .multipliedBy(validatorRewardPoints) + .dividedBy(totalRewardPoints); + + const valCut = commission.multipliedBy(avail); + + const unclaimedPayout = total.isZero() + ? new BigNumber(0) + : avail + .minus(valCut) + .multipliedBy(staked) + .dividedBy(total) + .plus(isValidator ? valCut : 0); + + if (!unclaimedPayout.isZero()) { + unclaimed[era] = { + ...unclaimed[era], + [validator]: unclaimedPayout.toString(), + }; + j++; + } + } + + // This is not currently useful for preventing re-syncing. Need to know the eras that have + // been claimed already and remove them from `erasToCheck`. + setLocalUnclaimedPayouts( + network, + era, + activeAccount, + unclaimed[era], + endEra.toString() + ); + i++; + } + + setUnclaimedPayouts({ + ...unclaimedPayouts, + ...unclaimed, + }); + }; + + // Removes a payout from `unclaimedPayouts` based on an era and validator record. + const removeEraPayout = (era: string, validator: string) => { + if (!unclaimedPayouts) return; + const newUnclaimedPayouts = { ...unclaimedPayouts }; + delete newUnclaimedPayouts[era][validator]; + setUnclaimedPayouts(newUnclaimedPayouts); + }; + + // Fetch payouts if active account is nominating. + useEffect(() => { + if (!activeEra.index.isZero()) { + if (!isNominating()) { + setStateWithRef('synced', setPayoutsSynced, payoutsSyncedRef); + } else if ( + unclaimedPayouts === null && + payoutsSyncedRef.current !== 'syncing' + ) { + setStateWithRef('syncing', setPayoutsSynced, payoutsSyncedRef); + // Start checking eras for exposures, starting with the previous one. + checkEra(activeEra.index.minus(1)); + } + } + }, [unclaimedPayouts, isNominating(), activeEra, payoutsSynced]); + + // Clear payout state on network / active account change. + useEffect(() => { + setUnclaimedPayouts(null); + setStateWithRef('unsynced', setPayoutsSynced, payoutsSyncedRef); + }, [network, activeAccount]); + + return ( + <PayoutsContext.Provider + value={{ + unclaimedPayouts, + payoutsSynced: payoutsSyncedRef.current, + removeEraPayout, + }} + > + {children} + </PayoutsContext.Provider> + ); +}; + +export const PayoutsContext = React.createContext<PayoutsContextInterface>( + defaultPayoutsContext +); + +export const usePayouts = () => React.useContext(PayoutsContext); diff --git a/src/contexts/Payouts/types.ts b/src/contexts/Payouts/types.ts new file mode 100644 index 0000000000..3adfe39aeb --- /dev/null +++ b/src/contexts/Payouts/types.ts @@ -0,0 +1,21 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import type { Sync } from 'types'; + +export type PayoutsContextInterface = { + payoutsSynced: Sync; + unclaimedPayouts: UnclaimedPayouts; + removeEraPayout: (era: string, validator: string) => void; +}; + +export type UnclaimedPayouts = Record<string, EraUnclaimedPayouts> | null; + +export type EraUnclaimedPayouts = Record<string, string>; + +export interface LocalValidatorExposure { + staked: string; + total: string; + share: string; + isValidator: boolean; +} diff --git a/src/contexts/Plugins/Polkawatch/defaults.tsx b/src/contexts/Plugins/Polkawatch/defaults.tsx new file mode 100644 index 0000000000..59bd28a2e3 --- /dev/null +++ b/src/contexts/Plugins/Polkawatch/defaults.tsx @@ -0,0 +1,8 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +// Polkawatch API version. +export const PolkaWatchApiVersion = 'v2'; + +// Polkawatch supported networks. +export const PolkaWatchNetworks = ['polkadot', 'kusama']; diff --git a/src/contexts/Plugins/Polkawatch/index.tsx b/src/contexts/Plugins/Polkawatch/index.tsx new file mode 100644 index 0000000000..b92aa8b049 --- /dev/null +++ b/src/contexts/Plugins/Polkawatch/index.tsx @@ -0,0 +1,71 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { Configuration, PolkawatchApi } from '@polkawatch/ddp-client'; +import React, { createContext, useContext, useEffect, useState } from 'react'; +import { localStorageOrDefault } from '@polkadot-cloud/utils'; +import { useNetwork } from 'contexts/Network'; +import type { NetworkName } from '../../../types'; +import type { PolkawatchState } from './types'; +import { DefaultNetwork } from '../../../consts'; +import { PolkaWatchApiVersion, PolkaWatchNetworks } from './defaults'; + +/** + * This is the Polkawatch API provider, which builds polkawatch API depending on the Chain that is currently + * in context. Also returns information about whether there exist decentralization analytics for the Network. + */ + +/** + * Builds the API Configuration based on Chain and API Version + * @param name the chain to query: polkadot, kusama, etc. + * @param version the API version + * @constructor + */ +const apiConfiguration = ( + name: NetworkName = localStorageOrDefault( + 'network', + DefaultNetwork + ) as NetworkName, + version = PolkaWatchApiVersion +): Configuration => + new Configuration({ + basePath: `https://${name}-${version}-api.polkawatch.app`, + }); + +const PolkawatchInitialState = { + pwApi: new PolkawatchApi(apiConfiguration()), + networkSupported: true, +}; + +export const PolkawatchProvider = ({ + children, +}: { + children: React.ReactNode; +}) => { + const { network } = useNetwork(); + + const [state, setState] = useState<PolkawatchState>(PolkawatchInitialState); + + /** + * We update the API object when another network is selected. The api Object is stateless, there + * is no network or computational cost involved in creating a new API. + */ + useEffect(() => { + setState({ + pwApi: new PolkawatchApi(apiConfiguration(network)), + networkSupported: PolkaWatchNetworks.includes(network), + }); + }, [network]); + + return ( + <PolkawatchContext.Provider value={state}> + {children} + </PolkawatchContext.Provider> + ); +}; + +const PolkawatchContext = createContext<PolkawatchState>( + PolkawatchInitialState +); + +export const usePolkawatchApi = () => useContext(PolkawatchContext); diff --git a/src/contexts/Plugins/Polkawatch/types.tsx b/src/contexts/Plugins/Polkawatch/types.tsx new file mode 100644 index 0000000000..9e0a15b4e7 --- /dev/null +++ b/src/contexts/Plugins/Polkawatch/types.tsx @@ -0,0 +1,13 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import type { PolkawatchApi } from '@polkawatch/ddp-client'; + +/** + * The provider will return an API and also information about whether the selected network has decentralization + * analytics support. + */ +export interface PolkawatchState { + pwApi: PolkawatchApi; + networkSupported: boolean; +} diff --git a/src/contexts/Plugins/Subscan/defaults.ts b/src/contexts/Plugins/Subscan/defaults.ts new file mode 100644 index 0000000000..92e9cef8e9 --- /dev/null +++ b/src/contexts/Plugins/Subscan/defaults.ts @@ -0,0 +1,17 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only +/* eslint-disable @typescript-eslint/no-unused-vars */ + +import type { SubscanContextInterface } from './types'; + +export const defaultSubscanContext: SubscanContextInterface = { + fetchEraPoints: (v, e) => {}, + payouts: [], + poolClaims: [], + unclaimedPayouts: [], + payoutsFromDate: undefined, + payoutsToDate: undefined, + fetchPoolDetails: (poolId) => new Promise((resolve) => resolve({})), + fetchPoolMembers: (poolId, page) => new Promise((resolve) => resolve([])), + setUnclaimedPayouts: (payouts) => {}, +}; diff --git a/src/contexts/Plugins/Subscan/index.tsx b/src/contexts/Plugins/Subscan/index.tsx new file mode 100644 index 0000000000..d373fe1ebe --- /dev/null +++ b/src/contexts/Plugins/Subscan/index.tsx @@ -0,0 +1,371 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { isNotZero } from '@polkadot-cloud/utils'; +import { format, fromUnixTime } from 'date-fns'; +import React, { useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { + ApiEndpoints, + ApiSubscanKey, + DefaultLocale, + ListItemsPerPage, +} from 'consts'; +import { useNetworkMetrics } from 'contexts/NetworkMetrics'; +import { sortNonZeroPayouts } from 'library/Graphs/Utils'; +import { useErasToTimeLeft } from 'library/Hooks/useErasToTimeLeft'; +import { locales } from 'locale'; +import type { AnyApi, AnySubscan } from 'types'; +import { useEffectIgnoreInitial } from '@polkadot-cloud/react/hooks'; +import { useNetwork } from 'contexts/Network'; +import { useActiveAccounts } from 'contexts/ActiveAccounts'; +import { useApi } from '../../Api'; +import { usePlugins } from '..'; +import { defaultSubscanContext } from './defaults'; +import type { SubscanContextInterface } from './types'; + +export const SubscanProvider = ({ + children, +}: { + children: React.ReactNode; +}) => { + const { i18n } = useTranslation(); + const { isReady } = useApi(); + const { + network, + networkData: { subscanEndpoint }, + } = useNetwork(); + const { activeAccount } = useActiveAccounts(); + const { activeEra } = useNetworkMetrics(); + const { erasToSeconds } = useErasToTimeLeft(); + const { plugins, pluginEnabled } = usePlugins(); + + // store fetched payouts from Subscan + const [payouts, setPayouts] = useState<AnySubscan>([]); + + // store fetched pool claims from Subscan + const [poolClaims, setPoolClaims] = useState<AnySubscan>([]); + + // store fetched unclaimed payouts from Subscan + const [unclaimedPayouts, setUnclaimedPayouts] = useState<AnyApi>([]); + + // store the start date of fetched payouts and pool claims combined. + const [payoutsFromDate, setPayoutsFromDate] = useState<string | undefined>(); + + // store the end date of fetched payouts and pool claims combined. + const [payoutsToDate, setPayoutsToDate] = useState<string | undefined>(); + + // handle fetching the various types of payout and set state in one render. + const handleFetchPayouts = async () => { + const results = await Promise.all([fetchPayouts(), fetchPoolClaims()]); + + const { newClaimedPayouts, newUnclaimedPayouts } = results[0]; + const newPoolClaims = results[1]; + + setPayouts(newClaimedPayouts); + setUnclaimedPayouts(newUnclaimedPayouts); + setPoolClaims(newPoolClaims); + }; + + // reset all payout state + const resetPayouts = () => { + setPayouts([]); + setUnclaimedPayouts([]); + setPoolClaims([]); + }; + + // Reset payouts on network switch. + useEffectIgnoreInitial(() => { + resetPayouts(); + }, [network]); + + // Reset payouts on no active account. + useEffectIgnoreInitial(() => { + if (!activeAccount) resetPayouts(); + }, [activeAccount]); + + // Reset payouts on subscan plugin not enabled. + useEffectIgnoreInitial(() => { + if (!plugins.includes('subscan')) resetPayouts(); + else if (isReady && isNotZero(activeEra.index)) handleFetchPayouts(); + }, [plugins.includes('subscan'), isReady, activeEra]); + + // Fetch payouts as soon as network is ready. + useEffectIgnoreInitial(() => { + if (isReady && isNotZero(activeEra.index)) { + handleFetchPayouts(); + } + }, [isReady, network, activeAccount, activeEra]); + + // Store start and end date of fetched payouts. + useEffectIgnoreInitial(() => { + const filteredPayouts = sortNonZeroPayouts(payouts, poolClaims, true); + if (filteredPayouts.length) { + setPayoutsFromDate( + format( + fromUnixTime( + filteredPayouts[filteredPayouts.length - 1].block_timestamp + ), + 'do MMM', + { + locale: locales[i18n.resolvedLanguage ?? DefaultLocale], + } + ) + ); + + // latest payout date + setPayoutsToDate( + format(fromUnixTime(filteredPayouts[0].block_timestamp), 'do MMM', { + locale: locales[i18n.resolvedLanguage ?? DefaultLocale], + }) + ); + } + }, [payouts, poolClaims, unclaimedPayouts]); + + /* fetchPayouts + * fetches payout history from Subscan. + * Fetches a total of 300 records from 3 asynchronous requests. + * Also checks if subscan service is active *after* the fetch has resolved + * as the user could have turned off the service while payouts were fetching. + * Stores resulting payouts in context state. + */ + const fetchPayouts = async () => { + let newClaimedPayouts: AnySubscan[] = []; + let newUnclaimedPayouts: AnySubscan[] = []; + + // fetch results if subscan is enabled + if (activeAccount && pluginEnabled('subscan')) { + // fetch 1 page of results + const results = await Promise.all([ + handleFetch(0, ApiEndpoints.subscanRewardSlash, 100, { + address: activeAccount, + is_stash: true, + }), + ]); + + // user may have turned off service while results were fetching. + // test again whether subscan service is still active. + if (pluginEnabled('subscan')) { + for (const result of results) { + if (!result?.data?.list) { + break; + } + // ensure no payouts have block_timestamp of 0 + const list = result.data.list.filter( + (l: AnyApi) => l.block_timestamp !== 0 + ); + newClaimedPayouts = newClaimedPayouts.concat(list); + + const unclaimedList = result.data.list.filter( + (l: AnyApi) => l.block_timestamp === 0 + ); + + // Inject block_timestamp for unclaimed payouts. We take the timestamp of the start of the + // following payout era - this is the time payouts become available to claim by + // validators. + unclaimedList.forEach((p: AnyApi) => { + p.block_timestamp = activeEra.start + .multipliedBy(0.001) + .minus(erasToSeconds(activeEra.index.minus(p.era).minus(1))) + .toNumber(); + }); + newUnclaimedPayouts = newUnclaimedPayouts.concat(unclaimedList); + } + } + } + return { + newClaimedPayouts, + newUnclaimedPayouts, + }; + }; + + /* fetchPoolClaims + * fetches claim history from Subscan. + * Fetches a total of 300 records from 3 asynchronous requests. + * Also checks if subscan service is active *after* the fetch has resolved + * as the user could have turned off the service while payouts were fetching. + * Stores resulting claims in context state. + */ + const fetchPoolClaims = async () => { + let newPoolClaims: AnySubscan[] = []; + + // fetch results if subscan is enabled + if (activeAccount && pluginEnabled('subscan')) { + // fetch 1 page of results + const results = await Promise.all([ + handleFetch(0, ApiEndpoints.subscanPoolRewards, 100, { + address: activeAccount, + }), + ]); + + // user may have turned off service while results were fetching. + // test again whether subscan service is still active. + if (pluginEnabled('subscan')) { + for (const result of results) { + // check incorrectly formatted result object + if (!result?.data?.list) { + break; + } + // check list has records + if (!result.data.list.length) { + break; + } + // ensure no payouts have block_timestamp of 0 + const list = result.data.list.filter( + (l: AnyApi) => l.block_timestamp !== 0 + ); + newPoolClaims = newPoolClaims.concat(list); + } + } + } + return newPoolClaims; + }; + + /* fetchEraPoints + * fetches recent era point history for a particular address. + * Also checks if subscan service is active *after* the fetch has resolved + * as the user could have turned off the service while payouts were fetching. + * returns eraPoints + */ + const fetchEraPoints = async (address: string, era: number) => { + if (address === '' || !plugins.includes('subscan')) { + return []; + } + const res = await handleFetch(0, ApiEndpoints.subscanEraStat, 100, { + address, + }); + + if (res.message === 'Success') { + if (pluginEnabled('subscan')) { + if (res.data?.list !== null) { + const list = []; + for (let i = era; i > era - 100; i--) { + list.push({ + era: i, + reward_point: + res.data.list.find((item: AnySubscan) => item.era === i) + ?.reward_point ?? 0, + }); + } + // removes last zero item and returns + return list.reverse().splice(0, list.length - 1); + } + return []; + } + } + return []; + }; + + /* fetchPoolDetails + * Also checks if subscan service is active *after* the fetch has resolved + * as the user could have turned off the service while payouts were fetching. + */ + const fetchPoolDetails = async (poolId: number) => { + if (!plugins.includes('subscan')) { + return []; + } + const res: Response = await fetch( + subscanEndpoint + ApiEndpoints.subscanPoolDetails, + { + headers: { + 'Content-Type': 'application/json', + 'X-API-Key': ApiSubscanKey, + }, + body: JSON.stringify({ + pool_id: poolId, + }), + method: 'POST', + } + ); + const json: AnySubscan = await res.json(); + return json?.data || undefined; + }; + + /* fetchPoolMembers + * Also checks if subscan service is active *after* the fetch has resolved + * as the user could have turned off the service while payouts were fetching. + */ + const fetchPoolMembers = async (poolId: number, page: number) => { + if (!plugins.includes('subscan')) { + return []; + } + const res = await handleFetch( + page - 1, + ApiEndpoints.subscanPoolMembers, + ListItemsPerPage, + { + pool_id: poolId, + } + ); + + if (res.message === 'Success') { + if (pluginEnabled('subscan')) { + if (res.data?.list !== null) { + const result = res.data?.list || []; + const list: AnySubscan = []; + for (const item of result) { + list.push({ + who: item.account_display.address, + poolId: item.pool_id, + }); + } + // removes last zero item and returns + return list.reverse().splice(0, list.length - 1); + } + } + return []; + } + return []; + }; + + /* handleFetch + * utility to handle a fetch request to Subscan + * returns resulting JSON. + */ + const handleFetch = async ( + page: number, + endpoint: string, + row: number, + body: AnyApi = {} + ): Promise<AnySubscan> => { + const bodyJson = { + row, + page, + ...body, + }; + const res: Response = await fetch(subscanEndpoint + endpoint, { + headers: { + 'Content-Type': 'application/json', + 'X-API-Key': ApiSubscanKey, + }, + body: JSON.stringify(bodyJson), + method: 'POST', + }); + const resJson: AnySubscan = await res.json(); + return resJson; + }; + + return ( + <SubscanContext.Provider + value={{ + fetchEraPoints, + payouts, + poolClaims, + unclaimedPayouts, + payoutsFromDate, + payoutsToDate, + fetchPoolDetails, + fetchPoolMembers, + setUnclaimedPayouts, + }} + > + {children} + </SubscanContext.Provider> + ); +}; + +export const SubscanContext = React.createContext<SubscanContextInterface>( + defaultSubscanContext +); + +export const useSubscan = () => React.useContext(SubscanContext); diff --git a/src/contexts/Plugins/Subscan/types.ts b/src/contexts/Plugins/Subscan/types.ts new file mode 100644 index 0000000000..65dd9deae6 --- /dev/null +++ b/src/contexts/Plugins/Subscan/types.ts @@ -0,0 +1,16 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import type { AnySubscan } from 'types'; + +export interface SubscanContextInterface { + fetchEraPoints: (v: string, e: number) => void; + payouts: AnySubscan; + poolClaims: AnySubscan; + unclaimedPayouts: AnySubscan; + payoutsFromDate: string | undefined; + payoutsToDate: string | undefined; + fetchPoolDetails: (poolId: number) => Promise<any>; + fetchPoolMembers: (poolId: number, page: number) => Promise<any[]>; + setUnclaimedPayouts: (payouts: AnySubscan) => void; +} diff --git a/src/contexts/Plugins/defaults.ts b/src/contexts/Plugins/defaults.ts new file mode 100644 index 0000000000..35d64a1168 --- /dev/null +++ b/src/contexts/Plugins/defaults.ts @@ -0,0 +1,11 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only +/* eslint-disable @typescript-eslint/no-unused-vars */ + +import type { PluginsContextInterface } from './types'; + +export const defaultPluginsContext: PluginsContextInterface = { + togglePlugin: (k) => {}, + pluginEnabled: (k) => false, + plugins: [], +}; diff --git a/src/contexts/Plugins/index.tsx b/src/contexts/Plugins/index.tsx new file mode 100644 index 0000000000..53158c2346 --- /dev/null +++ b/src/contexts/Plugins/index.tsx @@ -0,0 +1,72 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { localStorageOrDefault, setStateWithRef } from '@polkadot-cloud/utils'; +import React, { useRef, useState } from 'react'; +import { PluginsList } from 'consts'; +import type { Plugin } from 'types'; +import * as defaults from './defaults'; +import type { PluginsContextInterface } from './types'; + +export const PluginsProvider = ({ + children, +}: { + children: React.ReactNode; +}) => { + // Get initial plugins from local storage. + const getAvailablePlugins = () => { + const localPlugins = localStorageOrDefault( + 'plugins', + PluginsList, + true + ) as Plugin[]; + + // if fiat is disabled, remove binance_spot service + const DISABLE_FIAT = Number(import.meta.env.VITE_DISABLE_FIAT ?? 0); + if (DISABLE_FIAT && localPlugins.includes('binance_spot')) { + const index = localPlugins.indexOf('binance_spot'); + if (index !== -1) localPlugins.splice(index, 1); + } + return localPlugins; + }; + + // Store the currently active plugins. + const [plugins, setPlugins] = useState<Plugin[]>(getAvailablePlugins()); + const pluginsRef = useRef(plugins); + + // Toggle a plugin. + const togglePlugin = (key: Plugin) => { + let localPlugins = [...plugins]; + const found = localPlugins.find((p) => p === key); + + if (found) { + localPlugins = localPlugins.filter((p) => p !== key); + } else { + localPlugins.push(key); + } + + localStorage.setItem('plugins', JSON.stringify(localPlugins)); + setStateWithRef(localPlugins, setPlugins, pluginsRef); + }; + + // Check if a plugin is currently enabled. + const pluginEnabled = (key: Plugin) => pluginsRef.current.includes(key); + + return ( + <PluginsContext.Provider + value={{ + togglePlugin, + pluginEnabled, + plugins: pluginsRef.current, + }} + > + {children} + </PluginsContext.Provider> + ); +}; + +export const PluginsContext = React.createContext<PluginsContextInterface>( + defaults.defaultPluginsContext +); + +export const usePlugins = () => React.useContext(PluginsContext); diff --git a/src/contexts/Plugins/types.ts b/src/contexts/Plugins/types.ts new file mode 100644 index 0000000000..19ecb6550a --- /dev/null +++ b/src/contexts/Plugins/types.ts @@ -0,0 +1,10 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import type { Plugin } from 'types'; + +export interface PluginsContextInterface { + togglePlugin: (k: Plugin) => void; + pluginEnabled: (key: Plugin) => boolean; + plugins: Plugin[]; +} diff --git a/src/contexts/Pools/ActivePools/defaults.ts b/src/contexts/Pools/ActivePools/defaults.ts index 3aa3913c88..cb75b2ca4c 100644 --- a/src/contexts/Pools/ActivePools/defaults.ts +++ b/src/contexts/Pools/ActivePools/defaults.ts @@ -1,9 +1,9 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only +/* eslint-disable @typescript-eslint/no-unused-vars */ -import { BN } from 'bn.js'; -import { Sync } from 'types'; -import { ActivePool, ActivePoolsContextState } from '../types'; +import BigNumber from 'bignumber.js'; +import type { ActivePool, ActivePoolsContextState } from '../types'; export const nominationStatus = {}; @@ -11,7 +11,7 @@ export const poolRoles = { depositor: '', nominator: '', root: '', - stateToggler: '', + bouncer: '', }; export const bondedPool = { @@ -36,7 +36,7 @@ export const selectedActivePool: ActivePool = { bondedPool, rewardPool, rewardAccountBalance: {}, - unclaimedRewards: new BN(0), + pendingRewards: new BigNumber(0), }; export const targets = { @@ -54,17 +54,16 @@ export const defaultActivePoolContext: ActivePoolsContextState = { isOwner: () => false, isMember: () => false, isDepositor: () => false, - isStateToggler: () => false, + isBouncer: () => false, getPoolBondedAccount: () => null, getPoolUnlocking: () => [], getPoolRoles: () => poolRoles, - // eslint-disable-next-line setTargets: (t) => {}, getNominationsStatus: () => nominationStatus, - // eslint-disable-next-line setSelectedPoolId: (p) => {}, selectedActivePool, targets, poolNominations, - synced: Sync.Unsynced, + synced: 'unsynced', + selectedPoolMemberCount: 0, }; diff --git a/src/contexts/Pools/ActivePools/index.tsx b/src/contexts/Pools/ActivePools/index.tsx index 866122f9c9..a0fa72a39d 100644 --- a/src/contexts/Pools/ActivePools/index.tsx +++ b/src/contexts/Pools/ActivePools/index.tsx @@ -1,189 +1,119 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import BN from 'bn.js'; -import { +import { localStorageOrDefault, setStateWithRef } from '@polkadot-cloud/utils'; +import BigNumber from 'bignumber.js'; +import React, { useEffect, useMemo, useRef, useState } from 'react'; +import type { ActivePool, ActivePoolsContextState, - BondedPool, PoolAddresses, } from 'contexts/Pools/types'; import { useStaking } from 'contexts/Staking'; -import React, { useEffect, useMemo, useRef, useState } from 'react'; -import { AnyApi, Sync } from 'types'; -import { localStorageOrDefault, rmCommas, setStateWithRef } from 'Utils'; +import type { AnyApi, AnyJson, Sync } from 'types'; +import { useEffectIgnoreInitial } from '@polkadot-cloud/react/hooks'; +import { useSubscan } from 'contexts/Plugins/Subscan'; +import { usePlugins } from 'contexts/Plugins'; +import { useNetwork } from 'contexts/Network'; +import { useActiveAccounts } from 'contexts/ActiveAccounts'; import { useApi } from '../../Api'; -import { useConnect } from '../../Connect'; import { useBondedPools } from '../BondedPools'; -import { usePoolMembers } from '../PoolMembers'; import { usePoolMemberships } from '../PoolMemberships'; import { usePoolsConfig } from '../PoolsConfig'; import * as defaults from './defaults'; - -export const ActivePoolsContext = React.createContext<ActivePoolsContextState>( - defaults.defaultActivePoolContext -); - -export const useActivePools = () => React.useContext(ActivePoolsContext); +import { usePoolMembers } from '../PoolMembers'; export const ActivePoolsProvider = ({ children, }: { children: React.ReactNode; }) => { - const { api, network, isReady, consts } = useApi(); + const { network } = useNetwork(); + const { api, isReady } = useApi(); const { eraStakers } = useStaking(); - const { activeAccount } = useConnect(); - const { createAccounts } = usePoolsConfig(); + const { pluginEnabled } = usePlugins(); + const { fetchPoolDetails } = useSubscan(); const { membership } = usePoolMemberships(); + const { createAccounts } = usePoolsConfig(); + const { activeAccount } = useActiveAccounts(); const { getAccountPools, bondedPools } = useBondedPools(); - const { getPoolMember } = usePoolMembers(); + const { getMembersOfPoolFromNode, poolMembersNode } = usePoolMembers(); - // determine active pools to subscribe to. + // Determine active pools to subscribe to. const accountPools = useMemo(() => { - const _accountPools = Object.keys(getAccountPools(activeAccount)); + const newAccountPools = Object.keys(getAccountPools(activeAccount) || {}); const p = membership?.poolId ? String(membership.poolId) : '-1'; - if (membership?.poolId && !_accountPools.includes(p || '-1')) { - _accountPools.push(String(membership.poolId)); + if (membership?.poolId && !newAccountPools.includes(p || '-1')) { + newAccountPools.push(String(membership.poolId)); } - return _accountPools; + return newAccountPools; }, [activeAccount, bondedPools, membership]); - // stores member's active pools - const [activePools, setActivePools] = useState<Array<ActivePool>>([]); + // Stores member's active pools. + const [activePools, setActivePools] = useState<ActivePool[]>([]); const activePoolsRef = useRef(activePools); - // store active pools unsubs - const [unsubActivePools, setUnsubActivePools] = useState<Array<AnyApi>>([]); - const unsubActivePoolsRef = useRef(unsubActivePools); + // Store active pools unsubs. + const unsubActivePools = useRef<AnyApi[]>([]); - // store active pools nominations. - const [poolNominations, setPoolNominations] = useState<{ - [key: number]: any; - }>({}); + // Store active pools nominations. + const [poolNominations, setPoolNominations] = useState< + Record<number, AnyJson> + >({}); const poolNominationsRef = useRef(poolNominations); - // store pool nominations unsubs - const [unsubNominations, setUnsubNominations] = useState<Array<AnyApi>>([]); - const unsubNominationsRef = useRef(unsubNominations); + // Store pool nominations unsubs. + const unsubNominations = useRef<AnyApi[]>([]); - // store account target validators - const [targets, _setTargets] = useState<{ - [key: number]: any; - }>({}); + // Store account target validators. + const [targets, setTargetsState] = useState<Record<number, AnyJson>>({}); const targetsRef = useRef(targets); - // store whether active pool data has been synced. - // this will be true if no active pool exists for the active account. - // We just need confirmation this is the case. - const [synced, setSynced] = useState<Sync>(Sync.Unsynced); - const syncedRef = useRef(synced); + // Store the member count of the selected pool. + const [selectedPoolMemberCount, setSelectedPoolMemberCount] = + useState<number>(0); - // store the currently selected active pool for the UI. - // Should default to the membership pool (if present). - const [selectedPoolId, setSelectedPoolId] = useState<string | null>(null); + const fetchingMemberCount = useRef<boolean>(false); - // re-sync when number of accountRoles change. - // this can happen when bondedPools sync, when roles - // are edited within the dashboard, or when pool - // membership changes. - useEffect(() => { - if (unsubActivePoolsRef.current.length) { - unsubscribeActivePools(); - } - if (unsubNominationsRef.current.length) { - unsubscribePoolNominations(); - } - setStateWithRef(Sync.Unsynced, setSynced, syncedRef); - }, [activeAccount, accountPools.length]); + // Store whether active pool data has been synced. this will be true if no active pool exists for + // the active account. We just need confirmation this is the case. + const [synced, setSynced] = useState<Sync>('unsynced'); + const syncedRef = useRef(synced); - // subscribe to pool that the active account is a member of. - useEffect(() => { - if (isReady && synced === Sync.Unsynced) { - setStateWithRef(Sync.Syncing, setSynced, syncedRef); - handlePoolSubscriptions(); - } - }, [network, isReady, syncedRef.current]); + // Store the currently selected active pool for the UI. Should default to the membership pool (if + // present). + const [selectedPoolId, setSelectedPoolId] = useState<string | null>(null); - const getActivePoolMembership = () => { + const getActivePoolMembership = () => // get the activePool that the active account - return ( - activePoolsRef.current.find((a: ActivePool) => { - const p = membership?.poolId ? String(membership.poolId) : '0'; - return String(a.id) === p; - }) || null - ); - }; - - const getSelectedActivePool = () => { - return ( - activePoolsRef.current.find( - (a: ActivePool) => a.id === Number(selectedPoolId) - ) || null - ); - }; - - const getSelectedPoolNominations = () => { - return ( - poolNominationsRef.current[Number(selectedPoolId) ?? -1] || - defaults.poolNominations - ); - }; - - const getSelectedPoolTargets = () => { - return targetsRef.current[Number(selectedPoolId) ?? -1] || defaults.targets; - }; - - // unsubscribe all on component unmount - useEffect(() => { - return () => { - unsubscribeActivePools(); - unsubscribePoolNominations(); - }; - }, [network]); + activePoolsRef.current.find((a) => { + const p = membership?.poolId ? String(membership.poolId) : '0'; + return String(a.id) === p; + }) || null; + const getSelectedActivePool = () => + activePoolsRef.current.find((a) => a.id === Number(selectedPoolId)) || null; - // re-calculate unclaimed payout when membership changes - useEffect(() => { - const acitvePoolMembership = getActivePoolMembership(); - - if (acitvePoolMembership && membership && isReady) { - const unclaimedRewards = calculatePayout( - acitvePoolMembership.bondedPool ?? defaults.bondedPool, - acitvePoolMembership.rewardPool ?? defaults.rewardPool, - acitvePoolMembership.rewardAccountBalance ?? new BN(0) - ); - updateUnclaimedRewards(unclaimedRewards, acitvePoolMembership?.id || 0); - } - }, [ - network, - isReady, - getActivePoolMembership()?.bondedPool, - getActivePoolMembership()?.rewardPool, - membership, - ]); + const getSelectedPoolNominations = () => + poolNominationsRef.current[Number(selectedPoolId) ?? -1] || + defaults.poolNominations; - // when we are subscribed to all active pools, syncing is considered - // completed. - useEffect(() => { - if (unsubNominationsRef.current.length === accountPools.length) { - setStateWithRef(Sync.Synced, setSynced, syncedRef); - } - }, [unsubNominationsRef.current]); + const getSelectedPoolTargets = () => + targetsRef.current[Number(selectedPoolId) ?? -1] || defaults.targets; // handle active pool subscriptions const handlePoolSubscriptions = async () => { if (accountPools.length) { Promise.all(accountPools.map((p) => subscribeToActivePool(Number(p)))); } else { - setStateWithRef(Sync.Synced, setSynced, syncedRef); + setStateWithRef('synced', setSynced, syncedRef); } // assign default pool immediately if active pool not currently selected const defaultSelected = membership?.poolId || accountPools[0] || null; const activePoolSelected = activePoolsRef.current.find( - (a: ActivePool) => String(a.id) === String(selectedPoolId) + (a) => String(a.id) === String(selectedPoolId) ) || null; if (defaultSelected && !activePoolSelected) { @@ -191,25 +121,25 @@ export const ActivePoolsProvider = ({ } }; - // unsubscribe and reset poolNominations + // Unsubscribe and reset poolNominations. const unsubscribePoolNominations = () => { - if (unsubNominationsRef.current.length) { - for (const unsub of unsubNominationsRef.current) { + if (unsubNominations.current.length) { + for (const unsub of unsubNominations.current) { unsub(); } } setStateWithRef({}, setPoolNominations, poolNominationsRef); - setStateWithRef([], setUnsubNominations, unsubNominationsRef); + unsubNominations.current = []; }; - // unsubscribe and reset activePool and poolNominations + // Unsubscribe and reset activePool and poolNominations. const unsubscribeActivePools = () => { - if (unsubActivePoolsRef.current.length) { - for (const unsub of unsubActivePoolsRef.current) { + if (unsubActivePools.current.length) { + for (const unsub of unsubActivePools.current) { unsub(); } setStateWithRef([], setActivePools, activePoolsRef); - setStateWithRef([], setUnsubActivePools, unsubActivePoolsRef); + unsubActivePools.current = []; } }; @@ -221,11 +151,11 @@ export const ActivePoolsProvider = ({ const addresses: PoolAddresses = createAccounts(poolId); // new active pool subscription - const subscribeActivePool = async (_poolId: number) => { - const unsub: () => void = await api.queryMulti<[AnyApi, AnyApi, AnyApi]>( + const subscribeActivePool = async (id: number) => { + const unsub = await api.queryMulti<AnyApi>( [ - [api.query.nominationPools.bondedPools, _poolId], - [api.query.nominationPools.rewardPools, _poolId], + [api.query.nominationPools.bondedPools, id], + [api.query.nominationPools.rewardPools, id], [api.query.system.account, addresses.reward], ], async ([bondedPool, rewardPool, accountData]): Promise<void> => { @@ -234,53 +164,46 @@ export const ActivePoolsProvider = ({ rewardPool = rewardPool?.unwrapOr(undefined)?.toHuman(); if (rewardPool && bondedPool) { const rewardAccountBalance = balance?.free; - const unclaimedRewards = calculatePayout( - bondedPool, - rewardPool, - rewardAccountBalance - ); + + const pendingRewards = await fetchPendingRewards(); const pool = { - id: _poolId, + id, addresses, bondedPool, rewardPool, rewardAccountBalance, - unclaimedRewards, + pendingRewards, }; - // remove pool if it already exists - const _activePools = activePoolsRef.current.filter( - (a: ActivePool) => a.id !== pool.id - ); - // set active pool state + // set active pool state, removing the pool if it already exists first. setStateWithRef( - [..._activePools, pool], + [...activePoolsRef.current.filter((a) => a.id !== pool.id), pool], setActivePools, activePoolsRef ); // get pool target nominations and set in state - const _targets = localStorageOrDefault( + const newTargets = localStorageOrDefault( `${addresses.stash}_pool_targets`, defaults.targets, true ); // add or replace current pool targets in targetsRef - const _poolTargets = { ...targetsRef.current }; - _poolTargets[poolId] = _targets; + const newPoolTargets = { ...targetsRef.current }; + newPoolTargets[poolId] = newTargets; // set pool staking targets - setStateWithRef(_poolTargets, _setTargets, targetsRef); + setStateWithRef(newPoolTargets, setTargetsState, targetsRef); // subscribe to pool nominations subscribeToPoolNominations(poolId, addresses.stash); } else { // set default targets for pool - const _poolTargets = { ...targetsRef.current }; - _poolTargets[poolId] = defaults.targets; - setStateWithRef(_poolTargets, _setTargets, targetsRef); + const newPoolTargets = { ...targetsRef.current }; + newPoolTargets[poolId] = defaults.targets; + setStateWithRef(newPoolTargets, setTargetsState, targetsRef); } } ); @@ -289,11 +212,7 @@ export const ActivePoolsProvider = ({ // initiate subscription, add to unsubs. await Promise.all([subscribeActivePool(poolId)]).then((unsubs: any) => { - setStateWithRef( - [...unsubActivePoolsRef.current, ...unsubs], - setUnsubActivePools, - unsubActivePoolsRef - ); + unsubActivePools.current = unsubActivePools.current.concat(unsubs); }); }; @@ -302,28 +221,28 @@ export const ActivePoolsProvider = ({ poolBondAddress: string ) => { if (!api) return; - const subscribePoolNominations = async (_poolBondAddress: string) => { + const subscribePoolNominations = async (bondedAddress: string) => { const unsub = await api.query.staking.nominators( - _poolBondAddress, + bondedAddress, (nominations: AnyApi) => { // set pool nominations - let _nominations = nominations.unwrapOr(null); - if (_nominations === null) { - _nominations = defaults.poolNominations; + let newNominations = nominations.unwrapOr(null); + if (newNominations === null) { + newNominations = defaults.poolNominations; } else { - _nominations = { - targets: _nominations.targets.toHuman(), - submittedIn: _nominations.submittedIn.toHuman(), + newNominations = { + targets: newNominations.targets.toHuman(), + submittedIn: newNominations.submittedIn.toHuman(), }; } // add or replace current pool nominations in poolNominations - const _poolNominations = { ...poolNominationsRef.current }; - _poolNominations[poolId] = _nominations; + const newPoolNominations = { ...poolNominationsRef.current }; + newPoolNominations[poolId] = newNominations; // set pool nominations state setStateWithRef( - _poolNominations, + newPoolNominations, setPoolNominations, poolNominationsRef ); @@ -334,42 +253,43 @@ export const ActivePoolsProvider = ({ // initiate subscription, add to unsubs. await Promise.all([subscribePoolNominations(poolBondAddress)]).then( - (unsubs: any) => { - setStateWithRef( - [...unsubNominationsRef.current, ...unsubs], - setUnsubNominations, - unsubNominationsRef - ); + (unsubs) => { + unsubNominations.current = unsubNominations.current.concat(unsubs); } ); }; // Utility functions /* - * updateUnclaimedRewards + * updateActivePoolPendingRewards * A helper function to set the unclaimed rewards of an active pool. */ - const updateUnclaimedRewards = (amount: BN, poolId: number) => { + const updateActivePoolPendingRewards = ( + pendingRewards: BigNumber, + poolId: number + ) => { if (!poolId) return; - // update the active pool the account is a member of - const _activePools = [...activePoolsRef.current].map((a: ActivePool) => { - if (a.id === poolId) { - return { - ...a, - unclaimedRewards: amount, - }; - } - return a; - }); - setStateWithRef(_activePools, setActivePools, activePoolsRef); + // update the active pool the account is a member of. + setStateWithRef( + [...activePoolsRef.current].map((a) => + a.id === poolId + ? { + ...a, + pendingRewards, + } + : a + ), + setActivePools, + activePoolsRef + ); }; /* * setTargets * Sets currently selected pool's target validators in storage. */ - const setTargets = (_targets: any) => { + const setTargets = (newTargets: any) => { if (!selectedPoolId) { return; } @@ -378,13 +298,13 @@ export const ActivePoolsProvider = ({ if (stashAddress) { localStorage.setItem( `${stashAddress}_pool_targets`, - JSON.stringify(_targets) + JSON.stringify(newTargets) ); // inject targets into targets object - const _poolTargets = { ...targetsRef.current }; - _poolTargets[Number(selectedPoolId)] = _targets; + const newPoolTargets = { ...targetsRef.current }; + newPoolTargets[Number(selectedPoolId)] = newTargets; - setStateWithRef(_poolTargets, _setTargets, targetsRef); + setStateWithRef(newPoolTargets, setTargetsState, targetsRef); } }; @@ -392,9 +312,7 @@ export const ActivePoolsProvider = ({ * isBonding * Returns whether active pool exists */ - const isBonding = () => { - return !!getSelectedActivePool(); - }; + const isBonding = () => !!getSelectedActivePool(); /* * isNominator @@ -406,8 +324,7 @@ export const ActivePoolsProvider = ({ if (!activeAccount || !roles) { return false; } - const result = activeAccount === roles?.nominator; - return result; + return activeAccount === roles?.nominator; }; /* @@ -420,8 +337,7 @@ export const ActivePoolsProvider = ({ if (!activeAccount || !roles) { return false; } - const result = activeAccount === roles?.root; - return result; + return activeAccount === roles?.root; }; /* @@ -432,7 +348,7 @@ export const ActivePoolsProvider = ({ const isMember = () => { const selectedPool = getSelectedActivePool(); const p = selectedPool ? String(selectedPool.id) : '-1'; - return getPoolMember(activeAccount)?.poolId === p; + return String(membership?.poolId || '') === p; }; /* @@ -445,22 +361,20 @@ export const ActivePoolsProvider = ({ if (!activeAccount || !roles) { return false; } - const result = activeAccount === roles?.depositor; - return result; + return activeAccount === roles?.depositor; }; /* - * isStateToggler + * isBouncer * Returns whether the active account is * the depositor of the active pool. */ - const isStateToggler = () => { + const isBouncer = () => { const roles = getSelectedActivePool()?.bondedPool?.roles; if (!activeAccount || !roles) { return false; } - const result = activeAccount === roles?.stateToggler; - return result; + return activeAccount === roles?.bouncer; }; /* @@ -468,9 +382,8 @@ export const ActivePoolsProvider = ({ * get the stash address of the bonded pool * that the member is participating in. */ - const getPoolBondedAccount = () => { - return getSelectedActivePool()?.addresses?.stash || null; - }; + const getPoolBondedAccount = () => + getSelectedActivePool()?.addresses?.stash || null; /* * Get the status of nominations. @@ -478,18 +391,18 @@ export const ActivePoolsProvider = ({ */ const getNominationsStatus = () => { const nominations = getSelectedPoolNominations().nominations?.targets || []; - const statuses: { [key: string]: string } = {}; + const statuses: Record<string, string> = {}; for (const nomination of nominations) { - const s = eraStakers.stakers.find((_n: any) => _n.address === nomination); + const s = eraStakers.stakers.find( + ({ address }) => address === nomination + ); if (s === undefined) { statuses[nomination] = 'waiting'; continue; } - const exists = (s.others ?? []).find( - (_o: any) => _o.who === activeAccount - ); + const exists = (s.others ?? []).find(({ who }) => who === activeAccount); if (exists === undefined) { statuses[nomination] = 'inactive'; continue; @@ -504,11 +417,7 @@ export const ActivePoolsProvider = ({ * Returns the active pool's roles or a default roles object. */ const getPoolRoles = () => { - const roles = getSelectedActivePool()?.bondedPool?.roles ?? null; - if (!roles) { - return defaults.poolRoles; - } - return roles; + return getSelectedActivePool()?.bondedPool?.roles || defaults.poolRoles; }; const getPoolUnlocking = () => { @@ -523,63 +432,102 @@ export const ActivePoolsProvider = ({ return membership?.unlocking || []; }; - const calculatePayout = ( - bondedPool: BondedPool, - rewardPool: any, - rewardAccountBalance: BN - ): BN => { - const membershipPoolId = membership?.poolId - ? String(membership.poolId) - : '-1'; - - // exit early if the currently selected pool is not membership pool - if (selectedPoolId !== membershipPoolId || !membership) { - return new BN(0); + // Fetch and update unclaimed rewards from runtime call. + const fetchPendingRewards = async () => { + if (getActivePoolMembership() && membership && api && isReady) { + const pendingRewards = await api.call.nominationPoolsApi.pendingRewards( + membership?.address || '' + ); + return new BigNumber(pendingRewards?.toString() || 0); } + return new BigNumber(0); + }; - const rewardCounterBase = new BN(10).pow(new BN(18)); + // Fetch and update pending rewards when membership changes. + const updatePendingRewards = async () => { + const pendingRewards = await fetchPendingRewards(); - // convert needed values into BNs - const totalRewardsClaimed = new BN( - rmCommas(rewardPool.totalRewardsClaimed) - ); - const lastRecordedTotalPayouts = new BN( - rmCommas(rewardPool.lastRecordedTotalPayouts) - ); - const memberLastRecordedRewardCounter = new BN( - rmCommas(membership.lastRecordedRewardCounter) - ); - const poolLastRecordedRewardCounter = new BN( - rmCommas(rewardPool.lastRecordedRewardCounter) + updateActivePoolPendingRewards( + pendingRewards, + getActivePoolMembership()?.id || 0 ); - const bondedPoolPoints = new BN(rmCommas(bondedPool.points)); - const points = new BN(rmCommas(membership.points)); + }; - // calculate the latest reward account balance minus the existential deposit - const rewardPoolBalance = BN.max( - new BN(0), - new BN(rewardAccountBalance).sub(consts.existentialDeposit) - ); + // re-sync when number of accountRoles change. + // this can happen when bondedPools sync, when roles + // are edited within the dashboard, or when pool + // membership changes. + useEffectIgnoreInitial(() => { + unsubscribeActivePools(); + unsubscribePoolNominations(); + setStateWithRef('unsynced', setSynced, syncedRef); + }, [activeAccount, accountPools.length]); + + // subscribe to pool that the active account is a member of. + useEffectIgnoreInitial(() => { + if (isReady && syncedRef.current === 'unsynced') { + setStateWithRef('syncing', setSynced, syncedRef); + handlePoolSubscriptions(); + } + }, [network, isReady, syncedRef.current]); - // calculate the current reward counter - const payoutsSinceLastRecord = rewardPoolBalance - .add(totalRewardsClaimed) - .sub(lastRecordedTotalPayouts); + // unsubscribe all on component unmount + useEffect( + () => () => { + unsubscribeActivePools(); + unsubscribePoolNominations(); + }, + [network] + ); - const currentRewardCounter = ( - bondedPoolPoints.eq(new BN(0)) - ? new BN(0) - : payoutsSinceLastRecord.mul(rewardCounterBase).div(bondedPoolPoints) - ).add(poolLastRecordedRewardCounter); + // re-calculate pending rewards when membership changes + useEffectIgnoreInitial(() => { + updatePendingRewards(); + }, [ + network, + isReady, + getActivePoolMembership()?.bondedPool, + getActivePoolMembership()?.rewardPool, + membership, + ]); - const pendingRewards = currentRewardCounter - .sub(memberLastRecordedRewardCounter) - .mul(points) - .div(rewardCounterBase); + // Gets the member count of the currently selected pool. If Subscan is enabled, it is used instead of the connected node. + const getMemberCount = async () => { + const selectedActivePool = getSelectedActivePool(); - return pendingRewards; + if (!selectedActivePool?.id) { + setSelectedPoolMemberCount(0); + return; + } + // If `Subscan` plugin is enabled, fetch member count directly from the API. + if (pluginEnabled('subscan') && !fetchingMemberCount.current) { + fetchingMemberCount.current = true; + const poolDetails = await fetchPoolDetails(selectedActivePool.id); + fetchingMemberCount.current = false; + setSelectedPoolMemberCount(poolDetails?.member_count || 0); + return; + } + // If no plugin available, fetch all pool members from RPC and filter them to determine current + // pool member count. NOTE: Expensive operation. + setSelectedPoolMemberCount( + getMembersOfPoolFromNode(selectedActivePool?.id ?? 0).length + ); }; + // when we are subscribed to all active pools, syncing is considered + // completed. + useEffectIgnoreInitial(() => { + if (unsubNominations.current.length === accountPools.length) { + setStateWithRef('synced', setSynced, syncedRef); + } + }, [accountPools, unsubNominations.current]); + + // Fetch pool member count. We use `membership` as a dependency as the member count could change + // in the UI when active account's membership changes. + useEffect(() => { + getMemberCount(); + }, [activeAccount, getSelectedActivePool(), membership, poolMembersNode]); + return ( <ActivePoolsContext.Provider value={{ @@ -587,7 +535,7 @@ export const ActivePoolsProvider = ({ isOwner, isMember, isDepositor, - isStateToggler, + isBouncer, isBonding, getPoolBondedAccount, getPoolUnlocking, @@ -599,9 +547,16 @@ export const ActivePoolsProvider = ({ selectedActivePool: getSelectedActivePool(), targets: getSelectedPoolTargets(), poolNominations: getSelectedPoolNominations(), + selectedPoolMemberCount, }} > {children} </ActivePoolsContext.Provider> ); }; + +export const ActivePoolsContext = React.createContext<ActivePoolsContextState>( + defaults.defaultActivePoolContext +); + +export const useActivePools = () => React.useContext(ActivePoolsContext); diff --git a/src/contexts/Pools/BondedPools/defaults.ts b/src/contexts/Pools/BondedPools/defaults.ts index fbfb427973..0074e1771c 100644 --- a/src/contexts/Pools/BondedPools/defaults.ts +++ b/src/contexts/Pools/BondedPools/defaults.ts @@ -1,32 +1,21 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only +/* eslint-disable @typescript-eslint/no-unused-vars */ -import { BondedPoolsContextState } from '../types'; +import type { BondedPoolsContextState } from '../types'; export const defaultBondedPoolsContext: BondedPoolsContextState = { - // eslint-disable-next-line fetchPoolsMetaBatch: (k, v: [], r) => {}, - // eslint-disable-next-line queryBondedPool: (p) => {}, - // eslint-disable-next-line getBondedPool: (p) => null, - // eslint-disable-next-line updateBondedPools: (p) => {}, - // eslint-disable-next-line addToBondedPools: (p) => {}, - // eslint-disable-next-line removeFromBondedPools: (p) => {}, - // eslint-disable-next-line getPoolNominationStatus: (n, o) => {}, - // eslint-disable-next-line getPoolNominationStatusCode: (t) => '', - // eslint-disable-next-line getAccountRoles: (w) => null, - // eslint-disable-next-line getAccountPools: (w) => null, - // eslint-disable-next-line replacePoolRoles: (p, e) => {}, - // eslint-disable-next-line poolSearchFilter: (l, k, v) => {}, bondedPools: [], meta: {}, diff --git a/src/contexts/Pools/BondedPools/index.tsx b/src/contexts/Pools/BondedPools/index.tsx index ac57115df9..df6330040e 100644 --- a/src/contexts/Pools/BondedPools/index.tsx +++ b/src/contexts/Pools/BondedPools/index.tsx @@ -1,80 +1,48 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only import { u8aToString, u8aUnwrapBytes } from '@polkadot/util'; -import { +import { setStateWithRef, shuffle } from '@polkadot-cloud/utils'; +import React, { useRef, useState } from 'react'; +import type { BondedPool, BondedPoolsContextState, MaybePool, NominationStatuses, } from 'contexts/Pools/types'; import { useStaking } from 'contexts/Staking'; -import React, { useEffect, useRef, useState } from 'react'; -import { AnyApi, AnyMetaBatch, Fn, MaybeAccount } from 'types'; -import { setStateWithRef, shuffle } from 'Utils'; +import type { AnyApi, AnyMetaBatch, Fn, MaybeAddress } from 'types'; +import { useEffectIgnoreInitial } from '@polkadot-cloud/react/hooks'; +import { useNetwork } from 'contexts/Network'; import { useApi } from '../../Api'; import { usePoolsConfig } from '../PoolsConfig'; import { defaultBondedPoolsContext } from './defaults'; -export const BondedPoolsContext = React.createContext<BondedPoolsContextState>( - defaultBondedPoolsContext -); - -export const useBondedPools = () => React.useContext(BondedPoolsContext); - export const BondedPoolsProvider = ({ children, }: { children: React.ReactNode; }) => { - const { api, network, isReady } = useApi(); - const { getNominationsStatusFromTargets } = useStaking(); + const { network } = useNetwork(); + const { api, isReady } = useApi(); const { createAccounts, stats } = usePoolsConfig(); + const { getNominationsStatusFromTargets } = useStaking(); const { lastPoolId } = stats; - // stores the meta data batches for pool lists + // Stores the meta data batches for pool lists. const [poolMetaBatches, setPoolMetaBatch]: AnyMetaBatch = useState({}); const poolMetaBatchesRef = useRef(poolMetaBatches); - // stores the meta batch subscriptions for pool lists - const [poolSubs, setPoolSubs] = useState<{ - [key: string]: Array<Fn>; - }>({}); - const poolSubsRef = useRef(poolSubs); - - // store bonded pools - const [bondedPools, setBondedPools] = useState<Array<BondedPool>>([]); - - // clear existing state for network refresh - useEffect(() => { - setBondedPools([]); - setStateWithRef({}, setPoolMetaBatch, poolMetaBatchesRef); - }, [network]); - - // initial setup for fetching bonded pools - useEffect(() => { - if (isReady) { - // fetch bonded pools - fetchBondedPools(); - } - return () => { - unsubscribe(); - }; - }, [network, isReady, lastPoolId]); + // Stores the meta batch subscriptions for pool lists. + const poolSubs = useRef<Record<string, Fn[]>>({}); - // after bonded pools have synced, fetch metabatch - useEffect(() => { - if (bondedPools.length) { - fetchPoolsMetaBatch('bonded_pools', bondedPools, true); - } - }, [bondedPools]); + // Store bonded pools. + const [bondedPools, setBondedPools] = useState<BondedPool[]>([]); const unsubscribe = () => { - Object.values(poolSubsRef.current).map((batch: Array<Fn>) => { - return Object.entries(batch).map(([, v]) => { - return v(); - }); - }); + Object.values(poolSubs.current).map((batch: Fn[]) => + Object.entries(batch).map(([, v]) => v()) + ); setBondedPools([]); }; @@ -82,8 +50,8 @@ export const BondedPoolsProvider = ({ const fetchBondedPools = async () => { if (!api) return; - const _exposures = await api.query.nominationPools.bondedPools.entries(); - let exposures = _exposures.map(([_keys, _val]: AnyApi) => { + const result = await api.query.nominationPools.bondedPools.entries(); + let exposures = result.map(([_keys, _val]: AnyApi) => { const id = _keys.toHuman()[0]; const pool = _val.toHuman(); return getPoolWithAddresses(id, pool); @@ -130,12 +98,8 @@ export const BondedPoolsProvider = ({ p: AnyMetaBatch, refetch = false ) => { - if (!isReady || !api) { - return; - } - if (!p.length) { - return; - } + if (!isReady || !api || !p.length) return; + if (!refetch) { // if already exists, do not re-fetch if (poolMetaBatchesRef.current[key] !== undefined) { @@ -143,11 +107,18 @@ export const BondedPoolsProvider = ({ } } else { // tidy up if existing batch exists - delete poolMetaBatches[key]; - delete poolMetaBatchesRef.current[key]; + const updatedPoolMetaBatches: AnyMetaBatch = { + ...poolMetaBatchesRef.current, + }; + delete updatedPoolMetaBatches[key]; + setStateWithRef( + updatedPoolMetaBatches, + setPoolMetaBatch, + poolMetaBatchesRef + ); - if (poolSubsRef.current[key] !== undefined) { - for (const unsub of poolSubsRef.current[key]) { + if (poolSubs.current[key] !== undefined) { + for (const unsub of poolSubs.current[key]) { unsub(); } } @@ -156,11 +127,11 @@ export const BondedPoolsProvider = ({ // aggregate pool ids and addresses const ids = []; const addresses = []; - for (const _p of p) { - ids.push(Number(_p.id)); + for (const pool of p) { + ids.push(Number(pool.id)); - if (_p?.addresses?.stash) { - addresses.push(_p.addresses.stash); + if (pool?.addresses?.stash) { + addresses.push(pool.addresses.stash); } } @@ -182,14 +153,10 @@ export const BondedPoolsProvider = ({ for (let i = 0; i < _metadata.length; i++) { metadata.push(_metadata[i].toHuman()); } - const _batchesUpdated = Object.assign(poolMetaBatchesRef.current); - _batchesUpdated[key].metadata = metadata; - - setStateWithRef( - { ..._batchesUpdated }, - setPoolMetaBatch, - poolMetaBatchesRef - ); + const updated = Object.assign(poolMetaBatchesRef.current); + updated[key].metadata = metadata; + + setStateWithRef({ ...updated }, setPoolMetaBatch, poolMetaBatchesRef); } ); return unsub; @@ -203,13 +170,9 @@ export const BondedPoolsProvider = ({ for (let i = 0; i < _nominations.length; i++) { nominations.push(_nominations[i].toHuman()); } - const _batchesUpdated = Object.assign(poolMetaBatchesRef.current); - _batchesUpdated[key].nominations = nominations; - setStateWithRef( - { ..._batchesUpdated }, - setPoolMetaBatch, - poolMetaBatchesRef - ); + const updated = Object.assign(poolMetaBatchesRef.current); + updated[key].nominations = nominations; + setStateWithRef({ ...updated }, setPoolMetaBatch, poolMetaBatchesRef); } ); return unsub; @@ -219,7 +182,7 @@ export const BondedPoolsProvider = ({ await Promise.all([ subscribeToMetadata(ids), subscribeToNominations(addresses), - ]).then((unsubs: Array<Fn>) => { + ]).then((unsubs: Fn[]) => { addMetaBatchUnsubs(key, unsubs); }); }; @@ -228,8 +191,8 @@ export const BondedPoolsProvider = ({ * Get bonded pool nomination statuses */ const getPoolNominationStatus = ( - nominator: MaybeAccount, - nomination: MaybeAccount + nominator: MaybeAddress, + nomination: MaybeAddress ) => { const pool = bondedPools.find((p: any) => p.addresses.stash === nominator); @@ -257,12 +220,12 @@ export const BondedPoolsProvider = ({ let status = 'waiting'; if (statuses) { - for (const _status of Object.values(statuses)) { - if (_status === 'active') { + for (const childStatus of Object.values(statuses)) { + if (childStatus === 'active') { status = 'active'; break; } - if (_status === 'inactive') { + if (childStatus === 'inactive') { status = 'inactive'; } } @@ -273,30 +236,26 @@ export const BondedPoolsProvider = ({ /* * Helper: to add mataBatch unsubs by key. */ - const addMetaBatchUnsubs = (key: string, unsubs: Array<Fn>) => { - const _unsubs = poolSubsRef.current; - const _keyUnsubs = _unsubs[key] ?? []; + const addMetaBatchUnsubs = (key: string, unsubs: Fn[]) => { + const newUnsubs = poolSubs.current; + const newUnsubItem = newUnsubs[key] ?? []; - _keyUnsubs.push(...unsubs); - _unsubs[key] = _keyUnsubs; - setStateWithRef(_unsubs, setPoolSubs, poolSubsRef); + newUnsubItem.push(...unsubs); + newUnsubs[key] = newUnsubItem; + poolSubs.current = newUnsubs; }; /* * Helper: to add addresses to pool record. */ - const getPoolWithAddresses = (id: number, pool: BondedPool) => { - return { - ...pool, - id, - addresses: createAccounts(id), - }; - }; + const getPoolWithAddresses = (id: number, pool: BondedPool) => ({ + ...pool, + id, + addresses: createAccounts(id), + }); - const getBondedPool = (poolId: MaybePool) => { - const pool = bondedPools.find((p: BondedPool) => p.id === poolId) ?? null; - return pool; - }; + const getBondedPool = (poolId: MaybePool) => + bondedPools.find((p) => p.id === String(poolId)) ?? null; /* * poolSearchFilter @@ -356,22 +315,19 @@ export const BondedPoolsProvider = ({ return filteredList; }; - const updateBondedPools = (updatedPools: Array<BondedPool>) => { - if (!updatedPools) { - return; - } - const _bondedPools = bondedPools.map( - (original: BondedPool) => - updatedPools.find( - (updated: BondedPool) => updated.id === original.id - ) || original + const updateBondedPools = (updatedPools: BondedPool[]) => { + if (!updatedPools) return; + + setBondedPools( + bondedPools.map( + (original) => + updatedPools.find((updated) => updated.id === original.id) || original + ) ); - setBondedPools(_bondedPools); }; const removeFromBondedPools = (id: number) => { - const _bondedPools = bondedPools.filter((b: BondedPool) => b.id !== id); - setBondedPools(_bondedPools); + setBondedPools(bondedPools.filter((b: BondedPool) => b.id !== id)); }; // adds a record to bondedPools. @@ -379,59 +335,55 @@ export const BondedPoolsProvider = ({ const addToBondedPools = (pool: BondedPool) => { if (!pool) return; - const exists = bondedPools.find((b: BondedPool) => b.id === pool.id); - if (!exists) { - const _bondedPools = bondedPools.concat(pool); - setBondedPools(_bondedPools); - } + const exists = bondedPools.find((b) => b.id === pool.id); + if (!exists) setBondedPools(bondedPools.concat(pool)); }; // get all the roles belonging to one pool account - const getAccountRoles = (who: MaybeAccount) => { + const getAccountRoles = (who: MaybeAddress) => { if (!who) { return { depositor: [], root: [], nominator: [], - stateToggler: [], + bouncer: [], }; } const depositor = bondedPools - .filter((b: BondedPool) => b.roles.depositor === who) - .map((b: BondedPool) => b.id); + .filter((b) => b.roles.depositor === who) + .map((b) => b.id); const root = bondedPools .filter((b: BondedPool) => b.roles.root === who) - .map((b: BondedPool) => b.id); + .map((b) => b.id); const nominator = bondedPools - .filter((b: BondedPool) => b.roles.nominator === who) - .map((b: BondedPool) => b.id); + .filter((b) => b.roles.nominator === who) + .map((b) => b.id); - const stateToggler = bondedPools - .filter((b: BondedPool) => b.roles.stateToggler === who) - .map((b: BondedPool) => b.id); + const bouncer = bondedPools + .filter((b) => b.roles.bouncer === who) + .map((b) => b.id); return { depositor, root, nominator, - stateToggler, + bouncer, }; }; // accumulate account pool list - const getAccountPools = (who: MaybeAccount) => { + const getAccountPools = (who: MaybeAddress) => { // first get the roles of the account const roles = getAccountRoles(who); - // format new list has pool => roles const pools: any = {}; Object.entries(roles).forEach(([key, poolIds]: any) => { // now looping through a role poolIds.forEach((poolId: string) => { - const exists = Object.keys(pools).find((k: string) => k === poolId); + const exists = Object.keys(pools).find((k) => k === poolId); if (!exists) { pools[poolId] = [key]; } else { @@ -446,20 +398,18 @@ export const BondedPoolsProvider = ({ const toReplace = (roleEdits: any) => { const root = roleEdits?.root?.newAddress ?? ''; const nominator = roleEdits?.nominator?.newAddress ?? ''; - const stateToggler = roleEdits?.stateToggler?.newAddress ?? ''; + const bouncer = roleEdits?.bouncer?.newAddress ?? ''; return { root, nominator, - stateToggler, + bouncer, }; }; // replaces the pool roles from roleEdits const replacePoolRoles = (poolId: number, roleEdits: any) => { - let pool = - bondedPools.find((b: BondedPool) => String(b.id) === String(poolId)) || - null; + let pool = bondedPools.find((b) => String(b.id) === String(poolId)) || null; if (!pool) return; @@ -472,7 +422,7 @@ export const BondedPoolsProvider = ({ }; const newBondedPools = [ - ...bondedPools.map((b: BondedPool) => + ...bondedPools.map((b) => String(b.id) === String(poolId) && pool !== null ? pool : b ), ]; @@ -480,6 +430,26 @@ export const BondedPoolsProvider = ({ setBondedPools(newBondedPools); }; + // Clear existing state for network refresh. + useEffectIgnoreInitial(() => { + setBondedPools([]); + setStateWithRef({}, setPoolMetaBatch, poolMetaBatchesRef); + }, [network]); + + // Initial setup for fetching bonded pools. + useEffectIgnoreInitial(() => { + if (isReady) fetchBondedPools(); + return () => { + unsubscribe(); + }; + }, [network, isReady, lastPoolId]); + + // After bonded pools have synced, fetch metabatch. + useEffectIgnoreInitial(() => { + if (bondedPools.length) + fetchPoolsMetaBatch('bonded_pools', bondedPools, true); + }, [bondedPools]); + return ( <BondedPoolsContext.Provider value={{ @@ -503,3 +473,9 @@ export const BondedPoolsProvider = ({ </BondedPoolsContext.Provider> ); }; + +export const BondedPoolsContext = React.createContext<BondedPoolsContextState>( + defaultBondedPoolsContext +); + +export const useBondedPools = () => React.useContext(BondedPoolsContext); diff --git a/src/contexts/Pools/PoolMembers/defaults.ts b/src/contexts/Pools/PoolMembers/defaults.ts index 0d71ca74dd..8cb8fbce28 100644 --- a/src/contexts/Pools/PoolMembers/defaults.ts +++ b/src/contexts/Pools/PoolMembers/defaults.ts @@ -1,21 +1,20 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only +/* eslint-disable @typescript-eslint/no-unused-vars */ -import { PoolMemberContext } from '../types'; +import type { PoolMemberContext } from '../types'; export const defaultPoolMembers: PoolMemberContext = { - // eslint-disable-next-line fetchPoolMembersMetaBatch: (k, v, r) => {}, - // eslint-disable-next-line queryPoolMember: (w) => {}, - // eslint-disable-next-line - getMembersOfPool: (p) => {}, - // eslint-disable-next-line + getMembersOfPoolFromNode: (p) => {}, addToPoolMembers: (m) => {}, - // eslint-disable-next-line - getPoolMember: (w) => null, - // eslint-disable-next-line removePoolMember: (w) => {}, - poolMembers: [], + getPoolMemberCount: (p) => 0, + poolMembersApi: [], + setPoolMembersApi: (p) => {}, + poolMembersNode: [], meta: {}, + fetchedPoolMembersApi: 'unsynced', + setFetchedPoolMembersApi: (s) => {}, }; diff --git a/src/contexts/Pools/PoolMembers/index.tsx b/src/contexts/Pools/PoolMembers/index.tsx index 430515ff7d..bc3e9af98c 100644 --- a/src/contexts/Pools/PoolMembers/index.tsx +++ b/src/contexts/Pools/PoolMembers/index.tsx @@ -1,106 +1,106 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { useConnect } from 'contexts/Connect'; -import { PoolMemberContext } from 'contexts/Pools/types'; -import React, { useEffect, useRef, useState } from 'react'; -import { AnyApi, AnyMetaBatch, Fn, MaybeAccount } from 'types'; -import { setStateWithRef } from 'Utils'; +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { setStateWithRef } from '@polkadot-cloud/utils'; +import React, { useRef, useState } from 'react'; +import { usePlugins } from 'contexts/Plugins'; +import type { PoolMember, PoolMemberContext } from 'contexts/Pools/types'; +import type { AnyApi, AnyMetaBatch, Fn, MaybeAddress, Sync } from 'types'; +import { useEffectIgnoreInitial } from '@polkadot-cloud/react/hooks'; +import { useNetwork } from 'contexts/Network'; +import { useActiveAccounts } from 'contexts/ActiveAccounts'; import { useApi } from '../../Api'; import { defaultPoolMembers } from './defaults'; -export const PoolMembersContext = - React.createContext<PoolMemberContext>(defaultPoolMembers); - -export const usePoolMembers = () => React.useContext(PoolMembersContext); - export const PoolMembersProvider = ({ children, }: { children: React.ReactNode; }) => { - const { api, network, isReady } = useApi(); - const { activeAccount } = useConnect(); + const { network } = useNetwork(); + const { api, isReady } = useApi(); + const { pluginEnabled } = usePlugins(); + const { activeAccount } = useActiveAccounts(); + + // Store pool members from node. + const [poolMembersNode, setPoolMembersNode] = useState<PoolMember[]>([]); - // store pool members - const [poolMembers, setPoolMembers] = useState<Array<any>>([]); + // Store pool members from api. + const [poolMembersApi, setPoolMembersApi] = useState<PoolMember[]>([]); - // stores the meta data batches for pool member lists + // Store whether pool members from api have been fetched. + const fetchedPoolMembersApi = useRef<Sync>('unsynced'); + + // Stores the meta data batches for pool member lists. const [poolMembersMetaBatches, setPoolMembersMetaBatch]: AnyMetaBatch = useState({}); const poolMembersMetaBatchesRef = useRef(poolMembersMetaBatches); - // stores the meta batch subscriptions for pool lists - const [poolMembersSubs, setPoolMembersSubs] = useState<{ - [key: string]: Array<Fn>; - }>({}); - const poolMembersSubsRef = useRef(poolMembersSubs); + // Stores the meta batch subscriptions for pool lists. + const poolMembersSubs = useRef<Record<string, Fn[]>>({}); + + // Update poolMembersApi fetched status. + const setFetchedPoolMembersApi = (status: Sync) => { + fetchedPoolMembersApi.current = status; + }; - // clear existing state for network refresh - useEffect(() => { - setPoolMembers([]); + // Clear existing state for network refresh + useEffectIgnoreInitial(() => { + setPoolMembersNode([]); unsubscribeAndResetMeta(); }, [network]); - // clear meta state when activeAccount changes - useEffect(() => { + // Clear meta state when activeAccount changes + useEffectIgnoreInitial(() => { unsubscribeAndResetMeta(); }, [activeAccount]); - // initial setup for fetching members - useEffect(() => { - if (isReady) { - // fetch bonded pools - fetchPoolMembers(); + // Initial setup for fetching members if Subscan is not enabled. Ensure poolMembers are reset if + // subscan is disabled. + useEffectIgnoreInitial(() => { + if (!pluginEnabled('subscan')) { + if (isReady) fetchPoolMembers(); + } else { + setPoolMembersNode([]); } return () => { unsubscribe(); }; - }, [network, isReady]); + }, [network, isReady, pluginEnabled('subscan')]); const unsubscribe = () => { unsubscribeAndResetMeta(); - setPoolMembers([]); + setPoolMembersNode([]); }; const unsubscribeAndResetMeta = () => { - Object.values(poolMembersSubsRef.current).map((batch: Array<Fn>) => { - return Object.entries(batch).map(([, v]) => { - return v(); - }); - }); + Object.values(poolMembersSubs.current).map((batch: Fn[]) => + Object.entries(batch).map(([, v]) => v()) + ); setStateWithRef({}, setPoolMembersMetaBatch, poolMembersMetaBatchesRef); }; - // fetch all pool members entries + // Fetch all pool members entries from node. const fetchPoolMembers = async () => { if (!api) return; - const _exposures = await api.query.nominationPools.poolMembers.entries(); - const exposures = _exposures.map(([_keys, _val]: AnyApi) => { - const who = _keys.toHuman()[0]; - const membership = _val.toHuman(); - const { poolId } = membership; - + const result = await api.query.nominationPools.poolMembers.entries(); + const newMembers = result.map(([keys, val]: AnyApi) => { + const who = keys.toHuman()[0]; + const { poolId } = val.toHuman(); return { who, poolId, }; }); - - setPoolMembers(exposures); - }; - - const getMembersOfPool = (poolId: number) => { - return poolMembers.filter((p: any) => p.poolId === String(poolId)) ?? null; + setPoolMembersNode(newMembers); }; - const getPoolMember = (who: MaybeAccount) => { - return poolMembers.find((p: any) => p.who === who) ?? null; - }; + const getMembersOfPoolFromNode = (poolId: number) => + poolMembersNode.filter((p: any) => p.poolId === String(poolId)) ?? null; - // queries a pool member and formats to `poolMembers`. - const queryPoolMember = async (who: MaybeAccount) => { + // queries a pool member and formats to `PoolMember`. + const queryPoolMember = async (who: MaybeAddress) => { if (!api) return null; const poolMember: AnyApi = ( @@ -116,6 +116,10 @@ export const PoolMembersProvider = ({ }; }; + // Gets the count of members in a pool from node data. + const getPoolMemberCount = (poolId: number) => + getMembersOfPoolFromNode(poolId ?? 0).length; + /* Fetches a new batch of pool member metadata. structure: @@ -138,9 +142,6 @@ export const PoolMembersProvider = ({ if (!isReady || !api) { return; } - if (!poolMembers.length) { - return; - } if (!p.length) { return; } @@ -151,11 +152,18 @@ export const PoolMembersProvider = ({ } } else { // tidy up if existing batch exists - delete poolMembersMetaBatches[key]; - delete poolMembersMetaBatchesRef.current[key]; + const updatedPoolMembersMetaBatches: AnyMetaBatch = { + ...poolMembersMetaBatchesRef.current, + }; + delete updatedPoolMembersMetaBatches[key]; + setStateWithRef( + updatedPoolMembersMetaBatches, + setPoolMembersMetaBatch, + poolMembersMetaBatchesRef + ); - if (poolMembersSubsRef.current[key] !== undefined) { - for (const unsub of poolMembersSubsRef.current[key]) { + if (poolMembersSubs.current[key] !== undefined) { + for (const unsub of poolMembersSubs.current[key]) { unsub(); } } @@ -163,8 +171,8 @@ export const PoolMembersProvider = ({ // aggregate member addresses const addresses = []; - for (const _p of p) { - addresses.push(_p.who); + for (const { who } of p) { + addresses.push(who); } // store batch addresses @@ -185,12 +193,10 @@ export const PoolMembersProvider = ({ for (let i = 0; i < _pools.length; i++) { pools.push(_pools[i].toHuman()); } - const _batchesUpdated = Object.assign( - poolMembersMetaBatchesRef.current - ); - _batchesUpdated[key].poolMembers = pools; + const updated = Object.assign(poolMembersMetaBatchesRef.current); + updated[key].poolMembers = pools; setStateWithRef( - { ..._batchesUpdated }, + { ...updated }, setPoolMembersMetaBatch, poolMembersMetaBatchesRef ); @@ -207,12 +213,10 @@ export const PoolMembersProvider = ({ for (let i = 0; i < _identities.length; i++) { identities.push(_identities[i].toHuman()); } - const _batchesUpdated = Object.assign( - poolMembersMetaBatchesRef.current - ); - _batchesUpdated[key].identities = identities; + const updated = Object.assign(poolMembersMetaBatchesRef.current); + updated[key].identities = identities; setStateWithRef( - { ..._batchesUpdated }, + { ...updated }, setPoolMembersMetaBatch, poolMembersMetaBatchesRef ); @@ -224,15 +228,15 @@ export const PoolMembersProvider = ({ const subscribeToSuperIdentities = async (addr: string[]) => { const unsub = await api.query.identity.superOf.multi<AnyApi>( addr, - async (_supers) => { + async (result) => { // determine where supers exist const supers: AnyApi = []; const supersWithIdentity: AnyApi = []; - for (let i = 0; i < _supers.length; i++) { - const _super = _supers[i].toHuman(); - supers.push(_super); - if (_super !== null) { + for (let i = 0; i < result.length; i++) { + const item = result[i].toHuman(); + supers.push(item); + if (item !== null) { supersWithIdentity.push(i); } } @@ -246,20 +250,18 @@ export const PoolMembersProvider = ({ query, (_identities) => { for (let j = 0; j < _identities.length; j++) { - const _identity = _identities[j].toHuman(); + const identity = _identities[j].toHuman(); // inject identity into super array - supers[supersWithIdentity[j]].identity = _identity; + supers[supersWithIdentity[j]].identity = identity; } } ); temp(); - const _batchesUpdated = Object.assign( - poolMembersMetaBatchesRef.current - ); - _batchesUpdated[key].supers = supers; + const updated = Object.assign(poolMembersMetaBatchesRef.current); + updated[key].supers = supers; setStateWithRef( - { ..._batchesUpdated }, + { ...updated }, setPoolMembersMetaBatch, poolMembersMetaBatchesRef ); @@ -273,56 +275,64 @@ export const PoolMembersProvider = ({ subscribeToIdentities(addresses), subscribeToSuperIdentities(addresses), subscribeToPoolMembers(addresses), - ]).then((unsubs: Array<Fn>) => { + ]).then((unsubs: Fn[]) => { addMetaBatchUnsubs(key, unsubs); }); }; - /* - * Removes a member from the member list and updates state. - */ - const removePoolMember = (who: MaybeAccount) => { - const newMembers = poolMembers.filter((p: any) => p.who !== who); - setPoolMembers(newMembers ?? []); - }; + // Removes a member from the member list and updates state. + const removePoolMember = (who: MaybeAddress) => { + if (!pluginEnabled('subscan')) return; - /* - * Helper: to add mataBatch unsubs by key. - */ - const addMetaBatchUnsubs = (key: string, unsubs: Array<Fn>) => { - const _unsubs = poolMembersSubsRef.current; - const _keyUnsubs = _unsubs[key] ?? []; - _keyUnsubs.push(...unsubs); - _unsubs[key] = _keyUnsubs; - setStateWithRef(_unsubs, setPoolMembersSubs, poolMembersSubsRef); + const newMembers = poolMembersNode.filter((p: any) => p.who !== who); + setPoolMembersNode(newMembers ?? []); }; - // adds a record to poolMembers. - // currently only used when an account joins or creates a pool. + // Adds a record to poolMembers. + // Currently only used when an account joins or creates a pool. const addToPoolMembers = (member: any) => { - if (!member) return; + if (!member || pluginEnabled('subscan')) return; - const exists = poolMembers.find((m: any) => m.who === member.who); + const exists = poolMembersNode.find((m: any) => m.who === member.who); if (!exists) { - const _poolMembers = poolMembers.concat(member); - setPoolMembers(_poolMembers); + setPoolMembersNode(poolMembersNode.concat(member)); } }; + /* + * Helper: to add mataBatch unsubs by key. + */ + const addMetaBatchUnsubs = (key: string, unsubs: Fn[]) => { + const subs = poolMembersSubs.current; + const sub = subs[key] ?? []; + sub.push(...unsubs); + subs[key] = sub; + poolMembersSubs.current = subs; + }; + return ( <PoolMembersContext.Provider value={{ fetchPoolMembersMetaBatch, queryPoolMember, - getMembersOfPool, + getMembersOfPoolFromNode, addToPoolMembers, - getPoolMember, removePoolMember, - poolMembers, + getPoolMemberCount, + poolMembersNode, + poolMembersApi, + setPoolMembersApi, + fetchedPoolMembersApi: fetchedPoolMembersApi.current, meta: poolMembersMetaBatchesRef.current, + setFetchedPoolMembersApi, }} > {children} </PoolMembersContext.Provider> ); }; + +export const PoolMembersContext = + React.createContext<PoolMemberContext>(defaultPoolMembers); + +export const usePoolMembers = () => React.useContext(PoolMembersContext); diff --git a/src/contexts/Pools/PoolMemberships/defaults.ts b/src/contexts/Pools/PoolMemberships/defaults.ts index 53d4a1bb5c..b708431b9a 100644 --- a/src/contexts/Pools/PoolMemberships/defaults.ts +++ b/src/contexts/Pools/PoolMemberships/defaults.ts @@ -1,11 +1,12 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import { PoolMembership, PoolMembershipsContextState } from '../types'; +import type { PoolMembership, PoolMembershipsContextState } from '../types'; export const poolMembership: PoolMembership | null = null; export const defaultPoolMembershipsContext: PoolMembershipsContextState = { memberships: [], membership: null, + claimPermissionConfig: [], }; diff --git a/src/contexts/Pools/PoolMemberships/index.tsx b/src/contexts/Pools/PoolMemberships/index.tsx index 0f01475766..4ef225fbed 100644 --- a/src/contexts/Pools/PoolMemberships/index.tsx +++ b/src/contexts/Pools/PoolMemberships/index.tsx @@ -1,52 +1,46 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import BN from 'bn.js'; -import { ImportedAccount } from 'contexts/Connect/types'; -import { +import { rmCommas, setStateWithRef } from '@polkadot-cloud/utils'; +import BigNumber from 'bignumber.js'; +import React, { useEffect, useRef, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import type { + ClaimPermissionConfig, PoolMembership, PoolMembershipsContextState, } from 'contexts/Pools/types'; -import React, { useEffect, useRef, useState } from 'react'; -import { AnyApi, Fn } from 'types'; -import { rmCommas, setStateWithRef } from 'Utils'; +import type { AnyApi, Fn } from 'types'; +import { useEffectIgnoreInitial } from '@polkadot-cloud/react/hooks'; +import { useNetwork } from 'contexts/Network'; +import { useActiveAccounts } from 'contexts/ActiveAccounts'; +import { useImportedAccounts } from 'contexts/Connect/ImportedAccounts'; import { useApi } from '../../Api'; -import { useConnect } from '../../Connect'; import * as defaults from './defaults'; -export const PoolMembershipsContext = - React.createContext<PoolMembershipsContextState>( - defaults.defaultPoolMembershipsContext - ); - -export const usePoolMemberships = () => - React.useContext(PoolMembershipsContext); - export const PoolMembershipsProvider = ({ children, }: { children: React.ReactNode; }) => { - const { api, network, isReady } = useApi(); - const { accounts: connectAccounts, activeAccount } = useConnect(); - - // stores pool membership - const [poolMemberships, setPoolMemberships] = useState<Array<PoolMembership>>( - [] - ); + const { t } = useTranslation('base'); + const { network } = useNetwork(); + const { api, isReady } = useApi(); + const { activeAccount } = useActiveAccounts(); + const { accounts: connectAccounts } = useImportedAccounts(); + + // Stores pool memberships for the imported accounts. + const [poolMemberships, setPoolMemberships] = useState<PoolMembership[]>([]); const poolMembershipsRef = useRef(poolMemberships); - // stores pool subscription objects - const [poolMembershipUnsubs, setPoolMembershipUnsubs] = useState<Array<Fn>>( - [] - ); - const poolMembershipUnsubRefs = useRef<Array<AnyApi>>(poolMembershipUnsubs); + // Stores pool membership unsubs. + const unsubs = useRef<AnyApi[]>([]); - useEffect(() => { + useEffectIgnoreInitial(() => { if (isReady) { (() => { setStateWithRef([], setPoolMemberships, poolMembershipsRef); - unsubscribeAll(); + unsubAll(); getPoolMemberships(); })(); } @@ -55,82 +49,94 @@ export const PoolMembershipsProvider = ({ // subscribe to account pool memberships const getPoolMemberships = async () => { Promise.all( - connectAccounts.map((a: ImportedAccount) => - subscribeToPoolMembership(a.address) - ) + connectAccounts.map(({ address }) => subscribeToPoolMembership(address)) ); }; // unsubscribe from pool memberships on unmount - useEffect(() => { - return () => { - unsubscribeAll(); - }; - }, []); + useEffect( + () => () => { + unsubAll(); + }, + [] + ); // unsubscribe from all pool memberships - const unsubscribeAll = () => { - Object.values(poolMembershipUnsubRefs.current).forEach((v: Fn) => v()); + const unsubAll = () => { + Object.values(unsubs.current).forEach((v: Fn) => v()); }; // subscribe to an account's pool membership const subscribeToPoolMembership = async (address: string) => { - if (!api) return; - - const unsub = await api.query.nominationPools.poolMembers( - address, - async (result: AnyApi) => { - let membership = result?.unwrapOr(undefined)?.toHuman(); - - if (membership) { - // format pool's unlocking chunks - const unbondingEras: AnyApi = membership.unbondingEras; - const unlocking = []; - for (const [e, v] of Object.entries(unbondingEras || {})) { - const era = rmCommas(e as string); - const value = rmCommas(v as string); - unlocking.push({ - era: Number(era), - value: new BN(value), - }); - } - membership.points = membership.points - ? rmCommas(membership.points) - : '0'; - membership = { - address, - ...membership, - unlocking, - }; - - // remove stale membership if it's already in list - let _poolMemberships = Object.values(poolMembershipsRef.current); - _poolMemberships = _poolMemberships - .filter((m: PoolMembership) => m.address !== address) - .concat(membership); - - setStateWithRef( - _poolMemberships, - setPoolMemberships, - poolMembershipsRef - ); - } else { - // no membership: remove account membership if present - let _poolMemberships = Object.values(poolMembershipsRef.current); - _poolMemberships = _poolMemberships.filter( - (m: PoolMembership) => m.address !== address - ); - setStateWithRef( - _poolMemberships, - setPoolMemberships, - poolMembershipsRef - ); - } + if (!api) return undefined; + + const unsub = await api.queryMulti<AnyApi>( + [ + [api.query.nominationPools.poolMembers, address], + [api.query.nominationPools.claimPermissions, address], + ], + ([poolMember, claimPermission]) => { + handleMembership(poolMember, claimPermission); } ); - const _unsubs = poolMembershipUnsubRefs.current.concat(unsub); - setStateWithRef(_unsubs, setPoolMembershipUnsubs, poolMembershipUnsubRefs); + const handleMembership = async ( + poolMember: AnyApi, + claimPermission?: AnyApi + ) => { + let membership = poolMember?.unwrapOr(undefined)?.toHuman(); + + if (membership) { + // format pool's unlocking chunks + const unbondingEras: AnyApi = membership.unbondingEras; + const unlocking = []; + for (const [e, v] of Object.entries(unbondingEras || {})) { + unlocking.push({ + era: Number(rmCommas(e as string)), + value: new BigNumber(rmCommas(v as string)), + }); + } + membership.points = membership.points + ? rmCommas(membership.points) + : '0'; + + const balance = + ( + await api.call.nominationPoolsApi.pointsToBalance( + membership.poolId, + membership.points + ) + )?.toString() || '0'; + + membership = { + ...membership, + balance: new BigNumber(balance), + address, + unlocking, + claimPermission: claimPermission?.toString() || 'Permissioned', + }; + + // remove stale membership if it's already in list, and add to memberships. + setStateWithRef( + Object.values(poolMembershipsRef.current) + .filter((m) => m.address !== address) + .concat(membership), + setPoolMemberships, + poolMembershipsRef + ); + } else { + // no membership: remove account membership if present. + setStateWithRef( + Object.values(poolMembershipsRef.current).filter( + (m) => m.address !== address + ), + setPoolMemberships, + poolMembershipsRef + ); + } + }; + + unsubs.current = unsubs.current.concat(unsub); return unsub; }; @@ -140,7 +146,7 @@ export const PoolMembershipsProvider = ({ return defaults.poolMembership; } const poolMembership = poolMembershipsRef.current.find( - (m: PoolMembership) => m.address === activeAccount + (m) => m.address === activeAccount ); if (poolMembership === undefined) { return defaults.poolMembership; @@ -148,14 +154,41 @@ export const PoolMembershipsProvider = ({ return poolMembership; }; + const claimPermissionConfig: ClaimPermissionConfig[] = [ + { + label: t('allowCompound'), + value: 'PermissionlessCompound', + description: t('allowAnyoneCompound'), + }, + { + label: t('allowWithdraw'), + value: 'PermissionlessWithdraw', + description: t('allowAnyoneWithdraw'), + }, + { + label: t('allowAll'), + value: 'PermissionlessAll', + description: t('allowAnyoneCompoundWithdraw'), + }, + ]; + return ( <PoolMembershipsContext.Provider value={{ membership: getActiveAccountPoolMembership(), memberships: poolMembershipsRef.current, + claimPermissionConfig, }} > {children} </PoolMembershipsContext.Provider> ); }; + +export const PoolMembershipsContext = + React.createContext<PoolMembershipsContextState>( + defaults.defaultPoolMembershipsContext + ); + +export const usePoolMemberships = () => + React.useContext(PoolMembershipsContext); diff --git a/src/contexts/Pools/PoolPerformance/defaults.ts b/src/contexts/Pools/PoolPerformance/defaults.ts new file mode 100644 index 0000000000..a1e9bd2420 --- /dev/null +++ b/src/contexts/Pools/PoolPerformance/defaults.ts @@ -0,0 +1,10 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only +/* eslint-disable @typescript-eslint/no-unused-vars */ + +import type { PoolPerformanceContextInterface } from './types'; + +export const defaultPoolPerformanceContext: PoolPerformanceContextInterface = { + poolRewardPointsFetched: 'unsynced', + poolRewardPoints: {}, +}; diff --git a/src/contexts/Pools/PoolPerformance/index.tsx b/src/contexts/Pools/PoolPerformance/index.tsx new file mode 100644 index 0000000000..49e16e51d4 --- /dev/null +++ b/src/contexts/Pools/PoolPerformance/index.tsx @@ -0,0 +1,145 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import React, { useState } from 'react'; +import { MaxEraRewardPointsEras } from 'consts'; +import { useEffectIgnoreInitial } from '@polkadot-cloud/react/hooks'; +import Worker from 'workers/poolPerformance?worker'; +import { useNetwork } from 'contexts/Network'; +import { useValidators } from 'contexts/Validators/ValidatorEntries'; +import { useBondedPools } from 'contexts/Pools/BondedPools'; +import { useNetworkMetrics } from 'contexts/NetworkMetrics'; +import { useApi } from 'contexts/Api'; +import type { Sync } from '@polkadot-cloud/react/types'; +import BigNumber from 'bignumber.js'; +import { formatRawExposures } from 'contexts/Staking/Utils'; +import { mergeDeep } from '@polkadot-cloud/utils'; +import type { PoolPerformanceContextInterface } from './types'; +import { defaultPoolPerformanceContext } from './defaults'; + +const worker = new Worker(); + +export const PoolPerformanceProvider = ({ + children, +}: { + children: React.ReactNode; +}) => { + const { api } = useApi(); + const { network } = useNetwork(); + const { bondedPools } = useBondedPools(); + const { activeEra } = useNetworkMetrics(); + const { erasRewardPointsFetched, erasRewardPoints } = useValidators(); + + // Store whether pool performance data is being fetched. + const [poolRewardPointsFetched, setPoolRewardPointsFetched] = + useState<Sync>('unsynced'); + + // Store pool performance data. + const [poolRewardPoints, setPoolRewardPoints] = useState< + Record<string, Record<string, string>> + >({}); + + // Store the currently active era being processed for pool performance. + const [currentEra, setCurrentEra] = useState<BigNumber>(new BigNumber(0)); + + // Store the earliest era that should be processed. + const [finishEra, setFinishEra] = useState<BigNumber>(new BigNumber(0)); + + // Handle worker message on completed exposure check. + worker.onmessage = (message: MessageEvent) => { + if (message) { + const { data } = message; + const { task } = data; + if (task !== 'processNominationPoolsRewardData') return; + + // Update state with new data. + const { poolRewardData } = data; + setPoolRewardPoints(mergeDeep(poolRewardPoints, poolRewardData)); + + if (currentEra.isEqualTo(finishEra)) { + setPoolRewardPointsFetched('synced'); + } else { + const nextEra = BigNumber.max(currentEra.minus(1), 1); + processEra(nextEra); + } + } + }; + + // Start fetching pool performance calls from the current era. + const startGetPoolPerformance = async () => { + setPoolRewardPointsFetched('syncing'); + setFinishEra( + BigNumber.max(activeEra.index.minus(MaxEraRewardPointsEras), 1) + ); + const startEra = BigNumber.max(activeEra.index.minus(1), 1); + processEra(startEra); + }; + + // Get era data and send to worker. + const processEra = async (era: BigNumber) => { + if (!api) return; + setCurrentEra(era); + const result = await api.query.staking.erasStakersClipped.entries( + era.toString() + ); + const exposures = formatRawExposures(result); + worker.postMessage({ + task: 'processNominationPoolsRewardData', + era: era.toString(), + exposures, + bondedPools: bondedPools.map((b) => b.addresses.stash), + erasRewardPoints, + }); + }; + + // Trigger worker to calculate pool reward data for garaphs once: + // + // - active era is synced. + // - era reward points are fetched. + // - bonded pools have been fetched. + // + // Re-calculates when any of the above change. + useEffectIgnoreInitial(() => { + if ( + api && + bondedPools.length && + activeEra.index.isGreaterThan(0) && + erasRewardPointsFetched === 'synced' && + poolRewardPointsFetched === 'unsynced' + ) { + startGetPoolPerformance(); + } + }, [ + bondedPools, + activeEra, + erasRewardPointsFetched, + poolRewardPointsFetched, + ]); + + // Reset state data on network change. + useEffectIgnoreInitial(() => { + setPoolRewardPoints({}); + setCurrentEra(new BigNumber(0)); + setFinishEra(new BigNumber(0)); + setPoolRewardPointsFetched('unsynced'); + }, [network]); + + return ( + <PoolPerformanceContext.Provider + value={{ + poolRewardPointsFetched, + poolRewardPoints, + }} + > + {children} + </PoolPerformanceContext.Provider> + ); +}; + +export const PoolPerformanceContext = + React.createContext<PoolPerformanceContextInterface>( + defaultPoolPerformanceContext + ); + +export const usePoolPerformance = () => + React.useContext(PoolPerformanceContext); diff --git a/src/contexts/Pools/PoolPerformance/types.ts b/src/contexts/Pools/PoolPerformance/types.ts new file mode 100644 index 0000000000..acef8792bb --- /dev/null +++ b/src/contexts/Pools/PoolPerformance/types.ts @@ -0,0 +1,10 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import type { AnyJson } from '@polkadot-cloud/react/types'; +import type { Sync } from 'types'; + +export interface PoolPerformanceContextInterface { + poolRewardPointsFetched: Sync; + poolRewardPoints: AnyJson; +} diff --git a/src/contexts/Pools/PoolsConfig/defaults.ts b/src/contexts/Pools/PoolsConfig/defaults.ts index b750d2b2ec..e726d5e388 100644 --- a/src/contexts/Pools/PoolsConfig/defaults.ts +++ b/src/contexts/Pools/PoolsConfig/defaults.ts @@ -1,23 +1,24 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import BN from 'bn.js'; -import { +import BigNumber from 'bignumber.js'; +import type { PoolAddresses, PoolsConfigContextState, PoolStats, } from 'contexts/Pools/types'; export const stats: PoolStats = { - counterForPoolMembers: new BN(0), - counterForBondedPools: new BN(0), - counterForRewardPools: new BN(0), - lastPoolId: new BN(0), - maxPoolMembers: new BN(0), - maxPoolMembersPerPool: new BN(0), - maxPools: new BN(0), - minCreateBond: new BN(0), - minJoinBond: new BN(0), + counterForPoolMembers: new BigNumber(0), + counterForBondedPools: new BigNumber(0), + counterForRewardPools: new BigNumber(0), + lastPoolId: new BigNumber(0), + maxPoolMembers: null, + maxPoolMembersPerPool: null, + maxPools: null, + minCreateBond: new BigNumber(0), + minJoinBond: new BigNumber(0), + globalMaxCommission: 0, }; export const defaultPoolsConfigContext: PoolsConfigContextState = { diff --git a/src/contexts/Pools/PoolsConfig/index.tsx b/src/contexts/Pools/PoolsConfig/index.tsx index ed06f38dcc..9b2f38a56a 100644 --- a/src/contexts/Pools/PoolsConfig/index.tsx +++ b/src/contexts/Pools/PoolsConfig/index.tsx @@ -1,28 +1,29 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only import { bnToU8a, u8aConcat } from '@polkadot/util'; +import { rmCommas, setStateWithRef } from '@polkadot-cloud/utils'; +import BigNumber from 'bignumber.js'; import BN from 'bn.js'; +import React, { useRef, useState } from 'react'; import { EmptyH256, ModPrefix, U32Opts } from 'consts'; -import { PoolConfigState, PoolsConfigContextState } from 'contexts/Pools/types'; -import React, { useEffect, useRef, useState } from 'react'; -import { AnyApi } from 'types'; -import { rmCommas, setStateWithRef } from 'Utils'; +import type { + PoolConfigState, + PoolsConfigContextState, +} from 'contexts/Pools/types'; +import type { AnyApi } from 'types'; +import { useEffectIgnoreInitial } from '@polkadot-cloud/react/hooks'; +import { useNetwork } from 'contexts/Network'; import { useApi } from '../../Api'; import * as defaults from './defaults'; -export const PoolsConfigContext = React.createContext<PoolsConfigContextState>( - defaults.defaultPoolsConfigContext -); - -export const usePoolsConfig = () => React.useContext(PoolsConfigContext); - export const PoolsConfigProvider = ({ children, }: { children: React.ReactNode; }) => { - const { api, network, isReady, consts } = useApi(); + const { network } = useNetwork(); + const { api, isReady, consts } = useApi(); const { poolsPalletId } = consts; // store pool metadata @@ -32,18 +33,16 @@ export const PoolsConfigProvider = ({ }); const poolsConfigRef = useRef(poolsConfig); - // get favorite pools from local storage - const getFavorites = () => { - const _favorites = localStorage.getItem( - `${network.name.toLowerCase()}_favorite_pools` - ); - return _favorites !== null ? JSON.parse(_favorites) : []; + // get favorite pools from local storage. + const getLocalFavorites = () => { + const localFavorites = localStorage.getItem(`${network}_favorite_pools`); + return localFavorites !== null ? JSON.parse(localFavorites) : []; }; // stores the user's favorite pools - const [favorites, setFavorites] = useState<string[]>(getFavorites()); + const [favorites, setFavorites] = useState<string[]>(getLocalFavorites()); - useEffect(() => { + useEffectIgnoreInitial(() => { if (isReady) { subscribeToPoolConfig(); } @@ -73,45 +72,58 @@ export const PoolsConfigProvider = ({ api.query.nominationPools.maxPools, api.query.nominationPools.minCreateBond, api.query.nominationPools.minJoinBond, + api.query.nominationPools.globalMaxCommission, ], ([ - _counterForPoolMembers, - _counterForBondedPools, - _counterForRewardPools, - _lastPoolId, - _maxPoolMembers, - _maxPoolMembersPerPool, - _maxPools, - _minCreateBond, - _minJoinBond, + counterForPoolMembers, + counterForBondedPools, + counterForRewardPools, + lastPoolId, + maxPoolMembers, + maxPoolMembersPerPool, + maxPools, + minCreateBond, + minJoinBond, + globalMaxCommission, ]) => { - // format optional configs to BN or null - _maxPoolMembers = _maxPoolMembers.toHuman(); - if (_maxPoolMembers !== null) { - _maxPoolMembers = new BN(rmCommas(_maxPoolMembers)); + // format optional configs to BigNumber or null + maxPoolMembers = maxPoolMembers.toHuman(); + if (maxPoolMembers !== null) { + maxPoolMembers = new BigNumber(rmCommas(maxPoolMembers)); } - _maxPoolMembersPerPool = _maxPoolMembersPerPool.toHuman(); - if (_maxPoolMembersPerPool !== null) { - _maxPoolMembersPerPool = new BN(rmCommas(_maxPoolMembersPerPool)); + maxPoolMembersPerPool = maxPoolMembersPerPool.toHuman(); + if (maxPoolMembersPerPool !== null) { + maxPoolMembersPerPool = new BigNumber( + rmCommas(maxPoolMembersPerPool) + ); } - _maxPools = _maxPools.toHuman(); - if (_maxPools !== null) { - _maxPools = new BN(rmCommas(_maxPools)); + maxPools = maxPools.toHuman(); + if (maxPools !== null) { + maxPools = new BigNumber(rmCommas(maxPools)); } setStateWithRef( { ...poolsConfigRef.current, stats: { - counterForPoolMembers: _counterForPoolMembers.toBn(), - counterForBondedPools: _counterForBondedPools.toBn(), - counterForRewardPools: _counterForRewardPools.toBn(), - lastPoolId: _lastPoolId.toBn(), - maxPoolMembers: _maxPoolMembers, - maxPoolMembersPerPool: _maxPoolMembersPerPool, - maxPools: _maxPools, - minCreateBond: _minCreateBond.toBn(), - minJoinBond: _minJoinBond.toBn(), + counterForPoolMembers: new BigNumber( + counterForPoolMembers.toString() + ), + counterForBondedPools: new BigNumber( + counterForBondedPools.toString() + ), + counterForRewardPools: new BigNumber( + counterForRewardPools.toString() + ), + lastPoolId: new BigNumber(lastPoolId.toString()), + maxPoolMembers, + maxPoolMembersPerPool, + maxPools, + minCreateBond: new BigNumber(minCreateBond.toString()), + minJoinBond: new BigNumber(minJoinBond.toString()), + globalMaxCommission: Number( + globalMaxCommission.toHuman().slice(0, -1) + ), }, }, setPoolsConfig, @@ -134,43 +146,41 @@ export const PoolsConfigProvider = ({ * Adds a favorite validator. */ const addFavorite = (address: string) => { - const _favorites: any = Object.assign(favorites); - if (!_favorites.includes(address)) { - _favorites.push(address); - } + const newFavorites = Object.assign(favorites); + if (!newFavorites.includes(address)) newFavorites.push(address); localStorage.setItem( - `${network.name.toLowerCase()}_favorite_pools`, - JSON.stringify(_favorites) + `${network}_favorite_pools`, + JSON.stringify(newFavorites) ); - setFavorites([..._favorites]); + setFavorites([...newFavorites]); }; /* * Removes a favorite validator if they exist. */ const removeFavorite = (address: string) => { - let _favorites = Object.assign(favorites); - _favorites = _favorites.filter( + let newFavorites = Object.assign(favorites); + newFavorites = newFavorites.filter( (validator: string) => validator !== address ); localStorage.setItem( - `${network.name.toLowerCase()}_favorite_pools`, - JSON.stringify(_favorites) + `${network}_favorite_pools`, + JSON.stringify(newFavorites) ); - setFavorites([..._favorites]); + setFavorites([...newFavorites]); }; // Helper: generates pool stash and reward accounts. assumes poolsPalletId is synced. const createAccounts = (poolId: number) => { - const poolIdBN = new BN(poolId); + const poolIdBigNumber = new BigNumber(poolId); return { - stash: createAccount(poolIdBN, 0), - reward: createAccount(poolIdBN, 1), + stash: createAccount(poolIdBigNumber, 0), + reward: createAccount(poolIdBigNumber, 1), }; }; - const createAccount = (poolId: BN, index: number): string => { + const createAccount = (poolId: BigNumber, index: number): string => { if (!api) return ''; return api.registry .createType( @@ -179,7 +189,7 @@ export const PoolsConfigProvider = ({ ModPrefix, poolsPalletId, new Uint8Array([index]), - bnToU8a(poolId, U32Opts), + bnToU8a(new BN(poolId.toString()), U32Opts), EmptyH256 ) ) @@ -200,3 +210,9 @@ export const PoolsConfigProvider = ({ </PoolsConfigContext.Provider> ); }; + +export const PoolsConfigContext = React.createContext<PoolsConfigContextState>( + defaults.defaultPoolsConfigContext +); + +export const usePoolsConfig = () => React.useContext(PoolsConfigContext); diff --git a/src/contexts/Pools/types.ts b/src/contexts/Pools/types.ts index 9a0229c9c8..f8c068cff4 100644 --- a/src/contexts/Pools/types.ts +++ b/src/contexts/Pools/types.ts @@ -1,8 +1,8 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import BN from 'bn.js'; -import { AnyApi, AnyJson, AnyMetaBatch, MaybeAccount, Sync } from 'types'; +import type BigNumber from 'bignumber.js'; +import type { AnyApi, AnyJson, AnyMetaBatch, MaybeAddress, Sync } from 'types'; // PoolsConfig types export interface PoolsConfigContextState { @@ -18,34 +18,44 @@ export interface PoolConfigState { unsub: AnyApi; } +export type ClaimPermission = + | 'Permissioned' + | 'PermissionlessCompound' + | 'PermissionlessWithdraw' + | 'PermissionlessAll'; + export interface PoolStats { - counterForPoolMembers: BN; - counterForBondedPools: BN; - counterForRewardPools: BN; - lastPoolId: BN; - maxPoolMembers: BN; - maxPoolMembersPerPool: BN; - maxPools: BN; - minCreateBond: BN; - minJoinBond: BN; + counterForPoolMembers: BigNumber; + counterForBondedPools: BigNumber; + counterForRewardPools: BigNumber; + lastPoolId: BigNumber; + maxPoolMembers: BigNumber | null; + maxPoolMembersPerPool: BigNumber | null; + maxPools: BigNumber | null; + minCreateBond: BigNumber; + minJoinBond: BigNumber; + globalMaxCommission: number; } // PoolMemberships types export interface PoolMembershipsContextState { - memberships: Array<PoolMembership>; + memberships: PoolMembership[]; membership: PoolMembership | null; + claimPermissionConfig: ClaimPermissionConfig[]; } export interface PoolMembership { address: string; poolId: number; points: string; + balance: BigNumber; lastRecordedRewardCounter: string; - unbondingEras: { [key: number]: string }; - unlocking: Array<{ + unbondingEras: Record<number, string>; + claimPermission: ClaimPermission; + unlocking: { era: number; - value: BN; - }>; + value: BigNumber; + }[]; } // BondedPool types @@ -53,16 +63,16 @@ export interface BondedPoolsContextState { fetchPoolsMetaBatch: (k: string, v: [], r?: boolean) => void; queryBondedPool: (p: number) => any; getBondedPool: (p: number) => BondedPool | null; - updateBondedPools: (p: Array<BondedPool>) => void; + updateBondedPools: (p: BondedPool[]) => void; addToBondedPools: (p: BondedPool) => void; removeFromBondedPools: (p: number) => void; - getPoolNominationStatus: (n: MaybeAccount, o: MaybeAccount) => any; + getPoolNominationStatus: (n: MaybeAddress, o: MaybeAddress) => any; getPoolNominationStatusCode: (t: NominationStatuses | null) => string; - getAccountRoles: (w: MaybeAccount) => any; - getAccountPools: (w: MaybeAccount) => any; + getAccountRoles: (w: MaybeAddress) => any; + getAccountPools: (w: MaybeAddress) => any; replacePoolRoles: (poolId: number, roleEdits: AnyJson) => void; poolSearchFilter: (l: any, k: string, v: string) => void; - bondedPools: Array<BondedPool>; + bondedPools: BondedPool[]; meta: AnyMetaBatch; } @@ -72,7 +82,7 @@ export interface ActivePool { bondedPool: any; rewardPool: any; rewardAccountBalance: any; - unclaimedRewards: any; + pendingRewards: any; } export interface BondedPool { @@ -84,12 +94,21 @@ export interface BondedPool { depositor: string; nominator: string; root: string; - stateToggler: string; + bouncer: string; }; state: PoolState; + commission?: { + current?: AnyJson | null; + max?: AnyJson | null; + changeRate: { + maxIncrease: AnyJson; + minDelay: AnyJson; + } | null; + throttleFrom?: AnyJson | null; + }; } -export type NominationStatuses = { [key: string]: string }; +export type NominationStatuses = Record<string, string>; export interface ActivePoolsContextState { isBonding: () => boolean; @@ -97,8 +116,8 @@ export interface ActivePoolsContextState { isOwner: () => boolean; isMember: () => boolean; isDepositor: () => boolean; - isStateToggler: () => boolean; - getPoolBondedAccount: () => MaybeAccount; + isBouncer: () => boolean; + getPoolBondedAccount: () => MaybeAddress; getPoolUnlocking: () => any; getPoolRoles: () => PoolRoles; setTargets: (t: any) => void; @@ -108,18 +127,23 @@ export interface ActivePoolsContextState { targets: any; poolNominations: any; synced: Sync; + selectedPoolMemberCount: number; } // PoolMembers types export interface PoolMemberContext { - fetchPoolMembersMetaBatch: (k: string, v: [], r: boolean) => void; - queryPoolMember: (w: MaybeAccount) => any; - getMembersOfPool: (p: number) => any; + fetchPoolMembersMetaBatch: (k: string, v: AnyMetaBatch[], r: boolean) => void; + queryPoolMember: (w: MaybeAddress) => any; + getMembersOfPoolFromNode: (p: number) => any; addToPoolMembers: (m: any) => void; - getPoolMember: (w: MaybeAccount) => any | null; - removePoolMember: (w: MaybeAccount) => void; - poolMembers: any; + removePoolMember: (w: MaybeAddress) => void; + getPoolMemberCount: (p: number) => number; + poolMembersNode: any; meta: AnyMetaBatch; + poolMembersApi: PoolMember[]; + setPoolMembersApi: (p: PoolMember[]) => void; + fetchedPoolMembersApi: Sync; + setFetchedPoolMembersApi: (s: Sync) => void; } // Misc types @@ -127,7 +151,7 @@ export interface PoolRoles { depositor: string; nominator: string; root: string; - stateToggler: string; + bouncer: string; } export interface PoolAddresses { @@ -137,14 +161,15 @@ export interface PoolAddresses { export type MaybePool = number | null; -export enum PoolState { - /// The pool is open to be joined, and is working normally. - Open = 'Open', - /// The pool is blocked. No one else can join. - Block = 'Blocked', - /// The pool is in the process of being destroyed. - /// - /// All members can now be permissionlessly unbonded, and the pool can never go back to any - /// other state other than being dissolved. - Destroy = 'Destroying', +export type PoolState = 'Open' | 'Blocked' | 'Destroying'; + +export interface ClaimPermissionConfig { + label: string; + value: ClaimPermission; + description: string; +} + +export interface PoolMember { + poolId: number; + who: string; } diff --git a/src/contexts/Prompt/defaults.tsx b/src/contexts/Prompt/defaults.tsx new file mode 100644 index 0000000000..4612df759e --- /dev/null +++ b/src/contexts/Prompt/defaults.tsx @@ -0,0 +1,15 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only +/* eslint-disable @typescript-eslint/no-unused-vars */ + +import type { PromptContextInterface } from './types'; + +export const defaultPromptContext: PromptContextInterface = { + openPromptWith: (o, s) => {}, + closePrompt: () => {}, + setStatus: (s) => {}, + setPrompt: (d) => {}, + size: 'small', + status: 0, + Prompt: null, +}; diff --git a/src/contexts/Prompt/index.tsx b/src/contexts/Prompt/index.tsx new file mode 100644 index 0000000000..173947fb1f --- /dev/null +++ b/src/contexts/Prompt/index.tsx @@ -0,0 +1,67 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import React, { useState } from 'react'; +import { defaultPromptContext } from './defaults'; +import type { PromptContextInterface } from './types'; + +export const PromptProvider = ({ children }: { children: React.ReactNode }) => { + const [state, setState] = useState<any>({ + size: 'large', + status: 0, + Prompt: null, + }); + + const setPrompt = (Prompt: any) => { + setState({ + ...state, + Prompt, + }); + }; + + const setStatus = (status: number) => { + setState({ + ...state, + status, + dismissOpen: status !== 0, + }); + }; + + const openPromptWith = (Prompt: any, size = 'small') => { + setState({ + ...state, + size, + Prompt, + status: 1, + }); + }; + + const closePrompt = () => { + setState({ + ...state, + status: 0, + Prompt: null, + }); + }; + + return ( + <PromptContext.Provider + value={{ + openPromptWith, + closePrompt, + setStatus, + setPrompt, + size: state.size, + status: state.status, + Prompt: state.Prompt, + }} + > + {children} + </PromptContext.Provider> + ); +}; + +export const PromptContext = + React.createContext<PromptContextInterface>(defaultPromptContext); + +export const usePrompt = () => React.useContext(PromptContext); diff --git a/src/contexts/Prompt/types.ts b/src/contexts/Prompt/types.ts new file mode 100644 index 0000000000..8d79208511 --- /dev/null +++ b/src/contexts/Prompt/types.ts @@ -0,0 +1,15 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import type React from 'react'; +import type { MaybeString } from 'types'; + +export interface PromptContextInterface { + openPromptWith: (o: React.ReactNode | null, s?: string) => void; + closePrompt: () => void; + setStatus: (s: number) => void; + setPrompt: (d: MaybeString) => void; + size: string; + status: number; + Prompt: React.ReactNode | null; +} diff --git a/src/contexts/Proxies/defaults.ts b/src/contexts/Proxies/defaults.ts new file mode 100644 index 0000000000..f7aaab8608 --- /dev/null +++ b/src/contexts/Proxies/defaults.ts @@ -0,0 +1,14 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only +/* eslint-disable @typescript-eslint/no-unused-vars */ + +import type { ProxiesContextInterface } from './type'; + +export const defaultProxiesContext: ProxiesContextInterface = { + getDelegates: (a) => undefined, + getProxyDelegate: (x, y) => null, + getProxiedAccounts: (a) => [], + handleDeclareDelegate: (a) => new Promise((resolve) => resolve([])), + proxies: [], + delegates: {}, +}; diff --git a/src/contexts/Proxies/index.tsx b/src/contexts/Proxies/index.tsx new file mode 100644 index 0000000000..3289ada39e --- /dev/null +++ b/src/contexts/Proxies/index.tsx @@ -0,0 +1,301 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import type { VoidFn } from '@polkadot/api/types'; +import { + addedTo, + ellipsisFn, + localStorageOrDefault, + matchedProperties, + removedFrom, + rmCommas, + setStateWithRef, +} from '@polkadot-cloud/utils'; +import BigNumber from 'bignumber.js'; +import React, { useRef, useState } from 'react'; +import { isSupportedProxy } from 'config/proxies'; +import { useApi } from 'contexts/Api'; +import type { AnyApi, MaybeAddress } from 'types'; +import { useEffectIgnoreInitial } from '@polkadot-cloud/react/hooks'; +import { useNetwork } from 'contexts/Network'; +import { useActiveAccounts } from 'contexts/ActiveAccounts'; +import { useImportedAccounts } from 'contexts/Connect/ImportedAccounts'; +import { useOtherAccounts } from 'contexts/Connect/OtherAccounts'; +import * as defaults from './defaults'; +import type { + Delegates, + ProxiedAccounts, + Proxies, + ProxiesContextInterface, + Proxy, + ProxyDelegate, +} from './type'; + +export const ProxiesProvider = ({ + children, +}: { + children: React.ReactNode; +}) => { + const { network } = useNetwork(); + const { api, isReady } = useApi(); + const { accounts } = useImportedAccounts(); + const { addExternalAccount } = useOtherAccounts(); + const { activeProxy, setActiveProxy, activeAccount } = useActiveAccounts(); + + // store the proxy accounts of each imported account. + const [proxies, setProxies] = useState<Proxies>([]); + const proxiesRef = useRef(proxies); + const unsubs = useRef<Record<string, VoidFn>>({}); + + // Handle the syncing of accounts on accounts change. + const handleSyncAccounts = () => { + // Sync removed accounts. + const handleRemovedAccounts = () => { + const removed = removedFrom(accounts, proxiesRef.current, [ + 'address', + ]).map(({ address }) => address); + + removed?.forEach((address) => { + // if delegates still exist for removed account, re-add the account as a read only system + // account. + if (delegatesRef.current[address]) { + addExternalAccount(address, 'system'); + } else { + const unsub = unsubs.current[address]; + if (unsub) unsub(); + } + }); + + unsubs.current = Object.fromEntries( + Object.entries(unsubs.current).filter(([key]) => !removed.includes(key)) + ); + }; + // Sync added accounts. + const handleAddedAccounts = () => { + addedTo(accounts, proxiesRef.current, ['address'])?.map(({ address }) => + subscribeToProxies(address) + ); + }; + // Sync existing accounts. + const handleExistingAccounts = () => { + setStateWithRef( + matchedProperties(accounts, proxiesRef.current, ['address']), + setProxies, + proxiesRef + ); + }; + handleRemovedAccounts(); + handleAddedAccounts(); + handleExistingAccounts(); + }; + + // store the delegates and the corresponding delegators + const [delegates, setDelegates] = useState<Delegates>({}); + const delegatesRef = useRef(delegates); + + const subscribeToProxies = async (address: string) => { + if (!api) return undefined; + + const unsub = await api.queryMulti<AnyApi>( + [[api.query.proxy.proxies, address]], + async ([result]) => { + const data = result.toHuman(); + const newProxies = data[0]; + const reserved = new BigNumber(rmCommas(data[1])); + + if (newProxies.length) { + setStateWithRef( + [...proxiesRef.current] + .filter(({ delegator }) => delegator !== address) + .concat({ + address, + delegator: address, + delegates: newProxies.map((d: AnyApi) => ({ + delegate: d.delegate.toString(), + proxyType: d.proxyType.toString(), + })), + reserved, + }), + setProxies, + proxiesRef + ); + } else { + // no proxies: remove stale proxies if already in list. + setStateWithRef( + [...proxiesRef.current].filter( + ({ delegator }) => delegator !== address + ), + setProxies, + proxiesRef + ); + } + } + ); + + unsubs.current[address] = unsub; + return unsub; + }; + + // Gets the delegates of the given account + const getDelegates = (address: MaybeAddress): Proxy | undefined => + proxiesRef.current.find(({ delegator }) => delegator === address) || + undefined; + + // Gets delegators and proxy types for the given delegate address + const getProxiedAccounts = (address: MaybeAddress) => { + const delegate = delegatesRef.current[address || '']; + if (!delegate) { + return []; + } + const proxiedAccounts: ProxiedAccounts = delegate + .filter(({ proxyType }) => isSupportedProxy(proxyType)) + .map(({ delegator, proxyType }) => ({ + address: delegator, + name: ellipsisFn(delegator), + proxyType, + })); + return proxiedAccounts; + }; + + // Gets the delegates and proxy type of an account, if any. + const getProxyDelegate = ( + delegator: MaybeAddress, + delegate: MaybeAddress + ): ProxyDelegate | null => + proxiesRef.current + .find((p) => p.delegator === delegator) + ?.delegates.find((d) => d.delegate === delegate) ?? null; + + // Subscribe new accounts to proxies, and remove accounts that are no longer imported. + useEffectIgnoreInitial(() => { + if (isReady) { + handleSyncAccounts(); + } + }, [accounts, isReady, network]); + + // If active proxy has not yet been set, check local storage `activeProxy` & set it as active + // proxy if it is the delegate of `activeAccount`. + useEffectIgnoreInitial(() => { + const localActiveProxy = localStorageOrDefault( + `${network}_active_proxy`, + null + ); + + if (!localActiveProxy) { + setActiveProxy(null); + } else if ( + proxiesRef.current.length && + localActiveProxy && + !activeProxy && + activeAccount + ) { + try { + const { address, proxyType } = JSON.parse(localActiveProxy); + // Add proxy address as external account if not imported. + if (!accounts.find((a) => a.address === address)) { + addExternalAccount(address, 'system'); + } + + const isActive = ( + proxiesRef.current.find( + ({ delegator }) => delegator === activeAccount + )?.delegates || [] + ).find((d) => d.delegate === address && d.proxyType === proxyType); + if (isActive) { + setActiveProxy({ address, proxyType }); + } + } catch (e) { + // Corrupt local active proxy record. Remove it. + localStorage.removeItem(`${network}_active_proxy`); + } + } + }, [accounts, activeAccount, proxiesRef.current, network]); + + // Reset active proxy state, unsubscribe from subscriptions on network change & unmount. + useEffectIgnoreInitial(() => { + setActiveProxy(null, false); + unsubAll(); + return () => unsubAll(); + }, [network]); + + const unsubAll = () => { + for (const unsub of Object.values(unsubs.current)) { + unsub(); + } + unsubs.current = {}; + }; + + // Listens to `proxies` state updates and reformats the data into a list of delegates. + useEffectIgnoreInitial(() => { + // Reformat proxiesRef.current into a list of delegates. + const newDelegates: Delegates = {}; + for (const proxy of proxiesRef.current) { + const { delegator } = proxy; + + // checking if delegator is not null to keep types happy. + if (delegator) { + // get each delegate of this proxy record. + for (const { delegate, proxyType } of proxy.delegates) { + const item = { + delegator, + proxyType, + }; + // check if this delegate exists in `newDelegates`. + if (Object.keys(newDelegates).includes(delegate)) { + // append delegator to the existing delegate record if it exists. + newDelegates[delegate].push(item); + } else { + // create a new delegate record if it does not yet exist in `newDelegates`. + newDelegates[delegate] = [item]; + } + } + } + } + + setStateWithRef(newDelegates, setDelegates, delegatesRef); + }, [proxiesRef.current]); + + // Queries the chain to check if the given delegator & delegate pair is valid proxy. + const handleDeclareDelegate = async (delegator: string) => { + if (!api) return []; + + const result: AnyApi = (await api.query.proxy.proxies(delegator)).toHuman(); + + let addDelegatorAsExternal = false; + for (const { delegate: newDelegate } of result[0] || []) { + if ( + accounts.find(({ address }) => address === newDelegate) && + !delegatesRef.current[newDelegate] + ) { + subscribeToProxies(delegator); + addDelegatorAsExternal = true; + } + } + if (addDelegatorAsExternal) { + addExternalAccount(delegator, 'system'); + } + + return []; + }; + + return ( + <ProxiesContext.Provider + value={{ + proxies: proxiesRef.current, + delegates: delegatesRef.current, + handleDeclareDelegate, + getDelegates, + getProxyDelegate, + getProxiedAccounts, + }} + > + {children} + </ProxiesContext.Provider> + ); +}; + +export const ProxiesContext = React.createContext<ProxiesContextInterface>( + defaults.defaultProxiesContext +); + +export const useProxies = () => React.useContext(ProxiesContext); diff --git a/src/contexts/Proxies/type.ts b/src/contexts/Proxies/type.ts new file mode 100644 index 0000000000..9bff84464d --- /dev/null +++ b/src/contexts/Proxies/type.ts @@ -0,0 +1,51 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import type BigNumber from 'bignumber.js'; +import type { AnyJson, MaybeAddress } from 'types'; + +export type ProxyType = + | 'Any' + | 'NonTransfer' + | 'Governance' + | 'Staking' + | 'IdentityJudgement' + | 'CancelProxy' + | 'Auction'; + +export type Proxies = Proxy[]; + +export interface Proxy { + address: MaybeAddress; + delegator: MaybeAddress; + delegates: ProxyDelegate[]; + reserved: BigNumber; +} + +export interface ProxyDelegate { + delegate: string; + proxyType: ProxyType; +} +export type Delegates = Record<string, DelegateItem[]>; + +export interface DelegateItem { + delegator: string; + proxyType: ProxyType; +} + +export type ProxiedAccounts = ProxiedAccount[]; + +export interface ProxiedAccount { + address: string; + name: string; + proxyType: ProxyType; +} + +export interface ProxiesContextInterface { + getDelegates: (a: MaybeAddress) => Proxy | undefined; + getProxyDelegate: (x: MaybeAddress, y: MaybeAddress) => ProxyDelegate | null; + getProxiedAccounts: (a: MaybeAddress) => ProxiedAccounts; + handleDeclareDelegate: (delegator: string) => Promise<AnyJson[]>; + proxies: Proxies; + delegates: Delegates; +} diff --git a/src/contexts/SessionEra/defaults.ts b/src/contexts/SessionEra/defaults.ts deleted file mode 100644 index 930e114dd2..0000000000 --- a/src/contexts/SessionEra/defaults.ts +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { SessionEra, SessionEraContextInterface } from './types'; - -export const sessionEra: SessionEra = { - eraLength: 0, - eraProgress: 0, - sessionLength: 0, - sessionProgress: 0, - sessionsPerEra: 0, -}; - -export const defaultSessionEraContext: SessionEraContextInterface = { - getEraTimeLeft: () => 0, - sessionEra, -}; diff --git a/src/contexts/SessionEra/index.tsx b/src/contexts/SessionEra/index.tsx deleted file mode 100644 index 7ca14ce5f0..0000000000 --- a/src/contexts/SessionEra/index.tsx +++ /dev/null @@ -1,93 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { getUnixTime } from 'date-fns'; -import React, { useEffect, useRef, useState } from 'react'; -import { AnyApi } from 'types'; -import { setStateWithRef } from 'Utils'; -import { useApi } from '../Api'; -import * as defaults from './defaults'; -import { SessionEra, SessionEraContextInterface } from './types'; - -export const SessionEraContext = - React.createContext<SessionEraContextInterface>( - defaults.defaultSessionEraContext - ); - -// Warning: Do not use this hook in heavy components. -// Using this hook in a component makes the component rerender per each new block. -export const useSessionEra = () => React.useContext(SessionEraContext); - -export const SessionEraProvider = ({ - children, -}: { - children: React.ReactNode; -}) => { - const { isReady, api, status, consts } = useApi(); - const { expectedBlockTime } = consts; - - useEffect(() => { - if (status === 'connecting') { - setStateWithRef(defaults.sessionEra, setSessionEra, sessionEraRef); - } - }, [status]); - - // store network metrics in state - const [sessionEra, setSessionEra] = useState<SessionEra>(defaults.sessionEra); - const sessionEraRef = useRef(sessionEra); - - const [unsub, setUnsub] = useState<AnyApi>(null); - const unsubRef = useRef(unsub); - - // manage unsubscribe - useEffect(() => { - subscribeToSessionProgress(); - return () => { - if (unsubRef.current !== null) { - unsubRef.current(); - } - }; - }, [isReady]); - - // active subscription - const subscribeToSessionProgress = async () => { - if (isReady && api !== null) { - const _unsub = await api.derive.session.progress((session) => { - setStateWithRef( - { - eraLength: session.eraLength.toNumber(), - eraProgress: session.eraProgress.toNumber(), - sessionLength: session.sessionLength.toNumber(), - sessionProgress: session.sessionProgress.toNumber(), - sessionsPerEra: session.sessionsPerEra.toNumber(), - }, - setSessionEra, - sessionEraRef - ); - }); - setStateWithRef(_unsub, setUnsub, unsubRef); - } - }; - - const getEraTimeLeft = () => { - const eraBlocksLeft = - sessionEraRef.current.eraLength - sessionEraRef.current.eraProgress; - const eraTimeLeftSeconds = eraBlocksLeft * (expectedBlockTime * 0.001); - - const unixTime = getUnixTime(new Date()); - const eventTime = unixTime + eraTimeLeftSeconds; - const diffTime = eventTime - unixTime; - return diffTime; - }; - - return ( - <SessionEraContext.Provider - value={{ - getEraTimeLeft, - sessionEra, - }} - > - {children} - </SessionEraContext.Provider> - ); -}; diff --git a/src/contexts/SessionEra/types.ts b/src/contexts/SessionEra/types.ts deleted file mode 100644 index fed0d9eda1..0000000000 --- a/src/contexts/SessionEra/types.ts +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -export interface SessionEraContextInterface { - getEraTimeLeft: () => number; - sessionEra: SessionEra; -} - -export interface SessionEra { - eraLength: number; - eraProgress: number; - sessionLength: number; - sessionProgress: number; - sessionsPerEra: number; -} diff --git a/src/contexts/Setup/defaults.ts b/src/contexts/Setup/defaults.ts new file mode 100644 index 0000000000..f17bd8c25d --- /dev/null +++ b/src/contexts/Setup/defaults.ts @@ -0,0 +1,41 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only +/* eslint-disable @typescript-eslint/no-unused-vars */ + +import type { + NominatorProgress, + PoolProgress, + SetupContextInterface, +} from './types'; + +export const defaultNominatorProgress: NominatorProgress = { + payee: { + destination: null, + account: null, + }, + nominations: [], + bond: '', +}; + +export const defaultPoolProgress: PoolProgress = { + metadata: '', + bond: '', + nominations: [], + roles: null, +}; + +export const defaultSetupContext: SetupContextInterface = { + getSetupProgress: (a, b) => ({ + section: 1, + progress: defaultNominatorProgress, + }), + removeSetupProgress: (a, b) => {}, + getNominatorSetupPercent: (a) => 0, + getPoolSetupPercent: (a) => 0, + setActiveAccountSetup: (t, p) => {}, + setActiveAccountSetupSection: (t, s) => {}, + setOnNominatorSetup: (v) => {}, + setOnPoolSetup: (v) => {}, + onNominatorSetup: false, + onPoolSetup: false, +}; diff --git a/src/contexts/Setup/index.tsx b/src/contexts/Setup/index.tsx new file mode 100644 index 0000000000..1d89267de4 --- /dev/null +++ b/src/contexts/Setup/index.tsx @@ -0,0 +1,252 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { + greaterThanZero, + localStorageOrDefault, + unitToPlanck, +} from '@polkadot-cloud/utils'; +import React, { useState } from 'react'; +import { usePoolMemberships } from 'contexts/Pools/PoolMemberships'; +import type { BondFor, MaybeAddress } from 'types'; +import { useEffectIgnoreInitial } from '@polkadot-cloud/react/hooks'; +import { useNetwork } from 'contexts/Network'; +import { useActiveAccounts } from 'contexts/ActiveAccounts'; +import { useImportedAccounts } from 'contexts/Connect/ImportedAccounts'; +import { useStaking } from '../Staking'; +import { + defaultNominatorProgress, + defaultPoolProgress, + defaultSetupContext, +} from './defaults'; +import type { + NominatorProgress, + NominatorSetup, + NominatorSetups, + PoolProgress, + PoolSetup, + PoolSetups, + SetupContextInterface, +} from './types'; + +export const SetupProvider = ({ children }: { children: React.ReactNode }) => { + const { inSetup } = useStaking(); + const { + network, + networkData: { units }, + } = useNetwork(); + const { accounts } = useImportedAccounts(); + const { activeAccount } = useActiveAccounts(); + const { membership: poolMembership } = usePoolMemberships(); + + // is the user actively on the setup page + const [onNominatorSetup, setOnNominatorSetup] = useState<boolean>(false); + + // is the user actively on the pool creation page + const [onPoolSetup, setOnPoolSetup] = useState<boolean>(false); + + // Store all imported accounts nominator setups. + const [nominatorSetups, setNominatorSetups] = useState<NominatorSetups>({}); + + // Store all imported accounts pool creation setups. + const [poolSetups, setPoolSetups] = useState<PoolSetups>({}); + + // Generates the default setup objects or the currently connected accounts. Refers to local + // storage to hydrate state, falls back to defaults otherwise. + const refreshSetups = () => { + setNominatorSetups(localNominatorSetups()); + setPoolSetups(localPoolSetups()); + }; + + // Gets the setup progress for a connected account. Falls back to default setup if progress does + // not yet exist. + const getSetupProgress = ( + type: BondFor, + address: MaybeAddress + ): NominatorSetup | PoolSetup => { + const setup = Object.fromEntries( + Object.entries( + type === 'nominator' ? nominatorSetups : poolSetups + ).filter(([k]) => k === address) + ); + return ( + setup[address || ''] || { + progress: defaultProgress(type), + section: 1, + } + ); + }; + + // Remove setup progress for an account. + const removeSetupProgress = (type: BondFor, address: MaybeAddress) => { + const updatedSetups = Object.fromEntries( + Object.entries( + type === 'nominator' ? nominatorSetups : poolSetups + ).filter(([k]) => k !== address) + ); + setSetups(type, updatedSetups); + }; + + // Sets setup progress for an address. Updates localStorage followed by app state. + const setActiveAccountSetup = ( + type: BondFor, + progress: NominatorProgress | PoolProgress + ) => { + if (!activeAccount) return; + + const updatedSetups = updateSetups( + assignSetups(type), + progress, + activeAccount + ); + setSetups(type, updatedSetups); + }; + + // Sets active setup section for an address. + const setActiveAccountSetupSection = (type: BondFor, section: number) => { + if (!activeAccount) return; + + const setups = assignSetups(type); + const updatedSetups = updateSetups( + setups, + setups[activeAccount]?.progress ?? defaultProgress(type), + activeAccount, + section + ); + setSetups(type, updatedSetups); + }; + + // Utility to update the progress item of either a nominator setup or pool setup, + const updateSetups = < + T extends NominatorSetups | PoolSetups, + U extends NominatorProgress | PoolProgress, + >( + all: T, + newSetup: U, + account: string, + maybeSection?: number + ) => { + const current = Object.assign(all[account] || {}); + const section = maybeSection ?? current.section ?? 1; + + all[account] = { + ...current, + progress: newSetup, + section, + }; + + return all; + }; + + // Gets the stake setup progress as a percentage for an address. + const getNominatorSetupPercent = (address: MaybeAddress) => { + if (!address) return 0; + const setup = getSetupProgress('nominator', address) as NominatorSetup; + const { progress } = setup; + const bond = unitToPlanck(progress?.bond || '0', units); + + const p = 33; + let percentage = 0; + if (greaterThanZero(bond)) percentage += p; + if (progress.nominations.length) percentage += p; + if (progress.payee.destination !== null) percentage += p; + return percentage; + }; + + // Gets the stake setup progress as a percentage for an address. + const getPoolSetupPercent = (address: MaybeAddress) => { + if (!address) return 0; + const setup = getSetupProgress('pool', address) as PoolSetup; + const { progress } = setup; + const bond = unitToPlanck(progress?.bond || '0', units); + + const p = 25; + let percentage = 0; + if (progress.metadata !== '') percentage += p; + if (greaterThanZero(bond)) percentage += p; + if (progress.nominations.length) percentage += p; + if (progress.roles !== null) percentage += p - 1; + return percentage; + }; + + // Utility to copy the current setup state based on setup type. + const assignSetups = (type: BondFor) => + type === 'nominator' ? { ...nominatorSetups } : { ...poolSetups }; + + // Utility to get the default progress based on type. + const defaultProgress = (type: BondFor) => + type === 'nominator' ? defaultNominatorProgress : defaultPoolProgress; + + // Utility to get nominator setups, type casted as NominatorSetups. + const localNominatorSetups = () => + localStorageOrDefault('nominator_setups', {}, true) as NominatorSetups; + + // Utility to get pool setups, type casted as PoolSetups. + const localPoolSetups = () => + localStorageOrDefault('pool_setups', {}, true) as PoolSetups; + + // Utility to update setups state depending on type. + const setSetups = (type: BondFor, setups: NominatorSetups | PoolSetups) => { + setLocalSetups(type, setups); + + if (type === 'nominator') { + setNominatorSetups(setups as NominatorSetups); + } else { + setPoolSetups(setups as PoolSetups); + } + }; + + // Utility to either update local setups or remove if empty. + const setLocalSetups = ( + type: BondFor, + setups: NominatorSetups | PoolSetups + ) => { + const key = type === 'nominator' ? 'nominator_setups' : 'pool_setups'; + const setupsStr = JSON.stringify(setups); + + if (setupsStr === '{}') { + localStorage.removeItem(key); + } else { + localStorage.setItem(key, setupsStr); + } + }; + + // Move away from setup pages on completion / network change. + useEffectIgnoreInitial(() => { + if (!inSetup()) { + setOnNominatorSetup(false); + } + if (poolMembership) { + setOnPoolSetup(false); + } + }, [inSetup(), network, poolMembership]); + + // Update setup state when activeAccount changes + useEffectIgnoreInitial(() => { + if (accounts.length) refreshSetups(); + }, [activeAccount, network, accounts]); + + return ( + <SetupContext.Provider + value={{ + getSetupProgress, + removeSetupProgress, + getNominatorSetupPercent, + getPoolSetupPercent, + setActiveAccountSetup, + setActiveAccountSetupSection, + setOnNominatorSetup, + setOnPoolSetup, + onNominatorSetup, + onPoolSetup, + }} + > + {children} + </SetupContext.Provider> + ); +}; + +export const SetupContext = + React.createContext<SetupContextInterface>(defaultSetupContext); + +export const useSetup = () => React.useContext(SetupContext); diff --git a/src/contexts/Setup/types.ts b/src/contexts/Setup/types.ts new file mode 100644 index 0000000000..08e9178f92 --- /dev/null +++ b/src/contexts/Setup/types.ts @@ -0,0 +1,61 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import type { PoolRoles } from 'contexts/Pools/types'; +import type { ValidatorPrefs } from 'contexts/Validators/types'; +import type { AnyJson, BondFor, MaybeAddress, MaybeString } from 'types'; + +export type PayeeOptions = + | 'Staked' + | 'Stash' + | 'Controller' + | 'Account' + | 'None'; + +export type NominatorSetups = Record<string, NominatorSetup>; + +export interface NominatorSetup { + progress: NominatorProgress; + section: number; +} + +export interface NominatorProgress { + payee: PayeeConfig; + nominations: AnyJson[]; + bond: MaybeString; +} + +export interface PayeeConfig { + destination: PayeeOptions | null; + account: MaybeAddress; +} + +export type PoolSetups = Record<string, PoolSetup>; + +export interface PoolSetup { + progress: PoolProgress; + section: number; +} + +export interface PoolProgress { + metadata: string; + bond: string; + nominations: { address: string; prefs: ValidatorPrefs }[]; + roles: PoolRoles | null; +} + +export interface SetupContextInterface { + getSetupProgress: (t: BondFor, a: MaybeAddress) => any; + removeSetupProgress: (t: BondFor, a: MaybeAddress) => void; + getNominatorSetupPercent: (a: MaybeAddress) => number; + getPoolSetupPercent: (a: MaybeAddress) => number; + setActiveAccountSetup: ( + t: BondFor, + p: NominatorProgress | PoolProgress + ) => void; + setActiveAccountSetupSection: (t: BondFor, s: number) => void; + setOnNominatorSetup: (v: boolean) => void; + setOnPoolSetup: (v: boolean) => void; + onNominatorSetup: boolean; + onPoolSetup: boolean; +} diff --git a/src/contexts/Staking/Utils.ts b/src/contexts/Staking/Utils.ts new file mode 100644 index 0000000000..b41151958c --- /dev/null +++ b/src/contexts/Staking/Utils.ts @@ -0,0 +1,84 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import type { AnyApi, NetworkName } from 'types'; +import { rmCommas } from '@polkadot-cloud/utils'; +import type { Exposure, LocalExposure, LocalExposuresData } from './types'; + +// Get local `erasStakers` entries for an era. +export const getLocalEraExposures = ( + network: NetworkName, + era: string, + activeEra: string +) => { + const data = localStorage.getItem(`${network}_exposures`); + const current = data ? (JSON.parse(data) as LocalExposuresData) : null; + const currentEra = current?.era; + + if (currentEra && currentEra !== activeEra) + localStorage.removeItem(`${network}_exposures`); + + if (currentEra === era && current?.exposures) + return maxifyExposures(current.exposures) as Exposure[]; + + return null; +}; + +// Set local stakers entries data for an era. +export const setLocalEraExposures = ( + network: NetworkName, + era: string, + exposures: Exposure[] +) => { + localStorage.setItem( + `${network}_exposures`, + JSON.stringify({ + era, + exposures: minifyExposures(exposures), + }) + ); +}; + +// Humanise and remove commas from fetched exposures. +export const formatRawExposures = (exposures: AnyApi) => + exposures.map(([k, v]: AnyApi) => { + const keys = k.toHuman(); + const { own, total, others } = v.toHuman(); + + return { + keys: [rmCommas(keys[0]), keys[1]], + val: { + others: others.map(({ who, value }: AnyApi) => ({ + who, + value: rmCommas(value), + })), + own: rmCommas(own), + total: rmCommas(total), + }, + }; + }); + +// Minify exposures data structure for local storage. +const minifyExposures = (exposures: Exposure[]) => + exposures.map(({ keys, val: { others, own, total } }: AnyApi) => ({ + k: [keys[0], keys[1]], + v: { + o: others.map(({ who, value }: AnyApi) => [who, value]), + w: own, + t: total, + }, + })); + +// Expand local exposure data into JSON format. +const maxifyExposures = (exposures: LocalExposure[]) => + exposures.map(({ k, v }: AnyApi) => ({ + keys: [k[0], k[1]], + val: { + others: v.o.map(([who, value]: AnyApi) => ({ + who, + value, + })), + own: v.w, + total: v.t, + }, + })); diff --git a/src/contexts/Staking/defaults.ts b/src/contexts/Staking/defaults.ts index 6056a1227b..20f5bdfb2d 100644 --- a/src/contexts/Staking/defaults.ts +++ b/src/contexts/Staking/defaults.ts @@ -1,8 +1,9 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only +/* eslint-disable @typescript-eslint/no-unused-vars */ -import BN from 'bn.js'; -import { +import BigNumber from 'bignumber.js'; +import type { EraStakers, NominationStatuses, StakingContextInterface, @@ -10,49 +11,52 @@ import { StakingTargets, } from 'contexts/Staking/types'; -export const stakingMetrics: StakingMetrics = { - totalNominators: new BN(0), - totalValidators: new BN(0), - lastReward: new BN(0), - lastTotalStake: new BN(0), - validatorCount: new BN(0), - maxNominatorsCount: new BN(0), - maxValidatorsCount: new BN(0), - minNominatorBond: new BN(0), - payee: null, - unsub: null, +export const defaultStakingMetrics: StakingMetrics = { + totalNominators: new BigNumber(0), + totalValidators: new BigNumber(0), + lastReward: new BigNumber(0), + lastTotalStake: new BigNumber(0), + validatorCount: new BigNumber(0), + maxValidatorsCount: new BigNumber(0), + minNominatorBond: new BigNumber(0), + payee: { + destination: null, + account: null, + }, + totalStaked: new BigNumber(0), }; -export const eraStakers: EraStakers = { +export const defaultEraStakers: EraStakers = { + activeAccountOwnStake: [], + activeValidators: 0, stakers: [], - nominators: undefined, totalActiveNominators: 0, - activeValidators: 0, - minActiveBond: 0, - minStakingActiveBond: 0, - ownStake: 0, }; -export const targets: StakingTargets = { +export const defaultTargets: StakingTargets = { nominations: [], }; -export const nominationStatus: NominationStatuses = {}; +const defaultLowestReward = { + lowest: new BigNumber(0), + oversubscribed: false, +}; + +export const defaultNominationStatus: NominationStatuses = {}; export const defaultStakingContext: StakingContextInterface = { - getNominationsStatus: () => nominationStatus, - // eslint-disable-next-line - getNominationsStatusFromTargets: (w, t) => nominationStatus, - // eslint-disable-next-line + fetchEraStakers: async (e) => new Promise((resolve) => resolve([])), + getNominationsStatusFromTargets: (w, t) => defaultNominationStatus, setTargets: (t) => {}, hasController: () => false, - // eslint-disable-next-line getControllerNotImported: (a) => null, + addressDifferentToStash: (a) => false, isBonding: () => false, isNominating: () => false, inSetup: () => true, - staking: stakingMetrics, - eraStakers, - targets, + getLowestRewardFromStaker: (address) => defaultLowestReward, + staking: defaultStakingMetrics, + eraStakers: defaultEraStakers, + targets: defaultTargets, erasStakersSyncing: true, }; diff --git a/src/contexts/Staking/index.tsx b/src/contexts/Staking/index.tsx index d2c7754861..e9c07835dc 100644 --- a/src/contexts/Staking/index.tsx +++ b/src/contexts/Staking/index.tsx @@ -1,36 +1,46 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import BN from 'bn.js'; -import { ExternalAccount, ImportedAccount } from 'contexts/Connect/types'; +import type { VoidFn } from '@polkadot/api/types'; import { + greaterThanZero, + isNotZero, + localStorageOrDefault, + setStateWithRef, +} from '@polkadot-cloud/utils'; +import BigNumber from 'bignumber.js'; +import React, { useRef, useState } from 'react'; +import { useBalances } from 'contexts/Balances'; +import type { ExternalAccount } from '@polkadot-cloud/react/types'; +import type { PayeeConfig, PayeeOptions } from 'contexts/Setup/types'; +import type { EraStakers, - NominationStatuses, + Exposure, StakingContextInterface, StakingMetrics, StakingTargets, } from 'contexts/Staking/types'; -import React, { useEffect, useRef, useState } from 'react'; -import { AnyApi, MaybeAccount } from 'types'; -import { - localStorageOrDefault, - planckBnToUnit, - rmCommas, - setStateWithRef, -} from 'Utils'; -// eslint-disable-next-line import/no-unresolved -import Worker from 'worker-loader!../../workers/stakers'; +import type { AnyApi, AnyJson, MaybeAddress } from 'types'; +import Worker from 'workers/stakers?worker'; +import type { ResponseInitialiseExposures } from 'workers/types'; +import { useEffectIgnoreInitial } from '@polkadot-cloud/react/hooks'; +import { useNetwork } from 'contexts/Network'; +import { useActiveAccounts } from 'contexts/ActiveAccounts'; +import { useImportedAccounts } from 'contexts/Connect/ImportedAccounts'; import { useApi } from '../Api'; -import { useBalances } from '../Balances'; -import { useConnect } from '../Connect'; -import { useNetworkMetrics } from '../Network'; -import * as defaults from './defaults'; - -export const StakingContext = React.createContext<StakingContextInterface>( - defaults.defaultStakingContext -); - -export const useStaking = () => React.useContext(StakingContext); +import { useBonded } from '../Bonded'; +import { useNetworkMetrics } from '../NetworkMetrics'; +import { + defaultEraStakers, + defaultStakingContext, + defaultStakingMetrics, + defaultTargets, +} from './defaults'; +import { + setLocalEraExposures, + getLocalEraExposures, + formatRawExposures, +} from './Utils'; const worker = new Worker(); @@ -39,156 +49,82 @@ export const StakingProvider = ({ }: { children: React.ReactNode; }) => { - const { - activeAccount, - accounts: connectAccounts, - getActiveAccount, - } = useConnect(); - const { isReady, api, consts, status, network } = useApi(); - const { metrics } = useNetworkMetrics(); - const { - accounts, - getBondedAccount, - getLedgerForStash, - getAccountNominations, - } = useBalances(); - const { units } = network; + const { accounts: connectAccounts } = useImportedAccounts(); + const { activeAccount, getActiveAccount } = useActiveAccounts(); + const { getStashLedger } = useBalances(); + const { activeEra } = useNetworkMetrics(); + const { networkData, network } = useNetwork(); + const { isReady, api, apiStatus, consts } = useApi(); + const { bondedAccounts, getBondedAccount, getAccountNominations } = + useBonded(); const { maxNominatorRewardedPerValidator } = consts; - // store staking metrics in state + // Store staking metrics in state. const [stakingMetrics, setStakingMetrics] = useState<StakingMetrics>( - defaults.stakingMetrics + defaultStakingMetrics ); - // store stakers metadata in state - const [eraStakers, setEraStakers] = useState<EraStakers>(defaults.eraStakers); + // Store unsub object fro staking metrics. + const unsub = useRef<VoidFn | null>(null); + + // Store eras stakers in state. + const [eraStakers, setEraStakers] = useState<EraStakers>(defaultEraStakers); const eraStakersRef = useRef(eraStakers); - // flags whether erasStakers is resyncing + // Flags whether `eraStakers` is resyncing. const [erasStakersSyncing, setErasStakersSyncing] = useState(false); const erasStakersSyncingRef = useRef(erasStakersSyncing); - // store account target validators - const [targets, _setTargets] = useState<StakingTargets>( + // Store target validators for the active account. + const [targets, setTargetsState] = useState<StakingTargets>( localStorageOrDefault<StakingTargets>( `${activeAccount ?? ''}_targets`, - defaults.targets, + defaultTargets, true ) as StakingTargets ); - useEffect(() => { - if (status === 'connecting') { - setStateWithRef(defaults.eraStakers, setEraStakers, eraStakersRef); - setStakingMetrics(defaults.stakingMetrics); - } - }, [status]); - - // handle staking metrics subscription - useEffect(() => { - if (isReady) { - subscribeToStakingkMetrics(); - } - return () => { - // unsubscribe from staking metrics - if (stakingMetrics.unsub !== null) { - stakingMetrics.unsub(); - } - }; - }, [isReady, metrics.activeEra]); - - // handle syncing with eraStakers - useEffect(() => { - if (isReady) { - fetchEraStakers(); - } - }, [isReady, metrics.activeEra.index, activeAccount]); - - useEffect(() => { - if (activeAccount) { - // calculates minimum bond of the user's chosen nominated validators. - let _stakingMinActiveBond = new BN(0); - - const stakers = eraStakersRef.current?.stakers ?? null; - const nominations = getAccountNominations(activeAccount); - - if (nominations.length && stakers !== null) { - for (const n of nominations) { - const staker = stakers.find((item) => item.address === n); - - if (staker !== undefined) { - let { others } = staker; - - // order others by bonded value, largest first. - others = others.sort((a: any, b: any) => { - const x = new BN(rmCommas(a.value)); - const y = new BN(rmCommas(b.value)); - return y.sub(x); - }); - - if (others.length) { - const _minActive = new BN(rmCommas(others[0].value.toString())); - // set new minimum active bond if less than current value - if ( - _minActive.lt(_stakingMinActiveBond) || - _stakingMinActiveBond !== new BN(0) - ) { - _stakingMinActiveBond = _minActive; - } - } - } - } - } - - // convert _stakingMinActiveBond to base value - const stakingMinActiveBond = planckBnToUnit(_stakingMinActiveBond, units); - - setStateWithRef( - { - ...eraStakersRef.current, - minStakingActiveBond: stakingMinActiveBond, - }, - setEraStakers, - eraStakersRef - ); - - // set account's targets - _setTargets( - localStorageOrDefault( - `${activeAccount}_targets`, - defaults.targets, - true - ) as StakingTargets - ); + // Handle metrics unsubscribe. + const unsubscribeMetrics = () => { + if (unsub.current !== null) { + unsub.current(); + unsub.current = null; } - }, [isReady, accounts, activeAccount, eraStakersRef.current?.stakers]); + }; worker.onmessage = (message: MessageEvent) => { if (message) { - const { data } = message; + const { data }: { data: ResponseInitialiseExposures } = message; + const { task, networkName, era } = data; + + // ensure task matches, & era is still the same. + if ( + task !== 'processExposures' || + networkName !== network || + era !== activeEra.index.toString() + ) + return; + const { stakers, totalActiveNominators, activeValidators, - minActiveBond, - ownStake, - _activeAccount, + activeAccountOwnStake, + who, } = data; // finish sync setStateWithRef(false, setErasStakersSyncing, erasStakersSyncingRef); // check if account hasn't changed since worker started - if (getActiveAccount() === _activeAccount) { + if (getActiveAccount() === who) { setStateWithRef( { ...eraStakersRef.current, stakers, - // nominators, totalActiveNominators, activeValidators, - minActiveBond, - ownStake, + activeAccountOwnStake, }, setEraStakers, eraStakersRef @@ -197,168 +133,142 @@ export const StakingProvider = ({ } }; + // Multi subscription to staking metrics. const subscribeToStakingkMetrics = async () => { - if (api !== null && isReady && metrics.activeEra.index !== 0) { - const previousEra = metrics.activeEra.index - 1; + if (api !== null && isReady && isNotZero(activeEra.index)) { + const previousEra = activeEra.index.minus(1); - // subscribe to staking metrics - const unsub = await api.queryMulti<AnyApi>( + const u = await api.queryMulti<AnyApi>( [ api.query.staking.counterForNominators, api.query.staking.counterForValidators, - api.query.staking.maxNominatorsCount, api.query.staking.maxValidatorsCount, api.query.staking.validatorCount, - [api.query.staking.erasValidatorReward, previousEra], - [api.query.staking.erasTotalStake, previousEra], + [api.query.staking.erasValidatorReward, previousEra.toString()], + [api.query.staking.erasTotalStake, previousEra.toString()], api.query.staking.minNominatorBond, [api.query.staking.payee, activeAccount], + [api.query.staking.erasTotalStake, activeEra.index.toString()], ], - ([ - _totalNominators, - _totalValidators, - _maxNominatorsCount, - _maxValidatorsCount, - _validatorCount, - _lastReward, - _lastTotalStake, - _minNominatorBond, - _payee, - ]) => { + (q) => { setStakingMetrics({ - ...stakingMetrics, - payee: _payee.toHuman(), - lastTotalStake: _lastTotalStake.toBn(), - validatorCount: _validatorCount.toBn(), - totalNominators: _totalNominators.toBn(), - totalValidators: _totalValidators.toBn(), - minNominatorBond: _minNominatorBond.toBn(), - lastReward: _lastReward.unwrapOrDefault(new BN(0)), - maxValidatorsCount: new BN(_maxValidatorsCount.toString()), - maxNominatorsCount: new BN(_maxNominatorsCount.toString()), + totalNominators: new BigNumber(q[0].toString()), + totalValidators: new BigNumber(q[1].toString()), + maxValidatorsCount: new BigNumber(q[2].toString()), + validatorCount: new BigNumber(q[3].toString()), + lastReward: new BigNumber(q[4].toString()), + lastTotalStake: new BigNumber(q[5].toString()), + minNominatorBond: new BigNumber(q[6].toString()), + payee: processPayee(q[7]), + totalStaked: new BigNumber(q[8].toString()), }); } ); - setStakingMetrics({ - ...stakingMetrics, - unsub, - }); + unsub.current = u; } }; - /* - * Fetches the active nominator set. - * The top 256 nominators get rewarded. Nominators may have their bond spread - * among multiple nominees. - * the minimum nominator bond is calculated by summing a particular bond of a nominator. - */ - const fetchEraStakers = async () => { - if (!isReady || metrics.activeEra.index === 0 || !api) { - return; + // Process raw payee object from API. payee with `Account` type is returned as an key value pair, + // with all others strings. This function handles both cases and formats into a unified structure. + const processPayee = (rawPayee: AnyApi) => { + const payeeHuman = rawPayee.toHuman(); + + let payeeFinal: PayeeConfig; + if (typeof payeeHuman === 'string') { + const destination = payeeHuman as PayeeOptions; + payeeFinal = { + destination, + account: null, + }; + } else { + const payeeEntry = Object.entries(payeeHuman); + const destination = `${payeeEntry[0][0]}` as PayeeOptions; + const account = `${payeeEntry[0][1]}` as MaybeAddress; + payeeFinal = { + destination, + account, + }; } - const _exposures = await api.query.staking.erasStakers.entries( - metrics.activeEra.index + return payeeFinal; + }; + + // Fetches erasStakers exposures for an era, and saves to `localStorage`. + const fetchEraStakers = async (era: string) => { + if (!isReady || activeEra.index.isZero() || !api) return []; + + let exposures: Exposure[] = []; + const localExposures = getLocalEraExposures( + network, + era, + activeEra.index.toString() ); + if (localExposures) { + exposures = localExposures; + } else { + exposures = formatRawExposures( + await api.query.staking.erasStakers.entries(era) + ); + } + + // For resource limitation concerns, only store the current era in local storage. + if (era === activeEra.index.toString()) + setLocalEraExposures(network, era, exposures); + + return exposures; + }; + + // Fetches the active nominator set and metadata around it. + const fetchActiveEraStakers = async () => { + if (!isReady || activeEra.index.isZero() || !api) return; + // flag eraStakers is recyncing setStateWithRef(true, setErasStakersSyncing, erasStakersSyncingRef); - // humanise exposures to send to worker - const exposures = _exposures.map(([_keys, _val]: AnyApi) => { - return { - keys: _keys.toHuman(), - val: _val.toHuman(), - }; - }); + const exposures = await fetchEraStakers(activeEra.index.toString()); // worker to calculate stats worker.postMessage({ + era: activeEra.index.toString(), + networkName: network, + task: 'processExposures', activeAccount, - units: network.units, + units: networkData.units, exposures, - maxNominatorRewardedPerValidator, + maxNominatorRewardedPerValidator: + maxNominatorRewardedPerValidator.toNumber(), }); }; - /* - * Get the status of nominations. - * Possible statuses: waiting, inactive, active. - */ - const getNominationsStatus = () => { - if (inSetup()) { - return defaults.nominationStatus; - } - if (!activeAccount) { - return defaults.nominationStatus; - } - const nominations = getAccountNominations(activeAccount); - const statuses: NominationStatuses = {}; - - for (const nomination of nominations) { - const s = eraStakersRef.current.stakers.find( - (_n) => _n.address === nomination - ); - - if (s === undefined) { - statuses[nomination] = 'waiting'; - continue; - } - const exists = (s.others ?? []).find( - (_o: any) => _o.who === activeAccount - ); - if (exists === undefined) { - statuses[nomination] = 'inactive'; - continue; - } - statuses[nomination] = 'active'; - } - - return statuses; - }; - - /* Sets an account's stored target validators */ - const setTargets = (_targets: StakingTargets) => { - localStorage.setItem(`${activeAccount}_targets`, JSON.stringify(_targets)); - _setTargets(_targets); - return []; - }; - - /* - * Helper function to determine whether the active account - * has set a controller account. - */ - const hasController = () => { - if (!activeAccount) { - return false; - } - return getBondedAccount(activeAccount) !== null; + // Sets an account's stored target validators. + const setTargets = (value: StakingTargets) => { + localStorage.setItem(`${activeAccount}_targets`, JSON.stringify(value)); + setTargetsState(value); }; - /* - * Gets the nomination statuses of passed in nominations - */ + // Gets the nomination statuses of passed in nominations. const getNominationsStatusFromTargets = ( - who: MaybeAccount, - _targets: [any] + who: MaybeAddress, + fromTargets: AnyJson[] ) => { - const statuses: { [key: string]: string } = {}; + const statuses: Record<string, string> = {}; - if (!_targets.length) { + if (!fromTargets.length) { return statuses; } - for (const target of _targets) { - const s = eraStakersRef.current.stakers.find( - (_n: any) => _n.address === target + for (const target of fromTargets) { + const staker = eraStakersRef.current.stakers.find( + ({ address }) => address === target ); - if (s === undefined) { + if (staker === undefined) { statuses[target] = 'waiting'; continue; } - const exists = (s.others ?? []).find((_o: any) => _o.who === who); - if (exists === undefined) { + + if (!(staker.others ?? []).find((o: any) => o.who === who)) { statuses[target] = 'inactive'; continue; } @@ -367,91 +277,121 @@ export const StakingProvider = ({ return statuses; }; - /* - * Helper function to determine whether the controller account - * has been imported. - */ - const getControllerNotImported = (address: MaybeAccount) => { + // Helper function to determine whether the controller account is the same as the stash account. + const addressDifferentToStash = (address: MaybeAddress) => { + // check if controller is imported. + if (!connectAccounts.find((acc) => acc.address === address)) { + return false; + } + return address !== activeAccount && activeAccount !== null; + }; + + // Helper function to determine whether the controller account has been imported. + const getControllerNotImported = (address: MaybeAddress) => { if (address === null || !activeAccount) { return false; } // check if controller is imported - const exists = connectAccounts.find( - (acc: ImportedAccount) => acc.address === address - ); + const exists = connectAccounts.find((a) => a.address === address); if (exists === undefined) { return true; } - + // controller account exists. If it is a read-only account, then controller is imported. if (Object.prototype.hasOwnProperty.call(exists, 'addedBy')) { - const externalAccount = exists as ExternalAccount; - if (externalAccount.addedBy === 'user') { + if ((exists as ExternalAccount).addedBy === 'user') { return false; } } - + // if the controller is a Ledger account, then it can act as a signer. + if (exists.source === 'ledger') { + return false; + } + // if a `signer` does not exist on the account, then controller is not imported. return !Object.prototype.hasOwnProperty.call(exists, 'signer'); }; - /* - * Helper function to determine whether the active account - * is bonding, or is yet to start. - */ - const isBonding = () => { - if (!hasController() || !activeAccount) { - return false; - } + // Helper function to determine whether the active account. + const hasController = () => getBondedAccount(activeAccount) !== null; + + // Helper function to determine whether the active account is bonding, or is yet to start. + const isBonding = () => + hasController() && greaterThanZero(getStashLedger(activeAccount).active); + + // Helper function to determine whether the active account. + const isUnlocking = () => + hasController() && getStashLedger(activeAccount).unlocking.length; + + // Helper function to determine whether the active account is nominating, or is yet to start. + const isNominating = () => getAccountNominations(activeAccount).length > 0; - const ledger = getLedgerForStash(activeAccount); - return ledger.active.gt(new BN(0)); + // Helper function to determine whether the active account is nominating, or is yet to start. + const inSetup = () => + !activeAccount || + (!hasController() && !isBonding() && !isNominating() && !isUnlocking()); + + // Helper function to get the lowest reward from an active validator. + const getLowestRewardFromStaker = (address: MaybeAddress) => { + const staker = eraStakersRef.current.stakers.find( + (s) => s.address === address + ); + const lowest = new BigNumber(staker?.lowestReward || 0); + const oversubscribed = staker?.oversubscribed || false; + + return { + lowest, + oversubscribed, + }; }; - /* - * Helper function to determine whether the active account - * has funds unlocking. - */ - const isUnlocking = () => { - if (!hasController() || !activeAccount) { - return false; + useEffectIgnoreInitial(() => { + if (apiStatus === 'connecting') { + setStateWithRef(defaultEraStakers, setEraStakers, eraStakersRef); + setStakingMetrics(stakingMetrics); } - const ledger = getLedgerForStash(activeAccount); - return ledger.unlocking.length; - }; + }, [apiStatus]); - /* - * Helper function to determine whether the active account - * is nominating, or is yet to start. - */ - const isNominating = () => { - if (!activeAccount) { - return false; + // Handle staking metrics subscription + useEffectIgnoreInitial(() => { + if (isReady) { + unsubscribeMetrics(); + subscribeToStakingkMetrics(); } - const nominations = getAccountNominations(activeAccount); - return nominations.length > 0; - }; + return () => { + unsubscribeMetrics(); + }; + }, [isReady, activeEra, activeAccount]); - /* - * Helper function to determine whether the active account - * is nominating, or is yet to start. - */ - const inSetup = () => { - return ( - !activeAccount || - (!hasController() && !isBonding() && !isNominating() && !isUnlocking()) - ); - }; + // handle syncing with eraStakers + useEffectIgnoreInitial(() => { + if (isReady) fetchActiveEraStakers(); + }, [isReady, activeEra.index, activeAccount]); + + useEffectIgnoreInitial(() => { + if (activeAccount) { + // set account's targets + setTargetsState( + localStorageOrDefault( + `${activeAccount}_targets`, + defaultTargets, + true + ) as StakingTargets + ); + } + }, [isReady, bondedAccounts, activeAccount, eraStakersRef.current?.stakers]); return ( <StakingContext.Provider value={{ - getNominationsStatus, + fetchEraStakers, getNominationsStatusFromTargets, setTargets, hasController, getControllerNotImported, + addressDifferentToStash, isBonding, isNominating, inSetup, + getLowestRewardFromStaker, staking: stakingMetrics, eraStakers: eraStakersRef.current, erasStakersSyncing: erasStakersSyncingRef.current, @@ -462,3 +402,9 @@ export const StakingProvider = ({ </StakingContext.Provider> ); }; + +export const StakingContext = React.createContext<StakingContextInterface>( + defaultStakingContext +); + +export const useStaking = () => React.useContext(StakingContext); diff --git a/src/contexts/Staking/types.ts b/src/contexts/Staking/types.ts index 0b41a31052..3eebda7323 100644 --- a/src/contexts/Staking/types.ts +++ b/src/contexts/Staking/types.ts @@ -1,49 +1,101 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import BN from 'bn.js'; -import { MaybeAccount } from 'types'; +import type BigNumber from 'bignumber.js'; +import type { PayeeConfig } from 'contexts/Setup/types'; +import type { MaybeAddress } from 'types'; export interface StakingMetrics { - totalNominators: BN; - totalValidators: BN; - lastReward: BN; - lastTotalStake: BN; - validatorCount: BN; - maxNominatorsCount: BN; - maxValidatorsCount: BN; - minNominatorBond: BN; - payee: string | null; - unsub: { (): void } | null; + totalNominators: BigNumber; + totalValidators: BigNumber; + lastReward: BigNumber; + lastTotalStake: BigNumber; + validatorCount: BigNumber; + maxValidatorsCount: BigNumber; + minNominatorBond: BigNumber; + payee: PayeeConfig; + totalStaked: BigNumber; } +export interface ActiveAccountOwnStake { + address: string; + value: string; +} export interface EraStakers { - stakers: Array<any>; - nominators: Array<any> | undefined; - totalActiveNominators: number; + activeAccountOwnStake: ActiveAccountOwnStake[]; activeValidators: number; - minActiveBond: number; - minStakingActiveBond: number; - ownStake: any; + stakers: Staker[]; + totalActiveNominators: number; } -export type NominationStatuses = { [key: string]: string }; +export type NominationStatuses = Record<string, string>; export interface StakingTargets { nominations: string[]; } +export interface Exposure { + keys: string[]; + val: ExposureValue; +} + +export interface ExposureValue { + others: { + value: string; + who: string; + }[]; + own: string; + total: string; +} + +export type Staker = ExposureValue & { + address: string; + lowestReward: string; + oversubscribed: boolean; +}; + +export interface ActiveAccountStaker { + address: string; + value: string; +} + +export interface ExposureOther { + who: string; + value: string; +} + +interface LowestReward { + lowest: BigNumber; + oversubscribed: boolean; +} + export interface StakingContextInterface { - getNominationsStatus: () => any; - getNominationsStatusFromTargets: (w: MaybeAccount, t: [any]) => any; + fetchEraStakers: (era: string) => Promise<Exposure[]>; + getNominationsStatusFromTargets: (w: MaybeAddress, t: any[]) => any; setTargets: (t: any) => any; hasController: () => boolean; - getControllerNotImported: (a: MaybeAccount) => any; + getControllerNotImported: (a: MaybeAddress) => any; + addressDifferentToStash: (a: MaybeAddress) => boolean; isBonding: () => boolean; isNominating: () => boolean; inSetup: () => any; + getLowestRewardFromStaker: (a: MaybeAddress) => LowestReward; staking: StakingMetrics; eraStakers: EraStakers; targets: any; erasStakersSyncing: any; } + +export interface LocalExposuresData { + era: string; + exposures: LocalExposure[]; +} + +export interface LocalExposure { + k: [string, string]; + v: { + o: [string, string]; + w: string; + t: string; + }; +} diff --git a/src/contexts/Subscan/defaults.ts b/src/contexts/Subscan/defaults.ts deleted file mode 100644 index bd76de363a..0000000000 --- a/src/contexts/Subscan/defaults.ts +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { SubscanContextInterface } from './types'; - -export const defaultSubscanContext: SubscanContextInterface = { - // eslint-disable-next-line - fetchEraPoints: (v, e) => {}, - payouts: [], - poolClaims: [], -}; diff --git a/src/contexts/Subscan/index.tsx b/src/contexts/Subscan/index.tsx deleted file mode 100644 index f3642ee4c4..0000000000 --- a/src/contexts/Subscan/index.tsx +++ /dev/null @@ -1,221 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { ApiEndpoints, ApiSubscanKey } from 'consts'; -import { UIContextInterface } from 'contexts/UI/types'; -import React, { useEffect, useState } from 'react'; -import { AnyApi, AnySubscan } from 'types'; -import { useApi } from '../Api'; -import { useConnect } from '../Connect'; -import { useUi } from '../UI'; -import { defaultSubscanContext } from './defaults'; -import { SubscanContextInterface } from './types'; - -export const SubscanContext = React.createContext<SubscanContextInterface>( - defaultSubscanContext -); - -export const useSubscan = () => React.useContext(SubscanContext); - -export const SubscanProvider = ({ - children, -}: { - children: React.ReactNode; -}) => { - const { network, isReady } = useApi(); - const { services, getServices }: UIContextInterface = useUi(); - const { activeAccount } = useConnect(); - - // store fetched payouts from Subscan - const [payouts, setPayouts] = useState<AnySubscan>([]); - - // store fetched pool claims from Subscan - const [poolClaims, setPoolClaims] = useState<AnySubscan>([]); - - // reset payouts on network switch - useEffect(() => { - setPayouts([]); - setPoolClaims([]); - }, [network]); - - // fetch payouts as soon as network is ready - useEffect(() => { - if (isReady) { - fetchPayouts(); - fetchPoolClaims(); - } - }, [isReady, network, activeAccount]); - - // fetch payouts on services toggle - useEffect(() => { - fetchPayouts(); - fetchPoolClaims(); - }, [services]); - - /* fetchPayouts - * fetches payout history from Subscan. - * Fetches a total of 300 records from 3 asynchronous requests. - * Also checks if subscan service is active *after* the fetch has resolved - * as the user could have turned off the service while payouts were fetching. - * Stores resulting payouts in context state. - */ - const fetchPayouts = async () => { - if (activeAccount === null || !services.includes('subscan')) { - setPayouts([]); - return; - } - - // fetch 2 pages of results if subscan is enabled - if (getServices().includes('subscan')) { - let _payouts: Array<AnySubscan> = []; - - // fetch 3 pages of results - const results = await Promise.all([ - handleFetch(activeAccount, 0, ApiEndpoints.subscanRewardSlash, { - is_stash: true, - claimed_filter: 'claimed', - }), - handleFetch(activeAccount, 1, ApiEndpoints.subscanRewardSlash, { - is_stash: true, - claimed_filter: 'claimed', - }), - ]); - - // user may have turned off service while results were fetching. - // test again whether subscan service is still active. - if (getServices().includes('subscan')) { - for (const result of results) { - if (!result?.data?.list) { - break; - } - // ensure no payouts have block_timestamp of 0 - const list = result.data.list.filter( - (l: AnyApi) => l.block_timestamp !== 0 - ); - _payouts = _payouts.concat(list); - } - setPayouts(_payouts); - } - } - }; - - /* fetchPoolClaims - * fetches claim history from Subscan. - * Fetches a total of 300 records from 3 asynchronous requests. - * Also checks if subscan service is active *after* the fetch has resolved - * as the user could have turned off the service while payouts were fetching. - * Stores resulting claims in context state. - */ - const fetchPoolClaims = async () => { - if (activeAccount === null || !services.includes('subscan')) { - setPoolClaims([]); - return; - } - - // fetch 2 pages of results if subscan is enabled - if (getServices().includes('subscan')) { - let _poolClaims: Array<AnySubscan> = []; - - // fetch 3 pages of results - const results = await Promise.all([ - handleFetch(activeAccount, 0, ApiEndpoints.subscanPoolRewards), - handleFetch(activeAccount, 1, ApiEndpoints.subscanPoolRewards), - ]); - - // user may have turned off service while results were fetching. - // test again whether subscan service is still active. - if (getServices().includes('subscan')) { - for (const result of results) { - // check incorrectly formatted result object - if (!result?.data?.list) { - break; - } - // check list has records - if (!result.data.list.length) { - break; - } - // ensure no payouts have block_timestamp of 0 - const list = result.data.list.filter( - (l: AnyApi) => l.block_timestamp !== 0 - ); - _poolClaims = _poolClaims.concat(list); - } - setPoolClaims(_poolClaims); - } - } - }; - - /* fetchEraPoints - * fetches recent era point history for a particular address. - * Also checks if subscan service is active *after* the fetch has resolved - * as the user could have turned off the service while payouts were fetching. - * returns eraPoints - */ - const fetchEraPoints = async (address: string, era: number) => { - if (address === '' || !services.includes('subscan')) { - return []; - } - - const res = await handleFetch(address, 0, ApiEndpoints.subscanEraStat); - - if (res.message === 'Success') { - if (getServices().includes('subscan')) { - if (res.data?.list !== null) { - const list = []; - for (let i = era; i > era - 100; i--) { - list.push({ - era: i, - reward_point: - res.data.list.find((item: AnySubscan) => item.era === i) - ?.reward_point ?? 0, - }); - } - // removes last zero item and returns - return list.reverse().splice(0, list.length - 1); - } - return []; - } - } - return []; - }; - - /* handleFetch - * utility to handle a fetch request to Subscan - * returns resulting JSON. - */ - const handleFetch = async ( - address: string, - page: number, - endpoint: string, - body: AnyApi = {} - ): Promise<AnySubscan> => { - const bodyJson = { - row: 100, - page, - address, - ...body, - }; - const res: Response = await fetch(network.subscanEndpoint + endpoint, { - headers: { - 'Content-Type': 'application/json', - 'X-API-Key': ApiSubscanKey, - }, - body: JSON.stringify(bodyJson), - method: 'POST', - }); - const resJson: AnySubscan = await res.json(); - return resJson; - }; - - return ( - <SubscanContext.Provider - value={{ - fetchEraPoints, - payouts, - poolClaims, - }} - > - {children} - </SubscanContext.Provider> - ); -}; diff --git a/src/contexts/Subscan/types.ts b/src/contexts/Subscan/types.ts deleted file mode 100644 index 752ab7b5d7..0000000000 --- a/src/contexts/Subscan/types.ts +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { AnySubscan } from 'types'; - -export interface SubscanContextInterface { - fetchEraPoints: (v: string, e: number) => void; - payouts: AnySubscan; - poolClaims: AnySubscan; -} diff --git a/src/contexts/Themes/defaults.ts b/src/contexts/Themes/defaults.ts index 84451d48b5..b226cc91e9 100644 --- a/src/contexts/Themes/defaults.ts +++ b/src/contexts/Themes/defaults.ts @@ -1,10 +1,10 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only +/* eslint-disable @typescript-eslint/no-unused-vars */ -import { ThemeContextInterface } from './types'; +import type { ThemeContextInterface } from './types'; export const defaultThemeContext: ThemeContextInterface = { - // eslint-disable-next-line toggleTheme: (str) => {}, mode: 'light', }; diff --git a/src/contexts/Themes/index.tsx b/src/contexts/Themes/index.tsx index 39c3538b64..d0d68ba69f 100644 --- a/src/contexts/Themes/index.tsx +++ b/src/contexts/Themes/index.tsx @@ -1,67 +1,66 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only +import { setStateWithRef } from '@polkadot-cloud/utils'; import React, { useRef } from 'react'; -import { setStateWithRef } from 'Utils'; import { defaultThemeContext } from './defaults'; -import { ThemeContextInterface } from './types'; - -export const ThemeContext = - React.createContext<ThemeContextInterface>(defaultThemeContext); - -export const useTheme = () => React.useContext(ThemeContext); +import type { Theme, ThemeContextInterface } from './types'; export const ThemesProvider = ({ children }: { children: React.ReactNode }) => { + let initialTheme: Theme = 'light'; + // get the current theme - let localTheme = localStorage.getItem('theme') || ''; + const localThemeRaw = localStorage.getItem('theme') || ''; - // provide default theme if not set - if (!['light', 'dark'].includes(localTheme)) { - // check system theme - localTheme = + // Provide system theme if raw theme is not valid. + if (!['light', 'dark'].includes(localThemeRaw)) { + const systemTheme = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; - localStorage.setItem('theme', localTheme); + + initialTheme = systemTheme; + localStorage.setItem('theme', systemTheme); + } else { + // `localThemeRaw` is a valid theme. + initialTheme = localThemeRaw as Theme; } - // the theme state - const [state, setState] = React.useState<{ mode: string; card: string }>({ - mode: localTheme, - card: 'shadow', - }); - const stateRef = useRef(state); + // the theme mode + const [theme, setTheme] = React.useState<Theme>(initialTheme); + const themeRef = useRef(theme); - // auto change theme on system change + // Automatically change theme on system change. window .matchMedia('(prefers-color-scheme: dark)') .addEventListener('change', (event) => { - const _theme = event.matches ? 'dark' : 'light'; - localStorage.setItem('theme', _theme); - setStateWithRef( - { ...stateRef.current, mode: _theme }, - setState, - stateRef - ); + const newTheme = event.matches ? 'dark' : 'light'; + localStorage.setItem('theme', newTheme); + setStateWithRef(newTheme, setTheme, themeRef); }); - const toggleTheme = (_theme: string | null = null): void => { - if (_theme === null) { - _theme = state.mode === 'dark' ? 'light' : 'dark'; - } - localStorage.setItem('theme', _theme); - setStateWithRef({ ...stateRef.current, mode: _theme }, setState, stateRef); + const toggleTheme = (maybeTheme: Theme | null = null): void => { + const newTheme = + maybeTheme || (themeRef.current === 'dark' ? 'light' : 'dark'); + + localStorage.setItem('theme', newTheme); + setStateWithRef(newTheme, setTheme, themeRef); }; return ( <ThemeContext.Provider value={{ toggleTheme, - mode: stateRef.current.mode, + mode: themeRef.current, }} > {children} </ThemeContext.Provider> ); }; + +export const ThemeContext = + React.createContext<ThemeContextInterface>(defaultThemeContext); + +export const useTheme = () => React.useContext(ThemeContext); diff --git a/src/contexts/Themes/types.ts b/src/contexts/Themes/types.ts index c59c642960..c4e51cecc7 100644 --- a/src/contexts/Themes/types.ts +++ b/src/contexts/Themes/types.ts @@ -1,7 +1,9 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +export type Theme = 'light' | 'dark'; export interface ThemeContextInterface { - toggleTheme: (str?: string) => void; - mode: string; + toggleTheme: (str?: Theme) => void; + mode: Theme; } diff --git a/src/contexts/Tips/defaults.ts b/src/contexts/Tips/defaults.ts index fef1900078..c05f7ff595 100644 --- a/src/contexts/Tips/defaults.ts +++ b/src/contexts/Tips/defaults.ts @@ -1,7 +1,7 @@ // Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors // SPDX-License-Identifier: Apache-2.0 -import { TipsContextInterface } from './types'; +import type { TipsContextInterface } from './types'; export const defaultTipsContext: TipsContextInterface = { // eslint-disable-next-line diff --git a/src/contexts/Tips/index.tsx b/src/contexts/Tips/index.tsx index 6855355190..263140201e 100644 --- a/src/contexts/Tips/index.tsx +++ b/src/contexts/Tips/index.tsx @@ -2,9 +2,9 @@ // SPDX-License-Identifier: Apache-2.0 import React, { useEffect, useState } from 'react'; -import { MaybeString } from 'types'; +import type { MaybeString } from 'types'; import { defaultTipsContext } from './defaults'; -import { TipsContextInterface } from './types'; +import type { TipsContextInterface } from './types'; export const TipsContext = React.createContext<TipsContextInterface>(defaultTipsContext); diff --git a/src/contexts/Tips/types.ts b/src/contexts/Tips/types.ts index 3530088ac9..caa7c66613 100644 --- a/src/contexts/Tips/types.ts +++ b/src/contexts/Tips/types.ts @@ -1,7 +1,7 @@ // Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors // SPDX-License-Identifier: Apache-2.0 -import { MaybeString } from 'types'; +import type { MaybeString } from 'types'; export interface TipsContextInterface { openTipWith: (d: MaybeString, c: any) => void; diff --git a/src/contexts/Tooltip/defaults.ts b/src/contexts/Tooltip/defaults.ts index eedb6ef5c6..e2f9de4d9b 100644 --- a/src/contexts/Tooltip/defaults.ts +++ b/src/contexts/Tooltip/defaults.ts @@ -1,17 +1,15 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only +/* eslint-disable @typescript-eslint/no-unused-vars */ -import { TooltipContextInterface } from './types'; +import type { TooltipContextInterface } from './types'; export const defaultTooltipContext: TooltipContextInterface = { openTooltip: () => {}, closeTooltip: () => {}, - // eslint-disable-next-line - setTooltipPosition: (r) => {}, - // eslint-disable-next-line - checkTooltipPosition: (r) => {}, - // eslint-disable-next-line - setTooltipMeta: (t) => {}, + setTooltipPosition: (x, y) => {}, + showTooltip: () => {}, + setTooltipTextAndOpen: (t) => {}, open: 0, show: 0, position: [0, 0], diff --git a/src/contexts/Tooltip/index.tsx b/src/contexts/Tooltip/index.tsx index 8aa8862a61..c8872b5a1b 100644 --- a/src/contexts/Tooltip/index.tsx +++ b/src/contexts/Tooltip/index.tsx @@ -1,15 +1,10 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import React, { RefObject, useState } from 'react'; +import { setStateWithRef } from '@polkadot-cloud/utils'; +import React, { useState } from 'react'; import { defaultTooltipContext } from './defaults'; -import { TooltipContextInterface } from './types'; - -export const TooltipContext = React.createContext<TooltipContextInterface>( - defaultTooltipContext -); - -export const useTooltip = () => React.useContext(TooltipContext); +import type { TooltipContextInterface } from './types'; export const TooltipProvider = ({ children, @@ -18,6 +13,8 @@ export const TooltipProvider = ({ }) => { const [open, setOpen] = useState(0); const [show, setShow] = useState(0); + const showRef = React.useRef(show); + const [text, setText] = useState<string>(''); const [position, setPosition] = useState<[number, number]>([0, 0]); @@ -27,50 +24,23 @@ export const TooltipProvider = ({ }; const closeTooltip = () => { - setShow(0); + setStateWithRef(0, setShow, showRef); setOpen(0); }; - const setTooltipPosition = (ref: RefObject<HTMLDivElement>) => { - if (open || !ref?.current) return; - const bodyRect = document.body.getBoundingClientRect(); - const elemRect = ref.current.getBoundingClientRect(); - - const x = elemRect.left - bodyRect.left; - const y = elemRect.top - bodyRect.top; - + const setTooltipPosition = (x: number, y: number) => { setPosition([x, y]); openTooltip(); }; - const checkTooltipPosition = (ref: RefObject<HTMLDivElement>) => { - if (!ref?.current) return; - - const bodyRect = document.body.getBoundingClientRect(); - const menuRect = ref.current.getBoundingClientRect(); - - let x = menuRect.left - bodyRect.left; - let y = menuRect.top - bodyRect.top - menuRect.height; - const right = menuRect.right; - const bottom = menuRect.bottom; - - // small offset from menu start - y -= 5; - - const documentPadding = 20; - - if (right > bodyRect.right) { - x = bodyRect.right - ref.current.offsetWidth - documentPadding; - } - if (bottom > bodyRect.bottom) { - y = bodyRect.bottom - ref.current.offsetHeight - documentPadding; - } - setPosition([x, y]); - setShow(1); + const showTooltip = () => { + setStateWithRef(1, setShow, showRef); }; - const setTooltipMeta = (t: string) => { + const setTooltipTextAndOpen = (t: string) => { + if (open) return; setText(t); + openTooltip(); }; return ( @@ -79,10 +49,10 @@ export const TooltipProvider = ({ openTooltip, closeTooltip, setTooltipPosition, - checkTooltipPosition, - setTooltipMeta, + showTooltip, + setTooltipTextAndOpen, open, - show, + show: showRef.current, position, text, }} @@ -91,3 +61,9 @@ export const TooltipProvider = ({ </TooltipContext.Provider> ); }; + +export const TooltipContext = React.createContext<TooltipContextInterface>( + defaultTooltipContext +); + +export const useTooltip = () => React.useContext(TooltipContext); diff --git a/src/contexts/Tooltip/types.ts b/src/contexts/Tooltip/types.ts index 951f2d1b2c..f903b8e29c 100644 --- a/src/contexts/Tooltip/types.ts +++ b/src/contexts/Tooltip/types.ts @@ -1,14 +1,12 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { RefObject } from 'react'; +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only export interface TooltipContextInterface { openTooltip: () => void; closeTooltip: () => void; - setTooltipPosition: (ref: RefObject<HTMLDivElement>) => void; - checkTooltipPosition: (ref: RefObject<HTMLDivElement>) => void; - setTooltipMeta: (t: string) => void; + setTooltipPosition: (x: number, y: number) => void; + showTooltip: () => void; + setTooltipTextAndOpen: (t: string) => void; open: number; show: number; position: [number, number]; diff --git a/src/contexts/TransferOptions/defaults.ts b/src/contexts/TransferOptions/defaults.ts index 3a4551266d..d30021aeda 100644 --- a/src/contexts/TransferOptions/defaults.ts +++ b/src/contexts/TransferOptions/defaults.ts @@ -1,30 +1,33 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only +/* eslint-disable @typescript-eslint/no-unused-vars */ -import BN from 'bn.js'; -import { TransferOptions, TransferOptionsContextInterface } from './types'; +import BigNumber from 'bignumber.js'; +import type { TransferOptions, TransferOptionsContextInterface } from './types'; -export const defaultBalancesContext: TransferOptionsContextInterface = { - // eslint-disable-next-line +export const defaultBondedContext: TransferOptionsContextInterface = { getTransferOptions: (a) => transferOptions, + setFeeReserveBalance: (r) => {}, + feeReserve: new BigNumber(0), }; export const transferOptions: TransferOptions = { - freeBalance: new BN(0), + freeBalance: new BigNumber(0), + edReserved: new BigNumber(0), nominate: { - active: new BN(0), - freeToUnbond: new BN(0), - totalUnlocking: new BN(0), - totalUnlocked: new BN(0), - totalPossibleBond: new BN(0), + active: new BigNumber(0), + totalUnlocking: new BigNumber(0), + totalUnlocked: new BigNumber(0), + totalPossibleBond: new BigNumber(0), + totalAdditionalBond: new BigNumber(0), totalUnlockChuncks: 0, }, pool: { - active: new BN(0), - freeToUnbond: new BN(0), - totalUnlocking: new BN(0), - totalUnlocked: new BN(0), - totalPossibleBond: new BN(0), + active: new BigNumber(0), + totalUnlocking: new BigNumber(0), + totalUnlocked: new BigNumber(0), + totalPossibleBond: new BigNumber(0), + totalAdditionalBond: new BigNumber(0), totalUnlockChuncks: 0, }, }; diff --git a/src/contexts/TransferOptions/index.tsx b/src/contexts/TransferOptions/index.tsx index c6f73aab08..19c68955ee 100644 --- a/src/contexts/TransferOptions/index.tsx +++ b/src/contexts/TransferOptions/index.tsx @@ -1,133 +1,217 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import BN from 'bn.js'; +import { unitToPlanck } from '@polkadot-cloud/utils'; +import BigNumber from 'bignumber.js'; +import React, { useState } from 'react'; +import { useApi } from 'contexts/Api'; import { useBalances } from 'contexts/Balances'; -import { useNetworkMetrics } from 'contexts/Network'; +import { useBonded } from 'contexts/Bonded'; +import { useNetworkMetrics } from 'contexts/NetworkMetrics'; import { usePoolMemberships } from 'contexts/Pools/PoolMemberships'; -import React from 'react'; -import { MaybeAccount } from 'types'; +import type { MaybeAddress } from 'types'; +import { useEffectIgnoreInitial } from '@polkadot-cloud/react/hooks'; +import { useNetwork } from 'contexts/Network'; +import { useActiveAccounts } from 'contexts/ActiveAccounts'; import * as defaults from './defaults'; -import { TransferOptions, TransferOptionsContextInterface } from './types'; - -export const TransferOptionsContext = - React.createContext<TransferOptionsContextInterface>( - defaults.defaultBalancesContext - ); - -export const useTransferOptions = () => - React.useContext(TransferOptionsContext); +import type { TransferOptions, TransferOptionsContextInterface } from './types'; export const TransferOptionsProvider = ({ children, }: { children: React.ReactNode; }) => { - const { metrics } = useNetworkMetrics(); - const { getAccount, getAccountBalance, getLedgerForStash } = useBalances(); + const { consts } = useApi(); + const { + networkData: { name, units, defaultFeeReserve }, + } = useNetwork(); + const { activeEra } = useNetworkMetrics(); + const { getStashLedger, getBalance, getLocks } = useBalances(); + const { getAccount } = useBonded(); const { membership } = usePoolMemberships(); + const { existentialDeposit } = consts; + const { activeAccount } = useActiveAccounts(); + + // Get the local storage rcord for an account reserve balance. + const getFeeReserveLocalStorage = (address: MaybeAddress) => { + const reserves = JSON.parse( + localStorage.getItem('reserve_balances') ?? '{}' + ); + return new BigNumber( + reserves?.[name]?.[address || ''] ?? + unitToPlanck(String(defaultFeeReserve), units) + ); + }; + + // A user-configurable reserve amount to be used to pay for transaction fees. + const [feeReserve, setFeeReserve] = useState<BigNumber>( + getFeeReserveLocalStorage(activeAccount) + ); - const { activeEra } = metrics; + // Update an account's reserve amount on account or network change. + useEffectIgnoreInitial(() => { + setFeeReserve(getFeeReserveLocalStorage(activeAccount)); + }, [activeAccount, name]); - // get the bond and unbond amounts available to the user - const getTransferOptions = (address: MaybeAccount): TransferOptions => { + // Get the bond and unbond amounts available to the user + const getTransferOptions = (address: MaybeAddress): TransferOptions => { const account = getAccount(address); if (account === null) { return defaults.transferOptions; } - const balance = getAccountBalance(address); - const ledger = getLedgerForStash(address); - const { freeAfterReserve } = balance; - const { active, unlocking } = ledger; - - const points = membership?.points; - const activePool = points ? new BN(points) : new BN(0); + const balance = getBalance(address); + const ledger = getStashLedger(address); + const locks = getLocks(address); + + const { free } = balance; + const { active, total, unlocking } = ledger; + + const totalLocked = + locks?.reduce( + (prev, { amount }) => prev.plus(amount), + new BigNumber(0) + ) || new BigNumber(0); + + // Calculate a forced amount of free balance that needs to be reserved to keep the account + // alive. Deducts `locks` from free balance reserve needed. + const edReserved = BigNumber.max(existentialDeposit.minus(totalLocked), 0); + + // Total free balance after `edReserved` is subtracted. + const freeMinusReserve = BigNumber.max(free.minus(edReserved), 0); + + // calculate total balance locked + const maxLockBalance = + locks.reduce( + (prev, current) => { + return prev.amount.isGreaterThan(current.amount) ? prev : current; + }, + { amount: new BigNumber(0) } + )?.amount || new BigNumber(0); + + const poolBalance = membership?.balance; + const activePool = poolBalance || new BigNumber(0); // total amount actively unlocking - let totalUnlocking = new BN(0); - let totalUnlocked = new BN(0); + let totalUnlocking = new BigNumber(0); + let totalUnlocked = new BigNumber(0); for (const u of unlocking) { const { value, era } = u; - if (activeEra.index > era) { - totalUnlocked = totalUnlocked.add(value); + if (activeEra.index.isGreaterThan(era)) { + totalUnlocked = totalUnlocked.plus(value); } else { - totalUnlocking = totalUnlocking.add(value); + totalUnlocking = totalUnlocking.plus(value); } } - // free to bond balance - const freeBalance = BN.max( - freeAfterReserve.sub(active).sub(totalUnlocking).sub(totalUnlocked), - new BN(0) - ); + // free balance after `total` ledger amount. + const freeBalance = BigNumber.max(freeMinusReserve.minus(total), 0); const nominateOptions = () => { - const freeToUnbond = active; - // total possible balance that can be bonded - const totalPossibleBond = BN.max( - freeAfterReserve.sub(totalUnlocking).sub(totalUnlocked), - new BN(0) + const totalPossibleBond = BigNumber.max( + freeMinusReserve + .minus(totalUnlocking) + .minus(totalUnlocked) + .minus(feeReserve), + 0 ); + // total additional balance that can be bonded. + const totalAdditionalBond = totalPossibleBond.minus(active); + return { active, - freeToUnbond, totalUnlocking, totalUnlocked, totalPossibleBond, + totalAdditionalBond, totalUnlockChuncks: unlocking.length, }; }; const poolOptions = () => { const unlockingPool = membership?.unlocking || []; - const freeToUnbondPool = activePool; // total possible balance that can be bonded - const totalPossibleBondPool = BN.max( - freeAfterReserve - .sub(active) - .sub(totalUnlocking) - .sub(totalUnlocked) - .add(activePool), - new BN(0) + const totalPossibleBondPool = BigNumber.max( + freeMinusReserve.minus(maxLockBalance).minus(feeReserve), + new BigNumber(0) ); - let totalUnlockingPool = new BN(0); - let totalUnlockedPool = new BN(0); + // total additional balance that can be bonded. + const totalAdditionalBondPool = totalPossibleBondPool; + + let totalUnlockingPool = new BigNumber(0); + let totalUnlockedPool = new BigNumber(0); for (const u of unlockingPool) { const { value, era } = u; - if (activeEra.index > era) { - totalUnlockedPool = totalUnlockedPool.add(value); + if (activeEra.index.isGreaterThan(era)) { + totalUnlockedPool = totalUnlockedPool.plus(value); } else { - totalUnlockingPool = totalUnlockingPool.add(value); + totalUnlockingPool = totalUnlockingPool.plus(value); } } return { active: activePool, - freeToUnbond: freeToUnbondPool, totalUnlocking: totalUnlockingPool, totalUnlocked: totalUnlockedPool, totalPossibleBond: totalPossibleBondPool, + totalAdditionalBond: totalAdditionalBondPool, totalUnlockChuncks: unlockingPool.length, }; }; return { freeBalance, + edReserved, nominate: nominateOptions(), pool: poolOptions(), }; }; + // Updates account's reserve amount in state and in local storage. + const setFeeReserveBalance = (amount: BigNumber) => { + if (!activeAccount) return; + setFeeReserveLocalStorage(amount); + setFeeReserve(amount); + }; + + // Update the local storage record for account reserve balances. + const setFeeReserveLocalStorage = (amount: BigNumber) => { + if (!activeAccount) return; + + try { + const newReserves = JSON.parse( + localStorage.getItem('reserve_balances') ?? '{}' + ); + const newReservesNetwork = newReserves?.[name] ?? {}; + newReservesNetwork[activeAccount] = amount.toString(); + + newReserves[name] = newReservesNetwork; + localStorage.setItem('reserve_balances', JSON.stringify(newReserves)); + } catch (e) { + // corrupted local storage record - remove it. + localStorage.removeItem('reserve_balances'); + } + }; + return ( <TransferOptionsContext.Provider value={{ getTransferOptions, + setFeeReserveBalance, + feeReserve, }} > {children} </TransferOptionsContext.Provider> ); }; + +export const TransferOptionsContext = + React.createContext<TransferOptionsContextInterface>( + defaults.defaultBondedContext + ); + +export const useTransferOptions = () => + React.useContext(TransferOptionsContext); diff --git a/src/contexts/TransferOptions/types.ts b/src/contexts/TransferOptions/types.ts index abac8d9206..fe8a1ea136 100644 --- a/src/contexts/TransferOptions/types.ts +++ b/src/contexts/TransferOptions/types.ts @@ -1,29 +1,32 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import BN from 'bn.js'; -import { MaybeAccount } from 'types'; +import type BigNumber from 'bignumber.js'; +import type { MaybeAddress } from 'types'; export interface TransferOptionsContextInterface { - getTransferOptions: (a: MaybeAccount) => TransferOptions; + getTransferOptions: (a: MaybeAddress) => TransferOptions; + setFeeReserveBalance: (r: BigNumber) => void; + feeReserve: BigNumber; } export interface TransferOptions { - freeBalance: BN; + freeBalance: BigNumber; + edReserved: BigNumber; nominate: { - active: BN; - freeToUnbond: BN; - totalUnlocking: BN; - totalUnlocked: BN; - totalPossibleBond: BN; + active: BigNumber; + totalUnlocking: BigNumber; + totalUnlocked: BigNumber; + totalPossibleBond: BigNumber; + totalAdditionalBond: BigNumber; totalUnlockChuncks: number; }; pool: { - active: BN; - freeToUnbond: BN; - totalUnlocking: BN; - totalUnlocked: BN; - totalPossibleBond: BN; + active: BigNumber; + totalUnlocking: BigNumber; + totalUnlocked: BigNumber; + totalPossibleBond: BigNumber; + totalAdditionalBond: BigNumber; totalUnlockChuncks: number; }; } diff --git a/src/contexts/TxFees/defaults.ts b/src/contexts/TxFees/defaults.ts deleted file mode 100644 index ae37120671..0000000000 --- a/src/contexts/TxFees/defaults.ts +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import BN from 'bn.js'; -import { EstimatedFeeContext } from '.'; - -export const defaultTxFees: EstimatedFeeContext = { - txFees: new BN(0), - notEnoughFunds: false, - // eslint-disable-next-line - setTxFees: (f) => {}, - resetTxFees: () => {}, - // eslint-disable-next-line - setSender: (s) => {}, - txFeesValid: false, -}; diff --git a/src/contexts/TxFees/index.tsx b/src/contexts/TxFees/index.tsx deleted file mode 100644 index 0e527ee451..0000000000 --- a/src/contexts/TxFees/index.tsx +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import BN from 'bn.js'; -import { useConnect } from 'contexts/Connect'; -import { useTransferOptions } from 'contexts/TransferOptions'; -import React, { useEffect, useState } from 'react'; -import { MaybeAccount } from 'types'; -import * as defaults from './defaults'; - -export interface EstimatedFeeContext { - txFees: BN; - notEnoughFunds: boolean; - setTxFees: (f: BN) => void; - resetTxFees: () => void; - setSender: (s: MaybeAccount) => void; - txFeesValid: boolean; -} - -export const TxFeesContext = React.createContext<EstimatedFeeContext>( - defaults.defaultTxFees -); - -export const useTxFees = () => React.useContext(TxFeesContext); - -export const TxFeesProvider = ({ children }: { children: React.ReactNode }) => { - const { activeAccount } = useConnect(); - const { getTransferOptions } = useTransferOptions(); - - // store the transaction fees for the transaction. - const [txFees, _setTxFees] = useState(new BN(0)); - - // store the sender of the transaction - const [sender, setSender] = useState<MaybeAccount>(activeAccount); - - // store whether the sender does not have enough funds. - const [notEnoughFunds, setNotEnoughFunds] = useState(false); - - useEffect(() => { - const { freeBalance } = getTransferOptions(sender); - setNotEnoughFunds(freeBalance.sub(txFees).lt(new BN(0))); - }, [txFees, sender]); - - const setTxFees = (fees: BN) => { - _setTxFees(fees); - }; - - const resetTxFees = () => { - setSender(null); - _setTxFees(new BN(0)); - }; - - const txFeesValid = (() => { - if (txFees.isZero() || notEnoughFunds) { - return false; - } - return true; - })(); - - return ( - <TxFeesContext.Provider - value={{ - txFees, - notEnoughFunds, - setTxFees, - resetTxFees, - setSender, - txFeesValid, - }} - > - {children} - </TxFeesContext.Provider> - ); -}; diff --git a/src/contexts/TxMeta/defaults.ts b/src/contexts/TxMeta/defaults.ts new file mode 100644 index 0000000000..a98d2cb82f --- /dev/null +++ b/src/contexts/TxMeta/defaults.ts @@ -0,0 +1,24 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only +/* eslint-disable @typescript-eslint/no-unused-vars */ + +import BigNumber from 'bignumber.js'; +import type { TxMetaContextInterface } from './types'; + +export const defaultTxMeta: TxMetaContextInterface = { + controllerSignerAvailable: (a, b) => 'ok', + txFees: new BigNumber(0), + notEnoughFunds: false, + setTxFees: (f) => {}, + resetTxFees: () => {}, + sender: null, + setSender: (s) => {}, + txFeesValid: false, + incrementPayloadUid: () => 0, + getPayloadUid: () => 0, + getTxPayload: () => {}, + setTxPayload: (p, u) => {}, + getTxSignature: () => null, + resetTxPayloads: () => {}, + setTxSignature: (s) => {}, +}; diff --git a/src/contexts/TxMeta/index.tsx b/src/contexts/TxMeta/index.tsx new file mode 100644 index 0000000000..efac8cbdb3 --- /dev/null +++ b/src/contexts/TxMeta/index.tsx @@ -0,0 +1,147 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { setStateWithRef } from '@polkadot-cloud/utils'; +import BigNumber from 'bignumber.js'; +import React, { useState } from 'react'; +import { useBonded } from 'contexts/Bonded'; +import { useStaking } from 'contexts/Staking'; +import { useTransferOptions } from 'contexts/TransferOptions'; +import type { AnyJson, MaybeAddress } from 'types'; +import { useEffectIgnoreInitial } from '@polkadot-cloud/react/hooks'; +import { useActiveAccounts } from 'contexts/ActiveAccounts'; +import { useImportedAccounts } from 'contexts/Connect/ImportedAccounts'; +import * as defaults from './defaults'; +import type { TxMetaContextInterface } from './types'; + +export const TxMetaProvider = ({ children }: { children: React.ReactNode }) => { + const { getBondedAccount } = useBonded(); + const { activeProxy } = useActiveAccounts(); + const { getControllerNotImported } = useStaking(); + const { accountHasSigner } = useImportedAccounts(); + const { getTransferOptions } = useTransferOptions(); + + // Store the transaction fees for the transaction. + const [txFees, setTxFees] = useState(new BigNumber(0)); + + // Store the sender of the transaction. + const [sender, setSender] = useState<MaybeAddress>(null); + + // Store whether the sender does not have enough funds. + const [notEnoughFunds, setNotEnoughFunds] = useState(false); + + // Store the payloads of transactions if extrinsics require manual signing (e.g. Ledger). payloads + // are calculated asynchronously and extrinsic associated with them may be cancelled. For this + // reason we give every payload a uid, and check whether this uid matches the active extrinsic + // before submitting it. + const [txPayload, setTxPayloadState] = useState<{ + payload: AnyJson; + uid: number; + } | null>(null); + const txPayloadRef = React.useRef(txPayload); + + // Store an optional signed transaction if extrinsics require manual signing (e.g. Ledger). + const [txSignature, setTxSignatureState] = useState<AnyJson>(null); + const txSignatureRef = React.useRef(txSignature); + + useEffectIgnoreInitial(() => { + const { freeBalance } = getTransferOptions(sender); + setNotEnoughFunds(freeBalance.minus(txFees).isLessThan(0)); + }, [txFees, sender]); + + const resetTxFees = () => { + setTxFees(new BigNumber(0)); + }; + + const getPayloadUid = () => { + return txPayloadRef.current?.uid || 1; + }; + + const incrementPayloadUid = () => { + return (txPayloadRef.current?.uid || 0) + 1; + }; + + const getTxPayload = () => { + return txPayloadRef.current?.payload || null; + }; + + const setTxPayload = (p: AnyJson, uid: number) => { + setStateWithRef( + { + payload: p, + uid, + }, + setTxPayloadState, + txPayloadRef + ); + }; + + const resetTxPayloads = () => { + setStateWithRef(null, setTxPayloadState, txPayloadRef); + }; + + const getTxSignature = () => { + return txSignatureRef.current; + }; + + const setTxSignature = (s: AnyJson) => { + setStateWithRef(s, setTxSignatureState, txSignatureRef); + }; + + const txFeesValid = (() => { + if (txFees.isZero() || notEnoughFunds) { + return false; + } + return true; + })(); + + const controllerSignerAvailable = ( + stash: MaybeAddress, + proxySupported: boolean + ) => { + const controller = getBondedAccount(stash); + + if (controller !== stash) { + if (getControllerNotImported(controller)) + return 'controller_not_imported'; + + if (!accountHasSigner(controller)) return 'read_only'; + } else if ( + (!proxySupported || !accountHasSigner(activeProxy)) && + !accountHasSigner(stash) + ) { + return 'read_only'; + } + return 'ok'; + }; + + return ( + <TxMetaContext.Provider + value={{ + controllerSignerAvailable, + txFees, + notEnoughFunds, + setTxFees, + resetTxFees, + txFeesValid, + sender, + setSender, + incrementPayloadUid, + getPayloadUid, + getTxPayload, + setTxPayload, + resetTxPayloads, + getTxSignature, + setTxSignature, + }} + > + {children} + </TxMetaContext.Provider> + ); +}; + +export const TxMetaContext = React.createContext<TxMetaContextInterface>( + defaults.defaultTxMeta +); + +export const useTxMeta = () => React.useContext(TxMetaContext); diff --git a/src/contexts/TxMeta/types.ts b/src/contexts/TxMeta/types.ts new file mode 100644 index 0000000000..70510cdc76 --- /dev/null +++ b/src/contexts/TxMeta/types.ts @@ -0,0 +1,26 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import type BigNumber from 'bignumber.js'; +import type { AnyJson, MaybeAddress } from 'types'; + +export interface TxMetaContextInterface { + controllerSignerAvailable: ( + a: MaybeAddress, + b: boolean + ) => 'controller_not_imported' | 'read_only' | 'ok'; + txFees: BigNumber; + notEnoughFunds: boolean; + setTxFees: (f: BigNumber) => void; + resetTxFees: () => void; + sender: MaybeAddress; + setSender: (s: MaybeAddress) => void; + txFeesValid: boolean; + incrementPayloadUid: () => number; + getPayloadUid: () => number; + getTxPayload: () => AnyJson; + setTxPayload: (s: AnyJson, u: number) => void; + resetTxPayloads: () => void; + getTxSignature: () => AnyJson; + setTxSignature: (s: AnyJson) => void; +} diff --git a/src/contexts/UI/defaults.ts b/src/contexts/UI/defaults.ts index 3da8356479..02447cd199 100644 --- a/src/contexts/UI/defaults.ts +++ b/src/contexts/UI/defaults.ts @@ -1,56 +1,19 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only +/* eslint-disable @typescript-eslint/no-unused-vars */ -import { UIContextInterface } from './types'; - -export const defaultStakeSetup = { - controller: null, - payee: null, - nominations: [], - bond: 0, - section: 1, -}; - -export const defaultPoolSetup = { - metadata: '', - bond: 0, - nominations: [], - roles: null, - section: 1, -}; +import type { UIContextInterface } from './types'; export const defaultUIContext: UIContextInterface = { - // eslint-disable-next-line setSideMenu: (v) => {}, - // eslint-disable-next-line setUserSideMenuMinimised: (v) => {}, - // eslint-disable-next-line - toggleService: (k) => {}, - // eslint-disable-next-line - getSetupProgress: (a, b) => {}, - // eslint-disable-next-line - getStakeSetupProgressPercent: (a) => 0, - // eslint-disable-next-line - getPoolSetupProgressPercent: (a) => 0, - // eslint-disable-next-line - setActiveAccountSetup: (t, p) => {}, - // eslint-disable-next-line - setActiveAccountSetupSection: (t, s) => {}, - getServices: () => [], - // eslint-disable-next-line - setOnNominatorSetup: (v) => {}, - // eslint-disable-next-line - setOnPoolSetup: (v) => {}, - // eslint-disable-next-line setContainerRefs: (v) => {}, - sideMenuOpen: 0, - userSideMenuMinimised: 0, - sideMenuMinimised: 0, - services: [], - onNominatorSetup: 0, - onPoolSetup: 0, - isSyncing: false, - networkSyncing: false, - poolsSyncing: false, + sideMenuOpen: false, + userSideMenuMinimised: false, + sideMenuMinimised: false, containerRefs: {}, + isSyncing: false, + isNetworkSyncing: false, + isPoolSyncing: false, + isBraveBrowser: false, }; diff --git a/src/contexts/UI/index.tsx b/src/contexts/UI/index.tsx index 8d90a4cb82..be3e164e16 100644 --- a/src/contexts/UI/index.tsx +++ b/src/contexts/UI/index.tsx @@ -1,392 +1,170 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import BN from 'bn.js'; -import { ServiceList, SideMenuStickyThreshold } from 'consts'; -import { ImportedAccount } from 'contexts/Connect/types'; -import { useActivePools } from 'contexts/Pools/ActivePools'; -import { usePoolMemberships } from 'contexts/Pools/PoolMemberships'; +import { localStorageOrDefault, setStateWithRef } from '@polkadot-cloud/utils'; +import BigNumber from 'bignumber.js'; import React, { useEffect, useRef, useState } from 'react'; -import { MaybeAccount, Sync } from 'types'; -import { localStorageOrDefault, setStateWithRef } from 'Utils'; +import { SideMenuStickyThreshold } from 'consts'; +import { useBalances } from 'contexts/Balances'; +import type { ImportedAccount } from '@polkadot-cloud/react/types'; +import { useActivePools } from 'contexts/Pools/ActivePools'; +import { useEffectIgnoreInitial } from '@polkadot-cloud/react/hooks'; +import { useImportedAccounts } from 'contexts/Connect/ImportedAccounts'; +import type { AnyJson } from 'types'; import { useApi } from '../Api'; -import { useBalances } from '../Balances'; -import { useConnect } from '../Connect'; -import { useNetworkMetrics } from '../Network'; +import { useNetworkMetrics } from '../NetworkMetrics'; import { useStaking } from '../Staking'; import * as defaults from './defaults'; -import { SetupType, UIContextInterface } from './types'; - -export const UIContext = React.createContext<UIContextInterface>( - defaults.defaultUIContext -); - -export const useUi = () => React.useContext(UIContext); +import type { UIContextInterface } from './types'; export const UIProvider = ({ children }: { children: React.ReactNode }) => { - const { isReady, network } = useApi(); - const { accounts: connectAccounts, activeAccount } = useConnect(); - const { staking, eraStakers, inSetup } = useStaking(); - const { metrics } = useNetworkMetrics(); - const { accounts } = useBalances(); - const { membership: poolMembership } = usePoolMemberships(); + const { isReady } = useApi(); + const { balances } = useBalances(); + const { staking, eraStakers } = useStaking(); + const { activeEra, metrics } = useNetworkMetrics(); const { synced: activePoolsSynced } = useActivePools(); + const { accounts: connectAccounts } = useImportedAccounts(); - // set whether the network has been synced. - const [networkSyncing, setNetworkSyncing] = useState(false); + // Set whether the network has been synced. + const [isNetworkSyncing, setIsNetworkSyncing] = useState<boolean>(false); - // set whether pools are being synced. - const [poolsSyncing, setPoolsSyncing] = useState(false); + // Set whether pools are being synced. + const [isPoolSyncing, setIsPoolSyncing] = useState<boolean>(false); - // set whether app is syncing.ncludes workers (active nominations). - const [isSyncing, setIsSyncing] = useState(false); + // Set whether app is syncing. Includes workers (active nominations). + const [isSyncing, setIsSyncing] = useState<boolean>(false); - // get initial services - const getAvailableServices = () => { - // get services config from local storage - const _services: any = localStorageOrDefault('services', ServiceList, true); - - // if fiat is disabled, remove binance_spot service - const DISABLE_FIAT = Number(process.env.REACT_APP_DISABLE_FIAT ?? 0); - if (DISABLE_FIAT && _services.includes('binance_spot')) { - const index = _services.indexOf('binance_spot'); - if (index !== -1) { - _services.splice(index, 1); - } - } - return _services; - }; + // Side whether the side menu is open. + const [sideMenuOpen, setSideMenu] = useState<boolean>(false); - // get side menu minimised state from local storage, default to not - const _userSideMenuMinimised = Number( - localStorageOrDefault('side_menu_minimised', 0) - ); + // Store whether in Brave browser. Used for light client warning. + const [isBraveBrowser, setIsBraveBrowser] = useState<boolean>(false); - // side menu control - const [sideMenuOpen, setSideMenuOpen] = useState(0); + // Store referneces for main app conainers. + const [containerRefs, setContainerRefsState] = useState({}); + const setContainerRefs = (v: any) => { + setContainerRefsState(v); + }; - // side menu minimised - const [userSideMenuMinimised, _setUserSideMenuMinimised] = useState( - _userSideMenuMinimised + // Get side menu minimised state from local storage, default to false. + const [userSideMenuMinimised, setUserSideMenuMinimisedState] = useState( + localStorageOrDefault('side_menu_minimised', false, true) as boolean ); const userSideMenuMinimisedRef = useRef(userSideMenuMinimised); - const setUserSideMenuMinimised = (v: number) => { + const setUserSideMenuMinimised = (v: boolean) => { localStorage.setItem('side_menu_minimised', String(v)); - setStateWithRef(v, _setUserSideMenuMinimised, userSideMenuMinimisedRef); + setStateWithRef(v, setUserSideMenuMinimisedState, userSideMenuMinimisedRef); }; - // automatic side menu minimised + // Automatic side menu minimised. const [sideMenuMinimised, setSideMenuMinimised] = useState( window.innerWidth <= SideMenuStickyThreshold - ? 1 + ? true : userSideMenuMinimisedRef.current ); - // is the user actively on the setup page - const [onNominatorSetup, setOnNominatorSetup] = useState(0); - - // is the user actively on the pool creation page - const [onPoolSetup, setOnPoolSetup] = useState(0); - - // services - const [services, setServices] = useState(getAvailableServices()); - const servicesRef = useRef(services); - - // staking setup persist - const [setup, setSetup]: any = useState([]); - const setupRef = useRef<any>(setup); - - // resize side menu callback + // Resize side menu callback. const resizeCallback = () => { if (window.innerWidth <= SideMenuStickyThreshold) { - setSideMenuMinimised(0); + setSideMenuMinimised(false); } else { setSideMenuMinimised(userSideMenuMinimisedRef.current); } }; - // move away from setup pages on completion / network change + // Resize event listener. useEffect(() => { - if (!inSetup()) { - setOnNominatorSetup(0); - } - if (poolMembership) { - setOnPoolSetup(0); - } - }, [inSetup(), network, poolMembership]); + (window.navigator as AnyJson)?.brave + ?.isBrave() + .then(async (isBrave: boolean) => { + setIsBraveBrowser(isBrave); + }); - // resize event listener - useEffect(() => { window.addEventListener('resize', resizeCallback); return () => { window.removeEventListener('resize', resizeCallback); }; }, []); - // re-configure minimised on user change - useEffect(() => { + // Re-configure minimised on user change. + useEffectIgnoreInitial(() => { resizeCallback(); }, [userSideMenuMinimised]); - // update setup state when activeAccount changes - useEffect(() => { - if (connectAccounts.length) { - const _setup = setupDefault(); - setStateWithRef(_setup, setSetup, setupRef); - } - }, [activeAccount, network, connectAccounts]); - - // app syncing updates + // App syncing updates. useEffect(() => { - let _syncing = false; - let _networkSyncing = false; - let _poolsSyncing = false; + let syncing = false; + let networkSyncing = false; + let poolSyncing = false; if (!isReady) { - _syncing = true; - _networkSyncing = true; - _poolsSyncing = true; + syncing = true; + networkSyncing = true; + poolSyncing = true; } // staking metrics have synced - if (staking.lastReward === new BN(0)) { - _syncing = true; - _networkSyncing = true; - _poolsSyncing = true; + if (staking.lastReward === new BigNumber(0)) { + syncing = true; + networkSyncing = true; + poolSyncing = true; } // era has synced from Network - if (metrics.activeEra.index === 0) { - _syncing = true; - _networkSyncing = true; - _poolsSyncing = true; + if (activeEra.index.isZero()) { + syncing = true; + networkSyncing = true; + poolSyncing = true; } // all extension accounts have been synced const extensionAccounts = connectAccounts.filter( (a: ImportedAccount) => a.source !== 'external' ); - if (accounts.length < extensionAccounts.length) { - _syncing = true; - _networkSyncing = true; - _poolsSyncing = true; + if (balances.length < extensionAccounts.length) { + syncing = true; + networkSyncing = true; + poolSyncing = true; } - setNetworkSyncing(_networkSyncing); + setIsNetworkSyncing(networkSyncing); // active pools have been synced - if (activePoolsSynced !== Sync.Synced) { - _syncing = true; - _poolsSyncing = true; + if (activePoolsSynced !== 'synced') { + syncing = true; + poolSyncing = true; } - setPoolsSyncing(_poolsSyncing); + setIsPoolSyncing(poolSyncing); // eraStakers total active nominators has synced - if (!eraStakers.totalActiveNominators) { - _syncing = true; - } - - setIsSyncing(_syncing); - }, [isReady, staking, metrics, accounts, eraStakers, activePoolsSynced]); - - const setSideMenu = (v: number) => { - setSideMenuOpen(v); - }; - - /* - * Generates the default setup objects or the currently - * connected accounts. - */ - const setupDefault = () => { - // generate setup objects from connected accounts - const _setup = connectAccounts.map((item) => { - const localStakeSetup = localStorage.getItem( - `${network.name.toLowerCase()}_stake_setup_${item.address}` - ); - const localPoolSetup = localStorage.getItem( - `${network.name.toLowerCase()}_pool_setup_${item.address}` - ); - const stakeProgress = - localStakeSetup !== null - ? JSON.parse(localStakeSetup) - : defaults.defaultStakeSetup; - - const poolProgress = - localPoolSetup !== null - ? JSON.parse(localPoolSetup) - : defaults.defaultPoolSetup; - - return { - address: item.address, - progress: { - stake: stakeProgress, - pool: poolProgress, - }, - }; - }); - return _setup; - }; - - /* - * Gets the stake setup progress for a connected account. - */ - const getSetupProgress = (type: SetupType, address: MaybeAccount) => { - const _setup = setupRef.current.find((s: any) => s.address === address); - - if (_setup === undefined) { - return type === SetupType.Stake - ? defaults.defaultStakeSetup - : defaults.defaultPoolSetup; - } - return _setup.progress[type]; - }; - - /* - * Gets the stake setup progress as a percentage for an address. - */ - const getStakeSetupProgressPercent = (address: MaybeAccount) => { - if (!address) return 0; - const setupProgress = getSetupProgress(SetupType.Stake, address); - - const p = 25; - let progress = 0; - if (setupProgress.bond > 0) progress += p; - if (setupProgress.controller !== null) progress += p; - if (setupProgress.nominations.length) progress += p; - if (setupProgress.payee !== null) progress += p - 1; - return progress; - }; + if (!eraStakers.totalActiveNominators) syncing = true; - /* - * Gets the stake setup progress as a percentage for an address. - */ - const getPoolSetupProgressPercent = (address: MaybeAccount) => { - if (!address) return 0; - const setupProgress = getSetupProgress(SetupType.Pool, address); - - const p = 25; - let progress = 0; - if (setupProgress.metadata !== '') progress += p; - if (setupProgress.bond > 0) progress += p; - if (setupProgress.nominations.length) progress += p; - if (setupProgress.roles !== null) progress += p - 1; - return progress; - }; - - /* - * Sets stake setup progress for an address. - * Updates localStorage followed by app state. - */ - const setActiveAccountSetup = (type: SetupType, progress: any) => { - if (!activeAccount) return; - - localStorage.setItem( - `${network.name.toLowerCase()}_${type}_setup_${activeAccount}`, - JSON.stringify(progress) - ); - - const setupUpdated = setupRef.current.map((obj: any) => - obj.address === activeAccount - ? { - ...obj, - progress: { - ...obj.progress, - [type]: progress, - }, - } - : obj - ); - setStateWithRef(setupUpdated, setSetup, setupRef); - }; - - /* - * Sets active setup section for an address - */ - const setActiveAccountSetupSection = (type: SetupType, section: number) => { - if (!activeAccount) return; - - // get current progress - const _accountSetup = [...setupRef.current].find( - (item) => item.address === activeAccount - ); - - // abort if setup does not exist - if (_accountSetup === null) { - return; - } - - // amend section - _accountSetup.progress[type].section = section; - - // update context setup - const _setup = setupRef.current.map((obj: any) => - obj.address === activeAccount ? _accountSetup : obj - ); - - // update local storage - localStorage.setItem( - `${network.name.toLowerCase()}_${type}_setup_${activeAccount}`, - JSON.stringify(_accountSetup.progress[type]) - ); - - // update context - setStateWithRef(_setup, setSetup, setupRef); - }; - - /* - * Service toggling - */ - const toggleService = (key: string) => { - let _services = [...services]; - const found = _services.find((item) => item === key); - - if (found) { - _services = _services.filter((_s) => _s !== key); - } else { - _services.push(key); - } - - localStorage.setItem('services', JSON.stringify(_services)); - setStateWithRef(_services, setServices, servicesRef); - }; - - const getServices = () => { - return servicesRef.current; - }; - - const [containerRefs, _setContainerRefs] = useState({}); - const setContainerRefs = (v: any) => { - _setContainerRefs(v); - }; + setIsSyncing(syncing); + }, [isReady, staking, metrics, balances, eraStakers, activePoolsSynced]); return ( <UIContext.Provider value={{ setSideMenu, setUserSideMenuMinimised, - toggleService, - getSetupProgress, - getStakeSetupProgressPercent, - getPoolSetupProgressPercent, - setActiveAccountSetup, - setActiveAccountSetupSection, - getServices, - setOnNominatorSetup, - setOnPoolSetup, setContainerRefs, sideMenuOpen, - userSideMenuMinimised: userSideMenuMinimisedRef.current, sideMenuMinimised, - services: servicesRef.current, - onNominatorSetup, - onPoolSetup, isSyncing, - networkSyncing, - poolsSyncing, + isNetworkSyncing, + isPoolSyncing, containerRefs, + isBraveBrowser, + userSideMenuMinimised: userSideMenuMinimisedRef.current, }} > {children} </UIContext.Provider> ); }; + +export const UIContext = React.createContext<UIContextInterface>( + defaults.defaultUIContext +); + +export const useUi = () => React.useContext(UIContext); diff --git a/src/contexts/UI/types.ts b/src/contexts/UI/types.ts index 5f95d3e4a3..300daa25b9 100644 --- a/src/contexts/UI/types.ts +++ b/src/contexts/UI/types.ts @@ -1,34 +1,16 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { MaybeAccount } from 'types'; - -export enum SetupType { - Pool = 'pool', - Stake = 'stake', -} +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only export interface UIContextInterface { - setSideMenu: (v: number) => void; - setUserSideMenuMinimised: (v: number) => void; - toggleService: (k: string) => void; - getSetupProgress: (t: SetupType, a: MaybeAccount) => any; - getStakeSetupProgressPercent: (a: MaybeAccount) => number; - getPoolSetupProgressPercent: (a: MaybeAccount) => number; - setActiveAccountSetup: (t: SetupType, p: any) => void; - setActiveAccountSetupSection: (t: SetupType, s: number) => void; - getServices: () => string[]; - setOnNominatorSetup: (v: number) => void; - setOnPoolSetup: (v: number) => void; + setSideMenu: (v: boolean) => void; + setUserSideMenuMinimised: (v: boolean) => void; setContainerRefs: (v: any) => void; - sideMenuOpen: number; - userSideMenuMinimised: number; - sideMenuMinimised: number; - services: string[]; - onNominatorSetup: number; - onPoolSetup: number; - isSyncing: boolean; - networkSyncing: boolean; - poolsSyncing: boolean; + sideMenuOpen: boolean; + userSideMenuMinimised: boolean; + sideMenuMinimised: boolean; containerRefs: any; + isSyncing: boolean; + isNetworkSyncing: boolean; + isPoolSyncing: boolean; + isBraveBrowser: boolean; } diff --git a/src/contexts/Validators/FavoriteValidators/defaults.ts b/src/contexts/Validators/FavoriteValidators/defaults.ts new file mode 100644 index 0000000000..5559364cec --- /dev/null +++ b/src/contexts/Validators/FavoriteValidators/defaults.ts @@ -0,0 +1,20 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only +/* eslint-disable @typescript-eslint/no-unused-vars */ + +import BigNumber from 'bignumber.js'; +import type { FavoriteValidatorsContextInterface } from '../types'; + +export const defaultValidatorsData = { + entries: [], + notFullCommissionCount: 0, + totalNonAllCommission: new BigNumber(0), +}; + +export const defaultFavoriteValidatorsContext: FavoriteValidatorsContextInterface = + { + addFavorite: (a) => {}, + removeFavorite: (a) => {}, + favorites: [], + favoritesList: null, + }; diff --git a/src/contexts/Validators/FavoriteValidators/index.tsx b/src/contexts/Validators/FavoriteValidators/index.tsx new file mode 100644 index 0000000000..1884fdd06f --- /dev/null +++ b/src/contexts/Validators/FavoriteValidators/index.tsx @@ -0,0 +1,93 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import React, { useState } from 'react'; +import { useEffectIgnoreInitial } from '@polkadot-cloud/react/hooks'; +import { useNetwork } from 'contexts/Network'; +import { useApi } from 'contexts/Api'; +import type { Validator, FavoriteValidatorsContextInterface } from '../types'; +import { getLocalFavorites } from '../Utils'; +import { defaultFavoriteValidatorsContext } from './defaults'; +import { useValidators } from '../ValidatorEntries'; + +export const FavoriteValidatorsProvider = ({ + children, +}: { + children: React.ReactNode; +}) => { + const { isReady } = useApi(); + const { + networkData: { name }, + network, + } = useNetwork(); + const { fetchValidatorPrefs } = useValidators(); + + // Stores the user's favorite validators. + const [favorites, setFavorites] = useState<string[]>(getLocalFavorites(name)); + + // Stores the user's favorites validators as list. + const [favoritesList, setFavoritesList] = useState<Validator[] | null>(null); + + const fetchFavoriteList = async () => { + // fetch preferences + const favoritesWithPrefs = await fetchValidatorPrefs( + [...favorites].map((address) => ({ + address, + })) + ); + setFavoritesList(favoritesWithPrefs || []); + }; + + // Adds a favorite validator. + const addFavorite = (address: string) => { + const newFavorites: any = Object.assign(favorites); + if (!newFavorites.includes(address)) { + newFavorites.push(address); + } + + localStorage.setItem(`${network}_favorites`, JSON.stringify(newFavorites)); + setFavorites([...newFavorites]); + }; + + // Removes a favorite validator if they exist. + const removeFavorite = (address: string) => { + const newFavorites = Object.assign(favorites).filter( + (validator: string) => validator !== address + ); + localStorage.setItem(`${network}_favorites`, JSON.stringify(newFavorites)); + setFavorites([...newFavorites]); + }; + + // Re-fetch favorites on network change + useEffectIgnoreInitial(() => { + setFavorites(getLocalFavorites(name)); + }, [network]); + + // Fetch favorites in validator list format + useEffectIgnoreInitial(() => { + if (isReady) { + fetchFavoriteList(); + } + }, [isReady, favorites]); + + return ( + <FavoriteValidatorsContext.Provider + value={{ + addFavorite, + removeFavorite, + favorites, + favoritesList, + }} + > + {children} + </FavoriteValidatorsContext.Provider> + ); +}; + +export const FavoriteValidatorsContext = + React.createContext<FavoriteValidatorsContextInterface>( + defaultFavoriteValidatorsContext + ); + +export const useFavoriteValidators = () => + React.useContext(FavoriteValidatorsContext); diff --git a/src/contexts/Validators/Utils.ts b/src/contexts/Validators/Utils.ts new file mode 100644 index 0000000000..dd496f08e3 --- /dev/null +++ b/src/contexts/Validators/Utils.ts @@ -0,0 +1,139 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import BigNumber from 'bignumber.js'; +import type { LocalMeta } from 'contexts/FastUnstake/types'; +import type { + EraRewardPoints, + LocalValidatorEntriesData, + Validator, +} from 'contexts/Validators/types'; +import type { AnyJson, NetworkName } from 'types'; + +// Get favorite validators from local storage. +export const getLocalFavorites = (network: NetworkName) => { + const localFavourites = localStorage.getItem(`${network}_favorites`); + return localFavourites !== null + ? (JSON.parse(localFavourites) as string[]) + : []; +}; + +// Get local validator entries data for an era. +export const getLocalEraValidators = (network: NetworkName, era: string) => { + const data = localStorage.getItem(`${network}_validators`); + const current = data ? (JSON.parse(data) as LocalValidatorEntriesData) : null; + const currentEra = current?.era; + + if (currentEra && currentEra !== era) + localStorage.removeItem(`${network}_validators`); + + return currentEra === era ? current : null; +}; + +// Set local validator entries data for an era. +export const setLocalEraValidators = ( + network: NetworkName, + era: string, + entries: Validator[], + avgCommission: number +) => { + localStorage.setItem( + `${network}_validators`, + JSON.stringify({ + era, + entries, + avgCommission, + }) + ); +}; + +// Validate local exposure metadata, currently used for fast unstake only. +export const validateLocalExposure = ( + localMeta: AnyJson, + endEra: BigNumber +): LocalMeta | null => { + const localIsExposed = localMeta?.isExposed ?? null; + let localChecked = localMeta?.checked ?? null; + + // check types saved. + if (typeof localIsExposed !== 'boolean' || !Array.isArray(localChecked)) + return null; + + // check checked only contains numbers. + const checkedNumeric = localChecked.every((e) => typeof e === 'number'); + if (!checkedNumeric) return null; + + // remove any expired eras and sort highest first. + localChecked = localChecked + .filter((e: number) => endEra.isLessThan(e)) + .sort((a: number, b: number) => b - a); + + // if no remaining eras, invalid. + if (!localChecked.length) { + return null; + } + + // check if highest -> lowest are decremented, no missing eras. + let i = 0; + let prev = 0; + const noMissingEras = localChecked.every((e: number) => { + i++; + if (i === 1) { + prev = e; + return true; + } + const p = prev; + prev = e; + if (e === p - 1) return true; + return false; + }); + + if (!noMissingEras) return null; + + return { + isExposed: localIsExposed, + checked: localChecked, + }; +}; + +// Check if era reward points entry exists for an era. +export const hasLocalEraRewardPoints = (network: NetworkName, era: string) => { + const current = JSON.parse( + localStorage.getItem(`${network}_era_reward_points`) || '{}' + ); + return !!current?.[era]; +}; + +// Get local era reward points entry for an era. +export const getLocalEraRewardPoints = (network: NetworkName, era: string) => { + const current = JSON.parse( + localStorage.getItem(`${network}_era_reward_points`) || '{}' + ); + return current?.[era] || {}; +}; + +// Set local era reward points entry for an era. +export const setLocalEraRewardPoints = ( + network: NetworkName, + era: string, + eraRewardPoints: EraRewardPoints | null, + endEra: string +) => { + const current = JSON.parse( + localStorage.getItem(`${network}_era_reward_points`) || '{}' + ); + + const removeStaleEras = Object.fromEntries( + Object.entries(current || {}).filter(([k]: [string, unknown]) => + new BigNumber(k).isGreaterThanOrEqualTo(endEra) + ) + ); + + localStorage.setItem( + `${network}_era_reward_points`, + JSON.stringify({ + ...removeStaleEras, + [era]: eraRewardPoints, + }) + ); +}; diff --git a/src/contexts/Validators/ValidatorEntries/defaults.ts b/src/contexts/Validators/ValidatorEntries/defaults.ts new file mode 100644 index 0000000000..6624b6d499 --- /dev/null +++ b/src/contexts/Validators/ValidatorEntries/defaults.ts @@ -0,0 +1,35 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only +/* eslint-disable @typescript-eslint/no-unused-vars */ + +import BigNumber from 'bignumber.js'; +import type { EraPointsBoundaries, ValidatorsContextInterface } from '../types'; + +export const defaultValidatorsContext: ValidatorsContextInterface = { + fetchValidatorPrefs: async (a) => new Promise((resolve) => resolve(null)), + getValidatorPointsFromEras: (startEra, address) => ({}), + getNominated: (bondFor) => [], + injectValidatorListData: (entries) => [], + validators: [], + validatorIdentities: {}, + validatorSupers: {}, + avgCommission: 0, + sessionValidators: [], + sessionParaValidators: [], + nominated: null, + poolNominated: null, + validatorCommunity: [], + erasRewardPoints: {}, + validatorsFetched: 'unsynced', + eraPointsBoundaries: null, + validatorEraPointsHistory: {}, + erasRewardPointsFetched: 'unsynced', +}; + +export const defaultValidatorsData = { + entries: [], + notFullCommissionCount: 0, + totalNonAllCommission: new BigNumber(0), +}; + +export const defaultEraPointsBoundaries: EraPointsBoundaries = null; diff --git a/src/contexts/Validators/ValidatorEntries/index.tsx b/src/contexts/Validators/ValidatorEntries/index.tsx new file mode 100644 index 0000000000..5e30d33902 --- /dev/null +++ b/src/contexts/Validators/ValidatorEntries/index.tsx @@ -0,0 +1,603 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { greaterThanZero, rmCommas, shuffle } from '@polkadot-cloud/utils'; +import BigNumber from 'bignumber.js'; +import React, { useEffect, useRef, useState } from 'react'; +import { ValidatorCommunity } from 'config/validators'; +import type { AnyApi, AnyJson, BondFor, Fn, Sync } from 'types'; +import { useEffectIgnoreInitial } from '@polkadot-cloud/react/hooks'; +import { useBonded } from 'contexts/Bonded'; +import { useNetworkMetrics } from 'contexts/NetworkMetrics'; +import { useActivePools } from 'contexts/Pools/ActivePools'; +import { useNetwork } from 'contexts/Network'; +import { useApi } from 'contexts/Api'; +import { useActiveAccounts } from 'contexts/ActiveAccounts'; +import { MaxEraRewardPointsEras } from 'consts'; +import { useStaking } from 'contexts/Staking'; +import type { + EraPointsBoundaries, + ErasRewardPoints, + Identity, + Validator, + ValidatorAddresses, + ValidatorSuper, + ValidatorListEntry, + ValidatorsContextInterface, + ValidatorEraPointHistory, +} from '../types'; +import { + defaultValidatorsData, + defaultValidatorsContext, + defaultEraPointsBoundaries, +} from './defaults'; +import { getLocalEraValidators, setLocalEraValidators } from '../Utils'; + +export const ValidatorsProvider = ({ + children, +}: { + children: React.ReactNode; +}) => { + const { network } = useNetwork(); + const { isReady, api } = useApi(); + const { stakers } = useStaking().eraStakers; + const { poolNominations } = useActivePools(); + const { activeAccount } = useActiveAccounts(); + const { activeEra, metrics } = useNetworkMetrics(); + const { bondedAccounts, getAccountNominations } = useBonded(); + const { earliestStoredSession } = metrics; + + // Stores all validator entries. + const [validators, setValidators] = useState<Validator[]>([]); + + // Track whether the validator list has been fetched. + const [validatorsFetched, setValidatorsFetched] = useState<Sync>('unsynced'); + + // Store validator identity data. + const [validatorIdentities, setValidatorIdentities] = useState< + Record<string, Identity> + >({}); + + // Store validator super identity data. + const [validatorSupers, setValidatorSupers] = useState< + Record<string, ValidatorSuper> + >({}); + + // Stores the currently active validator set. + const [sessionValidators, setSessionValidators] = useState<string[]>([]); + + // Stores the currently active parachain validator set. + const [sessionParaValidators, setSessionParaValidators] = useState<string[]>( + [] + ); + + // Stores unsub object for para session. + const sessionParaUnsub = useRef<Fn>(); + + // Stores the average network commission rate. + const [avgCommission, setAvgCommission] = useState(0); + + // Stores the user's nominated validators as list + const [nominated, setNominated] = useState<Validator[] | null>(null); + + // Stores the nominated validators by the members pool's as list + const [poolNominated, setPoolNominated] = useState<Validator[] | null>(null); + + // Stores a randomised validator community dataset. + const [validatorCommunity] = useState([...shuffle(ValidatorCommunity)]); + + // Track whether the validator list has been fetched. + const [erasRewardPointsFetched, setErasRewawrdPointsFetched] = + useState<Sync>('unsynced'); + + // Store era reward points, keyed by era. + const [erasRewardPoints, setErasRewardPoints] = useState<ErasRewardPoints>( + {} + ); + + // Store validator era points history and metrics. + const [validatorEraPointsHistory, setValidatorEraPointsHistory] = useState< + Record<string, ValidatorEraPointHistory> + >({}); + + // Store era point high and low for `MaxEraPointsEras` eras. + const [eraPointsBoundaries, setEraPointsBoundaries] = + useState<EraPointsBoundaries>(defaultEraPointsBoundaries); + + // Processes reward points for a given era. + const processEraRewardPoints = (result: AnyJson, era: BigNumber) => { + if (erasRewardPoints[era.toString()]) + return erasRewardPoints[era.toString()]; + + return { + total: rmCommas(result.total), + individual: Object.fromEntries( + Object.entries(result.individual).map(([key, value]) => [ + key, + rmCommas(value as string), + ]) + ), + }; + }; + + // Get quartile data for validator performance data. + const getQuartile = (qIndex: number, total: number) => { + const q1 = Math.ceil(total * 0.25); + const q2 = Math.ceil(total * 0.5); + const q3 = Math.ceil(total * 0.75); + + if (qIndex <= q1) return 25; + if (qIndex <= q2) return 50; + if (qIndex <= q3) return 75; + return 100; + }; + + // Fetches era reward points for eligible eras. + const fetchErasRewardPoints = async () => { + if ( + activeEra.index.isZero() || + !api || + erasRewardPointsFetched !== 'unsynced' + ) + return; + + setErasRewawrdPointsFetched('syncing'); + + // start fetching from the current era. + let currentEra = BigNumber.max(activeEra.index.minus(1), 1); + const endEra = BigNumber.max( + currentEra.minus(MaxEraRewardPointsEras - 1), + 1 + ); + + // Introduce additional safeguard againt looping forever. + const totalEras = new BigNumber(MaxEraRewardPointsEras); + let erasProcessed = new BigNumber(0); + + // Iterate eras and process reward points. + const calls = []; + const eras = []; + do { + calls.push(api.query.staking.erasRewardPoints(currentEra.toString())); + eras.push(currentEra); + + currentEra = currentEra.minus(1); + erasProcessed = erasProcessed.plus(1); + } while ( + currentEra.isGreaterThanOrEqualTo(endEra) && + erasProcessed.isLessThan(totalEras) + ); + + // Make calls and format reward point results. + const newErasRewardPoints: ErasRewardPoints = {}; + let i = 0; + for (const result of await Promise.all(calls)) { + const formatted = processEraRewardPoints(result.toHuman(), eras[i]); + if (formatted) newErasRewardPoints[eras[i].toString()] = formatted; + i++; + } + + let newEraPointsHistory: Record<string, ValidatorEraPointHistory> = {}; + + // Calculate points per era and total points per era of each validator. + Object.entries(newErasRewardPoints).forEach(([era, { individual }]) => { + Object.entries(individual).forEach(([address, points]) => { + if (!newEraPointsHistory[address]) + newEraPointsHistory[address] = { + eras: {}, + totalPoints: new BigNumber(0), + }; + else { + newEraPointsHistory[address].eras[era] = new BigNumber(points); + newEraPointsHistory[address].totalPoints = + newEraPointsHistory[address].totalPoints.plus(points); + } + }); + }); + + // Iterate `newEraPointsHistory` and re-order the object based on its totalPoints, highest + // first. + newEraPointsHistory = Object.fromEntries( + Object.entries(newEraPointsHistory) + .sort( + ( + a: [string, ValidatorEraPointHistory], + b: [string, ValidatorEraPointHistory] + ) => a[1].totalPoints.minus(b[1].totalPoints).toNumber() + ) + .reverse() + ); + + const totalEntries = Object.entries(newEraPointsHistory).length; + let j = 0; + newEraPointsHistory = Object.fromEntries( + Object.entries(newEraPointsHistory).map(([k, v]) => { + j++; + return [k, { ...v, rank: j, quartile: getQuartile(j, totalEntries) }]; + }) + ); + + // Commit results to state. + setErasRewardPoints({ + ...newErasRewardPoints, + }); + setValidatorEraPointsHistory(newEraPointsHistory); + }; + + // Fetches the active account's nominees. + const fetchNominatedList = async () => { + if (!activeAccount) return; + + // format to list format + const targetsFormatted = getAccountNominations(activeAccount).map( + (item) => ({ address: item }) + ); + // fetch preferences + const nominationsWithPrefs = await fetchValidatorPrefs(targetsFormatted); + setNominated(nominationsWithPrefs || []); + }; + + // Fetches the active pool's nominees. + const fetchPoolNominatedList = async () => { + // get raw nominations list + let n = poolNominations.targets; + // format to list format + n = n.map((item: string) => ({ address: item })); + // fetch preferences + const nominationsWithPrefs = await fetchValidatorPrefs(n); + setPoolNominated(nominationsWithPrefs || []); + }; + + // Fetch validator entries and format the returning data. + const getValidatorEntries = async () => { + if (!isReady || !api) return defaultValidatorsData; + + const result = await api.query.staking.validators.entries(); + + const entries: Validator[] = []; + let notFullCommissionCount = 0; + let totalNonAllCommission = new BigNumber(0); + result.forEach(([a, p]: AnyApi) => { + const address = a.toHuman().pop(); + const prefs = p.toHuman(); + const commission = new BigNumber(prefs.commission.replace(/%/g, '')); + + if (!commission.isEqualTo(100)) + totalNonAllCommission = totalNonAllCommission.plus(commission); + else notFullCommissionCount++; + + entries.push({ + address, + prefs: { + commission: Number(commission.toFixed(2)), + blocked: prefs.blocked, + }, + }); + }); + + return { entries, notFullCommissionCount, totalNonAllCommission }; + }; + + // Fetches and formats the active validator set, and derives metrics from the result. + const fetchValidators = async () => { + if (!isReady || !api || validatorsFetched !== 'unsynced') return; + setValidatorsFetched('syncing'); + + // If local validator entries exist for the current era, store these values in state. Otherwise, + // fetch entries from API. + const localEraValidators = getLocalEraValidators( + network, + activeEra.index.toString() + ); + + // The validator entries for the current active era. + let validatorEntries: Validator[] = []; + // Average network commission for all non-100% commissioned validators. + let avg = 0; + + if (localEraValidators) { + validatorEntries = localEraValidators.entries; + avg = localEraValidators.avgCommission; + } else { + const { entries, notFullCommissionCount, totalNonAllCommission } = + await getValidatorEntries(); + + validatorEntries = entries; + avg = notFullCommissionCount + ? totalNonAllCommission + .dividedBy(notFullCommissionCount) + .decimalPlaces(2) + .toNumber() + : 0; + } + + // Set entries data for the era to local storage. + setLocalEraValidators( + network, + activeEra.index.toString(), + validatorEntries, + avg + ); + setAvgCommission(avg); + // Validators are shuffled before committed to state. + setValidators(shuffle(validatorEntries)); + + const addresses = validatorEntries.map(({ address }) => address); + const [identities, supers] = await Promise.all([ + fetchValidatorIdentities(addresses), + fetchValidatorSupers(addresses), + ]); + setValidatorIdentities(identities); + setValidatorSupers(supers); + setValidatorsFetched('synced'); + }; + + // Subscribe to active session validators. + const fetchSessionValidators = async () => { + if (!api || !isReady) return; + const sessionValidatorsRaw: AnyApi = await api.query.session.validators(); + setSessionValidators(sessionValidatorsRaw.toHuman()); + }; + + // Subscribe to active parachain validators. + const subscribeParachainValidators = async () => { + if (!api || !isReady) return; + const unsub: AnyApi = await api.query.paraSessionInfo.accountKeys( + earliestStoredSession.toString(), + (v: AnyApi) => { + setSessionParaValidators(v.toHuman()); + sessionParaUnsub.current = unsub; + } + ); + }; + + // Fetches prefs for a list of validators. + const fetchValidatorPrefs = async (addresses: ValidatorAddresses) => { + if (!addresses.length || !api) return null; + + const v: string[] = []; + for (const { address } of addresses) v.push(address); + const results = await api.query.staking.validators.multi(v); + + const formatted: Validator[] = []; + for (let i = 0; i < results.length; i++) { + const prefs: AnyApi = results[i].toHuman(); + formatted.push({ + address: v[i], + prefs: { + commission: prefs?.commission.replace(/%/g, '') ?? '0', + blocked: prefs.blocked, + }, + }); + } + return formatted; + }; + + // Fetches validator identities. + const fetchValidatorIdentities = async (addresses: string[]) => { + if (!api) return {}; + + const identities: AnyApi[] = ( + await api.query.identity.identityOf.multi(addresses) + ).map((identity) => identity.toHuman()); + + return Object.fromEntries( + Object.entries( + Object.fromEntries(identities.map((k, i) => [addresses[i], k])) + ).filter(([, v]) => v !== null) + ); + }; + + // Fetch validator super accounts and their identities. + const fetchValidatorSupers = async (addresses: string[]) => { + if (!api) return {}; + + const supersRaw: AnyApi[] = ( + await api.query.identity.superOf.multi(addresses) + ).map((superOf) => superOf.toHuman()); + + const supers = Object.fromEntries( + Object.entries( + Object.fromEntries( + supersRaw.map((k, i) => [ + addresses[i], + { + superOf: k, + }, + ]) + ) + ).filter(([, { superOf }]) => superOf !== null) + ); + + const superIdentities = ( + await api.query.identity.identityOf.multi( + Object.values(supers).map(({ superOf }) => superOf[0]) + ) + ).map((superIdentity) => superIdentity.toHuman()); + + const supersWithIdentity = Object.fromEntries( + Object.entries(supers).map(([k, v]: AnyApi, i) => [ + k, + { + ...v, + identity: superIdentities[i], + }, + ]) + ); + return supersWithIdentity; + }; + + // Gets era points for a validator + const getValidatorPointsFromEras = (startEra: BigNumber, address: string) => { + startEra = BigNumber.max(startEra, 1); + + // minus 1 from `MaxRewardPointsEras` to account for the current era. + const endEra = BigNumber.max(startEra.minus(MaxEraRewardPointsEras - 1), 1); + + const points: Record<string, BigNumber> = {}; + let currentEra = startEra; + do { + const eraPoints = erasRewardPoints[currentEra.toString()]; + if (eraPoints) { + const validatorPoints = eraPoints.individual[address]; + points[currentEra.toString()] = new BigNumber(validatorPoints || 0); + } else { + points[currentEra.toString()] = new BigNumber(0); + } + currentEra = currentEra.minus(1); + } while (currentEra.isGreaterThanOrEqualTo(endEra)); + + return points; + }; + + // Gets the highest and lowest (non-zero) era points earned `MaxEraRewardPointsEras` timeframe. + const calculateEraPointsBoundaries = () => { + let high: BigNumber | null = null; + let low: BigNumber | null = null; + + Object.entries(erasRewardPoints).forEach(([, { individual }]) => { + for (const [, points] of Object.entries(individual)) { + const p = new BigNumber(points); + + if (p.isGreaterThan(high || 0)) high = p; + if (low === null) low = p; + else if (p.isLessThan(low) && !p.isZero()) low = p; + } + }); + + setEraPointsBoundaries({ + high: high || new BigNumber(0), + low: low || new BigNumber(0), + }); + setErasRewawrdPointsFetched('synced'); + }; + + // Gets either `nominated` or `poolNominated` depending on bondFor, and injects the validator + // status into the entries. + const getNominated = (bondFor: BondFor) => + bondFor === 'nominator' ? nominated : poolNominated; + + // Inject status into validator entries. + const injectValidatorListData = ( + entries: Validator[] + ): ValidatorListEntry[] => { + const injected: ValidatorListEntry[] = + entries.map((entry) => { + const inEra = + stakers.find(({ address }) => address === entry.address) || false; + let totalStake = new BigNumber(0); + if (inEra) { + const { others, own } = inEra; + if (own) totalStake = totalStake.plus(own); + others.forEach(({ value }) => { + totalStake = totalStake.plus(value); + }); + } + return { + ...entry, + totalStake, + validatorStatus: inEra ? 'active' : 'waiting', + }; + }) || []; + return injected; + }; + + // Reset validator state data on network change. + useEffectIgnoreInitial(() => { + setValidatorsFetched('unsynced'); + setErasRewawrdPointsFetched('unsynced'); + setSessionValidators([]); + setSessionParaValidators([]); + setAvgCommission(0); + setValidators([]); + setValidatorIdentities({}); + setValidatorSupers({}); + setErasRewardPoints({}); + setEraPointsBoundaries(null); + setValidatorEraPointsHistory({}); + }, [network]); + + // Fetch validators and era reward points when fetched status changes. + useEffect(() => { + if (isReady && activeEra.index.isGreaterThan(0)) { + fetchValidators(); + fetchErasRewardPoints(); + } + }, [validatorsFetched, erasRewardPointsFetched, isReady, activeEra]); + + // Mark unsynced and fetch session validators when activeEra changes. + useEffectIgnoreInitial(() => { + if (isReady && activeEra.index.isGreaterThan(0)) { + if (erasRewardPointsFetched === 'synced') + setErasRewawrdPointsFetched('unsynced'); + + if (validatorsFetched === 'synced') setValidatorsFetched('unsynced'); + fetchSessionValidators(); + } + }, [isReady, activeEra]); + + // Fetch era points boundaries when `erasRewardPoints` ready. + useEffectIgnoreInitial(() => { + if (isReady && Object.values(erasRewardPoints).length) + calculateEraPointsBoundaries(); + }, [isReady, erasRewardPoints]); + + // Fetch parachain session validators when `earliestStoredSession` ready. + useEffectIgnoreInitial(() => { + if (isReady && greaterThanZero(earliestStoredSession)) + subscribeParachainValidators(); + }, [isReady, earliestStoredSession]); + + // Fetch active account's nominations in validator list format. + useEffectIgnoreInitial(() => { + if (isReady && activeAccount) { + fetchNominatedList(); + } + }, [isReady, activeAccount, bondedAccounts]); + + // Fetch active account's pool nominations in validator list format. + useEffectIgnoreInitial(() => { + if (isReady && poolNominations) fetchPoolNominatedList(); + }, [isReady, poolNominations]); + + // Unsubscribe on network change and component unmount. + useEffect(() => { + if (sessionParaValidators.length) sessionParaUnsub.current?.(); + + return () => { + sessionParaUnsub.current?.(); + }; + }, [network]); + + return ( + <ValidatorsContext.Provider + value={{ + fetchValidatorPrefs, + getValidatorPointsFromEras, + getNominated, + injectValidatorListData, + validators, + validatorIdentities, + validatorSupers, + avgCommission, + sessionValidators, + sessionParaValidators, + nominated, + poolNominated, + validatorCommunity, + erasRewardPoints, + validatorsFetched, + eraPointsBoundaries, + validatorEraPointsHistory, + erasRewardPointsFetched, + }} + > + {children} + </ValidatorsContext.Provider> + ); +}; + +export const ValidatorsContext = + React.createContext<ValidatorsContextInterface>(defaultValidatorsContext); + +export const useValidators = () => React.useContext(ValidatorsContext); diff --git a/src/contexts/Validators/defaults.ts b/src/contexts/Validators/defaults.ts deleted file mode 100644 index d3439fd101..0000000000 --- a/src/contexts/Validators/defaults.ts +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { ValidatorsContextInterface } from 'contexts/Validators/types'; - -export const sessionValidators = { - list: [], - unsub: null, -}; - -export const sessionParachainValidators = { - list: [], - unsub: null, -}; - -export const defaultValidatorsContext: ValidatorsContextInterface = { - // eslint-disable-next-line - fetchValidatorMetaBatch: (k, v, r) => {}, - // eslint-disable-next-line - removeValidatorMetaBatch: (k) => {}, - // eslint-disable-next-line - fetchValidatorPrefs: async (v) => null, - // eslint-disable-next-line - addFavorite: (a) => {}, - // eslint-disable-next-line - removeFavorite: (a) => {}, - validators: [], - avgCommission: 0, - meta: {}, - session: sessionValidators, - sessionParachain: [], - favorites: [], - nominated: null, - poolNominated: null, - favoritesList: null, - validatorCommunity: [], -}; diff --git a/src/contexts/Validators/index.tsx b/src/contexts/Validators/index.tsx deleted file mode 100644 index 79aa19d519..0000000000 --- a/src/contexts/Validators/index.tsx +++ /dev/null @@ -1,669 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import BN from 'bn.js'; -import { VALIDATOR_COMMUNITY } from 'config/validators'; -import { MinBondPrecision } from 'consts'; -import { - SessionParachainValidators, - SessionValidators, - Validator, - ValidatorAddresses, - ValidatorsContextInterface, -} from 'contexts/Validators/types'; -import React, { useEffect, useRef, useState } from 'react'; -import { AnyApi, AnyMetaBatch, Fn } from 'types'; -import { - planckBnToUnit, - removePercentage, - rmCommas, - setStateWithRef, - shuffle, - sleep, - toFixedIfNecessary, -} from 'Utils'; -import { useApi } from '../Api'; -import { useBalances } from '../Balances'; -import { useConnect } from '../Connect'; -import { useNetworkMetrics } from '../Network'; -import { useActivePools } from '../Pools/ActivePools'; -import * as defaults from './defaults'; - -export const ValidatorsContext = - React.createContext<ValidatorsContextInterface>( - defaults.defaultValidatorsContext - ); - -export const useValidators = () => React.useContext(ValidatorsContext); - -// wrapper component to provide components with context -export const ValidatorsProvider = ({ - children, -}: { - children: React.ReactNode; -}) => { - const { isReady, api, network, consts } = useApi(); - const { activeAccount } = useConnect(); - const { metrics } = useNetworkMetrics(); - const { accounts, getAccountNominations } = useBalances(); - const { poolNominations } = useActivePools(); - const { units } = network; - const { maxNominatorRewardedPerValidator } = consts; - const { activeEra } = metrics; - - // stores the total validator entries - const [validators, setValidators] = useState<Array<Validator>>([]); - - // track whether the validator list has been fetched yet - const [fetchedValidators, setFetchedValidators] = useState<number>(0); - - // stores the currently active validator set - const [sessionValidators, setSessionValidators] = useState<SessionValidators>( - defaults.sessionValidators - ); - - // stores the average network commission rate - const [avgCommission, setAvgCommission] = useState(0); - - // stores the currently active parachain validator set - const [sessionParachainValidators, setSessionParachainValidators] = - useState<SessionParachainValidators>(defaults.sessionParachainValidators); - - // stores the meta data batches for validator lists - const [validatorMetaBatches, setValidatorMetaBatch] = useState<AnyMetaBatch>( - {} - ); - const validatorMetaBatchesRef = useRef(validatorMetaBatches); - - // stores the meta batch subscriptions for validator lists - const [validatorSubs, setValidatorSubs] = useState<{ - [key: string]: Array<Fn>; - }>({}); - const validatorSubsRef = useRef(validatorSubs); - - // get favorites from local storage - const getFavorites = () => { - const _favorites = localStorage.getItem( - `${network.name.toLowerCase()}_favorites` - ); - return _favorites !== null ? JSON.parse(_favorites) : []; - }; - - // stores the user's favorite validators - const [favorites, setFavorites] = useState<string[]>(getFavorites()); - - // stores the user's nominated validators as list - const [nominated, setNominated] = useState<Array<Validator> | null>(null); - - // stores the nominated validators by the members pool's as list - const [poolNominated, setPoolNominated] = useState<Array<Validator> | null>( - null - ); - - // stores the user's favorites validators as list - const [favoritesList, setFavoritesList] = useState<Array<Validator> | null>( - null - ); - - // stores validator community - - const [validatorCommunity] = useState<any>([...shuffle(VALIDATOR_COMMUNITY)]); - - // reset validators list on network change - useEffect(() => { - setFetchedValidators(0); - setSessionValidators(defaults.sessionValidators); - setSessionParachainValidators(defaults.sessionParachainValidators); - removeValidatorMetaBatch('validators_browse'); - setAvgCommission(0); - setValidators([]); - }, [network]); - - // fetch validators and session validators when activeEra ready - useEffect(() => { - if (isReady) { - fetchValidators(); - subscribeSessionValidators(api); - } - - return () => { - // unsubscribe from any validator meta batches - Object.values(validatorSubsRef.current).map((batch: AnyMetaBatch) => { - return Object.entries(batch).map(([, v]: AnyApi) => { - return v(); - }); - }); - }; - }, [isReady, activeEra]); - - // fetch parachain session validators when earliestStoredSession ready - useEffect(() => { - if (isReady) { - subscribeParachainValidators(api); - } - }, [isReady]); - - // pre-populating validator meta batches. Needed for generating nominations - useEffect(() => { - if (validators.length > 0) { - fetchValidatorMetaBatch('validators_browse', validators, true); - } - }, [isReady, validators]); - - // fetch active account's nominations in validator list format - useEffect(() => { - if (isReady && activeAccount) { - fetchNominatedList(); - } - }, [isReady, activeAccount, accounts]); - - const fetchNominatedList = async () => { - if (!activeAccount) { - return; - } - - console.log(`Active account: ${activeAccount}`); - // get raw targets list - const targets = getAccountNominations(activeAccount); - - // format to list format - const targetsFormatted = targets.map((item: any) => { - return { address: item }; - }); - console.log(`Fetching validators from nominated list:`); - console.log(targetsFormatted); - // fetch preferences - const nominationsWithPrefs = await fetchValidatorPrefs(targetsFormatted); - - if (nominationsWithPrefs) { - setNominated(nominationsWithPrefs); - } else { - setNominated([]); - } - }; - - // fetch active account's pool nominations in validator list format - useEffect(() => { - if (isReady && poolNominations) { - fetchPoolNominatedList(); - } - }, [isReady, poolNominations]); - - const fetchPoolNominatedList = async () => { - // get raw nominations list - let n = poolNominations.targets; - console.log(`Raw nominations list:`); - console.log(n); - // format to list format - n = n.map((item: string) => { - return { address: item }; - }); - // fetch preferences - console.log(`Fetching validators from pool nominated list: ${n}`); - const nominationsWithPrefs = await fetchValidatorPrefs(n); - if (nominationsWithPrefs) { - setPoolNominated(nominationsWithPrefs); - } else { - setPoolNominated([]); - } - }; - - // re-fetch favorites on network change - useEffect(() => { - setFavorites(getFavorites()); - }, [network]); - - // fetch favorites in validator list format - useEffect(() => { - if (isReady) { - fetchFavoriteList(); - } - }, [isReady, favorites]); - - const fetchFavoriteList = async () => { - // format to list format - const _favorites = [...favorites].map((item: string) => { - return { address: item }; - }); - console.log(`Favourite validators:`); - console.log(_favorites); - // // fetch preferences - const favoritesWithPrefs = await fetchValidatorPrefs(_favorites); - if (favoritesWithPrefs) { - setFavoritesList(favoritesWithPrefs); - } else { - setFavoritesList([]); - } - }; - - /* - * Fetches the active validator set. - * Validator meta batches are derived from this initial list. - */ - const fetchValidators = async () => { - if (!isReady || !api) { - return; - } - - // return if fetching not started - if ([1, 2].includes(fetchedValidators)) { - return; - } - - setFetchedValidators(1); - - // fetch validator set - const v: Array<Validator> = []; - let totalNonAllCommission: BN = new BN(0); - const exposures = await api.query.staking.validators.entries(); - exposures.forEach(([_args, _prefs]: AnyApi) => { - const address = _args.args[0].toHuman(); - const prefs = _prefs.toHuman(); - - const _commission = removePercentage(prefs.commission); - if (_commission !== 100) { - totalNonAllCommission = totalNonAllCommission.add(new BN(_commission)); - } - - v.push({ - address, - prefs: { - commission: parseFloat(_commission.toFixed(2)), - blocked: prefs.blocked, - }, - }); - }); - - // get average network commission for all non-100% commissioned validators. - const nonCommissionCount = exposures.filter( - (e: AnyApi) => e.commission !== '100%' - ).length; - - const _avgCommission = nonCommissionCount - ? toFixedIfNecessary( - totalNonAllCommission.toNumber() / nonCommissionCount, - 2 - ) - : 0; - - setFetchedValidators(2); - setAvgCommission(_avgCommission); - // shuffle validators before setting them. - setValidators(shuffle(v)); - }; - - /* - * subscribe to active session - */ - const subscribeSessionValidators = async (_api: AnyApi) => { - if (isReady) { - const unsub = await _api.query.session.validators( - (_validators: AnyApi) => { - setSessionValidators({ - ...sessionValidators, - list: _validators.toHuman(), - unsub, - }); - } - ); - } - }; - - /* - * subscribe to active parachain validators - */ - const subscribeParachainValidators = async (_api: AnyApi) => { - if (isReady) { - const unsub = await _api.query.session.validators( - // earliestStoredSession.toString(), - (_validators: AnyApi) => { - setSessionParachainValidators({ - ...sessionParachainValidators, - list: _validators.toHuman(), - unsub, - }); - } - ); - } - }; - - /* - * fetches prefs for a list of validators - */ - const fetchValidatorPrefs = async (_validators: ValidatorAddresses) => { - if (!_validators.length || !api) { - return null; - } - - const v: string[] = []; - for (const _v of _validators) { - v.push(_v.address); - } - - const prefsAll = await api.query.staking.validators.multi(v); - - const validatorsWithPrefs = []; - let i = 0; - for (const _prefs of prefsAll) { - const prefs: AnyApi = _prefs.toHuman(); - - const commission = removePercentage(prefs?.commission ?? '0%'); - - validatorsWithPrefs.push({ - address: v[i], - prefs: { - commission, - blocked: prefs.blocked, - }, - }); - i++; - } - return validatorsWithPrefs; - }; - - /* - Fetches a new batch of subscribed validator metadata. Stores the returning - metadata alongside the unsubscribe function in state. - structure: - { - key: { - [ - { - addresses [], - identities: [], - } - ] - }, - }; - */ - const fetchValidatorMetaBatch = async ( - key: string, - v: AnyMetaBatch, - refetch = false - ) => { - if (!isReady || !api) { - return; - } - - if (!v.length) { - return; - } - - if (!refetch) { - // if already exists, do not re-fetch - if (validatorMetaBatchesRef.current[key] !== undefined) { - return; - } - } else { - // tidy up if existing batch exists - delete validatorMetaBatches[key]; - delete validatorMetaBatchesRef.current[key]; - - if (validatorSubsRef.current[key] !== undefined) { - for (const unsub of validatorSubsRef.current[key]) { - unsub(); - } - } - } - - const addresses = []; - for (const _v of v) { - addresses.push(_v.address); - } - - // store batch addresses - const batchesUpdated = Object.assign(validatorMetaBatchesRef.current); - batchesUpdated[key] = {}; - batchesUpdated[key].addresses = addresses; - setStateWithRef( - { ...batchesUpdated }, - setValidatorMetaBatch, - validatorMetaBatchesRef - ); - - const subscribeToIdentities = async (addr: AnyApi) => { - const unsub = await api.query.identity.identityOf.multi<AnyApi>( - addr, - (_identities) => { - const identities = []; - for (let i = 0; i < _identities.length; i++) { - identities.push(_identities[i].toHuman()); - } - const _batchesUpdated = Object.assign( - validatorMetaBatchesRef.current - ); - - // check if batch still exists before updating - if (_batchesUpdated[key]) { - _batchesUpdated[key].identities = identities; - setStateWithRef( - { ..._batchesUpdated }, - setValidatorMetaBatch, - validatorMetaBatchesRef - ); - } - } - ); - return unsub; - }; - - const subscribeToSuperIdentities = async (addr: AnyApi) => { - const unsub = await api.query.identity.superOf.multi<AnyApi>( - addr, - async (_supers) => { - // determine where supers exist - const supers: AnyApi = []; - const supersWithIdentity: AnyApi = []; - - for (let i = 0; i < _supers.length; i++) { - const _super = _supers[i].toHuman(); - supers.push(_super); - if (_super !== null) { - supersWithIdentity.push(i); - } - } - - // get supers one-off multi query - const query = supers - .filter((s: AnyApi) => s !== null) - .map((s: AnyApi) => s[0]); - - const temp = await api.query.identity.identityOf.multi<AnyApi>( - query, - (_identities) => { - for (let j = 0; j < _identities.length; j++) { - const _identity = _identities[j].toHuman(); - // inject identity into super array - supers[supersWithIdentity[j]].identity = _identity; - } - } - ); - temp(); - - const _batchesUpdated = Object.assign( - validatorMetaBatchesRef.current - ); - - // check if batch still exists before updating - if (_batchesUpdated[key]) { - _batchesUpdated[key].supers = supers; - setStateWithRef( - { ..._batchesUpdated }, - setValidatorMetaBatch, - validatorMetaBatchesRef - ); - } - } - ); - return unsub; - }; - - await Promise.all([ - subscribeToIdentities(addresses), - subscribeToSuperIdentities(addresses), - ]).then((unsubs: Array<Fn>) => { - addMetaBatchUnsubs(key, unsubs); - }); - - // intentional throttle to prevent slow render updates. - await sleep(250); - - // subscribe to validator nominators - const args: AnyApi = []; - - for (let i = 0; i < v.length; i++) { - args.push([activeEra.index, v[i].address]); - } - - const unsub3 = await api.query.staking.erasStakers.multi<AnyApi>( - args, - (_validators) => { - const stake = []; - - for (let _validator of _validators) { - _validator = _validator.toHuman(); - let others = _validator.others ?? []; - - // account for yourself being an additional nominator - const totalNominations = others.length + 1; - - // get lowest active stake for the validator - others = others.sort((a: AnyApi, b: AnyApi) => { - const x = new BN(rmCommas(a.value)); - const y = new BN(rmCommas(b.value)); - return x.sub(y); - }); - - const lowestActive = - others.length > 0 - ? toFixedIfNecessary( - planckBnToUnit(new BN(rmCommas(others[0].value)), units), - MinBondPrecision - ) - : 0; - - // get the lowest reward stake of the validator, which is - // the largest index - `maxNominatorRewardedPerValidator`, - // or the first index if does not exist. - const lowestRewardIndex = Math.max( - others.length - maxNominatorRewardedPerValidator, - 0 - ); - - const lowestReward = - others.length > 0 - ? toFixedIfNecessary( - planckBnToUnit( - new BN(rmCommas(others[lowestRewardIndex]?.value)), - units - ), - MinBondPrecision - ) - : 0; - - stake.push({ - total: _validator.total, - own: _validator.own, - total_nominations: totalNominations, - lowest: lowestActive, - lowestReward, - }); - } - - // commit update - const _batchesUpdated = Object.assign(validatorMetaBatchesRef.current); - - // check if batch still exists before updating - if (_batchesUpdated[key]) { - _batchesUpdated[key].stake = stake; - setStateWithRef( - { ..._batchesUpdated }, - setValidatorMetaBatch, - validatorMetaBatchesRef - ); - } - } - ); - - addMetaBatchUnsubs(key, [unsub3]); - }; - - /* - * Helper function to add mataBatch unsubs by key. - */ - const addMetaBatchUnsubs = (key: string, unsubs: Array<Fn>) => { - const _unsubs = validatorSubsRef.current; - const _keyUnsubs = _unsubs[key] ?? []; - - _keyUnsubs.push(...unsubs); - _unsubs[key] = _keyUnsubs; - setStateWithRef(_unsubs, setValidatorSubs, validatorSubsRef); - }; - - const removeValidatorMetaBatch = (key: string) => { - if (validatorSubsRef.current[key] !== undefined) { - // ubsubscribe from updates - for (const unsub of validatorSubsRef.current[key]) { - unsub(); - } - // wipe data - delete validatorMetaBatches[key]; - delete validatorMetaBatchesRef.current[key]; - } - }; - - /* - * Adds a favorite validator. - */ - const addFavorite = (address: string) => { - const _favorites: any = Object.assign(favorites); - if (!_favorites.includes(address)) { - _favorites.push(address); - } - - localStorage.setItem( - `${network.name.toLowerCase()}_favorites`, - JSON.stringify(_favorites) - ); - setFavorites([..._favorites]); - }; - - /* - * Removes a favorite validator if they exist. - */ - const removeFavorite = (address: string) => { - let _favorites = Object.assign(favorites); - _favorites = _favorites.filter( - (validator: string) => validator !== address - ); - localStorage.setItem( - `${network.name.toLowerCase()}_favorites`, - JSON.stringify(_favorites) - ); - setFavorites([..._favorites]); - }; - - return ( - <ValidatorsContext.Provider - value={{ - fetchValidatorMetaBatch, - removeValidatorMetaBatch, - fetchValidatorPrefs, - addFavorite, - removeFavorite, - validators, - avgCommission, - meta: validatorMetaBatchesRef.current, - session: sessionValidators, - sessionParachain: sessionParachainValidators.list, - favorites, - nominated, - poolNominated, - favoritesList, - validatorCommunity, - }} - > - {children} - </ValidatorsContext.Provider> - ); -}; diff --git a/src/contexts/Validators/types.ts b/src/contexts/Validators/types.ts index 3099a1723e..af772b6b58 100644 --- a/src/contexts/Validators/types.ts +++ b/src/contexts/Validators/types.ts @@ -1,42 +1,55 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import { AnyMetaBatch } from 'types'; +import type BigNumber from 'bignumber.js'; +import type { AnyJson, BondFor, Sync } from 'types'; export interface ValidatorsContextInterface { - fetchValidatorMetaBatch: (k: string, v: [], r?: boolean) => void; - removeValidatorMetaBatch: (k: string) => void; - fetchValidatorPrefs: ( - v: ValidatorAddresses - ) => Promise<Array<Validator> | null>; + fetchValidatorPrefs: (a: ValidatorAddresses) => Promise<Validator[] | null>; + getValidatorPointsFromEras: ( + startEra: BigNumber, + address: string + ) => Record<string, BigNumber>; + getNominated: (bondFor: BondFor) => Validator[] | null; + injectValidatorListData: (entries: Validator[]) => ValidatorListEntry[]; + validators: Validator[]; + validatorIdentities: Record<string, Identity>; + validatorSupers: Record<string, AnyJson>; + avgCommission: number; + sessionValidators: string[]; + sessionParaValidators: string[]; + nominated: Validator[] | null; + poolNominated: Validator[] | null; + validatorCommunity: any[]; + erasRewardPoints: ErasRewardPoints; + validatorsFetched: Sync; + eraPointsBoundaries: EraPointsBoundaries; + validatorEraPointsHistory: Record<string, ValidatorEraPointHistory>; + erasRewardPointsFetched: Sync; +} + +export interface FavoriteValidatorsContextInterface { addFavorite: (a: string) => void; removeFavorite: (a: string) => void; - validators: Array<Validator>; - avgCommission: number; - meta: AnyMetaBatch; - session: SessionValidators; - sessionParachain: string[]; favorites: string[]; - nominated: Array<Validator> | null; - poolNominated: Array<Validator> | null; - favoritesList: Array<Validator> | null; - validatorCommunity: Array<any>; + favoritesList: Validator[] | null; } -export type ValidatorAddresses = Array<{ - address: string; -}>; - -export interface SessionValidators { - list: string[]; - unsub: { (): void } | null; +export interface Identity { + deposit: string; + info: AnyJson; + judgements: AnyJson[]; } -export interface SessionParachainValidators { - list: string[]; - unsub: { (): void } | null; +export interface ValidatorSuper { + identity: Identity; + superOf: [string, { Raw: string }]; } +export type ValidatorAddresses = { + address: string; +}[]; + export interface Validator { address: string; prefs: ValidatorPrefs; @@ -46,3 +59,33 @@ export interface ValidatorPrefs { commission: number; blocked: boolean; } + +export interface LocalValidatorEntriesData { + avgCommission: number; + era: string; + entries: Validator[]; +} + +export type ErasRewardPoints = Record<string, EraRewardPoints>; + +export interface EraRewardPoints { + total: string; + individual: Record<string, string>; +} + +export type EraPointsBoundaries = { + high: BigNumber; + low: BigNumber; +} | null; + +export type ValidatorListEntry = Validator & { + validatorStatus: 'waiting' | 'active'; + totalStake: BigNumber; +}; + +export interface ValidatorEraPointHistory { + eras: Record<string, BigNumber>; + totalPoints: BigNumber; + rank?: number; + quartile?: number; +} diff --git a/src/img/appIcons/kusama.svg b/src/img/appIcons/kusama.svg new file mode 100644 index 0000000000..05eaa63921 --- /dev/null +++ b/src/img/appIcons/kusama.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 300.02 240.34"><path d="M0,149.38H60.29V120.6h60.29V90.52H150V59.9H209.1V29.75h31.11V0h29.71V30.71H300V60.86H269.87V120.6H240.29v29.78H209.58v30.19H180v30.14H150V180.13H0Z"/><path d="M180,210.71h29.62v29.63H180Z"/></svg> \ No newline at end of file diff --git a/src/img/appIcons/polkadot.svg b/src/img/appIcons/polkadot.svg new file mode 100644 index 0000000000..564dfcaae1 --- /dev/null +++ b/src/img/appIcons/polkadot.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 87.86 109.25"><path d="M87.86,44.13H65.05v-22H87.86ZM55.66,0H32.85V22H55.66ZM22.81,21.73H0v22H22.81ZM55.66,87.22H32.85v22H55.66Zm-32.85-22H0v22H22.81Zm65.05,0H65.05v22H87.86Z"/></svg> \ No newline at end of file diff --git a/src/img/language.svg b/src/img/language.svg new file mode 100644 index 0000000000..5b8b648342 --- /dev/null +++ b/src/img/language.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><g id="design"><path class="primary" d="M22 19c0 .55-.45 1-1 1-1.47 0-2.87-.46-4.01-1.26-1.06.79-2.39 1.25-3.86 1.25-.56 0-1-.45-1-1 0-.56.44-1 1-1 .9 0 1.71-.24 2.39-.66-.34-.43-.62-.9-.84-1.41-.22-.5.02-1.09.52-1.31.51-.23 1.1.01 1.32.51.12.27.26.52.42.76a4.8 4.8 0 00.62-1.88H13c-.55 0-1-.45-1-1s.45-1 1-1h2.87v-1c0-.55.44-1 1-1 .55 0 1 .45 1 1v1h3.11c.56 0 1 .45 1 1s-.44 1-1 1h-1.4a6.682 6.682 0 01-1.18 3.28c.76.46 1.66.72 2.6.72.55 0 1 .45 1 1zM6.23 4h-.46C3.69 4 2 5.72 2 7.84V14c0 .55.45 1 1 1s1-.45 1-1v-3h4v3c0 .55.45 1 1 1s1-.45 1-1V7.84C10 5.72 8.31 4 6.23 4zM4 9V7.84C4 6.83 4.79 6 5.77 6h.46C7.21 6 8 6.83 8 7.84V9H4z"/></g></svg> \ No newline at end of file diff --git a/src/img/ledgerLogo.svg b/src/img/ledgerLogo.svg new file mode 100644 index 0000000000..32b2408ed8 --- /dev/null +++ b/src/img/ledgerLogo.svg @@ -0,0 +1,3 @@ +<svg width="383" height="128" viewBox="0 0 383 128" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path d="M327.629 119.94V127.998H382.998V91.6548H374.931V119.94H327.629ZM327.629 0V8.05844H374.931V36.3452H382.998V0H327.629ZM299.075 62.3411V43.6158H311.731C317.901 43.6158 320.116 45.6696 320.116 51.2803V54.5982C320.116 60.3657 317.98 62.3411 311.731 62.3411H299.075ZM319.165 65.6589C324.939 64.1578 328.972 58.7842 328.972 52.3856C328.972 48.3564 327.391 44.7211 324.385 41.7972C320.589 38.1619 315.525 36.3452 308.961 36.3452H291.164V91.6529H299.075V69.6097H310.94C317.03 69.6097 319.483 72.1378 319.483 78.4599V91.6548H327.55V79.7239C327.55 71.0325 325.494 67.7147 319.165 66.7662V65.6589ZM252.565 67.4756H276.928V60.207H252.565V43.6139H279.3V36.3452H244.496V91.6529H280.487V84.3842H252.565V67.4756ZM226.065 70.3995V74.1916C226.065 82.1717 223.138 84.78 215.783 84.78H214.043C206.685 84.78 203.126 82.4088 203.126 71.4264V56.5717C203.126 45.5109 206.844 43.2181 214.2 43.2181H215.781C222.979 43.2181 225.273 45.9048 225.351 53.3322H234.052C233.262 42.4283 225.985 35.5555 215.069 35.5555C209.77 35.5555 205.34 37.2153 202.018 40.3745C197.035 45.0367 194.266 52.9383 194.266 63.9991C194.266 74.6659 196.64 82.5675 201.543 87.4649C204.865 90.7044 209.454 92.4426 213.962 92.4426C218.708 92.4426 223.06 90.5456 225.273 86.438H226.379V91.6529H233.656V63.1309H212.22V70.3995H226.065ZM156.301 43.6139H164.924C173.072 43.6139 177.502 45.6677 177.502 56.7304V71.2677C177.502 82.3285 173.072 84.3842 164.924 84.3842H156.301V43.6139ZM165.634 91.6548C180.743 91.6548 186.358 80.1982 186.358 64.001C186.358 47.5666 180.346 36.3471 165.475 36.3471H148.389V91.6548H165.634ZM110.186 67.4756H134.549V60.207H110.186V43.6139H136.921V36.3452H102.116V91.6529H138.108V84.3842H110.186V67.4756ZM63.5175 36.3452H55.45V91.6529H91.8359V84.3842H63.5175V36.3452ZM0 91.6548V128H55.3696V119.94H8.06747V91.6548H0ZM0 0V36.3452H8.06747V8.05844H55.3696V0H0Z" fill="black"/> +</svg> diff --git a/src/img/polkadot_icon.svg b/src/img/polkadot_icon.svg index 8759f045e6..ac9de52344 100644 --- a/src/img/polkadot_icon.svg +++ b/src/img/polkadot_icon.svg @@ -1,13 +1 @@ -<?xml version="1.0" encoding="utf-8"?> -<svg version="1.1" id="Logo" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" - viewBox="0 0 1326.1 1410.3" style="enable-background:new 0 0 1326.1 1410.3;" xml:space="preserve"> -<style type="text/css"> - .st0{fill:#E6007A;} -</style> -<ellipse class="st0" cx="663" cy="147.9" rx="254.3" ry="147.9"/> -<ellipse class="st0" cx="663" cy="1262.3" rx="254.3" ry="147.9"/> -<ellipse transform="matrix(0.5 -0.866 0.866 0.5 -279.1512 369.5916)" class="st0" cx="180.5" cy="426.5" rx="254.3" ry="148"/> -<ellipse transform="matrix(0.5 -0.866 0.866 0.5 -279.1552 1483.9517)" class="st0" cx="1145.6" cy="983.7" rx="254.3" ry="147.9"/> -<ellipse transform="matrix(0.866 -0.5 0.5 0.866 -467.6798 222.044)" class="st0" cx="180.5" cy="983.7" rx="148" ry="254.3"/> -<ellipse transform="matrix(0.866 -0.5 0.5 0.866 -59.8007 629.9254)" class="st0" cx="1145.6" cy="426.6" rx="147.9" ry="254.3"/> -</svg> +<svg version="1.1" id="Logo" xmlns="http://www.w3.org/2000/svg" x="0" y="0" viewBox="0 0 1326.1 1410.3" xml:space="preserve"><style>.ncp{fill:#e6007a}</style><ellipse class="ncp" cx="663" cy="147.9" rx="254.3" ry="147.9"/><ellipse class="ncp" cx="663" cy="1262.3" rx="254.3" ry="147.9"/><ellipse transform="rotate(-60 180.499 426.56)" class="ncp" cx="180.5" cy="426.5" rx="254.3" ry="148"/><ellipse transform="rotate(-60 1145.575 983.768)" class="ncp" cx="1145.6" cy="983.7" rx="254.3" ry="147.9"/><ellipse transform="rotate(-30 180.45 983.72)" class="ncp" cx="180.5" cy="983.7" rx="148" ry="254.3"/><ellipse transform="rotate(-30 1145.522 426.601)" class="ncp" cx="1145.6" cy="426.6" rx="147.9" ry="254.3"/></svg> diff --git a/src/img/polkadot_logo.svg b/src/img/polkadot_logo.svg index a9fef5aac2..5aa7d80e6e 100755 --- a/src/img/polkadot_logo.svg +++ b/src/img/polkadot_logo.svg @@ -1 +1 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 6593.8 1410.3"><path class="primary" d="M2047.8 210.4c-77.2 0-151.6 9.7-181.9 17.9-44.1 12.4-55.1 31.7-63.4 71.7l-175 807.5c-2.8 16.5-5.5 30.3-5.5 44.1 0 40 26.2 66.1 68.9 66.1 46.9 0 66.2-30.3 75.8-77.2l49.6-230.1c34.4 4.1 79.9 8.3 144.7 8.3 285.3 0 463-154.3 463-387.2 0-204-142-321.1-376.2-321.1zm-93.7 576c-44.1 0-78.6-1.4-110.2-5.5l92.3-428.6c28.9-4.1 71.7-9.7 115.8-9.7 151.6 0 228.8 71.7 228.8 190.2-.1 151.6-110.4 253.6-326.7 253.6zm4583.2 165.3c-23.4 0-38.6 13.8-67.5 45.5-51 52.4-82.7 81.3-122.6 81.3-35.8 0-55.1-28.9-55.1-75.8 0-26.2 5.5-57.9 12.4-92.3l51-239.8h153c48.2 0 77.2-27.6 77.2-77.2 0-27.6-17.9-48.2-59.2-48.2H6383l24.8-111.6c2.8-16.5 5.5-31.7 5.5-45.5 0-38.6-26.2-66.2-68.9-66.2-45.5 0-66.1 30.3-75.8 77.2l-31.7 146.1h-64.8c-49.6 0-78.5 27.6-78.5 77.2 0 27.6 19.3 48.2 60.6 48.2h56.5l-51 237c-5.5 26.2-12.4 68.9-12.4 113 0 111.6 57.9 192.9 181.9 192.9 71.7 0 135-35.8 183.3-78.6 46.9-41.3 81.3-92.3 81.3-125.4 0-31.6-24.8-57.8-56.5-57.8zm-3169.4-660c0-40-27.6-66.2-70.3-66.2-45.5 0-66.1 30.3-75.8 77.2l-172.2 800.6c-4.1 16.5-6.9 30.3-6.9 44.1 0 40 27.6 66.1 68.9 66.1 46.9 0 67.5-30.3 77.2-77.2L3361 335.8c2.8-16.6 6.9-30.3 6.9-44.1zm2459.7 239.7c-220.5 0-370.7 219.1-370.7 427.2 0 3.9.1 7.8.2 11.6-34.6 52.5-78.8 106.9-109.1 106.9-22 0-30.3-20.7-30.3-52.4 0-38.6 11-103.4 23.4-159.9l114.4-529.2c2.8-16.5 5.5-30.3 5.5-44.1 0-40-26.2-66.2-68.9-66.2-46.9 0-66.1 30.3-75.8 77.2l-62 286.6c-33.1-34.4-81.4-57.9-154.4-57.9-146.4 0-293.7 112.7-351.1 285.6-63.2 163.4-119.7 260.1-164.3 260.1-16.5 0-26.2-13.8-26.2-37.2 0-68.9 38.6-237 55.1-318.3 5.5-30.3 8.3-42.7 8.3-60.6 0-60.6-100.6-132.3-231.5-132.3-151.4 0-288.5 95.9-357.2 238.3-102.1 180.4-195.8 310.1-242.2 310.1-20.7 0-27.6-26.2-33.1-51L3723.4 836l210.8-172.2c19.3-16.5 44.1-40 44.1-70.3 0-37.2-24.8-62-62-62-27.6 0-51 16.5-74.4 35.8L3525 829.1l106.1-493.3c2.8-16.5 6.9-30.3 6.9-44.1 0-40-27.6-66.2-70.3-66.2-45.5 0-66.1 30.3-75.8 77.2l-172.2 800.6c-4.1 16.5-6.9 30.3-6.9 44.1 0 40 27.6 66.1 68.9 66.1 46.9 0 67.5-30.3 77.2-77.2L3481 1033l129.5-104.7 31.7 159.8c12.4 63.4 45.5 125.4 130.9 125.4 85 0 151.9-77.6 218.9-182.5 20.3 109.9 98.5 182.5 207 182.5 108.9 0 183.3-64.8 231.5-151.6v2.8c0 89.6 45.5 148.8 125.4 148.8 72.3 0 132.3-43.6 185.9-136.2 30.4 80.7 98.4 136.2 193 136.2 111.6 0 191.5-67.5 242.5-161.2v8.3c0 99.2 49.6 153 130.9 153 71.8 0 132.8-41.7 184.9-109.5 39.6 67.8 110.6 109.5 206.5 109.5 220.5 0 370.7-219.1 370.7-428.6-.1-147.5-89.7-253.6-242.7-253.6zM4453.8 811.2c-40 172.2-125.4 270.1-221.9 270.1-64.8 0-100.6-49.6-100.6-124 0-146.1 106.1-303.2 248-303.2 42.7 0 75.8 12.4 104.7 30.3l-30.2 126.8zm767.5-73c-44.1 202.6-144.7 343.1-254.9 343.1-60.6 0-96.5-46.9-96.5-124 0-147.4 102-300.4 238.4-300.4 52.4 0 92.3 17.9 121.3 41.3l-8.3 40zm494.7 343.1c-79.9 0-113-57.9-113-130.9 0-133.7 92.3-286.6 208.1-286.6 79.9 0 113 57.9 113 130.9 0 135-92.3 286.6-208.1 286.6zM2767.1 531.4c-220.5 0-370.7 219.1-370.7 427.2 0 148.8 89.6 254.9 242.5 254.9 220.5 0 370.7-219.1 370.7-428.6 0-147.4-89.6-253.5-242.5-253.5zm-111.6 549.9c-79.9 0-113-57.9-113-130.9 0-133.7 92.3-286.6 208.1-286.6 79.9 0 113 57.9 113 130.9-.1 135-92.4 286.6-208.1 286.6z"/><ellipse class="primary" cx="663" cy="147.9" rx="254.3" ry="147.9"/><ellipse class="primary" cx="663" cy="1262.3" rx="254.3" ry="147.9"/><ellipse class="primary" transform="rotate(-60 180.499 426.56)" cx="180.5" cy="426.5" rx="254.3" ry="148"/><ellipse class="primary" transform="rotate(-60 1145.575 983.768)" cx="1145.6" cy="983.7" rx="254.3" ry="147.9"/><ellipse class="primary" transform="rotate(-30 180.45 983.72)" cx="180.5" cy="983.7" rx="148" ry="254.3"/><ellipse class="primary" transform="rotate(-30 1145.522 426.601)" cx="1145.6" cy="426.6" rx="147.9" ry="254.3"/></svg> \ No newline at end of file +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 6593.8 1410.3"><style>.ncp{fill:#e6007a}</style><path class="primary" d="M2047.8 210.4c-77.2 0-151.6 9.7-181.9 17.9-44.1 12.4-55.1 31.7-63.4 71.7l-175 807.5c-2.8 16.5-5.5 30.3-5.5 44.1 0 40 26.2 66.1 68.9 66.1 46.9 0 66.2-30.3 75.8-77.2l49.6-230.1c34.4 4.1 79.9 8.3 144.7 8.3 285.3 0 463-154.3 463-387.2 0-204-142-321.1-376.2-321.1zm-93.7 576c-44.1 0-78.6-1.4-110.2-5.5l92.3-428.6c28.9-4.1 71.7-9.7 115.8-9.7 151.6 0 228.8 71.7 228.8 190.2-.1 151.6-110.4 253.6-326.7 253.6zm4583.2 165.3c-23.4 0-38.6 13.8-67.5 45.5-51 52.4-82.7 81.3-122.6 81.3-35.8 0-55.1-28.9-55.1-75.8 0-26.2 5.5-57.9 12.4-92.3l51-239.8h153c48.2 0 77.2-27.6 77.2-77.2 0-27.6-17.9-48.2-59.2-48.2H6383l24.8-111.6c2.8-16.5 5.5-31.7 5.5-45.5 0-38.6-26.2-66.2-68.9-66.2-45.5 0-66.1 30.3-75.8 77.2l-31.7 146.1h-64.8c-49.6 0-78.5 27.6-78.5 77.2 0 27.6 19.3 48.2 60.6 48.2h56.5l-51 237c-5.5 26.2-12.4 68.9-12.4 113 0 111.6 57.9 192.9 181.9 192.9 71.7 0 135-35.8 183.3-78.6 46.9-41.3 81.3-92.3 81.3-125.4 0-31.6-24.8-57.8-56.5-57.8zm-3169.4-660c0-40-27.6-66.2-70.3-66.2-45.5 0-66.1 30.3-75.8 77.2l-172.2 800.6c-4.1 16.5-6.9 30.3-6.9 44.1 0 40 27.6 66.1 68.9 66.1 46.9 0 67.5-30.3 77.2-77.2L3361 335.8c2.8-16.6 6.9-30.3 6.9-44.1zm2459.7 239.7c-220.5 0-370.7 219.1-370.7 427.2 0 3.9.1 7.8.2 11.6-34.6 52.5-78.8 106.9-109.1 106.9-22 0-30.3-20.7-30.3-52.4 0-38.6 11-103.4 23.4-159.9l114.4-529.2c2.8-16.5 5.5-30.3 5.5-44.1 0-40-26.2-66.2-68.9-66.2-46.9 0-66.1 30.3-75.8 77.2l-62 286.6c-33.1-34.4-81.4-57.9-154.4-57.9-146.4 0-293.7 112.7-351.1 285.6-63.2 163.4-119.7 260.1-164.3 260.1-16.5 0-26.2-13.8-26.2-37.2 0-68.9 38.6-237 55.1-318.3 5.5-30.3 8.3-42.7 8.3-60.6 0-60.6-100.6-132.3-231.5-132.3-151.4 0-288.5 95.9-357.2 238.3-102.1 180.4-195.8 310.1-242.2 310.1-20.7 0-27.6-26.2-33.1-51L3723.4 836l210.8-172.2c19.3-16.5 44.1-40 44.1-70.3 0-37.2-24.8-62-62-62-27.6 0-51 16.5-74.4 35.8L3525 829.1l106.1-493.3c2.8-16.5 6.9-30.3 6.9-44.1 0-40-27.6-66.2-70.3-66.2-45.5 0-66.1 30.3-75.8 77.2l-172.2 800.6c-4.1 16.5-6.9 30.3-6.9 44.1 0 40 27.6 66.1 68.9 66.1 46.9 0 67.5-30.3 77.2-77.2L3481 1033l129.5-104.7 31.7 159.8c12.4 63.4 45.5 125.4 130.9 125.4 85 0 151.9-77.6 218.9-182.5 20.3 109.9 98.5 182.5 207 182.5 108.9 0 183.3-64.8 231.5-151.6v2.8c0 89.6 45.5 148.8 125.4 148.8 72.3 0 132.3-43.6 185.9-136.2 30.4 80.7 98.4 136.2 193 136.2 111.6 0 191.5-67.5 242.5-161.2v8.3c0 99.2 49.6 153 130.9 153 71.8 0 132.8-41.7 184.9-109.5 39.6 67.8 110.6 109.5 206.5 109.5 220.5 0 370.7-219.1 370.7-428.6-.1-147.5-89.7-253.6-242.7-253.6zM4453.8 811.2c-40 172.2-125.4 270.1-221.9 270.1-64.8 0-100.6-49.6-100.6-124 0-146.1 106.1-303.2 248-303.2 42.7 0 75.8 12.4 104.7 30.3l-30.2 126.8zm767.5-73c-44.1 202.6-144.7 343.1-254.9 343.1-60.6 0-96.5-46.9-96.5-124 0-147.4 102-300.4 238.4-300.4 52.4 0 92.3 17.9 121.3 41.3l-8.3 40zm494.7 343.1c-79.9 0-113-57.9-113-130.9 0-133.7 92.3-286.6 208.1-286.6 79.9 0 113 57.9 113 130.9 0 135-92.3 286.6-208.1 286.6zM2767.1 531.4c-220.5 0-370.7 219.1-370.7 427.2 0 148.8 89.6 254.9 242.5 254.9 220.5 0 370.7-219.1 370.7-428.6 0-147.4-89.6-253.5-242.5-253.5zm-111.6 549.9c-79.9 0-113-57.9-113-130.9 0-133.7 92.3-286.6 208.1-286.6 79.9 0 113 57.9 113 130.9-.1 135-92.4 286.6-208.1 286.6z"/><ellipse class="ncp" cx="663" cy="147.9" rx="254.3" ry="147.9"/><ellipse class="ncp" cx="663" cy="1262.3" rx="254.3" ry="147.9"/><ellipse class="ncp" transform="rotate(-60 180.499 426.56)" cx="180.5" cy="426.5" rx="254.3" ry="148"/><ellipse class="ncp" transform="rotate(-60 1145.575 983.768)" cx="1145.6" cy="983.7" rx="254.3" ry="147.9"/><ellipse class="ncp" transform="rotate(-30 180.45 983.72)" cx="180.5" cy="983.7" rx="148" ry="254.3"/><ellipse class="ncp" transform="rotate(-30 1145.522 426.601)" cx="1145.6" cy="426.6" rx="147.9" ry="254.3"/></svg> diff --git a/src/img/walletConnect.svg b/src/img/walletConnect.svg new file mode 100644 index 0000000000..afd307e46b --- /dev/null +++ b/src/img/walletConnect.svg @@ -0,0 +1 @@ +<svg width="388" height="238" viewBox="0 0 388 238" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M79.5 46.539c63.216-61.894 165.709-61.894 228.926 0l7.608 7.449a7.807 7.807 0 010 11.207l-26.026 25.482a4.108 4.108 0 01-5.723 0l-10.47-10.251c-44.101-43.179-115.604-43.179-159.705 0l-11.212 10.977a4.108 4.108 0 01-5.723 0L71.149 65.922a7.808 7.808 0 010-11.207l8.35-8.176zm282.75 52.699l23.163 22.679a7.806 7.806 0 010 11.206L280.969 235.385c-3.161 3.095-8.286 3.095-11.447 0l-74.128-72.578a2.054 2.054 0 00-2.862 0l-74.127 72.578c-3.161 3.095-8.285 3.095-11.446 0L2.511 133.122a7.808 7.808 0 010-11.207l23.164-22.678c3.16-3.095 8.285-3.095 11.446 0l74.129 72.579a2.056 2.056 0 002.862 0v-.001l74.126-72.578c3.161-3.095 8.285-3.095 11.446 0l74.13 72.578c.79.774 2.071.774 2.861 0l74.129-72.577c3.16-3.095 8.285-3.095 11.446 0z" class="dark" /></svg> \ No newline at end of file diff --git a/src/img/westend_icon.svg b/src/img/westend_icon.svg index 3e0599e60e..28b4a2574a 100644 --- a/src/img/westend_icon.svg +++ b/src/img/westend_icon.svg @@ -1,17 +1 @@ -<svg width="1672" height="1672" viewBox="0 0 1672 1672" fill="none" xmlns="http://www.w3.org/2000/svg"> -<circle cx="836.274" cy="836.219" r="835.436" fill="#E6007A"/> -<mask id="mask0" mask-type="alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="1672" height="1672"> -<circle cx="836.274" cy="836.219" r="835.436" fill="#C4C4C4"/> -</mask> -<g mask="url(#mask0)"> -<rect x="-299.919" y="-66.0518" width="2840.48" height="1871.38" fill="url(#paint0_linear)"/> -<path d="M-30.6062 -2704.78C4.61255 -2283.66 177.141 -1879.1 455.772 -1562.26C741.535 -1237.35 1137.41 -1018.51 1561.82 -940.645C1770.91 -902.158 1989.36 -894.998 2199.33 -931.247C2399.05 -965.706 2600.56 -1052.97 2713.35 -1230.64C2763.73 -1309.4 2794.49 -1402.94 2793.15 -1496.92C2791.81 -1597.16 2749.46 -1687.56 2687.49 -1764.54C2618.84 -1850.01 2531.01 -1922.51 2444.08 -1989.19C2358.04 -2054.98 2265.31 -2113.16 2164.11 -2152.99C1939.42 -2240.7 1716.07 -2191.92 1500.75 -2101.07C1272.49 -2004.86 1051.82 -1891.19 830.697 -1780.65C378.201 -1554.65 -84.1033 -1331.33 -586.53 -1244.51C-712.694 -1222.59 -841.087 -1210.05 -969.035 -1208.71C-1083.61 -1207.37 -1207.99 -1208.26 -1318.99 -1239.59C-1368.03 -1253.46 -1418.41 -1276.74 -1446.94 -1321.04C-1483.94 -1378.32 -1471.02 -1449.93 -1454.97 -1511.69C-1427.77 -1614.62 -1376.5 -1710.39 -1310.08 -1793.18C-1172.77 -1964.58 -977.951 -2078.25 -768.866 -2137.77C-649.389 -2171.78 -525.454 -2189.24 -401.519 -2195.95C-282.042 -2202.21 -154.987 -2206.69 -36.4019 -2187C79.5085 -2168.2 159.754 -2095.7 199.431 -1985.61C239.554 -1875.07 232.421 -1757.82 228.409 -1642.36C222.614 -1469.17 236.879 -1221.69 431.252 -1150.98C532.005 -1114.28 652.374 -1152.77 750.897 -1181.41C853.434 -1211.4 953.295 -1250.78 1048.7 -1298.66C1147.22 -1348.34 1240.84 -1406.96 1328.22 -1474.54C1403.12 -1531.82 1490.05 -1596.27 1540.43 -1677.72C1576.09 -1735.45 1596.6 -1814.66 1533.29 -1860.31C1529.73 -1862.99 1523.48 -1862.54 1519.92 -1860.31C1336.69 -1747.98 1235.05 -1546.14 1196.71 -1339.39C1161.04 -1146.51 1161.93 -908.871 1290.77 -748.21C1394.2 -619.323 1597.04 -584.864 1724.99 -701.22C1786.07 -756.713 1805.24 -842.19 1777.15 -919.611C1743.71 -1011.35 1658.12 -1076.24 1564.94 -1098.62C1442.35 -1128.16 1313.06 -1095.04 1197.15 -1054.76C1089.71 -1017.17 986.73 -967.944 888.207 -911.109C682.689 -792.515 496.34 -644.385 306.426 -502.519C112.499 -357.969 -86.7783 -217.894 -310.574 -123.467C-561.565 -16.9561 -834.846 36.7468 -1107.68 33.1667C-1230.72 31.3765 -1371.15 29.1389 -1483.94 -25.9065C-1605.2 -85.4272 -1642.65 -211.629 -1611.89 -336.936C-1582.47 -456.872 -1509.36 -558.46 -1427.33 -647.517C-1345.74 -736.127 -1252.57 -813.996 -1152.71 -881.124C-953.431 -1014.49 -729.635 -1106.23 -500.489 -1173.81C-442.98 -1190.81 -385.025 -1206.03 -327.069 -1219.9C-264.656 -1235.12 -195.556 -1257.49 -130.913 -1253.46C14.8662 -1244.07 37.157 -1068.64 62.1223 -955.413C173.574 -455.529 216.818 67.1785 428.577 538.868C454.88 597.942 483.858 655.672 515.956 711.613C537.801 750.1 564.549 791.719 611.805 799.327C715.233 814.991 778.092 683.419 808.853 606.445C849.421 504.409 883.749 368.362 853.434 259.166C826.685 163.844 737.523 128.489 646.132 136.992C532.005 147.733 424.119 204.121 320.692 249.321C191.407 305.708 63.4595 366.124 -63.1504 428.33C-314.587 551.847 -562.457 682.524 -814.339 806.04C-1316.77 1054.42 -1846.39 1276.84 -2409 1336.36C-2544.08 1350.68 -2679.6 1355.15 -2815.13 1348.44C-2810.67 1352.91 -2806.21 1357.39 -2801.76 1361.86C-2798.19 1187.33 -2665.34 1045.02 -2507.52 987.287C-2332.32 923.292 -2139.73 971.624 -1983.25 1062.47C-1814.29 1160.93 -1684.11 1309.06 -1563.74 1461.21C-1446.5 1609.79 -1332.81 1760.61 -1202.19 1898.45C-720.273 2405.49 -88.1157 2767.54 593.081 2922.38C750.006 2958.18 928.329 2991.75 1065.64 2884.34C1165.5 2806.02 1210.53 2679.38 1227.02 2557.2C1246.19 2410.86 1230.14 2255.57 1185.12 2115.05C1135.18 1959.31 1027.74 1849.67 860.121 1837.14C679.122 1823.71 492.774 1895.31 320.692 1940.96C106.257 1997.8 -107.731 2054.63 -322.165 2111.47C-533.925 2167.41 -745.239 2223.8 -956.998 2279.74C-1061.32 2307.48 -1166.08 2335.23 -1270.4 2362.98C-1374.28 2390.72 -1478.59 2425.18 -1584.25 2444.43C-1685.9 2462.77 -1767.92 2415.34 -1771.94 2307.93C-1775.06 2230.51 -1747.42 2153.98 -1713.98 2085.51C-1633.29 1920.37 -1542.35 1760.61 -1442.04 1606.66C-1236.07 1289.81 -990.879 998.476 -713.586 742.044C-646.269 679.839 -577.168 619.423 -506.284 561.245C-512.526 561.245 -518.767 561.245 -525.009 561.245C-308.791 729.514 -286.946 1030.25 -243.257 1280.42C-215.171 1441.52 -179.952 1601.74 -138.938 1760.16C-117.985 1840.72 -95.2485 1920.82 -70.7292 2000.48C-50.6677 2066.27 -33.281 2136.53 -2.07446 2197.84C18.8787 2238.56 50.9771 2279.74 99.1243 2286.45C145.934 2293.16 184.719 2264.52 212.36 2229.17C252.928 2176.36 278.34 2110.57 306.426 2050.6C339.861 1979.45 371.959 1907.4 402.275 1834.9C521.306 1550.72 616.709 1256.25 687.146 956.408C766.5 616.738 813.756 269.459 828.468 -79.6094C828.914 -93.0352 810.19 -97.5103 803.503 -86.3223C646.132 184.43 573.02 501.277 599.322 813.648C621.167 1075.45 725.04 1353.81 994.755 1442.42C1129.39 1486.72 1276.51 1480.91 1414.71 1457.63C1558.7 1433.47 1700.02 1390.95 1832.88 1330.54C2095.46 1211.05 2322.38 1012.35 2442.3 745.625C2498.92 619.87 2531.46 482.48 2529.68 344.196C2528.34 224.707 2506.94 80.6042 2412.43 -3.08276C2304.1 -99.3003 2160.55 -42.4648 2059.35 34.5093C1952.35 115.958 1862.75 219.784 1792.75 334.35C1457.95 881.224 1581.44 1586.07 1764.22 2163.83C1946.56 2739.34 2247.48 3276.82 2643.8 3731.5C2692.4 3787.44 2742.77 3842.04 2794.49 3895.3C2806.97 3907.83 2823.91 3889.48 2813.21 3876.5C2279.13 3196.71 1709.83 2544.67 1107.1 1924.85C508.377 1309.5 -122.889 724.591 -782.686 175.032C-1099.66 -89.0073 -1430.89 -336.041 -1743.41 -605.002C-1891.41 -732.547 -2034.07 -866.804 -2163.36 -1013.59C-2230.23 -1089.67 -2293.53 -1169.33 -2351.49 -1252.57C-2377.35 -1290.16 -2402.76 -1328.2 -2426.38 -1367.14C-2438.42 -1386.83 -2450.01 -1406.52 -2461.16 -1426.21C-2478.1 -1455.74 -2477.65 -1469.17 -2462.94 -1499.15C-2381.8 -1662.05 -2155.78 -1692.49 -1995.73 -1708.6C-1783.53 -1729.63 -1569.54 -1718.44 -1359.56 -1681.3C-936.045 -1606.56 -535.262 -1434.71 -157.662 -1232.88C578.815 -839.952 1246.64 -310.979 2042.41 -36.1995C2231.43 29.1389 2425.8 78.814 2624.19 105.218C2723.16 118.644 2822.57 126.251 2922.43 127.594C2963.89 128.042 2984.4 136.992 3004.91 99.4001C3024.97 62.7031 3036.12 21.531 3039.24 -20.5361C3052.61 -187.015 2952.3 -344.991 2828.81 -447.921C2706.66 -549.509 2553.75 -607.24 2398.16 -630.511C2018.33 -686.899 1663.02 -532.503 1334.02 -357.969C1242.18 -309.189 1151.23 -259.066 1059.4 -210.734C972.91 -165.086 838.276 -66.6311 740.198 -127.942C667.085 -173.589 650.144 -277.415 640.337 -356.179C626.071 -472.088 617.155 -588.891 609.576 -705.248C579.707 -1168.88 593.527 -1635.2 651.036 -2096.15C665.748 -2216.09 684.026 -2336.02 704.979 -2455.07C721.92 -2551.28 733.511 -2655.11 773.634 -2745.51C814.202 -2835.91 894.894 -2894.53 996.093 -2858.28C1093.28 -2823.38 1166.39 -2734.32 1234.6 -2660.48C1721.42 -2131.95 2050.88 -1453.95 2634.44 -1018.07C2787.35 -903.948 2963.89 -790.725 3156.04 -765.216C3312.96 -744.182 3493.96 -797.885 3577.77 -942.435C3625.92 -1025.67 3627.7 -1123.68 3617.45 -1216.77C3606.31 -1318.8 3585.8 -1420.39 3563.06 -1520.64C3517.14 -1722.02 3453.39 -1918.93 3373.15 -2108.68C3292.01 -2299.77 3193.93 -2483.26 3080.25 -2656.9C3077.13 -2661.82 3070.89 -2664.95 3065.09 -2663.16C2689.72 -2530.25 2556.87 -2119.42 2368.74 -1807.5C2263.08 -1632.07 2121.31 -1454.85 1929.62 -1370.72C1727.67 -1281.66 1511 -1343.86 1361.66 -1500.5C1169.96 -1701.88 1127.61 -1987.85 1090.16 -2251.44C1083.47 -2246.52 1077.23 -2241.15 1070.54 -2236.23C1408.02 -2088.1 1548.45 -1716.65 1687.1 -1401.59C1723.65 -1318.36 1761.1 -1235.56 1803.01 -1155.01C1855.61 -1053.87 1915.8 -957.203 1969.29 -856.063C2068.26 -668.551 2145.83 -450.159 2081.64 -237.585C2020.56 -35.752 1836.44 43.0122 1662.13 126.699C1583.22 164.739 1504.31 206.358 1440.12 266.774C1369.68 333.008 1322.42 418.932 1284.08 506.647C1205.18 687.894 1151.68 901.81 974.248 1014.14C873.941 1077.69 768.284 1069.63 663.073 1021.75C573.911 981.47 491.436 934.927 395.142 912.551C42.9521 831.549 -374.325 997.58 -524.563 1340.38C-611.496 1539.08 -562.903 1759.27 -471.066 1947.23C-387.254 2118.63 -264.656 2269.89 -120.214 2393.85C200.323 2668.63 614.48 2797.52 1031.76 2811.84C1254.22 2819.45 1481.58 2798.42 1698.69 2749.19C1871.66 2709.81 2045.08 2646.26 2186.4 2535.72C2326.39 2426.52 2418.22 2270.79 2428.92 2091.33C2442.3 1872.94 2349.12 1661.71 2248.37 1473.75C2141.82 1275.05 2016.55 1075.45 1984.45 848.107C1955.03 641.351 2021.01 443.099 2118.64 263.194C2213.6 87.7646 2333.52 -72.0015 2434.27 -243.851C2545.28 -433.153 2612.6 -636.329 2668.77 -848.008C2769.08 -1227.96 2885.43 -1711.73 3291.12 -1876.42C3519.82 -1969.5 3770.81 -1907.3 3994.61 -1831.22C4192.1 -1764.09 4390.93 -1691.59 4577.72 -1597.61C4754.26 -1509 4921.89 -1395.78 5046.27 -1240.04C5062.76 -1219.45 5088.18 -1198.87 5077.48 -1173.36C5069.9 -1155.46 5055.63 -1138.45 5044.49 -1122.79C5015.06 -1081.61 4981.63 -1043.58 4944.62 -1008.67C4875.52 -942.883 4795.72 -889.627 4711.47 -846.665C4536.71 -757.16 4341.89 -709.723 4147.96 -687.347C3925.06 -661.39 3699.48 -662.733 3476.13 -686.899C3018.73 -736.574 2574.7 -882.915 2160.55 -1080.72C1955.03 -1178.73 1756.2 -1290.61 1563.61 -1411.89C1377.26 -1529.59 1196.26 -1655.34 1006.35 -1767.22C822.227 -1875.52 628.3 -1971.29 420.999 -2027.23C247.579 -2074.22 -0.737061 -2108.23 -97.4778 -1912.22C-129.13 -1848.22 -126.455 -1772.14 -84.5491 -1713.52C-49.7761 -1664.74 5.50415 -1637.44 60.7847 -1617.75C196.311 -1569.42 381.322 -1560.47 457.109 -1417.71C539.584 -1262.41 470.483 -1070.43 544.934 -912.004C559.199 -881.572 573.02 -852.035 607.347 -845.77C643.903 -839.057 681.797 -843.085 717.907 -850.693C796.37 -866.804 882.411 -913.794 963.994 -899.473C1074.11 -880.229 1065.64 -753.58 1093.72 -671.683C1116.02 -607.24 1179.32 -536.979 1252.88 -565.62C1264.02 -570.095 1266.7 -588.444 1252.88 -591.576C917.63 -667.656 568.115 -649.307 226.626 -647.07C-134.034 -644.384 -494.248 -640.804 -854.907 -635.881C-1215.57 -630.959 -1575.78 -625.141 -1936.44 -618.428C-2066.62 -615.743 -2203.48 -623.351 -2328.75 -580.836C-2376.01 -565.173 -2422.37 -541.901 -2457.15 -505.204C-2478.54 -482.828 -2519.56 -427.335 -2508.41 -394.219C-2500.84 -371.842 -2462.5 -344.991 -2445.55 -328.433C-2418.81 -302.476 -2391.17 -277.862 -2362.63 -253.696C-2311.81 -211.181 -2257.87 -171.352 -2202.14 -135.102C-2085.34 -58.5757 -1960.07 3.63013 -1830.78 55.5427C-1555.72 166.081 -1264.16 228.287 -972.601 273.934C-282.043 382.235 420.107 392.528 1116.91 354.936C1469.99 335.693 1822.62 304.366 2174.37 267.221C2310.78 252.901 2447.2 233.21 2584.51 238.58C2700.87 243.055 2825.69 262.746 2922.43 331.665C2970.14 365.677 3005.8 410.877 3029.87 463.685C3030.77 458.314 3031.21 452.944 3032.1 447.573C2891.23 560.797 2722.27 626.583 2548.85 670.888C2365.17 717.878 2176.15 747.415 1988.02 771.581C1596.15 821.704 1199.38 833.787 805.286 808.725C608.684 796.195 414.757 769.343 219.493 743.387C153.959 734.884 81.7378 735.331 33.1445 787.692C-2.96582 826.179 -9.20728 880.329 -4.74927 930.899C1.49219 999.37 22.8909 1065.16 38.9402 1131.39C59.0015 1215.52 75.4963 1317.56 -27.9314 1348.44C-105.948 1372.16 -197.785 1357.39 -277.584 1354.7C-380.121 1351.57 -482.211 1348.44 -584.747 1345.31C-1041.26 1330.99 -1498.21 1317.11 -1954.72 1302.79C-1967.65 1302.34 -1973.44 1321.59 -1961.41 1327.85C-1522.28 1558.78 -1083.16 1789.25 -644.04 2020.17C-442.534 2126.24 -242.812 2242.14 -29.7146 2323.59C164.212 2397.44 379.093 2436.82 580.598 2369.24C765.163 2307.48 914.509 2172.78 1046.02 2033.15C1183.78 1887.26 1307.71 1727.94 1416.49 1558.78C1635.38 1217.76 1791.86 835.129 1871.66 437.281C1963.5 -19.1936 1957.26 -486.856 1967.06 -950.491C1971.97 -1184.1 1981.33 -1418.6 2009.86 -1650.87C2024.57 -1772.14 2044.64 -1892.98 2072.28 -2012.02C2095.01 -2110.02 2124.88 -2208.93 2179.27 -2294.4C2234.1 -2379.88 2314.35 -2445.22 2415.1 -2465.36C2508.72 -2484.15 2603.68 -2457.75 2662.53 -2379.88C2746.34 -2268.45 2749.91 -2100.63 2862.7 -2010.23C3000.9 -1899.24 3194.38 -2009.33 3311.63 -2098.84C3447.6 -2202.66 3580 -2359.74 3755.21 -2393.75C3797.56 -2401.81 3843.92 -2402.26 3884.04 -2385.7C3942.45 -2361.53 3961.62 -2307.83 3962.51 -2248.31C3963.84 -2130.61 3949.13 -2010.67 3937.99 -1893.42C3927.29 -1783.78 3925.95 -1658.03 3869.33 -1560.47C3812.72 -1462.46 3701.26 -1424.42 3601.85 -1385.48C3490.4 -1341.63 3378.5 -1297.77 3267.04 -1253.91C3163.17 -1213.19 3057.51 -1163.51 2946.06 -1146.95C2824.36 -1128.61 2708.89 -1167.54 2600.56 -1220.35C2504.71 -1267.34 2412.43 -1321.94 2323.27 -1380.11C1962.61 -1615.96 1653.66 -1920.72 1304.15 -2171.78C968.452 -2412.55 573.465 -2616.17 151.73 -2619.75C-25.2566 -2621.1 -231.22 -2599.17 -339.552 -2440.74C-431.389 -2306.49 -436.739 -2129.72 -449.667 -1973.08C-467.945 -1754.24 -531.696 -1547.93 -572.71 -1333.12C-607.483 -1148.74 -610.158 -964.811 -546.853 -786.25C-436.293 -473.878 -167.024 -218.342 152.621 -131.97C329.162 -84.0847 506.148 -105.118 684.472 -130.627C874.386 -157.926 1091.94 -196.413 1268.93 -97.9578C1452.6 4.52515 1521.7 223.364 1509.22 423.407C1501.64 542.449 1469.1 658.357 1429.86 770.238C1400 856.163 1331.34 965.359 1382.61 1055.31C1402.22 1089.32 1432.09 1117.07 1458.84 1144.82C1496.29 1183.75 1533.74 1222.24 1572.97 1259.38C1641.62 1323.83 1715.18 1384.24 1797.66 1430.34C1976.87 1530.13 2167.23 1526.55 2356.7 1458.53C2456.56 1422.73 2551.97 1376.19 2649.15 1334.12C2752.58 1289.37 2857.35 1248.64 2963.89 1211.94C3169.41 1140.79 3380.28 1084.85 3593.38 1043.68C4024.03 960.883 4466.72 938.955 4903.16 980.127C5118.49 1000.27 5332.03 1036.07 5542.46 1086.64C5559.4 1090.67 5566.53 1064.71 5549.59 1060.68C4705.67 857.505 3807.37 898.23 2984.85 1176.14C2881.87 1211.05 2780.22 1249.54 2679.91 1291.6C2578.71 1334.12 2480.19 1382.9 2378.1 1421.83C2274.23 1461.21 2163.67 1490.75 2051.77 1482.7C1949.23 1475.09 1852.49 1435.26 1766.9 1379.77C1681.3 1324.72 1605.96 1255.35 1534.18 1183.75C1502.98 1152.42 1471.77 1120.65 1441.9 1087.98C1423.62 1067.84 1405.35 1047.26 1396.43 1020.85C1382.61 980.574 1395.54 940.297 1409.36 902.258C1482.02 702.662 1560.04 496.354 1531.06 279.752C1507.44 103.875 1414.26 -62.1558 1248.87 -137.34C1082.58 -212.971 892.219 -186.567 717.907 -161.954C526.655 -135.102 338.078 -106.908 148.609 -160.611C-21.2444 -208.944 -175.94 -306.504 -298.092 -434.048C-420.244 -561.593 -510.743 -720.463 -550.42 -893.208C-599.013 -1103.54 -553.095 -1313.88 -503.164 -1519.29C-477.307 -1624.91 -451.004 -1730.08 -436.739 -1837.93C-424.702 -1925.64 -420.689 -2014.25 -411.773 -2101.97C-392.604 -2282.32 -344.902 -2479.23 -156.77 -2549.94C-56.0173 -2587.53 58.5554 -2593.8 165.55 -2592.01C272.99 -2589.77 379.984 -2574.11 484.303 -2548.15C694.28 -2495.34 892.219 -2401.81 1076.78 -2289.48C1443.69 -2066.61 1753.52 -1764.54 2095.46 -1507.21C2264.87 -1379.67 2444.97 -1256.15 2640.24 -1171.57C2733.41 -1131.29 2831.93 -1105.78 2934.02 -1117.86C3041.46 -1130.84 3142.66 -1176.04 3242.53 -1215.42C3353.98 -1259.28 3465.88 -1303.14 3577.33 -1347C3663.81 -1381.01 3758.33 -1410.99 3829.66 -1472.75C3904.55 -1537.19 3931.3 -1631.62 3944.67 -1726.05C3960.72 -1838.83 3969.64 -1952.94 3977.66 -2066.17C3981.68 -2122.11 3986.58 -2178.05 3987.47 -2233.99C3988.36 -2277.85 3983.91 -2322.6 3958.05 -2359.3C3917.93 -2416.58 3842.58 -2430.9 3777.05 -2424.19C3608.98 -2407.18 3474.35 -2268 3350.41 -2165.52C3226.48 -2063.03 2986.63 -1891.63 2847.09 -2060.8C2754.81 -2172.68 2755.7 -2340.5 2642.02 -2438.95C2521.65 -2542.78 2333.52 -2499.82 2226.53 -2397.33C2083.87 -2260.84 2044.64 -2045.13 2011.65 -1858.96C1930.51 -1400.7 1941.21 -933.932 1928.73 -470.298C1916.69 -18.2986 1875.67 431.463 1724.99 860.19C1587.24 1253.12 1373.69 1619.19 1099.52 1931.56C966.223 2082.83 817.323 2237.22 630.529 2320.01C417.432 2413.99 185.611 2377.74 -25.2566 2297.19C-246.378 2213.06 -453.679 2090.88 -662.764 1980.79C-879.873 1866.67 -1096.98 1752.55 -1314.09 1638.43C-1526.3 1527 -1738.5 1415.57 -1950.71 1304.13C-1952.94 1312.64 -1955.16 1320.69 -1957.39 1329.2C-1542.35 1342.17 -1126.85 1354.7 -711.803 1367.68C-513.418 1373.95 -312.803 1389.16 -114.418 1385.58C-57.8005 1384.69 7.7334 1376.63 47.8562 1331.88C92.4373 1282.65 81.2917 1211.5 68.3633 1151.98C43.844 1035.62 -60.9211 781.427 140.139 765.763C183.382 762.183 227.517 771.581 269.869 778.294C318.908 785.902 367.947 792.615 416.986 798.88C518.185 811.858 619.83 822.151 721.92 829.759C1118.69 860.191 1518.13 852.583 1913.12 806.935C2123.1 782.769 2333.97 751.442 2539.48 699.977C2722.27 654.33 2900.14 585.411 3048.15 466.37C3053.06 462.342 3052.61 455.182 3050.38 450.259C2955.42 243.055 2706.22 207.253 2503.37 211.281C2359.82 214.414 2216.72 235.895 2074.51 250.663C1901.09 268.564 1727.67 284.675 1554.25 298.548C867.253 353.594 174.912 371.942 -511.634 305.709C-827.267 275.277 -1144.24 230.525 -1451.4 151.313C-1725.57 80.6042 -1995.29 -21.4314 -2229.34 -183.435C-2289.52 -225.055 -2346.59 -270.255 -2400.53 -319.93C-2424.6 -342.306 -2449.12 -364.682 -2470.97 -389.296C-2483.89 -403.617 -2484.79 -406.302 -2478.54 -424.65C-2472.75 -440.761 -2464.72 -455.977 -2454.47 -469.85C-2384.92 -562.935 -2244.05 -581.731 -2136.61 -587.101C-1965.86 -595.604 -1793.78 -594.262 -1623.04 -597.395C-1448.28 -600.527 -1273.52 -603.212 -1098.77 -605.897C-746.576 -610.82 -394.386 -615.295 -42.1973 -617.98C132.56 -619.323 306.871 -620.666 481.629 -621.561C648.361 -622.456 815.094 -622.903 981.381 -607.24C1069.65 -598.737 1157.03 -585.311 1243.52 -565.62C1243.52 -574.123 1243.52 -583.073 1243.52 -591.576C1161.93 -560.25 1118.24 -664.523 1105.32 -726.729C1093.72 -781.774 1085.7 -842.637 1044.69 -884.705C973.356 -958.546 866.808 -921.401 782.55 -896.788C731.282 -881.572 665.748 -857.853 611.805 -872.174C560.537 -886.047 543.596 -983.16 536.463 -1028.36C518.631 -1142.93 534.68 -1262.41 500.798 -1374.3C437.939 -1581.05 217.264 -1584.18 48.302 -1650.87C-56.9089 -1692.48 -134.034 -1789.15 -74.2957 -1904.61C5.50439 -2058.56 197.202 -2051.4 342.09 -2019.18C626.962 -1956.08 885.978 -1813.32 1130.73 -1658.92C1379.49 -1502.29 1621.12 -1335.36 1879.24 -1193.94C2395.93 -911.556 2963.45 -702.562 3552.81 -652.887C3827.87 -629.616 4112.3 -638.119 4381.57 -701.667C4614.28 -756.713 4848.33 -859.196 5009.71 -1041.34C5047.61 -1084.3 5081.04 -1131.29 5109.13 -1180.97C5111.36 -1184.99 5111.8 -1190.81 5109.13 -1194.39C4963.79 -1406.52 4747.13 -1548.38 4517.54 -1654.45C4400.29 -1709.04 4279.48 -1755.14 4158.22 -1799.44C4011.99 -1853.15 3864.43 -1907.74 3710.62 -1934.15C3566.63 -1958.76 3417.28 -1956.97 3279.97 -1901.93C3172.53 -1858.52 3078.91 -1786.91 3002.23 -1700.54C2822.13 -1497.81 2739.21 -1236.91 2671.44 -979.132C2632.21 -831.002 2596.1 -681.529 2542.16 -537.426C2492.67 -404.512 2424.47 -281.89 2349.12 -162.401C2213.15 52.8577 2051.77 262.746 1979.99 511.122C1944.77 632.848 1936.3 759.498 1958.59 884.357C1984.01 1026.67 2045.53 1160.03 2111.95 1287.13C2250.15 1551.61 2442.3 1838.03 2391.03 2152.19C2347.79 2414.89 2122.21 2578.23 1888.16 2664.61C1619.78 2763.96 1318.86 2794.84 1034.88 2785.89C471.821 2768.43 -107.285 2525.87 -404.64 2023.31C-479.982 1895.76 -539.72 1751.66 -548.191 1601.74C-557.553 1440.63 -495.139 1292.95 -388.145 1174.8C-215.617 985.497 51.4226 894.65 303.305 925.082C365.718 932.689 427.686 947.905 486.532 971.177C552.512 997.133 613.588 1034.28 679.568 1060.68C795.032 1106.78 912.281 1092.9 1011.7 1017.27C1225.68 854.373 1240.4 568.405 1388.85 360.754C1538.64 151.313 1821.29 133.86 1995.15 -45.1499C2174.37 -229.977 2144.5 -516.84 2051.77 -735.679C1992.48 -874.859 1910.89 -1002.4 1839.12 -1134.87C1780.72 -1243.17 1731.23 -1355.95 1681.75 -1468.72C1589.46 -1679.06 1496.74 -1899.69 1339.37 -2070.64C1265.81 -2150.3 1178.87 -2215.19 1079.46 -2258.6C1068.31 -2263.53 1058.06 -2255.47 1059.84 -2243.39C1094.17 -2004.41 1131.17 -1750.22 1278.74 -1551.52C1397.77 -1390.85 1582.78 -1278.08 1787.4 -1304.03C1984.01 -1328.65 2144.94 -1467.38 2264.42 -1616.85C2395.49 -1780.65 2479.3 -1972.19 2582.28 -2153.88C2696.41 -2355.27 2840.85 -2556.65 3067.32 -2636.76C3062.42 -2639 3057.07 -2640.79 3052.16 -2643.03C3287.11 -2284.11 3454.73 -1882.68 3543.89 -1462.46C3585.8 -1264.2 3656.24 -996.138 3459.19 -858.301C3277.3 -731.204 3049.04 -795.647 2870.72 -893.655C2220.28 -1250.33 1860.96 -1946.23 1403.56 -2501.16C1345.61 -2571.87 1285.42 -2640.79 1223.01 -2707.02C1161.49 -2772.36 1096.85 -2843.07 1012.59 -2878.42C943.487 -2907.06 865.916 -2900.8 808.853 -2849.33C755.355 -2800.55 729.499 -2726.71 712.558 -2657.79C659.952 -2442.98 632.758 -2218.33 608.684 -1998.59C582.827 -1766.33 568.115 -1533.17 564.549 -1299.56C560.982 -1065.95 568.115 -832.344 586.394 -599.184C594.418 -497.149 599.768 -392.428 619.384 -291.736C632.312 -225.502 656.832 -151.213 714.341 -110.041C783.887 -60.3657 866.808 -86.7698 937.246 -119.886C1035.32 -166.429 1130.28 -219.684 1225.68 -271.149C1414.71 -373.185 1605.51 -476.115 1810.59 -543.692C2133.8 -650.202 2536.36 -656.915 2810.54 -424.65C2946.06 -309.636 3072.23 -97.5105 2980.83 80.604C2967.01 107.903 2946.51 101.638 2918.42 101.19C2894.35 100.743 2870.72 100.295 2846.65 99.4001C2794.49 97.1626 2742.33 93.135 2690.61 87.7646C2588.52 77.0239 2486.88 59.5706 2387.02 36.7468C1561.38 -149.871 868.591 -668.103 149.947 -1088.33C-218.738 -1304.03 -603.471 -1501.39 -1013.62 -1624.46C-1211.11 -1683.53 -1413.95 -1724.71 -1619.92 -1739.48C-1811.61 -1752.9 -2015.79 -1750.22 -2203.03 -1702.33C-2275.26 -1683.98 -2347.48 -1655.79 -2406.77 -1609.69C-2432.63 -1589.55 -2455.81 -1566.28 -2474.09 -1538.98C-2487.46 -1518.85 -2513.76 -1476.78 -2509.75 -1451.27C-2506.18 -1431.13 -2484.79 -1405.62 -2474.53 -1388.17C-2462.94 -1368.48 -2450.9 -1348.79 -2438.87 -1329.54C-2412.12 -1287.03 -2384.03 -1245.41 -2354.61 -1204.68C-2300.67 -1129.95 -2242.71 -1057.9 -2181.64 -988.53C-1918.16 -690.479 -1602.53 -445.684 -1292.25 -199.993C-612.833 334.35 35.3735 903.6 651.928 1506.86C1265.81 2107.89 1848.93 2740.69 2396.82 3403.02C2531.46 3565.47 2663.86 3730.16 2794.49 3896.19C2800.73 3889.93 2806.97 3883.66 2813.21 3877.4C2421.79 3471.49 2110.62 2989.96 1900.64 2465.91C1796.32 2205.45 1716.52 1934.7 1664.36 1658.57C1605.07 1343.96 1582.78 1013.24 1666.59 701.32C1735.24 445.783 1878.79 189.8 2101.7 38.5371C2166.34 -5.32031 2246.14 -41.1223 2325.05 -20.0889C2391.48 -2.18774 2436.06 56.438 2461.91 116.854C2514.96 240.37 2512.29 390.738 2486.88 520.52C2432.94 797.985 2257.29 1036.07 2025.91 1193.15C1800.33 1346.2 1515.01 1442.87 1242.18 1449.13C1103.09 1452.26 957.307 1424.97 847.638 1333.67C744.656 1248.19 685.809 1121.99 654.157 993.552C585.056 712.06 620.721 401.479 725.932 133.86C754.018 62.7031 787.899 -5.76782 826.239 -72.0015C817.769 -74.239 809.744 -76.4766 801.274 -78.7144C776.309 513.807 655.94 1101.41 445.518 1655.89C391.129 1799.54 330.053 1940.96 263.628 2079.25C237.325 2134.29 210.577 2213.5 156.188 2247.96C85.75 2292.71 34.0361 2219.32 9.9624 2161.14C-47.5469 2021.52 -84.1033 1868.46 -121.551 1722.57C-160.337 1571.75 -192.881 1419.15 -219.184 1266.09C-262.873 1013.69 -288.284 712.508 -506.284 542.896C-512.526 537.974 -519.213 537.974 -525.009 542.896C-813.447 779.636 -1072.02 1050.84 -1295.37 1350.23C-1408.16 1501.04 -1511.14 1659.02 -1604.76 1822.81C-1651.57 1904.71 -1695.7 1987.95 -1736.72 2072.53C-1766.59 2133.84 -1790.22 2198.73 -1796.9 2267.21C-1802.25 2324.04 -1794.23 2387.14 -1753.66 2430.1C-1699.72 2487.39 -1618.13 2481.57 -1548.14 2465.01C-1334.15 2414.44 -1121.95 2352.23 -909.296 2295.85C-483.548 2182.62 -57.8005 2069.85 367.947 1956.62C544.042 1910.08 753.126 1825.5 936.354 1877.41C1121.81 1930.22 1177.09 2135.63 1198.94 2305.69C1217.21 2448.9 1212.31 2608.22 1146.78 2739.79C1115.57 2802.44 1069.65 2856.59 1008.13 2890.61C916.738 2941.18 810.19 2936.7 710.329 2919.7C355.465 2858.38 7.2876 2724.58 -307.899 2552.28C-624.424 2379.53 -914.646 2158.9 -1165.19 1899.34C-1296.26 1763.29 -1410.83 1615.16 -1526.74 1466.59C-1642.21 1318.9 -1765.25 1173.46 -1923.07 1069.18C-2065.28 975.204 -2238.25 912.998 -2410.34 936.27C-2552.99 955.513 -2689.86 1038.75 -2767.43 1160.93C-2806.21 1221.34 -2827.17 1290.26 -2828.5 1362.31C-2828.5 1369.92 -2822.26 1375.29 -2815.13 1375.74C-1779.52 1424.07 -869.173 848.107 28.6865 414.904C141.922 360.306 255.603 307.499 371.068 257.824C471.375 214.861 584.611 154.893 697.4 163.844C851.65 176.374 846.746 344.196 824.456 460.552C813.757 516.492 796.816 571.538 773.634 623.451C748.668 679.839 711.666 754.127 646.578 771.581C576.14 790.377 541.813 704.005 515.51 654.777C486.532 600.179 459.784 543.792 435.264 486.956C388.9 378.208 351.452 266.327 319.354 152.656C255.157 -75.134 213.697 -308.294 171.345 -541.006C149.501 -660.048 127.656 -779.537 102.691 -898.13C82.6294 -993.901 69.2549 -1104.44 17.0952 -1189.47C-38.6309 -1281.21 -131.805 -1288.37 -228.991 -1268.68C-333.311 -1247.65 -437.184 -1219.9 -538.829 -1189.02C-746.13 -1125.92 -949.419 -1041.79 -1132.65 -924.534C-1301.61 -816.681 -1470.57 -674.816 -1572.21 -498.939C-1662.27 -343.648 -1697.93 -120.334 -1518.72 -13.376C-1433.57 37.6418 -1329.69 47.9348 -1232.95 55.5427C-1109.91 64.9409 -985.53 62.7031 -862.932 49.2773C-629.328 23.321 -401.074 -42.9124 -190.206 -148.081C3.72095 -244.746 179.37 -372.29 352.344 -502.072C524.426 -631.406 694.28 -764.321 879.736 -873.964C975.585 -930.8 1075.45 -980.922 1180.21 -1019.41C1294.34 -1061.48 1421.84 -1099.07 1543.99 -1075.35C1654.11 -1053.87 1767.79 -963.916 1764.22 -841.295C1761.55 -754.028 1692.89 -692.717 1613.98 -669.446C1529.73 -644.384 1433.43 -656.468 1364.33 -712.408C1284.53 -776.404 1241.29 -873.516 1221.23 -971.524C1178.87 -1176.49 1214.98 -1412.78 1313.51 -1596.72C1335.8 -1638.33 1361.66 -1678.16 1391.97 -1713.97C1422.29 -1750.22 1468.65 -1806.16 1513.68 -1823.61C1530.17 -1829.87 1531.06 -1831.66 1542.21 -1813.76C1548.9 -1803.02 1549.79 -1787.81 1549.34 -1775.28C1548 -1748.87 1536.86 -1722.92 1524.38 -1700.54C1496.29 -1650.42 1451.26 -1611.93 1408.91 -1574.34C1235.49 -1419.94 1030.42 -1301.35 811.082 -1227.51C761.151 -1210.95 710.329 -1196.18 658.615 -1184.1C591.298 -1168.43 518.185 -1151.43 450.422 -1172.91C345.657 -1206.03 288.147 -1316.12 266.749 -1417.26C221.276 -1636.1 315.787 -1880 183.382 -2080.04C130.777 -2160.15 51.4226 -2201.77 -42.1973 -2215.19C-159.445 -2231.75 -282.489 -2229.51 -400.628 -2222.8C-817.46 -2200.42 -1273.08 -2028.58 -1446.94 -1615.06C-1482.61 -1530.48 -1521.39 -1415.92 -1479.93 -1326.41C-1444.27 -1249.44 -1358.23 -1217.21 -1280.21 -1202.89C-1046.61 -1159.04 -795.615 -1179.18 -562.902 -1220.8C-53.7883 -1312.09 414.311 -1541.67 873.049 -1771.25C1105.32 -1887.16 1337.14 -2011.12 1580.1 -2103.76C1697.35 -2148.51 1820.84 -2181.63 1947.45 -2174.47C2070.05 -2167.76 2186.85 -2122.56 2292.06 -2060.8C2391.92 -2002.17 2483.31 -1928.78 2568.91 -1850.46C2656.73 -1770.35 2740.54 -1675.48 2762.39 -1554.2C2803.85 -1320.59 2627.31 -1114.28 2427.59 -1025.23C2218.06 -931.247 1969.29 -921.849 1743.71 -942.435C1513.23 -963.021 1285.87 -1026.12 1077.23 -1126.37C671.097 -1321.49 347.886 -1658.03 162.429 -2069.3C71.9299 -2269.79 16.2039 -2485.05 -2.07446 -2704.34C-5.19507 -2721.79 -31.9438 -2721.79 -30.6062 -2704.78Z" fill="#E6007A"/> -</g> -<path d="M579.661 1151H750.827L805.398 953.118C812.923 925.833 851.29 925.85 858.791 953.141L913.173 1151H1084.01L1215 645H1054.61L1013.65 845.811C1007.7 874.972 966.681 875.778 959.61 846.872L910.233 645H753.767L705.665 848.126C698.797 877.128 657.69 876.566 651.603 847.387L637.158 778.138C620.973 700.547 553.102 645 474.48 645H449L579.661 1151Z" fill="white"/> -<defs> -<linearGradient id="paint0_linear" x1="-232.429" y1="816.169" x2="2713.69" y2="813.592" gradientUnits="userSpaceOnUse"> -<stop stop-color="#F79420" stop-opacity="0.92"/> -<stop offset="1" stop-color="#C4C4C4" stop-opacity="0"/> -</linearGradient> -</defs> -</svg> +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1671.16 1671.66"><defs><linearGradient id="b" x1="40.98" y1="835.47" x2="1774.13" y2="836.99" gradientTransform="matrix(1 0 0 -1 -.84 1672)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#f79420" stop-opacity=".92"/><stop offset="1" stop-color="#c4c4c4" stop-opacity="0"/></linearGradient><mask id="a" x="0" y="0" width="1671.16" height="1671.66" maskUnits="userSpaceOnUse"><circle cx="836.27" cy="836.22" r="835.44" fill="#c4c4c4" transform="translate(-.84)"/></mask></defs><circle cx="835.44" cy="836.22" r="835.44" fill="#e6007a"/><g mask="url(#a)"><path fill="url(#b)" d="M.66 0h1670.5v1671.65H.66z"/></g><path d="M533.46 1196.48H735.4L799.78 963c8.87-32.19 54.14-32.17 63 0l64.15 233.43h201.55l154.53-597h-189.23l-48.32 237c-7 34.4-55.41 35.35-63.75 1.25l-58.25-238.16h-184.6l-56.7 239.64c-8.11 34.22-56.6 33.55-63.78-.87l-17-81.7c-19.22-91.53-99.22-157.07-192-157.07h-30.06z" fill="#fff"/></svg> diff --git a/src/img/westend_logo.svg b/src/img/westend_logo.svg index 9d9f8735b6..a5e8cd2d7e 100644 --- a/src/img/westend_logo.svg +++ b/src/img/westend_logo.svg @@ -1,22 +1 @@ -<svg width="3921" height="692" viewBox="0 0 3921 692" fill="none" xmlns="http://www.w3.org/2000/svg"> -<mask id="mask0" mask-type="alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="3921" height="692"> -<path d="M131.837 681.774H304.517L359.111 481.804C366.563 454.509 405.249 454.432 412.811 481.696L468.3 681.774H640.65L772.797 171.035H610.992L567.997 374.349C561.847 403.435 520.507 403.935 513.654 375.007L465.334 171.035H307.483L259.56 376.378C252.793 405.374 211.367 404.948 205.197 375.819L189.881 303.502C173.525 226.278 105.357 171.035 26.4198 171.035H0.0195312L131.837 681.774Z" fill="white"/> -<path d="M1063.21 691.417C1196.01 691.417 1283.34 626.577 1301.8 526.158L1153.83 521.835C1141.31 555.752 1108.35 574.04 1066.17 574.04C1004.22 574.04 965.991 532.476 965.991 469.964V465.641H1303.44V425.074C1303.44 256.823 1201.94 164.385 1058.26 164.385C905.355 164.385 807.152 270.124 807.152 428.4C807.152 591.995 904.037 691.417 1063.21 691.417ZM965.991 371.208C968.298 320.333 1008.17 281.762 1061.56 281.762C1114.61 281.762 1152.84 319.003 1153.5 371.208H965.991Z" fill="white"/> -<path d="M1827.66 326.983C1819.09 225.9 1737.04 164.385 1599.95 164.385C1462.86 164.385 1375.53 224.57 1376.19 326.983C1375.53 405.456 1425.29 455.998 1526.46 474.951L1614.78 491.909C1656.63 500.222 1675.74 513.523 1676.4 536.133C1675.74 561.737 1647.4 578.695 1606.21 578.695C1560.4 578.695 1529.43 558.744 1522.51 521.503L1363.34 525.493C1374.87 626.577 1460.88 691.417 1605.55 691.417C1740.99 691.417 1839.53 623.252 1840.19 518.178C1839.53 443.695 1790.75 399.471 1689.91 379.853L1592.37 361.232C1549.53 352.587 1535.69 337.291 1536.02 317.673C1535.69 291.737 1565.68 275.777 1602.91 275.777C1645.43 275.777 1675.08 298.72 1679.7 330.974L1827.66 326.983Z" fill="white"/> -<path d="M2205.24 171.035H2113.95V48.671H1952.81V171.035H1885.91V290.74H1952.81V539.126C1951.82 642.537 2018.39 694.409 2132.08 689.089C2170.96 687.094 2198.97 679.114 2214.46 674.458L2190.08 557.082C2183.16 558.744 2167.01 562.069 2154.82 562.069C2128.78 562.069 2113.95 551.429 2113.95 521.17V290.74H2205.24V171.035Z" fill="white"/> -<path d="M2518.63 691.417C2651.44 691.417 2738.77 626.577 2757.22 526.158L2609.26 521.835C2596.73 555.752 2563.78 574.04 2521.6 574.04C2459.64 574.04 2421.42 532.476 2421.42 469.964V465.641H2758.87V425.074C2758.87 256.823 2657.37 164.385 2513.69 164.385C2360.78 164.385 2262.58 270.124 2262.58 428.4C2262.58 591.995 2359.46 691.417 2518.63 691.417ZM2421.42 371.208C2423.72 320.333 2463.6 281.762 2516.98 281.762C2570.04 281.762 2608.27 319.003 2608.93 371.208H2421.42Z" fill="white"/> -<path d="M3002.32 390.493C3002.65 332.304 3036.26 297.722 3087.34 297.722C3138.42 297.722 3168.74 331.639 3168.41 388.166V681.774H3329.55V356.245C3329.88 241.195 3260.35 164.385 3153.91 164.385C3079.1 164.385 3022.42 202.624 3000.01 264.804H2994.41V171.035H2841.17V681.774H3002.32V390.493Z" fill="white"/> -<path d="M3614.2 689.089C3690.65 689.089 3736.13 646.195 3756.56 598.978H3761.5V681.774H3921V0.789307H3759.85V258.486H3756.56C3737.44 211.602 3693.29 164.385 3613.54 164.385C3507.75 164.385 3411.86 246.516 3411.86 426.737C3411.86 600.641 3502.48 689.089 3614.2 689.089ZM3670.22 562.402C3610.57 562.402 3577.29 508.867 3577.29 426.405C3577.29 344.274 3610.24 291.405 3670.22 291.405C3729.21 291.405 3763.48 342.944 3763.48 426.405C3763.48 509.2 3728.88 562.402 3670.22 562.402Z" fill="white"/> -</mask> -<g mask="url(#mask0)"> -<path d="M-177.066 -1302.21H4586.49V1460.3H-177.066V-1302.21Z" fill="#E6007A"/> -<path d="M-354.148 -1160.54H4568.78V1672.8H-354.148V-1160.54Z" fill="url(#paint0_linear)"/> -<path d="M675.149 -3567.77C714.632 -3095.85 908.045 -2642.48 1220.41 -2287.41C1540.76 -1923.31 1984.56 -1678.07 2460.35 -1590.81C2694.75 -1547.68 2939.64 -1539.66 3175.03 -1580.28C3398.93 -1618.9 3624.83 -1716.69 3751.27 -1915.79C3807.75 -2004.06 3842.23 -2108.87 3840.73 -2214.19C3839.23 -2326.53 3791.75 -2427.83 3722.29 -2514.09C3645.32 -2609.88 3546.86 -2691.13 3449.41 -2765.85C3352.95 -2839.58 3249 -2904.77 3135.55 -2949.41C2883.66 -3047.7 2633.27 -2993.04 2391.88 -2891.23C2136 -2783.41 1888.61 -2656.02 1640.72 -2532.15C1133.44 -2278.89 615.176 -2028.63 51.9282 -1931.34C-89.5085 -1906.76 -233.444 -1892.72 -376.88 -1891.22C-505.322 -1889.71 -644.76 -1890.71 -769.204 -1925.82C-824.18 -1941.37 -880.655 -1967.45 -912.64 -2017.1C-954.122 -2081.29 -939.628 -2161.53 -921.636 -2230.74C-891.15 -2346.09 -833.676 -2453.41 -759.209 -2546.19C-605.278 -2738.27 -386.875 -2865.66 -152.48 -2932.36C-18.5403 -2970.47 120.398 -2990.03 259.335 -2997.55C393.275 -3004.57 535.712 -3009.59 668.652 -2987.52C798.594 -2966.46 888.554 -2885.21 933.034 -2761.84C978.014 -2637.97 970.018 -2506.57 965.52 -2377.18C959.022 -2183.1 975.015 -1905.76 1192.92 -1826.52C1305.87 -1785.4 1440.81 -1828.53 1551.26 -1860.62C1666.21 -1894.23 1778.16 -1938.36 1885.11 -1992.02C1995.56 -2047.69 2100.51 -2113.39 2198.47 -2189.11C2282.43 -2253.31 2379.89 -2325.53 2436.36 -2416.8C2476.34 -2481.5 2499.33 -2570.26 2428.37 -2621.42C2424.37 -2624.43 2417.37 -2623.93 2413.37 -2621.42C2207.96 -2495.54 2094.01 -2269.36 2051.03 -2037.66C2011.05 -1821.51 2012.05 -1555.2 2156.49 -1375.16C2272.43 -1230.72 2499.83 -1192.11 2643.27 -1322.5C2711.74 -1384.69 2733.23 -1480.48 2701.74 -1567.24C2664.26 -1670.05 2568.3 -1742.77 2463.85 -1767.84C2326.41 -1800.94 2181.48 -1763.83 2051.53 -1718.7C1931.09 -1676.57 1815.64 -1621.4 1705.19 -1557.71C1474.79 -1424.81 1265.89 -1258.81 1052.98 -1099.83C835.578 -937.842 612.177 -780.868 361.29 -675.049C79.9158 -555.688 -226.447 -495.507 -532.31 -499.52C-670.249 -501.526 -827.678 -504.033 -954.122 -565.719C-1090.06 -632.42 -1132.04 -773.847 -1097.56 -914.27C-1064.57 -1048.68 -982.609 -1162.52 -890.65 -1262.32C-799.191 -1361.62 -694.738 -1448.88 -582.788 -1524.11C-359.388 -1673.56 -108.5 -1776.37 148.385 -1852.1C212.856 -1871.16 277.827 -1888.21 342.798 -1903.75C412.767 -1920.81 490.232 -1945.88 562.7 -1941.37C726.126 -1930.84 751.115 -1734.24 779.103 -1607.36C904.047 -1047.17 952.525 -461.405 1189.92 67.1897C1219.41 133.389 1251.89 198.084 1287.88 260.773C1312.36 303.903 1342.35 350.544 1395.33 359.07C1511.28 376.623 1581.74 229.178 1616.23 142.918C1661.71 28.5732 1700.19 -123.886 1666.21 -246.256C1636.22 -353.078 1536.26 -392.698 1433.81 -383.169C1305.87 -371.132 1184.92 -307.942 1068.97 -257.289C924.038 -194.098 780.602 -126.394 638.666 -56.6841C356.792 81.7334 78.9163 228.175 -203.458 366.593C-766.706 644.932 -1360.44 894.184 -1991.16 960.885C-2142.59 976.934 -2294.52 981.949 -2446.45 974.426C-2441.46 979.441 -2436.46 984.457 -2431.46 989.472C-2427.46 793.882 -2278.53 634.4 -2101.61 569.705C-1905.2 497.989 -1689.29 552.152 -1513.87 653.959C-1324.46 764.292 -1178.52 930.292 -1043.58 1100.81C-912.14 1267.31 -784.698 1436.32 -638.263 1590.79C-98.0049 2159 610.678 2564.72 1374.34 2738.24C1550.26 2778.37 1750.17 2815.98 1904.1 2695.62C2016.05 2607.85 2066.53 2465.92 2085.02 2329.01C2106.51 2165.02 2088.52 1990.99 2038.04 1833.52C1982.06 1658.99 1861.62 1536.12 1673.7 1522.08C1470.79 1507.03 1261.89 1587.27 1068.97 1638.43C828.581 1702.12 588.688 1765.81 348.296 1829.5C110.902 1892.19 -125.992 1955.38 -363.386 2018.07C-480.334 2049.17 -597.781 2080.26 -714.729 2111.35C-831.177 2142.45 -948.124 2181.06 -1066.57 2202.63C-1180.52 2223.19 -1272.48 2170.03 -1276.98 2049.67C-1280.48 1962.91 -1249.49 1877.15 -1212.01 1800.42C-1121.55 1615.36 -1019.59 1436.32 -907.143 1263.8C-676.246 908.728 -401.369 582.243 -90.5083 294.876C-15.042 225.166 62.4233 157.462 141.888 92.2654C134.891 92.2654 127.894 92.2654 120.897 92.2654C363.289 280.834 387.778 617.851 436.756 898.196C468.242 1078.74 507.724 1258.28 553.704 1435.82C577.193 1526.09 602.681 1615.86 630.169 1705.13C652.659 1778.85 672.151 1857.59 707.135 1926.3C730.625 1971.93 766.609 2018.07 820.584 2025.6C873.061 2033.12 916.541 2001.02 947.527 1961.4C993.007 1902.22 1021.49 1828.5 1052.98 1761.3C1090.46 1681.56 1126.45 1600.81 1160.43 1519.57C1293.87 1201.11 1400.82 871.114 1479.79 535.101C1568.75 154.453 1621.73 -234.721 1638.22 -625.901C1638.72 -640.946 1617.73 -645.961 1610.23 -633.423C1433.81 -330.008 1351.85 25.0627 1381.33 375.118C1405.82 668.503 1522.27 980.444 1824.63 1079.74C1975.57 1129.39 2140.49 1122.87 2295.42 1096.79C2456.85 1069.71 2615.28 1022.07 2764.21 954.365C3058.58 820.461 3312.97 597.79 3447.41 298.888C3510.88 157.963 3547.36 3.99902 3545.37 -150.968C3543.87 -284.872 3519.88 -446.359 3413.92 -540.142C3292.48 -647.967 3131.55 -584.275 3018.1 -498.015C2898.15 -406.74 2797.7 -290.389 2719.23 -162.001C2343.9 450.847 2482.34 1240.73 2687.25 1888.18C2891.66 2533.13 3229.01 3135.44 3673.31 3644.98C3727.78 3707.67 3784.26 3768.85 3842.23 3828.53C3856.23 3842.58 3875.22 3822.01 3863.22 3807.47C3264.49 3045.67 2626.28 2314.97 1950.58 1620.37C1279.38 930.794 571.696 275.317 -167.974 -340.54C-523.315 -636.432 -894.648 -913.267 -1244.99 -1214.68C-1410.92 -1357.61 -1570.85 -1508.06 -1715.78 -1672.56C-1790.75 -1757.81 -1861.72 -1847.08 -1926.69 -1940.36C-1955.67 -1982.49 -1984.16 -2025.12 -2010.65 -2068.75C-2024.14 -2090.82 -2037.14 -2112.88 -2049.63 -2134.95C-2068.62 -2168.05 -2068.12 -2183.1 -2051.63 -2216.7C-1960.67 -2399.25 -1707.28 -2433.35 -1527.86 -2451.41C-1289.97 -2474.98 -1050.08 -2462.44 -814.684 -2420.81C-339.896 -2337.06 109.402 -2144.48 532.713 -1918.3C1358.34 -1477.97 2107.01 -885.183 2999.11 -577.254C3211.01 -504.033 3428.92 -448.365 3651.32 -418.776C3762.27 -403.731 3873.72 -395.205 3985.67 -393.7C4032.15 -393.199 4055.14 -383.169 4078.13 -425.296C4100.62 -466.42 4113.11 -512.559 4116.61 -559.701C4131.6 -746.263 4019.15 -923.298 3880.71 -1038.65C3743.78 -1152.49 3572.35 -1217.18 3397.93 -1243.26C2972.12 -1306.45 2573.8 -1133.43 2204.96 -937.841C2102.01 -883.177 2000.06 -827.007 1897.1 -772.844C1800.15 -721.689 1649.21 -611.357 1539.26 -680.064C1457.3 -731.218 1438.31 -847.569 1427.31 -935.835C1411.32 -1065.73 1401.32 -1196.62 1392.83 -1327.01C1359.34 -1846.58 1374.84 -2369.16 1439.31 -2885.72C1455.8 -3020.12 1476.29 -3154.53 1499.78 -3287.93C1518.77 -3395.75 1531.77 -3512.1 1576.75 -3613.41C1622.23 -3714.72 1712.69 -3780.41 1826.13 -3739.79C1935.09 -3700.67 2017.05 -3600.87 2093.52 -3518.12C2639.27 -2925.84 3008.61 -2166.05 3662.81 -1677.57C3834.24 -1549.69 4032.15 -1422.8 4247.55 -1394.22C4423.47 -1370.65 4626.38 -1430.83 4720.34 -1592.82C4774.31 -1686.1 4776.31 -1795.93 4764.82 -1900.24C4752.33 -2014.59 4729.34 -2128.43 4703.85 -2240.77C4652.37 -2466.45 4580.9 -2687.12 4490.94 -2899.76C4399.98 -3113.9 4290.03 -3319.52 4162.59 -3514.11C4159.09 -3519.63 4152.09 -3523.14 4145.6 -3521.13C3724.79 -3372.18 3575.85 -2911.79 3364.95 -2562.24C3246.5 -2365.65 3087.57 -2167.05 2872.67 -2072.76C2646.27 -1972.96 2403.38 -2042.67 2235.95 -2218.2C2021.05 -2443.88 1973.57 -2764.35 1931.59 -3059.74C1924.09 -3054.22 1917.09 -3048.21 1909.6 -3042.69C2287.93 -2876.69 2445.36 -2460.43 2600.79 -2107.37C2641.77 -2014.09 2683.75 -1921.31 2730.73 -1831.04C2789.7 -1717.69 2857.17 -1609.37 2917.15 -1496.02C3028.1 -1285.89 3115.06 -1041.15 3043.09 -802.935C2974.62 -576.752 2768.21 -488.486 2572.8 -394.703C2484.34 -352.075 2395.88 -305.434 2323.91 -237.73C2244.95 -163.506 2191.97 -67.2158 2148.99 31.0806C2060.53 234.193 2000.56 473.916 1801.65 599.796C1689.2 671.011 1570.75 661.984 1452.8 608.322C1352.85 563.186 1260.39 511.028 1152.44 485.953C757.613 395.179 289.822 581.24 121.397 965.399C23.9407 1188.07 78.4163 1434.81 181.37 1645.45C275.328 1837.53 412.767 2007.04 574.694 2145.96C934.034 2453.89 1398.33 2598.32 1866.12 2614.37C2115.5 2622.9 2370.39 2599.33 2613.78 2544.16C2807.7 2500.03 3002.11 2428.81 3160.54 2304.94C3317.47 2182.57 3420.42 2008.04 3432.42 1806.94C3447.41 1562.2 3342.96 1325.48 3230.01 1114.85C3110.56 892.178 2970.12 668.503 2934.14 413.734C2901.15 182.036 2975.12 -40.1338 3084.57 -241.742C3191.02 -438.335 3325.46 -617.375 3438.41 -809.956C3562.86 -1022.1 3638.32 -1249.78 3701.3 -1487C3813.75 -1912.78 3944.19 -2454.92 4398.98 -2639.47C4655.37 -2743.79 4936.74 -2674.08 5187.63 -2588.82C5409.03 -2513.59 5631.93 -2432.35 5841.34 -2327.03C6039.25 -2227.73 6227.17 -2100.85 6366.6 -1926.32C6385.09 -1903.25 6413.58 -1880.18 6401.59 -1851.6C6393.09 -1831.54 6377.1 -1812.48 6364.6 -1794.93C6331.62 -1748.79 6294.13 -1706.16 6252.65 -1667.04C6175.19 -1593.32 6085.73 -1533.64 5991.27 -1485.49C5795.36 -1385.19 5576.96 -1332.03 5359.55 -1306.95C5109.67 -1277.87 4856.78 -1279.37 4606.39 -1306.45C4093.62 -1362.12 3595.84 -1526.12 3131.55 -1747.78C2901.15 -1857.61 2678.25 -1982.99 2462.35 -2118.9C2253.44 -2250.8 2050.53 -2391.73 1837.63 -2517.1C1631.22 -2638.47 1413.82 -2745.79 1181.42 -2808.48C987.01 -2861.14 708.634 -2899.26 600.183 -2679.59C564.699 -2607.88 567.698 -2522.62 614.677 -2456.92C653.659 -2402.26 715.631 -2371.67 777.604 -2349.6C929.536 -2295.44 1136.94 -2285.41 1221.91 -2125.42C1314.36 -1951.4 1236.9 -1736.25 1320.36 -1558.71C1336.35 -1524.61 1351.85 -1491.51 1390.33 -1484.49C1431.31 -1476.97 1473.79 -1481.48 1514.27 -1490.01C1602.23 -1508.06 1698.69 -1560.72 1790.15 -1544.67C1913.6 -1523.11 1904.1 -1381.18 1935.59 -1289.4C1960.57 -1217.18 2031.54 -1138.45 2114.01 -1170.54C2126.5 -1175.56 2129.5 -1196.12 2114.01 -1199.63C1738.17 -1284.89 1346.35 -1264.33 963.52 -1261.82C559.201 -1258.81 155.382 -1254.8 -248.937 -1249.28C-653.256 -1243.76 -1057.08 -1237.24 -1461.39 -1229.72C-1607.33 -1226.71 -1760.76 -1235.24 -1901.2 -1187.59C-1954.17 -1170.04 -2006.15 -1143.96 -2045.13 -1102.84C-2069.12 -1077.76 -2115.1 -1015.58 -2102.61 -978.464C-2094.11 -953.388 -2051.13 -923.297 -2032.14 -904.741C-2002.15 -875.654 -1971.17 -848.07 -1939.18 -820.989C-1882.21 -773.345 -1821.73 -728.711 -1759.26 -688.088C-1628.32 -602.33 -1487.88 -532.619 -1342.95 -474.444C-1034.59 -350.57 -707.732 -280.86 -380.878 -229.706C393.275 -108.34 1180.42 -96.8047 1961.57 -138.932C2357.4 -160.497 2752.72 -195.603 3147.04 -237.229C3299.97 -253.277 3452.91 -275.343 3606.84 -269.325C3737.28 -264.31 3877.22 -242.243 3985.67 -165.01C4039.14 -126.896 4079.13 -76.2429 4106.11 -17.0645C4107.11 -23.0825 4107.61 -29.1011 4108.61 -35.1194C3950.68 91.7634 3761.27 165.486 3566.86 215.136C3360.95 267.795 3149.04 300.894 2938.14 327.976C2498.83 384.146 2054.03 397.686 1612.23 369.602C1391.83 355.559 1174.43 325.469 955.524 296.381C882.057 286.852 801.093 287.354 746.618 346.03C706.136 389.161 699.139 449.843 704.136 506.514C711.133 583.246 735.123 656.969 753.115 731.192C775.604 825.477 794.096 939.821 678.148 974.426C590.687 1001.01 487.733 984.456 398.273 981.447C283.324 977.937 168.876 974.426 53.9272 970.915C-457.844 954.867 -970.115 939.32 -1481.89 923.271C-1496.38 922.77 -1502.88 944.334 -1489.38 951.356C-997.103 1210.14 -504.823 1468.42 -12.543 1727.2C213.356 1846.05 437.256 1975.95 676.149 2067.22C893.552 2149.97 1134.44 2194.1 1360.34 2118.38C1567.25 2049.17 1734.68 1898.21 1882.11 1741.74C2036.54 1578.25 2175.48 1399.71 2297.42 1210.14C2542.81 827.984 2718.24 399.191 2807.7 -46.6538C2910.65 -558.197 2903.65 -1082.28 2914.65 -1601.84C2920.14 -1863.63 2930.64 -2126.43 2962.63 -2386.71C2979.12 -2522.62 3001.61 -2658.03 3032.59 -2791.43C3058.08 -2901.26 3091.57 -3012.1 3152.54 -3107.89C3214.01 -3203.67 3303.97 -3276.9 3416.92 -3299.46C3521.88 -3320.53 3628.33 -3290.94 3694.3 -3203.67C3788.26 -3078.8 3792.25 -2890.73 3918.7 -2789.43C4073.63 -2665.05 4290.53 -2788.42 4421.97 -2888.72C4574.4 -3005.08 4722.84 -3181.11 4919.25 -3219.22C4966.73 -3228.25 5018.71 -3228.75 5063.69 -3210.19C5129.16 -3183.11 5150.65 -3122.93 5151.65 -3056.23C5153.15 -2924.33 5136.65 -2789.93 5124.16 -2658.53C5112.16 -2535.66 5110.66 -2394.73 5047.19 -2285.41C4983.72 -2175.57 4858.78 -2132.95 4747.33 -2089.31C4622.38 -2040.17 4496.94 -1991.02 4372 -1941.87C4255.55 -1896.23 4137.1 -1840.56 4012.16 -1822.01C3875.72 -1801.45 3746.28 -1845.08 3624.83 -1904.26C3517.38 -1956.91 3413.92 -2018.1 3313.97 -2083.3C2909.65 -2347.59 2563.3 -2689.12 2171.48 -2970.47C1795.15 -3240.28 1352.35 -3468.47 879.558 -3472.49C681.146 -3473.99 450.25 -3449.42 328.804 -3271.88C225.85 -3121.43 219.853 -2923.33 205.359 -2747.8C184.869 -2502.56 113.401 -2271.36 67.4211 -2030.64C28.4387 -1824.01 25.4402 -1617.89 96.4084 -1417.79C220.353 -1067.73 522.218 -781.37 880.557 -684.578C1078.47 -630.916 1276.88 -654.487 1476.79 -683.073C1689.7 -713.665 1933.59 -756.795 2132 -646.463C2337.9 -531.616 2415.37 -286.377 2401.38 -62.2007C2392.88 71.2017 2356.4 201.094 2312.42 326.472C2278.93 422.762 2201.97 545.131 2259.44 645.935C2281.43 684.05 2314.92 715.144 2344.9 746.237C2386.88 789.869 2428.86 832.999 2472.84 874.625C2549.81 946.842 2632.27 1014.55 2724.73 1066.2C2925.64 1178.04 3139.05 1174.03 3351.45 1097.8C3463.4 1057.68 3570.35 1005.52 3679.31 958.377C3795.25 908.227 3912.7 862.588 4032.15 821.464C4262.54 741.724 4498.94 679.035 4737.83 632.896C5220.62 540.116 5716.89 515.542 6206.17 561.681C6447.57 584.249 6686.96 624.37 6922.85 681.041C6941.85 685.555 6949.84 656.467 6930.85 651.953C5984.77 424.267 4977.72 469.904 4055.64 781.343C3940.19 820.461 3826.24 863.591 3713.79 910.734C3600.34 958.377 3489.89 1013.04 3375.44 1056.67C3258.99 1100.81 3135.05 1133.91 3009.6 1124.88C2894.66 1116.35 2786.21 1071.72 2690.25 1009.53C2594.29 947.846 2509.83 870.111 2429.36 789.869C2394.38 754.763 2359.4 719.156 2325.91 682.546C2305.42 659.978 2284.93 636.908 2274.93 607.319C2259.44 562.183 2273.93 517.046 2289.43 474.418C2370.89 250.743 2458.35 19.5459 2425.87 -223.186C2399.38 -420.281 2294.92 -606.342 2109.51 -690.596C1923.09 -775.351 1709.69 -745.762 1514.27 -718.179C1299.87 -688.088 1088.46 -656.493 876.059 -716.674C685.645 -770.837 512.222 -880.167 375.283 -1023.1C238.344 -1166.03 136.89 -1344.07 92.4099 -1537.65C37.9343 -1773.36 89.4114 -2009.07 145.386 -2239.27C174.374 -2357.62 203.86 -2475.48 219.853 -2596.34C233.347 -2694.64 237.845 -2793.94 247.841 -2892.24C269.331 -3094.34 322.807 -3315.01 533.713 -3394.25C646.662 -3436.38 775.104 -3443.4 895.051 -3441.39C1015.5 -3438.88 1135.44 -3421.33 1252.39 -3392.24C1487.79 -3333.06 1709.69 -3228.25 1916.59 -3102.37C2327.91 -2852.62 2675.25 -2514.1 3058.58 -2225.73C3248.5 -2082.79 3450.41 -1944.38 3669.31 -1849.59C3773.76 -1804.45 3884.21 -1775.87 3998.66 -1789.41C4119.11 -1803.95 4232.56 -1854.61 4344.51 -1898.74C4469.45 -1947.89 4594.9 -1997.04 4719.84 -2046.18C4816.8 -2084.3 4922.75 -2117.9 5002.71 -2187.11C5086.68 -2259.33 5116.66 -2365.15 5131.66 -2470.96C5149.65 -2597.35 5159.64 -2725.23 5168.64 -2852.11C5173.14 -2914.8 5178.63 -2977.49 5179.63 -3040.18C5180.63 -3089.33 5175.64 -3139.48 5146.65 -3180.6C5101.67 -3244.8 5017.21 -3260.85 4943.74 -3253.32C4755.32 -3234.27 4604.39 -3078.3 4465.45 -2963.45C4326.52 -2848.6 4057.64 -2656.52 3901.21 -2846.1C3797.75 -2971.47 3798.75 -3159.54 3671.31 -3269.87C3536.37 -3386.23 3325.46 -3338.08 3205.52 -3223.23C3045.59 -3070.27 3001.61 -2828.54 2964.62 -2619.91C2873.67 -2106.37 2885.66 -1583.29 2871.67 -1063.72C2858.17 -557.193 2812.19 -53.1736 2643.27 427.275C2488.84 867.604 2249.44 1277.84 1942.08 1627.9C1792.65 1797.41 1625.72 1970.43 1416.32 2063.21C1177.42 2168.53 917.541 2127.91 681.146 2037.63C433.258 1943.35 200.862 1806.43 -33.5337 1683.06C-276.925 1555.18 -520.316 1427.29 -763.707 1299.41C-1001.6 1174.53 -1239.49 1049.65 -1477.39 924.776C-1479.89 934.305 -1482.39 943.332 -1484.88 952.86C-1019.59 967.404 -553.801 981.447 -88.5088 995.991C133.892 1003.01 358.791 1020.06 581.191 1016.05C644.663 1015.05 718.13 1006.02 763.11 955.87C813.087 900.704 800.593 820.963 786.1 754.262C758.612 623.869 641.165 339.009 866.564 321.457C915.042 317.444 964.52 327.977 1012 335.499C1066.97 344.025 1121.95 351.547 1176.92 358.568C1290.37 373.112 1404.32 384.647 1518.77 393.173C1963.57 427.276 2411.37 418.75 2854.17 367.596C3089.57 340.514 3325.96 305.408 3556.36 247.734C3761.27 196.58 3960.68 119.347 4126.6 -14.0552C4132.1 -18.5688 4131.6 -26.5933 4129.1 -32.1099C4022.65 -264.31 3743.28 -304.431 3515.88 -299.917C3354.95 -296.407 3194.52 -272.334 3035.09 -255.784C2840.68 -235.724 2646.27 -217.669 2451.85 -202.123C1681.7 -140.437 905.546 -119.875 135.891 -194.098C-217.951 -228.201 -573.292 -278.353 -917.638 -367.12C-1225 -446.359 -1527.37 -560.704 -1789.75 -742.251C-1857.22 -788.892 -1921.19 -839.545 -1981.66 -895.213C-2008.65 -920.289 -2036.14 -945.364 -2060.63 -972.948C-2075.12 -988.996 -2076.12 -992.005 -2069.12 -1012.57C-2062.63 -1030.62 -2053.63 -1047.67 -2042.13 -1063.22C-1964.17 -1167.53 -1806.24 -1188.6 -1685.79 -1194.62C-1494.38 -1204.14 -1301.47 -1202.64 -1110.05 -1206.15C-914.14 -1209.66 -718.227 -1212.67 -522.315 -1215.68C-127.491 -1221.2 267.332 -1226.21 662.155 -1229.22C858.067 -1230.72 1053.48 -1232.23 1249.39 -1233.23C1436.31 -1234.24 1623.23 -1234.74 1809.64 -1217.18C1908.6 -1207.66 2006.55 -1192.61 2103.51 -1170.54C2103.51 -1180.07 2103.51 -1190.1 2103.51 -1199.63C2012.05 -1164.52 1963.07 -1281.38 1948.58 -1351.09C1935.59 -1412.77 1926.59 -1480.98 1880.61 -1528.12C1800.65 -1610.87 1681.2 -1569.25 1586.74 -1541.66C1529.27 -1524.61 1455.8 -1498.03 1395.33 -1514.08C1337.85 -1529.63 1318.86 -1638.45 1310.86 -1689.11C1290.87 -1817.49 1308.87 -1951.4 1270.88 -2076.78C1200.41 -2308.47 953.025 -2311.99 763.61 -2386.71C645.662 -2433.35 559.201 -2541.68 626.171 -2671.07C715.631 -2843.59 930.535 -2835.56 1092.96 -2799.46C1412.32 -2728.74 1702.69 -2568.76 1977.07 -2395.74C2255.94 -2220.21 2526.82 -2033.14 2816.19 -1874.67C3395.43 -1558.21 4031.65 -1324.01 4692.35 -1268.34C5000.71 -1242.26 5319.57 -1251.79 5621.44 -1323C5882.32 -1384.69 6144.7 -1499.54 6325.62 -1703.65C6368.1 -1751.8 6405.58 -1804.45 6437.07 -1860.12C6439.57 -1864.64 6440.07 -1871.16 6437.07 -1875.17C6274.14 -2112.89 6031.25 -2271.86 5773.87 -2390.72C5642.43 -2451.91 5506.99 -2503.56 5371.05 -2553.21C5207.12 -2613.39 5041.7 -2674.58 4869.27 -2704.17C4707.85 -2731.75 4540.42 -2729.75 4386.49 -2668.06C4266.04 -2619.41 4161.09 -2539.17 4075.13 -2442.38C3873.22 -2215.19 3780.26 -1922.81 3704.29 -1633.94C3660.31 -1467.94 3619.83 -1300.43 3559.36 -1138.95C3503.88 -989.999 3427.42 -852.584 3342.96 -718.68C3190.52 -477.453 3009.6 -242.243 2929.14 36.0962C2889.66 172.508 2880.16 314.435 2905.15 454.357C2933.64 613.838 3002.61 763.289 3077.07 905.719C3232 1202.11 3447.41 1523.08 3389.94 1875.14C3341.46 2169.53 3088.57 2352.58 2826.19 2449.37C2525.32 2560.71 2187.97 2595.31 1869.61 2585.28C1238.4 2565.73 589.188 2293.91 255.837 1730.71C171.375 1587.78 104.405 1426.29 94.9089 1258.28C84.4136 1077.74 154.383 912.238 274.329 779.839C467.742 567.699 767.108 465.892 1049.48 499.995C1119.45 508.521 1188.92 525.572 1254.89 551.65C1328.86 580.738 1397.33 622.364 1471.29 651.953C1600.74 703.609 1732.18 688.062 1843.63 603.307C2083.52 420.756 2100.01 100.289 2266.44 -132.412C2434.36 -367.12 2751.22 -386.679 2946.13 -587.284C3147.04 -794.409 3113.56 -1115.88 3009.6 -1361.12C2943.13 -1517.09 2851.68 -1660.02 2771.21 -1808.47C2705.74 -1929.83 2650.27 -2056.21 2594.79 -2182.6C2491.34 -2418.31 2387.38 -2665.55 2210.96 -2857.13C2128.5 -2946.4 2031.04 -3019.12 1919.59 -3067.76C1907.1 -3073.28 1895.6 -3064.25 1897.6 -3050.71C1936.08 -2782.91 1977.57 -2498.05 2142.99 -2275.38C2276.43 -2095.33 2483.84 -1968.95 2713.24 -1998.04C2933.64 -2025.62 3114.06 -2181.09 3248 -2348.6C3394.93 -2532.15 3488.89 -2746.8 3604.34 -2950.41C3732.28 -3176.09 3894.21 -3401.77 4148.1 -3491.54C4142.6 -3494.05 4136.6 -3496.06 4131.1 -3498.56C4394.49 -3096.35 4582.4 -2646.49 4682.36 -2175.57C4729.34 -1953.4 4808.3 -1653 4587.4 -1498.53C4383.49 -1356.1 4127.6 -1428.32 3927.69 -1538.15C3198.52 -1937.86 2795.7 -2717.71 2282.93 -3339.58C2217.96 -3418.82 2150.49 -3496.06 2080.52 -3570.28C2011.55 -3643.5 1939.08 -3722.74 1844.63 -3762.36C1767.16 -3794.46 1680.2 -3787.44 1616.23 -3729.76C1556.26 -3675.1 1527.27 -3592.35 1508.28 -3515.11C1449.3 -3274.39 1418.82 -3022.63 1391.83 -2776.39C1362.84 -2516.1 1346.35 -2254.81 1342.35 -1993.02C1338.35 -1731.23 1346.35 -1469.44 1366.84 -1208.16C1375.84 -1093.81 1381.83 -976.458 1403.82 -863.617C1418.32 -789.394 1445.8 -706.143 1510.28 -660.003C1588.24 -604.335 1681.2 -633.925 1760.16 -671.037C1870.11 -723.194 1976.57 -782.874 2083.52 -840.548C2295.42 -954.893 2509.33 -1070.24 2739.23 -1145.97C3101.56 -1265.33 3552.86 -1272.85 3860.22 -1012.57C4012.16 -883.678 4153.59 -645.961 4051.14 -446.359C4035.65 -415.767 4012.66 -422.788 3981.17 -423.289C3954.18 -423.791 3927.69 -424.293 3900.71 -425.296C3842.23 -427.803 3783.76 -432.317 3725.78 -438.335C3611.34 -450.371 3497.39 -469.93 3385.44 -495.508C2459.85 -704.638 1683.2 -1285.39 877.559 -1756.31C464.244 -1998.04 32.9368 -2219.21 -426.858 -2357.12C-648.258 -2423.32 -875.657 -2469.46 -1106.55 -2486.01C-1321.46 -2501.06 -1550.35 -2498.05 -1760.26 -2444.38C-1841.22 -2423.82 -1922.19 -2392.23 -1988.66 -2340.57C-2017.65 -2318 -2043.63 -2291.93 -2064.13 -2261.33C-2079.12 -2238.76 -2108.61 -2191.62 -2104.11 -2163.04C-2100.11 -2140.47 -2076.12 -2111.88 -2064.62 -2092.32C-2051.63 -2070.26 -2038.14 -2048.19 -2024.64 -2026.62C-1994.66 -1978.98 -1963.17 -1932.34 -1930.18 -1886.7C-1869.71 -1802.95 -1804.74 -1722.21 -1736.27 -1644.47C-1440.9 -1310.47 -1087.06 -1036.14 -739.218 -760.807C22.4414 -162.001 749.116 475.922 1440.31 1151.96C2128.5 1825.49 2782.21 2534.63 3396.43 3276.87C3547.36 3458.92 3695.8 3643.48 3842.23 3829.54C3849.23 3822.52 3856.23 3815.49 3863.22 3808.47C3424.42 3353.6 3075.58 2813.97 2840.18 2226.7C2723.23 1934.82 2633.77 1631.41 2575.3 1321.97C2508.83 969.411 2483.84 598.793 2577.8 249.239C2654.76 -37.125 2815.69 -323.99 3065.58 -493.501C3138.05 -542.649 3227.51 -582.771 3315.97 -559.199C3390.43 -539.139 3440.41 -473.441 3469.4 -405.737C3528.87 -267.319 3525.87 -98.8108 3497.39 46.6277C3436.91 357.565 3240 624.37 2980.62 800.401C2727.73 971.918 2407.87 1080.24 2102.01 1087.27C1946.08 1090.78 1782.65 1060.18 1659.71 957.875C1544.26 862.086 1478.29 720.66 1442.81 576.726C1365.34 261.275 1405.32 -86.7749 1523.27 -386.679C1554.76 -466.42 1592.74 -543.151 1635.72 -617.375C1626.22 -619.882 1617.23 -622.39 1607.73 -624.897C1579.74 39.105 1444.81 697.591 1208.91 1318.96C1147.94 1479.95 1079.47 1638.43 1005 1793.4C975.515 1855.08 945.528 1943.85 884.555 1982.47C805.591 2032.62 747.617 1950.37 720.629 1885.17C656.158 1728.7 615.176 1557.18 573.195 1393.69C529.714 1224.68 493.231 1053.66 463.744 882.147C414.766 599.294 386.279 261.777 141.888 71.7034C134.891 66.1868 127.394 66.1868 120.897 71.7034C-202.458 337.003 -492.328 640.92 -742.716 976.432C-869.16 1145.44 -984.608 1322.48 -1089.56 1506.03C-1142.04 1597.81 -1191.52 1691.09 -1237.49 1785.87C-1270.98 1854.58 -1297.47 1927.3 -1304.96 2004.03C-1310.96 2067.72 -1301.97 2138.44 -1256.49 2186.58C-1196.01 2250.77 -1104.55 2244.26 -1026.09 2225.7C-786.197 2169.03 -548.303 2099.32 -309.91 2036.13C167.377 1909.25 644.663 1782.86 1121.95 1655.98C1319.36 1603.82 1553.76 1509.04 1759.16 1567.21C1967.07 1626.39 2029.04 1856.59 2053.53 2047.16C2074.02 2207.65 2068.53 2386.18 1995.06 2533.63C1960.07 2603.84 1908.6 2664.52 1839.63 2702.64C1737.17 2759.31 1617.73 2754.29 1505.78 2735.24C1107.96 2666.53 717.631 2516.58 364.289 2323.5C9.44727 2129.91 -315.907 1882.67 -596.781 1591.79C-743.716 1439.33 -872.158 1273.33 -1002.1 1106.83C-1131.54 941.326 -1269.48 778.334 -1446.4 661.482C-1605.83 556.165 -1799.74 486.454 -1992.66 512.533C-2152.59 534.098 -2306.02 627.379 -2392.98 764.292C-2436.46 831.996 -2459.95 909.229 -2461.45 989.972C-2461.45 998.498 -2454.45 1004.52 -2446.45 1005.02C-1285.47 1059.18 -264.93 413.735 741.62 -71.7295C868.563 -132.914 996.006 -192.092 1125.45 -247.76C1237.9 -295.905 1364.84 -363.108 1491.28 -353.078C1664.21 -339.035 1658.71 -150.968 1633.72 -20.575C1621.73 42.114 1602.73 103.8 1576.75 161.975C1548.76 225.166 1507.28 308.417 1434.31 327.976C1355.34 349.04 1316.86 252.248 1287.38 197.082C1254.89 135.897 1224.9 72.7065 1197.42 9.0144C1145.44 -112.853 1103.46 -238.231 1067.47 -365.615C995.506 -620.885 949.027 -882.173 901.548 -1142.96C877.059 -1276.36 852.57 -1410.27 824.583 -1543.17C802.093 -1650.49 787.099 -1774.36 728.625 -1869.65C666.153 -1972.46 561.7 -1980.49 452.749 -1958.42C335.801 -1934.85 219.353 -1903.75 105.404 -1869.15C-126.992 -1798.44 -354.89 -1704.15 -560.298 -1572.76C-749.713 -1451.89 -939.128 -1292.91 -1053.08 -1095.82C-1154.03 -921.793 -1194.01 -671.538 -993.104 -551.677C-897.647 -494.504 -781.199 -482.969 -672.748 -474.444C-534.809 -463.912 -395.372 -466.42 -257.933 -481.465C3.94971 -510.553 259.835 -584.777 496.229 -702.632C713.632 -810.959 910.544 -953.89 1104.46 -1099.33C1297.37 -1244.27 1487.79 -1393.21 1695.69 -1516.09C1803.14 -1579.78 1915.09 -1635.95 2032.54 -1679.08C2160.48 -1726.22 2303.42 -1768.35 2440.36 -1741.77C2563.8 -1717.69 2691.25 -1616.89 2687.25 -1479.47C2684.25 -1381.68 2607.28 -1312.97 2518.82 -1286.89C2424.37 -1258.81 2316.42 -1272.35 2238.95 -1335.04C2149.49 -1406.76 2101.01 -1515.58 2078.52 -1625.41C2031.04 -1855.11 2071.52 -2119.91 2181.98 -2326.03C2206.96 -2372.67 2235.95 -2417.3 2269.94 -2457.42C2303.92 -2498.05 2355.9 -2560.74 2406.37 -2580.29C2424.87 -2587.32 2425.87 -2589.32 2438.36 -2569.26C2445.86 -2557.22 2446.86 -2540.17 2446.36 -2526.13C2444.86 -2496.54 2432.36 -2467.45 2418.37 -2442.38C2386.88 -2386.21 2336.41 -2343.08 2288.93 -2300.95C2094.51 -2127.93 1864.62 -1995.03 1618.73 -1912.28C1562.75 -1893.72 1505.78 -1877.17 1447.8 -1863.63C1372.34 -1846.08 1290.37 -1827.02 1214.41 -1851.1C1096.96 -1888.21 1032.49 -2011.58 1008.5 -2124.92C957.523 -2370.16 1063.48 -2643.49 915.042 -2867.66C856.068 -2957.43 767.108 -3004.07 662.155 -3019.12C530.714 -3037.67 392.775 -3035.17 260.335 -3027.64C-206.956 -3002.57 -717.727 -2809.99 -912.64 -2346.59C-952.622 -2251.8 -996.103 -2123.42 -949.624 -2023.11C-909.642 -1936.85 -813.185 -1900.75 -725.724 -1884.7C-463.841 -1835.55 -182.467 -1858.12 78.4165 -1904.76C649.161 -2007.07 1173.93 -2264.34 1688.2 -2521.62C1948.58 -2651.51 2208.46 -2790.43 2480.84 -2894.24C2612.28 -2944.39 2750.72 -2981.5 2892.66 -2973.48C3030.1 -2965.96 3161.04 -2915.3 3278.98 -2846.1C3390.93 -2780.4 3493.39 -2698.15 3589.35 -2610.39C3687.8 -2520.61 3781.76 -2414.29 3806.25 -2278.38C3852.73 -2016.59 3654.82 -1785.4 3430.92 -1685.6C3196.02 -1580.28 2917.15 -1569.75 2664.26 -1592.82C2405.88 -1615.89 2150.99 -1686.6 1917.09 -1798.94C1461.8 -2017.6 1099.46 -2394.73 891.553 -2855.62C790.098 -3080.3 727.626 -3321.53 707.135 -3567.27C703.636 -3586.83 673.65 -3586.83 675.149 -3567.77Z" fill="#E6007A"/> -</g> -<defs> -<linearGradient id="paint0_linear" x1="-237.178" y1="175.18" x2="4868.84" y2="170.068" gradientUnits="userSpaceOnUse"> -<stop stop-color="#F79420" stop-opacity="0.92"/> -<stop offset="1" stop-color="#C4C4C4" stop-opacity="0"/> -</linearGradient> -</defs> -</svg> +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 3921 692"><defs><linearGradient id="b" x1="93.98" y1="344.13" x2="4159.18" y2="348.2" gradientTransform="matrix(1 0 0 -1 0 692)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#f79420" stop-opacity=".92"/><stop offset="1" stop-color="#c4c4c4" stop-opacity="0"/></linearGradient><mask id="a" x="0" y="0" width="3921" height="692" maskUnits="userSpaceOnUse"><path d="M131.84 681.77h172.68l54.59-200c7.45-27.29 46.14-27.37 53.7-.1l55.49 200.1h172.35L772.8 171H611l-43 203.35c-6.15 29.08-47.49 29.58-54.35.66L465.33 171H307.48l-47.92 205.38c-6.77 29-48.19 28.57-54.36-.56l-15.32-72.32A167.09 167.09 0 0026.42 171H0zM1063.21 691.42c132.8 0 220.13-64.84 238.59-165.26l-148-4.32c-12.52 33.91-45.48 52.2-87.66 52.2C1004.22 574 966 532.48 966 470v-4.32h337.45v-40.61c0-168.25-101.5-260.69-245.18-260.69-152.91 0-251.11 105.74-251.11 264C807.15 592 904 691.42 1063.21 691.42zM966 371.21c2.31-50.88 42.18-89.45 95.57-89.45 53 0 91.28 37.24 91.94 89.45zM1827.66 327C1819.09 225.9 1737 164.38 1600 164.38S1375.53 224.57 1376.19 327c-.66 78.48 49.1 129 150.27 148l88.32 17c41.85 8.31 61 21.61 61.62 44.22-.66 25.61-29 42.57-70.19 42.57-45.81 0-76.78-20-83.7-57.2l-159.17 4c11.53 101.09 97.54 165.93 242.21 165.93 135.44 0 234-68.17 234.64-173.24-.66-74.48-49.44-118.71-150.28-138.33l-97.54-18.62c-42.84-8.64-56.68-23.94-56.35-43.56-.33-25.93 29.66-41.89 66.89-41.89 42.52 0 72.17 22.94 76.79 55.19zM2205.24 171H2114V48.67h-161.19V171h-66.9v119.7h66.9v248.43c-1 103.41 65.58 155.28 179.27 150a335.44 335.44 0 0082.38-14.63l-24.38-117.38c-6.92 1.66-23.07 5-35.26 5-26 0-40.87-10.64-40.87-40.9V290.74h91.29zM2518.63 691.42c132.81 0 220.14-64.84 238.59-165.26l-148-4.32c-12.53 33.91-45.48 52.2-87.66 52.2-62 0-100.18-41.56-100.18-104.08v-4.32h337.45v-40.57c0-168.25-101.5-260.69-245.18-260.69-152.91 0-251.11 105.74-251.11 264 .04 163.62 96.92 263.04 256.09 263.04zm-97.21-320.21c2.3-50.88 42.18-89.45 95.56-89.45 53.06 0 91.29 37.24 91.95 89.45zM3002.32 390.49c.33-58.19 33.94-92.77 85-92.77s81.4 33.92 81.07 90.45v293.6h161.14V356.24c.33-115-69.2-191.86-175.64-191.86-74.81 0-131.49 38.24-153.9 100.42h-5.6V171h-153.22v510.77h161.15zM3614.2 689.09c76.45 0 121.93-42.89 142.36-90.11h4.94v82.79H3921V.79h-161.15v257.7h-3.29c-19.12-46.89-63.27-94.11-143-94.11-105.79 0-201.68 82.14-201.68 262.36-.02 173.9 90.6 262.35 202.32 262.35zm56-126.69c-59.65 0-92.93-53.53-92.93-136 0-82.13 32.95-135 92.93-135 59 0 93.26 51.54 93.26 135 .02 82.8-34.58 136-93.24 136z" fill="#fff"/></mask></defs><g mask="url(#a)"><path d="M0 0h3921v692H0z" fill="#e6007a"/><path d="M0 0h3921v692H0z" fill="url(#b)"/></g></svg> diff --git a/src/index.tsx b/src/index.tsx deleted file mode 100644 index 66c68843bd..0000000000 --- a/src/index.tsx +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import '@rossbulat/polkadot-dashboard-ui/index.css'; -import App from 'App'; -import 'index.css'; -import { createRoot } from 'react-dom/client'; -import reportWebVitals from './reportWebVitals'; - -const rootElement = document.getElementById('root'); -if (!rootElement) throw new Error('Failed to find the root element'); -const root = createRoot(rootElement); - -root.render(<App />); - -// If you want to start measuring performance in your app, pass a function -// to log results (for example: reportWebVitals(console.log)) -// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals -reportWebVitals(); diff --git a/src/library/Account/index.tsx b/src/library/Account/Default.tsx similarity index 56% rename from src/library/Account/index.tsx rename to src/library/Account/Default.tsx index 38037b71cc..c3b2d57f96 100644 --- a/src/library/Account/index.tsx +++ b/src/library/Account/Default.tsx @@ -1,19 +1,17 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only import { faGlasses } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { useConnect } from 'contexts/Connect'; -import { useTheme } from 'contexts/Themes'; +import { ellipsisFn, remToUnit } from '@polkadot-cloud/utils'; import { useEffect, useState } from 'react'; -import { defaultThemes } from 'theme/default'; -import { clipAddress, convertRemToPixels } from 'Utils'; -import Identicon from '../Identicon'; -import { AccountProps } from './types'; -import Wrapper from './Wrapper'; +import { useTranslation } from 'react-i18next'; +import { Polkicon } from '@polkadot-cloud/react'; +import { useImportedAccounts } from 'contexts/Connect/ImportedAccounts'; +import { Wrapper } from './Wrapper'; +import type { AccountProps } from './types'; export const Account = ({ - filled = false, fontSize = '1.05rem', format, value, @@ -23,8 +21,9 @@ export const Account = ({ title, onClick, }: AccountProps) => { - const { mode } = useTheme(); - const { getAccount } = useConnect(); + const { t } = useTranslation('library'); + const { getAccount } = useImportedAccounts(); + const [displayValue, setDisplayValue] = useState<string | undefined>(); const unassigned = value === null || value === undefined || !value.length; @@ -34,14 +33,16 @@ export const Account = ({ switch (format) { case 'name': setDisplayValue( - value !== '' ? getAccount(value)?.name : clipAddress(value) + value !== '' + ? getAccount(value)?.name || ellipsisFn(value) + : ellipsisFn(value) ); break; case 'text': setDisplayValue(value); break; default: - if (value) setDisplayValue(clipAddress(value)); + if (value) setDisplayValue(ellipsisFn(value)); } // if title prop is provided, override `displayValue` @@ -49,13 +50,7 @@ export const Account = ({ }, [value, title]); return ( - <Wrapper - whileHover={{ scale: 1.01 }} - onClick={onClick} - cursor={canClick ? 'pointer' : 'default'} - fill={filled ? defaultThemes.buttons.secondary.background[mode] : 'none'} - fontSize={fontSize} - > + <Wrapper onClick={onClick} $canClick={canClick} $fontSize={fontSize}> {label !== undefined && ( <div className="account-label"> {label}{' '} @@ -67,17 +62,13 @@ export const Account = ({ )} </div> )} - {unassigned ? ( - <span className="title unassigned">Not Staking</span> + <span className="title unassigned">{t('notStaking')}</span> ) : ( <> {format !== 'text' && ( <span className="identicon"> - <Identicon - value={value} - size={convertRemToPixels(fontSize) * 1.4} - /> + <Polkicon address={value} size={remToUnit(fontSize) * 1.4} /> </span> )} <span className="title">{displayValue}</span> @@ -86,5 +77,3 @@ export const Account = ({ </Wrapper> ); }; - -export default Account; diff --git a/src/library/PoolAccount/index.tsx b/src/library/Account/Pool.tsx similarity index 54% rename from src/library/PoolAccount/index.tsx rename to src/library/Account/Pool.tsx index 33429abccb..0c416f7832 100644 --- a/src/library/PoolAccount/index.tsx +++ b/src/library/Account/Pool.tsx @@ -1,26 +1,29 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only import { u8aToString, u8aUnwrapBytes } from '@polkadot/util'; +import { ellipsisFn, remToUnit } from '@polkadot-cloud/utils'; +import { useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; import { useApi } from 'contexts/Api'; -import { useConnect } from 'contexts/Connect'; import { useBondedPools } from 'contexts/Pools/BondedPools'; -import { useTheme } from 'contexts/Themes'; -import Identicon from 'library/Identicon'; -import { useEffect, useState } from 'react'; -import { defaultThemes } from 'theme/default'; -import { clipAddress, convertRemToPixels } from '../../Utils'; -import { PoolAccountProps } from './types'; -import Wrapper from './Wrapper'; +import { Polkicon } from '@polkadot-cloud/react'; +import { useActiveAccounts } from 'contexts/ActiveAccounts'; +import { Wrapper } from './Wrapper'; +import type { AccountProps } from './types'; -export const PoolAccount = (props: PoolAccountProps) => { - const { mode } = useTheme(); +export const Account = ({ + label, + pool, + onClick, + canClick, + fontSize = '1.05rem', +}: AccountProps) => { + const { t } = useTranslation('library'); const { isReady } = useApi(); - const { activeAccount } = useConnect(); + const { activeAccount } = useActiveAccounts(); const { fetchPoolsMetaBatch, meta } = useBondedPools(); - const { label } = props; - // is this the initial fetch const [fetched, setFetched] = useState(false); @@ -29,7 +32,7 @@ export const PoolAccount = (props: PoolAccountProps) => { // refetch when pool or active account changes useEffect(() => { setFetched(false); - }, [activeAccount, props.pool]); + }, [activeAccount, pool]); // configure pool list when network is ready to fetch useEffect(() => { @@ -44,21 +47,17 @@ export const PoolAccount = (props: PoolAccountProps) => { // handle pool list bootstrapping const getPoolMeta = () => { - const pools: any = [{ id: props.pool.id }]; + const pools: any = [{ id: pool.id }]; fetchPoolsMetaBatch(batchKey, pools, true); }; - const filled = props.filled ?? false; - const fontSize = props.fontSize ?? '1.05rem'; - const { canClick }: { canClick: boolean } = props; - const metaBatch = meta[batchKey]; const metaData = metaBatch?.metadata?.[0]; const syncing = metaData === undefined; // display value - const defaultDisplay = clipAddress(props.pool.addresses.stash); - let display = syncing ? 'Syncing...' : metaData ?? defaultDisplay; + const defaultDisplay = ellipsisFn(pool.addresses.stash); + let display = syncing ? t('syncing') : metaData ?? defaultDisplay; // check if super identity has been byte encoded const displayAsBytes = u8aToString(u8aUnwrapBytes(display)); @@ -71,19 +70,13 @@ export const PoolAccount = (props: PoolAccountProps) => { } return ( - <Wrapper - whileHover={{ scale: 1.01 }} - onClick={props.onClick} - cursor={canClick ? 'pointer' : 'default'} - fill={filled ? defaultThemes.buttons.secondary.background[mode] : 'none'} - fontSize={fontSize} - > + <Wrapper $canClick={canClick} $fontSize={fontSize} onClick={onClick}> {label !== undefined && <div className="account-label">{label}</div>} <span className="identicon"> - <Identicon - value={props.pool.addresses.stash} - size={convertRemToPixels(fontSize) * 1.45} + <Polkicon + address={pool.addresses.stash} + size={remToUnit(fontSize) * 1.4} /> </span> <span className={`title${syncing === true ? ` syncing` : ``}`}> diff --git a/src/library/Account/Wrapper.ts b/src/library/Account/Wrapper.ts index 9408a883b1..6a912c8ce2 100644 --- a/src/library/Account/Wrapper.ts +++ b/src/library/Account/Wrapper.ts @@ -1,35 +1,33 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import { motion } from 'framer-motion'; import styled from 'styled-components'; -import { borderPrimary, borderSecondary, textSecondary } from 'theme'; +import type { WrapperProps } from './types'; -export const Wrapper = styled(motion.button)<any>` - border: 1px solid ${borderPrimary}; - cursor: ${(props) => props.cursor}; - background: ${(props) => props.fill}; - font-size: ${(props) => props.fontSize}; +export const Wrapper = styled.button<WrapperProps>` + border: 1px solid var(--border-primary-color); + transition: transform var(--transition-duration) ease-out; + cursor: ${(props) => (props.$canClick ? 'pointer' : 'default')}; + font-size: ${(props) => props.$fontSize}; border-radius: 1.25rem; box-shadow: none; display: flex; flex-flow: row wrap; - justify-content: flex-start; align-items: center; padding: 0 1rem; - max-width: 225px; + max-width: 235px; flex: 1; - + &:hover { + transform: scale(1.03); + } .identicon { margin: 0.15rem 0.25rem 0 0; } - .account-label { - border-right: 1px solid ${borderSecondary}; - color: ${textSecondary}; + border-right: 1px solid var(--border-secondary-color); + color: var(--text-color-secondary); font-size: 0.8em; display: flex; - flex-flow: row nowrap; align-items: center; justify-content: flex-end; margin-right: 0.5rem; @@ -40,35 +38,27 @@ export const Wrapper = styled(motion.button)<any>` flex-shrink: 1; > svg { - color: ${textSecondary}; + color: var(--text-color-primary); } } .title { - color: ${textSecondary}; + color: var(--text-color-secondary); + font-family: InterSemiBold, sans-serif; margin-left: 0.25rem; text-overflow: ellipsis; white-space: nowrap; overflow: hidden; - line-height: 2.15rem; + line-height: 2.25rem; flex: 1; - &.unassigned { - color: ${textSecondary}; - opacity: 0.45; + &.syncing { + opacity: 0.4; } - } - - .wallet { - width: 1em; - height: 1em; - margin-left: 0.8rem; - opacity: 0.8; - path { - fill: ${textSecondary}; + &.unassigned { + color: var(--text-color-secondary); + opacity: 0.45; } } `; - -export default Wrapper; diff --git a/src/library/Account/types.ts b/src/library/Account/types.ts index d15c297871..a869a08ace 100644 --- a/src/library/Account/types.ts +++ b/src/library/Account/types.ts @@ -1,5 +1,5 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only export interface AccountProps { onClick?: () => void; @@ -7,8 +7,13 @@ export interface AccountProps { format: string; label?: string; canClick: boolean; - filled: boolean; fontSize?: string; title?: string; readOnly?: boolean; + pool?: any; +} + +export interface WrapperProps { + $canClick: boolean; + $fontSize: string; } diff --git a/src/library/AccountInput/Wrapper.ts b/src/library/AccountInput/Wrapper.ts new file mode 100644 index 0000000000..b0292c59a8 --- /dev/null +++ b/src/library/AccountInput/Wrapper.ts @@ -0,0 +1,93 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import styled from 'styled-components'; + +export const AccountInputWrapper = styled.div` + position: relative; + width: 100%; + margin-top: 0.5rem; + + &.inactive { + opacity: 0.5; + } + + .inactive-block { + position: absolute; + z-index: 2; + top: 0; + left: 0; + width: 100%; + height: 100%; + } + + &.border { + > .input { + border: 1px solid var(--border-primary-color); + } + } + + .input { + border: 1px solid transparent; + border-radius: 1rem; + display: flex; + flex-flow: row wrap; + align-items: center; + padding: 0.5rem 0.5rem 0.5rem 1rem; + + &.disabled { + background: var(--background-default); + } + > section { + display: flex; + flex-flow: row wrap; + align-items: center; + + > div { + &:first-child { + padding-right: 0.5rem; + .ph { + background: var(--background-default); + width: 22px; + height: 22px; + border-radius: 50%; + } + } + &:last-child { + display: flex; + flex-flow: column wrap; + flex-grow: 1; + + > input { + font-family: InterSemiBold, sans-serif; + width: 100%; + border: none; + margin: 0; + padding-right: 1rem; + + &:disabled { + opacity: 0.75; + } + } + } + } + + &:first-child { + flex: 1; + } + } + } + h5 { + margin: 0.75rem 0.25rem; + &.neutral { + color: var(--text-color-primary); + opacity: 0.8; + } + &.danger { + color: var(--status-danger-color); + } + &.success { + color: var(--status-success-color); + } + } +`; diff --git a/src/library/AccountInput/index.tsx b/src/library/AccountInput/index.tsx new file mode 100644 index 0000000000..6da53ba134 --- /dev/null +++ b/src/library/AccountInput/index.tsx @@ -0,0 +1,216 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { faCheck } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { ButtonSecondary, Polkicon } from '@polkadot-cloud/react'; +import { isValidAddress } from '@polkadot-cloud/utils'; +import React, { useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useOverlay } from '@polkadot-cloud/react/hooks'; +import { useImportedAccounts } from 'contexts/Connect/ImportedAccounts'; +import { useNetwork } from 'contexts/Network'; +import { formatAccountSs58 } from 'contexts/Connect/Utils'; +import { AccountInputWrapper } from './Wrapper'; +import type { AccountInputProps } from './types'; + +export const AccountInput = ({ + successCallback, + resetCallback, + defaultLabel, + resetOnSuccess = false, + successLabel, + locked = false, + inactive = false, + disallowAlreadyImported = true, + initialValue = null, + border = true, +}: AccountInputProps) => { + const { t } = useTranslation('library'); + + const { + networkData: { ss58 }, + } = useNetwork(); + const { accounts } = useImportedAccounts(); + const { setModalResize } = useOverlay().modal; + + // store current input value + const [value, setValue] = useState(initialValue || ''); + + // store whether current input value is valid + const [valid, setValid] = useState<string | null>(null); + + // store whether address was formatted (displays confirm prompt) + const [reformatted, setReformatted] = useState(false); + + // store whether the form is being submitted. + const [submitting, setSubmitting] = useState<boolean>(false); + + // store whether account input is in success lock state. + const [successLock, setSuccessLocked] = useState<boolean>(locked); + + const handleChange = (e: React.FormEvent<HTMLInputElement>) => { + const newValue = e.currentTarget.value; + // set value on key change + setValue(newValue); + + // reset reformatted if true - value has changed + if (reformatted) { + setReformatted(false); + } + + // reset valid if empty value + if (newValue === '') { + setValid(null); + return; + } + // check address already imported + const alreadyImported = accounts.find( + (a) => a.address.toUpperCase() === newValue.toUpperCase() + ); + if (alreadyImported !== undefined && disallowAlreadyImported) { + setValid('already_imported'); + return; + } + // check if valid address + setValid(isValidAddress(newValue) ? 'valid' : 'not_valid'); + }; + + const handleImport = async () => { + // reformat address if in wrong format + const addressFormatted = formatAccountSs58(value, ss58); + if (addressFormatted) { + setValid('confirm_reformat'); + setValue(addressFormatted); + setReformatted(true); + } else { + // handle successful import. + setSubmitting(true); + const result = await successCallback(value); + setSubmitting(false); + + // reset state on successful import. + if (result && resetOnSuccess) { + resetInput(); + } else { + // flag reset & lock state. + setSuccessLocked(true); + } + } + }; + + // If initial value changes, update current input value. + useEffect(() => { + setValue(initialValue || ''); + }, [initialValue]); + + let label; + let labelClass; + const showSuccess = successLock && successLabel; + + switch (valid) { + case 'confirm_reformat': + label = t('confirmReformat'); + labelClass = 'neutral'; + + break; + case 'already_imported': + label = t('alreadyImported'); + labelClass = 'danger'; + break; + case 'not_valid': + label = t('invalid'); + labelClass = 'danger'; + break; + case 'valid': + label = showSuccess ? successLabel : t('valid'); + labelClass = showSuccess ? 'neutral' : 'success'; + break; + default: + label = showSuccess ? successLabel : defaultLabel; + labelClass = 'neutral'; + break; + } + + const handleConfirm = () => { + setValid('valid'); + setReformatted(false); + handleImport(); + }; + + const resetInput = () => { + setReformatted(false); + setValue(''); + setValid(null); + setModalResize(); + setSuccessLocked(false); + if (resetCallback) { + resetCallback(); + } + }; + + const className = []; + if (inactive) className.push('inactive'); + if (border) className.push('border'); + + return ( + <AccountInputWrapper + className={className.length ? className.join(' ') : undefined} + > + {inactive && <div className="inactive-block" />} + <h5 className={labelClass}> + {successLock && ( + <> + <FontAwesomeIcon icon={faCheck} /> +   + </> + )}{' '} + {label} + </h5> + <div className={`input${successLock ? ` disabled` : ``}`}> + <section> + <div> + {isValidAddress(value) ? ( + <Polkicon address={value} size="2rem" /> + ) : ( + <div className="ph" /> + )} + </div> + <div> + <input + placeholder={t('address')} + type="text" + onChange={(e: React.FormEvent<HTMLInputElement>) => + handleChange(e) + } + value={value} + disabled={successLock} + /> + </div> + </section> + <section> + {successLock ? ( + <> + <ButtonSecondary onClick={() => resetInput()} text={t('reset')} /> + </> + ) : ( + <> + {!reformatted ? ( + <ButtonSecondary + onClick={() => handleImport()} + text={submitting ? t('importing') : t('import')} + disabled={valid !== 'valid' || submitting} + /> + ) : ( + <ButtonSecondary + onClick={() => handleConfirm()} + text={t('confirm')} + /> + )} + </> + )} + </section> + </div> + </AccountInputWrapper> + ); +}; diff --git a/src/library/AccountInput/types.ts b/src/library/AccountInput/types.ts new file mode 100644 index 0000000000..583af2aaa5 --- /dev/null +++ b/src/library/AccountInput/types.ts @@ -0,0 +1,17 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import type { AnyApi, MaybeAddress } from 'types'; + +export interface AccountInputProps { + successCallback: (a: string) => Promise<AnyApi>; + resetCallback?: () => void; + defaultLabel: string; + resetOnSuccess?: boolean; + successLabel?: string; + locked?: boolean; + inactive?: boolean; + disallowAlreadyImported?: boolean; + initialValue?: MaybeAddress; + border?: boolean; +} diff --git a/src/library/BarChart/BarSegment.tsx b/src/library/BarChart/BarSegment.tsx new file mode 100644 index 0000000000..d2172519db --- /dev/null +++ b/src/library/BarChart/BarSegment.tsx @@ -0,0 +1,26 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { BarSegmentShowLabelThreshold } from './defaults'; +import type { BarSegmentProps } from './types'; + +export const BarSegment = ({ + dataClass, + widthPercent, + flexGrow, + label, + forceShow, +}: BarSegmentProps) => ( + <div + className={dataClass} + style={{ + width: `${flexGrow || forceShow ? 100 : widthPercent}%`, + flexGrow, + }} + > + {widthPercent >= BarSegmentShowLabelThreshold || + (widthPercent < BarSegmentShowLabelThreshold && forceShow) ? ( + <span>{label}</span> + ) : null} + </div> +); diff --git a/src/library/BarChart/BondedChart.tsx b/src/library/BarChart/BondedChart.tsx new file mode 100644 index 0000000000..983c8168cd --- /dev/null +++ b/src/library/BarChart/BondedChart.tsx @@ -0,0 +1,103 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { greaterThanZero } from '@polkadot-cloud/utils'; +import BigNumber from 'bignumber.js'; +import { useTranslation } from 'react-i18next'; +import { BarSegment } from 'library/BarChart/BarSegment'; +import { LegendItem } from 'library/BarChart/LegendItem'; +import { Bar, BarChartWrapper, Legend } from 'library/BarChart/Wrappers'; +import { useNetwork } from 'contexts/Network'; +import type { BondedChartProps } from '../../pages/Nominate/Active/types'; + +export const BondedChart = ({ + active, + free, + unlocking, + unlocked, + inactive, +}: BondedChartProps) => { + const { t } = useTranslation('library'); + const { + networkData: { unit }, + } = useNetwork(); + const totalUnlocking = unlocking.plus(unlocked); + + const MinimumLowerBound = 0.5; + const MinimumNoNZeroPercent = 13; + + // graph percentages + const graphTotal = active.plus(totalUnlocking).plus(free); + + const graphActive = greaterThanZero(active) + ? BigNumber.max( + active.dividedBy(graphTotal.multipliedBy(0.01)), + active.isGreaterThan(MinimumLowerBound) ? MinimumNoNZeroPercent : 0 + ) + : new BigNumber(0); + + const graphUnlocking = greaterThanZero(totalUnlocking) + ? BigNumber.max( + totalUnlocking.dividedBy(graphTotal.multipliedBy(0.01)), + totalUnlocking.isGreaterThan(MinimumLowerBound) + ? MinimumNoNZeroPercent + : 0 + ) + : new BigNumber(0); + + const freeBalance = free.decimalPlaces(3); + const remaining = new BigNumber(100).minus(graphActive).minus(graphUnlocking); + + const graphFree = greaterThanZero(remaining) + ? BigNumber.max( + remaining, + freeBalance.isGreaterThan(MinimumLowerBound) ? MinimumNoNZeroPercent : 0 + ) + : new BigNumber(0); + + return ( + <> + <BarChartWrapper + $lessPadding + style={{ marginTop: '2rem', marginBottom: '2rem' }} + > + <Legend> + {totalUnlocking.plus(active).isZero() ? ( + <LegendItem dataClass="d4" label={t('available')} /> + ) : greaterThanZero(active) ? ( + <LegendItem dataClass="d1" label={t('bonded')} /> + ) : null} + + {greaterThanZero(totalUnlocking) ? ( + <LegendItem dataClass="d3" label={t('unlocking')} /> + ) : null} + + {greaterThanZero(totalUnlocking.plus(active)) ? ( + <LegendItem dataClass="d4" label={t('free')} /> + ) : null} + </Legend> + <Bar> + <BarSegment + dataClass="d1" + widthPercent={Number(graphActive.toFixed(2))} + flexGrow={0} + label={`${active.decimalPlaces(3).toFormat()} ${unit}`} + /> + <BarSegment + dataClass="d3" + widthPercent={Number(graphUnlocking.toFixed(2))} + flexGrow={0} + label={`${totalUnlocking.decimalPlaces(3).toFormat()} ${unit}`} + /> + <BarSegment + dataClass="d4" + widthPercent={Number(graphFree.toFixed(2))} + flexGrow={0} + label={`${freeBalance.toFormat()} ${unit}`} + forceShow={inactive && totalUnlocking.isZero()} + /> + </Bar> + </BarChartWrapper> + </> + ); +}; diff --git a/src/library/BarChart/LegendItem.tsx b/src/library/BarChart/LegendItem.tsx new file mode 100644 index 0000000000..8ec768a126 --- /dev/null +++ b/src/library/BarChart/LegendItem.tsx @@ -0,0 +1,25 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { ButtonHelp } from '@polkadot-cloud/react'; +import { useHelp } from 'contexts/Help'; +import type { LegendItemProps } from './types'; + +export const LegendItem = ({ + dataClass, + label, + helpKey, + button, +}: LegendItemProps) => { + const { openHelp } = useHelp(); + + return ( + <h4> + {dataClass ? <span className={dataClass} /> : null} {label} + {helpKey ? ( + <ButtonHelp marginLeft onClick={() => openHelp(helpKey)} /> + ) : null} + {button && button} + </h4> + ); +}; diff --git a/src/library/BarChart/Wrappers.ts b/src/library/BarChart/Wrappers.ts new file mode 100644 index 0000000000..b4d92e3434 --- /dev/null +++ b/src/library/BarChart/Wrappers.ts @@ -0,0 +1,109 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import styled from 'styled-components'; + +export const BarChartWrapper = styled.div<{ $lessPadding?: boolean }>` + padding: ${(props) => (props.$lessPadding ? '0' : '0 0.5rem')}; + margin-top: 1rem; + width: 100%; + + .available { + display: flex; + margin-top: 2.7rem; + width: 100%; + + > div { + display: flex; + flex-flow: row wrap; + padding: 0 0.35rem; + &:first-child { + padding-left: 0; + } + &:last-child { + padding-right: 0; + } + } + } + .d1 { + background: var(--accent-color-primary); + color: rgba(255, 255, 255, 0.95); + } + .d2 { + background: var(--accent-color-secondary); + color: rgba(255, 255, 255, 0.95); + } + .d3 { + background: var(--text-color-secondary); + color: rgba(255, 255, 255, 0.95); + } + .d4 { + background: var(--button-tertiary-background); + color: var(--text-color-secondary); + } +`; + +export const Legend = styled.div` + width: 100%; + margin-bottom: 0.4rem; + display: flex; + align-items: flex-end; + height: 2.5rem; + + &.end { + > h4 { + flex-direction: row; + flex-grow: 1; + justify-content: flex-end; + padding-right: 0; + } + } + + > h4 { + font-family: InterSemiBold, sans-serif; + display: flex; + align-items: center; + font-size: 1.1rem; + margin: 0; + padding: 0.5rem 1rem 0.25rem 1rem; + + &:first-child { + padding-left: 0; + } + > span { + width: 1rem; + height: 1rem; + margin-right: 0.5rem; + border-radius: 0.25rem; + } + } +`; + +export const Bar = styled.div` + background: var(--button-secondary-background); + border-radius: 0.65rem; + display: flex; + overflow: hidden; + height: 3.75rem; + width: 100%; + + > div { + position: relative; + height: 100%; + display: flex; + align-items: center; + transition: width 1.5s cubic-bezier(0, 1, 0, 1); + + > span { + font-family: InterBold, sans-serif; + position: absolute; + left: 0; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + padding: 0 0.8rem; + width: 100%; + font-size: 1.05rem; + } + } +`; diff --git a/src/library/BarChart/defaults.ts b/src/library/BarChart/defaults.ts new file mode 100644 index 0000000000..7d7eb7ce51 --- /dev/null +++ b/src/library/BarChart/defaults.ts @@ -0,0 +1,5 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +// Bar segment width threshold (as a percentage) to display graph labels. +export const BarSegmentShowLabelThreshold = 9; diff --git a/src/library/BarChart/types.ts b/src/library/BarChart/types.ts new file mode 100644 index 0000000000..84180f05a6 --- /dev/null +++ b/src/library/BarChart/types.ts @@ -0,0 +1,21 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import type React from 'react'; + +export type DataClass = 'd1' | 'd2' | 'd3' | 'd4'; + +export interface LegendItemProps { + dataClass?: DataClass; + label: string; + helpKey?: string; + button?: React.ReactNode; +} + +export interface BarSegmentProps { + dataClass: DataClass; + label?: string; + widthPercent: number; + flexGrow: number; + forceShow?: boolean; +} diff --git a/src/library/Button/index.tsx b/src/library/Button/index.tsx deleted file mode 100644 index 4d0619d5e3..0000000000 --- a/src/library/Button/index.tsx +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { motion } from 'framer-motion'; -import styled from 'styled-components'; -import { - buttonSecondaryBackground, - networkColor, - networkColorSecondary, - textSecondary, -} from 'theme'; -import { ButtonProps, ButtonWrapperProps } from './types'; - -export const ButtonRow = styled.div<{ verticalSpacing?: boolean }>` - display: flex; - align-items: center; - justify-content: flex-start; - margin-top: ${(props) => (props.verticalSpacing ? '1rem' : 0)}; -`; - -export const Wrapper = styled(motion.div)<ButtonWrapperProps>` - display: inline-block; - margin: ${(props) => props.margin}; - - > button { - display: flex; - flex-flow: row nowrap; - align-items: center; - background: ${(props) => - props.type === 'invert-primary' - ? networkColor - : props.type === 'invert-secondary' - ? networkColorSecondary - : buttonSecondaryBackground}; - color: ${(props) => - props.type === 'invert-primary' || props.type === 'invert-secondary' - ? 'white' - : textSecondary}; - - padding: ${(props) => props.padding}; - border-radius: 1.5rem; - font-size: ${(props) => props.fontSize}; - transition: opacity 0.2s; - - .space { - margin-right: 0.6rem; - } - - &:disabled { - cursor: default; - opacity: 0.25; - } - } -`; - -export const Button = (props: ButtonProps) => { - let { transform } = props; - const { primary, secondary, icon, title, disabled, small, inline } = props; - const { onClick } = props; - - transform = transform ?? 'shrink-1'; - - const type = primary - ? 'invert-primary' - : secondary - ? 'invert-secondary' - : 'default'; - - return ( - <Wrapper - whileHover={{ scale: !disabled ? 1.02 : 1 }} - whileTap={{ scale: !disabled ? 0.98 : 1 }} - type={type} - margin={inline ? '0' : '0 0.5rem'} - padding={small ? '0.42rem 0.9rem' : '0.52rem 1.2rem'} - fontSize={small ? '1rem' : '1.15rem'} - > - <button - type="button" - disabled={disabled} - onClick={() => onClick !== undefined && onClick()} - > - {icon && ( - <FontAwesomeIcon - icon={icon} - className={title ? 'space' : undefined} - transform={transform} - /> - )} - {title && title} - </button> - </Wrapper> - ); -}; - -export default Button; diff --git a/src/library/Button/types.ts b/src/library/Button/types.ts deleted file mode 100644 index b79c29e183..0000000000 --- a/src/library/Button/types.ts +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { IconProp } from '@fortawesome/fontawesome-svg-core'; - -export interface ButtonProps { - onClick?: () => void; - primary?: boolean; - secondary?: boolean; - inline?: boolean; - small?: boolean; - disabled?: boolean; - icon?: IconProp; - transform?: string; - title: string; -} - -export interface ButtonWrapperProps { - margin: string; - type: string; - padding: string; - fontSize: string; -} diff --git a/src/library/Card/Wrappers.ts b/src/library/Card/Wrappers.ts new file mode 100644 index 0000000000..f8f0441ba4 --- /dev/null +++ b/src/library/Card/Wrappers.ts @@ -0,0 +1,130 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import styled from 'styled-components'; +import { SideMenuStickyThreshold } from 'consts'; +import type { CardHeaderWrapperProps, CardWrapperProps } from '../Graphs/types'; + +/* CardHeaderWrapper + * + * Used as headers for individual cards. Usually a h4 accompanied + * with a h2. withAction allows a full-width header with a right-side + * button. + */ +export const CardHeaderWrapper = styled.div<CardHeaderWrapperProps>` + display: flex; + flex-flow: ${(props) => (props.$withAction ? 'row' : 'column')} wrap; + align-items: ${(props) => (props.$withAction ? 'center' : 'none')}; + justify-content: ${(props) => (props.$withAction ? 'none' : 'center')}; + margin-bottom: ${(props) => (props.$withMargin ? '1rem' : 0)}; + padding: 0rem 0.25rem; + width: 100%; + + h2 { + font-family: InterBold, sans-serif; + margin-bottom: 1rem; + } + h2, + h3 { + color: var(--text-color-primary); + display: flex; + flex-flow: row wrap; + align-items: center; + flex-grow: ${(props) => (props.$withAction ? 1 : 0)}; + + @media (max-width: ${SideMenuStickyThreshold}px) { + margin-top: 0.5rem; + } + } + h3, + h4 { + font-family: InterSemiBold, sans-serif; + } + h4 { + margin-top: 0; + margin-bottom: 0.4rem; + display: flex; + flex-flow: row wrap; + align-items: center; + flex-grow: ${(props) => (props.$withAction ? 1 : 0)}; + } + .note { + color: var(--text-color-secondary); + font-family: InterSemiBold, sans-serif; + font-size: 1.1rem; + margin-top: 0.2rem; + margin-left: 0.4rem; + } + .networkIcon { + width: 1.9rem; + height: 1.9rem; + margin-right: 0.55rem; + } + + > div { + display: flex; + align-items: center; + } +`; + +/* CardWrapper + * + * Used to separate the main modules throughout the app. + */ +export const CardWrapper = styled.div<CardWrapperProps>` + box-shadow: var(--card-shadow); + background: var(--background-primary); + border-radius: 1.1rem; + display: flex; + flex-direction: column; + flex: 1; + position: relative; + overflow: hidden; + margin-top: 1.4rem; + padding: 1.5rem; + + &.transparent { + background: none; + border: none; + border-radius: 0; + box-shadow: none; + margin-top: 0; + padding: 0; + } + + &.warning { + border: 1px solid var(--status-warning-color); + } + + @media (max-width: ${SideMenuStickyThreshold}px) { + padding: 1rem 0.75rem; + } + + @media (min-width: ${SideMenuStickyThreshold + 1}px) { + height: ${(props) => (props.height ? `${props.height}px` : 'inherit')}; + } + + .inner { + padding: 1rem; + display: flex; + flex-flow: column nowrap; + width: 100%; + position: relative; + } + + .content { + padding: 0 0.5rem; + + h3, + h4 { + margin-top: 0; + } + h3 { + margin-bottom: 0.75rem; + } + + h4 { + margin-bottom: 0; + } + } +`; diff --git a/src/library/Countdown/index.tsx b/src/library/Countdown/index.tsx new file mode 100644 index 0000000000..dc8da539e2 --- /dev/null +++ b/src/library/Countdown/index.tsx @@ -0,0 +1,58 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { useTranslation } from 'react-i18next'; +import type { CountdownProps } from './types'; + +export const Countdown = ({ timeleft, markup = true }: CountdownProps) => { + const { t } = useTranslation('base'); + const { days, hours, minutes, seconds } = timeleft; + + const secondsNumber = seconds ? seconds[0] : 0; + const secondsLabel = seconds + ? seconds[1] + : t('second', { count: secondsNumber }); + + if (markup) { + return ( + <> + {days[0] > 0 ? ( + <> + {days[0]} <span>{days[1]}</span> + </> + ) : null} + {hours[0] > 0 ? ( + <> + {hours[0]} <span>{hours[1]}</span> + </> + ) : null} + {minutes[0] > 0 ? ( + <> + {minutes[0]} <span>{minutes[1]}</span> + </> + ) : null} + {days[0] === 0 && hours[0] === 0 && minutes[0] > 0 ? ( + <>:  </> + ) : null} + + {days[0] === 0 && hours[0] === 0 && ( + <> + {secondsNumber} + {minutes[0] === 0 ? <span>{secondsLabel}</span> : null} + </> + )} + </> + ); + } + + return ( + <> + {days[0] > 0 ? `${days[0]} ${days[1]} ` : null} + {hours[0] > 0 ? `${hours[0]} ${hours[1]} ` : null} + {minutes[0] > 0 ? `${minutes[0]} ${minutes[1]} ` : null} + {days[0] === 0 && hours[0] === 0 + ? `${secondsNumber} ${minutes[0] === 0 ? secondsLabel : ''}` + : null} + </> + ); +}; diff --git a/src/library/Countdown/types.ts b/src/library/Countdown/types.ts new file mode 100644 index 0000000000..cf96ae2caa --- /dev/null +++ b/src/library/Countdown/types.ts @@ -0,0 +1,9 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import type { TimeLeftFormatted } from 'library/Hooks/useTimeLeft/types'; + +export interface CountdownProps { + timeleft: TimeLeftFormatted; + markup?: boolean; +} diff --git a/src/library/ErrorBoundary/Wrapper.ts b/src/library/ErrorBoundary/Wrapper.ts index e3bde58786..412ff6f325 100644 --- a/src/library/ErrorBoundary/Wrapper.ts +++ b/src/library/ErrorBoundary/Wrapper.ts @@ -1,8 +1,7 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only import styled from 'styled-components'; -import { textSecondary } from 'theme'; export const Wrapper = styled.div` width: 100%; @@ -20,24 +19,26 @@ export const Wrapper = styled.div` h1, h2 { - color: ${textSecondary}; + color: var(--text-color-secondary); + } + + h2 { + margin: 1rem 0; } h3 { + margin: 1rem 0; &.with-margin { margin-top: 10rem; } margin-bottom: 3rem; svg { - color: ${textSecondary}; + color: var(--text-color-secondary); } } - h4 { - margin-top: 0; - } button { - color: ${textSecondary}; + color: var(--text-color-secondary); font-size: 1.25rem; &:hover { diff --git a/src/library/ErrorBoundary/index.tsx b/src/library/ErrorBoundary/index.tsx index 6648d79363..e92fa6318d 100644 --- a/src/library/ErrorBoundary/index.tsx +++ b/src/library/ErrorBoundary/index.tsx @@ -1,57 +1,74 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only import { faBug } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { useTranslation } from 'react-i18next'; +import React from 'react'; import { Wrapper } from './Wrapper'; export const ErrorFallbackApp = ({ resetErrorBoundary, }: { resetErrorBoundary: () => void; -}) => ( - <Wrapper className="app"> - <h3> - <FontAwesomeIcon icon={faBug} transform="grow-25" /> - </h3> - <h1>Opps, Something Went Wrong</h1> - <h2> - <button type="button" onClick={resetErrorBoundary}> - Click to reload - </button> - </h2> - </Wrapper> -); +}) => { + const { t } = useTranslation('library'); + + return ( + <Wrapper className="app"> + <h3> + <FontAwesomeIcon icon={faBug} transform="grow-25" /> + </h3> + <h1>{t('errorUnknown')}</h1> + <h2> + <button type="button" onClick={resetErrorBoundary}> + {t('clickToReload')} + </button> + </h2> + </Wrapper> + ); +}; export const ErrorFallbackRoutes = ({ resetErrorBoundary, }: { resetErrorBoundary: () => void; -}) => ( - <Wrapper> - <h3 className="with-margin"> - <FontAwesomeIcon icon={faBug} transform="grow-25" /> - </h3> - <h1>Opps, Something Went Wrong</h1> - <h2> - <button type="button" onClick={resetErrorBoundary}> - Click to reload - </button> - </h2> - </Wrapper> -); +}) => { + const { t } = useTranslation('library'); -export const ErrorFallbackModal = ({ - resetErrorBoundary, -}: { - resetErrorBoundary: () => void; -}) => ( - <Wrapper className="modal"> - <h2>Opps, Something Went Wrong</h2> - <h4> - <button type="button" onClick={resetErrorBoundary}> - Click to reload modal - </button> - </h4> - </Wrapper> -); + return ( + <Wrapper> + <h3 className="with-margin"> + <FontAwesomeIcon icon={faBug} transform="grow-25" /> + </h3> + <h1>{t('errorUnknown')}</h1> + <h2> + <button type="button" onClick={resetErrorBoundary}> + {t('clickToReload')} + </button> + </h2> + </Wrapper> + ); +}; + +interface ErrorFallbackProps { + resetErrorBoundary?: () => void; +} +export const ErrorFallbackModal: React.FC = (props: ErrorFallbackProps) => { + const { resetErrorBoundary } = props; + const { t } = useTranslation('library'); + + return ( + <Wrapper className="modal"> + <h2>{t('errorUnknown')}</h2> + <h4> + <button + type="button" + onClick={() => resetErrorBoundary && resetErrorBoundary()} + > + {t('clickToReload')} + </button> + </h4> + </Wrapper> + ); +}; diff --git a/src/library/EstimatedTxFee/Wrapper.ts b/src/library/EstimatedTxFee/Wrapper.ts new file mode 100644 index 0000000000..ad43da3884 --- /dev/null +++ b/src/library/EstimatedTxFee/Wrapper.ts @@ -0,0 +1,17 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import styled from 'styled-components'; + +export const Wrapper = styled.div` + p { + color: var(--text-color-secondary); + padding: 0; + font-size: 1rem; + margin: 0.5rem 0; + + > span { + margin: 0 0.5rem 0 0; + } + } +`; diff --git a/src/library/EstimatedTxFee/index.tsx b/src/library/EstimatedTxFee/index.tsx index 9d055ee1f0..6308a02ee8 100644 --- a/src/library/EstimatedTxFee/index.tsx +++ b/src/library/EstimatedTxFee/index.tsx @@ -1,48 +1,38 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { useApi } from 'contexts/Api'; -import { useTheme } from 'contexts/Themes'; -import { EstimatedFeeContext, TxFeesContext, useTxFees } from 'contexts/TxFees'; -import React, { Context, useEffect } from 'react'; -import { defaultThemes } from 'theme/default'; -import { humanNumber, planckBnToUnit } from 'Utils'; -import { EstimatedTxFeeProps } from './types'; +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { planckToUnit } from '@polkadot-cloud/utils'; +import type { Context } from 'react'; +import React, { useEffect } from 'react'; +import { useTranslation } from 'react-i18next'; +import { TxMetaContext, useTxMeta } from 'contexts/TxMeta'; +import type { TxMetaContextInterface } from 'contexts/TxMeta/types'; +import { useNetwork } from 'contexts/Network'; import { Wrapper } from './Wrapper'; +import type { EstimatedTxFeeProps } from './types'; export const EstimatedTxFeeInner = ({ format }: EstimatedTxFeeProps) => { - const { - network: { unit, units }, - } = useApi(); - const { mode } = useTheme(); - const { txFees, resetTxFees, notEnoughFunds } = useTxFees(); + const { t } = useTranslation('library'); + const { unit, units } = useNetwork().networkData; + const { txFees, resetTxFees } = useTxMeta(); - useEffect(() => { - return () => { - resetTxFees(); - }; - }, []); + useEffect(() => () => resetTxFees(), []); - const txFeesBase = humanNumber(planckBnToUnit(txFees, units)); + const txFeesUnit = planckToUnit(txFees, units).toFormat(); return ( <> {format === 'table' ? ( <> - <div>Estimated Tx Fee:</div> - <div>{txFees.isZero() ? '...' : `${txFeesBase} ${unit}`}</div> + <div>{t('estimatedFee')}:</div> + <div>{txFees.isZero() ? `...` : `${txFeesUnit} ${unit}`}</div> </> ) : ( <Wrapper> <p> - Estimated Tx Fee:{' '} - {txFees.isZero() ? '...' : `${txFeesBase} ${unit}`} + <span>{t('estimatedFee')}:</span> + {txFees.isZero() ? `...` : `${txFeesUnit} ${unit}`} </p> - {notEnoughFunds === true && ( - <p style={{ color: defaultThemes.text.danger[mode] }}> - The sender does not have enough {unit} to submit this transaction. - </p> - )} </Wrapper> )} </> @@ -50,15 +40,15 @@ export const EstimatedTxFeeInner = ({ format }: EstimatedTxFeeProps) => { }; export class EstimatedTxFee extends React.Component<EstimatedTxFeeProps> { - static contextType: Context<EstimatedFeeContext> = TxFeesContext; + static contextType: Context<TxMetaContextInterface> = TxMetaContext; componentDidMount(): void { - const { resetTxFees } = this.context as EstimatedFeeContext; + const { resetTxFees } = this.context as TxMetaContextInterface; resetTxFees(); } componentWillUnmount(): void { - const { resetTxFees } = this.context as EstimatedFeeContext; + const { resetTxFees } = this.context as TxMetaContextInterface; resetTxFees(); } diff --git a/src/library/EstimatedTxFee/types.ts b/src/library/EstimatedTxFee/types.ts index 27c28194ff..c139c49779 100644 --- a/src/library/EstimatedTxFee/types.ts +++ b/src/library/EstimatedTxFee/types.ts @@ -1,5 +1,5 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only export interface EstimatedTxFeeProps { format?: string; diff --git a/src/library/Filter/Category.tsx b/src/library/Filter/Category.tsx index 7bde3d2c1e..b5451b79e2 100644 --- a/src/library/Filter/Category.tsx +++ b/src/library/Filter/Category.tsx @@ -1,7 +1,7 @@ // Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors // SPDX-License-Identifier: Apache-2.0 -import { CategoryProps } from './types'; +import type { CategoryProps } from './types'; export const Category = (props: CategoryProps) => { const { title } = props; diff --git a/src/library/Filter/Container.tsx b/src/library/Filter/Container.tsx index 0805f17ddd..8ebbff912f 100644 --- a/src/library/Filter/Container.tsx +++ b/src/library/Filter/Container.tsx @@ -1,5 +1,5 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only import React from 'react'; import { Wrapper } from './Wrappers'; diff --git a/src/library/Filter/Item.tsx b/src/library/Filter/Item.tsx index 32198366c5..999bcf1701 100644 --- a/src/library/Filter/Item.tsx +++ b/src/library/Filter/Item.tsx @@ -1,10 +1,10 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { motion } from 'framer-motion'; -import { ItemProps } from './types'; import { ItemWrapper } from './Wrappers'; +import type { ItemProps } from './types'; export const Item = ({ disabled = false, @@ -34,5 +34,3 @@ export const Item = ({ </ItemWrapper> </motion.button> ); - -export default Item; diff --git a/src/library/Filter/LargeItem.tsx b/src/library/Filter/LargeItem.tsx index 0f391ae8e0..c6f3b57d54 100644 --- a/src/library/Filter/LargeItem.tsx +++ b/src/library/Filter/LargeItem.tsx @@ -1,5 +1,5 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { motion } from 'framer-motion'; diff --git a/src/library/Filter/Tabs.tsx b/src/library/Filter/Tabs.tsx new file mode 100644 index 0000000000..876f879fce --- /dev/null +++ b/src/library/Filter/Tabs.tsx @@ -0,0 +1,41 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { useState } from 'react'; +import { useFilters } from 'contexts/Filters'; +import { TabsWrapper, TabWrapper } from './Wrappers'; + +export const Tabs = ({ config, activeIndex }: any) => { + const { resetFilters, setMultiFilters } = useFilters(); + + const [active, setActive] = useState<number>(activeIndex); + + return ( + <TabsWrapper> + {config.map((c: any, i: number) => ( + <TabWrapper + key={`pools_tab_filter_${i}`} + $active={i === active} + disabled={i === active} + onClick={() => { + if (c.includes?.length) { + setMultiFilters('include', 'pools', c.includes, true); + } else { + resetFilters('include', 'pools'); + } + + if (c.excludes?.length) { + setMultiFilters('exclude', 'pools', c.excludes, true); + } else { + resetFilters('exclude', 'pools'); + } + + setActive(i); + }} + > + {c.label} + </TabWrapper> + ))} + </TabsWrapper> + ); +}; diff --git a/src/library/Filter/Wrappers.ts b/src/library/Filter/Wrappers.ts index 53b260114a..7c9e74b2c3 100644 --- a/src/library/Filter/Wrappers.ts +++ b/src/library/Filter/Wrappers.ts @@ -1,13 +1,7 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only import styled from 'styled-components'; -import { - borderPrimary, - buttonPrimaryBackground, - networkColor, - textSecondary, -} from 'theme'; export const Wrapper = styled.div` padding: 0 0.5rem; @@ -35,9 +29,6 @@ export const Wrapper = styled.div` > .items { display: flex; - flex-flow: row nowrap; - justify-content: flex-start; - > button { padding: 0 0.25rem; } @@ -47,12 +38,12 @@ export const Wrapper = styled.div` `; export const ItemWrapper = styled.div` - border: 1px solid ${borderPrimary}; + border: 1px solid var(--border-primary-color); + font-family: InterSemiBold, sans-serif; border-radius: 1.5rem; display: flex; - flex-flow: row nowrap; position: relative; - padding: 0.6rem 0.95rem; + padding: 0.6rem 1rem; margin-right: 1rem; align-items: center; width: max-content; @@ -61,7 +52,7 @@ export const ItemWrapper = styled.div` margin-right: 0; } .icon { - color: ${textSecondary}; + color: var(--text-color-secondary); display: flex; flex-flow: column wrap; justify-content: center; @@ -69,11 +60,10 @@ export const ItemWrapper = styled.div` margin-right: 0.55rem; } p { - color: ${textSecondary}; + color: var(--text-color-secondary); font-size: 0.9rem; margin: 0; text-align: left; - padding-top: 0.15rem; line-height: 0.95rem; } `; @@ -84,14 +74,14 @@ export const LargeItemWrapper = styled.div` justify-content: center; padding: 0.5rem; > .inner { - border: 1.5px solid ${borderPrimary}; - background: ${buttonPrimaryBackground}; - border-radius: 1rem; + border: 1.5px solid var(--border-primary-color); + background: var(--background-list-item); + border-radius: 1.25rem; display: flex; flex-flow: column nowrap; justify-content: center; position: relative; - padding: 1rem; + padding: 1rem 1.25rem; &:last-child { margin-right: 0; @@ -102,19 +92,13 @@ export const LargeItemWrapper = styled.div` display: flex; flex-flow: row wrap; align-items: center; - - h3 { - margin: 0; - } } - svg { - color: ${networkColor}; + color: var(--accent-color-primary); margin-right: 0.75rem; } - p { - color: ${textSecondary}; + color: var(--text-color-secondary); margin: 0; text-align: left; padding: 0.5rem 0 0 0; @@ -122,4 +106,33 @@ export const LargeItemWrapper = styled.div` } `; -export default Wrapper; +export const TabsWrapper = styled.div` + display: flex; + margin-bottom: 0.75rem; + + > button { + &:first-child { + border-top-left-radius: 1.5rem; + border-bottom-left-radius: 1.5rem; + } + &:last-child { + border-top-right-radius: 1.5rem; + border-bottom-right-radius: 1.5rem; + } + } +`; + +export const TabWrapper = styled.button<{ $active?: boolean }>` + font-family: InterSemiBold, sans-serif; + border: 1px solid + ${(props) => + props.$active + ? 'var(--accent-color-primary)' + : 'var(--border-primary-color)'}; + color: ${(props) => + props.$active + ? 'var(--accent-color-primary)' + : 'var(--text-color-secondary)'}; + font-size: 0.9rem; + padding: 0.5rem 1.25rem; +`; diff --git a/src/library/Filter/context.tsx b/src/library/Filter/context.tsx deleted file mode 100644 index dc435bc46c..0000000000 --- a/src/library/Filter/context.tsx +++ /dev/null @@ -1,389 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { u8aToString, u8aUnwrapBytes } from '@polkadot/util'; -import { useApi } from 'contexts/Api'; -import { useValidators } from 'contexts/Validators'; -import React, { useState } from 'react'; -import * as defaults from './defaults'; -import { ValidatorFilterContextInterface } from './types'; - -// Note: This context should wrap both the filter component and UI in question for displaying the filtered list. -export const ValidatorFilterContext = - React.createContext<ValidatorFilterContextInterface>(defaults.defaultContext); - -export const useValidatorFilter = () => - React.useContext(ValidatorFilterContext); - -export const ValidatorFilterProvider = ({ - children, -}: { - children: React.ReactNode; -}) => { - const { consts } = useApi(); - const { meta, session, sessionParachain } = useValidators(); - const { maxNominatorRewardedPerValidator } = consts; - - // store validator filters that are currently active - const [validatorFilters, setValidatorFilters]: any = useState([]); - - // store the validator ordering method that is currently active - const [validatorOrder, setValidatorOrder]: any = useState('default'); - - const setValidatorsOrder = (by: string) => { - setValidatorOrder(by); - }; - - const setValidatorsFilter = (filter: any) => { - setValidatorFilters(filter); - }; - - /* - * toggleAllValidaorFilters - * Either turns all filters on or all filters off. - * This does not use the 'in_session' filter. - */ - const toggleAllValidatorFilters = (toggle: number) => { - if (toggle) { - setValidatorsFilter([ - 'all_commission', - 'blocked_nominations', - 'over_subscribed', - 'missing_identity', - 'inactive', - ]); - } else { - setValidatorFilters([]); - } - }; - - /* - * toggleFilterValidators - * Toggles a validator filter. If the filer is currently active, - * it is removed. State updates accordingly. - */ - const toggleFilterValidators = (f: string) => { - const filter = [...validatorFilters]; - const action = filter.includes(f) ? 'remove' : 'push'; - - if (action === 'remove') { - const index = filter.indexOf(f); - filter.splice(index, 1); - } else { - filter.push(f); - } - setValidatorsFilter(filter); - }; - - /* - * applyValidatorFilters - * Takes a list, batchKey and which filter should be applied - * to the data. The filter function in question is called, - * that is dependent on the filter key. - * The updated filtered list is returned. - * - */ - const applyValidatorFilters = ( - list: any, - batchKey: string, - filter: any = validatorFilters - ) => { - if (filter.includes('all_commission')) { - list = filterAllCommission(list); - } - if (filter.includes('blocked_nominations')) { - list = filterBlockedNominations(list); - } - if (filter.includes('over_subscribed')) { - list = filterOverSubscribed(list, batchKey); - } - if (filter.includes('missing_identity')) { - list = filterMissingIdentity(list, batchKey); - } - if (filter.includes('inactive')) { - list = filterInactive(list); - } - if (filter.includes('not_parachain_validator')) { - list = filterNonParachainValidator(list); - } - if (filter.includes('in_session')) { - list = filterInSession(list); - } - return list; - }; - - /* - * resetValidatorFilters - * Resets filters and ordering to the default state. - */ - const resetValidatorFilters = () => { - setValidatorFilters([]); - setValidatorOrder('default'); - }; - - /* - * filterMissingIdentity - * Iterates through the supplied list and refers to the meta - * batch of the list to filter those list items with - * missing identities. - * Returns the updated filtered list. - */ - const filterMissingIdentity = (list: any, batchKey: string) => { - if (meta[batchKey] === undefined) { - return list; - } - const filteredList: any = []; - for (const validator of list) { - const addressBatchIndex = - meta[batchKey].addresses?.indexOf(validator.address) ?? -1; - - // if we cannot derive data, fallback to include validator in filtered list - if (addressBatchIndex === -1) { - filteredList.push(validator); - continue; - } - - const identities = meta[batchKey]?.identities ?? []; - const supers = meta[batchKey]?.supers ?? []; - - // push validator if sync has not completed - if (!identities.length || !supers.length) { - filteredList.push(validator); - } - - const identityExists = identities[addressBatchIndex] ?? null; - const superExists = supers[addressBatchIndex] ?? null; - - // validator included if identity or super identity has been set - if (identityExists !== null || superExists !== null) { - filteredList.push(validator); - continue; - } - } - return filteredList; - }; - - /* - * filterOverSubscribed - * Iterates through the supplied list and refers to the meta - * batch of the list to filter those list items that are - * over subscribed. - * Returns the updated filtered list. - */ - const filterOverSubscribed = (list: any, batchKey: string) => { - if (meta[batchKey] === undefined) { - return list; - } - const filteredList: any = []; - for (const validator of list) { - const addressBatchIndex = - meta[batchKey].addresses?.indexOf(validator.address) ?? -1; - - // if we cannot derive data, fallback to include validator in filtered list - if (addressBatchIndex === -1) { - filteredList.push(validator); - continue; - } - const stake = meta[batchKey]?.stake ?? false; - if (!stake) { - filteredList.push(validator); - continue; - } - const totalNominations = stake[addressBatchIndex].total_nominations ?? 0; - if (totalNominations < maxNominatorRewardedPerValidator) { - filteredList.push(validator); - continue; - } - } - return filteredList; - }; - - /* - * filterAllCommission - * Filters the supplied list and removes items with 100% commission. - * Returns the updated filtered list. - */ - const filterAllCommission = (list: any) => { - list = list.filter( - (validator: any) => validator?.prefs?.commission !== 100 - ); - return list; - }; - - /* - * filterBlockedNominations - * Filters the supplied list and removes items that have blocked nominations. - * Returns the updated filtered list. - */ - const filterBlockedNominations = (list: any) => { - list = list.filter((validator: any) => validator?.prefs?.blocked !== true); - return list; - }; - - /* - * filterInactive - * Filters the supplied list and removes items that are inactive. - * Returns the updated filtered list. - */ - const filterInactive = (list: any) => { - // if list has not yet been populated, return original list - if (session.list.length === 0) { - return list; - } - list = list.filter((validator: any) => - session.list.includes(validator.address) - ); - return list; - }; - - /* - * filterNonParachainValidator - * Filters the supplied list and removes items that are inactive. - * Returns the updated filtered list. - */ - const filterNonParachainValidator = (list: any) => { - // if list has not yet been populated, return original list - if ((sessionParachain?.length ?? 0) === 0) { - return list; - } - list = list.filter((validator: any) => - sessionParachain.includes(validator.address) - ); - return list; - }; - - /* - * filterInSession - * Filters the supplied list and removes items that are in the current session. - * Returns the updated filtered list. - */ - const filterInSession = (list: any) => { - // if list has not yet been populated, return original list - if (session.list.length === 0) { - return list; - } - list = list.filter( - (validator: any) => !session.list.includes(validator.address) - ); - return list; - }; - - /* - * orderValidators - * Sets the ordering key for orderValidators - */ - const orderValidators = (by: string) => { - const order = validatorOrder === by ? 'default' : by; - setValidatorsOrder(order); - }; - - /* - * orderValidators - * Applies an ordering function to the validator list. - * Returns the updated ordered list. - */ - const applyValidatorOrder = (list: any, order: string) => { - if (order === 'commission') { - return orderLowestCommission(list); - } - return list; - }; - - /* - * orderLowestCommission - * Orders a list by commission. - * Returns the updated ordered list. - */ - const orderLowestCommission = (list: any) => { - const orderedList = [...list].sort( - (a: any, b: any) => a.prefs.commission - b.prefs.commission - ); - return orderedList; - }; - - /* - * validatorSearchFilter - * Iterates through the supplied list and refers to the meta - * batch of the list to filter those list items that match - * the search term. - * Returns the updated filtered list. - */ - const validatorSearchFilter = ( - list: any, - batchKey: string, - searchTerm: string - ) => { - if (meta[batchKey] === undefined) { - return list; - } - const filteredList: any = []; - for (const validator of list) { - const batchIndex = - meta[batchKey].addresses?.indexOf(validator.address) ?? -1; - - // if we cannot derive data, fallback to include validator in filtered list - if (batchIndex === -1) { - filteredList.push(validator); - continue; - } - const identities = meta[batchKey]?.identities ?? false; - if (!identities) { - filteredList.push(validator); - continue; - } - const supers = meta[batchKey]?.supers ?? false; - if (!supers) { - filteredList.push(validator); - continue; - } - - const address = meta[batchKey].addresses[batchIndex]; - - const identity = identities[batchIndex] ?? ''; - const identityRaw = identity?.info?.display?.Raw ?? ''; - const identityAsBytes = u8aToString(u8aUnwrapBytes(identityRaw)); - const identitySearch = ( - identityAsBytes === '' ? identityRaw : identityAsBytes - ).toLowerCase(); - - const superIdentity = supers[batchIndex] ?? null; - const superIdentityRaw = - superIdentity?.identity?.info?.display?.Raw ?? ''; - const superIdentityAsBytes = u8aToString( - u8aUnwrapBytes(superIdentityRaw) - ); - const superIdentitySearch = ( - superIdentityAsBytes === '' ? superIdentityRaw : superIdentityAsBytes - ).toLowerCase(); - - if (address.toLowerCase().includes(searchTerm.toLowerCase())) { - filteredList.push(validator); - } - if ( - identitySearch.includes(searchTerm.toLowerCase()) || - superIdentitySearch.includes(searchTerm.toLowerCase()) - ) { - filteredList.push(validator); - } - } - return filteredList; - }; - - return ( - <ValidatorFilterContext.Provider - value={{ - orderValidators, - applyValidatorOrder, - applyValidatorFilters, - resetValidatorFilters, - toggleFilterValidators, - toggleAllValidatorFilters, - validatorSearchFilter, - validatorFilters, - validatorOrder, - }} - > - {children} - </ValidatorFilterContext.Provider> - ); -}; diff --git a/src/library/Filter/defaults.ts b/src/library/Filter/defaults.ts index 726d291be5..dbb446c644 100644 --- a/src/library/Filter/defaults.ts +++ b/src/library/Filter/defaults.ts @@ -1,21 +1,16 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only +/* eslint-disable @typescript-eslint/no-unused-vars */ -import { ValidatorFilterContextInterface } from './types'; +import type { ValidatorFilterContextInterface } from './types'; export const defaultContext: ValidatorFilterContextInterface = { - // eslint-disable-next-line orderValidators: (v) => {}, - // eslint-disable-next-line applyValidatorOrder: (l, o) => {}, - // eslint-disable-next-line applyValidatorFilters: (l, k, f) => {}, - // eslint-disable-next-line toggleFilterValidators: (v) => {}, - // eslint-disable-next-line toggleAllValidatorFilters: (t) => {}, resetValidatorFilters: () => {}, - // eslint-disable-next-line validatorSearchFilter: (l, k, v) => {}, validatorFilters: [], validatorOrder: 'default', diff --git a/src/library/Filter/types.ts b/src/library/Filter/types.ts index 12e68048f0..07c6007394 100644 --- a/src/library/Filter/types.ts +++ b/src/library/Filter/types.ts @@ -1,8 +1,8 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import { IconProp } from '@fortawesome/fontawesome-svg-core'; -import React from 'react'; +import type { IconProp } from '@fortawesome/fontawesome-svg-core'; +import type React from 'react'; export interface ItemProps { icon?: IconProp; @@ -14,7 +14,7 @@ export interface ItemProps { export interface CategoryProps { title: string; - buttons?: Array<any>; + buttons?: any[]; children: React.ReactNode; } diff --git a/src/library/Form/AccountDropdown/Wrappers.ts b/src/library/Form/AccountDropdown/Wrappers.ts deleted file mode 100644 index e72f783adf..0000000000 --- a/src/library/Form/AccountDropdown/Wrappers.ts +++ /dev/null @@ -1,124 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import styled from 'styled-components'; -import { - backgroundDropdown, - borderPrimary, - borderSecondary, - textPrimary, - textSecondary, -} from 'theme'; - -export const StyledDownshift = styled.div` - position: relative; - width: 100%; - height: auto; - overflow: hidden; - - .label { - margin: 0.25rem 0 0.75rem 0; - } - .current { - flex: 1; - display: flex; - align-items: center; - margin-bottom: 1rem; - - > span { - margin: 0 0.75rem; - color: ${textSecondary}; - opacity: 0.5; - } - } - - /* input element of dropdown */ - .input-wrap { - border-bottom: 1px solid ${borderPrimary}; - display: flex; - flex-flow: row wrap; - align-items: center; - padding: 0.25rem 0 0 0; - margin: 0.25rem 0.7rem 0 0.7rem; - flex: 1; - - &.selected { - border: 1px solid ${borderPrimary}; - border-radius: 1rem; - margin: 0; - padding: 0.1rem 0.75rem; - } - } - - /* input element of dropdown */ - .input { - border: none; - padding-left: 0.75rem; - flex: 1; - text-overflow: ellipsis; - white-space: nowrap; - overflow: hidden; - } -`; - -export const StyledController = styled.button<any>` - color: ${textPrimary}; - border: none; - position: absolute; - right: 0.5rem; - top: 0.4rem; - width: 2.2rem; - height: 2.2rem; - display: flex; - flex-flow: column wrap; - justify-content: center; - align-items: center; - border-radius: 0.5rem; -`; - -/* dropdown box for vertical scroll */ -export const StyledDropdown = styled.div<any>` - background: ${backgroundDropdown}; - position: relative; - margin: 0.5rem 0 0; - border-bottom: none; - border-radius: 0.75rem; - z-index: 1; - - .items { - width: auto; - height: ${(props) => (props.height ? props.height : 'auto')}; - overflow: auto; - - .item { - padding: 0.5rem; - cursor: pointer; - margin: 0.25rem; - border-radius: 0.75rem; - display: flex; - flex-flow: row wrap; - justify-content: flex-start; - align-items: center; - - .icon { - margin-right: 0.5rem; - } - span { - color: ${textSecondary}; - border: 1px solid ${borderSecondary}; - border-radius: 0.5rem; - padding: 0.2rem 0.5rem; - font-size: 0.9rem; - margin-right: 0.5rem; - } - p { - font-size: 1rem; - color: ${textPrimary}; - text-overflow: ellipsis; - white-space: nowrap; - overflow: hidden; - flex: 1; - } - } - } -`; diff --git a/src/library/Form/AccountDropdown/index.tsx b/src/library/Form/AccountDropdown/index.tsx deleted file mode 100644 index 240cb062a5..0000000000 --- a/src/library/Form/AccountDropdown/index.tsx +++ /dev/null @@ -1,151 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { faAnglesRight, faTimes } from '@fortawesome/free-solid-svg-icons'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { useApi } from 'contexts/Api'; -import { useTheme } from 'contexts/Themes'; -import { useCombobox, UseComboboxStateChange } from 'downshift'; -import Identicon from 'library/Identicon'; -import { useEffect, useState } from 'react'; -import { defaultThemes, networkColors } from 'theme/default'; -import { convertRemToPixels } from 'Utils'; -import { AccountDropdownProps, InputItem } from '../types'; -import { StyledController, StyledDownshift, StyledDropdown } from './Wrappers'; - -export const AccountDropdown = ({ - items, - onChange, - placeholder, - value, - current, - height, -}: AccountDropdownProps) => { - // store input items - const [inputItems, setInputItems] = useState<Array<InputItem>>(items); - - useEffect(() => { - setInputItems(items); - }, [items]); - - const itemToString = (item: InputItem) => { - const name = item?.name ?? ''; - return name; - }; - - const c = useCombobox({ - items: inputItems, - itemToString, - onSelectedItemChange: onChange, - initialSelectedItem: value, - onInputValueChange: ({ inputValue }: UseComboboxStateChange<InputItem>) => { - setInputItems( - items.filter((item: InputItem) => - inputValue - ? item?.name?.toLowerCase().startsWith(inputValue?.toLowerCase()) - : true - ) - ); - }, - }); - - return ( - <StyledDownshift> - <div> - <div className="label" {...c.getLabelProps()}> - Currently Selected: - </div> - <div> - <div className="current"> - <div className="input-wrap selected"> - {current !== null && ( - <Identicon - value={current?.address ?? ''} - size={convertRemToPixels('2rem')} - /> - )} - <input className="input" disabled value={current?.name ?? ''} /> - </div> - <span> - <FontAwesomeIcon icon={faAnglesRight} /> - </span> - <div className="input-wrap selected"> - {value !== null && ( - <Identicon - value={value?.address ?? ''} - size={convertRemToPixels('2rem')} - /> - )} - <input className="input" disabled value={value?.name ?? '...'} /> - </div> - </div> - - <StyledDropdown {...c.getMenuProps()} height={height}> - <div className="input-wrap" {...c.getComboboxProps()}> - <input {...c.getInputProps({ placeholder })} className="input" /> - </div> - - <div className="items"> - {c.selectedItem && ( - <StyledController - onClick={() => c.reset()} - aria-label="clear selection" - > - <FontAwesomeIcon transform="grow-2" icon={faTimes} /> - </StyledController> - )} - - {inputItems.map((item: InputItem, index: number) => ( - <DropdownItem - key={`controller_acc_${index}`} - c={c} - item={item} - index={index} - /> - ))} - </div> - </StyledDropdown> - </div> - </div> - </StyledDownshift> - ); -}; - -const DropdownItem = ({ c, item, index }: any) => { - const { network } = useApi(); - const { mode } = useTheme(); - - let color; - let border; - - if (c.selectedItem === item) { - color = networkColors[`${network.name}-${mode}`]; - border = `2px solid ${networkColors[`${network.name}-${mode}`]}`; - } else { - color = defaultThemes.text.primary[mode]; - border = `2px solid ${defaultThemes.transparent[mode]}`; - } - - // disable item in list if account doesn't satisfy controller budget. - const itemProps = item.active - ? c.getItemProps({ key: item.name, index, item }) - : {}; - const opacity = item.active ? 1 : 0.5; - const cursor = item.active ? 'pointer' : 'default'; - - return ( - <div - className="item" - {...itemProps} - style={{ color, border, opacity, cursor }} - > - <div className="icon"> - <Identicon value={item.address} size={26} /> - </div> - {!item.active && <span>Not Enough {network.unit}</span>} - <p>{item.name}</p> - </div> - ); -}; - -export default AccountDropdown; diff --git a/src/library/Form/AccountSelect/Wrappers.ts b/src/library/Form/AccountSelect/Wrappers.ts deleted file mode 100644 index b858cedbe9..0000000000 --- a/src/library/Form/AccountSelect/Wrappers.ts +++ /dev/null @@ -1,121 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import styled from 'styled-components'; -import { backgroundToggle, borderPrimary, textPrimary } from 'theme'; - -export const StyledDownshift = styled.div<any>` - position: relative; - width: 100%; - height: ${(props) => (props.height ? props.height : 'auto')}; - overflow: hidden; - - /* title of dropdown */ - .label { - margin: 1rem 0; - display: block; - } - - /* input element of dropdown */ - .input-wrap { - border: 1px solid ${borderPrimary}; - display: flex; - flex-flow: row wrap; - align-items: center; - border-radius: 1rem; - padding: 0.1rem 0.75rem; - margin: 0.25rem 0; - } - - .input { - border: none; - padding-left: 0.75rem; - flex-grow: 1; - } -`; - -export const StyledController = styled.button<any>` - color: ${textPrimary}; - position: absolute; - right: 0.5rem; - top: 0.6rem; - width: 2.2rem; - height: 2.2rem; - display: flex; - flex-flow: column wrap; - justify-content: center; - align-items: center; -`; - -/* dropdown box for horizontal scroll */ -export const StyledSelect = styled.div` - border: 1px solid ${borderPrimary}; - position: relative; - margin: 0.75rem 0 0; - width: 100%; - border-radius: 1rem; - z-index: 1; - height: 170px; - padding: 0.25rem; - overflow: auto; - display: flex; - flex-flow: row wrap; - flex: 1; - border-radius: 1rem; - - .wrapper { - position: relative; - min-width: 240px; - height: 125px; - flex: 1 1 20%; - max-width: 20%; - padding: 0.35rem; - - .item { - background: ${backgroundToggle}; - position: relative; - width: 100%; - height: 100%; - padding: 0.65rem 1rem; - cursor: pointer; - border-radius: 1rem; - display: flex; - flex-flow: column wrap; - justify-content: center; - align-items: flex-start; - flex-grow: 1; - overflow: hidden; - - > .title { - display: flex; - flex-flow: row wrap; - max-width: 100%; - - h3 { - display: inline-block; - text-overflow: ellipsis; - white-space: nowrap; - overflow: hidden; - } - } - - &:first-child { - margin-left: 0rem; - } - &:last-child { - margin-right: 0rem; - } - p { - color: ${textPrimary}; - margin: 0.15rem 0 0; - text-overflow: ellipsis; - white-space: nowrap; - overflow: hidden; - flex: 1; - } - .icon { - margin-bottom: 0.7rem; - } - } - } -`; diff --git a/src/library/Form/AccountSelect/index.tsx b/src/library/Form/AccountSelect/index.tsx deleted file mode 100644 index d3ad43bab3..0000000000 --- a/src/library/Form/AccountSelect/index.tsx +++ /dev/null @@ -1,138 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { faTimes } from '@fortawesome/free-solid-svg-icons'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { useApi } from 'contexts/Api'; -import { useTheme } from 'contexts/Themes'; -import { useCombobox, UseComboboxStateChange } from 'downshift'; -import Identicon from 'library/Identicon'; -import { StatusLabel } from 'library/StatusLabel'; -import { useEffect, useState } from 'react'; -import { defaultThemes, networkColors } from 'theme/default'; -import { clipAddress, convertRemToPixels } from 'Utils'; -import { AccountSelectProps, InputItem } from '../types'; -import { StyledController, StyledDownshift, StyledSelect } from './Wrappers'; - -export const AccountSelect = ({ - items, - onChange, - placeholder, - value, -}: AccountSelectProps) => { - const [inputItems, setInputItems] = useState<Array<InputItem>>(items); - - useEffect(() => { - setInputItems(items); - }, [items]); - - const itemToString = (item: InputItem) => { - const name = item?.name ?? ''; - return name; - }; - - const c = useCombobox({ - items: inputItems, - itemToString, - onSelectedItemChange: onChange, - initialSelectedItem: value, - onInputValueChange: ({ inputValue }: UseComboboxStateChange<InputItem>) => { - setInputItems( - items.filter((item: InputItem) => - inputValue - ? item?.name?.toLowerCase().startsWith(inputValue?.toLowerCase()) - : true - ) - ); - }, - }); - - return ( - <StyledDownshift> - <div> - <div style={{ position: 'relative' }}> - <div className="input-wrap" {...c.getComboboxProps()}> - {value !== null && ( - <Identicon - value={value?.address ?? ''} - size={convertRemToPixels('2rem')} - /> - )} - <input {...c.getInputProps({ placeholder })} className="input" /> - </div> - - {c.selectedItem && ( - <StyledController - onClick={() => c.reset()} - aria-label="clear selection" - > - <FontAwesomeIcon transform="grow-4" icon={faTimes} /> - </StyledController> - )} - <StyledSelect {...c.getMenuProps()}> - {inputItems.map((item: InputItem, index: number) => ( - <DropdownItem - key={`controller_acc_${index}`} - c={c} - item={item} - index={index} - /> - ))} - </StyledSelect> - </div> - </div> - </StyledDownshift> - ); -}; - -const DropdownItem = ({ c, item, index }: any) => { - const { network } = useApi(); - const { mode } = useTheme(); - - // disable item in list if account doesn't satisfy controller budget. - const itemProps = item.active ? c.getItemProps({ index, item }) : {}; - - const color = - c.selectedItem?.address === item?.address - ? networkColors[`${network.name}-${mode}`] - : defaultThemes.text.primary[mode]; - - const border = - c.selectedItem?.address === item?.address - ? `2px solid ${networkColors[`${network.name}-${mode}`]}` - : `2px solid ${defaultThemes.transparent[mode]}`; - - const opacity = item.active ? 1 : 0.1; - - return ( - <div className="wrapper" key={item.name} {...itemProps}> - {!item.active && ( - <StatusLabel - status="not_enough_unit" - title={item.alert} - topOffset="40%" - helpKey="Controller Account Eligibility" - hideIcon - /> - )} - <div - className="item" - style={{ - color, - border, - opacity, - }} - > - <div className="icon"> - <Identicon value={item.address} size={40} /> - </div> - <div className="title"> - <h3 style={{ color }}>{item.name}</h3> - </div> - <p>{clipAddress(item.address)}</p> - </div> - </div> - ); -}; - -export default AccountSelect; diff --git a/src/library/Form/Bond/BondFeedback.tsx b/src/library/Form/Bond/BondFeedback.tsx index 2c191ad6a7..a4a2e21867 100644 --- a/src/library/Form/Bond/BondFeedback.tsx +++ b/src/library/Form/Bond/BondFeedback.tsx @@ -1,25 +1,26 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import { BN_ZERO } from '@polkadot/util'; -import BN, { max } from 'bn.js'; -import { useApi } from 'contexts/Api'; -import { useConnect } from 'contexts/Connect'; +import { planckToUnit, unitToPlanck } from '@polkadot-cloud/utils'; +import BigNumber from 'bignumber.js'; +import { useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; import { useActivePools } from 'contexts/Pools/ActivePools'; import { usePoolsConfig } from 'contexts/Pools/PoolsConfig'; import { useStaking } from 'contexts/Staking'; import { useTransferOptions } from 'contexts/TransferOptions'; -import { useEffect, useState } from 'react'; -import { planckBnToUnit, unitToPlanckBn } from 'Utils'; -import { BondFeedbackProps } from '../types'; +import { useNetwork } from 'contexts/Network'; +import { useActiveAccounts } from 'contexts/ActiveAccounts'; import { Warning } from '../Warning'; import { Spacer } from '../Wrappers'; +import type { BondFeedbackProps } from '../types'; import { BondInput } from './BondInput'; export const BondFeedback = ({ - bondType, + bondFor, inSetup = false, - warnings = [], + joiningPool = false, + parentErrors = [], setters = [], listenIsValid = () => {}, disableTxFeeUpdate = false, @@ -28,54 +29,62 @@ export const BondFeedback = ({ maxWidth, syncing = false, }: BondFeedbackProps) => { - const { network } = useApi(); - const { activeAccount } = useConnect(); + const { t } = useTranslation('library'); + const { + networkData: { units, unit }, + } = useNetwork(); const { staking } = useStaking(); - const { getTransferOptions } = useTransferOptions(); - const { isDepositor } = useActivePools(); + const { activeAccount } = useActiveAccounts(); const { stats } = usePoolsConfig(); + const { isDepositor } = useActivePools(); + const { getTransferOptions } = useTransferOptions(); const { minJoinBond, minCreateBond } = stats; - const { units, unit } = network; const { minNominatorBond } = staking; - - const defBond = defaultBond || ''; - const allTransferOptions = getTransferOptions(activeAccount); + const defaultBondStr = defaultBond ? String(defaultBond) : ''; + // get bond options for either staking or pooling. - const { freeBalance: freeBalanceBn } = allTransferOptions; + const freeBalanceBn = + bondFor === 'nominator' + ? allTransferOptions.nominate.totalAdditionalBond + : allTransferOptions.pool.totalAdditionalBond; // if we are bonding, subtract tx fees from bond amount const freeBondAmount = !disableTxFeeUpdate - ? BN.max(freeBalanceBn.sub(txFees), BN_ZERO) + ? BigNumber.max(freeBalanceBn.minus(txFees), 0) : freeBalanceBn; // the default bond balance - const freeBalance = planckBnToUnit(freeBondAmount, units); + const freeBalance = planckToUnit(freeBondAmount, units); // store errors - const [errors, setErrors] = useState<Array<string>>([]); + const [errors, setErrors] = useState<string[]>([]); // local bond state - const [bond, setBond] = useState<{ bond: number | string }>({ - bond: defBond, + const [bond, setBond] = useState<{ bond: string }>({ + bond: defaultBondStr, }); + // current bond value BigNumber + const bondBn = unitToPlanck(bond.bond, units); + // whether bond is disabled const [bondDisabled, setBondDisabled] = useState(false); // bond minus tx fees if too much - const enoughToCoverTxFees: boolean = - freeBalance - Number(bond.bond) > planckBnToUnit(txFees, units); + const enoughToCoverTxFees = freeBondAmount + .minus(bondBn) + .isGreaterThan(txFees); const bondAfterTxFees = enoughToCoverTxFees - ? unitToPlanckBn(Number(bond.bond), units) - : max(unitToPlanckBn(Number(bond.bond), units).sub(txFees), new BN(0)); + ? bondBn + : BigNumber.max(bondBn.minus(txFees), 0); // update bond on account change useEffect(() => { setBond({ - bond: defBond, + bond: defaultBondStr, }); }, [activeAccount]); @@ -87,8 +96,8 @@ export const BondFeedback = ({ // update max bond after txFee sync useEffect(() => { if (!disableTxFeeUpdate) { - if (Number(bond.bond) > freeBalance) { - setBond({ bond: freeBalance }); + if (bondBn.isGreaterThan(freeBondAmount)) { + setBond({ bond: String(freeBalance) }); } } }, [txFees]); @@ -99,70 +108,79 @@ export const BondFeedback = ({ current: bond, }); - // bond amount to minimum threshold - const minBondBase = - bondType === 'pool' + // bond amount to minimum threshold. + const minBondBn = + bondFor === 'pool' ? inSetup || isDepositor() - ? planckBnToUnit(minCreateBond, units) - : planckBnToUnit(minJoinBond, units) - : planckBnToUnit(minNominatorBond, units); + ? minCreateBond + : minJoinBond + : minNominatorBond; + const minBondUnit = planckToUnit(minBondBn, units); // handle error updates const handleErrors = () => { - let _bondDisabled = false; - const _errors = warnings; - const _bond = bond.bond; - const _planck = 1 / new BN(10).pow(new BN(units)).toNumber(); + let disabled = false; + const newErrors = parentErrors; + const decimals = bond.bond.toString().split('.')[1]?.length ?? 0; // bond errors - if (freeBalance === 0) { - _bondDisabled = true; - _errors.push(`You have no free ${network.unit} to bond.`); + if (freeBondAmount.isZero()) { + disabled = true; + newErrors.push(`${t('noFree', { unit })}`); } - if (Number(bond.bond) > freeBalance) { - _errors.push('Bond amount is more than your free balance.'); + // bond amount must not surpass freeBalalance + if (bondBn.isGreaterThan(freeBondAmount)) { + newErrors.push(t('moreThanBalance')); } - if (bond.bond !== '' && Number(bond.bond) < _planck) { - _errors.push('Bond amount is too small.'); + // bond amount must not be smaller than 1 planck + if (bond.bond !== '' && bondBn.isLessThan(1)) { + newErrors.push(t('tooSmall')); } - if (bond.bond !== '' && bondAfterTxFees.toNumber() < 0) { - _errors.push(`Not enough ${unit} to bond after transaction fees.`); + // check bond after transaction fees is still valid + if (bond.bond !== '' && bondAfterTxFees.isLessThan(0)) { + newErrors.push(`${t('notEnoughAfter', { unit })}`); } - if (inSetup) { - if (freeBalance < minBondBase) { - _bondDisabled = true; - _errors.push( - `You do not meet the minimum bond of ${minBondBase} ${network.unit}.` - ); + // cbond amount must not surpass network supported units + if (decimals > units) { + newErrors.push(`${t('bondDecimalsError', { units })}`); + } + + if (inSetup || joiningPool) { + if (freeBondAmount.isLessThan(minBondBn)) { + disabled = true; + newErrors.push(`${t('notMeet')} ${minBondUnit} ${unit}.`); } - if (bond.bond !== '' && Number(bond.bond) < minBondBase) { - _errors.push( - `Bond amount must be at least ${minBondBase} ${network.unit}.` - ); + // bond amount must be more than minimum required bond + if (bond.bond !== '' && bondBn.isLessThan(minBondBn)) { + newErrors.push(`${t('atLeast')} ${minBondUnit} ${unit}.`); } } - const bondValid = !_errors.length && _bond !== ''; - - setBondDisabled(_bondDisabled); - listenIsValid(bondValid); - setErrors(_errors); + const bondValid = !newErrors.length && bond.bond !== ''; + setBondDisabled(disabled); + listenIsValid(bondValid, newErrors); + setErrors(newErrors); }; return ( <> - {errors.map((err: string, index: number) => ( - <Warning key={`setup_error_${index}`} text={err} /> + {errors.map((err, i) => ( + <Warning key={`setup_error_${i}`} text={err} /> ))} <Spacer /> - <div style={{ maxWidth: maxWidth ? '500px' : '100%' }}> + <div + style={{ + width: '100%', + maxWidth: maxWidth ? '500px' : '100%', + }} + > <BondInput - value={bond.bond} - defaultValue={defBond} + value={String(bond.bond)} + defaultValue={defaultBondStr} syncing={syncing} disabled={bondDisabled} setters={setters} @@ -173,5 +191,3 @@ export const BondFeedback = ({ </> ); }; - -export default BondFeedback; diff --git a/src/library/Form/Bond/BondInput.tsx b/src/library/Form/Bond/BondInput.tsx index ed0cd10a0e..cc8f5835f0 100644 --- a/src/library/Form/Bond/BondInput.tsx +++ b/src/library/Form/Bond/BondInput.tsx @@ -1,57 +1,57 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import { ButtonInvert } from '@rossbulat/polkadot-dashboard-ui'; -import { useApi } from 'contexts/Api'; -import { useConnect } from 'contexts/Connect'; -import { useEffect, useState } from 'react'; -import { humanNumber, isNumeric } from 'Utils'; -import { BondInputProps } from '../types'; +import { ButtonSubmitInvert } from '@polkadot-cloud/react'; +import BigNumber from 'bignumber.js'; +import React, { useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useNetwork } from 'contexts/Network'; +import { useActiveAccounts } from 'contexts/ActiveAccounts'; import { InputWrapper } from '../Wrappers'; +import type { BondInputProps } from '../types'; export const BondInput = ({ - setters, + setters = [], disabled, defaultValue, freeBalance, - disableTxFeeUpdate, - value, + disableTxFeeUpdate = false, + value = '0', syncing = false, }: BondInputProps) => { - const sets = setters ?? []; - const _value = value ?? 0; - const disableTxFeeUpd = disableTxFeeUpdate ?? false; - - const { network } = useApi(); - const { activeAccount } = useConnect(); + const { t } = useTranslation('library'); + const { + networkData: { unit }, + } = useNetwork(); + const { activeAccount } = useActiveAccounts(); // the current local bond value - const [localBond, setLocalBond] = useState(_value); + const [localBond, setLocalBond] = useState<string>(value); - // reset value to default when changing account + // reset value to default when changing account. useEffect(() => { - setLocalBond(defaultValue ?? 0); + setLocalBond(defaultValue ?? '0'); }, [activeAccount]); useEffect(() => { - if (!disableTxFeeUpd) { - setLocalBond(_value.toString()); + if (!disableTxFeeUpdate) { + setLocalBond(value.toString()); } - }, [_value]); + }, [value]); - // handle change for bonding - const handleChangeBond = (e: any) => { + // handle change for bonding. + const handleChangeBond = (e: React.ChangeEvent<HTMLInputElement>) => { const val = e.target.value; - if (!isNumeric(val) && val !== '') { + if (new BigNumber(val).isNaN() && val !== '') { return; } setLocalBond(val); updateParentState(val); }; - // apply bond to parent setters - const updateParentState = (val: any) => { - for (const s of sets) { + // apply bond to parent setters. + const updateParentState = (val: string) => { + for (const s of setters) { s.set({ ...s.current, bond: val, @@ -59,16 +59,22 @@ export const BondInput = ({ } }; + // available funds as jsx. + const availableFundsJsx = ( + <p> + {syncing ? '...' : `${freeBalance.toFormat()} ${unit} ${t('available')}`} + </p> + ); + return ( <InputWrapper> - <h3>Bond {network.unit}:</h3> <div className="inner"> <section style={{ opacity: disabled ? 0.5 : 1 }}> <div className="input"> <div> <input type="text" - placeholder={`0 ${network.unit}`} + placeholder={`0 ${unit}`} value={localBond} onChange={(e) => { handleChangeBond(e); @@ -76,28 +82,21 @@ export const BondInput = ({ disabled={disabled} /> </div> - <div> - <p> - {syncing - ? '...' - : `${humanNumber(freeBalance)} ${network.unit} available`} - </p> - </div> + <div>{availableFundsJsx}</div> </div> </section> <section> - <ButtonInvert - text="Max" - disabled={disabled || syncing || freeBalance === 0} + <ButtonSubmitInvert + text={t('max')} + disabled={disabled || syncing || freeBalance.isZero()} onClick={() => { - setLocalBond(freeBalance); - updateParentState(freeBalance); + setLocalBond(freeBalance.toString()); + updateParentState(freeBalance.toString()); }} /> </section> </div> + <div className="availableOuter">{availableFundsJsx}</div> </InputWrapper> ); }; - -export default BondInput; diff --git a/src/library/Form/ClaimPermissionInput/index.tsx b/src/library/Form/ClaimPermissionInput/index.tsx new file mode 100644 index 0000000000..04720dc468 --- /dev/null +++ b/src/library/Form/ClaimPermissionInput/index.tsx @@ -0,0 +1,101 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { ActionItem } from '@polkadot-cloud/react'; +import { useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { usePoolMemberships } from 'contexts/Pools/PoolMemberships'; +import type { ClaimPermission } from 'contexts/Pools/types'; +import { TabWrapper, TabsWrapper } from 'library/Filter/Wrappers'; + +export interface ClaimPermissionInputProps { + current: ClaimPermission | undefined; + permissioned: boolean; + onChange: (value: ClaimPermission | undefined) => void; + disabled?: boolean; +} + +export const ClaimPermissionInput = ({ + current, + permissioned, + onChange, + disabled = false, +}: ClaimPermissionInputProps) => { + const { t } = useTranslation('library'); + const { claimPermissionConfig } = usePoolMemberships(); + + // Updated claim permission value + const [selected, setSelected] = useState<ClaimPermission | undefined>( + current + ); + + // Permissionless claim enabled. + const [enabled, setEnabled] = useState<boolean>(permissioned); + + const activeTab = claimPermissionConfig.find( + ({ value }) => value === selected + ); + + // Update selected value when current changes. + useEffect(() => { + setSelected(current); + }, [current]); + + return ( + <> + <ActionItem + style={{ + marginTop: '2rem', + }} + text={t('enablePermissionlessClaiming')} + toggled={enabled} + onToggle={(val) => { + // toggle enable claim permission. + setEnabled(val); + + const newClaimPermission = val + ? claimPermissionConfig[0].value + : current === undefined + ? undefined + : 'Permissioned'; + + setSelected(newClaimPermission); + onChange(newClaimPermission); + }} + disabled={disabled} + inactive={disabled} + /> + <TabsWrapper + style={{ + margin: '1rem 0', + opacity: enabled && !disabled ? 1 : 'var(--opacity-disabled)', + }} + > + {claimPermissionConfig.map(({ label, value }: any, i) => ( + <TabWrapper + key={`pools_tab_filter_${i}`} + $active={value === selected && enabled} + disabled={value === selected || !enabled || disabled} + onClick={() => { + setSelected(value); + onChange(value); + }} + > + {label} + </TabWrapper> + ))} + </TabsWrapper> + <div + style={{ + opacity: enabled && !disabled ? 1 : 'var(--opacity-disabled)', + }} + > + {activeTab ? ( + <p>{activeTab.description}</p> + ) : ( + <p>{t('permissionlessClaimingTurnedOff')}</p> + )} + </div> + </> + ); +}; diff --git a/src/library/Form/CreatePoolStatusBar/Wrapper.ts b/src/library/Form/CreatePoolStatusBar/Wrapper.ts index ee27f66422..10d736a083 100644 --- a/src/library/Form/CreatePoolStatusBar/Wrapper.ts +++ b/src/library/Form/CreatePoolStatusBar/Wrapper.ts @@ -1,14 +1,12 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only import styled from 'styled-components'; -import { backgroundLabel, networkColor, textSecondary } from 'theme'; export const Wrapper = styled.div` width: 100%; display: flex; flex-flow: row wrap; - justify-content: flex-start; align-items: flex-end; margin-top: 1rem; @@ -16,7 +14,6 @@ export const Wrapper = styled.div` width: 100%; display: flex; flex-flow: row wrap; - justify-content: flex-start; align-items: flex-end; margin-top: 1rem; @@ -31,27 +28,26 @@ export const Wrapper = styled.div` } h4, h5 { - color: ${textSecondary}; + color: var(--text-color-secondary); } h4 { display: flex; flex-flow: row wrap; align-items: center; - margin-bottom: 0.4rem; + margin: 1.25rem 0 0.4rem 0; } h5 { - margin: 0; position: relative; opacity: 0.75; } .bar { - background: ${backgroundLabel}; + background: var(--background-list-item); width: 100%; padding: 0.65rem 0.75rem; overflow: hidden; position: relative; - transition: background 0.15s; + transition: background var(--transition-duration); } &:first-child .bar { border-top-left-radius: 1.5rem; @@ -67,14 +63,14 @@ export const Wrapper = styled.div` &.invert { h4 { - color: ${networkColor}; + color: var(--accent-color-primary); } h5 { opacity: 1; - color: white; + color: var(--text-color-invert); } .bar { - background: ${networkColor}; + background: var(--accent-color-primary); } } } diff --git a/src/library/Form/CreatePoolStatusBar/index.tsx b/src/library/Form/CreatePoolStatusBar/index.tsx index c54ac0bd8e..ab4ece2fe1 100644 --- a/src/library/Form/CreatePoolStatusBar/index.tsx +++ b/src/library/Form/CreatePoolStatusBar/index.tsx @@ -1,24 +1,27 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import { IconProp } from '@fortawesome/fontawesome-svg-core'; import { faFlag } from '@fortawesome/free-regular-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { useApi } from 'contexts/Api'; +import { planckToUnit } from '@polkadot-cloud/utils'; +import { useTranslation } from 'react-i18next'; import { usePoolsConfig } from 'contexts/Pools/PoolsConfig'; import { useUi } from 'contexts/UI'; -import { planckBnToUnit } from 'Utils'; -import { NominateStatusBarProps } from '../types'; +import { useNetwork } from 'contexts/Network'; +import type { NominateStatusBarProps } from '../types'; import { Wrapper } from './Wrapper'; export const CreatePoolStatusBar = ({ value }: NominateStatusBarProps) => { - const { minCreateBond } = usePoolsConfig().stats; + const { t } = useTranslation('library'); const { isSyncing } = useUi(); - const { unit, units } = useApi().network; + const { unit, units } = useNetwork().networkData; + const { minCreateBond } = usePoolsConfig().stats; - const minCreateBondBase = planckBnToUnit(minCreateBond, units); + const minCreateBondUnit = planckToUnit(minCreateBond, units); const sectionClassName = - value >= minCreateBondBase && !isSyncing ? 'invert' : ''; + value.isGreaterThanOrEqualTo(minCreateBondUnit) && !isSyncing + ? 'invert' + : ''; return ( <Wrapper> @@ -31,11 +34,15 @@ export const CreatePoolStatusBar = ({ value }: NominateStatusBarProps) => { </section> <section className={sectionClassName}> <h4> - <FontAwesomeIcon icon={faFlag as IconProp} transform="shrink-4" /> -  Create Pool + <FontAwesomeIcon icon={faFlag} transform="shrink-4" /> +  {t('createPool')} </h4> <div className="bar"> - <h5>{isSyncing ? '...' : `${minCreateBondBase} ${unit}`}</h5> + <h5> + {isSyncing + ? '...' + : `${minCreateBondUnit.decimalPlaces(3).toFormat()} ${unit}`} + </h5> </div> </section> </div> diff --git a/src/library/Form/Dropdown/index.tsx b/src/library/Form/Dropdown/index.tsx deleted file mode 100644 index 4589122ece..0000000000 --- a/src/library/Form/Dropdown/index.tsx +++ /dev/null @@ -1,106 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { faAnglesRight } from '@fortawesome/free-solid-svg-icons'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { useApi } from 'contexts/Api'; -import { useTheme } from 'contexts/Themes'; -import { useCombobox } from 'downshift'; -import { useState } from 'react'; -import { defaultThemes, networkColors } from 'theme/default'; -import { StyledDownshift, StyledDropdown } from '../AccountDropdown/Wrappers'; -import { DropdownInput, DropdownProps } from '../types'; - -export const Dropdown = ({ - items, - onChange, - label, - placeholder, - value, - current, -}: DropdownProps) => { - const [inputItems, setInputItems] = useState<Array<DropdownInput>>(items); - - const itemToString = (item: DropdownInput | null) => item?.name ?? ''; - - const c = useCombobox({ - items: inputItems, - itemToString, - onSelectedItemChange: onChange, - initialSelectedItem: value, - onInputValueChange: () => { - setInputItems(items); - }, - }); - - return ( - <StyledDownshift> - <div> - {label ? ( - <div className="label" {...c.getLabelProps()}> - {label} - </div> - ) : null} - <div style={{ position: 'relative' }}> - <div className="current"> - <div className="input-wrap selected"> - <input className="input" disabled value={current?.name ?? ''} /> - </div> - <span> - <FontAwesomeIcon icon={faAnglesRight} /> - </span> - <div className="input-wrap selected" {...c.getComboboxProps()}> - <input - className="input" - disabled - {...c.getInputProps({ placeholder })} - value={value?.name ?? '...'} - /> - </div> - </div> - - <StyledDropdown {...c.getMenuProps()}> - <div className="items"> - {inputItems.map((item: DropdownInput, index: number) => ( - <DropdownItem - key={`controller_acc_${index}`} - c={c} - item={item} - index={index} - /> - ))} - </div> - </StyledDropdown> - </div> - </div> - </StyledDownshift> - ); -}; - -const DropdownItem = ({ c, item, index }: any) => { - const { name } = useApi().network; - const { mode } = useTheme(); - const color = - c.selectedItem?.key === item.key - ? networkColors[`${name}-${mode}`] - : defaultThemes.text.primary[mode]; - const border = - c.selectedItem?.key === item.key - ? `2px solid ${networkColors[`${name}-${mode}`]}` - : `2px solid ${defaultThemes.transparent[mode]}`; - - return ( - <div - className="item" - {...c.getItemProps({ key: item.name, index, item })} - style={{ - color, - border, - }} - > - <p>{item.name}</p> - </div> - ); -}; - -export default Dropdown; diff --git a/src/library/Form/MinDelayInput/Wrapper.ts b/src/library/Form/MinDelayInput/Wrapper.ts new file mode 100644 index 0000000000..c5af7b3c1f --- /dev/null +++ b/src/library/Form/MinDelayInput/Wrapper.ts @@ -0,0 +1,65 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import styled from 'styled-components'; + +export const MinDelayInputWrapper = styled.div` + color: var(--text-color-secondary); + border: 1px solid var(--border-primary-color); + flex: 0 1 auto; + display: flex; + height: 3rem; + align-items: center; + margin: 0.5rem 1rem 0 0; + border-radius: 0.75rem; + overflow: hidden; + + &:first-child { + margin-left: 0; + } + &:last-child { + margin-right: 0; + } + + > .input { + flex-grow: 1; + padding-right: 0.75rem; + + input { + font-family: InterSemiBold, sans-serif; + width: 32px; + margin: 0 0.6rem 0 0; + padding: 0.5rem 0.2rem 0.5rem 0.75rem; + } + } + + > .toggle { + background: var(--button-primary-background); + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + height: 100%; + width: 1.5rem; + + > button { + color: var(--text-color-secondary); + height: 1.5rem; + width: 100%; + display: flex; + justify-content: center; + align-items: center; + + &:first-child { + padding-top: 0.3rem; + } + &:last-child { + padding-bottom: 0.3rem; + } + + &:hover { + background: var(--button-secondary-background); + } + } + } +`; diff --git a/src/library/Form/MinDelayInput/index.tsx b/src/library/Form/MinDelayInput/index.tsx new file mode 100644 index 0000000000..f017393ec4 --- /dev/null +++ b/src/library/Form/MinDelayInput/index.tsx @@ -0,0 +1,62 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { faCaretDown, faCaretUp } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import BigNumber from 'bignumber.js'; +import { useEffect, useState } from 'react'; +import { MinDelayInputWrapper } from './Wrapper'; +import type { MinDelayProps } from './types'; + +export const MinDelayInput = ({ + initial, + field, + label, + handleChange, +}: MinDelayProps) => { + const [current, setCurrent] = useState<number>(initial); + + useEffect(() => { + setCurrent(initial); + }, [initial]); + + const onChange = (value: string) => { + if (!new BigNumber(value).isNaN() || value === '') { + const newValue = new BigNumber(value || 0).toNumber(); + setCurrent(newValue); + handleChange(field, newValue); + } + }; + + const onIncrement = () => { + const newValue = current + 1; + onChange(String(newValue)); + }; + + const onDecrement = () => { + const newValue = Math.max(current - 1, 0); + onChange(String(newValue)); + }; + + return ( + <MinDelayInputWrapper> + <div className="input"> + <input + type="text" + placeholder="0" + value={current} + onChange={({ target: { value } }) => onChange(value)} + /> + {label} + </div> + <div className="toggle"> + <button type="button" onClick={() => onIncrement()}> + <FontAwesomeIcon icon={faCaretUp} transform="shrink-5" /> + </button> + <button type="button" onClick={() => onDecrement()}> + <FontAwesomeIcon icon={faCaretDown} transform="shrink-5" /> + </button> + </div> + </MinDelayInputWrapper> + ); +}; diff --git a/src/library/Form/MinDelayInput/types.ts b/src/library/Form/MinDelayInput/types.ts new file mode 100644 index 0000000000..f34b6d0395 --- /dev/null +++ b/src/library/Form/MinDelayInput/types.ts @@ -0,0 +1,9 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +export interface MinDelayProps { + initial: number; + field: string; + label: string; + handleChange: (field: string, value: number) => void; +} diff --git a/src/library/Form/NominateStatusBar/Wrapper.ts b/src/library/Form/NominateStatusBar/Wrapper.ts index 4fe2480f69..dd1b5d9089 100644 --- a/src/library/Form/NominateStatusBar/Wrapper.ts +++ b/src/library/Form/NominateStatusBar/Wrapper.ts @@ -1,14 +1,12 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only import styled from 'styled-components'; -import { backgroundLabel, networkColor, textSecondary } from 'theme'; export const Wrapper = styled.div` width: 100%; display: flex; flex-flow: row wrap; - justify-content: flex-start; align-items: flex-end; margin-top: 1rem; @@ -16,7 +14,6 @@ export const Wrapper = styled.div` width: 100%; display: flex; flex-flow: row wrap; - justify-content: flex-start; align-items: flex-end; margin-top: 1rem; @@ -34,27 +31,26 @@ export const Wrapper = styled.div` } h4, h5 { - color: ${textSecondary}; + color: var(--text-color-secondary); } h4 { display: flex; flex-flow: row wrap; align-items: center; - margin-bottom: 0.4rem; + margin: 1.25rem 0 0.4rem 0; } h5 { - margin: 0; position: relative; opacity: 0.75; } .bar { - background: ${backgroundLabel}; + background: var(--background-list-item); width: 100%; padding: 0.65rem 0.75rem; overflow: hidden; position: relative; - transition: background 0.15s; + transition: background var(--transition-duration); } &:first-child .bar { border-top-left-radius: 1.5rem; @@ -70,14 +66,14 @@ export const Wrapper = styled.div` &.invert { h4 { - color: ${networkColor}; + color: var(--accent-color-primary); } h5 { opacity: 1; - color: white; + color: var(--text-color-invert); } .bar { - background: ${networkColor}; + background: var(--accent-color-primary); } } } diff --git a/src/library/Form/NominateStatusBar/index.tsx b/src/library/Form/NominateStatusBar/index.tsx index 0b6b7f484e..6fa05806d0 100644 --- a/src/library/Form/NominateStatusBar/index.tsx +++ b/src/library/Form/NominateStatusBar/index.tsx @@ -1,27 +1,33 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import { IconProp } from '@fortawesome/fontawesome-svg-core'; import { faFlag } from '@fortawesome/free-regular-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { useApi } from 'contexts/Api'; +import { ButtonHelp } from '@polkadot-cloud/react'; +import { planckToUnit } from '@polkadot-cloud/utils'; +import { useTranslation } from 'react-i18next'; +import { useHelp } from 'contexts/Help'; +import { useNetworkMetrics } from 'contexts/NetworkMetrics'; import { useStaking } from 'contexts/Staking'; import { useUi } from 'contexts/UI'; -import { OpenHelpIcon } from 'library/OpenHelpIcon'; -import { planckBnToUnit } from 'Utils'; -import { NominateStatusBarProps } from '../types'; +import { useNetwork } from 'contexts/Network'; +import type { NominateStatusBarProps } from '../types'; import { Wrapper } from './Wrapper'; export const NominateStatusBar = ({ value }: NominateStatusBarProps) => { - const { staking, eraStakers } = useStaking(); + const { t } = useTranslation('library'); + const { staking } = useStaking(); const { isSyncing } = useUi(); - const { unit, units } = useApi().network; + const { unit, units } = useNetwork().networkData; const { minNominatorBond } = staking; - const { minActiveBond } = eraStakers; + const { metrics } = useNetworkMetrics(); + const { minimumActiveStake } = metrics; + const { openHelp } = useHelp(); - const minNominatorBondBase = planckBnToUnit(minNominatorBond, units); - const gtMinNominatorBond = value >= minNominatorBondBase; - const gtMinActiveBond = value >= minActiveBond; + const minNominatorBondUnit = planckToUnit(minNominatorBond, units); + const minimumActiveStakeUnit = planckToUnit(minimumActiveStake, units); + const gtMinNominatorBond = value.isGreaterThanOrEqualTo(minNominatorBondUnit); + const gtMinActiveStake = value.isGreaterThanOrEqualTo(minimumActiveStakeUnit); return ( <Wrapper> @@ -29,29 +35,41 @@ export const NominateStatusBar = ({ value }: NominateStatusBarProps) => { <section className={gtMinNominatorBond && !isSyncing ? 'invert' : ''}> <h4> </h4> <div className="bar"> - <h5>Inactive</h5> + <h5>{t('nominateInactive')}</h5> </div> </section> <section className={gtMinNominatorBond && !isSyncing ? 'invert' : ''}> <h4> - <FontAwesomeIcon icon={faFlag as IconProp} transform="shrink-4" /> -   Nominate   - <OpenHelpIcon helpKey="Nominating" /> + <FontAwesomeIcon icon={faFlag} transform="shrink-4" /> +   {t('nominate')} + <ButtonHelp marginLeft onClick={() => openHelp('Nominating')} /> </h4> <div className="bar"> <h5> - {minNominatorBondBase} {unit} + {minNominatorBondUnit.decimalPlaces(3).toFormat()} {unit} </h5> </div> </section> - <section className={gtMinActiveBond && !isSyncing ? 'invert' : ''}> + <section className={gtMinActiveStake && !isSyncing ? 'invert' : ''}> <h4> - <FontAwesomeIcon icon={faFlag as IconProp} transform="shrink-4" /> -  Active   - <OpenHelpIcon helpKey="Active Bond Threshold" /> + <FontAwesomeIcon icon={faFlag} transform="shrink-4" /> +  {t('nominateActive')} + <ButtonHelp + marginLeft + onClick={() => openHelp('Active Stake Threshold')} + /> </h4> <div className="bar"> - <h5>{isSyncing ? '...' : `${minActiveBond} ${unit}`}</h5> + <h5> + {isSyncing + ? '...' + : `${(minimumActiveStakeUnit.isLessThan(minNominatorBondUnit) + ? minNominatorBondUnit + : minimumActiveStakeUnit + ) + .decimalPlaces(3) + .toFormat()} ${unit}`} + </h5> </div> </section> </div> diff --git a/src/library/Form/Unbond/UnbondFeedback.tsx b/src/library/Form/Unbond/UnbondFeedback.tsx index 494b81157f..462d7ea500 100644 --- a/src/library/Form/Unbond/UnbondFeedback.tsx +++ b/src/library/Form/Unbond/UnbondFeedback.tsx @@ -1,60 +1,62 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import BN from 'bn.js'; -import { useApi } from 'contexts/Api'; -import { useBalances } from 'contexts/Balances'; -import { useConnect } from 'contexts/Connect'; +import { isNotZero, planckToUnit, unitToPlanck } from '@polkadot-cloud/utils'; +import BigNumber from 'bignumber.js'; +import { useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; import { useActivePools } from 'contexts/Pools/ActivePools'; import { usePoolsConfig } from 'contexts/Pools/PoolsConfig'; import { useStaking } from 'contexts/Staking'; import { useTransferOptions } from 'contexts/TransferOptions'; -import { useEffect, useState } from 'react'; -import { planckBnToUnit } from 'Utils'; -import { UnbondFeedbackProps } from '../types'; +import { useNetwork } from 'contexts/Network'; +import { useActiveAccounts } from 'contexts/ActiveAccounts'; import { Warning } from '../Warning'; import { Spacer } from '../Wrappers'; +import type { UnbondFeedbackProps } from '../types'; import { UnbondInput } from './UnbondInput'; export const UnbondFeedback = ({ - bondType, + bondFor, inSetup = false, - warnings = [], setters = [], listenIsValid = () => {}, defaultBond, setLocalResize, + parentErrors = [], txFees, }: UnbondFeedbackProps) => { - const defaultValue = defaultBond || ''; - - const { network } = useApi(); - const { activeAccount } = useConnect(); - const { staking, getControllerNotImported } = useStaking(); - const { getBondedAccount } = useBalances(); + const { t } = useTranslation('library'); + const { + networkData: { units, unit }, + } = useNetwork(); + const { activeAccount } = useActiveAccounts(); + const { staking } = useStaking(); const { getTransferOptions } = useTransferOptions(); const { isDepositor } = useActivePools(); const { stats } = usePoolsConfig(); const { minJoinBond, minCreateBond } = stats; - const { units } = network; - const controller = getBondedAccount(activeAccount); const { minNominatorBond } = staking; const allTransferOptions = getTransferOptions(activeAccount); - // get bond options for either staking or pooling. - const transferOptions = - bondType === 'pool' ? allTransferOptions.pool : allTransferOptions.nominate; + const defaultValue = defaultBond ? String(defaultBond) : ''; - const { freeToUnbond: freeToUnbondBn, active } = transferOptions; + // get bond options for either nominating or pooling. + const transferOptions = + bondFor === 'pool' ? allTransferOptions.pool : allTransferOptions.nominate; + const { active } = transferOptions; // store errors - const [errors, setErrors] = useState<Array<string>>([]); + const [errors, setErrors] = useState<string[]>([]); // local bond state - const [bond, setBond] = useState<{ bond: number | string }>({ + const [bond, setBond] = useState<{ bond: string }>({ bond: defaultValue, }); + // current bond value BigNumber + const bondBn = unitToPlanck(String(bond.bond), units); + // update bond on account change useEffect(() => { setBond({ @@ -79,84 +81,85 @@ export const UnbondFeedback = ({ }); // bond amount to minimum threshold - const minBondBase = - bondType === 'pool' + const minBondBn = + bondFor === 'pool' ? inSetup || isDepositor() - ? planckBnToUnit(minCreateBond, units) - : planckBnToUnit(minJoinBond, units) - : planckBnToUnit(minNominatorBond, units); + ? minCreateBond + : minJoinBond + : minNominatorBond; + const minBondUnit = planckToUnit(minBondBn, units); // unbond amount to minimum threshold - const freeToUnbondToMin = - bondType === 'pool' + const unbondToMin = + bondFor === 'pool' ? inSetup || isDepositor() - ? planckBnToUnit( - BN.max(freeToUnbondBn.sub(minCreateBond), new BN(0)), - units - ) - : planckBnToUnit( - BN.max(freeToUnbondBn.sub(minJoinBond), new BN(0)), - units - ) - : planckBnToUnit( - BN.max(freeToUnbondBn.sub(minNominatorBond), new BN(0)), - units - ); - - // get the actively bonded amount. - const activeBase = planckBnToUnit(active, units); + ? BigNumber.max(active.minus(minCreateBond), 0) + : BigNumber.max(active.minus(minJoinBond), 0) + : BigNumber.max(active.minus(minNominatorBond), 0); + + // check if bonded is below the minimum required + const nominatorActiveBelowMin = + bondFor === 'nominator' && + isNotZero(active) && + active.isLessThan(minNominatorBond); + const poolToMinBn = isDepositor() ? minCreateBond : minJoinBond; + const poolActiveBelowMin = + bondFor === 'pool' && active.isLessThan(poolToMinBn); // handle error updates const handleErrors = () => { - const _errors = [...warnings]; - const _bond = bond.bond; - const _planck = 1 / new BN(10).pow(new BN(units)).toNumber(); - - // unbond errors - if (Number(bond.bond) > activeBase) - _errors.push('Unbond amount is more than your bonded balance.'); - - // unbond errors for staking only - if (bondType === 'stake') - if (getControllerNotImported(controller)) - _errors.push( - 'You must have your controller account imported to unbond.' - ); - - if (bond.bond !== '' && Number(bond.bond) < _planck) - _errors.push('Value is too small'); - - if (Number(bond.bond) > freeToUnbondToMin) - _errors.push( - `A minimum bond of ${minBondBase} ${network.unit} is required ${ - bondType === 'stake' - ? `when actively nominating` - : isDepositor() - ? `as the pool depositor` - : `as a pool member` - }.` - ); - - listenIsValid(!_errors.length && _bond !== ''); - setErrors(_errors); + const newErrors = parentErrors; + const decimals = bond.bond.toString().split('.')[1]?.length ?? 0; + + if (bondBn.isGreaterThan(active)) { + newErrors.push(t('unbondAmount')); + } + + if (bond.bond !== '' && bondBn.isLessThan(1)) { + newErrors.push(t('valueTooSmall')); + } + + if (decimals > units) { + newErrors.push(`${t('bondAmountDecimals', { unit })}`); + } + + if (bondBn.isGreaterThan(unbondToMin)) { + // start the error message stating a min bond is required. + let err = `${t('minimumBond', { + minBondUnit: minBondUnit.toString(), + unit, + })} `; + // append the subject to the error message. + if (bondFor === 'nominator') { + err += t('whenActivelyNominating'); + } else if (isDepositor()) { + err += t('asThePoolDepositor'); + } else { + err += t('asAPoolMember'); + } + newErrors.push(err); + } + + listenIsValid(!newErrors.length && bond.bond !== '', newErrors); + setErrors(newErrors); }; return ( <> - {errors.map((err: string, index: number) => ( - <Warning key={`unbond_error_${index}`} text={err} /> + {errors.map((err, i) => ( + <Warning key={`unbond_error_${i}`} text={err} /> ))} <Spacer /> <UnbondInput active={active} defaultValue={defaultValue} - disabled={false} - freeToUnbondToMin={freeToUnbondToMin} + disabled={ + active.isZero() || nominatorActiveBelowMin || poolActiveBelowMin + } + unbondToMin={unbondToMin} setters={setters} value={bond.bond} /> </> ); }; - -export default UnbondFeedback; diff --git a/src/library/Form/Unbond/UnbondInput.tsx b/src/library/Form/Unbond/UnbondInput.tsx index 43eaa4c2d8..91db1bbfb0 100644 --- a/src/library/Form/Unbond/UnbondInput.tsx +++ b/src/library/Form/Unbond/UnbondInput.tsx @@ -1,54 +1,52 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import { ButtonInvert } from '@rossbulat/polkadot-dashboard-ui'; -import { useApi } from 'contexts/Api'; -import { useConnect } from 'contexts/Connect'; +import { ButtonSubmitInvert } from '@polkadot-cloud/react'; +import { planckToUnit } from '@polkadot-cloud/utils'; +import BigNumber from 'bignumber.js'; import React, { useEffect, useState } from 'react'; -import { humanNumber, isNumeric, planckBnToUnit } from 'Utils'; -import { UnbondInputProps } from '../types'; +import { useTranslation } from 'react-i18next'; +import { useNetwork } from 'contexts/Network'; +import { useActiveAccounts } from 'contexts/ActiveAccounts'; import { InputWrapper } from '../Wrappers'; +import type { UnbondInputProps } from '../types'; export const UnbondInput = ({ defaultValue, disabled, - freeToUnbondToMin, - setters, - value, + unbondToMin, + setters = [], + value = 0, active, }: UnbondInputProps) => { - const { network } = useApi(); - const { activeAccount } = useConnect(); - - const sets = setters ?? []; - const _value = value ?? 0; + const { t } = useTranslation('library'); + const { networkData } = useNetwork(); + const { activeAccount } = useActiveAccounts(); // get the actively bonded amount. - const activeBase = planckBnToUnit(active, network.units); + const activeUnit = planckToUnit(active, networkData.units); - // the current local bond value - const [localBond, setLocalBond] = useState(_value); + // the current local bond value. + const [localBond, setLocalBond] = useState(value); - // reset value to default when changing account + // reset value to default when changing account. useEffect(() => { setLocalBond(defaultValue ?? 0); }, [activeAccount]); - // handle change for unbonding - const handleChangeUnbond = (e: React.ChangeEvent) => { - if (!e) return; - const element = e.currentTarget as HTMLInputElement; - const val = element.value; - - if (!(!isNumeric(val) && val !== '')) { - setLocalBond(val); - updateParentState(val); + // handle change for unbonding. + const handleChangeUnbond = (e: React.ChangeEvent<HTMLInputElement>) => { + const val = e.target.value; + if (new BigNumber(val).isNaN() && val !== '') { + return; } + setLocalBond(val); + updateParentState(val); }; - // apply bond to parent setters + // apply bond to parent setters. const updateParentState = (val: any) => { - for (const s of sets) { + for (const s of setters) { s.set({ ...s.current, bond: val, @@ -56,16 +54,25 @@ export const UnbondInput = ({ } }; + // unbond to min as unit. + const unbondToMinUnit = planckToUnit(unbondToMin, networkData.units); + + // available funds as jsx. + const maxBondedJsx = ( + <p> + {activeUnit.toFormat()} {networkData.unit} {t('bonded')} + </p> + ); + return ( <InputWrapper> - <h3>Unbond {network.unit}:</h3> <div className="inner"> <section style={{ opacity: disabled ? 0.5 : 1 }}> <div className="input"> <div> <input type="text" - placeholder={`0 ${network.unit}`} + placeholder={`0 ${networkData.unit}`} value={localBond} onChange={(e) => { handleChangeUnbond(e); @@ -73,22 +80,21 @@ export const UnbondInput = ({ disabled={disabled} /> </div> - <div> - {humanNumber(activeBase)} {network.unit} bonded - </div> + <div>{maxBondedJsx}</div> </div> </section> <section> - <ButtonInvert - text="Max" + <ButtonSubmitInvert + text={t('max')} disabled={disabled} onClick={() => { - setLocalBond(freeToUnbondToMin); - updateParentState(freeToUnbondToMin); + setLocalBond(unbondToMinUnit); + updateParentState(unbondToMinUnit); }} /> </section> </div> + <div className="availableOuter">{maxBondedJsx}</div> </InputWrapper> ); }; diff --git a/src/library/Form/Utils/getEligibleControllers.tsx b/src/library/Form/Utils/getEligibleControllers.tsx deleted file mode 100644 index 0e32c6cdee..0000000000 --- a/src/library/Form/Utils/getEligibleControllers.tsx +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import BN from 'bn.js'; -import { useApi } from 'contexts/Api'; -import { useBalances } from 'contexts/Balances'; -import { useConnect } from 'contexts/Connect'; -import { ImportedAccount } from 'contexts/Connect/types'; -import { useEffect, useState } from 'react'; -import { planckBnToUnit } from 'Utils'; -import { InputItem } from '../types'; - -export const getEligibleControllers = (): Array<InputItem> => { - const { network } = useApi(); - const { activeAccount, accounts: connectAccounts } = useConnect(); - const { - isController, - existentialAmount, - getAccountBalance, - accounts: balanceAccounts, - } = useBalances(); - - const [accounts, setAccounts] = useState<Array<InputItem>>([]); - - useEffect(() => { - setAccounts(filterAccounts()); - }, [activeAccount, connectAccounts, balanceAccounts]); - - const filterAccounts = () => { - // remove read only accounts - let _accounts = connectAccounts.filter((acc: ImportedAccount) => { - return acc?.source !== 'external'; - }); - // filter items that are already controller accounts - _accounts = _accounts.filter((acc: ImportedAccount) => { - return !isController(acc?.address ?? null); - }); - - // remove active account from eligible accounts - _accounts = _accounts.filter( - (acc: ImportedAccount) => acc.address !== activeAccount - ); - - // inject balances and whether account can be an active item - let _accountsAsInput: Array<InputItem> = _accounts.map( - (acc: ImportedAccount) => { - const balance = getAccountBalance(acc?.address); - return { - ...acc, - balance, - active: - planckBnToUnit(balance.free, network.units) > - planckBnToUnit(existentialAmount, network.units), - alert: `Not Enough ${network.unit}`, - }; - } - ); - - // sort accounts with at least free balance first - _accountsAsInput = _accountsAsInput.sort((a: InputItem, b: InputItem) => { - const aFree = a?.balance?.free ?? new BN(0); - const bFree = b?.balance?.free ?? new BN(0); - - if (bFree.lt(aFree)) { - return -1; - // eslint-disable-next-line no-else-return - } else { - return bFree.sub(aFree).toNumber(); - } - }); - - return _accountsAsInput; - }; - - return accounts; -}; diff --git a/src/library/Form/Warning/Wrapper.ts b/src/library/Form/Warning/Wrapper.ts index d31a020a64..cddd5e03b3 100644 --- a/src/library/Form/Warning/Wrapper.ts +++ b/src/library/Form/Warning/Wrapper.ts @@ -1,24 +1,25 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only import styled from 'styled-components'; -import { backgroundLabel } from 'theme'; export const Wrapper = styled.div` - background: ${backgroundLabel}; - margin: 0.6rem 0; - padding: 0.5rem 1rem; + background: var(--background-warning); + border: 1px solid var(--status-warning-color-transparent); + margin: 0.5rem 0; + padding: 0.75rem 0.75rem; border-radius: 0.75rem; display: flex; flex-flow: row wrap; align-items: center; + width: 100%; > h4 { - margin: 0; + color: var(--status-warning-color); .icon { - color: rgba(255, 144, 0, 1); - margin-right: 0.4rem; + color: var(--status-warning-color); + margin-right: 0.6rem; } } `; diff --git a/src/library/Form/Warning/index.tsx b/src/library/Form/Warning/index.tsx index d8762767fe..0af8518375 100644 --- a/src/library/Form/Warning/index.tsx +++ b/src/library/Form/Warning/index.tsx @@ -1,19 +1,15 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only import { faExclamationTriangle } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { WarningProps } from '../types'; +import type { WarningProps } from '../types'; import { Wrapper } from './Wrapper'; export const Warning = ({ text }: WarningProps) => ( <Wrapper> <h4> - <FontAwesomeIcon - icon={faExclamationTriangle} - transform="shrink-2" - className="icon" - /> + <FontAwesomeIcon icon={faExclamationTriangle} className="icon" /> {text} </h4> </Wrapper> diff --git a/src/library/Form/Wrappers.ts b/src/library/Form/Wrappers.ts index 0a7cfb6d03..1f80e5f3a7 100644 --- a/src/library/Form/Wrappers.ts +++ b/src/library/Form/Wrappers.ts @@ -1,8 +1,8 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only import styled from 'styled-components'; -import { borderPrimary, textSecondary } from 'theme'; +import { SmallFontSizeMaxWidth } from 'consts'; export const Spacer = styled.div` width: 100%; @@ -32,7 +32,7 @@ export const InputWrapper = styled.div` flex-flow: column wrap; h3 { - color: ${textSecondary}; + color: var(--text-color-secondary); margin: 0; padding: 0 0.25rem; } @@ -47,31 +47,38 @@ export const InputWrapper = styled.div` > section { &:first-child { - flex-grow: 1; + flex: 1; } &:last-child { padding: 0 0.25rem 0 1.25rem; } .input { - width: 100%; - max-width: 100%; - border: 1px solid ${borderPrimary}; + border: 1px solid var(--border-primary-color); padding: 1rem; border-radius: 0.75rem; display: flex; flex-flow: row wrap; align-items: center; + width: 100%; + max-width: 100%; > div { &:first-child { flex-grow: 1; } &:last-child { - color: ${textSecondary}; + color: var(--text-color-secondary); + padding-left: 0.5rem; justify-content: flex-end; opacity: 0.5; position: relative; + + @media (max-width: ${SmallFontSizeMaxWidth}px) { + display: none; + } + p { + font-family: InterSemiBold, sans-serif; position: absolute; top: 0; left: 0; @@ -85,6 +92,7 @@ export const InputWrapper = styled.div` } } > input { + font-family: InterBold, sans-serif; border: none; padding: 0; width: 100%; @@ -95,4 +103,13 @@ export const InputWrapper = styled.div` } } } + + .availableOuter { + @media (min-width: ${SmallFontSizeMaxWidth + 1}px) { + display: none; + } + color: var(--text-color-secondary); + opacity: 0.5; + padding: 0 0.5rem; + } `; diff --git a/src/library/Form/types.ts b/src/library/Form/types.ts index 648357493e..45617ae9b4 100644 --- a/src/library/Form/types.ts +++ b/src/library/Form/types.ts @@ -1,9 +1,13 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import BN from 'bn.js'; -import { Balance } from 'contexts/Balances/types'; -import { ExtensionAccount, ExternalAccount } from 'contexts/Connect/types'; +import type BigNumber from 'bignumber.js'; +import type { Balance } from 'contexts/Balances/types'; +import type { + ExtensionAccount, + ExternalAccount, +} from '@polkadot-cloud/react/types'; +import type { BondFor, MaybeAddress } from 'types'; export interface ExtensionAccountItem extends ExtensionAccount { active?: boolean; @@ -25,59 +29,49 @@ export interface DropdownInput { } export interface AccountDropdownProps { - items: Array<InputItem>; - onChange: (o: any) => void; - placeholder: string; - value: InputItem; current: InputItem; - height: string | number | undefined; -} - -export interface AccountSelectProps { - items: Array<InputItem>; - onChange: (o: any) => void; - placeholder: string; - value: InputItem; + to: MaybeAddress; } export interface BondFeedbackProps { syncing?: boolean; setters: any; - bondType: string; + bondFor: BondFor; defaultBond: number | null; inSetup?: boolean; - listenIsValid: { (v: boolean): void } | { (): void }; - warnings?: string[]; + joiningPool?: boolean; + listenIsValid: { (valid: boolean, errors: string[]): void } | { (): void }; + parentErrors?: string[]; disableTxFeeUpdate?: boolean; setLocalResize?: () => void; - txFees: BN; + txFees: BigNumber; maxWidth?: boolean; } export interface BondInputProps { + freeBalance: BigNumber; + value: string; + defaultValue: string; syncing?: boolean; setters: any; - value: any; - defaultValue: number | string; disabled: boolean; - freeBalance: number; disableTxFeeUpdate?: boolean; } export interface UnbondFeedbackProps { setters: any; - bondType: string; - defaultBond: number | null; + bondFor: BondFor; + defaultBond?: number; inSetup?: boolean; - listenIsValid: { (v: boolean): void } | { (): void }; - warnings?: string[]; + listenIsValid: { (valid: boolean, errors: string[]): void } | { (): void }; + parentErrors?: string[]; setLocalResize?: () => void; - txFees: BN; + txFees: BigNumber; } export interface UnbondInputProps { - active: BN; - freeToUnbondToMin: number; + active: BigNumber; + unbondToMin: BigNumber; defaultValue: number | string; disabled: boolean; setters: any; @@ -85,11 +79,11 @@ export interface UnbondInputProps { } export interface NominateStatusBarProps { - value: number; + value: BigNumber; } export interface DropdownProps { - items: Array<DropdownInput>; + items: DropdownInput[]; onChange: (o: any) => void; label?: string; placeholder: string; diff --git a/src/library/GenerateNominations/index.tsx b/src/library/GenerateNominations/index.tsx index 8b9d20e3aa..c8c4163670 100644 --- a/src/library/GenerateNominations/index.tsx +++ b/src/library/GenerateNominations/index.tsx @@ -1,63 +1,70 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import { IconProp } from '@fortawesome/fontawesome-svg-core'; import { faChartPie, + faChevronLeft, faCoins, faHeart, faPlus, - faTimes, faUserEdit, } from '@fortawesome/free-solid-svg-icons'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { useEffect, useRef, useState } from 'react'; +import { useTranslation } from 'react-i18next'; import { useApi } from 'contexts/Api'; -import { useConnect } from 'contexts/Connect'; -import { useModal } from 'contexts/Modal'; -import { useValidators } from 'contexts/Validators'; -import { LargeItem } from 'library/Filter/LargeItem'; +import { useValidators } from 'contexts/Validators/ValidatorEntries'; +import { useUnstaking } from 'library/Hooks/useUnstaking'; import { SelectableWrapper } from 'library/List'; +import { SelectItems } from 'library/SelectItems'; +import { SelectItem } from 'library/SelectItems/Item'; import { ValidatorList } from 'library/ValidatorList'; import { Wrapper } from 'pages/Overview/NetworkSats/Wrappers'; -import { useEffect, useRef, useState } from 'react'; -import { - GenerateNominationsInnerProps, - Nominations, -} from '../SetupSteps/types'; +import { useStaking } from 'contexts/Staking'; +import { useFavoriteValidators } from 'contexts/Validators/FavoriteValidators'; +import { useActiveAccounts } from 'contexts/ActiveAccounts'; +import { useImportedAccounts } from 'contexts/Connect/ImportedAccounts'; +import type { Validator } from 'contexts/Validators/types'; +import { ButtonMonoInvert, ButtonPrimaryInvert } from '@polkadot-cloud/react'; +import { Subheading } from 'pages/Nominate/Wrappers'; +import { FavoritesPrompt } from 'canvas/ManageNominations/Prompts/FavoritesPrompt'; +import { usePrompt } from 'contexts/Prompt'; import { useFetchMehods } from './useFetchMethods'; -import { GenerateOptionsWrapper } from './Wrappers'; - -export const GenerateNominations = (props: GenerateNominationsInnerProps) => { - // functional props - const setters = props.setters ?? []; - const defaultNominations = props.nominations; - const { batchKey } = props; +import type { AddNominationsType, GenerateNominationsProps } from './types'; - const { openModalWith } = useModal(); +export const GenerateNominations = ({ + setters = [], + nominations: defaultNominations, + displayFor = 'default', +}: GenerateNominationsProps) => { + const { t } = useTranslation('library'); const { isReady, consts } = useApi(); - const { activeAccount, isReadOnlyAccount } = useConnect(); - const { removeValidatorMetaBatch, validators, meta } = useValidators(); + const { isFastUnstaking } = useUnstaking(); + const { stakers } = useStaking().eraStakers; + const { activeAccount } = useActiveAccounts(); + const { favoritesList } = useFavoriteValidators(); + const { openPromptWith, closePrompt } = usePrompt(); + const { isReadOnlyAccount } = useImportedAccounts(); + const { validators, validatorsFetched } = useValidators(); const { fetch: fetchFromMethod, add: addNomination, available: availableToNominate, } = useFetchMehods(); const { maxNominations } = consts; + const defaultNominationsCount = defaultNominations.nominations?.length || 0; - let { favoritesList } = useValidators(); - if (favoritesList === null) { - favoritesList = []; - } // store the method of fetching validators const [method, setMethod] = useState<string | null>( - defaultNominations.length ? 'Manual' : null + defaultNominationsCount ? 'Manual' : null ); // store whether validators are being fetched const [fetching, setFetching] = useState<boolean>(false); // store the currently selected set of nominations - const [nominations, setNominations] = useState(defaultNominations); + const [nominations, setNominations] = useState<Validator[]>( + defaultNominations.nominations + ); // store the height of the container const [height, setHeight] = useState<number | null>(null); @@ -65,34 +72,28 @@ export const GenerateNominations = (props: GenerateNominationsInnerProps) => { // ref for the height of the container const heightRef = useRef<HTMLDivElement>(null); - const rawBatchKey = 'validators_browse'; - - // update nominations on account switch + // Update nominations on account switch, or if `defaultNominations` change. useEffect(() => { - if (nominations !== defaultNominations) { - removeValidatorMetaBatch(batchKey); - setNominations([...defaultNominations]); + if ( + nominations !== defaultNominations.nominations && + defaultNominationsCount > 0 + ) { + setNominations([...(defaultNominations?.nominations || [])]); + if (defaultNominationsCount) setMethod('manual'); } - }, [activeAccount]); + }, [activeAccount, defaultNominations]); // refetch if fetching is triggered useEffect(() => { - if (!isReady || !validators.length) { + if ( + !isReady || + !validators.length || + !stakers.length || + validatorsFetched !== 'synced' + ) return; - } - // wait for validator meta data to be fetched - const batch = meta[rawBatchKey]; - if (batch === undefined) { - return; - } - if (batch.stake === undefined) { - return; - } - - if (fetching) { - fetchNominationsForMethod(); - } + if (fetching) fetchNominationsForMethod(); }); // reset fixed height on window size change @@ -110,66 +111,52 @@ export const GenerateNominations = (props: GenerateNominationsInnerProps) => { // fetch nominations based on method const fetchNominationsForMethod = () => { if (method) { - const _nominations = fetchFromMethod(method); + const newNominations = fetchFromMethod(method); + // update component state - setNominations([..._nominations]); + setNominations([...newNominations]); setFetching(false); - updateSetters(_nominations); + updateSetters(newNominations); } }; // add nominations based on method - const addNominationByType = (type: string) => { + const addNominationByType = (type: AddNominationsType) => { if (method) { - const _nominations = addNomination(nominations, type); - removeValidatorMetaBatch(batchKey); - setNominations([..._nominations]); - updateSetters([..._nominations]); + const newNominations = addNomination(nominations, type); + setNominations([...newNominations]); + updateSetters([...newNominations]); } }; - const updateSetters = (_nominations: Nominations) => { - for (const s of setters) { - const { current, set } = s; - const callable = current?.callable ?? false; - let _current; - - if (!callable) { - _current = current; - } else { - _current = current.fn(); - } - const _set = { - ..._current, - nominations: _nominations, - }; - set(_set); + const updateSetters = (newNominations: Validator[]) => { + for (const { current, set } of setters) { + const currentValue = current?.callable ? current.fn() : current; + set({ + ...currentValue, + nominations: newNominations, + }); } }; - // callback function for adding nominations + // callback function for adding nominations. const cbAddNominations = ({ setSelectActive }: any) => { setSelectActive(false); - const updateList = (_nominations: Nominations) => { - removeValidatorMetaBatch(batchKey); - setNominations([..._nominations]); - updateSetters(_nominations); + const updateList = (newNominations: Validator[]) => { + setNominations([...newNominations]); + updateSetters(newNominations); + closePrompt(); }; - openModalWith( - 'SelectFavorites', - { - nominations, - callback: updateList, - }, - 'xl' + + openPromptWith( + <FavoritesPrompt callback={updateList} nominations={nominations} /> ); }; // function for clearing nomination list const clearNominations = () => { setMethod(null); - removeValidatorMetaBatch(batchKey); setNominations([]); updateSetters([]); }; @@ -180,65 +167,59 @@ export const GenerateNominations = (props: GenerateNominationsInnerProps) => { resetSelected, setSelectActive, }: any) => { - removeValidatorMetaBatch(batchKey); - const _nominations = [...nominations].filter((n: any) => { - return !selected.map((_s: any) => _s.address).includes(n.address); - }); - setNominations([..._nominations]); - updateSetters([..._nominations]); + const newNominations = [...nominations].filter( + (n: any) => !selected.map((_s: any) => _s.address).includes(n.address) + ); + setNominations([...newNominations]); + updateSetters([...newNominations]); setSelectActive(false); resetSelected(); }; - const disabledMaxNominations = () => { - return nominations.length >= maxNominations; - }; - const disabledAddFavorites = () => { - return !favoritesList?.length || nominations.length >= maxNominations; - }; + const disabledMaxNominations = () => + maxNominations.isLessThanOrEqualTo(nominations.length); + const disabledAddFavorites = () => + !favoritesList?.length || + maxNominations.isLessThanOrEqualTo(nominations.length); // accumulate generation methods const methods = [ { - title: 'Optimal Selection', - subtitle: 'Selects a mix of majority active and inactive validators.', - icon: faChartPie as IconProp, + title: t('optimalSelection'), + subtitle: t('optimalSelectionSubtitle'), + icon: faChartPie, onClick: () => { setMethod('Optimal Selection'); - removeValidatorMetaBatch(batchKey); setNominations([]); setFetching(true); }, }, { - title: 'Active Low Commission', - subtitle: 'Gets a set of active validators with low commission.', - icon: faCoins as IconProp, + title: t('activeLowCommission'), + subtitle: t('activeLowCommissionSubtitle'), + icon: faCoins, onClick: () => { setMethod('Active Low Commission'); - removeValidatorMetaBatch(batchKey); setNominations([]); setFetching(true); }, }, { - title: 'From Favorites', - subtitle: 'Gets a set of your favorite validators.', - icon: faHeart as IconProp, + title: t('fromFavorites'), + subtitle: t('fromFavoritesSubtitle'), + icon: faHeart, onClick: () => { setMethod('From Favorites'); - removeValidatorMetaBatch(batchKey); setNominations([]); setFetching(true); }, }, { - title: 'Manual Selection', - subtitle: 'Add validators from scratch.', - icon: faUserEdit as IconProp, + title: t('manual_selection'), + subtitle: t('manualSelectionSubtitle'), + icon: faUserEdit, onClick: () => { setMethod('Manual'); - removeValidatorMetaBatch(batchKey); setNominations([]); }, }, @@ -247,19 +228,28 @@ export const GenerateNominations = (props: GenerateNominationsInnerProps) => { // accumulate actions const actions = [ { - title: 'Add From Favorites', + title: t('addFromFavorites'), onClick: cbAddNominations, onSelected: false, isDisabled: disabledAddFavorites, }, { - title: `Remove Selected`, + title: `${t('removeSelected')}`, onClick: cbRemoveSelected, onSelected: true, isDisabled: () => false, }, { - title: 'Active Validator', + title: t('highPerformanceValidator'), + onClick: () => addNominationByType('High Performance Validator'), + onSelected: false, + icon: faPlus, + isDisabled: () => + disabledMaxNominations() || + !availableToNominate(nominations).highPerformance.length, + }, + { + title: t('activeValidator'), onClick: () => addNominationByType('Active Validator'), onSelected: false, icon: faPlus, @@ -268,7 +258,7 @@ export const GenerateNominations = (props: GenerateNominationsInnerProps) => { !availableToNominate(nominations).activeValidators.length, }, { - title: 'Random Validator', + title: t('randomValidator'), onClick: () => addNominationByType('Random Validator'), onSelected: false, icon: faPlus, @@ -278,30 +268,34 @@ export const GenerateNominations = (props: GenerateNominationsInnerProps) => { }, ]; + // Determine button style depending on in canvas. + const ButtonType = + displayFor === 'canvas' ? ButtonPrimaryInvert : ButtonMonoInvert; + return ( <> {method && ( <SelectableWrapper> - <button type="button" onClick={() => clearNominations()}> - <FontAwesomeIcon icon={faTimes as IconProp} /> - {method} - </button> + <ButtonType + text={t('backToMethods')} + iconLeft={faChevronLeft} + iconTransform="shrink-2" + onClick={() => clearNominations()} + marginRight + /> {['Active Low Commission', 'Optimal Selection'].includes( method || '' ) && ( - <button - type="button" + <ButtonType + text={t('reGenerate')} onClick={() => { // set a temporary height to prevent height snapping on re-renders. setHeight(heightRef?.current?.clientHeight || null); setTimeout(() => setHeight(null), 200); - removeValidatorMetaBatch(batchKey); setFetching(true); }} - > - Re-Generate - </button> + /> )} </SelectableWrapper> )} @@ -314,19 +308,30 @@ export const GenerateNominations = (props: GenerateNominationsInnerProps) => { <div> {!isReadOnlyAccount(activeAccount) && !method && ( <> - <GenerateOptionsWrapper> + <Subheading> + <h4> + {t('chooseValidators2', { + maxNominations: maxNominations.toString(), + })} + </h4> + </Subheading> + <SelectItems layout="three-col"> {methods.map((m: any, n: number) => ( - <LargeItem + <SelectItem key={`gen_method_${n}`} title={m.title} subtitle={m.subtitle} icon={m.icon} - transform="grow-2" - active={false} + selected={false} onClick={m.onClick} + disabled={isFastUnstaking} + includeToggle={false} + grow={false} + hoverBorder + layout="three-col" /> ))} - </GenerateOptionsWrapper> + </SelectItems> </> )} </div> @@ -343,13 +348,13 @@ export const GenerateNominations = (props: GenerateNominationsInnerProps) => { }} > <ValidatorList - bondType="stake" + bondFor="nominator" validators={nominations} - batchKey={batchKey} - selectable actions={actions} allowMoreCols allowListFormat={false} + displayFor={displayFor} + selectable /> </div> )} @@ -359,5 +364,3 @@ export const GenerateNominations = (props: GenerateNominationsInnerProps) => { </> ); }; - -export default GenerateNominations; diff --git a/src/library/GenerateNominations/types.ts b/src/library/GenerateNominations/types.ts new file mode 100644 index 0000000000..65262d10ed --- /dev/null +++ b/src/library/GenerateNominations/types.ts @@ -0,0 +1,24 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import type { Validator } from 'contexts/Validators/types'; +import type { AnyFunction, DisplayFor } from 'types'; + +export interface GenerateNominationsProps { + setters: AnyFunction[]; + nominations: NominationSelection; + displayFor?: DisplayFor; +} + +export type NominationSelectionWithResetCounter = NominationSelection & { + reset: number; +}; + +export interface NominationSelection { + nominations: Validator[]; +} + +export type AddNominationsType = + | 'High Performance Validator' + | 'Active Validator' + | 'Random Validator'; diff --git a/src/library/GenerateNominations/useFetchMethods.tsx b/src/library/GenerateNominations/useFetchMethods.tsx index 58e2f35e3c..d6580a82df 100644 --- a/src/library/GenerateNominations/useFetchMethods.tsx +++ b/src/library/GenerateNominations/useFetchMethods.tsx @@ -1,21 +1,17 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import { useValidators } from 'contexts/Validators'; -import { Validator } from 'contexts/Validators/types'; +import { shuffle } from '@polkadot-cloud/utils'; +import { useFavoriteValidators } from 'contexts/Validators/FavoriteValidators'; +import { useValidators } from 'contexts/Validators/ValidatorEntries'; +import type { Validator } from 'contexts/Validators/types'; import { useValidatorFilters } from 'library/Hooks/useValidatorFilters'; -import { shuffle } from 'Utils'; +import type { AddNominationsType } from './types'; export const useFetchMehods = () => { - const includeTestnet = process.env.REACT_APP_INCLUDE_TESTNET !== 'false'; - const { validators } = useValidators(); + const { favoritesList } = useFavoriteValidators(); const { applyFilter, applyOrder } = useValidatorFilters(); - let { favoritesList } = useValidators(); - if (favoritesList === null) { - favoritesList = []; - } - - const rawBatchKey = 'validators_browse'; + const { validators, validatorEraPointsHistory } = useValidators(); const fetch = (method: string) => { let nominations; @@ -35,10 +31,10 @@ export const useFetchMehods = () => { return nominations; }; - const add = (nominations: any, type: string) => { + const add = (nominations: Validator[], type: AddNominationsType) => { switch (type) { - case 'Parachain Validator': - nominations = addParachainValidator(nominations); + case 'High Performance Validator': + nominations = addHighPerformanceValidator(nominations); break; case 'Active Validator': nominations = addActiveValidator(nominations); @@ -53,168 +49,161 @@ export const useFetchMehods = () => { }; const fetchFavorites = () => { - let _favs: Array<Validator> = []; + let favs: Validator[] = []; if (!favoritesList) { - return _favs; + return favs; } - if (favoritesList.length) { + if (favoritesList?.length) { // take subset of up to 16 favorites - _favs = favoritesList.slice(0, 16); + favs = favoritesList.slice(0, 16); } - return _favs; + return favs; }; const fetchLowCommission = () => { - let _nominations = Object.assign(validators); + let filtered = Object.assign(validators); // filter validators to find active candidates - _nominations = applyFilter( + filtered = applyFilter( ['active'], - [ - 'all_commission', - 'blocked_nominations', - includeTestnet ? '' : 'missing_identity', - ], - _nominations, - rawBatchKey + ['all_commission', 'blocked_nominations', 'missing_identity'], + filtered ); // order validators to find profitable candidates - _nominations = applyOrder('low_commission', _nominations); + filtered = applyOrder('low_commission', filtered); + + // take the lowest commission half of the set + filtered = filtered.slice(0, filtered.length * 0.5); + + // keep validators that are in upper 75% performance quartile. + filtered = filtered.filter((a: Validator) => { + const quartile = validatorEraPointsHistory[a.address]?.quartile || 100; + return quartile <= 75; + }); // choose shuffled subset of validators - if (_nominations.length) { - _nominations = shuffle( - _nominations.slice(0, _nominations.length * 0.5) - ).slice(0, 16); + if (filtered.length) { + filtered = shuffle(filtered).slice(0, 16); } - return _nominations; + return filtered; }; const fetchOptimal = () => { - let _nominationsActive = Object.assign(validators); - let _nominationsWaiting = Object.assign(validators); + let active = Object.assign(validators); + let waiting = Object.assign(validators); // filter validators to find waiting candidates - _nominationsWaiting = applyFilter( + waiting = applyFilter( null, [ 'all_commission', 'blocked_nominations', - includeTestnet ? '' : 'missing_identity', + 'missing_identity', 'in_session', ], - _nominationsWaiting, - rawBatchKey + waiting ); // filter validators to find active candidates - _nominationsActive = applyFilter( + active = applyFilter( ['active'], - [ - 'all_commission', - 'blocked_nominations', - includeTestnet ? '' : 'missing_identity', - ], - _nominationsActive, - rawBatchKey + ['all_commission', 'blocked_nominations', 'missing_identity'], + active ); + // keep validators that are in upper 50% performance quartile. + active = active.filter((a: Validator) => { + const quartile = validatorEraPointsHistory[a.address]?.quartile || 100; + return quartile <= 50; + }); + // choose shuffled subset of waiting - if (_nominationsWaiting.length) { - _nominationsWaiting = shuffle(_nominationsWaiting).slice(0, 4); + if (waiting.length) { + waiting = shuffle(waiting).slice(0, 2); } // choose shuffled subset of active - if (_nominationsActive.length) { - _nominationsActive = shuffle(_nominationsActive).slice(0, 12); + if (active.length) { + active = shuffle(active).slice(0, 14); } - return shuffle(_nominationsWaiting.concat(_nominationsActive)); - }; - - const available = (nominations: any) => { - const _nominations = Object.assign(validators); - - const _parachainValidators = applyFilter( - ['active'], - [ - 'all_commission', - 'blocked_nominations', - 'missing_identity', - 'not_parachain_validator', - ], - _nominations, - rawBatchKey - ).filter( - (n: any) => !nominations.find((o: any) => o.address === n.address) - ); - const _activeValidators = applyFilter( - ['active'], - [ - 'all_commission', - 'blocked_nominations', - includeTestnet ? '' : 'missing_identity', - ], - _nominations, - rawBatchKey - ).filter( - (n: any) => !nominations.find((o: any) => o.address === n.address) - ); - // .filter((n: any) => !sessionParachain?.includes(n.address) || false); + return shuffle(waiting.concat(active)); + }; - const _randomValidator = applyFilter( - null, - [ - 'all_commission', - 'blocked_nominations', - includeTestnet ? '' : 'missing_identity', - ], - _nominations, - rawBatchKey - ).filter( - (n: any) => !nominations.find((o: any) => o.address === n.address) - ); + const available = (nominations: Validator[]) => { + const all = Object.assign(validators); + + const parachainActive = + applyFilter( + ['active'], + [ + 'all_commission', + 'blocked_nominations', + 'missing_identity', + 'not_parachain_validator', + ], + all + ).filter( + (n: Validator) => !nominations.find((o) => o.address === n.address) + ) || []; + + const active = + applyFilter( + ['active'], + ['all_commission', 'blocked_nominations', 'missing_identity'], + all + ).filter( + (n: Validator) => !nominations.find((o) => o.address === n.address) + ) || []; + + const highPerformance = active.filter((a: Validator) => { + const quartile = validatorEraPointsHistory[a.address]?.quartile || 100; + return quartile <= 50; + }); + + const random = + applyFilter( + null, + ['all_commission', 'blocked_nominations', 'missing_identity'], + all + ).filter( + (n: Validator) => !nominations.find((o) => o.address === n.address) + ) || []; return { - parachainValidators: _parachainValidators, - activeValidators: _activeValidators, - randomValidators: _randomValidator, + parachainValidators: parachainActive, + highPerformance, + activeValidators: active, + randomValidators: random, }; }; - const addActiveValidator = (nominations: any) => { - const _nominations = available(nominations).activeValidators; + const addActiveValidator = (nominations: Validator[]) => { + const all: Validator[] = available(nominations).activeValidators; // take one validator - const validator = shuffle(_nominations).slice(0, 1)[0] || null; - if (validator) { - nominations.push(validator); - } + const validator = shuffle(all).slice(0, 1)[0] || null; + if (validator) nominations.push(validator); return nominations; }; - const addParachainValidator = (nominations: any) => { - const _nominations = available(nominations).parachainValidators; + const addHighPerformanceValidator = (nominations: Validator[]) => { + const all: Validator[] = available(nominations).highPerformance; // take one validator - const validator = shuffle(_nominations).slice(0, 1)[0] || null; - if (validator) { - nominations.push(validator); - } + const validator = shuffle(all).slice(0, 1)[0] || null; + if (validator) nominations.push(validator); return nominations; }; - const addRandomValidator = (nominations: any) => { - const _nominations = available(nominations).randomValidators; + const addRandomValidator = (nominations: Validator[]) => { + const all: Validator[] = available(nominations).randomValidators; // take one validator - const validator = shuffle(_nominations).slice(0, 1)[0] || null; - - if (validator) { - nominations.push(validator); - } + const validator = shuffle(all).slice(0, 1)[0] || null; + if (validator) nominations.push(validator); return nominations; }; diff --git a/src/library/Graphs/Bonded.tsx b/src/library/Graphs/Bonded.tsx deleted file mode 100644 index f2734a8834..0000000000 --- a/src/library/Graphs/Bonded.tsx +++ /dev/null @@ -1,125 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { ArcElement, Chart as ChartJS, Legend, Tooltip } from 'chart.js'; -import { useApi } from 'contexts/Api'; -import { useTheme } from 'contexts/Themes'; -import { Doughnut } from 'react-chartjs-2'; -import { defaultThemes, networkColors } from 'theme/default'; -import { humanNumber } from 'Utils'; -import { BondedProps } from './types'; -import { GraphWrapper } from './Wrappers'; - -ChartJS.register(ArcElement, Tooltip, Legend); - -export const Bonded = ({ - active, - free, - unlocking, - unlocked, - inactive, -}: BondedProps) => { - const { mode } = useTheme(); - const { network } = useApi(); - - // graph data - let graphActive = active; - let graphUnlocking = unlocking + unlocked; - let graphFree = free; - - let zeroBalance = false; - if (inactive) { - graphActive = -1; - graphUnlocking = -1; - graphFree = -1; - zeroBalance = true; - } - - const options = { - responsive: true, - maintainAspectRatio: false, - spacing: zeroBalance ? 0 : 2, - plugins: { - legend: { - padding: { - right: 20, - }, - display: true, - position: 'left' as const, - align: 'center' as const, - labels: { - padding: 20, - color: defaultThemes.text.primary[mode], - font: { - size: 13, - weight: '600', - }, - }, - }, - tooltip: { - displayColors: false, - backgroundColor: defaultThemes.graphs.tooltip[mode], - titleColor: defaultThemes.text.invert[mode], - bodyColor: defaultThemes.text.invert[mode], - bodyFont: { - weight: '600', - }, - callbacks: { - label: (context: any) => { - if (inactive) { - return 'Inactive'; - } - return `${ - context.parsed === -1 ? 0 : humanNumber(context.parsed) - } ${network.unit}`; - }, - }, - }, - }, - cutout: '75%', - }; - const _colors = zeroBalance - ? [ - defaultThemes.graphs.colors[1][mode], - defaultThemes.graphs.inactive2[mode], - defaultThemes.graphs.inactive[mode], - ] - : [ - networkColors[`${network.name}-${mode}`], - defaultThemes.graphs.colors[0][mode], - defaultThemes.graphs.colors[1][mode], - ]; - - const data = { - labels: ['Active', 'Unlocking', 'Free'], - datasets: [ - { - label: network.unit, - data: [graphActive, graphUnlocking, graphFree], - backgroundColor: _colors, - borderWidth: 0, - }, - ], - }; - - return ( - <GraphWrapper - transparent - noMargin - style={{ border: 'none', boxShadow: 'none' }} - > - <div - className="graph" - style={{ - flex: 0, - paddingRight: '1rem', - height: 160, - }} - > - <Doughnut options={options} data={data} /> - </div> - </GraphWrapper> - ); -}; - -export default Bonded; diff --git a/src/library/Graphs/EraPoints.tsx b/src/library/Graphs/EraPoints.tsx index 986600efdd..459b644439 100644 --- a/src/library/Graphs/EraPoints.tsx +++ b/src/library/Graphs/EraPoints.tsx @@ -1,5 +1,5 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only import { CategoryScale, @@ -11,11 +11,12 @@ import { Title, Tooltip, } from 'chart.js'; -import { useApi } from 'contexts/Api'; -import { useTheme } from 'contexts/Themes'; import { Line } from 'react-chartjs-2'; -import { defaultThemes, networkColors } from 'theme/default'; -import { EraPointsProps } from './types'; +import { useTranslation } from 'react-i18next'; +import { useTheme } from 'contexts/Themes'; +import { graphColors } from 'styles/graphs'; +import { useNetwork } from 'contexts/Network'; +import type { EraPointsProps } from './types'; ChartJS.register( CategoryScale, @@ -28,8 +29,9 @@ ChartJS.register( ); export const EraPoints = ({ items = [], height }: EraPointsProps) => { + const { t } = useTranslation('library'); const { mode } = useTheme(); - const { name } = useApi().network; + const { colors } = useNetwork().networkData; const options = { responsive: true, @@ -40,7 +42,7 @@ export const EraPoints = ({ items = [], height }: EraPointsProps) => { display: false, }, grid: { - color: defaultThemes.transparent[mode], + color: 'rgba(0,0,0,0)', }, ticks: { display: true, @@ -60,7 +62,7 @@ export const EraPoints = ({ items = [], height }: EraPointsProps) => { display: false, }, grid: { - color: defaultThemes.graphs.grid[mode], + color: graphColors.grid[mode], }, ticks: { display: true, @@ -74,20 +76,18 @@ export const EraPoints = ({ items = [], height }: EraPointsProps) => { }, title: { display: false, - text: 'Era Points', + text: t('eraPoints'), }, tooltip: { displayColors: false, - backgroundColor: defaultThemes.graphs.tooltip[mode], - titleColor: defaultThemes.text.invert[mode], - bodyColor: defaultThemes.text.invert[mode], + backgroundColor: graphColors.tooltip[mode], + titleColor: graphColors.label[mode], + bodyColor: graphColors.label[mode], bodyFont: { weight: '600', }, callbacks: { - title: () => { - return []; - }, + title: () => [], label: (context: any) => `${context.parsed.y}`, }, intersect: false, @@ -102,10 +102,10 @@ export const EraPoints = ({ items = [], height }: EraPointsProps) => { labels: items.map((item: any) => item.era), datasets: [ { - label: 'Points', + label: t('points'), data: items.map((item: any) => item.reward_point), - borderColor: networkColors[`${name}-${mode}`], - backgroundColor: networkColors[`${name}-${mode}`], + borderColor: colors.primary[mode], + backgroundColor: colors.primary[mode], pointStyle: undefined, pointRadius: 0, borderWidth: 2, @@ -123,5 +123,3 @@ export const EraPoints = ({ items = [], height }: EraPointsProps) => { </div> ); }; - -export default EraPoints; diff --git a/src/library/Graphs/GeoDonut.tsx b/src/library/Graphs/GeoDonut.tsx new file mode 100644 index 0000000000..4c94142949 --- /dev/null +++ b/src/library/Graphs/GeoDonut.tsx @@ -0,0 +1,93 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { ArcElement, Chart as ChartJS, Legend, Tooltip } from 'chart.js'; +import { Doughnut } from 'react-chartjs-2'; +import { useTheme } from 'contexts/Themes'; +import { graphColors } from 'styles/graphs'; +import chroma from 'chroma-js'; +import { ellipsisFn } from '@polkadot-cloud/utils'; +import { useNetwork } from 'contexts/Network'; +import type { GeoDonutProps } from './types'; + +ChartJS.register(ArcElement, Tooltip, Legend); + +export const GeoDonut = ({ + title, + series = { labels: [], data: [] }, + height = 'auto', + width = 'auto', +}: GeoDonutProps) => { + const { mode } = useTheme(); + const { colors } = useNetwork().networkData; + + const { labels } = series; + let { data } = series; + const isZero = data.length === 0; + const backgroundColor = isZero + ? graphColors.inactive[mode] + : colors.primary[mode]; + + const total = data.reduce((acc: number, value: number) => acc + value, 0); + + data = data.map((value: number) => (value / total) * 100); + + const options = { + borderColor: graphColors.inactive[mode], + hoverBorderColor: graphColors.inactive[mode], + backgroundColor, + hoverBackgroundColor: [backgroundColor, graphColors.inactive[mode]], + responsive: true, + maintainAspectRatio: false, + spacing: 0, + cutout: '70%', + plugins: { + legend: { + display: true, + position: 'bottom' as const, + maxHeight: 25, + labels: { + boxWidth: 10, + generateLabels: (chart: any) => { + const ls = + ChartJS.overrides.doughnut.plugins.legend.labels.generateLabels( + chart + ); + return ls.map((l) => { + l.text = ellipsisFn(l.text, undefined, 'end'); + return l; + }); + }, + }, + }, + tooltip: { + enabled: true, + callbacks: { + label: (context: any) => ` ${title}: ${context.raw.toFixed(1)} %`, + }, + }, + }, + }; + + const chartData = { + labels, + datasets: [ + { + label: title, + data, + // We make a gradient of N+2 colors from active to inactive, and we discard both ends + // N is the number of datapoints to plot + backgroundColor: chroma + .scale([backgroundColor, graphColors.inactive[mode]]) + .colors(data.length + 1), + borderWidth: 0.5, + }, + ], + }; + + return ( + <div style={{ width, height }}> + <Doughnut options={options} data={chartData} /> + </div> + ); +}; diff --git a/src/library/Graphs/PayoutBar.tsx b/src/library/Graphs/PayoutBar.tsx index 27f2f520e2..8182cdcba5 100644 --- a/src/library/Graphs/PayoutBar.tsx +++ b/src/library/Graphs/PayoutBar.tsx @@ -1,6 +1,7 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only +import BigNumber from 'bignumber.js'; import { BarElement, CategoryScale, @@ -12,25 +13,20 @@ import { Title, Tooltip, } from 'chart.js'; -import { useApi } from 'contexts/Api'; -import { useCereStats } from 'contexts/CereStats'; +import { format, fromUnixTime } from 'date-fns'; +import { Bar } from 'react-chartjs-2'; +import { useTranslation } from 'react-i18next'; +import { DefaultLocale } from 'consts'; import { usePoolMemberships } from 'contexts/Pools/PoolMemberships'; import { useStaking } from 'contexts/Staking'; +import { useCereStats } from 'contexts/CereStats'; import { useTheme } from 'contexts/Themes'; import { useUi } from 'contexts/UI'; -import { format, fromUnixTime } from 'date-fns'; import { locales } from 'locale'; -import { Bar } from 'react-chartjs-2'; -import { useTranslation } from 'react-i18next'; -import { - defaultThemes, - networkColors, - networkColorsSecondary, - networkColorsTransparent, -} from 'theme/default'; -import { AnySubscan } from 'types'; -import { humanNumber } from 'Utils'; -import { PayoutBarProps } from './types'; +import { graphColors } from 'styles/graphs'; +import type { AnySubscan } from 'types'; +import { useNetwork } from 'contexts/Network'; +import type { PayoutBarProps } from './types'; import { formatRewardsForGraphs } from './Utils'; ChartJS.register( @@ -45,64 +41,85 @@ ChartJS.register( ); export const PayoutBar = ({ days, height }: PayoutBarProps) => { + const { i18n, t } = useTranslation('library'); const { mode } = useTheme(); - const { name, unit, units } = useApi().network; const { isSyncing } = useUi(); const { inSetup } = useStaking(); const { membership } = usePoolMemberships(); - const { payouts, poolClaims } = useCereStats(); - const { i18n } = useTranslation(); + const { unit, units, colors } = useNetwork().networkData; + const { payouts, poolClaims, unclaimedPayouts } = useCereStats(); + const notStaking = !isSyncing && inSetup() && !membership; // remove slashes from payouts (graph does not support negative values). const payoutsNoSlash = payouts.filter( (p: AnySubscan) => p.event_id !== 'Slashed' ); - const notStaking = !isSyncing && inSetup() && !membership; - const average = 1; - - const { payoutsByDay, poolClaimsByDay } = formatRewardsForGraphs( - days, - average, - units, - payoutsNoSlash, - poolClaims + // remove slashes from unclaimed payouts. + const unclaimedPayoutsNoSlash = unclaimedPayouts.filter( + (p: AnySubscan) => p.event_id !== 'Slashed' ); + // get formatted rewards data for graph. + const { allPayouts, allPoolClaims, allUnclaimedPayouts } = + formatRewardsForGraphs( + new Date(), + days, + units, + payoutsNoSlash, + poolClaims, + unclaimedPayoutsNoSlash + ); + + const { p: graphPayouts } = allPayouts; + const { p: graphUnclaimedPayouts } = allUnclaimedPayouts; + const { p: graphPoolClaims } = allPoolClaims; + // determine color for payouts const colorPayouts = notStaking - ? networkColorsTransparent[`${name}-${mode}`] - : networkColors[`${name}-${mode}`]; + ? colors.transparent[mode] + : colors.primary[mode]; // determine color for poolClaims const colorPoolClaims = notStaking - ? networkColorsTransparent[`${name}-${mode}`] - : networkColorsSecondary[`${name}-${mode}`]; + ? colors.transparent[mode] + : colors.secondary[mode]; const data = { - labels: payoutsByDay.map((item: AnySubscan) => { + labels: graphPayouts.map((item: AnySubscan) => { const dateObj = format(fromUnixTime(item.block_timestamp), 'do MMM', { - locale: locales[i18n.resolvedLanguage], + locale: locales[i18n.resolvedLanguage ?? DefaultLocale], }); return `${dateObj}`; }), datasets: [ { - label: 'Payout', - data: payoutsByDay.map((item: AnySubscan) => item.amount), + order: 1, + label: t('payout'), + data: graphPayouts.map((item: AnySubscan) => item.amount), borderColor: colorPayouts, backgroundColor: colorPayouts, pointRadius: 0, borderRadius: 3, }, { - label: 'Pool Claim', - data: poolClaimsByDay.map((item: AnySubscan) => item.amount), + order: 2, + label: t('poolClaim'), + data: graphPoolClaims.map((item: AnySubscan) => item.amount), borderColor: colorPoolClaims, backgroundColor: colorPoolClaims, pointRadius: 0, borderRadius: 3, }, + { + order: 3, + data: graphUnclaimedPayouts.map((item: AnySubscan) => item.amount), + label: t('unclaimedPayouts'), + borderColor: colorPayouts, + backgroundColor: colors.pending[mode], + pointRadius: 0, + borderRadius: 3, + }, ], }; @@ -135,7 +152,7 @@ export const PayoutBar = ({ days, height }: PayoutBarProps) => { display: false, }, grid: { - color: defaultThemes.graphs.grid[mode], + color: graphColors.grid[mode], }, }, }, @@ -148,15 +165,20 @@ export const PayoutBar = ({ days, height }: PayoutBarProps) => { }, tooltip: { displayColors: false, - backgroundColor: defaultThemes.graphs.tooltip[mode], - titleColor: defaultThemes.text.invert[mode], - bodyColor: defaultThemes.text.invert[mode], + backgroundColor: graphColors.tooltip[mode], + titleColor: graphColors.label[mode], + bodyColor: graphColors.label[mode], bodyFont: { weight: '600', }, callbacks: { title: () => [], - label: (context: any) => `${humanNumber(context.parsed.y)} ${unit}`, + label: (context: any) => + `${ + context.dataset.order === 3 ? `${t('pending')}: ` : '' + }${new BigNumber(context.parsed.y) + .decimalPlaces(units) + .toFormat()} ${unit}`, }, }, }, @@ -172,5 +194,3 @@ export const PayoutBar = ({ days, height }: PayoutBarProps) => { </div> ); }; - -export default PayoutBar; diff --git a/src/library/Graphs/PayoutLine.tsx b/src/library/Graphs/PayoutLine.tsx index 38a0140d70..dd485339dd 100644 --- a/src/library/Graphs/PayoutLine.tsx +++ b/src/library/Graphs/PayoutLine.tsx @@ -1,6 +1,7 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only +import BigNumber from 'bignumber.js'; import { CategoryScale, Chart as ChartJS, @@ -11,22 +12,22 @@ import { Title, Tooltip, } from 'chart.js'; -import { useApi } from 'contexts/Api'; -import { useCereStats } from 'contexts/CereStats'; +import { Line } from 'react-chartjs-2'; +import { useTranslation } from 'react-i18next'; import { usePoolMemberships } from 'contexts/Pools/PoolMemberships'; import { useStaking } from 'contexts/Staking'; +import { useCereStats } from 'contexts/CereStats'; import { useTheme } from 'contexts/Themes'; import { useUi } from 'contexts/UI'; -import { Line } from 'react-chartjs-2'; +import { graphColors } from 'styles/graphs'; +import type { AnySubscan } from 'types'; +import { useNetwork } from 'contexts/Network'; +import type { PayoutLineProps } from './types'; import { - defaultThemes, - networkColors, - networkColorsSecondary, -} from 'theme/default'; -import { AnySubscan } from 'types'; -import { humanNumber } from 'Utils'; -import { PayoutLineProps } from './types'; -import { combineRewardsByDay, formatRewardsForGraphs } from './Utils'; + calculatePayoutAverages, + combineRewards, + formatRewardsForGraphs, +} from './Utils'; ChartJS.register( CategoryScale, @@ -44,12 +45,13 @@ export const PayoutLine = ({ height, background, }: PayoutLineProps) => { + const { t } = useTranslation('library'); const { mode } = useTheme(); - const { name, unit, units } = useApi().network; const { isSyncing } = useUi(); const { inSetup } = useStaking(); - const { membership: poolMembership } = usePoolMemberships(); const { payouts, poolClaims } = useCereStats(); + const { unit, units, colors } = useNetwork().networkData; + const { membership: poolMembership } = usePoolMemberships(); const notStaking = !isSyncing && inSetup() && !poolMembership; const poolingOnly = !isSyncing && inSetup() && poolMembership !== null; @@ -59,23 +61,38 @@ export const PayoutLine = ({ (p: AnySubscan) => p.event_id !== 'Slashed' ); - const { payoutsByDay, poolClaimsByDay } = formatRewardsForGraphs( + // define the most recent date that we will show on the graph. + const fromDate = new Date(); + + const { allPayouts, allPoolClaims } = formatRewardsForGraphs( + fromDate, days, - average, units, payoutsNoSlash, - poolClaims + poolClaims, + [] // Note: we are not using `unclaimedPayouts` here. ); - // combine payouts and pool claims into one dataset - const combinedPayouts = combineRewardsByDay(payoutsByDay, poolClaimsByDay); + const { p: graphPayouts, a: graphPrePayouts } = allPayouts; + const { p: graphPoolClaims, a: graphPrePoolClaims } = allPoolClaims; + + // combine payouts and pool claims into one dataset and calculate averages. + const combined = combineRewards(graphPayouts, graphPoolClaims); + const preCombined = combineRewards(graphPrePayouts, graphPrePoolClaims); + + const combinedPayouts = calculatePayoutAverages( + preCombined.concat(combined), + fromDate, + days, + 10 + ); // determine color for payouts const color = notStaking - ? networkColors[`${name}-${mode}`] + ? colors.primary[mode] : !poolingOnly - ? networkColors[`${name}-${mode}`] - : networkColorsSecondary[`${name}-${mode}`]; + ? colors.primary[mode] + : colors.secondary[mode]; // configure graph options const options = { @@ -101,7 +118,7 @@ export const PayoutLine = ({ display: false, }, grid: { - color: defaultThemes.graphs.grid[mode], + color: graphColors.grid[mode], }, }, }, @@ -111,15 +128,18 @@ export const PayoutLine = ({ }, tooltip: { displayColors: false, - backgroundColor: defaultThemes.graphs.tooltip[mode], - titleColor: defaultThemes.text.invert[mode], - bodyColor: defaultThemes.text.invert[mode], + backgroundColor: graphColors.tooltip[mode], + titleColor: graphColors.label[mode], + bodyColor: graphColors.label[mode], bodyFont: { weight: '600', }, callbacks: { title: () => [], - label: (context: any) => ` ${humanNumber(context.parsed.y)} ${unit}`, + label: (context: any) => + ` ${new BigNumber(context.parsed.y) + .decimalPlaces(units) + .toFormat()} ${unit}`, }, intersect: false, interaction: { @@ -130,10 +150,10 @@ export const PayoutLine = ({ }; const data = { - labels: payoutsByDay.map(() => ''), + labels: combinedPayouts.map(() => ''), datasets: [ { - label: 'Payout', + label: t('payout'), data: combinedPayouts.map((item: AnySubscan) => item?.amount ?? 0), borderColor: color, backgroundColor: color, @@ -146,14 +166,15 @@ export const PayoutLine = ({ return ( <> - <h5 className="secondary"> - {average > 1 ? `${average} Day Average` : null} + <h5 className="secondary" style={{ paddingLeft: '1.5rem' }}> + {average > 1 ? `${average} ${t('dayAverage')}` : null} </h5> <div - className="graph_line" style={{ height: height || 'auto', background: background || 'none', + marginTop: '0.6rem', + padding: '0 0 0.5rem 1.5rem', }} > <Line options={options} data={data} /> @@ -161,5 +182,3 @@ export const PayoutLine = ({ </> ); }; - -export default PayoutLine; diff --git a/src/library/Graphs/StatBoxPie.tsx b/src/library/Graphs/StatBoxPie.tsx deleted file mode 100644 index 26db1641f7..0000000000 --- a/src/library/Graphs/StatBoxPie.tsx +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { ArcElement, Chart as ChartJS, Tooltip } from 'chart.js'; -import { useApi } from 'contexts/Api'; -import { useTheme } from 'contexts/Themes'; -import { Pie } from 'react-chartjs-2'; -import { - defaultThemes, - networkColors, - networkColorsTransparent, -} from 'theme/default'; -import { StatPieProps } from './types'; - -ChartJS.register(ArcElement, Tooltip); - -export const StatPie = ({ value, value2 }: StatPieProps) => { - // format zero value graph - const isZero = !value && !value; - if (isZero) { - value = 1; - value2 = 0; - } - - const { name } = useApi().network; - const { mode } = useTheme(); - - const borderColor = isZero - ? defaultThemes.buttons.toggle.background[mode] - : [networkColors[`${name}-${mode}`], defaultThemes.transparent[mode]]; - - const backgroundColor = isZero - ? defaultThemes.buttons.toggle.background[mode] - : networkColorsTransparent[`${name}-${mode}`]; - - const options = { - borderColor, - hoverBorderColor: borderColor, - backgroundColor, - hoverBackgroundColor: [ - networkColorsTransparent[`${name}-${mode}`], - defaultThemes.transparent[mode], - ], - responsive: true, - maintainAspectRatio: false, - spacing: 0, - plugins: { - legend: { - display: false, - }, - tooltip: { - enabled: false, - }, - }, - }; - - const data = { - datasets: [ - { - data: [value, value2], - backgroundColor: [ - networkColorsTransparent[`${name}-${mode}`], - defaultThemes.transparent[mode], - ], - borderWidth: 1.6, - }, - ], - }; - - return ( - <div className="graph" style={{ width: 36, height: 36 }}> - <Pie options={options} data={data} /> - </div> - ); -}; - -export default StatPie; diff --git a/src/library/Graphs/Utils.ts b/src/library/Graphs/Utils.ts index 776176a88f..2c75cb8582 100644 --- a/src/library/Graphs/Utils.ts +++ b/src/library/Graphs/Utils.ts @@ -1,356 +1,307 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import BN from 'bn.js'; -import { useUi } from 'contexts/UI'; +import { greaterThanZero, planckToUnit } from '@polkadot-cloud/utils'; +import BigNumber from 'bignumber.js'; import { + addDays, differenceInDays, - endOfDay, - endOfTomorrow, fromUnixTime, - getDayOfYear, getUnixTime, - getYear, - parse, + isSameDay, startOfDay, subDays, } from 'date-fns'; -import throttle from 'lodash.throttle'; -import React from 'react'; -import { AnySubscan } from 'types'; -import { planckBnToUnit } from 'Utils'; - -export const getSize = (element: any) => { - const width = element?.offsetWidth; - const height = element?.offsetHeight; - return { height, width }; -}; - -export const useSize = (element: any) => { - const { containerRefs } = useUi(); - - const [size, setSize] = React.useState(getSize(element)); - - const throttleCallback = () => { - setSize(getSize(element)); - }; - - React.useEffect(() => { - const resizeThrottle = throttle(throttleCallback, 100, { - trailing: true, - leading: false, - }); - - // listen to main interface resize if ref is available, otherwise - // fall back to window resize. - const listenFor = containerRefs?.mainInterface?.current ?? window; - listenFor.addEventListener('resize', resizeThrottle); - return () => { - listenFor.removeEventListener('resize', resizeThrottle); - }; - }); - return size; -}; - -interface FormatSizeIf { - width: string | number; - height: number; -} +import { MaxPayoutDays } from 'consts'; +import type { AnyApi, AnyJson, AnySubscan } from 'types'; +import type { PayoutDayCursor } from './types'; -export const formatSize = ( - { width, height }: FormatSizeIf, - minHeight: number -) => ({ - width: width || '100%', - height: height || minHeight, - minHeight, -}); +// Take non-zero rewards in most-recent order. +export const sortNonZeroPayouts = ( + payouts: AnySubscan, + poolClaims: AnySubscan, + capMaxDays: boolean +) => { + const list = [ + ...payouts.concat(poolClaims).filter((p: AnySubscan) => p.amount > 0), + ].sort( + (a: AnySubscan, b: AnySubscan) => b.block_timestamp - a.block_timestamp + ); -interface GetGradientIf { - right: number; - left: number; - top: number; - bottom: number; -} + // this function always calculates from the current date (not fromDate). + const fromTimestamp = getUnixTime(subDays(new Date(), MaxPayoutDays)); -export const getGradient = ( - ctx: any, - { right, left, top, bottom }: GetGradientIf -) => { - let width; - let height; - let gradient; - - const chartWidth = right - left; - const chartHeight = bottom - top; - - if (!gradient || width !== chartWidth || height !== chartHeight) { - // Create the gradient because this is either the first render - // or the size of the chart has changed - width = chartWidth; - height = chartHeight; - gradient = ctx.createLinearGradient(0, bottom, 0, top); - gradient.addColorStop(0, 'rgba(203, 37, 111, 0.9)'); - gradient.addColorStop(1, 'rgba(223, 81, 144, 0.7)'); + if (capMaxDays) { + return list.filter((l: AnySubscan) => l.block_timestamp >= fromTimestamp); } - return gradient; + + return list; }; -// given payouts, calculate daily income and fill missing days with zero amounts. -export const calculatePayoutsByDay = ( - payouts: any, +// Given payouts, calculate daily income and fill missing days with zero amounts. +export const calculateDailyPayouts = ( + payouts: AnySubscan, + fromDate: Date, maxDays: number, - average: number, - units: number + units: number, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + subject: string ) => { - if (!payouts.length) return payouts; + let dailyPayouts: AnySubscan = []; - // if we are taking an average, we will need extra days of data. - if (average > 1) maxDays += average; + // remove days that are beyond end day limit + payouts = payouts.filter( + (p: AnySubscan) => + daysPassed(fromUnixTime(p.block_timestamp), fromDate) <= maxDays + ); + + // return now if no payouts. + if (!payouts.length) return payouts; - const payoutsByDay: any = []; - let curDay = 366; - let curYear = 3000; - let curPayout = { - amount: new BN(0), + // post-fill any missing days. [current day -> last payout] + dailyPayouts = postFillMissingDays(payouts, fromDate, maxDays); + + // start iterating payouts, most recent first. + // + // payouts passed. + let p = 0; + // current day cursor. + let curDay: Date = fromDate; + // current payout cursor. + let curPayout: PayoutDayCursor = { + amount: new BigNumber(0), event_id: '', }; + for (const payout of payouts) { + p++; - // determine inactive days since last payout - const lastTs = fromUnixTime(payouts[0].block_timestamp); + // extract day from current payout. + const thisDay = startOfDay(fromUnixTime(payout.block_timestamp)); - // test from start of day as to not duplicate today's payout entry - let daysSinceLast = differenceInDays(startOfDay(new Date()), lastTs); - - // add inactive days - if (daysSinceLast > 0) { - daysSinceLast = daysSinceLast > maxDays ? maxDays : daysSinceLast; - - let timestamp = endOfTomorrow(); + // initialise current day if first payout. + if (p === 1) { + curDay = thisDay; + } - for (let i = 1; i <= daysSinceLast; i++) { - timestamp = subDays(timestamp, 1); - payoutsByDay.push({ - amount: 0, - event_id: 'Reward', - block_timestamp: getUnixTime(timestamp), + // handle surpassed maximum days. + if (daysPassed(thisDay, fromDate) >= maxDays) { + dailyPayouts.push({ + amount: planckToUnit(curPayout.amount, units), + event_id: getEventId(curPayout), + block_timestamp: getUnixTime(curDay), }); + break; } - } - if (payouts.length > 0) { - let p = 0; // payouts passed - let i = 0; // days passed + // get day difference between cursor and currentpayout. + const daysDiff = daysPassed(thisDay, curDay); - for (const payout of payouts) { - // break if graph limit reached - if (payoutsByDay.length >= maxDays) { - break; - } - // increment payout - p++; - - // extract day and year from payout timestamp - const date = fromUnixTime(payout.block_timestamp); - const _day = getDayOfYear(date); - const _year = getYear(date); - - // starting a new day - if (_day < curDay || _year < curYear) { - // check current day with previous, determine missing days - const prevTs = getUnixTime( - parse(`${pad(curDay, 3)}/${curYear}`, 'DDD/yyyy', new Date()) - ); - const thisTs = getUnixTime( - parse(`${pad(_day, 3)}/${_year}`, 'DDD/yyyy', new Date()) - ); - const gapDays = differenceInDays( - fromUnixTime(prevTs), - fromUnixTime(thisTs) - ); - - // increment by `gap `days - if (i !== 0) { - i += gapDays; - } else { - // increment 1st day - i++; - } - - // get timestamp of end of day - const dayTs = getUnixTime( - endOfDay( - parse(`${pad(curDay, 3)}/${curYear}`, 'DDD/yyyy', new Date()) - ) - ); - - // commit previous day payout - if (i > 1) { - payoutsByDay.push({ - amount: planckBnToUnit(curPayout.amount, units), - event_id: curPayout.amount.lt(new BN(0)) ? 'Slash' : 'Reward', - block_timestamp: dayTs, - }); - } - - // commit gap day payouts - if (i !== 0) { - // fill missing days - let gapDayTs = fromUnixTime(prevTs); - if (gapDays > 1) { - for (let j = 1; j < gapDays; j++) { - gapDayTs = subDays(gapDayTs, 1); - payoutsByDay.push({ - amount: 0, - event_id: 'Reward', - block_timestamp: getUnixTime(gapDayTs), - }); - } - } - } + // handle new day. + if (daysDiff > 0) { + // add current payout cursor to dailyPayouts. + dailyPayouts.push({ + amount: planckToUnit(curPayout.amount, units), + event_id: getEventId(curPayout), + block_timestamp: getUnixTime(curDay), + }); - // overwrite curPayout for this day - const amount = new BN(payout.amount); - curPayout = { - amount, - event_id: amount.lt(new BN(0)) ? 'Slash' : 'Reward', - }; - - // update day and year cursors - if (_day < curDay) curDay = _day; - if (_year < curYear) curYear = _year; - } else { - // same day: add to curPayout - curPayout.amount = curPayout.amount.add(new BN(payout.amount)); - } + // update day cursor to the new day. + curDay = thisDay; + // reset current payout cursor for the new day. + curPayout = { + amount: new BigNumber(payout.amount), + event_id: new BigNumber(payout.amount).isLessThan(0) + ? 'Slash' + : 'Reward', + }; + } else { + // in same day. Aadd payout amount to current payout cursor. + curPayout.amount = curPayout.amount.plus(payout.amount); + } - // commit last payout - if (p === payouts.length) - payoutsByDay.push({ - amount: planckBnToUnit(curPayout.amount, units), - event_id: curPayout.amount.lt(new BN(0)) ? 'Slash' : 'Reward', - block_timestamp: payout.block_timestamp, - }); + // if only 1 payout exists, exit early here. + if (payouts.length === 1) { + dailyPayouts.push({ + amount: planckToUnit(curPayout.amount, units), + event_id: getEventId(curPayout), + block_timestamp: getUnixTime(curDay), + }); + break; } } - // if we don't need to take an average, just return the `payoutsByDay`. - if (average <= 1) return payoutsByDay; + // return payout amounts as plain numbers. + return dailyPayouts.map((q: AnyApi) => ({ + ...q, + amount: Number(q.amount.toString()), + })); +}; + +// Calculate average payouts per day. +export const calculatePayoutAverages = ( + payouts: AnySubscan, + fromDate: Date, + days: number, + avgDays: number +) => { + // if we don't need to take an average, just return `payouts`. + if (avgDays <= 1) return payouts; + + // create moving average value over `avgDays` past days, if any. + let payoutsAverages = []; + for (let i = 0; i < payouts.length; i++) { + // average period end. + const end = Math.max(0, i - avgDays); - // create moving average value over `average` days - const averagePayoutsByDay = []; - for (let i = 0; i < payoutsByDay.length; i++) { + // the total amount earned in period. let total = 0; + // period length to be determined. let num = 0; - for (let j = 0; j < average; j++) { - if (payoutsByDay[i + j]) { - total += payoutsByDay[i + j].amount; + + for (let j = i; j >= end; j--) { + if (payouts[j]) { + total += payouts[j].amount; } - // increase by one anyway to treat non-existent as zero value + // increase by one to treat non-existent as zero value num += 1; } - averagePayoutsByDay.push({ + if (total === 0) { + total = payouts[i].amount; + } + + payoutsAverages.push({ amount: total / num, - event_id: payoutsByDay[i].event_id, - block_timestamp: payoutsByDay[i].block_timestamp, + block_timestamp: payouts[i].block_timestamp, }); } // return an array with the expected number of items - return averagePayoutsByDay.slice(0, maxDays - average); -}; - -// fill in the backlog of days up to `maxDays` -export const prefillToMaxDays = (payoutsByDay: any, maxDays: number) => { - if (payoutsByDay.length < maxDays) { - const remainingDays = maxDays - payoutsByDay.length; - - // get earliest timestamp - let timestamp; - if (!payoutsByDay.length) { - timestamp = endOfDay(new Date()); - } else { - timestamp = fromUnixTime( - payoutsByDay[payoutsByDay.length - 1].block_timestamp - ); - } + payoutsAverages = payoutsAverages.filter( + (p: AnySubscan) => + daysPassed(fromUnixTime(p.block_timestamp), fromDate) <= days + ); - // fill in remaining days - for (let i = 0; i < remainingDays; i++) { - timestamp = subDays(timestamp, 1); - payoutsByDay.push({ - amount: 0, - event_id: 'Reward', - block_timestamp: getUnixTime(timestamp), - }); - } - } - return payoutsByDay; + return payoutsAverages; }; -// format rewards and return last payment +// Fetch rewards and graph meta data. +// +// Format provided payouts and returns the last payment. export const formatRewardsForGraphs = ( + fromDate: Date, days: number, - average: number, units: number, payouts: AnySubscan, - poolClaims: AnySubscan + poolClaims: AnySubscan, + unclaimedPayouts: AnySubscan ) => { - const payoutsByDay = prefillToMaxDays( - calculatePayoutsByDay(payouts, days, average, units), - days + // process nominator payouts. + const allPayouts = processPayouts(payouts, fromDate, days, units, 'nominate'); + + // process unclaimed nominator payouts. + const allUnclaimedPayouts = processPayouts( + unclaimedPayouts, + fromDate, + days, + units, + 'nominate' ); - const poolClaimsByDay = prefillToMaxDays( - calculatePayoutsByDay(poolClaims, days, average, units), - days - ); - - // get most recent payout - const payoutExists = - payouts.find((p: AnySubscan) => new BN(p.amount).gt(new BN(0))) ?? null; - const poolClaimExists = - poolClaims.find((p: AnySubscan) => new BN(p.amount).gt(new BN(0))) ?? null; - // calculate which payout was most recent - let lastReward = null; - if (!payoutExists || !poolClaimExists) { - if (payoutExists) { - lastReward = payoutExists; - } - if (poolClaimExists) { - lastReward = poolClaimExists; - } - } else { - // both `payoutExists` and `poolClaimExists` are present - lastReward = - payoutExists.block_timestamp > poolClaimExists.block_timestamp - ? payoutExists - : poolClaimExists; - } + // process pool claims. + const allPoolClaims = processPayouts( + poolClaims, + fromDate, + days, + units, + 'pools' + ); return { // reverse rewards: most recent last - payoutsByDay: payoutsByDay.reverse(), - poolClaimsByDay: poolClaimsByDay.reverse(), - lastReward, + allPayouts, + allUnclaimedPayouts, + allPoolClaims, + lastReward: getLatestReward(payouts, poolClaims), }; }; -/* combineRewardsByDay - * combines payouts and pool claims into daily records. - * removes the `event_id` field from records. - */ -export const combineRewardsByDay = ( - payoutsByDay: AnySubscan, - poolClaimsByDay: AnySubscan +// Process payouts. +// +// calls the relevant functions on raw payouts to format them correctly. +const processPayouts = ( + payouts: AnySubscan, + fromDate: Date, + days: number, + units: number, + subject: string +) => { + // normalise payout timestamps. + const normalised = normalisePayouts(payouts); + // calculate payouts per day from the current day. + let p = calculateDailyPayouts(normalised, fromDate, days, units, subject); + // pre-fill payouts if max days have not been reached. + p = p.concat(prefillMissingDays(p, fromDate, days)); + // fill in gap days between payouts with zero values. + p = fillGapDays(p, fromDate); + // reverse payouts: most recent last. + p = p.reverse(); + + // use normalised payouts for calculating the 10-day average prior to the start of the payout graph. + const avgDays = 10; + const preNormalised = getPreMaxDaysPayouts( + normalised, + fromDate, + days, + avgDays + ); + // start of average calculation should be the earliest date. + const averageFromDate = subDays(fromDate, MaxPayoutDays); + + let a = calculateDailyPayouts( + preNormalised, + averageFromDate, + avgDays, + units, + subject + ); + // prefill payouts if we are missing the earlier dates. + a = a.concat(prefillMissingDays(a, averageFromDate, avgDays)); + // fill in gap days between payouts with zero values. + a = fillGapDays(a, averageFromDate); + // reverse payouts: most recent last. + a = a.reverse(); + + return { p, a }; +}; + +// Get payout average in `avgDays` day period after to `days` threshold +// +// These payouts are used for calculating the `avgDays`-day average prior to the start of the payout +// graph. +const getPreMaxDaysPayouts = ( + payouts: AnySubscan, + fromDate: Date, + days: number, + avgDays: number ) => { + // remove payouts that are not within `avgDays` `days` pre-graph window. + return payouts.filter( + (p: AnySubscan) => + daysPassed(fromUnixTime(p.block_timestamp), fromDate) > days && + daysPassed(fromUnixTime(p.block_timestamp), fromDate) <= days + avgDays + ); +}; + +// Combine payouts and pool claims. +// +// combines payouts and pool claims into daily records. Removes the `event_id` field from records. +export const combineRewards = (payouts: AnySubscan, poolClaims: AnySubscan) => { // we first check if actual payouts exist, e.g. there are non-zero payout // amounts present in either payouts or pool claims. const poolClaimExists = - poolClaimsByDay.find((p: AnySubscan) => p.amount > 0) || null; - const payoutExists = - payoutsByDay.find((p: AnySubscan) => p.amount > 0) || null; + poolClaims.find((p: AnySubscan) => p.amount > 0) || null; + const payoutExists = payouts.find((p: AnySubscan) => p.amount > 0) || null; // if no pool claims exist but payouts do, return payouts w.o. event_id // also do this if there are no payouts period. @@ -358,91 +309,220 @@ export const combineRewardsByDay = ( (!poolClaimExists && payoutExists) || (!payoutExists && !poolClaimExists) ) { - return payoutsByDay.map((p: AnySubscan) => { - return { - amount: p.amount, - block_timestamp: p.block_timestamp, - }; - }); + return payouts.map((p: AnySubscan) => ({ + amount: p.amount, + block_timestamp: p.block_timestamp, + })); } // if no payouts exist but pool claims do, return pool claims w.o. event_id if (!payoutExists && poolClaimExists) { - return poolClaimsByDay.map((p: AnySubscan) => { - return { - amount: p.amount, - block_timestamp: p.block_timestamp, - }; - }); + return poolClaims.map((p: AnySubscan) => ({ + amount: p.amount, + block_timestamp: p.block_timestamp, + })); } - // We now know pool claims *and* payouts exist. We can begin to combine them - // into one unified `rewards` array. - let rewards: AnySubscan = []; + // We now know pool claims *and* payouts exist. + // + // Now determine which dates to display. + let payoutDays: AnyJson[] = []; + // prefill `dates` with all pool claim and payout days + poolClaims.forEach((p: AnySubscan) => { + const dayStart = getUnixTime(startOfDay(fromUnixTime(p.block_timestamp))); + if (!payoutDays.includes(dayStart)) { + payoutDays.push(dayStart); + } + }); + payouts.forEach((p: AnySubscan) => { + const dayStart = getUnixTime(startOfDay(fromUnixTime(p.block_timestamp))); + if (!payoutDays.includes(dayStart)) { + payoutDays.push(dayStart); + } + }); + + // sort payoutDays by `block_timestamp`; + payoutDays = payoutDays.sort((a: AnySubscan, b: AnySubscan) => a - b); + + // Iterate payout days. + // + // Combine payouts into one unified `rewards` array. + const rewards: AnySubscan = []; // loop pool claims and consume / combine payouts - poolClaimsByDay.forEach((p: AnySubscan) => { - let { amount } = p; + payoutDays.forEach((d: AnySubscan) => { + let amount = 0; // check payouts exist on this day - const payoutsThisDay = payoutsByDay.filter((q: AnySubscan) => { - return unixSameDay(q.block_timestamp, p.block_timestamp); - }); - + const payoutsThisDay = payouts.filter((p: AnySubscan) => + isSameDay(fromUnixTime(p.block_timestamp), fromUnixTime(d)) + ); + // check pool claims exist on this day + const poolClaimsThisDay = poolClaims.filter((p: AnySubscan) => + isSameDay(fromUnixTime(p.block_timestamp), fromUnixTime(d)) + ); // add amounts - if (payoutsThisDay.length) { + if (payoutsThisDay.concat(poolClaimsThisDay).length) { for (const payout of payoutsThisDay) { amount += payout.amount; } } - // consume used payouts - payoutsByDay = payoutsByDay.filter((q: AnySubscan) => { - return !unixSameDay(q.block_timestamp, p.block_timestamp); - }); rewards.push({ amount, - block_timestamp: p.block_timestamp, + block_timestamp: d, }); }); + return rewards; +}; - // add remaining payouts - if (payoutsByDay.length) { - rewards = rewards.concat( - payoutsByDay.forEach((p: AnySubscan) => { - return { - amount: p.amount, - block_timestamp: p.block_timestamp, - }; - }) - ); +// Get latest reward. +// +// Gets the latest reward from pool claims and nominator payouts. +export const getLatestReward = ( + payouts: AnySubscan, + poolClaims: AnySubscan +) => { + // get most recent payout + const payoutExists = + payouts.find((p: AnySubscan) => greaterThanZero(new BigNumber(p.amount))) ?? + null; + const poolClaimExists = + poolClaims.find((p: AnySubscan) => + greaterThanZero(new BigNumber(p.amount)) + ) ?? null; + + // calculate which payout was most recent + let lastReward = null; + if (!payoutExists || !poolClaimExists) { + if (payoutExists) { + lastReward = payoutExists; + } + if (poolClaimExists) { + lastReward = poolClaimExists; + } + } else { + // both `payoutExists` and `poolClaimExists` are present + lastReward = + payoutExists.block_timestamp > poolClaimExists.block_timestamp + ? payoutExists + : poolClaimExists; } + return lastReward; +}; - // re-order combined rewards based on block timestamp, oldest first - rewards = rewards.sort((a: AnySubscan, b: AnySubscan) => { - const x = new BN(a.block_timestamp); - const y = new BN(b.block_timestamp); - return y.add(x); - }); +// Fill in the days from the earliest payout day to `maxDays`. +// +// Takes the last (earliest) payout and fills the missing days from that payout day to `maxDays`. +export const prefillMissingDays = ( + payouts: AnySubscan, + fromDate: Date, + maxDays: number +) => { + const newPayouts = []; + const payoutStartDay = subDays(startOfDay(fromDate), maxDays); + const payoutEndDay = !payouts.length + ? startOfDay(fromDate) + : startOfDay(fromUnixTime(payouts[payouts.length - 1].block_timestamp)); - return rewards; -}; + const daysToPreFill = daysPassed(payoutStartDay, payoutEndDay); -// calculate whether 2 unix timestamps are on the same day -export const unixSameDay = (p: number, q: number) => { - const dateQ = getUnixTime(q); - const _dayQ = getDayOfYear(dateQ); - const _yearQ = getYear(dateQ); + if (daysToPreFill > 0) { + for (let i = 1; i < daysToPreFill; i++) { + newPayouts.push({ + amount: 0, + event_id: 'Reward', + block_timestamp: getUnixTime(subDays(payoutEndDay, i)), + }); + } + } + return newPayouts; +}; - const dateP = getUnixTime(p); - const _dayP = getDayOfYear(dateP); - const _yearP = getYear(dateP); +// Fill in the days from the current day to the last payout. +// +// Takes the first payout (most recent) and fills the missing days from current day. +export const postFillMissingDays = ( + payouts: AnySubscan, + fromDate: Date, + maxDays: number +) => { + const newPayouts = []; + const payoutsEndDay = startOfDay(fromUnixTime(payouts[0].block_timestamp)); + const daysSinceLast = Math.min( + daysPassed(payoutsEndDay, startOfDay(fromDate)), + maxDays + ); - return _dayQ === _dayP && _yearQ === _yearP; + for (let i = daysSinceLast; i > 0; i--) { + newPayouts.push({ + amount: 0, + event_id: 'Reward', + block_timestamp: getUnixTime(addDays(payoutsEndDay, i)), + }); + } + return newPayouts; }; -// ensures leading zeroes of numbers -export const pad = (num: number, size: number) => { - let numStr = num.toString(); - while (numStr.length < size) numStr = `0${numStr}`; - return numStr; +// Fill gap days within payouts with zero amounts. +export const fillGapDays = (payouts: AnySubscan, fromDate: Date) => { + const finalPayouts: AnySubscan = []; + + // current day cursor. + let curDay = fromDate; + + for (const p of payouts) { + const thisDay = fromUnixTime(p.block_timestamp); + const gapDays = Math.max(0, daysPassed(thisDay, curDay) - 1); + + if (gapDays > 0) { + // add any gap days. + if (gapDays > 0) { + for (let j = 1; j <= gapDays; j++) { + finalPayouts.push({ + amount: 0, + event_id: 'Reward', + block_timestamp: getUnixTime(subDays(curDay, j)), + }); + } + } + } + + // add the current day. + finalPayouts.push(p); + + // day cursor is now the new day. + curDay = thisDay; + } + return finalPayouts; }; + +// Utiltiy: normalise payout timestamps to start of day. +export const normalisePayouts = (payouts: AnySubscan) => + payouts.map((p: AnySubscan) => ({ + ...p, + block_timestamp: getUnixTime(startOfDay(fromUnixTime(p.block_timestamp))), + })); + +// Utility: days passed since 2 dates. +export const daysPassed = (from: Date, to: Date) => + differenceInDays(startOfDay(to), startOfDay(from)); + +// Utility: extract whether an event id should be a slash or reward, based on the net day amount. +const getEventId = (c: PayoutDayCursor) => + c.amount.isLessThan(0) ? 'Slash' : 'Reward'; + +// Utility: Formats a width and height pair. +export const formatSize = ( + { + width, + height, + }: { + width: string | number; + height: number; + }, + minHeight: number +) => ({ + width: width || '100%', + height: height || minHeight, + minHeight, +}); diff --git a/src/library/Graphs/Wrapper.ts b/src/library/Graphs/Wrapper.ts new file mode 100644 index 0000000000..29ae05fea6 --- /dev/null +++ b/src/library/Graphs/Wrapper.ts @@ -0,0 +1,11 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import styled from 'styled-components'; + +export const GraphWrapper = styled.div` + position: relative; + flex: 0; + padding: 1rem 2rem 1rem 0; + width: 100%; +`; diff --git a/src/library/Graphs/Wrappers.ts b/src/library/Graphs/Wrappers.ts deleted file mode 100644 index 5f90ae3efc..0000000000 --- a/src/library/Graphs/Wrappers.ts +++ /dev/null @@ -1,284 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { SideMenuStickyThreshold } from 'consts'; -import styled from 'styled-components'; -import { - backgroundSecondary, - borderPrimary, - cardBorder, - cardShadow, - networkColor, - shadowColor, - textPrimary, - textSecondary, -} from 'theme'; -import { - CardHeaderWrapperProps, - CardWrapperProps, - GraphWrapperProps, -} from './types'; - -/* CardHeaderWrapper - * - * Used as headers for individual cards. Usually a h4 accompanied - * with a h2. withAction allows a full-width header with a right-side - * button. - */ -export const CardHeaderWrapper = styled.div<CardHeaderWrapperProps>` - display: flex; - flex-flow: ${(props) => (props.withAction ? 'row' : 'column')} wrap; - width: 100%; - padding: ${(props) => (props.padded ? '0.5rem 1.2rem' : '0.25rem')}; - - h2, - h3 { - color: ${textPrimary}; - display: flex; - flex-flow: row wrap; - align-items: center; - flex-grow: ${(props) => (props.withAction ? 1 : 0)}; - - .help-icon { - margin-left: 0.6rem; - } - } - h4 { - margin: 0 0 0.6rem 0; - display: flex; - flex-flow: row wrap; - align-items: center; - justify-content: flex-start; - flex-grow: ${(props) => (props.withAction ? 1 : 0)}; - - .help-icon { - margin-left: 0.5rem; - } - } - - > div { - display: flex; - flex-flow: row nowrap; - align-items: center; - } -`; - -/* CardWrapper - * - * Used to separate the main modules throughout the app. - */ -export const CardWrapper = styled.div<CardWrapperProps>` - border: ${cardBorder} ${borderPrimary}; - box-shadow: ${cardShadow} ${shadowColor}; - padding: ${(props) => - props.noPadding ? '0rem' : props.transparent ? '0rem 0rem' : '1.2rem'}; - border-radius: 1.1rem; - background: ${(props) => (props.transparent ? 'none' : backgroundSecondary)}; - display: flex; - flex-flow: column nowrap; - align-content: flex-start; - align-items: flex-start; - flex: 1; - width: 100%; - margin-top: ${(props) => (props.transparent ? '0rem' : '1.4rem')}; - position: relative; - ${(props) => - props.transparent && - ` - border: none; - box-shadow: none; - background: none; - `} - - @media (max-width: ${SideMenuStickyThreshold}px) { - padding: ${(props) => - props.noPadding - ? '0rem' - : props.transparent - ? '0rem 0rem' - : '1rem 0.75rem'}; - } - - @media (min-width: ${SideMenuStickyThreshold + 1}px) { - height: ${(props) => (props.height ? `${props.height}px` : 'inherit')}; - } - - .content { - padding: 0 0.5rem; - - h3 { - margin-bottom: 0.75rem; - } - h4 { - margin-top: 0; - margin-bottom: 0; - } - } - - .inner { - padding: 1rem; - display: flex; - flex-flow: column nowrap; - align-content: flex-start; - align-items: flex-start; - width: 100%; - position: relative; - } - - .option { - border-bottom: 1px solid #ddd; - padding: 0.75rem 1rem; - font-size: 1rem; - text-align: left; - } -`; - -/* GraphWrapper - * - * Acts as a module, but used to wrap graphs. - */ - -export const GraphWrapper = styled.div<GraphWrapperProps>` - border: ${cardBorder} ${borderPrimary}; - box-shadow: ${cardShadow} ${shadowColor}; - border-radius: 1rem; - background: ${backgroundSecondary}; - display: flex; - flex-flow: column nowrap; - align-content: flex-start; - align-items: flex-start; - flex: 1; - position: relative; - overflow: hidden; - margin-top: ${(props) => (props.noMargin ? 0 : '1.4rem')}; - ${(props) => - props.transparent && - ` - border: none; - box-shadow: none; - background: none; - `} - - .inner { - width: 100%; - height: 100%; - } - - .label { - position: absolute; - right: 10px; - top: 10px; - font-size: 0.8rem; - background: ${networkColor}; - border-radius: 0.3rem; - padding: 0.2rem 0.4rem; - color: #fff; - opacity: 0.8; - } - .head { - padding: 0.5rem 1.2rem; - } - - h2 { - .amount { - text-overflow: ellipsis; - white-space: nowrap; - overflow: hidden; - } - } - - h2, - h4 { - margin: 0; - padding: 0.25rem 0 0.5rem 0; - display: flex; - flex-flow: row wrap; - align-content: flex-end; - align-items: flex-end; - justify-content: flex-start; - - .fiat { - color: ${textSecondary}; - font-size: 1.1rem; - margin-top: 0.2rem; - margin-left: 0.3rem; - } - } - h2 { - display: flex; - flex-flow: row wrap; - justify-content: flex-start; - align-items: center; - } - p { - margin: 0.25rem 0 0; - } - h4 { - align-items: center; - margin-top: 0.4rem; - - .help-icon { - margin-left: 0.55rem; - } - } - - h5 { - &.secondary { - color: ${textSecondary}; - opacity: 0.7; - margin-bottom: 0; - margin-top: 1.5rem; - } - } - .small_button { - background: #f1f1f1; - padding: 0.25rem 0.75rem; - border-radius: 1rem; - } - .graph { - position: relative; - flex: ${(props) => (props.flex ? 1 : 0)}; - flex-flow: row wrap; - justify-content: center; - width: 100%; - padding: 1rem 1.5rem; - } - .graph_line { - margin-top: 0.6rem; - padding: 0rem 1rem 0.5rem 0rem; - } - .graph_with_extra { - width: 100%; - display: flex; - flex-flow: row nowrap; - justify-content: flex-start; - align-items: flex-start; - height: 190px; - flex: 1; - - .extra { - flex: 1; - display: flex; - flex-flow: row wrap; - justify-content: flex-end; - align-items: flex-end; - align-content: flex-end; - height: 190px; - border: 1px solid; - } - } - - .change { - margin-left: 0.6rem; - font-size: 0.9rem; - color: white; - border-radius: 0.75rem; - padding: 0.15rem 0.5rem; - &.pos { - background: #3eb955; - } - &.neg { - background: #d2545d; - } - } -`; diff --git a/src/library/Graphs/types.ts b/src/library/Graphs/types.ts index afaad9fb0b..5b05943115 100644 --- a/src/library/Graphs/types.ts +++ b/src/library/Graphs/types.ts @@ -1,14 +1,15 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import { AnySubscan } from 'types'; +import type BigNumber from 'bignumber.js'; +import type { AnyPolkawatch, AnySubscan } from 'types'; export interface BondedProps { - active: number; - unlocking: number; - unlocked: number; + active: BigNumber; + free: BigNumber; + unlocking: BigNumber; + unlocked: BigNumber; inactive: boolean; - free: number; } export interface EraPointsProps { @@ -28,25 +29,23 @@ export interface PayoutLineProps { background?: string; } -export interface StatPieProps { - value: number; - value2: number; -} - export interface CardHeaderWrapperProps { - withAction?: boolean; - padded?: boolean; + $withAction?: boolean; + $withMargin?: boolean; } export interface CardWrapperProps { - noPadding?: boolean; - transparent?: boolean; height?: string | number; - flex?: boolean; } -export interface GraphWrapperProps { - transparent?: boolean; - noMargin?: boolean; - flex?: boolean; +export interface PayoutDayCursor { + amount: BigNumber; + event_id: string; +} + +export interface GeoDonutProps { + title: string; + series: AnyPolkawatch; + width?: string | number; + height?: string | number; } diff --git a/src/library/Headers/Connect.tsx b/src/library/Headers/Connect.tsx index 12b443213c..5a2100ef49 100644 --- a/src/library/Headers/Connect.tsx +++ b/src/library/Headers/Connect.tsx @@ -1,35 +1,54 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import { faWallet } from '@fortawesome/free-solid-svg-icons'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { useConnect } from 'contexts/Connect'; -import { useModal } from 'contexts/Modal'; -import { HeadingWrapper, Item } from './Wrappers'; +import { faPlug, faWallet } from '@fortawesome/free-solid-svg-icons'; +import { ButtonText } from '@polkadot-cloud/react'; +import { useTranslation } from 'react-i18next'; +import { useOverlay } from '@polkadot-cloud/react/hooks'; +import { useImportedAccounts } from 'contexts/Connect/ImportedAccounts'; +import { ConnectedAccount, HeadingWrapper } from './Wrappers'; export const Connect = () => { - const { openModalWith } = useModal(); - const { activeAccount, accounts } = useConnect(); + const { t } = useTranslation('library'); + const { openModal } = useOverlay().modal; + const { accounts } = useImportedAccounts(); + return ( <HeadingWrapper> - <Item - className="connect" - onClick={() => { - openModalWith( - 'ConnectAccounts', - { section: accounts.length ? 1 : 0 }, - 'large' - ); - }} - whileHover={{ scale: 1.02 }} - > - <FontAwesomeIcon - icon={faWallet} - className="icon" - transform="shrink-2" - /> - <span>{activeAccount ? 'Accounts' : 'Connect'}</span> - </Item> + <ConnectedAccount> + {accounts.length ? ( + <> + <ButtonText + text={t('accounts')} + iconLeft={faWallet} + onClick={() => { + openModal({ key: 'Accounts' }); + }} + style={{ color: 'white', fontSize: '1.05rem' }} + /> + <span /> + <ButtonText + text="" + iconRight={faPlug} + iconTransform="grow-1" + onClick={() => { + openModal({ key: 'Connect' }); + }} + style={{ color: 'white', fontSize: '1.05rem' }} + /> + </> + ) : ( + <ButtonText + text={t('connect')} + iconRight={faPlug} + iconTransform="grow-1" + onClick={() => { + openModal({ key: accounts.length ? 'Accounts' : 'Connect' }); + }} + style={{ color: 'white', fontSize: '1.05rem' }} + /> + )} + </ConnectedAccount> </HeadingWrapper> ); }; diff --git a/src/library/Headers/Connected.tsx b/src/library/Headers/Connected.tsx index 24f30c3a9b..241e77a7d0 100644 --- a/src/library/Headers/Connected.tsx +++ b/src/library/Headers/Connected.tsx @@ -1,23 +1,23 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import { useBalances } from 'contexts/Balances'; -import { useConnect } from 'contexts/Connect'; +import { useTranslation } from 'react-i18next'; import { useActivePools } from 'contexts/Pools/ActivePools'; import { useStaking } from 'contexts/Staking'; import { useUi } from 'contexts/UI'; -import { PoolAccount } from 'library/PoolAccount'; -import { clipAddress } from 'Utils'; -import { Account } from '../Account'; +import { useActiveAccounts } from 'contexts/ActiveAccounts'; +import { useImportedAccounts } from 'contexts/Connect/ImportedAccounts'; +import { Account } from '../Account/Default'; +import { Account as PoolAccount } from '../Account/Pool'; import { HeadingWrapper } from './Wrappers'; export const Connected = () => { - const { activeAccount, accountHasSigner } = useConnect(); - const { hasController, getControllerNotImported } = useStaking(); - const { getBondedAccount } = useBalances(); - const controller = getBondedAccount(activeAccount); + const { t } = useTranslation('library'); + const { isNetworkSyncing } = useUi(); + const { isNominating } = useStaking(); const { selectedActivePool } = useActivePools(); - const { networkSyncing } = useUi(); + const { accountHasSigner } = useImportedAccounts(); + const { activeAccount, activeProxy } = useActiveAccounts(); let poolAddress = ''; if (selectedActivePool) { @@ -25,15 +25,9 @@ export const Connected = () => { poolAddress = addresses.stash; } - const activeAccountLabel = networkSyncing - ? undefined - : hasController() - ? 'Stash' - : undefined; - return ( <> - {activeAccount ? ( + {activeAccount && ( <> {/* default account display / stash label if actively nominating */} <HeadingWrapper> @@ -41,48 +35,45 @@ export const Connected = () => { canClick={false} value={activeAccount} readOnly={!accountHasSigner(activeAccount)} - label={activeAccountLabel} + label={ + isNetworkSyncing + ? undefined + : isNominating() + ? 'Nominator' + : undefined + } format="name" - filled /> </HeadingWrapper> - {/* controller account display / hide if no controller present */} - {hasController() && !networkSyncing && ( + {/* pool account display / hide if not in pool */} + {selectedActivePool !== null && !isNetworkSyncing && ( <HeadingWrapper> - <Account - value={controller ?? ''} - readOnly={!accountHasSigner(controller)} - title={ - getControllerNotImported(controller) - ? controller - ? clipAddress(controller) - : 'Not Imported' - : undefined - } + <PoolAccount format="name" - label="Controller" + value={poolAddress} + pool={selectedActivePool} + label={t('pool')} canClick={false} - filled + onClick={() => {}} /> </HeadingWrapper> )} - {/* pool account display / hide if not in pool */} - {selectedActivePool !== null && !networkSyncing && ( + {/* proxy account display / hide if no proxy */} + {activeProxy && ( <HeadingWrapper> - <PoolAccount - value={poolAddress} - pool={selectedActivePool} - label="Pool" + <Account canClick={false} - onClick={() => {}} - filled + value={activeProxy} + readOnly={!accountHasSigner(activeProxy)} + label={t('proxy')} + format="name" /> </HeadingWrapper> )} </> - ) : null} + )} </> ); }; diff --git a/src/library/Headers/Dropdown.tsx b/src/library/Headers/Dropdown.tsx index d36cb56816..ce38888b83 100644 --- a/src/library/Headers/Dropdown.tsx +++ b/src/library/Headers/Dropdown.tsx @@ -3,7 +3,7 @@ import { useOutsideAlerter } from 'library/Hooks'; import { useRef } from 'react'; -import { DropdownProps } from './types'; +import type { DropdownProps } from './types'; export const Dropdown = ({ toggleMenu, items }: DropdownProps) => { const ref = useRef(null); diff --git a/src/library/Headers/SideMenuToggle.tsx b/src/library/Headers/SideMenuToggle.tsx index 7db22c8a8a..8a0d111466 100644 --- a/src/library/Headers/SideMenuToggle.tsx +++ b/src/library/Headers/SideMenuToggle.tsx @@ -1,15 +1,12 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only import { faBars } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { useTheme } from 'contexts/Themes'; import { useUi } from 'contexts/UI'; -import { defaultThemes } from 'theme/default'; import { Item } from './Wrappers'; export const SideMenuToggle = () => { - const { mode } = useTheme(); const { setSideMenu, sideMenuOpen } = useUi(); return ( @@ -17,17 +14,11 @@ export const SideMenuToggle = () => { <Item style={{ width: '50px', flex: 0 }} onClick={() => { - setSideMenu(sideMenuOpen ? 0 : 1); + setSideMenu(!sideMenuOpen); }} > - <span className="toggle"> - <FontAwesomeIcon - icon={faBars} - style={{ - cursor: 'pointer', - color: defaultThemes.text.secondary[mode], - }} - /> + <span> + <FontAwesomeIcon className="icon" icon={faBars} /> </span> </Item> </div> diff --git a/src/library/Headers/Spinner.tsx b/src/library/Headers/Spinner.tsx index ff6b2934e9..6fa2969dfc 100644 --- a/src/library/Headers/Spinner.tsx +++ b/src/library/Headers/Spinner.tsx @@ -1,8 +1,7 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only import styled from 'styled-components'; -import { backgroundPrimary } from 'theme'; const StyledSpinner = styled.div` font-size: 10px; @@ -38,7 +37,7 @@ const StyledSpinner = styled.div` content: ''; } &:after { - background: ${backgroundPrimary}; + background: var(--background-primary); width: 75%; height: 75%; border-radius: 50%; @@ -72,8 +71,4 @@ const StyledSpinner = styled.div` } `; -export const Spinner = () => { - return <StyledSpinner />; -}; - -export default Spinner; +export const Spinner = () => <StyledSpinner />; diff --git a/src/library/Headers/Wrappers.ts b/src/library/Headers/Wrappers.ts index c2185456d0..516e437efa 100644 --- a/src/library/Headers/Wrappers.ts +++ b/src/library/Headers/Wrappers.ts @@ -1,19 +1,12 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only +import { motion } from 'framer-motion'; +import styled from 'styled-components'; import { ShowAccountsButtonWidthThreshold, SideMenuStickyThreshold, } from 'consts'; -import { motion } from 'framer-motion'; -import styled from 'styled-components'; -import { - borderPrimary, - buttonSecondaryBackground, - networkColor, - textPrimary, - textSecondary, -} from 'theme'; export const Wrapper = styled.div` position: fixed; @@ -25,7 +18,7 @@ export const Wrapper = styled.div` align-items: center; align-content: center; padding: 0 1.25rem; - transition: all 0.15s; + transition: all var(--transition-duration); margin: 0.5rem 0; height: 4rem; z-index: 6; @@ -37,16 +30,33 @@ export const Wrapper = styled.div` .menu { display: none; @media (max-width: ${SideMenuStickyThreshold}px) { - color: ${textSecondary}; + color: var(--text-color-secondary); display: flex; flex-flow: row wrap; - justify-content: flex-start; align-items: center; flex-grow: 1; } } `; +export const ConnectedAccount = styled(motion.div)` + background: var(--accent-color-primary); + border-radius: 1.5rem; + display: flex; + transition: transform var(--transition-duration); + padding: 0.1rem 0.75rem; + + &:hover { + transform: scale(1.015); + } + + > span { + border-right: 1px solid var(--text-color-invert); + opacity: 0.2; + margin: 0 0.4rem; + } +`; + export const HeadingWrapper = styled.div` display: flex; flex-flow: row wrap; @@ -54,23 +64,27 @@ export const HeadingWrapper = styled.div` margin-left: 0.9rem; `; -export const Item = styled(motion.button)` - background: ${buttonSecondaryBackground}; - border: 1px solid ${borderPrimary}; +export const Item = styled.button` + background: var(--button-tab-background); + border: 1px solid var(--border-primary-color); flex-grow: 1; padding: 0.05rem 1rem; border-radius: 1.5rem; box-shadow: none; display: flex; - flex-flow: row nowrap; justify-content: center; align-items: center; cursor: pointer; font-size: 1.05rem; + transition: transform var(--transition-duration) ease-out; + + &:hover { + transform: scale(1.03); + } .label { - color: ${networkColor}; - border: 0.125rem solid ${networkColor}; + color: var(--accent-color-primary); + border: 0.125rem solid var(--accent-color-primary); border-radius: 0.8rem; font-size: 0.85rem; margin-right: 0.6rem; @@ -80,12 +94,16 @@ export const Item = styled(motion.button)` > span { color: white; line-height: 2.2rem; + .icon { + color: var(--text-color-secondary); + cursor: pointer; + } } &.connect { - background: ${networkColor}; + background: var(--accent-color-primary); > span { - color: 'white'; + color: white; } .icon { margin-right: 0.6rem; @@ -97,10 +115,10 @@ export const Item = styled(motion.button)` `; export const ItemInactive = styled(motion.div)` + background: var(--button-secondary-background); flex-grow: 1; padding: 0 1rem; border-radius: 1rem; - background: ${buttonSecondaryBackground}; display: flex; flex-flow: column nowrap; justify-content: center; @@ -108,7 +126,7 @@ export const ItemInactive = styled(motion.div)` font-size: 1rem; > span { - color: ${textPrimary}; + color: var(--text-color-primary); line-height: 2.2rem; } `; diff --git a/src/library/Headers/index.tsx b/src/library/Headers/index.tsx index cc70b9d715..c4db3ff5ec 100644 --- a/src/library/Headers/index.tsx +++ b/src/library/Headers/index.tsx @@ -1,11 +1,15 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only +import { pageFromUri } from '@polkadot-cloud/utils'; +import { useLocation } from 'react-router-dom'; import { useExtrinsics } from 'contexts/Extrinsics'; +import { usePlugins } from 'contexts/Plugins'; +import { useBondedPools } from 'contexts/Pools/BondedPools'; +import { usePoolMembers } from 'contexts/Pools/PoolMembers'; import { useUi } from 'contexts/UI'; -import { useValidators } from 'contexts/Validators'; -import { useLocation } from 'react-router-dom'; -import { pageFromUri } from 'Utils'; +import { useValidators } from 'contexts/Validators/ValidatorEntries'; +import { usePayouts } from 'contexts/Payouts'; import { Connect } from './Connect'; import { Connected } from './Connected'; import { SideMenuToggle } from './SideMenuToggle'; @@ -13,19 +17,49 @@ import { Spinner } from './Spinner'; import { LargeScreensOnly, Wrapper } from './Wrappers'; export const Headers = () => { + const { isSyncing } = useUi(); const { pathname } = useLocation(); - const { validators } = useValidators(); const { pending } = useExtrinsics(); - const { isSyncing } = useUi(); + const { payoutsSynced } = usePayouts(); + const { pluginEnabled } = usePlugins(); + const { validators } = useValidators(); + const { bondedPools } = useBondedPools(); + const { poolMembersNode } = usePoolMembers(); - let syncing = isSyncing; + // Keep syncing if on nominate page and still fetching payouts. + const onNominateSyncing = () => { + if (pageFromUri(pathname, 'overview') === 'nominate') + if (payoutsSynced !== 'synced') return true; - // keep syncing if on validators page and still fetching - if (pageFromUri(pathname) === 'validators') { - if (!validators.length) { - syncing = true; - } - } + return false; + }; + + // Keep syncing if on pools page and still fetching bonded pools or pool members. Ignore pool + // member sync if Subscan is enabled. + const onPoolsSyncing = () => { + if (pageFromUri(pathname, 'overview') === 'pools') + if ( + !bondedPools.length || + (!poolMembersNode.length && !pluginEnabled('subscan')) + ) + return true; + + return false; + }; + + // Keep syncing if on validators page and still fetching. + const onValidatorsSyncing = () => { + if (pageFromUri(pathname, 'overview') === 'validators') + if (!validators.length) return true; + + return false; + }; + + const syncing = + isSyncing || + onNominateSyncing() || + onValidatorsSyncing() || + onPoolsSyncing(); return ( <> @@ -47,5 +81,3 @@ export const Headers = () => { </> ); }; - -export default Headers; diff --git a/src/library/Help/Items/ActiveDefinition.tsx b/src/library/Help/Items/ActiveDefinition.tsx new file mode 100644 index 0000000000..b84857741b --- /dev/null +++ b/src/library/Help/Items/ActiveDefinition.tsx @@ -0,0 +1,22 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { DefinitionWrapper } from '../Wrappers'; + +export const ActiveDefinition = ({ + description, +}: { + description: string[]; +}) => { + return ( + <DefinitionWrapper> + <div> + {description.map((item: any, index: number) => ( + <h4 key={`inner_def_${index}`} className="definition"> + {item} + </h4> + ))} + </div> + </DefinitionWrapper> + ); +}; diff --git a/src/library/Help/Items/Definition.tsx b/src/library/Help/Items/Definition.tsx index 28391af473..5235b786d6 100644 --- a/src/library/Help/Items/Definition.tsx +++ b/src/library/Help/Items/Definition.tsx @@ -1,35 +1,47 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import { useState } from 'react'; +import type { RefObject } from 'react'; +import { useEffect, useRef, useState } from 'react'; import { DefinitionWrapper } from '../Wrappers'; -export const Definition = ({ title, description, open: _open }: any) => { - const [open, setOpen] = useState(_open || false); +export const Definition = ({ title, description, open: o }: any) => { + // Store whether the definition is open or not. + const [open, setOpen] = useState(o || false); + + // Store the current height of the definition content. + const [height, setHeight] = useState<number>(0); + + const contentRef: RefObject<HTMLDivElement> = useRef(null); + + useEffect(() => { + const h = contentRef?.current?.clientHeight || 0; + setHeight(h); + }, [open]); return ( <DefinitionWrapper> - <div> - {!_open ? ( - <button onClick={() => setOpen(!open)} type="button"> - <h2> - {title} - <span>{open ? '-' : '+'}</span> - </h2> - </button> - ) : null} - {open ? ( - <> - {description.map((item: any, index: number) => ( - <h4 key={`inner_def_${index}`} className="definition"> - {item} - </h4> - ))} - </> - ) : null} + {!o ? ( + <button onClick={() => setOpen(!open)} type="button"> + <h2> + {title} + <span>{open ? '-' : '+'}</span> + </h2> + </button> + ) : null} + <div style={{ height }}> + <div className="content" ref={contentRef}> + {open ? ( + <> + {description.map((item: any, index: number) => ( + <h4 key={`inner_def_${index}`} className="definition"> + {item} + </h4> + ))} + </> + ) : null} + </div> </div> </DefinitionWrapper> ); }; - -export default Definition; diff --git a/src/library/Help/Items/External.tsx b/src/library/Help/Items/External.tsx index da4f85cad4..30416e97c6 100644 --- a/src/library/Help/Items/External.tsx +++ b/src/library/Help/Items/External.tsx @@ -1,5 +1,5 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only import { faExternalLinkAlt as faExt } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; @@ -20,7 +20,7 @@ export const External = ({ }, [url]); return ( - <ItemWrapper width={`${width}`} height={height || 'auto'}> + <ItemWrapper width={width} height={height || 'auto'}> <motion.button className="item" whileHover={{ scale: 1.004 }} @@ -42,5 +42,3 @@ export const External = ({ </ItemWrapper> ); }; - -export default External; diff --git a/src/library/Help/Wrappers.ts b/src/library/Help/Wrappers.ts index e612ddf9c4..face240cd7 100644 --- a/src/library/Help/Wrappers.ts +++ b/src/library/Help/Wrappers.ts @@ -1,123 +1,34 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only import { motion } from 'framer-motion'; import styled from 'styled-components'; -import { - helpButton, - modalOverlayBackground, - networkColor, - textPrimary, - textSecondary, -} from 'theme'; - -// Blurred background modal wrapper -export const Wrapper = styled(motion.div)` - background: ${modalOverlayBackground}; - position: fixed; - width: 100%; - height: 100%; - z-index: 9; - backdrop-filter: blur(14px); - - > div { - height: 100%; - display: flex; - flex-flow: row wrap; - justify-content: center; - align-items: center; - padding: 0 2rem; - - /* click anywhere behind modal content to close */ - .close { - position: fixed; - width: 100%; - height: 100%; - z-index: 8; - cursor: default; - } - } -`; - -export const HeightWrapper = styled.div` - width: 100%; - height: 100%; - max-width: 800px; - z-index: 9; - position: relative; - overflow: scroll; - - /* Hide scrollbar for Chrome, Safari and Opera */ - &::-webkit-scrollbar { - display: none; - } - -ms-overflow-style: none; - scrollbar-width: none; -`; - -export const ContentWrapper = styled.div` - width: 100%; - height: auto; - overflow: hidden; - position: relative; - padding: 5rem 0; - - > .buttons { - width: 100%; - display: flex; - flex-flow: row wrap; - margin-bottom: 2rem; - position: relative; - - > button { - > svg { - margin-right: 0.5rem; - } - color: ${networkColor}; - border: 1px solid ${networkColor}; - border-radius: 1.5rem; - padding: 0.4rem 0.8rem; - margin-right: 1.25rem; - margin-left: 0; - } - } - - h1 { - font-family: 'Unbounded', 'sans-serif', sans-serif; - margin-bottom: 1.75rem; - } - - h3 { - margin: 2rem 0.5rem 1rem 0.5rem; - } -`; export const ListWrapper = styled(motion.div)` display: flex; flex-flow: row wrap; flex-grow: 1; - align-content: flex-start; overflow: auto; padding: 0.75rem 0.5rem; > button { - color: ${textPrimary}; + color: var(--text-color-primary); padding: 0.25rem; display: flex; flex-flow: row wrap; align-items: center; } h2 { - color: ${textPrimary}; + color: var(--text-color-primary); padding: 0 0.75rem; margin: 0.5rem 0; width: 100%; } p { - color: ${textPrimary}; + color: var(--text-color-primary); } .definition { - color: ${textPrimary}; + color: var(--text-color-primary); padding: 0.75rem; line-height: 1.4rem; margin: 0; @@ -125,47 +36,56 @@ export const ListWrapper = styled(motion.div)` `; export const DefinitionWrapper = styled(motion.div)` - width: 100%; - display: flex; - background: ${helpButton}; + background: var(--background-floating-card); border-radius: 1.5rem; - margin-bottom: 1.25rem; - padding: 1.5rem 1.5rem 0 1.5rem; + display: flex; flex-flow: row wrap; - align-items: center; - position: relative; - overflow: hidden; flex: 1; + overflow: hidden; + margin-bottom: 1.25rem; + padding: 1.5rem 1.5rem 0 1.5rem; + width: 100%; button { padding: 0; + h2 { + margin: 0 0 1.5rem 0; + display: flex; + flex-flow: row wrap; + align-items: center; + + > span { + color: var(--text-color-secondary); + margin-left: 0.75rem; + opacity: 0.75; + font-size: 1.1rem; + } + } } - h2 { - margin: 0 0 1.5rem 0; - display: flex; - flex-flow: row wrap; - align-items: center; - > span { - color: ${textSecondary}; - margin-left: 0.75rem; - opacity: 0.75; - font-size: 1.1rem; + > div { + position: relative; + transition: height 0.4s cubic-bezier(0.1, 1, 0.2, 1); + width: 100%; + + > .content { + position: absolute; } - } - h4 { - margin-top: 0; - } + h4 { + font-family: InterSemiBold, sans-serif; + margin-bottom: 1.15rem; + } - p { - color: ${textPrimary}; - margin: 0.5rem 0 0 0; - text-align: left; - } + p { + color: var(--text-color-primary); + margin: 0.5rem 0 0 0; + text-align: left; + } - p.icon { - opacity: 0.5; + p.icon { + opacity: 0.5; + } } `; @@ -175,39 +95,34 @@ export const ItemWrapper = styled(motion.div)<any>` height: ${(props) => (props.height === undefined ? '160px' : props.height)}; overflow: hidden; flex-flow: row wrap; - justify-content: flex-start; > * { - background: ${helpButton}; + background: var(--background-floating-card); border-radius: 1.5rem; flex: 1; padding: 1.5rem; display: flex; flex-flow: column nowrap; - align-items: flex-start; - justify-content: flex-start; margin-bottom: 1.5rem; position: relative; + > h2 { + color: var(--text-color-primary); + text-align: left; + } > h4 { - color: ${textPrimary}; - font-weight: normal; + color: var(--text-color-primary); margin: 0.65rem 0; text-transform: uppercase; font-size: 0.7rem; } - > h2 { - color: ${textPrimary}; - margin: 0; - text-align: left; - } > p { - color: ${textPrimary}; + color: var(--text-color-primary); text-align: left; &.icon { - color: ${networkColor}; + color: var(--accent-color-primary); margin-bottom: 0; } } diff --git a/src/library/Help/index.tsx b/src/library/Help/index.tsx index fc03df6e7c..42fdf197ad 100644 --- a/src/library/Help/index.tsx +++ b/src/library/Help/index.tsx @@ -1,29 +1,35 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import { faReplyAll, faTimes } from '@fortawesome/free-solid-svg-icons'; -import { ButtonInvertRounded } from '@rossbulat/polkadot-dashboard-ui'; -import { HELP_CONFIG } from 'config/help'; -import { useHelp } from 'contexts/Help'; +import { faTimes } from '@fortawesome/free-solid-svg-icons'; import { + ButtonPrimaryInvert, + CanvasContainer, + ModalContent, + CanvasScroll, +} from '@polkadot-cloud/react'; +import { camelize } from '@polkadot-cloud/utils'; +import { useAnimation } from 'framer-motion'; +import { useCallback, useEffect } from 'react'; +import { useTranslation } from 'react-i18next'; +import { HelpConfig } from 'config/help'; +import { DefaultLocale } from 'consts'; +import { useHelp } from 'contexts/Help'; +import type { DefinitionWithKeys, - ExternalItem, ExternalItems, HelpItem, } from 'contexts/Help/types'; -import { useAnimation } from 'framer-motion'; -import useFillVariables from 'library/Hooks/useFillVariables'; -import { useCallback, useEffect } from 'react'; -import { useTranslation } from 'react-i18next'; -import { stringToKey } from 'Utils'; -import Definition from './Items/Definition'; -import { ContentWrapper, HeightWrapper, Wrapper } from './Wrappers'; +import { useFillVariables } from 'library/Hooks/useFillVariables'; +import { Definition } from './Items/Definition'; +import { External } from './Items/External'; +import { ActiveDefinition } from './Items/ActiveDefinition'; export const Help = () => { - const { setStatus, status, definition, closeHelp, setDefinition } = useHelp(); + const { t, i18n } = useTranslation('help'); const controls = useAnimation(); const { fillVariables } = useFillVariables(); - const { t, i18n } = useTranslation('help'); + const { setStatus, status, definition, closeHelp } = useHelp(); const onFadeIn = useCallback(async () => { await controls.start('visible'); @@ -31,63 +37,49 @@ export const Help = () => { const onFadeOut = useCallback(async () => { await controls.start('hidden'); - setStatus(0); + setStatus('closed'); }, []); - const variants = { - hidden: { - opacity: 0, - }, - visible: { - opacity: 1, - }, - }; - + // control canvas fade. useEffect(() => { - // help has been opened - fade in - if (status === 1) { - onFadeIn(); - } - // an external component triggered closure - fade out - if (status === 2) { - onFadeOut(); - } + if (status === 'open') onFadeIn(); + if (status === 'closing') onFadeOut(); }, [status]); // render early if help not open - if (status === 0) return <></>; + if (status === 'closed') return <></>; let meta: HelpItem | undefined; if (definition) { // get items for active category - meta = Object.values(HELP_CONFIG).find((c: HelpItem) => - c?.definitions?.find((d: string) => d === definition) + meta = Object.values(HelpConfig).find( + (c) => c?.definitions?.find((d) => d === definition) ); } else { // get all items - let _definitions: Array<string> = []; - let _external: ExternalItems = []; + let definitions: string[] = []; + let external: ExternalItems = []; - Object.values(HELP_CONFIG).forEach((c: HelpItem) => { - _definitions = _definitions.concat([...(c.definitions || [])]); - _external = _external.concat([...(c.external || [])]); + Object.values(HelpConfig).forEach((c) => { + definitions = definitions.concat([...(c.definitions || [])]); + external = external.concat([...(c.external || [])]); }); - meta = { definitions: _definitions, external: _external }; + meta = { definitions, external }; } let definitions = meta?.definitions ?? []; const activeDefinitions = definitions - .filter((d: string) => d !== definition) - .map((d: string) => { - const localeKey = stringToKey(d); + .filter((d) => d !== definition) + .map((d) => { + const localeKey = camelize(d); return fillVariables( { title: t(`definitions.${localeKey}.0`), description: i18n.getResource( - i18n.resolvedLanguage, + i18n.resolvedLanguage ?? DefaultLocale, 'help', `definitions.${localeKey}.1` ), @@ -98,16 +90,16 @@ export const Help = () => { // get active definiton const activeRecord = definition - ? definitions.find((d: string) => d === definition) + ? definitions.find((d) => d === definition) : null; let activeDefinition: DefinitionWithKeys | null = null; if (activeRecord) { - const localeKey = stringToKey(activeRecord); + const localeKey = camelize(activeRecord); const title = t(`definitions.${localeKey}.0`); const description = i18n.getResource( - i18n.resolvedLanguage, + i18n.resolvedLanguage ?? DefaultLocale, 'help', `definitions.${localeKey}.1` ); @@ -126,7 +118,7 @@ export const Help = () => { // accumulate external resources const externals = meta?.external ?? []; - const activeExternals = externals.map((e: ExternalItem) => { + const activeExternals = externals.map((e) => { const localeKey = e[0]; const url = e[1]; const website = e[2]; @@ -139,82 +131,85 @@ export const Help = () => { }); return ( - <Wrapper + <CanvasContainer initial={{ opacity: 0, + scale: 1.05, }} animate={controls} transition={{ - duration: 0.25, + duration: 0.2, + }} + variants={{ + hidden: { + opacity: 0, + scale: 1.05, + }, + visible: { + opacity: 1, + scale: 1, + }, + }} + style={{ + zIndex: 20, }} - variants={variants} > - <div> - <HeightWrapper> - <ContentWrapper> - <div className="buttons"> - {definition && ( - <ButtonInvertRounded - lg - text={t('modal.all_resources')} - iconLeft={faReplyAll} - onClick={() => setDefinition(null)} - /> - )} - <ButtonInvertRounded - lg - text={t('modal.close')} - iconLeft={faTimes} - onClick={() => closeHelp()} - /> - </div> - <h1> - {activeDefinition - ? `${activeDefinition.title}` - : `${t('modal.help_resources')}`} - </h1> - - {activeDefinition !== null && ( - <> + <CanvasScroll> + <ModalContent> + <div className="buttons"> + <ButtonPrimaryInvert + lg + text={t('modal.close')} + iconLeft={faTimes} + onClick={() => closeHelp()} + /> + </div> + <h1> + {activeDefinition + ? `${activeDefinition.title}` + : `${t('modal.helpResources')}`} + </h1> + + {activeDefinition !== null && ( + <ActiveDefinition description={activeDefinition?.description} /> + )} + + {definitions.length > 0 && ( + <> + <h3> + {activeDefinition ? `${t('modal.related')} ` : ''} + {t('modal.definitions')} + </h3> + {activeDefinitions.map((item, index: number) => ( <Definition - open + key={`def_${index}`} onClick={() => {}} - title={activeDefinition?.title} - description={activeDefinition?.description} + title={item.title} + description={item.description} + /> + ))} + </> + )} + + {activeExternals.length > 0 && ( + <> + <h3>{t('modal.articles')}</h3> + {activeExternals.map((item, index: number) => ( + <External + key={`ext_${index}`} + width="100%" + title={t(item.title)} + url={item.url} + website={item.website} /> - </> - )} - - {definitions.length > 0 && ( - <> - <h3> - {activeDefinition ? `${t('modal.related')} ` : ''} - {t('modal.definitions')} - </h3> - {activeDefinitions.map( - (item: DefinitionWithKeys, index: number) => ( - <Definition - key={`def_${index}`} - onClick={() => {}} - title={item.title} - description={item.description} - /> - ) - )} - </> - )} - </ContentWrapper> - </HeightWrapper> - <button - type="button" - className="close" - onClick={() => { - closeHelp(); - }} - > -   - </button> - </div> - </Wrapper> + ))} + </> + )} + </ModalContent> + </CanvasScroll> + <button type="button" className="close" onClick={() => closeHelp()}> +   + </button> + </CanvasContainer> ); }; diff --git a/src/library/Hooks/index.tsx b/src/library/Hooks/index.tsx index fc17c681fe..9f40972048 100644 --- a/src/library/Hooks/index.tsx +++ b/src/library/Hooks/index.tsx @@ -1,7 +1,9 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only +import type { FC } from 'react'; import { useEffect } from 'react'; +import type { AnyJson } from 'types'; /* * A hook that alerts clicks outside of the passed ref. @@ -29,19 +31,20 @@ export const useOutsideAlerter = ( }, [ref]); }; -/* - * A hook that wraps multiple context providers to a component and makes each parent context accessible. - */ -export const withProviders = - (...providers: any) => - (WrappedComponent: any) => - (props: any) => - providers.reduceRight((acc: any, prov: any) => { - let Provider = prov; +// A pure function that applies an arbitrary amount of context providers to a wrapped +// component. +export const withProviders = ( + providers: (FC<AnyJson> | [FC<AnyJson>, AnyJson])[], + Wrapped: FC +) => + providers.reduceRight( + (acc, prov) => { if (Array.isArray(prov)) { - Provider = prov[0]; + const Provider = prov[0]; return <Provider {...prov[1]}>{acc}</Provider>; } - + const Provider = prov; return <Provider>{acc}</Provider>; - }, <WrappedComponent {...props} />); + }, + <Wrapped /> + ); diff --git a/src/library/Hooks/useBatchCall/index.tsx b/src/library/Hooks/useBatchCall/index.tsx new file mode 100644 index 0000000000..b3ee2dfca7 --- /dev/null +++ b/src/library/Hooks/useBatchCall/index.tsx @@ -0,0 +1,38 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { useApi } from 'contexts/Api'; +import type { AnyApi, MaybeAddress } from 'types'; +import { useActiveAccounts } from 'contexts/ActiveAccounts'; +import { useProxySupported } from '../useProxySupported'; + +export const useBatchCall = () => { + const { api } = useApi(); + const { activeProxy } = useActiveAccounts(); + const { isProxySupported } = useProxySupported(); + + const newBatchCall = (txs: AnyApi[], from: MaybeAddress) => { + if (!api) return undefined; + + from = from || ''; + + if (activeProxy && isProxySupported(api.tx.utility.batch(txs), from)) { + return api?.tx.utility.batch( + txs.map((tx) => + api.tx.proxy.proxy( + { + id: from, + }, + null, + tx + ) + ) + ); + } + return api?.tx.utility.batch(txs); + }; + + return { + newBatchCall, + }; +}; diff --git a/src/library/Hooks/useBlockNumber/index.tsx b/src/library/Hooks/useBlockNumber/index.tsx new file mode 100644 index 0000000000..02bbd178ae --- /dev/null +++ b/src/library/Hooks/useBlockNumber/index.tsx @@ -0,0 +1,45 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { rmCommas } from '@polkadot-cloud/utils'; +import BigNumber from 'bignumber.js'; +import { useEffect, useRef, useState } from 'react'; +import { useApi } from 'contexts/Api'; +import type { AnyApi } from 'types'; +import { useNetwork } from 'contexts/Network'; + +export const useBlockNumber = () => { + const { network } = useNetwork(); + const { isReady, api } = useApi(); + + // store the current block number. + const [block, setBlock] = useState<BigNumber>(new BigNumber(0)); + + // store block unsub. + const unsub = useRef<AnyApi>(); + + useEffect(() => { + if (isReady) { + subscribeBlockNumber(); + } + return () => { + if (unsub.current) unsub.current(); + }; + }, [network, isReady]); + + const subscribeBlockNumber = async () => { + if (!api) return; + + const subscribeBlock = async () => { + const u = await api.query.system.number((number: AnyApi) => { + setBlock(new BigNumber(rmCommas(number.toString()))); + }); + return u; + }; + Promise.all([subscribeBlock]).then(([u]) => { + unsub.current = u; + }); + }; + + return block; +}; diff --git a/src/library/Hooks/useBondGreatestFee/index.tsx b/src/library/Hooks/useBondGreatestFee/index.tsx index 772e1bf812..49dffb0bba 100644 --- a/src/library/Hooks/useBondGreatestFee/index.tsx +++ b/src/library/Hooks/useBondGreatestFee/index.tsx @@ -1,20 +1,21 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import BN from 'bn.js'; +import BigNumber from 'bignumber.js'; +import { useEffect, useMemo, useState } from 'react'; import { useApi } from 'contexts/Api'; -import { useConnect } from 'contexts/Connect'; import { useTransferOptions } from 'contexts/TransferOptions'; -import { useEffect, useMemo, useState } from 'react'; +import type { BondFor } from 'types'; +import { useActiveAccounts } from 'contexts/ActiveAccounts'; interface Props { - bondType: string; + bondFor: BondFor; } -export const useBondGreatestFee = ({ bondType }: Props) => { +export const useBondGreatestFee = ({ bondFor }: Props) => { const { api } = useApi(); - const { activeAccount } = useConnect(); - const { getTransferOptions } = useTransferOptions(); + const { activeAccount } = useActiveAccounts(); + const { feeReserve, getTransferOptions } = useTransferOptions(); const transferOptions = useMemo( () => getTransferOptions(activeAccount), [activeAccount] @@ -22,7 +23,7 @@ export const useBondGreatestFee = ({ bondType }: Props) => { const { freeBalance } = transferOptions; // store the largest possible tx fees for bonding. - const [largestTxFee, setLargestTxFee] = useState<BN>(new BN(0)); + const [largestTxFee, setLargestTxFee] = useState<BigNumber>(new BigNumber(0)); // update max tx fee on free balance change useEffect(() => { @@ -37,28 +38,26 @@ export const useBondGreatestFee = ({ bondType }: Props) => { // estimate the largest possible tx fee based on users free balance. const txLargestFee = async () => { - const bond = freeBalance.toString(); + const bond = BigNumber.max(freeBalance.minus(feeReserve), 0).toString(); let tx = null; if (!api) { - return new BN(0); + return new BigNumber(0); } - if (bondType === 'pool') { + if (bondFor === 'pool') { tx = api.tx.nominationPools.bondExtra({ FreeBalance: bond, }); - } else if (bondType === 'stake') { + } else if (bondFor === 'nominator') { tx = api.tx.staking.bondExtra(bond); } if (tx) { const { partialFee } = await tx.paymentInfo(activeAccount || ''); - return new BN(partialFee.toString()); + return new BigNumber(partialFee.toString()); } - return new BN(0); + return new BigNumber(0); }; return largestTxFee; }; - -export default useBondGreatestFee; diff --git a/src/library/Hooks/useBuildPayload/index.tsx b/src/library/Hooks/useBuildPayload/index.tsx new file mode 100644 index 0000000000..230e24e25c --- /dev/null +++ b/src/library/Hooks/useBuildPayload/index.tsx @@ -0,0 +1,64 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { useApi } from 'contexts/Api'; +import { useBalances } from 'contexts/Balances'; +import { useTxMeta } from 'contexts/TxMeta'; +import type { AnyApi } from 'types'; + +export const useBuildPayload = () => { + const { api } = useApi(); + const { getNonce } = useBalances(); + const { setTxPayload } = useTxMeta(); + + // Build and set payload of the transaction and store it in TxMetaContext. + const buildPayload = async (tx: AnyApi, from: string, uid: number) => { + if (api && tx) { + const lastHeader = await api.rpc.chain.getHeader(); + const blockNumber = api.registry.createType( + 'BlockNumber', + lastHeader.number.toNumber() + ); + const method = api.createType('Call', tx); + const era = api.registry.createType('ExtrinsicEra', { + current: lastHeader.number.toNumber(), + period: 64, + }); + + const accountNonce = getNonce(from); + const nonce = api.registry.createType('Compact<Index>', accountNonce); + + const payload = { + specVersion: api.runtimeVersion.specVersion.toHex(), + transactionVersion: api.runtimeVersion.transactionVersion.toHex(), + address: from, + blockHash: lastHeader.hash.toHex(), + blockNumber: blockNumber.toHex(), + era: era.toHex(), + genesisHash: api.genesisHash.toHex(), + method: method.toHex(), + nonce: nonce.toHex(), + signedExtensions: [ + 'CheckNonZeroSender', + 'CheckSpecVersion', + 'CheckTxVersion', + 'CheckGenesis', + 'CheckMortality', + 'CheckNonce', + 'CheckWeight', + 'ChargeTransactionPayment', + ], + tip: api.registry.createType('Compact<Balance>', 0).toHex(), + version: tx.version, + }; + const raw = api.registry.createType('ExtrinsicPayload', payload, { + version: payload.version, + }); + setTxPayload(raw, uid); + } + }; + + return { + buildPayload, + }; +}; diff --git a/src/library/Hooks/useDotLottieButton/index.tsx b/src/library/Hooks/useDotLottieButton/index.tsx new file mode 100644 index 0000000000..3caf2c6d4d --- /dev/null +++ b/src/library/Hooks/useDotLottieButton/index.tsx @@ -0,0 +1,100 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { useEffect, useRef, useState } from 'react'; +import { useTheme } from 'contexts/Themes'; +import type { Theme } from 'contexts/Themes/types'; +import type { AnyJson } from 'types'; + +export const useDotLottieButton = (filename: string, options: AnyJson = {}) => { + const { mode } = useTheme(); + + const refLight = useRef<AnyJson>(null); + const refDark = useRef<AnyJson>(null); + const refsInitialised = useRef<boolean>(false); + + const getRef = (m: Theme) => { + return m === 'light' ? refLight.current : refDark.current; + }; + + const handlePlayAnimation = async () => { + if (!getRef(mode)) return; + getRef(mode).play(); + }; + + const handleComplete = (r: AnyJson) => { + if (options?.autoLoop !== true) { + r?.stop(); + } + }; + useEffect(() => { + if (!getRef('light') || !getRef('dark') || refsInitialised.current) return; + refsInitialised.current = true; + + getRef('light').addEventListener('loop', () => + handleComplete(getRef('light')) + ); + getRef('dark').addEventListener('loop', () => + handleComplete(getRef('dark')) + ); + }, [getRef('light'), getRef('dark'), refsInitialised.current]); + + useEffect(() => { + refsInitialised.current = false; + }, [options?.deps]); + + const autoPlay = options?.autoLoop ?? undefined; + + const [iconLight] = useState<any>( + <dotlottie-player + ref={refLight} + loop + autoPlay={autoPlay} + src={`${import.meta.env.BASE_URL}lottie/${filename}-light.lottie`} + style={{ height: 'inherit', width: 'inherit' }} + /> + ); + + const [iconDark] = useState<any>( + <dotlottie-player + ref={refDark} + loop + autoPlay={autoPlay} + src={`${import.meta.env.BASE_URL}lottie/${filename}-dark.lottie`} + style={{ height: 'inherit', width: 'inherit' }} + /> + ); + + const icon = ( + <> + <button + type="button" + style={{ + ...options?.style, + display: mode === 'light' ? 'block' : 'none', + height: 'inherit', + width: 'inherit', + }} + onClick={() => handlePlayAnimation()} + > + {iconLight} + </button> + <button + type="button" + style={{ + ...options?.style, + display: mode === 'dark' ? 'block' : 'none', + height: 'inherit', + width: 'inherit', + }} + onClick={() => { + handlePlayAnimation(); + }} + > + {iconDark} + </button> + </> + ); + + return { icon, play: handlePlayAnimation }; +}; diff --git a/src/library/Hooks/useEraTimeLeft/index.tsx b/src/library/Hooks/useEraTimeLeft/index.tsx index 1479bde570..b52a2221d2 100644 --- a/src/library/Hooks/useEraTimeLeft/index.tsx +++ b/src/library/Hooks/useEraTimeLeft/index.tsx @@ -1,36 +1,42 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import { useSessionEra } from 'contexts/SessionEra'; -import { useEffect, useRef, useState } from 'react'; +import BigNumber from 'bignumber.js'; +import { getUnixTime } from 'date-fns'; +import { useApi } from 'contexts/Api'; +import { useNetworkMetrics } from 'contexts/NetworkMetrics'; export const useEraTimeLeft = () => { - const { sessionEra, getEraTimeLeft } = useSessionEra(); + const { consts } = useApi(); + const { epochDuration, expectedBlockTime, sessionsPerEra } = consts; + const { activeEra } = useNetworkMetrics(); - // store era time left as state object - const [eraTimeLeft, _setEraTimeLeft] = useState(0); + // important to fetch the actual timeleft from when other components ask for it. + const get = () => { + // get timestamp of era start and convert to seconds. + const start = activeEra.start.multipliedBy(0.001); - const eraTimeLeftRef = useRef(eraTimeLeft); - const setEraTimeLeft = (_timeleft: number) => { - _setEraTimeLeft(_timeleft); - eraTimeLeftRef.current = _timeleft; + // store the duration of an era in block numbers. + const eraDurationBlocks = epochDuration.multipliedBy(sessionsPerEra); + + // estimate the duration of the era in seconds + const eraDuration = eraDurationBlocks + .multipliedBy(expectedBlockTime) + .multipliedBy(0.001); + + // estimate the end time of the era + const end = start.plus(eraDuration); + + // estimate remaining time of era. + const timeleft = BigNumber.max(0, end.minus(getUnixTime(new Date()))); + + // percentage of eraDuration + const percentage = eraDuration.multipliedBy(0.01); + const percentRemaining = timeleft.dividedBy(percentage); + const percentSurpassed = new BigNumber(100).minus(percentRemaining); + + return { timeleft, percentSurpassed, percentRemaining }; }; - // update time left every second - // clears and resets interval on `eraProgress` update. - let timeleftInterval: ReturnType<typeof setInterval>; - useEffect(() => { - setEraTimeLeft(getEraTimeLeft()); - - timeleftInterval = setInterval(() => { - setEraTimeLeft(eraTimeLeftRef.current - 1); - }, 1000); - return () => { - clearInterval(timeleftInterval); - }; - }, [sessionEra]); - - return eraTimeLeftRef.current; + return { get }; }; - -export default useEraTimeLeft; diff --git a/src/library/Hooks/useErasToTimeLeft/index.tsx b/src/library/Hooks/useErasToTimeLeft/index.tsx new file mode 100644 index 0000000000..bd27343776 --- /dev/null +++ b/src/library/Hooks/useErasToTimeLeft/index.tsx @@ -0,0 +1,32 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { greaterThanZero } from '@polkadot-cloud/utils'; +import type BigNumber from 'bignumber.js'; +import { useApi } from 'contexts/Api'; + +export const useErasToTimeLeft = () => { + const { consts } = useApi(); + const { epochDuration, expectedBlockTime, sessionsPerEra } = consts; + + // converts a number of eras to timeleft in seconds. + const erasToSeconds = (eras: BigNumber) => { + if (!greaterThanZero(eras)) { + return 0; + } + // store the duration of an era in number of blocks. + const eraDurationBlocks = epochDuration.multipliedBy(sessionsPerEra); + // estimate the duration of the era in seconds. + const eraDuration = eraDurationBlocks + .multipliedBy(expectedBlockTime) + .multipliedBy(0.001) + .integerValue(); + + // multiply by number of eras. + return eras.multipliedBy(eraDuration).toNumber(); + }; + + return { + erasToSeconds, + }; +}; diff --git a/src/library/Hooks/useFillVariables/index.tsx b/src/library/Hooks/useFillVariables/index.tsx index 9b82524f09..7cc2126443 100644 --- a/src/library/Hooks/useFillVariables/index.tsx +++ b/src/library/Hooks/useFillVariables/index.tsx @@ -1,67 +1,69 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only +import { capitalizeFirstLetter, planckToUnit } from '@polkadot-cloud/utils'; import { useApi } from 'contexts/Api'; +import { useNetwork } from 'contexts/Network'; +import { useNetworkMetrics } from 'contexts/NetworkMetrics'; import { usePoolsConfig } from 'contexts/Pools/PoolsConfig'; -import { useStaking } from 'contexts/Staking'; -import { AnyJson } from 'types'; -import { planckBnToUnit, replaceAll, toFixedIfNecessary } from 'Utils'; +import type { AnyJson } from 'types'; export const useFillVariables = () => { - const { network, consts } = useApi(); - const { eraStakers } = useStaking(); + const { consts } = useApi(); const { stats } = usePoolsConfig(); + const { networkData } = useNetwork(); const { maxNominations, maxNominatorRewardedPerValidator, existentialDeposit, } = consts; - const { minActiveBond } = eraStakers; const { minJoinBond, minCreateBond } = stats; + const { metrics } = useNetworkMetrics(); + const { minimumActiveStake } = metrics; - const fillVariables = (d: AnyJson, keys: Array<string>) => { + const fillVariables = (d: AnyJson, keys: string[]) => { const fields: AnyJson = Object.entries(d).filter(([k]: any) => keys.includes(k) ); const transformed = Object.entries(fields).map( ([, [key, val]]: AnyJson) => { const varsToValues = [ - ['{NETWORK_UNIT}', network.unit], - ['{NETWORK_NAME}', network.name], + ['{NETWORK_UNIT}', networkData.unit], + ['{NETWORK_NAME}', capitalizeFirstLetter(networkData.name)], [ - '{FallbackNominatorRewardedPerValidator}', - String(maxNominatorRewardedPerValidator), + '{MAX_NOMINATOR_REWARDED_PER_VALIDATOR}', + maxNominatorRewardedPerValidator.toString(), + ], + ['{MAX_NOMINATIONS}', maxNominations.toString()], + [ + '{MIN_ACTIVE_STAKE}', + planckToUnit(minimumActiveStake, networkData.units) + .decimalPlaces(3) + .toFormat(), ], - ['{FallbackMaxNominations}', String(maxNominations)], - ['{MIN_ACTIVE_BOND}', String(toFixedIfNecessary(minActiveBond, 3))], [ '{MIN_POOL_JOIN_BOND}', - String( - toFixedIfNecessary(planckBnToUnit(minJoinBond, network.units), 3) - ), + planckToUnit(minJoinBond, networkData.units) + .decimalPlaces(3) + .toFormat(), ], [ '{MIN_POOL_CREATE_BOND}', - String( - toFixedIfNecessary( - planckBnToUnit(minCreateBond, network.units), - 3 - ) - ), + planckToUnit(minCreateBond, networkData.units) + .decimalPlaces(3) + .toFormat(), ], [ '{EXISTENTIAL_DEPOSIT}', - String(planckBnToUnit(existentialDeposit, network.units)), + planckToUnit(existentialDeposit, networkData.units).toFormat(), ], ]; for (const varToVal of varsToValues) { if (val.constructor === Array) { - val = val.map((_d: string) => - replaceAll(_d, varToVal[0], varToVal[1]) - ); + val = val.map((_d) => _d.replaceAll(varToVal[0], varToVal[1])); } else { - val = replaceAll(val, varToVal[0], varToVal[1]); + val = val.replaceAll(varToVal[0], varToVal[1]); } } return [key, val]; @@ -78,5 +80,3 @@ export const useFillVariables = () => { fillVariables, }; }; - -export default useFillVariables; diff --git a/src/library/Hooks/useFillVariables/types.ts b/src/library/Hooks/useFillVariables/types.ts index c33c9e6d9f..0bd4de61e7 100644 --- a/src/library/Hooks/useFillVariables/types.ts +++ b/src/library/Hooks/useFillVariables/types.ts @@ -1,5 +1,5 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only export interface FillVariableItem { title: string; diff --git a/src/library/Hooks/useInflation/index.tsx b/src/library/Hooks/useInflation/index.tsx index 6e66ed4d7a..efd8b88e80 100644 --- a/src/library/Hooks/useInflation/index.tsx +++ b/src/library/Hooks/useInflation/index.tsx @@ -1,29 +1,32 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import BN from 'bn.js'; -import { useApi } from 'contexts/Api'; -import { useNetworkMetrics } from 'contexts/Network'; +import BigNumber from 'bignumber.js'; +import { useNetwork } from 'contexts/Network'; +import { useNetworkMetrics } from 'contexts/NetworkMetrics'; import { useStaking } from 'contexts/Staking'; export const useInflation = () => { - const { network } = useApi(); + const { + networkData: { params }, + } = useNetwork(); const { metrics } = useNetworkMetrics(); const { staking } = useStaking(); - const { params } = network; const { lastTotalStake } = staking; const { totalIssuance } = metrics; const { falloff, maxInflation, minInflation, stakeTarget } = params; - const BN_MILLION = new BN('1000000'); + const BIGNUMBER_MILLION = new BigNumber(1_000_000); - const calculateInflation = (totalStaked: BN) => { + const calculateInflation = (totalStaked: BigNumber) => { const stakedFraction = totalStaked.isZero() || totalIssuance.isZero() ? 0 - : totalStaked.mul(BN_MILLION).div(totalIssuance).toNumber() / - BN_MILLION.toNumber(); + : totalStaked + .multipliedBy(BIGNUMBER_MILLION) + .dividedBy(totalIssuance) + .toNumber() / BIGNUMBER_MILLION.toNumber(); // The idealStake is equal to stakeTarget since // Cere Network doesn't provide auctionMax, numAuctions and auctionAdjust so far. @@ -49,5 +52,3 @@ export const useInflation = () => { return calculateInflation(lastTotalStake); }; - -export default useInflation; diff --git a/src/library/Hooks/useLedgerLoop/index.tsx b/src/library/Hooks/useLedgerLoop/index.tsx new file mode 100644 index 0000000000..a8fece5af8 --- /dev/null +++ b/src/library/Hooks/useLedgerLoop/index.tsx @@ -0,0 +1,45 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { useLedgerHardware } from 'contexts/Hardware/Ledger'; +import { getLedgerApp } from 'contexts/Hardware/Utils'; +import { useTxMeta } from 'contexts/TxMeta'; +import { useNetwork } from 'contexts/Network'; +import type { LederLoopProps } from './types'; + +export const useLedgerLoop = ({ tasks, options, mounted }: LederLoopProps) => { + const { setIsPaired, getIsExecuting, getStatusCodes, executeLedgerLoop } = + useLedgerHardware(); + const { network } = useNetwork(); + const { getTxPayload, getPayloadUid } = useTxMeta(); + const { appName } = getLedgerApp(network); + + // Connect to Ledger device and perform necessary tasks. + // + // The tasks sent to the device depend on the current state of the import process. + const handleLedgerLoop = async () => { + // If the import modal is no longer open, cancel execution. + if (!mounted()) { + return; + } + // If the app is not open on-device, or device is not connected, cancel execution. + // If we are to explore auto looping via an interval, this may wish to use `determineStatusFromCode` instead. + if (['DeviceNotConnected'].includes(getStatusCodes()[0]?.statusCode)) { + setIsPaired('unpaired'); + } else { + // Get task options and execute the loop. + const uid = getPayloadUid(); + const accountIndex = options?.accountIndex ? options.accountIndex() : 0; + const payload = await getTxPayload(); + if (getIsExecuting()) { + await executeLedgerLoop(appName, tasks, { + uid, + accountIndex, + payload, + }); + } + } + }; + + return { handleLedgerLoop }; +}; diff --git a/src/library/Hooks/useLedgerLoop/types.ts b/src/library/Hooks/useLedgerLoop/types.ts new file mode 100644 index 0000000000..2e4b5aa817 --- /dev/null +++ b/src/library/Hooks/useLedgerLoop/types.ts @@ -0,0 +1,15 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import type { LedgerTask } from 'contexts/Hardware/types'; +import type { AnyJson } from 'types'; + +export interface LederLoopProps { + tasks: LedgerTask[]; + options: { + uid?: number; + accountIndex?: () => number; + payload?: () => Promise<AnyJson>; + }; + mounted: () => boolean; +} diff --git a/src/library/Hooks/useNominationStatus/index.tsx b/src/library/Hooks/useNominationStatus/index.tsx new file mode 100644 index 0000000000..df6a71a792 --- /dev/null +++ b/src/library/Hooks/useNominationStatus/index.tsx @@ -0,0 +1,120 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { planckToUnit } from '@polkadot-cloud/utils'; +import BigNumber from 'bignumber.js'; +import { useTranslation } from 'react-i18next'; +import { useBonded } from 'contexts/Bonded'; +import { useActivePools } from 'contexts/Pools/ActivePools'; +import { useStaking } from 'contexts/Staking'; +import { useUi } from 'contexts/UI'; +import { useValidators } from 'contexts/Validators/ValidatorEntries'; +import type { AnyJson, BondFor, MaybeAddress } from 'types'; +import { useNetwork } from 'contexts/Network'; + +export const useNominationStatus = () => { + const { t } = useTranslation(); + const { isSyncing } = useUi(); + const { + networkData: { units }, + } = useNetwork(); + const { + inSetup, + eraStakers, + erasStakersSyncing, + getNominationsStatusFromTargets, + getLowestRewardFromStaker, + } = useStaking(); + const { validators } = useValidators(); + const { poolNominations } = useActivePools(); + const { getAccountNominations } = useBonded(); + + // Utility to get an account's nominees alongside their status. + const getNomineesStatus = (who: MaybeAddress, type: BondFor) => { + const nominations = + type === 'nominator' + ? getAccountNominations(who) + : poolNominations?.targets ?? []; + + return getNominationsStatusFromTargets(who, nominations); + }; + + // Utility to get the nominees of a provided nomination status. + const getNomineesByStatus = (nominees: AnyJson[], status: string) => + nominees + .map(([k, v]) => (v === status ? k : false)) + .filter((v) => v !== false); + + // Utility to get the status of the provided account's nominations, and whether they are earning + // reards. + const getNominationStatus = (who: MaybeAddress, type: BondFor) => { + // Get the sets nominees from the provided account's targets. + const nominees = Object.entries(getNomineesStatus(who, type)); + const activeNominees = getNomineesByStatus(nominees, 'active'); + + // Determine whether active nominees are earning rewards. This function exists once the + // eras stakers has synced. + let earningRewards = false; + if (!erasStakersSyncing) { + getNomineesByStatus(nominees, 'active').every((nominee) => { + const validator = validators.find(({ address }) => address === nominee); + + if (validator) { + const others = + eraStakers.stakers.find(({ address }) => address === nominee) + ?.others || []; + + if (others.length) { + // If the provided account is a part of the validator's backers, check if they are above + // the lowest reward threshold. If so, they are earning rewards and this iteration can + // exit. + const stakedValue = + others?.find((o) => o.who === who)?.value ?? false; + if (stakedValue) { + const { lowest } = getLowestRewardFromStaker(nominee); + if ( + planckToUnit( + new BigNumber(stakedValue), + units + ).isGreaterThanOrEqualTo(lowest) + ) { + earningRewards = true; + return false; + } + } + } + } + return true; + }); + } + + // Determine the localised message to display based on the nomination status. + let str; + if (inSetup() || isSyncing) { + str = t('nominate.notNominating', { ns: 'pages' }); + } else if (!nominees.length) { + str = t('nominate.noNominationsSet', { ns: 'pages' }); + } else if (activeNominees.length) { + str = t('nominate.nominatingAnd', { ns: 'pages' }); + if (earningRewards) { + str += ` ${t('nominate.earningRewards', { ns: 'pages' })}`; + } else { + str += ` ${t('nominate.notEarningRewards', { ns: 'pages' })}`; + } + } else { + str = t('nominate.waitingForActiveNominations', { ns: 'pages' }); + } + + return { + nominees: { + active: activeNominees, + inactive: getNomineesByStatus(nominees, 'inactive'), + waiting: getNomineesByStatus(nominees, 'waiting'), + }, + earningRewards, + message: str, + }; + }; + + return { getNominationStatus, getNomineesStatus }; +}; diff --git a/src/library/Hooks/usePayeeConfig/index.tsx b/src/library/Hooks/usePayeeConfig/index.tsx new file mode 100644 index 0000000000..b0749ce9fd --- /dev/null +++ b/src/library/Hooks/usePayeeConfig/index.tsx @@ -0,0 +1,65 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import type { IconProp } from '@fortawesome/fontawesome-svg-core'; +import { + faArrowDown, + faArrowRightFromBracket, + faRedoAlt, + faStop, +} from '@fortawesome/free-solid-svg-icons'; +import { useTranslation } from 'react-i18next'; +import type { PayeeOptions } from 'contexts/Setup/types'; + +export interface PayeeItem { + icon: IconProp; + value: PayeeOptions; + title: string; + activeTitle: string; + subtitle: string; +} + +export const usePayeeConfig = () => { + const { t } = useTranslation('base'); + const getPayeeItems = (extended?: boolean): PayeeItem[] => { + let items: PayeeItem[] = [ + { + value: 'Staked', + title: t('payee.staked.title', { context: 'default' }), + activeTitle: t('payee.staked.title', { context: 'active' }), + subtitle: t('payee.staked.subtitle'), + icon: faRedoAlt, + }, + { + value: 'Stash', + title: t('payee.stash.title'), + activeTitle: t('payee.stash.title'), + subtitle: t('payee.stash.subtitle'), + icon: faArrowDown, + }, + { + value: 'Account', + title: t('payee.account.title', { context: 'default' }), + activeTitle: t('payee.account.title'), + subtitle: t('payee.account.subtitle'), + icon: faArrowRightFromBracket, + }, + ]; + + if (extended) { + items = items.concat([ + { + value: 'None', + title: t('payee.none.title', { context: 'default' }), + activeTitle: t('payee.none.title', { context: 'active' }), + subtitle: t('payee.none.subtitle'), + icon: faStop, + }, + ]); + } + + return items; + }; + + return { getPayeeItems }; +}; diff --git a/src/library/Hooks/usePoolCommission/index.tsx b/src/library/Hooks/usePoolCommission/index.tsx new file mode 100644 index 0000000000..7358421f84 --- /dev/null +++ b/src/library/Hooks/usePoolCommission/index.tsx @@ -0,0 +1,19 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { useBondedPools } from 'contexts/Pools/BondedPools'; +import { usePoolsConfig } from 'contexts/Pools/PoolsConfig'; + +export const usePoolCommission = () => { + const { getBondedPool } = useBondedPools(); + const { stats } = usePoolsConfig(); + const { globalMaxCommission } = stats; + + const getCurrentCommission = (id: number): number => + Math.min( + Number(getBondedPool(id)?.commission?.current?.[0]?.slice(0, -1) || 0), + globalMaxCommission + ); + + return { getCurrentCommission }; +}; diff --git a/src/library/Hooks/usePoolFilters/index.tsx b/src/library/Hooks/usePoolFilters/index.tsx index 0d74db6530..07e87b87bd 100644 --- a/src/library/Hooks/usePoolFilters/index.tsx +++ b/src/library/Hooks/usePoolFilters/index.tsx @@ -1,29 +1,30 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +import { useTranslation } from 'react-i18next'; import { useBondedPools } from 'contexts/Pools/BondedPools'; -import { BondedPool } from 'contexts/Pools/types'; +import type { BondedPool } from 'contexts/Pools/types'; import { useStaking } from 'contexts/Staking'; -import { AnyFunction, AnyJson } from 'types'; +import type { AnyFunction, AnyJson } from 'types'; export const usePoolFilters = () => { + const { t } = useTranslation('library'); const { meta } = useBondedPools(); const { getNominationsStatusFromTargets } = useStaking(); const { getPoolNominationStatusCode } = useBondedPools(); /* - * filterActive + * include active pools. * Iterates through the supplied list and refers to the meta * batch of the list to filter those list items that are * actively nominating. * Returns the updated filtered list. */ - const filterActive = (list: any, batchKey: string) => { + const includeActive = (list: any, batchKey: string) => { // get pool targets from nominations meta batch const nominations = meta[batchKey]?.nominations ?? []; if (!nominations) { return list; } - let i = -1; const filteredList = list.filter((p: BondedPool) => { i++; @@ -37,55 +38,103 @@ export const usePoolFilters = () => { }; /* - * filterLocked + * dont include active pools. * Iterates through the supplied list and refers to the meta * batch of the list to filter those list items that are - * locked. + * actively nominating. * Returns the updated filtered list. */ - const filterLocked = (list: any) => { - return list.filter((p: BondedPool) => p.state !== 'Blocked'); + const excludeActive = (list: any, batchKey: string) => { + // get pool targets from nominations meta batch + const nominations = meta[batchKey]?.nominations ?? []; + if (!nominations) { + return list; + } + let i = -1; + const filteredList = list.filter((p: BondedPool) => { + i++; + const targets = nominations[i]?.targets ?? []; + const status = getPoolNominationStatusCode( + getNominationsStatusFromTargets(p.addresses.stash, targets) + ); + return status !== 'active'; + }); + return filteredList; }; /* - * filterDestroying - * Iterates through the supplied list and refers to the meta - * batch of the list to filter those list items that are - * being destroyed. + * include locked pools. + * Iterates through the supplied list and checks whether state is locked. + * Returns the updated filtered list. + */ + const includeLocked = (list: any) => + list.filter((p: BondedPool) => p.state.toLowerCase() === 'Blocked'); + + /* + * include destroying pools. + * Iterates through the supplied list and checks whether state is destroying. * Returns the updated filtered list. */ - const filterDestroying = (list: any) => { - return list.filter((p: BondedPool) => p.state !== 'Destroying'); + const includeDestroying = (list: any) => + list.filter((p: BondedPool) => p.state === 'Destroying'); + + /* + * exclude locked pools. + * Iterates through the supplied list and checks whether state is locked. + * Returns the updated filtered list. + */ + const excludeLocked = (list: any) => + list.filter((p: BondedPool) => p.state !== 'Blocked'); + + /* + * exclude destroying pools. + * Iterates through the supplied list and checks whether state is destroying. + * Returns the updated filtered list. + */ + const excludeDestroying = (list: any) => + list.filter((p: BondedPool) => p.state !== 'Destroying'); + + // includes to be listed in filter overlay. + const includesToLabels: Record<string, string> = { + active: t('activePools'), }; - const includesToLabels: { [key: string]: string } = { - active: 'Active Pools', + // excludes to be listed in filter overlay. + const excludesToLabels: Record<string, string> = { + locked: t('lockedPools'), + destroying: t('destroyingPools'), }; - const excludesToLabels: { [key: string]: string } = { - locked: 'Locked Pools', - destroying: 'Destroying Pools', + // match include keys to their associated filter functions. + const includeToFunction: Record<string, AnyFunction> = { + active: includeActive, + locked: includeLocked, + destroying: includeDestroying, }; - const filterToFunction: { [key: string]: AnyFunction } = { - active: filterActive, - locked: filterLocked, - destroying: filterDestroying, + // match exclude keys to their associated filter functions. + const excludeToFunction: Record<string, AnyFunction> = { + active: excludeActive, + locked: excludeLocked, + destroying: excludeDestroying, }; - const getFiltersToApply = (excludes: Array<string>) => { + // get filter functions from keys and type of filter. + const getFiltersFromKey = (key: string[], type: string) => { + const filters = type === 'include' ? includeToFunction : excludeToFunction; const fns = []; - for (const exclude of excludes) { - if (filterToFunction[exclude]) { - fns.push(filterToFunction[exclude]); + for (const k of key) { + if (filters[k]) { + fns.push(filters[k]); } } return fns; }; + // applies filters based on the provided include and exclude keys. const applyFilter = ( - includes: Array<string> | null, - excludes: Array<string> | null, + includes: string[] | null, + excludes: string[] | null, list: AnyJson, batchKey: string ) => { @@ -93,12 +142,12 @@ export const usePoolFilters = () => { return list; } if (includes) { - for (const fn of getFiltersToApply(includes)) { + for (const fn of getFiltersFromKey(includes, 'include')) { list = fn(list, batchKey); } } if (excludes) { - for (const fn of getFiltersToApply(excludes)) { + for (const fn of getFiltersFromKey(excludes, 'exclude')) { list = fn(list, batchKey); } } @@ -106,7 +155,6 @@ export const usePoolFilters = () => { }; return { - filterToFunction, includesToLabels, excludesToLabels, applyFilter, diff --git a/src/library/Hooks/usePrices/index.tsx b/src/library/Hooks/usePrices/index.tsx index 5261982c02..6280659d1f 100644 --- a/src/library/Hooks/usePrices/index.tsx +++ b/src/library/Hooks/usePrices/index.tsx @@ -1,16 +1,18 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import { useApi } from 'contexts/Api'; -import { useUi } from 'contexts/UI'; import { useEffect, useRef, useState } from 'react'; +import { usePlugins } from 'contexts/Plugins'; +import { useUnitPrice } from 'library/Hooks/useUnitPrice'; +import { useNetwork } from 'contexts/Network'; export const usePrices = () => { - const { network, fetchDotPrice } = useApi(); - const { services } = useUi(); + const { network } = useNetwork(); + const { plugins } = usePlugins(); + const fetchUnitPrice = useUnitPrice(); const pricesLocalStorage = () => { - const pricesLocal = localStorage.getItem(`${network.name}_prices`); + const pricesLocal = localStorage.getItem(`${network}_prices`); return pricesLocal === null ? { lastPrice: 0, @@ -23,7 +25,7 @@ export const usePrices = () => { const pricesRef = useRef(prices); const setPrices = (p: any) => { - localStorage.setItem(`${network.name}_prices`, JSON.stringify(p)); + localStorage.setItem(`${network}_prices`, JSON.stringify(p)); pricesRef.current = { ...pricesRef.current, ...p, @@ -35,8 +37,7 @@ export const usePrices = () => { }; const initiatePriceInterval = async () => { - const _prices = await fetchDotPrice(); - setPrices(_prices); + setPrices(await fetchUnitPrice()); if (priceHandle === null) { setPriceInterval(); } @@ -45,8 +46,7 @@ export const usePrices = () => { let priceHandle: any = null; const setPriceInterval = async () => { priceHandle = setInterval(async () => { - const _prices = await fetchDotPrice(); - setPrices(_prices); + setPrices(await fetchUnitPrice()); }, 1000 * 30); }; @@ -70,16 +70,14 @@ export const usePrices = () => { // servie toggle useEffect(() => { - if (services.includes('binance_spot')) { + if (plugins.includes('binance_spot')) { if (priceHandle === null) { initiatePriceInterval(); } } else if (priceHandle !== null) { clearInterval(priceHandle); } - }, [services]); + }, [plugins]); return pricesRef.current; }; - -export default usePrices; diff --git a/src/library/Hooks/useProxySupported/index.tsx b/src/library/Hooks/useProxySupported/index.tsx new file mode 100644 index 0000000000..64dc7ff438 --- /dev/null +++ b/src/library/Hooks/useProxySupported/index.tsx @@ -0,0 +1,64 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { + UnsupportedIfUniqueController, + isSupportedProxyCall, +} from 'config/proxies'; +import { useBonded } from 'contexts/Bonded'; +import { useActiveAccounts } from 'contexts/ActiveAccounts'; +import { useProxies } from 'contexts/Proxies'; +import type { AnyApi, AnyJson, MaybeAddress } from 'types'; + +export const useProxySupported = () => { + const { getBondedAccount } = useBonded(); + const { getProxyDelegate } = useProxies(); + const { activeProxy } = useActiveAccounts(); + + // If call is from controller, & controller is different from stash, then proxy is not + // supported. + const controllerNotSupported = (c: string, f: MaybeAddress) => + UnsupportedIfUniqueController.includes(c) && getBondedAccount(f) !== f; + + // Determine whether the provided tx is proxy supported. + const isProxySupported = (tx: AnyApi, delegator: MaybeAddress) => { + // if already wrapped, return. + if ( + tx?.method.toHuman().section === 'proxy' && + tx?.method.toHuman().method === 'proxy' + ) { + return true; + } + + const proxyDelegate = getProxyDelegate(delegator, activeProxy); + const proxyType = proxyDelegate?.proxyType || ''; + const pallet = tx?.method.toHuman().section; + const method = tx?.method.toHuman().method; + const call = `${pallet}.${method}`; + + // If a batch call, test if every inner call is a supported proxy call. + if (call === 'utility.batch') { + return (tx?.method?.toHuman()?.args?.calls || []) + .map((c: AnyJson) => ({ + pallet: c.section, + method: c.method, + })) + .every( + (c: AnyJson) => + (isSupportedProxyCall(proxyType, c.pallet, c.method) || + (c.pallet === 'proxy' && c.method === 'proxy')) && + !controllerNotSupported(`${pallet}.${method}`, delegator) + ); + } + + // Check if the current call is a supported proxy call. + return ( + isSupportedProxyCall(proxyType, pallet, method) && + !controllerNotSupported(call, delegator) + ); + }; + + return { + isProxySupported, + }; +}; diff --git a/src/library/Hooks/useSignerWarnings/index.tsx b/src/library/Hooks/useSignerWarnings/index.tsx new file mode 100644 index 0000000000..0b51c27a85 --- /dev/null +++ b/src/library/Hooks/useSignerWarnings/index.tsx @@ -0,0 +1,47 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { useTranslation } from 'react-i18next'; +import { useTxMeta } from 'contexts/TxMeta'; +import type { MaybeAddress } from 'types'; +import { useActiveAccounts } from 'contexts/ActiveAccounts'; +import { useImportedAccounts } from 'contexts/Connect/ImportedAccounts'; + +export const useSignerWarnings = () => { + const { t } = useTranslation('modals'); + const { activeProxy } = useActiveAccounts(); + const { accountHasSigner } = useImportedAccounts(); + const { controllerSignerAvailable } = useTxMeta(); + + const getSignerWarnings = ( + account: MaybeAddress, + controller = false, + proxySupported = false + ) => { + const warnings = []; + + if (controller) { + switch (controllerSignerAvailable(account, proxySupported)) { + case 'controller_not_imported': + warnings.push(`${t('controllerImported')}`); + break; + case 'read_only': + warnings.push(`${t('readOnlyCannotSign')}`); + break; + default: + break; + } + } else if ( + !( + accountHasSigner(account) || + (accountHasSigner(activeProxy) && proxySupported) + ) + ) { + warnings.push(`${t('readOnlyCannotSign')}`); + } + + return warnings; + }; + + return { getSignerWarnings }; +}; diff --git a/src/library/Hooks/useSize/index.tsx b/src/library/Hooks/useSize/index.tsx new file mode 100644 index 0000000000..e6c07df999 --- /dev/null +++ b/src/library/Hooks/useSize/index.tsx @@ -0,0 +1,38 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import throttle from 'lodash.throttle'; +import React from 'react'; +import { useUi } from 'contexts/UI'; + +export const getSize = (element: any) => { + const width = element?.offsetWidth; + const height = element?.offsetHeight; + return { height, width }; +}; + +export const useSize = (element: any) => { + const { containerRefs } = useUi(); + + const [size, setSize] = React.useState(getSize(element)); + + const throttleCallback = () => { + setSize(getSize(element)); + }; + + React.useEffect(() => { + const resizeThrottle = throttle(throttleCallback, 100, { + trailing: true, + leading: false, + }); + + // listen to main interface resize if ref is available, otherwise + // fall back to window resize. + const listenFor = containerRefs?.mainInterface?.current ?? window; + listenFor.addEventListener('resize', resizeThrottle); + return () => { + listenFor.removeEventListener('resize', resizeThrottle); + }; + }); + return size; +}; diff --git a/src/library/Hooks/useSubmitExtrinsic/index.tsx b/src/library/Hooks/useSubmitExtrinsic/index.tsx index 70748a2f4f..70fabf3baa 100644 --- a/src/library/Hooks/useSubmitExtrinsic/index.tsx +++ b/src/library/Hooks/useSubmitExtrinsic/index.tsx @@ -1,145 +1,307 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import BN from 'bn.js'; -import { DappName } from 'consts'; +import BigNumber from 'bignumber.js'; +import { useEffect, useRef, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { DappName, ManualSigners } from 'consts'; import { useApi } from 'contexts/Api'; -import { useConnect } from 'contexts/Connect'; -import { useExtensions } from 'contexts/Extensions'; -import { Extension } from 'contexts/Extensions/types'; +import { useExtensions } from '@polkadot-cloud/react/hooks'; import { useExtrinsics } from 'contexts/Extrinsics'; +import { useLedgerHardware } from 'contexts/Hardware/Ledger'; import { useNotifications } from 'contexts/Notifications'; -import { useTxFees } from 'contexts/TxFees'; -import { useEffect, useState } from 'react'; -import { AnyApi } from 'types'; -import { UseSubmitExtrinsic, UseSubmitExtrinsicProps } from './types'; +import { useTxMeta } from 'contexts/TxMeta'; +import type { AnyApi, AnyJson } from 'types'; +import { useActiveAccounts } from 'contexts/ActiveAccounts'; +import { useImportedAccounts } from 'contexts/Connect/ImportedAccounts'; +import { useBuildPayload } from '../useBuildPayload'; +import { useProxySupported } from '../useProxySupported'; +import type { UseSubmitExtrinsic, UseSubmitExtrinsicProps } from './types'; export const useSubmitExtrinsic = ({ tx, + from, shouldSubmit, callbackSubmit, callbackInBlock, - from, }: UseSubmitExtrinsicProps): UseSubmitExtrinsic => { + const { t } = useTranslation('library'); const { api } = useApi(); - const { setTxFees, setSender, txFees } = useTxFees(); + const { buildPayload } = useBuildPayload(); + const { activeProxy } = useActiveAccounts(); + const { extensionsStatus } = useExtensions(); const { addNotification } = useNotifications(); + const { isProxySupported } = useProxySupported(); const { addPending, removePending } = useExtrinsics(); - const { extensions } = useExtensions(); - const { getAccount } = useConnect(); + const { setIsExecuting, resetStatusCodes, resetFeedback } = + useLedgerHardware(); + const { getAccount, requiresManualSign } = useImportedAccounts(); + const { + txFees, + setTxFees, + setSender, + getTxPayload, + getTxSignature, + setTxSignature, + resetTxPayloads, + incrementPayloadUid, + } = useTxMeta(); - // if null account is provided, fallback to empty string - const submitAddress: string = from ?? ''; + // Store given tx as a ref. + const txRef = useRef<AnyApi>(tx); - // whether the transaction is in progress + // Store given submit address as a ref. + const fromRef = useRef<string>(from || ''); + + // Store whether the transaction is in progress. const [submitting, setSubmitting] = useState(false); - // calculate fee upon setup changes and initial render - useEffect(() => { - setSender(from); - calculateEstimatedFee(); - }, [tx]); + // Store the uid of the extrinsic. + const [uid] = useState<number>(incrementPayloadUid()); + + // Track for one-shot transaction reset after submission. + const didTxReset = useRef<boolean>(false); + // If proxy account is active, wrap tx in a proxy call and set the sender to the proxy account. + const wrapTxIfActiveProxy = () => { + // if already wrapped, update fromRef and return. + if ( + txRef.current?.method.toHuman().section === 'proxy' && + txRef.current?.method.toHuman().method === 'proxy' + ) { + if (activeProxy) { + fromRef.current = activeProxy; + } + return; + } + + if ( + api && + activeProxy && + txRef.current && + isProxySupported(txRef.current, fromRef.current) + ) { + // update submit address to active proxy account. + fromRef.current = activeProxy; + + // Do not wrap batch transactions. Proxy calls should already be wrapping each tx within the + // batch via `useBatchCall`. + if ( + txRef.current?.method.toHuman().section === 'utility' && + txRef.current?.method.toHuman().method === 'batch' + ) { + return; + } + + // Not a batch transaction: wrap tx in proxy call. + txRef.current = api.tx.proxy.proxy( + { + id: from, + }, + null, + txRef.current + ); + } + }; + + // Calculate the estimated tx fee of the transaction. const calculateEstimatedFee = async () => { - if (tx === null) { + if (txRef.current === null) { return; } // get payment info - const { partialFee } = await tx.paymentInfo(submitAddress); - const partialFeeBn = new BN(partialFee.toString()); + const { partialFee } = await txRef.current.paymentInfo(fromRef.current); + const partialFeeBn = new BigNumber(partialFee.toString()); - // give tx fees to global useTxFees context + // give tx fees to global useTxMeta context if (partialFeeBn.toString() !== txFees.toString()) { setTxFees(partialFeeBn); } }; - // submit extrinsic - const submitTx = async () => { - if (submitting || !shouldSubmit || !api) { - return; - } - const account = getAccount(submitAddress); - if (account === null) { + // Extrinsic submission handler. + const onSubmit = async () => { + const account = getAccount(fromRef.current); + if ( + account === null || + submitting || + !shouldSubmit || + !api || + (requiresManualSign(fromRef.current) && !getTxSignature()) + ) { return; } - const _accountNonce = await api.rpc.system.accountNextIndex(submitAddress); - const accountNonce = _accountNonce.toHuman(); + const nonce = ( + await api.rpc.system.accountNextIndex(fromRef.current) + ).toHuman(); - const { signer, source } = account; + const { source } = account; + + // if `activeAccount` is imported from an extension, ensure it is enabled. + if (!ManualSigners.includes(source)) { + const isInstalled = Object.entries(extensionsStatus).find( + ([id, status]) => id === source && status === 'connected' + ); + + if (!isInstalled) throw new Error(`${t('walletNotFound')}`); + + if (!window?.injectedWeb3?.[source]) + throw new Error(`${t('walletNotFound')}`); - const extension = extensions.find((e: Extension) => e.id === source); - if (extension === undefined) { - throw new Error('wallet not found'); - } else { // summons extension popup if not already connected. - extension.enable(DappName); + window.injectedWeb3[source].enable(DappName); } + const onReady = () => { + addPending(nonce); + addNotification({ + title: t('pending'), + subtitle: t('transactionInitiated'), + }); + callbackSubmit(); + }; + + const onInBlock = () => { + setSubmitting(false); + removePending(nonce); + addNotification({ + title: t('inBlock'), + subtitle: t('transactionInBlock'), + }); + callbackInBlock(); + }; + + const onFinalizedEvent = (method: string) => { + if (method === 'ExtrinsicSuccess') { + addNotification({ + title: t('finalized'), + subtitle: t('transactionSuccessful'), + }); + } else if (method === 'ExtrinsicFailed') { + addNotification({ + title: t('failed'), + subtitle: t('errorWithTransaction'), + }); + setSubmitting(false); + removePending(nonce); + } + }; + + const resetTx = () => { + resetTxPayloads(); + setTxSignature(null); + setSubmitting(false); + }; + + const resetLedgerTx = () => { + setIsExecuting(false); + resetStatusCodes(); + resetFeedback(); + }; + const resetManualTx = () => { + resetTx(); + resetLedgerTx(); + }; + + const onError = (type?: string) => { + resetTx(); + if (type === 'ledger') { + resetLedgerTx(); + } + removePending(nonce); + addNotification({ + title: t('cancelled'), + subtitle: t('transactionCancelled'), + }); + }; + + const handleStatus = (status: AnyApi) => { + if (status.isReady) onReady(); + if (status.isInBlock) onInBlock(); + }; + + const unsubEvents = ['ExtrinsicSuccess', 'ExtrinsicFailed']; + // pre-submission state update setSubmitting(true); - try { - const unsub = await tx.signAndSend( - from, - { signer }, - ({ status, events = [] }: AnyApi) => { - // extrinsic is ready ( has been signed), add to pending - if (status.isReady) { - addPending(accountNonce); - addNotification({ - title: 'Pending', - subtitle: 'Transaction was initiated.', - }); - callbackSubmit(); - } + const txPayload: AnyJson = getTxPayload(); + const txSignature: AnyJson = getTxSignature(); + + // handle signed transaction. + if (getTxSignature()) { + try { + txRef.current.addSignature(fromRef.current, txSignature, txPayload); - // extrinsic is in block, assume tx completed - if (status.isInBlock) { - setSubmitting(false); - removePending(accountNonce); - addNotification({ - title: 'In Block', - subtitle: 'Transaction in block', - }); - callbackInBlock(); + const unsub = await txRef.current.send( + ({ status, events = [] }: AnyApi) => { + if (!didTxReset.current) { + didTxReset.current = true; + resetManualTx(); + } + + handleStatus(status); + if (status.isFinalized) { + events.forEach(({ event: { method } }: AnyApi) => { + onFinalizedEvent(method); + if (unsubEvents?.includes(method)) unsub(); + }); + } } + ); + } catch (e) { + onError(ManualSigners.includes(source) ? source : 'default'); + } + } else { + // handle unsigned transaction. + const { signer } = account; + try { + const unsub = await txRef.current.signAndSend( + fromRef.current, + { signer }, + ({ status, events = [] }: AnyApi) => { + if (!didTxReset.current) { + didTxReset.current = true; + resetTx(); + } - // let user know outcome of transaction - if (status.isFinalized) { - events.forEach(({ event: { method } }: AnyApi) => { - if (method === 'ExtrinsicSuccess') { - addNotification({ - title: 'Finalized', - subtitle: 'Transaction successful', - }); - unsub(); - } else if (method === 'ExtrinsicFailed') { - addNotification({ - title: 'Failed', - subtitle: 'Error with transaction', - }); - setSubmitting(false); - removePending(accountNonce); - unsub(); - } - }); + handleStatus(status); + if (status.isFinalized) { + events.forEach(({ event: { method } }: AnyApi) => { + onFinalizedEvent(method); + if (unsubEvents?.includes(method)) unsub(); + }); + } } - } - ); - } catch (e) { - setSubmitting(false); - removePending(accountNonce); - addNotification({ - title: 'Cancelled', - subtitle: 'Transaction was cancelled', - }); + ); + } catch (e) { + onError('default'); + } } }; + // Refresh state upon `tx` updates. + useEffect(() => { + // update txRef to latest tx. + txRef.current = tx; + // update submit address to latest from. + fromRef.current = from || ''; + // wrap tx in proxy call if active proxy & proxy supported. + wrapTxIfActiveProxy(); + // ensure sender is up to date. + setSender(fromRef.current); + // re-calculate estimated tx fee. + calculateEstimatedFee(); + // rebuild tx payload. + buildPayload(txRef.current, fromRef.current, uid); + }, [tx?.toString(), tx?.method?.args?.calls?.toString(), from]); + return { - submitTx, + uid, + onSubmit, submitting, + submitAddress: fromRef.current, + proxySupported: isProxySupported(txRef.current, fromRef.current), }; }; diff --git a/src/library/Hooks/useSubmitExtrinsic/types.ts b/src/library/Hooks/useSubmitExtrinsic/types.ts index 731d641643..9b281cf9d8 100644 --- a/src/library/Hooks/useSubmitExtrinsic/types.ts +++ b/src/library/Hooks/useSubmitExtrinsic/types.ts @@ -1,17 +1,20 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import { AnyApi, MaybeAccount } from 'types'; +import type { AnyApi, MaybeAddress } from 'types'; export interface UseSubmitExtrinsicProps { tx: AnyApi; shouldSubmit: boolean; callbackSubmit: { (): void }; callbackInBlock: { (): void }; - from: MaybeAccount; + from: MaybeAddress; } export interface UseSubmitExtrinsic { - submitTx: { (): void }; + uid: number; + onSubmit: { (): void }; submitting: boolean; + proxySupported: boolean; + submitAddress: MaybeAddress; } diff --git a/src/library/Hooks/useTimeLeft/defaults.ts b/src/library/Hooks/useTimeLeft/defaults.ts new file mode 100644 index 0000000000..3175e6e05d --- /dev/null +++ b/src/library/Hooks/useTimeLeft/defaults.ts @@ -0,0 +1,14 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import type { TimeleftDuration } from './types'; + +export const defaultDuration: TimeleftDuration = { + days: 0, + hours: 0, + minutes: 0, + seconds: 0, + lastMinute: false, +}; + +export const defaultRefreshInterval = 60; diff --git a/src/library/Hooks/useTimeLeft/index.tsx b/src/library/Hooks/useTimeLeft/index.tsx new file mode 100644 index 0000000000..31731db674 --- /dev/null +++ b/src/library/Hooks/useTimeLeft/index.tsx @@ -0,0 +1,134 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { setStateWithRef } from '@polkadot-cloud/utils'; +import { useEffect, useRef, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useNetwork } from 'contexts/Network'; +import type { + TimeLeftAll, + TimeLeftFormatted, + TimeLeftRaw, + TimeleftDuration, +} from './types'; +import { getDuration } from './utils'; + +export const useTimeLeft = () => { + const { network } = useNetwork(); + const { t, i18n } = useTranslation(); + + // check whether timeleft is within a minute of finishing. + const inLastHour = () => { + const { days, hours } = getDuration(toRef.current); + return !days && !hours; + }; + + // get the amount of seconds left if timeleft is in the last minute. + const lastMinuteCountdown = () => { + const { seconds } = getDuration(toRef.current); + if (!inLastHour()) { + return 60; + } + return seconds; + }; + + // calculate resulting timeleft object from latest duration. + const getTimeleft = (c?: TimeleftDuration): TimeLeftAll => { + const { days, hours, minutes, seconds } = c || getDuration(toRef.current); + + const raw: TimeLeftRaw = { + days, + hours, + minutes, + }; + const formatted: TimeLeftFormatted = { + days: [days, t('time.day', { count: days, ns: 'base' })], + hours: [hours, t('time.hr', { count: hours, ns: 'base' })], + minutes: [minutes, t('time.min', { count: minutes, ns: 'base' })], + }; + if (!days && !hours) { + formatted.seconds = [ + seconds, + t('time.second', { count: seconds, ns: 'base' }), + ]; + raw.seconds = seconds; + } + + return { + raw, + formatted, + }; + }; + + // the end time as a date. + const [to, setTo] = useState<Date | null>(null); + const toRef = useRef(to); + + // resulting timeleft object to be returned. + const [timeleft, setTimeleft] = useState<TimeLeftAll>(getTimeleft()); + + // timeleft refresh intervals. + const [minInterval, setMinInterval] = useState< + ReturnType<typeof setInterval> | undefined + >(undefined); + const minIntervalRef = useRef(minInterval); + + const [secInterval, setSecInterval] = useState< + ReturnType<typeof setInterval> | undefined + >(undefined); + const secIntervalRef = useRef(secInterval); + + // refresh effects. + useEffect(() => { + setTimeleft(getTimeleft()); + if (inLastHour()) { + // refresh timeleft every second. + if (!secIntervalRef.current) { + const interval = setInterval(() => { + if (!inLastHour()) { + clearInterval(secIntervalRef.current); + setStateWithRef(undefined, setSecInterval, secIntervalRef); + } + setTimeleft(getTimeleft()); + }, 1000); + + setStateWithRef(interval, setSecInterval, secIntervalRef); + } + } + // refresh timeleft every minute. + else if (!minIntervalRef.current) { + const interval = setInterval(() => { + if (inLastHour()) { + clearInterval(minIntervalRef.current); + setStateWithRef(undefined, setMinInterval, minIntervalRef); + } + setTimeleft(getTimeleft()); + }, 60000); + setStateWithRef(interval, setMinInterval, minIntervalRef); + } + }, [to, inLastHour(), lastMinuteCountdown(), network]); + + // re-render the timeleft upon langauge switch. + useEffect(() => { + setTimeleft(getTimeleft()); + }, [i18n.resolvedLanguage]); + + // clear intervals on unmount + useEffect( + () => () => { + clearInterval(minInterval); + clearInterval(secInterval); + }, + [] + ); + + const setFromNow = (dateFrom: Date, dateTo: Date) => { + setTimeleft(getTimeleft(getDuration(dateFrom))); + setStateWithRef(dateTo, setTo, toRef); + }; + + return { + setFromNow, + timeleft, + }; +}; diff --git a/src/library/Hooks/useTimeLeft/types.ts b/src/library/Hooks/useTimeLeft/types.ts new file mode 100644 index 0000000000..30bdd1c3fc --- /dev/null +++ b/src/library/Hooks/useTimeLeft/types.ts @@ -0,0 +1,32 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors + +export interface TimeleftDuration { + days: number; + hours: number; + minutes: number; + seconds: number; + lastMinute: boolean; +} + +export interface TimeLeftRaw { + days: number; + hours: number; + minutes: number; + seconds?: number; +} + +export interface TimeLeftFormatted { + days: [number, string]; + hours: [number, string]; + minutes: [number, string]; + seconds?: [number, string]; +} + +export interface TimeLeftAll { + raw: TimeLeftRaw; + formatted: TimeLeftFormatted; +} + +export interface TimeleftHookProps { + refreshInterval: number; +} diff --git a/src/library/Hooks/useTimeLeft/utils.ts b/src/library/Hooks/useTimeLeft/utils.ts new file mode 100644 index 0000000000..daf1e843ed --- /dev/null +++ b/src/library/Hooks/useTimeLeft/utils.ts @@ -0,0 +1,83 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { + differenceInDays, + fromUnixTime, + getUnixTime, + intervalToDuration, +} from 'date-fns'; +import type { AnyFunction } from 'types'; +import { defaultDuration } from './defaults'; +import type { TimeleftDuration } from './types'; + +// adds `seconds` to the current time and returns the resulting date. +export const fromNow = (seconds: number): Date => { + const end = new Date(); + end.setSeconds(end.getSeconds() + seconds); + return end; +}; + +// calculates the current timeleft duration. +export const getDuration = (toDate: Date | null): TimeleftDuration => { + if (!toDate) { + return defaultDuration; + } + if (getUnixTime(toDate) <= getUnixTime(new Date())) { + return defaultDuration; + } + + toDate.setSeconds(toDate.getSeconds()); + const d = intervalToDuration({ + start: Date.now(), + end: toDate, + }); + + const days = differenceInDays(toDate, Date.now()); + const hours = d?.hours || 0; + const minutes = d?.minutes || 0; + const seconds = d?.seconds || 0; + const lastHour = days === 0 && hours === 0; + const lastMinute = lastHour && minutes === 0; + + return { + days, + hours, + minutes, + seconds, + lastMinute, + }; +}; + +// format the duration (from seconds) as a string. +export const timeleftAsString = ( + t: AnyFunction, + start: number, + duration: number, + full?: boolean +) => { + const { days, hours, minutes, seconds } = getDuration( + fromUnixTime(start + duration) || null + ); + + const tHour = `time.${full ? `hour` : `hr`}`; + const tMinute = `time.${full ? `minute` : `min`}`; + + let str = ''; + if (days > 0) { + str += `${days} ${t('time.day', { count: days, ns: 'base' })}`; + } + if (hours > 0) { + if (str) str += ', '; + str += ` ${hours} ${t(tHour, { count: hours, ns: 'base' })}`; + } + if (minutes > 0) { + if (str) str += ', '; + str += ` ${minutes} ${t(tMinute, { count: minutes, ns: 'base' })}`; + } + if (!days && !hours) { + if (str) str += ', '; + str += ` ${seconds}`; + } + return str; +}; diff --git a/src/library/Hooks/useUnitPrice/index.tsx b/src/library/Hooks/useUnitPrice/index.tsx new file mode 100644 index 0000000000..e7e4c1a98f --- /dev/null +++ b/src/library/Hooks/useUnitPrice/index.tsx @@ -0,0 +1,41 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { NetworkList } from 'config/networks'; +import { ApiEndpoints } from 'consts'; +import { useNetwork } from 'contexts/Network'; + +export const useUnitPrice = () => { + const { network } = useNetwork(); + + const fetchUnitPrice = async () => { + const urls = [ + `${ApiEndpoints.priceChange}${NetworkList[network].api.priceTicker}`, + ]; + + const responses = await Promise.all( + urls.map((u) => fetch(u, { method: 'GET' })) + ); + const texts = await Promise.all(responses.map((res) => res.json())); + const newPrice = texts[0]; + + if ( + newPrice.lastPrice !== undefined && + newPrice.priceChangePercent !== undefined + ) { + const price: string = (Math.ceil(newPrice.lastPrice * 100) / 100).toFixed( + 2 + ); + + return { + lastPrice: price, + change: (Math.round(newPrice.priceChangePercent * 100) / 100).toFixed( + 2 + ), + }; + } + return null; + }; + + return fetchUnitPrice; +}; diff --git a/src/library/Hooks/useUnstaking/index.tsx b/src/library/Hooks/useUnstaking/index.tsx new file mode 100644 index 0000000000..8e0acffe41 --- /dev/null +++ b/src/library/Hooks/useUnstaking/index.tsx @@ -0,0 +1,63 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { useTranslation } from 'react-i18next'; +import { useApi } from 'contexts/Api'; +import { useFastUnstake } from 'contexts/FastUnstake'; +import { useNetworkMetrics } from 'contexts/NetworkMetrics'; +import { useStaking } from 'contexts/Staking'; +import { useTransferOptions } from 'contexts/TransferOptions'; +import type { AnyJson } from 'types'; +import { useActiveAccounts } from 'contexts/ActiveAccounts'; +import { useNominationStatus } from '../useNominationStatus'; + +export const useUnstaking = () => { + const { t } = useTranslation('library'); + const { consts } = useApi(); + const { inSetup } = useStaking(); + const { activeAccount } = useActiveAccounts(); + const { activeEra } = useNetworkMetrics(); + const { getTransferOptions } = useTransferOptions(); + const { getNominationStatus } = useNominationStatus(); + const { checking, head, isExposed, queueDeposit, meta } = useFastUnstake(); + const { bondDuration } = consts; + const transferOptions = getTransferOptions(activeAccount).nominate; + const { nominees } = getNominationStatus(activeAccount, 'nominator'); + + // determine if user is regular unstaking + const { active } = transferOptions; + + // determine if user is fast unstaking. + const inHead = + head?.stashes.find((s: AnyJson) => s[0] === activeAccount) ?? undefined; + const inQueue = queueDeposit?.isGreaterThan(0) ?? false; + + const registered = inHead || inQueue; + + // determine unstake button + const getFastUnstakeText = () => { + const { checked } = meta; + if (checking) { + return `${t('fastUnstakeCheckingEras', { + checked: checked.length, + total: bondDuration.toString(), + })}...`; + } + if (isExposed) { + const lastExposed = activeEra.index.minus(checked[0] || 0); + return t('fastUnstakeExposed', { + count: lastExposed.toNumber(), + }); + } + if (registered) { + return t('inQueue'); + } + return t('fastUnstake'); + }; + + return { + getFastUnstakeText, + isUnstaking: !inSetup() && !nominees.active.length && active.isZero(), + isFastUnstaking: !!registered, + }; +}; diff --git a/src/library/Hooks/useValidatorFilters/index.tsx b/src/library/Hooks/useValidatorFilters/index.tsx index 9e3fd09511..6cb47d1e27 100644 --- a/src/library/Hooks/useValidatorFilters/index.tsx +++ b/src/library/Hooks/useValidatorFilters/index.tsx @@ -1,51 +1,43 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only import { u8aToString, u8aUnwrapBytes } from '@polkadot/util'; -import { useApi } from 'contexts/Api'; -import { useValidators } from 'contexts/Validators'; -import { AnyFunction, AnyJson } from 'types'; +import { useTranslation } from 'react-i18next'; +import { useValidators } from 'contexts/Validators/ValidatorEntries'; +import type { AnyFunction, AnyJson } from 'types'; +import { useStaking } from 'contexts/Staking'; +import { MaxEraRewardPointsEras } from 'consts'; export const useValidatorFilters = () => { - const { consts } = useApi(); - const { meta, session, sessionParachain } = useValidators(); - const { maxNominatorRewardedPerValidator } = consts; + const { t } = useTranslation('library'); + const { + sessionValidators, + sessionParaValidators, + validatorIdentities, + validatorSupers, + validatorEraPointsHistory, + } = useValidators(); + const { erasStakersSyncing, getLowestRewardFromStaker } = useStaking(); /* - * filterMissingIdentity - * Iterates through the supplied list and refers to the meta - * batch of the list to filter those list items with - * missing identities. - * Returns the updated filtered list. + * filterMissingIdentity: Iterates through the supplied list and filters those with missing + * identities. Returns the updated filtered list. */ - const filterMissingIdentity = (list: any, batchKey: string) => { - if (meta[batchKey] === undefined) { + const filterMissingIdentity = (list: any) => { + // Return lsit early if identity sync has not completed. + if ( + !Object.values(validatorIdentities).length || + !Object.values(validatorSupers).length + ) { return list; } const filteredList: any = []; for (const validator of list) { - const addressBatchIndex = - meta[batchKey].addresses?.indexOf(validator.address) ?? -1; - - // if we cannot derive data, fallback to include validator in filtered list - if (addressBatchIndex === -1) { - filteredList.push(validator); - continue; - } - - const identities = meta[batchKey]?.identities ?? []; - const supers = meta[batchKey]?.supers ?? []; - - // push validator if sync has not completed - if (!identities.length || !supers.length) { - filteredList.push(validator); - } - - const identityExists = identities[addressBatchIndex] ?? null; - const superExists = supers[addressBatchIndex] ?? null; + const identityExists = validatorIdentities[validator.address] ?? false; + const superExists = validatorSupers[validator.address] ?? false; - // validator included if identity or super identity has been set - if (identityExists !== null || superExists !== null) { + // Validator included if identity or super identity has been set. + if (!!identityExists || !!superExists) { filteredList.push(validator); continue; } @@ -54,29 +46,20 @@ export const useValidatorFilters = () => { }; /* - * filterOverSubscribed - * Iterates through the supplied list and refers to the meta - * batch of the list to filter those list items that are - * over subscribed. - * Returns the updated filtered list. + * filterOverSubscribed: Iterates through the supplied list and filters those who are over + * subscribed. Returns the updated filtered list. */ - const filterOverSubscribed = (list: any, batchKey: string) => { - if (meta[batchKey] === undefined) { + const filterOverSubscribed = (list: any) => { + // Return list early if eraStakers is still syncing. + if (erasStakersSyncing) { return list; } + const filteredList: any = []; for (const validator of list) { - const addressBatchIndex = - meta[batchKey].addresses?.indexOf(validator.address) ?? -1; - const stake = meta[batchKey]?.stake ?? false; + const { oversubscribed } = getLowestRewardFromStaker(validator.address); - // if we cannot derive data, fallback to include validator in filtered list - if (addressBatchIndex === -1 || !stake) { - filteredList.push(validator); - continue; - } - const totalNominations = stake[addressBatchIndex].total_nominations ?? 0; - if (totalNominations < maxNominatorRewardedPerValidator) { + if (!oversubscribed) { filteredList.push(validator); continue; } @@ -85,77 +68,67 @@ export const useValidatorFilters = () => { }; /* - * filterAllCommission - * Filters the supplied list and removes items with 100% commission. - * Returns the updated filtered list. + * filterAllCommission: Filters the supplied list and removes items with 100% commission. Returns + * the updated filtered list. */ - const filterAllCommission = (list: any) => { - list = list.filter( - (validator: any) => validator?.prefs?.commission !== 100 - ); - return list; - }; + const filterAllCommission = (list: any) => + list.filter((validator: any) => validator?.prefs?.commission !== 100); /* - * filterBlockedNominations - * Filters the supplied list and removes items that have blocked nominations. - * Returns the updated filtered list. + * filterBlockedNominations: Filters the supplied list and removes items that have blocked + * nominations. Returns the updated filtered list. */ - const filterBlockedNominations = (list: any) => { - return list.filter((validator: any) => validator?.prefs?.blocked !== true); - }; + const filterBlockedNominations = (list: any) => + list.filter((validator: any) => validator?.prefs?.blocked !== true); /* - * filterActive - * Filters the supplied list and removes items that are inactive. - * Returns the updated filtered list. + * filterActive: Filters the supplied list and removes items that are inactive. Returns the + * updated filtered list. */ const filterActive = (list: any) => { // if list has not yet been populated, return original list - if (session.list.length === 0) return list; + if (sessionValidators.length === 0) return list; return list.filter((validator: any) => - session.list.includes(validator.address) + sessionValidators.includes(validator.address) ); }; /* - * filterNonParachainValidator - * Filters the supplied list and removes items that are inactive. + * filterNonParachainValidator: Filters the supplied list and removes items that are inactive. * Returns the updated filtered list. */ const filterNonParachainValidator = (list: any) => { // if list has not yet been populated, return original list - if ((sessionParachain?.length ?? 0) === 0) return list; + if ((sessionParaValidators?.length ?? 0) === 0) return list; return list.filter((validator: any) => - sessionParachain.includes(validator.address) + sessionParaValidators.includes(validator.address) ); }; /* - * filterInSession - * Filters the supplied list and removes items that are in the current session. + * filterInSession: Filters the supplied list and removes items that are in the current session. * Returns the updated filtered list. */ const filterInSession = (list: any) => { // if list has not yet been populated, return original list - if (session.list.length === 0) return list; + if (sessionValidators.length === 0) return list; return list.filter( - (validator: any) => !session.list.includes(validator.address) + (validator: any) => !sessionValidators.includes(validator.address) ); }; - const includesToLabels: { [key: string]: string } = { - active: 'Active Validators', + const includesToLabels: Record<string, string> = { + active: t('activeValidators'), }; - const excludesToLabels: { [key: string]: string } = { - over_subscribed: 'Over Subscribed', - all_commission: '100% Commission', - blocked_nominations: 'Blocked Nominations', - missing_identity: 'Missing Identity', + const excludesToLabels: Record<string, string> = { + over_subscribed: t('overSubscribed'), + all_commission: t('100Commission'), + blocked_nominations: t('blockedNominations'), + missing_identity: t('missingIdentity'), }; - const filterToFunction: { [key: string]: AnyFunction } = { + const filterToFunction: Record<string, AnyFunction> = { active: filterActive, missing_identity: filterMissingIdentity, over_subscribed: filterOverSubscribed, @@ -165,7 +138,7 @@ export const useValidatorFilters = () => { in_session: filterInSession, }; - const getFiltersToApply = (excludes: Array<string>) => { + const getFiltersToApply = (excludes: string[]) => { const fns = []; for (const exclude of excludes) { if (filterToFunction[exclude]) { @@ -176,56 +149,59 @@ export const useValidatorFilters = () => { }; const applyFilter = ( - includes: Array<string> | null, - excludes: Array<string> | null, - list: AnyJson, - batchKey: string + includes: string[] | null, + excludes: string[] | null, + list: AnyJson ) => { if (!excludes && !includes) { return list; } if (includes) { for (const fn of getFiltersToApply(includes)) { - list = fn(list, batchKey); + list = fn(list); } } if (excludes) { for (const fn of getFiltersToApply(excludes)) { - list = fn(list, batchKey); + list = fn(list); } } return list; }; /* - * orderLowestCommission - * Orders a list by commission, lowest first. - * Returns the updated ordered list. + * orderLowestCommission: Orders a list by commission, lowest first. Returns the updated ordered + * list. */ - const orderLowestCommission = (list: any) => { - return [...list].sort( - (a: any, b: any) => a.prefs.commission - b.prefs.commission - ); - }; + const orderLowestCommission = (list: any) => + [...list].sort((a, b) => a.prefs.commission - b.prefs.commission); /* - * orderHighestCommission - * Orders a list by commission, highest first. - * Returns the updated ordered list. + * orderHighestCommission: Orders a list by commission, highest first. Returns the updated ordered + * list. */ - const orderHighestCommission = (list: any) => { - return [...list].sort( - (a: any, b: any) => b.prefs.commission - a.prefs.commission - ); - }; + const orderHighestCommission = (list: any) => + [...list].sort((a, b) => b.prefs.commission - a.prefs.commission); - const ordersToLabels: { [key: string]: string } = { - default: 'Unordered', - low_commission: 'Low Commission', - high_commission: 'High Commission', + /* + * orderByRank: Orders a list by validator rank. + */ + const orderByRank = (list: any) => + [...list].sort((a, b) => { + const aRank = validatorEraPointsHistory[a.address]?.rank || 9999; + const bRank = validatorEraPointsHistory[b.address]?.rank || 9999; + return aRank - bRank; + }); + + const ordersToLabels: Record<string, string> = { + rank: `${MaxEraRewardPointsEras} ${t('dayPerformance')}`, + low_commission: t('lowCommission'), + high_commission: t('highCommission'), + default: t('unordered'), }; - const orderToFunction: { [key: string]: AnyFunction } = { + const orderToFunction: Record<string, AnyFunction> = { + rank: orderByRank, low_commission: orderLowestCommission, high_commission: orderHighestCommission, }; @@ -239,37 +215,29 @@ export const useValidatorFilters = () => { }; /* - * applySearch - * Iterates through the supplied list and refers to the meta - * batch of the list to filter those list items that match - * the search term. + * applySearch Iterates through the supplied list and filters those that match the search term. * Returns the updated filtered list. */ - const applySearch = (list: any, batchKey: string, searchTerm: string) => { - if (meta[batchKey] === undefined) return list; + const applySearch = (list: any, searchTerm: string) => { + // If we cannot derive data, fallback to include validator in filtered list. + if ( + !searchTerm || + !Object.values(validatorIdentities).length || + !Object.values(validatorSupers).length + ) { + return list; + } + const filteredList: any = []; for (const validator of list) { - const batchIndex = - meta[batchKey].addresses?.indexOf(validator.address) ?? -1; - const identities = meta[batchKey]?.identities ?? false; - const supers = meta[batchKey]?.supers ?? false; - - // if we cannot derive data, fallback to include validator in filtered list - if (batchIndex === -1 || !identities || !supers) { - filteredList.push(validator); - continue; - } - - const address = meta[batchKey].addresses[batchIndex]; - - const identity = identities[batchIndex] ?? ''; + const identity = validatorIdentities[validator.address] ?? ''; const identityRaw = identity?.info?.display?.Raw ?? ''; const identityAsBytes = u8aToString(u8aUnwrapBytes(identityRaw)); const identitySearch = ( identityAsBytes === '' ? identityRaw : identityAsBytes ).toLowerCase(); - const superIdentity = supers[batchIndex] ?? null; + const superIdentity = validatorSupers[validator.address] ?? null; const superIdentityRaw = superIdentity?.identity?.info?.display?.Raw ?? ''; const superIdentityAsBytes = u8aToString( @@ -279,7 +247,7 @@ export const useValidatorFilters = () => { superIdentityAsBytes === '' ? superIdentityRaw : superIdentityAsBytes ).toLowerCase(); - if (address.toLowerCase().includes(searchTerm.toLowerCase())) + if (validator.address.toLowerCase().includes(searchTerm.toLowerCase())) filteredList.push(validator); if ( identitySearch.includes(searchTerm.toLowerCase()) || diff --git a/src/library/Identicon/index.tsx b/src/library/Identicon/index.tsx deleted file mode 100644 index a5bf94f492..0000000000 --- a/src/library/Identicon/index.tsx +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { Identicon as IdenticonDefault } from '@polkadot/react-identicon'; -import styled from 'styled-components'; -import { backgroundIdenticon } from 'theme'; -import { IdenticonProps } from './types'; - -const Wrapper = styled.div` - svg > circle:first-child { - fill: ${backgroundIdenticon}; - } -`; -export const Identicon = ({ value, size }: IdenticonProps) => ( - <Wrapper> - <IdenticonDefault - value={value} - size={size} - theme="polkadot" - style={{ cursor: 'default' }} - /> - </Wrapper> -); - -export default Identicon; diff --git a/src/library/Identicon/types.ts b/src/library/Identicon/types.ts deleted file mode 100644 index 832a86ca64..0000000000 --- a/src/library/Identicon/types.ts +++ /dev/null @@ -1,7 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -export interface IdenticonProps { - value: string; - size?: number; -} diff --git a/src/library/Import/Confirm.tsx b/src/library/Import/Confirm.tsx new file mode 100644 index 0000000000..de1ee528b5 --- /dev/null +++ b/src/library/Import/Confirm.tsx @@ -0,0 +1,37 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { ButtonMono, ButtonMonoInvert, Polkicon } from '@polkadot-cloud/react'; +import { useTranslation } from 'react-i18next'; +import { usePrompt } from 'contexts/Prompt'; + +import { ConfirmWrapper } from 'library/Import/Wrappers'; +import { useOtherAccounts } from 'contexts/Connect/OtherAccounts'; +import type { ConfirmProps } from './types'; + +export const Confirm = ({ address, index, addHandler }: ConfirmProps) => { + const { t } = useTranslation('modals'); + const { setStatus } = usePrompt(); + const { addOtherAccounts } = useOtherAccounts(); + + return ( + <ConfirmWrapper> + <Polkicon address={address} size="3rem" /> + <h3>{t('importAccount')}</h3> + <h5>{address}</h5> + <div className="footer"> + <ButtonMonoInvert text={t('cancel')} onClick={() => setStatus(0)} /> + <ButtonMono + text={t('importAccount')} + onClick={() => { + const account = addHandler(address, index); + if (account) { + addOtherAccounts([account]); + } + setStatus(0); + }} + /> + </div> + </ConfirmWrapper> + ); +}; diff --git a/src/library/Import/Heading.tsx b/src/library/Import/Heading.tsx new file mode 100644 index 0000000000..4ef196a0e0 --- /dev/null +++ b/src/library/Import/Heading.tsx @@ -0,0 +1,56 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { + faChevronRight, + faCircleMinus, +} from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { ButtonText } from '@polkadot-cloud/react'; +import { useTranslation } from 'react-i18next'; +import { HeadingWrapper } from './Wrappers'; +import type { HeadingProps } from './types'; + +export const Heading = ({ + connectTo, + title, + Icon, + disabled, + handleReset, +}: HeadingProps) => { + const { t } = useTranslation('library'); + + return ( + <HeadingWrapper> + <section> + <h4> + {Icon && <Icon />} + <span> + {connectTo && ( + <> + {connectTo}{' '} + <FontAwesomeIcon icon={faChevronRight} transform="shrink-5" /> + </> + )} + {title} + </span> + </h4> + </section> + <section> + {handleReset && ( + <ButtonText + text={t('reset')} + iconLeft={faCircleMinus} + onClick={() => { + if (typeof handleReset === 'function') { + handleReset(); + } + }} + disabled={disabled || false} + marginLeft + /> + )} + </section> + </HeadingWrapper> + ); +}; diff --git a/src/library/Import/NoAccounts.tsx b/src/library/Import/NoAccounts.tsx new file mode 100644 index 0000000000..dce67c8bc5 --- /dev/null +++ b/src/library/Import/NoAccounts.tsx @@ -0,0 +1,35 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { faChevronLeft } from '@fortawesome/free-solid-svg-icons'; +import { ButtonSecondary } from '@polkadot-cloud/react'; +import { useTranslation } from 'react-i18next'; +import { useOverlay } from '@polkadot-cloud/react/hooks'; +import { NoAccountsWrapper } from './Wrappers'; + +export const NoAccounts = ({ children, text, Icon }: any) => { + const { t } = useTranslation('modals'); + const { replaceModal } = useOverlay().modal; + + return ( + <> + <div style={{ display: 'flex', padding: '1rem' }}> + <h1> + <ButtonSecondary + text={t('back')} + iconLeft={faChevronLeft} + iconTransform="shrink-3" + onClick={async () => + replaceModal({ key: 'Connect', options: { disableScroll: true } }) + } + /> + </h1> + </div> + <NoAccountsWrapper> + <Icon className="icon" /> + <h3>{text}</h3> + {children} + </NoAccountsWrapper> + </> + ); +}; diff --git a/src/library/Import/Remove.tsx b/src/library/Import/Remove.tsx new file mode 100644 index 0000000000..9728378249 --- /dev/null +++ b/src/library/Import/Remove.tsx @@ -0,0 +1,37 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { ButtonMono, ButtonMonoInvert, Polkicon } from '@polkadot-cloud/react'; +import { useTranslation } from 'react-i18next'; +import { usePrompt } from 'contexts/Prompt'; +import { ConfirmWrapper } from 'library/Import/Wrappers'; +import { useOtherAccounts } from 'contexts/Connect/OtherAccounts'; +import type { RemoveProps } from './types'; + +export const Remove = ({ address, getHandler, removeHandler }: RemoveProps) => { + const { t } = useTranslation('modals'); + const { setStatus } = usePrompt(); + const { forgetOtherAccounts } = useOtherAccounts(); + + return ( + <ConfirmWrapper> + <Polkicon address={address} size="3rem" /> + <h3>{t('removeAccount')}</h3> + <h5>{address}</h5> + <div className="footer"> + <ButtonMonoInvert text={t('cancel')} onClick={() => setStatus(0)} /> + <ButtonMono + text={t('removeAccount')} + onClick={() => { + const account = getHandler(address); + if (account) { + removeHandler(address); + forgetOtherAccounts([account]); + setStatus(0); + } + }} + /> + </div> + </ConfirmWrapper> + ); +}; diff --git a/src/library/Import/Wrappers.ts b/src/library/Import/Wrappers.ts new file mode 100644 index 0000000000..6345bce356 --- /dev/null +++ b/src/library/Import/Wrappers.ts @@ -0,0 +1,182 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import styled from 'styled-components'; + +export const HeadingWrapper = styled.div` + position: sticky; + width: 100%; + top: 0px; + padding: 1.25rem 1.25rem 0.5rem 1.25rem; + display: flex; + z-index: 3; + + > section { + flex: 1; + + &:first-child { + display: flex; + flex-grow: 1; + > h4 { + font-family: InterSemiBold, sans-serif; + padding: 0; + display: flex; + align-items: center; + > span { + color: var(--text-color-primary); + margin-right: 0.5rem; + > svg { + margin: 0 0.7rem 0 0.2rem; + } + } + > svg { + width: 1.1rem; + height: 1.1rem; + margin-right: 0.6rem; + path { + fill: var(--text-color-primary); + } + } + } + } + &:last-child { + display: flex; + justify-content: flex-end; + } + } +`; + +export const AddressesWrapper = styled.div` + --address-item-height: 7rem; + + box-sizing: content-box; + overflow: auto; + height: auto; + display: flex; + flex-direction: column; + padding: 0rem 0rem 7rem 0rem; + + .items { + display: flex; + flex-direction: column; + padding: 0 1rem; + } + + .more { + margin-top: 1rem; + padding: 0 1.5rem; + h4 { + opacity: var(--opacity-disabled); + padding: 0; + } + } +`; + +export const ConfirmWrapper = styled.div` + width: 100%; + display: flex; + flex-direction: column; + align-items: center; + padding: 1.5rem 2.5rem; + + h3, + h5, + p { + text-align: center; + } + h3 { + margin: 1.25rem 0 0.5rem 0; + } + h5 { + margin: 0.25rem 0; + } + .footer { + display: flex; + margin-top: 1rem; + + > button { + margin-right: 1rem; + &:last-child { + margin-right: 0; + } + } + } +`; + +export const QRViewerWrapper = styled.div` + width: 100%; + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; + padding: 2rem 1rem; + + .title { + color: var(--accent-color-primary); + font-family: 'Unbounded'; + margin-bottom: 1rem; + } + + .progress { + margin-bottom: 1rem; + border-radius: 1rem; + background: var(--background-menu); + padding: 0.45rem 1.5rem 0.75rem 1.5rem; + + span { + opacity: 0.4; + &.active { + opacity: 1; + } + } + .arrow { + margin: 0 0.85rem; + } + } + + .viewer { + border-radius: 1.25rem; + display: flex; + justify-content: center; + align-items: center; + position: relative; + overflow: hidden; + + &.withBorder { + padding: 0.95rem; + border: 3.75px solid var(--accent-color-pending); + } + } + .foot { + display: flex; + flex-direction: column; + align-items: center; + margin-top: 1.75rem; + padding: 0 1rem; + width: 100%; + + > div { + display: flex; + justify-content: center; + margin-top: 1rem; + width: 100%; + } + } +`; + +export const NoAccountsWrapper = styled.div` + display: flex; + flex-direction: column; + align-items: center; + padding: 2rem 0 3rem 0; + + .icon { + width: 6rem; + height: 6rem; + margin-bottom: 1rem; + } + + h3 { + margin-bottom: 1rem; + } +`; diff --git a/src/library/Import/types.ts b/src/library/Import/types.ts new file mode 100644 index 0000000000..ef4f3b8558 --- /dev/null +++ b/src/library/Import/types.ts @@ -0,0 +1,37 @@ +import type { FunctionComponent, SVGProps } from 'react'; +import type { AnyFunction } from 'types'; + +export interface HeadingProps { + connectTo?: string; + disabled?: boolean; + handleReset?: () => void; + Icon?: FunctionComponent<SVGProps<SVGSVGElement>>; + title: string; +} + +export interface AddressProps { + address: string; + index: number; + initial: string; + disableEditIfImported?: boolean; + renameHandler: AnyFunction; + existsHandler: AnyFunction; + openRemoveHandler: AnyFunction; + openConfirmHandler: AnyFunction; + t: { + tImport: string; + tRemove: string; + }; +} + +export interface ConfirmProps { + address: string; + index: number; + addHandler: AnyFunction; +} + +export interface RemoveProps { + address: string; + getHandler: AnyFunction; + removeHandler: AnyFunction; +} diff --git a/src/library/List/MotionContainer.tsx b/src/library/List/MotionContainer.tsx index fe38bdf55d..a6facfc9be 100644 --- a/src/library/List/MotionContainer.tsx +++ b/src/library/List/MotionContainer.tsx @@ -1,12 +1,14 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only import { motion } from 'framer-motion'; import React from 'react'; export const MotionContainer = ({ children, + staggerChildren = 0.015, }: { + staggerChildren?: number; children: React.ReactNode; }) => ( <motion.div @@ -17,7 +19,7 @@ export const MotionContainer = ({ show: { opacity: 1, transition: { - staggerChildren: 0.01, + staggerChildren, }, }, }} diff --git a/src/library/List/Pagination.tsx b/src/library/List/Pagination.tsx index c66a170d2a..64075eb952 100644 --- a/src/library/List/Pagination.tsx +++ b/src/library/List/Pagination.tsx @@ -1,11 +1,13 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only import { useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; import { PaginationWrapper } from '.'; -import { PaginationProps } from './types'; +import type { PaginationProps } from './types'; export const Pagination = ({ page, total, setter }: PaginationProps) => { + const { t } = useTranslation('library'); const [next, setNext] = useState<number>(page + 1 > total ? total : page + 1); const [prev, setPrev] = useState<number>(page - 1 < 1 ? 1 : page - 1); @@ -15,11 +17,9 @@ export const Pagination = ({ page, total, setter }: PaginationProps) => { }, [page, total]); return ( - <PaginationWrapper prev={page !== 1} next={page !== total}> + <PaginationWrapper $prev={page !== 1} $next={page !== total}> <div> - <h4> - Page {page} of {total} - </h4> + <h4>{t('page', { page, total })}</h4> </div> <div> <button @@ -29,7 +29,7 @@ export const Pagination = ({ page, total, setter }: PaginationProps) => { setter(prev); }} > - Prev + {t('prev')} </button> <button type="button" @@ -38,7 +38,7 @@ export const Pagination = ({ page, total, setter }: PaginationProps) => { setter(next); }} > - Next + {t('next')} </button> </div> </PaginationWrapper> diff --git a/src/library/List/SearchInput.tsx b/src/library/List/SearchInput.tsx index 71eade8dd2..77c3969b30 100644 --- a/src/library/List/SearchInput.tsx +++ b/src/library/List/SearchInput.tsx @@ -1,15 +1,20 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only import React from 'react'; +import { SearchInputWrapper } from '.'; +import type { SearchInputProps } from './types'; -export const SearchInput = ({ handleChange, placeholder }: any) => ( - <div className="search"> +export const SearchInput = ({ + handleChange, + placeholder, +}: SearchInputProps) => ( + <SearchInputWrapper> <input type="text" className="search searchbox" placeholder={placeholder} onChange={(e: React.FormEvent<HTMLInputElement>) => handleChange(e)} /> - </div> + </SearchInputWrapper> ); diff --git a/src/library/List/Selectable.tsx b/src/library/List/Selectable.tsx index da36e0385d..a96ad58358 100644 --- a/src/library/List/Selectable.tsx +++ b/src/library/List/Selectable.tsx @@ -1,53 +1,67 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { useTranslation } from 'react-i18next'; +import { useUnstaking } from 'library/Hooks/useUnstaking'; +import { ButtonMonoInvert } from '@polkadot-cloud/react'; import { SelectableWrapper } from '.'; import { useList } from './context'; +import type { SelectableProps } from './types'; -export const Selectable = ({ actionsAll, actionsSelected, canSelect }: any) => { +export const Selectable = ({ + actionsAll, + actionsSelected, + canSelect, + displayFor, +}: SelectableProps) => { + const { t } = useTranslation('library'); const provider = useList(); - // get list provider props + const { isFastUnstaking } = useUnstaking(); + + // Get list provider props. const { selectActive, setSelectActive, selected, selectToggleable } = provider; + // Determine button style depending on in canvas. Same for now, may change as design evolves. + const ButtonType = + displayFor === 'canvas' ? ButtonMonoInvert : ButtonMonoInvert; + return ( <SelectableWrapper> {selectToggleable === true ? ( - <button - type="button" - disabled={!canSelect} + <ButtonType + text={selectActive ? t('cancel') : t('select')} + disabled={!canSelect || isFastUnstaking} onClick={() => { setSelectActive(!selectActive); }} - > - {selectActive ? 'Cancel' : 'Select'} - </button> + marginRight + /> ) : null} {selected.length > 0 ? ( <> {actionsSelected.map((a: any, i: number) => ( - <button + <ButtonType key={`a_selected_${i}`} - disabled={a?.isDisabled ? a.isDisabled() : false} - type="button" + text={a.title} + disabled={ + isFastUnstaking || (a?.isDisabled ? a.isDisabled() : false) + } onClick={() => a.onClick(provider)} - > - {a.title} - </button> + marginRight + /> ))} </> ) : null} {actionsAll.map((a: any, i: number) => ( - <button + <ButtonType + text={a.title} key={`a_all_${i}`} - disabled={a?.isDisabled ? a.isDisabled() : false} - type="button" + disabled={isFastUnstaking || (a?.isDisabled ? a.isDisabled() : false)} onClick={() => a.onClick(provider)} - > - {a.icon ? <FontAwesomeIcon icon={a.icon} /> : null} - {a.title} - </button> + iconLeft={a.icon ? a.icon : undefined} + marginRight + /> ))} </SelectableWrapper> ); diff --git a/src/library/List/context.tsx b/src/library/List/context.tsx index b713255839..d99922ec46 100644 --- a/src/library/List/context.tsx +++ b/src/library/List/context.tsx @@ -1,5 +1,5 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only import React, { useState } from 'react'; import * as defaults from './defaults'; @@ -10,15 +10,17 @@ export const ListContext: React.Context<any> = React.createContext( export const useList = () => React.useContext(ListContext); -export const ListProvider = (props: any) => { - const selectToggleable = props.selectToggleable ?? true; - +export const ListProvider = ({ + selectToggleable = true, + selectActive: initialSelctActive = false, + children, +}: any) => { // store the currently selected validators from the list - const [selected, setSelected] = useState<Array<any>>([]); + const [selected, setSelected] = useState<any[]>([]); // store whether validator selection is active - const [selectActive, _setSelectActive] = useState( - props.selectActive ?? false + const [selectActive, setSelectActiveState] = useState( + initialSelctActive ?? false ); // store the list format of the list @@ -28,15 +30,16 @@ export const ListProvider = (props: any) => { setSelected([...selected].concat(_item)); }; - const removeFromSelected = (items: Array<any>) => { + const removeFromSelected = (items: any[]) => { setSelected([...selected].filter((item) => !items.includes(item))); }; const resetSelected = () => { setSelected([]); }; + const setSelectActive = (_selectActive: boolean) => { - _setSelectActive(_selectActive); + setSelectActiveState(_selectActive); if (_selectActive === false) { resetSelected(); } @@ -60,7 +63,7 @@ export const ListProvider = (props: any) => { selectToggleable, }} > - {props.children} + {children} </ListContext.Provider> ); }; diff --git a/src/library/List/defaults.ts b/src/library/List/defaults.ts index f977343cb7..72bf30b512 100644 --- a/src/library/List/defaults.ts +++ b/src/library/List/defaults.ts @@ -1,11 +1,11 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only /* eslint-disable @typescript-eslint/no-unused-vars */ export const defaultContext = { setSelectable: (_selectable: boolean) => {}, addToSelected: (item: any) => {}, - removeFromSelected: (items: Array<any>) => {}, + removeFromSelected: (items: any[]) => {}, resetSelected: () => {}, setListFormat: (v: string) => {}, selected: [], diff --git a/src/library/List/index.ts b/src/library/List/index.ts index 1eac5cfc37..cd888f458c 100644 --- a/src/library/List/index.ts +++ b/src/library/List/index.ts @@ -1,9 +1,9 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only import styled from 'styled-components'; -import { borderPrimary, networkColor, textPrimary, textSecondary } from 'theme'; -import { ListProps, PaginationWrapperProps } from './types'; +import type { DisplayFor } from 'types'; +import type { ListProps, PaginationWrapperProps } from './types'; export const Wrapper = styled.div` display: flex; @@ -11,39 +11,39 @@ export const Wrapper = styled.div` flex-flow: column nowrap; `; -export const Header = styled.div` - border-bottom: 1px solid ${borderPrimary}; +// NOTE: used for member lists and payout list only. +export const Header = styled.div<{ $displayFor?: DisplayFor }>` + border-bottom: ${(props) => + props.$displayFor === 'canvas' + ? '1px solid var(--border-secondary-color)' + : '1px solid var(--border-primary-color)'}; + display: flex; flex-flow: row wrap; justify-content: flex-end; - padding: 0 0.25rem 0.5rem 0.25rem; + padding: 0 0.25rem 0.75rem 0.25rem; flex: 1; h4 { - color: ${textSecondary}; - margin: 0; + color: var(--text-color-secondary); + font-family: InterSemiBold, sans-serif; } > div { display: flex; - flex-flow: row nowrap; align-items: center; } - > div:first-child { - justify-content: flex-start; - } - > div:last-child { justify-content: flex-end; flex: 1; button { - color: ${textSecondary}; + color: var(--text-color-secondary); font-size: 1.1rem; margin: 0 0.5rem 0 0.75rem; opacity: 0.6; - transition: all 0.2s; + transition: all var(--transition-duration); &:hover { opacity: 0.9; @@ -58,15 +58,11 @@ export const PaginationWrapper = styled.div<PaginationWrapperProps>` align-items: center; padding: 0.75rem 0.5rem; - h4 { - margin: 0; - } - > div:first-child { display: flex; - justify-content: flex-start; flex: 1; } + > div:last-child { display: flex; justify-content: flex-end; @@ -76,14 +72,20 @@ export const PaginationWrapper = styled.div<PaginationWrapperProps>` padding: 0 0.25rem; margin-left: 0.5rem; &.next { - color: ${(props) => (props.next ? networkColor : textSecondary)}; - cursor: ${(props) => (props.next ? 'pointer' : 'default')}; - opacity: ${(props) => (props.next ? 1 : 0.4)}; + color: ${(props) => + props.$next + ? 'var(--accent-color-primary)' + : 'var(--text-color-secondary)'}; + cursor: ${(props) => (props.$next ? 'pointer' : 'default')}; + opacity: ${(props) => (props.$next ? 1 : 0.4)}; } &.prev { - color: ${(props) => (props.prev ? networkColor : textSecondary)}; - cursor: ${(props) => (props.prev ? 'pointer' : 'default')}; - opacity: ${(props) => (props.prev ? 1 : 0.4)}; + color: ${(props) => + props.$prev + ? 'var(--accent-color-primary)' + : 'var(--text-color-secondary)'}; + cursor: ${(props) => (props.$prev ? 'pointer' : 'default')}; + opacity: ${(props) => (props.$prev ? 1 : 0.4)}; } } } @@ -93,61 +95,27 @@ export const SelectableWrapper = styled.div` width: 100%; display: flex; align-items: center; + padding: 0 0.15rem; + margin-top: 0.5rem; > button { - border: 1px solid ${borderPrimary}; - font-size: 1rem; - color: ${textSecondary}; - border-radius: 1rem; - padding: 0.45rem 1rem; - margin-right: 0.5rem; margin-bottom: 0.75rem; - - > svg { - margin-right: 0.5rem; - } - - &:disabled { - opacity: 0.5; - } - - &:hover { - color: ${textPrimary}; - } } `; +export const ListStatusHeader = styled.h4` + padding: 0.25rem 0.5rem; +`; + export const List = styled.div<ListProps>` - margin-top: 1rem; width: 100%; - .search { - width: 100%; - margin: 0.25rem 0 0.75rem 0; - display: flex; - flex-flow: row wrap; - - > input { - border: 1.75px solid ${borderPrimary}; - border-radius: 1.75rem; - padding: 0.75rem 1.25rem; - font-size: 1.15rem; - font-variation-settings: 'wght' 525; - &:focus { - border-width: 1.75px; - } - } - } - > div { display: flex; - flex-flow: row wrap; - justify-content: flex-start; - align-items: flex-start; + flex-wrap: wrap; > .item { display: flex; - flex-flow: row nowrap; align-items: center; overflow: hidden; @@ -163,12 +131,58 @@ export const List = styled.div<ListProps>` max-width: 50%; } @media (min-width: 1500px) { - flex-basis: ${(props) => props.flexBasisLarge}; - max-width: ${(props) => props.flexBasisLarge}; + flex-basis: ${(props) => props.$flexBasisLarge}; + max-width: ${(props) => props.$flexBasisLarge}; } } } } `; -export default List; +export const SearchInputWrapper = styled.div` + display: flex; + flex-flow: row wrap; + margin: 0.5rem 0 2rem 0; + width: 100%; + + > input { + border: 1px solid var(--border-primary-color); + color: var(--text-color-secondary); + font-family: InterBold, sans-serif; + border-radius: 1.75rem; + padding: 0.9rem 1.25rem; + font-size: 1.15rem; + width: 100%; + } +`; + +export const FilterHeaderWrapper = styled.div` + display: flex; + align-items: center; + width: 100%; + padding: 0 0.25rem; + + > div { + display: flex; + + &:first-child { + flex-grow: 1; + flex-direction: column; + } + &:last-child { + flex-shrink: 1; + + button { + color: var(--text-color-secondary); + font-size: 1.1rem; + margin: 0 0.5rem 0 0.75rem; + opacity: 0.6; + transition: all var(--transition-duration); + + &:hover { + opacity: 0.9; + } + } + } + } +`; diff --git a/src/library/List/types.ts b/src/library/List/types.ts index f0d7176c4e..7f36487d93 100644 --- a/src/library/List/types.ts +++ b/src/library/List/types.ts @@ -1,13 +1,17 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import type { AnyJson } from '@polkadot-cloud/react/types'; +import type React from 'react'; +import type { DisplayFor } from 'types'; export interface PaginationWrapperProps { - next: boolean; - prev: boolean; + $next: boolean; + $prev: boolean; } export interface ListProps { - flexBasisLarge: string; + $flexBasisLarge: string; } export interface PaginationProps { @@ -15,3 +19,15 @@ export interface PaginationProps { total: number; setter: (p: number) => void; } + +export interface SearchInputProps { + handleChange: (e: React.FormEvent<HTMLInputElement>) => void; + placeholder: string; +} + +export interface SelectableProps { + actionsAll: AnyJson[]; + actionsSelected: AnyJson[]; + canSelect: boolean; + displayFor: DisplayFor; +} diff --git a/src/library/ListItem/Labels/Blocked.tsx b/src/library/ListItem/Labels/Blocked.tsx index f94b1da53e..97d96ea768 100644 --- a/src/library/ListItem/Labels/Blocked.tsx +++ b/src/library/ListItem/Labels/Blocked.tsx @@ -1,28 +1,19 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only import { faUserSlash } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { useTranslation } from 'react-i18next'; import { useTooltip } from 'contexts/Tooltip'; -import { TooltipPosition, TooltipTrigger } from 'library/ListItem/Wrappers'; -import { useRef } from 'react'; -import { BlockedProps } from '../types'; +import { TooltipTrigger } from 'library/ListItem/Wrappers'; +import type { BlockedProps } from '../types'; -export const Blocked = (props: BlockedProps) => { - const { prefs } = props; +export const Blocked = ({ prefs }: BlockedProps) => { + const { t } = useTranslation('library'); const blocked = prefs?.blocked ?? null; - const { setTooltipPosition, setTooltipMeta, open } = useTooltip(); + const { setTooltipTextAndOpen } = useTooltip(); - const posRef = useRef(null); - - const tooltipText = 'Blocking Nominations'; - - const toggleTooltip = () => { - if (!open) { - setTooltipMeta(tooltipText); - setTooltipPosition(posRef); - } - }; + const tooltipText = t('blockingNominations'); return ( <> @@ -32,9 +23,8 @@ export const Blocked = (props: BlockedProps) => { <TooltipTrigger className="tooltip-trigger-element" data-tooltip-text={tooltipText} - onMouseMove={() => toggleTooltip()} + onMouseMove={() => setTooltipTextAndOpen(tooltipText)} /> - <TooltipPosition ref={posRef} /> <FontAwesomeIcon icon={faUserSlash} color="#d2545d" @@ -46,5 +36,3 @@ export const Blocked = (props: BlockedProps) => { </> ); }; - -export default Blocked; diff --git a/src/library/ListItem/Labels/Commission.tsx b/src/library/ListItem/Labels/Commission.tsx index aeeeba07bb..984d288613 100644 --- a/src/library/ListItem/Labels/Commission.tsx +++ b/src/library/ListItem/Labels/Commission.tsx @@ -1,34 +1,23 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only +import { useTranslation } from 'react-i18next'; import { useTooltip } from 'contexts/Tooltip'; -import { TooltipPosition, TooltipTrigger } from 'library/ListItem/Wrappers'; -import { useRef } from 'react'; +import { TooltipTrigger } from 'library/ListItem/Wrappers'; -export const Commission = (props: { commission: number }) => { - const { commission } = props; +export const Commission = ({ commission }: { commission: number }) => { + const { t } = useTranslation('library'); + const { setTooltipTextAndOpen } = useTooltip(); - const { setTooltipPosition, setTooltipMeta, open } = useTooltip(); - - const posRef = useRef<HTMLDivElement>(null); - - const tooltipText = 'Validator Commission'; - - const toggleTooltip = () => { - if (!open) { - setTooltipMeta(tooltipText); - setTooltipPosition(posRef); - } - }; + const tooltipText = t('validatorCommission'); return ( <div className="label"> <TooltipTrigger className="tooltip-trigger-element" data-tooltip-text={tooltipText} - onMouseMove={() => toggleTooltip()} + onMouseMove={() => setTooltipTextAndOpen(tooltipText)} /> - <TooltipPosition ref={posRef} /> {commission}% </div> ); diff --git a/src/library/ListItem/Labels/CopyAddress.tsx b/src/library/ListItem/Labels/CopyAddress.tsx index a914f5b3f9..06e148dda1 100644 --- a/src/library/ListItem/Labels/CopyAddress.tsx +++ b/src/library/ListItem/Labels/CopyAddress.tsx @@ -1,24 +1,23 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import { IconProp } from '@fortawesome/fontawesome-svg-core'; import { faCopy } from '@fortawesome/free-regular-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { useTranslation } from 'react-i18next'; import { useNotifications } from 'contexts/Notifications'; -import { NotificationText } from 'contexts/Notifications/types'; -import { CopyAddressProps } from '../types'; +import type { NotificationText } from 'contexts/Notifications/types'; +import type { CopyAddressProps } from '../types'; -export const CopyAddress = (props: CopyAddressProps) => { +export const CopyAddress = ({ address }: CopyAddressProps) => { + const { t } = useTranslation('library'); const { addNotification } = useNotifications(); - const { validator } = props; - const { address } = validator; // copy address notification const notificationCopyAddress: NotificationText | null = address == null ? null : { - title: 'Address Copied to Clipboard', + title: t('addressCopiedToClipboard'), subtitle: address, }; @@ -33,10 +32,8 @@ export const CopyAddress = (props: CopyAddressProps) => { navigator.clipboard.writeText(address || ''); }} > - <FontAwesomeIcon icon={faCopy as IconProp} /> + <FontAwesomeIcon icon={faCopy} transform="shrink-1" /> </button> </div> ); }; - -export default CopyAddress; diff --git a/src/library/ListItem/Labels/EraStatus.tsx b/src/library/ListItem/Labels/EraStatus.tsx index aaedab6fa5..13c84374f6 100644 --- a/src/library/ListItem/Labels/EraStatus.tsx +++ b/src/library/ListItem/Labels/EraStatus.tsx @@ -1,56 +1,33 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import { BN } from 'bn.js'; -import { useApi } from 'contexts/Api'; +import { capitalizeFirstLetter, planckToUnit } from '@polkadot-cloud/utils'; +import { useTranslation } from 'react-i18next'; import { useStaking } from 'contexts/Staking'; import { useUi } from 'contexts/UI'; import { ValidatorStatusWrapper } from 'library/ListItem/Wrappers'; -import { capitalizeFirstLetter, humanNumber, rmCommas } from 'Utils'; +import { useNetwork } from 'contexts/Network'; +import type { EraStatusProps } from '../types'; -export const EraStatus = (props: any) => { - const { address } = props; - - const { - network: { unit, units }, - } = useApi(); +export const EraStatus = ({ noMargin, status, totalStake }: EraStatusProps) => { + const { t } = useTranslation('library'); const { isSyncing } = useUi(); - const { eraStakers, erasStakersSyncing } = useStaking(); - const { stakers } = eraStakers; - - // is the validator in the active era - const validatorInEra = - stakers.find((s: any) => s.address === address) || null; - - // flag whether validator is active - - const validatorStatus = isSyncing - ? 'waiting' - : validatorInEra - ? 'active' - : 'waiting'; - - let totalStakePlanck = new BN(0); - if (validatorInEra) { - const { others, own } = validatorInEra; - others.forEach((o: any) => { - totalStakePlanck = totalStakePlanck.add(new BN(rmCommas(o.value))); - }); - if (own) { - totalStakePlanck = totalStakePlanck.add(new BN(rmCommas(own))); - } - } + const { erasStakersSyncing } = useStaking(); + const { unit, units } = useNetwork().networkData; - const totalStake = totalStakePlanck.div(new BN(10 ** units)).toNumber(); + // Fallback to `waiting` status if still syncing. + const validatorStatus = isSyncing ? 'waiting' : status; return ( - <ValidatorStatusWrapper status={validatorStatus}> + <ValidatorStatusWrapper $status={validatorStatus} $noMargin={noMargin}> <h5> {isSyncing || erasStakersSyncing - ? 'Syncing...' - : validatorInEra - ? `Active / ${humanNumber(totalStake)} ${unit}` - : capitalizeFirstLetter(validatorStatus ?? '')} + ? t('syncing') + : validatorStatus !== 'waiting' + ? `${t('listItemActive')} / ${planckToUnit(totalStake, units) + .integerValue() + .toFormat()} ${unit}` + : capitalizeFirstLetter(t(`${validatorStatus}`) ?? '')} </h5> </ValidatorStatusWrapper> ); diff --git a/src/library/ListItem/Labels/FavoritePool.tsx b/src/library/ListItem/Labels/FavoritePool.tsx index 23666f58f6..3ce62dbd63 100644 --- a/src/library/ListItem/Labels/FavoritePool.tsx +++ b/src/library/ListItem/Labels/FavoritePool.tsx @@ -1,52 +1,44 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import { IconProp } from '@fortawesome/fontawesome-svg-core'; import { faHeart as faHeartRegular } from '@fortawesome/free-regular-svg-icons'; import { faHeart } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { useTranslation } from 'react-i18next'; import { useNotifications } from 'contexts/Notifications'; import { usePoolsConfig } from 'contexts/Pools/PoolsConfig'; import { useTooltip } from 'contexts/Tooltip'; -import { TooltipPosition, TooltipTrigger } from 'library/ListItem/Wrappers'; -import { useRef } from 'react'; -import { FavoriteProps } from '../types'; +import { TooltipTrigger } from 'library/ListItem/Wrappers'; +import type { FavoriteProps } from '../types'; -export const FavoritePool = (props: FavoriteProps) => { +export const FavoritePool = ({ address }: FavoriteProps) => { + const { t } = useTranslation('library'); const { addNotification } = useNotifications(); + const { setTooltipTextAndOpen } = useTooltip(); const { favorites, addFavorite, removeFavorite } = usePoolsConfig(); - const { setTooltipPosition, setTooltipMeta, open } = useTooltip(); - const { address } = props; const isFavorite = favorites.includes(address); const notificationFavorite = !isFavorite ? { - title: 'Favorite Pool Added', + title: t('favoritePoolAdded'), subtitle: address, } : { - title: 'Favorite Pool Removed', + title: t('favoritePoolRemoved'), subtitle: address, }; - const posRef = useRef<HTMLDivElement>(null); - - const tooltipText = `${isFavorite ? `Remove` : `Add`} Favorite`; - - const toggleTooltip = () => { - if (!open) { - setTooltipMeta(tooltipText); - setTooltipPosition(posRef); - } - }; + const tooltipText = `${isFavorite ? `${t('remove')}` : `${t('add')}`} ${t( + 'favorite' + )}`; return ( - <div className="label pool"> + <div className="label"> <TooltipTrigger className="tooltip-trigger-element as-button" data-tooltip-text={tooltipText} - onMouseMove={() => toggleTooltip()} + onMouseMove={() => setTooltipTextAndOpen(tooltipText)} onClick={() => { if (isFavorite) { removeFavorite(address); @@ -56,12 +48,9 @@ export const FavoritePool = (props: FavoriteProps) => { addNotification(notificationFavorite); }} /> - <TooltipPosition ref={posRef} /> <button type="button" className={isFavorite ? 'active' : undefined}> <FontAwesomeIcon - icon={ - !isFavorite ? (faHeartRegular as IconProp) : (faHeart as IconProp) - } + icon={!isFavorite ? faHeartRegular : faHeart} transform="shrink-2" /> </button> diff --git a/src/library/ListItem/Labels/FavoriteValidator.tsx b/src/library/ListItem/Labels/FavoriteValidator.tsx index c1033a229b..043c2bf91c 100644 --- a/src/library/ListItem/Labels/FavoriteValidator.tsx +++ b/src/library/ListItem/Labels/FavoriteValidator.tsx @@ -1,52 +1,44 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import { IconProp } from '@fortawesome/fontawesome-svg-core'; import { faHeart as faHeartRegular } from '@fortawesome/free-regular-svg-icons'; import { faHeart } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { useTranslation } from 'react-i18next'; import { useNotifications } from 'contexts/Notifications'; import { useTooltip } from 'contexts/Tooltip'; -import { useValidators } from 'contexts/Validators'; -import { TooltipPosition, TooltipTrigger } from 'library/ListItem/Wrappers'; -import { useRef } from 'react'; -import { FavoriteProps } from '../types'; +import { TooltipTrigger } from 'library/ListItem/Wrappers'; +import { useFavoriteValidators } from 'contexts/Validators/FavoriteValidators'; +import type { FavoriteProps } from '../types'; -export const FavoriteValidator = (props: FavoriteProps) => { +export const FavoriteValidator = ({ address }: FavoriteProps) => { + const { t } = useTranslation('library'); + const { setTooltipTextAndOpen } = useTooltip(); const { addNotification } = useNotifications(); - const { favorites, addFavorite, removeFavorite } = useValidators(); - const { setTooltipPosition, setTooltipMeta, open } = useTooltip(); + const { favorites, addFavorite, removeFavorite } = useFavoriteValidators(); - const { address } = props; const isFavorite = favorites.includes(address); const notificationFavorite = !isFavorite ? { - title: 'Favorite Validator Added', + title: t('favoriteValidatorAdded'), subtitle: address, } : { - title: 'Favorite Validator Removed', + title: t('favoriteValidatorRemoved'), subtitle: address, }; - const posRef = useRef<HTMLDivElement>(null); - - const tooltipText = `${isFavorite ? `Remove` : `Add`} Favorite`; - - const toggleTooltip = () => { - if (!open) { - setTooltipMeta(tooltipText); - setTooltipPosition(posRef); - } - }; + const tooltipText = `${isFavorite ? `${t('remove')}` : `${t('add')}`} ${t( + 'favorite' + )}`; return ( <div className="label"> <TooltipTrigger className="tooltip-trigger-element as-button" data-tooltip-text={tooltipText} - onMouseMove={() => toggleTooltip()} + onMouseMove={() => setTooltipTextAndOpen(tooltipText)} onClick={() => { if (isFavorite) { removeFavorite(address); @@ -56,12 +48,9 @@ export const FavoriteValidator = (props: FavoriteProps) => { addNotification(notificationFavorite); }} /> - <TooltipPosition ref={posRef} /> <button type="button" className={isFavorite ? 'active' : undefined}> <FontAwesomeIcon - icon={ - !isFavorite ? (faHeartRegular as IconProp) : (faHeart as IconProp) - } + icon={!isFavorite ? faHeartRegular : faHeart} transform="shrink-2" /> </button> diff --git a/src/library/ListItem/Labels/Identity.tsx b/src/library/ListItem/Labels/Identity.tsx index 627909d2ab..289fe5694c 100644 --- a/src/library/ListItem/Labels/Identity.tsx +++ b/src/library/ListItem/Labels/Identity.tsx @@ -1,32 +1,28 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import Identicon from 'library/Identicon'; +import { ellipsisFn } from '@polkadot-cloud/utils'; +import { useEffect, useState } from 'react'; +import { useValidators } from 'contexts/Validators/ValidatorEntries'; +import { Polkicon } from '@polkadot-cloud/react'; import { IdentityWrapper } from 'library/ListItem/Wrappers'; -import { clipAddress } from 'Utils'; -import { getIdentityDisplay } from '../../ValidatorList/Validator/Utils'; -import { IdentityProps } from '../types'; +import { getIdentityDisplay } from '../../ValidatorList/ValidatorItem/Utils'; +import type { IdentityProps } from '../types'; -export const Identity = (props: IdentityProps) => { - const { address, batchKey, batchIndex, meta } = props; - const identities = meta[batchKey]?.identities ?? []; - const supers = meta[batchKey]?.supers ?? []; - const stake = meta[batchKey]?.stake ?? []; +export const Identity = ({ address }: IdentityProps) => { + const { validatorIdentities, validatorSupers, validatorsFetched } = + useValidators(); - // aggregate synced status - const identitiesSynced = identities.length > 0 ?? false; - const supersSynced = supers.length > 0 ?? false; - - const synced = { - identities: identitiesSynced && supersSynced, - stake: stake.length > 0 ?? false, - }; - - const display = getIdentityDisplay( - identities[batchIndex], - supers[batchIndex] + const [display, setDisplay] = useState( + getIdentityDisplay(validatorIdentities[address], validatorSupers[address]) ); + useEffect(() => { + setDisplay( + getIdentityDisplay(validatorIdentities[address], validatorSupers[address]) + ); + }, [validatorSupers, validatorIdentities, address]); + return ( <IdentityWrapper className="identity" @@ -34,12 +30,12 @@ export const Identity = (props: IdentityProps) => { animate={{ opacity: 1 }} transition={{ duration: 0.3 }} > - <Identicon value={address} size={24} /> + <Polkicon address={address} size="2rem" /> <div className="inner"> - {synced.identities && display !== null ? ( + {validatorsFetched && display !== null ? ( <h4>{display}</h4> ) : ( - <h4>{clipAddress(address)}</h4> + <h4>{ellipsisFn(address, 6)}</h4> )} </div> </IdentityWrapper> diff --git a/src/library/ListItem/Labels/JoinPool.tsx b/src/library/ListItem/Labels/JoinPool.tsx index f2e3ae19c7..11c96c0cad 100644 --- a/src/library/ListItem/Labels/JoinPool.tsx +++ b/src/library/ListItem/Labels/JoinPool.tsx @@ -1,30 +1,37 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only import { faCaretRight } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { useModal } from 'contexts/Modal'; +import { useTranslation } from 'react-i18next'; +import { useOverlay } from '@polkadot-cloud/react/hooks'; -export const JoinPool = (props: { id: number; setActiveTab: any }) => { - const { id, setActiveTab } = props; - const { openModalWith } = useModal(); +export const JoinPool = ({ + id, + setActiveTab, +}: { + id: number; + setActiveTab: any; +}) => { + const { t } = useTranslation('library'); + const { openModal } = useOverlay().modal; return ( <div className="label button-with-text"> <button type="button" onClick={() => { - openModalWith( - 'JoinPool', - { + openModal({ + key: 'JoinPool', + options: { id, setActiveTab, }, - 'small' - ); + size: 'sm', + }); }} > - Join + {t('join')} <FontAwesomeIcon icon={faCaretRight} transform="shrink-2" /> </button> </div> diff --git a/src/library/ListItem/Labels/Members.tsx b/src/library/ListItem/Labels/Members.tsx index b0c1d3c724..bf7d08db70 100644 --- a/src/library/ListItem/Labels/Members.tsx +++ b/src/library/ListItem/Labels/Members.tsx @@ -1,34 +1,25 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only import { faUsers } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { useTranslation } from 'react-i18next'; import { useTooltip } from 'contexts/Tooltip'; -import { TooltipPosition, TooltipTrigger } from 'library/ListItem/Wrappers'; -import { useRef } from 'react'; +import { TooltipTrigger } from 'library/ListItem/Wrappers'; -export const Members = (props: { members: string }) => { - const { members } = props; +export const Members = ({ members }: { members: string }) => { + const { t } = useTranslation('library'); + const { setTooltipTextAndOpen } = useTooltip(); - const { setTooltipPosition, setTooltipMeta, open } = useTooltip(); - const posRef = useRef<HTMLDivElement>(null); - const tooltipText = 'Pool Members'; - - const toggleTooltip = () => { - if (!open) { - setTooltipMeta(tooltipText); - setTooltipPosition(posRef); - } - }; + const tooltipText = t('poolMembers'); return ( <div className="label pool"> <TooltipTrigger className="tooltip-trigger-element" data-tooltip-text={tooltipText} - onMouseMove={() => toggleTooltip()} + onMouseMove={() => setTooltipTextAndOpen(tooltipText)} /> - <TooltipPosition ref={posRef} /> <FontAwesomeIcon icon={faUsers} />  {members} </div> diff --git a/src/library/ListItem/Labels/Metrics.tsx b/src/library/ListItem/Labels/Metrics.tsx index afd219619a..dabfe8a7ad 100644 --- a/src/library/ListItem/Labels/Metrics.tsx +++ b/src/library/ListItem/Labels/Metrics.tsx @@ -1,29 +1,26 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only import { faChartLine } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { useModal } from 'contexts/Modal'; -import { MetricsProps } from '../types'; +import { useOverlay } from '@polkadot-cloud/react/hooks'; +import type { MetricsProps } from '../types'; -export const Metrics = (props: MetricsProps) => { - const { openModalWith } = useModal(); - - const { display, address } = props; +export const Metrics = ({ display, address }: MetricsProps) => { + const { openModal } = useOverlay().modal; return ( <div className="label"> <button type="button" onClick={() => - openModalWith( - 'ValidatorMetrics', - { + openModal({ + key: 'ValidatorMetrics', + options: { address, identity: display, }, - 'large' - ) + }) } > <FontAwesomeIcon icon={faChartLine} transform="shrink-2" /> @@ -31,5 +28,3 @@ export const Metrics = (props: MetricsProps) => { </div> ); }; - -export default Metrics; diff --git a/src/library/ListItem/Labels/NominationStatus.tsx b/src/library/ListItem/Labels/NominationStatus.tsx index b32f11ac08..09e3a50adf 100644 --- a/src/library/ListItem/Labels/NominationStatus.tsx +++ b/src/library/ListItem/Labels/NominationStatus.tsx @@ -1,67 +1,58 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import { BN } from 'bn.js'; -import { useApi } from 'contexts/Api'; -import { useBondedPools } from 'contexts/Pools/BondedPools'; +import { greaterThanZero, planckToUnit } from '@polkadot-cloud/utils'; +import BigNumber from 'bignumber.js'; +import { useTranslation } from 'react-i18next'; import { useStaking } from 'contexts/Staking'; import { ValidatorStatusWrapper } from 'library/ListItem/Wrappers'; -import { - capitalizeFirstLetter, - humanNumber, - planckBnToUnit, - rmCommas, -} from 'Utils'; -import { NominationStatusProps } from '../types'; +import { useNetwork } from 'contexts/Network'; +import type { NominationStatusProps } from '../types'; -export const NominationStatus = (props: NominationStatusProps) => { - const { getNominationsStatus, eraStakers, erasStakersSyncing } = useStaking(); - const { getPoolNominationStatus } = useBondedPools(); +export const NominationStatus = ({ + address, + nominator, + bondFor, + noMargin = false, + status, +}: NominationStatusProps) => { + const { t } = useTranslation('library'); const { - network: { unit, units }, - } = useApi(); - - const { ownStake, stakers } = eraStakers; - const { address, nominator, bondType } = props; - - let nominationStatus; - if (bondType === 'pool') { - // get nomination status from pool metadata - nominationStatus = getPoolNominationStatus(nominator, address); - } else { - // get all active account's nominations. - const nominationStatuses = getNominationsStatus(); - // find the nominator status within the returned nominations. - nominationStatus = nominationStatuses[address]; - } + networkData: { unit, units }, + } = useNetwork(); + const { + eraStakers: { activeAccountOwnStake, stakers }, + erasStakersSyncing, + } = useStaking(); // determine staked amount - let stakedAmount = 0; - if (bondType === 'stake') { + let stakedAmount = new BigNumber(0); + if (bondFor === 'nominator') { // bonded amount within the validator. stakedAmount = - nominationStatus === 'active' - ? ownStake?.find((_own: any) => _own.address)?.value ?? 0 - : 0; + status === 'active' + ? new BigNumber( + activeAccountOwnStake?.find((own) => own.address)?.value ?? 0 + ) + : new BigNumber(0); } else { - const s = stakers?.find((_n: any) => _n.address === address); - const exists = (s?.others ?? []).find((_o: any) => _o.who === nominator); + const staker = stakers?.find((s) => s.address === address); + const exists = (staker?.others || []).find(({ who }) => who === nominator); if (exists) { - stakedAmount = planckBnToUnit(new BN(rmCommas(exists.value)), units); + stakedAmount = planckToUnit(new BigNumber(exists.value), units); } } return ( - <ValidatorStatusWrapper status={nominationStatus}> + <ValidatorStatusWrapper $status={status || 'waiting'} $noMargin={noMargin}> <h5> - {capitalizeFirstLetter(nominationStatus ?? '')} - {stakedAmount > 0 && - ` / ${ - erasStakersSyncing ? '...' : `${humanNumber(stakedAmount)} ${unit}` - }`} + {t(`${status || 'waiting'}`)} + {greaterThanZero(stakedAmount) + ? ` / ${ + erasStakersSyncing ? '...' : `${stakedAmount.toFormat()} ${unit}` + }` + : null} </h5> </ValidatorStatusWrapper> ); }; - -export default NominationStatus; diff --git a/src/library/ListItem/Labels/Oversubscribed.tsx b/src/library/ListItem/Labels/Oversubscribed.tsx index 1d04da9c86..ebb935fd65 100644 --- a/src/library/ListItem/Labels/Oversubscribed.tsx +++ b/src/library/ListItem/Labels/Oversubscribed.tsx @@ -1,58 +1,39 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only import { faExclamationTriangle } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { useApi } from 'contexts/Api'; -import { useTooltip } from 'contexts/Tooltip'; -import { useValidators } from 'contexts/Validators'; import { motion } from 'framer-motion'; +import { useTranslation } from 'react-i18next'; +import { MinBondPrecision } from 'consts'; +import { useTooltip } from 'contexts/Tooltip'; import { OverSubscribedWrapper, - TooltipPosition, TooltipTrigger, } from 'library/ListItem/Wrappers'; -import { useRef } from 'react'; -import { OversubscribedProps } from '../types'; - -export const Oversubscribed = (props: OversubscribedProps) => { - const { consts, network } = useApi(); - const { meta } = useValidators(); - const { setTooltipPosition, setTooltipMeta, open } = useTooltip(); - - const { batchIndex, batchKey } = props; - - const identities = meta[batchKey]?.identities ?? []; - const supers = meta[batchKey]?.supers ?? []; - const stake = meta[batchKey]?.stake ?? []; +import { useStaking } from 'contexts/Staking'; +import { useNetwork } from 'contexts/Network'; +import type { OversubscribedProps } from '../types'; - // aggregate synced status - const identitiesSynced = identities.length > 0 ?? false; - const supersSynced = supers.length > 0 ?? false; +export const Oversubscribed = ({ address }: OversubscribedProps) => { + const { t } = useTranslation('library'); + const { + networkData: { unit }, + } = useNetwork(); + const { setTooltipTextAndOpen } = useTooltip(); + const { erasStakersSyncing, getLowestRewardFromStaker } = useStaking(); - const synced = { - identities: identitiesSynced && supersSynced, - stake: stake.length > 0 ?? false, - }; + const { lowest, oversubscribed } = getLowestRewardFromStaker(address); - const eraStakers = stake[batchIndex]; + const displayOversubscribed = !erasStakersSyncing && oversubscribed; - const totalNominations = eraStakers?.total_nominations ?? 0; - const lowestReward = eraStakers?.lowestReward ?? 0; + const lowestRewardFormatted = lowest + .decimalPlaces(MinBondPrecision) + .toFormat(); - const displayOversubscribed = - synced.stake && totalNominations >= consts.maxNominatorRewardedPerValidator; - - const posRef = useRef(null); - - const tooltipText = `Over subscribed: Minimum reward bond is ${lowestReward} ${network.unit}`; - - const toggleTooltip = () => { - if (!open) { - setTooltipMeta(tooltipText); - setTooltipPosition(posRef); - } - }; + const tooltipText = `${t( + 'overSubscribedMinReward' + )} ${lowestRewardFormatted} ${unit}`; return ( <> @@ -66,9 +47,8 @@ export const Oversubscribed = (props: OversubscribedProps) => { <TooltipTrigger className="tooltip-trigger-element" data-tooltip-text={tooltipText} - onMouseMove={() => toggleTooltip()} + onMouseMove={() => setTooltipTextAndOpen(tooltipText)} /> - <TooltipPosition ref={posRef} /> <OverSubscribedWrapper> <span className="warning"> <FontAwesomeIcon @@ -77,7 +57,7 @@ export const Oversubscribed = (props: OversubscribedProps) => { className="warning" /> </span> - {lowestReward} {network.unit} + {lowestRewardFormatted} {unit} </OverSubscribedWrapper> </div> </motion.div> @@ -85,5 +65,3 @@ export const Oversubscribed = (props: OversubscribedProps) => { </> ); }; - -export default Oversubscribed; diff --git a/src/library/ListItem/Labels/ParaValidator.tsx b/src/library/ListItem/Labels/ParaValidator.tsx index 4b3cf55499..0b83068372 100644 --- a/src/library/ListItem/Labels/ParaValidator.tsx +++ b/src/library/ListItem/Labels/ParaValidator.tsx @@ -1,30 +1,22 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only import { faCubes } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { useTranslation } from 'react-i18next'; import { useTooltip } from 'contexts/Tooltip'; -import { useValidators } from 'contexts/Validators'; -import { TooltipPosition, TooltipTrigger } from 'library/ListItem/Wrappers'; -import { useRef } from 'react'; -import { ParaValidatorProps } from '../types'; +import { useValidators } from 'contexts/Validators/ValidatorEntries'; +import { TooltipTrigger } from 'library/ListItem/Wrappers'; +import type { ParaValidatorProps } from '../types'; export const ParaValidator = ({ address }: ParaValidatorProps) => { - const { sessionParachain } = useValidators(); - const { setTooltipPosition, setTooltipMeta, open } = useTooltip(); + const { t } = useTranslation('library'); + const { sessionParaValidators } = useValidators(); + const { setTooltipTextAndOpen } = useTooltip(); - const posRef = useRef(null); + const tooltipText = t('validatingParachainBlocks'); - const tooltipText = 'Validating Parachain Blocks'; - - const toggleTooltip = () => { - if (!open) { - setTooltipMeta(tooltipText); - setTooltipPosition(posRef); - } - }; - - if (!sessionParachain?.includes(address || '')) { + if (!sessionParaValidators?.includes(address || '')) { return <></>; } @@ -33,9 +25,8 @@ export const ParaValidator = ({ address }: ParaValidatorProps) => { <TooltipTrigger className="tooltip-trigger-element" data-tooltip-text={tooltipText} - onMouseMove={() => toggleTooltip()} + onMouseMove={() => setTooltipTextAndOpen(tooltipText)} /> - <TooltipPosition ref={posRef} /> <FontAwesomeIcon icon={faCubes} transform="shrink-1" /> </div> ); diff --git a/src/library/ListItem/Labels/PoolBonded.tsx b/src/library/ListItem/Labels/PoolBonded.tsx index 763468c731..e2a12a473c 100644 --- a/src/library/ListItem/Labels/PoolBonded.tsx +++ b/src/library/ListItem/Labels/PoolBonded.tsx @@ -1,20 +1,19 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import BN from 'bn.js'; -import { useApi } from 'contexts/Api'; -import { useBondedPools } from 'contexts/Pools/BondedPools'; -import { useStaking } from 'contexts/Staking'; -import { ValidatorStatusWrapper } from 'library/ListItem/Wrappers'; -import { Pool } from 'library/Pool/types'; -import { useEffect, useState } from 'react'; import { capitalizeFirstLetter, - humanNumber, - planckBnToUnit, + planckToUnit, rmCommas, - toFixedIfNecessary, -} from 'Utils'; +} from '@polkadot-cloud/utils'; +import BigNumber from 'bignumber.js'; +import { useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useBondedPools } from 'contexts/Pools/BondedPools'; +import { useStaking } from 'contexts/Staking'; +import { ValidatorStatusWrapper } from 'library/ListItem/Wrappers'; +import type { Pool } from 'library/Pool/types'; +import { useNetwork } from 'contexts/Network'; export const PoolBonded = ({ pool, @@ -25,31 +24,29 @@ export const PoolBonded = ({ batchKey: string; batchIndex: number; }) => { - const { addresses, points } = pool; - - const { network } = useApi(); - const { eraStakers, getNominationsStatusFromTargets } = useStaking(); + const { t } = useTranslation('library'); + const { + networkData: { units, unit }, + } = useNetwork(); const { meta, getPoolNominationStatusCode } = useBondedPools(); - const { units, unit } = network; + const { eraStakers, getNominationsStatusFromTargets } = useStaking(); + const { addresses, points } = pool; // get pool targets from nominations meta batch const nominations = meta[batchKey]?.nominations ?? []; const targets = nominations[batchIndex]?.targets ?? []; // store nomination status in state - const [nominationsStatus, setNominationsStatus] = useState<{ - [key: string]: string; - } | null>(null); + const [nominationsStatus, setNominationsStatus] = + useState<Record<string, string>>(); // update pool nomination status as nominations metadata becomes available. // we cannot add effect dependencies here as this needs to trigger // as soon as the component displays. (upon tab change). const handleNominationsStatus = () => { - const _nominationStatus = getNominationsStatusFromTargets( - addresses.stash, - targets + setNominationsStatus( + getNominationsStatusFromTargets(addresses.stash, targets) ); - setNominationsStatus(_nominationStatus); }; // recalculate nominations status as app syncs @@ -71,22 +68,24 @@ export const PoolBonded = ({ }, [meta, pool, eraStakers.stakers.length]); // calculate total bonded pool amount - const poolBonded = planckBnToUnit(new BN(rmCommas(points)), units); + const poolBonded = planckToUnit(new BigNumber(rmCommas(points)), units); // determine nominations status and display - const nominationStatus = getPoolNominationStatusCode(nominationsStatus); + const nominationStatus = getPoolNominationStatusCode( + nominationsStatus || null + ); return ( <> - <ValidatorStatusWrapper status={nominationStatus}> + <ValidatorStatusWrapper $status={nominationStatus} $noMargin> <h5> {nominationStatus === null || !eraStakers.stakers.length - ? `Syncing...` + ? `${t('syncing')}...` : targets.length - ? capitalizeFirstLetter(nominationStatus ?? '') - : 'Not Nominating'} + ? capitalizeFirstLetter(t(`${nominationStatus}`) ?? '') + : t('notNominating')} {' / '} - Bonded: {humanNumber(toFixedIfNecessary(poolBonded, 3))} {unit} + {t('bonded')}: {poolBonded.decimalPlaces(3).toFormat()} {unit} </h5> </ValidatorStatusWrapper> </> diff --git a/src/library/ListItem/Labels/PoolCommission.tsx b/src/library/ListItem/Labels/PoolCommission.tsx new file mode 100644 index 0000000000..51b56b83c4 --- /dev/null +++ b/src/library/ListItem/Labels/PoolCommission.tsx @@ -0,0 +1,28 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { useTranslation } from 'react-i18next'; +import { useTooltip } from 'contexts/Tooltip'; +import { TooltipTrigger } from 'library/ListItem/Wrappers'; + +export const PoolCommission = ({ commission }: { commission: string }) => { + const { t } = useTranslation('library'); + const { setTooltipTextAndOpen } = useTooltip(); + + const tooltipText = t('poolCommission'); + + if (!commission) { + return null; + } + + return ( + <div className="label"> + <TooltipTrigger + className="tooltip-trigger-element" + data-tooltip-text={tooltipText} + onMouseMove={() => setTooltipTextAndOpen(tooltipText)} + /> + {commission} + </div> + ); +}; diff --git a/src/library/ListItem/Labels/PoolId.tsx b/src/library/ListItem/Labels/PoolId.tsx index 896302e701..97da6b9600 100644 --- a/src/library/ListItem/Labels/PoolId.tsx +++ b/src/library/ListItem/Labels/PoolId.tsx @@ -1,34 +1,25 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only import { faHashtag } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { useTranslation } from 'react-i18next'; import { useTooltip } from 'contexts/Tooltip'; -import { TooltipPosition, TooltipTrigger } from 'library/ListItem/Wrappers'; -import { useRef } from 'react'; +import { TooltipTrigger } from 'library/ListItem/Wrappers'; -export const PoolId = (props: { id: number }) => { - const { id } = props; +export const PoolId = ({ id }: { id: number }) => { + const { t } = useTranslation('library'); + const { setTooltipTextAndOpen } = useTooltip(); - const { setTooltipPosition, setTooltipMeta, open } = useTooltip(); - const posRef = useRef<HTMLDivElement>(null); - const tooltipText = 'Pool ID'; - - const toggleTooltip = () => { - if (!open) { - setTooltipMeta(tooltipText); - setTooltipPosition(posRef); - } - }; + const tooltipText = t('poolId'); return ( <div className="label pool"> <TooltipTrigger className="tooltip-trigger-element" data-tooltip-text={tooltipText} - onMouseMove={() => toggleTooltip()} + onMouseMove={() => setTooltipTextAndOpen(tooltipText)} /> - <TooltipPosition ref={posRef} /> <FontAwesomeIcon icon={faHashtag} />  {id} </div> diff --git a/src/library/ListItem/Labels/PoolIdentity.tsx b/src/library/ListItem/Labels/PoolIdentity.tsx index d18dc89124..b9111f1d6d 100644 --- a/src/library/ListItem/Labels/PoolIdentity.tsx +++ b/src/library/ListItem/Labels/PoolIdentity.tsx @@ -1,16 +1,18 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only +import { ellipsisFn, determinePoolDisplay } from '@polkadot-cloud/utils'; import { useBondedPools } from 'contexts/Pools/BondedPools'; -import Identicon from 'library/Identicon'; +import { Polkicon } from '@polkadot-cloud/react'; import { IdentityWrapper } from 'library/ListItem/Wrappers'; -import { clipAddress, determinePoolDisplay } from 'Utils'; -import { PoolIdentityProps } from '../types'; +import type { PoolIdentityProps } from '../types'; -export const PoolIdentity = (props: PoolIdentityProps) => { +export const PoolIdentity = ({ + pool, + batchKey, + batchIndex, +}: PoolIdentityProps) => { const { meta } = useBondedPools(); - - const { pool, batchKey, batchIndex } = props; const { addresses } = pool; // get metadata from pools metabatch @@ -24,10 +26,10 @@ export const PoolIdentity = (props: PoolIdentityProps) => { return ( <IdentityWrapper className="identity"> - <Identicon value={addresses.stash} size={26} /> + <Polkicon address={addresses.stash} size="2rem" /> <div className="inner"> {!metadataSynced ? ( - <h4>{clipAddress(addresses.stash)}</h4> + <h4>{ellipsisFn(addresses.stash)}</h4> ) : ( <h4>{display}</h4> )} diff --git a/src/library/ListItem/Labels/PoolMemberBonded.tsx b/src/library/ListItem/Labels/PoolMemberBonded.tsx index cce15687e6..76e90bca17 100644 --- a/src/library/ListItem/Labels/PoolMemberBonded.tsx +++ b/src/library/ListItem/Labels/PoolMemberBonded.tsx @@ -1,65 +1,60 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import BN from 'bn.js'; -import { useApi } from 'contexts/Api'; +import { greaterThanZero, planckToUnit, rmCommas } from '@polkadot-cloud/utils'; +import BigNumber from 'bignumber.js'; +import { useTranslation } from 'react-i18next'; import { ValidatorStatusWrapper } from 'library/ListItem/Wrappers'; -import { - humanNumber, - planckBnToUnit, - rmCommas, - toFixedIfNecessary, -} from 'Utils'; +import { useNetwork } from 'contexts/Network'; -export const PoolMemberBonded = (props: any) => { - const { meta, batchKey, batchIndex } = props; - const { network } = useApi(); - const { units, unit } = network; +export const PoolMemberBonded = ({ meta, batchKey, batchIndex }: any) => { + const { t } = useTranslation('library'); + const { units, unit } = useNetwork().networkData; const poolMembers = meta[batchKey]?.poolMembers ?? []; const poolMember = poolMembers[batchIndex] ?? null; - let bonded = 0; + let bonded = new BigNumber(0); + let totalUnbonding = new BigNumber(0); + let status = ''; - let totalUnbonding = 0; if (poolMember) { const { points, unbondingEras } = poolMember; - bonded = planckBnToUnit(new BN(rmCommas(points)), units); - status = bonded > 0 ? 'active' : 'inactive'; + bonded = planckToUnit(new BigNumber(rmCommas(points)), units); + status = greaterThanZero(bonded) ? 'active' : 'inactive'; // converting unbonding eras from points to units - let totalUnbondingBase: BN = new BN(0); + let totalUnbondingUnit = new BigNumber(0); Object.values(unbondingEras).forEach((amount: any) => { - const amountBn: BN = new BN(rmCommas(amount)); - totalUnbondingBase = totalUnbondingBase.add(amountBn); + const amountBn = new BigNumber(rmCommas(amount)); + totalUnbondingUnit = totalUnbondingUnit.plus(amountBn); }); - totalUnbonding = planckBnToUnit(new BN(totalUnbondingBase), network.units); + totalUnbonding = planckToUnit(new BigNumber(totalUnbondingUnit), units); } return ( <> {!poolMember ? ( - <ValidatorStatusWrapper status="inactive"> - <h5>Syncing...</h5> + <ValidatorStatusWrapper $status="inactive"> + <h5>{t('syncing')}...</h5> </ValidatorStatusWrapper> ) : ( <> - {bonded > 0 && ( - <ValidatorStatusWrapper status={status}> + {greaterThanZero(bonded) && ( + <ValidatorStatusWrapper $status={status}> <h5> - Bonded: {humanNumber(toFixedIfNecessary(bonded, 3))} {unit} + {t('bonded')}: {bonded.decimalPlaces(3).toFormat()} {unit} </h5> </ValidatorStatusWrapper> )} </> )} - {poolMember && totalUnbonding > 0 && ( - <ValidatorStatusWrapper status="inactive"> + {poolMember && greaterThanZero(totalUnbonding) && ( + <ValidatorStatusWrapper $status="inactive"> <h5> - Unbonding {humanNumber(toFixedIfNecessary(totalUnbonding, 3))}{' '} - {unit} + {t('unbonding')} {totalUnbonding.decimalPlaces(3).toFormat()} {unit} </h5> </ValidatorStatusWrapper> )} diff --git a/src/library/ListItem/Labels/Quartile.tsx b/src/library/ListItem/Labels/Quartile.tsx new file mode 100644 index 0000000000..c6442f2dbd --- /dev/null +++ b/src/library/ListItem/Labels/Quartile.tsx @@ -0,0 +1,35 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { useValidators } from 'contexts/Validators/ValidatorEntries'; +import { MaxEraRewardPointsEras } from 'consts'; +import { useTooltip } from 'contexts/Tooltip'; +import { useTranslation } from 'react-i18next'; + +export const Quartile = ({ address }: { address: string }) => { + const { t } = useTranslation(); + const { setTooltipTextAndOpen } = useTooltip(); + const { validatorEraPointsHistory, erasRewardPointsFetched } = + useValidators(); + + const quartile = validatorEraPointsHistory[address]?.quartile; + const tooltipText = `${t('dayPerformanceStanding', { + count: MaxEraRewardPointsEras, + ns: 'library', + })}`; + + if (erasRewardPointsFetched !== 'synced') return null; + + return ( + <div + className="label tooltip-trigger-element" + data-tooltip-text={tooltipText} + onMouseMove={() => setTooltipTextAndOpen(tooltipText)} + style={{ cursor: 'default' }} + > + {![100, undefined].includes(quartile) + ? `${t('top', { ns: 'library' })} ${quartile}%` + : ``} + </div> + ); +}; diff --git a/src/library/ListItem/Labels/Select.tsx b/src/library/ListItem/Labels/Select.tsx index 6fe7ce5a0b..45e56f5722 100644 --- a/src/library/ListItem/Labels/Select.tsx +++ b/src/library/ListItem/Labels/Select.tsx @@ -1,19 +1,13 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import { IconProp } from '@fortawesome/fontawesome-svg-core'; import { faCheck } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { SelectWrapper } from 'library/ListItem/Wrappers'; -import { useTheme } from 'styled-components'; -import { defaultThemes } from 'theme/default'; import { useList } from '../../List/context'; -import { SelectProps } from '../types'; +import type { SelectProps } from '../types'; -export const Select = (props: SelectProps) => { - const { item } = props; - - const { mode }: any = useTheme(); +export const Select = ({ item }: SelectProps) => { const { addToSelected, removeFromSelected, selected } = useList(); const isSelected = selected.includes(item); @@ -28,15 +22,7 @@ export const Select = (props: SelectProps) => { } }} > - {isSelected && ( - <FontAwesomeIcon - icon={faCheck as IconProp} - transform="shrink-2" - color={defaultThemes.text.primary[mode]} - /> - )} + {isSelected && <FontAwesomeIcon icon={faCheck} transform="shrink-2" />} </SelectWrapper> ); }; - -export default Select; diff --git a/src/library/ListItem/Wrappers.ts b/src/library/ListItem/Wrappers.ts index 88f6d399b7..58035beb2f 100644 --- a/src/library/ListItem/Wrappers.ts +++ b/src/library/ListItem/Wrappers.ts @@ -1,65 +1,84 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import { SmallFontSizeMaxWidth } from 'consts'; import { motion } from 'framer-motion'; import styled from 'styled-components'; -import { - backgroundDropdown, - backgroundModalItem, - borderPrimary, - modalBackground, - networkColor, - shadowColorSecondary, - textSecondary, -} from 'theme'; - -export const Wrapper = styled.div<{ format?: string; inModal?: boolean }>` +import { SmallFontSizeMaxWidth } from 'consts'; + +export const Wrapper = styled.div` + --height-top-row: 3.25rem; + --height-bottom-row: 5rem; + + &.member { + --height-bottom-row: 2.75rem; + } + &.pool-join { + --height-bottom-row: 7.5rem; + } + + --height-total: calc(var(--height-top-row) + var(--height-bottom-row)); + + height: var(--height-total); display: flex; flex-flow: row wrap; - width: 100%; - height: ${(props) => (props.format === 'nomination' ? '6rem' : '3rem')}; position: relative; margin: 0.5rem; + width: 100%; > .inner { - background: ${(props) => - props.inModal ? backgroundModalItem : backgroundDropdown}; - box-shadow: 0px 1.75px 0px 1.25px ${shadowColorSecondary}; - - ${(props) => - props.inModal && - ` + background: var(--background-list-item); + &.modal { + background: var(--background-modal-card); + } + &.canvas { + background: var(--background-canvas-card); + } + &.modal, + &.canvas { box-shadow: none; - border: none;`} - flex: 1; + border: none; + } + border-radius: 1rem; display: flex; flex-flow: row wrap; - justify-content: flex-start; align-items: center; - flex: 1; overflow: hidden; position: absolute; + padding: 0; top: 0px; left: 0px; width: 100%; height: 100%; - padding: 0; + .row { flex: 1 0 100%; - height: 3.25rem; display: flex; - flex-flow: row nowrap; - justify-content: flex-start; align-items: center; padding: 0 0.5rem; - &.status { - height: 2.75rem; + &.top { + height: var(--height-top-row); } - svg { - margin: 0; + &.bottom { + height: var(--height-bottom-row); + + &.lg { + display: flex; + align-items: center; + > div { + &:first-child { + flex-grow: 1; + padding: 0 0.25rem; + } + &:last-child { + flex-shrink: 1; + display: flex; + flex-direction: column; + align-items: flex-end; + } + } + } } } } @@ -67,58 +86,75 @@ export const Wrapper = styled.div<{ format?: string; inModal?: boolean }>` export const Labels = styled.div` display: flex; - flex-flow: row nowrap; justify-content: flex-end; + font-size: 0.85rem; align-items: center; overflow: hidden; - flex: 1 1 100%; + flex-grow: 1; padding: 0 0 0 0.25rem; - height: 2.75rem; + height: inherit; button { padding: 0 0.1rem; + background: var(--shimmer-foreground); + font-size: 1rem; + border-radius: 50%; + width: 1.9rem; + height: 1.9rem; + @media (min-width: ${SmallFontSizeMaxWidth}px) { padding: 0 0.2rem; } - - color: ${textSecondary}; + color: var(--text-color-secondary); &:hover { opacity: 0.75; } &.active { - color: ${networkColor}; + color: var(--accent-color-primary); } &:disabled { - opacity: 0.35; + opacity: var(--opacity-disabled); } } + &.canvas button { + background: none; + border: 1px solid var(--border-secondary-color); + } + .label { + color: var(--text-color-secondary); position: relative; display: flex; - flex-flow: row nowrap; align-items: center; - color: ${textSecondary}; margin: 0 0.2rem; + font-size: inherit; + @media (min-width: ${SmallFontSizeMaxWidth}px) { - margin: 0 0.2rem; + margin: 0 0.35rem; &.pool { - margin: 0 0.4rem; + margin: 0 0.45rem; } } - button { - font-size: 1.1rem; - } + &.button-with-text { margin-right: 0; button { - color: ${networkColor}; + color: var(--accent-color-secondary); + font-family: InterSemiBold, sans-serif; font-size: 0.95rem; display: flex; flex-flow: row wrap; align-items: center; + width: auto; + height: auto; + border-radius: 0.75rem; + padding: 0.25rem 0.75rem; + &:hover { + opacity: 1; + } > svg { margin-left: 0.3rem; } @@ -137,7 +173,6 @@ export const Labels = styled.div` export const OverSubscribedWrapper = styled.div` display: flex; - flex-flow: row nowrap; align-items: center; width: 100%; height: 100%; @@ -152,7 +187,6 @@ export const OverSubscribedWrapper = styled.div` export const IdentityWrapper = styled(motion.div)` display: flex; margin-right: 0.5rem; - flex-flow: row nowrap; align-items: center; align-content: center; overflow: hidden; @@ -162,17 +196,16 @@ export const IdentityWrapper = styled(motion.div)` .inner { display: flex; flex-flow: row wrap; - justify-content: flex-start; align-items: center; width: 100%; height: 3.25rem; padding: 0 0 0 0.2rem; } h4 { - color: ${textSecondary}; + color: var(--text-color-secondary); + font-family: InterSemiBold, sans-serif; position: absolute; top: 0; - width: 100%; height: 3.25rem; line-height: 3.25rem; padding: 0 0 0 0.3rem; @@ -180,11 +213,11 @@ export const IdentityWrapper = styled(motion.div)` overflow: hidden; text-overflow: ellipsis; white-space: nowrap; - font-variation-settings: 'wght' 600; font-size: 1rem; + width: 100%; > span { - color: ${textSecondary}; + color: var(--text-color-secondary); opacity: 0.75; font-size: 0.88rem; margin-left: 0.35rem; @@ -194,16 +227,20 @@ export const IdentityWrapper = styled(motion.div)` } `; -export const ValidatorStatusWrapper = styled.div<{ status: string }>` - margin-right: 0.35rem; +export const ValidatorStatusWrapper = styled.div<{ + $status: string; + $noMargin?: boolean; +}>` + margin-right: ${(props) => (props.$noMargin ? '0' : '0.35rem')}; padding: 0 0.5rem; h5 { - color: ${(props) => (props.status === 'active' ? 'green' : textSecondary)}; - opacity: ${(props) => (props.status === 'active' ? 0.8 : 0.5)}; - margin: 0; + color: ${(props) => + props.$status === 'active' + ? 'var(--status-success-color)' + : 'var(--text-color-secondary)'}; + opacity: ${(props) => (props.$status === 'active' ? 0.8 : 0.5)}; display: flex; - flex-flow: row nowrap; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; @@ -211,7 +248,7 @@ export const ValidatorStatusWrapper = styled.div<{ status: string }>` `; export const SelectWrapper = styled.button` - background: ${modalBackground}; + background: var(--background-input); margin: 0 0.75rem 0 0.25rem; overflow: hidden; display: flex; @@ -233,6 +270,7 @@ export const SelectWrapper = styled.button` justify-content: center; } svg { + color: var(--text-color-primary); width: 1rem; height: 1rem; } @@ -245,9 +283,9 @@ export const SelectWrapper = styled.button` `; export const Separator = styled.div` + border-bottom: 1px solid var(--border-primary-color); width: 100%; height: 1px; - border-bottom: 1px solid ${borderPrimary}; opacity: 0.7; `; @@ -260,15 +298,6 @@ export const MenuPosition = styled.div` opacity: 0; `; -export const TooltipPosition = styled.div` - position: absolute; - top: 0; - left: 0.75rem; - width: 0; - height: 0; - opacity: 0; -`; - export const TooltipTrigger = styled.div` z-index: 1; width: 130%; @@ -282,4 +311,57 @@ export const TooltipTrigger = styled.div` } `; -export default Wrapper; +export const ValidatorPulseWrapper = styled.div` + border: 1px solid var(--grid-color-primary); + border-radius: 0.25rem; + height: 3.2rem; + display: flex; + align-items: center; + width: 100%; + max-width: 13.5rem; + position: relative; + padding: 0.15rem 0; + + &.canvas { + border: 1px solid var(--grid-color-secondary); + } + + > svg { + max-width: 100%; + max-height: 100%; + } + + > .preload { + position: absolute; + top: 0; + left: 0; + background: var(--shimmer-foreground); + background-image: linear-gradient( + to right, + var(--shimmer-foreground) 0%, + var(--shimmer-background) 20%, + var(--shimmer-foreground) 40%, + var(--shimmer-foreground) 100% + ); + background-repeat: no-repeat; + background-size: 500px 104px; + animation-duration: 1.5s; + opacity: 0.2; + animation-fill-mode: forwards; + animation-iteration-count: infinite; + animation-name: shimmer; + animation-timing-function: linear; + z-index: 0; + width: 100%; + height: 100%; + + @keyframes shimmer { + 0% { + background-position: -50% 0; + } + 100% { + background-position: 200% 0; + } + } + } +`; diff --git a/src/library/ListItem/types.ts b/src/library/ListItem/types.ts index 4e53ba457f..0a1967deab 100644 --- a/src/library/ListItem/types.ts +++ b/src/library/ListItem/types.ts @@ -1,21 +1,19 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import { BondedPool } from 'contexts/Pools/types'; -import React from 'react'; -import { AnyMetaBatch, MaybeAccount } from 'types'; +import type React from 'react'; +import type { BondedPool } from 'contexts/Pools/types'; +import type { BondFor, MaybeAddress } from 'types'; +import type { ValidatorPrefs } from 'contexts/Validators/types'; +import type BigNumber from 'bignumber.js'; +import type { NominationStatus } from 'library/ValidatorList/ValidatorItem/types'; export interface BlockedProps { - prefs: { - commission: string; - blocked: boolean; - }; + prefs: ValidatorPrefs; } export interface CopyAddressProps { - validator: { - address: string; - }; + address: string; } export interface FavoriteProps { @@ -24,9 +22,6 @@ export interface FavoriteProps { export interface IdentityProps { address: string; - batchIndex: number; - batchKey: string; - meta: AnyMetaBatch; } export interface PoolIdentityProps { @@ -42,13 +37,14 @@ export interface MetricsProps { export interface NominationStatusProps { address: string; - bondType: string; - nominator: MaybeAccount; + bondFor: BondFor; + nominator: MaybeAddress; + status?: NominationStatus; + noMargin?: boolean; } export interface OversubscribedProps { - batchIndex: number; - batchKey: string; + address: MaybeAddress; } export interface SelectProps { @@ -58,5 +54,12 @@ export interface SelectProps { } export interface ParaValidatorProps { - address: MaybeAccount; + address: MaybeAddress; +} + +export interface EraStatusProps { + address: MaybeAddress; + noMargin: boolean; + totalStake: BigNumber; + status: 'waiting' | 'active'; } diff --git a/src/library/Loader/Announcement.tsx b/src/library/Loader/Announcement.tsx new file mode 100644 index 0000000000..3d78af0dac --- /dev/null +++ b/src/library/Loader/Announcement.tsx @@ -0,0 +1,8 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { LoaderWrapper } from './Wrapper'; + +export const Announcement = () => ( + <LoaderWrapper style={{ width: '100%', height: 60 }} /> +); diff --git a/src/library/Loader/Wrapper.ts b/src/library/Loader/Wrapper.ts new file mode 100644 index 0000000000..2b64ddba6a --- /dev/null +++ b/src/library/Loader/Wrapper.ts @@ -0,0 +1,36 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import styled from 'styled-components'; + +export const LoaderWrapper = styled.div` + background: var(--shimmer-foreground); + background-image: linear-gradient( + to right, + var(--shimmer-foreground) 0%, + var(--shimmer-background) 20%, + var(--shimmer-foreground) 40%, + var(--shimmer-foreground) 100% + ); + background-repeat: no-repeat; + background-size: 600px 104px; + animation-duration: 1.5s; + animation-fill-mode: forwards; + animation-iteration-count: infinite; + animation-name: shimmer; + animation-timing-function: linear; + + opacity: 0.1; + border-radius: 0.75rem; + display: inline-block; + position: relative; + + @keyframes shimmer { + 0% { + background-position: 0px 0; + } + 100% { + background-position: 150% 0; + } + } +`; diff --git a/src/library/Loaders/Announcement.tsx b/src/library/Loaders/Announcement.tsx deleted file mode 100644 index 3af0884b01..0000000000 --- a/src/library/Loaders/Announcement.tsx +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { useTheme } from 'contexts/Themes'; -import ContentLoader from 'react-content-loader'; -import { defaultThemes } from 'theme/default'; - -export const Announcement = () => { - const { mode } = useTheme(); - - return ( - <> - <ContentLoader - height={90} - width="100%" - backgroundColor={defaultThemes.loader.background[mode]} - foregroundColor={defaultThemes.loader.foreground[mode]} - opacity={0.2} - style={{ marginTop: '0.75rem', marginBottom: '0.75rem' }} - > - <rect x="0" y="0" rx="0.5rem" ry="0.5rem" width="100%" height="100%" /> - </ContentLoader> - <ContentLoader - height={90} - width="100%" - backgroundColor={defaultThemes.loader.background[mode]} - foregroundColor={defaultThemes.loader.foreground[mode]} - opacity={0.2} - > - <rect x="0" y="0" rx="0.5rem" ry="0.5rem" width="100%" height="100%" /> - </ContentLoader> - </> - ); -}; - -export default Announcement; diff --git a/src/library/Loaders/DataList.tsx b/src/library/Loaders/DataList.tsx deleted file mode 100644 index bbc5586db6..0000000000 --- a/src/library/Loaders/DataList.tsx +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import ContentLoader from 'react-content-loader'; - -export const DataList = () => { - return ( - <ContentLoader - height={304} - backgroundColor="#e4e4e4" - foregroundColor="#d3d3d3" - style={{ width: '100%' }} - > - <rect x="583" y="33" rx="2" ry="2" width="123" height="33" /> - <rect x="66" y="49" rx="0" ry="0" width="165" height="11" /> - <rect x="66" y="97" rx="0" ry="0" width="202" height="41" /> - <rect x="284" y="97" rx="0" ry="0" width="202" height="41" /> - <rect x="506" y="96" rx="0" ry="0" width="202" height="41" /> - <rect x="66" y="155" rx="0" ry="0" width="642" height="4" /> - <rect x="66" y="175" rx="0" ry="0" width="642" height="4" /> - <rect x="65" y="195" rx="0" ry="0" width="642" height="4" /> - <rect x="66" y="215" rx="0" ry="0" width="642" height="4" /> - <rect x="66" y="236" rx="0" ry="0" width="642" height="4" /> - <rect x="65" y="256" rx="0" ry="0" width="642" height="4" /> - <rect x="315" y="274" rx="0" ry="0" width="31" height="28" /> - <rect x="356" y="274" rx="0" ry="0" width="31" height="28" /> - <rect x="396" y="274" rx="0" ry="0" width="31" height="28" /> - </ContentLoader> - ); -}; - -export default DataList; diff --git a/src/library/Loaders/Stake.tsx b/src/library/Loaders/Stake.tsx deleted file mode 100644 index c130932f83..0000000000 --- a/src/library/Loaders/Stake.tsx +++ /dev/null @@ -1,137 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { useTheme } from 'contexts/Themes'; -import ContentLoader from 'react-content-loader'; -import { defaultThemes } from 'theme/default'; -import { PageRowWrapper } from 'Wrappers'; - -export const Stake = () => { - const { mode } = useTheme(); - - return ( - <> - <PageRowWrapper - className="page-padding" - noVerticalSpacer - style={{ marginTop: '1rem', marginBottom: '1rem' }} - > - <ContentLoader - height={80} - width="100%" - backgroundColor={defaultThemes.loader.background[mode]} - foregroundColor={defaultThemes.loader.foreground[mode]} - opacity={0.2} - style={{ maxWidth: 275, marginRight: '1rem' }} - > - <rect - x="0" - y="0" - rx="0.75rem" - ry="0.75rem" - width="100%" - height="100%" - /> - </ContentLoader> - <ContentLoader - height={80} - width="100%" - backgroundColor={defaultThemes.loader.background[mode]} - foregroundColor={defaultThemes.loader.foreground[mode]} - opacity={0.2} - style={{ maxWidth: 275, marginRight: '1rem' }} - > - <rect - x="0" - y="0" - rx="0.75rem" - ry="0.75rem" - width="100%" - height="100%" - /> - </ContentLoader> - <ContentLoader - height={80} - width="100%" - backgroundColor={defaultThemes.loader.background[mode]} - foregroundColor={defaultThemes.loader.foreground[mode]} - opacity={0.2} - style={{ maxWidth: 275 }} - > - <rect - x="0" - y="0" - rx="0.75rem" - ry="0.75rem" - width="100%" - height="100%" - /> - </ContentLoader> - </PageRowWrapper> - <PageRowWrapper - className="page-padding" - noVerticalSpacer - style={{ marginBottom: '1rem' }} - > - <ContentLoader - height={60} - width="100%" - backgroundColor={defaultThemes.loader.background[mode]} - foregroundColor={defaultThemes.loader.foreground[mode]} - opacity={0.2} - > - <rect - x="0" - y="0" - rx="0.75rem" - ry="0.75rem" - width="100%" - height="100%" - /> - </ContentLoader> - </PageRowWrapper> - <PageRowWrapper - className="page-padding" - noVerticalSpacer - style={{ marginBottom: '1rem' }} - > - <ContentLoader - height={60} - width="100%" - backgroundColor={defaultThemes.loader.background[mode]} - foregroundColor={defaultThemes.loader.foreground[mode]} - opacity={0.2} - > - <rect - x="0" - y="0" - rx="0.75rem" - ry="0.75rem" - width="100%" - height="100%" - /> - </ContentLoader> - </PageRowWrapper> - <PageRowWrapper className="page-padding" noVerticalSpacer> - <ContentLoader - height={60} - width="100%" - backgroundColor={defaultThemes.loader.background[mode]} - foregroundColor={defaultThemes.loader.foreground[mode]} - opacity={0.2} - > - <rect - x="0" - y="0" - rx="0.75rem" - ry="0.75rem" - width="100%" - height="100%" - /> - </ContentLoader> - </PageRowWrapper> - </> - ); -}; - -export default Stake; diff --git a/src/library/Menu/Wrappers.ts b/src/library/Menu/Wrappers.ts index 90963e9a8d..02906fc2e5 100644 --- a/src/library/Menu/Wrappers.ts +++ b/src/library/Menu/Wrappers.ts @@ -1,17 +1,16 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import { FloatingMenuWidth } from 'consts'; import styled from 'styled-components'; -import { borderPrimary, modalBackground, textSecondary } from 'theme'; +import { FloatingMenuWidth } from 'consts'; export const Wrapper = styled.div` - background: ${modalBackground}; + background: var(--background-default); width: ${FloatingMenuWidth}px; padding: 0.25rem 0.75rem; display: flex; flex-flow: column wrap; - transition: opacity 0.1s; + transition: opacity var(--transition-duration); border-radius: 1rem; > button:last-child { @@ -20,21 +19,21 @@ export const Wrapper = styled.div` `; export const ItemWrapper = styled.button` - border-bottom: 1px solid ${borderPrimary}; + border-bottom: 1px solid var(--border-primary-color); + color: var(--text-color-secondary); display: flex; width: 100%; padding: 0.75rem 0.5rem; display: flex; flex-flow: row wrap; align-items: center; - color: ${textSecondary}; &:hover { opacity: 0.75; } .title { - color: ${textSecondary}; + color: var(--text-color-secondary); padding: 0 0 0 0.75rem; font-size: 1rem; } diff --git a/src/library/Menu/index.tsx b/src/library/Menu/index.tsx index cf82b1a7f7..d6149f4743 100644 --- a/src/library/Menu/index.tsx +++ b/src/library/Menu/index.tsx @@ -1,9 +1,9 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only +import { useEffect, useRef } from 'react'; import { useMenu } from 'contexts/Menu'; import { useOutsideAlerter } from 'library/Hooks'; -import { useEffect, useRef } from 'react'; import { ItemWrapper, Wrapper } from './Wrappers'; export const Menu = () => { @@ -72,5 +72,3 @@ export const Menu = () => { </> ); }; - -export default Menu; diff --git a/src/library/Modal/Close.tsx b/src/library/Modal/Close.tsx new file mode 100644 index 0000000000..5b69517bd2 --- /dev/null +++ b/src/library/Modal/Close.tsx @@ -0,0 +1,18 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import CrossSVG from 'img/cross.svg?react'; +import { useOverlay } from '@polkadot-cloud/react/hooks'; +import { CloseWrapper } from './Wrappers'; + +export const Close = () => { + const { setModalStatus } = useOverlay().modal; + + return ( + <CloseWrapper> + <button type="button" onClick={() => setModalStatus('closing')}> + <CrossSVG style={{ width: '1.25rem', height: '1.25rem' }} /> + </button> + </CloseWrapper> + ); +}; diff --git a/src/library/Modal/Title.tsx b/src/library/Modal/Title.tsx index dd9f0ec0c3..1a51353bcd 100644 --- a/src/library/Modal/Title.tsx +++ b/src/library/Modal/Title.tsx @@ -1,12 +1,14 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import { IconProp } from '@fortawesome/fontawesome-svg-core'; +import type { IconProp } from '@fortawesome/fontawesome-svg-core'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { useModal } from 'contexts/Modal'; -import { ReactComponent as CrossSVG } from 'img/cross.svg'; -import { OpenHelpIcon } from 'library/OpenHelpIcon'; -import { FunctionComponent } from 'react'; +import { ButtonHelp } from '@polkadot-cloud/react'; +import type { FunctionComponent } from 'react'; +import React from 'react'; +import { useHelp } from 'contexts/Help'; +import CrossSVG from 'img/cross.svg?react'; +import { useOverlay } from '@polkadot-cloud/react/hooks'; import { TitleWrapper } from './Wrappers'; interface TitleProps { @@ -15,10 +17,19 @@ interface TitleProps { Svg?: FunctionComponent<any>; fixed?: boolean; helpKey?: string; + style?: React.CSSProperties; } -export const Title = ({ helpKey, title, icon, fixed, Svg }: TitleProps) => { - const { setStatus } = useModal(); +export const Title = ({ + helpKey, + title, + icon, + fixed, + Svg, + style, +}: TitleProps) => { + const { setModalStatus } = useOverlay().modal; + const { openHelp } = useHelp(); const graphic = Svg ? ( <Svg style={{ width: '1.5rem', height: '1.5rem' }} /> @@ -27,17 +38,19 @@ export const Title = ({ helpKey, title, icon, fixed, Svg }: TitleProps) => { ) : null; return ( - <TitleWrapper fixed={fixed || false}> + <TitleWrapper $fixed={fixed || false} style={{ ...style }}> <div> {graphic} <h2> {title} - {helpKey && <OpenHelpIcon helpKey={helpKey} />} + {helpKey ? ( + <ButtonHelp marginLeft onClick={() => openHelp(helpKey)} /> + ) : null} </h2> </div> <div> - <button type="button" onClick={() => setStatus(2)}> - <CrossSVG style={{ width: '1.4rem', height: '1.4rem' }} /> + <button type="button" onClick={() => setModalStatus('closing')}> + <CrossSVG style={{ width: '1.25rem', height: '1.25rem' }} /> </button> </div> </TitleWrapper> diff --git a/src/library/Modal/Wrappers.ts b/src/library/Modal/Wrappers.ts new file mode 100644 index 0000000000..198d793454 --- /dev/null +++ b/src/library/Modal/Wrappers.ts @@ -0,0 +1,124 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import styled from 'styled-components'; + +export const TitleWrapper = styled.div<{ $fixed: boolean }>` + padding: ${(props) => + props.$fixed ? '0.6rem 1rem 0rem 1rem' : '2rem 1rem 0 1rem'}; + display: flex; + flex-flow: row wrap; + align-items: center; + width: 100%; + min-height: 3rem; + + > div { + display: flex; + flex-flow: row wrap; + align-items: center; + padding: 0 0.5rem; + + button { + padding: 0; + } + + path { + fill: var(--text-color-primary); + } + + &:first-child { + flex-grow: 1; + + > h2 { + display: flex; + align-items: center; + font-family: 'Unbounded', 'sans-serif', sans-serif; + font-size: 1.3rem; + margin: 0; + + > button { + margin-left: 0.85rem; + } + } + > svg { + margin-right: 0.9rem; + } + } + + &:last-child { + button { + position: absolute; + top: 1.5rem; + right: 1.5rem; + opacity: 0.25; + &:hover { + opacity: 1; + } + } + } + } +`; + +export const StatsWrapper = styled.div` + width: 100%; + display: flex; + flex-flow: row wrap; + margin-top: 1rem; +`; +export const StatWrapper = styled.div` + display: flex; + flex-flow: column wrap; + margin-bottom: 1rem; + padding: 0 0.75rem; + flex-grow: 1; + flex-basis: 100%; + + @media (min-width: 600px) { + margin-bottom: 0.5rem; + } + + @media (min-width: 601px) { + flex-basis: 33%; + } + + > .inner { + border-bottom: 1px solid var(--border-primary-color); + padding-bottom: 0.5rem; + + > h2, + h3, + h4 { + margin: 0.25rem 0; + } + h4 { + margin: 0rem 0 0.75rem 0; + display: flex; + align-items: center; + + .icon { + margin-right: 0.425rem; + } + } + h2, + h3, + h4 { + color: var(--text-color-secondary); + } + } +`; + +export const CloseWrapper = styled.div` + position: absolute; + right: 1.5rem; + top: 1.5rem; + z-index: 2; + + > button { + opacity: 0.4; + transition: opacity var(--transition-duration) ease-in-out; + + &:hover { + opacity: 1; + } + } +`; diff --git a/src/library/NetworkBar/Status.tsx b/src/library/NetworkBar/Status.tsx index 397a31b08b..1a429847e7 100644 --- a/src/library/NetworkBar/Status.tsx +++ b/src/library/NetworkBar/Status.tsx @@ -1,32 +1,31 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import { useApi } from 'contexts/Api'; -import { ConnectionStatus } from 'contexts/Api/types'; import { motion } from 'framer-motion'; +import { useTranslation } from 'react-i18next'; +import { useApi } from 'contexts/Api'; export const Status = () => { - const { status } = useApi(); + const { t } = useTranslation('library'); + const { apiStatus } = useApi(); return ( <> - {status === ConnectionStatus.Disconnected && ( + {apiStatus === 'disconnected' && ( <motion.p animate={{ opacity: [0, 1] }} transition={{ duration: 0.3 }}> - Disconnected + {t('disconnected')} </motion.p> )} - {status === ConnectionStatus.Connecting && ( + {apiStatus === 'connecting' && ( <motion.p animate={{ opacity: [0, 1] }} transition={{ duration: 0.3 }}> - Connecting... + {t('connecting')}... </motion.p> )} - {status === ConnectionStatus.Connected && ( + {apiStatus === 'connected' && ( <motion.p animate={{ opacity: [0, 1] }} transition={{ duration: 0.3 }}> - Connected to Network + {t('connectedToNetwork')} </motion.p> )} </> ); }; - -export default Status; diff --git a/src/library/NetworkBar/Wrappers.ts b/src/library/NetworkBar/Wrappers.ts index e9344b20eb..ce9f2c195d 100644 --- a/src/library/NetworkBar/Wrappers.ts +++ b/src/library/NetworkBar/Wrappers.ts @@ -1,44 +1,45 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import { SideMenuStickyThreshold } from 'consts'; -import { motion } from 'framer-motion'; import styled from 'styled-components'; -import { backgroundNetworkBar, networkColor, textSecondary } from 'theme'; +import { SideMenuStickyThreshold } from 'consts'; -export const Wrapper = styled(motion.div)` - width: 100%; +export const Wrapper = styled.div` + background: var(--background-app-footer); + color: var(--text-color-secondary); display: flex; - flex-flow: column nowrap; - justify-content: flex-start; + flex-flow: row nowrap; align-items: center; - font-size: 0.85rem; - color: #444; bottom: 0px; left: 0px; overflow: hidden; - background: ${backgroundNetworkBar}; z-index: 6; backdrop-filter: blur(4px); position: relative; + padding-top: 0.15rem; + font-size: 0.85rem; + width: 100%; + @media (min-width: ${SideMenuStickyThreshold + 1}px) { position: fixed; } + + .network_icon { + margin: 0 0 0 1rem; + width: 1.5rem; + height: 1.5rem; + } `; export const Summary = styled.div` width: 100%; display: flex; - flex-flow: row nowrap; - justify-content: flex-start; align-items: center; align-content: center; /* hide connection status text on small screens */ .hide-small { display: flex; - flex-flow: row nowrap; - justify-content: flex-start; align-items: center; align-content: center; @@ -47,24 +48,29 @@ export const Summary = styled.div` } } - a { + a, + button { + color: var(--text-color-secondary); + font-size: 0.85rem; opacity: 0.75; } p { - margin: 0 0.25rem; + border-left: 1px solid var(--accent-color-transparent); + margin: 0.25rem 0.5rem 0.25rem 0.15rem; font-size: 0.85rem; + padding-left: 0.5rem; + line-height: 1.2rem; } .stat { margin: 0 0.25rem; display: flex; - flex-flow: row nowrap; align-items: center; } /* left and right sections for each row*/ > section { - padding: 0.5rem 0.5rem; - color: ${textSecondary}; + color: var(--text-color-secondary); + padding: 0.5rem 0; /* left section */ &:nth-child(1) { @@ -72,11 +78,6 @@ export const Summary = styled.div` flex-flow: row wrap; align-items: center; flex-grow: 1; - .network_icon { - margin-right: 0.5rem; - width: 1.5rem; - height: 1.5rem; - } } /* right section */ @@ -85,10 +86,12 @@ export const Summary = styled.div` display: flex; align-items: center; flex-flow: row-reverse wrap; + padding-right: 0.5rem; + button { + color: var(--text-color-secondary); border-radius: 0.4rem; padding: 0.25rem 0.5rem; - color: ${textSecondary}; font-size: 0.85rem; } span { @@ -103,73 +106,10 @@ export const Summary = styled.div` } `; -export const NetworkInfo = styled(motion.div)` - width: 100%; - background: ${networkColor}; - flex: 1; - display: flex; - flex-flow: column nowrap; - justify-content: flex-start; - align-content: flex-end; - padding: 0.25rem 1rem 1rem 1rem; - overflow: auto; - - > .row { - display: flex; - flex-flow: row nowrap; - justify-content: flex-end; - align-content: flex-start; - align-items: flex-start; - - h2 { - color: #eee; - font-size: 1.1rem; - line-height: 2rem; - padding: 0 0.25rem; - margin: 1rem 0; - } - - > div, - > button { - background: rgba(0, 0, 0, 0.1); - margin-right: 1rem; - border-radius: 0.5rem; - padding: 0.5rem 1.25rem; - display: flex; - flex-flow: column nowrap; - - &:last-child { - margin-right: 0; - } - } - > div, - > span { - padding: 1rem; - } - h3 { - margin: 0.25rem 0; - color: #f1f1f1; - padding: 0.2rem 0; - - &.val { - font-size: 0.85rem; - color: #e6e6e6; - } - } - } - - > .row:first-child > h3 { - margin-top: 0.5rem; - border-top: 0; - } -`; - export const Separator = styled.div` - border-left: 1px solid ${textSecondary}; + border-left: 1px solid var(--text-color-secondary); opacity: 0.2; margin: 0 0.3rem; width: 1px; height: 1rem; `; - -export default Wrapper; diff --git a/src/library/NetworkBar/index.tsx b/src/library/NetworkBar/index.tsx index 615d6b167e..99ce621782 100644 --- a/src/library/NetworkBar/index.tsx +++ b/src/library/NetworkBar/index.tsx @@ -1,76 +1,48 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only +import { capitalizeFirstLetter } from '@polkadot-cloud/utils'; +import { useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; import { useApi } from 'contexts/Api'; -import { useUi } from 'contexts/UI'; -import { useOutsideAlerter } from 'library/Hooks'; +import { usePlugins } from 'contexts/Plugins'; import { usePrices } from 'library/Hooks/usePrices'; -import { useEffect, useRef, useState } from 'react'; +import { useNetwork } from 'contexts/Network'; import { Status } from './Status'; -import { NetworkInfo, Separator, Summary, Wrapper } from './Wrappers'; +import { Summary, Wrapper } from './Wrappers'; export const NetworkBar = () => { - const { services } = useUi(); - const { network, isLightClient } = useApi(); + const { t } = useTranslation('library'); + const { plugins } = usePlugins(); + const { isLightClient } = useApi(); + const { networkData, network } = useNetwork(); const prices = usePrices(); - // currently not in use - const [open, setOpen] = useState(false); + const PRIVACY_URL = import.meta.env.VITE_PRIVACY_URL; + const DISCLAIMER_URL = import.meta.env.VITE_DISCLAIMER_URL; + const ORGANISATION = import.meta.env.VITE_ORGANISATION; + const LEGAL_DISCLOSURES_URL = import.meta.env.VITE_LEGAL_DISCLOSURES_URL; - // handle expand transitions - const variants = { - minimised: { - height: '2.5rem', - }, - maximised: { - height: '155px', - }, - }; - - const animate = open ? 'maximised' : 'minimised'; - const ref = useRef(null); - - const PRIVACY_URL = process.env.REACT_APP_PRIVACY_URL; - const DISCLAIMER_URL = process.env.REACT_APP_DISCLAIMER_URL; - const ORGANISATION = process.env.REACT_APP_ORGANISATION; - - const [networkName, setNetworkName] = useState<string>(network.name); - - useOutsideAlerter( - ref, - () => { - setOpen(false); - }, - ['igignore-network-info-toggle'] + const [networkName, setNetworkName] = useState<string>( + capitalizeFirstLetter(network) ); useEffect(() => { setNetworkName( - isLightClient ? network.name.concat(' Light') : network.name + `${capitalizeFirstLetter(network)}${isLightClient ? ` Light` : ``}` ); - }, [network.name, isLightClient]); + }, [network, isLightClient]); return ( - <Wrapper - ref={ref} - initial={false} - animate={animate} - transition={{ - duration: 0.4, - type: 'spring', - bounce: 0.25, - }} - variants={variants} - > + <Wrapper> + <networkData.brand.icon className="network_icon" /> <Summary> <section> - <network.brand.icon className="network_icon" /> <p>{ORGANISATION === undefined ? networkName : ORGANISATION}</p> - <Separator /> {PRIVACY_URL !== undefined ? ( <p> <a href={PRIVACY_URL} target="_blank" rel="noreferrer"> - Privacy + {t('privacy')} </a> </p> ) : ( @@ -78,10 +50,22 @@ export const NetworkBar = () => { )} {DISCLAIMER_URL !== undefined && ( <> - <Separator /> <p> <a href={DISCLAIMER_URL} target="_blank" rel="noreferrer"> - Disclaimer + {t('disclaimer')} + </a> + </p> + </> + )} + {LEGAL_DISCLOSURES_URL !== undefined && ( + <> + <p> + <a + href={LEGAL_DISCLOSURES_URL} + target="_blank" + rel="noreferrer" + > + {t('legalDisclosures')} </a> </p> </> @@ -89,7 +73,7 @@ export const NetworkBar = () => { </section> <section> <div className="hide-small"> - {services.includes('binance_spot') && ( + {plugins.includes('binance_spot') && ( <> <div className="stat"> <span @@ -106,17 +90,13 @@ export const NetworkBar = () => { </span> </div> <div className="stat"> - 1 {network.api.unit} / {prices.lastPrice} USD + 1 {networkData.api.unit} / {prices.lastPrice} USD </div> </> )} </div> </section> </Summary> - - <NetworkInfo /> </Wrapper> ); }; - -export default NetworkBar; diff --git a/src/pages/Nominate/Active/Nominations/Wrapper.tsx b/src/library/Nominations/Wrapper.ts similarity index 75% rename from src/pages/Nominate/Active/Nominations/Wrapper.tsx rename to src/library/Nominations/Wrapper.ts index b17ab85fb0..4b1e615e0f 100644 --- a/src/pages/Nominate/Active/Nominations/Wrapper.tsx +++ b/src/library/Nominations/Wrapper.ts @@ -1,5 +1,5 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only import styled from 'styled-components'; diff --git a/src/library/Nominations/index.tsx b/src/library/Nominations/index.tsx new file mode 100644 index 0000000000..7729e76e06 --- /dev/null +++ b/src/library/Nominations/index.tsx @@ -0,0 +1,155 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { faCog, faStopCircle } from '@fortawesome/free-solid-svg-icons'; +import { ButtonHelp, ButtonPrimary } from '@polkadot-cloud/react'; +import { useTranslation } from 'react-i18next'; +import { useBonded } from 'contexts/Bonded'; +import { useHelp } from 'contexts/Help'; +import { useActivePools } from 'contexts/Pools/ActivePools'; +import { useStaking } from 'contexts/Staking'; +import { useUi } from 'contexts/UI'; +import { useValidators } from 'contexts/Validators/ValidatorEntries'; +import { CardHeaderWrapper } from 'library/Card/Wrappers'; +import { useUnstaking } from 'library/Hooks/useUnstaking'; +import { ValidatorList } from 'library/ValidatorList'; +import type { MaybeAddress } from 'types'; +import { useOverlay } from '@polkadot-cloud/react/hooks'; +import { useActiveAccounts } from 'contexts/ActiveAccounts'; +import { useImportedAccounts } from 'contexts/Connect/ImportedAccounts'; +import { ListStatusHeader } from 'library/List'; +import { Wrapper } from './Wrapper'; + +export const Nominations = ({ + bondFor, + nominator, +}: { + bondFor: 'pool' | 'nominator'; + nominator: MaybeAddress; +}) => { + const { t } = useTranslation('pages'); + const { + poolNominations, + selectedActivePool, + isOwner: isPoolOwner, + isNominator: isPoolNominator, + } = useActivePools(); + const { isSyncing } = useUi(); + const { openHelp } = useHelp(); + const { inSetup } = useStaking(); + const { + modal: { openModal }, + canvas: { openCanvas }, + } = useOverlay(); + const { getNominated } = useValidators(); + const { isFastUnstaking } = useUnstaking(); + const { activeAccount } = useActiveAccounts(); + const { getAccountNominations } = useBonded(); + const { isReadOnlyAccount } = useImportedAccounts(); + + // Determine if pool or nominator. + const isPool = bondFor === 'pool'; + + // Derive nominations from `bondFor` type. + const nominations = isPool + ? poolNominations.targets + : getAccountNominations(nominator); + const nominated = getNominated(bondFor); + + // Determine if this nominator is actually nominating. + const isNominating = nominated?.length ?? false; + + // Determine whether this is a pool that is in Destroying state & not nominating. + const poolDestroying = + isPool && + selectedActivePool?.bondedPool?.state === 'Destroying' && + !isNominating; + + // Determine whether to display buttons. + // + // If regular staking and nominating, or if pool and account is nominator or root, display stop + // button. + const displayBtns = + (!isPool && nominations.length) || + (isPool && (isPoolNominator() || isPoolOwner())); + + // Determine whether buttons are disabled. + const btnsDisabled = + (!isPool && inSetup()) || + isSyncing || + isReadOnlyAccount(activeAccount) || + poolDestroying || + isFastUnstaking; + + return ( + <Wrapper> + <CardHeaderWrapper $withAction $withMargin> + <h3> + {isPool ? t('nominate.poolNominations') : t('nominate.nominations')} + <ButtonHelp marginLeft onClick={() => openHelp('Nominations')} /> + </h3> + <div> + {displayBtns && ( + <> + <ButtonPrimary + text={t('nominate.stop')} + iconLeft={faStopCircle} + iconTransform="grow-1" + disabled={btnsDisabled} + onClick={() => + openModal({ + key: 'ChangeNominations', + options: { + nominations: [], + bondFor, + }, + size: 'sm', + }) + } + /> + <ButtonPrimary + text={t('nominate.manage')} + iconLeft={faCog} + iconTransform="grow-1" + disabled={btnsDisabled} + marginLeft + onClick={() => + openCanvas({ + key: 'ManageNominations', + scroll: false, + options: { + bondFor, + nominator, + nominated, + }, + size: 'xl', + }) + } + /> + </> + )} + </div> + </CardHeaderWrapper> + {nominated === null || isSyncing ? ( + <ListStatusHeader>{`${t('nominate.syncing')}...`}</ListStatusHeader> + ) : !nominator ? ( + <ListStatusHeader>{t('nominate.notNominating')}.</ListStatusHeader> + ) : nominated.length > 0 ? ( + <ValidatorList + bondFor={bondFor} + validators={nominated} + nominator={nominator} + format="nomination" + refetchOnListUpdate + allowMoreCols + disableThrottle + allowListFormat={false} + /> + ) : poolDestroying ? ( + <ListStatusHeader>{t('nominate.poolDestroy')}</ListStatusHeader> + ) : ( + <ListStatusHeader>{t('nominate.notNominating')}.</ListStatusHeader> + )} + </Wrapper> + ); +}; diff --git a/src/library/Nominations/types.ts b/src/library/Nominations/types.ts new file mode 100644 index 0000000000..22b7bc798d --- /dev/null +++ b/src/library/Nominations/types.ts @@ -0,0 +1,18 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import type { AnyJson } from '@polkadot-cloud/react/types'; +import type { ListFormat } from 'library/PoolList/types'; +import type { Validator } from 'contexts/Validators/types'; + +export interface ManageNominationsInterface { + addToSelected: (item: AnyJson) => void; + removeFromSelected: (item: AnyJson) => void; + setListFormat: (format: ListFormat) => void; + setSelectActive: (active: boolean) => void; + resetSelected: () => void; + selected: Validator[]; + listFormat: ListFormat; + selectActive: boolean; + selectTogglable: boolean; +} diff --git a/src/library/Notifications/Wrapper.ts b/src/library/Notifications/Wrapper.ts index 9dba8fefbc..d014dbf1e3 100644 --- a/src/library/Notifications/Wrapper.ts +++ b/src/library/Notifications/Wrapper.ts @@ -1,12 +1,11 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only import styled from 'styled-components'; -import { backgroundPrimary, networkColor, textSecondary } from 'theme'; export const Wrapper = styled.ul` position: fixed; - bottom: 20px; + top: 0; right: 0; display: flex; flex-direction: column; @@ -15,33 +14,29 @@ export const Wrapper = styled.ul` z-index: 10; li { - background: ${backgroundPrimary}; - width: 360px; - margin: 0.4rem 1.2rem; + background: var(--background-primary); + margin: 0.3rem 1.2rem; position: relative; - border-radius: 10px; - padding: 1rem 1.5rem; + border-radius: 1.25rem; + padding: 1rem 1.35rem; display: flex; flex-flow: column wrap; justify-content: center; cursor: pointer; overflow: hidden; + width: 375px; h3 { - color: ${networkColor}; - margin: 0 0 0.5rem; + color: var(--accent-color-primary); + font-family: InterSemiBold, sans-serif; + font-size: 1.2rem; + margin: 0.15rem 0 0.4rem; flex: 1; } - h5 { - color: ${textSecondary}; - margin: 0; - text-overflow: ellipsis; - white-space: nowrap; - overflow: hidden; - flex: 1; - max-width: 100%; + h4 { + font-family: InterSemiBold, sans-serif; + font-size: 1.05rem; + line-height: 1.45rem; } } `; - -export default Wrapper; diff --git a/src/library/Notifications/index.tsx b/src/library/Notifications/index.tsx index ab078fd1f4..598e6d0ed3 100644 --- a/src/library/Notifications/index.tsx +++ b/src/library/Notifications/index.tsx @@ -1,9 +1,10 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import { useNotifications } from 'contexts/Notifications'; import { AnimatePresence, motion } from 'framer-motion'; -import Wrapper from './Wrapper'; +import { useNotifications } from 'contexts/Notifications'; +import type { NotificationInterface } from 'contexts/Notifications/types'; +import { Wrapper } from './Wrapper'; export const Notifications = () => { const { notifications, removeNotification } = useNotifications(); @@ -12,33 +13,33 @@ export const Notifications = () => { <Wrapper> <AnimatePresence initial={false}> {notifications.length > 0 && - notifications.map((_n: any, i: number) => { - const { item } = _n; + notifications.map( + (notification: NotificationInterface, i: number) => { + const { item, index } = notification; - return ( - <motion.li - key={`notification_${i}`} - layout - initial={{ opacity: 0, y: 50, scale: 0.3 }} - animate={{ opacity: 1, y: 0, scale: 1 }} - exit={{ - opacity: 0, - scale: 0.5, - y: 50, - transition: { duration: 0.2 }, - }} - whileHover={{ scale: 1.02 }} - whileTap={{ scale: 0.98 }} - onClick={() => removeNotification(item)} - > - {item.title && <h3>{item.title}</h3>} - {item.subtitle && <h5>{item.subtitle}</h5>} - </motion.li> - ); - })} + return ( + <motion.li + key={`notification_${i}`} + layout + initial={{ opacity: 0, y: -50, scale: 0.75 }} + animate={{ opacity: 1, y: 0, scale: 1 }} + exit={{ + opacity: 0, + scale: 0.75, + y: -50, + transition: { duration: 0.2 }, + }} + whileHover={{ scale: 1.02 }} + whileTap={{ scale: 0.98 }} + onClick={() => removeNotification(index)} + > + {item.title && <h3>{item.title}</h3>} + {item.subtitle && <h4>{item.subtitle}</h4>} + </motion.li> + ); + } + )} </AnimatePresence> </Wrapper> ); }; - -export default Notifications; diff --git a/src/library/OpenHelpIcon/Wrapper.tsx b/src/library/OpenHelpIcon/Wrapper.tsx deleted file mode 100644 index 9813ba04ef..0000000000 --- a/src/library/OpenHelpIcon/Wrapper.tsx +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import styled from 'styled-components'; -import { - buttonHelpBackground, - buttonPrimaryBackground, - networkColor, - textSecondary, -} from 'theme'; - -export const Wrapper = styled.button<{ light?: boolean }>` - background: ${(props) => - props.light ? buttonPrimaryBackground : buttonHelpBackground}; - color: ${textSecondary}; - fill: ${textSecondary}; - display: flex; - flex-flow: row wrap; - align-items: center; - justify-content: center; - border-radius: 50%; - padding: 0.05rem; - transition: all 0.15s; - font-size: 1.15rem; - - &:hover { - fill: ${networkColor}; - } -`; - -export default Wrapper; diff --git a/src/library/OpenHelpIcon/index.tsx b/src/library/OpenHelpIcon/index.tsx deleted file mode 100644 index 36c905efca..0000000000 --- a/src/library/OpenHelpIcon/index.tsx +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { useHelp } from 'contexts/Help'; -import { ReactComponent as IconSVG } from 'img/info-outline.svg'; -import { OpenHelpIconProps } from './types'; -import { Wrapper } from './Wrapper'; - -export const OpenHelpIcon = (props: OpenHelpIconProps) => { - const { openHelpWith } = useHelp(); - - const { helpKey } = props; - - const size = props.size ?? '1.3em'; - - return ( - <Wrapper - onClick={() => { - openHelpWith(helpKey, {}); - }} - className="help-icon" - style={{ width: size, height: size }} - light={props.light ?? false} - > - <IconSVG /> - </Wrapper> - ); -}; - -export default OpenHelpIcon; diff --git a/src/library/OpenHelpIcon/types.ts b/src/library/OpenHelpIcon/types.ts deleted file mode 100644 index d2c9e2b495..0000000000 --- a/src/library/OpenHelpIcon/types.ts +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -export interface OpenHelpIconProps { - helpKey: string; - size?: string; - light?: boolean; -} diff --git a/src/library/Overlay/Title.tsx b/src/library/Overlay/Title.tsx deleted file mode 100644 index 4d5a91daf2..0000000000 --- a/src/library/Overlay/Title.tsx +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { IconProp } from '@fortawesome/fontawesome-svg-core'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { ButtonInvertRounded } from '@rossbulat/polkadot-dashboard-ui'; -import { useOverlay } from 'contexts/Overlay'; -import { OpenHelpIcon } from 'library/OpenHelpIcon'; -import { FunctionComponent } from 'react'; -import { TitleWrapper } from './Wrappers'; - -interface TitleProps { - title: string; - icon?: IconProp; - Svg?: FunctionComponent<any>; - helpKey?: string; -} - -export const Title = ({ helpKey, title, icon, Svg }: TitleProps) => { - const { closeOverlay } = useOverlay(); - - const graphic = Svg ? ( - <Svg style={{ width: '1.5rem', height: '1.5rem' }} /> - ) : icon ? ( - <FontAwesomeIcon transform="grow-3" icon={icon} /> - ) : null; - - return ( - <TitleWrapper> - <div> - {graphic} - <h2> - {title} - {helpKey && <OpenHelpIcon helpKey={helpKey} />} - </h2> - </div> - <div> - <ButtonInvertRounded text="Done" onClick={() => closeOverlay()} /> - </div> - </TitleWrapper> - ); -}; diff --git a/src/library/Overlay/index.tsx b/src/library/Overlay/index.tsx deleted file mode 100644 index da193f76bb..0000000000 --- a/src/library/Overlay/index.tsx +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { useOverlay } from 'contexts/Overlay'; -import { ContentWrapper, HeightWrapper, OverlayWrapper } from './Wrappers'; - -export const Overlay = () => { - const { closeOverlay, size, status, Overlay: OverlayInner } = useOverlay(); - - if (status === 0) { - return <></>; - } - - return ( - <OverlayWrapper> - <div> - <HeightWrapper size={size}> - <ContentWrapper>{OverlayInner}</ContentWrapper> - </HeightWrapper> - <button type="button" className="close" onClick={() => closeOverlay()}> -   - </button> - </div> - </OverlayWrapper> - ); -}; diff --git a/src/library/PageTitle/index.tsx b/src/library/PageTitle/index.tsx deleted file mode 100644 index 59f338a301..0000000000 --- a/src/library/PageTitle/index.tsx +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { faBars } from '@fortawesome/free-solid-svg-icons'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { useEffect, useRef, useState } from 'react'; -import { MenuPaddingWrapper, PageTitleWrapper } from 'Wrappers'; -import { PageTitleProps } from './types'; - -export const PageTitle = (props: PageTitleProps) => { - const { title, button } = props; - const tabs = props.tabs ?? []; - - const [sticky, setSticky] = useState(false); - - const ref = useRef<HTMLElement>(null); - - useEffect(() => { - const cachedRef = ref.current; - const observer = new IntersectionObserver( - ([e]) => { - setSticky(e.intersectionRatio < 1); - }, - { threshold: [1], rootMargin: '-1px 0px 0px 0px' } - ); - - if (cachedRef) { - observer.observe(cachedRef); - } - // unmount - return () => { - if (cachedRef) { - observer.unobserve(cachedRef); - } - }; - }, [sticky]); - - return ( - <> - <MenuPaddingWrapper /> - <PageTitleWrapper ref={ref} sticky={sticky}> - <div className="page-padding"> - <section className="title"> - <div> - <h1>{title}</h1> - </div> - <div> - {button && ( - <button type="button" onClick={() => button.onClick()}> - {button.title} - <FontAwesomeIcon - icon={faBars} - className="icon" - transform="shrink-4" - /> - </button> - )} - </div> - </section> - {tabs.length > 0 && ( - <section className="tabs"> - <div className="scroll"> - <div className="inner"> - {tabs.map((tab: any, i: number) => ( - <button - className={tab.active ? `active` : ``} - key={`page_tab_${i}`} - type="button" - onClick={() => tab.onClick()} - > - {tab.title} - </button> - ))} - </div> - </div> - </section> - )} - </div> - </PageTitleWrapper> - </> - ); -}; - -export default PageTitle; diff --git a/src/library/PageTitle/types.ts b/src/library/PageTitle/types.ts deleted file mode 100644 index a7783ce58b..0000000000 --- a/src/library/PageTitle/types.ts +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -export interface PageTitleProps { - title: string; - tabs?: Array<any>; - button?: { - title: string; - onClick: () => void; - }; -} diff --git a/src/library/PayeeInput/Wrapper.ts b/src/library/PayeeInput/Wrapper.ts new file mode 100644 index 0000000000..5f0fee4294 --- /dev/null +++ b/src/library/PayeeInput/Wrapper.ts @@ -0,0 +1,92 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import styled from 'styled-components'; + +export const Wrapper = styled.div<{ $activeInput?: boolean }>` + > .inner { + width: 100%; + display: flex; + flex-flow: column nowrap; + border-bottom: 1.5px solid + ${(props) => + props.$activeInput + ? 'var(--accent-color-primary)' + : 'var(--border-primary-color)'}; + padding: 0rem 0 0.4rem 0; + transition: border var(--transition-duration); + + > h4 { + color: var(--text-color-secondary); + margin-top: 1.25rem; + margin-bottom: 0.5rem; + } + + > .account { + width: 100%; + display: flex; + flex-flow: row nowrap; + align-items: center; + margin-top: 0.2rem; + + > .emptyIcon { + background: var(--background-list-item); + width: 2.5rem; + height: 2.5rem; + border-radius: 50%; + } + + > .input { + color: var(--text-color-secondary); + display: flex; + flex-flow: column nowrap; + margin-left: 0.75rem; + min-width: 150px; + max-width: 100%; + + > input { + color: var(--text-color-secondary); + font-size: 1.25rem; + z-index: 1; + opacity: 1; + + &:disabled { + opacity: 0.75; + } + } + + .hidden { + font-size: 1.25rem; + opacity: 0; + position: absolute; + top: -999px; + } + } + } + } + + .label { + position: relative; + display: flex; + align-items: flex-end; + max-width: 100%; + overflow: hidden; + height: 2rem; + margin-top: 0.65rem; + font-size: 0.85rem; + + h5 { + color: var(--text-color-secondary); + position: absolute; + top: 0; + left: 0; + max-width: 100%; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + > svg { + margin-right: 0.4rem; + } + } + } +`; diff --git a/src/library/PayeeInput/index.tsx b/src/library/PayeeInput/index.tsx new file mode 100644 index 0000000000..ce24e13e6f --- /dev/null +++ b/src/library/PayeeInput/index.tsx @@ -0,0 +1,155 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { faCheck } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { isValidAddress, remToUnit } from '@polkadot-cloud/utils'; +import React, { useEffect, useRef, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useBonded } from 'contexts/Bonded'; +import { Polkicon } from '@polkadot-cloud/react'; +import { useActiveAccounts } from 'contexts/ActiveAccounts'; +import { useImportedAccounts } from 'contexts/Connect/ImportedAccounts'; +import { useNetwork } from 'contexts/Network'; +import { formatAccountSs58 } from 'contexts/Connect/Utils'; +import { Wrapper } from './Wrapper'; +import type { PayeeInputProps } from './types'; + +export const PayeeInput = ({ + payee, + account, + setAccount, + handleChange, +}: PayeeInputProps) => { + const { t } = useTranslation('library'); + const { getBondedAccount } = useBonded(); + const { accounts } = useImportedAccounts(); + const { + networkData: { ss58 }, + } = useNetwork(); + const { activeAccount } = useActiveAccounts(); + const controller = getBondedAccount(activeAccount); + + const accountMeta = accounts.find((a) => a.address === activeAccount); + + // store whether account value is valid. + const [valid, setValid] = useState<boolean>(isValidAddress(account || '')); + + // Store whether input is currently active. + const [inputActive, setInputActive] = useState<boolean>(false); + + const hiddenRef = useRef<HTMLInputElement>(null); + const showingRef = useRef<HTMLInputElement>(null); + + // Adjust the width of account input based on text length. + const handleAdjustWidth = () => { + if (hiddenRef.current && showingRef.current) { + const hiddenWidth = hiddenRef.current.offsetWidth; + showingRef.current.style.width = `${hiddenWidth + remToUnit('2.5rem')}px`; + } + }; + + // Handle change of account value. Updates setup progress if the account is a valid value. + const handleChangeAccount = (e: React.ChangeEvent<HTMLInputElement>) => { + const newAddress = e.target.value; + const formatted = formatAccountSs58(newAddress, ss58) || newAddress || null; + const isValid = isValidAddress(formatted || ''); + + setValid(isValid); + setAccount(formatted); + + if (isValid) { + handleChange(formatted); + } else { + handleChange(null); + } + }; + + // Adjust width as ref values change. + useEffect(() => { + handleAdjustWidth(); + }, [hiddenRef.current, showingRef.current, payee.destination]); + + // Adjust width on window resize. + useEffect(() => { + window.addEventListener('resize', handleAdjustWidth); + return () => { + window.removeEventListener('resize', handleAdjustWidth); + }; + }, []); + + // Show empty Identicon on `None` and invalid `Account` accounts. + const showEmpty = + payee.destination === 'None' || (payee.destination === 'Account' && !valid); + + const accountDisplay = + payee.destination === 'Account' + ? account + : payee.destination === 'None' + ? '' + : payee.destination === 'Controller' + ? controller + : activeAccount; + + const placeholderDisplay = + payee.destination === 'None' ? t('noPayoutAddress') : t('payoutAddress'); + + return ( + <> + <Wrapper $activeInput={inputActive}> + <div className="inner"> + <h4>{t('payoutAccount')}:</h4> + <div className="account"> + {showEmpty ? ( + <div className="emptyIcon" /> + ) : ( + <Polkicon + address={accountDisplay || ''} + size={remToUnit('2.5rem')} + /> + )} + <div className="input" ref={showingRef}> + <input + type="text" + placeholder={placeholderDisplay} + disabled={payee.destination !== 'Account'} + value={accountDisplay || ''} + onFocus={() => setInputActive(true)} + onBlur={() => setInputActive(false)} + onChange={handleChangeAccount} + /> + <div ref={hiddenRef} className="hidden"> + {payee.destination === 'Account' + ? activeAccount + : accountDisplay} + </div> + </div> + </div> + </div> + <div className="label"> + <h5> + {payee.destination === 'Account' ? ( + <> + {account === '' ? ( + t('insertPayoutAddress') + ) : !valid ? ( + t('notValidAddress') + ) : ( + <> + <FontAwesomeIcon icon={faCheck} /> + {t('validAddress')} + </> + )} + </> + ) : payee.destination === 'None' ? null : ( + <> + <FontAwesomeIcon icon={faCheck} /> + {accountMeta?.name || ''} + </> + )} + </h5> + </div> + </Wrapper> + </> + ); +}; diff --git a/src/library/PayeeInput/types.ts b/src/library/PayeeInput/types.ts new file mode 100644 index 0000000000..e8d971af5b --- /dev/null +++ b/src/library/PayeeInput/types.ts @@ -0,0 +1,13 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import type { Dispatch, SetStateAction } from 'react'; +import type { PayeeConfig } from 'contexts/Setup/types'; +import type { MaybeAddress } from 'types'; + +export interface PayeeInputProps { + payee: PayeeConfig; + account: MaybeAddress; + setAccount: Dispatch<SetStateAction<MaybeAddress>>; + handleChange: (a: MaybeAddress) => void; +} diff --git a/src/library/PluginLabel/Wrapper.ts b/src/library/PluginLabel/Wrapper.ts new file mode 100644 index 0000000000..45e48e96a1 --- /dev/null +++ b/src/library/PluginLabel/Wrapper.ts @@ -0,0 +1,23 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import styled from 'styled-components'; + +export const Wrapper = styled.div<{ $active: boolean }>` + position: absolute; + right: 10px; + top: 10px; + font-size: 0.9rem; + border-radius: 0.3rem; + padding: 0.25rem 0.4rem; + color: ${(props) => + props.$active + ? 'var(--accent-color-primary)' + : 'var(--text-color-secondary)'}; + opacity: ${(props) => (props.$active ? 1 : 0.5)}; + z-index: 2; + + > svg { + margin-right: 0.3rem; + } +`; diff --git a/src/library/PluginLabel/index.tsx b/src/library/PluginLabel/index.tsx new file mode 100644 index 0000000000..d01ba11347 --- /dev/null +++ b/src/library/PluginLabel/index.tsx @@ -0,0 +1,20 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { faProjectDiagram } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { usePlugins } from 'contexts/Plugins'; +import { capitalizeFirstLetter } from '@polkadot-cloud/utils'; +import { Wrapper } from './Wrapper'; +import type { PluginLabelProps } from './types'; + +export const PluginLabel = ({ plugin }: PluginLabelProps) => { + const { plugins } = usePlugins(); + + return ( + <Wrapper $active={plugins.includes(plugin)}> + <FontAwesomeIcon icon={faProjectDiagram} transform="shrink-4" /> + {capitalizeFirstLetter(plugin)} + </Wrapper> + ); +}; diff --git a/src/library/PluginLabel/types.ts b/src/library/PluginLabel/types.ts new file mode 100644 index 0000000000..8eabbec574 --- /dev/null +++ b/src/library/PluginLabel/types.ts @@ -0,0 +1,8 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import type { Plugin } from 'types'; + +export interface PluginLabelProps { + plugin: Plugin; +} diff --git a/src/library/Pool/Rewards.tsx b/src/library/Pool/Rewards.tsx new file mode 100644 index 0000000000..11ac4d90e6 --- /dev/null +++ b/src/library/Pool/Rewards.tsx @@ -0,0 +1,165 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import BigNumber from 'bignumber.js'; +import { useValidators } from 'contexts/Validators/ValidatorEntries'; +import { + TooltipTrigger, + ValidatorPulseWrapper, +} from 'library/ListItem/Wrappers'; +import { useTooltip } from 'contexts/Tooltip'; +import { MaxEraRewardPointsEras } from 'consts'; +import { useApi } from 'contexts/Api'; +import { + normaliseEraPoints, + prefillEraPoints, +} from 'library/ValidatorList/ValidatorItem/Utils'; +import type { AnyJson } from '@polkadot-cloud/react/types'; +import { usePoolPerformance } from 'contexts/Pools/PoolPerformance'; +import { useTranslation } from 'react-i18next'; +import type { RewardProps, RewardsGraphProps } from './types'; + +export const Rewards = ({ address, displayFor = 'default' }: RewardProps) => { + const { t } = useTranslation('library'); + const { isReady } = useApi(); + const { setTooltipTextAndOpen } = useTooltip(); + const { eraPointsBoundaries } = useValidators(); + const { poolRewardPoints, poolRewardPointsFetched } = usePoolPerformance(); + + const eraRewardPoints = Object.fromEntries( + Object.entries(poolRewardPoints[address] || {}).map(([k, v]: AnyJson) => [ + k, + new BigNumber(v), + ]) + ); + + const high = eraPointsBoundaries?.high || new BigNumber(1); + const normalisedPoints = normaliseEraPoints(eraRewardPoints, high); + const prefilledPoints = prefillEraPoints(Object.values(normalisedPoints)); + + const empty = Object.values(poolRewardPoints).length === 0; + const syncing = !isReady || poolRewardPointsFetched !== 'synced'; + const tooltipText = `${MaxEraRewardPointsEras} ${t('dayPoolPerformance')}`; + + return ( + <ValidatorPulseWrapper className={displayFor}> + {syncing && <div className="preload" />} + <TooltipTrigger + className="tooltip-trigger-element" + data-tooltip-text={tooltipText} + onMouseMove={() => setTooltipTextAndOpen(tooltipText)} + /> + <RewardsGraph points={prefilledPoints} syncing={empty} /> + </ValidatorPulseWrapper> + ); +}; + +export const RewardsGraph = ({ points = [], syncing }: RewardsGraphProps) => { + const totalSegments = points.length - 1; + const vbWidth = 512; + const vbHeight = 115; + const xPadding = 5; + const yPadding = 10; + const xArea = vbWidth - 2 * xPadding; + const yArea = vbHeight - 2 * yPadding; + const xSegment = xArea / totalSegments; + let xCursor = xPadding; + + const pointsCoords = points.map((point: number) => { + const coord = { + x: xCursor, + y: vbHeight - yPadding - yArea * point, + zero: point === 0, + }; + xCursor += xSegment; + return coord; + }); + + const lineCoords = []; + for (let i = 0; i <= pointsCoords.length - 1; i++) { + const startZero = pointsCoords[i].zero; + const endZero = pointsCoords[i + 1]?.zero; + + lineCoords.push({ + x1: pointsCoords[i].x, + y1: pointsCoords[i].y, + x2: pointsCoords[i + 1]?.x || pointsCoords[i].x, + y2: pointsCoords[i + 1]?.y || pointsCoords[i].y, + zero: startZero && endZero, + }); + } + + const barCoords = []; + for (let i = 0; i <= pointsCoords.length - 1; i++) { + barCoords.push({ + x1: pointsCoords[i].x, + y1: vbHeight - yPadding, + x2: pointsCoords[i].x, + y2: pointsCoords[i]?.y, + }); + } + + return ( + <svg + width="100%" + height="100%" + viewBox={`0 0 ${vbWidth} ${vbHeight}`} + version="1.1" + xmlns="http://www.w3.org/2000/svg" + > + {!syncing && + [{ y1: vbHeight * 0.5, y2: vbHeight * 0.5 }].map( + ({ y1, y2 }, index) => { + return ( + <line + key={`grid_coord_${index}`} + strokeWidth="3.75" + stroke="var(--grid-color-primary)" + x1={0} + y1={y1} + x2={vbWidth} + y2={y2} + opacity={0.5} + /> + ); + } + )} + + {!syncing && + barCoords.map(({ x1, y1, x2, y2 }, index) => { + return ( + <line + key={`line_coord_${index}`} + strokeWidth={5} + opacity={1} + stroke="var(--accent-color-tertiary)" + x1={x1} + y1={y1} + x2={x2} + y2={y2} + /> + ); + })} + + {!syncing && + lineCoords.map(({ x1, y1, x2, y2, zero }, index) => { + return ( + <line + key={`line_coord_${index}`} + strokeWidth={5} + opacity={zero ? 0.5 : 1} + stroke={ + zero + ? 'var(--text-color-tertiary)' + : 'var(--accent-color-secondary)' + } + x1={x1} + y1={y1} + x2={x2} + y2={y2} + /> + ); + })} + </svg> + ); +}; diff --git a/src/library/Pool/index.tsx b/src/library/Pool/index.tsx index cc7fe905c4..98ed0be7f7 100644 --- a/src/library/Pool/index.tsx +++ b/src/library/Pool/index.tsx @@ -1,22 +1,22 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import { IconProp } from '@fortawesome/fontawesome-svg-core'; import { faCopy } from '@fortawesome/free-regular-svg-icons'; import { faBars, faProjectDiagram } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { useConnect } from 'contexts/Connect'; +import { useRef } from 'react'; +import { useTranslation } from 'react-i18next'; import { useMenu } from 'contexts/Menu'; -import { useModal } from 'contexts/Modal'; import { useNotifications } from 'contexts/Notifications'; -import { NotificationText } from 'contexts/Notifications/types'; +import type { NotificationText } from 'contexts/Notifications/types'; import { useBondedPools } from 'contexts/Pools/BondedPools'; import { usePoolMemberships } from 'contexts/Pools/PoolMemberships'; -import { PoolState } from 'contexts/Pools/types'; import { useUi } from 'contexts/UI'; -import { useValidators } from 'contexts/Validators'; +import { useValidators } from 'contexts/Validators/ValidatorEntries'; +import { usePoolCommission } from 'library/Hooks/usePoolCommission'; import { FavoritePool } from 'library/ListItem/Labels/FavoritePool'; import { PoolBonded } from 'library/ListItem/Labels/PoolBonded'; +import { PoolCommission } from 'library/ListItem/Labels/PoolCommission'; import { PoolIdentity } from 'library/ListItem/Labels/PoolIdentity'; import { Labels, @@ -25,28 +25,32 @@ import { Wrapper, } from 'library/ListItem/Wrappers'; import { usePoolsTabs } from 'pages/Pools/Home/context'; -import { useRef } from 'react'; +import { useOverlay } from '@polkadot-cloud/react/hooks'; +import { useActiveAccounts } from 'contexts/ActiveAccounts'; +import { useImportedAccounts } from 'contexts/Connect/ImportedAccounts'; import { JoinPool } from '../ListItem/Labels/JoinPool'; import { Members } from '../ListItem/Labels/Members'; import { PoolId } from '../ListItem/Labels/PoolId'; -import { PoolProps } from './types'; +import type { PoolProps } from './types'; +import { Rewards } from './Rewards'; -export const Pool = (props: PoolProps) => { - const { pool, batchKey, batchIndex } = props; +export const Pool = ({ pool, batchKey, batchIndex }: PoolProps) => { + const { t } = useTranslation('library'); const { memberCounter, addresses, id, state } = pool; - - const { openModalWith } = useModal(); - const { activeAccount, isReadOnlyAccount } = useConnect(); + const { isPoolSyncing } = useUi(); const { meta } = useBondedPools(); - const { membership } = usePoolMemberships(); - const { addNotification } = useNotifications(); const { validators } = useValidators(); - const { poolsSyncing } = useUi(); - - // assumes component is under `PoolsTabsProvider` (Pools page) const { setActiveTab } = usePoolsTabs(); + const { openModal } = useOverlay().modal; + const { membership } = usePoolMemberships(); + const { activeAccount } = useActiveAccounts(); + const { addNotification } = useNotifications(); + const { isReadOnlyAccount } = useImportedAccounts(); + const { getCurrentCommission } = usePoolCommission(); const { setMenuPosition, setMenuItems, open }: any = useMenu(); + const currentCommission = getCurrentCommission(id); + // get metadata from pools metabatch const nominations = meta[batchKey]?.nominations ?? []; @@ -54,8 +58,8 @@ export const Pool = (props: PoolProps) => { const targets = nominations[batchIndex]?.targets ?? []; // extract validator entries from pool targets - const targetValidators = validators.filter((v: any) => - targets.includes(v.address) + const targetValidators = validators.filter(({ address }) => + targets.includes(address) ); // configure floating menu position @@ -66,35 +70,34 @@ export const Pool = (props: PoolProps) => { addresses.stash == null ? null : { - title: 'Address Copied to Clipboard', + title: t('addressCopiedToClipboard'), subtitle: addresses.stash, }; // consruct pool menu items - const menuItems: Array<any> = []; + const menuItems: any[] = []; // add view pool nominations button to menu menuItems.push({ - icon: <FontAwesomeIcon icon={faProjectDiagram as IconProp} />, + icon: <FontAwesomeIcon icon={faProjectDiagram} transform="shrink-3" />, wrap: null, - title: `View Pool Nominations`, + title: `${t('viewPoolNominations')}`, cb: () => { - openModalWith( - 'PoolNominations', - { + openModal({ + key: 'PoolNominations', + options: { nominator: addresses.stash, targets: targetValidators, }, - 'large' - ); + }); }, }); // add copy pool address button to menu menuItems.push({ - icon: <FontAwesomeIcon icon={faCopy as IconProp} />, + icon: <FontAwesomeIcon icon={faCopy} transform="shrink-3" />, wrap: null, - title: `Copy Pool Address`, + title: t('copyPoolAddress'), cb: () => { navigator.clipboard.writeText(addresses.stash); if (notificationCopyAddress) { @@ -111,11 +114,18 @@ export const Pool = (props: PoolProps) => { } }; + const displayJoin = + !isPoolSyncing && + state === 'Open' && + !membership && + !isReadOnlyAccount(activeAccount) && + activeAccount; + return ( - <Wrapper format="nomination"> + <Wrapper className={displayJoin ? 'pool-join' : 'pool'}> <div className="inner"> <MenuPosition ref={posRef} /> - <div className="row"> + <div className="row top"> <PoolIdentity batchKey={batchKey} batchIndex={batchIndex} @@ -124,34 +134,40 @@ export const Pool = (props: PoolProps) => { <div> <Labels> <FavoritePool address={addresses.stash} /> - <PoolId id={id} /> - <Members members={memberCounter} /> - <button - type="button" - className="label" - onClick={() => toggleMenu()} - > - <FontAwesomeIcon icon={faBars} /> - </button> + <div className="label"> + <button type="button" onClick={() => toggleMenu()}> + <FontAwesomeIcon icon={faBars} transform="shrink-2" /> + </button> + </div> </Labels> </div> </div> <Separator /> - <div className="row status"> - <PoolBonded pool={pool} batchIndex={batchIndex} batchKey={batchKey} /> - {!poolsSyncing && - state === PoolState.Open && - !membership && - !isReadOnlyAccount(activeAccount) && - activeAccount && ( - <Labels> + <div className="row bottom lg"> + <div> + <Rewards address={addresses.stash} displayFor="default" /> + </div> + <div> + <Labels style={{ marginBottom: '0.9rem' }}> + {currentCommission > 0 && ( + <PoolCommission commission={`${currentCommission}%`} /> + )} + <PoolId id={id} /> + <Members members={memberCounter} /> + </Labels> + <PoolBonded + pool={pool} + batchIndex={batchIndex} + batchKey={batchKey} + /> + {displayJoin && ( + <Labels style={{ marginTop: '1rem' }}> <JoinPool id={id} setActiveTab={setActiveTab} /> </Labels> )} + </div> </div> </div> </Wrapper> ); }; - -export default Pool; diff --git a/src/library/Pool/types.ts b/src/library/Pool/types.ts index d905e8f6eb..acaf7ae0b0 100644 --- a/src/library/Pool/types.ts +++ b/src/library/Pool/types.ts @@ -1,7 +1,8 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import { PoolAddresses, PoolRoles, PoolState } from 'contexts/Pools/types'; +import type { PoolAddresses, PoolRoles, PoolState } from 'contexts/Pools/types'; +import type { DisplayFor } from 'types'; export interface PoolProps { pool: Pool; @@ -17,3 +18,13 @@ export interface Pool { state: PoolState; roles: PoolRoles; } + +export interface RewardProps { + address: string; + displayFor?: DisplayFor; +} + +export interface RewardsGraphProps { + points: number[]; + syncing: boolean; +} diff --git a/src/library/PoolAccount/Wrapper.ts b/src/library/PoolAccount/Wrapper.ts deleted file mode 100644 index 696981d7b6..0000000000 --- a/src/library/PoolAccount/Wrapper.ts +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { motion } from 'framer-motion'; -import styled from 'styled-components'; -import { borderSecondary, textSecondary } from 'theme'; -import { WrapperProps } from './types'; - -export const Wrapper = styled(motion.button)<WrapperProps>` - border-radius: 1rem; - box-shadow: none; - display: flex; - flex-flow: row wrap; - justify-content: flex-start; - align-items: center; - cursor: ${(props) => props.cursor}; - background: ${(props) => props.fill}; - font-size: ${(props) => props.fontSize}; - padding: 0 1rem; - max-width: 250px; - flex: 1; - - .identicon { - margin: 0.15rem 0.25rem 0 0; - } - - .account-label { - border-right: 1px solid ${borderSecondary}; - color: ${textSecondary}; - font-size: 0.8em; - display: flex; - flex-flow: row nowrap; - align-items: center; - justify-content: flex-end; - margin-right: 0.5rem; - padding-right: 0.5rem; - text-overflow: ellipsis; - white-space: nowrap; - overflow: hidden; - flex-shrink: 1; - } - - .title { - color: ${textSecondary}; - margin-left: 0.25rem; - text-overflow: ellipsis; - white-space: nowrap; - overflow: hidden; - line-height: 2.2rem; - flex: 1; - - &.syncing { - opacity: 0.4; - } - - &.unassigned { - color: ${textSecondary}; - opacity: 0.45; - } - } - - .wallet { - width: 1em; - height: 1em; - margin-left: 0.8rem; - opacity: 0.8; - - path { - fill: ${textSecondary}; - } - } -`; - -export default Wrapper; diff --git a/src/library/PoolAccount/types.ts b/src/library/PoolAccount/types.ts deleted file mode 100644 index 4159bcc11b..0000000000 --- a/src/library/PoolAccount/types.ts +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -export interface PoolAccountProps { - label?: string; - pool?: any; - filled?: boolean; - fontSize?: string; - canClick: any; - onClick?: () => void; - value?: string; - title?: string | undefined; -} - -export interface WrapperProps { - fill: string; - fontSize: string; - cursor: string; -} diff --git a/src/library/PoolList/Default.tsx b/src/library/PoolList/Default.tsx new file mode 100644 index 0000000000..9c470d015b --- /dev/null +++ b/src/library/PoolList/Default.tsx @@ -0,0 +1,282 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { faBars, faGripVertical } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { isNotZero } from '@polkadot-cloud/utils'; +import { motion } from 'framer-motion'; +import React, { useEffect, useRef, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { ListItemsPerBatch, ListItemsPerPage } from 'consts'; +import { useApi } from 'contexts/Api'; +import { useFilters } from 'contexts/Filters'; +import { useNetworkMetrics } from 'contexts/NetworkMetrics'; +import { useBondedPools } from 'contexts/Pools/BondedPools'; +import { useTheme } from 'contexts/Themes'; +import { useUi } from 'contexts/UI'; +import { Tabs } from 'library/Filter/Tabs'; +import { usePoolFilters } from 'library/Hooks/usePoolFilters'; +import { + FilterHeaderWrapper, + List, + ListStatusHeader, + Wrapper as ListWrapper, +} from 'library/List'; +import { MotionContainer } from 'library/List/MotionContainer'; +import { Pagination } from 'library/List/Pagination'; +import { SearchInput } from 'library/List/SearchInput'; +import { Pool } from 'library/Pool'; +import { useNetwork } from 'contexts/Network'; +import { usePoolList } from './context'; +import type { PoolListProps } from './types'; + +export const PoolList = ({ + allowMoreCols, + pagination, + batchKey = '', + disableThrottle, + allowSearch, + pools, + defaultFilters, + allowListFormat = true, +}: PoolListProps) => { + const { t } = useTranslation('library'); + const { mode } = useTheme(); + const { isReady } = useApi(); + const { + networkData: { colors }, + } = useNetwork(); + const { isSyncing } = useUi(); + const { applyFilter } = usePoolFilters(); + const { activeEra } = useNetworkMetrics(); + const { listFormat, setListFormat } = usePoolList(); + const { getFilters, setMultiFilters, getSearchTerm, setSearchTerm } = + useFilters(); + const { fetchPoolsMetaBatch, poolSearchFilter, meta } = useBondedPools(); + + const includes = getFilters('include', 'pools'); + const excludes = getFilters('exclude', 'pools'); + const searchTerm = getSearchTerm('pools'); + + // current page + const [page, setPage] = useState<number>(1); + + // current render iteration + const [renderIteration, setRenderIterationState] = useState<number>(1); + + // default list of pools + const [poolsDefault, setPoolsDefault] = useState(pools); + + // manipulated list (ordering, filtering) of pools + const [listPools, setListPools] = useState(pools); + + // is this the initial fetch + const [fetched, setFetched] = useState<boolean>(false); + + // render throttle iteration + const renderIterationRef = useRef(renderIteration); + const setRenderIteration = (iter: number) => { + renderIterationRef.current = iter; + setRenderIterationState(iter); + }; + + // pagination + const totalPages = Math.ceil(listPools.length / ListItemsPerPage); + const pageEnd = page * ListItemsPerPage - 1; + const pageStart = pageEnd - (ListItemsPerPage - 1); + + // render batch + const batchEnd = Math.min( + renderIteration * ListItemsPerBatch - 1, + ListItemsPerPage + ); + + // get throttled subset or entire list + const poolsToDisplay = disableThrottle + ? listPools + : listPools.slice(pageStart).slice(0, ListItemsPerPage); + + // handle pool list bootstrapping + const setupPoolList = () => { + setPoolsDefault(pools); + setListPools(pools); + setFetched(true); + fetchPoolsMetaBatch(batchKey, pools, true); + }; + + // handle filter / order update + const handlePoolsFilterUpdate = ( + filteredPools: any = Object.assign(poolsDefault) + ) => { + filteredPools = applyFilter(includes, excludes, filteredPools, batchKey); + if (searchTerm) { + filteredPools = poolSearchFilter(filteredPools, batchKey, searchTerm); + } + setListPools(filteredPools); + setPage(1); + setRenderIteration(1); + }; + + const handleSearchChange = (e: React.FormEvent<HTMLInputElement>) => { + const newValue = e.currentTarget.value; + let filteredPools = Object.assign(poolsDefault); + filteredPools = applyFilter(includes, excludes, filteredPools, batchKey); + filteredPools = poolSearchFilter(filteredPools, batchKey, newValue); + + // ensure no duplicates + filteredPools = filteredPools.filter( + (value: any, index: number, self: any) => + index === self.findIndex((i: any) => i.id === value.id) + ); + setPage(1); + setRenderIteration(1); + setListPools(filteredPools); + setSearchTerm('pools', newValue); + }; + + // Refetch list when pool list changes. + useEffect(() => { + if (pools !== poolsDefault) { + setFetched(false); + } + }, [pools]); + + // Configure pool list when network is ready to fetch. + useEffect(() => { + if (isReady && isNotZero(activeEra.index) && !fetched) { + setupPoolList(); + } + }, [isReady, fetched, activeEra.index]); + + // Render throttling. Only render a batch of pools at a time. + useEffect(() => { + if (!(batchEnd >= pageEnd || disableThrottle)) { + setTimeout(() => { + setRenderIteration(renderIterationRef.current + 1); + }, 500); + } + }, [renderIterationRef.current]); + + // List ui changes / validator changes trigger re-render of list. + useEffect(() => { + // only filter when pool nominations have been synced. + if (!isSyncing && meta[batchKey]?.nominations) { + handlePoolsFilterUpdate(); + } + }, [isSyncing, includes, excludes, meta]); + + // Scroll to top of the window on every filter. + useEffect(() => { + window.scrollTo(0, 0); + }, [includes, excludes]); + + // Set default filters. + useEffect(() => { + if (defaultFilters?.includes?.length) { + setMultiFilters('include', 'pools', defaultFilters?.includes, false); + } + if (defaultFilters?.excludes?.length) { + setMultiFilters('exclude', 'pools', defaultFilters?.excludes, false); + } + }, []); + + return ( + <ListWrapper> + <List $flexBasisLarge={allowMoreCols ? '33.33%' : '50%'}> + {allowSearch && poolsDefault.length > 0 && ( + <SearchInput + handleChange={handleSearchChange} + placeholder={t('search')} + /> + )} + <FilterHeaderWrapper> + <div> + <Tabs + config={[ + { + label: t('all'), + includes: [], + excludes: [], + }, + { + label: t('active'), + includes: ['active'], + excludes: ['locked', 'destroying'], + }, + { + label: t('locked'), + includes: ['locked'], + excludes: [], + }, + { + label: t('destroying'), + includes: ['destroying'], + excludes: [], + }, + ]} + activeIndex={1} + /> + </div> + <div> + {allowListFormat && ( + <div> + <button type="button" onClick={() => setListFormat('row')}> + <FontAwesomeIcon + icon={faBars} + color={ + listFormat === 'row' ? colors.primary[mode] : 'inherit' + } + /> + </button> + <button type="button" onClick={() => setListFormat('col')}> + <FontAwesomeIcon + icon={faGripVertical} + color={ + listFormat === 'col' ? colors.primary[mode] : 'inherit' + } + /> + </button> + </div> + )} + </div> + </FilterHeaderWrapper> + + {pagination && poolsToDisplay.length > 0 && ( + <Pagination page={page} total={totalPages} setter={setPage} /> + )} + <MotionContainer> + {poolsToDisplay.length ? ( + <> + {poolsToDisplay.map((pool: any, index: number) => ( + <motion.div + className={`item ${listFormat === 'row' ? 'row' : 'col'}`} + key={`nomination_${index}`} + variants={{ + hidden: { + y: 15, + opacity: 0, + }, + show: { + y: 0, + opacity: 1, + }, + }} + > + <Pool + pool={pool} + batchKey={batchKey} + batchIndex={poolsDefault.indexOf(pool)} + /> + </motion.div> + ))} + </> + ) : ( + <ListStatusHeader> + {isSyncing ? `${t('syncingPoolList')}...` : t('noMatch')} + </ListStatusHeader> + )} + </MotionContainer> + </List> + </ListWrapper> + ); +}; diff --git a/src/library/PoolList/FilterPools.tsx b/src/library/PoolList/FilterPools.tsx deleted file mode 100644 index 10f8523278..0000000000 --- a/src/library/PoolList/FilterPools.tsx +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { faCheckCircle, faCircle } from '@fortawesome/free-regular-svg-icons'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { useFilters } from 'contexts/Filters'; -import { FilterType } from 'contexts/Filters/types'; -import { usePoolFilters } from 'library/Hooks/usePoolFilters'; -import { Title } from 'library/Overlay/Title'; -import { FilterListButton, FilterListWrapper } from 'library/Overlay/Wrappers'; - -export const FilterPools = () => { - const { getFilters, toggleFilter } = useFilters(); - const { includesToLabels, excludesToLabels } = usePoolFilters(); - - const includes = getFilters(FilterType.Include, 'pools'); - const excludes = getFilters(FilterType.Exclude, 'pools'); - - return ( - <FilterListWrapper> - <Title title="Filter Pools" /> - <div className="body"> - <h4>Include:</h4> - {Object.entries(includesToLabels).map(([f, l]: any, i: number) => ( - <FilterListButton - active={includes?.includes(f) ?? false} - key={`pool_include_${i}`} - type="button" - onClick={() => { - toggleFilter(FilterType.Include, 'pools', f); - }} - > - <FontAwesomeIcon - transform="grow-4" - icon={includes?.includes(f) ? faCheckCircle : faCircle} - /> - <h4>{l}</h4> - </FilterListButton> - ))} - - <h4>Exclude:</h4> - {Object.entries(excludesToLabels).map(([f, l]: any, i: number) => ( - <FilterListButton - active={excludes?.includes(f) ?? false} - key={`validator_filter_${i}`} - type="button" - onClick={() => { - toggleFilter(FilterType.Exclude, 'pools', f); - }} - > - <FontAwesomeIcon - transform="grow-5" - icon={excludes?.includes(f) ? faCheckCircle : faCircle} - /> - <h4>{l}</h4> - </FilterListButton> - ))} - </div> - </FilterListWrapper> - ); -}; diff --git a/src/library/PoolList/Filters.tsx b/src/library/PoolList/Filters.tsx deleted file mode 100644 index ef594b6492..0000000000 --- a/src/library/PoolList/Filters.tsx +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { - faBan, - faCheck, - faFilterCircleXmark, -} from '@fortawesome/free-solid-svg-icons'; -import { - ButtonInvertRounded, - ButtonSecondary, -} from '@rossbulat/polkadot-dashboard-ui'; -import { useFilters } from 'contexts/Filters'; -import { FilterType } from 'contexts/Filters/types'; -import { useOverlay } from 'contexts/Overlay'; -import { Container } from 'library/Filter/Container'; -import { Item } from 'library/Filter/Item'; -import { useEffect } from 'react'; -import { usePoolFilters } from '../Hooks/usePoolFilters'; -import { FilterPools } from './FilterPools'; - -export const Filters = () => { - const { resetFilters, getFilters, toggleFilter } = useFilters(); - const { includesToLabels, excludesToLabels } = usePoolFilters(); - const { openOverlayWith } = useOverlay(); - - const includes = getFilters(FilterType.Include, 'pools'); - const excludes = getFilters(FilterType.Exclude, 'pools'); - const hasFilters = includes?.length || excludes?.length; - - // scroll to top of the window on every filter. - useEffect(() => { - window.scrollTo(0, 0); - }, [includes, excludes]); - - return ( - <> - <div style={{ marginBottom: '1.1rem' }}> - <ButtonInvertRounded - text="Filter" - marginRight - iconLeft={faFilterCircleXmark} - onClick={() => { - openOverlayWith(<FilterPools />); - }} - /> - <ButtonSecondary - text="Clear" - onClick={() => { - resetFilters(FilterType.Include, 'pools'); - resetFilters(FilterType.Exclude, 'pools'); - }} - disabled={!hasFilters} - /> - </div> - <Container> - <div className="items"> - {!hasFilters && <Item label="No filters" disabled />} - {includes?.map((e: string, i: number) => ( - <Item - key={`pool_include_${i}`} - label={includesToLabels[e]} - icon={faCheck} - transform="grow-2" - onClick={() => { - toggleFilter(FilterType.Include, 'pools', e); - }} - /> - ))} - {excludes?.map((e: string, i: number) => ( - <Item - key={`pool_filter_${i}`} - label={excludesToLabels[e]} - icon={faBan} - transform="grow-0" - onClick={() => { - toggleFilter(FilterType.Exclude, 'pools', e); - }} - /> - ))} - </div> - </Container> - </> - ); -}; diff --git a/src/library/PoolList/context.tsx b/src/library/PoolList/context.tsx index 0ced38ac05..f441f39664 100644 --- a/src/library/PoolList/context.tsx +++ b/src/library/PoolList/context.tsx @@ -1,13 +1,12 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only import React, { useState } from 'react'; +import { defaultListFormat, defaultPoolList } from './defaults'; +import type { ListFormat, PoolListContextProps } from './types'; -export const PoolListContext: React.Context<any> = React.createContext({ - // eslint-disable-next-line - setListFormat: (v: string) => {}, - listFormat: 'col', -}); +export const PoolListContext: React.Context<PoolListContextProps> = + React.createContext(defaultPoolList); export const usePoolList = () => React.useContext(PoolListContext); @@ -16,11 +15,10 @@ export const PoolListProvider = ({ }: { children: React.ReactNode; }) => { - const [listFormat, _setListFormat] = useState('col'); + const [listFormat, setListFormatState] = + useState<ListFormat>(defaultListFormat); - const setListFormat = (v: string) => { - _setListFormat(v); - }; + const setListFormat = (v: ListFormat) => setListFormatState(v); return ( <PoolListContext.Provider diff --git a/src/library/PoolList/defaults.ts b/src/library/PoolList/defaults.ts new file mode 100644 index 0000000000..ffc885bc74 --- /dev/null +++ b/src/library/PoolList/defaults.ts @@ -0,0 +1,12 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only +/* eslint-disable @typescript-eslint/no-unused-vars */ + +import type { PoolListContextProps } from './types'; + +export const defaultListFormat = 'col'; + +export const defaultPoolList: PoolListContextProps = { + setListFormat: (v) => {}, + listFormat: defaultListFormat, +}; diff --git a/src/library/PoolList/index.tsx b/src/library/PoolList/index.tsx deleted file mode 100644 index a7152822dc..0000000000 --- a/src/library/PoolList/index.tsx +++ /dev/null @@ -1,276 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { faBars, faGripVertical } from '@fortawesome/free-solid-svg-icons'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { ListItemsPerBatch, ListItemsPerPage } from 'consts'; -import { useApi } from 'contexts/Api'; -import { useFilters } from 'contexts/Filters'; -import { FilterType } from 'contexts/Filters/types'; -import { useNetworkMetrics } from 'contexts/Network'; -import { useBondedPools } from 'contexts/Pools/BondedPools'; -import { StakingContext } from 'contexts/Staking'; -import { useTheme } from 'contexts/Themes'; -import { useUi } from 'contexts/UI'; -import { motion } from 'framer-motion'; -import { usePoolFilters } from 'library/Hooks/usePoolFilters'; -import { Header, List, Wrapper as ListWrapper } from 'library/List'; -import { MotionContainer } from 'library/List/MotionContainer'; -import { Pagination } from 'library/List/Pagination'; -import { SearchInput } from 'library/List/SearchInput'; -import { Pool } from 'library/Pool'; -import React, { useEffect, useRef, useState } from 'react'; -import { networkColors } from 'theme/default'; -import { PoolListProvider, usePoolList } from './context'; -import { Filters } from './Filters'; -import { PoolListProps } from './types'; - -export const PoolListInner = ({ - allowMoreCols, - pagination, - batchKey = '', - disableThrottle, - allowSearch, - pools, - title, - defaultFilters, -}: PoolListProps) => { - const { mode } = useTheme(); - const { isReady, network } = useApi(); - const { metrics } = useNetworkMetrics(); - const { fetchPoolsMetaBatch, poolSearchFilter, meta } = useBondedPools(); - const { listFormat, setListFormat } = usePoolList(); - const { isSyncing } = useUi(); - - const { getFilters, setMultiFilters, getSearchTerm, setSearchTerm } = - useFilters(); - const { applyFilter } = usePoolFilters(); - const includes = getFilters(FilterType.Include, 'pools'); - const excludes = getFilters(FilterType.Exclude, 'pools'); - const searchTerm = getSearchTerm('pools'); - - // current page - const [page, setPage] = useState<number>(1); - - // current render iteration - const [renderIteration, _setRenderIteration] = useState<number>(1); - - // default list of pools - const [poolsDefault, setPoolsDefault] = useState(pools); - - // manipulated list (ordering, filtering) of pools - const [_pools, _setPools] = useState(pools); - - // is this the initial fetch - const [fetched, setFetched] = useState<boolean>(false); - - // render throttle iteration - const renderIterationRef = useRef(renderIteration); - const setRenderIteration = (iter: number) => { - renderIterationRef.current = iter; - _setRenderIteration(iter); - }; - - // pagination - const totalPages = Math.ceil(_pools.length / ListItemsPerPage); - const pageEnd = page * ListItemsPerPage - 1; - const pageStart = pageEnd - (ListItemsPerPage - 1); - - // render batch - const batchEnd = renderIteration * ListItemsPerBatch - 1; - - // refetch list when pool list changes - useEffect(() => { - if (pools !== poolsDefault) { - setFetched(false); - } - }, [pools]); - - // configure pool list when network is ready to fetch - useEffect(() => { - if (isReady && metrics.activeEra.index !== 0 && !fetched) { - setupPoolList(); - } - }, [isReady, fetched, metrics.activeEra.index]); - - // handle pool list bootstrapping - const setupPoolList = () => { - setPoolsDefault(pools); - _setPools(pools); - setFetched(true); - fetchPoolsMetaBatch(batchKey, pools, true); - }; - - // render throttle - useEffect(() => { - if (!(batchEnd >= pageEnd || disableThrottle)) { - setTimeout(() => { - setRenderIteration(renderIterationRef.current + 1); - }, 500); - } - }, [renderIterationRef.current]); - - // list ui changes / validator changes trigger re-render of list - useEffect(() => { - // only filter when pool nominations have been synced. - if (!isSyncing && meta[batchKey]?.nominations) { - handlePoolsFilterUpdate(); - } - }, [isSyncing, includes?.length, excludes?.length, meta]); - - // set default filters - useEffect(() => { - if (defaultFilters?.includes?.length) { - setMultiFilters(FilterType.Include, 'pools', defaultFilters?.includes); - } - if (defaultFilters?.excludes?.length) { - setMultiFilters(FilterType.Exclude, 'pools', defaultFilters?.excludes); - } - }, []); - - // handle filter / order update - const handlePoolsFilterUpdate = ( - filteredPools: any = Object.assign(poolsDefault) - ) => { - filteredPools = applyFilter(includes, excludes, filteredPools, batchKey); - if (searchTerm) { - filteredPools = poolSearchFilter(filteredPools, batchKey, searchTerm); - } - _setPools(filteredPools); - setPage(1); - setRenderIteration(1); - }; - - // get pools to render - let listPools = []; - - // get throttled subset or entire list - if (!disableThrottle) { - listPools = _pools.slice(pageStart).slice(0, ListItemsPerPage); - } else { - listPools = _pools; - } - - const handleSearchChange = (e: React.FormEvent<HTMLInputElement>) => { - const newValue = e.currentTarget.value; - let filteredPools = Object.assign(poolsDefault); - filteredPools = applyFilter(includes, excludes, filteredPools, batchKey); - filteredPools = poolSearchFilter(filteredPools, batchKey, newValue); - - // ensure no duplicates - filteredPools = filteredPools.filter( - (value: any, index: any, self: any) => - index === self.findIndex((t: any) => t.id === value.id) - ); - - setPage(1); - setRenderIteration(1); - _setPools(filteredPools); - setSearchTerm('pools', newValue); - }; - - return ( - <ListWrapper> - <Header> - <div> - <h4>{title}</h4> - </div> - <div> - <button type="button" onClick={() => setListFormat('row')}> - <FontAwesomeIcon - icon={faBars} - color={ - listFormat === 'row' - ? networkColors[`${network.name}-${mode}`] - : 'inherit' - } - /> - </button> - <button type="button" onClick={() => setListFormat('col')}> - <FontAwesomeIcon - icon={faGripVertical} - color={ - listFormat === 'col' - ? networkColors[`${network.name}-${mode}`] - : 'inherit' - } - /> - </button> - </div> - </Header> - <List flexBasisLarge={allowMoreCols ? '33.33%' : '50%'}> - {allowSearch && poolsDefault.length > 0 && ( - <SearchInput - handleChange={handleSearchChange} - placeholder="Search Pool ID, Name or Address" - /> - )} - <Filters /> - {pagination && listPools.length > 0 && ( - <Pagination page={page} total={totalPages} setter={setPage} /> - )} - <MotionContainer> - {listPools.length ? ( - <> - {listPools.map((pool: any, index: number) => { - // fetch batch data by referring to default list index. - const batchIndex = poolsDefault.indexOf(pool); - - return ( - <motion.div - className={`item ${listFormat === 'row' ? 'row' : 'col'}`} - key={`nomination_${index}`} - variants={{ - hidden: { - y: 15, - opacity: 0, - }, - show: { - y: 0, - opacity: 1, - }, - }} - > - <Pool - pool={pool} - batchKey={batchKey} - batchIndex={batchIndex} - /> - </motion.div> - ); - })} - </> - ) : ( - <h4 style={{ padding: '1rem 1rem 0 1rem' }}> - {isSyncing - ? 'Syncing Pool list...' - : 'No pools match this criteria.'} - </h4> - )} - </MotionContainer> - </List> - </ListWrapper> - ); -}; - -export const PoolList = (props: any) => { - return ( - <PoolListProvider> - <PoolListShouldUpdate {...props} /> - </PoolListProvider> - ); -}; - -export class PoolListShouldUpdate extends React.Component<any, any> { - static contextType = StakingContext; - - shouldComponentUpdate(nextProps: PoolListProps) { - return this.props.pools !== nextProps.pools; - } - - render() { - return <PoolListInner {...this.props} />; - } -} - -export default PoolList; diff --git a/src/library/PoolList/types.ts b/src/library/PoolList/types.ts index be4fb32fdb..e99b0de132 100644 --- a/src/library/PoolList/types.ts +++ b/src/library/PoolList/types.ts @@ -1,17 +1,23 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only +export type ListFormat = 'row' | 'col'; + +export interface PoolListContextProps { + setListFormat: (v: ListFormat) => void; + listFormat: ListFormat; +} export interface PoolListProps { - allowMoreCols?: string; + allowMoreCols?: boolean; allowSearch?: boolean; - pagination?: number; + pagination?: boolean; batchKey?: string; disableThrottle?: boolean; refetchOnListUpdate?: string; + allowListFormat?: boolean; pools?: any; - title?: string; defaultFilters?: { - includes: Array<string> | null; - excludes: Array<string> | null; + includes: string[] | null; + excludes: string[] | null; }; } diff --git a/src/library/Prompt/Title.tsx b/src/library/Prompt/Title.tsx new file mode 100644 index 0000000000..95d287958b --- /dev/null +++ b/src/library/Prompt/Title.tsx @@ -0,0 +1,59 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import type { IconProp } from '@fortawesome/fontawesome-svg-core'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { ButtonHelp, ButtonSecondary } from '@polkadot-cloud/react'; +import type { FunctionComponent } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useHelp } from 'contexts/Help'; +import { usePrompt } from 'contexts/Prompt'; +import { TitleWrapper } from './Wrappers'; + +interface TitleProps { + title: string; + icon?: IconProp; + Svg?: FunctionComponent<any>; + helpKey?: string; + hideDone?: boolean; + closeText?: string; +} + +export const Title = ({ + helpKey, + title, + icon, + Svg, + hideDone, + closeText, +}: TitleProps) => { + const { t } = useTranslation('library'); + const { closePrompt } = usePrompt(); + const { openHelp } = useHelp(); + + const graphic = Svg ? ( + <Svg style={{ width: '1.5rem', height: '1.5rem' }} /> + ) : icon ? ( + <FontAwesomeIcon transform="grow-3" icon={icon} /> + ) : null; + + return ( + <TitleWrapper> + <div> + {graphic} + <h2> + {title} + {helpKey ? <ButtonHelp onClick={() => openHelp(helpKey)} /> : null} + </h2> + </div> + {hideDone !== true ? ( + <div> + <ButtonSecondary + text={closeText || t('done')} + onClick={() => closePrompt()} + /> + </div> + ) : null} + </TitleWrapper> + ); +}; diff --git a/src/library/Overlay/Wrappers.tsx b/src/library/Prompt/Wrappers.ts similarity index 50% rename from src/library/Overlay/Wrappers.tsx rename to src/library/Prompt/Wrappers.ts index 4bf1b868c1..f73c3466f9 100644 --- a/src/library/Overlay/Wrappers.tsx +++ b/src/library/Prompt/Wrappers.ts @@ -1,25 +1,14 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only import styled from 'styled-components'; -import { - buttonPrimaryBackground, - cardShadow, - modalBackground, - networkColor, - networkColorStroke, - overlayBackground, - shadowColor, - textPrimary, - textSecondary, -} from 'theme'; - -export const OverlayWrapper = styled.div` - background: ${overlayBackground}; + +export const PromptWrapper = styled.div` + background: var(--overlay-background-color); position: fixed; width: 100%; height: 100%; - z-index: 9; + z-index: 11; /* content wrapper */ > div { @@ -28,7 +17,7 @@ export const OverlayWrapper = styled.div` flex-flow: row wrap; justify-content: center; align-items: center; - padding: 1rem 2rem; + padding: 2rem 2rem; /* click anywhere behind overlay to close */ .close { @@ -38,11 +27,20 @@ export const OverlayWrapper = styled.div` z-index: 8; cursor: default; } + + /* status message placed below title */ + h4.subheading { + margin-bottom: 1rem; + } + + /* padded content to give extra spacing */ + .padded { + padding: 1rem 1.5rem; + } } `; export const HeightWrapper = styled.div<{ size: string }>` - box-shadow: ${cardShadow} ${shadowColor}; transition: height 0.5s cubic-bezier(0.1, 1, 0.2, 1); width: 100%; max-width: ${(props) => (props.size === 'small' ? '500px' : '700px')}; @@ -50,29 +48,28 @@ export const HeightWrapper = styled.div<{ size: string }>` border-radius: 1.5rem; z-index: 9; position: relative; - overflow: hidden; + overflow: auto; `; export const ContentWrapper = styled.div` - background: ${modalBackground}; + background: var(--background-default); width: 100%; height: auto; overflow: hidden; position: relative; a { - color: ${networkColor}; + color: var(--accent-color-primary); } .header { width: 100%; display: flex; flex-flow: row wrap; align-items: center; - padding: 1rem 1rem 0 1rem; + padding: 1rem 2rem 0 2rem; } .body { - padding: 0.5rem 1.5rem 1rem 1.5rem; - + padding: 0.5rem 1.5rem 1.25rem 1.5rem; h4 { margin: 1rem 0; } @@ -83,7 +80,6 @@ export const TitleWrapper = styled.div` padding: 1.5rem 1rem 0 1rem; display: flex; flex-flow: row wrap; - justify-content: flex-start; align-items: center; width: 100%; @@ -98,7 +94,7 @@ export const TitleWrapper = styled.div` } path { - fill: ${textPrimary}; + fill: var(--text-color-primary); } &:first-child { @@ -106,11 +102,9 @@ export const TitleWrapper = styled.div` > h2 { display: flex; - flex-flow: row nowrap; align-items: center; font-family: 'Unbounded', 'sans-serif', sans-serif; font-size: 1.3rem; - margin: 0; > button { margin-left: 0.85rem; @@ -133,10 +127,13 @@ export const FilterListWrapper = styled.div` } `; -export const FilterListButton = styled.button<{ active: boolean }>` +export const FilterListButton = styled.button<{ $active: boolean }>` border: 1px solid - ${(props) => (props.active ? networkColorStroke : buttonPrimaryBackground)}; - background: ${buttonPrimaryBackground}; + ${(props) => + props.$active + ? 'var(--accent-color-stroke)' + : 'var(--button-primary-background)'}; + background: var(--button-primary-background); width: 100%; display: flex; flex-flow: row wrap; @@ -144,20 +141,61 @@ export const FilterListButton = styled.button<{ active: boolean }>` border-radius: 1rem; padding: 0rem 1rem; margin: 1rem 0; - transition: border 0.1s; + transition: border var(--transition-duration); h4 { - color: ${(props) => (props.active ? networkColorStroke : textSecondary)}; - font-variation-settings: 'wght' 560; - transition: color 0.1s; - margin: 0; + color: ${(props) => + props.$active + ? 'var(--accent-color-stroke)' + : 'var(--text-color-secondary)'}; + transition: color var(--transition-duration); } svg { - color: ${(props) => (props.active ? networkColorStroke : textSecondary)}; - opacity: ${(props) => (props.active ? 1 : 0.7)}; - transition: color 0.1s; + color: ${(props) => + props.$active + ? 'var(--accent-color-stroke)' + : 'var(--text-color-secondary)'}; + opacity: ${(props) => (props.$active ? 1 : 0.7)}; + transition: color var(--transition-duration); margin-left: 0.2rem; margin-right: 0.9rem; } `; + +export const FooterWrapper = styled.div` + margin: 1.5rem 0 0.5rem 0; +`; + +export const PromptListItem = styled.div` + display: flex; + align-items: center; + border-bottom: 1px solid var(--border-primary-color); + + &.inactive { + opacity: var(--opacity-disabled); + } +`; + +export const PromptSelectItem = styled.button` + border-bottom: 1px solid var(--border-primary-color); + display: flex; + flex-direction: column; + align-items: flex-start; + padding: 1rem 0.5rem; + border-radius: 0.25rem; + width: 100%; + + > h4 { + margin-top: 0.3rem; + } + &:hover { + background: var(--button-hover-background); + } + &.inactive { + h3, + h4 { + color: var(--accent-color-primary); + } + } +`; diff --git a/src/library/Prompt/index.tsx b/src/library/Prompt/index.tsx new file mode 100644 index 0000000000..55c6842cdb --- /dev/null +++ b/src/library/Prompt/index.tsx @@ -0,0 +1,26 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { usePrompt } from 'contexts/Prompt'; +import { ContentWrapper, HeightWrapper, PromptWrapper } from './Wrappers'; + +export const Prompt = () => { + const { closePrompt, size, status, Prompt: PromptInner } = usePrompt(); + + if (status === 0) { + return <></>; + } + + return ( + <PromptWrapper> + <div> + <HeightWrapper size={size}> + <ContentWrapper>{PromptInner}</ContentWrapper> + </HeightWrapper> + <button type="button" className="close" onClick={() => closePrompt()}> +   + </button> + </div> + </PromptWrapper> + ); +}; diff --git a/src/library/QRCode/Display.tsx b/src/library/QRCode/Display.tsx new file mode 100644 index 0000000000..7563ca79fa --- /dev/null +++ b/src/library/QRCode/Display.tsx @@ -0,0 +1,121 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { objectSpread } from '@polkadot/util'; +import { xxhashAsHex } from '@polkadot/util-crypto'; +import React, { useEffect, useMemo, useRef, useState } from 'react'; +import { DisplayWrapper } from './Wrappers.js'; +import { qrcode } from './qrcode.js'; +import type { DisplayProps, FrameState, TimerState } from './types.js'; +import { createFrames, createImgSize } from './util.js'; + +const DEFAULT_FRAME_DELAY = 2750; +const TIMER_INC = 500; + +const getDataUrl = (value: Uint8Array): string => { + const qr = qrcode(0, 'M'); + + // HACK See our qrcode stringToBytes override as used internally. This + // will only work for the case where we actually pass `Bytes` in here + qr.addData(value as unknown as string, 'Byte'); + qr.make(); + + return qr.createDataURL(16, 0); +}; + +const Display = ({ + className = '', + size, + skipEncoding, + style = {}, + timerDelay = DEFAULT_FRAME_DELAY, + value, +}: DisplayProps): React.ReactElement<DisplayProps> | null => { + const [{ image }, setFrameState] = useState<FrameState>({ + frameIdx: 0, + frames: [], + image: null, + valueHash: null, + }); + const timerRef = useRef<TimerState>({ timerDelay, timerId: null }); + + const containerStyle = useMemo(() => createImgSize(size), [size]); + + // run on initial load to setup the global timer and provide and unsubscribe + useEffect((): (() => void) => { + const nextFrame = () => + setFrameState((state): FrameState => { + // when we have a single frame, we only ever fire once + if (state.frames.length <= 1) { + return state; + } + + let frameIdx = state.frameIdx + 1; + + // when we overflow, skip to the first and slightly increase the delay between frames + if (frameIdx === state.frames.length) { + frameIdx = 0; + timerRef.current.timerDelay += TIMER_INC; + } + + // only encode the frames on demand, not above as part of the + // state derivation - in the case of large payloads, this should + // be slightly more responsive on initial load + const newState = objectSpread<FrameState>({}, state, { + frameIdx, + image: getDataUrl(state.frames[frameIdx]), + }); + + // set the new timer last + timerRef.current.timerId = setTimeout( + nextFrame, + timerRef.current.timerDelay + ); + + return newState; + }); + + timerRef.current.timerId = setTimeout( + nextFrame, + timerRef.current.timerDelay + ); + + return () => { + if (timerRef.current.timerId) clearTimeout(timerRef.current.timerId); + }; + }, []); + + useEffect((): void => { + setFrameState((state): FrameState => { + const valueHash = xxhashAsHex(value); + + if (valueHash === state.valueHash) { + return state; + } + + const frames: Uint8Array[] = skipEncoding ? [value] : createFrames(value); + + // encode on demand + return { + frameIdx: 0, + frames, + image: getDataUrl(frames[0]), + valueHash, + }; + }); + }, [skipEncoding, value]); + + if (!image) { + return null; + } + + return ( + <DisplayWrapper className={className} style={containerStyle}> + <div className="ui--qr-Display" style={style}> + <img src={image} alt="img" /> + </div> + </DisplayWrapper> + ); +}; + +export const QrDisplay = React.memo(Display); diff --git a/src/library/QRCode/DisplayPayload.tsx b/src/library/QRCode/DisplayPayload.tsx new file mode 100644 index 0000000000..583eeb2735 --- /dev/null +++ b/src/library/QRCode/DisplayPayload.tsx @@ -0,0 +1,39 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import React, { useMemo } from 'react'; +import { QrDisplay } from './Display.js'; +import type { DisplayPayloadProps } from './types.js'; +import { createSignPayload } from './util.js'; + +const DisplayPayload = ({ + address, + className, + cmd, + genesisHash, + payload, + size, + style, + timerDelay, +}: DisplayPayloadProps): React.ReactElement<DisplayPayloadProps> | null => { + const data = useMemo( + () => createSignPayload(address, cmd, payload, genesisHash), + [address, cmd, payload, genesisHash] + ); + + if (!data) { + return null; + } + + return ( + <QrDisplay + className={className} + size={size} + style={style} + timerDelay={timerDelay} + value={data} + /> + ); +}; + +export const QrDisplayPayload = React.memo(DisplayPayload); diff --git a/src/library/QRCode/Scan.tsx b/src/library/QRCode/Scan.tsx new file mode 100644 index 0000000000..93ec11c64d --- /dev/null +++ b/src/library/QRCode/Scan.tsx @@ -0,0 +1,49 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import React, { useCallback, useMemo } from 'react'; +import Reader from 'react-qr-reader'; +import { ScanWrapper } from './Wrappers.js'; +import type { ScanProps } from './types.js'; +import { createImgSize } from './util.js'; + +const DEFAULT_DELAY = 150; + +const DEFAULT_ERROR = (error: Error): void => { + throw new Error(error.message); +}; + +const Scan = ({ + className = '', + delay = DEFAULT_DELAY, + onError = DEFAULT_ERROR, + onScan, + size, + style = {}, +}: ScanProps): React.ReactElement<ScanProps> => { + const containerStyle = useMemo(() => createImgSize(size), [size]); + + const onErrorCallback = useCallback( + (error: Error) => onError(error), + [onError] + ); + + const onScanCallback = useCallback( + (data: string | null) => data && onScan(data), + [onScan] + ); + + return ( + <ScanWrapper className={className} style={containerStyle}> + <Reader + className="ui--qr-Scan" + delay={delay} + onError={onErrorCallback} + onScan={onScanCallback} + style={style} + /> + </ScanWrapper> + ); +}; + +export const QrScan = React.memo(Scan); diff --git a/src/library/QRCode/ScanSignature.tsx b/src/library/QRCode/ScanSignature.tsx new file mode 100644 index 0000000000..1188a1d8f0 --- /dev/null +++ b/src/library/QRCode/ScanSignature.tsx @@ -0,0 +1,32 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import React, { useCallback } from 'react'; +import { QrScan } from './Scan.js'; +import type { ScanSignatureProps } from './types.js'; + +const ScanSignature = ({ + className, + onError, + onScan, + size, + style, +}: ScanSignatureProps): React.ReactElement<ScanSignatureProps> => { + const onScanCallback = useCallback( + (signature: string | null) => + signature && onScan({ signature: `0x${signature}` }), + [onScan] + ); + + return ( + <QrScan + className={className} + onError={onError} + onScan={onScanCallback} + size={size} + style={style} + /> + ); +}; + +export const QrScanSignature = React.memo(ScanSignature); diff --git a/src/library/QRCode/Wrappers.ts b/src/library/QRCode/Wrappers.ts new file mode 100644 index 0000000000..5b983bb502 --- /dev/null +++ b/src/library/QRCode/Wrappers.ts @@ -0,0 +1,33 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import styled from 'styled-components'; + +export const DisplayWrapper = styled.div` + .ui--qr-Display { + height: 100%; + width: 100%; + + img, + svg { + background: white; + height: auto !important; + max-height: 100%; + max-width: 100%; + width: auto !important; + } + } +`; + +export const ScanWrapper = styled.div` + .ui--qr-Scan { + display: inline-block; + height: 100%; + transform: matrix(-1, 0, 0, 1, 0, 0); + width: 100%; + + video { + margin: 0; + } + } +`; diff --git a/src/library/QRCode/constants.ts b/src/library/QRCode/constants.ts new file mode 100644 index 0000000000..cc998ab00b --- /dev/null +++ b/src/library/QRCode/constants.ts @@ -0,0 +1,8 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +export const ADDRESS_PREFIX = 'substrate'; +export const SEED_PREFIX = 'secret'; +export const FRAME_SIZE = 1024; +export const SUBSTRATE_ID = new Uint8Array([0x53]); +export const CRYPTO_SR25519 = new Uint8Array([0x01]); diff --git a/src/library/QRCode/qrcode.ts b/src/library/QRCode/qrcode.ts new file mode 100644 index 0000000000..75cfb35df2 --- /dev/null +++ b/src/library/QRCode/qrcode.ts @@ -0,0 +1,13 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import _qrcode from 'qrcode-generator'; + +// A small hurdle to jump through, just to get the default/default correct (as generated) +const qrcode: typeof _qrcode = _qrcode; + +// HACK The default function take string -> number[], the Uint8array is compatible +// with that signature and the use thereof +(qrcode as any).stringToBytes = (data: Uint8Array): Uint8Array => data; + +export { qrcode }; diff --git a/src/library/QRCode/types.ts b/src/library/QRCode/types.ts new file mode 100644 index 0000000000..5a8812a08f --- /dev/null +++ b/src/library/QRCode/types.ts @@ -0,0 +1,58 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import type { HexString } from '@polkadot/util/types'; +import type React from 'react'; + +export interface FrameState { + frames: Uint8Array[]; + frameIdx: number; + image: string | null; + valueHash: string | null; +} + +export interface ScanType { + signature: HexString; +} + +export interface TimerState { + timerDelay: number; + timerId: ReturnType<typeof setTimeout> | null; +} + +export interface DisplayProps { + className?: string | undefined; + size?: string | number | undefined; + skipEncoding?: boolean; + style?: React.CSSProperties | undefined; + timerDelay?: number | undefined; + value: Uint8Array; +} + +export interface DisplayPayloadProps { + address: string; + className?: string; + cmd: number; + genesisHash: Uint8Array | string; + payload: Uint8Array; + size?: string | number; + style?: React.CSSProperties; + timerDelay?: number; +} + +export interface ScanProps { + className?: string | undefined; + delay?: number; + onError?: undefined | ((error: Error) => void); + onScan: (data: string) => void; + size?: string | number | undefined; + style?: React.CSSProperties | undefined; +} + +export interface ScanSignatureProps { + className?: string; + onError?: (error: Error) => void; + onScan: (scanned: ScanType) => void; + size?: string | number; + style?: React.CSSProperties; +} diff --git a/src/library/QRCode/util.ts b/src/library/QRCode/util.ts new file mode 100644 index 0000000000..b7d5577275 --- /dev/null +++ b/src/library/QRCode/util.ts @@ -0,0 +1,77 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { isString, u8aConcat, u8aToU8a } from '@polkadot/util'; +import { decodeAddress } from '@polkadot/util-crypto'; +import { CRYPTO_SR25519, FRAME_SIZE, SUBSTRATE_ID } from './constants.js'; + +const MULTIPART = new Uint8Array([0]); + +export const encodeNumber = (value: number): Uint8Array => + // eslint-disable-next-line no-bitwise + new Uint8Array([value >> 8, value & 0xff]); + +export const encodeString = (value: string): Uint8Array => { + const count = value.length; + const u8a = new Uint8Array(count); + + for (let i = 0; i < count; i++) { + u8a[i] = value.charCodeAt(i); + } + + return u8a; +}; + +export const createSignPayload = ( + address: string, + cmd: number, + payload: string | Uint8Array, + genesisHash: string | Uint8Array +): Uint8Array => + u8aConcat( + SUBSTRATE_ID, + CRYPTO_SR25519, + new Uint8Array([cmd]), + decodeAddress(address), + u8aToU8a(payload), + u8aToU8a(genesisHash) + ); + +export const createFrames = (input: Uint8Array): Uint8Array[] => { + const frames = []; + let idx = 0; + + while (idx < input.length) { + frames.push(input.subarray(idx, idx + FRAME_SIZE)); + + idx += FRAME_SIZE; + } + + return frames.map( + (frame, index: number): Uint8Array => + u8aConcat( + MULTIPART, + encodeNumber(frames.length), + encodeNumber(index), + frame + ) + ); +}; + +export const createImgSize = ( + size?: string | number +): Record<string, string> => { + if (!size) { + return { + height: 'auto', + width: '100%', + }; + } + + const height = isString(size) ? size : `${size}px`; + + return { + height, + width: height, + }; +}; diff --git a/src/library/SelectItems/Item.tsx b/src/library/SelectItems/Item.tsx new file mode 100644 index 0000000000..d0231af0b8 --- /dev/null +++ b/src/library/SelectItems/Item.tsx @@ -0,0 +1,50 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { faCircle } from '@fortawesome/free-regular-svg-icons'; +import { faCircleCheck } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { Wrapper } from './Wrapper'; +import type { SelectItemProps } from './types'; + +export const SelectItem = ({ + title, + subtitle, + icon, + selected, + onClick, + layout, + hoverBorder = false, + grow = true, + disabled = false, + includeToggle = true, + bodyRef, + containerRef, +}: SelectItemProps) => ( + <Wrapper + className={layout} + $grow={grow} + $hoverBorder={hoverBorder} + $selected={selected} + > + <div className="inner" ref={containerRef}> + <button type="button" onClick={() => onClick()} disabled={disabled}> + <div className="icon"> + <FontAwesomeIcon icon={icon} transform="grow-4" /> + </div> + <div className="body" ref={bodyRef}> + <h3>{title}</h3> + <p>{subtitle}</p> + </div> + {includeToggle ? ( + <div className="toggle"> + <FontAwesomeIcon + icon={selected ? faCircleCheck : faCircle} + transform="grow-6" + /> + </div> + ) : null} + </button> + </div> + </Wrapper> +); diff --git a/src/library/SelectItems/Wrapper.ts b/src/library/SelectItems/Wrapper.ts new file mode 100644 index 0000000000..b7f6fb9b90 --- /dev/null +++ b/src/library/SelectItems/Wrapper.ts @@ -0,0 +1,161 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import styled from 'styled-components'; + +export const TwoThreshold = 800; +export const ThreeRowThreshold = 1300; + +const TwoThresholdMin = TwoThreshold + 1; +const ThreeRowThresholdMin = ThreeRowThreshold + 1; + +// The outer container of select items. +// Removes the outer padding for 2 and 3 row layouts. +export const SelectItemsWrapper = styled.div` + width: 100%; + display: flex; + flex-flow: row wrap; + + &.two-col { + /* Remove outer padding for 2-per-row layout */ + @media (min-width: ${TwoThresholdMin}px) { + > div:nth-child(2n) { + padding-right: 0; + } + > div:nth-child(2n + 1) { + padding-left: 0; + } + } + } + + &.three-col { + /* Remove outer padding for 2-per-row layout */ + @media (min-width: ${TwoThresholdMin}px) and (max-width: ${ThreeRowThreshold}px) { + > div:nth-child(2n) { + padding-right: 0; + } + > div:nth-child(2n + 1) { + padding-left: 0; + } + } + /* Remove outer padding for 3-per-row layout */ + @media (min-width: ${ThreeRowThresholdMin}px) { + > div:nth-child(3n) { + padding-right: 0; + } + > div:nth-child(3n + 1) { + padding-left: 0; + } + } + } +`; + +// Item and surrounding padded area. +export const Wrapper = styled.div<{ + $selected?: boolean; + $grow: boolean; + $hoverBorder: boolean; +}>` + padding: 0.6rem; + width: 100%; + flex-grow: ${(props) => (props.$grow ? 1 : 0)}; + + &.two-col { + width: 50%; + /* flex basis for 3-per-row layout */ + @media (max-width: ${TwoThreshold}px) { + width: 100%; + } + } + + &.three-col { + width: 33.33%; + /* flex basis for 2-per-row layout */ + @media (min-width: ${TwoThreshold}px) and (max-width: ${ThreeRowThreshold}px) { + width: 50%; + } + /* flex basis for 3-per-row layout */ + @media (max-width: ${TwoThreshold}px) { + width: 100%; + } + } + + > .inner { + transition: border var(--transition-duration); + background: var(--background-primary); + border: 1.75px solid + ${(props) => + props.$selected + ? 'var(--accent-color-primary)' + : 'var(--border-primary-color)'}; + border-radius: 1rem; + width: 100%; + position: relative; + overflow: hidden; + + &:hover { + border-color: ${(props) => + props.$hoverBorder + ? 'var(--accent-color-primary)' + : props.$selected + ? 'var(--accent-color-primary)' + : 'var(--border-primary-color)'}; + } + + > button { + position: absolute; + top: 0; + left: 0; + width: 100%; + text-align: left; + display: flex; + flex-flow: row wrap; + align-items: center; + + &:disabled { + opacity: var(--opacity-disabled); + } + + > .icon { + background: var(--background-list-item); + color: var(--accent-color-primary); + width: 6rem; + display: flex; + align-items: center; + justify-content: center; + } + + > .body { + flex: 1; + display: flex; + flex-flow: column wrap; + justify-content: center; + padding: 1.25rem 1.35rem; + overflow: hidden; + + h3 { + font-family: InterSemiBold, sans-serif; + padding: 0; + margin: 0; + } + + p { + padding: 0; + margin: 0.4rem 0 0 0; + } + } + + > .toggle { + color: ${(props) => + props.$selected + ? 'var(--accent-color-primary)' + : 'var(--text-color-secondary)'}; + opacity: ${(props) => (props.$selected ? 1 : 0.5)}; + width: 4rem; + display: flex; + align-items: center; + justify-content: center; + } + } + } +`; diff --git a/src/library/SelectItems/index.tsx b/src/library/SelectItems/index.tsx new file mode 100644 index 0000000000..53c980d31c --- /dev/null +++ b/src/library/SelectItems/index.tsx @@ -0,0 +1,92 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import type { MutableRefObject } from 'react'; +import React, { useEffect, useLayoutEffect, useRef } from 'react'; +import type { AnyJson } from 'types'; +import { SelectItemsWrapper, TwoThreshold } from './Wrapper'; +import type { SelectItemsProps } from './types'; + +export const SelectItems = ({ layout, children }: SelectItemsProps) => { + // Initialise refs for container and body of items. + const containerRefs: MutableRefObject<AnyJson>[] = []; + const bodyRefs: MutableRefObject<AnyJson>[] = []; + + if (children) { + for (let i = 0; i < children.length; i++) { + bodyRefs.push(useRef(null)); + containerRefs.push(useRef(null)); + } + } + + // Adjust all container heights to be uniform. + const handleAdjustHeight = () => { + const refsInitialised = [...containerRefs] + .concat(bodyRefs) + .every((r: AnyJson) => r !== null); + + if (refsInitialised) { + // Get max height from button refs. + let maxHeight = 0; + for (let i = 0; i < bodyRefs.length; i++) { + const { current } = bodyRefs[i]; + if (current) { + const thisHeight = current.offsetHeight || 0; + if (thisHeight > maxHeight) { + maxHeight = thisHeight; + } + } + } + + // Update container heights to max height. + for (let i = 0; i < containerRefs.length; i++) { + const { current } = containerRefs[i]; + + if (current) { + const icon: AnyJson = current.querySelector('.icon'); + const toggle: AnyJson = current.querySelector('.toggle'); + + if (window.innerWidth <= TwoThreshold) { + current.style.height = `${bodyRefs[i].current.offsetHeight}px`; + if (icon) + icon.style.height = `${bodyRefs[i].current.offsetHeight}px`; + if (toggle) + toggle.style.height = `${bodyRefs[i].current.offsetHeight}px`; + } else { + current.style.height = `${maxHeight}px`; + if (icon) icon.style.height = `${maxHeight}px`; + if (toggle) toggle.style.height = `${maxHeight}px`; + } + } + } + } + }; + + // Update on ref change. + useLayoutEffect(() => { + handleAdjustHeight(); + }, [children, bodyRefs, containerRefs]); + + // Adjust height on window resize. + useEffect(() => { + window.addEventListener('resize', handleAdjustHeight); + return () => { + window.removeEventListener('resize', handleAdjustHeight); + }; + }, []); + + return ( + <SelectItemsWrapper className={layout}> + {children + ? children.map((child: any, i: number) => ( + <React.Fragment key={`select_${i}`}> + {React.cloneElement(child, { + bodyRef: bodyRefs[i], + containerRef: containerRefs[i], + })} + </React.Fragment> + )) + : null} + </SelectItemsWrapper> + ); +}; diff --git a/src/library/SelectItems/types.ts b/src/library/SelectItems/types.ts new file mode 100644 index 0000000000..fc88e8867b --- /dev/null +++ b/src/library/SelectItems/types.ts @@ -0,0 +1,28 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import type React from 'react'; +import type { Dispatch, SetStateAction } from 'react'; +import type { AnyJson, MaybeAddress } from 'types'; + +export interface SelectItemsProps { + layout?: 'two-col' | 'three-col'; + children?: React.ReactNode[]; +} + +export interface SelectItemProps { + title: string; + subtitle: string; + icon: AnyJson; + selected: boolean; + onClick: () => void; + layout?: 'two-col' | 'three-col'; + hoverBorder?: boolean; + grow?: boolean; + disabled?: boolean; + includeToggle?: boolean; + bodyRef?: AnyJson; + containerRef?: AnyJson; + account?: MaybeAddress; + setAccount?: Dispatch<SetStateAction<MaybeAddress>>; +} diff --git a/src/library/SetupSteps/Footer/Wrapper.ts b/src/library/SetupSteps/Footer/Wrapper.ts index a32d2eb414..73a1fc7d57 100644 --- a/src/library/SetupSteps/Footer/Wrapper.ts +++ b/src/library/SetupSteps/Footer/Wrapper.ts @@ -1,5 +1,5 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only import styled from 'styled-components'; diff --git a/src/library/SetupSteps/Footer/index.tsx b/src/library/SetupSteps/Footer/index.tsx index 75a2740143..7a2ecf204b 100644 --- a/src/library/SetupSteps/Footer/index.tsx +++ b/src/library/SetupSteps/Footer/index.tsx @@ -1,18 +1,18 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import { ButtonPrimary } from '@rossbulat/polkadot-dashboard-ui'; -import { useConnect } from 'contexts/Connect'; -import { useUi } from 'contexts/UI'; -import { FooterProps } from '../types'; +import { ButtonPrimary } from '@polkadot-cloud/react'; +import { useTranslation } from 'react-i18next'; +import { useSetup } from 'contexts/Setup'; +import { useActiveAccounts } from 'contexts/ActiveAccounts'; +import type { FooterProps } from '../types'; import { Wrapper } from './Wrapper'; -export const Footer = (props: FooterProps) => { - const { complete, setupType } = props; - - const { activeAccount } = useConnect(); - const { getSetupProgress, setActiveAccountSetupSection } = useUi(); - const setup = getSetupProgress(setupType, activeAccount); +export const Footer = ({ complete, bondFor }: FooterProps) => { + const { t } = useTranslation('library'); + const { activeAccount } = useActiveAccounts(); + const { getSetupProgress, setActiveAccountSetupSection } = useSetup(); + const setup = getSetupProgress(bondFor, activeAccount); return ( <Wrapper> @@ -20,19 +20,17 @@ export const Footer = (props: FooterProps) => { {complete ? ( <ButtonPrimary lg - text="Continue" + text={t('continue')} onClick={() => - setActiveAccountSetupSection(setupType, setup.section + 1) + setActiveAccountSetupSection(bondFor, setup.section + 1) } /> ) : ( <div style={{ opacity: 0.5 }}> - <ButtonPrimary text="Continue" disabled lg /> + <ButtonPrimary text={t('continue')} disabled lg /> </div> )} </section> </Wrapper> ); }; - -export default Footer; diff --git a/src/library/SetupSteps/Header/Wrapper.ts b/src/library/SetupSteps/Header/Wrapper.ts index 30c6b0e608..bc5db5e3b6 100644 --- a/src/library/SetupSteps/Header/Wrapper.ts +++ b/src/library/SetupSteps/Header/Wrapper.ts @@ -1,8 +1,7 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only import styled from 'styled-components'; -import { networkColor, textSecondary } from 'theme'; export const Wrapper = styled.div` width: 100%; @@ -11,7 +10,7 @@ export const Wrapper = styled.div` align-items: center; > section { - color: ${textSecondary}; + color: var(--text-color-secondary); display: flex; flex-flow: row wrap; align-items: center; @@ -22,13 +21,13 @@ export const Wrapper = styled.div` justify-content: flex-end; .progress { - color: ${textSecondary}; + color: var(--text-color-secondary); opacity: 0.5; } .complete { margin: 0; - color: ${networkColor}; + color: var(--accent-color-primary); } span { @@ -37,15 +36,9 @@ export const Wrapper = styled.div` } h2 { - margin: 0; padding: 0.3rem 0; display: flex; flex-flow: row wrap; - justify-content: flex-start; align-items: center; - - .help-icon { - margin-left: 0.6rem; - } } `; diff --git a/src/library/SetupSteps/Header/index.tsx b/src/library/SetupSteps/Header/index.tsx index 74f1fdd2c6..dd59fac29d 100644 --- a/src/library/SetupSteps/Header/index.tsx +++ b/src/library/SetupSteps/Header/index.tsx @@ -1,26 +1,35 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import { ButtonSecondary } from '@rossbulat/polkadot-dashboard-ui'; -import { useConnect } from 'contexts/Connect'; -import { useUi } from 'contexts/UI'; -import { OpenHelpIcon } from 'library/OpenHelpIcon'; -import { HeaderProps } from '../types'; +import { ButtonHelp, ButtonSecondary } from '@polkadot-cloud/react'; +import { useTranslation } from 'react-i18next'; +import { useHelp } from 'contexts/Help'; +import { useSetup } from 'contexts/Setup'; +import { useActiveAccounts } from 'contexts/ActiveAccounts'; +import type { HeaderProps } from '../types'; import { Wrapper } from './Wrapper'; -export const Header = (props: HeaderProps) => { - const { title, helpKey, complete, thisSection, setupType } = props; - - const { activeAccount } = useConnect(); - const { getSetupProgress, setActiveAccountSetupSection } = useUi(); - const setup = getSetupProgress(setupType, activeAccount); +export const Header = ({ + title, + helpKey, + complete, + thisSection, + bondFor, +}: HeaderProps) => { + const { t } = useTranslation('library'); + const { activeAccount } = useActiveAccounts(); + const { getSetupProgress, setActiveAccountSetupSection } = useSetup(); + const setup = getSetupProgress(bondFor, activeAccount); + const { openHelp } = useHelp(); return ( <Wrapper> <section> <h2> {title} - {helpKey !== undefined && <OpenHelpIcon helpKey={helpKey} />} + {helpKey !== undefined ? ( + <ButtonHelp marginLeft onClick={() => openHelp(helpKey)} /> + ) : null} </h2> </section> <section> @@ -29,19 +38,17 @@ export const Header = (props: HeaderProps) => { {setup.section !== thisSection && thisSection < setup.section && ( <span> <ButtonSecondary - text="Update" + text={t('update')} onClick={() => { - setActiveAccountSetupSection(setupType, thisSection); + setActiveAccountSetupSection(bondFor, thisSection); }} /> </span> )} - <h4 className="complete">Complete</h4> + <h4 className="complete">{t('complete')}</h4> </> )} </section> </Wrapper> ); }; - -export default Header; diff --git a/src/library/SetupSteps/MotionContainer.tsx b/src/library/SetupSteps/MotionContainer.tsx index fadf54609d..6a2d4dae7d 100644 --- a/src/library/SetupSteps/MotionContainer.tsx +++ b/src/library/SetupSteps/MotionContainer.tsx @@ -1,5 +1,5 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only import { motion } from 'framer-motion'; @@ -37,5 +37,3 @@ export const MotionContainer = ({ </motion.div> ); }; - -export default MotionContainer; diff --git a/src/library/SetupSteps/Nominate.tsx b/src/library/SetupSteps/Nominate.tsx index e6847a86a2..cc0748605a 100644 --- a/src/library/SetupSteps/Nominate.tsx +++ b/src/library/SetupSteps/Nominate.tsx @@ -1,63 +1,61 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only +import { useTranslation } from 'react-i18next'; import { useApi } from 'contexts/Api'; -import { useConnect } from 'contexts/Connect'; -import { useUi } from 'contexts/UI'; +import { useSetup } from 'contexts/Setup'; import { Footer } from 'library/SetupSteps/Footer'; import { Header } from 'library/SetupSteps/Header'; import { MotionContainer } from 'library/SetupSteps/MotionContainer'; +import { useActiveAccounts } from 'contexts/ActiveAccounts'; +import { Subheading } from 'pages/Nominate/Wrappers'; import { GenerateNominations } from '../GenerateNominations'; -import { NominationsProps } from './types'; - -export const Nominate = (props: NominationsProps) => { - const { batchKey, setupType, section } = props; +import type { NominationsProps } from './types'; +export const Nominate = ({ bondFor, section }: NominationsProps) => { + const { t } = useTranslation('library'); const { consts } = useApi(); - const { activeAccount } = useConnect(); - const { getSetupProgress, setActiveAccountSetup } = useUi(); - const setup = getSetupProgress(setupType, activeAccount); + const { activeAccount } = useActiveAccounts(); + const { getSetupProgress, setActiveAccountSetup } = useSetup(); + const setup = getSetupProgress(bondFor, activeAccount); + const { progress } = setup; const { maxNominations } = consts; - const setterFn = () => { - return getSetupProgress(setupType, activeAccount); - }; - - // handler for updating bond - const handleSetupUpdate = (value: any) => { - setActiveAccountSetup(setupType, value); - }; + // Handler for updating setup. + const handleSetupUpdate = (value: any) => + setActiveAccountSetup(bondFor, value); return ( <> <Header thisSection={section} - complete={setup.nominations.length > 0} - title="Nominate" + complete={progress.nominations.length > 0} + title={t('nominate')} helpKey="Nominating" - setupType={setupType} + bondFor={bondFor} /> <MotionContainer thisSection={section} activeSection={setup.section}> - <div style={{ marginTop: '0.5rem' }}> + <Subheading> <h4> - Choose up to {maxNominations} validators to nominate. Generate your - nominations automatically or manually insert them. + {t('chooseValidators', { + maxNominations: maxNominations.toString(), + })} </h4> - <GenerateNominations - batchKey={batchKey} - setters={[ - { - current: { - callable: true, - fn: setterFn, - }, - set: handleSetupUpdate, + </Subheading> + <GenerateNominations + setters={[ + { + current: { + callable: true, + fn: () => getSetupProgress(bondFor, activeAccount).progress, }, - ]} - nominations={setup.nominations} - /> - </div> - <Footer complete={setup.nominations.length > 0} setupType={setupType} /> + set: handleSetupUpdate, + }, + ]} + nominations={progress.nominations} + /> + + <Footer complete={progress.nominations.length > 0} bondFor={bondFor} /> </MotionContainer> </> ); diff --git a/src/library/SetupSteps/types.ts b/src/library/SetupSteps/types.ts index 95e63fde11..315bc9e8e1 100644 --- a/src/library/SetupSteps/types.ts +++ b/src/library/SetupSteps/types.ts @@ -1,23 +1,16 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import { SetupType } from 'contexts/UI/types'; +import type { BondFor } from 'types'; export interface NominationsProps { - batchKey: string; - setupType: SetupType; + bondFor: BondFor; section: number; } export interface FooterProps { complete: boolean; - setupType: SetupType; -} - -export interface GenerateNominationsInnerProps { - setters: Array<any>; - nominations: string[]; - batchKey: string; + bondFor: BondFor; } export interface HeaderProps { @@ -25,11 +18,9 @@ export interface HeaderProps { helpKey?: string; complete?: boolean | null; thisSection: number; - setupType: SetupType; + bondFor: BondFor; } -export type Nominations = string[]; - export interface SetupStepProps { section: number; } diff --git a/src/library/SideMenu/Heading/Heading.tsx b/src/library/SideMenu/Heading/Heading.tsx index 35ebdb6a18..816f6ff418 100644 --- a/src/library/SideMenu/Heading/Heading.tsx +++ b/src/library/SideMenu/Heading/Heading.tsx @@ -1,17 +1,11 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import { HeadingProps } from '../types'; +import type { HeadingProps } from '../types'; import { Wrapper } from './Wrapper'; -export const Heading = (props: HeadingProps) => { - const { title, minimised } = props; - - return ( - <Wrapper minimised={minimised}> - {minimised ? <h5>•</h5> : <h5>{title}</h5>} - </Wrapper> - ); -}; - -export default Heading; +export const Heading = ({ title, minimised }: HeadingProps) => ( + <Wrapper $minimised={minimised}> + {minimised ? <h5>•</h5> : <h5>{title}</h5>} + </Wrapper> +); diff --git a/src/library/SideMenu/Heading/Wrapper.ts b/src/library/SideMenu/Heading/Wrapper.ts new file mode 100644 index 0000000000..882efcc2d2 --- /dev/null +++ b/src/library/SideMenu/Heading/Wrapper.ts @@ -0,0 +1,19 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import styled from 'styled-components'; + +export const Wrapper = styled.div<{ $minimised: boolean }>` + display: flex; + flex-flow: row wrap; + justify-content: ${(props) => (props.$minimised ? 'center' : 'flex-start')}; + opacity: ${(props) => (props.$minimised ? 0.5 : 1)}; + align-items: center; + + h5 { + color: var(--text-color-secondary); + margin: 1.1rem 0 0.3rem 0; + padding: 0 0.5rem; + opacity: 0.7; + } +`; diff --git a/src/library/SideMenu/Main.tsx b/src/library/SideMenu/Main.tsx index 0ae780bad5..f247dc8dc3 100644 --- a/src/library/SideMenu/Main.tsx +++ b/src/library/SideMenu/Main.tsx @@ -1,76 +1,91 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { PAGES_CONFIG, PAGE_CATEGORIES } from 'config/pages'; -import { CereUrl, UriPrefix } from 'consts'; -import { useApi } from 'contexts/Api'; -import { useBalances } from 'contexts/Balances'; -import { useConnect } from 'contexts/Connect'; -import { usePoolMemberships } from 'contexts/Pools/PoolMemberships'; -import { useStaking } from 'contexts/Staking'; -import { useUi } from 'contexts/UI'; -import { UIContextInterface } from 'contexts/UI/types'; import React, { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useLocation } from 'react-router-dom'; -import { PageCategory, PageItem, PagesConfig } from 'types'; -import Heading from './Heading/Heading'; +import { PageCategories, PagesConfig } from 'config/pages'; +import { CereUrl } from 'consts'; +import { useBonded } from 'contexts/Bonded'; +import { usePoolMemberships } from 'contexts/Pools/PoolMemberships'; +import { useSetup } from 'contexts/Setup'; +import type { SetupContextInterface } from 'contexts/Setup/types'; +import { useStaking } from 'contexts/Staking'; +import { useUi } from 'contexts/UI'; +import type { UIContextInterface } from 'contexts/UI/types'; +import type { PageCategory, PageItem, PagesConfigItems } from 'types'; +import { useNetwork } from 'contexts/Network'; +import { useActiveAccounts } from 'contexts/ActiveAccounts'; +import { useImportedAccounts } from 'contexts/Connect/ImportedAccounts'; +import { Heading } from './Heading/Heading'; import { Primary } from './Primary'; import { LogoWrapper } from './Wrapper'; export const Main = () => { - const { network } = useApi(); - const { activeAccount, accounts } = useConnect(); + const { t, i18n } = useTranslation('base'); + const { networkData } = useNetwork(); const { pathname } = useLocation(); - const { getBondedAccount } = useBalances(); - const { getControllerNotImported, inSetup: inNominatorSetup } = useStaking(); + const { getBondedAccount } = useBonded(); + const { accounts } = useImportedAccounts(); + const { activeAccount } = useActiveAccounts(); + const { inSetup: inNominatorSetup, addressDifferentToStash } = useStaking(); const { membership } = usePoolMemberships(); const controller = getBondedAccount(activeAccount); const { - isSyncing, - sideMenuMinimised, - getPoolSetupProgressPercent, - getStakeSetupProgressPercent, - }: UIContextInterface = useUi(); - const controllerNotImported = getControllerNotImported(controller); - const { t } = useTranslation('base'); + onNominatorSetup, + onPoolSetup, + getPoolSetupPercent, + getNominatorSetupPercent, + }: SetupContextInterface = useSetup(); + const { isSyncing, sideMenuMinimised }: UIContextInterface = useUi(); + const controllerDifferentToStash = addressDifferentToStash(controller); const [pageConfig, setPageConfig] = useState({ - categories: Object.assign(PAGE_CATEGORIES), - pages: Object.assign(PAGES_CONFIG), + categories: Object.assign(PageCategories), + pages: Object.assign(PagesConfig), }); useEffect(() => { if (!accounts.length) return; // inject actions into menu items - const _pages = Object.assign(pageConfig.pages); - for (let i = 0; i < _pages.length; i++) { - const { uri } = _pages[i]; + const pages = Object.assign(pageConfig.pages); + for (let i = 0; i < pages.length; i++) { + const { uri } = pages[i]; // set undefined action as default - _pages[i].action = undefined; + pages[i].action = undefined; + if (uri === `${import.meta.env.BASE_URL}`) { + const warning = !isSyncing && controllerDifferentToStash; + if (warning) { + pages[i].action = { + type: 'bullet', + status: 'warning', + }; + } + } - if (uri === `${UriPrefix}/nominate`) { + if (uri === `${import.meta.env.BASE_URL}nominate`) { // configure Stake action - const warning = !isSyncing && controllerNotImported; const staking = !inNominatorSetup(); - const setupPercent = getStakeSetupProgressPercent(activeAccount); + const warning = !isSyncing && controllerDifferentToStash; + const setupPercent = getNominatorSetupPercent(activeAccount); if (staking) { - _pages[i].action = { + pages[i].action = { type: 'text', status: 'success', text: t('active'), }; - } else if (warning) { - _pages[i].action = { + } + if (warning) { + pages[i].action = { type: 'bullet', status: 'warning', }; - } else if (setupPercent > 0 && !staking) { - _pages[i].action = { + } + if (!staking && (onNominatorSetup || setupPercent > 0)) { + pages[i].action = { type: 'text', status: 'warning', text: `${setupPercent}%`, @@ -78,19 +93,20 @@ export const Main = () => { } } - if (uri === `${UriPrefix}/pools`) { + if (uri === `${import.meta.env.BASE_URL}pools`) { // configure Pools action const inPool = membership; - const setupPercent = getPoolSetupProgressPercent(activeAccount); + const setupPercent = getPoolSetupPercent(activeAccount); if (inPool) { - _pages[i].action = { + pages[i].action = { type: 'text', status: 'success', text: t('active'), }; - } else if (setupPercent > 0 && !inPool) { - _pages[i].action = { + } + if (!inPool && (setupPercent > 0 || onPoolSetup)) { + pages[i].action = { type: 'text', status: 'warning', text: `${setupPercent}%`, @@ -100,40 +116,43 @@ export const Main = () => { } setPageConfig({ categories: pageConfig.categories, - pages: _pages, + pages, }); }, [ - network, + networkData, activeAccount, accounts, - controllerNotImported, + controllerDifferentToStash, isSyncing, membership, inNominatorSetup(), - getStakeSetupProgressPercent(activeAccount), - getPoolSetupProgressPercent(activeAccount), + getNominatorSetupPercent(activeAccount), + getPoolSetupPercent(activeAccount), + i18n.resolvedLanguage, + onNominatorSetup, + onPoolSetup, ]); // remove pages that network does not support - const pagesToDisplay: PagesConfig = Object.values(pageConfig.pages); + const pagesToDisplay: PagesConfigItems = Object.values(pageConfig.pages); return ( <> <LogoWrapper - onClick={() => { - window.open(CereUrl, '_blank'); - }} - minimised={sideMenuMinimised} + $minimised={sideMenuMinimised} + onClick={() => window.open(CereUrl, '_blank')} > {sideMenuMinimised ? ( - <network.brand.icon style={{ maxHeight: '100%', width: '2rem' }} /> + <networkData.brand.icon + style={{ maxHeight: '100%', width: '2rem' }} + /> ) : ( <> - <network.brand.logo.svg + <networkData.brand.logo.svg style={{ maxHeight: '100%', height: '100%', - width: network.brand.logo.width, + width: networkData.brand.logo.width, }} /> </> @@ -150,23 +169,14 @@ export const Main = () => { {/* display category links */} {pagesToDisplay.map( - ({ category, hash, icon, key, animate, action }: PageItem) => ( + ({ category, hash, key, lottie, action }: PageItem) => ( <React.Fragment key={`sidemenu_page_${categoryId}_${key}`}> {category === categoryId && ( <Primary name={t(key)} to={hash} active={hash === pathname} - icon={ - icon ? ( - <FontAwesomeIcon - icon={icon} - transform="grow-1" - className="fa-icon" - /> - ) : undefined - } - animate={animate} + lottie={lottie} action={action} minimised={sideMenuMinimised} /> diff --git a/src/library/SideMenu/Primary/Wrappers.ts b/src/library/SideMenu/Primary/Wrappers.ts new file mode 100644 index 0000000000..6db03b9792 --- /dev/null +++ b/src/library/SideMenu/Primary/Wrappers.ts @@ -0,0 +1,104 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { motion } from 'framer-motion'; +import styled from 'styled-components'; + +export const Wrapper = styled(motion.div)` + border: none; + border-radius: 0.7rem; + height: 3.2rem; + display: flex; + flex-flow: row wrap; + align-items: center; + margin: 0.4rem 0.2rem 0.3rem 0; + padding: 0rem 0.5rem; + position: relative; + + &.minimised { + border: 1px solid rgba(255, 255, 255, 0); + border-radius: 0.5rem; + font-size: 1.1rem; + justify-content: center; + margin: 0.7rem 0.2rem 0.5rem 0; + padding: 0.65rem 0rem; + + &.success { + border: 1px solid var(--accent-color-primary); + } + &.warning { + border: 1px solid var(--status-warning-color); + } + } + + .dotlottie { + color: var(--text-color-primary); + margin-left: 0.25rem; + margin-right: 0.65rem; + width: 1.35rem; + height: 1.35rem; + .fa-icon { + margin: 0 0.15rem; + } + &.minimised { + margin: 0; + width: 1.5rem; + height: 1.5rem; + } + } + .name { + font-family: InterSemiBold, sans-serif; + margin: 0; + padding: 0; + line-height: 1.35rem; + } + .action { + color: var(--status-success-color); + display: flex; + flex: 1; + font-size: 0.88rem; + flex-flow: row wrap; + justify-content: flex-end; + margin-right: 0.4rem; + opacity: 0.7; + + > span { + &.success { + color: var(--accent-color-primary); + border: 1px solid var(--accent-color-primary); + } + &.warning { + color: var(--status-warning-color); + border: 1px solid var(--status-warning-color-transparent); + } + border-radius: 0.5rem; + padding: 0.15rem 0.5rem; + } + + &.success { + svg { + color: var(--status-success-color); + } + } + &.warning { + svg { + color: var(--status-warning-color); + } + } + &.minimised { + > svg { + flex: 0; + position: absolute; + right: -3px; + top: -4px; + } + } + } + + &.active { + background: var(--highlight-primary); + } + &.inactive:hover { + background: var(--highlight-secondary); + } +`; diff --git a/src/library/SideMenu/Primary/index.tsx b/src/library/SideMenu/Primary/index.tsx index 80488887eb..f7c825623e 100644 --- a/src/library/SideMenu/Primary/index.tsx +++ b/src/library/SideMenu/Primary/index.tsx @@ -1,22 +1,25 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import { IconProp } from '@fortawesome/fontawesome-svg-core'; import { faCircle } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { useUi } from 'contexts/UI'; -import { useState } from 'react'; -import Lottie from 'react-lottie'; import { Link } from 'react-router-dom'; -import { PrimaryProps } from '../types'; -import { MinimisedWrapper, Wrapper } from './Wrappers'; +import { useUi } from 'contexts/UI'; +import { useDotLottieButton } from 'library/Hooks/useDotLottieButton'; +import type { PrimaryProps } from '../types'; +import { Wrapper } from './Wrappers'; -export const Primary = (props: PrimaryProps) => { +export const Primary = ({ + name, + active, + to, + action, + minimised, + lottie, +}: PrimaryProps) => { const { setSideMenu } = useUi(); - const { name, active, to, icon, action, minimised } = props; - - const StyledWrapper = minimised ? MinimisedWrapper : Wrapper; + const { icon, play } = useDotLottieButton(lottie); let Action = null; const actionStatus = action?.status ?? null; @@ -25,14 +28,16 @@ export const Primary = (props: PrimaryProps) => { case 'text': Action = ( <div className="action text"> - <span className={`${actionStatus}`}>{action?.text ?? ''}</span> + <span className={actionStatus || undefined}> + {action?.text ?? ''} + </span> </div> ); break; case 'bullet': Action = ( <div className={`action ${actionStatus}`}> - <FontAwesomeIcon icon={faCircle as IconProp} transform="shrink-4" /> + <FontAwesomeIcon icon={faCircle} transform="shrink-4" /> </div> ); break; @@ -40,67 +45,35 @@ export const Primary = (props: PrimaryProps) => { Action = null; } - // animate icon config - - const { animate } = props; - const [isStopped, setIsStopped] = useState(true); - - const animateOptions = { - loop: true, - autoplay: false, - animationData: animate, - rendererSettings: { - preserveAspectRatio: 'xMidYMid slice', - }, - }; - return ( <Link to={to} onClick={() => { if (!active) { - setSideMenu(0); - setIsStopped(false); + play(); + setSideMenu(false); } }} > - <StyledWrapper + <Wrapper className={`${active ? `active` : `inactive`}${ - action ? ` action-${actionStatus}` : `` - }`} + minimised ? ` minimised` : `` + }${action ? ` ${actionStatus}` : ``}`} whileHover={{ scale: 1.02 }} whileTap={{ scale: 0.98 }} transition={{ duration: 0.1, }} > - <div className="icon"> - {animate === undefined ? ( - icon - ) : ( - <Lottie - options={animateOptions} - width={minimised ? '1.5rem' : '1.35rem'} - height={minimised ? '1.5rem' : '1.35rem'} - isStopped={isStopped} - isPaused={isStopped} - eventListeners={[ - { - eventName: 'loopComplete', - callback: () => setIsStopped(true), - }, - ]} - /> - )} + <div className={`dotlottie${minimised ? ` minimised` : ``}`}> + {icon} </div> {!minimised && ( <> <h4 className="name">{name}</h4> {Action} </> )} - </StyledWrapper> + </Wrapper> </Link> ); }; - -export default Primary; diff --git a/src/library/SideMenu/Secondary/Wrappers.ts b/src/library/SideMenu/Secondary/Wrappers.ts new file mode 100644 index 0000000000..17ab5f51b0 --- /dev/null +++ b/src/library/SideMenu/Secondary/Wrappers.ts @@ -0,0 +1,104 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { motion } from 'framer-motion'; +import styled from 'styled-components'; +import type { MinimisedProps } from '../types'; + +export const Wrapper = styled(motion.button)<MinimisedProps>` + border: 1px solid var(--border-primary-color); + border-radius: 0.7rem; + height: 3.2rem; + display: flex; + flex-flow: row wrap; + align-items: center; + position: relative; + padding: 0.75rem 0rem 0.75rem 0.5rem; + margin: 0.8rem 0.2rem 0.8rem 0; + width: 100%; + + .name { + color: var(--text-color-primary); + font-family: InterSemiBold, sans-serif; + font-size: 1.1rem; + } + .light { + color: var(--text-color-primary); + margin-left: 0.2rem; + } + .action { + color: var(--text-color-primary); + flex: 1; + display: flex; + flex-flow: row wrap; + justify-content: flex-end; + } + + &.active { + background: var(--highlight-primary); + } + &.inactive:hover { + background: var(--highlight-secondary); + } + &.success { + border: 1px solid var(--status-success-color-transparent); + } + &.warning { + border: 1px solid var(--status-warning-color-transparent); + } + &.danger { + border: 1px solid var(--status-danger-color-transparent); + } +`; + +export const MinimisedWrapper = styled(motion.button)` + border: 1px solid var(--border-primary-color); + border-radius: 0.5rem; + display: flex; + flex-flow: row wrap; + justify-content: center; + align-items: center; + position: relative; + padding: 0rem 0rem; + margin: 0.6rem 0 0.6rem 0; + min-height: 2.8rem; + width: 100%; + + &.active { + background: var(--highlight-primary); + } + &.inactive:hover { + background: var(--highlight-secondary); + } + .icon { + margin: 0; + } + .action { + &.minimised { + flex: 0; + position: absolute; + top: -2px; + right: -13px; + } + } + &.success { + border: 1px solid var(--status-success-color-transparent); + } + &.warning { + border: 1px solid var(--status-warning-color-transparent); + } + &.danger { + border: 1px solid var(--status-danger-color-transparent); + } +`; + +export const IconWrapper = styled.div<{ $minimised: boolean }>` + margin-left: ${(props) => (props.$minimised ? 0 : '0.25rem')}; + margin-right: ${(props) => (props.$minimised ? 0 : '0.65rem')}; + + svg { + .primary { + fill: var(--text-color-primary); + } + } +`; diff --git a/src/library/SideMenu/Secondary/index.tsx b/src/library/SideMenu/Secondary/index.tsx index 7c5e3f2c00..4cee45febf 100644 --- a/src/library/SideMenu/Secondary/index.tsx +++ b/src/library/SideMenu/Secondary/index.tsx @@ -1,70 +1,41 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import { useState } from 'react'; -import Lottie from 'react-lottie'; -import { SecondaryProps } from '../types'; +import type { SecondaryProps } from '../types'; import { IconWrapper, MinimisedWrapper, Wrapper } from './Wrappers'; -export const Secondary = (props: SecondaryProps) => { - const { action, name, icon, minimised, onClick, borderColor } = props; +export const Secondary = ({ + action, + classes, + name, + icon, + minimised, + onClick, +}: SecondaryProps) => { const { Svg, size } = icon || {}; const StyledWrapper = minimised ? MinimisedWrapper : Wrapper; - // animate icon config - const { animate } = props; - const [isStopped, setIsStopped] = useState(true); - - const animateOptions = { - loop: true, - autoplay: false, - animationData: animate, - rendererSettings: { - preserveAspectRatio: 'xMidYMid slice', - }, - }; - return ( <StyledWrapper + className={classes ? classes.join(' ') : undefined} onClick={() => { onClick(); - setIsStopped(false); }} whileHover={{ scale: 1.02 }} whileTap={{ scale: 0.98 }} transition={{ duration: 0.1, }} - style={{ - borderColor: minimised ? borderColor || undefined : undefined, - }} > - {animate === undefined ? ( - <IconWrapper - minimised={minimised} - className="icon" - style={{ width: size, height: size }} - > - {Svg && <Svg width={size} height={size} />} - </IconWrapper> - ) : ( - <IconWrapper minimised={minimised}> - <Lottie - options={animateOptions} - width={minimised ? '1.6rem' : '1.35rem'} - height={minimised ? '1.6rem' : '1.35rem'} - isStopped={isStopped} - isPaused={isStopped} - eventListeners={[ - { - eventName: 'loopComplete', - callback: () => setIsStopped(true), - }, - ]} - /> - </IconWrapper> - )} + <IconWrapper + $minimised={minimised} + className="icon" + style={{ width: size, height: size }} + > + {Svg && <Svg width={size} height={size} />} + </IconWrapper> + {!minimised && ( <> <div className="name">{name}</div> @@ -74,5 +45,3 @@ export const Secondary = (props: SecondaryProps) => { </StyledWrapper> ); }; - -export default Secondary; diff --git a/src/library/SideMenu/Wrapper.ts b/src/library/SideMenu/Wrapper.ts index 15253ed017..1f230562f1 100644 --- a/src/library/SideMenu/Wrapper.ts +++ b/src/library/SideMenu/Wrapper.ts @@ -1,22 +1,16 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only +import styled from 'styled-components'; import { SideMenuMaximisedWidth, SideMenuMinimisedWidth, SideMenuStickyThreshold, } from 'consts'; -import styled from 'styled-components'; -import { - backgroundOverlay, - borderPrimary, - networkColor, - textSecondary, -} from 'theme'; -import { MinimisedProps } from './types'; +import type { MinimisedProps } from './types'; export const Wrapper = styled.div<MinimisedProps>` - border-radius: ${(props) => (props.minimised ? '0.7rem' : 0)}; + border-radius: ${(props) => (props.$minimised ? '0.7rem' : 0)}; background: none; padding: 1rem 1rem 1rem 1.25rem; overflow: auto; @@ -26,20 +20,20 @@ export const Wrapper = styled.div<MinimisedProps>` flex-flow: column nowrap; backdrop-filter: blur(4px); width: ${(props) => - props.minimised + props.$minimised ? `${SideMenuMinimisedWidth}px` : `${SideMenuMaximisedWidth}px`}; + &::-webkit-scrollbar { + display: none; + } + -ms-overflow-style: none; + scrollbar-width: none; + @media (max-width: ${SideMenuStickyThreshold}px) { - background: ${backgroundOverlay}; - transition: all 0.2s; + background: var(--gradient-side-menu); + transition: all var(--transition-duration); border-radius: 0.75rem; - - &::-webkit-scrollbar { - display: none; - } - -ms-overflow-style: none; - scrollbar-width: none; } section { @@ -49,21 +43,21 @@ export const Wrapper = styled.div<MinimisedProps>` /* Footer */ &:last-child { display: flex; - flex-flow: ${(props) => (props.minimised ? 'column wrap' : 'row wrap')}; + flex-flow: ${(props) => (props.$minimised ? 'column wrap' : 'row wrap')}; align-items: center; padding-top: 0.5rem; button { + color: var(--text-color-secondary); position: relative; - color: ${textSecondary}; - transition: color 0.2s; - margin-top: ${(props) => (props.minimised ? '1.25rem' : 0)}; - margin-right: ${(props) => (props.minimised ? 0 : '1rem')}; + transition: color var(--transition-duration); + margin-top: ${(props) => (props.$minimised ? '1rem' : 0)}; + margin-right: ${(props) => (props.$minimised ? 0 : '0.9rem')}; opacity: 0.75; padding: 0.1rem; path { - fill: ${textSecondary}; + fill: var(--text-color-secondary); } &:hover { opacity: 1; @@ -76,28 +70,41 @@ export const Wrapper = styled.div<MinimisedProps>` export const LogoWrapper = styled.button<MinimisedProps>` display: flex; flex-flow: row wrap; - justify-content: ${(props) => (props.minimised ? 'center' : 'flex-start')}; + justify-content: ${(props) => (props.$minimised ? 'center' : 'flex-start')}; width: 100%; height: 2.8rem; - padding: ${(props) => (props.minimised ? '0' : '0.4rem 0.5rem')}; - margin-bottom: ${(props) => (props.minimised ? '1.5rem' : '1rem')}; + padding: ${(props) => (props.$minimised ? '0' : '0.4rem 0.5rem')}; + margin-bottom: ${(props) => (props.$minimised ? '1.5rem' : '1rem')}; position: relative; ellipse { - fill: ${networkColor}; + fill: var(--accent-color-primary); } `; export const Separator = styled.div` - border-bottom: 1px solid ${borderPrimary}; + border-bottom: 1px solid var(--border-primary-color); width: 100%; margin: 1rem 1rem 0.5rem 0; `; -export const ConnectionSymbol = styled.div<{ color: any }>` +export const ConnectionSymbol = styled.div` width: 0.6rem; height: 0.6rem; background: ${(props) => props.color}; border-radius: 50%; margin: 0 0.7rem; + + &.success { + background: var(--status-success-color); + color: var(--status-success-color); + } + &.warning { + background: var(--status-warning-color); + color: var(--status-warning-color); + } + &.danger { + background: var(--status-danger-color); + color: var(--status-danger-color); + } `; diff --git a/src/library/SideMenu/index.tsx b/src/library/SideMenu/index.tsx index 08ffbff436..8f50182fa4 100644 --- a/src/library/SideMenu/index.tsx +++ b/src/library/SideMenu/index.tsx @@ -1,44 +1,46 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only import { faCompressAlt, faExpandAlt } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { capitalizeFirstLetter } from '@polkadot-cloud/utils'; +import throttle from 'lodash.throttle'; +import { useEffect, useRef } from 'react'; +import { useTranslation } from 'react-i18next'; import { CereUrl, SideMenuStickyThreshold } from 'consts'; import { useApi } from 'contexts/Api'; -import { ConnectionStatus } from 'contexts/Api/types'; import { useHelp } from 'contexts/Help'; -import { useModal } from 'contexts/Modal'; import { useTheme } from 'contexts/Themes'; import { useUi } from 'contexts/UI'; -import { UIContextInterface } from 'contexts/UI/types'; -import { ReactComponent as CogOutlineSVG } from 'img/cog-outline.svg'; -import { ReactComponent as ForumSVG } from 'img/forum.svg'; -import { ReactComponent as InfoSVG } from 'img/info.svg'; -import { ReactComponent as LogoGithubSVG } from 'img/logo-github.svg'; -import { ReactComponent as MoonOutlineSVG } from 'img/moon-outline.svg'; -import { ReactComponent as SunnyOutlineSVG } from 'img/sunny-outline.svg'; +import type { UIContextInterface } from 'contexts/UI/types'; +import CogOutlineSVG from 'img/cog-outline.svg?react'; +import ForumSVG from 'img/forum.svg?react'; +import InfoSVG from 'img/info.svg?react'; +import LanguageSVG from 'img/language.svg?react'; +import LogoGithubSVG from 'img/logo-github.svg?react'; +import MoonOutlineSVG from 'img/moon-outline.svg?react'; +import SunnyOutlineSVG from 'img/sunny-outline.svg?react'; import { useOutsideAlerter } from 'library/Hooks'; -import throttle from 'lodash.throttle'; -import { useEffect, useRef } from 'react'; -import { useTranslation } from 'react-i18next'; -import { defaultThemes } from 'theme/default'; -import Heading from './Heading/Heading'; +import { useOverlay } from '@polkadot-cloud/react/hooks'; +import { useNetwork } from 'contexts/Network'; +import { Heading } from './Heading/Heading'; import { Main } from './Main'; import { Secondary } from './Secondary'; import { ConnectionSymbol, Separator, Wrapper } from './Wrapper'; export const SideMenu = () => { - const { network, status } = useApi(); + const { t } = useTranslation('base'); + const { apiStatus } = useApi(); + const { networkData, network } = useNetwork(); const { mode, toggleTheme } = useTheme(); - const { openModalWith } = useModal(); + const { openModal } = useOverlay().modal; const { setSideMenu, sideMenuMinimised, userSideMenuMinimised, setUserSideMenuMinimised, }: UIContextInterface = useUi(); - const { openHelpWith } = useHelp(); - const { t } = useTranslation('base'); + const { openHelp } = useHelp(); // listen to window resize to hide SideMenu useEffect(() => { @@ -50,7 +52,7 @@ export const SideMenu = () => { const throttleCallback = () => { if (window.innerWidth >= SideMenuStickyThreshold) { - setSideMenu(0); + setSideMenu(false); } }; const windowThrottle = throttle(throttleCallback, 200, { @@ -60,63 +62,57 @@ export const SideMenu = () => { const ref = useRef(null); useOutsideAlerter(ref, () => { - setSideMenu(0); + setSideMenu(false); }); - // handle connection symbol - const symbolColor = - status === ConnectionStatus.Connecting - ? defaultThemes.status.warning.solid[mode] - : status === ConnectionStatus.Connected - ? defaultThemes.status.success.solid[mode] - : defaultThemes.status.danger.solid[mode]; - - // handle transparent border color - const borderColor = - status === ConnectionStatus.Connecting - ? defaultThemes.status.warning.transparent[mode] - : status === ConnectionStatus.Connected - ? defaultThemes.status.success.transparent[mode] - : defaultThemes.status.danger.transparent[mode]; + const apiStatusClass = + apiStatus === 'connecting' + ? 'warning' + : apiStatus === 'connected' + ? 'success' + : 'danger'; return ( - <Wrapper ref={ref} minimised={sideMenuMinimised}> + <Wrapper ref={ref} $minimised={sideMenuMinimised}> <section> <Main /> <Heading title={t('support')} minimised={sideMenuMinimised} /> <Secondary onClick={() => { - openHelpWith(CereUrl, {}); + openHelp(CereUrl); }} name={t('resources')} minimised={sideMenuMinimised} icon={{ Svg: InfoSVG, - size: sideMenuMinimised ? '1.6rem' : '1.4rem', + size: sideMenuMinimised ? '1.4em' : '1.2em', }} /> <Secondary - onClick={() => openModalWith('GoToFeedback')} + onClick={() => openModal({ key: 'GoToFeedback' })} name={t('feedback')} minimised={sideMenuMinimised} icon={{ Svg: ForumSVG, - size: sideMenuMinimised ? '1.6rem' : '1.4rem', + size: sideMenuMinimised ? '1.4em' : '1.2em', }} /> <Separator /> <Heading title={t('network')} minimised={sideMenuMinimised} /> <Secondary - name={network.name} - borderColor={borderColor} - onClick={() => openModalWith('Networks')} + classes={[apiStatusClass]} + name={capitalizeFirstLetter(network)} + onClick={() => openModal({ key: 'Networks' })} icon={{ - Svg: network.brand.inline.svg, - size: network.brand.inline.size, + Svg: networkData.brand.inline.svg, + size: networkData.brand.inline.size, }} minimised={sideMenuMinimised} action={ - <ConnectionSymbol color={[symbolColor]} style={{ opacity: 0.7 }} /> + <ConnectionSymbol + className={apiStatusClass} + style={{ opacity: 0.7 }} + /> } /> </section> @@ -124,13 +120,10 @@ export const SideMenu = () => { <section> <button type="button" - onClick={() => - setUserSideMenuMinimised(userSideMenuMinimised ? 0 : 1) - } + onClick={() => setUserSideMenuMinimised(!userSideMenuMinimised)} > <FontAwesomeIcon icon={userSideMenuMinimised ? faExpandAlt : faCompressAlt} - transform="grow-3" /> </button> <button @@ -142,27 +135,27 @@ export const SideMenu = () => { ) } > - <LogoGithubSVG width="1.4rem" height="1.4rem" /> + <LogoGithubSVG width="1.2em" height="1.2em" /> + </button> + <button type="button" onClick={() => openModal({ key: 'Settings' })}> + <CogOutlineSVG width="1.3em" height="1.3em" /> </button> <button type="button" - onClick={() => openModalWith('Settings', {}, 'large')} + onClick={() => openModal({ key: 'ChooseLanguage' })} > - <CogOutlineSVG width="1.6rem" height="1.6rem" /> + <LanguageSVG width="1.25em" height="1.25em" /> </button> - - {mode === 'light' ? ( + {mode === 'dark' ? ( <button type="button" onClick={() => toggleTheme()}> - <SunnyOutlineSVG width="1.7rem" height="1.7rem" /> + <SunnyOutlineSVG width="1.25em" height="1.25em" /> </button> ) : ( <button type="button" onClick={() => toggleTheme()}> - <MoonOutlineSVG width="1.4rem" height="1.4rem" /> + <MoonOutlineSVG width="1.1em" height="1.1em" /> </button> )} </section> </Wrapper> ); }; - -export default SideMenu; diff --git a/src/library/SideMenu/types.ts b/src/library/SideMenu/types.ts index 37224ee308..18b8b0827f 100644 --- a/src/library/SideMenu/types.ts +++ b/src/library/SideMenu/types.ts @@ -1,38 +1,37 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import { AnyJson } from '@polkadot/types-codec/types'; -import React, { FunctionComponent, SVGProps } from 'react'; +import type React from 'react'; +import type { FunctionComponent, SVGProps } from 'react'; +import type { AnyJson } from 'types'; export interface MinimisedProps { - minimised: number; + $minimised?: boolean; } export interface HeadingProps { title: string; - minimised: number; + minimised: boolean; } export interface PrimaryProps { name: string; active: boolean; to: string; - icon?: React.ReactNode; - animate?: AnyJson; + lottie: AnyJson; action: undefined | { type: string; status: string; text?: string }; - minimised: number; + minimised: boolean; } export interface SecondaryProps { name: string; - borderColor?: string; + classes?: string[]; onClick: () => void; active?: boolean; to?: string; - icon?: IconProps; + icon: IconProps; action?: React.ReactNode; - minimised: number; - animate?: AnyJson; + minimised: boolean; } export interface IconProps { diff --git a/src/library/Stat/Wrapper.ts b/src/library/Stat/Wrapper.ts index cb1455f2ba..fd48be1beb 100644 --- a/src/library/Stat/Wrapper.ts +++ b/src/library/Stat/Wrapper.ts @@ -1,30 +1,87 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only import styled from 'styled-components'; -export const Wrapper = styled.div` - padding: 0.15rem 0.25rem; +export const Wrapper = styled.div<{ $isAddress?: boolean }>` width: 100%; + padding: 0.15rem 0.25rem; h4 { + font-family: InterSemiBold, sans-serif; display: flex; flex-flow: row wrap; align-items: center; - justify-content: flex-start; - margin: 0 0 0.5rem 0; + margin: 0 0 0.15rem 0; - .help-icon { - margin-left: 0.55rem; - } - } - h2 { - &.stat { + > .btn { + color: var(--text-color-secondary); + background: var(--background-primary); display: flex; flex-flow: row wrap; + justify-content: center; align-items: center; + border-radius: 2rem; + width: 1.5rem; + height: 1.5rem; + margin-left: 0.65rem; + transition: color var(--transition-duration); + &:hover { + color: var(--accent-color-primary); + } + } + } + + .content { + display: flex; + flex-flow: column nowrap; + align-items: center; + height: 2.6rem; + position: relative; + width: auto; + max-width: 100%; + overflow: hidden; + + .text { + padding-left: ${(props) => (props.$isAddress ? '3rem' : 0)}; + font-family: InterBold, sans-serif; + color: var(--text-color-primary); + padding-top: 0.25rem; + position: absolute; + left: 0; + top: 0; margin: 0; + height: 2.6rem; + height: 2.6rem; + font-size: 1.4rem; + width: auto; + max-width: 100%; + text-align: left; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + + h2 { + font-family: InterBold, sans-serif; + display: flex; + align-items: center; + text-overflow: ellipsis; + line-height: 1.4rem; + } + + .identicon { + position: absolute; + display: flex; + left: 0; + top: 0; + flex-flow: row wrap; + align-items: center; + } + > span { - flex-grow: 1; + position: absolute; + display: flex; + right: 0.2rem; + top: 0rem; } } } diff --git a/src/library/Stat/index.tsx b/src/library/Stat/index.tsx index 7c1b24a0ae..2d25c096a8 100644 --- a/src/library/Stat/index.tsx +++ b/src/library/Stat/index.tsx @@ -1,51 +1,144 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only +import { faCopy } from '@fortawesome/free-regular-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { ButtonPrimary } from '@rossbulat/polkadot-dashboard-ui'; -import { OpenHelpIcon } from 'library/OpenHelpIcon'; -import React from 'react'; -import { StatProps } from './types'; +import { + ButtonHelp, + ButtonPrimary, + ButtonSecondary, + Polkicon, + Odometer, +} from '@polkadot-cloud/react'; +import { applyWidthAsPadding, minDecimalPlaces } from '@polkadot-cloud/utils'; +import React, { useEffect, useLayoutEffect, useRef } from 'react'; +import { useHelp } from 'contexts/Help'; +import { useNotifications } from 'contexts/Notifications'; +import { useNetwork } from 'contexts/Network'; import { Wrapper } from './Wrapper'; +import type { StatAddress, StatProps } from './types'; -export const Stat = (props: StatProps) => { - const { label, stat, buttons, helpKey, icon } = props; +export const Stat = ({ + label, + stat, + buttons, + helpKey, + icon, + copy, + type = 'string', + buttonType = 'primary', +}: StatProps) => { + const { + brand: { token: Token }, + } = useNetwork().networkData; + const { openHelp } = useHelp(); + const { addNotification } = useNotifications(); + + const containerRef = useRef<HTMLDivElement>(null); + const subjectRef = useRef<HTMLDivElement>(null); + + const handleAdjustLayout = () => { + applyWidthAsPadding(subjectRef, containerRef); + }; + + useLayoutEffect(() => { + handleAdjustLayout(); + }); + + useEffect(() => { + window.addEventListener('resize', handleAdjustLayout); + return () => { + window.removeEventListener('resize', handleAdjustLayout); + }; + }, []); + + const Button = buttonType === 'primary' ? ButtonPrimary : ButtonSecondary; + + let display; + switch (type) { + case 'address': + display = stat.display; + break; + case 'odometer': + display = ( + <h2> + <Token + style={{ + width: '1.9rem', + height: '1.9rem', + marginRight: '0.55rem', + }} + /> + <Odometer + value={minDecimalPlaces(stat.value, 2)} + spaceAfter="0.4rem" + zeroDecimals={2} + /> + {stat?.unit ? stat.unit : null} + </h2> + ); + break; + default: + display = stat; + } return ( - <Wrapper> + <Wrapper $isAddress={type === 'address'}> <h4> - {label} {helpKey !== undefined && <OpenHelpIcon helpKey={helpKey} />} + {label} + {helpKey !== undefined ? ( + <ButtonHelp marginLeft onClick={() => openHelp(helpKey)} /> + ) : null} + {copy !== undefined ? ( + <button + type="button" + className="btn" + onClick={() => { + addNotification(copy.notification); + navigator.clipboard.writeText(copy.content); + }} + > + <FontAwesomeIcon icon={faCopy} transform="shrink-4" /> + </button> + ) : null} </h4> - <h2 className="stat"> - {icon && ( - <> - <FontAwesomeIcon icon={icon} transform="shrink-4" /> -   - </> - )} - {stat} - {buttons && ( - <span> -     - {buttons.map((btn: any, index: number) => ( - <React.Fragment key={`stat_${index}`}> - <ButtonPrimary - key={`btn_${index}_${Math.random()}`} - text={btn.title} - lg={btn.large ?? undefined} - iconLeft={btn.icon ?? undefined} - iconTransform={btn.transform ?? undefined} - disabled={btn.disabled ?? false} - onClick={() => btn.onClick()} - /> -    - </React.Fragment> - ))} - </span> - )} - </h2> + <div className="content"> + <div className="text" ref={containerRef}> + {icon ? ( + <> + <FontAwesomeIcon icon={icon} transform="shrink-4" /> +   + </> + ) : null} + {type === 'address' ? ( + <div className="identicon"> + <Polkicon + address={(stat as StatAddress)?.address || ''} + size="2.4rem" + /> + </div> + ) : null} + {display} + {buttons ? ( + <span ref={subjectRef}> + {buttons.map((btn: any, index: number) => ( + <React.Fragment key={`stat_${index}`}> + <Button + key={`btn_${index}_${Math.random()}`} + text={btn.title} + lg={btn.large ?? undefined} + iconLeft={btn.icon ?? undefined} + iconTransform={btn.transform ?? undefined} + disabled={btn.disabled ?? false} + onClick={() => btn.onClick()} + /> +    + </React.Fragment> + ))} + </span> + ) : null} + </div> + </div> </Wrapper> ); }; - -export default Stat; diff --git a/src/library/Stat/types.ts b/src/library/Stat/types.ts index fc5153d45d..e115163ce3 100644 --- a/src/library/Stat/types.ts +++ b/src/library/Stat/types.ts @@ -1,12 +1,28 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import { IconProp } from '@fortawesome/fontawesome-svg-core'; +import type { IconProp } from '@fortawesome/fontawesome-svg-core'; +import type { AnyObject } from '@polkadot-cloud/utils/types'; +import type { MaybeAddress } from 'types'; export interface StatProps { label: string; - stat: string; + stat: AnyObject; + type?: string; buttons?: any; helpKey: string; icon?: IconProp; + buttonType?: string; + copy?: { + content: string; + notification: { + title: string; + subtitle: string; + }; + }; +} + +export interface StatAddress { + address: MaybeAddress; + display: string; } diff --git a/src/library/StatBoxList/Item.tsx b/src/library/StatBoxList/Item.tsx index efb4c395a3..5b3bc3bd3e 100644 --- a/src/library/StatBoxList/Item.tsx +++ b/src/library/StatBoxList/Item.tsx @@ -1,5 +1,5 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only import React from 'react'; import { Number } from './Number'; @@ -7,22 +7,20 @@ import { Pie } from './Pie'; import { Text } from './Text'; import { StatBoxWrapper } from './Wrapper'; -export const StatBox = ({ children }: { children: React.ReactNode }) => { - return ( - <StatBoxWrapper - whileHover={{ scale: 1.02 }} - transition={{ - duration: 0.5, - type: 'spring', - bounce: 0.4, - }} - > - {children} - </StatBoxWrapper> - ); -}; +export const StatBox = ({ children }: { children: React.ReactNode }) => ( + <StatBoxWrapper + whileHover={{ scale: 1.02 }} + transition={{ + duration: 0.5, + type: 'spring', + bounce: 0.4, + }} + > + {children} + </StatBoxWrapper> +); -const StatBoxListItem = ({ format, params }: any) => { +export const StatBoxListItem = ({ format, params }: any) => { switch (format) { case 'chart-pie': return <Pie {...params} />; @@ -37,5 +35,3 @@ const StatBoxListItem = ({ format, params }: any) => { return null; } }; - -export default StatBoxListItem; diff --git a/src/library/StatBoxList/Number.tsx b/src/library/StatBoxList/Number.tsx index 8f0e7678c7..3fa585df2a 100644 --- a/src/library/StatBoxList/Number.tsx +++ b/src/library/StatBoxList/Number.tsx @@ -1,41 +1,39 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import NumberEasing from 'che-react-number-easing'; -import { OpenHelpIcon } from 'library/OpenHelpIcon'; +import { ButtonHelp, Odometer } from '@polkadot-cloud/react'; +import { useHelp } from 'contexts/Help'; +import BigNumber from 'bignumber.js'; import { StatBox } from './Item'; -import { NumberProps } from './types'; +import type { NumberProps } from './types'; -export const Number = (props: NumberProps) => { - const { label, value, unit, helpKey } = props; +export const Number = ({ + label, + value, + unit, + helpKey, + decimals, +}: NumberProps) => { const help = helpKey !== undefined; - - const currency = props.currency ?? ''; + const { openHelp } = useHelp(); return ( <StatBox> <div className="content chart"> <div className="labels"> - <h3 className="text"> - <NumberEasing - ease="quintInOut" - precision={2} - speed={250} - trail={false} - value={value} - useLocaleString - currency={currency} + <h3> + <Odometer + value={new BigNumber(value) + .decimalPlaces(decimals || 0) + .toFormat()} /> - {unit && ( - <> -   - {unit} - </> - )} + {unit ? <>{unit}</> : null} </h3> <h4> {label} - {help && <OpenHelpIcon helpKey={helpKey} />} + {help ? ( + <ButtonHelp marginLeft onClick={() => openHelp(helpKey)} /> + ) : null} </h4> </div> </div> diff --git a/src/library/StatBoxList/Pie.tsx b/src/library/StatBoxList/Pie.tsx index 48ca3a35ea..af5807d350 100644 --- a/src/library/StatBoxList/Pie.tsx +++ b/src/library/StatBoxList/Pie.tsx @@ -1,76 +1,73 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import NumberEasing from 'che-react-number-easing'; -import { StatPie } from 'library/Graphs/StatBoxPie'; -import { OpenHelpIcon } from 'library/OpenHelpIcon'; +import { ButtonHelp, Chart, Odometer } from '@polkadot-cloud/react'; +import { useEffect, useState } from 'react'; +import { useHelp } from 'contexts/Help'; +import BigNumber from 'bignumber.js'; import { StatBox } from './Item'; -import { PieProps } from './types'; +import type { PieProps } from './types'; -export const Pie = (props: PieProps) => { - const { label, stat, graph, tooltip, helpKey } = props; +export const Pie = ({ label, stat, graph, tooltip, helpKey }: PieProps) => { const help = helpKey !== undefined; - - const showValue = stat?.value !== 0 || stat?.total === 0; const showTotal = !!stat?.total; + const { openHelp } = useHelp(); + + const [values, setValues] = useState<any>({ + value: Number(stat?.value || 0), + total: Number(stat?.total || 0), + }); + + useEffect(() => { + setValues({ + value: Number(stat?.value || 0), + total: Number(stat?.total || 0), + }); + }, [stat]); return ( <StatBox> <div className="content chart"> <div className="chart"> - <StatPie value={graph?.value1} value2={graph?.value2} /> - {tooltip && ( + <Chart + items={[ + { + value: graph?.value1, + color: 'var(--accent-color-primary)', + }, + { + value: graph?.value2, + color: 'var(--background-default)', + }, + ]} + diameter={34} + speed={2} + /> + {tooltip ? ( <div className="tooltip"> <h3>{tooltip}</h3> </div> - )} + ) : null} </div> <div className="labels"> <h3> - {showValue ? ( - <> - <NumberEasing - ease="quintInOut" - precision={2} - speed={250} - trail={false} - value={stat?.value} - useLocaleString - /> - {stat?.unit && ( - <> -   - {stat?.unit} - </> - )} + <Odometer value={new BigNumber(values.value).toFormat()} /> + {stat?.unit && <>{stat?.unit}</>} - {showTotal && ( - <span className="total"> - /{' '} - <NumberEasing - ease="quintInOut" - precision={2} - speed={250} - trail={false} - value={stat?.total} - useLocaleString - /> - {stat?.unit && ( - <> -   - {stat?.unit} - </> - )} - </span> - )} - </> - ) : ( - <>0</> - )} + {showTotal ? ( + <span className="total"> + /  + <Odometer value={new BigNumber(values.total).toFormat()} /> + {stat?.unit ? <>{stat?.unit}unit</> : null} + </span> + ) : null} </h3> <h4> - {label} {help && <OpenHelpIcon helpKey={helpKey} />} + {label}{' '} + {help ? ( + <ButtonHelp marginLeft onClick={() => openHelp(helpKey)} /> + ) : null} </h4> </div> </div> diff --git a/src/library/StatBoxList/Text.tsx b/src/library/StatBoxList/Text.tsx index be40982f31..d5ab758a13 100644 --- a/src/library/StatBoxList/Text.tsx +++ b/src/library/StatBoxList/Text.tsx @@ -1,23 +1,34 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import { OpenHelpIcon } from 'library/OpenHelpIcon'; +import { ButtonHelp } from '@polkadot-cloud/react'; +import { useHelp } from 'contexts/Help'; import { StatBox } from './Item'; -import { TextProps } from './types'; - -export const Text = (props: TextProps) => { - const { label, value, helpKey } = props; +import { TextTitleWrapper } from './Wrapper'; +import type { TextProps } from './types'; +export const Text = ({ + label, + value, + secondaryValue, + helpKey, + primary, +}: TextProps) => { const help = helpKey !== undefined; - + const { openHelp } = useHelp(); return ( <StatBox> <div className="content chart"> <div className="labels"> - <h3 className="text">{value}</h3> + <TextTitleWrapper $primary={primary === true}> + {value} + {secondaryValue ? <span>{secondaryValue}</span> : null} + </TextTitleWrapper> <h4> {label} - {help && <OpenHelpIcon helpKey={helpKey} />} + {help ? ( + <ButtonHelp marginLeft onClick={() => openHelp(helpKey)} /> + ) : null} </h4> </div> </div> diff --git a/src/library/StatBoxList/Timeleft.tsx b/src/library/StatBoxList/Timeleft.tsx new file mode 100644 index 0000000000..e28ad34fad --- /dev/null +++ b/src/library/StatBoxList/Timeleft.tsx @@ -0,0 +1,59 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { ButtonHelp, Chart } from '@polkadot-cloud/react'; +import { useHelp } from 'contexts/Help'; +import { Countdown } from 'library/Countdown'; +import { StatBox } from './Item'; +import { TimeLeftWrapper } from './Wrapper'; +import type { TimeleftProps } from './types'; + +export const Timeleft = ({ + label, + timeleft, + graph, + tooltip, + helpKey, +}: TimeleftProps) => { + const help = helpKey !== undefined; + const { openHelp } = useHelp(); + + return ( + <StatBox> + <div className="content chart"> + <div className="chart"> + <Chart + items={[ + { + value: graph?.value1, + color: 'var(--accent-color-primary)', + }, + { + value: graph?.value2, + color: 'var(--background-default)', + }, + ]} + diameter={34} + /> + {tooltip ? ( + <div className="tooltip"> + <h3>{tooltip}</h3> + </div> + ) : null} + </div> + + <div className="labels"> + <TimeLeftWrapper> + <Countdown timeleft={timeleft} /> + </TimeLeftWrapper> + <h4> + {label}{' '} + {help ? ( + <ButtonHelp marginLeft onClick={() => openHelp(helpKey)} /> + ) : null} + </h4> + </div> + </div> + </StatBox> + ); +}; diff --git a/src/library/StatBoxList/Wrapper.ts b/src/library/StatBoxList/Wrapper.ts index 8b03087271..12bcc3aa8a 100644 --- a/src/library/StatBoxList/Wrapper.ts +++ b/src/library/StatBoxList/Wrapper.ts @@ -1,37 +1,26 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only import { motion } from 'framer-motion'; import styled from 'styled-components'; -import { - backgroundSecondary, - borderPrimary, - cardBorder, - cardShadow, - shadowColor, - textInvert, - textSecondary, - tooltipBackground, -} from 'theme'; - -export const Wrapper = styled.div` - display: flex; - flex-flow: column nowrap; - justify-content: flex-start; -`; - export const ListWrapper = styled.div` display: flex; flex-flow: row wrap; padding-top: 1rem; + + > div:last-child { + margin-bottom: 0; + .content { + margin-right: 0; + } + } `; export const StatBoxWrapper = styled(motion.div)` display: flex; flex-flow: column wrap; z-index: 0; - flex-basis: 100%; flex: 1; flex-basis: 100%; margin-bottom: 1rem; @@ -47,7 +36,6 @@ export const StatBoxWrapper = styled(motion.div)` h3 { font-size: 1.2rem; } - @media (min-width: 950px) { max-width: 300px; h3 { @@ -56,13 +44,16 @@ export const StatBoxWrapper = styled(motion.div)` } .content { - border: ${cardBorder} ${borderPrimary}; - box-shadow: ${cardShadow} ${shadowColor}; - background: ${backgroundSecondary}; + background: var(--background-primary); + box-shadow: var(--card-shadow-secondary); + + @media (max-width: 799px) { + box-shadow: var(--card-shadow); + } display: flex; border-radius: 0.95rem; margin-right: 1.25rem; - padding: 0.9rem 0; + padding: 0.9rem 0rem; max-height: 5.25rem; flex-flow: row wrap; @@ -71,37 +62,27 @@ export const StatBoxWrapper = styled(motion.div)` padding: 0.9rem 0; } - h3, - h4 { - margin: 0; - } - h4 { + font-family: InterSemiBold, sans-serif; flex: 1; display: flex; flex-flow: row wrap; align-items: center; - - .help-icon { - margin-left: 0.6rem; - } } > .chart { position: relative; display: flex; - flex-flow: row nowrap; justify-content: center; align-items: center; padding-left: 1rem; .graph { - opacity: 0.75; overflow: hidden; } .tooltip { - background: ${tooltipBackground}; + background: var(--background-invert); opacity: 0; position: absolute; top: -20px; @@ -109,13 +90,14 @@ export const StatBoxWrapper = styled(motion.div)` z-index: 2; border-radius: 0.5rem; padding: 0 0.5rem; - width: auto; - max-width: 200px; - transition: opacity 0.1s; + width: max-content; + max-width: 250px; + transition: opacity var(--transition-duration); h3 { + color: var(--text-color-invert); + font-family: InterSemiBold, sans-serif; text-align: center; - color: ${textInvert}; margin: 0; font-size: 0.9rem; } @@ -138,25 +120,80 @@ export const StatBoxWrapper = styled(motion.div)` overflow: hidden; h3 { + font-family: InterBold, sans-serif; display: flex; flex-flow: row wrap; - justify-content: flex-start; - align-items: flex-start; - margin-bottom: 0.3rem; + margin-top: 0.1rem; + margin-bottom: 0.1rem; &.text { margin-top: 0.15rem; + display: flex; + align-items: center; } - span.total { - color: ${textSecondary}; - font-size: 0.9rem; + color: var(--text-color-secondary); + display: flex; + font-size: 0.95rem; margin-left: 0.4rem; - margin-top: 0rem; + position: relative; + bottom: 0.1rem; } } } } `; -export default Wrapper; +export const TextTitleWrapper = styled.div<{ $primary?: boolean }>` + color: ${(props) => + props.$primary === true + ? 'var(--accent-color-primary)' + : 'var(--text-color-primary)'}; + font-family: InterBold, sans-serif; + display: flex; + flex-flow: row wrap; + margin-bottom: 0.35rem; + + font-size: 1.2rem; + @media (min-width: 950px) { + max-width: 300px; + font-size: 1.25rem; + } + + &.text { + margin-top: 0.15rem; + } + + span { + color: var(--text-color-primary); + font-family: InterSemiBold, sans-serif; + font-size: 0.95rem; + margin-left: 0.55rem; + margin-top: 0.1rem; + } +`; + +export const TimeLeftWrapper = styled.div<{ primary?: boolean }>` + color: ${(props) => + props.primary === true + ? 'var(--accent-color-primary)' + : 'var(--text-color-primary)'}; + font-family: InterBold, sans-serif; + display: flex; + flex-flow: row wrap; + font-size: 1.2rem; + @media (min-width: 950px) { + max-width: 300px; + font-size: 1.25rem; + } + margin-bottom: 0.15rem; + + span { + color: var(--text-color-primary); + font-family: InterSemiBold, sans-serif; + font-size: 0.95rem; + margin-left: 0.3rem; + margin-top: 0.1rem; + margin-right: 0.75rem; + } +`; diff --git a/src/library/StatBoxList/index.tsx b/src/library/StatBoxList/index.tsx index bea948fbb5..f5600d5b32 100644 --- a/src/library/StatBoxList/index.tsx +++ b/src/library/StatBoxList/index.tsx @@ -1,15 +1,12 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only +import { StatBoxRow } from '@polkadot-cloud/react'; import React from 'react'; -import { ListWrapper, Wrapper } from './Wrapper'; +import { ListWrapper } from './Wrapper'; -export const StatBoxList = ({ children }: { children: React.ReactNode }) => { - return ( - <Wrapper className="page-padding"> - <ListWrapper>{children}</ListWrapper> - </Wrapper> - ); -}; - -export default StatBoxList; +export const StatBoxList = ({ children }: { children: React.ReactNode }) => ( + <StatBoxRow> + <ListWrapper>{children}</ListWrapper> + </StatBoxRow> +); diff --git a/src/library/StatBoxList/types.ts b/src/library/StatBoxList/types.ts index b27f236ed0..e20fb52591 100644 --- a/src/library/StatBoxList/types.ts +++ b/src/library/StatBoxList/types.ts @@ -1,12 +1,14 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import type { TimeLeftFormatted } from 'library/Hooks/useTimeLeft/types'; export interface NumberProps { label: string; - value: string | number; + value: number; + decimals?: number; unit: string; helpKey: string; - currency?: string; } export interface PieProps { @@ -14,7 +16,7 @@ export interface PieProps { stat: { value: string | number; unit: string | number; - total?: number; + total?: string | number; }; graph: { value1: number; @@ -25,7 +27,23 @@ export interface PieProps { } export interface TextProps { + primary?: boolean; label: string; value: string; + secondaryValue?: string; helpKey: string; } + +export interface TimeleftProps { + label: string; + timeleft: TimeLeftFormatted; + graph: { + value1: number; + value2: number; + }; + tooltip?: string; + helpKey: string; +} + +export type TimeLeftRaw = TimeLeftRawItem[]; +export type TimeLeftRawItem = Array<number | string>; diff --git a/src/library/StatsHead/Wrapper.ts b/src/library/StatsHead/Wrapper.ts new file mode 100644 index 0000000000..ba80c8702d --- /dev/null +++ b/src/library/StatsHead/Wrapper.ts @@ -0,0 +1,72 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import styled from 'styled-components'; +import { SmallFontSizeMaxWidth } from 'consts'; + +export const Wrapper = styled.div` + flex-grow: 1; + display: flex; + flex-flow: row wrap; + align-items: center; + width: 100%; + + @media (min-width: ${SmallFontSizeMaxWidth + 225}px) { + margin-bottom: 1rem; + } + + > div { + border-right: 0; + flex-basis: 100%; + flex-grow: 1; + margin-bottom: 0.5rem; + + &:last-child { + border-right: 0; + } + + @media (min-width: ${SmallFontSizeMaxWidth + 225}px) { + border-right: 1px solid var(--border-primary-color); + flex-basis: 25%; + margin-bottom: 0; + padding-left: 1rem; + padding-right: 1rem; + max-width: 275px; + + &:last-child { + max-width: none; + } + } + + > .inner { + border-bottom: 1px solid var(--border-primary-color); + display: flex; + flex-flow: column wrap; + padding: 0.5rem 0.5rem 1rem 0.5rem; + + @media (min-width: ${SmallFontSizeMaxWidth + 225}px) { + margin-bottom: 0; + } + + h2 { + color: var(--accent-color-primary); + } + + h4 { + color: var(--text-color-secondary); + font-family: InterSemiBold, sans-serif; + display: flex; + flex-flow: row wrap; + align-items: center; + margin-top: 0.45rem; + } + } + + &:first-child { + padding-left: 0; + } + &:last-child { + padding-right: 0; + } + } +`; diff --git a/src/library/StatsHead/index.tsx b/src/library/StatsHead/index.tsx new file mode 100644 index 0000000000..9088bbe14a --- /dev/null +++ b/src/library/StatsHead/index.tsx @@ -0,0 +1,29 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { ButtonHelp } from '@polkadot-cloud/react'; +import { useHelp } from 'contexts/Help'; +import { Wrapper } from './Wrapper'; +import type { StatsHeadProps } from './types'; + +export const StatsHead = ({ items }: StatsHeadProps) => { + const { openHelp } = useHelp(); + + return ( + <Wrapper> + {items.map(({ label, value, helpKey }, i) => ( + <div key={`head_stat_${i}`}> + <div className="inner"> + <h2>{value}</h2> + <h4> + {label} + {!!helpKey && ( + <ButtonHelp marginLeft onClick={() => openHelp(helpKey)} /> + )} + </h4> + </div> + </div> + ))} + </Wrapper> + ); +}; diff --git a/src/library/StatsHead/types.ts b/src/library/StatsHead/types.ts new file mode 100644 index 0000000000..afc90c0f00 --- /dev/null +++ b/src/library/StatsHead/types.ts @@ -0,0 +1,10 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +export interface StatsHeadProps { + items: { + value: string; + label: string; + helpKey?: string; + }[]; +} diff --git a/src/library/StatusButton/Wrapper.ts b/src/library/StatusButton/Wrapper.ts index 1d843e6731..9ed5e1dfe5 100644 --- a/src/library/StatusButton/Wrapper.ts +++ b/src/library/StatusButton/Wrapper.ts @@ -1,10 +1,11 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only import styled from 'styled-components'; -import { buttonPrimaryBackground, textPrimary, textSecondary } from 'theme'; export const Wrapper = styled.button` + background: var(--button-primary-background); + color: var(--text-color-primary); width: 100%; flex: 1; padding: 1rem 0.75rem; @@ -14,24 +15,19 @@ export const Wrapper = styled.button` display: flex; flex-flow: row-reverse wrap; align-items: center; - background: ${buttonPrimaryBackground}; - transition: all 0.15s; - color: ${textPrimary}; + transition: all var(--transition-duration); > section:last-child { - color: ${textSecondary}; + color: var(--text-color-secondary); padding-left: 0.25rem; display: flex; flex-flow: row wrap; - justify-content: flex-start; flex: 1; } &:hover { > section { - color: ${textPrimary}; + color: var(--text-color-primary); } } `; - -export default Wrapper; diff --git a/src/library/StatusButton/index.tsx b/src/library/StatusButton/index.tsx index d9c2ef1790..bb2f8d2cff 100644 --- a/src/library/StatusButton/index.tsx +++ b/src/library/StatusButton/index.tsx @@ -1,33 +1,30 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import { IconProp } from '@fortawesome/fontawesome-svg-core'; import { faCircle } from '@fortawesome/free-regular-svg-icons'; import { faCheck } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { StatusButtonProps } from './types'; import { Wrapper } from './Wrapper'; +import type { StatusButtonProps } from './types'; -export const StatusButton = (props: StatusButtonProps) => { - const { checked, label, onClick } = props; - - return ( - <Wrapper - onClick={() => { - if (onClick !== undefined) { - onClick(); - } - }} - > - <section className={checked ? 'checked' : undefined}> - <FontAwesomeIcon - icon={checked ? (faCheck as IconProp) : (faCircle as IconProp)} - transform="shrink-3" - /> - </section> - <section>{label}</section> - </Wrapper> - ); -}; - -export default StatusButton; +export const StatusButton = ({ + checked, + label, + onClick, +}: StatusButtonProps) => ( + <Wrapper + onClick={() => { + if (onClick !== undefined) { + onClick(); + } + }} + > + <section className={checked ? 'checked' : undefined}> + <FontAwesomeIcon + icon={checked ? faCheck : faCircle} + transform="shrink-3" + /> + </section> + <section>{label}</section> + </Wrapper> +); diff --git a/src/library/StatusButton/types.ts b/src/library/StatusButton/types.ts index 2e9d71ebc5..420b62f425 100644 --- a/src/library/StatusButton/types.ts +++ b/src/library/StatusButton/types.ts @@ -1,5 +1,5 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only export interface StatusButtonProps { checked: boolean; diff --git a/src/library/StatusLabel/Wrapper.ts b/src/library/StatusLabel/Wrapper.ts index 3e8cf4b3be..b073f88ed8 100644 --- a/src/library/StatusLabel/Wrapper.ts +++ b/src/library/StatusLabel/Wrapper.ts @@ -1,32 +1,31 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only import styled from 'styled-components'; -import { backgroundLabel, textSecondary } from 'theme'; -import { WrapperProps } from './types'; +import type { WrapperProps } from './types'; export const Wrapper = styled.div<WrapperProps>` position: absolute; - top: ${(props) => (props.topOffset ? props.topOffset : '50%')}; + top: ${(props) => (props.$topOffset ? props.$topOffset : '50%')}; left: 0; width: 100%; display: flex; - flex-flow: row wrap; justify-content: center; align-items: center; z-index: 2; > div { - background: ${backgroundLabel}; + background: var(--background-list-item); + min-width: 125px; opacity: 0.75; padding: 1rem 1.25rem; border-radius: 1rem; display: flex; - flex-flow: row wrap; align-items: center; + justify-content: center; > svg { - color: ${textSecondary}; + color: var(--text-color-secondary); } h2 { padding: 0; @@ -37,9 +36,5 @@ export const Wrapper = styled.div<WrapperProps>` font-size: 1.2rem; opacity: 0.75; } - - span { - margin-left: 0.65rem; - } } `; diff --git a/src/library/StatusLabel/index.tsx b/src/library/StatusLabel/index.tsx index 17191d7873..c4948bf4f1 100644 --- a/src/library/StatusLabel/index.tsx +++ b/src/library/StatusLabel/index.tsx @@ -1,21 +1,30 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only import { faExclamationTriangle } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { ButtonHelp } from '@polkadot-cloud/react'; +import { useHelp } from 'contexts/Help'; +import { usePlugins } from 'contexts/Plugins'; import { usePoolMemberships } from 'contexts/Pools/PoolMemberships'; import { useStaking } from 'contexts/Staking'; import { useUi } from 'contexts/UI'; -import OpenHelpIcon from 'library/OpenHelpIcon'; -import { StatusLabelProps } from './types'; import { Wrapper } from './Wrapper'; - -export const StatusLabel = (props: StatusLabelProps) => { - const status = props.status ?? 'sync_or_setup'; - - const { isSyncing, services } = useUi(); +import type { StatusLabelProps } from './types'; + +export const StatusLabel = ({ + title, + helpKey, + hideIcon, + statusFor, + topOffset = '40%', + status = 'sync_or_setup', +}: StatusLabelProps) => { + const { isSyncing } = useUi(); + const { plugins } = usePlugins(); const { inSetup } = useStaking(); const { membership } = usePoolMemberships(); + const { openHelp } = useHelp(); // syncing or not staking if (status === 'sync_or_setup') { @@ -24,33 +33,29 @@ export const StatusLabel = (props: StatusLabelProps) => { } } - if (status === 'active_service') { - if (services.includes(props.statusFor || '')) { + if (status === 'active_service' && statusFor) + if (plugins.includes(statusFor)) { return <></>; } - } - - const { title } = props; - const topOffset = props.topOffset ?? '40%'; return ( - <Wrapper topOffset={topOffset}> + <Wrapper $topOffset={topOffset}> <div> - {props.hideIcon !== true && ( - <FontAwesomeIcon icon={faExclamationTriangle} /> - )} + {hideIcon !== true && <FontAwesomeIcon icon={faExclamationTriangle} />} <h2>    {title} - {props.helpKey && ( + {helpKey ? ( <span> - <OpenHelpIcon helpKey={props.helpKey} light /> + <ButtonHelp + marginLeft + onClick={() => openHelp(helpKey)} + background="secondary" + /> </span> - )} + ) : null} </h2> </div> </Wrapper> ); }; - -export default StatusLabel; diff --git a/src/library/StatusLabel/types.ts b/src/library/StatusLabel/types.ts index 5b9a6d1774..9e97ea75e5 100644 --- a/src/library/StatusLabel/types.ts +++ b/src/library/StatusLabel/types.ts @@ -1,15 +1,17 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import type { Plugin } from 'types'; export interface StatusLabelProps { hideIcon?: boolean; status: string; - statusFor?: string; + statusFor?: Plugin; title: string; topOffset?: string; helpKey?: string; } export interface WrapperProps { - topOffset?: string; + $topOffset?: string; } diff --git a/src/library/SubmitTx/Default.tsx b/src/library/SubmitTx/Default.tsx new file mode 100644 index 0000000000..6d11ae3809 --- /dev/null +++ b/src/library/SubmitTx/Default.tsx @@ -0,0 +1,46 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { faArrowAltCircleUp } from '@fortawesome/free-regular-svg-icons'; +import { ButtonSubmit } from '@polkadot-cloud/react'; +import React from 'react'; +import { useTxMeta } from 'contexts/TxMeta'; +import { EstimatedTxFee } from 'library/EstimatedTxFee'; +import { useImportedAccounts } from 'contexts/Connect/ImportedAccounts'; +import type { SubmitProps } from './types'; + +export const Default = ({ + onSubmit, + submitting, + valid, + submitText, + buttons, + submitAddress, + displayFor, +}: SubmitProps & { buttons?: React.ReactNode[] }) => { + const { txFeesValid } = useTxMeta(); + const { accountHasSigner } = useImportedAccounts(); + + const disabled = + submitting || !valid || !accountHasSigner(submitAddress) || !txFeesValid; + + return ( + <> + <div> + <EstimatedTxFee /> + </div> + <div> + {buttons} + <ButtonSubmit + lg={displayFor === 'canvas'} + text={submitText || ''} + iconLeft={faArrowAltCircleUp} + iconTransform="grow-2" + onClick={() => onSubmit()} + disabled={disabled} + pulse={!disabled} + /> + </div> + </> + ); +}; diff --git a/src/library/SubmitTx/ManualSign/Ledger.tsx b/src/library/SubmitTx/ManualSign/Ledger.tsx new file mode 100644 index 0000000000..d9509bc787 --- /dev/null +++ b/src/library/SubmitTx/ManualSign/Ledger.tsx @@ -0,0 +1,174 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { faSquarePen } from '@fortawesome/free-solid-svg-icons'; +import { ButtonHelp, ButtonSubmit } from '@polkadot-cloud/react'; +import React, { useEffect, useRef } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useLedgerHardware } from 'contexts/Hardware/Ledger'; +import type { LedgerResponse } from 'contexts/Hardware/types'; +import { useHelp } from 'contexts/Help'; +import { useTxMeta } from 'contexts/TxMeta'; +import { EstimatedTxFee } from 'library/EstimatedTxFee'; +import { useLedgerLoop } from 'library/Hooks/useLedgerLoop'; +import { useOverlay } from '@polkadot-cloud/react/hooks'; +import { useActiveAccounts } from 'contexts/ActiveAccounts'; +import { useImportedAccounts } from 'contexts/Connect/ImportedAccounts'; +import type { LedgerAccount } from '@polkadot-cloud/react/types'; +import type { SubmitProps } from '../types'; + +export const Ledger = ({ + uid, + onSubmit, + submitting, + valid, + submitText, + buttons, + submitAddress, + displayFor, +}: SubmitProps & { buttons?: React.ReactNode[] }) => { + const { t } = useTranslation('library'); + const { + pairDevice, + transportResponse, + setIsExecuting, + resetStatusCodes, + getIsExecuting, + handleNewStatusCode, + isPaired, + getStatusCodes, + getFeedback, + setFeedback, + handleUnmount, + } = useLedgerHardware(); + const { openHelp } = useHelp(); + const { setModalResize } = useOverlay().modal; + const { activeAccount } = useActiveAccounts(); + const { accountHasSigner } = useImportedAccounts(); + const { getAccount } = useImportedAccounts(); + const { txFeesValid, setTxSignature, getTxSignature } = useTxMeta(); + + const getAddressIndex = () => { + return (getAccount(activeAccount) as LedgerAccount)?.index || 0; + }; + + // Ledger loop needs to keep track of whether this component is mounted. If it is unmounted then + // the loop will cancel & ledger metadata will be cleared up. isMounted needs to be given as a + // function so the interval fetches the real value. + const isMounted = useRef(true); + const getIsMounted = () => isMounted.current; + + const { handleLedgerLoop } = useLedgerLoop({ + tasks: ['sign_tx'], + options: { + accountIndex: getAddressIndex, + }, + mounted: getIsMounted, + }); + + // Handle new Ledger status report. + const handleLedgerStatusResponse = (response: LedgerResponse) => { + if (!response) return; + const { ack, statusCode, body } = response; + + if (statusCode === 'SignedPayload') { + if (uid !== body.uid) { + // UIDs do not match, so this is not the transaction we are waiting for. + setFeedback(t('wrongTransaction'), 'Wrong Transaction'); + resetStatusCodes(); + setTxSignature(null); + } else { + // Important: only set the signature (and therefore trigger the transaction submission) if + // UIDs match. + handleNewStatusCode(ack, statusCode); + setTxSignature(body.sig); + resetStatusCodes(); + } + setIsExecuting(false); + } else { + handleNewStatusCode(ack, statusCode); + } + }; + + // Resize modal on content change. + useEffect(() => { + setModalResize(); + }, [isPaired, getStatusCodes()]); + + // Listen for new Ledger status reports. + useEffect(() => { + if (getIsExecuting()) { + handleLedgerStatusResponse(transportResponse); + } + }, [transportResponse]); + + // Tidy up context state when this component is no longer mounted. + useEffect(() => { + return () => { + isMounted.current = false; + handleUnmount(); + }; + }, []); + + // Get the latest Ledger loop feedback. + const feedback = getFeedback(); + + // Help key based on Ledger status. + const helpKey = feedback?.helpKey; + + // The state under which submission is disabled. + const disabled = + submitting || !valid || !accountHasSigner(submitAddress) || !txFeesValid; + + return ( + <> + <div> + <EstimatedTxFee /> + {valid ? ( + <p> + {feedback?.message || t('submitTransaction')} + {helpKey ? ( + <ButtonHelp + marginLeft + onClick={() => openHelp(helpKey)} + background="secondary" + /> + ) : null} + </p> + ) : ( + <p>...</p> + )} + </div> + <div> + {buttons} + {getTxSignature() !== null || submitting ? ( + <ButtonSubmit + lg={displayFor === 'canvas'} + text={submitText || ''} + iconLeft={faSquarePen} + iconTransform="grow-2" + onClick={() => onSubmit()} + disabled={disabled} + pulse={!(disabled || getIsExecuting())} + /> + ) : ( + <ButtonSubmit + lg={displayFor === 'canvas'} + text={getIsExecuting() ? t('signing') : t('sign')} + iconLeft={faSquarePen} + iconTransform="grow-2" + onClick={async () => { + const paired = await pairDevice(); + if (paired) { + setIsExecuting(true); + handleLedgerLoop(); + } + }} + disabled={disabled || getIsExecuting()} + pulse={!(disabled || getIsExecuting())} + /> + )} + </div> + </> + ); +}; diff --git a/src/library/SubmitTx/ManualSign/Vault/SignPrompt.tsx b/src/library/SubmitTx/ManualSign/Vault/SignPrompt.tsx new file mode 100644 index 0000000000..c7e7aaaf0f --- /dev/null +++ b/src/library/SubmitTx/ManualSign/Vault/SignPrompt.tsx @@ -0,0 +1,98 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { + faChevronLeft, + faChevronRight, +} from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { ButtonPrimary, ButtonSecondary } from '@polkadot-cloud/react'; +import { useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { usePrompt } from 'contexts/Prompt'; +import { useTxMeta } from 'contexts/TxMeta'; +import { QRViewerWrapper } from 'library/Import/Wrappers'; +import { QrDisplayPayload } from 'library/QRCode/DisplayPayload'; +import { QrScanSignature } from 'library/QRCode/ScanSignature'; +import type { SignerPromptProps } from 'library/SubmitTx/types'; +import type { AnyJson } from 'types'; + +export const SignPrompt = ({ submitAddress }: SignerPromptProps) => { + const { t } = useTranslation('library'); + const { getTxPayload, setTxSignature } = useTxMeta(); + const payload = getTxPayload(); + const payloadU8a = payload?.toU8a(); + const { setStatus: setPromptStatus } = usePrompt(); + + // Whether user is on sign or submit stage. + const [stage, setStage] = useState(1); + + return ( + <QRViewerWrapper> + {stage === 1 && <h3 className="title">{t('scanPolkadotVault')}</h3>} + {stage === 2 && <h3 className="title">{t('signPolkadotVault')}</h3>} + + <div className="progress"> + <span className={stage === 1 ? 'active' : undefined}>Scan</span> + <FontAwesomeIcon + icon={faChevronRight} + transform="shrink-4" + className="arrow" + /> + <span className={stage === 2 ? 'active' : undefined}>Sign</span> + </div> + {stage === 1 && ( + <div className="viewer withBorder"> + <QrDisplayPayload + address={submitAddress || ''} + cmd={2} + genesisHash={payload?.genesisHash} + payload={payloadU8a} + style={{ width: '100%', maxWidth: 250 }} + /> + </div> + )} + {stage === 2 && ( + <div className="viewer"> + <QrScanSignature + size={279} + onScan={({ signature }: AnyJson) => { + setPromptStatus(0); + setTxSignature(signature); + }} + /> + </div> + )} + <div className="foot"> + <div> + {stage === 2 && ( + <ButtonSecondary + text={t('backToScan')} + lg + onClick={() => setStage(1)} + iconLeft={faChevronLeft} + iconTransform="shrink-3" + /> + )} + {stage === 1 && ( + <ButtonPrimary + text={t('iHaveScanned')} + lg + onClick={() => { + setStage(2); + }} + iconRight={faChevronRight} + iconTransform="shrink-3" + /> + )} + <ButtonSecondary + text={t('cancel')} + lg + marginLeft + onClick={() => setPromptStatus(0)} + /> + </div> + </div> + </QRViewerWrapper> + ); +}; diff --git a/src/library/SubmitTx/ManualSign/Vault/index.tsx b/src/library/SubmitTx/ManualSign/Vault/index.tsx new file mode 100644 index 0000000000..e7c0875d7d --- /dev/null +++ b/src/library/SubmitTx/ManualSign/Vault/index.tsx @@ -0,0 +1,70 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { faSquarePen } from '@fortawesome/free-solid-svg-icons'; +import { ButtonSubmit } from '@polkadot-cloud/react'; +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import { usePrompt } from 'contexts/Prompt'; +import { useTxMeta } from 'contexts/TxMeta'; +import { EstimatedTxFee } from 'library/EstimatedTxFee'; +import { useImportedAccounts } from 'contexts/Connect/ImportedAccounts'; +import type { SubmitProps } from '../../types'; +import { SignPrompt } from './SignPrompt'; + +export const Vault = ({ + onSubmit, + submitting, + valid, + submitText, + buttons, + submitAddress, + displayFor, +}: SubmitProps & { buttons?: React.ReactNode[] }) => { + const { t } = useTranslation('library'); + const { accountHasSigner } = useImportedAccounts(); + const { txFeesValid, getTxSignature } = useTxMeta(); + const { openPromptWith, status: promptStatus } = usePrompt(); + + // The state under which submission is disabled. + const disabled = + submitting || !valid || !accountHasSigner(submitAddress) || !txFeesValid; + + return ( + <> + <div> + <EstimatedTxFee /> + {valid ? <p>{t('submitTransaction')}</p> : <p>...</p>} + </div> + <div> + {buttons} + {getTxSignature() !== null || submitting ? ( + <ButtonSubmit + lg={displayFor === 'canvas'} + text={submitText || ''} + iconLeft={faSquarePen} + iconTransform="grow-2" + onClick={() => onSubmit()} + disabled={disabled} + pulse={!(!valid || promptStatus !== 0)} + /> + ) : ( + <ButtonSubmit + lg={displayFor === 'canvas'} + text={promptStatus === 0 ? t('sign') : t('signing')} + iconLeft={faSquarePen} + iconTransform="grow-2" + onClick={async () => { + openPromptWith( + <SignPrompt submitAddress={submitAddress} />, + 'small' + ); + }} + disabled={disabled || promptStatus !== 0} + pulse={!disabled || promptStatus === 0} + /> + )} + </div> + </> + ); +}; diff --git a/src/library/SubmitTx/ManualSign/index.tsx b/src/library/SubmitTx/ManualSign/index.tsx new file mode 100644 index 0000000000..9220efa5b5 --- /dev/null +++ b/src/library/SubmitTx/ManualSign/index.tsx @@ -0,0 +1,34 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import React, { useEffect } from 'react'; +import { useTxMeta } from 'contexts/TxMeta'; +import { useImportedAccounts } from 'contexts/Connect/ImportedAccounts'; +import type { SubmitProps } from '../types'; +import { Ledger } from './Ledger'; +import { Vault } from './Vault'; + +export const ManualSign = ( + props: SubmitProps & { buttons?: React.ReactNode[] } +) => { + const { getAccount } = useImportedAccounts(); + const { getTxSignature, sender } = useTxMeta(); + const accountMeta = getAccount(sender); + const source = accountMeta?.source; + + const { onSubmit } = props; + + // Automatically submit transaction once it is signed. + useEffect(() => { + if (getTxSignature() !== null) { + onSubmit(); + } + }, [getTxSignature()]); + + return ( + <> + {source === 'ledger' && <Ledger {...props} />} + {source === 'vault' && <Vault {...props} />} + </> + ); +}; diff --git a/src/library/SubmitTx/index.tsx b/src/library/SubmitTx/index.tsx new file mode 100644 index 0000000000..485ac70789 --- /dev/null +++ b/src/library/SubmitTx/index.tsx @@ -0,0 +1,111 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { Tx } from '@polkadot-cloud/react'; +import { useEffect } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useBonded } from 'contexts/Bonded'; +import { useTxMeta } from 'contexts/TxMeta'; +import { useOverlay } from '@polkadot-cloud/react/hooks'; +import { useNetwork } from 'contexts/Network'; +import { useActiveAccounts } from 'contexts/ActiveAccounts'; +import { useImportedAccounts } from 'contexts/Connect/ImportedAccounts'; +import { Default } from './Default'; +import { ManualSign } from './ManualSign'; +import type { SubmitTxProps } from './types'; + +export const SubmitTx = ({ + uid, + onSubmit, + submitText, + buttons = [], + submitAddress, + valid = false, + noMargin = false, + submitting = false, + proxySupported, + displayFor = 'default', + fromController = false, +}: SubmitTxProps) => { + const { t } = useTranslation(); + const { getBondedAccount } = useBonded(); + const { unit } = useNetwork().networkData; + const { setModalResize } = useOverlay().modal; + const { activeAccount, activeProxy } = useActiveAccounts(); + const { notEnoughFunds, sender, setTxSignature } = useTxMeta(); + const { getAccount, requiresManualSign } = useImportedAccounts(); + const controller = getBondedAccount(activeAccount); + + // Default to active account + let signingOpts = { + label: t('signer', { ns: 'library' }), + who: getAccount(activeAccount), + }; + + if (activeProxy && proxySupported) { + signingOpts = { + label: t('signedByProxy', { ns: 'library' }), + who: getAccount(activeProxy), + }; + } else if (!(activeProxy && proxySupported) && fromController) { + signingOpts = { + label: t('signedByController', { ns: 'library' }), + who: getAccount(controller), + }; + } + + submitText = + submitText || + `${ + submitting + ? t('submitting', { ns: 'modals' }) + : t('submit', { ns: 'modals' }) + }`; + + // Set resize on not enough funds. + useEffect(() => { + setModalResize(); + }, [notEnoughFunds, fromController]); + + // Reset tx metadata on unmount. + useEffect(() => { + return () => { + setTxSignature(null); + }; + }, []); + + return ( + <Tx + displayFor={displayFor} + margin={!noMargin} + label={signingOpts.label} + name={signingOpts.who?.name || ''} + notEnoughFunds={notEnoughFunds} + dangerMessage={`${t('notEnough', { ns: 'library' })} ${unit}`} + SignerComponent={ + requiresManualSign(sender) ? ( + <ManualSign + uid={uid} + onSubmit={onSubmit} + submitting={submitting} + valid={valid} + submitText={submitText} + buttons={buttons} + submitAddress={submitAddress} + displayFor={displayFor} + /> + ) : ( + <Default + onSubmit={onSubmit} + submitting={submitting} + valid={valid} + submitText={submitText} + buttons={buttons} + submitAddress={submitAddress} + displayFor={displayFor} + /> + ) + } + /> + ); +}; diff --git a/src/library/SubmitTx/types.ts b/src/library/SubmitTx/types.ts new file mode 100644 index 0000000000..5b3261e832 --- /dev/null +++ b/src/library/SubmitTx/types.ts @@ -0,0 +1,27 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import type React from 'react'; +import type { DisplayFor, MaybeAddress } from 'types'; + +export type SubmitTxProps = SubmitProps & { + buttons?: React.ReactNode[]; + fromController?: boolean; + proxySupported: boolean; + submitAddress?: MaybeAddress; + noMargin?: boolean; +}; + +export interface SubmitProps { + uid?: number; + onSubmit: () => void; + submitting: boolean; + valid: boolean; + submitText?: string; + submitAddress: MaybeAddress; + displayFor?: DisplayFor; +} + +export interface SignerPromptProps { + submitAddress: MaybeAddress; +} diff --git a/src/library/SubscanButton/index.tsx b/src/library/SubscanButton/index.tsx deleted file mode 100644 index 2093847b52..0000000000 --- a/src/library/SubscanButton/index.tsx +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { faProjectDiagram } from '@fortawesome/free-solid-svg-icons'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { useApi } from 'contexts/Api'; -import { useTheme } from 'contexts/Themes'; -import { useUi } from 'contexts/UI'; -import styled from 'styled-components'; -import { defaultThemes, networkColors } from 'theme/default'; -import { WrapperProps } from './types'; - -const Wrapper = styled.div<WrapperProps>` - position: absolute; - right: 10px; - top: 10px; - font-size: 0.9rem; - border-radius: 0.3rem; - padding: 0.25rem 0.4rem; - color: ${(props) => props.color}; - opacity: ${(props) => props.opacity}; - z-index: 2; -`; - -export const SubscanButton = () => { - const { network } = useApi(); - const { mode } = useTheme(); - const { services } = useUi(); - - return ( - <Wrapper - color={ - services.includes('subscan') - ? networkColors[`${network.name}-${mode}`] - : defaultThemes.text.secondary[mode] - } - opacity={services.includes('cereStats') ? 1 : 0.5} - > - <FontAwesomeIcon - icon={faProjectDiagram} - transform="shrink-2" - style={{ marginRight: '0.3rem' }} - /> - Cere Stats - </Wrapper> - ); -}; - -export default SubscanButton; diff --git a/src/library/SubscanButton/types.ts b/src/library/SubscanButton/types.ts deleted file mode 100644 index 33e788290d..0000000000 --- a/src/library/SubscanButton/types.ts +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -export interface StatusLabelProps { - status: string; - statusFor?: string; - title: string; - topOffset?: string; -} - -export interface WrapperProps { - color: string; - opacity: number; -} diff --git a/src/library/Tips/Items/Dismiss.tsx b/src/library/Tips/Items/Dismiss.tsx deleted file mode 100644 index aa3671aacc..0000000000 --- a/src/library/Tips/Items/Dismiss.tsx +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { useTips } from 'contexts/Tips'; -import { useUi } from 'contexts/UI'; -import { Button } from 'library/Button'; -import { TipWrapper } from '../Wrappers'; - -export const Dismiss = () => { - const { closeTip } = useTips(); - const { toggleService } = useUi(); - - return ( - <TipWrapper> - <div> - <h1>Dismiss Tips</h1> - </div> - <div> - <h4>Dismissing tips will remove them from your overview.</h4> - <h4> - Tips can be turned re-enabled from dashboard settings, that can be - accessed via the cog icon in the bottom left corner of the side menu. - </h4> - <div className="buttons"> - <Button - primary - inline - title="Disable Dashboard Tips" - onClick={() => { - toggleService('tips'); - closeTip(); - }} - /> - <Button - inline - title="Cancel" - onClick={() => { - closeTip(); - }} - /> - </div> - </div> - </TipWrapper> - ); -}; diff --git a/src/library/Tips/Items/Tip.tsx b/src/library/Tips/Items/Tip.tsx deleted file mode 100644 index 0cbb95db6d..0000000000 --- a/src/library/Tips/Items/Tip.tsx +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { faTimes } from '@fortawesome/free-solid-svg-icons'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { useTips } from 'contexts/Tips'; -import { TipWrapper } from '../Wrappers'; - -export const Tip = (props: any) => { - const { title, description } = props; - - const { closeTip } = useTips(); - - return ( - <> - <TipWrapper> - <div className="close-button"> - <button type="button" onClick={() => closeTip()}> - <FontAwesomeIcon icon={faTimes} /> - Close - </button> - </div> - <div> - <h1>{title}</h1> - </div> - <div> - {description.map((item: any, index: number) => ( - <h4 key={`inner_def_${index}`} className="definition"> - {item} - </h4> - ))} - </div> - </TipWrapper> - </> - ); -}; diff --git a/src/library/Tips/Tip.tsx b/src/library/Tips/Tip.tsx index e3773cbdac..c25e0eb3d0 100644 --- a/src/library/Tips/Tip.tsx +++ b/src/library/Tips/Tip.tsx @@ -1,21 +1,88 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import { Title } from 'library/Overlay/Title'; +import { faAngleRight } from '@fortawesome/free-solid-svg-icons'; +import { + ButtonPrimary, + ButtonPrimaryInvert, + ButtonSecondary, +} from '@polkadot-cloud/react'; +import { useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useNavigate } from 'react-router-dom'; +import { Title } from 'library/Prompt/Title'; +import { usePrompt } from 'contexts/Prompt'; +import { usePlugins } from 'contexts/Plugins'; -export const Tip = (props: any) => { - const { title, description } = props; +export const Tip = ({ title, description, page }: any) => { + const { t } = useTranslation(); + const navigate = useNavigate(); + const { togglePlugin } = usePlugins(); + const { closePrompt } = usePrompt(); + + const [disabling, setDisabling] = useState<boolean>(false); return ( <> - <Title title={title} /> - <div className="body"> - {description.map((item: any, index: number) => ( - <h4 key={`inner_def_${index}`} className="definition"> - {item} - </h4> - ))} - </div> + {disabling ? ( + <> + <Title title={t('module.dismissTips', { ns: 'tips' })} hideDone /> + <div className="body"> + <h4>{t('module.dismissResult', { ns: 'tips' })}</h4> + <h4>{t('module.reEnable', { ns: 'tips' })}</h4> + + <div style={{ display: 'flex', marginTop: '1.5rem' }}> + <ButtonPrimary + marginRight + text={t('module.disableTips', { ns: 'tips' })} + onClick={() => { + togglePlugin('tips'); + closePrompt(); + }} + /> + <ButtonPrimaryInvert + text={t('module.cancel', { ns: 'tips' })} + onClick={() => setDisabling(false)} + style={{ marginLeft: '0.5rem' }} + /> + </div> + </div> + </> + ) : ( + <> + <Title title={title} /> + <div className="body"> + {description.map((item: any, index: number) => ( + <h4 key={`inner_def_${index}`} className="definition"> + {item} + </h4> + ))} + <div style={{ marginTop: '1.75rem', display: 'flex' }}> + {!!page && ( + <ButtonPrimary + marginRight + text={`${t('goTo', { ns: 'base' })} ${t(page, { + ns: 'base', + })}`} + onClick={() => { + closePrompt(); + navigate(`/${page}`); + }} + iconRight={faAngleRight} + iconTransform="shrink-1" + /> + )} + <ButtonSecondary + marginRight + text={t('module.disableTips', { ns: 'tips' })} + onClick={() => { + setDisabling(true); + }} + /> + </div> + </div> + </> + )} </> ); }; diff --git a/src/library/Tips/Wrappers.ts b/src/library/Tips/Wrappers.ts index d9c0014780..5c4fcfb644 100644 --- a/src/library/Tips/Wrappers.ts +++ b/src/library/Tips/Wrappers.ts @@ -1,19 +1,16 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only import { motion } from 'framer-motion'; import styled from 'styled-components'; -import { helpButton, textPrimary, textSecondary } from 'theme'; export const TipWrapper = styled(motion.div)` - background: ${helpButton}; width: 100%; display: flex; border-radius: 1.5rem; margin-bottom: 1.25rem; padding: 2rem 2rem 1rem 2rem; flex-flow: column wrap; - align-items: flex-start; position: relative; overflow: hidden; flex: 1; @@ -24,7 +21,7 @@ export const TipWrapper = styled(motion.div)` flex-flow: row wrap; align-items: center; > span { - color: ${textSecondary}; + color: var(--text-color-secondary); margin-left: 0.75rem; opacity: 0.75; font-size: 1.1rem; @@ -32,11 +29,11 @@ export const TipWrapper = styled(motion.div)` } h4 { - margin-top: 0; + margin-bottom: 1.25rem; } p { - color: ${textPrimary}; + color: var(--text-color-primary); margin: 0.5rem 0 0 0; text-align: left; } diff --git a/src/library/Tooltip/Wrapper.ts b/src/library/Tooltip/Wrapper.ts index a749509778..fdbc82d51c 100644 --- a/src/library/Tooltip/Wrapper.ts +++ b/src/library/Tooltip/Wrapper.ts @@ -1,24 +1,23 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only import styled from 'styled-components'; -import { textInvert, tooltipBackground } from 'theme'; export const Wrapper = styled.div` - background: ${tooltipBackground}; + background: var(--background-invert); + transition: opacity var(--transition-duration); display: flex; flex-flow: column wrap; - transition: opacity 0.1s; border-radius: 0.5rem; - padding: 0.25rem 0.5rem; - min-width: 100px; + padding: 0.25rem 0.75rem; + width: max-content; max-width: 200px; h3 { - color: ${textInvert}; + color: var(--text-color-invert); + font-family: InterSemiBold, sans-serif; font-size: 0.9rem; padding: 0; - margin: 0; text-align: center; } `; diff --git a/src/library/Tooltip/index.tsx b/src/library/Tooltip/index.tsx index f3140e62f4..ad1e02fc04 100644 --- a/src/library/Tooltip/index.tsx +++ b/src/library/Tooltip/index.tsx @@ -1,19 +1,26 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import { useTooltip } from 'contexts/Tooltip'; import { useEffect, useRef } from 'react'; +import { useTooltip } from 'contexts/Tooltip'; import { Wrapper } from './Wrapper'; export const Tooltip = () => { - const tooltip = useTooltip(); - const { position } = tooltip; + const { + open, + text, + show, + position, + showTooltip, + closeTooltip, + setTooltipPosition, + } = useTooltip(); - const ref = useRef(null); + // Ref for the tooltip element itself. + const tooltipRef: any = useRef(null); useEffect(() => { - if (tooltip.open === 1) { - tooltip.checkTooltipPosition(ref); + if (open === 1) { window.addEventListener('mousemove', mouseMoveCallback); } else { window.removeEventListener('mousemove', mouseMoveCallback); @@ -21,39 +28,44 @@ export const Tooltip = () => { return () => { window.removeEventListener('mousemove', mouseMoveCallback); }; - }, [tooltip.open]); + }, [open]); const mouseMoveCallback = (e: any) => { - const isTriggerElement = e.target?.classList.contains( + const { target, pageX, pageY } = e; + + if (tooltipRef?.current) { + setTooltipPosition(pageX, pageY - (tooltipRef.current.offsetHeight || 0)); + if (!show) showTooltip(); + } + + const isTriggerElement = target?.classList.contains( 'tooltip-trigger-element' ); - const dataAttribute = e.target?.getAttribute('data-tooltip-text') ?? false; + const dataAttribute = target?.getAttribute('data-tooltip-text') ?? false; if (!isTriggerElement) { - tooltip.closeTooltip(); - } else if (dataAttribute !== tooltip.text) { - tooltip.closeTooltip(); + closeTooltip(); + } else if (dataAttribute !== text) { + closeTooltip(); } }; return ( <> - {tooltip.open === 1 && ( + {open === 1 && ( <Wrapper className="tooltip-trigger-element" - ref={ref} + ref={tooltipRef} style={{ position: 'absolute', left: `${position[0]}px`, top: `${position[1]}px`, zIndex: 99, - opacity: tooltip.show === 1 ? 1 : 0, + opacity: show === 1 ? 1 : 0, }} > - <h3 className="tooltip-trigger-element">{tooltip.text}</h3> + <h3 className="tooltip-trigger-element">{text}</h3> </Wrapper> )} </> ); }; - -export default Tooltip; diff --git a/src/library/UpdateHeader/Wrapper.ts b/src/library/UpdateHeader/Wrapper.ts new file mode 100644 index 0000000000..237ef47cbd --- /dev/null +++ b/src/library/UpdateHeader/Wrapper.ts @@ -0,0 +1,29 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import styled from 'styled-components'; + +export const Wrapper = styled.div` + flex: 1; + display: flex; + align-items: center; + margin-bottom: 1rem; + + > span { + color: var(--text-color-secondary); + margin: 0 0.75rem; + opacity: 0.5; + } + + /* input element of dropdown */ + > div { + border-bottom: 1px solid var(--border-primary-color); + display: flex; + justify-content: center; + flex: 1; + + > h4 { + padding: 0.5rem 1rem; + } + } +`; diff --git a/src/library/UpdateHeader/index.tsx b/src/library/UpdateHeader/index.tsx new file mode 100644 index 0000000000..3dbf180cad --- /dev/null +++ b/src/library/UpdateHeader/index.tsx @@ -0,0 +1,36 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { faAnglesRight } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { usePayeeConfig } from 'library/Hooks/usePayeeConfig'; +import { Wrapper } from './Wrapper'; + +interface UpdateHeaderProps { + current: string | null; + selected: string | null; +} + +export const UpdateHeader = ({ current, selected }: UpdateHeaderProps) => { + const { getPayeeItems } = usePayeeConfig(); + + const currentTitle = + getPayeeItems(true).find((p) => p.value === current)?.title || ''; + + const selectedTitle = + getPayeeItems(true).find((p) => p.value === selected)?.title || ''; + + return ( + <Wrapper> + <div> + <h4>{currentTitle}</h4> + </div> + <span> + <FontAwesomeIcon icon={faAnglesRight} /> + </span> + <div> + <h4>{selectedTitle}</h4> + </div> + </Wrapper> + ); +}; diff --git a/src/library/ValidatorList/FilterValidators.tsx b/src/library/ValidatorList/FilterValidators.tsx index d2ac8a716e..00cce3ab52 100644 --- a/src/library/ValidatorList/FilterValidators.tsx +++ b/src/library/ValidatorList/FilterValidators.tsx @@ -1,33 +1,34 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only import { faCheckCircle, faCircle } from '@fortawesome/free-regular-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { useTranslation } from 'react-i18next'; +import { Title } from 'library/Prompt/Title'; +import { FilterListButton, FilterListWrapper } from 'library/Prompt/Wrappers'; import { useFilters } from 'contexts/Filters'; -import { FilterType } from 'contexts/Filters/types'; -import { Title } from 'library/Overlay/Title'; -import { FilterListButton, FilterListWrapper } from 'library/Overlay/Wrappers'; import { useValidatorFilters } from '../Hooks/useValidatorFilters'; export const FilterValidators = () => { + const { t } = useTranslation('library'); const { getFilters, toggleFilter } = useFilters(); const { excludesToLabels, includesToLabels } = useValidatorFilters(); - const includes = getFilters(FilterType.Include, 'validators'); - const excludes = getFilters(FilterType.Exclude, 'validators'); + const includes = getFilters('include', 'validators'); + const excludes = getFilters('exclude', 'validators'); return ( <FilterListWrapper> - <Title title="Filter Validators" /> + <Title title={t('filterValidators')} /> <div className="body"> - <h4>Include:</h4> - {Object.entries(includesToLabels).map(([f, l]: any, i: number) => ( + <h4>{t('include')}:</h4> + {Object.entries(includesToLabels).map(([f, l]: any, i) => ( <FilterListButton - active={includes?.includes(f) ?? false} + $active={includes?.includes(f) ?? false} key={`validator_include_${i}`} type="button" onClick={() => { - toggleFilter(FilterType.Include, 'validators', f); + toggleFilter('include', 'validators', f); }} > <FontAwesomeIcon @@ -38,14 +39,14 @@ export const FilterValidators = () => { </FilterListButton> ))} - <h4>Exclude:</h4> - {Object.entries(excludesToLabels).map(([f, l]: any, i: number) => ( + <h4>{t('exclude')}:</h4> + {Object.entries(excludesToLabels).map(([f, l]: any, i) => ( <FilterListButton - active={excludes?.includes(f) ?? false} + $active={excludes?.includes(f) ?? false} key={`validator_exclude_${i}`} type="button" onClick={() => { - toggleFilter(FilterType.Exclude, 'validators', f); + toggleFilter('exclude', 'validators', f); }} > <FontAwesomeIcon diff --git a/src/library/ValidatorList/Filters.tsx b/src/library/ValidatorList/Filters.tsx deleted file mode 100644 index c4d90cd76f..0000000000 --- a/src/library/ValidatorList/Filters.tsx +++ /dev/null @@ -1,105 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { - faArrowDownWideShort, - faBan, - faCheck, - faFilterCircleXmark, -} from '@fortawesome/free-solid-svg-icons'; -import { - ButtonInvertRounded, - ButtonSecondary, -} from '@rossbulat/polkadot-dashboard-ui'; -import { useFilters } from 'contexts/Filters'; -import { FilterType } from 'contexts/Filters/types'; -import { useOverlay } from 'contexts/Overlay'; -import { Container } from 'library/Filter/Container'; -import { Item } from 'library/Filter/Item'; -import { useEffect } from 'react'; -import { useValidatorFilters } from '../Hooks/useValidatorFilters'; -import { FilterValidators } from './FilterValidators'; -import { OrderValidators } from './OrderValidators'; - -export const Filters = () => { - const { openOverlayWith } = useOverlay(); - const { resetFilters, getFilters, getOrder, toggleFilter } = useFilters(); - const { includesToLabels, excludesToLabels, ordersToLabels } = - useValidatorFilters(); - - const includes = getFilters(FilterType.Include, 'validators'); - const excludes = getFilters(FilterType.Exclude, 'validators'); - const hasFilters = includes?.length || excludes?.length; - const order = getOrder('validators'); - - // scroll to top of the window on every filter. - useEffect(() => { - window.scrollTo(0, 0); - }, [includes, excludes]); - - return ( - <> - <div style={{ marginBottom: '1.1rem' }}> - <ButtonInvertRounded - text="Order" - marginRight - iconLeft={faArrowDownWideShort} - onClick={() => { - openOverlayWith(<OrderValidators />); - }} - /> - <ButtonInvertRounded - text="Filter" - marginRight - iconLeft={faFilterCircleXmark} - onClick={() => { - openOverlayWith(<FilterValidators />); - }} - /> - <ButtonSecondary - text="Clear" - onClick={() => { - resetFilters(FilterType.Include, 'validators'); - resetFilters(FilterType.Exclude, 'validators'); - }} - disabled={!hasFilters} - /> - </div> - <Container> - <div className="items"> - <Item - label={ - order === 'default' - ? 'Unordered' - : `Order: ${ordersToLabels[order]}` - } - disabled - /> - {!hasFilters && <Item label="No filters" disabled />} - {includes?.map((e: string, i: number) => ( - <Item - key={`validator_include_${i}`} - label={includesToLabels[e]} - icon={faCheck} - transform="grow-2" - onClick={() => { - toggleFilter(FilterType.Include, 'validators', e); - }} - /> - ))} - {excludes?.map((e: string, i: number) => ( - <Item - key={`validator_exclude_${i}`} - label={excludesToLabels[e]} - icon={faBan} - transform="grow-0" - onClick={() => { - toggleFilter(FilterType.Exclude, 'validators', e); - }} - /> - ))} - </div> - </Container> - </> - ); -}; diff --git a/src/library/ValidatorList/Filters/FilterBadges.tsx b/src/library/ValidatorList/Filters/FilterBadges.tsx new file mode 100644 index 0000000000..9d28aa20dc --- /dev/null +++ b/src/library/ValidatorList/Filters/FilterBadges.tsx @@ -0,0 +1,64 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { faBan, faCheck } from '@fortawesome/free-solid-svg-icons'; +import { useEffect } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useFilters } from 'contexts/Filters'; +import { Container } from 'library/Filter/Container'; +import { Item } from 'library/Filter/Item'; +import { useValidatorFilters } from 'library/Hooks/useValidatorFilters'; + +export const FilterBadges = () => { + const { t } = useTranslation('library'); + const { getFilters, getOrder, toggleFilter } = useFilters(); + const { includesToLabels, excludesToLabels, ordersToLabels } = + useValidatorFilters(); + + const includes = getFilters('include', 'validators'); + const excludes = getFilters('exclude', 'validators'); + const hasFilters = includes?.length || excludes?.length; + const order = getOrder('validators'); + + // scroll to top of the window on every filter. + useEffect(() => { + window.scrollTo(0, 0); + }, [includes, excludes]); + + return ( + <Container> + <div className="items"> + <Item + label={ + order === 'default' + ? `${t('unordered')}` + : `${t('order')}: ${ordersToLabels[order]}` + } + disabled + /> + {!hasFilters && <Item label={t('noFilters')} disabled />} + {includes?.map((e, i) => ( + <Item + key={`validator_include_${i}`} + label={includesToLabels[e]} + icon={faCheck} + onClick={() => { + toggleFilter('include', 'validators', e); + }} + /> + ))} + {excludes?.map((e, i) => ( + <Item + key={`validator_exclude_${i}`} + label={excludesToLabels[e]} + icon={faBan} + transform="shrink-2" + onClick={() => { + toggleFilter('exclude', 'validators', e); + }} + /> + ))} + </div> + </Container> + ); +}; diff --git a/src/library/ValidatorList/Filters/FilterHeaders.tsx b/src/library/ValidatorList/Filters/FilterHeaders.tsx new file mode 100644 index 0000000000..eb72f7173e --- /dev/null +++ b/src/library/ValidatorList/Filters/FilterHeaders.tsx @@ -0,0 +1,57 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { + faArrowDownWideShort, + faFilterCircleXmark, +} from '@fortawesome/free-solid-svg-icons'; +import { ButtonPrimaryInvert, ButtonSecondary } from '@polkadot-cloud/react'; +import { useTranslation } from 'react-i18next'; +import { useFilters } from 'contexts/Filters'; +import { usePrompt } from 'contexts/Prompt'; +import { OrderValidators } from '../OrderValidators'; +import { FilterValidators } from '../FilterValidators'; + +export const FilterHeaders = () => { + const { t } = useTranslation('library'); + const { openPromptWith } = usePrompt(); + const { resetFilters, getFilters } = useFilters(); + + const includes = getFilters('include', 'validators'); + const excludes = getFilters('exclude', 'validators'); + const hasFilters = includes?.length || excludes?.length; + + return ( + <div + style={{ + display: 'flex', + marginBottom: '1.1rem', + }} + > + <ButtonPrimaryInvert + text={t('order')} + marginRight + iconLeft={faArrowDownWideShort} + onClick={() => { + openPromptWith(<OrderValidators />); + }} + /> + <ButtonPrimaryInvert + text={t('filter')} + marginRight + iconLeft={faFilterCircleXmark} + onClick={() => { + openPromptWith(<FilterValidators />); + }} + /> + <ButtonSecondary + text={t('clear')} + onClick={() => { + resetFilters('include', 'validators'); + resetFilters('exclude', 'validators'); + }} + disabled={!hasFilters} + /> + </div> + ); +}; diff --git a/src/library/ValidatorList/OrderValidators.tsx b/src/library/ValidatorList/OrderValidators.tsx index 8e6a6d1dab..b4e9175a20 100644 --- a/src/library/ValidatorList/OrderValidators.tsx +++ b/src/library/ValidatorList/OrderValidators.tsx @@ -1,14 +1,16 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only import { faCheckCircle, faCircle } from '@fortawesome/free-regular-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { useTranslation } from 'react-i18next'; +import { Title } from 'library/Prompt/Title'; +import { FilterListButton, FilterListWrapper } from 'library/Prompt/Wrappers'; import { useFilters } from 'contexts/Filters'; -import { Title } from 'library/Overlay/Title'; -import { FilterListButton, FilterListWrapper } from 'library/Overlay/Wrappers'; import { useValidatorFilters } from '../Hooks/useValidatorFilters'; export const OrderValidators = () => { + const { t } = useTranslation('library'); const { getOrder, setOrder } = useFilters(); const { ordersToLabels } = useValidatorFilters(); @@ -16,16 +18,14 @@ export const OrderValidators = () => { return ( <FilterListWrapper> - <Title title="Order Validators" /> + <Title title={t('orderValidators')} /> <div className="body"> {Object.entries(ordersToLabels).map(([o, l]: any, i: number) => ( <FilterListButton - active={order === o ?? false} + $active={order === o ?? false} key={`validator_filter_${i}`} type="button" - onClick={() => { - setOrder('validators', o); - }} + onClick={() => setOrder('validators', o)} > <FontAwesomeIcon transform="grow-5" diff --git a/src/library/ValidatorList/Validator/Default.tsx b/src/library/ValidatorList/Validator/Default.tsx deleted file mode 100644 index 1b4aa500c8..0000000000 --- a/src/library/ValidatorList/Validator/Default.tsx +++ /dev/null @@ -1,155 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { IconProp } from '@fortawesome/fontawesome-svg-core'; -import { faCopy } from '@fortawesome/free-regular-svg-icons'; -import { faBars, faChartLine } from '@fortawesome/free-solid-svg-icons'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { useMenu } from 'contexts/Menu'; -import { useModal } from 'contexts/Modal'; -import { useNotifications } from 'contexts/Notifications'; -import { NotificationText } from 'contexts/Notifications/types'; -import CopyAddress from 'library/ListItem/Labels/CopyAddress'; -import { ParaValidator } from 'library/ListItem/Labels/ParaValidator'; -import { - Labels, - MenuPosition, - Separator, - Wrapper, -} from 'library/ListItem/Wrappers'; -import { useRef } from 'react'; -import { useValidators } from '../../../contexts/Validators'; -import { useList } from '../../List/context'; -import { Blocked } from '../../ListItem/Labels/Blocked'; -import { Commission } from '../../ListItem/Labels/Commission'; -import { EraStatus } from '../../ListItem/Labels/EraStatus'; -import { FavoriteValidator } from '../../ListItem/Labels/FavoriteValidator'; -import { Identity } from '../../ListItem/Labels/Identity'; -import { Oversubscribed } from '../../ListItem/Labels/Oversubscribed'; -import { Select } from '../../ListItem/Labels/Select'; -import { DefaultProps } from './types'; -import { getIdentityDisplay } from './Utils'; - -export const Default = (props: DefaultProps) => { - const { - validator, - toggleFavorites, - batchIndex, - batchKey, - showMenu, - inModal, - } = props; - - const { openModalWith } = useModal(); - const { addNotification } = useNotifications(); - const { setMenuPosition, setMenuItems, open }: any = useMenu(); - const { meta } = useValidators(); - const { selectActive } = useList(); - - const identities = meta[batchKey]?.identities ?? []; - const supers = meta[batchKey]?.supers ?? []; - - const { address, prefs } = validator; - const commission = prefs?.commission ?? null; - - const identity = getIdentityDisplay( - identities[batchIndex], - supers[batchIndex] - ); - - // copy address notification - const notificationCopyAddress: NotificationText | null = - address == null - ? null - : { - title: 'Address Copied to Clipboard', - subtitle: address, - }; - - // configure floating menu - const posRef = useRef(null); - const menuItems = [ - { - icon: <FontAwesomeIcon icon={faChartLine as IconProp} />, - wrap: null, - title: `View Metrics`, - cb: () => { - openModalWith( - 'ValidatorMetrics', - { - address, - identity, - }, - 'large' - ); - }, - }, - { - icon: <FontAwesomeIcon icon={faCopy as IconProp} />, - wrap: null, - title: `Copy Address`, - cb: () => { - navigator.clipboard.writeText(address); - if (notificationCopyAddress) { - addNotification(notificationCopyAddress); - } - }, - }, - ]; - - const toggleMenu = () => { - if (!open) { - setMenuItems(menuItems); - setMenuPosition(posRef); - } - }; - - return ( - <Wrapper format="nomination" inModal={inModal}> - <div className="inner"> - <MenuPosition ref={posRef} /> - <div className="row"> - {selectActive && <Select item={validator} />} - <Identity - meta={meta} - address={address} - batchIndex={batchIndex} - batchKey={batchKey} - /> - <div> - <Labels> - <Oversubscribed batchIndex={batchIndex} batchKey={batchKey} /> - <Blocked prefs={prefs} /> - <Commission commission={commission} /> - <ParaValidator address={address} /> - - {toggleFavorites && <FavoriteValidator address={address} />} - {showMenu && ( - <button - type="button" - className="label" - onClick={() => toggleMenu()} - > - <FontAwesomeIcon icon={faBars} /> - </button> - )} - </Labels> - </div> - </div> - <Separator /> - <div className="row status"> - <EraStatus address={address} /> - {inModal && ( - <> - <Labels> - <CopyAddress validator={validator} /> - </Labels> - </> - )} - </div> - </div> - </Wrapper> - ); -}; - -export default Default; diff --git a/src/library/ValidatorList/Validator/Nomination.tsx b/src/library/ValidatorList/Validator/Nomination.tsx deleted file mode 100644 index 214a4488c3..0000000000 --- a/src/library/ValidatorList/Validator/Nomination.tsx +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { useValidators } from 'contexts/Validators'; -import { ParaValidator } from 'library/ListItem/Labels/ParaValidator'; -import { Labels, Separator, Wrapper } from 'library/ListItem/Wrappers'; -import { useList } from '../../List/context'; -import { Blocked } from '../../ListItem/Labels/Blocked'; -import { Commission } from '../../ListItem/Labels/Commission'; -import { CopyAddress } from '../../ListItem/Labels/CopyAddress'; -import { FavoriteValidator } from '../../ListItem/Labels/FavoriteValidator'; -import { Identity } from '../../ListItem/Labels/Identity'; -import { Metrics } from '../../ListItem/Labels/Metrics'; -import { NominationStatus } from '../../ListItem/Labels/NominationStatus'; -import { Oversubscribed } from '../../ListItem/Labels/Oversubscribed'; -import { Select } from '../../ListItem/Labels/Select'; -import { NominationProps } from './types'; -import { getIdentityDisplay } from './Utils'; - -export const Nomination = (props: NominationProps) => { - const { meta } = useValidators(); - const { selectActive } = useList(); - - const { - validator, - nominator, - toggleFavorites, - batchIndex, - batchKey, - bondType, - inModal, - } = props; - - const identities = meta[batchKey]?.identities ?? []; - const supers = meta[batchKey]?.supers ?? []; - - const { address, prefs } = validator; - const commission = prefs?.commission ?? null; - - return ( - <Wrapper format="nomination" inModal={inModal}> - <div className="inner"> - <div className="row"> - {selectActive && <Select item={validator} />} - <Identity - meta={meta} - address={address} - batchIndex={batchIndex} - batchKey={batchKey} - /> - <div> - <Labels> - <CopyAddress validator={validator} /> - {toggleFavorites && <FavoriteValidator address={address} />} - </Labels> - </div> - </div> - <Separator /> - <div className="row status"> - <NominationStatus - address={address} - bondType={bondType} - nominator={nominator} - /> - <Labels> - <Oversubscribed batchIndex={batchIndex} batchKey={batchKey} /> - <Blocked prefs={prefs} /> - <Commission commission={commission} /> - <ParaValidator address={address} /> - - {/* restrict opening another modal within a modal */} - {!inModal && ( - <Metrics - address={address} - display={getIdentityDisplay( - identities[batchIndex], - supers[batchIndex] - )} - /> - )} - </Labels> - </div> - </div> - </Wrapper> - ); -}; - -export default Nomination; diff --git a/src/library/ValidatorList/Validator/index.tsx b/src/library/ValidatorList/Validator/index.tsx deleted file mode 100644 index 2e329dcbc4..0000000000 --- a/src/library/ValidatorList/Validator/index.tsx +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import React from 'react'; -import { Default } from './Default'; -import { Nomination } from './Nomination'; - -export const ValidatorInner = (props: any) => { - const { format } = props; - - return format === 'nomination' ? ( - <Nomination {...props} /> - ) : ( - <Default {...props} /> - ); -}; - -export class Validator extends React.Component<any, any> { - shouldComponentUpdate(nextProps: any) { - return ( - this.props.validator.address !== nextProps.validator.address || - this.props.batchIndex !== nextProps.batchIndex || - this.props.batchKey !== nextProps.batchKey - ); - } - - render() { - return <ValidatorInner {...this.props} />; - } -} - -export default Validator; diff --git a/src/library/ValidatorList/Validator/types.ts b/src/library/ValidatorList/Validator/types.ts deleted file mode 100644 index 08250bae2d..0000000000 --- a/src/library/ValidatorList/Validator/types.ts +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { MaybeAccount } from 'types'; - -export interface NominationProps { - validator: any; - nominator: MaybeAccount; - toggleFavorites: boolean; - batchIndex: number; - batchKey: string; - bondType: string; - inModal: boolean; -} - -export interface DefaultProps { - validator: any; - toggleFavorites: boolean; - batchIndex: number; - batchKey: string; - showMenu: boolean; - inModal: boolean; -} diff --git a/src/library/ValidatorList/ValidatorItem/Default.tsx b/src/library/ValidatorList/ValidatorItem/Default.tsx new file mode 100644 index 0000000000..1c0b79f022 --- /dev/null +++ b/src/library/ValidatorList/ValidatorItem/Default.tsx @@ -0,0 +1,148 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { + faBars, + faChartLine, + faGlobe, +} from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { useRef } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useMenu } from 'contexts/Menu'; +import { CopyAddress } from 'library/ListItem/Labels/CopyAddress'; +import { ParaValidator } from 'library/ListItem/Labels/ParaValidator'; +import { + Labels, + MenuPosition, + Separator, + Wrapper, +} from 'library/ListItem/Wrappers'; +import { useOverlay } from '@polkadot-cloud/react/hooks'; +import { usePlugins } from 'contexts/Plugins'; +import type { AnyJson } from 'types'; +import { Quartile } from 'library/ListItem/Labels/Quartile'; +import { useValidators } from '../../../contexts/Validators/ValidatorEntries'; +import { useList } from '../../List/context'; +import { Blocked } from '../../ListItem/Labels/Blocked'; +import { Commission } from '../../ListItem/Labels/Commission'; +import { EraStatus } from '../../ListItem/Labels/EraStatus'; +import { FavoriteValidator } from '../../ListItem/Labels/FavoriteValidator'; +import { Identity } from '../../ListItem/Labels/Identity'; +import { Oversubscribed } from '../../ListItem/Labels/Oversubscribed'; +import { Select } from '../../ListItem/Labels/Select'; +import { getIdentityDisplay } from './Utils'; +import type { ValidatorItemProps } from './types'; +import { Pulse } from './Pulse'; + +export const Default = ({ + validator, + toggleFavorites, + showMenu, + displayFor, +}: ValidatorItemProps) => { + const { t } = useTranslation('library'); + const { selectActive } = useList(); + const { pluginEnabled } = usePlugins(); + const { openModal } = useOverlay().modal; + const { setMenuPosition, setMenuItems, open } = useMenu(); + const { validatorIdentities, validatorSupers } = useValidators(); + + const { address, prefs, validatorStatus, totalStake } = validator; + const commission = prefs?.commission ?? null; + + const identity = getIdentityDisplay( + validatorIdentities[address], + validatorSupers[address] + ); + + // configure floating menu + const posRef = useRef(null); + const menuItems: AnyJson[] = []; + menuItems.push({ + icon: <FontAwesomeIcon icon={faChartLine} transform="shrink-3" />, + wrap: null, + title: `${t('viewMetrics')}`, + cb: () => { + openModal({ + key: 'ValidatorMetrics', + options: { + address, + identity, + }, + }); + }, + }); + + if (pluginEnabled('polkawatch')) { + menuItems.push({ + icon: <FontAwesomeIcon icon={faGlobe} transform="shrink-3" />, + wrap: null, + title: `${t('viewDecentralization')}`, + cb: () => { + openModal({ + key: 'ValidatorGeo', + options: { + address, + identity, + }, + }); + }, + }); + } + + const toggleMenu = () => { + if (!open) { + setMenuItems(menuItems); + setMenuPosition(posRef); + } + }; + + return ( + <Wrapper> + <div className={`inner ${displayFor}`}> + <MenuPosition ref={posRef} /> + <div className="row top"> + {selectActive && <Select item={validator} />} + <Identity address={address} /> + <div> + <Labels className={displayFor}> + <CopyAddress address={address} /> + {toggleFavorites && <FavoriteValidator address={address} />} + + {/* restrict opening modal within a canvas */} + {displayFor === 'default' && showMenu && ( + <div className="label"> + <button type="button" onClick={() => toggleMenu()}> + <FontAwesomeIcon icon={faBars} transform="shrink-2" /> + </button> + </div> + )} + </Labels> + </div> + </div> + <Separator /> + <div className="row bottom lg"> + <div> + <Pulse address={address} displayFor={displayFor} /> + </div> + <div> + <Labels style={{ marginBottom: '0.9rem' }}> + <Quartile address={address} /> + <Oversubscribed address={address} /> + <Blocked prefs={prefs} /> + <Commission commission={commission} /> + <ParaValidator address={address} /> + </Labels> + <EraStatus + address={address} + status={validatorStatus} + totalStake={totalStake} + noMargin + /> + </div> + </div> + </div> + </Wrapper> + ); +}; diff --git a/src/library/ValidatorList/ValidatorItem/Nomination.tsx b/src/library/ValidatorList/ValidatorItem/Nomination.tsx new file mode 100644 index 0000000000..ec3bc94285 --- /dev/null +++ b/src/library/ValidatorList/ValidatorItem/Nomination.tsx @@ -0,0 +1,81 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { useValidators } from 'contexts/Validators/ValidatorEntries'; +import { ParaValidator } from 'library/ListItem/Labels/ParaValidator'; +import { Labels, Separator, Wrapper } from 'library/ListItem/Wrappers'; +import { Quartile } from 'library/ListItem/Labels/Quartile'; +import { useList } from '../../List/context'; +import { Blocked } from '../../ListItem/Labels/Blocked'; +import { Commission } from '../../ListItem/Labels/Commission'; +import { CopyAddress } from '../../ListItem/Labels/CopyAddress'; +import { FavoriteValidator } from '../../ListItem/Labels/FavoriteValidator'; +import { Identity } from '../../ListItem/Labels/Identity'; +import { Metrics } from '../../ListItem/Labels/Metrics'; +import { NominationStatus } from '../../ListItem/Labels/NominationStatus'; +import { Oversubscribed } from '../../ListItem/Labels/Oversubscribed'; +import { Select } from '../../ListItem/Labels/Select'; +import { getIdentityDisplay } from './Utils'; +import type { ValidatorItemProps } from './types'; +import { Pulse } from './Pulse'; + +export const Nomination = ({ + validator, + nominator, + toggleFavorites, + bondFor, + displayFor, + nominationStatus, +}: ValidatorItemProps) => { + const { selectActive } = useList(); + const { validatorIdentities, validatorSupers } = useValidators(); + + const { address, prefs } = validator; + const commission = prefs?.commission ?? null; + + return ( + <Wrapper> + <div className={`inner ${displayFor}`}> + <div className="row top"> + {selectActive && <Select item={validator} />} + <Identity address={address} /> + <div> + <Labels className={displayFor}> + <CopyAddress address={address} /> + {toggleFavorites && <FavoriteValidator address={address} />} + <Metrics + address={address} + display={getIdentityDisplay( + validatorIdentities[address], + validatorSupers[address] + )} + /> + </Labels> + </div> + </div> + <Separator /> + <div className="row bottom lg"> + <div> + <Pulse address={address} displayFor={displayFor} /> + </div> + <div> + <Labels style={{ marginBottom: '0.9rem' }}> + <Quartile address={address} /> + <Oversubscribed address={address} /> + <Blocked prefs={prefs} /> + <Commission commission={commission} /> + <ParaValidator address={address} /> + </Labels> + <NominationStatus + address={address} + bondFor={bondFor} + nominator={nominator} + status={nominationStatus} + noMargin + /> + </div> + </div> + </div> + </Wrapper> + ); +}; diff --git a/src/library/ValidatorList/ValidatorItem/Pulse.tsx b/src/library/ValidatorList/ValidatorItem/Pulse.tsx new file mode 100644 index 0000000000..0a09b5e18e --- /dev/null +++ b/src/library/ValidatorList/ValidatorItem/Pulse.tsx @@ -0,0 +1,179 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import BigNumber from 'bignumber.js'; +import { useNetworkMetrics } from 'contexts/NetworkMetrics'; +import { useValidators } from 'contexts/Validators/ValidatorEntries'; +import { Fragment } from 'react'; +import { + TooltipTrigger, + ValidatorPulseWrapper, +} from 'library/ListItem/Wrappers'; +import { useTooltip } from 'contexts/Tooltip'; +import { useTranslation } from 'react-i18next'; +import { MaxEraRewardPointsEras } from 'consts'; +import { useApi } from 'contexts/Api'; +import { normaliseEraPoints, prefillEraPoints } from './Utils'; +import type { PulseGraphProps, PulseProps } from './types'; + +export const Pulse = ({ address, displayFor }: PulseProps) => { + const { t } = useTranslation('library'); + const { isReady } = useApi(); + const { activeEra } = useNetworkMetrics(); + const { setTooltipTextAndOpen } = useTooltip(); + const { getValidatorPointsFromEras, eraPointsBoundaries, erasRewardPoints } = + useValidators(); + const startEra = activeEra.index.minus(1); + const eraRewardPoints = getValidatorPointsFromEras(startEra, address); + + const high = eraPointsBoundaries?.high || new BigNumber(1); + const normalisedPoints = normaliseEraPoints(eraRewardPoints, high); + const prefilledPoints = prefillEraPoints(Object.values(normalisedPoints)); + + const syncing = !isReady || !Object.values(erasRewardPoints).length; + const tooltipText = t('validatorPerformance', { + count: MaxEraRewardPointsEras, + }); + + return ( + <ValidatorPulseWrapper className={displayFor}> + {syncing && <div className="preload" />} + <TooltipTrigger + className="tooltip-trigger-element" + data-tooltip-text={tooltipText} + onMouseMove={() => setTooltipTextAndOpen(tooltipText)} + /> + <PulseGraph + points={prefilledPoints} + syncing={syncing} + displayFor={displayFor} + /> + </ValidatorPulseWrapper> + ); +}; + +export const PulseGraph = ({ + points: rawPoints = [], + syncing, + displayFor, +}: PulseGraphProps) => { + // Prefill with duplicate of start point. + let points = [rawPoints[0] || 0]; + points = points.concat(rawPoints); + // Prefill with duplicate of end point. + points.push(rawPoints[rawPoints.length - 1] || 0); + + const totalSegments = points.length - 2; + const vbWidth = 512; + const vbHeight = 115; + const xPadding = 5; + const yPadding = 10; + const xArea = vbWidth - 2 * xPadding; + const yArea = vbHeight - 2 * yPadding; + const xSegment = xArea / totalSegments; + let xCursor = xPadding; + + const pointsCoords = points.map((point: number, index: number) => { + const coord = { + x: xCursor, + y: vbHeight - yPadding - yArea * point, + zero: point === 0, + }; + + if (index === 0 || index === points.length - 2) { + xCursor += xSegment * 0.5; + } else { + xCursor += xSegment; + } + return coord; + }); + + const lineCoords = []; + for (let i = 0; i <= pointsCoords.length - 1; i++) { + const startZero = pointsCoords[i].zero; + const endZero = pointsCoords[i + 1]?.zero; + + lineCoords.push({ + x1: pointsCoords[i].x, + y1: pointsCoords[i].y, + x2: pointsCoords[i + 1]?.x || pointsCoords[i].x, + y2: pointsCoords[i + 1]?.y || pointsCoords[i].y, + zero: startZero && endZero, + }); + } + + return ( + <svg + width="100%" + height="100%" + viewBox={`0 0 ${vbWidth} ${vbHeight}`} + version="1.1" + xmlns="http://www.w3.org/2000/svg" + > + {lineCoords.map(({ x1 }, index) => { + if (index === 0 || index === lineCoords.length - 1) { + return <Fragment key={`grid_y_coord_${index}`} />; + } + return ( + <line + key={`grid_coord_${index}`} + strokeWidth="3.75" + stroke={ + displayFor === 'canvas' + ? 'var(--grid-color-secondary)' + : 'var(--grid-color-primary)' + } + x1={x1} + y1={0} + x2={x1} + y2={vbHeight} + /> + ); + })} + + {!syncing && + [{ y1: vbHeight * 0.5, y2: vbHeight * 0.5 }].map( + ({ y1, y2 }, index) => { + return ( + <line + key={`grid_coord_${index}`} + strokeWidth="3.75" + stroke={ + displayFor === 'canvas' + ? 'var(--grid-color-secondary)' + : 'var(--grid-color-primary)' + } + x1={0} + y1={y1} + x2={vbWidth} + y2={y2} + opacity={0.5} + /> + ); + } + )} + + {!syncing && + lineCoords.map(({ x1, y1, x2, y2, zero }, index) => { + const startOrEnd = index === 0 || index === lineCoords.length - 2; + const opacity = startOrEnd ? 0.25 : zero ? 0.5 : 1; + return ( + <line + key={`line_coord_${index}`} + strokeWidth={5} + opacity={opacity} + stroke={ + zero + ? 'var(--text-color-tertiary)' + : 'var(--accent-color-primary)' + } + x1={x1} + y1={y1} + x2={x2} + y2={y2} + /> + ); + })} + </svg> + ); +}; diff --git a/src/library/ValidatorList/Validator/Utils.tsx b/src/library/ValidatorList/ValidatorItem/Utils.tsx similarity index 60% rename from src/library/ValidatorList/Validator/Utils.tsx rename to src/library/ValidatorList/ValidatorItem/Utils.tsx index 92e5eaad9a..aae21dab1c 100644 --- a/src/library/ValidatorList/Validator/Utils.tsx +++ b/src/library/ValidatorList/ValidatorItem/Utils.tsx @@ -1,7 +1,9 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only import { u8aToString, u8aUnwrapBytes } from '@polkadot/util'; +import type BigNumber from 'bignumber.js'; +import { MaxEraRewardPointsEras } from 'consts'; export const getIdentityDisplay = (_identity: any, _superIdentity: any) => { let displayFinal = ''; @@ -9,7 +11,8 @@ export const getIdentityDisplay = (_identity: any, _superIdentity: any) => { // check super identity exists, get display.Raw if it does const superIdentity = _superIdentity?.identity ?? null; - const superRaw = _superIdentity?.[1]?.Raw ?? null; + const superRaw = _superIdentity?.superOf?.[1]?.Raw ?? null; + const superDisplay = superIdentity?.info?.display?.Raw ?? null; // check if super raw has been encoded @@ -54,3 +57,27 @@ export const getIdentityDisplay = (_identity: any, _superIdentity: any) => { </> ); }; + +// Normalise era points between 0 and 1 relative to the highest recorded value. +export const normaliseEraPoints = ( + eraPoints: Record<string, BigNumber>, + high: BigNumber +): Record<string, number> => { + const percentile = high.dividedBy(100); + + return Object.fromEntries( + Object.entries(eraPoints).map(([era, points]) => [ + era, + points.dividedBy(percentile).multipliedBy(0.01).toNumber(), + ]) + ); +}; + +// Prefill low values where no points are recorded. +export const prefillEraPoints = (eraPoints: number[]): number[] => { + const missing = Math.max(MaxEraRewardPointsEras - eraPoints.length, 0); + + if (!missing) return eraPoints; + + return Array(missing).fill(0).concat(eraPoints); +}; diff --git a/src/library/ValidatorList/ValidatorItem/index.tsx b/src/library/ValidatorList/ValidatorItem/index.tsx new file mode 100644 index 0000000000..69568262dc --- /dev/null +++ b/src/library/ValidatorList/ValidatorItem/index.tsx @@ -0,0 +1,27 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import React from 'react'; +import { Default } from './Default'; +import { Nomination } from './Nomination'; +import type { ValidatorItemProps } from './types'; + +export const ValidatorItemInner = (props: ValidatorItemProps) => { + const { format } = props; + + return format === 'nomination' ? ( + <Nomination {...props} /> + ) : ( + <Default {...props} /> + ); +}; + +export class ValidatorItem extends React.Component<ValidatorItemProps> { + shouldComponentUpdate(nextProps: ValidatorItemProps) { + return this.props.validator.address !== nextProps.validator.address; + } + + render() { + return <ValidatorItemInner {...this.props} />; + } +} diff --git a/src/library/ValidatorList/ValidatorItem/types.ts b/src/library/ValidatorList/ValidatorItem/types.ts new file mode 100644 index 0000000000..e84d9e696c --- /dev/null +++ b/src/library/ValidatorList/ValidatorItem/types.ts @@ -0,0 +1,29 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import type { MaybeAddress } from '@polkadot-cloud/react/types'; +import type { ValidatorListEntry } from 'contexts/Validators/types'; +import type { BondFor, DisplayFor } from 'types'; + +export interface ValidatorItemProps { + validator: ValidatorListEntry; + bondFor: BondFor; + displayFor: DisplayFor; + nominator: MaybeAddress; + format?: string; + showMenu?: boolean; + toggleFavorites?: boolean; + nominationStatus?: NominationStatus; +} + +export interface PulseProps { + address: string; + displayFor: DisplayFor; +} +export interface PulseGraphProps { + points: number[]; + syncing: boolean; + displayFor: DisplayFor; +} + +export type NominationStatus = 'active' | 'inactive' | 'waiting'; diff --git a/src/library/ValidatorList/index.tsx b/src/library/ValidatorList/index.tsx index 4d844b6c88..ab464a887f 100644 --- a/src/library/ValidatorList/index.tsx +++ b/src/library/ValidatorList/index.tsx @@ -1,230 +1,206 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only import { faBars, faGripVertical } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { isNotZero } from '@polkadot-cloud/utils'; +import { motion } from 'framer-motion'; +import React, { useEffect, useRef, useState } from 'react'; +import { useTranslation } from 'react-i18next'; import { ListItemsPerBatch, ListItemsPerPage } from 'consts'; import { useApi } from 'contexts/Api'; -import { useConnect } from 'contexts/Connect'; import { useFilters } from 'contexts/Filters'; -import { FilterType } from 'contexts/Filters/types'; -import { useModal } from 'contexts/Modal'; -import { useNetworkMetrics } from 'contexts/Network'; -import { StakingContext } from 'contexts/Staking'; +import { useNetworkMetrics } from 'contexts/NetworkMetrics'; import { useTheme } from 'contexts/Themes'; import { useUi } from 'contexts/UI'; -import { useValidators } from 'contexts/Validators'; -import { motion } from 'framer-motion'; -import { Header, List, Wrapper as ListWrapper } from 'library/List'; +import { + FilterHeaderWrapper, + List, + Wrapper as ListWrapper, +} from 'library/List'; import { MotionContainer } from 'library/List/MotionContainer'; import { Pagination } from 'library/List/Pagination'; import { SearchInput } from 'library/List/SearchInput'; import { Selectable } from 'library/List/Selectable'; -import { Validator } from 'library/ValidatorList/Validator'; -import React, { useEffect, useRef, useState } from 'react'; -import { networkColors } from 'theme/default'; +import { ValidatorItem } from 'library/ValidatorList/ValidatorItem'; +import type { Validator } from 'contexts/Validators/types'; +import { useOverlay } from '@polkadot-cloud/react/hooks'; +import { useNetwork } from 'contexts/Network'; +import { useActiveAccounts } from 'contexts/ActiveAccounts'; +import { useValidators } from 'contexts/Validators/ValidatorEntries'; +import { useNominationStatus } from 'library/Hooks/useNominationStatus'; +import { useBondedPools } from 'contexts/Pools/BondedPools'; import { useValidatorFilters } from '../Hooks/useValidatorFilters'; import { ListProvider, useList } from '../List/context'; -import { Filters } from './Filters'; - -export const ValidatorListInner = (props: any) => { - const { mode } = useTheme(); - const { isReady, network } = useApi(); - const { activeAccount } = useConnect(); - const { metrics } = useNetworkMetrics(); - const { fetchValidatorMetaBatch } = useValidators(); - const provider = useList(); - const modal = useModal(); - const { isSyncing } = useUi(); - - // determine the nominator of the validator list. - // By default this will be the activeAccount. But for pools, - // the pool stash address should be the nominator. - const nominator = props.nominator ?? activeAccount; - - const { selected, listFormat, setListFormat } = provider; - +import type { ValidatorListProps } from './types'; +import { FilterHeaders } from './Filters/FilterHeaders'; +import { FilterBadges } from './Filters/FilterBadges'; +import type { NominationStatus } from './ValidatorItem/types'; + +export const ValidatorListInner = ({ + nominator: initialNominator, + validators: initialValidators, + allowMoreCols, + allowFilters, + toggleFavorites, + pagination, + format, + selectable, + bondFor, + onSelected, + actions = [], + showMenu = true, + displayFor = 'default', + allowSearch = false, + allowListFormat = true, + alwaysRefetchValidators = false, + defaultOrder = undefined, + defaultFilters = undefined, + disableThrottle = false, +}: ValidatorListProps) => { + const { t } = useTranslation('library'); + const { + networkData: { colors }, + } = useNetwork(); const { getFilters, setMultiFilters, getOrder, + setOrder, getSearchTerm, setSearchTerm, resetFilters, resetOrder, clearSearchTerm, } = useFilters(); + const { mode } = useTheme(); + const { isReady } = useApi(); + const { isSyncing } = useUi(); + const listProvider = useList(); + const { activeEra } = useNetworkMetrics(); + const { activeAccount } = useActiveAccounts(); + const { setModalResize } = useOverlay().modal; + const { injectValidatorListData } = useValidators(); + const { getNomineesStatus } = useNominationStatus(); + const { getPoolNominationStatus } = useBondedPools(); const { applyFilter, applyOrder, applySearch } = useValidatorFilters(); - const includes = getFilters(FilterType.Include, 'validators'); - const excludes = getFilters(FilterType.Exclude, 'validators'); + + const { selected, listFormat, setListFormat } = listProvider; + const includes = getFilters('include', 'validators'); + const excludes = getFilters('exclude', 'validators'); const order = getOrder('validators'); const searchTerm = getSearchTerm('validators'); - - const { - batchKey, - allowMoreCols, - allowFilters, - toggleFavorites, - pagination, - title, - format, - selectable, - bondType, - }: any = props; - - const actions = props.actions ?? []; - const showMenu = props.showMenu ?? true; - const inModal = props.inModal ?? false; - const allowSearch = props.allowSearch ?? false; - const allowListFormat = props.allowListFormat ?? true; - const alwaysRefetchValidators = props.alwaysRefetchValidators ?? false; - const defaultFilters = props.defaultFilters ?? undefined; - const actionsAll = [...actions].filter((action) => !action.onSelected); - const actionsSelected = [...actions].filter( - (action: any) => action.onSelected - ); + const actionsSelected = [...actions].filter((action) => action.onSelected); + + // Determine the nominator of the validator list. Fallback to activeAccount if not provided. + const nominator = initialNominator || activeAccount; + + // Store the current nomination status of validator records relative to the supplied nominator. + const nominationStatus = useRef<Record<string, NominationStatus>>({}); + + // Get nomination status relative to supplied nominator, if `format` is `nomination`. + const processNominationStatus = () => { + if (format === 'nomination') + if (bondFor === 'pool') { + nominationStatus.current = Object.fromEntries( + initialValidators.map(({ address }) => [ + address, + getPoolNominationStatus(nominator, address), + ]) + ); + } else { + // get all active account's nominations. + const nominationStatuses = getNomineesStatus(nominator, 'nominator'); + + // find the nominator status within the returned nominations. + nominationStatus.current = Object.fromEntries( + initialValidators.map(({ address }) => [ + address, + nominationStatuses[address], + ]) + ); + } + }; - const disableThrottle = props.disableThrottle ?? false; - const refetchOnListUpdate = - props.refetchOnListUpdate !== undefined ? props.refetchOnListUpdate : false; + // Injects status into supplied initial validators. + const prepareInitialValidators = () => { + processNominationStatus(); + const statusToIndex = { + active: 2, + inactive: 1, + waiting: 0, + }; + return injectValidatorListData(initialValidators).sort( + (a, b) => + statusToIndex[nominationStatus.current[b.address]] - + statusToIndex[nominationStatus.current[a.address]] + ); + }; - // current page + // Current page. const [page, setPage] = useState<number>(1); - // current render iteration - const [renderIteration, _setRenderIteration] = useState<number>(1); - - // default list of validators - const [validatorsDefault, setValidatorsDefault] = useState(props.validators); + // Default list of validators. + const [validatorsDefault, setValidatorsDefault] = useState( + prepareInitialValidators() + ); - // manipulated list (ordering, filtering) of validators - const [validators, setValidators]: any = useState(props.validators); + // Manipulated list (custom ordering, filtering) of validators. + const [validators, setValidators] = useState(prepareInitialValidators()); - // is this the initial fetch + // Store whether the validator list has been fetched initially. const [fetched, setFetched] = useState(false); - // store whether the search bar is being used + // Store whether the search bar is being used. const [isSearching, setIsSearching] = useState(false); - // render throttle iteration + // Current render iteration. + const [renderIteration, setRenderIterationState] = useState<number>(1); + + // Render throttle iteration. const renderIterationRef = useRef(renderIteration); const setRenderIteration = (iter: number) => { renderIterationRef.current = iter; - _setRenderIteration(iter); + setRenderIterationState(iter); }; - // pagination + // Pagination. const totalPages = Math.ceil(validators.length / ListItemsPerPage); const pageEnd = page * ListItemsPerPage - 1; const pageStart = pageEnd - (ListItemsPerPage - 1); - // render batch - const batchEnd = renderIteration * ListItemsPerBatch - 1; + // Render batch. + const batchEnd = Math.min( + renderIteration * ListItemsPerBatch - 1, + ListItemsPerPage + ); - // reset list when validator list changes + // Reset list when validator list changes. useEffect(() => { if (alwaysRefetchValidators) { if ( - JSON.stringify(props.validators) !== JSON.stringify(validatorsDefault) + JSON.stringify(initialValidators.map((v) => v.address)) !== + JSON.stringify(validatorsDefault.map((v) => v.address)) ) { setFetched(false); } } else { setFetched(false); } - }, [props.validators, nominator]); - - // set default filters - useEffect(() => { - if (allowFilters) { - if (defaultFilters?.includes?.length) { - setMultiFilters( - FilterType.Include, - 'validators', - defaultFilters?.includes - ); - } - if (defaultFilters?.excludes?.length) { - setMultiFilters( - FilterType.Exclude, - 'validators', - defaultFilters?.excludes - ); - } - - return () => { - resetFilters(FilterType.Exclude, 'validators'); - resetFilters(FilterType.Include, 'validators'); - resetOrder('validators'); - clearSearchTerm('validators'); - }; - } - }, []); - - // configure validator list when network is ready to fetch - useEffect(() => { - if (isReady && metrics.activeEra.index !== 0 && !fetched) { - setupValidatorList(); - } - }, [isReady, metrics.activeEra.index, fetched]); - - // render throttle - useEffect(() => { - if (!(batchEnd >= pageEnd || disableThrottle)) { - setTimeout(() => { - setRenderIteration(renderIterationRef.current + 1); - }, 50); - } - }, [renderIterationRef.current]); - - // trigger onSelected when selection changes - useEffect(() => { - if (props.onSelected) { - props.onSelected(provider); - } - }, [selected]); - - // list ui changes / validator changes trigger re-render of list - useEffect(() => { - if (allowFilters && fetched) { - handleValidatorsFilterUpdate(); - } - }, [order, isSyncing, includes?.length, excludes?.length]); - - // handle modal resize on list format change - useEffect(() => { - maybeHandleModalResize(); - }, [listFormat, renderIteration, validators, page]); - - // handle validator list bootstrapping - const setupValidatorList = () => { - setValidatorsDefault(props.validators); - setValidators(props.validators); - setFetched(true); - fetchValidatorMetaBatch(batchKey, props.validators, refetchOnListUpdate); - }; + }, [initialValidators, nominator]); // handle filter / order update const handleValidatorsFilterUpdate = ( - filteredValidators: any = Object.assign(validatorsDefault) + filteredValidators = Object.assign(validatorsDefault) ) => { if (allowFilters) { if (order !== 'default') { filteredValidators = applyOrder(order, filteredValidators); } - filteredValidators = applyFilter( - includes, - excludes, - filteredValidators, - batchKey - ); + filteredValidators = applyFilter(includes, excludes, filteredValidators); if (searchTerm) { - filteredValidators = applySearch( - filteredValidators, - batchKey, - searchTerm - ); + filteredValidators = applySearch(filteredValidators, searchTerm); } setValidators(filteredValidators); setPage(1); @@ -232,149 +208,194 @@ export const ValidatorListInner = (props: any) => { } }; - // get validators to render - let listValidators = []; - // get throttled subset or entire list - if (!disableThrottle) { - listValidators = validators.slice(pageStart).slice(0, batchEnd); - } else { - listValidators = validators; - } + const listValidators = disableThrottle + ? validators + : validators.slice(pageStart).slice(0, ListItemsPerPage); // if in modal, handle resize const maybeHandleModalResize = () => { - if (!inModal) return; - modal.setResize(); + if (displayFor === 'modal') setModalResize(); }; const handleSearchChange = (e: React.FormEvent<HTMLInputElement>) => { const newValue = e.currentTarget.value; let filteredValidators = Object.assign(validatorsDefault); - filteredValidators = applyFilter( - includes, - excludes, - filteredValidators, - batchKey - ); - filteredValidators = applySearch(filteredValidators, batchKey, newValue); + if (order !== 'default') { + filteredValidators = applyOrder(order, filteredValidators); + } + filteredValidators = applyFilter(includes, excludes, filteredValidators); + filteredValidators = applySearch(filteredValidators, newValue); // ensure no duplicates filteredValidators = filteredValidators.filter( - (value: any, index: any, self: any) => - index === self.findIndex((t: any) => t.address === value.address) + (value: Validator, index: number, self: Validator[]) => + index === self.findIndex((i) => i.address === value.address) ); - handleValidatorsFilterUpdate(filteredValidators); + setValidators(filteredValidators); setPage(1); setIsSearching(e.currentTarget.value !== ''); setRenderIteration(1); setSearchTerm('validators', newValue); }; + // Set default filters. + useEffect(() => { + if (allowFilters) { + if (defaultFilters?.includes?.length) { + setMultiFilters( + 'include', + 'validators', + defaultFilters?.includes, + false + ); + } + if (defaultFilters?.excludes?.length) { + setMultiFilters( + 'exclude', + 'validators', + defaultFilters?.excludes, + false + ); + } + + if (defaultOrder) { + setOrder('validators', defaultOrder); + } + } + return () => { + if (allowFilters) { + resetFilters('exclude', 'validators'); + resetFilters('include', 'validators'); + resetOrder('validators'); + clearSearchTerm('validators'); + } + }; + }, []); + + // Handle validator list bootstrapping. + const setupValidatorList = () => { + setValidatorsDefault(prepareInitialValidators()); + setValidators(prepareInitialValidators()); + setFetched(true); + }; + + // Configure validator list when network is ready to fetch. + useEffect(() => { + if (isReady && isNotZero(activeEra.index) && !fetched) setupValidatorList(); + }, [isReady, activeEra.index, fetched]); + + // Control render throttle. + useEffect(() => { + if (!(batchEnd >= pageEnd || disableThrottle)) { + setTimeout(() => { + setRenderIteration(renderIterationRef.current + 1); + }, 50); + } + }, [renderIterationRef.current]); + + // Trigger `onSelected` when selection changes. + useEffect(() => { + if (onSelected) onSelected(listProvider); + }, [selected]); + + // List ui changes / validator changes trigger re-render of list. + useEffect(() => { + if (allowFilters && fetched) handleValidatorsFilterUpdate(); + }, [order, isSyncing, includes, excludes]); + + // Handle modal resize on list format change. + useEffect(() => { + maybeHandleModalResize(); + }, [listFormat, renderIteration, validators, page]); + return ( <ListWrapper> - <Header> - <div> - <h4> - {title || - `Dispalying ${validators.length} Validator${ - validators.length === 1 ? '' : 's' - }`} - </h4> - </div> - <div> - {allowListFormat === true && ( - <> - <button type="button" onClick={() => setListFormat('row')}> - <FontAwesomeIcon - icon={faBars} - color={ - listFormat === 'row' - ? networkColors[`${network.name}-${mode}`] - : 'inherit' - } - /> - </button> - <button type="button" onClick={() => setListFormat('col')}> - <FontAwesomeIcon - icon={faGripVertical} - color={ - listFormat === 'col' - ? networkColors[`${network.name}-${mode}`] - : 'inherit' - } - /> - </button> - </> - )} - </div> - </Header> - <List flexBasisLarge={allowMoreCols ? '33.33%' : '50%'}> + <List $flexBasisLarge={allowMoreCols ? '33.33%' : '50%'}> {allowSearch && ( <SearchInput handleChange={handleSearchChange} - placeholder="Search Address or Identity" + placeholder={t('searchAddress')} /> )} - - {allowFilters && <Filters />} + <FilterHeaderWrapper> + <div>{allowFilters && <FilterHeaders />}</div> + <div> + {allowListFormat === true && ( + <> + <button type="button" onClick={() => setListFormat('row')}> + <FontAwesomeIcon + icon={faBars} + color={ + listFormat === 'row' ? colors.primary[mode] : 'inherit' + } + /> + </button> + <button type="button" onClick={() => setListFormat('col')}> + <FontAwesomeIcon + icon={faGripVertical} + color={ + listFormat === 'col' ? colors.primary[mode] : 'inherit' + } + /> + </button> + </> + )} + </div> + </FilterHeaderWrapper> + {allowFilters && <FilterBadges />} {listValidators.length > 0 && pagination && ( <Pagination page={page} total={totalPages} setter={setPage} /> )} - {selectable && ( + {selectable ? ( <Selectable canSelect={listValidators.length > 0} actionsAll={actionsAll} actionsSelected={actionsSelected} + displayFor={displayFor} /> - )} + ) : null} <MotionContainer> {listValidators.length ? ( <> - {listValidators.map((validator: any, index: number) => { - // fetch batch data by referring to default list index. - const batchIndex = validatorsDefault.indexOf(validator); - - return ( - <motion.div - className={`item ${listFormat === 'row' ? 'row' : 'col'}`} - key={`nomination_${index}`} - variants={{ - hidden: { - y: 15, - opacity: 0, - }, - show: { - y: 0, - opacity: 1, - }, - }} - > - <Validator - validator={validator} - nominator={nominator} - toggleFavorites={toggleFavorites} - batchIndex={batchIndex} - batchKey={batchKey} - format={format} - showMenu={showMenu} - bondType={bondType} - inModal={inModal} - /> - </motion.div> - ); - })} + {listValidators.map((validator, index) => ( + <motion.div + key={`nomination_${index}`} + className={`item ${listFormat === 'row' ? 'row' : 'col'}`} + variants={{ + hidden: { + y: 15, + opacity: 0, + }, + show: { + y: 0, + opacity: 1, + }, + }} + > + <ValidatorItem + validator={validator} + nominator={nominator} + toggleFavorites={toggleFavorites} + format={format} + showMenu={showMenu} + bondFor={bondFor} + displayFor={displayFor} + nominationStatus={ + nominationStatus.current[validator.address] + } + /> + </motion.div> + ))} </> ) : ( <h4 style={{ marginTop: '1rem' }}> - {isSearching - ? 'No validators match this criteria.' - : 'No validators.'} + {isSearching ? t('noValidatorsMatch') : t('noValidators')} </h4> )} </MotionContainer> @@ -383,28 +404,14 @@ export const ValidatorListInner = (props: any) => { ); }; -export const ValidatorList = (props: any) => { +export const ValidatorList = (props: ValidatorListProps) => { const { selectActive, selectToggleable } = props; return ( <ListProvider selectActive={selectActive} selectToggleable={selectToggleable} > - <ValidatorListShouldUpdate {...props} /> + <ValidatorListInner {...props} /> </ListProvider> ); }; - -export class ValidatorListShouldUpdate extends React.Component<any, any> { - static contextType = StakingContext; - - shouldComponentUpdate(nextProps: any) { - return this.props.validators !== nextProps.validators; - } - - render() { - return <ValidatorListInner {...this.props} />; - } -} - -export default ValidatorList; diff --git a/src/library/ValidatorList/types.ts b/src/library/ValidatorList/types.ts new file mode 100644 index 0000000000..325c2fe4f0 --- /dev/null +++ b/src/library/ValidatorList/types.ts @@ -0,0 +1,33 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import type { AnyJson } from '@polkadot-cloud/react/types'; +import type { Validator } from 'contexts/Validators/types'; +import type { AnyFunction, BondFor, DisplayFor, MaybeAddress } from 'types'; + +export interface ValidatorListProps { + validators: Validator[]; + bondFor: BondFor; + allowMoreCols?: boolean; + generateMethod?: string; + nominator?: MaybeAddress; + allowFilters?: boolean; + toggleFavorites?: boolean; + pagination?: boolean; + title?: string; + format?: 'nomination' | 'default'; + selectable?: boolean; + onSelected?: AnyFunction; + actions?: AnyJson[]; + showMenu?: boolean; + displayFor?: DisplayFor; + allowSearch?: boolean; + allowListFormat?: boolean; + alwaysRefetchValidators?: boolean; + defaultFilters?: AnyJson; + defaultOrder?: string; + disableThrottle?: boolean; + selectActive?: boolean; + selectToggleable?: boolean; + refetchOnListUpdate?: boolean; +} diff --git a/src/locale/cn/base.json b/src/locale/cn/base.json index 6b0e635183..cf19eb8a9a 100644 --- a/src/locale/cn/base.json +++ b/src/locale/cn/base.json @@ -1,19 +1,56 @@ { "base": { - "stake": "抵押", - "validators": "验证人", - "overview": "总览", - "nominate": "抵押", - "pools": "提名池", - "payouts": "收益", + "active": "激活", + "allowAll": "允许所有", + "allowAnyoneCompound": "允许任何人代表您复利收益", + "allowAnyoneCompoundWithdraw": "允许任何人代表您复利或取出收益", + "allowAnyoneWithdraw": "允许任何人代表您取出收益", + "allowCompound": "允许复利", + "allowWithdraw": "允许取出收益", "community": "社区", - "favorites": "收藏夹", - "network": "网络", "feedback": "反馈", + "goTo": "查看", "help": "帮助", - "support": "支持", "inactive": "未激活", - "active": "激活", - "resources": "信息" + "network": "网络", + "nominate": "抵押", + "overview": "总览", + "payee": { + "account": { + "subtitle": "收益作为自由余额发到其他帐户", + "title": "转到其他帐户" + }, + "none": { + "subtitle": "未设置收益到账地址'", + "title_active": "未分配", + "title_default": "无" + }, + "staked": { + "subtitle": "自动将收益添加到现有的抵押余额中", + "title_active": "复利中", + "title_default": "复利" + }, + "stash": { + "subtitle": "收益作为自由余额发回账户", + "title": "转到您的帐户" + } + }, + "payouts": "收益", + "pools": "提名池", + "resources": "信息", + "stake": "抵押", + "support": "支持", + "time": { + "day": "天", + "hour": "小时", + "hr": "小时", + "min": "分钟", + "minute": "分钟", + "second": "秒" + }, + "title_kusama": "Kusama抵押平台", + "title_polkadot": "Polkadot抵押平台", + "title_westend": "Westend抵押平台", + "validators": "验证人" } } diff --git a/src/locale/cn/help.json b/src/locale/cn/help.json index 5d3fa73c28..9cf33d59bf 100644 --- a/src/locale/cn/help.json +++ b/src/locale/cn/help.json @@ -1,59 +1,115 @@ { "help": { - "modal": { - "help_resources": "帮助信息", - "all_resources": "所有信息", - "close": "关闭", - "definitions": "定义", - "articles": "文章", - "related": "相关" - }, "definitions": { - "dashboard_tips": [ - "实用提示", + "activeNominators": [ + "活跃提名人", [ - "该应用将为您提供提示,帮助您完成在波卡抵押中的每一步.", - "您可通过侧菜单左下角的齿轮图标访问设置中关闭或启用提示." + "当前Session中活跃的提名人.", + "因为您的验证人也可能己超额认选,所以活跃的提名人并不能保证能获得奖励." ] ], - "supply_staked": [ - "抵押比例", + "activePools": [ + "活跃提名池", + ["{NETWORK_NAME}网络上当前活跃的提名池数量."] + ], + "activeStakeThreshold": [ + "保持活跃度的抵押阈值", [ - "目前全球{NETWORK_UNIT}的累计发行量.", - "抵押的百分比与{NETWORK_UNIT}总发行量相关." + "在一个Era中为保持活跃提名的所需的{NETWORK_UNIT}数量.", + "适用于提名人和提名池. 对于提名池情况来说, 在加入池时重要的是池的总质押金额不能低于该值.", + "高于这个标准能保证可以一直在这个Era的活跃提名人名单中. 但这数额不保证能得到奖励,因为您的活跃提名人有超额认选的可能.", + "在{NETWORK_NAME},只有前{MAX_NOMINATOR_REWARDED_PER_VALIDATOR}名提名人才能获得每个验证人的奖励. 确保您的活跃度质押金额高于此阈值将增加获得奖励的机会.", + "可以从页面中追踪这些指标, 并在必要时执行抵押操作如增加{NETWORK_UNIT}或更改提名或加入其它提名池." ] ], - "total_nominators": [ - "总提名人数", + "activeValidator": [ + "活跃验证人", [ - "在网络中参与抵押的账户,无论他们在当前Session中是否活跃.", - "抵押{NETWORK_UNIT}的前提是成为提名人或加入提名池,提名池本身就是一个提名人." + "正在验证区块的验证人. 奖励根据验证人的活动表现累积.", + "每个Era都会选择一组新的验证人,因此不能保证同一验证人在随后的Era中会处于活跃状态.", + "{NETWORK_NAME}允许提名者最多提名16名验证人,最大限度地提高您在每个Era 提名活跃验证人的机会." ] ], - "active_nominators": [ - "活跃提名人", + "adjustedRewardsRate": [ + "调整后收益率", [ - "当前Session中活跃的提名人.", - "因为您的验证人也可能己超额认选,所以活跃的提名人并不能保证能获得奖励." + "基于{NETWORK_NAME}奖励分配模型的估算年收益.", + "该数字实际上是历史报酬率减去通货膨胀率." ] ], - "your_balance": [ - "余额", + "averageCommission": [ + "平均佣金", [ - "除了抵押的总金额外,还包括在提名池中抵押了的{NETWORK_UNIT}总金额.", - "和抵押的金额不同,质押的池金额是被持有并锁定在提名池中." + "{NETWORK_NAME}上验证人的平均佣金额.", + "该指标不包括拥有100%佣金的验证人,因为这些节点通常不让提名是因为他们本身是交易平台自身来抵押." ] ], - "reserve_balance": [ - "储备金额", + "blockedNominations": [ + "停止提名", + ["当验证人停止(冻结)被提名功能时,提名人无法提名他们."] + ], + "bondedInPool": [ + "质押在池中的金额", [ - "在{NETWORK_NAME}中,帐户必须有高于一定金额的余额才能在链上存在.这一数额称为“最低存款“.", - "该应用确保账户永远不会低于这个数额." + "池中当前质押的{NETWORK_UNIT}金额.", + "直接抵押的质押金额保留在您的帐户中并被锁定,与之不同的是,质押到池中的{NETWORK_UNIT}将被转移到池的隐藏帐户。尽管如此,池成员仍然可以随时解除其资金的绑定." + ] + ], + "bonding": [ + "质押", + [ + "质押金额是“锁定”(或抵押){NETWORK_UNIT}的过程. 质押的{NETWORK_UNIT}将自动分配给一个或多个指定验证人.", + "作为提名人,您可自主分配提名额。在提名池中,提名池所有者或池提名人将代表您分配提名额,您的质押金将用于支持这些提名.", + "最低收益抵押额统计数是指在当前Era提名人中质押最少的{NETWORK_UNIT}.该值也是获得奖励所需的最低金额." + ] + ], + "commission": [ + "佣金", + [ + "验证人可以获得一定比例的奖励。这部分被称为他们的佣金.", + "提名佣金率较低的验证人意味着您将获得他们产生的更大份额的奖励.", + "许多验证人的佣金率为100%,这意味着提名这些验证人将不会获得任何奖励.", + "这类代表包括交易所运营的验证人,其中提名和奖励分配在相关交易所集中进行.", + "验证人可以随时更新他们的佣金率,这些变化将对您的盈利能力产生影响. 请务必在页面上监控您的提名,以留意其佣金率更新." + ] + ], + "controllerAccountEligibility": [ + "Controller账户标准", + [ + "一个账户要成为Controller,其余额必须有少最低存款. 在{NETWORK_NAME}上最低存款为 {EXISTENTIAL_DEPOSIT} {NETWORK_UNIT}.", + "如果一个帐户没达到该数额, 您将看到'{NETWORK_UNIT}不足' 的提示.", + "用至少{EXISTENTIAL_DEPOSIT} {NETWORK_UNIT}将使其符合资格并可选择作为Controller." + ] + ], + "epoch": [ + "Epoch", + [ + "Epoch是{NETWORK_NAME} Session的另一个名称. 每个Era分为6个Epoch, 在此期间, 验证人被指定为特定时段或Slots的区块生产者.", + "1个Epoch在波卡为4小时." ] ], - "network_stats": [ - "网络数据", - ["实时网络信息可为您的抵押提供参考.", "可从总览中得到最新的网络数据."] + "era": [ + "Era", + [ + "在每个Era结束时,根据验证人在当前Era累积的Era点数奖励{NETWORK_UNIT}. 该奖励随后会分配给该验证人下的提名人.", + "1 Era目前在波卡是24小时." + ] + ], + "eraPoints": [ + "Era 点数", + [ + "Era 点由验证人在每个Era累积,取决于验证人的性能.", + "作为抵押者,您不需要在意Era点数. 一般来说,性能更好的验证人会产生更多的Era 点数,这反过来会得到更高的奖励." + ] + ], + "historicalRewardsRate": [ + "历史年收益", + ["根据{NETWORK_NAME}奖励分配数据估算的年收益."] + ], + "idealStaked": ["最优比例", ["理想网络条件下的抵押占发行量的百分比."]], + "inactiveNominations": [ + "非活跃提名", + ["指质押资金未分配到当前Era的活跃验证人群中的提名."] ], "inflation": [ "通货膨胀", @@ -62,62 +118,55 @@ "在验证人的奖励是基于抵押金额, 其余归国库所有的情况下, 每年通货膨胀率约为10%." ] ], - "historical_rewards_rate": [ - "历史年收益", - ["根据{NETWORK_NAME}奖励分配数据估算的年收益."] - ], - "ideal_staked": ["最优比例", ["理想网络条件下的抵押占发行量的百分比."]], - "nomination_status": [ - "提名状态", + "lastEraPayout": [ + "上一Era收益", [ - "当前的抵押状态.", - "当这些提名人中没有一个是在当前活跃验证人群内(被选择用于验证网络的验证人群体)时,这组提名将处于非活跃状态.", - "当您的被提名人中至少有一人是活跃时,此提名状态将显示为活跃提名-但这仍然不能保证奖励.", - "每个活跃验证人的前{FallbackNominatorRewardedPerValidator}提名人可获得{NETWORK_NAME}奖励。因此,如果被提名人活跃且超额认购,您必须是{FallbackNominatorRewardedPerValidator}最高质押提名人里的一员才能获得奖励.", - "如果一位活跃的被提名人没有超额认购,您将获得奖励." + "上一Era活跃的总{NETWORK_UNIT}奖励金额.", + "收益在该Era的活跃验证人之间平均分配,然后进一步分配给参与该Era的活跃提名人.", + "得到的收益金额取决于提名人和验证人自己在那个Era绑定了多少{NETWORK_UNIT}." ] ], - "stash_and_controller_accounts": [ - "Stash和Controller帐户", + "ledgerHardwareWallets": [ + "Ledger硬件钱包", [ - "Stash和Controller只是用于管理抵押操作的{NETWORK_NAME}账户.", - "Stash账户是用于存放抵押资金的账户,而Controlle账户则用于代表Stash账户执行抵押操作.", - "切换帐户实际上是在自动切换Stash帐户到Controller帐户.", - "请提前导入Stash和Controller帐户。否则将无法使用该应用的所有功能.", - "可在抵押页面上导入不同的Controller帐户." + "Staking Dashboard己完全支持可兼容的Ledger设备.您可开始使用Ledger管理{NETWORK_NAME}上的抵押.", + "因为您的私钥一直在硬件设备上,所以硬件钱包也称为冷钱包.硬件钱包没有连接互联网,所以使用该类钱包可确保安全.", + "为了使用Ledger设备导入和签署交易, 您的设备必须安装{NETWORK_NAME} Ledger App. 该应用程序可以通过Ledger自己的应用程序Ledger Live安装.", + "支持USB和蓝牙连接. 如果您的Ledger有蓝牙功能, 并且您的设备允许蓝牙连接, 您将能够通过Ledger设备无线导入帐户并签署交易.", + "详情请在其官方网站上查找:ledger.com." ] ], - "controller_account_eligibility": [ - "Controller账户标准", + "ledgerRejectedTransaction": [ + "Ledger拒绝交易", [ - "一个账户要成为Controller,其余额必须有少最低存款. 在{NETWORK_NAME}上最低存款为 {EXISTENTIAL_DEPOSIT} {NETWORK_UNIT}.", - "如果一个帐户没达到该数额, 您将看到'{NETWORK_UNIT}不足' 的提示.", - "用至少{EXISTENTIAL_DEPOSIT} {NETWORK_UNIT}将使其符合资格并可选择作为Controller." + "己在Ledger设备上拒绝交易, 该交易不会被上交.", + "请在Ledger设备上通过批准交易来提交交易." ] ], - "bonding": [ - "质押", + "ledgerRequestTimeout": [ + "Ledger请求超时", [ - "质押金额是“锁定”(或抵押){NETWORK_UNIT}的过程. 质押的{NETWORK_UNIT}将自动分配给一个或多个指定验证人.", - "作为提名人,您可自主分配提名额。在提名池中,提名池所有者或池提名人将代表您分配提名额,您的质押金将用于支持这些提名.", - "最低可活跃质押统计数是指在当前Era提名人中质押最少的{NETWORK_UNIT}.该值也是获得奖励所需的最低金额." + "当Ledger设备繁忙时, 或者当操作未能及时完成时, 可能会发生超时报错.", + "请尝试再次执行该操作, 如果错误仍然存在, 请尝试重新启动Ledger设备." ] ], - "active_bond_threshold": [ - "保持活跃度的质押阈值", + "lockedBalance": [ + "锁定余额", [ - "在一个Era中为保持活跃提名的所需的{NETWORK_UNIT}数量. ", - "适用于提名人和提名池. 对于提名池情况来说, 在加入池时重要的是池的总质押金额不能低于该值.", - "高于这个标准能保证可以一直在这个Era的活跃提名人名单中. 但这数额不保证能得到奖励,因为您的活跃提名人有超额认选的可能.", - "在{NETWORK_NAME},只有前{FallbackNominatorRewardedPerValidator}名提名人才能获得每个验证人的奖励. 确保您的活跃度质押金额高于此阈值将增加获得奖励的机会.", - "可以从页面中追踪这些指标, 并在必要时执行抵押操作如增加{NETWORK_UNIT}或更改提名或加入其它提名池." + "在{NETWORK_NAME}中,某些操作需要锁定余额,例如抵押或对治理进行投票。如果您的部分余额被锁定,则无法转移.", + "该应用显示的锁定余额代表除您的抵押余额外的所有锁定.", + "您可以用锁定余额做任何提名,但不能加入池,因为这需要转移余额到池帐户." ] ], - "reward_destination": [ - "奖励地址", + "minimumToCreatePool": [ + "最低建提名池质押金", + ["建池所需的最低{NETWORK_UNIT}金额.", "创建池需要比加入池更多的存款."] + ], + "minimumToJoinPool": [ + "最低入提名池质押金", [ - "奖励地址是奖励被发送到的地方.", - "奖励可以自动绑定到当前的绑定金额上,也可以发送到Stash、Controller或外部帐户上." + "加入池所需的最低{NETWORK_UNIT}金额.", + "该金额不同于创建池所需的质押金." ] ], "nominating": [ @@ -127,42 +176,108 @@ "一旦提名了选定的验证人,他们就会成为您的提名." ] ], + "nominationPools": [ + "提名池", + [ + "提名池允许用户通过抵押{NETWORK_UNIT}获得奖励.", + "与个人提名不同,使用提名池只需要少量{NETWORK_UNIT},提名池就会代表您管理提名人选." + ] + ], + "nominationStatus": [ + "提名状态", + [ + "当前的抵押状态.", + "当这些提名人中没有一个是在当前活跃验证人群内(被选择用于验证网络的验证人群体)时,这组提名将处于非活跃状态.", + "当您的被提名人中至少有一人是活跃时,此提名状态将显示为活跃提名-但这仍然不能保证奖励.", + "每个活跃验证人的前{MAX_NOMINATOR_REWARDED_PER_VALIDATOR}提名人可获得{NETWORK_NAME}奖励。因此,如果被提名人活跃且超额认购,您必须是{MAX_NOMINATOR_REWARDED_PER_VALIDATOR}最高质押提名人里的一员才能获得奖励.", + "如果一位活跃的被提名人没有超额认购,您将获得奖励." + ] + ], "nominations": [ "提名", [ - "提名指选择验证人. 在{NETWORK_NAME}里最高提名数为 {FallbackMaxNominations}.", + "提名指选择验证人. 在{NETWORK_NAME}里最高提名数为 {MAX_NOMINATIONS}.", "对于提名池,提名池所有者和池提名人负责代表所有的池成员提名验证人.", "一旦提名一交, 质押金额会自动分配给当前Era活跃的提名人.", "只要本人提名中至少有一个提名在当前session中活跃执行验证,您的资金就会与该验证人绑定从而获得奖励." ] ], - "inactive_nominations": [ - "非活跃提名", - ["指质押资金未分配到当前Era的活跃验证人群中的提名."] + "nominatorStake": [ + "提名人抵押", + [ + "{NETWORK_UNIT}验证人的总提名人质押的数量.", + "该值和验证人的自我抵押一起形成验证人总抵押数,值得注意的是,该值因为随着提名人在该Session的质押金额给活跃验证人的重新分配,每一个era都会发生变化." + ] ], - "nomination_pools": [ - "提名池", + "openAppOnLedger": [ + "在Ledger上打开相应的App", [ - "提名池允许用户通过抵押{NETWORK_UNIT}获得奖励.", - "与个人提名不同,使用提名池只需要少量{NETWORK_UNIT},提名池就会代表您管理提名人选." + "当{NETWORK_NAME} Ledger App当前未在连接的设备上打开时, 会出现此消息.", + "请在Ledger设备上打开 {NETWORK_NAME} App 然后重试." ] ], - "active_pools": [ - "活跃提名池", - ["{NETWORK_NAME}网络上当前活跃的提名池数量."] + "overSubscribed": [ + "超额认选", + [ + "只有每个验证人的前{MAX_NOMINATOR_REWARDED_PER_VALIDATOR}名提名人才能在{NETWORK_NAME}获得奖励. 当超过该数时,该验证人将被视为超额认选." + ] ], - "minimum_join_bond": [ - "最低入提名池质押金", + "payout": [ + "收益", [ - "加入池所需的最低{NETWORK_UNIT}金额.", - "该金额不同于创建池所需的质押金." + "在{NETWORK_NAME}里抵押的收益. 取决于您验证人随时间累积的“Era点数.奖励金额会在每个Era结束时确定(24小时).", + "要获得抵押奖励,需要手动申领.任何支持该验证人的提名人都可以申领.", + "一个申请可触发每所有个提名人的奖励申领." ] ], - "minimum_create_bond": [ - "最低建提名池质押金", - ["建池所需的最低{NETWORK_UNIT}金额.", "创建池需要比加入池更多的存款."] + "payoutDestination": [ + "收益到账地址", + [ + "收益到账地址决定您的收益发送到哪个帐户", + "收益可以自动绑定在当前质押额上,也可以发送到您的Stash, Controller或您选择的外部帐户" + ] + ], + "payoutHistory": [ + "收益记录", + [ + "一名活跃提名人的收益历史记录.", + "申领奖励是一个手动的过程,可能会快速连续或以零星方式收到多次收益. 因此,您的收益图可能会在同一天发生多个收益,或者几天没有收益.", + "这并不意味着您在该期间没有提名或产生奖励,只是该期间的收益尚未被申领." + ] + ], + "polkadotVault": [ + "Polkadot Vault", + [ + "Polkadot Vault(前身为Parity Signer)是一种冷钱包解决方案, 允许您在飞行模式下将手机用作网络隔离钱包.", + "从技术上讲, Vault App不是一个钱包, 因为它不允许转账.", + "它更像是一个钥匙链工具, 能够创建、管理和恢复帐户." + ] + ], + "poolCommissionChangeRate": [ + "提名池佣金变化率", + [ + "佣金变化率由池提名主理人设置,并决定佣金可以增加多少以及多久增加一次.", + "最大增加率决定了在一次更新中可以增加多少佣金率. 最小延迟决定了在佣金率可被再次提高之前必须通过多少区块.", + "一旦设置了初始变化率,此后只能设置更限制的值。更限制的值包括更小的最大增加和更大的最小延迟." + ] + ], + "poolCommissionRate": [ + "提名池佣金率", + [ + "提名池的佣金率.该值由提名池主理人设置,是提名池将从其成员那里获得的奖励的百分比", + "必须提供收款人账户才能收取佣金.", + "佣金率在从提名池分配给会员之前的奖励中提取." + ] + ], + "poolMaxCommission": [ + "提名池最大佣金率", + [ + "最高佣金率可被提名池所有者设置.", + "此值由提名池主理人设置, 是提名池从其成员处获得的奖励的最大百分比.", + "一旦设定了初始最大佣金, 此后只能设定更限制的值." + ] ], - "pool_membership": [ + "poolMembership": [ "提名池成员制", [ "提名池成员身份状态会显示您是否是提名池成员.", @@ -171,14 +286,7 @@ "要离开提名池,只需解除质押并取出所有质押的{NETWORK_UNIT}. 这里提供了一个专用的离开按钮,用于从提名池中解除绑定." ] ], - "bonded_in_pool": [ - "质押在池中的金额", - [ - "池中当前质押的{NETWORK_UNIT}金额.", - "直接抵押的质押金额保留在您的帐户中并被锁定,与之不同的是,质押到池中的{NETWORK_UNIT}将被转移到池的隐藏帐户。尽管如此,池成员仍然可以随时解除其资金的绑定." - ] - ], - "pool_rewards": [ + "poolRewards": [ "提名池的奖励", [ "作为提名池的活跃参与者所产生的{NETWORK_UNIT}奖励金额.", @@ -186,129 +294,124 @@ "用户有2种领取奖励的选择. 奖励可以绑定回池, 这将增加您在池中的份额并积累更多奖励. 或作为免费{NETWORK_UNIT}发送到您的帐户." ] ], - "pool_roles": [ + "poolRoles": [ "提名池里的角色", [ "一个提名池里有4种角色,每个角色在管理池的运行方面具有不同的职责.", - "主理人:可以更改提名人、状态切换人或其本身. 此外,它可以执行提名人或状态切换人可以执行的任何操作.", + "主理人:可以更改提名人、守护人或其本身. 此外,它可以执行提名人或守护人可以执行的任何操作.", "存款人:创建提名池并作为初始成员.只有在所有其他成员离开后能离开提名池.一旦他们退出,提名池将从系统中完全移除.", "提名人:可以选择提名池验证人.", - "状态切换人:如果提名池被冻结,可以更改池的状态和踢出(无权限解除绑定/撤回)成员." + "守护人:如果提名池被冻结,可以更改池的状态和踢出(无权限解除绑定/撤回)成员." ] ], - "validator": [ - "验证人", + "proxyAccounts": [ + "代理帐户", [ - "验证在{NETWORK_NAME}中继链中的区块的实体. 验证人通过确保网络安全并生成区块在{NETWORK_NAME}起着关键作用.", - "作为提名人,您可以选择支持哪个验证人并获得奖励." + "代理帐户是代理另一个帐户的帐户.", + "在代理帐户术语中, 代理帐户称为委托帐户, 代理帐户所代表的代理帐户被称为委托人帐户.", + "Staking Dashboard允许被委托人代表委托人签署交易. 如果代理账户在没有委托人的委托的情况下而存在, 则委托人将自动列为只读帐户." ] ], - "active_validator": [ - "活跃验证人", + "readOnlyAccounts": [ + "只读帐户", [ - "正在验证区块的验证人. 奖励根据验证人的活动表现累积.每个Era都会选择一组新的验证人,因此不能保证同一验证人在随后的Era中会处于活跃状态.", - "{NETWORK_NAME}允许提名者最多提名16名验证人,最大限度地提高您在每个Era 提名活跃验证人的机会." + "只读帐户是可以导入但不能签署交易记录的帐户.这意味着您可以查看帐户的余额和质押信息, 但不能使用它执行任何抵押操作." ] ], - "average_commission": [ - "平均佣金", - [ - "{NETWORK_NAME}上验证人的平均佣金额.", - "该指标不包括拥有100%佣金的验证人,因为这些节点通常不让提名是因为他们本身是交易平台自身来抵押." - ] - ], - "era": [ - "Era", + "reserveBalance": [ + "储备金额", [ - "在每个Era结束时,根据验证人在当前Era累积的Era点数奖励{NETWORK_UNIT}. 该奖励随后会分配给该验证人下的提名人.", - "1 Era目前在波卡是24小时." + "在{NETWORK_NAME}中,帐户必须有高于一定金额的余额才能在链上存在.这一数额称为“最低存款“.", + "该应用确保账户永远不会低于这个数额." ] ], - "epoch": [ - "Epoch", + "reserveBalanceForExistentialDeposit": [ + "为最低存款存储", [ - "Epoch是{NETWORK_NAME} Session的另一个名称. 在每个Epoch开始时选择一组不同的验证人验证区块.", - "1个Epoch在波卡为4小时." + "如果您的账户已经锁定了足够涵盖最低存款的金额, 例如为提名而锁定的金额, Staking Dashboard则不会另外锁定任何额外的可用金额.", + "在这种情况下, \"无\" 会显示在存储下的 \"为最低存款存储\"部分." ] ], - "era_points": [ - "Era 点数", + "rewardsByCountryAndNetwork": [ + "按国家和网络划分的收益", [ - "Era 点由验证人在每个Era累积,取决于验证人的性能.作为抵押者,您不需要在意Era点数. 一般来说,性能更好的验证人会产生更多的Era 点数,这反过来会得到更高的奖励." + "显示来自不同国家和IP网络收益的预估百分比.", + "Polkawatch分布式分析器是通过聚合验证人的IP位置的收益来计算的, 取样期为过去的60天." ] ], - "self_stake": [ + "selfStake": [ "自我抵押", [ "验证人自身质押的{NETWORK_UNIT}数量.", "这个值也会被添加到提名人质押{NETWORK_UNIT}数量中,作为验证人的总抵押数的一部分." ] ], - "nominator_stake": [ - "提名人抵押", + "stashAndControllerAccounts": [ + "Stash和Controller帐户", [ - "{NETWORK_UNIT}验证人的总提名人质押的数量.", - "该值和验证人的自我抵押一起形成验证人总抵押数,值得注意的是,该值因为随着提名人在该Session的质押金额给活跃验证人的重新分配,每一个era都会发生变化." + "Stash和Controller只是用于管理抵押操作的{NETWORK_NAME}账户.", + "Stash账户是用于存放抵押资金的账户,而Controlle账户则用于代表Stash账户执行抵押操作.", + "切换帐户实际上是在自动切换Stash帐户到Controller帐户.", + "请提前导入Stash和Controller帐户。否则将无法使用该应用的所有功能.", + "可在抵押页面上导入不同的Controller帐户." ] ], - "commission": [ - "佣金", + "supplyStaked": [ + "抵押比例", [ - "验证人可以获得一定比例的奖励。这部分被称为他们的佣金.", - "提名佣金率较低的验证人意味着您将获得他们产生的更大份额的奖励.", - "许多验证人的佣金率为100%,这意味着提名这些验证人将不会获得任何奖励.", - "这类代表包括交易所运营的验证人,其中提名和奖励分配在相关交易所集中进行.", - "验证人可以随时更新他们的佣金率,这些变化将对您的盈利能力产生影响. 请务必在页面上监控您的提名,以留意其佣金率更新." + "目前全球{NETWORK_UNIT}的累计发行量.", + "抵押的百分比与{NETWORK_UNIT}总发行量相关." ] ], - "over_subscribed": [ - "超额认选", + "totalNominators": [ + "总提名人数", [ - "只有每个验证人的前{FallbackNominatorRewardedPerValidator}名提名人才能在{NETWORK_NAME}获得奖励. 当超过该数时,该验证人将被视为超额认选." + "在网络中参与抵押的账户,无论他们在当前Session中是否活跃.", + "抵押{NETWORK_UNIT}的前提是成为提名人或加入提名池,提名池本身就是一个提名人." ] ], - "blocked_nominations": [ - "停止提名", - ["当验证人停止(冻结)被提名功能时,提名人无法提名他们."] - ], - "payout": [ - "收益", + "validator": [ + "验证人", [ - "在{NETWORK_NAME}里抵押的收益. 取决于您验证人随时间累积的“Era点数.奖励金额会在每个Era结束时确定(24小时).", - "要获得抵押奖励,需要手动申领.任何支持该验证人的提名人都可以申领.", - "一个申请可触发每所有个提名人的奖励申领." + "验证在{NETWORK_NAME}中继链中的区块的实体. 验证人通过确保网络安全并生成区块在{NETWORK_NAME}起着关键作用.", + "作为提名人,您可以选择支持哪个验证人并获得奖励." ] ], - "last_era_payout": [ - "上一Era收益", + "wrongTransaction": [ + "错误交易", [ - "上一Era活跃的总{NETWORK_UNIT}奖励金额.", - "收益在该Era的活跃验证人之间平均分配,然后进一步分配给参与该Era的活跃提名人.", - "得到的收益金额取决于提名人和验证人自己在那个Era绑定了多少{NETWORK_UNIT}." + "此错误发生原因为当Ledger设备正在签名或签名后的交易与Staking Dashboard上当前活动交易不同.", + "请确保Ledger设备上没有未完成的交易, 然后重试." ] ], - "payout_history": [ - "收益记录", + "yourBalance": [ + "余额", [ - "一名活跃提名人的收益历史记录.", - "申领奖励是一个手动的过程,可能会快速连续或以零星方式收到多次收益. 因此,您的收益图可能会在同一天发生多个收益,或者几天没有收益.", - "这并不意味着您在该期间没有提名或产生奖励,只是该期间的收益尚未被申领." + "除了抵押的总金额外,还包括在提名池中抵押了的{NETWORK_UNIT}总金额.", + "和抵押的金额不同,质押的池金额是被持有并锁定在提名池中." ] ] }, "externals": { - "connect_your_accounts": "如何连接您的帐户", - "how_to_use": "如何使用Staking Dashboard:概述", - "stake_cere": "抵押您的CERE", - "change_destination": "更改奖励钱包地址", - "bond_more": "质押更多代币到现有的抵押", - "unbonding_tokens": "解除您的质押", + "bondMore": "质押更多代币到现有的抵押", + "changeAccount": "更改您的Controller账号", + "changeDestination": "更改奖励钱包地址", + "changeNominations": "更改您的提名", + "chooseValidators": "如何选择验证人?", + "claimRewards": "申领提名池奖励", + "connectAccounts": "如何连接您的帐户", + "createPools": "创建提名池", + "howToUse": "如何使用Staking Dashboard:概述", "rebonding": "解除质押中", - "change_account": "更改您的Controller账号", - "change_nominations": "更改您的提名", - "create_pools": "创建提名池", - "claim_rewards": "申领提名池奖励", - "choose_validators": "如何选择验证人?" + "stakeCere": "抵押您的CERE", + "unbondingTokens": "解除您的质押" + }, + "modal": { + "articles": "文章", + "close": "关闭", + "definitions": "定义", + "helpResources": "帮助信息", + "related": "相关" } } } diff --git a/src/locale/cn/library.json b/src/locale/cn/library.json new file mode 100644 index 0000000000..24debbb519 --- /dev/null +++ b/src/locale/cn/library.json @@ -0,0 +1,191 @@ +{ + "library": { + "100Commission": "100% 佣金", + "accountConnected": "帐户已连接", + "accounts": "账户", + "active": "活跃", + "activeLowCommission": "活跃低佣金", + "activeLowCommissionSubtitle": "选择低佣金且高效表现的验证人", + "activePools": "活跃提名池", + "activeValidator": "活跃验证人", + "activeValidators": "活跃验证人", + "add": "添加", + "addFromFavorites": "从收藏夹添加", + "address": "地址", + "addressCopiedToClipboard": "复制到剪贴板的地址", + "all": "全部", + "alreadyImported": "地址已导入", + "asAPoolMember": "作为提名池成员", + "asThePoolDepositor": "作为提名池存款人", + "atLeast": "质押金最低为", + "available": "可用", + "backToMethods": "返回方案选择", + "backToScan": "回到扫描", + "blockedNominations": "己冻结提名", + "blockingNominations": "冻结提名中", + "bond": "质押", + "bondAmountDecimals": "质押金额最多只能有 {{units}}个小数位", + "bondDecimalsError": "质押金额能最多有 {{units}} 位点数", + "bonded": "己质押", + "cancel": "取消", + "cancelled": "已取消", + "chooseValidators": "最多能选择 {{maxNominations}} 个验证人。", + "chooseValidators2": "自动生成提名或手动加入提名", + "clear": "清除", + "clearSelection": "清除选择", + "clickToReload": "重新加载", + "complete": "完成", + "confirm": "确认", + "confirmReformat": "地址已重新格式化。请确认", + "connect": "连接", + "connectedTo": "连接到", + "connectedToNetwork": "已连接网络", + "connecting": "连接中", + "continue": "继续", + "copyAddress": "复制地址", + "copyPoolAddress": "复制池地址", + "createPool": "创建提名池", + "dayAverage": "日平均值", + "dayPerformance": "天内表现", + "dayPerformanceStanding": "{{count}}天活跃验证人内表现排名", + "dayPoolPerformance": "天内提名池表现", + "destroying": "销毁中", + "destroyingPools": "正在销毁提名池", + "disclaimer": "免责声明", + "disconnected": "已断开", + "displayingValidators": "正在显示 {{count}} 个验证人", + "done": "完成", + "enablePermissionlessClaiming": "启用己许可申领", + "eraPoints": "Era 点数", + "errorUnknown": "抱歉,页面出现点小问题哦", + "errorWithTransaction": "交易出错", + "estimatedFee": "预计费用", + "exclude": "不含", + "failed": "失败", + "fastUnstake": "快速解除抵押", + "fastUnstakeCheckingEras": "正在查验 {{total}} Eras中的 {{checked}}", + "fastUnstakeExposed": "在 {{count}} Era前己被显示", + "favorite": "收藏夹", + "favoritePoolAdded": "己添加提名池", + "favoritePoolRemoved": "己删除提名池", + "favoriteValidatorAdded": "验证人已添加到收藏夹", + "favoriteValidatorRemoved": "收藏夹已删除验证人", + "filter": "筛选", + "filterValidators": "过滤验证人", + "finalized": "交易已确认", + "free": "己解锁", + "fromFavorites": "来自收藏夹", + "fromFavoritesSubtitle": "获取一组您喜欢的验证人", + "graphInactive": "不活跃", + "highCommission": "高佣金", + "highPerformanceValidator": "高效表现的验证人", + "iHaveScanned": "己扫描", + "import": "导入", + "importing": "导入中", + "inBlock": "己在区块中", + "inQueue": "在队列中", + "inactive": "非活跃", + "include": "包含", + "insertPayoutAddress": "输入收益到账地址", + "invalid": "地址无效", + "join": "加入提名池", + "legalDisclosures": "法律论述", + "listItemActive": "活跃", + "locked": "己锁", + "lockedPools": "已锁定提名池", + "lowCommission": "低佣金", + "manual": "手动", + "manualSelectionSubtitle": "从头开始添加验证人", + "manual_selection": "手动选择", + "max": "最高", + "minimumBond": "最低质押为 {{minBondUnit}} {{unit}}", + "missingIdentity": "无ID", + "moreThanBalance": "质押金额超过余额", + "next": "下一页", + "noFilters": "无筛选", + "noFree": "您没有可用的 {{unit}} 可质押", + "noMatch": "没有符合此条件的池", + "noPayoutAddress": "无收益到账地址", + "noValidators": "没有验证人", + "noValidatorsMatch": "没有符合此条件的验证人", + "nominate": "提名", + "nominateActive": "激活", + "nominateInactive": "未激活", + "nominationsReverted": "已恢复原来提名", + "nominator": "提名人", + "notEnough": "不足", + "notEnoughAfter": "交易费用后 {{unit}} 不足以质押", + "notEnoughFunds": "您的 {{unit}} 不足以提交这次交易", + "notImported": "未导入", + "notMeet": "未达到最低质押金款", + "notNominating": "非提名状态中", + "notStaking": "无抵押", + "notValidAddress": "无效地址", + "optimalSelection": "最佳选择", + "optimalSelectionSubtitle": "选择表现最佳且定期活跃的验证人", + "order": "顺序", + "orderValidators": "验证人排序", + "overSubscribed": " 己超额认选", + "overSubscribedMinReward": "超额认选:最低奖励质押为", + "page": "{{page}} / {{total}}", + "payout": "收益", + "payoutAccount": "收益到账账户", + "payoutAddress": "收益到账地址", + "pending": "待定中", + "permissionlessClaimingTurnedOff": "己许可申领己关闭", + "points": "点数", + "pool": "提名池", + "poolClaim": "提名池申领", + "poolCommission": "提名池佣金", + "poolId": "提名池ID", + "poolMembers": "提名池成员", + "prev": "上一页", + "privacy": "隐私", + "proxy": "代理账户", + "randomValidator": "随机验证人", + "reGenerate": "重新生成", + "remove": "删除", + "removeSelected": "移除选定项", + "reset": "重设", + "revertedToActiveSelection": "提名已恢复为您原来的选择", + "scanPolkadotVault": "请在Polkadot Vault上扫描", + "search": "搜索池ID、名称或地址", + "searchAddress": "搜索地址或身份", + "select": "选择", + "sign": "签署", + "signPolkadotVault": "请在Polkadot Vault上签名", + "signedByController": "由Controller签署", + "signedByProxy": "代理账户己签名", + "signer": "签名账户", + "signing": "签署中", + "submitTransaction": "准备提交交易", + "syncing": "正在同步", + "syncingPoolList": "同步提名池列表", + "tooSmall": "质押金额太少", + "top": "首", + "transactionCancelled": "交易已取消", + "transactionInBlock": "交易己包含区块里", + "transactionInitiated": "交易已启动", + "transactionSuccessful": "交易成功", + "unbond": "解除质押", + "unbondAmount": "解除金额超过质押余额", + "unbonding": "正在解除质押", + "unclaimedPayouts": "未申领收益", + "unlocking": "正在解锁", + "unordered": "无顺序", + "update": "更改", + "valid": "可用地址", + "validAddress": "有效地址", + "validatingParachainBlocks": "验证平行链区块", + "validatorCommission": "验证人佣金", + "validatorPerformance": "{{count}}天内验证人表现", + "valueTooSmall": "值太小", + "viewDecentralization": "分布式指标", + "viewMetrics": "验证人指标", + "viewPoolNominations": "查看池提名", + "waiting": "等待中", + "walletNotFound": "未找到钱包", + "whenActivelyNominating": "当活跃提名时", + "wrongTransaction": "错误交易,请重新签名" + } +} diff --git a/src/locale/cn/modals.json b/src/locale/cn/modals.json new file mode 100644 index 0000000000..982b7c3ed6 --- /dev/null +++ b/src/locale/cn/modals.json @@ -0,0 +1,279 @@ +{ + "modals": { + "aboveExisting": "高于现有", + "aboveGlobalMax": "高于总体最大值", + "aboveMax": "高于最大值", + "account": "账户", + "accountAlreadyImported": "帐户已导入", + "accounts": "账户", + "activeRoles": "有活跃角色在 {{count}} 个提名池", + "activeRoles_zero": "无活跃角色在提名池", + "add": "添加", + "addToBond": "添加到质押", + "addToNominations": "添加到提名列表", + "addUpToFavorites": "您最多可从收藏夹添加到{{count}}个验证人", + "addedToBond": "这笔{{unit}} 将添加到您当前的质押金中", + "addingFavorite": "正在添加{{count}} 个提名", + "addressReceived": "己收到地址:", + "afterClaiming": "申领后资金将立即变成可用余额", + "allNominations": "所有提名", + "allPoolRoles": "所有池角色", + "allowToJoin": "允许新成员加入", + "amountToBond": "质押额:", + "approveTransactionLedger": "在Ledger上批准交易", + "back": "返回", + "beingDestroyed": "正在销毁此池。", + "belowExisting": "低于现有", + "beyondMaxIncrease": "超出最大增量", + "binanceApi": "币安API", + "bond": "质押", + "bondAll": "质押所有", + "bondAllAvailable": "质押所有可用金额", + "bondExtra": "质押多余", + "bondMore": "质押更多", + "bondingWithdraw": "质押还将提取您的未申领收益", + "bouncer": " 守护人", + "braveText": "<b> 致Brave的用户</b> ! 由于最近的更新 (<i>Brave 版本 1.36</i>), 使用轻型客户端时可能会出现问题(例如不能连接).", + "cancel": "取消", + "changeControllerAccount": "更改Controller 帐户", + "changeNomination": "一旦提交,您的提名将被移除,从下一个Era开始就不会被提名", + "changePoolRoles": "更改池角色", + "changeRate": "更改率", + "changeToDestroy": "更改为销毁状态", + "checking": "检验中...", + "chooseLanguage": "选择语言", + "claim": "申领", + "claimCommission": "申领佣金", + "claimOutstandingCommission": "在提名池奖励帐户中申领任何未付佣金", + "claimPayouts": "申领收益", + "claimReward1": "提交后,收益将被绑定回提名池中。可以随时提取", + "claimReward2": "提取收益将立即将其作为余额转入帐户", + "claimsOnBehalf": "代表所有提名同一验证人的提名人申领收益。交易费通常更高,大多数提名人都依赖验证人代表他们进行收益申领", + "commissionRate": "佣金值率", + "compound": "复利", + "confirmReset": "确认重设", + "connect": "连接", + "connectLedgerToContinue": "连接您的Ledger 设备继续", + "connected": "己连接", + "connectionType": "连接类型", + "continue": "继续", + "controllerImported": "您必须导入您的Controller帐户才能签署此交易", + "dashboardTips": "提示消息", + "days": "天", + "decentralizationAnalyticsNotAvailable": "无法使用该分布式分析器", + "decentralizationAnalyticsNotSupported": "此网络不支持该分布式分析器", + "declare": "添加", + "declared": "己添加", + "destroyIrreversible": "销毁池是不可逆转的", + "destroyPool": "销毁提名池", + "destroyPoolResult": "销毁提名池后,所有成员都可以无权限解除质押,提名池永远无法重新开启", + "developerTools": "开发者工具", + "differentNetworkAddress": "不同的网络地址", + "disconnect": "断开", + "done": "完成", + "ensureLedgerIsConnected": "提示:请确保您的Ledger设备已连接", + "exitYourStakingPosition": "退出抵押", + "extensionConnected": "扩展已连接", + "extensions": "扩展", + "fastUnstakeCurrentQueue": "当前处于快速解除抵押队列中的帐户数", + "fastUnstakeExposedAgo": "您上次被显示于 {{count}} Era 前", + "fastUnstakeNote1": "如需快速解除抵押, 您必须无活跃抵押长于 {{bondDuration}} 个Eras", + "fastUnstakeNote2": "如您在至少 {{count}} 个era内处于非活动状态,您可选择快速解除抵押", + "fastUnstakeOnceRegistered": "点击快速解除抵押后,您将被列在等待队列中", + "fastUnstakeRegistered": "已点击并等待快速解除抵押中", + "fastUnstakeSubmit_cancel": "取消快速解除抵押", + "fastUnstakeSubmit_register": "快速解除抵押", + "fastUnstakeUnorderedNote": "快速解除抵押队列是无序的,因此被选择的确切时间是不确定的", + "fastUnstakeWarningUnlocksActive": "您有 {{count}} 个己解锁处于活跃状态, ", + "fastUnstakeWarningUnlocksActiveMore": "无己解锁可被快速解除抵押。重新解除质押或撤回己解锁以重回绑定,然后再次尝试快速解除抵押", + "fastUnstake_register": "点击快速解除抵押", + "fastUnstake_title": "快速解除抵押", + "favoriteNotNominated": "收藏夹中的验证人/未提名", + "favoriteValidators": "收藏夹中的验证人", + "favoritesAddedSubtitle": "已将{{count}}个验证人添加到您的提名列表中", + "favoritesAddedTitle": "已添加", + "feedback": "反馈", + "feedbackPage": "我们的反馈页面在", + "for": "委托人 {{who}}", + "forget": "清除", + "free": "可用余额", + "getAnotherAccount": "获取更多帐户", + "gettingAccount": "正在获取帐户", + "gettingAddress": "正在获取地址...", + "goToAccounts": "帐户", + "goToConnect": "连接", + "hardware": "硬件", + "hide": "隐藏", + "hours": "小时", + "import": "导入", + "importAccount": "导入帐户", + "importAddress": "导入地址", + "importAnotherAccount": "导入另一个帐户", + "importedAccount": "{{count}} 个己导入帐户", + "inPool": "提名池中", + "inputAddress": "输入地址", + "inputDelegatorAddress": "输入委托人地址", + "inputPayeeAccount": "输入收款人账户", + "invalidAddress": "无效地址", + "joinPool": "加入池", + "leavePool": "离开池", + "ledgerAccount": "Ledger帐户", + "ledgerAccounts": "正在显示Ledger帐户", + "ledgerRequestTimeout": "Ledger 请求超时.请再试一次", + "ledgerWillBeReset": "您的Ledger帐户列表将被重置,并且所有导入的帐户都将被删除", + "lightClient": "轻客户端", + "lockPool": "锁定", + "lockPoolSubtitle": "一旦锁定提名池,其他人就无法加入提名池", + "manageCommission": "管理佣金值", + "manageNominations": "提名管理", + "managePool": "管理池", + "maxCommission": "最高佣金值", + "maximumCommissionUpdated": "最高佣金值最新值", + "minDelayBetweenUpdates": "更新之间的最小延迟", + "minutes": "分钟", + "missingNesting": "该调用缺少内置支持,无法签署交易", + "months": "月", + "moreFavoritesSurpassLimit": "添加更多收藏夹提名人将超过{{max}}的提名限制", + "networks": "网络", + "newTotalBond": "新质押总额:", + "newlyBondedFunds": "新质押金将从下一个Era开始归回到活跃的提名中", + "noAccounts": "无帐户", + "noActiveAccount": "无活跃帐户", + "noEnough": "您没有足够的可使用余额做快速解除抵押。快速解除抵押需要余额", + "noFavoritesAvailable": "无收藏夹", + "noFavoritesSelected": "无选中收藏夹", + "noNominationsSet": "没有提名任何人", + "noNominatorRole": "您未参与任何提名池活动", + "noProxyAccountsDeclared": "尚未添加任何代理帐户", + "noReadOnlyAdded": "未添加任何只读帐户", + "noRewards": "无收益可申领", + "noVaultAccountsImported": "尚未导入任何Polkadot Vault帐户", + "nominateFavorites": "提名收藏夹中的验证人", + "nominating": "提名中", + "nominatingAndInPool": "己在提名并提名池中", + "nomination": "{{count}} 个提名", + "nominator": "提名人", + "nominatorStake": "提名人抵押", + "none": "无", + "notAuthenticated": "未验证", + "notInstalled": "未安装", + "notMeetMinimum": "未达到{{minNominatorBondUnit}} {{unit}}的提名人最低额.请在提名前质押一些资金", + "notStaking": "无抵押", + "notToClaim": "验证人通常代表其提名人申领收益。如果您决定现在不申领收益,您很可能会在1-2天内收到收益", + "notePoolDepositorMinBond_depositor": "作为提名池存款人,您必须至少持有 {{bond}} {{unit}}", + "notePoolDepositorMinBond_member": "作为一名提名池成员,你必须至少持有 {{bond}} {{unit}}", + "onceUnbonding": "一旦解除质押,您的资金过了{{bondDurationFormatted}} 后就能获回", + "openAppOnLedger": "在您的Ledger设备中打开{{appName}} App", + "openFeedback": "在 Canny.io反馈", + "payeeAdded": "已添加收款人", + "payoutDestination": "收益到账地址", + "pendingPayout": "{{count}} 个待申领收益", + "polkawatchDisabled": "Polkawatch己断开", + "pool": "池", + "poolIsNotNominating": "该提名池未提名任何验证人", + "poolName": "提名池名称", + "poolNominations": "池的提名", + "provider": "服务端", + "proxies": "代理", + "proxy": "代理账户", + "proxyAccounts": "代理帐户", + "queuedTransactionRejected": "上一笔交易己被拒绝.请再试一次", + "readOnly": "只读", + "readOnlyAccounts": "只读帐户", + "readOnlyCannotSign": "只读帐户无法签署交易", + "rebond": "重新质押", + "rebondSubtitle": "从下Era开始, 重新质押的资金将重回活跃的提名中", + "rebondUnlock": "在此之后可随时重新质押解锁,或将其提出", + "recentEraPoints": "最近Era点数", + "registerFastUnstake": "快速解除抵押需要余额", + "remove": "解除", + "removeAccount": "删除帐户", + "removeBond": "解除质押", + "renamePool": "重命名", + "reserveBalance": "预存金额", + "reserveForExistentialDeposit": "为最低存款预存金额", + "reserveForTxFees": "为交易费用预存金额", + "reserveText": "控制预存多少{{unit}}用于支付交易费用. 这笔金额是在您账户最低存款基础上增加的.", + "resetLedgerAccounts": "重设Ledger帐户", + "revertChanges": "回复原状", + "revertNominationChanges": "您确定要还原更改前的提名吗?", + "revertNominations": "还原提名", + "rewards": "收益", + "rewardsByCountryAndNetwork": "按国家/地区和网络供应商划分的收益", + "root": "主理人", + "rpcProviders": "RPC服务端", + "scanFromPolkadotVault": "从Polkadot Vault中扫描", + "searchAccount": "搜索帐户", + "selectNetwork": "网络选择", + "selectRpcProvider": "可选择任意一个RPC服务端来更改Staking Dashboard连接的{{network}}节点", + "selected": "己选", + "selfStake": "自我抵押", + "sentToCommissionPayee": "此金额将发送到为此提名池己设置的佣金收款人帐户", + "setToDestroying": "销毁", + "setToDestroyingSubtitle": "将提名池设置为销毁后将无法撤回, 请确定计划关闭提名池时才设置此状态", + "settings": "设置", + "signedTransactionSuccessfully": "已成功签署交易", + "stop": "停止", + "stopJoiningPool": "阻止新成员加入", + "stopNominating": "停止提名", + "stopNominatingBefore": "在解除所有质押前停止提名", + "storedOnChain": "更新的名称将作为编码字节存储在链上, 更新后将立即生效", + "submit": "提交", + "submitLock": "提交提名池锁定", + "submitUnlock": "提交提名池解锁", + "submitting": "提交中", + "subscanDisabled": "Subscan己断开", + "successfullyFetchedAddress": "成功获取地址", + "thisMinimumDelay": "这个最小延迟近似等于{{count}}个块", + "titleExtensionConnected": "{{title}} 扩展已连接", + "toggleFeatures": "功能切换", + "togglePlugins": "切换插件", + "total": "总共", + "transactionRejectedPending": "交易被拒后等待处理中", + "tryAgain": "请重试一次", + "unbond": "解除质押", + "unbondAll": "解除所有质押", + "unbondErrorBelowMinimum": "无法取消质押.您的债券资金低于{{bond}} {{unit}}", + "unbondErrorNoFunds": "您无{{unit}}可解除质押", + "unbondFundsLeavePool": "解除质押并退出提名池", + "unbondMemberFunds": "解除会员质押金", + "unbondSomeOfYour": "解除部分质押", + "unbondToMaintain": "存款人解除质押到{{minJoinBondUnit}} {{unit}}最小值,以保持池成员身份", + "unbondToMinimum": "解除至最少质押", + "unbondToMinimumCreate": "存款人解除质押到{{minCreateBondUnit}} {{unit}}的最低保证金", + "unbonding": "解除质押中", + "unbondingWithdraw": "解除质押还将提取您的未申领收益", + "undergoingMaintenance": "正在进行维护", + "unlockChunk": "己解锁的金额当前无法在池中重新质押。如果您希望重新质押,请先取出解锁金额并添加到您的质押中", + "unlockLedgerToContinue": "解锁您的Ledger设备后继续", + "unlockPool": "解锁", + "unlockPoolSubtitle": "一旦提名池被解锁, 任何帐户都可以作为成员加入提名池", + "unlockTake": "{{bondDurationFormatted}}后解锁金额可被提取", + "unlocked": "已解锁", + "unlocks": "解锁", + "unlocksAfter": "解锁于", + "unlocksInEra": "解锁于Era", + "unstake": "解除抵押", + "unstakeStopNominating": "停止提名 {{count}} 个验证人", + "unstakeUnbond": "解除质押 {{bond}} {{unit}}", + "updateClaimPermission": "更新申领权限", + "updateName": "更改名称", + "updatePayoutDestination": "更新收益到账地址", + "updatePoolCommission": "更新提名池佣金设置", + "updateWhoClaimRewards": "更改代表申领奖励地址", + "updated": "己更新", + "validatorDecentralization": "验证人的分布式地理指标", + "validatorMetrics": "验证人指标", + "vaultAccounts": "{{count}} 个帐户己被导入", + "vaultAccounts_zero": "无帐户导入", + "waitingForQRCode": "请扫描二维码", + "web": "网络", + "welcomeToReport": "欢迎Bug报告、功能请求和改进", + "willSurpass": "添加过多收藏夹将超{{maxNominations}}提名数", + "withdraw": "提取", + "withdrawMemberFunds": "取出会员资金", + "withdrawSubtitle": "资金将在取款后立即转作可用余额", + "withdrawUnlocked": "取出己解锁", + "years": "年" + } +} diff --git a/src/locale/cn/pages.json b/src/locale/cn/pages.json index acf7d87f3f..43ac4827e2 100644 --- a/src/locale/cn/pages.json +++ b/src/locale/cn/pages.json @@ -1,208 +1,230 @@ { "pages": { - "overview": { - "nominator_limit": "已达到提名人数上限", - "maximum_allowed": "已达到提名人数上限。请等待可用的名额", - "limit_reached": "%提名人限额己被达到", - "maximum_amount": "提名人数接近最高。提名人上限目前为", - "pools_are_active": "个提名池活跃中", - "available_to_join": "在{{networkName}}网络中可参与的池数", - "in_pools": "当前质押在提名池中", - "bonded_in_pools": "提名池中当前质押的{{networkUnit}}总数", - "pool_members_bonding": "名成员正在提名池活跃质押中.", - "total_num_accounts": "已加入提名池的帐户总数.", - "minimum_nominator_bond": "提名人抵押额最低为", - "minimum_bonding_amount": "在{{networkName}}提名最低绑定额为", - "currently_staked": "{{networkUnit}}中的{{supplyAsPercent}}% 目前己被抵押.", - "staking_on_the_network": "共 {{lastTotalStakeBase}} {{networkUnit}}在网络中被活跃抵押中", - "network_stats": "网络信息", - "historical_rewards_rate": "历史奖励率", - "inflation": "通货膨胀", - "supply_staked": "抵押比例", - "active_era": "活跃 Era", - "total_nominators": "提名人总数", - "active_nominators": "活跃提名人", - "address_copied": "地址已复制到剪贴板", - "no_account_connected": "未连接帐户", - "in_pool": "提名池中", - "available": "可用", - "unlocking": "正在解锁", - "nominating": "提名中", - "balance": "余额", - "overview": "概述", - "recent_payouts": "最近收益", - "subscan_disabled": "Subscan己断开", - "not_staking": "无抵押", - "reserved": "己储备" - }, - "favorites": { - "fetching_favorite_validators": "正在获取收藏夹的验证人...", - "no_favorites": "无收藏夹", - "favorite_validators": "藏夹中的验证人" + "community": { + "bio": "简介", + "connecting": "连接中", + "email": "邮箱", + "fetchingValidators": "正在获取验证人信息", + "goBack": "返回", + "noValidators": "不包含验证人", + "validator": "{{count}} 个验证人", + "website": "个人网站" }, "nominate": { - "none": "无", + "activeNominations": "活跃提名人", + "addressCopied": "地址已复制到剪贴板", + "automaticallyBonded": "将自动质押收益到现有的质押余额中", + "back": "返回", + "bond": "质押", + "bondAmount": "质押金额", + "bondedFunds": "己质押金额", + "cancel": "取消", "change": "更改", - "pool_nominations": "池的提名", + "controllerAccount": "Controller 账户", + "controllerAccountsDeprecated": "Controller帐户将逐渐被淘汰", + "controllerNotImported": "尚未导入Controller帐户。如果无法访问该帐户,请立即设置新帐户。否则,请将该账户导入扩展之一", + "earningRewards": "挣取收益中", + "inactiveNominations": "非活跃提名人", + "manage": "管理", + "minimumToEarnRewards": "最低收益抵押额", + "minimumToNominate": "最低提名质押额", + "noNominationsSet": "非活跃:未设置提名", + "nominate": "提名", + "nominating": "提名中", + "nominatingAnd": "提名并", "nominations": "提名", + "none": "无", + "notAssigned": "未分配", + "notEarningRewards": "非挣取收益状态", + "notNominating": "未提名", + "payoutDestination": "收益到账地址", + "payoutDestinationSubtitle": "选择如何接收收益。收益可以是复合式增长,也可以作为自由余额发回账户", + "pendingPayouts": "待付", + "poolDestroy": "提名池正在被摧毁,不能做提名操作", + "poolNominations": "池的提名", + "proxyprompt": "因本应用将很快取消对Controller帐户, 转而支持代理帐户. 请您尽快将Controller帐户切换到Stash帐户, 以便有更好体验", + "readOnly": "只读帐户无法签署交易", + "setNewController": "设置新Controller帐户", + "startNominating": "开始提名", + "status": "提名人状态", "stop": "停止", - "not_nominating": "未提名", - "syncing": "正在同步...", - "your_nominations": "您的提名名单", - "stop_nominating_selected": "停止指定选定验证人", - "add_from_favorites": "从收藏夹添加", - "pool_destroy": "提名池正在被摧毁,不能做提名操作", - "active_nominations": "活跃提名人", - "inactive_nominations": "非活跃提名人", - "minimum_active_bond": "最低活跃质押", - "total_supply_staked": "所有抵押比例", - "controller_not_imported": "尚未导入controller帐户。如果无法访问该帐户,请立即设置新帐户。否则,请将该账户导入扩展之一", - "nominate": "提名", - "start_nominating": "开始提名", - "bonded_funds": "己质押金额", - "status": "状态", - "no_nominations_set": "非活跃:未设置提名", - "nominating_and": "提名中和", - "earning_rewards": "挣取奖励中", - "not_earning_rewards": "非挣取奖励状态", - "waiting_for_active_nominations": "等待有效提名", - "reward_destination": "奖励钱包地址", - "not_assigned": "未分配", - "update": "更改", - "controller_account": "Controller 账户", - "bond": "质押", - "back_to_staking": "回到Staking", - "automatically_bonded": "将自动质押到现有的质押余额中", - "to_stash": "到Stash账户", - "sent_to_stash": "将作为余额发送到您的Stash帐户", - "to_controller": "到Controller账户", - "sent_to_controller": "将作为余额发送到您的Controller帐户", "summary": "总结", - "read_only": "只读帐户无法签署交易.", - "bond_amount": "质押金额:", - "back": "返回", - "cancel": "取消", - "search_account": "搜索帐户", - "none_of_your": "您的所有帐户都不足最低存款额", - "top_up_account": "为账户充值,使其有资格成为controller", - "select_a_controller": "您没有导入其他帐户。如要要选择controller,请导入另一个帐户余额至少为", - "set_controller_account": "设置Controller帐户" + "syncing": "正在同步", + "totalSupplyStaked": "所有抵押比例", + "unlocked": "已解锁", + "unstake": "解除抵押", + "unstakePromptInProgress_fast": "快速解除抵押正在进行中", + "unstakePromptInProgress_regular": "解除抵押正在进行中", + "unstakePromptInQueue": "您正在快速解除抵押队列中。当注册快速解除抵押时,您将无法执行任何提名人任务", + "unstakePromptReadyToWithdraw": "您的质押资金现已解锁并可提取", + "unstakePromptRevert": "如果您需要取消解除抵押, 请重新质押您的{{unit}}并再次开始提名", + "unstakePromptWaitingForUnlocks": "等待己解锁变为可提取", + "update": "更改", + "updateToStash": "将Controller帐户更新为Stash帐户", + "validator": "{{count}} 提名人", + "waitingForActiveNominations": "等待有效提名" + }, + "overview": { + "activeEra": "活跃 Era", + "activeNominators": "活跃提名人", + "activePools": "活跃提名池", + "addressCopied": "地址已复制到剪贴板", + "adjustedRewardsRate": "调整后收益率", + "afterInflation": "加入通货膨胀后", + "available": "可用", + "balance": "余额", + "bondedInPools": "提名池中当前质押的{{networkUnit}}总数", + "connect": "连接", + "free": "可用余额", + "historicalRewardsRate": "历史奖励率", + "inPool": "提名池中", + "inPools": "当前质押在提名池中", + "inflationRate": "通货膨胀率", + "locked": "己锁", + "manage": "管理", + "memberOf": "提名池", + "moreResources": "更多资讯", + "networkCurrentlyStaked": "{{total}} {{unit}} 目前正在{{network}}进行抵押", + "networkCurrentlyStakedSubtitle": "当前共有{{unit}} 在所有验证人和提名人之间做抵押", + "networkStats": "网络信息", + "noActiveAccount": "无活跃帐户", + "nominating": "提名中", + "notStaking": "无抵押", + "overview": "概述", + "pool": "提名池", + "poolMembersBonding": "名成员正在提名池活跃质押中", + "proxy": "代理账户", + "recentPayouts": "最近收益", + "reserve": "储备", + "reserveBalance": "预存金额", + "reserved": "己储备", + "start": "开始", + "subscanDisabled": "Subscan己断开", + "supplyStaked": "抵押比例", + "syncingStatus": "正在同步状态", + "timeRemainingThisEra": "本Era剩余时间", + "totalNominators": "提名人总数", + "totalNumAccounts": "已加入提名池的帐户总数", + "totalValidators": "所有验证人", + "unitSupplyStaked": "{{unit}} 抵押比例", + "unlocking": "正在解锁", + "updateReserve": "更新预存" + }, + "payouts": { + "deductedFromBond": "从质押里扣除", + "fromPool": "从提名池", + "lastEraPayout": "上Era收益", + "none": "无", + "notStaking": "无抵押", + "payout": "收益", + "payoutHistory": "收益记录", + "poolClaim": "提名池收益", + "recentPayouts": "最近收益", + "slashed": "除名", + "subscanDisabled": "Subscan己断开" }, "pools": { - "bond": "质押", - "pool_name": "提名池名称", - "pool_name_support": "提名池名称支持字符、符号和表情-发挥你的创意吧!", - "roles": "角色", - "assigned_to_any_account": " 您的<b>主理人</b>、<b>提名人</b>和<b>状态切换人</b>角色可以分配给任何帐户.", - "pool_creator": " 作为提名池创建者,您可使用提名池的<b>存款人</b>角色", - "summary": "总结", - "read_only": "只读帐户无法签署交易", - "bond_amount": "质押金额", - "nominations": "提名", + "activePools": "活跃提名池", + "address": "地址", + "addressCopied": "地址已复制到剪贴板", + "addressInvalid": "地址无效", + "allPools": "所有提名池", + "allRoles": "所有角色", "assigned": "己分配", - "create_pool": "创建提名池", - "create_a_pool": "创建提名池", + "assignedToAnyAccount": " 您的<b>主理人</b>、<b>提名人</b>和<b>守护人</b>角色可以分配给任何帐户。", + "availableToClaim": "成员可申领的奖励{{unit}}金额", "back": "返回", + "beenClaimed": "已被申领。", + "beenClaimedBy": "成员已申领奖励{{unit}}总数", + "bond": "质押", + "bondAmount": "质押金额", + "bondedFunds": "己质押金额", + "bouncer": "守护人", "cancel": "取消", - "fetching_favorite_pools": "正在获取收藏表中的提名池...", - "favorites_list": "收藏夹列表", - "no_favorites": "无收藏夹", - "generate_nominations": "生成提名", - "nominate": "提名", - "withdraw_funds": "取款", - "unbond_funds": "解除质押资金", - "been_claimed": "已被申领", - "been_claimed_by": "成员已申领奖励{{unit}}总数", - "available_to_claim": "成员可申领的奖励{{unit}}金额", - "outstanding_reward": "未申领奖励.", - "locked": "己锁", + "closePool": "可提取己解锁金额并关闭池", + "compound": "复利", + "create": "创建", + "createAPool": "创建提名池", + "createPool": "创建提名池", + "depositor": "存款人", + "destroyPool": "销毁提名池", "destroying": "销毁中", - "open": "打开", - "pool_state": "提名池状态", - "pool_members": "成员", - "total_bonded": "总绑定金额", - "pool_stats": "提名池信息", - "active_pools": "活跃提名池", - "minimum_create_bond": "最小建提名池质押", - "minimum_join_bond": "最低入提名池质押金", - "pool_membership": "提名池成员名单", - "not_in_pool": "不在提名池中", - "owner_of_pool": "提名池所有者", - "in_pool": "提名池中", + "earningRewards": "挣取收益中", + "edit": "编辑", + "favorites": "收藏夹", + "fetchingFavoritePools": "正在获取收藏表中的提名池", + "fetchingMemberList": "正在获取会员列表", + "generateNominations": "生成提名", + "inPool": "提名池中", + "inactivePoolNotNominating": "非活跃:提名池未提名任何验证人", + "join": "加入", "leave": "离开", + "leavingPool": "离开提名池中", + "leftThePool": "所有成员已离开", + "locked": "己锁", "manage": "管理", - "withdraw": "提取", - "inactive_pool_not_nominating": "非活跃:提名池未提名任何验证人", - "nominating_and": "提名中和", - "earning_rewards": "挣取奖励中", - "not_earning_rewards": "非挣取奖励状态", - "waiting_for_active_nominations": "等待有效提名", - "unclaimed_rewards": "待申领奖励", - "pool_status": "提名池状态", - "create": "创建", - "join": "加入", - "leaving_pool": "离开提名池中", - "member_of_pool": "提名池成员", - "destroy_pool": "销毁提名池", - "left_the_pool": "所有成员已离开", - "stop_nominating": "如需继续销毁池,请先停止提名", - "close_pool": "可提取己解锁金额并关闭池", - "unbond_your_funds": "可解锁资金质押", - "withdraw_unlock": "请取出己解锁金额以继续关闭池", - "unbond": "解除质押", - "overview": "概述", + "managementOptions": "选择管理选项", + "memberOfPool": "提名池成员", "members": "成员", - "all_pools": "所有提名池", - "favorites": "收藏夹", + "minimumToCreatePool": "最低建提名池质押金", + "minimumToJoinPool": "最低入提名池质押金", + "noFavorites": "无收藏夹", + "nominate": "提名", + "nominating": "提名中", + "nominatingAnd": "提名中和", + "nominator": "提名人", + "notEarningRewards": "非挣取收益状态", + "notInPool": "不在提名池中", + "notSet": "未设置", + "open": "打开", + "outstandingReward": "未申领奖励", + "overview": "概述", + "ownerOfPool": "提名池所有者", + "permissionToUnbond": "您有权解除和取出提名池里任何成员的资金。使用成员菜单", + "poolCommission": "提名池佣金", + "poolCreator": " 作为提名池创建者,您可使用提名池的<b>存款人</b>角色", + "poolCurrentlyLocked": "该提名池当前正处于锁定状态", + "poolInDestroyingState": "该提名池正处于销毁状态", + "poolMembers": "成员", + "poolMembership": "提名池成员名单", + "poolName": "提名池名称", + "poolNameSupport": "提名池名称支持字符、符号和表情-发挥你的创意吧!", + "poolState": "提名池状态", + "poolStats": "提名池信息", + "poolStatus": "提名池状态", "pools": "提名池", - "all_roles": "所有角色", - "bonded_funds": "己质押金额", - "pool_member": "{{count}} 个成员", - "pool_currently_locked": "该提名池当前正处于锁定状态", - "permission_to_unbond": "您有权解除和取出提名池里任何成员的资金。使用成员菜单", - "management_options": "选择管理选项.", - "pool_in_destroying_state": "该提名池正处于销毁状态", - "not_set": "未设置", - "address_invalid": "地址无效", + "readOnly": "只读帐户无法签署交易", "reformatted": "地址已重新格式化", - "save": "保存", - "edit": "编辑", + "roles": "角色", "root": "主理人", - "depositor": "存款人", - "nominator": "提名人", - "state_toggler": "状态切换人" - }, - "community": { - "fetching_validators": "正在获取验证人信息...", - "no_validators": "不包含验证人", - "connecting": "连接中...", - "go_back": "返回", - "website": "个人网站", - "email": "邮箱", - "bio": "简介", - "validators": "的验证人", - "validator": "{{count}} 个验证人" - }, - "payouts": { - "pool_claim": "提名池收益", - "payout": "收益", - "slashed": "除名", - "none": "无", - "recent_payouts": "最近收益", - "subscan_disabled": "Subscan己断开", - "last_era_payout": "上Era收益", - "payout_history": "收益记录", - "not_staking": "无抵押", - "from_pool": "从提名池", - "deducted_from_bond": "从质押里扣除" + "save": "保存", + "stopNominating": "如需继续销毁池,请先停止提名", + "summary": "总结", + "totalBonded": "总绑定金额", + "unbond": "解除质押", + "unbondFunds": "解除质押资金", + "unbondYourFunds": "可解锁资金质押", + "unclaimedRewards": "待申领奖励", + "unlocked": "已解锁", + "validator": "{{count}} 提名人", + "waitingForActiveNominations": "等待有效提名", + "withdraw": "提取", + "withdrawFunds": "取款", + "withdrawUnlock": "请取出己解锁金额以继续关闭池" }, "validators": { - "average_commission": "平均佣金", - "active_validators": "活跃验证人", - "total_validators": "所有验证人", - "network_validators": "验证人列表", - "connecting": "连接中...", - "fetching_validators": "正在获取验证人信息..." + "activeValidators": "活跃验证人", + "allValidators": "所有验证人", + "averageCommission": "平均佣金", + "connecting": "连接中", + "favoriteValidators": "收藏夹中的验证人", + "favorites": "收藏夹", + "fetchingFavoriteValidators": "正在获取收藏夹的验证人", + "fetchingValidators": "正在获取验证人信息", + "networkValidators": "验证人列表", + "noFavorites": "无收藏夹", + "totalValidators": "所有验证人", + "validators": "验证人" } } } diff --git a/src/locale/cn/tips.json b/src/locale/cn/tips.json index 6563b065ff..49b7b0329b 100644 --- a/src/locale/cn/tips.json +++ b/src/locale/cn/tips.json @@ -1,56 +1,43 @@ { "tips": { - "module": { - "tips": "提示", - "one_moment": "请稍等...", - "syncing_with": "同步 {{network}}", - "more": "更多", - "close": "关闭", - "dismiss_tips": "解除提示", - "dismiss_result": "在该页面中去除提示窗口.", - "re-enable": "提示可以通过侧菜单左下角的齿轮图标访问从设置中重新启用.", - "disable_dashboard_tips": "除去提示", - "cancel": "取消", - "of": "/" - }, - "connect_extensions": [ + "connectExtensions": [ "连接扩展", - "连接您的帐户,开始使用Cere staking.", + "连接您的帐户,开始使用Cere Staking Dashboard.", [ - "连接您的帐户,开始使用Cere staking应用.", + "连接您的帐户,开始使用Cere Staking Dashboard.", "通过充当钱包的网络扩展访问账户。您的钱包用于签署在应用中提交的交易.", "从应用中右上角的“连接”按钮连接您的钱包,然后选择您希望用于抵押的帐户继续.", "该应用支持一系列扩展和钱包." ] ], - "recommended_nominator": [ - "推荐:成为提名人", - "您有足够的{NETWORK_UNIT}成为提名人.", - [ - "您有足够的{NETWORK_UNIT}成为提名人并开始赚取奖励.", - "然而,直接提名确实需要您主动检查提名的验证人." - ] - ], - "recommended_join_pool": [ - "推荐: 加入池", - "您的帐户最适合加入提名池.", - [ - "根据您的帐户当前持有的{NETWORK_UNIT}金额,加入提名池是您开始抵押的最佳方式.", - "加入提名池需要最低存款{MIN_POOL_JOIN_BOND} {NETWORK_UNIT}." - ] - ], - "how_to_stake": [ + "howToStake": [ "您会如何抵押?", "要么成为提名人,要么成为提名池成员.", [ "有多种方式抵押 {NETWORK_NAME}.", "要么成为提名人,要么成为提名池成员.", - "直接提名要求您质押{NETWORK_UNIT}并选择要提名的验证人,并提名最多{FallbackMaxNominations} 个验证人。要作为提名人获得奖励,你目前至少需要有{MIN_ACTIVE_BOND} {NETWORK_UNIT}.", + "直接提名要求您质押{NETWORK_UNIT}并选择要提名的验证人,并提名最多{MAX_NOMINATIONS} 个验证人。要作为提名人获得奖励,你目前至少需要有{MIN_ACTIVE_STAKE} {NETWORK_UNIT}.", "加入提名池要便宜得多,需要最低存款 {MIN_POOL_JOIN_BOND} {NETWORK_UNIT}. 提名验证人是代表您完成的,您只需从提名池中领取奖励.", "创建提名池至少需要{MIN_POOL_CREATE_BOND} {NETWORK_UNIT}." ] ], - "managing_nominations": [ + "joinAnotherPool": [ + "加入另一个提名池", + "切换到其它帐户以加入其它提名池.", + [ + "{NETWORK_NAME}上的每个帐户只能加入一个提名池. 若要加入更多提名池,请首先切换到另一个帐户." + ] + ], + "keepPoolNominating": [ + "管理您的提名池", + "保持您的提名池活跃地提名很重要.", + [ + "保持您的提名池活跃地提名很重要.", + "如果您的提名池没有赚取奖励,会员将离开并加入另一个提名池.", + "您的主理人和提名人角色对于管理您的提名非常重要。如果提名表现不佳,请选择不同的提名人,以增加您的提名池获得奖励的机会." + ] + ], + "managingNominations": [ "管理提名", "务必定期检查提名人的表现.", [ @@ -58,7 +45,20 @@ "为了最大化验证人的多样性和分散混合,请从一系列实体中选择验证人." ] ], - "monitoring_pool": [ + "module": { + "cancel": "取消", + "close": "关闭", + "disableTips": "取消提示", + "dismissResult": "在该页面中取消提示窗口", + "dismissTips": "解除提示", + "more": "更多", + "of": "/", + "oneMoment": "请稍等", + "reEnable": "提示可以通过侧菜单左下角的齿轮图标访问从设置中重新启用", + "syncingWith": "同步 {{network}}", + "tips": "提示" + }, + "monitoringPool": [ "管理提名池成员", "最好定期检查您的提名池是否正在活跃地赚取奖励.", [ @@ -66,23 +66,23 @@ "监控您的提名池状态,以确保其正常运行,如果您没有收到任何奖励,请考虑加入另一个提名池." ] ], - "join_another_pool": [ - "加入另一个提名池", - "切换到其它帐户以加入其它提名池.", + "recommendedJoinPool": [ + "推荐: 加入池", + "您的帐户最适合加入提名池.", [ - "{NETWORK_NAME}上的每个帐户只能加入一个提名池. 若要加入更多提名池,请首先切换到另一个帐户." + "根据您的帐户当前持有的{NETWORK_UNIT}金额,加入提名池是您开始抵押的最佳方式.", + "加入提名池需要最低存款{MIN_POOL_JOIN_BOND} {NETWORK_UNIT}." ] ], - "keep_pool_nominating": [ - "管理您的提名池", - "保持您的提名池活跃地提名很重要.", + "recommendedNominator": [ + "推荐:成为提名人", + "您有足够的{NETWORK_UNIT}成为提名人.", [ - "保持您的提名池活跃地提名很重要.", - "如果您的提名池没有赚取奖励,会员将离开并加入另一个提名池.", - "您的主理人和提名人角色对于管理您的提名非常重要。如果提名表现不佳,请选择不同的提名人,以增加您的提名池获得奖励的机会." + "您有足够的{NETWORK_UNIT}成为提名人并开始赚取奖励.", + "然而,直接提名确实需要您主动检查提名的验证人." ] ], - "reviewing_payouts": [ + "reviewingPayouts": [ "查看收益", "定期审查您的奖金是衡量你的提名表现如何的好方法.", [ @@ -90,11 +90,11 @@ "转到“收益”页面,获取您收到的每一笔付款的详细明细,以及付款来自哪个验证人(或提名池)." ] ], - "understanding_validator_performance": [ + "understandingValidatorPerformance": [ "测量验证人性能", "各种因素会影响验证人的奖励金额.", [ - "各种因素都会影响验证人获得多少奖励,比如它产生的era点数、有多少提名者支持它,以及它是否被超额认购.", + "各种因素都会影响验证人获得多少奖励,比如它产生的Era点数、有多少提名者支持它,以及它是否被超额认购.", "所有这些指标都会随着时间而变化,有时会以不可预测的方式发生变化。因此,提名人积极监控验证人及其表现非常重要.", "该应用提供了一系列指标,帮助您了解验证人的性能." ] diff --git a/src/locale/en/base.json b/src/locale/en/base.json index a45b1cefc6..60d343dae8 100644 --- a/src/locale/en/base.json +++ b/src/locale/en/base.json @@ -1,19 +1,62 @@ { "base": { - "stake": "Stake", - "validators": "Validators", - "overview": "Overview", - "nominate": "Nominate", - "pools": "Pools", - "payouts": "Payouts", + "active": "Active", + "allowAll": "Allow All", + "allowAnyoneCompound": "Allow anyone to compound rewards on your behalf.", + "allowAnyoneCompoundWithdraw": "Allow anyone to compound or withdraw rewards on your behalf.", + "allowAnyoneWithdraw": "Allow anyone to withdraw rewards on your behalf.", + "allowCompound": "Allow Compound", + "allowWithdraw": "Allow Withdraw", "community": "Community", - "favorites": "Favorites", - "network": "Network", "feedback": "Feedback", + "goTo": "Go To", "help": "Help", "inactive": "Inactive", - "active": "Active", + "network": "Network", + "nominate": "Nominate", + "overview": "Overview", + "payee": { + "account": { + "subtitle": "Send payouts to another account as free balance.", + "title": "To Another Account" + }, + "none": { + "subtitle": "Have no payout destination set.", + "title_active": "Not Assigned", + "title_default": "None" + }, + "staked": { + "subtitle": "Add payouts to your existing staked balance automatically.", + "title_active": "Compounding", + "title_default": "Compound" + }, + "stash": { + "subtitle": "Payouts are sent to your account as free balance.", + "title": "To Your Account" + } + }, + "payouts": "Payouts", + "pools": "Pools", + "resources": "Resources", + "stake": "Stake", "support": "Support", - "resources": "Resources" + "time": { + "day_one": "day", + "day_other": "days", + "hour_one": "hour", + "hour_other": "hours", + "hr_one": "hr", + "hr_other": "hrs", + "min_one": "min", + "min_other": "mins", + "minute_one": "minute", + "minute_other": "minutes", + "second_one": "second", + "second_other": "seconds" + }, + "title_kusama": "Kusama Staking Dashboard", + "title_polkadot": "Polkadot Staking Dashboard", + "title_westend": "Westend Staking Dashboard", + "validators": "Validators" } } diff --git a/src/locale/en/help.json b/src/locale/en/help.json index 6377656757..3bd95aa07d 100644 --- a/src/locale/en/help.json +++ b/src/locale/en/help.json @@ -1,131 +1,176 @@ { "help": { - "modal": { - "help_resources": "Help Resources", - "all_resources": "All Resources", - "close": "Close", - "definitions": "Definitions", - "articles": "Articles", - "related": "Related" - }, "definitions": { - "dashboard_tips": [ - "Dashboard Tips", + "activeNominators": [ + "Active Nominators", [ - "Staking dashboard will present you tips to help you along each step of staking on Cere Network.", - "Tips can be turned off or re-enabled from dashboard settings, that can be accessed via the cog icon in the bottom left corner of the side menu." + "Nominators who are active in the current session.", + "Being an active nominator does not guarantee rewards, as your nominees may be oversubscribed." ] ], - "supply_staked": [ - "Supply Staked", + "activePools": [ + "Active Pools", + ["The current amount of active nomination pools on {NETWORK_NAME}."] + ], + "activeStakeThreshold": [ + "Active Stake Threshold", [ - "The current cumulative supply of {NETWORK_UNIT} being staked globally.", - "The percentage of staked {NETWORK_UNIT} is relative to the total supply of {NETWORK_UNIT}." + "The amount of {NETWORK_UNIT} needed to be actively nominating in an era.", + "This value applies to nominators and for pools. In the pool's case, it is important to join a pool with a total bond amount of at least this value.", + "Being above this metric simply guarantees that you will be present in the active nominator set for the era. This amount still does not guarantee rewards, as your active nominations may still be over-subscribed.", + "Only the top {MAX_NOMINATOR_REWARDED_PER_VALIDATOR} nominators are rewarded per validator in {NETWORK_NAME}. Ensuring your active bond is above this threshold will increase your chances of rewards.", + "You can keep track of these metrics from the dashboard and amend your staking position if necessary, whether increasing your bonded {NETWORK_UNIT}, changing your nominations, or joining another pool." ] ], - "total_nominators": [ - "Total Nominators", + "activeValidator": [ + "Active Validator", [ - "Accounts who are staking in the network, regardless of whether they are active or inactive in the current session.", - "In order to stake {NETWORK_UNIT}, you can either become a nominator or join a pool - that act as nominators themselves." + "A validator that is actively validating blocks. Rewards are accumulated based on the validator's activity.", + "A new set of validators are chosen for each era, so there is no guarantee the same validator will be active in subsequent eras.", + "{NETWORK_NAME} allows a nominator to nominate up to 16 validators, maximising your chances of nominating an active validator in each era." ] ], - "active_nominators": [ - "Active Nominators", + "adjustedRewardsRate": [ + "Adjusted Rewards Rate", [ - "Nominators who are active in the current session.", - "Being an active nominator does not guarantee rewards, as your nominees may be oversubscribed." + "An estimated realized annual yield based on the {NETWORK_NAME} reward distribution model.", + "This figure is effectively the historical rewards rate minus the inflation rate." ] ], - "your_balance": [ - "Your Balance", + "averageCommission": [ + "Average Commission", [ - "Your balance represents the total {NETWORK_UNIT} you have available in addition to your total staked amount, that includes the amount you have bonded in a Pool.", - "Unlike your staked balance, your bonded pool balance is held and locked in the pool itself." + "The average validator commission rate on {NETWORK_NAME}.", + "This metric excludes validators who host a 100% commission, as these nodes usually block nominations and are run for the purposes of staking on central exchange platforms." ] ], - "reserve_balance": [ - "Reserve Balance", + "blockedNominations": [ + "Blocked Nominations", [ - "In {NETWORK_NAME}, you must have a balance above a certain amount for your account to exist on-chain. This amount is called your 'existential deposit'.", - "Staking dashboard ensures that this amount of {NETWORK_UNIT} is never touched." + "When a validator has blocked nominations, nominators are unable to nominate them." ] ], - "network_stats": [ - "Network Stats", + "bondedInPool": [ + "Bonded in Pool", [ - "Real time network statistics that may affect your staking positions.", - "Keep up to date on the state of the network from your overview." + "The amount of {NETWORK_UNIT} currently bonded in a pool.", + "Unlike nominating directly, where your bonded funds remain in your account but become locked, the {NETWORK_UNIT} you bond to a pool is transferred to the pool's stash account. Nonetheless, pool members still have access to unbond their funds at any time." ] ], - "inflation": [ - "Inflation", + "bonding": [ + "Bonding", [ - "{NETWORK_UNIT} is inflationary; there is no maximum number of {NETWORK_UNIT}.", - "Inflation is designed to be approximately 10% annually, with validator rewards being a function of the amount staked and the remainder going to treasury." + "Bonding funds is the process of 'locking' (or staking) {NETWORK_UNIT}. Bonded {NETWORK_UNIT} will then be automatically allocated to one or more of your nominated validators.", + "As a nominator, you allocate nominations yourself. In a pool, the pool owner or pool nominator will allocate nominations on your behalf, and your bonded funds will back those nominations.", + "The minimum to earn rewards statistic is the minimum {NETWORK_UNIT} being bonded by a nominator for the current era. This value is also the minimum amount required to receive rewards." + ] + ], + "commission": [ + "Commission", + [ + "Validators can take a percentage of the rewards they earn. This chunk is called their commission.", + "Nominating validators with low commissions mean you will receive a larger share of the rewards they generate.", + "Many validators will have a commission rate of 100%, meaning you will receive no rewards by nominating these validators.", + "Examples of such validators include those operating on behalf of exchanges, where nominating and reward distribution is done centrally on the exchange in question.", + "A validator can update their commission rates as and when they please, and such changes will have an impact on your profitability. Be sure to monitor your nominations on this dashboard to keep updated on their commission rates." + ] + ], + "epoch": [ + "Epoch", + [ + "An epoch is another name for a session in {NETWORK_NAME}. Each era is divided into 6 epochs during which validators are assigned as block producers to specific time frames or slots.", + "1 epoch is currently 4 hours in Cere Network." ] ], - "historical_rewards_rate": [ + "era": [ + "Era", + [ + "At the end of each era, validators are rewarded {NETWORK_UNIT} based on how many era points they accumulated in that era. This {NETWORK_UNIT} reward is then distributed amongst the nominators of the validator via a payout.", + "1 era is currently 24 hours in Cere Network." + ] + ], + "eraPoints": [ + "Era Points", + [ + "Era Points are accumulated by validators during each era, and depend on a validator's performance.", + "As a staker, you do not need to worry about Era Points. In general, better performing validators produce more Era Points, which in-turn lead to higher staking rewards." + ] + ], + "historicalRewardsRate": [ "Historical Rewards Rate", [ "An estimated annual yield based on the {NETWORK_NAME} reward distribution model." ] ], - "ideal_staked": [ + "idealStaked": [ "Ideal Staked", ["The percentage of staked total supply in ideal network conditions."] ], - "nomination_status": [ - "Nomination Status", + "inactiveNominations": [ + "Inactive Nominations", [ - "The status of your nominations at a glance.", - "A set of nominations will be inactive when none of those nominees are participating in the current validator set (the set of validators currently elected to validate the network).", - "When at least one of your nominees are active, this nomination status will display as actively nominating - but this still does not guarantee rewards.", - "The top {FallbackNominatorRewardedPerValidator} nominators of each active validator receive rewards on {NETWORK_NAME}. So if a nominee is active and over-subscribed, you must be a part of the {FallbackNominatorRewardedPerValidator} highest bonded nominators to receive rewards.", - "If an active nominee is not over-subscribed, you will receive rewards." + "Nominations that are in the active validator set for the current era, but bonded funds have not been assigned to these nominations." ] ], - "stash_and_controller_accounts": [ - "Stash and Controller Accounts", + "inflation": [ + "Inflation", [ - "The Stash and Controller are simply {NETWORK_NAME} accounts that manage your staking activity.", - "Your Stash account is the account used to hold your staked funds, whereas the Controller account is used to carry out Staking actions on the Stash account's behalf.", - "When you switch accounts in this app, you are actually switching your Stash account. Your Controller account is then automatically fetched for you.", - "This app assumes you have both Stash and Controller accounts imported. If you do not, you will not be able to use all app functions.", - "You can assign a different Controller account on the Stake page." + "{NETWORK_UNIT} is inflationary; there is no maximum number of {NETWORK_UNIT}.", + "Inflation is designed to be approximately 10% annually, with validator rewards being a function of the amount staked and the remainder going to treasury." ] ], - "controller_account_eligibility": [ - "Controller Account Eligibility", + "lastEraPayout": [ + "Last Era Payout", [ - "For an account to become a controller, it must have a balance of at least the existential deposit. On {NETWORK_NAME} the existential deposit amount is {EXISTENTIAL_DEPOSIT} {NETWORK_UNIT}.", - "If an account does not have at least this amount, you will see the 'Not Enough {NETWORK_UNIT}' message overlaying it.", - "Topping up an account with at least {EXISTENTIAL_DEPOSIT} {NETWORK_UNIT} will make it eligible and selectable as a controller." + "The total amount of {NETWORK_UNIT} paid out for the last active era.", + "Payouts are distributed evenly amongst the active validators for that era, and are then further distributed to the active nominators that took part in that era.", + "The payout amounts received depend on how much {NETWORK_UNIT} the nominators, and validators themselves, had bonded for that era." ] ], - "bonding": [ - "Bonding", + "ledgerHardwareWallets": [ + "Ledger Hardware Wallets", [ - "Bonding funds is the process of 'locking' (or staking) {NETWORK_UNIT}. Bonded {NETWORK_UNIT} will then be automatically allocated to one or more of your nominated validators.", - "As a nominator, you allocate nominations yourself. In a pool, the pool owner or pool nominator will allocate nominations on your behalf, and your bonded funds will back those nominations.", - "The minimum active bond statistic is the minimum {NETWORK_UNIT} being bonded by a nominator for the current era.This value is also the minimum amount required to receive rewards." + "Compatible Ledger devices are fully supported on staking dashboard. Use your Ledger to manage your staking activity on {NETWORK_NAME}.", + "Using a hardware wallet ensures a secure experience, whereby your keys stay on the hardware device at all times. This is also known as cold storage, as there is no active internet connection to your wallet.", + "In order to import and sign transactions with your Ledger device, your device must have the {NETWORK_NAME} Ledger app installed. The {NETWORK_NAME} app can be installed via Ledger's very own Ledger Live application.", + "Both USB and Bluetooth connections are supported in staking dashboard. So if your Ledger is bluetooth enabled, and your device allows bluetooth connections, you will be able to import accounts and sign transactions wirelessly through your Ledger device.", + "Find more information about Ledger at their official website, ledger.com." ] ], - "active_bond_threshold": [ - "Active Bond Threshold", + "ledgerRejectedTransaction": [ + "Ledger Rejected Transaction", [ - "The amount of {NETWORK_UNIT} needed to be actively nominating in an era. ", - "This value applies to nominators and for pools. In the pool's case, it is important to join a pool with a total bond amount of at least this value.", - "Being above this metric simply guarantees that you will be present in the active nominator set for the era. This amount still does not guarantee rewards, as your active nominations may still be over-subscribed.", - "Only the top {FallbackNominatorRewardedPerValidator} nominators are rewarded per validator in {NETWORK_NAME}. Ensuring your active bond is above this threshold will increase your chances of rewards.", - "You can keep track of these metrics from the dashboard and amend your staking position if necessary, whether increasing your bonded {NETWORK_UNIT}, changing your nominations, or joining another pool." + "The transaction was rejected on the Ledger device and will not be submitted.", + "Submit a transaction by approving it on the Ledger device." + ] + ], + "ledgerRequestTimeout": [ + "Ledger Request Timeout", + [ + "Timeout errors can occur when the Ledger device is busy, or when an operation fails to complete in time.", + "Try performing the operation again, and if the error persists, try restarting the Ledger device." + ] + ], + "lockedBalance": [ + "Locked Balance", + [ + "In {NETWORK_NAME} some actions require your balance to be locked, such as staking or voting on governance. If some of your balance is locked, it cannot be transferred.", + "The amount of locked balance displayed on staking dashboard represents all your locks apart from the staking lock - hence displaying all the locked balance at your disposal that you can use to nominate.", + "You are able to nominate with any remaining locked balance, but you are not able to join a pool with it, as doing so requires a transfer to the pool account." + ] + ], + "minimumToCreatePool": [ + "Minimum To Create Pool", + [ + "The minimum amount of {NETWORK_UNIT} needed to bond in order to create a pool.", + "Creating a pool requires a larger deposit than that of joining a pool." ] ], - "reward_destination": [ - "Reward Destination", + "minimumToJoinPool": [ + "Minimum To Join Pool", [ - "Your reward destination is where your rewards are sent to.", - "Rewards can be automatically bonded on top of your current bond, or they can be sent to your stash, controller, or an external account of your choosing." + "The minimum amount of {NETWORK_UNIT} needed to bond in order to join a pool.", + "This amount is different from the bond needed to create a pool." ] ], "nominating": [ @@ -135,47 +180,108 @@ "Once you have nominated your selected validators, they become your nominations." ] ], + "nominationPools": [ + "Nomination Pools", + [ + "Nomination pools allow users to contribute {NETWORK_UNIT} and earn staking rewards.", + "Unlike nominating, staking using pools requires a small amount of {NETWORK_UNIT}, and the pool manages nominees on your behalf." + ] + ], + "nominationStatus": [ + "Nomination Status", + [ + "The status of your nominations at a glance.", + "A set of nominations will be inactive when none of those nominees are participating in the current validator set (the set of validators currently elected to validate the network).", + "When at least one of your nominees are active, this nomination status will display as actively nominating - but this still does not guarantee rewards.", + "The top {MAX_NOMINATOR_REWARDED_PER_VALIDATOR} nominators of each active validator receive rewards on {NETWORK_NAME}. So if a nominee is active and over-subscribed, you must be a part of the {MAX_NOMINATOR_REWARDED_PER_VALIDATOR} highest bonded nominators to receive rewards.", + "If an active nominee is not over-subscribed, you will receive rewards." + ] + ], "nominations": [ "Nominations", [ - "Nominations are the validators a staker chooses to nominate. You can nominate up to {FallbackMaxNominations} validators on {NETWORK_NAME}.", + "Nominations are the validators a staker chooses to nominate. You can nominate up to {MAX_NOMINATIONS} validators on {NETWORK_NAME}.", "For nomination pools, the pool owner and pool nominator are in charge of nominating validators on behalf of all the pool members.", - "Once nominations have been submitted, bonded funds are automatically distributed to nominees that are active in the curernt era.", + "Once nominations have been submitted, bonded funds are automatically distributed to nominees that are active in the current era.", "As long as at least one of your nominations is actively validating in a session, your funds will be backing that validator." ] ], - "inactive_nominations": [ - "Inactive Nominations", + "nominatorStake": [ + "Nominator Stake", [ - "Nominations that are in the active validator set for the current era, but bonded funds have not been assigned to these nominations." + "The amount of {NETWORK_UNIT} backing the validator from its nominators.", + "This value is added to the validator's self stake to form the total stake of the validator. Note that this value changes every era as the bonded funds of nominators are re-distributed to the active validators of that session." ] ], - "nomination_pools": [ - "Nomination Pools", + "openAppOnLedger": [ + "Open App On Ledger", [ - "Nomination pools allow users to contribute {NETWORK_UNIT} and earn staking rewards.", - "Unlike nominating, staking using pools requires a small amount of {NETWORK_UNIT}, and the pool manages nominees on your behalf." + "This message occurs when the {NETWORK_NAME} Ledger app is not currently open on the connected device.", + "Open the {NETWORK_NAME} app on the Ledger device and try again." ] ], - "active_pools": [ - "Active Pools", - ["The current amount of active nomination pools on {NETWORK_NAME}."] + "overSubscribed": [ + "Over Subscribed", + [ + "Only the top {MAX_NOMINATOR_REWARDED_PER_VALIDATOR} nominators for each validator are rewarded in {NETWORK_NAME}. When this number is surpassed, this validator is considered over subscribed." + ] + ], + "payout": [ + "Payout", + [ + "Payouts are staking rewards on {NETWORK_NAME}. They depend on how many 'Era Points' your nominated validators accrue over time. Rewards are determined at the end of every Era (24 hour periods).", + "To receive staking rewards, a Payout needs to be requested. Any nominator backing the validator in question can request a Payout.", + "One payout request triggers the reward payout for every nominator." + ] ], - "minimum_join_bond": [ - "Minimum Join Bond", + "payoutDestination": [ + "Payout Destination", [ - "The minimum amount of {NETWORK_UNIT} needed to bond in order to join a pool.", - "This amount is different from the bond needed to create a pool." + "Your payout destination determines to which account your payouts are sent to.", + "Payouts can be automatically bonded on top of your current bond, or they can be sent to your stash, controller, or an external account of your choosing." ] ], - "minimum_create_bond": [ - "Minimum Create Bond", + "payoutHistory": [ + "Payout History", [ - "The minimum amount of {NETWORK_UNIT} needed to bond in order to create a pool.", - "Creating a pool requires a larger deposit than that of joining a pool." + "Historical records of payouts made for being an active nominator.", + "Requesting payouts is a manual process, so you may receive payouts for multiple eras in quick succession or in a sporadic fashion. Your payout graphs may therefore have multiple payouts occur on the same day, or have days where there were no payouts.", + "This does not mean that you were not nominating or generating rewards in that period - only that the payout for that period was not yet made." ] ], - "pool_membership": [ + "polkadotVault": [ + "Cere Vault", + [ + "Cere Vault (formerly Parity Signer) is a cold storage solution that allows you to use a phone in airplane mode as an air-gapped wallet.", + "The Vault app is not technically a wallet, as it does not allow to transfer funds.", + "It is more of a key-chain tool that will enable you the create, manage, and restore accounts." + ] + ], + "poolCommissionChangeRate": [ + "Pool Commission Change Rate", + [ + "The commission change rate is set by the pool Root, and dictates by how much and how often commission can be increased.", + "The maximum increase of the change rate dictates how much the commission rate can be increaesd in a single update. The minimum delay dictates how many blocks must pass before the commission rate can be increased again.", + "Once an initial change rate is set, only more restrictive values can be set thereafter. More restrictive values comprise of a smaller max increase and larger minimum delay." + ] + ], + "poolCommissionRate": [ + "Pool Commission Rate", + [ + "The commission rate of the pool. This value is set by the pool Root, and is the percentage of rewards the pool will take from its members.", + "A payee account must also be provided to receive the commission.", + "The commission rate is taken from the pool's rewards before they are distributed to its members." + ] + ], + "poolMaxCommission": [ + "Pool Max Commission", + [ + "The maximum commission rate a pool owner can set for the pool.", + "This value is set by the pool Root, and is the maximum percentage of rewards the pool will take from its members.", + "Once an initial maximum commission is set, only more restrictive values can be set thereafter." + ] + ], + "poolMembership": [ "Pool Membership", [ "Your pool membership status reflects whether you are a member of a pool.", @@ -184,14 +290,7 @@ "To leave a pool, you simply need to unbond and withdraw all your bonded {NETWORK_UNIT}. Staking dashboard provides a dedicated Leave button to unbond from a pool." ] ], - "bonded_in_pool": [ - "Bonded in Pool", - [ - "The amount of {NETWORK_UNIT} currently bonded in a pool.", - "Unlike nominating directly, where your bonded funds remain in your account but become locked, the {NETWORK_UNIT} you bond to a pool is transferred to the pool's stash account. Nonetheless, pool members still have access to unbond their funds at any time." - ] - ], - "pool_rewards": [ + "poolRewards": [ "Pool Rewards", [ "The amount of {NETWORK_UNIT} generated by being an active participant in a pool.", @@ -199,131 +298,114 @@ "Users have 2 choices for claiming rewards. They can be bonded back into the pool, that will increase your share of the pool and accumulate further rewards. Rewards can also be withdrawn from the pool to your account as free {NETWORK_UNIT}." ] ], - "pool_roles": [ + "poolRoles": [ "Pool Roles", [ "A pool consists of 4 roles, each of which having different responsibilities in managing the running of the pool.", - "Root: Can change the nominator, state-toggler, or itself. Further, it can perform any of the actions the nominator or state-toggler can.", + "Root: Can change the nominator, bouncer, or itself. Further, it can perform any of the actions the nominator or bouncer can.", "Depositor: Creates the pool and is the initial member. The depositor can only leave the pool once all other members have left. Once they leave by withdrawing, the pool is fully removed from the system.", "Nominator: Can select the validators the pool nominates.", - "State-Toggler: Can change the pool's state and kick (permissionlessly unbond/withdraw) members if the pool is blocked." + "Bouncer: Can change the pool's state and kick (permissionlessly unbond/withdraw) members if the pool is blocked." ] ], - "validator": [ - "Validator", + "proxyAccounts": [ + "Proxy Accounts", [ - "An entity that validates blocks for the {NETWORK_NAME} Relay Chain. Validators play a key role in {NETWORK_NAME} to secure the network and produce blocks.", - "As a nominator, you choose which validators you wish to back, and receive rewards for doing so." + "Proxy accounts are accounts that act on behalf of another account.", + "In proxy account terms, the proxy account is called the delegate account, and the proxied account it is acting on behalf of is called the delegator account.", + "Staking dashboard allows delegates to sign transactions on behalf of a delegator. If a delegate exists in the dashboard without its delegator, the delegator is imported as a read only account automatically." ] ], - "active_validator": [ - "Active Validator", + "readOnlyAccounts": [ + "Read Only Accounts", [ - "A validator that is actively validating blocks. Rewards are accumulated based on the validator's activity.,'A new set of validators are chosen for each era, so there is no guarantee the same validator will be active in subsequent eras.", - "{NETWORK_NAME} allows a nominator to nominate up to 16 validators, maximising your chances of nominating an active validator in each era." + "Read Only accounts are accounts that can be imported, but are not able to sign transactions. This means that you can view the account's balance and staking information, but you cannot perform any staking actions with it." ] ], - "average_commission": [ - "Average Commission", - [ - "The average validator commission rate on {NETWORK_NAME}.", - "This metric excludes validators who host a 100% commission, as these nodes usually block nominations are are run for the purposes of staking on central exchange platforms." - ] - ], - "era": [ - "Era", + "reserveBalance": [ + "Reserve Balance", [ - "At the end of each era, validators are rewarded {NETWORK_UNIT} based on how many era points they accumulated in that era. This {NETWORK_UNIT} reward is then distributed amongst the nominators of the validator via a payout.", - "1 era is currently 24 hours in Cere Network." + "In {NETWORK_NAME}, you must have a balance above a certain amount for your account to exist on-chain. This amount is called your 'existential deposit'.", + "Staking dashboard ensures that this amount of {NETWORK_UNIT} is never touched." ] ], - "epoch": [ - "Epoch", + "reserveBalanceForExistentialDeposit": [ + "Reserve Balance for Existential Deposit", [ - "An epoch is another name for a session in {NETWORK_NAME}. A different set of validators are selected to validate blocks at the beginning of every epoch.", - "1 epoch is currently 4 hours in Cere Network." + "If your account already has locked balance that covers your account's existential deposit, such as balance locked for nominating, then staking dashboard will not lock any additional free balance.", + "In this scenario, \"None\" is displayed under the \"Reserve For Existential Deposit\" section in your Reserve Balance settings." ] ], - "era_points": [ - "Era Points", + "rewardsByCountryAndNetwork": [ + "Rewards By Country And Network", [ - "Era Points are accumulated by validators during each era, and depend on a validator's performance.As a staker, you do not need to worry about Era Points. In general, better performing validators produce more Era Points, which in-turn lead to higher staking rewards." + "Shows the estimated percentage of rewards that are generated from different countries and IP networks.", + "Polkawatch decentralization analytics are computed by aggregating rewards by the IP Geolocation of the Validator. The sample period is the last 60 days." ] ], - "self_stake": [ + "selfStake": [ "Self Stake", [ "The amount of {NETWORK_UNIT} the validator has bonded themselves.", "This value is added to the amount of {NETWORK_UNIT} bonded by nominators to form the total stake of the validator." ] ], - "nominator_stake": [ - "Nominator Stake", - [ - "The amount of {NETWORK_UNIT} backing the validator from its nominators.", - "This value is added to the validator's self stake to form the total stake of the validator.,'Note that this value changes every era as the bonded funds of nominators are re-distributed to the active validators of that session." - ] - ], - "commission": [ - "Commission", - [ - "Validators can take a percentage of the rewards they earn. This chunk is called their commission.", - "Nominating validators with low commissions mean you will receive a larger share of the rewards they generate.", - "Many validators will have a commission rate of 100%, meaning you will receive no rewards by nominating these validators.", - "Examples of such validators include those operating on behalf of exchanges, where nominating and reward distribution is done centrally on the exchange in question.", - "A validator can update their commission rates as and when they please, and such changes will have an impact on your profitability. Be sure to monitor your nominations on this dashboard to keep updated on their commission rates." - ] - ], - "over_subscribed": [ - "Over Subscribed", + "supplyStaked": [ + "Supply Staked", [ - "Only the top {FallbackNominatorRewardedPerValidator} nominators for each validator are rewarded in {NETWORK_NAME}. When this number is surpassed, this validator is considered over subscribed." + "The current cumulative supply of {NETWORK_UNIT} being staked globally.", + "The percentage of staked {NETWORK_UNIT} is relative to the total supply of {NETWORK_UNIT}." ] ], - "blocked_nominations": [ - "Blocked Nominations", + "totalNominators": [ + "Total Nominators", [ - "When a validator has blocked nominations, nominators are unable to nominate them." + "Accounts who are staking in the network, regardless of whether they are active or inactive in the current session.", + "In order to stake {NETWORK_UNIT}, you can either become a nominator or join a pool - that act as nominators themselves." ] ], - "payout": [ - "Payout", + "validator": [ + "Validator", [ - "Payouts are staking rewards on {NETWORK_NAME}. They depend on how many 'Era Points' your nominated validators accrue over time. Rewards are determined at the end of every Era (24 hour periods).", - "To receive staking rewards, a Payout needs to be requested. Any nominator backing the validator in question can request a Payout.", - "One payout request triggers the reward payout for every nominator." + "An entity that validates blocks for the {NETWORK_NAME} Relay Chain. Validators play a key role in {NETWORK_NAME} to secure the network and produce blocks.", + "As a nominator, you choose which validators you wish to back, and receive rewards for doing so." ] ], - "last_era_payout": [ - "Last Era Payout", + "wrongTransaction": [ + "Wrong Transaction", [ - "The total amount of {NETWORK_UNIT} paid out for the last active era.", - "Payouts are distributed evenly amongst the active validators for that era, and are then further distributed to the active nominators that took part in that era.", - "The payout amounts received depend on how much {NETWORK_UNIT} the nominators, and validators themselves, had bonded for that era." + "This error occurs when a Ledger device signed or is in the process of signing a different transaction than the currently active one on the dashboard.", + "Ensure there are no outstanding transactions on the Ledger device and try again." ] ], - "payout_history": [ - "Payout History", + "yourBalance": [ + "Your Balance", [ - "Historical records of payouts made for being an active nominator.", - "Requesting payouts is a manual process, so you may receive payouts for multiple eras in quick succession or in a sporadic fashion. Your payout graphs may therefore have multiple payouts occur on the same day, or have days where there were no payouts.", - "This does not mean that you were not nominating or generating rewards in that period - only that the payout for that period was not yet made." + "Your balance represents the total {NETWORK_UNIT} you have available in addition to your total staked amount, that includes the amount you have bonded in a pool.", + "Unlike your staked balance, your bonded pool balance is held and locked in the pool itself." ] ] }, "externals": { - "connect_your_accounts": "How to Connect Your Accounts", - "how_to_use": "How to Use the Staking Dashboard: Overview", - "stake_cere": "Staking your CERE", - "change_destination": "Changing Your Reward Destination", - "bond_more": "Bond More Tokens to Your Existing Stake", - "unbonding_tokens": "Unbonding Your Tokens", + "bondMore": "Bond More Tokens to Your Existing Stake", + "changeAccount": "Changing your Controller Account", + "changeDestination": "Changing Your Payout Destination", + "changeNominations": "Changing Your Nominations", + "chooseValidators": "How do I Know Which Validators to Choose?", + "claimRewards": "Claiming Nomination Pool Rewards", + "connectAccounts": "How to Connect Your Accounts", + "createPools": "Creating Nomination Pools", + "howToUse": "How to Use the Staking Dashboard: Overview", "rebonding": "Rebonding", - "change_account": "Changing your Controller Account", - "change_nominations": "Changing Your Nominations", - "create_pools": "Creating Nomination Pools", - "claim_rewards": "Claiming Nomination Pool Rewards", - "choose_validators": "How do I Know Which Validators to Choose?" + "stakeDot": "Staking your DOT", + "unbondingTokens": "Unbonding Your Tokens" + }, + "modal": { + "articles": "Articles", + "close": "Close", + "definitions": "Definitions", + "helpResources": "Help Resources", + "related": "Related" } } } diff --git a/src/locale/en/library.json b/src/locale/en/library.json new file mode 100644 index 0000000000..a2d856f2b2 --- /dev/null +++ b/src/locale/en/library.json @@ -0,0 +1,193 @@ +{ + "library": { + "100Commission": "100% commission", + "accountConnected": "Account Connected", + "accounts": "Accounts", + "active": "Active", + "activeLowCommission": "Active Low Commission", + "activeLowCommissionSubtitle": "Selects high performing validators with low commission.", + "activePools": "Active Pools", + "activeValidator": "Active Validator", + "activeValidators": "Active Validators", + "add": "Add", + "addFromFavorites": "Add From Favorites", + "address": "Address", + "addressCopiedToClipboard": "Address Copied to Clipboard", + "all": "All", + "alreadyImported": "Address Already Imported", + "asAPoolMember": "as a pool member.", + "asThePoolDepositor": "as the pool depositor.", + "atLeast": "Bond amount must be at least", + "available": "Available", + "backToMethods": "Back to Methods", + "backToScan": "Back to Scan", + "blockedNominations": "Blocked Nominations", + "blockingNominations": "Blocking Nominations", + "bond": "Bond", + "bondAmountDecimals": "Bond amount can only have at most {{units}} decimals.", + "bondDecimalsError": "Bond amount can have at most {{units}} decimals.", + "bonded": "Bonded", + "cancel": "Cancel", + "cancelled": "Cancelled", + "chooseValidators": "Choose up to {{maxNominations}} validators to nominate.", + "chooseValidators2": "Generate your nominations automatically or manually insert them.", + "clear": "Clear", + "clearSelection": "clear selection", + "clickToReload": "Click to reload", + "complete": "Complete", + "confirm": "Confirm", + "confirmReformat": "Address was reformatted. Please confirm.", + "connect": "Connect", + "connectedTo": "Connected To", + "connectedToNetwork": "Connected to Network", + "connecting": "Connecting", + "continue": "Continue", + "copyAddress": "Copy Address", + "copyPoolAddress": "Copy Pool Address", + "createPool": "Create Pool", + "dayAverage": "Day Average", + "dayPerformance": "Day Performance", + "dayPerformanceStanding": "{{count}} Day Active Validators Performance Standing", + "dayPoolPerformance": "Day Pool Performance", + "destroying": "Destroying", + "destroyingPools": "Destroying Pools", + "disclaimer": "Disclaimer", + "disconnected": "Disconnected", + "displayingValidators_one": "Displaying {{count}} Validator", + "displayingValidators_other": "Displaying {{count}} Validators", + "done": "Done", + "enablePermissionlessClaiming": "Enable Permissionless Claiming", + "eraPoints": "Era Points", + "errorUnknown": "Oops, Something Went Wrong", + "errorWithTransaction": "Error with transaction", + "estimatedFee": "Estimated Fee", + "exclude": "Exclude", + "failed": "Failed", + "fastUnstake": "Fast Unstake", + "fastUnstakeCheckingEras": "Checking {{checked}} of {{total}} Eras", + "fastUnstakeExposed_one": "Exposed {{count}} Era Ago", + "fastUnstakeExposed_other": "Exposed {{count}} Eras Ago", + "favorite": "Favorite", + "favoritePoolAdded": "Favorite Pool Added", + "favoritePoolRemoved": "Favorite Pool Removed", + "favoriteValidatorAdded": "Favorite Validator Added", + "favoriteValidatorRemoved": "Favorite Validator Removed", + "filter": "Filter", + "filterValidators": "Filter Validators", + "finalized": "Finalized", + "free": "Free", + "fromFavorites": "From Favorites", + "fromFavoritesSubtitle": "Gets a set of your favorite validators.", + "graphInactive": "Inactive", + "highCommission": "High Commission", + "highPerformanceValidator": "High Performance Validator", + "iHaveScanned": "I Have Scanned", + "import": "Import", + "importing": "Importing", + "inBlock": "In Block", + "inQueue": "In Queue", + "inactive": "Inactive", + "include": "Include", + "insertPayoutAddress": "Insert a payout address", + "invalid": "Address Invalid", + "join": "Join", + "legalDisclosures": "Legal Disclosures", + "listItemActive": "Active", + "locked": "Locked", + "lockedPools": "Locked Pools", + "lowCommission": "Low Commission", + "manual": "Manual", + "manualSelectionSubtitle": "Add validators from scratch.", + "manual_selection": "Manual Selection", + "max": "Max", + "minimumBond": "A minimum bond of {{minBondUnit}} {{unit}} is required", + "missingIdentity": "Missing Identity", + "moreThanBalance": "Bond amount is more than your free balance.", + "next": "Next", + "noFilters": "No filters", + "noFree": "You have no free {{unit}} to bond.", + "noMatch": "No pools match this criteria.", + "noPayoutAddress": "No Payout Adddress", + "noValidators": "No validators.", + "noValidatorsMatch": "No validators match this criteria.", + "nominate": "Nominate", + "nominateActive": "Active", + "nominateInactive": "Inactive", + "nominationsReverted": "Nominations Reverted", + "nominator": "Nominator", + "notEnough": "Not Enough", + "notEnoughAfter": "Not enough {{unit}} to bond after transaction fees.", + "notEnoughFunds": "The sender does have enough {{unit}} to submit this transaction.", + "notImported": "Not Imported", + "notMeet": "You do not meet the minimum bond of", + "notNominating": "Not Nominating", + "notStaking": "Not Staking", + "notValidAddress": "Not a valid address", + "optimalSelection": "Optimal Selection", + "optimalSelectionSubtitle": "Selects top performing and regularly active validators.", + "order": "Order", + "orderValidators": "Order Validators", + "overSubscribed": "Over Subscribed", + "overSubscribedMinReward": "Over subscribed: Minimum reward bond is", + "page": "Page {{page}} of {{total}}", + "payout": "Payout", + "payoutAccount": "Payout Account", + "payoutAddress": "Payout Adddress", + "pending": "Pending", + "permissionlessClaimingTurnedOff": "Permissionless claiming is turned off.", + "points": "Points", + "pool": "Pool", + "poolClaim": "Pool Claim", + "poolCommission": "Pool Commission", + "poolId": "Pool ID", + "poolMembers": "Pool Members", + "prev": "Prev", + "privacy": "Privacy", + "proxy": "Proxy", + "randomValidator": "Random Validator", + "reGenerate": "Re-Generate", + "remove": "Remove", + "removeSelected": "Remove Selected", + "reset": "Reset", + "revertedToActiveSelection": "Nominations have been reverted to your active selection.", + "scanPolkadotVault": "Scan on Polkadot Vault", + "search": "Search Pool ID, Name or Address", + "searchAddress": "Search Address or Identity", + "select": "Select", + "sign": "Sign", + "signPolkadotVault": "Sign From Polkadot Vault", + "signedByController": "Signed by Controller", + "signedByProxy": "Signed by Proxy", + "signer": "Signer", + "signing": "Signing", + "submitTransaction": "Ready to submit transaction.", + "syncing": "Syncing", + "syncingPoolList": "Syncing Pool list", + "tooSmall": "Bond amount is too small.", + "top": "Top", + "transactionCancelled": "Transaction was cancelled", + "transactionInBlock": "Transaction in block", + "transactionInitiated": "Transaction was initiated.", + "transactionSuccessful": "Transaction successful", + "unbond": "Unbond", + "unbondAmount": "Unbond amount is more than your bonded balance.", + "unbonding": "Unbonding", + "unclaimedPayouts": "Unclaimed Payouts", + "unlocking": "Unlocking", + "unordered": "Unordered", + "update": "Update", + "valid": "Valid Address", + "validAddress": "Valid address", + "validatingParachainBlocks": "Validating Parachain Blocks", + "validatorCommission": "Validator Commission", + "validatorPerformance": "{{count}} Day Validator Performance", + "valueTooSmall": "Value is too small", + "viewDecentralization": "View Decentralization", + "viewMetrics": "View Metrics", + "viewPoolNominations": "View Pool Nominations", + "waiting": "Waiting", + "walletNotFound": "wallet not found", + "whenActivelyNominating": "when actively nominating.", + "wrongTransaction": "Wrong transaction, please sign again." + } +} diff --git a/src/locale/en/modals.json b/src/locale/en/modals.json new file mode 100644 index 0000000000..74afe08f76 --- /dev/null +++ b/src/locale/en/modals.json @@ -0,0 +1,294 @@ +{ + "modals": { + "aboveExisting": "Above Existing", + "aboveGlobalMax": "Above Global Max", + "aboveMax": "Above Max", + "account": "Account", + "accountAlreadyImported": "Account Already Imported", + "accounts": "Accounts", + "activeRoles_one": "Active Roles in {{count}} Pool", + "activeRoles_other": "Active Roles in {{count}} Pools", + "activeRoles_zero": "Active Roles In No Pool", + "add": "Add", + "addToBond": "Add to Bond", + "addToNominations": "Add to Nominations", + "addUpToFavorites_one": "You can add {{count}} more validator", + "addUpToFavorites_other": "You can add up to {{count}} more favorite validators", + "addedToBond": "This amount of {{unit}} will be added to your current bonded funds.", + "addingFavorite_one": "Adding {{count}} Nomination", + "addingFavorite_other": "Adding {{count}} Nominations", + "addressReceived": "Address Received:", + "afterClaiming": "Funds will be immediately available as free balance after claiming.", + "allNominations": "All Nominations", + "allPoolRoles": "All Pool Roles", + "allowToJoin": "Allow new members to join the pool.", + "amountToBond": "Amount to bond:", + "approveTransactionLedger": "Approve transaction on Ledger", + "back": "Back", + "beingDestroyed": "This pool is being destroyed.", + "belowExisting": "Below Existing", + "beyondMaxIncrease": "Beyond Max Increase", + "binanceApi": "Binance Spot API", + "bond": "Bond", + "bondAll": "Bond All", + "bondAllAvailable": "Bond all available", + "bondExtra": "Bond Extra", + "bondMore": "Bond more", + "bondingWithdraw": "Bonding will also withdraw your outstanding rewards of", + "bouncer": "Bouncer", + "braveText": "<b>To Brave users!</b> Due to a recent update (<i>Brave version 1.36</i>), there may appear issues while using light clients (e.g. not connected).", + "cancel": "Cancel", + "changeControllerAccount": "Change Controller Account", + "changeNomination": "Once submitted, your nominations will be removed from your dashboard immediately, and will not be nominated from the start of the next era.", + "changePoolRoles": "Change Pool Roles", + "changeRate": "Change Rate", + "changeToDestroy": "Change pool to destroying state.", + "checking": "Checking...", + "chooseLanguage": "Choose Language", + "claim": "Claim", + "claimCommission": "Claim Commission", + "claimOutstandingCommission": " Claim any outstanding commission in the pool reward account.", + "claimPayouts": "Claim Payouts", + "claimReward1": "Once submitted, your rewards will be bonded back into the pool. You own these additional bonded funds and will be able to withdraw them at any time.", + "claimReward2": "Withdrawing rewards will immediately transfer them to your account as free balance.", + "claimsOnBehalf": "Claiming a payout claims on behalf of every nominator backing the validator for the era you are claiming for. For this reason transaction fees are usually higher, and most nominators rely on the validator to claim on their behalf.", + "commissionRate": "Commission Rate", + "compound": "Compound", + "confirmReset": "Confirm Reset", + "connect": "Connect", + "connectLedgerToContinue": "Connect your Ledger device to continue.", + "connected": "Connected", + "connectionType": "Connection Type", + "continue": "Continue", + "controllerImported": "You must have your controller account imported to sign this transaction.", + "dashboardTips": "Dashboard Tips", + "days": "Days", + "decentralizationAnalyticsNotAvailable": "Decentralization analytics not available", + "decentralizationAnalyticsNotSupported": "Decentralization analytics not Supported in this Network", + "declare": "Declare", + "declared": "Declared", + "destroyIrreversible": "Destroying a Pool is Irreversible", + "destroyPool": "Destroy Pool", + "destroyPoolResult": " Once you Destroy the pool, all members can be permissionlessly unbonded, and the pool can never be reopened.", + "developerTools": "Developer Tools", + "differentNetworkAddress": "Different Network Address", + "disconnect": "Disconnect", + "done": "Done", + "ensureLedgerIsConnected": "Tip: Ensure your Ledger device is connected before continuing.", + "exitYourStakingPosition": "Exit your staking position.", + "extensionConnected": "Extension Connected", + "extensions": "Extensions", + "fastUnstakeCurrentQueue": "Number of accounts currently in the fast unstake queue", + "fastUnstakeExposedAgo_one": "You were last exposed {{count}} Era Ago", + "fastUnstakeExposedAgo_other": "You were last exposed {{count}} Eras Ago", + "fastUnstakeNote1": "To register for fast unstake, you must not be actively staking for more than {{bondDuration}} eras.", + "fastUnstakeNote2_one": "If you are inactive for at least {{count}} more Era, you will be able to register for fast unstake.", + "fastUnstakeNote2_other": "If you are inactive for at least {{count}} more Eras, you will be able to register for fast unstake.", + "fastUnstakeOnceRegistered": "Once registerd you will be waiting in the fast unstake queue.", + "fastUnstakeRegistered": "Registered and Waiting to Unstake", + "fastUnstakeSubmit_cancel": "Cancel Fast Unstake", + "fastUnstakeSubmit_register": "Register", + "fastUnstakeUnorderedNote": "The fast unstake queue is unordered, so the exact timing of being selected is not known.", + "fastUnstakeWarningUnlocksActiveMore": "No unlocks can be active to register for fast unstake. Rebond or withdraw your unlocks to become fully bonded and try registering for fast unstake again.", + "fastUnstakeWarningUnlocksActive_one": "You have {{count}} unlock active.", + "fastUnstakeWarningUnlocksActive_other": "You have {{count}} unlocks active.", + "fastUnstake_register": "Register Fast Unstake", + "fastUnstake_title": "Fast Unstake", + "favoriteNotNominated": "Favorite Validators / Not Nominated", + "favoriteValidators": "Favorite Validators", + "favoritesAddedSubtitle_one": "{{count}} validator has been added to your nominees", + "favoritesAddedSubtitle_other": "{{count}} validators have been added to your nominees", + "favoritesAddedTitle_one": "Favorite Added", + "favoritesAddedTitle_other": "Favorites Added", + "feedback": "Feedback", + "feedbackPage": "We host a feedback page on", + "for": "For {{who}}", + "forget": "Forget", + "free": "Free", + "getAnotherAccount": "Get Another Account", + "gettingAccount": "Getting Account", + "gettingAddress": "Getting Address...", + "goToAccounts": "Go To Accounts", + "goToConnect": "Go To Connect", + "hardware": "Hardware", + "hide": "Hide", + "hours": "Hours", + "import": "Import", + "importAccount": "Import Account", + "importAddress": "Import Address", + "importAnotherAccount": "Import Another Account", + "importedAccount_one": "{{count}} Imported Account", + "importedAccount_other": "{{count}} Imported Accounts", + "inPool": "In Pool", + "inputAddress": "Input Address", + "inputDelegatorAddress": "Input Delegator Address", + "inputPayeeAccount": "Input Payee Account", + "invalidAddress": "Invalid Address", + "joinPool": "Join Pool", + "leavePool": "Leave Pool", + "ledgerAccount": "Ledger Account", + "ledgerAccounts_one": "Displaying {{count}} Ledger Account", + "ledgerAccounts_other": " Displaying {{count}} Ledger Accounts", + "ledgerRequestTimeout": "The Ledger request timed out. Please try again.", + "ledgerWillBeReset": "Your Ledger address list will be reset, and any imported accounts will be removed from the dashboard.", + "lightClient": "Light Client", + "lockPool": "Lock Pool", + "lockPoolSubtitle": "Once you lock the pool no one else can join the pool.", + "manageCommission": "Manage Commission", + "manageNominations": "Manage Nominations", + "managePool": "Manage Pool", + "maxCommission": "Max Commission", + "maximumCommissionUpdated": "Maximum Commission Updated", + "minDelayBetweenUpdates": "Min Delay Between Updates", + "minutes": "Minutes", + "missingNesting": "Call missing nesting support, cannot sign.", + "months": "Months", + "moreFavoritesSurpassLimit": "Adding more favorites will surpass {{max}} nominations.", + "networks": "Networks", + "newTotalBond": "New total bond:", + "newlyBondedFunds": "Newly bonded funds will back active nominations from the start of the next era.", + "noAccounts": "No Accounts", + "noActiveAccount": "No Active Account", + "noEnough": "You do not have enough free balance to register for fast unstake. Fast unstake requires a deposit of", + "noFavoritesAvailable": "No Favorites Available.", + "noFavoritesSelected": "No Favorites Selected", + "noNominationsSet": "You have no nominations set.", + "noNominatorRole": "You do not have a nominator role in any pools.", + "noProxyAccountsDeclared": "No proxy accounts have been declared.", + "noReadOnlyAdded": "No Read Only accounts have been added", + "noRewards": "You have no rewards to claim.", + "noVaultAccountsImported": "No Polkadot Vault accounts have been imported.", + "nominateFavorites": "Nominate Favorites", + "nominating": "Nominating", + "nominatingAndInPool": "Nominating and In Pool", + "nomination_one": "{{count}} Nomination", + "nomination_other": "{{count}} Nominations", + "nominator": "Nominator", + "nominatorStake": "Nominator Stake", + "none": "None", + "notAuthenticated": "Not Authenticated", + "notInstalled": "Not Installed", + "notMeetMinimum": "You do not meet the minimum nominator bond of {{minNominatorBondUnit}} {{unit}}. Please bond some funds before nominating.", + "notStaking": "Not Staking", + "notToClaim": "Validators usually claim payouts on behalf of their nominators. If you decide not to claim here, it is likely you will receive your payouts within 1-2 days of them becoming available.", + "notePoolDepositorMinBond_depositor": "As the pool depositor you must maintain a bond of at least {{bond}} {{unit}}.", + "notePoolDepositorMinBond_member": "As a pool member you must maintain a bond of at least {{bond}} {{unit}}.", + "onceUnbonding": "Once unbonding, your funds to become available after {{bondDurationFormatted}}.", + "openAppOnLedger": "Open the {{appName}} app on your Ledger device.", + "openFeedback": "Open Feedback on Canny.io", + "payeeAdded": "Payee Added", + "payoutDestination": "Payout Destination", + "pendingPayout_one": "{{count}} Pending Payout", + "pendingPayout_other": "{{count}} Pending Payouts", + "polkawatchDisabled": "Polkawatch Disabled", + "pool": "Pool", + "poolIsNotNominating": "Pool is Not Nominating.", + "poolName": "Pool Name", + "poolNominations": "Pool Nominations", + "provider": "Provider", + "proxies": "Proxies", + "proxy": "Proxy", + "proxyAccounts": "Proxy Accounts", + "queuedTransactionRejected": "Previous transaction rejected. Please try again.", + "readOnly": "Read Only", + "readOnlyAccounts": "Read Only Accounts", + "readOnlyCannotSign": "Your account is read only, and cannot sign transactions.", + "rebond": "Rebond", + "rebondSubtitle": "Rebonded funds will back active nominations from the start of the next era.", + "rebondUnlock": "You can rebond unlocks at any time in this period, or withdraw them to your free balance thereafter.", + "recentEraPoints": "Recent Era Points", + "registerFastUnstake": "Registering for fast unstake requires a deposit of", + "remove": "Remove", + "removeAccount": "Remove Account", + "removeBond": "Remove Bond", + "renamePool": "Rename Pool", + "reserveBalance": "Reserve Balance", + "reserveForExistentialDeposit": "Reserve For Existential Deposit", + "reserveForTxFees": "Reserve For Tx Fees", + "reserveText": "Control how much {{unit}} is reserved to pay for transaction fees. This amount is added on top of any funds needed to cover the existential deposit to your account.", + "resetLedgerAccounts": "Reset Ledger Accounts", + "revertChanges": "Revert Changes", + "revertNominationChanges": "Are you sure you wish to revert the changes to your selected nominations?", + "revertNominations": "Revert Nominations", + "rewards": "Rewards", + "rewardsByCountryAndNetwork": "Rewards by Country, Network Provider", + "root": "Root", + "rpcProviders": "RPC Providers", + "scanFromPolkadotVault": "Scan From Polkadot Vault", + "searchAccount": "Search Account", + "selectNetwork": "Select Network", + "selectRpcProvider": "Select an RPC provider to change the {{network}} node Staking Dashboard connects to.", + "selected": "Selected", + "selfStake": "Self Stake", + "sentToCommissionPayee": "This amount will be sent to the commission payee account configured for this pool.", + "setToDestroying": "Set To Destroying", + "setToDestroyingSubtitle": "Setting a pool to destroying cannot be reversed. Only set this state if you intend to close the pool.", + "settings": "Settings", + "signedTransactionSuccessfully": "Signed Transaction Successfully.", + "stop": "Stop", + "stopJoiningPool": "Stop new members from joining the pool.", + "stopNominating": "Stop Nominating", + "stopNominatingBefore": "Stop nominating before unbonding all funds.", + "storedOnChain": "Your updated name will be stored on-chain as encoded bytes. The update will take effect immediately.", + "submit": "Submit", + "submitLock": "Submit Pool Lock", + "submitUnlock": "Submit Pool Unlock", + "submitting": "Submitting", + "subscanDisabled": "Subscan Disabled", + "successfullyFetchedAddress": "Successfully Fetched Address", + "thisMinimumDelay_one": "This minimum delay is the approximate equivalent of {{count}} block.", + "thisMinimumDelay_other": "This minimum delay is the approximate equivalent of {{count}} blocks.", + "titleExtensionConnected": "The {{title}} extension has been connected.", + "toggleFeatures": "Toggle Features", + "togglePlugins": "Toggle Plugins", + "total": "Total", + "transactionRejectedPending": "Transaction was rejected and is still pending.", + "tryAgain": "Try Again", + "unbond": "Unbond", + "unbondAll": "Unbond All", + "unbondErrorBelowMinimum": "Unable to unbond. Your bonded funds are below the minumum of {{bond}} {{unit}}.", + "unbondErrorNoFunds": "You have no {{unit}} to unbond.", + "unbondFundsLeavePool": "Unbond your funds and leave the nomination pool.", + "unbondMemberFunds": "Unbond Member Funds", + "unbondSomeOfYour": "Unbond some of your", + "unbondToMaintain": "Unbond up to the {{minJoinBondUnit}} {{unit}} minimum to maintain your pool membership", + "unbondToMinimum": "Unbond To Minimum", + "unbondToMinimumCreate": "Unbond up to the {{minCreateBondUnit}} {{unit}} minimum bond for pool depositors.", + "unbonding": "Unbonding", + "unbondingWithdraw": "Unbonding will also withdraw your outstanding rewards of", + "undergoingMaintenance": "Undergoing Maintenance", + "unlockChunk": "Unlock chunks cannot currently be rebonded in a pool. If you wish to rebond, withdraw the unlock chunk first and re-bond the funds.", + "unlockLedgerToContinue": "Unlock your Ledger device to continue.", + "unlockPool": "Unlock Pool", + "unlockPoolSubtitle": "Once a pool is unlocked, any account will be able to join as a pool member.", + "unlockTake": "Unlocks can be withdrawn after {{bondDurationFormatted}}.", + "unlocked": "Unlocked", + "unlocks": "Unlocks", + "unlocksAfter": "Unlocks after", + "unlocksInEra": "Unlocks in Era", + "unstake": "Unstake", + "unstakeStopNominating_one": "Stop Nominating {{count}} Validator", + "unstakeStopNominating_other": "Stop Nominating {{count}} Validators", + "unstakeUnbond": "Unbond {{bond}} {{unit}}", + "updateClaimPermission": "Update Claim Permission", + "updateName": "Update the public name of the pool.", + "updatePayoutDestination": "Update Payout Destination", + "updatePoolCommission": "Update pool commission settings.", + "updateWhoClaimRewards": "Update who can claim rewards on your behalf.", + "updated": "Updated", + "validatorDecentralization": "Validator Decentralization", + "validatorMetrics": "Validator Metrics", + "vaultAccounts_one": "{{count}} Account Imported", + "vaultAccounts_other": "{{count}} Accounts Imported", + "vaultAccounts_zero": "No Accounts Imported", + "waitingForQRCode": "Waiting for QR Code", + "web": "Web", + "welcomeToReport": "Bug reports, feature requests and improvements are all welcome.", + "willSurpass": "Adding this many favorites will surpass {{maxNominations}} nominations.", + "withdraw": "Withdraw", + "withdrawMemberFunds": "Withdraw Member Funds", + "withdrawSubtitle": "Funds will be immediately available as free balance after withdrawing.", + "withdrawUnlocked": "Withdraw Unlocked", + "years": "Years" + } +} diff --git a/src/locale/en/pages.json b/src/locale/en/pages.json index 7dd63a8c21..37f16565dd 100644 --- a/src/locale/en/pages.json +++ b/src/locale/en/pages.json @@ -1,210 +1,233 @@ { "pages": { - "overview": { - "nominator_limit": "Nominator limit has been reached.", - "maximum_allowed": "The maximum allowed nominators have been reached on the network. Please wait for available slots if you wish to nominate.", - "limit_reached": "% of nominator limit reached.", - "maximum_amount": "The maximum amount of nominators has almost been reached. The nominator cap is currently", - "pools_are_active": "nomination pools are active.", - "available_to_join": "The number of nomination pools available to join on {{networkName}}.", - "in_pools": "is currently bonded in pools.", - "bonded_in_pools": "The total {{networkUnit}} currently bonded in nomination pools.", - "pool_members_bonding": "pool members are actively bonding in pools.", - "total_num_accounts": "The total number of accounts that have joined a pool.", - "minimum_nominator_bond": "The minimum nominator bond is now", - "minimum_bonding_amount": "The minimum bonding amount to start nominating on {{networkName}} is now", - "currently_staked": "{{supplyAsPercent}}% of total {{networkUnit}} supply is currently staked.", - "staking_on_the_network": "A total of {{lastTotalStakeBase}} {{networkUnit}} is actively staking on the network.", - "network_stats": "Network Stats", - "historical_rewards_rate": "Historical Rewards Rate", - "inflation": "Inflation", - "supply_staked": "Supply Staked", - "active_era": "Active Era", - "total_nominators": "Total Nominators", - "active_nominators": "Active Nominators", - "address_copied": "Address Copied to Clipboard", - "no_account_connected": "No Account Connected", - "in_pool": "In Pool", - "available": "Available", - "unlocking": "Unlocking", - "nominating": "Nominating", - "balance": "Balance", - "overview": "Overview", - "recent_payouts": "Recent Payouts", - "subscan_disabled": "Subscan Disabled", - "not_staking": "Not Staking", - "reserved": "Reserved" - }, - "favorites": { - "fetching_favorite_validators": "Fetching favorite validators...", - "no_favorites": "No Favorites.", - "favorite_validators": "Favorite Validators" + "community": { + "bio": "Bio", + "connecting": "Connecting", + "email": "Email", + "fetchingValidators": "Fetching validators", + "goBack": "Go Back", + "noValidators": "This entity contains no validators.", + "validator_one": "{{count}} Validator", + "validator_other": "{{count}} Validators", + "website": "Website" }, "nominate": { - "none": "None", + "activeNominations": "Active Nominations", + "addressCopied": "Address Copied to Clipboard", + "automaticallyBonded": "Automatically bond payouts to your existing staked balance.", + "back": "Back", + "bond": "Bond", + "bondAmount": "Bond Amount", + "bondedFunds": "Bonded Funds", + "cancel": "Cancel", "change": "Change", - "pool_nominations": "Pool Nominations", + "controllerAccount": "Controller Account", + "controllerAccountsDeprecated": "Controller Accounts Are Being Deprecated", + "controllerNotImported": "You have not imported your controller account. If you have lost access to your controller account, set a new one now. Otherwise, import the controller into one of your active extensions.", + "earningRewards": "Earning Rewards", + "inactiveNominations": "Inactive Nominations", + "manage": "Manage", + "minimumToEarnRewards": "Minimum To Earn Rewards", + "minimumToNominate": "Minimum To Nominate", + "noNominationsSet": "Inactive: No Nominations Set", + "nominate": "Nominate", + "nominating": "Nominating", + "nominatingAnd": "Nominating and", "nominations": "Nominations", + "none": "None", + "notAssigned": "Not Assigned", + "notEarningRewards": "Not Earning Rewards", + "notNominating": "Not Nominating", + "payoutDestination": "Payout Destination", + "payoutDestinationSubtitle": "Choose how payouts will be received. Payouts can either be compounded or sent to an account as free balance.", + "pendingPayouts": "Pending Payouts", + "poolDestroy": "Pool is being destroyed and nominating is no longer possible.", + "poolNominations": "Pool Nominations", + "proxyprompt": "Staking dashboard will soon remove support for controller accounts in favour of proxies. Switch your controller account to your stash account to continue using the dashboard and to help in the transition to a better", + "readOnly": "Your account is read only, and cannot sign transactions.", + "setNewController": "Set New Controller", + "startNominating": "Start Nominating", + "status": "Nominator Status", "stop": "Stop", - "not_nominating": "Not Nominating", - "syncing": "Syncing...", - "your_nominations": "Your Nominations", - "stop_nominating_selected": "Stop Nominating Selected", - "add_from_favorites": "Add From Favorites", - "pool_destroy": "Pool is being destroyed and nominating is no longer possible.", - "active_nominations": "Active Nominations", - "inactive_nominations": "Inactive Nominations", - "minimum_active_bond": "Minimum Active Bond", - "total_supply_staked": "Total Supply Staked", - "controller_not_imported": "You have not imported your controller account. If you have lost access to your controller account, set a new one now. Otherwise, import the controller into one of your active extensions.", - "nominate": "Nominate", - "start_nominating": "Start Nominating", - "bonded_funds": "Bonded Funds", - "status": "Status", - "no_nominations_set": "Inactive: No Nominations Set", - "nominating_and": "Nominating and", - "earning_rewards": "Earning Rewards", - "not_earning_rewards": "Not Earning Rewards", - "waiting_for_active_nominations": "Waiting for Active Nominations", - "reward_destination": "Reward Destination", - "not_assigned": "Not Assigned", - "update": "Update", - "controller_account": "Controller Account", - "bond": "Bond", - "back_to_staking": "Back to Staking", - "automatically_bonded": "Payouts are automatically bonded to your existing bonded balance.", - "to_stash": "To Stash", - "sent_to_stash": "Payouts will be sent to your stash account as free balance.", - "to_controller": "To Controller", - "sent_to_controller": "Payouts will be sent to your controller account as free balance.", "summary": "Summary", - "read_only": "Your account is read only, and cannot sign transactions.", - "bond_amount": "Bond Amount:", - "back": "Back", - "cancel": "Cancel", - "search_account": "Search Account", - "none_of_your": "None of your imported accounts have the minimum deposit of", - "top_up_account": "Top up an account to make it eligible to become a controller.", - "select_a_controller": "You have no other accounts imported. To select a controller, import another account with a balance of at least", - "set_controller_account": "Set Controller Account" + "syncing": "Syncing", + "totalSupplyStaked": "Total Supply Staked", + "unlocked": "Unlocked", + "unstake": "Unstake", + "unstakePromptInProgress_fast": "Fast Unstake in Progress", + "unstakePromptInProgress_regular": "Unstake in Progress", + "unstakePromptInQueue": "You are in the fast unstake queue. You will not be able to carry out any nominator tasks while you are registered for fast unstake.", + "unstakePromptReadyToWithdraw": "Your bonded funds are now unlocked and ready to withdraw.", + "unstakePromptRevert": "If you no longer wish to unstake, rebond your {{unit}} and start nominating again.", + "unstakePromptWaitingForUnlocks": "Waiting for unlocks to become available to withdraw.", + "update": "Update", + "updateToStash": "Update Controller To Stash", + "validator_one": "{{count}} Validator", + "validator_other": "{{count}} Validators", + "waitingForActiveNominations": "Waiting for Active Nominations" + }, + "overview": { + "activeEra": "Active Era", + "activeNominators": "Active Nominators", + "activePools": "Active Pools", + "addressCopied": "Address Copied to Clipboard", + "adjustedRewardsRate": "Adjusted Rewards Rate", + "afterInflation": "after inflation", + "available": "Available", + "balance": "Balance", + "bondedInPools": "The total {{networkUnit}} currently bonded in nomination pools.", + "connect": "Connect", + "free": "Free", + "historicalRewardsRate": "Historical Rewards Rate", + "inPool": "In a Pool", + "inPools": "is currently bonded in pools.", + "inflationRate": "Inflation Rate", + "locked": "Locked", + "manage": "Manage", + "memberOf": "Member of", + "moreResources": "More Resources", + "networkCurrentlyStaked": "{{total}} {{unit}} is currently being staked on {{network}}.", + "networkCurrentlyStakedSubtitle": "The total {{unit}} currently being staked amongst all validators and nominators.", + "networkStats": "Network Stats", + "noActiveAccount": "No Active Account", + "nominating": "Nominating", + "notStaking": "Not Staking", + "overview": "Overview", + "pool": "Pool", + "poolMembersBonding": "pool members are actively bonding in pools.", + "proxy": "Proxy", + "recentPayouts": "Recent Payouts", + "reserve": "Reserve", + "reserveBalance": "Reserve Balance", + "reserved": "Reserved", + "start": "Start", + "subscanDisabled": "Subscan Disabled", + "supplyStaked": "Supply Staked", + "syncingStatus": "Syncing Status", + "timeRemainingThisEra": "Time Remaining This Era", + "totalNominators": "Total Nominators", + "totalNumAccounts": "The total number of accounts that have joined a pool.", + "totalValidators": "Total Validators", + "unitSupplyStaked": "{{unit}} Supply Staked", + "unlocking": "Unlocking", + "updateReserve": "Update Reserve" + }, + "payouts": { + "deductedFromBond": "Deducted from bond", + "fromPool": "From Pool", + "lastEraPayout": "Last Era Payout", + "none": "None", + "notStaking": "Not Staking", + "payout": "Payout", + "payoutHistory": "Payout History", + "poolClaim": "Pool Claim", + "recentPayouts": "Recent Payouts", + "slashed": "Slashed", + "subscanDisabled": "Subscan Disabled" }, "pools": { - "bond": "Bond", - "pool_name": "Pool Name", - "pool_name_support": "Pool names support characters, symbols and emojis - be creative!", - "roles": "Roles", - "assigned_to_any_account": " Your <b>Root</b>, <b>Nominator</b> and <b>State Toggler</b> roles can be assigned to any account.", - "pool_creator": " As the pool creator, you will consume your pool's <b>Depositor</b> role.", - "summary": "Summary", - "read_only": "Your account is read only, and cannot sign transactions.", - "bond_amount": "Bond Amount", - "nominations": "Nominations", + "activePools": "Active Pools", + "address": "Address", + "addressCopied": "Address Copied to Clipboard", + "addressInvalid": "Address Invalid", + "allPools": "All Pools", + "allRoles": "All Roles", "assigned": "Assigned", - "create_pool": "Create Pool", - "create_a_pool": "Create a Pool", + "assignedToAnyAccount": " Your <b>Root</b>, <b>Nominator</b> and <b>Bouncer</b> roles can be assigned to any account.", + "availableToClaim": "The outstanding amount of {{unit}} available to claim by pool members.", "back": "Back", + "beenClaimed": "in rewards have been claimed.", + "beenClaimedBy": "The total amount of {{unit}} that has been claimed by pool members.", + "bond": "Bond", + "bondAmount": "Bond Amount", + "bondedFunds": "Bonded Funds", + "bouncer": "Bouncer", "cancel": "Cancel", - "fetching_favorite_pools": "Fetching favorite pools...", - "favorites_list": "Favorites List", - "no_favorites": "No Favorites.", - "generate_nominations": "Generate Nominations", - "nominate": "Nominate", - "withdraw_funds": "Withdraw Funds", - "unbond_funds": "Unbond Funds", - "been_claimed": "in rewards have been claimed.", - "been_claimed_by": "The total amount of {{unit}} that has been claimed by pool members.", - "available_to_claim": "The outstanding amount of {{unit}} available to claim by pool members.", - "outstanding_reward": "outstanding reward balance.", - "locked": "Locked", + "closePool": "You can now withdraw and close the pool.", + "compound": "Compound", + "create": "Create", + "createAPool": "Create a Pool", + "createPool": "Create Pool", + "depositor": "Depositor", + "destroyPool": "Destroy Pool", "destroying": "Destroying", - "open": "Open", - "pool_state": "Pool State", - "pool_members": "Pool Members", - "total_bonded": "Total Bonded", - "pool_stats": "Pool Stats", - "active_pools": "Active Pools", - "minimum_create_bond": "Minimum Create Bond", - "minimum_join_bond": "Minimum Join Bond", - "pool_membership": "Pool Membership", - "not_in_pool": "Not in Pool", - "owner_of_pool": "Owner of Pool", - "in_pool": "In Pool", + "earningRewards": "Earning Rewards", + "edit": "Edit", + "favorites": "Favorites", + "fetchingFavoritePools": "Fetching favorite pools", + "fetchingMemberList": "Fetching Member List", + "generateNominations": "Generate Nominations", + "inPool": "In Pool", + "inactivePoolNotNominating": "Inactive: Pool Not Nominating", + "join": "Join", "leave": "Leave", + "leavingPool": "Leaving Pool", + "leftThePool": "All members have now left the pool", + "locked": "Locked", "manage": "Manage", - "withdraw": "Withdraw", - "inactive_pool_not_nominating": "Inactive: Pool Not Nominating", - "nominating_and": "Nominating and", - "earning_rewards": "Earning Rewards", - "not_earning_rewards": "Not Earning Rewards", - "waiting_for_active_nominations": "Waiting for Active Nominations", - "unclaimed_rewards": "Unclaimed Rewards", - "pool_status": "Pool Status", - "create": "Create", - "join": "Join", - "leaving_pool": "Leaving Pool", - "member_of_pool": "Member of Pool", - "destroy_pool": "Destroy Pool", - "left_the_pool": "All members have now left the pool.", - "stop_nominating": "To continue with pool closure, stop nominating.", - "close_pool": "You can now withdraw and close the pool.", - "unbond_your_funds": "You can now unbond your funds.", - "withdraw_unlock": "Withdraw your unlock chunk to proceed with pool closure.", - "unbond": "Unbond", - "overview": "Overview", + "managementOptions": "to select management options.", + "memberOfPool": "Member of Pool", "members": "Members", - "all_pools": "All Pools", - "favorites": "Favorites", + "minimumToCreatePool": "Minimum To Create Pool", + "minimumToJoinPool": "Minimum To Join Pool", + "noFavorites": "No Favorites.", + "nominate": "Nominate", + "nominating": "Nominating", + "nominatingAnd": "Nominating and", + "nominator": "Nominator", + "notEarningRewards": "Not Earning Rewards", + "notInPool": "Not in Pool", + "notSet": "Not Set", + "open": "Open", + "outstandingReward": "outstanding reward balance.", + "overview": "Overview", + "ownerOfPool": "Owner of Pool", + "permissionToUnbond": "You have permission to unbond and withdraw funds of any pool member. Use a member's's menu", + "poolCommission": "Pool Commission", + "poolCreator": " As the pool creator, you will consume your pool's <b>Depositor</b> role.", + "poolCurrentlyLocked": "Pool Currently Locked", + "poolInDestroyingState": "Pool in Destroying State", + "poolMembers": "Pool Members", + "poolMembership": "Pool Membership", + "poolName": "Pool Name", + "poolNameSupport": "Pool names support characters, symbols and emojis - be creative!", + "poolState": "Pool State", + "poolStats": "Pool Stats", + "poolStatus": "Pool Status", "pools": "Pools", - "all_roles": "All Roles", - "bonded_funds": "Bonded Funds", - "pool_member_one": "{{count}} Pool Member", - "pool_member_other": "{{count}} Pool Members", - "pool_currently_locked": "Pool Currently Locked", - "permission_to_unbond": "You have permission to unbond and withdraw funds of any pool member. Use a member's's menu ", - "management_options": "to select management options.", - "pool_in_destroying_state": "Pool in Destroying State", - "not_set": "Not Set", - "address_invalid": "Address Invalid", + "readOnly": "Your account is read only, and cannot sign transactions.", "reformatted": "Address was reformatted", - "save": "Save", - "edit": "Edit", + "roles": "Roles", "root": "Root", - "depositor": "Depositor", - "nominator": "Nominator", - "state_toggler": "State Toggler" - }, - "community": { - "fetching_validators": "Fetching validators...", - "no_validators": "This entity contains no validators.", - "connecting": "Connecting...", - "go_back": "Go Back", - "website": "Website", - "email": "Email", - "bio": "Bio", - "validators": "'s Validators", + "save": "Save", + "stopNominating": "To continue with pool closure, stop nominating.", + "summary": "Summary", + "totalBonded": "Total Bonded", + "unbond": "Unbond", + "unbondFunds": "Unbond Funds", + "unbondYourFunds": "You can now unbond your funds.", + "unclaimedRewards": "Unclaimed Rewards", + "unlocked": "Unlocked", "validator_one": "{{count}} Validator", - "validator_other": "{{count}} Validators" - }, - "payouts": { - "pool_claim": "Pool Claim", - "payout": "Payout", - "slashed": "Slashed", - "none": "None", - "recent_payouts": "Recent Payouts", - "subscan_disabled": "Subscan Disabled", - "last_era_payout": "Last Era Payout", - "payout_history": "Payout History", - "not_staking": "Not Staking", - "from_pool": "From Pool", - "deducted_from_bond": "Deducted from bond" + "validator_other": "{{count}} Validators", + "waitingForActiveNominations": "Waiting for Active Nominations", + "withdraw": "Withdraw", + "withdrawFunds": "Withdraw Funds", + "withdrawUnlock": "Withdraw your unlock chunk to proceed with pool closure." }, "validators": { - "average_commission": "Average Commission", - "active_validators": "Active Validators", - "total_validators": "Total Validators", - "network_validators": "Network Validators", - "connecting": "Connecting...", - "fetching_validators": "Fetching validators..." + "activeValidators": "Active Validators", + "allValidators": "All Validators", + "averageCommission": "Average Commission", + "connecting": "Connecting", + "favoriteValidators": "Favorite Validators", + "favorites": "Favorites", + "fetchingFavoriteValidators": "Fetching favorite validators", + "fetchingValidators": "Fetching validators", + "networkValidators": "Network Validators", + "noFavorites": "No Favorites.", + "totalValidators": "Total Validators", + "validators": "Validators" } } } diff --git a/src/locale/en/tips.json b/src/locale/en/tips.json index fecaeb4d4c..6a5e1b4acf 100644 --- a/src/locale/en/tips.json +++ b/src/locale/en/tips.json @@ -1,56 +1,43 @@ { "tips": { - "module": { - "tips": "Tips", - "one_moment": "One moment please...", - "syncing_with": "Syncing with {{network}}", - "more": "More", - "close": "Close", - "dismiss_tips": "Dismiss Tips", - "dismiss_result": "Dismissing tips will remove them from your overview.", - "re-enable": "Tips can be turned re-enabled from dashboard settings, that can be accessed via the cog icon in the bottom left corner of the side menu.", - "disable_dashboard_tips": "Disable Dashboard Tips", - "cancel": "Cancel", - "of": "of" - }, - "connect_extensions": [ + "connectExtensions": [ "Connect Extensions", - "Connect your accounts to begin using Cere staking dashboard.", + "Connect your accounts to begin using Cere Staking Dashboard.", [ - "Connect your accounts to begin using Cere staking dashboard.", + "Connect your accounts to begin using Cere Staking Dashboard.", "Accounts are accessed via web extensions, that act as wallets. Your wallet is used to sign transactions that you submit within the dashboard.", "Connect your wallets from the Connect button at the top right of the dashboard, and select the account you wish to stake with to continue.", "Staking dashboard supports a range of extensions and wallets." ] ], - "recommended_nominator": [ - "Recommended: Become a Nominator", - "You have enough {NETWORK_UNIT} to become a nominator.", - [ - "You have enough {NETWORK_UNIT} to become a nominator and start earning rewards.", - "Nominating directly does however require you to actively check your backed validators." - ] - ], - "recommended_join_pool": [ - "Recommended: Join a Pool", - "Your account is best suited to join a pool.", - [ - "Based on the amount of {NETWORK_UNIT} your account currently holds, joining a pool is the best way for you to start staking.", - "Joining a pool requires a minimum deposit of {MIN_POOL_JOIN_BOND} {NETWORK_UNIT}." - ] - ], - "how_to_stake": [ + "howToStake": [ "How Would You Like to Stake?", "Either become a nominator or become a member of a pool.", [ "There are multiple ways to stake on {NETWORK_NAME}.", "You can either become a nominator or become a member of a pool.", - "Directly nominating requires you to bond {NETWORK_UNIT} and choose validators you wish to back, and to nominate up to {FallbackMaxNominations} of them. To earn rewards as a nominator, you currently need to bond at least {MIN_ACTIVE_BOND} {NETWORK_UNIT}.", + "Directly nominating requires you to bond {NETWORK_UNIT} and choose validators you wish to back, and to nominate up to {MAX_NOMINATIONS} of them. To earn rewards as a nominator, you currently need to bond at least {MIN_ACTIVE_STAKE} {NETWORK_UNIT}.", "Joining a pool is much cheaper, requiring a minimum deposit of {MIN_POOL_JOIN_BOND} {NETWORK_UNIT}. Nominating validators is done on your behalf, and you simply claim rewards from the pool.", "Creating a pool is also possible, requiring a minimum of {MIN_POOL_CREATE_BOND} {NETWORK_UNIT}." ] ], - "managing_nominations": [ + "joinAnotherPool": [ + "Joining Another Pool", + "Switch to a different account to join another pool.", + [ + "Only one pool can be joined per account on {NETWORK_NAME}. To join more pools, firstly switch to another account." + ] + ], + "keepPoolNominating": [ + "Managing Your Pool", + "It is important to keep your pool actively nominating.", + [ + "It is important to keep your pool actively nominating.", + "If your pool is not earning rewards, members will be tempted to leave and join another pool.", + "Your root and nominator roles are important for managing your nominations. If nominations are not performing well, choose different ones to increase your pool's chances of earning rewards." + ] + ], + "managingNominations": [ "Managing Nominations", "Be sure to check the performance of your nominations on a regular basis.", [ @@ -58,7 +45,20 @@ "To maximise variety and a decentralised mix of validators, choose validators from a range of entities." ] ], - "monitoring_pool": [ + "module": { + "cancel": "Cancel", + "close": "Close", + "disableTips": "Disable Dashboard Tips", + "dismissResult": "Dismissing tips will remove them from your overview.", + "dismissTips": "Dismiss Tips", + "more": "More", + "of": "of", + "oneMoment": "One moment please", + "reEnable": "Tips can be re-enabled from dashboard settings, that can be accessed via the cog icon in the bottom left corner of the side menu.", + "syncingWith": "Syncing with {{network}}", + "tips": "Tips" + }, + "monitoringPool": [ "Managing Pool Membership", "It's a good idea to check regularly whether your pool is actively earning rewards.", [ @@ -66,23 +66,23 @@ "Monitor your pool's status to ensure that it is performing well regularly, and consider joining another pool if you are not receiving any rewards." ] ], - "join_another_pool": [ - "Joining Another Pool", - "Switch to a different account to join another pool.", + "recommendedJoinPool": [ + "Recommended: Join a Pool", + "Your account is best suited to join a pool.", [ - "Only one pool can be joined per account on {NETWORK_NAME}. To join more pools, firstly switch to another account." + "Based on the amount of {NETWORK_UNIT} your account currently holds, joining a pool is the best way for you to start staking.", + "Joining a pool requires a minimum deposit of {MIN_POOL_JOIN_BOND} {NETWORK_UNIT}." ] ], - "keep_pool_nominating": [ - "Managing Your Pool", - "It is important to keep your pool actively nominating.", + "recommendedNominator": [ + "Recommended: Become a Nominator", + "You have enough {NETWORK_UNIT} to become a nominator.", [ - "It is important to keep your pool actively nominating.", - "If your pool is not earning rewards, members will be tempted to leave and join another pool.", - "Your root and nominator roles are important for managing your nominations. If nominations are not performing well, choose different ones to increase your pool's chances of earning rewards." + "You have enough {NETWORK_UNIT} to become a nominator and start earning rewards.", + "Nominating directly does however require you to actively check your backed validators." ] ], - "reviewing_payouts": [ + "reviewingPayouts": [ "Reviewing Payouts", "Regularly reviewing your payouts is a good way to gauge how well your nominations are performing.", [ @@ -90,7 +90,7 @@ "Go to the Payouts page to get a detailed breakdown of each payout you receive, and by which validator (or pool) the payout came from." ] ], - "understanding_validator_performance": [ + "understandingValidatorPerformance": [ "Measuring Validator Performance", "Various factors affect how much validators are rewarded.", [ diff --git a/src/locale/index.tsx b/src/locale/index.tsx index b7459fd508..2699e028fb 100644 --- a/src/locale/index.tsx +++ b/src/locale/index.tsx @@ -1,56 +1,72 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import { AppVersion, DefaultLocale } from 'consts'; import { enGB, zhCN } from 'date-fns/locale'; import i18next from 'i18next'; import { initReactI18next } from 'react-i18next'; -import { AnyJson } from 'types'; +import { AppVersion, DefaultLocale } from 'consts'; +import type { AnyJson } from 'types'; import baseEn from './en/base.json'; import helpEn from './en/help.json'; +import libEn from './en/library.json'; +import modalsEn from './en/modals.json'; import pagesEn from './en/pages.json'; import tipsEn from './en/tips.json'; -import { doDynamicImport, getActiveLanguage, getResources } from './utils'; +import { doDynamicImport, getInitialLanguage, getResources } from './utils'; // available locales as key value pairs -export const locales: { [key: string]: AnyJson } = { +export const locales: Record<string, AnyJson> = { en: enGB, cn: zhCN, }; // available languages as an array of strings. -export const availableLanguages = ['en', 'cn']; +export const availableLanguages: Array<string[]> = [ + ['en', 'English'], + ['cn', '中文'], +]; // the supported namespaces. -export const lngNamespaces = ['base', 'help', 'tips', 'pages']; +export const lngNamespaces = [ + 'base', + 'help', + 'library', + 'modals', + 'pages', + 'tips', +]; // default structure of language resources. -const fallbackResources = { ...baseEn, ...helpEn, ...tipsEn, ...pagesEn }; +export const fallbackResources = { + ...baseEn, + ...helpEn, + ...libEn, + ...modalsEn, + ...pagesEn, + ...tipsEn, +}; -// check app version, wipe `lng_resources` if version is different. -const localAppVersion = localStorage.getItem('app_version'); -if (localAppVersion !== AppVersion || process.env.NODE_ENV === 'development') { +// Refresh local storage resources if in development, or if new app version is present. +if ( + localStorage.getItem('app_version') !== AppVersion || + import.meta.env.MODE === 'development' +) { localStorage.removeItem('lng_resources'); - // localisation is currently the only feature that uses AppVersion. - // if more features require AppVersion in the future, this should be - // abstracted into a separate script that checks / updates AppVersion - // after any tidy up is completed. - localStorage.setItem('app_version', AppVersion); } -// get active language. -const lng: string = getActiveLanguage(); +// get initial language. +const lng: string = getInitialLanguage(); // get default resources and whether a dynamic load is required for // the active language. -const { resources, dynamicLoad } = getResources(lng, fallbackResources); +const { resources, dynamicLoad } = getResources(lng); // default language to show before any dynamic load const defaultLng = dynamicLoad ? DefaultLocale : lng; // configure i18n object. i18next.use(initReactI18next).init({ - debug: process.env.REACT_APP_DEBUG_I18N === '1', + debug: import.meta.env.VITE_DEBUG_I18N === '1', fallbackLng: DefaultLocale, lng: defaultLng, resources, @@ -62,16 +78,14 @@ if (dynamicLoad) { } // map i18n to BCP 47 keys, with any custom amendments. -const i18ToLocaleMap: { [key: string]: string } = { - ...Object.fromEntries(availableLanguages.map((a: string) => [a, a])), +const i18ToLocaleMap: Record<string, string> = { + ...Object.fromEntries(availableLanguages.map((a) => [a[0], a[0]])), en: 'en-gb', cn: 'zh-cn', }; // convert i18n locale key to BCP 47 key if needed. -export const i18ToLocale = (l: string) => { - return i18ToLocaleMap[l] || DefaultLocale; -}; +export const i18ToLocale = (l: string) => i18ToLocaleMap[l] || DefaultLocale; // export i18next for context. export { i18next }; diff --git a/src/locale/utils.ts b/src/locale/utils.ts new file mode 100644 index 0000000000..9b1b47bfa1 --- /dev/null +++ b/src/locale/utils.ts @@ -0,0 +1,137 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { extractUrlValue, varToUrlHash } from '@polkadot-cloud/utils'; +import { DefaultLocale } from 'consts'; +import type { AnyApi, AnyJson } from 'types'; +import { availableLanguages, fallbackResources, lngNamespaces } from '.'; + +// Gets the active language +// +// Get the stored language from localStorage, or fallback to +// DefaultLocale otherwise. + +export const getInitialLanguage = () => { + // get language from url if present + const urlLng = extractUrlValue('l'); + if (availableLanguages.find((n: any) => n[0] === urlLng) && urlLng) { + localStorage.setItem('lng', urlLng); + return urlLng; + } + + // fall back to localStorage if present. + const localLng = localStorage.getItem('lng'); + if (availableLanguages.find((n: any) => n[0] === localLng) && localLng) { + return localLng; + } + + localStorage.setItem('lng', DefaultLocale); + return DefaultLocale; +}; + +// Determine resources of selected language, and whether a dynamic +// import is needed for missing language resources. +// +// If selected language is DefaultLocale, then we fall back to +// the default language resources that have already been imported. +export const getResources = (lng: string) => { + let dynamicLoad = false; + + let resources: AnyJson = null; + if (lng === DefaultLocale) { + // determine resources exist without dynamically importing them. + resources = { + en: fallbackResources, + }; + localStorage.setItem( + 'lng_resources', + JSON.stringify({ l: DefaultLocale, r: fallbackResources }) + ); + } else { + // not the default locale, check if local resources exist + let localValid = false; + const localResources = localStorage.getItem('lng_resources'); + if (localResources !== null) { + const { l, r } = JSON.parse(localResources); + + if (l === lng) { + localValid = true; + // local resources found, load them in + resources = { + [lng]: { + ...r, + }, + }; + } + } + if (!localValid) { + // no resources exist locally, dynamic import needed. + dynamicLoad = true; + resources = { + en: fallbackResources, + }; + } + } + return { + resources, + dynamicLoad, + }; +}; + +// Change language +// +// On click handler for changing language in-app. +export const changeLanguage = async (lng: string, i18next: AnyApi) => { + // check whether resources exist and need to by dynamically loaded. + const { resources, dynamicLoad } = getResources(lng); + + localStorage.setItem('lng', lng); + // dynamically load default language resources if needed. + if (dynamicLoad) { + await doDynamicImport(lng, i18next); + } else { + localStorage.setItem( + 'lng_resources', + JSON.stringify({ l: lng, r: resources }) + ); + i18next.changeLanguage(lng); + } + // update url `l` if needed. + varToUrlHash('l', lng, false); +}; + +// Load language resources dynamically. +// +// Bootstraps i18next with additional language resources. +export const loadLngAsync = async (l: string) => { + const resources: AnyJson = await Promise.all( + lngNamespaces.map(async (u) => { + const mod = await import(`./${l}/${u}.json`); + return mod; + }) + ); + + const r: AnyJson = {}; + resources.forEach((mod: AnyJson, i: number) => { + r[lngNamespaces[i]] = mod[lngNamespaces[i]]; + }); + + return { + l, + r, + }; +}; + +// Handles a dynamic import +// +// Once imports have been loaded, they are added to i18next as resources. +// Finally, the active langauge is changed to the imported language. +export const doDynamicImport = async (lng: string, i18next: AnyApi) => { + const { l, r } = await loadLngAsync(lng); + localStorage.setItem('lng_resources', JSON.stringify({ l: lng, r })); + + Object.entries(r).forEach(([ns, inner]: [string, AnyJson]) => { + i18next.addResourceBundle(l, ns, inner); + }); + i18next.changeLanguage(l); +}; diff --git a/src/main.tsx b/src/main.tsx new file mode 100644 index 0000000000..109a09bea8 --- /dev/null +++ b/src/main.tsx @@ -0,0 +1,27 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +// Network themes. +import '@polkadot-cloud/core/accent/polkadot-relay.css'; +import '@polkadot-cloud/core/accent/kusama-relay.css'; +import '@polkadot-cloud/core/accent/westend-relay.css'; + +// Default template fonts. +import '@polkadot-cloud/core/theme/default/fonts/index.css'; +// Default template theme. +import '@polkadot-cloud/core/theme/default/index.css'; + +// Polkadot Cloud core styles. +import '@polkadot-cloud/core/css/styles/index.css'; + +import { createRoot } from 'react-dom/client'; +import { App } from 'App'; + +// App font sizes. +import 'styles/index.scss'; + +const rootElement = document.getElementById('root'); +if (!rootElement) throw new Error('Failed to find the root element'); +const root = createRoot(rootElement); + +root.render(<App />); diff --git a/src/modals/AccountPoolRoles/Wrappers.ts b/src/modals/AccountPoolRoles/Wrappers.ts index 53632b5ecb..a974d6ad65 100644 --- a/src/modals/AccountPoolRoles/Wrappers.ts +++ b/src/modals/AccountPoolRoles/Wrappers.ts @@ -1,26 +1,17 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only import styled from 'styled-components'; -import { - backgroundSecondary, - backgroundToggle, - borderPrimary, - buttonPrimaryBackground, - textPrimary, - textSecondary, - textSuccess, -} from 'theme'; export const ContentWrapper = styled.div` width: 100%; > h4 { - color: ${textSecondary}; + color: var(--text-color-secondary); + border-bottom: 1px solid var(--border-primary-color); margin: 0.75rem 0; padding-bottom: 0.5rem; width: 100%; - border-bottom: 1px solid ${borderPrimary}; } .items { @@ -42,69 +33,3 @@ export const ContentWrapper = styled.div` } } `; - -export const StyledButton = styled.button` - background: ${buttonPrimaryBackground}; - padding: 1rem 1.2rem; - cursor: pointer; - margin-bottom: 1rem; - border-radius: 1rem; - display: inline-flex; - flex-flow: row wrap; - justify-content: flex-start; - align-items: center; - width: 100%; - - &:last-child { - margin-bottom: 0; - } - - h3 { - margin: 0 0 0 0.25rem; - } - - h4 { - margin: 0; - &.selected { - color: ${textSuccess}; - margin-left: 0.75rem; - } - } - - > *:last-child { - flex: 1; - display: flex; - flex-flow: row wrap; - justify-content: flex-end; - } - &:hover { - background: ${backgroundToggle}; - } - .icon { - margin-right: 0.75rem; - } - .details { - display: flex; - flex-flow: row wrap; - align-items: center; - > h4 { - margin-left: 1rem; - span { - margin-right: 0.5rem; - border: 1px solid ${borderPrimary}; - background: ${backgroundSecondary}; - padding: 0.25rem 0.75rem; - border-radius: 1rem; - } - } - } - - svg { - color: ${textSecondary}; - fill: ${textSecondary}; - } - p { - color: ${textPrimary}; - font-size: 1rem; - } -`; diff --git a/src/modals/AccountPoolRoles/index.tsx b/src/modals/AccountPoolRoles/index.tsx index dd058b8592..e27a8a211c 100644 --- a/src/modals/AccountPoolRoles/index.tsx +++ b/src/modals/AccountPoolRoles/index.tsx @@ -1,33 +1,31 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import { faBars, faChevronRight } from '@fortawesome/free-solid-svg-icons'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { useModal } from 'contexts/Modal'; +import { faBars } from '@fortawesome/free-solid-svg-icons'; +import { ButtonOption, ModalPadding, Polkicon } from '@polkadot-cloud/react'; +import { useTranslation } from 'react-i18next'; import { useActivePools } from 'contexts/Pools/ActivePools'; import { useBondedPools } from 'contexts/Pools/BondedPools'; import { usePoolMemberships } from 'contexts/Pools/PoolMemberships'; -import { BondedPool } from 'contexts/Pools/types'; -import Identicon from 'library/Identicon'; import { Title } from 'library/Modal/Title'; import { useStatusButtons } from 'pages/Pools/Home/Status/useStatusButtons'; -import { PaddingWrapper } from '../Wrappers'; -import { ContentWrapper, StyledButton } from './Wrappers'; +import { useOverlay } from '@polkadot-cloud/react/hooks'; +import { ContentWrapper } from './Wrappers'; export const AccountPoolRoles = () => { - const { config } = useModal(); + const { t } = useTranslation('modals'); + const { options } = useOverlay().modal.config; const { getAccountPools } = useBondedPools(); const { membership } = usePoolMemberships(); - const { who } = config; - + const { who } = options; const accountPools = getAccountPools(who); const totalAccountPools = Object.entries(accountPools).length; const { label } = useStatusButtons(); return ( <> - <Title title="All Pool Roles" icon={faBars} /> - <PaddingWrapper> + <Title title={t('allPoolRoles')} icon={faBars} /> + <ModalPadding> <ContentWrapper> {membership && ( <> @@ -38,8 +36,9 @@ export const AccountPoolRoles = () => { </> )} <h4> - Active Roles in <b>{totalAccountPools}</b> Pool - {totalAccountPools === 1 ? '' : 's'} + {t('activeRoles', { + count: totalAccountPools, + })} </h4> <div className="items"> {Object.entries(accountPools).map(([key, item]: any, i: number) => ( @@ -47,46 +46,42 @@ export const AccountPoolRoles = () => { ))} </div> </ContentWrapper> - </PaddingWrapper> + </ModalPadding> </> ); }; -const Button = ({ item, poolId }: { item: Array<string>; poolId: string }) => { - const { setStatus } = useModal(); +const Button = ({ item, poolId }: { item: string[]; poolId: string }) => { + const { t } = useTranslation('modals'); + const { setModalStatus } = useOverlay().modal; const { bondedPools } = useBondedPools(); const { setSelectedPoolId } = useActivePools(); - - const pool = bondedPools.find((b: BondedPool) => String(b.id) === poolId); + const pool = bondedPools.find((b) => String(b.id) === poolId); const stash = pool?.addresses?.stash || ''; return ( - <StyledButton + <ButtonOption + content disabled={false} - type="button" - className="action-button" onClick={() => { setSelectedPoolId(poolId); - setStatus(2); + setModalStatus('closing'); }} > <div className="icon"> - <Identicon value={stash} size={30} /> + <Polkicon address={stash} size={30} /> </div> <div className="details"> - <h3>Pool {poolId}</h3> + <h3> + {t('pool')} {poolId} + </h3> <h4> - {item.includes('root') && <span>Root</span>} - {item.includes('nominator') && <span>Nominator</span>} - {item.includes('stateToggler') && <span>State Toggler</span>} + {item.includes('root') ? <span>{t('root')}</span> : null} + {item.includes('nominator') ? <span>{t('nominator')}</span> : null} + {item.includes('bouncer') ? <span>{t('bouncer')}</span> : null} </h4> </div> - <div> - <FontAwesomeIcon transform="shrink-2" icon={faChevronRight} /> - </div> - </StyledButton> + </ButtonOption> ); }; - -export default AccountPoolRoles; diff --git a/src/modals/Accounts/Account.tsx b/src/modals/Accounts/Account.tsx new file mode 100644 index 0000000000..51b4d8bdd0 --- /dev/null +++ b/src/modals/Accounts/Account.tsx @@ -0,0 +1,136 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { faGlasses } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { ellipsisFn, planckToUnit } from '@polkadot-cloud/utils'; +import { useTranslation } from 'react-i18next'; +import { ExtensionIcons } from '@polkadot-cloud/assets/extensions'; +import LedgerSVG from '@polkadot-cloud/assets/extensions/svg/ledger.svg?react'; +import PolkadotVaultSVG from '@polkadot-cloud/assets/extensions/svg/polkadotvault.svg?react'; +import { Polkicon } from '@polkadot-cloud/react'; +import { useTransferOptions } from 'contexts/TransferOptions'; +import { useOverlay } from '@polkadot-cloud/react/hooks'; +import { useNetwork } from 'contexts/Network'; +import { useActiveAccounts } from 'contexts/ActiveAccounts'; +import { useImportedAccounts } from 'contexts/Connect/ImportedAccounts'; +import { AccountWrapper } from './Wrappers'; +import type { AccountItemProps } from './types'; + +export const AccountButton = ({ + label, + address, + delegator, + proxyType, + noBorder = false, +}: AccountItemProps) => { + const { t } = useTranslation('modals'); + const { getAccount } = useImportedAccounts(); + const { + activeProxy, + activeAccount, + setActiveAccount, + setActiveProxy, + activeProxyType, + } = useActiveAccounts(); + const { setModalStatus } = useOverlay().modal; + const { units, unit } = useNetwork().networkData; + const { getTransferOptions } = useTransferOptions(); + const { freeBalance } = getTransferOptions(address || ''); + + // Accumulate account data. + const meta = getAccount(address || ''); + + const imported = !!meta; + const connectTo = delegator || address || ''; + const connectProxy = delegator ? address || null : ''; + + // Determine account source icon. + const Icon = + meta?.source === 'ledger' + ? LedgerSVG + : meta?.source === 'vault' + ? PolkadotVaultSVG + : ExtensionIcons[meta?.source || ''] || undefined; + + // Determine if this account is active (active account or proxy). + const isActive = + (connectTo === activeAccount && + address === activeAccount && + !activeProxy) || + (connectProxy === activeProxy && + proxyType === activeProxyType && + activeProxy); + + // Handle account click. Handles both active account and active proxy. + const handleClick = () => { + if (!imported) return; + setActiveAccount(getAccount(connectTo)?.address || null); + setActiveProxy(proxyType ? { address: connectProxy, proxyType } : null); + setModalStatus('closing'); + }; + + return ( + <AccountWrapper className={isActive ? 'active' : undefined}> + <div className={noBorder ? 'noBorder' : undefined}> + <section className="head"> + <button + type="button" + onClick={() => handleClick()} + disabled={!imported} + > + {delegator && ( + <div className="delegator"> + <Polkicon address={delegator} size={23} /> + </div> + )} + <div className="identicon"> + <Polkicon address={address ?? ''} size={23} /> + </div> + <span className="name"> + {delegator && ( + <> + <span> + {proxyType} {t('proxy')} + </span> + </> + )} + {meta?.name ?? ellipsisFn(address ?? '')} + </span> + {meta?.source === 'external' && ( + <div + className="label warning" + style={{ color: '#a17703', paddingLeft: '0.5rem' }} + > + {t('readOnly')} + </div> + )} + <div className={label === undefined ? `` : label[0]}> + {label !== undefined ? <h5>{label[1]}</h5> : null} + {Icon !== undefined ? ( + <span className="icon"> + <Icon /> + </span> + ) : null} + + {meta?.source === 'external' && ( + <FontAwesomeIcon + icon={faGlasses} + className="icon" + style={{ opacity: 0.7 }} + /> + )} + </div> + </button> + </section> + <section className="foot"> + <span className="balance"> + {`${t('free')}: ${planckToUnit(freeBalance, units) + .decimalPlaces(3) + .toFormat()} ${unit}`} + </span> + </section> + </div> + </AccountWrapper> + ); +}; diff --git a/src/modals/Accounts/Delegates/Wrapper.ts b/src/modals/Accounts/Delegates/Wrapper.ts new file mode 100644 index 0000000000..698a5ff8c6 --- /dev/null +++ b/src/modals/Accounts/Delegates/Wrapper.ts @@ -0,0 +1,22 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { styled } from 'styled-components'; + +export const DelegatesWrapper = styled.div` + border-left: 1px solid var(--border-primary-color); + width: 100%; + display: flex; + flex-direction: column; + padding-left: 1rem; + margin: 0.65rem 0 1.25rem 0; + + > div { + &:first-child { + margin-top: 0; + } + &:last-child { + margin-bottom: 0; + } + } +`; diff --git a/src/modals/Accounts/Delegates/index.tsx b/src/modals/Accounts/Delegates/index.tsx new file mode 100644 index 0000000000..96ed618870 --- /dev/null +++ b/src/modals/Accounts/Delegates/index.tsx @@ -0,0 +1,40 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { isSupportedProxy } from 'config/proxies'; +import { useImportedAccounts } from 'contexts/Connect/ImportedAccounts'; +import { AccountButton } from '../Account'; +import { DelegatesWrapper } from './Wrapper'; +import type { DelegatesProps } from '../types'; + +export const Delegates = ({ delegates, delegator }: DelegatesProps) => { + const { accounts } = useImportedAccounts(); + const { getAccount } = useImportedAccounts(); + + // Filter delegates that are external or not imported. Default to empty array if there are no + // delegates for this address. + const delegatesList = + delegates?.delegates.filter( + ({ delegate, proxyType }) => + accounts.find(({ address }) => address === delegate) !== undefined && + isSupportedProxy(proxyType) && + getAccount(delegate || null)?.source !== 'external' + ) || []; + + return ( + <> + {delegatesList.length ? ( + <DelegatesWrapper> + {delegatesList.map(({ delegate, proxyType }, i) => ( + <AccountButton + key={`_del_${i}`} + address={delegate} + delegator={delegator} + proxyType={proxyType} + /> + ))} + </DelegatesWrapper> + ) : null} + </> + ); +}; diff --git a/src/modals/Accounts/Wrappers.ts b/src/modals/Accounts/Wrappers.ts new file mode 100644 index 0000000000..01407c1aa1 --- /dev/null +++ b/src/modals/Accounts/Wrappers.ts @@ -0,0 +1,163 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import styled from 'styled-components'; + +export const AccountWrapper = styled.div` + transition: transform var(--transition-duration); + margin: 0.6rem 0 0 0; + width: 100%; + + &.active { + > div { + border: 1px solid var(--accent-color-primary); + } + } + + &:hover { + transform: scale(1.01); + } + + > div { + background: var(--button-primary-background); + color: var(--text-color-primary); + font-family: InterSemiBold, sans-serif; + border: 1px solid transparent; + display: flex; + align-items: flex-start; + flex-direction: column; + border-radius: 0.85rem; + width: 100%; + overflow: hidden; + + &.noBorder { + border: none; + } + + > section { + display: flex; + flex-direction: row; + align-items: center; + width: 100%; + + /* Top half of the button, account information */ + &.head { + background: var(--button-tertiary-background); + + > button { + color: var(--text-color-primary); + display: flex; + align-items: center; + justify-content: flex-start; + flex-shrink: 1; + padding: 0.5rem 0.75rem; + font-size: 1.05rem; + width: 100%; + transition: background var(--transition-duration); + + &:hover { + .name { + color: var(--accent-color-primary); + } + } + + .label { + font-size: 0.95rem; + display: flex; + align-items: flex-end; + } + + overflow: hidden; + .name { + transition: color var(--transition-duration); + font-family: InterSemiBold, sans-serif; + max-width: 100%; + margin: 0 0.5rem; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + + > span { + opacity: 0.7; + margin-right: 0.6rem; + > svg { + margin-left: 0.5rem; + } + } + } + + .badge { + background-color: var(--background-floating-card); + color: var(--text-color-secondary); + margin-left: 1rem; + padding: 0.25rem 0.5rem; + border-radius: 0.45rem; + font-size: 0.9rem; + } + .delegator { + width: 1rem; + z-index: 0; + } + .identicon { + z-index: 1; + } + + /* svg theming */ + svg { + .light { + fill: var(--text-color-invert); + } + .dark { + fill: var(--text-color-secondary); + } + } + + > div:last-child { + display: flex; + flex-grow: 1; + justify-content: flex-end; + + &.neutral { + h5 { + color: var(--text-color-secondary); + opacity: 0.75; + } + } + &.danger { + h5 { + color: var(--status-danger-color); + } + } + .icon { + width: 1.25rem; + height: 1.25rem; + margin-left: 0.75rem; + + svg { + width: inherit; + height: inherit; + } + } + } + } + } + + /* Bottom half of the button, account metadata */ + &.foot { + border-top: 1px solid var(--border-primary-color); + padding: 0.7rem 1rem; + + > .balance { + color: var(--text-color-secondary); + font-size: 0.9rem; + opacity: 0.6; + } + } + } + } +`; + +export const AccountSeparator = styled.div` + width: 100%; + height: 0.5rem; +`; diff --git a/src/modals/Accounts/index.tsx b/src/modals/Accounts/index.tsx new file mode 100644 index 0000000000..1f534ec27f --- /dev/null +++ b/src/modals/Accounts/index.tsx @@ -0,0 +1,245 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { faChevronLeft, faLinkSlash } from '@fortawesome/free-solid-svg-icons'; +import { + ActionItem, + ButtonPrimaryInvert, + ButtonText, + ModalCustomHeader, + ModalPadding, +} from '@polkadot-cloud/react'; +import React, { useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useBalances } from 'contexts/Balances'; +import { useBonded } from 'contexts/Bonded'; +import { + useExtensions, + useEffectIgnoreInitial, + useOverlay, +} from '@polkadot-cloud/react/hooks'; +import { usePoolMemberships } from 'contexts/Pools/PoolMemberships'; +import { useProxies } from 'contexts/Proxies'; +import { useActiveAccounts } from 'contexts/ActiveAccounts'; +import { useImportedAccounts } from 'contexts/Connect/ImportedAccounts'; +import { AccountButton } from './Account'; +import { Delegates } from './Delegates'; +import { AccountSeparator, AccountWrapper } from './Wrappers'; +import type { + AccountInPool, + AccountNominating, + AccountNominatingAndInPool, + AccountNotStaking, +} from './types'; + +export const Accounts = () => { + const { t } = useTranslation('modals'); + const { balances } = useBalances(); + const { getDelegates } = useProxies(); + const { bondedAccounts } = useBonded(); + const { ledgers, getLocks } = useBalances(); + const { extensionsStatus } = useExtensions(); + const { memberships } = usePoolMemberships(); + const { + replaceModal, + status: modalStatus, + setModalResize, + } = useOverlay().modal; + const { accounts } = useImportedAccounts(); + const { activeAccount, setActiveAccount, setActiveProxy } = + useActiveAccounts(); + + // Store local copy of accounts. + const [localAccounts, setLocalAccounts] = useState(accounts); + + const stashes: string[] = []; + // accumulate imported stash accounts + for (const { address } of localAccounts) { + const locks = getLocks(address); + + // account is a stash if they have an active `staking` lock + if (locks.find(({ id }) => id === 'staking')) { + stashes.push(address); + } + } + + // construct account groupings + const nominating: AccountNominating[] = []; + const inPool: AccountInPool[] = []; + const nominatingAndPool: AccountNominatingAndInPool[] = []; + const notStaking: AccountNotStaking[] = []; + + for (const { address } of localAccounts) { + let isNominating = false; + let isInPool = false; + const isStash = stashes[stashes.indexOf(address)] ?? null; + const delegates = getDelegates(address); + + const poolMember = memberships.find((m) => m.address === address) ?? null; + + // Check if nominating. + if (isStash && nominating.find((a) => a.address === address) === undefined) + isNominating = true; + + // Check if in pool. + if (poolMember) + if (!inPool.find((n) => n.address === address)) isInPool = true; + + // If not doing anything, add address to `notStaking`. + if ( + !isStash && + !poolMember && + !notStaking.find((n) => n.address === address) + ) { + notStaking.push({ address, delegates }); + continue; + } + + // If both nominating and in pool, add to this list. + if ( + isNominating && + isInPool && + poolMember && + !nominatingAndPool.find((n) => n.address === address) + ) { + nominatingAndPool.push({ + ...poolMember, + address, + stashImported: true, + delegates, + }); + continue; + } + + // Nominating only. + if (isNominating && !isInPool) { + nominating.push({ address, stashImported: true, delegates }); + continue; + } + + // In pool only. + if (!isNominating && isInPool && poolMember) + inPool.push({ ...poolMember, delegates }); + } + + // Refresh local accounts state when context accounts change. + useEffect(() => setLocalAccounts(accounts), [accounts]); + + // Resize if modal open upon state changes. + useEffectIgnoreInitial(() => { + if (modalStatus === 'open') setModalResize(); + }, [ + activeAccount, + accounts, + bondedAccounts, + balances, + ledgers, + extensionsStatus, + ]); + + return ( + <ModalPadding> + <ModalCustomHeader> + <div className="first"> + <h1>{t('accounts')}</h1> + <ButtonPrimaryInvert + text={t('goToConnect')} + iconLeft={faChevronLeft} + iconTransform="shrink-3" + onClick={() => + replaceModal({ key: 'Connect', options: { disableScroll: true } }) + } + marginLeft + /> + </div> + <div> + {activeAccount && ( + <ButtonText + style={{ + color: 'var(--accent-color-primary)', + }} + text={t('disconnect')} + iconRight={faLinkSlash} + onClick={() => { + setActiveAccount(null); + setActiveProxy(null); + }} + /> + )} + </div> + </ModalCustomHeader> + {!activeAccount && !accounts.length && ( + <AccountWrapper style={{ marginTop: '1.5rem' }}> + <div> + <div> + <h4 style={{ padding: '0.75rem 1rem' }}> + {t('noActiveAccount')} + </h4> + </div> + <div /> + </div> + </AccountWrapper> + )} + + {nominatingAndPool.length ? ( + <> + <AccountSeparator /> + <ActionItem text={t('nominatingAndInPool')} /> + {nominatingAndPool.map(({ address, delegates }, i) => ( + <React.Fragment key={`acc_nominating_and_pool_${i}`}> + <AccountButton address={address} /> + {address && ( + <Delegates delegator={address} delegates={delegates} /> + )} + </React.Fragment> + ))} + </> + ) : null} + + {nominating.length ? ( + <> + <AccountSeparator /> + <ActionItem text={t('nominating')} /> + {nominating.map(({ address, delegates }, i) => ( + <React.Fragment key={`acc_nominating_${i}`}> + <AccountButton address={address} /> + {address && ( + <Delegates delegator={address} delegates={delegates} /> + )} + </React.Fragment> + ))} + </> + ) : null} + + {inPool.length ? ( + <> + <AccountSeparator /> + <ActionItem text={t('inPool')} /> + {inPool.map(({ address, delegates }, i) => ( + <React.Fragment key={`acc_in_pool_${i}`}> + <AccountButton address={address} /> + {address && ( + <Delegates delegator={address} delegates={delegates} /> + )} + </React.Fragment> + ))} + </> + ) : null} + + {notStaking.length ? ( + <> + <AccountSeparator /> + <ActionItem text={t('notStaking')} /> + {notStaking.map(({ address, delegates }, i) => ( + <React.Fragment key={`acc_not_staking_${i}`}> + <AccountButton address={address} /> + {address && ( + <Delegates delegator={address} delegates={delegates} /> + )} + </React.Fragment> + ))} + </> + ) : null} + </ModalPadding> + ); +}; diff --git a/src/modals/Accounts/types.ts b/src/modals/Accounts/types.ts new file mode 100644 index 0000000000..8584ec8439 --- /dev/null +++ b/src/modals/Accounts/types.ts @@ -0,0 +1,37 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import type { PoolMembership } from 'contexts/Pools/types'; +import type { Proxy } from 'contexts/Proxies/type'; +import type { MaybeAddress } from 'types'; + +export interface AccountItemProps { + address?: MaybeAddress; + label?: string[]; + asElement?: boolean; + delegator?: string; + noBorder?: boolean; + proxyType?: string; +} + +export interface DelegatesProps { + delegator: string; + delegates: Proxy | undefined; +} + +export interface AccountInPool extends PoolMembership { + delegates?: Proxy; +} + +export interface AccountNominating { + address: MaybeAddress; + stashImported: boolean; + delegates?: Proxy; +} + +export interface AccountNotStaking { + address: string; + delegates?: Proxy; +} + +export type AccountNominatingAndInPool = AccountNominating & AccountInPool; diff --git a/src/modals/BalanceTest/index.tsx b/src/modals/BalanceTest/index.tsx new file mode 100644 index 0000000000..44203c2bf4 --- /dev/null +++ b/src/modals/BalanceTest/index.tsx @@ -0,0 +1,74 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { ModalPadding } from '@polkadot-cloud/react'; +import { unitToPlanck } from '@polkadot-cloud/utils'; +import { useApi } from 'contexts/Api'; +import { useOverlay } from '@polkadot-cloud/react/hooks'; +import { useTxMeta } from 'contexts/TxMeta'; +import { useBatchCall } from 'library/Hooks/useBatchCall'; +import { useSubmitExtrinsic } from 'library/Hooks/useSubmitExtrinsic'; +import { Close } from 'library/Modal/Close'; +import { SubmitTx } from 'library/SubmitTx'; +import { useEffect } from 'react'; +import { useNetwork } from 'contexts/Network'; +import { useActiveAccounts } from 'contexts/ActiveAccounts'; + +export const BalanceTest = () => { + const { api } = useApi(); + const { + networkData: { units }, + } = useNetwork(); + const { activeAccount } = useActiveAccounts(); + const { notEnoughFunds } = useTxMeta(); + const { newBatchCall } = useBatchCall(); + const { setModalStatus, setModalResize } = useOverlay().modal; + + // tx to submit + const getTx = () => { + const tx = null; + if (!api || !activeAccount) { + return tx; + } + + const txs = [ + api.tx.balances.transfer( + { + id: '1554u1a67ApEt5xmjbZwjgDNaVckbzB6cjRHWAQ1SpNkNxTd', + }, + unitToPlanck('0.1', units).toString() + ), + api.tx.balances.transfer( + { + id: '1554u1a67ApEt5xmjbZwjgDNaVckbzB6cjRHWAQ1SpNkNxTd', + }, + unitToPlanck('0.05', units).toString() + ), + ]; + const batch = newBatchCall(txs, activeAccount); + + return batch; + }; + + const submitExtrinsic = useSubmitExtrinsic({ + tx: getTx(), + from: activeAccount, + shouldSubmit: true, + callbackSubmit: () => { + setModalStatus('closing'); + }, + callbackInBlock: () => {}, + }); + + useEffect(() => setModalResize(), [notEnoughFunds]); + + return ( + <> + <Close /> + <ModalPadding> + <h2 className="title unbounded">Balance Test</h2> + </ModalPadding> + <SubmitTx valid {...submitExtrinsic} /> + </> + ); +}; diff --git a/src/modals/Bio/Wrapper.ts b/src/modals/Bio/Wrapper.ts index 9dabeed715..2c3566adcb 100644 --- a/src/modals/Bio/Wrapper.ts +++ b/src/modals/Bio/Wrapper.ts @@ -1,27 +1,19 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only import styled from 'styled-components'; -import { textPrimary } from 'theme'; export const Wrapper = styled.div` display: flex; flex-flow: column wrap; - align-items: flex-start; - justify-content: flex-start; padding: 0.5rem; h2 { + color: var(--text-color-primary); margin-top: 0.5rem; - margin-bottom: 0; - color: ${textPrimary}; } h3 { margin-bottom: 0.5rem; } - - h4 { - margin: 0; - } `; diff --git a/src/modals/Bio/index.tsx b/src/modals/Bio/index.tsx index da9afceef1..5afc2cc22c 100644 --- a/src/modals/Bio/index.tsx +++ b/src/modals/Bio/index.tsx @@ -1,23 +1,20 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import { useModal } from 'contexts/Modal'; +import { ModalPadding } from '@polkadot-cloud/react'; import { Title } from 'library/Modal/Title'; -import { PaddingWrapper } from '../Wrappers'; +import { useOverlay } from '@polkadot-cloud/react/hooks'; import { Wrapper } from './Wrapper'; export const Bio = () => { - const { config } = useModal(); - const { name, bio } = config; + const { name, bio } = useOverlay().modal.config.options; return ( <> <Title title={name} /> - <PaddingWrapper> + <ModalPadding> <Wrapper>{bio !== undefined && <h4>{bio}</h4>}</Wrapper> - </PaddingWrapper> + </ModalPadding> </> ); }; - -export default Bio; diff --git a/src/modals/Bond/index.tsx b/src/modals/Bond/index.tsx new file mode 100644 index 0000000000..81104962d5 --- /dev/null +++ b/src/modals/Bond/index.tsx @@ -0,0 +1,179 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { ModalPadding, ModalWarnings } from '@polkadot-cloud/react'; +import { planckToUnit, unitToPlanck } from '@polkadot-cloud/utils'; +import BigNumber from 'bignumber.js'; +import { useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useApi } from 'contexts/Api'; +import { useActivePools } from 'contexts/Pools/ActivePools'; +import { useTransferOptions } from 'contexts/TransferOptions'; +import { BondFeedback } from 'library/Form/Bond/BondFeedback'; +import { Warning } from 'library/Form/Warning'; +import { useBondGreatestFee } from 'library/Hooks/useBondGreatestFee'; +import { useSignerWarnings } from 'library/Hooks/useSignerWarnings'; +import { useSubmitExtrinsic } from 'library/Hooks/useSubmitExtrinsic'; +import { Close } from 'library/Modal/Close'; +import { SubmitTx } from 'library/SubmitTx'; +import { useTxMeta } from 'contexts/TxMeta'; +import { useOverlay } from '@polkadot-cloud/react/hooks'; +import { useNetwork } from 'contexts/Network'; +import { useActiveAccounts } from 'contexts/ActiveAccounts'; + +export const Bond = () => { + const { t } = useTranslation('modals'); + const { api } = useApi(); + const { + networkData: { units, unit }, + } = useNetwork(); + const { activeAccount } = useActiveAccounts(); + const { notEnoughFunds } = useTxMeta(); + const { selectedActivePool } = useActivePools(); + const { getSignerWarnings } = useSignerWarnings(); + const { feeReserve, getTransferOptions } = useTransferOptions(); + const { + setModalStatus, + config: { options }, + setModalResize, + } = useOverlay().modal; + + const { bondFor } = options; + const isStaking = bondFor === 'nominator'; + const isPooling = bondFor === 'pool'; + const { nominate, pool } = getTransferOptions(activeAccount); + + const freeBalanceBn = + bondFor === 'nominator' + ? nominate.totalAdditionalBond + : pool.totalAdditionalBond; + + const freeBalance = planckToUnit(freeBalanceBn.minus(feeReserve), units); + const largestTxFee = useBondGreatestFee({ bondFor }); + + // calculate any unclaimed pool rewards. + let { pendingRewards } = selectedActivePool || {}; + pendingRewards = pendingRewards ?? new BigNumber(0); + pendingRewards = planckToUnit(pendingRewards, units); + + // local bond value. + const [bond, setBond] = useState<{ bond: string }>({ + bond: freeBalance.toString(), + }); + + // bond valid. + const [bondValid, setBondValid] = useState<boolean>(false); + + // feedback errors to trigger modal resize + const [feedbackErrors, setFeedbackErrors] = useState<string[]>([]); + + // bond minus tx fees. + const enoughToCoverTxFees: boolean = freeBalance + .minus(bond.bond) + .isGreaterThan(planckToUnit(largestTxFee, units)); + + // bond value after max tx fees have been deducated. + let bondAfterTxFees: BigNumber; + + if (enoughToCoverTxFees) { + bondAfterTxFees = unitToPlanck(String(bond.bond), units); + } else { + bondAfterTxFees = BigNumber.max( + unitToPlanck(String(bond.bond), units).minus(largestTxFee), + 0 + ); + } + + // update bond value on task change. + useEffect(() => { + setBond({ bond: freeBalance.toString() }); + }, [freeBalance.toString()]); + + // determine whether this is a pool or staking transaction. + const determineTx = (bondToSubmit: BigNumber) => { + let tx = null; + if (!api) { + return tx; + } + + const bondAsString = !bondValid + ? '0' + : bondToSubmit.isNaN() + ? '0' + : bondToSubmit.toString(); + + if (isPooling) { + tx = api.tx.nominationPools.bondExtra({ + FreeBalance: bondAsString, + }); + } else if (isStaking) { + tx = api.tx.staking.bondExtra(bondAsString); + } + return tx; + }; + + // the actual bond tx to submit + const getTx = (bondToSubmit: BigNumber) => { + if (!api || !activeAccount) { + return null; + } + return determineTx(bondToSubmit); + }; + + const submitExtrinsic = useSubmitExtrinsic({ + tx: getTx(bondAfterTxFees), + from: activeAccount, + shouldSubmit: bondValid, + callbackSubmit: () => { + setModalStatus('closing'); + }, + callbackInBlock: () => {}, + }); + + const warnings = getSignerWarnings( + activeAccount, + false, + submitExtrinsic.proxySupported + ); + + // modal resize on form update + useEffect( + () => setModalResize(), + [bond, bondValid, notEnoughFunds, feedbackErrors.length, warnings.length] + ); + + return ( + <> + <Close /> + <ModalPadding> + <h2 className="title unbounded">{t('addToBond')}</h2> + {pendingRewards > 0 && bondFor === 'pool' ? ( + <ModalWarnings withMargin> + <Warning + text={`${t('bondingWithdraw')} ${pendingRewards} ${unit}.`} + /> + </ModalWarnings> + ) : null} + <BondFeedback + syncing={largestTxFee.isZero()} + bondFor={bondFor} + listenIsValid={(valid, errors) => { + setBondValid(valid); + setFeedbackErrors(errors); + }} + defaultBond={null} + setters={[ + { + set: setBond, + current: bond, + }, + ]} + parentErrors={warnings} + txFees={largestTxFee} + /> + <p>{t('newlyBondedFunds')}</p> + </ModalPadding> + <SubmitTx valid={bondValid} {...submitExtrinsic} /> + </> + ); +}; diff --git a/src/modals/ChangeNominations/index.tsx b/src/modals/ChangeNominations/index.tsx index 5a3d24c6de..3801a330c7 100644 --- a/src/modals/ChangeNominations/index.tsx +++ b/src/modals/ChangeNominations/index.tsx @@ -1,40 +1,43 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import { faArrowAltCircleUp } from '@fortawesome/free-regular-svg-icons'; -import { faStopCircle } from '@fortawesome/free-solid-svg-icons'; -import { ButtonSubmit } from '@rossbulat/polkadot-dashboard-ui'; +import { + ModalPadding, + ModalSeparator, + ModalWarnings, +} from '@polkadot-cloud/react'; +import { useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; import { useApi } from 'contexts/Api'; -import { useBalances } from 'contexts/Balances'; -import { useConnect } from 'contexts/Connect'; -import { useModal } from 'contexts/Modal'; +import { useBonded } from 'contexts/Bonded'; import { useActivePools } from 'contexts/Pools/ActivePools'; -import { useTxFees } from 'contexts/TxFees'; -import { EstimatedTxFee } from 'library/EstimatedTxFee'; import { Warning } from 'library/Form/Warning'; +import { useSignerWarnings } from 'library/Hooks/useSignerWarnings'; import { useSubmitExtrinsic } from 'library/Hooks/useSubmitExtrinsic'; -import { Title } from 'library/Modal/Title'; -import { useEffect, useState } from 'react'; -import { - FooterWrapper, - NotesWrapper, - PaddingWrapper, - Separator, -} from '../Wrappers'; +import { Close } from 'library/Modal/Close'; +import { SubmitTx } from 'library/SubmitTx'; +import { useTxMeta } from 'contexts/TxMeta'; +import { useOverlay } from '@polkadot-cloud/react/hooks'; +import { useActiveAccounts } from 'contexts/ActiveAccounts'; export const ChangeNominations = () => { + const { t } = useTranslation('modals'); const { api } = useApi(); - const { activeAccount, accountHasSigner } = useConnect(); - const { getBondedAccount, getAccountNominations } = useBalances(); - const { setStatus: setModalStatus, config } = useModal(); + const { activeAccount } = useActiveAccounts(); + const { notEnoughFunds } = useTxMeta(); + const { getSignerWarnings } = useSignerWarnings(); + const { getBondedAccount, getAccountNominations } = useBonded(); + const { + setModalStatus, + config: { options }, + setModalResize, + } = useOverlay().modal; const { poolNominations, isNominator, isOwner, selectedActivePool } = useActivePools(); - const { txFeesValid } = useTxFees(); + const { nominations: newNominations, provider, bondFor } = options; - const { nominations: newNominations, provider, bondType } = config; - - const isPool = bondType === 'pool'; - const isStaking = bondType === 'stake'; + const isPool = bondFor === 'pool'; + const isStaking = bondFor === 'nominator'; const controller = getBondedAccount(activeAccount); const signingAccount = isPool ? activeAccount : controller; @@ -58,9 +61,10 @@ export const ChangeNominations = () => { if (isPool) { isValid = (isNominator() || isOwner()) ?? false; } - useEffect(() => { - setValid(isValid); - }, [isValid]); + + useEffect(() => setModalResize(), [notEnoughFunds]); + + useEffect(() => setValid(isValid), [isValid]); // tx to submit const getTx = () => { @@ -99,12 +103,12 @@ export const ChangeNominations = () => { return tx; }; - const { submitTx, submitting } = useSubmitExtrinsic({ + const submitExtrinsic = useSubmitExtrinsic({ tx: getTx(), from: signingAccount, shouldSubmit: valid, callbackSubmit: () => { - setModalStatus(2); + setModalStatus('closing'); // if removing a subset of nominations, reset selected list if (provider) { @@ -115,59 +119,37 @@ export const ChangeNominations = () => { callbackInBlock: () => {}, }); + const warnings = getSignerWarnings( + activeAccount, + isStaking, + submitExtrinsic.proxySupported + ); + + if (!nominations.length) { + warnings.push(`${t('noNominationsSet')}`); + } + return ( <> - <Title title="Stop Nominating" icon={faStopCircle} /> - <PaddingWrapper verticalOnly> - <div - style={{ - padding: '0 1.25rem', - width: '100%', - }} - > - {!nominations.length && ( - <Warning text="You have no nominations set." /> - )} - {!accountHasSigner(signingAccount) && ( - <Warning - text={`You must have your${ - bondType === 'stake' ? ' controller ' : ' ' - }account imported to stop nominating.`} - /> - )} - <h2> - Stop {!remaining ? 'All Nomination' : `${removing} Nomination`} - {removing === 1 ? '' : 's'} - </h2> - <Separator /> - <NotesWrapper> - <p> - Once submitted, your nominations will be removed from your - dashboard immediately, and will not be nominated from the start of - the next era. - </p> - <EstimatedTxFee /> - </NotesWrapper> - <FooterWrapper> - <div> - <ButtonSubmit - text={`Submit${submitting ? 'ting' : ''}`} - iconLeft={faArrowAltCircleUp} - iconTransform="grow-2" - onClick={() => submitTx()} - disabled={ - !valid || - submitting || - !accountHasSigner(signingAccount) || - !txFeesValid - } - /> - </div> - </FooterWrapper> - </div> - </PaddingWrapper> + <Close /> + <ModalPadding> + <h2 className="title unbounded"> + {t('stop')}{' '} + {!remaining + ? t('allNominations') + : `${t('nomination', { count: removing })}`} + </h2> + <ModalSeparator /> + {warnings.length ? ( + <ModalWarnings> + {warnings.map((text, i) => ( + <Warning key={`warning_${i}`} text={text} /> + ))} + </ModalWarnings> + ) : null} + <p>{t('changeNomination')}</p> + </ModalPadding> + <SubmitTx fromController={isStaking} valid={valid} {...submitExtrinsic} /> </> ); }; - -export default ChangeNominations; diff --git a/src/modals/ChangePoolRoles/RoleChange.tsx b/src/modals/ChangePoolRoles/RoleChange.tsx index 592d633b84..b2bb1b9455 100644 --- a/src/modals/ChangePoolRoles/RoleChange.tsx +++ b/src/modals/ChangePoolRoles/RoleChange.tsx @@ -1,10 +1,10 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only import { faAnglesRight } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import Identicon from 'library/Identicon'; -import { clipAddress, convertRemToPixels } from 'Utils'; +import { ellipsisFn, remToUnit } from '@polkadot-cloud/utils'; +import { Polkicon } from '@polkadot-cloud/react'; import { RoleChangeWrapper } from './Wrapper'; export const RoleChange = ({ roleName, oldAddress, newAddress }: any) => { @@ -13,28 +13,22 @@ export const RoleChange = ({ roleName, oldAddress, newAddress }: any) => { <div className="label">{roleName}</div> <div className="role-change"> <div className="input-wrap selected"> - <Identicon - value={oldAddress ?? ''} - size={convertRemToPixels('2rem')} - /> + <Polkicon address={oldAddress ?? ''} size={remToUnit('2rem')} /> <input className="input" disabled - value={oldAddress ? clipAddress(oldAddress) : ''} + value={oldAddress ? ellipsisFn(oldAddress) : ''} /> </div> <span> <FontAwesomeIcon icon={faAnglesRight} /> </span> <div className="input-wrap selected"> - <Identicon - value={newAddress ?? ''} - size={convertRemToPixels('2rem')} - /> + <Polkicon address={newAddress ?? ''} size={remToUnit('2rem')} /> <input className="input" disabled - value={newAddress ? clipAddress(newAddress) : ''} + value={newAddress ? ellipsisFn(newAddress) : ''} /> </div> </div> diff --git a/src/modals/ChangePoolRoles/Wrapper.ts b/src/modals/ChangePoolRoles/Wrapper.ts index 44c1533514..7ce538a0d5 100644 --- a/src/modals/ChangePoolRoles/Wrapper.ts +++ b/src/modals/ChangePoolRoles/Wrapper.ts @@ -1,15 +1,13 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only import styled from 'styled-components'; -import { borderPrimary, textSecondary } from 'theme'; export const Wrapper = styled.div` display: flex; flex-flow: column wrap; - align-items: flex-start; - justify-content: flex-start; - padding: 1.25rem 0; + margin-top: 1rem; + width: 100%; `; export const RoleChangeWrapper = styled.div` @@ -19,7 +17,7 @@ export const RoleChangeWrapper = styled.div` overflow: hidden; .label { - color: ${textSecondary}; + color: var(--text-color-secondary); margin: 0.25rem 0 0.75rem 0; } .role-change { @@ -29,14 +27,14 @@ export const RoleChangeWrapper = styled.div` margin-bottom: 1rem; > span { + color: var(--text-color-secondary); margin: 0 0.75rem; - color: ${textSecondary}; opacity: 0.5; } } .input-wrap { - border-bottom: 1px solid ${borderPrimary}; + border-bottom: 1px solid var(--border-primary-color); display: flex; flex-flow: row wrap; align-items: center; @@ -45,7 +43,7 @@ export const RoleChangeWrapper = styled.div` flex: 1; &.selected { - border: 1px solid ${borderPrimary}; + border: 1px solid var(--border-primary-color); border-radius: 1rem; margin: 0; padding: 0.1rem 0.75rem; @@ -60,5 +58,3 @@ export const RoleChangeWrapper = styled.div` overflow: hidden; } `; - -export default Wrapper; diff --git a/src/modals/ChangePoolRoles/index.tsx b/src/modals/ChangePoolRoles/index.tsx index ad3f9611e5..53845ead90 100644 --- a/src/modals/ChangePoolRoles/index.tsx +++ b/src/modals/ChangePoolRoles/index.tsx @@ -1,29 +1,32 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import { faArrowAltCircleUp } from '@fortawesome/free-regular-svg-icons'; -import { faExchangeAlt } from '@fortawesome/free-solid-svg-icons'; -import { ButtonSubmit } from '@rossbulat/polkadot-dashboard-ui'; +import { ModalPadding } from '@polkadot-cloud/react'; +import { useTranslation } from 'react-i18next'; import { useApi } from 'contexts/Api'; -import { useConnect } from 'contexts/Connect'; -import { useModal } from 'contexts/Modal'; import { useBondedPools } from 'contexts/Pools/BondedPools'; -import { useTxFees } from 'contexts/TxFees'; -import { EstimatedTxFee } from 'library/EstimatedTxFee'; import { useSubmitExtrinsic } from 'library/Hooks/useSubmitExtrinsic'; -import { Title } from 'library/Modal/Title'; -import { FooterWrapper, NotesWrapper } from '../Wrappers'; +import { Close } from 'library/Modal/Close'; +import { SubmitTx } from 'library/SubmitTx'; +import { useTxMeta } from 'contexts/TxMeta'; +import { useEffect } from 'react'; +import { useOverlay } from '@polkadot-cloud/react/hooks'; +import { useActiveAccounts } from 'contexts/ActiveAccounts'; import { RoleChange } from './RoleChange'; -import Wrapper from './Wrapper'; +import { Wrapper } from './Wrapper'; export const ChangePoolRoles = () => { + const { t } = useTranslation('modals'); const { api } = useApi(); - const { setStatus: setModalStatus } = useModal(); + const { activeAccount } = useActiveAccounts(); + const { notEnoughFunds } = useTxMeta(); const { replacePoolRoles } = useBondedPools(); - const { activeAccount, accountHasSigner } = useConnect(); - const { config } = useModal(); - const { txFeesValid } = useTxFees(); - const { id: poolId, roleEdits } = config; + const { + setModalStatus, + config: { options }, + setModalResize, + } = useOverlay().modal; + const { id: poolId, roleEdits } = options; // tx to submit const getTx = () => { @@ -34,26 +37,21 @@ export const ChangePoolRoles = () => { const nominator = roleEdits?.nominator?.newAddress ? { Set: roleEdits?.nominator?.newAddress } : 'Remove'; - const stateToggler = roleEdits?.stateToggler?.newAddress - ? { Set: roleEdits?.stateToggler?.newAddress } + const bouncer = roleEdits?.bouncer?.newAddress + ? { Set: roleEdits?.bouncer?.newAddress } : 'Remove'; - tx = api?.tx.nominationPools?.updateRoles( - poolId, - root, - nominator, - stateToggler - ); + tx = api?.tx.nominationPools?.updateRoles(poolId, root, nominator, bouncer); return tx; }; // handle extrinsic - const { submitTx, submitting } = useSubmitExtrinsic({ + const submitExtrinsic = useSubmitExtrinsic({ tx: getTx(), from: activeAccount, shouldSubmit: true, callbackSubmit: () => { - setModalStatus(2); + setModalStatus('closing'); }, callbackInBlock: () => { // manually update bondedPools with new pool roles @@ -61,51 +59,32 @@ export const ChangePoolRoles = () => { }, }); + useEffect(() => setModalResize(), [notEnoughFunds]); + return ( <> - <Title title="Change Pool Roles" icon={faExchangeAlt} /> - <Wrapper> - <div - style={{ - padding: '0 1.25rem', - width: '100%', - }} - > + <Close /> + <ModalPadding> + <h2 className="title unbounded">{t('changePoolRoles')}</h2> + <Wrapper> <RoleChange - roleName="Root" + roleName={t('root')} oldAddress={roleEdits?.root?.oldAddress} newAddress={roleEdits?.root?.newAddress} /> <RoleChange - roleName="Nominator" + roleName={t('nominator')} oldAddress={roleEdits?.nominator?.oldAddress} newAddress={roleEdits?.nominator?.newAddress} /> <RoleChange - roleName="State Toggler" - oldAddress={roleEdits?.stateToggler?.oldAddress} - newAddress={roleEdits?.stateToggler?.newAddress} + roleName={t('bouncer')} + oldAddress={roleEdits?.bouncer?.oldAddress} + newAddress={roleEdits?.bouncer?.newAddress} /> - <NotesWrapper> - <EstimatedTxFee /> - </NotesWrapper> - <FooterWrapper> - <div> - <ButtonSubmit - text={`Submit${submitting ? 'ting' : ''}`} - iconLeft={faArrowAltCircleUp} - iconTransform="grow-2" - onClick={() => submitTx()} - disabled={ - submitting || !accountHasSigner(activeAccount) || !txFeesValid - } - /> - </div> - </FooterWrapper> - </div> - </Wrapper> + </Wrapper> + </ModalPadding> + <SubmitTx {...submitExtrinsic} valid /> </> ); }; - -export default ChangePoolRoles; diff --git a/src/modals/ChooseLanguage/Wrapper.ts b/src/modals/ChooseLanguage/Wrapper.ts new file mode 100644 index 0000000000..06999dac0e --- /dev/null +++ b/src/modals/ChooseLanguage/Wrapper.ts @@ -0,0 +1,55 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import styled from 'styled-components'; + +export const ContentWrapper = styled.div` + box-sizing: border-box; + width: 100%; + + .items { + box-sizing: border-box; + position: relative; + box-sizing: border-box; + border-bottom: none; + width: auto; + border-radius: 0.75rem; + overflow: hidden; + overflow-y: auto; + z-index: 1; + width: 100%; + margin: 1rem 0 1.5rem 0; + } +`; + +export const LocaleButton = styled.button<{ $connected: boolean }>` + color: var(--text-color-primary); + background: var(--button-primary-background); + font-family: InterSemiBold, sans-serif; + box-sizing: border-box; + padding: 1rem; + cursor: pointer; + border-radius: 0.75rem; + display: inline-flex; + flex-flow: row wrap; + align-items: center; + width: 100%; + border: 1px solid var(--status-success-color-transparent); + margin: 0.5rem 0; + ${(props) => + props.$connected !== true && + ` + border: 1px solid rgba(0,0,0,0); +`} + + h4 { + color: var(--text-color-secondary); + &.selected { + color: var(--status-success-color); + margin-left: 0.75rem; + } + } + &:hover { + background: var(--button-hover-background); + } +`; diff --git a/src/modals/ChooseLanguage/index.tsx b/src/modals/ChooseLanguage/index.tsx new file mode 100644 index 0000000000..b5c021c2be --- /dev/null +++ b/src/modals/ChooseLanguage/index.tsx @@ -0,0 +1,50 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { ModalPadding } from '@polkadot-cloud/react'; +import { useTranslation } from 'react-i18next'; +import LanguageSVG from 'img/language.svg?react'; +import { Title } from 'library/Modal/Title'; +import { availableLanguages } from 'locale'; +import { changeLanguage } from 'locale/utils'; +import { useOverlay } from '@polkadot-cloud/react/hooks'; +import { ContentWrapper, LocaleButton } from './Wrapper'; + +export const ChooseLanguage = () => { + const { i18n, t } = useTranslation('modals'); + const { setModalStatus } = useOverlay().modal; + + return ( + <> + <Title title={t('chooseLanguage')} Svg={LanguageSVG} /> + <ModalPadding> + <ContentWrapper> + <div className="item"> + {availableLanguages.map((a, i) => { + const code = a[0]; + const label = a[1]; + + return ( + <h3 key={`${code}_${i}`}> + <LocaleButton + $connected={i18n.resolvedLanguage === code} + type="button" + onClick={() => { + changeLanguage(code, i18n); + setModalStatus('closing'); + }} + > + {label} + {i18n.resolvedLanguage === code && ( + <h4 className="selected">{t('selected')}</h4> + )} + </LocaleButton> + </h3> + ); + })} + </div> + </ContentWrapper> + </ModalPadding> + </> + ); +}; diff --git a/src/modals/ClaimPayouts/Forms.tsx b/src/modals/ClaimPayouts/Forms.tsx new file mode 100644 index 0000000000..98de20ecbd --- /dev/null +++ b/src/modals/ClaimPayouts/Forms.tsx @@ -0,0 +1,163 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { faChevronLeft } from '@fortawesome/free-solid-svg-icons'; +import { + ActionItem, + ButtonSubmitInvert, + ModalWarnings, +} from '@polkadot-cloud/react'; +import { planckToUnit } from '@polkadot-cloud/utils'; +import BigNumber from 'bignumber.js'; +import { forwardRef, useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useApi } from 'contexts/Api'; +import { Warning } from 'library/Form/Warning'; +import { useSignerWarnings } from 'library/Hooks/useSignerWarnings'; +import { useSubmitExtrinsic } from 'library/Hooks/useSubmitExtrinsic'; +import { SubmitTx } from 'library/SubmitTx'; +import { useOverlay } from '@polkadot-cloud/react/hooks'; +import { useBatchCall } from 'library/Hooks/useBatchCall'; +import type { AnyApi, AnySubscan } from 'types'; +import { useSubscan } from 'contexts/Plugins/Subscan'; +import { usePayouts } from 'contexts/Payouts'; +import { useNetwork } from 'contexts/Network'; +import { useActiveAccounts } from 'contexts/ActiveAccounts'; +import type { FormProps, ActivePayout } from './types'; +import { ContentWrapper } from './Wrappers'; + +export const Forms = forwardRef( + ({ setSection, payouts, setPayouts }: FormProps, ref: any) => { + const { t } = useTranslation('modals'); + const { api } = useApi(); + const { + networkData: { units, unit }, + } = useNetwork(); + const { activeAccount } = useActiveAccounts(); + const { newBatchCall } = useBatchCall(); + const { removeEraPayout } = usePayouts(); + const { setModalStatus } = useOverlay().modal; + const { getSignerWarnings } = useSignerWarnings(); + const { unclaimedPayouts: unclaimedPayoutsSubscan, setUnclaimedPayouts } = + useSubscan(); + + const totalPayout = + payouts?.reduce( + (total: BigNumber, cur: ActivePayout) => total.plus(cur.payout), + new BigNumber(0) + ) || new BigNumber(0); + + const getCalls = () => { + if (!api) return []; + + const calls: AnyApi[] = []; + payouts?.forEach(({ era, validators }) => { + if (!validators) return []; + + return validators.forEach((v) => + calls.push(api.tx.staking.payoutStakers(v, era)) + ); + }); + return calls; + }; + + // Store whether form is valid to submit transaction. + const [valid, setValid] = useState<boolean>( + totalPayout.isGreaterThan(0) && getCalls().length > 0 + ); + + // Ensure payouts value is valid. + useEffect( + () => setValid(totalPayout.isGreaterThan(0) && getCalls().length > 0), + [payouts] + ); + + const getTx = () => { + const tx = null; + const calls = getCalls(); + if (!valid || !api || !calls.length) return tx; + + return calls.length === 1 + ? calls.pop() + : newBatchCall(calls, activeAccount); + }; + + const submitExtrinsic = useSubmitExtrinsic({ + tx: getTx(), + from: activeAccount, + shouldSubmit: valid, + callbackSubmit: () => { + setModalStatus('closing'); + }, + callbackInBlock: () => { + // Remove Subscan unclaimed payout record(s) if they exists. + let newUnclaimedPayoutsSubscan = unclaimedPayoutsSubscan; + + payouts?.forEach(({ era, validators }) => { + validators?.forEach((validator) => { + newUnclaimedPayoutsSubscan = newUnclaimedPayoutsSubscan.filter( + (u: AnySubscan) => + !(u.validator_stash === validator && String(u.era) === era) + ); + }); + }); + setUnclaimedPayouts(newUnclaimedPayoutsSubscan); + + // Deduct from `unclaimedPayouts` in Payouts context. + payouts?.forEach(({ era, validators }) => { + for (const v of validators || []) { + removeEraPayout(era, v); + } + }); + + // Reset active form payouts for this modal. + setPayouts([]); + }, + }); + + const warnings = getSignerWarnings( + activeAccount, + false, + submitExtrinsic.proxySupported + ); + + return ( + <ContentWrapper> + <div ref={ref}> + <div className="padding"> + {warnings.length > 0 ? ( + <ModalWarnings withMargin> + {warnings.map((text, i) => ( + <Warning key={`warning${i}`} text={text} /> + ))} + </ModalWarnings> + ) : null} + <div style={{ marginBottom: '2rem' }}> + <ActionItem + text={`${t('claim')} ${planckToUnit( + totalPayout, + units + )} ${unit}`} + /> + <p>{t('afterClaiming')}</p> + </div> + </div> + <SubmitTx + fromController={false} + valid={valid} + buttons={[ + <ButtonSubmitInvert + key="button_back" + text={t('back')} + iconLeft={faChevronLeft} + iconTransform="shrink-1" + onClick={() => setSection(0)} + />, + ]} + {...submitExtrinsic} + /> + </div> + </ContentWrapper> + ); + } +); diff --git a/src/modals/ClaimPayouts/Item.tsx b/src/modals/ClaimPayouts/Item.tsx new file mode 100644 index 0000000000..1bfdae8e99 --- /dev/null +++ b/src/modals/ClaimPayouts/Item.tsx @@ -0,0 +1,67 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { ButtonSubmit } from '@polkadot-cloud/react'; +import { useTranslation } from 'react-i18next'; +import BigNumber from 'bignumber.js'; +import { planckToUnit } from '@polkadot-cloud/utils'; +import { useNetwork } from 'contexts/Network'; +import { ItemWrapper } from './Wrappers'; +import type { ItemProps } from './types'; + +export const Item = ({ + era, + unclaimedPayout, + setPayouts, + setSection, +}: ItemProps) => { + const { t } = useTranslation('modals'); + const { + networkData: { units, unit }, + } = useNetwork(); + + const totalPayout = Object.values(unclaimedPayout).reduce( + (acc: BigNumber, cur: string) => acc.plus(cur), + new BigNumber(0) + ); + + const numPayouts = Object.values(unclaimedPayout).length; + + return ( + <ItemWrapper> + <div> + <section> + <h4> + <span> + Era {era}: {numPayouts} + {t('pendingPayout', { + count: numPayouts, + })} + </span> + </h4> + <h2> + {planckToUnit(totalPayout, units).toString()} {unit} + </h2> + </section> + + <section> + <div> + <ButtonSubmit + text={t('claim')} + onClick={() => { + setPayouts([ + { + era, + payout: totalPayout.toString(), + validators: Object.keys(unclaimedPayout), + }, + ]); + setSection(1); + }} + /> + </div> + </section> + </div> + </ItemWrapper> + ); +}; diff --git a/src/modals/ClaimPayouts/Overview.tsx b/src/modals/ClaimPayouts/Overview.tsx new file mode 100644 index 0000000000..f1da7cc7e4 --- /dev/null +++ b/src/modals/ClaimPayouts/Overview.tsx @@ -0,0 +1,39 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { ModalNotes } from '@polkadot-cloud/react'; +import { forwardRef } from 'react'; +import { usePayouts } from 'contexts/Payouts'; +import { useTranslation } from 'react-i18next'; +import { Item } from './Item'; +import { ContentWrapper } from './Wrappers'; +import type { OverviewProps } from './types'; + +export const Overview = forwardRef( + ({ setSection, setPayouts }: OverviewProps, ref: any) => { + const { t } = useTranslation('modals'); + const { unclaimedPayouts } = usePayouts(); + + return ( + <ContentWrapper> + <div className="padding" ref={ref}> + {Object.entries(unclaimedPayouts || {}).map( + ([era, unclaimedPayout]: any, i: number) => ( + <Item + key={`unclaimed_payout_${i}`} + era={era} + unclaimedPayout={unclaimedPayout} + setPayouts={setPayouts} + setSection={setSection} + /> + ) + )} + <ModalNotes withPadding> + <p>{t('claimsOnBehalf')}</p> + <p>{t('notToClaim')}</p> + </ModalNotes> + </div> + </ContentWrapper> + ); + } +); diff --git a/src/modals/ClaimPayouts/Wrappers.ts b/src/modals/ClaimPayouts/Wrappers.ts new file mode 100644 index 0000000000..a449d8d90c --- /dev/null +++ b/src/modals/ClaimPayouts/Wrappers.ts @@ -0,0 +1,61 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import styled from 'styled-components'; + +export const ContentWrapper = styled.div` + border-radius: 1rem; + display: flex; + flex-flow: column nowrap; + flex-basis: 50%; + flex-grow: 0; + flex-shrink: 1; + height: fit-content; + + .padding { + padding: 0 1rem; + } + + > div:last-child { + margin-bottom: 0; + } +`; + +export const ItemWrapper = styled.div<any>` + flex: 1; + display: flex; + flex-flow: column wrap; + margin-top: 1.25rem; + + > div { + background: var(--button-primary-background); + display: flex; + flex-flow: row wrap; + width: 100%; + padding: 0.5rem 1rem; + border-radius: 1rem; + + > section { + display: flex; + flex-flow: column wrap; + justify-content: flex-end; + padding: 0.75rem 0; + + &:first-child { + flex-grow: 1; + } + &:last-child { + justify-content: center; + } + } + } + + h2 { + margin: 0.75rem 0 0 0; + } + + h4 { + color: var(--text-color-secondary); + margin: 0; + } +`; diff --git a/src/modals/ClaimPayouts/index.tsx b/src/modals/ClaimPayouts/index.tsx new file mode 100644 index 0000000000..9dc9048520 --- /dev/null +++ b/src/modals/ClaimPayouts/index.tsx @@ -0,0 +1,102 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { + ModalFixedTitle, + ModalMotionTwoSection, + ModalSection, +} from '@polkadot-cloud/react'; +import { setStateWithRef } from '@polkadot-cloud/utils'; +import { useEffect, useRef, useState } from 'react'; +import { Title } from 'library/Modal/Title'; +import { useTxMeta } from 'contexts/TxMeta'; +import { useOverlay } from '@polkadot-cloud/react/hooks'; +import { usePayouts } from 'contexts/Payouts'; +import { useTranslation } from 'react-i18next'; +import { Forms } from './Forms'; +import { Overview } from './Overview'; +import type { ActivePayout } from './types'; + +export const ClaimPayouts = () => { + const { t } = useTranslation('modals'); + const { notEnoughFunds } = useTxMeta(); + const { unclaimedPayouts } = usePayouts(); + const { setModalHeight } = useOverlay().modal; + + // Active modal section. + const [section, setSectionState] = useState(0); + const sectionRef = useRef(section); + + const setSection = (s: number) => { + setStateWithRef(s, setSectionState, sectionRef); + }; + + // Unclaimed payout(s) that will be applied to submission form. + const [payouts, setPayouts] = useState<ActivePayout[] | null>(null); + + const headerRef = useRef<HTMLDivElement>(null); + const overviewRef = useRef<HTMLDivElement>(null); + const formsRef = useRef<HTMLDivElement>(null); + + const getModalHeight = () => { + let h = headerRef.current?.clientHeight ?? 0; + if (sectionRef.current === 0) { + h += overviewRef.current?.clientHeight ?? 0; + } else { + h += formsRef.current?.clientHeight ?? 0; + } + return h; + }; + + // Resize modal on state change. + useEffect(() => { + setModalHeight(getModalHeight()); + }, [unclaimedPayouts, notEnoughFunds, section]); + + // Resize this modal on window resize. + useEffect(() => { + window.addEventListener('resize', resizeCallback); + return () => { + window.removeEventListener('resize', resizeCallback); + }; + }, []); + const resizeCallback = () => { + setModalHeight(getModalHeight()); + }; + + return ( + <ModalSection type="carousel"> + <ModalFixedTitle ref={headerRef}> + <Title title={t('claimPayouts')} fixed /> + </ModalFixedTitle> + <ModalMotionTwoSection + animate={sectionRef.current === 0 ? 'home' : 'next'} + transition={{ + duration: 0.5, + type: 'spring', + bounce: 0.1, + }} + variants={{ + home: { + left: 0, + }, + next: { + left: '-100%', + }, + }} + > + <Overview + setSection={setSection} + setPayouts={setPayouts} + ref={overviewRef} + /> + <Forms + ref={formsRef} + payouts={payouts} + setPayouts={setPayouts} + setSection={setSection} + /> + </ModalMotionTwoSection> + </ModalSection> + ); +}; diff --git a/src/modals/ClaimPayouts/types.ts b/src/modals/ClaimPayouts/types.ts new file mode 100644 index 0000000000..e31995da68 --- /dev/null +++ b/src/modals/ClaimPayouts/types.ts @@ -0,0 +1,28 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import type { EraUnclaimedPayouts } from 'contexts/Payouts/types'; + +export interface ItemProps { + era: string; + unclaimedPayout: EraUnclaimedPayouts; + setSection: (v: number) => void; + setPayouts: (payout: ActivePayout[] | null) => void; +} + +export interface ActivePayout { + era: string; + payout: string; + validators: string[]; +} + +export interface OverviewProps { + setSection: (s: number) => void; + setPayouts: (p: ActivePayout[] | null) => void; +} + +export interface FormProps { + setSection: (s: number) => void; + payouts: ActivePayout[] | null; + setPayouts: (p: ActivePayout[] | null) => void; +} diff --git a/src/modals/ClaimReward/index.tsx b/src/modals/ClaimReward/index.tsx index 5d732e2e4d..9b3edd12e6 100644 --- a/src/modals/ClaimReward/index.tsx +++ b/src/modals/ClaimReward/index.tsx @@ -1,37 +1,46 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import { faArrowAltCircleUp } from '@fortawesome/free-regular-svg-icons'; -import { faPlus, faShare } from '@fortawesome/free-solid-svg-icons'; -import { ButtonSubmit } from '@rossbulat/polkadot-dashboard-ui'; -import { BN } from 'bn.js'; +import { ActionItem, ModalPadding, ModalWarnings } from '@polkadot-cloud/react'; +import { greaterThanZero, planckToUnit } from '@polkadot-cloud/utils'; +import BigNumber from 'bignumber.js'; +import { useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; import { useApi } from 'contexts/Api'; -import { useConnect } from 'contexts/Connect'; -import { useModal } from 'contexts/Modal'; import { useActivePools } from 'contexts/Pools/ActivePools'; -import { useTxFees } from 'contexts/TxFees'; -import { EstimatedTxFee } from 'library/EstimatedTxFee'; import { Warning } from 'library/Form/Warning'; +import { useSignerWarnings } from 'library/Hooks/useSignerWarnings'; import { useSubmitExtrinsic } from 'library/Hooks/useSubmitExtrinsic'; -import { Title } from 'library/Modal/Title'; -import { useEffect, useState } from 'react'; -import { planckBnToUnit } from 'Utils'; -import { FooterWrapper, PaddingWrapper, Separator } from '../Wrappers'; +import { Close } from 'library/Modal/Close'; +import { SubmitTx } from 'library/SubmitTx'; +import { useTxMeta } from 'contexts/TxMeta'; +import { useOverlay } from '@polkadot-cloud/react/hooks'; +import { useNetwork } from 'contexts/Network'; +import { useActiveAccounts } from 'contexts/ActiveAccounts'; export const ClaimReward = () => { - const { api, network } = useApi(); - const { setStatus: setModalStatus, config } = useModal(); + const { t } = useTranslation('modals'); + const { api } = useApi(); + const { + networkData: { units, unit }, + } = useNetwork(); + const { activeAccount } = useActiveAccounts(); + const { notEnoughFunds } = useTxMeta(); const { selectedActivePool } = useActivePools(); - const { activeAccount, accountHasSigner } = useConnect(); - const { txFeesValid } = useTxFees(); - const { units, unit } = network; - let { unclaimedRewards } = selectedActivePool || {}; - unclaimedRewards = unclaimedRewards ?? new BN(0); - const { claimType } = config; + const { getSignerWarnings } = useSignerWarnings(); + const { + setModalStatus, + config: { options }, + setModalResize, + } = useOverlay().modal; + + let { pendingRewards } = selectedActivePool || {}; + pendingRewards = pendingRewards ?? new BigNumber(0); + const { claimType } = options; // ensure selected payout is valid useEffect(() => { - if (unclaimedRewards?.gtn(0)) { + if (pendingRewards?.isGreaterThan(0)) { setValid(true); } else { setValid(false); @@ -56,73 +65,55 @@ export const ClaimReward = () => { return tx; }; - const { submitTx, submitting } = useSubmitExtrinsic({ + const submitExtrinsic = useSubmitExtrinsic({ tx: getTx(), from: activeAccount, shouldSubmit: valid, callbackSubmit: () => { - setModalStatus(2); + setModalStatus('closing'); }, callbackInBlock: () => {}, }); + const warnings = getSignerWarnings( + activeAccount, + false, + submitExtrinsic.proxySupported + ); + + if (!greaterThanZero(pendingRewards)) { + warnings.push(`${t('noRewards')}`); + } + + useEffect(() => setModalResize(), [notEnoughFunds, warnings.length]); + return ( <> - <Title - title={`${claimType === 'bond' ? 'Bond' : 'Withdraw'} Rewards`} - icon={claimType === 'bond' ? faPlus : faShare} - /> - <PaddingWrapper> - <div - style={{ - width: '100%', - }} - > - {!accountHasSigner(activeAccount) && ( - <Warning text="Your account is read only, and cannot sign transactions." /> - )} - {!unclaimedRewards?.gtn(0) && ( - <Warning text="You have no rewards to claim." /> - )} - <h2> - {planckBnToUnit(unclaimedRewards, units)} {unit} - </h2> - <Separator /> - <div className="notes"> - {claimType === 'bond' ? ( - <p> - Once submitted, your rewards will be bonded back into the pool. - You own these additional bonded funds and will be able to - withdraw them at any time. - </p> - ) : ( - <p> - Withdrawing rewards will immediately transfer them to your - account as free balance. - </p> - )} - <EstimatedTxFee /> - </div> - <FooterWrapper> - <div> - <ButtonSubmit - text={`Submit${submitting ? 'ting' : ''}`} - iconLeft={faArrowAltCircleUp} - iconTransform="grow-2" - onClick={() => submitTx()} - disabled={ - !valid || - submitting || - !accountHasSigner(activeAccount) || - !txFeesValid - } - /> - </div> - </FooterWrapper> - </div> - </PaddingWrapper> + <Close /> + <ModalPadding> + <h2 className="title unbounded"> + {claimType === 'bond' ? t('compound') : t('withdraw')} {t('rewards')} + </h2> + {warnings.length > 0 ? ( + <ModalWarnings withMargin> + {warnings.map((text, i) => ( + <Warning key={`warning${i}`} text={text} /> + ))} + </ModalWarnings> + ) : null} + <ActionItem + text={`${t('claim')} ${`${planckToUnit( + pendingRewards, + units + )} ${unit}`}`} + /> + {claimType === 'bond' ? ( + <p>{t('claimReward1')}</p> + ) : ( + <p>{t('claimReward2')}</p> + )} + </ModalPadding> + <SubmitTx valid={valid} {...submitExtrinsic} /> </> ); }; - -export default ClaimReward; diff --git a/src/modals/Connect/Extension.tsx b/src/modals/Connect/Extension.tsx new file mode 100644 index 0000000000..9f283a53eb --- /dev/null +++ b/src/modals/Connect/Extension.tsx @@ -0,0 +1,110 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { faExternalLinkAlt, faPlus } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { ModalConnectItem } from '@polkadot-cloud/react'; +import { useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { + useExtensions, + useExtensionAccounts, +} from '@polkadot-cloud/react/hooks'; +import { useNotifications } from 'contexts/Notifications'; +import { ExtensionIcons } from '@polkadot-cloud/assets/extensions'; +import { ExtensionInner } from './Wrappers'; +import type { ExtensionProps } from './types'; + +export const Extension = ({ meta, size, flag }: ExtensionProps) => { + const { t } = useTranslation('modals'); + const { addNotification } = useNotifications(); + const { connectExtensionAccounts } = useExtensionAccounts(); + const { extensionsStatus, extensionInstalled, extensionCanConnect } = + useExtensions(); + const { title, website, id } = meta; + const isInstalled = extensionInstalled(id); + const canConnect = extensionCanConnect(id); + + // Force re-render on click. + const [increment, setIncrement] = useState(0); + + // click to connect to extension + const handleClick = async () => { + if (canConnect) { + const connected = await connectExtensionAccounts(id); + // force re-render to display error messages + setIncrement(increment + 1); + + if (connected) + addNotification({ + title: t('extensionConnected'), + subtitle: `${t('titleExtensionConnected', { title })}`, + }); + } + }; + + const Icon = ExtensionIcons[id || ''] || undefined; + // determine message to be displayed based on extension status. + let statusJsx; + switch (extensionsStatus[id]) { + case 'connected': + statusJsx = <p className="success">{t('connected')}</p>; + break; + case 'not_authenticated': + statusJsx = <p>{t('notAuthenticated')}</p>; + break; + default: + statusJsx = ( + <p className="active"> + <FontAwesomeIcon icon={faPlus} className="plus" /> + {t('connect')} + </p> + ); + } + + const shortUrl = Array.isArray(website) ? website[0] : website; + const longUrl = Array.isArray(website) ? website[1] : website; + const disabled = extensionsStatus[id] === 'connected' || !isInstalled; + + return ( + <ModalConnectItem canConnect={canConnect}> + <ExtensionInner> + <div> + <div className="body"> + {!disabled ? ( + <button + type="button" + className="button" + onClick={() => handleClick()} + > +   + </button> + ) : null} + + <div className="row icon"> + <Icon style={{ width: size, height: size }} /> + </div> + <div className="status"> + {flag && flag} + {isInstalled ? statusJsx : <p>{t('notInstalled')}</p>} + </div> + <div className="row"> + <h3>{title}</h3> + </div> + </div> + <div className="foot"> + <a + className="link" + href={`https://${longUrl}`} + target="_blank" + rel="noreferrer" + > + {shortUrl} + <FontAwesomeIcon icon={faExternalLinkAlt} transform="shrink-6" /> + </a> + </div> + </div> + </ExtensionInner> + </ModalConnectItem> + ); +}; diff --git a/src/modals/Connect/Ledger.tsx b/src/modals/Connect/Ledger.tsx new file mode 100644 index 0000000000..7d43a96d6e --- /dev/null +++ b/src/modals/Connect/Ledger.tsx @@ -0,0 +1,86 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { faChrome, faUsb } from '@fortawesome/free-brands-svg-icons'; +import { + faExclamationTriangle, + faExternalLinkAlt, +} from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { + ButtonHelp, + ButtonPrimaryInvert, + ButtonText, + ModalConnectItem, + ModalHardwareItem, +} from '@polkadot-cloud/react'; +import { inChrome } from '@polkadot-cloud/utils'; +import React from 'react'; +import { useHelp } from 'contexts/Help'; +import LedgerLogoSVG from 'img/ledgerLogo.svg?react'; +import { useOverlay } from '@polkadot-cloud/react/hooks'; +import { useNetwork } from 'contexts/Network'; + +export const Ledger = (): React.ReactElement => { + const { openHelp } = useHelp(); + const { replaceModal } = useOverlay().modal; + const { network } = useNetwork(); + const url = 'ledger.com'; + + // Only render on Polkadot and Kusama networks. + if (!['polkadot', 'kusama'].includes(network)) { + return <></>; + } + + return ( + <ModalConnectItem> + <ModalHardwareItem> + <div className="body"> + <div className="status"> + <ButtonHelp onClick={() => openHelp('Ledger Hardware Wallets')} /> + </div> + <div className="row"> + <LedgerLogoSVG className="logo mono" /> + </div> + <div className="row margin"> + <ButtonText + text={network === 'cereMainnet' ? 'BETA' : 'EXPERIMENTAL'} + disabled + marginRight + iconLeft={ + network === 'cereMainnet' ? undefined : faExclamationTriangle + } + style={{ opacity: 0.5 }} + /> + <ButtonText + text="Chrome / Brave" + disabled + iconLeft={faChrome} + style={{ opacity: 0.5 }} + /> + </div> + <div className="row margin"> + <ButtonPrimaryInvert + text="USB" + onClick={() => replaceModal({ key: 'ImportLedger' })} + iconLeft={faUsb} + iconTransform="shrink-1" + disabled={!inChrome()} + /> + </div> + </div> + <div className="foot"> + <a + className="link" + href={`https://${url}`} + target="_blank" + rel="noreferrer" + > + {url} + <FontAwesomeIcon icon={faExternalLinkAlt} transform="shrink-6" /> + </a> + </div> + </ModalHardwareItem> + </ModalConnectItem> + ); +}; diff --git a/src/modals/Connect/Proxies.tsx b/src/modals/Connect/Proxies.tsx new file mode 100644 index 0000000000..fad0651e5e --- /dev/null +++ b/src/modals/Connect/Proxies.tsx @@ -0,0 +1,116 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { + faChevronRight, + faMinus, + faPlus, +} from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { + ButtonHelp, + ButtonMonoInvert, + ButtonSecondary, + Polkicon, +} from '@polkadot-cloud/react'; +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import { useHelp } from 'contexts/Help'; +import { useProxies } from 'contexts/Proxies'; +import { AccountInput } from 'library/AccountInput'; +import { useImportedAccounts } from 'contexts/Connect/ImportedAccounts'; +import { + ActionWithButton, + ManualAccount, + ManualAccountsWrapper, +} from './Wrappers'; +import type { ListWithInputProps } from './types'; + +export const Proxies = ({ setInputOpen, inputOpen }: ListWithInputProps) => { + const { t } = useTranslation('modals'); + const { openHelp } = useHelp(); + const { accounts } = useImportedAccounts(); + const { getAccount } = useImportedAccounts(); + const { delegates, handleDeclareDelegate } = useProxies(); + + // Filter delegates to only show those who are imported in the dashboard. + const importedDelegates = Object.fromEntries( + Object.entries(delegates).filter(([delegate]) => + accounts.find((a) => a.address === delegate) + ) + ); + return ( + <> + <ActionWithButton> + <div> + <FontAwesomeIcon icon={faChevronRight} transform="shrink-4" /> + <h3>{t('proxyAccounts')}</h3> + <ButtonHelp marginLeft onClick={() => openHelp('Proxy Accounts')} /> + </div> + <div> + <ButtonMonoInvert + iconLeft={inputOpen ? faMinus : faPlus} + text={!inputOpen ? t('declare') : t('hide')} + onClick={() => { + setInputOpen(!inputOpen); + }} + /> + </div> + </ActionWithButton> + <ManualAccountsWrapper> + <div className="content"> + {inputOpen && ( + <> + <AccountInput + resetOnSuccess + defaultLabel={t('inputDelegatorAddress')} + successCallback={async (delegator) => { + const result = await handleDeclareDelegate(delegator); + return result; + }} + /> + </> + )} + {Object.entries(importedDelegates).length ? ( + <div className="accounts"> + {Object.entries(importedDelegates).map( + ([delegate, delegators], i) => ( + <React.Fragment key={`user_delegate_account_${i}}`}> + {delegators.map(({ delegator, proxyType }, j) => ( + <ManualAccount key={`user_delegate_${i}_delegator_${j}`}> + <div> + <span> + <Polkicon address={delegate} size={26} /> + </span> + <div className="text"> + <h4 className="title"> + <span> + {proxyType} {t('proxy')} + </span> + {getAccount(delegate)?.name || delegate} + </h4> + <h4 className="subtitle"> + {t('for', { + who: getAccount(delegator)?.name || delegator, + })} + </h4> + </div> + </div> + <div /> + <ButtonSecondary text={t('declared')} disabled /> + </ManualAccount> + ))} + </React.Fragment> + ) + )} + </div> + ) : ( + <div style={{ padding: '0.5rem' }}> + <h4>{t('noProxyAccountsDeclared')}</h4> + </div> + )} + </div> + </ManualAccountsWrapper> + </> + ); +}; diff --git a/src/modals/Connect/ReadOnly.tsx b/src/modals/Connect/ReadOnly.tsx new file mode 100644 index 0000000000..ba916f7504 --- /dev/null +++ b/src/modals/Connect/ReadOnly.tsx @@ -0,0 +1,115 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { + faChevronRight, + faMinus, + faPlus, +} from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { + ButtonHelp, + ButtonMonoInvert, + ButtonSecondary, + Polkicon, +} from '@polkadot-cloud/react'; +import { useTranslation } from 'react-i18next'; +import { useHelp } from 'contexts/Help'; +import { AccountInput } from 'library/AccountInput'; +import { useOverlay } from '@polkadot-cloud/react/hooks'; +import { useImportedAccounts } from 'contexts/Connect/ImportedAccounts'; +import { useOtherAccounts } from 'contexts/Connect/OtherAccounts'; +import type { ExternalAccount } from '@polkadot-cloud/react/types'; +import { + ActionWithButton, + ManualAccount, + ManualAccountsWrapper, +} from './Wrappers'; +import type { ListWithInputProps } from './types'; + +export const ReadOnly = ({ setInputOpen, inputOpen }: ListWithInputProps) => { + const { t } = useTranslation('modals'); + const { openHelp } = useHelp(); + const { accounts } = useImportedAccounts(); + const { setModalResize } = useOverlay().modal; + const { forgetExternalAccounts, addExternalAccount } = useOtherAccounts(); + + // get all external accounts + const externalAccountsOnly = accounts.filter( + ({ source }) => source === 'external' + ) as ExternalAccount[]; + + // get external accounts added by user + const externalAccounts = externalAccountsOnly.filter( + ({ addedBy }) => addedBy === 'user' + ); + + // forget account + const forgetAccount = (account: ExternalAccount) => { + forgetExternalAccounts([account]); + setModalResize(); + }; + return ( + <> + <ActionWithButton> + <div> + <FontAwesomeIcon icon={faChevronRight} transform="shrink-4" /> + <h3>{t('readOnlyAccounts')}</h3> + <ButtonHelp + marginLeft + onClick={() => openHelp('Read Only Accounts')} + /> + </div> + <div> + <ButtonMonoInvert + iconLeft={inputOpen ? faMinus : faPlus} + text={!inputOpen ? t('add') : t('hide')} + onClick={() => { + setInputOpen(!inputOpen); + }} + /> + </div> + </ActionWithButton> + <ManualAccountsWrapper> + <div className="content"> + {inputOpen && ( + <AccountInput + resetOnSuccess + defaultLabel={t('inputAddress')} + successCallback={async (value: string) => { + addExternalAccount(value, 'user'); + return true; + }} + /> + )} + {externalAccounts.length ? ( + <div className="accounts"> + {externalAccounts.map((a, i) => ( + <ManualAccount key={`user_external_account_${i}`}> + <div> + <span> + <Polkicon address={a.address} size={26} /> + </span> + <div className="text"> + <h4>{a.address}</h4> + </div> + </div> + <ButtonSecondary + text={t('forget')} + onClick={() => { + forgetAccount(a); + }} + /> + </ManualAccount> + ))} + </div> + ) : ( + <div style={{ padding: '0.5rem' }}> + <h4>{t('noReadOnlyAdded')}</h4> + </div> + )} + </div> + </ManualAccountsWrapper> + </> + ); +}; diff --git a/src/modals/Connect/Vault.tsx b/src/modals/Connect/Vault.tsx new file mode 100644 index 0000000000..b4602ffc57 --- /dev/null +++ b/src/modals/Connect/Vault.tsx @@ -0,0 +1,72 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { faExternalLinkAlt, faQrcode } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { + ButtonHelp, + ButtonPrimaryInvert, + ButtonText, + ModalConnectItem, + ModalHardwareItem, +} from '@polkadot-cloud/react'; +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import { useHelp } from 'contexts/Help'; +import PolkadotVaultSVG from '@polkadot-cloud/assets/extensions/svg/polkadotvault.svg?react'; +import { useOverlay } from '@polkadot-cloud/react/hooks'; + +export const Vault = (): React.ReactElement => { + const { t } = useTranslation('modals'); + const { openHelp } = useHelp(); + const { replaceModal } = useOverlay().modal; + const url = 'signer.parity.io'; + + return ( + <ModalConnectItem> + <ModalHardwareItem> + <div className="body"> + <div className="status"> + <ButtonHelp onClick={() => openHelp('Polkadot Vault')} /> + </div> + <div className="row"> + <PolkadotVaultSVG className="logo vault" /> + </div> + <div className="row margin"> + <ButtonText + text="Polkadot Vault" + disabled + marginRight + style={{ + opacity: 1, + color: 'var(--accent-color-primary)', + fontFamily: 'Unbounded', + }} + /> + </div> + <div className="row margin"> + <ButtonPrimaryInvert + text={t('import')} + onClick={() => { + replaceModal({ key: 'ImportVault' }); + }} + iconLeft={faQrcode} + iconTransform="shrink-1" + /> + </div> + </div> + <div className="foot"> + <a + className="link" + href={`https://${url}`} + target="_blank" + rel="noreferrer" + > + {url} + <FontAwesomeIcon icon={faExternalLinkAlt} transform="shrink-6" /> + </a> + </div> + </ModalHardwareItem> + </ModalConnectItem> + ); +}; diff --git a/src/modals/Connect/Wrappers.ts b/src/modals/Connect/Wrappers.ts new file mode 100644 index 0000000000..8819a85c91 --- /dev/null +++ b/src/modals/Connect/Wrappers.ts @@ -0,0 +1,218 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import styled from 'styled-components'; +import { TwoThreshold } from 'library/SelectItems/Wrapper'; + +// Wraps a list of extensions. `SelectItems` typically follows this wrapper, with the items embedded +// within it. +export const ExtensionsWrapper = styled.div` + width: 100%; + padding: 0 0.4rem; + margin: 0.5rem 0 1rem 0; + + @media (max-width: ${TwoThreshold}px) { + padding: 0; + } +`; + +// Styling for an extension item, which can reflect the status of the extension connection. +export const ExtensionInner = styled.div` + background: var(--button-primary-background); + width: 100%; + border-radius: 1rem; + overflow: hidden; + position: relative; + + h3 { + font-family: InterSemiBold, sans-serif; + margin: 1rem 0 0 0; + > svg { + margin-right: 0.5rem; + } + } + p { + color: var(--text-color-secondary); + padding: 0; + margin: 0; + .plus { + margin-right: 0.4rem; + } + } + .body { + width: 100%; + padding: 1.35rem 0.85rem 0.75rem 0.85rem; + position: relative; + + .button { + z-index: 1; + position: absolute; + background: none; + top: 0; + left: 0; + width: 100%; + height: 100%; + &:disabled { + cursor: default; + } + } + } + .row { + width: 100%; + display: flex; + } + .foot { + padding: 0.25rem 1rem 1rem 1rem; + } + .status { + position: absolute; + top: 0.9rem; + right: 0.9rem; + .success { + color: var(--status-success-color); + } + .active { + color: var(--accent-color-primary); + } + } + .icon { + color: var(--text-color-primary); + width: 100%; + + svg { + max-width: 2.6rem; + max-height: 2.6rem; + } + } + svg { + .light { + fill: var(--text-color-invert); + } + .dark { + fill: var(--text-color-secondary); + } + } +`; + +// Styling for a separator between ExtensionItems. +export const Separator = styled.div` + width: 100%; + height: 0.25rem; +`; + +export const ActionWithButton = styled.div` + border-bottom: 1px solid var(--border-primary-color); + width: 100%; + color: var(--text-color-primary); + display: flex; + align-items: center; + margin: 1.25rem 0 0; + padding-bottom: 0.75rem; + + > div { + &:first-child { + display: flex; + align-items: center; + flex-grow: 1; + font-family: InterSemiBold, sans-serif; + > svg { + margin-right: 0.5rem; + } + } + &:last-child { + font-family: InterSemiBold, sans-serif; + } + } +`; + +export const ManualAccountsWrapper = styled.div` + color: var(--text-color-primary); + width: 100%; + display: flex; + flex-flow: column nowrap; + + h3 { + display: flex; + align-items: center; + > span { + margin-left: 1rem; + } + } + h4 { + margin: 0.25rem 0 0 0; + } + + > .content { + width: 100%; + > h5 { + margin-top: 1rem; + } + } + + .accounts { + margin-top: 1rem; + width: 100%; + } +`; + +export const ManualAccount = styled.div` + background: var(--button-primary-background); + width: 100%; + border-radius: 1rem; + margin-bottom: 1rem; + padding: 1rem; + display: flex; + flex-flow: row wrap; + align-items: center; + transition: border 0.1s; + + > div { + color: var(--text-color-secondary); + transition: opacity var(--transition-duration); + + &:first-child { + flex: 1; + display: flex; + align-items: center; + + > span { + margin-right: 0.75rem; + } + + > .text { + display: flex; + flex-direction: column; + h4 { + margin: 0; + &.title { + font-family: InterSemiBold, sans-serif; + > svg { + margin: 0 0.6rem; + } + } + &.subtitle { + margin-top: 0.4rem; + } + + &.title > span, + &.subtitle > span { + color: var(--text-color-secondary); + opacity: 0.65; + margin-right: 0.65rem; + } + .arrow { + margin: 0 0.25rem; + } + } + } + } + &:last-child { + padding-left: 2rem; + opacity: 0.25; + } + } + + button { + font-size: 1rem; + } +`; diff --git a/src/modals/Connect/index.tsx b/src/modals/Connect/index.tsx new file mode 100644 index 0000000000..cfe0379b95 --- /dev/null +++ b/src/modals/Connect/index.tsx @@ -0,0 +1,194 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { faChevronRight } from '@fortawesome/free-solid-svg-icons'; +import { + ActionItem, + ButtonPrimaryInvert, + ButtonTab, + ModalCustomHeader, + ModalFixedTitle, + ModalMotionThreeSection, + ModalPadding, + ModalSection, +} from '@polkadot-cloud/react'; +import { ExtensionsArray } from '@polkadot-cloud/assets/extensions'; +import { useEffect, useRef, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { + useExtensions, + useEffectIgnoreInitial, + useOverlay, +} from '@polkadot-cloud/react/hooks'; +import { Close } from 'library/Modal/Close'; +import { SelectItems } from 'library/SelectItems'; +import type { AnyFunction } from 'types'; +import { Extension } from './Extension'; +import { Ledger } from './Ledger'; +import { Proxies } from './Proxies'; +import { ReadOnly } from './ReadOnly'; +import { Vault } from './Vault'; +import { ExtensionsWrapper } from './Wrappers'; + +export const Connect = () => { + const { t } = useTranslation('modals'); + const { extensionsStatus } = useExtensions(); + const { replaceModal, setModalHeight, modalMaxHeight } = useOverlay().modal; + + const web = ExtensionsArray.filter((a) => a.id !== 'polkadot-js'); + const pjs = ExtensionsArray.filter((a) => a.id === 'polkadot-js'); + + const installed = web.filter((a) => + Object.keys(extensionsStatus).find((key) => key === a.id) + ); + const other = web.filter((a) => !installed.find((b) => b.id === a.id)); + + // toggle read only management + const [readOnlyOpen, setReadOnlyOpen] = useState(false); + + // toggle proxy delegate management + const [newProxyOpen, setNewProxyOpen] = useState(false); + + // active modal section + const [section, setSection] = useState<number>(0); + + // refs for wrappers + const headerRef = useRef<HTMLDivElement>(null); + const homeRef = useRef<HTMLDivElement>(null); + const readOnlyRef = useRef<HTMLDivElement>(null); + const proxiesRef = useRef<HTMLDivElement>(null); + + const refreshModalHeight = () => { + // Preserve height by taking largest height from modals. + let height = headerRef.current?.clientHeight || 0; + height += Math.max( + homeRef.current?.clientHeight || 0, + readOnlyRef.current?.clientHeight || 0, + proxiesRef.current?.clientHeight || 0 + ); + setModalHeight(height); + }; + + // Resize modal on state change. + useEffectIgnoreInitial(() => { + refreshModalHeight(); + }, [section, readOnlyOpen, newProxyOpen, extensionsStatus]); + + useEffect(() => { + window.addEventListener('resize', refreshModalHeight); + return () => { + window.removeEventListener('resize', refreshModalHeight); + }; + }, []); + + return ( + <> + <ModalSection type="carousel"> + <Close /> + <ModalFixedTitle ref={headerRef} withStyle> + <ModalCustomHeader> + <div className="first"> + <h1>{t('connect')}</h1> + <ButtonPrimaryInvert + text={t('goToAccounts')} + iconRight={faChevronRight} + iconTransform="shrink-3" + onClick={() => replaceModal({ key: 'Accounts' })} + marginLeft + /> + </div> + <ModalSection type="tab"> + <ButtonTab + title={t('extensions')} + onClick={() => setSection(0)} + active={section === 0} + /> + <ButtonTab + title={t('readOnly')} + onClick={() => setSection(1)} + active={section === 1} + /> + <ButtonTab + title={t('proxies')} + onClick={() => setSection(2)} + active={section === 2} + /> + </ModalSection> + </ModalCustomHeader> + </ModalFixedTitle> + + <ModalMotionThreeSection + style={{ + maxHeight: modalMaxHeight - (headerRef.current?.clientHeight || 0), + }} + animate={ + section === 0 ? 'home' : section === 1 ? 'readOnly' : 'proxies' + } + transition={{ + duration: 0.5, + type: 'spring', + bounce: 0.1, + }} + variants={{ + home: { + left: 0, + }, + readOnly: { + left: '-100%', + }, + proxies: { + left: '-200%', + }, + }} + > + <div className="section"> + <ModalPadding horizontalOnly ref={homeRef}> + <ActionItem text={t('hardware')} /> + <ExtensionsWrapper> + <SelectItems layout="two-col"> + {[Vault, Ledger].map((Item: AnyFunction, i: number) => ( + <Item key={`hardware_item_${i}`} /> + ))} + </SelectItems> + </ExtensionsWrapper> + + <ActionItem text={t('web')} /> + <ExtensionsWrapper> + <SelectItems layout="two-col"> + {installed.concat(other).map((extension, i) => ( + <Extension key={`extension_item_${i}`} meta={extension} /> + ))} + </SelectItems> + </ExtensionsWrapper> + + <ActionItem text={t('developerTools')} /> + <ExtensionsWrapper> + <SelectItems layout="two-col"> + {pjs.map((extension, i) => ( + <Extension key={`extension_item_${i}`} meta={extension} /> + ))} + </SelectItems> + </ExtensionsWrapper> + </ModalPadding> + </div> + <div className="section"> + <ModalPadding horizontalOnly ref={readOnlyRef}> + <ReadOnly + setInputOpen={setReadOnlyOpen} + inputOpen={readOnlyOpen} + /> + </ModalPadding> + </div> + <div className="section"> + <ModalPadding horizontalOnly ref={proxiesRef}> + <Proxies + setInputOpen={setNewProxyOpen} + inputOpen={newProxyOpen} + /> + </ModalPadding> + </div> + </ModalMotionThreeSection> + </ModalSection> + </> + ); +}; diff --git a/src/modals/Connect/types.ts b/src/modals/Connect/types.ts new file mode 100644 index 0000000000..cec867e696 --- /dev/null +++ b/src/modals/Connect/types.ts @@ -0,0 +1,29 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +export interface ExtensionProps { + meta: ExtensionMetaProps; + installed?: any; + size?: string; + message?: string; + flag?: boolean; + status?: string; +} + +export interface ExtensionMetaProps { + id: string; + title: string; + status?: string; + website: string | [string, string]; +} + +export interface ListWithInputProps { + setInputOpen: (k: boolean) => void; + inputOpen: boolean; +} + +export interface forwardRefProps { + setSection?: any; + readOnlyOpen: boolean; + setReadOnlyOpen: (e: boolean) => void; +} diff --git a/src/modals/ConnectAccounts/Account.tsx b/src/modals/ConnectAccounts/Account.tsx deleted file mode 100644 index 11ed885025..0000000000 --- a/src/modals/ConnectAccounts/Account.tsx +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { IconProp } from '@fortawesome/fontawesome-svg-core'; -import { faGlasses } from '@fortawesome/free-solid-svg-icons'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { useConnect } from 'contexts/Connect'; -import { useExtensions } from 'contexts/Extensions'; -import { Extension } from 'contexts/Extensions/types'; -import { useModal } from 'contexts/Modal'; -import Identicon from 'library/Identicon'; -import { clipAddress } from 'Utils'; -import { AccountElementProps } from './types'; -import { AccountWrapper } from './Wrappers'; - -export const AccountElement = (props: AccountElementProps) => { - return ( - <AccountWrapper> - <div> - <AccountInner {...props} /> - </div> - </AccountWrapper> - ); -}; - -export const AccountButton = (props: AccountElementProps) => { - const { meta } = props; - const disconnect = props.disconnect ?? false; - const { connectToAccount, disconnectFromAccount } = useConnect(); - const { setStatus } = useModal(); - const imported = meta !== null; - - return ( - <AccountWrapper> - <button - type="button" - disabled={!disconnect && !imported} - onClick={() => { - if (imported) { - if (disconnect) { - disconnectFromAccount(); - } else { - connectToAccount(meta); - setStatus(2); - } - } - }} - > - <AccountInner {...props} /> - </button> - </AccountWrapper> - ); -}; - -export const AccountInner = (props: AccountElementProps) => { - const { address, meta } = props; - - const { extensions } = useExtensions(); - const extension = extensions.find((e: Extension) => e.id === meta?.source); - const Icon = extension?.icon ?? null; - const label = props.label ?? null; - const source = meta?.source ?? null; - const imported = meta !== null && source !== 'external'; - - return ( - <> - <div> - <Identicon value={address ?? ''} size={26} /> - <span className="name"> -   {meta?.name ?? clipAddress(address ?? '')} - </span> - </div> - {!imported && ( - <div - className="label warning" - style={{ color: '#a17703', paddingLeft: '0.5rem' }} - > - Read Only - </div> - )} - - <div className={label === null ? `` : label[0]}> - {label !== null && <h5>{label[1]}</h5>} - {Icon !== null && <Icon className="icon" />} - - {!imported && ( - <FontAwesomeIcon - icon={faGlasses as IconProp} - className="icon" - style={{ opacity: 0.7 }} - /> - )} - </div> - </> - ); -}; diff --git a/src/modals/ConnectAccounts/Accounts.tsx b/src/modals/ConnectAccounts/Accounts.tsx deleted file mode 100644 index fce93f79d9..0000000000 --- a/src/modals/ConnectAccounts/Accounts.tsx +++ /dev/null @@ -1,296 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { - faCog, - faProjectDiagram, - faUsers, -} from '@fortawesome/free-solid-svg-icons'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { ButtonSecondary } from '@rossbulat/polkadot-dashboard-ui'; -import { useApi } from 'contexts/Api'; -import { useBalances } from 'contexts/Balances'; -import { useConnect } from 'contexts/Connect'; -import { ImportedAccount } from 'contexts/Connect/types'; -import { useModal } from 'contexts/Modal'; -import { usePoolMemberships } from 'contexts/Pools/PoolMemberships'; -import { PoolMembership } from 'contexts/Pools/types'; -import { forwardRef, useEffect, useState } from 'react'; -import { AnyJson } from 'types'; -import { AccountButton, AccountElement } from './Account'; -import { - ActivelyStakingAccount, - ControllerAccount, - StashAcount, -} from './types'; -import { - AccountGroupWrapper, - AccountWrapper, - ContentWrapper, - PaddingWrapper, -} from './Wrappers'; - -export const Accounts = forwardRef((props: AnyJson, ref: AnyJson) => { - const { setSection } = props; - - const { isReady } = useApi(); - const { getAccount, activeAccount } = useConnect(); - const { - getLedgerForController, - getAccountLocks, - getBondedAccount, - accounts: balanceAccounts, - ledgers, - } = useBalances(); - const { connectToAccount } = useConnect(); - const { setStatus } = useModal(); - const { accounts } = useConnect(); - const { memberships } = usePoolMemberships(); - - const _controllers: Array<ControllerAccount> = []; - const _stashes: Array<StashAcount> = []; - - // store local copy of accounts - const [localAccounts, setLocalAccounts] = useState(accounts); - - // store staking statuses - const [activeStaking, setActiveStaking] = useState< - Array<ActivelyStakingAccount> - >([]); - const [activePooling, setActivePooling] = useState<Array<PoolMembership>>([]); - const [inactive, setInactive] = useState<string[]>([]); - - useEffect(() => { - setLocalAccounts(accounts); - }, [isReady, accounts]); - - useEffect(() => { - getStakingStatuses(); - }, [localAccounts, balanceAccounts, ledgers, accounts, memberships]); - - const getStakingStatuses = () => { - // accumulate imported stash accounts - for (const account of localAccounts) { - const locks = getAccountLocks(account.address); - - // account is a stash if they have an active `staking` lock - const activeLocks = locks.find((l) => { - const { id } = l; - return id.trim() === 'staking'; - }); - if (activeLocks !== undefined) { - _stashes.push({ - address: account.address, - controller: getBondedAccount(account.address), - }); - } - } - - // accumulate imported controller accounts - for (const account of localAccounts) { - const ledger = getLedgerForController(account.address); - if (ledger) { - _controllers.push({ - address: account.address, - ledger, - }); - } - } - - // construct account groupings - const _activeStaking: Array<ActivelyStakingAccount> = []; - const _activePooling: Array<PoolMembership> = []; - const _inactive: string[] = []; - - for (const account of localAccounts) { - const stash = - _stashes.find((s: StashAcount) => s.address === account.address) ?? - null; - const controller = - _controllers.find( - (c: ControllerAccount) => c.address === account.address - ) ?? null; - const poolMember = - memberships.find( - (m: PoolMembership) => m.address === account.address - ) ?? null; - - // if stash, get controller - if (stash) { - const applied = - _activeStaking.find( - (a: ActivelyStakingAccount) => a.stash === account.address - ) !== undefined; - - if (!applied) { - const _record = { - stash: account.address, - controller: stash.controller, - stashImported: true, - controllerImported: - localAccounts.find( - (a: ImportedAccount) => a.address === stash.controller - ) !== undefined, - }; - _activeStaking.push(_record); - } - } - - // if controller, get stash - if (controller) { - const applied = - _activeStaking.find((a) => a.controller === account.address) !== - undefined; - - if (!applied) { - const _record = { - stash: controller.ledger.stash, - controller: controller.address, - stashImported: - localAccounts.find( - (a: ImportedAccount) => a.address === controller.ledger.stash - ) !== undefined, - controllerImported: true, - }; - _activeStaking.push(_record); - } - } - - // if pooling, add to active pooling - if (poolMember) { - if (!_activePooling.includes(poolMember)) { - _activePooling.push(poolMember); - } - } - - // if not doing anything, add to inactive - if (!stash && !controller && !poolMember) { - if (!_inactive.includes(account.address)) { - _inactive.push(account.address); - } - } - } - setActiveStaking(_activeStaking); - setActivePooling(_activePooling); - setInactive(_inactive); - }; - - return ( - <ContentWrapper> - <PaddingWrapper ref={ref}> - <div className="head"> - <div> - <h1>Accounts</h1> - </div> - <div> - <ButtonSecondary - text="Extensions" - iconLeft={faCog} - iconTransform="shrink-2" - onClick={() => setSection(0)} - /> - </div> - </div> - {activeAccount ? ( - <AccountButton - address={activeAccount} - meta={getAccount(activeAccount)} - label={['danger', 'Disconnect']} - disconnect - /> - ) : ( - <AccountWrapper> - <div> - <div> - <h3>No Account Connected</h3> - </div> - <div /> - </div> - </AccountWrapper> - )} - {activeStaking.length > 0 && ( - <> - <h3 className="heading"> - <FontAwesomeIcon icon={faProjectDiagram} transform="shrink-4" />{' '} - Nominating - </h3> - {activeStaking.map((item: ActivelyStakingAccount, i: number) => { - const { stash, controller } = item; - const stashAccount = getAccount(stash); - const controllerAccount = getAccount(controller); - - return ( - <AccountGroupWrapper - key={`active_staking_${i}`} - onClick={() => { - if (stashAccount) { - connectToAccount(stashAccount); - setStatus(2); - } - }} - > - <section> - <AccountElement - address={stash} - meta={stashAccount} - label={['neutral', 'Stash']} - asElement - /> - </section> - <section> - <AccountElement - address={controller} - meta={controllerAccount} - label={['neutral', 'Controller']} - asElement - /> - </section> - </AccountGroupWrapper> - ); - })} - </> - )} - - {activePooling.length > 0 && ( - <> - <h3 className="heading"> - <FontAwesomeIcon icon={faUsers} transform="shrink-4" /> In Pool - </h3> - {activePooling.map((item: PoolMembership, i: number) => { - const { address } = item; - const account = getAccount(address); - - return ( - <AccountButton - address={address} - meta={account} - key={`active_pool_${i}`} - /> - ); - })} - </> - )} - - {inactive.length > 0 && ( - <> - <h3 className="heading">Not Staking</h3> - {inactive.map((item: string, i: number) => { - const account = getAccount(item); - const address = account?.address ?? ''; - - return ( - <AccountButton - address={address} - meta={account} - key={`not_staking_${i}`} - /> - ); - })} - </> - )} - </PaddingWrapper> - </ContentWrapper> - ); -}); - -export default Accounts; diff --git a/src/modals/ConnectAccounts/Extension.tsx b/src/modals/ConnectAccounts/Extension.tsx deleted file mode 100644 index 5df14a4af5..0000000000 --- a/src/modals/ConnectAccounts/Extension.tsx +++ /dev/null @@ -1,132 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { faCheckCircle, faPlus } from '@fortawesome/free-solid-svg-icons'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { useConnect } from 'contexts/Connect'; -import { useExtensions } from 'contexts/Extensions'; -import { Extension as ExtensionInterface } from 'contexts/Extensions/types'; -import { useState } from 'react'; -import { ExtensionProps } from './types'; -import { ExtensionWrapper } from './Wrappers'; - -export const Extension = (props: ExtensionProps) => { - const { extensions } = useExtensions(); - const { extensionsStatus } = useExtensions(); - const { meta } = props; - const { id } = meta; - - const installed = extensions.find((e: ExtensionInterface) => e.id === id); - const status = !installed ? 'not_found' : extensionsStatus[id]; - - // determine message to be displayed based on extension status. - let message; - switch (status) { - case 'connected': - message = `Connected`; - break; - case 'not_authenticated': - message = 'Not Authenticated. Authenticate and Try Again'; - break; - default: - message = status === 'no_accounts' ? 'No Accounts' : 'Not Connected'; - } - - return ( - <ExtensionWrapper> - {status === 'connected' || !installed ? ( - <ExtensionElement - {...props} - message={message} - status={status} - size="1.5rem" - /> - ) : ( - <ExtensionButton - {...props} - message={message} - status={status} - size="1.5rem" - installed={installed} - /> - )} - </ExtensionWrapper> - ); -}; - -export const ExtensionButton = (props: any) => { - const { meta, setSection, installed } = props; - const { status } = meta; - - const { connectExtensionAccounts } = useConnect(); - - // force re-render on click - const [increment, setIncrement] = useState(0); - - // click to connect to extension - const handleClick = async () => { - if (status === 'connected') { - setSection(1); - } else { - (() => { - connectExtensionAccounts(installed); - // force re-render to display error messages - setIncrement(increment + 1); - })(); - } - }; - - return ( - <button - type="button" - disabled={status === 'connected'} - onClick={() => { - if (status !== 'connected') { - handleClick(); - } - }} - > - <ExtensionInner {...props} /> - </button> - ); -}; - -export const ExtensionElement = (props: any) => { - return ( - <div> - <ExtensionInner {...props} /> - </div> - ); -}; - -export const ExtensionInner = (props: any) => { - const { size, message, flag, meta, status } = props; - const { title, icon: Icon } = meta; - - return ( - <> - <div> - <Icon width={size} height={size} /> - <h3> - <span className="name">  {title}</span> - </h3> - </div> - - <div className={status === 'connected' ? 'success' : 'neutral'}> - <h4> - <span - className={`message ${status === 'connected' ? 'success' : ''}`} - > - {message} - </span> - </h4> - {flag && flag} - <FontAwesomeIcon - icon={status === 'connected' ? faCheckCircle : faPlus} - transform="shrink-0" - className="icon" - /> - </div> - </> - ); -}; diff --git a/src/modals/ConnectAccounts/Extensions.tsx b/src/modals/ConnectAccounts/Extensions.tsx deleted file mode 100644 index b258955b33..0000000000 --- a/src/modals/ConnectAccounts/Extensions.tsx +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { faAngleDoubleRight } from '@fortawesome/free-solid-svg-icons'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { ExtensionConfig, EXTENSIONS } from 'config/extensions'; -import { useConnect } from 'contexts/Connect'; -import { forwardRef } from 'react'; -import { Extension } from './Extension'; -import { ReadOnly } from './ReadOnly'; -import { forwardRefProps } from './types'; -import { - ContentWrapper, - ExtensionWrapper, - PaddingWrapper, - Separator, -} from './Wrappers'; - -export const Extensions = forwardRef((props: forwardRefProps, ref: any) => { - const { setSection } = props; - - const { accounts } = useConnect(); - - return ( - <ContentWrapper> - <PaddingWrapper ref={ref}> - <div className="head"> - <h1>Extensions</h1> - </div> - <ExtensionWrapper> - <button - type="button" - onClick={() => { - setSection(1); - }} - > - <div> - <h3> - <span className="name"> - {accounts.length} Imported Account - {accounts.length !== 1 && 's'} - </span> - </h3> - </div> - <div className="neutral"> - <FontAwesomeIcon icon={faAngleDoubleRight} className="icon" /> - </div> - </button> - </ExtensionWrapper> - <Separator /> - {EXTENSIONS.map((extension: ExtensionConfig, i: number) => { - return ( - <Extension - key={`active_extension_${i}`} - meta={extension} - setSection={setSection} - /> - ); - })} - <ReadOnly {...props} /> - </PaddingWrapper> - </ContentWrapper> - ); -}); diff --git a/src/modals/ConnectAccounts/ReadOnly/Wrapper.ts b/src/modals/ConnectAccounts/ReadOnly/Wrapper.ts deleted file mode 100644 index efdf8d2ced..0000000000 --- a/src/modals/ConnectAccounts/ReadOnly/Wrapper.ts +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import styled from 'styled-components'; -import { - borderPrimary, - borderSecondary, - buttonPrimaryBackground, - textPrimary, - textSecondary, -} from 'theme'; - -export const Wrapper = styled.div` - color: ${textPrimary}; - border-radius: 0.75rem; - width: 100%; - margin: 1rem 0; - border-radius: 0.5rem; - background: ${buttonPrimaryBackground}; - transition: background 0.15s; - display: flex; - flex-flow: column nowrap; - align-items: flex-start; - min-height: 3.5rem; - - > .content { - padding: 0 1rem; - width: 100%; - } - - .accounts { - margin-top: 1rem; - width: 100%; - } - - .account { - width: 100%; - border: 1px solid ${borderPrimary}; - border-radius: 0.75rem; - margin: 1rem 0; - padding: 1rem; - display: flex; - flex-flow: row wrap; - transition: border 0.1s; - - > div { - color: ${textSecondary}; - transition: opacity 0.2s; - - &:first-child { - flex: 1; - display: flex; - flex-flow: row wrap; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - } - &:last-child { - padding-left: 2rem; - opacity: 0.25; - } - } - - &:hover { - > div:last-child { - opacity: 1; - } - } - - &:hover { - border-color: ${borderSecondary}; - } - } -`; diff --git a/src/modals/ConnectAccounts/ReadOnly/index.tsx b/src/modals/ConnectAccounts/ReadOnly/index.tsx deleted file mode 100644 index 40df4dd84b..0000000000 --- a/src/modals/ConnectAccounts/ReadOnly/index.tsx +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { faCog, faGlasses } from '@fortawesome/free-solid-svg-icons'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { useConnect } from 'contexts/Connect'; -import { ExternalAccount, ImportedAccount } from 'contexts/Connect/types'; -import { ReadOnlyInput } from '../ReadOnlyInput'; -import { ReadOnlyProps } from '../types'; -import { ExtensionWrapper } from '../Wrappers'; -import { Wrapper } from './Wrapper'; - -export const ReadOnly = (props: ReadOnlyProps) => { - const { setReadOnlyOpen, readOnlyOpen } = props; - - const { accounts, forgetAccounts } = useConnect(); - - // get all external accounts - const externalAccountsOnly = accounts.filter((a: ImportedAccount) => { - return a.source === 'external'; - }) as Array<ExternalAccount>; - - // get external accounts added by user - const externalAccountsByUser = externalAccountsOnly.filter( - (a: ExternalAccount) => a.addedBy === 'user' - ); - - // forget account - const forgetAccount = (account: ExternalAccount) => { - forgetAccounts([account]); - }; - return ( - <Wrapper> - <ExtensionWrapper noSpacing> - <button - type="button" - onClick={() => { - setReadOnlyOpen(!readOnlyOpen); - }} - > - <FontAwesomeIcon - icon={faGlasses} - transform="grow-2" - style={{ margin: '0 0.75rem 0 1.25rem' }} - /> - <h3> - <span className="name">Read Only Accounts</span> - </h3> - - <div> - <h3> - <span - className={`message${ - externalAccountsByUser.length ? ` success` : `` - }`} - > - {externalAccountsByUser.length - ? `${externalAccountsByUser.length} Connected` - : ``} - </span> - </h3> - {!readOnlyOpen && <FontAwesomeIcon icon={faCog} className="icon" />} - </div> - </button> - </ExtensionWrapper> - {readOnlyOpen && ( - <div className="content"> - <ReadOnlyInput /> - {externalAccountsByUser.length > 0 && ( - <h5> - {externalAccountsByUser.length} Read Only Account - {externalAccountsByUser.length === 1 ? '' : 's'} - </h5> - )} - <div className="accounts"> - {externalAccountsByUser.map((a: ExternalAccount, i: number) => ( - <div key={`user_external_account_${i}`} className="account"> - <div>{a.address}</div> - <button - type="button" - onClick={() => { - forgetAccount(a); - }} - > - Forget - </button> - </div> - ))} - </div> - </div> - )} - </Wrapper> - ); -}; diff --git a/src/modals/ConnectAccounts/ReadOnlyInput/Wrapper.ts b/src/modals/ConnectAccounts/ReadOnlyInput/Wrapper.ts deleted file mode 100644 index c1c95bb873..0000000000 --- a/src/modals/ConnectAccounts/ReadOnlyInput/Wrapper.ts +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import styled from 'styled-components'; -import { borderPrimary, textDanger, textSecondary, textSuccess } from 'theme'; - -export const Wrapper = styled.div` - width: 100%; - margin-top: 0.5rem; - - .input { - border: 1px solid ${borderPrimary}; - border-radius: 1rem; - display: flex; - flex-flow: row wrap; - align-items: center; - padding: 0.25rem 0.5rem 0.25rem 1rem; - - > section { - display: flex; - flex-flow: column wrap; - - > input { - width: 100%; - border: none; - padding-right: 1rem; - } - - &:first-child { - flex: 1; - } - } - } - h5 { - margin: 0.75rem 0.25rem; - &.neutral { - color: ${textSecondary}; - opacity: 0.8; - } - &.danger { - color: ${textDanger}; - } - &.success { - color: ${textSuccess}; - } - } -`; diff --git a/src/modals/ConnectAccounts/ReadOnlyInput/index.tsx b/src/modals/ConnectAccounts/ReadOnlyInput/index.tsx deleted file mode 100644 index cc3ae58837..0000000000 --- a/src/modals/ConnectAccounts/ReadOnlyInput/index.tsx +++ /dev/null @@ -1,126 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { ButtonSecondary } from '@rossbulat/polkadot-dashboard-ui'; -import { useConnect } from 'contexts/Connect'; -import { ImportedAccount } from 'contexts/Connect/types'; -import React, { useState } from 'react'; -import { isValidAddress } from 'Utils'; -import { Wrapper } from './Wrapper'; - -export const ReadOnlyInput = () => { - const { formatAccountSs58, accounts, addExternalAccount } = useConnect(); - - // store current input value - const [value, setValue] = useState(''); - - // store whether current input value is valid - const [valid, setValid] = useState<string | null>(null); - - // store whether address was formatted (displays confirm prompt) - const [reformatted, setReformatted] = useState(false); - - const handleChange = (e: React.FormEvent<HTMLInputElement>) => { - const newValue = e.currentTarget.value; - // set value on key change - setValue(newValue); - - // reset reformatted if true - value has changed - if (reformatted) { - setReformatted(false); - } - - // reset valid if empty value - if (newValue === '') { - setValid(null); - return; - } - // check address already imported - const alreadyImported = accounts.find( - (a: ImportedAccount) => a.address.toUpperCase() === newValue.toUpperCase() - ); - if (alreadyImported !== undefined) { - setValid('already_imported'); - return; - } - // check if valid address - setValid(isValidAddress(newValue) ? 'valid' : 'not_valid'); - }; - - const handleImport = () => { - // reformat address if in wrong format - const addressFormatted = formatAccountSs58(value); - if (addressFormatted) { - setValid('confirm_reformat'); - setValue(addressFormatted); - setReformatted(true); - } else { - // add as external account - addExternalAccount(value, 'user'); - // reset state - setReformatted(false); - setValue(''); - setValid(null); - } - }; - - let label; - let labelClass; - switch (valid) { - case 'confirm_reformat': - label = 'Address was reformatted. Please confirm.'; - labelClass = 'neutral'; - - break; - case 'already_imported': - label = 'Address Already Imported'; - labelClass = 'danger'; - break; - case 'not_valid': - label = 'Address Invalid'; - labelClass = 'danger'; - break; - case 'valid': - label = 'Valid Address'; - labelClass = 'success'; - break; - default: - label = 'Input Address'; - labelClass = 'neutral'; - } - - const handleConfirm = () => { - setValid('valid'); - setReformatted(false); - handleImport(); - }; - - return ( - <Wrapper> - <h5 className={labelClass}>{label}</h5> - <div className="input"> - <section> - <input - placeholder="Address" - type="text" - onChange={(e: React.FormEvent<HTMLInputElement>) => handleChange(e)} - value={value} - /> - </section> - <section> - {!reformatted ? ( - <ButtonSecondary - onClick={() => handleImport()} - text="Import" - disabled={valid !== 'valid'} - /> - ) : ( - <ButtonSecondary onClick={() => handleConfirm()} text="Confirm" /> - )} - </section> - </div> - </Wrapper> - ); -}; - -export default ReadOnlyInput; diff --git a/src/modals/ConnectAccounts/Wrappers.ts b/src/modals/ConnectAccounts/Wrappers.ts deleted file mode 100644 index 29f8d602bb..0000000000 --- a/src/modals/ConnectAccounts/Wrappers.ts +++ /dev/null @@ -1,314 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { motion } from 'framer-motion'; -import styled from 'styled-components'; -import { - backgroundToggle, - borderPrimary, - buttonPrimaryBackground, - modalBackground, - textDanger, - textInvert, - textPrimary, - textSecondary, - textSuccess, -} from 'theme'; - -export const CardsWrapper = styled(motion.div)` - width: 200%; - display: flex; - flex-flow: row nowrap; - overflow: hidden; - position: relative; -`; - -export const ContentWrapper = styled.div` - border-radius: 1rem; - display: flex; - flex-flow: column nowrap; - width: 50%; - height: auto; - padding: 0 1rem 1rem 1rem; -`; - -export const Wrapper = styled.div` - display: flex; - flex-flow: column wrap; - align-items: center; - justify-content: flex-start; - padding: 0; - width: 100%; - overflow: hidden; - - h1 { - color: ${textPrimary}; - font-size: 1.4rem; - font-family: 'Unbounded', 'sans-serif', sans-serif; - padding: 0.5rem 0.5rem 0 0.5rem; - } - - h3 { - color: ${textPrimary}; - - &.heading { - border-bottom: 1px solid ${borderPrimary}; - padding-bottom: 0.75rem; - margin: 2rem 0 1rem 0; - } - } - - .head { - width: 100%; - display: flex; - flex-flow: row wrap; - align-items: center; - margin: 0.5rem 0 1.5rem 0; - - > div:last-child { - display: flex; - flex-flow: row-reverse wrap; - flex-grow: 1; - - button { - background: none; - opacity: 0.85; - } - } - } -`; - -export const PaddingWrapper = styled.div` - padding: 1rem 0 0.5rem 0rem; - height: auto; -`; - -export const AccountGroupWrapper = styled(motion.button)` - border-radius: 1rem; - width: 100%; - display: flex; - flex-flow: row wrap; - align-items: center; - background: ${buttonPrimaryBackground}; - margin-bottom: 1rem; - transition: background 0.15s; - - > section { - display: flex; - flex-flow: row wrap; - flex-basis: 100%; - - @media (min-width: 800px) { - flex-basis: 50%; - - &:first-child { - padding-right: 0.25rem; - } - &:last-child { - padding-left: 0.25rem; - } - } - - > h5 { - margin: 0 0 0.25rem 0; - opacity: 0.75; - } - - > div { - margin: 0.4rem 0; - > button, - > div { - border-radius: 0.75rem; - border: 1px solid ${modalBackground}; - margin: 0; - } - } - } - - &:hover { - background: ${backgroundToggle}; - > section > div { - > button, - > div { - background: ${backgroundToggle}; - } - } - } -`; - -export const AccountWrapper = styled.div` - width: 100%; - margin: 0.5rem 0; - - > button { - &:hover { - background: ${backgroundToggle}; - } - &:disabled { - cursor: default; - border: 2px solid rgba(242, 185, 27, 0.25); - } - } - - > div, - button { - width: 100%; - border-radius: 0.75rem; - font-size: 1rem; - background: ${buttonPrimaryBackground}; - transition: background 0.15s; - color: ${textPrimary}; - display: flex; - flex-flow: row nowrap; - align-items: center; - min-height: 3.5rem; - padding-left: 0.4rem; - padding-right: 0.4rem; - - > div { - display: flex; - flex-flow: row nowrap; - justify-content: flex-start; - align-items: center; - padding: 0 0.25rem; - - &:first-child { - flex-shrink: 1; - overflow: hidden; - .name { - max-width: 100%; - text-overflow: ellipsis; - white-space: nowrap; - overflow: hidden; - } - } - - &:last-child { - flex-grow: 1; - justify-content: flex-end; - } - - &.neutral { - h5 { - color: ${textSecondary}; - opacity: 0.75; - } - } - &.danger { - h5 { - color: ${textDanger}; - } - } - .icon { - width: 1.05rem; - height: 1.05rem; - margin-left: 0.75rem; - } - - /* svg theming */ - svg { - .light { - fill: ${textInvert}; - } - .dark { - fill: ${textSecondary}; - } - } - } - } -`; - -export const ExtensionWrapper = styled.div<{ noSpacing?: boolean }>` - width: 100%; - - > button, - > div { - width: 100%; - margin: ${(props) => (props.noSpacing ? 0 : '1rem 0')}; - padding: ${(props) => (props.noSpacing ? 0 : '1rem 0.25rem')}; - font-size: 1rem; - background: ${buttonPrimaryBackground}; - border-radius: 0.75rem; - transition: background 0.15s; - color: ${textPrimary}; - display: flex; - flex-flow: row nowrap; - align-items: center; - min-height: 3.5rem; - - > div { - display: flex; - flex-flow: row nowrap; - justify-content: flex-start; - align-items: center; - padding: 0 1rem; - h3, - h4 { - margin: 0; - padding: 0; - } - span { - margin-right: 1.25rem; - &.name { - max-width: 100%; - text-overflow: ellipsis; - white-space: nowrap; - overflow: hidden; - } - &.message { - opacity: 0.75; - } - } - &:first-child { - flex-shrink: 1; - overflow: hidden; - } - &:last-child { - flex-grow: 1; - justify-content: flex-end; - .icon { - margin-left: 1rem; - } - } - } - .neutral { - color: ${textSecondary}; - } - .danger { - color: ${textDanger}; - } - .success { - color: ${textSuccess}; - } - /* svg theming */ - svg { - .light { - fill: ${textInvert}; - } - .dark { - fill: ${textSecondary}; - } - } - } - > button { - padding: 0 0.2rem; - &:hover { - background: ${backgroundToggle}; - } - - &:disabled { - cursor: default; - opacity: 1; - &:hover { - background: ${buttonPrimaryBackground}; - } - } - } -`; - -export const Separator = styled.div` - border-top: 1px solid ${textSecondary}; - width: 100%; - opacity: 0.1; - margin: 1.5rem 0rem; -`; diff --git a/src/modals/ConnectAccounts/index.tsx b/src/modals/ConnectAccounts/index.tsx deleted file mode 100644 index 3d2e1b2470..0000000000 --- a/src/modals/ConnectAccounts/index.tsx +++ /dev/null @@ -1,90 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { useConnect } from 'contexts/Connect'; -import { ImportedAccount } from 'contexts/Connect/types'; -import { useExtensions } from 'contexts/Extensions'; -import { useModal } from 'contexts/Modal'; -import { useEffect, useRef, useState } from 'react'; -import { Accounts } from './Accounts'; -import { Extensions } from './Extensions'; -import { CardsWrapper, Wrapper } from './Wrappers'; - -export const ConnectAccounts = () => { - const modal = useModal(); - const { extensions } = useExtensions(); - const { activeAccount } = useConnect(); - let { accounts } = useConnect(); - const { config } = modal; - const _section = config?.section ?? null; - - // active section of modal - const [section, setSection] = useState( - _section !== null ? _section : activeAccount !== null ? 1 : 0 - ); - - // toggle read only management - const [readOnlyOpen, setReadOnlyOpen] = useState(false); - - // resize modal on state change - const extensionsRef = useRef<HTMLDivElement>(null); - const accountsRef = useRef<HTMLDivElement>(null); - - const resizeModal = () => { - let _height = 0; - if (section === 0) { - _height = extensionsRef.current?.clientHeight ?? 0; - } else if (section === 1) { - _height = accountsRef.current?.clientHeight ?? 0; - } - modal.setModalHeight(_height); - }; - - useEffect(() => { - resizeModal(); - }, [ - section, - activeAccount, - accounts, - extensions, - modal.height, - readOnlyOpen, - ]); - - // remove active account from connect list - accounts = accounts.filter( - (item: ImportedAccount) => item.address !== activeAccount - ); - - return ( - <Wrapper> - <CardsWrapper - animate={section === 0 ? 'home' : 'next'} - initial={false} - transition={{ - duration: 0.5, - type: 'spring', - bounce: 0.1, - }} - variants={{ - home: { - left: 0, - }, - next: { - left: '-100%', - }, - }} - > - <Extensions - setSection={setSection} - readOnlyOpen={readOnlyOpen} - setReadOnlyOpen={setReadOnlyOpen} - ref={extensionsRef} - /> - <Accounts setSection={setSection} ref={accountsRef} /> - </CardsWrapper> - </Wrapper> - ); -}; - -export default ConnectAccounts; diff --git a/src/modals/ConnectAccounts/types.ts b/src/modals/ConnectAccounts/types.ts deleted file mode 100644 index bc667880eb..0000000000 --- a/src/modals/ConnectAccounts/types.ts +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { BalanceLedger } from 'contexts/Balances/types'; -import { ExtensionAccount } from 'contexts/Connect/types'; -import { FunctionComponent, SVGProps } from 'react'; -import { MaybeAccount } from 'types'; - -export interface ExtensionProps { - meta: ExtensionMetaProps; - setSection: (n: number) => void; - installed?: any; - size?: string; - message?: string; - flag?: boolean; - status?: string; -} - -export interface ExtensionMetaProps { - id: string; - title: string; - icon: FunctionComponent< - SVGProps<SVGSVGElement> & { title?: string | undefined } - >; - status?: string; -} - -export interface AccountElementProps { - meta: ExtensionAccount | null; - address?: MaybeAccount; - label?: string[]; - disconnect?: boolean; - asElement?: boolean; -} - -export interface ReadOnlyProps { - setReadOnlyOpen: (k: boolean) => void; - readOnlyOpen: boolean; -} - -export interface forwardRefProps { - setSection?: any; - readOnlyOpen: boolean; - setReadOnlyOpen: (e: boolean) => void; -} - -export interface ControllerAccount { - address: string; - ledger: BalanceLedger; -} - -export interface StashAcount { - address: string; - controller: MaybeAccount; -} - -export interface ActivelyStakingAccount { - stash: MaybeAccount; - controller: MaybeAccount; - stashImported: boolean; - controllerImported: boolean; -} diff --git a/src/modals/DismissTips/index.tsx b/src/modals/DismissTips/index.tsx index 1160a9f539..0a56dfb6c5 100644 --- a/src/modals/DismissTips/index.tsx +++ b/src/modals/DismissTips/index.tsx @@ -1,22 +1,21 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import { ButtonSubmit } from '@rossbulat/polkadot-dashboard-ui'; -import { useModal } from 'contexts/Modal'; -import { useUi } from 'contexts/UI'; -import { Title } from 'library/Modal/Title'; -import { PaddingWrapper } from 'modals/Wrappers'; +import { ButtonSubmit, ModalPadding } from '@polkadot-cloud/react'; import { useTranslation } from 'react-i18next'; +import { usePlugins } from 'contexts/Plugins'; +import { Title } from 'library/Modal/Title'; +import { useOverlay } from '@polkadot-cloud/react/hooks'; export const DismissTips = () => { - const { toggleService } = useUi(); - const { setStatus } = useModal(); const { t } = useTranslation('tips'); + const { togglePlugin } = usePlugins(); + const { setModalStatus } = useOverlay().modal; return ( <> - <Title title={t('module.dismiss_tips')} /> - <PaddingWrapper horizontalOnly> + <Title title={t('module.dismissTips')} /> + <ModalPadding horizontalOnly> <div style={{ padding: '0 0.5rem 1.25rem 0.5rem', @@ -24,21 +23,21 @@ export const DismissTips = () => { }} > <div> - <h4>{t('module.dismiss_result')}</h4> - <h4>{t('module.re-enable')}</h4> + <h4>{t('module.dismissResult')}</h4> + <h4>{t('module.reEnable')}</h4> </div> <div className="buttons"> <ButtonSubmit marginRight - text={t('module.disable_dashboard_tips')} + text={t('module.disableTips')} onClick={() => { - toggleService('tips'); - setStatus(2); + togglePlugin('tips'); + setModalStatus('closing'); }} /> </div> </div> - </PaddingWrapper> + </ModalPadding> </> ); }; diff --git a/src/modals/GoToFeedback/index.tsx b/src/modals/GoToFeedback/index.tsx index 130ab4510c..aeb36b2197 100644 --- a/src/modals/GoToFeedback/index.tsx +++ b/src/modals/GoToFeedback/index.tsx @@ -1,46 +1,45 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only import { faExternalLinkAlt } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { ReactComponent as ForumSVG } from 'img/forum.svg'; +import { ModalPadding } from '@polkadot-cloud/react'; +import { useTranslation } from 'react-i18next'; +import ForumSVG from 'img/forum.svg?react'; import { Title } from 'library/Modal/Title'; -import { NotesWrapper, PaddingWrapper } from '../Wrappers'; export const GoToFeedback = () => { + const { t } = useTranslation('modals'); return ( <> - <Title title="Feedback" Svg={ForumSVG} /> - <PaddingWrapper verticalOnly> + <Title title={t('feedback')} Svg={ForumSVG} /> + <ModalPadding verticalOnly> <div style={{ - padding: '0 1.75rem', + padding: '0 1.75rem 0.5rem 1.75rem', width: '100%', }} > - <NotesWrapper style={{ paddingTop: 0 }}> - <p> - We host a feedback page on{' '} - <a href="https://canny.io/" target="_blank" rel="noreferrer"> - Canny.io - </a> - . Bug reports, feature requests and improvements are all welcome. - </p> - </NotesWrapper> - <h2 style={{ marginTop: 0 }}> + <h4 style={{ paddingBottom: '0.75rem' }}> + {t('feedbackPage')}{' '} + <a href="https://canny.io/" target="_blank" rel="noreferrer"> + Canny.io + </a> + . {t('welcomeToReport')} + </h4> + <h2 style={{ marginTop: '0.75rem' }}> <a href="https://polkadot-staking-dashboard.canny.io/feedback" target="_blank" rel="noreferrer" + style={{ color: 'var(--accent-color-primary' }} > - Open Feedback on Canny.io   + {t('openFeedback')}   <FontAwesomeIcon icon={faExternalLinkAlt} transform="shrink-3" /> </a> </h2> </div> - </PaddingWrapper> + </ModalPadding> </> ); }; - -export default GoToFeedback; diff --git a/src/modals/ImportLedger/Addresses.tsx b/src/modals/ImportLedger/Addresses.tsx new file mode 100644 index 0000000000..c7ede6ba6b --- /dev/null +++ b/src/modals/ImportLedger/Addresses.tsx @@ -0,0 +1,110 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { faArrowDown } from '@fortawesome/free-solid-svg-icons'; +import { ButtonText, HardwareAddress, Polkicon } from '@polkadot-cloud/react'; +import { ellipsisFn, unescape } from '@polkadot-cloud/utils'; +import { useTranslation } from 'react-i18next'; +import { useLedgerHardware } from 'contexts/Hardware/Ledger'; +import { getLocalLedgerAddresses } from 'contexts/Hardware/Utils'; +import { usePrompt } from 'contexts/Prompt'; +import { Confirm } from 'library/Import/Confirm'; +import { Remove } from 'library/Import/Remove'; +import { AddressesWrapper } from 'library/Import/Wrappers'; +import type { AnyJson } from 'types'; +import { useNetwork } from 'contexts/Network'; +import { useOtherAccounts } from 'contexts/Connect/OtherAccounts'; + +export const Addresess = ({ addresses, handleLedgerLoop }: AnyJson) => { + const { t } = useTranslation('modals'); + const { network } = useNetwork(); + + const { + getIsExecuting, + ledgerAccountExists, + renameLedgerAccount, + addLedgerAccount, + removeLedgerAccount, + setIsExecuting, + getLedgerAccount, + pairDevice, + } = useLedgerHardware(); + const isExecuting = getIsExecuting(); + const { openPromptWith } = usePrompt(); + const { renameOtherAccount } = useOtherAccounts(); + + const renameHandler = (address: string, newName: string) => { + renameLedgerAccount(address, newName); + renameOtherAccount(address, newName); + }; + + const openConfirmHandler = (address: string, index: number) => { + openPromptWith( + <Confirm address={address} index={index} addHandler={addLedgerAccount} />, + 'small' + ); + }; + + const openRemoveHandler = (address: string) => { + openPromptWith( + <Remove + address={address} + removeHandler={removeLedgerAccount} + getHandler={getLedgerAccount} + />, + 'small' + ); + }; + + return ( + <> + <AddressesWrapper> + <div className="items"> + {addresses.map(({ address, index }: AnyJson, i: number) => { + const initialName = (() => { + const localAddress = getLocalLedgerAddresses().find( + (a) => a.address === address && a.network === network + ); + return localAddress?.name + ? unescape(localAddress.name) + : ellipsisFn(address); + })(); + + return ( + <HardwareAddress + key={i} + address={address} + index={index} + initial={initialName} + Identicon={<Polkicon address={address} size={40} />} + existsHandler={ledgerAccountExists} + renameHandler={renameHandler} + openRemoveHandler={openRemoveHandler} + openConfirmHandler={openConfirmHandler} + t={{ + tRemove: t('remove'), + tImport: t('import'), + }} + /> + ); + })} + </div> + <div className="more"> + <ButtonText + iconLeft={faArrowDown} + text={isExecuting ? t('gettingAccount') : t('getAnotherAccount')} + disabled={isExecuting} + onClick={async () => { + // re-pair the device if it has been disconnected. + const paired = await pairDevice(); + if (paired) { + setIsExecuting(true); + handleLedgerLoop(); + } + }} + /> + </div> + </AddressesWrapper> + </> + ); +}; diff --git a/src/modals/ImportLedger/Manage.tsx b/src/modals/ImportLedger/Manage.tsx new file mode 100644 index 0000000000..7eef81fb0e --- /dev/null +++ b/src/modals/ImportLedger/Manage.tsx @@ -0,0 +1,87 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { HardwareStatusBar } from '@polkadot-cloud/react'; +import { useTranslation } from 'react-i18next'; +import { useLedgerHardware } from 'contexts/Hardware/Ledger'; +import { getLedgerApp } from 'contexts/Hardware/Utils'; +import { useHelp } from 'contexts/Help'; +import { usePrompt } from 'contexts/Prompt'; +import LedgerSVG from '@polkadot-cloud/assets/extensions/svg/ledger.svg?react'; +import { Heading } from 'library/Import/Heading'; +import type { AnyJson } from 'types'; +import { useOverlay } from '@polkadot-cloud/react/hooks'; +import { useNetwork } from 'contexts/Network'; +import { Addresess } from './Addresses'; +import { Reset } from './Reset'; + +export const Manage = ({ + addresses, + handleLedgerLoop, + removeLedgerAddress, +}: AnyJson) => { + const { t } = useTranslation(); + const { network } = useNetwork(); + const { setIsExecuting, getIsExecuting, resetStatusCodes, getFeedback } = + useLedgerHardware(); + const { openPromptWith } = usePrompt(); + const { replaceModal } = useOverlay().modal; + const { openHelp } = useHelp(); + + const { appName, Icon } = getLedgerApp(network); + const isExecuting = getIsExecuting(); + + const fallbackMessage = `${t('ledgerAccounts', { + ns: 'modals', + count: addresses.length, + })}`; + const feedback = getFeedback(); + const helpKey = feedback?.helpKey; + + return ( + <> + <Heading + connectTo="Ledger" + title={appName} + Icon={Icon} + disabled={!addresses.length} + handleReset={() => { + openPromptWith( + <Reset removeLedgerAddress={removeLedgerAddress} />, + 'small' + ); + }} + /> + <Addresess + addresses={addresses} + handleLedgerLoop={handleLedgerLoop} + removeLedgerAddress={removeLedgerAddress} + /> + <HardwareStatusBar + show + Icon={LedgerSVG} + text={feedback?.message || fallbackMessage} + help={ + helpKey + ? { + helpKey, + handleHelp: openHelp, + } + : undefined + } + inProgress={isExecuting} + handleCancel={() => { + setIsExecuting(false); + resetStatusCodes(); + }} + handleDone={() => + replaceModal({ key: 'Connect', options: { disableScroll: true } }) + } + t={{ + tDone: t('done', { ns: 'library' }), + tCancel: t('cancel', { ns: 'library' }), + }} + /> + </> + ); +}; diff --git a/src/modals/ImportLedger/Reset.tsx b/src/modals/ImportLedger/Reset.tsx new file mode 100644 index 0000000000..ba55b9fa87 --- /dev/null +++ b/src/modals/ImportLedger/Reset.tsx @@ -0,0 +1,55 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { ButtonMono, ButtonMonoInvert } from '@polkadot-cloud/react'; +import { useTranslation } from 'react-i18next'; +import { useLedgerHardware } from 'contexts/Hardware/Ledger'; +import { getLocalLedgerAddresses } from 'contexts/Hardware/Utils'; +import type { LedgerAddress } from 'contexts/Hardware/types'; +import { usePrompt } from 'contexts/Prompt'; +import { ConfirmWrapper } from 'library/Import/Wrappers'; +import type { AnyJson } from 'types'; +import { useOverlay } from '@polkadot-cloud/react/hooks'; +import { useOtherAccounts } from 'contexts/Connect/OtherAccounts'; +import type { LedgerAccount } from '@polkadot-cloud/react/types'; + +export const Reset = ({ removeLedgerAddress }: AnyJson) => { + const { t } = useTranslation('modals'); + const { setStatus } = usePrompt(); + const { replaceModal } = useOverlay().modal; + const { forgetOtherAccounts } = useOtherAccounts(); + const { ledgerAccounts, removeLedgerAccount } = useLedgerHardware(); + + const removeAccounts = () => { + // Remove imported Ledger accounts. + ledgerAccounts.forEach((account: LedgerAccount) => { + removeLedgerAccount(account.address); + }); + forgetOtherAccounts(ledgerAccounts); + + // Remove local Ledger addresses. + getLocalLedgerAddresses().forEach((address: LedgerAddress) => { + removeLedgerAddress(address.address); + }); + + // Go back to Connect modal. + replaceModal({ key: 'Connect', options: { disableScroll: true } }); + }; + + return ( + <ConfirmWrapper> + <h3>{t('resetLedgerAccounts')}</h3> + <p>{t('ledgerWillBeReset')}</p> + <div className="footer"> + <ButtonMonoInvert text={t('cancel')} onClick={() => setStatus(0)} /> + <ButtonMono + text={t('confirmReset')} + onClick={() => { + removeAccounts(); + setStatus(0); + }} + /> + </div> + </ConfirmWrapper> + ); +}; diff --git a/src/modals/ImportLedger/Splash.tsx b/src/modals/ImportLedger/Splash.tsx new file mode 100644 index 0000000000..9edd8fc95f --- /dev/null +++ b/src/modals/ImportLedger/Splash.tsx @@ -0,0 +1,112 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { faChevronLeft } from '@fortawesome/free-solid-svg-icons'; +import { ButtonHelp, ButtonSecondary } from '@polkadot-cloud/react'; +import { useEffect } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useLedgerHardware } from 'contexts/Hardware/Ledger'; +import { useHelp } from 'contexts/Help'; +import { useTheme } from 'contexts/Themes'; +import LogoSVG from 'img/ledgerLogo.svg?react'; +import type { AnyFunction } from 'types'; +import { useOverlay } from '@polkadot-cloud/react/hooks'; +import { SplashWrapper } from './Wrappers'; + +export const Splash = ({ handleLedgerLoop }: AnyFunction) => { + const { t } = useTranslation('modals'); + const { + getStatusCodes, + isPaired, + getIsExecuting, + setIsExecuting, + pairDevice, + getFeedback, + } = useLedgerHardware(); + const { mode } = useTheme(); + const { openHelp } = useHelp(); + const { replaceModal, setModalResize } = useOverlay().modal; + + const statusCodes = getStatusCodes(); + + const initFetchAddress = async () => { + const paired = await pairDevice(); + if (paired) { + setIsExecuting(true); + handleLedgerLoop(); + } + }; + + const fallbackMessage = t('checking'); + const feedback = getFeedback(); + const helpKey = feedback?.helpKey; + + // Initialise listeners for Ledger IO. + useEffect(() => { + if (isPaired !== 'paired') { + pairDevice(); + } + }, []); + + // Once the device is paired, start `handleLedgerLoop`. + useEffect(() => { + initFetchAddress(); + }, [isPaired]); + + // Resize modal on new message + useEffect(() => setModalResize(), [statusCodes, feedback]); + + return ( + <> + <div style={{ display: 'flex', padding: '1rem' }}> + <h1> + <ButtonSecondary + text={t('back')} + iconLeft={faChevronLeft} + iconTransform="shrink-3" + onClick={async () => + replaceModal({ key: 'Connect', options: { disableScroll: true } }) + } + /> + </h1> + </div> + <SplashWrapper> + <div className="icon"> + <LogoSVG + style={{ transform: 'scale(0.6)' }} + opacity={mode === 'dark' ? 0.5 : 0.1} + /> + </div> + + <div className="content"> + <h2> + {feedback?.message || fallbackMessage} + {helpKey ? ( + <ButtonHelp + marginLeft + onClick={() => openHelp(helpKey)} + background="secondary" + /> + ) : null} + </h2> + + {!getIsExecuting() ? ( + <> + <h5>{t('ensureLedgerIsConnected')}</h5> + <div className="button"> + <ButtonSecondary + text={ + statusCodes[0]?.statusCode === 'DeviceNotConnected' + ? t('continue') + : t('tryAgain') + } + onClick={async () => initFetchAddress()} + /> + </div> + </> + ) : null} + </div> + </SplashWrapper> + </> + ); +}; diff --git a/src/modals/ImportLedger/Wrappers.ts b/src/modals/ImportLedger/Wrappers.ts new file mode 100644 index 0000000000..2b25b3e7c3 --- /dev/null +++ b/src/modals/ImportLedger/Wrappers.ts @@ -0,0 +1,99 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import styled from 'styled-components'; + +export const SplashWrapper = styled.div` + width: 100%; + height: 100%; + display: flex; + flex-flow: column wrap; + align-items: center; + justify-content: center; + + .icon { + width: 100%; + display: flex; + justify-content: center; + z-index: 0; + margin-bottom: 2rem; + } + + .content { + z-index: 1; + display: flex; + flex-flow: column nowrap; + justify-content: center; + margin-bottom: 2rem; + + h2, + h5 { + color: var(--text-color-secondary); + display: flex; + align-items: center; + justify-content: center; + margin-top: 0.35rem; + } + + h2 { + margin-bottom: 0.75rem; + } + h5 { + min-height: 2rem; + } + + .button { + display: flex; + justify-content: center; + margin-top: 1rem; + } + } +`; + +export const TitleWrapper = styled.div` + --tab-height: 2.25rem; + + background: var(--background-primary); + -webkit-app-region: drag; + padding: 0.95rem 0.85rem 0.7rem 0.85rem; + position: fixed; + top: 0; + left: 0; + width: 100%; + z-index: 3; + display: flex; + flex-direction: column; + + .tabs { + width: 100%; + height: var(--tab-height); + display: flex; + margin: 0.5rem 0 0.1rem 0; + + button { + padding: 0rem 1rem; + transition: background 0.15s; + height: var(--tab-height); + border-radius: 0.4rem; + margin-right: 0.75rem; + + > div { + height: var(--tab-height); + display: flex; + align-items: center; + } + &:hover { + background: var(--background-secondary); + } + &.active { + background: var(--background-secondary); + } + } + } + + > h5 { + svg { + margin-right: 0.4rem; + } + } +`; diff --git a/src/modals/ImportLedger/index.tsx b/src/modals/ImportLedger/index.tsx new file mode 100644 index 0000000000..685279d5fa --- /dev/null +++ b/src/modals/ImportLedger/index.tsx @@ -0,0 +1,169 @@ +// Copyright 2022 @paritytech/polkadot-native authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { ellipsisFn, setStateWithRef } from '@polkadot-cloud/utils'; +import React, { useEffect, useRef, useState } from 'react'; +import { useLedgerHardware } from 'contexts/Hardware/Ledger'; +import { getLocalLedgerAddresses } from 'contexts/Hardware/Utils'; +import type { LedgerAddress, LedgerResponse } from 'contexts/Hardware/types'; +import { useLedgerLoop } from 'library/Hooks/useLedgerLoop'; +import type { AnyJson } from 'types'; +import { useOverlay } from '@polkadot-cloud/react/hooks'; +import { useNetwork } from 'contexts/Network'; +import { Manage } from './Manage'; +import { Splash } from './Splash'; + +export const ImportLedger: React.FC = () => { + const { network } = useNetwork(); + const { setModalResize } = useOverlay().modal; + const { + transportResponse, + getIsExecuting, + setIsExecuting, + resetStatusCodes, + handleNewStatusCode, + isPaired, + getStatusCodes, + handleUnmount, + } = useLedgerHardware(); + + // Gets the next non-imported address index. + const getNextAddressIndex = () => { + if (!addressesRef.current.length) { + return 0; + } + return addressesRef.current[addressesRef.current.length - 1].index + 1; + }; + + // Ledger loop needs to keep track of whether this component is mounted. If it is unmounted then + // the loop will cancel & ledger metadata will be cleared up. isMounted needs to be given as a + // function so the interval fetches the real value. + const isMounted = useRef(true); + const getIsMounted = () => isMounted.current; + + const { handleLedgerLoop } = useLedgerLoop({ + tasks: ['get_address'], + options: { + accountIndex: getNextAddressIndex, + }, + mounted: getIsMounted, + }); + + // Store addresses retreived from Ledger device. Defaults to local addresses. + const [addresses, setAddresses] = useState<LedgerAddress[]>( + getLocalLedgerAddresses(network) + ); + const addressesRef = useRef(addresses); + + const removeLedgerAddress = (address: string) => { + let newLedgerAddresses = getLocalLedgerAddresses(); + + newLedgerAddresses = newLedgerAddresses.filter((a) => { + if (a.address !== address) { + return true; + } + if (a.network !== network) { + return true; + } + return false; + }); + if (!newLedgerAddresses.length) { + localStorage.removeItem('ledger_addresses'); + } else { + localStorage.setItem( + 'ledger_addresses', + JSON.stringify(newLedgerAddresses) + ); + } + setStateWithRef( + newLedgerAddresses.filter((a: LedgerAddress) => a.network === network), + setAddresses, + addressesRef + ); + }; + + // refresh imported ledger accounts on network change. + useEffect(() => { + setStateWithRef( + getLocalLedgerAddresses(network), + setAddresses, + addressesRef + ); + }, [network]); + + // Handle new Ledger status report. + const handleLedgerStatusResponse = (response: LedgerResponse) => { + if (!response) return; + + const { ack, statusCode, body, options } = response; + handleNewStatusCode(ack, statusCode); + + if (statusCode === 'ReceivedAddress') { + const newAddress = body.map(({ pubKey, address }: LedgerAddress) => ({ + index: options.accountIndex, + pubKey, + address, + name: ellipsisFn(address), + network, + })); + + // update the full list of local ledger addresses with new entry. + const newAddresses = getLocalLedgerAddresses() + .filter((a: AnyJson) => { + if (a.address !== newAddress.address) { + return true; + } + if (a.network !== network) { + return true; + } + return false; + }) + .concat(newAddress); + localStorage.setItem('ledger_addresses', JSON.stringify(newAddresses)); + + setIsExecuting(false); + + // store only those accounts on the current network in state. + setStateWithRef( + newAddresses.filter((a) => a.network === network), + setAddresses, + addressesRef + ); + resetStatusCodes(); + } + }; + + // Resize modal on content change. + useEffect(() => { + setModalResize(); + }, [isPaired, getStatusCodes(), addressesRef.current]); + + // Listen for new Ledger status reports. + useEffect(() => { + if (getIsExecuting()) { + handleLedgerStatusResponse(transportResponse); + } + }, [transportResponse]); + + // Tidy up context state when this component is no longer mounted. + useEffect(() => { + return () => { + isMounted.current = false; + handleUnmount(); + }; + }, []); + + return ( + <> + {!addressesRef.current.length ? ( + <Splash handleLedgerLoop={handleLedgerLoop} /> + ) : ( + <Manage + addresses={addressesRef.current} + removeLedgerAddress={removeLedgerAddress} + handleLedgerLoop={handleLedgerLoop} + /> + )} + </> + ); +}; diff --git a/src/modals/ImportVault/Reader.tsx b/src/modals/ImportVault/Reader.tsx new file mode 100644 index 0000000000..406a56f970 --- /dev/null +++ b/src/modals/ImportVault/Reader.tsx @@ -0,0 +1,93 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { ButtonSecondary } from '@polkadot-cloud/react'; +import { isValidAddress } from '@polkadot-cloud/utils'; +import { useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useVaultHardware } from 'contexts/Hardware/Vault'; +import { usePrompt } from 'contexts/Prompt'; +import { QRViewerWrapper } from 'library/Import/Wrappers'; +import { QrScanSignature } from 'library/QRCode/ScanSignature'; +import { useNetwork } from 'contexts/Network'; +import { formatAccountSs58 } from 'contexts/Connect/Utils'; +import { useOtherAccounts } from 'contexts/Connect/OtherAccounts'; + +export const Reader = () => { + const { t } = useTranslation('modals'); + const { + networkData: { ss58 }, + } = useNetwork(); + const { addOtherAccounts } = useOtherAccounts(); + const { setStatus: setPromptStatus } = usePrompt(); + const { addVaultAccount, vaultAccountExists, vaultAccounts } = + useVaultHardware(); + + // Store data from QR Code scanner. + const [qrData, setQrData] = useState<any>(undefined); + + // Store QR data feedback. + const [feedback, setFeedback] = useState<string>(''); + + const handleQrData = (signature: string) => { + setQrData(signature.split(':')?.[1] || ''); + }; + + const valid = + isValidAddress(qrData) && + !vaultAccountExists(qrData) && + !formatAccountSs58(qrData, ss58); + + // Reset QR data on open. + useEffect(() => { + setQrData(undefined); + }, []); + + useEffect(() => { + // Add account and close overlay if valid. + if (valid) { + const account = addVaultAccount(qrData, vaultAccounts.length); + if (account) { + addOtherAccounts([account]); + } + setPromptStatus(0); + } + + // Display feedback. + setFeedback( + qrData === undefined + ? `${t('waitingForQRCode')}` + : isValidAddress(qrData) + ? formatAccountSs58(qrData, ss58) + ? `${t('differentNetworkAddress')}` + : vaultAccountExists(qrData) + ? `${t('accountAlreadyImported')}` + : `${t('addressReceived')}` + : `${t('invalidAddress')}` + ); + }, [qrData]); + + return ( + <QRViewerWrapper> + <h3 className="title">{t('scanFromPolkadotVault')}</h3> + <div className="viewer"> + <QrScanSignature + size={279} + onScan={({ signature }) => { + handleQrData(signature); + }} + /> + </div> + <div className="foot"> + <h3>{feedback}</h3> + <div> + <ButtonSecondary + lg + text={t('cancel')} + onClick={() => setPromptStatus(0)} + /> + </div> + </div> + </QRViewerWrapper> + ); +}; diff --git a/src/modals/ImportVault/index.tsx b/src/modals/ImportVault/index.tsx new file mode 100644 index 0000000000..ce6e3e8bd5 --- /dev/null +++ b/src/modals/ImportVault/index.tsx @@ -0,0 +1,143 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { faQrcode } from '@fortawesome/free-solid-svg-icons'; +import { + ButtonPrimary, + ButtonText, + HardwareAddress, + HardwareStatusBar, + Polkicon, +} from '@polkadot-cloud/react'; +import { useEffect } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useVaultHardware } from 'contexts/Hardware/Vault'; +import { usePrompt } from 'contexts/Prompt'; +import PolkadotVaultSVG from '@polkadot-cloud/assets/extensions/svg/polkadotvault.svg?react'; +import { Confirm } from 'library/Import/Confirm'; +import { Heading } from 'library/Import/Heading'; +import { NoAccounts } from 'library/Import/NoAccounts'; +import { Remove } from 'library/Import/Remove'; +import { AddressesWrapper } from 'library/Import/Wrappers'; +import type { AnyJson } from 'types'; +import { useOverlay } from '@polkadot-cloud/react/hooks'; +import { useOtherAccounts } from 'contexts/Connect/OtherAccounts'; +import { Reader } from './Reader'; + +export const ImportVault = () => { + const { t } = useTranslation(); + const { replaceModal } = useOverlay().modal; + const { renameOtherAccount } = useOtherAccounts(); + const { openPromptWith, status: promptStatus } = usePrompt(); + + const { + vaultAccounts, + vaultAccountExists, + renameVaultAccount, + addVaultAccount, + removeVaultAccount, + getVaultAccount, + } = useVaultHardware(); + const { setModalResize } = useOverlay().modal; + + const renameHandler = (address: string, newName: string) => { + renameVaultAccount(address, newName); + renameOtherAccount(address, newName); + }; + + const openConfirmHandler = (address: string, index: number) => { + openPromptWith( + <Confirm address={address} index={index} addHandler={addVaultAccount} />, + 'small' + ); + }; + + const openRemoveHandler = (address: string) => { + openPromptWith( + <Remove + address={address} + removeHandler={removeVaultAccount} + getHandler={getVaultAccount} + />, + 'small' + ); + }; + + useEffect(() => { + setModalResize(); + }, [vaultAccounts]); + + return ( + <> + {vaultAccounts.length === 0 ? ( + <NoAccounts + Icon={PolkadotVaultSVG} + text={t('noVaultAccountsImported', { ns: 'modals' })} + > + <div> + <ButtonPrimary + lg + iconLeft={faQrcode} + text={t('importAccount', { ns: 'modals' })} + disabled={promptStatus !== 0} + onClick={() => { + openPromptWith(<Reader />, 'small'); + }} + /> + </div> + </NoAccounts> + ) : ( + <> + <Heading title={vaultAccounts.length ? 'Polkadot Vault' : ''} /> + <AddressesWrapper> + <div className="items"> + {vaultAccounts.map(({ address, name, index }: AnyJson, i) => ( + <HardwareAddress + key={i} + address={address} + index={index} + initial={name} + Identicon={<Polkicon address={address} size={40} />} + existsHandler={vaultAccountExists} + renameHandler={renameHandler} + openRemoveHandler={openRemoveHandler} + openConfirmHandler={openConfirmHandler} + t={{ + tRemove: t('remove', { ns: 'modals' }), + tImport: t('import', { ns: 'modals' }), + }} + /> + ))} + </div> + <div className="more"> + <ButtonText + iconLeft={faQrcode} + text={t('importAnotherAccount', { ns: 'modals' })} + disabled={promptStatus !== 0} + onClick={() => { + openPromptWith(<Reader />, 'small'); + }} + /> + </div> + </AddressesWrapper> + <HardwareStatusBar + show + Icon={PolkadotVaultSVG} + text={t('vaultAccounts', { + ns: 'modals', + count: vaultAccounts.length, + })} + inProgress={false} + handleDone={() => + replaceModal({ key: 'Connect', options: { disableScroll: true } }) + } + t={{ + tDone: t('done', { ns: 'library' }), + tCancel: t('cancel', { ns: 'library' }), + }} + /> + </> + )} + </> + ); +}; diff --git a/src/modals/JoinPool/index.tsx b/src/modals/JoinPool/index.tsx index 07d9143023..10fee24f7f 100644 --- a/src/modals/JoinPool/index.tsx +++ b/src/modals/JoinPool/index.tsx @@ -1,75 +1,109 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import { faArrowAltCircleUp } from '@fortawesome/free-regular-svg-icons'; -import { faUserPlus } from '@fortawesome/free-solid-svg-icons'; -import { ButtonSubmit } from '@rossbulat/polkadot-dashboard-ui'; -import { BN } from 'bn.js'; +import { ModalPadding } from '@polkadot-cloud/react'; +import { planckToUnit, unitToPlanck } from '@polkadot-cloud/utils'; +import BigNumber from 'bignumber.js'; +import { useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; import { useApi } from 'contexts/Api'; -import { useConnect } from 'contexts/Connect'; -import { useModal } from 'contexts/Modal'; import { usePoolMembers } from 'contexts/Pools/PoolMembers'; +import type { ClaimPermission } from 'contexts/Pools/types'; +import { useSetup } from 'contexts/Setup'; +import { defaultPoolProgress } from 'contexts/Setup/defaults'; import { useTransferOptions } from 'contexts/TransferOptions'; -import { useTxFees } from 'contexts/TxFees'; -import { useUi } from 'contexts/UI'; -import { defaultPoolSetup } from 'contexts/UI/defaults'; -import { SetupType } from 'contexts/UI/types'; -import { EstimatedTxFee } from 'library/EstimatedTxFee'; +import { useTxMeta } from 'contexts/TxMeta'; import { BondFeedback } from 'library/Form/Bond/BondFeedback'; -import useBondGreatestFee from 'library/Hooks/useBondGreatestFee'; +import { ClaimPermissionInput } from 'library/Form/ClaimPermissionInput'; +import { useBatchCall } from 'library/Hooks/useBatchCall'; +import { useBondGreatestFee } from 'library/Hooks/useBondGreatestFee'; +import { useSignerWarnings } from 'library/Hooks/useSignerWarnings'; import { useSubmitExtrinsic } from 'library/Hooks/useSubmitExtrinsic'; -import { Title } from 'library/Modal/Title'; -import { useEffect, useState } from 'react'; -import { planckBnToUnit, unitToPlanckBn } from 'Utils'; -import { FooterWrapper, NotesWrapper, PaddingWrapper } from '../Wrappers'; -import { ContentWrapper } from './Wrapper'; +import { Close } from 'library/Modal/Close'; +import { SubmitTx } from 'library/SubmitTx'; +import { useOverlay } from '@polkadot-cloud/react/hooks'; +import { useNetwork } from 'contexts/Network'; +import { useActiveAccounts } from 'contexts/ActiveAccounts'; export const JoinPool = () => { - const { api, network } = useApi(); - const { units } = network; - const { setStatus: setModalStatus, config, setResize } = useModal(); - const { id: poolId, setActiveTab } = config; - const { activeAccount, accountHasSigner } = useConnect(); - const { queryPoolMember, addToPoolMembers } = usePoolMembers(); - const { setActiveAccountSetup } = useUi(); - const { txFeesValid } = useTxFees(); + const { t } = useTranslation('modals'); + const { api } = useApi(); + const { + networkData: { units }, + } = useNetwork(); + const { activeAccount } = useActiveAccounts(); + const { newBatchCall } = useBatchCall(); + const { setActiveAccountSetup } = useSetup(); + const { txFees, notEnoughFunds } = useTxMeta(); + const { getSignerWarnings } = useSignerWarnings(); const { getTransferOptions } = useTransferOptions(); - const { freeBalance } = getTransferOptions(activeAccount); - const largestTxFee = useBondGreatestFee({ bondType: 'pool' }); + const { queryPoolMember, addToPoolMembers } = usePoolMembers(); + const { + setModalStatus, + config: { options }, + setModalResize, + } = useOverlay().modal; + + const { id: poolId, setActiveTab } = options; + + const { totalPossibleBond, totalAdditionalBond } = + getTransferOptions(activeAccount).pool; + + const largestTxFee = useBondGreatestFee({ bondFor: 'pool' }); + + // if we are bonding, subtract tx fees from bond amount + const freeBondAmount = BigNumber.max(totalAdditionalBond.minus(txFees), 0); // local bond value - const [bond, setBond] = useState({ - bond: planckBnToUnit(freeBalance, units), + const [bond, setBond] = useState<{ bond: string }>({ + bond: planckToUnit(totalPossibleBond, units).toString(), }); + // Updated claim permission value + const [claimPermission, setClaimPermission] = useState< + ClaimPermission | undefined + >('Permissioned'); + // bond valid const [bondValid, setBondValid] = useState<boolean>(false); + // feedback errors to trigger modal resize + const [feedbackErrors, setFeedbackErrors] = useState<string[]>([]); + // modal resize on form update - useEffect(() => { - setResize(); - }, [bond]); + useEffect( + () => setModalResize(), + [bond, notEnoughFunds, feedbackErrors.length] + ); // tx to submit const getTx = () => { - let tx = null; - if (!bondValid || !api) { + const tx = null; + if (!api) { return tx; } - // remove decimal errors - const bondToSubmit = unitToPlanckBn(bond.bond, units); - tx = api.tx.nominationPools.join(bondToSubmit, poolId); + const bondToSubmit = unitToPlanck(!bondValid ? '0' : bond.bond, units); + const bondAsString = bondToSubmit.isNaN() ? '0' : bondToSubmit.toString(); + const txs = [api.tx.nominationPools.join(bondAsString, poolId)]; + + if (![undefined, 'Permissioned'].includes(claimPermission)) { + txs.push(api.tx.nominationPools.setClaimPermission(claimPermission)); + } + + if (txs.length === 1) { + return txs[0]; + } - return tx; + return newBatchCall(txs, activeAccount); }; - const { submitTx, submitting } = useSubmitExtrinsic({ + const submitExtrinsic = useSubmitExtrinsic({ tx: getTx(), from: activeAccount, shouldSubmit: bondValid, callbackSubmit: () => { - setModalStatus(2); + setModalStatus('closing'); setActiveTab(0); }, callbackInBlock: async () => { @@ -78,56 +112,49 @@ export const JoinPool = () => { addToPoolMembers(member); // reset localStorage setup progress - setActiveAccountSetup(SetupType.Pool, defaultPoolSetup); + setActiveAccountSetup('pool', defaultPoolProgress); }, }); - const warnings = []; - if (!accountHasSigner(activeAccount)) { - warnings.push('Your account is read only, and cannot sign transactions.'); - } + const warnings = getSignerWarnings( + activeAccount, + false, + submitExtrinsic.proxySupported + ); + return ( <> - <Title title="Join Pool" icon={faUserPlus} /> - <PaddingWrapper> - <ContentWrapper> - <div> - <BondFeedback - syncing={largestTxFee.eq(new BN(0))} - bondType="pool" - listenIsValid={setBondValid} - defaultBond={null} - setters={[ - { - set: setBond, - current: bond, - }, - ]} - warnings={warnings} - txFees={largestTxFee} - /> - <NotesWrapper> - <EstimatedTxFee /> - </NotesWrapper> - </div> - <FooterWrapper> - <div> - <ButtonSubmit - text={`Submit${submitting ? 'ting' : ''}`} - iconLeft={faArrowAltCircleUp} - iconTransform="grow-2" - onClick={() => submitTx()} - disabled={ - submitting || - !bondValid || - !accountHasSigner(activeAccount) || - !txFeesValid - } - /> - </div> - </FooterWrapper> - </ContentWrapper> - </PaddingWrapper> + <Close /> + <ModalPadding> + <h2 className="title unbounded">{t('joinPool')}</h2> + <BondFeedback + syncing={largestTxFee.isZero()} + joiningPool + bondFor="pool" + listenIsValid={(valid, errors) => { + setBondValid(valid); + setFeedbackErrors(errors); + }} + defaultBond={null} + setters={[ + { + set: setBond, + current: bond, + }, + ]} + parentErrors={warnings} + txFees={largestTxFee} + /> + <ClaimPermissionInput + current={undefined} + permissioned={false} + onChange={(val: ClaimPermission | undefined) => { + setClaimPermission(val); + }} + disabled={freeBondAmount.isZero()} + /> + </ModalPadding> + <SubmitTx valid={bondValid} {...submitExtrinsic} /> </> ); }; diff --git a/src/modals/LeavePool/Wrapper.ts b/src/modals/LeavePool/Wrapper.ts deleted file mode 100644 index 8694a143dc..0000000000 --- a/src/modals/LeavePool/Wrapper.ts +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import styled from 'styled-components'; - -export const ContentWrapper = styled.div` - width: 100%; -`; diff --git a/src/modals/LeavePool/index.tsx b/src/modals/LeavePool/index.tsx deleted file mode 100644 index 461376e7ae..0000000000 --- a/src/modals/LeavePool/index.tsx +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { faSignOutAlt } from '@fortawesome/free-solid-svg-icons'; -import { Title } from 'library/Modal/Title'; -import { UnbondAll } from 'modals/UpdateBond/Forms/UnbondAll'; -import { PaddingWrapper } from '../Wrappers'; - -export const LeavePool = () => { - return ( - <> - <Title title="Leave Pool" icon={faSignOutAlt} /> - <PaddingWrapper> - <UnbondAll /> - </PaddingWrapper> - </> - ); -}; diff --git a/src/modals/ManageFastUnstake/index.tsx b/src/modals/ManageFastUnstake/index.tsx new file mode 100644 index 0000000000..1e3fe4ca14 --- /dev/null +++ b/src/modals/ManageFastUnstake/index.tsx @@ -0,0 +1,223 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { + ActionItem, + ModalNotes, + ModalPadding, + ModalWarnings, +} from '@polkadot-cloud/react'; +import { planckToUnit } from '@polkadot-cloud/utils'; +import BigNumber from 'bignumber.js'; +import { useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useApi } from 'contexts/Api'; +import { useBonded } from 'contexts/Bonded'; +import { useFastUnstake } from 'contexts/FastUnstake'; +import { useNetworkMetrics } from 'contexts/NetworkMetrics'; +import { useTransferOptions } from 'contexts/TransferOptions'; +import { Warning } from 'library/Form/Warning'; +import { useSignerWarnings } from 'library/Hooks/useSignerWarnings'; +import { useSubmitExtrinsic } from 'library/Hooks/useSubmitExtrinsic'; +import { useUnstaking } from 'library/Hooks/useUnstaking'; +import { Close } from 'library/Modal/Close'; +import { SubmitTx } from 'library/SubmitTx'; +import { useTxMeta } from 'contexts/TxMeta'; +import { useOverlay } from '@polkadot-cloud/react/hooks'; +import { useNetwork } from 'contexts/Network'; +import { useActiveAccounts } from 'contexts/ActiveAccounts'; + +export const ManageFastUnstake = () => { + const { t } = useTranslation('modals'); + const { api, consts } = useApi(); + const { + networkData: { units, unit }, + } = useNetwork(); + const { activeAccount } = useActiveAccounts(); + const { notEnoughFunds } = useTxMeta(); + const { getBondedAccount } = useBonded(); + const { isFastUnstaking } = useUnstaking(); + const { setModalResize, setModalStatus } = useOverlay().modal; + const { getSignerWarnings } = useSignerWarnings(); + const { activeEra, metrics } = useNetworkMetrics(); + const { feeReserve, getTransferOptions } = useTransferOptions(); + const { isExposed, counterForQueue, queueDeposit, meta } = useFastUnstake(); + + const { bondDuration, fastUnstakeDeposit } = consts; + const { fastUnstakeErasToCheckPerBlock } = metrics; + const { checked } = meta; + const controller = getBondedAccount(activeAccount); + const allTransferOptions = getTransferOptions(activeAccount); + const { nominate, freeBalance } = allTransferOptions; + const { totalUnlockChuncks } = nominate; + + const enoughForDeposit = freeBalance + .minus(feeReserve) + .isGreaterThanOrEqualTo(fastUnstakeDeposit); + + // valid to submit transaction + const [valid, setValid] = useState<boolean>(false); + + useEffect(() => { + setValid( + fastUnstakeErasToCheckPerBlock > 0 && + ((!isFastUnstaking && + enoughForDeposit && + isExposed === false && + totalUnlockChuncks === 0) || + isFastUnstaking) + ); + }, [ + isExposed, + fastUnstakeErasToCheckPerBlock, + totalUnlockChuncks, + isFastUnstaking, + fastUnstakeDeposit, + freeBalance, + feeReserve, + ]); + + useEffect( + () => setModalResize(), + [notEnoughFunds, isExposed, queueDeposit, isFastUnstaking] + ); + + // tx to submit + const getTx = () => { + let tx = null; + if (!valid || !api) { + return tx; + } + if (!isFastUnstaking) { + tx = api.tx.fastUnstake.registerFastUnstake(); + } else { + tx = api.tx.fastUnstake.deregister(); + } + return tx; + }; + + const submitExtrinsic = useSubmitExtrinsic({ + tx: getTx(), + from: controller, + shouldSubmit: valid, + callbackSubmit: () => {}, + callbackInBlock: () => { + setModalStatus('closing'); + }, + }); + + // warnings + const warnings = getSignerWarnings( + activeAccount, + true, + submitExtrinsic.proxySupported + ); + + if (!isFastUnstaking) { + if (!enoughForDeposit) { + warnings.push( + `${t('noEnough')} ${planckToUnit( + fastUnstakeDeposit, + units + ).toString()} ${unit}` + ); + } + + if (totalUnlockChuncks > 0) { + warnings.push( + `${t('fastUnstakeWarningUnlocksActive', { + count: totalUnlockChuncks, + })} ${t('fastUnstakeWarningUnlocksActiveMore')}` + ); + } + } + + // manage last exposed + const lastExposedAgo = !isExposed + ? new BigNumber(0) + : activeEra.index.minus(checked[0] || 0); + + const erasRemaining = BigNumber.max(1, bondDuration.minus(lastExposedAgo)); + + return ( + <> + <Close /> + <ModalPadding> + <h2 className="title unbounded"> + {t('fastUnstake', { context: 'title' })} + </h2> + {warnings.length > 0 ? ( + <ModalWarnings withMargin> + {warnings.map((text, i) => ( + <Warning key={`warning_${i}`} text={text} /> + ))} + </ModalWarnings> + ) : null} + + {isExposed ? ( + <> + <ActionItem + text={t('fastUnstakeExposedAgo', { + count: lastExposedAgo.toNumber(), + })} + /> + <ModalNotes> + <p> + {t('fastUnstakeNote1', { + bondDuration: bondDuration.toString(), + })} + </p> + <p> + {t('fastUnstakeNote2', { count: erasRemaining.toNumber() })} + </p> + </ModalNotes> + </> + ) : ( + <> + {!isFastUnstaking ? ( + <> + <ActionItem text={t('fastUnstake', { context: 'register' })} /> + <ModalNotes> + <p> + <> + {t('registerFastUnstake')}{' '} + {planckToUnit(fastUnstakeDeposit, units).toString()}{' '} + {unit}. {t('fastUnstakeOnceRegistered')} + </> + </p> + <p> + {t('fastUnstakeCurrentQueue')}: <b>{counterForQueue}</b> + </p> + </ModalNotes> + </> + ) : ( + <> + <ActionItem text={t('fastUnstakeRegistered')} /> + <ModalNotes> + <p> + {t('fastUnstakeCurrentQueue')}: <b>{counterForQueue}</b> + </p> + <p>{t('fastUnstakeUnorderedNote')}</p> + </ModalNotes> + </> + )} + </> + )} + </ModalPadding> + {!isExposed ? ( + <SubmitTx + fromController + valid={valid} + submitText={ + submitExtrinsic.submitting + ? t('submitting') + : t('fastUnstakeSubmit', { + context: isFastUnstaking ? 'cancel' : 'register', + }) + } + {...submitExtrinsic} + /> + ) : null} + </> + ); +}; diff --git a/src/modals/ManagePool/Forms.tsx b/src/modals/ManagePool/Forms.tsx deleted file mode 100644 index c546fc7d35..0000000000 --- a/src/modals/ManagePool/Forms.tsx +++ /dev/null @@ -1,256 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { IconProp } from '@fortawesome/fontawesome-svg-core'; -import { faArrowAltCircleUp } from '@fortawesome/free-regular-svg-icons'; -import { faChevronLeft } from '@fortawesome/free-solid-svg-icons'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { u8aToString, u8aUnwrapBytes } from '@polkadot/util'; -import { ButtonSubmit } from '@rossbulat/polkadot-dashboard-ui'; -import { useApi } from 'contexts/Api'; -import { useConnect } from 'contexts/Connect'; -import { useModal } from 'contexts/Modal'; -import { useActivePools } from 'contexts/Pools/ActivePools'; -import { useBondedPools } from 'contexts/Pools/BondedPools'; -import { BondedPool, PoolState } from 'contexts/Pools/types'; -import { useTxFees } from 'contexts/TxFees'; -import { EstimatedTxFee } from 'library/EstimatedTxFee'; -import { Warning } from 'library/Form/Warning'; -import { useSubmitExtrinsic } from 'library/Hooks/useSubmitExtrinsic'; -import React, { forwardRef, useEffect, useState } from 'react'; -import { Separator } from 'Wrappers'; -import { FooterWrapper, NotesWrapper } from '../Wrappers'; -import { ContentWrapper } from './Wrappers'; - -export const Forms = forwardRef((props: any, ref: any) => { - const { setSection, task, section } = props; - - const { api } = useApi(); - const { setStatus: setModalStatus } = useModal(); - const { activeAccount, accountHasSigner } = useConnect(); - const { isOwner, isStateToggler, selectedActivePool } = useActivePools(); - const { bondedPools, meta, updateBondedPools, getBondedPool } = - useBondedPools(); - const { txFeesValid } = useTxFees(); - const poolId = selectedActivePool?.id; - - // valid to submit transaction - const [valid, setValid] = useState<boolean>(false); - - // updated metadata value - const [metadata, setMetadata] = useState<string>(''); - - // ensure account has relevant roles for task - const canToggle = - (isOwner() || isStateToggler()) && - ['destroy_pool', 'unlock_pool', 'lock_pool'].includes(task); - const canRename = isOwner() && task === 'set_pool_metadata'; - const isValid = canToggle || canRename; - - // determine current pool metadata and set in state - useEffect(() => { - if (task === 'set_pool_metadata') { - let _metadata = ''; - const pool = bondedPools.find((p: any) => { - return p.addresses.stash === selectedActivePool?.addresses.stash; - }); - - if (pool) { - const metadataBatch = meta.bonded_pools?.metadata ?? []; - const batchIndex = bondedPools.indexOf(pool); - _metadata = metadataBatch[batchIndex]; - setMetadata(u8aToString(u8aUnwrapBytes(_metadata))); - } - } - }, [section]); - - useEffect(() => { - setValid(isValid); - }, [isValid]); - - const content = (() => { - let title; - let message; - switch (task) { - case 'set_pool_metadata': - title = undefined; - message = ( - <p> - Your updated name will be stored on-chain as encoded bytes. The - update will take effect immediately. - </p> - ); - break; - case 'destroy_pool': - title = <h2>Destroying a Pool is Irreversible</h2>; - message = ( - <p> - Once you Destroy the pool, all members can be permissionlessly - unbonded, and the pool can never be reopened. - </p> - ); - break; - case 'unlock_pool': - title = <h2>Submit Pool Unlock</h2>; - message = <p>Once you Unlock the pool new people can join the pool.</p>; - break; - case 'lock_pool': - title = <h2>Submit Pool Lock</h2>; - message = <p>Once you Lock the pool no one else can join the pool.</p>; - break; - default: - title = null; - message = null; - } - return { title, message }; - })(); - - const poolStateFromTask = (t: string) => { - switch (t) { - case 'destroy_pool': - return PoolState.Destroy; - case 'lock_pool': - return PoolState.Block; - default: - return PoolState.Open; - } - }; - - // tx to submit - const getTx = () => { - let tx = null; - - if (!valid || !api) { - return tx; - } - - // remove decimal errors - switch (task) { - case 'set_pool_metadata': - tx = api.tx.nominationPools.setMetadata(poolId, metadata); - break; - case 'destroy_pool': - tx = api.tx.nominationPools.setState(poolId, PoolState.Destroy); - break; - case 'unlock_pool': - tx = api.tx.nominationPools.setState(poolId, PoolState.Open); - break; - case 'lock_pool': - tx = api.tx.nominationPools.setState(poolId, PoolState.Block); - break; - default: - tx = null; - } - - return tx; - }; - - const { submitTx, submitting } = useSubmitExtrinsic({ - tx: getTx(), - from: activeAccount, - shouldSubmit: true, - callbackSubmit: () => { - setModalStatus(2); - }, - callbackInBlock: () => { - // reflect updated state in bondedPools list - if ( - ['destroy_pool', 'unlock_pool', 'lock_pool'].includes(task) && - poolId - ) { - const pool: BondedPool | null = getBondedPool(poolId); - - if (pool) { - updateBondedPools([ - { - ...pool, - state: poolStateFromTask(task), - }, - ]); - } - } - }, - }); - - const handleMetadataChange = (e: React.FormEvent<HTMLInputElement>) => { - const newValue = e.currentTarget.value; - setMetadata(newValue); - // any string is valid metadata - setValid(true); - }; - - return ( - <ContentWrapper> - <div className="items" ref={ref}> - {!accountHasSigner(activeAccount) && ( - <Warning text="Your account is read only, and cannot sign transactions." /> - )} - <div> - <> - {/* include task title if present */} - {content.title !== undefined && ( - <> - {content.title} - <Separator /> - </> - )} - - {/* include form element if task is to set metadata */} - {task === 'set_pool_metadata' && ( - <> - <h2>Update Pool Name</h2> - <input - className="textbox" - style={{ width: '100%' }} - placeholder="Pool Name" - type="text" - onChange={(e: React.FormEvent<HTMLInputElement>) => - handleMetadataChange(e) - } - value={metadata ?? ''} - /> - </> - )} - - <NotesWrapper> - {content.message} - <EstimatedTxFee /> - </NotesWrapper> - </> - </div> - <FooterWrapper> - <div> - <button - type="button" - className="submit secondary" - onClick={() => setSection(0)} - disabled={submitting} - > - <FontAwesomeIcon - transform="grow-2" - icon={faChevronLeft as IconProp} - /> - Back - </button> - </div> - <div> - <ButtonSubmit - text={`Submit${submitting ? 'ting' : ''}`} - iconLeft={faArrowAltCircleUp} - iconTransform="grow-2" - onClick={() => submitTx()} - disabled={ - submitting || - !accountHasSigner(activeAccount) || - !valid || - !txFeesValid - } - /> - </div> - </FooterWrapper> - </div> - </ContentWrapper> - ); -}); - -export default Forms; diff --git a/src/modals/ManagePool/Forms/ClaimCommission.tsx b/src/modals/ManagePool/Forms/ClaimCommission.tsx new file mode 100644 index 0000000000..2abe71ed60 --- /dev/null +++ b/src/modals/ManagePool/Forms/ClaimCommission.tsx @@ -0,0 +1,106 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { faChevronLeft } from '@fortawesome/free-solid-svg-icons'; +import { + ActionItem, + ButtonSubmitInvert, + ModalNotes, + ModalWarnings, +} from '@polkadot-cloud/react'; +import { greaterThanZero, planckToUnit, rmCommas } from '@polkadot-cloud/utils'; +import BigNumber from 'bignumber.js'; +import { useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useApi } from 'contexts/Api'; +import { useActivePools } from 'contexts/Pools/ActivePools'; +import { Warning } from 'library/Form/Warning'; +import { useSignerWarnings } from 'library/Hooks/useSignerWarnings'; +import { useSubmitExtrinsic } from 'library/Hooks/useSubmitExtrinsic'; +import { SubmitTx } from 'library/SubmitTx'; +import { useOverlay } from '@polkadot-cloud/react/hooks'; +import { useNetwork } from 'contexts/Network'; +import { useActiveAccounts } from 'contexts/ActiveAccounts'; + +export const ClaimCommission = ({ setSection }: any) => { + const { t } = useTranslation('modals'); + const { api } = useApi(); + const { + networkData: { units, unit }, + } = useNetwork(); + const { setModalStatus } = useOverlay().modal; + const { activeAccount } = useActiveAccounts(); + const { isOwner, selectedActivePool } = useActivePools(); + const { getSignerWarnings } = useSignerWarnings(); + const poolId = selectedActivePool?.id; + const pendingCommission = new BigNumber( + rmCommas(selectedActivePool?.rewardPool?.totalCommissionPending || '0') + ); + + // valid to submit transaction + const [valid, setValid] = useState<boolean>(false); + + useEffect(() => { + setValid(isOwner() && greaterThanZero(pendingCommission)); + }, [selectedActivePool, pendingCommission]); + + // tx to submit + const getTx = () => { + if (!valid || !api) { + return null; + } + return api.tx.nominationPools.claimCommission(poolId); + }; + + const submitExtrinsic = useSubmitExtrinsic({ + tx: getTx(), + from: activeAccount, + shouldSubmit: true, + callbackSubmit: () => { + setModalStatus('closing'); + }, + callbackInBlock: () => {}, + }); + + const warnings = getSignerWarnings( + activeAccount, + false, + submitExtrinsic.proxySupported + ); + + return ( + <> + <div className="padding"> + {warnings.length > 0 ? ( + <ModalWarnings withMargin> + {warnings.map((text, i) => ( + <Warning key={`warning${i}`} text={text} /> + ))} + </ModalWarnings> + ) : null} + <ActionItem + text={`${t('claim')} ${planckToUnit( + pendingCommission, + units + )} ${unit} `} + /> + <ModalNotes> + <p>{t('sentToCommissionPayee')}</p> + </ModalNotes> + </div> + <SubmitTx + valid={valid} + buttons={[ + <ButtonSubmitInvert + key="button_back" + text={t('back')} + iconLeft={faChevronLeft} + iconTransform="shrink-1" + onClick={() => setSection(0)} + />, + ]} + {...submitExtrinsic} + /> + </> + ); +}; diff --git a/src/modals/ManagePool/Forms/Commission.tsx b/src/modals/ManagePool/Forms/Commission.tsx new file mode 100644 index 0000000000..123b1475f3 --- /dev/null +++ b/src/modals/ManagePool/Forms/Commission.tsx @@ -0,0 +1,656 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { faChevronLeft } from '@fortawesome/free-solid-svg-icons'; +import { + ActionItem, + ButtonHelp, + ButtonSubmitInvert, + ModalWarnings, +} from '@polkadot-cloud/react'; +import { rmCommas } from '@polkadot-cloud/utils'; +import BigNumber from 'bignumber.js'; +import { intervalToDuration } from 'date-fns'; +import Slider from 'rc-slider'; +import { useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useApi } from 'contexts/Api'; +import { useHelp } from 'contexts/Help'; +import { useActivePools } from 'contexts/Pools/ActivePools'; +import { useBondedPools } from 'contexts/Pools/BondedPools'; +import { usePoolsConfig } from 'contexts/Pools/PoolsConfig'; +import { AccountInput } from 'library/AccountInput'; +import { MinDelayInput } from 'library/Form/MinDelayInput'; +import { Warning } from 'library/Form/Warning'; +import { useBatchCall } from 'library/Hooks/useBatchCall'; +import { useSignerWarnings } from 'library/Hooks/useSignerWarnings'; +import { useSubmitExtrinsic } from 'library/Hooks/useSubmitExtrinsic'; +import { SubmitTx } from 'library/SubmitTx'; +import 'rc-slider/assets/index.css'; +import type { MaybeAddress } from 'types'; +import { useOverlay } from '@polkadot-cloud/react/hooks'; +import { useActiveAccounts } from 'contexts/ActiveAccounts'; +import { SliderWrapper } from '../Wrappers'; +import type { ChangeRateInput } from './types'; + +export const Commission = ({ setSection, incrementCalculateHeight }: any) => { + const { t } = useTranslation('modals'); + const { openHelp } = useHelp(); + const { api, consts } = useApi(); + const { activeAccount } = useActiveAccounts(); + const { newBatchCall } = useBatchCall(); + const { stats } = usePoolsConfig(); + const { setModalStatus } = useOverlay().modal; + const { getSignerWarnings } = useSignerWarnings(); + const { isOwner, selectedActivePool } = useActivePools(); + const { getBondedPool, updateBondedPools } = useBondedPools(); + const { expectedBlockTime } = consts; + const { globalMaxCommission } = stats; + + const poolId = selectedActivePool?.id || 0; + const bondedPool = getBondedPool(poolId); + + const commissionCurrentSet = !!bondedPool?.commission?.current; + const initialCommission = Number( + (bondedPool?.commission?.current?.[0] || '0%').slice(0, -1) + ); + const initialPayee = bondedPool?.commission?.current?.[1] || null; + + const maxCommissionSet = !!bondedPool?.commission?.max; + const initialMaxCommission = Number( + (bondedPool?.commission?.max || '100%').slice(0, -1) + ); + + const changeRateSet = !!bondedPool?.commission?.changeRate; + const initialChangeRate = (() => { + const raw = bondedPool?.commission?.changeRate; + return raw + ? { + maxIncrease: Number(raw.maxIncrease.slice(0, -1)), + minDelay: Number(rmCommas(raw.minDelay)), + } + : { + maxIncrease: 100, + minDelay: 0, + }; + })(); + + // Store the current commission value. + const [commission, setCommission] = useState<number>(initialCommission); + + // Max commission enabled. + const [maxCommissionEnabled, setMaxCommissionEnabled] = useState<boolean>( + !!maxCommissionSet + ); + + // Change rate enabled. + const [changeRateEnabled, setChangeRateEnabled] = useState<boolean>( + !!changeRateSet + ); + + // Store the commission payee. + const [payee, setPayee] = useState<MaybeAddress>(initialPayee); + + // Store the maximum commission value. + const [maxCommission, setMaxCommission] = + useState<number>(initialMaxCommission); + + // Store the change rate value. + const [changeRate, setChangeRate] = useState<{ + maxIncrease: number; + minDelay: number; + }>(initialChangeRate); + + // Convert a block number into an estimated change rate duration. + const minDelayToInput = (delay: number) => { + const milliseconds = expectedBlockTime.multipliedBy(delay); + const end = milliseconds.isZero() + ? 0 + : milliseconds.integerValue().toNumber(); + + const { years, months, days, hours, minutes } = intervalToDuration({ + start: 0, + end, + }); + + return { + years: years || 0, + months: months || 0, + days: days || 0, + hours: hours || 0, + minutes: minutes || 0, + }; + }; + + const inputToMinDelay = (input: ChangeRateInput) => { + const { years, months, days, hours, minutes } = input; + + // calculate number of seconds from changeRateInput + const yearsSeconds = new BigNumber(years).multipliedBy(31536000); + const monthsSeconds = new BigNumber(months).multipliedBy(2628288); + const daysSeconds = new BigNumber(days).multipliedBy(86400); + const hoursSeconds = new BigNumber(hours).multipliedBy(3600); + const minutesSeconds = new BigNumber(minutes).multipliedBy(60); + + return yearsSeconds + .plus(monthsSeconds) + .plus(daysSeconds) + .plus(hoursSeconds) + .plus(minutesSeconds) + .dividedBy(expectedBlockTime.dividedBy(1000)) + .integerValue() + .toNumber(); + }; + + // Store the change rate value in input format. + const [changeRateInput, setChangeRateInput] = useState<ChangeRateInput>( + minDelayToInput(changeRate.minDelay) + ); + + // Valid to submit transaction + const [valid, setValid] = useState<boolean>(false); + + const handleChangeRateInput = (field: string, value: number) => { + const newChangeRateInput = { + ...changeRateInput, + [field]: value, + }; + setChangeRateInput(newChangeRateInput); + setChangeRate({ + ...changeRate, + minDelay: inputToMinDelay(newChangeRateInput), + }); + }; + + const resetToDefault = () => { + setCommission(initialCommission); + setPayee(initialPayee); + setMaxCommission(initialMaxCommission); + }; + + const hasCurrentCommission = payee && commission !== 0; + const commissionCurrent = () => { + return hasCurrentCommission ? [`${commission.toFixed(2)}%`, payee] : null; + }; + + // Monitor when input items change. + const commissionUpdated = + (!commissionCurrentSet && commission !== initialCommission) || + (commissionCurrentSet && commission !== initialCommission); + + const maxCommissionUpdated = + (!maxCommissionSet && maxCommission === initialMaxCommission) || + maxCommission !== initialMaxCommission || + (!maxCommissionSet && maxCommissionEnabled); + + const changeRateUpdated = + (!changeRateSet && + JSON.stringify(changeRate) === JSON.stringify(initialChangeRate)) || + (changeRateSet && + JSON.stringify(changeRate) !== JSON.stringify(initialChangeRate)) || + (!changeRateSet && changeRateEnabled); + + const maxIncreaseUpdated = + changeRate.maxIncrease !== initialChangeRate.maxIncrease; + const minDelayUpdated = changeRate.minDelay !== initialChangeRate.minDelay; + + // Global form change. + const noChange = + !commissionUpdated && !maxCommissionUpdated && !changeRateUpdated; + + // Monitor when input items are invalid. + const commissionAboveMax = commission > maxCommission; + const commissionAboveGlobal = commission > globalMaxCommission; + + const commissionAboveMaxIncrease = + changeRateSet && commission - initialCommission > changeRate.maxIncrease; + + const invalidCurrentCommission = + commissionUpdated && + ((commission === 0 && payee !== null) || + (commission !== 0 && payee === null) || + commissionAboveMax || + commissionAboveMaxIncrease || + commission > globalMaxCommission); + + const invalidMaxCommission = + maxCommissionUpdated && maxCommission > initialMaxCommission; + const maxCommissionAboveGlobal = maxCommission > globalMaxCommission; + + // Change rate is invalid if updated is not more restrictive than current. + const invalidMaxIncrease = + changeRateUpdated && changeRate.maxIncrease > initialChangeRate.maxIncrease; + + const invalidMinDelay = + changeRateUpdated && changeRate.minDelay < initialChangeRate.minDelay; + + const invalidChangeRate = invalidMaxIncrease || invalidMinDelay; + + // Check there are txs to submit. + const txsToSubmit = + commissionUpdated || + (maxCommissionUpdated && maxCommissionEnabled) || + (changeRateUpdated && changeRateEnabled); + + useEffect(() => { + setValid( + isOwner() && + !invalidCurrentCommission && + !commissionAboveGlobal && + !invalidMaxCommission && + !maxCommissionAboveGlobal && + !invalidChangeRate && + !noChange && + txsToSubmit + ); + }, [ + isOwner(), + invalidCurrentCommission, + invalidMaxCommission, + commissionAboveGlobal, + maxCommissionAboveGlobal, + invalidChangeRate, + bondedPool, + noChange, + txsToSubmit, + ]); + + useEffect(() => { + resetToDefault(); + }, [bondedPool]); + + // Trigger modal resize when commission options are enabled / disabled. + useEffect(() => { + incrementCalculateHeight(); + }, [maxCommissionEnabled, changeRateEnabled]); + + // tx to submit. + const getTx = () => { + if (!valid || !api) { + return null; + } + + const txs = []; + if (commissionUpdated) { + txs.push( + api.tx.nominationPools.setCommission( + poolId, + hasCurrentCommission + ? [ + new BigNumber(commission).multipliedBy(10000000).toString(), + payee, + ] + : null + ) + ); + } + if (maxCommissionUpdated && maxCommissionEnabled) { + txs.push( + api.tx.nominationPools.setCommissionMax( + poolId, + new BigNumber(maxCommission).multipliedBy(10000000).toString() + ) + ); + } + if (changeRateUpdated && changeRateEnabled) { + txs.push( + api.tx.nominationPools.setCommissionChangeRate(poolId, { + maxIncrease: new BigNumber(changeRate.maxIncrease) + .multipliedBy(10000000) + .toString(), + minDelay: changeRate.minDelay.toString(), + }) + ); + } + + if (txs.length === 1) { + return txs[0]; + } + return newBatchCall(txs, activeAccount); + }; + + const submitExtrinsic = useSubmitExtrinsic({ + tx: getTx(), + from: activeAccount, + shouldSubmit: true, + callbackSubmit: () => { + setModalStatus('closing'); + }, + callbackInBlock: () => { + const pool = getBondedPool(poolId); + if (pool) { + updateBondedPools([ + { + ...pool, + commission: { + ...pool.commission, + current: commissionCurrent(), + max: maxCommissionUpdated + ? `${maxCommission.toFixed(2)}%` + : pool.commission?.max || null, + changeRate: changeRateUpdated + ? { + maxIncrease: `${changeRate.maxIncrease.toFixed(2)}%`, + minDelay: String(changeRate.minDelay), + } + : pool.commission?.changeRate || null, + }, + }, + ]); + } + }, + }); + + const warnings = getSignerWarnings( + activeAccount, + false, + submitExtrinsic.proxySupported + ); + + const commissionFeedback = (() => { + if (!commissionUpdated) { + return undefined; + } + if (commissionAboveMaxIncrease) { + return { + text: t('beyondMaxIncrease'), + label: 'danger', + }; + } + if (commissionAboveGlobal) { + return { + text: t('aboveGlobalMax'), + label: 'danger', + }; + } + if (commissionAboveMax) { + return { + text: t('aboveMax'), + label: 'danger', + }; + } + return { + text: t('updated'), + label: 'neutral', + }; + })(); + + const maxCommissionFeedback = (() => { + if (!maxCommissionUpdated) { + return undefined; + } + if (invalidMaxCommission) { + return { + text: t('aboveExisting'), + label: 'danger', + }; + } + if (maxCommissionAboveGlobal) { + return { + text: t('aboveGlobalMax'), + label: 'danger', + }; + } + return { + text: t('updated'), + label: 'neutral', + }; + })(); + + const maxIncreaseFeedback = (() => { + if (!maxIncreaseUpdated) { + return undefined; + } + if (invalidMaxIncrease) { + return { + text: t('aboveExisting'), + label: 'danger', + }; + } + return { + text: t('updated'), + label: 'neutral', + }; + })(); + + const minDelayFeedback = (() => { + if (!minDelayUpdated) { + return undefined; + } + if (invalidMinDelay) { + return { + text: t('belowExisting'), + label: 'danger', + }; + } + return { + text: t('updated'), + label: 'neutral', + }; + })(); + + const sliderProps = { + trackStyle: { + backgroundColor: 'var(--accent-color-primary)', + }, + railStyle: { + backgroundColor: 'var(--button-secondary-background)', + }, + handleStyle: { + backgroundColor: 'var(--background-primary)', + borderColor: 'var(--accent-color-primary)', + opacity: 1, + }, + activeDotStyle: { + backgroundColor: 'var(--background-primary)', + }, + }; + + return ( + <> + <div className="padding"> + {warnings.length > 0 ? ( + <ModalWarnings withMargin> + {warnings.map((text, i) => ( + <Warning key={`warning${i}`} text={text} /> + ))} + </ModalWarnings> + ) : null} + + <ActionItem + text={t('commissionRate')} + inlineButton={ + <ButtonHelp onClick={() => openHelp('Pool Commission Rate')} /> + } + /> + + <SliderWrapper> + <div> + <h2>{commission}% </h2> + <h5 className={commissionFeedback?.label || 'neutral'}> + {!!commissionFeedback && commissionFeedback.text} + </h5> + </div> + + <div className="slider"> + <Slider + value={commission} + step={0.1} + onChange={(val) => { + if (typeof val === 'number') { + setCommission(val); + if (val > maxCommission && maxCommissionEnabled) { + setMaxCommission(Math.min(initialMaxCommission, val)); + } + } + }} + {...sliderProps} + /> + </div> + </SliderWrapper> + + <AccountInput + defaultLabel={t('inputPayeeAccount')} + successLabel={t('payeeAdded')} + locked={payee !== null} + successCallback={async (input) => { + setPayee(input); + }} + resetCallback={() => { + setPayee(null); + }} + disallowAlreadyImported={false} + initialValue={payee} + inactive={commission === 0} + border={payee === null} + /> + + <ActionItem + style={{ + marginTop: '2rem', + borderBottomWidth: maxCommissionEnabled ? '1px' : 0, + }} + text={t('maxCommission')} + toggled={maxCommissionEnabled} + onToggle={(val) => setMaxCommissionEnabled(val)} + disabled={!!maxCommissionSet} + inlineButton={ + <ButtonHelp onClick={() => openHelp('Pool Max Commission')} /> + } + /> + + {maxCommissionEnabled && ( + <SliderWrapper> + <div> + <h2>{maxCommission}% </h2> + <h5 className={maxCommissionFeedback?.label || 'neutral'}> + {!!maxCommissionFeedback && maxCommissionFeedback.text} + </h5> + </div> + + <div className="slider"> + <Slider + value={maxCommission} + step={0.1} + onChange={(val) => { + if (typeof val === 'number') { + setMaxCommission(val); + if (val < commission) { + setCommission(val); + } + } + }} + {...sliderProps} + /> + </div> + </SliderWrapper> + )} + + <ActionItem + style={{ + marginTop: '2rem', + borderBottomWidth: changeRateEnabled ? '1px' : 0, + }} + text={t('changeRate')} + toggled={changeRateEnabled} + onToggle={(val) => setChangeRateEnabled(val)} + disabled={!!changeRateSet} + inlineButton={ + <ButtonHelp + onClick={() => openHelp('Pool Commission Change Rate')} + /> + } + /> + + {changeRateEnabled && ( + <SliderWrapper> + <div> + <h2>{changeRate.maxIncrease}% </h2> + <h5 className={maxIncreaseFeedback?.label || 'neutral'}> + {!!maxIncreaseFeedback && maxIncreaseFeedback.text} + </h5> + </div> + + <div className="slider"> + <Slider + value={changeRate.maxIncrease} + step={0.1} + onChange={(val) => { + if (typeof val === 'number') { + setChangeRate({ + ...changeRate, + maxIncrease: val, + }); + } + }} + {...sliderProps} + /> + </div> + + <h5 style={{ marginTop: '1rem' }}> + {t('minDelayBetweenUpdates')} + {minDelayFeedback && ( + <span className={minDelayFeedback?.label || 'neutral'}> + {minDelayFeedback.text} + </span> + )} + </h5> + <div className="changeRate"> + <MinDelayInput + initial={changeRateInput.years} + field="years" + label={t('years')} + handleChange={handleChangeRateInput} + /> + <MinDelayInput + initial={changeRateInput.months} + field="months" + label={t('months')} + handleChange={handleChangeRateInput} + /> + <MinDelayInput + initial={changeRateInput.days} + field="days" + label={t('days')} + handleChange={handleChangeRateInput} + /> + <MinDelayInput + initial={changeRateInput.hours} + field="hours" + label={t('hours')} + handleChange={handleChangeRateInput} + /> + <MinDelayInput + initial={changeRateInput.minutes} + field="minutes" + label={t('minutes')} + handleChange={handleChangeRateInput} + /> + </div> + <p> + {t('thisMinimumDelay', { + count: changeRate.minDelay, + })} + </p> + </SliderWrapper> + )} + </div> + <SubmitTx + valid={valid} + buttons={[ + <ButtonSubmitInvert + key="button_back" + text={t('back')} + iconLeft={faChevronLeft} + iconTransform="shrink-1" + onClick={() => { + setSection(0); + resetToDefault(); + }} + />, + ]} + {...submitExtrinsic} + /> + </> + ); +}; diff --git a/src/modals/ManagePool/Forms/LeavePool.tsx b/src/modals/ManagePool/Forms/LeavePool.tsx new file mode 100644 index 0000000000..f9e79b5c71 --- /dev/null +++ b/src/modals/ManagePool/Forms/LeavePool.tsx @@ -0,0 +1,152 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { faChevronLeft } from '@fortawesome/free-solid-svg-icons'; +import { + ActionItem, + ButtonSubmitInvert, + ModalWarnings, +} from '@polkadot-cloud/react'; +import { + greaterThanZero, + planckToUnit, + unitToPlanck, +} from '@polkadot-cloud/utils'; +import BigNumber from 'bignumber.js'; +import { getUnixTime } from 'date-fns'; +import { useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useApi } from 'contexts/Api'; +import { useActivePools } from 'contexts/Pools/ActivePools'; +import { useTransferOptions } from 'contexts/TransferOptions'; +import { Warning } from 'library/Form/Warning'; +import { useErasToTimeLeft } from 'library/Hooks/useErasToTimeLeft'; +import { useSignerWarnings } from 'library/Hooks/useSignerWarnings'; +import { useSubmitExtrinsic } from 'library/Hooks/useSubmitExtrinsic'; +import { timeleftAsString } from 'library/Hooks/useTimeLeft/utils'; +import { SubmitTx } from 'library/SubmitTx'; +import { StaticNote } from 'modals/Utils/StaticNote'; +import { useOverlay } from '@polkadot-cloud/react/hooks'; +import { useNetwork } from 'contexts/Network'; +import { useActiveAccounts } from 'contexts/ActiveAccounts'; + +export const LeavePool = ({ setSection }: any) => { + const { t } = useTranslation('modals'); + const { api, consts } = useApi(); + const { + networkData: { units, unit }, + } = useNetwork(); + const { activeAccount } = useActiveAccounts(); + const { setModalStatus, setModalResize } = useOverlay().modal; + const { getTransferOptions } = useTransferOptions(); + const { selectedActivePool } = useActivePools(); + const { erasToSeconds } = useErasToTimeLeft(); + const { getSignerWarnings } = useSignerWarnings(); + + const allTransferOptions = getTransferOptions(activeAccount); + const { active: activeBn } = allTransferOptions.pool; + const { bondDuration } = consts; + + const bondDurationFormatted = timeleftAsString( + t, + getUnixTime(new Date()) + 1, + erasToSeconds(bondDuration), + true + ); + + let { pendingRewards } = selectedActivePool || {}; + pendingRewards = pendingRewards ?? new BigNumber(0); + pendingRewards = planckToUnit(pendingRewards, units); + + // convert BigNumber values to number + const freeToUnbond = planckToUnit(activeBn, units); + + // local bond value + const [bond, setBond] = useState<{ bond: string }>({ + bond: freeToUnbond.toString(), + }); + + // bond valid + const [bondValid, setBondValid] = useState(false); + + // unbond all validation + const isValid = (() => greaterThanZero(freeToUnbond))(); + + // update bond value on task change + useEffect(() => { + setBond({ bond: freeToUnbond.toString() }); + setBondValid(isValid); + }, [freeToUnbond.toString(), isValid]); + + // modal resize on form update + useEffect(() => setModalResize(), [bond]); + + // tx to submit + const getTx = () => { + let tx = null; + if (!api || !activeAccount) { + return tx; + } + + const bondToSubmit = unitToPlanck(!bondValid ? '0' : bond.bond, units); + const bondAsString = bondToSubmit.isNaN() ? '0' : bondToSubmit.toString(); + tx = api.tx.nominationPools.unbond(activeAccount, bondAsString); + return tx; + }; + + const submitExtrinsic = useSubmitExtrinsic({ + tx: getTx(), + from: activeAccount, + shouldSubmit: bondValid, + callbackSubmit: () => { + setModalStatus('closing'); + }, + callbackInBlock: () => {}, + }); + + const warnings = getSignerWarnings( + activeAccount, + false, + submitExtrinsic.proxySupported + ); + + if (greaterThanZero(pendingRewards)) { + warnings.push( + `${t('unbondingWithdraw')} ${pendingRewards.toString()} ${unit}.` + ); + } + + return ( + <> + <div className="padding"> + {warnings.length > 0 ? ( + <ModalWarnings withMargin> + {warnings.map((text, i) => ( + <Warning key={`warning${i}`} text={text} /> + ))} + </ModalWarnings> + ) : null} + <ActionItem text={`${t('unbond')} ${freeToUnbond} ${unit}`} /> + <StaticNote + value={bondDurationFormatted} + tKey="onceUnbonding" + valueKey="bondDurationFormatted" + deps={[bondDuration]} + /> + </div> + <SubmitTx + valid={bondValid} + buttons={[ + <ButtonSubmitInvert + key="button_back" + text={t('back')} + iconLeft={faChevronLeft} + iconTransform="shrink-1" + onClick={() => setSection(0)} + />, + ]} + {...submitExtrinsic} + /> + </> + ); +}; diff --git a/src/modals/ManagePool/Forms/SetClaimPermission.tsx b/src/modals/ManagePool/Forms/SetClaimPermission.tsx new file mode 100644 index 0000000000..0bf3035701 --- /dev/null +++ b/src/modals/ManagePool/Forms/SetClaimPermission.tsx @@ -0,0 +1,109 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { faChevronLeft } from '@fortawesome/free-solid-svg-icons'; +import { ButtonSubmitInvert, ModalWarnings } from '@polkadot-cloud/react'; +import { useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useApi } from 'contexts/Api'; +import { useActivePools } from 'contexts/Pools/ActivePools'; +import { usePoolMemberships } from 'contexts/Pools/PoolMemberships'; +import type { ClaimPermission } from 'contexts/Pools/types'; +import { ClaimPermissionInput } from 'library/Form/ClaimPermissionInput'; +import { Warning } from 'library/Form/Warning'; +import { useSignerWarnings } from 'library/Hooks/useSignerWarnings'; +import { useSubmitExtrinsic } from 'library/Hooks/useSubmitExtrinsic'; +import { SubmitTx } from 'library/SubmitTx'; +import { useOverlay } from '@polkadot-cloud/react/hooks'; +import { useActiveAccounts } from 'contexts/ActiveAccounts'; + +export const SetClaimPermission = ({ setSection, section }: any) => { + const { t } = useTranslation('modals'); + const { api } = useApi(); + const { activeAccount } = useActiveAccounts(); + const { setModalStatus } = useOverlay().modal; + const { isOwner, isMember } = useActivePools(); + const { getSignerWarnings } = useSignerWarnings(); + const { membership } = usePoolMemberships(); + + // Valid to submit transaction. + const [valid, setValid] = useState<boolean>(false); + + // Updated claim permission value. + const [claimPermission, setClaimPermission] = useState< + ClaimPermission | undefined + >(membership?.claimPermission); + + // Determine current pool metadata and set in state. + useEffect(() => { + const current = membership?.claimPermission; + if (current) { + setClaimPermission(membership?.claimPermission); + } + }, [section, membership]); + + useEffect(() => { + setValid(isOwner() || (isMember() && claimPermission !== undefined)); + }, [isOwner(), isMember()]); + + // tx to submit. + const getTx = () => { + if (!valid || !api) { + return null; + } + return api.tx.nominationPools.setClaimPermission(claimPermission); + }; + + const submitExtrinsic = useSubmitExtrinsic({ + tx: getTx(), + from: activeAccount, + shouldSubmit: true, + callbackSubmit: () => { + setModalStatus('closing'); + }, + callbackInBlock: () => {}, + }); + + const warnings = getSignerWarnings( + activeAccount, + false, + submitExtrinsic.proxySupported + ); + + return ( + <> + <div className="padding"> + {warnings.length > 0 ? ( + <ModalWarnings withMargin> + {warnings.map((text, i) => ( + <Warning key={`warning${i}`} text={text} /> + ))} + </ModalWarnings> + ) : null} + + <ClaimPermissionInput + current={membership?.claimPermission} + permissioned={ + ![undefined, 'Permissioned'].includes(membership?.claimPermission) + } + onChange={(val: ClaimPermission | undefined) => { + setClaimPermission(val); + }} + /> + </div> + <SubmitTx + valid={valid && claimPermission !== membership?.claimPermission} + buttons={[ + <ButtonSubmitInvert + key="button_back" + text={t('back')} + iconLeft={faChevronLeft} + iconTransform="shrink-1" + onClick={() => setSection(0)} + />, + ]} + {...submitExtrinsic} + /> + </> + ); +}; diff --git a/src/modals/ManagePool/Forms/SetMetadata.tsx b/src/modals/ManagePool/Forms/SetMetadata.tsx new file mode 100644 index 0000000000..1ed361ca57 --- /dev/null +++ b/src/modals/ManagePool/Forms/SetMetadata.tsx @@ -0,0 +1,118 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { faChevronLeft } from '@fortawesome/free-solid-svg-icons'; +import { u8aToString, u8aUnwrapBytes } from '@polkadot/util'; +import { ButtonSubmitInvert, ModalWarnings } from '@polkadot-cloud/react'; +import React, { useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useApi } from 'contexts/Api'; +import { useActivePools } from 'contexts/Pools/ActivePools'; +import { useBondedPools } from 'contexts/Pools/BondedPools'; +import { Warning } from 'library/Form/Warning'; +import { useSignerWarnings } from 'library/Hooks/useSignerWarnings'; +import { useSubmitExtrinsic } from 'library/Hooks/useSubmitExtrinsic'; +import { SubmitTx } from 'library/SubmitTx'; +import { useOverlay } from '@polkadot-cloud/react/hooks'; +import { useActiveAccounts } from 'contexts/ActiveAccounts'; + +export const SetMetadata = ({ setSection, section }: any) => { + const { t } = useTranslation('modals'); + const { api } = useApi(); + const { setModalStatus } = useOverlay().modal; + const { activeAccount } = useActiveAccounts(); + const { isOwner, selectedActivePool } = useActivePools(); + const { bondedPools, meta } = useBondedPools(); + const { getSignerWarnings } = useSignerWarnings(); + + const poolId = selectedActivePool?.id; + + // Valid to submit transaction + const [valid, setValid] = useState<boolean>(false); + + // Updated metadata value + const [metadata, setMetadata] = useState<string>(''); + + // Determine current pool metadata and set in state. + useEffect(() => { + const pool = bondedPools.find( + ({ addresses }) => addresses.stash === selectedActivePool?.addresses.stash + ); + if (pool) { + const metadataBatch = meta.bonded_pools?.metadata ?? []; + const batchIndex = bondedPools.indexOf(pool); + setMetadata(u8aToString(u8aUnwrapBytes(metadataBatch[batchIndex]))); + } + }, [section]); + + useEffect(() => { + setValid(isOwner()); + }, [isOwner()]); + + // tx to submit + const getTx = () => { + if (!valid || !api) { + return null; + } + return api.tx.nominationPools.setMetadata(poolId, metadata); + }; + + const submitExtrinsic = useSubmitExtrinsic({ + tx: getTx(), + from: activeAccount, + shouldSubmit: true, + callbackSubmit: () => { + setModalStatus('closing'); + }, + callbackInBlock: () => {}, + }); + + const handleMetadataChange = (e: React.FormEvent<HTMLInputElement>) => { + setMetadata(e.currentTarget.value); + setValid(true); + }; + + const warnings = getSignerWarnings( + activeAccount, + false, + submitExtrinsic.proxySupported + ); + + return ( + <> + <div className="padding"> + {warnings.length > 0 ? ( + <ModalWarnings withMargin> + {warnings.map((text, i) => ( + <Warning key={`warning${i}`} text={text} /> + ))} + </ModalWarnings> + ) : null} + <input + className="textbox" + style={{ width: '100%' }} + placeholder={t('poolName')} + type="text" + onChange={(e: React.FormEvent<HTMLInputElement>) => + handleMetadataChange(e) + } + value={metadata ?? ''} + /> + <p>{t('storedOnChain')}</p> + </div> + <SubmitTx + valid={valid} + buttons={[ + <ButtonSubmitInvert + key="button_back" + text={t('back')} + iconLeft={faChevronLeft} + iconTransform="shrink-1" + onClick={() => setSection(0)} + />, + ]} + {...submitExtrinsic} + /> + </> + ); +}; diff --git a/src/modals/ManagePool/Forms/SetState.tsx b/src/modals/ManagePool/Forms/SetState.tsx new file mode 100644 index 0000000000..2262eb9f41 --- /dev/null +++ b/src/modals/ManagePool/Forms/SetState.tsx @@ -0,0 +1,158 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { faChevronLeft } from '@fortawesome/free-solid-svg-icons'; +import { + ActionItem, + ButtonSubmitInvert, + ModalWarnings, +} from '@polkadot-cloud/react'; +import { useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useApi } from 'contexts/Api'; +import { useActivePools } from 'contexts/Pools/ActivePools'; +import { useBondedPools } from 'contexts/Pools/BondedPools'; +import { Warning } from 'library/Form/Warning'; +import { useSignerWarnings } from 'library/Hooks/useSignerWarnings'; +import { useSubmitExtrinsic } from 'library/Hooks/useSubmitExtrinsic'; +import { SubmitTx } from 'library/SubmitTx'; +import { useOverlay } from '@polkadot-cloud/react/hooks'; +import { useActiveAccounts } from 'contexts/ActiveAccounts'; + +export const SetState = ({ setSection, task }: any) => { + const { t } = useTranslation('modals'); + const { api } = useApi(); + const { setModalStatus } = useOverlay().modal; + const { activeAccount } = useActiveAccounts(); + const { isOwner, isBouncer, selectedActivePool } = useActivePools(); + const { updateBondedPools, getBondedPool } = useBondedPools(); + const { getSignerWarnings } = useSignerWarnings(); + + const poolId = selectedActivePool?.id; + + // valid to submit transaction + const [valid, setValid] = useState<boolean>(false); + + // ensure account has relevant roles for task + const canToggle = + (isOwner() || isBouncer()) && + ['destroy_pool', 'unlock_pool', 'lock_pool'].includes(task); + + useEffect(() => { + setValid(canToggle); + }, [canToggle]); + + const content = (() => { + let title; + let message; + switch (task) { + case 'destroy_pool': + title = <ActionItem text={t('setToDestroying')} />; + message = <p>{t('setToDestroyingSubtitle')}</p>; + break; + case 'unlock_pool': + title = <ActionItem text={t('unlockPool')} />; + message = <p>{t('unlockPoolSubtitle')}</p>; + break; + default: + title = <ActionItem text={t('lockPool')} />; + message = <p>{t('lockPoolSubtitle')}</p>; + } + return { title, message }; + })(); + + const poolStateFromTask = (s: string) => { + switch (s) { + case 'destroy_pool': + return 'Destroying'; + case 'lock_pool': + return 'Blocked'; + default: + return 'Open'; + } + }; + + // tx to submit + const getTx = () => { + if (!valid || !api) { + return null; + } + + let tx; + switch (task) { + case 'destroy_pool': + tx = api.tx.nominationPools.setState(poolId, 'Destroying'); + break; + case 'unlock_pool': + tx = api.tx.nominationPools.setState(poolId, 'Open'); + break; + case 'lock_pool': + tx = api.tx.nominationPools.setState(poolId, 'Blocked'); + break; + default: + tx = null; + } + return tx; + }; + + const submitExtrinsic = useSubmitExtrinsic({ + tx: getTx(), + from: activeAccount, + shouldSubmit: true, + callbackSubmit: () => { + setModalStatus('closing'); + }, + callbackInBlock: () => { + // reflect updated state in `bondedPools` list. + if ( + ['destroy_pool', 'unlock_pool', 'lock_pool'].includes(task) && + poolId + ) { + const pool = getBondedPool(poolId); + if (pool) { + updateBondedPools([ + { + ...pool, + state: poolStateFromTask(task), + }, + ]); + } + } + }, + }); + + const warnings = getSignerWarnings( + activeAccount, + false, + submitExtrinsic.proxySupported + ); + + return ( + <> + <div className="padding"> + {warnings.length > 0 ? ( + <ModalWarnings withMargin> + {warnings.map((text, i) => ( + <Warning key={`warning${i}`} text={text} /> + ))} + </ModalWarnings> + ) : null} + {content.title} + {content.message} + </div> + <SubmitTx + valid={valid} + buttons={[ + <ButtonSubmitInvert + key="button_back" + text={t('back')} + iconLeft={faChevronLeft} + iconTransform="shrink-1" + onClick={() => setSection(0)} + />, + ]} + {...submitExtrinsic} + /> + </> + ); +}; diff --git a/src/modals/ManagePool/Forms/index.tsx b/src/modals/ManagePool/Forms/index.tsx new file mode 100644 index 0000000000..cc790bac70 --- /dev/null +++ b/src/modals/ManagePool/Forms/index.tsx @@ -0,0 +1,41 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { forwardRef } from 'react'; +import { ContentWrapper } from '../Wrappers'; +import { ClaimCommission } from './ClaimCommission'; +import { Commission } from './Commission'; +import { LeavePool } from './LeavePool'; +import { SetClaimPermission } from './SetClaimPermission'; +import { SetMetadata } from './SetMetadata'; +import { SetState } from './SetState'; + +export const Forms = forwardRef( + ({ setSection, task, section, incrementCalculateHeight }: any, ref: any) => { + return ( + <> + <ContentWrapper> + <div className="items" ref={ref}> + {task === 'set_pool_metadata' ? ( + <SetMetadata setSection={setSection} section={section} /> + ) : task === 'manage_commission' ? ( + <Commission + setSection={setSection} + section={section} + incrementCalculateHeight={incrementCalculateHeight} + /> + ) : task === 'set_claim_permission' ? ( + <SetClaimPermission setSection={setSection} section={section} /> + ) : task === 'leave_pool' ? ( + <LeavePool setSection={setSection} /> + ) : task === 'claim_commission' ? ( + <ClaimCommission setSection={setSection} /> + ) : ( + <SetState setSection={setSection} task={task} /> + )} + </div> + </ContentWrapper> + </> + ); + } +); diff --git a/src/modals/ManagePool/Forms/types.ts b/src/modals/ManagePool/Forms/types.ts new file mode 100644 index 0000000000..43560597b9 --- /dev/null +++ b/src/modals/ManagePool/Forms/types.ts @@ -0,0 +1,10 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +export interface ChangeRateInput { + years: number; + months: number; + days: number; + hours: number; + minutes: number; +} diff --git a/src/modals/ManagePool/Tasks.tsx b/src/modals/ManagePool/Tasks.tsx index 0607dd3aa4..4b65f14a3f 100644 --- a/src/modals/ManagePool/Tasks.tsx +++ b/src/modals/ManagePool/Tasks.tsx @@ -1,109 +1,150 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import { faChevronRight } from '@fortawesome/free-solid-svg-icons'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { ButtonOption } from '@polkadot-cloud/react'; +import { forwardRef } from 'react'; +import { useTranslation } from 'react-i18next'; import { useActivePools } from 'contexts/Pools/ActivePools'; -import { PoolState } from 'contexts/Pools/types'; +import { usePoolsConfig } from 'contexts/Pools/PoolsConfig'; +import { useTransferOptions } from 'contexts/TransferOptions'; import { Warning } from 'library/Form/Warning'; -import { forwardRef } from 'react'; +import { useActiveAccounts } from 'contexts/ActiveAccounts'; import { ContentWrapper } from './Wrappers'; -export const Tasks = forwardRef((props: any, ref: any) => { - const { setSection, setTask } = props; +export const Tasks = forwardRef(({ setSection, setTask }: any, ref: any) => { + const { t } = useTranslation('modals'); + const { activeAccount } = useActiveAccounts(); + const { selectedActivePool, isOwner, isBouncer, isMember, isDepositor } = + useActivePools(); + const { getTransferOptions } = useTransferOptions(); + const { stats } = usePoolsConfig(); + const { globalMaxCommission } = stats; + const { active } = getTransferOptions(activeAccount).pool; - const { selectedActivePool, isOwner, isStateToggler } = useActivePools(); - const poolLocked = selectedActivePool?.bondedPool?.state === PoolState.Block; - const poolDestroying = - selectedActivePool?.bondedPool?.state === PoolState.Destroy; + const poolLocked = selectedActivePool?.bondedPool?.state === 'Blocked'; + const poolDestroying = selectedActivePool?.bondedPool?.state === 'Destroying'; return ( <ContentWrapper> - {poolDestroying && ( - <Warning text="This pool is being destroyed. There are no management options available." /> - )} - - <div className="items" ref={ref}> - {isOwner() && ( - <button - type="button" - className="action-button" - disabled={poolDestroying} + <div className="padding"> + <div className="items" ref={ref} style={{ paddingBottom: '1.5rem' }}> + <div style={{ paddingBottom: '0.75rem' }}> + {poolDestroying && <Warning text={t('beingDestroyed')} />} + </div> + {isOwner() && ( + <> + {globalMaxCommission > 0 && ( + <> + <ButtonOption + onClick={() => { + setSection(1); + setTask('claim_commission'); + }} + > + <div> + <h3>{t('claimCommission')}</h3> + <p>{t('claimOutstandingCommission')}</p> + </div> + </ButtonOption> + <ButtonOption + onClick={() => { + setSection(1); + setTask('manage_commission'); + }} + > + <div> + <h3>{t('manageCommission')}</h3> + <p>{t('updatePoolCommission')}</p> + </div> + </ButtonOption> + </> + )} + </> + )} + <ButtonOption onClick={() => { setSection(1); - setTask('set_pool_metadata'); + setTask('set_claim_permission'); }} > <div> - <h3>Rename Pool</h3> - <p>Update the public name of the pool.</p> + <h3>{t('updateClaimPermission')}</h3> + <p>{t('updateWhoClaimRewards')}</p> </div> - <div> - <FontAwesomeIcon transform="shrink-2" icon={faChevronRight} /> - </div> - </button> - )} - {(isOwner() || isStateToggler()) && ( - <> - {poolLocked ? ( - <button - type="button" - className="action-button" - disabled={poolDestroying} - onClick={() => { - setSection(1); - setTask('unlock_pool'); - }} - > - <div> - <h3>Unlock Pool</h3> - <p>Allow new members to join the pool.</p> - </div> - <div> - <FontAwesomeIcon transform="shrink-2" icon={faChevronRight} /> - </div> - </button> - ) : ( - <button - type="button" - className="action-button" + </ButtonOption> + + {isOwner() && ( + <ButtonOption + disabled={poolDestroying} + onClick={() => { + setSection(1); + setTask('set_pool_metadata'); + }} + > + <div> + <h3>{t('renamePool')}</h3> + <p>{t('updateName')}</p> + </div> + </ButtonOption> + )} + {(isOwner() || isBouncer()) && ( + <> + {poolLocked ? ( + <ButtonOption + disabled={poolDestroying} + onClick={() => { + setSection(1); + setTask('unlock_pool'); + }} + > + <div> + <h3>{t('unlockPool')}</h3> + <p>{t('allowToJoin')}</p> + </div> + </ButtonOption> + ) : ( + <ButtonOption + disabled={poolDestroying} + onClick={() => { + setSection(1); + setTask('lock_pool'); + }} + > + <div> + <h3>{t('lockPool')}</h3> + <p>{t('stopJoiningPool')}</p> + </div> + </ButtonOption> + )} + <ButtonOption disabled={poolDestroying} onClick={() => { setSection(1); - setTask('lock_pool'); + setTask('destroy_pool'); }} > <div> - <h3>Lock Pool</h3> - <p>Stop new members from joining the pool.</p> - </div> - <div> - <FontAwesomeIcon transform="shrink-2" icon={faChevronRight} /> + <h3>{t('destroyPool')}</h3> + <p>{t('changeToDestroy')}</p> </div> - </button> - )} - <button - type="button" - className="action-button" - disabled={poolDestroying} + </ButtonOption> + </> + )} + {isMember() && !isDepositor() && active?.isGreaterThan(0) && ( + <ButtonOption onClick={() => { setSection(1); - setTask('destroy_pool'); + setTask('leave_pool'); }} > <div> - <h3>Destroy Pool</h3> - <p>Change pool to destroying state.</p> + <h3>{t('leavePool')}</h3> + <p>{t('unbondFundsLeavePool')}</p> </div> - <div> - <FontAwesomeIcon transform="shrink-2" icon={faChevronRight} /> - </div> - </button> - </> - )} + </ButtonOption> + )} + </div> </div> </ContentWrapper> ); }); - -export default Tasks; diff --git a/src/modals/ManagePool/Wrappers.ts b/src/modals/ManagePool/Wrappers.ts index 804defd3c2..2683239755 100644 --- a/src/modals/ManagePool/Wrappers.ts +++ b/src/modals/ManagePool/Wrappers.ts @@ -1,30 +1,7 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import { motion } from 'framer-motion'; import styled from 'styled-components'; -import { backgroundToggle, buttonPrimaryBackground, textPrimary } from 'theme'; - -export const Wrapper = styled.div` - display: flex; - flex-flow: column wrap; - align-items: flex-start; - justify-content: flex-start; - padding: 0; -`; - -export const FixedContentWrapper = styled.div` - padding-top: 1rem; - width: 100%; -`; - -export const CardsWrapper = styled(motion.div)` - width: 200%; - display: flex; - flex-flow: row nowrap; - overflow: hidden; - position: relative; -`; export const ContentWrapper = styled.div` border-radius: 1rem; @@ -33,12 +10,24 @@ export const ContentWrapper = styled.div` flex-basis: 50%; min-width: 50%; height: auto; - padding: 0 1rem 1rem 1rem; flex-grow: 1; + .padding { + padding: 0 1rem 1rem 1rem; + + h2 { + margin-bottom: 0.5rem; + } + + input { + font-family: InterBold, sans-serif; + margin-top: 0.5rem; + } + } + .items { position: relative; - padding: 0.5rem 0 1.5rem 0; + padding: 0.5rem 0 0rem 0; border-bottom: none; width: auto; border-radius: 0.75rem; @@ -50,56 +39,98 @@ export const ContentWrapper = styled.div` h4 { margin: 0.2rem 0; } + + .arrow { + color: var(--text-color-primary); + } + } +`; + +export const SliderWrapper = styled.div` + display: flex; + flex-direction: column; + padding: 0 0.5rem 0 0.5rem; + + h5 { + font-family: InterSemiBold, sans-serif; + margin: 0; + margin-left: 0.75rem; + + > span { + margin-left: 0.75rem; + } + &.neutral, + .neutral { + color: var(--accent-color-primary); + opacity: 0.8; + } + &.danger, + .danger { + color: var(--status-danger-color); + } + &.success, + .success { + color: var(--status-success-color); + } + } + + > div:first-child { + display: flex; + align-items: center; + margin: 1.25rem 0 0.5rem 0; + h2 { - margin: 0.75rem 0; + margin: 0; + font-family: InterBold, sans-serif; } + } - .action-button { - background: ${buttonPrimaryBackground}; - padding: 1rem; - cursor: pointer; - margin-bottom: 1rem; - border-radius: 0.75rem; - display: flex; - flex-flow: row wrap; - justify-content: flex-start; - align-items: center; - transition: all 0.15s; - width: 100%; - - &:last-child { - margin-bottom: 0; - } + .changeRate { + display: flex; + flex-wrap: wrap; + margin: 0.25rem 0; + } - h3, - p { - text-align: left; - margin: 0; - } - h3 { - margin-bottom: 0.5rem; - } - > *:last-child { - flex: 1; - display: flex; - flex-flow: row wrap; - justify-content: flex-end; - } - &:hover { - background: ${backgroundToggle}; - } - .icon { - margin-right: 0.5rem; - } - p { - color: ${textPrimary}; - font-size: 1rem; + > div { + display: flex; + align-items: center; + + > .slider { + flex-grow: 1; + + &.no-value { + padding-left: 0; } - &:disabled { - opacity: 0.5; - cursor: default; + .rc-slider-handle-dragging { + box-shadow: 0 0 0 5px var(--accent-color-transparent) !important; } } } + + .stats { + display: flex; + flex-direction: column; + align-items: flex-start; + margin-top: 1rem; + h2 { + border-bottom: 1px solid var(--border-primary-color); + font-family: InterBold, sans-serif; + margin-top: 0rem; + padding-bottom: 1rem; + } + } + + .done { + display: flex; + justify-content: flex-end; + margin-top: 1rem; + } + + .confirm { + display: flex; + flex-flow: column wrap; + align-items: flex-end; + margin-top: 2.5rem; + } `; diff --git a/src/modals/ManagePool/index.tsx b/src/modals/ManagePool/index.tsx index c2eb8068e7..cc7ee659eb 100644 --- a/src/modals/ManagePool/index.tsx +++ b/src/modals/ManagePool/index.tsx @@ -1,44 +1,68 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import { faCog } from '@fortawesome/free-solid-svg-icons'; -import { useModal } from 'contexts/Modal'; -import { Title } from 'library/Modal/Title'; +import { + ModalFixedTitle, + ModalMotionTwoSection, + ModalSection, +} from '@polkadot-cloud/react'; import { useEffect, useRef, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useActivePools } from 'contexts/Pools/ActivePools'; +import { Title } from 'library/Modal/Title'; +import { useTxMeta } from 'contexts/TxMeta'; +import { useOverlay } from '@polkadot-cloud/react/hooks'; import { Forms } from './Forms'; import { Tasks } from './Tasks'; -import { CardsWrapper, FixedContentWrapper, Wrapper } from './Wrappers'; export const ManagePool = () => { - const { setModalHeight } = useModal(); + const { t } = useTranslation('modals'); + const { notEnoughFunds } = useTxMeta(); + const { setModalHeight } = useOverlay().modal; + const { isOwner, selectedActivePool } = useActivePools(); + // modal task - const [task, setTask] = useState(null); + const [task, setTask] = useState<string>(); // active modal section - const [section, setSection] = useState(0); + const [section, setSection] = useState<number>(0); + + // counter to trigger modal height calculation + const [calculateHeight, setCalculateHeight] = useState<number>(0); + const incrementCalculateHeight = () => + setCalculateHeight(calculateHeight + 1); // refs for wrappers const headerRef = useRef<HTMLDivElement>(null); const tasksRef = useRef<HTMLDivElement>(null); const formsRef = useRef<HTMLDivElement>(null); - // resize modal on state change + // Resize modal on state change. useEffect(() => { - let _height = headerRef.current?.clientHeight ?? 0; + let height = headerRef.current?.clientHeight || 0; if (section === 0) { - _height += tasksRef.current?.clientHeight ?? 0; + height += tasksRef.current?.clientHeight || 0; } else { - _height += formsRef.current?.clientHeight ?? 0; + height += formsRef.current?.clientHeight || 0; } - setModalHeight(_height); - }, [section, task]); + setModalHeight(height); + }, [ + section, + task, + notEnoughFunds, + calculateHeight, + selectedActivePool?.bondedPool?.state, + ]); return ( - <Wrapper> - <FixedContentWrapper ref={headerRef}> - <Title title="Manage Pool" icon={faCog} fixed /> - </FixedContentWrapper> - <CardsWrapper + <ModalSection type="carousel"> + <ModalFixedTitle ref={headerRef}> + <Title + title={`${t('managePool')}${!isOwner() ? ` Membership` : ``}`} + fixed + /> + </ModalFixedTitle> + <ModalMotionTwoSection animate={section === 0 ? 'home' : 'next'} transition={{ duration: 0.5, @@ -60,8 +84,9 @@ export const ManagePool = () => { task={task} section={section} ref={formsRef} + incrementCalculateHeight={incrementCalculateHeight} /> - </CardsWrapper> - </Wrapper> + </ModalMotionTwoSection> + </ModalSection> ); }; diff --git a/src/modals/Networks/ProvidersPrompt.tsx b/src/modals/Networks/ProvidersPrompt.tsx new file mode 100644 index 0000000000..d2f05b3292 --- /dev/null +++ b/src/modals/Networks/ProvidersPrompt.tsx @@ -0,0 +1,52 @@ +import { useApi } from 'contexts/Api'; +import { Title } from 'library/Prompt/Title'; +import { useTranslation } from 'react-i18next'; +import { PromptSelectItem } from 'library/Prompt/Wrappers'; +import { useNetwork } from 'contexts/Network'; +import { NetworkList } from 'config/networks'; +import { usePrompt } from 'contexts/Prompt'; +import { capitalizeFirstLetter } from '@polkadot-cloud/utils'; + +export const ProvidersPrompt = () => { + const { t } = useTranslation(); + const { network } = useNetwork(); + const { closePrompt } = usePrompt(); + const { rpcEndpoint, setRpcEndpoint } = useApi(); + + const rpcProviders = NetworkList[network].endpoints.rpcEndpoints; + return ( + <> + <Title + title={t('rpcProviders', { ns: 'modals' })} + closeText={t('cancel', { ns: 'modals' })} + /> + <div className="padded"> + <h4 className="subheading"> + {t('selectRpcProvider', { + ns: 'modals', + network: capitalizeFirstLetter(network), + })} + </h4> + {Object.entries(rpcProviders)?.map(([key, url], i) => { + const isDisabled = rpcEndpoint === key; + + return ( + <PromptSelectItem + key={`favorite_${i}`} + className={isDisabled ? 'inactive' : undefined} + onClick={() => { + closePrompt(); + setRpcEndpoint(key); + }} + > + <h3> + {key} {isDisabled && ` (${t('selected', { ns: 'modals' })})`} + </h3> + <h4>{url}</h4> + </PromptSelectItem> + ); + })} + </div> + </> + ); +}; diff --git a/src/modals/Networks/Wrapper.ts b/src/modals/Networks/Wrapper.ts index 36f5a8fef7..0c3826700b 100644 --- a/src/modals/Networks/Wrapper.ts +++ b/src/modals/Networks/Wrapper.ts @@ -1,28 +1,19 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only +import { SectionFullWidthThreshold } from 'consts'; import styled from 'styled-components'; -import { - backgroundToggle, - borderPrimary, - buttonPrimaryBackground, - successTransparent, - textPrimary, - textSecondary, - textSuccess, -} from 'theme'; -import { NetworkButtonProps } from './types'; export const Wrapper = styled.div` display: flex; flex-flow: column wrap; align-items: center; - justify-content: flex-start; padding: 1rem; h2 { + color: var(--text-color-primary); margin-top: 0.5rem; - color: ${textPrimary}; + margin-bottom: 1rem; } `; @@ -30,11 +21,11 @@ export const ContentWrapper = styled.div` width: 100%; > h4 { - color: ${textSecondary}; + border-bottom: 1px solid var(--border-primary-color); + color: var(--text-color-secondary); margin: 0.75rem 0; padding-bottom: 0.5rem; width: 100%; - border-bottom: 1px solid ${borderPrimary}; } .items { @@ -57,20 +48,19 @@ export const ContentWrapper = styled.div` } `; -export const NetworkButton = styled.button<NetworkButtonProps>` - background: ${buttonPrimaryBackground}; +export const NetworkButton = styled.button<{ $connected: boolean }>` + background: var(--button-primary-background); + border: 1px solid var(--status-success-color-transparent); padding: 1rem; cursor: pointer; margin-bottom: 1rem; border-radius: 0.75rem; display: inline-flex; flex-flow: row wrap; - justify-content: flex-start; align-items: center; width: 100%; - border: 1px solid ${successTransparent}; ${(props) => - props.connected !== true && + props.$connected !== true && ` border: 1px solid rgba(0,0,0,0); `} @@ -80,13 +70,13 @@ export const NetworkButton = styled.button<NetworkButtonProps>` } h3 { + font-family: InterSemiBold, sans-serif; margin: 0 0.5rem; } h4 { - margin: 0; &.selected { - color: ${textSuccess}; + color: var(--status-success-color); margin-left: 0.75rem; } } @@ -98,46 +88,45 @@ export const NetworkButton = styled.button<NetworkButtonProps>` justify-content: flex-end; } &:hover { - background: ${backgroundToggle}; + background: var(--button-hover-background); } .icon { margin-right: 0.5rem; } svg { - color: ${textSecondary}; - fill: ${textSecondary}; + color: var(--text-color-secondary); + fill: var(--text-color-secondary); } p { - color: ${textPrimary}; + color: var(--text-color-primary); font-size: 1rem; } &:disabled { cursor: default; &:hover { - background: ${buttonPrimaryBackground}; + background: var(--button-primary-background); } } `; export const BraveWarning = styled.div` + border: 1px solid var(--border-primary-color); display: flex; - border: 1px solid ${borderPrimary}; border-radius: 0.75rem; padding: 1rem; .brave-text { + color: var(--text-color-primary); width: 90%; padding-left: 1rem; - color: ${textPrimary}; font-size: 1.2rem; align-self: center; .learn-more { - color: ${textSecondary}; - font-weight: bold; - text-decoration: underline ${borderPrimary}; + color: var(--text-color-secondary); + text-decoration: underline var(--border-primary-color); } } `; @@ -145,40 +134,58 @@ export const BraveWarning = styled.div` export const ConnectionsWrapper = styled.div` display: flex; flex-flow: row wrap; - align-items: center; + align-items: flex-start; margin-top: 1rem; margin-bottom: 1.5rem; + + > div { + flex-basis: 50%; + display: flex; + flex-direction: column; + align-items: flex-start; + + &:first-child { + padding-right: 1rem; + } + + @media (max-width: ${SectionFullWidthThreshold - 400}px) { + flex-basis: 100%; + &:first-child { + padding-right: 0; + } + } + } `; -export const ConnectionButton = styled.button<NetworkButtonProps>` - background: ${buttonPrimaryBackground}; +export const ConnectionButton = styled.button<{ $connected: boolean }>` + background: var(--button-primary-background); + border: 1px solid var(--status-success-color-transparent); position: relative; - padding: 0.75rem 0.75rem; + padding: 1rem 0.75rem; margin-bottom: 1rem; margin-right: 0.5rem; - border-radius: 0.5rem; - border: 1px solid ${successTransparent}; + border-radius: 0.75rem; ${(props) => - props.connected !== true && + props.$connected !== true && ` border: 1px solid rgba(0,0,0,0); `} display: inline-flex; flex-flow: row wrap; - justify-content: flex-start; align-items: center; + width: 100%; &:hover { - background: ${backgroundToggle}; + background: var(--button-hover-background); } > h3 { + font-family: InterSemiBold, sans-serif; margin: 0 0.75rem; } h4 { - margin: 0; &.selected { - color: ${textSuccess}; + color: var(--status-success-color); margin: 0 0.75rem 0 0; } } @@ -186,11 +193,11 @@ export const ConnectionButton = styled.button<NetworkButtonProps>` &:disabled { cursor: default; &:hover { - background: ${buttonPrimaryBackground}; + background: var(--button-primary-background); } &.off { h3 { - opacity: 0.33; + opacity: var(--opacity-disabled); } } } diff --git a/src/modals/Networks/index.tsx b/src/modals/Networks/index.tsx index d169178d26..52defcf211 100644 --- a/src/modals/Networks/index.tsx +++ b/src/modals/Networks/index.tsx @@ -1,18 +1,21 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only import { faChevronRight, faGlobe } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { NETWORKS } from 'config/networks'; +import { ButtonTertiary, ModalPadding } from '@polkadot-cloud/react'; +import { capitalizeFirstLetter } from '@polkadot-cloud/utils'; +import { useEffect } from 'react'; +import { Trans, useTranslation } from 'react-i18next'; +import { NetworkList } from 'config/networks'; import { useApi } from 'contexts/Api'; -import { useModal } from 'contexts/Modal'; -import { useTooltip } from 'contexts/Tooltip'; -import { TooltipPosition, TooltipTrigger } from 'library/ListItem/Wrappers'; import { Title } from 'library/Modal/Title'; -import { useEffect, useRef, useState } from 'react'; -import { NetworkName } from 'types'; -import { ReactComponent as BraveIconSVG } from '../../img/brave-logo.svg'; -import { PaddingWrapper } from '../Wrappers'; +import type { NetworkName } from 'types'; +import { useOverlay } from '@polkadot-cloud/react/hooks'; +import { useNetwork } from 'contexts/Network'; +import { useUi } from 'contexts/UI'; +import { usePrompt } from 'contexts/Prompt'; +import BraveIconSVG from '../../img/brave-logo.svg?react'; import { BraveWarning, ConnectionButton, @@ -20,132 +23,129 @@ import { ContentWrapper, NetworkButton, } from './Wrapper'; +import { ProvidersPrompt } from './ProvidersPrompt'; export const Networks = () => { - const [braveBrowser, setBraveBrowser] = useState<boolean>(false); - const { switchNetwork, network, isLightClient } = useApi(); - const { setStatus } = useModal(); - const networkKey: string = network.name.toLowerCase(); - const { setTooltipPosition, setTooltipMeta, open } = useTooltip(); + const { t } = useTranslation('modals'); + const { isBraveBrowser } = useUi(); + const { openPromptWith } = usePrompt(); + const { network, switchNetwork } = useNetwork(); + const { setModalStatus, setModalResize } = useOverlay().modal; + const { isLightClient, setIsLightClient, rpcEndpoint } = useApi(); + const networkKey = network; - useEffect(() => { - // @ts-ignore - window.navigator?.brave?.isBrave().then(async (isBrave: boolean) => { - setBraveBrowser(isBrave); - }); - }); - - const posRef = useRef(null); - - const tooltipText = 'Undergoing Maintenance'; - - const toggleTooltip = () => { - if (!open) { - setTooltipMeta(tooltipText); - setTooltipPosition(posRef); - } - }; + // Likely never going to happen; here just to be safe. + useEffect(() => setModalResize(), [isBraveBrowser]); return ( <> - <Title title="Networks" icon={faGlobe} /> - <PaddingWrapper> + <Title title={t('networks')} icon={faGlobe} /> + <ModalPadding> <ContentWrapper> - <h4>Select Network</h4> + <h4>{t('selectNetwork')}</h4> <div className="items"> - {Object.entries(NETWORKS).map(([key, item]: any, index: number) => { - const Svg = item.brand.inline.svg; - const rpcDisabled = networkKey === key; + {Object.entries(NetworkList).map( + ([key, item]: any, index: number) => { + const Svg = item.brand.inline.svg; + const rpcDisabled = networkKey === key; - return ( - <NetworkButton - connected={networkKey === key} - disabled={rpcDisabled} - key={`network_switch_${index}`} - type="button" - className="action-button" - onClick={() => { - if (networkKey !== key) { - switchNetwork(key, isLightClient); - setStatus(0); - } - }} - > - <div style={{ width: '1.75rem' }}> - <Svg - width={item.brand.inline.size} - height={item.brand.inline.size} - /> - </div> - <h3>{item.name}</h3> - {networkKey === key && <h4 className="selected">Selected</h4>} - <div> - <FontAwesomeIcon - transform="shrink-2" - icon={faChevronRight} - /> - </div> - </NetworkButton> - ); - })} + return ( + <NetworkButton + $connected={networkKey === key} + disabled={rpcDisabled} + key={`network_switch_${index}`} + type="button" + onClick={() => { + if (networkKey !== key) { + switchNetwork(key); + setModalStatus('closing'); + } + }} + > + <div style={{ width: '1.75rem' }}> + <Svg + width={item.brand.inline.size} + height={item.brand.inline.size} + /> + </div> + <h3>{capitalizeFirstLetter(item.name)}</h3> + {networkKey === key && ( + <h4 className="selected">{t('selected')}</h4> + )} + <div> + <FontAwesomeIcon + transform="shrink-2" + icon={faChevronRight} + /> + </div> + </NetworkButton> + ); + } + )} </div> - <h4>Connection Type</h4> + <h4>{t('connectionType')}</h4> <ConnectionsWrapper> - <ConnectionButton - connected={!isLightClient} - disabled={!isLightClient} - type="button" - onClick={() => { - switchNetwork(networkKey as NetworkName, false); - setStatus(0); - }} - > - <h3>RPC</h3> - {!isLightClient && <h4 className="selected">Selected</h4>} - </ConnectionButton> - <ConnectionButton - connected={isLightClient} - className="off" - disabled - type="button" - onClick={() => { - switchNetwork(networkKey as NetworkName, true); - setStatus(0); - }} - > - <TooltipTrigger - className="tooltip-trigger-element" - data-tooltip-text={tooltipText} - onMouseMove={() => toggleTooltip()} - /> - <TooltipPosition ref={posRef} style={{ left: '10px' }} /> - <h3>Light Client</h3> - {isLightClient && <h4 className="selected">Selected</h4>} - </ConnectionButton> + <div> + <ConnectionButton + $connected={!isLightClient} + disabled={!isLightClient} + type="button" + onClick={() => { + setIsLightClient(false); + switchNetwork(networkKey as NetworkName); + setModalStatus('closing'); + }} + > + <h3>RPC</h3> + {!isLightClient && ( + <h4 className="selected">{t('selected')}</h4> + )} + </ConnectionButton> + <div + style={{ + padding: '0 0.25rem', + display: 'flex', + alignItems: 'center', + }} + > + {t('provider')}:{' '} + <ButtonTertiary + text={rpcEndpoint} + onClick={() => openPromptWith(<ProvidersPrompt />)} + marginLeft + /> + </div> + </div> + <div> + <ConnectionButton + $connected={isLightClient} + className="off" + type="button" + onClick={() => { + setIsLightClient(true); + switchNetwork(networkKey as NetworkName); + setModalStatus('closing'); + }} + > + <h3>{t('lightClient')}</h3> + {isLightClient && <h4 className="selected">{t('selected')}</h4>} + </ConnectionButton> + </div> </ConnectionsWrapper> - {braveBrowser ? ( + {isBraveBrowser ? ( <BraveWarning> <BraveIconSVG /> <div className="brave-text"> - <b>To Brave users!</b> Due to a recent update ( - <i>Brave version 1.36</i>), there may appear issues while using - light clients (e.g. not connected).{' '} - <a - href="https://paritytech.github.io/substrate-connect/#troubleshooting" - target="_blank" - rel="noreferrer" - className="learn-more" - > - Learn more here. - </a> + <Trans + defaults={t('braveText')} + components={{ b: <b />, i: <i /> }} + /> </div> </BraveWarning> ) : null} </ContentWrapper> - </PaddingWrapper> + </ModalPadding> </> ); }; - -export default Networks; diff --git a/src/modals/Networks/types.ts b/src/modals/Networks/types.ts index d6c84fc44a..968cacbefd 100644 --- a/src/modals/Networks/types.ts +++ b/src/modals/Networks/types.ts @@ -1,5 +1,5 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only export interface NetworkButtonProps { connected?: boolean; diff --git a/src/modals/Nominate/index.tsx b/src/modals/Nominate/index.tsx deleted file mode 100644 index a842675c0f..0000000000 --- a/src/modals/Nominate/index.tsx +++ /dev/null @@ -1,130 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { faArrowAltCircleUp } from '@fortawesome/free-regular-svg-icons'; -import { faPlayCircle } from '@fortawesome/free-solid-svg-icons'; -import { ButtonSubmit } from '@rossbulat/polkadot-dashboard-ui'; -import { useApi } from 'contexts/Api'; -import { useBalances } from 'contexts/Balances'; -import { useConnect } from 'contexts/Connect'; -import { useModal } from 'contexts/Modal'; -import { useStaking } from 'contexts/Staking'; -import { useTxFees } from 'contexts/TxFees'; -import { EstimatedTxFee } from 'library/EstimatedTxFee'; -import { Warning } from 'library/Form/Warning'; -import { useSubmitExtrinsic } from 'library/Hooks/useSubmitExtrinsic'; -import { Title } from 'library/Modal/Title'; -import { useEffect, useState } from 'react'; -import { planckBnToUnit } from 'Utils'; -import { - FooterWrapper, - NotesWrapper, - PaddingWrapper, - Separator, -} from '../Wrappers'; - -export const Nominate = () => { - const { api, network } = useApi(); - const { activeAccount } = useConnect(); - const { targets, staking, getControllerNotImported } = useStaking(); - const { getBondedAccount, getLedgerForStash } = useBalances(); - const { setStatus: setModalStatus } = useModal(); - const { txFeesValid } = useTxFees(); - const { units } = network; - const { minNominatorBond } = staking; - const controller = getBondedAccount(activeAccount); - const { nominations } = targets; - const ledger = getLedgerForStash(activeAccount); - const { active } = ledger; - - const activeBase = planckBnToUnit(active, units); - const minNominatorBondBase = planckBnToUnit(minNominatorBond, units); - - // valid to submit transaction - const [valid, setValid] = useState<boolean>(false); - - // ensure selected key is valid - useEffect(() => { - setValid(nominations.length > 0 && activeBase >= minNominatorBondBase); - }, [targets]); - - // tx to submit - const getTx = () => { - let tx = null; - if (!valid || !api) { - return tx; - } - const targetsToSubmit = nominations.map((item: any) => { - return { - Id: item.address, - }; - }); - tx = api.tx.staking.nominate(targetsToSubmit); - return tx; - }; - - const { submitTx, submitting } = useSubmitExtrinsic({ - tx: getTx(), - from: controller, - shouldSubmit: valid, - callbackSubmit: () => { - setModalStatus(2); - }, - callbackInBlock: () => {}, - }); - - // warnings - const warnings = []; - if (getControllerNotImported(controller)) { - warnings.push( - 'You must have your controller account imported to start nominating' - ); - } - if (!nominations.length) { - warnings.push('You have no nominations set.'); - } - if (activeBase < minNominatorBondBase) { - warnings.push( - `You do not meet the minimum nominator bond of ${minNominatorBondBase} ${network.unit}. Please bond some funds before nominating.` - ); - } - - return ( - <> - <Title title="Nominate" icon={faPlayCircle} /> - <PaddingWrapper verticalOnly> - <div style={{ padding: '0 1rem', width: '100%' }}> - {warnings.map((text: any, index: number) => ( - <Warning key={index} text={text} /> - ))} - <h2> - You Have {nominations.length} Nomination - {nominations.length === 1 ? '' : 's'} - </h2> - <Separator /> - <NotesWrapper> - <p> - Once submitted, you will start nominating your chosen validators. - </p> - <EstimatedTxFee /> - </NotesWrapper> - <FooterWrapper> - <div> - <ButtonSubmit - text={`Submit${submitting ? 'ting' : ''}`} - iconLeft={faArrowAltCircleUp} - iconTransform="grow-2" - onClick={() => submitTx()} - disabled={ - !valid || submitting || warnings.length > 0 || !txFeesValid - } - /> - </div> - </FooterWrapper> - </div> - </PaddingWrapper> - </> - ); -}; - -export default Nominate; diff --git a/src/modals/NominateFromFavorites/Wrappers.ts b/src/modals/NominateFromFavorites/Wrappers.ts deleted file mode 100644 index 5692daea1a..0000000000 --- a/src/modals/NominateFromFavorites/Wrappers.ts +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import styled from 'styled-components'; -import { networkColor, textSecondary } from 'theme'; - -export const ListWrapper = styled.div` - display: flex; - flex-flow: column wrap; - align-items: center; - justify-content: flex-start; - position: relative; - width: 100%; - - > div, - h3 { - width: 100%; - } -`; - -export const FooterWrapper = styled.div` - position: relative; - bottom: 0px; - left: 0px; - margin: 1rem 0; - width: 100%; - display: flex; - flex-flow: row wrap; - - button { - font-size: 1.2rem; - color: ${networkColor}; - - &:disabled { - opacity: 0.5; - color: ${textSecondary}; - } - } -`; diff --git a/src/modals/NominateFromFavorites/index.tsx b/src/modals/NominateFromFavorites/index.tsx deleted file mode 100644 index 1bbd55e236..0000000000 --- a/src/modals/NominateFromFavorites/index.tsx +++ /dev/null @@ -1,201 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { faArrowAltCircleUp } from '@fortawesome/free-solid-svg-icons'; -import { ButtonSubmit } from '@rossbulat/polkadot-dashboard-ui'; -import { useApi } from 'contexts/Api'; -import { useBalances } from 'contexts/Balances'; -import { useConnect } from 'contexts/Connect'; -import { useModal } from 'contexts/Modal'; -import { useActivePools } from 'contexts/Pools/ActivePools'; -import { useTxFees } from 'contexts/TxFees'; -import { useValidators } from 'contexts/Validators'; -import { Validator } from 'contexts/Validators/types'; -import { EstimatedTxFee } from 'library/EstimatedTxFee'; -import { Warning } from 'library/Form/Warning'; -import { useSubmitExtrinsic } from 'library/Hooks/useSubmitExtrinsic'; -import { Title } from 'library/Modal/Title'; -import { ValidatorList } from 'library/ValidatorList'; -import { useEffect, useState } from 'react'; -import { FooterWrapper, NotesWrapper, PaddingWrapper } from '../Wrappers'; -import { ListWrapper } from './Wrappers'; - -export const NominateFromFavorites = () => { - const { consts, api } = useApi(); - const { activeAccount, accountHasSigner } = useConnect(); - const { getBondedAccount } = useBalances(); - const { config, setStatus: setModalStatus, setResize } = useModal(); - const { favoritesList } = useValidators(); - const { selectedActivePool, isNominator, isOwner } = useActivePools(); - const controller = getBondedAccount(activeAccount); - const { txFeesValid } = useTxFees(); - - const { maxNominations } = consts; - const { bondType, nominations } = config; - const signingAccount = bondType === 'pool' ? activeAccount : controller; - - // store filtered favorites - const [availableFavorites, setAvailableFavorites] = useState< - Array<Validator> - >([]); - - // store selected favorites in local state - const [selectedFavorites, setSelectedFavorites] = useState<Array<Validator>>( - [] - ); - - // store filtered favorites - useEffect(() => { - if (favoritesList) { - const _availableFavorites = favoritesList.filter( - (favorite: Validator) => - !nominations.find( - (nomination: string) => nomination === favorite.address - ) && !favorite.prefs.blocked - ); - setAvailableFavorites(_availableFavorites); - } - }, []); - - // calculate active + selected favorites - const nominationsToSubmit = nominations.concat( - selectedFavorites.map((favorite: Validator) => favorite.address) - ); - - // valid to submit transaction - const [valid, setValid] = useState<boolean>(false); - - useEffect(() => { - setResize(); - }, [selectedFavorites]); - - // ensure selected list is within limits - useEffect(() => { - setValid( - nominationsToSubmit.length > 0 && - nominationsToSubmit.length <= maxNominations && - selectedFavorites.length > 0 - ); - }, [selectedFavorites]); - - const batchKey = 'nominate_from_favorites'; - - const onSelected = (provider: any) => { - const { selected } = provider; - setSelectedFavorites(selected); - }; - - const totalAfterSelection = nominations.length + selectedFavorites.length; - const overMaxNominations = totalAfterSelection > maxNominations; - - // tx to submit - const getTx = () => { - let tx = null; - if (!valid || !api) { - return tx; - } - - const targetsToSubmit = nominationsToSubmit.map((item: any) => - bondType === 'pool' - ? item - : { - Id: item, - } - ); - - if (bondType === 'pool') { - tx = api.tx.nominationPools.nominate( - selectedActivePool?.id, - targetsToSubmit - ); - } else { - tx = api.tx.staking.nominate(targetsToSubmit); - } - return tx; - }; - - const { submitTx, submitting } = useSubmitExtrinsic({ - tx: getTx(), - from: signingAccount, - shouldSubmit: valid, - callbackSubmit: () => { - setModalStatus(2); - }, - callbackInBlock: () => {}, - }); - - return ( - <> - <Title title="Nominate Favorites" /> - <PaddingWrapper> - <div style={{ marginBottom: '1rem' }}> - {!accountHasSigner(signingAccount) && ( - <Warning - text={`You must have your${ - bondType === 'stake' ? ' controller' : ' ' - }account imported to add nominations.`} - /> - )} - </div> - <ListWrapper> - {availableFavorites.length > 0 ? ( - <ValidatorList - bondType="stake" - validators={availableFavorites} - batchKey={batchKey} - title="Favorite Validators / Not Nominated" - selectable - selectActive - selectToggleable={false} - onSelected={onSelected} - showMenu={false} - inModal - allowMoreCols - refetchOnListUpdate - /> - ) : ( - <h3>No Favorites Available.</h3> - )} - </ListWrapper> - <NotesWrapper style={{ paddingBottom: 0 }}> - <EstimatedTxFee /> - </NotesWrapper> - <FooterWrapper> - <h3 - className={ - selectedFavorites.length === 0 || - nominationsToSubmit.length > maxNominations - ? '' - : 'active' - } - > - {selectedFavorites.length > 0 - ? overMaxNominations - ? `Adding this many favorites will surpass ${maxNominations} nominations.` - : `Adding ${selectedFavorites.length} Nomination${ - selectedFavorites.length !== 1 ? `s` : `` - }` - : `No Favorites Selected`} - </h3> - <div> - <ButtonSubmit - text={`Submit${submitting ? 'ting' : ''}`} - iconLeft={faArrowAltCircleUp} - iconTransform="grow-2" - onClick={() => submitTx()} - disabled={ - !valid || - submitting || - (bondType === 'pool' && !isNominator() && !isOwner()) || - !accountHasSigner(signingAccount) || - !txFeesValid - } - /> - </div> - </FooterWrapper> - </PaddingWrapper> - </> - ); -}; - -export default NominateFromFavorites; diff --git a/src/modals/NominatePool/index.tsx b/src/modals/NominatePool/index.tsx deleted file mode 100644 index 46df800d8e..0000000000 --- a/src/modals/NominatePool/index.tsx +++ /dev/null @@ -1,120 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { faArrowAltCircleUp } from '@fortawesome/free-regular-svg-icons'; -import { faPlayCircle } from '@fortawesome/free-solid-svg-icons'; -import { ButtonSubmit } from '@rossbulat/polkadot-dashboard-ui'; -import { useApi } from 'contexts/Api'; -import { useConnect } from 'contexts/Connect'; -import { useModal } from 'contexts/Modal'; -import { useActivePools } from 'contexts/Pools/ActivePools'; -import { useTxFees } from 'contexts/TxFees'; -import { EstimatedTxFee } from 'library/EstimatedTxFee'; -import { Warning } from 'library/Form/Warning'; -import { useSubmitExtrinsic } from 'library/Hooks/useSubmitExtrinsic'; -import { Title } from 'library/Modal/Title'; -import { useEffect, useState } from 'react'; -import { - FooterWrapper, - NotesWrapper, - PaddingWrapper, - Separator, -} from '../Wrappers'; - -export const NominatePool = () => { - const { api } = useApi(); - const { setStatus: setModalStatus } = useModal(); - const { activeAccount, accountHasSigner } = useConnect(); - const { selectedActivePool, isOwner, isNominator, targets } = - useActivePools(); - const { txFeesValid } = useTxFees(); - const { nominations } = targets; - - // valid to submit transaction - const [valid, setValid] = useState<boolean>(false); - - const poolId = selectedActivePool?.id ?? null; - - // ensure selected roles are valid - const isValid = - (poolId !== null && isNominator() && nominations.length > 0) ?? false; - useEffect(() => { - setValid(isValid); - }, [isValid]); - - // tx to submit - const getTx = () => { - let tx = null; - if (!valid || !api) { - return tx; - } - const targetsToSubmit = nominations.map((item: any) => item.address); - tx = api.tx.nominationPools.nominate(poolId, targetsToSubmit); - return tx; - }; - - const { submitTx, submitting } = useSubmitExtrinsic({ - tx: getTx(), - from: activeAccount, - shouldSubmit: valid, - callbackSubmit: () => { - setModalStatus(2); - }, - callbackInBlock: () => {}, - }); - - // warnings - const warnings = []; - if (!accountHasSigner(activeAccount)) { - warnings.push('Your account is read only, and cannot sign transactions.'); - } - if (!nominations.length) { - warnings.push('You have no nominations set.'); - } - if (!isOwner() || !isNominator()) { - warnings.push(`You do not have a nominator role in any pools.`); - } - - return ( - <> - <Title title="Nominate" icon={faPlayCircle} /> - <PaddingWrapper verticalOnly> - <div style={{ padding: '0 1rem', width: '100%' }}> - {warnings.map((text: string, index: number) => ( - <Warning key={`warning_${index}`} text={text} /> - ))} - <h2> - You Have {nominations.length} Nomination - {nominations.length === 1 ? '' : 's'} - </h2> - <Separator /> - <NotesWrapper> - <p> - Once submitted, you will start nominating your chosen validators. - </p> - <EstimatedTxFee /> - </NotesWrapper> - <FooterWrapper> - <div> - <ButtonSubmit - text={`Submit${submitting ? 'ting' : ''}`} - iconLeft={faArrowAltCircleUp} - iconTransform="grow-2" - onClick={() => submitTx()} - disabled={ - !valid || - submitting || - warnings.length > 0 || - !accountHasSigner(activeAccount) || - !txFeesValid - } - /> - </div> - </FooterWrapper> - </div> - </PaddingWrapper> - </> - ); -}; - -export default NominatePool; diff --git a/src/modals/PoolNominations/Wrappers.ts b/src/modals/PoolNominations/Wrappers.ts index 5692daea1a..e043f85937 100644 --- a/src/modals/PoolNominations/Wrappers.ts +++ b/src/modals/PoolNominations/Wrappers.ts @@ -1,14 +1,12 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only import styled from 'styled-components'; -import { networkColor, textSecondary } from 'theme'; export const ListWrapper = styled.div` display: flex; flex-flow: column wrap; align-items: center; - justify-content: flex-start; position: relative; width: 100%; @@ -17,23 +15,3 @@ export const ListWrapper = styled.div` width: 100%; } `; - -export const FooterWrapper = styled.div` - position: relative; - bottom: 0px; - left: 0px; - margin: 1rem 0; - width: 100%; - display: flex; - flex-flow: row wrap; - - button { - font-size: 1.2rem; - color: ${networkColor}; - - &:disabled { - opacity: 0.5; - color: ${textSecondary}; - } - } -`; diff --git a/src/modals/PoolNominations/index.tsx b/src/modals/PoolNominations/index.tsx index 5381f787b3..694259460d 100644 --- a/src/modals/PoolNominations/index.tsx +++ b/src/modals/PoolNominations/index.tsx @@ -1,41 +1,41 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import { useModal } from 'contexts/Modal'; +import { ModalPadding } from '@polkadot-cloud/react'; +import { useTranslation } from 'react-i18next'; import { Title } from 'library/Modal/Title'; import { ValidatorList } from 'library/ValidatorList'; -import { PaddingWrapper } from '../Wrappers'; +import { useOverlay } from '@polkadot-cloud/react/hooks'; import { ListWrapper } from './Wrappers'; export const PoolNominations = () => { - const { config } = useModal(); - const { nominator, targets } = config; - const batchKey = 'pool_nominations'; + const { + config: { options }, + } = useOverlay().modal; + const { nominator, targets } = options; + const { t } = useTranslation('modals'); return ( <> - <Title title="Pool Nominations" /> - <PaddingWrapper> + <Title title={t('poolNominations')} /> + <ModalPadding> <ListWrapper> {targets.length > 0 ? ( <ValidatorList format="nomination" - bondType="pool" + bondFor="pool" validators={targets} nominator={nominator} - batchKey={batchKey} - title="Pool Nominations" showMenu={false} - inModal + displayFor="modal" + allowListFormat={false} refetchOnListUpdate /> ) : ( - <h3>This pool is not nominating.</h3> + <h3>{t('poolIsNotNominating')}</h3> )} </ListWrapper> - </PaddingWrapper> + </ModalPadding> </> ); }; - -export default PoolNominations; diff --git a/src/modals/SelectFavorites/Wrappers.ts b/src/modals/SelectFavorites/Wrappers.ts deleted file mode 100644 index 559575732e..0000000000 --- a/src/modals/SelectFavorites/Wrappers.ts +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import styled from 'styled-components'; -import { networkColor, textSecondary } from 'theme'; - -export const ListWrapper = styled.div` - display: flex; - flex-flow: column wrap; - align-items: center; - justify-content: flex-start; - position: relative; - width: 100%; - - > div { - width: 100%; - } -`; - -export const FooterWrapper = styled.div` - position: relative; - bottom: 0px; - left: 0px; - margin: 1rem 0; - width: 100%; - display: flex; - flex-flow: row wrap; - - button { - font-size: 1.2rem; - color: ${networkColor}; - - &:disabled { - opacity: 0.5; - color: ${textSecondary}; - } - } -`; -export default ListWrapper; diff --git a/src/modals/SelectFavorites/index.tsx b/src/modals/SelectFavorites/index.tsx deleted file mode 100644 index d53d955898..0000000000 --- a/src/modals/SelectFavorites/index.tsx +++ /dev/null @@ -1,107 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { useApi } from 'contexts/Api'; -import { useModal } from 'contexts/Modal'; -import { useValidators } from 'contexts/Validators'; -import { Validator } from 'contexts/Validators/types'; -import { Title } from 'library/Modal/Title'; -import { ValidatorList } from 'library/ValidatorList'; -import { useEffect, useState } from 'react'; -import { PaddingWrapper } from '../Wrappers'; -import { FooterWrapper, ListWrapper } from './Wrappers'; - -export const SelectFavorites = () => { - const { consts } = useApi(); - const { config, setStatus, setResize } = useModal(); - const { favoritesList } = useValidators(); - const { maxNominations } = consts; - const { nominations, callback: generateNominationsCallback } = config; - - // store filtered favorites - const [availableFavorites, setAvailableFavorites] = useState< - Array<Validator> - >([]); - - // store selected favorites in local state - const [selectedFavorites, setSelectedFavorites] = useState([]); - - // store filtered favorites - useEffect(() => { - if (favoritesList) { - const _availableFavorites = favoritesList.filter( - (favorite: Validator) => - !nominations.find( - (nomination: Validator) => nomination.address === favorite.address - ) && !favorite.prefs.blocked - ); - setAvailableFavorites(_availableFavorites); - } - }, []); - - useEffect(() => { - setResize(); - }, [selectedFavorites]); - - const batchKey = 'favorite_validators'; - - const onSelected = (provider: any) => { - const { selected } = provider; - setSelectedFavorites(selected); - }; - - const submitSelectedFavorites = () => { - if (!selectedFavorites.length) return; - const newNominations = [...nominations].concat(...selectedFavorites); - generateNominationsCallback(newNominations); - setStatus(0); - }; - - const totalAfterSelection = nominations.length + selectedFavorites.length; - const overMaxNominations = totalAfterSelection > maxNominations; - - return ( - <> - <Title title="Add From Favorites" /> - <PaddingWrapper> - <ListWrapper> - {availableFavorites.length > 0 ? ( - <ValidatorList - bondType="stake" - validators={availableFavorites} - batchKey={batchKey} - title="Favorite Validators" - selectable - selectActive - selectToggleable={false} - onSelected={onSelected} - showMenu={false} - inModal - allowMoreCols - refetchOnListUpdate - /> - ) : ( - <h3>No Favorites Available.</h3> - )} - </ListWrapper> - <FooterWrapper> - <button - type="button" - disabled={!selectedFavorites.length || overMaxNominations} - onClick={() => submitSelectedFavorites()} - > - {selectedFavorites.length > 0 - ? overMaxNominations - ? `Adding this many favorites will surpass ${maxNominations} nominations.` - : `Add ${selectedFavorites.length} Favorite${ - selectedFavorites.length !== 1 ? `s` : `` - } to Nominations` - : `No Favorites Selected`} - </button> - </FooterWrapper> - </PaddingWrapper> - </> - ); -}; - -export default SelectFavorites; diff --git a/src/modals/Settings/index.tsx b/src/modals/Settings/index.tsx index ca5e5f8c06..1a54994fda 100644 --- a/src/modals/Settings/index.tsx +++ b/src/modals/Settings/index.tsx @@ -1,51 +1,53 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import { useUi } from 'contexts/UI'; +import { ModalPadding } from '@polkadot-cloud/react'; +import { useTranslation } from 'react-i18next'; +import { usePlugins } from 'contexts/Plugins'; import { Title } from 'library/Modal/Title'; import { StatusButton } from 'library/StatusButton'; -import { PaddingWrapper } from '../Wrappers'; +import { ContentWrapper } from '../Networks/Wrapper'; export const Settings = () => { - const { services, toggleService } = useUi(); + const { plugins, togglePlugin } = usePlugins(); + const { t } = useTranslation('modals'); // fetch flag to disable fiat - const DISABLE_FIAT = Number(process.env.REACT_APP_DISABLE_FIAT ?? 0); + const DISABLE_FIAT = Number(import.meta.env.VITE_DISABLE_FIAT ?? 0); return ( <> - <Title title="Settings" /> - <PaddingWrapper> - <h4>Toggle Services</h4> - <StatusButton - checked={services.includes('subscan')} - label="Subscan API" - onClick={() => { - toggleService('subscan'); - }} - /> - {!DISABLE_FIAT && ( + <Title title={t('settings')} /> + <ModalPadding> + <ContentWrapper> + <h4>{t('togglePlugins')}</h4> <StatusButton - checked={services.includes('binance_spot')} - label="Binance Spot API" - onClick={() => { - toggleService('binance_spot'); - }} + checked={plugins.includes('subscan')} + label="Subscan API" + onClick={() => togglePlugin('subscan')} /> - )} + <StatusButton + checked={plugins.includes('polkawatch')} + label="Polkawatch API" + onClick={() => togglePlugin('polkawatch')} + /> + {!DISABLE_FIAT && ( + <StatusButton + checked={plugins.includes('binance_spot')} + label={t('binanceApi')} + onClick={() => togglePlugin('binance_spot')} + /> + )} - <h4>Toggle Features</h4> + <h4>{t('toggleFeatures')}</h4> - <StatusButton - checked={services.includes('tips')} - label="Dashboard Tips" - onClick={() => { - toggleService('tips'); - }} - /> - </PaddingWrapper> + <StatusButton + checked={plugins.includes('tips')} + label={t('dashboardTips')} + onClick={() => togglePlugin('tips')} + /> + </ContentWrapper> + </ModalPadding> </> ); }; - -export default Settings; diff --git a/src/modals/Unbond/index.tsx b/src/modals/Unbond/index.tsx new file mode 100644 index 0000000000..36d176fd1d --- /dev/null +++ b/src/modals/Unbond/index.tsx @@ -0,0 +1,245 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { ModalNotes, ModalPadding, ModalWarnings } from '@polkadot-cloud/react'; +import { isNotZero, planckToUnit, unitToPlanck } from '@polkadot-cloud/utils'; +import BigNumber from 'bignumber.js'; +import { getUnixTime } from 'date-fns'; +import { useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useApi } from 'contexts/Api'; +import { useBonded } from 'contexts/Bonded'; +import { useActivePools } from 'contexts/Pools/ActivePools'; +import { usePoolsConfig } from 'contexts/Pools/PoolsConfig'; +import { useStaking } from 'contexts/Staking'; +import { useTransferOptions } from 'contexts/TransferOptions'; +import { useTxMeta } from 'contexts/TxMeta'; +import { UnbondFeedback } from 'library/Form/Unbond/UnbondFeedback'; +import { Warning } from 'library/Form/Warning'; +import { useErasToTimeLeft } from 'library/Hooks/useErasToTimeLeft'; +import { useSignerWarnings } from 'library/Hooks/useSignerWarnings'; +import { useSubmitExtrinsic } from 'library/Hooks/useSubmitExtrinsic'; +import { timeleftAsString } from 'library/Hooks/useTimeLeft/utils'; +import { Close } from 'library/Modal/Close'; +import { SubmitTx } from 'library/SubmitTx'; +import { StaticNote } from 'modals/Utils/StaticNote'; +import { useOverlay } from '@polkadot-cloud/react/hooks'; +import { useNetwork } from 'contexts/Network'; +import { useActiveAccounts } from 'contexts/ActiveAccounts'; + +export const Unbond = () => { + const { t } = useTranslation('modals'); + const { txFees } = useTxMeta(); + const { staking } = useStaking(); + const { stats } = usePoolsConfig(); + const { activeAccount } = useActiveAccounts(); + const { notEnoughFunds } = useTxMeta(); + const { getBondedAccount } = useBonded(); + const { api, consts } = useApi(); + const { + networkData: { units, unit }, + } = useNetwork(); + const { erasToSeconds } = useErasToTimeLeft(); + const { getSignerWarnings } = useSignerWarnings(); + const { getTransferOptions } = useTransferOptions(); + const { isDepositor, selectedActivePool } = useActivePools(); + const { + setModalStatus, + setModalResize, + config: { options }, + } = useOverlay().modal; + + const { bondFor } = options; + const controller = getBondedAccount(activeAccount); + const { minNominatorBond: minNominatorBondBn } = staking; + const { minJoinBond: minJoinBondBn, minCreateBond: minCreateBondBn } = stats; + const { bondDuration } = consts; + + const bondDurationFormatted = timeleftAsString( + t, + getUnixTime(new Date()) + 1, + erasToSeconds(bondDuration), + true + ); + + let { pendingRewards } = selectedActivePool || {}; + pendingRewards = pendingRewards ?? new BigNumber(0); + pendingRewards = planckToUnit(pendingRewards, units); + + const isStaking = bondFor === 'nominator'; + const isPooling = bondFor === 'pool'; + + const allTransferOptions = getTransferOptions(activeAccount); + const { active: activeBn } = isPooling + ? allTransferOptions.pool + : allTransferOptions.nominate; + + // convert BigNumber values to number + const freeToUnbond = planckToUnit(activeBn, units); + const minJoinBond = planckToUnit(minJoinBondBn, units); + const minCreateBond = planckToUnit(minCreateBondBn, units); + const minNominatorBond = planckToUnit(minNominatorBondBn, units); + + // local bond value + const [bond, setBond] = useState<{ bond: string }>({ + bond: freeToUnbond.toString(), + }); + + // bond valid + const [bondValid, setBondValid] = useState<boolean>(false); + + // feedback errors to trigger modal resize + const [feedbackErrors, setFeedbackErrors] = useState<string[]>([]); + + // get the max amount available to unbond + const unbondToMin = isPooling + ? isDepositor() + ? BigNumber.max(freeToUnbond.minus(minCreateBond), 0) + : BigNumber.max(freeToUnbond.minus(minJoinBond), 0) + : BigNumber.max(freeToUnbond.minus(minNominatorBond), 0); + + // update bond value on task change + useEffect(() => { + setBond({ bond: unbondToMin.toString() }); + }, [freeToUnbond.toString()]); + + // tx to submit + const getTx = () => { + let tx = null; + if (!api || !activeAccount) { + return tx; + } + + const bondToSubmit = unitToPlanck(!bondValid ? '0' : bond.bond, units); + const bondAsString = bondToSubmit.isNaN() ? '0' : bondToSubmit.toString(); + + // determine tx + if (isPooling) { + tx = api.tx.nominationPools.unbond(activeAccount, bondAsString); + } else if (isStaking) { + tx = api.tx.staking.unbond(bondAsString); + } + return tx; + }; + + const signingAccount = isPooling ? activeAccount : controller; + + const submitExtrinsic = useSubmitExtrinsic({ + tx: getTx(), + from: signingAccount, + shouldSubmit: bondValid, + callbackSubmit: () => { + setModalStatus('closing'); + }, + callbackInBlock: () => {}, + }); + + const nominatorActiveBelowMin = + bondFor === 'nominator' && + isNotZero(activeBn) && + activeBn.isLessThan(minNominatorBondBn); + + const poolToMinBn = isDepositor() ? minCreateBondBn : minJoinBondBn; + const poolActiveBelowMin = + bondFor === 'pool' && activeBn.isLessThan(poolToMinBn); + + // accumulate warnings. + const warnings = getSignerWarnings( + activeAccount, + isStaking, + submitExtrinsic.proxySupported + ); + + if (pendingRewards > 0 && bondFor === 'pool') { + warnings.push(`${t('unbondingWithdraw')} ${pendingRewards} ${unit}.`); + } + if (nominatorActiveBelowMin) { + warnings.push( + t('unbondErrorBelowMinimum', { + bond: minNominatorBond, + unit, + }) + ); + } + if (poolActiveBelowMin) { + warnings.push( + t('unbondErrorBelowMinimum', { + bond: planckToUnit(poolToMinBn, units), + unit, + }) + ); + } + if (activeBn.isZero()) { + warnings.push(t('unbondErrorNoFunds', { unit })); + } + + // modal resize on form update + useEffect( + () => setModalResize(), + [bond, notEnoughFunds, feedbackErrors.length, warnings.length] + ); + + return ( + <> + <Close /> + <ModalPadding> + <h2 className="title unbounded">{t('removeBond')}</h2> + {warnings.length > 0 ? ( + <ModalWarnings withMargin> + {warnings.map((text, i) => ( + <Warning key={`warning${i}`} text={text} /> + ))} + </ModalWarnings> + ) : null} + <UnbondFeedback + bondFor={bondFor} + listenIsValid={(valid, errors) => { + setBondValid(valid); + setFeedbackErrors(errors); + }} + setters={[ + { + set: setBond, + current: bond, + }, + ]} + txFees={txFees} + /> + <ModalNotes withPadding> + {bondFor === 'pool' ? ( + <> + {isDepositor() ? ( + <p> + {t('notePoolDepositorMinBond', { + context: 'depositor', + bond: minCreateBond, + unit, + })} + </p> + ) : ( + <p> + {t('notePoolDepositorMinBond', { + context: 'member', + bond: minJoinBond, + unit, + })} + </p> + )} + </> + ) : null} + <StaticNote + value={bondDurationFormatted} + tKey="onceUnbonding" + valueKey="bondDurationFormatted" + deps={[bondDuration]} + /> + </ModalNotes> + </ModalPadding> + <SubmitTx + fromController={isStaking} + valid={bondValid} + {...submitExtrinsic} + /> + </> + ); +}; diff --git a/src/modals/UnbondPoolMember/index.tsx b/src/modals/UnbondPoolMember/index.tsx index d6b4bcc6be..6e8857ff72 100644 --- a/src/modals/UnbondPoolMember/index.tsx +++ b/src/modals/UnbondPoolMember/index.tsx @@ -1,124 +1,128 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import { faArrowAltCircleUp, faMinus } from '@fortawesome/free-solid-svg-icons'; -import { ButtonSubmit } from '@rossbulat/polkadot-dashboard-ui'; -import BN from 'bn.js'; +import { ActionItem, ModalPadding, ModalWarnings } from '@polkadot-cloud/react'; +import { + greaterThanZero, + planckToUnit, + rmCommas, + unitToPlanck, +} from '@polkadot-cloud/utils'; +import BigNumber from 'bignumber.js'; +import { getUnixTime } from 'date-fns'; +import { useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; import { useApi } from 'contexts/Api'; -import { useConnect } from 'contexts/Connect'; -import { useModal } from 'contexts/Modal'; -import { useTxFees } from 'contexts/TxFees'; -import { EstimatedTxFee } from 'library/EstimatedTxFee'; import { Warning } from 'library/Form/Warning'; +import { useErasToTimeLeft } from 'library/Hooks/useErasToTimeLeft'; +import { useSignerWarnings } from 'library/Hooks/useSignerWarnings'; import { useSubmitExtrinsic } from 'library/Hooks/useSubmitExtrinsic'; -import { Title } from 'library/Modal/Title'; -import { ContentWrapper } from 'modals/UpdateBond/Wrappers'; -import { - FooterWrapper, - NotesWrapper, - PaddingWrapper, - Separator, -} from 'modals/Wrappers'; -import { useEffect, useState } from 'react'; -import { planckBnToUnit, rmCommas, unitToPlanckBn } from 'Utils'; +import { timeleftAsString } from 'library/Hooks/useTimeLeft/utils'; +import { Close } from 'library/Modal/Close'; +import { SubmitTx } from 'library/SubmitTx'; +import { StaticNote } from 'modals/Utils/StaticNote'; +import { useTxMeta } from 'contexts/TxMeta'; +import { useOverlay } from '@polkadot-cloud/react/hooks'; +import { useNetwork } from 'contexts/Network'; +import { useActiveAccounts } from 'contexts/ActiveAccounts'; export const UnbondPoolMember = () => { - const { api, network, consts } = useApi(); - const { setStatus: setModalStatus, setResize, config } = useModal(); - const { activeAccount, accountHasSigner } = useConnect(); - const { txFeesValid } = useTxFees(); - const { units } = network; + const { t } = useTranslation('modals'); + const { api, consts } = useApi(); + const { + networkData: { units, unit }, + } = useNetwork(); + const { activeAccount } = useActiveAccounts(); + const { notEnoughFunds } = useTxMeta(); + const { erasToSeconds } = useErasToTimeLeft(); + const { getSignerWarnings } = useSignerWarnings(); + const { + setModalStatus, + setModalResize, + config: { options }, + } = useOverlay().modal; + const { bondDuration } = consts; - const { member, who } = config; + const { member, who } = options; const { points } = member; - const freeToUnbond = planckBnToUnit(new BN(rmCommas(points)), units); + const freeToUnbond = planckToUnit(new BigNumber(rmCommas(points)), units); + + const bondDurationFormatted = timeleftAsString( + t, + getUnixTime(new Date()) + 1, + erasToSeconds(bondDuration), + true + ); // local bond value - const [bond, setBond] = useState({ - bond: freeToUnbond, + const [bond, setBond] = useState<{ bond: string }>({ + bond: freeToUnbond.toString(), }); // bond valid const [bondValid, setBondValid] = useState(false); // unbond all validation - const isValid = (() => { - return freeToUnbond > 0; - })(); + const isValid = (() => greaterThanZero(freeToUnbond))(); // update bond value on task change useEffect(() => { - const _bond = freeToUnbond; - setBond({ bond: _bond }); + setBond({ bond: freeToUnbond.toString() }); setBondValid(isValid); - }, [freeToUnbond, isValid]); + }, [freeToUnbond.toString(), isValid]); - // modal resize on form update - useEffect(() => { - setResize(); - }, [bond]); + useEffect(() => setModalResize(), [bond, notEnoughFunds]); // tx to submit const getTx = () => { let tx = null; - if (!bondValid || !api || !activeAccount) { + if (!api || !activeAccount) { return tx; } // remove decimal errors - const bondToSubmit = unitToPlanckBn(bond.bond, units); - tx = api.tx.nominationPools.unbond(who, bondToSubmit); + const bondToSubmit = unitToPlanck(!bondValid ? '0' : bond.bond, units); + const bondAsString = bondToSubmit.isNaN() ? '0' : bondToSubmit.toString(); + tx = api.tx.nominationPools.unbond(who, bondAsString); return tx; }; - const { submitTx, submitting } = useSubmitExtrinsic({ + const submitExtrinsic = useSubmitExtrinsic({ tx: getTx(), from: activeAccount, shouldSubmit: bondValid, callbackSubmit: () => { - setModalStatus(2); + setModalStatus('closing'); }, callbackInBlock: () => {}, }); + const warnings = getSignerWarnings( + activeAccount, + false, + submitExtrinsic.proxySupported + ); + return ( <> - <Title title="Unbond Member Funds" icon={faMinus} /> - <PaddingWrapper verticalOnly /> - <ContentWrapper> - {!accountHasSigner(activeAccount) && ( - <Warning text="Your account is read only, and cannot sign transactions." /> - )} - <div className="items"> - <h4>Amount to unbond:</h4> - <h2> - {freeToUnbond} {network.unit} - </h2> - <Separator /> - <NotesWrapper> - <p> - Once unbonding, your funds to become available after{' '} - {bondDuration} eras. - </p> - {bondValid && <EstimatedTxFee />} - </NotesWrapper> - </div> - <FooterWrapper> - <div> - <ButtonSubmit - text={`Submit${submitting ? 'ting' : ''}`} - iconLeft={faArrowAltCircleUp} - iconTransform="grow-2" - onClick={() => submitTx()} - disabled={ - submitting || - !bondValid || - !accountHasSigner(activeAccount) || - !txFeesValid - } - /> - </div> - </FooterWrapper> - </ContentWrapper> + <Close /> + <ModalPadding> + <h2 className="title unbounded">{t('unbondMemberFunds')}</h2> + {warnings.length > 0 ? ( + <ModalWarnings withMargin> + {warnings.map((text, i) => ( + <Warning key={`warning${i}`} text={text} /> + ))} + </ModalWarnings> + ) : null} + <ActionItem text={`${t('unbond')} ${freeToUnbond} ${unit}`} /> + <StaticNote + value={bondDurationFormatted} + tKey="onceUnbonding" + valueKey="bondDurationFormatted" + deps={[bondDuration]} + /> + </ModalPadding> + <SubmitTx valid={bondValid} {...submitExtrinsic} /> </> ); }; diff --git a/src/modals/UnlockChunks/Chunk.tsx b/src/modals/UnlockChunks/Chunk.tsx new file mode 100644 index 0000000000..37a7472317 --- /dev/null +++ b/src/modals/UnlockChunks/Chunk.tsx @@ -0,0 +1,76 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { ButtonSubmit } from '@polkadot-cloud/react'; +import { planckToUnit } from '@polkadot-cloud/utils'; +import BigNumber from 'bignumber.js'; +import { fromUnixTime } from 'date-fns'; +import { useEffect } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useNetworkMetrics } from 'contexts/NetworkMetrics'; +import { Countdown } from 'library/Countdown'; +import { useErasToTimeLeft } from 'library/Hooks/useErasToTimeLeft'; +import { useTimeLeft } from 'library/Hooks/useTimeLeft'; +import { useUnstaking } from 'library/Hooks/useUnstaking'; +import { useNetwork } from 'contexts/Network'; +import { useActiveAccounts } from 'contexts/ActiveAccounts'; +import { ChunkWrapper } from './Wrappers'; + +export const Chunk = ({ chunk, bondFor, onRebond }: any) => { + const { t } = useTranslation('modals'); + + const { + networkData: { units, unit }, + network, + } = useNetwork(); + const { activeAccount } = useActiveAccounts(); + const { activeEra } = useNetworkMetrics(); + const { isFastUnstaking } = useUnstaking(); + const { erasToSeconds } = useErasToTimeLeft(); + const { timeleft, setFromNow } = useTimeLeft(); + const isStaking = bondFor === 'nominator'; + + const { era, value } = chunk; + const left = new BigNumber(era).minus(activeEra.index); + const start = activeEra.start.multipliedBy(0.001); + const erasDuration = erasToSeconds(left); + + const dateFrom = fromUnixTime(start.toNumber()); + const dateTo = fromUnixTime(start.plus(erasDuration).toNumber()); + + // reset timer on account or network change. + useEffect(() => { + setFromNow(dateFrom, dateTo); + }, [activeAccount, network]); + + return ( + <ChunkWrapper> + <div> + <section> + <h2>{`${planckToUnit(value, units)} ${unit}`}</h2> + <h4> + {left.isLessThanOrEqualTo(0) ? ( + t('unlocked') + ) : ( + <> + {t('unlocksInEra')} {era} /  + <Countdown timeleft={timeleft.formatted} markup={false} /> + </> + )} + </h4> + </section> + {isStaking ? ( + <section> + <div> + <ButtonSubmit + text={t('rebond')} + disabled={isFastUnstaking} + onClick={() => onRebond(chunk)} + /> + </div> + </section> + ) : null} + </div> + </ChunkWrapper> + ); +}; diff --git a/src/modals/UnlockChunks/Forms.tsx b/src/modals/UnlockChunks/Forms.tsx index 5345fc1165..131dc0ebbf 100644 --- a/src/modals/UnlockChunks/Forms.tsx +++ b/src/modals/UnlockChunks/Forms.tsx @@ -1,60 +1,64 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import { faArrowAltCircleUp } from '@fortawesome/free-regular-svg-icons'; import { faChevronLeft } from '@fortawesome/free-solid-svg-icons'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { ButtonSubmit } from '@rossbulat/polkadot-dashboard-ui'; -import BN from 'bn.js'; +import { + ActionItem, + ButtonSubmitInvert, + ModalWarnings, +} from '@polkadot-cloud/react'; +import { planckToUnit, rmCommas } from '@polkadot-cloud/utils'; +import BigNumber from 'bignumber.js'; +import { forwardRef, useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; import { useApi } from 'contexts/Api'; -import { useBalances } from 'contexts/Balances'; -import { useConnect } from 'contexts/Connect'; -import { useModal } from 'contexts/Modal'; +import { useBonded } from 'contexts/Bonded'; import { useActivePools } from 'contexts/Pools/ActivePools'; import { useBondedPools } from 'contexts/Pools/BondedPools'; import { usePoolMembers } from 'contexts/Pools/PoolMembers'; import { usePoolMemberships } from 'contexts/Pools/PoolMemberships'; import { usePoolsConfig } from 'contexts/Pools/PoolsConfig'; -import { useTxFees } from 'contexts/TxFees'; -import { EstimatedTxFee } from 'library/EstimatedTxFee'; import { Warning } from 'library/Form/Warning'; +import { useSignerWarnings } from 'library/Hooks/useSignerWarnings'; import { useSubmitExtrinsic } from 'library/Hooks/useSubmitExtrinsic'; -import { forwardRef, useEffect, useState } from 'react'; -import { planckBnToUnit, rmCommas } from 'Utils'; -import { FooterWrapper, NotesWrapper, Separator } from '../Wrappers'; +import { SubmitTx } from 'library/SubmitTx'; +import { useOverlay } from '@polkadot-cloud/react/hooks'; +import { useNetwork } from 'contexts/Network'; +import { useActiveAccounts } from 'contexts/ActiveAccounts'; import { ContentWrapper } from './Wrappers'; export const Forms = forwardRef( - ({ setSection, unlock, task }: any, ref: any) => { - const { api, network, consts } = useApi(); - const { activeAccount, accountHasSigner } = useConnect(); + ({ setSection, unlock, task, incrementCalculateHeight }: any, ref: any) => { + const { t } = useTranslation('modals'); + const { api, consts } = useApi(); + const { + networkData: { units, unit }, + } = useNetwork(); + const { activeAccount } = useActiveAccounts(); const { removeFavorite: removeFavoritePool } = usePoolsConfig(); const { membership } = usePoolMemberships(); const { selectedActivePool } = useActivePools(); const { removeFromBondedPools } = useBondedPools(); const { removePoolMember } = usePoolMembers(); - const { setStatus: setModalStatus, config } = useModal(); - const { getBondedAccount } = useBalances(); - const { txFeesValid } = useTxFees(); + const { + setModalStatus, + config: { options }, + } = useOverlay().modal; + const { getBondedAccount } = useBonded(); + const { getSignerWarnings } = useSignerWarnings(); - const { bondType, poolClosure } = config || {}; + const { bondFor, poolClosure } = options || {}; const { historyDepth } = consts; - const { units } = network; const controller = getBondedAccount(activeAccount); - const isStaking = bondType === 'stake'; - const isPooling = bondType === 'pool'; + const isStaking = bondFor === 'nominator'; + const isPooling = bondFor === 'pool'; // valid to submit transaction const [valid, setValid] = useState<boolean>( unlock?.value?.toNumber() > 0 ?? false ); - // ensure unlock value is valid - useEffect(() => { - setValid(unlock?.value?.toNumber() > 0 ?? false); - }, [unlock]); - // tx to submit const getTx = () => { let tx = null; @@ -65,22 +69,22 @@ export const Forms = forwardRef( if (task === 'rebond' && isStaking) { tx = api.tx.staking.rebond(unlock.value.toNumber()); } else if (task === 'withdraw' && isStaking) { - tx = api.tx.staking.withdrawUnbonded(historyDepth); + tx = api.tx.staking.withdrawUnbonded(historyDepth.toString()); } else if (task === 'withdraw' && isPooling && selectedActivePool) { tx = api.tx.nominationPools.withdrawUnbonded( activeAccount, - historyDepth + historyDepth.toString() ); } return tx; }; const signingAccount = isStaking ? controller : activeAccount; - const { submitTx, submitting } = useSubmitExtrinsic({ + const submitExtrinsic = useSubmitExtrinsic({ tx: getTx(), from: signingAccount, shouldSubmit: valid, callbackSubmit: () => { - setModalStatus(2); + setModalStatus('closing'); }, callbackInBlock: () => { // if pool is being closed, remove from static lists @@ -90,69 +94,84 @@ export const Forms = forwardRef( } // if no more bonded funds from pool, remove from poolMembers list - if (bondType === 'pool') { + if (bondFor === 'pool') { const points = membership?.points ? rmCommas(membership.points) : 0; - const bonded = planckBnToUnit(new BN(points), network.units); - if (bonded === 0) { + const bonded = planckToUnit(new BigNumber(points), units); + if (bonded.isZero()) { removePoolMember(activeAccount); } } }, }); - const value = unlock?.value ?? new BN(0); + const value = unlock?.value ?? new BigNumber(0); + + const warnings = getSignerWarnings( + activeAccount, + isStaking, + submitExtrinsic.proxySupported + ); + + // Ensure unlock value is valid. + useEffect(() => { + setValid(unlock?.value?.toNumber() > 0 ?? false); + }, [unlock]); + + // Trigger modal resize when commission options are enabled / disabled. + useEffect(() => { + incrementCalculateHeight(); + }, [valid]); return ( <ContentWrapper> - <div ref={ref} style={{ paddingBottom: '1rem' }}> - <div> - {!accountHasSigner(signingAccount) && ( - <Warning text="Your account is read only, and cannot sign transactions." /> - )} - - <div style={{ marginTop: '2rem' }}> + <div ref={ref}> + <div className="padding"> + {warnings.length > 0 ? ( + <ModalWarnings withMargin> + {warnings.map((text, i) => ( + <Warning key={`warning${i}`} text={text} /> + ))} + </ModalWarnings> + ) : null} + <div style={{ marginBottom: '2rem' }}> {task === 'rebond' && ( - <h2> - Rebond {planckBnToUnit(value, units)} {network.unit} - </h2> + <> + <ActionItem + text={`${t('rebond')} ${planckToUnit( + value, + units + )} ${unit}`} + /> + <p>{t('rebondSubtitle')}</p> + </> )} {task === 'withdraw' && ( - <h2> - Withdraw {planckBnToUnit(value, units)} {network.unit} - </h2> + <> + <ActionItem + text={`${t('withdraw')} ${planckToUnit( + value, + units + )} ${unit}`} + /> + <p>{t('withdrawSubtitle')}</p> + </> )} </div> - <Separator /> - <NotesWrapper> - <EstimatedTxFee /> - </NotesWrapper> </div> - <FooterWrapper> - <div> - <button - type="button" - className="submit secondary" + <SubmitTx + fromController={isStaking} + valid={valid} + buttons={[ + <ButtonSubmitInvert + key="button_back" + text={t('back')} + iconLeft={faChevronLeft} + iconTransform="shrink-1" onClick={() => setSection(0)} - > - <FontAwesomeIcon transform="shrink-2" icon={faChevronLeft} /> - Back - </button> - </div> - <div> - <ButtonSubmit - text={`Submit${submitting ? 'ting' : ''}`} - iconLeft={faArrowAltCircleUp} - iconTransform="grow-2" - onClick={() => submitTx()} - disabled={ - !valid || - submitting || - !accountHasSigner(signingAccount) || - !txFeesValid - } - /> - </div> - </FooterWrapper> + />, + ]} + {...submitExtrinsic} + /> </div> </ContentWrapper> ); diff --git a/src/modals/UnlockChunks/Overview.tsx b/src/modals/UnlockChunks/Overview.tsx index 55e78380ce..cdd235215e 100644 --- a/src/modals/UnlockChunks/Overview.tsx +++ b/src/modals/UnlockChunks/Overview.tsx @@ -1,154 +1,146 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only import { faCheckCircle, faClock } from '@fortawesome/free-regular-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { ButtonSubmit } from '@rossbulat/polkadot-dashboard-ui'; -import BN from 'bn.js'; -import { useApi } from 'contexts/Api'; -import { useNetworkMetrics } from 'contexts/Network'; -import { StatsWrapper, StatWrapper } from 'library/Modal/Wrappers'; +import { ButtonSubmit, ModalNotes } from '@polkadot-cloud/react'; +import { planckToUnit } from '@polkadot-cloud/utils'; +import BigNumber from 'bignumber.js'; +import { getUnixTime } from 'date-fns'; import { forwardRef } from 'react'; -import { humanNumber, planckBnToUnit, toFixedIfNecessary } from 'Utils'; -import { NotesWrapper, Separator } from '../Wrappers'; -import { ChunkWrapper, ContentWrapper } from './Wrappers'; +import { useTranslation } from 'react-i18next'; +import { useApi } from 'contexts/Api'; +import { useNetworkMetrics } from 'contexts/NetworkMetrics'; +import { useErasToTimeLeft } from 'library/Hooks/useErasToTimeLeft'; +import { timeleftAsString } from 'library/Hooks/useTimeLeft/utils'; +import { useUnstaking } from 'library/Hooks/useUnstaking'; +import { StatWrapper, StatsWrapper } from 'library/Modal/Wrappers'; +import { StaticNote } from 'modals/Utils/StaticNote'; +import type { AnyJson } from 'types'; +import { useNetwork } from 'contexts/Network'; +import { Chunk } from './Chunk'; +import { ContentWrapper } from './Wrappers'; export const Overview = forwardRef( - ({ unlocking, bondType, setSection, setUnlock, setTask }: any, ref: any) => { - const { network, consts } = useApi(); - const { metrics } = useNetworkMetrics(); + ({ unlocking, bondFor, setSection, setUnlock, setTask }: any, ref: any) => { + const { t } = useTranslation('modals'); + const { consts } = useApi(); + const { + networkData: { units, unit }, + } = useNetwork(); + const { activeEra } = useNetworkMetrics(); const { bondDuration } = consts; - const { units } = network; - const { activeEra } = metrics; + const { isFastUnstaking } = useUnstaking(); + const { erasToSeconds } = useErasToTimeLeft(); - const isStaking = bondType === 'stake'; + const bondDurationFormatted = timeleftAsString( + t, + getUnixTime(new Date()) + 1, + erasToSeconds(bondDuration), + true + ); - let withdrawAvailable = new BN(0); - let totalUnbonding = new BN(0); - for (const _chunk of unlocking) { - const { era, value } = _chunk; - const left = era - activeEra.index; + const isStaking = bondFor === 'nominator'; - totalUnbonding = totalUnbonding.add(value); - if (left <= 0) { - withdrawAvailable = withdrawAvailable.add(value); + let withdrawAvailable = new BigNumber(0); + let totalUnbonding = new BigNumber(0); + for (const c of unlocking) { + const { era, value } = c; + const left = new BigNumber(era).minus(activeEra.index); + + totalUnbonding = totalUnbonding.plus(value); + if (left.isLessThanOrEqualTo(0)) { + withdrawAvailable = withdrawAvailable.plus(value); } } + const onRebondHandler = (chunk: AnyJson) => { + setTask('rebond'); + setUnlock(chunk); + setSection(1); + }; + return ( - <ContentWrapper ref={ref}> - <StatsWrapper> - <StatWrapper> - <div className="inner"> - <h4> - <FontAwesomeIcon icon={faCheckCircle} className="icon" />{' '} - Unlocked - </h4> - <h2> - {humanNumber( - toFixedIfNecessary( - planckBnToUnit(withdrawAvailable, units), - 3 - ) - )}{' '} - {network.unit} - </h2> - </div> - </StatWrapper> - <StatWrapper> - <div className="inner"> - <h4> - <FontAwesomeIcon icon={faClock} className="icon" /> Unbonding - </h4> - <h2> - {humanNumber( - toFixedIfNecessary( - planckBnToUnit( - totalUnbonding.sub(withdrawAvailable), - units - ), - 3 - ) - )}{' '} - {network.unit} - </h2> - </div> - </StatWrapper> - <StatWrapper> - <div className="inner"> - <h4>Total</h4> - <h2> - {humanNumber( - toFixedIfNecessary(planckBnToUnit(totalUnbonding, units), 3) - )}{' '} - {network.unit} - </h2> + <ContentWrapper> + <div className="padding" ref={ref}> + <StatsWrapper> + <StatWrapper> + <div className="inner"> + <h4> + <FontAwesomeIcon icon={faCheckCircle} className="icon" />{' '} + {t('unlocked')} + </h4> + <h2> + {planckToUnit(withdrawAvailable, units) + .decimalPlaces(3) + .toFormat()}{' '} + {unit} + </h2> + </div> + </StatWrapper> + <StatWrapper> + <div className="inner"> + <h4> + <FontAwesomeIcon icon={faClock} className="icon" />{' '} + {t('unbonding')} + </h4> + <h2> + {planckToUnit(totalUnbonding.minus(withdrawAvailable), units) + .decimalPlaces(3) + .toFormat()}{' '} + {unit} + </h2> + </div> + </StatWrapper> + <StatWrapper> + <div className="inner"> + <h4>{t('total')}</h4> + <h2> + {planckToUnit(totalUnbonding, units) + .decimalPlaces(3) + .toFormat()}{' '} + {unit} + </h2> + </div> + </StatWrapper> + </StatsWrapper> + + {withdrawAvailable.toNumber() > 0 && ( + <div style={{ margin: '1rem 0 0.5rem 0' }}> + <ButtonSubmit + disabled={isFastUnstaking} + text={t('withdrawUnlocked')} + onClick={() => { + setTask('withdraw'); + setUnlock({ + era: 0, + value: withdrawAvailable, + }); + setSection(1); + }} + /> </div> - </StatWrapper> - </StatsWrapper> + )} - {withdrawAvailable.toNumber() > 0 && ( - <div style={{ margin: '1rem 0 0.5rem 0' }}> - <ButtonSubmit - text="Withdraw Unlocked" - onClick={() => { - setTask('withdraw'); - setUnlock({ - era: 0, - value: withdrawAvailable, - }); - setSection(1); - }} + {unlocking.map((chunk: any, i: number) => ( + <Chunk + key={`unlock_chunk_${i}`} + chunk={chunk} + bondFor={bondFor} + onRebond={onRebondHandler} /> - </div> - )} - - {unlocking.map((chunk: any, i: number) => { - const { era, value } = chunk; - const left = era - activeEra.index; - - return ( - <ChunkWrapper key={`unlock_chunk_${i}`}> - <div> - <section> - <h2> - {planckBnToUnit(value, units)} {network.unit} - </h2> - <h4>{left <= 0 ? 'Unlocked' : `Unlocks after era ${era}`}</h4> - </section> - {isStaking && ( - <section> - <div> - <ButtonSubmit - text="Rebond" - onClick={() => { - setTask('rebond'); - setUnlock(chunk); - setSection(1); - }} - /> - </div> - </section> - )} - </div> - {i === unlocking.length - 1 ? null : <Separator />} - </ChunkWrapper> - ); - })} - <NotesWrapper> - <p> - Unlocks take {bondDuration} eras before they can be withdrawn. - {isStaking && - `You can rebond unlocks at any time in this period, or withdraw them to your free balance thereafter.`} - </p> - {!isStaking && ( - <p> - Unlock chunks cannot currently be rebonded in a pool. If you wish - to rebond, withdraw the unlock chunk first and the add to your - bond. - </p> - )} - </NotesWrapper> + ))} + <ModalNotes withPadding> + <StaticNote + value={bondDurationFormatted} + tKey="unlockTake" + valueKey="bondDurationFormatted" + deps={[bondDuration]} + /> + <p> {isStaking ? ` ${t('rebondUnlock')}` : null}</p> + {!isStaking ? <p>{t('unlockChunk')}</p> : null} + </ModalNotes> + </div> </ContentWrapper> ); } diff --git a/src/modals/UnlockChunks/Wrappers.ts b/src/modals/UnlockChunks/Wrappers.ts index 62cee6b115..304d36900b 100644 --- a/src/modals/UnlockChunks/Wrappers.ts +++ b/src/modals/UnlockChunks/Wrappers.ts @@ -1,31 +1,7 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import { motion } from 'framer-motion'; import styled from 'styled-components'; -import { buttonPrimaryBackground, textSecondary } from 'theme'; - -export const Wrapper = styled.div` - display: flex; - flex-flow: column wrap; - align-items: flex-start; - justify-content: flex-start; -`; - -export const FixedContentWrapper = styled.div` - padding-top: 1rem; - width: 100%; -`; - -export const CardsWrapper = styled(motion.div)` - width: 200%; - display: flex; - flex-flow: row nowrap; - overflow: hidden; - overflow-y: auto; - position: relative; - height: 100%; -`; export const ContentWrapper = styled.div` border-radius: 1rem; @@ -33,7 +9,10 @@ export const ContentWrapper = styled.div` flex-flow: column nowrap; flex-basis: 50%; flex: 1; - padding: 0 1.25rem; + + .padding { + padding: 0 1rem; + } > div:last-child { margin-bottom: 0; @@ -47,18 +26,17 @@ export const ChunkWrapper = styled.div<any>` margin-top: 1.25rem; > div { + background: var(--button-primary-background); display: flex; flex-flow: row wrap; width: 100%; - padding: 0.25rem 1.1rem; + padding: 0.5rem 1rem; border-radius: 1rem; - background: ${buttonPrimaryBackground}; > section { display: flex; flex-flow: column wrap; justify-content: flex-end; - align-items: flex-start; padding: 0.75rem 0; &:first-child { @@ -73,10 +51,9 @@ export const ChunkWrapper = styled.div<any>` h2 { margin: 0; } + h4 { - color: ${textSecondary}; - margin: 0.35rem 0 0 0; + color: var(--text-color-secondary); + margin: 0.75rem 0 0 0; } `; - -export default Wrapper; diff --git a/src/modals/UnlockChunks/index.tsx b/src/modals/UnlockChunks/index.tsx index cf5f27badb..b4f124e0df 100644 --- a/src/modals/UnlockChunks/index.tsx +++ b/src/modals/UnlockChunks/index.tsx @@ -1,46 +1,59 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import { faLockOpen } from '@fortawesome/free-solid-svg-icons'; +import { + ModalFixedTitle, + ModalMotionTwoSection, + ModalSection, +} from '@polkadot-cloud/react'; +import { setStateWithRef } from '@polkadot-cloud/utils'; +import { useEffect, useRef, useState } from 'react'; +import { useTranslation } from 'react-i18next'; import { useBalances } from 'contexts/Balances'; -import { useConnect } from 'contexts/Connect'; -import { useModal } from 'contexts/Modal'; import { useActivePools } from 'contexts/Pools/ActivePools'; import { Title } from 'library/Modal/Title'; -import { useEffect, useRef, useState } from 'react'; +import { useTxMeta } from 'contexts/TxMeta'; +import { useOverlay } from '@polkadot-cloud/react/hooks'; +import { useActiveAccounts } from 'contexts/ActiveAccounts'; import { Forms } from './Forms'; import { Overview } from './Overview'; -import { CardsWrapper, FixedContentWrapper, Wrapper } from './Wrappers'; export const UnlockChunks = () => { - const { activeAccount } = useConnect(); - const { config, setModalHeight } = useModal(); - const { bondType } = config || {}; - const { getLedgerForStash } = useBalances(); + const { t } = useTranslation('modals'); + const { + config: { options }, + setModalHeight, + } = useOverlay().modal; + const { notEnoughFunds } = useTxMeta(); + const { getStashLedger } = useBalances(); + const { activeAccount } = useActiveAccounts(); const { getPoolUnlocking } = useActivePools(); + const { bondFor } = options || {}; - // get the unlocking per bondType - const _getUnlocking = () => { + // get the unlocking per bondFor + const getUnlocking = () => { let unlocking = []; let ledger; - switch (bondType) { - case 'stake': - ledger = getLedgerForStash(activeAccount); - unlocking = ledger.unlocking; - break; + switch (bondFor) { case 'pool': unlocking = getPoolUnlocking(); break; default: - // console.error(`unlocking modal bond-type ${bondType} is not defined.`); + ledger = getStashLedger(activeAccount); + unlocking = ledger.unlocking; } return unlocking; }; - const unlocking = _getUnlocking(); + const unlocking = getUnlocking(); // active modal section - const [section, setSection] = useState(0); + const [section, setSectionState] = useState(0); + const sectionRef = useRef(section); + + const setSection = (s: number) => { + setStateWithRef(s, setSectionState, sectionRef); + }; // modal task const [task, setTask] = useState<string | null>(null); @@ -48,6 +61,11 @@ export const UnlockChunks = () => { // unlock value of interest const [unlock, setUnlock] = useState(null); + // counter to trigger modal height calculation + const [calculateHeight, setCalculateHeight] = useState<number>(0); + const incrementCalculateHeight = () => + setCalculateHeight(calculateHeight + 1); + // refs for wrappers const headerRef = useRef<HTMLDivElement>(null); const overviewRef = useRef<HTMLDivElement>(null); @@ -56,7 +74,7 @@ export const UnlockChunks = () => { const getModalHeight = () => { let h = headerRef.current?.clientHeight ?? 0; - if (section === 0) { + if (sectionRef.current === 0) { h += overviewRef.current?.clientHeight ?? 0; } else { h += formsRef.current?.clientHeight ?? 0; @@ -64,10 +82,14 @@ export const UnlockChunks = () => { return h; }; + const resizeCallback = () => { + setModalHeight(getModalHeight()); + }; + // resize modal on state change useEffect(() => { setModalHeight(getModalHeight()); - }, [task, section]); + }, [task, calculateHeight, notEnoughFunds, sectionRef.current, unlocking]); // resize this modal on window resize useEffect(() => { @@ -76,17 +98,14 @@ export const UnlockChunks = () => { window.removeEventListener('resize', resizeCallback); }; }, []); - const resizeCallback = () => { - setModalHeight(getModalHeight()); - }; return ( - <Wrapper> - <FixedContentWrapper ref={headerRef}> - <Title title="Unlocks" icon={faLockOpen} fixed /> - </FixedContentWrapper> - <CardsWrapper - animate={section === 0 ? 'home' : 'next'} + <ModalSection type="carousel"> + <ModalFixedTitle ref={headerRef}> + <Title title={t('unlocks')} fixed /> + </ModalFixedTitle> + <ModalMotionTwoSection + animate={sectionRef.current === 0 ? 'home' : 'next'} transition={{ duration: 0.5, type: 'spring', @@ -103,19 +122,20 @@ export const UnlockChunks = () => { > <Overview unlocking={unlocking} - bondType={bondType} + bondFor={bondFor} setSection={setSection} setUnlock={setUnlock} setTask={setTask} ref={overviewRef} /> <Forms + incrementCalculateHeight={incrementCalculateHeight} setSection={setSection} unlock={unlock} task={task} ref={formsRef} /> - </CardsWrapper> - </Wrapper> + </ModalMotionTwoSection> + </ModalSection> ); }; diff --git a/src/modals/Unstake/index.tsx b/src/modals/Unstake/index.tsx new file mode 100644 index 0000000000..ac9a88ca2a --- /dev/null +++ b/src/modals/Unstake/index.tsx @@ -0,0 +1,152 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { ActionItem, ModalPadding, ModalWarnings } from '@polkadot-cloud/react'; +import { + greaterThanZero, + planckToUnit, + unitToPlanck, +} from '@polkadot-cloud/utils'; +import { getUnixTime } from 'date-fns'; +import { useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useApi } from 'contexts/Api'; +import { useBonded } from 'contexts/Bonded'; +import { useTransferOptions } from 'contexts/TransferOptions'; +import { Warning } from 'library/Form/Warning'; +import { useBatchCall } from 'library/Hooks/useBatchCall'; +import { useErasToTimeLeft } from 'library/Hooks/useErasToTimeLeft'; +import { useSignerWarnings } from 'library/Hooks/useSignerWarnings'; +import { useSubmitExtrinsic } from 'library/Hooks/useSubmitExtrinsic'; +import { timeleftAsString } from 'library/Hooks/useTimeLeft/utils'; +import { Close } from 'library/Modal/Close'; +import { SubmitTx } from 'library/SubmitTx'; +import { StaticNote } from 'modals/Utils/StaticNote'; +import { useTxMeta } from 'contexts/TxMeta'; +import { useOverlay } from '@polkadot-cloud/react/hooks'; +import { useNetwork } from 'contexts/Network'; +import { useActiveAccounts } from 'contexts/ActiveAccounts'; + +export const Unstake = () => { + const { t } = useTranslation('modals'); + const { newBatchCall } = useBatchCall(); + const { notEnoughFunds } = useTxMeta(); + const { activeAccount } = useActiveAccounts(); + const { api, consts } = useApi(); + const { + networkData: { units, unit }, + } = useNetwork(); + const { erasToSeconds } = useErasToTimeLeft(); + const { getSignerWarnings } = useSignerWarnings(); + const { getTransferOptions } = useTransferOptions(); + const { setModalStatus, setModalResize } = useOverlay().modal; + const { getBondedAccount, getAccountNominations } = useBonded(); + + const controller = getBondedAccount(activeAccount); + const nominations = getAccountNominations(activeAccount); + const { bondDuration } = consts; + const allTransferOptions = getTransferOptions(activeAccount); + const { active } = allTransferOptions.nominate; + + const bondDurationFormatted = timeleftAsString( + t, + getUnixTime(new Date()) + 1, + erasToSeconds(bondDuration), + true + ); + + // convert BigNumber values to number + const freeToUnbond = planckToUnit(active, units); + + // local bond value + const [bond, setBond] = useState<{ bond: string }>({ + bond: freeToUnbond.toString(), + }); + + // bond valid + const [bondValid, setBondValid] = useState(false); + + // unbond all validation + const isValid = (() => greaterThanZero(freeToUnbond))(); + + // update bond value on task change + useEffect(() => { + setBond({ bond: freeToUnbond.toString() }); + setBondValid(isValid); + }, [freeToUnbond.toString(), isValid]); + + // modal resize on form update + useEffect(() => setModalResize(), [bond, notEnoughFunds]); + + // tx to submit + const getTx = () => { + const tx = null; + if (!api || !activeAccount) { + return tx; + } + // remove decimal errors + const bondToSubmit = unitToPlanck( + String(!bondValid ? '0' : bond.bond), + units + ); + const bondAsString = bondToSubmit.isNaN() ? '0' : bondToSubmit.toString(); + + if (!bondAsString) { + return api.tx.staking.chill(); + } + const txs = [api.tx.staking.chill(), api.tx.staking.unbond(bondAsString)]; + return newBatchCall(txs, controller); + }; + + const submitExtrinsic = useSubmitExtrinsic({ + tx: getTx(), + from: controller, + shouldSubmit: bondValid, + callbackSubmit: () => { + setModalStatus('closing'); + }, + callbackInBlock: () => {}, + }); + + const warnings = getSignerWarnings( + activeAccount, + true, + submitExtrinsic.proxySupported + ); + + return ( + <> + <Close /> + <ModalPadding> + <h2 className="title unbounded">{t('unstake')} </h2> + {warnings.length > 0 ? ( + <ModalWarnings withMargin> + {warnings.map((text, i) => ( + <Warning key={`warning${i}`} text={text} /> + ))} + </ModalWarnings> + ) : null} + {greaterThanZero(freeToUnbond) ? ( + <ActionItem + text={t('unstakeUnbond', { + bond: freeToUnbond.toFormat(), + unit, + })} + /> + ) : null} + {nominations.length > 0 && ( + <ActionItem + text={t('unstakeStopNominating', { count: nominations.length })} + /> + )} + <StaticNote + value={bondDurationFormatted} + tKey="onceUnbonding" + valueKey="bondDurationFormatted" + deps={[bondDuration]} + /> + </ModalPadding> + <SubmitTx fromController valid={bondValid} {...submitExtrinsic} /> + </> + ); +}; diff --git a/src/modals/UpdateBond/Forms/BondAll.tsx b/src/modals/UpdateBond/Forms/BondAll.tsx deleted file mode 100644 index 3b3edfec13..0000000000 --- a/src/modals/UpdateBond/Forms/BondAll.tsx +++ /dev/null @@ -1,155 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { BN_ZERO } from '@polkadot/util'; -import BN from 'bn.js'; -import { useApi } from 'contexts/Api'; -import { useConnect } from 'contexts/Connect'; -import { useModal } from 'contexts/Modal'; -import { useActivePools } from 'contexts/Pools/ActivePools'; -import { useTransferOptions } from 'contexts/TransferOptions'; -import { useTxFees } from 'contexts/TxFees'; -import { EstimatedTxFee } from 'library/EstimatedTxFee'; -import { Warning } from 'library/Form/Warning'; -import useBondGreatestFee from 'library/Hooks/useBondGreatestFee'; -import { useSubmitExtrinsic } from 'library/Hooks/useSubmitExtrinsic'; -import { useEffect, useState } from 'react'; -import { planckBnToUnit } from 'Utils'; -import { Separator } from '../../Wrappers'; -import { FormsProps } from '../types'; -import { FormFooter } from './FormFooter'; - -export const BondAll = (props: FormsProps) => { - const { setSection, setLocalResize } = props; - - const { api, network } = useApi(); - const { units } = network; - const { setStatus: setModalStatus, config } = useModal(); - const { activeAccount, accountHasSigner } = useConnect(); - const { getTransferOptions } = useTransferOptions(); - const { bondType } = config; - const { txFees, txFeesValid } = useTxFees(); - const { selectedActivePool } = useActivePools(); - const largestTxFee = useBondGreatestFee({ bondType }); - - let { unclaimedRewards } = selectedActivePool || {}; - unclaimedRewards = unclaimedRewards ?? new BN(0); - unclaimedRewards = planckBnToUnit(unclaimedRewards, network.units); - - const isStaking = bondType === 'stake'; - const isPooling = bondType === 'pool'; - - const allTransferOptions = getTransferOptions(activeAccount); - const { freeBalance: freeBalanceBn } = allTransferOptions; - const { totalPossibleBond: totalPossibleBondBn } = isPooling - ? allTransferOptions.pool - : allTransferOptions.nominate; - - // convert BN values to number - const freeBalance = planckBnToUnit(freeBalanceBn, units); - - // local bond value - const [bond, setBond] = useState({ bond: freeBalance }); - - // bond minus tx fees - const bondAfterTxFees = BN.max(freeBalanceBn.sub(largestTxFee), BN_ZERO); - - // total possible bond after tx fees - const totalPossibleBond = planckBnToUnit( - BN.max(totalPossibleBondBn.sub(largestTxFee), BN_ZERO), - units - ); - - // bond valid - const [bondValid, setBondValid] = useState(false); - - // update bond value on task change - useEffect(() => { - const _bond = freeBalance; - setBond({ bond: _bond }); - if (_bond > 0 && bondAfterTxFees.gt(BN_ZERO)) { - setBondValid(true); - } else { - setBondValid(false); - } - }, [freeBalance, txFees]); - - // modal resize on form update - useEffect(() => { - if (setLocalResize) setLocalResize(); - }, [bond]); - - // tx to submit - const getTx = () => { - let tx = null; - if (!bondValid || !api || !activeAccount) { - return tx; - } - - // convert to submittable string - const bondToSubmit = bondAfterTxFees.toString(); - - // determine tx - if (isPooling) { - tx = api.tx.nominationPools.bondExtra({ FreeBalance: bondToSubmit }); - } else if (isStaking) { - tx = api.tx.staking.bondExtra(bondToSubmit); - } - return tx; - }; - - const { submitTx, submitting } = useSubmitExtrinsic({ - tx: getTx(), - from: activeAccount, - shouldSubmit: bondValid, - callbackSubmit: () => { - setModalStatus(2); - }, - callbackInBlock: () => {}, - }); - - return ( - <> - <div className="items"> - <> - {!accountHasSigner(activeAccount) && ( - <Warning text="Your account is read only, and cannot sign transactions." /> - )} - {freeBalance === 0 && ( - <Warning text={`You have no free ${network.unit} to bond.`} /> - )} - {unclaimedRewards > 0 && bondType === 'pool' && ( - <Warning - text={`Bonding will also withdraw your outstanding rewards of ${unclaimedRewards} ${network.unit}.`} - /> - )} - <h4>Amount to bond:</h4> - <h2> - {largestTxFee.eq(new BN(0)) - ? '...' - : `${planckBnToUnit(bondAfterTxFees, units)} ${network.unit}`} - </h2> - <p> - This amount of {network.unit} will be added to your current bonded - funds. - </p> - <Separator /> - <h4>New total bond:</h4> - <h2> - {largestTxFee.eq(new BN(0)) - ? '...' - : `${totalPossibleBond} ${network.unit}`} - </h2> - <Separator /> - <EstimatedTxFee /> - </> - </div> - <FormFooter - setSection={setSection} - submitTx={submitTx} - submitting={submitting} - isValid={bondValid && accountHasSigner(activeAccount) && txFeesValid} - /> - </> - ); -}; diff --git a/src/modals/UpdateBond/Forms/BondSome.tsx b/src/modals/UpdateBond/Forms/BondSome.tsx deleted file mode 100644 index 0361fc051f..0000000000 --- a/src/modals/UpdateBond/Forms/BondSome.tsx +++ /dev/null @@ -1,148 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import BN, { max } from 'bn.js'; -import { useApi } from 'contexts/Api'; -import { useConnect } from 'contexts/Connect'; -import { useModal } from 'contexts/Modal'; -import { useActivePools } from 'contexts/Pools/ActivePools'; -import { useTransferOptions } from 'contexts/TransferOptions'; -import { useTxFees } from 'contexts/TxFees'; -import { EstimatedTxFee } from 'library/EstimatedTxFee'; -import { BondFeedback } from 'library/Form/Bond/BondFeedback'; -import { Warning } from 'library/Form/Warning'; -import { useBondGreatestFee } from 'library/Hooks/useBondGreatestFee'; -import { useSubmitExtrinsic } from 'library/Hooks/useSubmitExtrinsic'; -import { useEffect, useState } from 'react'; -import { planckBnToUnit, unitToPlanckBn } from 'Utils'; -import { NotesWrapper } from '../../Wrappers'; -import { FormsProps } from '../types'; -import { FormFooter } from './FormFooter'; - -export const BondSome = ({ setSection, setLocalResize }: FormsProps) => { - const { api, network } = useApi(); - const { units } = network; - const { setStatus: setModalStatus, config, setResize } = useModal(); - const { activeAccount, accountHasSigner } = useConnect(); - const { getTransferOptions } = useTransferOptions(); - const { txFeesValid } = useTxFees(); - const { selectedActivePool } = useActivePools(); - const { bondType } = config; - const isStaking = bondType === 'stake'; - const isPooling = bondType === 'pool'; - const { freeBalance: freeBalanceBn } = getTransferOptions(activeAccount); - const freeBalance = planckBnToUnit(freeBalanceBn, units); - const largestTxFee = useBondGreatestFee({ bondType }); - - // calculate any unclaimed pool rewards. - let { unclaimedRewards } = selectedActivePool || {}; - unclaimedRewards = unclaimedRewards ?? new BN(0); - unclaimedRewards = planckBnToUnit(unclaimedRewards, network.units); - - // local bond value. - const [bond, setBond] = useState({ bond: freeBalance }); - - // bond valid. - const [bondValid, setBondValid] = useState<boolean>(false); - - // bond minus tx fees. - const enoughToCoverTxFees: boolean = - freeBalance - Number(bond.bond) > planckBnToUnit(largestTxFee, units); - - // bond value after max tx fees have been deducated. - let bondAfterTxFees: BN; - if (enoughToCoverTxFees) { - bondAfterTxFees = unitToPlanckBn(Number(bond.bond), units); - } else { - bondAfterTxFees = max( - unitToPlanckBn(Number(bond.bond), units).sub(largestTxFee), - new BN(0) - ); - } - - // update bond value on task change. - useEffect(() => { - const _bond = freeBalance; - setBond({ bond: _bond }); - }, [freeBalance]); - - // modal resize on form update - useEffect(() => { - setResize(); - }, [bond]); - - // determine whether this is a pool or staking transaction. - const determineTx = (bondToSubmit: string) => { - let tx = null; - if (!api) { - return tx; - } - if (isPooling) { - tx = api.tx.nominationPools.bondExtra({ - FreeBalance: bondToSubmit, - }); - } else if (isStaking) { - tx = api.tx.staking.bondExtra(bondToSubmit); - } - return tx; - }; - - // the actual bond tx to submit - const getTx = (bondToSubmit: string) => { - if (!bondValid || !activeAccount) { - return null; - } - return determineTx(bondToSubmit); - }; - - const { submitTx, submitting } = useSubmitExtrinsic({ - tx: getTx(bondAfterTxFees.toString()), - from: activeAccount, - shouldSubmit: bondValid, - callbackSubmit: () => { - setModalStatus(2); - }, - callbackInBlock: () => {}, - }); - - const warnings = []; - if (!accountHasSigner(activeAccount)) { - warnings.push('Your account is read only, and cannot sign transactions.'); - } - - return ( - <> - <div className="items"> - {unclaimedRewards > 0 && bondType === 'pool' && ( - <Warning - text={`Bonding will also withdraw your outstanding rewards of ${unclaimedRewards} ${network.unit}.`} - /> - )} - <BondFeedback - syncing={largestTxFee.eq(new BN(0))} - bondType={bondType} - listenIsValid={setBondValid} - defaultBond={null} - setLocalResize={setLocalResize} - setters={[ - { - set: setBond, - current: bond, - }, - ]} - warnings={warnings} - txFees={largestTxFee} - /> - <NotesWrapper> - <EstimatedTxFee /> - </NotesWrapper> - </div> - <FormFooter - setSection={setSection} - submitTx={submitTx} - submitting={submitting} - isValid={bondValid && accountHasSigner(activeAccount) && txFeesValid} - /> - </> - ); -}; diff --git a/src/modals/UpdateBond/Forms/FormFooter.tsx b/src/modals/UpdateBond/Forms/FormFooter.tsx deleted file mode 100644 index 31d4d1ac23..0000000000 --- a/src/modals/UpdateBond/Forms/FormFooter.tsx +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { faArrowAltCircleUp } from '@fortawesome/free-regular-svg-icons'; -import { faChevronLeft } from '@fortawesome/free-solid-svg-icons'; -import { ButtonInvert, ButtonSubmit } from '@rossbulat/polkadot-dashboard-ui'; -import { FooterWrapper } from '../../Wrappers'; - -export const FormFooter = ({ - setSection, - submitTx, - submitting, - isValid, -}: any) => { - const hasSections = setSection !== undefined; - - const handleSubmit = () => { - if (hasSections) { - setSection(0); - } - }; - - return ( - <FooterWrapper> - <div> - {hasSections && ( - <ButtonInvert - text="Back" - iconLeft={faChevronLeft} - onClick={() => handleSubmit()} - /> - )} - </div> - <div> - <ButtonSubmit - text={`Submit${submitting ? `ting` : ''}`} - iconLeft={faArrowAltCircleUp} - iconTransform="grow-2" - onClick={() => submitTx()} - disabled={submitting || !isValid} - /> - </div> - </FooterWrapper> - ); -}; diff --git a/src/modals/UpdateBond/Forms/UnbondAll.tsx b/src/modals/UpdateBond/Forms/UnbondAll.tsx deleted file mode 100644 index d07fcd5dce..0000000000 --- a/src/modals/UpdateBond/Forms/UnbondAll.tsx +++ /dev/null @@ -1,167 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { BN } from 'bn.js'; -import { useApi } from 'contexts/Api'; -import { useBalances } from 'contexts/Balances'; -import { useConnect } from 'contexts/Connect'; -import { useModal } from 'contexts/Modal'; -import { useActivePools } from 'contexts/Pools/ActivePools'; -import { useStaking } from 'contexts/Staking'; -import { useTransferOptions } from 'contexts/TransferOptions'; -import { useTxFees } from 'contexts/TxFees'; -import { EstimatedTxFee } from 'library/EstimatedTxFee'; -import { Warning } from 'library/Form/Warning'; -import { useSubmitExtrinsic } from 'library/Hooks/useSubmitExtrinsic'; -import { useEffect, useState } from 'react'; -import { planckBnToUnit, unitToPlanckBn } from 'Utils'; -import { NotesWrapper, Separator } from '../../Wrappers'; -import { FormsProps } from '../types'; -import { FormFooter } from './FormFooter'; - -export const UnbondAll = (props: FormsProps) => { - const { setSection } = props; - - const { api, network, consts } = useApi(); - const { units } = network; - const { setStatus: setModalStatus, setResize, config } = useModal(); - const { activeAccount, accountHasSigner } = useConnect(); - const { getControllerNotImported } = useStaking(); - const { getBondedAccount, getAccountNominations } = useBalances(); - const { bondType } = config; - const { getTransferOptions } = useTransferOptions(); - const { txFeesValid } = useTxFees(); - const { selectedActivePool } = useActivePools(); - - let { unclaimedRewards } = selectedActivePool || {}; - unclaimedRewards = unclaimedRewards ?? new BN(0); - unclaimedRewards = planckBnToUnit(unclaimedRewards, network.units); - - const controller = getBondedAccount(activeAccount); - const nominations = getAccountNominations(activeAccount); - const controllerNotImported = getControllerNotImported(controller); - - const { bondDuration } = consts; - const isStaking = bondType === 'stake'; - const isPooling = bondType === 'pool'; - - const allTransferOptions = getTransferOptions(activeAccount); - const { freeToUnbond: freeToUnbondBn } = isPooling - ? allTransferOptions.pool - : allTransferOptions.nominate; - - // convert BN values to number - const freeToUnbond = planckBnToUnit(freeToUnbondBn, units); - - // local bond value - const [bond, setBond] = useState({ - bond: freeToUnbond, - }); - - // bond valid - const [bondValid, setBondValid] = useState(false); - - // unbond all validation - const isValid = (() => { - let _isValid = false; - if (isPooling) { - _isValid = freeToUnbond > 0; - } else { - _isValid = - freeToUnbond > 0 && nominations.length === 0 && !controllerNotImported; - } - return _isValid; - })(); - - // update bond value on task change - useEffect(() => { - const _bond = freeToUnbond; - setBond({ bond: _bond }); - setBondValid(isValid); - }, [freeToUnbond, isValid]); - - // modal resize on form update - useEffect(() => { - setResize(); - }, [bond]); - - // tx to submit - const getTx = () => { - let tx = null; - if (!bondValid || !api || !activeAccount) { - return tx; - } - - // stake unbond: controller must be imported - if (isStaking && controllerNotImported) { - return tx; - } - // remove decimal errors - const bondToSubmit = unitToPlanckBn(bond.bond, units); - - // determine tx - if (isPooling) { - tx = api.tx.nominationPools.unbond(activeAccount, bondToSubmit); - } else if (isStaking) { - tx = api.tx.staking.unbond(bondToSubmit); - } - return tx; - }; - - const signingAccount = isPooling ? activeAccount : controller; - - const { submitTx, submitting } = useSubmitExtrinsic({ - tx: getTx(), - from: signingAccount, - shouldSubmit: bondValid, - callbackSubmit: () => { - setModalStatus(2); - }, - callbackInBlock: () => {}, - }); - - return ( - <> - <div className="items"> - <> - {!accountHasSigner(signingAccount) && ( - <Warning text="Your account is read only, and cannot sign transactions." /> - )} - {isStaking && controllerNotImported ? ( - <Warning text="You must have your controller account imported to unbond." /> - ) : ( - <></> - )} - {isStaking && nominations.length ? ( - <Warning text="Stop nominating before unbonding all funds." /> - ) : ( - <></> - )} - {unclaimedRewards > 0 && ( - <Warning - text={`Unbonding will also withdraw your outstanding rewards of ${unclaimedRewards} ${network.unit}.`} - /> - )} - <h4>Amount to unbond:</h4> - <h2> - {freeToUnbond} {network.unit} - </h2> - <Separator /> - <NotesWrapper> - <p> - Once unbonding, you must wait {bondDuration} eras for your funds - to become available. - </p> - {bondValid && <EstimatedTxFee />} - </NotesWrapper> - </> - </div> - <FormFooter - setSection={setSection} - submitTx={submitTx} - submitting={submitting} - isValid={bondValid && accountHasSigner(signingAccount) && txFeesValid} - /> - </> - ); -}; diff --git a/src/modals/UpdateBond/Forms/UnbondPoolToMinimum.tsx b/src/modals/UpdateBond/Forms/UnbondPoolToMinimum.tsx deleted file mode 100644 index 3fc37d70af..0000000000 --- a/src/modals/UpdateBond/Forms/UnbondPoolToMinimum.tsx +++ /dev/null @@ -1,134 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { BN } from 'bn.js'; -import { useApi } from 'contexts/Api'; -import { useConnect } from 'contexts/Connect'; -import { useModal } from 'contexts/Modal'; -import { useActivePools } from 'contexts/Pools/ActivePools'; -import { usePoolsConfig } from 'contexts/Pools/PoolsConfig'; -import { useTransferOptions } from 'contexts/TransferOptions'; -import { useTxFees } from 'contexts/TxFees'; -import { EstimatedTxFee } from 'library/EstimatedTxFee'; -import { Warning } from 'library/Form/Warning'; -import { useSubmitExtrinsic } from 'library/Hooks/useSubmitExtrinsic'; -import { useEffect, useState } from 'react'; -import { planckBnToUnit, unitToPlanckBn } from 'Utils'; -import { NotesWrapper, Separator } from '../../Wrappers'; -import { FormsProps } from '../types'; -import { FormFooter } from './FormFooter'; - -export const UnbondPoolToMinimum = (props: FormsProps) => { - const { setSection } = props; - - const { api, network, consts } = useApi(); - const { units } = network; - const { setStatus: setModalStatus, setResize } = useModal(); - const { activeAccount, accountHasSigner } = useConnect(); - const { isDepositor, selectedActivePool } = useActivePools(); - const { getTransferOptions } = useTransferOptions(); - const { stats } = usePoolsConfig(); - const { txFeesValid } = useTxFees(); - - let { unclaimedRewards } = selectedActivePool || {}; - unclaimedRewards = unclaimedRewards ?? new BN(0); - unclaimedRewards = planckBnToUnit(unclaimedRewards, network.units); - - const { minJoinBond, minCreateBond } = stats; - const { bondDuration } = consts; - - const { freeToUnbond: freeToUnbondBn } = - getTransferOptions(activeAccount).pool; - - // unbond amount to minimum threshold - const freeToUnbond = isDepositor() - ? planckBnToUnit( - BN.max(freeToUnbondBn.sub(minCreateBond), new BN(0)), - units - ) - : planckBnToUnit(BN.max(freeToUnbondBn.sub(minJoinBond), new BN(0)), units); - - // local bond value - const [bond, setBond] = useState({ - bond: freeToUnbond, - }); - - // bond valid - const [bondValid, setBondValid] = useState(false); - - // unbond all validation - const isValid = (() => { - return freeToUnbond > 0; - })(); - - // update bond value on task change - useEffect(() => { - const _bond = freeToUnbond; - setBond({ bond: _bond }); - setBondValid(isValid); - }, [freeToUnbond, isValid]); - - // modal resize on form update - useEffect(() => { - setResize(); - }, [bond]); - - // tx to submit - const getTx = () => { - let tx = null; - if (!bondValid || !api || !activeAccount) { - return tx; - } - - // remove decimal errors - const bondToSubmit = unitToPlanckBn(bond.bond, units); - - tx = api.tx.nominationPools.unbond(activeAccount, bondToSubmit); - return tx; - }; - - const { submitTx, submitting } = useSubmitExtrinsic({ - tx: getTx(), - from: activeAccount, - shouldSubmit: bondValid, - callbackSubmit: () => { - setModalStatus(2); - }, - callbackInBlock: () => {}, - }); - - return ( - <> - <div className="items"> - <> - {!accountHasSigner(activeAccount) && ( - <Warning text="Your account is read only, and cannot sign transactions." /> - )} - {unclaimedRewards > 0 && ( - <Warning - text={`Unbonding will also withdraw your outstanding rewards of ${unclaimedRewards} ${network.unit}.`} - /> - )} - <h4>Amount to unbond:</h4> - <h2> - {freeToUnbond} {network.unit} - </h2> - <Separator /> - <NotesWrapper> - <p> - Once unbonding, you must wait {bondDuration} eras for your funds - to become available. - </p> - {bondValid && <EstimatedTxFee />} - </NotesWrapper> - </> - </div> - <FormFooter - setSection={setSection} - submitTx={submitTx} - submitting={submitting} - isValid={bondValid && accountHasSigner(activeAccount) && txFeesValid} - /> - </> - ); -}; diff --git a/src/modals/UpdateBond/Forms/UnbondSome.tsx b/src/modals/UpdateBond/Forms/UnbondSome.tsx deleted file mode 100644 index b7547e1234..0000000000 --- a/src/modals/UpdateBond/Forms/UnbondSome.tsx +++ /dev/null @@ -1,170 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { BN } from 'bn.js'; -import { useApi } from 'contexts/Api'; -import { useBalances } from 'contexts/Balances'; -import { useConnect } from 'contexts/Connect'; -import { useModal } from 'contexts/Modal'; -import { useActivePools } from 'contexts/Pools/ActivePools'; -import { usePoolsConfig } from 'contexts/Pools/PoolsConfig'; -import { useStaking } from 'contexts/Staking'; -import { useTransferOptions } from 'contexts/TransferOptions'; -import { useTxFees } from 'contexts/TxFees'; -import { EstimatedTxFee } from 'library/EstimatedTxFee'; -import { UnbondFeedback } from 'library/Form/Unbond/UnbondFeedback'; -import { Warning } from 'library/Form/Warning'; -import { useSubmitExtrinsic } from 'library/Hooks/useSubmitExtrinsic'; -import { useEffect, useState } from 'react'; -import { planckBnToUnit, unitToPlanckBn } from 'Utils'; -import { NotesWrapper } from '../../Wrappers'; -import { FormsProps } from '../types'; -import { FormFooter } from './FormFooter'; - -export const UnbondSome = (props: FormsProps) => { - const { setSection } = props; - - const { api, network, consts } = useApi(); - const { units } = network; - const { setStatus: setModalStatus, setResize, config } = useModal(); - const { activeAccount, accountHasSigner } = useConnect(); - const { staking, getControllerNotImported } = useStaking(); - const { getBondedAccount } = useBalances(); - const { bondType } = config; - const { stats } = usePoolsConfig(); - const { isDepositor, selectedActivePool } = useActivePools(); - const { txFees, txFeesValid } = useTxFees(); - const { getTransferOptions } = useTransferOptions(); - - const controller = getBondedAccount(activeAccount); - const controllerNotImported = getControllerNotImported(controller); - const { minNominatorBond: minNominatorBondBn } = staking; - const { minJoinBond: minJoinBondBn, minCreateBond: minCreateBondBn } = stats; - const { bondDuration } = consts; - - let { unclaimedRewards } = selectedActivePool || {}; - unclaimedRewards = unclaimedRewards ?? new BN(0); - unclaimedRewards = planckBnToUnit(unclaimedRewards, network.units); - - const isStaking = bondType === 'stake'; - const isPooling = bondType === 'pool'; - - const allTransferOptions = getTransferOptions(activeAccount); - const { freeToUnbond: freeToUnbondBn } = isPooling - ? allTransferOptions.pool - : allTransferOptions.nominate; - - // convert BN values to number - const freeToUnbond = planckBnToUnit(freeToUnbondBn, units); - const minJoinBond = planckBnToUnit(minJoinBondBn, units); - const minCreateBond = planckBnToUnit(minCreateBondBn, units); - const minNominatorBond = planckBnToUnit(minNominatorBondBn, units); - - // local bond value - const [bond, setBond] = useState({ bond: freeToUnbond }); - - // bond valid - const [bondValid, setBondValid] = useState<boolean>(false); - - // get the max amount available to unbond - const freeToUnbondToMin = isPooling - ? isDepositor() - ? Math.max(freeToUnbond - minCreateBond, 0) - : Math.max(freeToUnbond - minJoinBond, 0) - : Math.max(freeToUnbond - minNominatorBond, 0); - - // unbond some validation - const isValid = isPooling ? true : !controllerNotImported; - - // update bond value on task change - useEffect(() => { - const _bond = freeToUnbondToMin; - setBond({ bond: _bond }); - - setBondValid(isValid); - }, [freeToUnbondToMin, isValid]); - - // modal resize on form update - useEffect(() => { - setResize(); - }, [bond]); - - // tx to submit - const getTx = () => { - let tx = null; - if (!bondValid || !api || !activeAccount) { - return tx; - } - // stake unbond: controller must be imported - if (isStaking && controllerNotImported) { - return tx; - } - // remove decimal errors - const bondToSubmit = unitToPlanckBn(bond.bond, units); - - // determine tx - if (isPooling) { - tx = api.tx.nominationPools.unbond(activeAccount, bondToSubmit); - } else if (isStaking) { - tx = api.tx.staking.unbond(bondToSubmit); - } - return tx; - }; - - const signingAccount = isPooling ? activeAccount : controller; - - const { submitTx, submitting } = useSubmitExtrinsic({ - tx: getTx(), - from: signingAccount, - shouldSubmit: bondValid, - callbackSubmit: () => { - setModalStatus(2); - }, - callbackInBlock: () => {}, - }); - - const warnings = []; - if (!accountHasSigner(activeAccount)) { - warnings.push('Your account is read only, and cannot sign transactions.'); - } - - return ( - <> - <div className="items"> - <> - {unclaimedRewards > 0 && bondType === 'pool' && ( - <Warning - text={`Unbonding will also withdraw your outstanding rewards of ${unclaimedRewards} ${network.unit}.`} - /> - )} - <UnbondFeedback - bondType={bondType} - listenIsValid={setBondValid} - defaultBond={freeToUnbondToMin} - setters={[ - { - set: setBond, - current: bond, - }, - ]} - warnings={warnings} - txFees={txFees} - /> - <NotesWrapper> - <p> - Once unbonding, you must wait {bondDuration} eras for your funds - to become available. - </p> - <EstimatedTxFee /> - </NotesWrapper> - </> - </div> - <FormFooter - setSection={setSection} - submitTx={submitTx} - submitting={submitting} - isValid={bondValid && accountHasSigner(signingAccount) && txFeesValid} - /> - </> - ); -}; diff --git a/src/modals/UpdateBond/Forms/index.tsx b/src/modals/UpdateBond/Forms/index.tsx deleted file mode 100644 index fecf4bc2a0..0000000000 --- a/src/modals/UpdateBond/Forms/index.tsx +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { forwardRef } from 'react'; -import { ContentWrapper } from '../Wrappers'; -import { BondAll } from './BondAll'; -import { BondSome } from './BondSome'; -import { UnbondAll } from './UnbondAll'; -import { UnbondPoolToMinimum } from './UnbondPoolToMinimum'; -import { UnbondSome } from './UnbondSome'; - -export const Forms = forwardRef((props: any, ref: any) => { - const { task } = props; - return ( - <ContentWrapper ref={ref}> - {task === 'bond_some' && <BondSome {...props} />} - {task === 'bond_all' && <BondAll {...props} />} - {task === 'unbond_some' && <UnbondSome {...props} />} - {task === 'unbond_all' && <UnbondAll {...props} />} - {task === 'unbond_pool_to_minimum' && <UnbondPoolToMinimum {...props} />} - </ContentWrapper> - ); -}); diff --git a/src/modals/UpdateBond/Tasks.tsx b/src/modals/UpdateBond/Tasks.tsx deleted file mode 100644 index 1e913a9b8c..0000000000 --- a/src/modals/UpdateBond/Tasks.tsx +++ /dev/null @@ -1,132 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { faChevronRight } from '@fortawesome/free-solid-svg-icons'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { useApi } from 'contexts/Api'; -import { useModal } from 'contexts/Modal'; -import { useActivePools } from 'contexts/Pools/ActivePools'; -import { usePoolsConfig } from 'contexts/Pools/PoolsConfig'; -import { forwardRef } from 'react'; -import { planckBnToUnit } from 'Utils'; -import { ContentWrapper } from './Wrappers'; - -export const Tasks = forwardRef((props: any, ref: any) => { - const { setSection, setTask, bondType } = props; - - const { network } = useApi(); - const { units, unit } = network; - const { config } = useModal(); - const { fn } = config; - const { isDepositor } = useActivePools(); - const { stats } = usePoolsConfig(); - const { minCreateBond, minJoinBond } = stats; - - const minJoinBondBase = planckBnToUnit(minJoinBond, units); - const minCreateBondBase = planckBnToUnit(minCreateBond, units); - - return ( - <ContentWrapper> - <div className="items" ref={ref}> - {fn === 'add' && ( - <> - <button - type="button" - className="action-button" - onClick={() => { - setSection(1); - setTask('bond_some'); - }} - > - <div> - <h3>Bond Extra</h3> - <p>Bond more {network.unit}.</p> - </div> - <div> - <FontAwesomeIcon transform="shrink-2" icon={faChevronRight} /> - </div> - </button> - <button - type="button" - className="action-button" - onClick={() => { - setSection(1); - setTask('bond_all'); - }} - > - <div> - <h3>Bond All</h3> - <p>Bond all available {network.unit}.</p> - </div> - <div> - <FontAwesomeIcon transform="shrink-2" icon={faChevronRight} /> - </div> - </button> - </> - )} - {fn === 'remove' && ( - <> - <button - type="button" - className="action-button" - onClick={() => { - setSection(1); - setTask('unbond_some'); - }} - > - <div> - <h3>Unbond</h3> - <p>Unbond some of your {network.unit}.</p> - </div> - <div> - <FontAwesomeIcon transform="shrink-2" icon={faChevronRight} /> - </div> - </button> - {bondType === 'stake' && ( - <button - type="button" - className="action-button" - onClick={() => { - setSection(1); - setTask('unbond_all'); - }} - > - <div> - <h3>Unbond All</h3> - <p>Exit your staking position.</p> - </div> - <div> - <FontAwesomeIcon transform="shrink-2" icon={faChevronRight} /> - </div> - </button> - )} - {bondType === 'pool' && ( - <button - type="button" - className="action-button" - onClick={() => { - setSection(1); - setTask('unbond_pool_to_minimum'); - }} - > - <div> - <h3>Unbond To Minimum</h3> - <p> - {isDepositor() - ? `Unbond up to the ${minCreateBondBase} ${unit} minimum bond for pool depositors.` - : `Unbond up to the ${minJoinBondBase} ${unit} minimum to maintain your pool membership`} - </p> - </div> - <div> - <FontAwesomeIcon transform="shrink-2" icon={faChevronRight} /> - </div> - </button> - )} - </> - )} - </div> - </ContentWrapper> - ); -}); - -export default Tasks; diff --git a/src/modals/UpdateBond/Wrappers.ts b/src/modals/UpdateBond/Wrappers.ts deleted file mode 100644 index 907bc1412f..0000000000 --- a/src/modals/UpdateBond/Wrappers.ts +++ /dev/null @@ -1,101 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { motion } from 'framer-motion'; -import styled from 'styled-components'; -import { backgroundToggle, buttonPrimaryBackground, textPrimary } from 'theme'; - -export const Wrapper = styled.div` - display: flex; - flex-flow: column wrap; - align-items: flex-start; - justify-content: flex-start; - padding: 0; -`; - -export const FixedContentWrapper = styled.div` - padding-top: 1rem; - width: 100%; -`; - -export const CardsWrapper = styled(motion.div)` - width: 200%; - display: flex; - flex-flow: row nowrap; - overflow: hidden; - position: relative; -`; - -export const ContentWrapper = styled.div` - border-radius: 1rem; - display: flex; - flex-flow: column nowrap; - flex-basis: 50%; - min-width: 50%; - height: auto; - flex: 1; - max-height: 100%; - padding: 0 1.25rem 1rem 1.25rem; - - .items { - position: relative; - padding: 0.5rem 0 1.5rem 0; - border-bottom: none; - width: auto; - border-radius: 0.75rem; - overflow: hidden; - overflow-y: auto; - z-index: 1; - width: 100%; - - h4 { - margin: 0.2rem 0; - } - h2 { - margin: 0.75rem 0; - } - - .action-button { - background: ${buttonPrimaryBackground}; - padding: 1rem; - cursor: pointer; - margin-bottom: 1rem; - border-radius: 0.75rem; - display: flex; - flex-flow: row wrap; - justify-content: flex-start; - align-items: center; - transition: all 0.15s; - width: 100%; - - &:last-child { - margin-bottom: 0; - } - - h3, - p { - text-align: left; - margin: 0; - } - h3 { - margin-bottom: 0.5rem; - } - > *:last-child { - flex: 1; - display: flex; - flex-flow: row wrap; - justify-content: flex-end; - } - &:hover { - background: ${backgroundToggle}; - } - .icon { - margin-right: 0.5rem; - } - p { - color: ${textPrimary}; - font-size: 1rem; - } - } - } -`; diff --git a/src/modals/UpdateBond/index.tsx b/src/modals/UpdateBond/index.tsx deleted file mode 100644 index 10d78d25bc..0000000000 --- a/src/modals/UpdateBond/index.tsx +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { faMinus, faPlus } from '@fortawesome/free-solid-svg-icons'; -import { useModal } from 'contexts/Modal'; -import { Title } from 'library/Modal/Title'; -import { useEffect, useRef, useState } from 'react'; -import { Forms } from './Forms'; -import { Tasks } from './Tasks'; -import { CardsWrapper, FixedContentWrapper, Wrapper } from './Wrappers'; - -export const UpdateBond = () => { - const { config, setModalHeight } = useModal(); - const { fn, bondType } = config; - - // modal task - const [task, setTask] = useState(null); - - // active modal section - const [section, setSection] = useState(0); - - // increment to resize modal - const [localResize, _setLocalResize] = useState(0); - const setLocalResize = () => { - _setLocalResize(localResize + 1); - }; - - // refs for wrappers - const headerRef = useRef<HTMLDivElement>(null); - const tasksRef = useRef<HTMLDivElement>(null); - const formsRef = useRef<HTMLDivElement>(null); - - // resize modal on state change - useEffect(() => { - let _height = headerRef.current?.clientHeight ?? 0; - if (section === 0) { - _height += tasksRef.current?.clientHeight ?? 0; - } else { - _height += formsRef.current?.clientHeight ?? 0; - } - setModalHeight(_height); - }, [section, task, localResize]); - - return ( - <Wrapper> - <FixedContentWrapper ref={headerRef}> - <Title - title={`${fn === 'add' ? 'Add To' : 'Remove'} Bond`} - icon={fn === 'add' ? faPlus : faMinus} - fixed - /> - </FixedContentWrapper> - <CardsWrapper - animate={section === 0 ? 'home' : 'next'} - transition={{ - duration: 0.5, - type: 'spring', - bounce: 0.1, - }} - variants={{ - home: { - left: 0, - }, - next: { - left: '-100%', - }, - }} - > - <Tasks - bondType={bondType} - setSection={setSection} - setTask={setTask} - ref={tasksRef} - /> - <Forms - section={section} - setSection={setSection} - task={task} - ref={formsRef} - bondType={bondType} - setLocalResize={setLocalResize} - /> - </CardsWrapper> - </Wrapper> - ); -}; - -export default UpdateBond; diff --git a/src/modals/UpdateBond/types.ts b/src/modals/UpdateBond/types.ts deleted file mode 100644 index b9e7579281..0000000000 --- a/src/modals/UpdateBond/types.ts +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -export interface FormsProps { - section?: number; - setSection?: string; - setLocalResize?: () => void; -} diff --git a/src/modals/UpdateController/Switch/Wrappers.ts b/src/modals/UpdateController/Switch/Wrappers.ts new file mode 100644 index 0000000000..105de3bec6 --- /dev/null +++ b/src/modals/UpdateController/Switch/Wrappers.ts @@ -0,0 +1,68 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import styled from 'styled-components'; + +export const StyledSelect = styled.div` + position: relative; + width: 100%; + height: auto; + overflow: hidden; + + .label { + margin: 0.25rem 0 0.75rem 0; + } + .current { + flex: 1; + display: flex; + align-items: center; + margin-bottom: 1rem; + + > span { + color: var(--text-color-secondary); + margin: 0 0.75rem; + opacity: 0.5; + } + } + + /* input element of dropdown */ + .input-wrap { + border-bottom: 1px solid var(--border-primary-color); + display: flex; + flex-flow: row wrap; + align-items: center; + padding: 0.25rem 0 0 0; + margin: 0.25rem 0rem 0 0rem; + flex: 1; + + &.selected { + margin: 0; + padding: 0.1rem 0.75rem; + } + } + + /* input element of dropdown */ + .input { + border: none; + padding-left: 0.75rem; + flex: 1; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + } +`; + +export const StyledController = styled.button<any>` + color: var(--text-color-primary); + border: none; + position: absolute; + right: 0.5rem; + top: 0.4rem; + width: 2.2rem; + height: 2.2rem; + display: flex; + flex-flow: column wrap; + justify-content: center; + align-items: center; + border-radius: 0.5rem; +`; diff --git a/src/modals/UpdateController/Switch/index.tsx b/src/modals/UpdateController/Switch/index.tsx new file mode 100644 index 0000000000..fa9c33bde3 --- /dev/null +++ b/src/modals/UpdateController/Switch/index.tsx @@ -0,0 +1,50 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { faAnglesRight } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { remToUnit } from '@polkadot-cloud/utils'; +import { Polkicon } from '@polkadot-cloud/react'; +import { useImportedAccounts } from 'contexts/Connect/ImportedAccounts'; +import type { AccountDropdownProps } from '../../../library/Form/types'; +import { StyledSelect } from './Wrappers'; + +export const Switch = ({ current, to }: AccountDropdownProps) => { + const { getAccount } = useImportedAccounts(); + const toAccount = getAccount(to); + + return ( + <StyledSelect> + <div> + <div className="current"> + <div className="input-wrap selected"> + {toAccount !== null && ( + <Polkicon + address={current?.address ?? ''} + size={remToUnit('2rem')} + /> + )} + <input className="input" disabled value={current?.name ?? ''} /> + </div> + <span> + <FontAwesomeIcon icon={faAnglesRight} /> + </span> + + <div className="input-wrap selected"> + {current?.active ? ( + <Polkicon + address={toAccount?.address ?? ''} + size={remToUnit('2rem')} + /> + ) : undefined} + <input + className="input" + disabled + value={toAccount?.address ? toAccount?.name : '...'} + /> + </div> + </div> + </div> + </StyledSelect> + ); +}; diff --git a/src/modals/UpdateController/Wrapper.ts b/src/modals/UpdateController/Wrapper.ts index ea69b145b9..48b0652194 100644 --- a/src/modals/UpdateController/Wrapper.ts +++ b/src/modals/UpdateController/Wrapper.ts @@ -1,14 +1,13 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only import styled from 'styled-components'; export const Wrapper = styled.div` display: flex; flex-flow: column wrap; - align-items: flex-start; - justify-content: flex-start; - padding: 1rem 0.25rem; + padding: 0rem 0.25rem; + width: 100%; .form { width: 100%; @@ -16,5 +15,3 @@ export const Wrapper = styled.div` overflow: hidden; } `; - -export default Wrapper; diff --git a/src/modals/UpdateController/index.tsx b/src/modals/UpdateController/index.tsx index f9b426eb8e..b4de652bed 100644 --- a/src/modals/UpdateController/index.tsx +++ b/src/modals/UpdateController/index.tsx @@ -1,121 +1,86 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import { faArrowAltCircleUp } from '@fortawesome/free-regular-svg-icons'; -import { faExchangeAlt } from '@fortawesome/free-solid-svg-icons'; -import { ButtonSubmit } from '@rossbulat/polkadot-dashboard-ui'; +import { ModalPadding, ModalWarnings } from '@polkadot-cloud/react'; +import { useTranslation } from 'react-i18next'; import { useApi } from 'contexts/Api'; -import { useBalances } from 'contexts/Balances'; -import { useConnect } from 'contexts/Connect'; -import { ImportedAccount } from 'contexts/Connect/types'; -import { useModal } from 'contexts/Modal'; -import { useTxFees } from 'contexts/TxFees'; -import { EstimatedTxFee } from 'library/EstimatedTxFee'; -import { AccountDropdown } from 'library/Form/AccountDropdown'; -import { InputItem } from 'library/Form/types'; -import { getEligibleControllers } from 'library/Form/Utils/getEligibleControllers'; +import { useBonded } from 'contexts/Bonded'; import { Warning } from 'library/Form/Warning'; +import { useSignerWarnings } from 'library/Hooks/useSignerWarnings'; import { useSubmitExtrinsic } from 'library/Hooks/useSubmitExtrinsic'; -import { Title } from 'library/Modal/Title'; -import { useEffect, useState } from 'react'; -import { FooterWrapper, NotesWrapper } from '../Wrappers'; -import Wrapper from './Wrapper'; +import { Close } from 'library/Modal/Close'; +import { SubmitTx } from 'library/SubmitTx'; +import { useTxMeta } from 'contexts/TxMeta'; +import { useEffect } from 'react'; +import { useOverlay } from '@polkadot-cloud/react/hooks'; +import { useActiveAccounts } from 'contexts/ActiveAccounts'; +import { useImportedAccounts } from 'contexts/Connect/ImportedAccounts'; +import { Switch } from './Switch'; +import { Wrapper } from './Wrapper'; export const UpdateController = () => { + const { t } = useTranslation('modals'); const { api } = useApi(); - const { setStatus: setModalStatus } = useModal(); - const { activeAccount, getAccount, accountHasSigner } = useConnect(); - const { getBondedAccount } = useBalances(); - const { txFeesValid } = useTxFees(); + const { notEnoughFunds } = useTxMeta(); + const { getBondedAccount } = useBonded(); + const { activeAccount } = useActiveAccounts(); + const { getAccount } = useImportedAccounts(); + const { getSignerWarnings } = useSignerWarnings(); + const { setModalStatus, setModalResize } = useOverlay().modal; const controller = getBondedAccount(activeAccount); const account = getAccount(controller); - // the selected value in the form - const [selected, setSelected] = useState<ImportedAccount | null>(null); - - // get eligible controller accounts - const items = getEligibleControllers(); - - // reset selected value on account change - useEffect(() => { - setSelected(null); - }, [activeAccount, items]); - - // handle account selection change - const handleOnChange = ({ selectedItem }: { selectedItem: InputItem }) => { - setSelected(selectedItem); - }; - // tx to submit const getTx = () => { let tx = null; - if (!selected || !api) { + if (!api) { return tx; } - const controllerToSubmit = { - Id: selected?.address ?? '', - }; - tx = api.tx.staking.setController(controllerToSubmit); + tx = api.tx.staking.setController(); return tx; }; + useEffect(() => setModalResize(), [notEnoughFunds]); + // handle extrinsic - const { submitTx, submitting } = useSubmitExtrinsic({ + const submitExtrinsic = useSubmitExtrinsic({ tx: getTx(), from: activeAccount, shouldSubmit: true, callbackSubmit: () => { - setModalStatus(2); + setModalStatus('closing'); }, callbackInBlock: () => {}, }); + const warnings = getSignerWarnings( + activeAccount, + false, + submitExtrinsic.proxySupported + ); + return ( <> - <Title - title="Change Controller Account" - icon={faExchangeAlt} - helpKey="Controller Account Eligibility" - /> - <Wrapper> - <div style={{ padding: '0 1rem', width: '100%' }}> - <div style={{ marginBottom: '1.5rem' }}> - {!accountHasSigner(activeAccount) && ( - <Warning text="Your stash account is read only and cannot sign transactions." /> - )} - </div> - <AccountDropdown - items={items} - onChange={handleOnChange} - placeholder="Search Account" - current={account} - value={selected} - height="17rem" - /> - <NotesWrapper> - <EstimatedTxFee /> - </NotesWrapper> - <FooterWrapper> - <div> - <ButtonSubmit - text={`Submit${submitting ? 'ting' : ''}`} - iconLeft={faArrowAltCircleUp} - iconTransform="grow-2" - onClick={() => submitTx()} - disabled={ - selected === null || - submitting || - !accountHasSigner(activeAccount) || - !txFeesValid - } - /> + <Close /> + <ModalPadding> + <h2 className="title unbounded">{t('changeControllerAccount')}</h2> + <Wrapper> + <div style={{ width: '100%' }}> + <div style={{ marginBottom: '1.5rem' }}> + {warnings.length > 0 ? ( + <ModalWarnings withMargin> + {warnings.map((text, i) => ( + <Warning key={`warning${i}`} text={text} /> + ))} + </ModalWarnings> + ) : null} </div> - </FooterWrapper> - </div> - </Wrapper> + <Switch current={account} to={activeAccount} /> + </div> + </Wrapper> + </ModalPadding> + <SubmitTx valid={activeAccount !== null} {...submitExtrinsic} /> </> ); }; - -export default UpdateController; diff --git a/src/modals/UpdatePayee/index.tsx b/src/modals/UpdatePayee/index.tsx index bdfd2b88dc..2b4865c34f 100644 --- a/src/modals/UpdatePayee/index.tsx +++ b/src/modals/UpdatePayee/index.tsx @@ -1,131 +1,167 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import { faArrowAltCircleUp } from '@fortawesome/free-regular-svg-icons'; -import { faWallet } from '@fortawesome/free-solid-svg-icons'; -import { ButtonSubmit } from '@rossbulat/polkadot-dashboard-ui'; -import { PayeeStatus } from 'consts'; +import { ModalPadding, ModalWarnings } from '@polkadot-cloud/react'; +import { isValidAddress } from '@polkadot-cloud/utils'; +import { useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; import { useApi } from 'contexts/Api'; -import { useBalances } from 'contexts/Balances'; -import { useConnect } from 'contexts/Connect'; -import { useModal } from 'contexts/Modal'; +import { useBonded } from 'contexts/Bonded'; +import type { PayeeConfig, PayeeOptions } from 'contexts/Setup/types'; import { useStaking } from 'contexts/Staking'; -import { useTxFees } from 'contexts/TxFees'; -import { EstimatedTxFee } from 'library/EstimatedTxFee'; -import { Dropdown } from 'library/Form/Dropdown'; import { Warning } from 'library/Form/Warning'; +import { usePayeeConfig } from 'library/Hooks/usePayeeConfig'; +import { useSignerWarnings } from 'library/Hooks/useSignerWarnings'; import { useSubmitExtrinsic } from 'library/Hooks/useSubmitExtrinsic'; import { Title } from 'library/Modal/Title'; -import { useEffect, useState } from 'react'; -import { FooterWrapper, PaddingWrapper } from '../Wrappers'; +import { PayeeInput } from 'library/PayeeInput'; +import { SelectItems } from 'library/SelectItems'; +import { SelectItem } from 'library/SelectItems/Item'; +import { SubmitTx } from 'library/SubmitTx'; +import type { MaybeAddress } from 'types'; +import { useTxMeta } from 'contexts/TxMeta'; +import { useOverlay } from '@polkadot-cloud/react/hooks'; +import { useActiveAccounts } from 'contexts/ActiveAccounts'; export const UpdatePayee = () => { + const { t } = useTranslation('modals'); const { api } = useApi(); - const { activeAccount } = useConnect(); - const { getBondedAccount } = useBalances(); - const { setStatus: setModalStatus } = useModal(); - const controller = getBondedAccount(activeAccount); - const { staking, getControllerNotImported } = useStaking(); - const { txFeesValid } = useTxFees(); + const { staking } = useStaking(); + const { activeAccount } = useActiveAccounts(); + const { notEnoughFunds } = useTxMeta(); + const { getBondedAccount } = useBonded(); + const { getPayeeItems } = usePayeeConfig(); + const { getSignerWarnings } = useSignerWarnings(); + const { setModalStatus, setModalResize } = useOverlay().modal; + const controller = getBondedAccount(activeAccount); const { payee } = staking; - const _selected: any = PayeeStatus.find((item) => item.key === payee); - const [selected, setSelected]: any = useState(null); + const DefaultSelected: PayeeConfig = { + destination: null, + account: null, + }; + + // Store the current user-inputted custom payout account. + const [account, setAccount] = useState<MaybeAddress>(payee.account); - // reset selected value on account change - useEffect(() => { - setSelected(null); - }, [activeAccount]); + // Store the currently selected payee option. + const [selected, setSelected]: any = useState<PayeeConfig>(DefaultSelected); - // ensure selected key is valid - useEffect(() => { - const exists = PayeeStatus.find((item) => item.key === selected?.key); - setValid(exists !== undefined); - }, [selected]); + // update setup progress with payee config. + const handleChangeDestination = (destination: PayeeOptions) => { + setSelected({ + destination, + account: isValidAddress(account || '') ? account : null, + }); + }; - const handleOnChange = ({ selectedItem }: any) => { - setSelected(selectedItem); + // update setup progress with payee account. + const handleChangeAccount = (newAccount: MaybeAddress) => { + setSelected({ + destination: selected?.destination ?? null, + account: newAccount, + }); }; - // bond valid - const [valid, setValid] = useState<boolean>(false); + // determine whether this section is completed. + const isComplete = () => + selected.destination !== null && + !(selected.destination === 'Account' && selected.account === null); - // tx to submit + // Tx to submit. const getTx = () => { let tx = null; - if (!api || !valid) { + if (!api) { return tx; } - tx = api.tx.staking.setPayee(selected.key); + const payeeToSubmit = !isComplete() + ? 'Staked' + : selected.destination === 'Account' + ? { + Account: selected.account, + } + : selected.destination; + + tx = api.tx.staking.setPayee(payeeToSubmit); return tx; }; - const { submitTx, submitting } = useSubmitExtrinsic({ + const submitExtrinsic = useSubmitExtrinsic({ tx: getTx(), from: controller, - shouldSubmit: valid, + shouldSubmit: isComplete(), callbackSubmit: () => { - setModalStatus(2); + setModalStatus('closing'); }, callbackInBlock: () => {}, }); - // remove active payee option from selectable items - const payeeItems = PayeeStatus.filter((item) => { - return item.key !== _selected.key; - }); + // Reset selected value on account change. + useEffect(() => { + setSelected(DefaultSelected); + }, [activeAccount]); + + // Inject default value after component mount. + useEffect(() => { + const initialSelected = getPayeeItems(true).find( + (item) => item.value === payee.destination + ); + setSelected( + initialSelected + ? { + destination: initialSelected.value, + account, + } + : DefaultSelected + ); + }, []); + + useEffect(() => setModalResize(), [notEnoughFunds]); + + const warnings = getSignerWarnings( + activeAccount, + true, + submitExtrinsic.proxySupported + ); return ( <> <Title - title="Update Reward Destination" - icon={faWallet} - helpKey="Reward Destination" + title={t('updatePayoutDestination')} + helpKey="Payout Destination" /> - <PaddingWrapper verticalOnly> - <div - style={{ - padding: '0 1.25rem', - marginTop: '1rem', - width: '100%', - }} - > - {getControllerNotImported(controller) && ( - <Warning text="You must have your controller account imported to update your reward destination" /> - )} - <Dropdown - items={payeeItems} - onChange={handleOnChange} - placeholder="Reward Destination" - value={selected} - current={_selected} - height="17rem" + <ModalPadding style={{ paddingBottom: 0 }}> + {warnings.length > 0 ? ( + <ModalWarnings withMargin> + {warnings.map((text, i) => ( + <Warning key={`warning${i}`} text={text} /> + ))} + </ModalWarnings> + ) : null} + <div style={{ width: '100%', padding: '0 0.5rem' }}> + <PayeeInput + payee={selected} + account={account} + setAccount={setAccount} + handleChange={handleChangeAccount} /> - <div style={{ marginTop: '1rem' }}> - <EstimatedTxFee /> - </div> - <FooterWrapper> - <div> - <ButtonSubmit - text={`Submit${submitting ? 'ting' : ''}`} - iconLeft={faArrowAltCircleUp} - iconTransform="grow-2" - onClick={() => submitTx()} - disabled={ - !valid || - submitting || - getControllerNotImported(controller) || - !txFeesValid - } - /> - </div> - </FooterWrapper> </div> - </PaddingWrapper> + <SelectItems> + {getPayeeItems(true).map((item) => ( + <SelectItem + key={`payee_option_${item.value}`} + account={account} + setAccount={setAccount} + selected={selected.destination === item.value} + onClick={() => handleChangeDestination(item.value)} + {...item} + /> + ))} + </SelectItems> + </ModalPadding> + <SubmitTx fromController valid={isComplete()} {...submitExtrinsic} /> </> ); }; - -export default UpdatePayee; diff --git a/src/modals/UpdateReserve/index.tsx b/src/modals/UpdateReserve/index.tsx new file mode 100644 index 0000000000..07db63a5a5 --- /dev/null +++ b/src/modals/UpdateReserve/index.tsx @@ -0,0 +1,153 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { faLock } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { + ButtonHelp, + ButtonPrimaryInvert, + ModalPadding, +} from '@polkadot-cloud/react'; +import { planckToUnit, unitToPlanck } from '@polkadot-cloud/utils'; +import BigNumber from 'bignumber.js'; +import Slider from 'rc-slider'; +import { useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useHelp } from 'contexts/Help'; +import { useTransferOptions } from 'contexts/TransferOptions'; +import { CardHeaderWrapper } from 'library/Card/Wrappers'; +import { Close } from 'library/Modal/Close'; +import { Title } from 'library/Modal/Title'; +import { SliderWrapper } from 'modals/ManagePool/Wrappers'; +import 'rc-slider/assets/index.css'; +import { useOverlay } from '@polkadot-cloud/react/hooks'; +import { useNetwork } from 'contexts/Network'; +import { useActiveAccounts } from 'contexts/ActiveAccounts'; +import { useImportedAccounts } from 'contexts/Connect/ImportedAccounts'; + +export const UpdateReserve = () => { + const { t } = useTranslation('modals'); + const { + network, + networkData: { units, unit }, + } = useNetwork(); + const { openHelp } = useHelp(); + const { setModalStatus } = useOverlay().modal; + const { activeAccount } = useActiveAccounts(); + const { accountHasSigner } = useImportedAccounts(); + const { feeReserve, setFeeReserveBalance, getTransferOptions } = + useTransferOptions(); + + const { edReserved } = getTransferOptions(activeAccount); + const minReserve = planckToUnit(edReserved, units); + const maxReserve = minReserve.plus( + ['polkadot', 'westend'].includes(network) ? 3 : 1 + ); + + const [sliderReserve, setSliderReserve] = useState<number>( + planckToUnit(feeReserve, units).plus(minReserve).decimalPlaces(3).toNumber() + ); + + const sliderProps = { + trackStyle: { + backgroundColor: 'var(--accent-color-primary)', + }, + handleStyle: { + backgroundColor: 'var(--background-primary)', + borderColor: 'var(--accent-color-primary)', + opacity: 1, + }, + }; + + const handleChange = (val: BigNumber) => { + // deduct ED from reserve amount. + val = val.decimalPlaces(3); + const actualReserve = BigNumber.max(val.minus(minReserve), 0).toNumber(); + const actualReservePlanck = unitToPlanck(actualReserve.toString(), units); + setSliderReserve(val.decimalPlaces(3).toNumber()); + setFeeReserveBalance(actualReservePlanck); + }; + + return ( + <> + <Close /> + <ModalPadding> + <Title + title={t('reserveBalance')} + helpKey="Reserve Balance" + style={{ padding: '0.5rem 0 0 0' }} + /> + <SliderWrapper style={{ marginTop: '1rem' }}> + <p>{t('reserveText', { unit })}</p> + <div> + <div className="slider no-value"> + <Slider + min={0} + max={maxReserve.toNumber()} + value={sliderReserve} + step={0.01} + onChange={(val) => { + if (typeof val === 'number' && val >= minReserve.toNumber()) { + handleChange(new BigNumber(val)); + } + }} + {...sliderProps} + /> + </div> + </div> + + <div className="stats"> + <CardHeaderWrapper> + <h4> + {t('reserveForExistentialDeposit')} + <FontAwesomeIcon + icon={faLock} + transform="shrink-3" + style={{ marginLeft: '0.5rem' }} + /> + </h4> + <h2> + {minReserve.isZero() ? ( + <> + {t('none')} + <ButtonHelp + onClick={() => + openHelp('Reserve Balance For Existential Deposit') + } + style={{ marginLeft: '0.65rem' }} + /> + </> + ) : ( + `${minReserve.decimalPlaces(4).toString()} ${unit}` + )} + </h2> + </CardHeaderWrapper> + + <CardHeaderWrapper> + <h4>{t('reserveForTxFees')}</h4> + <h2> + {BigNumber.max( + new BigNumber(sliderReserve) + .minus(minReserve) + .decimalPlaces(4) + .toString(), + 0 + ).toString()} +   + {unit} + </h2> + </CardHeaderWrapper> + </div> + + <div className="done"> + <ButtonPrimaryInvert + text={t('done')} + onClick={() => setModalStatus('closing')} + disabled={!accountHasSigner(activeAccount)} + /> + </div> + </SliderWrapper> + </ModalPadding> + </> + ); +}; diff --git a/src/modals/Utils/StaticNote.tsx b/src/modals/Utils/StaticNote.tsx new file mode 100644 index 0000000000..d63e7f227b --- /dev/null +++ b/src/modals/Utils/StaticNote.tsx @@ -0,0 +1,33 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import type { AnyJson } from 'types'; + +interface StaticNoteProps { + value: string; + tKey: string; + valueKey: string; + deps?: AnyJson[]; +} + +// Static notes store a single piece of text that is not updated after the initial render unless +// `deps` change. Deps should only change when syncing is complete. +export const StaticNote = ({ + value, + tKey, + valueKey, + deps = [], +}: StaticNoteProps) => { + const { t } = useTranslation('modals'); + + // store the next to be displayed. + const [staticText, setStaticText] = useState<string>(value); + + useEffect(() => { + setStaticText(value); + }, [...deps]); + + return <p>{t(tKey, { [valueKey]: staticText })}</p>; +}; diff --git a/src/modals/ValidatorGeo/index.tsx b/src/modals/ValidatorGeo/index.tsx new file mode 100644 index 0000000000..bde83a430b --- /dev/null +++ b/src/modals/ValidatorGeo/index.tsx @@ -0,0 +1,147 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { ButtonHelp, Polkicon } from '@polkadot-cloud/react'; +import { ellipsisFn } from '@polkadot-cloud/utils'; +import { useEffect, useState, useRef } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useHelp } from 'contexts/Help'; +import { CardHeaderWrapper, CardWrapper } from 'library/Card/Wrappers'; +import { GeoDonut } from 'library/Graphs/GeoDonut'; +import { formatSize } from 'library/Graphs/Utils'; +import { GraphWrapper } from 'library/Graphs/Wrapper'; +import { useSize } from 'library/Hooks/useSize'; +import { Title } from 'library/Modal/Title'; +import { StatusLabel } from 'library/StatusLabel'; +import type { ValidatorDetail } from '@polkawatch/ddp-client'; +import { useOverlay } from '@polkadot-cloud/react/hooks'; +import { PluginLabel } from 'library/PluginLabel'; +import { usePolkawatchApi } from 'contexts/Plugins/Polkawatch'; +import { usePlugins } from 'contexts/Plugins'; + +export const ValidatorGeo = () => { + const { t } = useTranslation('modals'); + const { pwApi, networkSupported } = usePolkawatchApi(); + const { options } = useOverlay().modal.config; + const { address, identity } = options; + const { openHelp } = useHelp(); + + const ref = useRef<HTMLDivElement>(null); + const size = useSize(ref.current); + const { height, minHeight } = formatSize(size, 300); + const [pwData, setPwData] = useState({} as ValidatorDetail); + const [analyticsAvailable, setAnalyticsAvailable] = useState(true); + const { pluginEnabled } = usePlugins(); + + const enabled = pluginEnabled('polkawatch'); + + // In Small Screens we will display the most relevant chart. + // For now, we are not going to complicate the UI. + const isSmallScreen = window.innerWidth <= 650; + const chartWidth = '330px'; + + useEffect(() => { + if (networkSupported && enabled) + pwApi + .ddpIpfsValidatorDetail({ + lastDays: 60, + validator: address, + validationType: 'public', + }) + .then((response) => { + setAnalyticsAvailable(true); + setPwData(response.data); + }) + .catch(() => setAnalyticsAvailable(false)); + else setAnalyticsAvailable(false); + return () => {}; + }, [pwApi, address]); + + return ( + <> + <Title title={t('validatorDecentralization')} /> + <div className="header"> + <Polkicon address={address} size={33} /> + <h2> +    + {identity === null ? ellipsisFn(address) : identity} + </h2> + </div> + <div + className="body" + style={{ position: 'relative', marginTop: '0.5rem' }} + > + <PluginLabel plugin="polkawatch" /> + <CardWrapper + className="transparent" + style={{ + margin: '0 0 0 0.5rem', + height: 350, + }} + > + <CardHeaderWrapper $withMargin> + <h4> + {t('rewardsByCountryAndNetwork')}{' '} + <ButtonHelp + marginLeft + onClick={() => openHelp('Rewards By Country And Network')} + /> + </h4> + </CardHeaderWrapper> + <div + ref={ref} + style={{ + minHeight, + display: 'flex', + justifyContent: 'space-evenly', + }} + > + {!enabled || analyticsAvailable ? ( + <StatusLabel + status="active_service" + statusFor="polkawatch" + title={t('polkawatchDisabled')} + /> + ) : ( + <StatusLabel + status="no_analytic_data" + title={ + networkSupported + ? t('decentralizationAnalyticsNotAvailable') + : t('decentralizationAnalyticsNotSupported') + } + /> + )} + <GraphWrapper + style={{ + height: `${height}px`, + }} + > + <GeoDonut + title={t('rewards')} + series={pwData.topCountryDistributionChart} + height={`${height}px`} + width={chartWidth} + /> + </GraphWrapper> + + <div style={{ display: isSmallScreen ? 'none' : 'block' }}> + <GraphWrapper + style={{ + height: `${height}px`, + }} + > + <GeoDonut + title={t('rewards')} + series={pwData.topNetworkDistributionChart} + height={`${height}px`} + width={chartWidth} + /> + </GraphWrapper> + </div> + </div> + </CardWrapper> + </div> + </> + ); +}; diff --git a/src/modals/ValidatorMetrics/index.tsx b/src/modals/ValidatorMetrics/index.tsx index ec29b97162..2b2ff9d078 100644 --- a/src/modals/ValidatorMetrics/index.tsx +++ b/src/modals/ValidatorMetrics/index.tsx @@ -1,61 +1,65 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import { BN } from 'bn.js'; -import { useApi } from 'contexts/Api'; +import { ButtonHelp, ModalPadding, Polkicon } from '@polkadot-cloud/react'; +import { ellipsisFn, planckToUnit } from '@polkadot-cloud/utils'; +import BigNumber from 'bignumber.js'; import { useCereStats } from 'contexts/CereStats'; -import { useModal } from 'contexts/Modal'; -import { useNetworkMetrics } from 'contexts/Network'; +import { useEffect, useState, useRef } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useHelp } from 'contexts/Help'; +import { useNetworkMetrics } from 'contexts/NetworkMetrics'; import { useStaking } from 'contexts/Staking'; +import { CardHeaderWrapper, CardWrapper } from 'library/Card/Wrappers'; import { EraPoints as EraPointsGraph } from 'library/Graphs/EraPoints'; -import { formatSize, useSize } from 'library/Graphs/Utils'; -import { GraphWrapper } from 'library/Graphs/Wrappers'; -import Identicon from 'library/Identicon'; +import { formatSize } from 'library/Graphs/Utils'; +import { GraphWrapper } from 'library/Graphs/Wrapper'; +import { useSize } from 'library/Hooks/useSize'; import { Title } from 'library/Modal/Title'; -import { StatsWrapper, StatWrapper } from 'library/Modal/Wrappers'; -import { OpenHelpIcon } from 'library/OpenHelpIcon'; +import { StatWrapper, StatsWrapper } from 'library/Modal/Wrappers'; import { StatusLabel } from 'library/StatusLabel'; -import { SubscanButton } from 'library/SubscanButton'; -import { PaddingWrapper } from 'modals/Wrappers'; -import React, { useEffect, useState } from 'react'; -import { clipAddress, humanNumber, planckBnToUnit, rmCommas } from 'Utils'; +import { useOverlay } from '@polkadot-cloud/react/hooks'; +import { PluginLabel } from 'library/PluginLabel'; +import { useNetwork } from 'contexts/Network'; +import type { AnyJson } from 'types'; export const ValidatorMetrics = () => { + const { t } = useTranslation('modals'); const { - network: { units, unit }, - } = useApi(); - const { config } = useModal(); - const { address, identity } = config; - const { fetchEraPoints }: any = useCereStats(); - const { metrics } = useNetworkMetrics(); - const { eraStakers } = useStaking(); - const { stakers } = eraStakers; + networkData: { units, unit }, + } = useNetwork(); + const { fetchEraPoints } = useCereStats(); + const { options } = useOverlay().modal.config; + const { address, identity } = options; + const { activeEra } = useNetworkMetrics(); + const { + eraStakers: { stakers }, + } = useStaking(); + const { openHelp } = useHelp(); // is the validator in the active era - const validatorInEra = - stakers.find((s: any) => s.address === address) || null; + const validatorInEra = stakers.find((s) => s.address === address) || null; - let ownStake = new BN(0); - let otherStake = new BN(0); + let validatorOwnStake = new BigNumber(0); + let otherStake = new BigNumber(0); if (validatorInEra) { const { others, own } = validatorInEra; - others.forEach((o: any) => { - otherStake = otherStake.add(new BN(rmCommas(o.value))); + others.forEach(({ value }) => { + otherStake = otherStake.plus(value); }); if (own) { - ownStake = new BN(rmCommas(own)); + validatorOwnStake = new BigNumber(own); } } - const [list, setList] = useState([]); + const [list, setList] = useState<AnyJson[]>([]); - const ref: any = React.useRef(); + const ref = useRef<HTMLDivElement>(null); const size = useSize(ref.current); const { width, height, minHeight } = formatSize(size, 300); const handleEraPoints = async () => { - const _list = await fetchEraPoints(address, metrics.activeEra.index); - setList(_list); + setList(await fetchEraPoints(address, activeEra.index.toNumber())); }; useEffect(() => { @@ -64,82 +68,77 @@ export const ValidatorMetrics = () => { const stats = [ { - label: 'Self Stake', - value: `${humanNumber(planckBnToUnit(ownStake, units))} ${unit}`, + label: t('selfStake'), + value: `${planckToUnit(validatorOwnStake, units).toFormat()} ${unit}`, help: 'Self Stake', }, { - label: 'Nominator Stake', - value: `${humanNumber(planckBnToUnit(otherStake, units))} ${unit}`, + label: t('nominatorStake'), + value: `${planckToUnit(otherStake, units).toFormat()} ${unit}`, help: 'Nominator Stake', }, ]; return ( <> - <Title title="Validator Metrics" /> + <Title title={t('validatorMetrics')} /> <div className="header"> - <Identicon value={address} size={33} /> + <Polkicon address={address} size={33} /> <h2>    - {identity === null ? clipAddress(address) : identity} + {identity === null ? ellipsisFn(address) : identity} </h2> </div> - <PaddingWrapper horizontalOnly> + <ModalPadding horizontalOnly> <StatsWrapper> - {stats.map( - (s: { label: string; value: string; help: string }, i: number) => ( - <StatWrapper key={`metrics_stat_${i}`}> - <div className="inner"> - <h4> - {s.label} <OpenHelpIcon helpKey={s.help} /> - </h4> - <h2>{s.value}</h2> - </div> - </StatWrapper> - ) - )} + {stats.map((s, i) => ( + <StatWrapper key={`metrics_stat_${i}`}> + <div className="inner"> + <h4> + {s.label}{' '} + <ButtonHelp marginLeft onClick={() => openHelp(s.help)} /> + </h4> + <h2>{s.value}</h2> + </div> + </StatWrapper> + ))} </StatsWrapper> - </PaddingWrapper> + </ModalPadding> <div className="body" style={{ position: 'relative', marginTop: '0.5rem' }} > - <SubscanButton /> - <GraphWrapper + <PluginLabel plugin="subscan" /> + <CardWrapper + className="transparent" style={{ - margin: '0 1.5rem 0 0.5rem', + margin: '0 0 0 0.5rem', height: 350, - border: 'none', - boxShadow: 'none', }} - flex > - <h4> - Recent Era Points <OpenHelpIcon helpKey="Era Points" /> - </h4> - <div className="inner" ref={ref} style={{ minHeight }}> + <CardHeaderWrapper $withMargin> + <h4> + {t('recentEraPoints')}{' '} + <ButtonHelp marginLeft onClick={() => openHelp('Era Points')} /> + </h4> + </CardHeaderWrapper> + <div ref={ref} style={{ minHeight }}> <StatusLabel status="active_service" - statusFor="cereStats" - title="Cere Stats Disabled" + statusFor="subscan" + title={t('subscanDisabled')} /> - <div - className="graph" + <GraphWrapper style={{ height: `${height}px`, width: `${width}px`, - position: 'absolute', - left: '-1rem', }} > <EraPointsGraph items={list} height={250} /> - </div> + </GraphWrapper> </div> - </GraphWrapper> + </CardWrapper> </div> </> ); }; - -export default ValidatorMetrics; diff --git a/src/modals/WithdrawPoolMember/index.tsx b/src/modals/WithdrawPoolMember/index.tsx index 31797fb1c7..14d3bf7b0c 100644 --- a/src/modals/WithdrawPoolMember/index.tsx +++ b/src/modals/WithdrawPoolMember/index.tsx @@ -1,61 +1,63 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import { faArrowAltCircleUp, faMinus } from '@fortawesome/free-solid-svg-icons'; -import { ButtonSubmit } from '@rossbulat/polkadot-dashboard-ui'; -import BN from 'bn.js'; +import { ActionItem, ModalPadding, ModalWarnings } from '@polkadot-cloud/react'; +import { isNotZero, planckToUnit, rmCommas } from '@polkadot-cloud/utils'; +import BigNumber from 'bignumber.js'; +import { useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; import { useApi } from 'contexts/Api'; -import { useConnect } from 'contexts/Connect'; -import { useModal } from 'contexts/Modal'; -import { useNetworkMetrics } from 'contexts/Network'; +import { useNetworkMetrics } from 'contexts/NetworkMetrics'; import { usePoolMembers } from 'contexts/Pools/PoolMembers'; -import { useTxFees } from 'contexts/TxFees'; -import { EstimatedTxFee } from 'library/EstimatedTxFee'; import { Warning } from 'library/Form/Warning'; +import { useSignerWarnings } from 'library/Hooks/useSignerWarnings'; import { useSubmitExtrinsic } from 'library/Hooks/useSubmitExtrinsic'; -import { Title } from 'library/Modal/Title'; -import { ContentWrapper } from 'modals/UpdateBond/Wrappers'; -import { - FooterWrapper, - NotesWrapper, - PaddingWrapper, - Separator, -} from 'modals/Wrappers'; -import { useState } from 'react'; -import { planckBnToUnit, rmCommas } from 'Utils'; +import { Close } from 'library/Modal/Close'; +import { SubmitTx } from 'library/SubmitTx'; +import { useTxMeta } from 'contexts/TxMeta'; +import { useOverlay } from '@polkadot-cloud/react/hooks'; +import { useNetwork } from 'contexts/Network'; +import { useActiveAccounts } from 'contexts/ActiveAccounts'; export const WithdrawPoolMember = () => { - const { api, network, consts } = useApi(); - const { activeAccount, accountHasSigner } = useConnect(); - const { setStatus: setModalStatus, config } = useModal(); - const { metrics } = useNetworkMetrics(); + const { t } = useTranslation('modals'); + const { api, consts } = useApi(); + const { + networkData: { units, unit }, + } = useNetwork(); + const { activeAccount } = useActiveAccounts(); + const { + setModalStatus, + config: { options }, + setModalResize, + } = useOverlay().modal; + const { activeEra } = useNetworkMetrics(); const { removePoolMember } = usePoolMembers(); - const { txFeesValid } = useTxFees(); + const { getSignerWarnings } = useSignerWarnings(); + const { notEnoughFunds } = useTxMeta(); - const { activeEra } = metrics; - const { member, who } = config; + const { member, who } = options; const { historyDepth } = consts; const { unbondingEras, points } = member; // calculate total for withdraw - let totalWithdrawBase: BN = new BN(0); + let totalWithdrawUnit = new BigNumber(0); Object.entries(unbondingEras).forEach((entry: any) => { const [era, amount] = entry; if (activeEra.index > era) { - totalWithdrawBase = totalWithdrawBase.add(new BN(rmCommas(amount))); + totalWithdrawUnit = totalWithdrawUnit.plus( + new BigNumber(rmCommas(amount)) + ); } }); - const bonded = planckBnToUnit(new BN(rmCommas(points)), network.units); + const bonded = planckToUnit(new BigNumber(rmCommas(points)), units); - const totalWithdraw = planckBnToUnit( - new BN(totalWithdrawBase), - network.units - ); + const totalWithdraw = planckToUnit(new BigNumber(totalWithdrawUnit), units); // valid to submit transaction - const [valid] = useState<boolean>(totalWithdraw > 0 ?? false); + const [valid] = useState<boolean>(isNotZero(totalWithdraw) ?? false); // tx to submit const getTx = () => { @@ -63,61 +65,47 @@ export const WithdrawPoolMember = () => { if (!valid || !api) { return tx; } - tx = api.tx.nominationPools.withdrawUnbonded(who, historyDepth); + tx = api.tx.nominationPools.withdrawUnbonded(who, historyDepth.toString()); return tx; }; - const { submitTx, submitting } = useSubmitExtrinsic({ + const submitExtrinsic = useSubmitExtrinsic({ tx: getTx(), from: activeAccount, shouldSubmit: valid, callbackSubmit: () => { - setModalStatus(2); + setModalStatus('closing'); }, callbackInBlock: () => { // remove the pool member from context if no more funds bonded - if (bonded === 0) { + if (bonded.isZero()) { removePoolMember(who); } }, }); + useEffect(() => setModalResize(), [notEnoughFunds]); + + const warnings = getSignerWarnings( + activeAccount, + false, + submitExtrinsic.proxySupported + ); + return ( <> - <Title title="Withdraw Member Funds" icon={faMinus} /> - <PaddingWrapper verticalOnly /> - <ContentWrapper> - <div> - <div> - {!accountHasSigner(activeAccount) && ( - <Warning text="Your account is read only, and cannot sign transactions." /> - )} - <h2> - Withdraw {totalWithdraw} {network.unit} - </h2> - - <Separator /> - <NotesWrapper> - <EstimatedTxFee /> - </NotesWrapper> - </div> - <FooterWrapper> - <div> - <ButtonSubmit - text={`Submit${submitting ? 'ting' : ''}`} - iconLeft={faArrowAltCircleUp} - iconTransform="grow-2" - onClick={() => submitTx()} - disabled={ - !valid || - submitting || - !accountHasSigner(activeAccount) || - !txFeesValid - } - /> - </div> - </FooterWrapper> - </div> - </ContentWrapper> + <Close /> + <ModalPadding> + <h2 className="title">{t('withdrawMemberFunds')}</h2> + <ActionItem text={`${t('withdraw')} ${totalWithdraw} ${unit}`} /> + {warnings.length > 0 ? ( + <ModalWarnings withMargin> + {warnings.map((text, i) => ( + <Warning key={`warning${i}`} text={text} /> + ))} + </ModalWarnings> + ) : null} + </ModalPadding> + <SubmitTx valid={valid} {...submitExtrinsic} /> </> ); }; diff --git a/src/modals/Wrappers.ts b/src/modals/Wrappers.ts deleted file mode 100644 index 600e43b0ef..0000000000 --- a/src/modals/Wrappers.ts +++ /dev/null @@ -1,188 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { motion } from 'framer-motion'; -import styled from 'styled-components'; -import { - borderPrimary, - cardBorder, - cardShadow, - modalBackground, - modalOverlayBackground, - networkColor, - shadowColor, - textSecondary, -} from 'theme'; - -// Blurred background modal wrapper -export const ModalWrapper = styled(motion.div)` - background: ${modalOverlayBackground}; - position: fixed; - width: 100%; - height: 100%; - z-index: 9; - backdrop-filter: blur(4px); - - /* modal content wrapper */ - > div { - height: 100%; - display: flex; - flex-flow: row wrap; - justify-content: center; - align-items: center; - padding: 1rem 2rem; - - /* click anywhere behind modal content to close */ - .close { - position: fixed; - width: 100%; - height: 100%; - z-index: 8; - cursor: default; - } - } -`; - -export const HeightWrapper = styled.div<{ size: string }>` - border: ${cardBorder} ${borderPrimary}; - box-shadow: ${cardShadow} ${shadowColor}; - transition: height 0.5s cubic-bezier(0.1, 1, 0.2, 1); - width: 100%; - max-width: ${(props) => - props.size === 'xl' - ? '1250px' - : props.size === 'large' - ? '800px' - : '600px'}; - max-height: 100%; - border-radius: 1.5rem; - z-index: 9; - position: relative; -`; - -// Modal content wrapper -export const ContentWrapper = styled.div` - background: ${modalBackground}; - width: 100%; - height: auto; - overflow: hidden; - position: relative; - - a { - color: ${networkColor}; - } - .header { - width: 100%; - display: flex; - flex-flow: row wrap; - align-items: center; - padding: 1rem 1rem 0 1rem; - } - .body { - padding: 1rem; - } - .notes { - padding: 1rem 0; - > p { - color: ${textSecondary}; - } - } -`; - -// generic wrapper for modal padding -export const PaddingWrapper = styled.div<{ - verticalOnly?: boolean; - horizontalOnly?: boolean; -}>` - display: flex; - flex-flow: column wrap; - align-items: flex-start; - justify-content: flex-start; - width: 100%; - padding: ${(props) => - props.verticalOnly - ? '1rem 0' - : props.horizontalOnly - ? '0 1rem' - : '1rem 1.25rem'}; -`; - -// modal header, used for extrinsics forms -export const HeadingWrapper = styled.h3<{ noPadding?: boolean }>` - display: flex; - flex-flow: row wrap; - justify-content: flex-start; - align-items: center; - width: 100%; - margin-top: 0.25rem; - padding: ${(props) => (props.noPadding ? '0' : '0 1rem')}; - color: ${textSecondary}; - flex: 1; - - > svg { - margin-right: 0.75rem; - } -`; - -// modal footer, used for extrinsics forms -export const FooterWrapper = styled.div` - display: flex; - flex-flow: row wrap; - justify-content: flex-end; - align-items: center; - width: 100%; - - h3 { - color: ${textSecondary}; - opacity: 0.5; - margin: 0; - &.active { - opacity: 1; - color: ${networkColor}; - } - } - - > div { - margin-left: 1rem; - } - .submit { - padding: 0.5rem 0.75rem; - border-radius: 0.7rem; - font-size: 1rem; - display: flex; - flex-flow: row nowrap; - justify-content: flex-start; - align-items: center; - &.primary { - color: white; - background: ${networkColor}; - border: 1px solid ${networkColor}; - } - &.secondary { - color: ${networkColor}; - border: 1px solid ${networkColor}; - } - - &:disabled { - opacity: 0.25; - } - svg { - margin-right: 0.5rem; - } - } -`; - -export const Separator = styled.div` - border-top: 1px solid ${textSecondary}; - width: 100%; - opacity: 0.1; - margin: 0.75rem 0rem; -`; - -export const NotesWrapper = styled.div` - width: 100%; - padding: 1rem 0; - > p { - color: ${textSecondary}; - } -`; diff --git a/src/modals/index.tsx b/src/modals/index.tsx deleted file mode 100644 index e49e890ea0..0000000000 --- a/src/modals/index.tsx +++ /dev/null @@ -1,152 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { useModal } from 'contexts/Modal'; -import { useAnimation } from 'framer-motion'; -import { ErrorFallbackModal } from 'library/ErrorBoundary'; -import { useEffect, useRef } from 'react'; -import { ErrorBoundary } from 'react-error-boundary'; -import { AccountPoolRoles } from './AccountPoolRoles'; -import { Bio } from './Bio'; -import { ChangeNominations } from './ChangeNominations'; -import { ChangePoolRoles } from './ChangePoolRoles'; -import { ClaimReward } from './ClaimReward'; -import { ConnectAccounts } from './ConnectAccounts'; -import { DismissTips } from './DismissTips'; -import { GoToFeedback } from './GoToFeedback'; -import { JoinPool } from './JoinPool'; -import { LeavePool } from './LeavePool'; -import { ManagePool } from './ManagePool'; -import { Networks } from './Networks'; -import { Nominate } from './Nominate'; -import { NominateFromFavorites } from './NominateFromFavorites'; -import { NominatePool } from './NominatePool'; -import { PoolNominations } from './PoolNominations'; -import { SelectFavorites } from './SelectFavorites'; -import { Settings } from './Settings'; -import { UnbondPoolMember } from './UnbondPoolMember'; -import { UnlockChunks } from './UnlockChunks'; -import { UpdateBond } from './UpdateBond'; -import { UpdateController } from './UpdateController'; -import { UpdatePayee } from './UpdatePayee'; -import { ValidatorMetrics } from './ValidatorMetrics'; -import { WithdrawPoolMember } from './WithdrawPoolMember'; -import { ContentWrapper, HeightWrapper, ModalWrapper } from './Wrappers'; - -export const Modal = () => { - const { setModalHeight, setStatus, status, modal, size, height, resize } = - useModal(); - const controls = useAnimation(); - - const maxHeight = window.innerHeight * 0.8; - - const onFadeIn = async () => { - await controls.start('visible'); - }; - - const onFadeOut = async () => { - await controls.start('hidden'); - setStatus(0); - }; - - const variants = { - hidden: { - opacity: 0, - }, - visible: { - opacity: 1, - }, - }; - - useEffect(() => { - // modal has been opened - fade in - if (status === 1) { - onFadeIn(); - } - // an external component triggered modal closure - fade out - if (status === 2) { - onFadeOut(); - } - }, [status]); - - const modalRef = useRef<HTMLDivElement>(null); - - // resize modal on status or resize change - useEffect(() => { - handleResize(); - }, [resize]); - - const handleResize = () => { - let _height = modalRef.current?.clientHeight ?? 0; - _height = _height > maxHeight ? maxHeight : _height; - setModalHeight(_height); - }; - - if (status === 0) { - return <></>; - } - - return ( - <ModalWrapper - initial={{ - opacity: 0, - }} - animate={controls} - transition={{ - duration: 0.15, - }} - variants={variants} - > - <div> - <HeightWrapper - size={size} - style={{ - height, - overflow: height >= maxHeight ? 'scroll' : 'hidden', - }} - > - <ContentWrapper ref={modalRef}> - <ErrorBoundary FallbackComponent={ErrorFallbackModal}> - {modal === 'AccountPoolRoles' && <AccountPoolRoles />} - {modal === 'Bio' && <Bio />} - {modal === 'ChangeNominations' && <ChangeNominations />} - {modal === 'ChangePoolRoles' && <ChangePoolRoles />} - {modal === 'ClaimReward' && <ClaimReward />} - {modal === 'ConnectAccounts' && <ConnectAccounts />} - {modal === 'DismissTips' && <DismissTips />} - {modal === 'JoinPool' && <JoinPool />} - {modal === 'Settings' && <Settings />} - {modal === 'UpdateController' && <UpdateController />} - {modal === 'UpdateBond' && <UpdateBond />} - {modal === 'UpdatePayee' && <UpdatePayee />} - {modal === 'ValidatorMetrics' && <ValidatorMetrics />} - {modal === 'ManagePool' && <ManagePool />} - {modal === 'Nominate' && <Nominate />} - {modal === 'UnlockChunks' && <UnlockChunks />} - {modal === 'NominatePool' && <NominatePool />} - {modal === 'LeavePool' && <LeavePool />} - {modal === 'SelectFavorites' && <SelectFavorites />} - {modal === 'Networks' && <Networks />} - {modal === 'NominateFromFavorites' && <NominateFromFavorites />} - {modal === 'PoolNominations' && <PoolNominations />} - {modal === 'UnbondPoolMember' && <UnbondPoolMember />} - {modal === 'WithdrawPoolMember' && <WithdrawPoolMember />} - {modal === 'GoToFeedback' && <GoToFeedback />} - </ErrorBoundary> - </ContentWrapper> - </HeightWrapper> - <button - type="button" - className="close" - onClick={() => { - onFadeOut(); - }} - > -   - </button> - </div> - </ModalWrapper> - ); -}; - -export default Modal; diff --git a/src/overlay/index.tsx b/src/overlay/index.tsx new file mode 100644 index 0000000000..535497141f --- /dev/null +++ b/src/overlay/index.tsx @@ -0,0 +1,80 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { useHelp } from 'contexts/Help'; +import { ErrorFallbackModal } from 'library/ErrorBoundary'; +import { Overlay } from '@polkadot-cloud/react'; +import { ClaimPayouts } from 'modals/ClaimPayouts'; +import { AccountPoolRoles } from '../modals/AccountPoolRoles'; +import { Accounts } from '../modals/Accounts'; +import { Bio } from '../modals/Bio'; +import { Bond } from '../modals/Bond'; +import { ChangeNominations } from '../modals/ChangeNominations'; +import { ChangePoolRoles } from '../modals/ChangePoolRoles'; +import { ChooseLanguage } from '../modals/ChooseLanguage'; +import { ClaimReward } from '../modals/ClaimReward'; +import { Connect } from '../modals/Connect'; +import { GoToFeedback } from '../modals/GoToFeedback'; +import { ImportLedger } from '../modals/ImportLedger'; +import { ImportVault } from '../modals/ImportVault'; +import { JoinPool } from '../modals/JoinPool'; +import { ManageFastUnstake } from '../modals/ManageFastUnstake'; +import { ManagePool } from '../modals/ManagePool'; +import { Networks } from '../modals/Networks'; +import { PoolNominations } from '../modals/PoolNominations'; +import { Settings } from '../modals/Settings'; +import { Unbond } from '../modals/Unbond'; +import { UnbondPoolMember } from '../modals/UnbondPoolMember'; +import { UnlockChunks } from '../modals/UnlockChunks'; +import { Unstake } from '../modals/Unstake'; +import { UpdateController } from '../modals/UpdateController'; +import { UpdatePayee } from '../modals/UpdatePayee'; +import { UpdateReserve } from '../modals/UpdateReserve'; +import { ValidatorMetrics } from '../modals/ValidatorMetrics'; +import { ValidatorGeo } from '../modals/ValidatorGeo'; +import { WithdrawPoolMember } from '../modals/WithdrawPoolMember'; +import { ManageNominations } from '../canvas/ManageNominations'; + +export const Overlays = () => { + const { status } = useHelp(); + return ( + <Overlay + fallback={ErrorFallbackModal} + externalOverlayStatus={status} + modals={{ + Bio, + AccountPoolRoles, + Bond, + ChangeNominations, + ChangePoolRoles, + ChooseLanguage, + ClaimPayouts, + ClaimReward, + Connect, + Accounts, + GoToFeedback, + JoinPool, + ImportLedger, + ImportVault, + ManagePool, + ManageFastUnstake, + Networks, + PoolNominations, + Settings, + ValidatorMetrics, + ValidatorGeo, + UnbondPoolMember, + UnlockChunks, + Unstake, + UpdateController, + Unbond, + UpdatePayee, + UpdateReserve, + WithdrawPoolMember, + }} + canvas={{ + ManageNominations, + }} + /> + ); +}; diff --git a/src/pages/Community/Entity.tsx b/src/pages/Community/Entity.tsx index 4df877f231..0fb92f7c43 100644 --- a/src/pages/Community/Entity.tsx +++ b/src/pages/Community/Entity.tsx @@ -1,28 +1,28 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only import { faChevronLeft } from '@fortawesome/free-solid-svg-icons'; -import { ButtonSecondary } from '@rossbulat/polkadot-dashboard-ui'; -import { useApi } from 'contexts/Api'; -import { useValidators } from 'contexts/Validators'; -import { CardWrapper } from 'library/Graphs/Wrappers'; -import ValidatorList from 'library/ValidatorList'; +import { ButtonSecondary, PageHeading, PageRow } from '@polkadot-cloud/react'; import { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { PageRowWrapper, TopBarWrapper } from 'Wrappers'; -import { useCommunitySections } from './context'; +import { useApi } from 'contexts/Api'; +import { useValidators } from 'contexts/Validators/ValidatorEntries'; +import { CardWrapper } from 'library/Card/Wrappers'; +import { ValidatorList } from 'library/ValidatorList'; +import { useNetwork } from 'contexts/Network'; import { Item } from './Item'; import { ItemsWrapper } from './Wrappers'; +import { useCommunitySections } from './context'; export const Entity = () => { - const { isReady, network } = useApi(); - const { validators: allValidators, removeValidatorMetaBatch } = - useValidators(); - const { setActiveSection, activeItem } = useCommunitySections(); const { t } = useTranslation('pages'); + const { isReady } = useApi(); + const { network } = useNetwork(); + const { validators: allValidators } = useValidators(); + const { setActiveSection, activeItem } = useCommunitySections(); const { name, validators: entityAllValidators } = activeItem; - const validators = entityAllValidators[network.name.toLowerCase()] ?? []; + const validators = entityAllValidators[network] ?? []; // include validators that exist in `erasStakers` const [activeValidators, setActiveValidators] = useState( @@ -36,7 +36,6 @@ export const Entity = () => { }, [allValidators, network]); useEffect(() => { - removeValidatorMetaBatch(batchKey); const newValidators = [...activeValidators]; setActiveValidators(newValidators); }, [name, activeItem, network]); @@ -52,26 +51,23 @@ export const Entity = () => { }, }; - const batchKey = 'community_entity_validators'; - return ( - <PageRowWrapper className="page-padding" noVerticalSpacer> - <TopBarWrapper> + <PageRow> + <PageHeading> <ButtonSecondary - lg - text={t('community.go_back')} + text={t('community.goBack')} iconLeft={faChevronLeft} iconTransform="shrink-3" onClick={() => setActiveSection(0)} /> - </TopBarWrapper> + </PageHeading> <ItemsWrapper variants={container} initial="hidden" animate="show"> <Item item={activeItem} actionable={false} /> </ItemsWrapper> <CardWrapper> {!isReady ? ( <div className="item"> - <h3>{t('community.connecting')}</h3> + <h3>{t('community.connecting')}...</h3> </div> ) : ( <> @@ -79,17 +75,16 @@ export const Entity = () => { <div className="item"> <h3> {validators.length - ? t('community.fetching_validators') - : t('community.no_validators')} + ? `${t('community.fetchingValidators')}...` + : t('community.noValidators')} </h3> </div> )} {activeValidators.length > 0 && ( <ValidatorList - bondType="stake" + bondFor="nominator" validators={activeValidators} - batchKey={batchKey} - title={`${name} ${t('community.validators')}`} + allowListFormat={false} selectable={false} allowMoreCols pagination @@ -101,8 +96,6 @@ export const Entity = () => { </> )} </CardWrapper> - </PageRowWrapper> + </PageRow> ); }; - -export default Entity; diff --git a/src/pages/Community/Item.tsx b/src/pages/Community/Item.tsx index 7897415895..32365e35bc 100644 --- a/src/pages/Community/Item.tsx +++ b/src/pages/Community/Item.tsx @@ -1,7 +1,6 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import { IconProp } from '@fortawesome/fontawesome-svg-core'; import { faTwitter } from '@fortawesome/free-brands-svg-icons'; import { faEnvelope, @@ -9,20 +8,19 @@ import { faServer, } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { useApi } from 'contexts/Api'; -import { useModal } from 'contexts/Modal'; -import { lazy, Suspense, useMemo } from 'react'; +import { Suspense, lazy, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; -import { useCommunitySections } from './context'; -import { ItemProps } from './types'; +import { useOverlay } from '@polkadot-cloud/react/hooks'; +import { useNetwork } from 'contexts/Network'; import { ItemWrapper } from './Wrappers'; +import { useCommunitySections } from './context'; +import type { ItemProps } from './types'; -export const Item = (props: ItemProps) => { - const { openModalWith } = useModal(); - const { network } = useApi(); +export const Item = ({ item, actionable }: ItemProps) => { const { t } = useTranslation('pages'); + const { openModal } = useOverlay().modal; + const { network } = useNetwork(); - const { item, actionable } = props; const { bio, name, @@ -32,8 +30,7 @@ export const Item = (props: ItemProps) => { thumbnail, validators: entityAllValidators, } = item; - const validatorCount = - entityAllValidators[network.name.toLowerCase()]?.length ?? 0; + const validatorCount = entityAllValidators[network]?.length ?? 0; const { setActiveSection, setActiveItem, setScrollPos } = useCommunitySections(); @@ -57,13 +54,15 @@ export const Item = (props: ItemProps) => { }, }; - const Thumbnail = useMemo(() => { - return lazy(() => import(`config/validators/thumbnails/${thumbnail}`)); - }, []); + const Thumbnail = useMemo( + () => lazy(() => import(`../../config/validators/${thumbnail}.tsx`)), + [] + ); return ( <ItemWrapper - whileHover={{ scale: actionable ? 1.005 : 1 }} + whileHover={{ scale: 1.005 }} + transition={{ duration: 0.15 }} variants={listItem} > <div className="inner"> @@ -77,7 +76,7 @@ export const Item = (props: ItemProps) => { {name} <button type="button" - onClick={() => openModalWith('Bio', { name, bio }, 'large')} + onClick={() => openModal({ key: 'Bio', options: { name, bio } })} className="active" > <span>{t('community.bio')}</span> @@ -137,10 +136,7 @@ export const Item = (props: ItemProps) => { window.open(`https://twitter.com/${twitter}`, '_blank'); }} > - <FontAwesomeIcon - icon={faTwitter as IconProp} - className="icon-left" - /> + <FontAwesomeIcon icon={faTwitter} className="icon-left" /> <h4>{twitter}</h4> <FontAwesomeIcon icon={faExternalLink} @@ -171,5 +167,3 @@ export const Item = (props: ItemProps) => { </ItemWrapper> ); }; - -export default Item; diff --git a/src/pages/Community/List.tsx b/src/pages/Community/List.tsx index fccfc68dbe..ac5d859bb3 100644 --- a/src/pages/Community/List.tsx +++ b/src/pages/Community/List.tsx @@ -1,30 +1,28 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import { useApi } from 'contexts/Api'; -import { useValidators } from 'contexts/Validators'; +import { PageRow } from '@polkadot-cloud/react'; import { useEffect, useState } from 'react'; -import { PageRowWrapper } from 'Wrappers'; -import { useCommunitySections } from './context'; +import { useValidators } from 'contexts/Validators/ValidatorEntries'; +import { useNetwork } from 'contexts/Network'; import { Item } from './Item'; import { ItemsWrapper } from './Wrappers'; +import { useCommunitySections } from './context'; export const List = () => { - const { network } = useApi(); + const { network } = useNetwork(); const { validatorCommunity } = useValidators(); const { scrollPos } = useCommunitySections(); const [entityItems, setEntityItems] = useState( - validatorCommunity.filter( - (v) => v.validators[network.name.toLowerCase()] !== undefined - ) + validatorCommunity.filter((v) => v.validators[network] !== undefined) ); + console.warn(`All validators: ${validatorCommunity}`); + useEffect(() => { setEntityItems( - validatorCommunity.filter( - (v) => v.validators[network.name.toLowerCase()] !== undefined - ) + validatorCommunity.filter((v) => v.validators[network] !== undefined) ); }, [network]); @@ -44,16 +42,12 @@ export const List = () => { }; return ( - <PageRowWrapper className="page-padding"> + <PageRow yMargin> <ItemsWrapper variants={container} initial="hidden" animate="show"> - {entityItems.map((item: any, index: number) => { - return ( - <Item key={`community_item_${index}`} item={item} actionable /> - ); - })} + {entityItems.map((item: any, index: number) => ( + <Item key={`community_item_${index}`} item={item} actionable /> + ))} </ItemsWrapper> - </PageRowWrapper> + </PageRow> ); }; - -export default List; diff --git a/src/pages/Community/Wrappers.ts b/src/pages/Community/Wrappers.ts index ade41490d2..4281a46fe3 100644 --- a/src/pages/Community/Wrappers.ts +++ b/src/pages/Community/Wrappers.ts @@ -1,26 +1,16 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only import { motion } from 'framer-motion'; import styled from 'styled-components'; -import { - backgroundDropdown, - backgroundSecondary, - borderPrimary, - buttonHoverBackground, - buttonSecondaryBackground, - cardBorder, - cardShadow, - shadowColor, - textSecondary, -} from 'theme'; const VERTICAL_THRESHOLD = 800; export const Wrapper = styled.div` h2 { - color: ${textSecondary}; + color: var(--text-color-secondary); margin-top: 2rem; + margin-bottom: 1rem; } `; export const ItemsWrapper = styled(motion.div)` @@ -41,10 +31,9 @@ export const ItemWrapper = styled(motion.div)` } > .inner { - color: ${textSecondary}; - background: ${backgroundSecondary}; - border: ${cardBorder} ${borderPrimary}; - box-shadow: ${cardShadow} ${shadowColor}; + color: var(--text-color-secondary); + background: var(--background-primary); + box-shadow: var(--card-shadow); border-radius: 0.75rem; width: 100%; height: 100%; @@ -72,10 +61,10 @@ export const ItemWrapper = styled(motion.div)` > button { font-size: 1.1rem; &.active { - color: ${textSecondary}; - background: ${backgroundDropdown}; + color: var(--text-color-secondary); + background: var(--background-list-item); &:hover { - background: ${backgroundDropdown}; + background: var(--background-list-item); } } padding: 0.35rem 0.75rem; @@ -91,24 +80,21 @@ export const ItemWrapper = styled(motion.div)` padding: 0.3rem 1rem; svg { - color: ${textSecondary}; + color: var(--text-color-secondary); } margin: 0.5rem 1rem 0.5rem 0; @media (min-width: ${VERTICAL_THRESHOLD + 1}px) { margin: 0.25rem 1rem 0.25rem 0; } - > h4 { - margin: 0; - } &:disabled { cursor: default; } &.active { - background: ${buttonSecondaryBackground}; - transition: background 0.1s; + background: var(--button-secondary-background); + transition: background var(--transition-duration); &:hover { - background: ${buttonHoverBackground}; + background: var(--button-hover-background); } } &:last-child { @@ -138,7 +124,6 @@ export const ItemWrapper = styled(motion.div)` &:first-child { flex-flow: row wrap; align-items: center; - justify-content: flex-start; width: 100%; padding: 1rem; svg { @@ -147,11 +132,10 @@ export const ItemWrapper = styled(motion.div)` } } &:last-child { + border-top: 1px solid var(--border-primary-color); border-left: none; flex-flow: column wrap; - align-items: flex-start; justify-content: center; - border-top: 1px solid ${borderPrimary}; height: 100%; height: 50%; width: 100%; diff --git a/src/pages/Community/context.tsx b/src/pages/Community/context.tsx index 525b9f254d..b7bd4948cf 100644 --- a/src/pages/Community/context.tsx +++ b/src/pages/Community/context.tsx @@ -1,8 +1,8 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import { useApi } from 'contexts/Api'; import React, { useEffect, useState } from 'react'; +import { useNetwork } from 'contexts/Network'; import * as defaults from './defaults'; export const CommunitySectionsContext: React.Context<any> = React.createContext( @@ -17,10 +17,10 @@ export const CommunitySectionsProvider = ({ }: { children: React.ReactNode; }) => { - const { network } = useApi(); + const { network } = useNetwork(); // store the active section of the community page - const [activeSection, _setActiveSection] = useState<number>(0); + const [activeSection, setActiveSectionState] = useState<number>(0); // store the active entity item of the community page const [activeItem, setActiveItem] = useState(defaults.item); @@ -31,12 +31,12 @@ export const CommunitySectionsProvider = ({ // go back to first section and reset item when network switches useEffect(() => { - _setActiveSection(0); + setActiveSectionState(0); setActiveItem(defaults.item); }, [network]); const setActiveSection = (t: any) => { - _setActiveSection(t); + setActiveSectionState(t); }; return ( diff --git a/src/pages/Community/defaults.ts b/src/pages/Community/defaults.ts index d78429a69b..face250f00 100644 --- a/src/pages/Community/defaults.ts +++ b/src/pages/Community/defaults.ts @@ -1,5 +1,6 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only +/* eslint-disable @typescript-eslint/no-unused-vars */ export const item = { name: '', @@ -8,7 +9,6 @@ export const item = { }; export const defaultContext = { - // eslint-disable-next-line setActiveSection: (t: number) => {}, activeSection: 0, }; diff --git a/src/pages/Community/index.tsx b/src/pages/Community/index.tsx index 30131ca2f2..298175a38e 100644 --- a/src/pages/Community/index.tsx +++ b/src/pages/Community/index.tsx @@ -1,36 +1,31 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import { PageTitle } from 'library/PageTitle'; +import { PageTitle } from '@polkadot-cloud/react'; import { useTranslation } from 'react-i18next'; -import { PageProps } from '../types'; -import { CommunitySectionsProvider, useCommunitySections } from './context'; +import type { PageProps } from 'types'; import { Entity } from './Entity'; import { List } from './List'; import { Wrapper } from './Wrappers'; +import { CommunitySectionsProvider, useCommunitySections } from './context'; -export const CommunityInner = (props: PageProps) => { - const { page } = props; - const { key } = page; - - const { activeSection } = useCommunitySections(); +export const CommunityInner = ({ page }: PageProps) => { const { t } = useTranslation('base'); + const { activeSection } = useCommunitySections(); + + const { key } = page; return ( <Wrapper> - <PageTitle title={`${t(key)}`} /> + <PageTitle title={t(key)} /> {activeSection === 0 && <List />} {activeSection === 1 && <Entity />} </Wrapper> ); }; -export const Community = (props: PageProps) => { - return ( - <CommunitySectionsProvider> - <CommunityInner {...props} /> - </CommunitySectionsProvider> - ); -}; - -export default Community; +export const Community = (props: PageProps) => ( + <CommunitySectionsProvider> + <CommunityInner {...props} /> + </CommunitySectionsProvider> +); diff --git a/src/pages/Community/types.ts b/src/pages/Community/types.ts index 820c716da8..688026280e 100644 --- a/src/pages/Community/types.ts +++ b/src/pages/Community/types.ts @@ -1,4 +1,4 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors export interface ItemProps { item: Item; @@ -12,5 +12,5 @@ export interface Item { twitter: string; website: string; thumbnail: string; - validators: { [key: string]: string }; + validators: Record<string, string>; } diff --git a/src/pages/Favorites/index.tsx b/src/pages/Favorites/index.tsx deleted file mode 100644 index a045c7bc83..0000000000 --- a/src/pages/Favorites/index.tsx +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { useApi } from 'contexts/Api'; -import { useValidators } from 'contexts/Validators'; -import { CardWrapper } from 'library/Graphs/Wrappers'; -import { PageTitle } from 'library/PageTitle'; -import { ValidatorList } from 'library/ValidatorList'; -import { useTranslation } from 'react-i18next'; -import { PageRowWrapper } from 'Wrappers'; -import { PageProps } from '../types'; - -export const Favorites = (props: PageProps) => { - const { isReady } = useApi(); - const { page } = props; - const { key } = page; - const { favoritesList } = useValidators(); - const { t } = useTranslation(); - - const batchKey = 'favorite_validators'; - - return ( - <> - <PageTitle title={t(key, { ns: 'base' })} /> - <PageRowWrapper className="page-padding" noVerticalSpacer> - <CardWrapper> - {favoritesList === null ? ( - <h3> - {t('favorites.fetching_favorite_validators', { ns: 'pages' })} - </h3> - ) : ( - <> - {isReady && ( - <> - {favoritesList.length > 0 ? ( - <ValidatorList - bondType="stake" - validators={favoritesList} - batchKey={batchKey} - title={t('favorites.favorite_validators', { - ns: 'pages', - })} - selectable={false} - refetchOnListUpdate - allowMoreCols - toggleFavorites - /> - ) : ( - <h3>{t('favorites.no_favorites', { ns: 'pages' })}</h3> - )} - </> - )} - </> - )} - </CardWrapper> - </PageRowWrapper> - </> - ); -}; - -export default Favorites; diff --git a/src/pages/Feedback/Wrappers.ts b/src/pages/Feedback/Wrappers.ts deleted file mode 100644 index c94710cdc0..0000000000 --- a/src/pages/Feedback/Wrappers.ts +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import styled from 'styled-components'; -import { textSecondary } from 'theme'; - -export const Wrapper = styled.div` - h2 { - color: ${textSecondary}; - margin-top: 2rem; - } -`; diff --git a/src/pages/Feedback/index.tsx b/src/pages/Feedback/index.tsx deleted file mode 100644 index 2ef9cab5fa..0000000000 --- a/src/pages/Feedback/index.tsx +++ /dev/null @@ -1,63 +0,0 @@ -/* eslint-disable */ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { PageTitle } from 'library/PageTitle'; -import { useEffect } from 'react'; -import { useTranslation } from 'react-i18next'; -import { PageRowWrapper } from 'Wrappers'; -import { Wrapper } from '../Community/Wrappers'; -import { PageProps } from '../types'; - -const BoardToken = '2dda48aa-e149-da7b-f016-98e22279df1e'; - -const Feedback = (props: PageProps) => { - const { page } = props; - const { key } = page; - const { t } = useTranslation('base'); - - useEffect(() => { - (function (w: any, d: any, i: any, s: any) { - function l() { - if (!d.getElementById(i)) { - var f = d.getElementsByTagName(s)[0], - e = d.createElement(s); - (e.type = 'text/javascript'), - (e.async = !0), - (e.src = 'https://canny.io/sdk.js'), - f.parentNode.insertBefore(e, f); - } - } - if ('function' != typeof w.Canny) { - var c: any = function () { - c.q.push(arguments); - }; - (c.q = []), - (w.Canny = c), - 'complete' === d.readyState - ? l() - : w.attachEvent - ? w.attachEvent('onload', l) - : w.addEventListener('load', l, !1); - } - })(window, document, 'canny-jssdk', 'script'); - - // @ts-ignore - Canny('render', { - boardToken: BoardToken, - basePath: null, // See step 2 - ssoToken: null, // See step 3 - }); - }, []); - - return ( - <Wrapper> - <PageTitle title={t(key)} /> - <PageRowWrapper className="page-padding"> - <div data-canny style={{ width: '100%' }} /> - </PageRowWrapper> - </Wrapper> - ); -}; - -export default Feedback; diff --git a/src/pages/Nominate/Active/Controller/Wrapper.ts b/src/pages/Nominate/Active/Controller/Wrapper.ts deleted file mode 100644 index 61a0742d5d..0000000000 --- a/src/pages/Nominate/Active/Controller/Wrapper.ts +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import styled from 'styled-components'; - -interface WrapperProps { - paddingLeft: boolean; - paddingRight: boolean; -} - -export const Wrapper = styled.div<WrapperProps>` - display: flex; - flex-flow: row wrap; - - > .hide-with-padding { - padding-left: ${(props) => (props.paddingLeft ? '3rem' : '0')}; - padding-right: ${(props) => (props.paddingRight ? '8.2rem' : '0')}; - padding-top: 0.4rem; - padding-bottom: 0.4rem; - flex-shrink: 1; - text-overflow: ellipsis; - white-space: nowrap; - overflow: hidden; - position: relative; - margin-bottom: 0; - - .icon { - position: absolute; - left: 0; - top: 0; - display: flex; - flex-flow: row wrap; - align-items: center; - } - - .btn { - position: absolute; - right: 0; - top: 0; - padding: 0.1rem; - display: flex; - flex-flow: row wrap; - align-items: center; - } - } -`; diff --git a/src/pages/Nominate/Active/Controller/index.tsx b/src/pages/Nominate/Active/Controller/index.tsx deleted file mode 100644 index d10d3d474f..0000000000 --- a/src/pages/Nominate/Active/Controller/index.tsx +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { faExchangeAlt } from '@fortawesome/free-solid-svg-icons'; -import { ButtonPrimary } from '@rossbulat/polkadot-dashboard-ui'; -import { useApi } from 'contexts/Api'; -import { useBalances } from 'contexts/Balances'; -import { useConnect } from 'contexts/Connect'; -import { useModal } from 'contexts/Modal'; -import { useStaking } from 'contexts/Staking'; -import { Identicon } from 'library/Identicon'; -import OpenHelpIcon from 'library/OpenHelpIcon'; -import { Wrapper as StatWrapper } from 'library/Stat/Wrapper'; -import { useTranslation } from 'react-i18next'; -import { clipAddress } from 'Utils'; -import { Wrapper } from './Wrapper'; - -export const Controller = ({ label }: { label: string }) => { - const { isReady } = useApi(); - const { activeAccount, isReadOnlyAccount, getAccount } = useConnect(); - const { openModalWith } = useModal(); - const { hasController } = useStaking(); - const { getBondedAccount } = useBalances(); - const controller = getBondedAccount(activeAccount); - const { t } = useTranslation('pages'); - - let display = t('nominate.none'); - if (hasController() && controller) { - display = clipAddress(controller); - } - - const displayName = getAccount(controller)?.name; - - return ( - <StatWrapper> - <h4> - {label} <OpenHelpIcon helpKey="Stash and Controller Accounts" /> - </h4> - <Wrapper paddingLeft={hasController()} paddingRight> - <h2 className="hide-with-padding"> - <div className="icon"> - <Identicon value={controller || ''} size={26} /> - </div> - {displayName || display}  - <div className="btn"> - <ButtonPrimary - text={t('nominate.change')} - iconLeft={faExchangeAlt} - disabled={ - !isReady || !hasController() || isReadOnlyAccount(activeAccount) - } - onClick={() => openModalWith('UpdateController', {}, 'large')} - style={{ minWidth: '7.5rem' }} - /> - </div> - </h2> - </Wrapper> - </StatWrapper> - ); -}; diff --git a/src/pages/Nominate/Active/ControllerNotImported.tsx b/src/pages/Nominate/Active/ControllerNotImported.tsx deleted file mode 100644 index a349d3dce0..0000000000 --- a/src/pages/Nominate/Active/ControllerNotImported.tsx +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { ButtonPrimary } from '@rossbulat/polkadot-dashboard-ui'; -import { useBalances } from 'contexts/Balances'; -import { useConnect } from 'contexts/Connect'; -import { useModal } from 'contexts/Modal'; -import { useStaking } from 'contexts/Staking'; -import { useTheme } from 'contexts/Themes'; -import { useUi } from 'contexts/UI'; -import { CardHeaderWrapper, CardWrapper } from 'library/Graphs/Wrappers'; -import { useTranslation } from 'react-i18next'; -import { defaultThemes } from 'theme/default'; -import { PageRowWrapper } from 'Wrappers'; - -export const ControllerNotImported = () => { - const { openModalWith } = useModal(); - const { isSyncing } = useUi(); - const { mode } = useTheme(); - const { getControllerNotImported } = useStaking(); - const { activeAccount, isReadOnlyAccount } = useConnect(); - const { getBondedAccount } = useBalances(); - const controller = getBondedAccount(activeAccount); - const { t } = useTranslation('pages'); - - return ( - <> - {getControllerNotImported(controller) && - !isSyncing && - !isReadOnlyAccount(activeAccount) && ( - <PageRowWrapper className="page-padding" noVerticalSpacer> - <CardWrapper - style={{ - border: `1px solid ${defaultThemes.status.warning.transparent[mode]}`, - }} - > - <CardHeaderWrapper> - <h4>{t('nominate.controller_not_imported')}</h4> - </CardHeaderWrapper> - <ButtonPrimary - text="Set New Controller" - onClick={() => openModalWith('UpdateController', {}, 'large')} - /> - </CardWrapper> - </PageRowWrapper> - )} - </> - ); -}; diff --git a/src/pages/Nominate/Active/ControllerNotStash.tsx b/src/pages/Nominate/Active/ControllerNotStash.tsx new file mode 100644 index 0000000000..bd45dac55d --- /dev/null +++ b/src/pages/Nominate/Active/ControllerNotStash.tsx @@ -0,0 +1,70 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { + faCircleArrowRight, + faExclamationTriangle, +} from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { stringUpperFirst } from '@polkadot/util'; +import { ButtonPrimary, PageRow } from '@polkadot-cloud/react'; +import { useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useBonded } from 'contexts/Bonded'; +import { useStaking } from 'contexts/Staking'; +import { useUi } from 'contexts/UI'; +import { CardHeaderWrapper, CardWrapper } from 'library/Card/Wrappers'; +import { useOverlay } from '@polkadot-cloud/react/hooks'; +import { useNetwork } from 'contexts/Network'; +import { useActiveAccounts } from 'contexts/ActiveAccounts'; +import { useImportedAccounts } from 'contexts/Connect/ImportedAccounts'; + +export const ControllerNotStash = () => { + const { t } = useTranslation('pages'); + const { network } = useNetwork(); + const { activeAccount } = useActiveAccounts(); + const { addressDifferentToStash } = useStaking(); + const { getBondedAccount } = useBonded(); + const { openModal } = useOverlay().modal; + const { isSyncing } = useUi(); + const { isReadOnlyAccount } = useImportedAccounts(); + const controller = getBondedAccount(activeAccount); + + const [showPrompt, setShowPrompt] = useState<boolean>( + addressDifferentToStash(controller) + ); + + useEffect(() => { + setShowPrompt(addressDifferentToStash(controller)); + }, [controller]); + + return ( + <> + {showPrompt + ? !isSyncing && + !isReadOnlyAccount(activeAccount) && ( + <PageRow> + <CardWrapper className="warning"> + <CardHeaderWrapper> + <h3 style={{ marginBottom: '0.75rem' }}> + <FontAwesomeIcon icon={faExclamationTriangle} /> +   {t('nominate.controllerAccountsDeprecated')} + </h3> + <h4> + {t('nominate.proxyprompt')} {stringUpperFirst(network)}. + </h4> + </CardHeaderWrapper> + <div> + <ButtonPrimary + text={t('nominate.updateToStash')} + iconLeft={faCircleArrowRight} + onClick={() => openModal({ key: 'UpdateController' })} + /> + </div> + </CardWrapper> + </PageRow> + ) + : null} + </> + ); +}; diff --git a/src/pages/Nominate/Active/ManageBond.tsx b/src/pages/Nominate/Active/ManageBond.tsx index e7b80eec6b..6b7805fea3 100644 --- a/src/pages/Nominate/Active/ManageBond.tsx +++ b/src/pages/Nominate/Active/ManageBond.tsx @@ -1,102 +1,131 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only import { faLockOpen } from '@fortawesome/free-solid-svg-icons'; -import { ButtonPrimary } from '@rossbulat/polkadot-dashboard-ui'; -import BN from 'bn.js'; -import { useApi } from 'contexts/Api'; +import { + ButtonHelp, + ButtonPrimary, + ButtonRow, + Odometer, +} from '@polkadot-cloud/react'; +import { minDecimalPlaces, planckToUnit } from '@polkadot-cloud/utils'; +import BigNumber from 'bignumber.js'; +import { useTranslation } from 'react-i18next'; import { useBalances } from 'contexts/Balances'; -import { useConnect } from 'contexts/Connect'; -import { useModal } from 'contexts/Modal'; +import { useHelp } from 'contexts/Help'; import { useStaking } from 'contexts/Staking'; import { useTransferOptions } from 'contexts/TransferOptions'; import { useUi } from 'contexts/UI'; -import BondedGraph from 'library/Graphs/Bonded'; -import { CardHeaderWrapper } from 'library/Graphs/Wrappers'; -import { OpenHelpIcon } from 'library/OpenHelpIcon'; -import { useTranslation } from 'react-i18next'; -import { humanNumber, planckBnToUnit } from 'Utils'; -import { ButtonRowWrapper } from 'Wrappers'; +import { CardHeaderWrapper } from 'library/Card/Wrappers'; +import { useUnstaking } from 'library/Hooks/useUnstaking'; +import { useOverlay } from '@polkadot-cloud/react/hooks'; +import { BondedChart } from 'library/BarChart/BondedChart'; +import { useNetwork } from 'contexts/Network'; +import { useActiveAccounts } from 'contexts/ActiveAccounts'; +import { useImportedAccounts } from 'contexts/Connect/ImportedAccounts'; export const ManageBond = () => { - const { network } = useApi(); - const { units } = network; - const { openModalWith } = useModal(); - const { activeAccount, isReadOnlyAccount } = useConnect(); - const { getLedgerForStash } = useBalances(); - const { getTransferOptions } = useTransferOptions(); - const { inSetup } = useStaking(); + const { t } = useTranslation('pages'); + const { + networkData: { + units, + brand: { token: Token }, + }, + } = useNetwork(); const { isSyncing } = useUi(); - const ledger = getLedgerForStash(activeAccount); - const { active }: { active: BN } = ledger; - + const { openHelp } = useHelp(); + const { inSetup } = useStaking(); + const { openModal } = useOverlay().modal; + const { getStashLedger } = useBalances(); + const { isFastUnstaking } = useUnstaking(); + const { isReadOnlyAccount } = useImportedAccounts(); + const { getTransferOptions, feeReserve } = useTransferOptions(); + const { activeAccount } = useActiveAccounts(); + const ledger = getStashLedger(activeAccount); + const { active }: { active: BigNumber } = ledger; const allTransferOptions = getTransferOptions(activeAccount); - const { freeBalance } = allTransferOptions; + const { freeBalance, edReserved } = allTransferOptions; const { totalUnlocking, totalUnlocked, totalUnlockChuncks } = allTransferOptions.nominate; - const { t } = useTranslation('pages'); + const totalFree = BigNumber.max( + 0, + freeBalance.minus(edReserved.plus(feeReserve)) + ); return ( <> <CardHeaderWrapper> <h4> - {t('nominate.bonded_funds')} - <OpenHelpIcon helpKey="Bonding" /> + {t('nominate.bondedFunds')} + <ButtonHelp marginLeft onClick={() => openHelp('Bonding')} /> </h4> <h2> - {humanNumber(planckBnToUnit(active, units))} {network.unit} + <Token className="networkIcon" /> + <Odometer + value={minDecimalPlaces(planckToUnit(active, units).toFormat(), 2)} + zeroDecimals={2} + /> </h2> - <ButtonRowWrapper> + <ButtonRow> <ButtonPrimary disabled={ - inSetup() || isSyncing || isReadOnlyAccount(activeAccount) + inSetup() || + isSyncing || + isReadOnlyAccount(activeAccount) || + isFastUnstaking } marginRight onClick={() => - openModalWith( - 'UpdateBond', - { fn: 'add', bondType: 'stake' }, - 'small' - ) + openModal({ + key: 'Bond', + options: { bondFor: 'nominator' }, + size: 'sm', + }) } text="+" /> <ButtonPrimary disabled={ - inSetup() || isSyncing || isReadOnlyAccount(activeAccount) + inSetup() || + isSyncing || + isReadOnlyAccount(activeAccount) || + isFastUnstaking } marginRight onClick={() => - openModalWith( - 'UpdateBond', - { fn: 'remove', bondType: 'stake' }, - 'small' - ) + openModal({ + key: 'Unbond', + options: { bondFor: 'nominator' }, + size: 'sm', + }) } text="-" /> <ButtonPrimary disabled={ - inSetup() || isSyncing || isReadOnlyAccount(activeAccount) + isSyncing || inSetup() || isReadOnlyAccount(activeAccount) } iconLeft={faLockOpen} + marginRight onClick={() => - openModalWith('UnlockChunks', { bondType: 'stake' }, 'small') + openModal({ + key: 'UnlockChunks', + options: { bondFor: 'nominator', disableWindowResize: true }, + size: 'sm', + }) } text={String(totalUnlockChuncks ?? 0)} /> - </ButtonRowWrapper> + </ButtonRow> </CardHeaderWrapper> - <BondedGraph - active={planckBnToUnit(active, units)} - unlocking={planckBnToUnit(totalUnlocking, units)} - unlocked={planckBnToUnit(totalUnlocked, units)} - free={planckBnToUnit(freeBalance, units)} - inactive={inSetup()} + <BondedChart + active={planckToUnit(active, units)} + unlocking={planckToUnit(totalUnlocking, units)} + unlocked={planckToUnit(totalUnlocked, units)} + free={planckToUnit(totalFree, units)} + inactive={active.isZero()} /> </> ); }; - -export default ManageBond; diff --git a/src/pages/Nominate/Active/Nominations/index.tsx b/src/pages/Nominate/Active/Nominations/index.tsx deleted file mode 100644 index 4d05d46aff..0000000000 --- a/src/pages/Nominate/Active/Nominations/index.tsx +++ /dev/null @@ -1,194 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { faStopCircle } from '@fortawesome/free-solid-svg-icons'; -import { ButtonPrimary } from '@rossbulat/polkadot-dashboard-ui'; -import { useBalances } from 'contexts/Balances'; -import { useConnect } from 'contexts/Connect'; -import { useModal } from 'contexts/Modal'; -import { useActivePools } from 'contexts/Pools/ActivePools'; -import { PoolState } from 'contexts/Pools/types'; -import { useStaking } from 'contexts/Staking'; -import { useUi } from 'contexts/UI'; -import { useValidators } from 'contexts/Validators'; -import { CardHeaderWrapper } from 'library/Graphs/Wrappers'; -import { OpenHelpIcon } from 'library/OpenHelpIcon'; -import { ValidatorList } from 'library/ValidatorList'; -import { useTranslation } from 'react-i18next'; -import { MaybeAccount } from 'types'; -import { Wrapper } from './Wrapper'; - -export const Nominations = ({ - bondType, - nominator, -}: { - bondType: 'pool' | 'stake'; - nominator: MaybeAccount; -}) => { - const { openModalWith } = useModal(); - const { inSetup } = useStaking(); - const { isSyncing } = useUi(); - const { activeAccount, isReadOnlyAccount } = useConnect(); - const { getAccountNominations } = useBalances(); - const { nominated: stakeNominated, poolNominated } = useValidators(); - const { t } = useTranslation('pages'); - let { favoritesList } = useValidators(); - if (favoritesList === null) { - favoritesList = []; - } - - const { - poolNominations, - isNominator: isPoolNominator, - isOwner: isPoolOwner, - selectedActivePool, - } = useActivePools(); - - const isPool = bondType === 'pool'; - const nominations = isPool - ? poolNominations.targets - : getAccountNominations(nominator); - const nominated = isPool ? poolNominated : stakeNominated; - const batchKey = isPool ? 'pool_nominations' : 'stake_nominations'; - - const nominating = nominated?.length ?? false; - - // callback function to stop nominating selected validators - const cbStopNominatingSelected = (provider: any) => { - const { selected } = provider; - const _nominations = [...nominations].filter((n) => { - return !selected.map((_s: any) => _s.address).includes(n); - }); - openModalWith( - 'ChangeNominations', - { - nominations: _nominations, - provider, - bondType, - }, - 'small' - ); - }; - - // callback function for adding nominations - const cbAddNominations = ({ setSelectActive }: any) => { - setSelectActive(false); - openModalWith( - 'NominateFromFavorites', - { - nominations, - bondType, - }, - 'xl' - ); - }; - - // determine whether buttons are disabled - const poolDestroying = - isPool && - selectedActivePool?.bondedPool?.state === PoolState.Destroy && - !nominating; - - const stopBtnDisabled = - (!isPool && inSetup()) || - isSyncing || - isReadOnlyAccount(activeAccount) || - poolDestroying; - - return ( - <Wrapper> - <CardHeaderWrapper withAction> - <h3> - {isPool ? t('nominate.pool_nominations') : t('nominate.nominations')} - <OpenHelpIcon helpKey="Nominations" /> - </h3> - <div> - {/* If regular staking and nominating, display stop button. - If Pool and account is nominator or root, display stop button. - */} - {((!isPool && nominations.length) || - (isPool && (isPoolNominator() || isPoolOwner()))) && ( - <ButtonPrimary - iconLeft={faStopCircle} - iconTransform="grow-1" - text={t('nominate.stop')} - disabled={stopBtnDisabled} - onClick={() => - openModalWith( - 'ChangeNominations', - { - nominations: [], - bondType, - }, - 'small' - ) - } - /> - )} - </div> - </CardHeaderWrapper> - {nominated === null || isSyncing ? ( - <div className="head"> - <h4> - {!isSyncing && nominated === null - ? t('nominate.not_nominating') - : t('nominate.syncing')} - </h4> - </div> - ) : !nominator ? ( - <div className="head"> - <h4>{t('nominate.not_nominating')}</h4> - </div> - ) : ( - <> - {nominated.length > 0 ? ( - <div style={{ marginTop: '1rem' }}> - <ValidatorList - bondType={isPool ? 'pool' : 'stake'} - validators={nominated} - nominator={nominator} - batchKey={batchKey} - title={t('nominate.your_nominations')} - format="nomination" - selectable={ - !isReadOnlyAccount(activeAccount) && - (!isPool || isPoolNominator() || isPoolOwner()) - } - actions={ - isReadOnlyAccount(activeAccount) - ? [] - : [ - { - title: t('nominate.stop_nominating_selected'), - onClick: cbStopNominatingSelected, - onSelected: true, - }, - { - disabled: !favoritesList.length, - title: t('nominate.add_from_favorites'), - onClick: cbAddNominations, - onSelected: false, - }, - ] - } - refetchOnListUpdate - allowMoreCols - disableThrottle - /> - </div> - ) : ( - <div className="head"> - {poolDestroying ? ( - <h4>{t('nominate.pool_destroy')}</h4> - ) : ( - <h4>{t('nominate.not_nominating')}</h4> - )} - </div> - )} - </> - )} - </Wrapper> - ); -}; - -export default Nominations; diff --git a/src/pages/Nominate/Active/Stats/ActiveNominations.tsx b/src/pages/Nominate/Active/Stats/ActiveNominations.tsx deleted file mode 100644 index 67d5146185..0000000000 --- a/src/pages/Nominate/Active/Stats/ActiveNominations.tsx +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { useStaking } from 'contexts/Staking'; -import { Pie } from 'library/StatBoxList/Pie'; -import { useTranslation } from 'react-i18next'; - -export const ActiveNominationsStatBox = () => { - const { getNominationsStatus } = useStaking(); - const nominationStatuses = getNominationsStatus(); - const { t } = useTranslation('pages'); - - const total = Object.values(nominationStatuses).length; - const active = - Object.values(nominationStatuses).filter((_v) => _v === 'active').length ?? - 0; - - const params = { - label: t('nominate.active_nominations'), - stat: { - value: active, - total, - unit: '', - }, - graph: { - value1: active, - value2: active ? 0 : 1, - }, - tooltip: active ? 'Active' : undefined, - helpKey: 'Nominations', - }; - - return <Pie {...params} />; -}; - -export default ActiveNominationsStatBox; diff --git a/src/pages/Nominate/Active/Stats/ActiveNominators.tsx b/src/pages/Nominate/Active/Stats/ActiveNominators.tsx new file mode 100644 index 0000000000..393c4ee8ab --- /dev/null +++ b/src/pages/Nominate/Active/Stats/ActiveNominators.tsx @@ -0,0 +1,41 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import BigNumber from 'bignumber.js'; +import { useTranslation } from 'react-i18next'; +import { useApi } from 'contexts/Api'; +import { useStaking } from 'contexts/Staking'; +import { Pie } from 'library/StatBoxList/Pie'; + +export const ActiveNominatorsStat = () => { + const { t } = useTranslation('pages'); + const { consts } = useApi(); + const { maxElectingVoters } = consts; + const { totalActiveNominators } = useStaking().eraStakers; + + // active nominators as percent + let totalNominatorsAsPercent = 0; + if (maxElectingVoters.isGreaterThan(0)) { + totalNominatorsAsPercent = + totalActiveNominators / maxElectingVoters.dividedBy(100).toNumber(); + } + + const params = { + label: t('overview.activeNominators'), + stat: { + value: totalActiveNominators, + total: maxElectingVoters.toNumber(), + unit: '', + }, + graph: { + value1: totalActiveNominators, + value2: maxElectingVoters.minus(totalActiveNominators).toNumber(), + }, + tooltip: `${new BigNumber(totalNominatorsAsPercent) + .decimalPlaces(2) + .toFormat()}%`, + helpKey: 'Active Nominators', + }; + + return <Pie {...params} />; +}; diff --git a/src/pages/Nominate/Active/Stats/InactiveNominations.tsx b/src/pages/Nominate/Active/Stats/InactiveNominations.tsx deleted file mode 100644 index d759ef5664..0000000000 --- a/src/pages/Nominate/Active/Stats/InactiveNominations.tsx +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { useStaking } from 'contexts/Staking'; -import { Pie } from 'library/StatBoxList/Pie'; -import { useTranslation } from 'react-i18next'; - -export const ActiveNominationsStatBox = () => { - const { getNominationsStatus, isNominating } = useStaking(); - const nominationStatuses = getNominationsStatus(); - const { t } = useTranslation('pages'); - - const total = Object.values(nominationStatuses).length; - const inactive = - Object.values(nominationStatuses).filter((_v) => _v === 'inactive') - .length ?? 0; - - // inactive nominations as percent - let inactiveAsPercent = 0; - if (total > 0) { - inactiveAsPercent = inactive / (total * 0.01); - } - - const params = { - label: t('nominate.inactive_nominations'), - stat: { - value: inactive, - total, - unit: '', - }, - graph: { - value1: inactive, - value2: total - inactive, - }, - tooltip: isNominating() ? `${inactiveAsPercent}%` : undefined, - helpKey: 'Inactive Nominations', - }; - - return <Pie {...params} />; -}; - -export default ActiveNominationsStatBox; diff --git a/src/pages/Nominate/Active/Stats/MinimumActiveBond.tsx b/src/pages/Nominate/Active/Stats/MinimumActiveBond.tsx deleted file mode 100644 index cbd7a92b8e..0000000000 --- a/src/pages/Nominate/Active/Stats/MinimumActiveBond.tsx +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { useApi } from 'contexts/Api'; -import { useStaking } from 'contexts/Staking'; -import { Number } from 'library/StatBoxList/Number'; -import { useTranslation } from 'react-i18next'; - -export const MinimumActiveBondStatBox = () => { - const { network } = useApi(); - const { eraStakers } = useStaking(); - const { minActiveBond } = eraStakers; - const { t } = useTranslation('pages'); - - const params = { - label: t('nominate.minimum_active_bond'), - value: minActiveBond, - unit: network.unit, - helpKey: 'Bonding', - }; - - return <Number {...params} />; -}; - -export default MinimumActiveBondStatBox; diff --git a/src/pages/Nominate/Active/Stats/MinimumActiveStake.tsx b/src/pages/Nominate/Active/Stats/MinimumActiveStake.tsx new file mode 100644 index 0000000000..d6600c10e0 --- /dev/null +++ b/src/pages/Nominate/Active/Stats/MinimumActiveStake.tsx @@ -0,0 +1,27 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { planckToUnit } from '@polkadot-cloud/utils'; +import { useTranslation } from 'react-i18next'; +import { useNetworkMetrics } from 'contexts/NetworkMetrics'; +import { Number } from 'library/StatBoxList/Number'; +import { useNetwork } from 'contexts/Network'; + +export const MinimumActiveStakeStat = () => { + const { t } = useTranslation('pages'); + const { + networkData: { unit, units }, + } = useNetwork(); + const { metrics } = useNetworkMetrics(); + const { minimumActiveStake } = metrics; + + const params = { + label: t('nominate.minimumToEarnRewards'), + value: planckToUnit(minimumActiveStake, units).toNumber(), + decimals: 3, + unit: `${unit}`, + helpKey: 'Bonding', + }; + + return <Number {...params} />; +}; diff --git a/src/pages/Nominate/Active/Stats/MinimumNominatorBond.tsx b/src/pages/Nominate/Active/Stats/MinimumNominatorBond.tsx new file mode 100644 index 0000000000..4df7ef99a1 --- /dev/null +++ b/src/pages/Nominate/Active/Stats/MinimumNominatorBond.tsx @@ -0,0 +1,25 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { planckToUnit } from '@polkadot-cloud/utils'; +import { useTranslation } from 'react-i18next'; +import { useStaking } from 'contexts/Staking'; +import { Number } from 'library/StatBoxList/Number'; +import { useNetwork } from 'contexts/Network'; + +export const MinimumNominatorBondStat = () => { + const { t } = useTranslation('pages'); + const { staking } = useStaking(); + const { unit, units } = useNetwork().networkData; + const { minNominatorBond } = staking; + + const params = { + label: t('nominate.minimumToNominate'), + value: planckToUnit(minNominatorBond, units).toNumber(), + decimals: 3, + unit: `${unit}`, + helpKey: 'Bonding', + }; + + return <Number {...params} />; +}; diff --git a/src/pages/Nominate/Active/Stats/SupplyStaked.tsx b/src/pages/Nominate/Active/Stats/SupplyStaked.tsx deleted file mode 100644 index 6c33eae48c..0000000000 --- a/src/pages/Nominate/Active/Stats/SupplyStaked.tsx +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import BN from 'bn.js'; -import { useApi } from 'contexts/Api'; -import { useNetworkMetrics } from 'contexts/Network'; -import { useStaking } from 'contexts/Staking'; -import { Pie } from 'library/StatBoxList/Pie'; -import { toFixedIfNecessary } from 'Utils'; - -export const SupplyStakedStatBox = () => { - const { network } = useApi(); - const { units } = network; - const { metrics } = useNetworkMetrics(); - const { totalIssuance } = metrics; - const { staking } = useStaking(); - - const { lastTotalStake } = staking; - - // total supply as percent - let supplyAsPercent = 0; - if (totalIssuance.gt(new BN(0))) { - supplyAsPercent = lastTotalStake - .div(totalIssuance.div(new BN(100))) - .toNumber(); - } - - // base values - const lastTotalStakeBase = lastTotalStake.div(new BN(10 ** units)); - const totalIssuanceBase = totalIssuance.div(new BN(10 ** units)); - - const params = { - label: 'Total Supply Staked', - stat: { - value: lastTotalStakeBase.toNumber(), - unit: network.unit, - }, - graph: { - value1: lastTotalStakeBase.toNumber(), - value2: totalIssuanceBase.sub(lastTotalStakeBase).toNumber(), - }, - - tooltip: `${toFixedIfNecessary(supplyAsPercent, 2)}%`, - helpKey: 'Supply Staked', - }; - - return <Pie {...params} />; -}; - -export default SupplyStakedStatBox; diff --git a/src/pages/Nominate/Active/Status.tsx b/src/pages/Nominate/Active/Status.tsx deleted file mode 100644 index bcb2e86398..0000000000 --- a/src/pages/Nominate/Active/Status.tsx +++ /dev/null @@ -1,168 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { IconProp } from '@fortawesome/fontawesome-svg-core'; -import { faCircle } from '@fortawesome/free-regular-svg-icons'; -import { - faChevronCircleRight, - faRedoAlt, - faWallet, -} from '@fortawesome/free-solid-svg-icons'; -import { BN } from 'bn.js'; -import { PayeeStatus } from 'consts'; -import { useApi } from 'contexts/Api'; -import { useBalances } from 'contexts/Balances'; -import { useConnect } from 'contexts/Connect'; -import { useModal } from 'contexts/Modal'; -import { useStaking } from 'contexts/Staking'; -import { useUi } from 'contexts/UI'; -import { useValidators } from 'contexts/Validators'; -import { CardWrapper } from 'library/Graphs/Wrappers'; -import Stat from 'library/Stat'; -import { useTranslation } from 'react-i18next'; -import { planckBnToUnit, rmCommas } from 'Utils'; -import { Separator } from 'Wrappers'; -import { Controller } from './Controller'; - -export const Status = ({ height }: { height: number }) => { - const { isReady, network } = useApi(); - const { setOnNominatorSetup, getStakeSetupProgressPercent }: any = useUi(); - const { openModalWith } = useModal(); - const { activeAccount, isReadOnlyAccount } = useConnect(); - const { isSyncing } = useUi(); - const { getNominationsStatus, staking, inSetup, eraStakers } = useStaking(); - const { getAccountNominations } = useBalances(); - const { stakers } = eraStakers; - const { payee } = staking; - const { meta, validators } = useValidators(); - const nominations = getAccountNominations(activeAccount); - const { t } = useTranslation('pages'); - - // get nomination status - const nominationStatuses = getNominationsStatus(); - - // get active nominations - const activeNominees = Object.entries(nominationStatuses) - .map(([k, v]: any) => (v === 'active' ? k : false)) - .filter((v) => v !== false); - - // check if rewards are being earned - const stake = meta.validators_browse?.stake ?? []; - const stakeSynced = stake.length > 0 ?? false; - - let earningRewards = false; - if (stakeSynced) { - for (const nominee of activeNominees) { - const validator = validators.find((v: any) => v.address === nominee); - if (validator) { - const batchIndex = validators.indexOf(validator); - const nomineeMeta = stake[batchIndex]; - const { lowestReward } = nomineeMeta; - - const validatorInEra = - stakers.find((s: any) => s.address === nominee) || null; - - if (validatorInEra) { - const { others } = validatorInEra; - const stakedValue = - others?.find((o: any) => o.who === activeAccount)?.value ?? false; - if (stakedValue) { - const stakedValueBase = planckBnToUnit( - new BN(rmCommas(stakedValue)), - network.units - ); - if (stakedValueBase >= lowestReward) { - earningRewards = true; - break; - } - } - } - } - } - } - - const payeeStatus = PayeeStatus.find((item) => item.key === payee); - - let startTitle = t('nominate.start_nominating'); - if (inSetup()) { - const progress = getStakeSetupProgressPercent(activeAccount); - if (progress > 0) { - startTitle += `: ${progress}%`; - } - } - return ( - <CardWrapper height={height}> - <Stat - label={t('nominate.status')} - helpKey="Nomination Status" - stat={ - inSetup() || isSyncing - ? t('nominate.not_nominating') - : !nominations.length - ? t('nominate.no_nominations_set') - : activeNominees.length - ? `${t('nominate.nominating_and')} ${ - earningRewards - ? t('nominate.earning_rewards') - : t('nominate.not_earning_rewards') - }` - : t('nominate.waiting_for_active_nominations') - } - buttons={ - !inSetup() - ? [] - : [ - { - title: startTitle, - icon: faChevronCircleRight, - transform: 'grow-1', - large: true, - disabled: - !isReady || - isReadOnlyAccount(activeAccount) || - !activeAccount, - onClick: () => setOnNominatorSetup(1), - }, - ] - } - /> - <Separator /> - <Stat - label={t('nominate.reward_destination')} - helpKey="Reward Destination" - icon={ - (payee === null - ? faCircle - : payee === 'Staked' - ? faRedoAlt - : payee === 'None' - ? faCircle - : faWallet) as IconProp - } - stat={ - inSetup() - ? t('nominate.not_assigned') - : payeeStatus?.name ?? t('nominate.not_assigned') - } - buttons={ - !inSetup() - ? [ - { - title: t('nominate.update'), - icon: faWallet, - small: true, - disabled: - inSetup() || isSyncing || isReadOnlyAccount(activeAccount), - onClick: () => openModalWith('UpdatePayee', {}, 'small'), - }, - ] - : [] - } - /> - <Separator /> - <Controller label={t('nominate.controller_account')} /> - </CardWrapper> - ); -}; - -export default Status; diff --git a/src/pages/Nominate/Active/Status/NominationStatus.tsx b/src/pages/Nominate/Active/Status/NominationStatus.tsx new file mode 100644 index 0000000000..1a2bf33222 --- /dev/null +++ b/src/pages/Nominate/Active/Status/NominationStatus.tsx @@ -0,0 +1,109 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { + faBolt, + faChevronCircleRight, + faSignOutAlt, +} from '@fortawesome/free-solid-svg-icons'; +import { useTranslation } from 'react-i18next'; +import { useApi } from 'contexts/Api'; +import { useBonded } from 'contexts/Bonded'; +import { useFastUnstake } from 'contexts/FastUnstake'; +import { useNetworkMetrics } from 'contexts/NetworkMetrics'; +import { useSetup } from 'contexts/Setup'; +import { useStaking } from 'contexts/Staking'; +import { useUi } from 'contexts/UI'; +import { useNominationStatus } from 'library/Hooks/useNominationStatus'; +import { useUnstaking } from 'library/Hooks/useUnstaking'; +import { Stat } from 'library/Stat'; +import { useOverlay } from '@polkadot-cloud/react/hooks'; +import { useActiveAccounts } from 'contexts/ActiveAccounts'; +import { useImportedAccounts } from 'contexts/Connect/ImportedAccounts'; + +export const NominationStatus = ({ + showButtons = true, + buttonType = 'primary', +}: { + showButtons?: boolean; + buttonType?: string; +}) => { + const { t } = useTranslation('pages'); + const { isReady } = useApi(); + const { inSetup } = useStaking(); + const { isNetworkSyncing } = useUi(); + const { openModal } = useOverlay().modal; + const { metrics } = useNetworkMetrics(); + const { getBondedAccount } = useBonded(); + const { checking, isExposed } = useFastUnstake(); + const { isReadOnlyAccount } = useImportedAccounts(); + const { getNominationStatus } = useNominationStatus(); + const { activeAccount } = useActiveAccounts(); + const { getFastUnstakeText, isUnstaking } = useUnstaking(); + const { setOnNominatorSetup, getNominatorSetupPercent } = useSetup(); + + const fastUnstakeText = getFastUnstakeText(); + const controller = getBondedAccount(activeAccount); + const { fastUnstakeErasToCheckPerBlock } = metrics; + const nominationStatus = getNominationStatus(activeAccount, 'nominator'); + + // Determine whether to display fast unstake button or regular unstake button. + const unstakeButton = + fastUnstakeErasToCheckPerBlock > 0 && + !nominationStatus.nominees.active.length && + (checking || !isExposed) + ? { + disabled: checking || isReadOnlyAccount(controller), + title: fastUnstakeText, + icon: faBolt, + onClick: () => { + openModal({ key: 'ManageFastUnstake', size: 'sm' }); + }, + } + : { + title: t('nominate.unstake'), + icon: faSignOutAlt, + disabled: !isReady || isReadOnlyAccount(controller) || !activeAccount, + onClick: () => openModal({ key: 'Unstake', size: 'sm' }), + }; + + // Display progress alongside start title if exists and in setup. + let startTitle = t('nominate.startNominating'); + if (inSetup()) { + const progress = getNominatorSetupPercent(activeAccount); + if (progress > 0) { + startTitle += `: ${progress}%`; + } + } + + return ( + <Stat + label={t('nominate.status')} + helpKey="Nomination Status" + stat={nominationStatus.message} + buttons={ + !showButtons + ? [] + : !inSetup() + ? !isUnstaking + ? [unstakeButton] + : [] + : isNetworkSyncing + ? [] + : [ + { + title: startTitle, + icon: faChevronCircleRight, + transform: 'grow-1', + disabled: + !isReady || + isReadOnlyAccount(activeAccount) || + !activeAccount, + onClick: () => setOnNominatorSetup(true), + }, + ] + } + buttonType={buttonType} + /> + ); +}; diff --git a/src/pages/Nominate/Active/Status/PayoutDestinationStatus.tsx b/src/pages/Nominate/Active/Status/PayoutDestinationStatus.tsx new file mode 100644 index 0000000000..fa7ca94ddb --- /dev/null +++ b/src/pages/Nominate/Active/Status/PayoutDestinationStatus.tsx @@ -0,0 +1,72 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { faGear, faWallet } from '@fortawesome/free-solid-svg-icons'; +import { useTranslation } from 'react-i18next'; +import { useStaking } from 'contexts/Staking'; +import { useUi } from 'contexts/UI'; +import { usePayeeConfig } from 'library/Hooks/usePayeeConfig'; +import { useUnstaking } from 'library/Hooks/useUnstaking'; +import { Stat } from 'library/Stat'; +import { useOverlay } from '@polkadot-cloud/react/hooks'; +import { useActiveAccounts } from 'contexts/ActiveAccounts'; +import { useImportedAccounts } from 'contexts/Connect/ImportedAccounts'; + +export const PayoutDestinationStatus = () => { + const { t } = useTranslation('pages'); + const { isSyncing } = useUi(); + const { openModal } = useOverlay().modal; + const { staking, inSetup } = useStaking(); + const { isFastUnstaking } = useUnstaking(); + const { getPayeeItems } = usePayeeConfig(); + const { activeAccount } = useActiveAccounts(); + const { isReadOnlyAccount } = useImportedAccounts(); + const { payee } = staking; + + // Get payee status text to display. + const getPayeeStatus = () => { + if (inSetup()) { + return t('nominate.notAssigned'); + } + const status = getPayeeItems(true).find( + ({ value }) => value === payee.destination + )?.activeTitle; + + if (status) { + return status; + } + return t('nominate.notAssigned'); + }; + + // Get the payee destination icon to display, falling back to wallet icon. + const payeeIcon = inSetup() + ? undefined + : getPayeeItems(true).find(({ value }) => value === payee.destination) + ?.icon || faWallet; + + return ( + <Stat + label={t('nominate.payoutDestination')} + helpKey="Payout Destination" + icon={payeeIcon} + stat={getPayeeStatus()} + buttons={ + !inSetup() + ? [ + { + title: t('nominate.update'), + icon: faGear, + small: true, + disabled: + inSetup() || + isSyncing || + isReadOnlyAccount(activeAccount) || + isFastUnstaking, + onClick: () => openModal({ key: 'UpdatePayee', size: 'sm' }), + }, + ] + : [] + } + /> + ); +}; diff --git a/src/pages/Nominate/Active/Status/UnclaimedPayoutsStatus.tsx b/src/pages/Nominate/Active/Status/UnclaimedPayoutsStatus.tsx new file mode 100644 index 0000000000..8fc09bd1e8 --- /dev/null +++ b/src/pages/Nominate/Active/Status/UnclaimedPayoutsStatus.tsx @@ -0,0 +1,68 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { useTranslation } from 'react-i18next'; +import { Stat } from 'library/Stat'; +import { usePayouts } from 'contexts/Payouts'; +import BigNumber from 'bignumber.js'; +import { useApi } from 'contexts/Api'; +import { minDecimalPlaces, planckToUnit } from '@polkadot-cloud/utils'; +import { faCircleDown } from '@fortawesome/free-solid-svg-icons'; +import { useOverlay } from '@polkadot-cloud/react/hooks'; +import { useNetwork } from 'contexts/Network'; +import { useActiveAccounts } from 'contexts/ActiveAccounts'; +import { useImportedAccounts } from 'contexts/Connect/ImportedAccounts'; + +export const UnclaimedPayoutsStatus = () => { + const { t } = useTranslation(); + const { isReady } = useApi(); + const { + networkData: { units }, + } = useNetwork(); + const { openModal } = useOverlay().modal; + const { unclaimedPayouts } = usePayouts(); + const { activeAccount } = useActiveAccounts(); + const { isReadOnlyAccount } = useImportedAccounts(); + + const totalUnclaimed = Object.values(unclaimedPayouts || {}).reduce( + (total, validators) => + Object.values(validators) + .reduce((amount, value) => amount.plus(value), new BigNumber(0)) + .plus(total), + new BigNumber(0) + ); + + return ( + <Stat + label={t('nominate.pendingPayouts', { ns: 'pages' })} + helpKey="Payout" + type="odometer" + stat={{ + value: minDecimalPlaces( + planckToUnit(totalUnclaimed, units).toFormat(), + 2 + ), + }} + buttons={ + Object.keys(unclaimedPayouts || {}).length > 0 + ? [ + { + title: t('claim', { ns: 'modals' }), + icon: faCircleDown, + disabled: !isReady || isReadOnlyAccount(activeAccount), + small: true, + onClick: () => + openModal({ + key: 'ClaimPayouts', + size: 'sm', + options: { + disableWindowResize: true, + }, + }), + }, + ] + : undefined + } + /> + ); +}; diff --git a/src/pages/Nominate/Active/Status/index.tsx b/src/pages/Nominate/Active/Status/index.tsx new file mode 100644 index 0000000000..1f28051986 --- /dev/null +++ b/src/pages/Nominate/Active/Status/index.tsx @@ -0,0 +1,18 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { Separator } from '@polkadot-cloud/react'; +import { CardWrapper } from 'library/Card/Wrappers'; +import { UnclaimedPayoutsStatus } from './UnclaimedPayoutsStatus'; +import { NominationStatus } from './NominationStatus'; +import { PayoutDestinationStatus } from './PayoutDestinationStatus'; + +export const Status = ({ height }: { height: number }) => ( + <CardWrapper height={height}> + <NominationStatus /> + <Separator /> + <UnclaimedPayoutsStatus /> + <Separator /> + <PayoutDestinationStatus /> + </CardWrapper> +); diff --git a/src/pages/Nominate/Active/UnstakePrompts.tsx b/src/pages/Nominate/Active/UnstakePrompts.tsx new file mode 100644 index 0000000000..8cf09fc07c --- /dev/null +++ b/src/pages/Nominate/Active/UnstakePrompts.tsx @@ -0,0 +1,99 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { faBolt, faLockOpen } from '@fortawesome/free-solid-svg-icons'; +import { ButtonPrimary, ButtonRow, PageRow } from '@polkadot-cloud/react'; +import { isNotZero } from '@polkadot-cloud/utils'; +import { useTranslation } from 'react-i18next'; +import { useTheme } from 'contexts/Themes'; +import { useTransferOptions } from 'contexts/TransferOptions'; +import { useUi } from 'contexts/UI'; +import { CardWrapper } from 'library/Card/Wrappers'; +import { useUnstaking } from 'library/Hooks/useUnstaking'; +import { useOverlay } from '@polkadot-cloud/react/hooks'; +import { useNetwork } from 'contexts/Network'; +import { useActiveAccounts } from 'contexts/ActiveAccounts'; + +export const UnstakePrompts = () => { + const { t } = useTranslation('pages'); + const { unit, colors } = useNetwork().networkData; + const { activeAccount } = useActiveAccounts(); + const { mode } = useTheme(); + const { openModal } = useOverlay().modal; + const { isNetworkSyncing } = useUi(); + const { isFastUnstaking, isUnstaking, getFastUnstakeText } = useUnstaking(); + const { getTransferOptions } = useTransferOptions(); + const { active, totalUnlockChuncks, totalUnlocked, totalUnlocking } = + getTransferOptions(activeAccount).nominate; + const annuncementBorderColor = colors.secondary[mode]; + + // unstaking can withdraw + const canWithdrawUnlocks = + isUnstaking && + active.isZero() && + totalUnlocking.isZero() && + isNotZero(totalUnlocked); + + return ( + <> + {(isUnstaking || isFastUnstaking) && !isNetworkSyncing && ( + <PageRow> + <CardWrapper + style={{ border: `1px solid ${annuncementBorderColor}` }} + > + <div className="content"> + <h3> + {t('nominate.unstakePromptInProgress', { + context: isFastUnstaking ? 'fast' : 'regular', + })} + </h3> + <h4> + {isFastUnstaking + ? t('nominate.unstakePromptInQueue') + : !canWithdrawUnlocks + ? t('nominate.unstakePromptWaitingForUnlocks') + : `${t('nominate.unstakePromptReadyToWithdraw')} ${t( + 'nominate.unstakePromptRevert', + { unit } + )}`} + </h4> + <ButtonRow yMargin> + {isFastUnstaking ? ( + <ButtonPrimary + marginRight + iconLeft={faBolt} + text={getFastUnstakeText()} + onClick={() => + openModal({ key: 'ManageFastUnstake', size: 'sm' }) + } + /> + ) : ( + <ButtonPrimary + iconLeft={faLockOpen} + text={ + canWithdrawUnlocks + ? t('nominate.unlocked') + : String(totalUnlockChuncks ?? 0) + } + disabled={false} + onClick={() => + openModal({ + key: 'UnlockChunks', + options: { + bondFor: 'nominator', + poolClosure: true, + disableWindowResize: true, + }, + size: 'sm', + }) + } + /> + )} + </ButtonRow> + </div> + </CardWrapper> + </PageRow> + )} + </> + ); +}; diff --git a/src/pages/Nominate/Active/index.tsx b/src/pages/Nominate/Active/index.tsx index ba4198d163..a98e757078 100644 --- a/src/pages/Nominate/Active/index.tsx +++ b/src/pages/Nominate/Active/index.tsx @@ -1,110 +1,108 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only import { faChevronCircleRight } from '@fortawesome/free-solid-svg-icons'; -import { ButtonPrimary } from '@rossbulat/polkadot-dashboard-ui'; -import { SectionFullWidthThreshold, SideMenuStickyThreshold } from 'consts'; -import { useBalances } from 'contexts/Balances'; -import { useConnect } from 'contexts/Connect'; -import { useModal } from 'contexts/Modal'; +import { + ButtonHelp, + ButtonPrimary, + PageRow, + PageTitle, + RowSection, +} from '@polkadot-cloud/react'; +import { useTranslation } from 'react-i18next'; +import { useHelp } from 'contexts/Help'; import { useStaking } from 'contexts/Staking'; import { useUi } from 'contexts/UI'; -import { GenerateNominations } from 'library/GenerateNominations'; -import { CardHeaderWrapper, CardWrapper } from 'library/Graphs/Wrappers'; -import { OpenHelpIcon } from 'library/OpenHelpIcon'; -import { PageTitle } from 'library/PageTitle'; +import { CardHeaderWrapper, CardWrapper } from 'library/Card/Wrappers'; +import { useUnstaking } from 'library/Hooks/useUnstaking'; import { StatBoxList } from 'library/StatBoxList'; -import { useTranslation } from 'react-i18next'; -import { - PageRowWrapper, - RowPrimaryWrapper, - RowSecondaryWrapper, -} from 'Wrappers'; -import { ControllerNotImported } from './ControllerNotImported'; +import { useOverlay } from '@polkadot-cloud/react/hooks'; +import { useActiveAccounts } from 'contexts/ActiveAccounts'; +import { Nominations } from 'library/Nominations'; +import { useValidators } from 'contexts/Validators/ValidatorEntries'; +import { ListStatusHeader } from 'library/List'; +import { ControllerNotStash } from './ControllerNotStash'; import { ManageBond } from './ManageBond'; -import { Nominations } from './Nominations'; -import ActiveNominationsStatBox from './Stats/ActiveNominations'; -import InacctiveNominationsStatBox from './Stats/InactiveNominations'; -import MinimumActiveBondStatBox from './Stats/MinimumActiveBond'; +import { ActiveNominatorsStat } from './Stats/ActiveNominators'; +import { MinimumActiveStakeStat } from './Stats/MinimumActiveStake'; +import { MinimumNominatorBondStat } from './Stats/MinimumNominatorBond'; import { Status } from './Status'; +import { UnstakePrompts } from './UnstakePrompts'; export const Active = () => { - const { openModalWith } = useModal(); - const { activeAccount } = useConnect(); + const { t } = useTranslation(); const { isSyncing } = useUi(); - const { targets, setTargets, inSetup } = useStaking(); - const { getAccountNominations } = useBalances(); - const nominations = getAccountNominations(activeAccount); - const { t } = useTranslation('pages'); + const { openHelp } = useHelp(); + const { inSetup } = useStaking(); + const { nominated } = useValidators(); + const { isFastUnstaking } = useUnstaking(); + const { openCanvas } = useOverlay().canvas; + const { activeAccount } = useActiveAccounts(); - const ROW_HEIGHT = 275; + const ROW_HEIGHT = 220; return ( <> - <PageTitle title={t('nominate.nominate')} /> + <PageTitle title={t('nominate.nominate', { ns: 'pages' })} /> <StatBoxList> - <MinimumActiveBondStatBox /> - <ActiveNominationsStatBox /> - <InacctiveNominationsStatBox /> + <ActiveNominatorsStat /> + <MinimumNominatorBondStat /> + <MinimumActiveStakeStat /> </StatBoxList> - <ControllerNotImported /> - <PageRowWrapper className="page-padding" noVerticalSpacer> - <RowPrimaryWrapper - hOrder={1} - vOrder={0} - thresholdStickyMenu={SideMenuStickyThreshold} - thresholdFullWidth={SectionFullWidthThreshold} - > + <ControllerNotStash /> + <UnstakePrompts /> + <PageRow> + <RowSection hLast> <Status height={ROW_HEIGHT} /> - </RowPrimaryWrapper> - <RowSecondaryWrapper - hOrder={0} - vOrder={1} - thresholdStickyMenu={SideMenuStickyThreshold} - thresholdFullWidth={SectionFullWidthThreshold} - > + </RowSection> + <RowSection secondary> <CardWrapper height={ROW_HEIGHT}> <ManageBond /> </CardWrapper> - </RowSecondaryWrapper> - </PageRowWrapper> - <PageRowWrapper className="page-padding" noVerticalSpacer> + </RowSection> + </PageRow> + <PageRow> <CardWrapper> - {nominations.length || inSetup() || isSyncing ? ( - <Nominations bondType="stake" nominator={activeAccount} /> + {nominated?.length || inSetup() || isSyncing ? ( + <Nominations bondFor="nominator" nominator={activeAccount} /> ) : ( <> - <CardHeaderWrapper withAction> + <CardHeaderWrapper $withAction $withMargin> <h3> - {t('nominate.start_nominating')} - <OpenHelpIcon helpKey="Nominations" /> + {t('nominate.nominate', { ns: 'pages' })} + <ButtonHelp + marginLeft + onClick={() => openHelp('Nominations')} + /> </h3> <div> <ButtonPrimary - text={t('nominate.nominate')} iconLeft={faChevronCircleRight} iconTransform="grow-1" - disabled={targets.length === 0 || inSetup() || isSyncing} - onClick={() => openModalWith('Nominate', {}, 'small')} + text={t('nominate.nominate', { ns: 'pages' })} + disabled={inSetup() || isSyncing || isFastUnstaking} + onClick={() => + openCanvas({ + key: 'ManageNominations', + scroll: false, + options: { + bondFor: 'nominator', + nominator: activeAccount, + nominated, + }, + size: 'xl', + }) + } /> </div> </CardHeaderWrapper> - <GenerateNominations - batchKey="generate_nominations_active" - setters={[ - { - set: setTargets, - current: targets, - }, - ]} - nominations={targets.nominations} - /> + <ListStatusHeader> + {t('notNominating', { ns: 'library' })}. + </ListStatusHeader> </> )} </CardWrapper> - </PageRowWrapper> + </PageRow> </> ); }; - -export default Active; diff --git a/src/pages/Nominate/Active/types.ts b/src/pages/Nominate/Active/types.ts new file mode 100644 index 0000000000..27c8ddc97d --- /dev/null +++ b/src/pages/Nominate/Active/types.ts @@ -0,0 +1,12 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import type BigNumber from 'bignumber.js'; + +export interface BondedChartProps { + active: BigNumber; + free: BigNumber; + unlocking: BigNumber; + unlocked: BigNumber; + inactive: boolean; +} diff --git a/src/pages/Nominate/Setup/Bond/index.tsx b/src/pages/Nominate/Setup/Bond/index.tsx index ae4c2c732d..2b842645ac 100644 --- a/src/pages/Nominate/Setup/Bond/index.tsx +++ b/src/pages/Nominate/Setup/Bond/index.tsx @@ -1,33 +1,32 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import { BN } from 'bn.js'; -import { useConnect } from 'contexts/Connect'; -import { useTxFees } from 'contexts/TxFees'; -import { useUi } from 'contexts/UI'; -import { SetupType } from 'contexts/UI/types'; +import BigNumber from 'bignumber.js'; +import { useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useSetup } from 'contexts/Setup'; +import { useTxMeta } from 'contexts/TxMeta'; import { BondFeedback } from 'library/Form/Bond/BondFeedback'; import { NominateStatusBar } from 'library/Form/NominateStatusBar'; import { Footer } from 'library/SetupSteps/Footer'; import { Header } from 'library/SetupSteps/Header'; import { MotionContainer } from 'library/SetupSteps/MotionContainer'; -import { SetupStepProps } from 'library/SetupSteps/types'; -import { useEffect, useState } from 'react'; -import { useTranslation } from 'react-i18next'; +import type { SetupStepProps } from 'library/SetupSteps/types'; +import { useActiveAccounts } from 'contexts/ActiveAccounts'; -export const Bond = (props: SetupStepProps) => { - const { section } = props; - const { activeAccount } = useConnect(); - const { txFees } = useTxFees(); - const { getSetupProgress, setActiveAccountSetup } = useUi(); - const setup = getSetupProgress(SetupType.Stake, activeAccount); +export const Bond = ({ section }: SetupStepProps) => { const { t } = useTranslation('pages'); + const { activeAccount } = useActiveAccounts(); + const { txFees } = useTxMeta(); + const { getSetupProgress, setActiveAccountSetup } = useSetup(); + const setup = getSetupProgress('nominator', activeAccount); + const { progress } = setup; // either free to bond or existing setup value - const initialBondValue = setup.bond === 0 ? 0 : setup.bond; + const initialBondValue = progress.bond === '0' ? '0' : progress.bond; // store local bond amount for form control - const [bond, setBond] = useState({ + const [bond, setBond] = useState<{ bond: string }>({ bond: initialBondValue, }); @@ -36,7 +35,7 @@ export const Bond = (props: SetupStepProps) => { // handler for updating bond const handleSetupUpdate = (value: any) => { - setActiveAccountSetup(SetupType.Stake, value); + setActiveAccountSetup('nominator', value); }; // update bond on account change @@ -50,8 +49,8 @@ export const Bond = (props: SetupStepProps) => { useEffect(() => { // only update if Bond is currently active if (setup.section === section) { - setActiveAccountSetup(SetupType.Stake, { - ...setup, + setActiveAccountSetup('nominator', { + ...progress, bond: initialBondValue, }); } @@ -61,22 +60,22 @@ export const Bond = (props: SetupStepProps) => { <> <Header thisSection={section} - complete={setup.bond !== 0} - title={t('nominate.bond') || ''} + complete={progress.bond !== '0' && progress.bond !== ''} + title={t('nominate.bond')} helpKey="Bonding" - setupType={SetupType.Stake} + bondFor="nominator" /> <MotionContainer thisSection={section} activeSection={setup.section}> <BondFeedback - syncing={txFees.eq(new BN(0))} - bondType="stake" + syncing={txFees.isZero()} + bondFor="nominator" inSetup - listenIsValid={setBondValid} + listenIsValid={(valid) => setBondValid(valid)} defaultBond={initialBondValue} setters={[ { set: handleSetupUpdate, - current: setup, + current: progress, }, { set: setBond, @@ -86,11 +85,9 @@ export const Bond = (props: SetupStepProps) => { txFees={txFees} maxWidth /> - <NominateStatusBar value={bond.bond} /> - <Footer complete={bondValid} setupType={SetupType.Stake} /> + <NominateStatusBar value={new BigNumber(bond.bond)} /> + <Footer complete={bondValid} bondFor="nominator" /> </MotionContainer> </> ); }; - -export default Bond; diff --git a/src/pages/Nominate/Setup/Payee/Wrappers.tsx b/src/pages/Nominate/Setup/Payee/Wrappers.tsx deleted file mode 100644 index 7251fb8461..0000000000 --- a/src/pages/Nominate/Setup/Payee/Wrappers.tsx +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { SectionFullWidthThreshold } from 'consts'; -import styled from 'styled-components'; -import { - borderPrimary, - buttonPrimaryBackground, - networkColor, - textPrimary, - textSecondary, -} from 'theme'; - -export const Items = styled.div` - position: relative; - margin: 0.75rem 0 0; - width: 100%; - border-radius: 0.75rem; - padding: 0.25rem; - overflow: auto; - display: flex; - flex-flow: row wrap; - flex: 1; -`; - -export const Item = styled.button<{ selected?: boolean }>` - flex-basis: 33%; - @media (max-width: ${SectionFullWidthThreshold}px) { - flex-basis: 100%; - } - padding: 0.5rem; - - > div { - background: ${buttonPrimaryBackground}; - border: 1.5px solid - ${(props) => (props.selected ? networkColor : borderPrimary)}; - width: 100%; - border-radius: 1rem; - display: flex; - flex-flow: row wrap; - justify-content: flex-start; - align-items: center; - padding: 1.25rem; - - > div { - width: 100%; - } - h3 { - color: ${textPrimary}; - font-size: 1.2rem; - } - &:first-child { - margin-left: 0rem; - } - &:last-child { - margin-right: 0rem; - } - p { - color: ${textSecondary}; - margin: 0.5rem 0 0 0; - text-align: left; - } - } -`; diff --git a/src/pages/Nominate/Setup/Payee/index.tsx b/src/pages/Nominate/Setup/Payee/index.tsx index 25162b5d26..6639fc2520 100644 --- a/src/pages/Nominate/Setup/Payee/index.tsx +++ b/src/pages/Nominate/Setup/Payee/index.tsx @@ -1,104 +1,110 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import { useConnect } from 'contexts/Connect'; -import { useUi } from 'contexts/UI'; -import { SetupType } from 'contexts/UI/types'; +import { useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useSetup } from 'contexts/Setup'; +import type { PayeeConfig, PayeeOptions } from 'contexts/Setup/types'; +import { Spacer } from 'library/Form/Wrappers'; +import { usePayeeConfig } from 'library/Hooks/usePayeeConfig'; +import { PayeeInput } from 'library/PayeeInput'; +import { SelectItems } from 'library/SelectItems'; +import { SelectItem } from 'library/SelectItems/Item'; import { Footer } from 'library/SetupSteps/Footer'; import { Header } from 'library/SetupSteps/Header'; import { MotionContainer } from 'library/SetupSteps/MotionContainer'; -import { SetupStepProps } from 'library/SetupSteps/types'; -import { useEffect, useState } from 'react'; -import { useTranslation } from 'react-i18next'; -import { isNumeric } from 'Utils'; -import { Spacer } from '../../Wrappers'; -import { Item, Items } from './Wrappers'; - -export const Payee = (props: SetupStepProps) => { - const { section } = props; +import type { SetupStepProps } from 'library/SetupSteps/types'; +import type { MaybeAddress } from 'types'; +import { useActiveAccounts } from 'contexts/ActiveAccounts'; +import { Subheading } from 'pages/Nominate/Wrappers'; - const { activeAccount } = useConnect(); - const { getSetupProgress, setActiveAccountSetup } = useUi(); - const setup = getSetupProgress(SetupType.Stake, activeAccount); +export const Payee = ({ section }: SetupStepProps) => { const { t } = useTranslation('pages'); + const { getPayeeItems } = usePayeeConfig(); + const { activeAccount } = useActiveAccounts(); + const { getSetupProgress, setActiveAccountSetup } = useSetup(); - const options = ['Staked', 'Stash', 'Controller']; - const buttons = [ - { - title: t('nominate.back_to_staking'), - subtitle: t('nominate.automatically_bonded'), - index: 0, - }, - { - title: t('nominate.to_stash'), - subtitle: t('nominate.sent_to_stash'), - index: 1, - }, - { - title: t('nominate.to_controller'), - subtitle: t('nominate.sent_to_controller'), - index: 2, - }, - ]; + const setup = getSetupProgress('nominator', activeAccount); + const { progress } = setup; + const { payee } = progress; - const [payee, setPayee]: any = useState(setup.payee); + // Store the current user-inputted custom payout account. + const [account, setAccount] = useState<MaybeAddress>(payee.account); - // update selected value on account switch - useEffect(() => { - setPayee(setup.payee); - }, [activeAccount]); + const DefaultPayeeConfig: PayeeConfig = { + destination: 'Staked', + account: null, + }; - const handleChangePayee = (i: number) => { - // not in options - if (!isNumeric(i)) { - return; - } - if (i >= options.length) { - return; - } + // determine whether this section is completed. + const isComplete = () => + payee.destination !== null && + !(payee.destination === 'Account' && payee.account === null); - // set local value to update input element - setPayee(options[i]); - // set setup payee - setActiveAccountSetup(SetupType.Stake, { - ...setup, - payee: options[i], + // update setup progress with payee config. + const handleChangeDestination = (destination: PayeeOptions) => { + // set local value to update input element set setup payee + setActiveAccountSetup('nominator', { + ...progress, + payee: { destination, account }, }); }; + // update setup progress with payee account. + const handleChangeAccount = (newAccount: MaybeAddress) => { + // set local value to update input element set setup payee + setActiveAccountSetup('nominator', { + ...progress, + payee: { ...payee, account: newAccount }, + }); + }; + + // set initial payee value to `Staked` if not yet set. + useEffect(() => { + if (!payee || (!payee.destination && !payee.account)) { + setActiveAccountSetup('nominator', { + ...progress, + payee: DefaultPayeeConfig, + }); + } + }, [activeAccount]); + return ( <> <Header thisSection={section} - complete={setup.payee !== null} - title={t('nominate.reward_destination') || ''} - helpKey="Reward Destination" - setupType={SetupType.Stake} + complete={isComplete()} + title={t('nominate.payoutDestination')} + helpKey="Payout Destination" + bondFor="nominator" /> <MotionContainer thisSection={section} activeSection={setup.section}> + <Subheading> + <h4>{t('nominate.payoutDestinationSubtitle')}</h4> + </Subheading> + + <SelectItems layout="three-col"> + {getPayeeItems().map((item) => ( + <SelectItem + key={`payee_option_${item.value}`} + account={account} + setAccount={setAccount} + selected={payee.destination === item.value} + onClick={() => handleChangeDestination(item.value)} + layout="three-col" + {...item} + /> + ))} + </SelectItems> <Spacer /> - <Items> - {buttons.map((item: any, index: number) => { - return ( - <Item - key={`payee_option_${index}`} - selected={payee === options[item.index]} - onClick={() => handleChangePayee(item.index)} - > - <div> - <h3>{item.title}</h3> - <div> - <p>{item.subtitle}</p> - </div> - </div> - </Item> - ); - })} - </Items> - <Footer complete={setup.payee !== null} setupType={SetupType.Stake} /> + <PayeeInput + payee={payee} + account={account} + setAccount={setAccount} + handleChange={handleChangeAccount} + /> + <Footer complete={isComplete()} bondFor="nominator" /> </MotionContainer> </> ); }; - -export default Payee; diff --git a/src/pages/Nominate/Setup/SetController.tsx b/src/pages/Nominate/Setup/SetController.tsx deleted file mode 100644 index dd37ccedc6..0000000000 --- a/src/pages/Nominate/Setup/SetController.tsx +++ /dev/null @@ -1,108 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { useApi } from 'contexts/Api'; -import { useConnect } from 'contexts/Connect'; -import { useUi } from 'contexts/UI'; -import { SetupType } from 'contexts/UI/types'; -import { AccountSelect } from 'library/Form/AccountSelect'; -import { InputItem } from 'library/Form/types'; -import { getEligibleControllers } from 'library/Form/Utils/getEligibleControllers'; -import { Warning } from 'library/Form/Warning'; -import { Footer } from 'library/SetupSteps/Footer'; -import { Header } from 'library/SetupSteps/Header'; -import { MotionContainer } from 'library/SetupSteps/MotionContainer'; -import { SetupStepProps } from 'library/SetupSteps/types'; -import { useEffect, useState } from 'react'; -import { useTranslation } from 'react-i18next'; -import { planckBnToUnit } from 'Utils'; -import { Spacer } from '../Wrappers'; - -export const SetController = (props: SetupStepProps) => { - const { section } = props; - const { t } = useTranslation('pages'); - - const { consts, network } = useApi(); - const { activeAccount, accounts, getAccount } = useConnect(); - const { getSetupProgress, setActiveAccountSetup } = useUi(); - const setup = getSetupProgress(SetupType.Stake, activeAccount); - const { existentialDeposit } = consts; - const existentialDepositBase = planckBnToUnit( - existentialDeposit, - network.units - ); - - // store the currently selected controller account - const _selected = setup.controller !== null ? setup.controller : null; - const [selected, setSelected] = useState<InputItem | null>( - getAccount(_selected) - ); - - // get eligible controllers for input - const items = getEligibleControllers(); - - // check if at least one item has enough unit to become a controller - const itemsWithEnoughBalance = items - .map( - (i: InputItem) => - i?.balance?.freeAfterReserve.gt(existentialDeposit) ?? false - ) - .filter((i: boolean) => i).length; - - // update selected value on account switch - useEffect(() => { - const _initial = getAccount( - setup.controller !== null ? setup.controller : null - ); - setSelected(_initial); - }, [activeAccount, accounts]); - - const handleOnChange = ({ selectedItem }: { selectedItem: InputItem }) => { - setSelected(selectedItem); - setActiveAccountSetup(SetupType.Stake, { - ...setup, - controller: selectedItem?.address ?? null, - }); - }; - - return ( - <> - <Header - thisSection={section} - title={t('nominate.set_controller_account') || ''} - helpKey="Stash and Controller Accounts" - complete={setup.controller !== null} - setupType={SetupType.Stake} - /> - <MotionContainer thisSection={section} activeSection={setup.section}> - {items.length === 0 && ( - <Warning - text={`${t('nominate.none_of_your')} ${existentialDepositBase} ${ - network.unit - }. ${t('nominate.top_up_account')}`} - /> - )} - {itemsWithEnoughBalance === 0 && ( - <Warning - text={`${t( - 'nominate.select_a_controller' - )} ${existentialDepositBase} ${network.unit}.`} - /> - )} - <Spacer /> - <AccountSelect - items={items} - onChange={handleOnChange} - placeholder={t('nominate.search_account')} - value={selected} - /> - <Footer - complete={setup.controller !== null} - setupType={SetupType.Stake} - /> - </MotionContainer> - </> - ); -}; - -export default SetController; diff --git a/src/pages/Nominate/Setup/Summary/Wrapper.ts b/src/pages/Nominate/Setup/Summary/Wrapper.ts new file mode 100644 index 0000000000..63cb28611e --- /dev/null +++ b/src/pages/Nominate/Setup/Summary/Wrapper.ts @@ -0,0 +1,43 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import styled from 'styled-components'; + +export const SummaryWrapper = styled.div` + display: flex; + flex-flow: row wrap; + width: 100%; + margin-bottom: 1rem; + + > section { + border-bottom: 1px solid var(--border-primary-color); + flex-basis: 100%; + display: flex; + flex-flow: row wrap; + margin-top: 1rem; + padding: 0.5rem 0 0.75rem 0; + + > div:first-child { + color: var(--text-color-secondary); + width: 200px; + display: flex; + flex-flow: row wrap; + align-items: center; + + svg { + color: var(--accent-color-primary); + } + } + + > div:last-child { + color: var(--text-color-secondary); + flex-grow: 1; + display: flex; + flex-flow: column nowrap; + + p { + margin: 0.25rem 0; + } + } + } +`; diff --git a/src/pages/Nominate/Setup/Summary/index.tsx b/src/pages/Nominate/Setup/Summary/index.tsx index 6098c60e15..7895ab7e09 100644 --- a/src/pages/Nominate/Setup/Summary/index.tsx +++ b/src/pages/Nominate/Setup/Summary/index.tsx @@ -1,157 +1,139 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import { IconProp } from '@fortawesome/fontawesome-svg-core'; import { faCheckCircle } from '@fortawesome/free-regular-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { ButtonPrimary } from '@rossbulat/polkadot-dashboard-ui'; -import { useApi } from 'contexts/Api'; -import { useConnect } from 'contexts/Connect'; -import { useTxFees } from 'contexts/TxFees'; -import { useUi } from 'contexts/UI'; -import { defaultStakeSetup } from 'contexts/UI/defaults'; -import { SetupType } from 'contexts/UI/types'; -import { EstimatedTxFee } from 'library/EstimatedTxFee'; +import { ellipsisFn, unitToPlanck } from '@polkadot-cloud/utils'; +import BigNumber from 'bignumber.js'; +import { useTranslation } from 'react-i18next'; +import { useSetup } from 'contexts/Setup'; import { Warning } from 'library/Form/Warning'; +import { useBatchCall } from 'library/Hooks/useBatchCall'; +import { usePayeeConfig } from 'library/Hooks/usePayeeConfig'; import { useSubmitExtrinsic } from 'library/Hooks/useSubmitExtrinsic'; import { Header } from 'library/SetupSteps/Header'; import { MotionContainer } from 'library/SetupSteps/MotionContainer'; -import { SetupStepProps } from 'library/SetupSteps/types'; -import { useTranslation } from 'react-i18next'; -import { humanNumber } from 'Utils'; +import type { SetupStepProps } from 'library/SetupSteps/types'; +import { SubmitTx } from 'library/SubmitTx'; +import { useNetwork } from 'contexts/Network'; +import { useApi } from 'contexts/Api'; +import { useActiveAccounts } from 'contexts/ActiveAccounts'; +import { useImportedAccounts } from 'contexts/Connect/ImportedAccounts'; import { SummaryWrapper } from './Wrapper'; -export const Summary = (props: SetupStepProps) => { - const { section } = props; - - const { api, network } = useApi(); - const { units } = network; - const { activeAccount, accountHasSigner } = useConnect(); - const { getSetupProgress, setActiveAccountSetup } = useUi(); - const { txFeesValid } = useTxFees(); +export const Summary = ({ section }: SetupStepProps) => { const { t } = useTranslation('pages'); + const { api } = useApi(); + const { + networkData: { units, unit }, + } = useNetwork(); + const { newBatchCall } = useBatchCall(); + const { getPayeeItems } = usePayeeConfig(); + const { accountHasSigner } = useImportedAccounts(); + const { activeAccount, activeProxy } = useActiveAccounts(); + const { getSetupProgress, removeSetupProgress } = useSetup(); - const setup = getSetupProgress(SetupType.Stake, activeAccount); - - const { controller, bond, nominations, payee } = setup; + const setup = getSetupProgress('nominator', activeAccount); + const { progress } = setup; + const { bond, nominations, payee } = progress; const getTxs = () => { if (!activeAccount || !api) { return null; } - const stashToSubmit = { - Id: activeAccount, - }; - const bondToSubmit = bond * 10 ** units; - const targetsToSubmit = nominations.map((item: any) => { - return { - Id: item.address, - }; - }); - const controllerToSubmit = { - Id: controller, - }; - // construct a batch of transactions + const targetsToSubmit = nominations.map((item: any) => ({ + Id: item.address, + })); + + const payeeToSubmit = + payee.destination === 'Account' + ? { + Account: payee.account, + } + : payee.destination; + + const bondToSubmit = unitToPlanck(bond, units); + const bondAsString = bondToSubmit.isNaN() ? '0' : bondToSubmit.toString(); + const txs = [ - api.tx.staking.bond(stashToSubmit, bondToSubmit, payee), + api.tx.staking.bond(bondAsString, payeeToSubmit), api.tx.staking.nominate(targetsToSubmit), - api.tx.staking.setController(controllerToSubmit), ]; - return api.tx.utility.batch(txs); + return newBatchCall(txs, activeAccount); }; - const { submitTx, submitting } = useSubmitExtrinsic({ + const submitExtrinsic = useSubmitExtrinsic({ tx: getTxs(), from: activeAccount, shouldSubmit: true, callbackSubmit: () => {}, callbackInBlock: () => { - // reset localStorage setup progress - setActiveAccountSetup(SetupType.Stake, defaultStakeSetup); + removeSetupProgress('nominator', activeAccount); }, }); + const payeeDisplay = + getPayeeItems().find(({ value }) => value === payee.destination)?.title || + payee.destination; + return ( <> <Header thisSection={section} complete={null} - title={t('nominate.summary') || ''} - setupType={SetupType.Stake} + title={t('nominate.summary')} + bondFor="nominator" /> <MotionContainer thisSection={section} activeSection={setup.section}> - {!accountHasSigner(activeAccount) && ( - <Warning text={t('nominate.read_only')} /> - )} + {!( + accountHasSigner(activeAccount) || accountHasSigner(activeProxy) + ) && <Warning text={t('nominate.readOnly')} />} <SummaryWrapper> <section> <div> - <FontAwesomeIcon - icon={faCheckCircle as IconProp} - transform="grow-1" - />{' '} -   Controller: + <FontAwesomeIcon icon={faCheckCircle} transform="grow-1" />  {' '} + {t('nominate.payoutDestination')}: </div> - <div>{controller}</div> - </section> - <section> <div> - <FontAwesomeIcon - icon={faCheckCircle as IconProp} - transform="grow-1" - />{' '} -   {t('nominate.reward_destination')}: + {payee.destination === 'Account' + ? `${payeeDisplay}: ${ellipsisFn(payee.account || '')}` + : payeeDisplay} </div> - <div>{payee}</div> </section> <section> <div> - <FontAwesomeIcon - icon={faCheckCircle as IconProp} - transform="grow-1" - />{' '} -   {t('nominate.nominate')}: + <FontAwesomeIcon icon={faCheckCircle} transform="grow-1" />  {' '} + {t('nominate.nominating')}: </div> - <div>{nominations.length}</div> + <div>{t('nominate.validator', { count: nominations.length })}</div> </section> <section> <div> - <FontAwesomeIcon - icon={faCheckCircle as IconProp} - transform="grow-1" - />{' '} -   {t('nominate.bond_amount')} + <FontAwesomeIcon icon={faCheckCircle} transform="grow-1" />  {' '} + {t('nominate.bondAmount')}: </div> <div> - {humanNumber(bond)} {network.unit} + {new BigNumber(bond).toFormat()} {unit} </div> </section> - <section> - <EstimatedTxFee format="table" /> - </section> </SummaryWrapper> <div style={{ flex: 1, - flexDirection: 'row', width: '100%', - display: 'flex', - justifyContent: 'end', + borderRadius: '1rem', + overflow: 'hidden', }} > - <ButtonPrimary - lg - onClick={() => submitTx()} - disabled={ - submitting || !accountHasSigner(activeAccount) || !txFeesValid - } - text={t('nominate.start_nominating')} + <SubmitTx + submitText={t('nominate.startNominating')} + valid + {...submitExtrinsic} + displayFor="canvas" /* Edge case: not canvas, but the larger button sizes suit this UI more. */ /> </div> </MotionContainer> </> ); }; - -export default Summary; diff --git a/src/pages/Nominate/Setup/index.tsx b/src/pages/Nominate/Setup/index.tsx index 300dc4b11a..a6519b356b 100644 --- a/src/pages/Nominate/Setup/index.tsx +++ b/src/pages/Nominate/Setup/index.tsx @@ -1,89 +1,89 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import { faChevronLeft } from '@fortawesome/free-solid-svg-icons'; -import { ButtonSecondary } from '@rossbulat/polkadot-dashboard-ui'; -import { useUi } from 'contexts/UI'; -import { defaultStakeSetup } from 'contexts/UI/defaults'; -import { SetupType } from 'contexts/UI/types'; -import { CardWrapper } from 'library/Graphs/Wrappers'; -import { PageTitle } from 'library/PageTitle'; -import { Nominate } from 'library/SetupSteps/Nominate'; +import { faChevronLeft, faTimes } from '@fortawesome/free-solid-svg-icons'; +import { + ButtonSecondary, + PageHeading, + PageRow, + PageTitle, +} from '@polkadot-cloud/react'; +import { extractUrlValue, removeVarFromUrlHash } from '@polkadot-cloud/utils'; import { useTranslation } from 'react-i18next'; +import { useNavigate } from 'react-router-dom'; import { Element } from 'react-scroll'; -import { PageRowWrapper, TopBarWrapper } from 'Wrappers'; +import { useSetup } from 'contexts/Setup'; +import { CardWrapper } from 'library/Card/Wrappers'; +import { Nominate } from 'library/SetupSteps/Nominate'; +import { useActiveAccounts } from 'contexts/ActiveAccounts'; import { Bond } from './Bond'; import { Payee } from './Payee'; -import { SetController } from './SetController'; import { Summary } from './Summary'; export const Setup = () => { - const { setOnNominatorSetup, setActiveAccountSetup } = useUi(); const { t } = useTranslation('pages'); + const navigate = useNavigate(); + const { activeAccount } = useActiveAccounts(); + const { setOnNominatorSetup, removeSetupProgress } = useSetup(); return ( <> - <PageTitle title={t('nominate.start_nominating')} /> - <PageRowWrapper className="page-padding" noVerticalSpacer> - <TopBarWrapper> + <PageTitle title={t('nominate.startNominating')} /> + <PageRow> + <PageHeading> <span> <ButtonSecondary - lg text={t('nominate.back')} iconLeft={faChevronLeft} iconTransform="shrink-3" - onClick={() => setOnNominatorSetup(0)} + onClick={() => { + if (extractUrlValue('f') === 'overview') { + navigate('/overview'); + } else { + removeVarFromUrlHash('f'); + setOnNominatorSetup(false); + } + }} /> </span> <span> <ButtonSecondary - lg text={t('nominate.cancel')} + iconLeft={faTimes} onClick={() => { - setOnNominatorSetup(0); - setActiveAccountSetup(SetupType.Stake, defaultStakeSetup); + removeVarFromUrlHash('f'); + setOnNominatorSetup(false); + removeSetupProgress('nominator', activeAccount); }} /> </span> <div className="right" /> - </TopBarWrapper> - </PageRowWrapper> - <PageRowWrapper className="page-padding" noVerticalSpacer> - <CardWrapper> - <Element name="controller" style={{ position: 'absolute' }} /> - <SetController section={1} /> - </CardWrapper> - </PageRowWrapper> - <PageRowWrapper className="page-padding" noVerticalSpacer> + </PageHeading> + </PageRow> + <PageRow> <CardWrapper> <Element name="payee" style={{ position: 'absolute' }} /> - <Payee section={2} /> + <Payee section={1} /> </CardWrapper> - </PageRowWrapper> - <PageRowWrapper className="page-padding" noVerticalSpacer> + </PageRow> + <PageRow> <CardWrapper> <Element name="nominate" style={{ position: 'absolute' }} /> - <Nominate - batchKey="generate_nominations_inactive" - setupType={SetupType.Stake} - section={3} - /> + <Nominate bondFor="nominator" section={2} /> </CardWrapper> - </PageRowWrapper> - <PageRowWrapper className="page-padding" noVerticalSpacer> + </PageRow> + <PageRow> <CardWrapper> <Element name="bond" style={{ position: 'absolute' }} /> - <Bond section={4} /> + <Bond section={3} /> </CardWrapper> - </PageRowWrapper> - <PageRowWrapper className="page-padding" noVerticalSpacer> + </PageRow> + <PageRow> <CardWrapper> <Element name="summary" style={{ position: 'absolute' }} /> - <Summary section={5} /> + <Summary section={4} /> </CardWrapper> - </PageRowWrapper> + </PageRow> </> ); }; - -export default Setup; diff --git a/src/pages/Nominate/Wrappers.ts b/src/pages/Nominate/Wrappers.ts new file mode 100644 index 0000000000..93df8167f6 --- /dev/null +++ b/src/pages/Nominate/Wrappers.ts @@ -0,0 +1,31 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import styled from 'styled-components'; + +export const Wrapper = styled.div` + display: flex; + flex-flow: column wrap; +`; + +export const Spacer = styled.div` + width: 100%; + height: 1px; + margin: 0.75rem 0; +`; + +export const Subheading = styled.div` + margin: 0.4rem 0 1rem 0; + + h3, + h4 { + margin-top: 0; + margin-left: 0; + display: flex; + align-items: center; + + > button { + margin-left: 0.75rem; + } + } +`; diff --git a/src/pages/Nominate/index.tsx b/src/pages/Nominate/index.tsx index 35b99cd23d..eaf2e2f04b 100644 --- a/src/pages/Nominate/index.tsx +++ b/src/pages/Nominate/index.tsx @@ -1,15 +1,12 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import { useUi } from 'contexts/UI'; +import { useSetup } from 'contexts/Setup'; import { Active } from './Active'; import { Setup } from './Setup'; import { Wrapper } from './Wrappers'; export const Nominate = () => { - const { onNominatorSetup } = useUi(); - + const { onNominatorSetup } = useSetup(); return <Wrapper>{onNominatorSetup ? <Setup /> : <Active />}</Wrapper>; }; - -export default Nominate; diff --git a/src/pages/Overview/ActiveAccount.tsx b/src/pages/Overview/ActiveAccount.tsx deleted file mode 100644 index 865d9a6e94..0000000000 --- a/src/pages/Overview/ActiveAccount.tsx +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { IconProp } from '@fortawesome/fontawesome-svg-core'; -import { faCopy } from '@fortawesome/free-regular-svg-icons'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { useConnect } from 'contexts/Connect'; -import { useNotifications } from 'contexts/Notifications'; -import { NotificationText } from 'contexts/Notifications/types'; -import { Identicon } from 'library/Identicon'; -import { useTranslation } from 'react-i18next'; -import { clipAddress, convertRemToPixels } from 'Utils'; -import { ActiveAccounWrapper } from './Wrappers'; - -export const ActiveAccount = () => { - const { addNotification } = useNotifications(); - const { activeAccount, getAccount } = useConnect(); - const accountData = getAccount(activeAccount); - const { t } = useTranslation('pages'); - - // click to copy notification - let notification: NotificationText | null = null; - if (accountData !== null) { - notification = { - title: t('overview.address_copied'), - subtitle: accountData.address, - }; - } - - return ( - <ActiveAccounWrapper> - <div className="account"> - <div className="title"> - <h3> - {accountData && ( - <> - <div className="icon"> - <Identicon - value={accountData.address} - size={convertRemToPixels('1.7rem')} - /> - </div> - {clipAddress(accountData.address)} - <button - type="button" - onClick={() => { - navigator.clipboard.writeText(accountData.address); - if (notification) { - addNotification(notification); - } - }} - > - <FontAwesomeIcon - className="copy" - icon={faCopy as IconProp} - transform="shrink-1" - /> - </button> - {accountData.name !== clipAddress(accountData.address) && ( - <> - <div className="sep" /> - <div className="rest"> - <span className="name">{accountData.name}</span> - </div> - </> - )} - </> - )} - - {!accountData && t('overview.no_account_connected')} - </h3> - </div> - </div> - </ActiveAccounWrapper> - ); -}; - -export default ActiveAccount; diff --git a/src/pages/Overview/ActiveAccounts/Item.tsx b/src/pages/Overview/ActiveAccounts/Item.tsx new file mode 100644 index 0000000000..849f220a2f --- /dev/null +++ b/src/pages/Overview/ActiveAccounts/Item.tsx @@ -0,0 +1,96 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { faCopy } from '@fortawesome/free-regular-svg-icons'; +import { faArrowLeft } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { ellipsisFn, remToUnit } from '@polkadot-cloud/utils'; +import { useTranslation } from 'react-i18next'; +import { useNotifications } from 'contexts/Notifications'; +import type { NotificationText } from 'contexts/Notifications/types'; +import { useProxies } from 'contexts/Proxies'; +import { Polkicon } from '@polkadot-cloud/react'; +import { useImportedAccounts } from 'contexts/Connect/ImportedAccounts'; +import { ItemWrapper } from './Wrappers'; +import type { ActiveAccountProps } from './types'; + +export const Item = ({ address, delegate = null }: ActiveAccountProps) => { + const { t } = useTranslation('pages'); + const { getProxyDelegate } = useProxies(); + const { getAccount } = useImportedAccounts(); + const { addNotification } = useNotifications(); + + const primaryAddress = delegate || address || ''; + const delegatorAddress = delegate ? address : null; + + const accountData = getAccount(primaryAddress); + + // click to copy notification + let notification: NotificationText | null = null; + if (accountData !== null) { + notification = { + title: t('overview.addressCopied'), + subtitle: accountData.address, + }; + } + + const proxyDelegate = getProxyDelegate(delegatorAddress, primaryAddress); + + return ( + <ItemWrapper> + <div className="title"> + <h4> + {accountData && ( + <> + {delegatorAddress && ( + <div className="delegator"> + <Polkicon + address={delegatorAddress || ''} + size={remToUnit('1.7rem')} + /> + </div> + )} + <div className="icon"> + <Polkicon address={primaryAddress} size={remToUnit('1.7rem')} /> + </div> + {delegatorAddress && ( + <> + <span> + {proxyDelegate?.proxyType} {t('overview.proxy')} + <FontAwesomeIcon icon={faArrowLeft} transform="shrink-2" /> + </span> + </> + )} + {ellipsisFn(primaryAddress)} + <button + type="button" + onClick={() => { + navigator.clipboard.writeText(primaryAddress); + if (notification) { + addNotification(notification); + } + }} + > + <FontAwesomeIcon + className="copy" + icon={faCopy} + transform="shrink-4" + /> + </button> + {accountData.name !== ellipsisFn(primaryAddress) && ( + <> + <div className="sep" /> + <div className="rest"> + <span className="name">{accountData.name}</span> + </div> + </> + )} + </> + )} + + {!accountData ? t('overview.noActiveAccount') : null} + </h4> + </div> + </ItemWrapper> + ); +}; diff --git a/src/pages/Overview/ActiveAccounts/Wrappers.ts b/src/pages/Overview/ActiveAccounts/Wrappers.ts new file mode 100644 index 0000000000..8fdfec1d33 --- /dev/null +++ b/src/pages/Overview/ActiveAccounts/Wrappers.ts @@ -0,0 +1,120 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import styled from 'styled-components'; + +export const ActiveAccounsWrapper = styled.div` + width: 100%; + display: flex; + flex-direction: column; + + > div { + border-bottom: 1px solid var(--border-primary-color); + padding: 0.65rem 0; + + &:last-child { + border: none; + padding-bottom: 0; + } + } +`; + +export const ItemWrapper = styled.div` + display: flex; + flex-flow: row wrap; + align-items: center; + overflow: hidden; + width: 100%; + + .delegator { + width: 0.75rem; + z-index: 0; + } + + .icon { + position: relative; + top: 0.1rem; + margin-right: 0.5rem; + z-index: 1; + } + .title { + font-family: InterSemiBold, sans-serif; + margin: 0; + padding: 0; + flex: 1; + overflow: hidden; + + &.signer { + padding-left: 2rem; + } + } + .rest { + flex: 1 1 0%; + min-height: 1.8rem; + overflow: hidden; + position: relative; + + .name { + color: var(--text-color-tertiary); + position: absolute; + left: 0; + bottom: 0; + display: inline; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + max-width: 100%; + } + } + + button { + width: 2rem; + height: 2rem; + border-radius: 50%; + margin-left: 0.25rem; + padding: 0; + } + + h4 { + display: flex; + flex-flow: row wrap; + align-items: center; + flex: 1; + + > .sep { + border-right: 1px solid var(--border-secondary-color); + margin: 0 0.65rem 0 0.25rem; + width: 1px; + height: 1.25rem; + } + > .addr { + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + } + + > span { + opacity: 0.7; + margin: 0 0.5rem; + > svg { + margin-left: 0.5rem; + } + } + } + + > *:last-child { + flex-grow: 1; + display: flex; + flex-flow: row-reverse wrap; + + .copy { + color: var(--text-color-secondary); + opacity: 0.9; + cursor: pointer; + transition: opacity var(--transition-duration); + &:hover { + opacity: 1; + } + } + } +`; diff --git a/src/pages/Overview/ActiveAccounts/index.tsx b/src/pages/Overview/ActiveAccounts/index.tsx new file mode 100644 index 0000000000..323ae101e2 --- /dev/null +++ b/src/pages/Overview/ActiveAccounts/index.tsx @@ -0,0 +1,17 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { useActiveAccounts } from 'contexts/ActiveAccounts'; +import { Item } from './Item'; +import { ActiveAccounsWrapper } from './Wrappers'; + +export const ActiveAccounts = () => { + const { activeProxy, activeAccount } = useActiveAccounts(); + + return ( + <ActiveAccounsWrapper> + <Item address={activeAccount} /> + {activeProxy && <Item address={activeAccount} delegate={activeProxy} />} + </ActiveAccounsWrapper> + ); +}; diff --git a/src/pages/Overview/ActiveAccounts/types.ts b/src/pages/Overview/ActiveAccounts/types.ts new file mode 100644 index 0000000000..e94406ab44 --- /dev/null +++ b/src/pages/Overview/ActiveAccounts/types.ts @@ -0,0 +1,9 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import type { MaybeAddress } from 'types'; + +export interface ActiveAccountProps { + address: MaybeAddress; + delegate?: MaybeAddress; +} diff --git a/src/pages/Overview/BalanceChart.tsx b/src/pages/Overview/BalanceChart.tsx new file mode 100644 index 0000000000..abc589aca8 --- /dev/null +++ b/src/pages/Overview/BalanceChart.tsx @@ -0,0 +1,297 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { faCheck, faCheckDouble } from '@fortawesome/free-solid-svg-icons'; +import { ButtonTertiary, Odometer } from '@polkadot-cloud/react'; +import { + greaterThanZero, + minDecimalPlaces, + planckToUnit, +} from '@polkadot-cloud/utils'; +import BigNumber from 'bignumber.js'; +import { useTranslation } from 'react-i18next'; +import { useBalances } from 'contexts/Balances'; +import { usePlugins } from 'contexts/Plugins'; +import { useTransferOptions } from 'contexts/TransferOptions'; +import { useUi } from 'contexts/UI'; +import { BarSegment } from 'library/BarChart/BarSegment'; +import { LegendItem } from 'library/BarChart/LegendItem'; +import { Bar, BarChartWrapper, Legend } from 'library/BarChart/Wrappers'; +import { CardHeaderWrapper } from 'library/Card/Wrappers'; +import { usePrices } from 'library/Hooks/usePrices'; +import { useOverlay } from '@polkadot-cloud/react/hooks'; +import { useNetwork } from 'contexts/Network'; +import { useActiveAccounts } from 'contexts/ActiveAccounts'; +import { useImportedAccounts } from 'contexts/Connect/ImportedAccounts'; + +export const BalanceChart = () => { + const { t } = useTranslation('pages'); + const { + networkData: { + units, + unit, + brand: { token: Token }, + }, + } = useNetwork(); + const prices = usePrices(); + const { plugins } = usePlugins(); + const { isNetworkSyncing } = useUi(); + const { openModal } = useOverlay().modal; + const { getBalance, getLocks } = useBalances(); + const { activeAccount } = useActiveAccounts(); + const { accountHasSigner } = useImportedAccounts(); + const { feeReserve, getTransferOptions } = useTransferOptions(); + const balance = getBalance(activeAccount); + const allTransferOptions = getTransferOptions(activeAccount); + const { edReserved } = allTransferOptions; + const poolBondOpions = allTransferOptions.pool; + const unlockingPools = poolBondOpions.totalUnlocking.plus( + poolBondOpions.totalUnlocked + ); + + // user's total balance + const { free, frozen } = balance; + const totalBalance = planckToUnit( + free.plus(poolBondOpions.active).plus(unlockingPools), + units + ); + // convert balance to fiat value + const freeFiat = totalBalance.multipliedBy( + new BigNumber(prices.lastPrice).decimalPlaces(2) + ); + + // total funds nominating + const nominating = planckToUnit( + allTransferOptions.nominate.active + .plus(allTransferOptions.nominate.totalUnlocking) + .plus(allTransferOptions.nominate.totalUnlocked), + units + ); + // total funds in pool + const inPool = planckToUnit( + allTransferOptions.pool.active + .plus(allTransferOptions.pool.totalUnlocking) + .plus(allTransferOptions.pool.totalUnlocked), + units + ); + + // check account non-staking locks + const locks = getLocks(activeAccount); + const locksStaking = locks.find(({ id }) => id === 'staking'); + const lockStakingAmount = locksStaking + ? locksStaking.amount + : new BigNumber(0); + + // total funds available, including existential deposit, minus staking. + const graphAvailable = planckToUnit( + BigNumber.max(free.minus(lockStakingAmount), 0), + units + ); + const notStaking = graphAvailable; + + // graph percentages + const graphTotal = nominating.plus(inPool).plus(graphAvailable); + const graphNominating = greaterThanZero(nominating) + ? nominating.dividedBy(graphTotal.multipliedBy(0.01)) + : new BigNumber(0); + + const graphInPool = greaterThanZero(inPool) + ? inPool.dividedBy(graphTotal.multipliedBy(0.01)) + : new BigNumber(0); + + const graphNotStaking = greaterThanZero(graphTotal) + ? BigNumber.max( + new BigNumber(100).minus(graphNominating).minus(graphInPool), + 0 + ) + : new BigNumber(0); + + // available balance data + const fundsLocked = planckToUnit( + BigNumber.max(frozen.minus(lockStakingAmount), 0), + units + ); + let fundsReserved = planckToUnit(edReserved.plus(feeReserve), units); + const fundsFree = planckToUnit( + BigNumber.max( + allTransferOptions.freeBalance.minus(feeReserve).minus(fundsLocked), + 0 + ), + units + ); + // available balance percentages + const graphLocked = greaterThanZero(fundsLocked) + ? fundsLocked.dividedBy(graphAvailable.multipliedBy(0.01)) + : new BigNumber(0); + + const graphFree = greaterThanZero(fundsFree) + ? fundsFree.dividedBy(graphAvailable.multipliedBy(0.01)) + : new BigNumber(0); + + // get total available balance, including reserve and locks + if (graphAvailable.isLessThan(fundsReserved)) { + fundsReserved = graphAvailable; + } + + // formatter for price feed. + const usdFormatter = new Intl.NumberFormat('en-US', { + style: 'currency', + currency: 'USD', + }); + + const isNominating = greaterThanZero(nominating); + const isInPool = greaterThanZero( + poolBondOpions.active + .plus(poolBondOpions.totalUnlocked) + .plus(poolBondOpions.totalUnlocking) + ); + + return ( + <> + <CardHeaderWrapper> + <h4>{t('overview.balance')}</h4> + <h2> + <Token className="networkIcon" /> + <Odometer + value={minDecimalPlaces(totalBalance.toFormat(), 2)} + zeroDecimals={2} + /> + <span className="note"> + {plugins.includes('binance_spot') ? ( + <> {usdFormatter.format(freeFiat.toNumber())}</> + ) : null} + </span> + </h2> + </CardHeaderWrapper> + + <BarChartWrapper> + <Legend> + {isNominating ? ( + <LegendItem dataClass="d1" label={t('overview.nominating')} /> + ) : null} + {greaterThanZero(inPool) ? ( + <LegendItem dataClass="d2" label={t('overview.inPool')} /> + ) : null} + <LegendItem dataClass="d4" label={t('overview.notStaking')} /> + </Legend> + <Bar> + <BarSegment + dataClass="d1" + widthPercent={Number(graphNominating.toFixed(2))} + flexGrow={!inPool && !notStaking && isNominating ? 1 : 0} + label={`${nominating.decimalPlaces(3).toFormat()} ${unit}`} + /> + <BarSegment + dataClass="d2" + widthPercent={Number(graphInPool.toFixed(2))} + flexGrow={!isNominating && !notStaking && inPool ? 1 : 0} + label={`${inPool.decimalPlaces(3).toFormat()} ${unit}`} + /> + <BarSegment + dataClass="d4" + widthPercent={Number(graphNotStaking.toFixed(2))} + flexGrow={!isNominating && !inPool ? 1 : 0} + label={`${notStaking.decimalPlaces(3).toFormat()} ${unit}`} + forceShow={!isNominating && !isInPool} + /> + </Bar> + <section className="available"> + <div + style={{ + flex: 1, + minWidth: '8.5rem', + flexBasis: `${ + greaterThanZero(graphFree) && greaterThanZero(graphLocked) + ? `${graphFree.toFixed(2)}%` + : 'auto' + }`, + }} + > + <Legend> + <LegendItem label={t('overview.free')} helpKey="Your Balance" /> + </Legend> + <Bar> + <BarSegment + dataClass="d4" + widthPercent={100} + flexGrow={1} + label={`${fundsFree.decimalPlaces(3).toFormat()} ${unit}`} + /> + </Bar> + </div> + {greaterThanZero(fundsLocked) ? ( + <div + style={{ + flex: 1, + minWidth: '8.5rem', + flexBasis: `${graphLocked.toFixed(2)}%`, + }} + > + <Legend> + <LegendItem + label={t('overview.locked')} + helpKey="Reserve Balance" + /> + </Legend> + <Bar> + <BarSegment + dataClass="d4" + widthPercent={100} + flexGrow={1} + label={`${fundsLocked.decimalPlaces(3).toFormat()} ${unit}`} + /> + </Bar> + </div> + ) : null} + {greaterThanZero(fundsReserved) ? ( + <div + style={{ + flex: 0, + minWidth: '12.5rem', + maxWidth: '12.5rem', + flexBasis: '50%', + }} + > + <Legend className="end"> + <LegendItem + label="" + button={ + <ButtonTertiary + text={t('overview.reserveBalance')} + onClick={() => + openModal({ key: 'UpdateReserve', size: 'sm' }) + } + iconRight={ + isNetworkSyncing + ? undefined + : !feeReserve.isZero() && !edReserved.isZero() + ? faCheckDouble + : feeReserve.isZero() && edReserved.isZero() + ? undefined + : faCheck + } + iconTransform="shrink-1" + disabled={ + !activeAccount || + isNetworkSyncing || + !accountHasSigner(activeAccount) + } + /> + } + /> + </Legend> + <Bar> + <BarSegment + dataClass="d4" + widthPercent={100} + flexGrow={1} + label={`${fundsReserved.decimalPlaces(3).toFormat()} ${unit}`} + /> + </Bar> + </div> + ) : null} + </section> + </BarChartWrapper> + </> + ); +}; diff --git a/src/pages/Overview/BalanceGraph.tsx b/src/pages/Overview/BalanceGraph.tsx deleted file mode 100644 index 7be3b8c1f9..0000000000 --- a/src/pages/Overview/BalanceGraph.tsx +++ /dev/null @@ -1,215 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { ArcElement, Chart as ChartJS, Legend, Tooltip } from 'chart.js'; -import { useApi } from 'contexts/Api'; -import { useBalances } from 'contexts/Balances'; -import { useConnect } from 'contexts/Connect'; -import { useTheme } from 'contexts/Themes'; -import { useTransferOptions } from 'contexts/TransferOptions'; -import { useUi } from 'contexts/UI'; -import { formatSize, useSize } from 'library/Graphs/Utils'; -import { usePrices } from 'library/Hooks/usePrices'; -import { OpenHelpIcon } from 'library/OpenHelpIcon'; -import React from 'react'; -import { Doughnut } from 'react-chartjs-2'; -import { useTranslation } from 'react-i18next'; -import { - defaultThemes, - networkColors, - networkColorsSecondary, -} from 'theme/default'; -import { - humanNumber, - planckBnToUnit, - toFixedIfNecessary, - usdFormatter, -} from 'Utils'; - -ChartJS.register(ArcElement, Tooltip, Legend); - -export const BalanceGraph = () => { - const { mode } = useTheme(); - const { network } = useApi(); - const { units } = network; - const { activeAccount } = useConnect(); - const { getAccountBalance } = useBalances(); - const { getTransferOptions } = useTransferOptions(); - const balance = getAccountBalance(activeAccount); - const { services } = useUi(); - const prices = usePrices(); - const { t } = useTranslation('pages'); - - const allTransferOptions = getTransferOptions(activeAccount); - const { freeBalance } = allTransferOptions; - - const { - freeToUnbond: staked, - totalUnlocking, - totalUnlocked, - } = allTransferOptions.nominate; - - const poolBondOpions = allTransferOptions.pool; - const unlockingPools = poolBondOpions.totalUnlocking.add( - poolBondOpions.totalUnlocked - ); - - const unlocking = unlockingPools.add(totalUnlocked).add(totalUnlocking); - - // get user's total balance - const { free } = balance; - const freeBase = planckBnToUnit( - free.add(poolBondOpions.active).add(unlockingPools), - units - ); - - // convert balance to fiat value - const freeFiat = toFixedIfNecessary(Number(freeBase * prices.lastPrice), 2); - - // graph data - let graphStaked = planckBnToUnit(staked, units); - let graphFreeToStake = planckBnToUnit(freeBalance, units); - - let graphInPool = planckBnToUnit(poolBondOpions.active, units); - let graphUnlocking = planckBnToUnit(unlocking, units); - - let zeroBalance = false; - if ( - graphStaked === 0 && - graphFreeToStake === 0 && - graphUnlocking === 0 && - graphInPool === 0 - ) { - graphStaked = -1; - graphUnlocking = -1; - graphFreeToStake = -1; - graphInPool = -1; - zeroBalance = true; - } - - const options = { - responsive: true, - maintainAspectRatio: false, - spacing: zeroBalance ? 0 : 5, - plugins: { - legend: { - display: true, - padding: { - right: 10, - }, - position: 'left' as const, - align: 'center' as const, - labels: { - padding: 20, - color: defaultThemes.text.primary[mode], - font: { - size: 13, - weight: '600', - }, - }, - }, - tooltip: { - displayColors: false, - backgroundColor: defaultThemes.graphs.tooltip[mode], - titleColor: defaultThemes.text.invert[mode], - bodyColor: defaultThemes.text.invert[mode], - bodyFont: { - weight: '600', - }, - callbacks: { - label: (context: any) => { - return `${ - context.parsed === -1 ? 0 : humanNumber(context.parsed) - } ${network.unit}`; - }, - }, - }, - }, - cutout: '78%', - }; - - // determine stats - const _labels = [ - t('overview.available'), - t('overview.unlocking'), - t('overview.nominating'), - t('overview.in_pool'), - ]; - const _data = [graphFreeToStake, graphUnlocking, graphStaked, graphInPool]; - const _colors = zeroBalance - ? [ - defaultThemes.graphs.colors[1][mode], - defaultThemes.graphs.inactive2[mode], - defaultThemes.graphs.inactive2[mode], - defaultThemes.graphs.inactive[mode], - ] - : [ - defaultThemes.graphs.colors[1][mode], - defaultThemes.graphs.colors[0][mode], - networkColors[`${network.name}-${mode}`], - networkColorsSecondary[`${network.name}-${mode}`], - ]; - - // default to a greyscale 50/50 donut on zero balance - let dataSet; - if (zeroBalance) { - dataSet = { - label: network.unit, - data: _data, - backgroundColor: _colors, - borderWidth: 0, - }; - } else { - dataSet = { - label: network.unit, - data: _data, - backgroundColor: _colors, - borderWidth: 0, - }; - } - - const data = { - labels: _labels, - datasets: [dataSet], - }; - - const ref = React.useRef<HTMLDivElement>(null); - const size = useSize(ref.current); - const { width, height, minHeight } = formatSize(size, 185); - - return ( - <> - <div className="head"> - <h4> - {t('overview.balance')} - <OpenHelpIcon helpKey="Your Balance" /> - </h4> - <h2> - <span className="amount">{humanNumber(freeBase)}</span>  - {network.unit} - <span className="fiat"> - {services.includes('binance_spot') && ( - <> {usdFormatter.format(Number(freeFiat))}</> - )} - </span> - </h2> - </div> - <div style={{ paddingTop: '1rem' }} /> - <div className="inner" ref={ref} style={{ minHeight }}> - <div - className="graph" - style={{ - height: `${height}px`, - width: `${width}px`, - position: 'absolute', - }} - > - <Doughnut options={options} data={data} /> - </div> - </div> - <div style={{ paddingTop: '1rem' }} /> - </> - ); -}; - -export default BalanceGraph; diff --git a/src/pages/Overview/BalanceLinks.tsx b/src/pages/Overview/BalanceLinks.tsx new file mode 100644 index 0000000000..ee9b5892f2 --- /dev/null +++ b/src/pages/Overview/BalanceLinks.tsx @@ -0,0 +1,59 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { faExternalLinkAlt } from '@fortawesome/free-solid-svg-icons'; +import { ButtonPrimaryInvert, Separator } from '@polkadot-cloud/react'; +import { useTranslation } from 'react-i18next'; +import { useNetwork } from 'contexts/Network'; +import { useActiveAccounts } from 'contexts/ActiveAccounts'; +import { useStaking } from 'contexts/Staking'; +import { MoreWrapper } from './Wrappers'; + +export const BalanceLinks = () => { + const { t } = useTranslation('pages'); + const { network } = useNetwork(); + const { isNominating } = useStaking(); + const { activeAccount } = useActiveAccounts(); + + return ( + <MoreWrapper> + <Separator /> + <h4>{t('overview.moreResources')}</h4> + <section> + <ButtonPrimaryInvert + lg + onClick={() => + window.open( + `https://${network}.subscan.io/account/${activeAccount}`, + '_blank' + ) + } + iconRight={faExternalLinkAlt} + iconTransform="shrink-2" + text="Subscan" + marginRight + disabled={!activeAccount} + /> + <ButtonPrimaryInvert + lg + onClick={() => + window.open( + `https://${network}.polkawatch.app/nomination/${activeAccount}`, + '_blank' + ) + } + iconRight={faExternalLinkAlt} + iconTransform="shrink-2" + text="Polkawatch" + disabled={ + !( + activeAccount && + ['polkadot', 'kusama'].includes(network) && + isNominating() + ) + } + /> + </section> + </MoreWrapper> + ); +}; diff --git a/src/pages/Overview/NetworkSats/Announcements.tsx b/src/pages/Overview/NetworkSats/Announcements.tsx index 5bc0c6b676..bef4483d1c 100644 --- a/src/pages/Overview/NetworkSats/Announcements.tsx +++ b/src/pages/Overview/NetworkSats/Announcements.tsx @@ -1,61 +1,45 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only import { faBullhorn as faBack } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import BN from 'bn.js'; -import { useApi } from 'contexts/Api'; -import { useNetworkMetrics } from 'contexts/Network'; +import { + capitalizeFirstLetter, + planckToUnit, + rmCommas, + sortWithNull, +} from '@polkadot-cloud/utils'; +import BigNumber from 'bignumber.js'; +import { motion } from 'framer-motion'; +import { useTranslation } from 'react-i18next'; import { useBondedPools } from 'contexts/Pools/BondedPools'; -import { usePoolMembers } from 'contexts/Pools/PoolMembers'; -import { BondedPool } from 'contexts/Pools/types'; +import { usePoolsConfig } from 'contexts/Pools/PoolsConfig'; +import type { BondedPool } from 'contexts/Pools/types'; import { useStaking } from 'contexts/Staking'; import { useUi } from 'contexts/UI'; -import { motion } from 'framer-motion'; -import { Announcement as AnnouncementLoader } from 'library/Loaders/Announcement'; -import { useTranslation } from 'react-i18next'; -import { - humanNumber, - planckBnToUnit, - rmCommas, - toFixedIfNecessary, -} from 'Utils'; +import { Announcement as AnnouncementLoader } from 'library/Loader/Announcement'; +import { useNetwork } from 'contexts/Network'; import { Item } from './Wrappers'; export const Announcements = () => { - const { networkSyncing, poolsSyncing, isSyncing } = useUi(); - const { network } = useApi(); - const { units } = network; + const { t } = useTranslation('pages'); + const { isSyncing } = useUi(); const { staking } = useStaking(); - const { metrics } = useNetworkMetrics(); - const { poolMembers } = usePoolMembers(); - + const { stats } = usePoolsConfig(); const { - minNominatorBond, - totalNominators, - maxNominatorsCount, - lastTotalStake, - } = staking; + network, + networkData: { units, unit }, + } = useNetwork(); const { bondedPools } = useBondedPools(); - const { totalIssuance } = metrics; - const { t } = useTranslation('pages'); - let totalPoolPoints = new BN(0); + const { totalStaked } = staking; + const { counterForPoolMembers } = stats; + + let totalPoolPoints = new BigNumber(0); bondedPools.forEach((b: BondedPool) => { - totalPoolPoints = totalPoolPoints.add(new BN(rmCommas(b.points))); + totalPoolPoints = totalPoolPoints.plus(rmCommas(b.points)); }); - const totalPoolPointsBase = humanNumber( - toFixedIfNecessary(planckBnToUnit(totalPoolPoints, units), 0) - ); - - // total supply as percent - // total supply as percent - const totalIssuanceBase = planckBnToUnit(totalIssuance, units); - const lastTotalStakeBase = planckBnToUnit(lastTotalStake, units); - const supplyAsPercent = - lastTotalStakeBase === 0 - ? 0 - : lastTotalStakeBase / (totalIssuanceBase * 0.01); + const totalPoolPointsUnit = planckToUnit(totalPoolPoints, units); const container = { hidden: { opacity: 0 }, @@ -76,97 +60,54 @@ export const Announcements = () => { }, }; - const nominatorCapReached = maxNominatorsCount.eq(totalNominators); - - let nominatorReachedPercentage = new BN(0); - if (maxNominatorsCount.gt(new BN(0)) && totalNominators.gt(new BN(0))) { - nominatorReachedPercentage = totalNominators.div( - maxNominatorsCount.div(new BN(100)) - ); - } - - const minNominatorBondBase = planckBnToUnit(minNominatorBond, units); - const announcements = []; - // maximum nominators have been reached - if (nominatorCapReached && !isSyncing) { - announcements.push({ - class: 'danger', - title: t('overview.nominator_limit'), - subtitle: t('overview.maximum_allowed'), - }); - } + const networkUnit = unit; - // 90% plus nominators reached - if (nominatorReachedPercentage.toNumber() >= 90) { + // total staked on the network + if (!isSyncing) { announcements.push({ class: 'neutral', - title: `${toFixedIfNecessary( - nominatorReachedPercentage.toNumber(), - 2 - )}${t('overview.limit_reached')}`, - subtitle: `${t('overview.maximum_amount')} ${humanNumber( - maxNominatorsCount.toNumber() - )}.`, + title: t('overview.networkCurrentlyStaked', { + total: planckToUnit(totalStaked, units).integerValue().toFormat(), + unit, + network: capitalizeFirstLetter(network), + }), + subtitle: t('overview.networkCurrentlyStakedSubtitle', { + unit, + }), }); + } else { + announcements.push(null); } - const networkName = network.name; - const networkUnit = network.unit; - // bonded pools available + // total locked in pools if (bondedPools.length) { - // total pools active announcements.push({ - class: 'pools', - title: `${bondedPools.length} ${t('overview.pools_are_active')}`, - subtitle: `${t('overview.available_to_join', { networkName })}`, + class: 'neutral', + title: `${totalPoolPointsUnit.integerValue().toFormat()} ${unit} ${t( + 'overview.inPools' + )}`, + subtitle: `${t('overview.bondedInPools', { networkUnit })}`, }); + } else { + announcements.push(null); + } + if (counterForPoolMembers.isGreaterThan(0)) { // total locked in pools announcements.push({ - class: 'pools', - title: `${totalPoolPointsBase} ${network.unit} ${t('overview.in_pools')}`, - subtitle: `${t('overview.bonded_in_pools', { networkUnit })}`, + class: 'neutral', + title: `${counterForPoolMembers.toFormat()} ${t( + 'overview.poolMembersBonding' + )}`, + subtitle: `${t('overview.totalNumAccounts')}`, }); - - if (poolMembers.length > 0 && !poolsSyncing) { - // total locked in pols - announcements.push({ - class: 'pools', - title: `${humanNumber(poolMembers.length)} ${t( - 'overview.pool_members_bonding' - )}`, - subtitle: `${t('overview.total_num_accounts')}`, - }); - } + } else { + announcements.push(null); } - // minimum nominator bond - announcements.push({ - class: 'neutral', - title: `${t('overview.minimum_nominator_bond')} ${minNominatorBondBase} ${ - network.unit - }.`, - subtitle: `${t('overview.minimum_bonding_amount', { - networkName, - })}${planckBnToUnit(minNominatorBond, units)} ${network.unit}.`, - }); - - // supply staked - announcements.push({ - class: 'neutral', - title: `${t('overview.currently_staked', { - supplyAsPercent: toFixedIfNecessary(supplyAsPercent, 2), - networkUnit, - })}`, - subtitle: `${t('overview.staking_on_the_network', { - lastTotalStakeBase: humanNumber( - toFixedIfNecessary(lastTotalStakeBase, 0) - ), - networkUnit, - })}`, - }); + announcements.sort(sortWithNull(true)); return ( <motion.div @@ -175,10 +116,10 @@ export const Announcements = () => { animate="show" style={{ width: '100%' }} > - {networkSyncing ? ( - <AnnouncementLoader /> - ) : ( - announcements.map((item, index) => ( + {announcements.map((item, index) => + item === null ? ( + <AnnouncementLoader key={`announcement_${index}`} /> + ) : ( <Item key={`announcement_${index}`} variants={listItem}> <h4 className={item.class}> <FontAwesomeIcon @@ -189,7 +130,7 @@ export const Announcements = () => { </h4> <p>{item.subtitle}</p> </Item> - )) + ) )} </motion.div> ); diff --git a/src/pages/Overview/NetworkSats/Inflation.tsx b/src/pages/Overview/NetworkSats/Inflation.tsx deleted file mode 100644 index b83f6bb0b3..0000000000 --- a/src/pages/Overview/NetworkSats/Inflation.tsx +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { useApi } from 'contexts/Api'; -import { useNetworkMetrics } from 'contexts/Network'; -import { useStaking } from 'contexts/Staking'; -import useInflation from 'library/Hooks/useInflation'; -import { OpenHelpIcon } from 'library/OpenHelpIcon'; -import { useTranslation } from 'react-i18next'; -import { planckBnToUnit, toFixedIfNecessary } from 'Utils'; -import { InflationWrapper } from './Wrappers'; - -export const Inflation = () => { - const { units } = useApi().network; - const { metrics } = useNetworkMetrics(); - const { staking } = useStaking(); - const { inflation, stakedReturn } = useInflation(); - const { t } = useTranslation('pages'); - - const { lastTotalStake } = staking; - const { totalIssuance } = metrics; - - // total supply as percent - const totalIssuanceBase = planckBnToUnit(totalIssuance, units); - const lastTotalStakeBase = planckBnToUnit(lastTotalStake, units); - const supplyAsPercent = - lastTotalStakeBase === 0 - ? 0 - : lastTotalStakeBase / (totalIssuanceBase * 0.01); - - return ( - <InflationWrapper> - <section> - <div className="items"> - <div> - <div className="inner"> - <h2> - {totalIssuance.toString() === '0' - ? '0' - : toFixedIfNecessary(stakedReturn, 2)} - % - </h2> - <h4> - {t('overview.historical_rewards_rate')}{' '} - <OpenHelpIcon helpKey="Historical Rewards Rate" /> - </h4> - </div> - </div> - <div> - <div className="inner"> - <h2> - {totalIssuance.toString() === '0' - ? '0' - : toFixedIfNecessary(inflation, 2)} - % - </h2> - <h4> - {t('overview.inflation')} <OpenHelpIcon helpKey="Inflation" /> - </h4> - </div> - </div> - <div> - <div className="inner"> - <h2>{toFixedIfNecessary(supplyAsPercent, 2)}%</h2> - <h4> - {t('overview.supply_staked')}{' '} - <OpenHelpIcon helpKey="Supply Staked" /> - </h4> - </div> - </div> - </div> - </section> - </InflationWrapper> - ); -}; diff --git a/src/pages/Overview/NetworkSats/Wrappers.ts b/src/pages/Overview/NetworkSats/Wrappers.ts index 88ad8aba9b..ba1b243e41 100644 --- a/src/pages/Overview/NetworkSats/Wrappers.ts +++ b/src/pages/Overview/NetworkSats/Wrappers.ts @@ -1,15 +1,8 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import { SmallFontSizeMaxWidth } from 'consts'; import { motion } from 'framer-motion'; import styled from 'styled-components'; -import { - borderPrimary, - networkColor, - networkColorSecondary, - textSecondary, -} from 'theme'; export const Wrapper = styled.div` display: flex; @@ -18,15 +11,12 @@ export const Wrapper = styled.div` `; export const Item = styled(motion.div)` + border-bottom: 1px solid var(--border-primary-color); list-style: none; flex: 1; - flex-flow: row nowrap; - justify-content: flex-start; - align-items: flex-start; margin-bottom: 1rem; padding: 0.75rem; padding-bottom: 1.5rem; - border-bottom: 1px solid ${borderPrimary}; &:last-child { border-bottom: 0; @@ -41,7 +31,7 @@ export const Item = styled(motion.div)` padding-bottom: 0.2rem; &.neutral { - color: ${networkColor}; + color: var(--accent-color-primary); } &.danger { color: #d2545d; @@ -50,101 +40,13 @@ export const Item = styled(motion.div)` color: #b5a200; } &.pools { - color: ${networkColorSecondary}; + color: var(--accent-color-secondary); } } p { + color: var(--text-color-secondary); margin: 0; - color: ${textSecondary}; line-height: 1.2rem; } `; - -export const InflationWrapper = styled.div` - width: 100%; - display: flex; - flex-flow: row wrap; - align-items: center; - margin-bottom: 0.75rem; - - h4 { - .help-icon { - margin-left: 0.6rem; - } - } - - > section { - display: flex; - flex-flow: column wrap; - justify-content: center; - flex-basis: 100%; - - .items { - flex-grow: 1; - display: flex; - flex-flow: row wrap; - align-items: center; - width: 100%; - - > div { - flex-grow: 1; - flex-basis: 100%; - width: 100%; - margin-bottom: 0.5rem; - border-right: 0; - - &:last-child { - border-right: 0; - } - - @media (min-width: ${SmallFontSizeMaxWidth + 150}px) { - flex-basis: 25%; - max-width: 275px; - padding-left: 1rem; - padding-right: 1rem; - margin-bottom: 0; - border-right: 1px solid ${borderPrimary}; - - &:last-child { - max-width: none; - } - } - - > .inner { - width: 100%; - padding: 0.5rem 0.5rem 1rem 0.5rem; - display: flex; - flex-flow: row nowrap; - border-bottom: 1px solid ${borderPrimary}; - - @media (min-width: ${SmallFontSizeMaxWidth + 150}px) { - margin-bottom: 0; - } - - h2 { - color: ${networkColor}; - margin-top: 0rem; - margin-bottom: 0; - } - h4 { - display: flex; - flex-flow: row wrap; - color: ${textSecondary}; - margin-top: 0.45rem; - margin-bottom: 0; - } - display: flex; - flex-flow: column wrap; - } - - &:first-child { - padding-left: 0; - } - &:last-child { - padding-right: 0; - } - } - } - } -`; diff --git a/src/pages/Overview/NetworkSats/index.tsx b/src/pages/Overview/NetworkSats/index.tsx index 9eb8aaa844..23dba70951 100644 --- a/src/pages/Overview/NetworkSats/index.tsx +++ b/src/pages/Overview/NetworkSats/index.tsx @@ -1,26 +1,60 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import { CardHeaderWrapper, CardWrapper } from 'library/Graphs/Wrappers'; -import { OpenHelpIcon } from 'library/OpenHelpIcon'; +import BigNumber from 'bignumber.js'; import { useTranslation } from 'react-i18next'; +import { useNetworkMetrics } from 'contexts/NetworkMetrics'; +import { useBondedPools } from 'contexts/Pools/BondedPools'; +import { useStaking } from 'contexts/Staking'; +import { CardHeaderWrapper, CardWrapper } from 'library/Card/Wrappers'; +import { useInflation } from 'library/Hooks/useInflation'; +import { StatsHead } from 'library/StatsHead'; import { Announcements } from './Announcements'; -import { Inflation } from './Inflation'; import { Wrapper } from './Wrappers'; export const NetworkStats = () => { const { t } = useTranslation('pages'); + const { bondedPools } = useBondedPools(); + const { inflation } = useInflation(); + const { metrics } = useNetworkMetrics(); + const { staking } = useStaking(); + const { totalNominators, totalValidators } = staking; + const { totalIssuance } = metrics; + + const items = [ + { + label: t('overview.totalValidators'), + value: totalValidators.toFormat(0), + helpKey: 'Validator', + }, + { + label: t('overview.totalNominators'), + value: totalNominators.toFormat(0), + helpKey: 'Total Nominators', + }, + { + label: t('overview.activePools'), + value: new BigNumber(bondedPools.length).toFormat(), + helpKey: 'Active Pools', + }, + { + label: t('overview.inflationRate'), + value: `${ + totalIssuance.toString() === '0' + ? '0' + : new BigNumber(inflation).decimalPlaces(2).toFormat() + }%`, + helpKey: 'Inflation', + }, + ]; return ( - <CardWrapper> - <CardHeaderWrapper> - <h3> - {t('overview.network_stats')} - <OpenHelpIcon helpKey="Network Stats" /> - </h3> + <CardWrapper style={{ boxShadow: 'var(--card-shadow-secondary)' }}> + <CardHeaderWrapper $withMargin> + <h3>{t('overview.networkStats')}</h3> </CardHeaderWrapper> <Wrapper> - <Inflation /> + <StatsHead items={items} /> <Announcements /> </Wrapper> </CardWrapper> diff --git a/src/pages/Overview/Payouts.tsx b/src/pages/Overview/Payouts.tsx index ef19316470..43a404023a 100644 --- a/src/pages/Overview/Payouts.tsx +++ b/src/pages/Overview/Payouts.tsx @@ -1,61 +1,61 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import { usePlugins } from 'contexts/Plugins'; import { useStaking } from 'contexts/Staking'; import { useUi } from 'contexts/UI'; import { PayoutBar } from 'library/Graphs/PayoutBar'; import { PayoutLine } from 'library/Graphs/PayoutLine'; -import { formatSize, useSize } from 'library/Graphs/Utils'; +import { formatSize } from 'library/Graphs/Utils'; +import { GraphWrapper } from 'library/Graphs/Wrapper'; +import { useSize } from 'library/Hooks/useSize'; import { StatusLabel } from 'library/StatusLabel'; -import React from 'react'; -import { useTranslation } from 'react-i18next'; export const Payouts = () => { - const { isSyncing, services } = useUi(); + const { t } = useTranslation('pages'); + const { isSyncing } = useUi(); + const { plugins } = usePlugins(); const { inSetup } = useStaking(); const notStaking = !isSyncing && inSetup(); - const { t } = useTranslation('pages'); const ref = React.useRef<HTMLDivElement>(null); const size = useSize(ref.current); - const { width, height, minHeight } = formatSize(size, 306); + const { width, height, minHeight } = formatSize(size, 260); return ( <div className="inner" ref={ref} style={{ minHeight }}> - {!services.includes('cereStats') ? ( + {!plugins.includes('cereStats') ? ( <StatusLabel status="active_service" statusFor="cereStats" - title="Cere Stats Disabled" + title={t('overview.subscanDisabled')} topOffset="37%" /> ) : ( <StatusLabel status="sync_or_setup" - title={t('overview.not_staking')} + title={t('overview.notStaking')} topOffset="37%" /> )} - <div - className="graph" + <GraphWrapper style={{ height: `${height}px`, width: `${width}px`, position: 'absolute', opacity: notStaking ? 0.75 : 1, transition: 'opacity 0.5s', - marginTop: '1.5rem', }} > - <PayoutBar days={19} height="160px" /> + <PayoutBar days={19} height="150px" /> <div style={{ marginTop: '3rem' }}> - <PayoutLine days={19} average={10} height="70px" /> + <PayoutLine days={19} average={10} height="65px" /> </div> - </div> + </GraphWrapper> </div> ); }; - -export default Payouts; diff --git a/src/pages/Overview/Reserve.tsx b/src/pages/Overview/Reserve.tsx deleted file mode 100644 index 2df6bb3098..0000000000 --- a/src/pages/Overview/Reserve.tsx +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { faLock } from '@fortawesome/free-solid-svg-icons'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { useApi } from 'contexts/Api'; -import { useBalances } from 'contexts/Balances'; -import { OpenHelpIcon } from 'library/OpenHelpIcon'; -import { useTranslation } from 'react-i18next'; -import { planckBnToUnit, toFixedIfNecessary } from 'Utils'; -import { ReserveProps } from './types'; -import { ReserveWrapper, SectionWrapper, Separator } from './Wrappers'; - -export const Reserve = (props: ReserveProps) => { - const { height } = props; - const { network } = useApi(); - const { existentialAmount } = useBalances(); - const { t } = useTranslation('pages'); - - return ( - <SectionWrapper style={{ height }}> - <ReserveWrapper> - <Separator /> - <h4> - {t('overview.reserved')} - <OpenHelpIcon helpKey="Reserve Balance" /> - </h4> - - <div className="inner"> - <section> - <h3 className="reserve"> - <FontAwesomeIcon - icon={faLock} - transform="shrink-4" - className="icon" - /> - {`${toFixedIfNecessary( - planckBnToUnit(existentialAmount, network.units), - 5 - )} ${network.unit}`} - </h3> - </section> - </div> - </ReserveWrapper> - </SectionWrapper> - ); -}; - -export default Reserve; diff --git a/src/pages/Overview/Tips/Items.tsx b/src/pages/Overview/StakeStatus/Tips/Items.tsx similarity index 54% rename from src/pages/Overview/Tips/Items.tsx rename to src/pages/Overview/StakeStatus/Tips/Items.tsx index 1cd5355619..ab14117221 100644 --- a/src/pages/Overview/Tips/Items.tsx +++ b/src/pages/Overview/StakeStatus/Tips/Items.tsx @@ -1,15 +1,13 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import { faChevronRight } from '@fortawesome/free-solid-svg-icons'; +import { faExternalLinkAlt } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { useOverlay } from 'contexts/Overlay'; -import { motion, useAnimationControls } from 'framer-motion'; -import { Tip } from 'library/Tips/Tip'; +import { useAnimationControls } from 'framer-motion'; import React, { useEffect, useState } from 'react'; -import { useTranslation } from 'react-i18next'; -import Lottie from 'react-lottie'; -import { ItemInnerWrapper, ItemsWrapper, ItemWrapper } from './Wrappers'; +import { usePrompt } from 'contexts/Prompt'; +import { Tip } from 'library/Tips/Tip'; +import { ItemInnerWrapper, ItemWrapper, ItemsWrapper } from './Wrappers'; export const ItemsInner = ({ items, page }: any) => { const controls = useAnimationControls(); @@ -59,14 +57,12 @@ const Item = ({ title, subtitle, description, - icon, index, controls, initial, + page, }: any) => { - const { openOverlayWith } = useOverlay(); - const { t } = useTranslation('tips'); - + const { openPromptWith } = usePrompt(); const [isStopped, setIsStopped] = useState(true); useEffect(() => { @@ -81,15 +77,6 @@ const Item = ({ } }, []); - const animateOptions = { - loop: false, - autoplay: false, - animationData: icon, - rendererSettings: { - preserveAspectRatio: 'xMidYMid slice', - }, - }; - return ( <ItemWrapper animate={controls} @@ -110,43 +97,26 @@ const Item = ({ }} > <ItemInnerWrapper> + <section /> <section> - <Lottie - options={animateOptions} - width="2.2rem" - height="2.2rem" - isStopped={isStopped} - isPaused={isStopped} - eventListeners={[ - { - eventName: 'loopComplete', - callback: () => setIsStopped(true), - }, - ]} - /> - </section> - <section> - <div className="title"> - <h3>{title}</h3> - </div> - <div className="desc"> - <h4> - {subtitle} - <motion.button - whileHover={{ scale: 1.02 }} - onClick={() => - openOverlayWith( - <Tip title={title} description={description} />, - 'large' - ) - } - type="button" - className="more" - > - {t('module.more')} - <FontAwesomeIcon icon={faChevronRight} transform="shrink-2" /> - </motion.button> - </h4> + <div className="desc active"> + <button + onClick={() => + openPromptWith( + <Tip title={title} description={description} page={page} />, + 'large' + ) + } + type="button" + > + <h4> + {subtitle} + <FontAwesomeIcon + icon={faExternalLinkAlt} + transform="shrink-2" + /> + </h4> + </button> </div> </section> </ItemInnerWrapper> @@ -163,5 +133,3 @@ export class Items extends React.Component<any, any> { return <ItemsInner {...this.props} />; } } - -export default Items; diff --git a/src/pages/Overview/StakeStatus/Tips/PageToggle.tsx b/src/pages/Overview/StakeStatus/Tips/PageToggle.tsx new file mode 100644 index 0000000000..d77048f9e6 --- /dev/null +++ b/src/pages/Overview/StakeStatus/Tips/PageToggle.tsx @@ -0,0 +1,69 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { + faChevronCircleLeft, + faChevronCircleRight, +} from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { useTranslation } from 'react-i18next'; +import { useUi } from 'contexts/UI'; +import { PageToggleWrapper } from './Wrappers'; +import type { PageToggleProps } from './types'; + +export const PageToggle = ({ + start, + end, + page, + itemsPerPage, + totalItems, + setPageHandler, +}: PageToggleProps) => { + const { t } = useTranslation(); + const { isNetworkSyncing } = useUi(); + + totalItems = isNetworkSyncing ? 1 : totalItems; + const totalPages = Math.ceil(totalItems / itemsPerPage); + + return ( + <PageToggleWrapper> + <button + type="button" + disabled={totalPages === 1 || page === 1} + onClick={() => { + setPageHandler(page - 1); + }} + > + <FontAwesomeIcon + icon={faChevronCircleLeft} + className="icon" + transform="grow-2" + /> + </button> + <h4 className={totalPages === 1 ? `disabled` : undefined}> + <span> + {start} + {itemsPerPage > 1 && totalItems > 1 && start !== end && ` - ${end}`} + </span> + {totalPages > 1 && ( + <> + {t('module.of', { ns: 'tips' })} <span>{totalItems}</span> + </> + )} + </h4> + <button + type="button" + disabled={totalPages === 1 || page === totalPages} + onClick={() => { + setPageHandler(page + 1); + }} + > + <FontAwesomeIcon + icon={faChevronCircleRight} + className="icon" + transform="grow-2" + /> + </button> + </PageToggleWrapper> + ); +}; diff --git a/src/pages/Overview/StakeStatus/Tips/Syncing.tsx b/src/pages/Overview/StakeStatus/Tips/Syncing.tsx new file mode 100644 index 0000000000..810e8010de --- /dev/null +++ b/src/pages/Overview/StakeStatus/Tips/Syncing.tsx @@ -0,0 +1,45 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { useTranslation } from 'react-i18next'; +import { useDotLottieButton } from 'library/Hooks/useDotLottieButton'; +import { ItemInnerWrapper, ItemWrapper, ItemsWrapper } from './Wrappers'; + +export const Syncing = () => { + const { t } = useTranslation('tips'); + const { icon } = useDotLottieButton('refresh', { autoLoop: true }); + + return ( + <ItemsWrapper + initial="show" + animate={undefined} + variants={{ + hidden: { opacity: 0 }, + show: { + opacity: 1, + }, + }} + > + <ItemWrapper> + <ItemInnerWrapper> + <section + style={{ + marginRight: '0.5rem', + width: '1.5rem', + height: '1.5rem', + }} + > + {icon} + </section> + <section> + <div className="desc"> + <button type="button" disabled> + <h4>{t('module.oneMoment')}...</h4> + </button> + </div> + </section> + </ItemInnerWrapper> + </ItemWrapper> + </ItemsWrapper> + ); +}; diff --git a/src/pages/Overview/StakeStatus/Tips/Wrappers.ts b/src/pages/Overview/StakeStatus/Tips/Wrappers.ts new file mode 100644 index 0000000000..58efb14be3 --- /dev/null +++ b/src/pages/Overview/StakeStatus/Tips/Wrappers.ts @@ -0,0 +1,155 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { motion } from 'framer-motion'; +import styled from 'styled-components'; +import { SideMenuStickyThreshold, SmallFontSizeMaxWidth } from 'consts'; + +export const TipsWrapper = styled.div` + width: 100%; + display: flex; + position: relative; + padding: 0.15rem 1rem 0.7rem 1.25rem; + margin-top: 0.5rem; + margin-bottom: 0.5rem; + + @media (max-width: ${SideMenuStickyThreshold}px) { + padding: 0.5rem 1rem; + } +`; + +export const ItemsWrapper = styled(motion.div)` + width: 100%; + display: flex; + justify-items: center; + margin: 0.25rem 0 0rem 0; +`; +export const ItemWrapper = styled(motion.div)` + padding: 0 0.25rem; + flex-basis: 100%; + &:last-child { + margin-right: 0.25rem; + } +`; + +export const ItemInnerWrapper = styled.div` + display: flex; + flex-flow: row wrap; + align-items: center; + + > section { + height: 100%; + + &:nth-child(1) { + display: flex; + flex-flow: row wrap; + align-items: center; + padding-top: 0.1rem; + } + + &:nth-child(2) { + display: flex; + flex-flow: column nowrap; + align-items: flex-start; + justify-content: center; + flex: 1; + + .desc { + display: flex; + flex-flow: column nowrap; + align-items: center; + justify-content: flex-start; + overflow: hidden; + width: 100%; + height: 1.85rem; + position: relative; + + &.active { + h4:hover { + color: var(--accent-color-primary); + .more { + color: var(--accent-color-primary); + opacity: 1; + } + } + } + + > button { + position: absolute; + top: 0; + left: 0; + height: 1.85rem; + max-width: 100%; + width: auto; + + > h4 { + color: var(--text-color-secondary); + transition: color var(--transition-duration); + font-family: InterSemiBold, sans-serif; + text-align: left; + font-size: 1.05rem; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + padding: 0.15rem 1.75rem 0rem 0; + width: 100%; + + > svg { + color: var(--text-color-secondary); + transition: all var(--transition-duration); + position: absolute; + right: 0.2rem; + top: 0.43rem; + display: flex; + align-items: center; + font-size: 1rem; + opacity: 0.5; + margin-left: 0.4rem; + } + } + } + } + } + } +`; + +export const PageToggleWrapper = styled.div` + color: var(--text-color-secondary); + border-radius: 1.5rem; + position: relative; + top: 0.2rem; + display: flex; + flex-flow: row wrap; + margin-left: 0.5rem; + + > button { + margin: 0 0.5rem; + opacity: 0.75; + font-size: 1.1rem; + transition: color var(--transition-duration); + > svg { + color: var(--text-color-secondary); + } + &:hover { + opacity: 1; + color: var(--accent-color-primary); + } + &:disabled { + color: var(--text-color-secondary); + opacity: var(--opacity-disabled); + } + } + + h4 { + @media (max-width: ${SmallFontSizeMaxWidth}px) { + display: none; + } + margin: 0; + span { + margin: 0 0.5rem; + } + &.disabled { + opacity: var(--opacity-disabled); + } + } +`; diff --git a/src/pages/Overview/StakeStatus/Tips/index.tsx b/src/pages/Overview/StakeStatus/Tips/index.tsx new file mode 100644 index 0000000000..0055ecda0d --- /dev/null +++ b/src/pages/Overview/StakeStatus/Tips/index.tsx @@ -0,0 +1,202 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { setStateWithRef } from '@polkadot-cloud/utils'; +import throttle from 'lodash.throttle'; +import { useEffect, useRef, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { TipsConfig } from 'config/tips'; +import { DefaultLocale, TipsThresholdMedium, TipsThresholdSmall } from 'consts'; +import { useActivePools } from 'contexts/Pools/ActivePools'; +import { usePoolMemberships } from 'contexts/Pools/PoolMemberships'; +import { useStaking } from 'contexts/Staking'; +import { useTransferOptions } from 'contexts/TransferOptions'; +import { useUi } from 'contexts/UI'; +import { useFillVariables } from 'library/Hooks/useFillVariables'; +import type { AnyJson } from 'types'; +import { useNetwork } from 'contexts/Network'; +import { useActiveAccounts } from 'contexts/ActiveAccounts'; +import { Items } from './Items'; +import { PageToggle } from './PageToggle'; +import { Syncing } from './Syncing'; +import { TipsWrapper } from './Wrappers'; + +export const Tips = () => { + const { i18n, t } = useTranslation(); + const { network } = useNetwork(); + const { isNetworkSyncing } = useUi(); + const { activeAccount } = useActiveAccounts(); + const { fillVariables } = useFillVariables(); + const { membership } = usePoolMemberships(); + const { isNominating, staking } = useStaking(); + const { isOwner } = useActivePools(); + const { feeReserve, getTransferOptions } = useTransferOptions(); + const { minNominatorBond } = staking; + const transferOptions = getTransferOptions(activeAccount); + + // multiple tips per row is currently turned off. + const multiTipsPerRow = false; + + // helper function to determine the number of items to display per page. + // UI displays 1 item by default. + const getItemsPerPage = () => { + if (!multiTipsPerRow) { + return 1; + } + if (window.innerWidth < TipsThresholdSmall) { + return 1; + } + if ( + window.innerWidth >= TipsThresholdSmall && + window.innerWidth < TipsThresholdMedium + ) { + return 2; + } + return 3; + }; + + // helper function to determine which page we should be on upon page resize. + // This function ensures totalPages is never surpassed, but does not guarantee + // that the start item will maintain across resizes. + const getPage = () => { + const totalItmes = isNetworkSyncing ? 1 : items.length; + const itemsPerPage = getItemsPerPage(); + const totalPages = Math.ceil(totalItmes / itemsPerPage); + if (pageRef.current > totalPages) { + return totalPages; + } + const end = pageRef.current * itemsPerPage; + const start = end - (itemsPerPage - 1); + return Math.ceil(start / itemsPerPage); + }; + + // resize callback + const resizeCallback = () => { + setStateWithRef(getPage(), setPage, pageRef); + setStateWithRef(getItemsPerPage(), setItemsPerPageState, itemsPerPageRef); + }; + + // throttle resize callback + const throttledResizeCallback = throttle(resizeCallback, 200, { + trailing: true, + leading: false, + }); + + // re-sync page when active account changes + useEffect(() => { + setStateWithRef(getPage(), setPage, pageRef); + }, [activeAccount, network]); + + // resize event listener + useEffect(() => { + window.addEventListener('resize', throttledResizeCallback); + return () => { + window.removeEventListener('resize', throttledResizeCallback); + }; + }, []); + + // store the current amount of allowed items on display + const [itemsPerPage, setItemsPerPageState] = useState<number>( + getItemsPerPage() + ); + const itemsPerPageRef = useRef(itemsPerPage); + + // store the current page + const [page, setPage] = useState<number>(1); + const pageRef = useRef(page); + + // accumulate segments to include in tips + const segments: AnyJson = []; + if (!activeAccount) { + segments.push(1); + } else if (!isNominating() && !membership) { + if ( + transferOptions.freeBalance + .minus(feeReserve) + .isGreaterThan(minNominatorBond) + ) { + segments.push(2); + } else { + segments.push(3); + } + segments.push(4); + } else { + if (isNominating()) { + segments.push(5); + } + if (membership) { + if (!isOwner()) { + segments.push(6); + } else { + segments.push(7); + } + } + segments.push(8); + } + + // filter tips relevant to connected account. + let items = TipsConfig.filter((i: AnyJson) => segments.includes(i.s)); + + items = items.map((i: any) => { + const { id } = i; + + return fillVariables( + { + ...i, + title: t(`${id}.0`, { ns: 'tips' }), + subtitle: t(`${id}.1`, { ns: 'tips' }), + description: i18n.getResource( + i18n.resolvedLanguage ?? DefaultLocale, + 'tips', + `${id}.2` + ), + }, + ['title', 'subtitle', 'description'] + ); + }); + + // determine items to be displayed + const end = isNetworkSyncing + ? 1 + : Math.min(pageRef.current * itemsPerPageRef.current, items.length); + const start = isNetworkSyncing + ? 1 + : pageRef.current * itemsPerPageRef.current - (itemsPerPageRef.current - 1); + + const itemsDisplay = items.slice(start - 1, end); + + const setPageHandler = (newPage: number) => { + setStateWithRef(newPage, setPage, pageRef); + }; + return ( + <TipsWrapper> + <div style={{ flexGrow: 1 }}> + {isNetworkSyncing ? ( + <Syncing /> + ) : ( + <Items + items={itemsDisplay} + page={pageRef.current} + showTitle={false} + /> + )} + </div> + <div + style={{ + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + }} + > + <PageToggle + start={start} + end={end} + page={page} + itemsPerPage={itemsPerPage} + totalItems={items.length} + setPageHandler={setPageHandler} + /> + </div> + </TipsWrapper> + ); +}; diff --git a/src/pages/Overview/StakeStatus/Tips/types.ts b/src/pages/Overview/StakeStatus/Tips/types.ts new file mode 100644 index 0000000000..1d53e35f20 --- /dev/null +++ b/src/pages/Overview/StakeStatus/Tips/types.ts @@ -0,0 +1,11 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +export interface PageToggleProps { + start: number; + end: number; + page: number; + itemsPerPage: number; + totalItems: number; + setPageHandler: (p: number) => void; +} diff --git a/src/pages/Overview/StakeStatus/Wrappers.ts b/src/pages/Overview/StakeStatus/Wrappers.ts new file mode 100644 index 0000000000..d345659ff2 --- /dev/null +++ b/src/pages/Overview/StakeStatus/Wrappers.ts @@ -0,0 +1,45 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import styled from 'styled-components'; +import { SideMenuStickyThreshold } from 'consts'; + +export const StatusWrapper = styled.div` + display: flex; + flex-wrap: wrap; + padding: 1.5rem 1.5rem 0 1.5rem; + + @media (max-width: ${SideMenuStickyThreshold}px) { + padding: 1.5rem 0.75rem 0 0.75rem; + } + + > div { + @media (max-width: ${SideMenuStickyThreshold}px) { + margin-top: 1rem; + } + + &:first-child { + margin-top: 0; + } + + &:last-child { + padding-left: 1.5rem; + @media (max-width: ${SideMenuStickyThreshold}px) { + padding-left: 0; + } + } + + > section { + border-bottom: 1px solid var(--border-primary-color); + padding-bottom: 0.75rem; + @media (max-width: ${SideMenuStickyThreshold}px) { + padding-bottom: 0.5rem; + } + border-radius: 0; + + > div { + padding-top: 0; + } + } + } +`; diff --git a/src/pages/Overview/StakeStatus/index.tsx b/src/pages/Overview/StakeStatus/index.tsx new file mode 100644 index 0000000000..d689a64039 --- /dev/null +++ b/src/pages/Overview/StakeStatus/index.tsx @@ -0,0 +1,34 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { RowSection } from '@polkadot-cloud/react'; +import { usePlugins } from 'contexts/Plugins'; +import { CardWrapper } from 'library/Card/Wrappers'; +import { NominationStatus } from 'pages/Nominate/Active/Status/NominationStatus'; +import { MembershipStatus } from 'pages/Pools/Home/Status/MembershipStatus'; +import { Tips } from './Tips'; +import { StatusWrapper } from './Wrappers'; + +export const StakeStatus = () => { + const { plugins } = usePlugins(); + const showTips = plugins.includes('tips'); + + return ( + <CardWrapper style={{ padding: 0 }}> + <StatusWrapper> + <RowSection secondary> + <section> + <NominationStatus showButtons={false} /> + </section> + </RowSection> + <RowSection hLast vLast> + <section> + <MembershipStatus showButtons={false} /> + </section> + </RowSection> + </StatusWrapper> + + {showTips ? <Tips /> : null} + </CardWrapper> + ); +}; diff --git a/src/pages/Overview/Stats/ActiveEra.tsx b/src/pages/Overview/Stats/ActiveEra.tsx deleted file mode 100644 index cd3dfd62cf..0000000000 --- a/src/pages/Overview/Stats/ActiveEra.tsx +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { useNetworkMetrics } from 'contexts/Network'; -import { useSessionEra } from 'contexts/SessionEra'; -import { format, fromUnixTime } from 'date-fns'; -import { useEraTimeLeft } from 'library/Hooks/useEraTimeLeft'; -import { Pie } from 'library/StatBoxList/Pie'; -import { locales } from 'locale'; -import { useTranslation } from 'react-i18next'; - -const ActiveEraStatBox = () => { - const { metrics } = useNetworkMetrics(); - const { sessionEra } = useSessionEra(); - const eraTimeLeft = useEraTimeLeft(); - const { i18n, t } = useTranslation('pages'); - - const _timeleft = fromUnixTime(eraTimeLeft); - const timeleft = format(_timeleft, 'kk:mm:ss', { - locale: locales[i18n.resolvedLanguage], - }); - - const params = { - label: t('overview.active_era'), - stat: { - value: metrics.activeEra.index, - unit: '', - }, - graph: { - value1: sessionEra.eraProgress, - value2: sessionEra.eraLength - sessionEra.eraProgress, - }, - tooltip: metrics.activeEra.index === 0 ? undefined : timeleft, - helpKey: 'Era', - }; - return <Pie {...params} />; -}; - -export default ActiveEraStatBox; diff --git a/src/pages/Overview/Stats/ActiveEraTimeLeft.tsx b/src/pages/Overview/Stats/ActiveEraTimeLeft.tsx new file mode 100644 index 0000000000..9eaa264ca5 --- /dev/null +++ b/src/pages/Overview/Stats/ActiveEraTimeLeft.tsx @@ -0,0 +1,44 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import BigNumber from 'bignumber.js'; +import { fromUnixTime } from 'date-fns'; +import { useEffect } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useApi } from 'contexts/Api'; +import { useNetworkMetrics } from 'contexts/NetworkMetrics'; +import { useEraTimeLeft } from 'library/Hooks/useEraTimeLeft'; +import { useTimeLeft } from 'library/Hooks/useTimeLeft'; +import { fromNow } from 'library/Hooks/useTimeLeft/utils'; +import { Timeleft } from 'library/StatBoxList/Timeleft'; + +export const ActiveEraStat = () => { + const { t } = useTranslation('pages'); + const { apiStatus } = useApi(); + const { activeEra } = useNetworkMetrics(); + const { get: getEraTimeleft } = useEraTimeLeft(); + const { timeleft, setFromNow } = useTimeLeft(); + + const dateFrom = fromUnixTime(Date.now() / 1000); + const dateTo = fromNow(getEraTimeleft().timeleft.toNumber()); + + // re-set timer on era change (also covers network change). + useEffect(() => { + setFromNow(dateFrom, dateTo); + }, [apiStatus, activeEra]); + + // NOTE: this maybe should be called in an interval. Needs more testing. + const { percentSurpassed, percentRemaining } = getEraTimeleft(); + + const params = { + label: t('overview.timeRemainingThisEra'), + timeleft: timeleft.formatted, + graph: { + value1: activeEra.index.isZero() ? 0 : percentSurpassed.toNumber(), + value2: activeEra.index.isZero() ? 100 : percentRemaining.toNumber(), + }, + tooltip: `Era ${new BigNumber(activeEra.index).toFormat()}` ?? undefined, + helpKey: 'Era', + }; + return <Timeleft {...params} />; +}; diff --git a/src/pages/Overview/Stats/ActiveNominators.tsx b/src/pages/Overview/Stats/ActiveNominators.tsx deleted file mode 100644 index 7f7e75cb19..0000000000 --- a/src/pages/Overview/Stats/ActiveNominators.tsx +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import BN from 'bn.js'; -import { useApi } from 'contexts/Api'; -import { useStaking } from 'contexts/Staking'; -import { Pie } from 'library/StatBoxList/Pie'; -import { useTranslation } from 'react-i18next'; -import { toFixedIfNecessary } from 'Utils'; - -export const ActiveNominatorsStatBox = () => { - const { consts } = useApi(); - const { maxElectingVoters } = consts; - const { eraStakers } = useStaking(); - const { totalActiveNominators } = eraStakers; - const { t } = useTranslation('pages'); - - // active nominators as percent - let totalNominatorsAsPercent = 0; - if (maxElectingVoters > 0) { - totalNominatorsAsPercent = - totalActiveNominators / - new BN(maxElectingVoters).div(new BN(100)).toNumber(); - } - - const params = { - label: t('overview.active_nominators'), - stat: { - value: totalActiveNominators, - total: maxElectingVoters, - unit: '', - }, - graph: { - value1: totalActiveNominators, - value2: maxElectingVoters - totalActiveNominators, - }, - tooltip: `${toFixedIfNecessary(totalNominatorsAsPercent, 2)}%`, - helpKey: 'Active Nominators', - }; - - return <Pie {...params} />; -}; - -export default ActiveNominatorsStatBox; diff --git a/src/pages/Overview/Stats/HistoricalRewardsRate.tsx b/src/pages/Overview/Stats/HistoricalRewardsRate.tsx new file mode 100644 index 0000000000..f147d07739 --- /dev/null +++ b/src/pages/Overview/Stats/HistoricalRewardsRate.tsx @@ -0,0 +1,38 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import BigNumber from 'bignumber.js'; +import { useTranslation } from 'react-i18next'; +import { useNetworkMetrics } from 'contexts/NetworkMetrics'; +import { useInflation } from 'library/Hooks/useInflation'; +import { Text } from 'library/StatBoxList/Text'; + +export const HistoricalRewardsRateStat = () => { + const { t } = useTranslation('pages'); + const { metrics } = useNetworkMetrics(); + const { inflation, stakedReturn } = useInflation(); + const { totalIssuance } = metrics; + + const value = `${ + totalIssuance.isZero() + ? '0' + : new BigNumber(stakedReturn).decimalPlaces(2).toFormat() + }%`; + + const secondaryValue = + totalIssuance.isZero() || stakedReturn === 0 + ? undefined + : `/ ${new BigNumber(Math.max(0, stakedReturn - inflation)) + .decimalPlaces(2) + .toFormat()}% ${t('overview.afterInflation')}`; + + const params = { + label: t('overview.historicalRewardsRate'), + value, + secondaryValue, + helpKey: 'Historical Rewards Rate', + primary: true, + }; + + return <Text {...params} />; +}; diff --git a/src/pages/Overview/Stats/SupplyStaked.tsx b/src/pages/Overview/Stats/SupplyStaked.tsx new file mode 100644 index 0000000000..e23c67bc51 --- /dev/null +++ b/src/pages/Overview/Stats/SupplyStaked.tsx @@ -0,0 +1,49 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { planckToUnit } from '@polkadot-cloud/utils'; +import BigNumber from 'bignumber.js'; +import { useTranslation } from 'react-i18next'; +import { useNetworkMetrics } from 'contexts/NetworkMetrics'; +import { useStaking } from 'contexts/Staking'; +import { Pie } from 'library/StatBoxList/Pie'; +import { useNetwork } from 'contexts/Network'; + +export const SupplyStakedStat = () => { + const { t } = useTranslation('pages'); + const { units, unit } = useNetwork().networkData; + const { metrics } = useNetworkMetrics(); + const { staking } = useStaking(); + + const { lastTotalStake } = staking; + const { totalIssuance } = metrics; + + console.warn(`Total Issuance: ${totalIssuance}`); + console.warn(`Total Stake: ${lastTotalStake}`); + // total supply as percent. + const totalIssuanceUnit = planckToUnit(totalIssuance, units); + const lastTotalStakeUnit = planckToUnit(lastTotalStake, units); + const supplyAsPercent = + lastTotalStakeUnit.isZero() || totalIssuanceUnit.isZero() + ? new BigNumber(0) + : lastTotalStakeUnit.dividedBy(totalIssuanceUnit.multipliedBy(0.01)); + + const params = { + label: t('overview.unitSupplyStaked', { unit }), + stat: { + value: `${supplyAsPercent.decimalPlaces(2).toFormat()}`, + unit: '%', + }, + graph: { + value1: supplyAsPercent.decimalPlaces(2).toNumber(), + value2: new BigNumber(100) + .minus(supplyAsPercent) + .decimalPlaces(2) + .toNumber(), + }, + tooltip: `${supplyAsPercent.decimalPlaces(2).toFormat()}%`, + helpKey: 'Supply Staked', + }; + + return <Pie {...params} />; +}; diff --git a/src/pages/Overview/Stats/TotalNominations.tsx b/src/pages/Overview/Stats/TotalNominations.tsx deleted file mode 100644 index 4361a574a9..0000000000 --- a/src/pages/Overview/Stats/TotalNominations.tsx +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import BN from 'bn.js'; -import { useStaking } from 'contexts/Staking'; -import { Pie } from 'library/StatBoxList/Pie'; -import { useTranslation } from 'react-i18next'; -import { toFixedIfNecessary } from 'Utils'; - -export const TotalNominatorsStatBox = () => { - const { staking } = useStaking(); - const { totalNominators, maxNominatorsCount } = staking; - const { t } = useTranslation('pages'); - - // total active nominators as percent - let totalNominatorsAsPercent = 0; - if (maxNominatorsCount.gt(new BN(0))) { - totalNominatorsAsPercent = totalNominators - .div(maxNominatorsCount.div(new BN(100))) - .toNumber(); - } - - const params = { - label: t('overview.total_nominators'), - stat: { - value: totalNominators.toNumber(), - total: maxNominatorsCount.toNumber(), - unit: '', - }, - graph: { - value1: totalNominators.toNumber(), - value2: maxNominatorsCount.sub(totalNominators).toNumber(), - }, - - tooltip: `${toFixedIfNecessary(totalNominatorsAsPercent, 2)}%`, - helpKey: 'Total Nominators', - }; - - return <Pie {...params} />; -}; - -export default TotalNominatorsStatBox; diff --git a/src/pages/Overview/Tips/Syncing.tsx b/src/pages/Overview/Tips/Syncing.tsx deleted file mode 100644 index 39a25da4bb..0000000000 --- a/src/pages/Overview/Tips/Syncing.tsx +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { useApi } from 'contexts/Api'; -import * as refreshChangeJson from 'img/json/refresh-change-outline.json'; -import { useTranslation } from 'react-i18next'; -import Lottie from 'react-lottie'; -import { ItemInnerWrapper, ItemsWrapper, ItemWrapper } from './Wrappers'; - -export const Syncing = () => { - const { - network: { name }, - } = useApi(); - const { t } = useTranslation('tips'); - - const animateOptions = { - loop: true, - autoplay: true, - animationData: refreshChangeJson, - rendererSettings: { - preserveAspectRatio: 'xMidYMid slice', - }, - }; - - return ( - <ItemsWrapper - initial="show" - animate={undefined} - variants={{ - hidden: { opacity: 0 }, - show: { - opacity: 1, - }, - }} - > - <ItemWrapper> - <ItemInnerWrapper inactive> - <section> - <Lottie - options={animateOptions} - width="2.2rem" - height="2.2rem" - isStopped={false} - isPaused={false} - /> - </section> - <section> - <div className="title"> - <h3>{t('module.syncing_with', { network: name })}</h3> - </div> - <div className="desc"> - <h4>{t('module.one_moment')}</h4> - </div> - </section> - </ItemInnerWrapper> - </ItemWrapper> - </ItemsWrapper> - ); -}; diff --git a/src/pages/Overview/Tips/Wrappers.tsx b/src/pages/Overview/Tips/Wrappers.tsx deleted file mode 100644 index 857bbc9cde..0000000000 --- a/src/pages/Overview/Tips/Wrappers.tsx +++ /dev/null @@ -1,195 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { motion } from 'framer-motion'; -import styled from 'styled-components'; -import { - backgroundLabel, - networkColor, - textPrimary, - textSecondary, -} from 'theme'; - -export const ItemsWrapper = styled(motion.div)` - width: 100%; - display: flex; - flex-flow: row nowrap; - justify-items: center; - margin: 0.25rem 0 0rem 0; -`; -export const ItemWrapper = styled(motion.div)` - padding: 0; - flex-basis: 100%; - &:last-child { - margin-right: 0.25rem; - } -`; - -export const ItemInnerWrapper = styled.div<{ inactive?: boolean }>` - border-radius: 1.25rem; - transition: border 0.05s; - display: flex; - flex-flow: row wrap; - height: 4rem; - transition: border 0.2s; - - > section { - height: 100%; - - &:nth-child(1) { - display: flex; - flex-flow: row wrap; - align-items: center; - padding-right: 1rem; - - .lpf { - fill: ${networkColor}; - } - .lps { - stroke: ${networkColor}; - } - } - - &:nth-child(2) { - display: flex; - flex-flow: column nowrap; - align-items: flex-start; - justify-content: center; - flex: 1; - - .title { - display: flex; - flex-flow: row nowrap; - align-items: center; - text-align: left; - overflow: hidden; - position: relative; - width: 100%; - height: 1.9rem; - position: relative; - top: 0.2rem; - - > h3 { - position: absolute; - top: 0; - left: 0; - width: auto; - max-width: 100%; - color: ${textPrimary}; - font-variation-settings: 'wght' 625; - margin: 0; - font-size: 1.2rem; - - padding-right: 6.75rem; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - height: 1.9rem; - - > span { - position: absolute; - right: 0; - min-width: 6.2rem; - font-variation-settings: 'wght' 500; - background: ${backgroundLabel}; - color: ${textSecondary}; - font-size: 0.97rem; - margin-left: 0.25rem; - padding: 0rem 0.6rem; - border-radius: 1.5rem; - opacity: 0.9; - text-align: center; - } - } - } - - .desc { - display: flex; - flex-flow: column nowrap; - align-items: center; - justify-content: flex-start; - overflow: hidden; - width: 100%; - height: 1.85rem; - position: relative; - - h4 { - color: ${textSecondary}; - position: absolute; - top: 0; - left: 0; - width: auto; - height: 1.85rem; - max-width: 100%; - margin: 0; - padding: 0.15rem 6.3rem 0rem 0; - text-align: left; - font-size: 1.05rem; - text-overflow: ellipsis; - white-space: nowrap; - overflow: hidden; - } - .more { - position: absolute; - right: 0.2rem; - top: 0rem; - display: flex; - flex-flow: row nowrap; - align-items: center; - border: 1px solid ${networkColor}; - color: ${networkColor}; - border-radius: 1.5rem; - padding: 0rem 0.8rem; - font-size: 1rem; - - > svg { - margin-left: 0.4rem; - } - } - } - } - } -`; - -export const PageToggleWrapper = styled.div` - background: ${backgroundLabel}; - color: ${textSecondary}; - padding: 0.25rem 0.5rem; - border-radius: 1.5rem; - position: relative; - top: -0.2rem; - display: flex; - flex-flow: row wrap; - margin-left: 0.75rem; - - > button { - margin: 0 0.5rem; - opacity: 0.75; - font-size: 1.1rem; - transition: color 0.2s; - - > svg { - color: ${textSecondary}; - } - - &:hover { - opacity: 1; - color: ${networkColor}; - } - - &:disabled { - color: ${textSecondary}; - opacity: 0.1; - } - } - - h4 { - margin: 0; - span { - margin: 0 0.5rem; - } - &.disabled { - opacity: 0.25; - } - } -`; diff --git a/src/pages/Overview/Tips/index.tsx b/src/pages/Overview/Tips/index.tsx deleted file mode 100644 index b01e62bb8e..0000000000 --- a/src/pages/Overview/Tips/index.tsx +++ /dev/null @@ -1,244 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { - faChevronCircleLeft, - faChevronCircleRight, - faCog, -} from '@fortawesome/free-solid-svg-icons'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { TIPS_CONFIG } from 'config/tips'; -import { TipsThresholdMedium, TipsThresholdSmall } from 'consts'; -import { useApi } from 'contexts/Api'; -import { useConnect } from 'contexts/Connect'; -import { useModal } from 'contexts/Modal'; -import { useActivePools } from 'contexts/Pools/ActivePools'; -import { usePoolMemberships } from 'contexts/Pools/PoolMemberships'; -import { useStaking } from 'contexts/Staking'; -import { useTransferOptions } from 'contexts/TransferOptions'; -import { useUi } from 'contexts/UI'; -import { CardHeaderWrapper, CardWrapper } from 'library/Graphs/Wrappers'; -import useFillVariables from 'library/Hooks/useFillVariables'; -import { OpenHelpIcon } from 'library/OpenHelpIcon'; -import throttle from 'lodash.throttle'; -import { useEffect, useRef, useState } from 'react'; -import { useTranslation } from 'react-i18next'; -import { AnyJson } from 'types'; -import { setStateWithRef } from 'Utils'; -import { Items } from './Items'; -import { Syncing } from './Syncing'; -import { PageToggleWrapper } from './Wrappers'; - -export const Tips = () => { - const { network } = useApi(); - const { activeAccount } = useConnect(); - const { networkSyncing } = useUi(); - const { openModalWith } = useModal(); - const { fillVariables } = useFillVariables(); - const { membership } = usePoolMemberships(); - const { isNominating, staking } = useStaking(); - const { isOwner } = useActivePools(); - const { getTransferOptions } = useTransferOptions(); - const { minNominatorBond, totalNominators, maxNominatorsCount } = staking; - const transferOptions = getTransferOptions(activeAccount); - const { t, i18n } = useTranslation(); - - // multiple tips per row is currently turned off. - const multiTipsPerRow = false; - - // helper function to determine the number of items to display per page. - // UI displays 1 item by default. - const getItemsPerPage = () => { - if (!multiTipsPerRow) { - return 1; - } - if (window.innerWidth < TipsThresholdSmall) { - return 1; - } - if ( - window.innerWidth >= TipsThresholdSmall && - window.innerWidth < TipsThresholdMedium - ) { - return 2; - } - return 3; - }; - - // helper function to determine which page we should be on upon page resize. - // This function ensures totalPages is never surpassed, but does not guarantee - // that the start item will maintain across resizes. - const getPage = () => { - const totalItmes = networkSyncing ? 1 : items.length; - const itemsPerPage = getItemsPerPage(); - const totalPages = Math.ceil(totalItmes / itemsPerPage); - if (pageRef.current > totalPages) { - return totalPages; - } - const end = pageRef.current * itemsPerPage; - const start = end - (itemsPerPage - 1); - return Math.ceil(start / itemsPerPage); - }; - - // resize callback - const resizeCallback = () => { - setStateWithRef(getPage(), setPage, pageRef); - setStateWithRef(getItemsPerPage(), setItemsPerPage, itemsPerPageRef); - }; - - // throttle resize callback - const throttledResizeCallback = throttle(resizeCallback, 200, { - trailing: true, - leading: false, - }); - - // re-sync page when active account changes - useEffect(() => { - setStateWithRef(getPage(), setPage, pageRef); - }, [activeAccount, network]); - - // resize event listener - useEffect(() => { - window.addEventListener('resize', throttledResizeCallback); - return () => { - window.removeEventListener('resize', throttledResizeCallback); - }; - }, []); - - // store the current amount of allowed items on display - const [itemsPerPage, setItemsPerPage] = useState<number>(getItemsPerPage()); - const itemsPerPageRef = useRef(itemsPerPage); - - // store the current page - const [page, setPage] = useState<number>(1); - const pageRef = useRef(page); - - const _itemsPerPage = itemsPerPageRef.current; - const _page = pageRef.current; - - // accumulate segments to include in tips - const segments: AnyJson = []; - if (!activeAccount) { - segments.push(1); - } else if (!isNominating() && !membership) { - if ( - transferOptions.freeBalance.gt(minNominatorBond) && - totalNominators.lt(maxNominatorsCount) - ) { - segments.push(2); - } else { - segments.push(3); - } - segments.push(4); - } else { - if (isNominating()) { - segments.push(5); - } - if (membership) { - if (!isOwner()) { - segments.push(6); - } else { - segments.push(7); - } - } - segments.push(8); - } - - // filter tips relevant to connected account. - let items = TIPS_CONFIG.filter((i: AnyJson) => segments.includes(i.s)); - - items = items.map((i: any) => { - const { id } = i; - - return fillVariables( - { - ...i, - title: t(`${id}.0`, { ns: 'tips' }), - subtitle: t(`${id}.1`, { ns: 'tips' }), - description: i18n.getResource(i18n.resolvedLanguage, 'tips', `${id}.2`), - }, - ['title', 'subtitle', 'description'] - ); - }); - - // determine items to be displayed - const endItem = networkSyncing - ? 1 - : Math.min(_page * _itemsPerPage, items.length); - const startItem = networkSyncing - ? 1 - : _page * _itemsPerPage - (_itemsPerPage - 1); - - const totalItems = networkSyncing ? 1 : items.length; - const itemsDisplay = items.slice(startItem - 1, endItem); - const totalPages = Math.ceil(totalItems / _itemsPerPage); - - return ( - <CardWrapper> - <CardHeaderWrapper withAction> - <h4> - {t('module.tips', { ns: 'tips' })} - <OpenHelpIcon helpKey="Dashboard Tips" /> - </h4> - <div> - <PageToggleWrapper> - <button - type="button" - disabled={totalPages === 1 || _page === 1} - onClick={() => { - setStateWithRef(_page - 1, setPage, pageRef); - }} - > - <FontAwesomeIcon - icon={faChevronCircleLeft} - className="icon" - transform="grow-1" - /> - </button> - <h4 className={totalPages === 1 ? `disabled` : undefined}> - <span> - {startItem} - {_itemsPerPage > 1 && - totalItems > 1 && - startItem !== endItem && - ` - ${endItem}`} - </span> - {totalPages > 1 && ( - <> - {t('module.of', { ns: 'tips' })} <span>{items.length}</span> - </> - )} - </h4> - <button - type="button" - disabled={totalPages === 1 || _page === totalPages} - onClick={() => { - setStateWithRef(_page + 1, setPage, pageRef); - }} - > - <FontAwesomeIcon - icon={faChevronCircleRight} - className="icon" - transform="grow-1" - /> - </button> - </PageToggleWrapper> - <PageToggleWrapper> - <button - type="button" - onClick={() => { - openModalWith('DismissTips', {}); - }} - > - <FontAwesomeIcon icon={faCog} /> - </button> - </PageToggleWrapper> - </div> - </CardHeaderWrapper> - {networkSyncing ? ( - <Syncing /> - ) : ( - <Items items={itemsDisplay} page={pageRef.current} /> - )} - </CardWrapper> - ); -}; diff --git a/src/pages/Overview/Wrappers.ts b/src/pages/Overview/Wrappers.ts index 929ea5df0a..4f9525237d 100644 --- a/src/pages/Overview/Wrappers.ts +++ b/src/pages/Overview/Wrappers.ts @@ -1,233 +1,104 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import { MediumFontSizeMaxWidth, SmallFontSizeMaxWidth } from 'consts'; import styled from 'styled-components'; -import { - borderPrimary, - borderSecondary, - buttonSecondaryBackground, - textSecondary, -} from 'theme'; +import { SectionFullWidthThreshold } from 'consts'; -export const ActiveAccounWrapper = styled.div` +export const Separator = styled.div` + border-bottom: 1px solid var(--border-primary-color); + margin-top: 0.8rem; width: 100%; - - .account { - display: flex; - flex-flow: row wrap; - align-items: center; - overflow: hidden; - width: 100%; - - .icon { - position: relative; - top: 0.1rem; - margin-right: 0.5rem; - } - .title { - margin: 0; - padding: 0; - flex: 1; - overflow: hidden; - } - .rest { - flex: 1 1 0%; - min-height: 1.8rem; - overflow: hidden; - position: relative; - - .name { - position: absolute; - left: 0; - bottom: 0.1rem; - max-width: 100%; - display: inline; - text-overflow: ellipsis; - white-space: nowrap; - overflow: hidden; - opacity: 0.75; - } - } - - button { - background: ${buttonSecondaryBackground}; - width: 2rem; - height: 2rem; - border-radius: 50%; - margin-left: 0.75rem; - padding: 0; - } - - h3 { - margin: 0; - display: flex; - flex-flow: row wrap; - align-items: center; - flex: 1; - - > .sep { - border-right: 1px solid ${borderSecondary}; - margin: 0 0.8rem; - width: 1px; - height: 1.25rem; - } - > .addr { - text-overflow: ellipsis; - white-space: nowrap; - overflow: hidden; - } - } - - > *:last-child { - flex-grow: 1; - display: flex; - flex-flow: row-reverse wrap; - - .copy { - color: ${textSecondary}; - opacity: 0.7; - cursor: pointer; - transition: opacity 0.1s; - &:hover { - opacity: 0.8; - } - } - } - } + height: 1px; `; -export const SectionWrapper = styled.div<{ noPadding?: boolean }>` - padding: ${(props) => (props.noPadding ? '0' : '0 1.25rem 0rem 1.25rem')}; +export const MoreWrapper = styled.div` + padding: 0 0.5rem; + padding-bottom: 1rem; width: 100%; display: flex; flex-flow: column wrap; + margin-top: 2.5rem; - .account { + @media (max-width: ${SectionFullWidthThreshold}px) { + margin-top: 1.5rem; + padding: 0 0.75rem; + margin-bottom: 0.5rem; + } + h4 { + margin-top: 0.25rem; + margin-bottom: 0.5rem; + } + section { display: flex; - flex-flow: row wrap; align-items: center; - - button { - color: ${textSecondary}; + width: 100%; + margin-top: 0.1rem; + div { margin-left: 0.5rem; - opacity: 0.7; } + } +`; - .icon { - position: relative; - top: 0.1rem; - } - .title { - margin: 0; - padding: 0 0.5rem; - flex: 1; - overflow: hidden; - } - h4 { - margin: 0; - display: flex; - flex-flow: row wrap; - align-items: center; - flex: 1; +export const BannerWrapper = styled.div` + &.light { + border: 1px solid var(--accent-color-primary); + background: var(--background-primary); - > .sep { - border-right: 1px solid ${borderSecondary}; - margin: 0 0.8rem; - width: 1px; - height: 1.25rem; - } - > .addr { - text-overflow: ellipsis; - white-space: nowrap; - overflow: hidden; - opacity: 0.75; - } + .label, + > div h3 { + color: var(--accent-color-primary); } + } + &.dark { + border: 1px solid var(--border-secondary-color); + background: var(--accent-color-secondary); - > *:last-child { - flex-grow: 1; - display: flex; - flex-flow: row-reverse wrap; - - > .copy { - color: ${textSecondary}; - opacity: 0.5; - cursor: pointer; - transition: opacity 0.1s; - &:hover { - opacity: 0.8; - } - } + .label, + > div h3 { + color: white; + } + div > button { + color: white; + border-color: white; } } -`; - -export const Separator = styled.div` - border-bottom: 1px solid ${borderPrimary}; - margin-top: 0.8rem; + box-shadow: var(--card-shadow-secondary); + border-radius: 1.25rem; + padding: 1.25rem 1.5rem; + margin-top: 5rem; width: 100%; - height: 1px; -`; -export const ReserveWrapper = styled.div` - width: 100%; - display: flex; - flex-flow: column wrap; - margin-top: 5.5rem; - @media (min-width: ${SmallFontSizeMaxWidth + 1}px) { - margin-top: 3rem; - } - @media (min-width: ${MediumFontSizeMaxWidth + 1}px) { - margin-top: 2.25rem; - } - > h4 { - margin-top: 0.75rem; - margin-bottom: 0.25rem; - @media (min-width: ${SmallFontSizeMaxWidth + 1}px) { - margin-top: 0.9rem; + .label { + color: white; + margin-bottom: 0.75rem; + + .icon { + margin-right: 0.35rem; } } - > .inner { - display: flex; - flex-flow: row wrap; - margin: 0; - - > section { - display: flex; - flex-flow: row wrap; - align-items: center; - position: relative; - - &:first-child { - overflow: hidden; - padding-left: 0; - - .reserve { - background: ${buttonSecondaryBackground}; - display: block; - text-overflow: ellipsis; - white-space: nowrap; - overflow: hidden; - position: relative; - border-radius: 1rem; - opacity: 0.75; - padding-top: 0.7rem; - padding-bottom: 0.7rem; - padding-left: 2.75rem; - padding-right: 1.5rem; - width: 100%; - .icon { - position: absolute; - top: 0.8rem; - left: 0.95rem; - } - } - } + > div { + display: flex; + align-items: center; - .help-icon { - margin-left: 0.6rem; + h3 { + font-family: InterSemiBold, sans-serif; + line-height: 1.8rem; + } + button { + flex-basis: auto; + font-size: 1.1rem; + margin-left: 0.75rem; + } + @media (max-width: 800px) { + flex-direction: column; + align-items: flex-start; + + button { + margin-top: 0.75rem; + margin-left: 0; + position: relative; + left: -0.5rem; } } } diff --git a/src/pages/Overview/index.tsx b/src/pages/Overview/index.tsx index 92fa8a2784..3a021e701d 100644 --- a/src/pages/Overview/index.tsx +++ b/src/pages/Overview/index.tsx @@ -1,52 +1,56 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import BN from 'bn.js'; -import { SectionFullWidthThreshold, SideMenuStickyThreshold } from 'consts'; -import { useApi } from 'contexts/Api'; -import { useCereStats } from 'contexts/CereStats'; -import { useUi } from 'contexts/UI'; +import { + Odometer, + PageHeading, + PageRow, + PageTitle, + RowSection, +} from '@polkadot-cloud/react'; +import BigNumber from 'bignumber.js'; import { formatDistance, fromUnixTime, getUnixTime } from 'date-fns'; +import { useTranslation } from 'react-i18next'; +import { DefaultLocale } from 'consts'; +import { useCereStats } from 'contexts/CereStats'; +import { CardHeaderWrapper, CardWrapper } from 'library/Card/Wrappers'; import { formatRewardsForGraphs } from 'library/Graphs/Utils'; -import { GraphWrapper } from 'library/Graphs/Wrappers'; -import { PageTitle } from 'library/PageTitle'; import { StatBoxList } from 'library/StatBoxList'; -import { SubscanButton } from 'library/SubscanButton'; import { locales } from 'locale'; -import { useTranslation } from 'react-i18next'; -import { humanNumber, planckBnToUnit } from 'Utils'; -import { - PageRowWrapper, - RowPrimaryWrapper, - RowSecondaryWrapper, - TopBarWrapper, -} from 'Wrappers'; -import { ActiveAccount } from './ActiveAccount'; -import BalanceGraph from './BalanceGraph'; +import { ControllerNotStash } from 'pages/Nominate/Active/ControllerNotStash'; +import { minDecimalPlaces, planckToUnit } from '@polkadot-cloud/utils'; +import { PluginLabel } from 'library/PluginLabel'; +import { useNetwork } from 'contexts/Network'; +import { ActiveAccounts } from './ActiveAccounts'; +import { BalanceChart } from './BalanceChart'; +import { BalanceLinks } from './BalanceLinks'; import { NetworkStats } from './NetworkSats'; -import Payouts from './Payouts'; -import Reserve from './Reserve'; -import ActiveEraStatBox from './Stats/ActiveEra'; -import { ActiveNominatorsStatBox } from './Stats/ActiveNominators'; -import TotalNominatorsStatBox from './Stats/TotalNominations'; -import { Tips } from './Tips'; +import { Payouts } from './Payouts'; +import { StakeStatus } from './StakeStatus'; +import { ActiveEraStat } from './Stats/ActiveEraTimeLeft'; +import { HistoricalRewardsRateStat } from './Stats/HistoricalRewardsRate'; +import { SupplyStakedStat } from './Stats/SupplyStaked'; export const Overview = () => { - const { network } = useApi(); - const { units } = network; - const { payouts, poolClaims } = useCereStats(); - const { services } = useUi(); + const { i18n, t } = useTranslation('pages'); + const { + networkData: { + units, + brand: { token: Token }, + }, + } = useNetwork(); + const { payouts, poolClaims, unclaimedPayouts } = useCereStats(); + const { lastReward } = formatRewardsForGraphs( + new Date(), 14, - 1, units, payouts, - poolClaims + poolClaims, + unclaimedPayouts ); - const { i18n, t } = useTranslation('pages'); - const PAYOUTS_HEIGHT = 410; - const BALANCE_HEIGHT = PAYOUTS_HEIGHT; + const PAYOUTS_HEIGHT = 380; let formatFrom = new Date(); let formatTo = new Date(); @@ -58,74 +62,71 @@ export const Overview = () => { formatTo = new Date(); formatOpts = { addSuffix: true, - locale: locales[i18n.resolvedLanguage], + locale: locales[i18n.resolvedLanguage ?? DefaultLocale], }; } return ( <> <PageTitle title={t('overview.overview')} /> - <PageRowWrapper className="page-padding" noVerticalSpacer> - <TopBarWrapper> - <ActiveAccount /> - </TopBarWrapper> - </PageRowWrapper> + <PageRow> + <PageHeading> + <ActiveAccounts /> + </PageHeading> + </PageRow> <StatBoxList> - <TotalNominatorsStatBox /> - <ActiveNominatorsStatBox /> - <ActiveEraStatBox /> + <HistoricalRewardsRateStat /> + <SupplyStakedStat /> + <ActiveEraStat /> </StatBoxList> - {services.includes('tips') && ( - <PageRowWrapper className="page-padding" noVerticalSpacer> - <Tips /> - </PageRowWrapper> - )} - <PageRowWrapper className="page-padding" noVerticalSpacer> - <RowSecondaryWrapper - hOrder={0} - vOrder={0} - thresholdStickyMenu={SideMenuStickyThreshold} - thresholdFullWidth={SectionFullWidthThreshold} - > - <GraphWrapper style={{ minHeight: BALANCE_HEIGHT }} flex> - <BalanceGraph /> - <Reserve /> - </GraphWrapper> - </RowSecondaryWrapper> - <RowPrimaryWrapper - hOrder={1} - vOrder={1} - thresholdStickyMenu={SideMenuStickyThreshold} - thresholdFullWidth={SectionFullWidthThreshold} - > - <GraphWrapper style={{ minHeight: PAYOUTS_HEIGHT }} flex> - <SubscanButton /> - <div className="head"> - <h4>{t('overview.recent_payouts')}</h4> + <ControllerNotStash /> + <PageRow> + <StakeStatus /> + </PageRow> + <PageRow> + <RowSection secondary> + <CardWrapper height={PAYOUTS_HEIGHT}> + <BalanceChart /> + <BalanceLinks /> + </CardWrapper> + </RowSection> + <RowSection hLast vLast> + <CardWrapper style={{ minHeight: PAYOUTS_HEIGHT }}> + <PluginLabel plugin="subscan" /> + <CardHeaderWrapper> + <h4>{t('overview.recentPayouts')}</h4> <h2> - {lastReward === null - ? 0 - : humanNumber( - planckBnToUnit(new BN(lastReward.amount), units) - )} -  {network.unit} -   - <span className="fiat"> - {lastReward === null - ? '' - : formatDistance(formatFrom, formatTo, formatOpts)} + <Token className="networkIcon" /> + <Odometer + value={minDecimalPlaces( + lastReward === null + ? '0' + : planckToUnit( + new BigNumber(lastReward.amount), + units + ).toFormat(), + 2 + )} + /> + + <span className="note"> + {lastReward === null ? ( + '' + ) : ( + <> +  {formatDistance(formatFrom, formatTo, formatOpts)} + </> + )} </span> </h2> - </div> + </CardHeaderWrapper> <Payouts /> - </GraphWrapper> - </RowPrimaryWrapper> - </PageRowWrapper> - <PageRowWrapper className="page-padding" noVerticalSpacer> + </CardWrapper> + </RowSection> + </PageRow> + <PageRow> <NetworkStats /> - </PageRowWrapper> + </PageRow> </> ); }; - -export default Overview; diff --git a/src/pages/Payouts/PayoutList/context.tsx b/src/pages/Payouts/PayoutList/context.tsx index bab8456503..d985cbca21 100644 --- a/src/pages/Payouts/PayoutList/context.tsx +++ b/src/pages/Payouts/PayoutList/context.tsx @@ -1,12 +1,11 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { PayoutListContextInterface } from 'pages/Pools/types'; +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only import React, { useState } from 'react'; +import type { PayoutListContextInterface } from 'pages/Pools/types'; export const PayoutListContext = React.createContext<PayoutListContextInterface>({ - // eslint-disable-next-line + // eslint-disable-next-line @typescript-eslint/no-unused-vars setListFormat: (v: string) => {}, listFormat: 'col', }); diff --git a/src/pages/Payouts/PayoutList/index.tsx b/src/pages/Payouts/PayoutList/index.tsx index c84b10d71b..9d9143f051 100644 --- a/src/pages/Payouts/PayoutList/index.tsx +++ b/src/pages/Payouts/PayoutList/index.tsx @@ -1,47 +1,50 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only import { faBars, faGripVertical } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { ListItemsPerBatch, ListItemsPerPage } from 'consts'; +import { ellipsisFn, isNotZero, planckToUnit } from '@polkadot-cloud/utils'; +import BigNumber from 'bignumber.js'; +import { formatDistance, fromUnixTime } from 'date-fns'; +import { motion } from 'framer-motion'; +import React, { useEffect, useRef, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { DefaultLocale, ListItemsPerBatch, ListItemsPerPage } from 'consts'; import { useApi } from 'contexts/Api'; -import { useNetworkMetrics } from 'contexts/Network'; +import { useNetworkMetrics } from 'contexts/NetworkMetrics'; import { useBondedPools } from 'contexts/Pools/BondedPools'; -import { BondedPool } from 'contexts/Pools/types'; import { StakingContext } from 'contexts/Staking'; import { useTheme } from 'contexts/Themes'; -import { useValidators } from 'contexts/Validators'; -import { Validator } from 'contexts/Validators/types'; -import { formatDistance, fromUnixTime } from 'date-fns'; -import { motion } from 'framer-motion'; +import { useValidators } from 'contexts/Validators/ValidatorEntries'; import { Header, List, Wrapper as ListWrapper } from 'library/List'; import { MotionContainer } from 'library/List/MotionContainer'; import { Pagination } from 'library/List/Pagination'; import { Identity } from 'library/ListItem/Labels/Identity'; import { PoolIdentity } from 'library/ListItem/Labels/PoolIdentity'; import { locales } from 'locale'; -import React, { useEffect, useRef, useState } from 'react'; -import { useTranslation } from 'react-i18next'; -import { networkColors } from 'theme/default'; -import { AnySubscan } from 'types'; -import { clipAddress, planckToUnit } from 'Utils'; -import { PayoutListProps } from '../types'; +import type { AnySubscan } from 'types'; +import { useNetwork } from 'contexts/Network'; import { ItemWrapper } from '../Wrappers'; +import type { PayoutListProps } from '../types'; import { PayoutListProvider, usePayoutList } from './context'; -export const PayoutListInner = (props: PayoutListProps) => { - const { allowMoreCols, pagination } = props; - +export const PayoutListInner = ({ + allowMoreCols, + pagination, + title, + payouts: initialPayouts, + disableThrottle = false, +}: PayoutListProps) => { + const { i18n, t } = useTranslation('pages'); const { mode } = useTheme(); - const { isReady, network } = useApi(); - const { units } = network; - const { metrics } = useNetworkMetrics(); + const { isReady } = useApi(); + const { + networkData: { units, unit, colors }, + } = useNetwork(); + const { activeEra } = useNetworkMetrics(); const { listFormat, setListFormat } = usePayoutList(); - const { validators, meta } = useValidators(); + const { validators } = useValidators(); const { bondedPools } = useBondedPools(); - const { i18n, t } = useTranslation('pages'); - - const disableThrottle = props.disableThrottle ?? false; // current page const [page, setPage] = useState<number>(1); @@ -50,7 +53,7 @@ export const PayoutListInner = (props: PayoutListProps) => { const [renderIteration, _setRenderIteration] = useState<number>(1); // manipulated list (ordering, filtering) of payouts - const [payouts, setPayouts] = useState(props.payouts); + const [payouts, setPayouts] = useState(initialPayouts); // is this the initial fetch const [fetched, setFetched] = useState<boolean>(false); @@ -68,20 +71,23 @@ export const PayoutListInner = (props: PayoutListProps) => { const pageStart = pageEnd - (ListItemsPerPage - 1); // render batch - const batchEnd = renderIteration * ListItemsPerBatch - 1; + const batchEnd = Math.min( + renderIteration * ListItemsPerBatch - 1, + ListItemsPerPage + ); // refetch list when list changes useEffect(() => { setFetched(false); - }, [props.payouts]); + }, [initialPayouts]); // configure list when network is ready to fetch useEffect(() => { - if (isReady && metrics.activeEra.index !== 0 && !fetched) { - setPayouts(props.payouts); + if (isReady && isNotZero(activeEra.index) && !fetched) { + setPayouts(initialPayouts); setFetched(true); } - }, [isReady, fetched, metrics.activeEra.index]); + }, [isReady, fetched, activeEra.index]); // render throttle useEffect(() => { @@ -106,39 +112,28 @@ export const PayoutListInner = (props: PayoutListProps) => { return <></>; } - // get validator metadata - const batchKey = 'validators_browse'; - return ( <ListWrapper> <Header> <div> - <h4>{props.title}</h4> + <h4>{title}</h4> </div> <div> <button type="button" onClick={() => setListFormat('row')}> <FontAwesomeIcon icon={faBars} - color={ - listFormat === 'row' - ? networkColors[`${network.name}-${mode}`] - : 'inherit' - } + color={listFormat === 'row' ? colors.primary[mode] : 'inherit'} /> </button> <button type="button" onClick={() => setListFormat('col')}> <FontAwesomeIcon icon={faGripVertical} - color={ - listFormat === 'col' - ? networkColors[`${network.name}-${mode}`] - : 'inherit' - } + color={listFormat === 'col' ? colors.primary[mode] : 'inherit'} /> </button> </div> </Header> - <List flexBasisLarge={allowMoreCols ? '33.33%' : '50%'}> + <List $flexBasisLarge={allowMoreCols ? '33.33%' : '50%'}> {pagination && ( <Pagination page={page} total={totalPages} setter={setPage} /> )} @@ -146,7 +141,7 @@ export const PayoutListInner = (props: PayoutListProps) => { {listPayouts.map((p: AnySubscan, index: number) => { const label = p.event_id === 'PaidOut' - ? t('payouts.pool_claim') + ? t('payouts.poolClaim') : p.event_id === 'Rewarded' ? t('payouts.payout') : p.event_id; @@ -160,12 +155,12 @@ export const PayoutListInner = (props: PayoutListProps) => { // get validator if it exists const validator = validators.find( - (v: Validator) => v.address === p.validator_stash + (v) => v.address === p.validator_stash ); // get pool if it exists const pool = bondedPools.find( - (_p: BondedPool) => String(_p.id) === String(p.pool_id) + ({ id }) => String(id) === String(p.pool_id) ); const batchIndex = validator @@ -194,13 +189,19 @@ export const PayoutListInner = (props: PayoutListProps) => { <div className="row"> <div> <div> - <h4 className={`${labelClass}`}> - {p.event_id === 'Slashed' ? '-' : '+'} - {planckToUnit(p.amount, units)} {network.unit} + <h4 className={labelClass}> + <> + {p.event_id === 'Slashed' ? '-' : '+'} + {planckToUnit( + new BigNumber(p.amount), + units + ).toString()}{' '} + {unit} + </> </h4> </div> <div> - <h5 className={`${labelClass}`}>{label}</h5> + <h5 className={labelClass}>{label}</h5> </div> </div> </div> @@ -210,34 +211,29 @@ export const PayoutListInner = (props: PayoutListProps) => { {label === t('payouts.payout') && ( <> {batchIndex > 0 ? ( - <Identity - meta={meta} - address={p.validator_stash} - batchIndex={batchIndex} - batchKey={batchKey} - /> + <Identity address={p.validator_stash} /> ) : ( - <div>{clipAddress(p.validator_stash)}</div> + <div>{ellipsisFn(p.validator_stash)}</div> )} </> )} - {label === t('payouts.pool_claim') && ( + {label === t('payouts.poolClaim') && ( <> {pool ? ( <PoolIdentity - batchKey={batchKey} + batchKey="bonded_pools" batchIndex={batchIndex} pool={pool} /> ) : ( <h4> - {t('payouts.from_pool')} {p.pool_id} + {t('payouts.fromPool')} {p.pool_id} </h4> )} </> )} {label === t('payouts.slashed') && ( - <h4>{t('payouts.deducted_from_bond')}</h4> + <h4>{t('payouts.deductedFromBond')}</h4> )} </div> <div> @@ -247,7 +243,10 @@ export const PayoutListInner = (props: PayoutListProps) => { new Date(), { addSuffix: true, - locale: locales[i18n.resolvedLanguage], + locale: + locales[ + i18n.resolvedLanguage ?? DefaultLocale + ], } )} </h5> @@ -265,13 +264,11 @@ export const PayoutListInner = (props: PayoutListProps) => { ); }; -export const PayoutList = (props: PayoutListProps) => { - return ( - <PayoutListProvider> - <PayoutListShouldUpdate {...props} /> - </PayoutListProvider> - ); -}; +export const PayoutList = (props: PayoutListProps) => ( + <PayoutListProvider> + <PayoutListShouldUpdate {...props} /> + </PayoutListProvider> +); export class PayoutListShouldUpdate extends React.Component { static contextType = StakingContext; @@ -280,5 +277,3 @@ export class PayoutListShouldUpdate extends React.Component { return <PayoutListInner {...this.props} />; } } - -export default PayoutList; diff --git a/src/pages/Payouts/Stats/LastEraPayout.tsx b/src/pages/Payouts/Stats/LastEraPayout.tsx index 478cb0b8b5..de4d13b7c8 100644 --- a/src/pages/Payouts/Stats/LastEraPayout.tsx +++ b/src/pages/Payouts/Stats/LastEraPayout.tsx @@ -1,28 +1,26 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import { useApi } from 'contexts/Api'; +import { planckToUnit } from '@polkadot-cloud/utils'; +import { useTranslation } from 'react-i18next'; import { useStaking } from 'contexts/Staking'; import { Number } from 'library/StatBoxList/Number'; -import { useTranslation } from 'react-i18next'; -import { planckBnToUnit } from 'Utils'; +import { useNetwork } from 'contexts/Network'; -export const LastEraPayoutStatBox = () => { - const { network } = useApi(); +export const LastEraPayoutStat = () => { + const { t } = useTranslation('pages'); + const { unit, units } = useNetwork().networkData; const { staking } = useStaking(); - const { unit, units } = network; const { lastReward } = staking; - const { t } = useTranslation('pages'); - const lastRewardBase = planckBnToUnit(lastReward, units).toFixed(0); + const lastRewardUnit = planckToUnit(lastReward, units).toNumber(); const params = { - label: t('payouts.last_era_payout'), - value: lastRewardBase, + label: t('payouts.lastEraPayout'), + value: lastRewardUnit, + decimals: 3, unit, helpKey: 'Last Era Payout', }; return <Number {...params} />; }; - -export default LastEraPayoutStatBox; diff --git a/src/pages/Payouts/Wrappers.ts b/src/pages/Payouts/Wrappers.ts index 51f7d1727c..85981a9a27 100644 --- a/src/pages/Payouts/Wrappers.ts +++ b/src/pages/Payouts/Wrappers.ts @@ -1,30 +1,20 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only import { motion } from 'framer-motion'; import styled from 'styled-components'; -import { - backgroundDropdown, - borderPrimary, - networkColor, - networkColorSecondary, - shadowColorSecondary, - textSecondary, -} from 'theme'; export const ItemWrapper = styled(motion.div)` padding: 0.5rem; width: 100%; > .inner { + background: var(--background-list-item); padding: 0 0.75rem; flex: 1; - background: ${backgroundDropdown}; - box-shadow: 0px 1.75px 0px 1.25px ${shadowColorSecondary}; border-radius: 1rem; display: flex; flex-flow: column wrap; - justify-content: flex-start; align-items: center; flex: 1; max-width: 100%; @@ -40,8 +30,8 @@ export const ItemWrapper = styled(motion.div)` } &:last-child { + border-top: 1px solid var(--border-primary-color); padding-top: 0rem; - border-top: 1px solid ${borderPrimary}; > div { min-height: 3.2rem; @@ -51,35 +41,32 @@ export const ItemWrapper = styled(motion.div)` > div { display: flex; flex-flow: row wrap; - justify-content: flex-start; align-items: center; flex: 1; max-width: 100%; h4 { - margin: 0; - color: ${textSecondary}; - font-variation-settings: 'wght' 575; + color: var(--text-color-secondary); + font-family: InterSemiBold, sans-serif; &.claim { - color: ${networkColorSecondary}; + color: var(--accent-color-secondary); } &.reward { - color: ${networkColor}; + color: var(--accent-color-primary); } } h5 { - margin: 0; - color: ${textSecondary}; + color: var(--text-color-secondary); &.claim { - color: ${networkColorSecondary}; - border: 1px solid ${networkColorSecondary}; + color: var(--accent-color-secondary); + border: 1px solid var(--accent-color-secondary); border-radius: 0.75rem; padding: 0.2rem 0.5rem; } &.reward { - color: ${networkColor}; - border: 1px solid ${networkColor}; + color: var(--accent-color-primary); + border: 1px solid var(--accent-color-primary); border-radius: 0.75rem; padding: 0.2rem 0.5rem; } @@ -89,7 +76,6 @@ export const ItemWrapper = styled(motion.div)` flex-grow: 1; display: flex; flex-flow: row wrap; - justify-content: flex-start; align-items: center; } @@ -99,7 +85,7 @@ export const ItemWrapper = styled(motion.div)` justify-content: flex-end; > h4 { - color: ${textSecondary}; + color: var(--text-color-secondary); opacity: 0.8; } } diff --git a/src/pages/Payouts/index.tsx b/src/pages/Payouts/index.tsx index c01693c230..1f39c82bb4 100644 --- a/src/pages/Payouts/index.tsx +++ b/src/pages/Payouts/index.tsx @@ -1,111 +1,74 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import { BN } from 'bn.js'; +import { ButtonHelp, PageRow, PageTitle } from '@polkadot-cloud/react'; +import { useEffect, useRef, useState } from 'react'; +import { useTranslation } from 'react-i18next'; import { MaxPayoutDays } from 'consts'; +import { useHelp } from 'contexts/Help'; +import { usePlugins } from 'contexts/Plugins'; import { useStaking } from 'contexts/Staking'; +import { useSubscan } from 'contexts/Plugins/Subscan'; import { useUi } from 'contexts/UI'; -import { format, fromUnixTime } from 'date-fns'; +import { CardHeaderWrapper, CardWrapper } from 'library/Card/Wrappers'; import { PayoutBar } from 'library/Graphs/PayoutBar'; import { PayoutLine } from 'library/Graphs/PayoutLine'; -import { formatSize, useSize } from 'library/Graphs/Utils'; -import { - CardHeaderWrapper, - CardWrapper, - GraphWrapper, -} from 'library/Graphs/Wrappers'; -import { OpenHelpIcon } from 'library/OpenHelpIcon'; -import { PageTitle } from 'library/PageTitle'; +import { formatSize, sortNonZeroPayouts } from 'library/Graphs/Utils'; +import { GraphWrapper } from 'library/Graphs/Wrapper'; +import { useSize } from 'library/Hooks/useSize'; import { StatBoxList } from 'library/StatBoxList'; import { StatusLabel } from 'library/StatusLabel'; -import { SubscanButton } from 'library/SubscanButton'; -import { locales } from 'locale'; -import { useEffect, useRef, useState } from 'react'; -import { useTranslation } from 'react-i18next'; -import { AnySubscan } from 'types'; -import { PageRowWrapper } from 'Wrappers'; -import { useCereStats } from '../../contexts/CereStats'; -import { PageProps } from '../types'; +import type { AnySubscan, PageProps } from 'types'; +import { PluginLabel } from 'library/PluginLabel'; import { PayoutList } from './PayoutList'; -import LastEraPayoutStatBox from './Stats/LastEraPayout'; +import { LastEraPayoutStat } from './Stats/LastEraPayout'; -export const Payouts = (props: PageProps) => { - const { payouts, poolClaims } = useCereStats(); - const { isSyncing, services } = useUi(); +export const Payouts = ({ page }: PageProps) => { + const { t } = useTranslation(); + const { payouts, poolClaims, payoutsFromDate, payoutsToDate } = useSubscan(); + const { isSyncing } = useUi(); + const { plugins } = usePlugins(); const { inSetup } = useStaking(); const notStaking = !isSyncing && inSetup(); - const { i18n, t } = useTranslation(); + const { openHelp } = useHelp(); - const [payoutsList, setPayoutLists] = useState<AnySubscan>(); - const [fromDate, setFromDate] = useState<string | undefined>(); - const [toDate, setToDate] = useState<string | undefined>(); + const [payoutsList, setPayoutLists] = useState<AnySubscan>([]); - const { page } = props; const { key } = page; const ref = useRef<HTMLDivElement>(null); const size = useSize(ref.current); - const { width, height, minHeight } = formatSize(size, 300); - - useEffect(() => { - // take non-zero rewards in most-recent order - let pList: AnySubscan = [ - ...payouts.concat(poolClaims).filter((p: AnySubscan) => p.amount > 0), - ].slice(0, MaxPayoutDays); - - // re-order rewards based on block timestamp - pList = pList.sort((a: AnySubscan, b: AnySubscan) => { - const x = new BN(a.block_timestamp); - const y = new BN(b.block_timestamp); - return y.sub(x); - }); - setPayoutLists(pList); - }, [payouts]); + const { width, height, minHeight } = formatSize(size, 280); useEffect(() => { - // calculate the earliest and latest payout dates if they exist. - if (payoutsList?.length) { - setFromDate( - format( - fromUnixTime( - payoutsList[Math.min(MaxPayoutDays - 2, payoutsList.length - 1)] - .block_timestamp - ), - 'do MMM', - { - locale: locales[i18n.resolvedLanguage], - } - ) - ); - - // latest payout date - setToDate( - format(fromUnixTime(payoutsList[0].block_timestamp), 'do MMM', { - locale: locales[i18n.resolvedLanguage], - }) - ); - } - }, [payoutsList?.length]); + // filter zero rewards and order via block timestamp, most recent first. + setPayoutLists(sortNonZeroPayouts(payouts, poolClaims, true)); + }, [payouts, poolClaims]); return ( <> <PageTitle title={t(key, { ns: 'base' })} /> <StatBoxList> - <LastEraPayoutStatBox /> + <LastEraPayoutStat /> </StatBoxList> - <PageRowWrapper className="page-padding" noVerticalSpacer> - <GraphWrapper> - <SubscanButton /> - <CardHeaderWrapper padded> + <PageRow> + <CardWrapper> + <PluginLabel plugin="cereStats" /> + <CardHeaderWrapper> <h4> - {t('payouts.payout_history', { ns: 'pages' })} - <OpenHelpIcon helpKey="Payout History" /> + {t('payouts.payoutHistory', { ns: 'pages' })} + <ButtonHelp + marginLeft + onClick={() => openHelp('Payout History')} + /> </h4> <h2> - {payouts.length ? ( + {payoutsFromDate && payoutsToDate ? ( <> - {fromDate} - {toDate !== fromDate && <> - {toDate}</>} + {payoutsFromDate} + {payoutsToDate !== payoutsFromDate && ( + <> - {payoutsToDate}</> + )} </> ) : ( t('payouts.none', { ns: 'pages' }) @@ -113,23 +76,22 @@ export const Payouts = (props: PageProps) => { </h2> </CardHeaderWrapper> <div className="inner" ref={ref} style={{ minHeight }}> - {!services.includes('cereStats') ? ( + {!plugins.includes('cereStats') ? ( <StatusLabel status="active_service" - statusFor="cereStats" - title="Cere Stats Disabled" + statusFor="subscan" + title={t('payouts.subscanDisabled', { ns: 'pages' })} topOffset="30%" /> ) : ( <StatusLabel status="sync_or_setup" - title={t('payouts.not_staking', { ns: 'pages' })} + title={t('payouts.notStaking', { ns: 'pages' })} topOffset="30%" /> )} - <div - className="graph" + <GraphWrapper style={{ height: `${height}px`, width: `${width}px`, @@ -138,27 +100,25 @@ export const Payouts = (props: PageProps) => { transition: 'opacity 0.5s', }} > - <PayoutBar days={MaxPayoutDays} height="150px" /> - <PayoutLine days={MaxPayoutDays} average={10} height="75px" /> - </div> + <PayoutBar days={MaxPayoutDays} height="165px" /> + <PayoutLine days={MaxPayoutDays} average={10} height="65px" /> + </GraphWrapper> </div> - </GraphWrapper> - </PageRowWrapper> + </CardWrapper> + </PageRow> {!payoutsList?.length ? ( <></> ) : ( - <PageRowWrapper className="page-padding" noVerticalSpacer> + <PageRow> <CardWrapper> <PayoutList - title={t('payouts.recent_payouts', { ns: 'pages' })} + title={t('payouts.recentPayouts', { ns: 'pages' })} payouts={payoutsList} pagination /> </CardWrapper> - </PageRowWrapper> + </PageRow> )} </> ); }; - -export default Payouts; diff --git a/src/pages/Payouts/types.ts b/src/pages/Payouts/types.ts index 51074af0e3..f9c08440d0 100644 --- a/src/pages/Payouts/types.ts +++ b/src/pages/Payouts/types.ts @@ -1,7 +1,7 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import { AnySubscan } from 'types'; +import type { AnySubscan } from 'types'; export interface PayoutListProps { allowMoreCols?: boolean; diff --git a/src/pages/Pools/Create/Bond/index.tsx b/src/pages/Pools/Create/Bond/index.tsx index e958abc4a4..d912d4d0e6 100644 --- a/src/pages/Pools/Create/Bond/index.tsx +++ b/src/pages/Pools/Create/Bond/index.tsx @@ -1,33 +1,32 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import { BN } from 'bn.js'; -import { useConnect } from 'contexts/Connect'; -import { useTxFees } from 'contexts/TxFees'; -import { useUi } from 'contexts/UI'; -import { SetupType } from 'contexts/UI/types'; +import BigNumber from 'bignumber.js'; +import { useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useSetup } from 'contexts/Setup'; +import { useTxMeta } from 'contexts/TxMeta'; import { BondFeedback } from 'library/Form/Bond/BondFeedback'; import { CreatePoolStatusBar } from 'library/Form/CreatePoolStatusBar'; import { Footer } from 'library/SetupSteps/Footer'; import { Header } from 'library/SetupSteps/Header'; import { MotionContainer } from 'library/SetupSteps/MotionContainer'; -import { SetupStepProps } from 'library/SetupSteps/types'; -import { useEffect, useState } from 'react'; -import { useTranslation } from 'react-i18next'; +import type { SetupStepProps } from 'library/SetupSteps/types'; +import { useActiveAccounts } from 'contexts/ActiveAccounts'; -export const Bond = (props: SetupStepProps) => { - const { section } = props; - const { activeAccount } = useConnect(); - const { txFees } = useTxFees(); - const { getSetupProgress, setActiveAccountSetup } = useUi(); - const setup = getSetupProgress(SetupType.Pool, activeAccount); +export const Bond = ({ section }: SetupStepProps) => { const { t } = useTranslation('pages'); + const { activeAccount } = useActiveAccounts(); + const { txFees } = useTxMeta(); + const { getSetupProgress, setActiveAccountSetup } = useSetup(); + const setup = getSetupProgress('pool', activeAccount); + const { progress } = setup; // either free to bond or existing setup value - const initialBondValue = setup.bond === 0 ? '' : setup.bond; + const initialBondValue = progress.bond === '0' ? '' : progress.bond; // store local bond amount for form control - const [bond, setBond] = useState({ + const [bond, setBond] = useState<{ bond: string }>({ bond: initialBondValue, }); @@ -36,7 +35,7 @@ export const Bond = (props: SetupStepProps) => { // handler for updating bond const handleSetupUpdate = (value: any) => { - setActiveAccountSetup(SetupType.Pool, value); + setActiveAccountSetup('pool', value); }; // update bond on account change @@ -50,8 +49,8 @@ export const Bond = (props: SetupStepProps) => { useEffect(() => { // only update if Bond is currently active if (setup.section === section) { - setActiveAccountSetup(SetupType.Pool, { - ...setup, + setActiveAccountSetup('pool', { + ...progress, bond: initialBondValue, }); } @@ -61,22 +60,22 @@ export const Bond = (props: SetupStepProps) => { <> <Header thisSection={section} - complete={setup.bond !== 0} - title={t('pools.bond') || ''} + complete={progress.bond !== '0' && progress.bond !== ''} + title={t('pools.bond')} helpKey="Bonding" - setupType={SetupType.Pool} + bondFor="pool" /> <MotionContainer thisSection={section} activeSection={setup.section}> <BondFeedback - syncing={txFees.eq(new BN(0))} - bondType="pool" + syncing={txFees.isZero()} + bondFor="pool" inSetup - listenIsValid={setBondValid} + listenIsValid={(valid) => setBondValid(valid)} defaultBond={initialBondValue} setters={[ { set: handleSetupUpdate, - current: setup, + current: progress, }, { set: setBond, @@ -86,8 +85,8 @@ export const Bond = (props: SetupStepProps) => { txFees={txFees} maxWidth /> - <CreatePoolStatusBar value={bond.bond} /> - <Footer complete={bondValid} setupType={SetupType.Pool} /> + <CreatePoolStatusBar value={new BigNumber(bond.bond)} /> + <Footer complete={bondValid} bondFor="pool" /> </MotionContainer> </> ); diff --git a/src/pages/Pools/Create/PoolName/Input.tsx b/src/pages/Pools/Create/PoolName/Input.tsx index 64b1ca764b..4ea4150e11 100644 --- a/src/pages/Pools/Create/PoolName/Input.tsx +++ b/src/pages/Pools/Create/PoolName/Input.tsx @@ -1,31 +1,33 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import { useConnect } from 'contexts/Connect'; import React, { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; +import { useActiveAccounts } from 'contexts/ActiveAccounts'; -export const Input = (props: any) => { - const { listenIsValid, defaultValue } = props; - const setters = props.setters ?? []; - const _value = props.value ?? 0; - const { activeAccount } = useConnect(); +export const Input = ({ + listenIsValid, + defaultValue, + setters = [], + value = 0, +}: any) => { const { t } = useTranslation('pages'); + const { activeAccount } = useActiveAccounts(); // the current local bond value - const [metadata, setMetadata] = useState(_value); + const [metadata, setMetadata] = useState(value); // handle change for bonding const handleChange = (e: any) => { - const { value } = e.target; - listenIsValid(value !== ''); - setMetadata(value); + const val = e.target.value; + listenIsValid(val !== ''); + setMetadata(val); // apply value to parent setters for (const s of setters) { s.set({ ...s.current, - metadata: value, + metadata: val, }); } }; @@ -40,14 +42,14 @@ export const Input = (props: any) => { <div style={{ margin: '1rem 0' }}> <input className="textbox" - style={{ width: '100%' }} - placeholder="Pool Name" + style={{ width: '100%', fontFamily: 'InterSemiBold, sans-serif' }} + placeholder={t('pools.poolName')} type="text" onChange={(e: React.FormEvent<HTMLInputElement>) => handleChange(e)} value={metadata ?? ''} /> </div> - <p>{t('pools.pool_name_support')}</p> + <p>{t('pools.poolNameSupport')}</p> </> ); }; diff --git a/src/pages/Pools/Create/PoolName/index.tsx b/src/pages/Pools/Create/PoolName/index.tsx index 6f08e2ded3..6d29c7fc32 100644 --- a/src/pages/Pools/Create/PoolName/index.tsx +++ b/src/pages/Pools/Create/PoolName/index.tsx @@ -1,25 +1,24 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import { useConnect } from 'contexts/Connect'; -import { useUi } from 'contexts/UI'; -import { SetupType } from 'contexts/UI/types'; +import { useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useSetup } from 'contexts/Setup'; import { Footer } from 'library/SetupSteps/Footer'; import { Header } from 'library/SetupSteps/Header'; import { MotionContainer } from 'library/SetupSteps/MotionContainer'; -import { SetupStepProps } from 'library/SetupSteps/types'; -import { useEffect, useState } from 'react'; -import { useTranslation } from 'react-i18next'; +import type { SetupStepProps } from 'library/SetupSteps/types'; +import { useActiveAccounts } from 'contexts/ActiveAccounts'; import { Input } from './Input'; -export const PoolName = (props: SetupStepProps) => { - const { section } = props; - const { activeAccount } = useConnect(); - const { getSetupProgress, setActiveAccountSetup } = useUi(); - const setup = getSetupProgress(SetupType.Pool, activeAccount); +export const PoolName = ({ section }: SetupStepProps) => { const { t } = useTranslation('pages'); + const { activeAccount } = useActiveAccounts(); + const { getSetupProgress, setActiveAccountSetup } = useSetup(); + const setup = getSetupProgress('pool', activeAccount); + const { progress } = setup; - const initialValue = setup.metadata; + const initialValue = progress.metadata; // store local pool name for form control const [metadata, setMetadata] = useState({ @@ -31,7 +30,7 @@ export const PoolName = (props: SetupStepProps) => { // handler for updating bond const handleSetupUpdate = (value: any) => { - setActiveAccountSetup(SetupType.Pool, value); + setActiveAccountSetup('pool', value); }; // update bond on account change @@ -45,8 +44,8 @@ export const PoolName = (props: SetupStepProps) => { useEffect(() => { // only update if this section is currently active if (setup.section === section) { - setActiveAccountSetup(SetupType.Pool, { - ...setup, + setActiveAccountSetup('pool', { + ...progress, metadata: initialValue, }); } @@ -56,10 +55,9 @@ export const PoolName = (props: SetupStepProps) => { <> <Header thisSection={section} - complete={setup.metadata !== ''} - title={t('pools.pool_name') || ''} - // helpKey="Bonding" - setupType={SetupType.Pool} + complete={progress.metadata !== ''} + title={t('pools.poolName')} + bondFor="pool" /> <MotionContainer thisSection={section} activeSection={setup.section}> <Input @@ -68,7 +66,7 @@ export const PoolName = (props: SetupStepProps) => { setters={[ { set: handleSetupUpdate, - current: setup, + current: progress, }, { set: setMetadata, @@ -76,7 +74,7 @@ export const PoolName = (props: SetupStepProps) => { }, ]} /> - <Footer complete={valid} setupType={SetupType.Pool} /> + <Footer complete={valid} bondFor="pool" /> </MotionContainer> </> ); diff --git a/src/pages/Pools/Create/PoolRoles/index.tsx b/src/pages/Pools/Create/PoolRoles/index.tsx index 82e5d1ec80..1f3cb57167 100644 --- a/src/pages/Pools/Create/PoolRoles/index.tsx +++ b/src/pages/Pools/Create/PoolRoles/index.tsx @@ -1,31 +1,30 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import { useConnect } from 'contexts/Connect'; -import { useUi } from 'contexts/UI'; -import { SetupType } from 'contexts/UI/types'; +import { useEffect, useState } from 'react'; +import { Trans, useTranslation } from 'react-i18next'; +import { useSetup } from 'contexts/Setup'; import { Footer } from 'library/SetupSteps/Footer'; import { Header } from 'library/SetupSteps/Header'; import { MotionContainer } from 'library/SetupSteps/MotionContainer'; -import { SetupStepProps } from 'library/SetupSteps/types'; -import { useEffect, useState } from 'react'; -import { useTranslation } from 'react-i18next'; +import type { SetupStepProps } from 'library/SetupSteps/types'; +import { useActiveAccounts } from 'contexts/ActiveAccounts'; import { Roles } from '../../Roles'; -export const PoolRoles = (props: SetupStepProps) => { - const { section } = props; - const { activeAccount } = useConnect(); - const { getSetupProgress, setActiveAccountSetup } = useUi(); - const setup = getSetupProgress(SetupType.Pool, activeAccount); +export const PoolRoles = ({ section }: SetupStepProps) => { const { t } = useTranslation('pages'); + const { activeAccount } = useActiveAccounts(); + const { getSetupProgress, setActiveAccountSetup } = useSetup(); + const setup = getSetupProgress('pool', activeAccount); + const { progress } = setup; // if no roles in setup already, inject `activeAccount` to be // root and depositor roles. - const initialValue = setup.roles ?? { + const initialValue = progress.roles ?? { root: activeAccount, depositor: activeAccount, nominator: activeAccount, - stateToggler: activeAccount, + bouncer: activeAccount, }; // store local pool name for form control @@ -38,7 +37,7 @@ export const PoolRoles = (props: SetupStepProps) => { // handler for updating pool roles const handleSetupUpdate = (value: any) => { - setActiveAccountSetup(SetupType.Pool, value); + setActiveAccountSetup('pool', value); }; // update pool roles on account change @@ -52,8 +51,8 @@ export const PoolRoles = (props: SetupStepProps) => { useEffect(() => { // only update if this section is currently active if (setup.section === section) { - setActiveAccountSetup(SetupType.Pool, { - ...setup, + setActiveAccountSetup('pool', { + ...progress, roles: initialValue, }); } @@ -63,22 +62,30 @@ export const PoolRoles = (props: SetupStepProps) => { <> <Header thisSection={section} - complete={setup.roles !== null} - title={t('pools.roles') || ''} + complete={progress.roles !== null} + title={t('pools.roles')} helpKey="Pool Roles" - setupType={SetupType.Pool} + bondFor="pool" /> <MotionContainer thisSection={section} activeSection={setup.section}> - <h4 style={{ margin: '0.5rem 0' }}>{t('pools.pool_creator')}</h4> - <h4 style={{ marginTop: 0 }}>{t('pools.assigned_to_any_account')}</h4> + <h4 style={{ margin: '0.5rem 0' }}> + <Trans defaults={t('pools.poolCreator')} components={{ b: <b /> }} /> + </h4> + <h4 style={{ margin: '0.5rem 0 1.5rem 0' }}> + <Trans + defaults={t('pools.assignedToAnyAccount')} + components={{ b: <b /> }} + /> + </h4> <Roles + inline batchKey="pool_roles_create" listenIsValid={setRolesValid} defaultRoles={initialValue} setters={[ { set: handleSetupUpdate, - current: setup, + current: progress, }, { set: setRoles, @@ -86,7 +93,7 @@ export const PoolRoles = (props: SetupStepProps) => { }, ]} /> - <Footer complete={rolesValid} setupType={SetupType.Pool} /> + <Footer complete={rolesValid} bondFor="pool" /> </MotionContainer> </> ); diff --git a/src/pages/Pools/Create/Summary/Wrapper.ts b/src/pages/Pools/Create/Summary/Wrapper.ts index 55a444ef5e..fb8d016312 100644 --- a/src/pages/Pools/Create/Summary/Wrapper.ts +++ b/src/pages/Pools/Create/Summary/Wrapper.ts @@ -1,41 +1,37 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only import styled from 'styled-components'; -import { borderPrimary, networkColor, textSecondary } from 'theme'; export const SummaryWrapper = styled.div` display: flex; flex-flow: row wrap; - justify-content: flex-start; - align-items: flex-start; width: 100%; margin-bottom: 1rem; > section { + border-bottom: 1px solid var(--border-primary-color); flex-basis: 100%; display: flex; flex-flow: row wrap; - justify-content: flex-start; align-items: flex-end; - border-bottom: 1px solid ${borderPrimary}; margin-top: 1rem; padding: 0.5rem 0 0.75rem 0; > div:first-child { - color: ${textSecondary}; + color: var(--text-color-secondary); width: 200px; display: flex; flex-flow: row wrap; - align-items: flex-end; + align-items: center; svg { - color: ${networkColor}; + color: var(--accent-color-primary); } } > div:last-child { - color: ${textSecondary}; + color: var(--text-color-secondary); flex-grow: 1; display: flex; flex-flow: row wrap; diff --git a/src/pages/Pools/Create/Summary/index.tsx b/src/pages/Pools/Create/Summary/index.tsx index d6a86fafd0..a926740aa0 100644 --- a/src/pages/Pools/Create/Summary/index.tsx +++ b/src/pages/Pools/Create/Summary/index.tsx @@ -1,78 +1,74 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import { IconProp } from '@fortawesome/fontawesome-svg-core'; import { faCheckCircle } from '@fortawesome/free-regular-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { ButtonPrimary } from '@rossbulat/polkadot-dashboard-ui'; -import { BN } from 'bn.js'; -import { useApi } from 'contexts/Api'; -import { useConnect } from 'contexts/Connect'; +import { unitToPlanck } from '@polkadot-cloud/utils'; +import BigNumber from 'bignumber.js'; +import { useTranslation } from 'react-i18next'; import { useBondedPools } from 'contexts/Pools/BondedPools'; import { usePoolMembers } from 'contexts/Pools/PoolMembers'; import { usePoolsConfig } from 'contexts/Pools/PoolsConfig'; -import { useTxFees } from 'contexts/TxFees'; -import { useUi } from 'contexts/UI'; -import { defaultPoolSetup } from 'contexts/UI/defaults'; -import { SetupType } from 'contexts/UI/types'; -import { EstimatedTxFee } from 'library/EstimatedTxFee'; +import { useSetup } from 'contexts/Setup'; import { Warning } from 'library/Form/Warning'; +import { useBatchCall } from 'library/Hooks/useBatchCall'; import { useSubmitExtrinsic } from 'library/Hooks/useSubmitExtrinsic'; import { Header } from 'library/SetupSteps/Header'; import { MotionContainer } from 'library/SetupSteps/MotionContainer'; -import { SetupStepProps } from 'library/SetupSteps/types'; -import { useTranslation } from 'react-i18next'; -import { humanNumber, unitToPlanckBn } from 'Utils'; +import type { SetupStepProps } from 'library/SetupSteps/types'; +import { SubmitTx } from 'library/SubmitTx'; +import { useNetwork } from 'contexts/Network'; +import { useApi } from 'contexts/Api'; +import { useActiveAccounts } from 'contexts/ActiveAccounts'; +import { useImportedAccounts } from 'contexts/Connect/ImportedAccounts'; import { SummaryWrapper } from './Wrapper'; -export const Summary = (props: SetupStepProps) => { - const { section } = props; - const { api, network } = useApi(); - const { units } = network; - const { activeAccount, accountHasSigner } = useConnect(); - const { getSetupProgress, setActiveAccountSetup } = useUi(); +export const Summary = ({ section }: SetupStepProps) => { + const { t } = useTranslation('pages'); + const { api } = useApi(); + const { + networkData: { units, unit }, + } = useNetwork(); const { stats } = usePoolsConfig(); + const { newBatchCall } = useBatchCall(); + const { accountHasSigner } = useImportedAccounts(); + const { getSetupProgress, removeSetupProgress } = useSetup(); const { queryPoolMember, addToPoolMembers } = usePoolMembers(); const { queryBondedPool, addToBondedPools } = useBondedPools(); + const { activeAccount, activeProxy } = useActiveAccounts(); + const { lastPoolId } = stats; - const poolId = lastPoolId.add(new BN(1)); - const { txFeesValid } = useTxFees(); - const { t } = useTranslation('pages'); + const poolId = lastPoolId.plus(1); - const setup = getSetupProgress(SetupType.Pool, activeAccount); + const setup = getSetupProgress('pool', activeAccount); + const { progress } = setup; - const { metadata, bond, roles, nominations } = setup; + const { metadata, bond, roles, nominations } = progress; const getTxs = () => { - if ( - !activeAccount || - !api || - !metadata || - bond === 0 || - !roles || - !nominations.length - ) { + if (!activeAccount || !api) { return null; } - const bondToSubmit = unitToPlanckBn(bond, units).toString(); const targetsToSubmit = nominations.map((item: any) => item.address); - // construct a batch of transactions + const bondToSubmit = unitToPlanck(bond, units); + const bondAsString = bondToSubmit.isNaN() ? '0' : bondToSubmit.toString(); + const txs = [ api.tx.nominationPools.create( - bondToSubmit, - roles.root, - roles.nominator, - roles.stateToggler + bondAsString, + roles?.root || activeAccount, + roles?.nominator || activeAccount, + roles?.bouncer || activeAccount ), api.tx.nominationPools.nominate(poolId.toString(), targetsToSubmit), api.tx.nominationPools.setMetadata(poolId.toString(), metadata), ]; - return api.tx.utility.batch(txs); + return newBatchCall(txs, activeAccount); }; - const { submitTx, submitting } = useSubmitExtrinsic({ + const submitExtrinsic = useSubmitExtrinsic({ tx: getTxs(), from: activeAccount, shouldSubmit: true, @@ -87,7 +83,7 @@ export const Summary = (props: SetupStepProps) => { addToPoolMembers(member); // reset localStorage setup progress - setActiveAccountSetup(SetupType.Pool, defaultPoolSetup); + removeSetupProgress('pool', activeAccount); }, }); @@ -96,81 +92,61 @@ export const Summary = (props: SetupStepProps) => { <Header thisSection={section} complete={null} - title={t('pools.summary') || ''} - setupType={SetupType.Pool} + title={t('pools.summary')} + bondFor="pool" /> <MotionContainer thisSection={section} activeSection={setup.section}> - {!accountHasSigner(activeAccount) && ( - <Warning text={t('pools.read_only')} /> - )} + {!( + accountHasSigner(activeAccount) || accountHasSigner(activeProxy) + ) && <Warning text={t('pools.readOnly')} />} <SummaryWrapper> <section> <div> - <FontAwesomeIcon - icon={faCheckCircle as IconProp} - transform="grow-1" - />{' '} -   {t('pools.pool_name')}: + <FontAwesomeIcon icon={faCheckCircle} transform="grow-1" />  {' '} + {t('pools.poolName')}: </div> - <div>{metadata ?? `Not Set`}</div> + <div>{metadata ?? `${t('pools.notSet')}`}</div> </section> <section> <div> - <FontAwesomeIcon - icon={faCheckCircle as IconProp} - transform="grow-1" - />{' '} -   {t('pools.bond_amount')}: + <FontAwesomeIcon icon={faCheckCircle} transform="grow-1" />  {' '} + {t('pools.bondAmount')}: </div> <div> - {humanNumber(bond)} {network.unit} + {new BigNumber(bond).toFormat()} {unit} </div> </section> <section> <div> - <FontAwesomeIcon - icon={faCheckCircle as IconProp} - transform="grow-1" - />{' '} -   {t('pools.nominations')}: + <FontAwesomeIcon icon={faCheckCircle} transform="grow-1" />   + {t('pools.nominating')}: </div> - <div>{nominations.length}</div> + <div>{t('nominate.validator', { count: nominations.length })}</div> </section> <section> <div> - <FontAwesomeIcon - icon={faCheckCircle as IconProp} - transform="grow-1" - />{' '} -   {t('pools.roles')}: + <FontAwesomeIcon icon={faCheckCircle} transform="grow-1" />  {' '} + {t('pools.roles')}: </div> <div>{t('pools.assigned')}</div> </section> - <section> - <EstimatedTxFee format="table" /> - </section> </SummaryWrapper> <div style={{ flex: 1, - flexDirection: 'row', width: '100%', - display: 'flex', - justifyContent: 'end', + borderRadius: '1rem', + overflow: 'hidden', }} > - <ButtonPrimary - lg - onClick={() => submitTx()} - disabled={ - submitting || !accountHasSigner(activeAccount) || !txFeesValid - } - text={t('pools.create_pool')} + <SubmitTx + submitText={t('pools.createPool')} + valid + {...submitExtrinsic} + displayFor="canvas" /* Edge case: not canvas, but the larger button sizes suit this UI more. */ /> </div> </MotionContainer> </> ); }; - -export default Summary; diff --git a/src/pages/Pools/Create/index.tsx b/src/pages/Pools/Create/index.tsx index 1c4afadc3a..c40f0abd40 100644 --- a/src/pages/Pools/Create/index.tsx +++ b/src/pages/Pools/Create/index.tsx @@ -1,88 +1,85 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only import { faChevronLeft } from '@fortawesome/free-solid-svg-icons'; -import { ButtonSecondary } from '@rossbulat/polkadot-dashboard-ui'; -import { useUi } from 'contexts/UI'; -import { defaultPoolSetup } from 'contexts/UI/defaults'; -import { SetupType } from 'contexts/UI/types'; -import { CardWrapper } from 'library/Graphs/Wrappers'; -import { PageTitle } from 'library/PageTitle'; -import { Nominate } from 'library/SetupSteps/Nominate'; +import { + ButtonSecondary, + PageHeading, + PageRow, + PageTitle, +} from '@polkadot-cloud/react'; import { useTranslation } from 'react-i18next'; import { Element } from 'react-scroll'; -import { PageRowWrapper, TopBarWrapper } from 'Wrappers'; +import { useSetup } from 'contexts/Setup'; +import { CardWrapper } from 'library/Card/Wrappers'; +import { Nominate } from 'library/SetupSteps/Nominate'; +import { useActiveAccounts } from 'contexts/ActiveAccounts'; import { Bond } from './Bond'; import { PoolName } from './PoolName'; import { PoolRoles } from './PoolRoles'; import { Summary } from './Summary'; export const Create = () => { - const { setOnPoolSetup, setActiveAccountSetup } = useUi(); const { t } = useTranslation('pages'); + const { activeAccount } = useActiveAccounts(); + const { setOnPoolSetup, removeSetupProgress } = useSetup(); return ( <> - <PageTitle title={t('pools.create_a_pool')} /> - <PageRowWrapper className="page-padding" noVerticalSpacer> - <TopBarWrapper> + <PageTitle title={t('pools.createAPool')} /> + <PageRow> + <PageHeading> <span> <ButtonSecondary - lg text={t('pools.back')} iconLeft={faChevronLeft} iconTransform="shrink-3" - onClick={() => setOnPoolSetup(0)} + onClick={() => setOnPoolSetup(false)} /> </span> <span> <ButtonSecondary - lg text={t('pools.cancel')} onClick={() => { - setOnPoolSetup(0); - setActiveAccountSetup(SetupType.Pool, defaultPoolSetup); + setOnPoolSetup(false); + removeSetupProgress('pool', activeAccount); }} /> </span> <div className="right" /> - </TopBarWrapper> - </PageRowWrapper> - <PageRowWrapper className="page-padding" noVerticalSpacer> + </PageHeading> + </PageRow> + <PageRow> <CardWrapper> <Element name="metadata" style={{ position: 'absolute' }} /> <PoolName section={1} /> </CardWrapper> - </PageRowWrapper> - <PageRowWrapper className="page-padding" noVerticalSpacer> + </PageRow> + <PageRow> <CardWrapper> <Element name="nominate" style={{ position: 'absolute' }} /> - <Nominate - batchKey="generate_nominations_create_pool" - setupType={SetupType.Pool} - section={2} - /> + <Nominate bondFor="pool" section={2} /> </CardWrapper> - </PageRowWrapper> - <PageRowWrapper className="page-padding" noVerticalSpacer> + </PageRow> + <PageRow> <CardWrapper> <Element name="roles" style={{ position: 'absolute' }} /> <PoolRoles section={3} /> </CardWrapper> - </PageRowWrapper> - <PageRowWrapper className="page-padding" noVerticalSpacer> + </PageRow> + <PageRow> <CardWrapper> <Element name="bond" style={{ position: 'absolute' }} /> <Bond section={4} /> </CardWrapper> - </PageRowWrapper> + </PageRow> - <PageRowWrapper className="page-padding" noVerticalSpacer> + <PageRow> <CardWrapper> <Element name="summary" style={{ position: 'absolute' }} /> <Summary section={5} /> </CardWrapper> - </PageRowWrapper> + </PageRow> </> ); }; diff --git a/src/pages/Pools/Home/ClosurePrompts.tsx b/src/pages/Pools/Home/ClosurePrompts.tsx index d7cdce9703..f8b4e92e4b 100644 --- a/src/pages/Pools/Home/ClosurePrompts.tsx +++ b/src/pages/Pools/Home/ClosurePrompts.tsx @@ -1,45 +1,41 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only import { faLockOpen } from '@fortawesome/free-solid-svg-icons'; -import { ButtonPrimary } from '@rossbulat/polkadot-dashboard-ui'; -import { useApi } from 'contexts/Api'; -import { useConnect } from 'contexts/Connect'; -import { useModal } from 'contexts/Modal'; +import { ButtonPrimary, ButtonRow, PageRow } from '@polkadot-cloud/react'; +import { useTranslation } from 'react-i18next'; import { useActivePools } from 'contexts/Pools/ActivePools'; import { usePoolMemberships } from 'contexts/Pools/PoolMemberships'; -import { PoolState } from 'contexts/Pools/types'; import { useTheme } from 'contexts/Themes'; import { useTransferOptions } from 'contexts/TransferOptions'; import { useUi } from 'contexts/UI'; -import { CardWrapper } from 'library/Graphs/Wrappers'; -import { useTranslation } from 'react-i18next'; -import { ButtonRowWrapper, PageRowWrapper } from 'Wrappers'; +import { CardWrapper } from 'library/Card/Wrappers'; +import { useOverlay } from '@polkadot-cloud/react/hooks'; +import { useNetwork } from 'contexts/Network'; +import { useActiveAccounts } from 'contexts/ActiveAccounts'; export const ClosurePrompts = () => { - const { network } = useApi(); - const { activeAccount } = useConnect(); + const { t } = useTranslation('pages'); + const { colors } = useNetwork().networkData; + const { activeAccount } = useActiveAccounts(); const { mode } = useTheme(); - const { openModalWith } = useModal(); + const { openModal } = useOverlay().modal; const { membership } = usePoolMemberships(); - const { poolsSyncing } = useUi(); + const { isPoolSyncing } = useUi(); const { isBonding, selectedActivePool, isDepositor, poolNominations } = useActivePools(); const { getTransferOptions } = useTransferOptions(); - const { t } = useTranslation('pages'); const { state, memberCounter } = selectedActivePool?.bondedPool || {}; const { active, totalUnlockChuncks } = getTransferOptions(activeAccount).pool; const targets = poolNominations?.targets ?? []; - - const networkColorsSecondary: any = network.colors.secondary; - const annuncementBorderColor = networkColorsSecondary[mode]; + const annuncementBorderColor = colors.secondary[mode]; // is the pool in a state for the depositor to close const depositorCanClose = - !poolsSyncing && + !isPoolSyncing && isDepositor() && - state === PoolState.Destroy && + state === 'Destroying' && memberCounter === '1'; // depositor needs to unbond funds @@ -52,54 +48,62 @@ export const ClosurePrompts = () => { return ( <> {depositorCanClose && ( - <PageRowWrapper className="page-padding" noVerticalSpacer> + <PageRow> <CardWrapper style={{ border: `1px solid ${annuncementBorderColor}` }} > <div className="content"> - <h3>{t('pools.destroy_pool')}</h3> + <h3>{t('pools.destroyPool')}</h3> <h4> - {t('pools.left_the_pool')} + {t('pools.leftThePool')}.{' '} {targets.length > 0 - ? t('pools.stop_nominating') + ? t('pools.stopNominating') : depositorCanWithdraw - ? t('pools.close_pool') + ? t('pools.closePool') : depositorCanUnbond - ? t('pools.unbond_your_funds') - : t('pools.withdraw_unlock')} + ? t('pools.unbondYourFunds') + : t('pools.withdrawUnlock')} </h4> - <ButtonRowWrapper verticalSpacing> + <ButtonRow yMargin> <ButtonPrimary marginRight text={t('pools.unbond')} disabled={ - poolsSyncing || + isPoolSyncing || (!depositorCanWithdraw && !depositorCanUnbond) } onClick={() => - openModalWith( - 'UnbondPoolMember', - { who: activeAccount, member: membership }, - 'small' - ) + openModal({ + key: 'UnbondPoolMember', + options: { who: activeAccount, member: membership }, + size: 'sm', + }) } /> <ButtonPrimary iconLeft={faLockOpen} - text={String(totalUnlockChuncks ?? 0)} - disabled={poolsSyncing || !isBonding()} + text={ + depositorCanWithdraw + ? t('pools.unlocked') + : String(totalUnlockChuncks ?? 0) + } + disabled={isPoolSyncing || !isBonding()} onClick={() => - openModalWith( - 'UnlockChunks', - { bondType: 'pool', poolClosure: true }, - 'small' - ) + openModal({ + key: 'UnlockChunks', + options: { + bondFor: 'pool', + poolClosure: true, + disableWindowResize: true, + }, + size: 'sm', + }) } /> - </ButtonRowWrapper> + </ButtonRow> </div> </CardWrapper> - </PageRowWrapper> + </PageRow> )} </> ); diff --git a/src/pages/Pools/Home/Favorites/index.tsx b/src/pages/Pools/Home/Favorites/index.tsx index d67a421fcf..62a91638d8 100644 --- a/src/pages/Pools/Home/Favorites/index.tsx +++ b/src/pages/Pools/Home/Favorites/index.tsx @@ -1,71 +1,67 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only +import { PageRow } from '@polkadot-cloud/react'; +import { useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; import { useApi } from 'contexts/Api'; import { useBondedPools } from 'contexts/Pools/BondedPools'; import { usePoolsConfig } from 'contexts/Pools/PoolsConfig'; import { useUi } from 'contexts/UI'; -import { CardWrapper } from 'library/Graphs/Wrappers'; -import PoolList from 'library/PoolList'; -import { useEffect, useState } from 'react'; -import { useTranslation } from 'react-i18next'; -import { PageRowWrapper } from 'Wrappers'; +import { CardWrapper } from 'library/Card/Wrappers'; +import { PoolList } from 'library/PoolList/Default'; +import { ListStatusHeader } from 'library/List'; +import { PoolListProvider } from 'library/PoolList/context'; -export const Favorites = () => { +export const PoolFavorites = () => { + const { t } = useTranslation('pages'); const { isReady } = useApi(); - const { favorites, removeFavorite } = usePoolsConfig(); + const { isPoolSyncing } = useUi(); const { bondedPools } = useBondedPools(); - const { poolsSyncing } = useUi(); - const { t } = useTranslation('pages'); + const { favorites, removeFavorite } = usePoolsConfig(); - // store local favorite list and update when favorites list is mutated - const [favoritesList, setFavoritesList] = useState<Array<any>>([]); + // Store local favorite list and update when favorites list is mutated. + const [favoritesList, setFavoritesList] = useState<any[]>([]); useEffect(() => { // map favorites to bonded pools - let _favoritesList = favorites.map((f: any) => { - const pool = bondedPools.find((b: any) => b.addresses.stash === f); - if (!pool) { - removeFavorite(f); - } + let newFavoritesList = favorites.map((f) => { + const pool = bondedPools.find((b) => b.addresses.stash === f); + if (!pool) removeFavorite(f); return pool; }); // filter not found bonded pools - _favoritesList = _favoritesList.filter((f: any) => f !== undefined); + newFavoritesList = newFavoritesList.filter((f: any) => f !== undefined); - setFavoritesList(_favoritesList); + setFavoritesList(newFavoritesList); }, [favorites]); return ( <> - <PageRowWrapper className="page-padding" noVerticalSpacer> + <PageRow> <CardWrapper> - {favoritesList === null || poolsSyncing ? ( - <h3>{t('pools.fetching_favorite_pools')}</h3> + {favoritesList === null || isPoolSyncing ? ( + <ListStatusHeader> + {t('pools.fetchingFavoritePools')}... + </ListStatusHeader> ) : ( - <> - {isReady && ( - <> - {favoritesList.length > 0 ? ( - <PoolList - batchKey="favorite_pools" - pools={favoritesList} - title={t('pools.favorites_list')} - allowMoreCols - pagination - /> - ) : ( - <h3>{t('pools.no_favorites')}</h3> - )} - </> - )} - </> + isReady && + (favoritesList.length > 0 ? ( + <PoolListProvider> + <PoolList + batchKey="favorite_pools" + pools={favoritesList} + allowMoreCols + pagination + /> + </PoolListProvider> + ) : ( + <ListStatusHeader>{t('pools.noFavorites')}</ListStatusHeader> + )) )} </CardWrapper> - </PageRowWrapper> + </PageRow> </> ); }; - -export default Favorites; diff --git a/src/pages/Pools/Home/ManageBond.tsx b/src/pages/Pools/Home/ManageBond.tsx index 108766430e..620508f268 100644 --- a/src/pages/Pools/Home/ManageBond.tsx +++ b/src/pages/Pools/Home/ManageBond.tsx @@ -1,36 +1,51 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only import { faLockOpen } from '@fortawesome/free-solid-svg-icons'; -import { ButtonPrimary } from '@rossbulat/polkadot-dashboard-ui'; -import { useApi } from 'contexts/Api'; -import { useConnect } from 'contexts/Connect'; -import { useModal } from 'contexts/Modal'; +import { + ButtonHelp, + ButtonPrimary, + ButtonRow, + Odometer, +} from '@polkadot-cloud/react'; +import { minDecimalPlaces, planckToUnit } from '@polkadot-cloud/utils'; +import { useTranslation } from 'react-i18next'; +import { useHelp } from 'contexts/Help'; import { useActivePools } from 'contexts/Pools/ActivePools'; -import { PoolState } from 'contexts/Pools/types'; import { useTransferOptions } from 'contexts/TransferOptions'; import { useUi } from 'contexts/UI'; -import BondedGraph from 'library/Graphs/Bonded'; -import { CardHeaderWrapper } from 'library/Graphs/Wrappers'; -import { OpenHelpIcon } from 'library/OpenHelpIcon'; -import { useTranslation } from 'react-i18next'; -import { humanNumber, planckBnToUnit } from 'Utils'; -import { ButtonRowWrapper } from 'Wrappers'; +import { BondedChart } from 'library/BarChart/BondedChart'; +import { CardHeaderWrapper } from 'library/Card/Wrappers'; +import { useOverlay } from '@polkadot-cloud/react/hooks'; +import { useNetwork } from 'contexts/Network'; +import { useActiveAccounts } from 'contexts/ActiveAccounts'; +import { useImportedAccounts } from 'contexts/Connect/ImportedAccounts'; export const ManageBond = () => { - const { network } = useApi(); - const { units } = network; - const { openModalWith } = useModal(); - const { activeAccount } = useConnect(); - const { poolsSyncing } = useUi(); - const { isBonding, isMember, selectedActivePool } = useActivePools(); - const { getTransferOptions } = useTransferOptions(); const { t } = useTranslation('pages'); + const { + networkData: { + units, + brand: { token: Token }, + }, + } = useNetwork(); + const { openHelp } = useHelp(); + const { isPoolSyncing } = useUi(); + const { openModal } = useOverlay().modal; + const { activeAccount } = useActiveAccounts(); + const { isReadOnlyAccount } = useImportedAccounts(); + const { getTransferOptions } = useTransferOptions(); + const { isBonding, isMember, selectedActivePool } = useActivePools(); + const allTransferOptions = getTransferOptions(activeAccount); - const { freeBalance } = allTransferOptions; - const { active, totalUnlocking, totalUnlocked, totalUnlockChuncks } = - allTransferOptions.pool; + const { + active, + totalUnlocking, + totalUnlocked, + totalUnlockChuncks, + totalAdditionalBond, + } = allTransferOptions.pool; const { state } = selectedActivePool?.bondedPool || {}; @@ -38,68 +53,76 @@ export const ManageBond = () => { <> <CardHeaderWrapper> <h4> - {t('pools.bonded_funds')} - <OpenHelpIcon helpKey="Bonded in Pool" /> + {t('pools.bondedFunds')} + <ButtonHelp marginLeft onClick={() => openHelp('Bonded in Pool')} /> </h4> <h2> - {humanNumber(planckBnToUnit(active, units))} {network.unit} + <Token className="networkIcon" /> + <Odometer + value={minDecimalPlaces(planckToUnit(active, units).toFormat(), 2)} + zeroDecimals={2} + /> </h2> - <ButtonRowWrapper> + <ButtonRow> <ButtonPrimary disabled={ - poolsSyncing || + isPoolSyncing || !isBonding() || !isMember() || - state === PoolState.Destroy + isReadOnlyAccount(activeAccount) || + state === 'Destroying' } marginRight onClick={() => - openModalWith( - 'UpdateBond', - { fn: 'add', bondType: 'pool' }, - 'small' - ) + openModal({ + key: 'Bond', + options: { bondFor: 'pool' }, + size: 'sm', + }) } text="+" /> <ButtonPrimary disabled={ - poolsSyncing || + isPoolSyncing || !isBonding() || !isMember() || - state === PoolState.Destroy + isReadOnlyAccount(activeAccount) || + state === 'Destroying' } marginRight onClick={() => - openModalWith( - 'UpdateBond', - { fn: 'remove', bondType: 'pool' }, - 'small' - ) + openModal({ + key: 'Unbond', + options: { bondFor: 'pool' }, + size: 'sm', + }) } text="-" /> <ButtonPrimary disabled={ - poolsSyncing || !isMember() || state === PoolState.Destroy + isPoolSyncing || !isMember() || isReadOnlyAccount(activeAccount) } iconLeft={faLockOpen} onClick={() => - openModalWith('UnlockChunks', { bondType: 'pool' }, 'small') + openModal({ + key: 'UnlockChunks', + options: { bondFor: 'pool', disableWindowResize: true }, + size: 'sm', + }) } text={String(totalUnlockChuncks ?? 0)} /> - </ButtonRowWrapper> + </ButtonRow> </CardHeaderWrapper> - <BondedGraph - active={planckBnToUnit(active, units)} - unlocking={planckBnToUnit(totalUnlocking, units)} - unlocked={planckBnToUnit(totalUnlocked, units)} - free={planckBnToUnit(freeBalance, units)} - inactive={!isMember()} + <BondedChart + active={planckToUnit(active, units)} + unlocking={planckToUnit(totalUnlocking, units)} + unlocked={planckToUnit(totalUnlocked, units)} + free={planckToUnit(totalAdditionalBond, units)} + inactive={active.isZero()} /> </> ); }; - -export default ManageBond; diff --git a/src/pages/Pools/Home/ManagePool/Wrappers.ts b/src/pages/Pools/Home/ManagePool/Wrappers.ts new file mode 100644 index 0000000000..e6a5173994 --- /dev/null +++ b/src/pages/Pools/Home/ManagePool/Wrappers.ts @@ -0,0 +1,53 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import styled from 'styled-components'; +import { SectionFullWidthThreshold } from 'consts'; + +export const RolesWrapper = styled.div` + display: flex; + flex-flow: row wrap; + width: 100%; + margin-top: 0.25rem; + + > section { + flex: 1 1 25%; + padding: 0 0.5rem; + border-right: 1px solid var(--border-primary-color); + + @media (max-width: ${SectionFullWidthThreshold}px) { + border-bottom: 1px solid var(--border-primary-color); + flex-basis: 100%; + border-right: none; + margin: 0.75rem 0; + + &:first-child { + margin-top: 0; + } + &:last-child { + margin-bottom: 0; + border-bottom: 0; + } + } + + &:last-child { + border-right: none; + } + + .inner { + flex: 1; + padding: 0 0.5rem; + + @media (max-width: ${SectionFullWidthThreshold}px) { + padding: 0; + } + + > h4 { + font-family: InterSemiBold, sans-serif; + display: flex; + align-items: center; + margin-top: 1.25rem; + } + } + } +`; diff --git a/src/pages/Pools/Home/ManagePool/index.tsx b/src/pages/Pools/Home/ManagePool/index.tsx index 3ba328b349..9f895b4f2a 100644 --- a/src/pages/Pools/Home/ManagePool/index.tsx +++ b/src/pages/Pools/Home/ManagePool/index.tsx @@ -1,79 +1,76 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only import { faChevronCircleRight } from '@fortawesome/free-solid-svg-icons'; -import { ButtonPrimary } from '@rossbulat/polkadot-dashboard-ui'; -import { useConnect } from 'contexts/Connect'; -import { useModal } from 'contexts/Modal'; +import { ButtonHelp, ButtonPrimary, PageRow } from '@polkadot-cloud/react'; +import { useTranslation } from 'react-i18next'; +import { useHelp } from 'contexts/Help'; import { useActivePools } from 'contexts/Pools/ActivePools'; -import { PoolState } from 'contexts/Pools/types'; import { useUi } from 'contexts/UI'; -import { GenerateNominations } from 'library/GenerateNominations'; -import { CardHeaderWrapper, CardWrapper } from 'library/Graphs/Wrappers'; -import { OpenHelpIcon } from 'library/OpenHelpIcon'; -import Nominations from 'pages/Nominate/Active/Nominations'; -import { useTranslation } from 'react-i18next'; -import { PageRowWrapper } from 'Wrappers'; +import { CardHeaderWrapper, CardWrapper } from 'library/Card/Wrappers'; +import { Nominations } from 'library/Nominations'; +import { useOverlay } from '@polkadot-cloud/react/hooks'; +import { useActiveAccounts } from 'contexts/ActiveAccounts'; +import { useValidators } from 'contexts/Validators/ValidatorEntries'; export const ManagePool = () => { + const { t } = useTranslation(); const { isSyncing } = useUi(); - const { openModalWith } = useModal(); - const { activeAccount } = useConnect(); - const { - isOwner, - isNominator, - setTargets, - targets, - poolNominations, - selectedActivePool, - } = useActivePools(); - const { t } = useTranslation('pages'); + const { poolNominated } = useValidators(); + const { openCanvas } = useOverlay().canvas; + const { activeAccount } = useActiveAccounts(); + const { isOwner, isNominator, poolNominations, selectedActivePool } = + useActivePools(); const isNominating = !!poolNominations?.targets?.length; const nominator = selectedActivePool?.addresses?.stash ?? null; const { state } = selectedActivePool?.bondedPool || {}; + const { openHelp } = useHelp(); const canNominate = isOwner() || isNominator(); return ( - <PageRowWrapper className="page-padding" noVerticalSpacer> + <PageRow> <CardWrapper> {isSyncing ? ( - <Nominations bondType="pool" nominator={activeAccount} /> - ) : canNominate && !isNominating && state !== PoolState.Destroy ? ( + <Nominations bondFor="pool" nominator={activeAccount} /> + ) : canNominate && !isNominating && state !== 'Destroying' ? ( <> - <CardHeaderWrapper withAction> + <CardHeaderWrapper $withAction $withMargin> <h3> - {t('pools.generate_nominations')} - <OpenHelpIcon helpKey="Nominations" /> + {t('nominate.nominations', { ns: 'pages' })} + <ButtonHelp + marginLeft + onClick={() => openHelp('Nominations')} + /> </h3> <div> <ButtonPrimary iconLeft={faChevronCircleRight} iconTransform="grow-1" - text={t('pools.nominate')} + text={t('pools.nominate', { ns: 'pages' })} disabled={!canNominate} - onClick={() => openModalWith('NominatePool', {}, 'small')} + onClick={() => + openCanvas({ + key: 'ManageNominations', + scroll: false, + options: { + bondFor: 'pool', + nominator, + nominated: poolNominated || [], + }, + size: 'xl', + }) + } /> </div> </CardHeaderWrapper> - <GenerateNominations - batchKey="generate_pool_nominations" - nominations={targets.nominations} - setters={[ - { - set: setTargets, - current: targets, - }, - ]} - /> + <h4>{t('notNominating', { ns: 'library' })}.</h4> </> ) : ( - <Nominations bondType="pool" nominator={nominator} /> + <Nominations bondFor="pool" nominator={nominator} /> )} </CardWrapper> - </PageRowWrapper> + </PageRow> ); }; - -export default ManagePool; diff --git a/src/pages/Pools/Home/Members.tsx b/src/pages/Pools/Home/Members.tsx index f74512fa34..143ae551d7 100644 --- a/src/pages/Pools/Home/Members.tsx +++ b/src/pages/Pools/Home/Members.tsx @@ -1,87 +1,94 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only import { faBars } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { useApi } from 'contexts/Api'; +import { PageRow } from '@polkadot-cloud/react'; +import { useTranslation } from 'react-i18next'; +import { usePlugins } from 'contexts/Plugins'; import { useActivePools } from 'contexts/Pools/ActivePools'; import { usePoolMembers } from 'contexts/Pools/PoolMembers'; -import { PoolState } from 'contexts/Pools/types'; import { useTheme } from 'contexts/Themes'; -import { CardWrapper } from 'library/Graphs/Wrappers'; -import { useTranslation } from 'react-i18next'; -import { PageRowWrapper } from 'Wrappers'; -import { MembersList } from './MembersList'; +import { CardWrapper } from 'library/Card/Wrappers'; +import { useNetwork } from 'contexts/Network'; +import { MembersList as DefaultMemberList } from './MembersList/Default'; +import { MembersList as FetchPageMemberList } from './MembersList/FetchPage'; export const Members = () => { - const { network } = useApi(); - const { mode } = useTheme(); - const { getMembersOfPool } = usePoolMembers(); - const { selectedActivePool, isOwner, isStateToggler } = useActivePools(); const { t } = useTranslation('pages'); + const { mode } = useTheme(); + const { pluginEnabled } = usePlugins(); + const { getMembersOfPoolFromNode } = usePoolMembers(); + const { selectedActivePool, isOwner, isBouncer, selectedPoolMemberCount } = + useActivePools(); + const { colors } = useNetwork().networkData; - const poolMembers = getMembersOfPool(selectedActivePool?.id ?? 0); - const poolMembersTitle = `${t('pools.pool_member', { - count: poolMembers.length, - })}`; - - const networkColorsSecondary: any = network.colors.secondary; - const annuncementBorderColor = networkColorsSecondary[mode]; + const annuncementBorderColor = colors.secondary[mode]; const showBlockedPrompt = - selectedActivePool?.bondedPool?.state === PoolState.Block && - (isOwner() || isStateToggler()); + selectedActivePool?.bondedPool?.state === 'Blocked' && + (isOwner() || isBouncer()); + + const membersListProps = { + batchKey: 'active_pool_members', + pagination: true, + selectToggleable: false, + allowMoreCols: true, + }; return ( <> - {/* Pool in Blocked state: allow root & stage toggler to unbond & withdraw members */} + {/* Pool in Blocked state: allow root & bouncer to unbond & withdraw members */} {showBlockedPrompt && ( - <PageRowWrapper className="page-padding" noVerticalSpacer> + <PageRow> <CardWrapper style={{ border: `1px solid ${annuncementBorderColor}` }} > <div className="content"> - <h3>{t('pools.pool_currently_locked')}</h3> + <h3>{t('pools.poolCurrentlyLocked')}</h3> <h4> - {t('pools.permission_to_unbond')}({' '} + {t('pools.permissionToUnbond')}({' '} <FontAwesomeIcon icon={faBars} transform="shrink-2" /> ){' '} - {t('pools.management_options')} + {t('pools.managementOptions')} </h4> </div> </CardWrapper> - </PageRowWrapper> + </PageRow> )} {/* Pool in Destroying state: allow anyone to unbond & withdraw members */} - {selectedActivePool?.bondedPool?.state === PoolState.Destroy && ( - <PageRowWrapper className="page-padding" noVerticalSpacer> + {selectedActivePool?.bondedPool?.state === 'Destroying' && ( + <PageRow> <CardWrapper style={{ border: `1px solid ${annuncementBorderColor}` }} > <div className="content"> - <h3>{t('pools.pool_in_destroying_state')}</h3> + <h3>{t('pools.poolInDestroyingState')}</h3> <h4> - {t('pools.permission_to_unbond')} ({' '} + {t('pools.permissionToUnbond')} ({' '} <FontAwesomeIcon icon={faBars} transform="shrink-2" /> ){' '} - {t('pools.management_options')} + {t('pools.managementOptions')} </h4> </div> </CardWrapper> - </PageRowWrapper> + </PageRow> )} - <PageRowWrapper className="page-padding" noVerticalSpacer> + <PageRow> <CardWrapper> - <MembersList - title={poolMembersTitle} - batchKey="active_pool_members" - members={poolMembers} - pagination - selectToggleable={false} - allowMoreCols - /> + {pluginEnabled('subscan') ? ( + <FetchPageMemberList + {...membersListProps} + memberCount={selectedPoolMemberCount} + /> + ) : ( + <DefaultMemberList + {...membersListProps} + members={getMembersOfPoolFromNode(selectedActivePool?.id ?? 0)} + /> + )} </CardWrapper> - </PageRowWrapper> + </PageRow> </> ); }; diff --git a/src/pages/Pools/Home/MembersList/Default.tsx b/src/pages/Pools/Home/MembersList/Default.tsx new file mode 100644 index 0000000000..eac1f55f2d --- /dev/null +++ b/src/pages/Pools/Home/MembersList/Default.tsx @@ -0,0 +1,195 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { faBars, faGripVertical } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { isNotZero } from '@polkadot-cloud/utils'; +import { motion } from 'framer-motion'; +import { useEffect, useRef, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { ListItemsPerBatch, ListItemsPerPage } from 'consts'; +import { useApi } from 'contexts/Api'; +import { useNetworkMetrics } from 'contexts/NetworkMetrics'; +import { usePoolMembers } from 'contexts/Pools/PoolMembers'; +import type { PoolMember } from 'contexts/Pools/types'; +import { useTheme } from 'contexts/Themes'; +import { + Header, + List, + ListStatusHeader, + Wrapper as ListWrapper, +} from 'library/List'; +import { MotionContainer } from 'library/List/MotionContainer'; +import { Pagination } from 'library/List/Pagination'; +import { ListProvider, useList } from 'library/List/context'; +import type { Sync } from 'types'; +import { useNetwork } from 'contexts/Network'; +import { Member } from './Member'; +import type { DefaultMembersListProps } from './types'; + +export const MembersListInner = ({ + allowMoreCols, + pagination, + batchKey, + members: initialMembers, + disableThrottle = false, +}: DefaultMembersListProps) => { + const { t } = useTranslation('pages'); + const { isReady } = useApi(); + const { + networkData: { colors }, + } = useNetwork(); + const provider = useList(); + const { mode } = useTheme(); + const { activeEra } = useNetworkMetrics(); + const { fetchPoolMembersMetaBatch } = usePoolMembers(); + + // get list provider properties. + const { listFormat, setListFormat } = provider; + + // current page + const [page, setPage] = useState<number>(1); + + // current render iteration + const [renderIteration, setRenderIterationState] = useState<number>(1); + + // default list of validators + const [membersDefault, setMembersDefault] = + useState<PoolMember[]>(initialMembers); + + // manipulated list (ordering, filtering) of payouts + const [members, setMembers] = useState<PoolMember[]>(initialMembers); + + // is this the initial fetch + const [fetched, setFetched] = useState<Sync>('unsynced'); + + // render throttle iteration + const renderIterationRef = useRef(renderIteration); + const setRenderIteration = (iter: number) => { + renderIterationRef.current = iter; + setRenderIterationState(iter); + }; + + // pagination + const totalPages = Math.ceil(members.length / ListItemsPerPage); + const pageEnd = page * ListItemsPerPage - 1; + const pageStart = pageEnd - (ListItemsPerPage - 1); + + // render batch + const batchEnd = Math.min( + renderIteration * ListItemsPerBatch - 1, + ListItemsPerPage + ); + + // get throttled subset or entire list + const listMembers = disableThrottle + ? members + : members.slice(pageStart).slice(0, ListItemsPerPage); + + // handle validator list bootstrapping + const setupMembersList = () => { + setMembersDefault(initialMembers); + setMembers(initialMembers); + fetchPoolMembersMetaBatch(batchKey, initialMembers, false); + setFetched('synced'); + }; + + // Refetch list when list changes. + useEffect(() => { + if (initialMembers !== membersDefault) { + setFetched('unsynced'); + } + }, [initialMembers]); + + // Configure list when network is ready to fetch. + useEffect(() => { + if (isReady && isNotZero(activeEra.index) && fetched === 'unsynced') { + setupMembersList(); + } + }, [isReady, fetched, activeEra.index]); + + // Render throttle. + useEffect(() => { + if (!(batchEnd >= pageEnd || disableThrottle)) { + setTimeout(() => { + setRenderIteration(renderIterationRef.current + 1); + }, 500); + } + }, [renderIterationRef.current]); + + return ( + <> + {!members.length ? ( + <></> + ) : ( + <ListWrapper> + <Header> + <div /> + <div> + <button type="button" onClick={() => setListFormat('row')}> + <FontAwesomeIcon + icon={faBars} + color={ + listFormat === 'row' ? colors.primary[mode] : 'inherit' + } + /> + </button> + <button type="button" onClick={() => setListFormat('col')}> + <FontAwesomeIcon + icon={faGripVertical} + color={ + listFormat === 'col' ? colors.primary[mode] : 'inherit' + } + /> + </button> + </div> + </Header> + <List $flexBasisLarge={allowMoreCols ? '33.33%' : '50%'}> + {listMembers.length > 0 && pagination && ( + <Pagination page={page} total={totalPages} setter={setPage} /> + )} + {fetched !== 'synced' ? ( + <ListStatusHeader style={{ marginTop: '0.5rem' }}> + {t('pools.fetchingMemberList')}... + </ListStatusHeader> + ) : ( + <MotionContainer> + {listMembers.map((member: PoolMember, index: number) => ( + <motion.div + className={`item ${listFormat === 'row' ? 'row' : 'col'}`} + key={`nomination_${index}`} + variants={{ + hidden: { + y: 15, + opacity: 0, + }, + show: { + y: 0, + opacity: 1, + }, + }} + > + <Member + who={member.who} + batchKey={batchKey} + batchIndex={membersDefault.indexOf(member)} + /> + </motion.div> + ))} + </MotionContainer> + )} + </List> + </ListWrapper> + )} + </> + ); +}; + +export const MembersList = (props: DefaultMembersListProps) => { + const { selectToggleable } = props; + return ( + <ListProvider selectToggleable={selectToggleable}> + <MembersListInner {...props} /> + </ListProvider> + ); +}; diff --git a/src/pages/Pools/Home/MembersList/FetchPage.tsx b/src/pages/Pools/Home/MembersList/FetchPage.tsx new file mode 100644 index 0000000000..ba0c3e7fb2 --- /dev/null +++ b/src/pages/Pools/Home/MembersList/FetchPage.tsx @@ -0,0 +1,201 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { faBars, faGripVertical } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { motion } from 'framer-motion'; +import { useEffect, useRef, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { ListItemsPerBatch, ListItemsPerPage } from 'consts'; +import { usePlugins } from 'contexts/Plugins'; +import { useActivePools } from 'contexts/Pools/ActivePools'; +import { usePoolMembers } from 'contexts/Pools/PoolMembers'; +import type { PoolMember } from 'contexts/Pools/types'; +import { useSubscan } from 'contexts/Plugins/Subscan'; +import { useTheme } from 'contexts/Themes'; +import { + Header, + List, + ListStatusHeader, + Wrapper as ListWrapper, +} from 'library/List'; +import { MotionContainer } from 'library/List/MotionContainer'; +import { Pagination } from 'library/List/Pagination'; +import { ListProvider, useList } from 'library/List/context'; +import { useNetwork } from 'contexts/Network'; +import { useActiveAccounts } from 'contexts/ActiveAccounts'; +import { Member } from './Member'; +import type { FetchpageMembersListProps } from './types'; + +export const MembersListInner = ({ + allowMoreCols, + pagination, + batchKey, + disableThrottle = false, + memberCount, +}: FetchpageMembersListProps) => { + const { t } = useTranslation('pages'); + const { + network, + networkData: { colors }, + } = useNetwork(); + const provider = useList(); + const { mode } = useTheme(); + const { pluginEnabled } = usePlugins(); + const { fetchPoolMembers } = useSubscan(); + const { activeAccount } = useActiveAccounts(); + const { selectedActivePool } = useActivePools(); + const { + poolMembersApi, + setPoolMembersApi, + fetchedPoolMembersApi, + setFetchedPoolMembersApi, + fetchPoolMembersMetaBatch, + } = usePoolMembers(); + + // get list provider properties. + const { listFormat, setListFormat } = provider; + + // current page. + const [page, setPage] = useState<number>(1); + + // current render iteration. + const [renderIteration, setRenderIterationState] = useState<number>(1); + + // render throttle iteration. + const renderIterationRef = useRef(renderIteration); + const setRenderIteration = (iter: number) => { + renderIterationRef.current = iter; + setRenderIterationState(iter); + }; + + // pagination + const totalPages = Math.ceil(memberCount / ListItemsPerPage); + const pageEnd = ListItemsPerPage - 1; + const pageStart = pageEnd - (ListItemsPerPage - 1); + + // render batch + const batchEnd = Math.min( + renderIteration * ListItemsPerBatch - 1, + ListItemsPerPage + ); + + // handle validator list bootstrapping + const fetchingMemberList = useRef<boolean>(false); + + const setupMembersList = async () => { + const poolId = selectedActivePool?.id || 0; + + if (poolId > 0 && !fetchingMemberList.current) { + fetchingMemberList.current = true; + const newMembers: PoolMember[] = await fetchPoolMembers(poolId, page); + fetchingMemberList.current = false; + setPoolMembersApi([...newMembers]); + fetchPoolMembersMetaBatch(batchKey, newMembers, true); + setFetchedPoolMembersApi('synced'); + } + }; + + // get throttled subset or entire list + const listMembers = disableThrottle + ? poolMembersApi + : poolMembersApi.slice(pageStart).slice(0, ListItemsPerPage); + + // Refetch list when page changes. + useEffect(() => { + if (pluginEnabled('subscan')) { + setFetchedPoolMembersApi('unsynced'); + setPoolMembersApi([]); + } + }, [page, activeAccount, pluginEnabled('subscan')]); + + // Refetch list when network changes. + useEffect(() => { + setFetchedPoolMembersApi('unsynced'); + setPoolMembersApi([]); + setPage(1); + }, [network]); + + // Configure list when network is ready to fetch. + useEffect(() => { + if (fetchedPoolMembersApi === 'unsynced') { + setupMembersList(); + } + }, [fetchedPoolMembersApi, selectedActivePool]); + + // Render throttle. + useEffect(() => { + if (!(batchEnd >= pageEnd || disableThrottle)) { + setTimeout(() => { + setRenderIteration(renderIterationRef.current + 1); + }, 500); + } + }, [renderIterationRef.current]); + + return ( + <ListWrapper> + <Header> + <div /> + <div> + <button type="button" onClick={() => setListFormat('row')}> + <FontAwesomeIcon + icon={faBars} + color={listFormat === 'row' ? colors.primary[mode] : 'inherit'} + /> + </button> + <button type="button" onClick={() => setListFormat('col')}> + <FontAwesomeIcon + icon={faGripVertical} + color={listFormat === 'col' ? colors.primary[mode] : 'inherit'} + /> + </button> + </div> + </Header> + <List $flexBasisLarge={allowMoreCols ? '33.33%' : '50%'}> + {listMembers.length > 0 && pagination && ( + <Pagination page={page} total={totalPages} setter={setPage} /> + )} + {fetchedPoolMembersApi !== 'synced' ? ( + <ListStatusHeader style={{ marginTop: '0.5rem' }}> + {t('pools.fetchingMemberList')}.... + </ListStatusHeader> + ) : ( + <MotionContainer> + {listMembers.map((member: PoolMember, index: number) => ( + <motion.div + className={`item ${listFormat === 'row' ? 'row' : 'col'}`} + key={`nomination_${index}`} + variants={{ + hidden: { + y: 15, + opacity: 0, + }, + show: { + y: 0, + opacity: 1, + }, + }} + > + <Member + who={member.who} + batchKey={batchKey} + batchIndex={poolMembersApi.indexOf(member)} + /> + </motion.div> + ))} + </MotionContainer> + )} + </List> + </ListWrapper> + ); +}; + +export const MembersList = (props: FetchpageMembersListProps) => { + const { selectToggleable } = props; + + return ( + <ListProvider selectToggleable={selectToggleable}> + <MembersListInner {...props} /> + </ListProvider> + ); +}; diff --git a/src/pages/Pools/Home/MembersList/Member.tsx b/src/pages/Pools/Home/MembersList/Member.tsx index 887766325a..08c9029f23 100644 --- a/src/pages/Pools/Home/MembersList/Member.tsx +++ b/src/pages/Pools/Home/MembersList/Member.tsx @@ -1,19 +1,18 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import { IconProp } from '@fortawesome/fontawesome-svg-core'; import { faBars, faShare, faUnlockAlt, } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { useRef } from 'react'; +import { useTranslation } from 'react-i18next'; import { useMenu } from 'contexts/Menu'; -import { useModal } from 'contexts/Modal'; -import { useNetworkMetrics } from 'contexts/Network'; +import { useNetworkMetrics } from 'contexts/NetworkMetrics'; import { useActivePools } from 'contexts/Pools/ActivePools'; import { usePoolMembers } from 'contexts/Pools/PoolMembers'; -import { PoolState } from 'contexts/Pools/types'; import { useList } from 'library/List/context'; import { Identity } from 'library/ListItem/Labels/Identity'; import { PoolMemberBonded } from 'library/ListItem/Labels/PoolMemberBonded'; @@ -24,52 +23,48 @@ import { Separator, Wrapper, } from 'library/ListItem/Wrappers'; -import { useRef } from 'react'; -import { useTranslation } from 'react-i18next'; +import { useOverlay } from '@polkadot-cloud/react/hooks'; -export const Member = (props: any) => { +export const Member = ({ who, batchKey, batchIndex }: any) => { + const { t } = useTranslation('pages'); const { meta } = usePoolMembers(); - const { openModalWith } = useModal(); + const { openModal } = useOverlay().modal; const { selectActive } = useList(); - const { metrics } = useNetworkMetrics(); - const { selectedActivePool, isOwner, isStateToggler } = useActivePools(); + const { activeEra } = useNetworkMetrics(); + const { selectedActivePool, isOwner, isBouncer } = useActivePools(); const { setMenuPosition, setMenuItems, open }: any = useMenu(); - const { activeEra } = metrics; const { state, roles } = selectedActivePool?.bondedPool || {}; - const { stateToggler, root, depositor } = roles || {}; - const { t } = useTranslation('pages'); - - const { who, batchKey, batchIndex } = props; + const { bouncer, root, depositor } = roles || {}; const canUnbondBlocked = - state === PoolState.Block && - (isOwner() || isStateToggler()) && - ![root, stateToggler].includes(who); + state === 'Blocked' && + (isOwner() || isBouncer()) && + ![root, bouncer].includes(who); - const canUnbondDestroying = state === PoolState.Destroy && who !== depositor; + const canUnbondDestroying = state === 'Destroying' && who !== depositor; const poolMembers = meta[batchKey]?.poolMembers ?? []; const member = poolMembers[batchIndex] ?? null; - const menuItems: Array<any> = []; + const menuItems: any[] = []; if (member && (canUnbondBlocked || canUnbondDestroying)) { const { points, unbondingEras } = member; if (points !== '0') { menuItems.push({ - icon: <FontAwesomeIcon icon={faUnlockAlt as IconProp} />, + icon: <FontAwesomeIcon icon={faUnlockAlt} transform="shrink-3" />, wrap: null, - title: `${t('pools.unbond_funds')}`, + title: `${t('pools.unbondFunds')}`, cb: () => { - openModalWith( - 'UnbondPoolMember', - { + openModal({ + key: 'UnbondPoolMember', + options: { who, member, }, - 'small' - ); + size: 'sm', + }); }, }); } @@ -77,18 +72,22 @@ export const Member = (props: any) => { if (Object.values(unbondingEras).length) { let canWithdraw = false; for (const k of Object.keys(unbondingEras)) { - if (Number(activeEra.index) > Number(k)) { + if (activeEra.index.isGreaterThan(Number(k))) { canWithdraw = true; } } if (canWithdraw) { menuItems.push({ - icon: <FontAwesomeIcon icon={faShare as IconProp} />, + icon: <FontAwesomeIcon icon={faShare} transform="shrink-3" />, wrap: null, - title: `${t('pools.withdraw_funds')}`, + title: `${t('pools.withdrawFunds')}`, cb: () => { - openModalWith('WithdrawPoolMember', { who, member }, 'small'); + openModal({ + key: 'WithdrawPoolMember', + options: { who, member }, + size: 'sm', + }); }, }); } @@ -105,17 +104,12 @@ export const Member = (props: any) => { }; return ( - <Wrapper format="nomination"> + <Wrapper className="member"> <div className="inner"> <MenuPosition ref={posRef} /> - <div className="row"> + <div className="row top"> {selectActive && <Select item={who} />} - <Identity - meta={meta} - address={who} - batchIndex={batchIndex} - batchKey={batchKey} - /> + <Identity address={who} /> <div> <Labels> {menuItems.length > 0 && ( @@ -132,7 +126,7 @@ export const Member = (props: any) => { </div> </div> <Separator /> - <div className="row status"> + <div className="row bottom"> <PoolMemberBonded who={who} meta={meta} diff --git a/src/pages/Pools/Home/MembersList/index.tsx b/src/pages/Pools/Home/MembersList/index.tsx deleted file mode 100644 index 2f91b75174..0000000000 --- a/src/pages/Pools/Home/MembersList/index.tsx +++ /dev/null @@ -1,210 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { faBars, faGripVertical } from '@fortawesome/free-solid-svg-icons'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { ListItemsPerBatch, ListItemsPerPage } from 'consts'; -import { useApi } from 'contexts/Api'; -import { useNetworkMetrics } from 'contexts/Network'; -import { usePoolMembers } from 'contexts/Pools/PoolMembers'; -import { useTheme } from 'contexts/Themes'; -import { motion } from 'framer-motion'; -import { Header, List, Wrapper as ListWrapper } from 'library/List'; -import { ListProvider, useList } from 'library/List/context'; -import { MotionContainer } from 'library/List/MotionContainer'; -import { Pagination } from 'library/List/Pagination'; -import { Selectable } from 'library/List/Selectable'; -import { useEffect, useRef, useState } from 'react'; -import { networkColors } from 'theme/default'; -import { AnyApi, Sync } from 'types'; -import { Member } from './Member'; - -export const MembersListInner = (props: any) => { - const { allowMoreCols, pagination, selectable, batchKey } = props; - - const actions = props.actions ?? []; - - const { mode } = useTheme(); - const provider = useList(); - const { isReady, network } = useApi(); - const { metrics } = useNetworkMetrics(); - const { fetchPoolMembersMetaBatch } = usePoolMembers(); - - // get list provider props - const { selected, listFormat, setListFormat } = provider; - - // get actions - const actionsAll = [...actions].filter((action) => !action.onSelected); - const actionsSelected = [...actions].filter( - (action: any) => action.onSelected - ); - - const disableThrottle = props.disableThrottle ?? false; - - // current page - const [page, setPage] = useState<number>(1); - - // current render iteration - const [renderIteration, _setRenderIteration] = useState<number>(1); - - // default list of validators - const [membersDefault, setMembersDefault] = useState(props.members); - - // manipulated list (ordering, filtering) of payouts - const [members, setMembers] = useState(props.members); - - // is this the initial fetch - const [fetched, setFetched] = useState<Sync>(Sync.Unsynced); - - // render throttle iteration - const renderIterationRef = useRef(renderIteration); - const setRenderIteration = (iter: number) => { - renderIterationRef.current = iter; - _setRenderIteration(iter); - }; - - // pagination - const totalPages = Math.ceil(members.length / ListItemsPerPage); - const pageEnd = page * ListItemsPerPage - 1; - const pageStart = pageEnd - (ListItemsPerPage - 1); - - // render batch - const batchEnd = renderIteration * ListItemsPerBatch - 1; - - // refetch list when list changes - useEffect(() => { - if (props.members !== membersDefault) { - setFetched(Sync.Unsynced); - } - }, [props.members]); - - // configure list when network is ready to fetch - useEffect(() => { - if (isReady && metrics.activeEra.index !== 0 && fetched === Sync.Unsynced) { - setupMembersList(); - } - }, [isReady, fetched, metrics.activeEra.index]); - - // render throttle - useEffect(() => { - if (!(batchEnd >= pageEnd || disableThrottle)) { - setTimeout(() => { - setRenderIteration(renderIterationRef.current + 1); - }, 500); - } - }, [renderIterationRef.current]); - - // trigger onSelected when selection changes - useEffect(() => { - if (props.onSelected) { - props.onSelected(provider); - } - }, [selected]); - - // handle validator list bootstrapping - const setupMembersList = () => { - setMembersDefault(props.members); - setMembers(props.members); - fetchPoolMembersMetaBatch(batchKey, props.members, false); - setFetched(Sync.Synced); - }; - - // get list items to render - let listMembers = []; - - // get throttled subset or entire list - if (!disableThrottle) { - listMembers = members.slice(pageStart).slice(0, ListItemsPerPage); - } else { - listMembers = members; - } - - if (!members.length) { - return <></>; - } - - return ( - <ListWrapper> - <Header> - <div> - <h4>{props.title}</h4> - </div> - <div> - <button type="button" onClick={() => setListFormat('row')}> - <FontAwesomeIcon - icon={faBars} - color={ - listFormat === 'row' - ? networkColors[`${network.name}-${mode}`] - : 'inherit' - } - /> - </button> - <button type="button" onClick={() => setListFormat('col')}> - <FontAwesomeIcon - icon={faGripVertical} - color={ - listFormat === 'col' - ? networkColors[`${network.name}-${mode}`] - : 'inherit' - } - /> - </button> - </div> - </Header> - <List flexBasisLarge={allowMoreCols ? '33.33%' : '50%'}> - {listMembers.length > 0 && pagination && ( - <Pagination page={page} total={totalPages} setter={setPage} /> - )} - {selectable && ( - <Selectable - actionsAll={actionsAll} - actionsSelected={actionsSelected} - /> - )} - <MotionContainer> - {listMembers.map((member: AnyApi, index: number) => { - // fetch batch data by referring to default list index. - const batchIndex = membersDefault.indexOf(member); - - return ( - <motion.div - className={`item ${listFormat === 'row' ? 'row' : 'col'}`} - key={`nomination_${index}`} - variants={{ - hidden: { - y: 15, - opacity: 0, - }, - show: { - y: 0, - opacity: 1, - }, - }} - > - <Member - who={member.who} - batchKey={batchKey} - batchIndex={batchIndex} - /> - </motion.div> - ); - })} - </MotionContainer> - </List> - </ListWrapper> - ); -}; - -export const MembersList = (props: any) => { - const { selectActive, selectToggleable } = props; - - return ( - <ListProvider - selectActive={selectActive} - selectToggleable={selectToggleable} - > - <MembersListInner {...props} /> - </ListProvider> - ); -}; diff --git a/src/pages/Pools/Home/MembersList/types.ts b/src/pages/Pools/Home/MembersList/types.ts new file mode 100644 index 0000000000..59b7b1f438 --- /dev/null +++ b/src/pages/Pools/Home/MembersList/types.ts @@ -0,0 +1,18 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +export interface MembersListProps { + allowMoreCols: boolean; + pagination: boolean; + batchKey: string; + disableThrottle?: boolean; + selectToggleable?: boolean; +} + +export type DefaultMembersListProps = MembersListProps & { + members: any; +}; + +export type FetchpageMembersListProps = MembersListProps & { + memberCount: number; +}; diff --git a/src/pages/Pools/Home/PoolStats/Announcements.tsx b/src/pages/Pools/Home/PoolStats/Announcements.tsx index 262d92141e..dc2bc85d1d 100644 --- a/src/pages/Pools/Home/PoolStats/Announcements.tsx +++ b/src/pages/Pools/Home/PoolStats/Announcements.tsx @@ -1,50 +1,42 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only import { faBullhorn as faBack } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import BN from 'bn.js'; -import { useApi } from 'contexts/Api'; -import { useActivePools } from 'contexts/Pools/ActivePools'; -import { useUi } from 'contexts/UI'; +import { planckToUnit, rmCommas } from '@polkadot-cloud/utils'; +import BigNumber from 'bignumber.js'; import { motion } from 'framer-motion'; -import { Announcement as AnnouncementLoader } from 'library/Loaders/Announcement'; import { useTranslation } from 'react-i18next'; -import { - humanNumber, - planckBnToUnit, - rmCommas, - toFixedIfNecessary, -} from 'Utils'; +import { useApi } from 'contexts/Api'; +import { useActivePools } from 'contexts/Pools/ActivePools'; +import { Announcement as AnnouncementLoader } from 'library/Loader/Announcement'; +import { useNetwork } from 'contexts/Network'; import { Item } from './Wrappers'; export const Announcements = () => { - const { poolsSyncing } = useUi(); - const { network, consts } = useApi(); + const { t } = useTranslation('pages'); + const { consts } = useApi(); + const { + networkData: { units, unit }, + } = useNetwork(); const { selectedActivePool } = useActivePools(); - const { units } = network; const { rewardAccountBalance } = selectedActivePool || {}; const { totalRewardsClaimed } = selectedActivePool?.rewardPool || {}; const { existentialDeposit } = consts; - const { t } = useTranslation('pages'); // calculate the latest reward account balance - const rewardPoolBalance = BN.max( - new BN(0), - new BN(rewardAccountBalance).sub(existentialDeposit) - ); - const rewardBalance = toFixedIfNecessary( - planckBnToUnit(rewardPoolBalance, units), - 3 + const rewardPoolBalance = BigNumber.max( + 0, + new BigNumber(rewardAccountBalance).minus(existentialDeposit) ); + const rewardBalance = planckToUnit(rewardPoolBalance, units); // calculate total rewards claimed - const rewardsClaimed = toFixedIfNecessary( - planckBnToUnit( - totalRewardsClaimed ? new BN(rmCommas(totalRewardsClaimed)) : new BN(0), - network.units - ), - 3 + const rewardsClaimed = planckToUnit( + totalRewardsClaimed + ? new BigNumber(rmCommas(totalRewardsClaimed)) + : new BigNumber(0), + units ); const container = { @@ -67,25 +59,22 @@ export const Announcements = () => { }; const announcements = []; - const unit = network.unit; announcements.push({ class: 'neutral', - title: `${humanNumber(rewardsClaimed)} ${network.unit} ${t( - 'pools.been_claimed' + title: `${rewardsClaimed.decimalPlaces(3).toFormat()} ${unit} ${t( + 'pools.beenClaimed' )}`, - subtitle: `${t('pools.been_claimed_by', { unit })}`, + subtitle: `${t('pools.beenClaimedBy', { unit })}`, }); - if (rewardBalance > 0) { - announcements.push({ - class: 'neutral', - title: `${humanNumber(rewardBalance)} ${network.unit} ${t( - 'pools.outstanding_reward' - )}`, - subtitle: `${t('pools.available_to_claim', { unit })}`, - }); - } + announcements.push({ + class: 'neutral', + title: `${rewardBalance.decimalPlaces(3).toFormat()} ${unit} ${t( + 'pools.outstandingReward' + )}`, + subtitle: `${t('pools.availableToClaim', { unit })}`, + }); return ( <motion.div @@ -94,10 +83,10 @@ export const Announcements = () => { animate="show" style={{ width: '100%' }} > - {poolsSyncing ? ( - <AnnouncementLoader /> - ) : ( - announcements.map((item, index) => ( + {announcements.map((item, index) => + item === null ? ( + <AnnouncementLoader key={`announcement_${index}`} /> + ) : ( <Item key={`announcement_${index}`} variants={listItem}> <h4 className={item.class}> <FontAwesomeIcon @@ -108,7 +97,7 @@ export const Announcements = () => { </h4> <p>{item.subtitle}</p> </Item> - )) + ) )} </motion.div> ); diff --git a/src/pages/Pools/Home/PoolStats/Header.tsx b/src/pages/Pools/Home/PoolStats/Header.tsx deleted file mode 100644 index 0b67873941..0000000000 --- a/src/pages/Pools/Home/PoolStats/Header.tsx +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import BN from 'bn.js'; -import { useApi } from 'contexts/Api'; -import { useActivePools } from 'contexts/Pools/ActivePools'; -import { usePoolMembers } from 'contexts/Pools/PoolMembers'; -import { PoolState } from 'contexts/Pools/types'; -import { useTranslation } from 'react-i18next'; -import { planckBnToUnit, rmCommas, toFixedIfNecessary } from 'Utils'; -import { HeaderWrapper } from './Wrappers'; - -export const Header = () => { - const { network } = useApi(); - const { selectedActivePool } = useActivePools(); - const { getMembersOfPool } = usePoolMembers(); - const { t } = useTranslation('pages'); - - const { state, points } = selectedActivePool?.bondedPool || {}; - const poolMembers = getMembersOfPool(selectedActivePool?.id ?? 0); - - const bonded = toFixedIfNecessary( - planckBnToUnit( - points ? new BN(rmCommas(points)) : new BN(0), - network.units - ), - 3 - ); - - let stateDisplay; - switch (state) { - case PoolState.Block: - stateDisplay = t('pools.locked'); - break; - case PoolState.Destroy: - stateDisplay = t('pools.destroying'); - break; - default: - stateDisplay = t('pools.open'); - break; - } - - return ( - <HeaderWrapper> - <section> - <div className="items"> - <div> - <div className="inner"> - <h2>{stateDisplay}</h2> - <h4>{t('pools.pool_state')}</h4> - </div> - </div> - <div> - <div className="inner"> - <h2>{poolMembers.length}</h2> - <h4>{t('pools.pool_members')}</h4> - </div> - </div> - <div> - <div className="inner"> - <h2> - {bonded} {network.unit} - </h2> - <h4>{t('pools.total_bonded')}</h4> - </div> - </div> - </div> - </section> - </HeaderWrapper> - ); -}; diff --git a/src/pages/Pools/Home/PoolStats/Wrappers.ts b/src/pages/Pools/Home/PoolStats/Wrappers.ts index 2ffe5e915e..07f2c82a24 100644 --- a/src/pages/Pools/Home/PoolStats/Wrappers.ts +++ b/src/pages/Pools/Home/PoolStats/Wrappers.ts @@ -1,15 +1,8 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import { SmallFontSizeMaxWidth } from 'consts'; import { motion } from 'framer-motion'; import styled from 'styled-components'; -import { - borderPrimary, - networkColor, - networkColorSecondary, - textSecondary, -} from 'theme'; export const Wrapper = styled.div` flex: 1; @@ -19,15 +12,12 @@ export const Wrapper = styled.div` `; export const Item = styled(motion.div)` + border-bottom: 1px solid var(--border-primary-color); list-style: none; flex: 1; - flex-flow: row nowrap; - justify-content: flex-start; - align-items: flex-start; margin-bottom: 1rem; padding: 0.75rem; padding-bottom: 1.5rem; - border-bottom: 1px solid ${borderPrimary}; &:last-child { border-bottom: 0; @@ -42,7 +32,7 @@ export const Item = styled(motion.div)` padding-bottom: 0.2rem; &.neutral { - color: ${networkColor}; + color: var(--accent-color-primary); } &.danger { color: #d2545d; @@ -51,101 +41,13 @@ export const Item = styled(motion.div)` color: #b5a200; } &.pools { - color: ${networkColorSecondary}; + color: var(--accent-color-secondary); } } p { + color: var(--text-color-secondary); margin: 0; - color: ${textSecondary}; line-height: 1.2rem; } `; - -export const HeaderWrapper = styled.div` - width: 100%; - display: flex; - flex-flow: row wrap; - align-items: center; - margin-bottom: 0.75rem; - - h4 { - .help-icon { - margin-left: 0.6rem; - } - } - - > section { - display: flex; - flex-flow: column wrap; - justify-content: center; - flex-basis: 100%; - - .items { - flex-grow: 1; - display: flex; - flex-flow: row wrap; - align-items: center; - width: 100%; - - > div { - flex-grow: 1; - flex-basis: 100%; - width: 100%; - margin-bottom: 0.5rem; - border-right: 0; - - &:last-child { - border-right: 0; - } - - @media (min-width: ${SmallFontSizeMaxWidth + 150}px) { - flex-basis: 25%; - max-width: 275px; - padding-left: 1rem; - padding-right: 1rem; - margin-bottom: 0; - border-right: 1px solid ${borderPrimary}; - - &:last-child { - max-width: none; - } - } - - > .inner { - width: 100%; - padding: 0.5rem 0.5rem 1rem 0.5rem; - display: flex; - flex-flow: row nowrap; - border-bottom: 1px solid ${borderPrimary}; - - @media (min-width: ${SmallFontSizeMaxWidth + 150}px) { - margin-bottom: 0; - } - - h2 { - color: ${networkColor}; - margin-top: 0rem; - margin-bottom: 0; - } - h4 { - display: flex; - flex-flow: row wrap; - color: ${textSecondary}; - margin-top: 0.45rem; - margin-bottom: 0; - } - display: flex; - flex-flow: column wrap; - } - - &:first-child { - padding-left: 0; - } - &:last-child { - padding-right: 0; - } - } - } - } -`; diff --git a/src/pages/Pools/Home/PoolStats/index.tsx b/src/pages/Pools/Home/PoolStats/index.tsx index 2602847574..140c0b118d 100644 --- a/src/pages/Pools/Home/PoolStats/index.tsx +++ b/src/pages/Pools/Home/PoolStats/index.tsx @@ -1,22 +1,80 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import { CardHeaderWrapper, CardWrapper } from 'library/Graphs/Wrappers'; +import { planckToUnit, rmCommas } from '@polkadot-cloud/utils'; +import BigNumber from 'bignumber.js'; import { useTranslation } from 'react-i18next'; +import { useActivePools } from 'contexts/Pools/ActivePools'; +import { CardHeaderWrapper, CardWrapper } from 'library/Card/Wrappers'; +import { usePoolCommission } from 'library/Hooks/usePoolCommission'; +import { StatsHead } from 'library/StatsHead'; +import { useNetwork } from 'contexts/Network'; import { Announcements } from './Announcements'; -import { Header } from './Header'; import { Wrapper } from './Wrappers'; export const PoolStats = () => { const { t } = useTranslation('pages'); + const { + networkData: { units, unit }, + } = useNetwork(); + const { selectedActivePool, selectedPoolMemberCount } = useActivePools(); + const { getCurrentCommission } = usePoolCommission(); + + const { state, points } = selectedActivePool?.bondedPool || {}; + const currentCommission = getCurrentCommission(selectedActivePool?.id ?? 0); + + const bonded = planckToUnit( + new BigNumber(points ? rmCommas(points) : 0), + units + ) + .decimalPlaces(3) + .toFormat(); + + let stateDisplay; + switch (state) { + case 'Blocked': + stateDisplay = t('pools.locked'); + break; + case 'Destroying': + stateDisplay = t('pools.destroying'); + break; + default: + stateDisplay = t('pools.open'); + break; + } + + const items = [ + { + label: t('pools.poolState'), + value: stateDisplay, + }, + ]; + + if (currentCommission) { + items.push({ + label: t('pools.poolCommission'), + value: `${currentCommission}%`, + }); + } + + items.push( + { + label: t('pools.poolMembers'), + value: `${selectedPoolMemberCount}`, + }, + { + label: t('pools.totalBonded'), + value: `${bonded} ${unit}`, + } + ); return ( - <CardWrapper> - <CardHeaderWrapper> - <h3>{t('pools.pool_stats')}</h3> + <CardWrapper style={{ boxShadow: 'var(--card-shadow-secondary)' }}> + <CardHeaderWrapper $withMargin> + <h3>{t('pools.poolStats')}</h3> </CardHeaderWrapper> <Wrapper> - <Header /> + <StatsHead items={items} /> <Announcements /> </Wrapper> </CardWrapper> diff --git a/src/pages/Pools/Home/Stats/ActivePools.tsx b/src/pages/Pools/Home/Stats/ActivePools.tsx index 4b09ad63f5..c02f994def 100644 --- a/src/pages/Pools/Home/Stats/ActivePools.tsx +++ b/src/pages/Pools/Home/Stats/ActivePools.tsx @@ -1,21 +1,19 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only +import { useTranslation } from 'react-i18next'; import { usePoolsConfig } from 'contexts/Pools/PoolsConfig'; import { Number } from 'library/StatBoxList/Number'; -import { useTranslation } from 'react-i18next'; -const ActivePoolsStatBox = () => { - const { stats } = usePoolsConfig(); +export const ActivePoolsStat = () => { const { t } = useTranslation('pages'); + const { stats } = usePoolsConfig(); const params = { - label: t('pools.active_pools'), + label: t('pools.activePools'), value: stats.counterForBondedPools.toNumber(), unit: '', helpKey: 'Active Pools', }; return <Number {...params} />; }; - -export default ActivePoolsStatBox; diff --git a/src/pages/Pools/Home/Stats/MinCreateBond.tsx b/src/pages/Pools/Home/Stats/MinCreateBond.tsx index 97c58f942d..d3d0f08c15 100644 --- a/src/pages/Pools/Home/Stats/MinCreateBond.tsx +++ b/src/pages/Pools/Home/Stats/MinCreateBond.tsx @@ -1,25 +1,25 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import { useApi } from 'contexts/Api'; +import { planckToUnit } from '@polkadot-cloud/utils'; +import { useTranslation } from 'react-i18next'; import { usePoolsConfig } from 'contexts/Pools/PoolsConfig'; import { Number } from 'library/StatBoxList/Number'; -import { useTranslation } from 'react-i18next'; -import { planckBnToUnit } from 'Utils'; +import { useNetwork } from 'contexts/Network'; -const MinCreateBondStatBox = () => { - const { network } = useApi(); - const { units } = network; - const { stats } = usePoolsConfig(); +export const MinCreateBondStat = () => { const { t } = useTranslation('pages'); + const { + networkData: { units, unit }, + } = useNetwork(); + const { stats } = usePoolsConfig(); const params = { - label: t('pools.minimum_create_bond'), - value: planckBnToUnit(stats.minCreateBond, units), - unit: network.unit, - helpKey: 'Minimum Create Bond', + label: t('pools.minimumToCreatePool'), + value: planckToUnit(stats.minCreateBond, units).toNumber(), + decimals: 3, + unit, + helpKey: 'Minimum To Create Pool', }; return <Number {...params} />; }; - -export default MinCreateBondStatBox; diff --git a/src/pages/Pools/Home/Stats/MinJoinBond.tsx b/src/pages/Pools/Home/Stats/MinJoinBond.tsx index 647875f5ac..d844490c37 100644 --- a/src/pages/Pools/Home/Stats/MinJoinBond.tsx +++ b/src/pages/Pools/Home/Stats/MinJoinBond.tsx @@ -1,25 +1,25 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import { useApi } from 'contexts/Api'; +import { planckToUnit } from '@polkadot-cloud/utils'; +import { useTranslation } from 'react-i18next'; import { usePoolsConfig } from 'contexts/Pools/PoolsConfig'; import { Number } from 'library/StatBoxList/Number'; -import { useTranslation } from 'react-i18next'; -import { planckBnToUnit } from 'Utils'; +import { useNetwork } from 'contexts/Network'; -const MinJoinBondStatBox = () => { - const { network } = useApi(); - const { units } = network; - const { stats } = usePoolsConfig(); +export const MinJoinBondStat = () => { const { t } = useTranslation('pages'); + const { + networkData: { units, unit }, + } = useNetwork(); + const { stats } = usePoolsConfig(); const params = { - label: t('pools.minimum_join_bond'), - value: planckBnToUnit(stats.minJoinBond, units), - unit: network.unit, - helpKey: 'Minimum Join Bond', + label: t('pools.minimumToJoinPool'), + value: planckToUnit(stats.minJoinBond, units).toNumber(), + decimals: 3, + unit: ` ${unit}`, + helpKey: 'Minimum To Join Pool', }; return <Number {...params} />; }; - -export default MinJoinBondStatBox; diff --git a/src/pages/Pools/Home/Status/Membership/Wrapper.ts b/src/pages/Pools/Home/Status/Membership/Wrapper.ts deleted file mode 100644 index ff20c16876..0000000000 --- a/src/pages/Pools/Home/Status/Membership/Wrapper.ts +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import styled from 'styled-components'; - -interface WrapperProps { - paddingLeft: boolean; - paddingRight: string | null; -} - -export const Wrapper = styled.div<WrapperProps>` - display: flex; - flex-flow: row wrap; - - > .hide-with-padding { - padding-left: ${(props) => (props.paddingLeft ? '3rem' : '0')}; - padding-right: ${(props) => - props.paddingRight ? props.paddingRight : '0'}; - padding-top: 0.4rem; - padding-bottom: 0.4rem; - flex-shrink: 1; - text-overflow: ellipsis; - white-space: nowrap; - overflow: hidden; - position: relative; - margin-bottom: 0; - - .icon { - position: absolute; - left: 0; - top: 0; - display: flex; - flex-flow: row wrap; - align-items: center; - } - - .btn { - position: absolute; - right: 0; - top: 0; - padding: 0.1rem; - display: flex; - flex-flow: row wrap; - align-items: center; - width: ${(props) => props.paddingRight}; - > div { - margin-left: 0.75rem; - } - - > span { - padding-left: 0.8rem; - } - } - } -`; diff --git a/src/pages/Pools/Home/Status/Membership/index.tsx b/src/pages/Pools/Home/Status/Membership/index.tsx deleted file mode 100644 index c52affca08..0000000000 --- a/src/pages/Pools/Home/Status/Membership/index.tsx +++ /dev/null @@ -1,104 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { faCog, faSignOutAlt } from '@fortawesome/free-solid-svg-icons'; -import { ButtonPrimary } from '@rossbulat/polkadot-dashboard-ui'; -import { useApi } from 'contexts/Api'; -import { useConnect } from 'contexts/Connect'; -import { useModal } from 'contexts/Modal'; -import { useActivePools } from 'contexts/Pools/ActivePools'; -import { useBondedPools } from 'contexts/Pools/BondedPools'; -import { useTransferOptions } from 'contexts/TransferOptions'; -import { Identicon } from 'library/Identicon'; -import OpenHelpIcon from 'library/OpenHelpIcon'; -import { Wrapper as StatWrapper } from 'library/Stat/Wrapper'; -import { useTranslation } from 'react-i18next'; -import { determinePoolDisplay } from 'Utils'; -import { Wrapper } from './Wrapper'; - -export const Membership = ({ label }: { label: string }) => { - const { isReady } = useApi(); - const { activeAccount, isReadOnlyAccount } = useConnect(); - const { openModalWith } = useModal(); - const { bondedPools, meta } = useBondedPools(); - const { selectedActivePool, isDepositor, isOwner, isMember, isStateToggler } = - useActivePools(); - const { getTransferOptions } = useTransferOptions(); - const { active } = getTransferOptions(activeAccount).pool; - const { t } = useTranslation('pages'); - - let display = t('pools.not_in_pool'); - if (selectedActivePool) { - const pool = bondedPools.find((p: any) => { - return p.addresses.stash === selectedActivePool.addresses.stash; - }); - - if (pool) { - const metadata = meta.bonded_pools?.metadata ?? []; - const batchIndex = bondedPools.indexOf(pool); - display = determinePoolDisplay( - selectedActivePool.addresses.stash, - metadata[batchIndex] - ); - } - } - - const buttons = []; - let paddingRight = 0; - - if (isOwner() || isStateToggler()) { - paddingRight += 9; - buttons.push( - <ButtonPrimary - text={t('pools.manage')} - iconLeft={faCog} - disabled={!isReady || isReadOnlyAccount(activeAccount)} - onClick={() => openModalWith('ManagePool', {}, 'small')} - /> - ); - } - - if (isMember() && !isDepositor() && active?.gtn(0)) { - paddingRight += 8.5; - buttons.push( - <ButtonPrimary - text={t('pools.leave')} - iconLeft={faSignOutAlt} - disabled={!isReady || isReadOnlyAccount(activeAccount)} - onClick={() => - openModalWith('LeavePool', { bondType: 'pool' }, 'small') - } - /> - ); - } - - return ( - <StatWrapper> - <h4> - {label} - <OpenHelpIcon helpKey="Pool Membership" /> - </h4> - <Wrapper - paddingLeft={selectedActivePool !== null} - paddingRight={paddingRight === 0 ? null : `${String(paddingRight)}rem`} - > - <h2 className="hide-with-padding"> - <div className="icon"> - <Identicon - value={selectedActivePool?.addresses?.stash ?? ''} - size={26} - /> - </div> - {display} - {buttons.length > 0 && ( - <div className="btn"> - {buttons.map((b: any, i: number) => ( - <span key={i}>{b}</span> - ))} - </div> - )} - </h2> - </Wrapper> - </StatWrapper> - ); -}; diff --git a/src/pages/Pools/Home/Status/MembershipStatus.tsx b/src/pages/Pools/Home/Status/MembershipStatus.tsx new file mode 100644 index 0000000000..95496c118e --- /dev/null +++ b/src/pages/Pools/Home/Status/MembershipStatus.tsx @@ -0,0 +1,103 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { faCog } from '@fortawesome/free-solid-svg-icons'; +import { determinePoolDisplay } from '@polkadot-cloud/utils'; +import { useTranslation } from 'react-i18next'; +import { useApi } from 'contexts/Api'; +import { useActivePools } from 'contexts/Pools/ActivePools'; +import { useBondedPools } from 'contexts/Pools/BondedPools'; +import { useTransferOptions } from 'contexts/TransferOptions'; +import { useUi } from 'contexts/UI'; +import { Stat } from 'library/Stat'; +import { useOverlay } from '@polkadot-cloud/react/hooks'; +import { useActiveAccounts } from 'contexts/ActiveAccounts'; +import { useImportedAccounts } from 'contexts/Connect/ImportedAccounts'; +import { useStatusButtons } from './useStatusButtons'; + +export const MembershipStatus = ({ + showButtons = true, + buttonType = 'primary', +}: { + showButtons?: boolean; + buttonType?: string; +}) => { + const { t } = useTranslation('pages'); + const { isReady } = useApi(); + const { isPoolSyncing } = useUi(); + const { openModal } = useOverlay().modal; + const { activeAccount } = useActiveAccounts(); + const { label, buttons } = useStatusButtons(); + const { bondedPools, meta } = useBondedPools(); + const { isReadOnlyAccount } = useImportedAccounts(); + const { getTransferOptions } = useTransferOptions(); + const { selectedActivePool, isOwner, isBouncer, isMember } = useActivePools(); + + const { active } = getTransferOptions(activeAccount).pool; + const poolState = selectedActivePool?.bondedPool?.state ?? null; + + const membershipButtons = []; + let membershipDisplay = t('pools.notInPool'); + + if (selectedActivePool) { + const pool = bondedPools.find( + (p: any) => p.addresses.stash === selectedActivePool.addresses.stash + ); + if (pool) { + // Determine pool membership display. + const metadata = meta.bonded_pools?.metadata ?? []; + const batchIndex = bondedPools.indexOf(pool); + membershipDisplay = determinePoolDisplay( + selectedActivePool.addresses.stash, + metadata[batchIndex] + ); + } + + // Display manage button if active account is pool owner or bouncer. + // Or display manage button if active account is a pool member. + if ( + (poolState !== 'Destroying' && (isOwner() || isBouncer())) || + (isMember() && active?.isGreaterThan(0)) + ) { + membershipButtons.push({ + title: t('pools.manage'), + icon: faCog, + disabled: !isReady || isReadOnlyAccount(activeAccount), + small: true, + onClick: () => + openModal({ + key: 'ManagePool', + options: { disableWindowResize: true }, + size: 'sm', + }), + }); + } + } + + return ( + <> + {selectedActivePool ? ( + <> + <Stat + label={label} + helpKey="Pool Membership" + type="address" + stat={{ + address: selectedActivePool?.addresses?.stash ?? '', + display: membershipDisplay, + }} + buttons={showButtons ? membershipButtons : []} + /> + </> + ) : ( + <Stat + label={t('pools.poolMembership')} + helpKey="Pool Membership" + stat={t('pools.notInPool')} + buttons={!showButtons || isPoolSyncing ? [] : buttons} + buttonType={buttonType} + /> + )} + </> + ); +}; diff --git a/src/pages/Pools/Home/Status/PoolStatus.tsx b/src/pages/Pools/Home/Status/PoolStatus.tsx new file mode 100644 index 0000000000..ac0cb6ea18 --- /dev/null +++ b/src/pages/Pools/Home/Status/PoolStatus.tsx @@ -0,0 +1,67 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { + faExclamationTriangle, + faLock, +} from '@fortawesome/free-solid-svg-icons'; +import { useTranslation } from 'react-i18next'; +import { useActivePools } from 'contexts/Pools/ActivePools'; +import { useUi } from 'contexts/UI'; +import { useNominationStatus } from 'library/Hooks/useNominationStatus'; +import { Stat } from 'library/Stat'; + +export const PoolStatus = () => { + const { t } = useTranslation('pages'); + const { isPoolSyncing } = useUi(); + const { getNominationStatus } = useNominationStatus(); + const { selectedActivePool, poolNominations } = useActivePools(); + + const poolStash = selectedActivePool?.addresses?.stash || ''; + const { earningRewards, nominees } = getNominationStatus(poolStash, 'pool'); + const poolState = selectedActivePool?.bondedPool?.state ?? null; + const poolNominating = !!poolNominations?.targets?.length; + + // Determine pool state icon. + let poolStateIcon; + switch (poolState) { + case 'Blocked': + poolStateIcon = faLock; + break; + case 'Destroying': + poolStateIcon = faExclamationTriangle; + break; + default: + poolStateIcon = undefined; + } + + // Determine pool status - left side. + const poolStatusLeft = + poolState === 'Blocked' + ? `${t('pools.locked')} / ` + : poolState === 'Destroying' + ? `${t('pools.destroying')} / ` + : ''; + + // Determine pool status - right side. + const poolStatusRight = isPoolSyncing + ? t('pools.inactivePoolNotNominating') + : !poolNominating + ? t('pools.inactivePoolNotNominating') + : nominees.active.length + ? `${t('pools.nominatingAnd')} ${ + earningRewards + ? t('pools.earningRewards') + : t('pools.notEarningRewards') + }` + : t('pools.waitingForActiveNominations'); + + return ( + <Stat + icon={isPoolSyncing ? undefined : poolStateIcon} + label={t('pools.poolStatus')} + helpKey="Nomination Status" + stat={`${poolStatusLeft}${poolStatusRight}`} + /> + ); +}; diff --git a/src/pages/Pools/Home/Status/RewardsStatus.tsx b/src/pages/Pools/Home/Status/RewardsStatus.tsx new file mode 100644 index 0000000000..d700c47b18 --- /dev/null +++ b/src/pages/Pools/Home/Status/RewardsStatus.tsx @@ -0,0 +1,81 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { faCircleDown, faPlus } from '@fortawesome/free-solid-svg-icons'; +import { planckToUnit } from '@polkadot-cloud/utils'; +import BigNumber from 'bignumber.js'; +import { useTranslation } from 'react-i18next'; +import { useApi } from 'contexts/Api'; +import { useActivePools } from 'contexts/Pools/ActivePools'; +import { useUi } from 'contexts/UI'; +import { Stat } from 'library/Stat'; +import { useOverlay } from '@polkadot-cloud/react/hooks'; +import { useNetwork } from 'contexts/Network'; +import { useActiveAccounts } from 'contexts/ActiveAccounts'; +import { useImportedAccounts } from 'contexts/Connect/ImportedAccounts'; + +export const RewardsStatus = () => { + const { t } = useTranslation('pages'); + const { + networkData: { units }, + } = useNetwork(); + const { isReady } = useApi(); + const { isPoolSyncing } = useUi(); + const { openModal } = useOverlay().modal; + const { activeAccount } = useActiveAccounts(); + const { selectedActivePool } = useActivePools(); + const { isReadOnlyAccount } = useImportedAccounts(); + + let { pendingRewards } = selectedActivePool || {}; + pendingRewards = pendingRewards ?? new BigNumber(0); + + // Set the minimum unclaimed planck value to prevent e numbers. + const minUnclaimedDisplay = new BigNumber(1_000_000); + + const labelRewards = pendingRewards.isGreaterThan(minUnclaimedDisplay) + ? planckToUnit(pendingRewards, units).toString() + : '0'; + + // Display Reward buttons if unclaimed rewards is a non-zero value. + const buttonsRewards = pendingRewards.isGreaterThan(minUnclaimedDisplay) + ? [ + { + title: t('pools.withdraw'), + icon: faCircleDown, + disabled: !isReady || isReadOnlyAccount(activeAccount), + small: true, + onClick: () => + openModal({ + key: 'ClaimReward', + options: { claimType: 'withdraw' }, + size: 'sm', + }), + }, + { + title: t('pools.compound'), + icon: faPlus, + disabled: + !isReady || + isReadOnlyAccount(activeAccount) || + selectedActivePool?.bondedPool?.state === 'Destroying', + small: true, + onClick: () => + openModal({ + key: 'ClaimReward', + options: { claimType: 'bond' }, + size: 'sm', + }), + }, + ] + : undefined; + + return ( + <Stat + label={t('pools.unclaimedRewards')} + helpKey="Pool Rewards" + type="odometer" + stat={{ value: labelRewards }} + buttons={isPoolSyncing ? [] : buttonsRewards} + /> + ); +}; diff --git a/src/pages/Pools/Home/Status/index.tsx b/src/pages/Pools/Home/Status/index.tsx index b3c955a11a..0a2036aeeb 100644 --- a/src/pages/Pools/Home/Status/index.tsx +++ b/src/pages/Pools/Home/Status/index.tsx @@ -1,193 +1,27 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import { - faExclamationTriangle, - faLock, - faPlus, - faShare, -} from '@fortawesome/free-solid-svg-icons'; -import BN from 'bn.js'; -import { useApi } from 'contexts/Api'; -import { useConnect } from 'contexts/Connect'; -import { useModal } from 'contexts/Modal'; +import { Separator } from '@polkadot-cloud/react'; import { useActivePools } from 'contexts/Pools/ActivePools'; -import { PoolState } from 'contexts/Pools/types'; -import { useStaking } from 'contexts/Staking'; -import { useUi } from 'contexts/UI'; -import { useValidators } from 'contexts/Validators'; -import { CardWrapper } from 'library/Graphs/Wrappers'; -import { Stat } from 'library/Stat'; -import { useTranslation } from 'react-i18next'; -import { planckBnToUnit, rmCommas } from 'Utils'; -import { Separator } from 'Wrappers'; -import { Membership } from './Membership'; -import { useStatusButtons } from './useStatusButtons'; +import { CardWrapper } from 'library/Card/Wrappers'; +import { MembershipStatus } from './MembershipStatus'; +import { PoolStatus } from './PoolStatus'; +import { RewardsStatus } from './RewardsStatus'; export const Status = ({ height }: { height: number }) => { - const { network, isReady } = useApi(); - const { activeAccount, isReadOnlyAccount } = useConnect(); - const { units, unit } = network; - const { poolsSyncing } = useUi(); - const { selectedActivePool, poolNominations } = useActivePools(); - const { openModalWith } = useModal(); - const { getNominationsStatusFromTargets, eraStakers } = useStaking(); - const { meta, validators } = useValidators(); - const { stakers } = eraStakers; - const poolStash = selectedActivePool?.addresses?.stash || ''; - const { t } = useTranslation('pages'); - - const nominationStatuses = getNominationsStatusFromTargets( - poolStash, - poolNominations?.targets ?? [] - ); - - // determine pool state - const poolState = selectedActivePool?.bondedPool?.state ?? null; - - const activeNominees = Object.entries(nominationStatuses) - .map(([k, v]: any) => (v === 'active' ? k : false)) - .filter((v) => v !== false); - - const isNominating = !!poolNominations?.targets?.length; - - // Set the minimum unclaimed planck value to prevent e numbers - const minUnclaimedDisplay = new BN(1_000_000); - - // Unclaimed rewards `Stat` props - let { unclaimedRewards } = selectedActivePool || {}; - unclaimedRewards = unclaimedRewards ?? new BN(0); - - const labelRewards = unclaimedRewards.gt(minUnclaimedDisplay) - ? `${planckBnToUnit(unclaimedRewards, units)} ${unit}` - : `0 ${unit}`; - - const buttonsRewards = unclaimedRewards.gt(minUnclaimedDisplay) - ? [ - { - title: t('pools.withdraw'), - icon: faShare, - disabled: !isReady || isReadOnlyAccount(activeAccount), - small: true, - onClick: () => - openModalWith('ClaimReward', { claimType: 'withdraw' }, 'small'), - }, - { - title: t('pools.bond'), - icon: faPlus, - disabled: - !isReady || - isReadOnlyAccount(activeAccount) || - poolState === PoolState.Destroy, - small: true, - onClick: () => - openModalWith('ClaimReward', { claimType: 'bond' }, 'small'), - }, - ] - : undefined; - - let poolStateIcon; - switch (poolState) { - case PoolState.Block: - poolStateIcon = faLock; - break; - case PoolState.Destroy: - poolStateIcon = faExclamationTriangle; - break; - default: - poolStateIcon = undefined; - } - - // check if rewards are being earned - const stake = meta.validators_browse?.stake ?? []; - const stakeSynced = stake.length > 0 ?? false; - - let earningRewards = false; - if (stakeSynced) { - for (const nominee of activeNominees) { - const validator = validators.find((v: any) => v.address === nominee); - if (validator) { - const batchIndex = validators.indexOf(validator); - const nomineeMeta = stake[batchIndex]; - const { lowestReward } = nomineeMeta; - - const validatorInEra = - stakers.find((s: any) => s.address === nominee) || null; - - if (validatorInEra) { - const { others } = validatorInEra; - const stakedValue = - others?.find((o: any) => o.who === poolStash)?.value ?? false; - if (stakedValue) { - const stakedValueBase = planckBnToUnit( - new BN(rmCommas(stakedValue)), - network.units - ); - if (stakedValueBase >= lowestReward) { - earningRewards = true; - break; - } - } - } - } - } - } - - // determine pool status - left side - const poolStatusLeft = - poolState === PoolState.Block - ? `${t('pools.locked')} / ` - : poolState === PoolState.Destroy - ? `${t('pools.destroying')} / ` - : ''; - - // determine pool status - right side - const poolStatusRight = poolsSyncing - ? t('pools.inactive_pool_not_nominating') - : !isNominating - ? t('pools.inactive_pool_not_nominating') - : activeNominees.length - ? `${t('pools.nominating_and')} ${ - earningRewards - ? t('pools.earning_rewards') - : t('pools.not_earning_rewards') - }` - : t('pools.waiting_for_active_nominations'); - - const { label, buttons } = useStatusButtons(); + const { selectedActivePool } = useActivePools(); return ( <CardWrapper height={height}> - {selectedActivePool ? ( - <Membership label={label} /> - ) : ( - <Stat - label={t('pools.pool_membership')} - helpKey="Pool Membership" - stat={t('pools.not_in_pool')} - buttons={poolsSyncing ? [] : buttons} - /> - )} + <MembershipStatus /> <Separator /> - <Stat - label={t('pools.unclaimed_rewards')} - helpKey="Pool Rewards" - stat={labelRewards} - buttons={poolsSyncing ? [] : buttonsRewards} - /> + <RewardsStatus /> {selectedActivePool && ( <> <Separator /> - <Stat - icon={poolsSyncing ? undefined : poolStateIcon} - label={t('pools.pool_status')} - helpKey="Nomination Status" - stat={`${poolStatusLeft}${poolStatusRight}`} - /> + <PoolStatus /> </> )} </CardWrapper> ); }; - -export default Status; diff --git a/src/pages/Pools/Home/Status/useStatusButtons.tsx b/src/pages/Pools/Home/Status/useStatusButtons.tsx index 143e5f6877..0674328e89 100644 --- a/src/pages/Pools/Home/Status/useStatusButtons.tsx +++ b/src/pages/Pools/Home/Status/useStatusButtons.tsx @@ -1,55 +1,64 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only import { faPlusCircle, faUserPlus } from '@fortawesome/free-solid-svg-icons'; +import { useTranslation } from 'react-i18next'; import { useApi } from 'contexts/Api'; -import { useConnect } from 'contexts/Connect'; import { useActivePools } from 'contexts/Pools/ActivePools'; import { useBondedPools } from 'contexts/Pools/BondedPools'; import { usePoolMemberships } from 'contexts/Pools/PoolMemberships'; import { usePoolsConfig } from 'contexts/Pools/PoolsConfig'; +import { useSetup } from 'contexts/Setup'; import { useTransferOptions } from 'contexts/TransferOptions'; -import { useUi } from 'contexts/UI'; -import { useTranslation } from 'react-i18next'; +import { useActiveAccounts } from 'contexts/ActiveAccounts'; +import { useImportedAccounts } from 'contexts/Connect/ImportedAccounts'; import { usePoolsTabs } from '../context'; export const useStatusButtons = () => { + const { t } = useTranslation('pages'); const { isReady } = useApi(); - const { setOnPoolSetup, getPoolSetupProgressPercent } = useUi(); - const { activeAccount, isReadOnlyAccount } = useConnect(); const { stats } = usePoolsConfig(); - const { membership } = usePoolMemberships(); + const { isOwner } = useActivePools(); const { setActiveTab } = usePoolsTabs(); const { bondedPools } = useBondedPools(); - const { isOwner } = useActivePools(); + const { membership } = usePoolMemberships(); + const { activeAccount } = useActiveAccounts(); const { getTransferOptions } = useTransferOptions(); - const { t } = useTranslation('pages'); + const { isReadOnlyAccount } = useImportedAccounts(); + const { setOnPoolSetup, getPoolSetupPercent } = useSetup(); + const { maxPools } = stats; const { active } = getTransferOptions(activeAccount).pool; - const poolSetupPercent = getPoolSetupProgressPercent(activeAccount); + const poolSetupPercent = getPoolSetupPercent(activeAccount); + + const disableCreate = () => { + if (!isReady || isReadOnlyAccount(activeAccount) || !activeAccount) + return true; + if ( + maxPools && + (maxPools.isZero() || bondedPools.length === stats.maxPools?.toNumber()) + ) + return true; + return false; + }; - let _label; - let _buttons; + let label; + let buttons; const createBtn = { title: `${t('pools.create')}${ poolSetupPercent > 0 ? `: ${poolSetupPercent}%` : `` }`, icon: faPlusCircle, - large: true, + large: false, transform: 'grow-1', - disabled: - !isReady || - isReadOnlyAccount(activeAccount) || - !activeAccount || - stats.maxPools.toNumber() === 0 || - bondedPools.length === stats.maxPools.toNumber(), - onClick: () => setOnPoolSetup(1), + disabled: disableCreate(), + onClick: () => setOnPoolSetup(true), }; const joinPoolBtn = { title: `${t('pools.join')}`, icon: faUserPlus, - large: true, + large: false, transform: 'grow-1', disabled: !isReady || @@ -60,14 +69,14 @@ export const useStatusButtons = () => { }; if (!membership) { - _label = t('pools.pool_membership'); - _buttons = [createBtn, joinPoolBtn]; + label = t('pools.poolMembership'); + buttons = [createBtn, joinPoolBtn]; } else if (isOwner()) { - _label = `${t('pools.owner_of_pool')} ${membership.poolId}`; - } else if (active?.gtn(0)) { - _label = `${t('pools.member_of_pool')} ${membership.poolId}`; + label = `${t('pools.ownerOfPool')} ${membership.poolId}`; + } else if (active?.isGreaterThan(0)) { + label = `${t('pools.memberOfPool')} ${membership.poolId}`; } else { - _label = `${t('pools.leaving_pool')} ${membership.poolId}`; + label = `${t('pools.leavingPool')} ${membership.poolId}`; } - return { label: _label, buttons: _buttons }; + return { label, buttons }; }; diff --git a/src/pages/Pools/Home/context.tsx b/src/pages/Pools/Home/context.tsx index be70a73d1f..cb9c9f4c99 100644 --- a/src/pages/Pools/Home/context.tsx +++ b/src/pages/Pools/Home/context.tsx @@ -1,12 +1,13 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only +import { extractUrlValue } from '@polkadot-cloud/utils'; import React, { useState } from 'react'; -import { PoolsTabsContextInterface } from '../types'; +import type { PoolsTabsContextInterface } from '../types'; export const PoolsTabsContext: React.Context<PoolsTabsContextInterface> = React.createContext({ - // eslint-disable-next-line + // eslint-disable-next-line @typescript-eslint/no-unused-vars setActiveTab: (t: number) => {}, activeTab: 0, }); @@ -18,10 +19,15 @@ export const PoolsTabsProvider = ({ }: { children: React.ReactNode; }) => { - const [activeTab, _setActiveTab] = useState<number>(0); + const tabFromUrl = extractUrlValue('t'); + const initialActiveTab = [0, 1, 2, 3].includes(Number(tabFromUrl)) + ? Number(tabFromUrl) + : 0; + + const [activeTab, setActiveTabState] = useState<number>(initialActiveTab); const setActiveTab = (t: number) => { - _setActiveTab(t); + setActiveTabState(t); }; return ( diff --git a/src/pages/Pools/Home/index.tsx b/src/pages/Pools/Home/index.tsx index acdc37326e..f9de8fda5a 100644 --- a/src/pages/Pools/Home/index.tsx +++ b/src/pages/Pools/Home/index.tsx @@ -1,57 +1,48 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import { SectionFullWidthThreshold, SideMenuStickyThreshold } from 'consts'; -import { useConnect } from 'contexts/Connect'; -import { useModal } from 'contexts/Modal'; +import { PageRow, PageTitle, RowSection } from '@polkadot-cloud/react'; +import { useEffect } from 'react'; +import { useTranslation } from 'react-i18next'; +import type { PageTitleTabProps } from '@polkadot-cloud/react/types'; import { useActivePools } from 'contexts/Pools/ActivePools'; import { useBondedPools } from 'contexts/Pools/BondedPools'; -import { CardWrapper } from 'library/Graphs/Wrappers'; -import { PageTitle } from 'library/PageTitle'; -import { PoolList } from 'library/PoolList'; +import { CardWrapper } from 'library/Card/Wrappers'; +import { PoolList } from 'library/PoolList/Default'; import { StatBoxList } from 'library/StatBoxList'; -import { useEffect } from 'react'; -import { useTranslation } from 'react-i18next'; -import { - PageRowWrapper, - RowPrimaryWrapper, - RowSecondaryWrapper, -} from 'Wrappers'; +import { usePoolsConfig } from 'contexts/Pools/PoolsConfig'; +import { useOverlay } from '@polkadot-cloud/react/hooks'; +import { useActiveAccounts } from 'contexts/ActiveAccounts'; +import { PoolListProvider } from 'library/PoolList/context'; import { Roles } from '../Roles'; import { ClosurePrompts } from './ClosurePrompts'; -import { PoolsTabsProvider, usePoolsTabs } from './context'; -import { Favorites } from './Favorites'; +import { PoolFavorites } from './Favorites'; import { ManageBond } from './ManageBond'; import { ManagePool } from './ManagePool'; import { Members } from './Members'; import { PoolStats } from './PoolStats'; -import ActivePoolsStatBox from './Stats/ActivePools'; -import MinCreateBondStatBox from './Stats/MinCreateBond'; -import MinJoinBondStatBox from './Stats/MinJoinBond'; -import PoolMembershipBox from './Stats/PoolMembership'; +import { ActivePoolsStat } from './Stats/ActivePools'; +import { MinCreateBondStat } from './Stats/MinCreateBond'; +import { MinJoinBondStat } from './Stats/MinJoinBond'; import { Status } from './Status'; +import { PoolsTabsProvider, usePoolsTabs } from './context'; export const HomeInner = () => { - const { activeAccount } = useConnect(); - const { bondedPools, getAccountPools } = useBondedPools(); - const { getPoolRoles, selectedActivePool } = useActivePools(); - const { activeTab, setActiveTab } = usePoolsTabs(); - const { openModalWith } = useModal(); const { t } = useTranslation('pages'); - + const { openModal } = useOverlay().modal; + const { activeAccount } = useActiveAccounts(); + const { + favorites, + stats: { counterForBondedPools }, + } = usePoolsConfig(); + const { activeTab, setActiveTab } = usePoolsTabs(); + const { bondedPools, getAccountPools } = useBondedPools(); + const { getPoolRoles, selectedActivePool, selectedPoolMemberCount } = + useActivePools(); const accountPools = getAccountPools(activeAccount); const totalAccountPools = Object.entries(accountPools).length; - // back to tab 0 if not in a pool & on members tab - useEffect(() => { - if (!selectedActivePool && [1].includes(activeTab)) { - setActiveTab(0); - } - }, [selectedActivePool]); - - const ROW_HEIGHT = 275; - - let tabs = [ + let tabs: PageTitleTabProps[] = [ { title: t('pools.overview'), active: activeTab === 0, @@ -64,22 +55,34 @@ export const HomeInner = () => { title: t('pools.members'), active: activeTab === 1, onClick: () => setActiveTab(1), + badge: String(selectedPoolMemberCount), }); } tabs = tabs.concat( { - title: t('pools.all_pools'), + title: t('pools.allPools'), active: activeTab === 2, onClick: () => setActiveTab(2), + badge: String(counterForBondedPools.toString()), }, { title: t('pools.favorites'), active: activeTab === 3, onClick: () => setActiveTab(3), + badge: String(favorites.length), } ); + // Back to tab 0 if not in a pool & on members tab. + useEffect(() => { + if (!selectedActivePool && [1].includes(activeTab)) { + setActiveTab(0); + } + }, [selectedActivePool]); + + const ROW_HEIGHT = 220; + return ( <> <PageTitle @@ -88,9 +91,12 @@ export const HomeInner = () => { button={ totalAccountPools ? { - title: t('pools.all_roles'), + title: t('pools.allRoles'), onClick: () => - openModalWith('AccountPoolRoles', { who: activeAccount }), + openModal({ + key: 'AccountPoolRoles', + options: { who: activeAccount }, + }), } : undefined } @@ -98,47 +104,37 @@ export const HomeInner = () => { {activeTab === 0 && ( <> <StatBoxList> - <ActivePoolsStatBox /> - <MinJoinBondStatBox /> - <MinCreateBondStatBox /> + <ActivePoolsStat /> + <MinJoinBondStat /> + <MinCreateBondStat /> </StatBoxList> <ClosurePrompts /> - <PageRowWrapper className="page-padding" noVerticalSpacer> - <RowPrimaryWrapper - hOrder={1} - vOrder={0} - thresholdStickyMenu={SideMenuStickyThreshold} - thresholdFullWidth={SectionFullWidthThreshold} - > + <PageRow> + <RowSection hLast> <Status height={ROW_HEIGHT} /> - </RowPrimaryWrapper> - <RowSecondaryWrapper - hOrder={0} - vOrder={1} - thresholdStickyMenu={SideMenuStickyThreshold} - thresholdFullWidth={SectionFullWidthThreshold} - > + </RowSection> + <RowSection secondary> <CardWrapper height={ROW_HEIGHT}> <ManageBond /> </CardWrapper> - </RowSecondaryWrapper> - </PageRowWrapper> + </RowSection> + </PageRow> {selectedActivePool !== null && ( <> <ManagePool /> - <PageRowWrapper className="page-padding" noVerticalSpacer> + <PageRow> <CardWrapper> <Roles batchKey="pool_roles_manage" defaultRoles={getPoolRoles()} /> </CardWrapper> - </PageRowWrapper> - <PageRowWrapper className="page-padding" noVerticalSpacer> + </PageRow> + <PageRow> <PoolStats /> - </PageRowWrapper> + </PageRow> </> )} </> @@ -146,44 +142,36 @@ export const HomeInner = () => { {activeTab === 1 && <Members />} {activeTab === 2 && ( <> - <StatBoxList> - <PoolMembershipBox /> - <ActivePoolsStatBox /> - <MinJoinBondStatBox /> - </StatBoxList> - <PageRowWrapper className="page-padding" noVerticalSpacer> + <PageRow> <CardWrapper> - <PoolList - batchKey="bonded_pools" - pools={bondedPools} - title={t('pools.active_pools')} - defaultFilters={{ - includes: ['active'], - excludes: ['locked', 'destroying'], - }} - allowMoreCols - allowSearch - pagination - /> + <PoolListProvider> + <PoolList + batchKey="bonded_pools" + pools={bondedPools} + defaultFilters={{ + includes: ['active'], + excludes: ['locked', 'destroying'], + }} + allowMoreCols + allowSearch + pagination + /> + </PoolListProvider> </CardWrapper> - </PageRowWrapper> + </PageRow> </> )} {activeTab === 3 && ( <> - <Favorites /> + <PoolFavorites /> </> )} </> ); }; -export const Home = () => { - return ( - <PoolsTabsProvider> - <HomeInner /> - </PoolsTabsProvider> - ); -}; - -export default Home; +export const Home = () => ( + <PoolsTabsProvider> + <HomeInner /> + </PoolsTabsProvider> +); diff --git a/src/pages/Pools/PoolAccount/Wrapper.ts b/src/pages/Pools/PoolAccount/Wrapper.ts new file mode 100644 index 0000000000..a5ba8ccd3c --- /dev/null +++ b/src/pages/Pools/PoolAccount/Wrapper.ts @@ -0,0 +1,53 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import styled from 'styled-components'; + +export const Wrapper = styled.div` + width: 100%; + display: flex; + flex-flow: column wrap; + padding-bottom: 0.5rem; + margin-top: 1rem; + + .account { + width: 100%; + display: flex; + flex-flow: row wrap; + align-items: center; + padding: 0; + + button { + color: var(--text-color-primary); + } + + .icon { + position: relative; + top: 0.1rem; + margin-right: 0.5rem; + } + h4 { + padding: 0; + + > .addr { + opacity: 0.75; + } + } + + > :last-child { + display: flex; + flex-flow: row-reverse wrap; + margin-left: 0.5rem; + + > .copy { + color: var(--text-color-secondary); + cursor: pointer; + transition: opacity var(--transition-duration); + margin-left: 0.5rem; + &:hover { + opacity: 0.8; + } + } + } + } +`; diff --git a/src/pages/Pools/PoolAccount/index.tsx b/src/pages/Pools/PoolAccount/index.tsx index 795ec038aa..9c820a9b65 100644 --- a/src/pages/Pools/PoolAccount/index.tsx +++ b/src/pages/Pools/PoolAccount/index.tsx @@ -1,26 +1,27 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import { IconProp } from '@fortawesome/fontawesome-svg-core'; import { faCopy } from '@fortawesome/free-regular-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { useAccount } from 'contexts/Account'; -import { useNotifications } from 'contexts/Notifications'; -import { NotificationText } from 'contexts/Notifications/types'; +import { ellipsisFn, remToUnit } from '@polkadot-cloud/utils'; import { motion } from 'framer-motion'; -import { Identicon } from 'library/Identicon'; -import { getIdentityDisplay } from 'library/ValidatorList/Validator/Utils'; import { useTranslation } from 'react-i18next'; -import { clipAddress, convertRemToPixels } from 'Utils'; -import { PoolAccountProps } from '../types'; +import { useIdentities } from 'contexts/Identities'; +import { useNotifications } from 'contexts/Notifications'; +import type { NotificationText } from 'contexts/Notifications/types'; +import { Polkicon } from '@polkadot-cloud/react'; +import { getIdentityDisplay } from 'library/ValidatorList/ValidatorItem/Utils'; +import type { PoolAccountProps } from '../types'; import { Wrapper } from './Wrapper'; -export const PoolAccount = (props: PoolAccountProps) => { - const { address, last, batchKey, batchIndex } = props; - - const { addNotification } = useNotifications(); - const { meta } = useAccount(); +export const PoolAccount = ({ + address, + batchKey, + batchIndex, +}: PoolAccountProps) => { const { t } = useTranslation('pages'); + const { meta } = useIdentities(); + const { addNotification } = useNotifications(); const identities = meta[batchKey]?.identities ?? []; const supers = meta[batchKey]?.supers ?? []; @@ -37,13 +38,13 @@ export const PoolAccount = (props: PoolAccountProps) => { let notification: NotificationText | null = null; if (address !== null) { notification = { - title: 'Address Copied to Clipboard', + title: t('pools.addressCopied'), subtitle: address, }; } return ( - <Wrapper last={last}> + <Wrapper> <motion.div className="account" initial={{ opacity: 0.5 }} @@ -51,20 +52,20 @@ export const PoolAccount = (props: PoolAccountProps) => { transition={{ duration: 0.3 }} > {address === null ? ( - <h4>{t('pools.not_set')}</h4> + <h4>{t('pools.notSet')}</h4> ) : synced && display !== null ? ( <> <div className="icon"> - <Identicon value={address} size={convertRemToPixels('1.6rem')} /> + <Polkicon address={address} size={remToUnit('1.6rem')} /> </div> <h4>{display}</h4> </> ) : ( <> <div className="icon"> - <Identicon value={address} size={convertRemToPixels('1.6rem')} /> + <Polkicon address={address} size={remToUnit('1.6rem')} /> </div> - <h4>{clipAddress(address)}</h4> + <h4>{ellipsisFn(address)}</h4> </> )} <div> @@ -83,7 +84,7 @@ export const PoolAccount = (props: PoolAccountProps) => { } }} > - <FontAwesomeIcon icon={faCopy as IconProp} transform="grow-1" /> + <FontAwesomeIcon icon={faCopy} transform="shrink-2" /> </button> )} </motion.div> @@ -92,5 +93,3 @@ export const PoolAccount = (props: PoolAccountProps) => { </Wrapper> ); }; - -export default PoolAccount; diff --git a/src/pages/Pools/Roles/RoleEditInput/Wrapper.ts b/src/pages/Pools/Roles/RoleEditInput/Wrapper.ts index c1c95bb873..04d46482d3 100644 --- a/src/pages/Pools/Roles/RoleEditInput/Wrapper.ts +++ b/src/pages/Pools/Roles/RoleEditInput/Wrapper.ts @@ -1,15 +1,14 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only import styled from 'styled-components'; -import { borderPrimary, textDanger, textSecondary, textSuccess } from 'theme'; export const Wrapper = styled.div` width: 100%; margin-top: 0.5rem; .input { - border: 1px solid ${borderPrimary}; + border: 1px solid var(--border-primary-color); border-radius: 1rem; display: flex; flex-flow: row wrap; @@ -21,6 +20,7 @@ export const Wrapper = styled.div` flex-flow: column wrap; > input { + font-family: InterSemiBold, sans-serif; width: 100%; border: none; padding-right: 1rem; @@ -34,14 +34,14 @@ export const Wrapper = styled.div` h5 { margin: 0.75rem 0.25rem; &.neutral { - color: ${textSecondary}; + color: var(--text-color-secondary); opacity: 0.8; } &.danger { - color: ${textDanger}; + color: var(--status-danger-color); } &.success { - color: ${textSuccess}; + color: var(--status-success-color); } } `; diff --git a/src/pages/Pools/Roles/RoleEditInput/index.tsx b/src/pages/Pools/Roles/RoleEditInput/index.tsx index 30d813e4bd..98490320e9 100644 --- a/src/pages/Pools/Roles/RoleEditInput/index.tsx +++ b/src/pages/Pools/Roles/RoleEditInput/index.tsx @@ -1,15 +1,18 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import { useConnect } from 'contexts/Connect'; +import { isValidAddress } from '@polkadot-cloud/utils'; import React from 'react'; import { useTranslation } from 'react-i18next'; -import { isValidAddress } from 'Utils'; +import { useNetwork } from 'contexts/Network'; +import { formatAccountSs58 } from 'contexts/Connect/Utils'; import { Wrapper } from './Wrapper'; export const RoleEditInput = ({ setRoleEdit, roleKey, roleEdit }: any) => { - const { formatAccountSs58 } = useConnect(); const { t } = useTranslation('pages'); + const { + networkData: { ss58 }, + } = useNetwork(); const processRoleEdit = (newAddress: string) => { let edit = { @@ -18,7 +21,7 @@ export const RoleEditInput = ({ setRoleEdit, roleKey, roleEdit }: any) => { reformatted: false, }; if (isValidAddress(newAddress)) { - const addressFormatted = formatAccountSs58(newAddress); + const addressFormatted = formatAccountSs58(newAddress, ss58); if (addressFormatted) { edit = { newAddress: addressFormatted, @@ -42,7 +45,7 @@ export const RoleEditInput = ({ setRoleEdit, roleKey, roleEdit }: any) => { let label; let labelClass; if (!roleEdit?.valid) { - label = t('pools.address_invalid'); + label = t('pools.addressInvalid'); labelClass = 'danger'; } else if (roleEdit?.reformatted) { label = t('pools.reformatted'); @@ -54,7 +57,7 @@ export const RoleEditInput = ({ setRoleEdit, roleKey, roleEdit }: any) => { <div className="input"> <section> <input - placeholder="Address" + placeholder={t('pools.address')} type="text" onChange={(e: React.FormEvent<HTMLInputElement>) => handleChange(e)} value={roleEdit?.newAddress ?? ''} @@ -65,5 +68,3 @@ export const RoleEditInput = ({ setRoleEdit, roleKey, roleEdit }: any) => { </Wrapper> ); }; - -export default RoleEditInput; diff --git a/src/pages/Pools/Roles/index.tsx b/src/pages/Pools/Roles/index.tsx index 68a1091ef7..8518007c21 100644 --- a/src/pages/Pools/Roles/index.tsx +++ b/src/pages/Pools/Roles/index.tsx @@ -1,40 +1,50 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only import { faCheckCircle, faEdit, faTimesCircle, } from '@fortawesome/free-solid-svg-icons'; -import { ButtonPrimary } from '@rossbulat/polkadot-dashboard-ui'; -import { useAccount } from 'contexts/Account'; +import { + ButtonHelp, + ButtonPrimary, + ButtonPrimaryInvert, +} from '@polkadot-cloud/react'; +import { useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; import { useApi } from 'contexts/Api'; -import { useConnect } from 'contexts/Connect'; -import { useModal } from 'contexts/Modal'; +import { useHelp } from 'contexts/Help'; +import { useIdentities } from 'contexts/Identities'; import { useActivePools } from 'contexts/Pools/ActivePools'; import { useUi } from 'contexts/UI'; -import { CardHeaderWrapper } from 'library/Graphs/Wrappers'; -import { OpenHelpIcon } from 'library/OpenHelpIcon'; -import { useEffect, useState } from 'react'; -import { useTranslation } from 'react-i18next'; +import { CardHeaderWrapper } from 'library/Card/Wrappers'; +import { useOverlay } from '@polkadot-cloud/react/hooks'; +import { useNetwork } from 'contexts/Network'; +import { useActiveAccounts } from 'contexts/ActiveAccounts'; +import { useImportedAccounts } from 'contexts/Connect/ImportedAccounts'; import { RolesWrapper } from '../Home/ManagePool/Wrappers'; import { PoolAccount } from '../PoolAccount'; -import RoleEditInput from './RoleEditInput'; -import { RoleEditEntry, RolesProps } from './types'; +import { RoleEditInput } from './RoleEditInput'; +import type { RoleEditEntry, RolesProps } from './types'; -export const Roles = (props: RolesProps) => { - const { batchKey, defaultRoles } = props; +export const Roles = ({ + batchKey, + defaultRoles, + setters = [], + inline = false, + listenIsValid = () => {}, +}: RolesProps) => { const { t } = useTranslation('pages'); - - const listenIsValid = props.listenIsValid ?? (() => {}); - const setters = props.setters ?? []; - - const { isReady, network } = useApi(); - const { activeAccount, isReadOnlyAccount } = useConnect(); - const { fetchAccountMetaBatch } = useAccount(); + const { isReady } = useApi(); + const { openHelp } = useHelp(); + const { network } = useNetwork(); + const { isPoolSyncing } = useUi(); + const { openModal } = useOverlay().modal; + const { activeAccount } = useActiveAccounts(); + const { isReadOnlyAccount } = useImportedAccounts(); + const { fetchIdentitiesMetaBatch } = useIdentities(); const { isOwner, selectedActivePool } = useActivePools(); - const { poolsSyncing } = useUi(); - const { openModalWith } = useModal(); const { id } = selectedActivePool || { id: 0 }; const roles = defaultRoles; @@ -75,7 +85,7 @@ export const Roles = (props: RolesProps) => { useEffect(() => { if (isReady && !fetched) { setFetched(true); - fetchAccountMetaBatch(batchKey, Object.values(roles), true); + fetchIdentitiesMetaBatch(batchKey, Object.values(roles), true); } }, [isReady, fetched]); @@ -110,7 +120,11 @@ export const Roles = (props: RolesProps) => { } } else { // else, open modal with role edits data to update pool roles. - openModalWith('ChangePoolRoles', { id, roleEdits }, 'small'); + openModal({ + key: 'ChangePoolRoles', + options: { id, roleEdits }, + size: 'sm', + }); } }; @@ -135,35 +149,41 @@ export const Roles = (props: RolesProps) => { setRoleEdits(newEdit); }; + const ButtonType = inline ? ButtonPrimaryInvert : ButtonPrimary; + return ( <> - <CardHeaderWrapper withAction> - <h3> - {t('pools.roles')} <OpenHelpIcon helpKey="Pool Roles" /> - </h3> + <CardHeaderWrapper $withAction $withMargin> + {!inline && ( + <h3> + {t('pools.roles')} + <ButtonHelp marginLeft onClick={() => openHelp('Pool Roles')} /> + </h3> + )} + {!(isOwner() === true || setters.length) ? ( <></> ) : ( <> {isEditing && ( <div> - <ButtonPrimary + <ButtonType iconLeft={faTimesCircle} iconTransform="grow-1" text={t('pools.cancel')} - disabled={poolsSyncing || isReadOnlyAccount(activeAccount)} + disabled={isPoolSyncing || isReadOnlyAccount(activeAccount)} onClick={() => cancelHandler()} /> </div> )}    <div> - <ButtonPrimary + <ButtonType iconLeft={isEditing ? faCheckCircle : faEdit} iconTransform="grow-1" text={isEditing ? t('pools.save') : t('pools.edit')} disabled={ - poolsSyncing || + isPoolSyncing || isReadOnlyAccount(activeAccount) || !isRoleEditsValid() } @@ -222,19 +242,18 @@ export const Roles = (props: RolesProps) => { </section> <section> <div className="inner"> - <h4>{t('pools.state_toggler')}</h4> + <h4>{t('pools.bouncer')}</h4> {isEditing ? ( <RoleEditInput - roleKey="stateToggler" - roleEdit={roleEdits?.stateToggler} + roleKey="bouncer" + roleEdit={roleEdits?.bouncer} setRoleEdit={setRoleEditHandler} /> ) : ( <PoolAccount - address={roles.stateToggler ?? null} - batchIndex={accounts.indexOf(roles.stateToggler ?? '-1')} + address={roles.bouncer ?? null} + batchIndex={accounts.indexOf(roles.bouncer ?? '-1')} batchKey={batchKey} - last /> )} </div> diff --git a/src/pages/Pools/Roles/types.ts b/src/pages/Pools/Roles/types.ts index 12cc176714..326001e1dd 100644 --- a/src/pages/Pools/Roles/types.ts +++ b/src/pages/Pools/Roles/types.ts @@ -1,13 +1,14 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import { PoolRoles } from 'contexts/Pools/types'; +import type { PoolRoles } from 'contexts/Pools/types'; export interface RolesProps { batchKey: string; defaultRoles: PoolRoles; listenIsValid?: any; setters?: any; + inline?: boolean; } export type RoleEditEntry = { diff --git a/src/pages/Pools/index.tsx b/src/pages/Pools/index.tsx index 209384b9bc..129eb2ed3c 100644 --- a/src/pages/Pools/index.tsx +++ b/src/pages/Pools/index.tsx @@ -1,15 +1,11 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -// import { useUi } from 'contexts/UI'; -import { useUi } from 'contexts/UI'; +import { useSetup } from 'contexts/Setup'; import { Create } from './Create'; import { Home } from './Home'; -export const Stake = () => { - const { onPoolSetup } = useUi(); - +export const Pools = () => { + const { onPoolSetup } = useSetup(); return <>{onPoolSetup ? <Create /> : <Home />}</>; }; - -export default Stake; diff --git a/src/pages/Pools/types.ts b/src/pages/Pools/types.ts index 77daa96618..0853c7dd1f 100644 --- a/src/pages/Pools/types.ts +++ b/src/pages/Pools/types.ts @@ -1,9 +1,8 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only export interface PoolAccountProps { address: string | null; - last?: boolean; batchKey: string; batchIndex: number; } diff --git a/src/pages/Validators/AllValidators.tsx b/src/pages/Validators/AllValidators.tsx new file mode 100644 index 0000000000..e8414300ff --- /dev/null +++ b/src/pages/Validators/AllValidators.tsx @@ -0,0 +1,70 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { PageRow } from '@polkadot-cloud/react'; +import { useTranslation } from 'react-i18next'; +import { useApi } from 'contexts/Api'; +import { useValidators } from 'contexts/Validators/ValidatorEntries'; +import { CardWrapper } from 'library/Card/Wrappers'; +import { StatBoxList } from 'library/StatBoxList'; +import { ValidatorList } from 'library/ValidatorList'; +import { ActiveValidatorsStat } from './Stats/ActiveValidators'; +import { AverageCommissionStat } from './Stats/AverageCommission'; +import { TotalValidatorsStat } from './Stats/TotalValidators'; + +export const AllValidators = () => { + const { t } = useTranslation('pages'); + const { isReady } = useApi(); + const { validators } = useValidators(); + + return ( + <> + <StatBoxList> + <ActiveValidatorsStat /> + <TotalValidatorsStat /> + <AverageCommissionStat /> + </StatBoxList> + <PageRow> + <CardWrapper> + {!isReady ? ( + <div className="item"> + <h3>{t('validators.connecting')}...</h3> + </div> + ) : ( + <> + {validators.length === 0 && ( + <div className="item"> + <h3>{t('validators.fetchingValidators')}...</h3> + </div> + )} + + {validators.length > 0 && ( + <ValidatorList + bondFor="nominator" + validators={validators} + title={t('validators.networkValidators')} + selectable={false} + defaultFilters={{ + includes: ['active'], + excludes: [ + 'all_commission', + 'blocked_nominations', + 'missing_identity', + ], + }} + defaultOrder="rank" + allowListFormat={false} + allowMoreCols + allowFilters + allowSearch + pagination + toggleFavorites + /> + )} + </> + )} + </CardWrapper> + </PageRow> + </> + ); +}; diff --git a/src/pages/Validators/Favorites.tsx b/src/pages/Validators/Favorites.tsx new file mode 100644 index 0000000000..9f108addda --- /dev/null +++ b/src/pages/Validators/Favorites.tsx @@ -0,0 +1,45 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { PageRow } from '@polkadot-cloud/react'; +import { useTranslation } from 'react-i18next'; +import { useApi } from 'contexts/Api'; +import { CardWrapper } from 'library/Card/Wrappers'; +import { ValidatorList } from 'library/ValidatorList'; +import { useFavoriteValidators } from 'contexts/Validators/FavoriteValidators'; +import { ListStatusHeader } from 'library/List'; + +export const ValidatorFavorites = () => { + const { t } = useTranslation('pages'); + const { isReady } = useApi(); + const { favoritesList } = useFavoriteValidators(); + + return ( + <PageRow> + <CardWrapper> + {favoritesList === null ? ( + <ListStatusHeader> + {t('validators.fetchingFavoriteValidators')}... + </ListStatusHeader> + ) : ( + isReady && + (favoritesList.length > 0 ? ( + <ValidatorList + bondFor="nominator" + validators={favoritesList} + title={t('validators.favoriteValidators')} + selectable={false} + allowListFormat={false} + allowFilters + refetchOnListUpdate + allowMoreCols + toggleFavorites + /> + ) : ( + <ListStatusHeader>{t('validators.noFavorites')}</ListStatusHeader> + )) + )} + </CardWrapper> + </PageRow> + ); +}; diff --git a/src/pages/Validators/Stats/ActiveValidators.tsx b/src/pages/Validators/Stats/ActiveValidators.tsx index 6c09d258cd..5accb0fcd8 100644 --- a/src/pages/Validators/Stats/ActiveValidators.tsx +++ b/src/pages/Validators/Stats/ActiveValidators.tsx @@ -1,27 +1,29 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import BN from 'bn.js'; +import { greaterThanZero } from '@polkadot-cloud/utils'; +import BigNumber from 'bignumber.js'; +import { useTranslation } from 'react-i18next'; import { useStaking } from 'contexts/Staking'; import { Pie } from 'library/StatBoxList/Pie'; -import { useTranslation } from 'react-i18next'; -import { toFixedIfNecessary } from 'Utils'; -const ActiveValidatorsStatBox = () => { - const { staking, eraStakers } = useStaking(); - const { validatorCount } = staking; - const { activeValidators } = eraStakers; +export const ActiveValidatorsStat = () => { const { t } = useTranslation('pages'); + const { + staking: { validatorCount }, + eraStakers: { activeValidators }, + } = useStaking(); - // active validators as percent - let activeValidatorsAsPercent = 0; - if (validatorCount.gt(new BN(0))) { - activeValidatorsAsPercent = - activeValidators / (validatorCount.toNumber() * 0.01); + // active validators as percent. Avoiding dividing by zero. + let activeValidatorsAsPercent = new BigNumber(0); + if (greaterThanZero(validatorCount)) { + activeValidatorsAsPercent = new BigNumber(activeValidators).dividedBy( + validatorCount.multipliedBy(0.01) + ); } const params = { - label: t('validators.active_validators'), + label: t('validators.activeValidators'), stat: { value: activeValidators, total: validatorCount.toNumber(), @@ -29,13 +31,11 @@ const ActiveValidatorsStatBox = () => { }, graph: { value1: activeValidators, - value2: validatorCount.sub(new BN(activeValidators)).toNumber(), + value2: validatorCount.minus(activeValidators).toNumber(), }, - tooltip: `${toFixedIfNecessary(activeValidatorsAsPercent, 2)}%`, + tooltip: `${activeValidatorsAsPercent.decimalPlaces(2).toFormat()}%`, helpKey: 'Active Validator', }; return <Pie {...params} />; }; - -export default ActiveValidatorsStatBox; diff --git a/src/pages/Validators/Stats/AverageCommission.tsx b/src/pages/Validators/Stats/AverageCommission.tsx index bdc25c87db..77c8c8f7df 100644 --- a/src/pages/Validators/Stats/AverageCommission.tsx +++ b/src/pages/Validators/Stats/AverageCommission.tsx @@ -1,20 +1,18 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import { useValidators } from 'contexts/Validators'; -import { Text } from 'library/StatBoxList/Text'; import { useTranslation } from 'react-i18next'; +import { useValidators } from 'contexts/Validators/ValidatorEntries'; +import { Text } from 'library/StatBoxList/Text'; -export const AverageCommission = () => { - const { avgCommission } = useValidators(); +export const AverageCommissionStat = () => { const { t } = useTranslation('pages'); + const { avgCommission } = useValidators(); const params = { - label: t('validators.average_commission'), + label: t('validators.averageCommission'), value: `${String(avgCommission)}%`, helpKey: 'Average Commission', }; return <Text {...params} />; }; - -export default AverageCommission; diff --git a/src/pages/Validators/Stats/TotalValidators.tsx b/src/pages/Validators/Stats/TotalValidators.tsx index e87af32d40..5481a09442 100644 --- a/src/pages/Validators/Stats/TotalValidators.tsx +++ b/src/pages/Validators/Stats/TotalValidators.tsx @@ -1,27 +1,27 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import BN from 'bn.js'; +import { greaterThanZero } from '@polkadot-cloud/utils'; +import BigNumber from 'bignumber.js'; +import { useTranslation } from 'react-i18next'; import { useStaking } from 'contexts/Staking'; import { Pie } from 'library/StatBoxList/Pie'; -import { useTranslation } from 'react-i18next'; -import { toFixedIfNecessary } from 'Utils'; -const TotalValidatorsStatBox = () => { +export const TotalValidatorsStat = () => { + const { t } = useTranslation('pages'); const { staking } = useStaking(); const { totalValidators, maxValidatorsCount } = staking; - const { t } = useTranslation('pages'); // total validators as percent let totalValidatorsAsPercent = 0; - if (maxValidatorsCount.gt(new BN(0))) { + if (greaterThanZero(maxValidatorsCount)) { totalValidatorsAsPercent = totalValidators - .div(maxValidatorsCount.div(new BN(100))) + .div(maxValidatorsCount.dividedBy(100)) .toNumber(); } const params = { - label: t('validators.total_validators'), + label: t('validators.totalValidators'), stat: { value: totalValidators.toNumber(), total: maxValidatorsCount.toNumber(), @@ -29,12 +29,12 @@ const TotalValidatorsStatBox = () => { }, graph: { value1: totalValidators.toNumber(), - value2: maxValidatorsCount.sub(totalValidators).toNumber(), + value2: maxValidatorsCount.minus(totalValidators).toNumber(), }, - tooltip: `${toFixedIfNecessary(totalValidatorsAsPercent, 2)}%`, + tooltip: `${new BigNumber(totalValidatorsAsPercent) + .decimalPlaces(2) + .toFormat()}%`, helpKey: 'Validator', }; return <Pie {...params} />; }; - -export default TotalValidatorsStatBox; diff --git a/src/pages/Validators/context.tsx b/src/pages/Validators/context.tsx new file mode 100644 index 0000000000..654ef46a90 --- /dev/null +++ b/src/pages/Validators/context.tsx @@ -0,0 +1,47 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { extractUrlValue } from '@polkadot-cloud/utils'; +import React, { useState } from 'react'; + +export interface ValidatorsTabsContextInterface { + setActiveTab: (t: number) => void; + activeTab: number; +} + +export const ValidatorsTabsContext: React.Context<ValidatorsTabsContextInterface> = + React.createContext({ + // eslint-disable-next-line + setActiveTab: (t: number) => {}, + activeTab: 0, + }); + +export const useValidatorsTabs = () => React.useContext(ValidatorsTabsContext); + +export const ValidatorsTabsProvider = ({ + children, +}: { + children: React.ReactNode; +}) => { + const tabFromUrl = extractUrlValue('t'); + const initialActiveTab = [0, 1].includes(Number(tabFromUrl)) + ? Number(tabFromUrl) + : 0; + + const [activeTab, setActiveTabState] = useState<number>(initialActiveTab); + + const setActiveTab = (t: number) => { + setActiveTabState(t); + }; + + return ( + <ValidatorsTabsContext.Provider + value={{ + activeTab, + setActiveTab, + }} + > + {children} + </ValidatorsTabsContext.Provider> + ); +}; diff --git a/src/pages/Validators/index.tsx b/src/pages/Validators/index.tsx index 79227ee378..735c757b00 100644 --- a/src/pages/Validators/index.tsx +++ b/src/pages/Validators/index.tsx @@ -1,76 +1,53 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import { useApi } from 'contexts/Api'; -import { useValidators } from 'contexts/Validators'; -import { CardWrapper } from 'library/Graphs/Wrappers'; -import { PageTitle } from 'library/PageTitle'; -import { StatBoxList } from 'library/StatBoxList'; -import { ValidatorList } from 'library/ValidatorList'; +import { PageTitle } from '@polkadot-cloud/react'; +import { useEffect } from 'react'; import { useTranslation } from 'react-i18next'; -import { PageRowWrapper } from 'Wrappers'; -import { PageProps } from '../types'; -import ActiveValidatorsStatBox from './Stats/ActiveValidators'; -import AverageCommissionStatBox from './Stats/AverageCommission'; -import TotalValidatorsStatBox from './Stats/TotalValidators'; +import { useFavoriteValidators } from 'contexts/Validators/FavoriteValidators'; +import type { PageTitleTabProps } from '@polkadot-cloud/react/types'; +import { AllValidators } from './AllValidators'; +import { ValidatorFavorites } from './Favorites'; +import { ValidatorsTabsProvider, useValidatorsTabs } from './context'; -export const Validators = (props: PageProps) => { - const { page } = props; - const { key } = page; +export const ValidatorsInner = () => { + const { t } = useTranslation('pages'); + const { favorites } = useFavoriteValidators(); + const { activeTab, setActiveTab } = useValidatorsTabs(); - const { isReady } = useApi(); - const { validators } = useValidators(); - const { t } = useTranslation(); - const defaultFilters = { - includes: ['active'], - excludes: ['all_commission', 'blocked_nominations', 'missing_identity'], - }; + // back to tab 0 if not in the first tab + useEffect(() => { + if (![0].includes(activeTab)) { + setActiveTab(0); + } + }, []); + + let tabs: PageTitleTabProps[] = [ + { + title: t('validators.allValidators'), + active: activeTab === 0, + onClick: () => setActiveTab(0), + }, + ]; + + tabs = tabs.concat({ + title: t('validators.favorites'), + active: activeTab === 1, + onClick: () => setActiveTab(1), + badge: String(favorites.length), + }); return ( <> - <PageTitle title={t(key, { ns: 'base' })} /> - <StatBoxList> - <TotalValidatorsStatBox /> - <ActiveValidatorsStatBox /> - <AverageCommissionStatBox /> - </StatBoxList> - <PageRowWrapper className="page-padding" noVerticalSpacer> - <CardWrapper> - {!isReady ? ( - <div className="item"> - <h3>{t('validators.connecting', { ns: 'pages' })}</h3> - </div> - ) : ( - <> - {validators.length === 0 && ( - <div className="item"> - <h3> - {t('validators.fetching_validators', { ns: 'pages' })} - </h3> - </div> - )} - - {validators.length > 0 && ( - <ValidatorList - bondType="stake" - validators={validators} - batchKey="validators_browse" - title={t('validators.network_validators', { ns: 'pages' })} - selectable={false} - defaultFilters={defaultFilters} - allowMoreCols - allowFilters - allowSearch - pagination - toggleFavorites - /> - )} - </> - )} - </CardWrapper> - </PageRowWrapper> + <PageTitle title={t('validators.validators')} tabs={tabs} /> + {activeTab === 0 && <AllValidators />} + {activeTab === 1 && <ValidatorFavorites />} </> ); }; -export default Validators; +export const Validators = () => ( + <ValidatorsTabsProvider> + <ValidatorsInner /> + </ValidatorsTabsProvider> +); diff --git a/src/reportWebVitals.ts b/src/reportWebVitals.ts deleted file mode 100644 index 8141a5b856..0000000000 --- a/src/reportWebVitals.ts +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { ReportHandler } from 'web-vitals'; - -const reportWebVitals = (onPerfEntry?: ReportHandler) => { - if (onPerfEntry && onPerfEntry instanceof Function) { - import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { - getCLS(onPerfEntry); - getFID(onPerfEntry); - getFCP(onPerfEntry); - getLCP(onPerfEntry); - getTTFB(onPerfEntry); - }); - } -}; - -export default reportWebVitals; diff --git a/src/styles/graphs.ts b/src/styles/graphs.ts new file mode 100644 index 0000000000..c50d52a722 --- /dev/null +++ b/src/styles/graphs.ts @@ -0,0 +1,23 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import type { AnyJson } from 'types'; + +export const graphColors: Record<string, AnyJson> = { + inactive: { + light: '#eee', + dark: 'rgb(39,35,39)', + }, + tooltip: { + light: '#333', + dark: '#ddd', + }, + label: { + light: '#fafafa', + dark: '#0e0e0e', + }, + grid: { + light: '#e8e8e8', + dark: 'rgb(64,55,64)', + }, +}; diff --git a/src/styles/index.scss b/src/styles/index.scss new file mode 100644 index 0000000000..be4cabf696 --- /dev/null +++ b/src/styles/index.scss @@ -0,0 +1,9 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +html { + font-size: 10.5px; + @media (min-width: 600px) { + font-size: 11px; + } +} diff --git a/src/theme/default.ts b/src/theme/default.ts deleted file mode 100644 index a41ac7176a..0000000000 --- a/src/theme/default.ts +++ /dev/null @@ -1,153 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import { NETWORKS } from 'config/networks'; -import { Network } from 'types'; - -// configure theme -const v = (light: string, dark: string) => ({ - light, - dark, -}); - -// eslint-disable-next-line -export const defaultThemes: { [key: string]: any } = { - transparent: v('rgba(255,255,255,0', 'rgba(0,0,0,0)'), - text: { - primary: v('#333', '#ccc'), - secondary: v('#444', '#aaa'), - invert: v('#fafafa', '#0e0e0e'), - warning: v('#be7900', '#be7900'), - danger: v('#ae2324', '#d14445'), - success: v('green', 'green'), - }, - background: { - primary: v('rgba(245,244,244,1)', 'rgba(39,39,39,1)'), - gradient: v( - 'linear-gradient(180deg, rgba(245,244,244,1) 0%, rgba(245,244,244,1) 100px, rgba(230,230,230, 1) 80%, rgba(253,239,234,1) 100%)', - 'linear-gradient(180deg, rgba(39,39,39,1) 0%, rgba(39,39,39,1) 100px, rgba(21,21,21,1) 100%)' - ), - secondary: v('rgba(255,255,255,0.58)', 'rgba(0,0,0,0.25)'), - network: v('rgba(244,225,225,0.75)', 'rgba(39,39,39,0.75)'), - dropdown: v('rgba(237,237,237,0.6)', 'rgba(33,33,33,0.6)'), - modalitem: v('rgba(244,244,244,0.6)', 'rgba(22,22,22,0.4)'), - validator: v( - 'linear-gradient(90deg, rgba(240,240,239,0.95) 0%, rgba(240,240,239,0.7) 100%)', - 'linear-gradient(90deg, rgba(30,30,30,0.8) 0%, rgba(30,30,30,0.5) 100%)' - ), - label: v( - 'linear-gradient(90deg, rgba(243,240,239,1) 0%, rgba(243,240,239,0.95) 100%)', - 'linear-gradient(90deg, rgba(40,40,40,0.85) 0%, rgba(40,40,40,0.95) 100%)' - ), - tag: v('rgba(220,220,220,0.75)', 'rgba(36,36,36,0.75)'), - identicon: v('#eee', '#333'), - overlay: v( - 'linear-gradient(180deg, rgba(244,242,242,0.93) 0%, rgba(228,225,225,0.93) 100%)', - 'linear-gradient(180deg, rgba(20,20,20,0.93) 0%, rgba(14,14,14,0.93) 100%)' - ), - }, - highlight: { - primary: v( - 'linear-gradient(90deg, rgba(0,0,0,0.06) 0%, rgba(0,0,0,0.03) 100%)', - 'linear-gradient(90deg, rgba(255,255,255,0.06) 0%, rgba(255,255,255,0.04) 100%)' - ), - secondary: v( - 'linear-gradient(90deg, rgba(0,0,0,0.04) 0%, rgba(0,0,0,0.01) 100%)', - 'linear-gradient(90deg, rgba(255,255,255,0.03) 0%, rgba(255,255,255,0.01) 100%)' - ), - }, - graphs: { - colors: [v('#ccc', '#555'), v('#eee', '#222')], - inactive: v('#cfcfcf', '#1a1a1a'), - inactive2: v('#dadada', '#383838'), - tooltip: v('#333', '#ddd'), - grid: v('#e8e8e8', '#222'), - }, - buttons: { - primary: { background: v('rgba(248, 248, 248, 0.9)', '#0f0f0f') }, - secondary: { background: v('#eeecec', '#333') }, - toggle: { background: v('rgba(244,243,242,1)', '#1a1a1a') }, - help: { background: v('#ececec', '#242424') }, - hover: { background: v('#e8e6e6', '#080808') }, - disabled: { - background: v('#F3F6F4', '#000000'), - text: v('#ececec', '#444444'), - }, - }, - border: { - primary: v('#e6e6e6', '#282828'), - secondary: v('#ccc', '#444'), - }, - modal: { - overlay: v('rgba(242,240,240,0.6)', 'rgba(16,16,16,0.6)'), - background: v('#fff', '#0b0b0b'), - }, - overlay: { - background: v('rgba(200,200,200,0.45)', 'rgba(30,30,30,0.6)'), - }, - help: { - button: { - background: v('rgba(255,255,255,0.90)', 'rgba(0,0,0,0.85)'), - }, - }, - loader: { - foreground: v('#e1e1e1', '#151515'), - background: v('#dadada', '#101010'), - }, - shadow: { - primary: v('#dedede', '#1f1f1f'), - secondary: v('#eaeaea', '#222'), - }, - status: { - danger: { - solid: v('red', 'red'), - transparent: v('rgba(255,0,0,0.25)', 'rgba(255,0,0,0.25)'), - }, - warning: { - solid: v('rgba(219, 161, 0, 1)', 'rgba(219, 161, 0,1)'), - transparent: v('rgba(255,165,0,0.5)', 'rgba(255,165,0,0.5)'), - }, - success: { - solid: v('green', 'green'), - transparent: v('rgba(0,128,0,0.25)', 'rgba(0,128,0,0.25)'), - }, - }, -}; - -// configure card style -const c = (flat: string, border: string, shadow: string) => ({ - flat, - border, - shadow, -}); - -// eslint-disable-next-line -export const cardThemes = { - card: { - border: c('none', '1px solid', 'none'), - shadow: c('none', 'none', '-2px 2px 10px'), - }, -}; - -// configure network colors -export const networkColors: { [key: string]: string } = {}; -export const networkColorsSecondary: { [key: string]: string } = {}; -export const networkColorsStroke: { [key: string]: string } = {}; -export const networkColorsTransparent: { [key: string]: string } = {}; - -Object.values(NETWORKS).forEach((node: Network) => { - const { name, colors } = node; - const { primary, secondary, stroke, transparent } = colors; - - networkColors[`${name}-light`] = primary.light; - networkColors[`${name}-dark`] = primary.dark; - - networkColorsSecondary[`${name}-light`] = secondary.light; - networkColorsSecondary[`${name}-dark`] = secondary.dark; - - networkColorsStroke[`${name}-light`] = stroke.light; - networkColorsStroke[`${name}-dark`] = stroke.dark; - - networkColorsTransparent[`${name}-light`] = transparent.light; - networkColorsTransparent[`${name}-dark`] = transparent.dark; -}); diff --git a/src/theme/index.ts b/src/theme/index.ts deleted file mode 100644 index 127647d86a..0000000000 --- a/src/theme/index.ts +++ /dev/null @@ -1,261 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import theme from 'styled-theming'; -import { - cardThemes, - defaultThemes, - networkColors, - networkColorsSecondary, - networkColorsStroke, - networkColorsTransparent, -} from './default'; - -/* Aggregates all theme configurations and serves the currently - * active mode via the theming context. - */ -const v = 'mode'; - -// text colors - -export const textPrimary: theme.ThemeSet = theme(v, defaultThemes.text.primary); - -export const textSecondary: theme.ThemeSet = theme( - v, - defaultThemes.text.secondary -); - -export const textInvert: theme.ThemeSet = theme(v, defaultThemes.text.invert); - -export const textWarning: theme.ThemeSet = theme(v, defaultThemes.text.warning); - -export const textDanger: theme.ThemeSet = theme(v, defaultThemes.text.danger); - -export const textSuccess: theme.ThemeSet = theme(v, defaultThemes.text.success); - -// background colors - -export const backgroundPrimary: theme.ThemeSet = theme( - v, - defaultThemes.background.primary -); - -export const backgroundSecondary: theme.ThemeSet = theme( - v, - defaultThemes.background.secondary -); - -export const backgroundGradient: theme.ThemeSet = theme( - v, - defaultThemes.background.gradient -); - -export const backgroundNetworkBar: theme.ThemeSet = theme( - v, - defaultThemes.background.network -); - -export const backgroundDropdown: theme.ThemeSet = theme( - v, - defaultThemes.background.dropdown -); - -export const backgroundModalItem: theme.ThemeSet = theme( - v, - defaultThemes.background.modalitem -); - -export const backgroundValidator: theme.ThemeSet = theme( - v, - defaultThemes.background.validator -); - -export const backgroundLabel: theme.ThemeSet = theme( - v, - defaultThemes.background.label -); - -export const backgroundIdenticon: theme.ThemeSet = theme( - v, - defaultThemes.background.identicon -); - -export const backgroundOverlay: theme.ThemeSet = theme( - v, - defaultThemes.background.overlay -); - -// highlights - -export const highlightPrimary: theme.ThemeSet = theme( - v, - defaultThemes.highlight.primary -); - -export const highlightSecondary: theme.ThemeSet = theme( - v, - defaultThemes.highlight.secondary -); - -// buttons - -export const buttonPrimaryBackground: theme.ThemeSet = theme( - v, - defaultThemes.buttons.primary.background -); - -export const buttonSecondaryBackground: theme.ThemeSet = theme( - v, - defaultThemes.buttons.secondary.background -); - -export const backgroundToggle: theme.ThemeSet = theme( - v, - defaultThemes.buttons.toggle.background -); - -export const buttonHelpBackground: theme.ThemeSet = theme( - v, - defaultThemes.buttons.help.background -); - -export const buttonHoverBackground: theme.ThemeSet = theme( - v, - defaultThemes.buttons.hover.background -); - -export const buttonDisabledBackground: theme.ThemeSet = theme( - v, - defaultThemes.buttons.disabled.background -); - -export const buttonDisabledText: theme.ThemeSet = theme( - v, - defaultThemes.buttons.disabled.text -); - -// labels - -export const tagBackground: theme.ThemeSet = theme( - v, - defaultThemes.background.tag -); - -// graphs - -export const tooltipBackground: theme.ThemeSet = theme( - v, - defaultThemes.graphs.tooltip -); - -export const gridColor: theme.ThemeSet = theme(v, defaultThemes.graphs.grid); - -// borders - -export const borderPrimary: theme.ThemeSet = theme( - v, - defaultThemes.border.primary -); - -export const borderSecondary: theme.ThemeSet = theme( - v, - defaultThemes.border.secondary -); - -// modal - -export const modalOverlayBackground: theme.ThemeSet = theme( - v, - defaultThemes.modal.overlay -); - -export const modalBackground: theme.ThemeSet = theme( - v, - defaultThemes.modal.background -); - -// overlay - -export const overlayBackground: theme.ThemeSet = theme( - v, - defaultThemes.overlay.background -); - -// help - -export const helpButton: theme.ThemeSet = theme( - v, - defaultThemes.help.button.background -); - -// status colors - -export const danger: theme.ThemeSet = theme( - v, - defaultThemes.status.danger.solid -); - -export const dangerTransparent: theme.ThemeSet = theme( - v, - defaultThemes.status.danger.transparent -); - -export const warning: theme.ThemeSet = theme( - v, - defaultThemes.status.warning.solid -); - -export const warningTransparent: theme.ThemeSet = theme( - v, - defaultThemes.status.warning.transparent -); - -export const success: theme.ThemeSet = theme( - v, - defaultThemes.status.success.solid -); - -export const successTransparent: theme.ThemeSet = theme( - v, - defaultThemes.status.success.transparent -); - -// shadow - -export const shadowColor: theme.ThemeSet = theme( - v, - defaultThemes.shadow.primary -); - -export const shadowColorSecondary: theme.ThemeSet = theme( - v, - defaultThemes.shadow.secondary -); - -/* Aggregates all card configurations and serves the currently - * active card style via the theming context. - */ -const c = 'card'; - -export const cardBorder: theme.ThemeSet = theme(c, cardThemes.card.border); - -export const cardShadow: theme.ThemeSet = theme(c, cardThemes.card.shadow); - -/* Serves the currently active network color via the theming context. - */ - -const n = 'network'; - -export const networkColor: theme.ThemeSet = theme(n, networkColors); - -export const networkColorSecondary: theme.ThemeSet = theme( - n, - networkColorsSecondary -); - -export const networkColorStroke: theme.ThemeSet = theme(n, networkColorsStroke); - -export const networkColorTransparent: theme.ThemeSet = theme( - n, - networkColorsTransparent -); diff --git a/src/types/index.ts b/src/types/index.ts index c33e4b9f86..50e953a60c 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -1,54 +1,43 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only -import { IconDefinition } from '@fortawesome/fontawesome-svg-core'; -import type { WellKnownChain } from '@polkadot/rpc-provider/substrate-connect'; -import { PageProps } from 'pages/types'; -import React, { FunctionComponent, SVGProps } from 'react'; +import type React from 'react'; +import type { FunctionComponent, SVGProps } from 'react'; +import type { Theme } from 'contexts/Themes/types'; +import type { ExtensionInjected } from '@polkadot-cloud/react/types'; -export type Fn = () => void; - -export enum NetworkName { - Polkadot = 'polkadot', - Kusama = 'kusama', - Westend = 'westend', +declare global { + interface Window { + injectedWeb3?: Record<string, ExtensionInjected>; + } } -export enum Toggle { - Open = 'open', - Closed = 'closed', -} +export type NetworkName = + | 'cereMainnet' + | 'cereTestnet' + | 'cereDevnet' + | 'cereQanet'; -export interface Networks { - [key: string]: Network; -} +export type Networks = Record<string, Network>; +type NetworkColor = + | 'primary' + | 'secondary' + | 'stroke' + | 'transparent' + | 'pending'; export interface Network { - name: string; + name: NetworkName; endpoints: { - rpc: string; - lightClient: WellKnownChain; - }; - colors: { - primary: { - light: string; - dark: string; - }; - secondary: { - light: string; - dark: string; - }; - stroke: { - light: string; - dark: string; - }; - transparent: { - light: string; - dark: string; - }; + lightClient: AnyApi; + defaultRpcEndpoint: string; + rpcEndpoints: Record<string, string>; }; - subscanEndpoint: string; cereStatsEndpoint: string; + namespace: string; + // eslint-disable-next-line @typescript-eslint/no-unused-vars + colors: Record<NetworkColor, { [key in Theme]: string }>; + subscanEndpoint: string; unit: string; units: number; ss58: number; @@ -56,6 +45,9 @@ export interface Network { icon: FunctionComponent< SVGProps<SVGSVGElement> & { title?: string | undefined } >; + token: FunctionComponent< + SVGProps<SVGSVGElement> & { title?: string | undefined } + >; logo: { svg: FunctionComponent< SVGProps<SVGSVGElement> & { title?: string | undefined } @@ -73,7 +65,8 @@ export interface Network { unit: string; priceTicker: string; }; - params: { [key: string]: number }; + params: Record<string, number>; + defaultFeeReserve: number; } export interface PageCategory { @@ -81,7 +74,7 @@ export interface PageCategory { key: string; } -export type PageCategories = Array<PageCategory>; +export type PageCategoryItems = PageCategory[]; export interface PageItem { category: number; @@ -89,8 +82,7 @@ export interface PageItem { uri: string; hash: string; Entry: React.FC<PageProps>; - icon?: IconDefinition; - animate?: AnyJson; + lottie: AnyJson; action?: { type: string; status: string; @@ -98,27 +90,50 @@ export interface PageItem { }; } -export type PagesConfig = Array<PageItem>; +export type PagesConfigItems = PageItem[]; + +export interface PageProps { + page: PageProp; +} + +interface PageProp { + key: string; +} -export type MaybeAccount = string | null; +export type MaybeAddress = string | null; export type MaybeString = string | null; +// list of available plugins. +export type Plugin = + | 'subscan' + | 'binance_spot' + | 'tips' + | 'polkawatch' + | 'cereStats'; // ToDo + +// track the status of a syncing / fetching process. +export type Sync = 'unsynced' | 'syncing' | 'synced'; + +// track whether bonding should be for nominator or nomination pool. +export type BondFor = 'pool' | 'nominator'; + +// which medium components are being displayed on. +export type DisplayFor = 'default' | 'modal' | 'canvas'; + +// generic function with no args or return type. +export type Fn = () => void; + // any types to compress compiler warnings -// eslint-disable-next-line +// eslint-disable-next-line @typescript-eslint/no-explicit-any export type AnyApi = any; -// eslint-disable-next-line +// eslint-disable-next-line @typescript-eslint/no-explicit-any export type AnyJson = any; -// eslint-disable-next-line +// eslint-disable-next-line @typescript-eslint/no-explicit-any export type AnyFunction = any; -// eslint-disable-next-line +// eslint-disable-next-line @typescript-eslint/no-explicit-any export type AnyMetaBatch = any; -// eslint-disable-next-line +// eslint-disable-next-line @typescript-eslint/no-explicit-any export type AnySubscan = any; - -// track the status of a syncing / fetching process. -export enum Sync { - Unsynced, - Syncing, - Synced, -} +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export type AnyPolkawatch = any; diff --git a/src/types/styles.ts b/src/types/styles.ts index fa4f8b6d55..69481d43b1 100644 --- a/src/types/styles.ts +++ b/src/types/styles.ts @@ -1,7 +1,7 @@ // Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors // SPDX-License-Identifier: Apache-2.0 -import { RefObject } from 'react'; +import type { RefObject } from 'react'; export interface PageRowWrapperProps { noVerticalSpacer?: boolean; diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts new file mode 100644 index 0000000000..5eeb1139ed --- /dev/null +++ b/src/vite-env.d.ts @@ -0,0 +1,8 @@ +/// <reference types="vite/client" /> +/// <reference types="vite-plugin-svgr/client" /> + +declare namespace JSX { + interface IntrinsicElements { + 'dotlottie-player': any; + } +} diff --git a/src/workers/poolPerformance.ts b/src/workers/poolPerformance.ts new file mode 100644 index 0000000000..ad4b46af4d --- /dev/null +++ b/src/workers/poolPerformance.ts @@ -0,0 +1,63 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only +/* eslint-disable no-await-in-loop */ + +import type { Exposure } from 'contexts/Staking/types'; +import type { ErasRewardPoints } from 'contexts/Validators/types'; +import type { AnyApi, AnyJson } from 'types'; + +// eslint-disable-next-line no-restricted-globals +export const ctx: Worker = self as any; + +// handle incoming message and route to correct handler. +ctx.addEventListener('message', async (event: AnyJson) => { + const { data } = event; + const { task } = data; + let message: AnyJson = {}; + switch (task) { + case 'processNominationPoolsRewardData': + message = await processErasStakersForNominationPoolRewards(data); + break; + default: + } + postMessage({ task, ...message }); +}); + +// Process `erasStakersClipped` and generate nomination pool reward data. +const processErasStakersForNominationPoolRewards = async ({ + bondedPools, + era, + erasRewardPoints, + exposures, +}: { + bondedPools: string[]; + era: string; + erasRewardPoints: ErasRewardPoints; + exposures: Exposure[]; +}) => { + const poolRewardData: Record<string, Record<string, string>> = {}; + + for (const address of bondedPools) { + let validator = null; + for (const exposure of exposures) { + const { others } = exposure.val; + const inOthers = others.find((o: AnyApi) => o.who === address); + + if (inOthers) { + validator = exposure.keys[1]; + break; + } + } + + if (validator) { + const rewardPoints: string = + erasRewardPoints[era]?.individual?.[validator || ''] ?? 0; + if (!poolRewardData[address]) poolRewardData[address] = {}; + poolRewardData[address][era] = rewardPoints; + } + } + + return { + poolRewardData, + }; +}; diff --git a/src/workers/stakers.ts b/src/workers/stakers.ts index 19195ae550..4353bae77b 100644 --- a/src/workers/stakers.ts +++ b/src/workers/stakers.ts @@ -1,89 +1,204 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import BN from 'bn.js'; -import { planckBnToUnit, rmCommas } from 'Utils'; +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { planckToUnit, rmCommas } from '@polkadot-cloud/utils'; +import BigNumber from 'bignumber.js'; +import type { + ActiveAccountStaker, + ExposureOther, + Staker, +} from 'contexts/Staking/types'; +import type { AnyJson } from 'types'; +import type { LocalValidatorExposure } from 'contexts/Payouts/types'; +import type { DataInitialiseExposures } from './types'; // eslint-disable-next-line no-restricted-globals export const ctx: Worker = self as any; -ctx.addEventListener('message', (event: any) => { +// handle incoming message and route to correct handler. +ctx.addEventListener('message', (event: AnyJson) => { const { data } = event; + const { task } = data; + let message: AnyJson = {}; + switch (task) { + case 'processExposures': + message = processExposures(data as DataInitialiseExposures); + break; + case 'processEraForExposure': + message = processEraForExposure(data); + break; + default: + } + postMessage({ task, ...message }); +}); - const { units, exposures, activeAccount } = data; +// Process era exposures and return if an account was exposed, along with the validator they backed. +const processEraForExposure = (data: AnyJson) => { + const { era, exposures, exitOnExposed, task, networkName, who } = data; + let exposed = false; + + // If exposed, the validator that was backed. + const exposedValidators: Record<string, LocalValidatorExposure> = {}; + + // Check exposed as validator or nominator. + exposures.every(({ keys, val }: any) => { + const validator = keys[1]; + const others = val?.others ?? []; + const own = val?.own || 0; + const total = val?.total || 0; + const isValidator = validator === who; + + if (isValidator) { + const share = new BigNumber(own).isZero() + ? '0' + : new BigNumber(own).dividedBy(total).toString(); + + exposedValidators[validator] = { + staked: own, + total, + share, + isValidator, + }; + + exposed = true; + if (exitOnExposed) return false; + } + + const inOthers = others.find((o: AnyJson) => o.who === who); + + if (inOthers) { + const share = new BigNumber(inOthers.value).isZero() + ? '0' + : new BigNumber(inOthers.value).dividedBy(total).toString(); + + exposedValidators[validator] = { + staked: inOthers.value, + total, + share, + isValidator, + }; + exposed = true; + if (exitOnExposed) return false; + } - const stakers: any = []; + return true; + }); + + return { + networkName, + era, + exposed, + exposedValidators: Object.keys(exposedValidators).length + ? exposedValidators + : null, + task, + who, + }; +}; + +// process exposures. +// +// abstracts active nominators erasStakers. +const processExposures = (data: DataInitialiseExposures) => { + const { + task, + networkName, + era, + units, + exposures, + activeAccount, + maxNominatorRewardedPerValidator, + } = data; + + const stakers: Staker[] = []; let activeValidators = 0; - const ownStake: Array<any> = []; - const nominators: any = []; + const activeAccountOwnStake: ActiveAccountStaker[] = []; + const nominators: ExposureOther[] = []; - exposures.forEach(({ keys, val }: any) => { - const address = keys[1]; + exposures.forEach(({ keys, val }) => { activeValidators++; - stakers.push({ - address, - ...val, - }); - - // sort `others` by value bonded, largest first - let others = val?.others ?? []; - others = others.sort((a: any, b: any) => { - const x = new BN(rmCommas(a.value)); - const y = new BN(rmCommas(b.value)); - return y.sub(x); - }); + const address = keys[1]; + let others = + val?.others.map((o) => ({ + ...o, + value: rmCommas(o.value), + })) ?? []; - // accumulate active nominators and min active bond threshold. + // Accumulate active nominators and min active stake threshold. if (others.length) { - // accumulate active bond for all nominators + // Sort `others` by value bonded, largest first. + others = others.sort((a, b) => { + const r = new BigNumber(rmCommas(b.value)).minus(rmCommas(a.value)); + return r.isZero() ? 0 : r.isLessThan(0) ? -1 : 1; + }); + + const lowestRewardIndex = Math.min( + maxNominatorRewardedPerValidator - 1, + others.length + ); + + const lowestReward = + others.length > 0 + ? planckToUnit( + new BigNumber(others[lowestRewardIndex]?.value || 0), + units + ).toString() + : '0'; + + const oversubscribed = others.length > maxNominatorRewardedPerValidator; + + stakers.push({ + address, + lowestReward, + oversubscribed, + others, + own: rmCommas(val.own), + total: rmCommas(val.total), + }); + + // Accumulate active stake for all nominators. for (const o of others) { - const _value = new BN(rmCommas(o.value)); + const value = new BigNumber(rmCommas(o.value)); - // check nominator already exists - const index = nominators.findIndex((_o: any) => _o.who === o.who); + // Check nominator already exists. + const index = nominators.findIndex(({ who }) => who === o.who); - // add value to nominator, otherwise add new entry + // Add value to nominator, otherwise add new entry. if (index === -1) { nominators.push({ who: o.who, - value: _value, + value: value.toString(), }); } else { - nominators[index].value = nominators[index].value.add(_value); + nominators[index].value = new BigNumber(nominators[index].value) + .plus(value) + .toString(); } } // get own stake if present - const own = others.find((_o: any) => _o.who === activeAccount); + const own = others.find(({ who }) => who === activeAccount); if (own !== undefined) { - ownStake.push({ + activeAccountOwnStake.push({ address, - value: planckBnToUnit(new BN(rmCommas(own.value)), units), + value: planckToUnit( + new BigNumber(rmCommas(own.value)), + units + ).toString(), }); } } }); - // order nominators by bond size, smallest first - const _getMinBonds = nominators.sort((a: any, b: any) => { - return a.value.sub(b.value); - }); - - // get the smallest actve nominator bond - let minActiveBond = _getMinBonds[0]?.value ?? new BN(0); - - // convert minActiveBond to base value - minActiveBond = planckBnToUnit(minActiveBond, units); - - postMessage({ + return { + networkName, + era, stakers, - ownStake, totalActiveNominators: nominators.length, + activeAccountOwnStake, activeValidators, - minActiveBond, - _activeAccount: activeAccount, - }); -}); - -export default null as any; + task, + who: activeAccount, + }; +}; diff --git a/src/workers/types.ts b/src/workers/types.ts new file mode 100644 index 0000000000..7293c7dd03 --- /dev/null +++ b/src/workers/types.ts @@ -0,0 +1,30 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import type { + ActiveAccountStaker, + Exposure, + Staker, +} from 'contexts/Staking/types'; +import type { MaybeAddress, NetworkName } from 'types'; + +export interface DataInitialiseExposures { + task: string; + networkName: NetworkName; + era: string; + activeAccount: MaybeAddress; + units: number; + exposures: Exposure[]; + maxNominatorRewardedPerValidator: number; +} + +export interface ResponseInitialiseExposures { + task: string; + networkName: NetworkName; + era: string; + stakers: Staker[]; + totalActiveNominators: number; + activeAccountOwnStake: ActiveAccountStaker[]; + activeValidators: number; + who: MaybeAddress; +} diff --git a/tests/graphs.test.ts b/tests/graphs.test.ts new file mode 100644 index 0000000000..557d325c06 --- /dev/null +++ b/tests/graphs.test.ts @@ -0,0 +1,157 @@ +/* Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors + * SPDX-License-Identifier: GPL-3.0-only */ + +import { fromUnixTime, getUnixTime, startOfToday, subDays } from 'date-fns'; +import { expect, test } from 'vitest'; +import { + daysPassed, + normalisePayouts, + postFillMissingDays, + prefillMissingDays, +} from 'library/Graphs/Utils'; + +// payouts that were made 2, 3 and 4 days ago. +const mockPayouts = [ + { + amount: '10000000000', + block_timestamp: getUnixTime(subDays(new Date(), 2)), + }, + { + amount: '15000000000', + block_timestamp: getUnixTime(subDays(new Date(), 3)), + }, + { + amount: '5000000000', + block_timestamp: getUnixTime(subDays(new Date(), 4)), + }, +]; + +// Get the correct amount of days passed between 2 payout timestamps. +// +// `daysPassed` is a utility function that is used throughout the graph data accumulation process. +test('days passed works', () => { + const payouts = normalisePayouts(mockPayouts); + // days passed works on `mockPayouts`. + expect( + daysPassed(fromUnixTime(payouts[0].block_timestamp), startOfToday()) + ).toBe(2); + expect( + daysPassed(fromUnixTime(payouts[1].block_timestamp), startOfToday()) + ).toBe(3); + expect( + daysPassed(fromUnixTime(payouts[2].block_timestamp), startOfToday()) + ).toBe(4); + + // max amount of missing days to process should be correct. + for (let i = 1; i < 368; i++) { + expect(daysPassed(subDays(new Date(), i), new Date())).toBe(i); + } +}); + +// Fill missing days from the latest payout to the current day. +// +// Note that the latest payout is assumed to be the first in the payout list. +test('post fill missing days works', () => { + // p0, p1, p2, p3, p4, p5, p6 + // - - x x x 0 0 + const payouts = normalisePayouts(mockPayouts); + const fromDate = new Date(); + const maxDays = 7; + + // post fill the missing days for mock payouts. + const missingDays = postFillMissingDays(payouts, fromDate, maxDays); + + // amount of missing days returned should be correct. + expect(missingDays.length).toBe(2); + + // concatenated payouts are correct + const concatPayouts = missingDays.concat(payouts); + + // days passed and ordering are correct. + for (let i = 0; i < concatPayouts.length; i++) { + if (i > 0) { + expect( + daysPassed( + fromUnixTime(concatPayouts[i].block_timestamp), + fromUnixTime(concatPayouts[i - 1].block_timestamp) + ) + ).toBe(1); + expect(concatPayouts[i].block_timestamp).toBeLessThan( + concatPayouts[i - 1].block_timestamp + ); + } + } +}); + +// Fill missing days from the earliest payout to the current day. +// +// Note that the earliest payout is assumed to be the last in the payout list. +test('pre fill missing days works', () => { + // p0, p1, p2, p3, p4, p5, p6 + // x x x - - + const payouts = normalisePayouts(mockPayouts); + const fromDate = new Date(); + const maxDays = 7; + + // post fill the missing days for mock payouts. + const missingDays = prefillMissingDays(payouts, fromDate, maxDays); + + // expect amount of missing days to be 2 + expect(missingDays.length).toBe(2); + + // concatenated payouts are correct + const concatPayouts = payouts.concat(missingDays); + + // days passed and ordering are correct. + for (let i = 0; i < concatPayouts.length; i++) { + if (i > 0) { + expect( + daysPassed( + fromUnixTime(concatPayouts[i].block_timestamp), + fromUnixTime(concatPayouts[i - 1].block_timestamp) + ) + ).toBe(1); + expect(concatPayouts[i].block_timestamp).toBeLessThan( + concatPayouts[i - 1].block_timestamp + ); + } + } +}); + +// Use post-fill and pre-fill together. +// +// Test filling days from both directions. +test('pre fill and post fill missing days work together', () => { + // p0, p1, p2, p3, p4, p5, p6, p7, p8, p9 + // - - x x x - - - - - + const payouts = normalisePayouts(mockPayouts); + const fromDate = new Date(); + const maxDays = 10; + + // post fill the missing days for mock payouts. + const missingPostDays = postFillMissingDays(payouts, fromDate, maxDays); + expect(missingPostDays.length).toBe(2); + + const missingPreDays = prefillMissingDays(payouts, fromDate, maxDays); + expect(missingPreDays.length).toBe(5); + + const finalPayouts = missingPostDays.concat(payouts).concat(missingPreDays); + expect(finalPayouts.length).toBe(10); + + // days passed and ordering are correct. + for (let i = 0; i < finalPayouts.length; i++) { + if (i > 0) { + expect( + daysPassed( + fromUnixTime(finalPayouts[i].block_timestamp), + fromUnixTime(finalPayouts[i - 1].block_timestamp) + ) + ).toBe(1); + expect(finalPayouts[i].block_timestamp).toBeLessThan( + finalPayouts[i - 1].block_timestamp + ); + } + } +}); + +export {}; diff --git a/tsconfig.json b/tsconfig.json index 0bd7b50e33..9a4e47a622 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,31 +1,30 @@ { "compilerOptions": { "baseUrl": "src", - "target": "es5", - "lib": [ - "dom", - "dom.iterable", - "esnext" - ], - "allowJs": true, + "target": "ESNext", + "useDefineForClassFields": true, + "lib": ["DOM", "DOM.Iterable", "ESNext"], + "allowJs": false, "skipLibCheck": true, - "esModuleInterop": true, + "esModuleInterop": false, "allowSyntheticDefaultImports": true, "strict": true, "forceConsistentCasingInFileNames": true, "noFallthroughCasesInSwitch": true, - "module": "esnext", - "moduleResolution": "node", + "module": "ESNext", + "moduleResolution": "Node", "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", "types": [ "react", - "react-dom" + "react-dom", ], }, "include": [ - "src" + "src", + "tests" ], -} \ No newline at end of file + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/tsconfig.node.json b/tsconfig.node.json new file mode 100644 index 0000000000..9d31e2aed9 --- /dev/null +++ b/tsconfig.node.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "composite": true, + "module": "ESNext", + "moduleResolution": "Node", + "allowSyntheticDefaultImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000000..1fa9c14a85 --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,46 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import react from '@vitejs/plugin-react-swc'; +import { defineConfig } from 'vite'; +import checker from 'vite-plugin-checker'; +import eslint from 'vite-plugin-eslint'; +import svgr from 'vite-plugin-svgr'; +import tsconfigPaths from 'vite-tsconfig-paths'; + +// https://vitejs.dev/config/ +// +// NOTES: +// - `base` is configured in `package.json` with the vite --base flag. In local dev it is `/`, +// whereas gh-pages always deploys to `/polkadot-staking-dashboard/`. Producution builds can also +// be configureed with the `--base` flag. +// - `BASE_URL`env variable is used in the codebase to refer to the supplied base. +export default defineConfig({ + plugins: [ + eslint(), + react(), + svgr(), + tsconfigPaths(), + checker({ + typescript: true, + }), + ], + build: { + outDir: 'build', + rollupOptions: { + output: { + manualChunks: { + '@substrate/connect': ['@substrate/connect'], + }, + }, + }, + }, + server: { + fs: { + strict: false, + }, + }, + optimizeDeps: { + include: ['react/jsx-runtime'], + }, +}); diff --git a/webpack.config.js b/webpack.config.js deleted file mode 100644 index e1afff2090..0000000000 --- a/webpack.config.js +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright 2022 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -// eslint-disable-next-line -const TerserPlugin = require('terser-webpack-plugin'); - -module.exports = { - module: { - rules: [ - { - test: /\.worker\.ts$/, - loader: 'worker-loader', - options: { - inline: true, - }, - }, - { - test: /\.ts$/, - loader: 'ts-loader', - options: { - inline: true, - }, - }, - ], - }, - resolve: { - extensions: ['.ts', '.js'], - }, - optimization: { - minimize: true, - minimizer: [new TerserPlugin({ - sourceMap: true, - terserOptions: { - compress: { - drop_console: true, - }, - } - })], - }, -}; diff --git a/yarn.lock b/yarn.lock index 6e1bf20d4f..81470807d4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,3106 +2,1494 @@ # yarn lockfile v1 -"@adobe/css-tools@^4.0.1": - version "4.0.1" - resolved "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.0.1.tgz#b38b444ad3aa5fedbb15f2f746dcd934226a12dd" - integrity sha512-+u76oB43nOHrF4DDWRLWDCtci7f3QJoEBigemIdIeTi1ODqjx6Tad9NCVnPRwewWlKkVab5PlK8DCtPTyX7S8g== +"@aashutoshrathi/word-wrap@^1.2.3": + version "1.2.6" + resolved "https://registry.yarnpkg.com/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz#bd9154aec9983f77b3a034ecaa015c2e4201f6cf" + integrity sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA== -"@ampproject/remapping@^2.1.0": - version "2.2.0" - resolved "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz#56c133824780de3174aed5ab6834f3026790154d" - integrity sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w== +"@ampproject/remapping@^2.2.0": + version "2.2.1" + resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.1.tgz#99e8e11851128b8702cd57c33684f1d0f260b630" + integrity sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg== dependencies: - "@jridgewell/gen-mapping" "^0.1.0" + "@jridgewell/gen-mapping" "^0.3.0" "@jridgewell/trace-mapping" "^0.3.9" -"@apideck/better-ajv-errors@^0.3.1": - version "0.3.6" - resolved "https://registry.npmjs.org/@apideck/better-ajv-errors/-/better-ajv-errors-0.3.6.tgz#957d4c28e886a64a8141f7522783be65733ff097" - integrity sha512-P+ZygBLZtkp0qqOAJJVX4oX/sFo5JR3eBWwwuqHHhK0GIgQOKWrAfiAaWX0aArHkRWHMuggFEgAZNxVPwPZYaA== - dependencies: - json-schema "^0.4.0" - jsonpointer "^5.0.0" - leven "^3.1.0" - "@apollo/client@^3.7.14": - version "3.8.9" - resolved "https://registry.yarnpkg.com/@apollo/client/-/client-3.8.9.tgz#0e4ac133eb04c63e618138c1ebf273d9f110a4d0" - integrity sha512-IcQDFEEPc9+PEQsxhxQvsoQ04BRarOzi/Ila5PcniRSDeKJWgY22dnp6+V1i1fWXRDVd1ybdvze4sFESDVQUCQ== + version "3.9.4" + resolved "https://registry.yarnpkg.com/@apollo/client/-/client-3.9.4.tgz#a0230ce42a4d0c26c9f75f2a10c0f330f3ef135c" + integrity sha512-Ip6dxjshDT2Dp6foLASTnKBW45Fytew/5JZutZwgc78hVrrGpO9UtZA9xteHXYdap0wIgCxCfeIQwbSu1ZdQpw== dependencies: "@graphql-typed-document-node/core" "^3.1.1" + "@wry/caches" "^1.0.0" "@wry/equality" "^0.5.6" "@wry/trie" "^0.5.0" graphql-tag "^2.12.6" hoist-non-react-statics "^3.3.2" optimism "^0.18.0" prop-types "^15.7.2" + rehackt "0.0.4" response-iterator "^0.2.6" symbol-observable "^4.0.0" ts-invariant "^0.10.3" tslib "^2.3.0" zen-observable-ts "^1.2.5" -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.16.0", "@babel/code-frame@^7.18.6", "@babel/code-frame@^7.8.3": - version "7.18.6" - resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz#3b25d38c89600baa2dcc219edfa88a74eb2c427a" - integrity sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q== - dependencies: - "@babel/highlight" "^7.18.6" - -"@babel/compat-data@^7.17.7", "@babel/compat-data@^7.20.0", "@babel/compat-data@^7.20.1": - version "7.20.1" - resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.20.1.tgz#f2e6ef7790d8c8dbf03d379502dcc246dcce0b30" - integrity sha512-EWZ4mE2diW3QALKvDMiXnbZpRvlj+nayZ112nK93SnhqOtpdsbVD4W+2tEoT3YNBAG9RBR0ISY758ZkOgsn6pQ== - -"@babel/core@^7.1.0", "@babel/core@^7.11.1", "@babel/core@^7.12.3", "@babel/core@^7.16.0", "@babel/core@^7.7.2", "@babel/core@^7.8.0": - version "7.20.2" - resolved "https://registry.npmjs.org/@babel/core/-/core-7.20.2.tgz#8dc9b1620a673f92d3624bd926dc49a52cf25b92" - integrity sha512-w7DbG8DtMrJcFOi4VrLm+8QM4az8Mo+PuLBKLp2zrYRCow8W/f9xiXm5sN53C8HksCyDQwCKha9JiDoIyPjT2g== - dependencies: - "@ampproject/remapping" "^2.1.0" - "@babel/code-frame" "^7.18.6" - "@babel/generator" "^7.20.2" - "@babel/helper-compilation-targets" "^7.20.0" - "@babel/helper-module-transforms" "^7.20.2" - "@babel/helpers" "^7.20.1" - "@babel/parser" "^7.20.2" - "@babel/template" "^7.18.10" - "@babel/traverse" "^7.20.1" - "@babel/types" "^7.20.2" - convert-source-map "^1.7.0" +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.22.13": + version "7.22.13" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.22.13.tgz#e3c1c099402598483b7a8c46a721d1038803755e" + integrity sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w== + dependencies: + "@babel/highlight" "^7.22.13" + chalk "^2.4.2" + +"@babel/compat-data@^7.22.9": + version "7.23.2" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.23.2.tgz#6a12ced93455827037bfb5ed8492820d60fc32cc" + integrity sha512-0S9TQMmDHlqAZ2ITT95irXKfxN9bncq8ZCoJhun3nHL/lLUxd2NKBJYoNGWH7S0hz6fRQwWlAWn/ILM0C70KZQ== + +"@babel/core@^7.21.3": + version "7.23.2" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.23.2.tgz#ed10df0d580fff67c5f3ee70fd22e2e4c90a9f94" + integrity sha512-n7s51eWdaWZ3vGT2tD4T7J6eJs3QoBXydv7vkUM06Bf1cbVD2Kc2UrkzhiQwobfV7NwOnQXYL7UBJ5VPU+RGoQ== + dependencies: + "@ampproject/remapping" "^2.2.0" + "@babel/code-frame" "^7.22.13" + "@babel/generator" "^7.23.0" + "@babel/helper-compilation-targets" "^7.22.15" + "@babel/helper-module-transforms" "^7.23.0" + "@babel/helpers" "^7.23.2" + "@babel/parser" "^7.23.0" + "@babel/template" "^7.22.15" + "@babel/traverse" "^7.23.2" + "@babel/types" "^7.23.0" + convert-source-map "^2.0.0" debug "^4.1.0" gensync "^1.0.0-beta.2" - json5 "^2.2.1" - semver "^6.3.0" - -"@babel/eslint-parser@^7.16.3": - version "7.19.1" - resolved "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.19.1.tgz#4f68f6b0825489e00a24b41b6a1ae35414ecd2f4" - integrity sha512-AqNf2QWt1rtu2/1rLswy6CDP7H9Oh3mMhk177Y67Rg8d7RD9WfOLLv8CGn6tisFvS2htm86yIe1yLF6I1UDaGQ== - dependencies: - "@nicolo-ribaudo/eslint-scope-5-internals" "5.1.1-v1" - eslint-visitor-keys "^2.1.0" - semver "^6.3.0" + json5 "^2.2.3" + semver "^6.3.1" -"@babel/generator@^7.20.1", "@babel/generator@^7.20.2", "@babel/generator@^7.7.2": - version "7.20.4" - resolved "https://registry.npmjs.org/@babel/generator/-/generator-7.20.4.tgz#4d9f8f0c30be75fd90a0562099a26e5839602ab8" - integrity sha512-luCf7yk/cm7yab6CAW1aiFnmEfBJplb/JojV56MYEK7ziWfGmFlTfmL9Ehwfy4gFhbjBfWO1wj7/TuSbVNEEtA== +"@babel/generator@^7.23.0": + version "7.23.0" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.23.0.tgz#df5c386e2218be505b34837acbcb874d7a983420" + integrity sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g== dependencies: - "@babel/types" "^7.20.2" + "@babel/types" "^7.23.0" "@jridgewell/gen-mapping" "^0.3.2" + "@jridgewell/trace-mapping" "^0.3.17" jsesc "^2.5.1" -"@babel/helper-annotate-as-pure@^7.16.0", "@babel/helper-annotate-as-pure@^7.18.6": - version "7.18.6" - resolved "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz#eaa49f6f80d5a33f9a5dd2276e6d6e451be0a6bb" - integrity sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA== - dependencies: - "@babel/types" "^7.18.6" +"@babel/helper-compilation-targets@^7.22.15": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.15.tgz#0698fc44551a26cf29f18d4662d5bf545a6cfc52" + integrity sha512-y6EEzULok0Qvz8yyLkCvVX+02ic+By2UdOhylwUOvOn9dvYc9mKICJuuU1n1XBI02YWsNsnrY1kc6DVbjcXbtw== + dependencies: + "@babel/compat-data" "^7.22.9" + "@babel/helper-validator-option" "^7.22.15" + browserslist "^4.21.9" + lru-cache "^5.1.1" + semver "^6.3.1" + +"@babel/helper-environment-visitor@^7.22.20": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz#96159db61d34a29dba454c959f5ae4a649ba9167" + integrity sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA== + +"@babel/helper-function-name@^7.23.0": + version "7.23.0" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz#1f9a3cdbd5b2698a670c30d2735f9af95ed52759" + integrity sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw== + dependencies: + "@babel/template" "^7.22.15" + "@babel/types" "^7.23.0" + +"@babel/helper-hoist-variables@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz#c01a007dac05c085914e8fb652b339db50d823bb" + integrity sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw== + dependencies: + "@babel/types" "^7.22.5" + +"@babel/helper-module-imports@^7.22.15": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz#16146307acdc40cc00c3b2c647713076464bdbf0" + integrity sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w== + dependencies: + "@babel/types" "^7.22.15" + +"@babel/helper-module-transforms@^7.23.0": + version "7.23.0" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.23.0.tgz#3ec246457f6c842c0aee62a01f60739906f7047e" + integrity sha512-WhDWw1tdrlT0gMgUJSlX0IQvoO1eN279zrAUbVB+KpV2c3Tylz8+GnKOLllCS6Z/iZQEyVYxhZVUdPTqs2YYPw== + dependencies: + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-module-imports" "^7.22.15" + "@babel/helper-simple-access" "^7.22.5" + "@babel/helper-split-export-declaration" "^7.22.6" + "@babel/helper-validator-identifier" "^7.22.20" + +"@babel/helper-simple-access@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz#4938357dc7d782b80ed6dbb03a0fba3d22b1d5de" + integrity sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w== + dependencies: + "@babel/types" "^7.22.5" + +"@babel/helper-split-export-declaration@^7.22.6": + version "7.22.6" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz#322c61b7310c0997fe4c323955667f18fcefb91c" + integrity sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g== + dependencies: + "@babel/types" "^7.22.5" + +"@babel/helper-string-parser@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz#533f36457a25814cf1df6488523ad547d784a99f" + integrity sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw== + +"@babel/helper-validator-identifier@^7.22.20": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz#c4ae002c61d2879e724581d96665583dbc1dc0e0" + integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A== + +"@babel/helper-validator-option@^7.22.15": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.22.15.tgz#694c30dfa1d09a6534cdfcafbe56789d36aba040" + integrity sha512-bMn7RmyFjY/mdECUbgn9eoSY4vqvacUnS9i9vGAGttgFWesO6B4CYWA7XlpbWgBt71iv/hfbPlynohStqnu5hA== + +"@babel/helpers@^7.23.2": + version "7.23.2" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.23.2.tgz#2832549a6e37d484286e15ba36a5330483cac767" + integrity sha512-lzchcp8SjTSVe/fPmLwtWVBFC7+Tbn8LGHDVfDp9JGxpAY5opSaEFgt8UQvrnECWOTdji2mOWMz1rOhkHscmGQ== + dependencies: + "@babel/template" "^7.22.15" + "@babel/traverse" "^7.23.2" + "@babel/types" "^7.23.0" + +"@babel/highlight@^7.22.13": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.22.20.tgz#4ca92b71d80554b01427815e06f2df965b9c1f54" + integrity sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg== + dependencies: + "@babel/helper-validator-identifier" "^7.22.20" + chalk "^2.4.2" + js-tokens "^4.0.0" + +"@babel/parser@^7.22.15", "@babel/parser@^7.23.0": + version "7.23.0" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.23.0.tgz#da950e622420bf96ca0d0f2909cdddac3acd8719" + integrity sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw== + +"@babel/runtime@^7.10.1", "@babel/runtime@^7.12.5", "@babel/runtime@^7.18.3", "@babel/runtime@^7.19.4", "@babel/runtime@^7.20.7", "@babel/runtime@^7.21.0", "@babel/runtime@^7.22.5": + version "7.23.2" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.23.2.tgz#062b0ac103261d68a966c4c7baf2ae3e62ec3885" + integrity sha512-mM8eg4yl5D6i3lu2QKPuPH4FArvJ8KhTofbE7jwMUv9KX5mBvwPAqnV3MlyBNqdp9RyRKP6Yck8TrfYrPvX3bg== + dependencies: + regenerator-runtime "^0.14.0" + +"@babel/template@^7.22.15": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.15.tgz#09576efc3830f0430f4548ef971dde1350ef2f38" + integrity sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w== + dependencies: + "@babel/code-frame" "^7.22.13" + "@babel/parser" "^7.22.15" + "@babel/types" "^7.22.15" + +"@babel/traverse@^7.23.2": + version "7.23.2" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.23.2.tgz#329c7a06735e144a506bdb2cad0268b7f46f4ad8" + integrity sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw== + dependencies: + "@babel/code-frame" "^7.22.13" + "@babel/generator" "^7.23.0" + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-function-name" "^7.23.0" + "@babel/helper-hoist-variables" "^7.22.5" + "@babel/helper-split-export-declaration" "^7.22.6" + "@babel/parser" "^7.23.0" + "@babel/types" "^7.23.0" + debug "^4.1.0" + globals "^11.1.0" -"@babel/helper-builder-binary-assignment-operator-visitor@^7.18.6": - version "7.18.9" - resolved "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.18.9.tgz#acd4edfd7a566d1d51ea975dff38fd52906981bb" - integrity sha512-yFQ0YCHoIqarl8BCRwBL8ulYUaZpz3bNsA7oFepAzee+8/+ImtADXNOmO5vJvsPff3qi+hvpkY/NYBTrBQgdNw== +"@babel/types@^7.21.3", "@babel/types@^7.22.15", "@babel/types@^7.22.5", "@babel/types@^7.23.0": + version "7.23.0" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.23.0.tgz#8c1f020c9df0e737e4e247c0619f58c68458aaeb" + integrity sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg== dependencies: - "@babel/helper-explode-assignable-expression" "^7.18.6" - "@babel/types" "^7.18.9" + "@babel/helper-string-parser" "^7.22.5" + "@babel/helper-validator-identifier" "^7.22.20" + to-fast-properties "^2.0.0" -"@babel/helper-compilation-targets@^7.17.7", "@babel/helper-compilation-targets@^7.18.9", "@babel/helper-compilation-targets@^7.20.0": - version "7.20.0" - resolved "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.0.tgz#6bf5374d424e1b3922822f1d9bdaa43b1a139d0a" - integrity sha512-0jp//vDGp9e8hZzBc6N/KwA5ZK3Wsm/pfm4CrY7vzegkVxc65SgSn6wYOnwHe9Js9HRQ1YTCKLGPzDtaS3RoLQ== +"@chainsafe/metamask-polkadot-adapter@^0.5.1": + version "0.5.1" + resolved "https://registry.yarnpkg.com/@chainsafe/metamask-polkadot-adapter/-/metamask-polkadot-adapter-0.5.1.tgz#04ecf78ce6cdcc63ebdc2c97f67d0c1efdb8bee8" + integrity sha512-t3/KoGTgayt2qQOmEWj5Gx8UVvqjhEuybMYadInWsvsDvmyhSKUxzGC8JlUSr4ww0Fa8Dzem21Hxo0FT9rxjyg== dependencies: - "@babel/compat-data" "^7.20.0" - "@babel/helper-validator-option" "^7.18.6" - browserslist "^4.21.3" - semver "^6.3.0" + "@polkadot/api" "^10.9.1" + "@polkadot/extension-inject" "^0.46.5" + "@polkadot/types-augment" "^10.9.1" -"@babel/helper-create-class-features-plugin@^7.18.6", "@babel/helper-create-class-features-plugin@^7.20.2": - version "7.20.2" - resolved "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.20.2.tgz#3c08a5b5417c7f07b5cf3dfb6dc79cbec682e8c2" - integrity sha512-k22GoYRAHPYr9I+Gvy2ZQlAe5mGy8BqWst2wRt8cwIufWTxrsVshhIBvYNqC80N0GSFWTsqRVexOtfzlgOEDvA== +"@dotlottie/player-component@^2.7.0": + version "2.7.0" + resolved "https://registry.yarnpkg.com/@dotlottie/player-component/-/player-component-2.7.0.tgz#b7f5edfa9a54a0d99eb5cd85b94f0aebcd9270b3" + integrity sha512-UZiBEViLVSjXGRZKHgFDRAFHettwKWJgh3eBriGY4ljNfaKxbItx/+KWVpUgPQxDGmIQt1ouMyDeQEZGwonCjQ== dependencies: - "@babel/helper-annotate-as-pure" "^7.18.6" - "@babel/helper-environment-visitor" "^7.18.9" - "@babel/helper-function-name" "^7.19.0" - "@babel/helper-member-expression-to-functions" "^7.18.9" - "@babel/helper-optimise-call-expression" "^7.18.6" - "@babel/helper-replace-supers" "^7.19.1" - "@babel/helper-split-export-declaration" "^7.18.6" + lit "^2.7.5" -"@babel/helper-create-regexp-features-plugin@^7.18.6", "@babel/helper-create-regexp-features-plugin@^7.19.0": - version "7.19.0" - resolved "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.19.0.tgz#7976aca61c0984202baca73d84e2337a5424a41b" - integrity sha512-htnV+mHX32DF81amCDrwIDr8nrp1PTm+3wfBN9/v8QJOLEioOCOG7qNyq0nHeFiWbT3Eb7gsPwEmV64UCQ1jzw== +"@emotion/is-prop-valid@^0.8.2": + version "0.8.8" + resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz#db28b1c4368a259b60a97311d6a952d4fd01ac1a" + integrity sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA== dependencies: - "@babel/helper-annotate-as-pure" "^7.18.6" - regexpu-core "^5.1.0" + "@emotion/memoize" "0.7.4" -"@babel/helper-define-polyfill-provider@^0.3.3": - version "0.3.3" - resolved "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.3.tgz#8612e55be5d51f0cd1f36b4a5a83924e89884b7a" - integrity sha512-z5aQKU4IzbqCC1XH0nAqfsFLMVSo22SBKUc0BxGrLkolTdPTructy0ToNnlO2zA4j9Q/7pjMZf0DSY+DSTYzww== +"@emotion/is-prop-valid@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-1.2.1.tgz#23116cf1ed18bfeac910ec6436561ecb1a3885cc" + integrity sha512-61Mf7Ufx4aDxx1xlDeOm8aFFigGHE4z+0sKCa+IHCeZKiyP9RLD0Mmx7m8b9/Cf37f7NAvQOOJAbQQGVr5uERw== dependencies: - "@babel/helper-compilation-targets" "^7.17.7" - "@babel/helper-plugin-utils" "^7.16.7" - debug "^4.1.1" - lodash.debounce "^4.0.8" - resolve "^1.14.2" - semver "^6.1.2" - -"@babel/helper-environment-visitor@^7.18.9": - version "7.18.9" - resolved "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz#0c0cee9b35d2ca190478756865bb3528422f51be" - integrity sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg== - -"@babel/helper-explode-assignable-expression@^7.18.6": - version "7.18.6" - resolved "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.18.6.tgz#41f8228ef0a6f1a036b8dfdfec7ce94f9a6bc096" - integrity sha512-eyAYAsQmB80jNfg4baAtLeWAQHfHFiR483rzFK+BhETlGZaQC9bsfrugfXDCbRHLQbIA7U5NxhhOxN7p/dWIcg== - dependencies: - "@babel/types" "^7.18.6" - -"@babel/helper-function-name@^7.18.9", "@babel/helper-function-name@^7.19.0": - version "7.19.0" - resolved "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz#941574ed5390682e872e52d3f38ce9d1bef4648c" - integrity sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w== - dependencies: - "@babel/template" "^7.18.10" - "@babel/types" "^7.19.0" - -"@babel/helper-hoist-variables@^7.18.6": - version "7.18.6" - resolved "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz#d4d2c8fb4baeaa5c68b99cc8245c56554f926678" - integrity sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q== - dependencies: - "@babel/types" "^7.18.6" - -"@babel/helper-member-expression-to-functions@^7.18.9": - version "7.18.9" - resolved "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.18.9.tgz#1531661e8375af843ad37ac692c132841e2fd815" - integrity sha512-RxifAh2ZoVU67PyKIO4AMi1wTenGfMR/O/ae0CCRqwgBAt5v7xjdtRw7UoSbsreKrQn5t7r89eruK/9JjYHuDg== - dependencies: - "@babel/types" "^7.18.9" - -"@babel/helper-module-imports@^7.0.0", "@babel/helper-module-imports@^7.10.4", "@babel/helper-module-imports@^7.16.0", "@babel/helper-module-imports@^7.18.6": - version "7.18.6" - resolved "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz#1e3ebdbbd08aad1437b428c50204db13c5a3ca6e" - integrity sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA== - dependencies: - "@babel/types" "^7.18.6" - -"@babel/helper-module-transforms@^7.18.6", "@babel/helper-module-transforms@^7.19.6", "@babel/helper-module-transforms@^7.20.2": - version "7.20.2" - resolved "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.20.2.tgz#ac53da669501edd37e658602a21ba14c08748712" - integrity sha512-zvBKyJXRbmK07XhMuujYoJ48B5yvvmM6+wcpv6Ivj4Yg6qO7NOZOSnvZN9CRl1zz1Z4cKf8YejmCMh8clOoOeA== - dependencies: - "@babel/helper-environment-visitor" "^7.18.9" - "@babel/helper-module-imports" "^7.18.6" - "@babel/helper-simple-access" "^7.20.2" - "@babel/helper-split-export-declaration" "^7.18.6" - "@babel/helper-validator-identifier" "^7.19.1" - "@babel/template" "^7.18.10" - "@babel/traverse" "^7.20.1" - "@babel/types" "^7.20.2" - -"@babel/helper-optimise-call-expression@^7.18.6": - version "7.18.6" - resolved "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.18.6.tgz#9369aa943ee7da47edab2cb4e838acf09d290ffe" - integrity sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA== - dependencies: - "@babel/types" "^7.18.6" - -"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.16.7", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.18.9", "@babel/helper-plugin-utils@^7.19.0", "@babel/helper-plugin-utils@^7.20.2", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": - version "7.20.2" - resolved "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.20.2.tgz#d1b9000752b18d0877cff85a5c376ce5c3121629" - integrity sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ== - -"@babel/helper-remap-async-to-generator@^7.18.6", "@babel/helper-remap-async-to-generator@^7.18.9": - version "7.18.9" - resolved "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.18.9.tgz#997458a0e3357080e54e1d79ec347f8a8cd28519" - integrity sha512-dI7q50YKd8BAv3VEfgg7PS7yD3Rtbi2J1XMXaalXO0W0164hYLnh8zpjRS0mte9MfVp/tltvr/cfdXPvJr1opA== - dependencies: - "@babel/helper-annotate-as-pure" "^7.18.6" - "@babel/helper-environment-visitor" "^7.18.9" - "@babel/helper-wrap-function" "^7.18.9" - "@babel/types" "^7.18.9" - -"@babel/helper-replace-supers@^7.18.6", "@babel/helper-replace-supers@^7.19.1": - version "7.19.1" - resolved "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.19.1.tgz#e1592a9b4b368aa6bdb8784a711e0bcbf0612b78" - integrity sha512-T7ahH7wV0Hfs46SFh5Jz3s0B6+o8g3c+7TMxu7xKfmHikg7EAZ3I2Qk9LFhjxXq8sL7UkP5JflezNwoZa8WvWw== - dependencies: - "@babel/helper-environment-visitor" "^7.18.9" - "@babel/helper-member-expression-to-functions" "^7.18.9" - "@babel/helper-optimise-call-expression" "^7.18.6" - "@babel/traverse" "^7.19.1" - "@babel/types" "^7.19.0" - -"@babel/helper-simple-access@^7.19.4", "@babel/helper-simple-access@^7.20.2": - version "7.20.2" - resolved "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.20.2.tgz#0ab452687fe0c2cfb1e2b9e0015de07fc2d62dd9" - integrity sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA== - dependencies: - "@babel/types" "^7.20.2" - -"@babel/helper-skip-transparent-expression-wrappers@^7.18.9": - version "7.20.0" - resolved "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.20.0.tgz#fbe4c52f60518cab8140d77101f0e63a8a230684" - integrity sha512-5y1JYeNKfvnT8sZcK9DVRtpTbGiomYIHviSP3OQWmDPU3DeH4a1ZlT/N2lyQ5P8egjcRaT/Y9aNqUxK0WsnIIg== - dependencies: - "@babel/types" "^7.20.0" - -"@babel/helper-split-export-declaration@^7.18.6": - version "7.18.6" - resolved "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz#7367949bc75b20c6d5a5d4a97bba2824ae8ef075" - integrity sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA== - dependencies: - "@babel/types" "^7.18.6" - -"@babel/helper-string-parser@^7.19.4": - version "7.19.4" - resolved "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz#38d3acb654b4701a9b77fb0615a96f775c3a9e63" - integrity sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw== - -"@babel/helper-validator-identifier@^7.18.6", "@babel/helper-validator-identifier@^7.19.1": - version "7.19.1" - resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz#7eea834cf32901ffdc1a7ee555e2f9c27e249ca2" - integrity sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w== - -"@babel/helper-validator-option@^7.18.6": - version "7.18.6" - resolved "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz#bf0d2b5a509b1f336099e4ff36e1a63aa5db4db8" - integrity sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw== - -"@babel/helper-wrap-function@^7.18.9": - version "7.19.0" - resolved "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.19.0.tgz#89f18335cff1152373222f76a4b37799636ae8b1" - integrity sha512-txX8aN8CZyYGTwcLhlk87KRqncAzhh5TpQamZUa0/u3an36NtDpUP6bQgBCBcLeBs09R/OwQu3OjK0k/HwfNDg== - dependencies: - "@babel/helper-function-name" "^7.19.0" - "@babel/template" "^7.18.10" - "@babel/traverse" "^7.19.0" - "@babel/types" "^7.19.0" - -"@babel/helpers@^7.20.1": - version "7.20.1" - resolved "https://registry.npmjs.org/@babel/helpers/-/helpers-7.20.1.tgz#2ab7a0fcb0a03b5bf76629196ed63c2d7311f4c9" - integrity sha512-J77mUVaDTUJFZ5BpP6mMn6OIl3rEWymk2ZxDBQJUG3P+PbmyMcF3bYWvz0ma69Af1oobDqT/iAsvzhB58xhQUg== - dependencies: - "@babel/template" "^7.18.10" - "@babel/traverse" "^7.20.1" - "@babel/types" "^7.20.0" - -"@babel/highlight@^7.18.6": - version "7.18.6" - resolved "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz#81158601e93e2563795adcbfbdf5d64be3f2ecdf" - integrity sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g== - dependencies: - "@babel/helper-validator-identifier" "^7.18.6" - chalk "^2.0.0" - js-tokens "^4.0.0" + "@emotion/memoize" "^0.8.1" -"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.18.10", "@babel/parser@^7.20.1", "@babel/parser@^7.20.2": - version "7.20.3" - resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.20.3.tgz#5358cf62e380cf69efcb87a7bb922ff88bfac6e2" - integrity sha512-OP/s5a94frIPXwjzEcv5S/tpQfc6XhxYUnmWpgdqMWGgYCuErA3SzozaRAMQgSZWKeTJxht9aWAkUY+0UzvOFg== - -"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.18.6": - version "7.18.6" - resolved "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.18.6.tgz#da5b8f9a580acdfbe53494dba45ea389fb09a4d2" - integrity sha512-Dgxsyg54Fx1d4Nge8UnvTrED63vrwOdPmyvPzlNN/boaliRP54pm3pGzZD1SJUwrBA+Cs/xdG8kXX6Mn/RfISQ== - dependencies: - "@babel/helper-plugin-utils" "^7.18.6" - -"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.18.9": - version "7.18.9" - resolved "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.18.9.tgz#a11af19aa373d68d561f08e0a57242350ed0ec50" - integrity sha512-AHrP9jadvH7qlOj6PINbgSuphjQUAK7AOT7DPjBo9EHoLhQTnnK5u45e1Hd4DbSQEO9nqPWtQ89r+XEOWFScKg== - dependencies: - "@babel/helper-plugin-utils" "^7.18.9" - "@babel/helper-skip-transparent-expression-wrappers" "^7.18.9" - "@babel/plugin-proposal-optional-chaining" "^7.18.9" - -"@babel/plugin-proposal-async-generator-functions@^7.20.1": - version "7.20.1" - resolved "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.20.1.tgz#352f02baa5d69f4e7529bdac39aaa02d41146af9" - integrity sha512-Gh5rchzSwE4kC+o/6T8waD0WHEQIsDmjltY8WnWRXHUdH8axZhuH86Ov9M72YhJfDrZseQwuuWaaIT/TmePp3g== - dependencies: - "@babel/helper-environment-visitor" "^7.18.9" - "@babel/helper-plugin-utils" "^7.19.0" - "@babel/helper-remap-async-to-generator" "^7.18.9" - "@babel/plugin-syntax-async-generators" "^7.8.4" - -"@babel/plugin-proposal-class-properties@^7.16.0", "@babel/plugin-proposal-class-properties@^7.18.6": - version "7.18.6" - resolved "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz#b110f59741895f7ec21a6fff696ec46265c446a3" - integrity sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ== - dependencies: - "@babel/helper-create-class-features-plugin" "^7.18.6" - "@babel/helper-plugin-utils" "^7.18.6" - -"@babel/plugin-proposal-class-static-block@^7.18.6": - version "7.18.6" - resolved "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.18.6.tgz#8aa81d403ab72d3962fc06c26e222dacfc9b9020" - integrity sha512-+I3oIiNxrCpup3Gi8n5IGMwj0gOCAjcJUSQEcotNnCCPMEnixawOQ+KeJPlgfjzx+FKQ1QSyZOWe7wmoJp7vhw== - dependencies: - "@babel/helper-create-class-features-plugin" "^7.18.6" - "@babel/helper-plugin-utils" "^7.18.6" - "@babel/plugin-syntax-class-static-block" "^7.14.5" - -"@babel/plugin-proposal-decorators@^7.16.4": - version "7.20.2" - resolved "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.20.2.tgz#1c6c32b2a44b154ebeec2bb534f9eaebdb541fb6" - integrity sha512-nkBH96IBmgKnbHQ5gXFrcmez+Z9S2EIDKDQGp005ROqBigc88Tky4rzCnlP/lnlj245dCEQl4/YyV0V1kYh5dw== - dependencies: - "@babel/helper-create-class-features-plugin" "^7.20.2" - "@babel/helper-plugin-utils" "^7.20.2" - "@babel/helper-replace-supers" "^7.19.1" - "@babel/helper-split-export-declaration" "^7.18.6" - "@babel/plugin-syntax-decorators" "^7.19.0" - -"@babel/plugin-proposal-dynamic-import@^7.18.6": - version "7.18.6" - resolved "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.18.6.tgz#72bcf8d408799f547d759298c3c27c7e7faa4d94" - integrity sha512-1auuwmK+Rz13SJj36R+jqFPMJWyKEDd7lLSdOj4oJK0UTgGueSAtkrCvz9ewmgyU/P941Rv2fQwZJN8s6QruXw== - dependencies: - "@babel/helper-plugin-utils" "^7.18.6" - "@babel/plugin-syntax-dynamic-import" "^7.8.3" - -"@babel/plugin-proposal-export-namespace-from@^7.18.9": - version "7.18.9" - resolved "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.18.9.tgz#5f7313ab348cdb19d590145f9247540e94761203" - integrity sha512-k1NtHyOMvlDDFeb9G5PhUXuGj8m/wiwojgQVEhJ/fsVsMCpLyOP4h0uGEjYJKrRI+EVPlb5Jk+Gt9P97lOGwtA== - dependencies: - "@babel/helper-plugin-utils" "^7.18.9" - "@babel/plugin-syntax-export-namespace-from" "^7.8.3" - -"@babel/plugin-proposal-json-strings@^7.18.6": - version "7.18.6" - resolved "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.18.6.tgz#7e8788c1811c393aff762817e7dbf1ebd0c05f0b" - integrity sha512-lr1peyn9kOdbYc0xr0OdHTZ5FMqS6Di+H0Fz2I/JwMzGmzJETNeOFq2pBySw6X/KFL5EWDjlJuMsUGRFb8fQgQ== - dependencies: - "@babel/helper-plugin-utils" "^7.18.6" - "@babel/plugin-syntax-json-strings" "^7.8.3" - -"@babel/plugin-proposal-logical-assignment-operators@^7.18.9": - version "7.18.9" - resolved "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.18.9.tgz#8148cbb350483bf6220af06fa6db3690e14b2e23" - integrity sha512-128YbMpjCrP35IOExw2Fq+x55LMP42DzhOhX2aNNIdI9avSWl2PI0yuBWarr3RYpZBSPtabfadkH2yeRiMD61Q== - dependencies: - "@babel/helper-plugin-utils" "^7.18.9" - "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" - -"@babel/plugin-proposal-nullish-coalescing-operator@^7.16.0", "@babel/plugin-proposal-nullish-coalescing-operator@^7.18.6": - version "7.18.6" - resolved "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz#fdd940a99a740e577d6c753ab6fbb43fdb9467e1" - integrity sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA== - dependencies: - "@babel/helper-plugin-utils" "^7.18.6" - "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" - -"@babel/plugin-proposal-numeric-separator@^7.16.0", "@babel/plugin-proposal-numeric-separator@^7.18.6": - version "7.18.6" - resolved "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.18.6.tgz#899b14fbafe87f053d2c5ff05b36029c62e13c75" - integrity sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q== - dependencies: - "@babel/helper-plugin-utils" "^7.18.6" - "@babel/plugin-syntax-numeric-separator" "^7.10.4" - -"@babel/plugin-proposal-object-rest-spread@^7.20.2": - version "7.20.2" - resolved "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.20.2.tgz#a556f59d555f06961df1e572bb5eca864c84022d" - integrity sha512-Ks6uej9WFK+fvIMesSqbAto5dD8Dz4VuuFvGJFKgIGSkJuRGcrwGECPA1fDgQK3/DbExBJpEkTeYeB8geIFCSQ== - dependencies: - "@babel/compat-data" "^7.20.1" - "@babel/helper-compilation-targets" "^7.20.0" - "@babel/helper-plugin-utils" "^7.20.2" - "@babel/plugin-syntax-object-rest-spread" "^7.8.3" - "@babel/plugin-transform-parameters" "^7.20.1" - -"@babel/plugin-proposal-optional-catch-binding@^7.18.6": - version "7.18.6" - resolved "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.18.6.tgz#f9400d0e6a3ea93ba9ef70b09e72dd6da638a2cb" - integrity sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw== - dependencies: - "@babel/helper-plugin-utils" "^7.18.6" - "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" +"@emotion/memoize@0.7.4": + version "0.7.4" + resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.7.4.tgz#19bf0f5af19149111c40d98bb0cf82119f5d9eeb" + integrity sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw== -"@babel/plugin-proposal-optional-chaining@^7.16.0", "@babel/plugin-proposal-optional-chaining@^7.18.9": - version "7.18.9" - resolved "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.18.9.tgz#e8e8fe0723f2563960e4bf5e9690933691915993" - integrity sha512-v5nwt4IqBXihxGsW2QmCWMDS3B3bzGIk/EQVZz2ei7f3NJl8NzAJVvUmpDW5q1CRNY+Beb/k58UAH1Km1N411w== - dependencies: - "@babel/helper-plugin-utils" "^7.18.9" - "@babel/helper-skip-transparent-expression-wrappers" "^7.18.9" - "@babel/plugin-syntax-optional-chaining" "^7.8.3" +"@emotion/memoize@^0.8.1": + version "0.8.1" + resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.8.1.tgz#c1ddb040429c6d21d38cc945fe75c818cfb68e17" + integrity sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA== -"@babel/plugin-proposal-private-methods@^7.16.0", "@babel/plugin-proposal-private-methods@^7.18.6": - version "7.18.6" - resolved "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.18.6.tgz#5209de7d213457548a98436fa2882f52f4be6bea" - integrity sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA== +"@emotion/unitless@^0.8.0": + version "0.8.1" + resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.8.1.tgz#182b5a4704ef8ad91bde93f7a860a88fd92c79a3" + integrity sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ== + +"@esbuild/android-arm64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz#984b4f9c8d0377443cc2dfcef266d02244593622" + integrity sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ== + +"@esbuild/android-arm@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.18.20.tgz#fedb265bc3a589c84cc11f810804f234947c3682" + integrity sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw== + +"@esbuild/android-x64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.18.20.tgz#35cf419c4cfc8babe8893d296cd990e9e9f756f2" + integrity sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg== + +"@esbuild/darwin-arm64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz#08172cbeccf95fbc383399a7f39cfbddaeb0d7c1" + integrity sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA== + +"@esbuild/darwin-x64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz#d70d5790d8bf475556b67d0f8b7c5bdff053d85d" + integrity sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ== + +"@esbuild/freebsd-arm64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz#98755cd12707f93f210e2494d6a4b51b96977f54" + integrity sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw== + +"@esbuild/freebsd-x64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz#c1eb2bff03915f87c29cece4c1a7fa1f423b066e" + integrity sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ== + +"@esbuild/linux-arm64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz#bad4238bd8f4fc25b5a021280c770ab5fc3a02a0" + integrity sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA== + +"@esbuild/linux-arm@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz#3e617c61f33508a27150ee417543c8ab5acc73b0" + integrity sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg== + +"@esbuild/linux-ia32@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz#699391cccba9aee6019b7f9892eb99219f1570a7" + integrity sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA== + +"@esbuild/linux-loong64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz#e6fccb7aac178dd2ffb9860465ac89d7f23b977d" + integrity sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg== + +"@esbuild/linux-mips64el@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz#eeff3a937de9c2310de30622a957ad1bd9183231" + integrity sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ== + +"@esbuild/linux-ppc64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz#2f7156bde20b01527993e6881435ad79ba9599fb" + integrity sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA== + +"@esbuild/linux-riscv64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz#6628389f210123d8b4743045af8caa7d4ddfc7a6" + integrity sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A== + +"@esbuild/linux-s390x@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz#255e81fb289b101026131858ab99fba63dcf0071" + integrity sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ== + +"@esbuild/linux-x64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz#c7690b3417af318a9b6f96df3031a8865176d338" + integrity sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w== + +"@esbuild/netbsd-x64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz#30e8cd8a3dded63975e2df2438ca109601ebe0d1" + integrity sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A== + +"@esbuild/openbsd-x64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz#7812af31b205055874c8082ea9cf9ab0da6217ae" + integrity sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg== + +"@esbuild/sunos-x64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz#d5c275c3b4e73c9b0ecd38d1ca62c020f887ab9d" + integrity sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ== + +"@esbuild/win32-arm64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz#73bc7f5a9f8a77805f357fab97f290d0e4820ac9" + integrity sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg== + +"@esbuild/win32-ia32@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz#ec93cbf0ef1085cc12e71e0d661d20569ff42102" + integrity sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g== + +"@esbuild/win32-x64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz#786c5f41f043b07afb1af37683d7c33668858f6d" + integrity sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ== + +"@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0": + version "4.4.0" + resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59" + integrity sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA== dependencies: - "@babel/helper-create-class-features-plugin" "^7.18.6" - "@babel/helper-plugin-utils" "^7.18.6" + eslint-visitor-keys "^3.3.0" -"@babel/plugin-proposal-private-property-in-object@^7.18.6": - version "7.18.6" - resolved "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.18.6.tgz#a64137b232f0aca3733a67eb1a144c192389c503" - integrity sha512-9Rysx7FOctvT5ouj5JODjAFAkgGoudQuLPamZb0v1TGLpapdNaftzifU8NTWQm0IRjqoYypdrSmyWgkocDQ8Dw== - dependencies: - "@babel/helper-annotate-as-pure" "^7.18.6" - "@babel/helper-create-class-features-plugin" "^7.18.6" - "@babel/helper-plugin-utils" "^7.18.6" - "@babel/plugin-syntax-private-property-in-object" "^7.14.5" +"@eslint-community/regexpp@^4.5.1", "@eslint-community/regexpp@^4.6.1": + version "4.10.0" + resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.10.0.tgz#548f6de556857c8bb73bbee70c35dc82a2e74d63" + integrity sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA== -"@babel/plugin-proposal-unicode-property-regex@^7.18.6", "@babel/plugin-proposal-unicode-property-regex@^7.4.4": - version "7.18.6" - resolved "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.18.6.tgz#af613d2cd5e643643b65cded64207b15c85cb78e" - integrity sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w== +"@eslint/eslintrc@^2.1.2": + version "2.1.2" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.1.2.tgz#c6936b4b328c64496692f76944e755738be62396" + integrity sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g== dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.18.6" - "@babel/helper-plugin-utils" "^7.18.6" + ajv "^6.12.4" + debug "^4.3.2" + espree "^9.6.0" + globals "^13.19.0" + ignore "^5.2.0" + import-fresh "^3.2.1" + js-yaml "^4.1.0" + minimatch "^3.1.2" + strip-json-comments "^3.1.1" -"@babel/plugin-syntax-async-generators@^7.8.4": - version "7.8.4" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d" - integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" +"@eslint/js@8.52.0": + version "8.52.0" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.52.0.tgz#78fe5f117840f69dc4a353adf9b9cd926353378c" + integrity sha512-mjZVbpaeMZludF2fsWLD0Z9gCref1Tk4i9+wddjRvpUNqqcndPkBD09N/Mapey0b3jaXbLm2kICwFv2E64QinA== -"@babel/plugin-syntax-bigint@^7.8.3": - version "7.8.3" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz#4c9a6f669f5d0cdf1b90a1671e9a146be5300cea" - integrity sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" +"@fortawesome/fontawesome-common-types@6.4.2": + version "6.4.2" + resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.4.2.tgz#1766039cad33f8ad87f9467b98e0d18fbc8f01c5" + integrity sha512-1DgP7f+XQIJbLFCTX1V2QnxVmpLdKdzzo2k8EmvDOePfchaIGQ9eCHj2up3/jNEbZuBqel5OxiaOJf37TWauRA== -"@babel/plugin-syntax-class-properties@^7.12.13", "@babel/plugin-syntax-class-properties@^7.8.3": - version "7.12.13" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz#b5c987274c4a3a82b89714796931a6b53544ae10" - integrity sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA== +"@fortawesome/fontawesome-svg-core@^6.4.2": + version "6.4.2" + resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.4.2.tgz#37f4507d5ec645c8b50df6db14eced32a6f9be09" + integrity sha512-gjYDSKv3TrM2sLTOKBc5rH9ckje8Wrwgx1CxAPbN5N3Fm4prfi7NsJVWd1jklp7i5uSCVwhZS5qlhMXqLrpAIg== dependencies: - "@babel/helper-plugin-utils" "^7.12.13" + "@fortawesome/fontawesome-common-types" "6.4.2" -"@babel/plugin-syntax-class-static-block@^7.14.5": - version "7.14.5" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz#195df89b146b4b78b3bf897fd7a257c84659d406" - integrity sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw== +"@fortawesome/free-brands-svg-icons@^6.4.2": + version "6.4.2" + resolved "https://registry.yarnpkg.com/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-6.4.2.tgz#9b8e78066ea6dd563da5dfa686615791d0f7cc71" + integrity sha512-LKOwJX0I7+mR/cvvf6qIiqcERbdnY+24zgpUSouySml+5w8B4BJOx8EhDR/FTKAu06W12fmUIcv6lzPSwYKGGg== dependencies: - "@babel/helper-plugin-utils" "^7.14.5" + "@fortawesome/fontawesome-common-types" "6.4.2" -"@babel/plugin-syntax-decorators@^7.19.0": - version "7.19.0" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.19.0.tgz#5f13d1d8fce96951bea01a10424463c9a5b3a599" - integrity sha512-xaBZUEDntt4faL1yN8oIFlhfXeQAWJW7CLKYsHTUqriCUbj8xOra8bfxxKGi/UwExPFBuPdH4XfHc9rGQhrVkQ== +"@fortawesome/free-regular-svg-icons@^6.4.2": + version "6.4.2" + resolved "https://registry.yarnpkg.com/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-6.4.2.tgz#aee79ed76ce5dd04931352f9d83700761b8b1b25" + integrity sha512-0+sIUWnkgTVVXVAPQmW4vxb9ZTHv0WstOa3rBx9iPxrrrDH6bNLsDYuwXF9b6fGm+iR7DKQvQshUH/FJm3ed9Q== dependencies: - "@babel/helper-plugin-utils" "^7.19.0" + "@fortawesome/fontawesome-common-types" "6.4.2" -"@babel/plugin-syntax-dynamic-import@^7.8.3": - version "7.8.3" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz#62bf98b2da3cd21d626154fc96ee5b3cb68eacb3" - integrity sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ== +"@fortawesome/free-solid-svg-icons@^6.4.2": + version "6.4.2" + resolved "https://registry.yarnpkg.com/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.4.2.tgz#33a02c4cb6aa28abea7bc082a9626b7922099df4" + integrity sha512-sYwXurXUEQS32fZz9hVCUUv/xu49PEJEyUOsA51l6PU/qVgfbTb2glsTEaJngVVT8VqBATRIdh7XVgV1JF1LkA== dependencies: - "@babel/helper-plugin-utils" "^7.8.0" + "@fortawesome/fontawesome-common-types" "6.4.2" -"@babel/plugin-syntax-export-namespace-from@^7.8.3": - version "7.8.3" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz#028964a9ba80dbc094c915c487ad7c4e7a66465a" - integrity sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q== +"@fortawesome/react-fontawesome@^0.2.0": + version "0.2.0" + resolved "https://registry.yarnpkg.com/@fortawesome/react-fontawesome/-/react-fontawesome-0.2.0.tgz#d90dd8a9211830b4e3c08e94b63a0ba7291ddcf4" + integrity sha512-uHg75Rb/XORTtVt7OS9WoK8uM276Ufi7gCzshVWkUJbHhh3svsUUeqXerrM96Wm7fRiDzfKRwSoahhMIkGAYHw== dependencies: - "@babel/helper-plugin-utils" "^7.8.3" + prop-types "^15.8.1" -"@babel/plugin-syntax-flow@^7.18.6": - version "7.18.6" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.18.6.tgz#774d825256f2379d06139be0c723c4dd444f3ca1" - integrity sha512-LUbR+KNTBWCUAqRG9ex5Gnzu2IOkt8jRJbHHXFT9q+L9zm7M/QQbEqXyw1n1pohYvOyWC8CjeyjrSaIwiYjK7A== - dependencies: - "@babel/helper-plugin-utils" "^7.18.6" +"@graphql-typed-document-node/core@^3.1.1": + version "3.2.0" + resolved "https://registry.yarnpkg.com/@graphql-typed-document-node/core/-/core-3.2.0.tgz#5f3d96ec6b2354ad6d8a28bf216a1d97b5426861" + integrity sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ== -"@babel/plugin-syntax-import-assertions@^7.20.0": - version "7.20.0" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.20.0.tgz#bb50e0d4bea0957235390641209394e87bdb9cc4" - integrity sha512-IUh1vakzNoWalR8ch/areW7qFopR2AEw03JlG7BbrDqmQ4X3q9uuipQwSGrUn7oGiemKjtSLDhNtQHzMHr1JdQ== +"@humanwhocodes/config-array@^0.11.13": + version "0.11.13" + resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.13.tgz#075dc9684f40a531d9b26b0822153c1e832ee297" + integrity sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ== dependencies: - "@babel/helper-plugin-utils" "^7.19.0" + "@humanwhocodes/object-schema" "^2.0.1" + debug "^4.1.1" + minimatch "^3.0.5" -"@babel/plugin-syntax-import-meta@^7.8.3": - version "7.10.4" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51" - integrity sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" +"@humanwhocodes/module-importer@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" + integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== -"@babel/plugin-syntax-json-strings@^7.8.3": - version "7.8.3" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a" - integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" +"@humanwhocodes/object-schema@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz#e5211452df060fa8522b55c7b3c0c4d1981cb044" + integrity sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw== -"@babel/plugin-syntax-jsx@^7.18.6": - version "7.18.6" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.18.6.tgz#a8feef63b010150abd97f1649ec296e849943ca0" - integrity sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q== +"@jest/schemas@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.3.tgz#430b5ce8a4e0044a7e3819663305a7b3091c8e03" + integrity sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA== dependencies: - "@babel/helper-plugin-utils" "^7.18.6" + "@sinclair/typebox" "^0.27.8" -"@babel/plugin-syntax-logical-assignment-operators@^7.10.4", "@babel/plugin-syntax-logical-assignment-operators@^7.8.3": - version "7.10.4" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699" - integrity sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig== +"@jridgewell/gen-mapping@^0.3.0", "@jridgewell/gen-mapping@^0.3.2": + version "0.3.3" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz#7e02e6eb5df901aaedb08514203b096614024098" + integrity sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ== dependencies: - "@babel/helper-plugin-utils" "^7.10.4" + "@jridgewell/set-array" "^1.0.1" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping" "^0.3.9" -"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.3": - version "7.8.3" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9" - integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" +"@jridgewell/resolve-uri@^3.1.0": + version "3.1.1" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz#c08679063f279615a3326583ba3a90d1d82cc721" + integrity sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA== -"@babel/plugin-syntax-numeric-separator@^7.10.4", "@babel/plugin-syntax-numeric-separator@^7.8.3": - version "7.10.4" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz#b9b070b3e33570cd9fd07ba7fa91c0dd37b9af97" - integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" +"@jridgewell/set-array@^1.0.1": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72" + integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== -"@babel/plugin-syntax-object-rest-spread@^7.8.3": - version "7.8.3" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871" - integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" +"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.4.15": + version "1.4.15" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" + integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== -"@babel/plugin-syntax-optional-catch-binding@^7.8.3": - version "7.8.3" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1" - integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q== +"@jridgewell/trace-mapping@^0.3.17", "@jridgewell/trace-mapping@^0.3.9": + version "0.3.20" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz#72e45707cf240fa6b081d0366f8265b0cd10197f" + integrity sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q== dependencies: - "@babel/helper-plugin-utils" "^7.8.0" + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" -"@babel/plugin-syntax-optional-chaining@^7.8.3": - version "7.8.3" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a" - integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" +"@kurkle/color@^0.3.0": + version "0.3.2" + resolved "https://registry.yarnpkg.com/@kurkle/color/-/color-0.3.2.tgz#5acd38242e8bde4f9986e7913c8fdf49d3aa199f" + integrity sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw== -"@babel/plugin-syntax-private-property-in-object@^7.14.5": - version "7.14.5" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz#0dc6671ec0ea22b6e94a1114f857970cd39de1ad" - integrity sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg== +"@ledgerhq/devices@^8.0.7": + version "8.0.7" + resolved "https://registry.yarnpkg.com/@ledgerhq/devices/-/devices-8.0.7.tgz#206434dbd8a097529bbfc95f5eef94c2923c7578" + integrity sha512-BbPyET52lXnVs7CxJWrGYqmtGdbGzj+XnfCqLsDnA7QYr1CZREysxmie+Rr6BKpNDBRVesAovXjtaVaZOn+upw== dependencies: - "@babel/helper-plugin-utils" "^7.14.5" + "@ledgerhq/errors" "^6.14.0" + "@ledgerhq/logs" "^6.10.1" + rxjs "6" + semver "^7.3.5" -"@babel/plugin-syntax-top-level-await@^7.14.5", "@babel/plugin-syntax-top-level-await@^7.8.3": - version "7.14.5" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz#c1cfdadc35a646240001f06138247b741c34d94c" - integrity sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw== - dependencies: - "@babel/helper-plugin-utils" "^7.14.5" +"@ledgerhq/errors@^6.14.0": + version "6.14.0" + resolved "https://registry.yarnpkg.com/@ledgerhq/errors/-/errors-6.14.0.tgz#0bf253983773ef12eebce2091f463bc719223b37" + integrity sha512-ZWJw2Ti6Dq1Ott/+qYqJdDWeZm16qI3VNG5rFlb0TQ3UcAyLIQZbnnzzdcVVwVeZiEp66WIpINd/pBdqsHVyOA== -"@babel/plugin-syntax-typescript@^7.20.0", "@babel/plugin-syntax-typescript@^7.7.2": - version "7.20.0" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.20.0.tgz#4e9a0cfc769c85689b77a2e642d24e9f697fc8c7" - integrity sha512-rd9TkG+u1CExzS4SM1BlMEhMXwFLKVjOAFFCDx9PbX5ycJWDoWMcwdJH9RhkPu1dOgn5TrxLot/Gx6lWFuAUNQ== +"@ledgerhq/hw-transport-webhid@^6.27.19": + version "6.27.19" + resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport-webhid/-/hw-transport-webhid-6.27.19.tgz#5a655b497258d94ec6494db7b56e17dd0c610638" + integrity sha512-RMnktayqqLE2uFQDw9TKoW+WSP8KnT0ElKcIISf3sXVrzHD2y0moPk/wXOzGfi+cgN4uiKy86UD/5mgz3wlm6Q== dependencies: - "@babel/helper-plugin-utils" "^7.19.0" + "@ledgerhq/devices" "^8.0.7" + "@ledgerhq/errors" "^6.14.0" + "@ledgerhq/hw-transport" "^6.28.8" + "@ledgerhq/logs" "^6.10.1" -"@babel/plugin-transform-arrow-functions@^7.18.6": - version "7.18.6" - resolved "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.18.6.tgz#19063fcf8771ec7b31d742339dac62433d0611fe" - integrity sha512-9S9X9RUefzrsHZmKMbDXxweEH+YlE8JJEuat9FdvW9Qh1cw7W64jELCtWNkPBPX5En45uy28KGvA/AySqUh8CQ== +"@ledgerhq/hw-transport@^6.27.1", "@ledgerhq/hw-transport@^6.28.8": + version "6.28.8" + resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport/-/hw-transport-6.28.8.tgz#f99a5c71c5c09591e9bfb1b970c42aafbe81351f" + integrity sha512-XxQVl4htd018u/M66r0iu5nlHi+J6QfdPsORzDF6N39jaz+tMqItb7tUlXM/isggcuS5lc7GJo7NOuJ8rvHZaQ== dependencies: - "@babel/helper-plugin-utils" "^7.18.6" + "@ledgerhq/devices" "^8.0.7" + "@ledgerhq/errors" "^6.14.0" + events "^3.3.0" -"@babel/plugin-transform-async-to-generator@^7.18.6": - version "7.18.6" - resolved "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.18.6.tgz#ccda3d1ab9d5ced5265fdb13f1882d5476c71615" - integrity sha512-ARE5wZLKnTgPW7/1ftQmSi1CmkqqHo2DNmtztFhvgtOWSDfq0Cq9/9L+KnZNYSNrydBekhW3rwShduf59RoXag== - dependencies: - "@babel/helper-module-imports" "^7.18.6" - "@babel/helper-plugin-utils" "^7.18.6" - "@babel/helper-remap-async-to-generator" "^7.18.6" +"@ledgerhq/logs@^6.10.1": + version "6.10.1" + resolved "https://registry.yarnpkg.com/@ledgerhq/logs/-/logs-6.10.1.tgz#5bd16082261d7364eabb511c788f00937dac588d" + integrity sha512-z+ILK8Q3y+nfUl43ctCPuR4Y2bIxk/ooCQFwZxhtci1EhAtMDzMAx2W25qx8G1PPL9UUOdnUax19+F0OjXoj4w== -"@babel/plugin-transform-block-scoped-functions@^7.18.6": - version "7.18.6" - resolved "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.18.6.tgz#9187bf4ba302635b9d70d986ad70f038726216a8" - integrity sha512-ExUcOqpPWnliRcPqves5HJcJOvHvIIWfuS4sroBUenPuMdmW+SMHDakmtS7qOo13sVppmUijqeTv7qqGsvURpQ== - dependencies: - "@babel/helper-plugin-utils" "^7.18.6" +"@lit-labs/ssr-dom-shim@^1.0.0", "@lit-labs/ssr-dom-shim@^1.1.0": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.1.2.tgz#d693d972974a354034454ec1317eb6afd0b00312" + integrity sha512-jnOD+/+dSrfTWYfSXBXlo5l5f0q1UuJo3tkbMDCYA2lKUYq79jaxqtGEvnRoh049nt1vdo1+45RinipU6FGY2g== -"@babel/plugin-transform-block-scoping@^7.20.2": - version "7.20.2" - resolved "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.20.2.tgz#f59b1767e6385c663fd0bce655db6ca9c8b236ed" - integrity sha512-y5V15+04ry69OV2wULmwhEA6jwSWXO1TwAtIwiPXcvHcoOQUqpyMVd2bDsQJMW8AurjulIyUV8kDqtjSwHy1uQ== +"@lit/reactive-element@^1.3.0", "@lit/reactive-element@^1.6.0": + version "1.6.3" + resolved "https://registry.yarnpkg.com/@lit/reactive-element/-/reactive-element-1.6.3.tgz#25b4eece2592132845d303e091bad9b04cdcfe03" + integrity sha512-QuTgnG52Poic7uM1AN5yJ09QMe0O28e10XzSvWDz02TJiiKee4stsiownEIadWm8nYzyDAyT+gKzUoZmiWQtsQ== dependencies: - "@babel/helper-plugin-utils" "^7.20.2" + "@lit-labs/ssr-dom-shim" "^1.0.0" -"@babel/plugin-transform-classes@^7.20.2": - version "7.20.2" - resolved "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.20.2.tgz#c0033cf1916ccf78202d04be4281d161f6709bb2" - integrity sha512-9rbPp0lCVVoagvtEyQKSo5L8oo0nQS/iif+lwlAz29MccX2642vWDlSZK+2T2buxbopotId2ld7zZAzRfz9j1g== +"@noble/curves@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.2.0.tgz#92d7e12e4e49b23105a2555c6984d41733d65c35" + integrity sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw== dependencies: - "@babel/helper-annotate-as-pure" "^7.18.6" - "@babel/helper-compilation-targets" "^7.20.0" - "@babel/helper-environment-visitor" "^7.18.9" - "@babel/helper-function-name" "^7.19.0" - "@babel/helper-optimise-call-expression" "^7.18.6" - "@babel/helper-plugin-utils" "^7.20.2" - "@babel/helper-replace-supers" "^7.19.1" - "@babel/helper-split-export-declaration" "^7.18.6" - globals "^11.1.0" + "@noble/hashes" "1.3.2" -"@babel/plugin-transform-computed-properties@^7.18.9": - version "7.18.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.18.9.tgz#2357a8224d402dad623caf6259b611e56aec746e" - integrity sha512-+i0ZU1bCDymKakLxn5srGHrsAPRELC2WIbzwjLhHW9SIE1cPYkLCL0NlnXMZaM1vhfgA2+M7hySk42VBvrkBRw== - dependencies: - "@babel/helper-plugin-utils" "^7.18.9" +"@noble/hashes@1.3.2", "@noble/hashes@^1.2.0", "@noble/hashes@^1.3.2": + version "1.3.2" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.2.tgz#6f26dbc8fbc7205873ce3cee2f690eba0d421b39" + integrity sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ== -"@babel/plugin-transform-destructuring@^7.20.2": - version "7.20.2" - resolved "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.20.2.tgz#c23741cfa44ddd35f5e53896e88c75331b8b2792" - integrity sha512-mENM+ZHrvEgxLTBXUiQ621rRXZes3KWUv6NdQlrnr1TkWVw+hUjQBZuP2X32qKlrlG2BzgR95gkuCRSkJl8vIw== +"@nodelib/fs.scandir@2.1.5": + version "2.1.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" + integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== dependencies: - "@babel/helper-plugin-utils" "^7.20.2" + "@nodelib/fs.stat" "2.0.5" + run-parallel "^1.1.9" -"@babel/plugin-transform-dotall-regex@^7.18.6", "@babel/plugin-transform-dotall-regex@^7.4.4": - version "7.18.6" - resolved "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.18.6.tgz#b286b3e7aae6c7b861e45bed0a2fafd6b1a4fef8" - integrity sha512-6S3jpun1eEbAxq7TdjLotAsl4WpQI9DxfkycRcKrjhQYzU87qpXdknpBg/e+TdcMehqGnLFi7tnFUBR02Vq6wg== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.18.6" - "@babel/helper-plugin-utils" "^7.18.6" +"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" + integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== -"@babel/plugin-transform-duplicate-keys@^7.18.9": - version "7.18.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.18.9.tgz#687f15ee3cdad6d85191eb2a372c4528eaa0ae0e" - integrity sha512-d2bmXCtZXYc59/0SanQKbiWINadaJXqtvIQIzd4+hNwkWBgyCd5F/2t1kXoUdvPMrxzPvhK6EMQRROxsue+mfw== +"@nodelib/fs.walk@^1.2.3", "@nodelib/fs.walk@^1.2.8": + version "1.2.8" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" + integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== dependencies: - "@babel/helper-plugin-utils" "^7.18.9" + "@nodelib/fs.scandir" "2.1.5" + fastq "^1.6.0" -"@babel/plugin-transform-exponentiation-operator@^7.18.6": - version "7.18.6" - resolved "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.18.6.tgz#421c705f4521888c65e91fdd1af951bfefd4dacd" - integrity sha512-wzEtc0+2c88FVR34aQmiz56dxEkxr2g8DQb/KfaFa1JYXOFVsbhvAonFN6PwVWj++fKmku8NP80plJ5Et4wqHw== +"@pkgr/utils@^2.3.1": + version "2.4.2" + resolved "https://registry.yarnpkg.com/@pkgr/utils/-/utils-2.4.2.tgz#9e638bbe9a6a6f165580dc943f138fd3309a2cbc" + integrity sha512-POgTXhjrTfbTV63DiFXav4lBHiICLKKwDeaKn9Nphwj7WH6m0hMMCaJkMyRWjgtPFyRKRVoMXXjczsTQRDEhYw== dependencies: - "@babel/helper-builder-binary-assignment-operator-visitor" "^7.18.6" - "@babel/helper-plugin-utils" "^7.18.6" - -"@babel/plugin-transform-flow-strip-types@^7.16.0": - version "7.19.0" - resolved "https://registry.npmjs.org/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.19.0.tgz#e9e8606633287488216028719638cbbb2f2dde8f" - integrity sha512-sgeMlNaQVbCSpgLSKP4ZZKfsJVnFnNQlUSk6gPYzR/q7tzCgQF2t8RBKAP6cKJeZdveei7Q7Jm527xepI8lNLg== + cross-spawn "^7.0.3" + fast-glob "^3.3.0" + is-glob "^4.0.3" + open "^9.1.0" + picocolors "^1.0.0" + tslib "^2.6.0" + +"@polkadot-cloud/assets@0.1.32", "@polkadot-cloud/assets@^0.1.32": + version "0.1.32" + resolved "https://registry.yarnpkg.com/@polkadot-cloud/assets/-/assets-0.1.32.tgz#b619e24db310ac9a23e4bb20a64245f096b73072" + integrity sha512-TC8m6RFbHtJ5omW4VuIugKp1u7YzGBIgG9vGVEXkeSMWKwgzvkrxn7qpcjcelu1XO8ujb/+64dnhIpuhdj4e9g== + +"@polkadot-cloud/core@^1.0.30": + version "1.0.30" + resolved "https://registry.yarnpkg.com/@polkadot-cloud/core/-/core-1.0.30.tgz#cc1e3f3477f402b8842338ed3e2ea4bfe8b19a23" + integrity sha512-hn3oo/JK17W5alwXPxL5M0zmZidihjklEsYv7hXF7XqWZP9musntdn5LydzHz0J7m05OTnH+9tw/GNusvqBaSg== + +"@polkadot-cloud/react@^0.1.101": + version "0.1.101" + resolved "https://registry.yarnpkg.com/@polkadot-cloud/react/-/react-0.1.101.tgz#59323a9880b5421f9980ee34ea0d2324974872c3" + integrity sha512-y8diS5HfHIseItbaGLeY2ez60cVGZXbJUJGOquzNhcHgZsuE9W05j4lwl+EKlxnrNr+lnhfyESz1eE1YO6j0yA== + dependencies: + "@chainsafe/metamask-polkadot-adapter" "^0.5.1" + "@fortawesome/fontawesome-svg-core" "^6.4.2" + "@fortawesome/free-brands-svg-icons" "^6.4.2" + "@fortawesome/free-regular-svg-icons" "^6.4.2" + "@fortawesome/free-solid-svg-icons" "^6.4.2" + "@fortawesome/react-fontawesome" "^0.2.0" + "@polkadot-cloud/assets" "0.1.32" + "@polkadot-cloud/core" "^1.0.30" + "@polkadot-cloud/utils" "^0.0.23" + "@polkadot/keyring" "^12.5.1" + "@polkadot/util" "^12.5.1" + "@polkadot/util-crypto" "^12.5.1" + framer-motion "^10.15.0" + react-error-boundary "^4.0.11" + +"@polkadot-cloud/utils@^0.0.23": + version "0.0.23" + resolved "https://registry.yarnpkg.com/@polkadot-cloud/utils/-/utils-0.0.23.tgz#eb703f74c7f05f763f7624727ba83589131ae1c5" + integrity sha512-IWqtn/cBgxletCH+MqBwZhxPxEbFLhfCU6L4RmCSCoes0bC1xUHMtFevPiJuaEkXQTdFvOnosA8BbSI01/GUuA== + dependencies: + "@polkadot/keyring" "^12.5.1" + "@polkadot/util" "^12.5.1" + bignumber.js "^9.1.1" + +"@polkadot/api-augment@10.10.1": + version "10.10.1" + resolved "https://registry.yarnpkg.com/@polkadot/api-augment/-/api-augment-10.10.1.tgz#d3d296c923b0ff915c8d4f163e9b3bad70b89b9b" + integrity sha512-J0r1DT1M5y75iO1iwcpUBokKD3q6b22kWlPfiHEDNFydVw5vm7OTRBk9Njjl8rOnlSzcW/Ya8qWfV/wkrqHxUQ== + dependencies: + "@polkadot/api-base" "10.10.1" + "@polkadot/rpc-augment" "10.10.1" + "@polkadot/types" "10.10.1" + "@polkadot/types-augment" "10.10.1" + "@polkadot/types-codec" "10.10.1" + "@polkadot/util" "^12.5.1" + tslib "^2.6.2" + +"@polkadot/api-base@10.10.1": + version "10.10.1" + resolved "https://registry.yarnpkg.com/@polkadot/api-base/-/api-base-10.10.1.tgz#2d02f96960cbdd9d0ab61fe016587585902d1ee8" + integrity sha512-joH2Ywxnn+AStkw+JWAdF3i3WJy4NcBYp0SWJM/WqGafWR/FuHnati2pcj/MHzkHT8JkBippmSSJFvsqRhlwcQ== + dependencies: + "@polkadot/rpc-core" "10.10.1" + "@polkadot/types" "10.10.1" + "@polkadot/util" "^12.5.1" + rxjs "^7.8.1" + tslib "^2.6.2" + +"@polkadot/api-derive@10.10.1": + version "10.10.1" + resolved "https://registry.yarnpkg.com/@polkadot/api-derive/-/api-derive-10.10.1.tgz#555d755c393f57c8855b9fc28062148a3723e333" + integrity sha512-Q9Ibs4eRPqdV8qnRzFPD3dlWNbLHxRqMqNTNPmNQwKPo5m6fcQbZ0UZy3yJ+PI9S4AQHGhsWtfoi5qW8006GHQ== + dependencies: + "@polkadot/api" "10.10.1" + "@polkadot/api-augment" "10.10.1" + "@polkadot/api-base" "10.10.1" + "@polkadot/rpc-core" "10.10.1" + "@polkadot/types" "10.10.1" + "@polkadot/types-codec" "10.10.1" + "@polkadot/util" "^12.5.1" + "@polkadot/util-crypto" "^12.5.1" + rxjs "^7.8.1" + tslib "^2.6.2" + +"@polkadot/api@10.10.1", "@polkadot/api@^10.10.1", "@polkadot/api@^10.9.1": + version "10.10.1" + resolved "https://registry.yarnpkg.com/@polkadot/api/-/api-10.10.1.tgz#06fcbdcc8e17d2312d4b4093733d506f15ff62ad" + integrity sha512-YHVkmNvjGF4Eg3thAbVhj9UX3SXx+Yxk6yVuzsEcckEudIRHzL2ikIWGCfUprfzSeFNpUCKdJIi1tsxVHtA7Tg== + dependencies: + "@polkadot/api-augment" "10.10.1" + "@polkadot/api-base" "10.10.1" + "@polkadot/api-derive" "10.10.1" + "@polkadot/keyring" "^12.5.1" + "@polkadot/rpc-augment" "10.10.1" + "@polkadot/rpc-core" "10.10.1" + "@polkadot/rpc-provider" "10.10.1" + "@polkadot/types" "10.10.1" + "@polkadot/types-augment" "10.10.1" + "@polkadot/types-codec" "10.10.1" + "@polkadot/types-create" "10.10.1" + "@polkadot/types-known" "10.10.1" + "@polkadot/util" "^12.5.1" + "@polkadot/util-crypto" "^12.5.1" + eventemitter3 "^5.0.1" + rxjs "^7.8.1" + tslib "^2.6.2" + +"@polkadot/extension-inject@^0.46.5": + version "0.46.5" + resolved "https://registry.yarnpkg.com/@polkadot/extension-inject/-/extension-inject-0.46.5.tgz#6abee0eb28a73fd1a9461f257cac5875c2e9fc63" + integrity sha512-QcpkCMuv7iFbWjufkw14JRozpEYFyjP0H8KOJ8IsHGfPd2DPiismQ0NXr+AS7f6U+0I+Rhv9E4dnXxtJPROVMQ== + dependencies: + "@polkadot/api" "^10.9.1" + "@polkadot/rpc-provider" "^10.9.1" + "@polkadot/types" "^10.9.1" + "@polkadot/util" "^12.3.2" + "@polkadot/util-crypto" "^12.3.2" + "@polkadot/x-global" "^12.3.2" + tslib "^2.5.3" + +"@polkadot/keyring@^12.1.1", "@polkadot/keyring@^12.5.1": + version "12.5.1" + resolved "https://registry.yarnpkg.com/@polkadot/keyring/-/keyring-12.5.1.tgz#2f38504aa915f54bbd265f3793a6be55010eb1f5" + integrity sha512-u6b+Q7wI6WY/vwmJS9uUHy/5hKZ226nTlVNmxjkj9GvrRsQvUSwS94163yHPJwiZJiIv5xK5m0rwCMyoYu+wjA== + dependencies: + "@polkadot/util" "12.5.1" + "@polkadot/util-crypto" "12.5.1" + tslib "^2.6.2" + +"@polkadot/networks@12.5.1", "@polkadot/networks@^12.5.1": + version "12.5.1" + resolved "https://registry.yarnpkg.com/@polkadot/networks/-/networks-12.5.1.tgz#685c69d24d78a64f4e750609af22678d57fe1192" + integrity sha512-PP6UUdzz6iHHZH4q96cUEhTcydHj16+61sqeaYEJSF6Q9iY+5WVWQ26+rdjmre/EBdrMQkSS/CKy73mO5z/JkQ== + dependencies: + "@polkadot/util" "12.5.1" + "@substrate/ss58-registry" "^1.43.0" + tslib "^2.6.2" + +"@polkadot/rpc-augment@10.10.1": + version "10.10.1" + resolved "https://registry.yarnpkg.com/@polkadot/rpc-augment/-/rpc-augment-10.10.1.tgz#c25ec45687631ea649e2d5c7f7f9b0813ac4ca9f" + integrity sha512-PcvsX8DNV8BNDXXnY2K8F4mE7cWz7fKg8ykXNZTN8XUN6MrI4k/ohv7itYic7X5LaP25ZmQt5UiGyjKDGIELow== + dependencies: + "@polkadot/rpc-core" "10.10.1" + "@polkadot/types" "10.10.1" + "@polkadot/types-codec" "10.10.1" + "@polkadot/util" "^12.5.1" + tslib "^2.6.2" + +"@polkadot/rpc-core@10.10.1": + version "10.10.1" + resolved "https://registry.yarnpkg.com/@polkadot/rpc-core/-/rpc-core-10.10.1.tgz#5837e9ce635d5804cad897c6336771b61f3ef61a" + integrity sha512-awfFfJYsVF6W4DrqTj5RP00SSDRNB770FIoe1QE1Op4NcSrfeLpwh54HUJS716f4l5mOSYuvMp+zCbKzt8zKow== + dependencies: + "@polkadot/rpc-augment" "10.10.1" + "@polkadot/rpc-provider" "10.10.1" + "@polkadot/types" "10.10.1" + "@polkadot/util" "^12.5.1" + rxjs "^7.8.1" + tslib "^2.6.2" + +"@polkadot/rpc-provider@10.10.1", "@polkadot/rpc-provider@^10.9.1": + version "10.10.1" + resolved "https://registry.yarnpkg.com/@polkadot/rpc-provider/-/rpc-provider-10.10.1.tgz#387b1a915fa7b40d5f48a408c7b0ee5980f7ce07" + integrity sha512-VMDWoJgx6/mPHAOT66Sq+Jf2lJABfV/ZUIXtT2k8HjOndbm6oKrFqGEOSSLvB2q4olDee3FkFFxkyW1s6k4JaQ== + dependencies: + "@polkadot/keyring" "^12.5.1" + "@polkadot/types" "10.10.1" + "@polkadot/types-support" "10.10.1" + "@polkadot/util" "^12.5.1" + "@polkadot/util-crypto" "^12.5.1" + "@polkadot/x-fetch" "^12.5.1" + "@polkadot/x-global" "^12.5.1" + "@polkadot/x-ws" "^12.5.1" + eventemitter3 "^5.0.1" + mock-socket "^9.3.1" + nock "^13.3.4" + tslib "^2.6.2" + optionalDependencies: + "@substrate/connect" "0.7.33" + +"@polkadot/types-augment@10.10.1", "@polkadot/types-augment@^10.9.1": + version "10.10.1" + resolved "https://registry.yarnpkg.com/@polkadot/types-augment/-/types-augment-10.10.1.tgz#178ce0b22681109396fc681a027f35da7d757cef" + integrity sha512-XRHE75IocXfFE6EADYov3pqXCyBk5SWbiHoZ0+4WYWP9SwMuzsBaAy84NlhLBlkG3+ehIqi0HpAd/qrljJGZbg== + dependencies: + "@polkadot/types" "10.10.1" + "@polkadot/types-codec" "10.10.1" + "@polkadot/util" "^12.5.1" + tslib "^2.6.2" + +"@polkadot/types-codec@10.10.1": + version "10.10.1" + resolved "https://registry.yarnpkg.com/@polkadot/types-codec/-/types-codec-10.10.1.tgz#61d28a461493bfb72606b4399078460969a049c8" + integrity sha512-ETPG0wzWzt/bDKRQmYbO7CLe/0lUt8VrG6/bECdv+Kye+8Qedba2LZyTWm/9f2ngms8TZ82yI8mPv/mozdtfnw== + dependencies: + "@polkadot/util" "^12.5.1" + "@polkadot/x-bigint" "^12.5.1" + tslib "^2.6.2" + +"@polkadot/types-create@10.10.1": + version "10.10.1" + resolved "https://registry.yarnpkg.com/@polkadot/types-create/-/types-create-10.10.1.tgz#76f1729ef3f4699d99e708801312e43825368827" + integrity sha512-7OiLzd+Ter5zrpjP7fDwA1m89kd38VvMVixfOSv8x7ld2pDT+yyyKl14TCwRSWrKWCMtIb6M3iasPhq5cUa7cw== + dependencies: + "@polkadot/types-codec" "10.10.1" + "@polkadot/util" "^12.5.1" + tslib "^2.6.2" + +"@polkadot/types-known@10.10.1": + version "10.10.1" + resolved "https://registry.yarnpkg.com/@polkadot/types-known/-/types-known-10.10.1.tgz#ccaa1364ea1073a95c5cb0d73258e154de5103d2" + integrity sha512-yRa1lbDRqg3V/zoa0vSwdGOiYTIWktILW8OfkaLDExTu0GZBSbVHZlLAta52XVpA9Zww7mrUUC9+iernOwk//w== + dependencies: + "@polkadot/networks" "^12.5.1" + "@polkadot/types" "10.10.1" + "@polkadot/types-codec" "10.10.1" + "@polkadot/types-create" "10.10.1" + "@polkadot/util" "^12.5.1" + tslib "^2.6.2" + +"@polkadot/types-support@10.10.1": + version "10.10.1" + resolved "https://registry.yarnpkg.com/@polkadot/types-support/-/types-support-10.10.1.tgz#a22d319d4ba795e386000ddf6fdc8c55f9d81a9c" + integrity sha512-Cd2mwk9RG6LlX8X3H0bRY7wCTbZPqU3z38CMFhvNkFDAyjqKjtn8hpS4n8mMrZK2EwCs/MjQH1wb7rtFkaWmJw== + dependencies: + "@polkadot/util" "^12.5.1" + tslib "^2.6.2" + +"@polkadot/types@10.10.1", "@polkadot/types@^10.9.1": + version "10.10.1" + resolved "https://registry.yarnpkg.com/@polkadot/types/-/types-10.10.1.tgz#4a55909ff35b0b568c0b1539ae923a259b0dba6a" + integrity sha512-Ben62P1tjYEhKag34GBGcLX6NqcFR1VD5nNbWaxgr+t36Jl/tlHs6P9DlbFqQP7Tt9FmGrAYY0m3oTkhjG1NzA== + dependencies: + "@polkadot/keyring" "^12.5.1" + "@polkadot/types-augment" "10.10.1" + "@polkadot/types-codec" "10.10.1" + "@polkadot/types-create" "10.10.1" + "@polkadot/util" "^12.5.1" + "@polkadot/util-crypto" "^12.5.1" + rxjs "^7.8.1" + tslib "^2.6.2" + +"@polkadot/util-crypto@12.5.1", "@polkadot/util-crypto@^12.3.2", "@polkadot/util-crypto@^12.5.1": + version "12.5.1" + resolved "https://registry.yarnpkg.com/@polkadot/util-crypto/-/util-crypto-12.5.1.tgz#1753b23abfb9d72db950399ef65b0cbe5bef9f2f" + integrity sha512-Y8ORbMcsM/VOqSG3DgqutRGQ8XXK+X9M3C8oOEI2Tji65ZsXbh9Yh+ryPLM0oBp/9vqOXjkLgZJbbVuQceOw0A== + dependencies: + "@noble/curves" "^1.2.0" + "@noble/hashes" "^1.3.2" + "@polkadot/networks" "12.5.1" + "@polkadot/util" "12.5.1" + "@polkadot/wasm-crypto" "^7.2.2" + "@polkadot/wasm-util" "^7.2.2" + "@polkadot/x-bigint" "12.5.1" + "@polkadot/x-randomvalues" "12.5.1" + "@scure/base" "^1.1.3" + tslib "^2.6.2" + +"@polkadot/util@12.5.1", "@polkadot/util@^12.3.2", "@polkadot/util@^12.4.2", "@polkadot/util@^12.5.1": + version "12.5.1" + resolved "https://registry.yarnpkg.com/@polkadot/util/-/util-12.5.1.tgz#f4e7415600b013d3b69527aa88904acf085be3f5" + integrity sha512-fDBZL7D4/baMG09Qowseo884m3QBzErGkRWNBId1UjWR99kyex+cIY9fOSzmuQxo6nLdJlLHw1Nz2caN3+Bq0A== + dependencies: + "@polkadot/x-bigint" "12.5.1" + "@polkadot/x-global" "12.5.1" + "@polkadot/x-textdecoder" "12.5.1" + "@polkadot/x-textencoder" "12.5.1" + "@types/bn.js" "^5.1.1" + bn.js "^5.2.1" + tslib "^2.6.2" + +"@polkadot/wasm-bridge@7.2.2": + version "7.2.2" + resolved "https://registry.yarnpkg.com/@polkadot/wasm-bridge/-/wasm-bridge-7.2.2.tgz#957b82b17927fe080729e8930b5b5c554f77b8df" + integrity sha512-CgNENd65DVYtackOVXXRA0D1RPoCv5+77IdBCf7kNqu6LeAnR4nfTI6qjaApUdN1xRweUsQjSH7tu7VjkMOA0A== + dependencies: + "@polkadot/wasm-util" "7.2.2" + tslib "^2.6.1" + +"@polkadot/wasm-crypto-asmjs@7.2.2": + version "7.2.2" + resolved "https://registry.yarnpkg.com/@polkadot/wasm-crypto-asmjs/-/wasm-crypto-asmjs-7.2.2.tgz#25243a4d5d8d997761141b616623cacff4329f13" + integrity sha512-wKg+cpsWQCTSVhjlHuNeB/184rxKqY3vaklacbLOMbUXieIfuDBav5PJdzS3yeiVE60TpYaHW4iX/5OYHS82gg== + dependencies: + tslib "^2.6.1" + +"@polkadot/wasm-crypto-init@7.2.2": + version "7.2.2" + resolved "https://registry.yarnpkg.com/@polkadot/wasm-crypto-init/-/wasm-crypto-init-7.2.2.tgz#ffd105b87fc1b679c06c85c0848183c27bc539e3" + integrity sha512-vD4iPIp9x+SssUIWUenxWLPw4BVIwhXHNMpsV81egK990tvpyIxL205/EF5QRb1mKn8WfWcNFm5tYwwh9NdnnA== + dependencies: + "@polkadot/wasm-bridge" "7.2.2" + "@polkadot/wasm-crypto-asmjs" "7.2.2" + "@polkadot/wasm-crypto-wasm" "7.2.2" + "@polkadot/wasm-util" "7.2.2" + tslib "^2.6.1" + +"@polkadot/wasm-crypto-wasm@7.2.2": + version "7.2.2" + resolved "https://registry.yarnpkg.com/@polkadot/wasm-crypto-wasm/-/wasm-crypto-wasm-7.2.2.tgz#9e49a1565bda2bc830708693b491b37ad8a2144d" + integrity sha512-3efoIB6jA3Hhv6k0YIBwCtlC8gCSWCk+R296yIXRLLr3cGN415KM/PO/d1JIXYI64lbrRzWRmZRhllw3jf6Atg== + dependencies: + "@polkadot/wasm-util" "7.2.2" + tslib "^2.6.1" + +"@polkadot/wasm-crypto@^7.2.2": + version "7.2.2" + resolved "https://registry.yarnpkg.com/@polkadot/wasm-crypto/-/wasm-crypto-7.2.2.tgz#3c4b300c0997f4f7e2ddcdf8101d97fa1f5d1a7f" + integrity sha512-1ZY1rxUTawYm0m1zylvBMFovNIHYgG2v/XoASNp/EMG5c8FQIxCbhJRaTBA983GVq4lN/IAKREKEp9ZbLLqssA== + dependencies: + "@polkadot/wasm-bridge" "7.2.2" + "@polkadot/wasm-crypto-asmjs" "7.2.2" + "@polkadot/wasm-crypto-init" "7.2.2" + "@polkadot/wasm-crypto-wasm" "7.2.2" + "@polkadot/wasm-util" "7.2.2" + tslib "^2.6.1" + +"@polkadot/wasm-util@7.2.2", "@polkadot/wasm-util@^7.2.2": + version "7.2.2" + resolved "https://registry.yarnpkg.com/@polkadot/wasm-util/-/wasm-util-7.2.2.tgz#f8aa62eba9a35466aa23f3c5634f3e8dbd398bbf" + integrity sha512-N/25960ifCc56sBlJZ2h5UBpEPvxBmMLgwYsl7CUuT+ea2LuJW9Xh8VHDN/guYXwmm92/KvuendYkEUykpm/JQ== + dependencies: + tslib "^2.6.1" + +"@polkadot/x-bigint@12.5.1", "@polkadot/x-bigint@^12.5.1": + version "12.5.1" + resolved "https://registry.yarnpkg.com/@polkadot/x-bigint/-/x-bigint-12.5.1.tgz#0a6a3a34fae51468e7b02b42e0ff0747fd88a80a" + integrity sha512-Fw39eoN9v0sqxSzfSC5awaDVdzojIiE7d1hRSQgVSrES+8whWvtbYMR0qwbVhTuW7DvogHmye41P9xKMlXZysg== + dependencies: + "@polkadot/x-global" "12.5.1" + tslib "^2.6.2" + +"@polkadot/x-fetch@^12.5.1": + version "12.5.1" + resolved "https://registry.yarnpkg.com/@polkadot/x-fetch/-/x-fetch-12.5.1.tgz#41532d1324cef56a28c31490ac81062d487b16fb" + integrity sha512-Bc019lOKCoQJrthiS+H3LwCahGtl5tNnb2HK7xe3DBQIUx9r2HsF/uEngNfMRUFkUYg5TPCLFbEWU8NIREBS1A== + dependencies: + "@polkadot/x-global" "12.5.1" + node-fetch "^3.3.2" + tslib "^2.6.2" + +"@polkadot/x-global@12.5.1", "@polkadot/x-global@^12.3.2", "@polkadot/x-global@^12.5.1": + version "12.5.1" + resolved "https://registry.yarnpkg.com/@polkadot/x-global/-/x-global-12.5.1.tgz#947bb90e0c46c853ffe216dd6dcb6847d5c18a98" + integrity sha512-6K0YtWEg0eXInDOihU5aSzeb1t9TiDdX9ZuRly+58ALSqw5kPZYmQLbzE1d8HWzyXRXK+YH65GtLzfMGqfYHmw== + dependencies: + tslib "^2.6.2" + +"@polkadot/x-randomvalues@12.5.1": + version "12.5.1" + resolved "https://registry.yarnpkg.com/@polkadot/x-randomvalues/-/x-randomvalues-12.5.1.tgz#b30c6fa8749f5776f1d8a78b6edddb9b0f9c2853" + integrity sha512-UsMb1d+77EPNjW78BpHjZLIm4TaIpfqq89OhZP/6gDIoS2V9iE/AK3jOWKm1G7Y2F8XIoX1qzQpuMakjfagFoQ== + dependencies: + "@polkadot/x-global" "12.5.1" + tslib "^2.6.2" + +"@polkadot/x-textdecoder@12.5.1": + version "12.5.1" + resolved "https://registry.yarnpkg.com/@polkadot/x-textdecoder/-/x-textdecoder-12.5.1.tgz#8d89d2b5efbffb2550a48f8afb4a834e1d8d4f6e" + integrity sha512-j2YZGWfwhMC8nHW3BXq10fAPY02ObLL/qoTjCMJ1Cmc/OGq18Ep7k9cXXbjFAq3wf3tUUewt/u/hStKCk3IvfQ== + dependencies: + "@polkadot/x-global" "12.5.1" + tslib "^2.6.2" + +"@polkadot/x-textencoder@12.5.1": + version "12.5.1" + resolved "https://registry.yarnpkg.com/@polkadot/x-textencoder/-/x-textencoder-12.5.1.tgz#9104e37a60068df2fbf57c81a7ce48669430c76c" + integrity sha512-1JNNpOGb4wD+c7zFuOqjibl49LPnHNr4rj4s3WflLUIZvOMY6euoDuN3ISjQSHCLlVSoH0sOCWA3qXZU4bCTDQ== + dependencies: + "@polkadot/x-global" "12.5.1" + tslib "^2.6.2" + +"@polkadot/x-ws@^12.5.1": + version "12.5.1" + resolved "https://registry.yarnpkg.com/@polkadot/x-ws/-/x-ws-12.5.1.tgz#ff9fc78ef701e18d765443779ab95296a406138c" + integrity sha512-efNMhB3Lh6pW2iTipMkqwrjpuUtb3EwR/jYZftiIGo5tDPB7rqoMOp9s6KRFJEIUfZkLnMUtbkZ5fHzUJaCjmQ== + dependencies: + "@polkadot/x-global" "12.5.1" + tslib "^2.6.2" + ws "^8.14.1" + +"@polkawatch/ddp-client@^2.0.8": + version "2.0.9" + resolved "https://registry.yarnpkg.com/@polkawatch/ddp-client/-/ddp-client-2.0.9.tgz#fedb4bd8ad3addebee7df2165e4cb83a2047b8d4" + integrity sha512-1e2nE7nfPT8PhdCuh37hdicvGzEiH7xNPsSKPAmcKii11C02qBFijJCF4nykx7VgaOFXAdaxZuD6tl3lqwcRCQ== + dependencies: + axios "^0.21.4" + +"@remix-run/router@1.10.0": + version "1.10.0" + resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.10.0.tgz#e2170dc2049b06e65bbe883adad0e8ddf8291278" + integrity sha512-Lm+fYpMfZoEucJ7cMxgt4dYt8jLfbpwRCzAjm9UgSLOkmlqo9gupxt6YX3DY0Fk155NT9l17d/ydi+964uS9Lw== + +"@rollup/pluginutils@^4.2.1": + version "4.2.1" + resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-4.2.1.tgz#e6c6c3aba0744edce3fb2074922d3776c0af2a6d" + integrity sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ== dependencies: - "@babel/helper-plugin-utils" "^7.19.0" - "@babel/plugin-syntax-flow" "^7.18.6" + estree-walker "^2.0.1" + picomatch "^2.2.2" -"@babel/plugin-transform-for-of@^7.18.8": - version "7.18.8" - resolved "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.18.8.tgz#6ef8a50b244eb6a0bdbad0c7c61877e4e30097c1" - integrity sha512-yEfTRnjuskWYo0k1mHUqrVWaZwrdq8AYbfrpqULOJOaucGSp4mNMVps+YtA8byoevxS/urwU75vyhQIxcCgiBQ== +"@rollup/pluginutils@^5.0.4": + version "5.0.5" + resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-5.0.5.tgz#bbb4c175e19ebfeeb8c132c2eea0ecb89941a66c" + integrity sha512-6aEYR910NyP73oHiJglti74iRyOwgFU4x3meH/H8OJx6Ry0j6cOVZ5X/wTvub7G7Ao6qaHBEaNsV3GLJkSsF+Q== dependencies: - "@babel/helper-plugin-utils" "^7.18.6" + "@types/estree" "^1.0.0" + estree-walker "^2.0.2" + picomatch "^2.3.1" -"@babel/plugin-transform-function-name@^7.18.9": - version "7.18.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.18.9.tgz#cc354f8234e62968946c61a46d6365440fc764e0" - integrity sha512-WvIBoRPaJQ5yVHzcnJFor7oS5Ls0PYixlTYE63lCj2RtdQEl15M68FXQlxnG6wdraJIXRdR7KI+hQ7q/9QjrCQ== - dependencies: - "@babel/helper-compilation-targets" "^7.18.9" - "@babel/helper-function-name" "^7.18.9" - "@babel/helper-plugin-utils" "^7.18.9" +"@scure/base@^1.1.1", "@scure/base@^1.1.3": + version "1.1.3" + resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.3.tgz#8584115565228290a6c6c4961973e0903bb3df2f" + integrity sha512-/+SgoRjLq7Xlf0CWuLHq2LUZeL/w65kfzAPG5NH9pcmBhs+nunQTn4gvdwgMTIXnt9b2C/1SeL2XiysZEyIC9Q== -"@babel/plugin-transform-literals@^7.18.9": - version "7.18.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.18.9.tgz#72796fdbef80e56fba3c6a699d54f0de557444bc" - integrity sha512-IFQDSRoTPnrAIrI5zoZv73IFeZu2dhu6irxQjY9rNjTT53VmKg9fenjvoiOWOkJ6mm4jKVPtdMzBY98Fp4Z4cg== - dependencies: - "@babel/helper-plugin-utils" "^7.18.9" +"@sinclair/typebox@^0.27.8": + version "0.27.8" + resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e" + integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA== -"@babel/plugin-transform-member-expression-literals@^7.18.6": - version "7.18.6" - resolved "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.18.6.tgz#ac9fdc1a118620ac49b7e7a5d2dc177a1bfee88e" - integrity sha512-qSF1ihLGO3q+/g48k85tUjD033C29TNTVB2paCwZPVmOsjn9pClvYYrM2VeJpBY2bcNkuny0YUyTNRyRxJ54KA== - dependencies: - "@babel/helper-plugin-utils" "^7.18.6" +"@substrate/connect-extension-protocol@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@substrate/connect-extension-protocol/-/connect-extension-protocol-1.0.1.tgz#fa5738039586c648013caa6a0c95c43265dbe77d" + integrity sha512-161JhCC1csjH3GE5mPLEd7HbWtwNSPJBg3p1Ksz9SFlTzj/bgEwudiRN2y5i0MoLGCIJRYKyKGMxVnd29PzNjg== -"@babel/plugin-transform-modules-amd@^7.19.6": - version "7.19.6" - resolved "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.19.6.tgz#aca391801ae55d19c4d8d2ebfeaa33df5f2a2cbd" - integrity sha512-uG3od2mXvAtIFQIh0xrpLH6r5fpSQN04gIVovl+ODLdUMANokxQLZnPBHcjmv3GxRjnqwLuHvppjjcelqUFZvg== +"@substrate/connect@0.7.33": + version "0.7.33" + resolved "https://registry.yarnpkg.com/@substrate/connect/-/connect-0.7.33.tgz#6fa309557b5b45cb918f5f4fe25a356384de9808" + integrity sha512-1B984/bmXVQvTT9oV3c3b7215lvWmulP9rfP3T3Ri+OU3uIsyCzYw0A+XG6J8/jgO2FnroeNIBWlgoLaUM1uzw== dependencies: - "@babel/helper-module-transforms" "^7.19.6" - "@babel/helper-plugin-utils" "^7.19.0" + "@substrate/connect-extension-protocol" "^1.0.1" + smoldot "2.0.1" -"@babel/plugin-transform-modules-commonjs@^7.19.6": - version "7.19.6" - resolved "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.19.6.tgz#25b32feef24df8038fc1ec56038917eacb0b730c" - integrity sha512-8PIa1ym4XRTKuSsOUXqDG0YaOlEuTVvHMe5JCfgBMOtHvJKw/4NGovEGN33viISshG/rZNVrACiBmPQLvWN8xQ== +"@substrate/connect@^0.7.34": + version "0.7.34" + resolved "https://registry.yarnpkg.com/@substrate/connect/-/connect-0.7.34.tgz#e1b74125a3bf04b0037ec6f71852c943db1dd16f" + integrity sha512-APxn6PlxM+V+bJU4NSwD4WcBivqoNAQPVZAFxrM4Qf0aExHNBmB9j+uISyyc6ZbM84bN7/PWh4xd+3Wy7xEZMA== dependencies: - "@babel/helper-module-transforms" "^7.19.6" - "@babel/helper-plugin-utils" "^7.19.0" - "@babel/helper-simple-access" "^7.19.4" + "@substrate/connect-extension-protocol" "^1.0.1" + smoldot "2.0.6" -"@babel/plugin-transform-modules-systemjs@^7.19.6": - version "7.19.6" - resolved "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.19.6.tgz#59e2a84064b5736a4471b1aa7b13d4431d327e0d" - integrity sha512-fqGLBepcc3kErfR9R3DnVpURmckXP7gj7bAlrTQyBxrigFqszZCkFkcoxzCp2v32XmwXLvbw+8Yq9/b+QqksjQ== - dependencies: - "@babel/helper-hoist-variables" "^7.18.6" - "@babel/helper-module-transforms" "^7.19.6" - "@babel/helper-plugin-utils" "^7.19.0" - "@babel/helper-validator-identifier" "^7.19.1" +"@substrate/ss58-registry@^1.43.0": + version "1.43.0" + resolved "https://registry.yarnpkg.com/@substrate/ss58-registry/-/ss58-registry-1.43.0.tgz#93108e45cb7ef6d82560c153e3692c2aa1c711b3" + integrity sha512-USEkXA46P9sqClL7PZv0QFsit4S8Im97wchKG0/H/9q3AT/S76r40UHfCr4Un7eBJPE23f7fU9BZ0ITpP9MCsA== -"@babel/plugin-transform-modules-umd@^7.18.6": - version "7.18.6" - resolved "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.18.6.tgz#81d3832d6034b75b54e62821ba58f28ed0aab4b9" - integrity sha512-dcegErExVeXcRqNtkRU/z8WlBLnvD4MRnHgNs3MytRO1Mn1sHRyhbcpYbVMGclAqOjdW+9cfkdZno9dFdfKLfQ== - dependencies: - "@babel/helper-module-transforms" "^7.18.6" - "@babel/helper-plugin-utils" "^7.18.6" +"@svgr/babel-plugin-add-jsx-attribute@8.0.0": + version "8.0.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-8.0.0.tgz#4001f5d5dd87fa13303e36ee106e3ff3a7eb8b22" + integrity sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g== -"@babel/plugin-transform-named-capturing-groups-regex@^7.19.1": - version "7.19.1" - resolved "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.19.1.tgz#ec7455bab6cd8fb05c525a94876f435a48128888" - integrity sha512-oWk9l9WItWBQYS4FgXD4Uyy5kq898lvkXpXQxoJEY1RnvPk4R/Dvu2ebXU9q8lP+rlMwUQTFf2Ok6d78ODa0kw== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.19.0" - "@babel/helper-plugin-utils" "^7.19.0" +"@svgr/babel-plugin-remove-jsx-attribute@8.0.0": + version "8.0.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-8.0.0.tgz#69177f7937233caca3a1afb051906698f2f59186" + integrity sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA== -"@babel/plugin-transform-new-target@^7.18.6": - version "7.18.6" - resolved "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.18.6.tgz#d128f376ae200477f37c4ddfcc722a8a1b3246a8" - integrity sha512-DjwFA/9Iu3Z+vrAn+8pBUGcjhxKguSMlsFqeCKbhb9BAV756v0krzVK04CRDi/4aqmk8BsHb4a/gFcaA5joXRw== - dependencies: - "@babel/helper-plugin-utils" "^7.18.6" +"@svgr/babel-plugin-remove-jsx-empty-expression@8.0.0": + version "8.0.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-8.0.0.tgz#c2c48104cfd7dcd557f373b70a56e9e3bdae1d44" + integrity sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA== -"@babel/plugin-transform-object-super@^7.18.6": - version "7.18.6" - resolved "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.18.6.tgz#fb3c6ccdd15939b6ff7939944b51971ddc35912c" - integrity sha512-uvGz6zk+pZoS1aTZrOvrbj6Pp/kK2mp45t2B+bTDre2UgsZZ8EZLSJtUg7m/no0zOJUWgFONpB7Zv9W2tSaFlA== - dependencies: - "@babel/helper-plugin-utils" "^7.18.6" - "@babel/helper-replace-supers" "^7.18.6" +"@svgr/babel-plugin-replace-jsx-attribute-value@8.0.0": + version "8.0.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-8.0.0.tgz#8fbb6b2e91fa26ac5d4aa25c6b6e4f20f9c0ae27" + integrity sha512-KVQ+PtIjb1BuYT3ht8M5KbzWBhdAjjUPdlMtpuw/VjT8coTrItWX6Qafl9+ji831JaJcu6PJNKCV0bp01lBNzQ== -"@babel/plugin-transform-parameters@^7.20.1": - version "7.20.3" - resolved "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.20.3.tgz#7b3468d70c3c5b62e46be0a47b6045d8590fb748" - integrity sha512-oZg/Fpx0YDrj13KsLyO8I/CX3Zdw7z0O9qOd95SqcoIzuqy/WTGWvePeHAnZCN54SfdyjHcb1S30gc8zlzlHcA== - dependencies: - "@babel/helper-plugin-utils" "^7.20.2" +"@svgr/babel-plugin-svg-dynamic-title@8.0.0": + version "8.0.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-8.0.0.tgz#1d5ba1d281363fc0f2f29a60d6d936f9bbc657b0" + integrity sha512-omNiKqwjNmOQJ2v6ge4SErBbkooV2aAWwaPFs2vUY7p7GhVkzRkJ00kILXQvRhA6miHnNpXv7MRnnSjdRjK8og== -"@babel/plugin-transform-property-literals@^7.18.6": - version "7.18.6" - resolved "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.18.6.tgz#e22498903a483448e94e032e9bbb9c5ccbfc93a3" - integrity sha512-cYcs6qlgafTud3PAzrrRNbQtfpQ8+y/+M5tKmksS9+M1ckbH6kzY8MrexEM9mcA6JDsukE19iIRvAyYl463sMg== - dependencies: - "@babel/helper-plugin-utils" "^7.18.6" +"@svgr/babel-plugin-svg-em-dimensions@8.0.0": + version "8.0.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-8.0.0.tgz#35e08df300ea8b1d41cb8f62309c241b0369e501" + integrity sha512-mURHYnu6Iw3UBTbhGwE/vsngtCIbHE43xCRK7kCw4t01xyGqb2Pd+WXekRRoFOBIY29ZoOhUCTEweDMdrjfi9g== -"@babel/plugin-transform-react-constant-elements@^7.12.1": - version "7.20.2" - resolved "https://registry.npmjs.org/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.20.2.tgz#3f02c784e0b711970d7d8ccc96c4359d64e27ac7" - integrity sha512-KS/G8YI8uwMGKErLFOHS/ekhqdHhpEloxs43NecQHVgo2QuQSyJhGIY1fL8UGl9wy5ItVwwoUL4YxVqsplGq2g== - dependencies: - "@babel/helper-plugin-utils" "^7.20.2" +"@svgr/babel-plugin-transform-react-native-svg@8.1.0": + version "8.1.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-8.1.0.tgz#90a8b63998b688b284f255c6a5248abd5b28d754" + integrity sha512-Tx8T58CHo+7nwJ+EhUwx3LfdNSG9R2OKfaIXXs5soiy5HtgoAEkDay9LIimLOcG8dJQH1wPZp/cnAv6S9CrR1Q== -"@babel/plugin-transform-react-display-name@^7.16.0", "@babel/plugin-transform-react-display-name@^7.18.6": - version "7.18.6" - resolved "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.18.6.tgz#8b1125f919ef36ebdfff061d664e266c666b9415" - integrity sha512-TV4sQ+T013n61uMoygyMRm+xf04Bd5oqFpv2jAEQwSZ8NwQA7zeRPg1LMVg2PWi3zWBz+CLKD+v5bcpZ/BS0aA== - dependencies: - "@babel/helper-plugin-utils" "^7.18.6" +"@svgr/babel-plugin-transform-svg-component@8.0.0": + version "8.0.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-8.0.0.tgz#013b4bfca88779711f0ed2739f3f7efcefcf4f7e" + integrity sha512-DFx8xa3cZXTdb/k3kfPeaixecQLgKh5NVBMwD0AQxOzcZawK4oo1Jh9LbrcACUivsCA7TLG8eeWgrDXjTMhRmw== -"@babel/plugin-transform-react-jsx-development@^7.18.6": - version "7.18.6" - resolved "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.18.6.tgz#dbe5c972811e49c7405b630e4d0d2e1380c0ddc5" - integrity sha512-SA6HEjwYFKF7WDjWcMcMGUimmw/nhNRDWxr+KaLSCrkD/LMDBvWRmHAYgE1HDeF8KUuI8OAu+RT6EOtKxSW2qA== +"@svgr/babel-preset@8.1.0": + version "8.1.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-preset/-/babel-preset-8.1.0.tgz#0e87119aecdf1c424840b9d4565b7137cabf9ece" + integrity sha512-7EYDbHE7MxHpv4sxvnVPngw5fuR6pw79SkcrILHJ/iMpuKySNCl5W1qcwPEpU+LgyRXOaAFgH0KhwD18wwg6ug== + dependencies: + "@svgr/babel-plugin-add-jsx-attribute" "8.0.0" + "@svgr/babel-plugin-remove-jsx-attribute" "8.0.0" + "@svgr/babel-plugin-remove-jsx-empty-expression" "8.0.0" + "@svgr/babel-plugin-replace-jsx-attribute-value" "8.0.0" + "@svgr/babel-plugin-svg-dynamic-title" "8.0.0" + "@svgr/babel-plugin-svg-em-dimensions" "8.0.0" + "@svgr/babel-plugin-transform-react-native-svg" "8.1.0" + "@svgr/babel-plugin-transform-svg-component" "8.0.0" + +"@svgr/core@^8.1.0": + version "8.1.0" + resolved "https://registry.yarnpkg.com/@svgr/core/-/core-8.1.0.tgz#41146f9b40b1a10beaf5cc4f361a16a3c1885e88" + integrity sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA== dependencies: - "@babel/plugin-transform-react-jsx" "^7.18.6" + "@babel/core" "^7.21.3" + "@svgr/babel-preset" "8.1.0" + camelcase "^6.2.0" + cosmiconfig "^8.1.3" + snake-case "^3.0.4" -"@babel/plugin-transform-react-jsx@^7.18.6": - version "7.19.0" - resolved "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.19.0.tgz#b3cbb7c3a00b92ec8ae1027910e331ba5c500eb9" - integrity sha512-UVEvX3tXie3Szm3emi1+G63jyw1w5IcMY0FSKM+CRnKRI5Mr1YbCNgsSTwoTwKphQEG9P+QqmuRFneJPZuHNhg== +"@svgr/hast-util-to-babel-ast@8.0.0": + version "8.0.0" + resolved "https://registry.yarnpkg.com/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-8.0.0.tgz#6952fd9ce0f470e1aded293b792a2705faf4ffd4" + integrity sha512-EbDKwO9GpfWP4jN9sGdYwPBU0kdomaPIL2Eu4YwmgP+sJeXT+L7bMwJUBnhzfH8Q2qMBqZ4fJwpCyYsAN3mt2Q== dependencies: - "@babel/helper-annotate-as-pure" "^7.18.6" - "@babel/helper-module-imports" "^7.18.6" - "@babel/helper-plugin-utils" "^7.19.0" - "@babel/plugin-syntax-jsx" "^7.18.6" - "@babel/types" "^7.19.0" + "@babel/types" "^7.21.3" + entities "^4.4.0" -"@babel/plugin-transform-react-pure-annotations@^7.18.6": - version "7.18.6" - resolved "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.18.6.tgz#561af267f19f3e5d59291f9950fd7b9663d0d844" - integrity sha512-I8VfEPg9r2TRDdvnHgPepTKvuRomzA8+u+nhY7qSI1fR2hRNebasZEETLyM5mAUr0Ku56OkXJ0I7NHJnO6cJiQ== - dependencies: - "@babel/helper-annotate-as-pure" "^7.18.6" - "@babel/helper-plugin-utils" "^7.18.6" +"@svgr/plugin-jsx@^8.1.0": + version "8.1.0" + resolved "https://registry.yarnpkg.com/@svgr/plugin-jsx/-/plugin-jsx-8.1.0.tgz#96969f04a24b58b174ee4cd974c60475acbd6928" + integrity sha512-0xiIyBsLlr8quN+WyuxooNW9RJ0Dpr8uOnH/xrCVO8GLUcwHISwj1AG0k+LFzteTkAA0GbX0kj9q6Dk70PTiPA== + dependencies: + "@babel/core" "^7.21.3" + "@svgr/babel-preset" "8.1.0" + "@svgr/hast-util-to-babel-ast" "8.0.0" + svg-parser "^2.0.4" + +"@swc/core-darwin-arm64@1.3.95": + version "1.3.95" + resolved "https://registry.yarnpkg.com/@swc/core-darwin-arm64/-/core-darwin-arm64-1.3.95.tgz#e6b6363fc0a22ee3cd9a63130d2042d5027aae2c" + integrity sha512-VAuBAP3MNetO/yBIBzvorUXq7lUBwhfpJxYViSxyluMwtoQDhE/XWN598TWMwMl1ZuImb56d7eUsuFdjgY7pJw== + +"@swc/core-darwin-x64@1.3.95": + version "1.3.95" + resolved "https://registry.yarnpkg.com/@swc/core-darwin-x64/-/core-darwin-x64-1.3.95.tgz#7911a03f4e0f9359710d3d6ad1dba7b5569efe5d" + integrity sha512-20vF2rvUsN98zGLZc+dsEdHvLoCuiYq/1B+TDeE4oolgTFDmI1jKO+m44PzWjYtKGU9QR95sZ6r/uec0QC5O4Q== + +"@swc/core-linux-arm-gnueabihf@1.3.95": + version "1.3.95" + resolved "https://registry.yarnpkg.com/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.3.95.tgz#95a2c9fc6849df9f1944957669c82c559d65b24f" + integrity sha512-oEudEM8PST1MRNGs+zu0cx5i9uP8TsLE4/L9HHrS07Ck0RJ3DCj3O2fU832nmLe2QxnAGPwBpSO9FntLfOiWEQ== + +"@swc/core-linux-arm64-gnu@1.3.95": + version "1.3.95" + resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.3.95.tgz#1914d42041469582e3cc56619890edbcc54e83d6" + integrity sha512-pIhFI+cuC1aYg+0NAPxwT/VRb32f2ia8oGxUjQR6aJg65gLkUYQzdwuUmpMtFR2WVf7WVFYxUnjo4UyMuyh3ng== + +"@swc/core-linux-arm64-musl@1.3.95": + version "1.3.95" + resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.3.95.tgz#8d73822a5807575a572d6a2d6cb64587a9f19ce6" + integrity sha512-ZpbTr+QZDT4OPJfjPAmScqdKKaT+wGurvMU5AhxLaf85DuL8HwUwwlL0n1oLieLc47DwIJEMuKQkYhXMqmJHlg== + +"@swc/core-linux-x64-gnu@1.3.95": + version "1.3.95" + resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.3.95.tgz#80467727ec11da3de49e6be2abf735964a808483" + integrity sha512-n9SuHEFtdfSJ+sHdNXNRuIOVprB8nbsz+08apKfdo4lEKq6IIPBBAk5kVhPhkjmg2dFVHVo4Tr/OHXM1tzWCCw== + +"@swc/core-linux-x64-musl@1.3.95": + version "1.3.95" + resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.3.95.tgz#675a53ac037846bd1bb9840a95ebcb5289265d3b" + integrity sha512-L1JrVlsXU3LC0WwmVnMK9HrOT2uhHahAoPNMJnZQpc18a0paO9fqifPG8M/HjNRffMUXR199G/phJsf326UvVg== + +"@swc/core-win32-arm64-msvc@1.3.95": + version "1.3.95" + resolved "https://registry.yarnpkg.com/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.3.95.tgz#7f0b5d0d0a090c5c625bbc54ffaf427d861c068a" + integrity sha512-YaP4x/aZbUyNdqCBpC2zL8b8n58MEpOUpmOIZK6G1SxGi+2ENht7gs7+iXpWPc0sy7X3YPKmSWMAuui0h8lgAA== + +"@swc/core-win32-ia32-msvc@1.3.95": + version "1.3.95" + resolved "https://registry.yarnpkg.com/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.3.95.tgz#06e2778549a37f0b505b24fd8f40c1c038e29f3e" + integrity sha512-w0u3HI916zT4BC/57gOd+AwAEjXeUlQbGJ9H4p/gzs1zkSHtoDQghVUNy3n/ZKp9KFod/95cA8mbVF9t1+6epQ== + +"@swc/core-win32-x64-msvc@1.3.95": + version "1.3.95" + resolved "https://registry.yarnpkg.com/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.3.95.tgz#40f6b131e84ba6ed97f516edf0f9d5a766c0da64" + integrity sha512-5RGnMt0S6gg4Gc6QtPUJ3Qs9Un4sKqccEzgH/tj7V/DVTJwKdnBKxFZfgQ34OR2Zpz7zGOn889xwsFVXspVWNA== + +"@swc/core@^1.3.85": + version "1.3.95" + resolved "https://registry.yarnpkg.com/@swc/core/-/core-1.3.95.tgz#2743b8460e6f29385e3dbe49f3f66277ab233536" + integrity sha512-PMrNeuqIusq9DPDooV3FfNEbZuTu5jKAc04N3Hm6Uk2Fl49cqElLFQ4xvl4qDmVDz97n3n/C1RE0/f6WyGPEiA== + dependencies: + "@swc/counter" "^0.1.1" + "@swc/types" "^0.1.5" + optionalDependencies: + "@swc/core-darwin-arm64" "1.3.95" + "@swc/core-darwin-x64" "1.3.95" + "@swc/core-linux-arm-gnueabihf" "1.3.95" + "@swc/core-linux-arm64-gnu" "1.3.95" + "@swc/core-linux-arm64-musl" "1.3.95" + "@swc/core-linux-x64-gnu" "1.3.95" + "@swc/core-linux-x64-musl" "1.3.95" + "@swc/core-win32-arm64-msvc" "1.3.95" + "@swc/core-win32-ia32-msvc" "1.3.95" + "@swc/core-win32-x64-msvc" "1.3.95" + +"@swc/counter@^0.1.1": + version "0.1.2" + resolved "https://registry.yarnpkg.com/@swc/counter/-/counter-0.1.2.tgz#bf06d0770e47c6f1102270b744e17b934586985e" + integrity sha512-9F4ys4C74eSTEUNndnER3VJ15oru2NumfQxS8geE+f3eB5xvfxpWyqE5XlVnxb/R14uoXi6SLbBwwiDSkv+XEw== -"@babel/plugin-transform-regenerator@^7.18.6": - version "7.18.6" - resolved "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.18.6.tgz#585c66cb84d4b4bf72519a34cfce761b8676ca73" - integrity sha512-poqRI2+qiSdeldcz4wTSTXBRryoq3Gc70ye7m7UD5Ww0nE29IXqMl6r7Nd15WBgRd74vloEMlShtH6CKxVzfmQ== - dependencies: - "@babel/helper-plugin-utils" "^7.18.6" - regenerator-transform "^0.15.0" +"@swc/types@^0.1.5": + version "0.1.5" + resolved "https://registry.yarnpkg.com/@swc/types/-/types-0.1.5.tgz#043b731d4f56a79b4897a3de1af35e75d56bc63a" + integrity sha512-myfUej5naTBWnqOCc/MdVOLVjXUXtIA+NpDrDBKJtLLg2shUjBu3cZmB/85RyitKc55+lUUyl7oRfLOvkr2hsw== -"@babel/plugin-transform-reserved-words@^7.18.6": - version "7.18.6" - resolved "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.18.6.tgz#b1abd8ebf8edaa5f7fe6bbb8d2133d23b6a6f76a" - integrity sha512-oX/4MyMoypzHjFrT1CdivfKZ+XvIPMFXwwxHp/r0Ddy2Vuomt4HDFGmft1TAY2yiTKiNSsh3kjBAzcM8kSdsjA== +"@types/bn.js@^5.1.1": + version "5.1.3" + resolved "https://registry.yarnpkg.com/@types/bn.js/-/bn.js-5.1.3.tgz#0857f00da3bf888a26a44b4a477c7819b17dacc5" + integrity sha512-wT1B4iIO82ecXkdN6waCK8Ou7E71WU+mP1osDA5Q8c6Ur+ozU2vIKUIhSpUr6uE5L2YHocKS1Z2jG2fBC1YVeg== dependencies: - "@babel/helper-plugin-utils" "^7.18.6" + "@types/node" "*" -"@babel/plugin-transform-runtime@^7.16.4": - version "7.19.6" - resolved "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.19.6.tgz#9d2a9dbf4e12644d6f46e5e75bfbf02b5d6e9194" - integrity sha512-PRH37lz4JU156lYFW1p8OxE5i7d6Sl/zV58ooyr+q1J1lnQPyg5tIiXlIwNVhJaY4W3TmOtdc8jqdXQcB1v5Yw== +"@types/chai-subset@^1.3.3": + version "1.3.4" + resolved "https://registry.yarnpkg.com/@types/chai-subset/-/chai-subset-1.3.4.tgz#7938fa929dd12db451457e4d6faa27bcd599a729" + integrity sha512-CCWNXrJYSUIojZ1149ksLl3AN9cmZ5djf+yUoVVV+NuYrtydItQVlL2ZDqyC6M6O9LWRnVf8yYDxbXHO2TfQZg== dependencies: - "@babel/helper-module-imports" "^7.18.6" - "@babel/helper-plugin-utils" "^7.19.0" - babel-plugin-polyfill-corejs2 "^0.3.3" - babel-plugin-polyfill-corejs3 "^0.6.0" - babel-plugin-polyfill-regenerator "^0.4.1" - semver "^6.3.0" - -"@babel/plugin-transform-shorthand-properties@^7.18.6": - version "7.18.6" - resolved "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.18.6.tgz#6d6df7983d67b195289be24909e3f12a8f664dc9" - integrity sha512-eCLXXJqv8okzg86ywZJbRn19YJHU4XUa55oz2wbHhaQVn/MM+XhukiT7SYqp/7o00dg52Rj51Ny+Ecw4oyoygw== - dependencies: - "@babel/helper-plugin-utils" "^7.18.6" - -"@babel/plugin-transform-spread@^7.19.0": - version "7.19.0" - resolved "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.19.0.tgz#dd60b4620c2fec806d60cfaae364ec2188d593b6" - integrity sha512-RsuMk7j6n+r752EtzyScnWkQyuJdli6LdO5Klv8Yx0OfPVTcQkIUfS8clx5e9yHXzlnhOZF3CbQ8C2uP5j074w== - dependencies: - "@babel/helper-plugin-utils" "^7.19.0" - "@babel/helper-skip-transparent-expression-wrappers" "^7.18.9" - -"@babel/plugin-transform-sticky-regex@^7.18.6": - version "7.18.6" - resolved "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.18.6.tgz#c6706eb2b1524028e317720339583ad0f444adcc" - integrity sha512-kfiDrDQ+PBsQDO85yj1icueWMfGfJFKN1KCkndygtu/C9+XUfydLC8Iv5UYJqRwy4zk8EcplRxEOeLyjq1gm6Q== - dependencies: - "@babel/helper-plugin-utils" "^7.18.6" - -"@babel/plugin-transform-template-literals@^7.18.9": - version "7.18.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.18.9.tgz#04ec6f10acdaa81846689d63fae117dd9c243a5e" - integrity sha512-S8cOWfT82gTezpYOiVaGHrCbhlHgKhQt8XH5ES46P2XWmX92yisoZywf5km75wv5sYcXDUCLMmMxOLCtthDgMA== - dependencies: - "@babel/helper-plugin-utils" "^7.18.9" - -"@babel/plugin-transform-typeof-symbol@^7.18.9": - version "7.18.9" - resolved "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.18.9.tgz#c8cea68263e45addcd6afc9091429f80925762c0" - integrity sha512-SRfwTtF11G2aemAZWivL7PD+C9z52v9EvMqH9BuYbabyPuKUvSWks3oCg6041pT925L4zVFqaVBeECwsmlguEw== - dependencies: - "@babel/helper-plugin-utils" "^7.18.9" - -"@babel/plugin-transform-typescript@^7.18.6": - version "7.20.2" - resolved "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.20.2.tgz#91515527b376fc122ba83b13d70b01af8fe98f3f" - integrity sha512-jvS+ngBfrnTUBfOQq8NfGnSbF9BrqlR6hjJ2yVxMkmO5nL/cdifNbI30EfjRlN4g5wYWNnMPyj5Sa6R1pbLeag== - dependencies: - "@babel/helper-create-class-features-plugin" "^7.20.2" - "@babel/helper-plugin-utils" "^7.20.2" - "@babel/plugin-syntax-typescript" "^7.20.0" - -"@babel/plugin-transform-unicode-escapes@^7.18.10": - version "7.18.10" - resolved "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.18.10.tgz#1ecfb0eda83d09bbcb77c09970c2dd55832aa246" - integrity sha512-kKAdAI+YzPgGY/ftStBFXTI1LZFju38rYThnfMykS+IXy8BVx+res7s2fxf1l8I35DV2T97ezo6+SGrXz6B3iQ== - dependencies: - "@babel/helper-plugin-utils" "^7.18.9" - -"@babel/plugin-transform-unicode-regex@^7.18.6": - version "7.18.6" - resolved "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.18.6.tgz#194317225d8c201bbae103364ffe9e2cea36cdca" - integrity sha512-gE7A6Lt7YLnNOL3Pb9BNeZvi+d8l7tcRrG4+pwJjK9hD2xX4mEvjlQW60G9EEmfXVYRPv9VRQcyegIVHCql/AA== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.18.6" - "@babel/helper-plugin-utils" "^7.18.6" - -"@babel/preset-env@^7.11.0", "@babel/preset-env@^7.12.1", "@babel/preset-env@^7.16.4": - version "7.20.2" - resolved "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.20.2.tgz#9b1642aa47bb9f43a86f9630011780dab7f86506" - integrity sha512-1G0efQEWR1EHkKvKHqbG+IN/QdgwfByUpM5V5QroDzGV2t3S/WXNQd693cHiHTlCFMpr9B6FkPFXDA2lQcKoDg== - dependencies: - "@babel/compat-data" "^7.20.1" - "@babel/helper-compilation-targets" "^7.20.0" - "@babel/helper-plugin-utils" "^7.20.2" - "@babel/helper-validator-option" "^7.18.6" - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.18.6" - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.18.9" - "@babel/plugin-proposal-async-generator-functions" "^7.20.1" - "@babel/plugin-proposal-class-properties" "^7.18.6" - "@babel/plugin-proposal-class-static-block" "^7.18.6" - "@babel/plugin-proposal-dynamic-import" "^7.18.6" - "@babel/plugin-proposal-export-namespace-from" "^7.18.9" - "@babel/plugin-proposal-json-strings" "^7.18.6" - "@babel/plugin-proposal-logical-assignment-operators" "^7.18.9" - "@babel/plugin-proposal-nullish-coalescing-operator" "^7.18.6" - "@babel/plugin-proposal-numeric-separator" "^7.18.6" - "@babel/plugin-proposal-object-rest-spread" "^7.20.2" - "@babel/plugin-proposal-optional-catch-binding" "^7.18.6" - "@babel/plugin-proposal-optional-chaining" "^7.18.9" - "@babel/plugin-proposal-private-methods" "^7.18.6" - "@babel/plugin-proposal-private-property-in-object" "^7.18.6" - "@babel/plugin-proposal-unicode-property-regex" "^7.18.6" - "@babel/plugin-syntax-async-generators" "^7.8.4" - "@babel/plugin-syntax-class-properties" "^7.12.13" - "@babel/plugin-syntax-class-static-block" "^7.14.5" - "@babel/plugin-syntax-dynamic-import" "^7.8.3" - "@babel/plugin-syntax-export-namespace-from" "^7.8.3" - "@babel/plugin-syntax-import-assertions" "^7.20.0" - "@babel/plugin-syntax-json-strings" "^7.8.3" - "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" - "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" - "@babel/plugin-syntax-numeric-separator" "^7.10.4" - "@babel/plugin-syntax-object-rest-spread" "^7.8.3" - "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" - "@babel/plugin-syntax-optional-chaining" "^7.8.3" - "@babel/plugin-syntax-private-property-in-object" "^7.14.5" - "@babel/plugin-syntax-top-level-await" "^7.14.5" - "@babel/plugin-transform-arrow-functions" "^7.18.6" - "@babel/plugin-transform-async-to-generator" "^7.18.6" - "@babel/plugin-transform-block-scoped-functions" "^7.18.6" - "@babel/plugin-transform-block-scoping" "^7.20.2" - "@babel/plugin-transform-classes" "^7.20.2" - "@babel/plugin-transform-computed-properties" "^7.18.9" - "@babel/plugin-transform-destructuring" "^7.20.2" - "@babel/plugin-transform-dotall-regex" "^7.18.6" - "@babel/plugin-transform-duplicate-keys" "^7.18.9" - "@babel/plugin-transform-exponentiation-operator" "^7.18.6" - "@babel/plugin-transform-for-of" "^7.18.8" - "@babel/plugin-transform-function-name" "^7.18.9" - "@babel/plugin-transform-literals" "^7.18.9" - "@babel/plugin-transform-member-expression-literals" "^7.18.6" - "@babel/plugin-transform-modules-amd" "^7.19.6" - "@babel/plugin-transform-modules-commonjs" "^7.19.6" - "@babel/plugin-transform-modules-systemjs" "^7.19.6" - "@babel/plugin-transform-modules-umd" "^7.18.6" - "@babel/plugin-transform-named-capturing-groups-regex" "^7.19.1" - "@babel/plugin-transform-new-target" "^7.18.6" - "@babel/plugin-transform-object-super" "^7.18.6" - "@babel/plugin-transform-parameters" "^7.20.1" - "@babel/plugin-transform-property-literals" "^7.18.6" - "@babel/plugin-transform-regenerator" "^7.18.6" - "@babel/plugin-transform-reserved-words" "^7.18.6" - "@babel/plugin-transform-shorthand-properties" "^7.18.6" - "@babel/plugin-transform-spread" "^7.19.0" - "@babel/plugin-transform-sticky-regex" "^7.18.6" - "@babel/plugin-transform-template-literals" "^7.18.9" - "@babel/plugin-transform-typeof-symbol" "^7.18.9" - "@babel/plugin-transform-unicode-escapes" "^7.18.10" - "@babel/plugin-transform-unicode-regex" "^7.18.6" - "@babel/preset-modules" "^0.1.5" - "@babel/types" "^7.20.2" - babel-plugin-polyfill-corejs2 "^0.3.3" - babel-plugin-polyfill-corejs3 "^0.6.0" - babel-plugin-polyfill-regenerator "^0.4.1" - core-js-compat "^3.25.1" - semver "^6.3.0" + "@types/chai" "*" -"@babel/preset-modules@^0.1.5": - version "0.1.5" - resolved "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.5.tgz#ef939d6e7f268827e1841638dc6ff95515e115d9" - integrity sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA== - dependencies: - "@babel/helper-plugin-utils" "^7.0.0" - "@babel/plugin-proposal-unicode-property-regex" "^7.4.4" - "@babel/plugin-transform-dotall-regex" "^7.4.4" - "@babel/types" "^7.4.4" - esutils "^2.0.2" +"@types/chai@*", "@types/chai@^4.3.5": + version "4.3.9" + resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.3.9.tgz#144d762491967db8c6dea38e03d2206c2623feec" + integrity sha512-69TtiDzu0bcmKQv3yg1Zx409/Kd7r0b5F1PfpYJfSHzLGtB53547V4u+9iqKYsTu/O2ai6KTb0TInNpvuQ3qmg== -"@babel/preset-react@^7.12.5", "@babel/preset-react@^7.16.0": - version "7.18.6" - resolved "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.18.6.tgz#979f76d6277048dc19094c217b507f3ad517dd2d" - integrity sha512-zXr6atUmyYdiWRVLOZahakYmOBHtWc2WGCkP8PYTgZi0iJXDY2CN180TdrIW4OGOAdLc7TifzDIvtx6izaRIzg== - dependencies: - "@babel/helper-plugin-utils" "^7.18.6" - "@babel/helper-validator-option" "^7.18.6" - "@babel/plugin-transform-react-display-name" "^7.18.6" - "@babel/plugin-transform-react-jsx" "^7.18.6" - "@babel/plugin-transform-react-jsx-development" "^7.18.6" - "@babel/plugin-transform-react-pure-annotations" "^7.18.6" - -"@babel/preset-typescript@^7.16.0": - version "7.18.6" - resolved "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.18.6.tgz#ce64be3e63eddc44240c6358daefac17b3186399" - integrity sha512-s9ik86kXBAnD760aybBucdpnLsAt0jK1xqJn2juOn9lkOvSHV60os5hxoVJsPzMQxvnUJFAlkont2DvvaYEBtQ== - dependencies: - "@babel/helper-plugin-utils" "^7.18.6" - "@babel/helper-validator-option" "^7.18.6" - "@babel/plugin-transform-typescript" "^7.18.6" - -"@babel/runtime-corejs3@^7.10.2": - version "7.20.1" - resolved "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.20.1.tgz#d0775a49bb5fba77e42cbb7276c9955c7b05af8d" - integrity sha512-CGulbEDcg/ND1Im7fUNRZdGXmX2MTWVVZacQi/6DiKE5HNwZ3aVTm5PV4lO8HHz0B2h8WQyvKKjbX5XgTtydsg== - dependencies: - core-js-pure "^3.25.1" - regenerator-runtime "^0.13.10" - -"@babel/runtime@^7.10.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.14.5", "@babel/runtime@^7.14.8", "@babel/runtime@^7.16.3", "@babel/runtime@^7.17.2", "@babel/runtime@^7.18.9", "@babel/runtime@^7.19.4", "@babel/runtime@^7.20.1", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2": - version "7.20.1" - resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.1.tgz#1148bb33ab252b165a06698fde7576092a78b4a9" - integrity sha512-mrzLkl6U9YLF8qpqI7TB82PESyEGjm/0Ly91jG575eVxMMlb8fYfOXFZIJ8XfLrJZQbm7dlKry2bJmXBUEkdFg== - dependencies: - regenerator-runtime "^0.13.10" - -"@babel/template@^7.18.10", "@babel/template@^7.3.3": - version "7.18.10" - resolved "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz#6f9134835970d1dbf0835c0d100c9f38de0c5e71" - integrity sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA== - dependencies: - "@babel/code-frame" "^7.18.6" - "@babel/parser" "^7.18.10" - "@babel/types" "^7.18.10" - -"@babel/traverse@^7.19.0", "@babel/traverse@^7.19.1", "@babel/traverse@^7.20.1", "@babel/traverse@^7.4.5", "@babel/traverse@^7.7.2": - version "7.20.1" - resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.20.1.tgz#9b15ccbf882f6d107eeeecf263fbcdd208777ec8" - integrity sha512-d3tN8fkVJwFLkHkBN479SOsw4DMZnz8cdbL/gvuDuzy3TS6Nfw80HuQqhw1pITbIruHyh7d1fMA47kWzmcUEGA== - dependencies: - "@babel/code-frame" "^7.18.6" - "@babel/generator" "^7.20.1" - "@babel/helper-environment-visitor" "^7.18.9" - "@babel/helper-function-name" "^7.19.0" - "@babel/helper-hoist-variables" "^7.18.6" - "@babel/helper-split-export-declaration" "^7.18.6" - "@babel/parser" "^7.20.1" - "@babel/types" "^7.20.0" - debug "^4.1.0" - globals "^11.1.0" +"@types/chroma-js@^2.4.0": + version "2.4.2" + resolved "https://registry.yarnpkg.com/@types/chroma-js/-/chroma-js-2.4.2.tgz#5c57e9f9ce5343f134e376fb76e07fd3271f150f" + integrity sha512-gbiHvCuBS9aXkE3OEDfS69bscNLTYtbbx2TQf6WyOu+4eCH1AH1gPSiDGF2UzwkRFAbqKNsC5F0mY0xcaEHCbg== -"@babel/types@^7.0.0", "@babel/types@^7.12.6", "@babel/types@^7.18.10", "@babel/types@^7.18.6", "@babel/types@^7.18.9", "@babel/types@^7.19.0", "@babel/types@^7.20.0", "@babel/types@^7.20.2", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4": - version "7.20.2" - resolved "https://registry.npmjs.org/@babel/types/-/types-7.20.2.tgz#67ac09266606190f496322dbaff360fdaa5e7842" - integrity sha512-FnnvsNWgZCr232sqtXggapvlkk/tuwR/qhGzcmxI0GXLCjmPYQPzio2FbdlWuY6y1sHFfQKk+rRbUZ9VStQMog== +"@types/eslint@^8.4.5": + version "8.44.6" + resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-8.44.6.tgz#60e564551966dd255f4c01c459f0b4fb87068603" + integrity sha512-P6bY56TVmX8y9J87jHNgQh43h6VVU+6H7oN7hgvivV81K2XY8qJZ5vqPy/HdUoVIelii2kChYVzQanlswPWVFw== dependencies: - "@babel/helper-string-parser" "^7.19.4" - "@babel/helper-validator-identifier" "^7.19.1" - to-fast-properties "^2.0.0" - -"@bcoe/v8-coverage@^0.2.3": - version "0.2.3" - resolved "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" - integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== + "@types/estree" "*" + "@types/json-schema" "*" -"@csstools/normalize.css@*": - version "12.0.0" - resolved "https://registry.npmjs.org/@csstools/normalize.css/-/normalize.css-12.0.0.tgz#a9583a75c3f150667771f30b60d9f059473e62c4" - integrity sha512-M0qqxAcwCsIVfpFQSlGN5XjXWu8l5JDZN+fPt1LeW5SZexQTgnaEvgXAY+CeygRw0EeppWHi12JxESWiWrB0Sg== +"@types/estree@*", "@types/estree@^1.0.0": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.3.tgz#2be19e759a3dd18c79f9f436bd7363556c1a73dd" + integrity sha512-CS2rOaoQ/eAgAfcTfq6amKG7bsN+EMcgGY4FAFQdvSj2y1ixvOZTUA9mOtCai7E1SYu283XNw7urKK30nP3wkQ== -"@csstools/postcss-cascade-layers@^1.1.1": - version "1.1.1" - resolved "https://registry.npmjs.org/@csstools/postcss-cascade-layers/-/postcss-cascade-layers-1.1.1.tgz#8a997edf97d34071dd2e37ea6022447dd9e795ad" - integrity sha512-+KdYrpKC5TgomQr2DlZF4lDEpHcoxnj5IGddYYfBWJAKfj1JtuHUIqMa+E1pJJ+z3kvDViWMqyqPlG4Ja7amQA== - dependencies: - "@csstools/selector-specificity" "^2.0.2" - postcss-selector-parser "^6.0.10" +"@types/json-schema@*", "@types/json-schema@^7.0.12": + version "7.0.14" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.14.tgz#74a97a5573980802f32c8e47b663530ab3b6b7d1" + integrity sha512-U3PUjAudAdJBeC2pgN8uTIKgxrb4nlDF3SF0++EldXQvQBGkpFZMSnwQiIoDU77tv45VgNkl/L4ouD+rEomujw== -"@csstools/postcss-color-function@^1.1.1": - version "1.1.1" - resolved "https://registry.npmjs.org/@csstools/postcss-color-function/-/postcss-color-function-1.1.1.tgz#2bd36ab34f82d0497cfacdc9b18d34b5e6f64b6b" - integrity sha512-Bc0f62WmHdtRDjf5f3e2STwRAl89N2CLb+9iAwzrv4L2hncrbDwnQD9PCq0gtAt7pOI2leIV08HIBUd4jxD8cw== - dependencies: - "@csstools/postcss-progressive-custom-properties" "^1.1.0" - postcss-value-parser "^4.2.0" +"@types/json5@^0.0.29": + version "0.0.29" + resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" + integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ== -"@csstools/postcss-font-format-keywords@^1.0.1": - version "1.0.1" - resolved "https://registry.npmjs.org/@csstools/postcss-font-format-keywords/-/postcss-font-format-keywords-1.0.1.tgz#677b34e9e88ae997a67283311657973150e8b16a" - integrity sha512-ZgrlzuUAjXIOc2JueK0X5sZDjCtgimVp/O5CEqTcs5ShWBa6smhWYbS0x5cVc/+rycTDbjjzoP0KTDnUneZGOg== +"@types/lodash.throttle@^4.1.7": + version "4.1.8" + resolved "https://registry.yarnpkg.com/@types/lodash.throttle/-/lodash.throttle-4.1.8.tgz#9d1a630d6e413a32030be1dda040ab913b80d57a" + integrity sha512-EJT8Wg9HLcrsaTlFJ+wmolrGMCC/WBmqOISNi1y9hukgp15cYnfO435X1ReUl0VTIAYnRailHqSZEmzLJb5fiQ== dependencies: - postcss-value-parser "^4.2.0" + "@types/lodash" "*" -"@csstools/postcss-hwb-function@^1.0.2": - version "1.0.2" - resolved "https://registry.npmjs.org/@csstools/postcss-hwb-function/-/postcss-hwb-function-1.0.2.tgz#ab54a9fce0ac102c754854769962f2422ae8aa8b" - integrity sha512-YHdEru4o3Rsbjmu6vHy4UKOXZD+Rn2zmkAmLRfPet6+Jz4Ojw8cbWxe1n42VaXQhD3CQUXXTooIy8OkVbUcL+w== - dependencies: - postcss-value-parser "^4.2.0" +"@types/lodash@*": + version "4.14.200" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.200.tgz#435b6035c7eba9cdf1e039af8212c9e9281e7149" + integrity sha512-YI/M/4HRImtNf3pJgbF+W6FrXovqj+T+/HpENLTooK9PnkacBsDpeP3IpHab40CClUfhNmdM2WTNP2sa2dni5Q== -"@csstools/postcss-ic-unit@^1.0.1": - version "1.0.1" - resolved "https://registry.npmjs.org/@csstools/postcss-ic-unit/-/postcss-ic-unit-1.0.1.tgz#28237d812a124d1a16a5acc5c3832b040b303e58" - integrity sha512-Ot1rcwRAaRHNKC9tAqoqNZhjdYBzKk1POgWfhN4uCOE47ebGcLRqXjKkApVDpjifL6u2/55ekkpnFcp+s/OZUw== +"@types/node@*": + version "20.8.9" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.8.9.tgz#646390b4fab269abce59c308fc286dcd818a2b08" + integrity sha512-UzykFsT3FhHb1h7yD4CA4YhBHq545JC0YnEz41xkipN88eKQtL6rSgocL5tbAP6Ola9Izm/Aw4Ora8He4x0BHg== dependencies: - "@csstools/postcss-progressive-custom-properties" "^1.1.0" - postcss-value-parser "^4.2.0" + undici-types "~5.26.4" -"@csstools/postcss-is-pseudo-class@^2.0.7": - version "2.0.7" - resolved "https://registry.npmjs.org/@csstools/postcss-is-pseudo-class/-/postcss-is-pseudo-class-2.0.7.tgz#846ae6c0d5a1eaa878fce352c544f9c295509cd1" - integrity sha512-7JPeVVZHd+jxYdULl87lvjgvWldYu+Bc62s9vD/ED6/QTGjy0jy0US/f6BG53sVMTBJ1lzKZFpYmofBN9eaRiA== - dependencies: - "@csstools/selector-specificity" "^2.0.0" - postcss-selector-parser "^6.0.10" +"@types/prop-types@*": + version "15.7.9" + resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.9.tgz#b6f785caa7ea1fe4414d9df42ee0ab67f23d8a6d" + integrity sha512-n1yyPsugYNSmHgxDFjicaI2+gCNjsBck8UX9kuofAKlc0h1bL+20oSF72KeNaW2DUlesbEVCFgyV2dPGTiY42g== -"@csstools/postcss-nested-calc@^1.0.0": - version "1.0.0" - resolved "https://registry.npmjs.org/@csstools/postcss-nested-calc/-/postcss-nested-calc-1.0.0.tgz#d7e9d1d0d3d15cf5ac891b16028af2a1044d0c26" - integrity sha512-JCsQsw1wjYwv1bJmgjKSoZNvf7R6+wuHDAbi5f/7MbFhl2d/+v+TvBTU4BJH3G1X1H87dHl0mh6TfYogbT/dJQ== +"@types/react-dom@^18.2.14": + version "18.2.14" + resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.2.14.tgz#c01ba40e5bb57fc1dc41569bb3ccdb19eab1c539" + integrity sha512-V835xgdSVmyQmI1KLV2BEIUgqEuinxp9O4G6g3FqO/SqLac049E53aysv0oEFD2kHfejeKU+ZqL2bcFWj9gLAQ== dependencies: - postcss-value-parser "^4.2.0" + "@types/react" "*" -"@csstools/postcss-normalize-display-values@^1.0.1": - version "1.0.1" - resolved "https://registry.npmjs.org/@csstools/postcss-normalize-display-values/-/postcss-normalize-display-values-1.0.1.tgz#15da54a36e867b3ac5163ee12c1d7f82d4d612c3" - integrity sha512-jcOanIbv55OFKQ3sYeFD/T0Ti7AMXc9nM1hZWu8m/2722gOTxFg7xYu4RDLJLeZmPUVQlGzo4jhzvTUq3x4ZUw== +"@types/react-helmet@^6.1.8": + version "6.1.8" + resolved "https://registry.yarnpkg.com/@types/react-helmet/-/react-helmet-6.1.8.tgz#92942afbf620435602de1f500cd9b47d3c09a218" + integrity sha512-UyJFvbGWO8xKvfCPFTt/DG/vsgkMqyXbUQAa1pSPco1Whw85Z3ypMEqoHtCDfoW4Qu8XgJp63jyXEhOa4te5Kw== dependencies: - postcss-value-parser "^4.2.0" + "@types/react" "*" -"@csstools/postcss-oklab-function@^1.1.1": - version "1.1.1" - resolved "https://registry.npmjs.org/@csstools/postcss-oklab-function/-/postcss-oklab-function-1.1.1.tgz#88cee0fbc8d6df27079ebd2fa016ee261eecf844" - integrity sha512-nJpJgsdA3dA9y5pgyb/UfEzE7W5Ka7u0CX0/HIMVBNWzWemdcTH3XwANECU6anWv/ao4vVNLTMxhiPNZsTK6iA== +"@types/react-qr-reader@^2.1.6": + version "2.1.6" + resolved "https://registry.yarnpkg.com/@types/react-qr-reader/-/react-qr-reader-2.1.6.tgz#72ebc45530bd40aabfa1aa978c6fb58b674fdee8" + integrity sha512-KF3WXsCUczlVasxLTiXNy0bO3043g/qWyYdklFK2xyZuqVyQZyzAY5Cg0+55DZ1WFmyQoL5eIqWndnlMk6RyWg== dependencies: - "@csstools/postcss-progressive-custom-properties" "^1.1.0" - postcss-value-parser "^4.2.0" + "@types/react" "*" -"@csstools/postcss-progressive-custom-properties@^1.1.0", "@csstools/postcss-progressive-custom-properties@^1.3.0": - version "1.3.0" - resolved "https://registry.npmjs.org/@csstools/postcss-progressive-custom-properties/-/postcss-progressive-custom-properties-1.3.0.tgz#542292558384361776b45c85226b9a3a34f276fa" - integrity sha512-ASA9W1aIy5ygskZYuWams4BzafD12ULvSypmaLJT2jvQ8G0M3I8PRQhC0h7mG0Z3LI05+agZjqSR9+K9yaQQjA== +"@types/react-scroll@^1.8.9": + version "1.8.9" + resolved "https://registry.yarnpkg.com/@types/react-scroll/-/react-scroll-1.8.9.tgz#690dcde92442e0083027eb7f285f36de372c675d" + integrity sha512-FvfY7wmN7UqwStZFny6riMMnx56rdTBshE3irmoaZPeIEgBRvt1R9/lDNPQ3CVhm5OPJLHKDSvMk4c3JSwoY0g== dependencies: - postcss-value-parser "^4.2.0" + "@types/react" "*" -"@csstools/postcss-stepped-value-functions@^1.0.1": - version "1.0.1" - resolved "https://registry.npmjs.org/@csstools/postcss-stepped-value-functions/-/postcss-stepped-value-functions-1.0.1.tgz#f8772c3681cc2befed695e2b0b1d68e22f08c4f4" - integrity sha512-dz0LNoo3ijpTOQqEJLY8nyaapl6umbmDcgj4AD0lgVQ572b2eqA1iGZYTTWhrcrHztWDDRAX2DGYyw2VBjvCvQ== +"@types/react@*", "@types/react@^18.2.33": + version "18.2.33" + resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.33.tgz#055356243dc4350a9ee6c6a2c07c5cae12e38877" + integrity sha512-v+I7S+hu3PIBoVkKGpSYYpiBT1ijqEzWpzQD62/jm4K74hPpSP7FF9BnKG6+fg2+62weJYkkBWDJlZt5JO/9hg== dependencies: - postcss-value-parser "^4.2.0" + "@types/prop-types" "*" + "@types/scheduler" "*" + csstype "^3.0.2" -"@csstools/postcss-text-decoration-shorthand@^1.0.0": - version "1.0.0" - resolved "https://registry.npmjs.org/@csstools/postcss-text-decoration-shorthand/-/postcss-text-decoration-shorthand-1.0.0.tgz#ea96cfbc87d921eca914d3ad29340d9bcc4c953f" - integrity sha512-c1XwKJ2eMIWrzQenN0XbcfzckOLLJiczqy+YvfGmzoVXd7pT9FfObiSEfzs84bpE/VqfpEuAZ9tCRbZkZxxbdw== - dependencies: - postcss-value-parser "^4.2.0" +"@types/scheduler@*": + version "0.16.5" + resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.5.tgz#4751153abbf8d6199babb345a52e1eb4167d64af" + integrity sha512-s/FPdYRmZR8SjLWGMCuax7r3qCWQw9QKHzXVukAuuIJkXkDRwp+Pu5LMIVFi0Fxbav35WURicYr8u1QsoybnQw== -"@csstools/postcss-trigonometric-functions@^1.0.2": - version "1.0.2" - resolved "https://registry.npmjs.org/@csstools/postcss-trigonometric-functions/-/postcss-trigonometric-functions-1.0.2.tgz#94d3e4774c36d35dcdc88ce091336cb770d32756" - integrity sha512-woKaLO///4bb+zZC2s80l+7cm07M7268MsyG3M0ActXXEFi6SuhvriQYcb58iiKGbjwwIU7n45iRLEHypB47Og== - dependencies: - postcss-value-parser "^4.2.0" +"@types/semver@^7.5.0": + version "7.5.4" + resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.4.tgz#0a41252ad431c473158b22f9bfb9a63df7541cff" + integrity sha512-MMzuxN3GdFwskAnb6fz0orFvhfqi752yjaXylr0Rp4oDg5H0Zn1IuyRhDVvYOwAXoJirx2xuS16I3WjxnAIHiQ== -"@csstools/postcss-unset-value@^1.0.2": - version "1.0.2" - resolved "https://registry.npmjs.org/@csstools/postcss-unset-value/-/postcss-unset-value-1.0.2.tgz#c99bb70e2cdc7312948d1eb41df2412330b81f77" - integrity sha512-c8J4roPBILnelAsdLr4XOAR/GsTm0GJi4XpcfvoWk3U6KiTCqiFYc63KhRMQQX35jYMp4Ao8Ij9+IZRgMfJp1g== +"@types/stylis@^4.0.2": + version "4.2.2" + resolved "https://registry.yarnpkg.com/@types/stylis/-/stylis-4.2.2.tgz#baabb6b3aa6787e90a6bd6cd75cd8fb9a4f256a3" + integrity sha512-Rm17MsTpQQP5Jq4BF7CdrxJsDufoiL/q5IbJZYZmOZAJALyijgF7BzLgobXUqraNcQdqFYLYGeglDp6QzaxPpg== -"@csstools/selector-specificity@^2.0.0", "@csstools/selector-specificity@^2.0.2": - version "2.0.2" - resolved "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-2.0.2.tgz#1bfafe4b7ed0f3e4105837e056e0a89b108ebe36" - integrity sha512-IkpVW/ehM1hWKln4fCA3NzJU8KwD+kIOvPZA4cqxoJHtE21CCzjyp+Kxbu0i5I4tBNOlXPL9mjwnWlL0VEG4Fg== +"@types/trusted-types@^2.0.2": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@types/trusted-types/-/trusted-types-2.0.5.tgz#5cac7e7df3275bb95f79594f192d97da3b4fd5fe" + integrity sha512-I3pkr8j/6tmQtKV/ZzHtuaqYSQvyjGRKH4go60Rr0IDLlFxuRT5V32uvB1mecM5G1EVAUyF/4r4QZ1GHgz+mxA== + +"@typescript-eslint/eslint-plugin@^6.9.0": + version "6.9.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.9.0.tgz#fdb6f3821c0167e3356e9d89c80e8230b2e401f4" + integrity sha512-lgX7F0azQwRPB7t7WAyeHWVfW1YJ9NIgd9mvGhfQpRY56X6AVf8mwM8Wol+0z4liE7XX3QOt8MN1rUKCfSjRIA== + dependencies: + "@eslint-community/regexpp" "^4.5.1" + "@typescript-eslint/scope-manager" "6.9.0" + "@typescript-eslint/type-utils" "6.9.0" + "@typescript-eslint/utils" "6.9.0" + "@typescript-eslint/visitor-keys" "6.9.0" + debug "^4.3.4" + graphemer "^1.4.0" + ignore "^5.2.4" + natural-compare "^1.4.0" + semver "^7.5.4" + ts-api-utils "^1.0.1" + +"@typescript-eslint/parser@^6.9.0": + version "6.9.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-6.9.0.tgz#2b402cadeadd3f211c25820e5433413347b27391" + integrity sha512-GZmjMh4AJ/5gaH4XF2eXA8tMnHWP+Pm1mjQR2QN4Iz+j/zO04b9TOvJYOX2sCNIQHtRStKTxRY1FX7LhpJT4Gw== + dependencies: + "@typescript-eslint/scope-manager" "6.9.0" + "@typescript-eslint/types" "6.9.0" + "@typescript-eslint/typescript-estree" "6.9.0" + "@typescript-eslint/visitor-keys" "6.9.0" + debug "^4.3.4" -"@emotion/is-prop-valid@^0.8.2": - version "0.8.8" - resolved "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz#db28b1c4368a259b60a97311d6a952d4fd01ac1a" - integrity sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA== +"@typescript-eslint/scope-manager@6.9.0": + version "6.9.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-6.9.0.tgz#2626e9a7fe0e004c3e25f3b986c75f584431134e" + integrity sha512-1R8A9Mc39n4pCCz9o79qRO31HGNDvC7UhPhv26TovDsWPBDx+Sg3rOZdCELIA3ZmNoWAuxaMOT7aWtGRSYkQxw== dependencies: - "@emotion/memoize" "0.7.4" + "@typescript-eslint/types" "6.9.0" + "@typescript-eslint/visitor-keys" "6.9.0" -"@emotion/is-prop-valid@^1.1.0": - version "1.2.0" - resolved "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.0.tgz#7f2d35c97891669f7e276eb71c83376a5dc44c83" - integrity sha512-3aDpDprjM0AwaxGE09bOPkNxHpBd+kA6jty3RnaEXdweX1DF1U3VQpPYb0g1IStAuK7SVQ1cy+bNBBKp4W3Fjg== +"@typescript-eslint/type-utils@6.9.0": + version "6.9.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-6.9.0.tgz#23923c8c9677c2ad41457cf8e10a5f2946be1b04" + integrity sha512-XXeahmfbpuhVbhSOROIzJ+b13krFmgtc4GlEuu1WBT+RpyGPIA4Y/eGnXzjbDj5gZLzpAXO/sj+IF/x2GtTMjQ== dependencies: - "@emotion/memoize" "^0.8.0" - -"@emotion/memoize@0.7.4": - version "0.7.4" - resolved "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz#19bf0f5af19149111c40d98bb0cf82119f5d9eeb" - integrity sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw== - -"@emotion/memoize@^0.8.0": - version "0.8.0" - resolved "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.0.tgz#f580f9beb67176fa57aae70b08ed510e1b18980f" - integrity sha512-G/YwXTkv7Den9mXDO7AhLWkE3q+I92B+VqAE+dYG4NGPaHZGvt3G8Q0p9vmE+sq7rTGphUbAvmQ9YpbfMQGGlA== - -"@emotion/stylis@^0.8.4": - version "0.8.5" - resolved "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.8.5.tgz#deacb389bd6ee77d1e7fcaccce9e16c5c7e78e04" - integrity sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ== + "@typescript-eslint/typescript-estree" "6.9.0" + "@typescript-eslint/utils" "6.9.0" + debug "^4.3.4" + ts-api-utils "^1.0.1" -"@emotion/unitless@^0.7.4": - version "0.7.5" - resolved "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz#77211291c1900a700b8a78cfafda3160d76949ed" - integrity sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg== +"@typescript-eslint/types@6.9.0": + version "6.9.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-6.9.0.tgz#86a0cbe7ac46c0761429f928467ff3d92f841098" + integrity sha512-+KB0lbkpxBkBSiVCuQvduqMJy+I1FyDbdwSpM3IoBS7APl4Bu15lStPjgBIdykdRqQNYqYNMa8Kuidax6phaEw== -"@eslint/eslintrc@^1.3.3": - version "1.3.3" - resolved "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.3.tgz#2b044ab39fdfa75b4688184f9e573ce3c5b0ff95" - integrity sha512-uj3pT6Mg+3t39fvLrj8iuCIJ38zKO9FpGtJ4BBJebJhEwjoT+KLVNCcHT5QC9NGRIEi7fZ0ZR8YRb884auB4Lg== +"@typescript-eslint/typescript-estree@6.9.0": + version "6.9.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-6.9.0.tgz#d0601b245be873d8fe49f3737f93f8662c8693d4" + integrity sha512-NJM2BnJFZBEAbCfBP00zONKXvMqihZCrmwCaik0UhLr0vAgb6oguXxLX1k00oQyD+vZZ+CJn3kocvv2yxm4awQ== dependencies: - ajv "^6.12.4" - debug "^4.3.2" - espree "^9.4.0" - globals "^13.15.0" - ignore "^5.2.0" - import-fresh "^3.2.1" - js-yaml "^4.1.0" - minimatch "^3.1.2" - strip-json-comments "^3.1.1" - -"@fortawesome/fontawesome-common-types@6.2.1": - version "6.2.1" - resolved "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.2.1.tgz#411e02a820744d3f7e0d8d9df9d82b471beaa073" - integrity sha512-Sz07mnQrTekFWLz5BMjOzHl/+NooTdW8F8kDQxjWwbpOJcnoSg4vUDng8d/WR1wOxM0O+CY9Zw0nR054riNYtQ== + "@typescript-eslint/types" "6.9.0" + "@typescript-eslint/visitor-keys" "6.9.0" + debug "^4.3.4" + globby "^11.1.0" + is-glob "^4.0.3" + semver "^7.5.4" + ts-api-utils "^1.0.1" + +"@typescript-eslint/utils@6.9.0": + version "6.9.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-6.9.0.tgz#5bdac8604fca4823f090e4268e681c84d3597c9f" + integrity sha512-5Wf+Jsqya7WcCO8me504FBigeQKVLAMPmUzYgDbWchINNh1KJbxCgVya3EQ2MjvJMVeXl3pofRmprqX6mfQkjQ== + dependencies: + "@eslint-community/eslint-utils" "^4.4.0" + "@types/json-schema" "^7.0.12" + "@types/semver" "^7.5.0" + "@typescript-eslint/scope-manager" "6.9.0" + "@typescript-eslint/types" "6.9.0" + "@typescript-eslint/typescript-estree" "6.9.0" + semver "^7.5.4" + +"@typescript-eslint/visitor-keys@6.9.0": + version "6.9.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-6.9.0.tgz#cc69421c10c4ac997ed34f453027245988164e80" + integrity sha512-dGtAfqjV6RFOtIP8I0B4ZTBRrlTT8NHHlZZSchQx3qReaoDeXhYM++M4So2AgFK9ZB0emRPA6JI1HkafzA2Ibg== + dependencies: + "@typescript-eslint/types" "6.9.0" + eslint-visitor-keys "^3.4.1" + +"@ungap/structured-clone@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406" + integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ== -"@fortawesome/fontawesome-svg-core@^6.2.1": - version "6.2.1" - resolved "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.2.1.tgz#e87e905e444b5e7b715af09b64d27b53d4c8f9d9" - integrity sha512-HELwwbCz6C1XEcjzyT1Jugmz2NNklMrSPjZOWMlc+ZsHIVk+XOvOXLGGQtFBwSyqfJDNgRq4xBCwWOaZ/d9DEA== +"@vitejs/plugin-react-swc@^3.4.0": + version "3.4.0" + resolved "https://registry.yarnpkg.com/@vitejs/plugin-react-swc/-/plugin-react-swc-3.4.0.tgz#53ca6a07423abadec92f967e188d5ba49b350830" + integrity sha512-m7UaA4Uvz82N/0EOVpZL4XsFIakRqrFKeSNxa1FBLSXGvWrWRBwmZb4qxk+ZIVAZcW3c3dn5YosomDgx62XWcQ== dependencies: - "@fortawesome/fontawesome-common-types" "6.2.1" + "@swc/core" "^1.3.85" -"@fortawesome/free-brands-svg-icons@^6.2.1": - version "6.2.1" - resolved "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-6.2.1.tgz#04a6d6f7898f7ef392aba7a65030a584d4f4c84f" - integrity sha512-L8l4MfdHPmZlJ72PvzdfwOwbwcCAL0vx48tJRnI6u1PJXh+j2f3yDoKyQgO3qjEsgD5Fr2tQV/cPP8F/k6aUig== +"@vitest/expect@0.34.6": + version "0.34.6" + resolved "https://registry.yarnpkg.com/@vitest/expect/-/expect-0.34.6.tgz#608a7b7a9aa3de0919db99b4cc087340a03ea77e" + integrity sha512-QUzKpUQRc1qC7qdGo7rMK3AkETI7w18gTCUrsNnyjjJKYiuUB9+TQK3QnR1unhCnWRC0AbKv2omLGQDF/mIjOw== dependencies: - "@fortawesome/fontawesome-common-types" "6.2.1" + "@vitest/spy" "0.34.6" + "@vitest/utils" "0.34.6" + chai "^4.3.10" -"@fortawesome/free-regular-svg-icons@^6.2.1": - version "6.2.1" - resolved "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-6.2.1.tgz#650e56d937755a8341f2eef258ecb6f95458820f" - integrity sha512-wiqcNDNom75x+pe88FclpKz7aOSqS2lOivZeicMV5KRwOAeypxEYWAK/0v+7r+LrEY30+qzh8r2XDaEHvoLsMA== +"@vitest/runner@0.34.6": + version "0.34.6" + resolved "https://registry.yarnpkg.com/@vitest/runner/-/runner-0.34.6.tgz#6f43ca241fc96b2edf230db58bcde5b974b8dcaf" + integrity sha512-1CUQgtJSLF47NnhN+F9X2ycxUP0kLHQ/JWvNHbeBfwW8CzEGgeskzNnHDyv1ieKTltuR6sdIHV+nmR6kPxQqzQ== dependencies: - "@fortawesome/fontawesome-common-types" "6.2.1" + "@vitest/utils" "0.34.6" + p-limit "^4.0.0" + pathe "^1.1.1" -"@fortawesome/free-solid-svg-icons@^6.2.1": - version "6.2.1" - resolved "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.2.1.tgz#2290ea5adcf1537cbd0c43de6feb38af02141d27" - integrity sha512-oKuqrP5jbfEPJWTij4sM+/RvgX+RMFwx3QZCZcK9PrBDgxC35zuc7AOFsyMjMd/PIFPeB2JxyqDr5zs/DZFPPw== +"@vitest/snapshot@0.34.6": + version "0.34.6" + resolved "https://registry.yarnpkg.com/@vitest/snapshot/-/snapshot-0.34.6.tgz#b4528cf683b60a3e8071cacbcb97d18b9d5e1d8b" + integrity sha512-B3OZqYn6k4VaN011D+ve+AA4whM4QkcwcrwaKwAbyyvS/NB1hCWjFIBQxAQQSQir9/RtyAAGuq+4RJmbn2dH4w== dependencies: - "@fortawesome/fontawesome-common-types" "6.2.1" + magic-string "^0.30.1" + pathe "^1.1.1" + pretty-format "^29.5.0" -"@fortawesome/react-fontawesome@^0.2.0": - version "0.2.0" - resolved "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.2.0.tgz#d90dd8a9211830b4e3c08e94b63a0ba7291ddcf4" - integrity sha512-uHg75Rb/XORTtVt7OS9WoK8uM276Ufi7gCzshVWkUJbHhh3svsUUeqXerrM96Wm7fRiDzfKRwSoahhMIkGAYHw== +"@vitest/spy@0.34.6": + version "0.34.6" + resolved "https://registry.yarnpkg.com/@vitest/spy/-/spy-0.34.6.tgz#b5e8642a84aad12896c915bce9b3cc8cdaf821df" + integrity sha512-xaCvneSaeBw/cz8ySmF7ZwGvL0lBjfvqc1LpQ/vcdHEvpLn3Ff1vAvjw+CoGn0802l++5L/pxb7whwcWAw+DUQ== dependencies: - prop-types "^15.8.1" - -"@graphql-typed-document-node/core@^3.1.1": - version "3.2.0" - resolved "https://registry.yarnpkg.com/@graphql-typed-document-node/core/-/core-3.2.0.tgz#5f3d96ec6b2354ad6d8a28bf216a1d97b5426861" - integrity sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ== + tinyspy "^2.1.1" -"@humanwhocodes/config-array@^0.11.6": - version "0.11.7" - resolved "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.7.tgz#38aec044c6c828f6ed51d5d7ae3d9b9faf6dbb0f" - integrity sha512-kBbPWzN8oVMLb0hOUYXhmxggL/1cJE6ydvjDIGi9EnAGUyA7cLVKQg+d/Dsm+KZwx2czGHrCmMVLiyg8s5JPKw== +"@vitest/utils@0.34.6": + version "0.34.6" + resolved "https://registry.yarnpkg.com/@vitest/utils/-/utils-0.34.6.tgz#38a0a7eedddb8e7291af09a2409cb8a189516968" + integrity sha512-IG5aDD8S6zlvloDsnzHw0Ut5xczlF+kv2BOTo+iXfPr54Yhi5qbVOgGB1hZaVq4iJ4C/MZ2J0y15IlsV/ZcI0A== dependencies: - "@humanwhocodes/object-schema" "^1.2.1" - debug "^4.1.1" - minimatch "^3.0.5" + diff-sequences "^29.4.3" + loupe "^2.3.6" + pretty-format "^29.5.0" -"@humanwhocodes/module-importer@^1.0.1": +"@wry/caches@^1.0.0": version "1.0.1" - resolved "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" - integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== - -"@humanwhocodes/object-schema@^1.2.1": - version "1.2.1" - resolved "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" - integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== - -"@istanbuljs/load-nyc-config@^1.0.0": - version "1.1.0" - resolved "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" - integrity sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ== - dependencies: - camelcase "^5.3.1" - find-up "^4.1.0" - get-package-type "^0.1.0" - js-yaml "^3.13.1" - resolve-from "^5.0.0" - -"@istanbuljs/schema@^0.1.2": - version "0.1.3" - resolved "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" - integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== - -"@jest/console@^27.5.1": - version "27.5.1" - resolved "https://registry.npmjs.org/@jest/console/-/console-27.5.1.tgz#260fe7239602fe5130a94f1aa386eff54b014bba" - integrity sha512-kZ/tNpS3NXn0mlXXXPNuDZnb4c0oZ20r4K5eemM2k30ZC3G0T02nXUvyhf5YdbXWHPEJLc9qGLxEZ216MdL+Zg== - dependencies: - "@jest/types" "^27.5.1" - "@types/node" "*" - chalk "^4.0.0" - jest-message-util "^27.5.1" - jest-util "^27.5.1" - slash "^3.0.0" - -"@jest/console@^28.1.3": - version "28.1.3" - resolved "https://registry.npmjs.org/@jest/console/-/console-28.1.3.tgz#2030606ec03a18c31803b8a36382762e447655df" - integrity sha512-QPAkP5EwKdK/bxIr6C1I4Vs0rm2nHiANzj/Z5X2JQkrZo6IqvC4ldZ9K95tF0HdidhA8Bo6egxSzUFPYKcEXLw== - dependencies: - "@jest/types" "^28.1.3" - "@types/node" "*" - chalk "^4.0.0" - jest-message-util "^28.1.3" - jest-util "^28.1.3" - slash "^3.0.0" - -"@jest/core@^27.5.1": - version "27.5.1" - resolved "https://registry.npmjs.org/@jest/core/-/core-27.5.1.tgz#267ac5f704e09dc52de2922cbf3af9edcd64b626" - integrity sha512-AK6/UTrvQD0Cd24NSqmIA6rKsu0tKIxfiCducZvqxYdmMisOYAsdItspT+fQDQYARPf8XgjAFZi0ogW2agH5nQ== - dependencies: - "@jest/console" "^27.5.1" - "@jest/reporters" "^27.5.1" - "@jest/test-result" "^27.5.1" - "@jest/transform" "^27.5.1" - "@jest/types" "^27.5.1" - "@types/node" "*" - ansi-escapes "^4.2.1" - chalk "^4.0.0" - emittery "^0.8.1" - exit "^0.1.2" - graceful-fs "^4.2.9" - jest-changed-files "^27.5.1" - jest-config "^27.5.1" - jest-haste-map "^27.5.1" - jest-message-util "^27.5.1" - jest-regex-util "^27.5.1" - jest-resolve "^27.5.1" - jest-resolve-dependencies "^27.5.1" - jest-runner "^27.5.1" - jest-runtime "^27.5.1" - jest-snapshot "^27.5.1" - jest-util "^27.5.1" - jest-validate "^27.5.1" - jest-watcher "^27.5.1" - micromatch "^4.0.4" - rimraf "^3.0.0" - slash "^3.0.0" - strip-ansi "^6.0.0" - -"@jest/environment@^27.5.1": - version "27.5.1" - resolved "https://registry.npmjs.org/@jest/environment/-/environment-27.5.1.tgz#d7425820511fe7158abbecc010140c3fd3be9c74" - integrity sha512-/WQjhPJe3/ghaol/4Bq480JKXV/Rfw8nQdN7f41fM8VDHLcxKXou6QyXAh3EFr9/bVG3x74z1NWDkP87EiY8gA== - dependencies: - "@jest/fake-timers" "^27.5.1" - "@jest/types" "^27.5.1" - "@types/node" "*" - jest-mock "^27.5.1" - -"@jest/expect-utils@^29.3.1": - version "29.3.1" - resolved "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.3.1.tgz#531f737039e9b9e27c42449798acb5bba01935b6" - integrity sha512-wlrznINZI5sMjwvUoLVk617ll/UYfGIZNxmbU+Pa7wmkL4vYzhV9R2pwVqUh4NWWuLQWkI8+8mOkxs//prKQ3g== - dependencies: - jest-get-type "^29.2.0" - -"@jest/fake-timers@^27.5.1": - version "27.5.1" - resolved "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-27.5.1.tgz#76979745ce0579c8a94a4678af7a748eda8ada74" - integrity sha512-/aPowoolwa07k7/oM3aASneNeBGCmGQsc3ugN4u6s4C/+s5M64MFo/+djTdiwcbQlRfFElGuDXWzaWj6QgKObQ== - dependencies: - "@jest/types" "^27.5.1" - "@sinonjs/fake-timers" "^8.0.1" - "@types/node" "*" - jest-message-util "^27.5.1" - jest-mock "^27.5.1" - jest-util "^27.5.1" - -"@jest/globals@^27.5.1": - version "27.5.1" - resolved "https://registry.npmjs.org/@jest/globals/-/globals-27.5.1.tgz#7ac06ce57ab966566c7963431cef458434601b2b" - integrity sha512-ZEJNB41OBQQgGzgyInAv0UUfDDj3upmHydjieSxFvTRuZElrx7tXg/uVQ5hYVEwiXs3+aMsAeEc9X7xiSKCm4Q== - dependencies: - "@jest/environment" "^27.5.1" - "@jest/types" "^27.5.1" - expect "^27.5.1" - -"@jest/reporters@^27.5.1": - version "27.5.1" - resolved "https://registry.npmjs.org/@jest/reporters/-/reporters-27.5.1.tgz#ceda7be96170b03c923c37987b64015812ffec04" - integrity sha512-cPXh9hWIlVJMQkVk84aIvXuBB4uQQmFqZiacloFuGiP3ah1sbCxCosidXFDfqG8+6fO1oR2dTJTlsOy4VFmUfw== - dependencies: - "@bcoe/v8-coverage" "^0.2.3" - "@jest/console" "^27.5.1" - "@jest/test-result" "^27.5.1" - "@jest/transform" "^27.5.1" - "@jest/types" "^27.5.1" - "@types/node" "*" - chalk "^4.0.0" - collect-v8-coverage "^1.0.0" - exit "^0.1.2" - glob "^7.1.2" - graceful-fs "^4.2.9" - istanbul-lib-coverage "^3.0.0" - istanbul-lib-instrument "^5.1.0" - istanbul-lib-report "^3.0.0" - istanbul-lib-source-maps "^4.0.0" - istanbul-reports "^3.1.3" - jest-haste-map "^27.5.1" - jest-resolve "^27.5.1" - jest-util "^27.5.1" - jest-worker "^27.5.1" - slash "^3.0.0" - source-map "^0.6.0" - string-length "^4.0.1" - terminal-link "^2.0.0" - v8-to-istanbul "^8.1.0" - -"@jest/schemas@^28.1.3": - version "28.1.3" - resolved "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz#ad8b86a66f11f33619e3d7e1dcddd7f2d40ff905" - integrity sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg== - dependencies: - "@sinclair/typebox" "^0.24.1" - -"@jest/schemas@^29.0.0": - version "29.0.0" - resolved "https://registry.npmjs.org/@jest/schemas/-/schemas-29.0.0.tgz#5f47f5994dd4ef067fb7b4188ceac45f77fe952a" - integrity sha512-3Ab5HgYIIAnS0HjqJHQYZS+zXc4tUmTmBH3z83ajI6afXp8X3ZtdLX+nXx+I7LNkJD7uN9LAVhgnjDgZa2z0kA== - dependencies: - "@sinclair/typebox" "^0.24.1" - -"@jest/source-map@^27.5.1": - version "27.5.1" - resolved "https://registry.npmjs.org/@jest/source-map/-/source-map-27.5.1.tgz#6608391e465add4205eae073b55e7f279e04e8cf" - integrity sha512-y9NIHUYF3PJRlHk98NdC/N1gl88BL08aQQgu4k4ZopQkCw9t9cV8mtl3TV8b/YCB8XaVTFrmUTAJvjsntDireg== - dependencies: - callsites "^3.0.0" - graceful-fs "^4.2.9" - source-map "^0.6.0" - -"@jest/test-result@^27.5.1": - version "27.5.1" - resolved "https://registry.npmjs.org/@jest/test-result/-/test-result-27.5.1.tgz#56a6585fa80f7cdab72b8c5fc2e871d03832f5bb" - integrity sha512-EW35l2RYFUcUQxFJz5Cv5MTOxlJIQs4I7gxzi2zVU7PJhOwfYq1MdC5nhSmYjX1gmMmLPvB3sIaC+BkcHRBfag== - dependencies: - "@jest/console" "^27.5.1" - "@jest/types" "^27.5.1" - "@types/istanbul-lib-coverage" "^2.0.0" - collect-v8-coverage "^1.0.0" - -"@jest/test-result@^28.1.3": - version "28.1.3" - resolved "https://registry.npmjs.org/@jest/test-result/-/test-result-28.1.3.tgz#5eae945fd9f4b8fcfce74d239e6f725b6bf076c5" - integrity sha512-kZAkxnSE+FqE8YjW8gNuoVkkC9I7S1qmenl8sGcDOLropASP+BkcGKwhXoyqQuGOGeYY0y/ixjrd/iERpEXHNg== - dependencies: - "@jest/console" "^28.1.3" - "@jest/types" "^28.1.3" - "@types/istanbul-lib-coverage" "^2.0.0" - collect-v8-coverage "^1.0.0" - -"@jest/test-sequencer@^27.5.1": - version "27.5.1" - resolved "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-27.5.1.tgz#4057e0e9cea4439e544c6353c6affe58d095745b" - integrity sha512-LCheJF7WB2+9JuCS7VB/EmGIdQuhtqjRNI9A43idHv3E4KltCTsPsLxvdaubFHSYwY/fNjMWjl6vNRhDiN7vpQ== - dependencies: - "@jest/test-result" "^27.5.1" - graceful-fs "^4.2.9" - jest-haste-map "^27.5.1" - jest-runtime "^27.5.1" - -"@jest/transform@^27.5.1": - version "27.5.1" - resolved "https://registry.npmjs.org/@jest/transform/-/transform-27.5.1.tgz#6c3501dcc00c4c08915f292a600ece5ecfe1f409" - integrity sha512-ipON6WtYgl/1329g5AIJVbUuEh0wZVbdpGwC99Jw4LwuoBNS95MVphU6zOeD9pDkon+LLbFL7lOQRapbB8SCHw== - dependencies: - "@babel/core" "^7.1.0" - "@jest/types" "^27.5.1" - babel-plugin-istanbul "^6.1.1" - chalk "^4.0.0" - convert-source-map "^1.4.0" - fast-json-stable-stringify "^2.0.0" - graceful-fs "^4.2.9" - jest-haste-map "^27.5.1" - jest-regex-util "^27.5.1" - jest-util "^27.5.1" - micromatch "^4.0.4" - pirates "^4.0.4" - slash "^3.0.0" - source-map "^0.6.1" - write-file-atomic "^3.0.0" - -"@jest/types@^27.5.1": - version "27.5.1" - resolved "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz#3c79ec4a8ba61c170bf937bcf9e98a9df175ec80" - integrity sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw== - dependencies: - "@types/istanbul-lib-coverage" "^2.0.0" - "@types/istanbul-reports" "^3.0.0" - "@types/node" "*" - "@types/yargs" "^16.0.0" - chalk "^4.0.0" - -"@jest/types@^28.1.3": - version "28.1.3" - resolved "https://registry.npmjs.org/@jest/types/-/types-28.1.3.tgz#b05de80996ff12512bc5ceb1d208285a7d11748b" - integrity sha512-RyjiyMUZrKz/c+zlMFO1pm70DcIlST8AeWTkoUdZevew44wcNZQHsEVOiCVtgVnlFFD82FPaXycys58cf2muVQ== - dependencies: - "@jest/schemas" "^28.1.3" - "@types/istanbul-lib-coverage" "^2.0.0" - "@types/istanbul-reports" "^3.0.0" - "@types/node" "*" - "@types/yargs" "^17.0.8" - chalk "^4.0.0" - -"@jest/types@^29.3.1": - version "29.3.1" - resolved "https://registry.npmjs.org/@jest/types/-/types-29.3.1.tgz#7c5a80777cb13e703aeec6788d044150341147e3" - integrity sha512-d0S0jmmTpjnhCmNpApgX3jrUZgZ22ivKJRvL2lli5hpCRoNnp1f85r2/wpKfXuYu8E7Jjh1hGfhPyup1NM5AmA== - dependencies: - "@jest/schemas" "^29.0.0" - "@types/istanbul-lib-coverage" "^2.0.0" - "@types/istanbul-reports" "^3.0.0" - "@types/node" "*" - "@types/yargs" "^17.0.8" - chalk "^4.0.0" - -"@jridgewell/gen-mapping@^0.1.0": - version "0.1.1" - resolved "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz#e5d2e450306a9491e3bd77e323e38d7aff315996" - integrity sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w== - dependencies: - "@jridgewell/set-array" "^1.0.0" - "@jridgewell/sourcemap-codec" "^1.4.10" - -"@jridgewell/gen-mapping@^0.3.0", "@jridgewell/gen-mapping@^0.3.2": - version "0.3.2" - resolved "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz#c1aedc61e853f2bb9f5dfe6d4442d3b565b253b9" - integrity sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A== - dependencies: - "@jridgewell/set-array" "^1.0.1" - "@jridgewell/sourcemap-codec" "^1.4.10" - "@jridgewell/trace-mapping" "^0.3.9" - -"@jridgewell/resolve-uri@3.1.0": - version "3.1.0" - resolved "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78" - integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w== - -"@jridgewell/set-array@^1.0.0", "@jridgewell/set-array@^1.0.1": - version "1.1.2" - resolved "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72" - integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== - -"@jridgewell/source-map@^0.3.2": - version "0.3.2" - resolved "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz#f45351aaed4527a298512ec72f81040c998580fb" - integrity sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw== - dependencies: - "@jridgewell/gen-mapping" "^0.3.0" - "@jridgewell/trace-mapping" "^0.3.9" - -"@jridgewell/sourcemap-codec@1.4.14", "@jridgewell/sourcemap-codec@^1.4.10": - version "1.4.14" - resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24" - integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== - -"@jridgewell/trace-mapping@^0.3.14", "@jridgewell/trace-mapping@^0.3.9": - version "0.3.17" - resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz#793041277af9073b0951a7fe0f0d8c4c98c36985" - integrity sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g== - dependencies: - "@jridgewell/resolve-uri" "3.1.0" - "@jridgewell/sourcemap-codec" "1.4.14" - -"@leichtgewicht/ip-codec@^2.0.1": - version "2.0.4" - resolved "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz#b2ac626d6cb9c8718ab459166d4bb405b8ffa78b" - integrity sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A== - -"@motionone/animation@^10.13.1": - version "10.14.0" - resolved "https://registry.npmjs.org/@motionone/animation/-/animation-10.14.0.tgz#2f2a3517183bb58d82e389aac777fe0850079de6" - integrity sha512-h+1sdyBP8vbxEBW5gPFDnj+m2DCqdlAuf2g6Iafb1lcMnqjsRXWlPw1AXgvUMXmreyhqmPbJqoNfIKdytampRQ== - dependencies: - "@motionone/easing" "^10.14.0" - "@motionone/types" "^10.14.0" - "@motionone/utils" "^10.14.0" - tslib "^2.3.1" - -"@motionone/dom@10.13.1": - version "10.13.1" - resolved "https://registry.npmjs.org/@motionone/dom/-/dom-10.13.1.tgz#fc29ea5d12538f21b211b3168e502cfc07a24882" - integrity sha512-zjfX+AGMIt/fIqd/SL1Lj93S6AiJsEA3oc5M9VkUr+Gz+juRmYN1vfvZd6MvEkSqEjwPQgcjN7rGZHrDB9APfQ== - dependencies: - "@motionone/animation" "^10.13.1" - "@motionone/generators" "^10.13.1" - "@motionone/types" "^10.13.0" - "@motionone/utils" "^10.13.1" - hey-listen "^1.0.8" - tslib "^2.3.1" - -"@motionone/easing@^10.14.0": - version "10.14.0" - resolved "https://registry.npmjs.org/@motionone/easing/-/easing-10.14.0.tgz#d8154b7f71491414f3cdee23bd3838d763fffd00" - integrity sha512-2vUBdH9uWTlRbuErhcsMmt1jvMTTqvGmn9fHq8FleFDXBlHFs5jZzHJT9iw+4kR1h6a4SZQuCf72b9ji92qNYA== - dependencies: - "@motionone/utils" "^10.14.0" - tslib "^2.3.1" - -"@motionone/generators@^10.13.1": - version "10.14.0" - resolved "https://registry.npmjs.org/@motionone/generators/-/generators-10.14.0.tgz#e05d9dd56da78a4b92db99185848a0f3db62242d" - integrity sha512-6kRHezoFfIjFN7pPpaxmkdZXD36tQNcyJe3nwVqwJ+ZfC0e3rFmszR8kp9DEVFs9QL/akWjuGPSLBI1tvz+Vjg== - dependencies: - "@motionone/types" "^10.14.0" - "@motionone/utils" "^10.14.0" - tslib "^2.3.1" - -"@motionone/types@^10.13.0", "@motionone/types@^10.14.0": - version "10.14.0" - resolved "https://registry.npmjs.org/@motionone/types/-/types-10.14.0.tgz#148c34f3270b175397e49c3058b33fab405c21e3" - integrity sha512-3bNWyYBHtVd27KncnJLhksMFQ5o2MSdk1cA/IZqsHtA9DnRM1SYgN01CTcJ8Iw8pCXF5Ocp34tyAjY7WRpOJJQ== - -"@motionone/utils@^10.13.1", "@motionone/utils@^10.14.0": - version "10.14.0" - resolved "https://registry.npmjs.org/@motionone/utils/-/utils-10.14.0.tgz#a19a3464ed35b08506747b062d035c7bc9bbe708" - integrity sha512-sLWBLPzRqkxmOTRzSaD3LFQXCPHvDzyHJ1a3VP9PRzBxyVd2pv51/gMOsdAcxQ9n+MIeGJnxzXBYplUHKj4jkw== - dependencies: - "@motionone/types" "^10.14.0" - hey-listen "^1.0.8" - tslib "^2.3.1" - -"@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1": - version "5.1.1-v1" - resolved "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz#dbf733a965ca47b1973177dc0bb6c889edcfb129" - integrity sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg== - dependencies: - eslint-scope "5.1.1" - -"@noble/hashes@1.1.3": - version "1.1.3" - resolved "https://registry.npmjs.org/@noble/hashes/-/hashes-1.1.3.tgz#360afc77610e0a61f3417e497dcf36862e4f8111" - integrity sha512-CE0FCR57H2acVI5UOzIGSSIYxZ6v/HOhDR0Ro9VLyhnzLwx0o8W1mmgaqlEUx4049qJDlIBRztv5k+MM8vbO3A== - -"@noble/secp256k1@1.7.0": - version "1.7.0" - resolved "https://registry.npmjs.org/@noble/secp256k1/-/secp256k1-1.7.0.tgz#d15357f7c227e751d90aa06b05a0e5cf993ba8c1" - integrity sha512-kbacwGSsH/CTout0ZnZWxnW1B+jH/7r/WAAKLBtrRJ/+CUH7lgmQzl3GTrQua3SGKWNSDsS6lmjnDpIJ5Dxyaw== - -"@nodelib/fs.scandir@2.1.5": - version "2.1.5" - resolved "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" - integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== - dependencies: - "@nodelib/fs.stat" "2.0.5" - run-parallel "^1.1.9" - -"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": - version "2.0.5" - resolved "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" - integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== - -"@nodelib/fs.walk@^1.2.3", "@nodelib/fs.walk@^1.2.8": - version "1.2.8" - resolved "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" - integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== - dependencies: - "@nodelib/fs.scandir" "2.1.5" - fastq "^1.6.0" - -"@pkgr/utils@^2.3.1": - version "2.3.1" - resolved "https://registry.npmjs.org/@pkgr/utils/-/utils-2.3.1.tgz#0a9b06ffddee364d6642b3cd562ca76f55b34a03" - integrity sha512-wfzX8kc1PMyUILA+1Z/EqoE4UCXGy0iRGMhPwdfae1+f0OXlLqCk+By+aMzgJBzR9AzS4CDizioG6Ss1gvAFJw== - dependencies: - cross-spawn "^7.0.3" - is-glob "^4.0.3" - open "^8.4.0" - picocolors "^1.0.0" - tiny-glob "^0.2.9" - tslib "^2.4.0" - -"@pmmmwh/react-refresh-webpack-plugin@^0.5.3": - version "0.5.9" - resolved "https://registry.npmjs.org/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.5.9.tgz#35aae6624a6270ca7ad755800b7eec417fa6f830" - integrity sha512-7QV4cqUwhkDIHpMAZ9mestSJ2DMIotVTbOUwbiudhjCRTAWWKIaBecELiEM2LT3AHFeOAaHIcFu4dbXjX+9GBA== - dependencies: - ansi-html-community "^0.0.8" - common-path-prefix "^3.0.0" - core-js-pure "^3.23.3" - error-stack-parser "^2.0.6" - find-up "^5.0.0" - html-entities "^2.1.0" - loader-utils "^2.0.3" - schema-utils "^3.0.0" - source-map "^0.7.3" - -"@polkadot/api-augment@9.9.4": - version "9.9.4" - resolved "https://registry.yarnpkg.com/@polkadot/api-augment/-/api-augment-9.9.4.tgz#cb09d8edfc3a5d61c6519f30a2f02b1bb939c9f6" - integrity sha512-+T9YWw5kEi7AkSoS2UfE1nrVeJUtD92elQBZ3bMMkfM1geKWhSnvBLyTMn6kFmNXTfK0qt8YKS1pwbux7cC9tg== - dependencies: - "@babel/runtime" "^7.20.1" - "@polkadot/api-base" "9.9.4" - "@polkadot/rpc-augment" "9.9.4" - "@polkadot/types" "9.9.4" - "@polkadot/types-augment" "9.9.4" - "@polkadot/types-codec" "9.9.4" - "@polkadot/util" "^10.1.14" - -"@polkadot/api-base@9.9.4": - version "9.9.4" - resolved "https://registry.yarnpkg.com/@polkadot/api-base/-/api-base-9.9.4.tgz#eccc645b60485bfe64a5e6a9ebb3195d2011c0ee" - integrity sha512-G1DcxcMeGcvaAAA3u5Tbf70zE5aIuAPEAXnptFMF0lvJz4O6CM8k8ZZFTSk25hjsYlnx8WI1FTc97q4/tKie+Q== - dependencies: - "@babel/runtime" "^7.20.1" - "@polkadot/rpc-core" "9.9.4" - "@polkadot/types" "9.9.4" - "@polkadot/util" "^10.1.14" - rxjs "^7.5.7" - -"@polkadot/api-derive@9.9.4": - version "9.9.4" - resolved "https://registry.yarnpkg.com/@polkadot/api-derive/-/api-derive-9.9.4.tgz#0eedd9c604be2425d8a1adcf048446184a5aaec9" - integrity sha512-3ka7GzY4QbI3d/DHjQ9SjfDOTDxeU8gM2Dn31BP1oFzGwdFe2GZhDIE//lR5S6UDVxNNlgWz4927AunOQcuAmg== - dependencies: - "@babel/runtime" "^7.20.1" - "@polkadot/api" "9.9.4" - "@polkadot/api-augment" "9.9.4" - "@polkadot/api-base" "9.9.4" - "@polkadot/rpc-core" "9.9.4" - "@polkadot/types" "9.9.4" - "@polkadot/types-codec" "9.9.4" - "@polkadot/util" "^10.1.14" - "@polkadot/util-crypto" "^10.1.14" - rxjs "^7.5.7" - -"@polkadot/api@9.9.4", "@polkadot/api@^9.9.4": - version "9.9.4" - resolved "https://registry.yarnpkg.com/@polkadot/api/-/api-9.9.4.tgz#a4899d7497644378a94e0cc6fcbf73a5e2d31b92" - integrity sha512-ze7W/DXsPHsixrFOACzugDQqezzrUGGX1Z2JOl6z+V8pd+ZKLSecsKJFUzf4yoBT82ArITYPtRVx/Dq9b9K2dA== - dependencies: - "@babel/runtime" "^7.20.1" - "@polkadot/api-augment" "9.9.4" - "@polkadot/api-base" "9.9.4" - "@polkadot/api-derive" "9.9.4" - "@polkadot/keyring" "^10.1.14" - "@polkadot/rpc-augment" "9.9.4" - "@polkadot/rpc-core" "9.9.4" - "@polkadot/rpc-provider" "9.9.4" - "@polkadot/types" "9.9.4" - "@polkadot/types-augment" "9.9.4" - "@polkadot/types-codec" "9.9.4" - "@polkadot/types-create" "9.9.4" - "@polkadot/types-known" "9.9.4" - "@polkadot/util" "^10.1.14" - "@polkadot/util-crypto" "^10.1.14" - eventemitter3 "^4.0.7" - rxjs "^7.5.7" - -"@polkadot/keyring@^10.1.12", "@polkadot/keyring@^10.1.14": - version "10.1.14" - resolved "https://registry.yarnpkg.com/@polkadot/keyring/-/keyring-10.1.14.tgz#431f1d3463da06b97ed9d7f69df5c0209cc6fc86" - integrity sha512-iejbAfGfdyTB0pixWX6HhWXkUKBHLNy7+Z+F4DU8IwpJE48iOsMRJb3qShbk5NERjx1QIjoOpSZYxiCaF6gd+w== - dependencies: - "@babel/runtime" "^7.20.1" - "@polkadot/util" "10.1.14" - "@polkadot/util-crypto" "10.1.14" - -"@polkadot/networks@10.1.14", "@polkadot/networks@^10.1.14": - version "10.1.14" - resolved "https://registry.yarnpkg.com/@polkadot/networks/-/networks-10.1.14.tgz#bb33015903e8220e11377141efa1e0d6c50c87e6" - integrity sha512-lo4Y57yBqiad4Z2zBW0r7Ct/iKXNgsTfazDTbHRkIh3RuX36PNYshaX3p7R0eNRuetV1jJv7jqwc1nAMNI2KwQ== - dependencies: - "@babel/runtime" "^7.20.1" - "@polkadot/util" "10.1.14" - "@substrate/ss58-registry" "^1.35.0" - -"@polkadot/networks@^10.1.12": - version "10.1.12" - resolved "https://registry.yarnpkg.com/@polkadot/networks/-/networks-10.1.12.tgz#19384b39de8369667312977120916b0bbf379688" - integrity sha512-asobCUdRSsnSIZKoqFsaPPtUPvbaNPh3r1XOvNqgrE65wsoNx15AiuqAigW2RKOoqVrUW7zthRJ5Xmk0MKS9GA== - dependencies: - "@babel/runtime" "^7.20.1" - "@polkadot/util" "10.1.12" - "@substrate/ss58-registry" "^1.34.0" - -"@polkadot/react-identicon@^2.9.13": - version "2.9.13" - resolved "https://registry.npmjs.org/@polkadot/react-identicon/-/react-identicon-2.9.13.tgz#67b5c775a282e741bc944fe06dcef05d3fde1e3f" - integrity sha512-tcZyeFO7NDtuiwDRAMc717u2Lqx67GFfrrD+kS9NC+9FCB4b1CSY9OUusBStz0tBh10GwzdOnfoYIkgL15mztg== - dependencies: - "@babel/runtime" "^7.20.1" - "@polkadot/keyring" "^10.1.12" - "@polkadot/ui-settings" "2.9.13" - "@polkadot/ui-shared" "2.9.13" - "@polkadot/util" "^10.1.12" - "@polkadot/util-crypto" "^10.1.12" - color "^3.2.1" - ethereum-blockies-base64 "^1.0.2" - jdenticon "3.1.1" - react-copy-to-clipboard "^5.1.0" - styled-components "^5.3.6" - -"@polkadot/rpc-augment@9.9.4": - version "9.9.4" - resolved "https://registry.yarnpkg.com/@polkadot/rpc-augment/-/rpc-augment-9.9.4.tgz#82a1473143cb9ec1183e01babcfe7ac396ad456b" - integrity sha512-67zGQAhJuXd/CZlwDZTgxNt3xGtsDwLvLvyFrHuNjJNM0KGCyt/OpQHVBlyZ6xfII0WZpccASN6P2MxsGTMnKw== - dependencies: - "@babel/runtime" "^7.20.1" - "@polkadot/rpc-core" "9.9.4" - "@polkadot/types" "9.9.4" - "@polkadot/types-codec" "9.9.4" - "@polkadot/util" "^10.1.14" - -"@polkadot/rpc-core@9.9.4": - version "9.9.4" - resolved "https://registry.yarnpkg.com/@polkadot/rpc-core/-/rpc-core-9.9.4.tgz#30cb94dfb9438ef54f6ab9367bc533fa6934dbc5" - integrity sha512-DxhJcq1GAi+28nLMqhTksNMqTX40bGNhYuyQyy/to39VxizAjx+lyAHAMfzG9lvPnTIi2KzXif2pCdWq3AgJag== - dependencies: - "@babel/runtime" "^7.20.1" - "@polkadot/rpc-augment" "9.9.4" - "@polkadot/rpc-provider" "9.9.4" - "@polkadot/types" "9.9.4" - "@polkadot/util" "^10.1.14" - rxjs "^7.5.7" - -"@polkadot/rpc-provider@9.9.4", "@polkadot/rpc-provider@^9.9.1": - version "9.9.4" - resolved "https://registry.yarnpkg.com/@polkadot/rpc-provider/-/rpc-provider-9.9.4.tgz#dab6d72e83e325dc170e03d0edf5f7bec07c0293" - integrity sha512-aUkPtlYukAOFX3FkUgLw3MNy+T0mCiCX7va3PIts9ggK4vl14NFZHurCZq+5ANvknRU4WG8P5teurH9Rd9oDjQ== - dependencies: - "@babel/runtime" "^7.20.1" - "@polkadot/keyring" "^10.1.14" - "@polkadot/types" "9.9.4" - "@polkadot/types-support" "9.9.4" - "@polkadot/util" "^10.1.14" - "@polkadot/util-crypto" "^10.1.14" - "@polkadot/x-fetch" "^10.1.14" - "@polkadot/x-global" "^10.1.14" - "@polkadot/x-ws" "^10.1.14" - "@substrate/connect" "0.7.17" - eventemitter3 "^4.0.7" - mock-socket "^9.1.5" - nock "^13.2.9" - -"@polkadot/types-augment@9.9.4": - version "9.9.4" - resolved "https://registry.yarnpkg.com/@polkadot/types-augment/-/types-augment-9.9.4.tgz#08a2a89c0b8000ef156a0ed41f5eb7aa55cc1bb1" - integrity sha512-mQNc0kxt3zM6SC+5hJbsg03fxEFpn5nakki+loE2mNsWr1g+rR7LECagAZ4wT2gvdbzWuY/LlRYyDQxe0PwdZg== - dependencies: - "@babel/runtime" "^7.20.1" - "@polkadot/types" "9.9.4" - "@polkadot/types-codec" "9.9.4" - "@polkadot/util" "^10.1.14" - -"@polkadot/types-codec@9.9.4", "@polkadot/types-codec@^9.9.1": - version "9.9.4" - resolved "https://registry.yarnpkg.com/@polkadot/types-codec/-/types-codec-9.9.4.tgz#1219a6b453dab8e53a0d376f13394b02964c7665" - integrity sha512-uSHoQQcj4813c9zNkDDH897K6JB0OznTrH5WeZ1wxpjML7lkuTJ2t/GQE9e4q5Ycl7YePZsvEp2qlc3GwrVm/w== - dependencies: - "@babel/runtime" "^7.20.1" - "@polkadot/util" "^10.1.14" - "@polkadot/x-bigint" "^10.1.14" - -"@polkadot/types-create@9.9.4": - version "9.9.4" - resolved "https://registry.yarnpkg.com/@polkadot/types-create/-/types-create-9.9.4.tgz#d2d3d0e4c3cd4a0a4581dcb418a8f6bec657b986" - integrity sha512-EOxLryRQ4JVRSRnIMXk3Tjry1tyegNuWK8OUj51A1wHrX76DF9chME27bXUP4d7el1pjqPuQ9/l+/928GG386g== - dependencies: - "@babel/runtime" "^7.20.1" - "@polkadot/types-codec" "9.9.4" - "@polkadot/util" "^10.1.14" - -"@polkadot/types-known@9.9.4": - version "9.9.4" - resolved "https://registry.yarnpkg.com/@polkadot/types-known/-/types-known-9.9.4.tgz#d30fa2c5c964b76b748413004758d05eb8f0e8f9" - integrity sha512-BaKXkg3yZLDv31g0CZPJsZDXX01VTjkQ0tmW9U6fmccEq3zHlxbUiXf3aKlwKRJyDWiEOxr4cQ4GT8jj6uEIuA== - dependencies: - "@babel/runtime" "^7.20.1" - "@polkadot/networks" "^10.1.14" - "@polkadot/types" "9.9.4" - "@polkadot/types-codec" "9.9.4" - "@polkadot/types-create" "9.9.4" - "@polkadot/util" "^10.1.14" - -"@polkadot/types-support@9.9.4": - version "9.9.4" - resolved "https://registry.yarnpkg.com/@polkadot/types-support/-/types-support-9.9.4.tgz#3f2eb1097a268bdd280d36fb53b7cdc98a5e238c" - integrity sha512-vjhdD7B5kdTLhm2iO0QAb7fM4D2ojNUVVocOJotC9NULYtoC+PkPvkvFbw7VQ1H3u7yxyZfWloMtBnCsIp5EAA== - dependencies: - "@babel/runtime" "^7.20.1" - "@polkadot/util" "^10.1.14" - -"@polkadot/types@9.9.4", "@polkadot/types@^9.9.1": - version "9.9.4" - resolved "https://registry.yarnpkg.com/@polkadot/types/-/types-9.9.4.tgz#a1b38174f5a9e2aa97612157d12faffd905b126e" - integrity sha512-/LJ029S0AtKzvV9JoQtIIeHRP/Xoq8MZmDfdHUEgThRd+uvtQzFyGmcupe4EzX0p5VAx93DUFQKm8vUdHE39Tw== - dependencies: - "@babel/runtime" "^7.20.1" - "@polkadot/keyring" "^10.1.14" - "@polkadot/types-augment" "9.9.4" - "@polkadot/types-codec" "9.9.4" - "@polkadot/types-create" "9.9.4" - "@polkadot/util" "^10.1.14" - "@polkadot/util-crypto" "^10.1.14" - rxjs "^7.5.7" - -"@polkadot/ui-settings@2.9.13": - version "2.9.13" - resolved "https://registry.npmjs.org/@polkadot/ui-settings/-/ui-settings-2.9.13.tgz#437a9b4d85768051ae48c1920a5341e676481ffb" - integrity sha512-wqhkjhauYF5/rcwrjqSDacaHzzPUo3YzO9DaTxiivvLcdXneRSbPRCJ6FTgzdC7+xTj5cqqvvqBEfBYB0bYxmA== - dependencies: - "@babel/runtime" "^7.20.1" - "@polkadot/networks" "^10.1.12" - "@polkadot/util" "^10.1.12" - eventemitter3 "^4.0.7" - store "^2.0.12" - -"@polkadot/ui-shared@2.9.13": - version "2.9.13" - resolved "https://registry.npmjs.org/@polkadot/ui-shared/-/ui-shared-2.9.13.tgz#2765d09078a5d48b3c24075911fb84301957af01" - integrity sha512-kHxqgpvFF+OfSCbgKyhAomdKkeEx7iwjk28Ilxw0mwqbHgKRxZmel33dKAthiJKndBd5D29QGCWA7Ag1xnwGpA== - dependencies: - "@babel/runtime" "^7.20.1" - color "^3.2.1" - -"@polkadot/util-crypto@10.1.14", "@polkadot/util-crypto@^10.1.12", "@polkadot/util-crypto@^10.1.14": - version "10.1.14" - resolved "https://registry.yarnpkg.com/@polkadot/util-crypto/-/util-crypto-10.1.14.tgz#68ae7dcb8ce53e21c6b87b6244756fc7c023ef71" - integrity sha512-Iq9C0Snv+pScZ9QgJoH7l++x9wdp9vhS3NMLm8ZqlDCNXUUl/3ViafZCfHRILQD9AsLcykE99mNzFDl3u8jZQA== - dependencies: - "@babel/runtime" "^7.20.1" - "@noble/hashes" "1.1.3" - "@noble/secp256k1" "1.7.0" - "@polkadot/networks" "10.1.14" - "@polkadot/util" "10.1.14" - "@polkadot/wasm-crypto" "^6.3.1" - "@polkadot/x-bigint" "10.1.14" - "@polkadot/x-randomvalues" "10.1.14" - "@scure/base" "1.1.1" - ed2curve "^0.3.0" - tweetnacl "^1.0.3" - -"@polkadot/util@10.1.12": - version "10.1.12" - resolved "https://registry.npmjs.org/@polkadot/util/-/util-10.1.12.tgz#9652de84c87b0b5b9b50767cd4d93f5f2d70a074" - integrity sha512-bOz1WqDFzIgkTpT6oRhAdXKqETr2GffZdRlYqyOvP1ATAEa48/sRgzIvg7WTiI68D8By3fJmKuA+ggX3YrNF/w== - dependencies: - "@babel/runtime" "^7.20.1" - "@polkadot/x-bigint" "10.1.12" - "@polkadot/x-global" "10.1.12" - "@polkadot/x-textdecoder" "10.1.12" - "@polkadot/x-textencoder" "10.1.12" - "@types/bn.js" "^5.1.1" - bn.js "^5.2.1" - -"@polkadot/util@10.1.14", "@polkadot/util@^10.1.12", "@polkadot/util@^10.1.13", "@polkadot/util@^10.1.14": - version "10.1.14" - resolved "https://registry.yarnpkg.com/@polkadot/util/-/util-10.1.14.tgz#f69630acebb1ce18e25e9b2ad626d4315ca268dd" - integrity sha512-DX8IUd3j+S4HJBs73gz5d7Z592aW5vn/aD7hzFUlBduQIYBy+L1KIoGchpD6hSSOs5HSy7owePmBlp1lPjUZBg== - dependencies: - "@babel/runtime" "^7.20.1" - "@polkadot/x-bigint" "10.1.14" - "@polkadot/x-global" "10.1.14" - "@polkadot/x-textdecoder" "10.1.14" - "@polkadot/x-textencoder" "10.1.14" - "@types/bn.js" "^5.1.1" - bn.js "^5.2.1" - -"@polkadot/wasm-bridge@6.3.1": - version "6.3.1" - resolved "https://registry.npmjs.org/@polkadot/wasm-bridge/-/wasm-bridge-6.3.1.tgz#439fa78e80947a7cb695443e1f64b25c30bb1487" - integrity sha512-1TYkHsb9AEFhU9uZj3biEnN2yKQNzdrwSjiTvfCYnt97pnEkKsZI6cku+YPZQv5w/x9CQa5Yua9e2DVVZSivGA== - dependencies: - "@babel/runtime" "^7.18.9" - -"@polkadot/wasm-crypto-asmjs@6.3.1": - version "6.3.1" - resolved "https://registry.npmjs.org/@polkadot/wasm-crypto-asmjs/-/wasm-crypto-asmjs-6.3.1.tgz#e8f469c9cf4a7709c8131a96f857291953f3e30a" - integrity sha512-zbombRfA5v/mUWQQhgg2YwaxhRmxRIrvskw65x+lruax3b6xPBFDs7yplopiJU3r8h2pTgQvX/DUksvqz2TCRQ== - dependencies: - "@babel/runtime" "^7.18.9" - -"@polkadot/wasm-crypto-init@6.3.1": - version "6.3.1" - resolved "https://registry.npmjs.org/@polkadot/wasm-crypto-init/-/wasm-crypto-init-6.3.1.tgz#b590220c53c94b9a54d5dc236d0cbe943db76706" - integrity sha512-9yaUBcu+snwjJLmPPGl3cyGRQ1afyFGm16qzTM0sgG/ZCfUlK4uk8KWZe+sBUKgoxb2oXY7Y4WklKgQI1YBdfw== - dependencies: - "@babel/runtime" "^7.18.9" - "@polkadot/wasm-bridge" "6.3.1" - "@polkadot/wasm-crypto-asmjs" "6.3.1" - "@polkadot/wasm-crypto-wasm" "6.3.1" - -"@polkadot/wasm-crypto-wasm@6.3.1": - version "6.3.1" - resolved "https://registry.npmjs.org/@polkadot/wasm-crypto-wasm/-/wasm-crypto-wasm-6.3.1.tgz#67f720e7f9694fef096abe9d60abbac02e032383" - integrity sha512-idSlzKGVzCfeCMRHsacRvqwojSaTadFxL/Dbls4z1thvfa3U9Ku0d2qVtlwg7Hj+tYWDiuP8Kygs+6bQwfs0XA== - dependencies: - "@babel/runtime" "^7.18.9" - "@polkadot/wasm-util" "6.3.1" - -"@polkadot/wasm-crypto@^6.3.1": - version "6.3.1" - resolved "https://registry.npmjs.org/@polkadot/wasm-crypto/-/wasm-crypto-6.3.1.tgz#63f5798aca2b2ff0696f190e6862d9781d8f280c" - integrity sha512-OO8h0qeVkqp4xYZaRVl4iuWOEtq282pNBHDKb6SOJuI2g59eWGcKh4EQU9Me2VP6qzojIqptrkrVt7KQXC68gA== - dependencies: - "@babel/runtime" "^7.18.9" - "@polkadot/wasm-bridge" "6.3.1" - "@polkadot/wasm-crypto-asmjs" "6.3.1" - "@polkadot/wasm-crypto-init" "6.3.1" - "@polkadot/wasm-crypto-wasm" "6.3.1" - "@polkadot/wasm-util" "6.3.1" - -"@polkadot/wasm-util@6.3.1": - version "6.3.1" - resolved "https://registry.npmjs.org/@polkadot/wasm-util/-/wasm-util-6.3.1.tgz#439ebb68a436317af388ed6438b8f879df3afcda" - integrity sha512-12oAv5J7Yoc9m6jixrSaQCxpOkWOyzHx3DMC8qmLjRiwdBWxqLmImOVRVnFsbaxqSbhBIHRuJphVxWE+GZETDg== - dependencies: - "@babel/runtime" "^7.18.9" - -"@polkadot/x-bigint@10.1.12": - version "10.1.12" - resolved "https://registry.npmjs.org/@polkadot/x-bigint/-/x-bigint-10.1.12.tgz#0aa4412851d2b8a2d18511549488ddf149fe4c7b" - integrity sha512-n9cRXhdPvA9qO9/dgb32Ej/5t4mI4KnBYoPqlAcGviOnn7lc9yEPlYSMfgt+4p7F2bX8o6nbmbvBXqZL457Yhw== - dependencies: - "@babel/runtime" "^7.20.1" - "@polkadot/x-global" "10.1.12" - -"@polkadot/x-bigint@10.1.14", "@polkadot/x-bigint@^10.1.14": - version "10.1.14" - resolved "https://registry.yarnpkg.com/@polkadot/x-bigint/-/x-bigint-10.1.14.tgz#5ae56c10f3ac8c29fcd2cc3f5236ac4ff7e52af0" - integrity sha512-HgrofhI+WM699ozJ9zFZcPUApB2jqwCEOMUgM1jv2WNxF0ILKNDpH08dB8OBy2SKfnKoSgmXwWtxWl1u+mq10A== - dependencies: - "@babel/runtime" "^7.20.1" - "@polkadot/x-global" "10.1.14" - -"@polkadot/x-fetch@^10.1.14": - version "10.1.14" - resolved "https://registry.yarnpkg.com/@polkadot/x-fetch/-/x-fetch-10.1.14.tgz#e89e0eb28b7275e674281cae68aff71835e1df33" - integrity sha512-07H9unwv0tT5RQRkj1WE67ug6x6RlUfGN/mJWSKqf0JjsfQlPMKDC9yZW1oUSsasBUyIf0qbspuVSyhZu+0cpg== - dependencies: - "@babel/runtime" "^7.20.1" - "@polkadot/x-global" "10.1.14" - "@types/node-fetch" "^2.6.2" - node-fetch "^3.3.0" - -"@polkadot/x-global@10.1.12": - version "10.1.12" - resolved "https://registry.npmjs.org/@polkadot/x-global/-/x-global-10.1.12.tgz#4af9eeca9f3d371fbe4f348681a46bb1dc82ed35" - integrity sha512-APFCIVoyaB9rhgcg2j5ayW0WVTSDO4yUriyrOPgX4A4ZJ6DFV+w30h9uWtfKzNzAV6dDs6pDNlB0K4Mpi5CmcA== - dependencies: - "@babel/runtime" "^7.20.1" - -"@polkadot/x-global@10.1.14", "@polkadot/x-global@^10.1.14": - version "10.1.14" - resolved "https://registry.yarnpkg.com/@polkadot/x-global/-/x-global-10.1.14.tgz#d7d788f00eddd05e72c8ccf44034e2292e312854" - integrity sha512-ye3Yx2bfIoHf5t78rbDad587J16JanrcfpGSWoknWOZ7wmatj/CJKWhJ/VKMPfJGEJm2LidH1B0W8QDfrMEmTA== - dependencies: - "@babel/runtime" "^7.20.1" - -"@polkadot/x-randomvalues@10.1.14": - version "10.1.14" - resolved "https://registry.yarnpkg.com/@polkadot/x-randomvalues/-/x-randomvalues-10.1.14.tgz#d34e74c983d7740cc30ac4c594fe237a1f60c0ca" - integrity sha512-mrZho4qogLZmox7wuP1XF03HTZ4CwAjzV7RvKmwH8ToNCR6E4NRo2btgG67Z0K+bUOQRbXWL2hQazusa2p2N6w== - dependencies: - "@babel/runtime" "^7.20.1" - "@polkadot/x-global" "10.1.14" - -"@polkadot/x-textdecoder@10.1.12": - version "10.1.12" - resolved "https://registry.npmjs.org/@polkadot/x-textdecoder/-/x-textdecoder-10.1.12.tgz#1873750006c7f075cb928f3bb1f8a173c2d7ef54" - integrity sha512-YKEr3QiTu8zxosVx9WWFhMmFw4hBmAKWkgd7dhpMU+wIaUlSBriV/7vL+HnvJMIKf1jz3iAZ7i0oDGfvj1cZ6w== - dependencies: - "@babel/runtime" "^7.20.1" - "@polkadot/x-global" "10.1.12" - -"@polkadot/x-textdecoder@10.1.14": - version "10.1.14" - resolved "https://registry.yarnpkg.com/@polkadot/x-textdecoder/-/x-textdecoder-10.1.14.tgz#06aee763c4023b2fa17302e9e403afa262c78423" - integrity sha512-qwbeR8v6a5Z9MdbjzcY5gFiRWbp8bBVoDEf1Dd+yH9/UAyFXodlMKs3irDdVhGVPCbZWQTVDEZRUgEqRxwKC7w== - dependencies: - "@babel/runtime" "^7.20.1" - "@polkadot/x-global" "10.1.14" - -"@polkadot/x-textencoder@10.1.12": - version "10.1.12" - resolved "https://registry.npmjs.org/@polkadot/x-textencoder/-/x-textencoder-10.1.12.tgz#5db7bcb9682717c8b8c4c70fed8c5d01cbe61d8d" - integrity sha512-WgHAUhiepWBAcOMOAJnBl2mZNu5KPmTndg4f1Z1CwNdg/AhAhJL/yh98f5KH1aajNkC+5xutlOzdmEySg+e21g== - dependencies: - "@babel/runtime" "^7.20.1" - "@polkadot/x-global" "10.1.12" - -"@polkadot/x-textencoder@10.1.14": - version "10.1.14" - resolved "https://registry.yarnpkg.com/@polkadot/x-textencoder/-/x-textencoder-10.1.14.tgz#595fc47f443471ba0e850c936893bd28e89903d8" - integrity sha512-MC30rtQiFVgQDSP8wO5wa1so5tW3T7qs/DCT018A4zgjiK+uFdIGOerAgoxcNw3yC6IGnPIL5lsRO/1C9N60zA== - dependencies: - "@babel/runtime" "^7.20.1" - "@polkadot/x-global" "10.1.14" - -"@polkadot/x-ws@^10.1.14": - version "10.1.14" - resolved "https://registry.yarnpkg.com/@polkadot/x-ws/-/x-ws-10.1.14.tgz#f96fa396283cdb345555da63da7a5cf0bd19b8d1" - integrity sha512-xgyMYR34sHxKCtQUJ2btFAyN3fK1OqCqvAyRYmU52801aguLstRhVPAZxXJp3Dahs91FX/h/ZZzmwXD01tJtyA== - dependencies: - "@babel/runtime" "^7.20.1" - "@polkadot/x-global" "10.1.14" - "@types/websocket" "^1.0.5" - websocket "^1.0.34" - -"@remix-run/router@1.0.3": - version "1.0.3" - resolved "https://registry.npmjs.org/@remix-run/router/-/router-1.0.3.tgz#953b88c20ea00d0eddaffdc1b115c08474aa295d" - integrity sha512-ceuyTSs7PZ/tQqi19YZNBc5X7kj1f8p+4DIyrcIYFY9h+hd1OKm4RqtiWldR9eGEvIiJfsqwM4BsuCtRIuEw6Q== - -"@rollup/plugin-babel@^5.2.0": - version "5.3.1" - resolved "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz#04bc0608f4aa4b2e4b1aebf284344d0f68fda283" - integrity sha512-WFfdLWU/xVWKeRQnKmIAQULUI7Il0gZnBIH/ZFO069wYIfPu+8zrfp/KMW0atmELoRDq8FbiP3VCss9MhCut7Q== - dependencies: - "@babel/helper-module-imports" "^7.10.4" - "@rollup/pluginutils" "^3.1.0" - -"@rollup/plugin-node-resolve@^11.2.1": - version "11.2.1" - resolved "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-11.2.1.tgz#82aa59397a29cd4e13248b106e6a4a1880362a60" - integrity sha512-yc2n43jcqVyGE2sqV5/YCmocy9ArjVAP/BeXyTtADTBBX6V0e5UMqwO8CdQ0kzjb6zu5P1qMzsScCMRvE9OlVg== - dependencies: - "@rollup/pluginutils" "^3.1.0" - "@types/resolve" "1.17.1" - builtin-modules "^3.1.0" - deepmerge "^4.2.2" - is-module "^1.0.0" - resolve "^1.19.0" - -"@rollup/plugin-replace@^2.4.1": - version "2.4.2" - resolved "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-2.4.2.tgz#a2d539314fbc77c244858faa523012825068510a" - integrity sha512-IGcu+cydlUMZ5En85jxHH4qj2hta/11BHq95iHEyb2sbgiN0eCdzvUcHw5gt9pBL5lTi4JDYJ1acCoMGpTvEZg== - dependencies: - "@rollup/pluginutils" "^3.1.0" - magic-string "^0.25.7" - -"@rollup/pluginutils@^3.1.0": - version "3.1.0" - resolved "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz#706b4524ee6dc8b103b3c995533e5ad680c02b9b" - integrity sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg== - dependencies: - "@types/estree" "0.0.39" - estree-walker "^1.0.1" - picomatch "^2.2.2" - -"@rossbulat/polkadot-dashboard-ui@0.1.0-alpha.23": - version "0.1.0-alpha.23" - resolved "https://registry.npmjs.org/@rossbulat/polkadot-dashboard-ui/-/polkadot-dashboard-ui-0.1.0-alpha.23.tgz#510f5ba3abcb79ca5d0f092ac1bd8f84910f16d2" - integrity sha512-pMdn+qap+qCJFJT2am03qKCe05Re6i07vnQB85EhIYnDfE2IagYf50kGiwfKNiZbK0L6GdE7TjfpfmeUWmOKqA== - -"@rushstack/eslint-patch@^1.1.0": - version "1.2.0" - resolved "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.2.0.tgz#8be36a1f66f3265389e90b5f9c9962146758f728" - integrity sha512-sXo/qW2/pAcmT43VoRKOJbDOfV3cYpq3szSVfIThQXNt+E4DfKj361vaAt3c88U5tPUxzEswam7GW48PJqtKAg== - -"@scure/base@1.1.1": - version "1.1.1" - resolved "https://registry.npmjs.org/@scure/base/-/base-1.1.1.tgz#ebb651ee52ff84f420097055f4bf46cfba403938" - integrity sha512-ZxOhsSyxYwLJj3pLZCefNitxsj093tb2vq90mp2txoYeBqbcjDjqFhyM8eUjq/uFm6zJ+mUuqxlS2FkuSY1MTA== - -"@sinclair/typebox@^0.24.1": - version "0.24.51" - resolved "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.51.tgz#645f33fe4e02defe26f2f5c0410e1c094eac7f5f" - integrity sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA== - -"@sinonjs/commons@^1.7.0": - version "1.8.5" - resolved "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.5.tgz#e280c94c95f206dcfd5aca00a43f2156b758c764" - integrity sha512-rTpCA0wG1wUxglBSFdMMY0oTrKYvgf4fNgv/sXbfCVAdf+FnPBdKJR/7XbpTCwbCrvCbdPYnlWaUUYz4V2fPDA== - dependencies: - type-detect "4.0.8" - -"@sinonjs/fake-timers@^8.0.1": - version "8.1.0" - resolved "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-8.1.0.tgz#3fdc2b6cb58935b21bfb8d1625eb1300484316e7" - integrity sha512-OAPJUAtgeINhh/TAlUID4QTs53Njm7xzddaVlEs/SXwgtiD1tW22zAB/W1wdqfrpmikgaWQ9Fw6Ws+hsiRm5Vg== - dependencies: - "@sinonjs/commons" "^1.7.0" - -"@substrate/connect-extension-protocol@^1.0.1": - version "1.0.1" - resolved "https://registry.npmjs.org/@substrate/connect-extension-protocol/-/connect-extension-protocol-1.0.1.tgz#fa5738039586c648013caa6a0c95c43265dbe77d" - integrity sha512-161JhCC1csjH3GE5mPLEd7HbWtwNSPJBg3p1Ksz9SFlTzj/bgEwudiRN2y5i0MoLGCIJRYKyKGMxVnd29PzNjg== - -"@substrate/connect@0.7.17": - version "0.7.17" - resolved "https://registry.npmjs.org/@substrate/connect/-/connect-0.7.17.tgz#b76ce23d24255e89028db81b3cb280c7f86db72e" - integrity sha512-s0XBmGpUCFWZFa+TS0TEvOKtWjJP2uT4xKmvzApH8INB5xbz79wqWFX6WWh3AlK/X1P0Smt+RVEH7HQiLJAYAw== - dependencies: - "@substrate/connect-extension-protocol" "^1.0.1" - "@substrate/smoldot-light" "0.7.7" - eventemitter3 "^4.0.7" - -"@substrate/smoldot-light@0.7.7": - version "0.7.7" - resolved "https://registry.npmjs.org/@substrate/smoldot-light/-/smoldot-light-0.7.7.tgz#ee5f89bb25af64d2014d97548b959b7da4c67f08" - integrity sha512-ksxeAed6dIUtYSl0f8ehgWQjwXnpDGTIJt+WVRIGt3OObZkA96ZdBWx0xP7GrXZtj37u4n/Y1z7TyTm4bwQvrw== - dependencies: - pako "^2.0.4" - ws "^8.8.1" - -"@substrate/ss58-registry@^1.34.0": - version "1.34.0" - resolved "https://registry.npmjs.org/@substrate/ss58-registry/-/ss58-registry-1.34.0.tgz#b6faed02343da7a8956444f5db23bc7246dd5fb5" - integrity sha512-8Df5usnWvjnw/WRAmKOqHXRPPRfiCd1kIN8ttH4YmBrRTERjVInsdu0xvLdbyUYKyvgK6zKhHWQfYohXqllHhg== - -"@substrate/ss58-registry@^1.35.0": - version "1.35.0" - resolved "https://registry.yarnpkg.com/@substrate/ss58-registry/-/ss58-registry-1.35.0.tgz#8afc88ddc15cc0ae3ae1dfdb8e7d3fbae646c3c3" - integrity sha512-cIY3J7RlT4mfPNFwd2mv1q9vTp/shImw2gN2y2outMhOcagH/HG+W8/JohpifjxPC/1pbQ0Z8nxfL5Td3EchcA== - -"@surma/rollup-plugin-off-main-thread@^2.2.3": - version "2.2.3" - resolved "https://registry.npmjs.org/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-2.2.3.tgz#ee34985952ca21558ab0d952f00298ad2190c053" - integrity sha512-lR8q/9W7hZpMWweNiAKU7NQerBnzQQLvi8qnTDU/fxItPhtZVMbPV3lbCwjhIlNBe9Bbr5V+KHshvWmVSG9cxQ== - dependencies: - ejs "^3.1.6" - json5 "^2.2.0" - magic-string "^0.25.0" - string.prototype.matchall "^4.0.6" - -"@svgr/babel-plugin-add-jsx-attribute@^5.4.0": - version "5.4.0" - resolved "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-5.4.0.tgz#81ef61947bb268eb9d50523446f9c638fb355906" - integrity sha512-ZFf2gs/8/6B8PnSofI0inYXr2SDNTDScPXhN7k5EqD4aZ3gi6u+rbmZHVB8IM3wDyx8ntKACZbtXSm7oZGRqVg== - -"@svgr/babel-plugin-remove-jsx-attribute@^5.4.0": - version "5.4.0" - resolved "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-5.4.0.tgz#6b2c770c95c874654fd5e1d5ef475b78a0a962ef" - integrity sha512-yaS4o2PgUtwLFGTKbsiAy6D0o3ugcUhWK0Z45umJ66EPWunAz9fuFw2gJuje6wqQvQWOTJvIahUwndOXb7QCPg== - -"@svgr/babel-plugin-remove-jsx-empty-expression@^5.0.1": - version "5.0.1" - resolved "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-5.0.1.tgz#25621a8915ed7ad70da6cea3d0a6dbc2ea933efd" - integrity sha512-LA72+88A11ND/yFIMzyuLRSMJ+tRKeYKeQ+mR3DcAZ5I4h5CPWN9AHyUzJbWSYp/u2u0xhmgOe0+E41+GjEueA== - -"@svgr/babel-plugin-replace-jsx-attribute-value@^5.0.1": - version "5.0.1" - resolved "https://registry.npmjs.org/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-5.0.1.tgz#0b221fc57f9fcd10e91fe219e2cd0dd03145a897" - integrity sha512-PoiE6ZD2Eiy5mK+fjHqwGOS+IXX0wq/YDtNyIgOrc6ejFnxN4b13pRpiIPbtPwHEc+NT2KCjteAcq33/F1Y9KQ== - -"@svgr/babel-plugin-svg-dynamic-title@^5.4.0": - version "5.4.0" - resolved "https://registry.npmjs.org/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-5.4.0.tgz#139b546dd0c3186b6e5db4fefc26cb0baea729d7" - integrity sha512-zSOZH8PdZOpuG1ZVx/cLVePB2ibo3WPpqo7gFIjLV9a0QsuQAzJiwwqmuEdTaW2pegyBE17Uu15mOgOcgabQZg== - -"@svgr/babel-plugin-svg-em-dimensions@^5.4.0": - version "5.4.0" - resolved "https://registry.npmjs.org/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-5.4.0.tgz#6543f69526632a133ce5cabab965deeaea2234a0" - integrity sha512-cPzDbDA5oT/sPXDCUYoVXEmm3VIoAWAPT6mSPTJNbQaBNUuEKVKyGH93oDY4e42PYHRW67N5alJx/eEol20abw== - -"@svgr/babel-plugin-transform-react-native-svg@^5.4.0": - version "5.4.0" - resolved "https://registry.npmjs.org/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-5.4.0.tgz#00bf9a7a73f1cad3948cdab1f8dfb774750f8c80" - integrity sha512-3eYP/SaopZ41GHwXma7Rmxcv9uRslRDTY1estspeB1w1ueZWd/tPlMfEOoccYpEMZU3jD4OU7YitnXcF5hLW2Q== - -"@svgr/babel-plugin-transform-svg-component@^5.5.0": - version "5.5.0" - resolved "https://registry.npmjs.org/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-5.5.0.tgz#583a5e2a193e214da2f3afeb0b9e8d3250126b4a" - integrity sha512-q4jSH1UUvbrsOtlo/tKcgSeiCHRSBdXoIoqX1pgcKK/aU3JD27wmMKwGtpB8qRYUYoyXvfGxUVKchLuR5pB3rQ== - -"@svgr/babel-preset@^5.5.0": - version "5.5.0" - resolved "https://registry.npmjs.org/@svgr/babel-preset/-/babel-preset-5.5.0.tgz#8af54f3e0a8add7b1e2b0fcd5a882c55393df327" - integrity sha512-4FiXBjvQ+z2j7yASeGPEi8VD/5rrGQk4Xrq3EdJmoZgz/tpqChpo5hgXDvmEauwtvOc52q8ghhZK4Oy7qph4ig== - dependencies: - "@svgr/babel-plugin-add-jsx-attribute" "^5.4.0" - "@svgr/babel-plugin-remove-jsx-attribute" "^5.4.0" - "@svgr/babel-plugin-remove-jsx-empty-expression" "^5.0.1" - "@svgr/babel-plugin-replace-jsx-attribute-value" "^5.0.1" - "@svgr/babel-plugin-svg-dynamic-title" "^5.4.0" - "@svgr/babel-plugin-svg-em-dimensions" "^5.4.0" - "@svgr/babel-plugin-transform-react-native-svg" "^5.4.0" - "@svgr/babel-plugin-transform-svg-component" "^5.5.0" - -"@svgr/core@^5.5.0": - version "5.5.0" - resolved "https://registry.npmjs.org/@svgr/core/-/core-5.5.0.tgz#82e826b8715d71083120fe8f2492ec7d7874a579" - integrity sha512-q52VOcsJPvV3jO1wkPtzTuKlvX7Y3xIcWRpCMtBF3MrteZJtBfQw/+u0B1BHy5ColpQc1/YVTrPEtSYIMNZlrQ== - dependencies: - "@svgr/plugin-jsx" "^5.5.0" - camelcase "^6.2.0" - cosmiconfig "^7.0.0" - -"@svgr/hast-util-to-babel-ast@^5.5.0": - version "5.5.0" - resolved "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-5.5.0.tgz#5ee52a9c2533f73e63f8f22b779f93cd432a5461" - integrity sha512-cAaR/CAiZRB8GP32N+1jocovUtvlj0+e65TB50/6Lcime+EA49m/8l+P2ko+XPJ4dw3xaPS3jOL4F2X4KWxoeQ== - dependencies: - "@babel/types" "^7.12.6" - -"@svgr/plugin-jsx@^5.5.0": - version "5.5.0" - resolved "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-5.5.0.tgz#1aa8cd798a1db7173ac043466d7b52236b369000" - integrity sha512-V/wVh33j12hGh05IDg8GpIUXbjAPnTdPTKuP4VNLggnwaHMPNQNae2pRnyTAILWCQdz5GyMqtO488g7CKM8CBA== - dependencies: - "@babel/core" "^7.12.3" - "@svgr/babel-preset" "^5.5.0" - "@svgr/hast-util-to-babel-ast" "^5.5.0" - svg-parser "^2.0.2" - -"@svgr/plugin-svgo@^5.5.0": - version "5.5.0" - resolved "https://registry.npmjs.org/@svgr/plugin-svgo/-/plugin-svgo-5.5.0.tgz#02da55d85320549324e201c7b2e53bf431fcc246" - integrity sha512-r5swKk46GuQl4RrVejVwpeeJaydoxkdwkM1mBKOgJLBUJPGaLci6ylg/IjhrRsREKDkr4kbMWdgOtbXEh0fyLQ== - dependencies: - cosmiconfig "^7.0.0" - deepmerge "^4.2.2" - svgo "^1.2.2" - -"@svgr/webpack@^5.5.0": - version "5.5.0" - resolved "https://registry.npmjs.org/@svgr/webpack/-/webpack-5.5.0.tgz#aae858ee579f5fa8ce6c3166ef56c6a1b381b640" - integrity sha512-DOBOK255wfQxguUta2INKkzPj6AIS6iafZYiYmHn6W3pHlycSRRlvWKCfLDG10fXfLWqE3DJHgRUOyJYmARa7g== - dependencies: - "@babel/core" "^7.12.3" - "@babel/plugin-transform-react-constant-elements" "^7.12.1" - "@babel/preset-env" "^7.12.1" - "@babel/preset-react" "^7.12.5" - "@svgr/core" "^5.5.0" - "@svgr/plugin-jsx" "^5.5.0" - "@svgr/plugin-svgo" "^5.5.0" - loader-utils "^2.0.0" - -"@testing-library/dom@^8.5.0": - version "8.19.0" - resolved "https://registry.npmjs.org/@testing-library/dom/-/dom-8.19.0.tgz#bd3f83c217ebac16694329e413d9ad5fdcfd785f" - integrity sha512-6YWYPPpxG3e/xOo6HIWwB/58HukkwIVTOaZ0VwdMVjhRUX/01E4FtQbck9GazOOj7MXHc5RBzMrU86iBJHbI+A== - dependencies: - "@babel/code-frame" "^7.10.4" - "@babel/runtime" "^7.12.5" - "@types/aria-query" "^4.2.0" - aria-query "^5.0.0" - chalk "^4.1.0" - dom-accessibility-api "^0.5.9" - lz-string "^1.4.4" - pretty-format "^27.0.2" - -"@testing-library/jest-dom@^5.16.5": - version "5.16.5" - resolved "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-5.16.5.tgz#3912846af19a29b2dbf32a6ae9c31ef52580074e" - integrity sha512-N5ixQ2qKpi5OLYfwQmUb/5mSV9LneAcaUfp32pn4yCnpb8r/Yz0pXFPck21dIicKmi+ta5WRAknkZCfA8refMA== - dependencies: - "@adobe/css-tools" "^4.0.1" - "@babel/runtime" "^7.9.2" - "@types/testing-library__jest-dom" "^5.9.1" - aria-query "^5.0.0" - chalk "^3.0.0" - css.escape "^1.5.1" - dom-accessibility-api "^0.5.6" - lodash "^4.17.15" - redent "^3.0.0" - -"@testing-library/react@^13.4.0": - version "13.4.0" - resolved "https://registry.npmjs.org/@testing-library/react/-/react-13.4.0.tgz#6a31e3bf5951615593ad984e96b9e5e2d9380966" - integrity sha512-sXOGON+WNTh3MLE9rve97ftaZukN3oNf2KjDy7YTx6hcTO2uuLHuCGynMDhFwGw/jYf4OJ2Qk0i4i79qMNNkyw== - dependencies: - "@babel/runtime" "^7.12.5" - "@testing-library/dom" "^8.5.0" - "@types/react-dom" "^18.0.0" - -"@testing-library/user-event@^14.4.3": - version "14.4.3" - resolved "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.4.3.tgz#af975e367743fa91989cd666666aec31a8f50591" - integrity sha512-kCUc5MEwaEMakkO5x7aoD+DLi02ehmEM2QCGWvNqAS1dV/fAvORWEjnjsEIvml59M7Y5kCkWN6fCCyPOe8OL6Q== - -"@tootallnate/once@1": - version "1.1.2" - resolved "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" - integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw== - -"@trysound/sax@0.2.0": - version "0.2.0" - resolved "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz#cccaab758af56761eb7bf37af6f03f326dd798ad" - integrity sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA== - -"@types/aria-query@^4.2.0": - version "4.2.2" - resolved "https://registry.npmjs.org/@types/aria-query/-/aria-query-4.2.2.tgz#ed4e0ad92306a704f9fb132a0cfcf77486dbe2bc" - integrity sha512-HnYpAE1Y6kRyKM/XkEuiRQhTHvkzMBurTHnpFLYLBGPIylZNPs9jJcuOOYWxPLJCSEtmZT0Y8rHDokKN7rRTig== - -"@types/babel__core@^7.0.0", "@types/babel__core@^7.1.14": - version "7.1.20" - resolved "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.20.tgz#e168cdd612c92a2d335029ed62ac94c95b362359" - integrity sha512-PVb6Bg2QuscZ30FvOU7z4guG6c926D9YRvOxEaelzndpMsvP+YM74Q/dAFASpg2l6+XLalxSGxcq/lrgYWZtyQ== - dependencies: - "@babel/parser" "^7.1.0" - "@babel/types" "^7.0.0" - "@types/babel__generator" "*" - "@types/babel__template" "*" - "@types/babel__traverse" "*" - -"@types/babel__generator@*": - version "7.6.4" - resolved "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.4.tgz#1f20ce4c5b1990b37900b63f050182d28c2439b7" - integrity sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg== - dependencies: - "@babel/types" "^7.0.0" - -"@types/babel__template@*": - version "7.4.1" - resolved "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz#3d1a48fd9d6c0edfd56f2ff578daed48f36c8969" - integrity sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g== - dependencies: - "@babel/parser" "^7.1.0" - "@babel/types" "^7.0.0" - -"@types/babel__traverse@*", "@types/babel__traverse@^7.0.4", "@types/babel__traverse@^7.0.6": - version "7.18.2" - resolved "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.18.2.tgz#235bf339d17185bdec25e024ca19cce257cc7309" - integrity sha512-FcFaxOr2V5KZCviw1TnutEMVUVsGt4D2hP1TAfXZAMKuHYW3xQhe3jTxNPWutgCJ3/X1c5yX8ZoGVEItxKbwBg== - dependencies: - "@babel/types" "^7.3.0" - -"@types/bn.js@^5.1.1": - version "5.1.1" - resolved "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.1.1.tgz#b51e1b55920a4ca26e9285ff79936bbdec910682" - integrity sha512-qNrYbZqMx0uJAfKnKclPh+dTwK33KfLHYqtyODwd5HnXOjnkhc4qgn3BrK6RWyGZm5+sIFE7Q7Vz6QQtJB7w7g== - dependencies: - "@types/node" "*" - -"@types/body-parser@*": - version "1.19.2" - resolved "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz#aea2059e28b7658639081347ac4fab3de166e6f0" - integrity sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g== - dependencies: - "@types/connect" "*" - "@types/node" "*" - -"@types/bonjour@^3.5.9": - version "3.5.10" - resolved "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.10.tgz#0f6aadfe00ea414edc86f5d106357cda9701e275" - integrity sha512-p7ienRMiS41Nu2/igbJxxLDWrSZ0WxM8UQgCeO9KhoVF7cOVFkrKsiDr1EsJIla8vV3oEEjGcz11jc5yimhzZw== - dependencies: - "@types/node" "*" - -"@types/connect-history-api-fallback@^1.3.5": - version "1.3.5" - resolved "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.3.5.tgz#d1f7a8a09d0ed5a57aee5ae9c18ab9b803205dae" - integrity sha512-h8QJa8xSb1WD4fpKBDcATDNGXghFj6/3GRWG6dhmRcu0RX1Ubasur2Uvx5aeEwlf0MwblEC2bMzzMQntxnw/Cw== - dependencies: - "@types/express-serve-static-core" "*" - "@types/node" "*" - -"@types/connect@*": - version "3.4.35" - resolved "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz#5fcf6ae445e4021d1fc2219a4873cc73a3bb2ad1" - integrity sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ== - dependencies: - "@types/node" "*" - -"@types/eslint-scope@^3.7.3": - version "3.7.4" - resolved "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.4.tgz#37fc1223f0786c39627068a12e94d6e6fc61de16" - integrity sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA== - dependencies: - "@types/eslint" "*" - "@types/estree" "*" - -"@types/eslint@*", "@types/eslint@^7.29.0 || ^8.4.1": - version "8.4.10" - resolved "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.10.tgz#19731b9685c19ed1552da7052b6f668ed7eb64bb" - integrity sha512-Sl/HOqN8NKPmhWo2VBEPm0nvHnu2LL3v9vKo8MEq0EtbJ4eVzGPl41VNPvn5E1i5poMk4/XD8UriLHpJvEP/Nw== - dependencies: - "@types/estree" "*" - "@types/json-schema" "*" - -"@types/estree@*": - version "1.0.0" - resolved "https://registry.npmjs.org/@types/estree/-/estree-1.0.0.tgz#5fb2e536c1ae9bf35366eed879e827fa59ca41c2" - integrity sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ== - -"@types/estree@0.0.39": - version "0.0.39" - resolved "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f" - integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw== - -"@types/estree@^0.0.51": - version "0.0.51" - resolved "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz#cfd70924a25a3fd32b218e5e420e6897e1ac4f40" - integrity sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ== - -"@types/express-serve-static-core@*", "@types/express-serve-static-core@^4.17.18": - version "4.17.31" - resolved "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.31.tgz#a1139efeab4e7323834bb0226e62ac019f474b2f" - integrity sha512-DxMhY+NAsTwMMFHBTtJFNp5qiHKJ7TeqOo23zVEM9alT1Ml27Q3xcTH0xwxn7Q0BbMcVEJOs/7aQtUWupUQN3Q== - dependencies: - "@types/node" "*" - "@types/qs" "*" - "@types/range-parser" "*" - -"@types/express@*", "@types/express@^4.17.13": - version "4.17.14" - resolved "https://registry.npmjs.org/@types/express/-/express-4.17.14.tgz#143ea0557249bc1b3b54f15db4c81c3d4eb3569c" - integrity sha512-TEbt+vaPFQ+xpxFLFssxUDXj5cWCxZJjIcB7Yg0k0GMHGtgtQgpvx/MUQUeAkNbA9AAGrwkAsoeItdTgS7FMyg== - dependencies: - "@types/body-parser" "*" - "@types/express-serve-static-core" "^4.17.18" - "@types/qs" "*" - "@types/serve-static" "*" - -"@types/graceful-fs@^4.1.2": - version "4.1.5" - resolved "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz#21ffba0d98da4350db64891f92a9e5db3cdb4e15" - integrity sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw== - dependencies: - "@types/node" "*" - -"@types/hoist-non-react-statics@*": - version "3.3.1" - resolved "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz#1124aafe5118cb591977aeb1ceaaed1070eb039f" - integrity sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA== - dependencies: - "@types/react" "*" - hoist-non-react-statics "^3.3.0" - -"@types/html-minifier-terser@^6.0.0": - version "6.1.0" - resolved "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz#4fc33a00c1d0c16987b1a20cf92d20614c55ac35" - integrity sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg== - -"@types/http-proxy@^1.17.8": - version "1.17.9" - resolved "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.9.tgz#7f0e7931343761efde1e2bf48c40f02f3f75705a" - integrity sha512-QsbSjA/fSk7xB+UXlCT3wHBy5ai9wOcNDWwZAtud+jXhwOM3l+EYZh8Lng4+/6n8uar0J7xILzqftJdJ/Wdfkw== - dependencies: - "@types/node" "*" - -"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": - version "2.0.4" - resolved "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz#8467d4b3c087805d63580480890791277ce35c44" - integrity sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g== - -"@types/istanbul-lib-report@*": - version "3.0.0" - resolved "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#c14c24f18ea8190c118ee7562b7ff99a36552686" - integrity sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg== - dependencies: - "@types/istanbul-lib-coverage" "*" - -"@types/istanbul-reports@^3.0.0": - version "3.0.1" - resolved "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz#9153fe98bba2bd565a63add9436d6f0d7f8468ff" - integrity sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw== - dependencies: - "@types/istanbul-lib-report" "*" - -"@types/jest@*": - version "29.2.3" - resolved "https://registry.npmjs.org/@types/jest/-/jest-29.2.3.tgz#f5fd88e43e5a9e4221ca361e23790d48fcf0a211" - integrity sha512-6XwoEbmatfyoCjWRX7z0fKMmgYKe9+/HrviJ5k0X/tjJWHGAezZOfYaxqQKuzG/TvQyr+ktjm4jgbk0s4/oF2w== - dependencies: - expect "^29.0.0" - pretty-format "^29.0.0" - -"@types/jest@^27.4.0": - version "27.5.2" - resolved "https://registry.npmjs.org/@types/jest/-/jest-27.5.2.tgz#ec49d29d926500ffb9fd22b84262e862049c026c" - integrity sha512-mpT8LJJ4CMeeahobofYWIjFo0xonRS/HfxnVEPMPFSQdGUt1uHCnoPT7Zhb+sjDU2wz0oKV0OLUR0WzrHNgfeA== - dependencies: - jest-matcher-utils "^27.0.0" - pretty-format "^27.0.0" - -"@types/json-schema@*", "@types/json-schema@^7.0.4", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9": - version "7.0.11" - resolved "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3" - integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ== - -"@types/json5@^0.0.29": - version "0.0.29" - resolved "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" - integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ== - -"@types/lodash.throttle@^4.1.6": - version "4.1.7" - resolved "https://registry.npmjs.org/@types/lodash.throttle/-/lodash.throttle-4.1.7.tgz#4ef379eb4f778068022310ef166625f420b6ba58" - integrity sha512-znwGDpjCHQ4FpLLx19w4OXDqq8+OvREa05H89obtSyXyOFKL3dDjCslsmfBz0T2FU8dmf5Wx1QvogbINiGIu9g== - dependencies: - "@types/lodash" "*" - -"@types/lodash@*": - version "4.14.189" - resolved "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.189.tgz#975ff8c38da5ae58b751127b19ad5e44b5b7f6d2" - integrity sha512-kb9/98N6X8gyME9Cf7YaqIMvYGnBSWqEci6tiettE6iJWH1XdJz/PO8LB0GtLCG7x8dU3KWhZT+lA1a35127tA== - -"@types/mime@*": - version "3.0.1" - resolved "https://registry.npmjs.org/@types/mime/-/mime-3.0.1.tgz#5f8f2bca0a5863cb69bc0b0acd88c96cb1d4ae10" - integrity sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA== - -"@types/node-fetch@^2.6.2": - version "2.6.2" - resolved "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.2.tgz#d1a9c5fd049d9415dce61571557104dec3ec81da" - integrity sha512-DHqhlq5jeESLy19TYhLakJ07kNumXWjcDdxXsLUMJZ6ue8VZJj4kLPQVE/2mdHh3xZziNF1xppu5lwmS53HR+A== - dependencies: - "@types/node" "*" - form-data "^3.0.0" - -"@types/node@*", "@types/node@^18.11.9": - version "18.11.9" - resolved "https://registry.npmjs.org/@types/node/-/node-18.11.9.tgz#02d013de7058cea16d36168ef2fc653464cfbad4" - integrity sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg== - -"@types/parse-json@^4.0.0": - version "4.0.0" - resolved "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" - integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== - -"@types/prettier@^2.1.5": - version "2.7.1" - resolved "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.1.tgz#dfd20e2dc35f027cdd6c1908e80a5ddc7499670e" - integrity sha512-ri0UmynRRvZiiUJdiz38MmIblKK+oH30MztdBVR95dv/Ubw6neWSb8u1XpRb72L4qsZOhz+L+z9JD40SJmfWow== - -"@types/prop-types@*": - version "15.7.5" - resolved "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz#5f19d2b85a98e9558036f6a3cacc8819420f05cf" - integrity sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w== - -"@types/q@^1.5.1": - version "1.5.5" - resolved "https://registry.npmjs.org/@types/q/-/q-1.5.5.tgz#75a2a8e7d8ab4b230414505d92335d1dcb53a6df" - integrity sha512-L28j2FcJfSZOnL1WBjDYp2vUHCeIFlyYI/53EwD/rKUBQ7MtUUfbQWiyKJGpcnv4/WgrhWsFKrcPstcAt/J0tQ== - -"@types/qs@*": - version "6.9.7" - resolved "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb" - integrity sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw== - -"@types/range-parser@*": - version "1.2.4" - resolved "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc" - integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw== - -"@types/react-dom@^18.0.0", "@types/react-dom@^18.0.9": - version "18.0.9" - resolved "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.9.tgz#ffee5e4bfc2a2f8774b15496474f8e7fe8d0b504" - integrity sha512-qnVvHxASt/H7i+XG1U1xMiY5t+IHcPGUK7TDMDzom08xa7e86eCeKOiLZezwCKVxJn6NEiiy2ekgX8aQssjIKg== - dependencies: - "@types/react" "*" - -"@types/react-helmet@^6.1.5": - version "6.1.5" - resolved "https://registry.npmjs.org/@types/react-helmet/-/react-helmet-6.1.5.tgz#35f89a6b1646ee2bc342a33a9a6c8777933f9083" - integrity sha512-/ICuy7OHZxR0YCAZLNg9r7I9aijWUWvxaPR6uTuyxe8tAj5RL4Sw1+R6NhXUtOsarkGYPmaHdBDvuXh2DIN/uA== - dependencies: - "@types/react" "*" - -"@types/react-lottie@^1.2.6": - version "1.2.6" - resolved "https://registry.npmjs.org/@types/react-lottie/-/react-lottie-1.2.6.tgz#4f351dfdf5f93a46a3a9714fbb319f1e0f030eaf" - integrity sha512-fvGJHD7SeUdVESHo7f7erRnXkTWaa/6Mo5TB+R0/ieSftKoFspA4sMlF2qMH6BljXI7ehFJbBtrD5bzDxPCkGg== - dependencies: - "@types/react" "*" - -"@types/react-scroll@^1.8.5": - version "1.8.5" - resolved "https://registry.npmjs.org/@types/react-scroll/-/react-scroll-1.8.5.tgz#be0803e62a40ba3ffbe4dcc7348cea32d8a87f41" - integrity sha512-+adEt41hQHMX4aoBOD9Y336QzQzAtlFXTrsFWToS+efgqsYXUOo0JKLeI0O5GLE50Peap6DsbUQRK6gnv8t6wQ== - dependencies: - "@types/react" "*" - -"@types/react@*", "@types/react@^18.0.25": - version "18.0.25" - resolved "https://registry.npmjs.org/@types/react/-/react-18.0.25.tgz#8b1dcd7e56fe7315535a4af25435e0bb55c8ae44" - integrity sha512-xD6c0KDT4m7n9uD4ZHi02lzskaiqcBxf4zi+tXZY98a04wvc0hi/TcCPC2FOESZi51Nd7tlUeOJY8RofL799/g== - dependencies: - "@types/prop-types" "*" - "@types/scheduler" "*" - csstype "^3.0.2" - -"@types/resolve@1.17.1": - version "1.17.1" - resolved "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz#3afd6ad8967c77e4376c598a82ddd58f46ec45d6" - integrity sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw== - dependencies: - "@types/node" "*" - -"@types/retry@0.12.0": - version "0.12.0" - resolved "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz#2b35eccfcee7d38cd72ad99232fbd58bffb3c84d" - integrity sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA== - -"@types/scheduler@*": - version "0.16.2" - resolved "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz#1a62f89525723dde24ba1b01b092bf5df8ad4d39" - integrity sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew== - -"@types/semver@^7.3.12": - version "7.3.13" - resolved "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz#da4bfd73f49bd541d28920ab0e2bf0ee80f71c91" - integrity sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw== - -"@types/serve-index@^1.9.1": - version "1.9.1" - resolved "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.1.tgz#1b5e85370a192c01ec6cec4735cf2917337a6278" - integrity sha512-d/Hs3nWDxNL2xAczmOVZNj92YZCS6RGxfBPjKzuu/XirCgXdpKEb88dYNbrYGint6IVWLNP+yonwVAuRC0T2Dg== - dependencies: - "@types/express" "*" - -"@types/serve-static@*", "@types/serve-static@^1.13.10": - version "1.15.0" - resolved "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.0.tgz#c7930ff61afb334e121a9da780aac0d9b8f34155" - integrity sha512-z5xyF6uh8CbjAu9760KDKsH2FcDxZ2tFCsA4HIMWE6IkiYMXfVoa+4f9KX+FN0ZLsaMw1WNG2ETLA6N+/YA+cg== - dependencies: - "@types/mime" "*" - "@types/node" "*" - -"@types/sockjs@^0.3.33": - version "0.3.33" - resolved "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.33.tgz#570d3a0b99ac995360e3136fd6045113b1bd236f" - integrity sha512-f0KEEe05NvUnat+boPTZ0dgaLZ4SfSouXUgv5noUiefG2ajgKjmETo9ZJyuqsl7dfl2aHlLJUiki6B4ZYldiiw== - dependencies: - "@types/node" "*" - -"@types/stack-utils@^2.0.0": - version "2.0.1" - resolved "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c" - integrity sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw== - -"@types/styled-components@*", "@types/styled-components@^5.1.21": - version "5.1.26" - resolved "https://registry.npmjs.org/@types/styled-components/-/styled-components-5.1.26.tgz#5627e6812ee96d755028a98dae61d28e57c233af" - integrity sha512-KuKJ9Z6xb93uJiIyxo/+ksS7yLjS1KzG6iv5i78dhVg/X3u5t1H7juRWqVmodIdz6wGVaIApo1u01kmFRdJHVw== - dependencies: - "@types/hoist-non-react-statics" "*" - "@types/react" "*" - csstype "^3.0.2" - -"@types/styled-theming@^2.2.5": - version "2.2.5" - resolved "https://registry.npmjs.org/@types/styled-theming/-/styled-theming-2.2.5.tgz#3942caf3fc03550a8501b56bc1e326137172c136" - integrity sha512-7Rxu3Oj2nFG40+barPjt/5hzexlZTjWKDHfngrprxO2+y9RhxtzJ2wSiZrY9+kwtC0mmlI0poVW4PxTiobv1qQ== - dependencies: - "@types/styled-components" "*" - csstype "^3.0.2" - -"@types/testing-library__jest-dom@^5.9.1": - version "5.14.5" - resolved "https://registry.npmjs.org/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.14.5.tgz#d113709c90b3c75fdb127ec338dad7d5f86c974f" - integrity sha512-SBwbxYoyPIvxHbeHxTZX2Pe/74F/tX2/D3mMvzabdeJ25bBojfW0TyB8BHrbq/9zaaKICJZjLP+8r6AeZMFCuQ== - dependencies: - "@types/jest" "*" - -"@types/trusted-types@^2.0.2": - version "2.0.2" - resolved "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.2.tgz#fc25ad9943bcac11cceb8168db4f275e0e72e756" - integrity sha512-F5DIZ36YVLE+PN+Zwws4kJogq47hNgX3Nx6WyDJ3kcplxyke3XIzB8uK5n/Lpm1HBsbGzd6nmGehL8cPekP+Tg== - -"@types/websocket@^1.0.5": - version "1.0.5" - resolved "https://registry.npmjs.org/@types/websocket/-/websocket-1.0.5.tgz#3fb80ed8e07f88e51961211cd3682a3a4a81569c" - integrity sha512-NbsqiNX9CnEfC1Z0Vf4mE1SgAJ07JnRYcNex7AJ9zAVzmiGHmjKFEk7O4TJIsgv2B1sLEb6owKFZrACwdYngsQ== - dependencies: - "@types/node" "*" - -"@types/ws@^8.5.1": - version "8.5.3" - resolved "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz#7d25a1ffbecd3c4f2d35068d0b283c037003274d" - integrity sha512-6YOoWjruKj1uLf3INHH7D3qTXwFfEsg1kf3c0uDdSBJwfa/llkwIjrAGV7j7mVgGNbzTQ3HiHKKDXl6bJPD97w== - dependencies: - "@types/node" "*" - -"@types/yargs-parser@*": - version "21.0.0" - resolved "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz#0c60e537fa790f5f9472ed2776c2b71ec117351b" - integrity sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA== - -"@types/yargs@^16.0.0": - version "16.0.4" - resolved "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz#26aad98dd2c2a38e421086ea9ad42b9e51642977" - integrity sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw== - dependencies: - "@types/yargs-parser" "*" - -"@types/yargs@^17.0.8": - version "17.0.13" - resolved "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.13.tgz#34cced675ca1b1d51fcf4d34c3c6f0fa142a5c76" - integrity sha512-9sWaruZk2JGxIQU+IhI1fhPYRcQ0UuTNuKuCW9bR5fp7qi2Llf7WDzNa17Cy7TKnh3cdxDOiyTu6gaLS0eDatg== - dependencies: - "@types/yargs-parser" "*" - -"@typescript-eslint/eslint-plugin@^5.44.0", "@typescript-eslint/eslint-plugin@^5.5.0": - version "5.44.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.44.0.tgz#105788f299050c917eb85c4d9fd04b089e3740de" - integrity sha512-j5ULd7FmmekcyWeArx+i8x7sdRHzAtXTkmDPthE4amxZOWKFK7bomoJ4r7PJ8K7PoMzD16U8MmuZFAonr1ERvw== - dependencies: - "@typescript-eslint/scope-manager" "5.44.0" - "@typescript-eslint/type-utils" "5.44.0" - "@typescript-eslint/utils" "5.44.0" - debug "^4.3.4" - ignore "^5.2.0" - natural-compare-lite "^1.4.0" - regexpp "^3.2.0" - semver "^7.3.7" - tsutils "^3.21.0" - -"@typescript-eslint/experimental-utils@^5.0.0": - version "5.43.0" - resolved "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-5.43.0.tgz#2fbea6ea89e59e780e42ca65bc39fc830db95ed4" - integrity sha512-WkT637CumTJbm/hRbFfnHBMgfUYTKr08LitVsD7gQId7bi6rnkx3pu3jac67lmp5ObW4MpJ9SNFZAIOUB/Qbsw== - dependencies: - "@typescript-eslint/utils" "5.43.0" - -"@typescript-eslint/parser@^5.44.0", "@typescript-eslint/parser@^5.5.0": - version "5.44.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.44.0.tgz#99e2c710a2252191e7a79113264f438338b846ad" - integrity sha512-H7LCqbZnKqkkgQHaKLGC6KUjt3pjJDx8ETDqmwncyb6PuoigYajyAwBGz08VU/l86dZWZgI4zm5k2VaKqayYyA== - dependencies: - "@typescript-eslint/scope-manager" "5.44.0" - "@typescript-eslint/types" "5.44.0" - "@typescript-eslint/typescript-estree" "5.44.0" - debug "^4.3.4" - -"@typescript-eslint/scope-manager@5.43.0": - version "5.43.0" - resolved "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.43.0.tgz#566e46303392014d5d163704724872e1f2dd3c15" - integrity sha512-XNWnGaqAtTJsUiZaoiGIrdJYHsUOd3BZ3Qj5zKp9w6km6HsrjPk/TGZv0qMTWyWj0+1QOqpHQ2gZOLXaGA9Ekw== - dependencies: - "@typescript-eslint/types" "5.43.0" - "@typescript-eslint/visitor-keys" "5.43.0" - -"@typescript-eslint/scope-manager@5.44.0": - version "5.44.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.44.0.tgz#988c3f34b45b3474eb9ff0674c18309dedfc3e04" - integrity sha512-2pKml57KusI0LAhgLKae9kwWeITZ7IsZs77YxyNyIVOwQ1kToyXRaJLl+uDEXzMN5hnobKUOo2gKntK9H1YL8g== - dependencies: - "@typescript-eslint/types" "5.44.0" - "@typescript-eslint/visitor-keys" "5.44.0" - -"@typescript-eslint/type-utils@5.44.0": - version "5.44.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.44.0.tgz#bc5a6e8a0269850714a870c9268c038150dfb3c7" - integrity sha512-A1u0Yo5wZxkXPQ7/noGkRhV4J9opcymcr31XQtOzcc5nO/IHN2E2TPMECKWYpM3e6olWEM63fq/BaL1wEYnt/w== - dependencies: - "@typescript-eslint/typescript-estree" "5.44.0" - "@typescript-eslint/utils" "5.44.0" - debug "^4.3.4" - tsutils "^3.21.0" - -"@typescript-eslint/types@5.43.0": - version "5.43.0" - resolved "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.43.0.tgz#e4ddd7846fcbc074325293515fa98e844d8d2578" - integrity sha512-jpsbcD0x6AUvV7tyOlyvon0aUsQpF8W+7TpJntfCUWU1qaIKu2K34pMwQKSzQH8ORgUrGYY6pVIh1Pi8TNeteg== - -"@typescript-eslint/types@5.44.0": - version "5.44.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.44.0.tgz#f3f0b89aaff78f097a2927fe5688c07e786a0241" - integrity sha512-Tp+zDnHmGk4qKR1l+Y1rBvpjpm5tGXX339eAlRBDg+kgZkz9Bw+pqi4dyseOZMsGuSH69fYfPJCBKBrbPCxYFQ== - -"@typescript-eslint/typescript-estree@5.43.0": - version "5.43.0" - resolved "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.43.0.tgz#b6883e58ba236a602c334be116bfc00b58b3b9f2" - integrity sha512-BZ1WVe+QQ+igWal2tDbNg1j2HWUkAa+CVqdU79L4HP9izQY6CNhXfkNwd1SS4+sSZAP/EthI1uiCSY/+H0pROg== - dependencies: - "@typescript-eslint/types" "5.43.0" - "@typescript-eslint/visitor-keys" "5.43.0" - debug "^4.3.4" - globby "^11.1.0" - is-glob "^4.0.3" - semver "^7.3.7" - tsutils "^3.21.0" - -"@typescript-eslint/typescript-estree@5.44.0": - version "5.44.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.44.0.tgz#0461b386203e8d383bb1268b1ed1da9bc905b045" - integrity sha512-M6Jr+RM7M5zeRj2maSfsZK2660HKAJawv4Ud0xT+yauyvgrsHu276VtXlKDFnEmhG+nVEd0fYZNXGoAgxwDWJw== - dependencies: - "@typescript-eslint/types" "5.44.0" - "@typescript-eslint/visitor-keys" "5.44.0" - debug "^4.3.4" - globby "^11.1.0" - is-glob "^4.0.3" - semver "^7.3.7" - tsutils "^3.21.0" - -"@typescript-eslint/utils@5.43.0": - version "5.43.0" - resolved "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.43.0.tgz#00fdeea07811dbdf68774a6f6eacfee17fcc669f" - integrity sha512-8nVpA6yX0sCjf7v/NDfeaOlyaIIqL7OaIGOWSPFqUKK59Gnumd3Wa+2l8oAaYO2lk0sO+SbWFWRSvhu8gLGv4A== - dependencies: - "@types/json-schema" "^7.0.9" - "@types/semver" "^7.3.12" - "@typescript-eslint/scope-manager" "5.43.0" - "@typescript-eslint/types" "5.43.0" - "@typescript-eslint/typescript-estree" "5.43.0" - eslint-scope "^5.1.1" - eslint-utils "^3.0.0" - semver "^7.3.7" - -"@typescript-eslint/utils@5.44.0", "@typescript-eslint/utils@^5.13.0": - version "5.44.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.44.0.tgz#d733da4d79d6c30f1a68b531cdda1e0c1f00d52d" - integrity sha512-fMzA8LLQ189gaBjS0MZszw5HBdZgVwxVFShCO3QN+ws3GlPkcy9YuS3U4wkT6su0w+Byjq3mS3uamy9HE4Yfjw== - dependencies: - "@types/json-schema" "^7.0.9" - "@types/semver" "^7.3.12" - "@typescript-eslint/scope-manager" "5.44.0" - "@typescript-eslint/types" "5.44.0" - "@typescript-eslint/typescript-estree" "5.44.0" - eslint-scope "^5.1.1" - eslint-utils "^3.0.0" - semver "^7.3.7" - -"@typescript-eslint/visitor-keys@5.43.0": - version "5.43.0" - resolved "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.43.0.tgz#cbbdadfdfea385310a20a962afda728ea106befa" - integrity sha512-icl1jNH/d18OVHLfcwdL3bWUKsBeIiKYTGxMJCoGe7xFht+E4QgzOqoWYrU8XSLJWhVw8nTacbm03v23J/hFTg== - dependencies: - "@typescript-eslint/types" "5.43.0" - eslint-visitor-keys "^3.3.0" - -"@typescript-eslint/visitor-keys@5.44.0": - version "5.44.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.44.0.tgz#10740dc28902bb903d12ee3a005cc3a70207d433" - integrity sha512-a48tLG8/4m62gPFbJ27FxwCOqPKxsb8KC3HkmYoq2As/4YyjQl1jDbRr1s63+g4FS/iIehjmN3L5UjmKva1HzQ== - dependencies: - "@typescript-eslint/types" "5.44.0" - eslint-visitor-keys "^3.3.0" - -"@webassemblyjs/ast@1.11.1": - version "1.11.1" - resolved "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz#2bfd767eae1a6996f432ff7e8d7fc75679c0b6a7" - integrity sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw== - dependencies: - "@webassemblyjs/helper-numbers" "1.11.1" - "@webassemblyjs/helper-wasm-bytecode" "1.11.1" - -"@webassemblyjs/floating-point-hex-parser@1.11.1": - version "1.11.1" - resolved "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz#f6c61a705f0fd7a6aecaa4e8198f23d9dc179e4f" - integrity sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ== - -"@webassemblyjs/helper-api-error@1.11.1": - version "1.11.1" - resolved "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz#1a63192d8788e5c012800ba6a7a46c705288fd16" - integrity sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg== - -"@webassemblyjs/helper-buffer@1.11.1": - version "1.11.1" - resolved "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz#832a900eb444884cde9a7cad467f81500f5e5ab5" - integrity sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA== - -"@webassemblyjs/helper-numbers@1.11.1": - version "1.11.1" - resolved "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz#64d81da219fbbba1e3bd1bfc74f6e8c4e10a62ae" - integrity sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ== - dependencies: - "@webassemblyjs/floating-point-hex-parser" "1.11.1" - "@webassemblyjs/helper-api-error" "1.11.1" - "@xtuc/long" "4.2.2" - -"@webassemblyjs/helper-wasm-bytecode@1.11.1": - version "1.11.1" - resolved "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz#f328241e41e7b199d0b20c18e88429c4433295e1" - integrity sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q== - -"@webassemblyjs/helper-wasm-section@1.11.1": - version "1.11.1" - resolved "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz#21ee065a7b635f319e738f0dd73bfbda281c097a" - integrity sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg== - dependencies: - "@webassemblyjs/ast" "1.11.1" - "@webassemblyjs/helper-buffer" "1.11.1" - "@webassemblyjs/helper-wasm-bytecode" "1.11.1" - "@webassemblyjs/wasm-gen" "1.11.1" - -"@webassemblyjs/ieee754@1.11.1": - version "1.11.1" - resolved "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz#963929e9bbd05709e7e12243a099180812992614" - integrity sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ== - dependencies: - "@xtuc/ieee754" "^1.2.0" - -"@webassemblyjs/leb128@1.11.1": - version "1.11.1" - resolved "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.1.tgz#ce814b45574e93d76bae1fb2644ab9cdd9527aa5" - integrity sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw== - dependencies: - "@xtuc/long" "4.2.2" - -"@webassemblyjs/utf8@1.11.1": - version "1.11.1" - resolved "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.1.tgz#d1f8b764369e7c6e6bae350e854dec9a59f0a3ff" - integrity sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ== - -"@webassemblyjs/wasm-edit@1.11.1": - version "1.11.1" - resolved "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz#ad206ebf4bf95a058ce9880a8c092c5dec8193d6" - integrity sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA== - dependencies: - "@webassemblyjs/ast" "1.11.1" - "@webassemblyjs/helper-buffer" "1.11.1" - "@webassemblyjs/helper-wasm-bytecode" "1.11.1" - "@webassemblyjs/helper-wasm-section" "1.11.1" - "@webassemblyjs/wasm-gen" "1.11.1" - "@webassemblyjs/wasm-opt" "1.11.1" - "@webassemblyjs/wasm-parser" "1.11.1" - "@webassemblyjs/wast-printer" "1.11.1" - -"@webassemblyjs/wasm-gen@1.11.1": - version "1.11.1" - resolved "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz#86c5ea304849759b7d88c47a32f4f039ae3c8f76" - integrity sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA== - dependencies: - "@webassemblyjs/ast" "1.11.1" - "@webassemblyjs/helper-wasm-bytecode" "1.11.1" - "@webassemblyjs/ieee754" "1.11.1" - "@webassemblyjs/leb128" "1.11.1" - "@webassemblyjs/utf8" "1.11.1" - -"@webassemblyjs/wasm-opt@1.11.1": - version "1.11.1" - resolved "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz#657b4c2202f4cf3b345f8a4c6461c8c2418985f2" - integrity sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw== - dependencies: - "@webassemblyjs/ast" "1.11.1" - "@webassemblyjs/helper-buffer" "1.11.1" - "@webassemblyjs/wasm-gen" "1.11.1" - "@webassemblyjs/wasm-parser" "1.11.1" - -"@webassemblyjs/wasm-parser@1.11.1": - version "1.11.1" - resolved "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz#86ca734534f417e9bd3c67c7a1c75d8be41fb199" - integrity sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA== - dependencies: - "@webassemblyjs/ast" "1.11.1" - "@webassemblyjs/helper-api-error" "1.11.1" - "@webassemblyjs/helper-wasm-bytecode" "1.11.1" - "@webassemblyjs/ieee754" "1.11.1" - "@webassemblyjs/leb128" "1.11.1" - "@webassemblyjs/utf8" "1.11.1" - -"@webassemblyjs/wast-printer@1.11.1": - version "1.11.1" - resolved "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz#d0c73beda8eec5426f10ae8ef55cee5e7084c2f0" - integrity sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg== - dependencies: - "@webassemblyjs/ast" "1.11.1" - "@xtuc/long" "4.2.2" - -"@wry/caches@^1.0.0": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@wry/caches/-/caches-1.0.1.tgz#8641fd3b6e09230b86ce8b93558d44cf1ece7e52" - integrity sha512-bXuaUNLVVkD20wcGBWRyo7j9N3TxePEWFZj2Y+r9OoUzfqmavM84+mFykRicNsBqatba5JLay1t48wxaXaWnlA== - dependencies: - tslib "^2.3.0" + resolved "https://registry.yarnpkg.com/@wry/caches/-/caches-1.0.1.tgz#8641fd3b6e09230b86ce8b93558d44cf1ece7e52" + integrity sha512-bXuaUNLVVkD20wcGBWRyo7j9N3TxePEWFZj2Y+r9OoUzfqmavM84+mFykRicNsBqatba5JLay1t48wxaXaWnlA== + dependencies: + tslib "^2.3.0" "@wry/context@^0.7.0": version "0.7.4" @@ -3131,113 +1519,37 @@ dependencies: tslib "^2.3.0" -"@xtuc/ieee754@^1.2.0": - version "1.2.0" - resolved "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790" - integrity sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA== - -"@xtuc/long@4.2.2": - version "4.2.2" - resolved "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d" - integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== - -abab@^2.0.3, abab@^2.0.5: - version "2.0.6" - resolved "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz#41b80f2c871d19686216b82309231cfd3cb3d291" - integrity sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA== - -accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.8: - version "1.3.8" - resolved "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" - integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== +"@zondax/ledger-substrate@^0.41.3": + version "0.41.3" + resolved "https://registry.yarnpkg.com/@zondax/ledger-substrate/-/ledger-substrate-0.41.3.tgz#04e33a8aa8c589551caf63139653aba4ed7b9219" + integrity sha512-pjsTGODRHP+SG+h4hBkA9NmvHQeplkj48cB5/TUlzRVBZSz7k172Cu70lpGDkVsKDKG6AuCP2pyWKKzPQIzNTA== dependencies: - mime-types "~2.1.34" - negotiator "0.6.3" - -acorn-globals@^6.0.0: - version "6.0.0" - resolved "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz#46cdd39f0f8ff08a876619b55f5ac8a6dc770b45" - integrity sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg== - dependencies: - acorn "^7.1.1" - acorn-walk "^7.1.1" - -acorn-import-assertions@^1.7.6: - version "1.8.0" - resolved "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz#ba2b5939ce62c238db6d93d81c9b111b29b855e9" - integrity sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw== + "@ledgerhq/hw-transport" "^6.27.1" + bip32 "^4.0.0" + bip32-ed25519 "https://github.com/Zondax/bip32-ed25519" + bip39 "^3.0.4" + blakejs "^1.2.1" + bs58 "^5.0.0" + hash.js "^1.1.7" acorn-jsx@^5.3.2: version "5.3.2" - resolved "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== -acorn-node@^1.8.2: - version "1.8.2" - resolved "https://registry.npmjs.org/acorn-node/-/acorn-node-1.8.2.tgz#114c95d64539e53dede23de8b9d96df7c7ae2af8" - integrity sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A== - dependencies: - acorn "^7.0.0" - acorn-walk "^7.0.0" - xtend "^4.0.2" - -acorn-walk@^7.0.0, acorn-walk@^7.1.1: - version "7.2.0" - resolved "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz#0de889a601203909b0fbe07b8938dc21d2e967bc" - integrity sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA== - -acorn@^7.0.0, acorn@^7.1.1: - version "7.4.1" - resolved "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" - integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== - -acorn@^8.2.4, acorn@^8.5.0, acorn@^8.7.1, acorn@^8.8.0: - version "8.8.1" - resolved "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz#0a3f9cbecc4ec3bea6f0a80b66ae8dd2da250b73" - integrity sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA== - -address@^1.0.1, address@^1.1.2: - version "1.2.1" - resolved "https://registry.npmjs.org/address/-/address-1.2.1.tgz#25bb61095b7522d65b357baa11bc05492d4c8acd" - integrity sha512-B+6bi5D34+fDYENiH5qOlA0cV2rAGKuWZ9LeyUUehbXy8e0VS9e498yO0Jeeh+iM+6KbfudHTFjXw2MmJD4QRA== - -adjust-sourcemap-loader@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/adjust-sourcemap-loader/-/adjust-sourcemap-loader-4.0.0.tgz#fc4a0fd080f7d10471f30a7320f25560ade28c99" - integrity sha512-OXwN5b9pCUXNQHJpwwD2qP40byEmSgzj8B4ydSN0uMNYWiFmJ6x6KwUllMmfk8Rwu/HJDFR7U8ubsWBoN0Xp0A== - dependencies: - loader-utils "^2.0.0" - regex-parser "^2.2.11" - -agent-base@6: - version "6.0.2" - resolved "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" - integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== - dependencies: - debug "4" - -ajv-formats@^2.1.1: - version "2.1.1" - resolved "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz#6e669400659eb74973bbf2e33327180a0996b520" - integrity sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA== - dependencies: - ajv "^8.0.0" - -ajv-keywords@^3.4.1, ajv-keywords@^3.5.2: - version "3.5.2" - resolved "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d" - integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ== +acorn-walk@^8.2.0: + version "8.3.0" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.0.tgz#2097665af50fd0cf7a2dfccd2b9368964e66540f" + integrity sha512-FS7hV565M5l1R08MXqo8odwMTB02C2UqzB17RVgu9EyuYFBqJZ3/ZY97sQD5FewVu1UyDFc1yztUDrAwT0EypA== -ajv-keywords@^5.0.0: - version "5.1.0" - resolved "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz#69d4d385a4733cdbeab44964a1170a88f87f0e16" - integrity sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw== - dependencies: - fast-deep-equal "^3.1.3" +acorn@^8.10.0, acorn@^8.9.0: + version "8.11.2" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.2.tgz#ca0d78b51895be5390a5903c5b3bdcdaf78ae40b" + integrity sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w== -ajv@^6.10.0, ajv@^6.12.2, ajv@^6.12.4, ajv@^6.12.5: +ajv@^6.12.4: version "6.12.6" - resolved "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== dependencies: fast-deep-equal "^3.1.1" @@ -3245,395 +1557,193 @@ ajv@^6.10.0, ajv@^6.12.2, ajv@^6.12.4, ajv@^6.12.5: json-schema-traverse "^0.4.1" uri-js "^4.2.2" -ajv@^8.0.0, ajv@^8.6.0, ajv@^8.8.0: - version "8.11.2" - resolved "https://registry.npmjs.org/ajv/-/ajv-8.11.2.tgz#aecb20b50607acf2569b6382167b65a96008bb78" - integrity sha512-E4bfmKAhGiSTvMfL1Myyycaub+cUEU2/IvpylXkUu7CHBkBj1f/ikdzbD7YQ6FKUbixDxeYvB/xY4fvyroDlQg== - dependencies: - fast-deep-equal "^3.1.1" - json-schema-traverse "^1.0.0" - require-from-string "^2.0.2" - uri-js "^4.2.2" - -ansi-escapes@^4.2.1, ansi-escapes@^4.3.1: +ansi-escapes@^4.3.0: version "4.3.2" - resolved "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== dependencies: type-fest "^0.21.3" -ansi-html-community@^0.0.8: - version "0.0.8" - resolved "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz#69fbc4d6ccbe383f9736934ae34c3f8290f1bf41" - integrity sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw== - ansi-regex@^5.0.1: version "5.0.1" - resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== -ansi-regex@^6.0.1: - version "6.0.1" - resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz#3183e38fae9a65d7cb5e53945cd5897d0260a06a" - integrity sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA== - ansi-styles@^3.2.1: version "3.2.1" - resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== dependencies: color-convert "^1.9.0" ansi-styles@^4.0.0, ansi-styles@^4.1.0: version "4.3.0" - resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== dependencies: color-convert "^2.0.1" ansi-styles@^5.0.0: version "5.2.0" - resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== -anymatch@^3.0.3, anymatch@~3.1.2: - version "3.1.2" - resolved "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716" - integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg== +anymatch@~3.1.2: + version "3.1.3" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" + integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== dependencies: normalize-path "^3.0.0" picomatch "^2.0.4" -arg@^5.0.2: - version "5.0.2" - resolved "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz#c81433cc427c92c4dcf4865142dbca6f15acd59c" - integrity sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg== - -argparse@^1.0.7: - version "1.0.10" - resolved "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" - integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== - dependencies: - sprintf-js "~1.0.2" - argparse@^2.0.1: version "2.0.1" - resolved "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== -aria-query@^4.2.2: - version "4.2.2" - resolved "https://registry.npmjs.org/aria-query/-/aria-query-4.2.2.tgz#0d2ca6c9aceb56b8977e9fed6aed7e15bbd2f83b" - integrity sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA== +aria-query@^5.1.3: + version "5.3.0" + resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.3.0.tgz#650c569e41ad90b51b3d7df5e5eed1c7549c103e" + integrity sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A== dependencies: - "@babel/runtime" "^7.10.2" - "@babel/runtime-corejs3" "^7.10.2" + dequal "^2.0.3" -aria-query@^5.0.0: - version "5.1.3" - resolved "https://registry.npmjs.org/aria-query/-/aria-query-5.1.3.tgz#19db27cd101152773631396f7a95a3b58c22c35e" - integrity sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ== +array-buffer-byte-length@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz#fabe8bc193fea865f317fe7807085ee0dee5aead" + integrity sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A== dependencies: - deep-equal "^2.0.5" - -array-flatten@1.1.1: - version "1.1.1" - resolved "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" - integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg== - -array-flatten@^2.1.2: - version "2.1.2" - resolved "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz#24ef80a28c1a893617e2149b0c6d0d788293b099" - integrity sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ== + call-bind "^1.0.2" + is-array-buffer "^3.0.1" -array-includes@^3.1.4, array-includes@^3.1.5, array-includes@^3.1.6: - version "3.1.6" - resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.6.tgz#9e9e720e194f198266ba9e18c29e6a9b0e4b225f" - integrity sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw== +array-includes@^3.1.6, array-includes@^3.1.7: + version "3.1.7" + resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.7.tgz#8cd2e01b26f7a3086cbc87271593fe921c62abda" + integrity sha512-dlcsNBIiWhPkHdOEEKnehA+RNUWDc4UqFtnIXU4uuYDPtA4LDkr7qip2p0VvFAEXNDr0yWZ9PJyIRiGjRLQzwQ== dependencies: call-bind "^1.0.2" - define-properties "^1.1.4" - es-abstract "^1.20.4" - get-intrinsic "^1.1.3" + define-properties "^1.2.0" + es-abstract "^1.22.1" + get-intrinsic "^1.2.1" is-string "^1.0.7" array-union@^1.0.1: version "1.0.2" - resolved "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz#9a34410e4f4e3da23dea375be5be70f24778ec39" + resolved "https://registry.yarnpkg.com/array-union/-/array-union-1.0.2.tgz#9a34410e4f4e3da23dea375be5be70f24778ec39" integrity sha512-Dxr6QJj/RdU/hCaBjOfxW+q6lyuVE6JFWIrAUpuOOhoJJoQ99cUn3igRaHVB5P9WrgFVN0FfArM3x0cueOU8ng== dependencies: array-uniq "^1.0.1" array-union@^2.1.0: version "2.1.0" - resolved "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" + resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== array-uniq@^1.0.1: version "1.0.3" - resolved "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6" + resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6" integrity sha512-MNha4BWQ6JbwhFhj03YK552f7cb3AzoE8SzeljgChvL1dl3IcvggXVz1DilzySZkCja+CXuZbdW7yATchWn8/Q== -array.prototype.flat@^1.2.5: - version "1.3.1" - resolved "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.1.tgz#ffc6576a7ca3efc2f46a143b9d1dda9b4b3cf5e2" - integrity sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA== +array.prototype.findlastindex@^1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.3.tgz#b37598438f97b579166940814e2c0493a4f50207" + integrity sha512-LzLoiOMAxvy+Gd3BAq3B7VeIgPdo+Q8hthvKtXybMvRV0jrXfJM/t8mw7nNlpEcVlVUnCnM2KSX4XU5HmpodOA== dependencies: call-bind "^1.0.2" - define-properties "^1.1.4" - es-abstract "^1.20.4" + define-properties "^1.2.0" + es-abstract "^1.22.1" es-shim-unscopables "^1.0.0" + get-intrinsic "^1.2.1" -array.prototype.flatmap@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.3.1.tgz#1aae7903c2100433cb8261cd4ed310aab5c4a183" - integrity sha512-8UGn9O1FDVvMNB0UlLv4voxRMze7+FpHyF5mSMRjWHUMlpoDViniy05870VlxhfgTnLbpuwTzvD76MTtWxB/mQ== +array.prototype.flat@^1.3.1, array.prototype.flat@^1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz#1476217df8cff17d72ee8f3ba06738db5b387d18" + integrity sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA== dependencies: call-bind "^1.0.2" - define-properties "^1.1.4" - es-abstract "^1.20.4" + define-properties "^1.2.0" + es-abstract "^1.22.1" es-shim-unscopables "^1.0.0" -array.prototype.reduce@^1.0.5: - version "1.0.5" - resolved "https://registry.npmjs.org/array.prototype.reduce/-/array.prototype.reduce-1.0.5.tgz#6b20b0daa9d9734dd6bc7ea66b5bbce395471eac" - integrity sha512-kDdugMl7id9COE8R7MHF5jWk7Dqt/fs4Pv+JXoICnYwqpjjjbUurz6w5fT5IG6brLdJhv6/VoHB0H7oyIBXd+Q== +array.prototype.flatmap@^1.3.1, array.prototype.flatmap@^1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz#c9a7c6831db8e719d6ce639190146c24bbd3e527" + integrity sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ== dependencies: call-bind "^1.0.2" - define-properties "^1.1.4" - es-abstract "^1.20.4" - es-array-method-boxes-properly "^1.0.0" - is-string "^1.0.7" + define-properties "^1.2.0" + es-abstract "^1.22.1" + es-shim-unscopables "^1.0.0" array.prototype.tosorted@^1.1.1: - version "1.1.1" - resolved "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.1.tgz#ccf44738aa2b5ac56578ffda97c03fd3e23dd532" - integrity sha512-pZYPXPRl2PqWcsUs6LOMn+1f1532nEoPTYowBtqLwAW+W8vSVhkIGnmOX1t/UQjD6YGI0vcD2B1U7ZFGQH9jnQ== + version "1.1.2" + resolved "https://registry.yarnpkg.com/array.prototype.tosorted/-/array.prototype.tosorted-1.1.2.tgz#620eff7442503d66c799d95503f82b475745cefd" + integrity sha512-HuQCHOlk1Weat5jzStICBCd83NxiIMwqDg/dHEsoefabn/hJRj5pVdWcPUSpRrwhwxZOsQassMpgN/xRYFBMIg== dependencies: call-bind "^1.0.2" - define-properties "^1.1.4" - es-abstract "^1.20.4" + define-properties "^1.2.0" + es-abstract "^1.22.1" es-shim-unscopables "^1.0.0" - get-intrinsic "^1.1.3" + get-intrinsic "^1.2.1" -asap@~2.0.6: - version "2.0.6" - resolved "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" - integrity sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA== +arraybuffer.prototype.slice@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.2.tgz#98bd561953e3e74bb34938e77647179dfe6e9f12" + integrity sha512-yMBKppFur/fbHu9/6USUe03bZ4knMYiwFBcyiaXB8Go0qNehwX6inYPzK9U0NeQvGxKthcmHcaR8P5MStSRBAw== + dependencies: + array-buffer-byte-length "^1.0.0" + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + get-intrinsic "^1.2.1" + is-array-buffer "^3.0.2" + is-shared-array-buffer "^1.0.2" + +assertion-error@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.1.0.tgz#e60b6b0e8f301bd97e5375215bda406c85118c0b" + integrity sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw== ast-types-flow@^0.0.7: version "0.0.7" - resolved "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz#f70b735c6bca1a5c9c22d982c3e39e7feba3bdad" + resolved "https://registry.yarnpkg.com/ast-types-flow/-/ast-types-flow-0.0.7.tgz#f70b735c6bca1a5c9c22d982c3e39e7feba3bdad" integrity sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag== -async@^2.6.1: - version "2.6.4" - resolved "https://registry.npmjs.org/async/-/async-2.6.4.tgz#706b7ff6084664cd7eae713f6f965433b5504221" - integrity sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA== - dependencies: - lodash "^4.17.14" - -async@^3.2.3: +async@^3.2.4: version "3.2.4" - resolved "https://registry.npmjs.org/async/-/async-3.2.4.tgz#2d22e00f8cddeb5fde5dd33522b56d1cf569a81c" + resolved "https://registry.yarnpkg.com/async/-/async-3.2.4.tgz#2d22e00f8cddeb5fde5dd33522b56d1cf569a81c" integrity sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ== -asynckit@^0.4.0: - version "0.4.0" - resolved "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" - integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== - -at-least-node@^1.0.0: +asynciterator.prototype@^1.0.0: version "1.0.0" - resolved "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2" - integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg== - -autoprefixer@^10.4.13: - version "10.4.13" - resolved "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.13.tgz#b5136b59930209a321e9fa3dca2e7c4d223e83a8" - integrity sha512-49vKpMqcZYsJjwotvt4+h/BCjJVnhGwcLpDt5xkcaOG3eLrG/HUYLagrihYsQ+qrIBgIzX1Rw7a6L8I/ZA1Atg== - dependencies: - browserslist "^4.21.4" - caniuse-lite "^1.0.30001426" - fraction.js "^4.2.0" - normalize-range "^0.1.2" - picocolors "^1.0.0" - postcss-value-parser "^4.2.0" + resolved "https://registry.yarnpkg.com/asynciterator.prototype/-/asynciterator.prototype-1.0.0.tgz#8c5df0514936cdd133604dfcc9d3fb93f09b2b62" + integrity sha512-wwHYEIS0Q80f5mosx3L/dfG5t5rjEa9Ft51GTaNt862EnpyGHpgz2RkZvLPp1oF5TnAiTohkEKVEu8pQPJI7Vg== + dependencies: + has-symbols "^1.0.3" available-typed-arrays@^1.0.5: version "1.0.5" - resolved "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz#92f95616501069d07d10edb2fc37d3e1c65123b7" + resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz#92f95616501069d07d10edb2fc37d3e1c65123b7" integrity sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw== -axe-core@^4.4.3: - version "4.5.2" - resolved "https://registry.npmjs.org/axe-core/-/axe-core-4.5.2.tgz#823fdf491ff717ac3c58a52631d4206930c1d9f7" - integrity sha512-u2MVsXfew5HBvjsczCv+xlwdNnB1oQR9HlAcsejZttNjKKSkeDNVwB1vMThIUIFI9GoT57Vtk8iQLwqOfAkboA== - -axobject-query@^2.2.0: - version "2.2.0" - resolved "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz#943d47e10c0b704aa42275e20edf3722648989be" - integrity sha512-Td525n+iPOOyUQIeBfcASuG6uJsDOITl7Mds5gFyerkWiX7qhUTdYUBlSgNMyVqtSJqwpt1kXGLdUt6SykLMRA== - -babel-jest@^27.4.2, babel-jest@^27.5.1: - version "27.5.1" - resolved "https://registry.npmjs.org/babel-jest/-/babel-jest-27.5.1.tgz#a1bf8d61928edfefd21da27eb86a695bfd691444" - integrity sha512-cdQ5dXjGRd0IBRATiQ4mZGlGlRE8kJpjPOixdNRdT+m3UcNqmYWN6rK6nvtXYfY3D76cb8s/O1Ss8ea24PIwcg== - dependencies: - "@jest/transform" "^27.5.1" - "@jest/types" "^27.5.1" - "@types/babel__core" "^7.1.14" - babel-plugin-istanbul "^6.1.1" - babel-preset-jest "^27.5.1" - chalk "^4.0.0" - graceful-fs "^4.2.9" - slash "^3.0.0" - -babel-loader@^8.2.3: - version "8.3.0" - resolved "https://registry.npmjs.org/babel-loader/-/babel-loader-8.3.0.tgz#124936e841ba4fe8176786d6ff28add1f134d6a8" - integrity sha512-H8SvsMF+m9t15HNLMipppzkC+Y2Yq+v3SonZyU70RBL/h1gxPkH08Ot8pEE9Z4Kd+czyWJClmFS8qzIP9OZ04Q== - dependencies: - find-cache-dir "^3.3.1" - loader-utils "^2.0.0" - make-dir "^3.1.0" - schema-utils "^2.6.5" - -babel-plugin-istanbul@^6.1.1: - version "6.1.1" - resolved "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz#fa88ec59232fd9b4e36dbbc540a8ec9a9b47da73" - integrity sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA== - dependencies: - "@babel/helper-plugin-utils" "^7.0.0" - "@istanbuljs/load-nyc-config" "^1.0.0" - "@istanbuljs/schema" "^0.1.2" - istanbul-lib-instrument "^5.0.4" - test-exclude "^6.0.0" - -babel-plugin-jest-hoist@^27.5.1: - version "27.5.1" - resolved "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-27.5.1.tgz#9be98ecf28c331eb9f5df9c72d6f89deb8181c2e" - integrity sha512-50wCwD5EMNW4aRpOwtqzyZHIewTYNxLA4nhB+09d8BIssfNfzBRhkBIHiaPv1Si226TQSvp8gxAJm2iY2qs2hQ== - dependencies: - "@babel/template" "^7.3.3" - "@babel/types" "^7.3.3" - "@types/babel__core" "^7.0.0" - "@types/babel__traverse" "^7.0.6" - -babel-plugin-macros@^3.1.0: - version "3.1.0" - resolved "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz#9ef6dc74deb934b4db344dc973ee851d148c50c1" - integrity sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg== - dependencies: - "@babel/runtime" "^7.12.5" - cosmiconfig "^7.0.0" - resolve "^1.19.0" - -babel-plugin-named-asset-import@^0.3.8: - version "0.3.8" - resolved "https://registry.npmjs.org/babel-plugin-named-asset-import/-/babel-plugin-named-asset-import-0.3.8.tgz#6b7fa43c59229685368683c28bc9734f24524cc2" - integrity sha512-WXiAc++qo7XcJ1ZnTYGtLxmBCVbddAml3CEXgWaBzNzLNoxtQ8AiGEFDMOhot9XjTCQbvP5E77Fj9Gk924f00Q== +axe-core@^4.6.2: + version "4.8.2" + resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.8.2.tgz#2f6f3cde40935825cf4465e3c1c9e77b240ff6ae" + integrity sha512-/dlp0fxyM3R8YW7MFzaHWXrf4zzbr0vaYb23VBFCl83R7nWNPg/yaQw2Dc8jzCMmDVLhSdzH8MjrsuIUuvX+6g== -babel-plugin-polyfill-corejs2@^0.3.3: - version "0.3.3" - resolved "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.3.tgz#5d1bd3836d0a19e1b84bbf2d9640ccb6f951c122" - integrity sha512-8hOdmFYFSZhqg2C/JgLUQ+t52o5nirNwaWM2B9LWteozwIvM14VSwdsCAUET10qT+kmySAlseadmfeeSWFCy+Q== - dependencies: - "@babel/compat-data" "^7.17.7" - "@babel/helper-define-polyfill-provider" "^0.3.3" - semver "^6.1.1" - -babel-plugin-polyfill-corejs3@^0.6.0: - version "0.6.0" - resolved "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.6.0.tgz#56ad88237137eade485a71b52f72dbed57c6230a" - integrity sha512-+eHqR6OPcBhJOGgsIar7xoAB1GcSwVUA3XjAd7HJNzOXT4wv6/H7KIdA/Nc60cvUlDbKApmqNvD1B1bzOt4nyA== +axios@^0.21.4: + version "0.21.4" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.4.tgz#c67b90dc0568e5c1cf2b0b858c43ba28e2eda575" + integrity sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg== dependencies: - "@babel/helper-define-polyfill-provider" "^0.3.3" - core-js-compat "^3.25.1" + follow-redirects "^1.14.0" -babel-plugin-polyfill-regenerator@^0.4.1: - version "0.4.1" - resolved "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.4.1.tgz#390f91c38d90473592ed43351e801a9d3e0fd747" - integrity sha512-NtQGmyQDXjQqQ+IzRkBVwEOz9lQ4zxAQZgoAYEtU9dJjnl1Oc98qnN7jcp+bE7O7aYzVpavXE3/VKXNzUbh7aw== +axobject-query@^3.1.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-3.2.1.tgz#39c378a6e3b06ca679f29138151e45b2b32da62a" + integrity sha512-jsyHu61e6N4Vbz/v18DHwWYKK0bSWLqn47eeDSKPB7m8tqMHF9YJ+mhIk2lVteyZrY8tnSj/jHOv4YiTCuCJgg== dependencies: - "@babel/helper-define-polyfill-provider" "^0.3.3" - -"babel-plugin-styled-components@>= 1.12.0": - version "2.0.7" - resolved "https://registry.npmjs.org/babel-plugin-styled-components/-/babel-plugin-styled-components-2.0.7.tgz#c81ef34b713f9da2b7d3f5550df0d1e19e798086" - integrity sha512-i7YhvPgVqRKfoQ66toiZ06jPNA3p6ierpfUuEWxNF+fV27Uv5gxBkf8KZLHUCc1nFA9j6+80pYoIpqCeyW3/bA== - dependencies: - "@babel/helper-annotate-as-pure" "^7.16.0" - "@babel/helper-module-imports" "^7.16.0" - babel-plugin-syntax-jsx "^6.18.0" - lodash "^4.17.11" - picomatch "^2.3.0" - -babel-plugin-syntax-jsx@^6.18.0: - version "6.18.0" - resolved "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz#0af32a9a6e13ca7a3fd5069e62d7b0f58d0d8946" - integrity sha512-qrPaCSo9c8RHNRHIotaufGbuOBN8rtdC4QrrFFc43vyWCCz7Kl7GL1PGaXtMGQZUXrkCjNEgxDfmAuAabr/rlw== - -babel-plugin-transform-react-remove-prop-types@^0.4.24: - version "0.4.24" - resolved "https://registry.npmjs.org/babel-plugin-transform-react-remove-prop-types/-/babel-plugin-transform-react-remove-prop-types-0.4.24.tgz#f2edaf9b4c6a5fbe5c1d678bfb531078c1555f3a" - integrity sha512-eqj0hVcJUR57/Ug2zE1Yswsw4LhuqqHhD+8v120T1cl3kjg76QwtyBrdIk4WVwK+lAhBJVYCd/v+4nc4y+8JsA== - -babel-preset-current-node-syntax@^1.0.0: - version "1.0.1" - resolved "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz#b4399239b89b2a011f9ddbe3e4f401fc40cff73b" - integrity sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ== - dependencies: - "@babel/plugin-syntax-async-generators" "^7.8.4" - "@babel/plugin-syntax-bigint" "^7.8.3" - "@babel/plugin-syntax-class-properties" "^7.8.3" - "@babel/plugin-syntax-import-meta" "^7.8.3" - "@babel/plugin-syntax-json-strings" "^7.8.3" - "@babel/plugin-syntax-logical-assignment-operators" "^7.8.3" - "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" - "@babel/plugin-syntax-numeric-separator" "^7.8.3" - "@babel/plugin-syntax-object-rest-spread" "^7.8.3" - "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" - "@babel/plugin-syntax-optional-chaining" "^7.8.3" - "@babel/plugin-syntax-top-level-await" "^7.8.3" - -babel-preset-jest@^27.5.1: - version "27.5.1" - resolved "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-27.5.1.tgz#91f10f58034cb7989cb4f962b69fa6eef6a6bc81" - integrity sha512-Nptf2FzlPCWYuJg41HBqXVT8ym6bXOevuCTbhxlUpjwtysGaIWFvDEjp4y+G7fl13FgOdjs7P/DmErqH7da0Ag== - dependencies: - babel-plugin-jest-hoist "^27.5.1" - babel-preset-current-node-syntax "^1.0.0" - -babel-preset-react-app@^10.0.1: - version "10.0.1" - resolved "https://registry.npmjs.org/babel-preset-react-app/-/babel-preset-react-app-10.0.1.tgz#ed6005a20a24f2c88521809fa9aea99903751584" - integrity sha512-b0D9IZ1WhhCWkrTXyFuIIgqGzSkRIH5D5AmB0bXbzYAB1OBAwHcUeyWW2LorutLWF5btNo/N7r/cIdmvvKJlYg== - dependencies: - "@babel/core" "^7.16.0" - "@babel/plugin-proposal-class-properties" "^7.16.0" - "@babel/plugin-proposal-decorators" "^7.16.4" - "@babel/plugin-proposal-nullish-coalescing-operator" "^7.16.0" - "@babel/plugin-proposal-numeric-separator" "^7.16.0" - "@babel/plugin-proposal-optional-chaining" "^7.16.0" - "@babel/plugin-proposal-private-methods" "^7.16.0" - "@babel/plugin-transform-flow-strip-types" "^7.16.0" - "@babel/plugin-transform-react-display-name" "^7.16.0" - "@babel/plugin-transform-runtime" "^7.16.4" - "@babel/preset-env" "^7.16.4" - "@babel/preset-react" "^7.16.0" - "@babel/preset-typescript" "^7.16.0" - "@babel/runtime" "^7.16.3" - babel-plugin-macros "^3.1.0" - babel-plugin-transform-react-remove-prop-types "^0.4.24" - -babel-runtime@^6.26.0: - version "6.26.0" - resolved "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe" - integrity sha512-ITKNuq2wKlW1fJg9sSW52eepoYgZBggvOAHC0u/CYu/qxQ9EVzThCgR69BnSXLHjy2f7SY5zaQ4yt7H9ZVxY2g== - dependencies: - core-js "^2.4.0" - regenerator-runtime "^0.11.0" + dequal "^2.0.3" backo2@^1.0.2: version "1.0.2" @@ -3642,278 +1752,237 @@ backo2@^1.0.2: balanced-match@^1.0.0: version "1.0.2" - resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== -batch@0.6.1: - version "0.6.1" - resolved "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz#dc34314f4e679318093fc760272525f94bf25c16" - integrity sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw== - -bfj@^7.0.2: - version "7.0.2" - resolved "https://registry.npmjs.org/bfj/-/bfj-7.0.2.tgz#1988ce76f3add9ac2913fd8ba47aad9e651bfbb2" - integrity sha512-+e/UqUzwmzJamNF50tBV6tZPTORow7gQ96iFow+8b562OdMpEK0BcJEq2OSPEDmAbSMBQ7PKZ87ubFkgxpYWgw== +base-x@^3.0.2: + version "3.0.9" + resolved "https://registry.yarnpkg.com/base-x/-/base-x-3.0.9.tgz#6349aaabb58526332de9f60995e548a53fe21320" + integrity sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ== dependencies: - bluebird "^3.5.5" - check-types "^11.1.1" - hoopy "^0.1.4" - tryer "^1.0.1" + safe-buffer "^5.0.1" -big.js@^5.2.2: - version "5.2.2" - resolved "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" - integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ== +base-x@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/base-x/-/base-x-4.0.0.tgz#d0e3b7753450c73f8ad2389b5c018a4af7b2224a" + integrity sha512-FuwxlW4H5kh37X/oW59pwTzzTKRzfrrQwhmyspRM7swOEZcHtDZSCt45U6oKgtuFE+WYPblePMVIPR4RZrh/hw== + +base64-js@^1.3.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== + +big-integer@^1.6.44: + version "1.6.51" + resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.51.tgz#0df92a5d9880560d3ff2d5fd20245c889d130686" + integrity sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg== + +bignumber.js@^9.1.1, bignumber.js@^9.1.2: + version "9.1.2" + resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.1.2.tgz#b7c4242259c008903b13707983b5f4bbd31eda0c" + integrity sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug== binary-extensions@^2.0.0: version "2.2.0" - resolved "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== -bluebird@^3.5.5: - version "3.7.2" - resolved "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" - integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== +"bip32-ed25519@https://github.com/Zondax/bip32-ed25519": + version "0.0.4" + resolved "https://github.com/Zondax/bip32-ed25519#0949df01b5c93885339bc28116690292088f6134" + dependencies: + bn.js "^5.1.1" + elliptic "^6.4.1" + hash.js "^1.1.7" + +bip32@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/bip32/-/bip32-4.0.0.tgz#7fac3c05072188d2d355a4d6596b37188f06aa2f" + integrity sha512-aOGy88DDlVUhspIXJN+dVEtclhIsfAUppD43V0j40cPTld3pv/0X/MlrZSZ6jowIaQQzFwP8M6rFU2z2mVYjDQ== + dependencies: + "@noble/hashes" "^1.2.0" + "@scure/base" "^1.1.1" + typeforce "^1.11.5" + wif "^2.0.6" + +bip39@^3.0.4: + version "3.1.0" + resolved "https://registry.yarnpkg.com/bip39/-/bip39-3.1.0.tgz#c55a418deaf48826a6ceb34ac55b3ee1577e18a3" + integrity sha512-c9kiwdk45Do5GL0vJMe7tS95VjCii65mYAH7DfWl3uW8AVzXKQVUm64i3hzVybBDMp9r7j9iNxR85+ul8MdN/A== + dependencies: + "@noble/hashes" "^1.2.0" + +blakejs@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/blakejs/-/blakejs-1.2.1.tgz#5057e4206eadb4a97f7c0b6e197a505042fc3814" + integrity sha512-QXUSXI3QVc/gJME0dBpXrag1kbzOqCjCX8/b54ntNyW6sjtoqxqRk3LTmXzaJoh71zMsDCjM+47jS7XiwN/+fQ== + +bn.js@^4.11.9: + version "4.12.0" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88" + integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA== -bn.js@^5.2.1: +bn.js@^5.1.1, bn.js@^5.2.1: version "5.2.1" - resolved "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz#0bc527a6a0d18d0aa8d5b0538ce4a77dccfa7b70" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.1.tgz#0bc527a6a0d18d0aa8d5b0538ce4a77dccfa7b70" integrity sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ== -body-parser@1.20.1: - version "1.20.1" - resolved "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz#b1812a8912c195cd371a3ee5e66faa2338a5c668" - integrity sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw== - dependencies: - bytes "3.1.2" - content-type "~1.0.4" - debug "2.6.9" - depd "2.0.0" - destroy "1.2.0" - http-errors "2.0.0" - iconv-lite "0.4.24" - on-finished "2.4.1" - qs "6.11.0" - raw-body "2.5.1" - type-is "~1.6.18" - unpipe "1.0.0" - -bonjour-service@^1.0.11: - version "1.0.14" - resolved "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.0.14.tgz#c346f5bc84e87802d08f8d5a60b93f758e514ee7" - integrity sha512-HIMbgLnk1Vqvs6B4Wq5ep7mxvj9sGz5d1JJyDNSGNIdA/w2MCz6GTjWTdjqOJV1bEPj+6IkxDvWNFKEBxNt4kQ== - dependencies: - array-flatten "^2.1.2" - dns-equal "^1.0.0" - fast-deep-equal "^3.1.3" - multicast-dns "^7.2.5" - -boolbase@^1.0.0, boolbase@~1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" - integrity sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww== +bplist-parser@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/bplist-parser/-/bplist-parser-0.2.0.tgz#43a9d183e5bf9d545200ceac3e712f79ebbe8d0e" + integrity sha512-z0M+byMThzQmD9NILRniCUXYsYpjwnlO8N5uCFaCqIOpqRsJCrQL9NK3JsD67CN5a08nF5oIL2bD6loTdHOuKw== + dependencies: + big-integer "^1.6.44" brace-expansion@^1.1.7: version "1.1.11" - resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== dependencies: balanced-match "^1.0.0" concat-map "0.0.1" -brace-expansion@^2.0.1: - version "2.0.1" - resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" - integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== - dependencies: - balanced-match "^1.0.0" - braces@^3.0.2, braces@~3.0.2: version "3.0.2" - resolved "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== dependencies: fill-range "^7.0.1" -browser-process-hrtime@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz#3c9b4b7d782c8121e56f10106d84c0d0ffc94626" - integrity sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow== +brorand@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" + integrity sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w== -browserslist@^4.0.0, browserslist@^4.14.5, browserslist@^4.16.6, browserslist@^4.18.1, browserslist@^4.21.3, browserslist@^4.21.4: - version "4.21.4" - resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.21.4.tgz#e7496bbc67b9e39dd0f98565feccdcb0d4ff6987" - integrity sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw== +browserslist@^4.21.9: + version "4.22.1" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.22.1.tgz#ba91958d1a59b87dab6fed8dfbcb3da5e2e9c619" + integrity sha512-FEVc202+2iuClEhZhrWy6ZiAcRLvNMyYcxZ8raemul1DYVOVdFsbqckWLdsixQZCpJlwe77Z3UTalE7jsjnKfQ== dependencies: - caniuse-lite "^1.0.30001400" - electron-to-chromium "^1.4.251" - node-releases "^2.0.6" - update-browserslist-db "^1.0.9" + caniuse-lite "^1.0.30001541" + electron-to-chromium "^1.4.535" + node-releases "^2.0.13" + update-browserslist-db "^1.0.13" -bser@2.1.1: - version "2.1.1" - resolved "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05" - integrity sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ== +bs58@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/bs58/-/bs58-4.0.1.tgz#be161e76c354f6f788ae4071f63f34e8c4f0a42a" + integrity sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw== dependencies: - node-int64 "^0.4.0" + base-x "^3.0.2" -buffer-from@^1.0.0: - version "1.1.2" - resolved "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" - integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== +bs58@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/bs58/-/bs58-5.0.0.tgz#865575b4d13c09ea2a84622df6c8cbeb54ffc279" + integrity sha512-r+ihvQJvahgYT50JD05dyJNKlmmSlMoOGwn1lCcEzanPglg7TxYjioQUYehQ9mAR/+hOSd2jRc/Z2y5UxBymvQ== + dependencies: + base-x "^4.0.0" -bufferutil@^4.0.1: - version "4.0.7" - resolved "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.7.tgz#60c0d19ba2c992dd8273d3f73772ffc894c153ad" - integrity sha512-kukuqc39WOHtdxtw4UScxF/WVnMFVSQVKhtx3AjZJzhd0RGZZldcrfSEbVsWWe6KNH253574cq5F+wpv0G9pJw== +bs58check@<3.0.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/bs58check/-/bs58check-2.1.2.tgz#53b018291228d82a5aa08e7d796fdafda54aebfc" + integrity sha512-0TS1jicxdU09dwJMNZtVAfzPi6Q6QeN0pM1Fkzrjn+XYHvzMKPU3pHVpva+769iNVSfIYWf7LJ6WR+BuuMf8cA== dependencies: - node-gyp-build "^4.3.0" + bs58 "^4.0.0" + create-hash "^1.1.0" + safe-buffer "^5.1.2" -builtin-modules@^3.1.0: - version "3.3.0" - resolved "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz#cae62812b89801e9656336e46223e030386be7b6" - integrity sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw== +buffer@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6" + integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.2.1" -bytes@3.0.0: +bundle-name@^3.0.0: version "3.0.0" - resolved "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" - integrity sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw== + resolved "https://registry.yarnpkg.com/bundle-name/-/bundle-name-3.0.0.tgz#ba59bcc9ac785fb67ccdbf104a2bf60c099f0e1a" + integrity sha512-PKA4BeSvBpQKQ8iPOGCSiell+N8P+Tf1DlwqmYhpe2gAhKPHn8EYOxVT+ShuGmhg8lN8XiSlS80yiExKXrURlw== + dependencies: + run-applescript "^5.0.0" -bytes@3.1.2: - version "3.1.2" - resolved "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" - integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== +cac@^6.7.14: + version "6.7.14" + resolved "https://registry.yarnpkg.com/cac/-/cac-6.7.14.tgz#804e1e6f506ee363cb0e3ccbb09cad5dd9870959" + integrity sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ== -call-bind@^1.0.0, call-bind@^1.0.2: - version "1.0.2" - resolved "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" - integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== +call-bind@^1.0.0, call-bind@^1.0.2, call-bind@^1.0.4, call-bind@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.5.tgz#6fa2b7845ce0ea49bf4d8b9ef64727a2c2e2e513" + integrity sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ== dependencies: - function-bind "^1.1.1" - get-intrinsic "^1.0.2" + function-bind "^1.1.2" + get-intrinsic "^1.2.1" + set-function-length "^1.1.1" callsites@^3.0.0: version "3.1.0" - resolved "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== -camel-case@^4.1.2: - version "4.1.2" - resolved "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz#9728072a954f805228225a6deea6b38461e1bd5a" - integrity sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw== - dependencies: - pascal-case "^3.1.2" - tslib "^2.0.3" - -camelcase-css@^2.0.1: - version "2.0.1" - resolved "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz#ee978f6947914cc30c6b44741b6ed1df7f043fd5" - integrity sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA== - -camelcase@^5.3.1: - version "5.3.1" - resolved "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" - integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== - -camelcase@^6.2.0, camelcase@^6.2.1: - version "6.3.0" - resolved "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" - integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== - -camelize@^1.0.0: - version "1.0.1" - resolved "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz#89b7e16884056331a35d6b5ad064332c91daa6c3" - integrity sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ== - -caniuse-api@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz#5e4d90e2274961d46291997df599e3ed008ee4c0" - integrity sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw== - dependencies: - browserslist "^4.0.0" - caniuse-lite "^1.0.0" - lodash.memoize "^4.1.2" - lodash.uniq "^4.5.0" - -caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001400, caniuse-lite@^1.0.30001426: - version "1.0.30001434" - resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001434.tgz#ec1ec1cfb0a93a34a0600d37903853030520a4e5" - integrity sha512-aOBHrLmTQw//WFa2rcF1If9fa3ypkC1wzqqiKHgfdrXTWcU8C4gKVZT77eQAPWN1APys3+uQ0Df07rKauXGEYA== - -canvas-renderer@~2.2.0: - version "2.2.1" - resolved "https://registry.npmjs.org/canvas-renderer/-/canvas-renderer-2.2.1.tgz#c1d131f78a9799aca8af9679ad0a005052b65550" - integrity sha512-RrBgVL5qCEDIXpJ6NrzyRNoTnXxYarqm/cS/W6ERhUJts5UQtt/XPEosGN3rqUkZ4fjBArlnCbsISJ+KCFnIAg== - dependencies: - "@types/node" "*" +camelcase@^6.2.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" + integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== -case-sensitive-paths-webpack-plugin@^2.4.0: - version "2.4.0" - resolved "https://registry.npmjs.org/case-sensitive-paths-webpack-plugin/-/case-sensitive-paths-webpack-plugin-2.4.0.tgz#db64066c6422eed2e08cc14b986ca43796dbc6d4" - integrity sha512-roIFONhcxog0JSSWbvVAh3OocukmSgpqOH6YpMkCvav/ySIV3JKg4Dc8vYtQjYi/UxpNE36r/9v+VqTQqgkYmw== +camelize@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/camelize/-/camelize-1.0.1.tgz#89b7e16884056331a35d6b5ad064332c91daa6c3" + integrity sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ== -chalk@^2.0.0, chalk@^2.4.1: +caniuse-lite@^1.0.30001541: + version "1.0.30001555" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001555.tgz#e36f4d49e345337d6788f32093867cec8d951789" + integrity sha512-NzbUFKUnJ3DTcq6YyZB6+qqhfD112uR3uoEnkmfzm2wVzUNsFkU7AwBjKQ654Sp5cau0JxhFyRSn/tQZ+XfygA== + +chai@^4.3.10: + version "4.3.10" + resolved "https://registry.yarnpkg.com/chai/-/chai-4.3.10.tgz#d784cec635e3b7e2ffb66446a63b4e33bd390384" + integrity sha512-0UXG04VuVbruMUYbJ6JctvH0YnC/4q3/AkT18q4NaITo91CUm0liMS9VqzT9vZhVQ/1eqPanMWjBM+Juhfb/9g== + dependencies: + assertion-error "^1.1.0" + check-error "^1.0.3" + deep-eql "^4.1.3" + get-func-name "^2.0.2" + loupe "^2.3.6" + pathval "^1.1.1" + type-detect "^4.0.8" + +chalk@^2.4.2: version "2.4.2" - resolved "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== dependencies: ansi-styles "^3.2.1" escape-string-regexp "^1.0.5" supports-color "^5.3.0" -chalk@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4" - integrity sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg== - dependencies: - ansi-styles "^4.1.0" - supports-color "^7.1.0" - -chalk@^4.0.0, chalk@^4.0.2, chalk@^4.1.0, chalk@^4.1.2: +chalk@^4.0.0, chalk@^4.1.1: version "4.1.2" - resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== dependencies: ansi-styles "^4.1.0" supports-color "^7.1.0" -char-regex@^1.0.2: - version "1.0.2" - resolved "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" - integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== - -char-regex@^2.0.0: - version "2.0.1" - resolved "https://registry.npmjs.org/char-regex/-/char-regex-2.0.1.tgz#6dafdb25f9d3349914079f010ba8d0e6ff9cd01e" - integrity sha512-oSvEeo6ZUD7NepqAat3RqoucZ5SeqLJgOvVIwkafu6IP3V0pO38s/ypdVUmDDK6qIIHNlYHJAKX9E7R7HoKElw== - -chart.js@^4.0.1: - version "4.0.1" - resolved "https://registry.npmjs.org/chart.js/-/chart.js-4.0.1.tgz#93d5d50ac222a5b3b6ac7488e82e1553ac031592" - integrity sha512-5/8/9eBivwBZK81mKvmIwTb2Pmw4D/5h1RK9fBWZLLZ8mCJ+kfYNmV9rMrGoa5Hgy2/wVDBMLSUDudul2/9ihA== - -chartjs-plugin-datalabels@^2.1.0: - version "2.1.0" - resolved "https://registry.npmjs.org/chartjs-plugin-datalabels/-/chartjs-plugin-datalabels-2.1.0.tgz#d42f05a0481831b2d79fb8c0521f8fa56e1e6db1" - integrity sha512-WA6R4saSlY6mnyX78SkbSo2gGc+cj87lFi5zBrsjjYxE76JgXyxHa1OTodVCzRPoqeYJqSEOffeJ/897kRHR6w== - -che-react-number-easing@^0.1.4: - version "0.1.4" - resolved "https://registry.npmjs.org/che-react-number-easing/-/che-react-number-easing-0.1.4.tgz#4dfc190b63d2503b31bf08fbedc120790797333c" - integrity sha512-88jsYH8ht3DsnpE3gneGZ/0cuPaPB1sDMxFs71MVTyZWWjChDaJnYZXt1ZnAlrFlTqf1ARR1s6LYXAeVyawCJg== +chart.js@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/chart.js/-/chart.js-4.4.0.tgz#df843fdd9ec6bd88d7f07e2b95348d221bd2698c" + integrity sha512-vQEj6d+z0dcsKLlQvbKIMYFHd3t8W/7L2vfJIbYcfyPcRx92CsHqECpueN8qVGNlKyDcr5wBrYAYKnfu/9Q1hQ== dependencies: - eases "^1.0.8" - prop-types "^15.7.2" - react "^16.13.1" + "@kurkle/color" "^0.3.0" -check-types@^11.1.1: - version "11.2.2" - resolved "https://registry.npmjs.org/check-types/-/check-types-11.2.2.tgz#7afc0b6a860d686885062f2dba888ba5710335b4" - integrity sha512-HBiYvXvn9Z70Z88XKjz3AEKd4HJhBXsa3j7xFnITAzoS8+q6eIGi8qDB8FKPBAjtuxjI/zFpwuiCb8oDtKOYrA== +check-error@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.3.tgz#a6502e4312a7ee969f646e83bb3ddd56281bd694" + integrity sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg== + dependencies: + get-func-name "^2.0.2" -chokidar@^3.4.2, chokidar@^3.5.3: +"chokidar@>=3.0.0 <4.0.0", chokidar@^3.5.1: version "3.5.3" - resolved "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== dependencies: anymatch "~3.1.2" @@ -3926,1039 +1995,453 @@ chokidar@^3.4.2, chokidar@^3.5.3: optionalDependencies: fsevents "~2.3.2" -chrome-trace-event@^1.0.2: - version "1.0.3" - resolved "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz#1015eced4741e15d06664a957dbbf50d041e26ac" - integrity sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg== - -ci-info@^3.2.0: - version "3.7.0" - resolved "https://registry.npmjs.org/ci-info/-/ci-info-3.7.0.tgz#6d01b3696c59915b6ce057e4aa4adfc2fa25f5ef" - integrity sha512-2CpRNYmImPx+RXKLq6jko/L07phmS9I02TyqkcNU20GCF/GgaWvc58hPtjxDX8lPpkdwc9sNh72V9k00S7ezog== - -cjs-module-lexer@^1.0.0: - version "1.2.2" - resolved "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz#9f84ba3244a512f3a54e5277e8eef4c489864e40" - integrity sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA== +chroma-js@^2.4.2: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chroma-js/-/chroma-js-2.4.2.tgz#dffc214ed0c11fa8eefca2c36651d8e57cbfb2b0" + integrity sha512-U9eDw6+wt7V8z5NncY2jJfZa+hUH8XEj8FQHgFJTrUFnJfXYf4Ml4adI2vXZOjqRDpFWtYVWypDfZwnJ+HIR4A== -clean-css@^5.2.2: - version "5.3.1" - resolved "https://registry.npmjs.org/clean-css/-/clean-css-5.3.1.tgz#d0610b0b90d125196a2894d35366f734e5d7aa32" - integrity sha512-lCr8OHhiWCTw4v8POJovCoh4T7I9U11yVsPjMWWnnMmp9ZowCxyad1Pathle/9HjaDp+fdQKjO9fQydE6RHTZg== +cipher-base@^1.0.1: + version "1.0.4" + resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.4.tgz#8760e4ecc272f4c363532f926d874aae2c1397de" + integrity sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q== dependencies: - source-map "~0.6.0" + inherits "^2.0.1" + safe-buffer "^5.0.1" + +classnames@^2.2.5: + version "2.3.2" + resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.2.tgz#351d813bf0137fcc6a76a16b88208d2560a0d924" + integrity sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw== -cliui@^7.0.2: - version "7.0.4" - resolved "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" - integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== +cliui@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" + integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== dependencies: string-width "^4.2.0" - strip-ansi "^6.0.0" + strip-ansi "^6.0.1" wrap-ansi "^7.0.0" -co@^4.6.0: - version "4.6.0" - resolved "https://registry.npmjs.org/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" - integrity sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ== - -coa@^2.0.2: - version "2.0.2" - resolved "https://registry.npmjs.org/coa/-/coa-2.0.2.tgz#43f6c21151b4ef2bf57187db0d73de229e3e7ec3" - integrity sha512-q5/jG+YQnSy4nRTV4F7lPepBJZ8qBNJJDBuJdoejDyLXgmL7IEo+Le2JDZudFTFt7mrCqIRaSjws4ygRCTCAXA== - dependencies: - "@types/q" "^1.5.1" - chalk "^2.4.1" - q "^1.1.2" - -collect-v8-coverage@^1.0.0: - version "1.0.1" - resolved "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz#cc2c8e94fc18bbdffe64d6534570c8a673b27f59" - integrity sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg== - -color-convert@^1.9.0, color-convert@^1.9.3: +color-convert@^1.9.0: version "1.9.3" - resolved "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== dependencies: color-name "1.1.3" color-convert@^2.0.1: version "2.0.1" - resolved "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== dependencies: color-name "~1.1.4" color-name@1.1.3: version "1.1.3" - resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== -color-name@^1.0.0, color-name@^1.1.4, color-name@~1.1.4: +color-name@~1.1.4: version "1.1.4" - resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== -color-string@^1.6.0: - version "1.9.1" - resolved "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz#4467f9146f036f855b764dfb5bf8582bf342c7a4" - integrity sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg== - dependencies: - color-name "^1.0.0" - simple-swizzle "^0.2.2" - -color@^3.2.1: - version "3.2.1" - resolved "https://registry.npmjs.org/color/-/color-3.2.1.tgz#3544dc198caf4490c3ecc9a790b54fe9ff45e164" - integrity sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA== - dependencies: - color-convert "^1.9.3" - color-string "^1.6.0" - -colord@^2.9.1: - version "2.9.3" - resolved "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz#4f8ce919de456f1d5c1c368c307fe20f3e59fb43" - integrity sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw== - -colorette@^2.0.10: - version "2.0.19" - resolved "https://registry.npmjs.org/colorette/-/colorette-2.0.19.tgz#cdf044f47ad41a0f4b56b3a0d5b4e6e1a2d5a798" - integrity sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ== - -combined-stream@^1.0.8: - version "1.0.8" - resolved "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" - integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== - dependencies: - delayed-stream "~1.0.0" - -commander@^2.18.0, commander@^2.20.0: - version "2.20.3" - resolved "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" - integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== - -commander@^7.2.0: - version "7.2.0" - resolved "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7" - integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== +commander@^11.0.0: + version "11.1.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-11.1.0.tgz#62fdce76006a68e5c1ab3314dc92e800eb83d906" + integrity sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ== -commander@^8.3.0: +commander@^8.0.0: version "8.3.0" - resolved "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66" + resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66" integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww== -common-path-prefix@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/common-path-prefix/-/common-path-prefix-3.0.0.tgz#7d007a7e07c58c4b4d5f433131a19141b29f11e0" - integrity sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w== - -common-tags@^1.8.0: - version "1.8.2" - resolved "https://registry.npmjs.org/common-tags/-/common-tags-1.8.2.tgz#94ebb3c076d26032745fd54face7f688ef5ac9c6" - integrity sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA== - commondir@^1.0.1: version "1.0.1" - resolved "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" + resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" integrity sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg== -compressible@~2.0.16: - version "2.0.18" - resolved "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz#af53cca6b070d4c3c0750fbd77286a6d7cc46fba" - integrity sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg== - dependencies: - mime-db ">= 1.43.0 < 2" - -compression@^1.7.4: - version "1.7.4" - resolved "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz#95523eff170ca57c29a0ca41e6fe131f41e5bb8f" - integrity sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ== - dependencies: - accepts "~1.3.5" - bytes "3.0.0" - compressible "~2.0.16" - debug "2.6.9" - on-headers "~1.0.2" - safe-buffer "5.1.2" - vary "~1.1.2" - -compute-scroll-into-view@^1.0.17: - version "1.0.17" - resolved "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-1.0.17.tgz#6a88f18acd9d42e9cf4baa6bec7e0522607ab7ab" - integrity sha512-j4dx+Fb0URmzbwwMUrhqWM2BEWHdFGx+qZ9qqASHRPqvTYdqvWnHg0H1hIbcyLnvgnoNAVMlwkepyqM3DaIFUg== - concat-map@0.0.1: version "0.0.1" - resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== -confusing-browser-globals@^1.0.10, confusing-browser-globals@^1.0.11: +confusing-browser-globals@^1.0.10: version "1.0.11" - resolved "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz#ae40e9b57cdd3915408a2805ebd3a5585608dc81" + resolved "https://registry.yarnpkg.com/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz#ae40e9b57cdd3915408a2805ebd3a5585608dc81" integrity sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA== -connect-history-api-fallback@^2.0.0: +convert-source-map@^2.0.0: version "2.0.0" - resolved "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz#647264845251a0daf25b97ce87834cace0f5f1c8" - integrity sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA== - -content-disposition@0.5.4: - version "0.5.4" - resolved "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" - integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ== - dependencies: - safe-buffer "5.2.1" - -content-type@~1.0.4: - version "1.0.4" - resolved "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" - integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" + integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== -convert-source-map@^1.4.0, convert-source-map@^1.6.0, convert-source-map@^1.7.0: - version "1.9.0" - resolved "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz#7faae62353fb4213366d0ca98358d22e8368b05f" - integrity sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A== - -cookie-signature@1.0.6: - version "1.0.6" - resolved "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" - integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ== - -cookie@0.5.0: - version "0.5.0" - resolved "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b" - integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw== - -copy-to-clipboard@^3.3.1: - version "3.3.3" - resolved "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz#55ac43a1db8ae639a4bd99511c148cdd1b83a1b0" - integrity sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA== - dependencies: - toggle-selection "^1.0.6" - -core-js-compat@^3.25.1: - version "3.26.1" - resolved "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.26.1.tgz#0e710b09ebf689d719545ac36e49041850f943df" - integrity sha512-622/KzTudvXCDLRw70iHW4KKs1aGpcRcowGWyYJr2DEBfRrd6hNJybxSWJFuZYD4ma86xhrwDDHxmDaIq4EA8A== - dependencies: - browserslist "^4.21.4" - -core-js-pure@^3.23.3, core-js-pure@^3.25.1: - version "3.26.1" - resolved "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.26.1.tgz#653f4d7130c427820dcecd3168b594e8bb095a33" - integrity sha512-VVXcDpp/xJ21KdULRq/lXdLzQAtX7+37LzpyfFM973il0tWSsDEoyzG38G14AjTpK9VTfiNM9jnFauq/CpaWGQ== - -core-js@^2.4.0: - version "2.6.12" - resolved "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz#d9333dfa7b065e347cc5682219d6f690859cc2ec" - integrity sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ== - -core-js@^3.19.2: - version "3.26.1" - resolved "https://registry.npmjs.org/core-js/-/core-js-3.26.1.tgz#7a9816dabd9ee846c1c0fe0e8fcad68f3709134e" - integrity sha512-21491RRQVzUn0GGM9Z1Jrpr6PNPxPi+Za8OM9q4tksTSnlbXXGKK1nXNg/QvwFYettXvSX6zWKCtHHfjN4puyA== - -core-util-is@~1.0.0: - version "1.0.3" - resolved "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" - integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== - -cosmiconfig@^6.0.0: - version "6.0.0" - resolved "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz#da4fee853c52f6b1e6935f41c1a2fc50bd4a9982" - integrity sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg== +cosmiconfig@^8.1.3: + version "8.3.6" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-8.3.6.tgz#060a2b871d66dba6c8538ea1118ba1ac16f5fae3" + integrity sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA== dependencies: - "@types/parse-json" "^4.0.0" - import-fresh "^3.1.0" - parse-json "^5.0.0" + import-fresh "^3.3.0" + js-yaml "^4.1.0" + parse-json "^5.2.0" path-type "^4.0.0" - yaml "^1.7.2" -cosmiconfig@^7.0.0: - version "7.1.0" - resolved "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz#1443b9afa596b670082ea46cbd8f6a62b84635f6" - integrity sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA== +create-hash@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.2.0.tgz#889078af11a63756bcfb59bd221996be3a9ef196" + integrity sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg== dependencies: - "@types/parse-json" "^4.0.0" - import-fresh "^3.2.1" - parse-json "^5.0.0" - path-type "^4.0.0" - yaml "^1.10.0" + cipher-base "^1.0.1" + inherits "^2.0.1" + md5.js "^1.3.4" + ripemd160 "^2.0.1" + sha.js "^2.4.0" cross-spawn@^7.0.2, cross-spawn@^7.0.3: version "7.0.3" - resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== dependencies: path-key "^3.1.0" shebang-command "^2.0.0" which "^2.0.1" -crypto-random-string@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5" - integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA== - -css-blank-pseudo@^3.0.3: - version "3.0.3" - resolved "https://registry.npmjs.org/css-blank-pseudo/-/css-blank-pseudo-3.0.3.tgz#36523b01c12a25d812df343a32c322d2a2324561" - integrity sha512-VS90XWtsHGqoM0t4KpH053c4ehxZ2E6HtGI7x68YFV0pTo/QmkV/YFA+NnlvK8guxZVNWGQhVNJGC39Q8XF4OQ== - dependencies: - postcss-selector-parser "^6.0.9" - css-color-keywords@^1.0.0: version "1.0.0" - resolved "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz#fea2616dc676b2962686b3af8dbdbe180b244e05" + resolved "https://registry.yarnpkg.com/css-color-keywords/-/css-color-keywords-1.0.0.tgz#fea2616dc676b2962686b3af8dbdbe180b244e05" integrity sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg== -css-declaration-sorter@^6.3.1: - version "6.3.1" - resolved "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.3.1.tgz#be5e1d71b7a992433fb1c542c7a1b835e45682ec" - integrity sha512-fBffmak0bPAnyqc/HO8C3n2sHrp9wcqQz6ES9koRF2/mLOVAx9zIQ3Y7R29sYCteTPqMCwns4WYQoCX91Xl3+w== - -css-has-pseudo@^3.0.4: - version "3.0.4" - resolved "https://registry.npmjs.org/css-has-pseudo/-/css-has-pseudo-3.0.4.tgz#57f6be91ca242d5c9020ee3e51bbb5b89fc7af73" - integrity sha512-Vse0xpR1K9MNlp2j5w1pgWIJtm1a8qS0JwS9goFYcImjlHEmywP9VUF05aGBXzGpDJF86QXk4L0ypBmwPhGArw== - dependencies: - postcss-selector-parser "^6.0.9" - -css-loader@^6.5.1: - version "6.7.2" - resolved "https://registry.npmjs.org/css-loader/-/css-loader-6.7.2.tgz#26bc22401b5921686a10fbeba75d124228302304" - integrity sha512-oqGbbVcBJkm8QwmnNzrFrWTnudnRZC+1eXikLJl0n4ljcfotgRifpg2a1lKy8jTrc4/d9A/ap1GFq1jDKG7J+Q== - dependencies: - icss-utils "^5.1.0" - postcss "^8.4.18" - postcss-modules-extract-imports "^3.0.0" - postcss-modules-local-by-default "^4.0.0" - postcss-modules-scope "^3.0.0" - postcss-modules-values "^4.0.0" - postcss-value-parser "^4.2.0" - semver "^7.3.8" - -css-minimizer-webpack-plugin@^3.2.0: - version "3.4.1" - resolved "https://registry.npmjs.org/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-3.4.1.tgz#ab78f781ced9181992fe7b6e4f3422e76429878f" - integrity sha512-1u6D71zeIfgngN2XNRJefc/hY7Ybsxd74Jm4qngIXyUEk7fss3VUzuHxLAq/R8NAba4QU9OUSaMZlbpRc7bM4Q== - dependencies: - cssnano "^5.0.6" - jest-worker "^27.0.2" - postcss "^8.3.5" - schema-utils "^4.0.0" - serialize-javascript "^6.0.0" - source-map "^0.6.1" - -css-prefers-color-scheme@^6.0.3: - version "6.0.3" - resolved "https://registry.npmjs.org/css-prefers-color-scheme/-/css-prefers-color-scheme-6.0.3.tgz#ca8a22e5992c10a5b9d315155e7caee625903349" - integrity sha512-4BqMbZksRkJQx2zAjrokiGMd07RqOa2IxIrrN10lyBe9xhn9DEvjUK79J6jkeiv9D9hQFXKb6g1jwU62jziJZA== - -css-select-base-adapter@^0.1.1: - version "0.1.1" - resolved "https://registry.npmjs.org/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz#3b2ff4972cc362ab88561507a95408a1432135d7" - integrity sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w== - -css-select@^2.0.0: - version "2.1.0" - resolved "https://registry.npmjs.org/css-select/-/css-select-2.1.0.tgz#6a34653356635934a81baca68d0255432105dbef" - integrity sha512-Dqk7LQKpwLoH3VovzZnkzegqNSuAziQyNZUcrdDM401iY+R5NkGBXGmtO05/yaXQziALuPogeG0b7UAgjnTJTQ== - dependencies: - boolbase "^1.0.0" - css-what "^3.2.1" - domutils "^1.7.0" - nth-check "^1.0.2" - -css-select@^4.1.3: - version "4.3.0" - resolved "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz#db7129b2846662fd8628cfc496abb2b59e41529b" - integrity sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ== - dependencies: - boolbase "^1.0.0" - css-what "^6.0.1" - domhandler "^4.3.1" - domutils "^2.8.0" - nth-check "^2.0.1" - -css-to-react-native@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.0.0.tgz#62dbe678072a824a689bcfee011fc96e02a7d756" - integrity sha512-Ro1yETZA813eoyUp2GDBhG2j+YggidUmzO1/v9eYBKR2EHVEniE2MI/NqpTQ954BMpTPZFsGNPm46qFB9dpaPQ== +css-to-react-native@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/css-to-react-native/-/css-to-react-native-3.2.0.tgz#cdd8099f71024e149e4f6fe17a7d46ecd55f1e32" + integrity sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ== dependencies: camelize "^1.0.0" css-color-keywords "^1.0.0" postcss-value-parser "^4.0.2" -css-tree@1.0.0-alpha.37: - version "1.0.0-alpha.37" - resolved "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.37.tgz#98bebd62c4c1d9f960ec340cf9f7522e30709a22" - integrity sha512-DMxWJg0rnz7UgxKT0Q1HU/L9BeJI0M6ksor0OgqOnF+aRCDWg/N2641HmVyU9KVIu0OVVWOb2IpC9A+BJRnejg== - dependencies: - mdn-data "2.0.4" - source-map "^0.6.1" - -css-tree@^1.1.2, css-tree@^1.1.3: - version "1.1.3" - resolved "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz#eb4870fb6fd7707327ec95c2ff2ab09b5e8db91d" - integrity sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q== - dependencies: - mdn-data "2.0.14" - source-map "^0.6.1" - -css-what@^3.2.1: - version "3.4.2" - resolved "https://registry.npmjs.org/css-what/-/css-what-3.4.2.tgz#ea7026fcb01777edbde52124e21f327e7ae950e4" - integrity sha512-ACUm3L0/jiZTqfzRM3Hi9Q8eZqd6IK37mMWPLz9PJxkLWllYeRf+EHUSHYEtFop2Eqytaq1FizFVh7XfBnXCDQ== - -css-what@^6.0.1: - version "6.1.0" - resolved "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz#fb5effcf76f1ddea2c81bdfaa4de44e79bac70f4" - integrity sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw== - -css.escape@^1.5.1: - version "1.5.1" - resolved "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz#42e27d4fa04ae32f931a4b4d4191fa9cddee97cb" - integrity sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg== - -cssdb@^7.1.0: - version "7.1.0" - resolved "https://registry.npmjs.org/cssdb/-/cssdb-7.1.0.tgz#574f97235a83eb753a29f0b1f2cbacac0d628bb8" - integrity sha512-Sd99PrFgx28ez4GHu8yoQIufc/70h9oYowDf4EjeIKi8mac9whxRjhM3IaMr6EllP6KKKWtJrMfN6C7T9tIWvQ== - -cssesc@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" - integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== - -cssnano-preset-default@^5.2.13: - version "5.2.13" - resolved "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-5.2.13.tgz#e7353b0c57975d1bdd97ac96e68e5c1b8c68e990" - integrity sha512-PX7sQ4Pb+UtOWuz8A1d+Rbi+WimBIxJTRyBdgGp1J75VU0r/HFQeLnMYgHiCAp6AR4rqrc7Y4R+1Rjk3KJz6DQ== - dependencies: - css-declaration-sorter "^6.3.1" - cssnano-utils "^3.1.0" - postcss-calc "^8.2.3" - postcss-colormin "^5.3.0" - postcss-convert-values "^5.1.3" - postcss-discard-comments "^5.1.2" - postcss-discard-duplicates "^5.1.0" - postcss-discard-empty "^5.1.1" - postcss-discard-overridden "^5.1.0" - postcss-merge-longhand "^5.1.7" - postcss-merge-rules "^5.1.3" - postcss-minify-font-values "^5.1.0" - postcss-minify-gradients "^5.1.1" - postcss-minify-params "^5.1.4" - postcss-minify-selectors "^5.2.1" - postcss-normalize-charset "^5.1.0" - postcss-normalize-display-values "^5.1.0" - postcss-normalize-positions "^5.1.1" - postcss-normalize-repeat-style "^5.1.1" - postcss-normalize-string "^5.1.0" - postcss-normalize-timing-functions "^5.1.0" - postcss-normalize-unicode "^5.1.1" - postcss-normalize-url "^5.1.0" - postcss-normalize-whitespace "^5.1.1" - postcss-ordered-values "^5.1.3" - postcss-reduce-initial "^5.1.1" - postcss-reduce-transforms "^5.1.0" - postcss-svgo "^5.1.0" - postcss-unique-selectors "^5.1.1" - -cssnano-utils@^3.1.0: - version "3.1.0" - resolved "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-3.1.0.tgz#95684d08c91511edfc70d2636338ca37ef3a6861" - integrity sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA== - -cssnano@^5.0.6: - version "5.1.14" - resolved "https://registry.npmjs.org/cssnano/-/cssnano-5.1.14.tgz#07b0af6da73641276fe5a6d45757702ebae2eb05" - integrity sha512-Oou7ihiTocbKqi0J1bB+TRJIQX5RMR3JghA8hcWSw9mjBLQ5Y3RWqEDoYG3sRNlAbCIXpqMoZGbq5KDR3vdzgw== - dependencies: - cssnano-preset-default "^5.2.13" - lilconfig "^2.0.3" - yaml "^1.10.2" - -csso@^4.0.2, csso@^4.2.0: - version "4.2.0" - resolved "https://registry.npmjs.org/csso/-/csso-4.2.0.tgz#ea3a561346e8dc9f546d6febedd50187cf389529" - integrity sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA== - dependencies: - css-tree "^1.1.2" - -cssom@^0.4.4: - version "0.4.4" - resolved "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz#5a66cf93d2d0b661d80bf6a44fb65f5c2e4e0a10" - integrity sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw== - -cssom@~0.3.6: - version "0.3.8" - resolved "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz#9f1276f5b2b463f2114d3f2c75250af8c1a36f4a" - integrity sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg== - -cssstyle@^2.3.0: - version "2.3.0" - resolved "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz#ff665a0ddbdc31864b09647f34163443d90b0852" - integrity sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A== - dependencies: - cssom "~0.3.6" - -csstype@^3.0.2: - version "3.1.1" - resolved "https://registry.npmjs.org/csstype/-/csstype-3.1.1.tgz#841b532c45c758ee546a11d5bd7b7b473c8c30b9" - integrity sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw== - -d@1, d@^1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/d/-/d-1.0.1.tgz#8698095372d58dbee346ffd0c7093f99f8f9eb5a" - integrity sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA== - dependencies: - es5-ext "^0.10.50" - type "^1.0.1" +csstype@^3.0.2, csstype@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.2.tgz#1d4bf9d572f11c14031f0436e1c10bc1f571f50b" + integrity sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ== damerau-levenshtein@^1.0.8: version "1.0.8" - resolved "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz#b43d286ccbd36bc5b2f7ed41caf2d0aba1f8a6e7" + resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz#b43d286ccbd36bc5b2f7ed41caf2d0aba1f8a6e7" integrity sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA== data-uri-to-buffer@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.0.tgz#b5db46aea50f6176428ac05b73be39a57701a64b" - integrity sha512-Vr3mLBA8qWmcuschSLAOogKgQ/Jwxulv3RNE4FXnYWRGujzrRWQI4m12fQqRkwX06C0KanhLr4hK+GydchZsaA== - -data-urls@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz#156485a72963a970f5d5821aaf642bef2bf2db9b" - integrity sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ== - dependencies: - abab "^2.0.3" - whatwg-mimetype "^2.3.0" - whatwg-url "^8.0.0" + version "4.0.1" + resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz#d8feb2b2881e6a4f58c2e08acfd0e2834e26222e" + integrity sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A== date-fns@^2.29.3: - version "2.29.3" - resolved "https://registry.npmjs.org/date-fns/-/date-fns-2.29.3.tgz#27402d2fc67eb442b511b70bbdf98e6411cd68a8" - integrity sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA== - -debug@2.6.9, debug@^2.2.0, debug@^2.6.0, debug@^2.6.9: - version "2.6.9" - resolved "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" - integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== - dependencies: - ms "2.0.0" - -debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.4: - version "4.3.4" - resolved "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" - integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== + version "2.30.0" + resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.30.0.tgz#f367e644839ff57894ec6ac480de40cae4b0f4d0" + integrity sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw== dependencies: - ms "2.1.2" + "@babel/runtime" "^7.21.0" debug@^3.2.7: version "3.2.7" - resolved "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== dependencies: ms "^2.1.1" -decimal.js@^10.2.1: - version "10.4.2" - resolved "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.2.tgz#0341651d1d997d86065a2ce3a441fbd0d8e8b98e" - integrity sha512-ic1yEvwT6GuvaYwBLLY6/aFFgjZdySKTE8en/fkU3QICTmRtgtSlFn0u0BXN06InZwtfCelR7j8LRiDI/02iGA== - -dedent@^0.7.0: - version "0.7.0" - resolved "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c" - integrity sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA== +debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.4: + version "4.3.4" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== + dependencies: + ms "2.1.2" -deep-equal@^2.0.5: - version "2.1.0" - resolved "https://registry.npmjs.org/deep-equal/-/deep-equal-2.1.0.tgz#5ba60402cf44ab92c2c07f3f3312c3d857a0e1dd" - integrity sha512-2pxgvWu3Alv1PoWEyVg7HS8YhGlUFUV7N5oOvfL6d+7xAmLSemMwv/c8Zv/i9KFzxV5Kt5CAvQc70fLwVuf4UA== +deep-eql@^4.1.3: + version "4.1.3" + resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-4.1.3.tgz#7c7775513092f7df98d8df9996dd085eb668cc6d" + integrity sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw== dependencies: - call-bind "^1.0.2" - es-get-iterator "^1.1.2" - get-intrinsic "^1.1.3" - is-arguments "^1.1.1" - is-date-object "^1.0.5" - is-regex "^1.1.4" - isarray "^2.0.5" - object-is "^1.1.5" - object-keys "^1.1.1" - object.assign "^4.1.4" - regexp.prototype.flags "^1.4.3" - side-channel "^1.0.4" - which-boxed-primitive "^1.0.2" - which-collection "^1.0.1" - which-typed-array "^1.1.8" + type-detect "^4.0.0" -deep-is@^0.1.3, deep-is@~0.1.3: +deep-is@^0.1.3: version "0.1.4" - resolved "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== -deepmerge@^4.2.2: - version "4.2.2" - resolved "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955" - integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg== - -default-gateway@^6.0.3: - version "6.0.3" - resolved "https://registry.npmjs.org/default-gateway/-/default-gateway-6.0.3.tgz#819494c888053bdb743edbf343d6cdf7f2943a71" - integrity sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg== +default-browser-id@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/default-browser-id/-/default-browser-id-3.0.0.tgz#bee7bbbef1f4e75d31f98f4d3f1556a14cea790c" + integrity sha512-OZ1y3y0SqSICtE8DE4S8YOE9UZOJ8wO16fKWVP5J1Qz42kV9jcnMVFrEE/noXb/ss3Q4pZIH79kxofzyNNtUNA== dependencies: - execa "^5.0.0" + bplist-parser "^0.2.0" + untildify "^4.0.0" -define-lazy-prop@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz#3f7ae421129bcaaac9bc74905c98a0009ec9ee7f" - integrity sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og== +default-browser@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/default-browser/-/default-browser-4.0.0.tgz#53c9894f8810bf86696de117a6ce9085a3cbc7da" + integrity sha512-wX5pXO1+BrhMkSbROFsyxUm0i/cJEScyNhA4PPxc41ICuv05ZZB/MX28s8aZx6xjmatvebIapF6hLEKEcpneUA== + dependencies: + bundle-name "^3.0.0" + default-browser-id "^3.0.0" + execa "^7.1.1" + titleize "^3.0.0" -define-properties@^1.1.3, define-properties@^1.1.4: - version "1.1.4" - resolved "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz#0b14d7bd7fbeb2f3572c3a7eda80ea5d57fb05b1" - integrity sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA== +define-data-property@^1.0.1, define-data-property@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.1.tgz#c35f7cd0ab09883480d12ac5cb213715587800b3" + integrity sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ== dependencies: + get-intrinsic "^1.2.1" + gopd "^1.0.1" has-property-descriptors "^1.0.0" - object-keys "^1.1.1" - -defined@^1.0.0: - version "1.0.1" - resolved "https://registry.npmjs.org/defined/-/defined-1.0.1.tgz#c0b9db27bfaffd95d6f61399419b893df0f91ebf" - integrity sha512-hsBd2qSVCRE+5PmNdHt1uzyrFu5d3RwmFDKzyNZMFq/EwDNJF7Ee5+D5oEKF0hU6LhtoUF1macFvOe4AskQC1Q== - -delayed-stream@~1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" - integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== -depd@2.0.0: +define-lazy-prop@^2.0.0: version "2.0.0" - resolved "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" - integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== - -depd@~1.1.2: - version "1.1.2" - resolved "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" - integrity sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ== - -destroy@1.2.0: - version "1.2.0" - resolved "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" - integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== - -detect-newline@^3.0.0: - version "3.1.0" - resolved "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" - integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== - -detect-node@^2.0.4: - version "2.1.0" - resolved "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz#c9c70775a49c3d03bc2c06d9a73be550f978f8b1" - integrity sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g== + resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz#3f7ae421129bcaaac9bc74905c98a0009ec9ee7f" + integrity sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og== -detect-port-alt@^1.1.6: - version "1.1.6" - resolved "https://registry.npmjs.org/detect-port-alt/-/detect-port-alt-1.1.6.tgz#24707deabe932d4a3cf621302027c2b266568275" - integrity sha512-5tQykt+LqfJFBEYaDITx7S7cR7mJ/zQmLXZ2qt5w04ainYZw6tBf9dBunMjVeVOdYVRUzUOE4HkY5J7+uttb5Q== - dependencies: - address "^1.0.1" - debug "^2.6.0" +define-lazy-prop@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz#dbb19adfb746d7fc6d734a06b72f4a00d021255f" + integrity sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg== -detective@^5.2.1: - version "5.2.1" - resolved "https://registry.npmjs.org/detective/-/detective-5.2.1.tgz#6af01eeda11015acb0e73f933242b70f24f91034" - integrity sha512-v9XE1zRnz1wRtgurGu0Bs8uHKFSTdteYZNbIPFVhUZ39L/S79ppMpdmVOZAnoz1jfEFodc48n6MX483Xo3t1yw== +define-properties@^1.1.3, define-properties@^1.1.4, define-properties@^1.2.0, define-properties@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.2.1.tgz#10781cc616eb951a80a034bafcaa7377f6af2b6c" + integrity sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg== dependencies: - acorn-node "^1.8.2" - defined "^1.0.0" - minimist "^1.2.6" - -didyoumean@^1.2.2: - version "1.2.2" - resolved "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz#989346ffe9e839b4555ecf5666edea0d3e8ad037" - integrity sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw== + define-data-property "^1.0.1" + has-property-descriptors "^1.0.0" + object-keys "^1.1.1" -diff-sequences@^27.5.1: - version "27.5.1" - resolved "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.5.1.tgz#eaecc0d327fd68c8d9672a1e64ab8dccb2ef5327" - integrity sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ== +dequal@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/dequal/-/dequal-2.0.3.tgz#2644214f1997d39ed0ee0ece72335490a7ac67be" + integrity sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA== -diff-sequences@^29.3.1: - version "29.3.1" - resolved "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.3.1.tgz#104b5b95fe725932421a9c6e5b4bef84c3f2249e" - integrity sha512-hlM3QR272NXCi4pq+N4Kok4kOp6EsgOM3ZSpJI7Da3UAs+Ttsi8MRmB6trM/lhyzUxGfOgnpkHtgqm5Q/CTcfQ== +diff-sequences@^29.4.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.6.3.tgz#4deaf894d11407c51efc8418012f9e70b84ea921" + integrity sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q== dir-glob@^3.0.1: version "3.0.1" - resolved "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" + resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== dependencies: path-type "^4.0.0" -dlv@^1.1.3: - version "1.1.3" - resolved "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz#5c198a8a11453596e751494d49874bc7732f2e79" - integrity sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA== - -dns-equal@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz#b39e7f1da6eb0a75ba9c17324b34753c47e0654d" - integrity sha512-z+paD6YUQsk+AbGCEM4PrOXSss5gd66QfcVBFTKR/HpFL9jCqikS94HYwKww6fQyO7IxrIIyUu+g0Ka9tUS2Cg== - -dns-packet@^5.2.2: - version "5.4.0" - resolved "https://registry.npmjs.org/dns-packet/-/dns-packet-5.4.0.tgz#1f88477cf9f27e78a213fb6d118ae38e759a879b" - integrity sha512-EgqGeaBB8hLiHLZtp/IbaDQTL8pZ0+IvwzSHA6d7VyMDM+B9hgddEMa9xjK5oYnw0ci0JQ6g2XCD7/f6cafU6g== - dependencies: - "@leichtgewicht/ip-codec" "^2.0.1" - doctrine@^2.1.0: version "2.1.0" - resolved "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d" integrity sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw== dependencies: esutils "^2.0.2" doctrine@^3.0.0: version "3.0.0" - resolved "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== dependencies: esutils "^2.0.2" -dom-accessibility-api@^0.5.6, dom-accessibility-api@^0.5.9: - version "0.5.14" - resolved "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.14.tgz#56082f71b1dc7aac69d83c4285eef39c15d93f56" - integrity sha512-NMt+m9zFMPZe0JcY9gN224Qvk6qLIdqex29clBvc/y75ZBX9YA9wNK3frsYvu2DI1xcCIwxwnX+TlsJ2DSOADg== - -dom-converter@^0.2.0: - version "0.2.0" - resolved "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz#6721a9daee2e293682955b6afe416771627bb768" - integrity sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA== - dependencies: - utila "~0.4" - -dom-serializer@0: - version "0.2.2" - resolved "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz#1afb81f533717175d478655debc5e332d9f9bb51" - integrity sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g== - dependencies: - domelementtype "^2.0.1" - entities "^2.0.0" - -dom-serializer@^1.0.1: - version "1.4.1" - resolved "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz#de5d41b1aea290215dc45a6dae8adcf1d32e2d30" - integrity sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag== - dependencies: - domelementtype "^2.0.1" - domhandler "^4.2.0" - entities "^2.0.0" - -domelementtype@1: - version "1.3.1" - resolved "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz#d048c44b37b0d10a7f2a3d5fee3f4333d790481f" - integrity sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w== - -domelementtype@^2.0.1, domelementtype@^2.2.0: - version "2.3.0" - resolved "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz#5c45e8e869952626331d7aab326d01daf65d589d" - integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw== - -domexception@^2.0.1: - version "2.0.1" - resolved "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz#fb44aefba793e1574b0af6aed2801d057529f304" - integrity sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg== - dependencies: - webidl-conversions "^5.0.0" - -domhandler@^4.0.0, domhandler@^4.2.0, domhandler@^4.3.1: - version "4.3.1" - resolved "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz#8d792033416f59d68bc03a5aa7b018c1ca89279c" - integrity sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ== - dependencies: - domelementtype "^2.2.0" - -domutils@^1.7.0: - version "1.7.0" - resolved "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz#56ea341e834e06e6748af7a1cb25da67ea9f8c2a" - integrity sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg== - dependencies: - dom-serializer "0" - domelementtype "1" - -domutils@^2.5.2, domutils@^2.8.0: - version "2.8.0" - resolved "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz#4437def5db6e2d1f5d6ee859bd95ca7d02048135" - integrity sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A== - dependencies: - dom-serializer "^1.0.1" - domelementtype "^2.2.0" - domhandler "^4.2.0" - dot-case@^3.0.4: version "3.0.4" - resolved "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz#9b2b670d00a431667a8a75ba29cd1b98809ce751" + resolved "https://registry.yarnpkg.com/dot-case/-/dot-case-3.0.4.tgz#9b2b670d00a431667a8a75ba29cd1b98809ce751" integrity sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w== dependencies: no-case "^3.0.4" - tslib "^2.0.3" - -dotenv-expand@^5.1.0: - version "5.1.0" - resolved "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-5.1.0.tgz#3fbaf020bfd794884072ea26b1e9791d45a629f0" - integrity sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA== - -dotenv@^10.0.0: - version "10.0.0" - resolved "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz#3d4227b8fb95f81096cdd2b66653fb2c7085ba81" - integrity sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q== - -downshift@^6.1.7: - version "6.1.12" - resolved "https://registry.npmjs.org/downshift/-/downshift-6.1.12.tgz#f14476b41a6f6fd080c340bad1ddf449f7143f6f" - integrity sha512-7XB/iaSJVS4T8wGFT3WRXmSF1UlBHAA40DshZtkrIscIN+VC+Lh363skLxFTvJwtNgHxAMDGEHT4xsyQFWL+UA== - dependencies: - "@babel/runtime" "^7.14.8" - compute-scroll-into-view "^1.0.17" - prop-types "^15.7.2" - react-is "^17.0.2" - tslib "^2.3.0" - -duplexer@^0.1.2: - version "0.1.2" - resolved "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz#3abe43aef3835f8ae077d136ddce0f276b0400e6" - integrity sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg== - -eases@^1.0.8: - version "1.0.8" - resolved "https://registry.npmjs.org/eases/-/eases-1.0.8.tgz#f1f5069a6b6ed2ea510f9c6110398d63efe9aee6" - integrity sha512-U8TwgFYPktV07tV9yxoIu9TIWsXHGOuDJNXlyihWEMnGMXaoh2kkfWf0N1itbCjHWrgs+/leubuG7m+OgkfA0Q== - -ed2curve@^0.3.0: - version "0.3.0" - resolved "https://registry.npmjs.org/ed2curve/-/ed2curve-0.3.0.tgz#322b575152a45305429d546b071823a93129a05d" - integrity sha512-8w2fmmq3hv9rCrcI7g9hms2pMunQr1JINfcjwR9tAyZqhtyaMN991lF/ZfHfr5tzZQ8c7y7aBgZbjfbd0fjFwQ== - dependencies: - tweetnacl "1.x.x" - -ee-first@1.1.1: - version "1.1.1" - resolved "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" - integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== - -ejs@^3.1.6: - version "3.1.8" - resolved "https://registry.npmjs.org/ejs/-/ejs-3.1.8.tgz#758d32910c78047585c7ef1f92f9ee041c1c190b" - integrity sha512-/sXZeMlhS0ArkfX2Aw780gJzXSMPnKjtspYZv+f3NiKLlubezAHDU5+9xz6gd3/NhG3txQCo6xlglmTS+oTGEQ== - dependencies: - jake "^10.8.5" - -electron-to-chromium@^1.4.251: - version "1.4.284" - resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz#61046d1e4cab3a25238f6bf7413795270f125592" - integrity sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA== - -email-addresses@^3.0.1: - version "3.1.0" - resolved "https://registry.npmjs.org/email-addresses/-/email-addresses-3.1.0.tgz#cabf7e085cbdb63008a70319a74e6136188812fb" - integrity sha512-k0/r7GrWVL32kZlGwfPNgB2Y/mMXVTq/decgLczm/j34whdaspNrZO8CnXPf1laaHxI6ptUlsnAxN+UAPw+fzg== + tslib "^2.0.3" -emittery@^0.10.2: - version "0.10.2" - resolved "https://registry.npmjs.org/emittery/-/emittery-0.10.2.tgz#902eec8aedb8c41938c46e9385e9db7e03182933" - integrity sha512-aITqOwnLanpHLNXZJENbOgjUBeHocD+xsSJmNrjovKBW5HbSpW3d1pEls7GFQPUWXiwG9+0P4GtHfEqC/4M0Iw== +electron-to-chromium@^1.4.535: + version "1.4.569" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.569.tgz#1298b67727187ffbaac005a7425490d157f3ad03" + integrity sha512-LsrJjZ0IbVy12ApW3gpYpcmHS3iRxH4bkKOW98y1/D+3cvDUWGcbzbsFinfUS8knpcZk/PG/2p/RnkMCYN7PVg== -emittery@^0.8.1: - version "0.8.1" - resolved "https://registry.npmjs.org/emittery/-/emittery-0.8.1.tgz#bb23cc86d03b30aa75a7f734819dee2e1ba70860" - integrity sha512-uDfvUjVrfGJJhymx/kz6prltenw1u7WrCg1oa94zYY8xxVpLLUu045LAT0dhDZdXG58/EpPL/5kA180fQ/qudg== +elliptic@^6.4.1: + version "6.5.4" + resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb" + integrity sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ== + dependencies: + bn.js "^4.11.9" + brorand "^1.1.0" + hash.js "^1.0.0" + hmac-drbg "^1.0.1" + inherits "^2.0.4" + minimalistic-assert "^1.0.1" + minimalistic-crypto-utils "^1.0.1" + +email-addresses@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/email-addresses/-/email-addresses-5.0.0.tgz#7ae9e7f58eef7d5e3e2c2c2d3ea49b78dc854fa6" + integrity sha512-4OIPYlA6JXqtVn8zpHpGiI7vE6EQOAg16aGnDMIAlZVinnoZ8208tW1hAbjWydgN/4PLTT9q+O1K6AH/vALJGw== emoji-regex@^8.0.0: version "8.0.0" - resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== emoji-regex@^9.2.2: version "9.2.2" - resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== -emojis-list@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78" - integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q== - -encodeurl@~1.0.2: - version "1.0.2" - resolved "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" - integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== - -enhanced-resolve@^5.10.0: - version "5.12.0" - resolved "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.12.0.tgz#300e1c90228f5b570c4d35babf263f6da7155634" - integrity sha512-QHTXI/sZQmko1cbDoNAa3mJ5qhWUUNAq3vR0/YiD379fWQrcfuoX1+HW2S0MTt7XmoPLapdaDKUtelUSPic7hQ== +enhanced-resolve@^5.12.0: + version "5.15.0" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz#1af946c7d93603eb88e9896cee4904dc012e9c35" + integrity sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg== dependencies: graceful-fs "^4.2.4" tapable "^2.2.0" -entities@^2.0.0: - version "2.2.0" - resolved "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55" - integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A== +entities@^4.4.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48" + integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== error-ex@^1.3.1: version "1.3.2" - resolved "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== dependencies: is-arrayish "^0.2.1" -error-stack-parser@^2.0.6: - version "2.1.4" - resolved "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz#229cb01cdbfa84440bfa91876285b94680188286" - integrity sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ== - dependencies: - stackframe "^1.3.4" - -es-abstract@^1.17.2, es-abstract@^1.19.0, es-abstract@^1.20.4: - version "1.20.4" - resolved "https://registry.npmjs.org/es-abstract/-/es-abstract-1.20.4.tgz#1d103f9f8d78d4cf0713edcd6d0ed1a46eed5861" - integrity sha512-0UtvRN79eMe2L+UNEF1BwRe364sj/DXhQ/k5FmivgoSdpM90b8Jc0mDzKMGo7QS0BVbOP/bTwBKNnDc9rNzaPA== +es-abstract@^1.22.1: + version "1.22.3" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.22.3.tgz#48e79f5573198de6dee3589195727f4f74bc4f32" + integrity sha512-eiiY8HQeYfYH2Con2berK+To6GrK2RxbPawDkGq4UiCQQfZHb6wX9qQqkbpPqaxQFcl8d9QzZqo0tGE0VcrdwA== dependencies: - call-bind "^1.0.2" + array-buffer-byte-length "^1.0.0" + arraybuffer.prototype.slice "^1.0.2" + available-typed-arrays "^1.0.5" + call-bind "^1.0.5" + es-set-tostringtag "^2.0.1" es-to-primitive "^1.2.1" - function-bind "^1.1.1" - function.prototype.name "^1.1.5" - get-intrinsic "^1.1.3" + function.prototype.name "^1.1.6" + get-intrinsic "^1.2.2" get-symbol-description "^1.0.0" - has "^1.0.3" + globalthis "^1.0.3" + gopd "^1.0.1" has-property-descriptors "^1.0.0" + has-proto "^1.0.1" has-symbols "^1.0.3" - internal-slot "^1.0.3" + hasown "^2.0.0" + internal-slot "^1.0.5" + is-array-buffer "^3.0.2" is-callable "^1.2.7" is-negative-zero "^2.0.2" is-regex "^1.1.4" is-shared-array-buffer "^1.0.2" is-string "^1.0.7" + is-typed-array "^1.1.12" is-weakref "^1.0.2" - object-inspect "^1.12.2" + object-inspect "^1.13.1" object-keys "^1.1.1" object.assign "^4.1.4" - regexp.prototype.flags "^1.4.3" + regexp.prototype.flags "^1.5.1" + safe-array-concat "^1.0.1" safe-regex-test "^1.0.0" - string.prototype.trimend "^1.0.5" - string.prototype.trimstart "^1.0.5" + string.prototype.trim "^1.2.8" + string.prototype.trimend "^1.0.7" + string.prototype.trimstart "^1.0.7" + typed-array-buffer "^1.0.0" + typed-array-byte-length "^1.0.0" + typed-array-byte-offset "^1.0.0" + typed-array-length "^1.0.4" unbox-primitive "^1.0.2" + which-typed-array "^1.1.13" -es-array-method-boxes-properly@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz#873f3e84418de4ee19c5be752990b2e44718d09e" - integrity sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA== - -es-get-iterator@^1.1.2: - version "1.1.2" - resolved "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.2.tgz#9234c54aba713486d7ebde0220864af5e2b283f7" - integrity sha512-+DTO8GYwbMCwbywjimwZMHp8AuYXOS2JZFWoi2AlPOS3ebnII9w/NLpNZtA7A0YLaVDw+O7KFCeoIV7OPvM7hQ== +es-iterator-helpers@^1.0.12: + version "1.0.15" + resolved "https://registry.yarnpkg.com/es-iterator-helpers/-/es-iterator-helpers-1.0.15.tgz#bd81d275ac766431d19305923707c3efd9f1ae40" + integrity sha512-GhoY8uYqd6iwUl2kgjTm4CZAf6oo5mHK7BPqx3rKgx893YSsy0LGHV6gfqqQvZt/8xM8xeOnfXBCfqclMKkJ5g== dependencies: + asynciterator.prototype "^1.0.0" call-bind "^1.0.2" - get-intrinsic "^1.1.0" - has-symbols "^1.0.1" - is-arguments "^1.1.0" - is-map "^2.0.2" - is-set "^2.0.2" - is-string "^1.0.5" - isarray "^2.0.5" + define-properties "^1.2.1" + es-abstract "^1.22.1" + es-set-tostringtag "^2.0.1" + function-bind "^1.1.1" + get-intrinsic "^1.2.1" + globalthis "^1.0.3" + has-property-descriptors "^1.0.0" + has-proto "^1.0.1" + has-symbols "^1.0.3" + internal-slot "^1.0.5" + iterator.prototype "^1.1.2" + safe-array-concat "^1.0.1" -es-module-lexer@^0.9.0: - version "0.9.3" - resolved "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz#6f13db00cc38417137daf74366f535c8eb438f19" - integrity sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ== +es-set-tostringtag@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.0.2.tgz#11f7cc9f63376930a5f20be4915834f4bc74f9c9" + integrity sha512-BuDyupZt65P9D2D2vA/zqcI3G5xRsklm5N3xCwuiy+/vKy8i0ifdsQP1sLgO4tZDSCaQUSnmC48khknGMV3D2Q== + dependencies: + get-intrinsic "^1.2.2" + has-tostringtag "^1.0.0" + hasown "^2.0.0" es-shim-unscopables@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz#702e632193201e3edf8713635d083d378e510241" - integrity sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w== + version "1.0.2" + resolved "https://registry.yarnpkg.com/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz#1f6942e71ecc7835ed1c8a83006d8771a63a3763" + integrity sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw== dependencies: - has "^1.0.3" + hasown "^2.0.0" es-to-primitive@^1.2.1: version "1.2.1" - resolved "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" + resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== dependencies: is-callable "^1.1.4" is-date-object "^1.0.1" is-symbol "^1.0.2" -es5-ext@^0.10.35, es5-ext@^0.10.50: - version "0.10.62" - resolved "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.62.tgz#5e6adc19a6da524bf3d1e02bbc8960e5eb49a9a5" - integrity sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA== - dependencies: - es6-iterator "^2.0.3" - es6-symbol "^3.1.3" - next-tick "^1.1.0" - -es6-iterator@^2.0.3: - version "2.0.3" - resolved "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz#a7de889141a05a94b0854403b2d0a0fbfa98f3b7" - integrity sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g== - dependencies: - d "1" - es5-ext "^0.10.35" - es6-symbol "^3.1.1" - -es6-symbol@^3.1.1, es6-symbol@^3.1.3: - version "3.1.3" - resolved "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz#bad5d3c1bcdac28269f4cb331e431c78ac705d18" - integrity sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA== - dependencies: - d "^1.0.1" - ext "^1.1.2" +esbuild@^0.18.10: + version "0.18.20" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.18.20.tgz#4709f5a34801b43b799ab7d6d82f7284a9b7a7a6" + integrity sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA== + optionalDependencies: + "@esbuild/android-arm" "0.18.20" + "@esbuild/android-arm64" "0.18.20" + "@esbuild/android-x64" "0.18.20" + "@esbuild/darwin-arm64" "0.18.20" + "@esbuild/darwin-x64" "0.18.20" + "@esbuild/freebsd-arm64" "0.18.20" + "@esbuild/freebsd-x64" "0.18.20" + "@esbuild/linux-arm" "0.18.20" + "@esbuild/linux-arm64" "0.18.20" + "@esbuild/linux-ia32" "0.18.20" + "@esbuild/linux-loong64" "0.18.20" + "@esbuild/linux-mips64el" "0.18.20" + "@esbuild/linux-ppc64" "0.18.20" + "@esbuild/linux-riscv64" "0.18.20" + "@esbuild/linux-s390x" "0.18.20" + "@esbuild/linux-x64" "0.18.20" + "@esbuild/netbsd-x64" "0.18.20" + "@esbuild/openbsd-x64" "0.18.20" + "@esbuild/sunos-x64" "0.18.20" + "@esbuild/win32-arm64" "0.18.20" + "@esbuild/win32-ia32" "0.18.20" + "@esbuild/win32-x64" "0.18.20" escalade@^3.1.1: version "3.1.1" - resolved "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== -escape-html@~1.0.3: - version "1.0.3" - resolved "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" - integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== - escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: version "1.0.5" - resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== -escape-string-regexp@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" - integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== - escape-string-regexp@^4.0.0: version "4.0.0" - resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== -escodegen@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/escodegen/-/escodegen-2.0.0.tgz#5e32b12833e8aa8fa35e1bf0befa89380484c7dd" - integrity sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw== - dependencies: - esprima "^4.0.1" - estraverse "^5.2.0" - esutils "^2.0.2" - optionator "^0.8.1" - optionalDependencies: - source-map "~0.6.1" - eslint-config-airbnb-base@^15.0.0: version "15.0.0" - resolved "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-15.0.0.tgz#6b09add90ac79c2f8d723a2580e07f3925afd236" + resolved "https://registry.yarnpkg.com/eslint-config-airbnb-base/-/eslint-config-airbnb-base-15.0.0.tgz#6b09add90ac79c2f8d723a2580e07f3925afd236" integrity sha512-xaX3z4ZZIcFLvh2oUNvcX5oEofXda7giYmuplVxoOg5A7EXJMrUyqRgR+mhDhPK8LZ4PttFOBvCYDbX3sUoUig== dependencies: confusing-browser-globals "^1.0.10" @@ -4966,159 +2449,129 @@ eslint-config-airbnb-base@^15.0.0: object.entries "^1.1.5" semver "^6.3.0" -eslint-config-airbnb-typescript@^17.0.0: - version "17.0.0" - resolved "https://registry.npmjs.org/eslint-config-airbnb-typescript/-/eslint-config-airbnb-typescript-17.0.0.tgz#360dbcf810b26bbcf2ff716198465775f1c49a07" - integrity sha512-elNiuzD0kPAPTXjFWg+lE24nMdHMtuxgYoD30OyMD6yrW1AhFZPAg27VX7d3tzOErw+dgJTNWfRSDqEcXb4V0g== +eslint-config-airbnb-typescript@^17.1.0: + version "17.1.0" + resolved "https://registry.yarnpkg.com/eslint-config-airbnb-typescript/-/eslint-config-airbnb-typescript-17.1.0.tgz#fda960eee4a510f092a9a1c139035ac588937ddc" + integrity sha512-GPxI5URre6dDpJ0CtcthSZVBAfI+Uw7un5OYNVxP2EYi3H81Jw701yFP7AU+/vCE7xBtFmjge7kfhhk4+RAiig== dependencies: eslint-config-airbnb-base "^15.0.0" eslint-config-airbnb@^19.0.4: version "19.0.4" - resolved "https://registry.npmjs.org/eslint-config-airbnb/-/eslint-config-airbnb-19.0.4.tgz#84d4c3490ad70a0ffa571138ebcdea6ab085fdc3" + resolved "https://registry.yarnpkg.com/eslint-config-airbnb/-/eslint-config-airbnb-19.0.4.tgz#84d4c3490ad70a0ffa571138ebcdea6ab085fdc3" integrity sha512-T75QYQVQX57jiNgpF9r1KegMICE94VYwoFQyMGhrvc+lB8YF2E/M/PYDaQe1AJcWaEgqLE+ErXV1Og/+6Vyzew== dependencies: eslint-config-airbnb-base "^15.0.0" object.assign "^4.1.2" object.entries "^1.1.5" -eslint-config-prettier@^8.5.0: - version "8.5.0" - resolved "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz#5a81680ec934beca02c7b1a61cf8ca34b66feab1" - integrity sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q== +eslint-config-prettier@^9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-9.0.0.tgz#eb25485946dd0c66cd216a46232dc05451518d1f" + integrity sha512-IcJsTkJae2S35pRsRAwoCE+925rJJStOdkKnLVgtE+tEpqU0EVVM7OqrwxqgptKdX29NUwC82I5pXsGFIgSevw== -eslint-config-react-app@^7.0.1: - version "7.0.1" - resolved "https://registry.npmjs.org/eslint-config-react-app/-/eslint-config-react-app-7.0.1.tgz#73ba3929978001c5c86274c017ea57eb5fa644b4" - integrity sha512-K6rNzvkIeHaTd8m/QEh1Zko0KI7BACWkkneSs6s9cKZC/J27X3eZR6Upt1jkmZ/4FK+XUOPPxMEN7+lbUXfSlA== - dependencies: - "@babel/core" "^7.16.0" - "@babel/eslint-parser" "^7.16.3" - "@rushstack/eslint-patch" "^1.1.0" - "@typescript-eslint/eslint-plugin" "^5.5.0" - "@typescript-eslint/parser" "^5.5.0" - babel-preset-react-app "^10.0.1" - confusing-browser-globals "^1.0.11" - eslint-plugin-flowtype "^8.0.3" - eslint-plugin-import "^2.25.3" - eslint-plugin-jest "^25.3.0" - eslint-plugin-jsx-a11y "^6.5.1" - eslint-plugin-react "^7.27.1" - eslint-plugin-react-hooks "^4.3.0" - eslint-plugin-testing-library "^5.0.1" - -eslint-import-resolver-node@^0.3.6: - version "0.3.6" - resolved "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.6.tgz#4048b958395da89668252001dbd9eca6b83bacbd" - integrity sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw== +eslint-import-resolver-node@^0.3.9: + version "0.3.9" + resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz#d4eaac52b8a2e7c3cd1903eb00f7e053356118ac" + integrity sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g== dependencies: debug "^3.2.7" - resolve "^1.20.0" + is-core-module "^2.13.0" + resolve "^1.22.4" -eslint-import-resolver-typescript@^3.5.2: - version "3.5.2" - resolved "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.5.2.tgz#9431acded7d898fd94591a08ea9eec3514c7de91" - integrity sha512-zX4ebnnyXiykjhcBvKIf5TNvt8K7yX6bllTRZ14MiurKPjDpCAZujlszTdB8pcNXhZcOf+god4s9SjQa5GnytQ== +eslint-import-resolver-typescript@^3.6.1: + version "3.6.1" + resolved "https://registry.yarnpkg.com/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.6.1.tgz#7b983680edd3f1c5bce1a5829ae0bc2d57fe9efa" + integrity sha512-xgdptdoi5W3niYeuQxKmzVDTATvLYqhpwmykwsh7f6HIOStGWEIL9iqZgQDF9u9OEzrRwR8no5q2VT+bjAujTg== dependencies: debug "^4.3.4" - enhanced-resolve "^5.10.0" - get-tsconfig "^4.2.0" - globby "^13.1.2" - is-core-module "^2.10.0" + enhanced-resolve "^5.12.0" + eslint-module-utils "^2.7.4" + fast-glob "^3.3.1" + get-tsconfig "^4.5.0" + is-core-module "^2.11.0" is-glob "^4.0.3" - synckit "^0.8.4" -eslint-module-utils@^2.7.3: - version "2.7.4" - resolved "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.7.4.tgz#4f3e41116aaf13a20792261e61d3a2e7e0583974" - integrity sha512-j4GT+rqzCoRKHwURX7pddtIPGySnX9Si/cgMI5ztrcqOPtk5dDEeZ34CQVPphnqkJytlc97Vuk05Um2mJ3gEQA== +eslint-module-utils@^2.7.4, eslint-module-utils@^2.8.0: + version "2.8.0" + resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.8.0.tgz#e439fee65fc33f6bba630ff621efc38ec0375c49" + integrity sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw== dependencies: debug "^3.2.7" -eslint-plugin-flowtype@^8.0.3: - version "8.0.3" - resolved "https://registry.npmjs.org/eslint-plugin-flowtype/-/eslint-plugin-flowtype-8.0.3.tgz#e1557e37118f24734aa3122e7536a038d34a4912" - integrity sha512-dX8l6qUL6O+fYPtpNRideCFSpmWOUVx5QcaGLVqe/vlDiBSe4vYljDWDETwnyFzpl7By/WVIu6rcrniCgH9BqQ== - dependencies: - lodash "^4.17.21" - string-natural-compare "^3.0.1" - -eslint-plugin-import@^2.25.3: - version "2.26.0" - resolved "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.26.0.tgz#f812dc47be4f2b72b478a021605a59fc6fe8b88b" - integrity sha512-hYfi3FXaM8WPLf4S1cikh/r4IxnO6zrhZbEGz2b660EJRbuxgpDS5gkCuYgGWg2xxh2rBuIr4Pvhve/7c31koA== +eslint-plugin-import@^2.29.0: + version "2.29.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.29.0.tgz#8133232e4329ee344f2f612885ac3073b0b7e155" + integrity sha512-QPOO5NO6Odv5lpoTkddtutccQjysJuFxoPS7fAHO+9m9udNHvTCPSAMW9zGAYj8lAIdr40I8yPCdUYrncXtrwg== dependencies: - array-includes "^3.1.4" - array.prototype.flat "^1.2.5" - debug "^2.6.9" + array-includes "^3.1.7" + array.prototype.findlastindex "^1.2.3" + array.prototype.flat "^1.3.2" + array.prototype.flatmap "^1.3.2" + debug "^3.2.7" doctrine "^2.1.0" - eslint-import-resolver-node "^0.3.6" - eslint-module-utils "^2.7.3" - has "^1.0.3" - is-core-module "^2.8.1" + eslint-import-resolver-node "^0.3.9" + eslint-module-utils "^2.8.0" + hasown "^2.0.0" + is-core-module "^2.13.1" is-glob "^4.0.3" minimatch "^3.1.2" - object.values "^1.1.5" - resolve "^1.22.0" - tsconfig-paths "^3.14.1" - -eslint-plugin-jest@^25.3.0: - version "25.7.0" - resolved "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-25.7.0.tgz#ff4ac97520b53a96187bad9c9814e7d00de09a6a" - integrity sha512-PWLUEXeeF7C9QGKqvdSbzLOiLTx+bno7/HC9eefePfEb257QFHg7ye3dh80AZVkaa/RQsBB1Q/ORQvg2X7F0NQ== - dependencies: - "@typescript-eslint/experimental-utils" "^5.0.0" - -eslint-plugin-jsx-a11y@^6.5.1: - version "6.6.1" - resolved "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.6.1.tgz#93736fc91b83fdc38cc8d115deedfc3091aef1ff" - integrity sha512-sXgFVNHiWffBq23uiS/JaP6eVR622DqwB4yTzKvGZGcPq6/yZ3WmOZfuBks/vHWo9GaFOqC2ZK4i6+C35knx7Q== - dependencies: - "@babel/runtime" "^7.18.9" - aria-query "^4.2.2" - array-includes "^3.1.5" + object.fromentries "^2.0.7" + object.groupby "^1.0.1" + object.values "^1.1.7" + semver "^6.3.1" + tsconfig-paths "^3.14.2" + +eslint-plugin-jsx-a11y@^6.7.1: + version "6.7.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.7.1.tgz#fca5e02d115f48c9a597a6894d5bcec2f7a76976" + integrity sha512-63Bog4iIethyo8smBklORknVjB0T2dwB8Mr/hIC+fBS0uyHdYYpzM/Ed+YC8VxTjlXHEWFOdmgwcDn1U2L9VCA== + dependencies: + "@babel/runtime" "^7.20.7" + aria-query "^5.1.3" + array-includes "^3.1.6" + array.prototype.flatmap "^1.3.1" ast-types-flow "^0.0.7" - axe-core "^4.4.3" - axobject-query "^2.2.0" + axe-core "^4.6.2" + axobject-query "^3.1.1" damerau-levenshtein "^1.0.8" emoji-regex "^9.2.2" has "^1.0.3" - jsx-ast-utils "^3.3.2" - language-tags "^1.0.5" + jsx-ast-utils "^3.3.3" + language-tags "=1.0.5" minimatch "^3.1.2" + object.entries "^1.1.6" + object.fromentries "^2.0.6" semver "^6.3.0" -eslint-plugin-prefer-arrow-functions@^3.1.4: - version "3.1.4" - resolved "https://registry.npmjs.org/eslint-plugin-prefer-arrow-functions/-/eslint-plugin-prefer-arrow-functions-3.1.4.tgz#a0761c7a0ba71461f6b2136fdf7f7cec1c82d778" - integrity sha512-LSO8VibqBKqzelr+L21mEIfachavCon+1SEumCJ6U8Ze2q0pntyojmomcVwd9RZBjrP+HV6k1Osz0B3Xwdq8WA== +eslint-plugin-prefer-arrow-functions@^3.2.4: + version "3.2.4" + resolved "https://registry.yarnpkg.com/eslint-plugin-prefer-arrow-functions/-/eslint-plugin-prefer-arrow-functions-3.2.4.tgz#7bfbaab9646d1bb7cd7e29a58cfb796548c69508" + integrity sha512-HbPmlbO/iYQeVs2fuShNkGVJDfVfgSd84Vzxv+xlh+nIVoSsZvTj6yOqszw4mtG9JbiqMShVWqbVeoVsejE59w== eslint-plugin-prefer-arrow@^1.2.3: version "1.2.3" - resolved "https://registry.npmjs.org/eslint-plugin-prefer-arrow/-/eslint-plugin-prefer-arrow-1.2.3.tgz#e7fbb3fa4cd84ff1015b9c51ad86550e55041041" + resolved "https://registry.yarnpkg.com/eslint-plugin-prefer-arrow/-/eslint-plugin-prefer-arrow-1.2.3.tgz#e7fbb3fa4cd84ff1015b9c51ad86550e55041041" integrity sha512-J9I5PKCOJretVuiZRGvPQxCbllxGAV/viI20JO3LYblAodofBxyMnZAJ+WGeClHgANnSJberTNoFWWjrWKBuXQ== -eslint-plugin-prettier@^4.0.0: - version "4.2.1" - resolved "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-4.2.1.tgz#651cbb88b1dab98bfd42f017a12fa6b2d993f94b" - integrity sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ== +eslint-plugin-prettier@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-5.0.1.tgz#a3b399f04378f79f066379f544e42d6b73f11515" + integrity sha512-m3u5RnR56asrwV/lDC4GHorlW75DsFfmUcjfCYylTUs85dBRnB7VM6xG8eCMJdeDRnppzmxZVf1GEPJvl1JmNg== dependencies: prettier-linter-helpers "^1.0.0" + synckit "^0.8.5" -eslint-plugin-react-hooks@^4.3.0: - version "4.6.0" - resolved "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz#4c3e697ad95b77e93f8646aaa1630c1ba607edd3" - integrity sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g== - -eslint-plugin-react@^7.27.1, eslint-plugin-react@^7.31.11: - version "7.31.11" - resolved "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.31.11.tgz#011521d2b16dcf95795df688a4770b4eaab364c8" - integrity sha512-TTvq5JsT5v56wPa9OYHzsrOlHzKZKjV+aLgS+55NJP/cuzdiQPC7PfYoUjMoxlffKtvijpk7vA/jmuqRb9nohw== +eslint-plugin-react@^7.33.2: + version "7.33.2" + resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.33.2.tgz#69ee09443ffc583927eafe86ffebb470ee737608" + integrity sha512-73QQMKALArI8/7xGLNI/3LylrEYrlKZSb5C9+q3OtOewTnMQi5cT+aE9E41sLCmli3I9PGGmD1yiZydyo4FEPw== dependencies: array-includes "^3.1.6" array.prototype.flatmap "^1.3.1" array.prototype.tosorted "^1.1.1" doctrine "^2.1.0" + es-iterator-helpers "^1.0.12" estraverse "^5.3.0" jsx-ast-utils "^2.4.1 || ^3.0.0" minimatch "^3.1.2" @@ -5127,184 +2580,135 @@ eslint-plugin-react@^7.27.1, eslint-plugin-react@^7.31.11: object.hasown "^1.1.2" object.values "^1.1.6" prop-types "^15.8.1" - resolve "^2.0.0-next.3" - semver "^6.3.0" + resolve "^2.0.0-next.4" + semver "^6.3.1" string.prototype.matchall "^4.0.8" -eslint-plugin-testing-library@^5.0.1: - version "5.9.1" - resolved "https://registry.npmjs.org/eslint-plugin-testing-library/-/eslint-plugin-testing-library-5.9.1.tgz#12e4bd34c48683ee98af4df2e3318ec9f51dcf8a" - integrity sha512-6BQp3tmb79jLLasPHJmy8DnxREe+2Pgf7L+7o09TSWPfdqqtQfRZmZNetr5mOs3yqZk/MRNxpN3RUpJe0wB4LQ== +eslint-plugin-unused-imports@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-unused-imports/-/eslint-plugin-unused-imports-3.0.0.tgz#d25175b0072ff16a91892c3aa72a09ca3a9e69e7" + integrity sha512-sduiswLJfZHeeBJ+MQaG+xYzSWdRXoSw61DpU13mzWumCkR0ufD0HmO4kdNokjrkluMHpj/7PJeN35pgbhW3kw== dependencies: - "@typescript-eslint/utils" "^5.13.0" + eslint-rule-composer "^0.3.0" -eslint-scope@5.1.1, eslint-scope@^5.1.1: - version "5.1.1" - resolved "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" - integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== - dependencies: - esrecurse "^4.3.0" - estraverse "^4.1.1" +eslint-rule-composer@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/eslint-rule-composer/-/eslint-rule-composer-0.3.0.tgz#79320c927b0c5c0d3d3d2b76c8b4a488f25bbaf9" + integrity sha512-bt+Sh8CtDmn2OajxvNO+BX7Wn4CIWMpTRm3MaiKPCQcnnlm0CS2mhui6QaoeQugs+3Kj2ESKEEGJUdVafwhiCg== -eslint-scope@^7.1.1: - version "7.1.1" - resolved "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz#fff34894c2f65e5226d3041ac480b4513a163642" - integrity sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw== +eslint-scope@^7.2.2: + version "7.2.2" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.2.2.tgz#deb4f92563390f32006894af62a22dba1c46423f" + integrity sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg== dependencies: esrecurse "^4.3.0" estraverse "^5.2.0" -eslint-utils@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz#8aebaface7345bb33559db0a1f13a1d2d48c3672" - integrity sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA== - dependencies: - eslint-visitor-keys "^2.0.0" - -eslint-visitor-keys@^2.0.0, eslint-visitor-keys@^2.1.0: - version "2.1.0" - resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303" - integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw== - -eslint-visitor-keys@^3.3.0: - version "3.3.0" - resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz#f6480fa6b1f30efe2d1968aa8ac745b862469826" - integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA== - -eslint-webpack-plugin@^3.1.1: - version "3.2.0" - resolved "https://registry.npmjs.org/eslint-webpack-plugin/-/eslint-webpack-plugin-3.2.0.tgz#1978cdb9edc461e4b0195a20da950cf57988347c" - integrity sha512-avrKcGncpPbPSUHX6B3stNGzkKFto3eL+DKM4+VyMrVnhPc3vRczVlCq3uhuFOdRvDHTVXuzwk1ZKUrqDQHQ9w== - dependencies: - "@types/eslint" "^7.29.0 || ^8.4.1" - jest-worker "^28.0.2" - micromatch "^4.0.5" - normalize-path "^3.0.0" - schema-utils "^4.0.0" - -eslint@^8.27.0, eslint@^8.3.0: - version "8.28.0" - resolved "https://registry.npmjs.org/eslint/-/eslint-8.28.0.tgz#81a680732634677cc890134bcdd9fdfea8e63d6e" - integrity sha512-S27Di+EVyMxcHiwDrFzk8dJYAaD+/5SoWKxL1ri/71CRHsnJnRDPNt2Kzj24+MT9FDupf4aqqyqPrvI8MvQ4VQ== - dependencies: - "@eslint/eslintrc" "^1.3.3" - "@humanwhocodes/config-array" "^0.11.6" +eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4.3: + version "3.4.3" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" + integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== + +eslint@^8.52.0: + version "8.52.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.52.0.tgz#d0cd4a1fac06427a61ef9242b9353f36ea7062fc" + integrity sha512-zh/JHnaixqHZsolRB/w9/02akBk9EPrOs9JwcTP2ek7yL5bVvXuRariiaAjjoJ5DvuwQ1WAE/HsMz+w17YgBCg== + dependencies: + "@eslint-community/eslint-utils" "^4.2.0" + "@eslint-community/regexpp" "^4.6.1" + "@eslint/eslintrc" "^2.1.2" + "@eslint/js" "8.52.0" + "@humanwhocodes/config-array" "^0.11.13" "@humanwhocodes/module-importer" "^1.0.1" "@nodelib/fs.walk" "^1.2.8" - ajv "^6.10.0" + "@ungap/structured-clone" "^1.2.0" + ajv "^6.12.4" chalk "^4.0.0" cross-spawn "^7.0.2" debug "^4.3.2" doctrine "^3.0.0" escape-string-regexp "^4.0.0" - eslint-scope "^7.1.1" - eslint-utils "^3.0.0" - eslint-visitor-keys "^3.3.0" - espree "^9.4.0" - esquery "^1.4.0" + eslint-scope "^7.2.2" + eslint-visitor-keys "^3.4.3" + espree "^9.6.1" + esquery "^1.4.2" esutils "^2.0.2" fast-deep-equal "^3.1.3" file-entry-cache "^6.0.1" find-up "^5.0.0" glob-parent "^6.0.2" - globals "^13.15.0" - grapheme-splitter "^1.0.4" + globals "^13.19.0" + graphemer "^1.4.0" ignore "^5.2.0" - import-fresh "^3.0.0" imurmurhash "^0.1.4" is-glob "^4.0.0" is-path-inside "^3.0.3" - js-sdsl "^4.1.4" js-yaml "^4.1.0" json-stable-stringify-without-jsonify "^1.0.1" levn "^0.4.1" lodash.merge "^4.6.2" minimatch "^3.1.2" natural-compare "^1.4.0" - optionator "^0.9.1" - regexpp "^3.2.0" + optionator "^0.9.3" strip-ansi "^6.0.1" - strip-json-comments "^3.1.0" text-table "^0.2.0" -espree@^9.4.0: - version "9.4.1" - resolved "https://registry.npmjs.org/espree/-/espree-9.4.1.tgz#51d6092615567a2c2cff7833445e37c28c0065bd" - integrity sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg== +espree@^9.6.0, espree@^9.6.1: + version "9.6.1" + resolved "https://registry.yarnpkg.com/espree/-/espree-9.6.1.tgz#a2a17b8e434690a5432f2f8018ce71d331a48c6f" + integrity sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ== dependencies: - acorn "^8.8.0" + acorn "^8.9.0" acorn-jsx "^5.3.2" - eslint-visitor-keys "^3.3.0" - -esprima@^4.0.0, esprima@^4.0.1: - version "4.0.1" - resolved "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" - integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + eslint-visitor-keys "^3.4.1" -esquery@^1.4.0: - version "1.4.0" - resolved "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz#2148ffc38b82e8c7057dfed48425b3e61f0f24a5" - integrity sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w== +esquery@^1.4.2: + version "1.5.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.5.0.tgz#6ce17738de8577694edd7361c57182ac8cb0db0b" + integrity sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg== dependencies: estraverse "^5.1.0" esrecurse@^4.3.0: version "4.3.0" - resolved "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== dependencies: estraverse "^5.2.0" -estraverse@^4.1.1: - version "4.3.0" - resolved "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" - integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== - estraverse@^5.1.0, estraverse@^5.2.0, estraverse@^5.3.0: version "5.3.0" - resolved "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== -estree-walker@^1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz#31bc5d612c96b704106b477e6dd5d8aa138cb700" - integrity sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg== +estree-walker@^2.0.1, estree-walker@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac" + integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w== esutils@^2.0.2: version "2.0.3" - resolved "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== -etag@~1.8.1: - version "1.8.1" - resolved "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" - integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== - -ethereum-blockies-base64@^1.0.2: - version "1.0.2" - resolved "https://registry.npmjs.org/ethereum-blockies-base64/-/ethereum-blockies-base64-1.0.2.tgz#4aebca52142bf4d16a3144e6e2b59303e39ed2b3" - integrity sha512-Vg2HTm7slcWNKaRhCUl/L3b4KrB8ohQXdd5Pu3OI897EcR6tVRvUqdTwAyx+dnmoDzj8e2bwBLDQ50ByFmcz6w== - dependencies: - pnglib "0.0.1" - eventemitter3@^3.1.0: version "3.1.2" resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-3.1.2.tgz#2d3d48f9c346698fce83a85d7d664e98535df6e7" integrity sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q== -eventemitter3@^4.0.0, eventemitter3@^4.0.7: - version "4.0.7" - resolved "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" - integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== +eventemitter3@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-5.0.1.tgz#53f5ffd0a492ac800721bb42c66b841de96423c4" + integrity sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA== -events@^3.2.0: +events@^3.3.0: version "3.3.0" - resolved "https://registry.npmjs.org/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" + resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== execa@^5.0.0: version "5.1.1" - resolved "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" + resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== dependencies: cross-spawn "^7.0.3" @@ -5317,90 +2721,35 @@ execa@^5.0.0: signal-exit "^3.0.3" strip-final-newline "^2.0.0" -exit@^0.1.2: - version "0.1.2" - resolved "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" - integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ== - -expect@^27.5.1: - version "27.5.1" - resolved "https://registry.npmjs.org/expect/-/expect-27.5.1.tgz#83ce59f1e5bdf5f9d2b94b61d2050db48f3fef74" - integrity sha512-E1q5hSUG2AmYQwQJ041nvgpkODHQvB+RKlB4IYdru6uJsyFTRyZAP463M+1lINorwbqAmUggi6+WwkD8lCS/Dw== - dependencies: - "@jest/types" "^27.5.1" - jest-get-type "^27.5.1" - jest-matcher-utils "^27.5.1" - jest-message-util "^27.5.1" - -expect@^29.0.0: - version "29.3.1" - resolved "https://registry.npmjs.org/expect/-/expect-29.3.1.tgz#92877aad3f7deefc2e3f6430dd195b92295554a6" - integrity sha512-gGb1yTgU30Q0O/tQq+z30KBWv24ApkMgFUpvKBkyLUBL68Wv8dHdJxTBZFl/iT8K/bqDHvUYRH6IIN3rToopPA== - dependencies: - "@jest/expect-utils" "^29.3.1" - jest-get-type "^29.2.0" - jest-matcher-utils "^29.3.1" - jest-message-util "^29.3.1" - jest-util "^29.3.1" - -express@^4.17.3: - version "4.18.2" - resolved "https://registry.npmjs.org/express/-/express-4.18.2.tgz#3fabe08296e930c796c19e3c516979386ba9fd59" - integrity sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ== - dependencies: - accepts "~1.3.8" - array-flatten "1.1.1" - body-parser "1.20.1" - content-disposition "0.5.4" - content-type "~1.0.4" - cookie "0.5.0" - cookie-signature "1.0.6" - debug "2.6.9" - depd "2.0.0" - encodeurl "~1.0.2" - escape-html "~1.0.3" - etag "~1.8.1" - finalhandler "1.2.0" - fresh "0.5.2" - http-errors "2.0.0" - merge-descriptors "1.0.1" - methods "~1.1.2" - on-finished "2.4.1" - parseurl "~1.3.3" - path-to-regexp "0.1.7" - proxy-addr "~2.0.7" - qs "6.11.0" - range-parser "~1.2.1" - safe-buffer "5.2.1" - send "0.18.0" - serve-static "1.15.0" - setprototypeof "1.2.0" - statuses "2.0.1" - type-is "~1.6.18" - utils-merge "1.0.1" - vary "~1.1.2" - -ext@^1.1.2: - version "1.7.0" - resolved "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz#0ea4383c0103d60e70be99e9a7f11027a33c4f5f" - integrity sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw== - dependencies: - type "^2.7.2" +execa@^7.1.1: + version "7.2.0" + resolved "https://registry.yarnpkg.com/execa/-/execa-7.2.0.tgz#657e75ba984f42a70f38928cedc87d6f2d4fe4e9" + integrity sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA== + dependencies: + cross-spawn "^7.0.3" + get-stream "^6.0.1" + human-signals "^4.3.0" + is-stream "^3.0.0" + merge-stream "^2.0.0" + npm-run-path "^5.1.0" + onetime "^6.0.0" + signal-exit "^3.0.7" + strip-final-newline "^3.0.0" fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: version "3.1.3" - resolved "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== fast-diff@^1.1.2: - version "1.2.0" - resolved "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz#73ee11982d86caaf7959828d519cfe927fac5f03" - integrity sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w== + version "1.3.0" + resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.3.0.tgz#ece407fa550a64d638536cd727e129c61616e0f0" + integrity sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw== -fast-glob@^3.2.11, fast-glob@^3.2.12, fast-glob@^3.2.9: - version "3.2.12" - resolved "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz#7f39ec99c2e6ab030337142da9e0c18f37afae80" - integrity sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w== +fast-glob@^3.2.7, fast-glob@^3.2.9, fast-glob@^3.3.0, fast-glob@^3.3.1: + version "3.3.1" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.1.tgz#784b4e897340f3dbbef17413b3f11acf03c874c4" + integrity sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg== dependencies: "@nodelib/fs.stat" "^2.0.2" "@nodelib/fs.walk" "^1.2.3" @@ -5408,40 +2757,26 @@ fast-glob@^3.2.11, fast-glob@^3.2.12, fast-glob@^3.2.9: merge2 "^1.3.0" micromatch "^4.0.4" -fast-json-stable-stringify@^2.0.0, fast-json-stable-stringify@^2.1.0: +fast-json-stable-stringify@^2.0.0: version "2.1.0" - resolved "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== -fast-levenshtein@^2.0.6, fast-levenshtein@~2.0.6: +fast-levenshtein@^2.0.6: version "2.0.6" - resolved "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== fastq@^1.6.0: - version "1.13.0" - resolved "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz#616760f88a7526bdfc596b7cab8c18938c36b98c" - integrity sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw== + version "1.15.0" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.15.0.tgz#d04d07c6a2a68fe4599fea8d2e103a937fae6b3a" + integrity sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw== dependencies: reusify "^1.0.4" -faye-websocket@^0.11.3: - version "0.11.4" - resolved "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz#7f0d9275cfdd86a1c963dc8b65fcc451edcbb1da" - integrity sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g== - dependencies: - websocket-driver ">=0.5.1" - -fb-watchman@^2.0.0: - version "2.0.2" - resolved "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz#e9524ee6b5c77e9e5001af0f85f3adbb8623255c" - integrity sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA== - dependencies: - bser "2.1.1" - fetch-blob@^3.1.2, fetch-blob@^3.1.4: version "3.2.0" - resolved "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz#f09b8d4bbd45adc6f0c20b7e787e793e309dcce9" + resolved "https://registry.yarnpkg.com/fetch-blob/-/fetch-blob-3.2.0.tgz#f09b8d4bbd45adc6f0c20b7e787e793e309dcce9" integrity sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ== dependencies: node-domexception "^1.0.0" @@ -5449,84 +2784,44 @@ fetch-blob@^3.1.2, fetch-blob@^3.1.4: file-entry-cache@^6.0.1: version "6.0.1" - resolved "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== dependencies: flat-cache "^3.0.4" -file-loader@^6.2.0: - version "6.2.0" - resolved "https://registry.npmjs.org/file-loader/-/file-loader-6.2.0.tgz#baef7cf8e1840df325e4390b4484879480eebe4d" - integrity sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw== - dependencies: - loader-utils "^2.0.0" - schema-utils "^3.0.0" - -filelist@^1.0.1: - version "1.0.4" - resolved "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz#f78978a1e944775ff9e62e744424f215e58352b5" - integrity sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q== - dependencies: - minimatch "^5.0.1" - filename-reserved-regex@^2.0.0: version "2.0.0" - resolved "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-2.0.0.tgz#abf73dfab735d045440abfea2d91f389ebbfa229" + resolved "https://registry.yarnpkg.com/filename-reserved-regex/-/filename-reserved-regex-2.0.0.tgz#abf73dfab735d045440abfea2d91f389ebbfa229" integrity sha512-lc1bnsSr4L4Bdif8Xb/qrtokGbq5zlsms/CYH8PP+WtCkGNF65DPiQY8vG3SakEdRn8Dlnm+gW/qWKKjS5sZzQ== filenamify@^4.3.0: version "4.3.0" - resolved "https://registry.npmjs.org/filenamify/-/filenamify-4.3.0.tgz#62391cb58f02b09971c9d4f9d63b3cf9aba03106" + resolved "https://registry.yarnpkg.com/filenamify/-/filenamify-4.3.0.tgz#62391cb58f02b09971c9d4f9d63b3cf9aba03106" integrity sha512-hcFKyUG57yWGAzu1CMt/dPzYZuv+jAJUT85bL8mrXvNe6hWj6yEHEc4EdcgiA6Z3oi1/9wXJdZPXF2dZNgwgOg== dependencies: filename-reserved-regex "^2.0.0" strip-outer "^1.0.1" trim-repeated "^1.0.0" -filesize@^8.0.6: - version "8.0.7" - resolved "https://registry.npmjs.org/filesize/-/filesize-8.0.7.tgz#695e70d80f4e47012c132d57a059e80c6b580bd8" - integrity sha512-pjmC+bkIF8XI7fWaH8KxHcZL3DPybs1roSKP4rKDvy20tAWwIObE4+JIseG2byfGKhud5ZnM4YSGKBz7Sh0ndQ== - fill-range@^7.0.1: version "7.0.1" - resolved "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== dependencies: to-regex-range "^5.0.1" -finalhandler@1.2.0: - version "1.2.0" - resolved "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz#7d23fe5731b207b4640e4fcd00aec1f9207a7b32" - integrity sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg== - dependencies: - debug "2.6.9" - encodeurl "~1.0.2" - escape-html "~1.0.3" - on-finished "2.4.1" - parseurl "~1.3.3" - statuses "2.0.1" - unpipe "~1.0.0" - find-cache-dir@^3.3.1: version "3.3.2" - resolved "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz#b30c5b6eff0730731aea9bbd9dbecbd80256d64b" + resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-3.3.2.tgz#b30c5b6eff0730731aea9bbd9dbecbd80256d64b" integrity sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig== dependencies: commondir "^1.0.1" make-dir "^3.0.2" pkg-dir "^4.1.0" -find-up@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73" - integrity sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg== - dependencies: - locate-path "^3.0.0" - -find-up@^4.0.0, find-up@^4.1.0: +find-up@^4.0.0: version "4.1.0" - resolved "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== dependencies: locate-path "^5.0.0" @@ -5534,253 +2829,168 @@ find-up@^4.0.0, find-up@^4.1.0: find-up@^5.0.0: version "5.0.0" - resolved "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== dependencies: locate-path "^6.0.0" path-exists "^4.0.0" flat-cache@^3.0.4: - version "3.0.4" - resolved "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11" - integrity sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg== + version "3.1.1" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.1.1.tgz#a02a15fdec25a8f844ff7cc658f03dd99eb4609b" + integrity sha512-/qM2b3LUIaIgviBQovTLvijfyOQXPtSRnRK26ksj2J7rzPIecePUIpJsZ4T02Qg+xiAEKIs5K8dsHEd+VaKa/Q== dependencies: - flatted "^3.1.0" + flatted "^3.2.9" + keyv "^4.5.3" rimraf "^3.0.2" -flatted@^3.1.0: - version "3.2.7" - resolved "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz#609f39207cb614b89d0765b477cb2d437fbf9787" - integrity sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ== +flatted@^3.2.9: + version "3.2.9" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.9.tgz#7eb4c67ca1ba34232ca9d2d93e9886e611ad7daf" + integrity sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ== -follow-redirects@^1.0.0: - version "1.15.2" - resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13" - integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA== +follow-redirects@^1.14.0: + version "1.15.3" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.3.tgz#fe2f3ef2690afce7e82ed0b44db08165b207123a" + integrity sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q== for-each@^0.3.3: version "0.3.3" - resolved "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e" + resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e" integrity sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw== dependencies: is-callable "^1.1.3" -fork-ts-checker-webpack-plugin@^6.5.0: - version "6.5.2" - resolved "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-6.5.2.tgz#4f67183f2f9eb8ba7df7177ce3cf3e75cdafb340" - integrity sha512-m5cUmF30xkZ7h4tWUgTAcEaKmUW7tfyUyTqNNOz7OxWJ0v1VWKTcOvH8FWHUwSjlW/356Ijc9vi3XfcPstpQKA== - dependencies: - "@babel/code-frame" "^7.8.3" - "@types/json-schema" "^7.0.5" - chalk "^4.1.0" - chokidar "^3.4.2" - cosmiconfig "^6.0.0" - deepmerge "^4.2.2" - fs-extra "^9.0.0" - glob "^7.1.6" - memfs "^3.1.2" - minimatch "^3.0.4" - schema-utils "2.7.0" - semver "^7.3.2" - tapable "^1.0.0" - -form-data@^3.0.0: - version "3.0.1" - resolved "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz#ebd53791b78356a99af9a300d4282c4d5eb9755f" - integrity sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg== - dependencies: - asynckit "^0.4.0" - combined-stream "^1.0.8" - mime-types "^2.1.12" - formdata-polyfill@^4.0.10: version "4.0.10" - resolved "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz#24807c31c9d402e002ab3d8c720144ceb8848423" + resolved "https://registry.yarnpkg.com/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz#24807c31c9d402e002ab3d8c720144ceb8848423" integrity sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g== dependencies: fetch-blob "^3.1.2" -forwarded@0.2.0: - version "0.2.0" - resolved "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" - integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== - -fraction.js@^4.2.0: - version "4.2.0" - resolved "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz#448e5109a313a3527f5a3ab2119ec4cf0e0e2950" - integrity sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA== - -framer-motion@^7.6.7: - version "7.6.12" - resolved "https://registry.npmjs.org/framer-motion/-/framer-motion-7.6.12.tgz#a1f228e00da03ceb78482ae4a7fba63be39d34e4" - integrity sha512-Xm1jPB91v6vIr9b+V3q6c96sPzU1jWdvN+Mv4CvIAY23ZIezGNIWxWNGIWj1We0CZ8SSOQkttz8921M10TQjXg== - dependencies: - "@motionone/dom" "10.13.1" - framesync "6.1.2" - hey-listen "^1.0.8" - popmotion "11.0.5" - style-value-types "5.1.2" - tslib "2.4.0" +framer-motion@^10.15.0, framer-motion@^10.16.3: + version "10.16.4" + resolved "https://registry.yarnpkg.com/framer-motion/-/framer-motion-10.16.4.tgz#30279ef5499b8d85db3a298ee25c83429933e9f8" + integrity sha512-p9V9nGomS3m6/CALXqv6nFGMuFOxbWsmaOrdmhyQimMIlLl3LC7h7l86wge/Js/8cRu5ktutS/zlzgR7eBOtFA== + dependencies: + tslib "^2.4.0" optionalDependencies: "@emotion/is-prop-valid" "^0.8.2" -framesync@6.1.2: - version "6.1.2" - resolved "https://registry.npmjs.org/framesync/-/framesync-6.1.2.tgz#755eff2fb5b8f3b4d2b266dd18121b300aefea27" - integrity sha512-jBTqhX6KaQVDyus8muwZbBeGGP0XgujBRbQ7gM7BRdS3CadCZIHiawyzYLnafYcvZIh5j8WE7cxZKFn7dXhu9g== - dependencies: - tslib "2.4.0" - -fresh@0.5.2: - version "0.5.2" - resolved "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" - integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== - -fs-extra@^10.0.0: - version "10.1.0" - resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz#02873cfbc4084dde127eaa5f9905eef2325d1abf" - integrity sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ== - dependencies: - graceful-fs "^4.2.0" - jsonfile "^6.0.1" - universalify "^2.0.0" - -fs-extra@^8.1.0: - version "8.1.0" - resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0" - integrity sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g== - dependencies: - graceful-fs "^4.2.0" - jsonfile "^4.0.0" - universalify "^0.1.0" - -fs-extra@^9.0.0, fs-extra@^9.0.1: - version "9.1.0" - resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz#5954460c764a8da2094ba3554bf839e6b9a7c86d" - integrity sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ== +fs-extra@^11.1.0, fs-extra@^11.1.1: + version "11.1.1" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-11.1.1.tgz#da69f7c39f3b002378b0954bb6ae7efdc0876e2d" + integrity sha512-MGIE4HOvQCeUCzmlHs0vXpih4ysz4wg9qiSAu6cd42lVwPbTM1TjV7RusoyQqMmk/95gdQZX72u+YW+c3eEpFQ== dependencies: - at-least-node "^1.0.0" graceful-fs "^4.2.0" jsonfile "^6.0.1" universalify "^2.0.0" -fs-monkey@^1.0.3: - version "1.0.3" - resolved "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.3.tgz#ae3ac92d53bb328efe0e9a1d9541f6ad8d48e2d3" - integrity sha512-cybjIfiiE+pTWicSCLFHSrXZ6EilF30oh91FDP9S2B051prEa7QWfrVTQm10/dDpswBDXZugPa1Ogu8Yh+HV0Q== - fs.realpath@^1.0.0: version "1.0.0" - resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== -fsevents@^2.3.2, fsevents@~2.3.2: - version "2.3.2" - resolved "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" - integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== +fsevents@~2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== -function-bind@^1.1.1: - version "1.1.1" - resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" - integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== +function-bind@^1.1.1, function-bind@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" + integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== -function.prototype.name@^1.1.5: - version "1.1.5" - resolved "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz#cce0505fe1ffb80503e6f9e46cc64e46a12a9621" - integrity sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA== +function.prototype.name@^1.1.5, function.prototype.name@^1.1.6: + version "1.1.6" + resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.6.tgz#cdf315b7d90ee77a4c6ee216c3c3362da07533fd" + integrity sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg== dependencies: call-bind "^1.0.2" - define-properties "^1.1.3" - es-abstract "^1.19.0" - functions-have-names "^1.2.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + functions-have-names "^1.2.3" -functions-have-names@^1.2.2: +functions-have-names@^1.2.3: version "1.2.3" - resolved "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834" + resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834" integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== gensync@^1.0.0-beta.2: version "1.0.0-beta.2" - resolved "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" + resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== get-caller-file@^2.0.5: version "2.0.5" - resolved "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== -get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3: - version "1.1.3" - resolved "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz#063c84329ad93e83893c7f4f243ef63ffa351385" - integrity sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A== +get-func-name@^2.0.1, get-func-name@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.2.tgz#0d7cf20cd13fda808669ffa88f4ffc7a3943fc41" + integrity sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ== + +get-intrinsic@^1.0.2, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3, get-intrinsic@^1.2.0, get-intrinsic@^1.2.1, get-intrinsic@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.2.tgz#281b7622971123e1ef4b3c90fd7539306da93f3b" + integrity sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA== dependencies: - function-bind "^1.1.1" - has "^1.0.3" + function-bind "^1.1.2" + has-proto "^1.0.1" has-symbols "^1.0.3" + hasown "^2.0.0" -get-own-enumerable-property-symbols@^3.0.0: - version "3.0.2" - resolved "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz#b5fde77f22cbe35f390b4e089922c50bce6ef664" - integrity sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g== - -get-package-type@^0.1.0: - version "0.1.0" - resolved "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" - integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== - -get-stream@^6.0.0: +get-stream@^6.0.0, get-stream@^6.0.1: version "6.0.1" - resolved "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== get-symbol-description@^1.0.0: version "1.0.0" - resolved "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz#7fdb81c900101fbd564dd5f1a30af5aadc1e58d6" + resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.0.0.tgz#7fdb81c900101fbd564dd5f1a30af5aadc1e58d6" integrity sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw== dependencies: call-bind "^1.0.2" get-intrinsic "^1.1.1" -get-tsconfig@^4.2.0: - version "4.2.0" - resolved "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.2.0.tgz#ff368dd7104dab47bf923404eb93838245c66543" - integrity sha512-X8u8fREiYOE6S8hLbq99PeykTDoLVnxvF4DjWKJmz9xy2nNRdUcV8ZN9tniJFeKyTU3qnC9lL8n4Chd6LmVKHg== +get-tsconfig@^4.5.0: + version "4.7.2" + resolved "https://registry.yarnpkg.com/get-tsconfig/-/get-tsconfig-4.7.2.tgz#0dcd6fb330391d46332f4c6c1bf89a6514c2ddce" + integrity sha512-wuMsz4leaj5hbGgg4IvDU0bqJagpftG5l5cXIAvo8uZrqn0NJqwtfupTN00VnkQJPcIRrxYrm1Ue24btpCha2A== + dependencies: + resolve-pkg-maps "^1.0.0" -gh-pages@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/gh-pages/-/gh-pages-4.0.0.tgz#bd7447bab7eef008f677ac8cc4f6049ab978f4a6" - integrity sha512-p8S0T3aGJc68MtwOcZusul5qPSNZCalap3NWbhRUZYu1YOdp+EjZ+4kPmRM8h3NNRdqw00yuevRjlkuSzCn7iQ== +gh-pages@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/gh-pages/-/gh-pages-6.0.0.tgz#3bb46ea13dc7cee306662db0d3f02bf05635cdc1" + integrity sha512-FXZWJRsvP/fK2HJGY+Di6FRNHvqFF6gOIELaopDjXXgjeOYSNURcuYwEO/6bwuq6koP5Lnkvnr5GViXzuOB89g== dependencies: - async "^2.6.1" - commander "^2.18.0" - email-addresses "^3.0.1" + async "^3.2.4" + commander "^11.0.0" + email-addresses "^5.0.0" filenamify "^4.3.0" find-cache-dir "^3.3.1" - fs-extra "^8.1.0" + fs-extra "^11.1.1" globby "^6.1.0" glob-parent@^5.1.2, glob-parent@~5.1.2: version "5.1.2" - resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== dependencies: is-glob "^4.0.1" glob-parent@^6.0.2: version "6.0.2" - resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== dependencies: is-glob "^4.0.3" -glob-to-regexp@^0.4.1: - version "0.4.1" - resolved "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e" - integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== - -glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: +glob@^7.0.3, glob@^7.1.3: version "7.2.3" - resolved "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== dependencies: fs.realpath "^1.0.0" @@ -5790,42 +3000,28 @@ glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: once "^1.3.0" path-is-absolute "^1.0.0" -global-modules@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz#997605ad2345f27f51539bea26574421215c7780" - integrity sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A== - dependencies: - global-prefix "^3.0.0" - -global-prefix@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz#fc85f73064df69f50421f47f883fe5b913ba9b97" - integrity sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg== - dependencies: - ini "^1.3.5" - kind-of "^6.0.2" - which "^1.3.1" - globals@^11.1.0: version "11.12.0" - resolved "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" + resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== -globals@^13.15.0: - version "13.18.0" - resolved "https://registry.npmjs.org/globals/-/globals-13.18.0.tgz#fb224daeeb2bb7d254cd2c640f003528b8d0c1dc" - integrity sha512-/mR4KI8Ps2spmoc0Ulu9L7agOF0du1CZNQ3dke8yItYlyKNmGrkONemBbd6V8UTc1Wgcqn21t3WYB7dbRmh6/A== +globals@^13.19.0: + version "13.23.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-13.23.0.tgz#ef31673c926a0976e1f61dab4dca57e0c0a8af02" + integrity sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA== dependencies: type-fest "^0.20.2" -globalyzer@0.1.0: - version "0.1.0" - resolved "https://registry.npmjs.org/globalyzer/-/globalyzer-0.1.0.tgz#cb76da79555669a1519d5a8edf093afaa0bf1465" - integrity sha512-40oNTM9UfG6aBmuKxk/giHn5nQ8RVz/SS4Ir6zgzOv9/qC3kKZ9v4etGTcJbEl/NyVQH7FGU7d+X1egr57Md2Q== +globalthis@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.3.tgz#5852882a52b80dc301b0660273e1ed082f0b6ccf" + integrity sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA== + dependencies: + define-properties "^1.1.3" -globby@^11.0.4, globby@^11.1.0: +globby@^11.1.0: version "11.1.0" - resolved "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" + resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== dependencies: array-union "^2.1.0" @@ -5835,20 +3031,9 @@ globby@^11.0.4, globby@^11.1.0: merge2 "^1.4.1" slash "^3.0.0" -globby@^13.1.2: - version "13.1.2" - resolved "https://registry.npmjs.org/globby/-/globby-13.1.2.tgz#29047105582427ab6eca4f905200667b056da515" - integrity sha512-LKSDZXToac40u8Q1PQtZihbNdTYSNMuWe+K5l+oa6KgDzSvVrHXlJy40hUP522RjAIoNLJYBJi7ow+rbFpIhHQ== - dependencies: - dir-glob "^3.0.1" - fast-glob "^3.2.11" - ignore "^5.2.0" - merge2 "^1.4.1" - slash "^4.0.0" - globby@^6.1.0: version "6.1.0" - resolved "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz#f5a6d70e8395e21c858fb0489d64df02424d506c" + resolved "https://registry.yarnpkg.com/globby/-/globby-6.1.0.tgz#f5a6d70e8395e21c858fb0489d64df02424d506c" integrity sha512-KVbFv2TQtbzCoxAnfD6JcHZTYCzyliEaaeM/gH8qQdkKr5s0OP9scEgvdcngyk7AVdY6YVW/TJHd+lQ/Df3Daw== dependencies: array-union "^1.0.1" @@ -5859,25 +3044,25 @@ globby@^6.1.0: globrex@^0.1.2: version "0.1.2" - resolved "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz#dd5d9ec826232730cd6793a5e33a9302985e6098" + resolved "https://registry.yarnpkg.com/globrex/-/globrex-0.1.2.tgz#dd5d9ec826232730cd6793a5e33a9302985e6098" integrity sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg== gopd@^1.0.1: version "1.0.1" - resolved "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c" + resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c" integrity sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA== dependencies: get-intrinsic "^1.1.3" -graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4, graceful-fs@^4.2.6, graceful-fs@^4.2.9: - version "4.2.10" - resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c" - integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA== +graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4: + version "4.2.11" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" + integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== -grapheme-splitter@^1.0.4: - version "1.0.4" - resolved "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz#9cf3a665c6247479896834af35cf1dbb4400767e" - integrity sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ== +graphemer@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" + integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== graphql-tag@^2.12.6: version "2.12.6" @@ -5891,385 +3076,209 @@ graphql@^16.6.0: resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.8.1.tgz#1930a965bef1170603702acdb68aedd3f3cf6f07" integrity sha512-59LZHPdGZVh695Ud9lRzPBVTtlX9ZCV150Er2W43ro37wVof0ctenSaskPPjN7lVTIN8mSZt8PHUNKZuNQUuxw== -gzip-size@^6.0.0: - version "6.0.0" - resolved "https://registry.npmjs.org/gzip-size/-/gzip-size-6.0.0.tgz#065367fd50c239c0671cbcbad5be3e2eeb10e462" - integrity sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q== - dependencies: - duplexer "^0.1.2" - -handle-thing@^2.0.0: - version "2.0.1" - resolved "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz#857f79ce359580c340d43081cc648970d0bb234e" - integrity sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg== - -harmony-reflect@^1.4.6: - version "1.6.2" - resolved "https://registry.npmjs.org/harmony-reflect/-/harmony-reflect-1.6.2.tgz#31ecbd32e648a34d030d86adb67d4d47547fe710" - integrity sha512-HIp/n38R9kQjDEziXyDTuW3vvoxxyxjxFzXLrBr18uB47GnSt+G9D29fqrpM5ZkspMcPICud3XsBJQ4Y2URg8g== - has-bigints@^1.0.1, has-bigints@^1.0.2: version "1.0.2" - resolved "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz#0871bd3e3d51626f6ca0966668ba35d5602d6eaa" + resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.2.tgz#0871bd3e3d51626f6ca0966668ba35d5602d6eaa" integrity sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ== has-flag@^3.0.0: version "3.0.0" - resolved "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== has-flag@^4.0.0: version "4.0.0" - resolved "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== -has-property-descriptors@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz#610708600606d36961ed04c196193b6a607fa861" - integrity sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ== - dependencies: - get-intrinsic "^1.1.1" - -has-symbols@^1.0.1, has-symbols@^1.0.2, has-symbols@^1.0.3: - version "1.0.3" - resolved "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" - integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== - -has-tostringtag@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz#7e133818a7d394734f941e73c3d3f9291e658b25" - integrity sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ== - dependencies: - has-symbols "^1.0.2" - -has@^1.0.3: - version "1.0.3" - resolved "https://registry.npmjs.org/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" - integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== - dependencies: - function-bind "^1.1.1" - -he@^1.2.0: - version "1.2.0" - resolved "https://registry.npmjs.org/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" - integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== - -hey-listen@^1.0.8: - version "1.0.8" - resolved "https://registry.npmjs.org/hey-listen/-/hey-listen-1.0.8.tgz#8e59561ff724908de1aa924ed6ecc84a56a9aa68" - integrity sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q== - -hoist-non-react-statics@^3.0.0, hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.2: - version "3.3.2" - resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" - integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== - dependencies: - react-is "^16.7.0" - -hoopy@^0.1.4: - version "0.1.4" - resolved "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz#609207d661100033a9a9402ad3dea677381c1b1d" - integrity sha512-HRcs+2mr52W0K+x8RzcLzuPPmVIKMSv97RGHy0Ea9y/mpcaK+xTrjICA04KAHi4GRzxliNqNJEFYWHghy3rSfQ== - -hpack.js@^2.1.6: - version "2.1.6" - resolved "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz#87774c0949e513f42e84575b3c45681fade2a0b2" - integrity sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ== - dependencies: - inherits "^2.0.1" - obuf "^1.0.0" - readable-stream "^2.0.1" - wbuf "^1.1.0" - -html-encoding-sniffer@^2.0.1: - version "2.0.1" - resolved "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz#42a6dc4fd33f00281176e8b23759ca4e4fa185f3" - integrity sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ== - dependencies: - whatwg-encoding "^1.0.5" - -html-entities@^2.1.0, html-entities@^2.3.2: - version "2.3.3" - resolved "https://registry.npmjs.org/html-entities/-/html-entities-2.3.3.tgz#117d7626bece327fc8baace8868fa6f5ef856e46" - integrity sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA== - -html-escaper@^2.0.0: - version "2.0.2" - resolved "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" - integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== - -html-minifier-terser@^6.0.2: - version "6.1.0" - resolved "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz#bfc818934cc07918f6b3669f5774ecdfd48f32ab" - integrity sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw== - dependencies: - camel-case "^4.1.2" - clean-css "^5.2.2" - commander "^8.3.0" - he "^1.2.0" - param-case "^3.0.4" - relateurl "^0.2.7" - terser "^5.10.0" - -html-parse-stringify@^3.0.1: - version "3.0.1" - resolved "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz#dfc1017347ce9f77c8141a507f233040c59c55d2" - integrity sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg== - dependencies: - void-elements "3.1.0" - -html-webpack-plugin@^5.5.0: - version "5.5.0" - resolved "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.5.0.tgz#c3911936f57681c1f9f4d8b68c158cd9dfe52f50" - integrity sha512-sy88PC2cRTVxvETRgUHFrL4No3UxvcH8G1NepGhqaTT+GXN2kTamqasot0inS5hXeg1cMbFDt27zzo9p35lZVw== +has-property-descriptors@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz#52ba30b6c5ec87fd89fa574bc1c39125c6f65340" + integrity sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg== dependencies: - "@types/html-minifier-terser" "^6.0.0" - html-minifier-terser "^6.0.2" - lodash "^4.17.21" - pretty-error "^4.0.0" - tapable "^2.0.0" + get-intrinsic "^1.2.2" -htmlparser2@^6.1.0: - version "6.1.0" - resolved "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz#c4d762b6c3371a05dbe65e94ae43a9f845fb8fb7" - integrity sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A== - dependencies: - domelementtype "^2.0.1" - domhandler "^4.0.0" - domutils "^2.5.2" - entities "^2.0.0" +has-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.1.tgz#1885c1305538958aff469fef37937c22795408e0" + integrity sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg== -http-deceiver@^1.2.7: - version "1.2.7" - resolved "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87" - integrity sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw== +has-symbols@^1.0.2, has-symbols@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" + integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== -http-errors@2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3" - integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ== +has-tostringtag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.0.tgz#7e133818a7d394734f941e73c3d3f9291e658b25" + integrity sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ== dependencies: - depd "2.0.0" - inherits "2.0.4" - setprototypeof "1.2.0" - statuses "2.0.1" - toidentifier "1.0.1" + has-symbols "^1.0.2" -http-errors@~1.6.2: - version "1.6.3" - resolved "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz#8b55680bb4be283a0b5bf4ea2e38580be1d9320d" - integrity sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A== +has@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.4.tgz#2eb2860e000011dae4f1406a86fe80e530fb2ec6" + integrity sha512-qdSAmqLF6209RFj4VVItywPMbm3vWylknmB3nvNiUIs72xAimcM8nVYxYr7ncvZq5qzk9MKIZR8ijqD/1QuYjQ== + +hash-base@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.1.0.tgz#55c381d9e06e1d2997a883b4a3fddfe7f0d3af33" + integrity sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA== dependencies: - depd "~1.1.2" - inherits "2.0.3" - setprototypeof "1.1.0" - statuses ">= 1.4.0 < 2" + inherits "^2.0.4" + readable-stream "^3.6.0" + safe-buffer "^5.2.0" -http-parser-js@>=0.5.1: - version "0.5.8" - resolved "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz#af23090d9ac4e24573de6f6aecc9d84a48bf20e3" - integrity sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q== +hash.js@^1.0.0, hash.js@^1.0.3, hash.js@^1.1.7: + version "1.1.7" + resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42" + integrity sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA== + dependencies: + inherits "^2.0.3" + minimalistic-assert "^1.0.1" -http-proxy-agent@^4.0.1: - version "4.0.1" - resolved "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz#8a8c8ef7f5932ccf953c296ca8291b95aa74aa3a" - integrity sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg== +hasown@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.0.tgz#f4c513d454a57b7c7e1650778de226b11700546c" + integrity sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA== dependencies: - "@tootallnate/once" "1" - agent-base "6" - debug "4" + function-bind "^1.1.2" -http-proxy-middleware@^2.0.3: - version "2.0.6" - resolved "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz#e1a4dd6979572c7ab5a4e4b55095d1f32a74963f" - integrity sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw== +hmac-drbg@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" + integrity sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg== dependencies: - "@types/http-proxy" "^1.17.8" - http-proxy "^1.18.1" - is-glob "^4.0.1" - is-plain-obj "^3.0.0" - micromatch "^4.0.2" + hash.js "^1.0.3" + minimalistic-assert "^1.0.0" + minimalistic-crypto-utils "^1.0.1" -http-proxy@^1.18.1: - version "1.18.1" - resolved "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz#401541f0534884bbf95260334e72f88ee3976549" - integrity sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ== +hoist-non-react-statics@^3.3.2: + version "3.3.2" + resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" + integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== dependencies: - eventemitter3 "^4.0.0" - follow-redirects "^1.0.0" - requires-port "^1.0.0" + react-is "^16.7.0" -https-proxy-agent@^5.0.0: - version "5.0.1" - resolved "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6" - integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA== +html-parse-stringify@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz#dfc1017347ce9f77c8141a507f233040c59c55d2" + integrity sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg== dependencies: - agent-base "6" - debug "4" + void-elements "3.1.0" human-signals@^2.1.0: version "2.1.0" - resolved "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== -i18next-browser-languagedetector@^7.0.1: - version "7.0.1" - resolved "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-7.0.1.tgz#ead34592edc96c6c3a618a51cb57ad027c5b5d87" - integrity sha512-Pa5kFwaczXJAeHE56CHG2aWzFBMJNUNghf0Pm4SwSrEMps/PTKqW90EYWlIvhuYStf3Sn1K0vw+gH3+TLdkH1g== - dependencies: - "@babel/runtime" "^7.19.4" - -i18next@^22.0.6: - version "22.0.6" - resolved "https://registry.npmjs.org/i18next/-/i18next-22.0.6.tgz#d7029912f8aa74ff295c0d9afd1b7dea45859b49" - integrity sha512-RlreNGoPIdDP4QG+qSA9PxZKGwlzmcozbI9ObI6+OyUa/Rp0EjZZA9ubyBjw887zVNZsC+7FI3sXX8oiTzAfig== - dependencies: - "@babel/runtime" "^7.17.2" +human-signals@^4.3.0: + version "4.3.1" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-4.3.1.tgz#ab7f811e851fca97ffbd2c1fe9a958964de321b2" + integrity sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ== -iconv-lite@0.4.24: - version "0.4.24" - resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" - integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== +i18next-browser-languagedetector@^7.1.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/i18next-browser-languagedetector/-/i18next-browser-languagedetector-7.1.0.tgz#01876fac51f86b78975e79b48ccb62e2313a2d7d" + integrity sha512-cr2k7u1XJJ4HTOjM9GyOMtbOA47RtUoWRAtt52z43r3AoMs2StYKyjS3URPhzHaf+mn10hY9dZWamga5WPQjhA== dependencies: - safer-buffer ">= 2.1.2 < 3" + "@babel/runtime" "^7.19.4" -iconv-lite@^0.6.3: - version "0.6.3" - resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" - integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== +i18next@^23.6.0: + version "23.6.0" + resolved "https://registry.yarnpkg.com/i18next/-/i18next-23.6.0.tgz#c6e996cfd3fef0bf60be3b7c581c35338dba5a71" + integrity sha512-z0Cxr0MGkt+kli306WS4nNNM++9cgt2b2VCMprY92j+AIab/oclgPxdwtTZVLP1zn5t5uo8M6uLsZmYrcjr3HA== dependencies: - safer-buffer ">= 2.1.2 < 3.0.0" + "@babel/runtime" "^7.22.5" -icss-utils@^5.0.0, icss-utils@^5.1.0: - version "5.1.0" - resolved "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz#c6be6858abd013d768e98366ae47e25d5887b1ae" - integrity sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA== - -idb@^7.0.1: - version "7.1.1" - resolved "https://registry.npmjs.org/idb/-/idb-7.1.1.tgz#d910ded866d32c7ced9befc5bfdf36f572ced72b" - integrity sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ== - -identity-obj-proxy@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/identity-obj-proxy/-/identity-obj-proxy-3.0.0.tgz#94d2bda96084453ef36fbc5aaec37e0f79f1fc14" - integrity sha512-00n6YnVHKrinT9t0d9+5yZC6UBNJANpYEQvL2LlX6Ab9lnmxzIRcEmTPuyGScvl1+jKuCICX1Z0Ab1pPKKdikA== - dependencies: - harmony-reflect "^1.4.6" +ieee754@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" + integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== -ignore@^5.2.0: - version "5.2.0" - resolved "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a" - integrity sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ== +ignore@^5.2.0, ignore@^5.2.4: + version "5.2.4" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.4.tgz#a291c0c6178ff1b960befe47fcdec301674a6324" + integrity sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ== -immer@^9.0.7: - version "9.0.16" - resolved "https://registry.npmjs.org/immer/-/immer-9.0.16.tgz#8e7caab80118c2b54b37ad43e05758cdefad0198" - integrity sha512-qenGE7CstVm1NrHQbMh8YaSzTZTFNP3zPqr3YU0S0UY441j4bJTg4A2Hh5KAhwgaiU6ZZ1Ar6y/2f4TblnMReQ== +immutable@^4.0.0: + version "4.3.4" + resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.3.4.tgz#2e07b33837b4bb7662f288c244d1ced1ef65a78f" + integrity sha512-fsXeu4J4i6WNWSikpI88v/PcVflZz+6kMhUfIwc5SY+poQRPnaf5V7qds6SUyUN3cVxEzuCab7QIoLOQ+DQ1wA== -import-fresh@^3.0.0, import-fresh@^3.1.0, import-fresh@^3.2.1: +import-fresh@^3.2.1, import-fresh@^3.3.0: version "3.3.0" - resolved "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== dependencies: parent-module "^1.0.0" resolve-from "^4.0.0" -import-local@^3.0.2: - version "3.1.0" - resolved "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz#b4479df8a5fd44f6cdce24070675676063c95cb4" - integrity sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg== - dependencies: - pkg-dir "^4.2.0" - resolve-cwd "^3.0.0" - imurmurhash@^0.1.4: version "0.1.4" - resolved "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== -indent-string@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" - integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== - inflight@^1.0.4: version "1.0.6" - resolved "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== dependencies: once "^1.3.0" wrappy "1" -inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.3: +inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4: version "2.0.4" - resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== -inherits@2.0.3: - version "2.0.3" - resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" - integrity sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw== - -ini@^1.3.5: - version "1.3.8" - resolved "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" - integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== - -internal-slot@^1.0.3: - version "1.0.3" - resolved "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz#7347e307deeea2faac2ac6205d4bc7d34967f59c" - integrity sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA== +internal-slot@^1.0.5: + version "1.0.6" + resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.6.tgz#37e756098c4911c5e912b8edbf71ed3aa116f930" + integrity sha512-Xj6dv+PsbtwyPpEflsejS+oIZxmMlV44zAhG479uYu89MsjcYOhCFnNyKrkJrihbsiasQyY0afoCl/9BLR65bg== dependencies: - get-intrinsic "^1.1.0" - has "^1.0.3" + get-intrinsic "^1.2.2" + hasown "^2.0.0" side-channel "^1.0.4" -ipaddr.js@1.9.1: - version "1.9.1" - resolved "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" - integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== - -ipaddr.js@^2.0.1: - version "2.0.1" - resolved "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.0.1.tgz#eca256a7a877e917aeb368b0a7497ddf42ef81c0" - integrity sha512-1qTgH9NG+IIJ4yfKs2e6Pp1bZg8wbDbKHT21HrLIeYBTRLgMYKnMTPAuI3Lcs61nfx5h1xlXnbJtH1kX5/d/ng== - -is-arguments@^1.1.0, is-arguments@^1.1.1: - version "1.1.1" - resolved "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz#15b3f88fda01f2a97fec84ca761a560f123efa9b" - integrity sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA== +is-array-buffer@^3.0.1, is-array-buffer@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.2.tgz#f2653ced8412081638ecb0ebbd0c41c6e0aecbbe" + integrity sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w== dependencies: call-bind "^1.0.2" - has-tostringtag "^1.0.0" + get-intrinsic "^1.2.0" + is-typed-array "^1.1.10" is-arrayish@^0.2.1: version "0.2.1" - resolved "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== -is-arrayish@^0.3.1: - version "0.3.2" - resolved "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03" - integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== +is-async-function@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-async-function/-/is-async-function-2.0.0.tgz#8e4418efd3e5d3a6ebb0164c05ef5afb69aa9646" + integrity sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA== + dependencies: + has-tostringtag "^1.0.0" is-bigint@^1.0.1: version "1.0.4" - resolved "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz#08147a1875bc2b32005d41ccd8291dffc6691df3" + resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.4.tgz#08147a1875bc2b32005d41ccd8291dffc6691df3" integrity sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg== dependencies: has-bigints "^1.0.1" is-binary-path@~2.1.0: version "2.1.0" - resolved "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== dependencies: binary-extensions "^2.0.0" is-boolean-object@^1.1.0: version "1.1.2" - resolved "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz#5c6dc200246dd9321ae4b885a114bb1f75f63719" + resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.2.tgz#5c6dc200246dd9321ae4b885a114bb1f75f63719" integrity sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA== dependencies: call-bind "^1.0.2" @@ -6277,1424 +3286,597 @@ is-boolean-object@^1.1.0: is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.2.7: version "1.2.7" - resolved "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA== -is-core-module@^2.10.0, is-core-module@^2.8.1, is-core-module@^2.9.0: - version "2.11.0" - resolved "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz#ad4cb3e3863e814523c96f3f58d26cc570ff0144" - integrity sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw== +is-core-module@^2.11.0, is-core-module@^2.13.0, is-core-module@^2.13.1: + version "2.13.1" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.13.1.tgz#ad0d7532c6fea9da1ebdc82742d74525c6273384" + integrity sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw== dependencies: - has "^1.0.3" + hasown "^2.0.0" is-date-object@^1.0.1, is-date-object@^1.0.5: version "1.0.5" - resolved "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz#0841d5536e724c25597bf6ea62e1bd38298df31f" + resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.5.tgz#0841d5536e724c25597bf6ea62e1bd38298df31f" integrity sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ== dependencies: has-tostringtag "^1.0.0" is-docker@^2.0.0, is-docker@^2.1.1: version "2.2.1" - resolved "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" + resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== +is-docker@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-3.0.0.tgz#90093aa3106277d8a77a5910dbae71747e15a200" + integrity sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ== + is-extglob@^2.1.1: version "2.1.1" - resolved "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== +is-finalizationregistry@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-finalizationregistry/-/is-finalizationregistry-1.0.2.tgz#c8749b65f17c133313e661b1289b95ad3dbd62e6" + integrity sha512-0by5vtUJs8iFQb5TYUHHPudOR+qXYIMKtiUzvLIZITZUjknFmziyBJuLhVRc+Ds0dREFlskDNJKYIdIzu/9pfw== + dependencies: + call-bind "^1.0.2" + is-fullwidth-code-point@^3.0.0: version "3.0.0" - resolved "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== -is-generator-fn@^2.0.0: - version "2.1.0" - resolved "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" - integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== +is-generator-function@^1.0.10: + version "1.0.10" + resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.0.10.tgz#f1558baf1ac17e0deea7c0415c438351ff2b3c72" + integrity sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A== + dependencies: + has-tostringtag "^1.0.0" is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: version "4.0.3" - resolved "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== dependencies: is-extglob "^2.1.1" -is-map@^2.0.1, is-map@^2.0.2: +is-inside-container@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-inside-container/-/is-inside-container-1.0.0.tgz#e81fba699662eb31dbdaf26766a61d4814717ea4" + integrity sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA== + dependencies: + is-docker "^3.0.0" + +is-map@^2.0.1: version "2.0.2" - resolved "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz#00922db8c9bf73e81b7a335827bc2a43f2b91127" + resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.2.tgz#00922db8c9bf73e81b7a335827bc2a43f2b91127" integrity sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg== -is-module@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz#3258fb69f78c14d5b815d664336b4cffb6441591" - integrity sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g== - is-negative-zero@^2.0.2: version "2.0.2" - resolved "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz#7bf6f03a28003b8b3965de3ac26f664d765f3150" + resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.2.tgz#7bf6f03a28003b8b3965de3ac26f664d765f3150" integrity sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA== is-number-object@^1.0.4: version "1.0.7" - resolved "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz#59d50ada4c45251784e9904f5246c742f07a42fc" + resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.7.tgz#59d50ada4c45251784e9904f5246c742f07a42fc" integrity sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ== dependencies: has-tostringtag "^1.0.0" is-number@^7.0.0: version "7.0.0" - resolved "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== -is-obj@^1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f" - integrity sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg== - is-path-inside@^3.0.3: - version "3.0.3" - resolved "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" - integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== - -is-plain-obj@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz#af6f2ea14ac5a646183a5bbdb5baabbc156ad9d7" - integrity sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA== - -is-potential-custom-element-name@^1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz#171ed6f19e3ac554394edf78caa05784a45bebb5" - integrity sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ== - -is-regex@^1.1.4: - version "1.1.4" - resolved "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958" - integrity sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg== - dependencies: - call-bind "^1.0.2" - has-tostringtag "^1.0.0" - -is-regexp@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz#fd2d883545c46bac5a633e7b9a09e87fa2cb5069" - integrity sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA== - -is-root@^2.1.0: - version "2.1.0" - resolved "https://registry.npmjs.org/is-root/-/is-root-2.1.0.tgz#809e18129cf1129644302a4f8544035d51984a9c" - integrity sha512-AGOriNp96vNBd3HtU+RzFEc75FfR5ymiYv8E553I71SCeXBiMsVDUtdio1OEFvrPyLIQ9tVR5RxXIFe5PUFjMg== - -is-set@^2.0.1, is-set@^2.0.2: - version "2.0.2" - resolved "https://registry.npmjs.org/is-set/-/is-set-2.0.2.tgz#90755fa4c2562dc1c5d4024760d6119b94ca18ec" - integrity sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g== - -is-shared-array-buffer@^1.0.2: - version "1.0.2" - resolved "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz#8f259c573b60b6a32d4058a1a07430c0a7344c79" - integrity sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA== - dependencies: - call-bind "^1.0.2" - -is-stream@^2.0.0: - version "2.0.1" - resolved "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" - integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== - -is-string@^1.0.5, is-string@^1.0.7: - version "1.0.7" - resolved "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz#0dd12bf2006f255bb58f695110eff7491eebc0fd" - integrity sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg== - dependencies: - has-tostringtag "^1.0.0" - -is-symbol@^1.0.2, is-symbol@^1.0.3: - version "1.0.4" - resolved "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz#a6dac93b635b063ca6872236de88910a57af139c" - integrity sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg== - dependencies: - has-symbols "^1.0.2" - -is-typed-array@^1.1.10: - version "1.1.10" - resolved "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz#36a5b5cb4189b575d1a3e4b08536bfb485801e3f" - integrity sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A== - dependencies: - available-typed-arrays "^1.0.5" - call-bind "^1.0.2" - for-each "^0.3.3" - gopd "^1.0.1" - has-tostringtag "^1.0.0" - -is-typedarray@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" - integrity sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA== - -is-weakmap@^2.0.1: - version "2.0.1" - resolved "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz#5008b59bdc43b698201d18f62b37b2ca243e8cf2" - integrity sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA== - -is-weakref@^1.0.2: - version "1.0.2" - resolved "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz#9529f383a9338205e89765e0392efc2f100f06f2" - integrity sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ== - dependencies: - call-bind "^1.0.2" - -is-weakset@^2.0.1: - version "2.0.2" - resolved "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.2.tgz#4569d67a747a1ce5a994dfd4ef6dcea76e7c0a1d" - integrity sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg== - dependencies: - call-bind "^1.0.2" - get-intrinsic "^1.1.1" - -is-wsl@^2.2.0: - version "2.2.0" - resolved "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" - integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== - dependencies: - is-docker "^2.0.0" - -isarray@^2.0.5: - version "2.0.5" - resolved "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723" - integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw== - -isarray@~1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" - integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ== - -isexe@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" - integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== - -istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: - version "3.2.0" - resolved "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz#189e7909d0a39fa5a3dfad5b03f71947770191d3" - integrity sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw== - -istanbul-lib-instrument@^5.0.4, istanbul-lib-instrument@^5.1.0: - version "5.2.1" - resolved "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz#d10c8885c2125574e1c231cacadf955675e1ce3d" - integrity sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg== - dependencies: - "@babel/core" "^7.12.3" - "@babel/parser" "^7.14.7" - "@istanbuljs/schema" "^0.1.2" - istanbul-lib-coverage "^3.2.0" - semver "^6.3.0" - -istanbul-lib-report@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#7518fe52ea44de372f460a76b5ecda9ffb73d8a6" - integrity sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw== - dependencies: - istanbul-lib-coverage "^3.0.0" - make-dir "^3.0.0" - supports-color "^7.1.0" - -istanbul-lib-source-maps@^4.0.0: - version "4.0.1" - resolved "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz#895f3a709fcfba34c6de5a42939022f3e4358551" - integrity sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw== - dependencies: - debug "^4.1.1" - istanbul-lib-coverage "^3.0.0" - source-map "^0.6.1" - -istanbul-reports@^3.1.3: - version "3.1.5" - resolved "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.5.tgz#cc9a6ab25cb25659810e4785ed9d9fb742578bae" - integrity sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w== - dependencies: - html-escaper "^2.0.0" - istanbul-lib-report "^3.0.0" - -iterall@^1.2.1: - version "1.3.0" - resolved "https://registry.yarnpkg.com/iterall/-/iterall-1.3.0.tgz#afcb08492e2915cbd8a0884eb93a8c94d0d72fea" - integrity sha512-QZ9qOMdF+QLHxy1QIpUHUU1D5pS2CG2P69LF6L6CPjPYA/XMOmKV3PZpawHoAjHNyB0swdVTRxdYT4tbBbxqwg== - -jake@^10.8.5: - version "10.8.5" - resolved "https://registry.npmjs.org/jake/-/jake-10.8.5.tgz#f2183d2c59382cb274226034543b9c03b8164c46" - integrity sha512-sVpxYeuAhWt0OTWITwT98oyV0GsXyMlXCF+3L1SuafBVUIr/uILGRB+NqwkzhgXKvoJpDIpQvqkUALgdmQsQxw== - dependencies: - async "^3.2.3" - chalk "^4.0.2" - filelist "^1.0.1" - minimatch "^3.0.4" - -jdenticon@3.1.1: - version "3.1.1" - resolved "https://registry.npmjs.org/jdenticon/-/jdenticon-3.1.1.tgz#76a5b81195d7147c635a1303adf314dd08bcd4cb" - integrity sha512-/m0Kk5ou7tPHjW6YovCysRETqPlFcTabWG96r8NbbSsEK+eQ3jHiGULaGOtp4XBqkRxWS1XMopLRGwdhet5ezw== - dependencies: - canvas-renderer "~2.2.0" - -jest-changed-files@^27.5.1: - version "27.5.1" - resolved "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-27.5.1.tgz#a348aed00ec9bf671cc58a66fcbe7c3dfd6a68f5" - integrity sha512-buBLMiByfWGCoMsLLzGUUSpAmIAGnbR2KJoMN10ziLhOLvP4e0SlypHnAel8iqQXTrcbmfEY9sSqae5sgUsTvw== - dependencies: - "@jest/types" "^27.5.1" - execa "^5.0.0" - throat "^6.0.1" - -jest-circus@^27.5.1: - version "27.5.1" - resolved "https://registry.npmjs.org/jest-circus/-/jest-circus-27.5.1.tgz#37a5a4459b7bf4406e53d637b49d22c65d125ecc" - integrity sha512-D95R7x5UtlMA5iBYsOHFFbMD/GVA4R/Kdq15f7xYWUfWHBto9NYRsOvnSauTgdF+ogCpJ4tyKOXhUifxS65gdw== - dependencies: - "@jest/environment" "^27.5.1" - "@jest/test-result" "^27.5.1" - "@jest/types" "^27.5.1" - "@types/node" "*" - chalk "^4.0.0" - co "^4.6.0" - dedent "^0.7.0" - expect "^27.5.1" - is-generator-fn "^2.0.0" - jest-each "^27.5.1" - jest-matcher-utils "^27.5.1" - jest-message-util "^27.5.1" - jest-runtime "^27.5.1" - jest-snapshot "^27.5.1" - jest-util "^27.5.1" - pretty-format "^27.5.1" - slash "^3.0.0" - stack-utils "^2.0.3" - throat "^6.0.1" - -jest-cli@^27.5.1: - version "27.5.1" - resolved "https://registry.npmjs.org/jest-cli/-/jest-cli-27.5.1.tgz#278794a6e6458ea8029547e6c6cbf673bd30b145" - integrity sha512-Hc6HOOwYq4/74/c62dEE3r5elx8wjYqxY0r0G/nFrLDPMFRu6RA/u8qINOIkvhxG7mMQ5EJsOGfRpI8L6eFUVw== - dependencies: - "@jest/core" "^27.5.1" - "@jest/test-result" "^27.5.1" - "@jest/types" "^27.5.1" - chalk "^4.0.0" - exit "^0.1.2" - graceful-fs "^4.2.9" - import-local "^3.0.2" - jest-config "^27.5.1" - jest-util "^27.5.1" - jest-validate "^27.5.1" - prompts "^2.0.1" - yargs "^16.2.0" - -jest-config@^27.5.1: - version "27.5.1" - resolved "https://registry.npmjs.org/jest-config/-/jest-config-27.5.1.tgz#5c387de33dca3f99ad6357ddeccd91bf3a0e4a41" - integrity sha512-5sAsjm6tGdsVbW9ahcChPAFCk4IlkQUknH5AvKjuLTSlcO/wCZKyFdn7Rg0EkC+OGgWODEy2hDpWB1PgzH0JNA== - dependencies: - "@babel/core" "^7.8.0" - "@jest/test-sequencer" "^27.5.1" - "@jest/types" "^27.5.1" - babel-jest "^27.5.1" - chalk "^4.0.0" - ci-info "^3.2.0" - deepmerge "^4.2.2" - glob "^7.1.1" - graceful-fs "^4.2.9" - jest-circus "^27.5.1" - jest-environment-jsdom "^27.5.1" - jest-environment-node "^27.5.1" - jest-get-type "^27.5.1" - jest-jasmine2 "^27.5.1" - jest-regex-util "^27.5.1" - jest-resolve "^27.5.1" - jest-runner "^27.5.1" - jest-util "^27.5.1" - jest-validate "^27.5.1" - micromatch "^4.0.4" - parse-json "^5.2.0" - pretty-format "^27.5.1" - slash "^3.0.0" - strip-json-comments "^3.1.1" - -jest-diff@^27.5.1: - version "27.5.1" - resolved "https://registry.npmjs.org/jest-diff/-/jest-diff-27.5.1.tgz#a07f5011ac9e6643cf8a95a462b7b1ecf6680def" - integrity sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw== - dependencies: - chalk "^4.0.0" - diff-sequences "^27.5.1" - jest-get-type "^27.5.1" - pretty-format "^27.5.1" - -jest-diff@^29.3.1: - version "29.3.1" - resolved "https://registry.npmjs.org/jest-diff/-/jest-diff-29.3.1.tgz#d8215b72fed8f1e647aed2cae6c752a89e757527" - integrity sha512-vU8vyiO7568tmin2lA3r2DP8oRvzhvRcD4DjpXc6uGveQodyk7CKLhQlCSiwgx3g0pFaE88/KLZ0yaTWMc4Uiw== - dependencies: - chalk "^4.0.0" - diff-sequences "^29.3.1" - jest-get-type "^29.2.0" - pretty-format "^29.3.1" - -jest-docblock@^27.5.1: - version "27.5.1" - resolved "https://registry.npmjs.org/jest-docblock/-/jest-docblock-27.5.1.tgz#14092f364a42c6108d42c33c8cf30e058e25f6c0" - integrity sha512-rl7hlABeTsRYxKiUfpHrQrG4e2obOiTQWfMEH3PxPjOtdsfLQO4ReWSZaQ7DETm4xu07rl4q/h4zcKXyU0/OzQ== - dependencies: - detect-newline "^3.0.0" - -jest-each@^27.5.1: - version "27.5.1" - resolved "https://registry.npmjs.org/jest-each/-/jest-each-27.5.1.tgz#5bc87016f45ed9507fed6e4702a5b468a5b2c44e" - integrity sha512-1Ff6p+FbhT/bXQnEouYy00bkNSY7OUpfIcmdl8vZ31A1UUaurOLPA8a8BbJOF2RDUElwJhmeaV7LnagI+5UwNQ== - dependencies: - "@jest/types" "^27.5.1" - chalk "^4.0.0" - jest-get-type "^27.5.1" - jest-util "^27.5.1" - pretty-format "^27.5.1" - -jest-environment-jsdom@^27.5.1: - version "27.5.1" - resolved "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-27.5.1.tgz#ea9ccd1fc610209655a77898f86b2b559516a546" - integrity sha512-TFBvkTC1Hnnnrka/fUb56atfDtJ9VMZ94JkjTbggl1PEpwrYtUBKMezB3inLmWqQsXYLcMwNoDQwoBTAvFfsfw== - dependencies: - "@jest/environment" "^27.5.1" - "@jest/fake-timers" "^27.5.1" - "@jest/types" "^27.5.1" - "@types/node" "*" - jest-mock "^27.5.1" - jest-util "^27.5.1" - jsdom "^16.6.0" - -jest-environment-node@^27.5.1: - version "27.5.1" - resolved "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-27.5.1.tgz#dedc2cfe52fab6b8f5714b4808aefa85357a365e" - integrity sha512-Jt4ZUnxdOsTGwSRAfKEnE6BcwsSPNOijjwifq5sDFSA2kesnXTvNqKHYgM0hDq3549Uf/KzdXNYn4wMZJPlFLw== - dependencies: - "@jest/environment" "^27.5.1" - "@jest/fake-timers" "^27.5.1" - "@jest/types" "^27.5.1" - "@types/node" "*" - jest-mock "^27.5.1" - jest-util "^27.5.1" - -jest-get-type@^27.5.1: - version "27.5.1" - resolved "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.5.1.tgz#3cd613c507b0f7ace013df407a1c1cd578bcb4f1" - integrity sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw== - -jest-get-type@^29.2.0: - version "29.2.0" - resolved "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.2.0.tgz#726646f927ef61d583a3b3adb1ab13f3a5036408" - integrity sha512-uXNJlg8hKFEnDgFsrCjznB+sTxdkuqiCL6zMgA75qEbAJjJYTs9XPrvDctrEig2GDow22T/LvHgO57iJhXB/UA== - -jest-haste-map@^27.5.1: - version "27.5.1" - resolved "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-27.5.1.tgz#9fd8bd7e7b4fa502d9c6164c5640512b4e811e7f" - integrity sha512-7GgkZ4Fw4NFbMSDSpZwXeBiIbx+t/46nJ2QitkOjvwPYyZmqttu2TDSimMHP1EkPOi4xUZAN1doE5Vd25H4Jng== - dependencies: - "@jest/types" "^27.5.1" - "@types/graceful-fs" "^4.1.2" - "@types/node" "*" - anymatch "^3.0.3" - fb-watchman "^2.0.0" - graceful-fs "^4.2.9" - jest-regex-util "^27.5.1" - jest-serializer "^27.5.1" - jest-util "^27.5.1" - jest-worker "^27.5.1" - micromatch "^4.0.4" - walker "^1.0.7" - optionalDependencies: - fsevents "^2.3.2" - -jest-jasmine2@^27.5.1: - version "27.5.1" - resolved "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-27.5.1.tgz#a037b0034ef49a9f3d71c4375a796f3b230d1ac4" - integrity sha512-jtq7VVyG8SqAorDpApwiJJImd0V2wv1xzdheGHRGyuT7gZm6gG47QEskOlzsN1PG/6WNaCo5pmwMHDf3AkG2pQ== - dependencies: - "@jest/environment" "^27.5.1" - "@jest/source-map" "^27.5.1" - "@jest/test-result" "^27.5.1" - "@jest/types" "^27.5.1" - "@types/node" "*" - chalk "^4.0.0" - co "^4.6.0" - expect "^27.5.1" - is-generator-fn "^2.0.0" - jest-each "^27.5.1" - jest-matcher-utils "^27.5.1" - jest-message-util "^27.5.1" - jest-runtime "^27.5.1" - jest-snapshot "^27.5.1" - jest-util "^27.5.1" - pretty-format "^27.5.1" - throat "^6.0.1" - -jest-leak-detector@^27.5.1: - version "27.5.1" - resolved "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-27.5.1.tgz#6ec9d54c3579dd6e3e66d70e3498adf80fde3fb8" - integrity sha512-POXfWAMvfU6WMUXftV4HolnJfnPOGEu10fscNCA76KBpRRhcMN2c8d3iT2pxQS3HLbA+5X4sOUPzYO2NUyIlHQ== - dependencies: - jest-get-type "^27.5.1" - pretty-format "^27.5.1" - -jest-matcher-utils@^27.0.0, jest-matcher-utils@^27.5.1: - version "27.5.1" - resolved "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-27.5.1.tgz#9c0cdbda8245bc22d2331729d1091308b40cf8ab" - integrity sha512-z2uTx/T6LBaCoNWNFWwChLBKYxTMcGBRjAt+2SbP929/Fflb9aa5LGma654Rz8z9HLxsrUaYzxE9T/EFIL/PAw== - dependencies: - chalk "^4.0.0" - jest-diff "^27.5.1" - jest-get-type "^27.5.1" - pretty-format "^27.5.1" - -jest-matcher-utils@^29.3.1: - version "29.3.1" - resolved "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.3.1.tgz#6e7f53512f80e817dfa148672bd2d5d04914a572" - integrity sha512-fkRMZUAScup3txIKfMe3AIZZmPEjWEdsPJFK3AIy5qRohWqQFg1qrmKfYXR9qEkNc7OdAu2N4KPHibEmy4HPeQ== - dependencies: - chalk "^4.0.0" - jest-diff "^29.3.1" - jest-get-type "^29.2.0" - pretty-format "^29.3.1" - -jest-message-util@^27.5.1: - version "27.5.1" - resolved "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.5.1.tgz#bdda72806da10d9ed6425e12afff38cd1458b6cf" - integrity sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g== - dependencies: - "@babel/code-frame" "^7.12.13" - "@jest/types" "^27.5.1" - "@types/stack-utils" "^2.0.0" - chalk "^4.0.0" - graceful-fs "^4.2.9" - micromatch "^4.0.4" - pretty-format "^27.5.1" - slash "^3.0.0" - stack-utils "^2.0.3" - -jest-message-util@^28.1.3: - version "28.1.3" - resolved "https://registry.npmjs.org/jest-message-util/-/jest-message-util-28.1.3.tgz#232def7f2e333f1eecc90649b5b94b0055e7c43d" - integrity sha512-PFdn9Iewbt575zKPf1286Ht9EPoJmYT7P0kY+RibeYZ2XtOr53pDLEFoTWXbd1h4JiGiWpTBC84fc8xMXQMb7g== - dependencies: - "@babel/code-frame" "^7.12.13" - "@jest/types" "^28.1.3" - "@types/stack-utils" "^2.0.0" - chalk "^4.0.0" - graceful-fs "^4.2.9" - micromatch "^4.0.4" - pretty-format "^28.1.3" - slash "^3.0.0" - stack-utils "^2.0.3" + version "3.0.3" + resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" + integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== -jest-message-util@^29.3.1: - version "29.3.1" - resolved "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.3.1.tgz#37bc5c468dfe5120712053dd03faf0f053bd6adb" - integrity sha512-lMJTbgNcDm5z+6KDxWtqOFWlGQxD6XaYwBqHR8kmpkP+WWWG90I35kdtQHY67Ay5CSuydkTBbJG+tH9JShFCyA== +is-regex@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958" + integrity sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg== dependencies: - "@babel/code-frame" "^7.12.13" - "@jest/types" "^29.3.1" - "@types/stack-utils" "^2.0.0" - chalk "^4.0.0" - graceful-fs "^4.2.9" - micromatch "^4.0.4" - pretty-format "^29.3.1" - slash "^3.0.0" - stack-utils "^2.0.3" + call-bind "^1.0.2" + has-tostringtag "^1.0.0" + +is-set@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/is-set/-/is-set-2.0.2.tgz#90755fa4c2562dc1c5d4024760d6119b94ca18ec" + integrity sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g== -jest-mock@^27.5.1: - version "27.5.1" - resolved "https://registry.npmjs.org/jest-mock/-/jest-mock-27.5.1.tgz#19948336d49ef4d9c52021d34ac7b5f36ff967d6" - integrity sha512-K4jKbY1d4ENhbrG2zuPWaQBvDly+iZ2yAW+T1fATN78hc0sInwn7wZB8XtlNnvHug5RMwV897Xm4LqmPM4e2Og== +is-shared-array-buffer@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz#8f259c573b60b6a32d4058a1a07430c0a7344c79" + integrity sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA== dependencies: - "@jest/types" "^27.5.1" - "@types/node" "*" + call-bind "^1.0.2" -jest-pnp-resolver@^1.2.2: - version "1.2.3" - resolved "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz#930b1546164d4ad5937d5540e711d4d38d4cad2e" - integrity sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w== - -jest-regex-util@^27.5.1: - version "27.5.1" - resolved "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-27.5.1.tgz#4da143f7e9fd1e542d4aa69617b38e4a78365b95" - integrity sha512-4bfKq2zie+x16okqDXjXn9ql2B0dScQu+vcwe4TvFVhkVyuWLqpZrZtXxLLWoXYgn0E87I6r6GRYHF7wFZBUvg== - -jest-regex-util@^28.0.0: - version "28.0.2" - resolved "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-28.0.2.tgz#afdc377a3b25fb6e80825adcf76c854e5bf47ead" - integrity sha512-4s0IgyNIy0y9FK+cjoVYoxamT7Zeo7MhzqRGx7YDYmaQn1wucY9rotiGkBzzcMXTtjrCAP/f7f+E0F7+fxPNdw== - -jest-resolve-dependencies@^27.5.1: - version "27.5.1" - resolved "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-27.5.1.tgz#d811ecc8305e731cc86dd79741ee98fed06f1da8" - integrity sha512-QQOOdY4PE39iawDn5rzbIePNigfe5B9Z91GDD1ae/xNDlu9kaat8QQ5EKnNmVWPV54hUdxCVwwj6YMgR2O7IOg== - dependencies: - "@jest/types" "^27.5.1" - jest-regex-util "^27.5.1" - jest-snapshot "^27.5.1" - -jest-resolve@^27.4.2, jest-resolve@^27.5.1: - version "27.5.1" - resolved "https://registry.npmjs.org/jest-resolve/-/jest-resolve-27.5.1.tgz#a2f1c5a0796ec18fe9eb1536ac3814c23617b384" - integrity sha512-FFDy8/9E6CV83IMbDpcjOhumAQPDyETnU2KZ1O98DwTnz8AOBsW/Xv3GySr1mOZdItLR+zDZ7I/UdTFbgSOVCw== - dependencies: - "@jest/types" "^27.5.1" - chalk "^4.0.0" - graceful-fs "^4.2.9" - jest-haste-map "^27.5.1" - jest-pnp-resolver "^1.2.2" - jest-util "^27.5.1" - jest-validate "^27.5.1" - resolve "^1.20.0" - resolve.exports "^1.1.0" - slash "^3.0.0" +is-stream@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" + integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== -jest-runner@^27.5.1: - version "27.5.1" - resolved "https://registry.npmjs.org/jest-runner/-/jest-runner-27.5.1.tgz#071b27c1fa30d90540805c5645a0ec167c7b62e5" - integrity sha512-g4NPsM4mFCOwFKXO4p/H/kWGdJp9V8kURY2lX8Me2drgXqG7rrZAx5kv+5H7wtt/cdFIjhqYx1HrlqWHaOvDaQ== - dependencies: - "@jest/console" "^27.5.1" - "@jest/environment" "^27.5.1" - "@jest/test-result" "^27.5.1" - "@jest/transform" "^27.5.1" - "@jest/types" "^27.5.1" - "@types/node" "*" - chalk "^4.0.0" - emittery "^0.8.1" - graceful-fs "^4.2.9" - jest-docblock "^27.5.1" - jest-environment-jsdom "^27.5.1" - jest-environment-node "^27.5.1" - jest-haste-map "^27.5.1" - jest-leak-detector "^27.5.1" - jest-message-util "^27.5.1" - jest-resolve "^27.5.1" - jest-runtime "^27.5.1" - jest-util "^27.5.1" - jest-worker "^27.5.1" - source-map-support "^0.5.6" - throat "^6.0.1" - -jest-runtime@^27.5.1: - version "27.5.1" - resolved "https://registry.npmjs.org/jest-runtime/-/jest-runtime-27.5.1.tgz#4896003d7a334f7e8e4a53ba93fb9bcd3db0a1af" - integrity sha512-o7gxw3Gf+H2IGt8fv0RiyE1+r83FJBRruoA+FXrlHw6xEyBsU8ugA6IPfTdVyA0w8HClpbK+DGJxH59UrNMx8A== - dependencies: - "@jest/environment" "^27.5.1" - "@jest/fake-timers" "^27.5.1" - "@jest/globals" "^27.5.1" - "@jest/source-map" "^27.5.1" - "@jest/test-result" "^27.5.1" - "@jest/transform" "^27.5.1" - "@jest/types" "^27.5.1" - chalk "^4.0.0" - cjs-module-lexer "^1.0.0" - collect-v8-coverage "^1.0.0" - execa "^5.0.0" - glob "^7.1.3" - graceful-fs "^4.2.9" - jest-haste-map "^27.5.1" - jest-message-util "^27.5.1" - jest-mock "^27.5.1" - jest-regex-util "^27.5.1" - jest-resolve "^27.5.1" - jest-snapshot "^27.5.1" - jest-util "^27.5.1" - slash "^3.0.0" - strip-bom "^4.0.0" +is-stream@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-3.0.0.tgz#e6bfd7aa6bef69f4f472ce9bb681e3e57b4319ac" + integrity sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA== -jest-serializer@^27.5.1: - version "27.5.1" - resolved "https://registry.npmjs.org/jest-serializer/-/jest-serializer-27.5.1.tgz#81438410a30ea66fd57ff730835123dea1fb1f64" - integrity sha512-jZCyo6iIxO1aqUxpuBlwTDMkzOAJS4a3eYz3YzgxxVQFwLeSA7Jfq5cbqCY+JLvTDrWirgusI/0KwxKMgrdf7w== +is-string@^1.0.5, is-string@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.7.tgz#0dd12bf2006f255bb58f695110eff7491eebc0fd" + integrity sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg== dependencies: - "@types/node" "*" - graceful-fs "^4.2.9" - -jest-snapshot@^27.5.1: - version "27.5.1" - resolved "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-27.5.1.tgz#b668d50d23d38054a51b42c4039cab59ae6eb6a1" - integrity sha512-yYykXI5a0I31xX67mgeLw1DZ0bJB+gpq5IpSuCAoyDi0+BhgU/RIrL+RTzDmkNTchvDFWKP8lp+w/42Z3us5sA== - dependencies: - "@babel/core" "^7.7.2" - "@babel/generator" "^7.7.2" - "@babel/plugin-syntax-typescript" "^7.7.2" - "@babel/traverse" "^7.7.2" - "@babel/types" "^7.0.0" - "@jest/transform" "^27.5.1" - "@jest/types" "^27.5.1" - "@types/babel__traverse" "^7.0.4" - "@types/prettier" "^2.1.5" - babel-preset-current-node-syntax "^1.0.0" - chalk "^4.0.0" - expect "^27.5.1" - graceful-fs "^4.2.9" - jest-diff "^27.5.1" - jest-get-type "^27.5.1" - jest-haste-map "^27.5.1" - jest-matcher-utils "^27.5.1" - jest-message-util "^27.5.1" - jest-util "^27.5.1" - natural-compare "^1.4.0" - pretty-format "^27.5.1" - semver "^7.3.2" + has-tostringtag "^1.0.0" -jest-util@^27.5.1: - version "27.5.1" - resolved "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz#3ba9771e8e31a0b85da48fe0b0891fb86c01c2f9" - integrity sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw== +is-symbol@^1.0.2, is-symbol@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.4.tgz#a6dac93b635b063ca6872236de88910a57af139c" + integrity sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg== dependencies: - "@jest/types" "^27.5.1" - "@types/node" "*" - chalk "^4.0.0" - ci-info "^3.2.0" - graceful-fs "^4.2.9" - picomatch "^2.2.3" + has-symbols "^1.0.2" -jest-util@^28.1.3: - version "28.1.3" - resolved "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz#f4f932aa0074f0679943220ff9cbba7e497028b0" - integrity sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ== +is-typed-array@^1.1.10, is-typed-array@^1.1.12, is-typed-array@^1.1.9: + version "1.1.12" + resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.12.tgz#d0bab5686ef4a76f7a73097b95470ab199c57d4a" + integrity sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg== dependencies: - "@jest/types" "^28.1.3" - "@types/node" "*" - chalk "^4.0.0" - ci-info "^3.2.0" - graceful-fs "^4.2.9" - picomatch "^2.2.3" + which-typed-array "^1.1.11" -jest-util@^29.3.1: - version "29.3.1" - resolved "https://registry.npmjs.org/jest-util/-/jest-util-29.3.1.tgz#1dda51e378bbcb7e3bc9d8ab651445591ed373e1" - integrity sha512-7YOVZaiX7RJLv76ZfHt4nbNEzzTRiMW/IiOG7ZOKmTXmoGBxUDefgMAxQubu6WPVqP5zSzAdZG0FfLcC7HOIFQ== - dependencies: - "@jest/types" "^29.3.1" - "@types/node" "*" - chalk "^4.0.0" - ci-info "^3.2.0" - graceful-fs "^4.2.9" - picomatch "^2.2.3" +is-weakmap@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-weakmap/-/is-weakmap-2.0.1.tgz#5008b59bdc43b698201d18f62b37b2ca243e8cf2" + integrity sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA== -jest-validate@^27.5.1: - version "27.5.1" - resolved "https://registry.npmjs.org/jest-validate/-/jest-validate-27.5.1.tgz#9197d54dc0bdb52260b8db40b46ae668e04df067" - integrity sha512-thkNli0LYTmOI1tDB3FI1S1RTp/Bqyd9pTarJwL87OIBFuqEb5Apv5EaApEudYg4g86e3CT6kM0RowkhtEnCBQ== +is-weakref@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.0.2.tgz#9529f383a9338205e89765e0392efc2f100f06f2" + integrity sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ== dependencies: - "@jest/types" "^27.5.1" - camelcase "^6.2.0" - chalk "^4.0.0" - jest-get-type "^27.5.1" - leven "^3.1.0" - pretty-format "^27.5.1" + call-bind "^1.0.2" -jest-watch-typeahead@^1.0.0: - version "1.1.0" - resolved "https://registry.npmjs.org/jest-watch-typeahead/-/jest-watch-typeahead-1.1.0.tgz#b4a6826dfb9c9420da2f7bc900de59dad11266a9" - integrity sha512-Va5nLSJTN7YFtC2jd+7wsoe1pNe5K4ShLux/E5iHEwlB9AxaxmggY7to9KUqKojhaJw3aXqt5WAb4jGPOolpEw== +is-weakset@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/is-weakset/-/is-weakset-2.0.2.tgz#4569d67a747a1ce5a994dfd4ef6dcea76e7c0a1d" + integrity sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg== dependencies: - ansi-escapes "^4.3.1" - chalk "^4.0.0" - jest-regex-util "^28.0.0" - jest-watcher "^28.0.0" - slash "^4.0.0" - string-length "^5.0.1" - strip-ansi "^7.0.1" - -jest-watcher@^27.5.1: - version "27.5.1" - resolved "https://registry.npmjs.org/jest-watcher/-/jest-watcher-27.5.1.tgz#71bd85fb9bde3a2c2ec4dc353437971c43c642a2" - integrity sha512-z676SuD6Z8o8qbmEGhoEUFOM1+jfEiL3DXHK/xgEiG2EyNYfFG60jluWcupY6dATjfEsKQuibReS1djInQnoVw== - dependencies: - "@jest/test-result" "^27.5.1" - "@jest/types" "^27.5.1" - "@types/node" "*" - ansi-escapes "^4.2.1" - chalk "^4.0.0" - jest-util "^27.5.1" - string-length "^4.0.1" + call-bind "^1.0.2" + get-intrinsic "^1.1.1" -jest-watcher@^28.0.0: - version "28.1.3" - resolved "https://registry.npmjs.org/jest-watcher/-/jest-watcher-28.1.3.tgz#c6023a59ba2255e3b4c57179fc94164b3e73abd4" - integrity sha512-t4qcqj9hze+jviFPUN3YAtAEeFnr/azITXQEMARf5cMwKY2SMBRnCQTXLixTl20OR6mLh9KLMrgVJgJISym+1g== +is-wsl@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" + integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== dependencies: - "@jest/test-result" "^28.1.3" - "@jest/types" "^28.1.3" - "@types/node" "*" - ansi-escapes "^4.2.1" - chalk "^4.0.0" - emittery "^0.10.2" - jest-util "^28.1.3" - string-length "^4.0.1" + is-docker "^2.0.0" -jest-worker@^26.2.1: - version "26.6.2" - resolved "https://registry.npmjs.org/jest-worker/-/jest-worker-26.6.2.tgz#7f72cbc4d643c365e27b9fd775f9d0eaa9c7a8ed" - integrity sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ== - dependencies: - "@types/node" "*" - merge-stream "^2.0.0" - supports-color "^7.0.0" +isarray@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723" + integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw== -jest-worker@^27.0.2, jest-worker@^27.4.5, jest-worker@^27.5.1: - version "27.5.1" - resolved "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz#8d146f0900e8973b106b6f73cc1e9a8cb86f8db0" - integrity sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg== - dependencies: - "@types/node" "*" - merge-stream "^2.0.0" - supports-color "^8.0.0" +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== -jest-worker@^28.0.2: - version "28.1.3" - resolved "https://registry.npmjs.org/jest-worker/-/jest-worker-28.1.3.tgz#7e3c4ce3fa23d1bb6accb169e7f396f98ed4bb98" - integrity sha512-CqRA220YV/6jCo8VWvAt1KKx6eek1VIHMPeLEbpcfSfkEeWyBNppynM/o6q+Wmw+sOhos2ml34wZbSX3G13//g== - dependencies: - "@types/node" "*" - merge-stream "^2.0.0" - supports-color "^8.0.0" +iterall@^1.2.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/iterall/-/iterall-1.3.0.tgz#afcb08492e2915cbd8a0884eb93a8c94d0d72fea" + integrity sha512-QZ9qOMdF+QLHxy1QIpUHUU1D5pS2CG2P69LF6L6CPjPYA/XMOmKV3PZpawHoAjHNyB0swdVTRxdYT4tbBbxqwg== -jest@^27.4.3: - version "27.5.1" - resolved "https://registry.npmjs.org/jest/-/jest-27.5.1.tgz#dadf33ba70a779be7a6fc33015843b51494f63fc" - integrity sha512-Yn0mADZB89zTtjkPJEXwrac3LHudkQMR+Paqa8uxJHCBr9agxztUifWCyiYrjhMPBoUVBjyny0I7XH6ozDr7QQ== +iterator.prototype@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/iterator.prototype/-/iterator.prototype-1.1.2.tgz#5e29c8924f01916cb9335f1ff80619dcff22b0c0" + integrity sha512-DR33HMMr8EzwuRL8Y9D3u2BMj8+RqSE850jfGu59kS7tbmPLzGkZmVSfyCFSDxuZiEY6Rzt3T2NA/qU+NwVj1w== dependencies: - "@jest/core" "^27.5.1" - import-local "^3.0.2" - jest-cli "^27.5.1" - -js-sdsl@^4.1.4: - version "4.2.0" - resolved "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.2.0.tgz#278e98b7bea589b8baaf048c20aeb19eb7ad09d0" - integrity sha512-dyBIzQBDkCqCu+0upx25Y2jGdbTGxE9fshMsCdK0ViOongpV+n5tXRcZY9v7CaVQ79AGS9KA1KHtojxiM7aXSQ== + define-properties "^1.2.1" + get-intrinsic "^1.2.1" + has-symbols "^1.0.3" + reflect.getprototypeof "^1.0.4" + set-function-name "^2.0.1" "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: version "4.0.0" - resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== -js-yaml@^3.13.1: - version "3.14.1" - resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" - integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== - dependencies: - argparse "^1.0.7" - esprima "^4.0.0" - js-yaml@^4.1.0: version "4.1.0" - resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== dependencies: argparse "^2.0.1" -jsdom@^16.6.0: - version "16.7.0" - resolved "https://registry.npmjs.org/jsdom/-/jsdom-16.7.0.tgz#918ae71965424b197c819f8183a754e18977b710" - integrity sha512-u9Smc2G1USStM+s/x1ru5Sxrl6mPYCbByG1U/hUmqaVsm4tbNyS7CicOSRyuGQYZhTu0h84qkZZQ/I+dzizSVw== - dependencies: - abab "^2.0.5" - acorn "^8.2.4" - acorn-globals "^6.0.0" - cssom "^0.4.4" - cssstyle "^2.3.0" - data-urls "^2.0.0" - decimal.js "^10.2.1" - domexception "^2.0.1" - escodegen "^2.0.0" - form-data "^3.0.0" - html-encoding-sniffer "^2.0.1" - http-proxy-agent "^4.0.1" - https-proxy-agent "^5.0.0" - is-potential-custom-element-name "^1.0.1" - nwsapi "^2.2.0" - parse5 "6.0.1" - saxes "^5.0.1" - symbol-tree "^3.2.4" - tough-cookie "^4.0.0" - w3c-hr-time "^1.0.2" - w3c-xmlserializer "^2.0.0" - webidl-conversions "^6.1.0" - whatwg-encoding "^1.0.5" - whatwg-mimetype "^2.3.0" - whatwg-url "^8.5.0" - ws "^7.4.6" - xml-name-validator "^3.0.0" - jsesc@^2.5.1: version "2.5.2" - resolved "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== -jsesc@~0.5.0: - version "0.5.0" - resolved "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" - integrity sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA== +json-buffer@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" + integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== -json-parse-even-better-errors@^2.3.0, json-parse-even-better-errors@^2.3.1: +json-parse-even-better-errors@^2.3.0: version "2.3.1" - resolved "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" + resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== json-schema-traverse@^0.4.1: version "0.4.1" - resolved "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== -json-schema-traverse@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" - integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== - -json-schema@^0.4.0: - version "0.4.0" - resolved "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz#f7de4cf6efab838ebaeb3236474cbba5a1930ab5" - integrity sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA== - json-stable-stringify-without-jsonify@^1.0.1: version "1.0.1" - resolved "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" + resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== json-stringify-safe@^5.0.1: version "5.0.1" - resolved "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" + resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" integrity sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA== -json5@^1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe" - integrity sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow== +json5@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.2.tgz#63d98d60f21b313b77c4d6da18bfa69d80e1d593" + integrity sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA== dependencies: minimist "^1.2.0" -json5@^2.1.2, json5@^2.2.0, json5@^2.2.1: - version "2.2.1" - resolved "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c" - integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA== +json5@^2.2.3: + version "2.2.3" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" + integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== -jsonfile@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" - integrity sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg== - optionalDependencies: - graceful-fs "^4.1.6" +jsonc-parser@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-3.2.0.tgz#31ff3f4c2b9793f89c67212627c51c6394f88e76" + integrity sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w== jsonfile@^6.0.1: version "6.1.0" - resolved "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae" integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ== dependencies: universalify "^2.0.0" optionalDependencies: graceful-fs "^4.1.6" -jsonpointer@^5.0.0: - version "5.0.1" - resolved "https://registry.npmjs.org/jsonpointer/-/jsonpointer-5.0.1.tgz#2110e0af0900fd37467b5907ecd13a7884a1b559" - integrity sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ== +jsqr@^1.2.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/jsqr/-/jsqr-1.4.0.tgz#8efb8d0a7cc6863cb6d95116b9069123ce9eb2d1" + integrity sha512-dxLob7q65Xg2DvstYkRpkYtmKm2sPJ9oFhrhmudT1dZvNFFTlroai3AWSpLey/w5vMcLBXRgOJsbXpdN9HzU/A== -"jsx-ast-utils@^2.4.1 || ^3.0.0", jsx-ast-utils@^3.3.2: - version "3.3.3" - resolved "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.3.tgz#76b3e6e6cece5c69d49a5792c3d01bd1a0cdc7ea" - integrity sha512-fYQHZTZ8jSfmWZ0iyzfwiU4WDX4HpHbMCZ3gPlWYiCl3BoeOTsqKBqnTVfH2rYT7eP5c3sVbeSPHnnJOaTrWiw== +"jsx-ast-utils@^2.4.1 || ^3.0.0", jsx-ast-utils@^3.3.3: + version "3.3.5" + resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz#4766bd05a8e2a11af222becd19e15575e52a853a" + integrity sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ== dependencies: - array-includes "^3.1.5" - object.assign "^4.1.3" - -kind-of@^6.0.2: - version "6.0.3" - resolved "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" - integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== - -kleur@^3.0.3: - version "3.0.3" - resolved "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" - integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== + array-includes "^3.1.6" + array.prototype.flat "^1.3.1" + object.assign "^4.1.4" + object.values "^1.1.6" -klona@^2.0.4, klona@^2.0.5: - version "2.0.5" - resolved "https://registry.npmjs.org/klona/-/klona-2.0.5.tgz#d166574d90076395d9963aa7a928fabb8d76afbc" - integrity sha512-pJiBpiXMbt7dkzXe8Ghj/u4FfXOOa98fPW+bihOJ4SjnoijweJrNThJfd3ifXpXhREjpoF2mZVH1GfS9LV3kHQ== +keyv@^4.5.3: + version "4.5.4" + resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" + integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== + dependencies: + json-buffer "3.0.1" language-subtag-registry@~0.3.2: version "0.3.22" - resolved "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.22.tgz#2e1500861b2e457eba7e7ae86877cbd08fa1fd1d" + resolved "https://registry.yarnpkg.com/language-subtag-registry/-/language-subtag-registry-0.3.22.tgz#2e1500861b2e457eba7e7ae86877cbd08fa1fd1d" integrity sha512-tN0MCzyWnoz/4nHS6uxdlFWoUZT7ABptwKPQ52Ea7URk6vll88bWBVhodtnlfEuCcKWNGoc+uGbw1cwa9IKh/w== -language-tags@^1.0.5: +language-tags@=1.0.5: version "1.0.5" - resolved "https://registry.npmjs.org/language-tags/-/language-tags-1.0.5.tgz#d321dbc4da30ba8bf3024e040fa5c14661f9193a" + resolved "https://registry.yarnpkg.com/language-tags/-/language-tags-1.0.5.tgz#d321dbc4da30ba8bf3024e040fa5c14661f9193a" integrity sha512-qJhlO9cGXi6hBGKoxEG/sKZDAHD5Hnu9Hs4WbOY3pCWXDhw0N8x1NenNzm2EnNLkLkk7J2SdxAkDSbb6ftT+UQ== dependencies: language-subtag-registry "~0.3.2" -leven@^3.1.0: - version "3.1.0" - resolved "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" - integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== - levn@^0.4.1: version "0.4.1" - resolved "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== dependencies: prelude-ls "^1.2.1" type-check "~0.4.0" -levn@~0.3.0: - version "0.3.0" - resolved "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" - integrity sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA== - dependencies: - prelude-ls "~1.1.2" - type-check "~0.3.2" - -lilconfig@^2.0.3, lilconfig@^2.0.5, lilconfig@^2.0.6: - version "2.0.6" - resolved "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.6.tgz#32a384558bd58af3d4c6e077dd1ad1d397bc69d4" - integrity sha512-9JROoBW7pobfsx+Sq2JsASvCo6Pfo6WWoUW79HuB1BCoBXD4PLWJPqDF6fNj67pqBYTbAHkE57M1kS/+L1neOg== - lines-and-columns@^1.1.6: version "1.2.4" - resolved "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" + resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== -loader-runner@^4.2.0: - version "4.3.0" - resolved "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz#c1b4a163b99f614830353b16755e7149ac2314e1" - integrity sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg== - -loader-utils@^2.0.0, loader-utils@^2.0.3: - version "2.0.4" - resolved "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz#8b5cb38b5c34a9a018ee1fc0e6a066d1dfcc528c" - integrity sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw== +lit-element@^3.3.0: + version "3.3.3" + resolved "https://registry.yarnpkg.com/lit-element/-/lit-element-3.3.3.tgz#10bc19702b96ef5416cf7a70177255bfb17b3209" + integrity sha512-XbeRxmTHubXENkV4h8RIPyr8lXc+Ff28rkcQzw3G6up2xg5E8Zu1IgOWIwBLEQsu3cOVFqdYwiVi0hv0SlpqUA== dependencies: - big.js "^5.2.2" - emojis-list "^3.0.0" - json5 "^2.1.2" + "@lit-labs/ssr-dom-shim" "^1.1.0" + "@lit/reactive-element" "^1.3.0" + lit-html "^2.8.0" -loader-utils@^3.2.0: - version "3.2.1" - resolved "https://registry.npmjs.org/loader-utils/-/loader-utils-3.2.1.tgz#4fb104b599daafd82ef3e1a41fb9265f87e1f576" - integrity sha512-ZvFw1KWS3GVyYBYb7qkmRM/WwL2TQQBxgCK62rlvm4WpVQ23Nb4tYjApUlfjrEGvOs7KHEsmyUn75OHZrJMWPw== +lit-html@^2.8.0: + version "2.8.0" + resolved "https://registry.yarnpkg.com/lit-html/-/lit-html-2.8.0.tgz#96456a4bb4ee717b9a7d2f94562a16509d39bffa" + integrity sha512-o9t+MQM3P4y7M7yNzqAyjp7z+mQGa4NS4CxiyLqFPyFWyc4O+nodLrkrxSaCTrla6M5YOLaT3RpbbqjszB5g3Q== + dependencies: + "@types/trusted-types" "^2.0.2" -locate-path@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e" - integrity sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A== +lit@^2.7.5: + version "2.8.0" + resolved "https://registry.yarnpkg.com/lit/-/lit-2.8.0.tgz#4d838ae03059bf9cafa06e5c61d8acc0081e974e" + integrity sha512-4Sc3OFX9QHOJaHbmTMk28SYgVxLN3ePDjg7hofEft2zWlehFL3LiAuapWc4U/kYwMYJSh2hTCPZ6/LIC7ii0MA== dependencies: - p-locate "^3.0.0" - path-exists "^3.0.0" + "@lit/reactive-element" "^1.6.0" + lit-element "^3.3.0" + lit-html "^2.8.0" + +local-pkg@^0.4.3: + version "0.4.3" + resolved "https://registry.yarnpkg.com/local-pkg/-/local-pkg-0.4.3.tgz#0ff361ab3ae7f1c19113d9bb97b98b905dbc4963" + integrity sha512-SFppqq5p42fe2qcZQqqEOiVRXl+WCP1MdT6k7BDEW1j++sp5fIY+/fdRQitvKgB5BrBcmrs5m/L0v2FrU5MY1g== locate-path@^5.0.0: version "5.0.0" - resolved "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== dependencies: p-locate "^4.1.0" locate-path@^6.0.0: version "6.0.0" - resolved "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== dependencies: p-locate "^5.0.0" lodash.debounce@^4.0.8: version "4.0.8" - resolved "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" + resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow== -lodash.memoize@^4.1.2: - version "4.1.2" - resolved "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" - integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag== - lodash.merge@^4.6.2: version "4.6.2" - resolved "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" + resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== -lodash.sortby@^4.7.0: - version "4.7.0" - resolved "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438" - integrity sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA== +lodash.pick@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.pick/-/lodash.pick-4.4.0.tgz#52f05610fff9ded422611441ed1fc123a03001b3" + integrity sha512-hXt6Ul/5yWjfklSGvLQl8vM//l3FtyHZeuelpzK6mm99pNvN9yTDruNZPEJZD1oWrqo+izBmB7oUfWgcCX7s4Q== lodash.throttle@^4.1.1: version "4.1.1" - resolved "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz#c23e91b710242ac70c37f1e1cda9274cc39bf2f4" + resolved "https://registry.yarnpkg.com/lodash.throttle/-/lodash.throttle-4.1.1.tgz#c23e91b710242ac70c37f1e1cda9274cc39bf2f4" integrity sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ== -lodash.uniq@^4.5.0: - version "4.5.0" - resolved "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" - integrity sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ== - -lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.7.0: - version "4.17.21" - resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" - integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== - loose-envify@^1.1.0, loose-envify@^1.4.0: version "1.4.0" - resolved "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" + resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== dependencies: js-tokens "^3.0.0 || ^4.0.0" -lottie-web@^5.1.3: - version "5.9.6" - resolved "https://registry.npmjs.org/lottie-web/-/lottie-web-5.9.6.tgz#62ae68563355d3e04aa75d53dec3dd4bea0e57c9" - integrity sha512-JFs7KsHwflugH5qIXBpB4905yC1Sub2MZWtl/elvO/QC6qj1ApqbUZJyjzJseJUtVpgiDaXQLjBlIJGS7UUUXA== +loupe@^2.3.6: + version "2.3.7" + resolved "https://registry.yarnpkg.com/loupe/-/loupe-2.3.7.tgz#6e69b7d4db7d3ab436328013d37d1c8c3540c697" + integrity sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA== + dependencies: + get-func-name "^2.0.1" lower-case@^2.0.2: version "2.0.2" - resolved "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz#6fa237c63dbdc4a82ca0fd882e4722dc5e634e28" + resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-2.0.2.tgz#6fa237c63dbdc4a82ca0fd882e4722dc5e634e28" integrity sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg== dependencies: tslib "^2.0.3" +lru-cache@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" + integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== + dependencies: + yallist "^3.0.2" + lru-cache@^6.0.0: version "6.0.0" - resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== dependencies: yallist "^4.0.0" -lz-string@^1.4.4: - version "1.4.4" - resolved "https://registry.npmjs.org/lz-string/-/lz-string-1.4.4.tgz#c0d8eaf36059f705796e1e344811cf4c498d3a26" - integrity sha512-0ckx7ZHRPqb0oUm8zNr+90mtf9DQB60H1wMCjBtfi62Kl3a7JbHob6gA2bC+xRvZoOL+1hzUK8jeuEIQE8svEQ== - -magic-string@^0.25.0, magic-string@^0.25.7: - version "0.25.9" - resolved "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz#de7f9faf91ef8a1c91d02c2e5314c8277dbcdd1c" - integrity sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ== +magic-string@^0.30.1: + version "0.30.5" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.5.tgz#1994d980bd1c8835dc6e78db7cbd4ae4f24746f9" + integrity sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA== dependencies: - sourcemap-codec "^1.4.8" + "@jridgewell/sourcemap-codec" "^1.4.15" -make-dir@^3.0.0, make-dir@^3.0.2, make-dir@^3.1.0: +make-dir@^3.0.2: version "3.1.0" - resolved "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== dependencies: semver "^6.0.0" -makeerror@1.0.12: - version "1.0.12" - resolved "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz#3e5dd2079a82e812e983cc6610c4a2cb0eaa801a" - integrity sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg== - dependencies: - tmpl "1.0.5" - -mdn-data@2.0.14: - version "2.0.14" - resolved "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz#7113fc4281917d63ce29b43446f701e68c25ba50" - integrity sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow== - -mdn-data@2.0.4: - version "2.0.4" - resolved "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.4.tgz#699b3c38ac6f1d728091a64650b65d388502fd5b" - integrity sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA== - -media-typer@0.3.0: - version "0.3.0" - resolved "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" - integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== - -memfs@^3.1.2, memfs@^3.4.3: - version "3.4.12" - resolved "https://registry.npmjs.org/memfs/-/memfs-3.4.12.tgz#d00f8ad8dab132dc277c659dc85bfd14b07d03bd" - integrity sha512-BcjuQn6vfqP+k100e0E9m61Hyqa//Brp+I3f0OBmN0ATHlFA8vx3Lt8z57R3u2bPqe3WGDBC+nF72fTH7isyEw== +md5.js@^1.3.4: + version "1.3.5" + resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f" + integrity sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg== dependencies: - fs-monkey "^1.0.3" - -merge-descriptors@1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" - integrity sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w== + hash-base "^3.0.0" + inherits "^2.0.1" + safe-buffer "^5.1.2" merge-stream@^2.0.0: version "2.0.0" - resolved "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" + resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== merge2@^1.3.0, merge2@^1.4.1: version "1.4.1" - resolved "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" + resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== -methods@~1.1.2: - version "1.1.2" - resolved "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" - integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== - -micromatch@^4.0.2, micromatch@^4.0.4, micromatch@^4.0.5: +micromatch@^4.0.4: version "4.0.5" - resolved "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== dependencies: braces "^3.0.2" picomatch "^2.3.1" -mime-db@1.52.0, "mime-db@>= 1.43.0 < 2": - version "1.52.0" - resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" - integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== - -mime-types@^2.1.12, mime-types@^2.1.27, mime-types@^2.1.31, mime-types@~2.1.17, mime-types@~2.1.24, mime-types@~2.1.34: - version "2.1.35" - resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" - integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== - dependencies: - mime-db "1.52.0" - -mime@1.6.0: - version "1.6.0" - resolved "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" - integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== - mimic-fn@^2.1.0: version "2.1.0" - resolved "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== -min-indent@^1.0.0: - version "1.0.1" - resolved "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869" - integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg== - -mini-css-extract-plugin@^2.4.5: - version "2.7.0" - resolved "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.7.0.tgz#d7d9ba0c5b596d155e36e2b174082fc7f010dd64" - integrity sha512-auqtVo8KhTScMsba7MbijqZTfibbXiBNlPAQbsVt7enQfcDYLdgG57eGxMqwVU3mfeWANY4F1wUg+rMF+ycZgw== - dependencies: - schema-utils "^4.0.0" +mimic-fn@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-4.0.0.tgz#60a90550d5cb0b239cca65d893b1a53b29871ecc" + integrity sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw== -minimalistic-assert@^1.0.0: +minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: version "1.0.1" - resolved "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" + resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== +minimalistic-crypto-utils@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" + integrity sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg== + minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: version "3.1.2" - resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== dependencies: brace-expansion "^1.1.7" -minimatch@^5.0.1: - version "5.1.0" - resolved "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz#1717b464f4971b144f6aabe8f2d0b8e4511e09c7" - integrity sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg== - dependencies: - brace-expansion "^2.0.1" - minimist@^1.2.0, minimist@^1.2.6: - version "1.2.7" - resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz#daa1c4d91f507390437c6a8bc01078e7000c4d18" - integrity sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g== + version "1.2.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" + integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== -mkdirp@~0.5.1: - version "0.5.6" - resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6" - integrity sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw== +mlly@^1.2.0, mlly@^1.4.0: + version "1.4.2" + resolved "https://registry.yarnpkg.com/mlly/-/mlly-1.4.2.tgz#7cf406aa319ff6563d25da6b36610a93f2a8007e" + integrity sha512-i/Ykufi2t1EZ6NaPLdfnZk2AX8cs0d+mTzVKuPfqPKPatxLApaBoxJQ9x1/uckXtrS/U5oisPMDkNs0yQTaBRg== dependencies: - minimist "^1.2.6" - -mock-socket@^9.1.5: - version "9.1.5" - resolved "https://registry.npmjs.org/mock-socket/-/mock-socket-9.1.5.tgz#2c4e44922ad556843b6dfe09d14ed8041fa2cdeb" - integrity sha512-3DeNIcsQixWHHKk6NdoBhWI4t1VMj5/HzfnI1rE/pLl5qKx7+gd4DNA07ehTaZ6MoUU053si6Hd+YtiM/tQZfg== + acorn "^8.10.0" + pathe "^1.1.1" + pkg-types "^1.0.3" + ufo "^1.3.0" -ms@2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" - integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== +mock-socket@^9.3.1: + version "9.3.1" + resolved "https://registry.yarnpkg.com/mock-socket/-/mock-socket-9.3.1.tgz#24fb00c2f573c84812aa4a24181bb025de80cc8e" + integrity sha512-qxBgB7Qa2sEQgHFjj0dSigq7fX4k6Saisd5Nelwp2q8mlbAFh5dHV9JTTlF8viYJLSSWgMCZFUom8PJcMNBoJw== ms@2.1.2: version "2.1.2" - resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -ms@2.1.3, ms@^2.1.1: +ms@^2.1.1: version "2.1.3" - resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== -multicast-dns@^7.2.5: - version "7.2.5" - resolved "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz#77eb46057f4d7adbd16d9290fa7299f6fa64cced" - integrity sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg== - dependencies: - dns-packet "^5.2.2" - thunky "^1.0.2" - -nanoid@^3.3.4: - version "3.3.4" - resolved "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz#730b67e3cd09e2deacf03c027c81c9d9dbc5e8ab" - integrity sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw== - -natural-compare-lite@^1.4.0: - version "1.4.0" - resolved "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz#17b09581988979fddafe0201e931ba933c96cbb4" - integrity sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g== +nanoid@^3.3.6: + version "3.3.6" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.6.tgz#443380c856d6e9f9824267d960b4236ad583ea4c" + integrity sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA== natural-compare@^1.4.0: version "1.4.0" - resolved "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== -negotiator@0.6.3: - version "0.6.3" - resolved "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" - integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== - -neo-async@^2.6.2: - version "2.6.2" - resolved "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" - integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== - -next-tick@^1.1.0: - version "1.1.0" - resolved "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz#1836ee30ad56d67ef281b22bd199f709449b35eb" - integrity sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ== - no-case@^3.0.4: version "3.0.4" - resolved "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz#d361fd5c9800f558551a8369fc0dcd4662b6124d" + resolved "https://registry.yarnpkg.com/no-case/-/no-case-3.0.4.tgz#d361fd5c9800f558551a8369fc0dcd4662b6124d" integrity sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg== dependencies: lower-case "^2.0.2" tslib "^2.0.3" -nock@^13.2.9: - version "13.2.9" - resolved "https://registry.npmjs.org/nock/-/nock-13.2.9.tgz#4faf6c28175d36044da4cfa68e33e5a15086ad4c" - integrity sha512-1+XfJNYF1cjGB+TKMWi29eZ0b82QOvQs2YoLNzbpWGqFMtRQHTa57osqdGj4FrFPgkO4D4AZinzUJR9VvW3QUA== +nock@^13.3.4: + version "13.3.6" + resolved "https://registry.yarnpkg.com/nock/-/nock-13.3.6.tgz#b279968ec8d076c2393810a6c9bf2d4d5b3a1071" + integrity sha512-lT6YuktKroUFM+27mubf2uqQZVy2Jf+pfGzuh9N6VwdHlFoZqvi4zyxFTVR1w/ChPqGY6yxGehHp6C3wqCASCw== dependencies: debug "^4.1.0" json-stringify-safe "^5.0.1" - lodash "^4.17.21" propagate "^2.0.0" node-domexception@^1.0.0: version "1.0.0" - resolved "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz#6888db46a1f71c0b76b3f7555016b63fe64766e5" + resolved "https://registry.yarnpkg.com/node-domexception/-/node-domexception-1.0.0.tgz#6888db46a1f71c0b76b3f7555016b63fe64766e5" integrity sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ== -node-fetch@^3.3.0: - version "3.3.0" - resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.0.tgz#37e71db4ecc257057af828d523a7243d651d91e4" - integrity sha512-BKwRP/O0UvoMKp7GNdwPlObhYGB5DQqwhEDQlNKuoqwVYSxkSZCSbHjnFFmUEtwSKRPU4kNK8PbDYYitwaE3QA== +node-fetch@^3.3.2: + version "3.3.2" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-3.3.2.tgz#d1e889bacdf733b4ff3b2b243eb7a12866a0b78b" + integrity sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA== dependencies: data-uri-to-buffer "^4.0.0" fetch-blob "^3.1.4" formdata-polyfill "^4.0.10" -node-forge@^1: - version "1.3.1" - resolved "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz#be8da2af243b2417d5f646a770663a92b7e9ded3" - integrity sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA== - -node-gyp-build@^4.3.0: - version "4.5.0" - resolved "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.5.0.tgz#7a64eefa0b21112f89f58379da128ac177f20e40" - integrity sha512-2iGbaQBV+ITgCz76ZEjmhUKAKVf7xfY1sRl4UiKQspfZMH2h06SyhNsnSVy50cwkFQDGLyif6m/6uFXHkOZ6rg== - -node-int64@^0.4.0: - version "0.4.0" - resolved "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" - integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw== - -node-releases@^2.0.6: - version "2.0.6" - resolved "https://registry.npmjs.org/node-releases/-/node-releases-2.0.6.tgz#8a7088c63a55e493845683ebf3c828d8c51c5503" - integrity sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg== +node-releases@^2.0.13: + version "2.0.13" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.13.tgz#d5ed1627c23e3461e819b02e57b75e4899b1c81d" + integrity sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ== normalize-path@^3.0.0, normalize-path@~3.0.0: version "3.0.0" - resolved "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== -normalize-range@^0.1.2: - version "0.1.2" - resolved "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942" - integrity sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA== - -normalize-url@^6.0.1: - version "6.1.0" - resolved "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz#40d0885b535deffe3f3147bec877d05fe4c5668a" - integrity sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A== - npm-run-path@^4.0.1: version "4.0.1" - resolved "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== dependencies: path-key "^3.0.0" -nth-check@^1.0.2: - version "1.0.2" - resolved "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz#b2bd295c37e3dd58a3bf0700376663ba4d9cf05c" - integrity sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg== - dependencies: - boolbase "~1.0.0" - -nth-check@^2.0.1: - version "2.1.1" - resolved "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz#c9eab428effce36cd6b92c924bdb000ef1f1ed1d" - integrity sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w== +npm-run-path@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-5.1.0.tgz#bc62f7f3f6952d9894bd08944ba011a6ee7b7e00" + integrity sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q== dependencies: - boolbase "^1.0.0" - -nwsapi@^2.2.0: - version "2.2.2" - resolved "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.2.tgz#e5418863e7905df67d51ec95938d67bf801f0bb0" - integrity sha512-90yv+6538zuvUMnN+zCr8LuV6bPFdq50304114vJYJ8RDyK8D5O9Phpbd6SZWgI7PwzmmfN1upeOJlvybDSgCw== + path-key "^4.0.0" object-assign@^4.0.1, object-assign@^4.1.1: version "4.1.1" - resolved "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== -object-hash@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz#73f97f753e7baffc0e2cc9d6e079079744ac82e9" - integrity sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw== - -object-inspect@^1.12.2, object-inspect@^1.9.0: - version "1.12.2" - resolved "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz#c0641f26394532f28ab8d796ab954e43c009a8ea" - integrity sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ== - -object-is@^1.1.5: - version "1.1.5" - resolved "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz#b9deeaa5fc7f1846a0faecdceec138e5778f53ac" - integrity sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.3" +object-inspect@^1.13.1, object-inspect@^1.9.0: + version "1.13.1" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.1.tgz#b96c6109324ccfef6b12216a956ca4dc2ff94bc2" + integrity sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ== object-keys@^1.1.1: version "1.1.1" - resolved "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== -object.assign@^4.1.2, object.assign@^4.1.3, object.assign@^4.1.4: +object.assign@^4.1.2, object.assign@^4.1.4: version "4.1.4" - resolved "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz#9673c7c7c351ab8c4d0b516f4343ebf4dfb7799f" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.4.tgz#9673c7c7c351ab8c4d0b516f4343ebf4dfb7799f" integrity sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ== dependencies: call-bind "^1.0.2" @@ -7703,90 +3885,90 @@ object.assign@^4.1.2, object.assign@^4.1.3, object.assign@^4.1.4: object-keys "^1.1.1" object.entries@^1.1.5, object.entries@^1.1.6: - version "1.1.6" - resolved "https://registry.npmjs.org/object.entries/-/object.entries-1.1.6.tgz#9737d0e5b8291edd340a3e3264bb8a3b00d5fa23" - integrity sha512-leTPzo4Zvg3pmbQ3rDK69Rl8GQvIqMWubrkxONG9/ojtFE2rD9fjMKfSI5BxW3osRH1m6VdzmqK8oAY9aT4x5w== + version "1.1.7" + resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.7.tgz#2b47760e2a2e3a752f39dd874655c61a7f03c131" + integrity sha512-jCBs/0plmPsOnrKAfFQXRG2NFjlhZgjjcBLSmTnEhU8U6vVTsVe8ANeQJCHTl3gSsI4J+0emOoCgoKlmQPMgmA== dependencies: call-bind "^1.0.2" - define-properties "^1.1.4" - es-abstract "^1.20.4" + define-properties "^1.2.0" + es-abstract "^1.22.1" -object.fromentries@^2.0.6: - version "2.0.6" - resolved "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.6.tgz#cdb04da08c539cffa912dcd368b886e0904bfa73" - integrity sha512-VciD13dswC4j1Xt5394WR4MzmAQmlgN72phd/riNp9vtD7tp4QQWJ0R4wvclXcafgcYK8veHRed2W6XeGBvcfg== +object.fromentries@^2.0.6, object.fromentries@^2.0.7: + version "2.0.7" + resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.7.tgz#71e95f441e9a0ea6baf682ecaaf37fa2a8d7e616" + integrity sha512-UPbPHML6sL8PI/mOqPwsH4G6iyXcCGzLin8KvEPenOZN5lpCNBZZQ+V62vdjB1mQHrmqGQt5/OJzemUA+KJmEA== dependencies: call-bind "^1.0.2" - define-properties "^1.1.4" - es-abstract "^1.20.4" + define-properties "^1.2.0" + es-abstract "^1.22.1" -object.getownpropertydescriptors@^2.1.0: - version "2.1.5" - resolved "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.5.tgz#db5a9002489b64eef903df81d6623c07e5b4b4d3" - integrity sha512-yDNzckpM6ntyQiGTik1fKV1DcVDRS+w8bvpWNCBanvH5LfRX9O8WTHqQzG4RZwRAM4I0oU7TV11Lj5v0g20ibw== +object.groupby@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/object.groupby/-/object.groupby-1.0.1.tgz#d41d9f3c8d6c778d9cbac86b4ee9f5af103152ee" + integrity sha512-HqaQtqLnp/8Bn4GL16cj+CUYbnpe1bh0TtEaWvybszDG4tgxCJuRpV8VGuvNaI1fAnI4lUJzDG55MXcOH4JZcQ== dependencies: - array.prototype.reduce "^1.0.5" call-bind "^1.0.2" - define-properties "^1.1.4" - es-abstract "^1.20.4" + define-properties "^1.2.0" + es-abstract "^1.22.1" + get-intrinsic "^1.2.1" object.hasown@^1.1.2: - version "1.1.2" - resolved "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.2.tgz#f919e21fad4eb38a57bc6345b3afd496515c3f92" - integrity sha512-B5UIT3J1W+WuWIU55h0mjlwaqxiE5vYENJXIXZ4VFe05pNYrkKuK0U/6aFcb0pKywYJh7IhfoqUfKVmrJJHZHw== + version "1.1.3" + resolved "https://registry.yarnpkg.com/object.hasown/-/object.hasown-1.1.3.tgz#6a5f2897bb4d3668b8e79364f98ccf971bda55ae" + integrity sha512-fFI4VcYpRHvSLXxP7yiZOMAd331cPfd2p7PFDVbgUsYOfCT3tICVqXWngbjr4m49OvsBwUBQ6O2uQoJvy3RexA== dependencies: - define-properties "^1.1.4" - es-abstract "^1.20.4" + define-properties "^1.2.0" + es-abstract "^1.22.1" -object.values@^1.1.0, object.values@^1.1.5, object.values@^1.1.6: - version "1.1.6" - resolved "https://registry.npmjs.org/object.values/-/object.values-1.1.6.tgz#4abbaa71eba47d63589d402856f908243eea9b1d" - integrity sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw== +object.values@^1.1.6, object.values@^1.1.7: + version "1.1.7" + resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.7.tgz#617ed13272e7e1071b43973aa1655d9291b8442a" + integrity sha512-aU6xnDFYT3x17e/f0IiiwlGPTy2jzMySGfUB4fq6z7CV8l85CWHDk5ErhyhpfDHhrOMwGFhSQkhMGHaIotA6Ng== dependencies: call-bind "^1.0.2" - define-properties "^1.1.4" - es-abstract "^1.20.4" - -obuf@^1.0.0, obuf@^1.1.2: - version "1.1.2" - resolved "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz#09bea3343d41859ebd446292d11c9d4db619084e" - integrity sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg== - -on-finished@2.4.1: - version "2.4.1" - resolved "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" - integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== - dependencies: - ee-first "1.1.1" - -on-headers@~1.0.2: - version "1.0.2" - resolved "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz#772b0ae6aaa525c399e489adfad90c403eb3c28f" - integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA== + define-properties "^1.2.0" + es-abstract "^1.22.1" once@^1.3.0: version "1.4.0" - resolved "https://registry.npmjs.org/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== dependencies: wrappy "1" onetime@^5.1.2: version "5.1.2" - resolved "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== dependencies: mimic-fn "^2.1.0" -open@^8.0.9, open@^8.4.0: - version "8.4.0" - resolved "https://registry.npmjs.org/open/-/open-8.4.0.tgz#345321ae18f8138f82565a910fdc6b39e8c244f8" - integrity sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q== +onetime@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-6.0.0.tgz#7c24c18ed1fd2e9bca4bd26806a33613c77d34b4" + integrity sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ== + dependencies: + mimic-fn "^4.0.0" + +open@^8.4.0: + version "8.4.2" + resolved "https://registry.yarnpkg.com/open/-/open-8.4.2.tgz#5b5ffe2a8f793dcd2aad73e550cb87b59cb084f9" + integrity sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ== dependencies: define-lazy-prop "^2.0.0" is-docker "^2.1.1" is-wsl "^2.2.0" +open@^9.1.0: + version "9.1.0" + resolved "https://registry.yarnpkg.com/open/-/open-9.1.0.tgz#684934359c90ad25742f5a26151970ff8c6c80b6" + integrity sha512-OS+QTnw1/4vrf+9hh1jc1jnYjzSG4ttTBB8UxOwAnInG3Uo4ssetzC1ihqaIHjLJnA5GGlRl6QlZXOTQhRBUvg== + dependencies: + default-browser "^4.0.0" + define-lazy-prop "^3.0.0" + is-inside-container "^1.0.0" + is-wsl "^2.2.0" + optimism@^0.18.0: version "0.18.0" resolved "https://registry.yarnpkg.com/optimism/-/optimism-0.18.0.tgz#e7bb38b24715f3fdad8a9a7fc18e999144bbfa63" @@ -7797,101 +3979,68 @@ optimism@^0.18.0: "@wry/trie" "^0.4.3" tslib "^2.3.0" -optionator@^0.8.1: - version "0.8.3" - resolved "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" - integrity sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA== - dependencies: - deep-is "~0.1.3" - fast-levenshtein "~2.0.6" - levn "~0.3.0" - prelude-ls "~1.1.2" - type-check "~0.3.2" - word-wrap "~1.2.3" - -optionator@^0.9.1: - version "0.9.1" - resolved "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499" - integrity sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw== +optionator@^0.9.3: + version "0.9.3" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.3.tgz#007397d44ed1872fdc6ed31360190f81814e2c64" + integrity sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg== dependencies: + "@aashutoshrathi/word-wrap" "^1.2.3" deep-is "^0.1.3" fast-levenshtein "^2.0.6" levn "^0.4.1" prelude-ls "^1.2.1" type-check "^0.4.0" - word-wrap "^1.2.3" -p-limit@^2.0.0, p-limit@^2.2.0: +p-limit@^2.2.0: version "2.3.0" - resolved "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== dependencies: p-try "^2.0.0" p-limit@^3.0.2: version "3.1.0" - resolved "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== dependencies: yocto-queue "^0.1.0" -p-locate@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4" - integrity sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ== +p-limit@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-4.0.0.tgz#914af6544ed32bfa54670b061cafcbd04984b644" + integrity sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ== dependencies: - p-limit "^2.0.0" + yocto-queue "^1.0.0" p-locate@^4.1.0: version "4.1.0" - resolved "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== dependencies: p-limit "^2.2.0" p-locate@^5.0.0: version "5.0.0" - resolved "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== dependencies: p-limit "^3.0.2" -p-retry@^4.5.0: - version "4.6.2" - resolved "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz#9baae7184057edd4e17231cee04264106e092a16" - integrity sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ== - dependencies: - "@types/retry" "0.12.0" - retry "^0.13.1" - p-try@^2.0.0: version "2.2.0" - resolved "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== -pako@^2.0.4: - version "2.1.0" - resolved "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz#266cc37f98c7d883545d11335c00fbd4062c9a86" - integrity sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug== - -param-case@^3.0.4: - version "3.0.4" - resolved "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz#7d17fe4aa12bde34d4a77d91acfb6219caad01c5" - integrity sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A== - dependencies: - dot-case "^3.0.4" - tslib "^2.0.3" - parent-module@^1.0.0: version "1.0.1" - resolved "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" + resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== dependencies: callsites "^3.0.0" -parse-json@^5.0.0, parse-json@^5.2.0: +parse-json@^5.2.0: version "5.2.0" - resolved "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== dependencies: "@babel/code-frame" "^7.0.0" @@ -7899,773 +4048,137 @@ parse-json@^5.0.0, parse-json@^5.2.0: json-parse-even-better-errors "^2.3.0" lines-and-columns "^1.1.6" -parse5@6.0.1: - version "6.0.1" - resolved "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b" - integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw== - -parseurl@~1.3.2, parseurl@~1.3.3: - version "1.3.3" - resolved "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" - integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== - -pascal-case@^3.1.2: - version "3.1.2" - resolved "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz#b48e0ef2b98e205e7c1dae747d0b1508237660eb" - integrity sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g== - dependencies: - no-case "^3.0.4" - tslib "^2.0.3" - -path-exists@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" - integrity sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ== - path-exists@^4.0.0: version "4.0.0" - resolved "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== path-is-absolute@^1.0.0: version "1.0.1" - resolved "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== path-key@^3.0.0, path-key@^3.1.0: version "3.1.1" - resolved "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== +path-key@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-4.0.0.tgz#295588dc3aee64154f877adb9d780b81c554bf18" + integrity sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ== + path-parse@^1.0.7: version "1.0.7" - resolved "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== -path-to-regexp@0.1.7: - version "0.1.7" - resolved "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" - integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ== - path-type@^4.0.0: version "4.0.0" - resolved "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== -performance-now@^2.1.0: - version "2.1.0" - resolved "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" - integrity sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow== +pathe@^1.1.0, pathe@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/pathe/-/pathe-1.1.1.tgz#1dd31d382b974ba69809adc9a7a347e65d84829a" + integrity sha512-d+RQGp0MAYTIaDBIMmOfMwz3E+LOZnxx1HZd5R18mmCZY0QBlK0LDZfPc8FW8Ed2DlvsuE6PRjroDY+wg4+j/Q== -picocolors@^0.2.1: - version "0.2.1" - resolved "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz#570670f793646851d1ba135996962abad587859f" - integrity sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA== +pathval@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/pathval/-/pathval-1.1.1.tgz#8534e77a77ce7ac5a2512ea21e0fdb8fcf6c3d8d" + integrity sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ== picocolors@^1.0.0: version "1.0.0" - resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== -picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.2, picomatch@^2.2.3, picomatch@^2.3.0, picomatch@^2.3.1: +picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.2, picomatch@^2.3.1: version "2.3.1" - resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== -pify@^2.0.0, pify@^2.3.0: +pify@^2.0.0: version "2.3.0" - resolved "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" + resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" integrity sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog== pinkie-promise@^2.0.0: version "2.0.1" - resolved "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa" + resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa" integrity sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw== dependencies: pinkie "^2.0.0" pinkie@^2.0.0: version "2.0.4" - resolved "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870" + resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870" integrity sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg== -pirates@^4.0.4: - version "4.0.5" - resolved "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz#feec352ea5c3268fb23a37c702ab1699f35a5f3b" - integrity sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ== - -pkg-dir@^4.1.0, pkg-dir@^4.2.0: +pkg-dir@^4.1.0: version "4.2.0" - resolved "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== dependencies: find-up "^4.0.0" -pkg-up@^3.1.0: - version "3.1.0" - resolved "https://registry.npmjs.org/pkg-up/-/pkg-up-3.1.0.tgz#100ec235cc150e4fd42519412596a28512a0def5" - integrity sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA== - dependencies: - find-up "^3.0.0" - -pnglib@0.0.1: - version "0.0.1" - resolved "https://registry.npmjs.org/pnglib/-/pnglib-0.0.1.tgz#f9ab6f9c688f4a9d579ad8be28878a716e30c096" - integrity sha512-95ChzOoYLOPIyVmL+Y6X+abKGXUJlvOVLkB1QQkyXl7Uczc6FElUy/x01NS7r2GX6GRezloO/ecCX9h4U9KadA== - -popmotion@11.0.5: - version "11.0.5" - resolved "https://registry.npmjs.org/popmotion/-/popmotion-11.0.5.tgz#8e3e014421a0ffa30ecd722564fd2558954e1f7d" - integrity sha512-la8gPM1WYeFznb/JqF4GiTkRRPZsfaj2+kCxqQgr2MJylMmIKUwBfWW8Wa5fml/8gmtlD5yI01MP1QCZPWmppA== - dependencies: - framesync "6.1.2" - hey-listen "^1.0.8" - style-value-types "5.1.2" - tslib "2.4.0" - -postcss-attribute-case-insensitive@^5.0.2: - version "5.0.2" - resolved "https://registry.npmjs.org/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-5.0.2.tgz#03d761b24afc04c09e757e92ff53716ae8ea2741" - integrity sha512-XIidXV8fDr0kKt28vqki84fRK8VW8eTuIa4PChv2MqKuT6C9UjmSKzen6KaWhWEoYvwxFCa7n/tC1SZ3tyq4SQ== - dependencies: - postcss-selector-parser "^6.0.10" - -postcss-browser-comments@^4: - version "4.0.0" - resolved "https://registry.npmjs.org/postcss-browser-comments/-/postcss-browser-comments-4.0.0.tgz#bcfc86134df5807f5d3c0eefa191d42136b5e72a" - integrity sha512-X9X9/WN3KIvY9+hNERUqX9gncsgBA25XaeR+jshHz2j8+sYyHktHw1JdKuMjeLpGktXidqDhA7b/qm1mrBDmgg== - -postcss-calc@^8.2.3: - version "8.2.4" - resolved "https://registry.npmjs.org/postcss-calc/-/postcss-calc-8.2.4.tgz#77b9c29bfcbe8a07ff6693dc87050828889739a5" - integrity sha512-SmWMSJmB8MRnnULldx0lQIyhSNvuDl9HfrZkaqqE/WHAhToYsAvDq+yAsA/kIyINDszOp3Rh0GFoNuH5Ypsm3Q== - dependencies: - postcss-selector-parser "^6.0.9" - postcss-value-parser "^4.2.0" - -postcss-clamp@^4.1.0: - version "4.1.0" - resolved "https://registry.npmjs.org/postcss-clamp/-/postcss-clamp-4.1.0.tgz#7263e95abadd8c2ba1bd911b0b5a5c9c93e02363" - integrity sha512-ry4b1Llo/9zz+PKC+030KUnPITTJAHeOwjfAyyB60eT0AorGLdzp52s31OsPRHRf8NchkgFoG2y6fCfn1IV1Ow== - dependencies: - postcss-value-parser "^4.2.0" - -postcss-color-functional-notation@^4.2.4: - version "4.2.4" - resolved "https://registry.npmjs.org/postcss-color-functional-notation/-/postcss-color-functional-notation-4.2.4.tgz#21a909e8d7454d3612d1659e471ce4696f28caec" - integrity sha512-2yrTAUZUab9s6CpxkxC4rVgFEVaR6/2Pipvi6qcgvnYiVqZcbDHEoBDhrXzyb7Efh2CCfHQNtcqWcIruDTIUeg== - dependencies: - postcss-value-parser "^4.2.0" - -postcss-color-hex-alpha@^8.0.4: - version "8.0.4" - resolved "https://registry.npmjs.org/postcss-color-hex-alpha/-/postcss-color-hex-alpha-8.0.4.tgz#c66e2980f2fbc1a63f5b079663340ce8b55f25a5" - integrity sha512-nLo2DCRC9eE4w2JmuKgVA3fGL3d01kGq752pVALF68qpGLmx2Qrk91QTKkdUqqp45T1K1XV8IhQpcu1hoAQflQ== - dependencies: - postcss-value-parser "^4.2.0" - -postcss-color-rebeccapurple@^7.1.1: - version "7.1.1" - resolved "https://registry.npmjs.org/postcss-color-rebeccapurple/-/postcss-color-rebeccapurple-7.1.1.tgz#63fdab91d878ebc4dd4b7c02619a0c3d6a56ced0" - integrity sha512-pGxkuVEInwLHgkNxUc4sdg4g3py7zUeCQ9sMfwyHAT+Ezk8a4OaaVZ8lIY5+oNqA/BXXgLyXv0+5wHP68R79hg== - dependencies: - postcss-value-parser "^4.2.0" - -postcss-colormin@^5.3.0: - version "5.3.0" - resolved "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-5.3.0.tgz#3cee9e5ca62b2c27e84fce63affc0cfb5901956a" - integrity sha512-WdDO4gOFG2Z8n4P8TWBpshnL3JpmNmJwdnfP2gbk2qBA8PWwOYcmjmI/t3CmMeL72a7Hkd+x/Mg9O2/0rD54Pg== - dependencies: - browserslist "^4.16.6" - caniuse-api "^3.0.0" - colord "^2.9.1" - postcss-value-parser "^4.2.0" - -postcss-convert-values@^5.1.3: - version "5.1.3" - resolved "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-5.1.3.tgz#04998bb9ba6b65aa31035d669a6af342c5f9d393" - integrity sha512-82pC1xkJZtcJEfiLw6UXnXVXScgtBrjlO5CBmuDQc+dlb88ZYheFsjTn40+zBVi3DkfF7iezO0nJUPLcJK3pvA== - dependencies: - browserslist "^4.21.4" - postcss-value-parser "^4.2.0" - -postcss-custom-media@^8.0.2: - version "8.0.2" - resolved "https://registry.npmjs.org/postcss-custom-media/-/postcss-custom-media-8.0.2.tgz#c8f9637edf45fef761b014c024cee013f80529ea" - integrity sha512-7yi25vDAoHAkbhAzX9dHx2yc6ntS4jQvejrNcC+csQJAXjj15e7VcWfMgLqBNAbOvqi5uIa9huOVwdHbf+sKqg== - dependencies: - postcss-value-parser "^4.2.0" - -postcss-custom-properties@^12.1.10: - version "12.1.10" - resolved "https://registry.npmjs.org/postcss-custom-properties/-/postcss-custom-properties-12.1.10.tgz#624517179fd4cf50078a7a60f628d5782e7d4903" - integrity sha512-U3BHdgrYhCrwTVcByFHs9EOBoqcKq4Lf3kXwbTi4hhq0qWhl/pDWq2THbv/ICX/Fl9KqeHBb8OVrTf2OaYF07A== - dependencies: - postcss-value-parser "^4.2.0" - -postcss-custom-selectors@^6.0.3: - version "6.0.3" - resolved "https://registry.npmjs.org/postcss-custom-selectors/-/postcss-custom-selectors-6.0.3.tgz#1ab4684d65f30fed175520f82d223db0337239d9" - integrity sha512-fgVkmyiWDwmD3JbpCmB45SvvlCD6z9CG6Ie6Iere22W5aHea6oWa7EM2bpnv2Fj3I94L3VbtvX9KqwSi5aFzSg== - dependencies: - postcss-selector-parser "^6.0.4" - -postcss-dir-pseudo-class@^6.0.5: - version "6.0.5" - resolved "https://registry.npmjs.org/postcss-dir-pseudo-class/-/postcss-dir-pseudo-class-6.0.5.tgz#2bf31de5de76added44e0a25ecf60ae9f7c7c26c" - integrity sha512-eqn4m70P031PF7ZQIvSgy9RSJ5uI2171O/OO/zcRNYpJbvaeKFUlar1aJ7rmgiQtbm0FSPsRewjpdS0Oew7MPA== - dependencies: - postcss-selector-parser "^6.0.10" - -postcss-discard-comments@^5.1.2: - version "5.1.2" - resolved "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-5.1.2.tgz#8df5e81d2925af2780075840c1526f0660e53696" - integrity sha512-+L8208OVbHVF2UQf1iDmRcbdjJkuBF6IS29yBDSiWUIzpYaAhtNl6JYnYm12FnkeCwQqF5LeklOu6rAqgfBZqQ== - -postcss-discard-duplicates@^5.1.0: - version "5.1.0" - resolved "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-5.1.0.tgz#9eb4fe8456706a4eebd6d3b7b777d07bad03e848" - integrity sha512-zmX3IoSI2aoenxHV6C7plngHWWhUOV3sP1T8y2ifzxzbtnuhk1EdPwm0S1bIUNaJ2eNbWeGLEwzw8huPD67aQw== - -postcss-discard-empty@^5.1.1: - version "5.1.1" - resolved "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-5.1.1.tgz#e57762343ff7f503fe53fca553d18d7f0c369c6c" - integrity sha512-zPz4WljiSuLWsI0ir4Mcnr4qQQ5e1Ukc3i7UfE2XcrwKK2LIPIqE5jxMRxO6GbI3cv//ztXDsXwEWT3BHOGh3A== - -postcss-discard-overridden@^5.1.0: - version "5.1.0" - resolved "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-5.1.0.tgz#7e8c5b53325747e9d90131bb88635282fb4a276e" - integrity sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw== - -postcss-double-position-gradients@^3.1.2: - version "3.1.2" - resolved "https://registry.npmjs.org/postcss-double-position-gradients/-/postcss-double-position-gradients-3.1.2.tgz#b96318fdb477be95997e86edd29c6e3557a49b91" - integrity sha512-GX+FuE/uBR6eskOK+4vkXgT6pDkexLokPaz/AbJna9s5Kzp/yl488pKPjhy0obB475ovfT1Wv8ho7U/cHNaRgQ== - dependencies: - "@csstools/postcss-progressive-custom-properties" "^1.1.0" - postcss-value-parser "^4.2.0" - -postcss-env-function@^4.0.6: - version "4.0.6" - resolved "https://registry.npmjs.org/postcss-env-function/-/postcss-env-function-4.0.6.tgz#7b2d24c812f540ed6eda4c81f6090416722a8e7a" - integrity sha512-kpA6FsLra+NqcFnL81TnsU+Z7orGtDTxcOhl6pwXeEq1yFPpRMkCDpHhrz8CFQDr/Wfm0jLiNQ1OsGGPjlqPwA== - dependencies: - postcss-value-parser "^4.2.0" - -postcss-flexbugs-fixes@^5.0.2: - version "5.0.2" - resolved "https://registry.npmjs.org/postcss-flexbugs-fixes/-/postcss-flexbugs-fixes-5.0.2.tgz#2028e145313074fc9abe276cb7ca14e5401eb49d" - integrity sha512-18f9voByak7bTktR2QgDveglpn9DTbBWPUzSOe9g0N4WR/2eSt6Vrcbf0hmspvMI6YWGywz6B9f7jzpFNJJgnQ== - -postcss-focus-visible@^6.0.4: - version "6.0.4" - resolved "https://registry.npmjs.org/postcss-focus-visible/-/postcss-focus-visible-6.0.4.tgz#50c9ea9afa0ee657fb75635fabad25e18d76bf9e" - integrity sha512-QcKuUU/dgNsstIK6HELFRT5Y3lbrMLEOwG+A4s5cA+fx3A3y/JTq3X9LaOj3OC3ALH0XqyrgQIgey/MIZ8Wczw== - dependencies: - postcss-selector-parser "^6.0.9" - -postcss-focus-within@^5.0.4: - version "5.0.4" - resolved "https://registry.npmjs.org/postcss-focus-within/-/postcss-focus-within-5.0.4.tgz#5b1d2ec603195f3344b716c0b75f61e44e8d2e20" - integrity sha512-vvjDN++C0mu8jz4af5d52CB184ogg/sSxAFS+oUJQq2SuCe7T5U2iIsVJtsCp2d6R4j0jr5+q3rPkBVZkXD9fQ== - dependencies: - postcss-selector-parser "^6.0.9" - -postcss-font-variant@^5.0.0: - version "5.0.0" - resolved "https://registry.npmjs.org/postcss-font-variant/-/postcss-font-variant-5.0.0.tgz#efd59b4b7ea8bb06127f2d031bfbb7f24d32fa66" - integrity sha512-1fmkBaCALD72CK2a9i468mA/+tr9/1cBxRRMXOUaZqO43oWPR5imcyPjXwuv7PXbCid4ndlP5zWhidQVVa3hmA== - -postcss-gap-properties@^3.0.5: - version "3.0.5" - resolved "https://registry.npmjs.org/postcss-gap-properties/-/postcss-gap-properties-3.0.5.tgz#f7e3cddcf73ee19e94ccf7cb77773f9560aa2fff" - integrity sha512-IuE6gKSdoUNcvkGIqdtjtcMtZIFyXZhmFd5RUlg97iVEvp1BZKV5ngsAjCjrVy+14uhGBQl9tzmi1Qwq4kqVOg== - -postcss-image-set-function@^4.0.7: - version "4.0.7" - resolved "https://registry.npmjs.org/postcss-image-set-function/-/postcss-image-set-function-4.0.7.tgz#08353bd756f1cbfb3b6e93182c7829879114481f" - integrity sha512-9T2r9rsvYzm5ndsBE8WgtrMlIT7VbtTfE7b3BQnudUqnBcBo7L758oc+o+pdj/dUV0l5wjwSdjeOH2DZtfv8qw== - dependencies: - postcss-value-parser "^4.2.0" - -postcss-import@^14.1.0: - version "14.1.0" - resolved "https://registry.npmjs.org/postcss-import/-/postcss-import-14.1.0.tgz#a7333ffe32f0b8795303ee9e40215dac922781f0" - integrity sha512-flwI+Vgm4SElObFVPpTIT7SU7R3qk2L7PyduMcokiaVKuWv9d/U+Gm/QAd8NDLuykTWTkcrjOeD2Pp1rMeBTGw== - dependencies: - postcss-value-parser "^4.0.0" - read-cache "^1.0.0" - resolve "^1.1.7" - -postcss-initial@^4.0.1: - version "4.0.1" - resolved "https://registry.npmjs.org/postcss-initial/-/postcss-initial-4.0.1.tgz#529f735f72c5724a0fb30527df6fb7ac54d7de42" - integrity sha512-0ueD7rPqX8Pn1xJIjay0AZeIuDoF+V+VvMt/uOnn+4ezUKhZM/NokDeP6DwMNyIoYByuN/94IQnt5FEkaN59xQ== - -postcss-js@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.0.tgz#31db79889531b80dc7bc9b0ad283e418dce0ac00" - integrity sha512-77QESFBwgX4irogGVPgQ5s07vLvFqWr228qZY+w6lW599cRlK/HmnlivnnVUxkjHnCu4J16PDMHcH+e+2HbvTQ== - dependencies: - camelcase-css "^2.0.1" - -postcss-lab-function@^4.2.1: - version "4.2.1" - resolved "https://registry.npmjs.org/postcss-lab-function/-/postcss-lab-function-4.2.1.tgz#6fe4c015102ff7cd27d1bd5385582f67ebdbdc98" - integrity sha512-xuXll4isR03CrQsmxyz92LJB2xX9n+pZJ5jE9JgcnmsCammLyKdlzrBin+25dy6wIjfhJpKBAN80gsTlCgRk2w== - dependencies: - "@csstools/postcss-progressive-custom-properties" "^1.1.0" - postcss-value-parser "^4.2.0" - -postcss-load-config@^3.1.4: - version "3.1.4" - resolved "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-3.1.4.tgz#1ab2571faf84bb078877e1d07905eabe9ebda855" - integrity sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg== - dependencies: - lilconfig "^2.0.5" - yaml "^1.10.2" - -postcss-loader@^6.2.1: - version "6.2.1" - resolved "https://registry.npmjs.org/postcss-loader/-/postcss-loader-6.2.1.tgz#0895f7346b1702103d30fdc66e4d494a93c008ef" - integrity sha512-WbbYpmAaKcux/P66bZ40bpWsBucjx/TTgVVzRZ9yUO8yQfVBlameJ0ZGVaPfH64hNSBh63a+ICP5nqOpBA0w+Q== - dependencies: - cosmiconfig "^7.0.0" - klona "^2.0.5" - semver "^7.3.5" - -postcss-logical@^5.0.4: - version "5.0.4" - resolved "https://registry.npmjs.org/postcss-logical/-/postcss-logical-5.0.4.tgz#ec75b1ee54421acc04d5921576b7d8db6b0e6f73" - integrity sha512-RHXxplCeLh9VjinvMrZONq7im4wjWGlRJAqmAVLXyZaXwfDWP73/oq4NdIp+OZwhQUMj0zjqDfM5Fj7qby+B4g== - -postcss-media-minmax@^5.0.0: - version "5.0.0" - resolved "https://registry.npmjs.org/postcss-media-minmax/-/postcss-media-minmax-5.0.0.tgz#7140bddec173e2d6d657edbd8554a55794e2a5b5" - integrity sha512-yDUvFf9QdFZTuCUg0g0uNSHVlJ5X1lSzDZjPSFaiCWvjgsvu8vEVxtahPrLMinIDEEGnx6cBe6iqdx5YWz08wQ== - -postcss-merge-longhand@^5.1.7: - version "5.1.7" - resolved "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-5.1.7.tgz#24a1bdf402d9ef0e70f568f39bdc0344d568fb16" - integrity sha512-YCI9gZB+PLNskrK0BB3/2OzPnGhPkBEwmwhfYk1ilBHYVAZB7/tkTHFBAnCrvBBOmeYyMYw3DMjT55SyxMBzjQ== - dependencies: - postcss-value-parser "^4.2.0" - stylehacks "^5.1.1" - -postcss-merge-rules@^5.1.3: - version "5.1.3" - resolved "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-5.1.3.tgz#8f97679e67cc8d08677a6519afca41edf2220894" - integrity sha512-LbLd7uFC00vpOuMvyZop8+vvhnfRGpp2S+IMQKeuOZZapPRY4SMq5ErjQeHbHsjCUgJkRNrlU+LmxsKIqPKQlA== - dependencies: - browserslist "^4.21.4" - caniuse-api "^3.0.0" - cssnano-utils "^3.1.0" - postcss-selector-parser "^6.0.5" - -postcss-minify-font-values@^5.1.0: - version "5.1.0" - resolved "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-5.1.0.tgz#f1df0014a726083d260d3bd85d7385fb89d1f01b" - integrity sha512-el3mYTgx13ZAPPirSVsHqFzl+BBBDrXvbySvPGFnQcTI4iNslrPaFq4muTkLZmKlGk4gyFAYUBMH30+HurREyA== - dependencies: - postcss-value-parser "^4.2.0" - -postcss-minify-gradients@^5.1.1: - version "5.1.1" - resolved "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-5.1.1.tgz#f1fe1b4f498134a5068240c2f25d46fcd236ba2c" - integrity sha512-VGvXMTpCEo4qHTNSa9A0a3D+dxGFZCYwR6Jokk+/3oB6flu2/PnPXAh2x7x52EkY5xlIHLm+Le8tJxe/7TNhzw== - dependencies: - colord "^2.9.1" - cssnano-utils "^3.1.0" - postcss-value-parser "^4.2.0" - -postcss-minify-params@^5.1.4: - version "5.1.4" - resolved "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-5.1.4.tgz#c06a6c787128b3208b38c9364cfc40c8aa5d7352" - integrity sha512-+mePA3MgdmVmv6g+30rn57USjOGSAyuxUmkfiWpzalZ8aiBkdPYjXWtHuwJGm1v5Ojy0Z0LaSYhHaLJQB0P8Jw== - dependencies: - browserslist "^4.21.4" - cssnano-utils "^3.1.0" - postcss-value-parser "^4.2.0" - -postcss-minify-selectors@^5.2.1: - version "5.2.1" - resolved "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-5.2.1.tgz#d4e7e6b46147b8117ea9325a915a801d5fe656c6" - integrity sha512-nPJu7OjZJTsVUmPdm2TcaiohIwxP+v8ha9NehQ2ye9szv4orirRU3SDdtUmKH+10nzn0bAyOXZ0UEr7OpvLehg== - dependencies: - postcss-selector-parser "^6.0.5" - -postcss-modules-extract-imports@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz#cda1f047c0ae80c97dbe28c3e76a43b88025741d" - integrity sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw== - -postcss-modules-local-by-default@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz#ebbb54fae1598eecfdf691a02b3ff3b390a5a51c" - integrity sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ== - dependencies: - icss-utils "^5.0.0" - postcss-selector-parser "^6.0.2" - postcss-value-parser "^4.1.0" - -postcss-modules-scope@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz#9ef3151456d3bbfa120ca44898dfca6f2fa01f06" - integrity sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg== - dependencies: - postcss-selector-parser "^6.0.4" - -postcss-modules-values@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz#d7c5e7e68c3bb3c9b27cbf48ca0bb3ffb4602c9c" - integrity sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ== - dependencies: - icss-utils "^5.0.0" - -postcss-nested@6.0.0: - version "6.0.0" - resolved "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.0.0.tgz#1572f1984736578f360cffc7eb7dca69e30d1735" - integrity sha512-0DkamqrPcmkBDsLn+vQDIrtkSbNkv5AD/M322ySo9kqFkCIYklym2xEmWkwo+Y3/qZo34tzEPNUw4y7yMCdv5w== - dependencies: - postcss-selector-parser "^6.0.10" - -postcss-nesting@^10.2.0: - version "10.2.0" - resolved "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-10.2.0.tgz#0b12ce0db8edfd2d8ae0aaf86427370b898890be" - integrity sha512-EwMkYchxiDiKUhlJGzWsD9b2zvq/r2SSubcRrgP+jujMXFzqvANLt16lJANC+5uZ6hjI7lpRmI6O8JIl+8l1KA== - dependencies: - "@csstools/selector-specificity" "^2.0.0" - postcss-selector-parser "^6.0.10" - -postcss-normalize-charset@^5.1.0: - version "5.1.0" - resolved "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-5.1.0.tgz#9302de0b29094b52c259e9b2cf8dc0879879f0ed" - integrity sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg== - -postcss-normalize-display-values@^5.1.0: - version "5.1.0" - resolved "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-5.1.0.tgz#72abbae58081960e9edd7200fcf21ab8325c3da8" - integrity sha512-WP4KIM4o2dazQXWmFaqMmcvsKmhdINFblgSeRgn8BJ6vxaMyaJkwAzpPpuvSIoG/rmX3M+IrRZEz2H0glrQNEA== - dependencies: - postcss-value-parser "^4.2.0" - -postcss-normalize-positions@^5.1.1: - version "5.1.1" - resolved "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-5.1.1.tgz#ef97279d894087b59325b45c47f1e863daefbb92" - integrity sha512-6UpCb0G4eofTCQLFVuI3EVNZzBNPiIKcA1AKVka+31fTVySphr3VUgAIULBhxZkKgwLImhzMR2Bw1ORK+37INg== - dependencies: - postcss-value-parser "^4.2.0" - -postcss-normalize-repeat-style@^5.1.1: - version "5.1.1" - resolved "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-5.1.1.tgz#e9eb96805204f4766df66fd09ed2e13545420fb2" - integrity sha512-mFpLspGWkQtBcWIRFLmewo8aC3ImN2i/J3v8YCFUwDnPu3Xz4rLohDO26lGjwNsQxB3YF0KKRwspGzE2JEuS0g== - dependencies: - postcss-value-parser "^4.2.0" - -postcss-normalize-string@^5.1.0: - version "5.1.0" - resolved "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-5.1.0.tgz#411961169e07308c82c1f8c55f3e8a337757e228" - integrity sha512-oYiIJOf4T9T1N4i+abeIc7Vgm/xPCGih4bZz5Nm0/ARVJ7K6xrDlLwvwqOydvyL3RHNf8qZk6vo3aatiw/go3w== - dependencies: - postcss-value-parser "^4.2.0" - -postcss-normalize-timing-functions@^5.1.0: - version "5.1.0" - resolved "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-5.1.0.tgz#d5614410f8f0b2388e9f240aa6011ba6f52dafbb" - integrity sha512-DOEkzJ4SAXv5xkHl0Wa9cZLF3WCBhF3o1SKVxKQAa+0pYKlueTpCgvkFAHfk+Y64ezX9+nITGrDZeVGgITJXjg== - dependencies: - postcss-value-parser "^4.2.0" - -postcss-normalize-unicode@^5.1.1: - version "5.1.1" - resolved "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-5.1.1.tgz#f67297fca3fea7f17e0d2caa40769afc487aa030" - integrity sha512-qnCL5jzkNUmKVhZoENp1mJiGNPcsJCs1aaRmURmeJGES23Z/ajaln+EPTD+rBeNkSryI+2WTdW+lwcVdOikrpA== - dependencies: - browserslist "^4.21.4" - postcss-value-parser "^4.2.0" - -postcss-normalize-url@^5.1.0: - version "5.1.0" - resolved "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-5.1.0.tgz#ed9d88ca82e21abef99f743457d3729a042adcdc" - integrity sha512-5upGeDO+PVthOxSmds43ZeMeZfKH+/DKgGRD7TElkkyS46JXAUhMzIKiCa7BabPeIy3AQcTkXwVVN7DbqsiCew== - dependencies: - normalize-url "^6.0.1" - postcss-value-parser "^4.2.0" - -postcss-normalize-whitespace@^5.1.1: - version "5.1.1" - resolved "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-5.1.1.tgz#08a1a0d1ffa17a7cc6efe1e6c9da969cc4493cfa" - integrity sha512-83ZJ4t3NUDETIHTa3uEg6asWjSBYL5EdkVB0sDncx9ERzOKBVJIUeDO9RyA9Zwtig8El1d79HBp0JEi8wvGQnA== - dependencies: - postcss-value-parser "^4.2.0" - -postcss-normalize@^10.0.1: - version "10.0.1" - resolved "https://registry.npmjs.org/postcss-normalize/-/postcss-normalize-10.0.1.tgz#464692676b52792a06b06880a176279216540dd7" - integrity sha512-+5w18/rDev5mqERcG3W5GZNMJa1eoYYNGo8gB7tEwaos0ajk3ZXAI4mHGcNT47NE+ZnZD1pEpUOFLvltIwmeJA== - dependencies: - "@csstools/normalize.css" "*" - postcss-browser-comments "^4" - sanitize.css "*" - -postcss-opacity-percentage@^1.1.2: - version "1.1.2" - resolved "https://registry.npmjs.org/postcss-opacity-percentage/-/postcss-opacity-percentage-1.1.2.tgz#bd698bb3670a0a27f6d657cc16744b3ebf3b1145" - integrity sha512-lyUfF7miG+yewZ8EAk9XUBIlrHyUE6fijnesuz+Mj5zrIHIEw6KcIZSOk/elVMqzLvREmXB83Zi/5QpNRYd47w== - -postcss-ordered-values@^5.1.3: - version "5.1.3" - resolved "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-5.1.3.tgz#b6fd2bd10f937b23d86bc829c69e7732ce76ea38" - integrity sha512-9UO79VUhPwEkzbb3RNpqqghc6lcYej1aveQteWY+4POIwlqkYE21HKWaLDF6lWNuqCobEAyTovVhtI32Rbv2RQ== - dependencies: - cssnano-utils "^3.1.0" - postcss-value-parser "^4.2.0" - -postcss-overflow-shorthand@^3.0.4: - version "3.0.4" - resolved "https://registry.npmjs.org/postcss-overflow-shorthand/-/postcss-overflow-shorthand-3.0.4.tgz#7ed6486fec44b76f0eab15aa4866cda5d55d893e" - integrity sha512-otYl/ylHK8Y9bcBnPLo3foYFLL6a6Ak+3EQBPOTR7luMYCOsiVTUk1iLvNf6tVPNGXcoL9Hoz37kpfriRIFb4A== - dependencies: - postcss-value-parser "^4.2.0" - -postcss-page-break@^3.0.4: - version "3.0.4" - resolved "https://registry.npmjs.org/postcss-page-break/-/postcss-page-break-3.0.4.tgz#7fbf741c233621622b68d435babfb70dd8c1ee5f" - integrity sha512-1JGu8oCjVXLa9q9rFTo4MbeeA5FMe00/9C7lN4va606Rdb+HkxXtXsmEDrIraQ11fGz/WvKWa8gMuCKkrXpTsQ== - -postcss-place@^7.0.5: - version "7.0.5" - resolved "https://registry.npmjs.org/postcss-place/-/postcss-place-7.0.5.tgz#95dbf85fd9656a3a6e60e832b5809914236986c4" - integrity sha512-wR8igaZROA6Z4pv0d+bvVrvGY4GVHihBCBQieXFY3kuSuMyOmEnnfFzHl/tQuqHZkfkIVBEbDvYcFfHmpSet9g== - dependencies: - postcss-value-parser "^4.2.0" - -postcss-preset-env@^7.0.1: - version "7.8.3" - resolved "https://registry.npmjs.org/postcss-preset-env/-/postcss-preset-env-7.8.3.tgz#2a50f5e612c3149cc7af75634e202a5b2ad4f1e2" - integrity sha512-T1LgRm5uEVFSEF83vHZJV2z19lHg4yJuZ6gXZZkqVsqv63nlr6zabMH3l4Pc01FQCyfWVrh2GaUeCVy9Po+Aag== - dependencies: - "@csstools/postcss-cascade-layers" "^1.1.1" - "@csstools/postcss-color-function" "^1.1.1" - "@csstools/postcss-font-format-keywords" "^1.0.1" - "@csstools/postcss-hwb-function" "^1.0.2" - "@csstools/postcss-ic-unit" "^1.0.1" - "@csstools/postcss-is-pseudo-class" "^2.0.7" - "@csstools/postcss-nested-calc" "^1.0.0" - "@csstools/postcss-normalize-display-values" "^1.0.1" - "@csstools/postcss-oklab-function" "^1.1.1" - "@csstools/postcss-progressive-custom-properties" "^1.3.0" - "@csstools/postcss-stepped-value-functions" "^1.0.1" - "@csstools/postcss-text-decoration-shorthand" "^1.0.0" - "@csstools/postcss-trigonometric-functions" "^1.0.2" - "@csstools/postcss-unset-value" "^1.0.2" - autoprefixer "^10.4.13" - browserslist "^4.21.4" - css-blank-pseudo "^3.0.3" - css-has-pseudo "^3.0.4" - css-prefers-color-scheme "^6.0.3" - cssdb "^7.1.0" - postcss-attribute-case-insensitive "^5.0.2" - postcss-clamp "^4.1.0" - postcss-color-functional-notation "^4.2.4" - postcss-color-hex-alpha "^8.0.4" - postcss-color-rebeccapurple "^7.1.1" - postcss-custom-media "^8.0.2" - postcss-custom-properties "^12.1.10" - postcss-custom-selectors "^6.0.3" - postcss-dir-pseudo-class "^6.0.5" - postcss-double-position-gradients "^3.1.2" - postcss-env-function "^4.0.6" - postcss-focus-visible "^6.0.4" - postcss-focus-within "^5.0.4" - postcss-font-variant "^5.0.0" - postcss-gap-properties "^3.0.5" - postcss-image-set-function "^4.0.7" - postcss-initial "^4.0.1" - postcss-lab-function "^4.2.1" - postcss-logical "^5.0.4" - postcss-media-minmax "^5.0.0" - postcss-nesting "^10.2.0" - postcss-opacity-percentage "^1.1.2" - postcss-overflow-shorthand "^3.0.4" - postcss-page-break "^3.0.4" - postcss-place "^7.0.5" - postcss-pseudo-class-any-link "^7.1.6" - postcss-replace-overflow-wrap "^4.0.0" - postcss-selector-not "^6.0.1" - postcss-value-parser "^4.2.0" - -postcss-pseudo-class-any-link@^7.1.6: - version "7.1.6" - resolved "https://registry.npmjs.org/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-7.1.6.tgz#2693b221902da772c278def85a4d9a64b6e617ab" - integrity sha512-9sCtZkO6f/5ML9WcTLcIyV1yz9D1rf0tWc+ulKcvV30s0iZKS/ONyETvoWsr6vnrmW+X+KmuK3gV/w5EWnT37w== - dependencies: - postcss-selector-parser "^6.0.10" - -postcss-reduce-initial@^5.1.1: - version "5.1.1" - resolved "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-5.1.1.tgz#c18b7dfb88aee24b1f8e4936541c29adbd35224e" - integrity sha512-//jeDqWcHPuXGZLoolFrUXBDyuEGbr9S2rMo19bkTIjBQ4PqkaO+oI8wua5BOUxpfi97i3PCoInsiFIEBfkm9w== - dependencies: - browserslist "^4.21.4" - caniuse-api "^3.0.0" - -postcss-reduce-transforms@^5.1.0: - version "5.1.0" - resolved "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-5.1.0.tgz#333b70e7758b802f3dd0ddfe98bb1ccfef96b6e9" - integrity sha512-2fbdbmgir5AvpW9RLtdONx1QoYG2/EtqpNQbFASDlixBbAYuTcJ0dECwlqNqH7VbaUnEnh8SrxOe2sRIn24XyQ== - dependencies: - postcss-value-parser "^4.2.0" - -postcss-replace-overflow-wrap@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-4.0.0.tgz#d2df6bed10b477bf9c52fab28c568b4b29ca4319" - integrity sha512-KmF7SBPphT4gPPcKZc7aDkweHiKEEO8cla/GjcBK+ckKxiZslIu3C4GCRW3DNfL0o7yW7kMQu9xlZ1kXRXLXtw== - -postcss-selector-not@^6.0.1: - version "6.0.1" - resolved "https://registry.npmjs.org/postcss-selector-not/-/postcss-selector-not-6.0.1.tgz#8f0a709bf7d4b45222793fc34409be407537556d" - integrity sha512-1i9affjAe9xu/y9uqWH+tD4r6/hDaXJruk8xn2x1vzxC2U3J3LKO3zJW4CyxlNhA56pADJ/djpEwpH1RClI2rQ== - dependencies: - postcss-selector-parser "^6.0.10" - -postcss-selector-parser@^6.0.10, postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4, postcss-selector-parser@^6.0.5, postcss-selector-parser@^6.0.9: - version "6.0.11" - resolved "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.11.tgz#2e41dc39b7ad74046e1615185185cd0b17d0c8dc" - integrity sha512-zbARubNdogI9j7WY4nQJBiNqQf3sLS3wCP4WfOidu+p28LofJqDH1tcXypGrcmMHhDk2t9wGhCsYe/+szLTy1g== - dependencies: - cssesc "^3.0.0" - util-deprecate "^1.0.2" - -postcss-svgo@^5.1.0: - version "5.1.0" - resolved "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-5.1.0.tgz#0a317400ced789f233a28826e77523f15857d80d" - integrity sha512-D75KsH1zm5ZrHyxPakAxJWtkyXew5qwS70v56exwvw542d9CRtTo78K0WeFxZB4G7JXKKMbEZtZayTGdIky/eA== - dependencies: - postcss-value-parser "^4.2.0" - svgo "^2.7.0" - -postcss-unique-selectors@^5.1.1: - version "5.1.1" - resolved "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-5.1.1.tgz#a9f273d1eacd09e9aa6088f4b0507b18b1b541b6" - integrity sha512-5JiODlELrz8L2HwxfPnhOWZYWDxVHWL83ufOv84NrcgipI7TaeRsatAhK4Tr2/ZiYldpK/wBvw5BD3qfaK96GA== +pkg-types@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/pkg-types/-/pkg-types-1.0.3.tgz#988b42ab19254c01614d13f4f65a2cfc7880f868" + integrity sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A== dependencies: - postcss-selector-parser "^6.0.5" + jsonc-parser "^3.2.0" + mlly "^1.2.0" + pathe "^1.1.0" -postcss-value-parser@^4.0.0, postcss-value-parser@^4.0.2, postcss-value-parser@^4.1.0, postcss-value-parser@^4.2.0: +postcss-value-parser@^4.0.2: version "4.2.0" - resolved "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" + resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== -postcss@^7.0.35: - version "7.0.39" - resolved "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz#9624375d965630e2e1f2c02a935c82a59cb48309" - integrity sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA== - dependencies: - picocolors "^0.2.1" - source-map "^0.6.1" - -postcss@^8.3.5, postcss@^8.4.18, postcss@^8.4.4: - version "8.4.19" - resolved "https://registry.npmjs.org/postcss/-/postcss-8.4.19.tgz#61178e2add236b17351897c8bcc0b4c8ecab56fc" - integrity sha512-h+pbPsyhlYj6N2ozBmHhHrs9DzGmbaarbLvWipMRO7RLS+v4onj26MPFXA5OBYFxyqYhUJK456SwDcY9H2/zsA== +postcss@^8.4.27, postcss@^8.4.31: + version "8.4.31" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.31.tgz#92b451050a9f914da6755af352bdc0192508656d" + integrity sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ== dependencies: - nanoid "^3.3.4" + nanoid "^3.3.6" picocolors "^1.0.0" source-map-js "^1.0.2" prelude-ls@^1.2.1: version "1.2.1" - resolved "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== -prelude-ls@~1.1.2: - version "1.1.2" - resolved "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" - integrity sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w== - prettier-linter-helpers@^1.0.0: version "1.0.0" - resolved "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz#d23d41fe1375646de2d0104d3454a3008802cf7b" + resolved "https://registry.yarnpkg.com/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz#d23d41fe1375646de2d0104d3454a3008802cf7b" integrity sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w== dependencies: fast-diff "^1.1.2" -prettier-plugin-organize-imports@^3.2.0: - version "3.2.1" - resolved "https://registry.npmjs.org/prettier-plugin-organize-imports/-/prettier-plugin-organize-imports-3.2.1.tgz#7e0e0a18457e0166e740daaff1aed1c08069fcb9" - integrity sha512-bty7C2Ecard5EOXirtzeCAqj4FU4epeuWrQt/Z+sh8UVEpBlBZ3m3KNPz2kFu7KgRTQx/C9o4/TdquPD1jOqjQ== - -prettier@^2.8.0: - version "2.8.0" - resolved "https://registry.npmjs.org/prettier/-/prettier-2.8.0.tgz#c7df58393c9ba77d6fba3921ae01faf994fb9dc9" - integrity sha512-9Lmg8hTFZKG0Asr/kW9Bp8tJjRVluO8EJQVfY2T7FMw9T5jy4I/Uvx0Rca/XWf50QQ1/SS48+6IJWnrb+2yemA== - -pretty-bytes@^5.3.0, pretty-bytes@^5.4.1: - version "5.6.0" - resolved "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz#356256f643804773c82f64723fe78c92c62beaeb" - integrity sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg== - -pretty-error@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/pretty-error/-/pretty-error-4.0.0.tgz#90a703f46dd7234adb46d0f84823e9d1cb8f10d6" - integrity sha512-AoJ5YMAcXKYxKhuJGdcvse+Voc6v1RgnsR3nWcYU7q4t6z0Q6T86sv5Zq8VIRbOWWFpvdGE83LtdSMNd+6Y0xw== - dependencies: - lodash "^4.17.20" - renderkid "^3.0.0" - -pretty-format@^27.0.0, pretty-format@^27.0.2, pretty-format@^27.5.1: - version "27.5.1" - resolved "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz#2181879fdea51a7a5851fb39d920faa63f01d88e" - integrity sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ== - dependencies: - ansi-regex "^5.0.1" - ansi-styles "^5.0.0" - react-is "^17.0.1" +prettier-plugin-organize-imports@^3.2.3: + version "3.2.3" + resolved "https://registry.yarnpkg.com/prettier-plugin-organize-imports/-/prettier-plugin-organize-imports-3.2.3.tgz#6b0141ac71f7ee9a673ce83e95456319e3a7cf0d" + integrity sha512-KFvk8C/zGyvUaE3RvxN2MhCLwzV6OBbFSkwZ2OamCrs9ZY4i5L77jQ/w4UmUr+lqX8qbaqVq6bZZkApn+IgJSg== -pretty-format@^28.1.3: - version "28.1.3" - resolved "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz#c9fba8cedf99ce50963a11b27d982a9ae90970d5" - integrity sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q== - dependencies: - "@jest/schemas" "^28.1.3" - ansi-regex "^5.0.1" - ansi-styles "^5.0.0" - react-is "^18.0.0" +prettier@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.0.3.tgz#432a51f7ba422d1469096c0fdc28e235db8f9643" + integrity sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg== -pretty-format@^29.0.0, pretty-format@^29.3.1: - version "29.3.1" - resolved "https://registry.npmjs.org/pretty-format/-/pretty-format-29.3.1.tgz#1841cac822b02b4da8971dacb03e8a871b4722da" - integrity sha512-FyLnmb1cYJV8biEIiRyzRFvs2lry7PPIvOqKVe1GCUEYg4YGmlx1qG9EJNMxArYm7piII4qb8UV1Pncq5dxmcg== +pretty-format@^29.5.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.7.0.tgz#ca42c758310f365bfa71a0bda0a807160b776812" + integrity sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ== dependencies: - "@jest/schemas" "^29.0.0" + "@jest/schemas" "^29.6.3" ansi-styles "^5.0.0" react-is "^18.0.0" -process-nextick-args@~2.0.0: - version "2.0.1" - resolved "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" - integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== - -promise@^8.1.0: - version "8.3.0" - resolved "https://registry.npmjs.org/promise/-/promise-8.3.0.tgz#8cb333d1edeb61ef23869fbb8a4ea0279ab60e0a" - integrity sha512-rZPNPKTOYVNEEKFaq1HqTgOwZD+4/YHS5ukLzQCypkj+OkYx7iv0mA91lJlpPPZ8vMau3IIGj5Qlwrx+8iiSmg== - dependencies: - asap "~2.0.6" - -prompts@^2.0.1, prompts@^2.4.2: - version "2.4.2" - resolved "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" - integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q== - dependencies: - kleur "^3.0.3" - sisteransi "^1.0.5" - -prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1: +prop-types@^15.7.2, prop-types@^15.8.1: version "15.8.1" - resolved "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" + resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== dependencies: loose-envify "^1.4.0" @@ -8673,172 +4186,70 @@ prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1: react-is "^16.13.1" propagate@^2.0.0: - version "2.0.1" - resolved "https://registry.npmjs.org/propagate/-/propagate-2.0.1.tgz#40cdedab18085c792334e64f0ac17256d38f9a45" - integrity sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag== - -proxy-addr@~2.0.7: - version "2.0.7" - resolved "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" - integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg== - dependencies: - forwarded "0.2.0" - ipaddr.js "1.9.1" - -psl@^1.1.33: - version "1.9.0" - resolved "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz#d0df2a137f00794565fcaf3b2c00cd09f8d5a5a7" - integrity sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag== - -punycode@^2.1.0, punycode@^2.1.1: - version "2.1.1" - resolved "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" - integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== - -q@^1.1.2: - version "1.5.1" - resolved "https://registry.npmjs.org/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" - integrity sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw== - -qs@6.11.0: - version "6.11.0" - resolved "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a" - integrity sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q== - dependencies: - side-channel "^1.0.4" - -querystringify@^2.1.1: - version "2.2.0" - resolved "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6" - integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ== - -queue-microtask@^1.2.2: - version "1.2.3" - resolved "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" - integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== - -quick-lru@^5.1.1: - version "5.1.1" - resolved "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932" - integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA== - -raf@^3.4.1: - version "3.4.1" - resolved "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz#0742e99a4a6552f445d73e3ee0328af0ff1ede39" - integrity sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA== - dependencies: - performance-now "^2.1.0" - -randombytes@^2.1.0: - version "2.1.0" - resolved "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" - integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== - dependencies: - safe-buffer "^5.1.0" - -range-parser@^1.2.1, range-parser@~1.2.1: - version "1.2.1" - resolved "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" - integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== - -raw-body@2.5.1: - version "2.5.1" - resolved "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz#fe1b1628b181b700215e5fd42389f98b71392857" - integrity sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig== - dependencies: - bytes "3.1.2" - http-errors "2.0.0" - iconv-lite "0.4.24" - unpipe "1.0.0" + version "2.0.1" + resolved "https://registry.yarnpkg.com/propagate/-/propagate-2.0.1.tgz#40cdedab18085c792334e64f0ac17256d38f9a45" + integrity sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag== -react-app-polyfill@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/react-app-polyfill/-/react-app-polyfill-3.0.0.tgz#95221e0a9bd259e5ca6b177c7bb1cb6768f68fd7" - integrity sha512-sZ41cxiU5llIB003yxxQBYrARBqe0repqPTTYBTmMqTz9szeBbE37BehCE891NZsmdZqqP+xWKdT3eo3vOzN8w== - dependencies: - core-js "^3.19.2" - object-assign "^4.1.1" - promise "^8.1.0" - raf "^3.4.1" - regenerator-runtime "^0.13.9" - whatwg-fetch "^3.6.2" +punycode@^2.1.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.0.tgz#f67fa67c94da8f4d0cfff981aee4118064199b8f" + integrity sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA== -react-chartjs-2@^5.0.1: - version "5.0.1" - resolved "https://registry.npmjs.org/react-chartjs-2/-/react-chartjs-2-5.0.1.tgz#7ef7dd57441903e8d34e1d06a1aead1095d0bca8" - integrity sha512-u38C9OxynlNCBp+79grgXRs7DSJ9w8FuQ5/HO5FbYBbri8HSZW+9SWgjVshLkbXBfXnMGWakbHEtvN0nL2UG7Q== +qrcode-generator@1.4.4: + version "1.4.4" + resolved "https://registry.yarnpkg.com/qrcode-generator/-/qrcode-generator-1.4.4.tgz#63f771224854759329a99048806a53ed278740e7" + integrity sha512-HM7yY8O2ilqhmULxGMpcHSF1EhJJ9yBj8gvDEuZ6M+KGJ0YY2hKpnXvRD+hZPLrDVck3ExIGhmPtSdcjC+guuw== -react-content-loader@^6.2.0: - version "6.2.0" - resolved "https://registry.npmjs.org/react-content-loader/-/react-content-loader-6.2.0.tgz#cd8fee8160b8fda6610d0c69ce5aee7b8094cba6" - integrity sha512-r1dI6S+uHNLW68qraLE2njJYOuy6976PpCExuCZUcABWbfnF3FMcmuESRI8L4Bj45wnZ7n8g71hkPLzbma7/Cw== +queue-microtask@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" + integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== -react-copy-to-clipboard@^5.1.0: - version "5.1.0" - resolved "https://registry.npmjs.org/react-copy-to-clipboard/-/react-copy-to-clipboard-5.1.0.tgz#09aae5ec4c62750ccb2e6421a58725eabc41255c" - integrity sha512-k61RsNgAayIJNoy9yDsYzDe/yAZAzEbEgcz3DZMhF686LEyukcE1hzurxe85JandPUG+yTfGVFzuEw3xt8WP/A== +rc-slider@^10.3.1: + version "10.3.1" + resolved "https://registry.yarnpkg.com/rc-slider/-/rc-slider-10.3.1.tgz#345e818975f4bb61b66340799af8cfccad7c8ad7" + integrity sha512-XszsZLkbjcG9ogQy/zUC0n2kndoKUAnY/Vnk1Go5Gx+JJQBz0Tl15d5IfSiglwBUZPS9vsUJZkfCmkIZSqWbcA== dependencies: - copy-to-clipboard "^3.3.1" - prop-types "^15.8.1" + "@babel/runtime" "^7.10.1" + classnames "^2.2.5" + rc-util "^5.27.0" -react-dev-utils@^12.0.1: - version "12.0.1" - resolved "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-12.0.1.tgz#ba92edb4a1f379bd46ccd6bcd4e7bc398df33e73" - integrity sha512-84Ivxmr17KjUupyqzFode6xKhjwuEJDROWKJy/BthkL7Wn6NJ8h4WE6k/exAv6ImS+0oZLRRW5j/aINMHyeGeQ== +rc-util@^5.27.0: + version "5.38.0" + resolved "https://registry.yarnpkg.com/rc-util/-/rc-util-5.38.0.tgz#18a3d1c26ba3c43fabfbe6303e825dabd9e5f4f0" + integrity sha512-yV/YBNdFn+edyBpBdCqkPE29Su0jWcHNgwx2dJbRqMrMfrUcMJUjCRV+ZPhcvWyKFJ63GzEerPrz9JIVo0zXmA== dependencies: - "@babel/code-frame" "^7.16.0" - address "^1.1.2" - browserslist "^4.18.1" - chalk "^4.1.2" - cross-spawn "^7.0.3" - detect-port-alt "^1.1.6" - escape-string-regexp "^4.0.0" - filesize "^8.0.6" - find-up "^5.0.0" - fork-ts-checker-webpack-plugin "^6.5.0" - global-modules "^2.0.0" - globby "^11.0.4" - gzip-size "^6.0.0" - immer "^9.0.7" - is-root "^2.1.0" - loader-utils "^3.2.0" - open "^8.4.0" - pkg-up "^3.1.0" - prompts "^2.4.2" - react-error-overlay "^6.0.11" - recursive-readdir "^2.2.2" - shell-quote "^1.7.3" - strip-ansi "^6.0.1" - text-table "^0.2.0" + "@babel/runtime" "^7.18.3" + react-is "^18.2.0" + +react-chartjs-2@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/react-chartjs-2/-/react-chartjs-2-5.2.0.tgz#43c1e3549071c00a1a083ecbd26c1ad34d385f5d" + integrity sha512-98iN5aguJyVSxp5U3CblRLH67J8gkfyGNbiK3c+l1QI/G4irHMPQw44aEPmjVag+YKTyQ260NcF82GTQ3bdscA== -react-dom@^18.1.0: +react-dom@^18.2.0: version "18.2.0" - resolved "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d" integrity sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g== dependencies: loose-envify "^1.1.0" scheduler "^0.23.0" -react-error-boundary@^3.1.4: - version "3.1.4" - resolved "https://registry.npmjs.org/react-error-boundary/-/react-error-boundary-3.1.4.tgz#255db92b23197108757a888b01e5b729919abde0" - integrity sha512-uM9uPzZJTF6wRQORmSrvOIgt4lJ9MC1sNgEOj2XGsDTRE4kmpWxg7ENK9EWNKJRMAOY9z0MuF4yIfl6gp4sotA== +react-error-boundary@^4.0.11: + version "4.0.11" + resolved "https://registry.yarnpkg.com/react-error-boundary/-/react-error-boundary-4.0.11.tgz#36bf44de7746714725a814630282fee83a7c9a1c" + integrity sha512-U13ul67aP5DOSPNSCWQ/eO0AQEYzEFkVljULQIjMV0KlffTAhxuDoBKdO0pb/JZ8mDhMKFZ9NZi0BmLGUiNphw== dependencies: "@babel/runtime" "^7.12.5" -react-error-overlay@^6.0.11: - version "6.0.11" - resolved "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.11.tgz#92835de5841c5cf08ba00ddd2d677b6d17ff9adb" - integrity sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg== - react-fast-compare@^3.1.1: - version "3.2.0" - resolved "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.0.tgz#641a9da81b6a6320f270e89724fb45a0b39e43bb" - integrity sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA== + version "3.2.2" + resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.2.tgz#929a97a532304ce9fee4bcae44234f1ce2c21d49" + integrity sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ== react-helmet@^6.1.0: version "6.1.0" - resolved "https://registry.npmjs.org/react-helmet/-/react-helmet-6.1.0.tgz#a750d5165cb13cf213e44747502652e794468726" + resolved "https://registry.yarnpkg.com/react-helmet/-/react-helmet-6.1.0.tgz#a750d5165cb13cf213e44747502652e794468726" integrity sha512-4uMzEY9nlDlgxr61NL3XbKRy1hEkXmKNXhjbAIOVw5vcFrsdYbH2FEwcNyWvWinl103nXgzYNlns9ca+8kFiWw== dependencies: object-assign "^4.1.1" @@ -8846,165 +4257,72 @@ react-helmet@^6.1.0: react-fast-compare "^3.1.1" react-side-effect "^2.1.0" -react-i18next@^12.0.0: - version "12.0.0" - resolved "https://registry.npmjs.org/react-i18next/-/react-i18next-12.0.0.tgz#634015a2c035779c5736ae4c2e5c34c1659753b1" - integrity sha512-/O7N6aIEAl1FaWZBNvhdIo9itvF/MO/nRKr9pYqRc9LhuC1u21SlfwpiYQqvaeNSEW3g3qUXLREOWMt+gxrWbg== +react-i18next@^13.3.1: + version "13.3.1" + resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-13.3.1.tgz#9b072bf4dd4cafb028e92315a8a1415f8034bdca" + integrity sha512-JAtYREK879JXaN9GdzfBI4yJeo/XyLeXWUsRABvYXiFUakhZJ40l+kaTo+i+A/3cKIED41kS/HAbZ5BzFtq/Og== dependencies: - "@babel/runtime" "^7.14.5" + "@babel/runtime" "^7.22.5" html-parse-stringify "^3.0.1" react-is@^16.13.1, react-is@^16.7.0: version "16.13.1" - resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== -react-is@^17.0.1, react-is@^17.0.2: - version "17.0.2" - resolved "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" - integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== - -react-is@^18.0.0: +react-is@^18.0.0, react-is@^18.2.0: version "18.2.0" - resolved "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b" integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w== -react-lottie@^1.2.3: - version "1.2.3" - resolved "https://registry.npmjs.org/react-lottie/-/react-lottie-1.2.3.tgz#8544b96939e088658072eea5e12d912cdaa3acc1" - integrity sha512-qLCERxUr8M+4mm1LU0Ruxw5Y5Fn/OmYkGfnA+JDM/dZb3oKwVAJCjwnjkj9TMHtzR2U6sMEUD3ZZ1RaHagM7kA== +react-qr-reader@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/react-qr-reader/-/react-qr-reader-2.2.1.tgz#dc89046d1c1a1da837a683dd970de5926817d55b" + integrity sha512-EL5JEj53u2yAOgtpAKAVBzD/SiKWn0Bl7AZy6ZrSf1lub7xHwtaXe6XSx36Wbhl1VMGmvmrwYMRwO1aSCT2fwA== dependencies: - babel-runtime "^6.26.0" - lottie-web "^5.1.3" - -react-refresh@^0.11.0: - version "0.11.0" - resolved "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz#77198b944733f0f1f1a90e791de4541f9f074046" - integrity sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A== + jsqr "^1.2.0" + prop-types "^15.7.2" + webrtc-adapter "^7.2.1" -react-router-dom@^6.4.3: - version "6.4.3" - resolved "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.4.3.tgz#70093b5f65f85f1df9e5d4182eb7ff3a08299275" - integrity sha512-MiaYQU8CwVCaOfJdYvt84KQNjT78VF0TJrA17SIQgNHRvLnXDJO6qsFqq8F/zzB1BWZjCFIrQpu4QxcshitziQ== +react-router-dom@^6.17.0: + version "6.17.0" + resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.17.0.tgz#ea73f89186546c1cf72b10fcb7356d874321b2ad" + integrity sha512-qWHkkbXQX+6li0COUUPKAUkxjNNqPJuiBd27dVwQGDNsuFBdMbrS6UZ0CLYc4CsbdLYTckn4oB4tGDuPZpPhaQ== dependencies: - "@remix-run/router" "1.0.3" - react-router "6.4.3" + "@remix-run/router" "1.10.0" + react-router "6.17.0" -react-router@6.4.3: - version "6.4.3" - resolved "https://registry.npmjs.org/react-router/-/react-router-6.4.3.tgz#9ed3ee4d6e95889e9b075a5d63e29acc7def0d49" - integrity sha512-BT6DoGn6aV1FVP5yfODMOiieakp3z46P1Fk0RNzJMACzE7C339sFuHebfvWtnB4pzBvXXkHP2vscJzWRuUjTtA== +react-router@6.17.0: + version "6.17.0" + resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.17.0.tgz#7b680c4cefbc425b57537eb9c73bedecbdc67c1e" + integrity sha512-YJR3OTJzi3zhqeJYADHANCGPUu9J+6fT5GLv82UWRGSxu6oJYCKVmxUcaBQuGm9udpWmPsvpme/CdHumqgsoaA== dependencies: - "@remix-run/router" "1.0.3" - -react-scripts@5.0.1: - version "5.0.1" - resolved "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz#6285dbd65a8ba6e49ca8d651ce30645a6d980003" - integrity sha512-8VAmEm/ZAwQzJ+GOMLbBsTdDKOpuZh7RPs0UymvBR2vRk4iZWCskjbFnxqjrzoIvlNNRZ3QJFx6/qDSi6zSnaQ== - dependencies: - "@babel/core" "^7.16.0" - "@pmmmwh/react-refresh-webpack-plugin" "^0.5.3" - "@svgr/webpack" "^5.5.0" - babel-jest "^27.4.2" - babel-loader "^8.2.3" - babel-plugin-named-asset-import "^0.3.8" - babel-preset-react-app "^10.0.1" - bfj "^7.0.2" - browserslist "^4.18.1" - camelcase "^6.2.1" - case-sensitive-paths-webpack-plugin "^2.4.0" - css-loader "^6.5.1" - css-minimizer-webpack-plugin "^3.2.0" - dotenv "^10.0.0" - dotenv-expand "^5.1.0" - eslint "^8.3.0" - eslint-config-react-app "^7.0.1" - eslint-webpack-plugin "^3.1.1" - file-loader "^6.2.0" - fs-extra "^10.0.0" - html-webpack-plugin "^5.5.0" - identity-obj-proxy "^3.0.0" - jest "^27.4.3" - jest-resolve "^27.4.2" - jest-watch-typeahead "^1.0.0" - mini-css-extract-plugin "^2.4.5" - postcss "^8.4.4" - postcss-flexbugs-fixes "^5.0.2" - postcss-loader "^6.2.1" - postcss-normalize "^10.0.1" - postcss-preset-env "^7.0.1" - prompts "^2.4.2" - react-app-polyfill "^3.0.0" - react-dev-utils "^12.0.1" - react-refresh "^0.11.0" - resolve "^1.20.0" - resolve-url-loader "^4.0.0" - sass-loader "^12.3.0" - semver "^7.3.5" - source-map-loader "^3.0.0" - style-loader "^3.3.1" - tailwindcss "^3.0.2" - terser-webpack-plugin "^5.2.5" - webpack "^5.64.4" - webpack-dev-server "^4.6.0" - webpack-manifest-plugin "^4.0.2" - workbox-webpack-plugin "^6.4.1" - optionalDependencies: - fsevents "^2.3.2" + "@remix-run/router" "1.10.0" -react-scroll@^1.8.6: - version "1.8.8" - resolved "https://registry.npmjs.org/react-scroll/-/react-scroll-1.8.8.tgz#445c6137174d6f2ed02352af408a48d160959e22" - integrity sha512-RnU0wLaLozKIhLNAfoz6yxMus+PMypk7eBRfOitalYd2+qCiHrZdJ0MSDBs1Y23IFSfqSCdCEmENMJeSh0KoHA== +react-scroll@^1.9.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/react-scroll/-/react-scroll-1.9.0.tgz#2984006e184afd0e4018f837d127edf5fa8f152c" + integrity sha512-mamNcaX9Ng+JeSbBu97nWwRhYvL2oba+xR2GxvyXsbDeGP+gkYIKZ+aDMMj/n20TbV9SCWm/H7nyuNTSiXA6yA== dependencies: lodash.throttle "^4.1.1" prop-types "^15.7.2" react-side-effect@^2.1.0: version "2.1.2" - resolved "https://registry.npmjs.org/react-side-effect/-/react-side-effect-2.1.2.tgz#dc6345b9e8f9906dc2eeb68700b615e0b4fe752a" + resolved "https://registry.yarnpkg.com/react-side-effect/-/react-side-effect-2.1.2.tgz#dc6345b9e8f9906dc2eeb68700b615e0b4fe752a" integrity sha512-PVjOcvVOyIILrYoyGEpDN3vmYNLdy1CajSFNt4TDsVQC5KpTijDvWVoR+/7Rz2xT978D8/ZtFceXxzsPwZEDvw== -react@^16.13.1: - version "16.14.0" - resolved "https://registry.npmjs.org/react/-/react-16.14.0.tgz#94d776ddd0aaa37da3eda8fc5b6b18a4c9a3114d" - integrity sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g== - dependencies: - loose-envify "^1.1.0" - object-assign "^4.1.1" - prop-types "^15.6.2" - react@^18.2.0: version "18.2.0" - resolved "https://registry.npmjs.org/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5" + resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5" integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ== dependencies: loose-envify "^1.1.0" -read-cache@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz#e664ef31161166c9751cdbe8dbcf86b5fb58f774" - integrity sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA== - dependencies: - pify "^2.3.0" - -readable-stream@^2.0.1: - version "2.3.7" - resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" - integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.3" - isarray "~1.0.0" - process-nextick-args "~2.0.0" - safe-buffer "~5.1.1" - string_decoder "~1.1.1" - util-deprecate "~1.0.1" - -readable-stream@^3.0.6: - version "3.6.0" - resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" - integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== +readable-stream@^3.6.0: + version "3.6.2" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" + integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== dependencies: inherits "^2.0.3" string_decoder "^1.1.1" @@ -9012,177 +4330,72 @@ readable-stream@^3.0.6: readdirp@~3.6.0: version "3.6.0" - resolved "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== dependencies: picomatch "^2.2.1" -recursive-readdir@^2.2.2: - version "2.2.3" - resolved "https://registry.npmjs.org/recursive-readdir/-/recursive-readdir-2.2.3.tgz#e726f328c0d69153bcabd5c322d3195252379372" - integrity sha512-8HrF5ZsXk5FAH9dgsx3BlUer73nIhuj+9OrQwEbLTPOBzGkL1lsFCR01am+v+0m2Cmbs1nP12hLDl5FA7EszKA== - dependencies: - minimatch "^3.0.5" - -redent@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz#e557b7998316bb53c9f1f56fa626352c6963059f" - integrity sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg== - dependencies: - indent-string "^4.0.0" - strip-indent "^3.0.0" - -regenerate-unicode-properties@^10.1.0: - version "10.1.0" - resolved "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.0.tgz#7c3192cab6dd24e21cb4461e5ddd7dd24fa8374c" - integrity sha512-d1VudCLoIGitcU/hEg2QqvyGZQmdC0Lf8BqdOMXGFSvJP4bNV1+XqbPQeHHLD51Jh4QJJ225dlIFvY4Ly6MXmQ== - dependencies: - regenerate "^1.4.2" - -regenerate@^1.4.2: - version "1.4.2" - resolved "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz#b9346d8827e8f5a32f7ba29637d398b69014848a" - integrity sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A== - -regenerator-runtime@^0.11.0: - version "0.11.1" - resolved "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9" - integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg== - -regenerator-runtime@^0.13.10, regenerator-runtime@^0.13.9: - version "0.13.11" - resolved "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9" - integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg== - -regenerator-transform@^0.15.0: - version "0.15.1" - resolved "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.1.tgz#f6c4e99fc1b4591f780db2586328e4d9a9d8dc56" - integrity sha512-knzmNAcuyxV+gQCufkYcvOqX/qIIfHLv0u5x79kRxuGojfYVky1f15TzZEu2Avte8QGepvUNTnLskf8E6X6Vyg== +reflect.getprototypeof@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/reflect.getprototypeof/-/reflect.getprototypeof-1.0.4.tgz#aaccbf41aca3821b87bb71d9dcbc7ad0ba50a3f3" + integrity sha512-ECkTw8TmJwW60lOTR+ZkODISW6RQ8+2CL3COqtiJKLd6MmB45hN51HprHFziKLGkAuTGQhBb91V8cy+KHlaCjw== dependencies: - "@babel/runtime" "^7.8.4" - -regex-parser@^2.2.11: - version "2.2.11" - resolved "https://registry.npmjs.org/regex-parser/-/regex-parser-2.2.11.tgz#3b37ec9049e19479806e878cabe7c1ca83ccfe58" - integrity sha512-jbD/FT0+9MBU2XAZluI7w2OBs1RBi6p9M83nkoZayQXXU9e8Robt69FcZc7wU4eJD/YFTjn1JdCk3rbMJajz8Q== - -regexp.prototype.flags@^1.4.3: - version "1.4.3" - resolved "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz#87cab30f80f66660181a3bb7bf5981a872b367ac" - integrity sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA== + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + get-intrinsic "^1.2.1" + globalthis "^1.0.3" + which-builtin-type "^1.1.3" + +regenerator-runtime@^0.14.0: + version "0.14.0" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz#5e19d68eb12d486f797e15a3c6a918f7cec5eb45" + integrity sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA== + +regexp.prototype.flags@^1.5.0, regexp.prototype.flags@^1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.1.tgz#90ce989138db209f81492edd734183ce99f9677e" + integrity sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg== dependencies: call-bind "^1.0.2" - define-properties "^1.1.3" - functions-have-names "^1.2.2" - -regexpp@^3.2.0: - version "3.2.0" - resolved "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2" - integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg== + define-properties "^1.2.0" + set-function-name "^2.0.0" -regexpu-core@^5.1.0: - version "5.2.2" - resolved "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.2.2.tgz#3e4e5d12103b64748711c3aad69934d7718e75fc" - integrity sha512-T0+1Zp2wjF/juXMrMxHxidqGYn8U4R+zleSJhX9tQ1PUsS8a9UtYfbsF9LdiVgNX3kiX8RNaKM42nfSgvFJjmw== - dependencies: - regenerate "^1.4.2" - regenerate-unicode-properties "^10.1.0" - regjsgen "^0.7.1" - regjsparser "^0.9.1" - unicode-match-property-ecmascript "^2.0.0" - unicode-match-property-value-ecmascript "^2.1.0" - -regjsgen@^0.7.1: - version "0.7.1" - resolved "https://registry.npmjs.org/regjsgen/-/regjsgen-0.7.1.tgz#ee5ef30e18d3f09b7c369b76e7c2373ed25546f6" - integrity sha512-RAt+8H2ZEzHeYWxZ3H2z6tF18zyyOnlcdaafLrm21Bguj7uZy6ULibiAFdXEtKQY4Sy7wDTwDiOazasMLc4KPA== - -regjsparser@^0.9.1: - version "0.9.1" - resolved "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz#272d05aa10c7c1f67095b1ff0addae8442fc5709" - integrity sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ== - dependencies: - jsesc "~0.5.0" - -relateurl@^0.2.7: - version "0.2.7" - resolved "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz#54dbf377e51440aca90a4cd274600d3ff2d888a9" - integrity sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog== - -renderkid@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/renderkid/-/renderkid-3.0.0.tgz#5fd823e4d6951d37358ecc9a58b1f06836b6268a" - integrity sha512-q/7VIQA8lmM1hF+jn+sFSPWGlMkSAeNYcPLmDQx2zzuiDfaLrOmumR8iaUKlenFgh0XRPIUeSPlH3A+AW3Z5pg== - dependencies: - css-select "^4.1.3" - dom-converter "^0.2.0" - htmlparser2 "^6.1.0" - lodash "^4.17.21" - strip-ansi "^6.0.1" +rehackt@0.0.4: + version "0.0.4" + resolved "https://registry.yarnpkg.com/rehackt/-/rehackt-0.0.4.tgz#dca5498e1f6c81d3d610ff2abfb3c3feec6afce8" + integrity sha512-xFroSGCbMEK/cTJVhq+c8l/AzIeMeojVyLqtZmr2jmIAFvePjapkCSGg9MnrcNk68HPaMxGf+Ndqozotu78ITw== require-directory@^2.1.1: version "2.1.1" - resolved "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== -require-from-string@^2.0.2: - version "2.0.2" - resolved "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" - integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== - -requires-port@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" - integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ== - -resolve-cwd@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" - integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg== - dependencies: - resolve-from "^5.0.0" - resolve-from@^4.0.0: version "4.0.0" - resolved "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== -resolve-from@^5.0.0: - version "5.0.0" - resolved "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" - integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== - -resolve-url-loader@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/resolve-url-loader/-/resolve-url-loader-4.0.0.tgz#d50d4ddc746bb10468443167acf800dcd6c3ad57" - integrity sha512-05VEMczVREcbtT7Bz+C+96eUO5HDNvdthIiMB34t7FcF8ehcu4wC0sSgPUubs3XW2Q3CNLJk/BJrCU9wVRymiA== - dependencies: - adjust-sourcemap-loader "^4.0.0" - convert-source-map "^1.7.0" - loader-utils "^2.0.0" - postcss "^7.0.35" - source-map "0.6.1" - -resolve.exports@^1.1.0: - version "1.1.0" - resolved "https://registry.npmjs.org/resolve.exports/-/resolve.exports-1.1.0.tgz#5ce842b94b05146c0e03076985d1d0e7e48c90c9" - integrity sha512-J1l+Zxxp4XK3LUDZ9m60LRJF/mAe4z6a4xyabPHk7pvK5t35dACV32iIjJDFeWZFfZlO29w6SZ67knR0tHzJtQ== +resolve-pkg-maps@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz#616b3dc2c57056b5588c31cdf4b3d64db133720f" + integrity sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw== -resolve@^1.1.7, resolve@^1.14.2, resolve@^1.19.0, resolve@^1.20.0, resolve@^1.22.0, resolve@^1.22.1: - version "1.22.1" - resolved "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177" - integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw== +resolve@^1.22.4: + version "1.22.8" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d" + integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== dependencies: - is-core-module "^2.9.0" + is-core-module "^2.13.0" path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" -resolve@^2.0.0-next.3: - version "2.0.0-next.4" - resolved "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.4.tgz#3d37a113d6429f496ec4752d2a2e58efb1fd4660" - integrity sha512-iMDbmAWtfU+MHpxt/I5iWI7cY6YVEZUQ3MBgPQ++XD1PELuJHIl82xBmObyP2KyQmkNB2dsqF7seoQQiAn5yDQ== +resolve@^2.0.0-next.4: + version "2.0.0-next.5" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-2.0.0-next.5.tgz#6b0ec3107e671e52b68cd068ef327173b90dc03c" + integrity sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA== dependencies: - is-core-module "^2.9.0" + is-core-module "^2.13.0" path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" @@ -9191,592 +4404,370 @@ response-iterator@^0.2.6: resolved "https://registry.yarnpkg.com/response-iterator/-/response-iterator-0.2.6.tgz#249005fb14d2e4eeb478a3f735a28fd8b4c9f3da" integrity sha512-pVzEEzrsg23Sh053rmDUvLSkGXluZio0qu8VT6ukrYuvtjVfCbDZH9d6PGXb8HZfzdNZt8feXv/jvUzlhRgLnw== -retry@^0.13.1: - version "0.13.1" - resolved "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz#185b1587acf67919d63b357349e03537b2484658" - integrity sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg== - reusify@^1.0.4: version "1.0.4" - resolved "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" + resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== -rimraf@^3.0.0, rimraf@^3.0.2: +rimraf@^3.0.2: version "3.0.2" - resolved "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== dependencies: glob "^7.1.3" -rollup-plugin-terser@^7.0.0: - version "7.0.2" - resolved "https://registry.npmjs.org/rollup-plugin-terser/-/rollup-plugin-terser-7.0.2.tgz#e8fbba4869981b2dc35ae7e8a502d5c6c04d324d" - integrity sha512-w3iIaU4OxcF52UUXiZNsNeuXIMDvFrr+ZXK6bFZ0Q60qyVfq4uLptoS4bbq3paG3x216eQllFZX7zt6TIImguQ== +ripemd160@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.2.tgz#a1c1a6f624751577ba5d07914cbc92850585890c" + integrity sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA== + dependencies: + hash-base "^3.0.0" + inherits "^2.0.1" + +rollup-plugin-visualizer@^5.9.2: + version "5.9.2" + resolved "https://registry.yarnpkg.com/rollup-plugin-visualizer/-/rollup-plugin-visualizer-5.9.2.tgz#f1aa2d9b1be8ebd6869223c742324897464d8891" + integrity sha512-waHktD5mlWrYFrhOLbti4YgQCn1uR24nYsNuXxg7LkPH8KdTXVWR9DNY1WU0QqokyMixVXJS4J04HNrVTMP01A== dependencies: - "@babel/code-frame" "^7.10.4" - jest-worker "^26.2.1" - serialize-javascript "^4.0.0" - terser "^5.0.0" + open "^8.4.0" + picomatch "^2.3.1" + source-map "^0.7.4" + yargs "^17.5.1" -rollup@^2.43.1: +rollup@^2.77.2: version "2.79.1" - resolved "https://registry.npmjs.org/rollup/-/rollup-2.79.1.tgz#bedee8faef7c9f93a2647ac0108748f497f081c7" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.79.1.tgz#bedee8faef7c9f93a2647ac0108748f497f081c7" integrity sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw== optionalDependencies: fsevents "~2.3.2" +rollup@^3.27.1: + version "3.29.4" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-3.29.4.tgz#4d70c0f9834146df8705bfb69a9a19c9e1109981" + integrity sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw== + optionalDependencies: + fsevents "~2.3.2" + +rtcpeerconnection-shim@^1.2.15: + version "1.2.15" + resolved "https://registry.yarnpkg.com/rtcpeerconnection-shim/-/rtcpeerconnection-shim-1.2.15.tgz#e7cc189a81b435324c4949aa3dfb51888684b243" + integrity sha512-C6DxhXt7bssQ1nHb154lqeL0SXz5Dx4RczXZu2Aa/L1NJFnEVDxFwCBo3fqtuljhHIGceg5JKBV4XJ0gW5JKyw== + dependencies: + sdp "^2.6.0" + +run-applescript@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/run-applescript/-/run-applescript-5.0.0.tgz#e11e1c932e055d5c6b40d98374e0268d9b11899c" + integrity sha512-XcT5rBksx1QdIhlFOCtgZkB99ZEouFZ1E2Kc2LHqNW13U3/74YGdkQRmThTwxy4QIyookibDKYZOPqX//6BlAg== + dependencies: + execa "^5.0.0" + run-parallel@^1.1.9: version "1.2.0" - resolved "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" + resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== dependencies: queue-microtask "^1.2.2" -rxjs@^7.5.7: - version "7.5.7" - resolved "https://registry.npmjs.org/rxjs/-/rxjs-7.5.7.tgz#2ec0d57fdc89ece220d2e702730ae8f1e49def39" - integrity sha512-z9MzKh/UcOqB3i20H6rtrlaE/CgjLOvheWK/9ILrbhROGTweAi1BaFsTT9FbwZi5Trr1qNRs+MXkhmR06awzQA== +rxjs@6: + version "6.6.7" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.7.tgz#90ac018acabf491bf65044235d5863c4dab804c9" + integrity sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ== + dependencies: + tslib "^1.9.0" + +rxjs@^7.8.1: + version "7.8.1" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.1.tgz#6f6f3d99ea8044291efd92e7c7fcf562c4057543" + integrity sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg== dependencies: tslib "^2.1.0" -safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: - version "5.1.2" - resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" - integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== +safe-array-concat@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/safe-array-concat/-/safe-array-concat-1.0.1.tgz#91686a63ce3adbea14d61b14c99572a8ff84754c" + integrity sha512-6XbUAseYE2KtOuGueyeobCySj9L4+66Tn6KQMOPQJrAJEowYKW/YR/MGJZl7FdydUdaFu4LYyDZjxf4/Nmo23Q== + dependencies: + call-bind "^1.0.2" + get-intrinsic "^1.2.1" + has-symbols "^1.0.3" + isarray "^2.0.5" -safe-buffer@5.2.1, safe-buffer@>=5.1.0, safe-buffer@^5.1.0, safe-buffer@~5.2.0: +safe-buffer@^5.0.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@~5.2.0: version "5.2.1" - resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== safe-regex-test@^1.0.0: version "1.0.0" - resolved "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz#793b874d524eb3640d1873aad03596db2d4f2295" + resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.0.0.tgz#793b874d524eb3640d1873aad03596db2d4f2295" integrity sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA== dependencies: call-bind "^1.0.2" get-intrinsic "^1.1.3" is-regex "^1.1.4" -"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0": - version "2.1.2" - resolved "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" - integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== - -sanitize.css@*: - version "13.0.0" - resolved "https://registry.npmjs.org/sanitize.css/-/sanitize.css-13.0.0.tgz#2675553974b27964c75562ade3bd85d79879f173" - integrity sha512-ZRwKbh/eQ6w9vmTjkuG0Ioi3HBwPFce0O+v//ve+aOq1oeCy7jMV2qzzAlpsNuqpqCBjjriM1lbtZbF/Q8jVyA== - -sass-loader@^12.3.0: - version "12.6.0" - resolved "https://registry.npmjs.org/sass-loader/-/sass-loader-12.6.0.tgz#5148362c8e2cdd4b950f3c63ac5d16dbfed37bcb" - integrity sha512-oLTaH0YCtX4cfnJZxKSLAyglED0naiYfNG1iXfU5w1LNZ+ukoA5DtyDIN5zmKVZwYNJP4KRc5Y3hkWga+7tYfA== - dependencies: - klona "^2.0.4" - neo-async "^2.6.2" - -sax@~1.2.4: - version "1.2.4" - resolved "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" - integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== - -saxes@^5.0.1: - version "5.0.1" - resolved "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz#eebab953fa3b7608dbe94e5dadb15c888fa6696d" - integrity sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw== +sass@^1.69.5: + version "1.69.5" + resolved "https://registry.yarnpkg.com/sass/-/sass-1.69.5.tgz#23e18d1c757a35f2e52cc81871060b9ad653dfde" + integrity sha512-qg2+UCJibLr2LCVOt3OlPhr/dqVHWOa9XtZf2OjbLs/T4VPSJ00udtgJxH3neXZm+QqX8B+3cU7RaLqp1iVfcQ== dependencies: - xmlchars "^2.2.0" + chokidar ">=3.0.0 <4.0.0" + immutable "^4.0.0" + source-map-js ">=0.6.2 <2.0.0" scheduler@^0.23.0: version "0.23.0" - resolved "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz#ba8041afc3d30eb206a487b6b384002e4e61fdfe" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.0.tgz#ba8041afc3d30eb206a487b6b384002e4e61fdfe" integrity sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw== dependencies: loose-envify "^1.1.0" -schema-utils@2.7.0: - version "2.7.0" - resolved "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.0.tgz#17151f76d8eae67fbbf77960c33c676ad9f4efc7" - integrity sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A== - dependencies: - "@types/json-schema" "^7.0.4" - ajv "^6.12.2" - ajv-keywords "^3.4.1" - -schema-utils@^2.6.5: - version "2.7.1" - resolved "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz#1ca4f32d1b24c590c203b8e7a50bf0ea4cd394d7" - integrity sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg== - dependencies: - "@types/json-schema" "^7.0.5" - ajv "^6.12.4" - ajv-keywords "^3.5.2" - -schema-utils@^3.0.0, schema-utils@^3.1.0, schema-utils@^3.1.1: - version "3.1.1" - resolved "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz#bc74c4b6b6995c1d88f76a8b77bea7219e0c8281" - integrity sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw== - dependencies: - "@types/json-schema" "^7.0.8" - ajv "^6.12.5" - ajv-keywords "^3.5.2" - -schema-utils@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz#60331e9e3ae78ec5d16353c467c34b3a0a1d3df7" - integrity sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg== - dependencies: - "@types/json-schema" "^7.0.9" - ajv "^8.8.0" - ajv-formats "^2.1.1" - ajv-keywords "^5.0.0" - -select-hose@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca" - integrity sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg== - -selfsigned@^2.1.1: - version "2.1.1" - resolved "https://registry.npmjs.org/selfsigned/-/selfsigned-2.1.1.tgz#18a7613d714c0cd3385c48af0075abf3f266af61" - integrity sha512-GSL3aowiF7wa/WtSFwnUrludWFoNhftq8bUkH9pkzjpN2XSPOAYEgg6e0sS9s0rZwgJzJiQRPU18A6clnoW5wQ== - dependencies: - node-forge "^1" +sdp@^2.12.0, sdp@^2.6.0: + version "2.12.0" + resolved "https://registry.yarnpkg.com/sdp/-/sdp-2.12.0.tgz#338a106af7560c86e4523f858349680350d53b22" + integrity sha512-jhXqQAQVM+8Xj5EjJGVweuEzgtGWb3tmEEpl3CLP3cStInSbVHSg0QWOGQzNq8pSID4JkpeV2mPqlMDLrm0/Vw== -semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.3.0: - version "6.3.0" - resolved "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" - integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== +semver@^6.0.0, semver@^6.3.0, semver@^6.3.1: + version "6.3.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" + integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== -semver@^7.3.2, semver@^7.3.5, semver@^7.3.7, semver@^7.3.8: - version "7.3.8" - resolved "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz#07a78feafb3f7b32347d725e33de7e2a2df67798" - integrity sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A== +semver@^7.3.4, semver@^7.3.5, semver@^7.5.0, semver@^7.5.4: + version "7.5.4" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" + integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== dependencies: lru-cache "^6.0.0" -send@0.18.0: - version "0.18.0" - resolved "https://registry.npmjs.org/send/-/send-0.18.0.tgz#670167cc654b05f5aa4a767f9113bb371bc706be" - integrity sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg== - dependencies: - debug "2.6.9" - depd "2.0.0" - destroy "1.2.0" - encodeurl "~1.0.2" - escape-html "~1.0.3" - etag "~1.8.1" - fresh "0.5.2" - http-errors "2.0.0" - mime "1.6.0" - ms "2.1.3" - on-finished "2.4.1" - range-parser "~1.2.1" - statuses "2.0.1" - -serialize-javascript@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz#b525e1238489a5ecfc42afacc3fe99e666f4b1aa" - integrity sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw== - dependencies: - randombytes "^2.1.0" - -serialize-javascript@^6.0.0: - version "6.0.0" - resolved "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz#efae5d88f45d7924141da8b5c3a7a7e663fefeb8" - integrity sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag== +set-function-length@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.1.1.tgz#4bc39fafb0307224a33e106a7d35ca1218d659ed" + integrity sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ== dependencies: - randombytes "^2.1.0" + define-data-property "^1.1.1" + get-intrinsic "^1.2.1" + gopd "^1.0.1" + has-property-descriptors "^1.0.0" -serve-index@^1.9.1: - version "1.9.1" - resolved "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz#d3768d69b1e7d82e5ce050fff5b453bea12a9239" - integrity sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw== +set-function-name@^2.0.0, set-function-name@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/set-function-name/-/set-function-name-2.0.1.tgz#12ce38b7954310b9f61faa12701620a0c882793a" + integrity sha512-tMNCiqYVkXIZgc2Hnoy2IvC/f8ezc5koaRFkCjrpWzGpCd3qbZXPzVy9MAZzK1ch/X0jvSkojys3oqJN0qCmdA== dependencies: - accepts "~1.3.4" - batch "0.6.1" - debug "2.6.9" - escape-html "~1.0.3" - http-errors "~1.6.2" - mime-types "~2.1.17" - parseurl "~1.3.2" + define-data-property "^1.0.1" + functions-have-names "^1.2.3" + has-property-descriptors "^1.0.0" -serve-static@1.15.0: - version "1.15.0" - resolved "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz#faaef08cffe0a1a62f60cad0c4e513cff0ac9540" - integrity sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g== +sha.js@^2.4.0: + version "2.4.11" + resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7" + integrity sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ== dependencies: - encodeurl "~1.0.2" - escape-html "~1.0.3" - parseurl "~1.3.3" - send "0.18.0" - -setprototypeof@1.1.0: - version "1.1.0" - resolved "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656" - integrity sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ== - -setprototypeof@1.2.0: - version "1.2.0" - resolved "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" - integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== + inherits "^2.0.1" + safe-buffer "^5.0.1" shallowequal@^1.1.0: version "1.1.0" - resolved "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz#188d521de95b9087404fd4dcb68b13df0ae4e7f8" + resolved "https://registry.yarnpkg.com/shallowequal/-/shallowequal-1.1.0.tgz#188d521de95b9087404fd4dcb68b13df0ae4e7f8" integrity sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ== shebang-command@^2.0.0: version "2.0.0" - resolved "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== dependencies: shebang-regex "^3.0.0" shebang-regex@^3.0.0: version "3.0.0" - resolved "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== -shell-quote@^1.7.3: - version "1.7.4" - resolved "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.4.tgz#33fe15dee71ab2a81fcbd3a52106c5cfb9fb75d8" - integrity sha512-8o/QEhSSRb1a5i7TFR0iM4G16Z0vYB2OQVs4G3aAFXjn3T6yEx8AZxy1PgDF7I00LZHYA3WxaSYIf5e5sAX8Rw== - side-channel@^1.0.4: version "1.0.4" - resolved "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== dependencies: call-bind "^1.0.0" get-intrinsic "^1.0.2" object-inspect "^1.9.0" -signal-exit@^3.0.2, signal-exit@^3.0.3: +siginfo@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/siginfo/-/siginfo-2.0.0.tgz#32e76c70b79724e3bb567cb9d543eb858ccfaf30" + integrity sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g== + +signal-exit@^3.0.3, signal-exit@^3.0.7: version "3.0.7" - resolved "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== -simple-swizzle@^0.2.2: - version "0.2.2" - resolved "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a" - integrity sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg== - dependencies: - is-arrayish "^0.3.1" - -sisteransi@^1.0.5: - version "1.0.5" - resolved "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" - integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== - slash@^3.0.0: version "3.0.0" - resolved "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" + resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== -slash@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz#2422372176c4c6c5addb5e2ada885af984b396a7" - integrity sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew== - -sockjs@^0.3.24: - version "0.3.24" - resolved "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz#c9bc8995f33a111bea0395ec30aa3206bdb5ccce" - integrity sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ== - dependencies: - faye-websocket "^0.11.3" - uuid "^8.3.2" - websocket-driver "^0.7.4" - -source-list-map@^2.0.0, source-list-map@^2.0.1: +smoldot@2.0.1: version "2.0.1" - resolved "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34" - integrity sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw== - -source-map-js@^1.0.1, source-map-js@^1.0.2: - version "1.0.2" - resolved "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" - integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== + resolved "https://registry.yarnpkg.com/smoldot/-/smoldot-2.0.1.tgz#c899cbb0827a010d3ca7944034f081786f533a4d" + integrity sha512-Wqw2fL/sELQByLSeeTX1Z/d0H4McmphPMx8vh6UZS/bIIDx81oU7s/drmx2iL/ME36uk++YxpRuJey8/MOyfOA== + dependencies: + ws "^8.8.1" -source-map-loader@^3.0.0: - version "3.0.2" - resolved "https://registry.npmjs.org/source-map-loader/-/source-map-loader-3.0.2.tgz#af23192f9b344daa729f6772933194cc5fa54fee" - integrity sha512-BokxPoLjyl3iOrgkWaakaxqnelAJSS+0V+De0kKIq6lyWrXuiPgYTGp6z3iHmqljKAaLXwZa+ctD8GccRJeVvg== +smoldot@2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/smoldot/-/smoldot-2.0.6.tgz#9e24cb4ab2a308c639cf4db1e95daf7066f319eb" + integrity sha512-/FQ6urmnG1TQuIu15NB9lXeNS0MX5f57vkX4RoBuVA+gAq4bcF1k2nMb40tfKCBW9PKyfEf478DN1YUMVOlWjg== dependencies: - abab "^2.0.5" - iconv-lite "^0.6.3" - source-map-js "^1.0.1" + ws "^8.8.1" -source-map-support@^0.5.6, source-map-support@~0.5.20: - version "0.5.21" - resolved "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" - integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== +snake-case@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/snake-case/-/snake-case-3.0.4.tgz#4f2bbd568e9935abdfd593f34c691dadb49c452c" + integrity sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg== dependencies: - buffer-from "^1.0.0" - source-map "^0.6.0" + dot-case "^3.0.4" + tslib "^2.0.3" -source-map@0.6.1, source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.0, source-map@~0.6.1: - version "0.6.1" - resolved "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" - integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== +"source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" + integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== -source-map@^0.7.3: +source-map@^0.7.4: version "0.7.4" - resolved "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz#a9bbe705c9d8846f4e08ff6765acf0f1b0898656" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.4.tgz#a9bbe705c9d8846f4e08ff6765acf0f1b0898656" integrity sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA== -source-map@^0.8.0-beta.0: - version "0.8.0-beta.0" - resolved "https://registry.npmjs.org/source-map/-/source-map-0.8.0-beta.0.tgz#d4c1bb42c3f7ee925f005927ba10709e0d1d1f11" - integrity sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA== - dependencies: - whatwg-url "^7.0.0" - -sourcemap-codec@^1.4.8: - version "1.4.8" - resolved "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4" - integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA== - -spdy-transport@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz#00d4863a6400ad75df93361a1608605e5dcdcf31" - integrity sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw== - dependencies: - debug "^4.1.0" - detect-node "^2.0.4" - hpack.js "^2.1.6" - obuf "^1.1.2" - readable-stream "^3.0.6" - wbuf "^1.7.3" - -spdy@^4.0.2: - version "4.0.2" - resolved "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz#b74f466203a3eda452c02492b91fb9e84a27677b" - integrity sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA== - dependencies: - debug "^4.1.0" - handle-thing "^2.0.0" - http-deceiver "^1.2.7" - select-hose "^2.0.0" - spdy-transport "^3.0.0" - -sprintf-js@~1.0.2: - version "1.0.3" - resolved "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" - integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== - -stable@^0.1.8: - version "0.1.8" - resolved "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf" - integrity sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w== - -stack-utils@^2.0.3: - version "2.0.6" - resolved "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz#aaf0748169c02fc33c8232abccf933f54a1cc34f" - integrity sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ== - dependencies: - escape-string-regexp "^2.0.0" - -stackframe@^1.3.4: - version "1.3.4" - resolved "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz#b881a004c8c149a5e8efef37d51b16e412943310" - integrity sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw== - -statuses@2.0.1: - version "2.0.1" - resolved "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" - integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== - -"statuses@>= 1.4.0 < 2": - version "1.5.0" - resolved "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" - integrity sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA== - -store@^2.0.12: - version "2.0.12" - resolved "https://registry.npmjs.org/store/-/store-2.0.12.tgz#8c534e2a0b831f72b75fc5f1119857c44ef5d593" - integrity sha512-eO9xlzDpXLiMr9W1nQ3Nfp9EzZieIQc10zPPMP5jsVV7bLOziSFFBP0XoDXACEIFtdI+rIz0NwWVA/QVJ8zJtw== - -string-length@^4.0.1: - version "4.0.2" - resolved "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a" - integrity sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ== - dependencies: - char-regex "^1.0.2" - strip-ansi "^6.0.0" - -string-length@^5.0.1: - version "5.0.1" - resolved "https://registry.npmjs.org/string-length/-/string-length-5.0.1.tgz#3d647f497b6e8e8d41e422f7e0b23bc536c8381e" - integrity sha512-9Ep08KAMUn0OadnVaBuRdE2l615CQ508kr0XMadjClfYpdCyvrbFp6Taebo8yyxokQ4viUd/xPPUA4FGgUa0ow== - dependencies: - char-regex "^2.0.0" - strip-ansi "^7.0.1" +stackback@0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/stackback/-/stackback-0.0.2.tgz#1ac8a0d9483848d1695e418b6d031a3c3ce68e3b" + integrity sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw== -string-natural-compare@^3.0.1: - version "3.0.1" - resolved "https://registry.npmjs.org/string-natural-compare/-/string-natural-compare-3.0.1.tgz#7a42d58474454963759e8e8b7ae63d71c1e7fdf4" - integrity sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw== +std-env@^3.3.3: + version "3.4.3" + resolved "https://registry.yarnpkg.com/std-env/-/std-env-3.4.3.tgz#326f11db518db751c83fd58574f449b7c3060910" + integrity sha512-f9aPhy8fYBuMN+sNfakZV18U39PbalgjXG3lLB9WkaYTxijru61wb57V9wxxNthXM5Sd88ETBWi29qLAsHO52Q== -string-width@^4.1.0, string-width@^4.2.0: +string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" - resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== dependencies: emoji-regex "^8.0.0" is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.1" -string.prototype.matchall@^4.0.6, string.prototype.matchall@^4.0.8: - version "4.0.8" - resolved "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.8.tgz#3bf85722021816dcd1bf38bb714915887ca79fd3" - integrity sha512-6zOCOcJ+RJAQshcTvXPHoxoQGONa3e/Lqx90wUA+wEzX78sg5Bo+1tQo4N0pohS0erG9qtCqJDjNCQBjeWVxyg== +string.prototype.matchall@^4.0.8: + version "4.0.10" + resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-4.0.10.tgz#a1553eb532221d4180c51581d6072cd65d1ee100" + integrity sha512-rGXbGmOEosIQi6Qva94HUjgPs9vKW+dkG7Y8Q5O2OYkWL6wFaTRZO8zM4mhP94uX55wgyrXzfS2aGtGzUL7EJQ== dependencies: call-bind "^1.0.2" - define-properties "^1.1.4" - es-abstract "^1.20.4" - get-intrinsic "^1.1.3" + define-properties "^1.2.0" + es-abstract "^1.22.1" + get-intrinsic "^1.2.1" has-symbols "^1.0.3" - internal-slot "^1.0.3" - regexp.prototype.flags "^1.4.3" + internal-slot "^1.0.5" + regexp.prototype.flags "^1.5.0" + set-function-name "^2.0.0" side-channel "^1.0.4" -string.prototype.trimend@^1.0.5: - version "1.0.6" - resolved "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz#c4a27fa026d979d79c04f17397f250a462944533" - integrity sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ== +string.prototype.trim@^1.2.8: + version "1.2.8" + resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.8.tgz#f9ac6f8af4bd55ddfa8895e6aea92a96395393bd" + integrity sha512-lfjY4HcixfQXOfaqCvcBuOIapyaroTXhbkfJN3gcB1OtyupngWK4sEET9Knd0cXd28kTUqu/kHoV4HKSJdnjiQ== dependencies: call-bind "^1.0.2" - define-properties "^1.1.4" - es-abstract "^1.20.4" + define-properties "^1.2.0" + es-abstract "^1.22.1" -string.prototype.trimstart@^1.0.5: - version "1.0.6" - resolved "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz#e90ab66aa8e4007d92ef591bbf3cd422c56bdcf4" - integrity sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA== +string.prototype.trimend@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.7.tgz#1bb3afc5008661d73e2dc015cd4853732d6c471e" + integrity sha512-Ni79DqeB72ZFq1uH/L6zJ+DKZTkOtPIHovb3YZHQViE+HDouuU4mBrLOLDn5Dde3RF8qw5qVETEjhu9locMLvA== dependencies: call-bind "^1.0.2" - define-properties "^1.1.4" - es-abstract "^1.20.4" + define-properties "^1.2.0" + es-abstract "^1.22.1" + +string.prototype.trimstart@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.7.tgz#d4cdb44b83a4737ffbac2d406e405d43d0184298" + integrity sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" string_decoder@^1.1.1: version "1.3.0" - resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== dependencies: safe-buffer "~5.2.0" -string_decoder@~1.1.1: - version "1.1.1" - resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" - integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== - dependencies: - safe-buffer "~5.1.0" - -stringify-object@^3.3.0: - version "3.3.0" - resolved "https://registry.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz#703065aefca19300d3ce88af4f5b3956d7556629" - integrity sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw== - dependencies: - get-own-enumerable-property-symbols "^3.0.0" - is-obj "^1.0.1" - is-regexp "^1.0.0" - strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" - resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== dependencies: ansi-regex "^5.0.1" -strip-ansi@^7.0.1: - version "7.0.1" - resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz#61740a08ce36b61e50e65653f07060d000975fb2" - integrity sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw== - dependencies: - ansi-regex "^6.0.1" - strip-bom@^3.0.0: version "3.0.0" - resolved "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" integrity sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA== -strip-bom@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" - integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== - -strip-comments@^2.0.1: - version "2.0.1" - resolved "https://registry.npmjs.org/strip-comments/-/strip-comments-2.0.1.tgz#4ad11c3fbcac177a67a40ac224ca339ca1c1ba9b" - integrity sha512-ZprKx+bBLXv067WTCALv8SSz5l2+XhpYCsVtSqlMnkAXMWDq+/ekVbl1ghqP9rUHTzv6sm/DwCOiYutU/yp1fw== - strip-final-newline@^2.0.0: version "2.0.0" - resolved "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" + resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== -strip-indent@^3.0.0: +strip-final-newline@^3.0.0: version "3.0.0" - resolved "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz#c32e1cee940b6b3432c771bc2c54bcce73cd3001" - integrity sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ== - dependencies: - min-indent "^1.0.0" + resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-3.0.0.tgz#52894c313fbff318835280aed60ff71ebf12b8fd" + integrity sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw== -strip-json-comments@^3.1.0, strip-json-comments@^3.1.1: +strip-json-comments@^3.1.1: version "3.1.1" - resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== +strip-literal@^1.0.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/strip-literal/-/strip-literal-1.3.0.tgz#db3942c2ec1699e6836ad230090b84bb458e3a07" + integrity sha512-PugKzOsyXpArk0yWmUwqOZecSO0GH0bPoctLcqNDH9J04pVW3lflYE0ujElBGTloevcxF5MofAOZ7C5l2b+wLg== + dependencies: + acorn "^8.10.0" + strip-outer@^1.0.1: version "1.0.1" - resolved "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.1.tgz#b2fd2abf6604b9d1e6013057195df836b8a9d631" + resolved "https://registry.yarnpkg.com/strip-outer/-/strip-outer-1.0.1.tgz#b2fd2abf6604b9d1e6013057195df836b8a9d631" integrity sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg== dependencies: escape-string-regexp "^1.0.2" -style-loader@^3.3.1: - version "3.3.1" - resolved "https://registry.npmjs.org/style-loader/-/style-loader-3.3.1.tgz#057dfa6b3d4d7c7064462830f9113ed417d38575" - integrity sha512-GPcQ+LDJbrcxHORTRes6Jy2sfvK2kS6hpSfI/fXhPt+spVzxF6LJ1dHLN9zIGmVaaP044YKaIatFaufENRiDoQ== - -style-value-types@5.1.2: - version "5.1.2" - resolved "https://registry.npmjs.org/style-value-types/-/style-value-types-5.1.2.tgz#6be66b237bd546048a764883528072ed95713b62" - integrity sha512-Vs9fNreYF9j6W2VvuDTP7kepALi7sk0xtk2Tu8Yxi9UoajJdEVpNpCov0HsLTqXvNGKX+Uv09pkozVITi1jf3Q== - dependencies: - hey-listen "^1.0.8" - tslib "2.4.0" - -styled-components@^5.3.3, styled-components@^5.3.6: - version "5.3.6" - resolved "https://registry.npmjs.org/styled-components/-/styled-components-5.3.6.tgz#27753c8c27c650bee9358e343fc927966bfd00d1" - integrity sha512-hGTZquGAaTqhGWldX7hhfzjnIYBZ0IXQXkCYdvF1Sq3DsUaLx6+NTHC5Jj1ooM2F68sBiVz3lvhfwQs/S3l6qg== - dependencies: - "@babel/helper-module-imports" "^7.0.0" - "@babel/traverse" "^7.4.5" - "@emotion/is-prop-valid" "^1.1.0" - "@emotion/stylis" "^0.8.4" - "@emotion/unitless" "^0.7.4" - babel-plugin-styled-components ">= 1.12.0" - css-to-react-native "^3.0.0" - hoist-non-react-statics "^3.0.0" +styled-components@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/styled-components/-/styled-components-6.1.0.tgz#228e3ab9c1ee1daa4b0a06aae30df0ed14fda274" + integrity sha512-VWNfYYBuXzuLS/QYEeoPgMErP26WL+dX9//rEh80B2mmlS1yRxRxuL5eax4m6ybYEUoHWlTy2XOU32767mlMkg== + dependencies: + "@emotion/is-prop-valid" "^1.2.1" + "@emotion/unitless" "^0.8.0" + "@types/stylis" "^4.0.2" + css-to-react-native "^3.2.0" + csstype "^3.1.2" + postcss "^8.4.31" shallowequal "^1.1.0" - supports-color "^5.5.0" - -styled-theming@^2.2.0: - version "2.2.0" - resolved "https://registry.npmjs.org/styled-theming/-/styled-theming-2.2.0.tgz#3084e43d40eaab4bc11ebafd3de04e3622fee37e" - integrity sha512-5nqUoCR/BJ6ziJ39xN/K5nmrzMn37BgPc693vgeQlO0hMaat/hJvJ0/u0vtm1ZQxU82sdltEbynqjkoYYMVwaA== + stylis "^4.3.0" + tslib "^2.5.0" -stylehacks@^5.1.1: - version "5.1.1" - resolved "https://registry.npmjs.org/stylehacks/-/stylehacks-5.1.1.tgz#7934a34eb59d7152149fa69d6e9e56f2fc34bcc9" - integrity sha512-sBpcd5Hx7G6seo7b1LkpttvTz7ikD0LlH5RmdcBNb6fFR0Fl7LQwHDFr300q4cwUqi+IYrFGmsIHieMBfnN/Bw== - dependencies: - browserslist "^4.21.4" - postcss-selector-parser "^6.0.4" +stylis@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.3.0.tgz#abe305a669fc3d8777e10eefcfc73ad861c5588c" + integrity sha512-E87pIogpwUsUwXw7dNyU4QDjdgVMy52m+XEOPEKUn161cCzWjjhPSQhByfd1CcNvrOLnXQ6OnnZDwnJrz/Z4YQ== subscriptions-transport-ws@^0.11.0: version "0.11.0" @@ -9789,77 +4780,30 @@ subscriptions-transport-ws@^0.11.0: symbol-observable "^1.0.4" ws "^5.2.0 || ^6.0.0 || ^7.0.0" -supports-color@^5.3.0, supports-color@^5.5.0: +supports-color@^5.3.0: version "5.5.0" - resolved "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== dependencies: has-flag "^3.0.0" -supports-color@^7.0.0, supports-color@^7.1.0: +supports-color@^7.1.0: version "7.2.0" - resolved "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== dependencies: has-flag "^4.0.0" -supports-color@^8.0.0: - version "8.1.1" - resolved "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" - integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== - dependencies: - has-flag "^4.0.0" - -supports-hyperlinks@^2.0.0: - version "2.3.0" - resolved "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz#3943544347c1ff90b15effb03fc14ae45ec10624" - integrity sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA== - dependencies: - has-flag "^4.0.0" - supports-color "^7.0.0" - supports-preserve-symlinks-flag@^1.0.0: version "1.0.0" - resolved "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" + resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== -svg-parser@^2.0.2: +svg-parser@^2.0.4: version "2.0.4" - resolved "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz#fdc2e29e13951736140b76cb122c8ee6630eb6b5" + resolved "https://registry.yarnpkg.com/svg-parser/-/svg-parser-2.0.4.tgz#fdc2e29e13951736140b76cb122c8ee6630eb6b5" integrity sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ== -svgo@^1.2.2: - version "1.3.2" - resolved "https://registry.npmjs.org/svgo/-/svgo-1.3.2.tgz#b6dc511c063346c9e415b81e43401145b96d4167" - integrity sha512-yhy/sQYxR5BkC98CY7o31VGsg014AKLEPxdfhora76l36hD9Rdy5NZA/Ocn6yayNPgSamYdtX2rFJdcv07AYVw== - dependencies: - chalk "^2.4.1" - coa "^2.0.2" - css-select "^2.0.0" - css-select-base-adapter "^0.1.1" - css-tree "1.0.0-alpha.37" - csso "^4.0.2" - js-yaml "^3.13.1" - mkdirp "~0.5.1" - object.values "^1.1.0" - sax "~1.2.4" - stable "^0.1.8" - unquote "~1.1.1" - util.promisify "~1.0.0" - -svgo@^2.7.0: - version "2.8.0" - resolved "https://registry.npmjs.org/svgo/-/svgo-2.8.0.tgz#4ff80cce6710dc2795f0c7c74101e6764cfccd24" - integrity sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg== - dependencies: - "@trysound/sax" "0.2.0" - commander "^7.2.0" - css-select "^4.1.3" - css-tree "^1.1.3" - csso "^4.2.0" - picocolors "^1.0.0" - stable "^0.1.8" - symbol-observable@^1.0.4: version "1.2.0" resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804" @@ -9868,198 +4812,74 @@ symbol-observable@^1.0.4: symbol-observable@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-4.0.0.tgz#5b425f192279e87f2f9b937ac8540d1984b39205" - integrity sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ== - -symbol-tree@^3.2.4: - version "3.2.4" - resolved "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" - integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw== + integrity sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ== -synckit@^0.8.4: - version "0.8.4" - resolved "https://registry.npmjs.org/synckit/-/synckit-0.8.4.tgz#0e6b392b73fafdafcde56692e3352500261d64ec" - integrity sha512-Dn2ZkzMdSX827QbowGbU/4yjWuvNaCoScLLoMo/yKbu+P4GBR6cRGKZH27k6a9bRzdqcyd1DE96pQtQ6uNkmyw== +synckit@^0.8.5: + version "0.8.5" + resolved "https://registry.yarnpkg.com/synckit/-/synckit-0.8.5.tgz#b7f4358f9bb559437f9f167eb6bc46b3c9818fa3" + integrity sha512-L1dapNV6vu2s/4Sputv8xGsCdAVlb5nRDMFU/E27D44l5U6cw1g0dGd45uLc+OXjNMmF4ntiMdCimzcjFKQI8Q== dependencies: "@pkgr/utils" "^2.3.1" - tslib "^2.4.0" - -tailwindcss@^3.0.2: - version "3.2.4" - resolved "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.2.4.tgz#afe3477e7a19f3ceafb48e4b083e292ce0dc0250" - integrity sha512-AhwtHCKMtR71JgeYDaswmZXhPcW9iuI9Sp2LvZPo9upDZ7231ZJ7eA9RaURbhpXGVlrjX4cFNlB4ieTetEb7hQ== - dependencies: - arg "^5.0.2" - chokidar "^3.5.3" - color-name "^1.1.4" - detective "^5.2.1" - didyoumean "^1.2.2" - dlv "^1.1.3" - fast-glob "^3.2.12" - glob-parent "^6.0.2" - is-glob "^4.0.3" - lilconfig "^2.0.6" - micromatch "^4.0.5" - normalize-path "^3.0.0" - object-hash "^3.0.0" - picocolors "^1.0.0" - postcss "^8.4.18" - postcss-import "^14.1.0" - postcss-js "^4.0.0" - postcss-load-config "^3.1.4" - postcss-nested "6.0.0" - postcss-selector-parser "^6.0.10" - postcss-value-parser "^4.2.0" - quick-lru "^5.1.1" - resolve "^1.22.1" - -tapable@^1.0.0: - version "1.1.3" - resolved "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2" - integrity sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA== + tslib "^2.5.0" -tapable@^2.0.0, tapable@^2.1.1, tapable@^2.2.0: +tapable@^2.2.0: version "2.2.1" - resolved "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0" + resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0" integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ== -temp-dir@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/temp-dir/-/temp-dir-2.0.0.tgz#bde92b05bdfeb1516e804c9c00ad45177f31321e" - integrity sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg== - -tempy@^0.6.0: - version "0.6.0" - resolved "https://registry.npmjs.org/tempy/-/tempy-0.6.0.tgz#65e2c35abc06f1124a97f387b08303442bde59f3" - integrity sha512-G13vtMYPT/J8A4X2SjdtBTphZlrp1gKv6hZiOjw14RCWg6GbHuQBGtjlx75xLbYV/wEc0D7G5K4rxKP/cXk8Bw== - dependencies: - is-stream "^2.0.0" - temp-dir "^2.0.0" - type-fest "^0.16.0" - unique-string "^2.0.0" - -terminal-link@^2.0.0: - version "2.1.1" - resolved "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz#14a64a27ab3c0df933ea546fba55f2d078edc994" - integrity sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ== - dependencies: - ansi-escapes "^4.2.1" - supports-hyperlinks "^2.0.0" - -terser-webpack-plugin@^5.1.3, terser-webpack-plugin@^5.2.5, terser-webpack-plugin@^5.3.3: - version "5.3.6" - resolved "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.6.tgz#5590aec31aa3c6f771ce1b1acca60639eab3195c" - integrity sha512-kfLFk+PoLUQIbLmB1+PZDMRSZS99Mp+/MHqDNmMA6tOItzRt+Npe3E+fsMs5mfcM0wCtrrdU387UnV+vnSffXQ== - dependencies: - "@jridgewell/trace-mapping" "^0.3.14" - jest-worker "^27.4.5" - schema-utils "^3.1.1" - serialize-javascript "^6.0.0" - terser "^5.14.1" - -terser@^5.0.0, terser@^5.10.0, terser@^5.14.1: - version "5.16.0" - resolved "https://registry.npmjs.org/terser/-/terser-5.16.0.tgz#29362c6f5506e71545c73b069ccd199bb28f7f54" - integrity sha512-KjTV81QKStSfwbNiwlBXfcgMcOloyuRdb62/iLFPGBcVNF4EXjhdYBhYHmbJpiBrVxZhDvltE11j+LBQUxEEJg== - dependencies: - "@jridgewell/source-map" "^0.3.2" - acorn "^8.5.0" - commander "^2.20.0" - source-map-support "~0.5.20" - -test-exclude@^6.0.0: - version "6.0.0" - resolved "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e" - integrity sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w== - dependencies: - "@istanbuljs/schema" "^0.1.2" - glob "^7.1.4" - minimatch "^3.0.4" - text-table@^0.2.0: version "0.2.0" - resolved "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" + resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== -throat@^6.0.1: - version "6.0.1" - resolved "https://registry.npmjs.org/throat/-/throat-6.0.1.tgz#d514fedad95740c12c2d7fc70ea863eb51ade375" - integrity sha512-8hmiGIJMDlwjg7dlJ4yKGLK8EsYqKgPWbG3b4wjJddKNwc7N7Dpn08Df4szr/sZdMVeOstrdYSsqzX6BYbcB+w== +tiny-invariant@^1.1.0: + version "1.3.1" + resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.3.1.tgz#8560808c916ef02ecfd55e66090df23a4b7aa642" + integrity sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw== -thunky@^1.0.2: - version "1.1.0" - resolved "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz#5abaf714a9405db0504732bbccd2cedd9ef9537d" - integrity sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA== +tinybench@^2.5.0: + version "2.5.1" + resolved "https://registry.yarnpkg.com/tinybench/-/tinybench-2.5.1.tgz#3408f6552125e53a5a48adee31261686fd71587e" + integrity sha512-65NKvSuAVDP/n4CqH+a9w2kTlLReS9vhsAP06MWx+/89nMinJyB2icyl58RIcqCmIggpojIGeuJGhjU1aGMBSg== -tiny-glob@^0.2.9: - version "0.2.9" - resolved "https://registry.npmjs.org/tiny-glob/-/tiny-glob-0.2.9.tgz#2212d441ac17928033b110f8b3640683129d31e2" - integrity sha512-g/55ssRPUjShh+xkfx9UPDXqhckHEsHr4Vd9zX55oSdGZc/MD0m3sferOkwWtp98bv+kcVfEHtRJgBVJzelrzg== - dependencies: - globalyzer "0.1.0" - globrex "^0.1.2" +tinypool@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/tinypool/-/tinypool-0.7.0.tgz#88053cc99b4a594382af23190c609d93fddf8021" + integrity sha512-zSYNUlYSMhJ6Zdou4cJwo/p7w5nmAH17GRfU/ui3ctvjXFErXXkruT4MWW6poDeXgCaIBlGLrfU6TbTXxyGMww== -tmpl@1.0.5: - version "1.0.5" - resolved "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" - integrity sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw== +tinyspy@^2.1.1: + version "2.2.0" + resolved "https://registry.yarnpkg.com/tinyspy/-/tinyspy-2.2.0.tgz#9dc04b072746520b432f77ea2c2d17933de5d6ce" + integrity sha512-d2eda04AN/cPOR89F7Xv5bK/jrQEhmcLFe6HFldoeO9AJtps+fqEnh486vnT/8y4bw38pSyxDcTCAq+Ks2aJTg== + +titleize@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/titleize/-/titleize-3.0.0.tgz#71c12eb7fdd2558aa8a44b0be83b8a76694acd53" + integrity sha512-KxVu8EYHDPBdUYdKZdKtU2aj2XfEx9AfjXxE/Aj0vT06w2icA09Vus1rh6eSu1y01akYg6BjIK/hxyLJINoMLQ== to-fast-properties@^2.0.0: version "2.0.0" - resolved "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" + resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" integrity sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog== to-regex-range@^5.0.1: version "5.0.1" - resolved "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== dependencies: is-number "^7.0.0" -toggle-selection@^1.0.6: - version "1.0.6" - resolved "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz#6e45b1263f2017fa0acc7d89d78b15b8bf77da32" - integrity sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ== - -toidentifier@1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" - integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== - -tough-cookie@^4.0.0: - version "4.1.2" - resolved "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.2.tgz#e53e84b85f24e0b65dd526f46628db6c85f6b874" - integrity sha512-G9fqXWoYFZgTc2z8Q5zaHy/vJMjm+WV0AkAeHxVCQiEB1b+dGvWzFW6QV07cY5jQ5gRkeid2qIkzkxUnmoQZUQ== - dependencies: - psl "^1.1.33" - punycode "^2.1.1" - universalify "^0.2.0" - url-parse "^1.5.3" - -tr46@^1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz#a8b13fd6bfd2489519674ccde55ba3693b706d09" - integrity sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA== - dependencies: - punycode "^2.1.0" - -tr46@^2.1.0: - version "2.1.0" - resolved "https://registry.npmjs.org/tr46/-/tr46-2.1.0.tgz#fa87aa81ca5d5941da8cbf1f9b749dc969a4e240" - integrity sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw== - dependencies: - punycode "^2.1.1" - trim-repeated@^1.0.0: version "1.0.0" - resolved "https://registry.npmjs.org/trim-repeated/-/trim-repeated-1.0.0.tgz#e3646a2ea4e891312bf7eace6cfb05380bc01c21" + resolved "https://registry.yarnpkg.com/trim-repeated/-/trim-repeated-1.0.0.tgz#e3646a2ea4e891312bf7eace6cfb05380bc01c21" integrity sha512-pkonvlKk8/ZuR0D5tLW8ljt5I8kmxp2XKymhepUeOdCEfKpZaktSArkLHZt76OB1ZvO9bssUsDty4SWhLvZpLg== dependencies: escape-string-regexp "^1.0.2" -tryer@^1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/tryer/-/tryer-1.0.1.tgz#f2c85406800b9b0f74c9f7465b81eaad241252f8" - integrity sha512-c3zayb8/kWWpycWYg87P71E1S1ZL6b6IJxfb5fvsUgsf0S2MVGaDhDXXjDMpdCpfWXqptc+4mXwmiy1ypXqRAA== +ts-api-utils@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.0.3.tgz#f12c1c781d04427313dbac808f453f050e54a331" + integrity sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg== ts-invariant@^0.10.3: version "0.10.3" @@ -10068,110 +4888,110 @@ ts-invariant@^0.10.3: dependencies: tslib "^2.1.0" -tsconfig-paths@^3.14.1: - version "3.14.1" - resolved "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz#ba0734599e8ea36c862798e920bcf163277b137a" - integrity sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ== +tsconfck@^2.1.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/tsconfck/-/tsconfck-2.1.2.tgz#f667035874fa41d908c1fe4d765345fcb1df6e35" + integrity sha512-ghqN1b0puy3MhhviwO2kGF8SeMDNhEbnKxjK7h6+fvY9JAxqvXi8y5NAHSQv687OVboS2uZIByzGd45/YxrRHg== + +tsconfig-paths@^3.14.2: + version "3.14.2" + resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz#6e32f1f79412decd261f92d633a9dc1cfa99f088" + integrity sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g== dependencies: "@types/json5" "^0.0.29" - json5 "^1.0.1" + json5 "^1.0.2" minimist "^1.2.6" strip-bom "^3.0.0" -tslib@2.4.0: - version "2.4.0" - resolved "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3" - integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ== - -tslib@^1.8.1: +tslib@^1.9.0: version "1.14.1" - resolved "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== -tslib@^2.0.3, tslib@^2.1.0, tslib@^2.3.0, tslib@^2.3.1, tslib@^2.4.0: - version "2.4.1" - resolved "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz#0d0bfbaac2880b91e22df0768e55be9753a5b17e" - integrity sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA== - -tsutils@^3.21.0: - version "3.21.0" - resolved "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" - integrity sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA== - dependencies: - tslib "^1.8.1" - -tweetnacl@1.x.x, tweetnacl@^1.0.3: - version "1.0.3" - resolved "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz#ac0af71680458d8a6378d0d0d050ab1407d35596" - integrity sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw== +tslib@^2.0.3, tslib@^2.1.0, tslib@^2.3.0, tslib@^2.4.0, tslib@^2.5.0, tslib@^2.5.3, tslib@^2.6.0, tslib@^2.6.1, tslib@^2.6.2: + version "2.6.2" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" + integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== type-check@^0.4.0, type-check@~0.4.0: version "0.4.0" - resolved "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== dependencies: prelude-ls "^1.2.1" -type-check@~0.3.2: - version "0.3.2" - resolved "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" - integrity sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg== - dependencies: - prelude-ls "~1.1.2" - -type-detect@4.0.8: +type-detect@^4.0.0, type-detect@^4.0.8: version "4.0.8" - resolved "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" + resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== -type-fest@^0.16.0: - version "0.16.0" - resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.16.0.tgz#3240b891a78b0deae910dbeb86553e552a148860" - integrity sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg== - type-fest@^0.20.2: version "0.20.2" - resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== type-fest@^0.21.3: version "0.21.3" - resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== -type-is@~1.6.18: - version "1.6.18" - resolved "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" - integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== +typed-array-buffer@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/typed-array-buffer/-/typed-array-buffer-1.0.0.tgz#18de3e7ed7974b0a729d3feecb94338d1472cd60" + integrity sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw== dependencies: - media-typer "0.3.0" - mime-types "~2.1.24" + call-bind "^1.0.2" + get-intrinsic "^1.2.1" + is-typed-array "^1.1.10" -type@^1.0.1: - version "1.2.0" - resolved "https://registry.npmjs.org/type/-/type-1.2.0.tgz#848dd7698dafa3e54a6c479e759c4bc3f18847a0" - integrity sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg== +typed-array-byte-length@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/typed-array-byte-length/-/typed-array-byte-length-1.0.0.tgz#d787a24a995711611fb2b87a4052799517b230d0" + integrity sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA== + dependencies: + call-bind "^1.0.2" + for-each "^0.3.3" + has-proto "^1.0.1" + is-typed-array "^1.1.10" -type@^2.7.2: - version "2.7.2" - resolved "https://registry.npmjs.org/type/-/type-2.7.2.tgz#2376a15a3a28b1efa0f5350dcf72d24df6ef98d0" - integrity sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw== +typed-array-byte-offset@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/typed-array-byte-offset/-/typed-array-byte-offset-1.0.0.tgz#cbbe89b51fdef9cd6aaf07ad4707340abbc4ea0b" + integrity sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg== + dependencies: + available-typed-arrays "^1.0.5" + call-bind "^1.0.2" + for-each "^0.3.3" + has-proto "^1.0.1" + is-typed-array "^1.1.10" -typedarray-to-buffer@^3.1.5: - version "3.1.5" - resolved "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" - integrity sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q== +typed-array-length@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/typed-array-length/-/typed-array-length-1.0.4.tgz#89d83785e5c4098bec72e08b319651f0eac9c1bb" + integrity sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng== dependencies: - is-typedarray "^1.0.0" + call-bind "^1.0.2" + for-each "^0.3.3" + is-typed-array "^1.1.9" + +typeforce@^1.11.5: + version "1.18.0" + resolved "https://registry.yarnpkg.com/typeforce/-/typeforce-1.18.0.tgz#d7416a2c5845e085034d70fcc5b6cc4a90edbfdc" + integrity sha512-7uc1O8h1M1g0rArakJdf0uLRSSgFcYexrVoKo+bzJd32gd4gDy2L/Z+8/FjPnU9ydY3pEnVPtr9FyscYY60K1g== + +typescript@^5.2.2: + version "5.2.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.2.2.tgz#5ebb5e5a5b75f085f22bc3f8460fba308310fa78" + integrity sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w== -typescript@^4.9.3: - version "4.9.3" - resolved "https://registry.npmjs.org/typescript/-/typescript-4.9.3.tgz#3aea307c1746b8c384435d8ac36b8a2e580d85db" - integrity sha512-CIfGzTelbKNEnLpLdGFgdyKhG23CKdKgQPOBc+OUNrkJ2vr+KSzsSV5kq5iWhEQbok+quxgGzrAtGWCyU7tHnA== +ufo@^1.3.0: + version "1.3.1" + resolved "https://registry.yarnpkg.com/ufo/-/ufo-1.3.1.tgz#e085842f4627c41d4c1b60ebea1f75cdab4ce86b" + integrity sha512-uY/99gMLIOlJPwATcMVYfqDSxUR9//AUcgZMzwfSTJPDKzA1S8mX4VLqa+fiAtveraQUBCz4FFcwVZBGbwBXIw== unbox-primitive@^1.0.2: version "1.0.2" - resolved "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz#29032021057d5e6cdbd08c5129c226dff8ed6f9e" + resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.2.tgz#29032021057d5e6cdbd08c5129c226dff8ed6f9e" integrity sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw== dependencies: call-bind "^1.0.2" @@ -10179,375 +4999,217 @@ unbox-primitive@^1.0.2: has-symbols "^1.0.3" which-boxed-primitive "^1.0.2" -unicode-canonical-property-names-ecmascript@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz#301acdc525631670d39f6146e0e77ff6bbdebddc" - integrity sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ== - -unicode-match-property-ecmascript@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz#54fd16e0ecb167cf04cf1f756bdcc92eba7976c3" - integrity sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q== - dependencies: - unicode-canonical-property-names-ecmascript "^2.0.0" - unicode-property-aliases-ecmascript "^2.0.0" - -unicode-match-property-value-ecmascript@^2.1.0: - version "2.1.0" - resolved "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz#cb5fffdcd16a05124f5a4b0bf7c3770208acbbe0" - integrity sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA== - -unicode-property-aliases-ecmascript@^2.0.0: - version "2.1.0" - resolved "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz#43d41e3be698bd493ef911077c9b131f827e8ccd" - integrity sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w== - -unique-string@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz#39c6451f81afb2749de2b233e3f7c5e8843bd89d" - integrity sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg== - dependencies: - crypto-random-string "^2.0.0" - -universalify@^0.1.0: - version "0.1.2" - resolved "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" - integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== - -universalify@^0.2.0: - version "0.2.0" - resolved "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz#6451760566fa857534745ab1dde952d1b1761be0" - integrity sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg== +undici-types@~5.26.4: + version "5.26.5" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" + integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== universalify@^2.0.0: version "2.0.0" - resolved "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717" integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ== -unpipe@1.0.0, unpipe@~1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" - integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== - -unquote@~1.1.1: - version "1.1.1" - resolved "https://registry.npmjs.org/unquote/-/unquote-1.1.1.tgz#8fded7324ec6e88a0ff8b905e7c098cdc086d544" - integrity sha512-vRCqFv6UhXpWxZPyGDh/F3ZpNv8/qo7w6iufLpQg9aKnQ71qM4B5KiI7Mia9COcjEhrO9LueHpMYjYzsWH3OIg== - -upath@^1.2.0: - version "1.2.0" - resolved "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz#8f66dbcd55a883acdae4408af8b035a5044c1894" - integrity sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg== +untildify@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/untildify/-/untildify-4.0.0.tgz#2bc947b953652487e4600949fb091e3ae8cd919b" + integrity sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw== -update-browserslist-db@^1.0.9: - version "1.0.10" - resolved "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz#0f54b876545726f17d00cd9a2561e6dade943ff3" - integrity sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ== +update-browserslist-db@^1.0.13: + version "1.0.13" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz#3c5e4f5c083661bd38ef64b6328c26ed6c8248c4" + integrity sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg== dependencies: escalade "^3.1.1" picocolors "^1.0.0" uri-js@^4.2.2: version "4.4.1" - resolved "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== dependencies: punycode "^2.1.0" -url-parse@^1.5.3: - version "1.5.10" - resolved "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz#9d3c2f736c1d75dd3bd2be507dcc111f1e2ea9c1" - integrity sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ== - dependencies: - querystringify "^2.1.1" - requires-port "^1.0.0" - -utf-8-validate@^5.0.2: - version "5.0.10" - resolved "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz#d7d10ea39318171ca982718b6b96a8d2442571a2" - integrity sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ== - dependencies: - node-gyp-build "^4.3.0" - -util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1: +util-deprecate@^1.0.1: version "1.0.2" - resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== -util.promisify@~1.0.0: - version "1.0.1" - resolved "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.1.tgz#6baf7774b80eeb0f7520d8b81d07982a59abbaee" - integrity sha512-g9JpC/3He3bm38zsLupWryXHoEcS22YHthuPQSJdMy6KNrzIRzWqcsHzD/WUnqe45whVou4VIsPew37DoXWNrA== +vite-bundle-visualizer@^0.10.0: + version "0.10.0" + resolved "https://registry.yarnpkg.com/vite-bundle-visualizer/-/vite-bundle-visualizer-0.10.0.tgz#bdeafe5f8e69eb4c157174ae8d852279272e3010" + integrity sha512-11AwKlkhvw6jjiGbTiCZqBSGg/FQDLc0mVcoLWVov2jU/Ban67l+Sk4Fa0Iyctb5sObqg/dA28HkKCEmSRjw9g== dependencies: - define-properties "^1.1.3" - es-abstract "^1.17.2" - has-symbols "^1.0.1" - object.getownpropertydescriptors "^2.1.0" - -utila@~0.4: - version "0.4.0" - resolved "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz#8a16a05d445657a3aea5eecc5b12a4fa5379772c" - integrity sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA== - -utils-merge@1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" - integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== - -uuid@^8.3.2: - version "8.3.2" - resolved "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" - integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== + cac "^6.7.14" + rollup-plugin-visualizer "^5.9.2" -v8-to-istanbul@^8.1.0: - version "8.1.1" - resolved "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-8.1.1.tgz#77b752fd3975e31bbcef938f85e9bd1c7a8d60ed" - integrity sha512-FGtKtv3xIpR6BYhvgH8MI/y78oT7d8Au3ww4QIxymrCtZEh5b8gCw2siywE+puhEmuWKDtmfrvF5UlB298ut3w== +vite-node@0.34.6: + version "0.34.6" + resolved "https://registry.yarnpkg.com/vite-node/-/vite-node-0.34.6.tgz#34d19795de1498562bf21541a58edcd106328a17" + integrity sha512-nlBMJ9x6n7/Amaz6F3zJ97EBwR2FkzhBRxF5e+jE6LA3yi6Wtc2lyTij1OnDMIr34v5g/tVQtsVAzhT0jc5ygA== dependencies: - "@types/istanbul-lib-coverage" "^2.0.1" - convert-source-map "^1.6.0" - source-map "^0.7.3" - -vary@~1.1.2: - version "1.1.2" - resolved "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" - integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== - -void-elements@3.1.0: - version "3.1.0" - resolved "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz#614f7fbf8d801f0bb5f0661f5b2f5785750e4f09" - integrity sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w== + cac "^6.7.14" + debug "^4.3.4" + mlly "^1.4.0" + pathe "^1.1.1" + picocolors "^1.0.0" + vite "^3.0.0 || ^4.0.0 || ^5.0.0-0" -w3c-hr-time@^1.0.2: - version "1.0.2" - resolved "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz#0a89cdf5cc15822df9c360543676963e0cc308cd" - integrity sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ== +vite-plugin-checker@^0.6.2: + version "0.6.2" + resolved "https://registry.yarnpkg.com/vite-plugin-checker/-/vite-plugin-checker-0.6.2.tgz#3790381734440033e6cb3cee9d92fcfdd69a4d71" + integrity sha512-YvvvQ+IjY09BX7Ab+1pjxkELQsBd4rPhWNw8WLBeFVxu/E7O+n6VYAqNsKdK/a2luFlX/sMpoWdGFfg4HvwdJQ== dependencies: - browser-process-hrtime "^1.0.0" + "@babel/code-frame" "^7.12.13" + ansi-escapes "^4.3.0" + chalk "^4.1.1" + chokidar "^3.5.1" + commander "^8.0.0" + fast-glob "^3.2.7" + fs-extra "^11.1.0" + lodash.debounce "^4.0.8" + lodash.pick "^4.4.0" + npm-run-path "^4.0.1" + semver "^7.5.0" + strip-ansi "^6.0.0" + tiny-invariant "^1.1.0" + vscode-languageclient "^7.0.0" + vscode-languageserver "^7.0.0" + vscode-languageserver-textdocument "^1.0.1" + vscode-uri "^3.0.2" -w3c-xmlserializer@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz#3e7104a05b75146cc60f564380b7f683acf1020a" - integrity sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA== +vite-plugin-eslint@^1.8.1: + version "1.8.1" + resolved "https://registry.yarnpkg.com/vite-plugin-eslint/-/vite-plugin-eslint-1.8.1.tgz#0381b8272e7f0fd8b663311b64f7608d55d8b04c" + integrity sha512-PqdMf3Y2fLO9FsNPmMX+//2BF5SF8nEWspZdgl4kSt7UvHDRHVVfHvxsD7ULYzZrJDGRxR81Nq7TOFgwMnUang== dependencies: - xml-name-validator "^3.0.0" + "@rollup/pluginutils" "^4.2.1" + "@types/eslint" "^8.4.5" + rollup "^2.77.2" -walker@^1.0.7: - version "1.0.8" - resolved "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz#bd498db477afe573dc04185f011d3ab8a8d7653f" - integrity sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ== +vite-plugin-svgr@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/vite-plugin-svgr/-/vite-plugin-svgr-4.1.0.tgz#f11072a873856039702bb66657379c53d3bb5d5a" + integrity sha512-v7Qic+FWmCChgQNGSI4V8X63OEYsdUoLt66iqIcHozq9bfK/Dwmr0V+LBy1NE8CE98Y8HouEBJ+pto4AMfN5xw== dependencies: - makeerror "1.0.12" + "@rollup/pluginutils" "^5.0.4" + "@svgr/core" "^8.1.0" + "@svgr/plugin-jsx" "^8.1.0" -watchpack@^2.4.0: - version "2.4.0" - resolved "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz#fa33032374962c78113f93c7f2fb4c54c9862a5d" - integrity sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg== +vite-tsconfig-paths@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/vite-tsconfig-paths/-/vite-tsconfig-paths-4.2.1.tgz#e53b89096b91d31a6d1e26f75999ea8c336a89ed" + integrity sha512-GNUI6ZgPqT3oervkvzU+qtys83+75N/OuDaQl7HmOqFTb0pjZsuARrRipsyJhJ3enqV8beI1xhGbToR4o78nSQ== dependencies: - glob-to-regexp "^0.4.1" - graceful-fs "^4.1.2" + debug "^4.1.1" + globrex "^0.1.2" + tsconfck "^2.1.0" -wbuf@^1.1.0, wbuf@^1.7.3: - version "1.7.3" - resolved "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz#c1d8d149316d3ea852848895cb6a0bfe887b87df" - integrity sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA== +"vite@^3.0.0 || ^4.0.0 || ^5.0.0-0", "vite@^3.1.0 || ^4.0.0 || ^5.0.0-0", vite@^4.4.11: + version "4.5.0" + resolved "https://registry.yarnpkg.com/vite/-/vite-4.5.0.tgz#ec406295b4167ac3bc23e26f9c8ff559287cff26" + integrity sha512-ulr8rNLA6rkyFAlVWw2q5YJ91v098AFQ2R0PRFwPzREXOUJQPtFUG0t+/ZikhaOCDqFoDhN6/v8Sq0o4araFAw== dependencies: - minimalistic-assert "^1.0.0" + esbuild "^0.18.10" + postcss "^8.4.27" + rollup "^3.27.1" + optionalDependencies: + fsevents "~2.3.2" -web-streams-polyfill@^3.0.3: - version "3.2.1" - resolved "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz#71c2718c52b45fd49dbeee88634b3a60ceab42a6" - integrity sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q== +vitest@^0.34.5: + version "0.34.6" + resolved "https://registry.yarnpkg.com/vitest/-/vitest-0.34.6.tgz#44880feeeef493c04b7f795ed268f24a543250d7" + integrity sha512-+5CALsOvbNKnS+ZHMXtuUC7nL8/7F1F2DnHGjSsszX8zCjWSSviphCb/NuS9Nzf4Q03KyyDRBAXhF/8lffME4Q== + dependencies: + "@types/chai" "^4.3.5" + "@types/chai-subset" "^1.3.3" + "@types/node" "*" + "@vitest/expect" "0.34.6" + "@vitest/runner" "0.34.6" + "@vitest/snapshot" "0.34.6" + "@vitest/spy" "0.34.6" + "@vitest/utils" "0.34.6" + acorn "^8.9.0" + acorn-walk "^8.2.0" + cac "^6.7.14" + chai "^4.3.10" + debug "^4.3.4" + local-pkg "^0.4.3" + magic-string "^0.30.1" + pathe "^1.1.1" + picocolors "^1.0.0" + std-env "^3.3.3" + strip-literal "^1.0.1" + tinybench "^2.5.0" + tinypool "^0.7.0" + vite "^3.1.0 || ^4.0.0 || ^5.0.0-0" + vite-node "0.34.6" + why-is-node-running "^2.2.2" -web-vitals@^3.1.0: +void-elements@3.1.0: version "3.1.0" - resolved "https://registry.npmjs.org/web-vitals/-/web-vitals-3.1.0.tgz#a6f5156cb6c7fee562da46078540265ac2cd2d16" - integrity sha512-zCeQ+bOjWjJbXv5ZL0r8Py3XP2doCQMZXNKlBGfUjPAVZWokApdeF/kFlK1peuKlCt8sL9TFkKzyXE9/cmNJQA== - -webidl-conversions@^4.0.2: - version "4.0.2" - resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad" - integrity sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg== + resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-3.1.0.tgz#614f7fbf8d801f0bb5f0661f5b2f5785750e4f09" + integrity sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w== -webidl-conversions@^5.0.0: - version "5.0.0" - resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz#ae59c8a00b121543a2acc65c0434f57b0fc11aff" - integrity sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA== +vscode-jsonrpc@6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-6.0.0.tgz#108bdb09b4400705176b957ceca9e0880e9b6d4e" + integrity sha512-wnJA4BnEjOSyFMvjZdpiOwhSq9uDoK8e/kpRJDTaMYzwlkrhG1fwDIZI94CLsLzlCK5cIbMMtFlJlfR57Lavmg== -webidl-conversions@^6.1.0: - version "6.1.0" - resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz#9111b4d7ea80acd40f5270d666621afa78b69514" - integrity sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w== - -webpack-dev-middleware@^5.3.1: - version "5.3.3" - resolved "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.3.tgz#efae67c2793908e7311f1d9b06f2a08dcc97e51f" - integrity sha512-hj5CYrY0bZLB+eTO+x/j67Pkrquiy7kWepMHmUMoPsmcUaeEnQJqFzHJOyxgWlq746/wUuA64p9ta34Kyb01pA== - dependencies: - colorette "^2.0.10" - memfs "^3.4.3" - mime-types "^2.1.31" - range-parser "^1.2.1" - schema-utils "^4.0.0" - -webpack-dev-server@^4.6.0: - version "4.11.1" - resolved "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.11.1.tgz#ae07f0d71ca0438cf88446f09029b92ce81380b5" - integrity sha512-lILVz9tAUy1zGFwieuaQtYiadImb5M3d+H+L1zDYalYoDl0cksAB1UNyuE5MMWJrG6zR1tXkCP2fitl7yoUJiw== - dependencies: - "@types/bonjour" "^3.5.9" - "@types/connect-history-api-fallback" "^1.3.5" - "@types/express" "^4.17.13" - "@types/serve-index" "^1.9.1" - "@types/serve-static" "^1.13.10" - "@types/sockjs" "^0.3.33" - "@types/ws" "^8.5.1" - ansi-html-community "^0.0.8" - bonjour-service "^1.0.11" - chokidar "^3.5.3" - colorette "^2.0.10" - compression "^1.7.4" - connect-history-api-fallback "^2.0.0" - default-gateway "^6.0.3" - express "^4.17.3" - graceful-fs "^4.2.6" - html-entities "^2.3.2" - http-proxy-middleware "^2.0.3" - ipaddr.js "^2.0.1" - open "^8.0.9" - p-retry "^4.5.0" - rimraf "^3.0.2" - schema-utils "^4.0.0" - selfsigned "^2.1.1" - serve-index "^1.9.1" - sockjs "^0.3.24" - spdy "^4.0.2" - webpack-dev-middleware "^5.3.1" - ws "^8.4.2" - -webpack-manifest-plugin@^4.0.2: - version "4.1.1" - resolved "https://registry.npmjs.org/webpack-manifest-plugin/-/webpack-manifest-plugin-4.1.1.tgz#10f8dbf4714ff93a215d5a45bcc416d80506f94f" - integrity sha512-YXUAwxtfKIJIKkhg03MKuiFAD72PlrqCiwdwO4VEXdRO5V0ORCNwaOwAZawPZalCbmH9kBDmXnNeQOw+BIEiow== +vscode-languageclient@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-7.0.0.tgz#b505c22c21ffcf96e167799757fca07a6bad0fb2" + integrity sha512-P9AXdAPlsCgslpP9pRxYPqkNYV7Xq8300/aZDpO35j1fJm/ncize8iGswzYlcvFw5DQUx4eVk+KvfXdL0rehNg== dependencies: - tapable "^2.0.0" - webpack-sources "^2.2.0" + minimatch "^3.0.4" + semver "^7.3.4" + vscode-languageserver-protocol "3.16.0" -webpack-sources@^1.4.3: - version "1.4.3" - resolved "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz#eedd8ec0b928fbf1cbfe994e22d2d890f330a933" - integrity sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ== +vscode-languageserver-protocol@3.16.0: + version "3.16.0" + resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.16.0.tgz#34135b61a9091db972188a07d337406a3cdbe821" + integrity sha512-sdeUoAawceQdgIfTI+sdcwkiK2KU+2cbEYA0agzM2uqaUy2UpnnGHtWTHVEtS0ES4zHU0eMFRGN+oQgDxlD66A== dependencies: - source-list-map "^2.0.0" - source-map "~0.6.1" + vscode-jsonrpc "6.0.0" + vscode-languageserver-types "3.16.0" -webpack-sources@^2.2.0: - version "2.3.1" - resolved "https://registry.npmjs.org/webpack-sources/-/webpack-sources-2.3.1.tgz#570de0af163949fe272233c2cefe1b56f74511fd" - integrity sha512-y9EI9AO42JjEcrTJFOYmVywVZdKVUfOvDUPsJea5GIr1JOEGFVqwlY2K098fFoIjOkDzHn2AjRvM8dsBZu+gCA== - dependencies: - source-list-map "^2.0.1" - source-map "^0.6.1" +vscode-languageserver-textdocument@^1.0.1: + version "1.0.11" + resolved "https://registry.yarnpkg.com/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.11.tgz#0822a000e7d4dc083312580d7575fe9e3ba2e2bf" + integrity sha512-X+8T3GoiwTVlJbicx/sIAF+yuJAqz8VvwJyoMVhwEMoEKE/fkDmrqUgDMyBECcM2A2frVZIUj5HI/ErRXCfOeA== -webpack-sources@^3.2.3: - version "3.2.3" - resolved "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde" - integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w== - -webpack@^5.64.4: - version "5.75.0" - resolved "https://registry.npmjs.org/webpack/-/webpack-5.75.0.tgz#1e440468647b2505860e94c9ff3e44d5b582c152" - integrity sha512-piaIaoVJlqMsPtX/+3KTTO6jfvrSYgauFVdt8cr9LTHKmcq/AMd4mhzsiP7ZF/PGRNPGA8336jldh9l2Kt2ogQ== - dependencies: - "@types/eslint-scope" "^3.7.3" - "@types/estree" "^0.0.51" - "@webassemblyjs/ast" "1.11.1" - "@webassemblyjs/wasm-edit" "1.11.1" - "@webassemblyjs/wasm-parser" "1.11.1" - acorn "^8.7.1" - acorn-import-assertions "^1.7.6" - browserslist "^4.14.5" - chrome-trace-event "^1.0.2" - enhanced-resolve "^5.10.0" - es-module-lexer "^0.9.0" - eslint-scope "5.1.1" - events "^3.2.0" - glob-to-regexp "^0.4.1" - graceful-fs "^4.2.9" - json-parse-even-better-errors "^2.3.1" - loader-runner "^4.2.0" - mime-types "^2.1.27" - neo-async "^2.6.2" - schema-utils "^3.1.0" - tapable "^2.1.1" - terser-webpack-plugin "^5.1.3" - watchpack "^2.4.0" - webpack-sources "^3.2.3" - -websocket-driver@>=0.5.1, websocket-driver@^0.7.4: - version "0.7.4" - resolved "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz#89ad5295bbf64b480abcba31e4953aca706f5760" - integrity sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg== - dependencies: - http-parser-js ">=0.5.1" - safe-buffer ">=5.1.0" - websocket-extensions ">=0.1.1" +vscode-languageserver-types@3.16.0: + version "3.16.0" + resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.16.0.tgz#ecf393fc121ec6974b2da3efb3155644c514e247" + integrity sha512-k8luDIWJWyenLc5ToFQQMaSrqCHiLwyKPHKPQZ5zz21vM+vIVUSvsRpcbiECH4WR88K2XZqc4ScRcZ7nk/jbeA== -websocket-extensions@>=0.1.1: - version "0.1.4" - resolved "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz#7f8473bc839dfd87608adb95d7eb075211578a42" - integrity sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg== - -websocket@^1.0.34: - version "1.0.34" - resolved "https://registry.npmjs.org/websocket/-/websocket-1.0.34.tgz#2bdc2602c08bf2c82253b730655c0ef7dcab3111" - integrity sha512-PRDso2sGwF6kM75QykIesBijKSVceR6jL2G8NGYyq2XrItNC2P5/qL5XeR056GhA+Ly7JMFvJb9I312mJfmqnQ== - dependencies: - bufferutil "^4.0.1" - debug "^2.2.0" - es5-ext "^0.10.50" - typedarray-to-buffer "^3.1.5" - utf-8-validate "^5.0.2" - yaeti "^0.0.6" - -whatwg-encoding@^1.0.5: - version "1.0.5" - resolved "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz#5abacf777c32166a51d085d6b4f3e7d27113ddb0" - integrity sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw== +vscode-languageserver@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/vscode-languageserver/-/vscode-languageserver-7.0.0.tgz#49b068c87cfcca93a356969d20f5d9bdd501c6b0" + integrity sha512-60HTx5ID+fLRcgdHfmz0LDZAXYEV68fzwG0JWwEPBode9NuMYTIxuYXPg4ngO8i8+Ou0lM7y6GzaYWbiDL0drw== dependencies: - iconv-lite "0.4.24" + vscode-languageserver-protocol "3.16.0" -whatwg-fetch@^3.6.2: - version "3.6.2" - resolved "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.2.tgz#dced24f37f2624ed0281725d51d0e2e3fe677f8c" - integrity sha512-bJlen0FcuU/0EMLrdbJ7zOnW6ITZLrZMIarMUVmdKtsGvZna8vxKYaexICWPfZ8qwf9fzNq+UEIZrnSaApt6RA== - -whatwg-mimetype@^2.3.0: - version "2.3.0" - resolved "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf" - integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g== +vscode-uri@^3.0.2: + version "3.0.8" + resolved "https://registry.yarnpkg.com/vscode-uri/-/vscode-uri-3.0.8.tgz#1770938d3e72588659a172d0fd4642780083ff9f" + integrity sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw== -whatwg-url@^7.0.0: - version "7.1.0" - resolved "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz#c2c492f1eca612988efd3d2266be1b9fc6170d06" - integrity sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg== - dependencies: - lodash.sortby "^4.7.0" - tr46 "^1.0.1" - webidl-conversions "^4.0.2" +web-streams-polyfill@^3.0.3: + version "3.2.1" + resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz#71c2718c52b45fd49dbeee88634b3a60ceab42a6" + integrity sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q== -whatwg-url@^8.0.0, whatwg-url@^8.5.0: - version "8.7.0" - resolved "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.7.0.tgz#656a78e510ff8f3937bc0bcbe9f5c0ac35941b77" - integrity sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg== +webrtc-adapter@^7.2.1: + version "7.7.1" + resolved "https://registry.yarnpkg.com/webrtc-adapter/-/webrtc-adapter-7.7.1.tgz#b2c227a6144983b35057df67bd984a7d4bfd17f1" + integrity sha512-TbrbBmiQBL9n0/5bvDdORc6ZfRY/Z7JnEj+EYOD1ghseZdpJ+nF2yx14k3LgQKc7JZnG7HAcL+zHnY25So9d7A== dependencies: - lodash "^4.7.0" - tr46 "^2.1.0" - webidl-conversions "^6.1.0" + rtcpeerconnection-shim "^1.2.15" + sdp "^2.12.0" which-boxed-primitive@^1.0.2: version "1.0.2" - resolved "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" + resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" integrity sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg== dependencies: is-bigint "^1.0.1" @@ -10556,9 +5218,27 @@ which-boxed-primitive@^1.0.2: is-string "^1.0.5" is-symbol "^1.0.3" +which-builtin-type@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/which-builtin-type/-/which-builtin-type-1.1.3.tgz#b1b8443707cc58b6e9bf98d32110ff0c2cbd029b" + integrity sha512-YmjsSMDBYsM1CaFiayOVT06+KJeXf0o5M/CAd4o1lTadFAtacTUM49zoYxr/oroopFDfhvN6iEcBxUyc3gvKmw== + dependencies: + function.prototype.name "^1.1.5" + has-tostringtag "^1.0.0" + is-async-function "^2.0.0" + is-date-object "^1.0.5" + is-finalizationregistry "^1.0.2" + is-generator-function "^1.0.10" + is-regex "^1.1.4" + is-weakref "^1.0.2" + isarray "^2.0.5" + which-boxed-primitive "^1.0.2" + which-collection "^1.0.1" + which-typed-array "^1.1.9" + which-collection@^1.0.1: version "1.0.1" - resolved "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz#70eab71ebbbd2aefaf32f917082fc62cdcb70906" + resolved "https://registry.yarnpkg.com/which-collection/-/which-collection-1.0.1.tgz#70eab71ebbbd2aefaf32f917082fc62cdcb70906" integrity sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A== dependencies: is-map "^2.0.1" @@ -10566,217 +5246,42 @@ which-collection@^1.0.1: is-weakmap "^2.0.1" is-weakset "^2.0.1" -which-typed-array@^1.1.8: - version "1.1.9" - resolved "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz#307cf898025848cf995e795e8423c7f337efbde6" - integrity sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA== +which-typed-array@^1.1.11, which-typed-array@^1.1.13, which-typed-array@^1.1.9: + version "1.1.13" + resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.13.tgz#870cd5be06ddb616f504e7b039c4c24898184d36" + integrity sha512-P5Nra0qjSncduVPEAr7xhoF5guty49ArDTwzJ/yNuPIbZppyRxFQsRCWrocxIY+CnMVG+qfbU2FmDKyvSGClow== dependencies: available-typed-arrays "^1.0.5" - call-bind "^1.0.2" + call-bind "^1.0.4" for-each "^0.3.3" gopd "^1.0.1" has-tostringtag "^1.0.0" - is-typed-array "^1.1.10" - -which@^1.3.1: - version "1.3.1" - resolved "https://registry.npmjs.org/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" - integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== - dependencies: - isexe "^2.0.0" which@^2.0.1: version "2.0.2" - resolved "https://registry.npmjs.org/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== dependencies: isexe "^2.0.0" -word-wrap@^1.2.3, word-wrap@~1.2.3: - version "1.2.3" - resolved "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" - integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== - -workbox-background-sync@6.5.4: - version "6.5.4" - resolved "https://registry.npmjs.org/workbox-background-sync/-/workbox-background-sync-6.5.4.tgz#3141afba3cc8aa2ae14c24d0f6811374ba8ff6a9" - integrity sha512-0r4INQZMyPky/lj4Ou98qxcThrETucOde+7mRGJl13MPJugQNKeZQOdIJe/1AchOP23cTqHcN/YVpD6r8E6I8g== - dependencies: - idb "^7.0.1" - workbox-core "6.5.4" - -workbox-broadcast-update@6.5.4: - version "6.5.4" - resolved "https://registry.npmjs.org/workbox-broadcast-update/-/workbox-broadcast-update-6.5.4.tgz#8441cff5417cd41f384ba7633ca960a7ffe40f66" - integrity sha512-I/lBERoH1u3zyBosnpPEtcAVe5lwykx9Yg1k6f8/BGEPGaMMgZrwVrqL1uA9QZ1NGGFoyE6t9i7lBjOlDhFEEw== - dependencies: - workbox-core "6.5.4" - -workbox-build@6.5.4: - version "6.5.4" - resolved "https://registry.npmjs.org/workbox-build/-/workbox-build-6.5.4.tgz#7d06d31eb28a878817e1c991c05c5b93409f0389" - integrity sha512-kgRevLXEYvUW9WS4XoziYqZ8Q9j/2ziJYEtTrjdz5/L/cTUa2XfyMP2i7c3p34lgqJ03+mTiz13SdFef2POwbA== - dependencies: - "@apideck/better-ajv-errors" "^0.3.1" - "@babel/core" "^7.11.1" - "@babel/preset-env" "^7.11.0" - "@babel/runtime" "^7.11.2" - "@rollup/plugin-babel" "^5.2.0" - "@rollup/plugin-node-resolve" "^11.2.1" - "@rollup/plugin-replace" "^2.4.1" - "@surma/rollup-plugin-off-main-thread" "^2.2.3" - ajv "^8.6.0" - common-tags "^1.8.0" - fast-json-stable-stringify "^2.1.0" - fs-extra "^9.0.1" - glob "^7.1.6" - lodash "^4.17.20" - pretty-bytes "^5.3.0" - rollup "^2.43.1" - rollup-plugin-terser "^7.0.0" - source-map "^0.8.0-beta.0" - stringify-object "^3.3.0" - strip-comments "^2.0.1" - tempy "^0.6.0" - upath "^1.2.0" - workbox-background-sync "6.5.4" - workbox-broadcast-update "6.5.4" - workbox-cacheable-response "6.5.4" - workbox-core "6.5.4" - workbox-expiration "6.5.4" - workbox-google-analytics "6.5.4" - workbox-navigation-preload "6.5.4" - workbox-precaching "6.5.4" - workbox-range-requests "6.5.4" - workbox-recipes "6.5.4" - workbox-routing "6.5.4" - workbox-strategies "6.5.4" - workbox-streams "6.5.4" - workbox-sw "6.5.4" - workbox-window "6.5.4" - -workbox-cacheable-response@6.5.4: - version "6.5.4" - resolved "https://registry.npmjs.org/workbox-cacheable-response/-/workbox-cacheable-response-6.5.4.tgz#a5c6ec0c6e2b6f037379198d4ef07d098f7cf137" - integrity sha512-DCR9uD0Fqj8oB2TSWQEm1hbFs/85hXXoayVwFKLVuIuxwJaihBsLsp4y7J9bvZbqtPJ1KlCkmYVGQKrBU4KAug== - dependencies: - workbox-core "6.5.4" - -workbox-core@6.5.4: - version "6.5.4" - resolved "https://registry.npmjs.org/workbox-core/-/workbox-core-6.5.4.tgz#df48bf44cd58bb1d1726c49b883fb1dffa24c9ba" - integrity sha512-OXYb+m9wZm8GrORlV2vBbE5EC1FKu71GGp0H4rjmxmF4/HLbMCoTFws87M3dFwgpmg0v00K++PImpNQ6J5NQ6Q== - -workbox-expiration@6.5.4: - version "6.5.4" - resolved "https://registry.npmjs.org/workbox-expiration/-/workbox-expiration-6.5.4.tgz#501056f81e87e1d296c76570bb483ce5e29b4539" - integrity sha512-jUP5qPOpH1nXtjGGh1fRBa1wJL2QlIb5mGpct3NzepjGG2uFFBn4iiEBiI9GUmfAFR2ApuRhDydjcRmYXddiEQ== - dependencies: - idb "^7.0.1" - workbox-core "6.5.4" - -workbox-google-analytics@6.5.4: - version "6.5.4" - resolved "https://registry.npmjs.org/workbox-google-analytics/-/workbox-google-analytics-6.5.4.tgz#c74327f80dfa4c1954cbba93cd7ea640fe7ece7d" - integrity sha512-8AU1WuaXsD49249Wq0B2zn4a/vvFfHkpcFfqAFHNHwln3jK9QUYmzdkKXGIZl9wyKNP+RRX30vcgcyWMcZ9VAg== - dependencies: - workbox-background-sync "6.5.4" - workbox-core "6.5.4" - workbox-routing "6.5.4" - workbox-strategies "6.5.4" - -workbox-navigation-preload@6.5.4: - version "6.5.4" - resolved "https://registry.npmjs.org/workbox-navigation-preload/-/workbox-navigation-preload-6.5.4.tgz#ede56dd5f6fc9e860a7e45b2c1a8f87c1c793212" - integrity sha512-IIwf80eO3cr8h6XSQJF+Hxj26rg2RPFVUmJLUlM0+A2GzB4HFbQyKkrgD5y2d84g2IbJzP4B4j5dPBRzamHrng== - dependencies: - workbox-core "6.5.4" - -workbox-precaching@6.5.4: - version "6.5.4" - resolved "https://registry.npmjs.org/workbox-precaching/-/workbox-precaching-6.5.4.tgz#740e3561df92c6726ab5f7471e6aac89582cab72" - integrity sha512-hSMezMsW6btKnxHB4bFy2Qfwey/8SYdGWvVIKFaUm8vJ4E53JAY+U2JwLTRD8wbLWoP6OVUdFlXsTdKu9yoLTg== - dependencies: - workbox-core "6.5.4" - workbox-routing "6.5.4" - workbox-strategies "6.5.4" - -workbox-range-requests@6.5.4: - version "6.5.4" - resolved "https://registry.npmjs.org/workbox-range-requests/-/workbox-range-requests-6.5.4.tgz#86b3d482e090433dab38d36ae031b2bb0bd74399" - integrity sha512-Je2qR1NXCFC8xVJ/Lux6saH6IrQGhMpDrPXWZWWS8n/RD+WZfKa6dSZwU+/QksfEadJEr/NfY+aP/CXFFK5JFg== - dependencies: - workbox-core "6.5.4" - -workbox-recipes@6.5.4: - version "6.5.4" - resolved "https://registry.npmjs.org/workbox-recipes/-/workbox-recipes-6.5.4.tgz#cca809ee63b98b158b2702dcfb741b5cc3e24acb" - integrity sha512-QZNO8Ez708NNwzLNEXTG4QYSKQ1ochzEtRLGaq+mr2PyoEIC1xFW7MrWxrONUxBFOByksds9Z4//lKAX8tHyUA== - dependencies: - workbox-cacheable-response "6.5.4" - workbox-core "6.5.4" - workbox-expiration "6.5.4" - workbox-precaching "6.5.4" - workbox-routing "6.5.4" - workbox-strategies "6.5.4" - -workbox-routing@6.5.4: - version "6.5.4" - resolved "https://registry.npmjs.org/workbox-routing/-/workbox-routing-6.5.4.tgz#6a7fbbd23f4ac801038d9a0298bc907ee26fe3da" - integrity sha512-apQswLsbrrOsBUWtr9Lf80F+P1sHnQdYodRo32SjiByYi36IDyL2r7BH1lJtFX8fwNHDa1QOVY74WKLLS6o5Pg== - dependencies: - workbox-core "6.5.4" - -workbox-strategies@6.5.4: - version "6.5.4" - resolved "https://registry.npmjs.org/workbox-strategies/-/workbox-strategies-6.5.4.tgz#4edda035b3c010fc7f6152918370699334cd204d" - integrity sha512-DEtsxhx0LIYWkJBTQolRxG4EI0setTJkqR4m7r4YpBdxtWJH1Mbg01Cj8ZjNOO8etqfA3IZaOPHUxCs8cBsKLw== - dependencies: - workbox-core "6.5.4" - -workbox-streams@6.5.4: - version "6.5.4" - resolved "https://registry.npmjs.org/workbox-streams/-/workbox-streams-6.5.4.tgz#1cb3c168a6101df7b5269d0353c19e36668d7d69" - integrity sha512-FXKVh87d2RFXkliAIheBojBELIPnWbQdyDvsH3t74Cwhg0fDheL1T8BqSM86hZvC0ZESLsznSYWw+Va+KVbUzg== - dependencies: - workbox-core "6.5.4" - workbox-routing "6.5.4" - -workbox-sw@6.5.4: - version "6.5.4" - resolved "https://registry.npmjs.org/workbox-sw/-/workbox-sw-6.5.4.tgz#d93e9c67924dd153a61367a4656ff4d2ae2ed736" - integrity sha512-vo2RQo7DILVRoH5LjGqw3nphavEjK4Qk+FenXeUsknKn14eCNedHOXWbmnvP4ipKhlE35pvJ4yl4YYf6YsJArA== - -workbox-webpack-plugin@^6.4.1: - version "6.5.4" - resolved "https://registry.npmjs.org/workbox-webpack-plugin/-/workbox-webpack-plugin-6.5.4.tgz#baf2d3f4b8f435f3469887cf4fba2b7fac3d0fd7" - integrity sha512-LmWm/zoaahe0EGmMTrSLUi+BjyR3cdGEfU3fS6PN1zKFYbqAKuQ+Oy/27e4VSXsyIwAw8+QDfk1XHNGtZu9nQg== - dependencies: - fast-json-stable-stringify "^2.1.0" - pretty-bytes "^5.4.1" - upath "^1.2.0" - webpack-sources "^1.4.3" - workbox-build "6.5.4" - -workbox-window@6.5.4: - version "6.5.4" - resolved "https://registry.npmjs.org/workbox-window/-/workbox-window-6.5.4.tgz#d991bc0a94dff3c2dbb6b84558cff155ca878e91" - integrity sha512-HnLZJDwYBE+hpG25AQBO8RUWBJRaCsI9ksQJEp3aCOFCaG5kqaToAYXFRAHxzRluM2cQbGzdQF5rjKPWPA1fug== +why-is-node-running@^2.2.2: + version "2.2.2" + resolved "https://registry.yarnpkg.com/why-is-node-running/-/why-is-node-running-2.2.2.tgz#4185b2b4699117819e7154594271e7e344c9973e" + integrity sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA== dependencies: - "@types/trusted-types" "^2.0.2" - workbox-core "6.5.4" + siginfo "^2.0.0" + stackback "0.0.2" -worker-loader@^3.0.8: - version "3.0.8" - resolved "https://registry.npmjs.org/worker-loader/-/worker-loader-3.0.8.tgz#5fc5cda4a3d3163d9c274a4e3a811ce8b60dbb37" - integrity sha512-XQyQkIFeRVC7f7uRhFdNMe/iJOdO6zxAaR3EWbDp45v3mDhrTi+++oswKNxShUNjPC/1xUp5DB29YKLhFo129g== +wif@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/wif/-/wif-2.0.6.tgz#08d3f52056c66679299726fade0d432ae74b4704" + integrity sha512-HIanZn1zmduSF+BQhkE+YXIbEiH0xPr1012QbFEGB0xsKqJii0/SqJjyn8dFv6y36kOznMgMB+LGcbZTJ1xACQ== dependencies: - loader-utils "^2.0.0" - schema-utils "^3.0.0" + bs58check "<3.0.0" wrap-ansi@^7.0.0: version "7.0.0" - resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== dependencies: ansi-styles "^4.0.0" @@ -10785,92 +5290,62 @@ wrap-ansi@^7.0.0: wrappy@1: version "1.0.2" - resolved "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== -write-file-atomic@^3.0.0: - version "3.0.3" - resolved "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz#56bd5c5a5c70481cd19c571bd39ab965a5de56e8" - integrity sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q== - dependencies: - imurmurhash "^0.1.4" - is-typedarray "^1.0.0" - signal-exit "^3.0.2" - typedarray-to-buffer "^3.1.5" - -"ws@^5.2.0 || ^6.0.0 || ^7.0.0", ws@^7.4.6: +"ws@^5.2.0 || ^6.0.0 || ^7.0.0": version "7.5.9" - resolved "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz#54fa7db29f4c7cec68b1ddd3a89de099942bb591" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.9.tgz#54fa7db29f4c7cec68b1ddd3a89de099942bb591" integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q== -ws@^8.4.2, ws@^8.8.1: - version "8.11.0" - resolved "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz#6a0d36b8edfd9f96d8b25683db2f8d7de6e8e143" - integrity sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg== - -xml-name-validator@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a" - integrity sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw== - -xmlchars@^2.2.0: - version "2.2.0" - resolved "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" - integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== - -xtend@^4.0.2: - version "4.0.2" - resolved "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" - integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== +ws@^8.14.1, ws@^8.8.1: + version "8.14.2" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.14.2.tgz#6c249a806eb2db7a20d26d51e7709eab7b2e6c7f" + integrity sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g== y18n@^5.0.5: version "5.0.8" - resolved "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== -yaeti@^0.0.6: - version "0.0.6" - resolved "https://registry.npmjs.org/yaeti/-/yaeti-0.0.6.tgz#f26f484d72684cf42bedfb76970aa1608fbf9577" - integrity sha512-MvQa//+KcZCUkBTIC9blM+CU9J2GzuTytsOUwf2lidtvkx/6gnEp1QvJv34t9vdjhFmha/mUiNDbN0D0mJWdug== +yallist@^3.0.2: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" + integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== yallist@^4.0.0: version "4.0.0" - resolved "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== -yaml@^1.10.0, yaml@^1.10.2, yaml@^1.7.2: - version "1.10.2" - resolved "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" - integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== - -yargs-parser@^20.2.2: - version "20.2.9" - resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" - integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== +yargs-parser@^21.1.1: + version "21.1.1" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" + integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== -yargs@^16.2.0: - version "16.2.0" - resolved "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" - integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== +yargs@^17.5.1: + version "17.7.2" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" + integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== dependencies: - cliui "^7.0.2" + cliui "^8.0.1" escalade "^3.1.1" get-caller-file "^2.0.5" require-directory "^2.1.1" - string-width "^4.2.0" + string-width "^4.2.3" y18n "^5.0.5" - yargs-parser "^20.2.2" - -yarn@^1.22.18: - version "1.22.19" - resolved "https://registry.npmjs.org/yarn/-/yarn-1.22.19.tgz#4ba7fc5c6e704fce2066ecbfb0b0d8976fe62447" - integrity sha512-/0V5q0WbslqnwP91tirOvldvYISzaqhClxzyUKXYxs07yUILIs5jx/k6CFe8bvKSkds5w+eiOqta39Wk3WxdcQ== + yargs-parser "^21.1.1" yocto-queue@^0.1.0: version "0.1.0" - resolved "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" + resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== +yocto-queue@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-1.0.0.tgz#7f816433fb2cbc511ec8bf7d263c3b58a1a3c251" + integrity sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g== + zen-observable-ts@^1.2.5: version "1.2.5" resolved "https://registry.yarnpkg.com/zen-observable-ts/-/zen-observable-ts-1.2.5.tgz#6c6d9ea3d3a842812c6e9519209365a122ba8b58"