feat: theme as plugin (#1252)
* chore: remove ui * chore: remove size step * chore: update theme plugin * chore: update lock file * Update main.workflow * chore: update js-yaml dep * chore: @verdaccio/ui-theme@0.0.4 * feat: allows theme as a plugin * chore: update package description
@ -128,18 +128,6 @@ jobs:
|
||||
- run:
|
||||
name: Test End-to-End
|
||||
command: yarn run test:e2e
|
||||
|
||||
test_size:
|
||||
<<: *defaults
|
||||
<<: *default_executor
|
||||
steps:
|
||||
- *restore_repo
|
||||
- restore_cache:
|
||||
key: *base_config_key
|
||||
- run:
|
||||
name: Test size
|
||||
command: yarn test:size
|
||||
|
||||
coverage:
|
||||
<<: *defaults
|
||||
<<: *default_executor
|
||||
@ -192,17 +180,12 @@ workflows:
|
||||
requires:
|
||||
- prepare
|
||||
<<: *ignore_non_dev_branches
|
||||
- test_size:
|
||||
requires:
|
||||
- prepare
|
||||
<<: *ignore_non_dev_branches
|
||||
- coverage:
|
||||
requires:
|
||||
- test_node8
|
||||
- test_node10
|
||||
- test_node11
|
||||
- test_e2e
|
||||
- test_size
|
||||
<<: *ignore_non_dev_branches
|
||||
- publish_package:
|
||||
requires:
|
||||
|
70
.eslintrc
@ -1,7 +1,6 @@
|
||||
{
|
||||
"plugins": [
|
||||
"babel",
|
||||
"react",
|
||||
"flowtype",
|
||||
"jest",
|
||||
"verdaccio",
|
||||
@ -13,17 +12,9 @@
|
||||
"plugin:flowtype/recommended",
|
||||
"plugin:jest/recommended",
|
||||
"plugin:prettier/recommended",
|
||||
"plugin:react/recommended",
|
||||
"plugin:verdaccio/recommended",
|
||||
"plugin:jsx-a11y/recommended"
|
||||
],
|
||||
"settings": {
|
||||
"react": {
|
||||
"pragma": "React",
|
||||
"version": "16.4.2",
|
||||
"flowVersion": "0.81.0"
|
||||
}
|
||||
},
|
||||
"parser": "babel-eslint",
|
||||
"parserOptions": {
|
||||
"sourceType": "module",
|
||||
@ -45,67 +36,6 @@
|
||||
"rules": {
|
||||
"babel/no-invalid-this": 1,
|
||||
"prettier/prettier": ["error", null, "@prettier"],
|
||||
"react/no-deprecated": 1,
|
||||
"react/jsx-no-target-blank": 1,
|
||||
"react/destructuring-assignment": ["error", "always"],
|
||||
"react/forbid-component-props": ["warn", { "forbid": ["style"] }],
|
||||
"react/no-this-in-sfc": ["warn"],
|
||||
"react/no-unsafe": ["warn"],
|
||||
"react/sort-comp": ["warn", {
|
||||
"order": [
|
||||
"static-methods",
|
||||
"lifecycle",
|
||||
"render",
|
||||
"everything-else",
|
||||
"/^on.+$/",
|
||||
"/^render.+$/"
|
||||
]
|
||||
}],
|
||||
"react/void-dom-elements-no-children": ["warn"],
|
||||
"react/no-did-mount-set-state": ["error", "disallow-in-func"],
|
||||
"react/jsx-wrap-multilines": ["error",{
|
||||
"declaration": "parens",
|
||||
"assignment": "parens",
|
||||
"return": "parens",
|
||||
"arrow": "parens",
|
||||
"condition": "parens",
|
||||
"logical": "parens",
|
||||
"prop": "parens"
|
||||
}],
|
||||
"react/jsx-boolean-value": ["error", "always"],
|
||||
"react/jsx-closing-tag-location": ["error"],
|
||||
"react/jsx-curly-spacing": ["error", "never"],
|
||||
"react/jsx-equals-spacing": ["error", "never"],
|
||||
"react/jsx-first-prop-new-line": ["error", "multiline-multiprop"],
|
||||
"react/jsx-handler-names": ["warn"],
|
||||
"react/jsx-indent": ["error", 2],
|
||||
"react/jsx-indent-props": ["error", 2],
|
||||
"react/jsx-key": ["error"],
|
||||
"react/jsx-max-depth": ["error", { "max": 2}],
|
||||
"react/jsx-max-props-per-line": ["error", {"maximum": 3, "when": "multiline" }],
|
||||
"react/jsx-no-bind": ["error"],
|
||||
"react/jsx-no-comment-textnodes": ["error"],
|
||||
"react/jsx-no-duplicate-props": ["error"],
|
||||
"react/jsx-no-literals": ["error"],
|
||||
"react/jsx-no-undef": ["error"],
|
||||
"react/jsx-one-expression-per-line": ["error", {"allow": "single-child"}],
|
||||
"react/jsx-curly-brace-presence": ["error", { "props": "always", "children": "ignore" }],
|
||||
"react/jsx-pascal-case": ["error"],
|
||||
"react/jsx-props-no-multi-spaces": ["error"],
|
||||
"react/jsx-sort-default-props": ["error"],
|
||||
"react/jsx-sort-props": ["error"],
|
||||
"react/no-string-refs": ["error"],
|
||||
"react/no-danger-with-children": ["error"],
|
||||
"react/jsx-tag-spacing": ["error", {
|
||||
"closingSlash": "never",
|
||||
"beforeSelfClosing": "always",
|
||||
"afterOpening": "allow-multiline",
|
||||
"beforeClosing": "allow"
|
||||
}],
|
||||
"react/prefer-es6-class": [
|
||||
2,
|
||||
"always"
|
||||
],
|
||||
"semi": ["error"],
|
||||
"comma-dangle": ["error"],
|
||||
"camelcase": 0,
|
||||
|
3
.github/main.workflow
vendored
@ -9,6 +9,9 @@ workflow "New workflow" {
|
||||
action "Docker build health check" {
|
||||
uses = "actions/docker/cli@8cdf801b322af5f369e00d85e9cf3a7122f49108"
|
||||
args = "build ."
|
||||
env = {
|
||||
VERDACCIO_BUILD_REGISTRY = "https://registry.verdaccio.org"
|
||||
}
|
||||
}
|
||||
|
||||
action "Test Publish Verdaccio" {
|
||||
|
@ -16,7 +16,6 @@ RUN yarn config set registry $VERDACCIO_BUILD_REGISTRY && \
|
||||
yarn install --production=false --no-lockfile && \
|
||||
yarn lint && \
|
||||
yarn code:docker-build && \
|
||||
yarn build:webui && \
|
||||
yarn cache clean && \
|
||||
yarn install --production=true --no-lockfile
|
||||
|
||||
|
@ -53,10 +53,10 @@ Parameters:
|
||||
Default: verdaccio/verdaccio:3
|
||||
AsgMinSize:
|
||||
Type: String
|
||||
Default: "1"
|
||||
Default: '1'
|
||||
AsgMaxSize:
|
||||
Type: String
|
||||
Default: "1"
|
||||
Default: '1'
|
||||
AsgInstanceType:
|
||||
Type: String
|
||||
Default: t3.nano
|
||||
@ -185,14 +185,14 @@ Resources:
|
||||
Type: AWS::ElasticLoadBalancingV2::TargetGroup
|
||||
Properties:
|
||||
HealthCheckIntervalSeconds: 5
|
||||
HealthCheckPort: "4873"
|
||||
HealthCheckProtocol: "HTTP"
|
||||
HealthCheckPort: '4873'
|
||||
HealthCheckProtocol: 'HTTP'
|
||||
HealthCheckTimeoutSeconds: 2
|
||||
Port: 4873
|
||||
Protocol: "HTTP"
|
||||
Protocol: 'HTTP'
|
||||
TargetGroupAttributes:
|
||||
- Key: deregistration_delay.timeout_seconds
|
||||
Value: "10"
|
||||
Value: '10'
|
||||
VpcId:
|
||||
Ref: Vpc
|
||||
|
||||
|
@ -4,12 +4,8 @@ module.exports = {
|
||||
name: 'verdaccio-unit-jest',
|
||||
verbose: true,
|
||||
collectCoverage: true,
|
||||
testEnvironment: 'jest-environment-jsdom-global',
|
||||
testURL: 'http://localhost',
|
||||
testRegex: '(test/unit.*\\.spec|test/unit/webui/.*\\.spec)\\.js',
|
||||
setupFiles: [
|
||||
'./test/unit/setup.js'
|
||||
],
|
||||
// Some unit tests rely on data folders that look like packages. This confuses jest-hast-map
|
||||
// when it tries to scan for package.json files.
|
||||
modulePathIgnorePatterns: [
|
||||
@ -39,14 +35,5 @@ module.exports = {
|
||||
'fixtures',
|
||||
'<rootDir>/src/api/debug',
|
||||
'<rootDir>/test',
|
||||
],
|
||||
moduleNameMapper: {
|
||||
'\\.(s?css)$': '<rootDir>/node_modules/identity-obj-proxy',
|
||||
'github-markdown-css': '<rootDir>/node_modules/identity-obj-proxy',
|
||||
'\\.(png)$': '<rootDir>/node_modules/identity-obj-proxy',
|
||||
'\\.(svg)$': '<rootDir>/test/unit/empty.js'
|
||||
},
|
||||
transformIgnorePatterns: [
|
||||
'<rootDir>/node_modules/(?!react-syntax-highlighter)'
|
||||
]
|
||||
};
|
||||
|
112
package.json
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "verdaccio",
|
||||
"version": "4.0.0-alpha.6",
|
||||
"description": "Private npm repository server",
|
||||
"description": "npm private proxy registry server",
|
||||
"author": {
|
||||
"name": "Verdaccio Maintainers",
|
||||
"email": "verdaccio.npm@gmail.com"
|
||||
@ -15,8 +15,9 @@
|
||||
"verdaccio": "./bin/verdaccio"
|
||||
},
|
||||
"dependencies": {
|
||||
"@verdaccio/local-storage": "2.1.0",
|
||||
"@verdaccio/streams": "2.0.0",
|
||||
"@verdaccio/local-storage": "2.0.0-beta.3",
|
||||
"@verdaccio/streams": "2.0.0-beta.0",
|
||||
"@verdaccio/ui-theme": "0.0.4",
|
||||
"JSONStream": "1.3.5",
|
||||
"async": "3.0.1-0",
|
||||
"body-parser": "1.18.3",
|
||||
@ -47,92 +48,39 @@
|
||||
"request": "2.88.0",
|
||||
"semver": "5.6.0",
|
||||
"verdaccio-audit": "1.1.0",
|
||||
"verdaccio-htpasswd": "2.0.0-beta.1",
|
||||
"verror": "1.10.0"
|
||||
"verdaccio-htpasswd": "2.0.0-beta.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@commitlint/cli": "7.5.2",
|
||||
"@commitlint/config-conventional": "7.5.0",
|
||||
"@material-ui/core": "3.9.0",
|
||||
"@material-ui/icons": "3.0.2",
|
||||
"@verdaccio/babel-preset": "0.1.0",
|
||||
"@verdaccio/types": "5.0.0-beta.4",
|
||||
"autosuggest-highlight": "3.1.1",
|
||||
"bundlesize": "0.17.1",
|
||||
"@verdaccio/babel-preset": "0.0.4",
|
||||
"@verdaccio/types": "5.0.0-beta.3",
|
||||
"codecov": "3.2.0",
|
||||
"cross-env": "5.2.0",
|
||||
"css-loader": "0.28.10",
|
||||
"emotion": "9.2.12",
|
||||
"enzyme": "3.9.0",
|
||||
"enzyme-adapter-react-16": "1.10.0",
|
||||
"eslint": "5.14.1",
|
||||
"eslint-config-google": "0.12.0",
|
||||
"eslint-config-prettier": "4.1.0",
|
||||
"eslint-loader": "2.1.2",
|
||||
"eslint-plugin-babel": "5.3.0",
|
||||
"eslint-plugin-flowtype": "3.4.2",
|
||||
"eslint-plugin-import": "2.16.0",
|
||||
"eslint-plugin-jest": "22.3.0",
|
||||
"eslint-plugin-jsx-a11y": "6.2.1",
|
||||
"eslint-plugin-prettier": "3.0.1",
|
||||
"eslint-plugin-react": "7.11.1",
|
||||
"eslint-plugin-verdaccio": "0.0.5",
|
||||
"file-loader": "2.0.0",
|
||||
"flow-bin": "0.81.0",
|
||||
"flow-runtime": "0.17.0",
|
||||
"friendly-errors-webpack-plugin": "1.7.0",
|
||||
"github-markdown-css": "2.10.0",
|
||||
"html-webpack-plugin": "3.2.0",
|
||||
"husky": "0.15.0-rc.8",
|
||||
"identity-obj-proxy": "3.0.0",
|
||||
"in-publish": "2.0.0",
|
||||
"jest": "24.1.0",
|
||||
"jest-environment-jsdom": "24.0.0",
|
||||
"jest-environment-jsdom-global": "1.1.1",
|
||||
"jest-environment-node": "24.0.0",
|
||||
"lint-staged": "7.3.0",
|
||||
"localstorage-memory": "1.0.3",
|
||||
"mini-css-extract-plugin": "0.5.0",
|
||||
"node-mocks-http": "1.7.3",
|
||||
"node-sass": "4.11.0",
|
||||
"normalize.css": "8.0.1",
|
||||
"optimize-css-assets-webpack-plugin": "5.0.1",
|
||||
"ora": "1.4.0",
|
||||
"prettier": "1.14.3",
|
||||
"prop-types": "15.7.2",
|
||||
"puppeteer": "1.8.0",
|
||||
"react": "16.8.3",
|
||||
"react-autosuggest": "9.4.2",
|
||||
"react-dom": "16.8.3",
|
||||
"react-emotion": "9.2.12",
|
||||
"react-hot-loader": "4.7.1",
|
||||
"react-router": "4.3.1",
|
||||
"react-router-dom": "4.3.1",
|
||||
"resolve-url-loader": "3.0.1",
|
||||
"rimraf": "2.6.3",
|
||||
"sass-loader": "7.1.0",
|
||||
"source-map-loader": "0.2.4",
|
||||
"standard-version": "4.4.0",
|
||||
"style-loader": "0.23.1",
|
||||
"stylelint": "9.10.1",
|
||||
"stylelint-config-recommended": "2.1.0",
|
||||
"stylelint-config-recommended-scss": "3.2.0",
|
||||
"stylelint-config-styled-components": "0.1.1",
|
||||
"stylelint-processor-styled-components": "1.5.2",
|
||||
"stylelint-scss": "3.5.4",
|
||||
"stylelint-webpack-plugin": "0.10.5",
|
||||
"supertest": "3.4.2",
|
||||
"typeface-roboto": "0.0.54",
|
||||
"url-loader": "1.1.2",
|
||||
"verdaccio-auth-memory": "0.0.4",
|
||||
"verdaccio-memory": "2.0.0",
|
||||
"webpack": "4.20.2",
|
||||
"webpack-bundle-analyzer": "3.0.4",
|
||||
"webpack-cli": "3.2.3",
|
||||
"webpack-dev-server": "3.2.1",
|
||||
"webpack-merge": "4.2.1",
|
||||
"whatwg-fetch": "3.0.0",
|
||||
"xss": "1.0.3"
|
||||
"verdaccio-memory": "2.0.0-beta.0"
|
||||
},
|
||||
"keywords": [
|
||||
"private",
|
||||
@ -147,7 +95,7 @@
|
||||
],
|
||||
"scripts": {
|
||||
"release": "standard-version -a -s",
|
||||
"prepublish": "in-publish && npm run build:webui && npm run code:build || not-in-publish",
|
||||
"prepublish": "in-publish && npm run code:build || not-in-publish",
|
||||
"flow": "flow check",
|
||||
"pretest": "npm run code:build",
|
||||
"test": "npm run test:unit",
|
||||
@ -155,24 +103,18 @@
|
||||
"test:unit": "cross-env NODE_ENV=test BABEL_ENV=test TZ=UTC FORCE_COLOR=1 jest --config ./jest.config.js --maxWorkers 2 --passWithNoTests",
|
||||
"test:functional": "cross-env NODE_ENV=test jest --config ./test/jest.config.functional.js --testPathPattern ./test/functional/index* --passWithNoTests",
|
||||
"test:e2e": "cross-env BABEL_ENV=test jest --config ./test/jest.config.e2e.js",
|
||||
"test:size": "bundlesize",
|
||||
"test:all": "npm run build:webui && npm run test && npm run test:functional && npm run test:e2e && npm run test:size",
|
||||
"pre:ci": "npm run lint && npm run build:webui",
|
||||
"test:all": "npm run test && npm run test:functional && npm run test:e2e",
|
||||
"pre:ci": "npm run lint",
|
||||
"coverage:publish": "codecov",
|
||||
"lint": "npm run flow && npm run lint:js && npm run lint:css",
|
||||
"lint": "npm run flow && npm run lint:js",
|
||||
"lint:js": "eslint .",
|
||||
"lint:css": "stylelint 'src/webui/**/styles.js'",
|
||||
"dev:start": "cross-env BABEL_ENV=registry babel-node src/lib/cli",
|
||||
"code:build": "cross-env BABEL_ENV=registry babel src/ --out-dir build/ --ignore src/webui/ --copy-files",
|
||||
"code:docker-build": "cross-env BABEL_ENV=registry-docker babel src/ --out-dir build/ --ignore src/webui/ --copy-files",
|
||||
"pre:webpack": "rimraf static/*",
|
||||
"dev:webui": "cross-env BABEL_ENV=ui babel-node tools/dev.server.js",
|
||||
"build:webui": "npm run pre:webpack && cross-env BABEL_ENV=ui webpack --config tools/webpack.prod.config.babel.js",
|
||||
"build:docker": "docker build -t verdaccio . --no-cache",
|
||||
"build:docker:rpi": "docker build -f Dockerfile.rpi -t verdaccio:rpi ."
|
||||
"code:build": "cross-env BABEL_ENV=registry babel src/ --out-dir build/ --copy-files",
|
||||
"code:docker-build": "cross-env BABEL_ENV=registry-docker babel src/ --out-dir build/ --copy-files",
|
||||
"build:docker": "docker build -t verdaccio . --no-cache"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.15.0",
|
||||
"node": ">=8",
|
||||
"npm": ">=5"
|
||||
},
|
||||
"preferGlobal": true,
|
||||
@ -198,28 +140,6 @@
|
||||
"*.json"
|
||||
]
|
||||
},
|
||||
"bundlesize": [
|
||||
{
|
||||
"path": "./static/vendor*.js",
|
||||
"maxSize": "200 kB"
|
||||
},
|
||||
{
|
||||
"path": "./static/[0-9].*.js",
|
||||
"maxSize": "20 kB"
|
||||
},
|
||||
{
|
||||
"path": "./static/[1-9].*.css",
|
||||
"maxSize": "5 kB"
|
||||
},
|
||||
{
|
||||
"path": "./static/0.*.css",
|
||||
"maxSize": "45 kB"
|
||||
},
|
||||
{
|
||||
"path": "./build/**/*.js",
|
||||
"maxSize": "5.90 kB"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"commitlint": {
|
||||
"extends": [
|
||||
|
@ -4,42 +4,47 @@
|
||||
|
||||
import _ from 'lodash';
|
||||
import fs from 'fs';
|
||||
|
||||
import path from 'path';
|
||||
import VError from 'verror';
|
||||
import chalk from 'chalk';
|
||||
import express from 'express';
|
||||
|
||||
import { combineBaseUrl, getWebProtocol } from '../../lib/utils';
|
||||
import Search from '../../lib/search';
|
||||
import { HEADERS, HTTP_STATUS, WEB_TITLE } from '../../lib/constants';
|
||||
import loadPlugin from '../../lib/plugin-loader';
|
||||
|
||||
const { securityIframe } = require('../middleware');
|
||||
/* eslint new-cap:off */
|
||||
const env = require('../../config/env');
|
||||
const templatePath = path.join(env.DIST_PATH, '/index.html');
|
||||
const existTemplate = fs.existsSync(templatePath);
|
||||
|
||||
if (!existTemplate) {
|
||||
const err = new VError('missing file: "%s", run `yarn build:webui`', templatePath);
|
||||
/* eslint no-console:off */
|
||||
console.error(chalk.red(err.message));
|
||||
/* eslint no-console:off */
|
||||
process.exit(2);
|
||||
export function loadTheme(config) {
|
||||
if (_.isNil(config.theme) === false) {
|
||||
return _.head(
|
||||
loadPlugin(
|
||||
config,
|
||||
config.theme,
|
||||
{},
|
||||
function(plugin) {
|
||||
return _.isString(plugin);
|
||||
},
|
||||
'verdaccio-theme'
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const template = fs.readFileSync(templatePath).toString();
|
||||
|
||||
module.exports = function(config, auth, storage) {
|
||||
Search.configureStorage(storage);
|
||||
|
||||
/* eslint new-cap:off */
|
||||
const router = express.Router();
|
||||
|
||||
router.use(auth.webUIJWTmiddleware());
|
||||
router.use(securityIframe);
|
||||
const themePath = loadTheme(config) || require('@verdaccio/ui-theme')();
|
||||
const indexTemplate = path.join(themePath, 'index.html');
|
||||
const template = fs.readFileSync(indexTemplate).toString();
|
||||
|
||||
// Static
|
||||
router.get('/-/static/:filename', function(req, res, next) {
|
||||
const file = `${env.DIST_PATH}/${req.params.filename}`;
|
||||
const file = `${themePath}/${req.params.filename}`;
|
||||
res.sendFile(file, function(err) {
|
||||
if (!err) {
|
||||
return;
|
||||
|
@ -49,7 +49,7 @@ function isES6(plugin) {
|
||||
* @param {*} sanityCheck callback that check the shape that should fulfill the plugin
|
||||
* @return {Array} list of plugins
|
||||
*/
|
||||
export default function loadPlugin<T>(config: Config, pluginConfigs: any = {}, params: any, sanityCheck: Function): T[] {
|
||||
export default function loadPlugin<T>(config: Config, pluginConfigs: any = {}, params: any, sanityCheck: Function, prefix: string = 'verdaccio'): T[] {
|
||||
return Object.keys(pluginConfigs).map((pluginId: string) => {
|
||||
let plugin;
|
||||
|
||||
@ -65,7 +65,7 @@ export default function loadPlugin<T>(config: Config, pluginConfigs: any = {}, p
|
||||
|
||||
// npm package
|
||||
if (plugin === null && pluginId.match(/^[^\.\/]/)) {
|
||||
plugin = tryLoad(Path.resolve(pluginDir, `verdaccio-${pluginId}`));
|
||||
plugin = tryLoad(Path.resolve(pluginDir, `${prefix}-${pluginId}`));
|
||||
// compatibility for old sinopia plugins
|
||||
if (!plugin) {
|
||||
plugin = tryLoad(Path.resolve(pluginDir, `sinopia-${pluginId}`));
|
||||
@ -75,7 +75,7 @@ export default function loadPlugin<T>(config: Config, pluginConfigs: any = {}, p
|
||||
|
||||
// npm package
|
||||
if (plugin === null && pluginId.match(/^[^\.\/]/)) {
|
||||
plugin = tryLoad(`verdaccio-${pluginId}`);
|
||||
plugin = tryLoad(`${prefix}-${pluginId}`);
|
||||
// compatibility for old sinopia plugins
|
||||
if (!plugin) {
|
||||
plugin = tryLoad(`sinopia-${pluginId}`);
|
||||
@ -94,9 +94,7 @@ export default function loadPlugin<T>(config: Config, pluginConfigs: any = {}, p
|
||||
if (plugin === null) {
|
||||
logger.logger.error({ content: pluginId }, 'plugin not found. try npm install verdaccio-@{content}');
|
||||
throw Error(`
|
||||
${pluginId} plugin not found.
|
||||
try "npm install verdaccio-'${pluginId}
|
||||
`);
|
||||
${prefix}-${pluginId} plugin not found. try "npm install ${prefix}-${pluginId}"`);
|
||||
}
|
||||
|
||||
if (!isValid(plugin)) {
|
||||
@ -112,7 +110,7 @@ export default function loadPlugin<T>(config: Config, pluginConfigs: any = {}, p
|
||||
throw Error(`"${pluginId}" is not a valid plugin`);
|
||||
}
|
||||
|
||||
logger.logger.warn({ content: pluginId }, 'Plugin successfully loaded: @{content}');
|
||||
logger.logger.warn({ content: `${prefix}-${pluginId}` }, 'Plugin successfully loaded: @{content}');
|
||||
return plugin;
|
||||
});
|
||||
}
|
||||
|
@ -1,30 +0,0 @@
|
||||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"node": true,
|
||||
"jest": true,
|
||||
"es6": true
|
||||
},
|
||||
"globals": {
|
||||
"__DEBUG__": true
|
||||
},
|
||||
"rules": {
|
||||
"require-jsdoc": 0,
|
||||
"camelcase": ["error"],
|
||||
"no-console": [
|
||||
1,
|
||||
{
|
||||
"allow": [
|
||||
"log"
|
||||
]
|
||||
}
|
||||
],
|
||||
"no-unused-vars": [
|
||||
2,
|
||||
{
|
||||
"vars": "all",
|
||||
"args": "all"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
191
src/webui/app.js
@ -1,191 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
*/
|
||||
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import isNil from 'lodash/isNil';
|
||||
|
||||
import storage from './utils/storage';
|
||||
import { makeLogin, isTokenExpire } from './utils/login';
|
||||
|
||||
import Loading from './components/Loading';
|
||||
import LoginModal from './components/Login';
|
||||
import Header from './components/Header';
|
||||
import { Container, Content } from './components/Layout';
|
||||
import RouterApp from './router';
|
||||
import API from './utils/api';
|
||||
import './styles/typeface-roboto.scss';
|
||||
import './styles/main.scss';
|
||||
import 'normalize.css';
|
||||
import Footer from './components/Footer';
|
||||
|
||||
export const AppContext = React.createContext();
|
||||
export const AppContextProvider = AppContext.Provider;
|
||||
export const AppContextConsumer = AppContext.Consumer;
|
||||
|
||||
export default class App extends Component {
|
||||
state = {
|
||||
error: {},
|
||||
logoUrl: window.VERDACCIO_LOGO,
|
||||
user: {},
|
||||
scope: window.VERDACCIO_SCOPE ? `${window.VERDACCIO_SCOPE}:` : '',
|
||||
showLoginModal: false,
|
||||
isUserLoggedIn: false,
|
||||
packages: [],
|
||||
isLoading: true,
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this.isUserAlreadyLoggedIn();
|
||||
this.loadOnHandler();
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
componentDidUpdate(_, prevState) {
|
||||
const { isUserLoggedIn } = this.state;
|
||||
if (prevState.isUserLoggedIn !== isUserLoggedIn) {
|
||||
this.loadOnHandler();
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { isLoading, isUserLoggedIn, packages, logoUrl, user, scope } = this.state;
|
||||
|
||||
return (
|
||||
<Container isLoading={isLoading}>
|
||||
{isLoading ? (
|
||||
<Loading />
|
||||
) : (
|
||||
<Fragment>
|
||||
<AppContextProvider value={{ isUserLoggedIn, packages, logoUrl, user, scope }}>{this.renderContent()}</AppContextProvider>
|
||||
</Fragment>
|
||||
)}
|
||||
{this.renderLoginModal()}
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
isUserAlreadyLoggedIn = () => {
|
||||
// checks for token validity
|
||||
const token = storage.getItem('token');
|
||||
const username = storage.getItem('username');
|
||||
if (isTokenExpire(token) || isNil(username)) {
|
||||
this.handleLogout();
|
||||
} else {
|
||||
this.setState({
|
||||
user: { username, token },
|
||||
isUserLoggedIn: true,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
loadOnHandler = async () => {
|
||||
try {
|
||||
this.req = await API.request('packages', 'GET');
|
||||
this.setState({
|
||||
packages: this.req,
|
||||
isLoading: false,
|
||||
});
|
||||
} catch (error) {
|
||||
// FIXME: add dialog
|
||||
console.error({
|
||||
title: 'Warning',
|
||||
message: `Unable to load package list: ${error.message}`,
|
||||
});
|
||||
this.setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
setLoading = isLoading =>
|
||||
this.setState({
|
||||
isLoading,
|
||||
});
|
||||
|
||||
/**
|
||||
* Toggles the login modal
|
||||
* Required by: <LoginModal /> <Header />
|
||||
*/
|
||||
handleToggleLoginModal = () => {
|
||||
this.setState(prevState => ({
|
||||
showLoginModal: !prevState.showLoginModal,
|
||||
error: {},
|
||||
}));
|
||||
};
|
||||
|
||||
/**
|
||||
* handles login
|
||||
* Required by: <Header />
|
||||
*/
|
||||
handleDoLogin = async (usernameValue, passwordValue) => {
|
||||
const { username, token, error } = await makeLogin(usernameValue, passwordValue);
|
||||
|
||||
if (username && token) {
|
||||
this.setLoggedUser(username, token);
|
||||
storage.setItem('username', username);
|
||||
storage.setItem('token', token);
|
||||
}
|
||||
|
||||
if (error) {
|
||||
this.setState({
|
||||
user: {},
|
||||
error,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
setLoggedUser = (username, token) => {
|
||||
this.setState({
|
||||
user: {
|
||||
username,
|
||||
token,
|
||||
},
|
||||
isUserLoggedIn: true, // close login modal after successful login
|
||||
showLoginModal: false, // set isUserLoggedIn to true
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Logouts user
|
||||
* Required by: <Header />
|
||||
*/
|
||||
handleLogout = () => {
|
||||
storage.removeItem('username');
|
||||
storage.removeItem('token');
|
||||
this.setState({
|
||||
user: {},
|
||||
isUserLoggedIn: false,
|
||||
});
|
||||
};
|
||||
|
||||
renderLoginModal = () => {
|
||||
const { error, showLoginModal } = this.state;
|
||||
return (
|
||||
<LoginModal
|
||||
error={error}
|
||||
onCancel={this.handleToggleLoginModal}
|
||||
onChange={this.handleSetUsernameAndPassword}
|
||||
onSubmit={this.handleDoLogin}
|
||||
visibility={showLoginModal}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
renderContent = () => {
|
||||
return (
|
||||
<Fragment>
|
||||
<Content>
|
||||
<RouterApp onLogout={this.handleLogout} onToggleLoginModal={this.handleToggleLoginModal}>
|
||||
{this.renderHeader()}
|
||||
</RouterApp>
|
||||
</Content>
|
||||
<Footer />
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
renderHeader = () => {
|
||||
const { logoUrl, user: { username } = {}, scope } = this.state;
|
||||
|
||||
return <Header logo={logoUrl} onLogout={this.handleLogout} onToggleLoginModal={this.handleToggleLoginModal} scope={scope} username={username} />;
|
||||
};
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
@import './styles/variables';
|
||||
|
||||
.alertError {
|
||||
background-color: $red !important;
|
||||
min-width: inherit !important;
|
||||
}
|
||||
|
||||
.alertErrorMsg {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.alertIcon {
|
||||
opacity: 0.9;
|
||||
margin-right: 8px;
|
||||
}
|
@ -1,109 +0,0 @@
|
||||
/* eslint-disable react/jsx-max-depth */
|
||||
/**
|
||||
* @prettier
|
||||
*/
|
||||
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import { DetailContextConsumer } from '../../pages/version/index';
|
||||
import List from '@material-ui/core/List/index';
|
||||
|
||||
import DownloadIcon from '@material-ui/icons/CloudDownload';
|
||||
import BugReportIcon from '@material-ui/icons/BugReport';
|
||||
import HomeIcon from '@material-ui/icons/Home';
|
||||
import Tooltip from '@material-ui/core/Tooltip/index';
|
||||
|
||||
import { Fab, ActionListItem } from './styles';
|
||||
|
||||
const ACTIONS = {
|
||||
homepage: {
|
||||
icon: <HomeIcon />,
|
||||
title: 'Visit homepage',
|
||||
},
|
||||
issue: {
|
||||
icon: <BugReportIcon />,
|
||||
title: 'Open an issue',
|
||||
},
|
||||
tarball: {
|
||||
icon: <DownloadIcon />,
|
||||
title: 'Download tarball',
|
||||
},
|
||||
};
|
||||
|
||||
class ActionBar extends Component<any, any> {
|
||||
render() {
|
||||
return (
|
||||
<DetailContextConsumer>
|
||||
{context => {
|
||||
return this.renderActionBar(context);
|
||||
}}
|
||||
</DetailContextConsumer>
|
||||
);
|
||||
}
|
||||
|
||||
renderIconsWithLink(link, component) {
|
||||
if (!link) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<a href={link} target={"_blank"}>
|
||||
{component}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
renderActionBarListItems = (packageMeta) => {
|
||||
const {
|
||||
latest: {
|
||||
bugs: {
|
||||
url: issue,
|
||||
} = {},
|
||||
homepage,
|
||||
dist: {
|
||||
tarball,
|
||||
} = {},
|
||||
} = {},
|
||||
} = packageMeta;
|
||||
|
||||
const actionsMap = {
|
||||
homepage,
|
||||
issue,
|
||||
tarball,
|
||||
};
|
||||
|
||||
const renderList = Object.keys(actionsMap).reduce((component, value, key) => {
|
||||
const link = actionsMap[value];
|
||||
if (link) {
|
||||
const fab = (
|
||||
<Fab size={'small'}>
|
||||
{ACTIONS[value]['icon']}
|
||||
</Fab>
|
||||
);
|
||||
component.push(
|
||||
<Tooltip key={key} title={ACTIONS[value]['title']}>
|
||||
{this.renderIconsWithLink(link, fab)}
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
return component;
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<ActionListItem alignItems={'flex-start'}>
|
||||
{renderList}
|
||||
</ActionListItem>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
renderActionBar = ({ packageMeta = {} }) => {
|
||||
return (
|
||||
<List>
|
||||
{this.renderActionBarListItems(packageMeta)}
|
||||
</List>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
export default ActionBar;
|
@ -1,24 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
*/
|
||||
import styled from 'react-emotion';
|
||||
import { default as MuiFab } from '@material-ui/core/Fab';
|
||||
import ListItem from '@material-ui/core/ListItem/index';
|
||||
|
||||
import colors from '../../utils/styles/colors';
|
||||
|
||||
export const ActionListItem = styled(ListItem)`
|
||||
&& {
|
||||
padding-top: 0;
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
`;
|
||||
|
||||
export const Fab = styled(MuiFab)`
|
||||
&& {
|
||||
background-color: ${colors.primary};
|
||||
color: ${colors.white};
|
||||
margin-right: 10px;
|
||||
}
|
||||
`;
|
@ -1,55 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
*/
|
||||
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import Avatar from '@material-ui/core/Avatar/index';
|
||||
import List from '@material-ui/core/List/index';
|
||||
import ListItemText from '@material-ui/core/ListItemText/index';
|
||||
|
||||
import { DetailContextConsumer } from '../../pages/version/index';
|
||||
import { Heading, AuthorListItem } from './styles';
|
||||
|
||||
class Authors extends Component<any, any> {
|
||||
render() {
|
||||
return (
|
||||
<DetailContextConsumer>
|
||||
{context => {
|
||||
return this.renderAuthor(context);
|
||||
}}
|
||||
</DetailContextConsumer>
|
||||
);
|
||||
}
|
||||
|
||||
renderLinkForMail(email, avatarComponent, packageName, version) {
|
||||
if (!email) {
|
||||
return avatarComponent;
|
||||
}
|
||||
return (
|
||||
<a href={`mailto:${email}?subject=${packageName}@${version}`} target={'_top'}>
|
||||
{avatarComponent}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
renderAuthor = ({ packageMeta }) => {
|
||||
const { author, name: packageName, version } = packageMeta.latest;
|
||||
|
||||
if (!author) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const avatarComponent = <Avatar alt={author.name} src={author.avatar} />;
|
||||
return (
|
||||
<List subheader={<Heading variant={'subheading'}>{'Author'}</Heading>}>
|
||||
<AuthorListItem>
|
||||
{this.renderLinkForMail(author.email, avatarComponent, packageName, version)}
|
||||
<ListItemText primary={author.name} />
|
||||
</AuthorListItem>
|
||||
</List>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
export default Authors;
|
@ -1,21 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import styled from 'react-emotion';
|
||||
import ListItem from '@material-ui/core/ListItem/index';
|
||||
import Typography from '@material-ui/core/Typography/index';
|
||||
|
||||
export const Heading = styled(Typography)`
|
||||
&& {
|
||||
font-weight: 700;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
`;
|
||||
export const AuthorListItem = styled(ListItem)`
|
||||
&& {
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
`;
|
@ -1,129 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import type { Node } from 'react';
|
||||
import Autosuggest from 'react-autosuggest';
|
||||
import match from 'autosuggest-highlight/match';
|
||||
import parse from 'autosuggest-highlight/parse';
|
||||
import Paper from '@material-ui/core/Paper';
|
||||
import MenuItem from '@material-ui/core/MenuItem';
|
||||
|
||||
import { fontWeight } from '../../utils/styles/sizes';
|
||||
import { Wrapper, InputField } from './styles';
|
||||
import { IProps } from './types';
|
||||
|
||||
const renderInputComponent = (inputProps): Node => {
|
||||
const { ref, startAdornment, disableUnderline, onKeyDown, ...others } = inputProps;
|
||||
return (
|
||||
<InputField
|
||||
InputProps={{
|
||||
inputRef: node => {
|
||||
ref(node);
|
||||
},
|
||||
startAdornment,
|
||||
disableUnderline,
|
||||
onKeyDown,
|
||||
}}
|
||||
fullWidth={true}
|
||||
{...others}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const getSuggestionValue = (suggestion): string => suggestion.name;
|
||||
|
||||
const renderSuggestion = (suggestion, { query, isHighlighted }): Node => {
|
||||
const matches = match(suggestion.name, query);
|
||||
const parts = parse(suggestion.name, matches);
|
||||
return (
|
||||
<MenuItem component={'div'} selected={isHighlighted}>
|
||||
<div>
|
||||
{parts.map((part, index) => {
|
||||
return part.highlight ? (
|
||||
<span href={suggestion.link} key={String(index)} style={{ fontWeight: fontWeight.semiBold }}>
|
||||
{part.text}
|
||||
</span>
|
||||
) : (
|
||||
<span href={suggestion.link} key={String(index)} style={{ fontWeight: fontWeight.light }}>
|
||||
{part.text}
|
||||
</span>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</MenuItem>
|
||||
);
|
||||
};
|
||||
|
||||
const renderMessage = (message): Node => {
|
||||
return (
|
||||
<MenuItem component={'div'} selected={false}>
|
||||
<div>{message}</div>
|
||||
</MenuItem>
|
||||
);
|
||||
};
|
||||
|
||||
const SUGGESTIONS_RESPONSE = {
|
||||
LOADING: 'Loading...',
|
||||
FAILURE: 'Something went wrong.',
|
||||
NO_RESULT: 'No results found.',
|
||||
};
|
||||
|
||||
const AutoComplete = ({
|
||||
suggestions,
|
||||
startAdornment,
|
||||
onChange,
|
||||
onSuggestionsFetch,
|
||||
onCleanSuggestions,
|
||||
value = '',
|
||||
placeholder = '',
|
||||
disableUnderline = false,
|
||||
color,
|
||||
onClick,
|
||||
onKeyDown,
|
||||
onBlur,
|
||||
suggestionsLoading = false,
|
||||
suggestionsLoaded = false,
|
||||
suggestionsError = false,
|
||||
}: IProps): Node => {
|
||||
const autosuggestProps = {
|
||||
renderInputComponent,
|
||||
suggestions,
|
||||
getSuggestionValue,
|
||||
renderSuggestion,
|
||||
onSuggestionsFetchRequested: onSuggestionsFetch,
|
||||
onSuggestionsClearRequested: onCleanSuggestions,
|
||||
};
|
||||
const inputProps = {
|
||||
value,
|
||||
onChange,
|
||||
placeholder,
|
||||
startAdornment,
|
||||
disableUnderline,
|
||||
color,
|
||||
onKeyDown,
|
||||
onBlur,
|
||||
};
|
||||
|
||||
// this format avoid arrow function eslint rule
|
||||
function renderSuggestionsContainer({ containerProps, children, query }) {
|
||||
return (
|
||||
<Paper {...containerProps} square={true}>
|
||||
{suggestionsLoaded && children === null && query && renderMessage(SUGGESTIONS_RESPONSE.NO_RESULT)}
|
||||
{suggestionsLoading && query && renderMessage(SUGGESTIONS_RESPONSE.LOADING)}
|
||||
{suggestionsError && renderMessage(SUGGESTIONS_RESPONSE.FAILURE)}
|
||||
{children}
|
||||
</Paper>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Wrapper>
|
||||
<Autosuggest {...autosuggestProps} inputProps={inputProps} onSuggestionSelected={onClick} renderSuggestionsContainer={renderSuggestionsContainer} />
|
||||
</Wrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default AutoComplete;
|
@ -1,52 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import styled, { css } from 'react-emotion';
|
||||
|
||||
import TextField from '../TextField';
|
||||
import { IInputField } from './types';
|
||||
|
||||
export const Wrapper = styled.div`
|
||||
&& {
|
||||
width: 100%;
|
||||
height: 32px;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
`;
|
||||
|
||||
export const InputField = ({ color, ...others }: IInputField) => (
|
||||
<TextField
|
||||
{...others}
|
||||
classes={{
|
||||
input: css`
|
||||
&& {
|
||||
${color &&
|
||||
css`
|
||||
color: ${color};
|
||||
`};
|
||||
}
|
||||
`,
|
||||
root: css`
|
||||
&& {
|
||||
&:before {
|
||||
content: '';
|
||||
border: none;
|
||||
}
|
||||
&:after {
|
||||
${color &&
|
||||
css`
|
||||
border-color: ${color};
|
||||
`};
|
||||
}
|
||||
&:hover:before {
|
||||
content: none;
|
||||
}
|
||||
}
|
||||
`,
|
||||
}}
|
||||
/>
|
||||
);
|
@ -1,29 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import { InputAdornmentProps } from '@material-ui/core/InputAdornment';
|
||||
|
||||
export interface IProps {
|
||||
suggestions: any[];
|
||||
suggestionsLoading?: boolean;
|
||||
suggestionsLoaded?: boolean;
|
||||
suggestionsError?: boolean;
|
||||
apiLoading?: boolean;
|
||||
color?: string;
|
||||
value?: string;
|
||||
placeholder?: string;
|
||||
startAdornment?: React.ComponentType<InputAdornmentProps>;
|
||||
disableUnderline?: boolean;
|
||||
onChange?: (event: SyntheticKeyboardEvent<HTMLInputElement>, { newValue: string, method: string }) => void;
|
||||
onSuggestionsFetch?: ({ value: string }) => Promise<void>;
|
||||
onCleanSuggestions?: () => void;
|
||||
onClick?: (event: SyntheticKeyboardEvent<HTMLInputElement>, { suggestionValue: any[], method: string }) => void;
|
||||
onKeyDown?: (event: SyntheticKeyboardEvent<HTMLInputElement>) => void;
|
||||
onBlur?: (event: SyntheticKeyboardEvent<HTMLInputElement>) => void;
|
||||
}
|
||||
|
||||
export interface IInputField {
|
||||
color: string;
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import FileCopy from '@material-ui/icons/FileCopy';
|
||||
import Tooltip from '@material-ui/core/Tooltip/index';
|
||||
|
||||
import type { Node } from 'react';
|
||||
import { IProps } from './types';
|
||||
|
||||
import { ClipBoardCopy, ClipBoardCopyText, CopyIcon } from './styles';
|
||||
import { copyToClipBoardUtility } from '../../utils/cli-utils';
|
||||
import { TEXT } from '../../utils/constants';
|
||||
|
||||
const CopyToClipBoard = ({ text, children }: IProps): Node => {
|
||||
const renderToolTipFileCopy = () => (
|
||||
<Tooltip disableFocusListener={true} title={TEXT.CLIPBOARD_COPY}>
|
||||
<CopyIcon onClick={copyToClipBoardUtility(text)}>
|
||||
<FileCopy />
|
||||
</CopyIcon>
|
||||
</Tooltip>
|
||||
);
|
||||
|
||||
const renderText = children => {
|
||||
if (children) {
|
||||
return <ClipBoardCopyText>{children}</ClipBoardCopyText>;
|
||||
}
|
||||
|
||||
return <ClipBoardCopyText>{text}</ClipBoardCopyText>;
|
||||
};
|
||||
return (
|
||||
<ClipBoardCopy>
|
||||
{renderText(children)}
|
||||
{renderToolTipFileCopy()}
|
||||
</ClipBoardCopy>
|
||||
);
|
||||
};
|
||||
|
||||
export default CopyToClipBoard;
|
@ -1,26 +0,0 @@
|
||||
import styled from 'react-emotion';
|
||||
import IconButton from '@material-ui/core/IconButton/index';
|
||||
|
||||
export const ClipBoardCopy = styled.div`
|
||||
&& {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
`;
|
||||
|
||||
export const ClipBoardCopyText = styled.span`
|
||||
&& {
|
||||
display: inline-block;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
height: 21px;
|
||||
}
|
||||
`;
|
||||
|
||||
export const CopyIcon = styled(IconButton)`
|
||||
&& {
|
||||
margin: 0 0 0 10px;
|
||||
}
|
||||
`;
|
@ -1,9 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
* @flow
|
||||
*/
|
||||
|
||||
export interface IProps {
|
||||
text: string;
|
||||
children?: any;
|
||||
}
|
@ -1,116 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
* @flow
|
||||
*/
|
||||
|
||||
/* eslint react/jsx-max-depth: 0 */
|
||||
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
import CardContent from '@material-ui/core/CardContent/index';
|
||||
|
||||
import { DetailContextConsumer } from '../../pages/version';
|
||||
|
||||
import { CardWrap, Heading, Tags, Tag } from './styles';
|
||||
import NoItems from '../NoItems';
|
||||
|
||||
class DepDetail extends Component<any, any> {
|
||||
constructor(props: any) {
|
||||
super(props);
|
||||
const { name, version } = this.props;
|
||||
|
||||
this.state = {
|
||||
name,
|
||||
version,
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
const { name, version } = this.state;
|
||||
const tagText = `${name}@${version}`;
|
||||
return <Tag className={'dep-tag'} clickable={true} component={'div'} label={tagText} onClick={this.handleOnClick} />;
|
||||
}
|
||||
|
||||
handleOnClick = () => {
|
||||
const { name } = this.state;
|
||||
const { onLoading, history } = this.props;
|
||||
|
||||
onLoading();
|
||||
history.push(`/-/web/detail/${name}`);
|
||||
};
|
||||
}
|
||||
|
||||
const WrappDepDetail = withRouter(DepDetail);
|
||||
|
||||
class DependencyBlock extends Component<any, any> {
|
||||
render() {
|
||||
const { dependencies, title } = this.props;
|
||||
const deps = Object.entries(dependencies);
|
||||
|
||||
return (
|
||||
// $FlowFixMe
|
||||
<DetailContextConsumer>
|
||||
{({ enableLoading }) => {
|
||||
return (
|
||||
<CardWrap>
|
||||
<CardContent>
|
||||
<Heading variant={'subheading'}>{`${title} (${deps.length})`}</Heading>
|
||||
<Tags>{this.renderTags(deps, enableLoading)}</Tags>
|
||||
</CardContent>
|
||||
</CardWrap>
|
||||
);
|
||||
}}
|
||||
</DetailContextConsumer>
|
||||
);
|
||||
}
|
||||
|
||||
renderTags = (deps: any, enableLoading: any) =>
|
||||
deps.map(dep => {
|
||||
const [name, version] = dep;
|
||||
|
||||
return <WrappDepDetail key={name} name={name} onLoading={enableLoading} version={version} />;
|
||||
});
|
||||
}
|
||||
|
||||
class Dependencies extends Component<any, any> {
|
||||
state = {
|
||||
tabPosition: 0,
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<DetailContextConsumer>
|
||||
{packageMeta => {
|
||||
return this.renderDependencies(packageMeta);
|
||||
}}
|
||||
</DetailContextConsumer>
|
||||
);
|
||||
}
|
||||
|
||||
checkDependencyLength(dependency: Object = {}) {
|
||||
return Object.keys(dependency).length > 0;
|
||||
}
|
||||
|
||||
// $FlowFixMe
|
||||
renderDependencies({ packageMeta }) {
|
||||
const { latest } = packageMeta;
|
||||
const { dependencies, devDependencies, peerDependencies, name } = latest;
|
||||
|
||||
const dependencyMap = { dependencies, devDependencies, peerDependencies };
|
||||
|
||||
const dependencyList = Object.keys(dependencyMap).reduce((result, value, key) => {
|
||||
const selectedDepndency = dependencyMap[value];
|
||||
if (selectedDepndency && this.checkDependencyLength(selectedDepndency)) {
|
||||
result.push(<DependencyBlock className={'dependency-block'} dependencies={selectedDepndency} key={key} title={value} />);
|
||||
}
|
||||
return result;
|
||||
}, []);
|
||||
|
||||
if (dependencyList.length) {
|
||||
return <Fragment>{dependencyList}</Fragment>;
|
||||
}
|
||||
return <NoItems className={'no-dependencies'} text={`${name} has no dependencies.`} />;
|
||||
}
|
||||
}
|
||||
|
||||
export default Dependencies;
|
@ -1,37 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import styled from 'react-emotion';
|
||||
import Card from '@material-ui/core/Card/index';
|
||||
import Typography from '@material-ui/core/Typography/index';
|
||||
import Chip from '@material-ui/core/Chip/index';
|
||||
|
||||
export const CardWrap = styled(Card)`
|
||||
&& {
|
||||
margin: 0 0 16px;
|
||||
}
|
||||
`;
|
||||
|
||||
export const Heading = styled(Typography)`
|
||||
&& {
|
||||
font-weight: 700;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
`;
|
||||
|
||||
export const Tags = styled('div')`
|
||||
&& {
|
||||
display: flex;
|
||||
justify-content: start;
|
||||
flex-wrap: wrap;
|
||||
margin: 0 -5px;
|
||||
}
|
||||
`;
|
||||
|
||||
export const Tag = styled(Chip)`
|
||||
&& {
|
||||
margin: 5px;
|
||||
}
|
||||
`;
|
@ -1,12 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import type { Node } from 'react';
|
||||
|
||||
export interface IProps {
|
||||
children: Node;
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
}
|
@ -1,68 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import { DetailContextConsumer } from '../../pages/version/index';
|
||||
import Readme from '../Readme';
|
||||
import Versions from '../Versions';
|
||||
import { preventXSS } from '../../utils/sec-utils';
|
||||
import Tabs from '@material-ui/core/Tabs/index';
|
||||
import Tab from '@material-ui/core/Tab/index';
|
||||
import { Content } from './styles';
|
||||
import Dependencies from '../Dependencies';
|
||||
import UpLinks from '../UpLinks';
|
||||
|
||||
class DetailContainer extends Component<any, any> {
|
||||
state = {
|
||||
tabPosition: 0,
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<DetailContextConsumer>
|
||||
{context => {
|
||||
return this.renderTabs(context);
|
||||
}}
|
||||
</DetailContextConsumer>
|
||||
);
|
||||
}
|
||||
|
||||
handleChange = (event: any, tabPosition: number) => {
|
||||
event.preventDefault();
|
||||
this.setState({ tabPosition });
|
||||
};
|
||||
|
||||
// $FlowFixMe
|
||||
renderTabs = ({ readMe }) => {
|
||||
const { tabPosition } = this.state;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Content>
|
||||
<Tabs indicatorColor={'primary'} onChange={this.handleChange} textColor={'primary'} value={tabPosition} variant={'fullWidth'}>
|
||||
<Tab id={'readme-tab'} label={'Readme'} />
|
||||
<Tab id={'dependencies-tab'} label={'Dependencies'} />
|
||||
<Tab id={'versions-tab'} label={'Versions'} />
|
||||
<Tab id={'uplinks-tab'} label={'Uplinks'} />
|
||||
</Tabs>
|
||||
<br />
|
||||
{tabPosition === 0 && this.renderReadme(readMe)}
|
||||
{tabPosition === 1 && <Dependencies />}
|
||||
{tabPosition === 2 && <Versions />}
|
||||
{tabPosition === 3 && <UpLinks />}
|
||||
</Content>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
renderReadme = (readMe: string) => {
|
||||
const encodedReadme = preventXSS(readMe);
|
||||
|
||||
return <Readme description={encodedReadme} />;
|
||||
};
|
||||
}
|
||||
|
||||
export default DetailContainer;
|
@ -1,12 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import styled from 'react-emotion';
|
||||
|
||||
export const Content = styled.div`
|
||||
&& {
|
||||
padding: 15px;
|
||||
}
|
||||
`;
|
@ -1,12 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import type { Node } from 'react';
|
||||
|
||||
export interface IProps {
|
||||
children: Node;
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
}
|
@ -1,95 +0,0 @@
|
||||
import React, {Component} from 'react';
|
||||
|
||||
import Card from '@material-ui/core/Card/index';
|
||||
import CardContent from '@material-ui/core/CardContent/index';
|
||||
import List from '@material-ui/core/List/index';
|
||||
|
||||
import ActtionBar from '../ActionBar';
|
||||
import Author from '../Author';
|
||||
import Developers from '../Developers';
|
||||
import Dist from '../Dist';
|
||||
import Engine from '../Engines';
|
||||
import Install from '../Install';
|
||||
import Repository from '../Repository';
|
||||
|
||||
|
||||
import { DetailContextConsumer } from '../../pages/version/index';
|
||||
|
||||
import { TitleListItem, TitleListItemText } from './styles';
|
||||
|
||||
class DetailSidebar extends Component {
|
||||
render() {
|
||||
return (
|
||||
<DetailContextConsumer>
|
||||
{(context) => this.renderSideBar(context)}
|
||||
</DetailContextConsumer>
|
||||
);
|
||||
};
|
||||
|
||||
renderSideBar = ({packageName, packageMeta}) => {
|
||||
return (
|
||||
<div className={'sidebar-info'}>
|
||||
<Card>
|
||||
<CardContent>
|
||||
{this.renderTitle(packageName, packageMeta)}
|
||||
{this.renderActionBar()}
|
||||
{this.renderCopyCLI()}
|
||||
{this.renderRepository()}
|
||||
{this.renderEngine()}
|
||||
{this.renderDist()}
|
||||
{this.renderAuthor()}
|
||||
{this.renderMaintainers()}
|
||||
{this.renderContributors()}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderTitle = (packageName, packageMeta) => {
|
||||
return (
|
||||
<List className={'detail-info'}>
|
||||
<TitleListItem alignItems={"flex-start"}>
|
||||
<TitleListItemText
|
||||
primary={<b>{packageName}</b>}
|
||||
secondary={packageMeta.latest.description}
|
||||
/>
|
||||
</TitleListItem>
|
||||
</List>
|
||||
);
|
||||
}
|
||||
|
||||
renderCopyCLI = () => {
|
||||
return <Install />;
|
||||
}
|
||||
|
||||
renderMaintainers = () => {
|
||||
return <Developers type={'maintainers'} />;
|
||||
}
|
||||
|
||||
renderContributors = () => {
|
||||
return <Developers type={'contributors'} />;
|
||||
}
|
||||
|
||||
renderRepository = () => {
|
||||
return <Repository />;
|
||||
}
|
||||
|
||||
renderAuthor = () => {
|
||||
return <Author />;
|
||||
}
|
||||
|
||||
renderEngine = () => {
|
||||
return <Engine />;
|
||||
}
|
||||
|
||||
renderDist = () => {
|
||||
return <Dist />;
|
||||
}
|
||||
|
||||
renderActionBar = () => {
|
||||
return <ActtionBar />;
|
||||
}
|
||||
}
|
||||
|
||||
export default DetailSidebar;
|
@ -1,35 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import styled from 'react-emotion';
|
||||
import Avatar from '@material-ui/core/Avatar/index';
|
||||
import ListItem from '@material-ui/core/ListItem/index';
|
||||
import ListItemText from '@material-ui/core/ListItemText/index';
|
||||
|
||||
import colors from '../../utils/styles/colors';
|
||||
|
||||
export const TitleListItem = styled(ListItem)`
|
||||
&& {
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
`;
|
||||
|
||||
export const TitleListItemText = styled(ListItemText)`
|
||||
&& {
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
padding-top: 8px;
|
||||
}
|
||||
`;
|
||||
|
||||
export const TitleAvatar = styled(Avatar)`
|
||||
&& {
|
||||
color: ${colors.greySuperLight};
|
||||
background-color: ${colors.primary};
|
||||
text-transform: capitalize;
|
||||
}
|
||||
`;
|
@ -1,12 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import type { Node } from 'react';
|
||||
|
||||
export interface IProps {
|
||||
children: Node;
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
}
|
@ -1,83 +0,0 @@
|
||||
import React, {Component} from 'react';
|
||||
|
||||
import Avatar from '@material-ui/core/Avatar';
|
||||
import Tooltip from '@material-ui/core/Tooltip';
|
||||
import Add from '@material-ui/icons/Add';
|
||||
|
||||
import { DetailContextConsumer } from '../../pages/version';
|
||||
|
||||
import { Details, Heading, Content, Fab } from './styles';
|
||||
|
||||
interface Props {
|
||||
type: 'contributors' | 'maintainers'
|
||||
}
|
||||
|
||||
class Developers extends Component<Props, any> {
|
||||
state = {
|
||||
visibleDevs: 6,
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<DetailContextConsumer>
|
||||
{({ packageMeta }) => {
|
||||
const { type } = this.props;
|
||||
const developerType = packageMeta.latest[type];
|
||||
if (!developerType || developerType.length === 0) return null;
|
||||
return this.renderDevelopers(developerType, packageMeta);
|
||||
}}
|
||||
</DetailContextConsumer>
|
||||
);
|
||||
};
|
||||
|
||||
handleLoadMore = () => {
|
||||
this.setState((prev) => ({ visibleDevs: prev.visibleDevs + 6 }));
|
||||
}
|
||||
|
||||
renderDevelopers = (developers, packageMeta) => {
|
||||
const { type } = this.props;
|
||||
const { visibleDevs } = this.state;
|
||||
return (
|
||||
<>
|
||||
<Heading variant={'subheading'}>{type}</Heading>
|
||||
<Content>
|
||||
{developers.slice(0, visibleDevs).map(developer => (
|
||||
<Details key={developer.email}>{this.renderDeveloperDetails(developer, packageMeta)}</Details>
|
||||
))}
|
||||
{visibleDevs < developers.length &&
|
||||
<Fab onClick={this.handleLoadMore} size={'small'}><Add /></Fab>
|
||||
}
|
||||
</Content>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
renderLinkForMail(email, avatarComponent, packageName, version) {
|
||||
if(!email) {
|
||||
return avatarComponent;
|
||||
}
|
||||
return (
|
||||
<a href={`mailto:${email}?subject=${packageName}@${version}`} target={"_top"}>
|
||||
{avatarComponent}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
renderDeveloperDetails = ({ name, avatar, email }, packageMeta) => {
|
||||
const {
|
||||
name: packageName,
|
||||
version,
|
||||
} = packageMeta.latest;
|
||||
|
||||
const avatarComponent = <Avatar aria-label={name} src={avatar} />;
|
||||
return (
|
||||
<Tooltip title={name}>
|
||||
{this.renderLinkForMail(email, avatarComponent, packageName, version)}
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
export default Developers;
|
@ -1,38 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
*/
|
||||
|
||||
import styled from 'react-emotion';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import { default as MuiFab } from '@material-ui/core/Fab';
|
||||
import colors from '../../utils/styles/colors';
|
||||
|
||||
export const Details = styled('span')`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
`;
|
||||
|
||||
export const Content = styled('div')`
|
||||
margin: 10px 0 10px 0;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
> * {
|
||||
margin: 5px;
|
||||
}
|
||||
`;
|
||||
|
||||
export const Heading = styled(Typography)`
|
||||
&& {
|
||||
font-weight: 700;
|
||||
margin-bottom: 10px;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
`;
|
||||
|
||||
export const Fab = styled(MuiFab)`
|
||||
&& {
|
||||
background-color: ${colors.primary};
|
||||
color: ${colors.white};
|
||||
}
|
||||
`;
|
@ -1,59 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
*/
|
||||
|
||||
/* eslint-disable */
|
||||
import React, { Component } from 'react';
|
||||
import List from '@material-ui/core/List/index';
|
||||
|
||||
import { DetailContextConsumer } from '../../pages/version/index';
|
||||
|
||||
import { Heading, DistListItem, DistChips, DownloadButton } from './styles';
|
||||
import fileSizeSI from '../../utils/file-size';
|
||||
|
||||
class Dist extends Component<any, any> {
|
||||
render() {
|
||||
return (
|
||||
<DetailContextConsumer>
|
||||
{context => {
|
||||
return this.renderDist(context);
|
||||
}}
|
||||
</DetailContextConsumer>
|
||||
);
|
||||
}
|
||||
|
||||
renderChips(dist, license) {
|
||||
const distDict = {
|
||||
'file-count': dist.fileCount,
|
||||
size: dist.unpackedSize && fileSizeSI(dist.unpackedSize),
|
||||
license,
|
||||
};
|
||||
|
||||
const chipsList = Object.keys(distDict).reduce((componentList, title, key) => {
|
||||
const value = distDict[title];
|
||||
if (value) {
|
||||
const label = (
|
||||
<span>
|
||||
<b>{title.split('-').join(' ')}</b>: {value}
|
||||
</span>
|
||||
);
|
||||
componentList.push(<DistChips label={label} key={key} />);
|
||||
}
|
||||
return componentList;
|
||||
}, []);
|
||||
|
||||
return chipsList;
|
||||
}
|
||||
|
||||
renderDist = ({ packageMeta }) => {
|
||||
const { dist = {}, license } = packageMeta.latest;
|
||||
|
||||
return (
|
||||
<List subheader={<Heading variant={'subheading'}>{'Latest Distribution'}</Heading>}>
|
||||
<DistListItem>{this.renderChips(dist, license)}</DistListItem>
|
||||
</List>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
export default Dist;
|
@ -1,39 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
*/
|
||||
|
||||
import styled from 'react-emotion';
|
||||
import { default as MuiFab } from '@material-ui/core/Fab/index';
|
||||
import Chip from '@material-ui/core/Chip/index';
|
||||
import ListItem from '@material-ui/core/ListItem/index';
|
||||
import Typography from '@material-ui/core/Typography/index';
|
||||
|
||||
import colors from '../../utils/styles/colors';
|
||||
|
||||
export const Heading = styled(Typography)`
|
||||
&& {
|
||||
font-weight: 700;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
`;
|
||||
|
||||
export const DistListItem = styled(ListItem)`
|
||||
&& {
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
`;
|
||||
|
||||
export const DistChips = styled(Chip)`
|
||||
&& {
|
||||
margin-right: 5px;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
`;
|
||||
|
||||
export const DownloadButton = styled(MuiFab)`
|
||||
&& {
|
||||
background-color: ${colors.primary};
|
||||
color: ${colors.white};
|
||||
}
|
||||
`;
|
Before Width: | Height: | Size: 27 KiB |
@ -1,78 +0,0 @@
|
||||
/* eslint-disable */
|
||||
import React, {Component} from 'react';
|
||||
|
||||
import Avatar from '@material-ui/core/Avatar/index';
|
||||
import Grid from '@material-ui/core/Grid/index';
|
||||
import List from '@material-ui/core/List/index';
|
||||
import ListItemText from '@material-ui/core/ListItemText/index';
|
||||
|
||||
import { DetailContextConsumer } from '../../pages/version/index';
|
||||
|
||||
import { Heading, EngineListItem } from './styles';
|
||||
import node from './img/node.png';
|
||||
import npm from '../Install/img/npm.svg'
|
||||
|
||||
|
||||
const ICONS = {
|
||||
'node-JS': <Avatar src={node} />,
|
||||
'NPM-version': <Avatar src={npm} />,
|
||||
}
|
||||
|
||||
class Engine extends Component {
|
||||
render() {
|
||||
return (
|
||||
<DetailContextConsumer>
|
||||
{(context) => {
|
||||
return this.renderEngine(context);
|
||||
}}
|
||||
</DetailContextConsumer>
|
||||
);
|
||||
};
|
||||
|
||||
renderEngine = ({packageMeta}) => {
|
||||
const { engines } = packageMeta.latest;
|
||||
if (!engines) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const engineDict = {
|
||||
'node-JS': engines.node,
|
||||
'NPM-version': engines.npm
|
||||
}
|
||||
|
||||
const items = Object.keys(engineDict).reduce((markup, text, key) => {
|
||||
const heading = engineDict[text]
|
||||
if (heading){
|
||||
markup.push(
|
||||
<Grid item={true} xs={6} key={key}>
|
||||
{this.renderListItems(heading, text)}
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
return markup;
|
||||
}, []);
|
||||
|
||||
if (items.length < 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Grid container={true}>
|
||||
{items}
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
|
||||
renderListItems = (heading, text) => {
|
||||
return (
|
||||
<List subheader={<Heading variant={"subheading"}>{text.split('-').join(' ')}</Heading>}>
|
||||
<EngineListItem>
|
||||
{ ICONS[text] }
|
||||
<ListItemText primary={heading} />
|
||||
</EngineListItem>
|
||||
</List>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Engine;
|
@ -1,21 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import styled from 'react-emotion';
|
||||
import ListItem from '@material-ui/core/ListItem/index';
|
||||
import Typography from '@material-ui/core/Typography/index';
|
||||
|
||||
export const Heading = styled(Typography)`
|
||||
&& {
|
||||
font-weight: 700;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
`;
|
||||
|
||||
export const EngineListItem = styled(ListItem)`
|
||||
&& {
|
||||
padding-left: 0;
|
||||
}
|
||||
`;
|
@ -1,57 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import type { Element } from 'react';
|
||||
|
||||
import { version } from '../../../../package.json';
|
||||
import { Wrapper, Left, Right, Earth, Flags, Love, Flag, Logo, Inner, ToolTip } from './styles';
|
||||
import { goToVerdaccioWebsite } from '../../utils/windows.js';
|
||||
|
||||
const renderTooltip = () => (
|
||||
<ToolTip>
|
||||
<Earth name={'earth'} size={'md'} />
|
||||
<Flags>
|
||||
<Flag name={'spain'} size={'md'} />
|
||||
<Flag name={'nicaragua'} size={'md'} />
|
||||
<Flag name={'india'} size={'md'} />
|
||||
<Flag name={'brazil'} size={'md'} />
|
||||
<Flag name={'china'} size={'md'} />
|
||||
<Flag name={'austria'} size={'md'} />
|
||||
</Flags>
|
||||
</ToolTip>
|
||||
);
|
||||
const POWERED_LABEL = 'Powered by';
|
||||
const MADEWITH_LABEL = ' Made with';
|
||||
const ON_LABEL = 'on';
|
||||
const HEARTH_EMOJI = '♥';
|
||||
|
||||
const renderRight = () => (
|
||||
<Right>
|
||||
{POWERED_LABEL}
|
||||
<Logo img={true} name={'verdaccio'} onClick={goToVerdaccioWebsite} pointer={true} size={'md'} />
|
||||
{`/ ${version}`}
|
||||
</Right>
|
||||
);
|
||||
|
||||
const renderLeft = () => (
|
||||
<Left>
|
||||
{MADEWITH_LABEL}
|
||||
<Love>{HEARTH_EMOJI}</Love>
|
||||
{ON_LABEL}
|
||||
{renderTooltip()}
|
||||
</Left>
|
||||
);
|
||||
|
||||
const Footer = (): Element<Wrapper> => (
|
||||
<Wrapper>
|
||||
<Inner>
|
||||
{renderLeft()}
|
||||
{renderRight()}
|
||||
</Inner>
|
||||
</Wrapper>
|
||||
);
|
||||
|
||||
export default Footer;
|
@ -1,107 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import styled, { css } from 'react-emotion';
|
||||
import mq from '../../utils/styles/media';
|
||||
import Icon from '../Icon';
|
||||
|
||||
export const Wrapper = styled.div`
|
||||
&& {
|
||||
background: #f9f9f9;
|
||||
border-top: 1px solid #e3e3e3;
|
||||
color: #999999;
|
||||
font-size: 14px;
|
||||
padding: 20px;
|
||||
}
|
||||
`;
|
||||
|
||||
export const Inner = styled.div`
|
||||
&& {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
width: 100%;
|
||||
${mq.medium(css`
|
||||
min-width: 400px;
|
||||
max-width: 800px;
|
||||
margin: auto;
|
||||
justify-content: space-between;
|
||||
`)};
|
||||
${mq.large(css`
|
||||
max-width: 1240px;
|
||||
`)};
|
||||
}
|
||||
`;
|
||||
|
||||
export const Left = styled.div`
|
||||
&& {
|
||||
align-items: center;
|
||||
display: none;
|
||||
${mq.medium(css`
|
||||
display: flex;
|
||||
`)};
|
||||
}
|
||||
`;
|
||||
|
||||
export const Right = styled(Left)`
|
||||
&& {
|
||||
display: flex;
|
||||
}
|
||||
`;
|
||||
|
||||
export const ToolTip = styled.span`
|
||||
&& {
|
||||
position: relative;
|
||||
height: 18px;
|
||||
}
|
||||
`;
|
||||
|
||||
export const Earth = styled(Icon)`
|
||||
&& {
|
||||
padding: 0 10px;
|
||||
}
|
||||
`;
|
||||
|
||||
export const Flags = styled.span`
|
||||
&& {
|
||||
position: absolute;
|
||||
background: #d3dddd;
|
||||
padding: 1px 4px;
|
||||
border-radius: 3px;
|
||||
height: 20px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
visibility: hidden;
|
||||
top: -2px;
|
||||
:before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 29%;
|
||||
left: -4px;
|
||||
margin-left: -5px;
|
||||
border: 5px solid;
|
||||
border-color: #d3dddd transparent transparent transparent;
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
${ToolTip}:hover & {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const Love = styled.span`
|
||||
&& {
|
||||
color: #e25555;
|
||||
padding: 0 5px;
|
||||
}
|
||||
`;
|
||||
|
||||
export const Flag = styled(Icon)`
|
||||
&& {
|
||||
padding: 0 5px;
|
||||
}
|
||||
`;
|
||||
|
||||
export const Logo = Flag;
|
@ -1,269 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import type { Node } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import Button from '@material-ui/core/Button/index';
|
||||
import IconButton from '@material-ui/core/IconButton/index';
|
||||
import MenuItem from '@material-ui/core/MenuItem/index';
|
||||
import Menu from '@material-ui/core/Menu/index';
|
||||
import Info from '@material-ui/icons/Info';
|
||||
import Help from '@material-ui/icons/Help';
|
||||
import Tooltip from '@material-ui/core/Tooltip/index';
|
||||
import AccountCircle from '@material-ui/icons/AccountCircle';
|
||||
import { default as IconSearch } from '@material-ui/icons/Search';
|
||||
|
||||
import { getRegistryURL } from '../../utils/url';
|
||||
import ExternalLink from '../Link';
|
||||
import Logo from '../Logo';
|
||||
import RegistryInfoDialog from '../RegistryInfoDialog';
|
||||
import Label from '../Label';
|
||||
import Search from '../Search';
|
||||
import RegistryInfoContent from '../RegistryInfoContent';
|
||||
|
||||
import { IProps, IState } from './types';
|
||||
import type { ToolTipType } from './types';
|
||||
import { Greetings, NavBar, InnerNavBar, MobileNavBar, InnerMobileNavBar, LeftSide, RightSide, IconSearchButton, SearchWrapper } from './styles';
|
||||
|
||||
class Header extends Component<IProps, IState> {
|
||||
handleLoggedInMenu: Function;
|
||||
handleLoggedInMenuClose: Function;
|
||||
handleOpenRegistryInfoDialog: Function;
|
||||
handleCloseRegistryInfoDialog: Function;
|
||||
handleToggleLogin: Function;
|
||||
renderInfoDialog: Function;
|
||||
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
this.state = {
|
||||
openInfoDialog: false,
|
||||
registryUrl: getRegistryURL(),
|
||||
showMobileNavBar: false,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* opens popover menu for logged in user.
|
||||
*/
|
||||
handleLoggedInMenu = (event: SyntheticEvent<HTMLElement>) => {
|
||||
this.setState({
|
||||
anchorEl: event.currentTarget,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* closes popover menu for logged in user
|
||||
*/
|
||||
handleLoggedInMenuClose = () => {
|
||||
this.setState({
|
||||
anchorEl: null,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* opens registry information dialog.
|
||||
*/
|
||||
handleOpenRegistryInfoDialog = () => {
|
||||
this.setState({
|
||||
openInfoDialog: true,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* closes registry information dialog.
|
||||
*/
|
||||
handleCloseRegistryInfoDialog = () => {
|
||||
this.setState({
|
||||
openInfoDialog: false,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* close/open popover menu for logged in users.
|
||||
*/
|
||||
handleToggleLogin = () => {
|
||||
const { onToggleLoginModal } = this.props;
|
||||
this.setState(
|
||||
{
|
||||
anchorEl: null,
|
||||
},
|
||||
onToggleLoginModal
|
||||
);
|
||||
};
|
||||
|
||||
handleToggleMNav = () => {
|
||||
const { showMobileNavBar } = this.state;
|
||||
this.setState({
|
||||
showMobileNavBar: !showMobileNavBar,
|
||||
});
|
||||
};
|
||||
|
||||
handleDismissMNav = () => {
|
||||
this.setState({
|
||||
showMobileNavBar: false,
|
||||
});
|
||||
};
|
||||
|
||||
renderLeftSide = (): Node => {
|
||||
const { withoutSearch = false } = this.props;
|
||||
return (
|
||||
<LeftSide>
|
||||
<Link style={{ marginRight: '1em' }} to={'/'}>
|
||||
{this.renderLogo()}
|
||||
</Link>
|
||||
{!withoutSearch && (
|
||||
<SearchWrapper>
|
||||
<Search />
|
||||
</SearchWrapper>
|
||||
)}
|
||||
</LeftSide>
|
||||
);
|
||||
};
|
||||
|
||||
renderLogo = (): Node => {
|
||||
const { logo } = this.props;
|
||||
|
||||
if (logo !== '') {
|
||||
return <img alt={'logo'} height={'40px'} src={logo} />;
|
||||
} else {
|
||||
return <Logo />;
|
||||
}
|
||||
};
|
||||
|
||||
renderToolTipIcon = (title: string, type: ToolTipType) => {
|
||||
let content;
|
||||
switch (type) {
|
||||
case 'help':
|
||||
content = (
|
||||
<IconButton blank={true} color={'inherit'} component={ExternalLink} to={'https://verdaccio.org/docs/en/installation'}>
|
||||
<Help />
|
||||
</IconButton>
|
||||
);
|
||||
break;
|
||||
case 'info':
|
||||
content = (
|
||||
<IconButton color={'inherit'} id={'header--button-registryInfo'} onClick={this.handleOpenRegistryInfoDialog}>
|
||||
<Info />
|
||||
</IconButton>
|
||||
);
|
||||
break;
|
||||
case 'search':
|
||||
content = (
|
||||
<IconSearchButton color={'inherit'} onClick={this.handleToggleMNav}>
|
||||
<IconSearch />
|
||||
</IconSearchButton>
|
||||
);
|
||||
break;
|
||||
}
|
||||
return (
|
||||
<Tooltip disableFocusListener={true} title={title}>
|
||||
{content}
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
||||
renderRightSide = (): Node => {
|
||||
const { username = '', withoutSearch = false } = this.props;
|
||||
return (
|
||||
<RightSide>
|
||||
{!withoutSearch && this.renderToolTipIcon('Search packages', 'search')}
|
||||
{this.renderToolTipIcon('Documentation', 'help')}
|
||||
{this.renderToolTipIcon('Registry Information', 'info')}
|
||||
{username ? (
|
||||
this.renderMenu()
|
||||
) : (
|
||||
<Button color={'inherit'} id={'header--button-login'} onClick={this.handleToggleLogin}>
|
||||
{'Login'}
|
||||
</Button>
|
||||
)}
|
||||
</RightSide>
|
||||
);
|
||||
};
|
||||
|
||||
renderGreetings = () => {
|
||||
const { username = '' } = this.props;
|
||||
return (
|
||||
<>
|
||||
<Greetings>{`Hi,`}</Greetings>
|
||||
<Label capitalize={true} limit={140} text={username} weight={'bold'} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* render popover menu
|
||||
*/
|
||||
renderMenu = (): Node => {
|
||||
const { onLogout } = this.props;
|
||||
const { anchorEl } = this.state;
|
||||
const open = Boolean(anchorEl);
|
||||
return (
|
||||
<React.Fragment>
|
||||
<IconButton color={'inherit'} id={'header--button-account'} onClick={this.handleLoggedInMenu}>
|
||||
<AccountCircle />
|
||||
</IconButton>
|
||||
<Menu
|
||||
anchorEl={anchorEl}
|
||||
anchorOrigin={{
|
||||
vertical: 'top',
|
||||
horizontal: 'right',
|
||||
}}
|
||||
id={'sidebar-menu'}
|
||||
onClose={this.handleLoggedInMenuClose}
|
||||
open={open}
|
||||
transformOrigin={{
|
||||
vertical: 'top',
|
||||
horizontal: 'right',
|
||||
}}>
|
||||
<MenuItem disabled={true}>{this.renderGreetings()}</MenuItem>
|
||||
<MenuItem id={'header--button-logout'} onClick={onLogout}>
|
||||
{'Logout'}
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
renderInfoDialog = (): Node => {
|
||||
const { scope } = this.props;
|
||||
const { openInfoDialog, registryUrl } = this.state;
|
||||
return (
|
||||
<RegistryInfoDialog onClose={this.handleCloseRegistryInfoDialog} open={openInfoDialog}>
|
||||
<RegistryInfoContent registryUrl={registryUrl} scope={scope} />
|
||||
</RegistryInfoDialog>
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { showMobileNavBar } = this.state;
|
||||
const { withoutSearch = false } = this.props;
|
||||
return (
|
||||
<div>
|
||||
<NavBar position={'static'}>
|
||||
<InnerNavBar>
|
||||
{this.renderLeftSide()}
|
||||
{this.renderRightSide()}
|
||||
</InnerNavBar>
|
||||
{this.renderInfoDialog()}
|
||||
</NavBar>
|
||||
{showMobileNavBar &&
|
||||
!withoutSearch && (
|
||||
<MobileNavBar>
|
||||
<InnerMobileNavBar>
|
||||
<Search />
|
||||
</InnerMobileNavBar>
|
||||
<Button color={'inherit'} onClick={this.handleDismissMNav}>
|
||||
{'Cancel'}
|
||||
</Button>
|
||||
</MobileNavBar>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Header;
|
@ -1,106 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import styled, { css } from 'react-emotion';
|
||||
import AppBar from '@material-ui/core/AppBar/index';
|
||||
import Toolbar from '@material-ui/core/Toolbar/index';
|
||||
import IconButton from '@material-ui/core/IconButton/index';
|
||||
|
||||
import colors from '../../utils/styles/colors';
|
||||
import mq from '../../utils/styles/media';
|
||||
|
||||
export const InnerNavBar = styled(Toolbar)`
|
||||
&& {
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0 15px;
|
||||
}
|
||||
`;
|
||||
|
||||
export const Greetings = styled.span`
|
||||
&& {
|
||||
margin: 0 5px 0 0;
|
||||
}
|
||||
`;
|
||||
|
||||
export const RightSide = styled(Toolbar)`
|
||||
&& {
|
||||
display: flex;
|
||||
padding: 0;
|
||||
}
|
||||
`;
|
||||
|
||||
export const LeftSide = styled(RightSide)`
|
||||
&& {
|
||||
flex: 1;
|
||||
}
|
||||
`;
|
||||
|
||||
export const MobileNavBar = styled.div`
|
||||
&& {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
border-bottom: 1px solid ${colors.greyLight};
|
||||
padding: 8px;
|
||||
position: relative;
|
||||
}
|
||||
`;
|
||||
|
||||
export const InnerMobileNavBar = styled.div`
|
||||
&& {
|
||||
border-radius: 4px;
|
||||
background-color: ${colors.greyLight};
|
||||
color: ${colors.white};
|
||||
width: 100%;
|
||||
padding: 0px 5px;
|
||||
margin: 0 10px 0 0;
|
||||
}
|
||||
`;
|
||||
|
||||
export const IconSearchButton = styled(IconButton)`
|
||||
&& {
|
||||
display: block;
|
||||
}
|
||||
`;
|
||||
|
||||
export const SearchWrapper = styled.div`
|
||||
&& {
|
||||
display: none;
|
||||
max-width: 393px;
|
||||
width: 100%;
|
||||
}
|
||||
`;
|
||||
|
||||
export const NavBar = styled(AppBar)`
|
||||
&& {
|
||||
background-color: ${colors.primary};
|
||||
min-height: 60px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
${mq.medium(css`
|
||||
${SearchWrapper} {
|
||||
display: flex;
|
||||
}
|
||||
${IconSearchButton} {
|
||||
display: none;
|
||||
}
|
||||
${MobileNavBar} {
|
||||
display: none;
|
||||
}
|
||||
`)};
|
||||
${mq.large(css`
|
||||
${InnerNavBar} {
|
||||
padding: 0 20px;
|
||||
}
|
||||
`)};
|
||||
${mq.xlarge(css`
|
||||
${InnerNavBar} {
|
||||
max-width: 1240px;
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
}
|
||||
`)};
|
||||
}
|
||||
`;
|
@ -1,22 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
* @flow
|
||||
*/
|
||||
|
||||
export interface IProps {
|
||||
logo: string;
|
||||
username?: string;
|
||||
onLogout?: Function;
|
||||
onToggleLoginModal: Function;
|
||||
scope: string;
|
||||
withoutSearch?: boolean;
|
||||
}
|
||||
|
||||
export interface IState {
|
||||
anchorEl?: any;
|
||||
openInfoDialog: boolean;
|
||||
registryUrl: string;
|
||||
showMobileNavBar: boolean;
|
||||
}
|
||||
|
||||
export type ToolTipType = 'search' | 'help' | 'info';
|
@ -1,51 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import React, { Fragment } from 'react';
|
||||
import type { Node } from 'react';
|
||||
import CardActions from '@material-ui/core/CardActions/index';
|
||||
import CardContent from '@material-ui/core/CardContent/index';
|
||||
import Button from '@material-ui/core/Button/index';
|
||||
import Typography from '@material-ui/core/Typography/index';
|
||||
|
||||
import CopyToClipBoard from '../CopyToClipBoard/index';
|
||||
import { getRegistryURL } from '../../utils/url';
|
||||
import { CardStyled as Card, HelpTitle } from './styles';
|
||||
|
||||
function renderHeadingClipboardSegments(title: string, text: string): Node {
|
||||
return (
|
||||
<Fragment>
|
||||
<Typography variant={'body2'}>{title}</Typography>
|
||||
<CopyToClipBoard text={text} />
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
const Help = (): Node => {
|
||||
const registryUrl = getRegistryURL();
|
||||
|
||||
return (
|
||||
<Card id={'help-card'}>
|
||||
<CardContent>
|
||||
<Typography component={'h2'} gutterBottom={true} id={'help-card__title'} variant={'headline'}>
|
||||
{'No Package Published Yet.'}
|
||||
</Typography>
|
||||
<HelpTitle color={'textSecondary'} gutterBottom={true}>
|
||||
{'To publish your first package just:'}
|
||||
</HelpTitle>
|
||||
{renderHeadingClipboardSegments('1. Login', `npm adduser --registry ${registryUrl}`)}
|
||||
{renderHeadingClipboardSegments('2. Publish', `npm publish --registry ${registryUrl}`)}
|
||||
<Typography variant={'body2'}>{'3. Refresh this page.'}</Typography>
|
||||
</CardContent>
|
||||
<CardActions>
|
||||
<Button color={'primary'} href={'https://verdaccio.org/docs/en/installation'} size={'small'} target={'_blank'}>
|
||||
{'Learn More'}
|
||||
</Button>
|
||||
</CardActions>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export default Help;
|
@ -1,21 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import styled from 'react-emotion';
|
||||
import Card from '@material-ui/core/Card/index';
|
||||
import Typography from '@material-ui/core/Typography/index';
|
||||
|
||||
export const CardStyled = styled(Card)`
|
||||
&& {
|
||||
width: 600px;
|
||||
margin: auto;
|
||||
}
|
||||
`;
|
||||
|
||||
export const HelpTitle = styled(Typography)`
|
||||
&& {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
`;
|
@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" id="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"/></svg>
|
Before Width: | Height: | Size: 380 B |
@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 45 45" id="brazil"><defs><clipPath id="a"><path d="M0 36h36V0H0v36z"/></clipPath></defs><g clip-path="url(#a)" transform="matrix(1.25 0 0 -1.25 0 45)"><path d="M36 9a4 4 0 0 0-4-4H4a4 4 0 0 0-4 4v18a4 4 0 0 0 4 4h28a4 4 0 0 0 4-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 1 1-12.917 0 6.458 6.458 0 0 1 12.917 0" fill="#002776"/><path d="M12.277 21.113a6.406 6.406 0 0 1-.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-1zM14 14.767h-1v1h1v-1z" fill="#88c9f9"/><path d="M16 16.767h-1v1h1v-1zM18 15.767h-1v1h1v-1zM22 13.767h-1v1h1v-1zM19 12.767h-1v1h1v-1zM22 18.767h-1v1h1v-1z" fill="#55acee"/><path d="M20 14.767h-1v1h1v-1z" fill="#3b88c3"/></g></svg>
|
Before Width: | Height: | Size: 898 B |
@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 45 45" id="china"><defs><clipPath id="a"><path d="M0 36h36V0H0v36z"/></clipPath></defs><g clip-path="url(#a)" transform="matrix(1.25 0 0 -1.25 0 45)"><path d="M36 9a4 4 0 0 0-4-4H4a4 4 0 0 0-4 4v18a4 4 0 0 0 4 4h28a4 4 0 0 0 4-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.67zM13 28.472l.34-.688.759-.11-.549-.536.129-.756-.679.357-.679-.357.13.756-.55.536.76.11.339.688zM15 24.472l.34-.688.759-.11-.549-.536.129-.756-.679.357-.679-.357.13.756-.55.536.76.11.339.688zM15 20.472l.34-.688.759-.11-.549-.536.129-.756-.679.357-.679-.357.13.756-.55.536.76.11.339.688zM13 16.473l.34-.69.759-.11-.549-.534.129-.757-.679.357-.679-.357.13.757-.55.535.76.11.339.689z" fill="#ffde02"/></g></svg>
|
Before Width: | Height: | Size: 833 B |
@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 45 45" id="earth"><Title>Earth</Title><defs><clipPath id="a"><path d="M0 36h36V0H0v36z"/></clipPath><clipPath id="b"><path d="M18 36C8.059 36 0 27.941 0 18S8.059 0 18 0s18 8.059 18 18-8.059 18-18 18z"/></clipPath></defs><g clip-path="url(#a)" transform="matrix(1.25 0 0 -1.25 0 45)"><path d="M36 18c0-9.941-8.059-18-18-18S0 8.059 0 18s8.059 18 18 18 18-8.059 18-18" fill="#88c9f9"/></g><g clip-path="url(#b)" transform="matrix(1.25 0 0 -1.25 0 45)"><path d="M3.627 28.952c-.45 2.93 2.195 4.156 3.607 4.47 1.412.314 2.776.62 2.933-.006.156-.628.311-1.46 1.173-1.148.862.314 3.043.56 4.063 1.342 1.02.783 2.244.787 3.264.473 1.02-.313 3.877-.227 3.25-1.167-.627-.94-1.825-.827-2.45-1.924-.628-1.099.171-1.826 1.033-1.826.865 0 1.71-.135 2.26.727.548.863-.383 2.463.324 2.357.706-.106 1.477-.866 2.03-2.043.547-1.176 1.408-.47 1.723-1.176.313-.705 2.04-2.039 1.177-1.804-.864.236-1.726.392-1.96-.47-.237-.863.388-1.726-.237-1.647-.627.08-.86-.089-1.725-.004-.862.083-1.333.631-2.039-.545-.705-1.175-1.254-1.96-1.567-2.509-.315-.549-.785-.86-.55-1.96.235-1.099-.628-.785-.628.156 0 .94-.548 1.098-1.253.942-.706-.157-1.803-.313-1.724-1.098.077-.784-.315-1.725.313-2.352.627-.629 1.33.076 1.723-.158.393-.237 1.525-.023 1.133-.416-.393-.39-1.76-.88-.976-1.509a4.831 4.831 0 0 1 1.893-.907c.313-.08.062.774 1.083 1.166 1.017.392 2.608 1.29 3 .584.391-.705.338-.595 1.75-.75 1.41-.156 1.79-.585 2.417-1.917.626-1.333.446-1.192 1.462-1.58 1.021-.394 1.678-.223.737-1.087-.94-.86-1.65-.814-2.199-1.833-.55-1.017-.153-1.73-1.25-2.75A20.755 20.755 0 0 0 24 4c-.618-.37-2.162-2.07-3.083-2.667-.834-.54-1.083 0-1.083 0s.256 1.667.964 2.372c.704.705 1.105 3.344.87 4.128-.235.783-1.36 1.02-1.75 1.333-.393.312-1.418 1.548-1.418 2.334 0 .784 1.71 2.81 1.71 2.81.218-1.089-1.039.328-1.627.523-.47.157-1.542 1.656-2.459 1.814-.916.16-1.363.7-2.068 1.25-.706.55-2.43 1.332-2.353 2.195.08.862-1.725 1.568-2.038 1.568-.314 0-1.019 0-1.647 1.098-.627 1.098-1.725 2.196-1.41 2.98.312.783.391 1.726.233 2.588-.156.862-1.332 1.176-1.567.941-.235-.236-1.489-1.335-1.647-.315" fill="#5c913b"/></g></svg>
|
Before Width: | Height: | Size: 2.1 KiB |
@ -1,3 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="filebinary">
|
||||
<path d="M8.5 1H1c-.55 0-1 .45-1 1v12c0 .55.45 1 1 1h10c.55 0 1-.45 1-1V4.5L8.5 1zM11 14H1V2h7l3 3v9zM5 6.98L3.5 8.5 5 10l-.5 1L2 8.5 4.5 6l.5.98zM7.5 6L10 8.5 7.5 11l-.5-.98L8.5 8.5 7 7l.5-1z"></path>
|
||||
</svg>
|
Before Width: | Height: | Size: 265 B |
@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" id="india"><Title>India</Title><path d="M0 384c0 31.418 25.473 56.889 56.889 56.889H455.11C486.53 440.889 512 415.416 512 384v-56.889H0V384z" fill="#138808"/><path d="M0 327.111h512V184.89H0V327.11z" 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=".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"/></svg>
|
Before Width: | Height: | Size: 1.3 KiB |
@ -1,3 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="law">
|
||||
<path fill-rule="evenodd" d="M7 4c-.83 0-1.5-.67-1.5-1.5S6.17 1 7 1s1.5.67 1.5 1.5S7.83 4 7 4zm7 6c0 1.11-.89 2-2 2h-1c-1.11 0-2-.89-2-2l2-4h-1c-.55 0-1-.45-1-1H8v8c.42 0 1 .45 1 1h1c.42 0 1 .45 1 1H3c0-.55.58-1 1-1h1c0-.55.58-1 1-1h.03L6 5H5c0 .55-.45 1-1 1H3l2 4c0 1.11-.89 2-2 2H2c-1.11 0-2-.89-2-2l2-4H1V5h3c0-.55.45-1 1-1h4c.55 0 1 .45 1 1h3v1h-1l2 4zM2.5 7L1 10h3L2.5 7zM13 10l-1.5-3-1.5 3h3z"></path>
|
||||
</svg>
|
Before Width: | Height: | Size: 464 B |
@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 490.652 490.652" id="license"><path d="M456.607 9.904H34.04C15.269 9.904 0 25.17 0 43.945V310.17c0 18.77 15.269 34.039 34.04 34.039h260.642c-4.043-10.35-6.534-21.737-7.159-33.742l-253.974-.297.491-266.718 423.063.493V310.17c0 .279-.23.492-.495.492l-15.251-.019c-.637 11.942-3.111 23.263-7.138 33.565h22.389c18.777 0 34.045-15.27 34.045-34.039V43.945c-.001-18.775-15.268-34.041-34.046-34.041z"/><path d="M364.447 381.02c33.354 0 60.386-34.152 60.386-76.289 0-42.131-27.031-76.284-60.386-76.284-33.35 0-60.38 34.153-60.38 76.284 0 42.137 27.031 76.289 60.38 76.289zM81.087 118.345h149.561c9.273 0 16.776-7.499 16.776-16.772 0-9.271-7.504-16.775-16.776-16.775H81.087c-9.271 0-16.771 7.504-16.771 16.775 0 9.273 7.499 16.772 16.771 16.772zM415.167 177.061c0-9.273-7.505-16.779-16.776-16.779H81.087c-9.271 0-16.771 7.506-16.771 16.779 0 9.271 7.499 16.77 16.771 16.77h317.304c9.271 0 16.776-7.498 16.776-16.77zM81.087 235.768c-9.271 0-16.771 7.504-16.771 16.771 0 9.273 7.499 16.777 16.771 16.777h149.561c9.273 0 16.776-7.504 16.776-16.777 0-9.268-7.504-16.771-16.776-16.771H81.087zM415.425 374.452c-.065.082-23.295 26.869-55.728 23.081l40.331 81.577a2.93 2.93 0 0 0 2.637 1.639h.215a2.95 2.95 0 0 0 2.571-2.031l10.174-31.234a3.024 3.024 0 0 1 1.488-1.728 3.07 3.07 0 0 1 2.28-.13l30.977 10.88c.324.112.651.163.98.163.836 0 1.623-.348 2.195-.982.803-.9.964-2.18.441-3.244l-38.561-77.991zM313.653 374.598c-.049-.047-.118-.096-.185-.146l-38.561 77.973a2.953 2.953 0 0 0 .448 3.262 2.961 2.961 0 0 0 3.174.819l30.978-10.88c.328-.113.658-.16.982-.16.442 0 .884.096 1.294.29.706.36 1.245.987 1.493 1.741l10.173 31.221c.378 1.133 1.391 1.936 2.572 2.014h.21a2.95 2.95 0 0 0 2.642-1.621l26.206-53.01-16.755-33.893c-.066-.034-14.672-5.884-24.671-17.61z"/></svg>
|
Before Width: | Height: | Size: 1.8 KiB |
@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" id="nicaragua"><Title>Nicaragua</Title><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.89h512V327.11z" 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"/><g fill="#e2f09f"><path d="M237.995 262.67l10.226 11.107-5.12.442-5.774-8.434.668-3.115zM250.226 263.553l8.22 7.338-3.782.654-3.996-5.546-.442-2.446zM260.665 263.111l7.111 6.67L266 270.89l-5.774-6.229.44-1.55zM269.995 263.111l-2.888 2.66 1.338 1.565 1.55-4.225zM278.884 263.339l-3.328 4.224 1.109 1.99 2.446-4.664-.227-1.55z"/></g><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"/><g fill="#eee"><path d="M264.291 322.873h-14.165V309.29h14.165v13.582zM247.865 203.975h-10.183l-5.106-12.459 10.198-1.137 5.091 13.596zM271.075 209.635l-6.784-2.261 3.385-16.426 13.028 1.137-9.629 17.55zM209.934 279.281l-19.811 4.523-.57-13.583 19.812-2.83.569 11.89zM215.04 293.433l-16.426 6.812-3.954-10.766 15.289-5.106 5.091 9.06zM324.309 280.989l-18.105-5.091v-9.63l20.366-2.83-2.261 17.55zM315.306 299.663l-16.981-7.937 5.646-10.182 18.703 5.66-7.368 12.459z"/></g></svg>
|
Before Width: | Height: | Size: 3.0 KiB |
@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 45 45" id="pakistan"><defs><clipPath id="a"><path d="M0 36h36V0H0v36z"/></clipPath></defs><g clip-path="url(#a)" transform="matrix(1.25 0 0 -1.25 0 45)"><path d="M27.61 20.952l-1.213-2.022-.208 2.349-2.298.528 2.17.924-.207 2.349 1.548-1.78 2.17.925-1.212-2.023 1.548-1.78-2.298.53zM22.5 10.528a7.5 7.5 0 0 0-7.5 7.5c0 3.72 2.711 6.798 6.263 7.389a6.494 6.494 0 0 1-3.763-5.89 6.5 6.5 0 0 1 6.5-6.5 6.494 6.494 0 0 1 5.89 3.764c-.592-3.552-3.67-6.263-7.39-6.263M32 31H9V5h23a4 4 0 0 1 4 4v18a4 4 0 0 1-4 4" fill="#004600"/><path d="M4 31a4 4 0 0 1-4-4V9a4 4 0 0 1 4-4h5v26H4z" fill="#eee"/><path d="M29.572 24.225l-2.17-.924-1.548 1.779.207-2.35-2.17-.922 2.298-.528.208-2.35 1.213 2.022 2.298-.53-1.548 1.78 1.212 2.023z" fill="#fff"/><path d="M24 13.028a6.496 6.496 0 1 0-2.737 12.39c-3.552-.591-6.263-3.67-6.263-7.39a7.5 7.5 0 0 1 7.5-7.5c3.72 0 6.799 2.71 7.39 6.263A6.496 6.496 0 0 0 24 13.028" fill="#fff"/></g></svg>
|
Before Width: | Height: | Size: 977 B |
@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 45 45" id="spain"><defs><clipPath id="a"><path d="M0 36h36V0H0v36z"/></clipPath></defs><g clip-path="url(#a)" transform="matrix(1.25 0 0 -1.25 0 45)"><path d="M36 9a4 4 0 0 0-4-4H4a4 4 0 0 0-4 4v18a4 4 0 0 0 4 4h28a4 4 0 0 0 4-4V9z" fill="#c60a1d"/><path d="M36 12H0v12h36V12z" fill="#ffc400"/><path d="M9 19v-3a3 3 0 1 1 6 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-7zM17 13h-1v7h1v-7z" fill="#99aab5"/><path d="M9 13H6v1h3v-1zM18 13h-3v1h3v-1zM8 20H7v1h1v-1zM17 20h-1v1h1v-1z" fill="#66757f"/></g></svg>
|
Before Width: | Height: | Size: 851 B |
@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" id="time"><path d="M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8z"/><path d="M0 0h24v24H0z" fill="none"/><path d="M12.5 7H11v6l5.25 3.15.75-1.23-4.5-2.67z"/></svg>
|
Before Width: | Height: | Size: 321 B |
@ -1 +0,0 @@
|
||||
<svg width='100px' height='100px' xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" id="verdaccio"><defs><path d="M48 17.6L32.8 48H24L.4.8h15.2l12.8 25.6 4.4-8.8H48z" id="a"/><filter id="b" filterUnits="objectBoundingBox" height="140.3%" width="139.9%" y="-11.7%" x="-20%"><feGaussianBlur stdDeviation="2.5"/></filter><path d="M50.8 12H35.6L41.2.8h15.2L50.8 12z" id="c"/><filter id="d" filterUnits="objectBoundingBox" height="269.6%" width="191.3%" y="-49.1%" x="-45.7%"><feGaussianBlur stdDeviation="2.5"/></filter><path d="M32.8 48H24L.4.8h15.2l20.377 40.89L32.8 48z" id="e"/></defs><path fill="none" d="M-1-1h582v402H-1z"/><g stroke="null" fill-rule="evenodd" fill="none"><use transform="translate(-37.027 -40.362) scale(1.71429)" x="22.366" y="28.311" xlink:href="#a" filter="url(#b)" fill="#000"/><use transform="translate(-37.027 -40.362) scale(1.71429)" x="22.366" y="28.311" xlink:href="#a" fill="#405236"/><path stroke="#405236" d="M80.27 40.4H58.816L50 58.028 26.785 11.6H5.33l38.4 76.8h12.542l24-48z" stroke-width="2.4"/><use transform="translate(-37.027 -40.362) scale(1.71429)" x="22.366" y="28.311" xlink:href="#c" filter="url(#d)" fill="#000"/><use transform="translate(-37.027 -40.362) scale(1.71429)" x="22.366" y="28.311" xlink:href="#c" fill="#CD4000"/><path stroke="#CD4000" d="M87.128 26.686L94.671 11.6H73.215l-7.543 15.086h21.456z" stroke-width="2.4"/><use transform="translate(-37.027 -40.362) scale(1.71429)" x="22.366" y="28.311" xlink:href="#e" fill="#4A5E3F"/><path stroke="#405236" d="M56.274 88.4l4.415-8.763L26.783 11.6H5.33l38.4 76.8h12.547-.002z" stroke-width="2.4"/><path stroke="#CD4000" stroke-linecap="square" stroke-width="2.4" d="M65.771 11.6h26.094m-32.95 6.857h26.092m-32.95 8.229H78.15"/></g></svg>
|
Before Width: | Height: | Size: 1.7 KiB |
@ -1,6 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 14 16" height="16" width="14" id="version">
|
||||
<path fill-rule="evenodd" d="M13 3H7c-.55 0-1 .45-1 1v8c0 .55.45 1 1 1h6c.55 0 1-.45 1-1V4c0-.55-.45-1-1-1zm-1 8H8V5h4v6zM4 4h1v1H4v6h1v1H4c-.55 0-1-.45-1-1V5c0-.55.45-1 1-1zM1 5h1v1H1v4h1v1H1c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1z"></path>
|
||||
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 344 B |
@ -1,61 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import type { Node } from 'react';
|
||||
import capitalize from 'lodash/capitalize';
|
||||
|
||||
import { Svg, Img, ImgWrapper } from './styles';
|
||||
import { IProps, IIconsMap } from './types';
|
||||
|
||||
import brazil from './img/brazil.svg';
|
||||
import china from './img/china.svg';
|
||||
import india from './img/india.svg';
|
||||
import nicaragua from './img/nicaragua.svg';
|
||||
import pakistan from './img/pakistan.svg';
|
||||
import austria from './img/austria.svg';
|
||||
import spain from './img/spain.svg';
|
||||
import earth from './img/earth.svg';
|
||||
import verdaccio from './img/verdaccio.svg';
|
||||
import filebinary from './img/filebinary.svg';
|
||||
import law from './img/law.svg';
|
||||
import license from './img/license.svg';
|
||||
import time from './img/time.svg';
|
||||
import version from './img/version.svg';
|
||||
|
||||
export const Icons: $Shape<IIconsMap> = {
|
||||
// flags
|
||||
brazil,
|
||||
spain,
|
||||
china,
|
||||
nicaragua,
|
||||
pakistan,
|
||||
india,
|
||||
austria,
|
||||
earth,
|
||||
verdaccio,
|
||||
// other icons
|
||||
filebinary,
|
||||
law,
|
||||
license,
|
||||
time,
|
||||
version,
|
||||
};
|
||||
|
||||
const Icon = ({ className, name, size = 'sm', img = false, pointer = false, ...props }: IProps): Node => {
|
||||
const title = capitalize(name);
|
||||
return img ? (
|
||||
<ImgWrapper className={className} pointer={pointer} size={size} title={title} {...props}>
|
||||
<Img alt={title} src={Icons[name]} />
|
||||
</ImgWrapper>
|
||||
) : (
|
||||
<Svg className={className} pointer={pointer} size={size} {...props}>
|
||||
<title>{title}</title>
|
||||
<use xlinkHref={`${Icons[name]}#${name}`} />
|
||||
</Svg>
|
||||
);
|
||||
};
|
||||
|
||||
export default Icon;
|
@ -1,55 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import styled, { css } from 'react-emotion';
|
||||
import { IProps } from './types';
|
||||
|
||||
const getSize = (size: string) => {
|
||||
switch (size) {
|
||||
case 'md':
|
||||
return `
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
`;
|
||||
case 'lg':
|
||||
return `
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
`;
|
||||
default:
|
||||
return `
|
||||
width: 14px;
|
||||
height: 16px;
|
||||
`;
|
||||
}
|
||||
};
|
||||
|
||||
const commonStyle = ({ size = 'sm', pointer, modifiers }: IProps) => css`
|
||||
&& {
|
||||
display: inline-block;
|
||||
cursor: ${pointer ? 'pointer' : 'default'};
|
||||
${getSize(size)};
|
||||
${modifiers && modifiers};
|
||||
}
|
||||
`;
|
||||
|
||||
export const Svg = styled.svg`
|
||||
&& {
|
||||
${commonStyle};
|
||||
}
|
||||
`;
|
||||
|
||||
export const ImgWrapper = styled.span`
|
||||
&& {
|
||||
${commonStyle};
|
||||
}
|
||||
`;
|
||||
|
||||
export const Img = styled.img`
|
||||
&& {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
`;
|
@ -1,35 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
* @flow
|
||||
*/
|
||||
import { Icons } from './index';
|
||||
import type { Styles } from '../../../../types';
|
||||
|
||||
export interface IIconsMap {
|
||||
brazil: string;
|
||||
spain: string;
|
||||
china: string;
|
||||
nicaragua: string;
|
||||
pakistan: string;
|
||||
austria: string;
|
||||
india: string;
|
||||
earth: string;
|
||||
verdaccio: string;
|
||||
license: string;
|
||||
time: string;
|
||||
law: string;
|
||||
version: string;
|
||||
filebinary: string;
|
||||
[key: string]: string;
|
||||
}
|
||||
|
||||
export interface IProps {
|
||||
name: $Keys<typeof Icons>;
|
||||
className?: string;
|
||||
// $FlowFixMe
|
||||
onClick?: (event: SyntheticMouseEvent<SVGElement | HTMLSpanElement>) => void;
|
||||
size?: 'sm' | 'md';
|
||||
pointer?: boolean;
|
||||
img?: boolean;
|
||||
modifiers?: Styles;
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 256 256" enable-background="new 0 0 256 256" xml:space="preserve">
|
||||
<polygon fill="#CC0000" points="23,65.6 130,3.3 237,65.6 237,190.1 130,252.4 23,190.1 "/>
|
||||
<polygon fill="#FFFFFF" points="133,127.8 232.5,70.5 236.5,186 133,248.5 "/>
|
||||
<g>
|
||||
<path fill="#CC0000" d="M234,67l-0.3,122.4l-103.8,60.2l-0.5-120.3L234,67z M146.7,139.3l0.3,80.4l34.5-20.1l-0.1-60.6l17.4-10.3
|
||||
l0,60.8l17.4-10.2l0.1-81.4L146.7,139.3z"/>
|
||||
</g>
|
||||
<path fill="#910505" d="M136.8,4.2c-4.8-2.7-12.5-2.7-17.3,0L24.7,58.7c-4.8,2.7-8.6,9.5-8.6,14.9v109c0,5.5,3.9,12.2,8.6,14.9
|
||||
l94.8,54.5c4.8,2.7,12.5,2.7,17.3,0l94.8-54.5c4.8-2.7,8.6-9.5,8.6-14.9v-109c0-5.5-3.9-12.2-8.6-14.9L136.8,4.2z M220.9,61.2
|
||||
c4.8,2.7,4.8,7.2,0,9.9l-83,47.7c-4.8,2.7-12.5,2.7-17.3,0L36.4,70.4c-4.8-2.7-4.8-7.2,0-9.9l83-47.7c4.8-2.7,12.5-2.7,17.3,0
|
||||
L220.9,61.2z M23.5,81.5c0-5.5,3.9-7.7,8.6-5l84.9,48.8c4.8,2.7,8.6,9.5,8.6,14.9V237c0,5.5-3.9,7.7-8.6,5l-84.9-48.8
|
||||
c-4.8-2.7-8.6-9.5-8.6-14.9V81.5z M141.8,240.5c-4.8,2.7-8.6,0.5-8.6-5v-95.3c0-5.5,3.9-12.2,8.6-14.9L224.2,78
|
||||
c4.8-2.7,8.6-0.5,8.6,5v95.3c0,5.5-3.9,12.2-8.6,14.9L141.8,240.5z"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 1.2 KiB |
@ -1 +0,0 @@
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid meet" viewBox="66.09157809474142 33.5 184.5 184.49999999999997" width="180" height="180"><defs><path d="M67.59 35L247.59 35L247.59 215L67.59 215L67.59 35Z" id="b2JZZcA3fT"></path><path d="M237.6 95L187.6 95L187.6 45L237.6 45L237.6 95Z" id="bj0tb0Y8q"></path><path d="M182.59 95L132.59 95L132.59 45L182.59 45L182.59 95Z" id="dkDSTzPj3"></path><path d="M127.59 95L77.59 95L77.59 45L127.59 45L127.59 95Z" id="a4vNdcNLpF"></path><path d="M237.6 150L187.6 150L187.6 100L237.6 100L237.6 150Z" id="h2t4Zj1jSU"></path><path d="M182.59 150L132.59 150L132.59 100L182.59 100L182.59 150Z" id="b4t5pngwvT"></path><path d="M182.59 205L132.59 205L132.59 155L182.59 155L182.59 205Z" id="b9s1gd5m2"></path><path d="M237.6 205L187.6 205L187.6 155L237.6 155L237.6 205Z" id="cmt9WLvz7"></path><path d="M127.59 205L77.59 205L77.59 155L127.59 155L127.59 205Z" id="bJUNqgFSg"></path></defs><g><g><use xlink:href="#b2JZZcA3fT" opacity="1" fill="#ffffff" fill-opacity="1"></use></g><g><use xlink:href="#bj0tb0Y8q" opacity="1" fill="#f9ad00" fill-opacity="1"></use></g><g><use xlink:href="#dkDSTzPj3" opacity="1" fill="#f9ad00" fill-opacity="1"></use></g><g><use xlink:href="#a4vNdcNLpF" opacity="1" fill="#f9ad00" fill-opacity="1"></use></g><g><use xlink:href="#h2t4Zj1jSU" opacity="1" fill="#f9ad00" fill-opacity="1"></use></g><g><use xlink:href="#b4t5pngwvT" opacity="1" fill="#4e4e4e" fill-opacity="1"></use></g><g><use xlink:href="#b9s1gd5m2" opacity="1" fill="#4e4e4e" fill-opacity="1"></use></g><g><use xlink:href="#cmt9WLvz7" opacity="1" fill="#4e4e4e" fill-opacity="1"></use></g><g><use xlink:href="#bJUNqgFSg" opacity="1" fill="#4e4e4e" fill-opacity="1"></use></g></g></svg>
|
Before Width: | Height: | Size: 1.7 KiB |
@ -1 +0,0 @@
|
||||
<svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 518 518"><style>.st0{fill:#2c8ebb}.st1{fill:#fff}</style><path class="st0" d="M259 0c143 0 259 116 259 259S402 518 259 518 0 402 0 259 116 0 259 0z"/><path class="st1" d="M435.2 337.5c-1.8-14.2-13.8-24-29.2-23.8-23 .3-42.3 12.2-55.1 20.1-5 3.1-9.3 5.4-13 7.1.8-11.6.1-26.8-5.9-43.5-7.3-20-17.1-32.3-24.1-39.4 8.1-11.8 19.2-29 24.4-55.6 4.5-22.7 3.1-58-7.2-77.8-2.1-4-5.6-6.9-10-8.1-1.8-.5-5.2-1.5-11.9.4C293.1 96 289.6 93.8 286.9 92c-5.6-3.6-12.2-4.4-18.4-2.1-8.3 3-15.4 11-22.1 25.2-1 2.1-1.9 4.1-2.7 6.1-12.7.9-32.7 5.5-49.6 23.8-2.1 2.3-6.2 4-10.5 5.6h.1c-8.8 3.1-12.8 10.3-17.7 23.3-6.8 18.2.2 36.1 7.1 47.7-9.4 8.4-21.9 21.8-28.5 37.5-8.2 19.4-9.1 38.4-8.8 48.7-7 7.4-17.8 21.3-19 36.9-1.6 21.8 6.3 36.6 9.8 42 1 1.6 2.1 2.9 3.3 4.2-.4 2.7-.5 5.6.1 8.6 1.3 7 5.7 12.7 12.4 16.3 13.2 7 31.6 10 45.8 2.9 5.1 5.4 14.4 10.6 31.3 10.6h1c4.3 0 58.9-2.9 74.8-6.8 7.1-1.7 12-4.7 15.2-7.4 10.2-3.2 38.4-12.8 65-30 18.8-12.2 25.3-14.8 39.3-18.2 13.6-3.3 22.1-15.7 20.4-29.4zm-23.8 14.7c-16 3.8-24.1 7.3-43.9 20.2-30.9 20-64.7 29.3-64.7 29.3s-2.8 4.2-10.9 6.1c-14 3.4-66.7 6.3-71.5 6.4-12.9.1-20.8-3.3-23-8.6-6.7-16 9.6-23 9.6-23s-3.6-2.2-5.7-4.2c-1.9-1.9-3.9-5.7-4.5-4.3-2.5 6.1-3.8 21-10.5 27.7-9.2 9.3-26.6 6.2-36.9.8-11.3-6 .8-20.1.8-20.1s-6.1 3.6-11-3.8c-4.4-6.8-8.5-18.4-7.4-32.7 1.2-16.3 19.4-32.1 19.4-32.1s-3.2-24.1 7.3-48.8c9.5-22.5 35.1-40.6 35.1-40.6s-21.5-23.8-13.5-45.2c5.2-14 7.3-13.9 9-14.5 6-2.3 11.8-4.8 16.1-9.5 21.5-23.2 48.9-18.8 48.9-18.8s13-39.5 25-31.8c3.7 2.4 17 32 17 32s14.2-8.3 15.8-5.2c8.6 16.7 9.6 48.6 5.8 68-6.4 32-22.4 49.2-28.8 60-1.5 2.5 17.2 10.4 29 43.1 10.9 29.9 1.2 55 2.9 57.8.3.5.4.7.4.7s12.5 1 37.6-14.5c13.4-8.3 29.3-17.6 47.4-17.8 17.5-.3 18.4 20.2 5.2 23.4z"/></svg>
|
Before Width: | Height: | Size: 1.7 KiB |
@ -1,58 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
*/
|
||||
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import List from '@material-ui/core/List/index';
|
||||
import ListItemText from '@material-ui/core/ListItemText/index';
|
||||
|
||||
import { DetailContextConsumer } from '../../pages/version/index';
|
||||
import CopyToClipBoard from '../CopyToClipBoard';
|
||||
|
||||
import { Heading, InstallItem, PackageMangerAvatar } from './styles';
|
||||
// logos of package managers
|
||||
import npm from './img/npm.svg';
|
||||
import pnpm from './img/pnpm.svg';
|
||||
import yarn from './img/yarn.svg';
|
||||
|
||||
class Install extends Component {
|
||||
render() {
|
||||
return (
|
||||
<DetailContextConsumer>
|
||||
{context => {
|
||||
return this.renderCopyCLI(context);
|
||||
}}
|
||||
</DetailContextConsumer>
|
||||
);
|
||||
}
|
||||
|
||||
renderCopyCLI = ({ packageName }) => {
|
||||
return (
|
||||
<>
|
||||
<List subheader={<Heading variant={'subheading'}>{'Installation'}</Heading>}>{this.renderListItems(packageName)}</List>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
renderListItems = packageName => {
|
||||
return (
|
||||
<>
|
||||
<InstallItem>
|
||||
<PackageMangerAvatar alt={'npm logo'} src={npm} />
|
||||
<ListItemText primary={<CopyToClipBoard text={`npm install ${packageName}`} />} secondary={'Install using NPM'} />
|
||||
</InstallItem>
|
||||
<InstallItem>
|
||||
<PackageMangerAvatar alt={'yarn logo'} src={yarn} />
|
||||
<ListItemText primary={<CopyToClipBoard text={`yarn add ${packageName}`} />} secondary={'Install using Yarn'} />
|
||||
</InstallItem>
|
||||
<InstallItem>
|
||||
<PackageMangerAvatar alt={'pnpm logo'} src={pnpm} />
|
||||
<ListItemText primary={<CopyToClipBoard text={`pnpm install ${packageName}`} />} secondary={'Install using PNPM'} />
|
||||
</InstallItem>
|
||||
</>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
export default Install;
|
@ -1,28 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import styled from 'react-emotion';
|
||||
import Typography from '@material-ui/core/Typography/index';
|
||||
import ListItem from '@material-ui/core/ListItem/index';
|
||||
import Avatar from '@material-ui/core/Avatar/index';
|
||||
|
||||
export const Heading = styled(Typography)`
|
||||
&& {
|
||||
font-weight: 700;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
`;
|
||||
|
||||
export const InstallItem = styled(ListItem)`
|
||||
&& {
|
||||
padding: 0;
|
||||
}
|
||||
`;
|
||||
|
||||
export const PackageMangerAvatar = styled(Avatar)`
|
||||
&& {
|
||||
border-radius: 0px;
|
||||
}
|
||||
`;
|
@ -1,27 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import styled from 'react-emotion';
|
||||
import { fontWeight } from '../../utils/styles/sizes';
|
||||
|
||||
import type { Node } from 'react';
|
||||
import { IProps } from './types';
|
||||
|
||||
const Wrapper = styled.div`
|
||||
font-weight: ${({ weight }) => fontWeight[weight]};
|
||||
text-transform: ${({ capitalize }) => (capitalize ? 'capitalize' : 'none')};
|
||||
${({ modifiers }: IProps) => modifiers && modifiers};
|
||||
`;
|
||||
|
||||
const Label = ({ text = '', capitalize = false, weight = 'regular', ...props }: IProps): Node => {
|
||||
return (
|
||||
<Wrapper capitalize={capitalize} weight={weight} {...props}>
|
||||
{text}
|
||||
</Wrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default Label;
|
@ -1,12 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
* @flow
|
||||
*/
|
||||
import type { Styles } from '../../../../types';
|
||||
|
||||
export interface IProps {
|
||||
text: string;
|
||||
capitalize?: boolean;
|
||||
weight?: string;
|
||||
modifiers?: Styles;
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import styled, { css } from 'react-emotion';
|
||||
|
||||
export const Content = styled.div`
|
||||
&& {
|
||||
background-color: #ffffff;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
position: relative;
|
||||
flex-direction: column;
|
||||
}
|
||||
`;
|
||||
|
||||
export const Container = styled.div`
|
||||
&& {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
overflow: hidden;
|
||||
${({ isLoading }) =>
|
||||
isLoading &&
|
||||
css`
|
||||
${Content} {
|
||||
background-color: #f5f6f8;
|
||||
}
|
||||
`}
|
||||
`;
|
@ -1,16 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import type { Node } from 'react';
|
||||
import { IProps } from './types';
|
||||
|
||||
const Link = ({ children, to = '#', blank = false, ...props }: IProps): Node => (
|
||||
<a href={to} target={blank ? '_blank' : '_self'} {...props}>
|
||||
{children}
|
||||
</a>
|
||||
);
|
||||
|
||||
export default Link;
|
@ -1,12 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import type { Node } from 'react';
|
||||
|
||||
export interface IProps {
|
||||
children?: Node;
|
||||
to?: string;
|
||||
blank?: boolean;
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import type { Node } from 'react';
|
||||
|
||||
import Logo from '../Logo';
|
||||
import Spinner from '../Spinner';
|
||||
|
||||
import { Wrapper, Badge } from './styles';
|
||||
|
||||
const Loading = (): Node => (
|
||||
<Wrapper>
|
||||
<Badge>
|
||||
<Logo md={true} />
|
||||
</Badge>
|
||||
<Spinner />
|
||||
</Wrapper>
|
||||
);
|
||||
|
||||
export default Loading;
|
@ -1,24 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import styled from 'react-emotion';
|
||||
|
||||
export const Wrapper = styled.div`
|
||||
&& {
|
||||
transform: translate(-50%, -50%);
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
position: absolute;
|
||||
}
|
||||
`;
|
||||
|
||||
export const Badge = styled.div`
|
||||
&& {
|
||||
margin: 0 0 30px 0;
|
||||
border-radius: 25px;
|
||||
box-shadow: 0 10px 20px 0 rgba(69, 58, 100, 0.2);
|
||||
background: #f7f8f6;
|
||||
}
|
||||
`;
|
@ -1,240 +0,0 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Button from '@material-ui/core/Button';
|
||||
import DialogTitle from '@material-ui/core/DialogTitle';
|
||||
import Dialog from '@material-ui/core/Dialog';
|
||||
import DialogActions from '@material-ui/core/DialogActions';
|
||||
import DialogContent from '@material-ui/core/DialogContent';
|
||||
import SnackbarContent from '@material-ui/core/SnackbarContent';
|
||||
import ErrorIcon from '@material-ui/icons/Error';
|
||||
import InputLabel from '@material-ui/core/InputLabel';
|
||||
import Input from '@material-ui/core/Input';
|
||||
import FormControl from '@material-ui/core/FormControl';
|
||||
import FormHelperText from '@material-ui/core/FormHelperText';
|
||||
|
||||
import classes from "./login.scss";
|
||||
|
||||
export default class LoginModal extends Component {
|
||||
static propTypes = {
|
||||
visibility: PropTypes.bool,
|
||||
error: PropTypes.object,
|
||||
onCancel: PropTypes.func,
|
||||
onSubmit: PropTypes.func,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
error: {},
|
||||
onCancel: () => {},
|
||||
onSubmit: () => {},
|
||||
visibility: true,
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
form: {
|
||||
username: {
|
||||
required: true,
|
||||
pristine: true,
|
||||
helperText: 'Field required',
|
||||
value: '',
|
||||
},
|
||||
password: {
|
||||
required: true,
|
||||
pristine: true,
|
||||
helperText: 'Field required',
|
||||
value: '',
|
||||
},
|
||||
},
|
||||
error: props.error,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* set login modal's username and password to current state
|
||||
* Required to login
|
||||
*/
|
||||
setCredentials = (name, e) => {
|
||||
const { form } = this.state;
|
||||
this.setState({
|
||||
form: {
|
||||
...form,
|
||||
[name]: {
|
||||
...form[name],
|
||||
value: e.target.value,
|
||||
pristine: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
setUsername = (event) => {
|
||||
this.setCredentials('username', event);
|
||||
}
|
||||
|
||||
setPassword = (event) => {
|
||||
this.setCredentials('password', event);
|
||||
}
|
||||
|
||||
validateCredentials = (event) => {
|
||||
const { form } = this.state;
|
||||
// prevents default submit behavior
|
||||
event.preventDefault();
|
||||
|
||||
this.setState({
|
||||
form: Object.keys(form).reduce((acc, key) => ({
|
||||
...acc,
|
||||
...{ [key]: {...form[key], pristine: false } },
|
||||
}), {}),
|
||||
}, () => {
|
||||
if (!Object.keys(form).some(id => !form[id])) {
|
||||
this.submitCredentials();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
submitCredentials = async () => {
|
||||
const { form } = this.state;
|
||||
const { onSubmit } = this.props;
|
||||
await onSubmit(form.username.value, form.password.value);
|
||||
// let's wait for API response and then set
|
||||
// username and password filed to empty state
|
||||
this.setState({
|
||||
form: Object.keys(form).reduce((acc, key) => ({
|
||||
...acc,
|
||||
...{ [key]: {...form[key], value: "", pristine: true } },
|
||||
}), {}),
|
||||
});
|
||||
}
|
||||
|
||||
renderErrorMessage(title, description) {
|
||||
return (
|
||||
<span>
|
||||
<div>
|
||||
<strong>
|
||||
{title}
|
||||
</strong>
|
||||
</div>
|
||||
<div>
|
||||
{description}
|
||||
</div>
|
||||
</span>);
|
||||
}
|
||||
|
||||
renderMessage(title, description) {
|
||||
return (
|
||||
<div
|
||||
className={classes.loginErrorMsg}
|
||||
id={"client-snackbar"}>
|
||||
<ErrorIcon className={classes.loginIcon} />
|
||||
{this.renderErrorMessage(title, description)}
|
||||
</div>);
|
||||
}
|
||||
|
||||
renderLoginError({ type, title, description } = {}) {
|
||||
return type === 'error' && (
|
||||
<SnackbarContent
|
||||
className={classes.loginError}
|
||||
message={this.renderMessage(title, description)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
renderNameField = () => {
|
||||
const { form: { username } } = this.state;
|
||||
return (
|
||||
<FormControl
|
||||
error={!username.value && !username.pristine}
|
||||
fullWidth={true}
|
||||
required={username.required}
|
||||
>
|
||||
<InputLabel htmlFor={"username"}>{'Username'}</InputLabel>
|
||||
<Input
|
||||
id={"login--form-username"}
|
||||
onChange={this.setUsername}
|
||||
placeholder={"Your username"}
|
||||
value={username.value}
|
||||
/>
|
||||
{!username.value && !username.pristine && (
|
||||
<FormHelperText id={"username-error"}>
|
||||
{username.helperText}
|
||||
</FormHelperText>
|
||||
)}
|
||||
</FormControl>
|
||||
);
|
||||
}
|
||||
|
||||
renderPasswordField = () => {
|
||||
const { form: { password } } = this.state;
|
||||
return (
|
||||
<FormControl
|
||||
error={!password.value && !password.pristine}
|
||||
fullWidth={true}
|
||||
required={password.required}
|
||||
style={{ marginTop: '8px' }}
|
||||
>
|
||||
<InputLabel htmlFor={"password"}>{'Password'}</InputLabel>
|
||||
<Input
|
||||
id={"login--form-password"}
|
||||
onChange={this.setPassword}
|
||||
placeholder={"Your strong password"}
|
||||
type={"password"}
|
||||
value={password.value}
|
||||
/>
|
||||
{!password.value && !password.pristine && (
|
||||
<FormHelperText id={"password-error"}>
|
||||
{password.helperText}
|
||||
</FormHelperText>
|
||||
)}
|
||||
</FormControl>
|
||||
);
|
||||
}
|
||||
|
||||
renderActions = () => {
|
||||
const { form: { username, password } } = this.state;
|
||||
const { onCancel } = this.props;
|
||||
return (
|
||||
<DialogActions className={"dialog-footer"}>
|
||||
<Button
|
||||
color={"inherit"}
|
||||
id={"login--form-cancel"}
|
||||
onClick={onCancel}
|
||||
type={"button"}
|
||||
>
|
||||
{'Cancel'}
|
||||
</Button>
|
||||
<Button
|
||||
color={"inherit"}
|
||||
disabled={!password.value || !username.value}
|
||||
id={"login--form-submit"}
|
||||
type={"submit"}
|
||||
>
|
||||
{'Login'}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { visibility, onCancel, error } = this.props;
|
||||
return (
|
||||
<Dialog
|
||||
fullWidth={true}
|
||||
id={"login--form-container"}
|
||||
maxWidth={"xs"}
|
||||
onClose={onCancel}
|
||||
open={visibility}
|
||||
>
|
||||
<form noValidate={true} onSubmit={this.validateCredentials}>
|
||||
<DialogTitle>{'Login'}</DialogTitle>
|
||||
<DialogContent>
|
||||
{this.renderLoginError(error)}
|
||||
{this.renderNameField()}
|
||||
{this.renderPasswordField()}
|
||||
</DialogContent>
|
||||
{this.renderActions()}
|
||||
</form>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
@import '../../styles/variables';
|
||||
|
||||
.loginDialog {
|
||||
min-width: 300px;
|
||||
}
|
||||
|
||||
.loginError {
|
||||
background-color: $red !important;
|
||||
min-width: inherit !important;
|
||||
margin-bottom: 10px !important;
|
||||
}
|
||||
|
||||
.loginErrorMsg {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.loginIcon {
|
||||
opacity: 0.9;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
@ -1 +0,0 @@
|
||||
<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" id="verdaccio"><Title>Verdaccio</Title><defs><path id="b" d="M48 17.6L32.8 48H24L.4.8h15.2l12.8 25.6 4.4-8.8H48z"/><filter x="-20%" y="-11.7%" width="139.9%" height="140.3%" filterUnits="objectBoundingBox" id="a"><feOffset dy="4" in="SourceAlpha" result="shadowOffsetOuter1"/><feGaussianBlur stdDeviation="2.5" in="shadowOffsetOuter1" result="shadowBlurOuter1"/><feComposite in="shadowBlurOuter1" in2="SourceAlpha" operator="out" result="shadowBlurOuter1"/><feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.0906646286 0" in="shadowBlurOuter1"/></filter><path id="d" d="M50.8 12H35.6L41.2.8h15.2L50.8 12z"/><filter x="-45.7%" y="-49.1%" width="191.3%" height="269.6%" filterUnits="objectBoundingBox" id="c"><feOffset dy="4" in="SourceAlpha" result="shadowOffsetOuter1"/><feGaussianBlur stdDeviation="2.5" in="shadowOffsetOuter1" result="shadowBlurOuter1"/><feComposite in="shadowBlurOuter1" in2="SourceAlpha" operator="out" result="shadowBlurOuter1"/><feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.0906646286 0" in="shadowBlurOuter1"/></filter></defs><g fill="none" fill-rule="evenodd"><rect fill="#F7F8F6" width="100" height="100" rx="37"/><g transform="translate(22 29)"><use fill="#000" filter="url(#a)" xlink:href="#b"/><path stroke="#405236" stroke-width="2.4" d="M46.058 18.8H33.542L28.4 29.083 14.858 2H2.342l22.4 44.8h7.316l14-28z" stroke-linejoin="square" fill="#405236"/></g><g transform="translate(22 29)"><use fill="#000" filter="url(#c)" xlink:href="#d"/><path stroke="#CD4000" stroke-width="2.4" d="M50.058 10.8l4.4-8.8H41.942l-4.4 8.8h12.516z" stroke-linejoin="square" fill="#CD4000"/></g><path d="M54.06 75.8l2.575-5.112L36.857 31H24.342l22.4 44.8h7.319z" stroke="#405236" stroke-width="2.4" fill="#4A5E3F"/><path d="M59.6 31h15.221M55.6 35h15.221M51.6 39.8h15.221" stroke="#CD4000" stroke-width="2.4" stroke-linecap="square"/></g></svg>
|
Before Width: | Height: | Size: 1.9 KiB |
@ -1,29 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import styled, { css } from 'react-emotion';
|
||||
import logo from './img/logo.svg';
|
||||
|
||||
const Logo = styled.div`
|
||||
&& {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
box-sizing: border-box;
|
||||
background-position: center;
|
||||
background-size: contain;
|
||||
background-image: url(${logo});
|
||||
background-repeat: no-repeat;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
${props =>
|
||||
props.md &&
|
||||
css`
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
`};
|
||||
}
|
||||
`;
|
||||
|
||||
export default Logo;
|
@ -1,17 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import Typography from '@material-ui/core/Typography/index';
|
||||
|
||||
import { IProps } from './types';
|
||||
|
||||
const NoItems = ({ text }: IProps) => (
|
||||
<Typography gutterBottom={true} variant={'subtitle1'}>
|
||||
{text}
|
||||
</Typography>
|
||||
);
|
||||
|
||||
export default NoItems;
|
@ -1,8 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
* @flow
|
||||
*/
|
||||
|
||||
export interface IProps {
|
||||
text: string;
|
||||
}
|
@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 495.2 495.2"><path fill="#d38c0d" d="M325.6 224.4L495.2 126 325.6 28.4l-168.8 99.2z"/><g fill="#efbb67"><path d="M170.4 224.4l168-97.6-168-98.4L0 126.8z"/><path d="M416 368.4l-168 98.4-168-98.4v-196L248 74l168 98.4z"/></g><path fill="#d38c0d" d="M248 74l168 98.4v196l-168 98.4"/><path fill="#efbb67" d="M326.4 314.8L495.2 218l-169.6-98.4L156 218z"/><path fill="#d38c0d" d="M170.4 316.4l168.8-99.2-168.8-97.6L0 218z"/><path fill="#704a0e" d="M248.8 270.8L416 172.4 248.8 74 78.4 172.4z"/><path fill="#513307" d="M248.8 270.8L416 172.4 248.8 74"/><path fill="#2d1c05" d="M248.8 270.8l36-21.6-36-20.8-36 20.8z"/><g fill="#0dd396"><path d="M368 379.6l40-23.2v-12.8l-40 23.2zM368 356.4l40-23.2v-12.8l-40 23.2zM368 333.2l40-23.2v-13.6l-40 24z"/></g></svg>
|
Before Width: | Height: | Size: 802 B |
@ -1,57 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
import withWidth, { isWidthUp } from '@material-ui/core/withWidth/index';
|
||||
import ListItem from '@material-ui/core/ListItem/index';
|
||||
import Typography from '@material-ui/core/Typography/index';
|
||||
import { Wrapper, Inner, EmptyPackage, Heading, Card, List } from './styles';
|
||||
import PackageImg from './img/package.svg';
|
||||
|
||||
export const NOT_FOUND_TEXT = "Sorry, we couldn't find it...";
|
||||
|
||||
// eslint-disable-next-line react/prop-types
|
||||
const NotFound = ({ history, width }) => {
|
||||
const handleGoTo = to => () => {
|
||||
history.push(to);
|
||||
};
|
||||
|
||||
const handleGoBack = () => () => {
|
||||
history.goBack();
|
||||
};
|
||||
|
||||
const renderList = () => (
|
||||
<List>
|
||||
<ListItem button={true} divider={true} onClick={handleGoTo('/')}>
|
||||
{'Home'}
|
||||
</ListItem>
|
||||
<ListItem button={true} divider={true} onClick={handleGoBack()}>
|
||||
{'Back'}
|
||||
</ListItem>
|
||||
</List>
|
||||
);
|
||||
|
||||
const renderSubTitle = () => (
|
||||
<Typography variant={'subtitle1'}>
|
||||
<div>{"The page you're looking for doesn't exist."}</div>
|
||||
<div>{'Perhaps these links will help find what you are looking for:'}</div>
|
||||
</Typography>
|
||||
);
|
||||
|
||||
return (
|
||||
<Wrapper>
|
||||
<Inner>
|
||||
<EmptyPackage alt={'404 - Page not found'} src={PackageImg} />
|
||||
<Heading className={'not-found-text'} variant={isWidthUp('sm', width) ? 'h2' : 'h4'}>
|
||||
{NOT_FOUND_TEXT}
|
||||
</Heading>
|
||||
{renderSubTitle()}
|
||||
<Card>{renderList()}</Card>
|
||||
</Inner>
|
||||
</Wrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default withRouter(withWidth()(NotFound));
|
@ -1,46 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import styled from 'react-emotion';
|
||||
import Typography from '@material-ui/core/Typography/index';
|
||||
import { default as MuiList } from '@material-ui/core/List/index';
|
||||
import { default as MuiCard } from '@material-ui/core/Card/index';
|
||||
|
||||
export const Wrapper = styled('div')`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
flex: 1;
|
||||
padding: 16px;
|
||||
`;
|
||||
|
||||
export const Inner = styled('div')`
|
||||
max-width: 650px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
`;
|
||||
|
||||
export const EmptyPackage = styled('img')`
|
||||
width: 150px;
|
||||
margin: 0 auto;
|
||||
`;
|
||||
|
||||
export const Heading = styled(Typography)`
|
||||
&& {
|
||||
color: #4b5e40;
|
||||
}
|
||||
`;
|
||||
|
||||
export const List = styled(MuiList)`
|
||||
&& {
|
||||
padding: 0;
|
||||
color: #4b5e40;
|
||||
}
|
||||
`;
|
||||
|
||||
export const Card = styled(MuiCard)`
|
||||
margin-top: 24px;
|
||||
`;
|
@ -1,161 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
* @flow
|
||||
*/
|
||||
import React from 'react';
|
||||
import type { Element } from 'react';
|
||||
|
||||
import BugReport from '@material-ui/icons/BugReport';
|
||||
import Grid from '@material-ui/core/Grid/index';
|
||||
import HomeIcon from '@material-ui/icons/Home';
|
||||
import ListItem from '@material-ui/core/ListItem/index';
|
||||
import Tooltip from '@material-ui/core/Tooltip/index';
|
||||
|
||||
import Tag from '../Tag';
|
||||
import fileSizeSI from '../../utils/file-size';
|
||||
import { formatDate, formatDateDistance } from '../../utils/package';
|
||||
import { IProps } from './types';
|
||||
|
||||
import {
|
||||
Author,
|
||||
Avatar,
|
||||
Description,
|
||||
Details,
|
||||
GridRightAligned,
|
||||
Icon,
|
||||
IconButton,
|
||||
OverviewItem,
|
||||
PackageList,
|
||||
PackageListItem,
|
||||
PackageListItemText,
|
||||
PackageTitle,
|
||||
Published,
|
||||
TagContainer,
|
||||
Text,
|
||||
WrapperLink,
|
||||
} from './styles';
|
||||
|
||||
const Package = ({
|
||||
author: { name: authorName, avatar: authorAvatar },
|
||||
bugs: { url } = {},
|
||||
description,
|
||||
dist: { unpackedSize } = {},
|
||||
homepage,
|
||||
keywords = [],
|
||||
license,
|
||||
name: packageName,
|
||||
time,
|
||||
version,
|
||||
}: IProps): Element<WrapperLink> => {
|
||||
//
|
||||
const renderVersionInfo = () =>
|
||||
version && (
|
||||
<OverviewItem>
|
||||
<Icon name={'version'} />
|
||||
{`v${version}`}
|
||||
</OverviewItem>
|
||||
);
|
||||
|
||||
const renderAuthorInfo = () =>
|
||||
authorName && (
|
||||
<Author>
|
||||
<Avatar alt={authorName} src={authorAvatar} />
|
||||
<Details>
|
||||
<Text text={authorName} />
|
||||
</Details>
|
||||
</Author>
|
||||
);
|
||||
|
||||
const renderFileSize = () =>
|
||||
unpackedSize && (
|
||||
<OverviewItem>
|
||||
<Icon name={'filebinary'} />
|
||||
{fileSizeSI(unpackedSize)}
|
||||
</OverviewItem>
|
||||
);
|
||||
|
||||
const renderLicenseInfo = () =>
|
||||
license && (
|
||||
<OverviewItem>
|
||||
<Icon name={'law'} />
|
||||
{license}
|
||||
</OverviewItem>
|
||||
);
|
||||
|
||||
const renderPublishedInfo = () =>
|
||||
time && (
|
||||
<OverviewItem>
|
||||
<Icon name={'time'} />
|
||||
<Published>{`Published on ${formatDate(time)} •`}</Published>
|
||||
{`${formatDateDistance(time)} ago`}
|
||||
</OverviewItem>
|
||||
);
|
||||
|
||||
const renderHomePageLink = () =>
|
||||
homepage && (
|
||||
<a href={homepage} target={'_blank'}>
|
||||
<Tooltip aria-label={'Homepage'} title={'Visit homepage'}>
|
||||
<IconButton aria-label={'Homepage'}>
|
||||
{/* eslint-disable-next-line react/jsx-max-depth */}
|
||||
<HomeIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</a>
|
||||
);
|
||||
|
||||
const renderBugsLink = () =>
|
||||
url && (
|
||||
<a href={url} target={'_blank'}>
|
||||
<Tooltip aria-label={'Bugs'} title={'Open an issue'}>
|
||||
<IconButton aria-label={'Bugs'}>
|
||||
{/* eslint-disable-next-line react/jsx-max-depth */}
|
||||
<BugReport />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</a>
|
||||
);
|
||||
|
||||
const renderPrimaryComponent = () => {
|
||||
return (
|
||||
<Grid container={true} item={true} xs={12}>
|
||||
<Grid item={true} xs={true}>
|
||||
<WrapperLink to={`/-/web/detail/${packageName}`}>
|
||||
{/* eslint-disable-next-line react/jsx-max-depth */}
|
||||
<PackageTitle>{packageName}</PackageTitle>
|
||||
</WrapperLink>
|
||||
</Grid>
|
||||
<GridRightAligned item={true} xs={true}>
|
||||
{renderHomePageLink()}
|
||||
{renderBugsLink()}
|
||||
</GridRightAligned>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
||||
const renderSecondaryComponent = () => {
|
||||
const tags = keywords.sort().map((keyword, index) => <Tag key={index}>{keyword}</Tag>);
|
||||
return (
|
||||
<>
|
||||
<Description component={'span'}>{description}</Description>
|
||||
{tags.length > 0 && <TagContainer>{tags}</TagContainer>}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<PackageList className={'package'}>
|
||||
<ListItem alignItems={'flex-start'}>
|
||||
<PackageListItemText className={'package-link'} component={'div'} primary={renderPrimaryComponent()} secondary={renderSecondaryComponent()} />
|
||||
</ListItem>
|
||||
<PackageListItem alignItems={'flex-start'}>
|
||||
{renderAuthorInfo()}
|
||||
{renderVersionInfo()}
|
||||
{renderPublishedInfo()}
|
||||
{renderFileSize()}
|
||||
{renderLicenseInfo()}
|
||||
</PackageListItem>
|
||||
</PackageList>
|
||||
);
|
||||
};
|
||||
|
||||
export default Package;
|
@ -1,166 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import styled from 'react-emotion';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import Grid from '@material-ui/core/Grid/index';
|
||||
import List from '@material-ui/core/List/index';
|
||||
import ListItem from '@material-ui/core/ListItem/index';
|
||||
import ListItemText from '@material-ui/core/ListItemText/index';
|
||||
import MuiIconButton from '@material-ui/core/IconButton/index';
|
||||
import Photo from '@material-ui/core/Avatar';
|
||||
import Typography from '@material-ui/core/Typography/index';
|
||||
|
||||
import { breakpoints } from '../../utils/styles/media';
|
||||
import Ico from '../Icon';
|
||||
import Label from '../Label';
|
||||
import colors from '../../utils/styles/colors';
|
||||
|
||||
export const OverviewItem = styled.span`
|
||||
&& {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 0 0 0 16px;
|
||||
color: ${colors.greyLight2};
|
||||
font-size: 12px;
|
||||
@media (max-width: ${breakpoints.medium}px) {
|
||||
&:nth-child(3) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
@media (max-width: ${breakpoints.small}px) {
|
||||
&:nth-child(4) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const Icon = styled(Ico)`
|
||||
&& {
|
||||
margin: 2px 10px 0px 0;
|
||||
fill: ${colors.greyLight2};
|
||||
}
|
||||
`;
|
||||
|
||||
export const Published = styled.span`
|
||||
&& {
|
||||
color: ${colors.greyLight2};
|
||||
margin: 0px 5px 0px 0px;
|
||||
}
|
||||
`;
|
||||
|
||||
export const Text = styled(Label)`
|
||||
&& {
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
color: ${colors.greyLight2};
|
||||
}
|
||||
`;
|
||||
|
||||
export const Details = styled.span`
|
||||
&& {
|
||||
margin-left: 5px;
|
||||
line-height: 1.5;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
`;
|
||||
|
||||
export const Author = styled.div`
|
||||
&& {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
`;
|
||||
|
||||
export const Avatar = styled(Photo)`
|
||||
&& {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
`;
|
||||
|
||||
export const WrapperLink = styled(Link)`
|
||||
&& {
|
||||
text-decoration: none;
|
||||
}
|
||||
`;
|
||||
|
||||
export const PackageTitle = styled.span`
|
||||
&& {
|
||||
font-weight: 600;
|
||||
font-size: 20px;
|
||||
display: block;
|
||||
margin-bottom: 12px;
|
||||
color: ${colors.eclipse};
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
color: ${colors.black};
|
||||
}
|
||||
|
||||
@media (max-width: ${breakpoints.small}px) {
|
||||
font-size: 14px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const GridRightAligned = styled(Grid)`
|
||||
&& {
|
||||
text-align: right;
|
||||
}
|
||||
`;
|
||||
|
||||
export const PackageList = styled(List)`
|
||||
&& {
|
||||
padding: 12px 0 12px 0;
|
||||
|
||||
&:hover {
|
||||
background-color: ${colors.greyLight3};
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const IconButton = styled(MuiIconButton)`
|
||||
&& {
|
||||
padding: 6px;
|
||||
|
||||
svg {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const TagContainer = styled.span`
|
||||
&& {
|
||||
margin-top: 8px;
|
||||
margin-bottom: 12px;
|
||||
display: block;
|
||||
@media (max-width: ${breakpoints.medium}px) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const PackageListItem = styled(ListItem)`
|
||||
&& {
|
||||
padding-top: 0;
|
||||
}
|
||||
`;
|
||||
|
||||
export const PackageListItemText = styled(ListItemText)`
|
||||
&& {
|
||||
padding-right: 0;
|
||||
}
|
||||
`;
|
||||
|
||||
export const Description = styled(Typography)`
|
||||
color: ${colors.greyDark2};
|
||||
font-size: 14px;
|
||||
padding-right: 0;
|
||||
`;
|
@ -1,30 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
* @flow
|
||||
*/
|
||||
|
||||
export interface IProps {
|
||||
name: string;
|
||||
version: string;
|
||||
time: string;
|
||||
author: IAuthor;
|
||||
description?: string;
|
||||
keywords?: string[];
|
||||
license?: string;
|
||||
homepage: string;
|
||||
bugs: IBugs;
|
||||
dist: IDist;
|
||||
}
|
||||
|
||||
export interface IAuthor {
|
||||
name: string;
|
||||
avatar: string;
|
||||
email: string;
|
||||
}
|
||||
|
||||
export interface IBugs {
|
||||
url: string;
|
||||
}
|
||||
export interface IDist {
|
||||
unpackedSize: number;
|
||||
}
|
@ -1,57 +0,0 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import Divider from '@material-ui/core/Divider';
|
||||
|
||||
import Package from '../Package';
|
||||
import Help from '../Help';
|
||||
import { formatLicense } from '../../utils/package';
|
||||
|
||||
import classes from './packageList.scss';
|
||||
|
||||
export default class PackageList extends React.Component {
|
||||
static propTypes = {
|
||||
packages: PropTypes.array,
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className={"package-list-items"}>
|
||||
<div className={classes.pkgContainer}>
|
||||
{this.hasPackages() ? this.renderPackages(): <Help /> }
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
hasPackages() {
|
||||
const {packages} = this.props;
|
||||
return packages.length > 0;
|
||||
}
|
||||
|
||||
renderPackages = () => {
|
||||
return (
|
||||
<Fragment>
|
||||
{this.renderList()}
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
renderList = () => {
|
||||
const { packages } = this.props;
|
||||
return (
|
||||
packages.map((pkg, i) => {
|
||||
const { name, version, description, time, keywords, dist, homepage, bugs } = pkg;
|
||||
const author = pkg.author;
|
||||
// TODO: move format license to API side.
|
||||
const license = formatLicense(pkg.license);
|
||||
return (
|
||||
<React.Fragment key={i}>
|
||||
{i !== 0 && <Divider></Divider>}
|
||||
<Package {...{ name, dist, version, author, description, license, time, keywords, homepage, bugs }} />
|
||||
</React.Fragment>
|
||||
);
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
@import '../../styles/mixins';
|
||||
|
||||
.pkgContainer {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
.listTitle {
|
||||
font-weight: $font-weight-regular;
|
||||
font-size: $font-size-xl;
|
||||
margin: 0 0 10px 0;
|
||||
}
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import 'github-markdown-css';
|
||||
|
||||
import { IProps } from './types';
|
||||
|
||||
const Readme = ({ description }: IProps) => <div className={'markdown-body'} dangerouslySetInnerHTML={{ __html: description }} />;
|
||||
|
||||
export default Readme;
|
@ -1,8 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
* @flow
|
||||
*/
|
||||
|
||||
export interface IProps {
|
||||
description: string;
|
||||
}
|
@ -1,90 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import type { IProps, IState } from './types';
|
||||
import { CommandContainer } from './styles';
|
||||
import CopyToClipBoard from '../CopyToClipBoard';
|
||||
import Tabs from '@material-ui/core/Tabs/index';
|
||||
import Tab from '@material-ui/core/Tab/index';
|
||||
import Typography from '@material-ui/core/Typography/index';
|
||||
|
||||
import { getCLISetRegistry, getCLIChangePassword, getCLISetConfigRegistry } from '../../utils/cli-utils';
|
||||
import { NODE_MANAGER } from '../../utils/constants';
|
||||
|
||||
/* eslint react/prop-types:0 */
|
||||
function TabContainer({ children }) {
|
||||
return (
|
||||
<CommandContainer>
|
||||
<Typography component={'div'} style={{ padding: 0, minHeight: 170 }}>
|
||||
{children}
|
||||
</Typography>
|
||||
</CommandContainer>
|
||||
);
|
||||
}
|
||||
|
||||
class RegistryInfoContent extends Component<IProps, IState> {
|
||||
state = {
|
||||
tabPosition: 0,
|
||||
};
|
||||
|
||||
render() {
|
||||
return <div>{this.renderTabs()}</div>;
|
||||
}
|
||||
|
||||
renderTabs() {
|
||||
const { scope, registryUrl } = this.props;
|
||||
const { tabPosition } = this.state;
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Tabs indicatorColor={'primary'} onChange={this.handleChange} textColor={'primary'} value={tabPosition} variant={'fullWidth'}>
|
||||
<Tab label={NODE_MANAGER.npm} />
|
||||
<Tab label={NODE_MANAGER.pnpm} />
|
||||
<Tab label={NODE_MANAGER.yarn} />
|
||||
</Tabs>
|
||||
{tabPosition === 0 && <TabContainer>{this.renderNpmTab(scope, registryUrl)}</TabContainer>}
|
||||
{tabPosition === 1 && <TabContainer>{this.renderPNpmTab(scope, registryUrl)}</TabContainer>}
|
||||
{tabPosition === 2 && <TabContainer>{this.renderYarnTab(scope, registryUrl)}</TabContainer>}
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
renderNpmTab(scope: string, registryUrl: string) {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<CopyToClipBoard text={getCLISetConfigRegistry(`${NODE_MANAGER.npm} set`, scope, registryUrl)} />
|
||||
<CopyToClipBoard text={getCLISetRegistry(`${NODE_MANAGER.npm} adduser`, registryUrl)} />
|
||||
<CopyToClipBoard text={getCLIChangePassword(NODE_MANAGER.npm, registryUrl)} />
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
renderPNpmTab(scope: string, registryUrl: string) {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<CopyToClipBoard text={getCLISetConfigRegistry(`${NODE_MANAGER.pnpm} set`, scope, registryUrl)} />
|
||||
<CopyToClipBoard text={getCLISetRegistry(`${NODE_MANAGER.pnpm} adduser`, registryUrl)} />
|
||||
<CopyToClipBoard text={getCLIChangePassword(NODE_MANAGER.pnpm, registryUrl)} />
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
renderYarnTab(scope: string, registryUrl: string) {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<CopyToClipBoard text={getCLISetConfigRegistry(`${NODE_MANAGER.yarn} config set`, scope, registryUrl)} />
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
handleChange = (event: any, tabPosition: number) => {
|
||||
event.preventDefault();
|
||||
this.setState({ tabPosition });
|
||||
};
|
||||
}
|
||||
|
||||
export default RegistryInfoContent;
|
@ -1,7 +0,0 @@
|
||||
import styled from 'react-emotion';
|
||||
|
||||
export const CommandContainer = styled.div`
|
||||
&& {
|
||||
padding-top: 20px;
|
||||
}
|
||||
`;
|
@ -1,13 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
* @flow
|
||||
*/
|
||||
|
||||
export interface IProps {
|
||||
scope: string;
|
||||
registryUrl: string;
|
||||
}
|
||||
|
||||
export interface IState {
|
||||
tabPosition: number;
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import Dialog from '@material-ui/core/Dialog/index';
|
||||
import DialogActions from '@material-ui/core/DialogActions/index';
|
||||
import Button from '@material-ui/core/Button/index';
|
||||
import { Title, Content } from './styles';
|
||||
|
||||
import type { Node } from 'react';
|
||||
|
||||
import { IProps } from './types';
|
||||
|
||||
const RegistryInfoDialog = ({ open = false, children, onClose }: IProps): Node => (
|
||||
<Dialog id={'registryInfo--dialog-container'} onClose={onClose} open={open}>
|
||||
<Title disableTypography={true}>{'Register Info'}</Title>
|
||||
<Content>{children}</Content>
|
||||
<DialogActions>
|
||||
<Button color={'inherit'} id={'registryInfo--dialog-close'} onClick={onClose}>
|
||||
{'CLOSE'}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
|
||||
export default RegistryInfoDialog;
|
@ -1,24 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import styled from 'react-emotion';
|
||||
import DialogTitle from '@material-ui/core/DialogTitle/index';
|
||||
import DialogContent from '@material-ui/core/DialogContent/index';
|
||||
import colors from '../../utils/styles/colors';
|
||||
import { fontSize } from '../../utils/styles/sizes';
|
||||
|
||||
export const Title = styled(DialogTitle)`
|
||||
&& {
|
||||
background-color: ${colors.primary};
|
||||
color: ${colors.white};
|
||||
font-size: ${fontSize.lg};
|
||||
}
|
||||
`;
|
||||
|
||||
export const Content = styled(DialogContent)`
|
||||
&& {
|
||||
padding: 0 24px;
|
||||
}
|
||||
`;
|
@ -1,12 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import type { Node } from 'react';
|
||||
|
||||
export interface IProps {
|
||||
children: Node;
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
}
|
Before Width: | Height: | Size: 2.3 KiB |
@ -1,62 +0,0 @@
|
||||
/* eslint react/jsx-max-depth: 0 */
|
||||
|
||||
import React, {Component} from 'react';
|
||||
import Avatar from '@material-ui/core/Avatar';
|
||||
import List from '@material-ui/core/List';
|
||||
import ListItemText from '@material-ui/core/ListItemText';
|
||||
|
||||
import { DetailContextConsumer } from '../../pages/version/index';
|
||||
import CopyToClipBoard from '../CopyToClipBoard';
|
||||
|
||||
import { Heading, GithubLink, RepositoryListItem } from './styles';
|
||||
import git from './img/git.png';
|
||||
|
||||
class Repository extends Component<any, any> {
|
||||
render() {
|
||||
return (
|
||||
<DetailContextConsumer>
|
||||
{(context) => {
|
||||
return this.renderRepository(context);
|
||||
}}
|
||||
</DetailContextConsumer>
|
||||
);
|
||||
};
|
||||
|
||||
renderRepositoryText(url) {
|
||||
return (<GithubLink href={url} target={"_blank"}>{url}</GithubLink>);
|
||||
}
|
||||
|
||||
renderRepository = ({packageMeta}) => {
|
||||
const {
|
||||
repository: {
|
||||
url,
|
||||
} = {},
|
||||
} = packageMeta.latest;
|
||||
|
||||
if (!url) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<List dense={true} subheader={<Heading variant={"subheading"}>{'Repository'}</Heading>}>
|
||||
<RepositoryListItem>
|
||||
<Avatar src={git} />
|
||||
<ListItemText primary={this.renderContent(url)} />
|
||||
</RepositoryListItem>
|
||||
</List>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
renderContent(url) {
|
||||
return (
|
||||
<CopyToClipBoard text={url}>
|
||||
{this.renderRepositoryText(url)}
|
||||
</CopyToClipBoard>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export default Repository;
|
@ -1,46 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import styled from 'react-emotion';
|
||||
import Grid from '@material-ui/core/Grid/index';
|
||||
import ListItem from '@material-ui/core/ListItem/index';
|
||||
import Typography from '@material-ui/core/Typography/index';
|
||||
|
||||
import Github from '../../icons/GitHub';
|
||||
import colors from '../../utils/styles/colors';
|
||||
|
||||
export const Heading = styled(Typography)`
|
||||
&& {
|
||||
font-weight: 700;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
`;
|
||||
|
||||
export const GridRepo = styled(Grid)`
|
||||
&& {
|
||||
align-items: center;
|
||||
}
|
||||
`;
|
||||
|
||||
export const GithubLink = styled('a')`
|
||||
&& {
|
||||
color: ${colors.primary};
|
||||
}
|
||||
`;
|
||||
|
||||
export const GithubLogo = styled(Github)`
|
||||
&& {
|
||||
font-size: 40px;
|
||||
color: ${colors.primary};
|
||||
background-color: ${colors.greySuperLight};
|
||||
}
|
||||
`;
|
||||
|
||||
export const RepositoryListItem = styled(ListItem)`
|
||||
&& {
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
`;
|
@ -1,186 +0,0 @@
|
||||
/**
|
||||
* @prettier
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import type { Node } from 'react';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
|
||||
import { default as IconSearch } from '@material-ui/icons/Search';
|
||||
import InputAdornment from '@material-ui/core/InputAdornment';
|
||||
import debounce from 'lodash/debounce';
|
||||
|
||||
import API from '../../utils/api';
|
||||
import AutoComplete from '../AutoComplete';
|
||||
import colors from '../../utils/styles/colors';
|
||||
|
||||
import { IProps, IState } from './types';
|
||||
import type { cancelAllSearchRequests, handlePackagesClearRequested, handleSearch, handleClickSearch, handleFetchPackages, onBlur } from './types';
|
||||
|
||||
const CONSTANTS = {
|
||||
API_DELAY: 300,
|
||||
PLACEHOLDER_TEXT: 'Search Packages',
|
||||
ABORT_ERROR: 'AbortError',
|
||||
};
|
||||
|
||||
export class Search extends Component<IProps, IState> {
|
||||
requestList: Array<any>;
|
||||
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
this.state = {
|
||||
search: '',
|
||||
suggestions: [],
|
||||
// loading: A boolean value to indicate that request is in pending state.
|
||||
loading: false,
|
||||
// loaded: A boolean value to indicate that result has been loaded.
|
||||
loaded: false,
|
||||
// error: A boolean value to indicate API error.
|
||||
error: false,
|
||||
};
|
||||
this.requestList = [];
|
||||
this.handleFetchPackages = debounce(this.handleFetchPackages, CONSTANTS.API_DELAY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel all the requests which are in pending state.
|
||||
*/
|
||||
cancelAllSearchRequests: cancelAllSearchRequests = () => {
|
||||
this.requestList.forEach(request => request.abort());
|
||||
this.requestList = [];
|
||||
};
|
||||
|
||||
/**
|
||||
* Cancel all the request from list and make request list empty.
|
||||
*/
|
||||
handlePackagesClearRequested: handlePackagesClearRequested = () => {
|
||||
this.setState({
|
||||
suggestions: [],
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* onChange method for the input element.
|
||||
*/
|
||||
handleSearch: handleSearch = (event, { newValue, method }) => {
|
||||
// stops event bubbling
|
||||
event.stopPropagation();
|
||||
if (method === 'type') {
|
||||
const value = newValue.trim();
|
||||
this.setState(
|
||||
{
|
||||
search: value,
|
||||
loading: true,
|
||||
loaded: false,
|
||||
error: false,
|
||||
},
|
||||
() => {
|
||||
/**
|
||||
* A use case where User keeps adding and removing value in input field,
|
||||
* so we cancel all the existing requests when input is empty.
|
||||
*/
|
||||
if (value.length === 0) {
|
||||
this.cancelAllSearchRequests();
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* When an user select any package by clicking or pressing return key.
|
||||
*/
|
||||
handleClickSearch: handleClickSearch = (event, { suggestionValue, method }) => {
|
||||
const { history } = this.props;
|
||||
// stops event bubbling
|
||||
event.stopPropagation();
|
||||
switch (method) {
|
||||
case 'click':
|
||||
case 'enter':
|
||||
this.setState({ search: '' });
|
||||
// $FlowFixMe
|
||||
history.push(`/-/web/detail/${suggestionValue}`);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetch packages from API.
|
||||
* For AbortController see: https://developer.mozilla.org/en-US/docs/Web/API/AbortController
|
||||
*/
|
||||
handleFetchPackages: handleFetchPackages = async ({ value }) => {
|
||||
try {
|
||||
const controller = new window.AbortController();
|
||||
const signal = controller.signal;
|
||||
// Keep track of search requests.
|
||||
this.requestList.push(controller);
|
||||
const suggestions = await API.request(`search/${encodeURIComponent(value)}`, 'GET', { signal });
|
||||
this.setState({
|
||||
suggestions,
|
||||
loaded: true,
|
||||
});
|
||||
} catch (error) {
|
||||
/**
|
||||
* AbortError is not the API error.
|
||||
* It means browser has cancelled the API request.
|
||||
*/
|
||||
if (error.name === CONSTANTS.ABORT_ERROR) {
|
||||
this.setState({ error: false, loaded: false });
|
||||
} else {
|
||||
this.setState({ error: true, loaded: false });
|
||||
}
|
||||
} finally {
|
||||
this.setState({ loading: false });
|
||||
}
|
||||
};
|
||||
|
||||
render(): Node {
|
||||
const { suggestions, search, loaded, loading, error } = this.state;
|
||||
|
||||
return (
|
||||
<AutoComplete
|
||||
color={colors.white}
|
||||
onBlur={this.onBlur}
|
||||
onChange={this.handleSearch}
|
||||
onCleanSuggestions={this.handlePackagesClearRequested}
|
||||
onClick={this.handleClickSearch}
|
||||
onSuggestionsFetch={this.handleFetchPackages}
|
||||
placeholder={CONSTANTS.PLACEHOLDER_TEXT}
|
||||
startAdornment={this.renderAdorment()}
|
||||
suggestions={suggestions}
|
||||
suggestionsError={error}
|
||||
suggestionsLoaded={loaded}
|
||||
suggestionsLoading={loading}
|
||||
value={search}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* As user focuses out from input, we cancel all the request from requestList
|
||||
* and set the API state parameters to default boolean values.
|
||||
*/
|
||||
onBlur: onBlur = event => {
|
||||
// stops event bubbling
|
||||
event.stopPropagation();
|
||||
this.setState(
|
||||
{
|
||||
loaded: false,
|
||||
loading: false,
|
||||
error: false,
|
||||
},
|
||||
() => this.cancelAllSearchRequests()
|
||||
);
|
||||
};
|
||||
|
||||
renderAdorment() {
|
||||
return (
|
||||
<InputAdornment position={'start'} style={{ color: colors.white }}>
|
||||
<IconSearch />
|
||||
</InputAdornment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withRouter(Search);
|