Compare commits
23 Commits
@verdaccio
...
@verdaccio
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cd7947adbc | ||
|
|
5fed1955a9 | ||
|
|
9dbf73e955 | ||
|
|
2c594910d8 | ||
|
|
90818700a3 | ||
|
|
7af1e6cc84 | ||
|
|
757bded72e | ||
|
|
1b9bf35c87 | ||
|
|
eb2afc4d6d | ||
|
|
5be013a059 | ||
|
|
8d6d6097c6 | ||
|
|
531289f59d | ||
|
|
5e784d1188 | ||
|
|
7dde848d0c | ||
|
|
71874de027 | ||
|
|
f13dacef9c | ||
|
|
761f1696f2 | ||
|
|
f412e8f8d6 | ||
|
|
f5fd7bf5bf | ||
|
|
3d26a8190c | ||
|
|
e50410a875 | ||
|
|
60d7b35d88 | ||
|
|
1042f9bf76 |
5
.changeset/olive-candles-speak.md
Normal file
5
.changeset/olive-candles-speak.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@verdaccio/ui-theme': minor
|
||||
---
|
||||
|
||||
feat: integrate rematch for ui state storage
|
||||
@@ -66,6 +66,7 @@
|
||||
"many-vans-care",
|
||||
"modern-spies-tell",
|
||||
"neat-toes-report",
|
||||
"olive-candles-speak",
|
||||
"perfect-emus-clean",
|
||||
"perfect-kangaroos-agree",
|
||||
"plenty-news-remember",
|
||||
@@ -74,6 +75,7 @@
|
||||
"pretty-hounds-tap",
|
||||
"proud-jeans-walk",
|
||||
"red-chefs-float",
|
||||
"shaggy-carrots-unite",
|
||||
"shiny-chefs-heal",
|
||||
"smart-apricots-kneel",
|
||||
"spicy-frogs-press",
|
||||
|
||||
5
.changeset/shaggy-carrots-unite.md
Normal file
5
.changeset/shaggy-carrots-unite.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@verdaccio/logger': patch
|
||||
---
|
||||
|
||||
do not show deprecation warning on default logger config
|
||||
2
.github/ISSUE_TEMPLATE/config.yml
vendored
2
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -7,7 +7,7 @@ contact_links:
|
||||
url: https://github.com/verdaccio/verdaccio/security/policy
|
||||
about: I want to report a security vulnerability
|
||||
- name: Chat 🏘
|
||||
url: http://chat.verdaccio.org
|
||||
url: https://discord.gg/7qWJxBf
|
||||
about: For a quick question you should do it through our community chat
|
||||
- name: User Interface Report 👩🏼🎨👨🏼🎨
|
||||
url: https://github.com/verdaccio/ui/issues/new/choose
|
||||
|
||||
4
.github/workflows/benckmark.yml
vendored
4
.github/workflows/benckmark.yml
vendored
@@ -58,7 +58,7 @@ jobs:
|
||||
- 3.13.1
|
||||
- 4.12.2
|
||||
- 5.1.3
|
||||
- 6.0.0-6-next.22
|
||||
- 6.0.0-6-next.23
|
||||
name: Benchmark autocannon
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
@@ -119,7 +119,7 @@ jobs:
|
||||
- 3.13.1
|
||||
- 4.12.2
|
||||
- 5.1.3
|
||||
- 6.0.0-6-next.22
|
||||
- 6.0.0-6-next.23
|
||||
name: Benchmark hyperfine
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
48
.github/workflows/ci.yml
vendored
48
.github/workflows/ci.yml
vendored
@@ -83,6 +83,7 @@ jobs:
|
||||
run: pnpm recursive install --frozen-lockfile --ignore-scripts
|
||||
- name: Lint
|
||||
run: pnpm format:check
|
||||
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
name: build
|
||||
@@ -101,6 +102,12 @@ jobs:
|
||||
key: pnpm-${{ hashFiles('pnpm-lock.yaml') }}
|
||||
- name: Install
|
||||
run: pnpm recursive install --frozen-lockfile --ignore-scripts
|
||||
- name: crowdin download
|
||||
env:
|
||||
CROWDIN_VERDACCIO_PROJECT_ID: ${{ secrets.CROWDIN_VERDACCIO_PROJECT_ID }}
|
||||
CROWDIN_VERDACCIO_API_KEY: ${{ secrets.CROWDIN_VERDACCIO_API_KEY }}
|
||||
CONTEXT: production
|
||||
run: pnpm crowdin:download
|
||||
- name: build
|
||||
run: pnpm build
|
||||
- name: tar packages
|
||||
@@ -116,6 +123,7 @@ jobs:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
os: [ubuntu-latest]
|
||||
## Node 16 breaks UI test, jest issue
|
||||
node_version: [14]
|
||||
name: ${{ matrix.os }} / Node ${{ matrix.node_version }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
@@ -165,8 +173,8 @@ jobs:
|
||||
run: pnpm recursive install --frozen-lockfile
|
||||
- name: Test UI
|
||||
run: pnpm test:e2e:ui
|
||||
env:
|
||||
DEBUG: verdaccio:e2e*
|
||||
# env:
|
||||
# DEBUG: verdaccio:e2e*
|
||||
ci-e2e-cli:
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
@@ -192,8 +200,8 @@ jobs:
|
||||
run: pnpm recursive install --frozen-lockfile
|
||||
- name: Test CLI
|
||||
run: pnpm test:e2e:cli
|
||||
env:
|
||||
DEBUG: verdaccio*
|
||||
# env:
|
||||
# DEBUG: verdaccio*
|
||||
test-windows:
|
||||
needs: [format, lint]
|
||||
runs-on: windows-latest
|
||||
@@ -217,3 +225,35 @@ jobs:
|
||||
run: pnpm build
|
||||
- name: Test
|
||||
run: pnpm test
|
||||
sync-translations:
|
||||
needs: [ci-e2e-cli, ci-e2e-ui, test-windows, test]
|
||||
runs-on: ubuntu-latest
|
||||
name: synchronize translations
|
||||
if: (github.event_name == 'push' && github.ref == 'refs/heads/master') || github.event_name == 'workflow_dispatch'
|
||||
steps:
|
||||
- uses: actions/checkout@v2.3.1
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 14
|
||||
- uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: verdaccio-artifact
|
||||
- name: untar packages
|
||||
run: tar -xzvf pkg.tar.gz -C ${{ github.workspace }}/packages
|
||||
- name: Install pnpm
|
||||
run: npm i pnpm@6.10.3 -g
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.pnpm-store
|
||||
key: pnpm-${{ hashFiles('pnpm-lock.yaml') }}
|
||||
- name: Install
|
||||
## we need scripts, pupetter downloads aditional content
|
||||
run: pnpm recursive install --frozen-lockfile
|
||||
- name: generate website translations
|
||||
run: pnpm write-translations --filter ...@verdaccio/website
|
||||
- name: sync
|
||||
env:
|
||||
CROWDIN_VERDACCIO_PROJECT_ID: ${{ secrets.CROWDIN_VERDACCIO_PROJECT_ID }}
|
||||
CROWDIN_VERDACCIO_API_KEY: ${{ secrets.CROWDIN_VERDACCIO_API_KEY }}
|
||||
CONTEXT: production
|
||||
run: pnpm crowdin:sync
|
||||
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -34,6 +34,9 @@ packages/standalone/dist/
|
||||
|
||||
## ui
|
||||
packages/plugins/ui-theme/static
|
||||
/packages/plugins/ui-theme/src/i18n/download_translations/
|
||||
!/packages/plugins/ui-theme/src/i18n/crowdin/ui.json
|
||||
|
||||
|
||||
# CI Pnpm cache
|
||||
.pnpm-store/
|
||||
@@ -45,5 +48,5 @@ hyper-results*.json
|
||||
api-results*.json
|
||||
|
||||
#docs
|
||||
api/
|
||||
./api
|
||||
packages/core/core/docs
|
||||
|
||||
5
.husky/pre-commit
Executable file
5
.husky/pre-commit
Executable file
@@ -0,0 +1,5 @@
|
||||
#!/bin/sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
|
||||
#./node_modules/.bin/lint-staged
|
||||
npm run husky:pre-commit
|
||||
@@ -19,6 +19,7 @@ guidelines for you:
|
||||
- [What's is not considered a bug?](#whats-is-not-considered-a-bug)
|
||||
- [Issue Search](#issue-search)
|
||||
- [Chat](#chat)
|
||||
- [Translations](#translations)
|
||||
- [Request Features](#request-features)
|
||||
- [Contributing Guidelines](#contributing-guidelines)
|
||||
- [Submitting a Pull Request](#submitting-a-pull-request)
|
||||
@@ -190,10 +191,28 @@ affecting multiple people.
|
||||
|
||||
### Chat
|
||||
|
||||
Questions can be asked via [Discord](http://chat.verdaccio.org/)
|
||||
Questions can be asked via [Discord](https://discord.gg/7qWJxBf)
|
||||
|
||||
**Please use the `#help` channel.**
|
||||
|
||||
## Translations
|
||||
|
||||
All translations are provided by the `crowdin` platform:
|
||||
[https://translate.verdaccio.org/](https://translate.verdaccio.org/)
|
||||
|
||||
If you want to contribute by adding translations, create an account (GitHub could be used as fast alternative), in the platform you can contribute to two areas, the website or improve User Interface translations.
|
||||
|
||||
If a language is not listed, ask for it in the [Discord](https://discord.gg/7qWJxBf) channel #contribute channel.
|
||||
|
||||
For adding a new **language** on the UI follow these steps:
|
||||
|
||||
1. Ensure the **language** has been enabled, must be visible in the `crowdin` platform.
|
||||
2. Find in the explorer the file `en.US.json` in the path `packages/plugins/ui-theme/src/i18n/crowdin/ui.json` and complete the translations, **not need to find approval on this**.
|
||||
3. Into the project, add a new field into `packages/plugins/ui-theme/src/i18n/crowdin/ui.json` file, in the section `lng`, the new language, eg: `{ lng: {korean:"Korean"}}`. (This file is English based, once the PR has been merged, this string will be available in crowdin for translate to the targeted language).
|
||||
4. Add the language, [flag icon](https://www.npmjs.com/package/country-flag-icons), and the menu key fort he new language eg: `menuKey: 'lng.korean'` to the file `packages/plugins/ui-theme/src/i18n/enabledLanguages.ts`.
|
||||
5. For local testing, read `packages/plugins/ui-theme/src/i18n/ABOUT_TRANSLATIONS.md`.
|
||||
6. Add a `changeset` file, see more info below.
|
||||
|
||||
## Request Features
|
||||
|
||||
New feature requests are welcome. Analyse whether the idea fits within scope of
|
||||
|
||||
31
README.md
31
README.md
@@ -75,7 +75,7 @@ booted in a couple of seconds, fast enough for any CI. Many open source projects
|
||||
|
||||
### **Testing the integrity of your React components by publishing in a private registry - React Finland 2021**.
|
||||
|
||||
[](https://www.youtube.com/watch?v=5olfi5wbgF4)
|
||||
[](https://www.youtube.com/watch?v=bRKZbrlQqLY&t=16s&ab_channel=ReactFinland)
|
||||
|
||||
You might want to check out as well our previous talks:
|
||||
|
||||
@@ -190,7 +190,20 @@ Verdaccio aims to support all features of a standard npm client that make sense
|
||||
|
||||
If you want to report a security vulnerability, please follow the steps which we have defined for you in our [security policy](https://github.com/verdaccio/verdaccio/security/policy).
|
||||
|
||||
## Core Team
|
||||
## Special Thanks
|
||||
|
||||
Thanks to the following companies to help us to achieve our goals providing free open source licenses. Every company provides enough resources to move this project forward.
|
||||
|
||||
| Company | Logo | License |
|
||||
| ------------ | ------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------- |
|
||||
| JetBrains | [](https://www.jetbrains.com/) | JetBrains provides licenses for products for active maintainers, renewable yearly |
|
||||
| Crowdin | [](https://crowdin.com/) | Crowdin provides platform for translations |
|
||||
| BrowserStack | [](https://www.browserstack.com/) | BrowserStack provides plan to run End to End testing for the UI |
|
||||
| Netlify | [](https://www.netlify.com/) | Netlify provides pro plan for website deployment |
|
||||
| Algolia | [](https://algolia.com/) | Algolia provides search services for the website |
|
||||
| Docker | [](https://www.docker.com/community/open-source/application) | Docker offers unlimited pulls and unlimited egress to any and all users |
|
||||
|
||||
## Maintainers
|
||||
|
||||
| [Juan Picado](https://github.com/juanpicado) | [Ayush Sharma](https://github.com/ayusharma) | [Sergio Hg](https://github.com/sergiohgz) |
|
||||
| ------------------------------------------------------------------------------ | ------------------------------------------------------------------------ | ------------------------------------------------------------------------- |
|
||||
@@ -244,18 +257,6 @@ Thank you to all our backers! 🙏 [[Become a backer](https://opencollective.com
|
||||
|
||||
[](https://opencollective.com/verdaccio#backers)
|
||||
|
||||
## Special Thanks
|
||||
|
||||
Thanks to the following companies to help us to achieve our goals providing free open source licenses.
|
||||
|
||||
[](https://www.jetbrains.com/)
|
||||
[](https://crowdin.com/)
|
||||
[](https://www.browserstack.com/)
|
||||
[](https://www.netlify.com/)
|
||||
[](https://algolia.com/)
|
||||
|
||||
Verdaccio also is part of to the [Docker Open Source Program](https://www.docker.com/blog/expanded-support-for-open-source-software-projects/).
|
||||
|
||||
## Contributors
|
||||
|
||||
This project exists thanks to all the people who contribute. [[Contribute](CONTRIBUTING.md)].
|
||||
@@ -270,7 +271,7 @@ If you have any issue you can try the following options, do no desist to ask or
|
||||
- [Donations](https://github.com/sponsors/verdaccio)
|
||||
- [Reporting an issue](https://github.com/verdaccio/verdaccio/issues/new/choose)
|
||||
- [Running discussions](https://github.com/verdaccio/verdaccio/issues?q=is%3Aissue+is%3Aopen+label%3Adiscuss)
|
||||
- [Chat](http://chat.verdaccio.org/)
|
||||
- [Chat](https://discord.gg/7qWJxBf)
|
||||
- [Logos](https://verdaccio.org/docs/en/logo)
|
||||
- [Docker Examples](https://github.com/verdaccio/verdaccio/tree/master/docker-examples)
|
||||
- [FAQ](https://github.com/verdaccio/verdaccio/discussions/categories/q-a)
|
||||
|
||||
@@ -5,6 +5,10 @@ preserve_hierarchy: true
|
||||
|
||||
files:
|
||||
[
|
||||
{
|
||||
source: 'packages/plugins/ui-theme/src/i18n/crowdin/**/*',
|
||||
translation: '/packages/plugins/ui-theme/src/i18n/download_translations/%locale%/**/%original_file_name%',
|
||||
},
|
||||
{
|
||||
source: '/website/i18n/en/**/*',
|
||||
translation: '/website/i18n/%locale%/**/%original_file_name%',
|
||||
@@ -13,4 +17,4 @@ files:
|
||||
source: '/website/docs/**/*',
|
||||
translation: '/website/i18n/%locale%/docusaurus-plugin-content-docs/current/**/%original_file_name%',
|
||||
}
|
||||
]
|
||||
]
|
||||
|
||||
33
package.json
33
package.json
@@ -52,12 +52,6 @@
|
||||
"@types/mime": "2.0.3",
|
||||
"@types/minimatch": "3.0.5",
|
||||
"@types/node": "14.6.0",
|
||||
"@types/react": "17.0.19",
|
||||
"@types/react-autosuggest": "10.1.5",
|
||||
"@types/react-dom": "17.0.9",
|
||||
"@types/react-helmet": "6.1.2",
|
||||
"@types/react-router-dom": "5.1.8",
|
||||
"@types/react-virtualized": "9.21.13",
|
||||
"@types/request": "2.48.7",
|
||||
"@types/semver": "7.3.8",
|
||||
"@types/supertest": "2.0.11",
|
||||
@@ -84,25 +78,13 @@
|
||||
"debug": "4.3.2",
|
||||
"detect-secrets": "1.0.6",
|
||||
"eslint": "7.32.0",
|
||||
"eslint-config-google": "0.14.0",
|
||||
"eslint-config-prettier": "8.3.0",
|
||||
"eslint-plugin-babel": "5.3.1",
|
||||
"eslint-plugin-import": "2.24.2",
|
||||
"eslint-plugin-jest": "24.4.0",
|
||||
"eslint-plugin-jsx-a11y": "6.4.1",
|
||||
"eslint-plugin-prettier": "4.0.0",
|
||||
"eslint-plugin-react": "7.25.1",
|
||||
"eslint-plugin-react-hooks": "4.2.0",
|
||||
"eslint-plugin-simple-import-sort": "7.0.0",
|
||||
"eslint-plugin-verdaccio": "10.0.0",
|
||||
"fs-extra": "10.0.0",
|
||||
"husky": "4.3.5",
|
||||
"husky": "7.0.2",
|
||||
"in-publish": "2.0.1",
|
||||
"jest": "27.1.0",
|
||||
"jest-environment-jsdom": "27.1.0",
|
||||
"jest-environment-jsdom-global": "3.0.0",
|
||||
"jest-environment-node": "27.1.0",
|
||||
"jest-fetch-mock": "3.0.3",
|
||||
"jest-junit": "12.2.0",
|
||||
"kleur": "3.0.3",
|
||||
"lint-staged": "11.1.2",
|
||||
@@ -124,6 +106,7 @@
|
||||
"verdaccio-memory": "workspace:*"
|
||||
},
|
||||
"scripts": {
|
||||
"husky:pre-commit": "lint-staged",
|
||||
"clean": "pnpm recursive run clean",
|
||||
"build": "pnpm recursive run build --filter=!@verdaccio/website",
|
||||
"docker": "docker build -t verdaccio/verdaccio:local . --no-cache",
|
||||
@@ -139,7 +122,7 @@
|
||||
"benchmark:submit": "pnpm ts-node ./scripts/submit-metrics.ts",
|
||||
"start:watch": "concurrently --kill-others \"pnpm _build:watch\" \"pnpm _start:server\" \"pnpm _debug:reload\"",
|
||||
"_build:watch": "pnpm run --parallel watch --filter ./packages",
|
||||
"_start:server": "node packages/verdaccio/debug/bootstrap.js --listen 8000",
|
||||
"_start:server": "node --inspect packages/verdaccio/debug/bootstrap.js --listen 8000",
|
||||
"_start:web": "pnpm start --filter ...@verdaccio/ui-theme",
|
||||
"_debug:reload": "nodemon -d 3 packages/verdaccio/debug/bootstrap.js",
|
||||
"start:ts": "ts-node packages/verdaccio/src/start.ts -- --listen 8000",
|
||||
@@ -154,15 +137,11 @@
|
||||
"ts:ref": "update-ts-references --discardComments",
|
||||
"website": "pnpm build --filter ...@verdaccio/website",
|
||||
"crowdin:upload": "crowdin upload sources --auto-update --config ./crowdin.yaml",
|
||||
"crowdin:download": "crowdin download --config ./crowdin.yaml",
|
||||
"crowdin:sync": "pnpm crowdin:upload && pnpm crowdin:download --verbose"
|
||||
"crowdin:download": "crowdin download --verbose --config ./crowdin.yaml",
|
||||
"crowdin:sync": "pnpm crowdin:upload && pnpm crowdin:download --verbose",
|
||||
"postinstall": "husky install"
|
||||
},
|
||||
"license": "MIT",
|
||||
"husky": {
|
||||
"hooks": {
|
||||
"pre-commit": "lint-staged"
|
||||
}
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{js,jsx,ts,tsx,json,yml,yaml,md}": "prettier --write",
|
||||
"*.{js,jsx,ts,tsx}": "eslint --cache --fix"
|
||||
|
||||
@@ -1,5 +1,16 @@
|
||||
# @verdaccio/api
|
||||
|
||||
## 6.0.0-6-next.15
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [2c594910]
|
||||
- @verdaccio/logger@6.0.0-6-next.5
|
||||
- @verdaccio/auth@6.0.0-6-next.12
|
||||
- @verdaccio/hooks@6.0.0-6-next.7
|
||||
- @verdaccio/middleware@6.0.0-6-next.12
|
||||
- @verdaccio/store@6.0.0-6-next.13
|
||||
|
||||
## 6.0.0-6-next.14
|
||||
|
||||
### Major Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@verdaccio/api",
|
||||
"version": "6.0.0-6-next.14",
|
||||
"version": "6.0.0-6-next.15",
|
||||
"description": "loaders logic",
|
||||
"main": "./build/index.js",
|
||||
"types": "build/index.d.ts",
|
||||
@@ -39,14 +39,14 @@
|
||||
},
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@verdaccio/auth": "workspace:6.0.0-6-next.11",
|
||||
"@verdaccio/auth": "workspace:6.0.0-6-next.12",
|
||||
"@verdaccio/commons-api": "workspace:11.0.0-6-next.4",
|
||||
"@verdaccio/config": "workspace:6.0.0-6-next.8",
|
||||
"@verdaccio/core": "workspace:6.0.0-6-next.1",
|
||||
"@verdaccio/hooks": "workspace:6.0.0-6-next.6",
|
||||
"@verdaccio/logger": "workspace:6.0.0-6-next.4",
|
||||
"@verdaccio/middleware": "workspace:6.0.0-6-next.11",
|
||||
"@verdaccio/store": "workspace:6.0.0-6-next.12",
|
||||
"@verdaccio/hooks": "workspace:6.0.0-6-next.7",
|
||||
"@verdaccio/logger": "workspace:6.0.0-6-next.5",
|
||||
"@verdaccio/middleware": "workspace:6.0.0-6-next.12",
|
||||
"@verdaccio/store": "workspace:6.0.0-6-next.13",
|
||||
"@verdaccio/tarball": "workspace:11.0.0-6-next.7",
|
||||
"@verdaccio/utils": "workspace:6.0.0-6-next.6",
|
||||
"cookies": "0.8.0",
|
||||
@@ -57,7 +57,8 @@
|
||||
"semver": "7.3.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@verdaccio/server": "workspace:6.0.0-6-next.19",
|
||||
"@types/node": "16.9.1",
|
||||
"@verdaccio/server": "workspace:6.0.0-6-next.20",
|
||||
"@verdaccio/types": "workspace:11.0.0-6-next.8",
|
||||
"body-parser": "1.19.0",
|
||||
"lodash": "4.17.21",
|
||||
|
||||
@@ -1,124 +1,14 @@
|
||||
import { Transform, pipeline, PassThrough } from 'stream';
|
||||
import _ from 'lodash';
|
||||
import buildDebug from 'debug';
|
||||
import { Package } from '@verdaccio/types';
|
||||
import { logger } from '@verdaccio/logger';
|
||||
import { IAuth } from '@verdaccio/auth';
|
||||
import { searchUtils } from '@verdaccio/core';
|
||||
import { HTTP_STATUS, getInternalError } from '@verdaccio/commons-api';
|
||||
import { HTTP_STATUS } from '@verdaccio/commons-api';
|
||||
import { Storage } from '@verdaccio/store';
|
||||
import { Package } from '@verdaccio/types';
|
||||
|
||||
const debug = buildDebug('verdaccio:api:search');
|
||||
|
||||
type SearchResults = {
|
||||
objects: searchUtils.SearchItemPkg[];
|
||||
total: number;
|
||||
time: string;
|
||||
};
|
||||
|
||||
// const personMatch = (person, search) => {
|
||||
// if (typeof person === 'string') {
|
||||
// return person.includes(search);
|
||||
// }
|
||||
|
||||
// if (typeof person === 'object') {
|
||||
// for (const field of Object.values(person)) {
|
||||
// if (typeof field === 'string' && field.includes(search)) {
|
||||
// return true;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// return false;
|
||||
// };
|
||||
|
||||
// const matcher = function (query) {
|
||||
// const match = query.match(/author:(.*)/);
|
||||
// if (match !== null) {
|
||||
// return function (pkg) {
|
||||
// return personMatch(pkg.author, match[1]);
|
||||
// };
|
||||
// }
|
||||
|
||||
// // TODO: maintainer, keywords, boost-exact
|
||||
// // TODO implement some scoring system for freetext
|
||||
// return (pkg) => {
|
||||
// return ['name', 'displayName', 'description']
|
||||
// .map((k) => {
|
||||
// return pkg[k];
|
||||
// })
|
||||
// .filter((x) => {
|
||||
// return x !== undefined;
|
||||
// })
|
||||
// .some((txt) => {
|
||||
// return txt.includes(query);
|
||||
// });
|
||||
// };
|
||||
// };
|
||||
|
||||
function removeDuplicates(results) {
|
||||
const pkgNames: any[] = [];
|
||||
return results.filter((pkg) => {
|
||||
if (pkgNames.includes(pkg?.package?.name)) {
|
||||
return false;
|
||||
}
|
||||
pkgNames.push(pkg?.package?.name);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
function checkAccess(pkg: any, auth: any, remoteUser): Promise<Package | null> {
|
||||
return new Promise((resolve, reject) => {
|
||||
auth.allow_access({ packageName: pkg?.package?.name }, remoteUser, function (err, allowed) {
|
||||
if (err) {
|
||||
if (err.status && String(err.status).match(/^4\d\d$/)) {
|
||||
// auth plugin returns 4xx user error,
|
||||
// that's equivalent of !allowed basically
|
||||
allowed = false;
|
||||
return resolve(null);
|
||||
} else {
|
||||
reject(err);
|
||||
}
|
||||
} else {
|
||||
return resolve(allowed ? pkg : null);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
class TransFormResults extends Transform {
|
||||
public constructor(options) {
|
||||
super(options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform either array of packages or a single package into a stream of packages.
|
||||
* From uplinks the chunks are array but from local packages are objects.
|
||||
* @param {string} chunk
|
||||
* @param {string} encoding
|
||||
* @param {function} done
|
||||
* @returns {void}
|
||||
* @override
|
||||
*/
|
||||
public _transform(chunk, _encoding, callback) {
|
||||
if (_.isArray(chunk)) {
|
||||
(chunk as searchUtils.SearchItem[])
|
||||
.filter((pkgItem) => {
|
||||
debug(`streaming remote pkg name ${pkgItem?.package?.name}`);
|
||||
return true;
|
||||
})
|
||||
.forEach((pkgItem) => {
|
||||
this.push(pkgItem);
|
||||
});
|
||||
return callback();
|
||||
} else {
|
||||
debug(`streaming local pkg name ${chunk?.package?.name}`);
|
||||
this.push(chunk);
|
||||
return callback();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Endpoint for npm search v1
|
||||
* Empty value
|
||||
@@ -126,37 +16,40 @@ class TransFormResults extends Transform {
|
||||
* req: 'GET /-/v1/search?text=react&size=20&frpom=0&quality=0.65&popularity=0.98&maintenance=0.5'
|
||||
*/
|
||||
export default function (route, auth: IAuth, storage: Storage): void {
|
||||
function checkAccess(pkg: any, auth: any, remoteUser): Promise<Package | null> {
|
||||
return new Promise((resolve, reject) => {
|
||||
auth.allow_access({ packageName: pkg?.package?.name }, remoteUser, function (err, allowed) {
|
||||
if (err) {
|
||||
if (err.status && String(err.status).match(/^4\d\d$/)) {
|
||||
// auth plugin returns 4xx user error,
|
||||
// that's equivalent of !allowed basically
|
||||
allowed = false;
|
||||
return resolve(null);
|
||||
} else {
|
||||
reject(err);
|
||||
}
|
||||
} else {
|
||||
return resolve(allowed ? pkg : null);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
route.get('/-/v1/search', async (req, res, next) => {
|
||||
let [size, from] = ['size', 'from'].map((k) => req.query[k]);
|
||||
let data;
|
||||
|
||||
size = parseInt(size, 10) || 20;
|
||||
from = parseInt(from, 10) || 0;
|
||||
const data: any[] = [];
|
||||
const transformResults = new TransFormResults({ objectMode: true });
|
||||
|
||||
const streamPassThrough = new PassThrough({ objectMode: true });
|
||||
storage.searchManager?.search(streamPassThrough, {
|
||||
query: req.query,
|
||||
url: req.url,
|
||||
});
|
||||
|
||||
const outPutStream = new PassThrough({ objectMode: true });
|
||||
pipeline(streamPassThrough, transformResults, outPutStream, (err) => {
|
||||
if (err) {
|
||||
next(getInternalError(err ? err.message : 'unknown error'));
|
||||
} else {
|
||||
debug('Pipeline succeeded.');
|
||||
}
|
||||
});
|
||||
|
||||
outPutStream.on('data', (chunk) => {
|
||||
data.push(chunk);
|
||||
});
|
||||
|
||||
outPutStream.on('finish', async () => {
|
||||
try {
|
||||
data = await storage.searchManager?.search({
|
||||
query: req.query,
|
||||
url: req.url,
|
||||
});
|
||||
debug('stream finish');
|
||||
const checkAccessPromises: searchUtils.SearchItemPkg[] = await Promise.all(
|
||||
removeDuplicates(data).map((pkgItem) => {
|
||||
data.map((pkgItem) => {
|
||||
return checkAccess(pkgItem, auth, req.remote_user);
|
||||
})
|
||||
);
|
||||
@@ -166,13 +59,17 @@ export default function (route, auth: IAuth, storage: Storage): void {
|
||||
.slice(from, size);
|
||||
logger.debug(`search results ${final?.length}`);
|
||||
|
||||
const response: SearchResults = {
|
||||
const response: searchUtils.SearchResults = {
|
||||
objects: final,
|
||||
total: final.length,
|
||||
time: new Date().toUTCString(),
|
||||
};
|
||||
|
||||
res.status(HTTP_STATUS.OK).json(response);
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error({ error }, 'search endpoint has failed @{error.message}');
|
||||
next(next);
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# @verdaccio/auth
|
||||
|
||||
## 6.0.0-6-next.12
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [2c594910]
|
||||
- @verdaccio/logger@6.0.0-6-next.5
|
||||
- @verdaccio/loaders@6.0.0-6-next.5
|
||||
|
||||
## 6.0.0-6-next.11
|
||||
|
||||
### Major Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@verdaccio/auth",
|
||||
"version": "6.0.0-6-next.11",
|
||||
"version": "6.0.0-6-next.12",
|
||||
"description": "logger",
|
||||
"main": "./build/index.js",
|
||||
"types": "build/index.d.ts",
|
||||
@@ -41,8 +41,8 @@
|
||||
"dependencies": {
|
||||
"@verdaccio/commons-api": "workspace:11.0.0-6-next.4",
|
||||
"@verdaccio/config": "workspace:6.0.0-6-next.8",
|
||||
"@verdaccio/loaders": "workspace:6.0.0-6-next.4",
|
||||
"@verdaccio/logger": "workspace:6.0.0-6-next.4",
|
||||
"@verdaccio/loaders": "workspace:6.0.0-6-next.5",
|
||||
"@verdaccio/logger": "workspace:6.0.0-6-next.5",
|
||||
"@verdaccio/utils": "workspace:6.0.0-6-next.6",
|
||||
"debug": "4.3.2",
|
||||
"express": "4.17.1",
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
# @verdaccio/cli
|
||||
|
||||
## 6.0.0-6-next.22
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [2c594910]
|
||||
- @verdaccio/logger@6.0.0-6-next.5
|
||||
- @verdaccio/fastify-migration@6.0.0-6-next.13
|
||||
- @verdaccio/node-api@6.0.0-6-next.21
|
||||
|
||||
## 6.0.0-6-next.21
|
||||
|
||||
### Major Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@verdaccio/cli",
|
||||
"version": "6.0.0-6-next.21",
|
||||
"version": "6.0.0-6-next.22",
|
||||
"author": {
|
||||
"name": "Juan Picado",
|
||||
"email": "juanpicado19@gmail.com"
|
||||
@@ -45,9 +45,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@verdaccio/config": "workspace:6.0.0-6-next.8",
|
||||
"@verdaccio/logger": "workspace:6.0.0-6-next.4",
|
||||
"@verdaccio/node-api": "workspace:6.0.0-6-next.20",
|
||||
"@verdaccio/fastify-migration": "workspace:6.0.0-6-next.12",
|
||||
"@verdaccio/logger": "workspace:6.0.0-6-next.5",
|
||||
"@verdaccio/node-api": "workspace:6.0.0-6-next.21",
|
||||
"@verdaccio/fastify-migration": "workspace:6.0.0-6-next.13",
|
||||
"clipanion": "3.0.1",
|
||||
"envinfo": "7.8.1",
|
||||
"kleur": "3.0.3",
|
||||
|
||||
@@ -39,8 +39,7 @@
|
||||
"semver": "7.3.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@verdaccio/types": "workspace:11.0.0-6-next.8",
|
||||
"typedoc": "next"
|
||||
"@verdaccio/types": "workspace:11.0.0-6-next.8"
|
||||
},
|
||||
"scripts": {
|
||||
"clean": "rimraf ./build",
|
||||
@@ -49,8 +48,7 @@
|
||||
"build:types": "tsc --emitDeclarationOnly -p tsconfig.build.json",
|
||||
"build:js": "babel src/ --out-dir build/ --copy-files --extensions \".ts,.tsx\" --source-maps",
|
||||
"watch": "pnpm build:js -- --watch",
|
||||
"build": "pnpm run build:js && pnpm run build:types",
|
||||
"doc": "typedoc src/index.ts --tsconfig tsconfig.build.json"
|
||||
"build": "pnpm run build:js && pnpm run build:types"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
|
||||
@@ -26,6 +26,12 @@ export type Score = {
|
||||
detail: SearchMetrics;
|
||||
};
|
||||
|
||||
export type SearchResults = {
|
||||
objects: SearchItemPkg[];
|
||||
total: number;
|
||||
time: string;
|
||||
};
|
||||
|
||||
type PublisherMaintainer = {
|
||||
username: string;
|
||||
email: string;
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
# @verdaccio/fastify-migration
|
||||
|
||||
## 6.0.0-6-next.13
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [2c594910]
|
||||
- @verdaccio/logger@6.0.0-6-next.5
|
||||
- @verdaccio/auth@6.0.0-6-next.12
|
||||
- @verdaccio/store@6.0.0-6-next.13
|
||||
|
||||
## 6.0.0-6-next.12
|
||||
|
||||
### Major Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@verdaccio/fastify-migration",
|
||||
"version": "6.0.0-6-next.12",
|
||||
"version": "6.0.0-6-next.13",
|
||||
"description": "Fastify server migration package",
|
||||
"keywords": [
|
||||
"private",
|
||||
@@ -35,15 +35,16 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@verdaccio/config": "workspace:6.0.0-6-next.8",
|
||||
"@verdaccio/auth": "workspace:6.0.0-6-next.11",
|
||||
"@verdaccio/logger": "workspace:6.0.0-6-next.4",
|
||||
"@verdaccio/store": "workspace:6.0.0-6-next.12",
|
||||
"@verdaccio/auth": "workspace:6.0.0-6-next.12",
|
||||
"@verdaccio/logger": "workspace:6.0.0-6-next.5",
|
||||
"@verdaccio/store": "workspace:6.0.0-6-next.13",
|
||||
"core-js": "3.17.2",
|
||||
"debug": "4.3.2",
|
||||
"fastify": "3.20.2",
|
||||
"fastify-plugin": "3.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "16.9.1",
|
||||
"@verdaccio/types": "workspace:11.0.0-6-next.8",
|
||||
"ts-node": "10.2.1"
|
||||
},
|
||||
|
||||
25
packages/core/server/src/endpoints/search.ts
Normal file
25
packages/core/server/src/endpoints/search.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
/* eslint-disable no-console */
|
||||
/* eslint-disable no-invalid-this */
|
||||
import { logger } from '@verdaccio/logger';
|
||||
|
||||
async function searchRoute(fastify) {
|
||||
fastify.get('/-/v1/search', async (request, reply) => {
|
||||
// TODO: apply security layer here like in
|
||||
// packages/api/src/v1/search.ts
|
||||
// TODO: add validations for query, some parameters are mandatory
|
||||
// TODO: review which query fields are mandatory
|
||||
|
||||
const { url, query } = request;
|
||||
const storage = fastify.storage;
|
||||
|
||||
const data = await storage.searchManager?.search({
|
||||
query: query,
|
||||
url: url,
|
||||
});
|
||||
|
||||
logger.http('search endpoint');
|
||||
reply.code(200).send(data);
|
||||
});
|
||||
}
|
||||
|
||||
export default searchRoute;
|
||||
@@ -6,6 +6,7 @@ import buildDebug from 'debug';
|
||||
import fp from 'fastify-plugin';
|
||||
|
||||
import ping from './endpoints/ping';
|
||||
import search from './endpoints/search';
|
||||
import { storageService } from './plugins/storage';
|
||||
|
||||
const debug = buildDebug('verdaccio:fastify');
|
||||
@@ -22,6 +23,7 @@ async function startServer({ logger, config }) {
|
||||
app.register((instance, opts, done) => {
|
||||
instance.decorate('utility', new Map());
|
||||
instance.register(ping);
|
||||
instance.register(search);
|
||||
done();
|
||||
});
|
||||
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# @verdaccio/hooks
|
||||
|
||||
## 6.0.0-6-next.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [2c594910]
|
||||
- @verdaccio/logger@6.0.0-6-next.5
|
||||
|
||||
## 6.0.0-6-next.6
|
||||
|
||||
### Major Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@verdaccio/hooks",
|
||||
"version": "6.0.0-6-next.6",
|
||||
"version": "6.0.0-6-next.7",
|
||||
"description": "loaders logic",
|
||||
"main": "./build/index.js",
|
||||
"types": "build/index.d.ts",
|
||||
@@ -31,15 +31,16 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@verdaccio/commons-api": "workspace:11.0.0-6-next.4",
|
||||
"@verdaccio/logger": "workspace:6.0.0-6-next.4",
|
||||
"@verdaccio/logger": "workspace:6.0.0-6-next.5",
|
||||
"core-js": "3.17.2",
|
||||
"debug": "4.3.2",
|
||||
"handlebars": "4.7.7",
|
||||
"undici": "4.5.1",
|
||||
"undici": "4.4.7",
|
||||
"undici-fetch": "1.0.0-rc.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@verdaccio/auth": "workspace:6.0.0-6-next.11",
|
||||
"@types/node": "16.9.1",
|
||||
"@verdaccio/auth": "workspace:6.0.0-6-next.12",
|
||||
"@verdaccio/commons-api": "workspace:11.0.0-6-next.4",
|
||||
"@verdaccio/config": "workspace:6.0.0-6-next.8",
|
||||
"@verdaccio/types": "workspace:11.0.0-6-next.8"
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# @verdaccio/loaders
|
||||
|
||||
## 6.0.0-6-next.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [2c594910]
|
||||
- @verdaccio/logger@6.0.0-6-next.5
|
||||
|
||||
## 6.0.0-6-next.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@verdaccio/loaders",
|
||||
"version": "6.0.0-6-next.4",
|
||||
"version": "6.0.0-6-next.5",
|
||||
"description": "loaders logic",
|
||||
"main": "./build/index.js",
|
||||
"types": "build/index.d.ts",
|
||||
@@ -13,7 +13,7 @@
|
||||
"url": "https://github.com/verdaccio/verdaccio"
|
||||
},
|
||||
"dependencies": {
|
||||
"@verdaccio/logger": "workspace:6.0.0-6-next.4",
|
||||
"@verdaccio/logger": "workspace:6.0.0-6-next.5",
|
||||
"debug": "4.3.2",
|
||||
"lodash": "4.17.21"
|
||||
},
|
||||
|
||||
@@ -46,8 +46,8 @@
|
||||
"prettier-bytes": "1.0.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/pino": "6.3.11",
|
||||
"pino": "6.13.1"
|
||||
"@types/pino": "^6.3.3",
|
||||
"pino": "6.2.1"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# @verdaccio/logger
|
||||
|
||||
## 6.0.0-6-next.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 2c594910: do not show deprecation warning on default logger config
|
||||
|
||||
## 6.0.0-6-next.4
|
||||
|
||||
### Major Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@verdaccio/logger",
|
||||
"version": "6.0.0-6-next.4",
|
||||
"version": "6.0.0-6-next.5",
|
||||
"description": "logger",
|
||||
"main": "./build/index.js",
|
||||
"types": "build/index.d.ts",
|
||||
@@ -42,10 +42,10 @@
|
||||
"@verdaccio/logger-prettify": "workspace:6.0.0-alpha.3",
|
||||
"debug": "4.3.2",
|
||||
"lodash": "4.17.21",
|
||||
"pino": "6.13.1"
|
||||
"pino": "6.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/pino": "6.3.11",
|
||||
"@types/pino": "6.3.3",
|
||||
"@verdaccio/types": "workspace:11.0.0-6-next.8"
|
||||
},
|
||||
"funding": {
|
||||
|
||||
@@ -98,7 +98,7 @@ export type LoggerConfigItem = {
|
||||
|
||||
export type LoggerConfig = LoggerConfigItem[];
|
||||
|
||||
export function setup(options: LoggerConfig | LoggerConfigItem = [DEFAULT_LOGGER_CONF]) {
|
||||
export function setup(options: LoggerConfig | LoggerConfigItem = DEFAULT_LOGGER_CONF) {
|
||||
debug('setup logger');
|
||||
const isLegacyConf = Array.isArray(options);
|
||||
if (isLegacyConf) {
|
||||
|
||||
@@ -13,4 +13,29 @@ describe('logger', () => {
|
||||
// FIXME: check expect
|
||||
// expect(spyOn).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
test('throw deprecation warning if multiple loggers configured', () => {
|
||||
const spy = jest.spyOn(process, 'emitWarning');
|
||||
setup([
|
||||
{
|
||||
level: 'info',
|
||||
},
|
||||
{
|
||||
level: 'http',
|
||||
},
|
||||
]);
|
||||
expect(spy).toHaveBeenCalledWith(
|
||||
'deprecate: multiple logger configuration is deprecated, please check the migration guide.'
|
||||
);
|
||||
spy.mockRestore();
|
||||
});
|
||||
|
||||
test('regression: do not throw deprecation warning if no logger config is provided', () => {
|
||||
const spy = jest.spyOn(process, 'emitWarning');
|
||||
setup();
|
||||
expect(spy).not.toHaveBeenCalledWith(
|
||||
'deprecate: multiple logger configuration is deprecated, please check the migration guide.'
|
||||
);
|
||||
spy.mockRestore();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# @verdaccio/middleware
|
||||
|
||||
## 6.0.0-6-next.12
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [2c594910]
|
||||
- @verdaccio/logger@6.0.0-6-next.5
|
||||
- @verdaccio/auth@6.0.0-6-next.12
|
||||
|
||||
## 6.0.0-6-next.11
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@verdaccio/middleware",
|
||||
"version": "6.0.0-6-next.11",
|
||||
"version": "6.0.0-6-next.12",
|
||||
"description": "loaders logic",
|
||||
"main": "./build/index.js",
|
||||
"types": "build/index.d.ts",
|
||||
@@ -39,9 +39,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"debug": "4.3.2",
|
||||
"@verdaccio/auth": "workspace:6.0.0-6-next.11",
|
||||
"@verdaccio/auth": "workspace:6.0.0-6-next.12",
|
||||
"@verdaccio/commons-api": "workspace:11.0.0-6-next.4",
|
||||
"@verdaccio/logger": "workspace:6.0.0-6-next.4",
|
||||
"@verdaccio/logger": "workspace:6.0.0-6-next.5",
|
||||
"@verdaccio/utils": "workspace:6.0.0-6-next.6",
|
||||
"lodash": "4.17.21"
|
||||
},
|
||||
|
||||
@@ -6,7 +6,11 @@ import { parseConfigFile } from '@verdaccio/config';
|
||||
/**
|
||||
* Override the default.yaml configuration file with any new config provided.
|
||||
*/
|
||||
function configExample(externalConfig, configFile: string = 'default.yaml', location: string = '') {
|
||||
function configExample(
|
||||
externalConfig = {},
|
||||
configFile: string = 'default.yaml',
|
||||
location: string = ''
|
||||
) {
|
||||
const locationFile = location
|
||||
? path.join(location, configFile)
|
||||
: path.join(__dirname, `./config/yaml/${configFile}`);
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# @verdaccio/node-api
|
||||
|
||||
## 6.0.0-6-next.21
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [2c594910]
|
||||
- @verdaccio/logger@6.0.0-6-next.5
|
||||
- @verdaccio/server@6.0.0-6-next.20
|
||||
|
||||
## 6.0.0-6-next.20
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@verdaccio/node-api",
|
||||
"version": "6.0.0-6-next.20",
|
||||
"version": "6.0.0-6-next.21",
|
||||
"description": "node API",
|
||||
"main": "build/index.js",
|
||||
"types": "build/index.d.ts",
|
||||
@@ -41,13 +41,14 @@
|
||||
"dependencies": {
|
||||
"@verdaccio/commons-api": "workspace:11.0.0-6-next.4",
|
||||
"@verdaccio/config": "workspace:6.0.0-6-next.8",
|
||||
"@verdaccio/logger": "workspace:6.0.0-6-next.4",
|
||||
"@verdaccio/server": "workspace:6.0.0-6-next.19",
|
||||
"@verdaccio/logger": "workspace:6.0.0-6-next.5",
|
||||
"@verdaccio/server": "workspace:6.0.0-6-next.20",
|
||||
"core-js": "3.17.2",
|
||||
"debug": "4.3.2",
|
||||
"lodash": "4.17.21"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "16.9.1",
|
||||
"@verdaccio/mock": "workspace:6.0.0-6-next.9",
|
||||
"@verdaccio/types": "workspace:11.0.0-6-next.8",
|
||||
"jest-mock-process": "1.4.1",
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# @verdaccio/ui-theme
|
||||
|
||||
## 6.0.0-6-next.11
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 5fed1955: feat: integrate rematch for ui state storage
|
||||
|
||||
## 6.0.0-6-next.10
|
||||
|
||||
### Major Changes
|
||||
|
||||
@@ -1,94 +0,0 @@
|
||||
import i18n from 'i18next';
|
||||
import { initReactI18next } from 'react-i18next';
|
||||
|
||||
import translationCS from './translations/cs-CZ.json';
|
||||
import translationDE from './translations/de-DE.json';
|
||||
import translationEN from './translations/en-US.json';
|
||||
import translationES from './translations/es-ES.json';
|
||||
import translationFR from './translations/fr-FR.json';
|
||||
import translationJP from './translations/ja-JP.json';
|
||||
import translationKM from './translations/km-KH.json';
|
||||
import translationPT from './translations/pt-BR.json';
|
||||
import translationRU from './translations/ru-RU.json';
|
||||
import translationTR from './translations/tr-TR.json';
|
||||
import translationUA from './translations/uk-UA.json';
|
||||
import translationCN from './translations/zh-CN.json';
|
||||
import translatiobTW from './translations/zh-TW.json';
|
||||
|
||||
const languages = {
|
||||
'en-US': {
|
||||
translation: translationEN,
|
||||
},
|
||||
'cs-CZ': {
|
||||
translation: translationCS,
|
||||
},
|
||||
'pt-BR': {
|
||||
translation: translationPT,
|
||||
},
|
||||
'es-ES': {
|
||||
translation: translationES,
|
||||
},
|
||||
'de-DE': {
|
||||
translation: translationDE,
|
||||
},
|
||||
'fr-FR': {
|
||||
translation: translationFR,
|
||||
},
|
||||
'zh-CN': {
|
||||
translation: translationCN,
|
||||
},
|
||||
'ja-JP': {
|
||||
translation: translationJP,
|
||||
},
|
||||
'ru-RU': {
|
||||
translation: translationRU,
|
||||
},
|
||||
'tr-TR': {
|
||||
translation: translationTR,
|
||||
},
|
||||
'uk-UA': {
|
||||
translation: translationUA,
|
||||
},
|
||||
'km-KH': {
|
||||
translation: translationKM,
|
||||
},
|
||||
'zh-TW': {
|
||||
translation: translatiobTW,
|
||||
},
|
||||
};
|
||||
|
||||
type Language = keyof typeof languages;
|
||||
i18n
|
||||
// pass the i18n instance to react-i18next.
|
||||
.use(initReactI18next)
|
||||
// init i18next
|
||||
// for all options read: https://www.i18next.com/overview/configuration-options
|
||||
.init({
|
||||
// in case window.VEDACCIO_LANGUAGE is undefined,it will fall back to 'en-US'
|
||||
lng: window?.__VERDACCIO_BASENAME_UI_OPTIONS?.language || 'en-US',
|
||||
fallbackLng: 'en-US',
|
||||
whitelist: [
|
||||
'en-US',
|
||||
'cs-CZ',
|
||||
'pt-BR',
|
||||
'es-ES',
|
||||
'de-DE',
|
||||
'fr-FR',
|
||||
'zh-CN',
|
||||
'ja-JP',
|
||||
'ru-RU',
|
||||
'tr-TR',
|
||||
'uk-UA',
|
||||
'km-KH',
|
||||
'zh-TW',
|
||||
],
|
||||
load: 'currentOnly',
|
||||
resources: languages,
|
||||
debug: false,
|
||||
interpolation: {
|
||||
escapeValue: false, // react already safes from xss
|
||||
},
|
||||
});
|
||||
|
||||
export default i18n;
|
||||
export { Language };
|
||||
42
packages/plugins/ui-theme/jest/api/packages.json
Normal file
42
packages/plugins/ui-theme/jest/api/packages.json
Normal file
@@ -0,0 +1,42 @@
|
||||
[
|
||||
{
|
||||
"name": "test",
|
||||
"version": "1.0.22",
|
||||
"description": "test",
|
||||
"main": "src/index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"repository": {
|
||||
"type": "http",
|
||||
"url": "git+https://github.com/test/test.git"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": {
|
||||
"name": "",
|
||||
"email": "",
|
||||
"url": "",
|
||||
"avatar": "data:image/svg+xml;utf8,%3Csvg%20height%3D%22100%22%20viewBox%3D%22-27%2024%20100%20100%22%20width%3D%22100%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%3E%3Cdefs%3E%3Ccircle%20cx%3D%2223%22%20cy%3D%2274%22%20id%3D%22a%22%20r%3D%2250%22%2F%3E%3C%2Fdefs%3E%3Cuse%20fill%3D%22%23F5EEE5%22%20overflow%3D%22visible%22%20xlink%3Ahref%3D%22%23a%22%2F%3E%3CclipPath%20id%3D%22b%22%3E%3Cuse%20overflow%3D%22visible%22%20xlink%3Ahref%3D%22%23a%22%2F%3E%3C%2FclipPath%3E%3Cg%20clip-path%3D%22url(%23b)%22%3E%3Cdefs%3E%3Cpath%20d%3D%22M36%2095.9c0%204%204.7%205.2%207.1%205.8%207.6%202%2022.8%205.9%2022.8%205.9%203.2%201.1%205.7%203.5%207.1%206.6v9.8H-27v-9.8c1.3-3.1%203.9-5.5%207.1-6.6%200%200%2015.2-3.9%2022.8-5.9%202.4-.6%207.1-1.8%207.1-5.8V85h26v10.9z%22%20id%3D%22c%22%2F%3E%3C%2Fdefs%3E%3Cuse%20fill%3D%22%23E6C19C%22%20overflow%3D%22visible%22%20xlink%3Ahref%3D%22%23c%22%2F%3E%3CclipPath%20id%3D%22d%22%3E%3Cuse%20overflow%3D%22visible%22%20xlink%3Ahref%3D%22%23c%22%2F%3E%3C%2FclipPath%3E%3Cpath%20clip-path%3D%22url(%23d)%22%20d%3D%22M23.2%2035h.2c3.3%200%208.2.2%2011.4%202%203.3%201.9%207.3%205.6%208.5%2012.1%202.4%2013.7-2.1%2035.4-6.3%2042.4-4%206.7-9.8%209.2-13.5%209.4H23h-.1c-3.7-.2-9.5-2.7-13.5-9.4-4.2-7-8.7-28.7-6.3-42.4%201.2-6.5%205.2-10.2%208.5-12.1%203.2-1.8%208.1-2%2011.4-2h.2z%22%20fill%3D%22%23D4B08C%22%2F%3E%3C%2Fg%3E%3Cpath%20d%3D%22M22.6%2040c19.1%200%2020.7%2013.8%2020.8%2015.1%201.1%2011.9-3%2028.1-6.8%2033.7-4%205.9-9.8%208.1-13.5%208.3h-.5c-3.8-.3-9.6-2.5-13.6-8.4-3.8-5.6-7.9-21.8-6.8-33.8C2.3%2053.7%203.5%2040%2022.6%2040z%22%20fill%3D%22%23F2CEA5%22%2F%3E%3C%2Fsvg%3E"
|
||||
},
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"lodash": "^4.17.21"
|
||||
},
|
||||
"readmeFilename": "README.md",
|
||||
"bugs": {
|
||||
"url": "https://github.com/test/test/issues"
|
||||
},
|
||||
"homepage": "https://github.com/test/test#readme",
|
||||
"_id": "test@1.0.22",
|
||||
"_nodeVersion": "14.17.4",
|
||||
"_npmVersion": "7.20.5",
|
||||
"dist": {
|
||||
"integrity": "sha512-2IDD0lLzGUL7YJ+17Oh9VtbOwdKLqBLS+ZFATDXi5R22TL2hZ9LBFE10bzsDovNc4xtgwZAk1/K+5LHTye4ztg==",
|
||||
"shasum": "c9152f57636bce762ccb5a83113c42a5831579bc",
|
||||
"tarball": "http://localhost:4873/test/-/test-1.0.22.tgz"
|
||||
},
|
||||
"contributors": [],
|
||||
"time": "2021-08-14T20:15:19.336Z",
|
||||
"users": {}
|
||||
}
|
||||
]
|
||||
@@ -10,9 +10,9 @@ module.exports = Object.assign({}, config, {
|
||||
'^.+\\.(js|ts|tsx)$': 'babel-jest',
|
||||
},
|
||||
moduleFileExtensions: ['js', 'ts', 'tsx'],
|
||||
testURL: 'http://localhost',
|
||||
testURL: 'http://localhost:9000/',
|
||||
rootDir: '..',
|
||||
setupFilesAfterEnv: ['@testing-library/jest-dom/extend-expect'],
|
||||
setupFilesAfterEnv: ['@testing-library/jest-dom/extend-expect', '<rootDir>/jest/setup-env.ts'],
|
||||
setupFiles: ['<rootDir>/jest/setup.ts'],
|
||||
transformIgnorePatterns: ['<rootDir>/node_modules/(?!react-syntax-highlighter)'],
|
||||
modulePathIgnorePatterns: [
|
||||
|
||||
9
packages/plugins/ui-theme/jest/server-handlers.ts
Normal file
9
packages/plugins/ui-theme/jest/server-handlers.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { rest } from 'msw';
|
||||
|
||||
const packagesPayload = require('./api/packages.json');
|
||||
|
||||
export const handlers = [
|
||||
rest.get('http://localhost:9000/-/verdaccio/packages', (req, res, ctx) => {
|
||||
return res(ctx.json(packagesPayload));
|
||||
}),
|
||||
];
|
||||
6
packages/plugins/ui-theme/jest/server.ts
Normal file
6
packages/plugins/ui-theme/jest/server.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { setupServer } from 'msw/node';
|
||||
|
||||
import { handlers } from './server-handlers';
|
||||
|
||||
const server = setupServer(...handlers);
|
||||
export { server };
|
||||
15
packages/plugins/ui-theme/jest/setup-env.ts
Normal file
15
packages/plugins/ui-theme/jest/setup-env.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import '@testing-library/jest-dom/extend-expect';
|
||||
import 'whatwg-fetch';
|
||||
import '@testing-library/jest-dom';
|
||||
|
||||
import { server } from './server';
|
||||
|
||||
beforeAll(() => {
|
||||
server.listen({
|
||||
onUnhandledRequest: 'warn',
|
||||
});
|
||||
});
|
||||
afterEach(() => server.resetHandlers());
|
||||
afterAll(() => {
|
||||
server.close();
|
||||
});
|
||||
@@ -2,29 +2,24 @@
|
||||
* Setup configuration for Jest
|
||||
* This file includes global settings for the JEST environment.
|
||||
*/
|
||||
import { GlobalWithFetchMock } from 'jest-fetch-mock';
|
||||
import 'mutationobserver-shim';
|
||||
|
||||
// @ts-ignore : Property '__VERDACCIO_BASENAME_UI_OPTIONS' does not exist on type 'Global'.
|
||||
global.__VERDACCIO_BASENAME_UI_OPTIONS = {
|
||||
base: 'http://localhost',
|
||||
base: 'http://localhost:9000/',
|
||||
protocol: 'http',
|
||||
host: 'localhost',
|
||||
primaryColor: '#4b5e40',
|
||||
url_prefix: '',
|
||||
darkMode: false,
|
||||
language: 'en-US',
|
||||
uri: 'http://localhost:4873',
|
||||
uri: 'http://localhost:9000/',
|
||||
pkgManagers: ['pnpm', 'yarn', 'npm'],
|
||||
title: 'Verdaccio Dev UI',
|
||||
scope: '',
|
||||
version: 'v1.0.0',
|
||||
};
|
||||
|
||||
const customGlobal: GlobalWithFetchMock = global as GlobalWithFetchMock;
|
||||
customGlobal.fetch = require('jest-fetch-mock');
|
||||
customGlobal.fetchMock = customGlobal.fetch;
|
||||
|
||||
// mocking few DOM methods
|
||||
// @ts-ignore : Property 'document' does not exist on type 'Global'.
|
||||
if (global.document) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@verdaccio/ui-theme",
|
||||
"version": "6.0.0-6-next.10",
|
||||
"version": "6.0.0-6-next.11",
|
||||
"description": "Verdaccio User Interface",
|
||||
"author": {
|
||||
"name": "Verdaccio Contributors",
|
||||
@@ -17,6 +17,13 @@
|
||||
"npm": ">=6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "17.0.19",
|
||||
"@types/react-autosuggest": "10.1.5",
|
||||
"@types/react-dom": "17.0.9",
|
||||
"@types/react-helmet": "6.1.2",
|
||||
"@types/redux": "3.6.0",
|
||||
"@types/react-router-dom": "5.1.8",
|
||||
"@types/react-virtualized": "9.21.13",
|
||||
"@emotion/core": "10.1.1",
|
||||
"@emotion/jest": "11.3.0",
|
||||
"@emotion/styled": "10.0.27",
|
||||
@@ -24,10 +31,12 @@
|
||||
"@material-ui/core": "4.11.4",
|
||||
"@material-ui/icons": "4.11.2",
|
||||
"@material-ui/styles": "4.11.4",
|
||||
"@testing-library/dom": "8.2.0",
|
||||
"@rematch/core": "2.1.0",
|
||||
"@rematch/loading": "2.1.0",
|
||||
"@testing-library/dom": "8.5.0",
|
||||
"@testing-library/jest-dom": "5.14.1",
|
||||
"@testing-library/react": "12.0.0",
|
||||
"@verdaccio/node-api": "workspace:6.0.0-6-next.20",
|
||||
"@testing-library/react": "12.1.0",
|
||||
"@verdaccio/node-api": "workspace:6.0.0-6-next.21",
|
||||
"autosuggest-highlight": "3.1.1",
|
||||
"babel-loader": "8.2.2",
|
||||
"babel-plugin-dynamic-import-node": "2.3.3",
|
||||
@@ -42,8 +51,9 @@
|
||||
"harmony-reflect": "1.6.2",
|
||||
"history": "4.10.1",
|
||||
"html-webpack-plugin": "5.3.2",
|
||||
"i18next": "20.6.0",
|
||||
"i18next": "20.6.1",
|
||||
"in-publish": "2.0.1",
|
||||
"country-flag-icons": "1.4.10",
|
||||
"js-base64": "3.6.1",
|
||||
"js-yaml": "3.14.1",
|
||||
"localstorage-memory": "1.0.3",
|
||||
@@ -58,13 +68,16 @@
|
||||
"react": "17.0.2",
|
||||
"react-autosuggest": "10.1.0",
|
||||
"react-dom": "17.0.2",
|
||||
"react-hook-form": "7.14.2",
|
||||
"react-hook-form": "7.15.3",
|
||||
"react-hot-loader": "4.13.0",
|
||||
"react-i18next": "11.11.4",
|
||||
"react-i18next": "11.12.0",
|
||||
"react-router": "5.2.1",
|
||||
"react-router-dom": "5.3.0",
|
||||
"react-virtualized": "9.22.3",
|
||||
"react-redux": "7.2.1",
|
||||
"redux": "4.1.1",
|
||||
"rimraf": "3.0.2",
|
||||
"msw": "0.35.0",
|
||||
"standard-version": "9.3.1",
|
||||
"style-loader": "3.2.1",
|
||||
"stylelint": "13.13.1",
|
||||
@@ -123,7 +136,7 @@
|
||||
"type-check": "tsc --noEmit -p tsconfig.build.json",
|
||||
"start": "babel-node tools/dev.server.js",
|
||||
"test:clean": "jest --clearCache",
|
||||
"test": "cross-env BABEL_ENV=test cross-env NODE_ENV=test cross-env TZ=UTC jest --config ./jest/jest.config.js --maxWorkers 2 --passWithNoTests",
|
||||
"test": "cross-env BABEL_ENV=test cross-env NODE_ENV=test cross-env TZ=UTC jest --config ./jest/jest.config.js --runInBand",
|
||||
"test:update-snapshot": "yarn run test -- -u",
|
||||
"test:size": "bundlesize",
|
||||
"lint": "pnpm lint:js && pnpm lint:css",
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
import React from 'react';
|
||||
|
||||
import storage from 'verdaccio-ui/utils/storage';
|
||||
import { render, waitFor, fireEvent } from 'verdaccio-ui/utils/test-react-testing-library';
|
||||
import {
|
||||
renderWithStore,
|
||||
act,
|
||||
waitFor,
|
||||
fireEvent,
|
||||
screen,
|
||||
} from 'verdaccio-ui/utils/test-react-testing-library';
|
||||
|
||||
// eslint-disable-next-line jest/no-mocks-import
|
||||
import { generateTokenWithTimeRange } from '../../jest/unit/components/__mocks__/token';
|
||||
import { store } from '../store';
|
||||
|
||||
import App from './App';
|
||||
|
||||
@@ -30,68 +36,88 @@ jest.mock('verdaccio-ui/utils/storage', () => {
|
||||
return new LocalStorageMock();
|
||||
});
|
||||
|
||||
jest.mock('verdaccio-ui/providers/API/api', () => ({
|
||||
// eslint-disable-next-line jest/no-mocks-import
|
||||
request: require('../../jest/unit/components/__mocks__/api').default.request,
|
||||
}));
|
||||
// force the windows to expand to display items
|
||||
// https://github.com/bvaughn/react-virtualized/issues/493#issuecomment-640084107
|
||||
jest.spyOn(HTMLElement.prototype, 'offsetHeight', 'get').mockReturnValue(600);
|
||||
jest.spyOn(HTMLElement.prototype, 'offsetWidth', 'get').mockReturnValue(600);
|
||||
|
||||
/* eslint-disable react/jsx-no-bind*/
|
||||
describe('<App />', () => {
|
||||
// test('should display the Header component ', async () => {
|
||||
// const { queryByTestId } = render(<App />);
|
||||
//
|
||||
// expect(queryByTestId('loading')).toBeTruthy();
|
||||
//
|
||||
// // wait for the Header component appearance and return the element
|
||||
// const headerElement = await waitFor(() => queryByTestId('header'));
|
||||
// expect(headerElement).toBeTruthy();
|
||||
// });
|
||||
describe('login - log out', () => {
|
||||
test('handleLogout - logouts the user and clear localstorage', async () => {
|
||||
const { queryByTestId } = renderWithStore(<App />, store);
|
||||
store.dispatch.login.logInUser({
|
||||
username: 'verdaccio',
|
||||
token: generateTokenWithTimeRange(24),
|
||||
});
|
||||
|
||||
test('handleLogout - logouts the user and clear localstorage', async () => {
|
||||
storage.setItem('username', 'verdaccio');
|
||||
storage.setItem('token', generateTokenWithTimeRange(24));
|
||||
// wait for the Account's circle element component appearance and return the element
|
||||
const accountCircleElement = await waitFor(() => queryByTestId('logInDialogIcon'));
|
||||
expect(accountCircleElement).toBeTruthy();
|
||||
|
||||
const { queryByTestId } = render(<App />);
|
||||
if (accountCircleElement) {
|
||||
fireEvent.click(accountCircleElement);
|
||||
|
||||
// wait for the Account's circle element component appearance and return the element
|
||||
const accountCircleElement = await waitFor(() => queryByTestId('header--menu-accountcircle'));
|
||||
expect(accountCircleElement).toBeTruthy();
|
||||
// wait for the Button's logout element component appearance and return the element
|
||||
const buttonLogoutElement = await waitFor(() => queryByTestId('logOutDialogIcon'));
|
||||
expect(buttonLogoutElement).toBeTruthy();
|
||||
|
||||
if (accountCircleElement) {
|
||||
fireEvent.click(accountCircleElement);
|
||||
if (buttonLogoutElement) {
|
||||
fireEvent.click(buttonLogoutElement);
|
||||
|
||||
// wait for the Button's logout element component appearance and return the element
|
||||
const buttonLogoutElement = await waitFor(() => queryByTestId('header--button-logout'));
|
||||
expect(buttonLogoutElement).toBeTruthy();
|
||||
|
||||
if (buttonLogoutElement) {
|
||||
fireEvent.click(buttonLogoutElement);
|
||||
|
||||
expect(queryByTestId('greetings-label')).toBeFalsy();
|
||||
expect(queryByTestId('greetings-label')).toBeFalsy();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test('isUserAlreadyLoggedIn: token already available in storage', async () => {
|
||||
const { queryByTestId, queryAllByText } = renderWithStore(<App />, store);
|
||||
store.dispatch.login.logInUser({
|
||||
username: 'verdaccio',
|
||||
token: generateTokenWithTimeRange(24),
|
||||
});
|
||||
|
||||
// wait for the Account's circle element component appearance and return the element
|
||||
const accountCircleElement = await waitFor(() => queryByTestId('logInDialogIcon'));
|
||||
expect(accountCircleElement).toBeTruthy();
|
||||
|
||||
if (accountCircleElement) {
|
||||
fireEvent.click(accountCircleElement);
|
||||
|
||||
// wait for the Greeting's label element component appearance and return the element
|
||||
const greetingsLabelElement = await waitFor(() => queryByTestId('greetings-label'));
|
||||
expect(greetingsLabelElement).toBeTruthy();
|
||||
|
||||
if (greetingsLabelElement) {
|
||||
expect(queryAllByText('verdaccio')).toBeTruthy();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test('isUserAlreadyLoggedIn: token already available in storage', async () => {
|
||||
storage.setItem('username', 'verdaccio');
|
||||
storage.setItem('token', generateTokenWithTimeRange(24));
|
||||
describe('list packages', () => {
|
||||
test('should display the Header component', async () => {
|
||||
renderWithStore(<App />, store);
|
||||
|
||||
const { queryByTestId, queryAllByText } = render(<App />);
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByTestId('loading')).toBeTruthy();
|
||||
});
|
||||
|
||||
// wait for the Account's circle element component appearance and return the element
|
||||
const accountCircleElement = await waitFor(() => queryByTestId('header--menu-accountcircle'));
|
||||
expect(accountCircleElement).toBeTruthy();
|
||||
// wait for the Header component appearance and return the element
|
||||
const headerElement = await waitFor(() => screen.queryByTestId('header'));
|
||||
expect(headerElement).toBeTruthy();
|
||||
});
|
||||
|
||||
if (accountCircleElement) {
|
||||
fireEvent.click(accountCircleElement);
|
||||
test('should display package lists', async () => {
|
||||
act(() => {
|
||||
renderWithStore(<App />, store);
|
||||
});
|
||||
|
||||
// wait for the Greeting's label element component appearance and return the element
|
||||
const greetingsLabelElement = await waitFor(() => queryByTestId('greetings-label'));
|
||||
expect(greetingsLabelElement).toBeTruthy();
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('package-item-list')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
if (greetingsLabelElement) {
|
||||
expect(queryAllByText('verdaccio')).toBeTruthy();
|
||||
}
|
||||
}
|
||||
expect(store.getState().packages.response).toHaveLength(1);
|
||||
}, 10000);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
/* eslint-disable react/jsx-max-depth */
|
||||
import styled from '@emotion/styled';
|
||||
import isNil from 'lodash/isNil';
|
||||
import React, { useState, useEffect, Suspense } from 'react';
|
||||
import React, { useEffect, Suspense } from 'react';
|
||||
import { Router } from 'react-router-dom';
|
||||
|
||||
import Box from 'verdaccio-ui/components/Box';
|
||||
@@ -9,15 +8,12 @@ import Loading from 'verdaccio-ui/components/Loading';
|
||||
import loadDayJSLocale from 'verdaccio-ui/design-tokens/load-dayjs-locale';
|
||||
import StyleBaseline from 'verdaccio-ui/design-tokens/StyleBaseline';
|
||||
import { Theme } from 'verdaccio-ui/design-tokens/theme';
|
||||
import { isTokenExpire } from 'verdaccio-ui/utils/login';
|
||||
import storage from 'verdaccio-ui/utils/storage';
|
||||
|
||||
import AppContextProvider from './AppContextProvider';
|
||||
import AppRoute, { history } from './AppRoute';
|
||||
import Footer from './Footer';
|
||||
import Header from './Header';
|
||||
|
||||
import '../../i18n/config';
|
||||
import '../i18n/config';
|
||||
|
||||
const StyledBox = styled(Box)<{ theme?: Theme }>(({ theme }) => ({
|
||||
backgroundColor: theme?.palette.background.default,
|
||||
@@ -32,35 +28,8 @@ const StyledBoxContent = styled(Box)<{ theme?: Theme }>(({ theme }) => ({
|
||||
},
|
||||
}));
|
||||
|
||||
/* eslint-disable react/jsx-no-bind */
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
const App: React.FC = () => {
|
||||
const [user, setUser] = useState<undefined | { username: string | null }>();
|
||||
/**
|
||||
* Logout user
|
||||
* Required by: <Header />
|
||||
*/
|
||||
const logout = () => {
|
||||
storage.removeItem('username');
|
||||
storage.removeItem('token');
|
||||
setUser(undefined);
|
||||
};
|
||||
|
||||
const checkUserAlreadyLoggedIn = () => {
|
||||
// checks for token validity
|
||||
const token = storage.getItem('token');
|
||||
const username = storage.getItem('username');
|
||||
|
||||
if (isTokenExpire(token) || isNil(username)) {
|
||||
logout();
|
||||
return;
|
||||
}
|
||||
|
||||
setUser({ username });
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
checkUserAlreadyLoggedIn();
|
||||
loadDayJSLocale();
|
||||
}, []);
|
||||
|
||||
@@ -70,12 +39,10 @@ const App: React.FC = () => {
|
||||
<StyledBox display="flex" flexDirection="column" height="100%">
|
||||
<>
|
||||
<Router history={history}>
|
||||
<AppContextProvider user={user}>
|
||||
<Header />
|
||||
<StyledBoxContent flexGrow={1}>
|
||||
<AppRoute />
|
||||
</StyledBoxContent>
|
||||
</AppContextProvider>
|
||||
<Header />
|
||||
<StyledBoxContent flexGrow={1}>
|
||||
<AppRoute />
|
||||
</StyledBoxContent>
|
||||
</Router>
|
||||
<Footer />
|
||||
</>
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
import { createContext } from 'react';
|
||||
|
||||
export interface AppProps {
|
||||
user?: User;
|
||||
scope: string;
|
||||
}
|
||||
|
||||
export interface User {
|
||||
username: string | null;
|
||||
}
|
||||
|
||||
export interface AppContextProps extends AppProps {
|
||||
setUser: (user?: User) => void;
|
||||
}
|
||||
|
||||
const AppContext = createContext<undefined | AppContextProps>(undefined);
|
||||
|
||||
export default AppContext;
|
||||
@@ -1,44 +0,0 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
|
||||
import { useConfig } from 'verdaccio-ui/providers/config';
|
||||
|
||||
import AppContext, { AppProps, User } from './AppContext';
|
||||
|
||||
interface Props {
|
||||
user?: User;
|
||||
}
|
||||
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
const AppContextProvider: React.FC<Props> = ({ children, user }) => {
|
||||
const { configOptions } = useConfig();
|
||||
const [state, setState] = useState<AppProps>({
|
||||
scope: configOptions.scope ?? '',
|
||||
user,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
setState({
|
||||
...state,
|
||||
user,
|
||||
});
|
||||
}, [user]);
|
||||
|
||||
const setUser = (user?: User) => {
|
||||
setState({
|
||||
...state,
|
||||
user,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<AppContext.Provider
|
||||
value={{
|
||||
...state,
|
||||
setUser,
|
||||
}}>
|
||||
{children}
|
||||
</AppContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export default AppContextProvider;
|
||||
@@ -1,9 +1,7 @@
|
||||
import { createBrowserHistory } from 'history';
|
||||
import React, { useContext } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import React from 'react';
|
||||
import { Route as ReactRouterDomRoute, Switch, Router } from 'react-router-dom';
|
||||
|
||||
import AppContext from './AppContext';
|
||||
import loadable from './utils/loadable';
|
||||
|
||||
const NotFound = loadable(
|
||||
@@ -28,22 +26,11 @@ export const history = createBrowserHistory({
|
||||
});
|
||||
|
||||
const AppRoute: React.FC = () => {
|
||||
const appContext = useContext(AppContext);
|
||||
const { t } = useTranslation();
|
||||
|
||||
if (!appContext) {
|
||||
throw Error(t('app-context-not-correct-used'));
|
||||
}
|
||||
|
||||
const { user } = appContext;
|
||||
|
||||
const isUserLoggedIn = user?.username;
|
||||
|
||||
return (
|
||||
<Router history={history}>
|
||||
<Switch>
|
||||
<ReactRouterDomRoute exact={true} path={Route.ROOT}>
|
||||
<HomePage isUserLoggedIn={!!isUserLoggedIn} />
|
||||
<HomePage />
|
||||
</ReactRouterDomRoute>
|
||||
<ReactRouterDomRoute exact={true} path={Route.PACKAGE}>
|
||||
<VersionContextProvider>
|
||||
|
||||
@@ -1,18 +1,11 @@
|
||||
/* eslint-disable react/jsx-max-depth */
|
||||
/* eslint-disable react/jsx-pascal-case */
|
||||
import styled from '@emotion/styled';
|
||||
import FlagsIcon from 'country-flag-icons/react/3x2';
|
||||
import React from 'react';
|
||||
import { useTranslation, Trans } from 'react-i18next';
|
||||
|
||||
import {
|
||||
Austria,
|
||||
Brazil,
|
||||
Earth,
|
||||
Nicaragua,
|
||||
Spain,
|
||||
Germany,
|
||||
India,
|
||||
China,
|
||||
Taiwan,
|
||||
} from 'verdaccio-ui/components/Icons';
|
||||
import { Earth } from 'verdaccio-ui/components/Icons';
|
||||
import Logo from 'verdaccio-ui/components/Logo';
|
||||
import { Theme } from 'verdaccio-ui/design-tokens/theme';
|
||||
import { useConfig } from 'verdaccio-ui/providers/config';
|
||||
@@ -32,14 +25,30 @@ const Footer = () => {
|
||||
<ToolTip>
|
||||
<StyledEarth />
|
||||
<Flags>
|
||||
<Spain />
|
||||
<Nicaragua />
|
||||
<India />
|
||||
<Brazil />
|
||||
<China />
|
||||
<Austria />
|
||||
<Germany />
|
||||
<Taiwan />
|
||||
<Icon>
|
||||
<FlagsIcon.ES />
|
||||
</Icon>
|
||||
<Icon>
|
||||
<FlagsIcon.NI />
|
||||
</Icon>
|
||||
<Icon>
|
||||
<FlagsIcon.IN />
|
||||
</Icon>
|
||||
<Icon>
|
||||
<FlagsIcon.BR />
|
||||
</Icon>
|
||||
<Icon>
|
||||
<FlagsIcon.CN />
|
||||
</Icon>
|
||||
<Icon>
|
||||
<FlagsIcon.AU />
|
||||
</Icon>
|
||||
<Icon>
|
||||
<FlagsIcon.DE />
|
||||
</Icon>
|
||||
<Icon>
|
||||
<FlagsIcon.TW />
|
||||
</Icon>
|
||||
</Flags>
|
||||
</ToolTip>
|
||||
</Left>
|
||||
@@ -87,6 +96,10 @@ const Flags = styled('span')<{ theme?: Theme }>(({ theme }) => ({
|
||||
},
|
||||
}));
|
||||
|
||||
const Icon = styled('div')({
|
||||
width: '10px',
|
||||
});
|
||||
|
||||
const ToolTip = styled('span')({
|
||||
position: 'relative',
|
||||
height: '18px',
|
||||
|
||||
@@ -111,8 +111,7 @@ exports[`<Footer /> component should load the initial state of Footer component
|
||||
}
|
||||
|
||||
.emotion-15 {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
width: 10px;
|
||||
}
|
||||
|
||||
.emotion-31 {
|
||||
@@ -211,344 +210,235 @@ exports[`<Footer /> component should load the initial state of Footer component
|
||||
<span
|
||||
class="emotion-13 emotion-14"
|
||||
>
|
||||
<svg
|
||||
class="MuiSvgIcon-root emotion-15 emotion-12"
|
||||
focusable="false"
|
||||
role="img"
|
||||
viewBox="0 0 45 45"
|
||||
<div
|
||||
class="emotion-15 emotion-16"
|
||||
>
|
||||
<defs>
|
||||
<clippath
|
||||
id="prefix__a"
|
||||
>
|
||||
<path
|
||||
d="M0 36h36V0H0v36z"
|
||||
/>
|
||||
</clippath>
|
||||
</defs>
|
||||
<g
|
||||
clip-path="url(#prefix__a)"
|
||||
transform="matrix(1.25 0 0 -1.25 0 45)"
|
||||
<svg
|
||||
viewBox="0 85.333 512 341.333"
|
||||
>
|
||||
<path
|
||||
d="M36 9a4 4 0 00-4-4H4a4 4 0 00-4 4v18a4 4 0 004 4h28a4 4 0 004-4V9z"
|
||||
fill="#c60a1d"
|
||||
d="M0 85.331h512v341.337H0z"
|
||||
fill="#FFDA44"
|
||||
/>
|
||||
<path
|
||||
d="M36 12H0v12h36V12z"
|
||||
fill="#ffc400"
|
||||
d="M0 85.331h512v113.775H0zm0 227.551h512v113.775H0z"
|
||||
fill="#D80027"
|
||||
/>
|
||||
<path
|
||||
d="M9 19v-3a3 3 0 116 0v3H9z"
|
||||
fill="#ea596e"
|
||||
/>
|
||||
<path
|
||||
d="M12 17h3v3h-3v-3z"
|
||||
fill="#f4a2b2"
|
||||
/>
|
||||
<path
|
||||
d="M12 17H9v3h3v-3z"
|
||||
fill="#dd2e44"
|
||||
/>
|
||||
<path
|
||||
d="M15 21.5c0-.829-1.343-1.5-3-1.5s-3 .671-3 1.5 1.343 1.5 3 1.5 3-.671 3-1.5"
|
||||
fill="#ea596e"
|
||||
/>
|
||||
<path
|
||||
d="M15 22.25c0 .414-1.343.75-3 .75s-3-.336-3-.75 1.343-.75 3-.75 3 .336 3 .75"
|
||||
fill="#ffac33"
|
||||
/>
|
||||
<path
|
||||
d="M7 13h1v7H7v-7zm10 0h-1v7h1v-7z"
|
||||
fill="#99aab5"
|
||||
/>
|
||||
<path
|
||||
d="M9 13H6v1h3v-1zm9 0h-3v1h3v-1zM8 20H7v1h1v-1zm9 0h-1v1h1v-1z"
|
||||
fill="#66757f"
|
||||
/>
|
||||
</g>
|
||||
<title>
|
||||
Spain
|
||||
</title>
|
||||
</svg>
|
||||
<svg
|
||||
class="MuiSvgIcon-root emotion-15 emotion-12"
|
||||
focusable="false"
|
||||
role="img"
|
||||
viewBox="0 0 512 512"
|
||||
</svg>
|
||||
</div>
|
||||
<div
|
||||
class="emotion-15 emotion-16"
|
||||
>
|
||||
<path
|
||||
d="M512 384c0 31.418-25.473 56.889-56.889 56.889H56.89C25.472 440.889 0 415.417 0 384V128c0-31.418 25.472-56.889 56.889-56.889H455.11C486.53 71.111 512 96.584 512 128v256z"
|
||||
fill="#265fb5"
|
||||
/>
|
||||
<path
|
||||
d="M512 327.111H0V184.89h512v142.22z"
|
||||
fill="#eee"
|
||||
/>
|
||||
<path
|
||||
d="M320.811 256c0 35.797-29.014 64.811-64.811 64.811-35.783 0-64.811-29.014-64.811-64.811s29.027-64.811 64.811-64.811c35.797 0 64.811 29.013 64.811 64.811"
|
||||
fill="#a9bf4c"
|
||||
/>
|
||||
<path
|
||||
d="M312.889 256c0 31.418-25.473 56.889-56.889 56.889S199.111 287.416 199.111 256s25.473-56.889 56.889-56.889 56.889 25.471 56.889 56.889"
|
||||
fill="#eee"
|
||||
/>
|
||||
<path
|
||||
d="M209.891 286.649l45.909-79.517 45.909 79.517H209.89z"
|
||||
fill="#265fb5"
|
||||
/>
|
||||
<path
|
||||
d="M215.04 283.591l40.818-70.685 40.803 70.685H215.04z"
|
||||
fill="#55acee"
|
||||
/>
|
||||
<path
|
||||
d="M215.04 283.591l9.841-17.052 61.483-.783 10.297 17.835H215.04z"
|
||||
fill="#bbddf5"
|
||||
/>
|
||||
<path
|
||||
d="M222.891 272.441l15.331-12.445 6.67 7.553 5.774-6.215 4.893 4.892 4.665-5.12 5.561 5.12 5.106-5.334 4.665 5.334 4.451-4.892 7.325 9.102 1.338 2.674s-7.78 1.55-18.446.669c-10.667-.896-16.882 1.55-25.33 2.66-8.448 1.109-23.553-1.109-23.553-1.109l1.55-2.889z"
|
||||
fill="#5c913b"
|
||||
/>
|
||||
<path
|
||||
d="M237.995 262.67l10.226 11.107-5.12.442-5.774-8.434.668-3.115zm12.231.883l8.22 7.338-3.782.654-3.996-5.546-.442-2.446zm10.439-.442l7.111 6.67L266 270.89l-5.774-6.229.44-1.55zm9.33 0l-2.888 2.66 1.338 1.565 1.55-4.225zm8.889.228l-3.328 4.224 1.109 1.99 2.446-4.664-.227-1.55z"
|
||||
fill="#e2f09f"
|
||||
/>
|
||||
<path
|
||||
d="M256.426 233.671l.939 13.227 7.566-10.867-5.675 11.805 11.805-5.66-10.851 7.553 13.213.939-13.213.952 10.851 7.553-11.805-5.66 5.675 11.805-7.566-10.867-.939 13.226-.939-13.226-7.566 10.867 5.675-11.805-11.805 5.66 10.851-7.553-13.212-.953 13.212-.938-10.85-7.553 11.804 5.66-5.675-11.805 7.566 10.866.94-13.226z"
|
||||
fill="#bbddf5"
|
||||
/>
|
||||
<path
|
||||
d="M256 244.665l-2.66 2.66s.654 4.011.441 4.679C253.554 252.658 256 256 256 256l3.327-3.996-.88-5.334-2.447-2.005z"
|
||||
fill="#dd2e44"
|
||||
/>
|
||||
<path
|
||||
d="M257.28 240.071c10.894 0 17.109 5.334 17.109 5.334l-3.996-7.111s-6.443-4.893-13.995-4.893c-7.567 0-16 6.001-16 6.001l-3.783 7.553s9.771-6.884 20.665-6.884"
|
||||
fill="#269"
|
||||
/>
|
||||
<path
|
||||
d="M257.28 240.071c10.894 0 17.109 5.334 17.109 5.334l-2.888-5.106s-7.78-4.665-15.331-4.665-16.896 5.987-16.896 5.987l-2.66 5.334c.001 0 9.772-6.884 20.666-6.884"
|
||||
fill="#ffcc4d"
|
||||
/>
|
||||
<path
|
||||
d="M257.28 240.071c10.894 0 17.109 5.334 17.109 5.334l-2.005-3.327s-5.988-4.224-16.214-3.783c-7.553 0-18.005 5.561-18.005 5.561l-1.55 3.1c0-.001 9.771-6.885 20.665-6.885"
|
||||
fill="#dd2e44"
|
||||
/>
|
||||
<path
|
||||
d="M264.291 322.873h-14.165V309.29h14.165v13.582zm-16.426-118.898h-10.183l-5.106-12.459 10.198-1.137 5.091 13.596zm23.21 5.66l-6.784-2.261 3.385-16.426 13.028 1.137-9.629 17.55zm-61.141 69.646l-19.811 4.523-.57-13.583 19.812-2.83.569 11.89zm5.106 14.152l-16.426 6.812-3.954-10.766 15.289-5.106 5.091 9.06zm109.269-12.444l-18.105-5.091v-9.63l20.366-2.83-2.261 17.55zm-9.003 18.674l-16.981-7.937 5.646-10.182 18.703 5.66-7.368 12.459z"
|
||||
fill="#eee"
|
||||
/>
|
||||
<title>
|
||||
Nicaragua
|
||||
</title>
|
||||
</svg>
|
||||
<svg
|
||||
class="MuiSvgIcon-root emotion-15 emotion-12"
|
||||
focusable="false"
|
||||
role="img"
|
||||
viewBox="0 0 512 512"
|
||||
>
|
||||
<path
|
||||
d="M0 384c0 31.418 25.473 56.889 56.889 56.889H455.11c31.42 0 56.89-25.473 56.89-56.889v-56.889H0V384z"
|
||||
fill="#138808"
|
||||
/>
|
||||
<path
|
||||
d="M0 327.111h512V184.89H0v142.22z"
|
||||
fill="#eee"
|
||||
/>
|
||||
<path
|
||||
d="M512 184.889V128c0-31.417-25.473-56.889-56.889-56.889H56.89C25.472 71.111 0 96.583 0 128v56.889h512z"
|
||||
fill="#f93"
|
||||
/>
|
||||
<path
|
||||
d="M312.889 256c0-31.431-25.473-56.902-56.903-56.902-31.417 0-56.888 25.472-56.888 56.902 0 31.418 25.472 56.889 56.888 56.889 31.432 0 56.903-25.472 56.903-56.889"
|
||||
fill="navy"
|
||||
/>
|
||||
<path
|
||||
d="M298.666 256c0-23.566-19.115-42.681-42.681-42.681S213.319 232.434 213.319 256s19.1 42.666 42.666 42.666 42.681-19.1 42.681-42.666"
|
||||
fill="#eee"
|
||||
/>
|
||||
<path
|
||||
d="M256 213.334l2.076 32.199 14.251-28.943-10.396 30.535 24.235-21.305-21.291 24.249 30.535-10.396-28.942 14.25L298.666 256l-32.198 2.076 28.942 14.237-30.535-10.383 21.291 24.235-24.235-21.29 10.396 30.535-14.25-28.942L256 298.666l-2.076-32.198-14.252 28.942 10.397-30.535-24.249 21.291 21.305-24.235-30.535 10.383 28.942-14.236L213.334 256l32.199-2.076-28.943-14.251 30.535 10.396-21.305-24.249 24.249 21.305-10.396-30.535 14.25 28.943 2.077-32.2z"
|
||||
fill="navy"
|
||||
opacity="0.6"
|
||||
/>
|
||||
<path
|
||||
d="M241.778 256c0-7.851 6.37-14.223 14.222-14.223s14.223 6.372 14.223 14.223-6.372 14.223-14.223 14.223-14.223-6.372-14.223-14.223"
|
||||
fill="navy"
|
||||
/>
|
||||
<title>
|
||||
India
|
||||
</title>
|
||||
</svg>
|
||||
<svg
|
||||
class="MuiSvgIcon-root emotion-15 emotion-12"
|
||||
focusable="false"
|
||||
role="img"
|
||||
viewBox="0 0 45 45"
|
||||
>
|
||||
<defs>
|
||||
<clippath
|
||||
id="prefix__a"
|
||||
>
|
||||
<path
|
||||
d="M0 36h36V0H0v36z"
|
||||
/>
|
||||
</clippath>
|
||||
</defs>
|
||||
<g
|
||||
clip-path="url(#prefix__a)"
|
||||
transform="matrix(1.25 0 0 -1.25 0 45)"
|
||||
<svg
|
||||
viewBox="0 85.333 512 341.333"
|
||||
>
|
||||
<path
|
||||
d="M36 9a4 4 0 00-4-4H4a4 4 0 00-4 4v18a4 4 0 004 4h28a4 4 0 004-4V9z"
|
||||
d="M0 85.337h512v341.326H0z"
|
||||
fill="#FFF"
|
||||
/>
|
||||
<path
|
||||
d="M0 85.337h512v113.775H0zm0 227.551h512v113.775H0z"
|
||||
fill="#338AF3"
|
||||
/>
|
||||
<path
|
||||
d="M256 214.447c-22.949 0-41.553 18.603-41.553 41.553S233.05 297.553 256 297.553c22.949 0 41.553-18.603 41.553-41.553S278.949 214.447 256 214.447zm0 65.298c-13.114 0-23.745-10.631-23.745-23.745s10.631-23.745 23.745-23.745 23.745 10.631 23.745 23.745-10.631 23.745-23.745 23.745z"
|
||||
fill="#FFDA44"
|
||||
/>
|
||||
<path
|
||||
d="M276.563 261.936L256 256l-20.563 5.936-6.855 11.873h54.836z"
|
||||
fill="#0052B4"
|
||||
/>
|
||||
<path
|
||||
d="M256 226.32l-13.709 23.744L256 256l13.709-5.936z"
|
||||
fill="#338AF3"
|
||||
/>
|
||||
<path
|
||||
d="M235.437 261.936h41.126l-6.854-11.872h-27.418z"
|
||||
fill="#6DA544"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div
|
||||
class="emotion-15 emotion-16"
|
||||
>
|
||||
<svg
|
||||
viewBox="0 0 513 342"
|
||||
>
|
||||
<path
|
||||
d="M17.3 0h478.4v342H17.3V0z"
|
||||
fill="#181A93"
|
||||
/>
|
||||
<path
|
||||
d="M0 0h513v114H0V0z"
|
||||
fill="#FFA44A"
|
||||
/>
|
||||
<path
|
||||
d="M0 228h513v114H0V228z"
|
||||
fill="#1A9F0B"
|
||||
/>
|
||||
<path
|
||||
d="M0 114h513v114H0V114z"
|
||||
fill="#FFF"
|
||||
/>
|
||||
<circle
|
||||
cx="256.5"
|
||||
cy="171"
|
||||
fill="#FFF"
|
||||
r="34.2"
|
||||
/>
|
||||
<path
|
||||
d="M256.5 216.6c-25.1 0-45.6-20.5-45.6-45.6s20.5-45.6 45.6-45.6 45.6 20.5 45.6 45.6-20.5 45.6-45.6 45.6zm0-11.4c18.2 0 34.2-16 34.2-34.2s-15.9-34.2-34.2-34.2-34.2 16-34.2 34.2 16 34.2 34.2 34.2z"
|
||||
fill="#181A93"
|
||||
/>
|
||||
<circle
|
||||
cx="256.5"
|
||||
cy="171"
|
||||
fill="#181A93"
|
||||
r="22.8"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div
|
||||
class="emotion-15 emotion-16"
|
||||
>
|
||||
<svg
|
||||
viewBox="0 0 513 342"
|
||||
>
|
||||
<path
|
||||
d="M0 0h513v342H0z"
|
||||
fill="#009b3a"
|
||||
/>
|
||||
<path
|
||||
d="M32.727 18L18 6.876 3.27 18 18 29.125 32.727 18z"
|
||||
fill="#fedf01"
|
||||
d="M256.5 19.3l204.9 151.4L256.5 322 50.6 170.7z"
|
||||
fill="#fedf00"
|
||||
/>
|
||||
<circle
|
||||
cx="256.5"
|
||||
cy="171"
|
||||
fill="#FFF"
|
||||
r="80.4"
|
||||
/>
|
||||
<path
|
||||
d="M24.434 18.076a6.458 6.458 0 11-12.917 0 6.458 6.458 0 0112.917 0"
|
||||
d="M215.9 165.7c-13.9 0-27.4 2.1-40.1 6 .6 43.9 36.3 79.3 80.3 79.3 27.2 0 51.3-13.6 65.8-34.3-24.9-31-63.2-51-106-51zm119 20.3c.9-5 1.5-10.1 1.5-15.4 0-44.4-36-80.4-80.4-80.4-33.1 0-61.5 20.1-73.9 48.6 10.9-2.2 22.1-3.4 33.6-3.4 46.8.1 89 19.5 119.2 50.6z"
|
||||
fill="#002776"
|
||||
/>
|
||||
<path
|
||||
d="M12.277 21.113a6.406 6.406 0 01-.672-2.023c3.994.29 9.417-1.892 11.744-4.596.402.604.7 1.28.882 2.004-2.871 2.809-7.916 4.63-11.954 4.615"
|
||||
fill="#cbe9d4"
|
||||
/>
|
||||
<path
|
||||
d="M13 16.767h-1v1h1v-1zm1-2h-1v1h1v-1z"
|
||||
fill="#88c9f9"
|
||||
/>
|
||||
<path
|
||||
d="M16 16.767h-1v1h1v-1zm2-1h-1v1h1v-1zm4-2h-1v1h1v-1zm-3-1h-1v1h1v-1zm3 6h-1v1h1v-1z"
|
||||
fill="#55acee"
|
||||
/>
|
||||
<path
|
||||
d="M20 14.767h-1v1h1v-1z"
|
||||
fill="#3b88c3"
|
||||
/>
|
||||
</g>
|
||||
<title>
|
||||
Brazil
|
||||
</title>
|
||||
</svg>
|
||||
<svg
|
||||
class="MuiSvgIcon-root emotion-15 emotion-12"
|
||||
focusable="false"
|
||||
role="img"
|
||||
viewBox="0 0 45 45"
|
||||
</svg>
|
||||
</div>
|
||||
<div
|
||||
class="emotion-15 emotion-16"
|
||||
>
|
||||
<defs>
|
||||
<clippath
|
||||
id="prefix__a"
|
||||
>
|
||||
<path
|
||||
d="M0 36h36V0H0v36z"
|
||||
/>
|
||||
</clippath>
|
||||
</defs>
|
||||
<g
|
||||
clip-path="url(#prefix__a)"
|
||||
transform="matrix(1.25 0 0 -1.25 0 45)"
|
||||
<svg
|
||||
viewBox="0 0 513 342"
|
||||
>
|
||||
<path
|
||||
d="M36 9a4 4 0 00-4-4H4a4 4 0 00-4 4v18a4 4 0 004 4h28a4 4 0 004-4V9z"
|
||||
fill="#de2910"
|
||||
d="M0 0h513v342H0z"
|
||||
fill="#D80027"
|
||||
/>
|
||||
<path
|
||||
d="M7 25.049l.929-2.67 2.826-.06-2.253-1.706.819-2.707L7 19.52l-2.321-1.615.819 2.707-2.253 1.707 2.826.059.929 2.67zm6 3.423l.34-.688.759-.11-.549-.536.129-.756-.679.357-.679-.357.13.756-.55.536.76.11.339.688zm2-4l.34-.688.759-.11-.549-.536.129-.756-.679.357-.679-.357.13.756-.55.536.76.11.339.688zm0-4l.34-.688.759-.11-.549-.536.129-.756-.679.357-.679-.357.13.756-.55.536.76.11.339.688zm-2-3.999l.34-.69.759-.11-.549-.534.129-.757-.679.357-.679-.357.13.757-.55.535.76.11.339.689z"
|
||||
fill="#ffde02"
|
||||
d="M226.8 239.2l-9.7-15.6-17.9 4.4 11.9-14.1-9.7-15.6 17.1 6.9 11.8-14.1-1.3 18.4 17.1 6.9-17.9 4.4zM290.6 82l-10.1 15.4 11.6 14.3-17.7-4.8-10.1 15.5-1-18.4-17.7-4.8 17.2-6.6-1-18.4 11.6 14.3zm-54.4-56.6l-2 18.3 16.8 7.6-18 3.8-2 18.3-9.2-16-17.9 3.8 12.3-13.7-9.2-15.9 16.8 7.5zm56.6 136.4l-14.9 10.9 5.8 17.5-14.9-10.8-14.9 11 5.6-17.6-14.9-10.7 18.4-.1 5.6-17.6 5.8 17.5zM115 46.3l17.3 53.5h56.2l-45.4 32.9 17.3 53.5-45.4-33-45.5 33 17.4-53.5-45.5-32.9h56.3z"
|
||||
fill="#FFDA44"
|
||||
/>
|
||||
</g>
|
||||
<title>
|
||||
China
|
||||
</title>
|
||||
</svg>
|
||||
<svg
|
||||
class="MuiSvgIcon-root emotion-15 emotion-12"
|
||||
focusable="false"
|
||||
role="img"
|
||||
viewBox="0 0 512 512"
|
||||
</svg>
|
||||
</div>
|
||||
<div
|
||||
class="emotion-15 emotion-16"
|
||||
>
|
||||
<path
|
||||
d="M473.655 88.276H38.345C17.167 88.276 0 105.443 0 126.621v73.471h512v-73.471c0-21.178-17.167-38.345-38.345-38.345zM0 385.379c0 21.177 17.167 38.345 38.345 38.345h435.31c21.177 0 38.345-17.167 38.345-38.345v-73.471H0v73.471z"
|
||||
fill="#ff4b55"
|
||||
/>
|
||||
<path
|
||||
d="M0 200.09h512V311.9H0z"
|
||||
fill="#f5f5f5"
|
||||
/>
|
||||
<title>
|
||||
Austria
|
||||
</title>
|
||||
</svg>
|
||||
<svg
|
||||
class="MuiSvgIcon-root emotion-15 emotion-12"
|
||||
focusable="false"
|
||||
role="img"
|
||||
viewBox="0 0 512 512"
|
||||
<svg
|
||||
viewBox="0 0 513 342"
|
||||
>
|
||||
<path
|
||||
d="M0 0h513v342H0z"
|
||||
fill="#10338c"
|
||||
/>
|
||||
<g
|
||||
fill="#FFF"
|
||||
>
|
||||
<path
|
||||
d="M222.2 170.7c.3-.3.5-.6.8-.9-.2.3-.5.6-.8.9zM188 212.6l11 22.9 24.7-5.7-11 22.8 19.9 15.8-24.8 5.6.1 25.4-19.9-15.9-19.8 15.9.1-25.4-24.8-5.6 19.9-15.8-11.1-22.8 24.8 5.7zm197.9 28.5l5.2 10.9 11.8-2.7-5.3 10.9 9.5 7.5-11.8 2.6v12.2l-9.4-7.6-9.5 7.6.1-12.2-11.8-2.6 9.5-7.5-5.3-10.9 11.8 2.7zm-48.6-116l5.2 10.9 11.8-2.7-5.3 10.9 9.5 7.5-11.8 2.7v12.1l-9.4-7.6-9.5 7.6.1-12.1-11.9-2.7 9.5-7.5-5.3-10.9L332 136zm48.6-66.2l5.2 10.9 11.8-2.7-5.3 10.9 9.5 7.5-11.8 2.7v12.1l-9.4-7.6-9.5 7.6.1-12.1-11.8-2.7 9.5-7.5-5.3-10.9 11.8 2.7zm42.5 49.7l5.2 10.9 11.8-2.7-5.3 10.9 9.5 7.5-11.8 2.6V150l-9.4-7.6-9.5 7.6v-12.2l-11.8-2.6 9.5-7.5-5.3-10.9 11.8 2.7zM398 166.5l4.1 12.7h13.3l-10.8 7.8 4.2 12.7-10.8-7.9-10.8 7.9 4.1-12.7-10.7-7.8h13.3z"
|
||||
/>
|
||||
<path
|
||||
d="M254.8 0v30.6l-45.1 25.1h45.1V115h-59.1l59.1 32.8v22.9h-26.7l-73.5-40.9v40.9H99v-48.6l-87.4 48.6H-1.2v-30.6L44 115H-1.2V55.7h59.1L-1.2 22.8V0h26.7L99 40.8V0h55.6v48.6L242.1 0z"
|
||||
/>
|
||||
</g>
|
||||
<path
|
||||
d="M142.8 0h-32v69.3h-112v32h112v69.4h32v-69.4h112v-32h-112z"
|
||||
fill="#D80027"
|
||||
/>
|
||||
<path
|
||||
d="M154.6 115l100.2 55.7v-15.8L183 115z"
|
||||
fill="#0052B4"
|
||||
/>
|
||||
<path
|
||||
d="M154.6 115l100.2 55.7v-15.8L183 115z"
|
||||
fill="#FFF"
|
||||
/>
|
||||
<path
|
||||
d="M154.6 115l100.2 55.7v-15.8L183 115zm-83.9 0l-71.9 39.9v15.8L99 115z"
|
||||
fill="#D80027"
|
||||
/>
|
||||
<path
|
||||
d="M99 55.7L-1.2 0v15.7l71.9 40z"
|
||||
fill="#0052B4"
|
||||
/>
|
||||
<path
|
||||
d="M99 55.7L-1.2 0v15.7l71.9 40z"
|
||||
fill="#FFF"
|
||||
/>
|
||||
<path
|
||||
d="M99 55.7L-1.2 0v15.7l71.9 40zm84 0l71.8-40V0L154.6 55.7z"
|
||||
fill="#D80027"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div
|
||||
class="emotion-15 emotion-16"
|
||||
>
|
||||
<path
|
||||
d="M473.655 88.276H38.345C17.167 88.276 0 105.443 0 126.621v73.471h512v-73.471c0-21.178-17.167-38.345-38.345-38.345z"
|
||||
fill="#464655"
|
||||
/>
|
||||
<path
|
||||
d="M0 385.379c0 21.177 17.167 38.345 38.345 38.345h435.31c21.177 0 38.345-17.167 38.345-38.345v-73.471H0v73.471z"
|
||||
fill="#ffe15a"
|
||||
/>
|
||||
<path
|
||||
d="M0 200.09h512V311.9H0z"
|
||||
fill="#ff4b55"
|
||||
/>
|
||||
<title>
|
||||
Germany
|
||||
</title>
|
||||
</svg>
|
||||
<svg
|
||||
class="MuiSvgIcon-root emotion-15 emotion-12"
|
||||
focusable="false"
|
||||
role="img"
|
||||
viewBox="-60 -40 240 160"
|
||||
<svg
|
||||
viewBox="0 85.333 512 341.333"
|
||||
>
|
||||
<path
|
||||
d="M0 85.331h512v341.337H0z"
|
||||
fill="#D80027"
|
||||
/>
|
||||
<path
|
||||
d="M0 85.331h512v113.775H0z"
|
||||
/>
|
||||
<path
|
||||
d="M0 312.882h512v113.775H0z"
|
||||
fill="#FFDA44"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div
|
||||
class="emotion-15 emotion-16"
|
||||
>
|
||||
<rect
|
||||
fill="#fe0000"
|
||||
height="100%"
|
||||
width="100%"
|
||||
x="-60"
|
||||
y="-40"
|
||||
/>
|
||||
<rect
|
||||
fill="#000095"
|
||||
height="50%"
|
||||
width="50%"
|
||||
x="-60"
|
||||
y="-40"
|
||||
/>
|
||||
<path
|
||||
d="M8 0L0 30-8 0l8-30M0 8l30-8L0-8l-30 8"
|
||||
fill="#fff"
|
||||
id="prefix__a"
|
||||
/>
|
||||
<use
|
||||
transform="rotate(30)"
|
||||
xlink:href="#prefix__a"
|
||||
/>
|
||||
<use
|
||||
transform="rotate(60)"
|
||||
xlink:href="#prefix__a"
|
||||
/>
|
||||
<circle
|
||||
fill="#000095"
|
||||
r="17"
|
||||
/>
|
||||
<circle
|
||||
fill="#fff"
|
||||
r="15"
|
||||
/>
|
||||
<title>
|
||||
Taiwan
|
||||
</title>
|
||||
</svg>
|
||||
<svg
|
||||
viewBox="0 85.333 512 341.333"
|
||||
>
|
||||
<path
|
||||
d="M0 85.337h512v341.326H0z"
|
||||
fill="#D80027"
|
||||
/>
|
||||
<path
|
||||
d="M0 85.337h256V256H0z"
|
||||
fill="#0052B4"
|
||||
/>
|
||||
<path
|
||||
d="M186.435 170.669L162.558 181.9l12.714 23.125-25.927-4.961-3.286 26.192L128 206.993l-18.06 19.263-3.285-26.192-25.927 4.96 12.714-23.125-23.877-11.23 23.877-11.231-12.714-23.125 25.927 4.96 3.286-26.192L128 134.344l18.06-19.263 3.285 26.192 25.928-4.96-12.715 23.125z"
|
||||
fill="#FFF"
|
||||
/>
|
||||
<circle
|
||||
cx="128"
|
||||
cy="170.674"
|
||||
fill="#0052B4"
|
||||
r="29.006"
|
||||
/>
|
||||
<path
|
||||
d="M128 190.06c-10.692 0-19.391-8.7-19.391-19.391 0-10.692 8.7-19.391 19.391-19.391 10.692 0 19.391 8.7 19.391 19.391 0 10.691-8.699 19.391-19.391 19.391z"
|
||||
fill="#FFF"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -2,79 +2,78 @@ import React from 'react';
|
||||
import { BrowserRouter as Router } from 'react-router-dom';
|
||||
|
||||
import {
|
||||
render,
|
||||
renderWithStore,
|
||||
fireEvent,
|
||||
waitFor,
|
||||
cleanup,
|
||||
screen,
|
||||
waitForElementToBeRemoved,
|
||||
} from 'verdaccio-ui/utils/test-react-testing-library';
|
||||
|
||||
import translationEN from '../../../i18n/translations/en-US.json';
|
||||
import { AppContextProvider } from '../../App';
|
||||
import translationEN from '../../i18n/crowdin/ui.json';
|
||||
import { store } from '../../store';
|
||||
|
||||
import Header from './Header';
|
||||
|
||||
const props = {
|
||||
user: {
|
||||
username: 'verddacio-user',
|
||||
},
|
||||
packages: [],
|
||||
};
|
||||
|
||||
/* eslint-disable react/jsx-no-bind*/
|
||||
describe('<Header /> component with logged in state', () => {
|
||||
afterEach(cleanup);
|
||||
|
||||
test('should load the component in logged out state', () => {
|
||||
render(
|
||||
renderWithStore(
|
||||
<Router>
|
||||
<AppContextProvider>
|
||||
<Header />
|
||||
</AppContextProvider>
|
||||
</Router>
|
||||
<Header />
|
||||
</Router>,
|
||||
store
|
||||
);
|
||||
|
||||
expect(screen.queryByTestId('header--menu-accountcircle')).toBeNull();
|
||||
expect(screen.queryByTestId('logInDialogIcon')).toBeNull();
|
||||
expect(screen.getByText('Login')).toBeTruthy();
|
||||
expect(screen.queryByTestId('header--button-login')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('should load the component in logged in state', () => {
|
||||
const { getByTestId, queryByText } = render(
|
||||
test('should load the component in logged in state', async () => {
|
||||
renderWithStore(
|
||||
<Router>
|
||||
<AppContextProvider user={props.user}>
|
||||
<Header />
|
||||
</AppContextProvider>
|
||||
</Router>
|
||||
<Header />
|
||||
</Router>,
|
||||
store
|
||||
);
|
||||
store.dispatch.login.logInUser({ username: 'store', token: '12345' });
|
||||
|
||||
expect(getByTestId('header--menu-accountcircle')).toBeTruthy();
|
||||
expect(queryByText('Login')).toBeNull();
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('logInDialogIcon')).toBeTruthy();
|
||||
expect(screen.queryByText('Login')).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
test('should open login dialog', async () => {
|
||||
const { getByTestId } = render(
|
||||
renderWithStore(
|
||||
<Router>
|
||||
<AppContextProvider>
|
||||
<Header />
|
||||
</AppContextProvider>
|
||||
</Router>
|
||||
<Header />
|
||||
</Router>,
|
||||
store
|
||||
);
|
||||
|
||||
const loginBtn = getByTestId('header--button-login');
|
||||
store.dispatch.login.logOutUser();
|
||||
|
||||
const loginBtn = screen.getByTestId('header--button-login');
|
||||
fireEvent.click(loginBtn);
|
||||
const loginDialog = await waitFor(() => getByTestId('login--dialog'));
|
||||
const loginDialog = await waitFor(() => screen.getByTestId('login--dialog'));
|
||||
expect(loginDialog).toBeTruthy();
|
||||
});
|
||||
|
||||
test('should logout the user', async () => {
|
||||
const { getByText, getByTestId } = render(
|
||||
const { getByText, getByTestId } = renderWithStore(
|
||||
<Router>
|
||||
<AppContextProvider user={props.user}>
|
||||
<Header />
|
||||
</AppContextProvider>
|
||||
</Router>
|
||||
<Header />
|
||||
</Router>,
|
||||
store
|
||||
);
|
||||
|
||||
const headerMenuAccountCircle = getByTestId('header--menu-accountcircle');
|
||||
store.dispatch.login.logInUser({ username: 'store', token: '12345' });
|
||||
|
||||
const headerMenuAccountCircle = getByTestId('logInDialogIcon');
|
||||
fireEvent.click(headerMenuAccountCircle);
|
||||
|
||||
// wait for button Logout's appearance and return the element
|
||||
@@ -84,12 +83,11 @@ describe('<Header /> component with logged in state', () => {
|
||||
});
|
||||
|
||||
test("The question icon should open a new tab of verdaccio's website - installation doc", () => {
|
||||
const { getByTestId } = render(
|
||||
const { getByTestId } = renderWithStore(
|
||||
<Router>
|
||||
<AppContextProvider user={props.user}>
|
||||
<Header />
|
||||
</AppContextProvider>
|
||||
</Router>
|
||||
<Header />
|
||||
</Router>,
|
||||
store
|
||||
);
|
||||
|
||||
const documentationBtn = getByTestId('header--tooltip-documentation');
|
||||
@@ -99,12 +97,11 @@ describe('<Header /> component with logged in state', () => {
|
||||
});
|
||||
|
||||
test('should open the registrationInfo modal when clicking on the info icon', async () => {
|
||||
const { getByTestId } = render(
|
||||
const { getByTestId } = renderWithStore(
|
||||
<Router>
|
||||
<AppContextProvider user={props.user}>
|
||||
<Header />
|
||||
</AppContextProvider>
|
||||
</Router>
|
||||
<Header />
|
||||
</Router>,
|
||||
store
|
||||
);
|
||||
|
||||
const infoBtn = getByTestId('header--tooltip-info');
|
||||
@@ -116,12 +113,11 @@ describe('<Header /> component with logged in state', () => {
|
||||
});
|
||||
|
||||
test('should close the registrationInfo modal when clicking on the button close', async () => {
|
||||
const { getByTestId, getByText, queryByTestId } = render(
|
||||
const { getByTestId, getByText, queryByTestId } = renderWithStore(
|
||||
<Router>
|
||||
<AppContextProvider user={props.user}>
|
||||
<Header />
|
||||
</AppContextProvider>
|
||||
</Router>
|
||||
<Header />
|
||||
</Router>,
|
||||
store
|
||||
);
|
||||
|
||||
const infoBtn = getByTestId('header--tooltip-info');
|
||||
@@ -138,17 +134,15 @@ describe('<Header /> component with logged in state', () => {
|
||||
});
|
||||
|
||||
test('should hide login if is disabled', () => {
|
||||
// @ts-expect-error
|
||||
window.__VERDACCIO_BASENAME_UI_OPTIONS = {
|
||||
base: 'foo',
|
||||
login: false,
|
||||
};
|
||||
render(
|
||||
renderWithStore(
|
||||
<Router>
|
||||
<AppContextProvider user={props.user}>
|
||||
<Header />
|
||||
</AppContextProvider>
|
||||
</Router>
|
||||
<Header />
|
||||
</Router>,
|
||||
store
|
||||
);
|
||||
|
||||
expect(screen.queryByTestId('header--button-login')).not.toBeInTheDocument();
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import React, { useState, useContext } from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
import Button from 'verdaccio-ui/components/Button';
|
||||
import { useConfig } from 'verdaccio-ui/providers/config';
|
||||
import storage from 'verdaccio-ui/utils/storage';
|
||||
|
||||
import AppContext from '../../App/AppContext';
|
||||
import { Dispatch, RootState } from '../../store/store';
|
||||
|
||||
import HeaderInfoDialog from './HeaderInfoDialog';
|
||||
import HeaderLeft from './HeaderLeft';
|
||||
@@ -21,26 +21,15 @@ interface Props {
|
||||
/* eslint-disable react/jsx-no-bind*/
|
||||
const Header: React.FC<Props> = ({ withoutSearch }) => {
|
||||
const { t } = useTranslation();
|
||||
const appContext = useContext(AppContext);
|
||||
const [isInfoDialogOpen, setOpenInfoDialog] = useState<boolean>(false);
|
||||
const [showMobileNavBar, setShowMobileNavBar] = useState<boolean>(false);
|
||||
const [showLoginModal, setShowLoginModal] = useState<boolean>(false);
|
||||
|
||||
if (!appContext) {
|
||||
throw Error(t('app-context-not-correct-used'));
|
||||
}
|
||||
|
||||
const { user, scope, setUser } = appContext;
|
||||
const loginStore = useSelector((state: RootState) => state.login);
|
||||
const configStore = useSelector((state: RootState) => state.configuration);
|
||||
const { configOptions } = useConfig();
|
||||
|
||||
/**
|
||||
* Logouts user
|
||||
* Required by: <Header />
|
||||
*/
|
||||
const dispatch = useDispatch<Dispatch>();
|
||||
const handleLogout = () => {
|
||||
storage.removeItem('username');
|
||||
storage.removeItem('token');
|
||||
setUser(undefined);
|
||||
dispatch.login.logOutUser();
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -54,7 +43,7 @@ const Header: React.FC<Props> = ({ withoutSearch }) => {
|
||||
onOpenRegistryInfoDialog={() => setOpenInfoDialog(true)}
|
||||
onToggleLogin={() => setShowLoginModal(!showLoginModal)}
|
||||
onToggleMobileNav={() => setShowMobileNavBar(!showMobileNavBar)}
|
||||
username={user?.username}
|
||||
username={loginStore?.username}
|
||||
withoutSearch={withoutSearch}
|
||||
/>
|
||||
</InnerNavBar>
|
||||
@@ -62,7 +51,7 @@ const Header: React.FC<Props> = ({ withoutSearch }) => {
|
||||
isOpen={isInfoDialogOpen}
|
||||
onCloseDialog={() => setOpenInfoDialog(false)}
|
||||
registryUrl={configOptions.base}
|
||||
scope={scope}
|
||||
scope={configStore.scope}
|
||||
/>
|
||||
</NavBar>
|
||||
{showMobileNavBar && !withoutSearch && (
|
||||
@@ -75,7 +64,9 @@ const Header: React.FC<Props> = ({ withoutSearch }) => {
|
||||
</Button>
|
||||
</MobileNavBar>
|
||||
)}
|
||||
{!user && <LoginDialog onClose={() => setShowLoginModal(false)} open={showLoginModal} />}
|
||||
{!loginStore.user && (
|
||||
<LoginDialog onClose={() => setShowLoginModal(false)} open={showLoginModal} />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -30,7 +30,7 @@ const HeaderMenu: React.FC<Props> = ({
|
||||
<>
|
||||
<IconButton
|
||||
color="inherit"
|
||||
data-testid="header--menu-accountcircle"
|
||||
data-testid="logInDialogIcon"
|
||||
id="header--button-account"
|
||||
onClick={onLoggedInMenu}>
|
||||
<AccountCircle />
|
||||
@@ -52,8 +52,8 @@ const HeaderMenu: React.FC<Props> = ({
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
button={true}
|
||||
data-testid="header--button-logout"
|
||||
id="header--button-logout"
|
||||
data-testid="logOutDialogIcon"
|
||||
id="logOutDialogIcon"
|
||||
onClick={onLogout}>
|
||||
{t('button.logout')}
|
||||
</MenuItem>
|
||||
|
||||
@@ -1,86 +1,25 @@
|
||||
/* eslint-disable react/jsx-pascal-case */
|
||||
import styled from '@emotion/styled';
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
import LanguageIcon from '@material-ui/icons/Language';
|
||||
import i18next, { TFunctionKeys } from 'i18next';
|
||||
import i18next from 'i18next';
|
||||
import React, { useCallback, useContext, useState, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { AutoComplete } from 'verdaccio-ui/components/AutoComplete/AutoCompleteV2';
|
||||
import {
|
||||
France,
|
||||
Brazil,
|
||||
Germany,
|
||||
Spain,
|
||||
China,
|
||||
Russia,
|
||||
Turkey,
|
||||
Ukraine,
|
||||
Khmer,
|
||||
Japan,
|
||||
Usa,
|
||||
Czech,
|
||||
Taiwan,
|
||||
} from 'verdaccio-ui/components/Icons';
|
||||
import MenuItem from 'verdaccio-ui/components/MenuItem';
|
||||
import { Theme } from 'verdaccio-ui/design-tokens/theme';
|
||||
import ThemeContext from 'verdaccio-ui/design-tokens/ThemeContext';
|
||||
|
||||
import { Language } from '../../../i18n/config';
|
||||
import { Language, listLanguages } from '../../i18n/enabledLanguages';
|
||||
|
||||
const lngDetails: Record<Language, { translation: TFunctionKeys; icon: React.ReactElement }> = {
|
||||
'fr-FR': {
|
||||
translation: 'lng.french',
|
||||
icon: <France size="md" />,
|
||||
},
|
||||
'pt-BR': {
|
||||
translation: 'lng.portuguese',
|
||||
icon: <Brazil size="md" />,
|
||||
},
|
||||
'de-DE': {
|
||||
translation: 'lng.german',
|
||||
icon: <Germany size="md" />,
|
||||
},
|
||||
'es-ES': {
|
||||
translation: 'lng.spanish',
|
||||
icon: <Spain size="md" />,
|
||||
},
|
||||
'zh-CN': {
|
||||
translation: 'lng.chinese',
|
||||
icon: <China size="md" />,
|
||||
},
|
||||
'ru-RU': {
|
||||
translation: 'lng.russian',
|
||||
icon: <Russia size="md" />,
|
||||
},
|
||||
'tr-TR': {
|
||||
translation: 'lng.turkish',
|
||||
icon: <Turkey size="md" />,
|
||||
},
|
||||
'uk-UA': {
|
||||
translation: 'lng.ukraine',
|
||||
icon: <Ukraine size="md" />,
|
||||
},
|
||||
'km-KH': {
|
||||
translation: 'lng.khmer',
|
||||
icon: <Khmer size="md" />,
|
||||
},
|
||||
'ja-JP': {
|
||||
translation: 'lng.japanese',
|
||||
icon: <Japan size="md" />,
|
||||
},
|
||||
'en-US': {
|
||||
translation: 'lng.english',
|
||||
icon: <Usa size="md" />,
|
||||
},
|
||||
'cs-CZ': {
|
||||
translation: 'lng.czech',
|
||||
icon: <Czech size="md" />,
|
||||
},
|
||||
'zh-TW': {
|
||||
translation: 'lng.chineseTraditional',
|
||||
icon: <Taiwan size="md" />,
|
||||
},
|
||||
};
|
||||
const listConverted = listLanguages.reduce((prev, item) => {
|
||||
prev[item.lng] = {
|
||||
translation: item.menuKey,
|
||||
icon: item.icon,
|
||||
};
|
||||
return prev;
|
||||
}, {});
|
||||
|
||||
const LanguageSwitch = () => {
|
||||
const themeContext = useContext(ThemeContext);
|
||||
@@ -104,10 +43,10 @@ const LanguageSwitch = () => {
|
||||
|
||||
const getCurrentLngDetails = useCallback(
|
||||
(language: Language) => {
|
||||
const { icon, translation } = lngDetails[language] || lngDetails['en-US'];
|
||||
const lng = listConverted[language] || listConverted['en-US'];
|
||||
return {
|
||||
icon,
|
||||
translation: t(translation),
|
||||
icon: <lng.icon />,
|
||||
translation: t(lng.translation),
|
||||
};
|
||||
},
|
||||
[t]
|
||||
|
||||
@@ -2,7 +2,7 @@ import React from 'react';
|
||||
|
||||
import api from 'verdaccio-ui/providers/API/api';
|
||||
import {
|
||||
render,
|
||||
renderWithStore,
|
||||
waitFor,
|
||||
fireEvent,
|
||||
cleanup,
|
||||
@@ -10,15 +10,10 @@ import {
|
||||
act,
|
||||
} from 'verdaccio-ui/utils/test-react-testing-library';
|
||||
|
||||
import AppContext, { AppContextProps } from '../../AppContext';
|
||||
import { store } from '../../../store';
|
||||
|
||||
import LoginDialog from './LoginDialog';
|
||||
|
||||
const appContextValue: AppContextProps = {
|
||||
scope: '',
|
||||
setUser: jest.fn(),
|
||||
};
|
||||
|
||||
describe('<LoginDialog /> component', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
@@ -30,11 +25,7 @@ describe('<LoginDialog /> component', () => {
|
||||
const props = {
|
||||
onClose: jest.fn(),
|
||||
};
|
||||
const { container } = render(
|
||||
<AppContext.Provider value={appContextValue}>
|
||||
<LoginDialog onClose={props.onClose} />
|
||||
</AppContext.Provider>
|
||||
);
|
||||
const { container } = renderWithStore(<LoginDialog onClose={props.onClose} />, store);
|
||||
expect(container.firstChild).toMatchSnapshot();
|
||||
});
|
||||
|
||||
@@ -44,10 +35,9 @@ describe('<LoginDialog /> component', () => {
|
||||
onClose: jest.fn(),
|
||||
};
|
||||
|
||||
const { getByTestId } = render(
|
||||
<AppContext.Provider value={appContextValue}>
|
||||
<LoginDialog onClose={props.onClose} open={props.open} />
|
||||
</AppContext.Provider>
|
||||
const { getByTestId } = renderWithStore(
|
||||
<LoginDialog onClose={props.onClose} open={props.open} />,
|
||||
store
|
||||
);
|
||||
|
||||
const loginDialogHeading = await waitFor(() => getByTestId('login-dialog-form-login-button'));
|
||||
@@ -60,18 +50,18 @@ describe('<LoginDialog /> component', () => {
|
||||
onClose: jest.fn(),
|
||||
};
|
||||
|
||||
const { getByTestId } = render(
|
||||
<AppContext.Provider value={appContextValue}>
|
||||
<LoginDialog onClose={props.onClose} open={props.open} />
|
||||
</AppContext.Provider>
|
||||
const { getByTestId } = renderWithStore(
|
||||
<LoginDialog onClose={props.onClose} open={props.open} />,
|
||||
store
|
||||
);
|
||||
|
||||
const loginDialogButton = await waitFor(() => getByTestId('close-login-dialog-button'));
|
||||
expect(loginDialogButton).toBeTruthy();
|
||||
|
||||
act(() => {
|
||||
await act(() => {
|
||||
fireEvent.click(loginDialogButton, { open: false });
|
||||
});
|
||||
|
||||
expect(props.onClose).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -88,11 +78,9 @@ describe('<LoginDialog /> component', () => {
|
||||
})
|
||||
);
|
||||
|
||||
render(
|
||||
<AppContext.Provider value={appContextValue}>
|
||||
<LoginDialog onClose={props.onClose} open={props.open} />
|
||||
</AppContext.Provider>
|
||||
);
|
||||
await act(async () => {
|
||||
renderWithStore(<LoginDialog onClose={props.onClose} open={props.open} />, store);
|
||||
});
|
||||
|
||||
const userNameInput = screen.getByPlaceholderText('Your username');
|
||||
expect(userNameInput).toBeInTheDocument();
|
||||
@@ -104,13 +92,18 @@ describe('<LoginDialog /> component', () => {
|
||||
const passwordInput = screen.getByPlaceholderText('Your strong password');
|
||||
expect(userNameInput).toBeInTheDocument();
|
||||
fireEvent.focus(passwordInput);
|
||||
fireEvent.change(passwordInput, { target: { value: '1234' } });
|
||||
|
||||
act(async () => {
|
||||
const signInButton = await screen.getByTestId('login-dialog-form-login-button');
|
||||
expect(signInButton).not.toBeDisabled();
|
||||
await act(async () => {
|
||||
fireEvent.change(passwordInput, { target: { value: '1234' } });
|
||||
});
|
||||
const signInButton = screen.getByTestId('login-dialog-form-login-button');
|
||||
expect(signInButton).not.toBeDisabled();
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(signInButton);
|
||||
});
|
||||
expect(props.onClose).toHaveBeenCalledTimes(1);
|
||||
// screen.debug();
|
||||
});
|
||||
|
||||
test.todo('validateCredentials: should validate credentials');
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
import i18next from 'i18next';
|
||||
import isEmpty from 'lodash/isEmpty';
|
||||
import React, { useState, useContext, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import React, { useEffect, useCallback } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
import Dialog from 'verdaccio-ui/components/Dialog';
|
||||
import DialogContent from 'verdaccio-ui/components/DialogContent';
|
||||
import { useAPI, LoginBody } from 'verdaccio-ui/providers/API/APIProvider';
|
||||
import { LoginError } from 'verdaccio-ui/utils/login';
|
||||
import storage from 'verdaccio-ui/utils/storage';
|
||||
|
||||
import AppContext from '../../../App/AppContext';
|
||||
import { LoginBody } from '../../../store/models/login';
|
||||
import { Dispatch, RootState } from '../../../store/store';
|
||||
|
||||
import LoginDialogCloseButton from './LoginDialogCloseButton';
|
||||
import LoginDialogForm, { FormValues } from './LoginDialogForm';
|
||||
@@ -21,62 +19,48 @@ interface Props {
|
||||
}
|
||||
|
||||
const LoginDialog: React.FC<Props> = ({ onClose, open = false }) => {
|
||||
const { t } = useTranslation();
|
||||
const appContext = useContext(AppContext);
|
||||
const { doLogin } = useAPI();
|
||||
|
||||
const loginStore = useSelector((state: RootState) => state.login);
|
||||
const dispatch = useDispatch<Dispatch>();
|
||||
const makeLogin = useCallback(
|
||||
async (username?: string, password?: string): Promise<LoginBody> => {
|
||||
async (username?: string, password?: string): Promise<LoginBody | void> => {
|
||||
// checks isEmpty
|
||||
if (isEmpty(username) || isEmpty(password)) {
|
||||
const error = {
|
||||
dispatch.login.addError({
|
||||
type: 'error',
|
||||
description: i18next.t('form-validation.username-or-password-cant-be-empty'),
|
||||
};
|
||||
return { error };
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response: LoginBody = await doLogin(username as string, password as string);
|
||||
|
||||
return response;
|
||||
dispatch.login.getUser({ username, password });
|
||||
// const response: LoginBody = await doLogin(username as string, password as string);
|
||||
dispatch.login.clearError();
|
||||
} catch (e: any) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error('login error', e.message);
|
||||
const error = {
|
||||
dispatch.login.addError({
|
||||
type: 'error',
|
||||
description: i18next.t('form-validation.unable-to-sign-in'),
|
||||
};
|
||||
return { error };
|
||||
});
|
||||
// eslint-disable-next-line no-console
|
||||
console.error('login error', e.message);
|
||||
}
|
||||
},
|
||||
[doLogin]
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
if (!appContext) {
|
||||
throw Error(t('app-context-not-correct-used'));
|
||||
}
|
||||
|
||||
const [error, setError] = useState<LoginError>();
|
||||
|
||||
const handleDoLogin = useCallback(
|
||||
async (data: FormValues) => {
|
||||
const { username, token, error } = await makeLogin(data.username, data.password);
|
||||
|
||||
if (error) {
|
||||
setError(error);
|
||||
}
|
||||
|
||||
if (username && token) {
|
||||
storage.setItem('username', username);
|
||||
storage.setItem('token', token);
|
||||
appContext.setUser({ username });
|
||||
onClose();
|
||||
}
|
||||
await makeLogin(data.username, data.password);
|
||||
},
|
||||
[appContext, onClose, makeLogin]
|
||||
[makeLogin]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (loginStore.token && typeof loginStore.error === 'undefined') {
|
||||
onClose();
|
||||
}
|
||||
}, [loginStore, onClose]);
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
data-testid="login--dialog"
|
||||
@@ -88,7 +72,7 @@ const LoginDialog: React.FC<Props> = ({ onClose, open = false }) => {
|
||||
<LoginDialogCloseButton onClose={onClose} />
|
||||
<DialogContent>
|
||||
<LoginDialogHeader />
|
||||
<LoginDialogForm error={error} onSubmit={handleDoLogin} />
|
||||
<LoginDialogForm error={loginStore.error} onSubmit={handleDoLogin} />
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
|
||||
@@ -18,7 +18,6 @@ describe('<RegistryInfoContent /> component', () => {
|
||||
const props = { registryUrl: 'http://localhost:4872', scope: '@' };
|
||||
render(<RegistryInfoContent registryUrl={props.registryUrl} scope={props.scope} />);
|
||||
|
||||
screen.debug();
|
||||
expect(screen.getByText('pnpm set @:registry http://localhost:4872')).toBeInTheDocument();
|
||||
expect(screen.getByText('pnpm adduser --registry http://localhost:4872')).toBeInTheDocument();
|
||||
expect(
|
||||
|
||||
@@ -2,7 +2,9 @@ import React from 'react';
|
||||
import { BrowserRouter as Router } from 'react-router-dom';
|
||||
|
||||
import api from 'verdaccio-ui/providers/API/api';
|
||||
import { render, fireEvent, waitFor } from 'verdaccio-ui/utils/test-react-testing-library';
|
||||
import { renderWithStore, fireEvent, waitFor } from 'verdaccio-ui/utils/test-react-testing-library';
|
||||
|
||||
import { store } from '../../../store/store';
|
||||
|
||||
jest.mock('lodash/debounce', () =>
|
||||
jest.fn((fn) => {
|
||||
@@ -41,12 +43,15 @@ describe('<Search /> component', () => {
|
||||
});
|
||||
|
||||
test('should load the component in default state', () => {
|
||||
const { container } = render(<ComponentToBeRendered />);
|
||||
const { container } = renderWithStore(<ComponentToBeRendered />, store);
|
||||
expect(container.firstChild).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('handleSearch: when user type package name in search component, show suggestions', async () => {
|
||||
const { getByPlaceholderText, getAllByText } = render(<ComponentToBeRendered />);
|
||||
const { getByPlaceholderText, getAllByText } = renderWithStore(
|
||||
<ComponentToBeRendered />,
|
||||
store
|
||||
);
|
||||
|
||||
const autoCompleteInput = getByPlaceholderText('Search Packages');
|
||||
|
||||
@@ -62,7 +67,10 @@ describe('<Search /> component', () => {
|
||||
});
|
||||
|
||||
test('onBlur: should cancel all search requests', async () => {
|
||||
const { getByPlaceholderText, getByRole, getAllByText } = render(<ComponentToBeRendered />);
|
||||
const { getByPlaceholderText, getByRole, getAllByText } = renderWithStore(
|
||||
<ComponentToBeRendered />,
|
||||
store
|
||||
);
|
||||
|
||||
const autoCompleteInput = getByPlaceholderText('Search Packages');
|
||||
|
||||
@@ -80,7 +88,7 @@ describe('<Search /> component', () => {
|
||||
});
|
||||
|
||||
test('handleSearch: cancel all search requests when there is no value in search component with type method', async () => {
|
||||
const { getByPlaceholderText, getByRole } = render(<ComponentToBeRendered />);
|
||||
const { getByPlaceholderText, getByRole } = renderWithStore(<ComponentToBeRendered />, store);
|
||||
|
||||
const autoCompleteInput = getByPlaceholderText('Search Packages');
|
||||
fireEvent.focus(autoCompleteInput);
|
||||
@@ -92,7 +100,7 @@ describe('<Search /> component', () => {
|
||||
});
|
||||
|
||||
test('handleSearch: when method is not type method', async () => {
|
||||
const { getByPlaceholderText, getByRole } = render(<ComponentToBeRendered />);
|
||||
const { getByPlaceholderText, getByRole } = renderWithStore(<ComponentToBeRendered />, store);
|
||||
|
||||
const autoCompleteInput = getByPlaceholderText('Search Packages');
|
||||
|
||||
@@ -105,7 +113,7 @@ describe('<Search /> component', () => {
|
||||
});
|
||||
|
||||
test('handleSearch: loading is been displayed', async () => {
|
||||
const { getByPlaceholderText, getByText } = render(<ComponentToBeRendered />);
|
||||
const { getByPlaceholderText, getByText } = renderWithStore(<ComponentToBeRendered />, store);
|
||||
const autoCompleteInput = getByPlaceholderText('Search Packages');
|
||||
|
||||
fireEvent.focus(autoCompleteInput);
|
||||
@@ -117,7 +125,10 @@ describe('<Search /> component', () => {
|
||||
});
|
||||
|
||||
test('handlePackagesClearRequested: should clear suggestions', async () => {
|
||||
const { getByPlaceholderText, getAllByText, getByRole } = render(<ComponentToBeRendered />);
|
||||
const { getByPlaceholderText, getAllByText, getByRole } = renderWithStore(
|
||||
<ComponentToBeRendered />,
|
||||
store
|
||||
);
|
||||
const autoCompleteInput = getByPlaceholderText('Search Packages');
|
||||
|
||||
fireEvent.focus(autoCompleteInput);
|
||||
@@ -135,7 +146,10 @@ describe('<Search /> component', () => {
|
||||
});
|
||||
|
||||
test('handleClickSearch: should change the window location on click or return key', async () => {
|
||||
const { getByPlaceholderText, getAllByText, getByRole } = render(<ComponentToBeRendered />);
|
||||
const { getByPlaceholderText, getAllByText, getByRole } = renderWithStore(
|
||||
<ComponentToBeRendered />,
|
||||
store
|
||||
);
|
||||
const autoCompleteInput = getByPlaceholderText('Search Packages');
|
||||
|
||||
fireEvent.focus(autoCompleteInput);
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import debounce from 'lodash/debounce';
|
||||
import React, { useState, FormEvent, useCallback, useRef, useEffect } from 'react';
|
||||
import React, { useState, FormEvent, useCallback } from 'react';
|
||||
import { SuggestionSelectedEventData } from 'react-autosuggest';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { RouteComponentProps, withRouter } from 'react-router';
|
||||
|
||||
import AutoComplete from 'verdaccio-ui/components/AutoComplete';
|
||||
import { useAPI } from 'verdaccio-ui/providers/API/APIProvider';
|
||||
|
||||
import { Dispatch, RootState } from '../../../store/store';
|
||||
|
||||
import SearchAdornment from './SearchAdornment';
|
||||
|
||||
@@ -16,22 +18,18 @@ const CONSTANTS = {
|
||||
|
||||
const Search: React.FC<RouteComponentProps> = ({ history }) => {
|
||||
const { t } = useTranslation();
|
||||
const [suggestions, setSuggestions] = useState([]);
|
||||
const [loaded, setLoaded] = useState(false);
|
||||
const [search, setSearch] = useState('');
|
||||
const [error, setError] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const mountedRef = useRef(true);
|
||||
const [requestList, setRequestList] = useState<{ abort: () => void }[]>([]);
|
||||
const { callSearch } = useAPI();
|
||||
|
||||
// const mountedRef = useRef(true);
|
||||
const { isError, suggestions } = useSelector((state: RootState) => state.search);
|
||||
const isLoading = useSelector((state: RootState) => state?.loading?.models.search);
|
||||
const dispatch = useDispatch<Dispatch>();
|
||||
/**
|
||||
* Cancel all the requests which are in pending state.
|
||||
*/
|
||||
const cancelAllSearchRequests = useCallback(() => {
|
||||
requestList.forEach((request) => request.abort());
|
||||
setRequestList([]);
|
||||
}, [requestList, setRequestList]);
|
||||
dispatch.search.clearRequestQueue();
|
||||
}, [dispatch]);
|
||||
|
||||
/**
|
||||
* As user focuses out from input, we cancel all the request from requestList
|
||||
@@ -41,12 +39,9 @@ const Search: React.FC<RouteComponentProps> = ({ history }) => {
|
||||
(event: FormEvent<HTMLInputElement>) => {
|
||||
// stops event bubbling
|
||||
event.stopPropagation();
|
||||
setLoaded(false);
|
||||
setLoading(false);
|
||||
setError(false);
|
||||
cancelAllSearchRequests();
|
||||
},
|
||||
[setLoaded, setLoading, cancelAllSearchRequests, setError]
|
||||
[cancelAllSearchRequests]
|
||||
);
|
||||
|
||||
/**
|
||||
@@ -58,9 +53,6 @@ const Search: React.FC<RouteComponentProps> = ({ history }) => {
|
||||
event.stopPropagation();
|
||||
if (method === 'type') {
|
||||
const value = newValue.trim();
|
||||
|
||||
setLoading(true);
|
||||
setError(false);
|
||||
setSearch(value);
|
||||
setLoaded(false);
|
||||
/**
|
||||
@@ -79,8 +71,8 @@ const Search: React.FC<RouteComponentProps> = ({ history }) => {
|
||||
* Cancel all the request from list and make request list empty.
|
||||
*/
|
||||
const handlePackagesClearRequested = useCallback(() => {
|
||||
setSuggestions([]);
|
||||
}, [setSuggestions]);
|
||||
dispatch.search.saveSearch({ suggestions: [] });
|
||||
}, [dispatch]);
|
||||
|
||||
/**
|
||||
* When an user select any package by clicking or pressing return key.
|
||||
@@ -108,46 +100,12 @@ const Search: React.FC<RouteComponentProps> = ({ history }) => {
|
||||
* For AbortController see: https://developer.mozilla.org/en-US/docs/Web/API/AbortController
|
||||
*/
|
||||
const handleFetchPackages = useCallback(
|
||||
async ({ value }: { value: string }) => {
|
||||
try {
|
||||
const controller = new window.AbortController();
|
||||
const signal = controller.signal;
|
||||
if (!mountedRef.current) {
|
||||
return null;
|
||||
}
|
||||
// Keep track of search requests.
|
||||
setRequestList([...requestList, controller]);
|
||||
const suggestions = await callSearch(value, signal);
|
||||
// FIXME: Argument of type 'unknown' is not assignable to parameter of type 'SetStateAction<never[]>'
|
||||
setSuggestions(suggestions as any);
|
||||
setLoaded(true);
|
||||
} catch (error: any) {
|
||||
/**
|
||||
* AbortError is not the API error.
|
||||
* It means browser has cancelled the API request.
|
||||
*/
|
||||
if (error.name === CONSTANTS.ABORT_ERROR) {
|
||||
setError(false);
|
||||
setLoaded(false);
|
||||
} else {
|
||||
setError(true);
|
||||
setLoaded(false);
|
||||
}
|
||||
} finally {
|
||||
if (mountedRef.current) {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
({ value }: { value: string }) => {
|
||||
dispatch.search.getSuggestions({ value });
|
||||
},
|
||||
[requestList, setRequestList, setSuggestions, setLoaded, setError, setLoading, callSearch]
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
mountedRef.current = false;
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<AutoComplete
|
||||
onBlur={handleOnBlur}
|
||||
@@ -158,9 +116,9 @@ const Search: React.FC<RouteComponentProps> = ({ history }) => {
|
||||
placeholder={t('search.packages')}
|
||||
startAdornment={<SearchAdornment />}
|
||||
suggestions={suggestions}
|
||||
suggestionsError={error}
|
||||
suggestionsError={isError}
|
||||
suggestionsLoaded={loaded}
|
||||
suggestionsLoading={loading}
|
||||
suggestionsLoading={isLoading}
|
||||
value={search}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -1,2 +1 @@
|
||||
export { default } from './App';
|
||||
export { default as AppContextProvider } from './AppContextProvider';
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import React from 'react';
|
||||
|
||||
import { render, cleanup } from 'verdaccio-ui/utils/test-react-testing-library';
|
||||
import { renderWithStore, cleanup } from 'verdaccio-ui/utils/test-react-testing-library';
|
||||
|
||||
import { DetailContext, DetailContextProps } from '../../pages/Version';
|
||||
import { store } from '../../store/store';
|
||||
|
||||
import ActionBar from './ActionBar';
|
||||
|
||||
@@ -44,7 +45,10 @@ describe('<ActionBar /> component', () => {
|
||||
});
|
||||
|
||||
test('should render the component in default state', () => {
|
||||
const { container } = render(<ComponentToBeRendered contextValue={detailContextValue} />);
|
||||
const { container } = renderWithStore(
|
||||
<ComponentToBeRendered contextValue={detailContextValue} />,
|
||||
store
|
||||
);
|
||||
expect(container.firstChild).toMatchSnapshot();
|
||||
});
|
||||
|
||||
@@ -62,22 +66,25 @@ describe('<ActionBar /> component', () => {
|
||||
},
|
||||
};
|
||||
|
||||
const { container } = render(
|
||||
<ComponentToBeRendered contextValue={{ ...detailContextValue, packageMeta }} />
|
||||
const { container } = renderWithStore(
|
||||
<ComponentToBeRendered contextValue={{ ...detailContextValue, packageMeta }} />,
|
||||
store
|
||||
);
|
||||
expect(container.firstChild).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('when there is a button to download a tarball', () => {
|
||||
const { getByTitle } = render(
|
||||
<ComponentToBeRendered contextValue={{ ...detailContextValue }} />
|
||||
const { getByTitle } = renderWithStore(
|
||||
<ComponentToBeRendered contextValue={{ ...detailContextValue }} />,
|
||||
store
|
||||
);
|
||||
expect(getByTitle('Download tarball')).toBeTruthy();
|
||||
});
|
||||
|
||||
test('when there is a button to open an issue', () => {
|
||||
const { getByTitle } = render(
|
||||
<ComponentToBeRendered contextValue={{ ...detailContextValue }} />
|
||||
const { getByTitle } = renderWithStore(
|
||||
<ComponentToBeRendered contextValue={{ ...detailContextValue }} />,
|
||||
store
|
||||
);
|
||||
expect(getByTitle('Open an issue')).toBeTruthy();
|
||||
});
|
||||
|
||||
@@ -4,11 +4,11 @@ import DownloadIcon from '@material-ui/icons/CloudDownload';
|
||||
import HomeIcon from '@material-ui/icons/Home';
|
||||
import React, { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
import { Theme } from 'verdaccio-ui/design-tokens/theme';
|
||||
import { useAPI } from 'verdaccio-ui/providers/API/APIProvider';
|
||||
import { extractFileName, downloadFile } from 'verdaccio-ui/utils/url';
|
||||
|
||||
import { Dispatch } from '../../store/store';
|
||||
import FloatingActionButton from '../FloatingActionButton';
|
||||
import Link from '../Link';
|
||||
import Tooltip from '../Tooltip';
|
||||
@@ -34,13 +34,11 @@ export interface ActionBarActionProps {
|
||||
/* eslint-disable react/jsx-no-bind */
|
||||
const ActionBarAction: React.FC<ActionBarActionProps> = ({ type, link }) => {
|
||||
const { t } = useTranslation();
|
||||
const { getResource } = useAPI();
|
||||
const dispatch = useDispatch<Dispatch>();
|
||||
|
||||
const handleDownload = useCallback(async () => {
|
||||
const fileStream = await getResource(link);
|
||||
const fileName = extractFileName(link);
|
||||
downloadFile(fileStream, fileName);
|
||||
}, [getResource, link]);
|
||||
dispatch.download.getTarball({ link });
|
||||
}, [dispatch, link]);
|
||||
|
||||
switch (type) {
|
||||
case 'VISIT_HOMEPAGE':
|
||||
|
||||
@@ -51,7 +51,6 @@ describe('<Author /> component', () => {
|
||||
};
|
||||
|
||||
const wrapper = render(withAuthorComponent(packageMeta));
|
||||
wrapper.debug();
|
||||
expect(wrapper.queryAllByText('verdaccio')).toHaveLength(0);
|
||||
});
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ interface Props<Option extends {}> extends Pick<TextFieldProps, 'variant'> {
|
||||
inputStartAdornment?: React.ReactNode;
|
||||
hasClearIcon?: boolean;
|
||||
className?: string;
|
||||
onClick?: (option: Option) => void;
|
||||
onClick?: (option: any) => void;
|
||||
}
|
||||
|
||||
const AutoComplete = <Option extends {}>({
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { SvgIcon } from '../SvgIcon';
|
||||
|
||||
type Props = React.ComponentProps<typeof SvgIcon>;
|
||||
|
||||
const Austria = React.forwardRef(function Austria(props: Props, ref: React.Ref<SVGSVGElement>) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<SvgIcon viewBox="0 0 512 512" {...props} ref={ref} title={t('flag.austria')}>
|
||||
<path
|
||||
d="M473.655 88.276H38.345C17.167 88.276 0 105.443 0 126.621v73.471h512v-73.471c0-21.178-17.167-38.345-38.345-38.345zM0 385.379c0 21.177 17.167 38.345 38.345 38.345h435.31c21.177 0 38.345-17.167 38.345-38.345v-73.471H0v73.471z"
|
||||
fill="#ff4b55"
|
||||
/>
|
||||
<path fill="#f5f5f5" d="M0 200.09h512V311.9H0z" />
|
||||
</SvgIcon>
|
||||
);
|
||||
});
|
||||
|
||||
Austria.displayName = 'Austria';
|
||||
|
||||
export { Austria };
|
||||
@@ -1,44 +0,0 @@
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { SvgIcon } from '../SvgIcon';
|
||||
|
||||
type Props = React.ComponentProps<typeof SvgIcon>;
|
||||
|
||||
const Brazil = React.forwardRef(function Brazil(props: Props, ref: React.Ref<SVGSVGElement>) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<SvgIcon viewBox="0 0 45 45" {...props} ref={ref} title={t('flag.brazil')}>
|
||||
<defs>
|
||||
<clipPath id="prefix__a">
|
||||
<path d="M0 36h36V0H0v36z" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
<g clipPath="url(#prefix__a)" transform="matrix(1.25 0 0 -1.25 0 45)">
|
||||
<path
|
||||
d="M36 9a4 4 0 00-4-4H4a4 4 0 00-4 4v18a4 4 0 004 4h28a4 4 0 004-4V9z"
|
||||
fill="#009b3a"
|
||||
/>
|
||||
<path d="M32.727 18L18 6.876 3.27 18 18 29.125 32.727 18z" fill="#fedf01" />
|
||||
<path
|
||||
d="M24.434 18.076a6.458 6.458 0 11-12.917 0 6.458 6.458 0 0112.917 0"
|
||||
fill="#002776"
|
||||
/>
|
||||
<path
|
||||
d="M12.277 21.113a6.406 6.406 0 01-.672-2.023c3.994.29 9.417-1.892 11.744-4.596.402.604.7 1.28.882 2.004-2.871 2.809-7.916 4.63-11.954 4.615"
|
||||
fill="#cbe9d4"
|
||||
/>
|
||||
<path d="M13 16.767h-1v1h1v-1zm1-2h-1v1h1v-1z" fill="#88c9f9" />
|
||||
<path
|
||||
d="M16 16.767h-1v1h1v-1zm2-1h-1v1h1v-1zm4-2h-1v1h1v-1zm-3-1h-1v1h1v-1zm3 6h-1v1h1v-1z"
|
||||
fill="#55acee"
|
||||
/>
|
||||
<path d="M20 14.767h-1v1h1v-1z" fill="#3b88c3" />
|
||||
</g>
|
||||
</SvgIcon>
|
||||
);
|
||||
});
|
||||
|
||||
Brazil.displayName = 'Brazil';
|
||||
|
||||
export { Brazil };
|
||||
@@ -1,33 +0,0 @@
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { SvgIcon } from '../SvgIcon';
|
||||
|
||||
type Props = React.ComponentProps<typeof SvgIcon>;
|
||||
|
||||
const China = React.forwardRef(function China(props: Props, ref: React.Ref<SVGSVGElement>) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<SvgIcon viewBox="0 0 45 45" {...props} ref={ref} title={t('flag.china')}>
|
||||
<defs>
|
||||
<clipPath id="prefix__a">
|
||||
<path d="M0 36h36V0H0v36z" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
<g clipPath="url(#prefix__a)" transform="matrix(1.25 0 0 -1.25 0 45)">
|
||||
<path
|
||||
d="M36 9a4 4 0 00-4-4H4a4 4 0 00-4 4v18a4 4 0 004 4h28a4 4 0 004-4V9z"
|
||||
fill="#de2910"
|
||||
/>
|
||||
<path
|
||||
d="M7 25.049l.929-2.67 2.826-.06-2.253-1.706.819-2.707L7 19.52l-2.321-1.615.819 2.707-2.253 1.707 2.826.059.929 2.67zm6 3.423l.34-.688.759-.11-.549-.536.129-.756-.679.357-.679-.357.13.756-.55.536.76.11.339.688zm2-4l.34-.688.759-.11-.549-.536.129-.756-.679.357-.679-.357.13.756-.55.536.76.11.339.688zm0-4l.34-.688.759-.11-.549-.536.129-.756-.679.357-.679-.357.13.756-.55.536.76.11.339.688zm-2-3.999l.34-.69.759-.11-.549-.534.129-.757-.679.357-.679-.357.13.757-.55.535.76.11.339.689z"
|
||||
fill="#ffde02"
|
||||
/>
|
||||
</g>
|
||||
</SvgIcon>
|
||||
);
|
||||
});
|
||||
|
||||
China.displayName = 'China';
|
||||
|
||||
export { China };
|
||||
@@ -1,21 +0,0 @@
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { SvgIcon } from '../SvgIcon';
|
||||
|
||||
type Props = React.ComponentProps<typeof SvgIcon>;
|
||||
|
||||
const Czech = React.forwardRef(function Czech(props: Props, ref: React.Ref<SVGSVGElement>) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<SvgIcon viewBox="0 0 45 45" {...props} ref={ref} title={t('flag.czech')}>
|
||||
<path fill="#d7141a" d="M0 22.5h45v16H0z" />
|
||||
<path fill="#fff" d="M0 6.5h45v16H0z" />
|
||||
<path d="M22.5 22.5L0 6.5v32z" fill="#11457e" />
|
||||
</SvgIcon>
|
||||
);
|
||||
});
|
||||
|
||||
Czech.displayName = 'Czech';
|
||||
|
||||
export { Czech };
|
||||
@@ -1,27 +0,0 @@
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { SvgIcon } from '../SvgIcon';
|
||||
|
||||
type Props = React.ComponentProps<typeof SvgIcon>;
|
||||
|
||||
const France = React.forwardRef(function France(props: Props, ref: React.Ref<SVGSVGElement>) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<SvgIcon viewBox="0 0 512 512" {...props} ref={ref} title={t('flag.france')}>
|
||||
<path
|
||||
d="M38.345 88.273C17.167 88.273 0 105.44 0 126.618v258.759c0 21.177 17.167 38.345 38.345 38.345h132.322V88.273H38.345z"
|
||||
fill="#41479b"
|
||||
/>
|
||||
<path fill="#f5f5f5" d="M170.67 88.277h170.67v335.45H170.67z" />
|
||||
<path
|
||||
d="M473.655 88.273H341.333v335.448h132.322c21.177 0 38.345-17.167 38.345-38.345V126.618c0-21.178-17.167-38.345-38.345-38.345z"
|
||||
fill="#ff4b55"
|
||||
/>
|
||||
</SvgIcon>
|
||||
);
|
||||
});
|
||||
|
||||
France.displayName = 'France';
|
||||
|
||||
export { France };
|
||||
@@ -1,27 +0,0 @@
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { SvgIcon } from '../SvgIcon';
|
||||
|
||||
type Props = React.ComponentProps<typeof SvgIcon>;
|
||||
|
||||
const Germany = React.forwardRef(function Germany(props: Props, ref: React.Ref<SVGSVGElement>) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<SvgIcon viewBox="0 0 512 512" {...props} ref={ref} title={t('flag.germany')}>
|
||||
<path
|
||||
d="M473.655 88.276H38.345C17.167 88.276 0 105.443 0 126.621v73.471h512v-73.471c0-21.178-17.167-38.345-38.345-38.345z"
|
||||
fill="#464655"
|
||||
/>
|
||||
<path
|
||||
d="M0 385.379c0 21.177 17.167 38.345 38.345 38.345h435.31c21.177 0 38.345-17.167 38.345-38.345v-73.471H0v73.471z"
|
||||
fill="#ffe15a"
|
||||
/>
|
||||
<path fill="#ff4b55" d="M0 200.09h512V311.9H0z" />
|
||||
</SvgIcon>
|
||||
);
|
||||
});
|
||||
|
||||
Germany.displayName = 'Germany';
|
||||
|
||||
export { Germany };
|
||||
@@ -1,44 +0,0 @@
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { SvgIcon } from '../SvgIcon';
|
||||
|
||||
type Props = React.ComponentProps<typeof SvgIcon>;
|
||||
|
||||
const India = React.forwardRef(function India(props: Props, ref: React.Ref<SVGSVGElement>) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<SvgIcon viewBox="0 0 512 512" {...props} ref={ref} title={t('flag.india')}>
|
||||
<path
|
||||
d="M0 384c0 31.418 25.473 56.889 56.889 56.889H455.11c31.42 0 56.89-25.473 56.89-56.889v-56.889H0V384z"
|
||||
fill="#138808"
|
||||
/>
|
||||
<path d="M0 327.111h512V184.89H0v142.22z" fill="#eee" />
|
||||
<path
|
||||
d="M512 184.889V128c0-31.417-25.473-56.889-56.889-56.889H56.89C25.472 71.111 0 96.583 0 128v56.889h512z"
|
||||
fill="#f93"
|
||||
/>
|
||||
<path
|
||||
d="M312.889 256c0-31.431-25.473-56.902-56.903-56.902-31.417 0-56.888 25.472-56.888 56.902 0 31.418 25.472 56.889 56.888 56.889 31.432 0 56.903-25.472 56.903-56.889"
|
||||
fill="navy"
|
||||
/>
|
||||
<path
|
||||
d="M298.666 256c0-23.566-19.115-42.681-42.681-42.681S213.319 232.434 213.319 256s19.1 42.666 42.666 42.666 42.681-19.1 42.681-42.666"
|
||||
fill="#eee"
|
||||
/>
|
||||
<path
|
||||
d="M256 213.334l2.076 32.199 14.251-28.943-10.396 30.535 24.235-21.305-21.291 24.249 30.535-10.396-28.942 14.25L298.666 256l-32.198 2.076 28.942 14.237-30.535-10.383 21.291 24.235-24.235-21.29 10.396 30.535-14.25-28.942L256 298.666l-2.076-32.198-14.252 28.942 10.397-30.535-24.249 21.291 21.305-24.235-30.535 10.383 28.942-14.236L213.334 256l32.199-2.076-28.943-14.251 30.535 10.396-21.305-24.249 24.249 21.305-10.396-30.535 14.25 28.943 2.077-32.2z"
|
||||
fill="navy"
|
||||
opacity={0.6}
|
||||
/>
|
||||
<path
|
||||
d="M241.778 256c0-7.851 6.37-14.223 14.222-14.223s14.223 6.372 14.223 14.223-6.372 14.223-14.223 14.223-14.223-6.372-14.223-14.223"
|
||||
fill="navy"
|
||||
/>
|
||||
</SvgIcon>
|
||||
);
|
||||
});
|
||||
|
||||
India.displayName = 'India';
|
||||
|
||||
export { India };
|
||||
@@ -1,23 +0,0 @@
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { SvgIcon } from '../SvgIcon';
|
||||
|
||||
type Props = React.ComponentProps<typeof SvgIcon>;
|
||||
|
||||
const Japan = React.forwardRef(function Japan(props: Props, ref: React.Ref<SVGSVGElement>) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<SvgIcon viewBox="0 0 512 512" {...props} ref={ref} title={t('flag.japan')}>
|
||||
<path
|
||||
d="M473.655 88.275H38.345C17.167 88.275 0 105.442 0 126.62v258.76c0 21.177 17.167 38.345 38.345 38.345h435.31c21.177 0 38.345-17.167 38.345-38.345V126.62c0-21.178-17.167-38.345-38.345-38.345z"
|
||||
fill="#f5f5f5"
|
||||
/>
|
||||
<circle cx={256} cy={255.999} r={97.1} fill="#ff4b55" />
|
||||
</SvgIcon>
|
||||
);
|
||||
});
|
||||
|
||||
Japan.displayName = 'Japan';
|
||||
|
||||
export { Japan };
|
||||
@@ -1,40 +0,0 @@
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { SvgIcon } from '../SvgIcon';
|
||||
|
||||
type Props = React.ComponentProps<typeof SvgIcon>;
|
||||
|
||||
const Khmer = React.forwardRef(function Khmer(props: Props, ref: React.Ref<SVGSVGElement>) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<SvgIcon viewBox="0 0 512 512" {...props} ref={ref} title={t('flag.khmer')}>
|
||||
<path
|
||||
d="M0 304.8v64.8a48.3 48.3 0 0048 48.8h400c26.4 0 48-21.6 48-48.8v-64.8H0zM448 77.6H48c-26.4 0-48 21.6-48 48.8v64.8h496v-64.8a48.3 48.3 0 00-48-48.8z"
|
||||
fill="#032ea1"
|
||||
/>
|
||||
<path fill="#e00025" d="M0 189.6h496v116H0z" />
|
||||
<path
|
||||
d="M448 418.4c26.4 0 48-21.6 48-48.8v-64.8H315.2L448 418.4zm0-340.8H48l132.8 113.6H496v-64.8a48.3 48.3 0 00-48-48.8z"
|
||||
fill="#042a7f"
|
||||
/>
|
||||
<path fill="#b50030" d="M316 306.4h180V189.6H180z" />
|
||||
<path fill="#002566" d="M448 77.6H48l370.4 113.6H496v-64.8a48.3 48.3 0 00-48-48.8z" />
|
||||
<path fill="#8c002b" d="M496 214.4v-24.8h-81.6z" />
|
||||
<path fill="#002566" d="M496 369.6a48.3 48.3 0 01-48 48.8H48c-26.4 0-48-21.6-48-48.8" />
|
||||
<g fill="#fff">
|
||||
<path d="M249.6 200s0-2.4-1.6-2.4-1.6 2.4-1.6 2.4h3.2zm72 90.4v-7.2h-24 20v-6.4h-4V272h-16H312v-4h-3.2c-.8-.8-2.4-1.6-2.4-3.2v-12c.8-1.6 1.6-3.2 2.4-3.2v-11.2c-1.6 0-2.4 1.6-2.4 1.6v-4.8h-1.6v9.6-6.4h-2.4 2.4v-1.6h-2.4v-5.6c-.8 0-.8 2.4-2.4 2.4-.8 0-.8-.8 0-1.6s.8-1.6 0-4c-.8 1.6-1.6 1.6-1.6.8.8-1.6 1.6-1.6.8-4 0 2.4-1.6 2.4-1.6.8 0-1.6.8-1.6 0-4-.8 2.4-1.6 2.4-1.6.8 0-2.4 0-4-2.4-4.8 0 0 0-1.6-.8-1.6s-.8 1.6-.8 1.6c-2.4.8-2.4 3.2-2.4 4.8s-.8 1.6-1.6-.8c-.8 1.6 0 2.4 0 4s-.8 1.6-1.6-.8c-.8 2.4 0 2.4.8 4 0 .8-.8.8-1.6-.8-.8 1.6 0 3.2 0 4 .8.8.8 1.6 0 1.6-1.6 0-1.6-2.4-2.4-2.4v9.6H280v-.8h-1.6v.8H264V232c-1.6.8-1.6 1.6-1.6 1.6v7.2-5.6h-1.6s-.8 0-.8-1.6.8-2.4 1.6-2.4V228s-3.2 0-3.2 4v-3.2c-.8 0-.8.8-.8 4h-2.4 1.6v-3.2c0-1.6 1.6-1.6 1.6-3.2V224c-.8 2.4-1.6 1.6-1.6.8s.8-1.6 2.4-4c.8-.8 0-2.4-.8-3.2 0 1.6-.8 2.4-1.6 2.4s-.8 0-.8-1.6c0-.8.8-1.6.8-3.2.8-1.6 0-2.4-.8-3.2 0 1.6 0 2.4-.8 2.4-1.6-.8 0-3.2 0-3.2 0-.8-.8-2.4-.8-2.4-.8 1.6-.8 1.6-1.6 1.6s-.8-1.6 0-2.4c.8-.8.8-1.6 0-1.6-.8 1.6-1.6.8-1.6 0l.8-2.4h-10.4l.8 2.4c0 .8-.8.8-1.6 0-.8.8-.8 1.6 0 1.6.8.8.8 1.6 0 2.4-.8 0-.8 0-1.6-1.6 0 0-.8 1.6-.8 2.4 0 .8 1.6 3.2 0 3.2-.8 0-.8-.8-.8-2.4-.8.8-.8 1.6-.8 3.2 0 .8.8 1.6.8 3.2 0 .8-.8 1.6-.8 1.6-.8 0-.8-.8-1.6-2.4-.8.8-.8 3.2-.8 3.2 1.6 2.4 2.4 3.2 2.4 4s-.8 1.6-1.6-.8c-.8.8 0 2.4 0 2.4 0 1.6 1.6 1.6 1.6 3.2v3.2h1.6-2.4c0-3.2 0-4-.8-4v3.2c0-3.2-3.2-3.2-3.2-3.2v3.2c.8 0 1.6.8 1.6 2.4s-.8 1.6-.8 1.6h-1.6v5.6-7.2s0-1.6-1.6-1.6v8.8h-14.4V240h-1.6v.8h-3.2v-9.6c-.8 0-.8 2.4-2.4 2.4-.8 0-.8-.8 0-1.6s.8-1.6 0-4c-.8 1.6-1.6 1.6-1.6.8.8-1.6 1.6-1.6.8-4 0 2.4-1.6 2.4-1.6.8 0-1.6.8-1.6 0-4-.8 2.4-1.6 2.4-1.6.8 0-2.4 0-4-2.4-4.8 0 0 0-1.6-.8-1.6s-.8 1.6-.8 1.6c-2.4.8-2.4 3.2-2.4 4.8s-.8 1.6-1.6-.8c-.8 1.6 0 2.4 0 4s-.8 1.6-1.6-.8c-.8 2.4 0 2.4.8 4 0 .8-.8.8-1.6-.8-.8 1.6 0 3.2 0 4 .8.8.8 1.6 0 1.6-1.6 0-1.6-2.4-2.4-2.4v5.6h-2.4v-.8h-1.6v4s-.8-1.6-2.4-1.6v12c.8 0 2.4 1.6 2.4 3.2v12c0 1.6-1.6 2.4-2.4 3.2h-.8v3.2h13.6-16v4.8h-4v6.4h20-24v7.2h-4v7.2h156v-7.2h-4zm-16.8-25.6zm-2.4-17.6v.8-.8zm-3.2.8h4-4zm-105.6-9.6h-2.4 2.4zm3.2 10.4h-3.2 3.2zm12-.8h4-4zm-.8 24h35.2H208zm35.2 11.2H208h35.2zm.8-50.4zm8 0zm32 15.2h3.2-3.2c-.8.8-.8 0 0 0zm4 35.2h-36 36zm0-11.2h-36 36z" />
|
||||
<path d="M252.8 205.6v-2.4h-9.6v2.4zm-.8-2.4v-1.6h-8v1.6zm-1.6-1.6V200h-4.8v1.6z" />
|
||||
</g>
|
||||
<g fill="#cecece">
|
||||
<path d="M231.2 232v2.4l1.6 1.6v-1.6c0-.8 0-2.4-1.6-2.4z" />
|
||||
<path d="M321.6 283.2h-24 20v-6.4h-4V272h-16H312v-4h-3.2c-.8-.8-2.4-1.6-2.4-3.2v-12c.8-1.6 1.6-3.2 2.4-3.2v-11.2c-1.6 0-2.4 1.6-2.4 1.6v-4.8h-1.6v9.6-6.4h-2.4 2.4v-1.6h-2.4v-5.6c-.8 0-.8 2.4-2.4 2.4-.8 0-.8-.8 0-1.6s.8-1.6 0-4c-.8 1.6-1.6 1.6-1.6.8.8-1.6 1.6-1.6.8-4 0 2.4-1.6 2.4-1.6.8 0-1.6.8-1.6 0-4-.8 2.4-1.6 2.4-1.6.8 0-2.4 0-4-2.4-4.8 0 0 0-1.6-.8-1.6s-.8 1.6-.8 1.6c-2.4.8-2.4 3.2-2.4 4.8s-.8 1.6-1.6-.8c-.8 1.6 0 2.4 0 4s-.8 1.6-1.6-.8c-.8 2.4 0 2.4.8 4 0 .8-.8.8-1.6-.8-.8 1.6 0 3.2 0 4 .8.8.8 1.6 0 1.6-1.6 0-1.6-2.4-2.4-2.4v9.6H280v-.8h-1.6v.8H264V232c-1.6.8-1.6 1.6-1.6 1.6v7.2-5.6h-1.6s-.8 0-.8-1.6.8-2.4 1.6-2.4V228s-3.2 0-3.2 4v-3.2c-.8 0-.8.8-.8 4h-.8v-3.2c0-1.6 1.6-1.6 1.6-3.2V224c-.8 2.4-1.6 1.6-1.6.8s.8-1.6 2.4-4c.8-.8 0-2.4-.8-3.2 0 1.6-.8 2.4-1.6 2.4s-.8 0-.8-1.6c0-.8.8-1.6.8-3.2.8-1.6 0-2.4-.8-3.2 0 1.6 0 2.4-.8 2.4-1.6-.8 0-3.2 0-3.2 0-.8-.8-2.4-.8-2.4-.8 1.6-.8 1.6-1.6 1.6s-.8-1.6 0-2.4c.8-.8.8-1.6 0-1.6-.8 1.6-1.6.8-1.6 0l.8-2.4h-10.4l.8 2.4c0 .8-.8.8-1.6 0-.8.8-.8 1.6 0 1.6.8.8.8 1.6 0 2.4-.8 0-.8 0-1.6-1.6 0 0-.8 1.6-.8 2.4 0 .8 1.6 3.2 0 3.2-.8 0-.8-.8-.8-2.4-.8.8-.8 1.6-.8 3.2 0 .8.8 1.6.8 3.2 0 .8-.8 1.6-.8 1.6-.8 0-.8-.8-1.6-2.4-.8.8-.8 3.2-.8 3.2 1.6 2.4 2.4 3.2 2.4 4s-.8 1.6-1.6-.8c-.8.8 0 2.4 0 2.4 0 1.6 1.6 1.6 1.6 3.2v3.2h-.8c0-3.2 0-4-.8-4v3.2c0-3.2-3.2-3.2-3.2-3.2v3.2c.8 0 1.6.8 1.6 2.4s-.8 1.6-.8 1.6h-1.6l42.4 36.8H288h-12l31.2 26.4h19.2V292h-4v-8.8h-.8zm-19.2-36v.8-.8zm-3.2.8h4-4zm-12 .8h-4 4zM248 197.6c-1.6 0-1.6 2.4-1.6 2.4h2.4c.8 0 .8-2.4-.8-2.4z" />
|
||||
<path d="M243.2 203.2v2.4h9.6v-2.4zm8.8 0v-1.6h-8v1.6zm-1.6-1.6V200h-4.8v1.6z" />
|
||||
</g>
|
||||
</SvgIcon>
|
||||
);
|
||||
});
|
||||
|
||||
Khmer.displayName = 'Khmer';
|
||||
|
||||
export { Khmer };
|
||||
@@ -1,66 +0,0 @@
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { SvgIcon } from '../SvgIcon';
|
||||
|
||||
type Props = React.ComponentProps<typeof SvgIcon>;
|
||||
|
||||
const Nicaragua = React.forwardRef(function Nicaragua(props: Props, ref: React.Ref<SVGSVGElement>) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<SvgIcon viewBox="0 0 512 512" {...props} ref={ref} title={t('flag.nicaragua')}>
|
||||
<path
|
||||
d="M512 384c0 31.418-25.473 56.889-56.889 56.889H56.89C25.472 440.889 0 415.417 0 384V128c0-31.418 25.472-56.889 56.889-56.889H455.11C486.53 71.111 512 96.584 512 128v256z"
|
||||
fill="#265fb5"
|
||||
/>
|
||||
<path d="M512 327.111H0V184.89h512v142.22z" fill="#eee" />
|
||||
<path
|
||||
d="M320.811 256c0 35.797-29.014 64.811-64.811 64.811-35.783 0-64.811-29.014-64.811-64.811s29.027-64.811 64.811-64.811c35.797 0 64.811 29.013 64.811 64.811"
|
||||
fill="#a9bf4c"
|
||||
/>
|
||||
<path
|
||||
d="M312.889 256c0 31.418-25.473 56.889-56.889 56.889S199.111 287.416 199.111 256s25.473-56.889 56.889-56.889 56.889 25.471 56.889 56.889"
|
||||
fill="#eee"
|
||||
/>
|
||||
<path d="M209.891 286.649l45.909-79.517 45.909 79.517H209.89z" fill="#265fb5" />
|
||||
<path d="M215.04 283.591l40.818-70.685 40.803 70.685H215.04z" fill="#55acee" />
|
||||
<path d="M215.04 283.591l9.841-17.052 61.483-.783 10.297 17.835H215.04z" fill="#bbddf5" />
|
||||
<path
|
||||
d="M222.891 272.441l15.331-12.445 6.67 7.553 5.774-6.215 4.893 4.892 4.665-5.12 5.561 5.12 5.106-5.334 4.665 5.334 4.451-4.892 7.325 9.102 1.338 2.674s-7.78 1.55-18.446.669c-10.667-.896-16.882 1.55-25.33 2.66-8.448 1.109-23.553-1.109-23.553-1.109l1.55-2.889z"
|
||||
fill="#5c913b"
|
||||
/>
|
||||
<path
|
||||
d="M237.995 262.67l10.226 11.107-5.12.442-5.774-8.434.668-3.115zm12.231.883l8.22 7.338-3.782.654-3.996-5.546-.442-2.446zm10.439-.442l7.111 6.67L266 270.89l-5.774-6.229.44-1.55zm9.33 0l-2.888 2.66 1.338 1.565 1.55-4.225zm8.889.228l-3.328 4.224 1.109 1.99 2.446-4.664-.227-1.55z"
|
||||
fill="#e2f09f"
|
||||
/>
|
||||
<path
|
||||
d="M256.426 233.671l.939 13.227 7.566-10.867-5.675 11.805 11.805-5.66-10.851 7.553 13.213.939-13.213.952 10.851 7.553-11.805-5.66 5.675 11.805-7.566-10.867-.939 13.226-.939-13.226-7.566 10.867 5.675-11.805-11.805 5.66 10.851-7.553-13.212-.953 13.212-.938-10.85-7.553 11.804 5.66-5.675-11.805 7.566 10.866.94-13.226z"
|
||||
fill="#bbddf5"
|
||||
/>
|
||||
<path
|
||||
d="M256 244.665l-2.66 2.66s.654 4.011.441 4.679C253.554 252.658 256 256 256 256l3.327-3.996-.88-5.334-2.447-2.005z"
|
||||
fill="#dd2e44"
|
||||
/>
|
||||
<path
|
||||
d="M257.28 240.071c10.894 0 17.109 5.334 17.109 5.334l-3.996-7.111s-6.443-4.893-13.995-4.893c-7.567 0-16 6.001-16 6.001l-3.783 7.553s9.771-6.884 20.665-6.884"
|
||||
fill="#269"
|
||||
/>
|
||||
<path
|
||||
d="M257.28 240.071c10.894 0 17.109 5.334 17.109 5.334l-2.888-5.106s-7.78-4.665-15.331-4.665-16.896 5.987-16.896 5.987l-2.66 5.334c.001 0 9.772-6.884 20.666-6.884"
|
||||
fill="#ffcc4d"
|
||||
/>
|
||||
<path
|
||||
d="M257.28 240.071c10.894 0 17.109 5.334 17.109 5.334l-2.005-3.327s-5.988-4.224-16.214-3.783c-7.553 0-18.005 5.561-18.005 5.561l-1.55 3.1c0-.001 9.771-6.885 20.665-6.885"
|
||||
fill="#dd2e44"
|
||||
/>
|
||||
<path
|
||||
d="M264.291 322.873h-14.165V309.29h14.165v13.582zm-16.426-118.898h-10.183l-5.106-12.459 10.198-1.137 5.091 13.596zm23.21 5.66l-6.784-2.261 3.385-16.426 13.028 1.137-9.629 17.55zm-61.141 69.646l-19.811 4.523-.57-13.583 19.812-2.83.569 11.89zm5.106 14.152l-16.426 6.812-3.954-10.766 15.289-5.106 5.091 9.06zm109.269-12.444l-18.105-5.091v-9.63l20.366-2.83-2.261 17.55zm-9.003 18.674l-16.981-7.937 5.646-10.182 18.703 5.66-7.368 12.459z"
|
||||
fill="#eee"
|
||||
/>
|
||||
</SvgIcon>
|
||||
);
|
||||
});
|
||||
|
||||
Nicaragua.displayName = 'Nicaragua';
|
||||
|
||||
export { Nicaragua };
|
||||
@@ -1,27 +0,0 @@
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { SvgIcon } from '../SvgIcon';
|
||||
|
||||
type Props = React.ComponentProps<typeof SvgIcon>;
|
||||
|
||||
const Russia = React.forwardRef(function Russia(props: Props, ref: React.Ref<SVGSVGElement>) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<SvgIcon viewBox="0 0 512 512" {...props} ref={ref} title={t('flag.russia')}>
|
||||
<path
|
||||
d="M473.655 88.276H38.345C17.167 88.276 0 105.443 0 126.621v73.471h512v-73.471c0-21.178-17.167-38.345-38.345-38.345z"
|
||||
fill="#f5f5f5"
|
||||
/>
|
||||
<path d="M0 200.09h512V311.9H0z" fill="#0039a6" />
|
||||
<path
|
||||
d="M0 385.379c0 21.177 17.167 38.345 38.345 38.345h435.31c21.177 0 38.345-17.167 38.345-38.345v-73.471H0v73.471z"
|
||||
fill="#d52b1e"
|
||||
/>
|
||||
</SvgIcon>
|
||||
);
|
||||
});
|
||||
|
||||
Russia.displayName = 'Russia';
|
||||
|
||||
export { Russia };
|
||||
@@ -1,43 +0,0 @@
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { SvgIcon } from '../SvgIcon';
|
||||
|
||||
type Props = React.ComponentProps<typeof SvgIcon>;
|
||||
|
||||
const Spain = React.forwardRef(function Spain(props: Props, ref: React.Ref<SVGSVGElement>) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<SvgIcon viewBox="0 0 45 45" {...props} ref={ref} title={t('flag.spain')}>
|
||||
<defs>
|
||||
<clipPath id="prefix__a">
|
||||
<path d="M0 36h36V0H0v36z" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
<g clipPath="url(#prefix__a)" transform="matrix(1.25 0 0 -1.25 0 45)">
|
||||
<path
|
||||
d="M36 9a4 4 0 00-4-4H4a4 4 0 00-4 4v18a4 4 0 004 4h28a4 4 0 004-4V9z"
|
||||
fill="#c60a1d"
|
||||
/>
|
||||
<path d="M36 12H0v12h36V12z" fill="#ffc400" />
|
||||
<path d="M9 19v-3a3 3 0 116 0v3H9z" fill="#ea596e" />
|
||||
<path d="M12 17h3v3h-3v-3z" fill="#f4a2b2" />
|
||||
<path d="M12 17H9v3h3v-3z" fill="#dd2e44" />
|
||||
<path
|
||||
d="M15 21.5c0-.829-1.343-1.5-3-1.5s-3 .671-3 1.5 1.343 1.5 3 1.5 3-.671 3-1.5"
|
||||
fill="#ea596e"
|
||||
/>
|
||||
<path
|
||||
d="M15 22.25c0 .414-1.343.75-3 .75s-3-.336-3-.75 1.343-.75 3-.75 3 .336 3 .75"
|
||||
fill="#ffac33"
|
||||
/>
|
||||
<path d="M7 13h1v7H7v-7zm10 0h-1v7h1v-7z" fill="#99aab5" />
|
||||
<path d="M9 13H6v1h3v-1zm9 0h-3v1h3v-1zM8 20H7v1h1v-1zm9 0h-1v1h1v-1z" fill="#66757f" />
|
||||
</g>
|
||||
</SvgIcon>
|
||||
);
|
||||
});
|
||||
|
||||
Spain.displayName = 'Spain';
|
||||
|
||||
export { Spain };
|
||||
@@ -1,25 +0,0 @@
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { SvgIcon } from '../SvgIcon';
|
||||
|
||||
type Props = React.ComponentProps<typeof SvgIcon>;
|
||||
|
||||
const Taiwan = React.forwardRef(function Taiwan(props: Props, ref: React.Ref<SVGSVGElement>) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<SvgIcon viewBox="-60 -40 240 160" {...props} ref={ref} title={t('flag.taiwan')}>
|
||||
<rect x={-60} y={-40} width="100%" height="100%" fill="#fe0000" />
|
||||
<rect x={-60} y={-40} width="50%" height="50%" fill="#000095" />
|
||||
<path id="prefix__a" d="M8 0L0 30-8 0l8-30M0 8l30-8L0-8l-30 8" fill="#fff" />
|
||||
<use xlinkHref="#prefix__a" transform="rotate(30)" />
|
||||
<use xlinkHref="#prefix__a" transform="rotate(60)" />
|
||||
<circle r={17} fill="#000095" />
|
||||
<circle r={15} fill="#fff" />
|
||||
</SvgIcon>
|
||||
);
|
||||
});
|
||||
|
||||
Taiwan.displayName = 'Taiwan';
|
||||
|
||||
export { Taiwan };
|
||||
@@ -1,23 +0,0 @@
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { SvgIcon } from '../SvgIcon';
|
||||
|
||||
type Props = React.ComponentProps<typeof SvgIcon>;
|
||||
|
||||
const Turkey = React.forwardRef(function Turkey(props: Props, ref: React.Ref<SVGSVGElement>) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<SvgIcon viewBox="0 0 512 512" {...props} ref={ref} title={t('flag.turkey')}>
|
||||
<path fill="#d80027" d="M0 85.337h512v341.326H0z" />
|
||||
<g fill="#f0f0f0">
|
||||
<path d="M247.213 216.787l17.594 24.246 28.493-9.239-17.621 24.224 17.592 24.244-28.484-9.274-17.621 24.225.018-29.955-28.484-9.275 28.494-9.239z" />
|
||||
<path d="M199.202 316.602c-33.469 0-60.602-27.133-60.602-60.602s27.133-60.602 60.602-60.602c10.436 0 20.254 2.639 28.827 7.284-13.448-13.152-31.84-21.269-52.135-21.269-41.193 0-74.586 33.394-74.586 74.586s33.394 74.586 74.586 74.586c20.295 0 38.687-8.117 52.135-21.269-8.572 4.647-18.391 7.286-28.827 7.286z" />
|
||||
</g>
|
||||
</SvgIcon>
|
||||
);
|
||||
});
|
||||
|
||||
Turkey.displayName = 'Turkey';
|
||||
|
||||
export { Turkey };
|
||||
@@ -1,30 +0,0 @@
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { SvgIcon } from '../SvgIcon';
|
||||
|
||||
type Props = React.ComponentProps<typeof SvgIcon>;
|
||||
|
||||
const Ukraine = React.forwardRef(function Ukraine(props: Props, ref: React.Ref<SVGSVGElement>) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<SvgIcon viewBox="0 0 512 512" {...props} ref={ref} title={t('flag.ukraine')}>
|
||||
<path
|
||||
d="M0 248v121.6C0 396.8 21.6 416 48 416h400c26.4 0 48-19.2 48-46.4V248H0z"
|
||||
fill="#fdce0c"
|
||||
/>
|
||||
<path d="M248 248l197.6 168c26.4 0 50.4-19.2 50.4-46.4V248H248z" fill="#f4ba00" />
|
||||
<path
|
||||
d="M448 80H48C21.6 80 0 99.2 0 126.4V248h496V126.4c0-27.2-21.6-46.4-48-46.4z"
|
||||
fill="#44c1ef"
|
||||
/>
|
||||
<path d="M448 80H48l200 168h248V126.4c0-27.2-21.6-46.4-48-46.4z" fill="#18b4ea" />
|
||||
<path d="M496 368.8c0 29.6-21.6 47.2-48 47.2H48c-26.4 0-48-20.8-48-48" fill="#f2a700" />
|
||||
<path d="M48 80h400c26.4 0 48 19.2 48 46.4V216" fill="#10a2e2" />
|
||||
</SvgIcon>
|
||||
);
|
||||
});
|
||||
|
||||
Ukraine.displayName = 'Ukraine';
|
||||
|
||||
export { Ukraine };
|
||||
File diff suppressed because one or more lines are too long
@@ -1,21 +1,5 @@
|
||||
export { Austria } from './Austria';
|
||||
export { Brazil } from './Brazil';
|
||||
export { China } from './China';
|
||||
export { Czech } from './Czech';
|
||||
export { France } from './France';
|
||||
export { Germany } from './Germany';
|
||||
export { India } from './India';
|
||||
export { Japan } from './Japan';
|
||||
export { Nicaragua } from './Nicaragua';
|
||||
export { Taiwan } from './Taiwan';
|
||||
export { Spain } from './Spain';
|
||||
export { Khmer } from './Khmer';
|
||||
export { Russia } from './Russia';
|
||||
export { Ukraine } from './Ukraine';
|
||||
export { Usa } from './Usa';
|
||||
export { Turkey } from './Turkey';
|
||||
export { Version } from './Version';
|
||||
export { Time } from './Time';
|
||||
export { Earth } from './Earth';
|
||||
export { FileBinary } from './FileBinary';
|
||||
export { Law } from './Law';
|
||||
export { Earth } from './Earth';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { createContext } from 'react';
|
||||
import { Language } from 'src/i18n/enabledLanguages';
|
||||
|
||||
import { Language } from '../../i18n/config';
|
||||
interface Props {
|
||||
isDarkMode: boolean;
|
||||
setIsDarkMode: (isDarkMode: boolean) => void;
|
||||
|
||||
13
packages/plugins/ui-theme/src/i18n/ABOUT_TRANSLATIONS.md
Normal file
13
packages/plugins/ui-theme/src/i18n/ABOUT_TRANSLATIONS.md
Normal file
@@ -0,0 +1,13 @@
|
||||
## Translate UI Strings
|
||||
|
||||
If you are willing to translate User Interfances strings, are located in the `crowdin` project which is available [here](https://crowdin.com/project/verdaccio) at `packages/plugins/src/i18n/crowdin/ui.json`.
|
||||
|
||||
### Requirements
|
||||
|
||||
- Need a crowdin account, use your GitHub account for fast access as an option.
|
||||
|
||||
### Adding a new language to the UI
|
||||
|
||||
- If the language is not available, ask in the Discord channel to enable it, that will be pretty fast.
|
||||
- After you have translated the strings, add to `packages/plugins/ui-theme/src/i18n/enabledLanguages.ts` the configuration for the new language.
|
||||
- In development mode the file must not be available since you need admin credentials to download translations, as a work around, create a folder withing the `crowdin` folder eg: `/packages/plugins/ui-theme/src/i18n/download_translations/fr-FR/ui.json` and then will be available for testing. If the language is not found, will fallback to `en-US`.
|
||||
52
packages/plugins/ui-theme/src/i18n/config.ts
Normal file
52
packages/plugins/ui-theme/src/i18n/config.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import i18n from 'i18next';
|
||||
import { initReactI18next } from 'react-i18next';
|
||||
|
||||
import {
|
||||
DEFAULT_LANGUAGE,
|
||||
LanguageConfiguration,
|
||||
listLanguages,
|
||||
listLanguagesAsString,
|
||||
} from './enabledLanguages';
|
||||
|
||||
/**
|
||||
* In development mode translations files might be not available,
|
||||
* crowdin translations are only available in CI.
|
||||
*/
|
||||
function loadTranslationFile(lng) {
|
||||
try {
|
||||
return require(`./download_translations/${lng}/ui.json`);
|
||||
} catch {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn(`language ${lng} file not found, fallback to en-US`);
|
||||
// in case the file is not there, fallback to en-US
|
||||
return require(`./crowdin/ui.json`);
|
||||
}
|
||||
}
|
||||
|
||||
const languages = listLanguages.reduce((acc, item: LanguageConfiguration) => {
|
||||
acc[item.lng] = {
|
||||
translation:
|
||||
item.lng === DEFAULT_LANGUAGE ? require(`./crowdin/ui.json`) : loadTranslationFile(item.lng),
|
||||
};
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
i18n
|
||||
// pass the i18n instance to react-i18next.
|
||||
.use(initReactI18next)
|
||||
// init i18next
|
||||
// for all options read: https://www.i18next.com/overview/configuration-options
|
||||
.init({
|
||||
// in case window.VEDACCIO_LANGUAGE is undefined,it will fall back to 'en-US'
|
||||
lng: window?.__VERDACCIO_BASENAME_UI_OPTIONS?.language || DEFAULT_LANGUAGE,
|
||||
fallbackLng: DEFAULT_LANGUAGE,
|
||||
whitelist: [...listLanguagesAsString],
|
||||
load: 'currentOnly',
|
||||
resources: languages,
|
||||
debug: false,
|
||||
interpolation: {
|
||||
escapeValue: false, // react already safes from xss
|
||||
},
|
||||
});
|
||||
|
||||
export default i18n;
|
||||
170
packages/plugins/ui-theme/src/i18n/crowdin/ui.json
Normal file
170
packages/plugins/ui-theme/src/i18n/crowdin/ui.json
Normal file
@@ -0,0 +1,170 @@
|
||||
{
|
||||
"copy-to-clipboard": "Copy to clipboard",
|
||||
"author-anonymous": "Anonymous",
|
||||
"author-unknown": "Unknown",
|
||||
"action-bar-action": {
|
||||
"visit-home-page": "Visit homepage",
|
||||
"open-an-issue": "Open an issue",
|
||||
"download-tarball": "Download tarball"
|
||||
},
|
||||
"dialog": {
|
||||
"registry-info": {
|
||||
"title": "Registry Info"
|
||||
}
|
||||
},
|
||||
"header": {
|
||||
"documentation": "Documentation",
|
||||
"registry-info": "Registry Information",
|
||||
"registry-info-link": "Learn more",
|
||||
"registry-no-conf": "No configurations available",
|
||||
"greetings": "Hi "
|
||||
},
|
||||
"search": {
|
||||
"packages": "Search Packages"
|
||||
},
|
||||
"autoComplete": {
|
||||
"loading": "Loading...",
|
||||
"no-results-found": "No results found",
|
||||
"clear": "Clear",
|
||||
"expand": "Expand",
|
||||
"collapse": "Collapse"
|
||||
},
|
||||
"tab": {
|
||||
"uplinks": "Uplinks",
|
||||
"versions": "Versions",
|
||||
"dependencies": "Dependencies",
|
||||
"readme": "Readme"
|
||||
},
|
||||
"uplinks": {
|
||||
"title": "Uplinks",
|
||||
"no-items": "{{name}} has no uplinks."
|
||||
},
|
||||
"versions": {
|
||||
"current-tags": "Current Tags",
|
||||
"version-history": "Version history",
|
||||
"not-available": "Not available"
|
||||
},
|
||||
"package": {
|
||||
"published-on": "Published on {{time}} •",
|
||||
"version": "v{{version}}",
|
||||
"visit-home-page": "Visit homepage",
|
||||
"homepage": "Homepage",
|
||||
"open-an-issue": "Open an issue",
|
||||
"bugs": "Bugs",
|
||||
"download": "Download {{what}}",
|
||||
"the-tar-file": "the tar file",
|
||||
"tarball": "Tarball"
|
||||
},
|
||||
"dependencies": {
|
||||
"has-no-dependencies": "{{package}} has no dependencies.",
|
||||
"dependency-block": "{{package}}@{{version}}"
|
||||
},
|
||||
"form": {
|
||||
"username": "Username",
|
||||
"password": "Password"
|
||||
},
|
||||
"form-placeholder": {
|
||||
"username": "Your username",
|
||||
"password": "Your strong password"
|
||||
},
|
||||
"form-validation": {
|
||||
"required-field": "This field is required",
|
||||
"required-min-length": "This field required the min length of {{length}}",
|
||||
"unable-to-sign-in": "Unable to sign in",
|
||||
"username-or-password-cant-be-empty": "Username or password can't be empty!"
|
||||
},
|
||||
"help": {
|
||||
"title": "No Package Published Yet.",
|
||||
"sub-title": "To publish your first package just:",
|
||||
"first-step": "1. Login",
|
||||
"first-step-command-line": "npm adduser --registry {{registryUrl}}",
|
||||
"second-step": "2. Publish",
|
||||
"second-step-command-line": "npm publish --registry {{registryUrl}}",
|
||||
"third-step": "3. Refresh this page"
|
||||
},
|
||||
"sidebar": {
|
||||
"detail": {
|
||||
"latest-version": "Latest v{{version}}",
|
||||
"version": "v{{version}}"
|
||||
},
|
||||
"installation": {
|
||||
"title": "Installation",
|
||||
"install-using-yarn": "Install using yarn",
|
||||
"install-using-yarn-command": "yarn add {{packageName}}",
|
||||
"install-using-npm": "Install using npm",
|
||||
"install-using-npm-command": "npm install {{packageName}}",
|
||||
"install-using-pnpm": "Install using pnpm",
|
||||
"install-using-pnpm-command": "pnpm install {{packageName}}"
|
||||
},
|
||||
"repository": {
|
||||
"title": "Repository"
|
||||
},
|
||||
"author": {
|
||||
"title": "Author"
|
||||
},
|
||||
"distribution": {
|
||||
"title": "Latest Distribution",
|
||||
"license": "License",
|
||||
"size": "Size",
|
||||
"file-count": "file count"
|
||||
},
|
||||
"maintainers": {
|
||||
"title": "Maintainers"
|
||||
},
|
||||
"contributors": {
|
||||
"title": "Contributors"
|
||||
},
|
||||
"engines": {
|
||||
"npm-version": "NPM Version",
|
||||
"node-js": "NODE JS"
|
||||
}
|
||||
},
|
||||
"footer": {
|
||||
"powered-by": "Powered by",
|
||||
"made-with-love-on": "Made with <0>♥</0> on"
|
||||
},
|
||||
"button": {
|
||||
"close": "Close",
|
||||
"cancel": "Cancel",
|
||||
"login": "Login",
|
||||
"logout": "Logout",
|
||||
"go-to-the-home-page": "Go to the home page",
|
||||
"learn-more": "Learn More",
|
||||
"fund-this-package": "<0>Fund</0> this package"
|
||||
},
|
||||
"error": {
|
||||
"unspecific": "Something went wrong.",
|
||||
"404": {
|
||||
"page-not-found": "404 - Page not found",
|
||||
"sorry-we-could-not-find-it": "Sorry, we couldn't find it..."
|
||||
},
|
||||
"app-context-not-correct-used": "The app context was not used correctly",
|
||||
"theme-context-not-correct-used": "The theme context was not used correctly",
|
||||
"package-meta-is-required-at-detail-context": "packageMeta is required at DetailContext"
|
||||
},
|
||||
"lng": {
|
||||
"english": "English",
|
||||
"czech": "Czech",
|
||||
"japanese": "Japanese",
|
||||
"portuguese": "Portuguese",
|
||||
"spanish": "Spanish",
|
||||
"german": "German",
|
||||
"chinese": "Chinese",
|
||||
"chineseTraditional": "Chinese (Traditional)",
|
||||
"french": "French",
|
||||
"russian": "Russian",
|
||||
"turkish": "Turkish",
|
||||
"ukraine": "Ukraine",
|
||||
"khmer": "Khmer"
|
||||
},
|
||||
"flag": {
|
||||
"austria": "Austria",
|
||||
"brazil": "Brazil",
|
||||
"spain": "Spain",
|
||||
"nicaragua": "Nicaragua",
|
||||
"india": "India",
|
||||
"china": "China",
|
||||
"germany": "Germany",
|
||||
"taiwan": "Taiwan"
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user