106 - migrate mainnet stats to monorepo (#107)

* Initial commit of generated mainnet-stats directory

* Migration partly complete

* Migration complete

* Vega logo now in ui toolkit

* Specified a port that stops conflicts with other e2e tests

* Extra config for swapping to port that stops conflicts with other e2e tests. Also file name case change

* Adding the Cypress project ID to allow recording

* fix test

* Mainnet stats manager moved into library

* Mainnet stats project now importing manager element from lib

* Block Explorer has mainnet stats on home route

* Styling tweak to stop mainnet stats tables trying to match heights

* Spacing alignment tweaks

* Changing header text and tests to align with other branches

* Correct header test for mainnet-stats app

* Correct h3 test for mainnet-stats app

* Fix linting issue

* fix casing issue for CI

* Corrected mistake in merge

Co-authored-by: Dexter <dexter.edwards93@gmail.com>
This commit is contained in:
Sam Keen 2022-03-23 17:40:15 +00:00 committed by GitHub
parent d3f5410747
commit 05d53bbf4c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
79 changed files with 1397 additions and 5 deletions

View File

@ -54,7 +54,7 @@ export default class BasePage {
validateSearchDisplayed() {
cy.getByTestId(this.blockExplorerHeader).should(
'have.text',
'Vega Block Explorer'
'Vega Explorer'
);
cy.getByTestId(this.searchField).should('be.visible');
}

View File

@ -1,7 +1,9 @@
import { StatsManager } from '@vegaprotocol/mainnet-stats-manager';
const Home = () => {
return (
<section>
<h1>Home page content</h1>
<StatsManager className="mt-12 grid grid-cols-1 lg:grid-cols-2 lg:gap-16" />
</section>
);
};

View File

@ -0,0 +1,10 @@
{
"extends": ["plugin:cypress/recommended", "../../.eslintrc.json"],
"ignorePatterns": ["!**/*"],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {}
}
]
}

View File

@ -0,0 +1,14 @@
{
"baseUrl": "http://localhost:3010",
"projectId": "et4snf",
"fileServerFolder": ".",
"fixturesFolder": "./src/fixtures",
"integrationFolder": "./src/integration",
"modifyObstructiveCode": false,
"supportFile": "./src/support/index.ts",
"pluginsFile": false,
"video": true,
"videosFolder": "../../dist/cypress/apps/stats-mainnet-e2e/videos",
"screenshotsFolder": "../../dist/cypress/apps/stats-mainnet-e2e/screenshots",
"chromeWebSecurity": false
}

View File

@ -0,0 +1,28 @@
{
"root": "apps/stats-mainnet-e2e",
"sourceRoot": "apps/stats-mainnet-e2e/src",
"projectType": "application",
"targets": {
"e2e": {
"executor": "@nrwl/cypress:cypress",
"options": {
"cypressConfig": "apps/stats-mainnet-e2e/cypress.json",
"devServerTarget": "stats-mainnet:serve"
},
"configurations": {
"production": {
"devServerTarget": "stats-mainnet:serve:production"
}
}
},
"lint": {
"executor": "@nrwl/linter:eslint",
"outputs": ["{options.outputFile}"],
"options": {
"lintFilePatterns": ["apps/stats-mainnet-e2e/**/*.{js,ts}"]
}
}
},
"tags": [],
"implicitDependencies": ["stats-mainnet"]
}

View File

@ -0,0 +1,4 @@
{
"name": "Using fixtures to represent data",
"email": "hello@cypress.io"
}

View File

@ -0,0 +1,7 @@
describe('stats-mainnet', () => {
beforeEach(() => cy.visit('/'));
it('should display header', () => {
cy.get('h3').should('have.text', '/ Mainnet');
});
});

View File

@ -0,0 +1,33 @@
// ***********************************************
// This example commands.js shows you how to
// create various custom commands and overwrite
// existing commands.
//
// For more comprehensive examples of custom
// commands please read more here:
// https://on.cypress.io/custom-commands
// ***********************************************
// eslint-disable-next-line @typescript-eslint/no-namespace
declare namespace Cypress {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
interface Chainable<Subject> {
login(email: string, password: string): void;
}
}
//
// -- This is a parent command --
Cypress.Commands.add('login', (email, password) => {
console.log('Custom command example: Login', email, password);
});
//
// -- This is a child command --
// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
//
//
// -- This is a dual command --
// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
//
//
// -- This will overwrite an existing command --
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })

View File

@ -0,0 +1,17 @@
// ***********************************************************
// This example support/index.js is processed and
// loaded automatically before your test files.
//
// This is a great place to put global configuration and
// behavior that modifies Cypress.
//
// You can change the location of this file or turn off
// automatically serving support files with the
// 'supportFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/configuration
// ***********************************************************
// Import commands.js using ES2015 syntax:
import './commands';

View File

@ -0,0 +1,10 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"sourceMap": false,
"outDir": "../../dist/out-tsc",
"allowJs": true,
"types": ["cypress", "node"]
},
"include": ["src/**/*.ts", "src/**/*.js"]
}

View File

@ -0,0 +1,11 @@
{
"presets": [
[
"@nrwl/react/babel",
{
"runtime": "automatic"
}
]
],
"plugins": []
}

View File

@ -0,0 +1,16 @@
# This file is used by:
# 1. autoprefixer to adjust CSS to support the below specified browsers
# 2. babel preset-env to adjust included polyfills
#
# For additional information regarding the format and rule options, please see:
# https://github.com/browserslist/browserslist#queries
#
# If you need to support different browsers in production, you may tweak the list below.
last 1 Chrome version
last 1 Firefox version
last 2 Edge major versions
last 2 Safari major version
last 2 iOS major versions
Firefox ESR
not IE 9-11 # For IE 9-11 support, remove 'not'.

View File

@ -0,0 +1,18 @@
{
"extends": ["plugin:@nrwl/nx/react", "../../.eslintrc.json"],
"ignorePatterns": ["!**/*"],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {}
},
{
"files": ["*.ts", "*.tsx"],
"rules": {}
},
{
"files": ["*.js", "*.jsx"],
"rules": {}
}
]
}

View File

@ -0,0 +1,10 @@
module.exports = {
displayName: 'stats-mainnet',
preset: '../../jest.preset.js',
transform: {
'^(?!.*\\.(js|jsx|ts|tsx|css|json)$)': '@nrwl/react/plugins/jest',
'^.+\\.[tj]sx?$': 'babel-jest',
},
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
coverageDirectory: '../../coverage/apps/stats-mainnet',
};

View File

@ -0,0 +1,10 @@
const { join } = require('path');
module.exports = {
plugins: {
tailwindcss: {
config: join(__dirname, 'tailwind.config.js'),
},
autoprefixer: {},
},
};

View File

@ -0,0 +1,74 @@
{
"root": "apps/stats-mainnet",
"sourceRoot": "apps/stats-mainnet/src",
"projectType": "application",
"targets": {
"build": {
"executor": "@nrwl/web:webpack",
"outputs": ["{options.outputPath}"],
"defaultConfiguration": "production",
"options": {
"compiler": "babel",
"outputPath": "dist/apps/stats-mainnet",
"index": "apps/stats-mainnet/src/index.html",
"baseHref": "/",
"main": "apps/stats-mainnet/src/main.tsx",
"polyfills": "apps/stats-mainnet/src/polyfills.ts",
"tsConfig": "apps/stats-mainnet/tsconfig.app.json",
"assets": [
"apps/stats-mainnet/src/assets/favicon.ico",
"apps/stats-mainnet/src/assets"
],
"styles": ["apps/stats-mainnet/src/styles/styles.css"],
"scripts": [],
"webpackConfig": "@nrwl/react/plugins/webpack"
},
"configurations": {
"production": {
"fileReplacements": [
{
"replace": "apps/stats-mainnet/src/environments/environment.ts",
"with": "apps/stats-mainnet/src/environments/environment.prod.ts"
}
],
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"namedChunks": false,
"extractLicenses": true,
"vendorChunk": false
}
}
},
"serve": {
"executor": "@nrwl/web:dev-server",
"options": {
"port": 3010,
"buildTarget": "stats-mainnet:build",
"hmr": true
},
"configurations": {
"production": {
"buildTarget": "stats-mainnet:build:production",
"hmr": false
}
}
},
"lint": {
"executor": "@nrwl/linter:eslint",
"outputs": ["{options.outputFile}"],
"options": {
"lintFilePatterns": ["apps/stats-mainnet/**/*.{ts,tsx,js,jsx}"]
}
},
"test": {
"executor": "@nrwl/jest:jest",
"outputs": ["coverage/apps/stats-mainnet"],
"options": {
"jestConfig": "apps/stats-mainnet/jest.config.js",
"passWithNoTests": true
}
}
},
"tags": []
}

View File

@ -0,0 +1,24 @@
import React, { useState } from 'react';
import { Header } from './components/header';
import { StatsManager } from '@vegaprotocol/mainnet-stats-manager';
function App() {
const [darkMode, setDarkMode] = useState<boolean>(
document.documentElement.classList.contains('dark-mode-preferred')
);
return (
<div
className={`w-screen min-h-screen grid pb-24 ${
darkMode ? 'bg-black text-white-80' : 'bg-white text-black-95'
}`}
>
<div className="layout-grid w-screen justify-self-center">
<Header darkMode={darkMode} setDarkMode={setDarkMode} />
<StatsManager className="max-w-3xl px-24" />
</div>
</div>
);
}
export default App;

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 547 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -0,0 +1,20 @@
{
"short_name": "Mainnet Stats",
"name": "Vega Mainnet statistics",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

View File

@ -0,0 +1,3 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:

View File

@ -0,0 +1,32 @@
import { VegaLogo } from '@vegaprotocol/ui-toolkit';
import { LightModeToggle, DarkModeToggle } from '../images';
import { VegaBackgroundVideo } from '../videos';
import { DarkModeState } from '../../config/types';
export const Header = ({ darkMode, setDarkMode }: DarkModeState) => {
return (
<header className="relative overflow-hidden py-8 mb-40 md:mb-64">
<VegaBackgroundVideo />
<div
className={`relative flex justify-center px-8 ${
darkMode ? 'bg-black' : 'bg-white'
}`}
>
<div className="w-full max-w-3xl p-20 flex items-center justify-between">
<VegaLogo />
<button
onClick={() => setDarkMode(!darkMode)}
aria-label="Switch theme color"
className={`transition-colors rounded-full cursor-pointer ${
darkMode ? 'hover:bg-neutral-900' : 'hover:bg-neutral-200'
}`}
>
{darkMode ? <LightModeToggle /> : <DarkModeToggle />}
</button>
</div>
</div>
</header>
);
};

View File

@ -0,0 +1 @@
export { Header } from './header';

View File

@ -0,0 +1,18 @@
export const DarkModeToggle = () => {
return (
<svg width="45" height="45">
<g>
<path
d="M22.5 27.79a5.29 5.29 0 1 0 0-10.58 5.29 5.29 0 0 0 0 10.58Z"
fill="currentColor"
></path>
<path
d="M15.01 22.5H10M35 22.5h-5.01M22.5 29.99V35M22.5 10v5.01M17.21 27.79l-3.55 3.55M31.34 13.66l-3.55 3.55M27.79 27.79l3.55 3.55M13.66 13.66l3.55 3.55"
stroke="currentColor"
strokeWidth="1.3"
strokeMiterlimit="10"
></path>
</g>
</svg>
);
};

View File

@ -0,0 +1,2 @@
export { DarkModeToggle } from './dark-mode-toggle';
export { LightModeToggle } from './light-mode-toggle';

View File

@ -0,0 +1,10 @@
export const LightModeToggle = () => {
return (
<svg width="45" height="45">
<path
d="M28.75 11.69A12.39 12.39 0 0 0 22.5 10a12.5 12.5 0 1 0 0 25c2.196 0 4.353-.583 6.25-1.69A12.46 12.46 0 0 0 35 22.5a12.46 12.46 0 0 0-6.25-10.81Zm-6.25 22a11.21 11.21 0 0 1-11.2-11.2 11.21 11.21 0 0 1 11.2-11.2c1.246 0 2.484.209 3.66.62a13.861 13.861 0 0 0-5 10.58 13.861 13.861 0 0 0 5 10.58 11.078 11.078 0 0 1-3.66.63v-.01Z"
fill="currentColor"
></path>
</svg>
);
};

View File

@ -0,0 +1 @@
export { VegaBackgroundVideo } from './vega-background-video';

View File

@ -0,0 +1,16 @@
export const VegaBackgroundVideo = () => {
return (
<video
autoPlay
muted
loop
playsInline
className="absolute left-0 top-0 w-full h-auto"
>
<source
src="https://d33wubrfki0l68.cloudfront.net/2500bc5ef1b96927e0220eeb2bef0b22b87bcda1/3e0d3/static/moshed-aa65f0933af9abe9afb5e5663c9b3f68.mp4"
type="video/mp4"
/>
</video>
);
};

View File

@ -0,0 +1,4 @@
export interface DarkModeState {
darkMode: boolean;
setDarkMode: (arg0: boolean) => void;
}

View File

@ -0,0 +1,52 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="assets/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta name="description" content="Health check for Vega's mainnet" />
<link rel="apple-touch-icon" href="assets/apple-touch-icon.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="assets/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>Vega Mainnet Stats</title>
<script>
// Set dark mode preference indicator here to avoid FOUC
if (
localStorage.theme === 'dark' ||
(!('theme' in localStorage) &&
window.matchMedia('(prefers-color-scheme: dark)').matches)
) {
document.documentElement.classList.add('dark-mode-preferred');
} else {
document.documentElement.classList.remove('dark-mode-preferred');
}
</script>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

View File

@ -0,0 +1,17 @@
import React from 'react';
import ReactDOM from 'react-dom';
import './styles/styles.css';
import App from './app';
import reportWebVitals from './reportWebVitals';
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

View File

@ -0,0 +1,7 @@
/**
* Polyfill stable language features. These imports will be optimized by `@babel/preset-env`.
*
* See: https://github.com/zloirock/core-js#babel
*/
import 'core-js/stable';
import 'regenerator-runtime/runtime';

View File

@ -0,0 +1 @@
/// <reference types="react-scripts" />

View File

@ -0,0 +1,15 @@
import { ReportHandler } from 'web-vitals';
const reportWebVitals = (onPerfEntry?: ReportHandler) => {
if (onPerfEntry && onPerfEntry instanceof Function) {
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
getCLS(onPerfEntry);
getFID(onPerfEntry);
getFCP(onPerfEntry);
getLCP(onPerfEntry);
getTTFB(onPerfEntry);
});
}
};
export default reportWebVitals;

View File

@ -0,0 +1,5 @@
// jest-dom adds custom jest matchers for asserting on DOM nodes.
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom';

Binary file not shown.

View File

@ -0,0 +1,39 @@
@tailwind base;
@tailwind components;
@font-face {
font-family: 'AlphaLyrae-Medium';
src: url('./AlphaLyrae-Medium.woff');
}
.layout-grid {
display: grid;
grid-template-columns: 1fr;
grid-template-rows: repeat(2, auto) 1fr;
}
.stats-grid {
display: grid;
}
@media (min-width: 768px) {
.stats-grid {
grid-template-columns: 18rem 1fr;
grid-column-gap: 1.25rem;
align-items: flex-start;
}
}
@media (min-width: 640px) {
.promoted-stats {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
@media (min-width: 768px) {
.promoted-stats {
grid-template-columns: repeat(1, minmax(0, 1fr));
}
}
@tailwind utilities;

View File

@ -0,0 +1,12 @@
const { join } = require('path');
const { createGlobPatternsForDependencies } = require('@nrwl/next/tailwind');
const theme = require('../../libs/tailwindcss-config/src/theme');
module.exports = {
content: [
join(__dirname, 'src/**/*.{js,ts,jsx,tsx}'),
...createGlobPatternsForDependencies(__dirname),
],
theme,
plugins: [],
};

View File

@ -0,0 +1,22 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"types": ["node"]
},
"files": [
"../../node_modules/@nrwl/react/typings/cssmodule.d.ts",
"../../node_modules/@nrwl/react/typings/image.d.ts"
],
"exclude": [
"**/*.spec.ts",
"**/*.test.ts",
"**/*.spec.tsx",
"**/*.test.tsx",
"**/*.spec.js",
"**/*.test.js",
"**/*.spec.jsx",
"**/*.test.jsx"
],
"include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"]
}

View File

@ -0,0 +1,25 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"jsx": "react-jsx",
"allowJs": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
},
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.app.json"
},
{
"path": "./tsconfig.spec.json"
}
]
}

View File

@ -0,0 +1,23 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"module": "commonjs",
"types": ["jest", "node"]
},
"include": [
"**/*.test.ts",
"**/*.spec.ts",
"**/*.test.tsx",
"**/*.spec.tsx",
"**/*.test.js",
"**/*.spec.js",
"**/*.test.jsx",
"**/*.spec.jsx",
"**/*.d.ts"
],
"files": [
"../../node_modules/@nrwl/react/typings/cssmodule.d.ts",
"../../node_modules/@nrwl/react/typings/image.d.ts"
]
}

View File

@ -0,0 +1,12 @@
{
"presets": [
[
"@nrwl/react/babel",
{
"runtime": "automatic",
"useBuiltIns": "usage"
}
]
],
"plugins": []
}

View File

@ -0,0 +1,18 @@
{
"extends": ["plugin:@nrwl/nx/react", "../../.eslintrc.json"],
"ignorePatterns": ["!**/*"],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {}
},
{
"files": ["*.ts", "*.tsx"],
"rules": {}
},
{
"files": ["*.js", "*.jsx"],
"rules": {}
}
]
}

View File

@ -0,0 +1,7 @@
# mainnet-stats-manager
This library was generated with [Nx](https://nx.dev).
## Running unit tests
Run `nx test mainnet-stats-manager` to execute the unit tests via [Jest](https://jestjs.io).

View File

@ -0,0 +1,9 @@
module.exports = {
displayName: 'mainnet-stats-manager',
preset: '../../jest.preset.js',
transform: {
'^.+\\.[tj]sx?$': 'babel-jest',
},
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
coverageDirectory: '../../coverage/libs/mainnet-stats-manager',
};

View File

@ -0,0 +1,4 @@
{
"name": "@vegaprotocol/mainnet-stats-manager",
"version": "0.0.1"
}

View File

@ -0,0 +1,43 @@
{
"root": "libs/mainnet-stats-manager",
"sourceRoot": "libs/mainnet-stats-manager/src",
"projectType": "library",
"tags": [],
"targets": {
"build": {
"executor": "@nrwl/web:rollup",
"outputs": ["{options.outputPath}"],
"options": {
"outputPath": "dist/libs/mainnet-stats-manager",
"tsConfig": "libs/mainnet-stats-manager/tsconfig.lib.json",
"project": "libs/mainnet-stats-manager/package.json",
"entryFile": "libs/mainnet-stats-manager/src/index.ts",
"external": ["react/jsx-runtime"],
"rollupConfig": "@nrwl/react/plugins/bundle-rollup",
"compiler": "babel",
"assets": [
{
"glob": "libs/mainnet-stats-manager/README.md",
"input": ".",
"output": "."
}
]
}
},
"lint": {
"executor": "@nrwl/linter:eslint",
"outputs": ["{options.outputFile}"],
"options": {
"lintFilePatterns": ["libs/mainnet-stats-manager/**/*.{ts,tsx,js,jsx}"]
}
},
"test": {
"executor": "@nrwl/jest:jest",
"outputs": ["coverage/libs/mainnet-stats-manager"],
"options": {
"jestConfig": "libs/mainnet-stats-manager/jest.config.js",
"passWithNoTests": true
}
}
}
}

View File

@ -0,0 +1,21 @@
import { value, goodThreshold } from '../../config/types';
interface GoodThresholdIndicatorProps {
goodThreshold: goodThreshold | undefined;
value: value;
}
export const GoodThresholdIndicator = ({
goodThreshold,
value,
}: GoodThresholdIndicatorProps) => {
return (
<div
className={`inline-block w-8 h-8 mb-[0.15rem] mr-8 rounded ${
(goodThreshold &&
(goodThreshold(value) ? 'bg-intent-success' : 'bg-intent-danger')) ||
'bg-current'
}`}
/>
);
};

View File

@ -0,0 +1 @@
export { GoodThresholdIndicator } from './good-threshold-indicator';

View File

@ -0,0 +1 @@
export { PromotedStatsItem } from './promoted-stats-item';

View File

@ -0,0 +1,26 @@
import { Tooltip } from '../tooltip';
import { StatFields } from '../../config/types';
import { defaultFieldFormatter } from '../table-row';
import { GoodThresholdIndicator } from '../good-threshold-indicator';
export const PromotedStatsItem = ({
title,
formatter,
goodThreshold,
value,
description,
}: StatFields) => {
return (
<Tooltip description={description}>
<div className="px-24 py-16 pr-64 border items-center">
<div className="uppercase text-[0.9375rem]">
<GoodThresholdIndicator goodThreshold={goodThreshold} value={value} />
<span>{title}</span>
</div>
<div className="mt-4 text-h4 leading-none">
{formatter ? formatter(value) : defaultFieldFormatter(value)}
</div>
</div>
</Tooltip>
);
};

View File

@ -0,0 +1 @@
export { PromotedStats } from './promoted-stats';

View File

@ -0,0 +1,13 @@
import React from 'react';
interface PromotedStatsProps {
children: React.ReactNode;
}
export const PromotedStats = ({ children }: PromotedStatsProps) => {
return (
<div className="grid promoted-stats content-start gap-4 mb-24">
{children}
</div>
);
};

View File

@ -0,0 +1 @@
export { StatsManager } from './stats-manager';

View File

@ -0,0 +1,120 @@
import { useEffect, useState } from 'react';
import classnames from 'classnames';
import { statsFields } from '../../config/stats-fields';
import {
Stats as IStats,
StructuredStats as IStructuredStats,
} from '../../config/types';
import { Table } from '../table';
import { TableRow } from '../table-row';
import { PromotedStats } from '../promoted-stats';
import { PromotedStatsItem } from '../promoted-stats-item';
interface statsManagerProps {
className?: string;
}
export const StatsManager = ({ className }: statsManagerProps) => {
const [data, setData] = useState<IStructuredStats | null>(null);
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
async function getStats() {
try {
const [res1, res2] = await Promise.all([
fetch('https://api.token.vega.xyz/statistics'),
fetch('https://api.token.vega.xyz/nodes-data'),
]);
const [{ statistics }, { nodeData }] = await Promise.all([
res1.json(),
res2.json(),
]);
const returned = { ...nodeData, ...statistics };
if (!statistics || !nodeData) {
throw new Error('Failed to get data from endpoints');
}
// Loop through the stats fields config, grabbing values from the fetched
// data and building a set of promoted and standard table entries.
const structured = Object.entries(statsFields).reduce(
(acc, [key, value]) => {
const statKey = key as keyof IStats;
const statData = returned[statKey];
value.forEach((x) => {
const stat = {
...x,
value: statData,
};
stat.promoted ? acc.promoted.push(stat) : acc.table.push(stat);
});
return acc;
},
{ promoted: [], table: [] } as IStructuredStats
);
setData(structured);
setError(null);
} catch (e) {
setData(null);
setError(e as Error);
}
}
const interval = setInterval(getStats, 1000);
return () => {
clearInterval(interval);
};
}, []);
const classes = classnames(
className,
'stats-grid w-full self-start justify-self-center'
);
return (
<div className={classes}>
<h3 className="font-alpha uppercase text-h3 pb-16 col-span-full">
{(error && `/ ${error}`) || (data ? '/ Mainnet' : '/ Connecting...')}
</h3>
{data?.promoted ? (
<PromotedStats>
{data.promoted.map((stat, i) => {
return (
<PromotedStatsItem
title={stat.title}
value={stat.value}
formatter={stat.formatter}
goodThreshold={stat.goodThreshold}
description={stat.description}
key={i}
/>
);
})}
</PromotedStats>
) : null}
<Table>
{data?.table
? data.table.map((stat, i) => {
return (
<TableRow
title={stat.title}
value={stat.value}
formatter={stat.formatter}
goodThreshold={stat.goodThreshold}
description={stat.description}
key={i}
/>
);
})
: null}
</Table>
</div>
);
};

View File

@ -0,0 +1 @@
export { TableRow, defaultFieldFormatter } from './table-row';

View File

@ -0,0 +1,28 @@
import { Tooltip } from '../tooltip';
import { StatFields } from '../../config/types';
import { GoodThresholdIndicator } from '../good-threshold-indicator';
export const defaultFieldFormatter = (field: unknown) =>
field === undefined ? 'no data' : field;
export const TableRow = ({
title,
formatter,
goodThreshold,
value,
description,
}: StatFields) => {
return (
<Tooltip description={description}>
<tr className="border">
<td className="py-4 px-8">{title}</td>
<td className="py-4 px-8 text-right">
{formatter ? formatter(value) : defaultFieldFormatter(value)}
</td>
<td className="py-4 px-8">
<GoodThresholdIndicator goodThreshold={goodThreshold} value={value} />
</td>
</tr>
</Tooltip>
);
};

View File

@ -0,0 +1 @@
export { Table } from './table';

View File

@ -0,0 +1,13 @@
import React from 'react';
interface TableProps {
children: React.ReactNode;
}
export const Table = ({ children }: TableProps) => {
return (
<table>
<tbody>{children}</tbody>
</table>
);
};

View File

@ -0,0 +1 @@
export { Tooltip } from './tooltip';

View File

@ -0,0 +1,36 @@
import React from 'react';
import {
Provider,
Root,
Trigger,
Content,
Arrow,
} from '@radix-ui/react-tooltip';
interface TooltipProps {
children: React.ReactElement;
description?: string;
}
// Conditionally rendered tooltip if description content is provided.
export const Tooltip = ({ children, description }: TooltipProps) =>
description ? (
<Provider delayDuration={200} skipDelayDuration={100}>
<Root>
<Trigger asChild>{children}</Trigger>
<Content align={'start'} alignOffset={5}>
<Arrow
width={10}
height={5}
offset={10}
className="fill-vega-green"
/>
<div className="px-12 py-8 bg-vega-green text-black rounded-sm max-w-sm">
{description}
</div>
</Content>
</Root>
</Provider>
) : (
children
);

View File

@ -0,0 +1,174 @@
import { Stats as IStats, StatFields as IStatFields } from './types';
// Stats fields config. Keys will correspond to graphql queries when used, and values
// contain the associated data and methods we need to render. A single query
// can be rendered in multiple ways (see 'upTime').
export const statsFields: { [key in keyof IStats]: IStatFields[] } = {
status: [
{
title: 'Status',
formatter: (status: string) => {
if (!status) {
return;
}
const i = status.lastIndexOf('_');
if (i === -1) {
return status;
} else {
return status.substr(i + 1);
}
},
goodThreshold: (status: string) =>
status === 'CONNECTED' || status === 'CHAIN_STATUS_CONNECTED',
promoted: true,
description:
'Status is either connected, replaying, unspecified or disconnected',
},
],
blockHeight: [
{
title: 'Height',
goodThreshold: (height: number) => height >= 60,
promoted: true,
description: 'Block height',
},
],
totalNodes: [
{
title: 'Total nodes',
description: 'The total number of nodes registered on the network',
},
],
validatingNodes: [
{
title: 'Validating nodes',
promoted: true,
description: 'Nodes participating in consensus',
},
],
inactiveNodes: [
{
title: 'Inactive nodes',
goodThreshold: (totalInactive: number) => totalInactive < 1,
description: 'Nodes that are registered but not validating',
},
],
stakedTotal: [
{
title: 'Total staked',
formatter: (total: string) =>
total.length > 18 &&
parseInt(total.substring(0, total.length - 18)).toLocaleString('en-US'),
description: 'Sum of VEGA associated with a Vega key',
},
],
backlogLength: [
{
title: 'Backlog',
goodThreshold: (length: number, blockDuration: number) => {
return (
length < 1000 || (length >= 1000 && blockDuration / 1000000000 <= 1)
);
},
description: 'Number of transactions waiting to be processed',
},
],
tradesPerSecond: [
{
title: 'Trades / second',
goodThreshold: (trades: number) => trades >= 2,
description: 'Number of trades processed in the last second',
},
],
averageOrdersPerBlock: [
{
title: 'Orders / block',
goodThreshold: (orders: number) => orders >= 2,
description:
'Number of new orders processed in the last block. All pegged orders and liquidity provisions count as a single order',
},
],
ordersPerSecond: [
{
title: 'Orders / second',
goodThreshold: (orders: number) => orders >= 2,
description:
'Number of orders processed in the last second. All pegged orders and liquidity provisions count as a single order',
},
],
txPerBlock: [
{
title: 'Transactions / block',
goodThreshold: (tx: number) => tx > 2,
description: 'Number of transactions processed in the last block',
},
],
blockDuration: [
{
title: 'Block time',
formatter: (duration: number) => (duration / 1000000000).toFixed(3),
goodThreshold: (blockDuration: number) =>
blockDuration > 0 && blockDuration <= 2000000000,
description: 'Seconds between the two most recent blocks',
},
],
vegaTime: [
{
title: 'Time',
formatter: (time: Date) => new Date(time).toLocaleTimeString(),
goodThreshold: (time: Date) => {
const diff = new Date().getTime() - new Date(time).getTime();
return diff > 0 && diff < 5000;
},
description: 'The time on the blockchain',
},
],
appVersion: [
{
title: 'App',
description: 'Vega node software version on this node',
},
],
chainVersion: [
{
title: 'Tendermint',
description: 'Tendermint software version on this node',
},
],
uptime: [
{
title: 'Uptime',
formatter: (t: string) => {
if (!t) {
return;
}
const secSinceStart =
(new Date().getTime() - new Date(t).getTime()) / 1000;
const days = Math.floor(secSinceStart / 60 / 60 / 24);
const hours = Math.floor((secSinceStart / 60 / 60) % 24);
const mins = Math.floor((secSinceStart / 60) % 60);
const secs = Math.floor(secSinceStart % 60);
return `${days}d ${hours}h ${mins}m ${secs}s`;
},
promoted: true,
description: 'Time since genesis',
},
{
title: 'Up since',
formatter: (t: string) => {
if (!t) {
return;
}
return `${new Date(t).toLocaleString().replace(',', ' ')}`;
},
description: 'Genesis',
},
],
chainId: [
{
title: 'Chain ID',
description: 'Identifier',
},
],
};

View File

@ -0,0 +1,36 @@
export interface Stats {
blockHeight: string;
totalNodes: string;
validatingNodes: string;
inactiveNodes: string;
stakedTotal: string;
backlogLength: string;
tradesPerSecond: string;
averageOrdersPerBlock: string;
ordersPerSecond: string;
txPerBlock: string;
blockDuration: string;
status: string;
vegaTime: string;
appVersion: string;
chainVersion: string;
uptime: string;
chainId: string;
}
export type value = any;
export type goodThreshold = (...args: value[]) => boolean;
export interface StatFields {
title: string;
goodThreshold?: goodThreshold;
formatter?: (arg0: value) => any;
promoted?: boolean;
value?: value;
description?: string;
}
export interface StructuredStats {
promoted: StatFields[];
table: StatFields[];
}

View File

@ -0,0 +1 @@
export { StatsManager } from './components/stats-manager';

View File

@ -0,0 +1,25 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"jsx": "react-jsx",
"allowJs": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
},
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.lib.json"
},
{
"path": "./tsconfig.spec.json"
}
]
}

View File

@ -0,0 +1,22 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"types": ["node"]
},
"files": [
"../../node_modules/@nrwl/react/typings/cssmodule.d.ts",
"../../node_modules/@nrwl/react/typings/image.d.ts"
],
"exclude": [
"**/*.spec.ts",
"**/*.test.ts",
"**/*.spec.tsx",
"**/*.test.tsx",
"**/*.spec.js",
"**/*.test.js",
"**/*.spec.jsx",
"**/*.test.jsx"
],
"include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"]
}

View File

@ -0,0 +1,19 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"module": "commonjs",
"types": ["jest", "node"]
},
"include": [
"**/*.test.ts",
"**/*.spec.ts",
"**/*.test.tsx",
"**/*.spec.tsx",
"**/*.test.js",
"**/*.spec.js",
"**/*.test.jsx",
"**/*.spec.jsx",
"**/*.d.ts"
]
}

View File

@ -2,7 +2,9 @@ const defaultTheme = require('tailwindcss/defaultTheme');
module.exports = {
screens: {
sm: '500px',
xs: '500px',
sm: '640px',
md: '768px',
lg: '960px',
},
colors: {
@ -63,7 +65,9 @@ module.exports = {
24: '1.5rem',
28: '1.75rem',
32: '2rem',
40: '2.5rem',
44: '2.75rem',
64: '4rem',
},
opacity: {
0: '0',

View File

@ -0,0 +1 @@
export * from './vega-logo';

View File

@ -0,0 +1,11 @@
import { Story, Meta } from '@storybook/react';
import { VegaLogo } from './vega-logo';
export default {
component: VegaLogo,
title: 'Vega logo',
} as Meta;
const Template: Story = () => <VegaLogo />;
export const Default = Template.bind({});

View File

@ -0,0 +1,10 @@
import { render } from '@testing-library/react';
import { VegaLogo } from './vega-logo';
describe('Vega logo', () => {
it('should render successfully', () => {
const { baseElement } = render(<VegaLogo />);
expect(baseElement).toBeTruthy();
});
});

View File

@ -0,0 +1,16 @@
export const VegaLogo = () => {
return (
<svg
aria-label="Vega logo"
width="111"
height="24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill="currentColor"
d="M10.371 20.794 17.183.02h3.42l-8.436 23.965H8.471L0 .02h3.48l6.891 20.774ZM34.994 13.019v8.249h13.382v2.716H31.782V.019h16.3v2.715H34.994v7.577h11.658v2.708H34.994ZM61.25 2.92h2.88v18.195h-2.88V2.92ZM75.653 0v2.92H64.129V0h11.525ZM64.129 24v-2.885h11.525V24H64.129Zm14.405-2.885h-2.88v-5.77h-5.762v-2.89h8.642v8.66Zm0-18.195v2.884h-2.88V2.92h2.88ZM110.71 23.984h-3.354l-1.763-4.826h-12.03L91.8 23.984h-3.279L97.807.019h3.626l9.277 23.965ZM99.528 2.904l-4.949 13.544h10.002L99.597 2.903h-.07Z"
></path>
</svg>
);
};

View File

@ -17,6 +17,7 @@ export { Splash } from './components/splash';
export { TextArea } from './components/text-area';
export { ThemeSwitcher } from './components/theme-switcher';
export { Dialog } from './components/dialog/dialog';
export { VegaLogo } from './components/vega-logo';
// Utils
export * from './utils/intent';

View File

@ -19,6 +19,7 @@
"@nrwl/next": "13.8.1",
"@radix-ui/react-dialog": "^0.1.5",
"@radix-ui/react-tabs": "^0.1.5",
"@radix-ui/react-tooltip": "^0.1.7",
"@sentry/react": "^6.18.1",
"@sentry/tracing": "^6.18.1",
"@types/uuid": "^8.3.4",
@ -51,7 +52,8 @@
"sha3": "^2.1.4",
"tailwindcss": "^3.0.23",
"tslib": "^2.0.0",
"uuid": "^8.3.2"
"uuid": "^8.3.2",
"web-vitals": "^2.1.4"
},
"devDependencies": {
"@babel/core": "7.12.13",

View File

@ -17,6 +17,9 @@
"paths": {
"@vegaprotocol/deal-ticket": ["libs/deal-ticket/src/index.ts"],
"@vegaprotocol/graphql": ["libs/graphql/src/index.ts"],
"@vegaprotocol/mainnet-stats-manager": [
"libs/mainnet-stats-manager/src/index.ts"
],
"@vegaprotocol/market-list": ["libs/market-list/src/index.ts"],
"@vegaprotocol/react-helpers": ["libs/react-helpers/src/index.ts"],
"@vegaprotocol/tailwindcss-config": [

View File

@ -5,8 +5,11 @@
"explorer": "apps/explorer",
"explorer-e2e": "apps/explorer-e2e",
"graphql": "libs/graphql",
"mainnet-stats-manager": "libs/mainnet-stats-manager",
"market-list": "libs/market-list",
"react-helpers": "libs/react-helpers",
"stats-mainnet": "apps/stats-mainnet",
"stats-mainnet-e2e": "apps/stats-mainnet-e2e",
"tailwindcss-config": "libs/tailwindcss-config",
"trading": "apps/trading",
"trading-e2e": "apps/trading-e2e",

View File

@ -3199,6 +3199,14 @@
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.2.tgz#830beaec4b4091a9e9398ac50f865ddea52186b9"
integrity sha512-92FRmppjjqz29VMJ2dn+xdyXZBrMlE42AV6Kq6BwjWV7CNUW1hs2FtxSNLQE+gJhaZ6AAmYuO9y8dshhcBl7vA==
"@radix-ui/popper@0.1.0":
version "0.1.0"
resolved "https://registry.yarnpkg.com/@radix-ui/popper/-/popper-0.1.0.tgz#c387a38f31b7799e1ea0d2bb1ca0c91c2931b063"
integrity sha512-uzYeElL3w7SeNMuQpXiFlBhTT+JyaNMCwDfjKkrzugEcYrf5n52PHqncNdQPUtR42hJh8V9FsqyEDbDxkeNjJQ==
dependencies:
"@babel/runtime" "^7.13.10"
csstype "^3.0.4"
"@radix-ui/primitive@0.1.0":
version "0.1.0"
resolved "https://registry.yarnpkg.com/@radix-ui/primitive/-/primitive-0.1.0.tgz#6206b97d379994f0d1929809db035733b337e543"
@ -3206,6 +3214,14 @@
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/react-arrow@0.1.4":
version "0.1.4"
resolved "https://registry.yarnpkg.com/@radix-ui/react-arrow/-/react-arrow-0.1.4.tgz#a871448a418cd3507d83840fdd47558cb961672b"
integrity sha512-BB6XzAb7Ml7+wwpFdYVtZpK1BlMgqyafSQNGzhIpSZ4uXvXOHPlR5GP8M449JkeQzgQjv9Mp1AsJxFC0KuOtuA==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/react-primitive" "0.1.4"
"@radix-ui/react-collection@0.1.4":
version "0.1.4"
resolved "https://registry.yarnpkg.com/@radix-ui/react-collection/-/react-collection-0.1.4.tgz#734061ffd5bb93e88889d49b87391a73a63824c9"
@ -3290,6 +3306,21 @@
"@babel/runtime" "^7.13.10"
"@radix-ui/react-use-layout-effect" "0.1.0"
"@radix-ui/react-popper@0.1.4":
version "0.1.4"
resolved "https://registry.yarnpkg.com/@radix-ui/react-popper/-/react-popper-0.1.4.tgz#dfc055dcd7dfae6a2eff7a70d333141d15a5d029"
integrity sha512-18gDYof97t8UQa7zwklG1Dr8jIdj3u+rVOQLzPi9f5i1YQak/pVGkaqw8aY+iDUknKKuZniTk/7jbAJUYlKyOw==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/popper" "0.1.0"
"@radix-ui/react-arrow" "0.1.4"
"@radix-ui/react-compose-refs" "0.1.0"
"@radix-ui/react-context" "0.1.1"
"@radix-ui/react-primitive" "0.1.4"
"@radix-ui/react-use-rect" "0.1.1"
"@radix-ui/react-use-size" "0.1.1"
"@radix-ui/rect" "0.1.1"
"@radix-ui/react-portal@0.1.4":
version "0.1.4"
resolved "https://registry.yarnpkg.com/@radix-ui/react-portal/-/react-portal-0.1.4.tgz#17bdce3d7f1a9a0b35cb5e935ab8bc562441a7d2"
@ -3352,6 +3383,27 @@
"@radix-ui/react-roving-focus" "0.1.5"
"@radix-ui/react-use-controllable-state" "0.1.0"
"@radix-ui/react-tooltip@^0.1.7":
version "0.1.7"
resolved "https://registry.yarnpkg.com/@radix-ui/react-tooltip/-/react-tooltip-0.1.7.tgz#6f8c00d6e489565d14abf209ce0fb8853c8c8ee3"
integrity sha512-eiBUsVOHenZ0JR16tl970bB0DafJBz6mFgSGfIGIVpflFj0LIsIDiLMsYyvYdx1KwwsIUDTEZtxcPm/sWjPzqA==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/primitive" "0.1.0"
"@radix-ui/react-compose-refs" "0.1.0"
"@radix-ui/react-context" "0.1.1"
"@radix-ui/react-id" "0.1.5"
"@radix-ui/react-popper" "0.1.4"
"@radix-ui/react-portal" "0.1.4"
"@radix-ui/react-presence" "0.1.2"
"@radix-ui/react-primitive" "0.1.4"
"@radix-ui/react-slot" "0.1.2"
"@radix-ui/react-use-controllable-state" "0.1.0"
"@radix-ui/react-use-escape-keydown" "0.1.0"
"@radix-ui/react-use-previous" "0.1.1"
"@radix-ui/react-use-rect" "0.1.1"
"@radix-ui/react-visually-hidden" "0.1.4"
"@radix-ui/react-use-body-pointer-events@0.1.1":
version "0.1.1"
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-body-pointer-events/-/react-use-body-pointer-events-0.1.1.tgz#63e7fd81ca7ffd30841deb584cd2b7f460df2597"
@ -3390,6 +3442,43 @@
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/react-use-previous@0.1.1":
version "0.1.1"
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-previous/-/react-use-previous-0.1.1.tgz#0226017f72267200f6e832a7103760e96a6db5d0"
integrity sha512-O/ZgrDBr11dR8rhO59ED8s5zIXBRFi8MiS+CmFGfi7MJYdLbfqVOmQU90Ghf87aifEgWe6380LA69KBneaShAg==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/react-use-rect@0.1.1":
version "0.1.1"
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-rect/-/react-use-rect-0.1.1.tgz#6c15384beee59c086e75b89a7e66f3d2e583a856"
integrity sha512-kHNNXAsP3/PeszEmM/nxBBS9Jbo93sO+xuMTcRfwzXsmxT5gDXQzAiKbZQ0EecCPtJIzqvr7dlaQi/aP1PKYqQ==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/rect" "0.1.1"
"@radix-ui/react-use-size@0.1.1":
version "0.1.1"
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-size/-/react-use-size-0.1.1.tgz#f6b75272a5d41c3089ca78c8a2e48e5f204ef90f"
integrity sha512-pTgWM5qKBu6C7kfKxrKPoBI2zZYZmp2cSXzpUiGM3qEBQlMLtYhaY2JXdXUCxz+XmD1YEjc8oRwvyfsD4AG4WA==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/react-visually-hidden@0.1.4":
version "0.1.4"
resolved "https://registry.yarnpkg.com/@radix-ui/react-visually-hidden/-/react-visually-hidden-0.1.4.tgz#6c75eae34fb5d084b503506fbfc05587ced05f03"
integrity sha512-K/q6AEEzqeeEq/T0NPChvBqnwlp8Tl4NnQdrI/y8IOY7BRR+Ug0PEsVk6g48HJ7cA1//COugdxXXVVK/m0X1mA==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/react-primitive" "0.1.4"
"@radix-ui/rect@0.1.1":
version "0.1.1"
resolved "https://registry.yarnpkg.com/@radix-ui/rect/-/rect-0.1.1.tgz#95b5ba51f469bea6b1b841e2d427e17e37d38419"
integrity sha512-g3hnE/UcOg7REdewduRPAK88EPuLZtaq7sA9ouu8S+YEtnyFRI16jgv6GZYe3VMoQLL1T171ebmEPtDjyxWLzw==
dependencies:
"@babel/runtime" "^7.13.10"
"@rollup/plugin-babel@^5.3.0":
version "5.3.1"
resolved "https://registry.yarnpkg.com/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz#04bc0608f4aa4b2e4b1aebf284344d0f68fda283"
@ -8961,7 +9050,7 @@ csstype@^2.5.7:
resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.20.tgz#9229c65ea0b260cf4d3d997cb06288e36a8d6dda"
integrity sha512-/WwNkdXfckNgw6S5R125rrW8ez139lBHWouiBvX8dfMFtcn6V81REDqnH7+CRpRipfYlyU1CmOnOxrmGcFOjeA==
csstype@^3.0.2:
csstype@^3.0.2, csstype@^3.0.4:
version "3.0.11"
resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.11.tgz#d66700c5eacfac1940deb4e3ee5642792d85cd33"
integrity sha512-sa6P2wJ+CAbgyy4KFssIb/JNMLxFvKF1pCYCSXS8ZMuqZnMsrxqI2E5sPyoTpxoPU/gVZMzr2zjOfg8GIZOMsw==
@ -19675,6 +19764,11 @@ web-namespaces@^1.0.0:
resolved "https://registry.yarnpkg.com/web-namespaces/-/web-namespaces-1.1.4.tgz#bc98a3de60dadd7faefc403d1076d529f5e030ec"
integrity sha512-wYxSGajtmoP4WxfejAPIr4l0fVh+jeMXZb08wNc0tMg6xsfZXj3cECqIK0G7ZAqUq0PP8WlMDtaOGVBTAWztNw==
web-vitals@^2.1.4:
version "2.1.4"
resolved "https://registry.yarnpkg.com/web-vitals/-/web-vitals-2.1.4.tgz#76563175a475a5e835264d373704f9dde718290c"
integrity sha512-sVWcwhU5mX6crfI5Vd2dC4qchyTqxV8URinzt25XqVh+bHEPGH4C3NPrNionCP7Obx59wrYEbNlw4Z8sjALzZg==
webidl-conversions@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871"