diff --git a/ui/pages/bridge/prepare/__snapshots__/bridge-cta-button.test.tsx.snap b/ui/pages/bridge/prepare/__snapshots__/bridge-cta-button.test.tsx.snap
index 0b46d2764523..6014dbaddb23 100644
--- a/ui/pages/bridge/prepare/__snapshots__/bridge-cta-button.test.tsx.snap
+++ b/ui/pages/bridge/prepare/__snapshots__/bridge-cta-button.test.tsx.snap
@@ -2,9 +2,13 @@
exports[`BridgeCTAButton should disable the component when quotes are loading and there are no existing quotes 1`] = `
-
+
+
+
`;
@@ -23,20 +27,28 @@ exports[`BridgeCTAButton should enable the component when quotes are loading and
exports[`BridgeCTAButton should render the component when amount and dest token is missing 1`] = `
-
- Select token and amount
-
+
+ Select token and amount
+
+
`;
exports[`BridgeCTAButton should render the component's initial state 1`] = `
+
+
+ {t('basicConfigurationLabel')}
+
+ {
+ if (useExternalServices) {
+ // If we are going to be disabling external services, then we want to show the "turn off" warning modal
+ setBasicFunctionalityModalOpen();
+ } else {
+ toggleExternalServices(true);
+ this.context.trackEvent({
+ category: MetaMetricsEventCategory.Settings,
+ event: MetaMetricsEventName.SettingsUpdated,
+ properties: {
+ settings_group: 'security_privacy',
+ settings_type: 'basic_functionality',
+ old_value: false,
+ new_value: true,
+ // these values will always be set to false
+ // when basic functionality is re-enabled
+ was_notifications_on: false,
+ was_profile_syncing_on: false,
+ },
+ });
+ }
+ }}
+ offLabel={t('off')}
+ onLabel={t('on')}
+ />
+
+
{t('basicConfigurationDescription', [
,
])}
-
+
-
- {
- if (useExternalServices) {
- // If we are going to be disabling external services, then we want to show the "turn off" warning modal
- setBasicFunctionalityModalOpen();
- } else {
- toggleExternalServices(true);
- this.context.trackEvent({
- category: MetaMetricsEventCategory.Settings,
- event: MetaMetricsEventName.SettingsUpdated,
- properties: {
- settings_group: 'security_privacy',
- settings_type: 'basic_functionality',
- old_value: false,
- new_value: true,
- // these values will always be set to false
- // when basic functionality is re-enabled
- was_notifications_on: false,
- was_profile_syncing_on: false,
- },
- });
- }
- }}
- offLabel={t('off')}
- onLabel={t('on')}
- />
-
+
);
}
From bdd92438275867c9904cb8d1ec5e1f6c930f706b Mon Sep 17 00:00:00 2001
From: Nick Gambino <35090461+gambinish@users.noreply.github.com>
Date: Wed, 18 Dec 2024 08:54:55 -0800
Subject: [PATCH 027/202] fix: Token details should not display zero balance
for tokens without marketData (#29299)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
## **Description**
On main asset list, if a token doesn't have marketData, we do not
display the fiat value. On tokenDetails we were falling back to zero
balance (this is incorrect)
This PR adds a fix to not fallback to zero balance, and to instead
simply omit the value.
[![Open in GitHub
Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29299?quickstart=1)
## **Related issues**
Fixes: https://github.com/MetaMask/metamask-extension/issues/29244
## **Manual testing steps**
1. Add a memcoin without marketData
2. Validate that no fiat value is shown on main asset list
3. Validate that no fiat value is shown on token details (should not
display zero value)
4. Ensure nothing results in `NaN` value.
## **Screenshots/Recordings**
### **Before**
### **After**
## **Pre-merge author checklist**
- [x] I've followed [MetaMask Contributor
Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask
Extension Coding
Standards](https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/CODING_GUIDELINES.md).
- [x] I've completed the PR template to the best of my ability
- [x] I’ve included tests if applicable
- [x] I’ve documented my code using [JSDoc](https://jsdoc.app/) format
if applicable
- [x] I’ve applied the right labels on the PR (see [labeling
guidelines](https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/LABELING_GUIDELINES.md)).
Not required for external contributors.
## **Pre-merge reviewer checklist**
- [ ] I've manually tested the PR (e.g. pull and build branch, run the
app, test code being changed).
- [ ] I confirm that this PR addresses all acceptance criteria described
in the ticket it closes and includes the necessary testing evidence such
as recordings and or screenshots.
---
.../asset/components/__snapshots__/asset-page.test.tsx.snap | 4 +---
ui/pages/asset/components/asset-page.tsx | 6 ++++--
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/ui/pages/asset/components/__snapshots__/asset-page.test.tsx.snap b/ui/pages/asset/components/__snapshots__/asset-page.test.tsx.snap
index dc3cf4c408de..6a55226a42cb 100644
--- a/ui/pages/asset/components/__snapshots__/asset-page.test.tsx.snap
+++ b/ui/pages/asset/components/__snapshots__/asset-page.test.tsx.snap
@@ -223,9 +223,7 @@ exports[`AssetPage should render a native asset 1`] = `
- $0.00
-
+ />
Date: Wed, 18 Dec 2024 12:02:46 -0500
Subject: [PATCH 028/202] refactor: remove duplication of selectors in
`selectors/confirm-transaction.js` (#27641)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
These selectors were duplicated because our circular dependencies were
not letting us import them. Now that I've untangled a bunch of files we
can import them just fine!
---
ui/selectors/confirm-transaction.js | 12 ++++--------
1 file changed, 4 insertions(+), 8 deletions(-)
diff --git a/ui/selectors/confirm-transaction.js b/ui/selectors/confirm-transaction.js
index 10183c916652..2017c3c68932 100644
--- a/ui/selectors/confirm-transaction.js
+++ b/ui/selectors/confirm-transaction.js
@@ -42,17 +42,13 @@ import {
getUnapprovedTransactions,
selectTransactionMetadata,
selectTransactionSender,
+ unapprovedPersonalMsgsSelector,
+ unapprovedDecryptMsgsSelector,
+ unapprovedEncryptionPublicKeyMsgsSelector,
+ unapprovedTypedMessagesSelector,
} from './transactions';
const unapprovedTxsSelector = (state) => getUnapprovedTransactions(state);
-const unapprovedPersonalMsgsSelector = (state) =>
- state.metamask.unapprovedPersonalMsgs;
-const unapprovedDecryptMsgsSelector = (state) =>
- state.metamask.unapprovedDecryptMsgs;
-const unapprovedEncryptionPublicKeyMsgsSelector = (state) =>
- state.metamask.unapprovedEncryptionPublicKeyMsgs;
-const unapprovedTypedMessagesSelector = (state) =>
- state.metamask.unapprovedTypedMessages;
export const unconfirmedTransactionsListSelector = createSelector(
unapprovedTxsSelector,
From 2258c15fe513bb6a9bc952fc879b357a27842bfd Mon Sep 17 00:00:00 2001
From: cmd-ob
Date: Wed, 18 Dec 2024 17:44:54 +0000
Subject: [PATCH 029/202] test: [POM] Migrate add-multiple-tokens test (#29288)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
## **Description**
* Moving `test/e2e/tests/tokens/add-multiple-tokens.spec.js` to use POM
and TS
* Adds new generic modal PO for when we just need to click confirm or
cancel `test/e2e/page-objects/pages/dialog/dialog.ts`
* New PO for add tokens dialog
`test/e2e/page-objects/pages/dialog/add-tokens.ts`
* Minor updates to asset-list and dapp POs
[![Open in GitHub
Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29288?quickstart=1)
## **Related issues**
Fixes:
## **Manual testing steps**
Tests should pass, and test logic should be checked for correctness.
## **Screenshots/Recordings**
### **Before**
### **After**
## **Pre-merge author checklist**
- [x] I've followed [MetaMask Contributor
Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask
Extension Coding
Standards](https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/CODING_GUIDELINES.md).
- [x] I've completed the PR template to the best of my ability
- [x] I’ve included tests if applicable
- [x] I’ve documented my code using [JSDoc](https://jsdoc.app/) format
if applicable
- [x] I’ve applied the right labels on the PR (see [labeling
guidelines](https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/LABELING_GUIDELINES.md)).
Not required for external contributors.
## **Pre-merge reviewer checklist**
- [x] I've manually tested the PR (e.g. pull and build branch, run the
app, test code being changed).
- [x] I confirm that this PR addresses all acceptance criteria described
in the ticket it closes and includes the necessary testing evidence such
as recordings and or screenshots.
---
.../page-objects/pages/dialog/add-tokens.ts | 50 ++++++++
.../pages/dialog/create-contract.ts | 39 ++++++
.../e2e/page-objects/pages/home/asset-list.ts | 15 +++
test/e2e/page-objects/pages/test-dapp.ts | 47 +++++++-
.../tests/tokens/add-multiple-tokens.spec.js | 114 ------------------
.../tests/tokens/add-multiple-tokens.spec.ts | 70 +++++++++++
6 files changed, 220 insertions(+), 115 deletions(-)
create mode 100644 test/e2e/page-objects/pages/dialog/add-tokens.ts
create mode 100644 test/e2e/page-objects/pages/dialog/create-contract.ts
delete mode 100644 test/e2e/tests/tokens/add-multiple-tokens.spec.js
create mode 100644 test/e2e/tests/tokens/add-multiple-tokens.spec.ts
diff --git a/test/e2e/page-objects/pages/dialog/add-tokens.ts b/test/e2e/page-objects/pages/dialog/add-tokens.ts
new file mode 100644
index 000000000000..e587113ab7f5
--- /dev/null
+++ b/test/e2e/page-objects/pages/dialog/add-tokens.ts
@@ -0,0 +1,50 @@
+import { strict as assert } from 'assert';
+import { Driver } from '../../../webdriver/driver';
+
+class AddTokensModal {
+ protected driver: Driver;
+
+ private addTokenButton = { text: 'Add token', tag: 'button' };
+
+ private tokenListItem = '.confirm-add-suggested-token__token-list-item';
+
+ constructor(driver: Driver) {
+ this.driver = driver;
+ }
+
+ async check_pageIsLoaded(): Promise {
+ try {
+ await this.driver.waitForMultipleSelectors([
+ this.tokenListItem,
+ this.addTokenButton,
+ ]);
+ } catch (e) {
+ console.log(
+ 'Timeout while waiting for Add tokens dialog to be loaded',
+ e,
+ );
+ throw e;
+ }
+ console.log('Add tokens dialog was loaded');
+ }
+
+ /**
+ * Checks the count of suggested tokens.
+ *
+ * @param expectedTokenCount - The expected count of suggested tokens.
+ */
+ async check_SuggestedTokensCount(expectedTokenCount: number) {
+ const multipleSuggestedTokens = await this.driver.findElements(
+ this.tokenListItem,
+ );
+
+ // Confirm the expected number of tokens are present as suggested token list
+ assert.equal(multipleSuggestedTokens.length, expectedTokenCount);
+ }
+
+ async confirmAddTokens() {
+ await this.driver.clickElementAndWaitForWindowToClose(this.addTokenButton);
+ }
+}
+
+export default AddTokensModal;
diff --git a/test/e2e/page-objects/pages/dialog/create-contract.ts b/test/e2e/page-objects/pages/dialog/create-contract.ts
new file mode 100644
index 000000000000..44c101271ffe
--- /dev/null
+++ b/test/e2e/page-objects/pages/dialog/create-contract.ts
@@ -0,0 +1,39 @@
+import { Driver } from '../../../webdriver/driver';
+
+class CreateContractModal {
+ protected driver: Driver;
+
+ private readonly confirmButtton = { text: 'Confirm', tag: 'button' };
+
+ private readonly cancelButton = { text: 'Cancel', tag: 'button' };
+
+ constructor(driver: Driver) {
+ this.driver = driver;
+ }
+
+ async check_pageIsLoaded(): Promise {
+ try {
+ await this.driver.waitForMultipleSelectors([
+ this.confirmButtton,
+ this.cancelButton,
+ ]);
+ } catch (e) {
+ console.log(
+ 'Timeout while waiting for create contract dialog to be loaded',
+ e,
+ );
+ throw e;
+ }
+ console.log('Create contract dialog was loaded');
+ }
+
+ async clickConfirm() {
+ await this.driver.clickElementAndWaitForWindowToClose(this.confirmButtton);
+ }
+
+ async clickCancel() {
+ await this.driver.clickElementAndWaitForWindowToClose(this.cancelButton);
+ }
+}
+
+export default CreateContractModal;
diff --git a/test/e2e/page-objects/pages/home/asset-list.ts b/test/e2e/page-objects/pages/home/asset-list.ts
index db9367991bf7..9db896e3a035 100644
--- a/test/e2e/page-objects/pages/home/asset-list.ts
+++ b/test/e2e/page-objects/pages/home/asset-list.ts
@@ -204,6 +204,21 @@ class AssetListPage {
});
}
+ /**
+ * This function checks if the specified token is displayed in the token list by its name.
+ *
+ * @param tokenName - The name of the token to check for.
+ * @returns A promise that resolves if the specified token is displayed.
+ */
+ async check_tokenIsDisplayed(tokenName: string): Promise {
+ console.log(`Waiting for token ${tokenName} to be displayed`);
+ await this.driver.waitForSelector({
+ text: tokenName,
+ tag: 'p',
+ });
+ console.log(`Token ${tokenName} is displayed.`);
+ }
+
/**
* This function checks if the specified number of token items is displayed in the token list.
*
diff --git a/test/e2e/page-objects/pages/test-dapp.ts b/test/e2e/page-objects/pages/test-dapp.ts
index 6b82926fd9ca..5471afb3556a 100644
--- a/test/e2e/page-objects/pages/test-dapp.ts
+++ b/test/e2e/page-objects/pages/test-dapp.ts
@@ -8,6 +8,11 @@ const DAPP_URL = `http://${DAPP_HOST_ADDRESS}`;
class TestDapp {
private driver: Driver;
+ private readonly addTokensToWalletButton = {
+ text: 'Add Token(s) to Wallet',
+ tag: 'button',
+ };
+
private readonly confirmDepositButton =
'[data-testid="confirm-footer-button"]';
@@ -38,6 +43,8 @@ class TestDapp {
private readonly simpleSendButton = '#sendButton';
+ private readonly erc20TokenAddresses = '#erc20TokenAddresses';
+
private readonly erc721MintButton = '#mintButton';
private readonly erc721TransferFromButton = '#transferFromButton';
@@ -177,6 +184,11 @@ class TestDapp {
private erc20TokenTransferButton = '#transferTokens';
+ private createTokenButton = {
+ text: 'Create Token',
+ tag: 'button',
+ };
+
constructor(driver: Driver) {
this.driver = driver;
}
@@ -221,6 +233,10 @@ class TestDapp {
});
}
+ public async clickAddTokenToWallet() {
+ await this.driver.clickElement(this.addTokensToWalletButton);
+ }
+
async clickSimpleSendButton() {
await this.driver.clickElement(this.simpleSendButton);
}
@@ -339,7 +355,18 @@ class TestDapp {
}
/**
- * Check if the accounts connected to the test dapp.
+ * Scrolls to the create token button and clicks it.
+ */
+ public async findAndClickCreateToken() {
+ const createTokenElement = await this.driver.findElement(
+ this.createTokenButton,
+ );
+ await this.driver.scrollToElement(createTokenElement);
+ await this.driver.clickElement(this.createTokenButton);
+ }
+
+ /**
+ * Verifies the accounts connected to the test dapp.
*
* @param connectedAccounts - Account addresses to check if connected to test dapp, separated by a comma.
* @param shouldBeConnected - Whether the accounts should be connected to test dapp. Defaults to true.
@@ -599,6 +626,24 @@ class TestDapp {
});
}
+ /**
+ * Checks the count of token addresses.
+ *
+ * @param expectedCount - The expected count of token addresses.
+ */
+ async check_TokenAddressesCount(expectedCount: number) {
+ console.log(`checking token addresses count: ${expectedCount}`);
+ await this.driver.wait(async () => {
+ const tokenAddressesElement = await this.driver.findElement(
+ this.erc20TokenAddresses,
+ );
+ const tokenAddresses = await tokenAddressesElement.getText();
+ const addresses = tokenAddresses.split(',').filter(Boolean);
+
+ return addresses.length === expectedCount;
+ }, 10000);
+ }
+
async verify_successSignTypedDataV4Result(result: string) {
await this.driver.waitForSelector({
css: this.signTypedDataV4Result,
diff --git a/test/e2e/tests/tokens/add-multiple-tokens.spec.js b/test/e2e/tests/tokens/add-multiple-tokens.spec.js
deleted file mode 100644
index bc6ea5e6f2c0..000000000000
--- a/test/e2e/tests/tokens/add-multiple-tokens.spec.js
+++ /dev/null
@@ -1,114 +0,0 @@
-const { strict: assert } = require('assert');
-const {
- withFixtures,
- defaultGanacheOptions,
- openDapp,
- switchToNotificationWindow,
- WINDOW_TITLES,
- DAPP_URL,
- unlockWallet,
-} = require('../../helpers');
-const FixtureBuilder = require('../../fixture-builder');
-
-describe('Multiple ERC20 Watch Asset', function () {
- // TODO: This assertion will change once the method is fixed.
- it('should show multiple erc20 watchAsset token list, only confirms one bug', async function () {
- await withFixtures(
- {
- dapp: true,
- fixtures: new FixtureBuilder()
- .withPermissionControllerConnectedToTestDapp()
- .build(),
- ganacheOptions: defaultGanacheOptions,
- title: this.test.fullTitle(),
- },
- async ({ driver }) => {
- await unlockWallet(driver);
-
- await openDapp(driver, undefined, DAPP_URL);
-
- // Create Token 1
- const createToken = await driver.findElement({
- text: 'Create Token',
- tag: 'button',
- });
- await driver.scrollToElement(createToken);
- await driver.clickElement({ text: 'Create Token', tag: 'button' });
- await switchToNotificationWindow(driver);
- await driver.findClickableElement({ text: 'Confirm', tag: 'button' });
- await driver.clickElement({ text: 'Confirm', tag: 'button' });
-
- // Wait for token 1 address to populate in dapp
- await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp);
- await driver.wait(async () => {
- const tokenAddressesElement = await driver.findElement(
- '#erc20TokenAddresses',
- );
- const tokenAddresses = await tokenAddressesElement.getText();
- return tokenAddresses !== '';
- }, 10000);
-
- // Create Token 2
- await driver.clickElement({ text: 'Create Token', tag: 'button' });
- await switchToNotificationWindow(driver);
- await driver.findClickableElement({ text: 'Confirm', tag: 'button' });
- await driver.clickElement({ text: 'Confirm', tag: 'button' });
-
- // Wait for token 2 address to populate in dapp
- await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp);
- await driver.wait(async () => {
- const tokenAddressesElement = await driver.findElement(
- '#erc20TokenAddresses',
- );
- const tokenAddresses = await tokenAddressesElement.getText();
- return tokenAddresses.split(',').length === 2;
- }, 10000);
-
- // Create Token 3
- await driver.clickElement({ text: 'Create Token', tag: 'button' });
- await switchToNotificationWindow(driver);
- await driver.findClickableElement({ text: 'Confirm', tag: 'button' });
- await driver.clickElement({ text: 'Confirm', tag: 'button' });
-
- // Wait for token 3 address to populate in dapp
- await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp);
- await driver.wait(async () => {
- const tokenAddressesElement = await driver.findElement(
- '#erc20TokenAddresses',
- );
- const tokenAddresses = await tokenAddressesElement.getText();
- return tokenAddresses.split(',').length === 3;
- }, 10000);
-
- // Watch all 3 tokens
- await driver.clickElement({
- text: 'Add Token(s) to Wallet',
- tag: 'button',
- });
-
- // Switch to watchAsset notification
- await switchToNotificationWindow(driver);
- const multipleSuggestedtokens = await driver.findElements(
- '.confirm-add-suggested-token__token-list-item',
- );
-
- // Confirm all 3 tokens are present as suggested token list
- assert.equal(multipleSuggestedtokens.length, 3);
- await driver.findClickableElement({ text: 'Add token', tag: 'button' });
- await driver.clickElement({ text: 'Add token', tag: 'button' });
-
- // Switch to fullscreen extension
- await driver.switchToWindowWithTitle(
- WINDOW_TITLES.ExtensionInFullScreenView,
- );
-
- // Check all three tokens have been added to the token list.
- const addedTokens = await driver.findElements({
- tag: 'p',
- text: 'TST',
- });
- assert.equal(addedTokens.length, 3);
- },
- );
- });
-});
diff --git a/test/e2e/tests/tokens/add-multiple-tokens.spec.ts b/test/e2e/tests/tokens/add-multiple-tokens.spec.ts
new file mode 100644
index 000000000000..78daa2531da1
--- /dev/null
+++ b/test/e2e/tests/tokens/add-multiple-tokens.spec.ts
@@ -0,0 +1,70 @@
+import AddTokensModal from '../../page-objects/pages/dialog/add-tokens';
+import AssetListPage from '../../page-objects/pages/home/asset-list';
+import TestDapp from '../../page-objects/pages/test-dapp';
+import {
+ withFixtures,
+ defaultGanacheOptions,
+ openDapp,
+ WINDOW_TITLES,
+ DAPP_URL,
+ unlockWallet,
+} from '../../helpers';
+import FixtureBuilder from '../../fixture-builder';
+import CreateContractModal from '../../page-objects/pages/dialog/create-contract';
+
+describe('Multiple ERC20 Watch Asset', function () {
+ it('should show multiple erc20 watchAsset token list, only confirms one bug', async function () {
+ await withFixtures(
+ {
+ dapp: true,
+ fixtures: new FixtureBuilder()
+ .withPermissionControllerConnectedToTestDapp()
+ .build(),
+ ganacheOptions: defaultGanacheOptions,
+ title: this.test?.fullTitle(),
+ },
+ async ({ driver }) => {
+ await unlockWallet(driver);
+ await openDapp(driver, undefined, DAPP_URL);
+ const testDapp = new TestDapp(driver);
+
+ // Create multiple tokens
+ for (let i = 0; i < 3; i++) {
+ // Create token
+ await testDapp.findAndClickCreateToken();
+
+ // Confirm token creation
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
+ const createContractModal = new CreateContractModal(driver);
+ await createContractModal.check_pageIsLoaded();
+ await createContractModal.clickConfirm();
+
+ // Wait for token address to populate in dapp
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp);
+ await testDapp.check_pageIsLoaded();
+ await testDapp.check_TokenAddressesCount(i + 1);
+ }
+
+ // Watch all 3 tokens
+ // Switch to watchAsset notification
+ await testDapp.clickAddTokenToWallet();
+ await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
+ const addTokensPopupModal = new AddTokensModal(driver);
+ await addTokensPopupModal.check_pageIsLoaded();
+ await addTokensPopupModal.check_SuggestedTokensCount(3);
+ await addTokensPopupModal.confirmAddTokens();
+
+ // Switch to fullscreen extension
+ await driver.switchToWindowWithTitle(
+ WINDOW_TITLES.ExtensionInFullScreenView,
+ );
+
+ // Check all three tokens have been added to the token list.
+ const tokenList = new AssetListPage(driver);
+ await tokenList.check_tokenItemNumber(4); // 3 tokens plus ETH
+ await tokenList.check_tokenIsDisplayed('Ethereum');
+ await tokenList.check_tokenIsDisplayed('TST');
+ },
+ );
+ });
+});
From f8a1d4fca314c890fd03f6d76d98183052a9fe7f Mon Sep 17 00:00:00 2001
From: Michele Esposito <34438276+mikesposito@users.noreply.github.com>
Date: Wed, 18 Dec 2024 18:45:58 +0100
Subject: [PATCH 030/202] fix: block tracker stops polling when switching away
from the network (#29045)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
## **Description**
This PR bumps `@metamask/network-controller` and
`@metamask/eth-json-rpc-middleware` by a patch version to fix an issue
related to `@metamask/eth-block-tracker`, used to listen to new blocks
emitted by networks.
[![Open in GitHub
Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29045?quickstart=1)
## **Related issues**
Fixes: #17040
## **Manual testing steps**
This only applies to views that show a single chain: since now the
wallet home shows all networks, requests will be fired regardless of the
globally selected network. To test this, the chain filter in the home
should be set to a specific chain, instead of all
1. Add a local ganache network
2. Turn off the local ganache server
3. Observe requests failing when navigating to the home of the wallet
(while showing all networks)
4. Filter a single chain which is not the local one (e.g. mainnet)
5. Polling to localhost should stop
## **Screenshots/Recordings**
### **Before**
### **After**
## **Pre-merge author checklist**
- [ ] I've followed [MetaMask Contributor
Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask
Extension Coding
Standards](https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/CODING_GUIDELINES.md).
- [ ] I've completed the PR template to the best of my ability
- [ ] I’ve included tests if applicable
- [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format
if applicable
- [ ] I’ve applied the right labels on the PR (see [labeling
guidelines](https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/LABELING_GUIDELINES.md)).
Not required for external contributors.
## **Pre-merge reviewer checklist**
- [ ] I've manually tested the PR (e.g. pull and build branch, run the
app, test code being changed).
- [ ] I confirm that this PR addresses all acceptance criteria described
in the ticket it closes and includes the necessary testing evidence such
as recordings and or screenshots.
---
...rk-controller-npm-22.1.1-09b6510f1e.patch} | 7 ++-
package.json | 6 +--
yarn.lock | 50 +++++--------------
3 files changed, 18 insertions(+), 45 deletions(-)
rename .yarn/patches/{@metamask-network-controller-npm-22.1.0-621c281f70.patch => @metamask-network-controller-npm-22.1.1-09b6510f1e.patch} (85%)
diff --git a/.yarn/patches/@metamask-network-controller-npm-22.1.0-621c281f70.patch b/.yarn/patches/@metamask-network-controller-npm-22.1.1-09b6510f1e.patch
similarity index 85%
rename from .yarn/patches/@metamask-network-controller-npm-22.1.0-621c281f70.patch
rename to .yarn/patches/@metamask-network-controller-npm-22.1.1-09b6510f1e.patch
index 027c44b96395..a1a3e7401c0c 100644
--- a/.yarn/patches/@metamask-network-controller-npm-22.1.0-621c281f70.patch
+++ b/.yarn/patches/@metamask-network-controller-npm-22.1.1-09b6510f1e.patch
@@ -1,6 +1,6 @@
diff --git a/PATCH.txt b/PATCH.txt
new file mode 100644
-index 0000000000000000000000000000000000000000..ce3b18534f055ee00aa5821793f855fd300fb72c
+index 0000000000000000000000000000000000000000..376255036ca54b83a3f3c3e0277c2666473df30e
--- /dev/null
+++ b/PATCH.txt
@@ -0,0 +1,4 @@
@@ -8,9 +8,8 @@ index 0000000000000000000000000000000000000000..ce3b18534f055ee00aa5821793f855fd
+The network lookup is done after onboarding is completed, and when the extension reloads if onboarding has been completed.
+This patch is part of a temporary fix that will be reverted soon to make way for a more permanent solution. https://github.com/MetaMask/metamask-extension/pull/23005
+You can see the changes before compilation on this branch: https://github.com/MetaMask/core/compare/pnf/ext-23622-review?expand=1
-\ No newline at end of file
diff --git a/dist/NetworkController.cjs b/dist/NetworkController.cjs
-index cc9793f576eb39a51ab141b7d03de57cf99e5570..c573b5134d40f522217a6ab6df129040d02e9660 100644
+index cc9793f576eb39a51ab141b7d03de57cf99e5570..184153067f2bbd58ea76d7db33c2af56245cd8c0 100644
--- a/dist/NetworkController.cjs
+++ b/dist/NetworkController.cjs
@@ -422,7 +422,6 @@ class NetworkController extends base_controller_1.BaseController {
@@ -22,7 +21,7 @@ index cc9793f576eb39a51ab141b7d03de57cf99e5570..c573b5134d40f522217a6ab6df129040
/**
* Refreshes the network meta with EIP-1559 support and the network status
diff --git a/dist/NetworkController.mjs b/dist/NetworkController.mjs
-index 806f32edeffaad9f7eb1cafa4184368ec95f63e7..9268947cbed4bf717729ca6ac8ea83a8b91b6e8a 100644
+index 806f32edeffaad9f7eb1cafa4184368ec95f63e7..7ba60e613ec8de7d273c32282be564f36873cbd2 100644
--- a/dist/NetworkController.mjs
+++ b/dist/NetworkController.mjs
@@ -397,7 +397,6 @@ export class NetworkController extends BaseController {
diff --git a/package.json b/package.json
index c3e6ff9eac24..31fed3a39b52 100644
--- a/package.json
+++ b/package.json
@@ -237,7 +237,7 @@
"@expo/config-plugins/glob": "^10.3.10",
"@solana/web3.js/rpc-websockets": "^8.0.1",
"@metamask/nonce-tracker@npm:^5.0.0": "patch:@metamask/nonce-tracker@npm%3A5.0.0#~/.yarn/patches/@metamask-nonce-tracker-npm-5.0.0-d81478218e.patch",
- "@metamask/network-controller@npm:^22.0.0": "patch:@metamask/network-controller@npm%3A22.1.0#~/.yarn/patches/@metamask-network-controller-npm-22.1.0-621c281f70.patch",
+ "@metamask/network-controller@npm:^22.0.2": "patch:@metamask/network-controller@npm%3A22.1.1#~/.yarn/patches/@metamask-network-controller-npm-22.1.1-09b6510f1e.patch",
"path-to-regexp": "1.9.0",
"@ledgerhq/cryptoassets-evm-signatures/axios": "^0.28.0",
"@ledgerhq/domain-service/axios": "^0.28.0",
@@ -296,7 +296,7 @@
"@metamask/ens-controller": "^15.0.0",
"@metamask/ens-resolver-snap": "^0.1.2",
"@metamask/eth-json-rpc-filters": "^9.0.0",
- "@metamask/eth-json-rpc-middleware": "^15.0.0",
+ "@metamask/eth-json-rpc-middleware": "^15.0.1",
"@metamask/eth-ledger-bridge-keyring": "^5.0.1",
"@metamask/eth-query": "^4.0.0",
"@metamask/eth-sig-util": "^7.0.1",
@@ -321,7 +321,7 @@
"@metamask/message-signing-snap": "^0.6.0",
"@metamask/metamask-eth-abis": "^3.1.1",
"@metamask/name-controller": "^8.0.0",
- "@metamask/network-controller": "patch:@metamask/network-controller@npm%3A22.1.0#~/.yarn/patches/@metamask-network-controller-npm-22.1.0-621c281f70.patch",
+ "@metamask/network-controller": "patch:@metamask/network-controller@npm%3A22.1.1#~/.yarn/patches/@metamask-network-controller-npm-22.1.1-09b6510f1e.patch",
"@metamask/notification-services-controller": "^0.15.0",
"@metamask/object-multiplex": "^2.0.0",
"@metamask/obs-store": "^9.0.0",
diff --git a/yarn.lock b/yarn.lock
index 0b959e498c51..7bfb4b6e4336 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -5221,7 +5221,7 @@ __metadata:
languageName: node
linkType: hard
-"@metamask/eth-block-tracker@npm:^11.0.2, @metamask/eth-block-tracker@npm:^11.0.3":
+"@metamask/eth-block-tracker@npm:^11.0.3":
version: 11.0.3
resolution: "@metamask/eth-block-tracker@npm:11.0.3"
dependencies:
@@ -5272,7 +5272,7 @@ __metadata:
languageName: node
linkType: hard
-"@metamask/eth-json-rpc-middleware@npm:^15.0.0, @metamask/eth-json-rpc-middleware@npm:^15.0.1":
+"@metamask/eth-json-rpc-middleware@npm:^15.0.1":
version: 15.0.1
resolution: "@metamask/eth-json-rpc-middleware@npm:15.0.1"
dependencies:
@@ -5785,33 +5785,7 @@ __metadata:
languageName: node
linkType: hard
-"@metamask/network-controller@npm:22.1.0":
- version: 22.1.0
- resolution: "@metamask/network-controller@npm:22.1.0"
- dependencies:
- "@metamask/base-controller": "npm:^7.0.2"
- "@metamask/controller-utils": "npm:^11.4.4"
- "@metamask/eth-block-tracker": "npm:^11.0.2"
- "@metamask/eth-json-rpc-infura": "npm:^10.0.0"
- "@metamask/eth-json-rpc-middleware": "npm:^15.0.0"
- "@metamask/eth-json-rpc-provider": "npm:^4.1.6"
- "@metamask/eth-query": "npm:^4.0.0"
- "@metamask/json-rpc-engine": "npm:^10.0.1"
- "@metamask/rpc-errors": "npm:^7.0.1"
- "@metamask/swappable-obj-proxy": "npm:^2.2.0"
- "@metamask/utils": "npm:^10.0.0"
- async-mutex: "npm:^0.5.0"
- fast-deep-equal: "npm:^3.1.3"
- immer: "npm:^9.0.6"
- loglevel: "npm:^1.8.1"
- reselect: "npm:^5.1.1"
- uri-js: "npm:^4.4.1"
- uuid: "npm:^8.3.2"
- checksum: 10/587ad3eba45e898d83a06c800c1649c1dc1ae708c53114a2e8a82bb18fc3773eaca2abc9b8f2afd46106aa96e22a28e79d2d5bd620d9de1606e5180b5a8996df
- languageName: node
- linkType: hard
-
-"@metamask/network-controller@npm:^22.1.1":
+"@metamask/network-controller@npm:22.1.1, @metamask/network-controller@npm:^22.1.1":
version: 22.1.1
resolution: "@metamask/network-controller@npm:22.1.1"
dependencies:
@@ -5837,20 +5811,20 @@ __metadata:
languageName: node
linkType: hard
-"@metamask/network-controller@patch:@metamask/network-controller@npm%3A22.1.0#~/.yarn/patches/@metamask-network-controller-npm-22.1.0-621c281f70.patch":
- version: 22.1.0
- resolution: "@metamask/network-controller@patch:@metamask/network-controller@npm%3A22.1.0#~/.yarn/patches/@metamask-network-controller-npm-22.1.0-621c281f70.patch::version=22.1.0&hash=93e992"
+"@metamask/network-controller@patch:@metamask/network-controller@npm%3A22.1.1#~/.yarn/patches/@metamask-network-controller-npm-22.1.1-09b6510f1e.patch":
+ version: 22.1.1
+ resolution: "@metamask/network-controller@patch:@metamask/network-controller@npm%3A22.1.1#~/.yarn/patches/@metamask-network-controller-npm-22.1.1-09b6510f1e.patch::version=22.1.1&hash=64b409"
dependencies:
"@metamask/base-controller": "npm:^7.0.2"
"@metamask/controller-utils": "npm:^11.4.4"
- "@metamask/eth-block-tracker": "npm:^11.0.2"
+ "@metamask/eth-block-tracker": "npm:^11.0.3"
"@metamask/eth-json-rpc-infura": "npm:^10.0.0"
- "@metamask/eth-json-rpc-middleware": "npm:^15.0.0"
+ "@metamask/eth-json-rpc-middleware": "npm:^15.0.1"
"@metamask/eth-json-rpc-provider": "npm:^4.1.6"
"@metamask/eth-query": "npm:^4.0.0"
"@metamask/json-rpc-engine": "npm:^10.0.1"
"@metamask/rpc-errors": "npm:^7.0.1"
- "@metamask/swappable-obj-proxy": "npm:^2.2.0"
+ "@metamask/swappable-obj-proxy": "npm:^2.3.0"
"@metamask/utils": "npm:^10.0.0"
async-mutex: "npm:^0.5.0"
fast-deep-equal: "npm:^3.1.3"
@@ -5859,7 +5833,7 @@ __metadata:
reselect: "npm:^5.1.1"
uri-js: "npm:^4.4.1"
uuid: "npm:^8.3.2"
- checksum: 10/21427a6bc57fa318dadd223342d3eda07c077705ef641c3448fb4dc438fb9f87e730e0bd5f7c9fc3f785bfce87c6a52f9b4ae5e17d933ecb5792e0bdc5ea4746
+ checksum: 10/ffe5156f3129a8c09242c01d477a6e1149e42f8884c721f03b51fc64a933cc6324d922f20cef6954e974ee3d7a2433d9c72a72b0899d83aa68ecf775d4edd4dc
languageName: node
linkType: hard
@@ -26587,7 +26561,7 @@ __metadata:
"@metamask/eslint-config-typescript": "npm:^9.0.1"
"@metamask/eslint-plugin-design-tokens": "npm:^1.1.0"
"@metamask/eth-json-rpc-filters": "npm:^9.0.0"
- "@metamask/eth-json-rpc-middleware": "npm:^15.0.0"
+ "@metamask/eth-json-rpc-middleware": "npm:^15.0.1"
"@metamask/eth-json-rpc-provider": "npm:^4.1.6"
"@metamask/eth-ledger-bridge-keyring": "npm:^5.0.1"
"@metamask/eth-query": "npm:^4.0.0"
@@ -26614,7 +26588,7 @@ __metadata:
"@metamask/message-signing-snap": "npm:^0.6.0"
"@metamask/metamask-eth-abis": "npm:^3.1.1"
"@metamask/name-controller": "npm:^8.0.0"
- "@metamask/network-controller": "patch:@metamask/network-controller@npm%3A22.1.0#~/.yarn/patches/@metamask-network-controller-npm-22.1.0-621c281f70.patch"
+ "@metamask/network-controller": "patch:@metamask/network-controller@npm%3A22.1.1#~/.yarn/patches/@metamask-network-controller-npm-22.1.1-09b6510f1e.patch"
"@metamask/notification-services-controller": "npm:^0.15.0"
"@metamask/object-multiplex": "npm:^2.0.0"
"@metamask/obs-store": "npm:^9.0.0"
From f05425713723e74a6cba5ab86ce5f7dcbc709e4e Mon Sep 17 00:00:00 2001
From: George Marshall
Date: Wed, 18 Dec 2024 12:43:54 -0800
Subject: [PATCH 031/202] chore: updating menu item to use text component
(#29304)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
## **Description**
This PR updates the MenuItem component to better align with our design
system by:
1. Implementing responsive text sizes - 14px for small screens and 16px
for large screens
2. Adjusting the padding to improve visual spacing and alignment
These changes improve consistency across the application and enhance the
overall user experience by providing better readability and spacing
across different device sizes.
1. Reason for change: MenuItem text size and padding are not following
our design system guidelines
2. Solution: Update text sizing and padding using our design system's
specifications
[![Open in GitHub
Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29304?quickstart=1)
## **Related issues**
Fixes: https://github.com/MetaMask/metamask-extension/issues/26668
## **Manual testing steps**
1. Open MetaMask extension
2. Click on any menu with MenuItems
3. Verify text is 14px on small screens (< 768px)
4. Verify text is 16px on large screens (≥ 768px)
5. Verify padding provides proper spacing around menu items
6. Verify all alignments and spacing remain consistent with icons and
subtitles
## **Screenshots/Recordings**
### **Before**
Menu item is locked to 16px font size and y:14px x:16px padding
https://github.com/user-attachments/assets/6978a281-7de2-4141-a664-117952f63195
### **After**
Menu item uses responsive typography to 14px/16px font size and y:16px
x:16px padding
https://github.com/user-attachments/assets/0dd13e70-0be2-4f85-8399-6337ce379e02
Other menus using the `MenuItem` still function as expected in both
expanded and popup views.
https://github.com/user-attachments/assets/4d3a215a-6778-452d-84e1-535bb10b9b2e
## **Pre-merge author checklist**
- [x] I've followed [MetaMask Contributor
Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask
Extension Coding
Standards](https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/CODING_GUIDELINES.md).
- [x] I've completed the PR template to the best of my ability
- [x] I've included tests if applicable
- [x] I've documented my code using [JSDoc](https://jsdoc.app/) format
if applicable
- [x] I've applied the right labels on the PR (see [labeling
guidelines](https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/LABELING_GUIDELINES.md)).
Not required for external contributors.
## **Pre-merge reviewer checklist**
- [ ] I've manually tested the PR (e.g. pull and build branch, run the
app, test code being changed).
- [ ] I confirm that this PR addresses all acceptance criteria described
in the ticket it closes and includes the necessary testing evidence such
as recordings and or screenshots.
---
ui/components/ui/menu/menu-item.js | 2 +-
ui/components/ui/menu/menu.scss | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/ui/components/ui/menu/menu-item.js b/ui/components/ui/menu/menu-item.js
index 6b501d6d7709..275515a8f3fd 100644
--- a/ui/components/ui/menu/menu-item.js
+++ b/ui/components/ui/menu/menu-item.js
@@ -66,7 +66,7 @@ const MenuItem = React.forwardRef(
/>
)}
-
{children}
+ {children}
{subtitle ? {subtitle} : null}
diff --git a/ui/components/ui/menu/menu.scss b/ui/components/ui/menu/menu.scss
index d43545e67fd0..2eed45745d9e 100644
--- a/ui/components/ui/menu/menu.scss
+++ b/ui/components/ui/menu/menu.scss
@@ -33,7 +33,7 @@
text-align: start;
align-items: center;
width: 100%;
- padding: 14px 16px;
+ padding: 16px;
cursor: pointer;
color: inherit;
From c8a3c58dff76890f3ca0302daa7799db15d35463 Mon Sep 17 00:00:00 2001
From: David Walsh
Date: Wed, 18 Dec 2024 17:38:47 -0600
Subject: [PATCH 032/202] fix: MMASSETS-475 - Add network name to asset details
page (#29211)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
## **Description**
Adds the network name to the assets details page so the user can be sure
to know what network a given asset lives on.
[![Open in GitHub
Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29211?quickstart=1)
## **Related issues**
Fixes: https://consensyssoftware.atlassian.net/browse/MMASSETS-475
## **Manual testing steps**
1. Click any number of asset items in the asset list
2. Ensure that the network name that displays matches the asset network
and images
## **Screenshots/Recordings**
### **Before**
### **After**
## **Pre-merge author checklist**
- [ ] I've followed [MetaMask Contributor
Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask
Extension Coding
Standards](https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/CODING_GUIDELINES.md).
- [ ] I've completed the PR template to the best of my ability
- [ ] I’ve included tests if applicable
- [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format
if applicable
- [ ] I’ve applied the right labels on the PR (see [labeling
guidelines](https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/LABELING_GUIDELINES.md)).
Not required for external contributors.
## **Pre-merge reviewer checklist**
- [ ] I've manually tested the PR (e.g. pull and build branch, run the
app, test code being changed).
- [ ] I confirm that this PR addresses all acceptance criteria described
in the ticket it closes and includes the necessary testing evidence such
as recordings and or screenshots.
---
.../__snapshots__/asset-page.test.tsx.snap | 72 +++++++++++++++++++
ui/pages/asset/components/asset-page.test.tsx | 17 ++++-
ui/pages/asset/components/asset-page.tsx | 31 +++++++-
3 files changed, 118 insertions(+), 2 deletions(-)
diff --git a/ui/pages/asset/components/__snapshots__/asset-page.test.tsx.snap b/ui/pages/asset/components/__snapshots__/asset-page.test.tsx.snap
index 6a55226a42cb..e4bc078a8c2b 100644
--- a/ui/pages/asset/components/__snapshots__/asset-page.test.tsx.snap
+++ b/ui/pages/asset/components/__snapshots__/asset-page.test.tsx.snap
@@ -262,6 +262,30 @@ exports[`AssetPage should render a native asset 1`] = `
+
+
+ Network:
+
+
+
+
+
+ Ethereum Mainnet
+
+
@@ -581,6 +605,30 @@ exports[`AssetPage should render an ERC20 asset without prices 1`] = `
+
+
+ Network:
+
+
+
+
+
+ Ethereum Mainnet
+
+
@@ -1086,6 +1134,30 @@ exports[`AssetPage should render an ERC20 token with prices 1`] = `
+
+
+ Network:
+
+
+
+
+
+ Ethereum Mainnet
+
+
diff --git a/ui/pages/asset/components/asset-page.test.tsx b/ui/pages/asset/components/asset-page.test.tsx
index 71ca5483b50c..3918003f64b5 100644
--- a/ui/pages/asset/components/asset-page.test.tsx
+++ b/ui/pages/asset/components/asset-page.test.tsx
@@ -4,7 +4,10 @@ import thunk from 'redux-thunk';
import { fireEvent, waitFor } from '@testing-library/react';
import { EthAccountType } from '@metamask/keyring-api';
import nock from 'nock';
-import { CHAIN_IDS } from '../../../../shared/constants/network';
+import {
+ CHAIN_IDS,
+ MAINNET_DISPLAY_NAME,
+} from '../../../../shared/constants/network';
import { renderWithProvider } from '../../../../test/jest/rendering';
import { KeyringType } from '../../../../shared/constants/keyring';
import { AssetType } from '../../../../shared/constants/transaction';
@@ -327,6 +330,18 @@ describe('AssetPage', () => {
expect(mmiPortfolioButton).toBeInTheDocument();
});
+ it('should render the network name', async () => {
+ const mockedStore = configureMockStore([thunk])(mockStore);
+
+ const { queryByTestId } = renderWithProvider(
+ ,
+ mockedStore,
+ );
+ const networkNode = queryByTestId('asset-network');
+ expect(networkNode).toBeInTheDocument();
+ expect(networkNode?.textContent).toBe(MAINNET_DISPLAY_NAME);
+ });
+
it('should render a native asset', () => {
const { container } = renderWithProvider(
,
diff --git a/ui/pages/asset/components/asset-page.tsx b/ui/pages/asset/components/asset-page.tsx
index c26f2d025f70..ad3f54761798 100644
--- a/ui/pages/asset/components/asset-page.tsx
+++ b/ui/pages/asset/components/asset-page.tsx
@@ -21,6 +21,7 @@ import {
getShowFiatInTestnets,
} from '../../../selectors';
import {
+ AlignItems,
Display,
FlexDirection,
IconColor,
@@ -29,6 +30,8 @@ import {
TextVariant,
} from '../../../helpers/constants/design-system';
import {
+ AvatarNetwork,
+ AvatarNetworkSize,
Box,
ButtonIcon,
ButtonIconSize,
@@ -54,7 +57,11 @@ import { getIsNativeTokenBuyable } from '../../../ducks/ramps';
import { calculateTokenBalance } from '../../../components/app/assets/util/calculateTokenBalance';
import { useTokenBalances } from '../../../hooks/useTokenBalances';
import { useMultichainSelector } from '../../../hooks/useMultichainSelector';
-import { getMultichainShouldShowFiat } from '../../../selectors/multichain';
+import {
+ getImageForChainId,
+ getMultichainShouldShowFiat,
+} from '../../../selectors/multichain';
+import { getNetworkConfigurationsByChainId } from '../../../../shared/modules/selectors/networks';
import { getPortfolioUrl } from '../../../helpers/utils/portfolio';
import { hexToDecimal } from '../../../../shared/modules/conversion.utils';
import AssetChart from './chart/asset-chart';
@@ -214,6 +221,12 @@ const AssetPage = ({
[account.address, isMarketingEnabled, isMetaMetricsEnabled, metaMetricsId],
);
+ const networkConfigurationsByChainId = useSelector(
+ getNetworkConfigurationsByChainId,
+ );
+ const networkName = networkConfigurationsByChainId[chainId]?.name;
+ const tokenChainImage = getImageForChainId(chainId);
+
return (
+ {renderRow(
+ t('network'),
+
+
+ {networkName}
+ ,
+ )}
{type === AssetType.token && (
{renderRow(
From fd3366caba51fc512d217b44e5bb25227330adc1 Mon Sep 17 00:00:00 2001
From: Prithpal Sooriya
Date: Thu, 19 Dec 2024 10:35:50 +0000
Subject: [PATCH 033/202] fix: handle notification api calls settings (#29327)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
## **Description**
Adds effect checks; removes old hooks unused; and adds tests.
I really HATE the useFootguns we have everywhere for notifications. I
want us to do a wider cleanup task.
1. Remove the Provider values. We can keep the provider to run initial
effects, but other than that, we should not expose anything from it!
2. Remove nearly all of the useEffects scattered across all the
components. Instead we can utilise 1 reusable hook for fetching.
- potentially we can expose this in the provider if we really want to
fetch only once!
[![Open in GitHub
Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29327?quickstart=1)
## **Related issues**
Fixes: https://github.com/MetaMask/metamask-extension/issues/28173
## **Manual testing steps**
1. Go through onboarding, and settings pages > Notifications
2. Check console and network tab, are we making API calls and throwing
errors in console?
## **Screenshots/Recordings**
### **Before**
### **After**
## **Pre-merge author checklist**
- [x] I've followed [MetaMask Contributor
Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask
Extension Coding
Standards](https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/CODING_GUIDELINES.md).
- [x] I've completed the PR template to the best of my ability
- [x] I’ve included tests if applicable
- [x] I’ve documented my code using [JSDoc](https://jsdoc.app/) format
if applicable
- [x] I’ve applied the right labels on the PR (see [labeling
guidelines](https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/LABELING_GUIDELINES.md)).
Not required for external contributors.
## **Pre-merge reviewer checklist**
- [ ] I've manually tested the PR (e.g. pull and build branch, run the
app, test code being changed).
- [ ] I confirm that this PR addresses all acceptance criteria described
in the ticket it closes and includes the necessary testing evidence such
as recordings and or screenshots.
---
.../useSwitchNotifications.test.tsx | 256 +++++++++++-------
.../useSwitchNotifications.ts | 59 +---
...fications-settings-allow-notifications.tsx | 2 +-
3 files changed, 168 insertions(+), 149 deletions(-)
diff --git a/ui/hooks/metamask-notifications/useSwitchNotifications.test.tsx b/ui/hooks/metamask-notifications/useSwitchNotifications.test.tsx
index b67c2ea80cef..b228698f391e 100644
--- a/ui/hooks/metamask-notifications/useSwitchNotifications.test.tsx
+++ b/ui/hooks/metamask-notifications/useSwitchNotifications.test.tsx
@@ -1,125 +1,183 @@
-import React from 'react';
-import { Provider } from 'react-redux';
-import { renderHook, act } from '@testing-library/react-hooks';
-import configureStore from 'redux-mock-store';
-import thunk from 'redux-thunk';
-import type { Store } from 'redux';
-import * as actions from '../../store/actions';
-import { MetamaskNotificationsProvider } from '../../contexts/metamask-notifications/metamask-notifications';
+import { waitFor } from '@testing-library/react';
+import * as ActionsModule from '../../store/actions';
+import * as NotificationSelectorsModule from '../../selectors/metamask-notifications/metamask-notifications';
+import { renderHookWithProviderTyped } from '../../../test/lib/render-helpers';
import {
useSwitchFeatureAnnouncementsChange,
- useSwitchAccountNotifications,
useSwitchAccountNotificationsChange,
+ useAccountSettingsProps,
} from './useSwitchNotifications';
-const middlewares = [thunk];
-const mockStore = configureStore(middlewares);
-
-jest.mock('../../store/actions', () => ({
- setFeatureAnnouncementsEnabled: jest.fn(),
- checkAccountsPresence: jest.fn(),
- updateOnChainTriggersByAccount: jest.fn(),
- deleteOnChainTriggersByAccount: jest.fn(),
- showLoadingIndication: jest.fn(),
- hideLoadingIndication: jest.fn(),
- fetchAndUpdateMetamaskNotifications: jest.fn(),
-}));
-
-describe('useSwitchNotifications', () => {
- let store: Store;
-
+describe('useSwitchFeatureAnnouncementsChange() tests', () => {
beforeEach(() => {
- store = mockStore({
- metamask: {
- isFeatureAnnouncementsEnabled: false,
- internalAccounts: {
- accounts: {
- '0x123': {
- address: '0x123',
- id: 'account1',
- metadata: {},
- options: {},
- methods: [],
- type: 'eip155:eoa',
- },
- },
- },
- },
- });
-
- store.dispatch = jest.fn().mockImplementation((action) => {
- if (typeof action === 'function') {
- return action(store.dispatch, store.getState);
- }
- return Promise.resolve();
- });
+ jest.restoreAllMocks();
+ });
- jest.clearAllMocks();
+ const arrangeMocks = () => {
+ const mockSetFeatureAnnouncementsEnabled = jest.spyOn(
+ ActionsModule,
+ 'setFeatureAnnouncementsEnabled',
+ );
+ return {
+ mockSetFeatureAnnouncementsEnabled,
+ };
+ };
+
+ it('should update feature announcement when callback invoked', async () => {
+ const mocks = arrangeMocks();
+ const hook = renderHookWithProviderTyped(
+ () => useSwitchFeatureAnnouncementsChange(),
+ {},
+ );
+
+ await hook.result.current.onChange(true);
+ expect(mocks.mockSetFeatureAnnouncementsEnabled).toHaveBeenCalled();
});
- it('should toggle feature announcements', async () => {
- const { result } = renderHook(() => useSwitchFeatureAnnouncementsChange(), {
- wrapper: ({ children }) => (
-
-
- {children}
-
-
- ),
+ it('should update error state when callback fails', async () => {
+ const mocks = arrangeMocks();
+ mocks.mockSetFeatureAnnouncementsEnabled.mockImplementation(() => {
+ throw new Error('Mock Fail');
});
+ const hook = renderHookWithProviderTyped(
+ () => useSwitchFeatureAnnouncementsChange(),
+ {},
+ );
- await act(async () => {
- await result.current.onChange(true);
- });
+ await hook.result.current.onChange(true);
+ expect(hook.result.current.error).toBeDefined();
+ });
+});
- expect(actions.setFeatureAnnouncementsEnabled).toHaveBeenCalledWith(true);
+describe('useSwitchAccountNotificationsChange() tests', () => {
+ const arrangeMocks = () => {
+ const mockUpdateOnChainTriggersByAccount = jest.spyOn(
+ ActionsModule,
+ 'updateOnChainTriggersByAccount',
+ );
+ const mockDeleteOnChainTriggersByAccount = jest.spyOn(
+ ActionsModule,
+ 'deleteOnChainTriggersByAccount',
+ );
+
+ return {
+ mockUpdateOnChainTriggersByAccount,
+ mockDeleteOnChainTriggersByAccount,
+ };
+ };
+
+ it('should invoke update notification triggers when an address is enabled', async () => {
+ const mocks = arrangeMocks();
+ const hook = renderHookWithProviderTyped(
+ () => useSwitchAccountNotificationsChange(),
+ {},
+ );
+
+ await hook.result.current.onChange(['0x1'], true);
+ expect(mocks.mockUpdateOnChainTriggersByAccount).toHaveBeenCalledWith([
+ '0x1',
+ ]);
+ expect(mocks.mockDeleteOnChainTriggersByAccount).not.toHaveBeenCalled();
});
- it('should check account presence', async () => {
- const { result } = renderHook(() => useSwitchAccountNotifications(), {
- wrapper: ({ children }) => (
-
-
- {children}
-
-
- ),
- });
+ it('should invoke delete notification triggers when an address is disabled', async () => {
+ const mocks = arrangeMocks();
+ const hook = renderHookWithProviderTyped(
+ () => useSwitchAccountNotificationsChange(),
+ {},
+ );
+
+ await hook.result.current.onChange(['0x1'], false);
+ expect(mocks.mockUpdateOnChainTriggersByAccount).not.toHaveBeenCalled();
+ expect(mocks.mockDeleteOnChainTriggersByAccount).toHaveBeenCalledWith([
+ '0x1',
+ ]);
+ });
- await act(async () => {
- await result.current.switchAccountNotifications(['0x123']);
+ it('should return an error value if it fails to update or delete triggers', async () => {
+ const mocks = arrangeMocks();
+ mocks.mockUpdateOnChainTriggersByAccount.mockImplementation(() => {
+ throw new Error('Mock Error');
+ });
+ mocks.mockDeleteOnChainTriggersByAccount.mockImplementation(() => {
+ throw new Error('Mock Error');
});
- expect(actions.checkAccountsPresence).toHaveBeenCalledWith(['0x123']);
- });
+ const act = async (testEnableOrDisable: boolean) => {
+ const hook = renderHookWithProviderTyped(
+ () => useSwitchAccountNotificationsChange(),
+ {},
+ );
+ await hook.result.current.onChange(['0x1'], testEnableOrDisable);
+ return hook.result.current.error;
+ };
- it('should handle account notification changes', async () => {
- const { result } = renderHook(() => useSwitchAccountNotificationsChange(), {
- wrapper: ({ children }) => (
-
-
- {children}
-
-
- ),
- });
+ const enableError = await act(true);
+ expect(enableError).toBeDefined();
+
+ const disableError = await act(false);
+ expect(disableError).toBeDefined();
+ });
+});
- // Test enabling notifications
- await act(async () => {
- await result.current.onChange(['0x123'], true);
+describe('useAccountSettingsProps() tests', () => {
+ const arrangeMocks = () => {
+ const mockCheckAccountsPresence = jest.spyOn(
+ ActionsModule,
+ 'checkAccountsPresence',
+ );
+ const mockGetIsUpdatingMetamaskNotificationsAccount = jest
+ .spyOn(
+ NotificationSelectorsModule,
+ 'getIsUpdatingMetamaskNotificationsAccount',
+ )
+ .mockReturnValue([]);
+ const mockSelectIsMetamaskNotificationsEnabled = jest
+ .spyOn(
+ NotificationSelectorsModule,
+ 'selectIsMetamaskNotificationsEnabled',
+ )
+ .mockReturnValue(true);
+
+ return {
+ mockCheckAccountsPresence,
+ mockGetIsUpdatingMetamaskNotificationsAccount,
+ mockSelectIsMetamaskNotificationsEnabled,
+ };
+ };
+
+ it('Should invoke effect when notifications are enabled', async () => {
+ const mocks = arrangeMocks();
+ renderHookWithProviderTyped(() => useAccountSettingsProps(['0x1']), {});
+
+ await waitFor(() => {
+ expect(mocks.mockCheckAccountsPresence).toHaveBeenCalled();
});
+ });
- expect(actions.updateOnChainTriggersByAccount).toHaveBeenCalledWith([
- '0x123',
- ]);
+ it('Should not invoke effect when notifications are disabled', async () => {
+ const mocks = arrangeMocks();
+ mocks.mockSelectIsMetamaskNotificationsEnabled.mockReturnValue(false);
+ renderHookWithProviderTyped(() => useAccountSettingsProps(['0x1']), {});
- // Test disabling notifications
- await act(async () => {
- await result.current.onChange(['0x123'], false);
+ await waitFor(() => {
+ expect(mocks.mockCheckAccountsPresence).not.toHaveBeenCalled();
});
+ });
- expect(actions.deleteOnChainTriggersByAccount).toHaveBeenCalledWith([
- '0x123',
- ]);
+ it('Should be able to invoke refetch accounts function', async () => {
+ const mocks = arrangeMocks();
+ const hook = renderHookWithProviderTyped(
+ () => useAccountSettingsProps(['0x1']),
+ {},
+ );
+
+ await hook.result.current.update(['0x1', '0x2']);
+ await waitFor(() => {
+ expect(mocks.mockCheckAccountsPresence).toHaveBeenCalledWith([
+ '0x1',
+ '0x2',
+ ]);
+ });
});
});
diff --git a/ui/hooks/metamask-notifications/useSwitchNotifications.ts b/ui/hooks/metamask-notifications/useSwitchNotifications.ts
index 53e121473bac..08a03c33e4f1 100644
--- a/ui/hooks/metamask-notifications/useSwitchNotifications.ts
+++ b/ui/hooks/metamask-notifications/useSwitchNotifications.ts
@@ -8,7 +8,10 @@ import {
updateOnChainTriggersByAccount,
hideLoadingIndication,
} from '../../store/actions';
-import { getIsUpdatingMetamaskNotificationsAccount } from '../../selectors/metamask-notifications/metamask-notifications';
+import {
+ getIsUpdatingMetamaskNotificationsAccount,
+ selectIsMetamaskNotificationsEnabled,
+} from '../../selectors/metamask-notifications/metamask-notifications';
export function useSwitchFeatureAnnouncementsChange(): {
onChange: (state: boolean) => Promise;
@@ -28,7 +31,6 @@ export function useSwitchFeatureAnnouncementsChange(): {
const errorMessage =
e instanceof Error ? e.message : JSON.stringify(e ?? '');
setError(errorMessage);
- throw e;
}
},
[dispatch],
@@ -42,44 +44,6 @@ export function useSwitchFeatureAnnouncementsChange(): {
export type UseSwitchAccountNotificationsData = { [address: string]: boolean };
-export function useSwitchAccountNotifications(): {
- switchAccountNotifications: (
- accounts: string[],
- ) => Promise;
- isLoading: boolean;
- error: string | null;
-} {
- const dispatch = useDispatch();
-
- const [isLoading, setIsLoading] = useState(false);
- const [error, setError] = useState(null);
-
- const switchAccountNotifications = useCallback(
- async (
- accounts: string[],
- ): Promise => {
- setIsLoading(true);
- setError(null);
-
- try {
- const data = await dispatch(checkAccountsPresence(accounts));
- return data as unknown as UseSwitchAccountNotificationsData;
- } catch (e) {
- const errorMessage =
- e instanceof Error ? e.message : JSON.stringify(e ?? '');
- setError(errorMessage);
- log.error(errorMessage);
- throw e;
- } finally {
- setIsLoading(false);
- }
- },
- [dispatch],
- );
-
- return { switchAccountNotifications, isLoading, error };
-}
-
export function useSwitchAccountNotificationsChange(): {
onChange: (addresses: string[], state: boolean) => Promise;
error: string | null;
@@ -103,7 +67,6 @@ export function useSwitchAccountNotificationsChange(): {
e instanceof Error ? e.message : JSON.stringify(e ?? '');
log.error(errorMessage);
setError(errorMessage);
- throw e;
}
dispatch(hideLoadingIndication());
},
@@ -146,6 +109,7 @@ export function useAccountSettingsProps(accounts: string[]) {
const accountsBeingUpdated = useSelector(
getIsUpdatingMetamaskNotificationsAccount,
);
+ const isEnabled = useSelector(selectIsMetamaskNotificationsEnabled);
const fetchAccountSettings = useRefetchAccountSettings();
const [data, setData] = useState({});
const [loading, setLoading] = useState(false);
@@ -169,15 +133,12 @@ export function useAccountSettingsProps(accounts: string[]) {
// Effect - async get if accounts are enabled/disabled
useEffect(() => {
- try {
- const memoAccounts: string[] = JSON.parse(jsonAccounts);
- update(memoAccounts);
- } catch {
- setError('Failed to get account settings');
- } finally {
- setLoading(false);
+ if (!isEnabled) {
+ return;
}
- }, [jsonAccounts, fetchAccountSettings]);
+ const memoAccounts: string[] = JSON.parse(jsonAccounts);
+ update(memoAccounts);
+ }, [jsonAccounts, fetchAccountSettings, isEnabled]);
return {
data,
diff --git a/ui/pages/notifications-settings/notifications-settings-allow-notifications.tsx b/ui/pages/notifications-settings/notifications-settings-allow-notifications.tsx
index b442a4092185..fdfaeb1109cb 100644
--- a/ui/pages/notifications-settings/notifications-settings-allow-notifications.tsx
+++ b/ui/pages/notifications-settings/notifications-settings-allow-notifications.tsx
@@ -74,7 +74,7 @@ export function NotificationsSettingsAllowNotifications({
}, [isMetamaskNotificationsEnabled]);
useEffect(() => {
- if (!error) {
+ if (!error && isMetamaskNotificationsEnabled) {
listNotifications();
}
}, [isMetamaskNotificationsEnabled, error, listNotifications]);
From dd659446809d793399ee97f581cdc9f285493988 Mon Sep 17 00:00:00 2001
From: javiergarciavera <76975121+javiergarciavera@users.noreply.github.com>
Date: Thu, 19 Dec 2024 11:46:31 +0100
Subject: [PATCH 034/202] feat: added create solana account test (#28866)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
## **Description**
- E2E tests covering different scenarios for creating/removing Solana
accounts.
- Refactor around some snap logic for BTC/Solana
[![Open in GitHub
Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29054?quickstart=1)
## **Related issues**
Fixes:
## **Manual testing steps**
Running flask tests should pass
## **Screenshots/Recordings**
### **Before**
### **After**
## **Pre-merge author checklist**
- [x] I've followed [MetaMask Contributor
Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask
Extension Coding
Standards](https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/CODING_GUIDELINES.md).
- [x] I've completed the PR template to the best of my ability
- [x] I’ve included tests if applicable
- [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format
if applicable
- [x] I’ve applied the right labels on the PR (see [labeling
guidelines](https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/LABELING_GUIDELINES.md)).
Not required for external contributors.
## **Pre-merge reviewer checklist**
- [x] I've manually tested the PR (e.g. pull and build branch, run the
app, test code being changed).
- [x] I confirm that this PR addresses all acceptance criteria described
in the ticket it closes and includes the necessary testing evidence such
as recordings and or screenshots.
---------
Co-authored-by: Ulisses Ferreira
Co-authored-by: Dan J Miller
Co-authored-by: Charly Chevalier
---
test/data/mock-state.json | 1 +
test/e2e/constants.ts | 3 +
test/e2e/flask/btc/common-btc.ts | 3 +-
test/e2e/flask/btc/create-btc-account.spec.ts | 25 +++--
test/e2e/flask/solana/common-solana.ts | 71 ++++++++++++
.../solana/create-solana-account.spec.ts | 62 +++++++++++
.../flask/solana/solana-eth-networks.spec.ts | 24 +++++
test/e2e/page-objects/common.ts | 6 ++
.../page-objects/pages/account-list-page.ts | 102 ++++++++++--------
test/e2e/page-objects/pages/header-navbar.ts | 30 +++---
10 files changed, 261 insertions(+), 66 deletions(-)
create mode 100644 test/e2e/flask/solana/common-solana.ts
create mode 100644 test/e2e/flask/solana/create-solana-account.spec.ts
create mode 100644 test/e2e/flask/solana/solana-eth-networks.spec.ts
diff --git a/test/data/mock-state.json b/test/data/mock-state.json
index 3131b8335287..9ea2e674e7a3 100644
--- a/test/data/mock-state.json
+++ b/test/data/mock-state.json
@@ -1891,6 +1891,7 @@
"watchEthereumAccountEnabled": false,
"bitcoinSupportEnabled": false,
"bitcoinTestnetSupportEnabled": false,
+ "solanaSupportEnabled": false,
"pendingApprovals": {
"testApprovalId": {
"id": "testApprovalId",
diff --git a/test/e2e/constants.ts b/test/e2e/constants.ts
index 8296cdc3e3d1..9d4d5bbf3c8d 100644
--- a/test/e2e/constants.ts
+++ b/test/e2e/constants.ts
@@ -57,6 +57,9 @@ export const DEFAULT_BTC_FEES_RATE = 0.00001; // BTC
/* Default BTC conversion rate to USD */
export const DEFAULT_BTC_CONVERSION_RATE = 62000; // USD
+/* Default SOL conversion rate to USD */
+export const DEFAULT_SOL_CONVERSION_RATE = 226; // USD
+
/* Default BTC transaction ID */
export const DEFAULT_BTC_TRANSACTION_ID =
'e4111a707317da67d49a71af4cbcf6c0546f900ca32c3842d2254e315d1fca18';
diff --git a/test/e2e/flask/btc/common-btc.ts b/test/e2e/flask/btc/common-btc.ts
index 05f382281e29..db2db85eb554 100644
--- a/test/e2e/flask/btc/common-btc.ts
+++ b/test/e2e/flask/btc/common-btc.ts
@@ -14,6 +14,7 @@ import { Driver } from '../../webdriver/driver';
import { loginWithBalanceValidation } from '../../page-objects/flows/login.flow';
import AccountListPage from '../../page-objects/pages/account-list-page';
import HeaderNavbar from '../../page-objects/pages/header-navbar';
+import { ACCOUNT_TYPE } from '../../page-objects/common';
const QUICKNODE_URL_REGEX = /^https:\/\/.*\.btc.*\.quiknode\.pro(\/|$)/u;
@@ -217,7 +218,7 @@ export async function withBtcAccountSnap(
await new HeaderNavbar(driver).openAccountMenu();
const accountListPage = new AccountListPage(driver);
await accountListPage.check_pageIsLoaded();
- await accountListPage.addNewBtcAccount();
+ await accountListPage.addAccount(ACCOUNT_TYPE.Bitcoin, '');
await test(driver, mockServer);
},
);
diff --git a/test/e2e/flask/btc/create-btc-account.spec.ts b/test/e2e/flask/btc/create-btc-account.spec.ts
index 0dfb24a8a931..f563454ad1e6 100644
--- a/test/e2e/flask/btc/create-btc-account.spec.ts
+++ b/test/e2e/flask/btc/create-btc-account.spec.ts
@@ -7,6 +7,7 @@ import LoginPage from '../../page-objects/pages/login-page';
import PrivacySettings from '../../page-objects/pages/settings/privacy-settings';
import ResetPasswordPage from '../../page-objects/pages/reset-password-page';
import SettingsPage from '../../page-objects/pages/settings/settings-page';
+import { ACCOUNT_TYPE } from '../../page-objects/common';
import { withBtcAccountSnap } from './common-btc';
describe('Create BTC Account', function (this: Suite) {
@@ -34,14 +35,12 @@ describe('Create BTC Account', function (this: Suite) {
await headerNavbar.openAccountMenu();
const accountListPage = new AccountListPage(driver);
await accountListPage.check_pageIsLoaded();
- await accountListPage.addNewBtcAccount({
- btcAccountCreationEnabled: false,
- });
-
- // check the number of available accounts is 2
- await headerNavbar.openAccountMenu();
- await accountListPage.check_pageIsLoaded();
await accountListPage.check_numberOfAvailableAccounts(2);
+ await accountListPage.openAddAccountModal();
+ assert.equal(
+ await accountListPage.isBtcAccountCreationButtonEnabled(),
+ false,
+ );
},
);
});
@@ -91,8 +90,14 @@ describe('Create BTC Account', function (this: Suite) {
// Recreate account and check that the address is the same
await headerNavbar.openAccountMenu();
- await accountListPage.check_pageIsLoaded();
- await accountListPage.addNewBtcAccount();
+ await accountListPage.openAddAccountModal();
+ assert.equal(
+ await accountListPage.isBtcAccountCreationButtonEnabled(),
+ true,
+ );
+ await accountListPage.closeAccountModal();
+ await headerNavbar.openAccountMenu();
+ await accountListPage.addAccount(ACCOUNT_TYPE.Bitcoin, '');
await headerNavbar.check_accountLabel('Bitcoin Account');
await headerNavbar.openAccountMenu();
@@ -146,7 +151,7 @@ describe('Create BTC Account', function (this: Suite) {
await headerNavbar.check_pageIsLoaded();
await headerNavbar.openAccountMenu();
await accountListPage.check_pageIsLoaded();
- await accountListPage.addNewBtcAccount();
+ await accountListPage.addAccount(ACCOUNT_TYPE.Bitcoin, '');
await headerNavbar.check_accountLabel('Bitcoin Account');
await headerNavbar.openAccountMenu();
diff --git a/test/e2e/flask/solana/common-solana.ts b/test/e2e/flask/solana/common-solana.ts
new file mode 100644
index 000000000000..2ac107bb442c
--- /dev/null
+++ b/test/e2e/flask/solana/common-solana.ts
@@ -0,0 +1,71 @@
+import { Mockttp } from 'mockttp';
+import { withFixtures } from '../../helpers';
+import { Driver } from '../../webdriver/driver';
+import HeaderNavbar from '../../page-objects/pages/header-navbar';
+import AccountListPage from '../../page-objects/pages/account-list-page';
+import FixtureBuilder from '../../fixture-builder';
+import { ACCOUNT_TYPE } from '../../page-objects/common';
+import { loginWithBalanceValidation } from '../../page-objects/flows/login.flow';
+
+const SOLANA_URL_REGEX = /^https:\/\/.*\.solana.*/u;
+
+export enum SendFlowPlaceHolders {
+ AMOUNT = 'Enter amount to send',
+ RECIPIENT = 'Enter receiving address',
+ LOADING = 'Preparing transaction',
+}
+
+export async function mockSolanaBalanceQuote(mockServer: Mockttp) {
+ return await mockServer
+ .forPost(SOLANA_URL_REGEX)
+ .withJsonBodyIncluding({
+ method: 'getBalance',
+ })
+ .thenCallback(() => {
+ return {
+ statusCode: 200,
+ json: {
+ result: {
+ context: {
+ apiVersion: '2.0.15',
+ slot: 305352614,
+ },
+ value: 0,
+ },
+ },
+ };
+ });
+}
+
+export async function withSolanaAccountSnap(
+ {
+ title,
+ solanaSupportEnabled,
+ }: { title?: string; solanaSupportEnabled?: boolean },
+ test: (driver: Driver, mockServer: Mockttp) => Promise,
+) {
+ console.log('Starting withSolanaAccountSnap');
+ await withFixtures(
+ {
+ fixtures: new FixtureBuilder()
+ .withPreferencesControllerAndFeatureFlag({
+ solanaSupportEnabled: solanaSupportEnabled ?? true,
+ })
+ .build(),
+ title,
+ dapp: true,
+ testSpecificMock: async (mockServer: Mockttp) => {
+ console.log('Setting up test-specific mocks');
+ return [await mockSolanaBalanceQuote(mockServer)];
+ },
+ },
+ async ({ driver, mockServer }: { driver: Driver; mockServer: Mockttp }) => {
+ await loginWithBalanceValidation(driver);
+ const headerComponen = new HeaderNavbar(driver);
+ await headerComponen.openAccountMenu();
+ const accountListPage = new AccountListPage(driver);
+ await accountListPage.addAccount(ACCOUNT_TYPE.Solana, 'Solana 1');
+ await test(driver, mockServer);
+ },
+ );
+}
diff --git a/test/e2e/flask/solana/create-solana-account.spec.ts b/test/e2e/flask/solana/create-solana-account.spec.ts
new file mode 100644
index 000000000000..cca4f5222993
--- /dev/null
+++ b/test/e2e/flask/solana/create-solana-account.spec.ts
@@ -0,0 +1,62 @@
+import { Suite } from 'mocha';
+import HeaderNavbar from '../../page-objects/pages/header-navbar';
+import AccountListPage from '../../page-objects/pages/account-list-page';
+import { ACCOUNT_TYPE } from '../../page-objects/common';
+import { withSolanaAccountSnap } from './common-solana';
+
+// Scenarios skipped due to https://consensyssoftware.atlassian.net/browse/SOL-87
+describe('Create Solana Account', function (this: Suite) {
+ it.skip('Creates 2 Solana accounts', async function () {
+ await withSolanaAccountSnap(
+ { title: this.test?.fullTitle() },
+ async (driver) => {
+ // check that we have one Solana account
+ const headerNavbar = new HeaderNavbar(driver);
+ await headerNavbar.check_pageIsLoaded();
+ await headerNavbar.check_accountLabel('Solana 1');
+ await headerNavbar.openAccountMenu();
+ const accountListPage = new AccountListPage(driver);
+ await accountListPage.check_accountDisplayedInAccountList('Account 1');
+ await accountListPage.addAccount(ACCOUNT_TYPE.Solana, 'Solana 2');
+ await headerNavbar.check_accountLabel('Solana 2');
+ await headerNavbar.openAccountMenu();
+ await accountListPage.check_numberOfAvailableAccounts(3);
+ },
+ );
+ });
+ it('Creates a Solana account from the menu', async function () {
+ await withSolanaAccountSnap(
+ { title: this.test?.fullTitle() },
+ async (driver) => {
+ const headerNavbar = new HeaderNavbar(driver);
+ await headerNavbar.check_pageIsLoaded();
+ await headerNavbar.check_accountLabel('Solana 1');
+ await headerNavbar.openAccountMenu();
+ const accountListPage = new AccountListPage(driver);
+ await accountListPage.check_accountDisplayedInAccountList('Account 1');
+ await accountListPage.check_accountDisplayedInAccountList('Solana 1');
+ },
+ );
+ });
+});
+it.skip('Removes Solana account after creating it', async function () {
+ await withSolanaAccountSnap(
+ { title: this.test?.fullTitle() },
+ async (driver) => {
+ // check that we have one Solana account
+ const headerNavbar = new HeaderNavbar(driver);
+ await headerNavbar.check_accountLabel('Solana 1');
+ // check user can cancel the removal of the Solana account
+ await headerNavbar.openAccountMenu();
+ const accountListPage = new AccountListPage(driver);
+ await accountListPage.check_accountDisplayedInAccountList('Account 1');
+ await accountListPage.removeAccount('Solana 1', true);
+ await headerNavbar.check_accountLabel('Account 1');
+ await headerNavbar.openAccountMenu();
+ await accountListPage.check_accountDisplayedInAccountList('Account 1');
+ await accountListPage.check_accountIsNotDisplayedInAccountList(
+ 'Solana 1',
+ );
+ },
+ );
+});
diff --git a/test/e2e/flask/solana/solana-eth-networks.spec.ts b/test/e2e/flask/solana/solana-eth-networks.spec.ts
new file mode 100644
index 000000000000..56a68135fad8
--- /dev/null
+++ b/test/e2e/flask/solana/solana-eth-networks.spec.ts
@@ -0,0 +1,24 @@
+import { Suite } from 'mocha';
+import HeaderNavbar from '../../page-objects/pages/header-navbar';
+import AccountListPage from '../../page-objects/pages/account-list-page';
+import { withSolanaAccountSnap } from './common-solana';
+
+describe('Solana/Evm accounts', function (this: Suite) {
+ it('Network picker is disabled when Solana account is selected', async function () {
+ await withSolanaAccountSnap(
+ { title: this.test?.fullTitle() },
+ async (driver) => {
+ const headerNavbar = new HeaderNavbar(driver);
+ await headerNavbar.check_pageIsLoaded();
+ await headerNavbar.check_accountLabel('Solana 1');
+ await headerNavbar.check_currentSelectedNetwork('Solana');
+ await headerNavbar.check_ifNetworkPickerClickable(false);
+ await headerNavbar.openAccountMenu();
+ const accountMenu = new AccountListPage(driver);
+ await accountMenu.switchToAccount('Account 1');
+ await headerNavbar.check_currentSelectedNetwork('Localhost 8545');
+ await headerNavbar.check_ifNetworkPickerClickable(true);
+ },
+ );
+ });
+});
diff --git a/test/e2e/page-objects/common.ts b/test/e2e/page-objects/common.ts
index 5bf1a91e1859..40eb625d94ac 100644
--- a/test/e2e/page-objects/common.ts
+++ b/test/e2e/page-objects/common.ts
@@ -2,3 +2,9 @@ export type RawLocator =
| string
| { css?: string; text?: string }
| { tag: string; text: string };
+
+export enum ACCOUNT_TYPE {
+ Ethereum,
+ Bitcoin,
+ Solana,
+}
diff --git a/test/e2e/page-objects/pages/account-list-page.ts b/test/e2e/page-objects/pages/account-list-page.ts
index 50eb70ce553c..628d758e7e71 100644
--- a/test/e2e/page-objects/pages/account-list-page.ts
+++ b/test/e2e/page-objects/pages/account-list-page.ts
@@ -1,7 +1,7 @@
-import { strict as assert } from 'assert';
import { Driver } from '../../webdriver/driver';
import { largeDelayMs, regularDelayMs } from '../../helpers';
import messages from '../../../../app/_locales/en/messages.json';
+import { ACCOUNT_TYPE } from '../common';
class AccountListPage {
private readonly driver: Driver;
@@ -40,6 +40,11 @@ class AccountListPage {
tag: 'button',
};
+ private readonly addSolanaAccountButton = {
+ text: messages.addNewSolanaAccount.message,
+ tag: 'button',
+ };
+
private readonly addEthereumAccountButton =
'[data-testid="multichain-account-menu-popover-add-account"]';
@@ -163,48 +168,11 @@ class AccountListPage {
);
}
- /**
- * Adds a new BTC account with an optional custom name.
- *
- * @param options - Options for adding a new BTC account.
- * @param [options.btcAccountCreationEnabled] - Indicates if the BTC account creation is expected to be enabled or disabled. Defaults to true.
- * @param [options.accountName] - The custom name for the BTC account. Defaults to an empty string, which means the default name will be used.
- */
- async addNewBtcAccount({
- btcAccountCreationEnabled = true,
- accountName = '',
- }: {
- btcAccountCreationEnabled?: boolean;
- accountName?: string;
- } = {}): Promise {
- console.log(
- `Adding new BTC account${
- accountName ? ` with custom name: ${accountName}` : ' with default name'
- }`,
+ async isBtcAccountCreationButtonEnabled() {
+ const createButton = await this.driver.findElement(
+ this.addBtcAccountButton,
);
- await this.driver.clickElement(this.createAccountButton);
- if (btcAccountCreationEnabled) {
- await this.driver.clickElement(this.addBtcAccountButton);
- // needed to mitigate a race condition with the state update
- // there is no condition we can wait for in the UI
- await this.driver.delay(largeDelayMs);
- if (accountName) {
- await this.driver.fill(this.accountNameInput, accountName);
- }
- await this.driver.clickElementAndWaitToDisappear(
- this.addAccountConfirmButton,
- // Longer timeout than usual, this reduces the flakiness
- // around Bitcoin account creation (mainly required for
- // Firefox)
- 5000,
- );
- } else {
- const createButton = await this.driver.findElement(
- this.addBtcAccountButton,
- );
- assert.equal(await createButton.isEnabled(), false);
- await this.driver.clickElement(this.closeAccountModalButton);
- }
+ return await createButton.isEnabled();
}
/**
@@ -234,6 +202,48 @@ class AccountListPage {
}
}
+ /**
+ * Adds a new account of the specified type with an optional custom name.
+ *
+ * @param accountType - The type of account to add (Ethereum, Bitcoin, or Solana)
+ * @param accountName - Optional custom name for the new account
+ * @throws {Error} If the specified account type is not supported
+ * @example
+ * // Add a new Ethereum account with default name
+ * await accountListPage.addAccount(ACCOUNT_TYPE.Ethereum);
+ *
+ * // Add a new Bitcoin account with custom name
+ * await accountListPage.addAccount(ACCOUNT_TYPE.Bitcoin, 'My BTC Wallet');
+ */
+ async addAccount(accountType: ACCOUNT_TYPE, accountName?: string) {
+ await this.driver.clickElement(this.createAccountButton);
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ let addAccountButton: any;
+ switch (accountType) {
+ case ACCOUNT_TYPE.Ethereum:
+ addAccountButton = this.addEthereumAccountButton;
+ break;
+ case ACCOUNT_TYPE.Bitcoin:
+ addAccountButton = this.addBtcAccountButton;
+ break;
+ case ACCOUNT_TYPE.Solana:
+ addAccountButton = this.addSolanaAccountButton;
+ break;
+ default:
+ throw new Error('Account type not supported');
+ }
+
+ await this.driver.clickElement(addAccountButton);
+ if (accountName) {
+ await this.driver.fill(this.accountNameInput, accountName);
+ }
+
+ await this.driver.clickElementAndWaitToDisappear(
+ this.addAccountConfirmButton,
+ 5000,
+ );
+ }
+
/**
* Changes the label of the current account.
*
@@ -600,11 +610,17 @@ class AccountListPage {
console.log(
`Verify the number of accounts in the account menu is: ${expectedNumberOfAccounts}`,
);
+
+ await this.driver.waitForSelector(this.accountListItem);
await this.driver.wait(async () => {
const internalAccounts = await this.driver.findElements(
this.accountListItem,
);
- return internalAccounts.length === expectedNumberOfAccounts;
+ const isValid = internalAccounts.length === expectedNumberOfAccounts;
+ console.log(
+ `Number of accounts: ${internalAccounts.length} is equal to ${expectedNumberOfAccounts}? ${isValid}`,
+ );
+ return isValid;
}, 20000);
}
diff --git a/test/e2e/page-objects/pages/header-navbar.ts b/test/e2e/page-objects/pages/header-navbar.ts
index df45ecd0277d..57e02e864624 100644
--- a/test/e2e/page-objects/pages/header-navbar.ts
+++ b/test/e2e/page-objects/pages/header-navbar.ts
@@ -1,3 +1,4 @@
+import { strict as assert } from 'assert';
import { Driver } from '../../webdriver/driver';
class HeaderNavbar {
@@ -22,6 +23,8 @@ class HeaderNavbar {
private readonly switchNetworkDropDown = '[data-testid="network-display"]';
+ private readonly networkPicker = '.mm-picker-network';
+
constructor(driver: Driver) {
this.driver = driver;
}
@@ -80,6 +83,21 @@ class HeaderNavbar {
await this.driver.clickElement(this.switchNetworkDropDown);
}
+ async check_currentSelectedNetwork(networkName: string): Promise {
+ console.log(`Validate the Switch network to ${networkName}`);
+ await this.driver.waitForSelector(
+ `button[data-testid="network-display"][aria-label="Network Menu ${networkName}"]`,
+ );
+ }
+
+ async check_ifNetworkPickerClickable(clickable: boolean): Promise {
+ console.log('Check whether the network picker is clickable or not');
+ assert.equal(
+ await (await this.driver.findElement(this.networkPicker)).isEnabled(),
+ clickable,
+ );
+ }
+
/**
* Verifies that the displayed account label in header matches the expected label.
*
@@ -94,18 +112,6 @@ class HeaderNavbar {
text: expectedLabel,
});
}
-
- /**
- * Validates that the currently selected network matches the expected network name.
- *
- * @param networkName - The expected name of the currently selected network.
- */
- async check_currentSelectedNetwork(networkName: string): Promise {
- console.log(`Validate the Switch network to ${networkName}`);
- await this.driver.waitForSelector(
- `button[data-testid="network-display"][aria-label="Network Menu ${networkName}"]`,
- );
- }
}
export default HeaderNavbar;
From 749d0acc42a1a10b00da25e0dd1571a0f2054d0f Mon Sep 17 00:00:00 2001
From: Pedro Figueiredo
Date: Thu, 19 Dec 2024 11:01:41 +0000
Subject: [PATCH 035/202] fix: Spending cap flicker as decimals are determined
(#29206)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
## **Description**
Sometimes, when approving a token, the spending cap displayed in the
transaction simulation component flickers for a split second. This can
also seen when editing the spending cap on the same screen.
In the approve screen, `useAssetDetails` returns `decimals` at first as
`undefined` while it's determined asynchronously. Since
`useApproveTokenSimulation` takes `decimals` as an argument, a default
of `'0'` was set to quiet the type-checker. This default is what
provokes the UI flicker.
In the example video below, the token has 4 decimals and the spending
cap is 70000 / 10 ** 4 = 7. But while decimals is still `undefined`,
`'0'` is used in `useApproveTokenSimulation` to determine the spending
cap (dividing the value by 10 to the number of decimals). This amounts
to `70000` instead of `7` for a split second, before decimals `'4'` is
returned by `useAssetDetails`.
The fix for this bug is to let the loading spinner linger a few
miliseconds longer while decimals is still `undefined`.
[![Open in GitHub
Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29206?quickstart=1)
## **Related issues**
Fixes:
## **Manual testing steps**
1. Go to this page...
2.
3.
## **Screenshots/Recordings**
### **Before**
https://github.com/user-attachments/assets/a09f449a-78ea-4083-b47b-2f329126d4b6
### **After**
https://github.com/user-attachments/assets/68142912-ae20-47f6-8dd4-2b9184b57bbf
## **Pre-merge author checklist**
- [ ] I've followed [MetaMask Contributor
Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask
Extension Coding
Standards](https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/CODING_GUIDELINES.md).
- [ ] I've completed the PR template to the best of my ability
- [ ] I’ve included tests if applicable
- [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format
if applicable
- [ ] I’ve applied the right labels on the PR (see [labeling
guidelines](https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/LABELING_GUIDELINES.md)).
Not required for external contributors.
## **Pre-merge reviewer checklist**
- [ ] I've manually tested the PR (e.g. pull and build branch, run the
app, test code being changed).
- [ ] I confirm that this PR addresses all acceptance criteria described
in the ticket it closes and includes the necessary testing evidence such
as recordings and or screenshots.
---
.../signatures/permit-batch.test.tsx | 21 ++++++++++++++---
.../signatures/permit-seaport.test.tsx | 21 ++++++++++++++---
.../signatures/permit-single.test.tsx | 21 ++++++++++++++---
.../signatures/permit-tradeOrder.test.tsx | 21 ++++++++++++++---
.../confirmations/signatures/permit.test.tsx | 15 ++++++++++++
.../signatures/personalSign.test.tsx | 17 +++++++++++++-
.../transactions/alerts.test.tsx | 23 +++++++++++++++----
.../transactions/contract-deployment.test.tsx | 15 ++++++++++++
.../contract-interaction.test.tsx | 15 ++++++++++++
.../transactions/erc20-approve.test.tsx | 15 ++++++++++++
.../transactions/erc721-approve.test.tsx | 16 +++++++++++++
.../transactions/increase-allowance.test.tsx | 15 ++++++++++++
.../set-approval-for-all.test.tsx | 15 ++++++++++++
.../approve-static-simulation.tsx | 4 +---
.../confirm/info/approve/approve.test.tsx | 16 ++++++++++++-
.../confirm/info/approve/approve.tsx | 4 ++--
.../edit-spending-cap-modal.tsx | 2 +-
.../hooks/use-approve-token-simulation.ts | 4 ++--
.../approve/spending-cap/spending-cap.tsx | 5 +---
.../components/confirm/info/info.test.tsx | 17 ++++++++++++++
.../title/hooks/useCurrentSpendingCap.ts | 2 +-
.../confirmations/confirm/confirm.test.tsx | 16 +++++++++++++
22 files changed, 269 insertions(+), 31 deletions(-)
diff --git a/test/integration/confirmations/signatures/permit-batch.test.tsx b/test/integration/confirmations/signatures/permit-batch.test.tsx
index ea311537001c..7a3050dc15c8 100644
--- a/test/integration/confirmations/signatures/permit-batch.test.tsx
+++ b/test/integration/confirmations/signatures/permit-batch.test.tsx
@@ -1,10 +1,11 @@
import { act, fireEvent, screen } from '@testing-library/react';
import nock from 'nock';
-import mockMetaMaskState from '../../data/integration-init-state.json';
-import { integrationTestRender } from '../../../lib/render-helpers';
+import { useAssetDetails } from '../../../../ui/pages/confirmations/hooks/useAssetDetails';
import * as backgroundConnection from '../../../../ui/store/background-connection';
-import { createMockImplementation } from '../../helpers';
import { tEn } from '../../../lib/i18n-helpers';
+import { integrationTestRender } from '../../../lib/render-helpers';
+import mockMetaMaskState from '../../data/integration-init-state.json';
+import { createMockImplementation } from '../../helpers';
import {
getMetaMaskStateWithUnapprovedPermitSign,
verifyDetails,
@@ -15,10 +16,20 @@ jest.mock('../../../../ui/store/background-connection', () => ({
submitRequestToBackground: jest.fn(),
}));
+jest.mock('../../../../ui/pages/confirmations/hooks/useAssetDetails', () => ({
+ ...jest.requireActual(
+ '../../../../ui/pages/confirmations/hooks/useAssetDetails',
+ ),
+ useAssetDetails: jest.fn().mockResolvedValue({
+ decimals: '4',
+ }),
+}));
+
const mockedBackgroundConnection = jest.mocked(backgroundConnection);
const backgroundConnectionMocked = {
onNotification: jest.fn(),
};
+const mockedAssetDetails = jest.mocked(useAssetDetails);
const renderPermitBatchSignature = async () => {
const account =
@@ -58,6 +69,10 @@ describe('Permit Batch Signature Tests', () => {
getTokenStandardAndDetails: { decimals: '2' },
}),
);
+ mockedAssetDetails.mockImplementation(() => ({
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ decimals: '4' as any,
+ }));
});
afterEach(() => {
diff --git a/test/integration/confirmations/signatures/permit-seaport.test.tsx b/test/integration/confirmations/signatures/permit-seaport.test.tsx
index 7829a2714b57..dc32671da245 100644
--- a/test/integration/confirmations/signatures/permit-seaport.test.tsx
+++ b/test/integration/confirmations/signatures/permit-seaport.test.tsx
@@ -1,10 +1,11 @@
import { act, fireEvent, screen } from '@testing-library/react';
import nock from 'nock';
-import mockMetaMaskState from '../../data/integration-init-state.json';
-import { integrationTestRender } from '../../../lib/render-helpers';
+import { useAssetDetails } from '../../../../ui/pages/confirmations/hooks/useAssetDetails';
import * as backgroundConnection from '../../../../ui/store/background-connection';
-import { createMockImplementation } from '../../helpers';
import { tEn } from '../../../lib/i18n-helpers';
+import { integrationTestRender } from '../../../lib/render-helpers';
+import mockMetaMaskState from '../../data/integration-init-state.json';
+import { createMockImplementation } from '../../helpers';
import {
getMetaMaskStateWithUnapprovedPermitSign,
verifyDetails,
@@ -15,10 +16,20 @@ jest.mock('../../../../ui/store/background-connection', () => ({
submitRequestToBackground: jest.fn(),
}));
+jest.mock('../../../../ui/pages/confirmations/hooks/useAssetDetails', () => ({
+ ...jest.requireActual(
+ '../../../../ui/pages/confirmations/hooks/useAssetDetails',
+ ),
+ useAssetDetails: jest.fn().mockResolvedValue({
+ decimals: '4',
+ }),
+}));
+
const mockedBackgroundConnection = jest.mocked(backgroundConnection);
const backgroundConnectionMocked = {
onNotification: jest.fn(),
};
+const mockedAssetDetails = jest.mocked(useAssetDetails);
const renderSeaportSignature = async () => {
const account =
@@ -58,6 +69,10 @@ describe('Permit Seaport Tests', () => {
getTokenStandardAndDetails: { decimals: '2' },
}),
);
+ mockedAssetDetails.mockImplementation(() => ({
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ decimals: '4' as any,
+ }));
});
afterEach(() => {
diff --git a/test/integration/confirmations/signatures/permit-single.test.tsx b/test/integration/confirmations/signatures/permit-single.test.tsx
index abb5d07c1587..96fe5c26491d 100644
--- a/test/integration/confirmations/signatures/permit-single.test.tsx
+++ b/test/integration/confirmations/signatures/permit-single.test.tsx
@@ -1,10 +1,11 @@
import { act, fireEvent, screen } from '@testing-library/react';
import nock from 'nock';
-import mockMetaMaskState from '../../data/integration-init-state.json';
-import { integrationTestRender } from '../../../lib/render-helpers';
+import { useAssetDetails } from '../../../../ui/pages/confirmations/hooks/useAssetDetails';
import * as backgroundConnection from '../../../../ui/store/background-connection';
-import { createMockImplementation } from '../../helpers';
import { tEn } from '../../../lib/i18n-helpers';
+import { integrationTestRender } from '../../../lib/render-helpers';
+import mockMetaMaskState from '../../data/integration-init-state.json';
+import { createMockImplementation } from '../../helpers';
import {
getMetaMaskStateWithUnapprovedPermitSign,
verifyDetails,
@@ -15,10 +16,20 @@ jest.mock('../../../../ui/store/background-connection', () => ({
submitRequestToBackground: jest.fn(),
}));
+jest.mock('../../../../ui/pages/confirmations/hooks/useAssetDetails', () => ({
+ ...jest.requireActual(
+ '../../../../ui/pages/confirmations/hooks/useAssetDetails',
+ ),
+ useAssetDetails: jest.fn().mockResolvedValue({
+ decimals: '4',
+ }),
+}));
+
const mockedBackgroundConnection = jest.mocked(backgroundConnection);
const backgroundConnectionMocked = {
onNotification: jest.fn(),
};
+const mockedAssetDetails = jest.mocked(useAssetDetails);
const renderSingleBatchSignature = async () => {
const account =
@@ -58,6 +69,10 @@ describe('Permit Single Signature Tests', () => {
getTokenStandardAndDetails: { decimals: '2' },
}),
);
+ mockedAssetDetails.mockImplementation(() => ({
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ decimals: '4' as any,
+ }));
});
afterEach(() => {
diff --git a/test/integration/confirmations/signatures/permit-tradeOrder.test.tsx b/test/integration/confirmations/signatures/permit-tradeOrder.test.tsx
index 429e533ff8f8..3f915967dc00 100644
--- a/test/integration/confirmations/signatures/permit-tradeOrder.test.tsx
+++ b/test/integration/confirmations/signatures/permit-tradeOrder.test.tsx
@@ -1,10 +1,11 @@
import { act, screen } from '@testing-library/react';
import nock from 'nock';
-import mockMetaMaskState from '../../data/integration-init-state.json';
-import { integrationTestRender } from '../../../lib/render-helpers';
+import { useAssetDetails } from '../../../../ui/pages/confirmations/hooks/useAssetDetails';
import * as backgroundConnection from '../../../../ui/store/background-connection';
-import { createMockImplementation } from '../../helpers';
import { tEn } from '../../../lib/i18n-helpers';
+import { integrationTestRender } from '../../../lib/render-helpers';
+import mockMetaMaskState from '../../data/integration-init-state.json';
+import { createMockImplementation } from '../../helpers';
import {
getMetaMaskStateWithUnapprovedPermitSign,
verifyDetails,
@@ -15,10 +16,20 @@ jest.mock('../../../../ui/store/background-connection', () => ({
submitRequestToBackground: jest.fn(),
}));
+jest.mock('../../../../ui/pages/confirmations/hooks/useAssetDetails', () => ({
+ ...jest.requireActual(
+ '../../../../ui/pages/confirmations/hooks/useAssetDetails',
+ ),
+ useAssetDetails: jest.fn().mockResolvedValue({
+ decimals: '4',
+ }),
+}));
+
const mockedBackgroundConnection = jest.mocked(backgroundConnection);
const backgroundConnectionMocked = {
onNotification: jest.fn(),
};
+const mockedAssetDetails = jest.mocked(useAssetDetails);
const renderTradeOrderSignature = async () => {
const account =
@@ -58,6 +69,10 @@ describe('Permit Trade Order Tests', () => {
getTokenStandardAndDetails: { decimals: '2' },
}),
);
+ mockedAssetDetails.mockImplementation(() => ({
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ decimals: '4' as any,
+ }));
});
afterEach(() => {
diff --git a/test/integration/confirmations/signatures/permit.test.tsx b/test/integration/confirmations/signatures/permit.test.tsx
index 6b736e4add90..332cebc3a6b2 100644
--- a/test/integration/confirmations/signatures/permit.test.tsx
+++ b/test/integration/confirmations/signatures/permit.test.tsx
@@ -7,6 +7,7 @@ import {
MetaMetricsEventName,
} from '../../../../shared/constants/metametrics';
import { shortenAddress } from '../../../../ui/helpers/utils/util';
+import { useAssetDetails } from '../../../../ui/pages/confirmations/hooks/useAssetDetails';
import * as backgroundConnection from '../../../../ui/store/background-connection';
import { integrationTestRender } from '../../../lib/render-helpers';
import mockMetaMaskState from '../../data/integration-init-state.json';
@@ -18,10 +19,20 @@ jest.mock('../../../../ui/store/background-connection', () => ({
submitRequestToBackground: jest.fn(),
}));
+jest.mock('../../../../ui/pages/confirmations/hooks/useAssetDetails', () => ({
+ ...jest.requireActual(
+ '../../../../ui/pages/confirmations/hooks/useAssetDetails',
+ ),
+ useAssetDetails: jest.fn().mockResolvedValue({
+ decimals: '4',
+ }),
+}));
+
const mockedBackgroundConnection = jest.mocked(backgroundConnection);
const backgroundConnectionMocked = {
onNotification: jest.fn(),
};
+const mockedAssetDetails = jest.mocked(useAssetDetails);
describe('Permit Confirmation', () => {
beforeEach(() => {
@@ -31,6 +42,10 @@ describe('Permit Confirmation', () => {
getTokenStandardAndDetails: { decimals: '2', standard: 'ERC20' },
}),
);
+ mockedAssetDetails.mockImplementation(() => ({
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ decimals: '4' as any,
+ }));
});
afterEach(() => {
diff --git a/test/integration/confirmations/signatures/personalSign.test.tsx b/test/integration/confirmations/signatures/personalSign.test.tsx
index 03685f46ab7b..d58e0d441e03 100644
--- a/test/integration/confirmations/signatures/personalSign.test.tsx
+++ b/test/integration/confirmations/signatures/personalSign.test.tsx
@@ -1,6 +1,6 @@
import { ApprovalType } from '@metamask/controller-utils';
-import { act, fireEvent, screen, waitFor } from '@testing-library/react';
import { CHAIN_IDS } from '@metamask/transaction-controller';
+import { act, fireEvent, screen, waitFor } from '@testing-library/react';
import { MESSAGE_TYPE } from '../../../../shared/constants/app';
import {
MetaMetricsEventCategory,
@@ -8,6 +8,7 @@ import {
MetaMetricsEventName,
} from '../../../../shared/constants/metametrics';
import { shortenAddress } from '../../../../ui/helpers/utils/util';
+import { useAssetDetails } from '../../../../ui/pages/confirmations/hooks/useAssetDetails';
import * as backgroundConnection from '../../../../ui/store/background-connection';
import { integrationTestRender } from '../../../lib/render-helpers';
import mockMetaMaskState from '../../data/integration-init-state.json';
@@ -17,7 +18,17 @@ jest.mock('../../../../ui/store/background-connection', () => ({
submitRequestToBackground: jest.fn(),
}));
+jest.mock('../../../../ui/pages/confirmations/hooks/useAssetDetails', () => ({
+ ...jest.requireActual(
+ '../../../../ui/pages/confirmations/hooks/useAssetDetails',
+ ),
+ useAssetDetails: jest.fn().mockResolvedValue({
+ decimals: '4',
+ }),
+}));
+
const mockedBackgroundConnection = jest.mocked(backgroundConnection);
+const mockedAssetDetails = jest.mocked(useAssetDetails);
const backgroundConnectionMocked = {
onNotification: jest.fn(),
@@ -68,6 +79,10 @@ const getMetaMaskStateWithUnapprovedPersonalSign = (accountAddress: string) => {
describe('PersonalSign Confirmation', () => {
beforeEach(() => {
jest.resetAllMocks();
+ mockedAssetDetails.mockImplementation(() => ({
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ decimals: '4' as any,
+ }));
});
it('displays the header account modal with correct data', async () => {
diff --git a/test/integration/confirmations/transactions/alerts.test.tsx b/test/integration/confirmations/transactions/alerts.test.tsx
index b21a7dbb679c..ee7e95762ac3 100644
--- a/test/integration/confirmations/transactions/alerts.test.tsx
+++ b/test/integration/confirmations/transactions/alerts.test.tsx
@@ -1,12 +1,13 @@
import { randomUUID } from 'crypto';
-import { act, fireEvent, screen } from '@testing-library/react';
import { ApprovalType } from '@metamask/controller-utils';
+import { act, fireEvent, screen } from '@testing-library/react';
import nock from 'nock';
-import mockMetaMaskState from '../../data/integration-init-state.json';
-import { integrationTestRender } from '../../../lib/render-helpers';
+import { useAssetDetails } from '../../../../ui/pages/confirmations/hooks/useAssetDetails';
import * as backgroundConnection from '../../../../ui/store/background-connection';
-import { createMockImplementation, mock4byte } from '../../helpers';
+import { integrationTestRender } from '../../../lib/render-helpers';
import { createTestProviderTools } from '../../../stub/provider';
+import mockMetaMaskState from '../../data/integration-init-state.json';
+import { createMockImplementation, mock4byte } from '../../helpers';
import { getUnapprovedApproveTransaction } from './transactionDataHelpers';
jest.mock('../../../../ui/store/background-connection', () => ({
@@ -15,7 +16,17 @@ jest.mock('../../../../ui/store/background-connection', () => ({
callBackgroundMethod: jest.fn(),
}));
+jest.mock('../../../../ui/pages/confirmations/hooks/useAssetDetails', () => ({
+ ...jest.requireActual(
+ '../../../../ui/pages/confirmations/hooks/useAssetDetails',
+ ),
+ useAssetDetails: jest.fn().mockResolvedValue({
+ decimals: '4',
+ }),
+}));
+
const mockedBackgroundConnection = jest.mocked(backgroundConnection);
+const mockedAssetDetails = jest.mocked(useAssetDetails);
const backgroundConnectionMocked = {
onNotification: jest.fn(),
@@ -92,6 +103,10 @@ describe('Contract Interaction Confirmation Alerts', () => {
setupSubmitRequestToBackgroundMocks();
const APPROVE_NFT_HEX_SIG = '0x095ea7b3';
mock4byte(APPROVE_NFT_HEX_SIG);
+ mockedAssetDetails.mockImplementation(() => ({
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ decimals: '4' as any,
+ }));
});
afterEach(() => {
diff --git a/test/integration/confirmations/transactions/contract-deployment.test.tsx b/test/integration/confirmations/transactions/contract-deployment.test.tsx
index 7698ce3ef8b2..45410c0a76d9 100644
--- a/test/integration/confirmations/transactions/contract-deployment.test.tsx
+++ b/test/integration/confirmations/transactions/contract-deployment.test.tsx
@@ -13,6 +13,7 @@ import {
MetaMetricsEventLocation,
MetaMetricsEventName,
} from '../../../../shared/constants/metametrics';
+import { useAssetDetails } from '../../../../ui/pages/confirmations/hooks/useAssetDetails';
import * as backgroundConnection from '../../../../ui/store/background-connection';
import { tEn } from '../../../lib/i18n-helpers';
import { integrationTestRender } from '../../../lib/render-helpers';
@@ -26,7 +27,17 @@ jest.mock('../../../../ui/store/background-connection', () => ({
callBackgroundMethod: jest.fn(),
}));
+jest.mock('../../../../ui/pages/confirmations/hooks/useAssetDetails', () => ({
+ ...jest.requireActual(
+ '../../../../ui/pages/confirmations/hooks/useAssetDetails',
+ ),
+ useAssetDetails: jest.fn().mockResolvedValue({
+ decimals: '4',
+ }),
+}));
+
const mockedBackgroundConnection = jest.mocked(backgroundConnection);
+const mockedAssetDetails = jest.mocked(useAssetDetails);
const backgroundConnectionMocked = {
onNotification: jest.fn(),
@@ -136,6 +147,10 @@ describe('Contract Deployment Confirmation', () => {
setupSubmitRequestToBackgroundMocks();
const DEPOSIT_HEX_SIG = '0xd0e30db0';
mock4byte(DEPOSIT_HEX_SIG);
+ mockedAssetDetails.mockImplementation(() => ({
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ decimals: '4' as any,
+ }));
});
afterEach(() => {
diff --git a/test/integration/confirmations/transactions/contract-interaction.test.tsx b/test/integration/confirmations/transactions/contract-interaction.test.tsx
index 5db121bc9fda..e3da57aeceb3 100644
--- a/test/integration/confirmations/transactions/contract-interaction.test.tsx
+++ b/test/integration/confirmations/transactions/contract-interaction.test.tsx
@@ -13,6 +13,7 @@ import {
MetaMetricsEventLocation,
MetaMetricsEventName,
} from '../../../../shared/constants/metametrics';
+import { useAssetDetails } from '../../../../ui/pages/confirmations/hooks/useAssetDetails';
import * as backgroundConnection from '../../../../ui/store/background-connection';
import { tEn } from '../../../lib/i18n-helpers';
import { integrationTestRender } from '../../../lib/render-helpers';
@@ -31,7 +32,17 @@ jest.mock('../../../../ui/store/background-connection', () => ({
callBackgroundMethod: jest.fn(),
}));
+jest.mock('../../../../ui/pages/confirmations/hooks/useAssetDetails', () => ({
+ ...jest.requireActual(
+ '../../../../ui/pages/confirmations/hooks/useAssetDetails',
+ ),
+ useAssetDetails: jest.fn().mockResolvedValue({
+ decimals: '4',
+ }),
+}));
+
const mockedBackgroundConnection = jest.mocked(backgroundConnection);
+const mockedAssetDetails = jest.mocked(useAssetDetails);
const backgroundConnectionMocked = {
onNotification: jest.fn(),
@@ -156,6 +167,10 @@ describe('Contract Interaction Confirmation', () => {
setupSubmitRequestToBackgroundMocks();
const MINT_NFT_HEX_SIG = '0x3b4b1381';
mock4byte(MINT_NFT_HEX_SIG);
+ mockedAssetDetails.mockImplementation(() => ({
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ decimals: '4' as any,
+ }));
});
afterEach(() => {
diff --git a/test/integration/confirmations/transactions/erc20-approve.test.tsx b/test/integration/confirmations/transactions/erc20-approve.test.tsx
index c25b2ee3627d..f77c8162b79d 100644
--- a/test/integration/confirmations/transactions/erc20-approve.test.tsx
+++ b/test/integration/confirmations/transactions/erc20-approve.test.tsx
@@ -3,6 +3,7 @@ import { act, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import nock from 'nock';
import { TokenStandard } from '../../../../shared/constants/transaction';
+import { useAssetDetails } from '../../../../ui/pages/confirmations/hooks/useAssetDetails';
import * as backgroundConnection from '../../../../ui/store/background-connection';
import { tEn } from '../../../lib/i18n-helpers';
import { integrationTestRender } from '../../../lib/render-helpers';
@@ -17,7 +18,17 @@ jest.mock('../../../../ui/store/background-connection', () => ({
callBackgroundMethod: jest.fn(),
}));
+jest.mock('../../../../ui/pages/confirmations/hooks/useAssetDetails', () => ({
+ ...jest.requireActual(
+ '../../../../ui/pages/confirmations/hooks/useAssetDetails',
+ ),
+ useAssetDetails: jest.fn().mockImplementation(() => ({
+ decimals: '4',
+ })),
+}));
+
const mockedBackgroundConnection = jest.mocked(backgroundConnection);
+const mockedAssetDetails = jest.mocked(useAssetDetails);
const backgroundConnectionMocked = {
onNotification: jest.fn(),
@@ -140,6 +151,10 @@ describe('ERC20 Approve Confirmation', () => {
const APPROVE_ERC20_HEX_SIG = '0x095ea7b3';
const APPROVE_ERC20_TEXT_SIG = 'approve(address,uint256)';
mock4byte(APPROVE_ERC20_HEX_SIG, APPROVE_ERC20_TEXT_SIG);
+ mockedAssetDetails.mockImplementation(() => ({
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ decimals: '4' as any,
+ }));
});
afterEach(() => {
diff --git a/test/integration/confirmations/transactions/erc721-approve.test.tsx b/test/integration/confirmations/transactions/erc721-approve.test.tsx
index 4f211576e6cd..cce5456ae0a2 100644
--- a/test/integration/confirmations/transactions/erc721-approve.test.tsx
+++ b/test/integration/confirmations/transactions/erc721-approve.test.tsx
@@ -3,6 +3,7 @@ import { act, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import nock from 'nock';
import { TokenStandard } from '../../../../shared/constants/transaction';
+import { useAssetDetails } from '../../../../ui/pages/confirmations/hooks/useAssetDetails';
import * as backgroundConnection from '../../../../ui/store/background-connection';
import { tEn } from '../../../lib/i18n-helpers';
import { integrationTestRender } from '../../../lib/render-helpers';
@@ -17,7 +18,17 @@ jest.mock('../../../../ui/store/background-connection', () => ({
callBackgroundMethod: jest.fn(),
}));
+jest.mock('../../../../ui/pages/confirmations/hooks/useAssetDetails', () => ({
+ ...jest.requireActual(
+ '../../../../ui/pages/confirmations/hooks/useAssetDetails',
+ ),
+ useAssetDetails: jest.fn().mockResolvedValue({
+ decimals: '4',
+ }),
+}));
+
const mockedBackgroundConnection = jest.mocked(backgroundConnection);
+const mockedAssetDetails = jest.mocked(useAssetDetails);
const backgroundConnectionMocked = {
onNotification: jest.fn(),
@@ -140,6 +151,10 @@ describe('ERC721 Approve Confirmation', () => {
const APPROVE_NFT_HEX_SIG = '0x095ea7b3';
const APPROVE_NFT_TEXT_SIG = 'approve(address,uint256)';
mock4byte(APPROVE_NFT_HEX_SIG, APPROVE_NFT_TEXT_SIG);
+ mockedAssetDetails.mockImplementation(() => ({
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ decimals: '4' as any,
+ }));
});
afterEach(() => {
@@ -220,6 +235,7 @@ describe('ERC721 Approve Confirmation', () => {
const approveDetails = await screen.findByTestId(
'confirmation__approve-details',
);
+
expect(approveDetails).toBeInTheDocument();
const approveDetailsSpender = await screen.findByTestId(
'confirmation__approve-spender',
diff --git a/test/integration/confirmations/transactions/increase-allowance.test.tsx b/test/integration/confirmations/transactions/increase-allowance.test.tsx
index 810477d3a3a5..49b33bf2d21b 100644
--- a/test/integration/confirmations/transactions/increase-allowance.test.tsx
+++ b/test/integration/confirmations/transactions/increase-allowance.test.tsx
@@ -3,6 +3,7 @@ import { act, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import nock from 'nock';
import { TokenStandard } from '../../../../shared/constants/transaction';
+import { useAssetDetails } from '../../../../ui/pages/confirmations/hooks/useAssetDetails';
import * as backgroundConnection from '../../../../ui/store/background-connection';
import { tEn } from '../../../lib/i18n-helpers';
import { integrationTestRender } from '../../../lib/render-helpers';
@@ -17,7 +18,17 @@ jest.mock('../../../../ui/store/background-connection', () => ({
callBackgroundMethod: jest.fn(),
}));
+jest.mock('../../../../ui/pages/confirmations/hooks/useAssetDetails', () => ({
+ ...jest.requireActual(
+ '../../../../ui/pages/confirmations/hooks/useAssetDetails',
+ ),
+ useAssetDetails: jest.fn().mockResolvedValue({
+ decimals: '4',
+ }),
+}));
+
const mockedBackgroundConnection = jest.mocked(backgroundConnection);
+const mockedAssetDetails = jest.mocked(useAssetDetails);
const backgroundConnectionMocked = {
onNotification: jest.fn(),
@@ -144,6 +155,10 @@ describe('ERC20 increaseAllowance Confirmation', () => {
INCREASE_ALLOWANCE_ERC20_HEX_SIG,
INCREASE_ALLOWANCE_ERC20_TEXT_SIG,
);
+ mockedAssetDetails.mockImplementation(() => ({
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ decimals: '4' as any,
+ }));
});
afterEach(() => {
diff --git a/test/integration/confirmations/transactions/set-approval-for-all.test.tsx b/test/integration/confirmations/transactions/set-approval-for-all.test.tsx
index ebe680983a6c..236162c7b08f 100644
--- a/test/integration/confirmations/transactions/set-approval-for-all.test.tsx
+++ b/test/integration/confirmations/transactions/set-approval-for-all.test.tsx
@@ -3,6 +3,7 @@ import { act, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import nock from 'nock';
import { TokenStandard } from '../../../../shared/constants/transaction';
+import { useAssetDetails } from '../../../../ui/pages/confirmations/hooks/useAssetDetails';
import * as backgroundConnection from '../../../../ui/store/background-connection';
import { tEn } from '../../../lib/i18n-helpers';
import { integrationTestRender } from '../../../lib/render-helpers';
@@ -17,7 +18,17 @@ jest.mock('../../../../ui/store/background-connection', () => ({
callBackgroundMethod: jest.fn(),
}));
+jest.mock('../../../../ui/pages/confirmations/hooks/useAssetDetails', () => ({
+ ...jest.requireActual(
+ '../../../../ui/pages/confirmations/hooks/useAssetDetails',
+ ),
+ useAssetDetails: jest.fn().mockResolvedValue({
+ decimals: '4',
+ }),
+}));
+
const mockedBackgroundConnection = jest.mocked(backgroundConnection);
+const mockedAssetDetails = jest.mocked(useAssetDetails);
const backgroundConnectionMocked = {
onNotification: jest.fn(),
@@ -144,6 +155,10 @@ describe('ERC721 setApprovalForAll Confirmation', () => {
INCREASE_SET_APPROVAL_FOR_ALL_HEX_SIG,
INCREASE_SET_APPROVAL_FOR_ALL_TEXT_SIG,
);
+ mockedAssetDetails.mockImplementation(() => ({
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ decimals: '4' as any,
+ }));
});
afterEach(() => {
diff --git a/ui/pages/confirmations/components/confirm/info/approve/approve-static-simulation/approve-static-simulation.tsx b/ui/pages/confirmations/components/confirm/info/approve/approve-static-simulation/approve-static-simulation.tsx
index 8d66b17a1f46..67ff183784c9 100644
--- a/ui/pages/confirmations/components/confirm/info/approve/approve-static-simulation/approve-static-simulation.tsx
+++ b/ui/pages/confirmations/components/confirm/info/approve/approve-static-simulation/approve-static-simulation.tsx
@@ -27,15 +27,13 @@ export const ApproveStaticSimulation = () => {
const { currentConfirmation: transactionMeta } =
useConfirmContext();
- const { decimals: initialDecimals } = useAssetDetails(
+ const { decimals } = useAssetDetails(
transactionMeta?.txParams?.to,
transactionMeta?.txParams?.from,
transactionMeta?.txParams?.data,
transactionMeta?.chainId,
);
- const decimals = initialDecimals || '0';
-
const {
spendingCap,
isUnlimitedSpendingCap,
diff --git a/ui/pages/confirmations/components/confirm/info/approve/approve.test.tsx b/ui/pages/confirmations/components/confirm/info/approve/approve.test.tsx
index 914e276369f5..f69671dccf9f 100644
--- a/ui/pages/confirmations/components/confirm/info/approve/approve.test.tsx
+++ b/ui/pages/confirmations/components/confirm/info/approve/approve.test.tsx
@@ -1,8 +1,10 @@
+import { screen, waitFor } from '@testing-library/dom';
import React from 'react';
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import { getMockApproveConfirmState } from '../../../../../../../test/data/confirmations/helper';
import { renderWithConfirmContextProvider } from '../../../../../../../test/lib/confirmations/render-helpers';
+import { useAssetDetails } from '../../../../hooks/useAssetDetails';
import ApproveInfo from './approve';
jest.mock('../../../../../../store/actions', () => ({
@@ -32,7 +34,7 @@ jest.mock('./hooks/use-approve-token-simulation', () => ({
jest.mock('../../../../hooks/useAssetDetails', () => ({
useAssetDetails: jest.fn(() => ({
- decimals: 18,
+ decimals: '18',
})),
}));
@@ -70,6 +72,14 @@ jest.mock('../hooks/useDecodedTransactionData', () => ({
describe('', () => {
const middleware = [thunk];
+ const mockedAssetDetails = jest.mocked(useAssetDetails);
+
+ beforeEach(() => {
+ mockedAssetDetails.mockImplementation(() => ({
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ decimals: '4' as any,
+ }));
+ });
it('renders component for approve request', async () => {
const state = getMockApproveConfirmState();
@@ -81,6 +91,10 @@ describe('', () => {
mockStore,
);
+ await waitFor(() => {
+ expect(screen.getByText('Speed')).toBeInTheDocument();
+ });
+
expect(container).toMatchSnapshot();
});
});
diff --git a/ui/pages/confirmations/components/confirm/info/approve/approve.tsx b/ui/pages/confirmations/components/confirm/info/approve/approve.tsx
index d1d9fbc08364..e5015af533fd 100644
--- a/ui/pages/confirmations/components/confirm/info/approve/approve.tsx
+++ b/ui/pages/confirmations/components/confirm/info/approve/approve.tsx
@@ -35,7 +35,7 @@ const ApproveInfo = () => {
const { spendingCap, pending } = useApproveTokenSimulation(
transactionMeta,
- decimals || '0',
+ decimals,
);
const showRevokeVariant =
@@ -46,7 +46,7 @@ const ApproveInfo = () => {
return null;
}
- if (pending) {
+ if (pending || (!isNFT && !decimals)) {
return ;
}
diff --git a/ui/pages/confirmations/components/confirm/info/approve/edit-spending-cap-modal/edit-spending-cap-modal.tsx b/ui/pages/confirmations/components/confirm/info/approve/edit-spending-cap-modal/edit-spending-cap-modal.tsx
index 1cd080de96d4..f53c7bae42b2 100644
--- a/ui/pages/confirmations/components/confirm/info/approve/edit-spending-cap-modal/edit-spending-cap-modal.tsx
+++ b/ui/pages/confirmations/components/confirm/info/approve/edit-spending-cap-modal/edit-spending-cap-modal.tsx
@@ -64,7 +64,7 @@ export const EditSpendingCapModal = ({
const { formattedSpendingCap, spendingCap } = useApproveTokenSimulation(
transactionMeta,
- decimals || '0',
+ decimals,
);
const [customSpendingCapInputValue, setCustomSpendingCapInputValue] =
diff --git a/ui/pages/confirmations/components/confirm/info/approve/hooks/use-approve-token-simulation.ts b/ui/pages/confirmations/components/confirm/info/approve/hooks/use-approve-token-simulation.ts
index acd470ba8822..74c857f7078b 100644
--- a/ui/pages/confirmations/components/confirm/info/approve/hooks/use-approve-token-simulation.ts
+++ b/ui/pages/confirmations/components/confirm/info/approve/hooks/use-approve-token-simulation.ts
@@ -18,7 +18,7 @@ function isSpendingCapUnlimited(decodedSpendingCap: number) {
export const useApproveTokenSimulation = (
transactionMeta: TransactionMeta,
- decimals: string,
+ decimals: string | undefined,
) => {
const locale = useSelector(getIntlLocale);
const { isNFT, pending: isNFTPending } = useIsNFT(transactionMeta);
@@ -43,7 +43,7 @@ export const useApproveTokenSimulation = (
return calcTokenAmount(
value.data[0].params[paramIndex].value,
- Number(decimals),
+ Number(decimals || '0'),
).toFixed();
}, [value, decimals]);
diff --git a/ui/pages/confirmations/components/confirm/info/approve/spending-cap/spending-cap.tsx b/ui/pages/confirmations/components/confirm/info/approve/spending-cap/spending-cap.tsx
index 29d009c5b810..3a8c6a5a26ec 100644
--- a/ui/pages/confirmations/components/confirm/info/approve/spending-cap/spending-cap.tsx
+++ b/ui/pages/confirmations/components/confirm/info/approve/spending-cap/spending-cap.tsx
@@ -90,10 +90,7 @@ export const SpendingCap = ({
Number(decimals ?? '0'),
).toFixed();
- const { pending } = useApproveTokenSimulation(
- transactionMeta,
- decimals || '0',
- );
+ const { pending } = useApproveTokenSimulation(transactionMeta, decimals);
if (pending) {
return ;
diff --git a/ui/pages/confirmations/components/confirm/info/info.test.tsx b/ui/pages/confirmations/components/confirm/info/info.test.tsx
index 75d98d91a1bd..50046ec96764 100644
--- a/ui/pages/confirmations/components/confirm/info/info.test.tsx
+++ b/ui/pages/confirmations/components/confirm/info/info.test.tsx
@@ -9,6 +9,7 @@ import {
getMockTypedSignConfirmState,
} from '../../../../../../test/data/confirmations/helper';
import { renderWithConfirmContextProvider } from '../../../../../../test/lib/confirmations/render-helpers';
+import { useAssetDetails } from '../../../hooks/useAssetDetails';
import Info from './info';
jest.mock(
@@ -28,7 +29,23 @@ jest.mock('../../../../../store/actions', () => ({
}),
}));
+jest.mock('../../../hooks/useAssetDetails', () => ({
+ ...jest.requireActual('../../../hooks/useAssetDetails'),
+ useAssetDetails: jest.fn().mockResolvedValue({
+ decimals: '4',
+ }),
+}));
+
describe('Info', () => {
+ const mockedAssetDetails = jest.mocked(useAssetDetails);
+
+ beforeEach(() => {
+ mockedAssetDetails.mockImplementation(() => ({
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ decimals: '4' as any,
+ }));
+ });
+
it('renders info section for personal sign request', () => {
const state = getMockPersonalSignConfirmState();
const mockStore = configureMockStore([])(state);
diff --git a/ui/pages/confirmations/components/confirm/title/hooks/useCurrentSpendingCap.ts b/ui/pages/confirmations/components/confirm/title/hooks/useCurrentSpendingCap.ts
index b50948259734..5fa3918804d7 100644
--- a/ui/pages/confirmations/components/confirm/title/hooks/useCurrentSpendingCap.ts
+++ b/ui/pages/confirmations/components/confirm/title/hooks/useCurrentSpendingCap.ts
@@ -43,7 +43,7 @@ export function useCurrentSpendingCap(currentConfirmation: Confirmation) {
const { spendingCap, pending } = useApproveTokenSimulation(
currentConfirmation as TransactionMeta,
- decimals || '0',
+ decimals,
);
let customSpendingCap = '';
diff --git a/ui/pages/confirmations/confirm/confirm.test.tsx b/ui/pages/confirmations/confirm/confirm.test.tsx
index 939ca8768afe..158884fc36a0 100644
--- a/ui/pages/confirmations/confirm/confirm.test.tsx
+++ b/ui/pages/confirmations/confirm/confirm.test.tsx
@@ -16,6 +16,7 @@ import {
import mockState from '../../../../test/data/mock-state.json';
import { renderWithConfirmContextProvider } from '../../../../test/lib/confirmations/render-helpers';
import * as actions from '../../../store/actions';
+import { useAssetDetails } from '../hooks/useAssetDetails';
import { SignatureRequestType } from '../types/confirm';
import { memoizedGetTokenStandardAndDetails } from '../utils/token';
import Confirm from './confirm';
@@ -27,7 +28,15 @@ jest.mock('react-router-dom', () => ({
}),
}));
+jest.mock('../hooks/useAssetDetails', () => ({
+ ...jest.requireActual('../hooks/useAssetDetails'),
+ useAssetDetails: jest.fn().mockResolvedValue({
+ decimals: '4',
+ }),
+}));
+
const middleware = [thunk];
+const mockedAssetDetails = jest.mocked(useAssetDetails);
describe('Confirm', () => {
afterEach(() => {
@@ -37,6 +46,13 @@ describe('Confirm', () => {
memoizedGetTokenStandardAndDetails?.cache?.clear?.();
});
+ beforeEach(() => {
+ mockedAssetDetails.mockImplementation(() => ({
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ decimals: '4' as any,
+ }));
+ });
+
it('should render', () => {
const mockStore = configureMockStore(middleware)(mockState);
From a1b41c4e49497805f437011b643e99cf1680ebb2 Mon Sep 17 00:00:00 2001
From: Jyoti Puri
Date: Thu, 19 Dec 2024 16:38:26 +0530
Subject: [PATCH 036/202] fix: personal sign message - decode message to UTF-8
string only if it is valid UTF-8 (#29232)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
## **Description**
Converts personal sign message to UTF-8 string only if it can be
converted to valid UTF-8 string.
## **Related issues**
Fixes: https://github.com/MetaMask/metamask-extension/issues/3931
## **Manual testing steps**
1. Send personal sign request to extension with message string
`0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470`
2. Check on confirmation page that it is displayed as UTF-8 string
## **Screenshots/Recordings**
## **Pre-merge author checklist**
- [X] I've followed [MetaMask Contributor
Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask
Extension Coding
Standards](https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/CODING_GUIDELINES.md).
- [X] I've completed the PR template to the best of my ability
- [X] I’ve included tests if applicable
- [X] I’ve documented my code using [JSDoc](https://jsdoc.app/) format
if applicable
- [X] I’ve applied the right labels on the PR (see [labeling
guidelines](https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/LABELING_GUIDELINES.md)).
Not required for external contributors.
## **Pre-merge reviewer checklist**
- [ ] I've manually tested the PR (e.g. pull and build branch, run the
app, test code being changed).
- [ ] I confirm that this PR addresses all acceptance criteria described
in the ticket it closes and includes the necessary testing evidence such
as recordings and or screenshots.
---
.../info/personal-sign/personal-sign.test.tsx | 26 +++++++++++++++++--
.../info/personal-sign/personal-sign.tsx | 13 ++++++++--
.../components/confirm/info/utils.test.ts | 9 +++++++
.../components/confirm/info/utils.ts | 14 ++++++++++
4 files changed, 58 insertions(+), 4 deletions(-)
diff --git a/ui/pages/confirmations/components/confirm/info/personal-sign/personal-sign.test.tsx b/ui/pages/confirmations/components/confirm/info/personal-sign/personal-sign.test.tsx
index e105493485ec..3b5d1dfc3e62 100644
--- a/ui/pages/confirmations/components/confirm/info/personal-sign/personal-sign.test.tsx
+++ b/ui/pages/confirmations/components/confirm/info/personal-sign/personal-sign.test.tsx
@@ -9,9 +9,13 @@ import {
getMockTypedSignConfirmStateForRequest,
} from '../../../../../../../test/data/confirmations/helper';
import { renderWithConfirmContextProvider } from '../../../../../../../test/lib/confirmations/render-helpers';
-import { signatureRequestSIWE } from '../../../../../../../test/data/confirmations/personal_sign';
-import * as utils from '../../../../utils';
+import {
+ signatureRequestSIWE,
+ unapprovedPersonalSignMsg,
+} from '../../../../../../../test/data/confirmations/personal_sign';
import * as snapUtils from '../../../../../../helpers/utils/snaps';
+import { SignatureRequestType } from '../../../../types/confirm';
+import * as utils from '../../../../utils';
import PersonalSignInfo from './personal-sign';
jest.mock(
@@ -184,4 +188,22 @@ describe('PersonalSignInfo', () => {
queryByText('This is the site asking for your signature.'),
).toBeDefined();
});
+
+ it('display hex message value if it can not be converted to valid UTF-8 string', () => {
+ const message =
+ '0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470';
+ const state = getMockPersonalSignConfirmStateForRequest({
+ ...unapprovedPersonalSignMsg,
+ msgParams: {
+ ...unapprovedPersonalSignMsg.msgParams,
+ data: message,
+ },
+ } as SignatureRequestType);
+ const mockStore = configureMockStore([])(state);
+ const { getByText } = renderWithConfirmContextProvider(
+ ,
+ mockStore,
+ );
+ expect(getByText(message)).toBeDefined();
+ });
});
diff --git a/ui/pages/confirmations/components/confirm/info/personal-sign/personal-sign.tsx b/ui/pages/confirmations/components/confirm/info/personal-sign/personal-sign.tsx
index bd6c41866917..38238a2fa49e 100644
--- a/ui/pages/confirmations/components/confirm/info/personal-sign/personal-sign.tsx
+++ b/ui/pages/confirmations/components/confirm/info/personal-sign/personal-sign.tsx
@@ -38,8 +38,17 @@ import { selectUseTransactionSimulations } from '../../../../selectors/preferenc
import { SignatureRequestType } from '../../../../types/confirm';
import { isSIWESignatureRequest } from '../../../../utils';
import { SigningInWithRow } from '../shared/sign-in-with-row/sign-in-with-row';
+import { isValidUTF8 } from '../utils';
import { SIWESignInfo } from './siwe-sign';
+const getMessageText = (hexString?: string) => {
+ if (!hexString) {
+ return hexString;
+ }
+ const messageText = sanitizeString(hexToText(hexString));
+ return isValidUTF8(messageText) ? messageText : hexString;
+};
+
const PersonalSignInfo: React.FC = () => {
const t = useI18nContext();
const { currentConfirmation } = useConfirmContext();
@@ -52,8 +61,8 @@ const PersonalSignInfo: React.FC = () => {
}
const isSIWE = isSIWESignatureRequest(currentConfirmation);
- const messageText = sanitizeString(
- hexToText(currentConfirmation.msgParams?.data),
+ const messageText = getMessageText(
+ currentConfirmation.msgParams?.data as string,
);
let toolTipMessage;
diff --git a/ui/pages/confirmations/components/confirm/info/utils.test.ts b/ui/pages/confirmations/components/confirm/info/utils.test.ts
index 9c12b9127811..8723cfc3f05d 100644
--- a/ui/pages/confirmations/components/confirm/info/utils.test.ts
+++ b/ui/pages/confirmations/components/confirm/info/utils.test.ts
@@ -4,6 +4,7 @@ import { DecodedTransactionDataSource } from '../../../../../../shared/types/tra
import {
getIsRevokeSetApprovalForAll,
hasValueAndNativeBalanceMismatch,
+ isValidUTF8,
} from './utils';
describe('getIsRevokeSetApprovalForAll', () => {
@@ -141,3 +142,11 @@ describe('hasValueAndNativeBalanceMismatch', () => {
expect(hasValueAndNativeBalanceMismatch(transaction)).toBe(true);
});
});
+
+describe('isValidUTF8', () => {
+ it('returns true for valid UTF-8 string', () => {
+ expect(isValidUTF8('Hello')).toEqual(true);
+ expect(isValidUTF8('\xC3\x28')).toEqual(true);
+ expect(isValidUTF8('😀')).toEqual(true);
+ });
+});
diff --git a/ui/pages/confirmations/components/confirm/info/utils.ts b/ui/pages/confirmations/components/confirm/info/utils.ts
index 1af918aea74e..17b10e555952 100644
--- a/ui/pages/confirmations/components/confirm/info/utils.ts
+++ b/ui/pages/confirmations/components/confirm/info/utils.ts
@@ -110,3 +110,17 @@ export function hasValueAndNativeBalanceMismatch(
nativeBalanceChange?.isDecrease === false,
);
}
+
+export function isValidUTF8(inputString: string) {
+ try {
+ const encoder = new TextEncoder();
+ const encoded = encoder.encode(inputString);
+
+ const decoder = new TextDecoder('utf-8', { fatal: true });
+ decoder.decode(encoded);
+
+ return true;
+ } catch (e) {
+ return false;
+ }
+}
From 3b7c9cbc9f0399d138229ad152ebec4eeadd13da Mon Sep 17 00:00:00 2001
From: Pedro Figueiredo
Date: Thu, 19 Dec 2024 11:32:15 +0000
Subject: [PATCH 037/202] fix: Ellipsis displayed for petname even though the
full name is visible (#29282)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
## **Description**
Previously, if a pet name with 12 characters would be set, the
truncation would be triggered with the 12 chars plus the three ellipsis
dots. This would result in a elongated pill component that would be too
long.
To fix this, we now truncate at 12 characters, but showing 9 characters
only plus the three dots, totalling the same maximum 12 chars.
Examples:
- Very large account name (23 characters) -> Very larg... (9 + 3
characters)
- My DeFi Account (15 characters) -> My DeFi A... (9 + 3 characters)
- DeFi Account (12 characters) -> DeFi Acco... (9 + 3 characters)
- Own Account (11 characters) -> Own Account (11 characters)
[![Open in GitHub
Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29282?quickstart=1)
## **Related issues**
Fixes: https://github.com/MetaMask/MetaMask-planning/issues/3775
## **Manual testing steps**
1. Go to this page...
2.
3.
## **Screenshots/Recordings**
### **Before**
### **After**
## **Pre-merge author checklist**
- [ ] I've followed [MetaMask Contributor
Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask
Extension Coding
Standards](https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/CODING_GUIDELINES.md).
- [ ] I've completed the PR template to the best of my ability
- [ ] I’ve included tests if applicable
- [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format
if applicable
- [ ] I’ve applied the right labels on the PR (see [labeling
guidelines](https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/LABELING_GUIDELINES.md)).
Not required for external contributors.
## **Pre-merge reviewer checklist**
- [ ] I've manually tested the PR (e.g. pull and build branch, run the
app, test code being changed).
- [ ] I confirm that this PR addresses all acceptance criteria described
in the ticket it closes and includes the necessary testing evidence such
as recordings and or screenshots.
---
test/e2e/tests/petnames/petnames-signatures.spec.js | 4 ++--
ui/components/app/name/__snapshots__/name.test.tsx.snap | 2 +-
.../name-details/__snapshots__/name-details.test.tsx.snap | 2 +-
ui/components/app/name/name.tsx | 2 +-
4 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/test/e2e/tests/petnames/petnames-signatures.spec.js b/test/e2e/tests/petnames/petnames-signatures.spec.js
index 4641424e053f..1ca7f47340d7 100644
--- a/test/e2e/tests/petnames/petnames-signatures.spec.js
+++ b/test/e2e/tests/petnames/petnames-signatures.spec.js
@@ -169,7 +169,7 @@ describe('Petnames - Signatures', function () {
await createSignatureRequest(driver, SIGNATURE_TYPE.TYPED_V4);
await switchToNotificationWindow(driver, 3);
await expectName(driver, 'test.lens', true);
- await expectName(driver, 'Test Token 2', true);
+ await expectName(driver, 'Test Toke...', true);
await showThirdPartyDetails(driver);
await expectName(driver, 'Custom Name', true);
},
@@ -269,7 +269,7 @@ describe('Petnames - Signatures', function () {
await createSignatureRequest(driver, SIGNATURE_TYPE.TYPED_V4);
await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
await expectName(driver, 'test.lens', true);
- await expectName(driver, 'Test Token 2', true);
+ await expectName(driver, 'Test Toke...', true);
await expectName(driver, 'Custom Name', true);
},
);
diff --git a/ui/components/app/name/__snapshots__/name.test.tsx.snap b/ui/components/app/name/__snapshots__/name.test.tsx.snap
index 286429760c1e..883717fca1f4 100644
--- a/ui/components/app/name/__snapshots__/name.test.tsx.snap
+++ b/ui/components/app/name/__snapshots__/name.test.tsx.snap
@@ -79,7 +79,7 @@ exports[`Name renders address with long saved name 1`] = `
- Very long an...
+ Very long...
diff --git a/ui/components/app/name/name-details/__snapshots__/name-details.test.tsx.snap b/ui/components/app/name/name-details/__snapshots__/name-details.test.tsx.snap
index 3520a1b64a13..8ddce02439c9 100644
--- a/ui/components/app/name/name-details/__snapshots__/name-details.test.tsx.snap
+++ b/ui/components/app/name/name-details/__snapshots__/name-details.test.tsx.snap
@@ -753,7 +753,7 @@ exports[`NameDetails renders with recognized name 1`] = `
- iZUMi Bond U...
+ iZUMi Bon...
diff --git a/ui/components/app/name/name.tsx b/ui/components/app/name/name.tsx
index 4871cc481f0c..1b5305c2b61f 100644
--- a/ui/components/app/name/name.tsx
+++ b/ui/components/app/name/name.tsx
@@ -106,7 +106,7 @@ const Name = memo(
const MAX_PET_NAME_LENGTH = 12;
const formattedName = shortenString(name || undefined, {
truncatedCharLimit: MAX_PET_NAME_LENGTH,
- truncatedStartChars: MAX_PET_NAME_LENGTH,
+ truncatedStartChars: MAX_PET_NAME_LENGTH - 3,
truncatedEndChars: 0,
skipCharacterInEnd: true,
});
From 5855938e202d2babe05cb1cd8785ef12fb3e0959 Mon Sep 17 00:00:00 2001
From: cryptodev-2s <109512101+cryptodev-2s@users.noreply.github.com>
Date: Thu, 19 Dec 2024 12:36:23 +0100
Subject: [PATCH 038/202] chore: bump `@metamask/user-operation-controller` to
`^21.0.0` (#29089)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
## **Description**
This PR bumps `@metamask/user-operation-controller` to `^21.0.0`
[![Open in GitHub
Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29089?quickstart=1)
## **Related issues**
Fixes: https://github.com/MetaMask/metamask-extension/issues/28986
## **Manual testing steps**
## **Screenshots/Recordings**
### **Before**
### **After**
## **Pre-merge author checklist**
- [x] I've followed [MetaMask Contributor
Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask
Extension Coding
Standards](https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/CODING_GUIDELINES.md).
- [x] I've completed the PR template to the best of my ability
- [x] I’ve included tests if applicable
- [x] I’ve documented my code using [JSDoc](https://jsdoc.app/) format
if applicable
- [x] I’ve applied the right labels on the PR (see [labeling
guidelines](https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/LABELING_GUIDELINES.md)).
Not required for external contributors.
## **Pre-merge reviewer checklist**
- [ ] I've manually tested the PR (e.g. pull and build branch, run the
app, test code being changed).
- [ ] I confirm that this PR addresses all acceptance criteria described
in the ticket it closes and includes the necessary testing evidence such
as recordings and or screenshots.
---------
Co-authored-by: MetaMask Bot
---
lavamoat/browserify/beta/policy.json | 24 ++++++++--
lavamoat/browserify/flask/policy.json | 24 ++++++++--
lavamoat/browserify/main/policy.json | 24 ++++++++--
lavamoat/browserify/mmi/policy.json | 24 ++++++++--
package.json | 2 +-
ui/ducks/metamask/metamask.js | 2 +-
yarn.lock | 69 +++++++++++----------------
7 files changed, 115 insertions(+), 54 deletions(-)
diff --git a/lavamoat/browserify/beta/policy.json b/lavamoat/browserify/beta/policy.json
index 82075f709022..05a22743204c 100644
--- a/lavamoat/browserify/beta/policy.json
+++ b/lavamoat/browserify/beta/policy.json
@@ -2155,7 +2155,7 @@
"TextEncoder": true
},
"packages": {
- "@noble/hashes": true
+ "@ethereumjs/tx>ethereum-cryptography>@noble/curves>@noble/hashes": true
}
},
"@noble/hashes": {
@@ -2170,6 +2170,24 @@
"crypto": true
}
},
+ "@ethereumjs/tx>ethereum-cryptography>@noble/curves>@noble/hashes": {
+ "globals": {
+ "TextEncoder": true,
+ "crypto": true
+ }
+ },
+ "@ethereumjs/tx>ethereum-cryptography>@scure/bip32>@noble/hashes": {
+ "globals": {
+ "TextEncoder": true,
+ "crypto": true
+ }
+ },
+ "@ethereumjs/tx>ethereum-cryptography>@noble/hashes": {
+ "globals": {
+ "TextEncoder": true,
+ "crypto": true
+ }
+ },
"eth-lattice-keyring>@ethereumjs/tx>ethereum-cryptography>@noble/hashes": {
"globals": {
"TextEncoder": true,
@@ -2248,7 +2266,7 @@
"@ethereumjs/tx>ethereum-cryptography>@scure/bip32": {
"packages": {
"@ethereumjs/tx>ethereum-cryptography>@noble/curves": true,
- "@noble/hashes": true,
+ "@ethereumjs/tx>ethereum-cryptography>@scure/bip32>@noble/hashes": true,
"@metamask/utils>@scure/base": true
}
},
@@ -3448,7 +3466,7 @@
},
"packages": {
"@ethereumjs/tx>ethereum-cryptography>@noble/curves": true,
- "@noble/hashes": true,
+ "@ethereumjs/tx>ethereum-cryptography>@noble/hashes": true,
"@ethereumjs/tx>ethereum-cryptography>@scure/bip32": true
}
},
diff --git a/lavamoat/browserify/flask/policy.json b/lavamoat/browserify/flask/policy.json
index 82075f709022..05a22743204c 100644
--- a/lavamoat/browserify/flask/policy.json
+++ b/lavamoat/browserify/flask/policy.json
@@ -2155,7 +2155,7 @@
"TextEncoder": true
},
"packages": {
- "@noble/hashes": true
+ "@ethereumjs/tx>ethereum-cryptography>@noble/curves>@noble/hashes": true
}
},
"@noble/hashes": {
@@ -2170,6 +2170,24 @@
"crypto": true
}
},
+ "@ethereumjs/tx>ethereum-cryptography>@noble/curves>@noble/hashes": {
+ "globals": {
+ "TextEncoder": true,
+ "crypto": true
+ }
+ },
+ "@ethereumjs/tx>ethereum-cryptography>@scure/bip32>@noble/hashes": {
+ "globals": {
+ "TextEncoder": true,
+ "crypto": true
+ }
+ },
+ "@ethereumjs/tx>ethereum-cryptography>@noble/hashes": {
+ "globals": {
+ "TextEncoder": true,
+ "crypto": true
+ }
+ },
"eth-lattice-keyring>@ethereumjs/tx>ethereum-cryptography>@noble/hashes": {
"globals": {
"TextEncoder": true,
@@ -2248,7 +2266,7 @@
"@ethereumjs/tx>ethereum-cryptography>@scure/bip32": {
"packages": {
"@ethereumjs/tx>ethereum-cryptography>@noble/curves": true,
- "@noble/hashes": true,
+ "@ethereumjs/tx>ethereum-cryptography>@scure/bip32>@noble/hashes": true,
"@metamask/utils>@scure/base": true
}
},
@@ -3448,7 +3466,7 @@
},
"packages": {
"@ethereumjs/tx>ethereum-cryptography>@noble/curves": true,
- "@noble/hashes": true,
+ "@ethereumjs/tx>ethereum-cryptography>@noble/hashes": true,
"@ethereumjs/tx>ethereum-cryptography>@scure/bip32": true
}
},
diff --git a/lavamoat/browserify/main/policy.json b/lavamoat/browserify/main/policy.json
index 82075f709022..05a22743204c 100644
--- a/lavamoat/browserify/main/policy.json
+++ b/lavamoat/browserify/main/policy.json
@@ -2155,7 +2155,7 @@
"TextEncoder": true
},
"packages": {
- "@noble/hashes": true
+ "@ethereumjs/tx>ethereum-cryptography>@noble/curves>@noble/hashes": true
}
},
"@noble/hashes": {
@@ -2170,6 +2170,24 @@
"crypto": true
}
},
+ "@ethereumjs/tx>ethereum-cryptography>@noble/curves>@noble/hashes": {
+ "globals": {
+ "TextEncoder": true,
+ "crypto": true
+ }
+ },
+ "@ethereumjs/tx>ethereum-cryptography>@scure/bip32>@noble/hashes": {
+ "globals": {
+ "TextEncoder": true,
+ "crypto": true
+ }
+ },
+ "@ethereumjs/tx>ethereum-cryptography>@noble/hashes": {
+ "globals": {
+ "TextEncoder": true,
+ "crypto": true
+ }
+ },
"eth-lattice-keyring>@ethereumjs/tx>ethereum-cryptography>@noble/hashes": {
"globals": {
"TextEncoder": true,
@@ -2248,7 +2266,7 @@
"@ethereumjs/tx>ethereum-cryptography>@scure/bip32": {
"packages": {
"@ethereumjs/tx>ethereum-cryptography>@noble/curves": true,
- "@noble/hashes": true,
+ "@ethereumjs/tx>ethereum-cryptography>@scure/bip32>@noble/hashes": true,
"@metamask/utils>@scure/base": true
}
},
@@ -3448,7 +3466,7 @@
},
"packages": {
"@ethereumjs/tx>ethereum-cryptography>@noble/curves": true,
- "@noble/hashes": true,
+ "@ethereumjs/tx>ethereum-cryptography>@noble/hashes": true,
"@ethereumjs/tx>ethereum-cryptography>@scure/bip32": true
}
},
diff --git a/lavamoat/browserify/mmi/policy.json b/lavamoat/browserify/mmi/policy.json
index a2453b1686dc..27ddfc86ff15 100644
--- a/lavamoat/browserify/mmi/policy.json
+++ b/lavamoat/browserify/mmi/policy.json
@@ -2247,7 +2247,7 @@
"TextEncoder": true
},
"packages": {
- "@noble/hashes": true
+ "@ethereumjs/tx>ethereum-cryptography>@noble/curves>@noble/hashes": true
}
},
"@noble/hashes": {
@@ -2262,6 +2262,24 @@
"crypto": true
}
},
+ "@ethereumjs/tx>ethereum-cryptography>@noble/curves>@noble/hashes": {
+ "globals": {
+ "TextEncoder": true,
+ "crypto": true
+ }
+ },
+ "@ethereumjs/tx>ethereum-cryptography>@scure/bip32>@noble/hashes": {
+ "globals": {
+ "TextEncoder": true,
+ "crypto": true
+ }
+ },
+ "@ethereumjs/tx>ethereum-cryptography>@noble/hashes": {
+ "globals": {
+ "TextEncoder": true,
+ "crypto": true
+ }
+ },
"eth-lattice-keyring>@ethereumjs/tx>ethereum-cryptography>@noble/hashes": {
"globals": {
"TextEncoder": true,
@@ -2340,7 +2358,7 @@
"@ethereumjs/tx>ethereum-cryptography>@scure/bip32": {
"packages": {
"@ethereumjs/tx>ethereum-cryptography>@noble/curves": true,
- "@noble/hashes": true,
+ "@ethereumjs/tx>ethereum-cryptography>@scure/bip32>@noble/hashes": true,
"@metamask/utils>@scure/base": true
}
},
@@ -3540,7 +3558,7 @@
},
"packages": {
"@ethereumjs/tx>ethereum-cryptography>@noble/curves": true,
- "@noble/hashes": true,
+ "@ethereumjs/tx>ethereum-cryptography>@noble/hashes": true,
"@ethereumjs/tx>ethereum-cryptography>@scure/bip32": true
}
},
diff --git a/package.json b/package.json
index 261ddd874484..1553c739936f 100644
--- a/package.json
+++ b/package.json
@@ -350,7 +350,7 @@
"@metamask/snaps-utils": "^8.6.1",
"@metamask/solana-wallet-snap": "^1.0.3",
"@metamask/transaction-controller": "^42.0.0",
- "@metamask/user-operation-controller": "^19.0.0",
+ "@metamask/user-operation-controller": "^21.0.0",
"@metamask/utils": "^10.0.1",
"@ngraveio/bc-ur": "^1.1.12",
"@noble/hashes": "^1.3.3",
diff --git a/ui/ducks/metamask/metamask.js b/ui/ducks/metamask/metamask.js
index f3453b77e7d9..c9c57057f2f9 100644
--- a/ui/ducks/metamask/metamask.js
+++ b/ui/ducks/metamask/metamask.js
@@ -373,7 +373,7 @@ export function isEIP1559Network(state, networkClientId) {
return (
state.metamask.networksMetadata?.[
networkClientId ?? selectedNetworkClientId
- ].EIPS[1559] === true
+ ]?.EIPS[1559] === true
);
}
diff --git a/yarn.lock b/yarn.lock
index 7bfb4b6e4336..494ffcc5b085 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -5958,19 +5958,19 @@ __metadata:
languageName: node
linkType: hard
-"@metamask/polling-controller@npm:^12.0.0, @metamask/polling-controller@npm:^12.0.1":
- version: 12.0.1
- resolution: "@metamask/polling-controller@npm:12.0.1"
+"@metamask/polling-controller@npm:^12.0.0, @metamask/polling-controller@npm:^12.0.1, @metamask/polling-controller@npm:^12.0.2":
+ version: 12.0.2
+ resolution: "@metamask/polling-controller@npm:12.0.2"
dependencies:
"@metamask/base-controller": "npm:^7.0.2"
- "@metamask/controller-utils": "npm:^11.4.2"
+ "@metamask/controller-utils": "npm:^11.4.4"
"@metamask/utils": "npm:^10.0.0"
"@types/uuid": "npm:^8.3.0"
fast-json-stable-stringify: "npm:^2.1.0"
uuid: "npm:^8.3.2"
peerDependencies:
"@metamask/network-controller": ^22.0.0
- checksum: 10/eac9ed2fcc9697a2aa55e9746d4eac8d762dd6948b00d77cd2d4894b8c3e1a8e6ed5d0df4d01a69d9a7e2b3c09d9d7c1ffc6f9504023388dd7452d45b5d87065
+ checksum: 10/15bb6c087b506e15474d4d3555a37a540389c47674ac14052f7877bbf2be29e3bf78f2ea34a10d1ed21696bc0f45525cb2916e020a1b87e74ad695cb98ae4a94
languageName: node
linkType: hard
@@ -6432,14 +6432,14 @@ __metadata:
languageName: node
linkType: hard
-"@metamask/user-operation-controller@npm:^19.0.0":
- version: 19.0.0
- resolution: "@metamask/user-operation-controller@npm:19.0.0"
+"@metamask/user-operation-controller@npm:^21.0.0":
+ version: 21.0.0
+ resolution: "@metamask/user-operation-controller@npm:21.0.0"
dependencies:
"@metamask/base-controller": "npm:^7.0.2"
- "@metamask/controller-utils": "npm:^11.4.3"
+ "@metamask/controller-utils": "npm:^11.4.4"
"@metamask/eth-query": "npm:^4.0.0"
- "@metamask/polling-controller": "npm:^12.0.1"
+ "@metamask/polling-controller": "npm:^12.0.2"
"@metamask/rpc-errors": "npm:^7.0.1"
"@metamask/superstruct": "npm:^3.1.0"
"@metamask/utils": "npm:^10.0.0"
@@ -6449,11 +6449,12 @@ __metadata:
uuid: "npm:^8.3.2"
peerDependencies:
"@metamask/approval-controller": ^7.0.0
+ "@metamask/eth-block-tracker": ">=9"
"@metamask/gas-fee-controller": ^22.0.0
"@metamask/keyring-controller": ^19.0.0
"@metamask/network-controller": ^22.0.0
- "@metamask/transaction-controller": ^40.0.0
- checksum: 10/ca3d8ee77243eb3bdc455420185d5c41d45cb5520735af0b05c1792d66fdb6a7404c557b053b16b6fded57124da21c3bb0b6b1c943d290e9808164f05453a8d9
+ "@metamask/transaction-controller": ^42.0.0
+ checksum: 10/c9d794ea386c57c3213714181444a0483bdff361de386716f4c38b0e99c411db91183d8bcc1f229b3bdcf9b1611219636c39b726e9f56e23d29b3fb7874c0e0a
languageName: node
linkType: hard
@@ -6596,13 +6597,20 @@ __metadata:
languageName: node
linkType: hard
-"@noble/hashes@npm:1.4.0, @noble/hashes@npm:^1.1.2, @noble/hashes@npm:^1.2.0, @noble/hashes@npm:^1.3.1, @noble/hashes@npm:^1.3.2, @noble/hashes@npm:^1.3.3, @noble/hashes@npm:^1.4.0, @noble/hashes@npm:~1.4.0":
+"@noble/hashes@npm:1.4.0, @noble/hashes@npm:~1.4.0":
version: 1.4.0
resolution: "@noble/hashes@npm:1.4.0"
checksum: 10/e156e65794c473794c52fa9d06baf1eb20903d0d96719530f523cc4450f6c721a957c544796e6efd0197b2296e7cd70efeb312f861465e17940a3e3c7e0febc6
languageName: node
linkType: hard
+"@noble/hashes@npm:^1.1.2, @noble/hashes@npm:^1.2.0, @noble/hashes@npm:^1.3.1, @noble/hashes@npm:^1.3.2, @noble/hashes@npm:^1.3.3, @noble/hashes@npm:^1.4.0":
+ version: 1.5.0
+ resolution: "@noble/hashes@npm:1.5.0"
+ checksum: 10/da7fc7af52af7afcf59810a7eea6155075464ff462ffda2572dc6d57d53e2669b1ea2ec774e814f6273f1697e567f28d36823776c9bf7068cba2a2855140f26e
+ languageName: node
+ linkType: hard
+
"@noble/hashes@npm:~1.1.1":
version: 1.1.3
resolution: "@noble/hashes@npm:1.1.3"
@@ -7750,9 +7758,9 @@ __metadata:
linkType: hard
"@scure/base@npm:^1.0.0, @scure/base@npm:^1.1.1, @scure/base@npm:^1.1.3, @scure/base@npm:~1.1.0, @scure/base@npm:~1.1.3, @scure/base@npm:~1.1.6":
- version: 1.1.7
- resolution: "@scure/base@npm:1.1.7"
- checksum: 10/fc50ffaab36cb46ff9fa4dc5052a06089ab6a6707f63d596bb34aaaec76173c9a564ac312a0b981b5e7a5349d60097b8878673c75d6cbfc4da7012b63a82099b
+ version: 1.1.9
+ resolution: "@scure/base@npm:1.1.9"
+ checksum: 10/f0ab7f687bbcdee2a01377fe3cd808bf63977999672751295b6a92625d5322f4754a96d40f6bd579bc367aad48ecf8a4e6d0390e70296e6ded1076f52adb16bb
languageName: node
linkType: hard
@@ -15820,14 +15828,11 @@ __metadata:
linkType: hard
"crc-32@npm:^1.2.0":
- version: 1.2.0
- resolution: "crc-32@npm:1.2.0"
- dependencies:
- exit-on-epipe: "npm:~1.0.1"
- printj: "npm:~1.1.0"
+ version: 1.2.2
+ resolution: "crc-32@npm:1.2.2"
bin:
- crc32: ./bin/crc32.njs
- checksum: 10/10c648c986b005ed0ea8393bb0d1ccb99e7a102505b136d313dee6abe204aa682d9bb9bc6fd180f9cd98ef92aa029964f1cc96a2a85eb50507dedd9ead1a262f
+ crc32: bin/crc32.njs
+ checksum: 10/824f696a5baaf617809aa9cd033313c8f94f12d15ebffa69f10202480396be44aef9831d900ab291638a8022ed91c360696dd5b1ba691eb3f34e60be8835b7c3
languageName: node
linkType: hard
@@ -19092,13 +19097,6 @@ __metadata:
languageName: node
linkType: hard
-"exit-on-epipe@npm:~1.0.1":
- version: 1.0.1
- resolution: "exit-on-epipe@npm:1.0.1"
- checksum: 10/b180aa277aec5bef2609b34e5876061f421a1f81bf343beb213c4d60b382ddcb6b83012833f0ba329d6bc38042685c8d89b1c52ea495b9b6327948ea80627398
- languageName: node
- linkType: hard
-
"exit@npm:^0.1.2":
version: 0.1.2
resolution: "exit@npm:0.1.2"
@@ -26621,7 +26619,7 @@ __metadata:
"@metamask/test-bundler": "npm:^1.0.0"
"@metamask/test-dapp": "npm:8.13.0"
"@metamask/transaction-controller": "npm:^42.0.0"
- "@metamask/user-operation-controller": "npm:^19.0.0"
+ "@metamask/user-operation-controller": "npm:^21.0.0"
"@metamask/utils": "npm:^10.0.1"
"@ngraveio/bc-ur": "npm:^1.1.12"
"@noble/hashes": "npm:^1.3.3"
@@ -30285,15 +30283,6 @@ __metadata:
languageName: node
linkType: hard
-"printj@npm:~1.1.0":
- version: 1.1.2
- resolution: "printj@npm:1.1.2"
- bin:
- printj: ./bin/printj.njs
- checksum: 10/45376a5ee7ef2e0d7ff0b4fecc893d73995a332e63d7e0622a544fe662c8213d22f0c9750e627c6d732a7d7a543266be960e6cd51cf19485cce87cf80468bb41
- languageName: node
- linkType: hard
-
"prismjs@npm:^1.27.0":
version: 1.29.0
resolution: "prismjs@npm:1.29.0"
From d06dad70d8ade01aa569280cfddd05da24bfaa54 Mon Sep 17 00:00:00 2001
From: Jony Bursztyn
Date: Thu, 19 Dec 2024 12:18:54 +0000
Subject: [PATCH 039/202] feat: add metametrics events to carousel (#29141)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
## **Description**
This PR introduces tracking metrics for banners in the carousel to
monitor their effectiveness. The following events are added:
- **Banner selected:** Triggered when a banner or a button within a
banner is clicked.
- **Close all banners:** Triggered when the last banner in the carousel
is closed.
- **Banner shown:** Triggered when a banner is displayed in the
carousel.
### Tracking Implementation Details:
#### Banner selected
When a banner or button in a banner is clicked:
```javascript
trackEvent({
event: MetaMetricsEventName.BannerSelect,
category: MetaMetricsEventCategory.Banner,
properties: {
banner_name: e.g. buy, bridge, sell, card
}
});
```
#### Close all banners
When the last banner in the carousel is closed:
```javascript
trackEvent({
event: MetaMetricsEventName.BannerCloseAll,
category: MetaMetricsEventCategory.Banner
});
```
#### Banner shown
When a banner is displayed in the carousel:
```javascript
trackEvent({
event: MetaMetricsEventName.BannerDisplay,
category: MetaMetricsEventCategory.Banner,
properties: {
banner_name: e.g. buy, bridge, sell, card
}
});
```
## **Related issues**
Fixes: https://github.com/MetaMask/MetaMask-planning/issues/3764
## **Manual testing steps**
1. Open the carousel.
2. Click on banners or buttons within banners to trigger the "Banner
selected" event.
3. Close the last banner to trigger the "Close all banners" event.
4. Navigate through the carousel to ensure the "Banner shown" event is
fired for each displayed banner.
## **Screenshots/Recordings**
### **Before**
### **After**
## **Pre-merge author checklist**
- [ ] I've followed [MetaMask Contributor
Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask
Extension Coding
Standards](https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/CODING_GUIDELINES.md).
- [ ] I've completed the PR template to the best of my ability
- [ ] I’ve included tests if applicable
- [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format
if applicable
- [ ] I’ve applied the right labels on the PR (see [labeling
guidelines](https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/LABELING_GUIDELINES.md)).
Not required for external contributors.
## **Pre-merge reviewer checklist**
- [ ] I've manually tested the PR (e.g. pull and build branch, run the
app, test code being changed).
- [ ] I confirm that this PR addresses all acceptance criteria described
in the ticket it closes and includes the necessary testing evidence such
as recordings and or screenshots.
---
shared/constants/metametrics.ts | 4 ++
.../account-overview-layout.tsx | 51 +++++++++++++++---
.../multichain/carousel/carousel.test.tsx | 53 +++++++++++++++++--
.../multichain/carousel/carousel.tsx | 16 +++++-
.../multichain/carousel/carousel.types.ts | 3 +-
5 files changed, 114 insertions(+), 13 deletions(-)
diff --git a/shared/constants/metametrics.ts b/shared/constants/metametrics.ts
index 9b99140b7d24..8b59e95e650c 100644
--- a/shared/constants/metametrics.ts
+++ b/shared/constants/metametrics.ts
@@ -645,6 +645,9 @@ export enum MetaMetricsEventName {
AppUnlockedFailed = 'App Unlocked Failed',
AppLocked = 'App Locked',
AppWindowExpanded = 'App Window Expanded',
+ BannerDisplay = 'Banner Display',
+ BannerCloseAll = 'Banner Close All',
+ BannerSelect = 'Banner Select',
BridgeLinkClicked = 'Bridge Link Clicked',
BitcoinSupportToggled = 'Bitcoin Support Toggled',
BitcoinTestnetSupportToggled = 'Bitcoin Testnet Support Toggled',
@@ -912,6 +915,7 @@ export enum MetaMetricsEventCategory {
App = 'App',
Auth = 'Auth',
Background = 'Background',
+ Banner = 'Banner',
// The TypeScript ESLint rule is incorrectly marking this line.
/* eslint-disable-next-line @typescript-eslint/no-shadow */
Error = 'Error',
diff --git a/ui/components/multichain/account-overview/account-overview-layout.tsx b/ui/components/multichain/account-overview/account-overview-layout.tsx
index 01396b77d2c1..69fc2297ffea 100644
--- a/ui/components/multichain/account-overview/account-overview-layout.tsx
+++ b/ui/components/multichain/account-overview/account-overview-layout.tsx
@@ -1,4 +1,4 @@
-import React, { useEffect } from 'react';
+import React, { useEffect, useContext, useState, useCallback } from 'react';
import { useDispatch, useSelector } from 'react-redux';
///: BEGIN:ONLY_INCLUDE_IF(build-main,build-beta,build-flask)
import { isEqual } from 'lodash';
@@ -16,6 +16,12 @@ import {
///: BEGIN:ONLY_INCLUDE_IF(build-main,build-beta,build-flask)
import useBridging from '../../../hooks/bridge/useBridging';
///: END:ONLY_INCLUDE_IF
+import { MetaMetricsContext } from '../../../contexts/metametrics';
+import {
+ MetaMetricsEventName,
+ MetaMetricsEventCategory,
+} from '../../../../shared/constants/metametrics';
+import type { CarouselSlide } from '../../../../shared/constants/app-state';
import {
AccountOverviewTabsProps,
AccountOverviewTabs,
@@ -42,6 +48,8 @@ export const AccountOverviewLayout = ({
const slides = useSelector(getSlides);
const totalBalance = useSelector(getSelectedAccountCachedBalance);
const isLoading = useSelector(getAppIsLoading);
+ const trackEvent = useContext(MetaMetricsContext);
+ const [hasRendered, setHasRendered] = useState(false);
///: BEGIN:ONLY_INCLUDE_IF(build-main,build-beta,build-flask)
const defaultSwapsToken = useSelector(getSwapsDefaultToken, isEqual);
@@ -76,8 +84,8 @@ export const AccountOverviewLayout = ({
dispatch(updateSlides(defaultSlides));
}, [hasZeroBalance]);
- ///: BEGIN:ONLY_INCLUDE_IF(build-main,build-beta,build-flask)
const handleCarouselClick = (id: string) => {
+ ///: BEGIN:ONLY_INCLUDE_IF(build-main,build-beta,build-flask)
if (id === 'bridge') {
openBridgeExperience(
'Carousel',
@@ -85,26 +93,57 @@ export const AccountOverviewLayout = ({
location.pathname.includes('asset') ? '&token=native' : '',
);
}
+ ///: END:ONLY_INCLUDE_IF
+
+ trackEvent({
+ event: MetaMetricsEventName.BannerSelect,
+ category: MetaMetricsEventCategory.Banner,
+ properties: {
+ banner_name: id,
+ },
+ });
};
- ///: END:ONLY_INCLUDE_IF
- const handleRemoveSlide = (id: string) => {
+ const handleRemoveSlide = (isLastSlide: boolean, id: string) => {
if (id === 'fund' && hasZeroBalance) {
return;
}
+ if (isLastSlide) {
+ trackEvent({
+ event: MetaMetricsEventName.BannerCloseAll,
+ category: MetaMetricsEventCategory.Banner,
+ });
+ }
dispatch(removeSlide(id));
};
+ const handleRenderSlides = useCallback(
+ (renderedSlides: CarouselSlide[]) => {
+ if (!hasRendered) {
+ renderedSlides.forEach((slide) => {
+ trackEvent({
+ event: MetaMetricsEventName.BannerDisplay,
+ category: MetaMetricsEventCategory.Banner,
+ properties: {
+ banner_name: slide.id,
+ },
+ });
+ });
+ setHasRendered(true);
+ }
+ },
+ [hasRendered, trackEvent],
+ );
+
return (
<>
diff --git a/ui/components/multichain/connected-site-menu/__snapshots__/connected-site-menu.test.js.snap b/ui/components/multichain/connected-site-menu/__snapshots__/connected-site-menu.test.js.snap
index fa6f6a0b202a..18b794d1e971 100644
--- a/ui/components/multichain/connected-site-menu/__snapshots__/connected-site-menu.test.js.snap
+++ b/ui/components/multichain/connected-site-menu/__snapshots__/connected-site-menu.test.js.snap
@@ -32,7 +32,7 @@ exports[`Connected Site Menu should render the site menu in connected state 1`]
style="bottom: -1px; right: -4px; z-index: 1;"
>
@@ -74,7 +74,7 @@ exports[`Connected Site Menu should render the site menu in not connected state
style="bottom: -1px; right: -4px; z-index: 1;"
>
diff --git a/ui/components/multichain/connected-site-menu/connected-site-menu.js b/ui/components/multichain/connected-site-menu/connected-site-menu.js
index 4f532710288e..cbceafa7d99f 100644
--- a/ui/components/multichain/connected-site-menu/connected-site-menu.js
+++ b/ui/components/multichain/connected-site-menu/connected-site-menu.js
@@ -88,9 +88,9 @@ export const ConnectedSiteMenu = ({
borderColor={
isConnectedtoOtherAccountOrSnap
? BorderColor.successDefault
- : BackgroundColor.backgroundDefault
+ : BorderColor.backgroundDefault
}
- borderWidth={isConnectedtoOtherAccountOrSnap ? 2 : 3}
+ borderWidth={2}
/>
}
>
diff --git a/ui/components/multichain/connected-status/connected-status.tsx b/ui/components/multichain/connected-status/connected-status.tsx
index 83dc0c77ec45..8de3d7c4b38b 100644
--- a/ui/components/multichain/connected-status/connected-status.tsx
+++ b/ui/components/multichain/connected-status/connected-status.tsx
@@ -3,7 +3,6 @@ import { useSelector } from 'react-redux';
import {
BackgroundColor,
BorderColor,
- Color,
} from '../../../helpers/constants/design-system';
import { isAccountConnectedToCurrentTab } from '../../../selectors';
import {
@@ -43,11 +42,11 @@ export const ConnectedStatus: React.FC = ({
status = STATUS_CONNECTED_TO_ANOTHER_ACCOUNT;
}
- let badgeBorderColor = BackgroundColor.backgroundDefault; // TODO: Replace it once border-color has this value.
- let badgeBackgroundColor = Color.borderMuted; // //TODO: Replace it once Background color has this value.
+ let badgeBorderColor = BorderColor.backgroundDefault; // TODO: Replace it once border-color has this value.
+ let badgeBackgroundColor = BackgroundColor.iconAlternative;
let tooltipText = t('statusNotConnected');
if (status === STATUS_CONNECTED) {
- badgeBorderColor = BackgroundColor.backgroundDefault;
+ badgeBorderColor = BorderColor.backgroundDefault;
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: type 'string' can't be used to index type '{}'
badgeBackgroundColor = BackgroundColor.successDefault;
diff --git a/ui/components/multichain/pages/connections/__snapshots__/connections.test.tsx.snap b/ui/components/multichain/pages/connections/__snapshots__/connections.test.tsx.snap
index ad2dc490d7c0..9250404743fc 100644
--- a/ui/components/multichain/pages/connections/__snapshots__/connections.test.tsx.snap
+++ b/ui/components/multichain/pages/connections/__snapshots__/connections.test.tsx.snap
@@ -171,7 +171,7 @@ exports[`Connections Content should render correctly 1`] = `
style="bottom: -1px; right: 2px;"
>
diff --git a/ui/components/multichain/pages/send/__snapshots__/send.test.js.snap b/ui/components/multichain/pages/send/__snapshots__/send.test.js.snap
index 7b0605b7ea60..a23161205c8e 100644
--- a/ui/components/multichain/pages/send/__snapshots__/send.test.js.snap
+++ b/ui/components/multichain/pages/send/__snapshots__/send.test.js.snap
@@ -348,7 +348,7 @@ exports[`SendPage render and initialization should render correctly even when a
style="bottom: -1px; right: 2px;"
>
`;
-exports[`TransactionStatusLabel Component should render PENDING properly 1`] = `
+exports[`TransactionStatusLabel Component renders PENDING properly and tooltip 1`] = `
`;
-exports[`TransactionStatusLabel Component should render QUEUED properly 1`] = `
+exports[`TransactionStatusLabel Component renders QUEUED properly and tooltip 1`] = `
`;
-exports[`TransactionStatusLabel Component should render SIGNING if status is approved 1`] = `
+exports[`TransactionStatusLabel Component renders SIGNING properly and tooltip 1`] = `
`;
-exports[`TransactionStatusLabel Component should render UNAPPROVED properly 1`] = `
+exports[`TransactionStatusLabel Component renders UNAPPROVED properly and tooltip 1`] = `
{statusText}
+ ) : (
+
+ {statusText}
+
);
}
@@ -120,8 +131,13 @@ TransactionStatusLabel.propTypes = {
error: PropTypes.object,
isEarliestNonce: PropTypes.bool,
statusOnly: PropTypes.bool,
+ shouldShowTooltip: PropTypes.bool,
///: BEGIN:ONLY_INCLUDE_IF(build-mmi)
custodyStatus: PropTypes.string,
custodyStatusDisplayText: PropTypes.string,
///: END:ONLY_INCLUDE_IF
};
+
+TransactionStatusLabel.defaultProps = {
+ shouldShowTooltip: true,
+};
diff --git a/ui/components/app/transaction-status-label/transaction-status-label.test.js b/ui/components/app/transaction-status-label/transaction-status-label.test.js
index 4fa8e832201f..663cb45e802e 100644
--- a/ui/components/app/transaction-status-label/transaction-status-label.test.js
+++ b/ui/components/app/transaction-status-label/transaction-status-label.test.js
@@ -7,103 +7,160 @@ import { renderWithProvider } from '../../../../test/lib/render-helpers';
import { ETH_EOA_METHODS } from '../../../../shared/constants/eth-methods';
import TransactionStatusLabel from '.';
-describe('TransactionStatusLabel Component', () => {
- const createMockStore = configureMockStore([thunk]);
- const mockState = {
- metamask: {
- custodyStatusMaps: {},
- internalAccounts: {
- accounts: {
- 'cf8dace4-9439-4bd4-b3a8-88c821c8fcb3': {
- address: '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc',
- id: 'cf8dace4-9439-4bd4-b3a8-88c821c8fcb3',
- metadata: {
- name: 'Test Account',
- keyring: {
- type: 'HD Key Tree',
- },
- },
- options: {},
- methods: ETH_EOA_METHODS,
- type: EthAccountType.Eoa,
- },
- },
- selectedAccount: 'cf8dace4-9439-4bd4-b3a8-88c821c8fcb3',
- },
+const TEST_ACCOUNT_ID = 'cf8dace4-9439-4bd4-b3a8-88c821c8fcb3';
+const TEST_ACCOUNT_ADDRESS = '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc';
+
+const createBasicAccount = (type = 'HD Key Tree') => ({
+ address: TEST_ACCOUNT_ADDRESS,
+ id: TEST_ACCOUNT_ID,
+ metadata: {
+ name: 'Test Account',
+ keyring: {
+ type,
},
- };
+ },
+ options: {},
+ methods: ETH_EOA_METHODS,
+ type: EthAccountType.Eoa,
+});
- let store = createMockStore(mockState);
- it('should render CONFIRMED properly', () => {
- const confirmedProps = {
- status: 'confirmed',
- date: 'June 1',
- };
+const createCustodyAccount = () => ({
+ ...createBasicAccount('Custody - JSONRPC'),
+ metadata: {
+ name: 'Account 1',
+ keyring: {
+ type: 'Custody - JSONRPC',
+ },
+ },
+});
- const { container } = renderWithProvider(
- ,
- store,
- );
+const createMockStateWithAccount = (account) => ({
+ metamask: {
+ custodyStatusMaps: {},
+ internalAccounts: {
+ accounts: {
+ [TEST_ACCOUNT_ID]: account,
+ },
+ selectedAccount: TEST_ACCOUNT_ID,
+ },
+ },
+});
- expect(container).toMatchSnapshot();
- });
+const createCustodyMockState = (account) => ({
+ metamask: {
+ custodyStatusMaps: {
+ saturn: {
+ approved: {
+ shortText: 'Short Text Test',
+ longText: 'Long Text Test',
+ },
+ },
+ },
+ internalAccounts: {
+ accounts: {
+ [TEST_ACCOUNT_ID]: account,
+ },
+ selectedAccount: TEST_ACCOUNT_ID,
+ },
+ keyrings: [
+ {
+ type: 'Custody - JSONRPC',
+ accounts: [TEST_ACCOUNT_ADDRESS],
+ },
+ ],
+ },
+});
- it('should render PENDING properly', () => {
- const props = {
+const statusTestCases = [
+ {
+ name: 'CONFIRMED',
+ props: { status: 'confirmed', date: 'June 1' },
+ },
+ {
+ name: 'PENDING',
+ props: {
date: 'June 1',
status: TransactionStatus.submitted,
isEarliestNonce: true,
- };
-
- const { container } = renderWithProvider(
- ,
- store,
- );
-
- expect(container).toMatchSnapshot();
- });
-
- it('should render QUEUED properly', () => {
- const props = {
+ },
+ },
+ {
+ name: 'QUEUED',
+ props: {
status: TransactionStatus.submitted,
isEarliestNonce: false,
- };
-
- const { container } = renderWithProvider(
- ,
- store,
- );
-
- expect(container).toMatchSnapshot();
- });
-
- it('should render UNAPPROVED properly', () => {
- const props = {
+ },
+ },
+ {
+ name: 'UNAPPROVED',
+ props: {
status: TransactionStatus.unapproved,
- };
+ },
+ },
+ {
+ name: 'SIGNING',
+ props: {
+ status: TransactionStatus.approved,
+ },
+ },
+];
- const { container } = renderWithProvider(
- ,
- store,
- );
+const errorTestCases = [
+ {
+ name: 'error message',
+ props: {
+ status: 'approved',
+ custodyStatus: 'approved',
+ error: { message: 'An error occurred' },
+ },
+ expectedText: 'Error',
+ },
+ {
+ name: 'error with aborted custody status',
+ props: {
+ status: 'approved',
+ custodyStatus: 'aborted',
+ error: { message: 'An error occurred' },
+ custodyStatusDisplayText: 'Test',
+ shouldShowTooltip: true,
+ },
+ expectedText: 'Test',
+ },
+];
+
+describe('TransactionStatusLabel Component', () => {
+ const createMockStore = configureMockStore([thunk]);
+ let store;
- expect(container).toMatchSnapshot();
+ beforeEach(() => {
+ const basicAccount = createBasicAccount();
+ const mockState = createMockStateWithAccount(basicAccount);
+ store = createMockStore(mockState);
});
- it('should render SIGNING if status is approved', () => {
- const props = {
- status: TransactionStatus.approved,
- };
+ statusTestCases.forEach(({ name, props }) => {
+ it(`renders ${name} properly and tooltip`, () => {
+ const { container, queryByTestId } = renderWithProvider(
+ ,
+ store,
+ );
+ expect(container).toMatchSnapshot();
+ expect(queryByTestId('transaction-status-label')).not.toBeInTheDocument();
+ });
+ });
- const { container } = renderWithProvider(
- ,
+ it('renders pure text for status when shouldShowTooltip is specified as false', () => {
+ const { queryByTestId } = renderWithProvider(
+ ,
store,
);
-
- expect(container).toMatchSnapshot();
+ expect(queryByTestId('transaction-status-label')).toBeInTheDocument();
});
- it('should render statusText properly when is custodyStatusDisplayText is defined', () => {
+ it('renders statusText properly when is custodyStatusDisplayText is defined', () => {
const props = {
custodyStatusDisplayText: 'test',
};
@@ -116,51 +173,15 @@ describe('TransactionStatusLabel Component', () => {
expect(getByText(props.custodyStatusDisplayText)).toBeVisible();
});
- it('should display the correct status text and tooltip', () => {
- const mockShortText = 'Short Text Test';
- const mockLongText = 'Long Text Test';
+ it('displays correct text and tooltip', () => {
const props = {
status: 'approved',
custodyStatus: 'approved',
custodyStatusDisplayText: 'Test',
};
- const customMockStore = {
- metamask: {
- custodyStatusMaps: {
- saturn: {
- approved: {
- shortText: mockShortText,
- longText: mockLongText,
- },
- },
- },
- internalAccounts: {
- accounts: {
- 'cf8dace4-9439-4bd4-b3a8-88c821c8fcb3': {
- address: '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc',
- id: 'cf8dace4-9439-4bd4-b3a8-88c821c8fcb3',
- metadata: {
- name: 'Account 1',
- keyring: {
- type: 'Custody - JSONRPC',
- },
- },
- options: {},
- methods: ETH_EOA_METHODS,
- type: EthAccountType.Eoa,
- },
- },
- selectedAccount: 'cf8dace4-9439-4bd4-b3a8-88c821c8fcb3',
- },
- keyrings: [
- {
- type: 'Custody - JSONRPC',
- accounts: ['0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc'],
- },
- ],
- },
- };
+ const custodyAccount = createCustodyAccount();
+ const customMockStore = createCustodyMockState(custodyAccount);
store = createMockStore(customMockStore);
const { getByText } = renderWithProvider(
@@ -170,114 +191,19 @@ describe('TransactionStatusLabel Component', () => {
expect(getByText(props.custodyStatusDisplayText)).toBeVisible();
});
- it('should display the error message when there is an error', () => {
- const mockShortText = 'Short Text Test';
- const mockLongText = 'Long Text Test';
- const props = {
- status: 'approved',
- custodyStatus: 'approved',
- error: { message: 'An error occurred' },
- };
- const customMockStore = {
- metamask: {
- custodyStatusMaps: {
- saturn: {
- approved: {
- shortText: mockShortText,
- longText: mockLongText,
- },
- },
- },
- internalAccounts: {
- accounts: {
- 'cf8dace4-9439-4bd4-b3a8-88c821c8fcb3': {
- address: '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc',
- id: 'cf8dace4-9439-4bd4-b3a8-88c821c8fcb3',
- metadata: {
- name: 'Account 1',
- keyring: {
- type: 'Custody - JSONRPC',
- },
- },
- options: {},
- methods: ETH_EOA_METHODS,
- type: EthAccountType.Eoa,
- },
- },
- selectedAccount: 'cf8dace4-9439-4bd4-b3a8-88c821c8fcb3',
- },
- keyrings: [
- {
- type: 'Custody - JSONRPC',
- accounts: ['0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc'],
- },
- ],
- },
- };
- store = createMockStore(customMockStore);
-
- const { getByText } = renderWithProvider(
- ,
- store,
- );
-
- expect(getByText('Error')).toBeVisible();
- });
-
- it('should display correctly the error message when there is an error and custodyStatus is aborted', () => {
- const mockShortText = 'Short Text Test';
- const mockLongText = 'Long Text Test';
- const props = {
- status: 'approved',
- custodyStatus: 'aborted',
- error: { message: 'An error occurred' },
- custodyStatusDisplayText: 'Test',
- };
- const customMockStore = {
- metamask: {
- custodyStatusMaps: {
- saturn: {
- approved: {
- shortText: mockShortText,
- longText: mockLongText,
- },
- },
- },
- internalAccounts: {
- accounts: {
- 'cf8dace4-9439-4bd4-b3a8-88c821c8fcb3': {
- address: '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc',
- id: 'cf8dace4-9439-4bd4-b3a8-88c821c8fcb3',
- metadata: {
- name: 'Account 1',
- keyring: {
- type: 'Custody - JSONRPC',
- },
- },
- options: {},
- methods: ETH_EOA_METHODS,
- type: EthAccountType.Eoa,
- },
- },
- selectedAccount: 'cf8dace4-9439-4bd4-b3a8-88c821c8fcb3',
- },
- keyrings: [
- {
- type: 'Custody - JSONRPC',
- accounts: ['0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc'],
- },
- ],
- },
- };
+ errorTestCases.forEach(({ name, props, expectedText }) => {
+ it(`displays correctly the ${name}`, () => {
+ const custodyAccount = createCustodyAccount();
+ const customMockStore = createCustodyMockState(custodyAccount);
+ store = createMockStore(customMockStore);
- store = createMockStore(customMockStore);
+ const { getByText } = renderWithProvider(
+ ,
+ store,
+ );
- const { getByText } = renderWithProvider(
- ,
- store,
- );
-
- expect(getByText(props.custodyStatusDisplayText)).toBeVisible();
+ expect(getByText(expectedText)).toBeVisible();
+ });
});
});
From 695d0db025fff9d9b29d6ca2c03c42bfd58cf57e Mon Sep 17 00:00:00 2001
From: AugmentedMode <31675118+AugmentedMode@users.noreply.github.com>
Date: Fri, 20 Dec 2024 12:15:55 -0500
Subject: [PATCH 071/202] fix: Add main frame URL property to req object
whenever req is triggered from an iframe (#29337)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
## **Description**
See the attached issue in metamask planning for more details.
[![Open in GitHub
Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29337?quickstart=1)
## **Related issues**
Fixes:
## **Manual testing steps**
1. Go to `https://develop.d3bkcslj57l47p.amplifyapp.com/`
2. Click on Proceed anyways (This phishing warning page here is
expected)
3. Open the network tab to monitor network requests
4. Connect your wallet and click on a signature or transaction
5. Verify that mainFrameOrigin is included in the payload of the network
request to the security alerts API
## **Screenshots/Recordings**
Below are screenshots demonstrating the behavior of a test HTML page I
created:
1. In the first screenshot, before the iframe is loaded, the console
shows only the origin of the main frame.
2. In the second screenshot, after clicking the button to load an iframe
pointing to example.com, the solution correctly identifies both the
mainFrameOrigin (main frame) and the origin (iframe).
### **Before**
### **After**
## **Pre-merge author checklist**
- [ ] I've followed [MetaMask Contributor
Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask
Extension Coding
Standards](https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/CODING_GUIDELINES.md).
- [ ] I've completed the PR template to the best of my ability
- [ ] I’ve included tests if applicable
- [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format
if applicable
- [ ] I’ve applied the right labels on the PR (see [labeling
guidelines](https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/LABELING_GUIDELINES.md)).
Not required for external contributors.
## **Pre-merge reviewer checklist**
- [ ] I've manually tested the PR (e.g. pull and build branch, run the
app, test code being changed).
- [ ] I confirm that this PR addresses all acceptance criteria described
in the ticket it closes and includes the necessary testing evidence such
as recordings and or screenshots.
---
.../lib/createMainFrameOriginMiddleware.ts | 24 +++++++++++++++++++
app/scripts/metamask-controller.js | 22 ++++++++++++++++-
2 files changed, 45 insertions(+), 1 deletion(-)
create mode 100644 app/scripts/lib/createMainFrameOriginMiddleware.ts
diff --git a/app/scripts/lib/createMainFrameOriginMiddleware.ts b/app/scripts/lib/createMainFrameOriginMiddleware.ts
new file mode 100644
index 000000000000..bcbc2cb7d6fd
--- /dev/null
+++ b/app/scripts/lib/createMainFrameOriginMiddleware.ts
@@ -0,0 +1,24 @@
+// Request and responses are currently untyped.
+/* eslint-disable @typescript-eslint/no-explicit-any */
+
+/**
+ * Returns a middleware that appends the mainFrameOrigin to request
+ *
+ * @param {{ mainFrameOrigin: string }} opts - The middleware options
+ * @returns {Function}
+ */
+
+export default function createMainFrameOriginMiddleware({
+ mainFrameOrigin,
+}: {
+ mainFrameOrigin: string;
+}) {
+ return function mainFrameOriginMiddleware(
+ req: any,
+ _res: any,
+ next: () => void,
+ ) {
+ req.mainFrameOrigin = mainFrameOrigin;
+ next();
+ };
+}
diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js
index 933522c449f6..62fe3c942589 100644
--- a/app/scripts/metamask-controller.js
+++ b/app/scripts/metamask-controller.js
@@ -305,6 +305,7 @@ import {
createUnsupportedMethodMiddleware,
} from './lib/rpc-method-middleware';
import createOriginMiddleware from './lib/createOriginMiddleware';
+import createMainFrameOriginMiddleware from './lib/createMainFrameOriginMiddleware';
import createTabIdMiddleware from './lib/createTabIdMiddleware';
import { NetworkOrderController } from './controllers/network-order';
import { AccountOrderController } from './controllers/account-order';
@@ -5804,11 +5805,18 @@ export default class MetamaskController extends EventEmitter {
tabId = sender.tab.id;
}
+ let mainFrameOrigin = origin;
+ if (sender.tab && sender.tab.url) {
+ // If sender origin is an iframe, then get the top-level frame's origin
+ mainFrameOrigin = new URL(sender.tab.url).origin;
+ }
+
const engine = this.setupProviderEngineEip1193({
origin,
sender,
subjectType,
tabId,
+ mainFrameOrigin,
});
const dupeReqFilterStream = createDupeReqFilterStream();
@@ -5929,13 +5937,25 @@ export default class MetamaskController extends EventEmitter {
* @param {MessageSender | SnapSender} options.sender - The sender object.
* @param {string} options.subjectType - The type of the sender subject.
* @param {tabId} [options.tabId] - The tab ID of the sender - if the sender is within a tab
+ * @param {mainFrameOrigin} [options.mainFrameOrigin] - The origin of the main frame if the sender is an iframe
*/
- setupProviderEngineEip1193({ origin, subjectType, sender, tabId }) {
+ setupProviderEngineEip1193({
+ origin,
+ subjectType,
+ sender,
+ tabId,
+ mainFrameOrigin,
+ }) {
const engine = new JsonRpcEngine();
// Append origin to each request
engine.push(createOriginMiddleware({ origin }));
+ // Append mainFrameOrigin to each request if present
+ if (mainFrameOrigin) {
+ engine.push(createMainFrameOriginMiddleware({ mainFrameOrigin }));
+ }
+
// Append selectedNetworkClientId to each request
engine.push(createSelectedNetworkMiddleware(this.controllerMessenger));
From 547b264a3993aa4d40caad5d2993df2a1c7ca32e Mon Sep 17 00:00:00 2001
From: Pedro Figueiredo
Date: Fri, 20 Dec 2024 17:46:37 +0000
Subject: [PATCH 072/202] chore: Update to the latest transaction controller
(#29395)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
## **Description**
Updates from v42 to v42.1 in order to get the validation of the gas
limit hexadecimal string properties. See
https://github.com/MetaMask/core/pull/5093 for more details.
[![Open in GitHub
Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29395?quickstart=1)
## **Related issues**
Fixes: https://github.com/MetaMask/MetaMask-planning/issues/3826
## **Manual testing steps**
1. Go to this page...
2.
3.
## **Screenshots/Recordings**
### **Before**
### **After**
## **Pre-merge author checklist**
- [ ] I've followed [MetaMask Contributor
Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask
Extension Coding
Standards](https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/CODING_GUIDELINES.md).
- [ ] I've completed the PR template to the best of my ability
- [ ] I’ve included tests if applicable
- [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format
if applicable
- [ ] I’ve applied the right labels on the PR (see [labeling
guidelines](https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/LABELING_GUIDELINES.md)).
Not required for external contributors.
## **Pre-merge reviewer checklist**
- [ ] I've manually tested the PR (e.g. pull and build branch, run the
app, test code being changed).
- [ ] I confirm that this PR addresses all acceptance criteria described
in the ticket it closes and includes the necessary testing evidence such
as recordings and or screenshots.
---------
Co-authored-by: MetaMask Bot
---
lavamoat/browserify/beta/policy.json | 17 ++++++++++++++++-
lavamoat/browserify/flask/policy.json | 17 ++++++++++++++++-
lavamoat/browserify/main/policy.json | 17 ++++++++++++++++-
lavamoat/browserify/mmi/policy.json | 17 ++++++++++++++++-
package.json | 2 +-
yarn.lock | 24 ++++++++++++------------
6 files changed, 77 insertions(+), 17 deletions(-)
diff --git a/lavamoat/browserify/beta/policy.json b/lavamoat/browserify/beta/policy.json
index ee84b5c3e0e8..e55d57f5ec0f 100644
--- a/lavamoat/browserify/beta/policy.json
+++ b/lavamoat/browserify/beta/policy.json
@@ -1835,7 +1835,7 @@
"@metamask/network-controller": true,
"@metamask/transaction-controller>@metamask/nonce-tracker": true,
"@metamask/rpc-errors": true,
- "@metamask/utils": true,
+ "@metamask/transaction-controller>@metamask/utils": true,
"@metamask/name-controller>async-mutex": true,
"bn.js": true,
"browserify>buffer": true,
@@ -2256,6 +2256,21 @@
"semver": true
}
},
+ "@metamask/transaction-controller>@metamask/utils": {
+ "globals": {
+ "TextDecoder": true,
+ "TextEncoder": true
+ },
+ "packages": {
+ "@metamask/utils>@metamask/superstruct": true,
+ "@noble/hashes": true,
+ "@metamask/utils>@scure/base": true,
+ "browserify>buffer": true,
+ "nock>debug": true,
+ "@metamask/utils>pony-cause": true,
+ "semver": true
+ }
+ },
"@ngraveio/bc-ur": {
"packages": {
"@ngraveio/bc-ur>@keystonehq/alias-sampling": true,
diff --git a/lavamoat/browserify/flask/policy.json b/lavamoat/browserify/flask/policy.json
index ee84b5c3e0e8..e55d57f5ec0f 100644
--- a/lavamoat/browserify/flask/policy.json
+++ b/lavamoat/browserify/flask/policy.json
@@ -1835,7 +1835,7 @@
"@metamask/network-controller": true,
"@metamask/transaction-controller>@metamask/nonce-tracker": true,
"@metamask/rpc-errors": true,
- "@metamask/utils": true,
+ "@metamask/transaction-controller>@metamask/utils": true,
"@metamask/name-controller>async-mutex": true,
"bn.js": true,
"browserify>buffer": true,
@@ -2256,6 +2256,21 @@
"semver": true
}
},
+ "@metamask/transaction-controller>@metamask/utils": {
+ "globals": {
+ "TextDecoder": true,
+ "TextEncoder": true
+ },
+ "packages": {
+ "@metamask/utils>@metamask/superstruct": true,
+ "@noble/hashes": true,
+ "@metamask/utils>@scure/base": true,
+ "browserify>buffer": true,
+ "nock>debug": true,
+ "@metamask/utils>pony-cause": true,
+ "semver": true
+ }
+ },
"@ngraveio/bc-ur": {
"packages": {
"@ngraveio/bc-ur>@keystonehq/alias-sampling": true,
diff --git a/lavamoat/browserify/main/policy.json b/lavamoat/browserify/main/policy.json
index ee84b5c3e0e8..e55d57f5ec0f 100644
--- a/lavamoat/browserify/main/policy.json
+++ b/lavamoat/browserify/main/policy.json
@@ -1835,7 +1835,7 @@
"@metamask/network-controller": true,
"@metamask/transaction-controller>@metamask/nonce-tracker": true,
"@metamask/rpc-errors": true,
- "@metamask/utils": true,
+ "@metamask/transaction-controller>@metamask/utils": true,
"@metamask/name-controller>async-mutex": true,
"bn.js": true,
"browserify>buffer": true,
@@ -2256,6 +2256,21 @@
"semver": true
}
},
+ "@metamask/transaction-controller>@metamask/utils": {
+ "globals": {
+ "TextDecoder": true,
+ "TextEncoder": true
+ },
+ "packages": {
+ "@metamask/utils>@metamask/superstruct": true,
+ "@noble/hashes": true,
+ "@metamask/utils>@scure/base": true,
+ "browserify>buffer": true,
+ "nock>debug": true,
+ "@metamask/utils>pony-cause": true,
+ "semver": true
+ }
+ },
"@ngraveio/bc-ur": {
"packages": {
"@ngraveio/bc-ur>@keystonehq/alias-sampling": true,
diff --git a/lavamoat/browserify/mmi/policy.json b/lavamoat/browserify/mmi/policy.json
index 831feb96e1c5..5658498ad3a7 100644
--- a/lavamoat/browserify/mmi/policy.json
+++ b/lavamoat/browserify/mmi/policy.json
@@ -1927,7 +1927,7 @@
"@metamask/network-controller": true,
"@metamask/transaction-controller>@metamask/nonce-tracker": true,
"@metamask/rpc-errors": true,
- "@metamask/utils": true,
+ "@metamask/transaction-controller>@metamask/utils": true,
"@metamask/name-controller>async-mutex": true,
"bn.js": true,
"browserify>buffer": true,
@@ -2348,6 +2348,21 @@
"semver": true
}
},
+ "@metamask/transaction-controller>@metamask/utils": {
+ "globals": {
+ "TextDecoder": true,
+ "TextEncoder": true
+ },
+ "packages": {
+ "@metamask/utils>@metamask/superstruct": true,
+ "@noble/hashes": true,
+ "@metamask/utils>@scure/base": true,
+ "browserify>buffer": true,
+ "nock>debug": true,
+ "@metamask/utils>pony-cause": true,
+ "semver": true
+ }
+ },
"@ngraveio/bc-ur": {
"packages": {
"@ngraveio/bc-ur>@keystonehq/alias-sampling": true,
diff --git a/package.json b/package.json
index 4a7632d2d2d3..da93c6c75761 100644
--- a/package.json
+++ b/package.json
@@ -349,7 +349,7 @@
"@metamask/snaps-sdk": "^6.14.0",
"@metamask/snaps-utils": "^8.7.0",
"@metamask/solana-wallet-snap": "^1.0.4",
- "@metamask/transaction-controller": "^42.0.0",
+ "@metamask/transaction-controller": "^42.1.0",
"@metamask/user-operation-controller": "^21.0.0",
"@metamask/utils": "^10.0.1",
"@ngraveio/bc-ur": "^1.1.12",
diff --git a/yarn.lock b/yarn.lock
index e00947e58386..f45eb5cf233e 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -5095,13 +5095,13 @@ __metadata:
languageName: node
linkType: hard
-"@metamask/base-controller@npm:^7.0.0, @metamask/base-controller@npm:^7.0.1, @metamask/base-controller@npm:^7.0.2":
- version: 7.0.2
- resolution: "@metamask/base-controller@npm:7.0.2"
+"@metamask/base-controller@npm:^7.0.0, @metamask/base-controller@npm:^7.0.1, @metamask/base-controller@npm:^7.0.2, @metamask/base-controller@npm:^7.1.0":
+ version: 7.1.0
+ resolution: "@metamask/base-controller@npm:7.1.0"
dependencies:
"@metamask/utils": "npm:^10.0.0"
immer: "npm:^9.0.6"
- checksum: 10/6f78ec5af840c9947aa8eac6e402df6469600260d613a92196daefd5b072097a176fe5da1c386f2d36853513254b74140d667d817a12880c46f088e18ff3606a
+ checksum: 10/5a0b50c1e096cbf6483e308eddb3ca2e5e1865b803b5dba778bf635ec59657290895e21ada71c7508d8e34ff9695a192a414fd75e287d290346359ef8e23960a
languageName: node
linkType: hard
@@ -6446,9 +6446,9 @@ __metadata:
languageName: node
linkType: hard
-"@metamask/transaction-controller@npm:^42.0.0":
- version: 42.0.0
- resolution: "@metamask/transaction-controller@npm:42.0.0"
+"@metamask/transaction-controller@npm:^42.1.0":
+ version: 42.1.0
+ resolution: "@metamask/transaction-controller@npm:42.1.0"
dependencies:
"@ethereumjs/common": "npm:^3.2.0"
"@ethereumjs/tx": "npm:^4.2.0"
@@ -6456,13 +6456,13 @@ __metadata:
"@ethersproject/abi": "npm:^5.7.0"
"@ethersproject/contracts": "npm:^5.7.0"
"@ethersproject/providers": "npm:^5.7.0"
- "@metamask/base-controller": "npm:^7.0.2"
+ "@metamask/base-controller": "npm:^7.1.0"
"@metamask/controller-utils": "npm:^11.4.4"
"@metamask/eth-query": "npm:^4.0.0"
"@metamask/metamask-eth-abis": "npm:^3.1.1"
"@metamask/nonce-tracker": "npm:^6.0.0"
- "@metamask/rpc-errors": "npm:^7.0.1"
- "@metamask/utils": "npm:^10.0.0"
+ "@metamask/rpc-errors": "npm:^7.0.2"
+ "@metamask/utils": "npm:^11.0.1"
async-mutex: "npm:^0.5.0"
bn.js: "npm:^5.2.1"
eth-method-registry: "npm:^4.0.0"
@@ -6476,7 +6476,7 @@ __metadata:
"@metamask/eth-block-tracker": ">=9"
"@metamask/gas-fee-controller": ^22.0.0
"@metamask/network-controller": ^22.0.0
- checksum: 10/73c510803a720b4c1da0b82f1279a404a9b11c4ab76f8e5e4378c65d5d08bbb32c52062abfe319476cc3f5e2623a8987775c4524e55aa94002af73d73721b869
+ checksum: 10/9f842e2b68e84cbffdda301a0e15faab08226fd8e22eb954690ed41df60fe92c24acffdd9186b4c9f1da911a368cbe22cdb9ee046fc02d079c53f76100c66755
languageName: node
linkType: hard
@@ -26683,7 +26683,7 @@ __metadata:
"@metamask/solana-wallet-snap": "npm:^1.0.4"
"@metamask/test-bundler": "npm:^1.0.0"
"@metamask/test-dapp": "npm:8.13.0"
- "@metamask/transaction-controller": "npm:^42.0.0"
+ "@metamask/transaction-controller": "npm:^42.1.0"
"@metamask/user-operation-controller": "npm:^21.0.0"
"@metamask/utils": "npm:^10.0.1"
"@ngraveio/bc-ur": "npm:^1.1.12"
From a02799d97397fdfda492341566851203ae04027d Mon Sep 17 00:00:00 2001
From: Mark Stacey
Date: Fri, 20 Dec 2024 14:49:06 -0330
Subject: [PATCH 073/202] ci: Migrate dependency linting (#29370)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
## **Description**
Migrate dependency/lockfile linting steps from CircleCI to GitHub
actions.
[![Open in GitHub
Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29370?quickstart=1)
## **Related issues**
Relates to #28572
These changes were extracted from #29256
## **Manual testing steps**
Review logs to ensure the same commands are run. Introduce errors on a
branch from here to ensure the problems are caught.
https://github.com/MetaMask/metamask-extension/pull/29391
## **Screenshots/Recordings**
N/A
## **Pre-merge author checklist**
- [x] I've followed [MetaMask Contributor
Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask
Extension Coding
Standards](https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/CODING_GUIDELINES.md).
- [x] I've completed the PR template to the best of my ability
- [x] I’ve included tests if applicable
- [x] I’ve documented my code using [JSDoc](https://jsdoc.app/) format
if applicable
- [x] I’ve applied the right labels on the PR (see [labeling
guidelines](https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/LABELING_GUIDELINES.md)).
Not required for external contributors.
## **Pre-merge reviewer checklist**
- [ ] I've manually tested the PR (e.g. pull and build branch, run the
app, test code being changed).
- [ ] I confirm that this PR addresses all acceptance criteria described
in the ticket it closes and includes the necessary testing evidence such
as recordings and or screenshots.
---
.circleci/config.yml | 43 ------------------------
.github/workflows/main.yml | 14 ++++++++
.github/workflows/test-deps-audit.yml | 18 ++++++++++
.github/workflows/test-deps-depcheck.yml | 18 ++++++++++
.github/workflows/test-yarn-dedupe.yml | 18 ++++++++++
5 files changed, 68 insertions(+), 43 deletions(-)
create mode 100644 .github/workflows/test-deps-audit.yml
create mode 100644 .github/workflows/test-deps-depcheck.yml
create mode 100644 .github/workflows/test-yarn-dedupe.yml
diff --git a/.circleci/config.yml b/.circleci/config.yml
index 552aa3305509..5598e6450cfe 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -127,15 +127,6 @@ workflows:
- master
requires:
- prep-deps
- - test-deps-audit:
- requires:
- - prep-deps
- - test-deps-depcheck:
- requires:
- - prep-deps
- - test-yarn-dedupe:
- requires:
- - prep-deps
- validate-lavamoat-allow-scripts:
requires:
- prep-deps
@@ -291,7 +282,6 @@ workflows:
- prep-build-flask-mv2
- all-tests-pass:
requires:
- - test-deps-depcheck
- validate-lavamoat-allow-scripts
- validate-lavamoat-policy-build
- validate-lavamoat-policy-webapp
@@ -964,17 +954,6 @@ jobs:
name: Rerun workflows from failed
command: yarn ci-rerun-from-failed
- test-yarn-dedupe:
- executor: node-browsers-small
- steps:
- - run: *shallow-git-clone-and-enable-vnc
- - run: sudo corepack enable
- - attach_workspace:
- at: .
- - run:
- name: Detect yarn lock deduplications
- command: yarn dedupe --check
-
test-lint:
executor: node-browsers-medium
steps:
@@ -1053,28 +1032,6 @@ jobs:
name: Validate release candidate changelog
command: .circleci/scripts/validate-changelog-in-rc.sh
- test-deps-audit:
- executor: node-browsers-small
- steps:
- - run: *shallow-git-clone-and-enable-vnc
- - run: sudo corepack enable
- - attach_workspace:
- at: .
- - run:
- name: yarn audit
- command: yarn audit
-
- test-deps-depcheck:
- executor: node-browsers-small
- steps:
- - run: *shallow-git-clone-and-enable-vnc
- - run: sudo corepack enable
- - attach_workspace:
- at: .
- - run:
- name: depcheck
- command: yarn depcheck
-
test-e2e-chrome-webpack:
executor: node-browsers-medium-plus
parallelism: 20
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index c7907455701d..d8850502e7da 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -28,6 +28,18 @@ jobs:
run: ${{ steps.download-actionlint.outputs.executable }} -color
shell: bash
+ test-deps-audit:
+ name: Test deps audit
+ uses: ./.github/workflows/test-deps-audit.yml
+
+ test-yarn-dedupe:
+ name: Test yarn dedupe
+ uses: ./.github/workflows/test-yarn-dedupe.yml
+
+ test-deps-depcheck:
+ name: Test deps depcheck
+ uses: ./.github/workflows/test-deps-depcheck.yml
+
run-tests:
name: Run tests
uses: ./.github/workflows/run-tests.yml
@@ -41,6 +53,8 @@ jobs:
runs-on: ubuntu-latest
needs:
- check-workflows
+ - test-yarn-dedupe
+ - test-deps-depcheck
- run-tests
- wait-for-circleci-workflow-status
outputs:
diff --git a/.github/workflows/test-deps-audit.yml b/.github/workflows/test-deps-audit.yml
new file mode 100644
index 000000000000..271746da2429
--- /dev/null
+++ b/.github/workflows/test-deps-audit.yml
@@ -0,0 +1,18 @@
+name: Test deps audit
+
+on:
+ workflow_call:
+
+jobs:
+ test-deps-audit:
+ name: Test deps audit
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Setup environment
+ uses: metamask/github-tools/.github/actions/setup-environment@main
+
+ - name: Run audit
+ run: yarn audit
diff --git a/.github/workflows/test-deps-depcheck.yml b/.github/workflows/test-deps-depcheck.yml
new file mode 100644
index 000000000000..3860c485f25b
--- /dev/null
+++ b/.github/workflows/test-deps-depcheck.yml
@@ -0,0 +1,18 @@
+name: Test deps depcheck
+
+on:
+ workflow_call:
+
+jobs:
+ test-deps-depcheck:
+ name: Test deps depcheck
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Setup environment
+ uses: metamask/github-tools/.github/actions/setup-environment@main
+
+ - name: Run depcheck
+ run: yarn depcheck
diff --git a/.github/workflows/test-yarn-dedupe.yml b/.github/workflows/test-yarn-dedupe.yml
new file mode 100644
index 000000000000..40bda1dfb3d2
--- /dev/null
+++ b/.github/workflows/test-yarn-dedupe.yml
@@ -0,0 +1,18 @@
+name: Test yarn dedupe
+
+on:
+ workflow_call:
+
+jobs:
+ test-yarn-dedupe:
+ name: Test yarn dedupe
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Setup environment
+ uses: metamask/github-tools/.github/actions/setup-environment@main
+
+ - name: Detect yarn lock deduplications
+ run: yarn dedupe --check
From 6f11eda56785b6ca1bd253a6fc6a3498eef5bc5f Mon Sep 17 00:00:00 2001
From: Mark Stacey
Date: Fri, 20 Dec 2024 16:13:27 -0330
Subject: [PATCH 074/202] ci: Migrate lint CI steps (#29371)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
## **Description**
Migrate lint steps from CircleCI to GitHub Actions.
[![Open in GitHub
Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29371?quickstart=1)
## **Related issues**
Relates to #28572
These changes were extracted from #29256
## **Manual testing steps**
Branch from here, create a new draft PR, Introduce lint errors, then
ensure the jobs fail.
https://github.com/MetaMask/metamask-extension/pull/29390
## **Screenshots/Recordings**
N/A
## **Pre-merge author checklist**
- [x] I've followed [MetaMask Contributor
Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask
Extension Coding
Standards](https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/CODING_GUIDELINES.md).
- [x] I've completed the PR template to the best of my ability
- [x] I’ve included tests if applicable
- [x] I’ve documented my code using [JSDoc](https://jsdoc.app/) format
if applicable
- [x] I’ve applied the right labels on the PR (see [labeling
guidelines](https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/LABELING_GUIDELINES.md)).
Not required for external contributors.
## **Pre-merge reviewer checklist**
- [ ] I've manually tested the PR (e.g. pull and build branch, run the
app, test code being changed).
- [ ] I confirm that this PR addresses all acceptance criteria described
in the ticket it closes and includes the necessary testing evidence such
as recordings and or screenshots.
---
.circleci/config.yml | 82 ----------------------
.github/workflows/main.yml | 20 ++++++
.github/workflows/test-lint-changelog.yml | 23 ++++++
.github/workflows/test-lint-lockfile.yml | 21 ++++++
.github/workflows/test-lint-shellcheck.yml | 15 ++++
.github/workflows/test-lint.yml | 21 ++++++
6 files changed, 100 insertions(+), 82 deletions(-)
create mode 100644 .github/workflows/test-lint-changelog.yml
create mode 100644 .github/workflows/test-lint-lockfile.yml
create mode 100644 .github/workflows/test-lint-shellcheck.yml
create mode 100644 .github/workflows/test-lint.yml
diff --git a/.circleci/config.yml b/.circleci/config.yml
index 5598e6450cfe..60bb80eaf449 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -25,10 +25,6 @@ executors:
resource_class: medium+
environment:
NODE_OPTIONS: --max_old_space_size=4096
- shellcheck:
- docker:
- - image: koalaman/shellcheck-alpine@sha256:dfaf08fab58c158549d3be64fb101c626abc5f16f341b569092577ae207db199
- resource_class: small
playwright:
docker:
- image: mcr.microsoft.com/playwright:v1.44.1-focal
@@ -184,16 +180,6 @@ workflows:
- prep-build-ts-migration-dashboard:
requires:
- prep-deps
- - test-lint:
- requires:
- - prep-deps
- - test-lint-shellcheck
- - test-lint-lockfile:
- requires:
- - prep-deps
- - test-lint-changelog:
- requires:
- - prep-deps
- test-e2e-chrome-webpack:
<<: *main_master_rc_only
requires:
@@ -285,10 +271,6 @@ workflows:
- validate-lavamoat-allow-scripts
- validate-lavamoat-policy-build
- validate-lavamoat-policy-webapp
- - test-lint
- - test-lint-shellcheck
- - test-lint-lockfile
- - test-lint-changelog
- validate-source-maps
- validate-source-maps-beta
- validate-source-maps-flask
@@ -954,20 +936,6 @@ jobs:
name: Rerun workflows from failed
command: yarn ci-rerun-from-failed
- test-lint:
- executor: node-browsers-medium
- steps:
- - run: *shallow-git-clone-and-enable-vnc
- - run: sudo corepack enable
- - attach_workspace:
- at: .
- - run:
- name: Lint
- command: yarn lint
- - run:
- name: Verify locales
- command: yarn verify-locales --quiet
-
test-storybook:
executor: node-browsers-medium-plus
steps:
@@ -982,56 +950,6 @@ jobs:
name: Test Storybook
command: yarn test-storybook:ci
- test-lint-shellcheck:
- executor: shellcheck
- steps:
- - checkout
- - run: apk add --no-cache bash jq yarn
- - run:
- name: ShellCheck Lint
- command: ./development/shellcheck.sh
-
- test-lint-lockfile:
- executor: node-browsers-medium-plus
- steps:
- - run: *shallow-git-clone-and-enable-vnc
- - run: sudo corepack enable
- - attach_workspace:
- at: .
- - run:
- name: lockfile-lint
- command: yarn lint:lockfile
- - run:
- name: check yarn resolutions
- command: yarn --check-resolutions
-
- test-lint-changelog:
- executor: node-browsers-small
- steps:
- - run: *shallow-git-clone-and-enable-vnc
- - run: sudo corepack enable
- - attach_workspace:
- at: .
- - when:
- condition:
- not:
- matches:
- pattern: /^Version-v(\d+)[.](\d+)[.](\d+)$/
- value: << pipeline.git.branch >>
- steps:
- - run:
- name: Validate changelog
- command: yarn lint:changelog
- - when:
- condition:
- matches:
- pattern: /^Version-v(\d+)[.](\d+)[.](\d+)$/
- value: << pipeline.git.branch >>
- steps:
- - run:
- name: Validate release candidate changelog
- command: .circleci/scripts/validate-changelog-in-rc.sh
-
test-e2e-chrome-webpack:
executor: node-browsers-medium-plus
parallelism: 20
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index d8850502e7da..ee29c54e94ac 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -28,6 +28,22 @@ jobs:
run: ${{ steps.download-actionlint.outputs.executable }} -color
shell: bash
+ test-lint-shellcheck:
+ name: Test lint shellcheck
+ uses: ./.github/workflows/test-lint-shellcheck.yml
+
+ test-lint:
+ name: Test lint
+ uses: ./.github/workflows/test-lint.yml
+
+ test-lint-changelog:
+ name: Test lint changelog
+ uses: ./.github/workflows/test-lint-changelog.yml
+
+ test-lint-lockfile:
+ name: Test lint lockfile
+ uses: ./.github/workflows/test-lint-lockfile.yml
+
test-deps-audit:
name: Test deps audit
uses: ./.github/workflows/test-deps-audit.yml
@@ -53,6 +69,10 @@ jobs:
runs-on: ubuntu-latest
needs:
- check-workflows
+ - test-lint-shellcheck
+ - test-lint
+ - test-lint-changelog
+ - test-lint-lockfile
- test-yarn-dedupe
- test-deps-depcheck
- run-tests
diff --git a/.github/workflows/test-lint-changelog.yml b/.github/workflows/test-lint-changelog.yml
new file mode 100644
index 000000000000..66c0219551f4
--- /dev/null
+++ b/.github/workflows/test-lint-changelog.yml
@@ -0,0 +1,23 @@
+name: Test lint changelog
+
+on:
+ workflow_call:
+
+jobs:
+ test-lint-changelog:
+ name: Test lint changelog
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Setup environment
+ uses: metamask/github-tools/.github/actions/setup-environment@main
+
+ - name: Validate changelog
+ if: ${{ !startsWith(github.head_ref || github.ref_name, 'Version-v') }}
+ run: yarn lint:changelog
+
+ - name: Validate release candidate changelog
+ if: ${{ startsWith(github.head_ref || github.ref_name, 'Version-v') }}
+ run: .circleci/scripts/validate-changelog-in-rc.sh
diff --git a/.github/workflows/test-lint-lockfile.yml b/.github/workflows/test-lint-lockfile.yml
new file mode 100644
index 000000000000..cc84318624ce
--- /dev/null
+++ b/.github/workflows/test-lint-lockfile.yml
@@ -0,0 +1,21 @@
+name: Test lint lockfile
+
+on:
+ workflow_call:
+
+jobs:
+ test-lint-lockfile:
+ name: Test lint lockfile
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Setup environment
+ uses: metamask/github-tools/.github/actions/setup-environment@main
+
+ - name: Lint lockfile
+ run: yarn lint:lockfile
+
+ - name: Check yarn resolutions
+ run: yarn --check-resolutions
diff --git a/.github/workflows/test-lint-shellcheck.yml b/.github/workflows/test-lint-shellcheck.yml
new file mode 100644
index 000000000000..c4127902a2f4
--- /dev/null
+++ b/.github/workflows/test-lint-shellcheck.yml
@@ -0,0 +1,15 @@
+name: Test lint shellcheck
+
+on:
+ workflow_call:
+
+jobs:
+ test-lint-shellcheck:
+ name: Test lint shellcheck
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: ShellCheck Lint
+ run: ./development/shellcheck.sh
diff --git a/.github/workflows/test-lint.yml b/.github/workflows/test-lint.yml
new file mode 100644
index 000000000000..df40a3a7ef27
--- /dev/null
+++ b/.github/workflows/test-lint.yml
@@ -0,0 +1,21 @@
+name: Test lint
+
+on:
+ workflow_call:
+
+jobs:
+ test-lint:
+ name: Test lint
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Setup environment
+ uses: metamask/github-tools/.github/actions/setup-environment@main
+
+ - name: Lint
+ run: yarn lint
+
+ - name: Verify locales
+ run: yarn verify-locales --quiet
From ce8b502529b1131c65506d23a6ec7b5f68c5b526 Mon Sep 17 00:00:00 2001
From: Mark Stacey
Date: Fri, 20 Dec 2024 17:23:21 -0330
Subject: [PATCH 075/202] ci: Migrate LavaMoat validation to GitHub Actions
(#29369)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
## **Description**
Migrate LavaMoat policy validation from CircleCI to GitHub actions. No
functional changes.
[![Open in GitHub
Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29369?quickstart=1)
## **Related issues**
Relates to #28572
These changes were extracted from #29256
## **Manual testing steps**
* Checkout this branch (`migrate-lavamoat-validation`), then from there
create a new branch to test with
* On this new branch, make a dependency change with a policy impact
(e.g. add or remove a package, upgrade something, etc.), but make sure
the build still passes (validation requires a passing build)
* Create a draft PR, and verify that the policy validation fails
* Use the `metamaskbot update-policies` bot command to update the
policies, then verify the validation now succeeds.
PR with errors -
https://github.com/MetaMask/metamask-extension/pull/29396
Failure -
https://github.com/MetaMask/metamask-extension/actions/runs/12434996100/job/34719873040?pr=29396
Passing -
https://github.com/MetaMask/metamask-extension/actions/runs/12435253146/job/34720674397?pr=29396
## **Screenshots/Recordings**
N/A
## **Pre-merge author checklist**
- [x] I've followed [MetaMask Contributor
Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask
Extension Coding
Standards](https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/CODING_GUIDELINES.md).
- [x] I've completed the PR template to the best of my ability
- [x] I’ve included tests if applicable
- [x] I’ve documented my code using [JSDoc](https://jsdoc.app/) format
if applicable
- [x] I’ve applied the right labels on the PR (see [labeling
guidelines](https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/LABELING_GUIDELINES.md)).
Not required for external contributors.
## **Pre-merge reviewer checklist**
- [ ] I've manually tested the PR (e.g. pull and build branch, run the
app, test code being changed).
- [ ] I confirm that this PR addresses all acceptance criteria described
in the ticket it closes and includes the necessary testing evidence such
as recordings and or screenshots.
---
.circleci/config.yml | 60 -------------------
.github/workflows/main.yml | 15 +++++
.../validate-lavamoat-allow-scripts.yml | 25 ++++++++
.../validate-lavamoat-policy-build.yml | 27 +++++++++
.../validate-lavamoat-policy-webapp.yml | 30 ++++++++++
5 files changed, 97 insertions(+), 60 deletions(-)
create mode 100644 .github/workflows/validate-lavamoat-allow-scripts.yml
create mode 100644 .github/workflows/validate-lavamoat-policy-build.yml
create mode 100644 .github/workflows/validate-lavamoat-policy-webapp.yml
diff --git a/.circleci/config.yml b/.circleci/config.yml
index 60bb80eaf449..f800ab484ebe 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -123,18 +123,6 @@ workflows:
- master
requires:
- prep-deps
- - validate-lavamoat-allow-scripts:
- requires:
- - prep-deps
- - validate-lavamoat-policy-build:
- requires:
- - prep-deps
- - validate-lavamoat-policy-webapp:
- matrix:
- parameters:
- build-type: [main, beta, flask, mmi]
- requires:
- - prep-deps
- prep-build-mmi:
requires:
- prep-deps
@@ -268,9 +256,6 @@ workflows:
- prep-build-flask-mv2
- all-tests-pass:
requires:
- - validate-lavamoat-allow-scripts
- - validate-lavamoat-policy-build
- - validate-lavamoat-policy-webapp
- validate-source-maps
- validate-source-maps-beta
- validate-source-maps-flask
@@ -481,51 +466,6 @@ jobs:
at: .
- run: yarn tsx .circleci/scripts/validate-locales-only.ts
- validate-lavamoat-allow-scripts:
- executor: node-browsers-small
- steps:
- - run: *shallow-git-clone-and-enable-vnc
- - run: sudo corepack enable
- - attach_workspace:
- at: .
- - run:
- name: Validate allow-scripts config
- command: yarn allow-scripts auto
- - run:
- name: Check working tree
- command: .circleci/scripts/check-working-tree.sh
-
- validate-lavamoat-policy-build:
- executor: node-browsers-medium
- steps:
- - run: *shallow-git-clone-and-enable-vnc
- - run: sudo corepack enable
- - attach_workspace:
- at: .
- - run:
- name: Validate LavaMoat build policy
- command: yarn lavamoat:build:auto
- - run:
- name: Check working tree
- command: .circleci/scripts/check-working-tree.sh
-
- validate-lavamoat-policy-webapp:
- executor: node-browsers-medium-plus
- parameters:
- build-type:
- type: string
- steps:
- - run: *shallow-git-clone-and-enable-vnc
- - run: sudo corepack enable
- - attach_workspace:
- at: .
- - run:
- name: Validate LavaMoat << parameters.build-type >> policy
- command: yarn lavamoat:webapp:auto:ci '--build-types=<< parameters.build-type >>'
- - run:
- name: Check working tree
- command: .circleci/scripts/check-working-tree.sh
-
prep-build:
executor: node-linux-medium
steps:
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index ee29c54e94ac..6e5a5121f336 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -56,6 +56,18 @@ jobs:
name: Test deps depcheck
uses: ./.github/workflows/test-deps-depcheck.yml
+ validate-lavamoat-allow-scripts:
+ name: Validate lavamoat allow scripts
+ uses: ./.github/workflows/validate-lavamoat-allow-scripts.yml
+
+ validate-lavamoat-policy-build:
+ name: Validate lavamoat policy build
+ uses: ./.github/workflows/validate-lavamoat-policy-build.yml
+
+ validate-lavamoat-policy-webapp:
+ name: Validate lavamoat policy webapp
+ uses: ./.github/workflows/validate-lavamoat-policy-webapp.yml
+
run-tests:
name: Run tests
uses: ./.github/workflows/run-tests.yml
@@ -75,6 +87,9 @@ jobs:
- test-lint-lockfile
- test-yarn-dedupe
- test-deps-depcheck
+ - validate-lavamoat-allow-scripts
+ - validate-lavamoat-policy-build
+ - validate-lavamoat-policy-webapp
- run-tests
- wait-for-circleci-workflow-status
outputs:
diff --git a/.github/workflows/validate-lavamoat-allow-scripts.yml b/.github/workflows/validate-lavamoat-allow-scripts.yml
new file mode 100644
index 000000000000..637a2d9aeb54
--- /dev/null
+++ b/.github/workflows/validate-lavamoat-allow-scripts.yml
@@ -0,0 +1,25 @@
+name: Validate lavamoat allow scripts
+
+on:
+ workflow_call:
+
+jobs:
+ validate-lavamoat-allow-scripts:
+ name: Validate lavamoat allow scripts
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Setup environment
+ uses: metamask/github-tools/.github/actions/setup-environment@main
+
+ - name: Validate allow-scripts config
+ run: yarn allow-scripts auto
+
+ - name: Check working tree
+ run: |
+ if ! git diff --exit-code; then
+ echo "::error::Working tree dirty."
+ exit 1
+ fi
diff --git a/.github/workflows/validate-lavamoat-policy-build.yml b/.github/workflows/validate-lavamoat-policy-build.yml
new file mode 100644
index 000000000000..4524cc26a546
--- /dev/null
+++ b/.github/workflows/validate-lavamoat-policy-build.yml
@@ -0,0 +1,27 @@
+name: Validate lavamoat policy build
+
+on:
+ workflow_call:
+
+jobs:
+ validate-lavamoat-policy-build:
+ name: Validate lavamoat policy build
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Setup environment
+ uses: metamask/github-tools/.github/actions/setup-environment@main
+
+ - name: Validate lavamoat build policy
+ run: yarn lavamoat:build:auto
+ env:
+ INFURA_PROJECT_ID: 00000000000
+
+ - name: Check working tree
+ run: |
+ if ! git diff --exit-code; then
+ echo "::error::Working tree dirty."
+ exit 1
+ fi
diff --git a/.github/workflows/validate-lavamoat-policy-webapp.yml b/.github/workflows/validate-lavamoat-policy-webapp.yml
new file mode 100644
index 000000000000..37ff9ede00fc
--- /dev/null
+++ b/.github/workflows/validate-lavamoat-policy-webapp.yml
@@ -0,0 +1,30 @@
+name: Validate lavamoat policy webapp
+
+on:
+ workflow_call:
+
+jobs:
+ validate-lavamoat-policy-webapp:
+ name: Validate lavamoat policy webapp
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ build-type: [main, beta, flask, mmi]
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Setup environment
+ uses: metamask/github-tools/.github/actions/setup-environment@main
+
+ - name: Validate lavamoat ${{ matrix.build-type }} policy
+ run: yarn lavamoat:webapp:auto:ci --build-types=${{ matrix.build-type }}
+ env:
+ INFURA_PROJECT_ID: 00000000000
+
+ - name: Check working tree
+ run: |
+ if ! git diff --exit-code; then
+ echo "::error::Working tree dirty."
+ exit 1
+ fi
From f64a2d003e6e69be0305c43534334461fadc6e7e Mon Sep 17 00:00:00 2001
From: Matthew Walsh
Date: Fri, 20 Dec 2024 20:55:52 +0000
Subject: [PATCH 076/202] fix: hide first interaction alert if token transfer
recipient is internal account (#29389)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
## **Description**
Hide the first-time interaction alert if the transaction is a token
transfer, and the recipient is an internal account.
[![Open in GitHub
Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29389?quickstart=1)
## **Related issues**
Fixes: #29225
## **Manual testing steps**
Create token transfer to internal account and verify no alert is
displayed.
## **Screenshots/Recordings**
### **Before**
### **After**
## **Pre-merge author checklist**
- [x] I've followed [MetaMask Contributor
Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask
Extension Coding
Standards](https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/CODING_GUIDELINES.md).
- [x] I've completed the PR template to the best of my ability
- [x] I’ve included tests if applicable
- [x] I’ve documented my code using [JSDoc](https://jsdoc.app/) format
if applicable
- [x] I’ve applied the right labels on the PR (see [labeling
guidelines](https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/LABELING_GUIDELINES.md)).
Not required for external contributors.
## **Pre-merge reviewer checklist**
- [ ] I've manually tested the PR (e.g. pull and build branch, run the
app, test code being changed).
- [ ] I confirm that this PR addresses all acceptance criteria described
in the ticket it closes and includes the necessary testing evidence such
as recordings and or screenshots.
---
.../info/hooks/useTransferRecipient.test.ts | 74 +++++++++++++++++++
.../info/hooks/useTransferRecipient.ts | 20 +++++
.../transaction-flow-section.tsx | 15 +---
.../useFirstTimeInteractionAlert.test.ts | 67 ++++++++++-------
.../useFirstTimeInteractionAlert.ts | 6 +-
5 files changed, 141 insertions(+), 41 deletions(-)
create mode 100644 ui/pages/confirmations/components/confirm/info/hooks/useTransferRecipient.test.ts
create mode 100644 ui/pages/confirmations/components/confirm/info/hooks/useTransferRecipient.ts
diff --git a/ui/pages/confirmations/components/confirm/info/hooks/useTransferRecipient.test.ts b/ui/pages/confirmations/components/confirm/info/hooks/useTransferRecipient.test.ts
new file mode 100644
index 000000000000..26d007ba148b
--- /dev/null
+++ b/ui/pages/confirmations/components/confirm/info/hooks/useTransferRecipient.test.ts
@@ -0,0 +1,74 @@
+import {
+ TransactionMeta,
+ TransactionType,
+} from '@metamask/transaction-controller';
+import { renderHookWithConfirmContextProvider } from '../../../../../../../test/lib/confirmations/render-helpers';
+import { getMockConfirmStateForTransaction } from '../../../../../../../test/data/confirmations/helper';
+import { genUnapprovedContractInteractionConfirmation } from '../../../../../../../test/data/confirmations/contract-interaction';
+import { genUnapprovedTokenTransferConfirmation } from '../../../../../../../test/data/confirmations/token-transfer';
+import { useTransferRecipient } from './useTransferRecipient';
+
+const ADDRESS_MOCK = '0x2e0D7E8c45221FcA00d74a3609A0f7097035d09B';
+const ADDRESS_2_MOCK = '0x2e0D7E8c45221FcA00d74a3609A0f7097035d09C';
+
+const TRANSACTION_METADATA_MOCK =
+ genUnapprovedContractInteractionConfirmation() as TransactionMeta;
+
+function runHook(transaction?: TransactionMeta) {
+ const state = transaction
+ ? getMockConfirmStateForTransaction(transaction)
+ : {};
+
+ const { result } = renderHookWithConfirmContextProvider(
+ useTransferRecipient,
+ state,
+ );
+
+ return result.current as string | undefined;
+}
+
+describe('useTransferRecipient', () => {
+ it('returns undefined if no transaction', () => {
+ expect(runHook()).toBeUndefined();
+ });
+
+ it('returns parameter to address if simple send', () => {
+ expect(
+ runHook({
+ ...TRANSACTION_METADATA_MOCK,
+ type: TransactionType.simpleSend,
+ txParams: {
+ ...TRANSACTION_METADATA_MOCK.txParams,
+ to: ADDRESS_MOCK,
+ },
+ }),
+ ).toBe(ADDRESS_MOCK);
+ });
+
+ it('returns data to address if token data', () => {
+ expect(
+ runHook({
+ ...TRANSACTION_METADATA_MOCK,
+ txParams: {
+ ...TRANSACTION_METADATA_MOCK.txParams,
+ to: ADDRESS_2_MOCK,
+ data: genUnapprovedTokenTransferConfirmation().txParams.data,
+ },
+ }),
+ ).toBe(ADDRESS_MOCK);
+ });
+
+ it('returns parameter to address if token data but type is simple send', () => {
+ expect(
+ runHook({
+ ...TRANSACTION_METADATA_MOCK,
+ type: TransactionType.simpleSend,
+ txParams: {
+ ...TRANSACTION_METADATA_MOCK.txParams,
+ to: ADDRESS_2_MOCK,
+ data: genUnapprovedTokenTransferConfirmation().txParams.data,
+ },
+ }),
+ ).toBe(ADDRESS_2_MOCK);
+ });
+});
diff --git a/ui/pages/confirmations/components/confirm/info/hooks/useTransferRecipient.ts b/ui/pages/confirmations/components/confirm/info/hooks/useTransferRecipient.ts
new file mode 100644
index 000000000000..fcfd98fedaf4
--- /dev/null
+++ b/ui/pages/confirmations/components/confirm/info/hooks/useTransferRecipient.ts
@@ -0,0 +1,20 @@
+import {
+ TransactionMeta,
+ TransactionType,
+} from '@metamask/transaction-controller';
+import { useConfirmContext } from '../../../../context/confirm';
+import { useTokenTransactionData } from './useTokenTransactionData';
+
+export function useTransferRecipient() {
+ const { currentConfirmation: transactionMetadata } =
+ useConfirmContext();
+
+ const transactionData = useTokenTransactionData();
+ const transactionType = transactionMetadata?.type;
+ const transactionTo = transactionMetadata?.txParams?.to;
+ const transferTo = transactionData?.args?._to as string | undefined;
+
+ return transactionType === TransactionType.simpleSend
+ ? transactionTo
+ : transferTo;
+}
diff --git a/ui/pages/confirmations/components/confirm/info/token-transfer/transaction-flow-section.tsx b/ui/pages/confirmations/components/confirm/info/token-transfer/transaction-flow-section.tsx
index fe9b9f319c9f..5ed2b103b809 100644
--- a/ui/pages/confirmations/components/confirm/info/token-transfer/transaction-flow-section.tsx
+++ b/ui/pages/confirmations/components/confirm/info/token-transfer/transaction-flow-section.tsx
@@ -1,7 +1,4 @@
-import {
- TransactionMeta,
- TransactionType,
-} from '@metamask/transaction-controller';
+import { TransactionMeta } from '@metamask/transaction-controller';
import React from 'react';
import { ConfirmInfoSection } from '../../../../../../components/app/confirm/info/row/section';
import {
@@ -22,7 +19,7 @@ import { ConfirmInfoAlertRow } from '../../../../../../components/app/confirm/in
import { RowAlertKey } from '../../../../../../components/app/confirm/info/row/constants';
import { useI18nContext } from '../../../../../../hooks/useI18nContext';
import { useConfirmContext } from '../../../../context/confirm';
-import { useTokenTransactionData } from '../hooks/useTokenTransactionData';
+import { useTransferRecipient } from '../hooks/useTransferRecipient';
export const TransactionFlowSection = () => {
const t = useI18nContext();
@@ -30,13 +27,7 @@ export const TransactionFlowSection = () => {
const { currentConfirmation: transactionMeta } =
useConfirmContext();
- const parsedTransactionData = useTokenTransactionData();
-
- const recipientAddress =
- transactionMeta.type === TransactionType.simpleSend
- ? transactionMeta.txParams.to
- : parsedTransactionData?.args?._to;
-
+ const recipientAddress = useTransferRecipient();
const { chainId } = transactionMeta;
return (
diff --git a/ui/pages/confirmations/hooks/alerts/transactions/useFirstTimeInteractionAlert.test.ts b/ui/pages/confirmations/hooks/alerts/transactions/useFirstTimeInteractionAlert.test.ts
index 93da09a9674e..7fe4ceb1d2e2 100644
--- a/ui/pages/confirmations/hooks/alerts/transactions/useFirstTimeInteractionAlert.test.ts
+++ b/ui/pages/confirmations/hooks/alerts/transactions/useFirstTimeInteractionAlert.test.ts
@@ -1,17 +1,19 @@
-import { ApprovalType } from '@metamask/controller-utils';
import {
TransactionMeta,
TransactionStatus,
TransactionType,
} from '@metamask/transaction-controller';
-import { getMockConfirmState } from '../../../../../../test/data/confirmations/helper';
+import { getMockConfirmStateForTransaction } from '../../../../../../test/data/confirmations/helper';
import { renderHookWithConfirmContextProvider } from '../../../../../../test/lib/confirmations/render-helpers';
import { Severity } from '../../../../../helpers/constants/design-system';
import { RowAlertKey } from '../../../../../components/app/confirm/info/row/constants';
+import { genUnapprovedTokenTransferConfirmation } from '../../../../../../test/data/confirmations/token-transfer';
import { useFirstTimeInteractionAlert } from './useFirstTimeInteractionAlert';
-const ACCOUNT_ADDRESS = '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc';
+const ACCOUNT_ADDRESS_MOCK = '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc';
+const ACCOUNT_ADDRESS_2_MOCK = '0x2e0d7e8c45221fca00d74a3609a0f7097035d09b';
+const CONTRACT_ADDRESS_MOCK = '0x0dcd5d886577d5081b0c52e242ef29e70be3e7be';
const TRANSACTION_ID_MOCK = '123-456';
const TRANSACTION_META_MOCK = {
@@ -21,7 +23,7 @@ const TRANSACTION_META_MOCK = {
status: TransactionStatus.unapproved,
type: TransactionType.contractInteraction,
txParams: {
- from: ACCOUNT_ADDRESS,
+ from: ACCOUNT_ADDRESS_MOCK,
},
time: new Date().getTime() - 10000,
} as TransactionMeta;
@@ -33,28 +35,20 @@ function runHook({
currentConfirmation?: TransactionMeta;
internalAccountAddresses?: string[];
} = {}) {
- const pendingApprovals = currentConfirmation
- ? {
- [currentConfirmation.id as string]: {
- id: currentConfirmation.id,
- type: ApprovalType.Transaction,
- },
- }
- : {};
-
- const transactions = currentConfirmation ? [currentConfirmation] : [];
-
const internalAccounts = {
accounts: internalAccountAddresses?.map((address) => ({ address })) ?? [],
};
- const state = getMockConfirmState({
- metamask: {
- internalAccounts,
- pendingApprovals,
- transactions,
- },
- });
+ const state = currentConfirmation
+ ? getMockConfirmStateForTransaction(
+ currentConfirmation as TransactionMeta,
+ {
+ metamask: {
+ internalAccounts,
+ },
+ },
+ )
+ : {};
const response = renderHookWithConfirmContextProvider(
useFirstTimeInteractionAlert,
@@ -101,15 +95,35 @@ describe('useFirstTimeInteractionAlert', () => {
const firstTimeConfirmation = {
...TRANSACTION_META_MOCK,
isFirstTimeInteraction: true,
+ type: TransactionType.simpleSend,
+ txParams: {
+ ...TRANSACTION_META_MOCK.txParams,
+ to: ACCOUNT_ADDRESS_2_MOCK,
+ },
+ };
+ expect(
+ runHook({
+ currentConfirmation: firstTimeConfirmation,
+ internalAccountAddresses: [ACCOUNT_ADDRESS_2_MOCK],
+ }),
+ ).toEqual([]);
+ });
+
+ it('returns no alerts if token transfer recipient is internal account', () => {
+ const firstTimeConfirmation = {
+ ...TRANSACTION_META_MOCK,
+ isFirstTimeInteraction: true,
+ type: TransactionType.tokenMethodTransfer,
txParams: {
...TRANSACTION_META_MOCK.txParams,
- to: ACCOUNT_ADDRESS,
+ to: CONTRACT_ADDRESS_MOCK,
+ data: genUnapprovedTokenTransferConfirmation().txParams.data,
},
};
expect(
runHook({
currentConfirmation: firstTimeConfirmation,
- internalAccountAddresses: [ACCOUNT_ADDRESS],
+ internalAccountAddresses: [ACCOUNT_ADDRESS_2_MOCK],
}),
).toEqual([]);
});
@@ -118,15 +132,16 @@ describe('useFirstTimeInteractionAlert', () => {
const firstTimeConfirmation = {
...TRANSACTION_META_MOCK,
isFirstTimeInteraction: true,
+ type: TransactionType.simpleSend,
txParams: {
...TRANSACTION_META_MOCK.txParams,
- to: ACCOUNT_ADDRESS.toLowerCase(),
+ to: ACCOUNT_ADDRESS_2_MOCK.toLowerCase(),
},
};
expect(
runHook({
currentConfirmation: firstTimeConfirmation,
- internalAccountAddresses: [ACCOUNT_ADDRESS.toUpperCase()],
+ internalAccountAddresses: [ACCOUNT_ADDRESS_2_MOCK.toUpperCase()],
}),
).toEqual([]);
});
diff --git a/ui/pages/confirmations/hooks/alerts/transactions/useFirstTimeInteractionAlert.ts b/ui/pages/confirmations/hooks/alerts/transactions/useFirstTimeInteractionAlert.ts
index c74552575667..f83f5d1ce30e 100644
--- a/ui/pages/confirmations/hooks/alerts/transactions/useFirstTimeInteractionAlert.ts
+++ b/ui/pages/confirmations/hooks/alerts/transactions/useFirstTimeInteractionAlert.ts
@@ -8,14 +8,14 @@ import { Severity } from '../../../../../helpers/constants/design-system';
import { RowAlertKey } from '../../../../../components/app/confirm/info/row/constants';
import { useConfirmContext } from '../../../context/confirm';
import { getInternalAccounts } from '../../../../../selectors';
+import { useTransferRecipient } from '../../../components/confirm/info/hooks/useTransferRecipient';
export function useFirstTimeInteractionAlert(): Alert[] {
const t = useI18nContext();
const { currentConfirmation } = useConfirmContext();
const internalAccounts = useSelector(getInternalAccounts);
-
- const { txParams, isFirstTimeInteraction } = currentConfirmation ?? {};
- const { to } = txParams ?? {};
+ const to = useTransferRecipient();
+ const { isFirstTimeInteraction } = currentConfirmation ?? {};
const isInternalAccount = internalAccounts.some(
(account) => account.address?.toLowerCase() === to?.toLowerCase(),
From 57d564d6603f00d7d7a568efd742412810846489 Mon Sep 17 00:00:00 2001
From: Mark Stacey
Date: Fri, 20 Dec 2024 19:47:24 -0330
Subject: [PATCH 077/202] chore: Remove broken MV3 perf stats (#29408)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
## **Description**
Remove broken MV3 reports. These reports relied upon data from the
`mv3-perf-stats` E2E test job, which itself relied upon the
`user-data-dir` chromedriver setting removed in #24696. They have been
broken since that PR.
These reports were very useful in prioritizing MV3 work at the time, but
we haven't needed them recently.
The `mv3-stats` E2E test suite has also been removed (this is an older
version of `mv3-perf-stats` that has been unused for even longer), along
with the charts that were used for this report.
[![Open in GitHub
Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29408?quickstart=1)
## **Related issues**
Relates to https://github.com/MetaMask/metamask-extension/issues/28572
## **Manual testing steps**
Check that the `metamaskbot` comment no longer has the links to these
broken reports. They look like this:
- mv3: Background Module Init Stats
- mv3: UI Init Stats
- mv3: Module Load Stats
## **Screenshots/Recordings**
N/A
## **Pre-merge author checklist**
- [x] I've followed [MetaMask Contributor
Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask
Extension Coding
Standards](https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/CODING_GUIDELINES.md).
- [x] I've completed the PR template to the best of my ability
- [x] I’ve included tests if applicable
- [x] I’ve documented my code using [JSDoc](https://jsdoc.app/) format
if applicable
- [x] I’ve applied the right labels on the PR (see [labeling
guidelines](https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/LABELING_GUIDELINES.md)).
Not required for external contributors.
## **Pre-merge reviewer checklist**
- [ ] I've manually tested the PR (e.g. pull and build branch, run the
app, test code being changed).
- [ ] I confirm that this PR addresses all acceptance criteria described
in the ticket it closes and includes the necessary testing evidence such
as recordings and or screenshots.
---
.circleci/config.yml | 42 -
.prettierignore | 1 -
.vscode/cspell.json | 1 -
.../charts/flamegraph/chart/index.html | 167 -
.../flamegraph/lib/d3-flamegraph-tooltip.js | 3117 ---------
.../charts/flamegraph/lib/d3-flamegraph.css | 46 -
.../charts/flamegraph/lib/d3-flamegraph.js | 5719 -----------------
development/charts/table/index.html | 67 -
development/charts/table/jquery.min.js | 18 -
development/metamaskbot-build-announce.js | 9 -
package.json | 1 -
test/e2e/mv3-perf-stats/bundle-size.js | 140 -
test/e2e/mv3-perf-stats/index.js | 2 -
test/e2e/mv3-perf-stats/init-load-stats.js | 118 -
test/e2e/mv3-stats.js | 115 -
15 files changed, 9563 deletions(-)
delete mode 100644 development/charts/flamegraph/chart/index.html
delete mode 100644 development/charts/flamegraph/lib/d3-flamegraph-tooltip.js
delete mode 100644 development/charts/flamegraph/lib/d3-flamegraph.css
delete mode 100644 development/charts/flamegraph/lib/d3-flamegraph.js
delete mode 100644 development/charts/table/index.html
delete mode 100644 development/charts/table/jquery.min.js
delete mode 100755 test/e2e/mv3-perf-stats/bundle-size.js
delete mode 100644 test/e2e/mv3-perf-stats/index.js
delete mode 100755 test/e2e/mv3-perf-stats/init-load-stats.js
delete mode 100755 test/e2e/mv3-stats.js
diff --git a/.circleci/config.yml b/.circleci/config.yml
index f800ab484ebe..607571e36eb1 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -278,9 +278,6 @@ workflows:
- user-actions-benchmark:
requires:
- prep-build-test
- - stats-module-load-init:
- requires:
- - prep-build-test
- job-publish-prerelease:
requires:
- prep-deps
@@ -294,7 +291,6 @@ workflows:
- prep-build-ts-migration-dashboard
- benchmark
- user-actions-benchmark
- - stats-module-load-init
- all-tests-pass
- job-publish-release:
filters:
@@ -1271,44 +1267,6 @@ jobs:
paths:
- test-artifacts
- stats-module-load-init:
- executor: node-browsers-small
- steps:
- - run: *shallow-git-clone-and-enable-vnc
- - run: sudo corepack enable
- - attach_workspace:
- at: .
- - run:
- name: Move test build to dist
- command: mv ./dist-test ./dist
- - run:
- name: Move test zips to builds
- command: mv ./builds-test ./builds
- - run:
- name: Run page load benchmark
- command: |
- mkdir -p test-artifacts/chrome/
- cp -R development/charts/flamegraph test-artifacts/chrome/initialisation
- cp -R development/charts/flamegraph/chart test-artifacts/chrome/initialisation/background
- cp -R development/charts/flamegraph/chart test-artifacts/chrome/initialisation/ui
- cp -R development/charts/table test-artifacts/chrome/load_time
- - run:
- name: Run page load benchmark
- command: yarn mv3:stats:chrome --out test-artifacts/chrome
- - run:
- name: Install jq
- command: sudo apt install jq -y
- - run:
- name: Record bundle size at commit
- command: ./.circleci/scripts/bundle-stats-commit.sh
- - store_artifacts:
- path: test-artifacts
- destination: test-artifacts
- - persist_to_workspace:
- root: .
- paths:
- - test-artifacts
-
job-publish-prerelease:
executor: node-browsers-medium
steps:
diff --git a/.prettierignore b/.prettierignore
index d8d8cfe4a15c..6f500515e7c6 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -6,7 +6,6 @@ node_modules/**/*
/app/vendor/**
/builds/**/*
/coverage/**/*
-/development/charts/**
/development/chromereload.js
/development/ts-migration-dashboard/filesToConvert.json
/development/ts-migration-dashboard/build/**
diff --git a/.vscode/cspell.json b/.vscode/cspell.json
index f962a85ef3ad..a8c5ea9d864e 100644
--- a/.vscode/cspell.json
+++ b/.vscode/cspell.json
@@ -47,7 +47,6 @@
"devcontainers",
"endregion",
"ensdomains",
- "flamegraph",
"FONTCONFIG",
"hardfork",
"hexstring",
diff --git a/development/charts/flamegraph/chart/index.html b/development/charts/flamegraph/chart/index.html
deleted file mode 100644
index ce53076ad9e4..000000000000
--- a/development/charts/flamegraph/chart/index.html
+++ /dev/null
@@ -1,167 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
- Performance Measurements
-
-
-
-
-
-
-
-
-
d3-flame-graph
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/development/charts/flamegraph/lib/d3-flamegraph-tooltip.js b/development/charts/flamegraph/lib/d3-flamegraph-tooltip.js
deleted file mode 100644
index cc042a0f281b..000000000000
--- a/development/charts/flamegraph/lib/d3-flamegraph-tooltip.js
+++ /dev/null
@@ -1,3117 +0,0 @@
-(function webpackUniversalModuleDefinition(root, factory) {
- if(typeof exports === 'object' && typeof module === 'object')
- module.exports = factory();
- else if(typeof define === 'function' && define.amd)
- define([], factory);
- else if(typeof exports === 'object')
- exports["flamegraph"] = factory();
- else
- root["flamegraph"] = root["flamegraph"] || {}, root["flamegraph"]["tooltip"] = factory();
-})(self, function() {
-return /******/ (() => { // webpackBootstrap
-/******/ "use strict";
-/******/ // The require scope
-/******/ var __webpack_require__ = {};
-/******/
-/************************************************************************/
-/******/ /* webpack/runtime/define property getters */
-/******/ (() => {
-/******/ // define getter functions for harmony exports
-/******/ __webpack_require__.d = (exports, definition) => {
-/******/ for(var key in definition) {
-/******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
-/******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
-/******/ }
-/******/ }
-/******/ };
-/******/ })();
-/******/
-/******/ /* webpack/runtime/hasOwnProperty shorthand */
-/******/ (() => {
-/******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
-/******/ })();
-/******/
-/******/ /* webpack/runtime/make namespace object */
-/******/ (() => {
-/******/ // define __esModule on exports
-/******/ __webpack_require__.r = (exports) => {
-/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
-/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
-/******/ }
-/******/ Object.defineProperty(exports, '__esModule', { value: true });
-/******/ };
-/******/ })();
-/******/
-/************************************************************************/
-var __webpack_exports__ = {};
-// ESM COMPAT FLAG
-__webpack_require__.r(__webpack_exports__);
-
-// EXPORTS
-__webpack_require__.d(__webpack_exports__, {
- "defaultFlamegraphTooltip": () => (/* binding */ defaultFlamegraphTooltip)
-});
-
-;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selector.js
-function none() {}
-
-/* harmony default export */ function selector(selector) {
- return selector == null ? none : function() {
- return this.querySelector(selector);
- };
-}
-
-;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/select.js
-
-
-
-/* harmony default export */ function selection_select(select) {
- if (typeof select !== "function") select = selector(select);
-
- for (var groups = this._groups, m = groups.length, subgroups = new Array(m), j = 0; j < m; ++j) {
- for (var group = groups[j], n = group.length, subgroup = subgroups[j] = new Array(n), node, subnode, i = 0; i < n; ++i) {
- if ((node = group[i]) && (subnode = select.call(node, node.__data__, i, group))) {
- if ("__data__" in node) subnode.__data__ = node.__data__;
- subgroup[i] = subnode;
- }
- }
- }
-
- return new Selection(subgroups, this._parents);
-}
-
-;// CONCATENATED MODULE: ../node_modules/d3-selection/src/array.js
-// Given something array like (or null), returns something that is strictly an
-// array. This is used to ensure that array-like objects passed to d3.selectAll
-// or selection.selectAll are converted into proper arrays when creating a
-// selection; we don’t ever want to create a selection backed by a live
-// HTMLCollection or NodeList. However, note that selection.selectAll will use a
-// static NodeList as a group, since it safely derived from querySelectorAll.
-function array(x) {
- return x == null ? [] : Array.isArray(x) ? x : Array.from(x);
-}
-
-;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selectorAll.js
-function empty() {
- return [];
-}
-
-/* harmony default export */ function selectorAll(selector) {
- return selector == null ? empty : function() {
- return this.querySelectorAll(selector);
- };
-}
-
-;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/selectAll.js
-
-
-
-
-function arrayAll(select) {
- return function() {
- return array(select.apply(this, arguments));
- };
-}
-
-/* harmony default export */ function selectAll(select) {
- if (typeof select === "function") select = arrayAll(select);
- else select = selectorAll(select);
-
- for (var groups = this._groups, m = groups.length, subgroups = [], parents = [], j = 0; j < m; ++j) {
- for (var group = groups[j], n = group.length, node, i = 0; i < n; ++i) {
- if (node = group[i]) {
- subgroups.push(select.call(node, node.__data__, i, group));
- parents.push(node);
- }
- }
- }
-
- return new Selection(subgroups, parents);
-}
-
-;// CONCATENATED MODULE: ../node_modules/d3-selection/src/matcher.js
-/* harmony default export */ function matcher(selector) {
- return function() {
- return this.matches(selector);
- };
-}
-
-function childMatcher(selector) {
- return function(node) {
- return node.matches(selector);
- };
-}
-
-
-;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/selectChild.js
-
-
-var find = Array.prototype.find;
-
-function childFind(match) {
- return function() {
- return find.call(this.children, match);
- };
-}
-
-function childFirst() {
- return this.firstElementChild;
-}
-
-/* harmony default export */ function selectChild(match) {
- return this.select(match == null ? childFirst
- : childFind(typeof match === "function" ? match : childMatcher(match)));
-}
-
-;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/selectChildren.js
-
-
-var filter = Array.prototype.filter;
-
-function children() {
- return Array.from(this.children);
-}
-
-function childrenFilter(match) {
- return function() {
- return filter.call(this.children, match);
- };
-}
-
-/* harmony default export */ function selectChildren(match) {
- return this.selectAll(match == null ? children
- : childrenFilter(typeof match === "function" ? match : childMatcher(match)));
-}
-
-;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/filter.js
-
-
-
-/* harmony default export */ function selection_filter(match) {
- if (typeof match !== "function") match = matcher(match);
-
- for (var groups = this._groups, m = groups.length, subgroups = new Array(m), j = 0; j < m; ++j) {
- for (var group = groups[j], n = group.length, subgroup = subgroups[j] = [], node, i = 0; i < n; ++i) {
- if ((node = group[i]) && match.call(node, node.__data__, i, group)) {
- subgroup.push(node);
- }
- }
- }
-
- return new Selection(subgroups, this._parents);
-}
-
-;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/sparse.js
-/* harmony default export */ function sparse(update) {
- return new Array(update.length);
-}
-
-;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/enter.js
-
-
-
-/* harmony default export */ function enter() {
- return new Selection(this._enter || this._groups.map(sparse), this._parents);
-}
-
-function EnterNode(parent, datum) {
- this.ownerDocument = parent.ownerDocument;
- this.namespaceURI = parent.namespaceURI;
- this._next = null;
- this._parent = parent;
- this.__data__ = datum;
-}
-
-EnterNode.prototype = {
- constructor: EnterNode,
- appendChild: function(child) { return this._parent.insertBefore(child, this._next); },
- insertBefore: function(child, next) { return this._parent.insertBefore(child, next); },
- querySelector: function(selector) { return this._parent.querySelector(selector); },
- querySelectorAll: function(selector) { return this._parent.querySelectorAll(selector); }
-};
-
-;// CONCATENATED MODULE: ../node_modules/d3-selection/src/constant.js
-/* harmony default export */ function src_constant(x) {
- return function() {
- return x;
- };
-}
-
-;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/data.js
-
-
-
-
-function bindIndex(parent, group, enter, update, exit, data) {
- var i = 0,
- node,
- groupLength = group.length,
- dataLength = data.length;
-
- // Put any non-null nodes that fit into update.
- // Put any null nodes into enter.
- // Put any remaining data into enter.
- for (; i < dataLength; ++i) {
- if (node = group[i]) {
- node.__data__ = data[i];
- update[i] = node;
- } else {
- enter[i] = new EnterNode(parent, data[i]);
- }
- }
-
- // Put any non-null nodes that don’t fit into exit.
- for (; i < groupLength; ++i) {
- if (node = group[i]) {
- exit[i] = node;
- }
- }
-}
-
-function bindKey(parent, group, enter, update, exit, data, key) {
- var i,
- node,
- nodeByKeyValue = new Map,
- groupLength = group.length,
- dataLength = data.length,
- keyValues = new Array(groupLength),
- keyValue;
-
- // Compute the key for each node.
- // If multiple nodes have the same key, the duplicates are added to exit.
- for (i = 0; i < groupLength; ++i) {
- if (node = group[i]) {
- keyValues[i] = keyValue = key.call(node, node.__data__, i, group) + "";
- if (nodeByKeyValue.has(keyValue)) {
- exit[i] = node;
- } else {
- nodeByKeyValue.set(keyValue, node);
- }
- }
- }
-
- // Compute the key for each datum.
- // If there a node associated with this key, join and add it to update.
- // If there is not (or the key is a duplicate), add it to enter.
- for (i = 0; i < dataLength; ++i) {
- keyValue = key.call(parent, data[i], i, data) + "";
- if (node = nodeByKeyValue.get(keyValue)) {
- update[i] = node;
- node.__data__ = data[i];
- nodeByKeyValue.delete(keyValue);
- } else {
- enter[i] = new EnterNode(parent, data[i]);
- }
- }
-
- // Add any remaining nodes that were not bound to data to exit.
- for (i = 0; i < groupLength; ++i) {
- if ((node = group[i]) && (nodeByKeyValue.get(keyValues[i]) === node)) {
- exit[i] = node;
- }
- }
-}
-
-function datum(node) {
- return node.__data__;
-}
-
-/* harmony default export */ function data(value, key) {
- if (!arguments.length) return Array.from(this, datum);
-
- var bind = key ? bindKey : bindIndex,
- parents = this._parents,
- groups = this._groups;
-
- if (typeof value !== "function") value = src_constant(value);
-
- for (var m = groups.length, update = new Array(m), enter = new Array(m), exit = new Array(m), j = 0; j < m; ++j) {
- var parent = parents[j],
- group = groups[j],
- groupLength = group.length,
- data = arraylike(value.call(parent, parent && parent.__data__, j, parents)),
- dataLength = data.length,
- enterGroup = enter[j] = new Array(dataLength),
- updateGroup = update[j] = new Array(dataLength),
- exitGroup = exit[j] = new Array(groupLength);
-
- bind(parent, group, enterGroup, updateGroup, exitGroup, data, key);
-
- // Now connect the enter nodes to their following update node, such that
- // appendChild can insert the materialized enter node before this node,
- // rather than at the end of the parent node.
- for (var i0 = 0, i1 = 0, previous, next; i0 < dataLength; ++i0) {
- if (previous = enterGroup[i0]) {
- if (i0 >= i1) i1 = i0 + 1;
- while (!(next = updateGroup[i1]) && ++i1 < dataLength);
- previous._next = next || null;
- }
- }
- }
-
- update = new Selection(update, parents);
- update._enter = enter;
- update._exit = exit;
- return update;
-}
-
-// Given some data, this returns an array-like view of it: an object that
-// exposes a length property and allows numeric indexing. Note that unlike
-// selectAll, this isn’t worried about “live” collections because the resulting
-// array will only be used briefly while data is being bound. (It is possible to
-// cause the data to change while iterating by using a key function, but please
-// don’t; we’d rather avoid a gratuitous copy.)
-function arraylike(data) {
- return typeof data === "object" && "length" in data
- ? data // Array, TypedArray, NodeList, array-like
- : Array.from(data); // Map, Set, iterable, string, or anything else
-}
-
-;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/exit.js
-
-
-
-/* harmony default export */ function exit() {
- return new Selection(this._exit || this._groups.map(sparse), this._parents);
-}
-
-;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/join.js
-/* harmony default export */ function join(onenter, onupdate, onexit) {
- var enter = this.enter(), update = this, exit = this.exit();
- if (typeof onenter === "function") {
- enter = onenter(enter);
- if (enter) enter = enter.selection();
- } else {
- enter = enter.append(onenter + "");
- }
- if (onupdate != null) {
- update = onupdate(update);
- if (update) update = update.selection();
- }
- if (onexit == null) exit.remove(); else onexit(exit);
- return enter && update ? enter.merge(update).order() : update;
-}
-
-;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/merge.js
-
-
-/* harmony default export */ function merge(context) {
- var selection = context.selection ? context.selection() : context;
-
- for (var groups0 = this._groups, groups1 = selection._groups, m0 = groups0.length, m1 = groups1.length, m = Math.min(m0, m1), merges = new Array(m0), j = 0; j < m; ++j) {
- for (var group0 = groups0[j], group1 = groups1[j], n = group0.length, merge = merges[j] = new Array(n), node, i = 0; i < n; ++i) {
- if (node = group0[i] || group1[i]) {
- merge[i] = node;
- }
- }
- }
-
- for (; j < m0; ++j) {
- merges[j] = groups0[j];
- }
-
- return new Selection(merges, this._parents);
-}
-
-;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/order.js
-/* harmony default export */ function order() {
-
- for (var groups = this._groups, j = -1, m = groups.length; ++j < m;) {
- for (var group = groups[j], i = group.length - 1, next = group[i], node; --i >= 0;) {
- if (node = group[i]) {
- if (next && node.compareDocumentPosition(next) ^ 4) next.parentNode.insertBefore(node, next);
- next = node;
- }
- }
- }
-
- return this;
-}
-
-;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/sort.js
-
-
-/* harmony default export */ function sort(compare) {
- if (!compare) compare = ascending;
-
- function compareNode(a, b) {
- return a && b ? compare(a.__data__, b.__data__) : !a - !b;
- }
-
- for (var groups = this._groups, m = groups.length, sortgroups = new Array(m), j = 0; j < m; ++j) {
- for (var group = groups[j], n = group.length, sortgroup = sortgroups[j] = new Array(n), node, i = 0; i < n; ++i) {
- if (node = group[i]) {
- sortgroup[i] = node;
- }
- }
- sortgroup.sort(compareNode);
- }
-
- return new Selection(sortgroups, this._parents).order();
-}
-
-function ascending(a, b) {
- return a < b ? -1 : a > b ? 1 : a >= b ? 0 : NaN;
-}
-
-;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/call.js
-/* harmony default export */ function call() {
- var callback = arguments[0];
- arguments[0] = this;
- callback.apply(null, arguments);
- return this;
-}
-
-;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/nodes.js
-/* harmony default export */ function nodes() {
- return Array.from(this);
-}
-
-;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/node.js
-/* harmony default export */ function node() {
-
- for (var groups = this._groups, j = 0, m = groups.length; j < m; ++j) {
- for (var group = groups[j], i = 0, n = group.length; i < n; ++i) {
- var node = group[i];
- if (node) return node;
- }
- }
-
- return null;
-}
-
-;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/size.js
-/* harmony default export */ function size() {
- let size = 0;
- for (const node of this) ++size; // eslint-disable-line no-unused-vars
- return size;
-}
-
-;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/empty.js
-/* harmony default export */ function selection_empty() {
- return !this.node();
-}
-
-;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/each.js
-/* harmony default export */ function each(callback) {
-
- for (var groups = this._groups, j = 0, m = groups.length; j < m; ++j) {
- for (var group = groups[j], i = 0, n = group.length, node; i < n; ++i) {
- if (node = group[i]) callback.call(node, node.__data__, i, group);
- }
- }
-
- return this;
-}
-
-;// CONCATENATED MODULE: ../node_modules/d3-selection/src/namespaces.js
-var xhtml = "http://www.w3.org/1999/xhtml";
-
-/* harmony default export */ const namespaces = ({
- svg: "http://www.w3.org/2000/svg",
- xhtml: xhtml,
- xlink: "http://www.w3.org/1999/xlink",
- xml: "http://www.w3.org/XML/1998/namespace",
- xmlns: "http://www.w3.org/2000/xmlns/"
-});
-
-;// CONCATENATED MODULE: ../node_modules/d3-selection/src/namespace.js
-
-
-/* harmony default export */ function namespace(name) {
- var prefix = name += "", i = prefix.indexOf(":");
- if (i >= 0 && (prefix = name.slice(0, i)) !== "xmlns") name = name.slice(i + 1);
- return namespaces.hasOwnProperty(prefix) ? {space: namespaces[prefix], local: name} : name; // eslint-disable-line no-prototype-builtins
-}
-
-;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/attr.js
-
-
-function attrRemove(name) {
- return function() {
- this.removeAttribute(name);
- };
-}
-
-function attrRemoveNS(fullname) {
- return function() {
- this.removeAttributeNS(fullname.space, fullname.local);
- };
-}
-
-function attrConstant(name, value) {
- return function() {
- this.setAttribute(name, value);
- };
-}
-
-function attrConstantNS(fullname, value) {
- return function() {
- this.setAttributeNS(fullname.space, fullname.local, value);
- };
-}
-
-function attrFunction(name, value) {
- return function() {
- var v = value.apply(this, arguments);
- if (v == null) this.removeAttribute(name);
- else this.setAttribute(name, v);
- };
-}
-
-function attrFunctionNS(fullname, value) {
- return function() {
- var v = value.apply(this, arguments);
- if (v == null) this.removeAttributeNS(fullname.space, fullname.local);
- else this.setAttributeNS(fullname.space, fullname.local, v);
- };
-}
-
-/* harmony default export */ function attr(name, value) {
- var fullname = namespace(name);
-
- if (arguments.length < 2) {
- var node = this.node();
- return fullname.local
- ? node.getAttributeNS(fullname.space, fullname.local)
- : node.getAttribute(fullname);
- }
-
- return this.each((value == null
- ? (fullname.local ? attrRemoveNS : attrRemove) : (typeof value === "function"
- ? (fullname.local ? attrFunctionNS : attrFunction)
- : (fullname.local ? attrConstantNS : attrConstant)))(fullname, value));
-}
-
-;// CONCATENATED MODULE: ../node_modules/d3-selection/src/window.js
-/* harmony default export */ function src_window(node) {
- return (node.ownerDocument && node.ownerDocument.defaultView) // node is a Node
- || (node.document && node) // node is a Window
- || node.defaultView; // node is a Document
-}
-
-;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/style.js
-
-
-function styleRemove(name) {
- return function() {
- this.style.removeProperty(name);
- };
-}
-
-function styleConstant(name, value, priority) {
- return function() {
- this.style.setProperty(name, value, priority);
- };
-}
-
-function styleFunction(name, value, priority) {
- return function() {
- var v = value.apply(this, arguments);
- if (v == null) this.style.removeProperty(name);
- else this.style.setProperty(name, v, priority);
- };
-}
-
-/* harmony default export */ function style(name, value, priority) {
- return arguments.length > 1
- ? this.each((value == null
- ? styleRemove : typeof value === "function"
- ? styleFunction
- : styleConstant)(name, value, priority == null ? "" : priority))
- : styleValue(this.node(), name);
-}
-
-function styleValue(node, name) {
- return node.style.getPropertyValue(name)
- || src_window(node).getComputedStyle(node, null).getPropertyValue(name);
-}
-
-;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/property.js
-function propertyRemove(name) {
- return function() {
- delete this[name];
- };
-}
-
-function propertyConstant(name, value) {
- return function() {
- this[name] = value;
- };
-}
-
-function propertyFunction(name, value) {
- return function() {
- var v = value.apply(this, arguments);
- if (v == null) delete this[name];
- else this[name] = v;
- };
-}
-
-/* harmony default export */ function property(name, value) {
- return arguments.length > 1
- ? this.each((value == null
- ? propertyRemove : typeof value === "function"
- ? propertyFunction
- : propertyConstant)(name, value))
- : this.node()[name];
-}
-
-;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/classed.js
-function classArray(string) {
- return string.trim().split(/^|\s+/);
-}
-
-function classList(node) {
- return node.classList || new ClassList(node);
-}
-
-function ClassList(node) {
- this._node = node;
- this._names = classArray(node.getAttribute("class") || "");
-}
-
-ClassList.prototype = {
- add: function(name) {
- var i = this._names.indexOf(name);
- if (i < 0) {
- this._names.push(name);
- this._node.setAttribute("class", this._names.join(" "));
- }
- },
- remove: function(name) {
- var i = this._names.indexOf(name);
- if (i >= 0) {
- this._names.splice(i, 1);
- this._node.setAttribute("class", this._names.join(" "));
- }
- },
- contains: function(name) {
- return this._names.indexOf(name) >= 0;
- }
-};
-
-function classedAdd(node, names) {
- var list = classList(node), i = -1, n = names.length;
- while (++i < n) list.add(names[i]);
-}
-
-function classedRemove(node, names) {
- var list = classList(node), i = -1, n = names.length;
- while (++i < n) list.remove(names[i]);
-}
-
-function classedTrue(names) {
- return function() {
- classedAdd(this, names);
- };
-}
-
-function classedFalse(names) {
- return function() {
- classedRemove(this, names);
- };
-}
-
-function classedFunction(names, value) {
- return function() {
- (value.apply(this, arguments) ? classedAdd : classedRemove)(this, names);
- };
-}
-
-/* harmony default export */ function classed(name, value) {
- var names = classArray(name + "");
-
- if (arguments.length < 2) {
- var list = classList(this.node()), i = -1, n = names.length;
- while (++i < n) if (!list.contains(names[i])) return false;
- return true;
- }
-
- return this.each((typeof value === "function"
- ? classedFunction : value
- ? classedTrue
- : classedFalse)(names, value));
-}
-
-;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/text.js
-function textRemove() {
- this.textContent = "";
-}
-
-function textConstant(value) {
- return function() {
- this.textContent = value;
- };
-}
-
-function textFunction(value) {
- return function() {
- var v = value.apply(this, arguments);
- this.textContent = v == null ? "" : v;
- };
-}
-
-/* harmony default export */ function selection_text(value) {
- return arguments.length
- ? this.each(value == null
- ? textRemove : (typeof value === "function"
- ? textFunction
- : textConstant)(value))
- : this.node().textContent;
-}
-
-;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/html.js
-function htmlRemove() {
- this.innerHTML = "";
-}
-
-function htmlConstant(value) {
- return function() {
- this.innerHTML = value;
- };
-}
-
-function htmlFunction(value) {
- return function() {
- var v = value.apply(this, arguments);
- this.innerHTML = v == null ? "" : v;
- };
-}
-
-/* harmony default export */ function html(value) {
- return arguments.length
- ? this.each(value == null
- ? htmlRemove : (typeof value === "function"
- ? htmlFunction
- : htmlConstant)(value))
- : this.node().innerHTML;
-}
-
-;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/raise.js
-function raise() {
- if (this.nextSibling) this.parentNode.appendChild(this);
-}
-
-/* harmony default export */ function selection_raise() {
- return this.each(raise);
-}
-
-;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/lower.js
-function lower() {
- if (this.previousSibling) this.parentNode.insertBefore(this, this.parentNode.firstChild);
-}
-
-/* harmony default export */ function selection_lower() {
- return this.each(lower);
-}
-
-;// CONCATENATED MODULE: ../node_modules/d3-selection/src/creator.js
-
-
-
-function creatorInherit(name) {
- return function() {
- var document = this.ownerDocument,
- uri = this.namespaceURI;
- return uri === xhtml && document.documentElement.namespaceURI === xhtml
- ? document.createElement(name)
- : document.createElementNS(uri, name);
- };
-}
-
-function creatorFixed(fullname) {
- return function() {
- return this.ownerDocument.createElementNS(fullname.space, fullname.local);
- };
-}
-
-/* harmony default export */ function creator(name) {
- var fullname = namespace(name);
- return (fullname.local
- ? creatorFixed
- : creatorInherit)(fullname);
-}
-
-;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/append.js
-
-
-/* harmony default export */ function append(name) {
- var create = typeof name === "function" ? name : creator(name);
- return this.select(function() {
- return this.appendChild(create.apply(this, arguments));
- });
-}
-
-;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/insert.js
-
-
-
-function constantNull() {
- return null;
-}
-
-/* harmony default export */ function insert(name, before) {
- var create = typeof name === "function" ? name : creator(name),
- select = before == null ? constantNull : typeof before === "function" ? before : selector(before);
- return this.select(function() {
- return this.insertBefore(create.apply(this, arguments), select.apply(this, arguments) || null);
- });
-}
-
-;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/remove.js
-function remove() {
- var parent = this.parentNode;
- if (parent) parent.removeChild(this);
-}
-
-/* harmony default export */ function selection_remove() {
- return this.each(remove);
-}
-
-;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/clone.js
-function selection_cloneShallow() {
- var clone = this.cloneNode(false), parent = this.parentNode;
- return parent ? parent.insertBefore(clone, this.nextSibling) : clone;
-}
-
-function selection_cloneDeep() {
- var clone = this.cloneNode(true), parent = this.parentNode;
- return parent ? parent.insertBefore(clone, this.nextSibling) : clone;
-}
-
-/* harmony default export */ function clone(deep) {
- return this.select(deep ? selection_cloneDeep : selection_cloneShallow);
-}
-
-;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/datum.js
-/* harmony default export */ function selection_datum(value) {
- return arguments.length
- ? this.property("__data__", value)
- : this.node().__data__;
-}
-
-;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/on.js
-function contextListener(listener) {
- return function(event) {
- listener.call(this, event, this.__data__);
- };
-}
-
-function parseTypenames(typenames) {
- return typenames.trim().split(/^|\s+/).map(function(t) {
- var name = "", i = t.indexOf(".");
- if (i >= 0) name = t.slice(i + 1), t = t.slice(0, i);
- return {type: t, name: name};
- });
-}
-
-function onRemove(typename) {
- return function() {
- var on = this.__on;
- if (!on) return;
- for (var j = 0, i = -1, m = on.length, o; j < m; ++j) {
- if (o = on[j], (!typename.type || o.type === typename.type) && o.name === typename.name) {
- this.removeEventListener(o.type, o.listener, o.options);
- } else {
- on[++i] = o;
- }
- }
- if (++i) on.length = i;
- else delete this.__on;
- };
-}
-
-function onAdd(typename, value, options) {
- return function() {
- var on = this.__on, o, listener = contextListener(value);
- if (on) for (var j = 0, m = on.length; j < m; ++j) {
- if ((o = on[j]).type === typename.type && o.name === typename.name) {
- this.removeEventListener(o.type, o.listener, o.options);
- this.addEventListener(o.type, o.listener = listener, o.options = options);
- o.value = value;
- return;
- }
- }
- this.addEventListener(typename.type, listener, options);
- o = {type: typename.type, name: typename.name, value: value, listener: listener, options: options};
- if (!on) this.__on = [o];
- else on.push(o);
- };
-}
-
-/* harmony default export */ function on(typename, value, options) {
- var typenames = parseTypenames(typename + ""), i, n = typenames.length, t;
-
- if (arguments.length < 2) {
- var on = this.node().__on;
- if (on) for (var j = 0, m = on.length, o; j < m; ++j) {
- for (i = 0, o = on[j]; i < n; ++i) {
- if ((t = typenames[i]).type === o.type && t.name === o.name) {
- return o.value;
- }
- }
- }
- return;
- }
-
- on = value ? onAdd : onRemove;
- for (i = 0; i < n; ++i) this.each(on(typenames[i], value, options));
- return this;
-}
-
-;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/dispatch.js
-
-
-function dispatchEvent(node, type, params) {
- var window = src_window(node),
- event = window.CustomEvent;
-
- if (typeof event === "function") {
- event = new event(type, params);
- } else {
- event = window.document.createEvent("Event");
- if (params) event.initEvent(type, params.bubbles, params.cancelable), event.detail = params.detail;
- else event.initEvent(type, false, false);
- }
-
- node.dispatchEvent(event);
-}
-
-function dispatchConstant(type, params) {
- return function() {
- return dispatchEvent(this, type, params);
- };
-}
-
-function dispatchFunction(type, params) {
- return function() {
- return dispatchEvent(this, type, params.apply(this, arguments));
- };
-}
-
-/* harmony default export */ function dispatch(type, params) {
- return this.each((typeof params === "function"
- ? dispatchFunction
- : dispatchConstant)(type, params));
-}
-
-;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/iterator.js
-/* harmony default export */ function* iterator() {
- for (var groups = this._groups, j = 0, m = groups.length; j < m; ++j) {
- for (var group = groups[j], i = 0, n = group.length, node; i < n; ++i) {
- if (node = group[i]) yield node;
- }
- }
-}
-
-;// CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/index.js
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-var root = [null];
-
-function Selection(groups, parents) {
- this._groups = groups;
- this._parents = parents;
-}
-
-function selection() {
- return new Selection([[document.documentElement]], root);
-}
-
-function selection_selection() {
- return this;
-}
-
-Selection.prototype = selection.prototype = {
- constructor: Selection,
- select: selection_select,
- selectAll: selectAll,
- selectChild: selectChild,
- selectChildren: selectChildren,
- filter: selection_filter,
- data: data,
- enter: enter,
- exit: exit,
- join: join,
- merge: merge,
- selection: selection_selection,
- order: order,
- sort: sort,
- call: call,
- nodes: nodes,
- node: node,
- size: size,
- empty: selection_empty,
- each: each,
- attr: attr,
- style: style,
- property: property,
- classed: classed,
- text: selection_text,
- html: html,
- raise: selection_raise,
- lower: selection_lower,
- append: append,
- insert: insert,
- remove: selection_remove,
- clone: clone,
- datum: selection_datum,
- on: on,
- dispatch: dispatch,
- [Symbol.iterator]: iterator
-};
-
-/* harmony default export */ const src_selection = (selection);
-
-;// CONCATENATED MODULE: ../node_modules/d3-selection/src/select.js
-
-
-/* harmony default export */ function src_select(selector) {
- return typeof selector === "string"
- ? new Selection([[document.querySelector(selector)]], [document.documentElement])
- : new Selection([[selector]], root);
-}
-
-;// CONCATENATED MODULE: ../node_modules/d3-dispatch/src/dispatch.js
-var noop = {value: () => {}};
-
-function dispatch_dispatch() {
- for (var i = 0, n = arguments.length, _ = {}, t; i < n; ++i) {
- if (!(t = arguments[i] + "") || (t in _) || /[\s.]/.test(t)) throw new Error("illegal type: " + t);
- _[t] = [];
- }
- return new Dispatch(_);
-}
-
-function Dispatch(_) {
- this._ = _;
-}
-
-function dispatch_parseTypenames(typenames, types) {
- return typenames.trim().split(/^|\s+/).map(function(t) {
- var name = "", i = t.indexOf(".");
- if (i >= 0) name = t.slice(i + 1), t = t.slice(0, i);
- if (t && !types.hasOwnProperty(t)) throw new Error("unknown type: " + t);
- return {type: t, name: name};
- });
-}
-
-Dispatch.prototype = dispatch_dispatch.prototype = {
- constructor: Dispatch,
- on: function(typename, callback) {
- var _ = this._,
- T = dispatch_parseTypenames(typename + "", _),
- t,
- i = -1,
- n = T.length;
-
- // If no callback was specified, return the callback of the given type and name.
- if (arguments.length < 2) {
- while (++i < n) if ((t = (typename = T[i]).type) && (t = get(_[t], typename.name))) return t;
- return;
- }
-
- // If a type was specified, set the callback for the given type and name.
- // Otherwise, if a null callback was specified, remove callbacks of the given name.
- if (callback != null && typeof callback !== "function") throw new Error("invalid callback: " + callback);
- while (++i < n) {
- if (t = (typename = T[i]).type) _[t] = set(_[t], typename.name, callback);
- else if (callback == null) for (t in _) _[t] = set(_[t], typename.name, null);
- }
-
- return this;
- },
- copy: function() {
- var copy = {}, _ = this._;
- for (var t in _) copy[t] = _[t].slice();
- return new Dispatch(copy);
- },
- call: function(type, that) {
- if ((n = arguments.length - 2) > 0) for (var args = new Array(n), i = 0, n, t; i < n; ++i) args[i] = arguments[i + 2];
- if (!this._.hasOwnProperty(type)) throw new Error("unknown type: " + type);
- for (t = this._[type], i = 0, n = t.length; i < n; ++i) t[i].value.apply(that, args);
- },
- apply: function(type, that, args) {
- if (!this._.hasOwnProperty(type)) throw new Error("unknown type: " + type);
- for (var t = this._[type], i = 0, n = t.length; i < n; ++i) t[i].value.apply(that, args);
- }
-};
-
-function get(type, name) {
- for (var i = 0, n = type.length, c; i < n; ++i) {
- if ((c = type[i]).name === name) {
- return c.value;
- }
- }
-}
-
-function set(type, name, callback) {
- for (var i = 0, n = type.length; i < n; ++i) {
- if (type[i].name === name) {
- type[i] = noop, type = type.slice(0, i).concat(type.slice(i + 1));
- break;
- }
- }
- if (callback != null) type.push({name: name, value: callback});
- return type;
-}
-
-/* harmony default export */ const src_dispatch = (dispatch_dispatch);
-
-;// CONCATENATED MODULE: ../node_modules/d3-timer/src/timer.js
-var timer_frame = 0, // is an animation frame pending?
- timeout = 0, // is a timeout pending?
- interval = 0, // are any timers active?
- pokeDelay = 1000, // how frequently we check for clock skew
- taskHead,
- taskTail,
- clockLast = 0,
- clockNow = 0,
- clockSkew = 0,
- clock = typeof performance === "object" && performance.now ? performance : Date,
- setFrame = typeof window === "object" && window.requestAnimationFrame ? window.requestAnimationFrame.bind(window) : function(f) { setTimeout(f, 17); };
-
-function now() {
- return clockNow || (setFrame(clearNow), clockNow = clock.now() + clockSkew);
-}
-
-function clearNow() {
- clockNow = 0;
-}
-
-function Timer() {
- this._call =
- this._time =
- this._next = null;
-}
-
-Timer.prototype = timer.prototype = {
- constructor: Timer,
- restart: function(callback, delay, time) {
- if (typeof callback !== "function") throw new TypeError("callback is not a function");
- time = (time == null ? now() : +time) + (delay == null ? 0 : +delay);
- if (!this._next && taskTail !== this) {
- if (taskTail) taskTail._next = this;
- else taskHead = this;
- taskTail = this;
- }
- this._call = callback;
- this._time = time;
- sleep();
- },
- stop: function() {
- if (this._call) {
- this._call = null;
- this._time = Infinity;
- sleep();
- }
- }
-};
-
-function timer(callback, delay, time) {
- var t = new Timer;
- t.restart(callback, delay, time);
- return t;
-}
-
-function timerFlush() {
- now(); // Get the current time, if not already set.
- ++timer_frame; // Pretend we’ve set an alarm, if we haven’t already.
- var t = taskHead, e;
- while (t) {
- if ((e = clockNow - t._time) >= 0) t._call.call(undefined, e);
- t = t._next;
- }
- --timer_frame;
-}
-
-function wake() {
- clockNow = (clockLast = clock.now()) + clockSkew;
- timer_frame = timeout = 0;
- try {
- timerFlush();
- } finally {
- timer_frame = 0;
- nap();
- clockNow = 0;
- }
-}
-
-function poke() {
- var now = clock.now(), delay = now - clockLast;
- if (delay > pokeDelay) clockSkew -= delay, clockLast = now;
-}
-
-function nap() {
- var t0, t1 = taskHead, t2, time = Infinity;
- while (t1) {
- if (t1._call) {
- if (time > t1._time) time = t1._time;
- t0 = t1, t1 = t1._next;
- } else {
- t2 = t1._next, t1._next = null;
- t1 = t0 ? t0._next = t2 : taskHead = t2;
- }
- }
- taskTail = t0;
- sleep(time);
-}
-
-function sleep(time) {
- if (timer_frame) return; // Soonest alarm already set, or will be.
- if (timeout) timeout = clearTimeout(timeout);
- var delay = time - clockNow; // Strictly less than if we recomputed clockNow.
- if (delay > 24) {
- if (time < Infinity) timeout = setTimeout(wake, time - clock.now() - clockSkew);
- if (interval) interval = clearInterval(interval);
- } else {
- if (!interval) clockLast = clock.now(), interval = setInterval(poke, pokeDelay);
- timer_frame = 1, setFrame(wake);
- }
-}
-
-;// CONCATENATED MODULE: ../node_modules/d3-timer/src/timeout.js
-
-
-/* harmony default export */ function src_timeout(callback, delay, time) {
- var t = new Timer;
- delay = delay == null ? 0 : +delay;
- t.restart(elapsed => {
- t.stop();
- callback(elapsed + delay);
- }, delay, time);
- return t;
-}
-
-;// CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/schedule.js
-
-
-
-var emptyOn = src_dispatch("start", "end", "cancel", "interrupt");
-var emptyTween = [];
-
-var CREATED = 0;
-var SCHEDULED = 1;
-var STARTING = 2;
-var STARTED = 3;
-var RUNNING = 4;
-var ENDING = 5;
-var ENDED = 6;
-
-/* harmony default export */ function schedule(node, name, id, index, group, timing) {
- var schedules = node.__transition;
- if (!schedules) node.__transition = {};
- else if (id in schedules) return;
- create(node, id, {
- name: name,
- index: index, // For context during callback.
- group: group, // For context during callback.
- on: emptyOn,
- tween: emptyTween,
- time: timing.time,
- delay: timing.delay,
- duration: timing.duration,
- ease: timing.ease,
- timer: null,
- state: CREATED
- });
-}
-
-function init(node, id) {
- var schedule = schedule_get(node, id);
- if (schedule.state > CREATED) throw new Error("too late; already scheduled");
- return schedule;
-}
-
-function schedule_set(node, id) {
- var schedule = schedule_get(node, id);
- if (schedule.state > STARTED) throw new Error("too late; already running");
- return schedule;
-}
-
-function schedule_get(node, id) {
- var schedule = node.__transition;
- if (!schedule || !(schedule = schedule[id])) throw new Error("transition not found");
- return schedule;
-}
-
-function create(node, id, self) {
- var schedules = node.__transition,
- tween;
-
- // Initialize the self timer when the transition is created.
- // Note the actual delay is not known until the first callback!
- schedules[id] = self;
- self.timer = timer(schedule, 0, self.time);
-
- function schedule(elapsed) {
- self.state = SCHEDULED;
- self.timer.restart(start, self.delay, self.time);
-
- // If the elapsed delay is less than our first sleep, start immediately.
- if (self.delay <= elapsed) start(elapsed - self.delay);
- }
-
- function start(elapsed) {
- var i, j, n, o;
-
- // If the state is not SCHEDULED, then we previously errored on start.
- if (self.state !== SCHEDULED) return stop();
-
- for (i in schedules) {
- o = schedules[i];
- if (o.name !== self.name) continue;
-
- // While this element already has a starting transition during this frame,
- // defer starting an interrupting transition until that transition has a
- // chance to tick (and possibly end); see d3/d3-transition#54!
- if (o.state === STARTED) return src_timeout(start);
-
- // Interrupt the active transition, if any.
- if (o.state === RUNNING) {
- o.state = ENDED;
- o.timer.stop();
- o.on.call("interrupt", node, node.__data__, o.index, o.group);
- delete schedules[i];
- }
-
- // Cancel any pre-empted transitions.
- else if (+i < id) {
- o.state = ENDED;
- o.timer.stop();
- o.on.call("cancel", node, node.__data__, o.index, o.group);
- delete schedules[i];
- }
- }
-
- // Defer the first tick to end of the current frame; see d3/d3#1576.
- // Note the transition may be canceled after start and before the first tick!
- // Note this must be scheduled before the start event; see d3/d3-transition#16!
- // Assuming this is successful, subsequent callbacks go straight to tick.
- src_timeout(function() {
- if (self.state === STARTED) {
- self.state = RUNNING;
- self.timer.restart(tick, self.delay, self.time);
- tick(elapsed);
- }
- });
-
- // Dispatch the start event.
- // Note this must be done before the tween are initialized.
- self.state = STARTING;
- self.on.call("start", node, node.__data__, self.index, self.group);
- if (self.state !== STARTING) return; // interrupted
- self.state = STARTED;
-
- // Initialize the tween, deleting null tween.
- tween = new Array(n = self.tween.length);
- for (i = 0, j = -1; i < n; ++i) {
- if (o = self.tween[i].value.call(node, node.__data__, self.index, self.group)) {
- tween[++j] = o;
- }
- }
- tween.length = j + 1;
- }
-
- function tick(elapsed) {
- var t = elapsed < self.duration ? self.ease.call(null, elapsed / self.duration) : (self.timer.restart(stop), self.state = ENDING, 1),
- i = -1,
- n = tween.length;
-
- while (++i < n) {
- tween[i].call(node, t);
- }
-
- // Dispatch the end event.
- if (self.state === ENDING) {
- self.on.call("end", node, node.__data__, self.index, self.group);
- stop();
- }
- }
-
- function stop() {
- self.state = ENDED;
- self.timer.stop();
- delete schedules[id];
- for (var i in schedules) return; // eslint-disable-line no-unused-vars
- delete node.__transition;
- }
-}
-
-;// CONCATENATED MODULE: ../node_modules/d3-transition/src/interrupt.js
-
-
-/* harmony default export */ function interrupt(node, name) {
- var schedules = node.__transition,
- schedule,
- active,
- empty = true,
- i;
-
- if (!schedules) return;
-
- name = name == null ? null : name + "";
-
- for (i in schedules) {
- if ((schedule = schedules[i]).name !== name) { empty = false; continue; }
- active = schedule.state > STARTING && schedule.state < ENDING;
- schedule.state = ENDED;
- schedule.timer.stop();
- schedule.on.call(active ? "interrupt" : "cancel", node, node.__data__, schedule.index, schedule.group);
- delete schedules[i];
- }
-
- if (empty) delete node.__transition;
-}
-
-;// CONCATENATED MODULE: ../node_modules/d3-transition/src/selection/interrupt.js
-
-
-/* harmony default export */ function selection_interrupt(name) {
- return this.each(function() {
- interrupt(this, name);
- });
-}
-
-;// CONCATENATED MODULE: ../node_modules/d3-interpolate/src/number.js
-/* harmony default export */ function number(a, b) {
- return a = +a, b = +b, function(t) {
- return a * (1 - t) + b * t;
- };
-}
-
-;// CONCATENATED MODULE: ../node_modules/d3-interpolate/src/transform/decompose.js
-var degrees = 180 / Math.PI;
-
-var identity = {
- translateX: 0,
- translateY: 0,
- rotate: 0,
- skewX: 0,
- scaleX: 1,
- scaleY: 1
-};
-
-/* harmony default export */ function decompose(a, b, c, d, e, f) {
- var scaleX, scaleY, skewX;
- if (scaleX = Math.sqrt(a * a + b * b)) a /= scaleX, b /= scaleX;
- if (skewX = a * c + b * d) c -= a * skewX, d -= b * skewX;
- if (scaleY = Math.sqrt(c * c + d * d)) c /= scaleY, d /= scaleY, skewX /= scaleY;
- if (a * d < b * c) a = -a, b = -b, skewX = -skewX, scaleX = -scaleX;
- return {
- translateX: e,
- translateY: f,
- rotate: Math.atan2(b, a) * degrees,
- skewX: Math.atan(skewX) * degrees,
- scaleX: scaleX,
- scaleY: scaleY
- };
-}
-
-;// CONCATENATED MODULE: ../node_modules/d3-interpolate/src/transform/parse.js
-
-
-var svgNode;
-
-/* eslint-disable no-undef */
-function parseCss(value) {
- const m = new (typeof DOMMatrix === "function" ? DOMMatrix : WebKitCSSMatrix)(value + "");
- return m.isIdentity ? identity : decompose(m.a, m.b, m.c, m.d, m.e, m.f);
-}
-
-function parseSvg(value) {
- if (value == null) return identity;
- if (!svgNode) svgNode = document.createElementNS("http://www.w3.org/2000/svg", "g");
- svgNode.setAttribute("transform", value);
- if (!(value = svgNode.transform.baseVal.consolidate())) return identity;
- value = value.matrix;
- return decompose(value.a, value.b, value.c, value.d, value.e, value.f);
-}
-
-;// CONCATENATED MODULE: ../node_modules/d3-interpolate/src/transform/index.js
-
-
-
-function interpolateTransform(parse, pxComma, pxParen, degParen) {
-
- function pop(s) {
- return s.length ? s.pop() + " " : "";
- }
-
- function translate(xa, ya, xb, yb, s, q) {
- if (xa !== xb || ya !== yb) {
- var i = s.push("translate(", null, pxComma, null, pxParen);
- q.push({i: i - 4, x: number(xa, xb)}, {i: i - 2, x: number(ya, yb)});
- } else if (xb || yb) {
- s.push("translate(" + xb + pxComma + yb + pxParen);
- }
- }
-
- function rotate(a, b, s, q) {
- if (a !== b) {
- if (a - b > 180) b += 360; else if (b - a > 180) a += 360; // shortest path
- q.push({i: s.push(pop(s) + "rotate(", null, degParen) - 2, x: number(a, b)});
- } else if (b) {
- s.push(pop(s) + "rotate(" + b + degParen);
- }
- }
-
- function skewX(a, b, s, q) {
- if (a !== b) {
- q.push({i: s.push(pop(s) + "skewX(", null, degParen) - 2, x: number(a, b)});
- } else if (b) {
- s.push(pop(s) + "skewX(" + b + degParen);
- }
- }
-
- function scale(xa, ya, xb, yb, s, q) {
- if (xa !== xb || ya !== yb) {
- var i = s.push(pop(s) + "scale(", null, ",", null, ")");
- q.push({i: i - 4, x: number(xa, xb)}, {i: i - 2, x: number(ya, yb)});
- } else if (xb !== 1 || yb !== 1) {
- s.push(pop(s) + "scale(" + xb + "," + yb + ")");
- }
- }
-
- return function(a, b) {
- var s = [], // string constants and placeholders
- q = []; // number interpolators
- a = parse(a), b = parse(b);
- translate(a.translateX, a.translateY, b.translateX, b.translateY, s, q);
- rotate(a.rotate, b.rotate, s, q);
- skewX(a.skewX, b.skewX, s, q);
- scale(a.scaleX, a.scaleY, b.scaleX, b.scaleY, s, q);
- a = b = null; // gc
- return function(t) {
- var i = -1, n = q.length, o;
- while (++i < n) s[(o = q[i]).i] = o.x(t);
- return s.join("");
- };
- };
-}
-
-var interpolateTransformCss = interpolateTransform(parseCss, "px, ", "px)", "deg)");
-var interpolateTransformSvg = interpolateTransform(parseSvg, ", ", ")", ")");
-
-;// CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/tween.js
-
-
-function tweenRemove(id, name) {
- var tween0, tween1;
- return function() {
- var schedule = schedule_set(this, id),
- tween = schedule.tween;
-
- // If this node shared tween with the previous node,
- // just assign the updated shared tween and we’re done!
- // Otherwise, copy-on-write.
- if (tween !== tween0) {
- tween1 = tween0 = tween;
- for (var i = 0, n = tween1.length; i < n; ++i) {
- if (tween1[i].name === name) {
- tween1 = tween1.slice();
- tween1.splice(i, 1);
- break;
- }
- }
- }
-
- schedule.tween = tween1;
- };
-}
-
-function tweenFunction(id, name, value) {
- var tween0, tween1;
- if (typeof value !== "function") throw new Error;
- return function() {
- var schedule = schedule_set(this, id),
- tween = schedule.tween;
-
- // If this node shared tween with the previous node,
- // just assign the updated shared tween and we’re done!
- // Otherwise, copy-on-write.
- if (tween !== tween0) {
- tween1 = (tween0 = tween).slice();
- for (var t = {name: name, value: value}, i = 0, n = tween1.length; i < n; ++i) {
- if (tween1[i].name === name) {
- tween1[i] = t;
- break;
- }
- }
- if (i === n) tween1.push(t);
- }
-
- schedule.tween = tween1;
- };
-}
-
-/* harmony default export */ function tween(name, value) {
- var id = this._id;
-
- name += "";
-
- if (arguments.length < 2) {
- var tween = schedule_get(this.node(), id).tween;
- for (var i = 0, n = tween.length, t; i < n; ++i) {
- if ((t = tween[i]).name === name) {
- return t.value;
- }
- }
- return null;
- }
-
- return this.each((value == null ? tweenRemove : tweenFunction)(id, name, value));
-}
-
-function tweenValue(transition, name, value) {
- var id = transition._id;
-
- transition.each(function() {
- var schedule = schedule_set(this, id);
- (schedule.value || (schedule.value = {}))[name] = value.apply(this, arguments);
- });
-
- return function(node) {
- return schedule_get(node, id).value[name];
- };
-}
-
-;// CONCATENATED MODULE: ../node_modules/d3-color/src/define.js
-/* harmony default export */ function src_define(constructor, factory, prototype) {
- constructor.prototype = factory.prototype = prototype;
- prototype.constructor = constructor;
-}
-
-function extend(parent, definition) {
- var prototype = Object.create(parent.prototype);
- for (var key in definition) prototype[key] = definition[key];
- return prototype;
-}
-
-;// CONCATENATED MODULE: ../node_modules/d3-color/src/color.js
-
-
-function Color() {}
-
-var darker = 0.7;
-var brighter = 1 / darker;
-
-var reI = "\\s*([+-]?\\d+)\\s*",
- reN = "\\s*([+-]?\\d*\\.?\\d+(?:[eE][+-]?\\d+)?)\\s*",
- reP = "\\s*([+-]?\\d*\\.?\\d+(?:[eE][+-]?\\d+)?)%\\s*",
- reHex = /^#([0-9a-f]{3,8})$/,
- reRgbInteger = new RegExp("^rgb\\(" + [reI, reI, reI] + "\\)$"),
- reRgbPercent = new RegExp("^rgb\\(" + [reP, reP, reP] + "\\)$"),
- reRgbaInteger = new RegExp("^rgba\\(" + [reI, reI, reI, reN] + "\\)$"),
- reRgbaPercent = new RegExp("^rgba\\(" + [reP, reP, reP, reN] + "\\)$"),
- reHslPercent = new RegExp("^hsl\\(" + [reN, reP, reP] + "\\)$"),
- reHslaPercent = new RegExp("^hsla\\(" + [reN, reP, reP, reN] + "\\)$");
-
-var named = {
- aliceblue: 0xf0f8ff,
- antiquewhite: 0xfaebd7,
- aqua: 0x00ffff,
- aquamarine: 0x7fffd4,
- azure: 0xf0ffff,
- beige: 0xf5f5dc,
- bisque: 0xffe4c4,
- black: 0x000000,
- blanchedalmond: 0xffebcd,
- blue: 0x0000ff,
- blueviolet: 0x8a2be2,
- brown: 0xa52a2a,
- burlywood: 0xdeb887,
- cadetblue: 0x5f9ea0,
- chartreuse: 0x7fff00,
- chocolate: 0xd2691e,
- coral: 0xff7f50,
- cornflowerblue: 0x6495ed,
- cornsilk: 0xfff8dc,
- crimson: 0xdc143c,
- cyan: 0x00ffff,
- darkblue: 0x00008b,
- darkcyan: 0x008b8b,
- darkgoldenrod: 0xb8860b,
- darkgray: 0xa9a9a9,
- darkgreen: 0x006400,
- darkgrey: 0xa9a9a9,
- darkkhaki: 0xbdb76b,
- darkmagenta: 0x8b008b,
- darkolivegreen: 0x556b2f,
- darkorange: 0xff8c00,
- darkorchid: 0x9932cc,
- darkred: 0x8b0000,
- darksalmon: 0xe9967a,
- darkseagreen: 0x8fbc8f,
- darkslateblue: 0x483d8b,
- darkslategray: 0x2f4f4f,
- darkslategrey: 0x2f4f4f,
- darkturquoise: 0x00ced1,
- darkviolet: 0x9400d3,
- deeppink: 0xff1493,
- deepskyblue: 0x00bfff,
- dimgray: 0x696969,
- dimgrey: 0x696969,
- dodgerblue: 0x1e90ff,
- firebrick: 0xb22222,
- floralwhite: 0xfffaf0,
- forestgreen: 0x228b22,
- fuchsia: 0xff00ff,
- gainsboro: 0xdcdcdc,
- ghostwhite: 0xf8f8ff,
- gold: 0xffd700,
- goldenrod: 0xdaa520,
- gray: 0x808080,
- green: 0x008000,
- greenyellow: 0xadff2f,
- grey: 0x808080,
- honeydew: 0xf0fff0,
- hotpink: 0xff69b4,
- indianred: 0xcd5c5c,
- indigo: 0x4b0082,
- ivory: 0xfffff0,
- khaki: 0xf0e68c,
- lavender: 0xe6e6fa,
- lavenderblush: 0xfff0f5,
- lawngreen: 0x7cfc00,
- lemonchiffon: 0xfffacd,
- lightblue: 0xadd8e6,
- lightcoral: 0xf08080,
- lightcyan: 0xe0ffff,
- lightgoldenrodyellow: 0xfafad2,
- lightgray: 0xd3d3d3,
- lightgreen: 0x90ee90,
- lightgrey: 0xd3d3d3,
- lightpink: 0xffb6c1,
- lightsalmon: 0xffa07a,
- lightseagreen: 0x20b2aa,
- lightskyblue: 0x87cefa,
- lightslategray: 0x778899,
- lightslategrey: 0x778899,
- lightsteelblue: 0xb0c4de,
- lightyellow: 0xffffe0,
- lime: 0x00ff00,
- limegreen: 0x32cd32,
- linen: 0xfaf0e6,
- magenta: 0xff00ff,
- maroon: 0x800000,
- mediumaquamarine: 0x66cdaa,
- mediumblue: 0x0000cd,
- mediumorchid: 0xba55d3,
- mediumpurple: 0x9370db,
- mediumseagreen: 0x3cb371,
- mediumslateblue: 0x7b68ee,
- mediumspringgreen: 0x00fa9a,
- mediumturquoise: 0x48d1cc,
- mediumvioletred: 0xc71585,
- midnightblue: 0x191970,
- mintcream: 0xf5fffa,
- mistyrose: 0xffe4e1,
- moccasin: 0xffe4b5,
- navajowhite: 0xffdead,
- navy: 0x000080,
- oldlace: 0xfdf5e6,
- olive: 0x808000,
- olivedrab: 0x6b8e23,
- orange: 0xffa500,
- orangered: 0xff4500,
- orchid: 0xda70d6,
- palegoldenrod: 0xeee8aa,
- palegreen: 0x98fb98,
- paleturquoise: 0xafeeee,
- palevioletred: 0xdb7093,
- papayawhip: 0xffefd5,
- peachpuff: 0xffdab9,
- peru: 0xcd853f,
- pink: 0xffc0cb,
- plum: 0xdda0dd,
- powderblue: 0xb0e0e6,
- purple: 0x800080,
- rebeccapurple: 0x663399,
- red: 0xff0000,
- rosybrown: 0xbc8f8f,
- royalblue: 0x4169e1,
- saddlebrown: 0x8b4513,
- salmon: 0xfa8072,
- sandybrown: 0xf4a460,
- seagreen: 0x2e8b57,
- seashell: 0xfff5ee,
- sienna: 0xa0522d,
- silver: 0xc0c0c0,
- skyblue: 0x87ceeb,
- slateblue: 0x6a5acd,
- slategray: 0x708090,
- slategrey: 0x708090,
- snow: 0xfffafa,
- springgreen: 0x00ff7f,
- steelblue: 0x4682b4,
- tan: 0xd2b48c,
- teal: 0x008080,
- thistle: 0xd8bfd8,
- tomato: 0xff6347,
- turquoise: 0x40e0d0,
- violet: 0xee82ee,
- wheat: 0xf5deb3,
- white: 0xffffff,
- whitesmoke: 0xf5f5f5,
- yellow: 0xffff00,
- yellowgreen: 0x9acd32
-};
-
-src_define(Color, color, {
- copy: function(channels) {
- return Object.assign(new this.constructor, this, channels);
- },
- displayable: function() {
- return this.rgb().displayable();
- },
- hex: color_formatHex, // Deprecated! Use color.formatHex.
- formatHex: color_formatHex,
- formatHsl: color_formatHsl,
- formatRgb: color_formatRgb,
- toString: color_formatRgb
-});
-
-function color_formatHex() {
- return this.rgb().formatHex();
-}
-
-function color_formatHsl() {
- return hslConvert(this).formatHsl();
-}
-
-function color_formatRgb() {
- return this.rgb().formatRgb();
-}
-
-function color(format) {
- var m, l;
- format = (format + "").trim().toLowerCase();
- return (m = reHex.exec(format)) ? (l = m[1].length, m = parseInt(m[1], 16), l === 6 ? rgbn(m) // #ff0000
- : l === 3 ? new Rgb((m >> 8 & 0xf) | (m >> 4 & 0xf0), (m >> 4 & 0xf) | (m & 0xf0), ((m & 0xf) << 4) | (m & 0xf), 1) // #f00
- : l === 8 ? rgba(m >> 24 & 0xff, m >> 16 & 0xff, m >> 8 & 0xff, (m & 0xff) / 0xff) // #ff000000
- : l === 4 ? rgba((m >> 12 & 0xf) | (m >> 8 & 0xf0), (m >> 8 & 0xf) | (m >> 4 & 0xf0), (m >> 4 & 0xf) | (m & 0xf0), (((m & 0xf) << 4) | (m & 0xf)) / 0xff) // #f000
- : null) // invalid hex
- : (m = reRgbInteger.exec(format)) ? new Rgb(m[1], m[2], m[3], 1) // rgb(255, 0, 0)
- : (m = reRgbPercent.exec(format)) ? new Rgb(m[1] * 255 / 100, m[2] * 255 / 100, m[3] * 255 / 100, 1) // rgb(100%, 0%, 0%)
- : (m = reRgbaInteger.exec(format)) ? rgba(m[1], m[2], m[3], m[4]) // rgba(255, 0, 0, 1)
- : (m = reRgbaPercent.exec(format)) ? rgba(m[1] * 255 / 100, m[2] * 255 / 100, m[3] * 255 / 100, m[4]) // rgb(100%, 0%, 0%, 1)
- : (m = reHslPercent.exec(format)) ? hsla(m[1], m[2] / 100, m[3] / 100, 1) // hsl(120, 50%, 50%)
- : (m = reHslaPercent.exec(format)) ? hsla(m[1], m[2] / 100, m[3] / 100, m[4]) // hsla(120, 50%, 50%, 1)
- : named.hasOwnProperty(format) ? rgbn(named[format]) // eslint-disable-line no-prototype-builtins
- : format === "transparent" ? new Rgb(NaN, NaN, NaN, 0)
- : null;
-}
-
-function rgbn(n) {
- return new Rgb(n >> 16 & 0xff, n >> 8 & 0xff, n & 0xff, 1);
-}
-
-function rgba(r, g, b, a) {
- if (a <= 0) r = g = b = NaN;
- return new Rgb(r, g, b, a);
-}
-
-function rgbConvert(o) {
- if (!(o instanceof Color)) o = color(o);
- if (!o) return new Rgb;
- o = o.rgb();
- return new Rgb(o.r, o.g, o.b, o.opacity);
-}
-
-function color_rgb(r, g, b, opacity) {
- return arguments.length === 1 ? rgbConvert(r) : new Rgb(r, g, b, opacity == null ? 1 : opacity);
-}
-
-function Rgb(r, g, b, opacity) {
- this.r = +r;
- this.g = +g;
- this.b = +b;
- this.opacity = +opacity;
-}
-
-src_define(Rgb, color_rgb, extend(Color, {
- brighter: function(k) {
- k = k == null ? brighter : Math.pow(brighter, k);
- return new Rgb(this.r * k, this.g * k, this.b * k, this.opacity);
- },
- darker: function(k) {
- k = k == null ? darker : Math.pow(darker, k);
- return new Rgb(this.r * k, this.g * k, this.b * k, this.opacity);
- },
- rgb: function() {
- return this;
- },
- displayable: function() {
- return (-0.5 <= this.r && this.r < 255.5)
- && (-0.5 <= this.g && this.g < 255.5)
- && (-0.5 <= this.b && this.b < 255.5)
- && (0 <= this.opacity && this.opacity <= 1);
- },
- hex: rgb_formatHex, // Deprecated! Use color.formatHex.
- formatHex: rgb_formatHex,
- formatRgb: rgb_formatRgb,
- toString: rgb_formatRgb
-}));
-
-function rgb_formatHex() {
- return "#" + hex(this.r) + hex(this.g) + hex(this.b);
-}
-
-function rgb_formatRgb() {
- var a = this.opacity; a = isNaN(a) ? 1 : Math.max(0, Math.min(1, a));
- return (a === 1 ? "rgb(" : "rgba(")
- + Math.max(0, Math.min(255, Math.round(this.r) || 0)) + ", "
- + Math.max(0, Math.min(255, Math.round(this.g) || 0)) + ", "
- + Math.max(0, Math.min(255, Math.round(this.b) || 0))
- + (a === 1 ? ")" : ", " + a + ")");
-}
-
-function hex(value) {
- value = Math.max(0, Math.min(255, Math.round(value) || 0));
- return (value < 16 ? "0" : "") + value.toString(16);
-}
-
-function hsla(h, s, l, a) {
- if (a <= 0) h = s = l = NaN;
- else if (l <= 0 || l >= 1) h = s = NaN;
- else if (s <= 0) h = NaN;
- return new Hsl(h, s, l, a);
-}
-
-function hslConvert(o) {
- if (o instanceof Hsl) return new Hsl(o.h, o.s, o.l, o.opacity);
- if (!(o instanceof Color)) o = color(o);
- if (!o) return new Hsl;
- if (o instanceof Hsl) return o;
- o = o.rgb();
- var r = o.r / 255,
- g = o.g / 255,
- b = o.b / 255,
- min = Math.min(r, g, b),
- max = Math.max(r, g, b),
- h = NaN,
- s = max - min,
- l = (max + min) / 2;
- if (s) {
- if (r === max) h = (g - b) / s + (g < b) * 6;
- else if (g === max) h = (b - r) / s + 2;
- else h = (r - g) / s + 4;
- s /= l < 0.5 ? max + min : 2 - max - min;
- h *= 60;
- } else {
- s = l > 0 && l < 1 ? 0 : h;
- }
- return new Hsl(h, s, l, o.opacity);
-}
-
-function hsl(h, s, l, opacity) {
- return arguments.length === 1 ? hslConvert(h) : new Hsl(h, s, l, opacity == null ? 1 : opacity);
-}
-
-function Hsl(h, s, l, opacity) {
- this.h = +h;
- this.s = +s;
- this.l = +l;
- this.opacity = +opacity;
-}
-
-src_define(Hsl, hsl, extend(Color, {
- brighter: function(k) {
- k = k == null ? brighter : Math.pow(brighter, k);
- return new Hsl(this.h, this.s, this.l * k, this.opacity);
- },
- darker: function(k) {
- k = k == null ? darker : Math.pow(darker, k);
- return new Hsl(this.h, this.s, this.l * k, this.opacity);
- },
- rgb: function() {
- var h = this.h % 360 + (this.h < 0) * 360,
- s = isNaN(h) || isNaN(this.s) ? 0 : this.s,
- l = this.l,
- m2 = l + (l < 0.5 ? l : 1 - l) * s,
- m1 = 2 * l - m2;
- return new Rgb(
- hsl2rgb(h >= 240 ? h - 240 : h + 120, m1, m2),
- hsl2rgb(h, m1, m2),
- hsl2rgb(h < 120 ? h + 240 : h - 120, m1, m2),
- this.opacity
- );
- },
- displayable: function() {
- return (0 <= this.s && this.s <= 1 || isNaN(this.s))
- && (0 <= this.l && this.l <= 1)
- && (0 <= this.opacity && this.opacity <= 1);
- },
- formatHsl: function() {
- var a = this.opacity; a = isNaN(a) ? 1 : Math.max(0, Math.min(1, a));
- return (a === 1 ? "hsl(" : "hsla(")
- + (this.h || 0) + ", "
- + (this.s || 0) * 100 + "%, "
- + (this.l || 0) * 100 + "%"
- + (a === 1 ? ")" : ", " + a + ")");
- }
-}));
-
-/* From FvD 13.37, CSS Color Module Level 3 */
-function hsl2rgb(h, m1, m2) {
- return (h < 60 ? m1 + (m2 - m1) * h / 60
- : h < 180 ? m2
- : h < 240 ? m1 + (m2 - m1) * (240 - h) / 60
- : m1) * 255;
-}
-
-;// CONCATENATED MODULE: ../node_modules/d3-interpolate/src/basis.js
-function basis(t1, v0, v1, v2, v3) {
- var t2 = t1 * t1, t3 = t2 * t1;
- return ((1 - 3 * t1 + 3 * t2 - t3) * v0
- + (4 - 6 * t2 + 3 * t3) * v1
- + (1 + 3 * t1 + 3 * t2 - 3 * t3) * v2
- + t3 * v3) / 6;
-}
-
-/* harmony default export */ function src_basis(values) {
- var n = values.length - 1;
- return function(t) {
- var i = t <= 0 ? (t = 0) : t >= 1 ? (t = 1, n - 1) : Math.floor(t * n),
- v1 = values[i],
- v2 = values[i + 1],
- v0 = i > 0 ? values[i - 1] : 2 * v1 - v2,
- v3 = i < n - 1 ? values[i + 2] : 2 * v2 - v1;
- return basis((t - i / n) * n, v0, v1, v2, v3);
- };
-}
-
-;// CONCATENATED MODULE: ../node_modules/d3-interpolate/src/basisClosed.js
-
-
-/* harmony default export */ function basisClosed(values) {
- var n = values.length;
- return function(t) {
- var i = Math.floor(((t %= 1) < 0 ? ++t : t) * n),
- v0 = values[(i + n - 1) % n],
- v1 = values[i % n],
- v2 = values[(i + 1) % n],
- v3 = values[(i + 2) % n];
- return basis((t - i / n) * n, v0, v1, v2, v3);
- };
-}
-
-;// CONCATENATED MODULE: ../node_modules/d3-interpolate/src/constant.js
-/* harmony default export */ const d3_interpolate_src_constant = (x => () => x);
-
-;// CONCATENATED MODULE: ../node_modules/d3-interpolate/src/color.js
-
-
-function linear(a, d) {
- return function(t) {
- return a + t * d;
- };
-}
-
-function exponential(a, b, y) {
- return a = Math.pow(a, y), b = Math.pow(b, y) - a, y = 1 / y, function(t) {
- return Math.pow(a + t * b, y);
- };
-}
-
-function hue(a, b) {
- var d = b - a;
- return d ? linear(a, d > 180 || d < -180 ? d - 360 * Math.round(d / 360) : d) : constant(isNaN(a) ? b : a);
-}
-
-function gamma(y) {
- return (y = +y) === 1 ? nogamma : function(a, b) {
- return b - a ? exponential(a, b, y) : d3_interpolate_src_constant(isNaN(a) ? b : a);
- };
-}
-
-function nogamma(a, b) {
- var d = b - a;
- return d ? linear(a, d) : d3_interpolate_src_constant(isNaN(a) ? b : a);
-}
-
-;// CONCATENATED MODULE: ../node_modules/d3-interpolate/src/rgb.js
-
-
-
-
-
-/* harmony default export */ const rgb = ((function rgbGamma(y) {
- var color = gamma(y);
-
- function rgb(start, end) {
- var r = color((start = color_rgb(start)).r, (end = color_rgb(end)).r),
- g = color(start.g, end.g),
- b = color(start.b, end.b),
- opacity = nogamma(start.opacity, end.opacity);
- return function(t) {
- start.r = r(t);
- start.g = g(t);
- start.b = b(t);
- start.opacity = opacity(t);
- return start + "";
- };
- }
-
- rgb.gamma = rgbGamma;
-
- return rgb;
-})(1));
-
-function rgbSpline(spline) {
- return function(colors) {
- var n = colors.length,
- r = new Array(n),
- g = new Array(n),
- b = new Array(n),
- i, color;
- for (i = 0; i < n; ++i) {
- color = color_rgb(colors[i]);
- r[i] = color.r || 0;
- g[i] = color.g || 0;
- b[i] = color.b || 0;
- }
- r = spline(r);
- g = spline(g);
- b = spline(b);
- color.opacity = 1;
- return function(t) {
- color.r = r(t);
- color.g = g(t);
- color.b = b(t);
- return color + "";
- };
- };
-}
-
-var rgbBasis = rgbSpline(src_basis);
-var rgbBasisClosed = rgbSpline(basisClosed);
-
-;// CONCATENATED MODULE: ../node_modules/d3-interpolate/src/string.js
-
-
-var reA = /[-+]?(?:\d+\.?\d*|\.?\d+)(?:[eE][-+]?\d+)?/g,
- reB = new RegExp(reA.source, "g");
-
-function zero(b) {
- return function() {
- return b;
- };
-}
-
-function one(b) {
- return function(t) {
- return b(t) + "";
- };
-}
-
-/* harmony default export */ function string(a, b) {
- var bi = reA.lastIndex = reB.lastIndex = 0, // scan index for next number in b
- am, // current match in a
- bm, // current match in b
- bs, // string preceding current number in b, if any
- i = -1, // index in s
- s = [], // string constants and placeholders
- q = []; // number interpolators
-
- // Coerce inputs to strings.
- a = a + "", b = b + "";
-
- // Interpolate pairs of numbers in a & b.
- while ((am = reA.exec(a))
- && (bm = reB.exec(b))) {
- if ((bs = bm.index) > bi) { // a string precedes the next number in b
- bs = b.slice(bi, bs);
- if (s[i]) s[i] += bs; // coalesce with previous string
- else s[++i] = bs;
- }
- if ((am = am[0]) === (bm = bm[0])) { // numbers in a & b match
- if (s[i]) s[i] += bm; // coalesce with previous string
- else s[++i] = bm;
- } else { // interpolate non-matching numbers
- s[++i] = null;
- q.push({i: i, x: number(am, bm)});
- }
- bi = reB.lastIndex;
- }
-
- // Add remains of b.
- if (bi < b.length) {
- bs = b.slice(bi);
- if (s[i]) s[i] += bs; // coalesce with previous string
- else s[++i] = bs;
- }
-
- // Special optimization for only a single match.
- // Otherwise, interpolate each of the numbers and rejoin the string.
- return s.length < 2 ? (q[0]
- ? one(q[0].x)
- : zero(b))
- : (b = q.length, function(t) {
- for (var i = 0, o; i < b; ++i) s[(o = q[i]).i] = o.x(t);
- return s.join("");
- });
-}
-
-;// CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/interpolate.js
-
-
-
-/* harmony default export */ function interpolate(a, b) {
- var c;
- return (typeof b === "number" ? number
- : b instanceof color ? rgb
- : (c = color(b)) ? (b = c, rgb)
- : string)(a, b);
-}
-
-;// CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/attr.js
-
-
-
-
-
-function attr_attrRemove(name) {
- return function() {
- this.removeAttribute(name);
- };
-}
-
-function attr_attrRemoveNS(fullname) {
- return function() {
- this.removeAttributeNS(fullname.space, fullname.local);
- };
-}
-
-function attr_attrConstant(name, interpolate, value1) {
- var string00,
- string1 = value1 + "",
- interpolate0;
- return function() {
- var string0 = this.getAttribute(name);
- return string0 === string1 ? null
- : string0 === string00 ? interpolate0
- : interpolate0 = interpolate(string00 = string0, value1);
- };
-}
-
-function attr_attrConstantNS(fullname, interpolate, value1) {
- var string00,
- string1 = value1 + "",
- interpolate0;
- return function() {
- var string0 = this.getAttributeNS(fullname.space, fullname.local);
- return string0 === string1 ? null
- : string0 === string00 ? interpolate0
- : interpolate0 = interpolate(string00 = string0, value1);
- };
-}
-
-function attr_attrFunction(name, interpolate, value) {
- var string00,
- string10,
- interpolate0;
- return function() {
- var string0, value1 = value(this), string1;
- if (value1 == null) return void this.removeAttribute(name);
- string0 = this.getAttribute(name);
- string1 = value1 + "";
- return string0 === string1 ? null
- : string0 === string00 && string1 === string10 ? interpolate0
- : (string10 = string1, interpolate0 = interpolate(string00 = string0, value1));
- };
-}
-
-function attr_attrFunctionNS(fullname, interpolate, value) {
- var string00,
- string10,
- interpolate0;
- return function() {
- var string0, value1 = value(this), string1;
- if (value1 == null) return void this.removeAttributeNS(fullname.space, fullname.local);
- string0 = this.getAttributeNS(fullname.space, fullname.local);
- string1 = value1 + "";
- return string0 === string1 ? null
- : string0 === string00 && string1 === string10 ? interpolate0
- : (string10 = string1, interpolate0 = interpolate(string00 = string0, value1));
- };
-}
-
-/* harmony default export */ function transition_attr(name, value) {
- var fullname = namespace(name), i = fullname === "transform" ? interpolateTransformSvg : interpolate;
- return this.attrTween(name, typeof value === "function"
- ? (fullname.local ? attr_attrFunctionNS : attr_attrFunction)(fullname, i, tweenValue(this, "attr." + name, value))
- : value == null ? (fullname.local ? attr_attrRemoveNS : attr_attrRemove)(fullname)
- : (fullname.local ? attr_attrConstantNS : attr_attrConstant)(fullname, i, value));
-}
-
-;// CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/attrTween.js
-
-
-function attrInterpolate(name, i) {
- return function(t) {
- this.setAttribute(name, i.call(this, t));
- };
-}
-
-function attrInterpolateNS(fullname, i) {
- return function(t) {
- this.setAttributeNS(fullname.space, fullname.local, i.call(this, t));
- };
-}
-
-function attrTweenNS(fullname, value) {
- var t0, i0;
- function tween() {
- var i = value.apply(this, arguments);
- if (i !== i0) t0 = (i0 = i) && attrInterpolateNS(fullname, i);
- return t0;
- }
- tween._value = value;
- return tween;
-}
-
-function attrTween(name, value) {
- var t0, i0;
- function tween() {
- var i = value.apply(this, arguments);
- if (i !== i0) t0 = (i0 = i) && attrInterpolate(name, i);
- return t0;
- }
- tween._value = value;
- return tween;
-}
-
-/* harmony default export */ function transition_attrTween(name, value) {
- var key = "attr." + name;
- if (arguments.length < 2) return (key = this.tween(key)) && key._value;
- if (value == null) return this.tween(key, null);
- if (typeof value !== "function") throw new Error;
- var fullname = namespace(name);
- return this.tween(key, (fullname.local ? attrTweenNS : attrTween)(fullname, value));
-}
-
-;// CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/delay.js
-
-
-function delayFunction(id, value) {
- return function() {
- init(this, id).delay = +value.apply(this, arguments);
- };
-}
-
-function delayConstant(id, value) {
- return value = +value, function() {
- init(this, id).delay = value;
- };
-}
-
-/* harmony default export */ function delay(value) {
- var id = this._id;
-
- return arguments.length
- ? this.each((typeof value === "function"
- ? delayFunction
- : delayConstant)(id, value))
- : schedule_get(this.node(), id).delay;
-}
-
-;// CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/duration.js
-
-
-function durationFunction(id, value) {
- return function() {
- schedule_set(this, id).duration = +value.apply(this, arguments);
- };
-}
-
-function durationConstant(id, value) {
- return value = +value, function() {
- schedule_set(this, id).duration = value;
- };
-}
-
-/* harmony default export */ function duration(value) {
- var id = this._id;
-
- return arguments.length
- ? this.each((typeof value === "function"
- ? durationFunction
- : durationConstant)(id, value))
- : schedule_get(this.node(), id).duration;
-}
-
-;// CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/ease.js
-
-
-function easeConstant(id, value) {
- if (typeof value !== "function") throw new Error;
- return function() {
- schedule_set(this, id).ease = value;
- };
-}
-
-/* harmony default export */ function ease(value) {
- var id = this._id;
-
- return arguments.length
- ? this.each(easeConstant(id, value))
- : schedule_get(this.node(), id).ease;
-}
-
-;// CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/easeVarying.js
-
-
-function easeVarying(id, value) {
- return function() {
- var v = value.apply(this, arguments);
- if (typeof v !== "function") throw new Error;
- schedule_set(this, id).ease = v;
- };
-}
-
-/* harmony default export */ function transition_easeVarying(value) {
- if (typeof value !== "function") throw new Error;
- return this.each(easeVarying(this._id, value));
-}
-
-;// CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/filter.js
-
-
-
-/* harmony default export */ function transition_filter(match) {
- if (typeof match !== "function") match = matcher(match);
-
- for (var groups = this._groups, m = groups.length, subgroups = new Array(m), j = 0; j < m; ++j) {
- for (var group = groups[j], n = group.length, subgroup = subgroups[j] = [], node, i = 0; i < n; ++i) {
- if ((node = group[i]) && match.call(node, node.__data__, i, group)) {
- subgroup.push(node);
- }
- }
- }
-
- return new Transition(subgroups, this._parents, this._name, this._id);
-}
-
-;// CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/merge.js
-
-
-/* harmony default export */ function transition_merge(transition) {
- if (transition._id !== this._id) throw new Error;
-
- for (var groups0 = this._groups, groups1 = transition._groups, m0 = groups0.length, m1 = groups1.length, m = Math.min(m0, m1), merges = new Array(m0), j = 0; j < m; ++j) {
- for (var group0 = groups0[j], group1 = groups1[j], n = group0.length, merge = merges[j] = new Array(n), node, i = 0; i < n; ++i) {
- if (node = group0[i] || group1[i]) {
- merge[i] = node;
- }
- }
- }
-
- for (; j < m0; ++j) {
- merges[j] = groups0[j];
- }
-
- return new Transition(merges, this._parents, this._name, this._id);
-}
-
-;// CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/on.js
-
-
-function start(name) {
- return (name + "").trim().split(/^|\s+/).every(function(t) {
- var i = t.indexOf(".");
- if (i >= 0) t = t.slice(0, i);
- return !t || t === "start";
- });
-}
-
-function onFunction(id, name, listener) {
- var on0, on1, sit = start(name) ? init : schedule_set;
- return function() {
- var schedule = sit(this, id),
- on = schedule.on;
-
- // If this node shared a dispatch with the previous node,
- // just assign the updated shared dispatch and we’re done!
- // Otherwise, copy-on-write.
- if (on !== on0) (on1 = (on0 = on).copy()).on(name, listener);
-
- schedule.on = on1;
- };
-}
-
-/* harmony default export */ function transition_on(name, listener) {
- var id = this._id;
-
- return arguments.length < 2
- ? schedule_get(this.node(), id).on.on(name)
- : this.each(onFunction(id, name, listener));
-}
-
-;// CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/remove.js
-function removeFunction(id) {
- return function() {
- var parent = this.parentNode;
- for (var i in this.__transition) if (+i !== id) return;
- if (parent) parent.removeChild(this);
- };
-}
-
-/* harmony default export */ function transition_remove() {
- return this.on("end.remove", removeFunction(this._id));
-}
-
-;// CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/select.js
-
-
-
-
-/* harmony default export */ function transition_select(select) {
- var name = this._name,
- id = this._id;
-
- if (typeof select !== "function") select = selector(select);
-
- for (var groups = this._groups, m = groups.length, subgroups = new Array(m), j = 0; j < m; ++j) {
- for (var group = groups[j], n = group.length, subgroup = subgroups[j] = new Array(n), node, subnode, i = 0; i < n; ++i) {
- if ((node = group[i]) && (subnode = select.call(node, node.__data__, i, group))) {
- if ("__data__" in node) subnode.__data__ = node.__data__;
- subgroup[i] = subnode;
- schedule(subgroup[i], name, id, i, subgroup, schedule_get(node, id));
- }
- }
- }
-
- return new Transition(subgroups, this._parents, name, id);
-}
-
-;// CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/selectAll.js
-
-
-
-
-/* harmony default export */ function transition_selectAll(select) {
- var name = this._name,
- id = this._id;
-
- if (typeof select !== "function") select = selectorAll(select);
-
- for (var groups = this._groups, m = groups.length, subgroups = [], parents = [], j = 0; j < m; ++j) {
- for (var group = groups[j], n = group.length, node, i = 0; i < n; ++i) {
- if (node = group[i]) {
- for (var children = select.call(node, node.__data__, i, group), child, inherit = schedule_get(node, id), k = 0, l = children.length; k < l; ++k) {
- if (child = children[k]) {
- schedule(child, name, id, k, children, inherit);
- }
- }
- subgroups.push(children);
- parents.push(node);
- }
- }
- }
-
- return new Transition(subgroups, parents, name, id);
-}
-
-;// CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/selection.js
-
-
-var selection_Selection = src_selection.prototype.constructor;
-
-/* harmony default export */ function transition_selection() {
- return new selection_Selection(this._groups, this._parents);
-}
-
-;// CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/style.js
-
-
-
-
-
-
-function styleNull(name, interpolate) {
- var string00,
- string10,
- interpolate0;
- return function() {
- var string0 = styleValue(this, name),
- string1 = (this.style.removeProperty(name), styleValue(this, name));
- return string0 === string1 ? null
- : string0 === string00 && string1 === string10 ? interpolate0
- : interpolate0 = interpolate(string00 = string0, string10 = string1);
- };
-}
-
-function style_styleRemove(name) {
- return function() {
- this.style.removeProperty(name);
- };
-}
-
-function style_styleConstant(name, interpolate, value1) {
- var string00,
- string1 = value1 + "",
- interpolate0;
- return function() {
- var string0 = styleValue(this, name);
- return string0 === string1 ? null
- : string0 === string00 ? interpolate0
- : interpolate0 = interpolate(string00 = string0, value1);
- };
-}
-
-function style_styleFunction(name, interpolate, value) {
- var string00,
- string10,
- interpolate0;
- return function() {
- var string0 = styleValue(this, name),
- value1 = value(this),
- string1 = value1 + "";
- if (value1 == null) string1 = value1 = (this.style.removeProperty(name), styleValue(this, name));
- return string0 === string1 ? null
- : string0 === string00 && string1 === string10 ? interpolate0
- : (string10 = string1, interpolate0 = interpolate(string00 = string0, value1));
- };
-}
-
-function styleMaybeRemove(id, name) {
- var on0, on1, listener0, key = "style." + name, event = "end." + key, remove;
- return function() {
- var schedule = schedule_set(this, id),
- on = schedule.on,
- listener = schedule.value[key] == null ? remove || (remove = style_styleRemove(name)) : undefined;
-
- // If this node shared a dispatch with the previous node,
- // just assign the updated shared dispatch and we’re done!
- // Otherwise, copy-on-write.
- if (on !== on0 || listener0 !== listener) (on1 = (on0 = on).copy()).on(event, listener0 = listener);
-
- schedule.on = on1;
- };
-}
-
-/* harmony default export */ function transition_style(name, value, priority) {
- var i = (name += "") === "transform" ? interpolateTransformCss : interpolate;
- return value == null ? this
- .styleTween(name, styleNull(name, i))
- .on("end.style." + name, style_styleRemove(name))
- : typeof value === "function" ? this
- .styleTween(name, style_styleFunction(name, i, tweenValue(this, "style." + name, value)))
- .each(styleMaybeRemove(this._id, name))
- : this
- .styleTween(name, style_styleConstant(name, i, value), priority)
- .on("end.style." + name, null);
-}
-
-;// CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/styleTween.js
-function styleInterpolate(name, i, priority) {
- return function(t) {
- this.style.setProperty(name, i.call(this, t), priority);
- };
-}
-
-function styleTween(name, value, priority) {
- var t, i0;
- function tween() {
- var i = value.apply(this, arguments);
- if (i !== i0) t = (i0 = i) && styleInterpolate(name, i, priority);
- return t;
- }
- tween._value = value;
- return tween;
-}
-
-/* harmony default export */ function transition_styleTween(name, value, priority) {
- var key = "style." + (name += "");
- if (arguments.length < 2) return (key = this.tween(key)) && key._value;
- if (value == null) return this.tween(key, null);
- if (typeof value !== "function") throw new Error;
- return this.tween(key, styleTween(name, value, priority == null ? "" : priority));
-}
-
-;// CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/text.js
-
-
-function text_textConstant(value) {
- return function() {
- this.textContent = value;
- };
-}
-
-function text_textFunction(value) {
- return function() {
- var value1 = value(this);
- this.textContent = value1 == null ? "" : value1;
- };
-}
-
-/* harmony default export */ function transition_text(value) {
- return this.tween("text", typeof value === "function"
- ? text_textFunction(tweenValue(this, "text", value))
- : text_textConstant(value == null ? "" : value + ""));
-}
-
-;// CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/textTween.js
-function textInterpolate(i) {
- return function(t) {
- this.textContent = i.call(this, t);
- };
-}
-
-function textTween(value) {
- var t0, i0;
- function tween() {
- var i = value.apply(this, arguments);
- if (i !== i0) t0 = (i0 = i) && textInterpolate(i);
- return t0;
- }
- tween._value = value;
- return tween;
-}
-
-/* harmony default export */ function transition_textTween(value) {
- var key = "text";
- if (arguments.length < 1) return (key = this.tween(key)) && key._value;
- if (value == null) return this.tween(key, null);
- if (typeof value !== "function") throw new Error;
- return this.tween(key, textTween(value));
-}
-
-;// CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/transition.js
-
-
-
-/* harmony default export */ function transition() {
- var name = this._name,
- id0 = this._id,
- id1 = newId();
-
- for (var groups = this._groups, m = groups.length, j = 0; j < m; ++j) {
- for (var group = groups[j], n = group.length, node, i = 0; i < n; ++i) {
- if (node = group[i]) {
- var inherit = schedule_get(node, id0);
- schedule(node, name, id1, i, group, {
- time: inherit.time + inherit.delay + inherit.duration,
- delay: 0,
- duration: inherit.duration,
- ease: inherit.ease
- });
- }
- }
- }
-
- return new Transition(groups, this._parents, name, id1);
-}
-
-;// CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/end.js
-
-
-/* harmony default export */ function end() {
- var on0, on1, that = this, id = that._id, size = that.size();
- return new Promise(function(resolve, reject) {
- var cancel = {value: reject},
- end = {value: function() { if (--size === 0) resolve(); }};
-
- that.each(function() {
- var schedule = schedule_set(this, id),
- on = schedule.on;
-
- // If this node shared a dispatch with the previous node,
- // just assign the updated shared dispatch and we’re done!
- // Otherwise, copy-on-write.
- if (on !== on0) {
- on1 = (on0 = on).copy();
- on1._.cancel.push(cancel);
- on1._.interrupt.push(cancel);
- on1._.end.push(end);
- }
-
- schedule.on = on1;
- });
-
- // The selection was empty, resolve end immediately
- if (size === 0) resolve();
- });
-}
-
-;// CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/index.js
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-var id = 0;
-
-function Transition(groups, parents, name, id) {
- this._groups = groups;
- this._parents = parents;
- this._name = name;
- this._id = id;
-}
-
-function transition_transition(name) {
- return src_selection().transition(name);
-}
-
-function newId() {
- return ++id;
-}
-
-var selection_prototype = src_selection.prototype;
-
-Transition.prototype = transition_transition.prototype = {
- constructor: Transition,
- select: transition_select,
- selectAll: transition_selectAll,
- selectChild: selection_prototype.selectChild,
- selectChildren: selection_prototype.selectChildren,
- filter: transition_filter,
- merge: transition_merge,
- selection: transition_selection,
- transition: transition,
- call: selection_prototype.call,
- nodes: selection_prototype.nodes,
- node: selection_prototype.node,
- size: selection_prototype.size,
- empty: selection_prototype.empty,
- each: selection_prototype.each,
- on: transition_on,
- attr: transition_attr,
- attrTween: transition_attrTween,
- style: transition_style,
- styleTween: transition_styleTween,
- text: transition_text,
- textTween: transition_textTween,
- remove: transition_remove,
- tween: tween,
- delay: delay,
- duration: duration,
- ease: ease,
- easeVarying: transition_easeVarying,
- end: end,
- [Symbol.iterator]: selection_prototype[Symbol.iterator]
-};
-
-;// CONCATENATED MODULE: ../node_modules/d3-ease/src/cubic.js
-function cubicIn(t) {
- return t * t * t;
-}
-
-function cubicOut(t) {
- return --t * t * t + 1;
-}
-
-function cubicInOut(t) {
- return ((t *= 2) <= 1 ? t * t * t : (t -= 2) * t * t + 2) / 2;
-}
-
-;// CONCATENATED MODULE: ../node_modules/d3-transition/src/selection/transition.js
-
-
-
-
-
-var defaultTiming = {
- time: null, // Set on use.
- delay: 0,
- duration: 250,
- ease: cubicInOut
-};
-
-function inherit(node, id) {
- var timing;
- while (!(timing = node.__transition) || !(timing = timing[id])) {
- if (!(node = node.parentNode)) {
- throw new Error(`transition ${id} not found`);
- }
- }
- return timing;
-}
-
-/* harmony default export */ function selection_transition(name) {
- var id,
- timing;
-
- if (name instanceof Transition) {
- id = name._id, name = name._name;
- } else {
- id = newId(), (timing = defaultTiming).time = now(), name = name == null ? null : name + "";
- }
-
- for (var groups = this._groups, m = groups.length, j = 0; j < m; ++j) {
- for (var group = groups[j], n = group.length, node, i = 0; i < n; ++i) {
- if (node = group[i]) {
- schedule(node, name, id, i, group, timing || inherit(node, id));
- }
- }
- }
-
- return new Transition(groups, this._parents, name, id);
-}
-
-;// CONCATENATED MODULE: ../node_modules/d3-transition/src/selection/index.js
-
-
-
-
-src_selection.prototype.interrupt = selection_interrupt;
-src_selection.prototype.transition = selection_transition;
-
-;// CONCATENATED MODULE: ../node_modules/d3-transition/src/index.js
-
-
-
-
-
-;// CONCATENATED MODULE: ./tooltip.js
-/* global event */
-
-
-
-
-
-
-function defaultLabel (d) {
- return d.data.name
-}
-
-function defaultFlamegraphTooltip () {
- var rootElement = src_select('body')
- var tooltip = null
- // Function to get HTML content from data.
- var html = defaultLabel
- // Function to get text content from data.
- var text = defaultLabel
- // Whether to use d3's .html() to set content, otherwise use .text().
- var contentIsHTML = false
-
- function tip () {
- tooltip = rootElement
- .append('div')
- .style('display', 'none')
- .style('position', 'absolute')
- .style('opacity', 0)
- .style('pointer-events', 'none')
- .attr('class', 'd3-flame-graph-tip')
- }
-
- tip.show = function (d) {
- tooltip
- .style('display', 'block')
- .style('left', event.pageX + 5 + 'px')
- .style('top', event.pageY + 5 + 'px')
- .transition()
- .duration(200)
- .style('opacity', 1)
- .style('pointer-events', 'all')
-
- if (contentIsHTML) {
- tooltip.html(html(d))
- } else {
- tooltip.text(text(d))
- }
-
- return tip
- }
-
- tip.hide = function () {
- tooltip
- .style('display', 'none')
- .transition()
- .duration(200)
- .style('opacity', 0)
- .style('pointer-events', 'none')
-
- return tip
- }
-
- /**
- * Gets/sets a function converting the d3 data into the tooltip's textContent.
- *
- * Cannot be combined with tip.html().
- */
- tip.text = function (_) {
- if (!arguments.length) return text
- text = _
- contentIsHTML = false
- return tip
- }
-
- /**
- * Gets/sets a function converting the d3 data into the tooltip's innerHTML.
- *
- * Cannot be combined with tip.text().
- *
- * @deprecated prefer tip.text().
- */
- tip.html = function (_) {
- if (!arguments.length) return html
- html = _
- contentIsHTML = true
- return tip
- }
-
- tip.destroy = function () {
- tooltip.remove()
- }
-
- return tip
-}
-
-/******/ return __webpack_exports__;
-/******/ })()
-;
-});
\ No newline at end of file
diff --git a/development/charts/flamegraph/lib/d3-flamegraph.css b/development/charts/flamegraph/lib/d3-flamegraph.css
deleted file mode 100644
index fa6f345ff7a9..000000000000
--- a/development/charts/flamegraph/lib/d3-flamegraph.css
+++ /dev/null
@@ -1,46 +0,0 @@
-.d3-flame-graph rect {
- stroke: #EEEEEE;
- fill-opacity: .8;
-}
-
-.d3-flame-graph rect:hover {
- stroke: #474747;
- stroke-width: 0.5;
- cursor: pointer;
-}
-
-.d3-flame-graph-label {
- pointer-events: none;
- white-space: nowrap;
- text-overflow: ellipsis;
- overflow: hidden;
- font-size: 12px;
- font-family: Verdana;
- margin-left: 4px;
- margin-right: 4px;
- line-height: 1.5;
- padding: 0 0 0;
- font-weight: 400;
- color: black;
- text-align: left;
-}
-
-.d3-flame-graph .fade {
- opacity: 0.6 !important;
-}
-
-.d3-flame-graph .title {
- font-size: 20px;
- font-family: Verdana;
-}
-
-.d3-flame-graph-tip {
- background-color: black;
- border: none;
- border-radius: 3px;
- padding: 5px 10px 5px 10px;
- min-width: 250px;
- text-align: left;
- color: white;
- z-index: 10;
-}
\ No newline at end of file
diff --git a/development/charts/flamegraph/lib/d3-flamegraph.js b/development/charts/flamegraph/lib/d3-flamegraph.js
deleted file mode 100644
index eabb2c44972c..000000000000
--- a/development/charts/flamegraph/lib/d3-flamegraph.js
+++ /dev/null
@@ -1,5719 +0,0 @@
-(function webpackUniversalModuleDefinition(root, factory) {
- if (typeof exports === 'object' && typeof module === 'object')
- module.exports = factory();
- else if (typeof define === 'function' && define.amd) define([], factory);
- else if (typeof exports === 'object') exports['flamegraph'] = factory();
- else root['flamegraph'] = factory();
-})(self, function () {
- return /******/ (() => {
- // webpackBootstrap
- /******/ 'use strict'; // The require scope
- /******/ /******/ var __webpack_require__ = {}; /* webpack/runtime/define property getters */
- /******/
- /************************************************************************/
- /******/ /******/ (() => {
- /******/ // define getter functions for harmony exports
- /******/ __webpack_require__.d = (exports, definition) => {
- /******/ for (var key in definition) {
- /******/ if (
- __webpack_require__.o(definition, key) &&
- !__webpack_require__.o(exports, key)
- ) {
- /******/ Object.defineProperty(exports, key, {
- enumerable: true,
- get: definition[key],
- });
- /******/
- }
- /******/
- }
- /******/
- };
- /******/
- })(); /* webpack/runtime/hasOwnProperty shorthand */
- /******/
- /******/ /******/ (() => {
- /******/ __webpack_require__.o = (obj, prop) =>
- Object.prototype.hasOwnProperty.call(obj, prop);
- /******/
- })();
- /******/
- /************************************************************************/
- var __webpack_exports__ = {};
-
- // EXPORTS
- __webpack_require__.d(__webpack_exports__, {
- default: () => /* binding */ flamegraph,
- }); // CONCATENATED MODULE: ../node_modules/d3-selection/src/selector.js
-
- function none() {}
-
- /* harmony default export */ function selector(selector) {
- return selector == null
- ? none
- : function () {
- return this.querySelector(selector);
- };
- } // CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/select.js
-
- /* harmony default export */ function selection_select(select) {
- if (typeof select !== 'function') select = selector(select);
-
- for (
- var groups = this._groups,
- m = groups.length,
- subgroups = new Array(m),
- j = 0;
- j < m;
- ++j
- ) {
- for (
- var group = groups[j],
- n = group.length,
- subgroup = (subgroups[j] = new Array(n)),
- node,
- subnode,
- i = 0;
- i < n;
- ++i
- ) {
- if (
- (node = group[i]) &&
- (subnode = select.call(node, node.__data__, i, group))
- ) {
- if ('__data__' in node) subnode.__data__ = node.__data__;
- subgroup[i] = subnode;
- }
- }
- }
-
- return new Selection(subgroups, this._parents);
- } // CONCATENATED MODULE: ../node_modules/d3-selection/src/array.js
-
- // Given something array like (or null), returns something that is strictly an
- // array. This is used to ensure that array-like objects passed to d3.selectAll
- // or selection.selectAll are converted into proper arrays when creating a
- // selection; we don’t ever want to create a selection backed by a live
- // HTMLCollection or NodeList. However, note that selection.selectAll will use a
- // static NodeList as a group, since it safely derived from querySelectorAll.
- function array(x) {
- return x == null ? [] : Array.isArray(x) ? x : Array.from(x);
- } // CONCATENATED MODULE: ../node_modules/d3-selection/src/selectorAll.js
-
- function empty() {
- return [];
- }
-
- /* harmony default export */ function selectorAll(selector) {
- return selector == null
- ? empty
- : function () {
- return this.querySelectorAll(selector);
- };
- } // CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/selectAll.js
-
- function arrayAll(select) {
- return function () {
- return array(select.apply(this, arguments));
- };
- }
-
- /* harmony default export */ function selectAll(select) {
- if (typeof select === 'function') select = arrayAll(select);
- else select = selectorAll(select);
-
- for (
- var groups = this._groups,
- m = groups.length,
- subgroups = [],
- parents = [],
- j = 0;
- j < m;
- ++j
- ) {
- for (var group = groups[j], n = group.length, node, i = 0; i < n; ++i) {
- if ((node = group[i])) {
- subgroups.push(select.call(node, node.__data__, i, group));
- parents.push(node);
- }
- }
- }
-
- return new Selection(subgroups, parents);
- } // CONCATENATED MODULE: ../node_modules/d3-selection/src/matcher.js
-
- /* harmony default export */ function matcher(selector) {
- return function () {
- return this.matches(selector);
- };
- }
-
- function childMatcher(selector) {
- return function (node) {
- return node.matches(selector);
- };
- } // CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/selectChild.js
-
- var find = Array.prototype.find;
-
- function childFind(match) {
- return function () {
- return find.call(this.children, match);
- };
- }
-
- function childFirst() {
- return this.firstElementChild;
- }
-
- /* harmony default export */ function selectChild(match) {
- return this.select(
- match == null
- ? childFirst
- : childFind(
- typeof match === 'function' ? match : childMatcher(match),
- ),
- );
- } // CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/selectChildren.js
-
- var filter = Array.prototype.filter;
-
- function children() {
- return Array.from(this.children);
- }
-
- function childrenFilter(match) {
- return function () {
- return filter.call(this.children, match);
- };
- }
-
- /* harmony default export */ function selectChildren(match) {
- return this.selectAll(
- match == null
- ? children
- : childrenFilter(
- typeof match === 'function' ? match : childMatcher(match),
- ),
- );
- } // CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/filter.js
-
- /* harmony default export */ function selection_filter(match) {
- if (typeof match !== 'function') match = matcher(match);
-
- for (
- var groups = this._groups,
- m = groups.length,
- subgroups = new Array(m),
- j = 0;
- j < m;
- ++j
- ) {
- for (
- var group = groups[j],
- n = group.length,
- subgroup = (subgroups[j] = []),
- node,
- i = 0;
- i < n;
- ++i
- ) {
- if ((node = group[i]) && match.call(node, node.__data__, i, group)) {
- subgroup.push(node);
- }
- }
- }
-
- return new Selection(subgroups, this._parents);
- } // CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/sparse.js
-
- /* harmony default export */ function sparse(update) {
- return new Array(update.length);
- } // CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/enter.js
-
- /* harmony default export */ function enter() {
- return new Selection(
- this._enter || this._groups.map(sparse),
- this._parents,
- );
- }
-
- function EnterNode(parent, datum) {
- this.ownerDocument = parent.ownerDocument;
- this.namespaceURI = parent.namespaceURI;
- this._next = null;
- this._parent = parent;
- this.__data__ = datum;
- }
-
- EnterNode.prototype = {
- constructor: EnterNode,
- appendChild: function (child) {
- return this._parent.insertBefore(child, this._next);
- },
- insertBefore: function (child, next) {
- return this._parent.insertBefore(child, next);
- },
- querySelector: function (selector) {
- return this._parent.querySelector(selector);
- },
- querySelectorAll: function (selector) {
- return this._parent.querySelectorAll(selector);
- },
- }; // CONCATENATED MODULE: ../node_modules/d3-selection/src/constant.js
-
- /* harmony default export */ function src_constant(x) {
- return function () {
- return x;
- };
- } // CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/data.js
-
- function bindIndex(parent, group, enter, update, exit, data) {
- var i = 0,
- node,
- groupLength = group.length,
- dataLength = data.length;
-
- // Put any non-null nodes that fit into update.
- // Put any null nodes into enter.
- // Put any remaining data into enter.
- for (; i < dataLength; ++i) {
- if ((node = group[i])) {
- node.__data__ = data[i];
- update[i] = node;
- } else {
- enter[i] = new EnterNode(parent, data[i]);
- }
- }
-
- // Put any non-null nodes that don’t fit into exit.
- for (; i < groupLength; ++i) {
- if ((node = group[i])) {
- exit[i] = node;
- }
- }
- }
-
- function bindKey(parent, group, enter, update, exit, data, key) {
- var i,
- node,
- nodeByKeyValue = new Map(),
- groupLength = group.length,
- dataLength = data.length,
- keyValues = new Array(groupLength),
- keyValue;
-
- // Compute the key for each node.
- // If multiple nodes have the same key, the duplicates are added to exit.
- for (i = 0; i < groupLength; ++i) {
- if ((node = group[i])) {
- keyValues[i] = keyValue =
- key.call(node, node.__data__, i, group) + '';
- if (nodeByKeyValue.has(keyValue)) {
- exit[i] = node;
- } else {
- nodeByKeyValue.set(keyValue, node);
- }
- }
- }
-
- // Compute the key for each datum.
- // If there a node associated with this key, join and add it to update.
- // If there is not (or the key is a duplicate), add it to enter.
- for (i = 0; i < dataLength; ++i) {
- keyValue = key.call(parent, data[i], i, data) + '';
- if ((node = nodeByKeyValue.get(keyValue))) {
- update[i] = node;
- node.__data__ = data[i];
- nodeByKeyValue.delete(keyValue);
- } else {
- enter[i] = new EnterNode(parent, data[i]);
- }
- }
-
- // Add any remaining nodes that were not bound to data to exit.
- for (i = 0; i < groupLength; ++i) {
- if ((node = group[i]) && nodeByKeyValue.get(keyValues[i]) === node) {
- exit[i] = node;
- }
- }
- }
-
- function datum(node) {
- return node.__data__;
- }
-
- /* harmony default export */ function data(value, key) {
- if (!arguments.length) return Array.from(this, datum);
-
- var bind = key ? bindKey : bindIndex,
- parents = this._parents,
- groups = this._groups;
-
- if (typeof value !== 'function') value = src_constant(value);
-
- for (
- var m = groups.length,
- update = new Array(m),
- enter = new Array(m),
- exit = new Array(m),
- j = 0;
- j < m;
- ++j
- ) {
- var parent = parents[j],
- group = groups[j],
- groupLength = group.length,
- data = arraylike(
- value.call(parent, parent && parent.__data__, j, parents),
- ),
- dataLength = data.length,
- enterGroup = (enter[j] = new Array(dataLength)),
- updateGroup = (update[j] = new Array(dataLength)),
- exitGroup = (exit[j] = new Array(groupLength));
-
- bind(parent, group, enterGroup, updateGroup, exitGroup, data, key);
-
- // Now connect the enter nodes to their following update node, such that
- // appendChild can insert the materialized enter node before this node,
- // rather than at the end of the parent node.
- for (var i0 = 0, i1 = 0, previous, next; i0 < dataLength; ++i0) {
- if ((previous = enterGroup[i0])) {
- if (i0 >= i1) i1 = i0 + 1;
- while (!(next = updateGroup[i1]) && ++i1 < dataLength);
- previous._next = next || null;
- }
- }
- }
-
- update = new Selection(update, parents);
- update._enter = enter;
- update._exit = exit;
- return update;
- }
-
- // Given some data, this returns an array-like view of it: an object that
- // exposes a length property and allows numeric indexing. Note that unlike
- // selectAll, this isn’t worried about “live” collections because the resulting
- // array will only be used briefly while data is being bound. (It is possible to
- // cause the data to change while iterating by using a key function, but please
- // don’t; we’d rather avoid a gratuitous copy.)
- function arraylike(data) {
- return typeof data === 'object' && 'length' in data
- ? data // Array, TypedArray, NodeList, array-like
- : Array.from(data); // Map, Set, iterable, string, or anything else
- } // CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/exit.js
-
- /* harmony default export */ function exit() {
- return new Selection(
- this._exit || this._groups.map(sparse),
- this._parents,
- );
- } // CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/join.js
-
- /* harmony default export */ function join(onenter, onupdate, onexit) {
- var enter = this.enter(),
- update = this,
- exit = this.exit();
- if (typeof onenter === 'function') {
- enter = onenter(enter);
- if (enter) enter = enter.selection();
- } else {
- enter = enter.append(onenter + '');
- }
- if (onupdate != null) {
- update = onupdate(update);
- if (update) update = update.selection();
- }
- if (onexit == null) exit.remove();
- else onexit(exit);
- return enter && update ? enter.merge(update).order() : update;
- } // CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/merge.js
-
- /* harmony default export */ function merge(context) {
- var selection = context.selection ? context.selection() : context;
-
- for (
- var groups0 = this._groups,
- groups1 = selection._groups,
- m0 = groups0.length,
- m1 = groups1.length,
- m = Math.min(m0, m1),
- merges = new Array(m0),
- j = 0;
- j < m;
- ++j
- ) {
- for (
- var group0 = groups0[j],
- group1 = groups1[j],
- n = group0.length,
- merge = (merges[j] = new Array(n)),
- node,
- i = 0;
- i < n;
- ++i
- ) {
- if ((node = group0[i] || group1[i])) {
- merge[i] = node;
- }
- }
- }
-
- for (; j < m0; ++j) {
- merges[j] = groups0[j];
- }
-
- return new Selection(merges, this._parents);
- } // CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/order.js
-
- /* harmony default export */ function order() {
- for (var groups = this._groups, j = -1, m = groups.length; ++j < m; ) {
- for (
- var group = groups[j], i = group.length - 1, next = group[i], node;
- --i >= 0;
-
- ) {
- if ((node = group[i])) {
- if (next && node.compareDocumentPosition(next) ^ 4)
- next.parentNode.insertBefore(node, next);
- next = node;
- }
- }
- }
-
- return this;
- } // CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/sort.js
-
- /* harmony default export */ function sort(compare) {
- if (!compare) compare = ascending;
-
- function compareNode(a, b) {
- return a && b ? compare(a.__data__, b.__data__) : !a - !b;
- }
-
- for (
- var groups = this._groups,
- m = groups.length,
- sortgroups = new Array(m),
- j = 0;
- j < m;
- ++j
- ) {
- for (
- var group = groups[j],
- n = group.length,
- sortgroup = (sortgroups[j] = new Array(n)),
- node,
- i = 0;
- i < n;
- ++i
- ) {
- if ((node = group[i])) {
- sortgroup[i] = node;
- }
- }
- sortgroup.sort(compareNode);
- }
-
- return new Selection(sortgroups, this._parents).order();
- }
-
- function ascending(a, b) {
- return a < b ? -1 : a > b ? 1 : a >= b ? 0 : NaN;
- } // CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/call.js
-
- /* harmony default export */ function call() {
- var callback = arguments[0];
- arguments[0] = this;
- callback.apply(null, arguments);
- return this;
- } // CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/nodes.js
-
- /* harmony default export */ function nodes() {
- return Array.from(this);
- } // CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/node.js
-
- /* harmony default export */ function node() {
- for (var groups = this._groups, j = 0, m = groups.length; j < m; ++j) {
- for (var group = groups[j], i = 0, n = group.length; i < n; ++i) {
- var node = group[i];
- if (node) return node;
- }
- }
-
- return null;
- } // CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/size.js
-
- /* harmony default export */ function size() {
- let size = 0;
- for (const node of this) ++size; // eslint-disable-line no-unused-vars
- return size;
- } // CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/empty.js
-
- /* harmony default export */ function selection_empty() {
- return !this.node();
- } // CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/each.js
-
- /* harmony default export */ function each(callback) {
- for (var groups = this._groups, j = 0, m = groups.length; j < m; ++j) {
- for (var group = groups[j], i = 0, n = group.length, node; i < n; ++i) {
- if ((node = group[i])) callback.call(node, node.__data__, i, group);
- }
- }
-
- return this;
- } // CONCATENATED MODULE: ../node_modules/d3-selection/src/namespaces.js
-
- var xhtml = 'http://www.w3.org/1999/xhtml';
-
- /* harmony default export */ const namespaces = {
- svg: 'http://www.w3.org/2000/svg',
- xhtml: xhtml,
- xlink: 'http://www.w3.org/1999/xlink',
- xml: 'http://www.w3.org/XML/1998/namespace',
- xmlns: 'http://www.w3.org/2000/xmlns/',
- }; // CONCATENATED MODULE: ../node_modules/d3-selection/src/namespace.js
-
- /* harmony default export */ function namespace(name) {
- var prefix = (name += ''),
- i = prefix.indexOf(':');
- if (i >= 0 && (prefix = name.slice(0, i)) !== 'xmlns')
- name = name.slice(i + 1);
- return namespaces.hasOwnProperty(prefix)
- ? { space: namespaces[prefix], local: name }
- : name; // eslint-disable-line no-prototype-builtins
- } // CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/attr.js
-
- function attrRemove(name) {
- return function () {
- this.removeAttribute(name);
- };
- }
-
- function attrRemoveNS(fullname) {
- return function () {
- this.removeAttributeNS(fullname.space, fullname.local);
- };
- }
-
- function attrConstant(name, value) {
- return function () {
- this.setAttribute(name, value);
- };
- }
-
- function attrConstantNS(fullname, value) {
- return function () {
- this.setAttributeNS(fullname.space, fullname.local, value);
- };
- }
-
- function attrFunction(name, value) {
- return function () {
- var v = value.apply(this, arguments);
- if (v == null) this.removeAttribute(name);
- else this.setAttribute(name, v);
- };
- }
-
- function attrFunctionNS(fullname, value) {
- return function () {
- var v = value.apply(this, arguments);
- if (v == null) this.removeAttributeNS(fullname.space, fullname.local);
- else this.setAttributeNS(fullname.space, fullname.local, v);
- };
- }
-
- /* harmony default export */ function attr(name, value) {
- var fullname = namespace(name);
-
- if (arguments.length < 2) {
- var node = this.node();
- return fullname.local
- ? node.getAttributeNS(fullname.space, fullname.local)
- : node.getAttribute(fullname);
- }
-
- return this.each(
- (value == null
- ? fullname.local
- ? attrRemoveNS
- : attrRemove
- : typeof value === 'function'
- ? fullname.local
- ? attrFunctionNS
- : attrFunction
- : fullname.local
- ? attrConstantNS
- : attrConstant)(fullname, value),
- );
- } // CONCATENATED MODULE: ../node_modules/d3-selection/src/window.js
-
- /* harmony default export */ function src_window(node) {
- return (
- (node.ownerDocument && node.ownerDocument.defaultView) || // node is a Node
- (node.document && node) || // node is a Window
- node.defaultView
- ); // node is a Document
- } // CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/style.js
-
- function styleRemove(name) {
- return function () {
- this.style.removeProperty(name);
- };
- }
-
- function styleConstant(name, value, priority) {
- return function () {
- this.style.setProperty(name, value, priority);
- };
- }
-
- function styleFunction(name, value, priority) {
- return function () {
- var v = value.apply(this, arguments);
- if (v == null) this.style.removeProperty(name);
- else this.style.setProperty(name, v, priority);
- };
- }
-
- /* harmony default export */ function style(name, value, priority) {
- return arguments.length > 1
- ? this.each(
- (value == null
- ? styleRemove
- : typeof value === 'function'
- ? styleFunction
- : styleConstant)(name, value, priority == null ? '' : priority),
- )
- : styleValue(this.node(), name);
- }
-
- function styleValue(node, name) {
- return (
- node.style.getPropertyValue(name) ||
- src_window(node).getComputedStyle(node, null).getPropertyValue(name)
- );
- } // CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/property.js
-
- function propertyRemove(name) {
- return function () {
- delete this[name];
- };
- }
-
- function propertyConstant(name, value) {
- return function () {
- this[name] = value;
- };
- }
-
- function propertyFunction(name, value) {
- return function () {
- var v = value.apply(this, arguments);
- if (v == null) delete this[name];
- else this[name] = v;
- };
- }
-
- /* harmony default export */ function property(name, value) {
- return arguments.length > 1
- ? this.each(
- (value == null
- ? propertyRemove
- : typeof value === 'function'
- ? propertyFunction
- : propertyConstant)(name, value),
- )
- : this.node()[name];
- } // CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/classed.js
-
- function classArray(string) {
- return string.trim().split(/^|\s+/);
- }
-
- function classList(node) {
- return node.classList || new ClassList(node);
- }
-
- function ClassList(node) {
- this._node = node;
- this._names = classArray(node.getAttribute('class') || '');
- }
-
- ClassList.prototype = {
- add: function (name) {
- var i = this._names.indexOf(name);
- if (i < 0) {
- this._names.push(name);
- this._node.setAttribute('class', this._names.join(' '));
- }
- },
- remove: function (name) {
- var i = this._names.indexOf(name);
- if (i >= 0) {
- this._names.splice(i, 1);
- this._node.setAttribute('class', this._names.join(' '));
- }
- },
- contains: function (name) {
- return this._names.indexOf(name) >= 0;
- },
- };
-
- function classedAdd(node, names) {
- var list = classList(node),
- i = -1,
- n = names.length;
- while (++i < n) list.add(names[i]);
- }
-
- function classedRemove(node, names) {
- var list = classList(node),
- i = -1,
- n = names.length;
- while (++i < n) list.remove(names[i]);
- }
-
- function classedTrue(names) {
- return function () {
- classedAdd(this, names);
- };
- }
-
- function classedFalse(names) {
- return function () {
- classedRemove(this, names);
- };
- }
-
- function classedFunction(names, value) {
- return function () {
- (value.apply(this, arguments) ? classedAdd : classedRemove)(
- this,
- names,
- );
- };
- }
-
- /* harmony default export */ function classed(name, value) {
- var names = classArray(name + '');
-
- if (arguments.length < 2) {
- var list = classList(this.node()),
- i = -1,
- n = names.length;
- while (++i < n) if (!list.contains(names[i])) return false;
- return true;
- }
-
- return this.each(
- (typeof value === 'function'
- ? classedFunction
- : value
- ? classedTrue
- : classedFalse)(names, value),
- );
- } // CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/text.js
-
- function textRemove() {
- this.textContent = '';
- }
-
- function textConstant(value) {
- return function () {
- this.textContent = value;
- };
- }
-
- function textFunction(value) {
- return function () {
- var v = value.apply(this, arguments);
- this.textContent = v == null ? '' : v;
- };
- }
-
- /* harmony default export */ function selection_text(value) {
- return arguments.length
- ? this.each(
- value == null
- ? textRemove
- : (typeof value === 'function' ? textFunction : textConstant)(
- value,
- ),
- )
- : this.node().textContent;
- } // CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/html.js
-
- function htmlRemove() {
- this.innerHTML = '';
- }
-
- function htmlConstant(value) {
- return function () {
- this.innerHTML = value;
- };
- }
-
- function htmlFunction(value) {
- return function () {
- var v = value.apply(this, arguments);
- this.innerHTML = v == null ? '' : v;
- };
- }
-
- /* harmony default export */ function html(value) {
- return arguments.length
- ? this.each(
- value == null
- ? htmlRemove
- : (typeof value === 'function' ? htmlFunction : htmlConstant)(
- value,
- ),
- )
- : this.node().innerHTML;
- } // CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/raise.js
-
- function raise() {
- if (this.nextSibling) this.parentNode.appendChild(this);
- }
-
- /* harmony default export */ function selection_raise() {
- return this.each(raise);
- } // CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/lower.js
-
- function lower() {
- if (this.previousSibling)
- this.parentNode.insertBefore(this, this.parentNode.firstChild);
- }
-
- /* harmony default export */ function selection_lower() {
- return this.each(lower);
- } // CONCATENATED MODULE: ../node_modules/d3-selection/src/creator.js
-
- function creatorInherit(name) {
- return function () {
- var document = this.ownerDocument,
- uri = this.namespaceURI;
- return uri === xhtml && document.documentElement.namespaceURI === xhtml
- ? document.createElement(name)
- : document.createElementNS(uri, name);
- };
- }
-
- function creatorFixed(fullname) {
- return function () {
- return this.ownerDocument.createElementNS(
- fullname.space,
- fullname.local,
- );
- };
- }
-
- /* harmony default export */ function creator(name) {
- var fullname = namespace(name);
- return (fullname.local ? creatorFixed : creatorInherit)(fullname);
- } // CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/append.js
-
- /* harmony default export */ function append(name) {
- var create = typeof name === 'function' ? name : creator(name);
- return this.select(function () {
- return this.appendChild(create.apply(this, arguments));
- });
- } // CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/insert.js
-
- function constantNull() {
- return null;
- }
-
- /* harmony default export */ function insert(name, before) {
- var create = typeof name === 'function' ? name : creator(name),
- select =
- before == null
- ? constantNull
- : typeof before === 'function'
- ? before
- : selector(before);
- return this.select(function () {
- return this.insertBefore(
- create.apply(this, arguments),
- select.apply(this, arguments) || null,
- );
- });
- } // CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/remove.js
-
- function remove() {
- var parent = this.parentNode;
- if (parent) parent.removeChild(this);
- }
-
- /* harmony default export */ function selection_remove() {
- return this.each(remove);
- } // CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/clone.js
-
- function selection_cloneShallow() {
- var clone = this.cloneNode(false),
- parent = this.parentNode;
- return parent ? parent.insertBefore(clone, this.nextSibling) : clone;
- }
-
- function selection_cloneDeep() {
- var clone = this.cloneNode(true),
- parent = this.parentNode;
- return parent ? parent.insertBefore(clone, this.nextSibling) : clone;
- }
-
- /* harmony default export */ function clone(deep) {
- return this.select(deep ? selection_cloneDeep : selection_cloneShallow);
- } // CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/datum.js
-
- /* harmony default export */ function selection_datum(value) {
- return arguments.length
- ? this.property('__data__', value)
- : this.node().__data__;
- } // CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/on.js
-
- function contextListener(listener) {
- return function (event) {
- listener.call(this, event, this.__data__);
- };
- }
-
- function parseTypenames(typenames) {
- return typenames
- .trim()
- .split(/^|\s+/)
- .map(function (t) {
- var name = '',
- i = t.indexOf('.');
- if (i >= 0) (name = t.slice(i + 1)), (t = t.slice(0, i));
- return { type: t, name: name };
- });
- }
-
- function onRemove(typename) {
- return function () {
- var on = this.__on;
- if (!on) return;
- for (var j = 0, i = -1, m = on.length, o; j < m; ++j) {
- if (
- ((o = on[j]),
- (!typename.type || o.type === typename.type) &&
- o.name === typename.name)
- ) {
- this.removeEventListener(o.type, o.listener, o.options);
- } else {
- on[++i] = o;
- }
- }
- if (++i) on.length = i;
- else delete this.__on;
- };
- }
-
- function onAdd(typename, value, options) {
- return function () {
- var on = this.__on,
- o,
- listener = contextListener(value);
- if (on)
- for (var j = 0, m = on.length; j < m; ++j) {
- if (
- (o = on[j]).type === typename.type &&
- o.name === typename.name
- ) {
- this.removeEventListener(o.type, o.listener, o.options);
- this.addEventListener(
- o.type,
- (o.listener = listener),
- (o.options = options),
- );
- o.value = value;
- return;
- }
- }
- this.addEventListener(typename.type, listener, options);
- o = {
- type: typename.type,
- name: typename.name,
- value: value,
- listener: listener,
- options: options,
- };
- if (!on) this.__on = [o];
- else on.push(o);
- };
- }
-
- /* harmony default export */ function on(typename, value, options) {
- var typenames = parseTypenames(typename + ''),
- i,
- n = typenames.length,
- t;
-
- if (arguments.length < 2) {
- var on = this.node().__on;
- if (on)
- for (var j = 0, m = on.length, o; j < m; ++j) {
- for (i = 0, o = on[j]; i < n; ++i) {
- if ((t = typenames[i]).type === o.type && t.name === o.name) {
- return o.value;
- }
- }
- }
- return;
- }
-
- on = value ? onAdd : onRemove;
- for (i = 0; i < n; ++i) this.each(on(typenames[i], value, options));
- return this;
- } // CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/dispatch.js
-
- function dispatchEvent(node, type, params) {
- var window = src_window(node),
- event = window.CustomEvent;
-
- if (typeof event === 'function') {
- event = new event(type, params);
- } else {
- event = window.document.createEvent('Event');
- if (params)
- event.initEvent(type, params.bubbles, params.cancelable),
- (event.detail = params.detail);
- else event.initEvent(type, false, false);
- }
-
- node.dispatchEvent(event);
- }
-
- function dispatchConstant(type, params) {
- return function () {
- return dispatchEvent(this, type, params);
- };
- }
-
- function dispatchFunction(type, params) {
- return function () {
- return dispatchEvent(this, type, params.apply(this, arguments));
- };
- }
-
- /* harmony default export */ function dispatch(type, params) {
- return this.each(
- (typeof params === 'function' ? dispatchFunction : dispatchConstant)(
- type,
- params,
- ),
- );
- } // CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/iterator.js
-
- /* harmony default export */ function* iterator() {
- for (var groups = this._groups, j = 0, m = groups.length; j < m; ++j) {
- for (var group = groups[j], i = 0, n = group.length, node; i < n; ++i) {
- if ((node = group[i])) yield node;
- }
- }
- } // CONCATENATED MODULE: ../node_modules/d3-selection/src/selection/index.js
-
- var root = [null];
-
- function Selection(groups, parents) {
- this._groups = groups;
- this._parents = parents;
- }
-
- function selection() {
- return new Selection([[document.documentElement]], root);
- }
-
- function selection_selection() {
- return this;
- }
-
- Selection.prototype = selection.prototype = {
- constructor: Selection,
- select: selection_select,
- selectAll: selectAll,
- selectChild: selectChild,
- selectChildren: selectChildren,
- filter: selection_filter,
- data: data,
- enter: enter,
- exit: exit,
- join: join,
- merge: merge,
- selection: selection_selection,
- order: order,
- sort: sort,
- call: call,
- nodes: nodes,
- node: node,
- size: size,
- empty: selection_empty,
- each: each,
- attr: attr,
- style: style,
- property: property,
- classed: classed,
- text: selection_text,
- html: html,
- raise: selection_raise,
- lower: selection_lower,
- append: append,
- insert: insert,
- remove: selection_remove,
- clone: clone,
- datum: selection_datum,
- on: on,
- dispatch: dispatch,
- [Symbol.iterator]: iterator,
- };
-
- /* harmony default export */ const src_selection = selection; // CONCATENATED MODULE: ../node_modules/d3-selection/src/select.js
-
- /* harmony default export */ function src_select(selector) {
- return typeof selector === 'string'
- ? new Selection(
- [[document.querySelector(selector)]],
- [document.documentElement],
- )
- : new Selection([[selector]], root);
- } // CONCATENATED MODULE: ../node_modules/d3-format/src/formatDecimal.js
-
- /* harmony default export */ function formatDecimal(x) {
- return Math.abs((x = Math.round(x))) >= 1e21
- ? x.toLocaleString('en').replace(/,/g, '')
- : x.toString(10);
- }
-
- // Computes the decimal coefficient and exponent of the specified number x with
- // significant digits p, where x is positive and p is in [1, 21] or undefined.
- // For example, formatDecimalParts(1.23) returns ["123", 0].
- function formatDecimalParts(x, p) {
- if (
- (i = (x = p ? x.toExponential(p - 1) : x.toExponential()).indexOf(
- 'e',
- )) < 0
- )
- return null; // NaN, ±Infinity
- var i,
- coefficient = x.slice(0, i);
-
- // The string returned by toExponential either has the form \d\.\d+e[-+]\d+
- // (e.g., 1.2e+3) or the form \de[-+]\d+ (e.g., 1e+3).
- return [
- coefficient.length > 1
- ? coefficient[0] + coefficient.slice(2)
- : coefficient,
- +x.slice(i + 1),
- ];
- } // CONCATENATED MODULE: ../node_modules/d3-format/src/exponent.js
-
- /* harmony default export */ function exponent(x) {
- return (x = formatDecimalParts(Math.abs(x))), x ? x[1] : NaN;
- } // CONCATENATED MODULE: ../node_modules/d3-format/src/formatGroup.js
-
- /* harmony default export */ function formatGroup(grouping, thousands) {
- return function (value, width) {
- var i = value.length,
- t = [],
- j = 0,
- g = grouping[0],
- length = 0;
-
- while (i > 0 && g > 0) {
- if (length + g + 1 > width) g = Math.max(1, width - length);
- t.push(value.substring((i -= g), i + g));
- if ((length += g + 1) > width) break;
- g = grouping[(j = (j + 1) % grouping.length)];
- }
-
- return t.reverse().join(thousands);
- };
- } // CONCATENATED MODULE: ../node_modules/d3-format/src/formatNumerals.js
-
- /* harmony default export */ function formatNumerals(numerals) {
- return function (value) {
- return value.replace(/[0-9]/g, function (i) {
- return numerals[+i];
- });
- };
- } // CONCATENATED MODULE: ../node_modules/d3-format/src/formatSpecifier.js
-
- // [[fill]align][sign][symbol][0][width][,][.precision][~][type]
- var re = /^(?:(.)?([<>=^]))?([+\-( ])?([$#])?(0)?(\d+)?(,)?(\.\d+)?(~)?([a-z%])?$/i;
-
- function formatSpecifier(specifier) {
- if (!(match = re.exec(specifier)))
- throw new Error('invalid format: ' + specifier);
- var match;
- return new FormatSpecifier({
- fill: match[1],
- align: match[2],
- sign: match[3],
- symbol: match[4],
- zero: match[5],
- width: match[6],
- comma: match[7],
- precision: match[8] && match[8].slice(1),
- trim: match[9],
- type: match[10],
- });
- }
-
- formatSpecifier.prototype = FormatSpecifier.prototype; // instanceof
-
- function FormatSpecifier(specifier) {
- this.fill = specifier.fill === undefined ? ' ' : specifier.fill + '';
- this.align = specifier.align === undefined ? '>' : specifier.align + '';
- this.sign = specifier.sign === undefined ? '-' : specifier.sign + '';
- this.symbol = specifier.symbol === undefined ? '' : specifier.symbol + '';
- this.zero = !!specifier.zero;
- this.width = specifier.width === undefined ? undefined : +specifier.width;
- this.comma = !!specifier.comma;
- this.precision =
- specifier.precision === undefined ? undefined : +specifier.precision;
- this.trim = !!specifier.trim;
- this.type = specifier.type === undefined ? '' : specifier.type + '';
- }
-
- FormatSpecifier.prototype.toString = function () {
- return (
- this.fill +
- this.align +
- this.sign +
- this.symbol +
- (this.zero ? '0' : '') +
- (this.width === undefined ? '' : Math.max(1, this.width | 0)) +
- (this.comma ? ',' : '') +
- (this.precision === undefined
- ? ''
- : '.' + Math.max(0, this.precision | 0)) +
- (this.trim ? '~' : '') +
- this.type
- );
- }; // CONCATENATED MODULE: ../node_modules/d3-format/src/formatTrim.js
-
- // Trims insignificant zeros, e.g., replaces 1.2000k with 1.2k.
- /* harmony default export */ function formatTrim(s) {
- out: for (var n = s.length, i = 1, i0 = -1, i1; i < n; ++i) {
- switch (s[i]) {
- case '.':
- i0 = i1 = i;
- break;
- case '0':
- if (i0 === 0) i0 = i;
- i1 = i;
- break;
- default:
- if (!+s[i]) break out;
- if (i0 > 0) i0 = 0;
- break;
- }
- }
- return i0 > 0 ? s.slice(0, i0) + s.slice(i1 + 1) : s;
- } // CONCATENATED MODULE: ../node_modules/d3-format/src/formatPrefixAuto.js
-
- var prefixExponent;
-
- /* harmony default export */ function formatPrefixAuto(x, p) {
- var d = formatDecimalParts(x, p);
- if (!d) return x + '';
- var coefficient = d[0],
- exponent = d[1],
- i =
- exponent -
- (prefixExponent =
- Math.max(-8, Math.min(8, Math.floor(exponent / 3))) * 3) +
- 1,
- n = coefficient.length;
- return i === n
- ? coefficient
- : i > n
- ? coefficient + new Array(i - n + 1).join('0')
- : i > 0
- ? coefficient.slice(0, i) + '.' + coefficient.slice(i)
- : '0.' +
- new Array(1 - i).join('0') +
- formatDecimalParts(x, Math.max(0, p + i - 1))[0]; // less than 1y!
- } // CONCATENATED MODULE: ../node_modules/d3-format/src/formatRounded.js
-
- /* harmony default export */ function formatRounded(x, p) {
- var d = formatDecimalParts(x, p);
- if (!d) return x + '';
- var coefficient = d[0],
- exponent = d[1];
- return exponent < 0
- ? '0.' + new Array(-exponent).join('0') + coefficient
- : coefficient.length > exponent + 1
- ? coefficient.slice(0, exponent + 1) +
- '.' +
- coefficient.slice(exponent + 1)
- : coefficient + new Array(exponent - coefficient.length + 2).join('0');
- } // CONCATENATED MODULE: ../node_modules/d3-format/src/formatTypes.js
-
- /* harmony default export */ const formatTypes = {
- '%': (x, p) => (x * 100).toFixed(p),
- b: (x) => Math.round(x).toString(2),
- c: (x) => x + '',
- d: formatDecimal,
- e: (x, p) => x.toExponential(p),
- f: (x, p) => x.toFixed(p),
- g: (x, p) => x.toPrecision(p),
- o: (x) => Math.round(x).toString(8),
- p: (x, p) => formatRounded(x * 100, p),
- r: formatRounded,
- s: formatPrefixAuto,
- X: (x) => Math.round(x).toString(16).toUpperCase(),
- x: (x) => Math.round(x).toString(16),
- }; // CONCATENATED MODULE: ../node_modules/d3-format/src/identity.js
-
- /* harmony default export */ function identity(x) {
- return x;
- } // CONCATENATED MODULE: ../node_modules/d3-format/src/locale.js
-
- var map = Array.prototype.map,
- prefixes = [
- 'y',
- 'z',
- 'a',
- 'f',
- 'p',
- 'n',
- 'µ',
- 'm',
- '',
- 'k',
- 'M',
- 'G',
- 'T',
- 'P',
- 'E',
- 'Z',
- 'Y',
- ];
-
- /* harmony default export */ function locale(locale) {
- var group =
- locale.grouping === undefined || locale.thousands === undefined
- ? identity
- : formatGroup(
- map.call(locale.grouping, Number),
- locale.thousands + '',
- ),
- currencyPrefix =
- locale.currency === undefined ? '' : locale.currency[0] + '',
- currencySuffix =
- locale.currency === undefined ? '' : locale.currency[1] + '',
- decimal = locale.decimal === undefined ? '.' : locale.decimal + '',
- numerals =
- locale.numerals === undefined
- ? identity
- : formatNumerals(map.call(locale.numerals, String)),
- percent = locale.percent === undefined ? '%' : locale.percent + '',
- minus = locale.minus === undefined ? '−' : locale.minus + '',
- nan = locale.nan === undefined ? 'NaN' : locale.nan + '';
-
- function newFormat(specifier) {
- specifier = formatSpecifier(specifier);
-
- var fill = specifier.fill,
- align = specifier.align,
- sign = specifier.sign,
- symbol = specifier.symbol,
- zero = specifier.zero,
- width = specifier.width,
- comma = specifier.comma,
- precision = specifier.precision,
- trim = specifier.trim,
- type = specifier.type;
-
- // The "n" type is an alias for ",g".
- if (type === 'n') (comma = true), (type = 'g');
- // The "" type, and any invalid type, is an alias for ".12~g".
- else if (!formatTypes[type])
- precision === undefined && (precision = 12),
- (trim = true),
- (type = 'g');
-
- // If zero fill is specified, padding goes after sign and before digits.
- if (zero || (fill === '0' && align === '='))
- (zero = true), (fill = '0'), (align = '=');
-
- // Compute the prefix and suffix.
- // For SI-prefix, the suffix is lazily computed.
- var prefix =
- symbol === '$'
- ? currencyPrefix
- : symbol === '#' && /[boxX]/.test(type)
- ? '0' + type.toLowerCase()
- : '',
- suffix =
- symbol === '$' ? currencySuffix : /[%p]/.test(type) ? percent : '';
-
- // What format function should we use?
- // Is this an integer type?
- // Can this type generate exponential notation?
- var formatType = formatTypes[type],
- maybeSuffix = /[defgprs%]/.test(type);
-
- // Set the default precision if not specified,
- // or clamp the specified precision to the supported range.
- // For significant precision, it must be in [1, 21].
- // For fixed precision, it must be in [0, 20].
- precision =
- precision === undefined
- ? 6
- : /[gprs]/.test(type)
- ? Math.max(1, Math.min(21, precision))
- : Math.max(0, Math.min(20, precision));
-
- function format(value) {
- var valuePrefix = prefix,
- valueSuffix = suffix,
- i,
- n,
- c;
-
- if (type === 'c') {
- valueSuffix = formatType(value) + valueSuffix;
- value = '';
- } else {
- value = +value;
-
- // Determine the sign. -0 is not less than 0, but 1 / -0 is!
- var valueNegative = value < 0 || 1 / value < 0;
-
- // Perform the initial formatting.
- value = isNaN(value) ? nan : formatType(Math.abs(value), precision);
-
- // Trim insignificant zeros.
- if (trim) value = formatTrim(value);
-
- // If a negative value rounds to zero after formatting, and no explicit positive sign is requested, hide the sign.
- if (valueNegative && +value === 0 && sign !== '+')
- valueNegative = false;
-
- // Compute the prefix and suffix.
- valuePrefix =
- (valueNegative
- ? sign === '('
- ? sign
- : minus
- : sign === '-' || sign === '('
- ? ''
- : sign) + valuePrefix;
- valueSuffix =
- (type === 's' ? prefixes[8 + prefixExponent / 3] : '') +
- valueSuffix +
- (valueNegative && sign === '(' ? ')' : '');
-
- // Break the formatted value into the integer “value” part that can be
- // grouped, and fractional or exponential “suffix” part that is not.
- if (maybeSuffix) {
- (i = -1), (n = value.length);
- while (++i < n) {
- if (((c = value.charCodeAt(i)), 48 > c || c > 57)) {
- valueSuffix =
- (c === 46 ? decimal + value.slice(i + 1) : value.slice(i)) +
- valueSuffix;
- value = value.slice(0, i);
- break;
- }
- }
- }
- }
-
- // If the fill character is not "0", grouping is applied before padding.
- if (comma && !zero) value = group(value, Infinity);
-
- // Compute the padding.
- var length = valuePrefix.length + value.length + valueSuffix.length,
- padding =
- length < width ? new Array(width - length + 1).join(fill) : '';
-
- // If the fill character is "0", grouping is applied after padding.
- if (comma && zero)
- (value = group(
- padding + value,
- padding.length ? width - valueSuffix.length : Infinity,
- )),
- (padding = '');
-
- // Reconstruct the final output based on the desired alignment.
- switch (align) {
- case '<':
- value = valuePrefix + value + valueSuffix + padding;
- break;
- case '=':
- value = valuePrefix + padding + value + valueSuffix;
- break;
- case '^':
- value =
- padding.slice(0, (length = padding.length >> 1)) +
- valuePrefix +
- value +
- valueSuffix +
- padding.slice(length);
- break;
- default:
- value = padding + valuePrefix + value + valueSuffix;
- break;
- }
-
- return numerals(value);
- }
-
- format.toString = function () {
- return specifier + '';
- };
-
- return format;
- }
-
- function formatPrefix(specifier, value) {
- var f = newFormat(
- ((specifier = formatSpecifier(specifier)),
- (specifier.type = 'f'),
- specifier),
- ),
- e = Math.max(-8, Math.min(8, Math.floor(exponent(value) / 3))) * 3,
- k = Math.pow(10, -e),
- prefix = prefixes[8 + e / 3];
- return function (value) {
- return f(k * value) + prefix;
- };
- }
-
- return {
- format: newFormat,
- formatPrefix: formatPrefix,
- };
- } // CONCATENATED MODULE: ../node_modules/d3-format/src/defaultLocale.js
-
- var defaultLocale_locale;
- var format;
- var formatPrefix;
-
- defaultLocale({
- thousands: ',',
- grouping: [3],
- currency: ['$', ''],
- });
-
- function defaultLocale(definition) {
- defaultLocale_locale = locale(definition);
- format = defaultLocale_locale.format;
- formatPrefix = defaultLocale_locale.formatPrefix;
- return defaultLocale_locale;
- } // CONCATENATED MODULE: ../node_modules/d3-array/src/ascending.js
-
- function ascending_ascending(a, b) {
- return a == null || b == null
- ? NaN
- : a < b
- ? -1
- : a > b
- ? 1
- : a >= b
- ? 0
- : NaN;
- } // CONCATENATED MODULE: ../node_modules/d3-hierarchy/src/treemap/round.js
-
- /* harmony default export */ function treemap_round(node) {
- node.x0 = Math.round(node.x0);
- node.y0 = Math.round(node.y0);
- node.x1 = Math.round(node.x1);
- node.y1 = Math.round(node.y1);
- } // CONCATENATED MODULE: ../node_modules/d3-hierarchy/src/treemap/dice.js
-
- /* harmony default export */ function dice(parent, x0, y0, x1, y1) {
- var nodes = parent.children,
- node,
- i = -1,
- n = nodes.length,
- k = parent.value && (x1 - x0) / parent.value;
-
- while (++i < n) {
- (node = nodes[i]), (node.y0 = y0), (node.y1 = y1);
- (node.x0 = x0), (node.x1 = x0 += node.value * k);
- }
- } // CONCATENATED MODULE: ../node_modules/d3-hierarchy/src/partition.js
-
- /* harmony default export */ function partition() {
- var dx = 1,
- dy = 1,
- padding = 0,
- round = false;
-
- function partition(root) {
- var n = root.height + 1;
- root.x0 = root.y0 = padding;
- root.x1 = dx;
- root.y1 = dy / n;
- root.eachBefore(positionNode(dy, n));
- if (round) root.eachBefore(treemap_round);
- return root;
- }
-
- function positionNode(dy, n) {
- return function (node) {
- if (node.children) {
- dice(
- node,
- node.x0,
- (dy * (node.depth + 1)) / n,
- node.x1,
- (dy * (node.depth + 2)) / n,
- );
- }
- var x0 = node.x0,
- y0 = node.y0,
- x1 = node.x1 - padding,
- y1 = node.y1 - padding;
- if (x1 < x0) x0 = x1 = (x0 + x1) / 2;
- if (y1 < y0) y0 = y1 = (y0 + y1) / 2;
- node.x0 = x0;
- node.y0 = y0;
- node.x1 = x1;
- node.y1 = y1;
- };
- }
-
- partition.round = function (x) {
- return arguments.length ? ((round = !!x), partition) : round;
- };
-
- partition.size = function (x) {
- return arguments.length
- ? ((dx = +x[0]), (dy = +x[1]), partition)
- : [dx, dy];
- };
-
- partition.padding = function (x) {
- return arguments.length ? ((padding = +x), partition) : padding;
- };
-
- return partition;
- } // CONCATENATED MODULE: ../node_modules/d3-hierarchy/src/hierarchy/count.js
-
- function count(node) {
- var sum = 0,
- children = node.children,
- i = children && children.length;
- if (!i) sum = 1;
- else while (--i >= 0) sum += children[i].value;
- node.value = sum;
- }
-
- /* harmony default export */ function hierarchy_count() {
- return this.eachAfter(count);
- } // CONCATENATED MODULE: ../node_modules/d3-hierarchy/src/hierarchy/each.js
-
- /* harmony default export */ function hierarchy_each(callback, that) {
- let index = -1;
- for (const node of this) {
- callback.call(that, node, ++index, this);
- }
- return this;
- } // CONCATENATED MODULE: ../node_modules/d3-hierarchy/src/hierarchy/eachBefore.js
-
- /* harmony default export */ function eachBefore(callback, that) {
- var node = this,
- nodes = [node],
- children,
- i,
- index = -1;
- while ((node = nodes.pop())) {
- callback.call(that, node, ++index, this);
- if ((children = node.children)) {
- for (i = children.length - 1; i >= 0; --i) {
- nodes.push(children[i]);
- }
- }
- }
- return this;
- } // CONCATENATED MODULE: ../node_modules/d3-hierarchy/src/hierarchy/eachAfter.js
-
- /* harmony default export */ function eachAfter(callback, that) {
- var node = this,
- nodes = [node],
- next = [],
- children,
- i,
- n,
- index = -1;
- while ((node = nodes.pop())) {
- next.push(node);
- if ((children = node.children)) {
- for (i = 0, n = children.length; i < n; ++i) {
- nodes.push(children[i]);
- }
- }
- }
- while ((node = next.pop())) {
- callback.call(that, node, ++index, this);
- }
- return this;
- } // CONCATENATED MODULE: ../node_modules/d3-hierarchy/src/hierarchy/find.js
-
- /* harmony default export */ function hierarchy_find(callback, that) {
- let index = -1;
- for (const node of this) {
- if (callback.call(that, node, ++index, this)) {
- return node;
- }
- }
- } // CONCATENATED MODULE: ../node_modules/d3-hierarchy/src/hierarchy/sum.js
-
- /* harmony default export */ function sum(value) {
- return this.eachAfter(function (node) {
- var sum = +value(node.data) || 0,
- children = node.children,
- i = children && children.length;
- while (--i >= 0) sum += children[i].value;
- node.value = sum;
- });
- } // CONCATENATED MODULE: ../node_modules/d3-hierarchy/src/hierarchy/sort.js
-
- /* harmony default export */ function hierarchy_sort(compare) {
- return this.eachBefore(function (node) {
- if (node.children) {
- node.children.sort(compare);
- }
- });
- } // CONCATENATED MODULE: ../node_modules/d3-hierarchy/src/hierarchy/path.js
-
- /* harmony default export */ function path(end) {
- var start = this,
- ancestor = leastCommonAncestor(start, end),
- nodes = [start];
- while (start !== ancestor) {
- start = start.parent;
- nodes.push(start);
- }
- var k = nodes.length;
- while (end !== ancestor) {
- nodes.splice(k, 0, end);
- end = end.parent;
- }
- return nodes;
- }
-
- function leastCommonAncestor(a, b) {
- if (a === b) return a;
- var aNodes = a.ancestors(),
- bNodes = b.ancestors(),
- c = null;
- a = aNodes.pop();
- b = bNodes.pop();
- while (a === b) {
- c = a;
- a = aNodes.pop();
- b = bNodes.pop();
- }
- return c;
- } // CONCATENATED MODULE: ../node_modules/d3-hierarchy/src/hierarchy/ancestors.js
-
- /* harmony default export */ function ancestors() {
- var node = this,
- nodes = [node];
- while ((node = node.parent)) {
- nodes.push(node);
- }
- return nodes;
- } // CONCATENATED MODULE: ../node_modules/d3-hierarchy/src/hierarchy/descendants.js
-
- /* harmony default export */ function descendants() {
- return Array.from(this);
- } // CONCATENATED MODULE: ../node_modules/d3-hierarchy/src/hierarchy/leaves.js
-
- /* harmony default export */ function leaves() {
- var leaves = [];
- this.eachBefore(function (node) {
- if (!node.children) {
- leaves.push(node);
- }
- });
- return leaves;
- } // CONCATENATED MODULE: ../node_modules/d3-hierarchy/src/hierarchy/links.js
-
- /* harmony default export */ function links() {
- var root = this,
- links = [];
- root.each(function (node) {
- if (node !== root) {
- // Don’t include the root’s parent, if any.
- links.push({ source: node.parent, target: node });
- }
- });
- return links;
- } // CONCATENATED MODULE: ../node_modules/d3-hierarchy/src/hierarchy/iterator.js
-
- /* harmony default export */ function* hierarchy_iterator() {
- var node = this,
- current,
- next = [node],
- children,
- i,
- n;
- do {
- (current = next.reverse()), (next = []);
- while ((node = current.pop())) {
- yield node;
- if ((children = node.children)) {
- for (i = 0, n = children.length; i < n; ++i) {
- next.push(children[i]);
- }
- }
- }
- } while (next.length);
- } // CONCATENATED MODULE: ../node_modules/d3-hierarchy/src/hierarchy/index.js
-
- function hierarchy(data, children) {
- if (data instanceof Map) {
- data = [undefined, data];
- if (children === undefined) children = mapChildren;
- } else if (children === undefined) {
- children = objectChildren;
- }
-
- var root = new Node(data),
- node,
- nodes = [root],
- child,
- childs,
- i,
- n;
-
- while ((node = nodes.pop())) {
- if (
- (childs = children(node.data)) &&
- (n = (childs = Array.from(childs)).length)
- ) {
- node.children = childs;
- for (i = n - 1; i >= 0; --i) {
- nodes.push((child = childs[i] = new Node(childs[i])));
- child.parent = node;
- child.depth = node.depth + 1;
- }
- }
- }
-
- return root.eachBefore(computeHeight);
- }
-
- function node_copy() {
- return hierarchy(this).eachBefore(copyData);
- }
-
- function objectChildren(d) {
- return d.children;
- }
-
- function mapChildren(d) {
- return Array.isArray(d) ? d[1] : null;
- }
-
- function copyData(node) {
- if (node.data.value !== undefined) node.value = node.data.value;
- node.data = node.data.data;
- }
-
- function computeHeight(node) {
- var height = 0;
- do node.height = height;
- while ((node = node.parent) && node.height < ++height);
- }
-
- function Node(data) {
- this.data = data;
- this.depth = this.height = 0;
- this.parent = null;
- }
-
- Node.prototype = hierarchy.prototype = {
- constructor: Node,
- count: hierarchy_count,
- each: hierarchy_each,
- eachAfter: eachAfter,
- eachBefore: eachBefore,
- find: hierarchy_find,
- sum: sum,
- sort: hierarchy_sort,
- path: path,
- ancestors: ancestors,
- descendants: descendants,
- leaves: leaves,
- links: links,
- copy: node_copy,
- [Symbol.iterator]: hierarchy_iterator,
- }; // CONCATENATED MODULE: ../node_modules/d3-array/src/ticks.js
-
- var e10 = Math.sqrt(50),
- e5 = Math.sqrt(10),
- e2 = Math.sqrt(2);
-
- function ticks(start, stop, count) {
- var reverse,
- i = -1,
- n,
- ticks,
- step;
-
- (stop = +stop), (start = +start), (count = +count);
- if (start === stop && count > 0) return [start];
- if ((reverse = stop < start)) (n = start), (start = stop), (stop = n);
- if ((step = tickIncrement(start, stop, count)) === 0 || !isFinite(step))
- return [];
-
- if (step > 0) {
- let r0 = Math.round(start / step),
- r1 = Math.round(stop / step);
- if (r0 * step < start) ++r0;
- if (r1 * step > stop) --r1;
- ticks = new Array((n = r1 - r0 + 1));
- while (++i < n) ticks[i] = (r0 + i) * step;
- } else {
- step = -step;
- let r0 = Math.round(start * step),
- r1 = Math.round(stop * step);
- if (r0 / step < start) ++r0;
- if (r1 / step > stop) --r1;
- ticks = new Array((n = r1 - r0 + 1));
- while (++i < n) ticks[i] = (r0 + i) / step;
- }
-
- if (reverse) ticks.reverse();
-
- return ticks;
- }
-
- function tickIncrement(start, stop, count) {
- var step = (stop - start) / Math.max(0, count),
- power = Math.floor(Math.log(step) / Math.LN10),
- error = step / Math.pow(10, power);
- return power >= 0
- ? (error >= e10 ? 10 : error >= e5 ? 5 : error >= e2 ? 2 : 1) *
- Math.pow(10, power)
- : -Math.pow(10, -power) /
- (error >= e10 ? 10 : error >= e5 ? 5 : error >= e2 ? 2 : 1);
- }
-
- function tickStep(start, stop, count) {
- var step0 = Math.abs(stop - start) / Math.max(0, count),
- step1 = Math.pow(10, Math.floor(Math.log(step0) / Math.LN10)),
- error = step0 / step1;
- if (error >= e10) step1 *= 10;
- else if (error >= e5) step1 *= 5;
- else if (error >= e2) step1 *= 2;
- return stop < start ? -step1 : step1;
- } // CONCATENATED MODULE: ../node_modules/d3-array/src/bisector.js
-
- function bisector(f) {
- let delta = f;
- let compare1 = f;
- let compare2 = f;
-
- if (f.length !== 2) {
- delta = (d, x) => f(d) - x;
- compare1 = ascending_ascending;
- compare2 = (d, x) => ascending_ascending(f(d), x);
- }
-
- function left(a, x, lo = 0, hi = a.length) {
- if (lo < hi) {
- if (compare1(x, x) !== 0) return hi;
- do {
- const mid = (lo + hi) >>> 1;
- if (compare2(a[mid], x) < 0) lo = mid + 1;
- else hi = mid;
- } while (lo < hi);
- }
- return lo;
- }
-
- function right(a, x, lo = 0, hi = a.length) {
- if (lo < hi) {
- if (compare1(x, x) !== 0) return hi;
- do {
- const mid = (lo + hi) >>> 1;
- if (compare2(a[mid], x) <= 0) lo = mid + 1;
- else hi = mid;
- } while (lo < hi);
- }
- return lo;
- }
-
- function center(a, x, lo = 0, hi = a.length) {
- const i = left(a, x, lo, hi - 1);
- return i > lo && delta(a[i - 1], x) > -delta(a[i], x) ? i - 1 : i;
- }
-
- return { left, center, right };
- } // CONCATENATED MODULE: ../node_modules/d3-array/src/number.js
-
- function number(x) {
- return x === null ? NaN : +x;
- }
-
- function* numbers(values, valueof) {
- if (valueof === undefined) {
- for (let value of values) {
- if (value != null && (value = +value) >= value) {
- yield value;
- }
- }
- } else {
- let index = -1;
- for (let value of values) {
- if (
- (value = valueof(value, ++index, values)) != null &&
- (value = +value) >= value
- ) {
- yield value;
- }
- }
- }
- } // CONCATENATED MODULE: ../node_modules/d3-array/src/bisect.js
-
- const ascendingBisect = bisector(ascending_ascending);
- const bisectRight = ascendingBisect.right;
- const bisectLeft = ascendingBisect.left;
- const bisectCenter = bisector(number).center;
- /* harmony default export */ const bisect = bisectRight; // CONCATENATED MODULE: ../node_modules/d3-color/src/define.js
-
- /* harmony default export */ function src_define(
- constructor,
- factory,
- prototype,
- ) {
- constructor.prototype = factory.prototype = prototype;
- prototype.constructor = constructor;
- }
-
- function extend(parent, definition) {
- var prototype = Object.create(parent.prototype);
- for (var key in definition) prototype[key] = definition[key];
- return prototype;
- } // CONCATENATED MODULE: ../node_modules/d3-color/src/color.js
-
- function Color() {}
-
- var darker = 0.7;
- var brighter = 1 / darker;
-
- var reI = '\\s*([+-]?\\d+)\\s*',
- reN = '\\s*([+-]?\\d*\\.?\\d+(?:[eE][+-]?\\d+)?)\\s*',
- reP = '\\s*([+-]?\\d*\\.?\\d+(?:[eE][+-]?\\d+)?)%\\s*',
- reHex = /^#([0-9a-f]{3,8})$/,
- reRgbInteger = new RegExp('^rgb\\(' + [reI, reI, reI] + '\\)$'),
- reRgbPercent = new RegExp('^rgb\\(' + [reP, reP, reP] + '\\)$'),
- reRgbaInteger = new RegExp('^rgba\\(' + [reI, reI, reI, reN] + '\\)$'),
- reRgbaPercent = new RegExp('^rgba\\(' + [reP, reP, reP, reN] + '\\)$'),
- reHslPercent = new RegExp('^hsl\\(' + [reN, reP, reP] + '\\)$'),
- reHslaPercent = new RegExp('^hsla\\(' + [reN, reP, reP, reN] + '\\)$');
-
- var named = {
- aliceblue: 0xf0f8ff,
- antiquewhite: 0xfaebd7,
- aqua: 0x00ffff,
- aquamarine: 0x7fffd4,
- azure: 0xf0ffff,
- beige: 0xf5f5dc,
- bisque: 0xffe4c4,
- black: 0x000000,
- blanchedalmond: 0xffebcd,
- blue: 0x0000ff,
- blueviolet: 0x8a2be2,
- brown: 0xa52a2a,
- burlywood: 0xdeb887,
- cadetblue: 0x5f9ea0,
- chartreuse: 0x7fff00,
- chocolate: 0xd2691e,
- coral: 0xff7f50,
- cornflowerblue: 0x6495ed,
- cornsilk: 0xfff8dc,
- crimson: 0xdc143c,
- cyan: 0x00ffff,
- darkblue: 0x00008b,
- darkcyan: 0x008b8b,
- darkgoldenrod: 0xb8860b,
- darkgray: 0xa9a9a9,
- darkgreen: 0x006400,
- darkgrey: 0xa9a9a9,
- darkkhaki: 0xbdb76b,
- darkmagenta: 0x8b008b,
- darkolivegreen: 0x556b2f,
- darkorange: 0xff8c00,
- darkorchid: 0x9932cc,
- darkred: 0x8b0000,
- darksalmon: 0xe9967a,
- darkseagreen: 0x8fbc8f,
- darkslateblue: 0x483d8b,
- darkslategray: 0x2f4f4f,
- darkslategrey: 0x2f4f4f,
- darkturquoise: 0x00ced1,
- darkviolet: 0x9400d3,
- deeppink: 0xff1493,
- deepskyblue: 0x00bfff,
- dimgray: 0x696969,
- dimgrey: 0x696969,
- dodgerblue: 0x1e90ff,
- firebrick: 0xb22222,
- floralwhite: 0xfffaf0,
- forestgreen: 0x228b22,
- fuchsia: 0xff00ff,
- gainsboro: 0xdcdcdc,
- ghostwhite: 0xf8f8ff,
- gold: 0xffd700,
- goldenrod: 0xdaa520,
- gray: 0x808080,
- green: 0x008000,
- greenyellow: 0xadff2f,
- grey: 0x808080,
- honeydew: 0xf0fff0,
- hotpink: 0xff69b4,
- indianred: 0xcd5c5c,
- indigo: 0x4b0082,
- ivory: 0xfffff0,
- khaki: 0xf0e68c,
- lavender: 0xe6e6fa,
- lavenderblush: 0xfff0f5,
- lawngreen: 0x7cfc00,
- lemonchiffon: 0xfffacd,
- lightblue: 0xadd8e6,
- lightcoral: 0xf08080,
- lightcyan: 0xe0ffff,
- lightgoldenrodyellow: 0xfafad2,
- lightgray: 0xd3d3d3,
- lightgreen: 0x90ee90,
- lightgrey: 0xd3d3d3,
- lightpink: 0xffb6c1,
- lightsalmon: 0xffa07a,
- lightseagreen: 0x20b2aa,
- lightskyblue: 0x87cefa,
- lightslategray: 0x778899,
- lightslategrey: 0x778899,
- lightsteelblue: 0xb0c4de,
- lightyellow: 0xffffe0,
- lime: 0x00ff00,
- limegreen: 0x32cd32,
- linen: 0xfaf0e6,
- magenta: 0xff00ff,
- maroon: 0x800000,
- mediumaquamarine: 0x66cdaa,
- mediumblue: 0x0000cd,
- mediumorchid: 0xba55d3,
- mediumpurple: 0x9370db,
- mediumseagreen: 0x3cb371,
- mediumslateblue: 0x7b68ee,
- mediumspringgreen: 0x00fa9a,
- mediumturquoise: 0x48d1cc,
- mediumvioletred: 0xc71585,
- midnightblue: 0x191970,
- mintcream: 0xf5fffa,
- mistyrose: 0xffe4e1,
- moccasin: 0xffe4b5,
- navajowhite: 0xffdead,
- navy: 0x000080,
- oldlace: 0xfdf5e6,
- olive: 0x808000,
- olivedrab: 0x6b8e23,
- orange: 0xffa500,
- orangered: 0xff4500,
- orchid: 0xda70d6,
- palegoldenrod: 0xeee8aa,
- palegreen: 0x98fb98,
- paleturquoise: 0xafeeee,
- palevioletred: 0xdb7093,
- papayawhip: 0xffefd5,
- peachpuff: 0xffdab9,
- peru: 0xcd853f,
- pink: 0xffc0cb,
- plum: 0xdda0dd,
- powderblue: 0xb0e0e6,
- purple: 0x800080,
- rebeccapurple: 0x663399,
- red: 0xff0000,
- rosybrown: 0xbc8f8f,
- royalblue: 0x4169e1,
- saddlebrown: 0x8b4513,
- salmon: 0xfa8072,
- sandybrown: 0xf4a460,
- seagreen: 0x2e8b57,
- seashell: 0xfff5ee,
- sienna: 0xa0522d,
- silver: 0xc0c0c0,
- skyblue: 0x87ceeb,
- slateblue: 0x6a5acd,
- slategray: 0x708090,
- slategrey: 0x708090,
- snow: 0xfffafa,
- springgreen: 0x00ff7f,
- steelblue: 0x4682b4,
- tan: 0xd2b48c,
- teal: 0x008080,
- thistle: 0xd8bfd8,
- tomato: 0xff6347,
- turquoise: 0x40e0d0,
- violet: 0xee82ee,
- wheat: 0xf5deb3,
- white: 0xffffff,
- whitesmoke: 0xf5f5f5,
- yellow: 0xffff00,
- yellowgreen: 0x9acd32,
- };
-
- src_define(Color, color, {
- copy: function (channels) {
- return Object.assign(new this.constructor(), this, channels);
- },
- displayable: function () {
- return this.rgb().displayable();
- },
- hex: color_formatHex, // Deprecated! Use color.formatHex.
- formatHex: color_formatHex,
- formatHsl: color_formatHsl,
- formatRgb: color_formatRgb,
- toString: color_formatRgb,
- });
-
- function color_formatHex() {
- return this.rgb().formatHex();
- }
-
- function color_formatHsl() {
- return hslConvert(this).formatHsl();
- }
-
- function color_formatRgb() {
- return this.rgb().formatRgb();
- }
-
- function color(format) {
- var m, l;
- format = (format + '').trim().toLowerCase();
- return (m = reHex.exec(format))
- ? ((l = m[1].length),
- (m = parseInt(m[1], 16)),
- l === 6
- ? rgbn(m) // #ff0000
- : l === 3
- ? new Rgb(
- ((m >> 8) & 0xf) | ((m >> 4) & 0xf0),
- ((m >> 4) & 0xf) | (m & 0xf0),
- ((m & 0xf) << 4) | (m & 0xf),
- 1,
- ) // #f00
- : l === 8
- ? rgba(
- (m >> 24) & 0xff,
- (m >> 16) & 0xff,
- (m >> 8) & 0xff,
- (m & 0xff) / 0xff,
- ) // #ff000000
- : l === 4
- ? rgba(
- ((m >> 12) & 0xf) | ((m >> 8) & 0xf0),
- ((m >> 8) & 0xf) | ((m >> 4) & 0xf0),
- ((m >> 4) & 0xf) | (m & 0xf0),
- (((m & 0xf) << 4) | (m & 0xf)) / 0xff,
- ) // #f000
- : null) // invalid hex
- : (m = reRgbInteger.exec(format))
- ? new Rgb(m[1], m[2], m[3], 1) // rgb(255, 0, 0)
- : (m = reRgbPercent.exec(format))
- ? new Rgb((m[1] * 255) / 100, (m[2] * 255) / 100, (m[3] * 255) / 100, 1) // rgb(100%, 0%, 0%)
- : (m = reRgbaInteger.exec(format))
- ? rgba(m[1], m[2], m[3], m[4]) // rgba(255, 0, 0, 1)
- : (m = reRgbaPercent.exec(format))
- ? rgba((m[1] * 255) / 100, (m[2] * 255) / 100, (m[3] * 255) / 100, m[4]) // rgb(100%, 0%, 0%, 1)
- : (m = reHslPercent.exec(format))
- ? hsla(m[1], m[2] / 100, m[3] / 100, 1) // hsl(120, 50%, 50%)
- : (m = reHslaPercent.exec(format))
- ? hsla(m[1], m[2] / 100, m[3] / 100, m[4]) // hsla(120, 50%, 50%, 1)
- : named.hasOwnProperty(format)
- ? rgbn(named[format]) // eslint-disable-line no-prototype-builtins
- : format === 'transparent'
- ? new Rgb(NaN, NaN, NaN, 0)
- : null;
- }
-
- function rgbn(n) {
- return new Rgb((n >> 16) & 0xff, (n >> 8) & 0xff, n & 0xff, 1);
- }
-
- function rgba(r, g, b, a) {
- if (a <= 0) r = g = b = NaN;
- return new Rgb(r, g, b, a);
- }
-
- function rgbConvert(o) {
- if (!(o instanceof Color)) o = color(o);
- if (!o) return new Rgb();
- o = o.rgb();
- return new Rgb(o.r, o.g, o.b, o.opacity);
- }
-
- function color_rgb(r, g, b, opacity) {
- return arguments.length === 1
- ? rgbConvert(r)
- : new Rgb(r, g, b, opacity == null ? 1 : opacity);
- }
-
- function Rgb(r, g, b, opacity) {
- this.r = +r;
- this.g = +g;
- this.b = +b;
- this.opacity = +opacity;
- }
-
- src_define(
- Rgb,
- color_rgb,
- extend(Color, {
- brighter: function (k) {
- k = k == null ? brighter : Math.pow(brighter, k);
- return new Rgb(this.r * k, this.g * k, this.b * k, this.opacity);
- },
- darker: function (k) {
- k = k == null ? darker : Math.pow(darker, k);
- return new Rgb(this.r * k, this.g * k, this.b * k, this.opacity);
- },
- rgb: function () {
- return this;
- },
- displayable: function () {
- return (
- -0.5 <= this.r &&
- this.r < 255.5 &&
- -0.5 <= this.g &&
- this.g < 255.5 &&
- -0.5 <= this.b &&
- this.b < 255.5 &&
- 0 <= this.opacity &&
- this.opacity <= 1
- );
- },
- hex: rgb_formatHex, // Deprecated! Use color.formatHex.
- formatHex: rgb_formatHex,
- formatRgb: rgb_formatRgb,
- toString: rgb_formatRgb,
- }),
- );
-
- function rgb_formatHex() {
- return '#' + hex(this.r) + hex(this.g) + hex(this.b);
- }
-
- function rgb_formatRgb() {
- var a = this.opacity;
- a = isNaN(a) ? 1 : Math.max(0, Math.min(1, a));
- return (
- (a === 1 ? 'rgb(' : 'rgba(') +
- Math.max(0, Math.min(255, Math.round(this.r) || 0)) +
- ', ' +
- Math.max(0, Math.min(255, Math.round(this.g) || 0)) +
- ', ' +
- Math.max(0, Math.min(255, Math.round(this.b) || 0)) +
- (a === 1 ? ')' : ', ' + a + ')')
- );
- }
-
- function hex(value) {
- value = Math.max(0, Math.min(255, Math.round(value) || 0));
- return (value < 16 ? '0' : '') + value.toString(16);
- }
-
- function hsla(h, s, l, a) {
- if (a <= 0) h = s = l = NaN;
- else if (l <= 0 || l >= 1) h = s = NaN;
- else if (s <= 0) h = NaN;
- return new Hsl(h, s, l, a);
- }
-
- function hslConvert(o) {
- if (o instanceof Hsl) return new Hsl(o.h, o.s, o.l, o.opacity);
- if (!(o instanceof Color)) o = color(o);
- if (!o) return new Hsl();
- if (o instanceof Hsl) return o;
- o = o.rgb();
- var r = o.r / 255,
- g = o.g / 255,
- b = o.b / 255,
- min = Math.min(r, g, b),
- max = Math.max(r, g, b),
- h = NaN,
- s = max - min,
- l = (max + min) / 2;
- if (s) {
- if (r === max) h = (g - b) / s + (g < b) * 6;
- else if (g === max) h = (b - r) / s + 2;
- else h = (r - g) / s + 4;
- s /= l < 0.5 ? max + min : 2 - max - min;
- h *= 60;
- } else {
- s = l > 0 && l < 1 ? 0 : h;
- }
- return new Hsl(h, s, l, o.opacity);
- }
-
- function hsl(h, s, l, opacity) {
- return arguments.length === 1
- ? hslConvert(h)
- : new Hsl(h, s, l, opacity == null ? 1 : opacity);
- }
-
- function Hsl(h, s, l, opacity) {
- this.h = +h;
- this.s = +s;
- this.l = +l;
- this.opacity = +opacity;
- }
-
- src_define(
- Hsl,
- hsl,
- extend(Color, {
- brighter: function (k) {
- k = k == null ? brighter : Math.pow(brighter, k);
- return new Hsl(this.h, this.s, this.l * k, this.opacity);
- },
- darker: function (k) {
- k = k == null ? darker : Math.pow(darker, k);
- return new Hsl(this.h, this.s, this.l * k, this.opacity);
- },
- rgb: function () {
- var h = (this.h % 360) + (this.h < 0) * 360,
- s = isNaN(h) || isNaN(this.s) ? 0 : this.s,
- l = this.l,
- m2 = l + (l < 0.5 ? l : 1 - l) * s,
- m1 = 2 * l - m2;
- return new Rgb(
- hsl2rgb(h >= 240 ? h - 240 : h + 120, m1, m2),
- hsl2rgb(h, m1, m2),
- hsl2rgb(h < 120 ? h + 240 : h - 120, m1, m2),
- this.opacity,
- );
- },
- displayable: function () {
- return (
- ((0 <= this.s && this.s <= 1) || isNaN(this.s)) &&
- 0 <= this.l &&
- this.l <= 1 &&
- 0 <= this.opacity &&
- this.opacity <= 1
- );
- },
- formatHsl: function () {
- var a = this.opacity;
- a = isNaN(a) ? 1 : Math.max(0, Math.min(1, a));
- return (
- (a === 1 ? 'hsl(' : 'hsla(') +
- (this.h || 0) +
- ', ' +
- (this.s || 0) * 100 +
- '%, ' +
- (this.l || 0) * 100 +
- '%' +
- (a === 1 ? ')' : ', ' + a + ')')
- );
- },
- }),
- );
-
- /* From FvD 13.37, CSS Color Module Level 3 */
- function hsl2rgb(h, m1, m2) {
- return (
- (h < 60
- ? m1 + ((m2 - m1) * h) / 60
- : h < 180
- ? m2
- : h < 240
- ? m1 + ((m2 - m1) * (240 - h)) / 60
- : m1) * 255
- );
- } // CONCATENATED MODULE: ../node_modules/d3-interpolate/src/basis.js
-
- function basis(t1, v0, v1, v2, v3) {
- var t2 = t1 * t1,
- t3 = t2 * t1;
- return (
- ((1 - 3 * t1 + 3 * t2 - t3) * v0 +
- (4 - 6 * t2 + 3 * t3) * v1 +
- (1 + 3 * t1 + 3 * t2 - 3 * t3) * v2 +
- t3 * v3) /
- 6
- );
- }
-
- /* harmony default export */ function src_basis(values) {
- var n = values.length - 1;
- return function (t) {
- var i =
- t <= 0 ? (t = 0) : t >= 1 ? ((t = 1), n - 1) : Math.floor(t * n),
- v1 = values[i],
- v2 = values[i + 1],
- v0 = i > 0 ? values[i - 1] : 2 * v1 - v2,
- v3 = i < n - 1 ? values[i + 2] : 2 * v2 - v1;
- return basis((t - i / n) * n, v0, v1, v2, v3);
- };
- } // CONCATENATED MODULE: ../node_modules/d3-interpolate/src/basisClosed.js
-
- /* harmony default export */ function basisClosed(values) {
- var n = values.length;
- return function (t) {
- var i = Math.floor(((t %= 1) < 0 ? ++t : t) * n),
- v0 = values[(i + n - 1) % n],
- v1 = values[i % n],
- v2 = values[(i + 1) % n],
- v3 = values[(i + 2) % n];
- return basis((t - i / n) * n, v0, v1, v2, v3);
- };
- } // CONCATENATED MODULE: ../node_modules/d3-interpolate/src/constant.js
-
- /* harmony default export */ const d3_interpolate_src_constant = (
- x,
- ) => () => x; // CONCATENATED MODULE: ../node_modules/d3-interpolate/src/color.js
-
- function linear(a, d) {
- return function (t) {
- return a + t * d;
- };
- }
-
- function exponential(a, b, y) {
- return (
- (a = Math.pow(a, y)),
- (b = Math.pow(b, y) - a),
- (y = 1 / y),
- function (t) {
- return Math.pow(a + t * b, y);
- }
- );
- }
-
- function hue(a, b) {
- var d = b - a;
- return d
- ? linear(a, d > 180 || d < -180 ? d - 360 * Math.round(d / 360) : d)
- : constant(isNaN(a) ? b : a);
- }
-
- function gamma(y) {
- return (y = +y) === 1
- ? nogamma
- : function (a, b) {
- return b - a
- ? exponential(a, b, y)
- : d3_interpolate_src_constant(isNaN(a) ? b : a);
- };
- }
-
- function nogamma(a, b) {
- var d = b - a;
- return d ? linear(a, d) : d3_interpolate_src_constant(isNaN(a) ? b : a);
- } // CONCATENATED MODULE: ../node_modules/d3-interpolate/src/rgb.js
-
- /* harmony default export */ const rgb = (function rgbGamma(y) {
- var color = gamma(y);
-
- function rgb(start, end) {
- var r = color((start = color_rgb(start)).r, (end = color_rgb(end)).r),
- g = color(start.g, end.g),
- b = color(start.b, end.b),
- opacity = nogamma(start.opacity, end.opacity);
- return function (t) {
- start.r = r(t);
- start.g = g(t);
- start.b = b(t);
- start.opacity = opacity(t);
- return start + '';
- };
- }
-
- rgb.gamma = rgbGamma;
-
- return rgb;
- })(1);
-
- function rgbSpline(spline) {
- return function (colors) {
- var n = colors.length,
- r = new Array(n),
- g = new Array(n),
- b = new Array(n),
- i,
- color;
- for (i = 0; i < n; ++i) {
- color = color_rgb(colors[i]);
- r[i] = color.r || 0;
- g[i] = color.g || 0;
- b[i] = color.b || 0;
- }
- r = spline(r);
- g = spline(g);
- b = spline(b);
- color.opacity = 1;
- return function (t) {
- color.r = r(t);
- color.g = g(t);
- color.b = b(t);
- return color + '';
- };
- };
- }
-
- var rgbBasis = rgbSpline(src_basis);
- var rgbBasisClosed = rgbSpline(basisClosed); // CONCATENATED MODULE: ../node_modules/d3-interpolate/src/array.js
-
- /* harmony default export */ function src_array(a, b) {
- return (isNumberArray(b) ? numberArray : genericArray)(a, b);
- }
-
- function genericArray(a, b) {
- var nb = b ? b.length : 0,
- na = a ? Math.min(nb, a.length) : 0,
- x = new Array(na),
- c = new Array(nb),
- i;
-
- for (i = 0; i < na; ++i) x[i] = value(a[i], b[i]);
- for (; i < nb; ++i) c[i] = b[i];
-
- return function (t) {
- for (i = 0; i < na; ++i) c[i] = x[i](t);
- return c;
- };
- } // CONCATENATED MODULE: ../node_modules/d3-interpolate/src/date.js
-
- /* harmony default export */ function date(a, b) {
- var d = new Date();
- return (
- (a = +a),
- (b = +b),
- function (t) {
- return d.setTime(a * (1 - t) + b * t), d;
- }
- );
- } // CONCATENATED MODULE: ../node_modules/d3-interpolate/src/number.js
-
- /* harmony default export */ function src_number(a, b) {
- return (
- (a = +a),
- (b = +b),
- function (t) {
- return a * (1 - t) + b * t;
- }
- );
- } // CONCATENATED MODULE: ../node_modules/d3-interpolate/src/object.js
-
- /* harmony default export */ function object(a, b) {
- var i = {},
- c = {},
- k;
-
- if (a === null || typeof a !== 'object') a = {};
- if (b === null || typeof b !== 'object') b = {};
-
- for (k in b) {
- if (k in a) {
- i[k] = value(a[k], b[k]);
- } else {
- c[k] = b[k];
- }
- }
-
- return function (t) {
- for (k in i) c[k] = i[k](t);
- return c;
- };
- } // CONCATENATED MODULE: ../node_modules/d3-interpolate/src/string.js
-
- var reA = /[-+]?(?:\d+\.?\d*|\.?\d+)(?:[eE][-+]?\d+)?/g,
- reB = new RegExp(reA.source, 'g');
-
- function zero(b) {
- return function () {
- return b;
- };
- }
-
- function one(b) {
- return function (t) {
- return b(t) + '';
- };
- }
-
- /* harmony default export */ function string(a, b) {
- var bi = (reA.lastIndex = reB.lastIndex = 0), // scan index for next number in b
- am, // current match in a
- bm, // current match in b
- bs, // string preceding current number in b, if any
- i = -1, // index in s
- s = [], // string constants and placeholders
- q = []; // number interpolators
-
- // Coerce inputs to strings.
- (a = a + ''), (b = b + '');
-
- // Interpolate pairs of numbers in a & b.
- while ((am = reA.exec(a)) && (bm = reB.exec(b))) {
- if ((bs = bm.index) > bi) {
- // a string precedes the next number in b
- bs = b.slice(bi, bs);
- if (s[i]) s[i] += bs;
- // coalesce with previous string
- else s[++i] = bs;
- }
- if ((am = am[0]) === (bm = bm[0])) {
- // numbers in a & b match
- if (s[i]) s[i] += bm;
- // coalesce with previous string
- else s[++i] = bm;
- } else {
- // interpolate non-matching numbers
- s[++i] = null;
- q.push({ i: i, x: src_number(am, bm) });
- }
- bi = reB.lastIndex;
- }
-
- // Add remains of b.
- if (bi < b.length) {
- bs = b.slice(bi);
- if (s[i]) s[i] += bs;
- // coalesce with previous string
- else s[++i] = bs;
- }
-
- // Special optimization for only a single match.
- // Otherwise, interpolate each of the numbers and rejoin the string.
- return s.length < 2
- ? q[0]
- ? one(q[0].x)
- : zero(b)
- : ((b = q.length),
- function (t) {
- for (var i = 0, o; i < b; ++i) s[(o = q[i]).i] = o.x(t);
- return s.join('');
- });
- } // CONCATENATED MODULE: ../node_modules/d3-interpolate/src/numberArray.js
-
- /* harmony default export */ function src_numberArray(a, b) {
- if (!b) b = [];
- var n = a ? Math.min(b.length, a.length) : 0,
- c = b.slice(),
- i;
- return function (t) {
- for (i = 0; i < n; ++i) c[i] = a[i] * (1 - t) + b[i] * t;
- return c;
- };
- }
-
- function numberArray_isNumberArray(x) {
- return ArrayBuffer.isView(x) && !(x instanceof DataView);
- } // CONCATENATED MODULE: ../node_modules/d3-interpolate/src/value.js
-
- /* harmony default export */ function value(a, b) {
- var t = typeof b,
- c;
- return b == null || t === 'boolean'
- ? d3_interpolate_src_constant(b)
- : (t === 'number'
- ? src_number
- : t === 'string'
- ? (c = color(b))
- ? ((b = c), rgb)
- : string
- : b instanceof color
- ? rgb
- : b instanceof Date
- ? date
- : numberArray_isNumberArray(b)
- ? src_numberArray
- : Array.isArray(b)
- ? genericArray
- : (typeof b.valueOf !== 'function' &&
- typeof b.toString !== 'function') ||
- isNaN(b)
- ? object
- : src_number)(a, b);
- } // CONCATENATED MODULE: ../node_modules/d3-interpolate/src/round.js
-
- /* harmony default export */ function round(a, b) {
- return (
- (a = +a),
- (b = +b),
- function (t) {
- return Math.round(a * (1 - t) + b * t);
- }
- );
- } // CONCATENATED MODULE: ../node_modules/d3-scale/src/constant.js
-
- function constants(x) {
- return function () {
- return x;
- };
- } // CONCATENATED MODULE: ../node_modules/d3-scale/src/number.js
-
- function number_number(x) {
- return +x;
- } // CONCATENATED MODULE: ../node_modules/d3-scale/src/continuous.js
-
- var unit = [0, 1];
-
- function continuous_identity(x) {
- return x;
- }
-
- function normalize(a, b) {
- return (b -= a = +a)
- ? function (x) {
- return (x - a) / b;
- }
- : constants(isNaN(b) ? NaN : 0.5);
- }
-
- function clamper(a, b) {
- var t;
- if (a > b) (t = a), (a = b), (b = t);
- return function (x) {
- return Math.max(a, Math.min(b, x));
- };
- }
-
- // normalize(a, b)(x) takes a domain value x in [a,b] and returns the corresponding parameter t in [0,1].
- // interpolate(a, b)(t) takes a parameter t in [0,1] and returns the corresponding range value x in [a,b].
- function bimap(domain, range, interpolate) {
- var d0 = domain[0],
- d1 = domain[1],
- r0 = range[0],
- r1 = range[1];
- if (d1 < d0) (d0 = normalize(d1, d0)), (r0 = interpolate(r1, r0));
- else (d0 = normalize(d0, d1)), (r0 = interpolate(r0, r1));
- return function (x) {
- return r0(d0(x));
- };
- }
-
- function polymap(domain, range, interpolate) {
- var j = Math.min(domain.length, range.length) - 1,
- d = new Array(j),
- r = new Array(j),
- i = -1;
-
- // Reverse descending domains.
- if (domain[j] < domain[0]) {
- domain = domain.slice().reverse();
- range = range.slice().reverse();
- }
-
- while (++i < j) {
- d[i] = normalize(domain[i], domain[i + 1]);
- r[i] = interpolate(range[i], range[i + 1]);
- }
-
- return function (x) {
- var i = bisect(domain, x, 1, j) - 1;
- return r[i](d[i](x));
- };
- }
-
- function copy(source, target) {
- return target
- .domain(source.domain())
- .range(source.range())
- .interpolate(source.interpolate())
- .clamp(source.clamp())
- .unknown(source.unknown());
- }
-
- function transformer() {
- var domain = unit,
- range = unit,
- interpolate = value,
- transform,
- untransform,
- unknown,
- clamp = continuous_identity,
- piecewise,
- output,
- input;
-
- function rescale() {
- var n = Math.min(domain.length, range.length);
- if (clamp !== continuous_identity)
- clamp = clamper(domain[0], domain[n - 1]);
- piecewise = n > 2 ? polymap : bimap;
- output = input = null;
- return scale;
- }
-
- function scale(x) {
- return x == null || isNaN((x = +x))
- ? unknown
- : (
- output ||
- (output = piecewise(domain.map(transform), range, interpolate))
- )(transform(clamp(x)));
- }
-
- scale.invert = function (y) {
- return clamp(
- untransform(
- (
- input ||
- (input = piecewise(range, domain.map(transform), src_number))
- )(y),
- ),
- );
- };
-
- scale.domain = function (_) {
- return arguments.length
- ? ((domain = Array.from(_, number_number)), rescale())
- : domain.slice();
- };
-
- scale.range = function (_) {
- return arguments.length
- ? ((range = Array.from(_)), rescale())
- : range.slice();
- };
-
- scale.rangeRound = function (_) {
- return (range = Array.from(_)), (interpolate = round), rescale();
- };
-
- scale.clamp = function (_) {
- return arguments.length
- ? ((clamp = _ ? true : continuous_identity), rescale())
- : clamp !== continuous_identity;
- };
-
- scale.interpolate = function (_) {
- return arguments.length ? ((interpolate = _), rescale()) : interpolate;
- };
-
- scale.unknown = function (_) {
- return arguments.length ? ((unknown = _), scale) : unknown;
- };
-
- return function (t, u) {
- (transform = t), (untransform = u);
- return rescale();
- };
- }
-
- function continuous() {
- return transformer()(continuous_identity, continuous_identity);
- } // CONCATENATED MODULE: ../node_modules/d3-scale/src/init.js
-
- function initRange(domain, range) {
- switch (arguments.length) {
- case 0:
- break;
- case 1:
- this.range(domain);
- break;
- default:
- this.range(range).domain(domain);
- break;
- }
- return this;
- }
-
- function initInterpolator(domain, interpolator) {
- switch (arguments.length) {
- case 0:
- break;
- case 1: {
- if (typeof domain === 'function') this.interpolator(domain);
- else this.range(domain);
- break;
- }
- default: {
- this.domain(domain);
- if (typeof interpolator === 'function')
- this.interpolator(interpolator);
- else this.range(interpolator);
- break;
- }
- }
- return this;
- } // CONCATENATED MODULE: ../node_modules/d3-format/src/precisionPrefix.js
-
- /* harmony default export */ function precisionPrefix(step, value) {
- return Math.max(
- 0,
- Math.max(-8, Math.min(8, Math.floor(exponent(value) / 3))) * 3 -
- exponent(Math.abs(step)),
- );
- } // CONCATENATED MODULE: ../node_modules/d3-format/src/precisionRound.js
-
- /* harmony default export */ function precisionRound(step, max) {
- (step = Math.abs(step)), (max = Math.abs(max) - step);
- return Math.max(0, exponent(max) - exponent(step)) + 1;
- } // CONCATENATED MODULE: ../node_modules/d3-format/src/precisionFixed.js
-
- /* harmony default export */ function precisionFixed(step) {
- return Math.max(0, -exponent(Math.abs(step)));
- } // CONCATENATED MODULE: ../node_modules/d3-scale/src/tickFormat.js
-
- function tickFormat(start, stop, count, specifier) {
- var step = tickStep(start, stop, count),
- precision;
- specifier = formatSpecifier(specifier == null ? ',f' : specifier);
- switch (specifier.type) {
- case 's': {
- var value = Math.max(Math.abs(start), Math.abs(stop));
- if (
- specifier.precision == null &&
- !isNaN((precision = precisionPrefix(step, value)))
- )
- specifier.precision = precision;
- return formatPrefix(specifier, value);
- }
- case '':
- case 'e':
- case 'g':
- case 'p':
- case 'r': {
- if (
- specifier.precision == null &&
- !isNaN(
- (precision = precisionRound(
- step,
- Math.max(Math.abs(start), Math.abs(stop)),
- )),
- )
- )
- specifier.precision = precision - (specifier.type === 'e');
- break;
- }
- case 'f':
- case '%': {
- if (
- specifier.precision == null &&
- !isNaN((precision = precisionFixed(step)))
- )
- specifier.precision = precision - (specifier.type === '%') * 2;
- break;
- }
- }
- return format(specifier);
- } // CONCATENATED MODULE: ../node_modules/d3-scale/src/linear.js
-
- function linearish(scale) {
- var domain = scale.domain;
-
- scale.ticks = function (count) {
- var d = domain();
- return ticks(d[0], d[d.length - 1], count == null ? 10 : count);
- };
-
- scale.tickFormat = function (count, specifier) {
- var d = domain();
- return tickFormat(
- d[0],
- d[d.length - 1],
- count == null ? 10 : count,
- specifier,
- );
- };
-
- scale.nice = function (count) {
- if (count == null) count = 10;
-
- var d = domain();
- var i0 = 0;
- var i1 = d.length - 1;
- var start = d[i0];
- var stop = d[i1];
- var prestep;
- var step;
- var maxIter = 10;
-
- if (stop < start) {
- (step = start), (start = stop), (stop = step);
- (step = i0), (i0 = i1), (i1 = step);
- }
-
- while (maxIter-- > 0) {
- step = tickIncrement(start, stop, count);
- if (step === prestep) {
- d[i0] = start;
- d[i1] = stop;
- return domain(d);
- } else if (step > 0) {
- start = Math.floor(start / step) * step;
- stop = Math.ceil(stop / step) * step;
- } else if (step < 0) {
- start = Math.ceil(start * step) / step;
- stop = Math.floor(stop * step) / step;
- } else {
- break;
- }
- prestep = step;
- }
-
- return scale;
- };
-
- return scale;
- }
-
- function linear_linear() {
- var scale = continuous();
-
- scale.copy = function () {
- return copy(scale, linear_linear());
- };
-
- initRange.apply(scale, arguments);
-
- return linearish(scale);
- } // CONCATENATED MODULE: ../node_modules/d3-ease/src/cubic.js
-
- function cubicIn(t) {
- return t * t * t;
- }
-
- function cubicOut(t) {
- return --t * t * t + 1;
- }
-
- function cubicInOut(t) {
- return ((t *= 2) <= 1 ? t * t * t : (t -= 2) * t * t + 2) / 2;
- } // CONCATENATED MODULE: ../node_modules/d3-dispatch/src/dispatch.js
-
- var noop = { value: () => {} };
-
- function dispatch_dispatch() {
- for (var i = 0, n = arguments.length, _ = {}, t; i < n; ++i) {
- if (!(t = arguments[i] + '') || t in _ || /[\s.]/.test(t))
- throw new Error('illegal type: ' + t);
- _[t] = [];
- }
- return new Dispatch(_);
- }
-
- function Dispatch(_) {
- this._ = _;
- }
-
- function dispatch_parseTypenames(typenames, types) {
- return typenames
- .trim()
- .split(/^|\s+/)
- .map(function (t) {
- var name = '',
- i = t.indexOf('.');
- if (i >= 0) (name = t.slice(i + 1)), (t = t.slice(0, i));
- if (t && !types.hasOwnProperty(t))
- throw new Error('unknown type: ' + t);
- return { type: t, name: name };
- });
- }
-
- Dispatch.prototype = dispatch_dispatch.prototype = {
- constructor: Dispatch,
- on: function (typename, callback) {
- var _ = this._,
- T = dispatch_parseTypenames(typename + '', _),
- t,
- i = -1,
- n = T.length;
-
- // If no callback was specified, return the callback of the given type and name.
- if (arguments.length < 2) {
- while (++i < n)
- if ((t = (typename = T[i]).type) && (t = get(_[t], typename.name)))
- return t;
- return;
- }
-
- // If a type was specified, set the callback for the given type and name.
- // Otherwise, if a null callback was specified, remove callbacks of the given name.
- if (callback != null && typeof callback !== 'function')
- throw new Error('invalid callback: ' + callback);
- while (++i < n) {
- if ((t = (typename = T[i]).type))
- _[t] = set(_[t], typename.name, callback);
- else if (callback == null)
- for (t in _) _[t] = set(_[t], typename.name, null);
- }
-
- return this;
- },
- copy: function () {
- var copy = {},
- _ = this._;
- for (var t in _) copy[t] = _[t].slice();
- return new Dispatch(copy);
- },
- call: function (type, that) {
- if ((n = arguments.length - 2) > 0)
- for (var args = new Array(n), i = 0, n, t; i < n; ++i)
- args[i] = arguments[i + 2];
- if (!this._.hasOwnProperty(type))
- throw new Error('unknown type: ' + type);
- for (t = this._[type], i = 0, n = t.length; i < n; ++i)
- t[i].value.apply(that, args);
- },
- apply: function (type, that, args) {
- if (!this._.hasOwnProperty(type))
- throw new Error('unknown type: ' + type);
- for (var t = this._[type], i = 0, n = t.length; i < n; ++i)
- t[i].value.apply(that, args);
- },
- };
-
- function get(type, name) {
- for (var i = 0, n = type.length, c; i < n; ++i) {
- if ((c = type[i]).name === name) {
- return c.value;
- }
- }
- }
-
- function set(type, name, callback) {
- for (var i = 0, n = type.length; i < n; ++i) {
- if (type[i].name === name) {
- (type[i] = noop), (type = type.slice(0, i).concat(type.slice(i + 1)));
- break;
- }
- }
- if (callback != null) type.push({ name: name, value: callback });
- return type;
- }
-
- /* harmony default export */ const src_dispatch = dispatch_dispatch; // CONCATENATED MODULE: ../node_modules/d3-timer/src/timer.js
-
- var timer_frame = 0, // is an animation frame pending?
- timeout = 0, // is a timeout pending?
- interval = 0, // are any timers active?
- pokeDelay = 1000, // how frequently we check for clock skew
- taskHead,
- taskTail,
- clockLast = 0,
- clockNow = 0,
- clockSkew = 0,
- clock =
- typeof performance === 'object' && performance.now ? performance : Date,
- setFrame =
- typeof window === 'object' && window.requestAnimationFrame
- ? window.requestAnimationFrame.bind(window)
- : function (f) {
- setTimeout(f, 17);
- };
-
- function now() {
- return (
- clockNow || (setFrame(clearNow), (clockNow = clock.now() + clockSkew))
- );
- }
-
- function clearNow() {
- clockNow = 0;
- }
-
- function Timer() {
- this._call = this._time = this._next = null;
- }
-
- Timer.prototype = timer.prototype = {
- constructor: Timer,
- restart: function (callback, delay, time) {
- if (typeof callback !== 'function')
- throw new TypeError('callback is not a function');
- time = (time == null ? now() : +time) + (delay == null ? 0 : +delay);
- if (!this._next && taskTail !== this) {
- if (taskTail) taskTail._next = this;
- else taskHead = this;
- taskTail = this;
- }
- this._call = callback;
- this._time = time;
- sleep();
- },
- stop: function () {
- if (this._call) {
- this._call = null;
- this._time = Infinity;
- sleep();
- }
- },
- };
-
- function timer(callback, delay, time) {
- var t = new Timer();
- t.restart(callback, delay, time);
- return t;
- }
-
- function timerFlush() {
- now(); // Get the current time, if not already set.
- ++timer_frame; // Pretend we’ve set an alarm, if we haven’t already.
- var t = taskHead,
- e;
- while (t) {
- if ((e = clockNow - t._time) >= 0) t._call.call(undefined, e);
- t = t._next;
- }
- --timer_frame;
- }
-
- function wake() {
- clockNow = (clockLast = clock.now()) + clockSkew;
- timer_frame = timeout = 0;
- try {
- timerFlush();
- } finally {
- timer_frame = 0;
- nap();
- clockNow = 0;
- }
- }
-
- function poke() {
- var now = clock.now(),
- delay = now - clockLast;
- if (delay > pokeDelay) (clockSkew -= delay), (clockLast = now);
- }
-
- function nap() {
- var t0,
- t1 = taskHead,
- t2,
- time = Infinity;
- while (t1) {
- if (t1._call) {
- if (time > t1._time) time = t1._time;
- (t0 = t1), (t1 = t1._next);
- } else {
- (t2 = t1._next), (t1._next = null);
- t1 = t0 ? (t0._next = t2) : (taskHead = t2);
- }
- }
- taskTail = t0;
- sleep(time);
- }
-
- function sleep(time) {
- if (timer_frame) return; // Soonest alarm already set, or will be.
- if (timeout) timeout = clearTimeout(timeout);
- var delay = time - clockNow; // Strictly less than if we recomputed clockNow.
- if (delay > 24) {
- if (time < Infinity)
- timeout = setTimeout(wake, time - clock.now() - clockSkew);
- if (interval) interval = clearInterval(interval);
- } else {
- if (!interval)
- (clockLast = clock.now()), (interval = setInterval(poke, pokeDelay));
- (timer_frame = 1), setFrame(wake);
- }
- } // CONCATENATED MODULE: ../node_modules/d3-timer/src/timeout.js
-
- /* harmony default export */ function src_timeout(callback, delay, time) {
- var t = new Timer();
- delay = delay == null ? 0 : +delay;
- t.restart(
- (elapsed) => {
- t.stop();
- callback(elapsed + delay);
- },
- delay,
- time,
- );
- return t;
- } // CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/schedule.js
-
- var emptyOn = src_dispatch('start', 'end', 'cancel', 'interrupt');
- var emptyTween = [];
-
- var CREATED = 0;
- var SCHEDULED = 1;
- var STARTING = 2;
- var STARTED = 3;
- var RUNNING = 4;
- var ENDING = 5;
- var ENDED = 6;
-
- /* harmony default export */ function schedule(
- node,
- name,
- id,
- index,
- group,
- timing,
- ) {
- var schedules = node.__transition;
- if (!schedules) node.__transition = {};
- else if (id in schedules) return;
- create(node, id, {
- name: name,
- index: index, // For context during callback.
- group: group, // For context during callback.
- on: emptyOn,
- tween: emptyTween,
- time: timing.time,
- delay: timing.delay,
- duration: timing.duration,
- ease: timing.ease,
- timer: null,
- state: CREATED,
- });
- }
-
- function init(node, id) {
- var schedule = schedule_get(node, id);
- if (schedule.state > CREATED)
- throw new Error('too late; already scheduled');
- return schedule;
- }
-
- function schedule_set(node, id) {
- var schedule = schedule_get(node, id);
- if (schedule.state > STARTED)
- throw new Error('too late; already running');
- return schedule;
- }
-
- function schedule_get(node, id) {
- var schedule = node.__transition;
- if (!schedule || !(schedule = schedule[id]))
- throw new Error('transition not found');
- return schedule;
- }
-
- function create(node, id, self) {
- var schedules = node.__transition,
- tween;
-
- // Initialize the self timer when the transition is created.
- // Note the actual delay is not known until the first callback!
- schedules[id] = self;
- self.timer = timer(schedule, 0, self.time);
-
- function schedule(elapsed) {
- self.state = SCHEDULED;
- self.timer.restart(start, self.delay, self.time);
-
- // If the elapsed delay is less than our first sleep, start immediately.
- if (self.delay <= elapsed) start(elapsed - self.delay);
- }
-
- function start(elapsed) {
- var i, j, n, o;
-
- // If the state is not SCHEDULED, then we previously errored on start.
- if (self.state !== SCHEDULED) return stop();
-
- for (i in schedules) {
- o = schedules[i];
- if (o.name !== self.name) continue;
-
- // While this element already has a starting transition during this frame,
- // defer starting an interrupting transition until that transition has a
- // chance to tick (and possibly end); see d3/d3-transition#54!
- if (o.state === STARTED) return src_timeout(start);
-
- // Interrupt the active transition, if any.
- if (o.state === RUNNING) {
- o.state = ENDED;
- o.timer.stop();
- o.on.call('interrupt', node, node.__data__, o.index, o.group);
- delete schedules[i];
- }
-
- // Cancel any pre-empted transitions.
- else if (+i < id) {
- o.state = ENDED;
- o.timer.stop();
- o.on.call('cancel', node, node.__data__, o.index, o.group);
- delete schedules[i];
- }
- }
-
- // Defer the first tick to end of the current frame; see d3/d3#1576.
- // Note the transition may be canceled after start and before the first tick!
- // Note this must be scheduled before the start event; see d3/d3-transition#16!
- // Assuming this is successful, subsequent callbacks go straight to tick.
- src_timeout(function () {
- if (self.state === STARTED) {
- self.state = RUNNING;
- self.timer.restart(tick, self.delay, self.time);
- tick(elapsed);
- }
- });
-
- // Dispatch the start event.
- // Note this must be done before the tween are initialized.
- self.state = STARTING;
- self.on.call('start', node, node.__data__, self.index, self.group);
- if (self.state !== STARTING) return; // interrupted
- self.state = STARTED;
-
- // Initialize the tween, deleting null tween.
- tween = new Array((n = self.tween.length));
- for (i = 0, j = -1; i < n; ++i) {
- if (
- (o = self.tween[i].value.call(
- node,
- node.__data__,
- self.index,
- self.group,
- ))
- ) {
- tween[++j] = o;
- }
- }
- tween.length = j + 1;
- }
-
- function tick(elapsed) {
- var t =
- elapsed < self.duration
- ? self.ease.call(null, elapsed / self.duration)
- : (self.timer.restart(stop), (self.state = ENDING), 1),
- i = -1,
- n = tween.length;
-
- while (++i < n) {
- tween[i].call(node, t);
- }
-
- // Dispatch the end event.
- if (self.state === ENDING) {
- self.on.call('end', node, node.__data__, self.index, self.group);
- stop();
- }
- }
-
- function stop() {
- self.state = ENDED;
- self.timer.stop();
- delete schedules[id];
- for (var i in schedules) return; // eslint-disable-line no-unused-vars
- delete node.__transition;
- }
- } // CONCATENATED MODULE: ../node_modules/d3-transition/src/interrupt.js
-
- /* harmony default export */ function interrupt(node, name) {
- var schedules = node.__transition,
- schedule,
- active,
- empty = true,
- i;
-
- if (!schedules) return;
-
- name = name == null ? null : name + '';
-
- for (i in schedules) {
- if ((schedule = schedules[i]).name !== name) {
- empty = false;
- continue;
- }
- active = schedule.state > STARTING && schedule.state < ENDING;
- schedule.state = ENDED;
- schedule.timer.stop();
- schedule.on.call(
- active ? 'interrupt' : 'cancel',
- node,
- node.__data__,
- schedule.index,
- schedule.group,
- );
- delete schedules[i];
- }
-
- if (empty) delete node.__transition;
- } // CONCATENATED MODULE: ../node_modules/d3-transition/src/selection/interrupt.js
-
- /* harmony default export */ function selection_interrupt(name) {
- return this.each(function () {
- interrupt(this, name);
- });
- } // CONCATENATED MODULE: ../node_modules/d3-interpolate/src/transform/decompose.js
-
- var degrees = 180 / Math.PI;
-
- var decompose_identity = {
- translateX: 0,
- translateY: 0,
- rotate: 0,
- skewX: 0,
- scaleX: 1,
- scaleY: 1,
- };
-
- /* harmony default export */ function decompose(a, b, c, d, e, f) {
- var scaleX, scaleY, skewX;
- if ((scaleX = Math.sqrt(a * a + b * b))) (a /= scaleX), (b /= scaleX);
- if ((skewX = a * c + b * d)) (c -= a * skewX), (d -= b * skewX);
- if ((scaleY = Math.sqrt(c * c + d * d)))
- (c /= scaleY), (d /= scaleY), (skewX /= scaleY);
- if (a * d < b * c)
- (a = -a), (b = -b), (skewX = -skewX), (scaleX = -scaleX);
- return {
- translateX: e,
- translateY: f,
- rotate: Math.atan2(b, a) * degrees,
- skewX: Math.atan(skewX) * degrees,
- scaleX: scaleX,
- scaleY: scaleY,
- };
- } // CONCATENATED MODULE: ../node_modules/d3-interpolate/src/transform/parse.js
-
- var svgNode;
-
- /* eslint-disable no-undef */
- function parseCss(value) {
- const m = new (typeof DOMMatrix === 'function'
- ? DOMMatrix
- : WebKitCSSMatrix)(value + '');
- return m.isIdentity
- ? decompose_identity
- : decompose(m.a, m.b, m.c, m.d, m.e, m.f);
- }
-
- function parseSvg(value) {
- if (value == null) return decompose_identity;
- if (!svgNode)
- svgNode = document.createElementNS('http://www.w3.org/2000/svg', 'g');
- svgNode.setAttribute('transform', value);
- if (!(value = svgNode.transform.baseVal.consolidate()))
- return decompose_identity;
- value = value.matrix;
- return decompose(value.a, value.b, value.c, value.d, value.e, value.f);
- } // CONCATENATED MODULE: ../node_modules/d3-interpolate/src/transform/index.js
-
- function interpolateTransform(parse, pxComma, pxParen, degParen) {
- function pop(s) {
- return s.length ? s.pop() + ' ' : '';
- }
-
- function translate(xa, ya, xb, yb, s, q) {
- if (xa !== xb || ya !== yb) {
- var i = s.push('translate(', null, pxComma, null, pxParen);
- q.push(
- { i: i - 4, x: src_number(xa, xb) },
- { i: i - 2, x: src_number(ya, yb) },
- );
- } else if (xb || yb) {
- s.push('translate(' + xb + pxComma + yb + pxParen);
- }
- }
-
- function rotate(a, b, s, q) {
- if (a !== b) {
- if (a - b > 180) b += 360;
- else if (b - a > 180) a += 360; // shortest path
- q.push({
- i: s.push(pop(s) + 'rotate(', null, degParen) - 2,
- x: src_number(a, b),
- });
- } else if (b) {
- s.push(pop(s) + 'rotate(' + b + degParen);
- }
- }
-
- function skewX(a, b, s, q) {
- if (a !== b) {
- q.push({
- i: s.push(pop(s) + 'skewX(', null, degParen) - 2,
- x: src_number(a, b),
- });
- } else if (b) {
- s.push(pop(s) + 'skewX(' + b + degParen);
- }
- }
-
- function scale(xa, ya, xb, yb, s, q) {
- if (xa !== xb || ya !== yb) {
- var i = s.push(pop(s) + 'scale(', null, ',', null, ')');
- q.push(
- { i: i - 4, x: src_number(xa, xb) },
- { i: i - 2, x: src_number(ya, yb) },
- );
- } else if (xb !== 1 || yb !== 1) {
- s.push(pop(s) + 'scale(' + xb + ',' + yb + ')');
- }
- }
-
- return function (a, b) {
- var s = [], // string constants and placeholders
- q = []; // number interpolators
- (a = parse(a)), (b = parse(b));
- translate(a.translateX, a.translateY, b.translateX, b.translateY, s, q);
- rotate(a.rotate, b.rotate, s, q);
- skewX(a.skewX, b.skewX, s, q);
- scale(a.scaleX, a.scaleY, b.scaleX, b.scaleY, s, q);
- a = b = null; // gc
- return function (t) {
- var i = -1,
- n = q.length,
- o;
- while (++i < n) s[(o = q[i]).i] = o.x(t);
- return s.join('');
- };
- };
- }
-
- var interpolateTransformCss = interpolateTransform(
- parseCss,
- 'px, ',
- 'px)',
- 'deg)',
- );
- var interpolateTransformSvg = interpolateTransform(
- parseSvg,
- ', ',
- ')',
- ')',
- ); // CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/tween.js
-
- function tweenRemove(id, name) {
- var tween0, tween1;
- return function () {
- var schedule = schedule_set(this, id),
- tween = schedule.tween;
-
- // If this node shared tween with the previous node,
- // just assign the updated shared tween and we’re done!
- // Otherwise, copy-on-write.
- if (tween !== tween0) {
- tween1 = tween0 = tween;
- for (var i = 0, n = tween1.length; i < n; ++i) {
- if (tween1[i].name === name) {
- tween1 = tween1.slice();
- tween1.splice(i, 1);
- break;
- }
- }
- }
-
- schedule.tween = tween1;
- };
- }
-
- function tweenFunction(id, name, value) {
- var tween0, tween1;
- if (typeof value !== 'function') throw new Error();
- return function () {
- var schedule = schedule_set(this, id),
- tween = schedule.tween;
-
- // If this node shared tween with the previous node,
- // just assign the updated shared tween and we’re done!
- // Otherwise, copy-on-write.
- if (tween !== tween0) {
- tween1 = (tween0 = tween).slice();
- for (
- var t = { name: name, value: value }, i = 0, n = tween1.length;
- i < n;
- ++i
- ) {
- if (tween1[i].name === name) {
- tween1[i] = t;
- break;
- }
- }
- if (i === n) tween1.push(t);
- }
-
- schedule.tween = tween1;
- };
- }
-
- /* harmony default export */ function tween(name, value) {
- var id = this._id;
-
- name += '';
-
- if (arguments.length < 2) {
- var tween = schedule_get(this.node(), id).tween;
- for (var i = 0, n = tween.length, t; i < n; ++i) {
- if ((t = tween[i]).name === name) {
- return t.value;
- }
- }
- return null;
- }
-
- return this.each(
- (value == null ? tweenRemove : tweenFunction)(id, name, value),
- );
- }
-
- function tweenValue(transition, name, value) {
- var id = transition._id;
-
- transition.each(function () {
- var schedule = schedule_set(this, id);
- (schedule.value || (schedule.value = {}))[name] = value.apply(
- this,
- arguments,
- );
- });
-
- return function (node) {
- return schedule_get(node, id).value[name];
- };
- } // CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/interpolate.js
-
- /* harmony default export */ function interpolate(a, b) {
- var c;
- return (typeof b === 'number'
- ? src_number
- : b instanceof color
- ? rgb
- : (c = color(b))
- ? ((b = c), rgb)
- : string)(a, b);
- } // CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/attr.js
-
- function attr_attrRemove(name) {
- return function () {
- this.removeAttribute(name);
- };
- }
-
- function attr_attrRemoveNS(fullname) {
- return function () {
- this.removeAttributeNS(fullname.space, fullname.local);
- };
- }
-
- function attr_attrConstant(name, interpolate, value1) {
- var string00,
- string1 = value1 + '',
- interpolate0;
- return function () {
- var string0 = this.getAttribute(name);
- return string0 === string1
- ? null
- : string0 === string00
- ? interpolate0
- : (interpolate0 = interpolate((string00 = string0), value1));
- };
- }
-
- function attr_attrConstantNS(fullname, interpolate, value1) {
- var string00,
- string1 = value1 + '',
- interpolate0;
- return function () {
- var string0 = this.getAttributeNS(fullname.space, fullname.local);
- return string0 === string1
- ? null
- : string0 === string00
- ? interpolate0
- : (interpolate0 = interpolate((string00 = string0), value1));
- };
- }
-
- function attr_attrFunction(name, interpolate, value) {
- var string00, string10, interpolate0;
- return function () {
- var string0,
- value1 = value(this),
- string1;
- if (value1 == null) return void this.removeAttribute(name);
- string0 = this.getAttribute(name);
- string1 = value1 + '';
- return string0 === string1
- ? null
- : string0 === string00 && string1 === string10
- ? interpolate0
- : ((string10 = string1),
- (interpolate0 = interpolate((string00 = string0), value1)));
- };
- }
-
- function attr_attrFunctionNS(fullname, interpolate, value) {
- var string00, string10, interpolate0;
- return function () {
- var string0,
- value1 = value(this),
- string1;
- if (value1 == null)
- return void this.removeAttributeNS(fullname.space, fullname.local);
- string0 = this.getAttributeNS(fullname.space, fullname.local);
- string1 = value1 + '';
- return string0 === string1
- ? null
- : string0 === string00 && string1 === string10
- ? interpolate0
- : ((string10 = string1),
- (interpolate0 = interpolate((string00 = string0), value1)));
- };
- }
-
- /* harmony default export */ function transition_attr(name, value) {
- var fullname = namespace(name),
- i = fullname === 'transform' ? interpolateTransformSvg : interpolate;
- return this.attrTween(
- name,
- typeof value === 'function'
- ? (fullname.local ? attr_attrFunctionNS : attr_attrFunction)(
- fullname,
- i,
- tweenValue(this, 'attr.' + name, value),
- )
- : value == null
- ? (fullname.local ? attr_attrRemoveNS : attr_attrRemove)(fullname)
- : (fullname.local ? attr_attrConstantNS : attr_attrConstant)(
- fullname,
- i,
- value,
- ),
- );
- } // CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/attrTween.js
-
- function attrInterpolate(name, i) {
- return function (t) {
- this.setAttribute(name, i.call(this, t));
- };
- }
-
- function attrInterpolateNS(fullname, i) {
- return function (t) {
- this.setAttributeNS(fullname.space, fullname.local, i.call(this, t));
- };
- }
-
- function attrTweenNS(fullname, value) {
- var t0, i0;
- function tween() {
- var i = value.apply(this, arguments);
- if (i !== i0) t0 = (i0 = i) && attrInterpolateNS(fullname, i);
- return t0;
- }
- tween._value = value;
- return tween;
- }
-
- function attrTween(name, value) {
- var t0, i0;
- function tween() {
- var i = value.apply(this, arguments);
- if (i !== i0) t0 = (i0 = i) && attrInterpolate(name, i);
- return t0;
- }
- tween._value = value;
- return tween;
- }
-
- /* harmony default export */ function transition_attrTween(name, value) {
- var key = 'attr.' + name;
- if (arguments.length < 2) return (key = this.tween(key)) && key._value;
- if (value == null) return this.tween(key, null);
- if (typeof value !== 'function') throw new Error();
- var fullname = namespace(name);
- return this.tween(
- key,
- (fullname.local ? attrTweenNS : attrTween)(fullname, value),
- );
- } // CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/delay.js
-
- function delayFunction(id, value) {
- return function () {
- init(this, id).delay = +value.apply(this, arguments);
- };
- }
-
- function delayConstant(id, value) {
- return (
- (value = +value),
- function () {
- init(this, id).delay = value;
- }
- );
- }
-
- /* harmony default export */ function delay(value) {
- var id = this._id;
-
- return arguments.length
- ? this.each(
- (typeof value === 'function' ? delayFunction : delayConstant)(
- id,
- value,
- ),
- )
- : schedule_get(this.node(), id).delay;
- } // CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/duration.js
-
- function durationFunction(id, value) {
- return function () {
- schedule_set(this, id).duration = +value.apply(this, arguments);
- };
- }
-
- function durationConstant(id, value) {
- return (
- (value = +value),
- function () {
- schedule_set(this, id).duration = value;
- }
- );
- }
-
- /* harmony default export */ function duration(value) {
- var id = this._id;
-
- return arguments.length
- ? this.each(
- (typeof value === 'function' ? durationFunction : durationConstant)(
- id,
- value,
- ),
- )
- : schedule_get(this.node(), id).duration;
- } // CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/ease.js
-
- function easeConstant(id, value) {
- if (typeof value !== 'function') throw new Error();
- return function () {
- schedule_set(this, id).ease = value;
- };
- }
-
- /* harmony default export */ function ease(value) {
- var id = this._id;
-
- return arguments.length
- ? this.each(easeConstant(id, value))
- : schedule_get(this.node(), id).ease;
- } // CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/easeVarying.js
-
- function easeVarying(id, value) {
- return function () {
- var v = value.apply(this, arguments);
- if (typeof v !== 'function') throw new Error();
- schedule_set(this, id).ease = v;
- };
- }
-
- /* harmony default export */ function transition_easeVarying(value) {
- if (typeof value !== 'function') throw new Error();
- return this.each(easeVarying(this._id, value));
- } // CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/filter.js
-
- /* harmony default export */ function transition_filter(match) {
- if (typeof match !== 'function') match = matcher(match);
-
- for (
- var groups = this._groups,
- m = groups.length,
- subgroups = new Array(m),
- j = 0;
- j < m;
- ++j
- ) {
- for (
- var group = groups[j],
- n = group.length,
- subgroup = (subgroups[j] = []),
- node,
- i = 0;
- i < n;
- ++i
- ) {
- if ((node = group[i]) && match.call(node, node.__data__, i, group)) {
- subgroup.push(node);
- }
- }
- }
-
- return new Transition(subgroups, this._parents, this._name, this._id);
- } // CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/merge.js
-
- /* harmony default export */ function transition_merge(transition) {
- if (transition._id !== this._id) throw new Error();
-
- for (
- var groups0 = this._groups,
- groups1 = transition._groups,
- m0 = groups0.length,
- m1 = groups1.length,
- m = Math.min(m0, m1),
- merges = new Array(m0),
- j = 0;
- j < m;
- ++j
- ) {
- for (
- var group0 = groups0[j],
- group1 = groups1[j],
- n = group0.length,
- merge = (merges[j] = new Array(n)),
- node,
- i = 0;
- i < n;
- ++i
- ) {
- if ((node = group0[i] || group1[i])) {
- merge[i] = node;
- }
- }
- }
-
- for (; j < m0; ++j) {
- merges[j] = groups0[j];
- }
-
- return new Transition(merges, this._parents, this._name, this._id);
- } // CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/on.js
-
- function start(name) {
- return (name + '')
- .trim()
- .split(/^|\s+/)
- .every(function (t) {
- var i = t.indexOf('.');
- if (i >= 0) t = t.slice(0, i);
- return !t || t === 'start';
- });
- }
-
- function onFunction(id, name, listener) {
- var on0,
- on1,
- sit = start(name) ? init : schedule_set;
- return function () {
- var schedule = sit(this, id),
- on = schedule.on;
-
- // If this node shared a dispatch with the previous node,
- // just assign the updated shared dispatch and we’re done!
- // Otherwise, copy-on-write.
- if (on !== on0) (on1 = (on0 = on).copy()).on(name, listener);
-
- schedule.on = on1;
- };
- }
-
- /* harmony default export */ function transition_on(name, listener) {
- var id = this._id;
-
- return arguments.length < 2
- ? schedule_get(this.node(), id).on.on(name)
- : this.each(onFunction(id, name, listener));
- } // CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/remove.js
-
- function removeFunction(id) {
- return function () {
- var parent = this.parentNode;
- for (var i in this.__transition) if (+i !== id) return;
- if (parent) parent.removeChild(this);
- };
- }
-
- /* harmony default export */ function transition_remove() {
- return this.on('end.remove', removeFunction(this._id));
- } // CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/select.js
-
- /* harmony default export */ function transition_select(select) {
- var name = this._name,
- id = this._id;
-
- if (typeof select !== 'function') select = selector(select);
-
- for (
- var groups = this._groups,
- m = groups.length,
- subgroups = new Array(m),
- j = 0;
- j < m;
- ++j
- ) {
- for (
- var group = groups[j],
- n = group.length,
- subgroup = (subgroups[j] = new Array(n)),
- node,
- subnode,
- i = 0;
- i < n;
- ++i
- ) {
- if (
- (node = group[i]) &&
- (subnode = select.call(node, node.__data__, i, group))
- ) {
- if ('__data__' in node) subnode.__data__ = node.__data__;
- subgroup[i] = subnode;
- schedule(
- subgroup[i],
- name,
- id,
- i,
- subgroup,
- schedule_get(node, id),
- );
- }
- }
- }
-
- return new Transition(subgroups, this._parents, name, id);
- } // CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/selectAll.js
-
- /* harmony default export */ function transition_selectAll(select) {
- var name = this._name,
- id = this._id;
-
- if (typeof select !== 'function') select = selectorAll(select);
-
- for (
- var groups = this._groups,
- m = groups.length,
- subgroups = [],
- parents = [],
- j = 0;
- j < m;
- ++j
- ) {
- for (var group = groups[j], n = group.length, node, i = 0; i < n; ++i) {
- if ((node = group[i])) {
- for (
- var children = select.call(node, node.__data__, i, group),
- child,
- inherit = schedule_get(node, id),
- k = 0,
- l = children.length;
- k < l;
- ++k
- ) {
- if ((child = children[k])) {
- schedule(child, name, id, k, children, inherit);
- }
- }
- subgroups.push(children);
- parents.push(node);
- }
- }
- }
-
- return new Transition(subgroups, parents, name, id);
- } // CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/selection.js
-
- var selection_Selection = src_selection.prototype.constructor;
-
- /* harmony default export */ function transition_selection() {
- return new selection_Selection(this._groups, this._parents);
- } // CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/style.js
-
- function styleNull(name, interpolate) {
- var string00, string10, interpolate0;
- return function () {
- var string0 = styleValue(this, name),
- string1 = (this.style.removeProperty(name), styleValue(this, name));
- return string0 === string1
- ? null
- : string0 === string00 && string1 === string10
- ? interpolate0
- : (interpolate0 = interpolate(
- (string00 = string0),
- (string10 = string1),
- ));
- };
- }
-
- function style_styleRemove(name) {
- return function () {
- this.style.removeProperty(name);
- };
- }
-
- function style_styleConstant(name, interpolate, value1) {
- var string00,
- string1 = value1 + '',
- interpolate0;
- return function () {
- var string0 = styleValue(this, name);
- return string0 === string1
- ? null
- : string0 === string00
- ? interpolate0
- : (interpolate0 = interpolate((string00 = string0), value1));
- };
- }
-
- function style_styleFunction(name, interpolate, value) {
- var string00, string10, interpolate0;
- return function () {
- var string0 = styleValue(this, name),
- value1 = value(this),
- string1 = value1 + '';
- if (value1 == null)
- string1 = value1 =
- (this.style.removeProperty(name), styleValue(this, name));
- return string0 === string1
- ? null
- : string0 === string00 && string1 === string10
- ? interpolate0
- : ((string10 = string1),
- (interpolate0 = interpolate((string00 = string0), value1)));
- };
- }
-
- function styleMaybeRemove(id, name) {
- var on0,
- on1,
- listener0,
- key = 'style.' + name,
- event = 'end.' + key,
- remove;
- return function () {
- var schedule = schedule_set(this, id),
- on = schedule.on,
- listener =
- schedule.value[key] == null
- ? remove || (remove = style_styleRemove(name))
- : undefined;
-
- // If this node shared a dispatch with the previous node,
- // just assign the updated shared dispatch and we’re done!
- // Otherwise, copy-on-write.
- if (on !== on0 || listener0 !== listener)
- (on1 = (on0 = on).copy()).on(event, (listener0 = listener));
-
- schedule.on = on1;
- };
- }
-
- /* harmony default export */ function transition_style(
- name,
- value,
- priority,
- ) {
- var i =
- (name += '') === 'transform' ? interpolateTransformCss : interpolate;
- return value == null
- ? this.styleTween(name, styleNull(name, i)).on(
- 'end.style.' + name,
- style_styleRemove(name),
- )
- : typeof value === 'function'
- ? this.styleTween(
- name,
- style_styleFunction(
- name,
- i,
- tweenValue(this, 'style.' + name, value),
- ),
- ).each(styleMaybeRemove(this._id, name))
- : this.styleTween(
- name,
- style_styleConstant(name, i, value),
- priority,
- ).on('end.style.' + name, null);
- } // CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/styleTween.js
-
- function styleInterpolate(name, i, priority) {
- return function (t) {
- this.style.setProperty(name, i.call(this, t), priority);
- };
- }
-
- function styleTween(name, value, priority) {
- var t, i0;
- function tween() {
- var i = value.apply(this, arguments);
- if (i !== i0) t = (i0 = i) && styleInterpolate(name, i, priority);
- return t;
- }
- tween._value = value;
- return tween;
- }
-
- /* harmony default export */ function transition_styleTween(
- name,
- value,
- priority,
- ) {
- var key = 'style.' + (name += '');
- if (arguments.length < 2) return (key = this.tween(key)) && key._value;
- if (value == null) return this.tween(key, null);
- if (typeof value !== 'function') throw new Error();
- return this.tween(
- key,
- styleTween(name, value, priority == null ? '' : priority),
- );
- } // CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/text.js
-
- function text_textConstant(value) {
- return function () {
- this.textContent = value;
- };
- }
-
- function text_textFunction(value) {
- return function () {
- var value1 = value(this);
- this.textContent = value1 == null ? '' : value1;
- };
- }
-
- /* harmony default export */ function transition_text(value) {
- return this.tween(
- 'text',
- typeof value === 'function'
- ? text_textFunction(tweenValue(this, 'text', value))
- : text_textConstant(value == null ? '' : value + ''),
- );
- } // CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/textTween.js
-
- function textInterpolate(i) {
- return function (t) {
- this.textContent = i.call(this, t);
- };
- }
-
- function textTween(value) {
- var t0, i0;
- function tween() {
- var i = value.apply(this, arguments);
- if (i !== i0) t0 = (i0 = i) && textInterpolate(i);
- return t0;
- }
- tween._value = value;
- return tween;
- }
-
- /* harmony default export */ function transition_textTween(value) {
- var key = 'text';
- if (arguments.length < 1) return (key = this.tween(key)) && key._value;
- if (value == null) return this.tween(key, null);
- if (typeof value !== 'function') throw new Error();
- return this.tween(key, textTween(value));
- } // CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/transition.js
-
- /* harmony default export */ function transition() {
- var name = this._name,
- id0 = this._id,
- id1 = newId();
-
- for (var groups = this._groups, m = groups.length, j = 0; j < m; ++j) {
- for (var group = groups[j], n = group.length, node, i = 0; i < n; ++i) {
- if ((node = group[i])) {
- var inherit = schedule_get(node, id0);
- schedule(node, name, id1, i, group, {
- time: inherit.time + inherit.delay + inherit.duration,
- delay: 0,
- duration: inherit.duration,
- ease: inherit.ease,
- });
- }
- }
- }
-
- return new Transition(groups, this._parents, name, id1);
- } // CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/end.js
-
- /* harmony default export */ function end() {
- var on0,
- on1,
- that = this,
- id = that._id,
- size = that.size();
- return new Promise(function (resolve, reject) {
- var cancel = { value: reject },
- end = {
- value: function () {
- if (--size === 0) resolve();
- },
- };
-
- that.each(function () {
- var schedule = schedule_set(this, id),
- on = schedule.on;
-
- // If this node shared a dispatch with the previous node,
- // just assign the updated shared dispatch and we’re done!
- // Otherwise, copy-on-write.
- if (on !== on0) {
- on1 = (on0 = on).copy();
- on1._.cancel.push(cancel);
- on1._.interrupt.push(cancel);
- on1._.end.push(end);
- }
-
- schedule.on = on1;
- });
-
- // The selection was empty, resolve end immediately
- if (size === 0) resolve();
- });
- } // CONCATENATED MODULE: ../node_modules/d3-transition/src/transition/index.js
-
- var id = 0;
-
- function Transition(groups, parents, name, id) {
- this._groups = groups;
- this._parents = parents;
- this._name = name;
- this._id = id;
- }
-
- function transition_transition(name) {
- return src_selection().transition(name);
- }
-
- function newId() {
- return ++id;
- }
-
- var selection_prototype = src_selection.prototype;
-
- Transition.prototype = transition_transition.prototype = {
- constructor: Transition,
- select: transition_select,
- selectAll: transition_selectAll,
- selectChild: selection_prototype.selectChild,
- selectChildren: selection_prototype.selectChildren,
- filter: transition_filter,
- merge: transition_merge,
- selection: transition_selection,
- transition: transition,
- call: selection_prototype.call,
- nodes: selection_prototype.nodes,
- node: selection_prototype.node,
- size: selection_prototype.size,
- empty: selection_prototype.empty,
- each: selection_prototype.each,
- on: transition_on,
- attr: transition_attr,
- attrTween: transition_attrTween,
- style: transition_style,
- styleTween: transition_styleTween,
- text: transition_text,
- textTween: transition_textTween,
- remove: transition_remove,
- tween: tween,
- delay: delay,
- duration: duration,
- ease: ease,
- easeVarying: transition_easeVarying,
- end: end,
- [Symbol.iterator]: selection_prototype[Symbol.iterator],
- }; // CONCATENATED MODULE: ../node_modules/d3-transition/src/selection/transition.js
-
- var defaultTiming = {
- time: null, // Set on use.
- delay: 0,
- duration: 250,
- ease: cubicInOut,
- };
-
- function inherit(node, id) {
- var timing;
- while (!(timing = node.__transition) || !(timing = timing[id])) {
- if (!(node = node.parentNode)) {
- throw new Error(`transition ${id} not found`);
- }
- }
- return timing;
- }
-
- /* harmony default export */ function selection_transition(name) {
- var id, timing;
-
- if (name instanceof Transition) {
- (id = name._id), (name = name._name);
- } else {
- (id = newId()),
- ((timing = defaultTiming).time = now()),
- (name = name == null ? null : name + '');
- }
-
- for (var groups = this._groups, m = groups.length, j = 0; j < m; ++j) {
- for (var group = groups[j], n = group.length, node, i = 0; i < n; ++i) {
- if ((node = group[i])) {
- schedule(node, name, id, i, group, timing || inherit(node, id));
- }
- }
- }
-
- return new Transition(groups, this._parents, name, id);
- } // CONCATENATED MODULE: ../node_modules/d3-transition/src/selection/index.js
-
- src_selection.prototype.interrupt = selection_interrupt;
- src_selection.prototype.transition = selection_transition; // CONCATENATED MODULE: ../node_modules/d3-transition/src/index.js // CONCATENATED MODULE: ./colorUtils.js
-
- function generateHash(name) {
- // Return a vector (0.0->1.0) that is a hash of the input string.
- // The hash is computed to favor early characters over later ones, so
- // that strings with similar starts have similar vectors. Only the first
- // 6 characters are considered.
- const MAX_CHAR = 6;
-
- let hash = 0;
- let maxHash = 0;
- let weight = 1;
- const mod = 10;
-
- if (name) {
- for (let i = 0; i < name.length; i++) {
- if (i > MAX_CHAR) {
- break;
- }
- hash += weight * (name.charCodeAt(i) % mod);
- maxHash += weight * (mod - 1);
- weight *= 0.7;
- }
- if (maxHash > 0) {
- hash = hash / maxHash;
- }
- }
- return hash;
- }
-
- function generateColorVector(name) {
- let vector = 0;
- if (name) {
- const nameArr = name.split('`');
- if (nameArr.length > 1) {
- name = nameArr[nameArr.length - 1]; // drop module name if present
- }
- name = name.split('(')[0]; // drop extra info
- vector = generateHash(name);
- }
- return vector;
- } // CONCATENATED MODULE: ./colorScheme.js
-
- function calculateColor(hue, vector) {
- let r;
- let g;
- let b;
-
- if (hue === 'red') {
- r = 200 + Math.round(55 * vector);
- g = 50 + Math.round(80 * vector);
- b = g;
- } else if (hue === 'orange') {
- r = 190 + Math.round(65 * vector);
- g = 90 + Math.round(65 * vector);
- b = 0;
- } else if (hue === 'yellow') {
- r = 175 + Math.round(55 * vector);
- g = r;
- b = 50 + Math.round(20 * vector);
- } else if (hue === 'green') {
- r = 50 + Math.round(60 * vector);
- g = 200 + Math.round(55 * vector);
- b = r;
- } else if (hue === 'pastelgreen') {
- // rgb(163,195,72) - rgb(238,244,221)
- r = 163 + Math.round(75 * vector);
- g = 195 + Math.round(49 * vector);
- b = 72 + Math.round(149 * vector);
- } else if (hue === 'blue') {
- // rgb(91,156,221) - rgb(217,232,247)
- r = 91 + Math.round(126 * vector);
- g = 156 + Math.round(76 * vector);
- b = 221 + Math.round(26 * vector);
- } else if (hue === 'aqua') {
- r = 50 + Math.round(60 * vector);
- g = 165 + Math.round(55 * vector);
- b = g;
- } else if (hue === 'cold') {
- r = 0 + Math.round(55 * (1 - vector));
- g = 0 + Math.round(230 * (1 - vector));
- b = 200 + Math.round(55 * vector);
- } else {
- // original warm palette
- r = 200 + Math.round(55 * vector);
- g = 0 + Math.round(230 * (1 - vector));
- b = 0 + Math.round(55 * (1 - vector));
- }
-
- return 'rgb(' + r + ',' + g + ',' + b + ')';
- } // CONCATENATED MODULE: ./flamegraph.js
-
- /* harmony default export */ function flamegraph() {
- let w = 960; // graph width
- let h = null; // graph height
- let c = 18; // cell height
- let selection = null; // selection
- let tooltip = null; // tooltip
- let title = ''; // graph title
- let transitionDuration = 750;
- let transitionEase = cubicInOut; // tooltip offset
- let sort = false;
- let inverted = false; // invert the graph direction
- let clickHandler = null;
- let hoverHandler = null;
- let minFrameSize = 0;
- let detailsElement = null;
- let searchDetails = null;
- let selfValue = false;
- let resetHeightOnZoom = false;
- let scrollOnZoom = false;
- let minHeight = null;
- let computeDelta = false;
- let colorHue = null;
-
- let getName = function (d) {
- return d.data.n || d.data.name;
- };
-
- let getValue = function (d) {
- if ('v' in d) {
- return d.v;
- } else {
- return d.value;
- }
- };
-
- let getChildren = function (d) {
- return d.c || d.children;
- };
-
- let getLibtype = function (d) {
- return d.data.l || d.data.libtype;
- };
-
- let getDelta = function (d) {
- if ('d' in d.data) {
- return d.data.d;
- } else {
- return d.data.delta;
- }
- };
-
- let searchHandler = function (searchResults, searchSum, totalValue) {
- searchDetails = () => {
- if (detailsElement) {
- detailsElement.textContent =
- 'search: ' +
- searchSum +
- ' of ' +
- totalValue +
- ' total time ( ' +
- format('.3f')(100 * (searchSum / totalValue), 3) +
- '%)';
- }
- };
- searchDetails();
- };
- const originalSearchHandler = searchHandler;
-
- let searchMatch = (d, term, ignoreCase = false) => {
- if (!term) {
- return false;
- }
- let label = getName(d);
- if (ignoreCase) {
- term = term.toLowerCase();
- label = label.toLowerCase();
- }
- const re = new RegExp(term);
- return typeof label !== 'undefined' && label && label.match(re);
- };
- const originalSearchMatch = searchMatch;
-
- let detailsHandler = function (d) {
- if (detailsElement) {
- if (d) {
- detailsElement.textContent = d;
- } else {
- if (typeof searchDetails === 'function') {
- searchDetails();
- } else {
- detailsElement.textContent = '';
- }
- }
- }
- };
- const originalDetailsHandler = detailsHandler;
-
- let labelHandler = function (d) {
- return (
- getName(d) +
- ' (' +
- format('.3f')(100 * (d.x1 - d.x0), 3) +
- '%, ' +
- getValue(d) +
- ' ms)'
- );
- };
-
- let colorMapper = function (d) {
- return d.highlight ? '#E600E6' : colorHash(getName(d), getLibtype(d));
- };
- const originalColorMapper = colorMapper;
-
- function colorHash(name, libtype) {
- // Return a color for the given name and library type. The library type
- // selects the hue, and the name is hashed to a color in that hue.
-
- // default when libtype is not in use
- let hue = colorHue || 'warm';
-
- if (!colorHue && !(typeof libtype === 'undefined' || libtype === '')) {
- // Select hue. Order is important.
- hue = 'red';
- if (typeof name !== 'undefined' && name && name.match(/::/)) {
- hue = 'yellow';
- }
- if (libtype === 'kernel') {
- hue = 'orange';
- } else if (libtype === 'jit') {
- hue = 'green';
- } else if (libtype === 'inlined') {
- hue = 'aqua';
- }
- }
-
- const vector = generateColorVector(name);
- return calculateColor(hue, vector);
- }
-
- function show(d) {
- d.data.fade = false;
- d.data.hide = false;
- if (d.children) {
- d.children.forEach(show);
- }
- }
-
- function hideSiblings(node) {
- let child = node;
- let parent = child.parent;
- let children, i, sibling;
- while (parent) {
- children = parent.children;
- i = children.length;
- while (i--) {
- sibling = children[i];
- if (sibling !== child) {
- sibling.data.hide = true;
- }
- }
- child = parent;
- parent = child.parent;
- }
- }
-
- function fadeAncestors(d) {
- if (d.parent) {
- d.parent.data.fade = true;
- fadeAncestors(d.parent);
- }
- }
-
- function zoom(d) {
- if (tooltip) tooltip.hide();
- hideSiblings(d);
- show(d);
- fadeAncestors(d);
- update();
- if (scrollOnZoom) {
- const chartOffset = src_select(this).select('svg')._groups[0][0]
- .parentNode.offsetTop;
- const maxFrames = (window.innerHeight - chartOffset) / c;
- const frameOffset = (d.height - maxFrames + 10) * c;
- window.scrollTo({
- top: chartOffset + frameOffset,
- left: 0,
- behavior: 'smooth',
- });
- }
- if (typeof clickHandler === 'function') {
- clickHandler(d);
- }
- }
-
- function searchTree(d, term) {
- const results = [];
- let sum = 0;
-
- function searchInner(d, foundParent) {
- let found = false;
-
- if (searchMatch(d, term)) {
- d.highlight = true;
- found = true;
- if (!foundParent) {
- sum += getValue(d);
- }
- results.push(d);
- } else {
- d.highlight = false;
- }
-
- if (getChildren(d)) {
- getChildren(d).forEach(function (child) {
- searchInner(child, foundParent || found);
- });
- }
- }
- searchInner(d, false);
-
- return [results, sum];
- }
-
- function findTree(d, id) {
- if (d.id === id) {
- return d;
- } else {
- const children = getChildren(d);
- if (children) {
- for (let i = 0; i < children.length; i++) {
- const found = findTree(children[i], id);
- if (found) {
- return found;
- }
- }
- }
- }
- }
-
- function clear(d) {
- d.highlight = false;
- if (getChildren(d)) {
- getChildren(d).forEach(function (child) {
- clear(child);
- });
- }
- }
-
- function doSort(a, b) {
- if (typeof sort === 'function') {
- return sort(a, b);
- } else if (sort) {
- return ascending_ascending(getName(a), getName(b));
- }
- }
-
- const p = partition();
-
- function filterNodes(root) {
- let nodeList = root.descendants();
- if (minFrameSize > 0) {
- const kx = w / (root.x1 - root.x0);
- nodeList = nodeList.filter(function (el) {
- return (el.x1 - el.x0) * kx > minFrameSize;
- });
- }
- return nodeList;
- }
-
- function update() {
- selection.each(function (root) {
- const x = linear_linear().range([0, w]);
- const y = linear_linear().range([0, c]);
-
- reappraiseNode(root);
-
- if (sort) root.sort(doSort);
-
- p(root);
-
- const kx = w / (root.x1 - root.x0);
- function width(d) {
- return (d.x1 - d.x0) * kx;
- }
-
- const descendants = filterNodes(root);
- const svg = src_select(this).select('svg');
- svg.attr('width', w);
-
- let g = svg.selectAll('g').data(descendants, function (d) {
- return d.id;
- });
-
- // if height is not set: set height on first update, after nodes were filtered by minFrameSize
- if (!h || resetHeightOnZoom) {
- const maxDepth = Math.max.apply(
- null,
- descendants.map(function (n) {
- return n.depth;
- }),
- );
-
- h = (maxDepth + 3) * c;
- if (h < minHeight) h = minHeight;
-
- svg.attr('height', h);
- }
-
- g.transition()
- .duration(transitionDuration)
- .ease(transitionEase)
- .attr('transform', function (d) {
- return (
- 'translate(' +
- x(d.x0) +
- ',' +
- (inverted ? y(d.depth) : h - y(d.depth) - c) +
- ')'
- );
- });
-
- g.select('rect')
- .transition()
- .duration(transitionDuration)
- .ease(transitionEase)
- .attr('width', width);
-
- const node = g
- .enter()
- .append('svg:g')
- .attr('transform', function (d) {
- return (
- 'translate(' +
- x(d.x0) +
- ',' +
- (inverted ? y(d.depth) : h - y(d.depth) - c) +
- ')'
- );
- });
-
- node
- .append('svg:rect')
- .transition()
- .delay(transitionDuration / 2)
- .attr('width', width);
-
- if (!tooltip) {
- node.append('svg:title');
- }
-
- node.append('foreignObject').append('xhtml:div');
-
- // Now we have to re-select to see the new elements (why?).
- g = svg.selectAll('g').data(descendants, function (d) {
- return d.id;
- });
-
- g.attr('width', width)
- .attr('height', function (d) {
- return c;
- })
- .attr('name', function (d) {
- return getName(d);
- })
- .attr('class', function (d) {
- return d.data.fade ? 'frame fade' : 'frame';
- });
-
- g.select('rect')
- .attr('height', function (d) {
- return c;
- })
- .attr('fill', function (d) {
- return colorMapper(d);
- });
-
- if (!tooltip) {
- g.select('title').text(labelHandler);
- }
-
- g.select('foreignObject')
- .attr('width', width)
- .attr('height', function (d) {
- return c;
- })
- .select('div')
- .attr('class', 'd3-flame-graph-label')
- .style('display', function (d) {
- return width(d) < 35 ? 'none' : 'block';
- })
- .transition()
- .delay(transitionDuration)
- .text(getName);
-
- g.on('click', (_, d) => {
- zoom(d);
- });
-
- g.exit().remove();
-
- g.on('mouseover', function (_, d) {
- if (tooltip) tooltip.show(d, this);
- detailsHandler(labelHandler(d));
- if (typeof hoverHandler === 'function') {
- hoverHandler(d);
- }
- }).on('mouseout', function () {
- if (tooltip) tooltip.hide();
- detailsHandler(null);
- });
- });
- }
-
- function merge(data, samples) {
- samples.forEach(function (sample) {
- const node = data.find(function (element) {
- return element.name === sample.name;
- });
-
- if (node) {
- node.value += sample.value;
- if (sample.children) {
- if (!node.children) {
- node.children = [];
- }
- merge(node.children, sample.children);
- }
- } else {
- data.push(sample);
- }
- });
- }
-
- function forEachNode(node, f) {
- f(node);
- let children = node.children;
- if (children) {
- const stack = [children];
- let count, child, grandChildren;
- while (stack.length) {
- children = stack.pop();
- count = children.length;
- while (count--) {
- child = children[count];
- f(child);
- grandChildren = child.children;
- if (grandChildren) {
- stack.push(grandChildren);
- }
- }
- }
- }
- }
-
- function adoptNode(node) {
- let id = 0;
- forEachNode(node, function (n) {
- n.id = id++;
- });
- }
-
- function reappraiseNode(root) {
- let node,
- children,
- grandChildren,
- childrenValue,
- i,
- j,
- child,
- childValue;
- const stack = [];
- const included = [];
- const excluded = [];
- const compoundValue = !selfValue;
- let item = root.data;
- if (item.hide) {
- root.value = 0;
- children = root.children;
- if (children) {
- excluded.push(children);
- }
- } else {
- root.value = item.fade ? 0 : getValue(item);
- stack.push(root);
- }
- // First DFS pass:
- // 1. Update node.value with node's self value
- // 2. Populate excluded list with children under hidden nodes
- // 3. Populate included list with children under visible nodes
- while ((node = stack.pop())) {
- children = node.children;
- if (children && (i = children.length)) {
- childrenValue = 0;
- while (i--) {
- child = children[i];
- item = child.data;
- if (item.hide) {
- child.value = 0;
- grandChildren = child.children;
- if (grandChildren) {
- excluded.push(grandChildren);
- }
- continue;
- }
- if (item.fade) {
- child.value = 0;
- } else {
- childValue = getValue(item);
- child.value = childValue;
- childrenValue += childValue;
- }
- stack.push(child);
- }
- // Here second part of `&&` is actually checking for `node.data.fade`. However,
- // checking for node.value is faster and presents more oportunities for JS optimizer.
- if (compoundValue && node.value) {
- node.value -= childrenValue;
- }
- included.push(children);
- }
- }
- // Postorder traversal to compute compound value of each visible node.
- i = included.length;
- while (i--) {
- children = included[i];
- childrenValue = 0;
- j = children.length;
- while (j--) {
- childrenValue += children[j].value;
- }
- children[0].parent.value += childrenValue;
- }
- // Continue DFS to set value of all hidden nodes to 0.
- while (excluded.length) {
- children = excluded.pop();
- j = children.length;
- while (j--) {
- child = children[j];
- child.value = 0;
- grandChildren = child.children;
- if (grandChildren) {
- excluded.push(grandChildren);
- }
- }
- }
- }
-
- function processData() {
- selection.datum((data) => {
- if (data.constructor.name !== 'Node') {
- // creating a root hierarchical structure
- const root = hierarchy(data, getChildren);
-
- // augumenting nodes with ids
- adoptNode(root);
-
- // calculate actual value
- reappraiseNode(root);
-
- // store value for later use
- root.originalValue = root.value;
-
- // computing deltas for differentials
- if (computeDelta) {
- root.eachAfter((node) => {
- let sum = getDelta(node);
- const children = node.children;
- let i = children && children.length;
- while (--i >= 0) sum += children[i].delta;
- node.delta = sum;
- });
- }
-
- // setting the bound data for the selection
- return root;
- }
- });
- }
-
- function chart(s) {
- if (!arguments.length) {
- return chart;
- }
-
- // saving the selection on `.call`
- selection = s;
-
- // processing raw data to be used in the chart
- processData();
-
- // create chart svg
- selection.each(function (data) {
- if (src_select(this).select('svg').size() === 0) {
- const svg = src_select(this)
- .append('svg:svg')
- .attr('width', w)
- .attr('class', 'partition d3-flame-graph');
-
- if (h) {
- if (h < minHeight) h = minHeight;
- svg.attr('height', h);
- }
-
- svg
- .append('svg:text')
- .attr('class', 'title')
- .attr('text-anchor', 'middle')
- .attr('y', '25')
- .attr('x', w / 2)
- .attr('fill', '#808080')
- .text(title);
-
- if (tooltip) svg.call(tooltip);
- }
- });
-
- // first draw
- update();
- }
-
- chart.height = function (_) {
- if (!arguments.length) {
- return h;
- }
- h = _;
- return chart;
- };
-
- chart.minHeight = function (_) {
- if (!arguments.length) {
- return minHeight;
- }
- minHeight = _;
- return chart;
- };
-
- chart.width = function (_) {
- if (!arguments.length) {
- return w;
- }
- w = _;
- return chart;
- };
-
- chart.cellHeight = function (_) {
- if (!arguments.length) {
- return c;
- }
- c = _;
- return chart;
- };
-
- chart.tooltip = function (_) {
- if (!arguments.length) {
- return tooltip;
- }
- if (typeof _ === 'function') {
- tooltip = _;
- }
- return chart;
- };
-
- chart.title = function (_) {
- if (!arguments.length) {
- return title;
- }
- title = _;
- return chart;
- };
-
- chart.transitionDuration = function (_) {
- if (!arguments.length) {
- return transitionDuration;
- }
- transitionDuration = _;
- return chart;
- };
-
- chart.transitionEase = function (_) {
- if (!arguments.length) {
- return transitionEase;
- }
- transitionEase = _;
- return chart;
- };
-
- chart.sort = function (_) {
- if (!arguments.length) {
- return sort;
- }
- sort = _;
- return chart;
- };
-
- chart.inverted = function (_) {
- if (!arguments.length) {
- return inverted;
- }
- inverted = _;
- return chart;
- };
-
- chart.computeDelta = function (_) {
- if (!arguments.length) {
- return computeDelta;
- }
- computeDelta = _;
- return chart;
- };
-
- chart.setLabelHandler = function (_) {
- if (!arguments.length) {
- return labelHandler;
- }
- labelHandler = _;
- return chart;
- };
- // Kept for backwards compatibility.
- chart.label = chart.setLabelHandler;
-
- chart.search = function (term) {
- const searchResults = [];
- let searchSum = 0;
- let totalValue = 0;
- selection.each(function (data) {
- const res = searchTree(data, term);
- searchResults.push(...res[0]);
- searchSum += res[1];
- totalValue += data.originalValue;
- });
- searchHandler(searchResults, searchSum, totalValue);
- update();
- };
-
- chart.findById = function (id) {
- if (typeof id === 'undefined' || id === null) {
- return null;
- }
- let found = null;
- selection.each(function (data) {
- if (found === null) {
- found = findTree(data, id);
- }
- });
- return found;
- };
-
- chart.clear = function () {
- detailsHandler(null);
- selection.each(function (root) {
- clear(root);
- update();
- });
- };
-
- chart.zoomTo = function (d) {
- zoom(d);
- };
-
- chart.resetZoom = function () {
- selection.each(function (root) {
- zoom(root); // zoom to root
- });
- };
-
- chart.onClick = function (_) {
- if (!arguments.length) {
- return clickHandler;
- }
- clickHandler = _;
- return chart;
- };
-
- chart.onHover = function (_) {
- if (!arguments.length) {
- return hoverHandler;
- }
- hoverHandler = _;
- return chart;
- };
-
- chart.merge = function (data) {
- if (!selection) {
- return chart;
- }
-
- // TODO: Fix merge with zoom
- // Merging a zoomed chart doesn't work properly, so
- // clearing zoom before merge.
- // To apply zoom on merge, we would need to set hide
- // and fade on new data according to current data.
- // New ids are generated for the whole data structure,
- // so previous ids might not be the same. For merge to
- // work with zoom, previous ids should be maintained.
- this.resetZoom();
-
- // Clear search details
- // Merge requires a new search, updating data and
- // the details handler with search results.
- // Since we don't store the search term, can't
- // perform search again.
- searchDetails = null;
- detailsHandler(null);
-
- selection.datum((root) => {
- merge([root.data], [data]);
- return root.data;
- });
- processData();
- update();
- return chart;
- };
-
- chart.update = function (data) {
- if (!selection) {
- return chart;
- }
- if (data) {
- selection.datum(data);
- processData();
- }
- update();
- return chart;
- };
-
- chart.destroy = function () {
- if (!selection) {
- return chart;
- }
- if (tooltip) {
- tooltip.hide();
- if (typeof tooltip.destroy === 'function') {
- tooltip.destroy();
- }
- }
- selection.selectAll('svg').remove();
- return chart;
- };
-
- chart.setColorMapper = function (_) {
- if (!arguments.length) {
- colorMapper = originalColorMapper;
- return chart;
- }
- colorMapper = (d) => {
- const originalColor = originalColorMapper(d);
- return _(d, originalColor);
- };
- return chart;
- };
- // Kept for backwards compatibility.
- chart.color = chart.setColorMapper;
-
- chart.setColorHue = function (_) {
- if (!arguments.length) {
- colorHue = null;
- return chart;
- }
- colorHue = _;
- return chart;
- };
-
- chart.minFrameSize = function (_) {
- if (!arguments.length) {
- return minFrameSize;
- }
- minFrameSize = _;
- return chart;
- };
-
- chart.setDetailsElement = function (_) {
- if (!arguments.length) {
- return detailsElement;
- }
- detailsElement = _;
- return chart;
- };
- // Kept for backwards compatibility.
- chart.details = chart.setDetailsElement;
-
- chart.selfValue = function (_) {
- if (!arguments.length) {
- return selfValue;
- }
- selfValue = _;
- return chart;
- };
-
- chart.resetHeightOnZoom = function (_) {
- if (!arguments.length) {
- return resetHeightOnZoom;
- }
- resetHeightOnZoom = _;
- return chart;
- };
-
- chart.scrollOnZoom = function (_) {
- if (!arguments.length) {
- return scrollOnZoom;
- }
- scrollOnZoom = _;
- return chart;
- };
-
- chart.getName = function (_) {
- if (!arguments.length) {
- return getName;
- }
- getName = _;
- return chart;
- };
-
- chart.getValue = function (_) {
- if (!arguments.length) {
- return getValue;
- }
- getValue = _;
- return chart;
- };
-
- chart.getChildren = function (_) {
- if (!arguments.length) {
- return getChildren;
- }
- getChildren = _;
- return chart;
- };
-
- chart.getLibtype = function (_) {
- if (!arguments.length) {
- return getLibtype;
- }
- getLibtype = _;
- return chart;
- };
-
- chart.getDelta = function (_) {
- if (!arguments.length) {
- return getDelta;
- }
- getDelta = _;
- return chart;
- };
-
- chart.setSearchHandler = function (_) {
- if (!arguments.length) {
- searchHandler = originalSearchHandler;
- return chart;
- }
- searchHandler = _;
- return chart;
- };
-
- chart.setDetailsHandler = function (_) {
- if (!arguments.length) {
- detailsHandler = originalDetailsHandler;
- return chart;
- }
- detailsHandler = _;
- return chart;
- };
-
- chart.setSearchMatch = function (_) {
- if (!arguments.length) {
- searchMatch = originalSearchMatch;
- return chart;
- }
- searchMatch = _;
- return chart;
- };
-
- return chart;
- }
-
- __webpack_exports__ = __webpack_exports__['default'];
- /******/ return __webpack_exports__;
- /******/
- })();
-});
diff --git a/development/charts/table/index.html b/development/charts/table/index.html
deleted file mode 100644
index 8fa7c604f91b..000000000000
--- a/development/charts/table/index.html
+++ /dev/null
@@ -1,67 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
S.No
-
Name
-
TotalTime
-
-
-
-
-
-
-
diff --git a/development/charts/table/jquery.min.js b/development/charts/table/jquery.min.js
deleted file mode 100644
index 8cdc80eb85d8..000000000000
--- a/development/charts/table/jquery.min.js
+++ /dev/null
@@ -1,18 +0,0 @@
-/*!
- * jQuery JavaScript Library v1.6.2
- * http://jquery.com/
- *
- * Copyright 2011, John Resig
- * Dual licensed under the MIT or GPL Version 2 licenses.
- * http://jquery.org/license
- *
- * Includes Sizzle.js
- * http://sizzlejs.com/
- * Copyright 2011, The Dojo Foundation
- * Released under the MIT, BSD, and GPL Licenses.
- *
- * Date: Thu Jun 30 14:16:56 2011 -0400
- */
-(function(a,b){function cv(a){return f.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:!1}function cs(a){if(!cg[a]){var b=c.body,d=f("<"+a+">").appendTo(b),e=d.css("display");d.remove();if(e==="none"||e===""){ch||(ch=c.createElement("iframe"),ch.frameBorder=ch.width=ch.height=0),b.appendChild(ch);if(!ci||!ch.createElement)ci=(ch.contentWindow||ch.contentDocument).document,ci.write((c.compatMode==="CSS1Compat"?"":"")+""),ci.close();d=ci.createElement(a),ci.body.appendChild(d),e=f.css(d,"display"),b.removeChild(ch)}cg[a]=e}return cg[a]}function cr(a,b){var c={};f.each(cm.concat.apply([],cm.slice(0,b)),function(){c[this]=a});return c}function cq(){cn=b}function cp(){setTimeout(cq,0);return cn=f.now()}function cf(){try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}function ce(){try{return new a.XMLHttpRequest}catch(b){}}function b$(a,c){a.dataFilter&&(c=a.dataFilter(c,a.dataType));var d=a.dataTypes,e={},g,h,i=d.length,j,k=d[0],l,m,n,o,p;for(g=1;g0){c!=="border"&&f.each(e,function(){c||(d-=parseFloat(f.css(a,"padding"+this))||0),c==="margin"?d+=parseFloat(f.css(a,c+this))||0:d-=parseFloat(f.css(a,"border"+this+"Width"))||0});return d+"px"}d=bx(a,b,b);if(d<0||d==null)d=a.style[b]||0;d=parseFloat(d)||0,c&&f.each(e,function(){d+=parseFloat(f.css(a,"padding"+this))||0,c!=="padding"&&(d+=parseFloat(f.css(a,"border"+this+"Width"))||0),c==="margin"&&(d+=parseFloat(f.css(a,c+this))||0)});return d+"px"}function bm(a,b){b.src?f.ajax({url:b.src,async:!1,dataType:"script"}):f.globalEval((b.text||b.textContent||b.innerHTML||"").replace(be,"/*$0*/")),b.parentNode&&b.parentNode.removeChild(b)}function bl(a){f.nodeName(a,"input")?bk(a):"getElementsByTagName"in a&&f.grep(a.getElementsByTagName("input"),bk)}function bk(a){if(a.type==="checkbox"||a.type==="radio")a.defaultChecked=a.checked}function bj(a){return"getElementsByTagName"in a?a.getElementsByTagName("*"):"querySelectorAll"in a?a.querySelectorAll("*"):[]}function bi(a,b){var c;if(b.nodeType===1){b.clearAttributes&&b.clearAttributes(),b.mergeAttributes&&b.mergeAttributes(a),c=b.nodeName.toLowerCase();if(c==="object")b.outerHTML=a.outerHTML;else if(c!=="input"||a.type!=="checkbox"&&a.type!=="radio"){if(c==="option")b.selected=a.defaultSelected;else if(c==="input"||c==="textarea")b.defaultValue=a.defaultValue}else a.checked&&(b.defaultChecked=b.checked=a.checked),b.value!==a.value&&(b.value=a.value);b.removeAttribute(f.expando)}}function bh(a,b){if(b.nodeType===1&&!!f.hasData(a)){var c=f.expando,d=f.data(a),e=f.data(b,d);if(d=d[c]){var g=d.events;e=e[c]=f.extend({},d);if(g){delete e.handle,e.events={};for(var h in g)for(var i=0,j=g[h].length;i=0===c})}function V(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function N(a,b){return(a&&a!=="*"?a+".":"")+b.replace(z,"`").replace(A,"&")}function M(a){var b,c,d,e,g,h,i,j,k,l,m,n,o,p=[],q=[],r=f._data(this,"events");if(!(a.liveFired===this||!r||!r.live||a.target.disabled||a.button&&a.type==="click")){a.namespace&&(n=new RegExp("(^|\\.)"+a.namespace.split(".").join("\\.(?:.*\\.)?")+"(\\.|$)")),a.liveFired=this;var s=r.live.slice(0);for(i=0;ic)break;a.currentTarget=e.elem,a.data=e.handleObj.data,a.handleObj=e.handleObj,o=e.handleObj.origHandler.apply(e.elem,arguments);if(o===!1||a.isPropagationStopped()){c=e.level,o===!1&&(b=!1);if(a.isImmediatePropagationStopped())break}}return b}}function K(a,c,d){var e=f.extend({},d[0]);e.type=a,e.originalEvent={},e.liveFired=b,f.event.handle.call(c,e),e.isDefaultPrevented()&&d[0].preventDefault()}function E(){return!0}function D(){return!1}function m(a,c,d){var e=c+"defer",g=c+"queue",h=c+"mark",i=f.data(a,e,b,!0);i&&(d==="queue"||!f.data(a,g,b,!0))&&(d==="mark"||!f.data(a,h,b,!0))&&setTimeout(function(){!f.data(a,g,b,!0)&&!f.data(a,h,b,!0)&&(f.removeData(a,e,!0),i.resolve())},0)}function l(a){for(var b in a)if(b!=="toJSON")return!1;return!0}function k(a,c,d){if(d===b&&a.nodeType===1){var e="data-"+c.replace(j,"$1-$2").toLowerCase();d=a.getAttribute(e);if(typeof d=="string"){try{d=d==="true"?!0:d==="false"?!1:d==="null"?null:f.isNaN(d)?i.test(d)?f.parseJSON(d):d:parseFloat(d)}catch(g){}f.data(a,c,d)}else d=b}return d}var c=a.document,d=a.navigator,e=a.location,f=function(){function J(){if(!e.isReady){try{c.documentElement.doScroll("left")}catch(a){setTimeout(J,1);return}e.ready()}}var e=function(a,b){return new e.fn.init(a,b,h)},f=a.jQuery,g=a.$,h,i=/^(?:[^<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,j=/\S/,k=/^\s+/,l=/\s+$/,m=/\d/,n=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,o=/^[\],:{}\s]*$/,p=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,q=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,r=/(?:^|:|,)(?:\s*\[)+/g,s=/(webkit)[ \/]([\w.]+)/,t=/(opera)(?:.*version)?[ \/]([\w.]+)/,u=/(msie) ([\w.]+)/,v=/(mozilla)(?:.*? rv:([\w.]+))?/,w=/-([a-z])/ig,x=function(a,b){return b.toUpperCase()},y=d.userAgent,z,A,B,C=Object.prototype.toString,D=Object.prototype.hasOwnProperty,E=Array.prototype.push,F=Array.prototype.slice,G=String.prototype.trim,H=Array.prototype.indexOf,I={};e.fn=e.prototype={constructor:e,init:function(a,d,f){var g,h,j,k;if(!a)return this;if(a.nodeType){this.context=this[0]=a,this.length=1;return this}if(a==="body"&&!d&&c.body){this.context=c,this[0]=c.body,this.selector=a,this.length=1;return this}if(typeof a=="string"){a.charAt(0)!=="<"||a.charAt(a.length-1)!==">"||a.length<3?g=i.exec(a):g=[null,a,null];if(g&&(g[1]||!d)){if(g[1]){d=d instanceof e?d[0]:d,k=d?d.ownerDocument||d:c,j=n.exec(a),j?e.isPlainObject(d)?(a=[c.createElement(j[1])],e.fn.attr.call(a,d,!0)):a=[k.createElement(j[1])]:(j=e.buildFragment([g[1]],[k]),a=(j.cacheable?e.clone(j.fragment):j.fragment).childNodes);return e.merge(this,a)}h=c.getElementById(g[2]);if(h&&h.parentNode){if(h.id!==g[2])return f.find(a);this.length=1,this[0]=h}this.context=c,this.selector=a;return this}return!d||d.jquery?(d||f).find(a):this.constructor(d).find(a)}if(e.isFunction(a))return f.ready(a);a.selector!==b&&(this.selector=a.selector,this.context=a.context);return e.makeArray(a,this)},selector:"",jquery:"1.6.2",length:0,size:function(){return this.length},toArray:function(){return F.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this[this.length+a]:this[a]},pushStack:function(a,b,c){var d=this.constructor();e.isArray(a)?E.apply(d,a):e.merge(d,a),d.prevObject=this,d.context=this.context,b==="find"?d.selector=this.selector+(this.selector?" ":"")+c:b&&(d.selector=this.selector+"."+b+"("+c+")");return d},each:function(a,b){return e.each(this,a,b)},ready:function(a){e.bindReady(),A.done(a);return this},eq:function(a){return a===-1?this.slice(a):this.slice(a,+a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(F.apply(this,arguments),"slice",F.call(arguments).join(","))},map:function(a){return this.pushStack(e.map(this,function(b,c){return a.call(b,c,b)}))},end:function(){return this.prevObject||this.constructor(null)},push:E,sort:[].sort,splice:[].splice},e.fn.init.prototype=e.fn,e.extend=e.fn.extend=function(){var a,c,d,f,g,h,i=arguments[0]||{},j=1,k=arguments.length,l=!1;typeof i=="boolean"&&(l=i,i=arguments[1]||{},j=2),typeof i!="object"&&!e.isFunction(i)&&(i={}),k===j&&(i=this,--j);for(;j0)return;A.resolveWith(c,[e]),e.fn.trigger&&e(c).trigger("ready").unbind("ready")}},bindReady:function(){if(!A){A=e._Deferred();if(c.readyState==="complete")return setTimeout(e.ready,1);if(c.addEventListener)c.addEventListener("DOMContentLoaded",B,!1),a.addEventListener("load",e.ready,!1);else if(c.attachEvent){c.attachEvent("onreadystatechange",B),a.attachEvent("onload",e.ready);var b=!1;try{b=a.frameElement==null}catch(d){}c.documentElement.doScroll&&b&&J()}}},isFunction:function(a){return e.type(a)==="function"},isArray:Array.isArray||function(a){return e.type(a)==="array"},isWindow:function(a){return a&&typeof a=="object"&&"setInterval"in a},isNaN:function(a){return a==null||!m.test(a)||isNaN(a)},type:function(a){return a==null?String(a):I[C.call(a)]||"object"},isPlainObject:function(a){if(!a||e.type(a)!=="object"||a.nodeType||e.isWindow(a))return!1;if(a.constructor&&!D.call(a,"constructor")&&!D.call(a.constructor.prototype,"isPrototypeOf"))return!1;var c;for(c in a);return c===b||D.call(a,c)},isEmptyObject:function(a){for(var b in a)return!1;return!0},error:function(a){throw a},parseJSON:function(b){if(typeof b!="string"||!b)return null;b=e.trim(b);if(a.JSON&&a.JSON.parse)return a.JSON.parse(b);if(o.test(b.replace(p,"@").replace(q,"]").replace(r,"")))return(new Function("return "+b))();e.error("Invalid JSON: "+b)},parseXML:function(b,c,d){a.DOMParser?(d=new DOMParser,c=d.parseFromString(b,"text/xml")):(c=new ActiveXObject("Microsoft.XMLDOM"),c.async="false",c.loadXML(b)),d=c.documentElement,(!d||!d.nodeName||d.nodeName==="parsererror")&&e.error("Invalid XML: "+b);return c},noop:function(){},globalEval:function(b){b&&j.test(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(w,x)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toUpperCase()===b.toUpperCase()},each:function(a,c,d){var f,g=0,h=a.length,i=h===b||e.isFunction(a);if(d){if(i){for(f in a)if(c.apply(a[f],d)===!1)break}else for(;g0&&a[0]&&a[j-1]||j===0||e.isArray(a));if(k)for(;i1?h.call(arguments,0):c,--e||g.resolveWith(g,h.call(b,0))}}var b=arguments,c=0,d=b.length,e=d,g=d<=1&&a&&f.isFunction(a.promise)?a:f.Deferred();if(d>1){for(;c
a",d=a.getElementsByTagName("*"),e=a.getElementsByTagName("a")[0];if(!d||!d.length||!e)return{};g=c.createElement("select"),h=g.appendChild(c.createElement("option")),i=a.getElementsByTagName("input")[0],k={leadingWhitespace:a.firstChild.nodeType===3,tbody:!a.getElementsByTagName("tbody").length,htmlSerialize:!!a.getElementsByTagName("link").length,style:/top/.test(e.getAttribute("style")),hrefNormalized:e.getAttribute("href")==="/a",opacity:/^0.55$/.test(e.style.opacity),cssFloat:!!e.style.cssFloat,checkOn:i.value==="on",optSelected:h.selected,getSetAttribute:a.className!=="t",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0},i.checked=!0,k.noCloneChecked=i.cloneNode(!0).checked,g.disabled=!0,k.optDisabled=!h.disabled;try{delete a.test}catch(v){k.deleteExpando=!1}!a.addEventListener&&a.attachEvent&&a.fireEvent&&(a.attachEvent("onclick",function(){k.noCloneEvent=!1}),a.cloneNode(!0).fireEvent("onclick")),i=c.createElement("input"),i.value="t",i.setAttribute("type","radio"),k.radioValue=i.value==="t",i.setAttribute("checked","checked"),a.appendChild(i),l=c.createDocumentFragment(),l.appendChild(a.firstChild),k.checkClone=l.cloneNode(!0).cloneNode(!0).lastChild.checked,a.innerHTML="",a.style.width=a.style.paddingLeft="1px",m=c.getElementsByTagName("body")[0],o=c.createElement(m?"div":"body"),p={visibility:"hidden",width:0,height:0,border:0,margin:0},m&&f.extend(p,{position:"absolute",left:-1e3,top:-1e3});for(t in p)o.style[t]=p[t];o.appendChild(a),n=m||b,n.insertBefore(o,n.firstChild),k.appendChecked=i.checked,k.boxModel=a.offsetWidth===2,"zoom"in a.style&&(a.style.display="inline",a.style.zoom=1,k.inlineBlockNeedsLayout=a.offsetWidth===2,a.style.display="",a.innerHTML="",k.shrinkWrapBlocks=a.offsetWidth!==2),a.innerHTML="