Merge branch 'main' of github.com:blockscout/frontend into tom2drum/issue-1977
@@ -257,7 +257,7 @@ const rollupSchema = yup
|
||||
.when('NEXT_PUBLIC_ROLLUP_TYPE', {
|
||||
is: (value: string) => value === 'optimistic',
|
||||
then: (schema) => schema.test(urlTest).required(),
|
||||
otherwise: (schema) => schema.max(-1, 'NEXT_PUBLIC_ROLLUP_L2_WITHDRAWAL_URL cannot not be used if NEXT_PUBLIC_ROLLUP_TYPE is not defined'),
|
||||
otherwise: (schema) => schema.max(-1, 'NEXT_PUBLIC_ROLLUP_L2_WITHDRAWAL_URL can be used only if NEXT_PUBLIC_ROLLUP_TYPE is set to \'optimistic\' '),
|
||||
}),
|
||||
});
|
||||
|
||||
|
||||
@@ -402,8 +402,8 @@ This feature is **enabled by default** with the `coinzilla` ads provider. To swi
|
||||
| --- | --- | --- | --- | --- | --- | --- |
|
||||
| NEXT_PUBLIC_ROLLUP_TYPE | `'optimistic' \| 'arbitrum' \| 'shibarium' \| 'zkEvm' \| 'zkSync' ` | Rollup chain type | Required | - | `'optimistic'` | v1.24.0+ |
|
||||
| NEXT_PUBLIC_ROLLUP_L1_BASE_URL | `string` | Blockscout base URL for L1 network | Required | - | `'http://eth-goerli.blockscout.com'` | v1.24.0+ |
|
||||
| NEXT_PUBLIC_ROLLUP_L2_WITHDRAWAL_URL | `string` | URL for L2 -> L1 withdrawals | Required only for `optimistic` rollups | - | `https://app.optimism.io/bridge/withdraw` | v1.24.0+ |
|
||||
| NEXT_PUBLIC_FAULT_PROOF_ENABLED | `boolean` | Set to `true` for chains with fault proof system enabled (OP stack only) | - | - | `true` | v1.31.0+ |
|
||||
| NEXT_PUBLIC_ROLLUP_L2_WITHDRAWAL_URL | `string` | URL for L2 -> L1 withdrawals (Optimistic stack only) | Required for `optimistic` rollups | - | `https://app.optimism.io/bridge/withdraw` | v1.24.0+ |
|
||||
| NEXT_PUBLIC_FAULT_PROOF_ENABLED | `boolean` | Set to `true` for chains with fault proof system enabled (Optimistic stack only) | - | - | `true` | v1.31.0+ |
|
||||
|
||||
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 286 B After Width: | Height: | Size: 286 B |
|
Before Width: | Height: | Size: 666 B After Width: | Height: | Size: 666 B |
|
Before Width: | Height: | Size: 822 B After Width: | Height: | Size: 822 B |
|
Before Width: | Height: | Size: 791 B After Width: | Height: | Size: 791 B |
|
Before Width: | Height: | Size: 949 B After Width: | Height: | Size: 949 B |
|
Before Width: | Height: | Size: 367 B After Width: | Height: | Size: 367 B |
|
Before Width: | Height: | Size: 352 B After Width: | Height: | Size: 352 B |
|
Before Width: | Height: | Size: 181 B After Width: | Height: | Size: 181 B |
@@ -1,4 +0,0 @@
|
||||
<svg viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.392 5.45c.252-.288.592-.45.948-.45h8.038a.63.63 0 0 1 .474.225l4.689 5.385a.83.83 0 0 1 .196.544v8.574l-.985 1.055-.355-.38v-8.666h-4.485a.702.702 0 0 1-.702-.702V6.538H7.34v16.924h9.44L18.217 25H7.34c-.356 0-.696-.162-.948-.45A1.661 1.661 0 0 1 6 23.461V6.538c0-.408.141-.799.392-1.087Zm9.222 1.678 2.791 3.205h-2.791V7.128ZM8.85 15.5a.65.65 0 0 1 .65-.65h7.2a.65.65 0 1 1 0 1.3H9.5a.65.65 0 0 1-.65-.65Zm0 2.4a.65.65 0 0 1 .65-.65h7.2a.65.65 0 1 1 0 1.3H9.5a.65.65 0 0 1-.65-.65Z" fill="currentColor"/>
|
||||
<path d="m17.552 21.357 2.2 2.357 4.4-4.714" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 776 B |
@@ -1,4 +1,6 @@
|
||||
import type * as bens from '@blockscout/bens-types';
|
||||
import type * as stats from '@blockscout/stats-types';
|
||||
import type * as visualizer from '@blockscout/visualizer-types';
|
||||
import { getFeaturePayload } from 'configs/app/features/types';
|
||||
import type {
|
||||
UserInfo,
|
||||
@@ -71,7 +73,7 @@ import type {
|
||||
import type { RawTracesResponse } from 'types/api/rawTrace';
|
||||
import type { SearchRedirectResult, SearchResult, SearchResultFilters, SearchResultItem } from 'types/api/search';
|
||||
import type { ShibariumWithdrawalsResponse, ShibariumDepositsResponse } from 'types/api/shibarium';
|
||||
import type { Counters, StatsCharts, StatsChart, HomeStats } from 'types/api/stats';
|
||||
import type { HomeStats } from 'types/api/stats';
|
||||
import type {
|
||||
TokenCounters,
|
||||
TokenInfo,
|
||||
@@ -99,7 +101,6 @@ import type { TxStateChanges } from 'types/api/txStateChanges';
|
||||
import type { UserOpsResponse, UserOp, UserOpsFilters, UserOpsAccount } from 'types/api/userOps';
|
||||
import type { ValidatorsCountersResponse, ValidatorsFilters, ValidatorsResponse, ValidatorsSorting } from 'types/api/validators';
|
||||
import type { VerifiedContractsSorting } from 'types/api/verifiedContracts';
|
||||
import type { VisualizedContract } from 'types/api/visualization';
|
||||
import type { WithdrawalsResponse, WithdrawalsCounters } from 'types/api/withdrawals';
|
||||
import type {
|
||||
ZkEvmL2DepositsResponse,
|
||||
@@ -931,9 +932,9 @@ Q extends 'homepage_zkevm_l2_batches' ? { items: Array<ZkEvmL2TxnBatchesItem> }
|
||||
Q extends 'homepage_indexing_status' ? IndexingStatus :
|
||||
Q extends 'homepage_zkevm_latest_batch' ? number :
|
||||
Q extends 'homepage_zksync_latest_batch' ? number :
|
||||
Q extends 'stats_counters' ? Counters :
|
||||
Q extends 'stats_lines' ? StatsCharts :
|
||||
Q extends 'stats_line' ? StatsChart :
|
||||
Q extends 'stats_counters' ? stats.Counters :
|
||||
Q extends 'stats_lines' ? stats.LineCharts :
|
||||
Q extends 'stats_line' ? stats.LineChart :
|
||||
Q extends 'blocks' ? BlocksResponse :
|
||||
Q extends 'block' ? Block :
|
||||
Q extends 'block_txs' ? BlockTransactionsResponse :
|
||||
@@ -986,7 +987,7 @@ Q extends 'contract' ? SmartContract :
|
||||
Q extends 'contract_solidityscan_report' ? SolidityscanReport :
|
||||
Q extends 'verified_contracts' ? VerifiedContractsResponse :
|
||||
Q extends 'verified_contracts_counters' ? VerifiedContractsCounters :
|
||||
Q extends 'visualize_sol2uml' ? VisualizedContract :
|
||||
Q extends 'visualize_sol2uml' ? visualizer.VisualizeResponse :
|
||||
Q extends 'contract_verification_config' ? SmartContractVerificationConfig :
|
||||
Q extends 'withdrawals' ? WithdrawalsResponse :
|
||||
Q extends 'withdrawals_counters' ? WithdrawalsCounters :
|
||||
|
||||
@@ -14,6 +14,7 @@ const defaultOptions: UseToastOptions & { toastComponent?: React.FC<ToastProps>
|
||||
containerStyle: {
|
||||
margin: 8,
|
||||
},
|
||||
variant: 'subtle',
|
||||
};
|
||||
|
||||
export default function useToastModified() {
|
||||
|
||||
@@ -49,6 +49,7 @@ const OG_TYPE_DICT: Record<Route['pathname'], OGPageType> = {
|
||||
|
||||
// service routes, added only to make typescript happy
|
||||
'/login': 'Regular page',
|
||||
'/sprite': 'Regular page',
|
||||
'/api/metrics': 'Regular page',
|
||||
'/api/log': 'Regular page',
|
||||
'/api/media-type': 'Regular page',
|
||||
@@ -56,6 +57,7 @@ const OG_TYPE_DICT: Record<Route['pathname'], OGPageType> = {
|
||||
'/api/csrf': 'Regular page',
|
||||
'/api/healthz': 'Regular page',
|
||||
'/api/config': 'Regular page',
|
||||
'/api/sprite': 'Regular page',
|
||||
'/auth/auth0': 'Regular page',
|
||||
'/auth/unverified-email': 'Regular page',
|
||||
};
|
||||
|
||||
@@ -53,6 +53,7 @@ const TEMPLATE_MAP: Record<Route['pathname'], string> = {
|
||||
|
||||
// service routes, added only to make typescript happy
|
||||
'/login': DEFAULT_TEMPLATE,
|
||||
'/sprite': DEFAULT_TEMPLATE,
|
||||
'/api/metrics': DEFAULT_TEMPLATE,
|
||||
'/api/log': DEFAULT_TEMPLATE,
|
||||
'/api/media-type': DEFAULT_TEMPLATE,
|
||||
@@ -60,6 +61,7 @@ const TEMPLATE_MAP: Record<Route['pathname'], string> = {
|
||||
'/api/csrf': DEFAULT_TEMPLATE,
|
||||
'/api/healthz': DEFAULT_TEMPLATE,
|
||||
'/api/config': DEFAULT_TEMPLATE,
|
||||
'/api/sprite': DEFAULT_TEMPLATE,
|
||||
'/auth/auth0': DEFAULT_TEMPLATE,
|
||||
'/auth/unverified-email': DEFAULT_TEMPLATE,
|
||||
};
|
||||
|
||||
@@ -49,13 +49,15 @@ const TEMPLATE_MAP: Record<Route['pathname'], string> = {
|
||||
|
||||
// service routes, added only to make typescript happy
|
||||
'/login': '%network_name% login',
|
||||
'/sprite': '%network_name% SVG sprite',
|
||||
'/api/metrics': '%network_name% node API prometheus metrics',
|
||||
'/api/log': '%network_name% node API request log',
|
||||
'/api/media-type': '%network_name% node API media type',
|
||||
'/api/proxy': '%network_name% node API proxy',
|
||||
'/api/csrf': '%network_name% node API CSRF token',
|
||||
'/api/healthz': '%network_name% node API health check',
|
||||
'/api/config': '%network_name% node API health check',
|
||||
'/api/config': '%network_name% node API app config',
|
||||
'/api/sprite': '%network_name% node API SVG sprite content',
|
||||
'/auth/auth0': '%network_name% authentication',
|
||||
'/auth/unverified-email': '%network_name% unverified email',
|
||||
};
|
||||
|
||||
@@ -47,13 +47,15 @@ export const PAGE_TYPE_DICT: Record<Route['pathname'], string> = {
|
||||
|
||||
// service routes, added only to make typescript happy
|
||||
'/login': 'Login',
|
||||
'/sprite': 'Sprite',
|
||||
'/api/metrics': 'Node API: Prometheus metrics',
|
||||
'/api/log': 'Node API: Request log',
|
||||
'/api/media-type': 'Node API: Media type',
|
||||
'/api/proxy': 'Node API: Proxy',
|
||||
'/api/csrf': 'Node API: CSRF token',
|
||||
'/api/healthz': 'Node API: Health check',
|
||||
'/api/config': 'Node API: Health check',
|
||||
'/api/config': 'Node API: App config',
|
||||
'/api/sprite': 'Node API: SVG sprite content',
|
||||
'/auth/auth0': 'Auth',
|
||||
'/auth/unverified-email': 'Unverified email',
|
||||
};
|
||||
|
||||
@@ -1,128 +1,161 @@
|
||||
export const averageGasPrice = {
|
||||
import type * as stats from '@blockscout/stats-types';
|
||||
|
||||
export const averageGasPrice: stats.LineChart = {
|
||||
chart: [
|
||||
{
|
||||
date: '2023-12-22',
|
||||
value: '37.7804422597599',
|
||||
is_approximate: false,
|
||||
},
|
||||
{
|
||||
date: '2023-12-23',
|
||||
value: '25.84889883009387',
|
||||
is_approximate: false,
|
||||
},
|
||||
{
|
||||
date: '2023-12-24',
|
||||
value: '25.818463227198574',
|
||||
is_approximate: false,
|
||||
},
|
||||
{
|
||||
date: '2023-12-25',
|
||||
value: '26.045513050051298',
|
||||
is_approximate: false,
|
||||
},
|
||||
{
|
||||
date: '2023-12-26',
|
||||
value: '21.42600692652399',
|
||||
is_approximate: false,
|
||||
},
|
||||
{
|
||||
date: '2023-12-27',
|
||||
value: '31.066730409846656',
|
||||
is_approximate: false,
|
||||
},
|
||||
{
|
||||
date: '2023-12-28',
|
||||
value: '33.63955781902089',
|
||||
is_approximate: false,
|
||||
},
|
||||
{
|
||||
date: '2023-12-29',
|
||||
value: '28.064736756058384',
|
||||
is_approximate: false,
|
||||
},
|
||||
{
|
||||
date: '2023-12-30',
|
||||
value: '23.074500869678175',
|
||||
is_approximate: false,
|
||||
},
|
||||
{
|
||||
date: '2023-12-31',
|
||||
value: '17.651005734615133',
|
||||
is_approximate: false,
|
||||
},
|
||||
{
|
||||
date: '2024-01-01',
|
||||
value: '14.906085174476441',
|
||||
is_approximate: false,
|
||||
},
|
||||
{
|
||||
date: '2024-01-02',
|
||||
value: '22.28459059038656',
|
||||
is_approximate: false,
|
||||
},
|
||||
{
|
||||
date: '2024-01-03',
|
||||
value: '39.8311646806592',
|
||||
is_approximate: false,
|
||||
},
|
||||
{
|
||||
date: '2024-01-04',
|
||||
value: '26.09989322256083',
|
||||
is_approximate: false,
|
||||
},
|
||||
{
|
||||
date: '2024-01-05',
|
||||
value: '22.821996688111998',
|
||||
is_approximate: false,
|
||||
},
|
||||
{
|
||||
date: '2024-01-06',
|
||||
value: '20.32680041262083',
|
||||
is_approximate: false,
|
||||
},
|
||||
{
|
||||
date: '2024-01-07',
|
||||
value: '32.535045831809704',
|
||||
is_approximate: false,
|
||||
},
|
||||
{
|
||||
date: '2024-01-08',
|
||||
value: '27.443477102139482',
|
||||
is_approximate: false,
|
||||
},
|
||||
{
|
||||
date: '2024-01-09',
|
||||
value: '20.7911332558055',
|
||||
is_approximate: false,
|
||||
},
|
||||
{
|
||||
date: '2024-01-10',
|
||||
value: '42.10740192523919',
|
||||
is_approximate: false,
|
||||
},
|
||||
{
|
||||
date: '2024-01-11',
|
||||
value: '35.75215680343582',
|
||||
is_approximate: false,
|
||||
},
|
||||
{
|
||||
date: '2024-01-12',
|
||||
value: '27.430414798093253',
|
||||
is_approximate: false,
|
||||
},
|
||||
{
|
||||
date: '2024-01-13',
|
||||
value: '20.170934096589875',
|
||||
is_approximate: false,
|
||||
},
|
||||
{
|
||||
date: '2024-01-14',
|
||||
value: '38.79660984371034',
|
||||
is_approximate: false,
|
||||
},
|
||||
{
|
||||
date: '2024-01-15',
|
||||
value: '26.140740484554204',
|
||||
is_approximate: false,
|
||||
},
|
||||
{
|
||||
date: '2024-01-16',
|
||||
value: '36.708543184194156',
|
||||
is_approximate: false,
|
||||
},
|
||||
{
|
||||
date: '2024-01-17',
|
||||
value: '40.325438794298876',
|
||||
is_approximate: false,
|
||||
},
|
||||
{
|
||||
date: '2024-01-18',
|
||||
value: '37.55145309930694',
|
||||
is_approximate: false,
|
||||
},
|
||||
{
|
||||
date: '2024-01-19',
|
||||
value: '33.271450114434664',
|
||||
is_approximate: false,
|
||||
},
|
||||
{
|
||||
date: '2024-01-20',
|
||||
value: '19.303304377685638',
|
||||
is_approximate: false,
|
||||
},
|
||||
{
|
||||
date: '2024-01-21',
|
||||
value: '14.375908594704976',
|
||||
is_approximate: false,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
export const base = {
|
||||
import type * as stats from '@blockscout/stats-types';
|
||||
|
||||
export const base: stats.LineCharts = {
|
||||
sections: [
|
||||
{
|
||||
id: 'accounts',
|
||||
@@ -8,19 +10,19 @@ export const base = {
|
||||
id: 'accountsGrowth',
|
||||
title: 'Accounts growth',
|
||||
description: 'Cumulative accounts number per period',
|
||||
units: null,
|
||||
units: undefined,
|
||||
},
|
||||
{
|
||||
id: 'activeAccounts',
|
||||
title: 'Active accounts',
|
||||
description: 'Active accounts number per period',
|
||||
units: null,
|
||||
units: undefined,
|
||||
},
|
||||
{
|
||||
id: 'newAccounts',
|
||||
title: 'New accounts',
|
||||
description: 'New accounts number per day',
|
||||
units: null,
|
||||
units: undefined,
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -38,7 +40,7 @@ export const base = {
|
||||
id: 'newTxns',
|
||||
title: 'New transactions',
|
||||
description: 'New transactions number',
|
||||
units: null,
|
||||
units: undefined,
|
||||
},
|
||||
{
|
||||
id: 'txnsFee',
|
||||
@@ -50,13 +52,13 @@ export const base = {
|
||||
id: 'txnsGrowth',
|
||||
title: 'Transactions growth',
|
||||
description: 'Cumulative transactions number',
|
||||
units: null,
|
||||
units: undefined,
|
||||
},
|
||||
{
|
||||
id: 'txnsSuccessRate',
|
||||
title: 'Transactions success rate',
|
||||
description: 'Successful transactions rate per day',
|
||||
units: null,
|
||||
units: undefined,
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -80,7 +82,7 @@ export const base = {
|
||||
id: 'newBlocks',
|
||||
title: 'New blocks',
|
||||
description: 'New blocks number',
|
||||
units: null,
|
||||
units: undefined,
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -92,7 +94,7 @@ export const base = {
|
||||
id: 'newNativeCoinTransfers',
|
||||
title: 'New ETH transfers',
|
||||
description: 'New token transfers number for the period',
|
||||
units: null,
|
||||
units: undefined,
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -104,7 +106,7 @@ export const base = {
|
||||
id: 'averageGasLimit',
|
||||
title: 'Average gas limit',
|
||||
description: 'Average gas limit per block for the period',
|
||||
units: null,
|
||||
units: undefined,
|
||||
},
|
||||
{
|
||||
id: 'averageGasPrice',
|
||||
@@ -116,7 +118,7 @@ export const base = {
|
||||
id: 'gasUsedGrowth',
|
||||
title: 'Gas used growth',
|
||||
description: 'Cumulative gas used for the period',
|
||||
units: null,
|
||||
units: undefined,
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -128,13 +130,13 @@ export const base = {
|
||||
id: 'newVerifiedContracts',
|
||||
title: 'New verified contracts',
|
||||
description: 'New verified contracts number for the period',
|
||||
units: null,
|
||||
units: undefined,
|
||||
},
|
||||
{
|
||||
id: 'verifiedContractsGrowth',
|
||||
title: 'Verified contracts growth',
|
||||
description: 'Cumulative number verified contracts for the period',
|
||||
units: null,
|
||||
units: undefined,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -245,6 +245,16 @@ export const login: GetServerSideProps<Props> = async(context) => {
|
||||
return base(context);
|
||||
};
|
||||
|
||||
export const dev: GetServerSideProps<Props> = async(context) => {
|
||||
if (!config.app.isDev) {
|
||||
return {
|
||||
notFound: true,
|
||||
};
|
||||
}
|
||||
|
||||
return base(context);
|
||||
};
|
||||
|
||||
export const publicTagsSubmit: GetServerSideProps<Props> = async(context) => {
|
||||
|
||||
if (!config.features.publicTagsSubmission.isEnabled) {
|
||||
|
||||
@@ -22,6 +22,7 @@ declare module "nextjs-routes" {
|
||||
| StaticRoute<"/api/media-type">
|
||||
| StaticRoute<"/api/metrics">
|
||||
| StaticRoute<"/api/proxy">
|
||||
| StaticRoute<"/api/sprite">
|
||||
| StaticRoute<"/api-docs">
|
||||
| DynamicRoute<"/apps/[id]", { "id": string }>
|
||||
| StaticRoute<"/apps">
|
||||
@@ -48,6 +49,7 @@ declare module "nextjs-routes" {
|
||||
| StaticRoute<"/output-roots">
|
||||
| StaticRoute<"/public-tags/submit">
|
||||
| StaticRoute<"/search-results">
|
||||
| StaticRoute<"/sprite">
|
||||
| StaticRoute<"/stats">
|
||||
| DynamicRoute<"/token/[hash]", { "hash": string }>
|
||||
| DynamicRoute<"/token/[hash]/instance/[id]", { "hash": string; "id": string }>
|
||||
|
||||
@@ -35,7 +35,9 @@
|
||||
"monitoring:grafana:local": "docker run -d -p 4000:3000 --name=blockscout_grafana --user $(id -u) --volume $(pwd)/grafana:/var/lib/grafana grafana/grafana-enterprise"
|
||||
},
|
||||
"dependencies": {
|
||||
"@blockscout/bens-types": "v1.3.0-beta",
|
||||
"@blockscout/bens-types": "1.3.4",
|
||||
"@blockscout/stats-types": "1.6.0",
|
||||
"@blockscout/visualizer-types": "0.2.0",
|
||||
"@chakra-ui/react": "2.7.1",
|
||||
"@chakra-ui/theme-tools": "^2.0.18",
|
||||
"@emotion/react": "^11.10.4",
|
||||
@@ -67,13 +69,13 @@
|
||||
"chakra-react-select": "^4.4.3",
|
||||
"crypto-js": "^4.2.0",
|
||||
"d3": "^7.6.1",
|
||||
"dappscout-iframe": "0.2.1",
|
||||
"dappscout-iframe": "0.2.2",
|
||||
"dayjs": "^1.11.5",
|
||||
"dom-to-image": "^2.6.0",
|
||||
"focus-visible": "^5.2.0",
|
||||
"framer-motion": "^6.5.1",
|
||||
"getit-sdk": "^1.0.4",
|
||||
"gradient-avatar": "^1.0.2",
|
||||
"gradient-avatar": "git+https://github.com/blockscout/gradient-avatar.git",
|
||||
"graphiql": "^2.2.0",
|
||||
"graphql": "^16.8.1",
|
||||
"graphql-ws": "^5.11.3",
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import fs from 'fs';
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import path from 'path';
|
||||
|
||||
import config from 'configs/app';
|
||||
|
||||
const ROOT_DIR = './icons';
|
||||
const NAME_PREFIX = ROOT_DIR.replace('./', '') + '/';
|
||||
|
||||
interface IconInfo {
|
||||
name: string;
|
||||
fileSize: number;
|
||||
}
|
||||
|
||||
const getIconName = (filePath: string) => filePath.replace(NAME_PREFIX, '').replace('.svg', '');
|
||||
|
||||
function collectIconNames(dir: string) {
|
||||
const files = fs.readdirSync(dir, { withFileTypes: true });
|
||||
let icons: Array<IconInfo> = [];
|
||||
|
||||
files.forEach((file) => {
|
||||
const filePath = path.join(dir, file.name);
|
||||
const stats = fs.statSync(filePath);
|
||||
|
||||
file.name.endsWith('.svg') && icons.push({
|
||||
name: getIconName(filePath),
|
||||
fileSize: stats.size,
|
||||
});
|
||||
|
||||
if (file.isDirectory()) {
|
||||
icons = [ ...icons, ...collectIconNames(filePath) ];
|
||||
}
|
||||
});
|
||||
|
||||
return icons;
|
||||
}
|
||||
|
||||
export default async function spriteHandler(req: NextApiRequest, res: NextApiResponse) {
|
||||
|
||||
if (!config.app.isDev) {
|
||||
return res.status(404).json({ error: 'Not found' });
|
||||
}
|
||||
|
||||
const icons = collectIconNames(ROOT_DIR);
|
||||
|
||||
res.status(200).json({
|
||||
icons,
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import type { NextPage } from 'next';
|
||||
import React from 'react';
|
||||
|
||||
import PageNextJs from 'nextjs/PageNextJs';
|
||||
|
||||
import Sprite from 'ui/pages/Sprite';
|
||||
|
||||
const Page: NextPage = () => {
|
||||
return (
|
||||
<PageNextJs pathname="/sprite">
|
||||
<Sprite/>
|
||||
</PageNextJs>
|
||||
);
|
||||
};
|
||||
|
||||
export default Page;
|
||||
|
||||
export { dev as getServerSideProps } from 'nextjs/getServerSideProps';
|
||||
@@ -0,0 +1,52 @@
|
||||
import type { TestFixture, Page } from '@playwright/test';
|
||||
import _isEqual from 'lodash/isEqual';
|
||||
import type { PublicRpcSchema } from 'viem';
|
||||
|
||||
import { getEnvValue } from 'configs/app/utils';
|
||||
|
||||
type Params = PublicRpcSchema[number];
|
||||
|
||||
export type MockRpcResponseFixture = (params: Params) => Promise<void>;
|
||||
|
||||
// WIP
|
||||
const fixture: TestFixture<MockRpcResponseFixture, { page: Page }> = async({ page }, use) => {
|
||||
await use(async({ Method, ReturnType }) => {
|
||||
const rpcUrl = getEnvValue('NEXT_PUBLIC_NETWORK_RPC_URL');
|
||||
|
||||
if (!rpcUrl) {
|
||||
return;
|
||||
}
|
||||
|
||||
await page.route(rpcUrl, (route) => {
|
||||
const method = route.request().method();
|
||||
|
||||
if (method !== 'POST') {
|
||||
route.continue();
|
||||
return;
|
||||
}
|
||||
|
||||
const json = route.request().postDataJSON();
|
||||
const id = json?.id;
|
||||
|
||||
const payload = {
|
||||
id,
|
||||
jsonrpc: '2.0',
|
||||
method: Method,
|
||||
// TODO: add params to match actual payload
|
||||
};
|
||||
|
||||
if (_isEqual(json, payload) && id !== undefined) {
|
||||
return route.fulfill({
|
||||
status: 200,
|
||||
body: JSON.stringify({
|
||||
id,
|
||||
jsonrpc: '2.0',
|
||||
result: ReturnType,
|
||||
}),
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export default fixture;
|
||||
@@ -8,6 +8,7 @@ import * as mockConfigResponse from './fixtures/mockConfigResponse';
|
||||
import * as mockContractReadResponse from './fixtures/mockContractReadResponse';
|
||||
import * as mockEnvs from './fixtures/mockEnvs';
|
||||
import * as mockFeatures from './fixtures/mockFeatures';
|
||||
import * as mockRpcResponse from './fixtures/mockRpcResponse';
|
||||
import * as mockTextAd from './fixtures/mockTextAd';
|
||||
import * as render from './fixtures/render';
|
||||
import * as socketServer from './fixtures/socketServer';
|
||||
@@ -20,6 +21,7 @@ interface Fixtures {
|
||||
mockContractReadResponse: mockContractReadResponse.MockContractReadResponseFixture;
|
||||
mockEnvs: mockEnvs.MockEnvsFixture;
|
||||
mockFeatures: mockFeatures.MockFeaturesFixture;
|
||||
mockRpcResponse: mockRpcResponse.MockRpcResponseFixture;
|
||||
createSocket: socketServer.CreateSocketFixture;
|
||||
injectMetaMaskProvider: injectMetaMaskProvider.InjectMetaMaskProvider;
|
||||
mockTextAd: mockTextAd.MockTextAdFixture;
|
||||
@@ -33,6 +35,7 @@ const test = base.extend<Fixtures>({
|
||||
mockContractReadResponse: mockContractReadResponse.default,
|
||||
mockEnvs: mockEnvs.default,
|
||||
mockFeatures: mockFeatures.default,
|
||||
mockRpcResponse: mockRpcResponse.default,
|
||||
// FIXME: for some reason Playwright does not intercept requests to text ad provider when running multiple tests in parallel
|
||||
// even if we have a global request interceptor (maybe it is related to service worker issue, maybe not)
|
||||
// so we have to inject mockTextAd fixture in each test and mock the response where it is needed
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
| "arrows/north-east"
|
||||
| "arrows/south-east"
|
||||
| "arrows/up-down"
|
||||
| "arrows/up-head"
|
||||
| "beta_xs"
|
||||
| "beta"
|
||||
| "blob"
|
||||
@@ -30,10 +31,10 @@
|
||||
| "clock"
|
||||
| "coins/bitcoin"
|
||||
| "collection"
|
||||
| "contract_verified"
|
||||
| "contract"
|
||||
| "contracts_verified"
|
||||
| "contracts"
|
||||
| "contracts/regular_many"
|
||||
| "contracts/regular"
|
||||
| "contracts/verified_many"
|
||||
| "contracts/verified"
|
||||
| "copy"
|
||||
| "cross"
|
||||
| "delete"
|
||||
@@ -59,7 +60,6 @@
|
||||
| "files/sol"
|
||||
| "files/yul"
|
||||
| "filter"
|
||||
| "finalized"
|
||||
| "flame"
|
||||
| "games"
|
||||
| "gas_xl"
|
||||
@@ -99,7 +99,7 @@
|
||||
| "publictags"
|
||||
| "qr_code"
|
||||
| "refresh"
|
||||
| "repeat_arrow"
|
||||
| "repeat"
|
||||
| "restAPI"
|
||||
| "rocket_xl"
|
||||
| "rocket"
|
||||
@@ -146,14 +146,13 @@
|
||||
| "transactions"
|
||||
| "txn_batches_slim"
|
||||
| "txn_batches"
|
||||
| "unfinalized"
|
||||
| "uniswap"
|
||||
| "up"
|
||||
| "user_op_slim"
|
||||
| "user_op"
|
||||
| "validator"
|
||||
| "verification-steps/finalized"
|
||||
| "verification-steps/unfinalized"
|
||||
| "verified"
|
||||
| "verify-contract"
|
||||
| "wallet"
|
||||
| "wallets/coinbase"
|
||||
| "wallets/metamask"
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { Counter, HomeStats, StatsChartsSection } from 'types/api/stats';
|
||||
import type * as stats from '@blockscout/stats-types';
|
||||
import type { HomeStats } from 'types/api/stats';
|
||||
|
||||
export const HOMEPAGE_STATS: HomeStats = {
|
||||
average_block_time: 14346,
|
||||
@@ -41,7 +42,7 @@ export const HOMEPAGE_STATS: HomeStats = {
|
||||
tvl: '1767425.102766552',
|
||||
};
|
||||
|
||||
export const STATS_CHARTS_SECTION: StatsChartsSection = {
|
||||
export const STATS_CHARTS_SECTION: stats.LineChartSection = {
|
||||
id: 'placeholder',
|
||||
title: 'Placeholder',
|
||||
charts: [
|
||||
@@ -61,13 +62,13 @@ export const STATS_CHARTS_SECTION: StatsChartsSection = {
|
||||
id: 'chart_2',
|
||||
title: 'New transactions',
|
||||
description: 'New transactions number',
|
||||
units: null,
|
||||
units: undefined,
|
||||
},
|
||||
{
|
||||
id: 'chart_3',
|
||||
title: 'Transactions growth',
|
||||
description: 'Cumulative transactions number',
|
||||
units: null,
|
||||
units: undefined,
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -76,7 +77,7 @@ export const STATS_CHARTS = {
|
||||
sections: [ STATS_CHARTS_SECTION ],
|
||||
};
|
||||
|
||||
export const STATS_COUNTER: Counter = {
|
||||
export const STATS_COUNTER: stats.Counter = {
|
||||
id: 'stub',
|
||||
value: '9074405',
|
||||
title: 'Placeholder Counter',
|
||||
|
||||
@@ -34,39 +34,3 @@ export interface GasPriceInfo {
|
||||
base_fee: number | null;
|
||||
priority_fee: number | null;
|
||||
}
|
||||
|
||||
export type Counters = {
|
||||
counters: Array<Counter>;
|
||||
}
|
||||
|
||||
export type Counter = {
|
||||
id: string;
|
||||
value: string;
|
||||
title: string;
|
||||
description?: string;
|
||||
units: string;
|
||||
}
|
||||
|
||||
export type StatsCharts = {
|
||||
sections: Array<StatsChartsSection>;
|
||||
}
|
||||
|
||||
export type StatsChartsSection = {
|
||||
id: string;
|
||||
title: string;
|
||||
charts: Array<StatsChartInfo>;
|
||||
}
|
||||
|
||||
export type StatsChartInfo = {
|
||||
id: string;
|
||||
title: string;
|
||||
description: string;
|
||||
units: string | null;
|
||||
}
|
||||
|
||||
export type StatsChart = { chart: Array<StatsChartItem> };
|
||||
|
||||
export type StatsChartItem = {
|
||||
date: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
export interface VisualizedContract {
|
||||
png: string | null;
|
||||
svg: string | null;
|
||||
}
|
||||
@@ -222,7 +222,7 @@ const ContractMethodForm = ({ data, attempt, onSubmit, onReset, isOpen }: Props)
|
||||
onClick={ onReset }
|
||||
ml={ 1 }
|
||||
>
|
||||
<IconSvg name="repeat_arrow" boxSize={ 5 } mr={ 1 }/>
|
||||
<IconSvg name="repeat" boxSize={ 5 } mr={ 1 }/>
|
||||
Reset
|
||||
</Button>
|
||||
) }
|
||||
|
||||
@@ -86,33 +86,33 @@ const BlocksTableItem = ({ data, isLoading, enableTimeIncrement }: Props) => {
|
||||
</Skeleton>
|
||||
) : data.tx_count }
|
||||
</Td>
|
||||
<Td fontSize="sm">
|
||||
<Skeleton isLoaded={ !isLoading } display="inline-block">{ BigNumber(data.gas_used || 0).toFormat() }</Skeleton>
|
||||
<Flex mt={ 2 }>
|
||||
<Tooltip label={ isLoading ? undefined : 'Gas Used %' }>
|
||||
<Box>
|
||||
<Utilization
|
||||
colorScheme="gray"
|
||||
value={ BigNumber(data.gas_used || 0).dividedBy(BigNumber(data.gas_limit)).toNumber() }
|
||||
isLoading={ isLoading }
|
||||
/>
|
||||
</Box>
|
||||
</Tooltip>
|
||||
{ data.gas_target_percentage && (
|
||||
<>
|
||||
<TextSeparator color={ separatorColor } mx={ 1 }/>
|
||||
<GasUsedToTargetRatio value={ data.gas_target_percentage } isLoading={ isLoading }/>
|
||||
</>
|
||||
) }
|
||||
</Flex>
|
||||
</Td>
|
||||
{ !isRollup && !config.UI.views.block.hiddenFields?.total_reward && (
|
||||
<Td fontSize="sm">
|
||||
<Skeleton isLoaded={ !isLoading } display="inline-block">{ BigNumber(data.gas_used || 0).toFormat() }</Skeleton>
|
||||
<Flex mt={ 2 }>
|
||||
<Tooltip label={ isLoading ? undefined : 'Gas Used %' }>
|
||||
<Box>
|
||||
<Utilization
|
||||
colorScheme="gray"
|
||||
value={ BigNumber(data.gas_used || 0).dividedBy(BigNumber(data.gas_limit)).toNumber() }
|
||||
isLoading={ isLoading }
|
||||
/>
|
||||
</Box>
|
||||
</Tooltip>
|
||||
{ data.gas_target_percentage && (
|
||||
<>
|
||||
<TextSeparator color={ separatorColor } mx={ 1 }/>
|
||||
<GasUsedToTargetRatio value={ data.gas_target_percentage } isLoading={ isLoading }/>
|
||||
</>
|
||||
) }
|
||||
</Flex>
|
||||
<Skeleton isLoaded={ !isLoading } display="inline-block">
|
||||
{ totalReward.toFixed(8) }
|
||||
</Skeleton>
|
||||
</Td>
|
||||
) }
|
||||
<Td fontSize="sm">
|
||||
<Skeleton isLoaded={ !isLoading } display="inline-block">
|
||||
{ totalReward.toFixed(8) }
|
||||
</Skeleton>
|
||||
</Td>
|
||||
{ !isRollup && !config.UI.views.block.hiddenFields?.burnt_fees && (
|
||||
<Td fontSize="sm">
|
||||
<Flex alignItems="center" columnGap={ 2 }>
|
||||
|
||||
@@ -68,7 +68,7 @@ const ChainIndicatorItem = ({ id, title, value, valueDiff, icon, isSelected, onC
|
||||
|
||||
return (
|
||||
<Skeleton isLoaded={ !stats.isPlaceholderData } ml={ 3 } display="flex" alignItems="center" color={ diffColor }>
|
||||
<IconSvg name="up" boxSize={ 5 } mr={ 1 } transform={ diff < 0 ? 'rotate(180deg)' : 'rotate(0)' }/>
|
||||
<IconSvg name="arrows/up-head" boxSize={ 5 } mr={ 1 } transform={ diff < 0 ? 'rotate(180deg)' : 'rotate(0)' }/>
|
||||
<Text color={ diffColor } fontWeight={ 600 }>{ diff }%</Text>
|
||||
</Skeleton>
|
||||
);
|
||||
|
||||
@@ -77,7 +77,7 @@ const ChainIndicators = () => {
|
||||
|
||||
return (
|
||||
<Skeleton isLoaded={ !statsQueryResult.isPlaceholderData } display="flex" alignItems="center" color={ diffColor } ml={ 2 }>
|
||||
<IconSvg name="up" boxSize={ 5 } mr={ 1 } transform={ diff < 0 ? 'rotate(180deg)' : 'rotate(0)' }/>
|
||||
<IconSvg name="arrows/up-head" boxSize={ 5 } mr={ 1 } transform={ diff < 0 ? 'rotate(180deg)' : 'rotate(0)' }/>
|
||||
<Text color={ diffColor } fontWeight={ 600 }>{ diff }%</Text>
|
||||
</Skeleton>
|
||||
);
|
||||
|
||||
@@ -77,7 +77,7 @@ const AppSecurityReport = ({
|
||||
<Text fontWeight="500" fontSize="xs" mb={ 2 } variant="secondary">Smart contracts info</Text>
|
||||
<Flex alignItems="center" justifyContent="space-between" py={ 1.5 }>
|
||||
<Flex alignItems="center">
|
||||
<IconSvg name="contracts_verified" boxSize={ 5 } color="green.500" mr={ 1 }/>
|
||||
<IconSvg name="contracts/verified_many" boxSize={ 5 } color="green.500" mr={ 1 }/>
|
||||
<Text>Verified contracts</Text>
|
||||
</Flex>
|
||||
<Link fontSize="sm" fontWeight="500" onClick={ showAllContracts }>
|
||||
|
||||
@@ -179,7 +179,7 @@ const MarketplaceAppCard = ({
|
||||
showContractList={ showContractList }
|
||||
isLoading={ isLoading }
|
||||
source="Discovery view"
|
||||
popoverPlacement={ isMobile ? 'bottom-end' : 'bottom-start' }
|
||||
popoverPlacement={ isMobile ? 'bottom-end' : 'left' }
|
||||
position="absolute"
|
||||
right={{ base: 3, md: 5 }}
|
||||
top={{ base: '10px', md: 5 }}
|
||||
|
||||
@@ -191,7 +191,7 @@ const MarketplaceAppModal = ({
|
||||
mb={ 6 }
|
||||
>
|
||||
<Flex alignItems="center" gap={ 2 } flexWrap="wrap">
|
||||
<IconSvg name="contracts_verified" boxSize={ 5 } color="green.500"/>
|
||||
<IconSvg name="contracts/verified_many" boxSize={ 5 } color="green.500"/>
|
||||
<Text>Verified contracts</Text>
|
||||
<Text fontWeight="500">
|
||||
{ securityReport?.overallInfo.verifiedNumber ?? 0 } of { securityReport?.overallInfo.totalContractsNumber ?? 0 }
|
||||
|
||||
@@ -53,7 +53,7 @@ const NameDomainsActionBar = ({
|
||||
minW={{ base: 'auto', lg: '250px' }}
|
||||
size="xs"
|
||||
onChange={ onSearchChange }
|
||||
placeholder="Search by name"
|
||||
placeholder="Search by name or address"
|
||||
initialValue={ searchTerm }
|
||||
isLoading={ isInitialLoading }
|
||||
/>
|
||||
|
||||
@@ -64,21 +64,12 @@ const GasTracker = () => {
|
||||
</Flex>
|
||||
);
|
||||
|
||||
const content = (() => {
|
||||
const snippets = (() => {
|
||||
if (!isPlaceholderData && data?.gas_prices?.slow === null && data?.gas_prices.average === null && data.gas_prices.fast === null) {
|
||||
return <Alert status="warning">No data available yet</Alert>;
|
||||
return <Alert status="warning">No recent data available</Alert>;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{ data?.gas_prices && <GasTrackerPrices prices={ data.gas_prices } isLoading={ isLoading }/> }
|
||||
{ config.features.stats.isEnabled && (
|
||||
<Box mt={ 12 }>
|
||||
<GasTrackerChart/>
|
||||
</Box>
|
||||
) }
|
||||
</>
|
||||
);
|
||||
return data?.gas_prices ? <GasTrackerPrices prices={ data.gas_prices } isLoading={ isLoading }/> : null;
|
||||
})();
|
||||
|
||||
return (
|
||||
@@ -88,7 +79,12 @@ const GasTracker = () => {
|
||||
secondRow={ titleSecondRow }
|
||||
withTextAd
|
||||
/>
|
||||
{ content }
|
||||
{ snippets }
|
||||
{ config.features.stats.isEnabled && (
|
||||
<Box mt={ 12 }>
|
||||
<GasTrackerChart/>
|
||||
</Box>
|
||||
) }
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -33,9 +33,9 @@ const testFn: Parameters<typeof test>[1] = async({ render, mockConfigResponse, m
|
||||
await expect(component).toHaveScreenshot();
|
||||
};
|
||||
|
||||
test('base view +@dark-mode', testFn);
|
||||
test.fixme('base view +@dark-mode', testFn);
|
||||
|
||||
test.describe('mobile', () => {
|
||||
test.use({ viewport: devices['iPhone 13 Pro'].viewport });
|
||||
test('base view', testFn);
|
||||
test.fixme('base view', testFn);
|
||||
});
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
import { Flex, Box, Tooltip, useClipboard, useColorModeValue } from '@chakra-ui/react';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import React from 'react';
|
||||
|
||||
import type { StaticRoute } from 'nextjs-routes';
|
||||
import { route } from 'nextjs-routes';
|
||||
|
||||
import useFetch from 'lib/hooks/useFetch';
|
||||
import ContentLoader from 'ui/shared/ContentLoader';
|
||||
import DataFetchAlert from 'ui/shared/DataFetchAlert';
|
||||
import EmptySearchResult from 'ui/shared/EmptySearchResult';
|
||||
import FilterInput from 'ui/shared/filters/FilterInput';
|
||||
import type { IconName } from 'ui/shared/IconSvg';
|
||||
import IconSvg from 'ui/shared/IconSvg';
|
||||
import PageTitle from 'ui/shared/Page/PageTitle';
|
||||
|
||||
const formatFileSize = (fileSizeInBytes: number) => `${ (fileSizeInBytes / 1_024).toFixed(2) } Kb`;
|
||||
|
||||
interface IconInfo {
|
||||
name: string;
|
||||
fileSize: number;
|
||||
}
|
||||
|
||||
const Item = ({ name, fileSize, bgColor }: IconInfo & { bgColor: string }) => {
|
||||
const { hasCopied, onCopy } = useClipboard(name, 1000);
|
||||
const [ copied, setCopied ] = React.useState(false);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (hasCopied) {
|
||||
setCopied(true);
|
||||
} else {
|
||||
setCopied(false);
|
||||
}
|
||||
}, [ hasCopied ]);
|
||||
|
||||
return (
|
||||
<Flex
|
||||
flexDir="column"
|
||||
alignItems="center"
|
||||
whiteSpace="pre-wrap"
|
||||
wordBreak="break-word"
|
||||
maxW="100px"
|
||||
textAlign="center"
|
||||
onClick={ onCopy }
|
||||
cursor="pointer"
|
||||
>
|
||||
<IconSvg name={ name as IconName } boxSize="100px" bgColor={ bgColor } borderRadius="base"/>
|
||||
<Tooltip label={ copied ? 'Copied' : 'Copy to clipboard' } isOpen={ copied }>
|
||||
<Box fontWeight={ 500 } mt={ 2 }>{ name }</Box>
|
||||
</Tooltip>
|
||||
<Box color="text_secondary">{ formatFileSize(fileSize) }</Box>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
const Sprite = () => {
|
||||
const [ searchTerm, setSearchTerm ] = React.useState('');
|
||||
const bgColor = useColorModeValue('blackAlpha.100', 'whiteAlpha.100');
|
||||
|
||||
const fetch = useFetch();
|
||||
const { data, isFetching, isError } = useQuery({
|
||||
queryKey: [ 'sprite' ],
|
||||
queryFn: () => {
|
||||
const url = route({ pathname: '/node-api/sprite' as StaticRoute<'/api/sprite'>['pathname'] });
|
||||
return fetch<{ icons: Array<IconInfo> }, unknown>(url);
|
||||
},
|
||||
});
|
||||
|
||||
const content = (() => {
|
||||
if (isFetching) {
|
||||
return <ContentLoader/>;
|
||||
}
|
||||
|
||||
if (isError || !data || !('icons' in data)) {
|
||||
return <DataFetchAlert/>;
|
||||
}
|
||||
|
||||
const items = data.icons.filter((icon) => icon.name.includes(searchTerm));
|
||||
|
||||
if (items.length === 0) {
|
||||
return <EmptySearchResult text="No icons found"/>;
|
||||
}
|
||||
|
||||
return (
|
||||
<Flex flexWrap="wrap" fontSize="sm" columnGap={ 5 } rowGap={ 5 } justifyContent="flex-start">
|
||||
{ items.map((item) => <Item key={ item.name } { ...item } bgColor={ bgColor }/>) }
|
||||
</Flex>
|
||||
);
|
||||
})();
|
||||
|
||||
const total = React.useMemo(() => {
|
||||
if (!data || !('icons' in data)) {
|
||||
return;
|
||||
}
|
||||
return data?.icons.reduce((result, item) => {
|
||||
result.num++;
|
||||
result.fileSize += item.fileSize;
|
||||
return result;
|
||||
}, { num: 0, fileSize: 0 });
|
||||
}, [ data ]);
|
||||
|
||||
const searchInput = <FilterInput placeholder="Search by name..." onChange={ setSearchTerm } isLoading={ isFetching } minW={{ base: '100%', lg: '300px' }}/>;
|
||||
const totalEl = total ? <Box ml="auto">Items: { total.num } / Size: { formatFileSize(total.fileSize) }</Box> : null;
|
||||
|
||||
const contentAfter = (
|
||||
<>
|
||||
{ totalEl }
|
||||
{ searchInput }
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<PageTitle title="SVG sprite 🥤" contentAfter={ contentAfter }/>
|
||||
{ content }
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(Sprite);
|
||||
|
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 64 KiB |
|
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 75 KiB |
|
Before Width: | Height: | Size: 122 KiB After Width: | Height: | Size: 122 KiB |
@@ -20,7 +20,7 @@ function getBgColor(status?: AlertStatus) {
|
||||
}
|
||||
}
|
||||
|
||||
const Toast = ({ onClose, title, description, id, isClosable, status }: ToastProps) => {
|
||||
const Toast = ({ onClose, title, description, id, isClosable, status, icon }: ToastProps) => {
|
||||
|
||||
const ids = id ?
|
||||
{
|
||||
@@ -48,7 +48,7 @@ const Toast = ({ onClose, title, description, id, isClosable, status }: ToastPro
|
||||
maxWidth="400px"
|
||||
>
|
||||
<chakra.div flex="1" maxWidth="100%">
|
||||
{ title && <AlertTitle id={ ids?.title }>{ title }</AlertTitle> }
|
||||
{ title && <AlertTitle id={ ids?.title } display="flex" alignItems="center">{ icon }{ title }</AlertTitle> }
|
||||
{ description && (
|
||||
<AlertDescription id={ ids?.description } display="block">
|
||||
{ description }
|
||||
|
||||
@@ -198,7 +198,7 @@ const ChartWidget = ({ items, title, description, isLoading, className, isError,
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={ handleZoomResetClick }
|
||||
icon={ <IconSvg name="repeat_arrow" w={ 4 } h={ 4 }/> }
|
||||
icon={ <IconSvg name="repeat" w={ 4 } h={ 4 }/> }
|
||||
/>
|
||||
</Tooltip>
|
||||
|
||||
|
||||
@@ -71,7 +71,7 @@ const FullscreenChartModal = ({
|
||||
|
||||
{ !isZoomResetInitial && (
|
||||
<Button
|
||||
leftIcon={ <IconSvg name="repeat_arrow" w={ 4 } h={ 4 }/> }
|
||||
leftIcon={ <IconSvg name="repeat" w={ 4 } h={ 4 }/> }
|
||||
colorScheme="blue"
|
||||
gridColumn={ 2 }
|
||||
justifySelf="end"
|
||||
|
||||
@@ -63,7 +63,7 @@ const Icon = (props: IconProps) => {
|
||||
<span>
|
||||
<EntityBase.Icon
|
||||
{ ...props }
|
||||
name="contract_verified"
|
||||
name="contracts/verified"
|
||||
color="green.500"
|
||||
borderRadius={ 0 }
|
||||
/>
|
||||
@@ -77,7 +77,7 @@ const Icon = (props: IconProps) => {
|
||||
<span>
|
||||
<EntityBase.Icon
|
||||
{ ...props }
|
||||
name="contract"
|
||||
name="contracts/regular"
|
||||
borderRadius={ 0 }
|
||||
/>
|
||||
</span>
|
||||
|
||||
@@ -54,7 +54,7 @@ const Icon = dynamic(
|
||||
|
||||
// eslint-disable-next-line react/display-name
|
||||
return (props: IconProps) => {
|
||||
const svg = GradientAvatar(props.hash, props.size);
|
||||
const svg = GradientAvatar(props.hash, props.size, 'circle');
|
||||
return <div dangerouslySetInnerHTML={{ __html: svg }}/>;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ if (config.features.dataAvailability.isEnabled) {
|
||||
}
|
||||
|
||||
if (config.features.nameService.isEnabled) {
|
||||
searchCategories.push({ id: 'domain', title: 'Names' });
|
||||
searchCategories.unshift({ id: 'domain', title: 'Names' });
|
||||
}
|
||||
|
||||
export const searchItemTitles: Record<Category, { itemTitle: string; itemTitleShort: string }> = {
|
||||
|
||||
@@ -22,7 +22,7 @@ const VerificationStep = ({ step, isLast, isPassed, isPending }: Props) => {
|
||||
|
||||
return (
|
||||
<HStack gap={ 2 } color={ stepColor }>
|
||||
<IconSvg name={ isPassed ? 'finalized' : 'unfinalized' } boxSize={ 5 }/>
|
||||
<IconSvg name={ isPassed ? 'verification-steps/finalized' : 'verification-steps/unfinalized' } boxSize={ 5 }/>
|
||||
<Box color={ stepColor }>{ typeof step === 'string' ? step : step.content }</Box>
|
||||
{ !isLast && <IconSvg name="arrows/east" boxSize={ 5 }/> }
|
||||
</HStack>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { chakra, Tooltip, useColorModeValue } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
|
||||
import type * as visualizer from '@blockscout/visualizer-types';
|
||||
import type { SmartContract } from 'types/api/contract';
|
||||
|
||||
import type { ResourceError } from 'lib/api/resources';
|
||||
@@ -13,7 +14,7 @@ interface Props {
|
||||
addressHash: string;
|
||||
}
|
||||
|
||||
function composeSources(contract: SmartContract | undefined) {
|
||||
function composeSources(contract: SmartContract | undefined): visualizer.VisualizeStorageRequest['sources'] {
|
||||
if (!contract) {
|
||||
return {};
|
||||
}
|
||||
@@ -23,7 +24,7 @@ function composeSources(contract: SmartContract | undefined) {
|
||||
}, {});
|
||||
|
||||
return {
|
||||
[contract.file_path || 'index.sol']: contract.source_code,
|
||||
[contract.file_path || 'index.sol']: contract.source_code || '',
|
||||
...additionalSources,
|
||||
};
|
||||
}
|
||||
@@ -76,7 +77,7 @@ const Sol2UmlDiagram = ({ addressHash }: Props) => {
|
||||
return (
|
||||
<Tooltip label="Click on image to zoom" placement="top">
|
||||
<chakra.img
|
||||
src={ `data:image/svg+xml;base64,${ umlQuery.data.svg }` }
|
||||
src={ imgUrl }
|
||||
alt={ `Contract ${ contractQuery.data.name } UML diagram` }
|
||||
onClick={ handleClick }
|
||||
cursor="pointer"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Box, Grid, Heading, List, ListItem, Skeleton } from '@chakra-ui/react';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
|
||||
import type { StatsChartsSection } from 'types/api/stats';
|
||||
import type * as stats from '@blockscout/stats-types';
|
||||
import type { StatsIntervalIds } from 'types/client/stats';
|
||||
|
||||
import useApiQuery from 'lib/api/useApiQuery';
|
||||
@@ -18,7 +18,7 @@ type Props = {
|
||||
initialFilterQuery: string;
|
||||
isError: boolean;
|
||||
isPlaceholderData: boolean;
|
||||
charts?: Array<StatsChartsSection>;
|
||||
charts?: Array<stats.LineChartSection>;
|
||||
interval: StatsIntervalIds;
|
||||
}
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ const NumberWidgetsList = () => {
|
||||
data?.counters?.map(({ id, title, value, units, description }, index) => {
|
||||
|
||||
let unitsStr = '';
|
||||
if (UNITS_WITHOUT_SPACE.includes(units)) {
|
||||
if (units && UNITS_WITHOUT_SPACE.includes(units)) {
|
||||
unitsStr = units;
|
||||
} else if (units) {
|
||||
unitsStr = ' ' + units;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Grid, GridItem, Skeleton } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
|
||||
import type { StatsChartsSection } from 'types/api/stats';
|
||||
import type * as stats from '@blockscout/stats-types';
|
||||
import type { StatsInterval, StatsIntervalIds } from 'types/client/stats';
|
||||
|
||||
import FilterInput from 'ui/shared/filters/FilterInput';
|
||||
@@ -15,7 +15,7 @@ const intervalList = Object.keys(STATS_INTERVALS).map((id: string) => ({
|
||||
})) as Array<StatsInterval>;
|
||||
|
||||
type Props = {
|
||||
sections?: Array<StatsChartsSection>;
|
||||
sections?: Array<stats.LineChartSection>;
|
||||
currentSection: string;
|
||||
onSectionChange: (newSection: string) => void;
|
||||
interval: StatsIntervalIds;
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import { useRouter } from 'next/router';
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
|
||||
import type { StatsChartInfo, StatsChartsSection } from 'types/api/stats';
|
||||
import type * as stats from '@blockscout/stats-types';
|
||||
import type { StatsIntervalIds } from 'types/client/stats';
|
||||
|
||||
import useApiQuery from 'lib/api/useApiQuery';
|
||||
import getQueryParamString from 'lib/router/getQueryParamString';
|
||||
import { STATS_CHARTS } from 'stubs/stats';
|
||||
|
||||
function isSectionMatches(section: StatsChartsSection, currentSection: string): boolean {
|
||||
function isSectionMatches(section: stats.LineChartSection, currentSection: string): boolean {
|
||||
return currentSection === 'all' || section.id === currentSection;
|
||||
}
|
||||
|
||||
function isChartNameMatches(q: string, chart: StatsChartInfo) {
|
||||
function isChartNameMatches(q: string, chart: stats.LineChartInfo) {
|
||||
return chart.title.toLowerCase().includes(q.toLowerCase());
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { chakra, Alert, Modal, ModalBody, ModalCloseButton, ModalContent, ModalHeader, ModalOverlay } from '@chakra-ui/react';
|
||||
import type { ToastId } from '@chakra-ui/react';
|
||||
import { chakra, Alert, Modal, ModalBody, ModalCloseButton, ModalContent, ModalHeader, ModalOverlay, Spinner } from '@chakra-ui/react';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import React from 'react';
|
||||
import ReCaptcha from 'react-google-recaptcha';
|
||||
@@ -9,7 +10,7 @@ import type { TokenInstance } from 'types/api/token';
|
||||
import config from 'configs/app';
|
||||
import useApiFetch from 'lib/api/useApiFetch';
|
||||
import { getResourceKey } from 'lib/api/useApiQuery';
|
||||
import { MINUTE } from 'lib/consts';
|
||||
import { MINUTE, SECOND } from 'lib/consts';
|
||||
import useToast from 'lib/hooks/useToast';
|
||||
import useSocketChannel from 'lib/socket/useSocketChannel';
|
||||
import useSocketMessage from 'lib/socket/useSocketMessage';
|
||||
@@ -23,6 +24,7 @@ interface Props {
|
||||
|
||||
const TokenInstanceMetadataFetcher = ({ hash, id }: Props) => {
|
||||
const timeoutId = React.useRef<number>();
|
||||
const toastId = React.useRef<ToastId>();
|
||||
|
||||
const { status, setStatus } = useMetadataUpdateContext() || {};
|
||||
const apiFetch = useApiFetch();
|
||||
@@ -31,12 +33,12 @@ const TokenInstanceMetadataFetcher = ({ hash, id }: Props) => {
|
||||
|
||||
const handleRefreshError = React.useCallback(() => {
|
||||
setStatus?.('ERROR');
|
||||
toast.closeAll();
|
||||
toast({
|
||||
toastId.current && toast.update(toastId.current, {
|
||||
title: 'Error',
|
||||
description: 'The refreshing process has failed. Please try again.',
|
||||
status: 'warning',
|
||||
variant: 'subtle',
|
||||
duration: 5 * SECOND,
|
||||
isClosable: true,
|
||||
});
|
||||
}, [ setStatus, toast ]);
|
||||
|
||||
@@ -49,13 +51,15 @@ const TokenInstanceMetadataFetcher = ({ hash, id }: Props) => {
|
||||
},
|
||||
})
|
||||
.then(() => {
|
||||
toast({
|
||||
setStatus?.('WAITING_FOR_RESPONSE');
|
||||
toastId.current = toast({
|
||||
title: 'Please wait',
|
||||
description: 'Refetching metadata request sent',
|
||||
icon: <Spinner size="sm" mr={ 2 }/>,
|
||||
status: 'warning',
|
||||
variant: 'subtle',
|
||||
duration: null,
|
||||
isClosable: false,
|
||||
});
|
||||
setStatus?.('WAITING_FOR_RESPONSE');
|
||||
timeoutId.current = window.setTimeout(handleRefreshError, 2 * MINUTE);
|
||||
})
|
||||
.catch(() => {
|
||||
@@ -63,7 +67,6 @@ const TokenInstanceMetadataFetcher = ({ hash, id }: Props) => {
|
||||
title: 'Error',
|
||||
description: 'Unable to initialize metadata update',
|
||||
status: 'warning',
|
||||
variant: 'subtle',
|
||||
});
|
||||
setStatus?.('ERROR');
|
||||
});
|
||||
@@ -112,12 +115,12 @@ const TokenInstanceMetadataFetcher = ({ hash, id }: Props) => {
|
||||
};
|
||||
});
|
||||
|
||||
toast.closeAll();
|
||||
toast({
|
||||
toastId.current && toast.update(toastId.current, {
|
||||
title: 'Success!',
|
||||
description: 'Metadata has been refreshed',
|
||||
status: 'success',
|
||||
variant: 'subtle',
|
||||
duration: 5 * SECOND,
|
||||
isClosable: true,
|
||||
});
|
||||
|
||||
setStatus?.('SUCCESS');
|
||||
@@ -138,6 +141,15 @@ const TokenInstanceMetadataFetcher = ({ hash, id }: Props) => {
|
||||
handler: handleSocketMessage,
|
||||
});
|
||||
|
||||
React.useEffect(() => {
|
||||
return () => {
|
||||
timeoutId.current && window.clearTimeout(timeoutId.current);
|
||||
toastId.current && toast.close(toastId.current);
|
||||
};
|
||||
// run only on mount/unmount
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Modal isOpen={ status === 'MODAL_OPENED' } onClose={ handleModalClose } size={{ base: 'full', lg: 'sm' }}>
|
||||
<ModalOverlay/>
|
||||
|
||||
@@ -1327,10 +1327,20 @@
|
||||
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
|
||||
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
|
||||
|
||||
"@blockscout/bens-types@v1.3.0-beta":
|
||||
version "1.3.0-beta"
|
||||
resolved "https://registry.yarnpkg.com/@blockscout/bens-types/-/bens-types-1.3.0-beta.tgz#791fa102a33b1add14188beffe7c879219825424"
|
||||
integrity sha512-Gh4qYrj7bNo6fzPGsdtPDPqwboxv/4OWx9QyrblkKt4YSNAlyElIS9rcpGGJ9rVP3YyhflaCh52YOvtDcRXRLw==
|
||||
"@blockscout/[email protected].4":
|
||||
version "1.3.4"
|
||||
resolved "https://registry.yarnpkg.com/@blockscout/bens-types/-/bens-types-1.3.4.tgz#e75b863c6d065e7d6d5d01e1a1d64da8df261640"
|
||||
integrity sha512-kKRa8jKu/CBLR3QbWpRXmtwIXiIwIPDrFeEPIYUQp5bg9uY+ActOyQERixo/9FE+BHZShWUDm+75FoaAmIGIOw==
|
||||
|
||||
"@blockscout/[email protected]":
|
||||
version "1.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@blockscout/stats-types/-/stats-types-1.6.0.tgz#cdb27ab3d3cb1eef7b8b069c39d4e09afda1aec9"
|
||||
integrity sha512-MzItYOsLa3zgoFzRgFAgg7gynSXG0w/GqHzg5BGHcBPbPSp/g7A6mMtyIchI6TnZxxnCwziHHvzmJFXz11emUg==
|
||||
|
||||
"@blockscout/[email protected]":
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@blockscout/visualizer-types/-/visualizer-types-0.2.0.tgz#a8ef326d69e29417db124c78f868d68aca959ad1"
|
||||
integrity sha512-gasqbEL89iH8YnH/TIEk0MBSG9SwhEJegY9tnQ1c/jFZOCYjiVkgNwm4oH0ncwCKoNX1GoKAregbkEUwDDw7FQ==
|
||||
|
||||
"@braintree/sanitize-url@=6.0.4":
|
||||
version "6.0.4"
|
||||
@@ -8595,10 +8605,10 @@ damerau-levenshtein@^1.0.8:
|
||||
resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz#b43d286ccbd36bc5b2f7ed41caf2d0aba1f8a6e7"
|
||||
integrity sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==
|
||||
|
||||
[email protected].1:
|
||||
version "0.2.1"
|
||||
resolved "https://registry.yarnpkg.com/dappscout-iframe/-/dappscout-iframe-0.2.1.tgz#b4718515ee4f00022af3912fac6ca1a321c156f9"
|
||||
integrity sha512-EsiAAEk2I6hN+/E8o45WUn4BFd7aN8UvBwsIcOH79WOly0GOOHkPEO/puPkBCV0EcdxBsZIfssx3X0fSWVz5Bw==
|
||||
[email protected].2:
|
||||
version "0.2.2"
|
||||
resolved "https://registry.yarnpkg.com/dappscout-iframe/-/dappscout-iframe-0.2.2.tgz#de3df6abccad68a27c9304300b92d86ec0ab1c59"
|
||||
integrity sha512-ASOimgBRG61pSYQLdYGWePdiO3IsfTEgWZ6CHpZ4XQjJRmj1+WiWF56vFTeLIo5aucp+2+6oRCJ8KgKHGVDj0A==
|
||||
dependencies:
|
||||
react "^18.2.0"
|
||||
react-dom "^18.2.0"
|
||||
@@ -10439,10 +10449,9 @@ graceful-fs@^4.2.4, graceful-fs@^4.2.9:
|
||||
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c"
|
||||
integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==
|
||||
|
||||
gradient-avatar@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/gradient-avatar/-/gradient-avatar-1.0.2.tgz#42bb408e402b1f21aafba3878858721055515224"
|
||||
integrity sha512-Od9KI2YImV60wnsvU/u6GEyBm2fiHUUHgiLySE243GYl/T/tiJMJ5QYey8o7tepugmlnUGQRaCItHv19UnUjUg==
|
||||
"gradient-avatar@git+https://github.com/blockscout/gradient-avatar.git":
|
||||
version "1.0.3"
|
||||
resolved "git+https://github.com/blockscout/gradient-avatar.git#86810368c2581d2dc3aca089dc648336c8e17045"
|
||||
dependencies:
|
||||
hsl-rgb "^1.0.0"
|
||||
hsl-triad "^1.0.0"
|
||||
@@ -14846,16 +14855,7 @@ string-template@~0.2.1:
|
||||
resolved "https://registry.yarnpkg.com/string-template/-/string-template-0.2.1.tgz#42932e598a352d01fc22ec3367d9d84eec6c9add"
|
||||
integrity sha512-Yptehjogou2xm4UJbxJ4CxgZx12HBfeystp0y3x7s4Dj32ltVVG1Gg8YhKjHZkHicuKpZX/ffilA8505VbUbpw==
|
||||
|
||||
"string-width-cjs@npm:string-width@^4.2.0":
|
||||
version "4.2.3"
|
||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
||||
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
||||
dependencies:
|
||||
emoji-regex "^8.0.0"
|
||||
is-fullwidth-code-point "^3.0.0"
|
||||
strip-ansi "^6.0.1"
|
||||
|
||||
string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
|
||||
"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
|
||||
version "4.2.3"
|
||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
||||
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
||||
@@ -14983,14 +14983,7 @@ string_decoder@~1.1.1:
|
||||
dependencies:
|
||||
safe-buffer "~5.1.0"
|
||||
|
||||
"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
||||
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
||||
dependencies:
|
||||
ansi-regex "^5.0.1"
|
||||
|
||||
strip-ansi@^6.0.0, strip-ansi@^6.0.1:
|
||||
"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
||||
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
||||
@@ -16150,7 +16143,7 @@ word-wrap@^1.2.5, word-wrap@~1.2.3:
|
||||
resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34"
|
||||
integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==
|
||||
|
||||
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
|
||||
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
|
||||
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
|
||||
@@ -16168,15 +16161,6 @@ wrap-ansi@^6.2.0:
|
||||
string-width "^4.1.0"
|
||||
strip-ansi "^6.0.0"
|
||||
|
||||
wrap-ansi@^7.0.0:
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
|
||||
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
|
||||
dependencies:
|
||||
ansi-styles "^4.0.0"
|
||||
string-width "^4.1.0"
|
||||
strip-ansi "^6.0.0"
|
||||
|
||||
wrap-ansi@^8.1.0:
|
||||
version "8.1.0"
|
||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"
|
||||
|
||||