Commit 10881261 authored by tom goriunov's avatar tom goriunov Committed by GitHub

Replace `lodash` with `es-toolkit` (#2503)

* first part of migration

* second part of migration

* bump up es-toolkit version

* bump up `es-toolkit` version and remove `lodash` completely

* update screenshot
parent 2b47bbed
......@@ -30,14 +30,9 @@ const RESTRICTED_MODULES = {
importNames: [ 'Popover', 'Menu', 'PinInput', 'useToast', 'Skeleton' ],
message: 'Please use corresponding component or hook from ui/shared/chakra component instead',
},
{
name: 'lodash',
message: 'Please use `import [package] from \'lodash/[package]\'` instead.',
},
],
patterns: [
'icons/*',
'!lodash/*',
],
};
......
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M4.444 18.315a10 10 0 1 0 11.112-16.63 10 10 0 0 0-11.112 16.63ZM5.3 2.965a8.462 8.462 0 1 1 9.402 14.07 8.462 8.462 0 0 1-9.402-14.07Zm8.637 11.978a.768.768 0 0 0 .295.057.769.769 0 0 0 .546-1.315l-4.008-4V3.846a.769.769 0 1 0-1.538 0V10a.77.77 0 0 0 .223.546l4.23 4.23a.77.77 0 0 0 .252.167Z" fill="currentColor"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M4.444 18.315a10 10 0 1 0 11.112-16.63 10 10 0 0 0-11.112 16.63ZM5.3 2.965a8.462 8.462 0 1 1 9.402 14.07A8.462 8.462 0 0 1 5.3 2.965Zm8.637 11.978a.768.768 0 0 0 .295.057.769.769 0 0 0 .546-1.315l-4.008-4V3.846a.769.769 0 1 0-1.538 0V10a.77.77 0 0 0 .223.546l4.23 4.23a.77.77 0 0 0 .252.167Z" fill="currentColor"/>
</svg>
import { useQueryClient } from '@tanstack/react-query';
import _omit from 'lodash/omit';
import _pickBy from 'lodash/pickBy';
import { omit, pickBy } from 'es-toolkit';
import React from 'react';
import type { CsrfData } from 'types/client/account';
......@@ -38,7 +37,7 @@ export default function useApiFetch() {
const resource: ApiResource = RESOURCES[resourceName];
const url = buildUrl(resourceName, pathParams, queryParams);
const withBody = isBodyAllowed(fetchParams?.method);
const headers = _pickBy({
const headers = pickBy({
'x-endpoint': resource.endpoint && isNeedProxy() ? resource.endpoint : undefined,
Authorization: resource.endpoint && resource.needAuth ? apiToken : undefined,
'x-csrf-token': withBody && csrfToken ? csrfToken : undefined,
......@@ -55,7 +54,7 @@ export default function useApiFetch() {
// change condition here if something is changed
credentials: config.features.account.isEnabled ? 'include' : 'same-origin',
headers,
..._omit(fetchParams, 'headers'),
...(fetchParams ? omit(fetchParams, [ 'headers' ]) : {}),
},
{
resource: resource.path,
......
import clamp from 'lodash/clamp';
import throttle from 'lodash/throttle';
import { throttle, clamp } from 'es-toolkit';
import React from 'react';
const ScrollDirectionContext = React.createContext<'up' | 'down' | null>(null);
......
import _debounce from 'lodash/debounce';
import { debounce } from 'es-toolkit';
import type { LegacyRef } from 'react';
import React from 'react';
......@@ -19,7 +19,7 @@ export default function useClientRect<E extends Element>(): [ DOMRect | null, Le
return;
}
const resizeHandler = _debounce(() => {
const resizeHandler = debounce(() => {
setRect(nodeRef.current?.getBoundingClientRect() ?? null);
}, 100);
......
import throttle from 'lodash/throttle';
import { throttle } from 'es-toolkit';
import React from 'react';
export default function useIsSticky(ref: React.RefObject<HTMLDivElement>, offset = 0, isEnabled = true) {
......
import _clamp from 'lodash/clamp';
import { clamp } from 'es-toolkit';
import React from 'react';
import { useInView } from 'react-intersection-observer';
......@@ -15,7 +15,7 @@ export default function useLazyRenderedList(list: Array<unknown>, isEnabled: boo
React.useEffect(() => {
if (inView) {
setRenderedItemsNum((prev) => _clamp(prev + STEP, 0, list.length));
setRenderedItemsNum((prev) => clamp(prev + STEP, 0, list.length));
}
}, [ inView, list.length ]);
......
import _capitalize from 'lodash/capitalize';
import { capitalize } from 'es-toolkit';
export default function getTabName(tab: string) {
return tab !== '' ? _capitalize(tab.replaceAll('_', ' ')) : 'Default';
return tab !== '' ? capitalize(tab.replaceAll('_', ' ')) : 'Default';
}
import _capitalize from 'lodash/capitalize';
import { capitalize } from 'es-toolkit';
import type { Config } from 'mixpanel-browser';
import mixpanel from 'mixpanel-browser';
import { useRouter } from 'next/router';
......@@ -40,12 +40,12 @@ export default function useMixpanelInit() {
'Viewport width': window.innerWidth,
'Viewport height': window.innerHeight,
Language: window.navigator.language,
'Device type': _capitalize(deviceType),
'Device type': capitalize(deviceType),
'User id': userId,
});
mixpanel.identify(userId);
userProfile.set({
'Device Type': _capitalize(deviceType),
'Device Type': capitalize(deviceType),
...(isAuth ? { 'With Account': true } : {}),
});
userProfile.setOnce({
......
import _compose from 'lodash/fp/compose';
import _mapValues from 'lodash/mapValues';
import { mapValues } from 'es-toolkit';
import type { NetworkExplorer } from 'types/networks';
......@@ -32,7 +31,7 @@ const networkExplorers: Array<NetworkExplorer> = (() => {
return config.UI.explorers.items.map((explorer) => ({
...explorer,
baseUrl: stripTrailingSlash(explorer.baseUrl),
paths: _mapValues(explorer.paths, _compose(stripTrailingSlash, addLeadingSlash)),
paths: mapValues(explorer.paths, (value) => value ? stripTrailingSlash(addLeadingSlash(value)) : value),
}));
})();
......
import _uniq from 'lodash/uniq';
import { uniq } from 'es-toolkit';
import isBrowser from './isBrowser';
......@@ -27,7 +27,7 @@ export function saveToRecentKeywords(value: string) {
}
const keywordsArr = getRecentSearchKeywords();
const result = _uniq([ value, ...keywordsArr ]).slice(0, MAX_KEYWORDS_NUMBER - 1);
const result = uniq([ value, ...keywordsArr ]).slice(0, MAX_KEYWORDS_NUMBER - 1);
window.localStorage.setItem(RECENT_KEYWORDS_LS_KEY, JSON.stringify(result));
}
......
import _upperFirst from 'lodash/upperFirst';
import { upperFirst } from 'es-toolkit';
import type { Metadata, MetadataAttributes } from 'types/client/token';
......@@ -72,7 +72,7 @@ export default function attributesParser(attributes: Array<unknown>): Metadata['
return {
...formatValue(value, display, trait),
trait_type: _upperFirst(trait || 'property'),
trait_type: upperFirst(trait || 'property'),
};
})
.filter((item) => item?.value)
......
import _get from 'lodash/get';
import { get } from 'es-toolkit/compat';
import React from 'react';
import config from 'configs/app';
......@@ -25,7 +25,7 @@ export default function useAddOrSwitchChain() {
const errorObj = getErrorObj(error);
const code = errorObj && 'code' in errorObj ? errorObj.code : undefined;
const originalErrorCode = _get(errorObj, 'data.originalError.code');
const originalErrorCode = get(errorObj, 'data.originalError.code');
// This error code indicates that the chain has not been added to Wallet.
if (code === 4902 || originalErrorCode === 4902) {
......
import _padStart from 'lodash/padStart';
import { padStart } from 'es-toolkit/compat';
import type { BlockEpoch, BlockEpochElectionRewardDetails, BlockEpochElectionRewardDetailsResponse } from 'types/api/block';
......@@ -42,11 +42,11 @@ function getRewardDetailsItem(index: number): BlockEpochElectionRewardDetails {
amount: `${ 100 - index }210001063118670575`,
account: {
...addressMock.withoutName,
hash: `0x30D060F129817c4DE5fBc1366d53e19f43c8c6${ _padStart(String(index), 2, '0') }`,
hash: `0x30D060F129817c4DE5fBc1366d53e19f43c8c6${ padStart(String(index), 2, '0') }`,
},
associated_account: {
...addressMock.withoutName,
hash: `0x456f41406B32c45D59E539e4BBA3D7898c3584${ _padStart(String(index), 2, '0') }`,
hash: `0x456f41406B32c45D59E539e4BBA3D7898c3584${ padStart(String(index), 2, '0') }`,
},
};
}
......
import _mapValues from 'lodash/mapValues';
import { mapValues } from 'es-toolkit';
import type { HomeStats } from 'types/api/stats';
......@@ -51,17 +51,17 @@ export const withBtcLocked: HomeStats = {
export const withoutFiatPrices: HomeStats = {
...base,
gas_prices: _mapValues(base.gas_prices, (price) => price ? ({ ...price, fiat_price: null }) : null),
gas_prices: base.gas_prices ? mapValues(base.gas_prices, (price) => price ? ({ ...price, fiat_price: null }) : null) : null,
};
export const withoutGweiPrices: HomeStats = {
...base,
gas_prices: _mapValues(base.gas_prices, (price) => price ? ({ ...price, price: null }) : null),
gas_prices: base.gas_prices ? mapValues(base.gas_prices, (price) => price ? ({ ...price, price: null }) : null) : null,
};
export const withoutBothPrices: HomeStats = {
...base,
gas_prices: _mapValues(base.gas_prices, (price) => price ? ({ ...price, price: null, fiat_price: null }) : null),
gas_prices: base.gas_prices ? mapValues(base.gas_prices, (price) => price ? ({ ...price, price: null, fiat_price: null }) : null) : null,
};
export const withoutGasInfo: HomeStats = {
......
import type CspDev from 'csp-dev';
import { uniq } from 'es-toolkit';
export const KEY_WORDS = {
BLOB: 'blob:',
......@@ -11,17 +12,6 @@ export const KEY_WORDS = {
UNSAFE_EVAL: '\'unsafe-eval\'',
};
// we cannot use lodash/uniq and lodash/mergeWith in middleware code since it calls new Set() and it'is causing an error in Next.js
// "Dynamic Code Evaluation (e. g. 'eval', 'new Function', 'WebAssembly.compile') not allowed in Edge Runtime"
export function unique(array: Array<string | undefined>) {
const set: Record<string, boolean> = {};
for (const item of array) {
item && (set[item] = true);
}
return Object.keys(set);
}
export function mergeDescriptors(...descriptors: Array<CspDev.DirectiveDescriptor>) {
return descriptors.reduce((result, item) => {
for (const _key in item) {
......@@ -50,7 +40,7 @@ export function makePolicyString(policyDescriptor: CspDev.DirectiveDescriptor) {
return;
}
const uniqueValues = unique(value);
const uniqueValues = uniq(value);
return [ key, uniqueValues.join(' ') ].join(' ');
})
.filter(Boolean)
......
import { pick } from 'es-toolkit';
import type { IncomingMessage } from 'http';
import _pick from 'lodash/pick';
import type { NextApiRequest } from 'next';
import type { NextApiRequestCookies } from 'next/dist/server/api-utils';
import type { RequestInit, Response } from 'node-fetch';
......@@ -21,7 +21,7 @@ export default function fetchFactory(
accept: _req.headers['accept'] || 'application/json',
'content-type': _req.headers['content-type'] || 'application/json',
cookie: apiToken ? `${ cookies.NAMES.API_TOKEN }=${ apiToken }` : '',
..._pick(_req.headers, [
...pick(_req.headers, [
'x-csrf-token',
'Authorization', // the old value, just in case
'authorization', // Node.js automatically lowercases headers
......
import _pick from 'lodash/pick';
import _pickBy from 'lodash/pickBy';
import { pick, pickBy } from 'es-toolkit';
import type { NextApiRequest, NextApiResponse } from 'next';
import fetchFactory from 'nextjs/utils/fetchProxy';
......@@ -18,7 +17,7 @@ const handler = async(nextReq: NextApiRequest, nextRes: NextApiResponse) => {
);
const apiRes = await fetchFactory(nextReq)(
url.toString(),
_pickBy(_pick(nextReq, [ 'body', 'method' ]), Boolean),
pickBy(pick(nextReq, [ 'body', 'method' ]), Boolean),
);
// proxy some headers from API
......
import type { TestFixture, Page } from '@playwright/test';
import _isEqual from 'lodash/isEqual';
import { isEqual } from 'es-toolkit';
import { encodeFunctionData, encodeFunctionResult, type AbiFunction } from 'viem';
import { getEnvValue } from 'configs/app/utils';
......@@ -43,7 +43,7 @@ const fixture: TestFixture<MockContractReadResponseFixture, { page: Page }> = as
value: params?.value,
};
if (_isEqual(params, callParams) && id) {
if (isEqual(params, callParams) && id) {
return route.fulfill({
status: 200,
json: {
......
import type { TestFixture, Page } from '@playwright/test';
import _isEqual from 'lodash/isEqual';
import { isEqual } from 'es-toolkit';
import type { PublicRpcSchema } from 'viem';
import { getEnvValue } from 'configs/app/utils';
......@@ -34,7 +34,7 @@ const fixture: TestFixture<MockRpcResponseFixture, { page: Page }> = async({ pag
...(rpcMock.Parameters ? { params: rpcMock.Parameters } : {}),
};
if (_isEqual(json, payload) && id !== undefined) {
if (isEqual(json, payload) && id !== undefined) {
return route.fulfill({
status: 200,
json: {
......
import './fonts.css';
import './index.css';
import { beforeMount } from '@playwright/experimental-ct-react/hooks';
import _defaultsDeep from 'lodash/defaultsDeep';
import MockDate from 'mockdate';
import * as router from 'next/router';
......@@ -12,12 +11,15 @@ const NEXT_ROUTER_MOCK = {
replace: () => Promise.resolve(),
};
beforeMount(async({ hooksConfig }) => {
beforeMount(async({ hooksConfig }: { hooksConfig?: { router: typeof router } }) => {
// Before mount, redefine useRouter to return mock value from test.
// @ts-ignore: I really want to redefine this property :)
// eslint-disable-next-line no-import-assign
router.useRouter = () => _defaultsDeep(hooksConfig?.router, NEXT_ROUTER_MOCK);
router.useRouter = () => ({
...NEXT_ROUTER_MOCK,
...hooksConfig?.router,
});
// set current date
MockDate.set('2022-11-11T12:00:00Z');
......
This source diff could not be displayed because it is too large. You can view the blob instead.
import noop from 'lodash/noop';
import { noop } from 'es-toolkit';
import React from 'react';
import { test, expect } from 'playwright/lib';
......
import { Accordion, Box, Flex, Link } from '@chakra-ui/react';
import _range from 'lodash/range';
import { range } from 'es-toolkit';
import React from 'react';
import type { SmartContractMethod } from './types';
......@@ -39,7 +39,7 @@ const ContractAbi = ({ abi, addressHash, sourceAddress, tab, visibleItems }: Pro
}
if (expandedSections.length < abi.length) {
setExpandedSections(_range(0, abi.length));
setExpandedSections(range(0, abi.length));
} else {
setExpandedSections([]);
}
......
import _set from 'lodash/set';
import { set } from 'es-toolkit/compat';
import type { ContractAbiItemInput } from '../types';
......@@ -78,7 +78,7 @@ export function transformFormDataToMethodArgs(formData: ContractMethodFormFields
for (const field in formData) {
const value = formData[field];
_set(result, field.replaceAll(':', '.'), value);
set(result, field.replaceAll(':', '.'), value);
}
const filteredResult = filterOutEmptyItems(result);
......
import _pickBy from 'lodash/pickBy';
import { pickBy } from 'es-toolkit';
import { useRouter } from 'next/router';
import React from 'react';
......@@ -63,7 +63,7 @@ export default function useMethodsFilters({ abi }: Params) {
return;
}
const queryForPathname = _pickBy(router.query, (value, key) => router.pathname.includes(`[${ key }]`));
const queryForPathname = pickBy(router.query, (value, key) => router.pathname.includes(`[${ key }]`));
router.push(
{ pathname: router.pathname, query: { ...queryForPathname, tab: nextTab } },
undefined,
......
import { Image, Tooltip } from '@chakra-ui/react';
import _capitalize from 'lodash/capitalize';
import { capitalize } from 'es-toolkit';
import React from 'react';
import type { MultichainProviderConfigParsed } from 'types/client/multichainProviderConfig';
......@@ -25,10 +25,10 @@ const AddressMultichainButton = ({ item, addressHash, onClick, hasSingleProvider
const buttonContent = hasSingleProvider ? (
<>
{ buttonIcon }
{ _capitalize(item.name) }
{ capitalize(item.name) }
</>
) : (
<Tooltip label={ _capitalize(item.name) }>{ buttonIcon }</Tooltip>
<Tooltip label={ capitalize(item.name) }>{ buttonIcon }</Tooltip>
);
const linkProps = {
......
......@@ -12,7 +12,7 @@ import {
chakra,
} from '@chakra-ui/react';
import type { UseQueryResult } from '@tanstack/react-query';
import _clamp from 'lodash/clamp';
import { clamp } from 'es-toolkit';
import React from 'react';
import type * as bens from '@blockscout/bens-types';
......@@ -37,7 +37,7 @@ interface Props {
const DomainsGrid = ({ data }: { data: Array<bens.Domain> }) => {
return (
<Grid
templateColumns={{ base: `repeat(${ _clamp(data.length, 1, 2) }, 1fr)`, lg: `repeat(${ _clamp(data.length, 1, 3) }, 1fr)` }}
templateColumns={{ base: `repeat(${ clamp(data.length, 1, 2) }, 1fr)`, lg: `repeat(${ clamp(data.length, 1, 3) }, 1fr)` }}
columnGap={ 8 }
rowGap={ 4 }
mt={ 2 }
......
import { Box, Flex, IconButton, Tooltip } from '@chakra-ui/react';
import { useQueryClient, useIsFetching } from '@tanstack/react-query';
import _sumBy from 'lodash/sumBy';
import { sumBy } from 'es-toolkit';
import NextLink from 'next/link';
import { useRouter } from 'next/router';
import React from 'react';
......@@ -50,7 +50,7 @@ const TokenSelect = ({ onClick }: Props) => {
);
}
const hasTokens = _sumBy(Object.values(data), ({ items }) => items.length) > 0;
const hasTokens = sumBy(Object.values(data), ({ items }) => items.length) > 0;
if (isError || !hasTokens) {
return <Box py="6px">0</Box>;
}
......
import { Text, Box, Input, InputGroup, InputLeftElement, useColorModeValue, Flex, Link } from '@chakra-ui/react';
import _sumBy from 'lodash/sumBy';
import { sumBy } from 'es-toolkit';
import type { ChangeEvent } from 'react';
import React from 'react';
......@@ -26,7 +26,7 @@ interface Props {
const TokenSelectMenu = ({ erc20sort, erc1155sort, erc404sort, filteredData, onInputChange, onSortClick, searchTerm }: Props) => {
const searchIconColor = useColorModeValue('blackAlpha.600', 'whiteAlpha.600');
const hasFilteredResult = _sumBy(Object.values(filteredData), ({ items }) => items.length) > 0;
const hasFilteredResult = sumBy(Object.values(filteredData), ({ items }) => items.length) > 0;
return (
<>
......
import _mapValues from 'lodash/mapValues';
import { mapValues } from 'es-toolkit';
import type { ChangeEvent } from 'react';
import React from 'react';
......@@ -31,7 +31,7 @@ export default function useTokenSelect(data: FormattedData) {
}, []);
const filteredData = React.useMemo(() => {
return _mapValues(data, ({ items, isOverflow }) => ({
return mapValues(data, ({ items, isOverflow }) => ({
isOverflow,
items: items.filter(filterTokens(searchTerm.toLowerCase())),
}));
......
import BigNumber from 'bignumber.js';
import fpAdd from 'lodash/fp/add';
import type { AddressTokenBalance } from 'types/api/address';
import type { TokenType } from 'types/api/token';
......@@ -100,7 +99,7 @@ export const getTokensTotalInfo = (data: TokenSelectData) => {
const num = Object.values(data)
.map(({ items }) => items.length)
.reduce(fpAdd, 0);
.reduce((result, item) => result + item, 0);
const isOverflow = Object.values(data).some(({ isOverflow }) => isOverflow);
......
import { Flex, Select, Input, InputGroup, InputRightElement, VStack, IconButton } from '@chakra-ui/react';
import isEqual from 'lodash/isEqual';
import { isEqual } from 'es-toolkit';
import type { ChangeEvent } from 'react';
import React from 'react';
......
import { Flex, Input, Text } from '@chakra-ui/react';
import isEqual from 'lodash/isEqual';
import { isEqual } from 'es-toolkit';
import type { ChangeEvent } from 'react';
import React from 'react';
......
import { Flex, Input, Tag, Text } from '@chakra-ui/react';
import isEqual from 'lodash/isEqual';
import { isEqual } from 'es-toolkit';
import type { ChangeEvent } from 'react';
import React from 'react';
......
import { Flex, Checkbox, CheckboxGroup, Text, Spinner, Select } from '@chakra-ui/react';
import isEqual from 'lodash/isEqual';
import { isEqual } from 'es-toolkit';
import React from 'react';
import type { AdvancedFilterParams } from 'types/api/advancedFilter';
......
import { Flex, Checkbox, CheckboxGroup, Spinner, chakra } from '@chakra-ui/react';
import differenceBy from 'lodash/differenceBy';
import isEqual from 'lodash/isEqual';
import { isEqual, differenceBy } from 'es-toolkit';
import type { ChangeEvent } from 'react';
import React from 'react';
......
import { Flex, Checkbox, CheckboxGroup } from '@chakra-ui/react';
import isEqual from 'lodash/isEqual';
import without from 'lodash/without';
import { isEqual, without } from 'es-toolkit';
import type { ChangeEvent } from 'react';
import React from 'react';
......
import castArray from 'lodash/castArray';
import { castArray } from 'es-toolkit/compat';
import type { AdvancedFilterAge, AdvancedFilterParams } from 'types/api/advancedFilter';
......
import { Grid, GridItem, Text, Link, Box, Tooltip } from '@chakra-ui/react';
import BigNumber from 'bignumber.js';
import capitalize from 'lodash/capitalize';
import { capitalize } from 'es-toolkit';
import { useRouter } from 'next/router';
import React from 'react';
import { scroller, Element } from 'react-scroll';
......
import _padStart from 'lodash/padStart';
import { padStart } from 'es-toolkit/compat';
export default function splitSecondsInPeriods(value: number) {
const seconds = value % 60;
......@@ -7,9 +7,9 @@ export default function splitSecondsInPeriods(value: number) {
const days = (value - seconds - minutes * 60 - hours * 60 * 60) / (60 * 60 * 24);
return {
seconds: _padStart(String(seconds), 2, '0'),
minutes: _padStart(String(minutes), 2, '0'),
hours: _padStart(String(hours), 2, '0'),
days: _padStart(String(days), 2, '0'),
seconds: padStart(String(seconds), 2, '0'),
minutes: padStart(String(minutes), 2, '0'),
hours: padStart(String(hours), 2, '0'),
days: padStart(String(days), 2, '0'),
};
}
import { Flex, Text, Box, Tooltip } from '@chakra-ui/react';
import BigNumber from 'bignumber.js';
import capitalize from 'lodash/capitalize';
import { capitalize } from 'es-toolkit';
import React from 'react';
import type { Block } from 'types/api/block';
......
import { Table, Tbody, Tr, Th } from '@chakra-ui/react';
import { capitalize } from 'es-toolkit';
import { AnimatePresence } from 'framer-motion';
import capitalize from 'lodash/capitalize';
import React from 'react';
import type { Block } from 'types/api/block';
......
import _get from 'lodash/get';
import { get } from 'es-toolkit/compat';
import React from 'react';
import { useFormContext } from 'react-hook-form';
......@@ -43,7 +43,7 @@ const ContractVerificationFieldGitHubRepo = ({ onCommitHashChange }: Props) => {
const response = await fetch(`https://api.github.com/repos/${ gitHubData.owner }/${ gitHubData.repo }/commits?per_page=1`);
repoErrorRef.current = undefined;
trigger('repository_url');
onCommitHashChange(_get(response, '[0].sha'));
onCommitHashChange(get(response, '[0].sha'));
return;
} catch (error) {
repoErrorRef.current = 'GitHub repository not found';
......
import _capitalize from 'lodash/capitalize';
import { capitalize } from 'es-toolkit';
import React from 'react';
import type { UseFormReturn } from 'react-hook-form';
......@@ -40,7 +40,7 @@ const CsvExportFormField = ({ formApi, name }: Props) => {
name={ name }
type="date"
max={ dayjs().format('YYYY-MM-DD') }
placeholder={ _capitalize(name) }
placeholder={ capitalize(name) }
isRequired
rules={{ validate }}
size={{ base: 'md', lg: 'lg' }}
......
......@@ -15,7 +15,7 @@ import {
HStack,
Link,
} from '@chakra-ui/react';
import omit from 'lodash/omit';
import { omit } from 'es-toolkit';
import { useRouter } from 'next/router';
import React from 'react';
......
import { chakra } from '@chakra-ui/react';
import capitalize from 'lodash/capitalize';
import { capitalize } from 'es-toolkit';
import { useRouter } from 'next/router';
import React from 'react';
......
import { Flex } from '@chakra-ui/react';
import capitalize from 'lodash/capitalize';
import { capitalize } from 'es-toolkit';
import { useRouter } from 'next/router';
import React from 'react';
......
import _inRange from 'lodash/inRange';
import { inRange } from 'es-toolkit';
import { useRouter } from 'next/router';
import React from 'react';
......@@ -43,7 +43,7 @@ const UserOp = () => {
if (!userOpQuery.data) {
return true;
} else {
if (_inRange(
if (inRange(
Number(tt.log_index),
userOpQuery.data?.user_logs_start_index,
userOpQuery.data?.user_logs_start_index + userOpQuery.data?.user_logs_count,
......@@ -58,7 +58,7 @@ const UserOp = () => {
if (!userOpQuery.data) {
return true;
} else {
if (_inRange(log.index, userOpQuery.data?.user_logs_start_index, userOpQuery.data?.user_logs_start_index + userOpQuery.data?.user_logs_count)) {
if (inRange(log.index, userOpQuery.data?.user_logs_start_index, userOpQuery.data?.user_logs_start_index + userOpQuery.data?.user_logs_count)) {
return true;
}
return false;
......
import { Alert, Box, Button, Flex, Grid, GridItem } from '@chakra-ui/react';
import _pickBy from 'lodash/pickBy';
import { pickBy } from 'es-toolkit';
import React from 'react';
import type { FormSubmitResult } from './types';
......@@ -26,7 +26,7 @@ const PublicTagsSubmitResult = ({ data }: Props) => {
const hasErrors = groupedData.items.some((item) => item.error !== null);
const companyWebsite = makePrettyLink(groupedData.companyWebsite);
const startOverButtonQuery = hasErrors ? _pickBy({
const startOverButtonQuery = hasErrors ? pickBy({
requesterName: groupedData.requesterName,
requesterEmail: groupedData.requesterEmail,
companyName: groupedData.companyName,
......
import { chakra, Flex } from '@chakra-ui/react';
import type { GroupBase, SelectComponentsConfig, SingleValueProps } from 'chakra-react-select';
import { chakraComponents } from 'chakra-react-select';
import _capitalize from 'lodash/capitalize';
import { capitalize } from 'es-toolkit';
import React from 'react';
import { useFormContext } from 'react-hook-form';
......@@ -22,7 +22,7 @@ const PublicTagsSubmitFieldTagType = ({ index, tagTypes }: Props) => {
const typeOptions = React.useMemo(() => tagTypes?.map((type) => ({
value: type.type,
label: _capitalize(type.type),
label: capitalize(type.type),
})) ?? [], [ tagTypes ]);
const fieldValue = watch(`tags.${ index }.type`).value;
......
import { Box, Button, Flex, Grid, GridItem, useColorModeValue } from '@chakra-ui/react';
import _pickBy from 'lodash/pickBy';
import { pickBy } from 'es-toolkit';
import React from 'react';
import type { FormSubmitResultGrouped } from '../types';
......@@ -23,7 +23,7 @@ const PublicTagsSubmitResultWithErrors = ({ data }: Props) => {
<Flex flexDir="column" rowGap={ 3 }>
{ data.items.map((item, index) => {
const startOverButtonQuery = _pickBy({
const startOverButtonQuery = pickBy({
addresses: item.addresses,
requesterName: data.requesterName,
requesterEmail: data.requesterEmail,
......
import _isEqual from 'lodash/isEqual';
import _pickBy from 'lodash/pickBy';
import { pickBy, isEqual } from 'es-toolkit';
import type { FormFieldTag, FormFields, FormSubmitResult, FormSubmitResultGrouped, FormSubmitResultItemGrouped, SubmitRequestBody } from './types';
import type { UserInfo } from 'types/api/account';
......@@ -22,7 +21,7 @@ export function convertFormDataToRequestsBody(data: FormFields): Array<SubmitReq
name: tag.name,
tagType: tag.type.value,
description: data.description,
meta: _pickBy({
meta: pickBy({
bgColor: tag.bgColor,
textColor: tag.textColor,
tagUrl: tag.url,
......@@ -72,7 +71,7 @@ export function groupSubmitResult(data: FormSubmitResult | undefined): FormSubmi
// merge items with the same error and tags
for (const item of _items) {
const existingItem = items.find(({ error, tags }) => error === item.error && _isEqual(tags, item.tags));
const existingItem = items.find(({ error, tags }) => error === item.error && isEqual(tags, item.tags));
if (existingItem) {
existingItem.addresses.push(...item.addresses);
continue;
......
import { PopoverTrigger, PopoverContent, PopoverBody, useDisclosure } from '@chakra-ui/react';
import _debounce from 'lodash/debounce';
import { debounce } from 'es-toolkit';
import type { FormEvent, FocusEvent } from 'react';
import React from 'react';
......@@ -59,7 +59,7 @@ const SearchResultsInput = ({ searchTerm, handleSubmit, handleSearchTermChange }
}
calculateMenuWidth();
const resizeHandler = _debounce(calculateMenuWidth, 200);
const resizeHandler = debounce(calculateMenuWidth, 200);
const resizeObserver = new ResizeObserver(resizeHandler);
resizeObserver.observe(inputRef.current);
......
import { Box, useColorModeValue } from '@chakra-ui/react';
import _debounce from 'lodash/debounce';
import { debounce } from 'es-toolkit';
import React, { useRef, useEffect, useState, useCallback } from 'react';
const CUT_HEIGHT = 144;
......@@ -25,7 +25,7 @@ const AccountPageDescription = ({ children, allowCut = true }: { children: React
}
calculateCut();
const resizeHandler = _debounce(calculateCut, 300);
const resizeHandler = debounce(calculateCut, 300);
window.addEventListener('resize', resizeHandler);
return function cleanup() {
window.removeEventListener('resize', resizeHandler);
......
......@@ -10,7 +10,7 @@
import type { As } from '@chakra-ui/react';
import { Tooltip, chakra } from '@chakra-ui/react';
import _debounce from 'lodash/debounce';
import { debounce } from 'es-toolkit';
import React, { useCallback, useEffect, useRef } from 'react';
import type { FontFace } from 'use-font-face-observer';
import useFontFaceObserver from 'use-font-face-observer';
......@@ -81,7 +81,7 @@ const HashStringShortenDynamic = ({ hash, fontWeight = '400', isTooltipDisabled,
}, [ calculateString, isFontFaceLoaded ]);
useEffect(() => {
const resizeHandler = _debounce(calculateString, 100);
const resizeHandler = debounce(calculateString, 100);
const resizeObserver = new ResizeObserver(resizeHandler);
resizeObserver.observe(document.body);
......
import { Heading, Flex, Tooltip, Link, chakra, useDisclosure } from '@chakra-ui/react';
import _debounce from 'lodash/debounce';
import { debounce } from 'es-toolkit';
import React from 'react';
import useIsMobile from 'lib/hooks/useIsMobile';
......@@ -94,7 +94,7 @@ const PageTitle = ({ title, contentAfter, withTextAd, backLink, className, isLoa
}, [ isLoading, updatedTruncateState ]);
React.useEffect(() => {
const handleResize = _debounce(updatedTruncateState, 1000);
const handleResize = debounce(updatedTruncateState, 1000);
window.addEventListener('resize', handleResize);
return function cleanup() {
......
import type { ChakraProps, ThemingProps } from '@chakra-ui/react';
import { chakra } from '@chakra-ui/react';
import _pickBy from 'lodash/pickBy';
import { pickBy } from 'es-toolkit';
import { useRouter } from 'next/router';
import React, { useEffect, useRef } from 'react';
......@@ -42,7 +42,7 @@ const RoutedTabs = ({
const handleTabChange = React.useCallback((index: number) => {
const nextTab = tabs[index];
const queryForPathname = _pickBy(router.query, (value, key) => router.pathname.includes(`[${ key }]`));
const queryForPathname = pickBy(router.query, (value, key) => router.pathname.includes(`[${ key }]`));
const tabId = Array.isArray(nextTab.id) ? nextTab.id[0] : nextTab.id;
router.push(
{ pathname: router.pathname, query: { ...queryForPathname, tab: tabId } },
......
......@@ -6,7 +6,7 @@ import {
TabPanels,
chakra,
} from '@chakra-ui/react';
import _debounce from 'lodash/debounce';
import { debounce } from 'es-toolkit';
import React, { useEffect, useRef, useState } from 'react';
import type { TabItem } from './types';
......@@ -69,7 +69,7 @@ const TabsWithScroll = ({
}, [ defaultTabIndex ]);
React.useEffect(() => {
const resizeHandler = _debounce(() => {
const resizeHandler = debounce(() => {
setScreenWidth(window.innerWidth);
}, 100);
const resizeObserver = new ResizeObserver(resizeHandler);
......
import { Thead, useColorModeValue } from '@chakra-ui/react';
import type { TableHeadProps, PositionProps } from '@chakra-ui/react';
import throttle from 'lodash/throttle';
import { throttle } from 'es-toolkit';
import React from 'react';
interface Props extends TableHeadProps {
......
import type { PlacementWithLogical } from '@chakra-ui/react';
import { Tooltip, useDisclosure } from '@chakra-ui/react';
import debounce from 'lodash/debounce';
import { debounce } from 'es-toolkit';
import React from 'react';
import useFontFaceObserver from 'use-font-face-observer';
......
import { Box, Flex, chakra, useColorModeValue } from '@chakra-ui/react';
import clamp from 'lodash/clamp';
import { clamp } from 'es-toolkit';
import React from 'react';
import Skeleton from 'ui/shared/chakra/Skeleton';
......
import * as d3 from 'd3';
import _clamp from 'lodash/clamp';
import { clamp } from 'es-toolkit';
import React from 'react';
import { POINT_SIZE } from './utils';
......@@ -69,33 +69,33 @@ function calculatePosition({ pointX, pointY, canvasWidth, canvasHeight, nodeWidt
// right
if (pointX + offset + nodeWidth <= canvasWidth) {
const x = pointX + offset;
const y = _clamp(pointY - nodeHeight / 2, 0, canvasHeight - nodeHeight);
const y = clamp(pointY - nodeHeight / 2, 0, canvasHeight - nodeHeight);
return [ x, y ];
}
// left
if (nodeWidth + offset <= pointX) {
const x = pointX - offset - nodeWidth;
const y = _clamp(pointY - nodeHeight / 2, 0, canvasHeight - nodeHeight);
const y = clamp(pointY - nodeHeight / 2, 0, canvasHeight - nodeHeight);
return [ x, y ];
}
// top
if (nodeHeight + offset <= pointY) {
const x = _clamp(pointX - nodeWidth / 2, 0, canvasWidth - nodeWidth);
const x = clamp(pointX - nodeWidth / 2, 0, canvasWidth - nodeWidth);
const y = pointY - offset - nodeHeight;
return [ x, y ];
}
// bottom
if (pointY + offset + nodeHeight <= canvasHeight) {
const x = _clamp(pointX - nodeWidth / 2, 0, canvasWidth - nodeWidth);
const x = clamp(pointX - nodeWidth / 2, 0, canvasWidth - nodeWidth);
const y = pointY + offset;
return [ x, y ];
}
const x = _clamp(pointX / 2, 0, canvasWidth - nodeWidth);
const y = _clamp(pointY / 2, 0, canvasHeight - nodeHeight);
const x = clamp(pointX / 2, 0, canvasWidth - nodeWidth);
const y = clamp(pointY / 2, 0, canvasHeight - nodeHeight);
return [ x, y ];
}
import _range from 'lodash/range';
import { range } from 'es-toolkit';
import React from 'react';
export default function useChartLegend(dataLength: number) {
const [ selectedLines, setSelectedLines ] = React.useState<Array<number>>(_range(dataLength));
const [ selectedLines, setSelectedLines ] = React.useState<Array<number>>(range(dataLength));
const handleLegendItemClick = React.useCallback((index: number) => {
const nextSelectedLines = selectedLines.includes(index) ? selectedLines.filter((item) => item !== index) : [ ...selectedLines, index ];
......
import * as d3 from 'd3';
import _maxBy from 'lodash/maxBy';
import _unique from 'lodash/uniq';
import { maxBy, uniq } from 'es-toolkit';
import type { AxesConfig, AxisConfig, TimeChartData } from '../types';
......@@ -88,8 +87,8 @@ function getYLabelFormatParams(ticks: Array<number>, maximumSignificantDigits =
notation: 'compact' as const,
};
const uniqTicksStr = _unique(ticks.map((tick) => tick.toLocaleString(undefined, params)));
const maxLabelLength = _maxBy(uniqTicksStr, (items) => items.length)?.length ?? DEFAULT_LABEL_LENGTH;
const uniqTicksStr = uniq(ticks.map((tick) => tick.toLocaleString(undefined, params)));
const maxLabelLength = maxBy(uniqTicksStr, (items) => items.length)?.length ?? DEFAULT_LABEL_LENGTH;
if (uniqTicksStr.length === ticks.length || maximumSignificantDigits === MAXIMUM_SIGNIFICANT_DIGITS_LIMIT) {
return { ...params, maxLabelLength };
......
import _noop from 'lodash/noop';
import { noop } from 'es-toolkit';
import React from 'react';
import { test, expect } from 'playwright/lib';
......@@ -18,7 +18,7 @@ const defaultProps = {
isRequired: true,
placeholder: 'Compiler',
name: 'compiler',
onChange: _noop,
onChange: noop,
};
[ 'md' as const, 'lg' as const ].forEach((size) => {
......
import type { HTMLChakraProps } from '@chakra-ui/react';
import { Box, Tab, TabList, TabPanel, TabPanels, Tabs, useBoolean } from '@chakra-ui/react';
import _throttle from 'lodash/throttle';
import { throttle } from 'es-toolkit';
import type * as monaco from 'monaco-editor/esm/vs/editor/editor.api';
import React from 'react';
......@@ -47,7 +47,7 @@ const CodeEditorSideBar = ({ onFileSelect, data, monaco, editor, selectedFile, m
letterSpacing: 0.3,
};
const handleScrollThrottled = React.useRef(_throttle((event: React.SyntheticEvent) => {
const handleScrollThrottled = React.useRef(throttle((event: React.SyntheticEvent) => {
setIsStuck((event.target as HTMLDivElement).scrollTop > 0);
}, 100));
......
import type { UseQueryResult } from '@tanstack/react-query';
import { useQueryClient } from '@tanstack/react-query';
import omit from 'lodash/omit';
import { omit } from 'es-toolkit';
import { useRouter } from 'next/router';
import React, { useCallback } from 'react';
import { animateScroll } from 'react-scroll';
......@@ -154,7 +154,14 @@ export default function useQueryWithPages<Resource extends PaginatedResources>({
}, [ queryClient, resourceName, router, scrollToTop ]);
const onFilterChange = useCallback(<R extends PaginatedResources = Resource>(newFilters: PaginationFilters<R> | undefined) => {
const newQuery = omit<typeof router.query>(router.query, 'next_page_params', 'page', 'filterFields' in resource ? resource.filterFields : []);
const newQuery: typeof router.query = omit(
router.query,
[
'next_page_params',
'page',
...('filterFields' in resource ? resource.filterFields : []),
],
);
if (newFilters) {
Object.entries(newFilters).forEach(([ key, value ]) => {
const isValidValue = typeof value === 'boolean' || (value && value.length);
......@@ -179,8 +186,8 @@ export default function useQueryWithPages<Resource extends PaginatedResources>({
}, [ router, resource, scrollToTop ]);
const onSortingChange = useCallback((newSorting: PaginationSorting<Resource> | undefined) => {
const newQuery = {
...omit<typeof router.query>(router.query, 'next_page_params', 'page', SORTING_FIELDS),
const newQuery: typeof router.query = {
...omit(router.query, [ 'next_page_params', 'page', ...SORTING_FIELDS ]),
...newSorting,
};
scrollToTop();
......
import _noop from 'lodash/noop';
import { noop } from 'es-toolkit';
import React from 'react';
import { test, expect } from 'playwright/lib';
......@@ -12,7 +12,7 @@ test('base view +@dark-mode', async({ render }) => {
<TagGroupSelect
items={ [ { id: '1', title: 'Option 1' }, { id: '2', title: 'Option 2' }, { id: 'duck', title: 'Cute little duck' } ] }
value="duck"
onChange={ _noop }
onChange={ noop }
/>,
);
......
import { Box, Select, VStack, Flex } from '@chakra-ui/react';
import capitalize from 'lodash/capitalize';
import { capitalize } from 'es-toolkit';
import React from 'react';
import type { NetworkGroup, FeaturedNetwork } from 'types/networks';
......
......@@ -8,7 +8,7 @@ import {
useDisclosure,
useOutsideClick,
} from '@chakra-ui/react';
import _debounce from 'lodash/debounce';
import { debounce } from 'es-toolkit';
import { useRouter } from 'next/router';
import type { FormEvent } from 'react';
import React from 'react';
......@@ -115,7 +115,7 @@ const SearchBar = ({ isHomepage }: Props) => {
}
calculateMenuWidth();
const resizeHandler = _debounce(calculateMenuWidth, 200);
const resizeHandler = debounce(calculateMenuWidth, 200);
const resizeObserver = new ResizeObserver(resizeHandler);
resizeObserver.observe(inputRef.current);
......
import { InputGroup, Input, InputLeftElement, chakra, useColorModeValue, forwardRef, InputRightElement, Center } from '@chakra-ui/react';
import throttle from 'lodash/throttle';
import { throttle } from 'es-toolkit';
import React from 'react';
import type { ChangeEvent, FormEvent, FocusEvent } from 'react';
......
import { Box, Tab, TabList, Tabs, Text, useColorModeValue } from '@chakra-ui/react';
import type { UseQueryResult } from '@tanstack/react-query';
import throttle from 'lodash/throttle';
import { throttle } from 'es-toolkit';
import React from 'react';
import { scroller, Element } from 'react-scroll';
......
import _upperFirst from 'lodash/upperFirst';
import { upperFirst } from 'es-toolkit';
export function formatName(_name: string) {
const name = _name
.replaceAll('_', ' ')
.replaceAll(/\burl|nft|id\b/gi, (str) => str.toUpperCase());
return _upperFirst(name.trim());
return upperFirst(name.trim());
}
const PINNED_FIELDS = [ 'name', 'description' ];
......
import { Table, Tbody, Tr, Th, Box, Text, Show, Hide } from '@chakra-ui/react';
import _chunk from 'lodash/chunk';
import { chunk } from 'es-toolkit';
import React, { useMemo, useState } from 'react';
import type { PaginationParams } from 'ui/shared/pagination/types';
......@@ -34,7 +34,7 @@ export default function TxAssetFlows(props: FlowViewProps) {
const [ page, setPage ] = useState<number>(1);
const ViewData = useMemo(() => (queryData ? generateFlowViewData(queryData) : []), [ queryData ]);
const chunkedViewData = _chunk(ViewData, 50);
const chunkedViewData = chunk(ViewData, 50);
const paginationProps: PaginationParams = useMemo(() => ({
onNextPageClick: () => setPage(page + 1),
......
import _findIndex from 'lodash/findIndex';
import type { NovesNft, NovesResponseData, NovesSentReceived, NovesToken } from 'types/api/noves';
export interface NovesAction {
......@@ -27,7 +25,7 @@ export function generateFlowViewData(data: NovesResponseData): Array<NovesFlowVi
const txItems = [ ...sent, ...received ];
const paidGasIndex = _findIndex(txItems, (item) => item.action === 'paidGas');
const paidGasIndex = txItems.findIndex((item) => item.action === 'paidGas');
if (paidGasIndex >= 0) {
const element = txItems.splice(paidGasIndex, 1)[0];
element.to.name = 'Validators';
......
import _groupBy from 'lodash/groupBy';
import _keysIn from 'lodash/keysIn';
import _mapValues from 'lodash/mapValues';
import { groupBy, mapValues } from 'es-toolkit';
import type { NovesResponseData } from 'types/api/noves';
import type { TokenInfo } from 'types/api/token';
......@@ -49,28 +47,28 @@ export function getTokensData(data: NovesResponseData): TokensData {
});
// Group tokens by property into arrays
const tokensGroupByname = _groupBy(tokens, 'name');
const tokensGroupBySymbol = _groupBy(tokens, 'symbol');
const tokensGroupById = _groupBy(tokens, 'id');
const tokensGroupByName = groupBy(tokens, (item) => item.name || 'null');
const tokensGroupBySymbol = groupBy(tokens, (item) => item.symbol || 'null');
const tokensGroupById = groupBy(tokens, (item) => item.id || 'null');
// Map properties to an object and remove duplicates
const mappedNames = _mapValues(tokensGroupByname, (i) => {
const mappedNames = mapValues(tokensGroupByName, (i) => {
return i[0];
});
const mappedSymbols = _mapValues(tokensGroupBySymbol, (i) => {
const mappedSymbols = mapValues(tokensGroupBySymbol, (i) => {
return i[0];
});
const mappedIds = _mapValues(tokensGroupById, (i) => {
const mappedIds = mapValues(tokensGroupById, (i) => {
return i[0];
});
const filters = [ 'undefined', 'null' ];
// Array of keys to match in string
const nameList = _keysIn(mappedNames).filter(i => !filters.includes(i));
const symbolList = _keysIn(mappedSymbols).filter(i => !filters.includes(i));
const idList = _keysIn(mappedIds).filter(i => !filters.includes(i));
const nameList = Object.keys(mappedNames).filter(i => !filters.includes(i));
const symbolList = Object.keys(mappedSymbols).filter(i => !filters.includes(i));
const idList = Object.keys(mappedIds).filter(i => !filters.includes(i));
return {
nameList,
......
import { useQuery } from '@tanstack/react-query';
import _chunk from 'lodash/chunk';
import _uniq from 'lodash/uniq';
import { uniq, chunk } from 'es-toolkit';
import React from 'react';
import type { NovesDescribeTxsResponse } from 'types/api/noves';
......@@ -16,8 +15,8 @@ const translateEnabled = feature.isEnabled && feature.provider === 'noves';
export default function useDescribeTxs(items: Array<Transaction> | undefined, viewAsAccountAddress: string | undefined, isPlaceholderData: boolean) {
const apiFetch = useApiFetch();
const txsHash = _uniq(items?.map(i => i.hash));
const txChunks = _chunk(txsHash, 10);
const txsHash = items ? uniq(items.map(i => i.hash)) : [];
const txChunks = chunk(txsHash, 10);
const queryKey = {
viewAsAccountAddress,
......
......@@ -10376,6 +10376,11 @@ es-to-primitive@^1.2.1:
is-date-object "^1.0.1"
is-symbol "^1.0.2"
es-toolkit@1.31.0:
version "1.31.0"
resolved "https://registry.yarnpkg.com/es-toolkit/-/es-toolkit-1.31.0.tgz#f4fc1382aea09cb239afa38f3c724a5658ff3163"
integrity sha512-vwS0lv/tzjM2/t4aZZRAgN9I9TP0MSkWuvt6By+hEXfG/uLs8yg2S1/ayRXH/x3pinbLgVJYT+eppueg3cM6tg==
esbuild@^0.21.3:
version "0.21.5"
resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.21.5.tgz#9ca301b120922959b766360d8ac830da0d02997d"
......@@ -13573,7 +13578,7 @@ lodash.throttle@^4.1.1:
resolved "https://registry.yarnpkg.com/lodash.throttle/-/lodash.throttle-4.1.1.tgz#c23e91b710242ac70c37f1e1cda9274cc39bf2f4"
integrity sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==
lodash@^4.0.0, lodash@^4.15.0, lodash@^4.17.21:
lodash@^4.15.0, lodash@^4.17.21:
version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment