@reown/appkit-scaffold-ui
Version:
The full stack toolkit to build onchain app UX.
662 lines (660 loc) • 28.2 kB
JavaScript
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
import { LitElement, html } from 'lit';
import { state } from 'lit/decorators.js';
import { classMap } from 'lit/directives/class-map.js';
import { ConstantsUtil as CommonConstantsUtil, ParseUtil } from '@reown/appkit-common';
import { AccountController, AssetUtil, ChainController, ConnectionController, ConnectionControllerUtil, ConnectorController, CoreHelperUtil, OptionsController, RouterController, SnackController, StorageUtil } from '@reown/appkit-controllers';
import { MathUtil, customElement } from '@reown/appkit-ui';
import '@reown/appkit-ui/wui-active-profile-wallet-item';
import '@reown/appkit-ui/wui-balance';
import '@reown/appkit-ui/wui-button';
import '@reown/appkit-ui/wui-icon';
import '@reown/appkit-ui/wui-icon-box';
import '@reown/appkit-ui/wui-inactive-profile-wallet-item';
import '@reown/appkit-ui/wui-list-item';
import '@reown/appkit-ui/wui-separator';
import '@reown/appkit-ui/wui-tabs';
import '@reown/appkit-ui/wui-text';
import { HelpersUtil } from '@reown/appkit-utils';
import { ConnectionUtil } from '../../utils/ConnectionUtil.js';
import styles from './styles.js';
const TABS_PADDING = 16;
const TABS_INNER_PADDING = 4;
const UI_CONFIG = {
ADDRESS_DISPLAY: { START: 4, END: 6 },
BADGE: { SIZE: 'md', ICON: 'lightbulb' },
SCROLL_THRESHOLD: 50,
OPACITY_RANGE: [0, 1]
};
const NAMESPACE_ICONS = {
eip155: 'ethereum',
solana: 'solana',
bip122: 'bitcoin'
};
const NAMESPACE_TABS = [
{ namespace: 'eip155', icon: NAMESPACE_ICONS.eip155, label: 'EVM' },
{ namespace: 'solana', icon: NAMESPACE_ICONS.solana, label: 'Solana' },
{ namespace: 'bip122', icon: NAMESPACE_ICONS.bip122, label: 'Bitcoin' }
];
const CHAIN_LABELS = {
eip155: { title: 'Add EVM Wallet', description: 'Add your first EVM wallet' },
solana: { title: 'Add Solana Wallet', description: 'Add your first Solana wallet' },
bip122: { title: 'Add Bitcoin Wallet', description: 'Add your first Bitcoin wallet' }
};
let W3mProfileWalletsView = class W3mProfileWalletsView extends LitElement {
constructor() {
super();
this.unsubscribers = [];
this.currentTab = 0;
this.namespace = ChainController.state.activeChain;
this.namespaces = Array.from(ChainController.state.chains.keys());
this.caipAddress = undefined;
this.profileName = undefined;
this.activeConnectorIds = ConnectorController.state.activeConnectorIds;
this.lastSelectedAddress = '';
this.lastSelectedConnectorId = '';
this.isSwitching = false;
this.caipNetwork = ChainController.state.activeCaipNetwork;
this.user = AccountController.state.user;
this.remoteFeatures = OptionsController.state.remoteFeatures;
this.tabWidth = '';
this.currentTab = this.namespace ? this.namespaces.indexOf(this.namespace) : 0;
this.caipAddress = ChainController.getAccountData(this.namespace)?.caipAddress;
this.profileName = ChainController.getAccountData(this.namespace)?.profileName;
this.unsubscribers.push(...[
ConnectionController.subscribeKey('connections', () => this.onConnectionsChange()),
ConnectionController.subscribeKey('recentConnections', () => this.requestUpdate()),
ConnectorController.subscribeKey('activeConnectorIds', ids => {
this.activeConnectorIds = ids;
}),
ChainController.subscribeKey('activeCaipNetwork', val => (this.caipNetwork = val)),
AccountController.subscribeKey('user', val => (this.user = val)),
OptionsController.subscribeKey('remoteFeatures', val => (this.remoteFeatures = val))
]);
this.chainListener = ChainController.subscribeChainProp('accountState', accountState => {
this.caipAddress = accountState?.caipAddress;
this.profileName = accountState?.profileName;
}, this.namespace);
}
disconnectedCallback() {
this.unsubscribers.forEach(unsubscribe => unsubscribe());
this.resizeObserver?.disconnect();
this.tabsResizeObserver?.disconnect();
this.removeScrollListener();
this.chainListener?.();
}
firstUpdated() {
const walletListEl = this.shadowRoot?.querySelector('.wallet-list');
const tabsEl = this.shadowRoot?.querySelector('wui-tabs');
if (!walletListEl) {
return;
}
const handleScroll = () => this.updateScrollOpacity(walletListEl);
requestAnimationFrame(handleScroll);
walletListEl.addEventListener('scroll', handleScroll);
this.resizeObserver = new ResizeObserver(handleScroll);
this.resizeObserver.observe(walletListEl);
handleScroll();
if (tabsEl) {
const handleTabsResize = () => {
const availableTabs = NAMESPACE_TABS.filter(tab => this.namespaces.includes(tab.namespace));
const tabCount = availableTabs.length;
if (tabCount > 1) {
const containerWidth = this.getBoundingClientRect()?.width;
const totalInnerTabsPadding = TABS_INNER_PADDING * 2;
const totalTabsPadding = TABS_PADDING * 2;
const availableWidth = containerWidth - totalTabsPadding - totalInnerTabsPadding;
const tabWidth = availableWidth / tabCount;
this.tabWidth = `${tabWidth}px`;
this.requestUpdate();
}
};
this.tabsResizeObserver = new ResizeObserver(handleTabsResize);
this.tabsResizeObserver.observe(this);
handleTabsResize();
}
}
render() {
const namespace = this.namespace;
if (!namespace) {
throw new Error('Namespace is not set');
}
return html `
<wui-flex flexDirection="column" .padding=${['0', 'l', 'l', 'l']} gap="l">
${this.renderTabs()} ${this.renderHeader(namespace)} ${this.renderConnections(namespace)}
${this.renderAddConnectionButton(namespace)}
</wui-flex>
`;
}
renderTabs() {
const availableTabs = NAMESPACE_TABS.filter(tab => this.namespaces.includes(tab.namespace));
const tabCount = availableTabs.length;
if (tabCount > 1) {
return html `
<wui-tabs
.onTabChange=${(index) => this.handleTabChange(index)}
.activeTab=${this.currentTab}
localTabWidth=${this.tabWidth}
.tabs=${availableTabs}
></wui-tabs>
`;
}
return null;
}
renderHeader(namespace) {
const connections = this.getActiveConnections(namespace);
const totalConnections = connections.flatMap(({ accounts }) => accounts).length + (this.caipAddress ? 1 : 0);
return html `
<wui-flex alignItems="center" columnGap="3xs">
<wui-icon
name=${NAMESPACE_ICONS[namespace] ??
NAMESPACE_ICONS.eip155}
size="lg"
></wui-icon>
<wui-text color="fg-200" variant="small-400"
>${totalConnections > 1 ? 'Wallets' : 'Wallet'}</wui-text
>
<wui-text
color="fg-100"
variant="small-400"
class="balance-amount"
data-testid="balance-amount"
>
${totalConnections}
</wui-text>
<wui-link
color="fg-200"
=${() => ConnectionController.disconnect({ namespace })}
?disabled=${!this.hasAnyConnections(namespace)}
data-testid="disconnect-all-button"
>
Disconnect All
</wui-link>
</wui-flex>
`;
}
renderConnections(namespace) {
const hasConnections = this.hasAnyConnections(namespace);
const classes = {
'wallet-list': true,
'active-wallets-box': hasConnections,
'empty-wallet-list-box': !hasConnections
};
return html `
<wui-flex flexDirection="column" class=${classMap(classes)} rowGap="s">
${hasConnections
? this.renderActiveConnections(namespace)
: this.renderEmptyState(namespace)}
</wui-flex>
`;
}
renderActiveConnections(namespace) {
const connections = this.getActiveConnections(namespace);
const connectorId = this.activeConnectorIds[namespace];
const plainAddress = this.getPlainAddress();
return html `
${plainAddress || connectorId || connections.length > 0
? html `<wui-flex
flexDirection="column"
.padding=${['l', '0', 'xs', '0']}
class="active-wallets"
>
${this.renderActiveProfile(namespace)} ${this.renderActiveConnectionsList(namespace)}
</wui-flex>`
: null}
${this.renderRecentConnections(namespace)}
`;
}
renderActiveProfile(namespace) {
const connectorId = this.activeConnectorIds[namespace];
if (!connectorId) {
return null;
}
const { connections } = ConnectionControllerUtil.getConnectionsData(namespace);
const connector = ConnectorController.getConnectorById(connectorId);
const connectorImage = AssetUtil.getConnectorImage(connector);
const plainAddress = this.getPlainAddress();
if (!plainAddress) {
return null;
}
const isBitcoin = namespace === CommonConstantsUtil.CHAIN.BITCOIN;
const authData = ConnectionUtil.getAuthData({ connectorId, accounts: [] });
const shouldShowSeparator = this.getActiveConnections(namespace).flatMap(connection => connection.accounts).length > 0;
const connection = connections.find(c => c.connectorId === connectorId);
const account = connection?.accounts.filter(a => !HelpersUtil.isLowerCaseMatch(a.address, plainAddress));
return html `
<wui-flex flexDirection="column" .padding=${['0', 'l', '0', 'l']}>
<wui-active-profile-wallet-item
address=${plainAddress}
alt=${connector?.name}
.content=${this.getProfileContent({
address: plainAddress,
connections,
connectorId,
namespace
})}
.charsStart=${UI_CONFIG.ADDRESS_DISPLAY.START}
.charsEnd=${UI_CONFIG.ADDRESS_DISPLAY.END}
.icon=${authData.icon}
.iconSize=${authData.iconSize}
.iconBadge=${this.isSmartAccount(plainAddress) ? UI_CONFIG.BADGE.ICON : undefined}
.iconBadgeSize=${this.isSmartAccount(plainAddress) ? UI_CONFIG.BADGE.SIZE : undefined}
imageSrc=${connectorImage}
?enableMoreButton=${authData.isAuth}
=${() => this.handleCopyAddress(plainAddress)}
=${() => this.handleDisconnect(namespace, { id: connectorId })}
=${() => {
if (isBitcoin && connection && account?.[0]) {
this.handleSwitchWallet(connection, account[0].address, namespace);
}
}}
=${() => this.handleExternalLink(plainAddress)}
=${() => this.handleMore()}
data-testid="wui-active-profile-wallet-item"
></wui-active-profile-wallet-item>
${shouldShowSeparator ? html `<wui-separator></wui-separator>` : null}
</wui-flex>
`;
}
renderActiveConnectionsList(namespace) {
const connections = this.getActiveConnections(namespace);
if (connections.length === 0) {
return null;
}
return html `
<wui-flex flexDirection="column" .padding=${['0', 'xs', '0', 'xs']}>
${this.renderConnectionList(connections, false, namespace)}
</wui-flex>
`;
}
renderRecentConnections(namespace) {
const { recentConnections } = ConnectionControllerUtil.getConnectionsData(namespace);
const allAccounts = recentConnections.flatMap(connection => connection.accounts);
if (allAccounts.length === 0) {
return null;
}
return html `
<wui-flex flexDirection="column" .padding=${['0', 'xs', '0', 'xs']} rowGap="xs">
<wui-text color="fg-200" variant="micro-500" data-testid="recently-connected-text"
>RECENTLY CONNECTED</wui-text
>
<wui-flex flexDirection="column" .padding=${['0', 'xs', '0', 'xs']}>
${this.renderConnectionList(recentConnections, true, namespace)}
</wui-flex>
</wui-flex>
`;
}
renderConnectionList(connections, isRecentConnections, namespace) {
return connections
.filter(connection => connection.accounts.length > 0)
.map((connection, connectionIdx) => {
const connector = ConnectorController.getConnectorById(connection.connectorId);
const connectorImage = AssetUtil.getConnectorImage(connector) ?? '';
const authData = ConnectionUtil.getAuthData(connection);
return connection.accounts.map((account, accountIdx) => {
const shouldShowSeparator = connectionIdx !== 0 || accountIdx !== 0;
const isLoading = this.isAccountLoading(connection.connectorId, account.address);
return html `
<wui-flex flexDirection="column">
${shouldShowSeparator ? html `<wui-separator></wui-separator>` : null}
<wui-inactive-profile-wallet-item
address=${account.address}
alt=${connection.connectorId}
buttonLabel=${isRecentConnections ? 'Connect' : 'Switch'}
buttonVariant=${isRecentConnections ? 'neutral' : 'accent'}
rightIcon=${isRecentConnections ? 'bin' : 'off'}
rightIconSize="sm"
class=${isRecentConnections ? 'recent-connection' : 'active-connection'}
data-testid=${isRecentConnections ? 'recent-connection' : 'active-connection'}
imageSrc=${connectorImage}
.iconBadge=${this.isSmartAccount(account.address)
? UI_CONFIG.BADGE.ICON
: undefined}
.iconBadgeSize=${this.isSmartAccount(account.address)
? UI_CONFIG.BADGE.SIZE
: undefined}
.icon=${authData.icon}
.iconSize=${authData.iconSize}
.loading=${isLoading}
.showBalance=${false}
.charsStart=${UI_CONFIG.ADDRESS_DISPLAY.START}
.charsEnd=${UI_CONFIG.ADDRESS_DISPLAY.END}
=${() => this.handleSwitchWallet(connection, account.address, namespace)}
=${() => this.handleWalletAction({
connection,
address: account.address,
isRecentConnection: isRecentConnections,
namespace
})}
></wui-inactive-profile-wallet-item>
</wui-flex>
`;
});
});
}
renderAddConnectionButton(namespace) {
if (!this.isMultiWalletEnabled() && this.caipAddress) {
return null;
}
if (!this.hasAnyConnections(namespace)) {
return null;
}
const { title } = this.getChainLabelInfo(namespace);
return html `
<wui-list-item
variant="icon"
iconVariant="overlay"
icon="plus"
iconSize="sm"
?chevron=${true}
=${() => this.handleAddConnection(namespace)}
data-testid="add-connection-button"
>
<wui-text variant="paragraph-500" color="fg-200">${title}</wui-text>
</wui-list-item>
`;
}
renderEmptyState(namespace) {
const { title, description } = this.getChainLabelInfo(namespace);
return html `
<wui-flex alignItems="flex-start" class="empty-template" data-testid="empty-template">
<wui-flex
flexDirection="column"
alignItems="center"
justifyContent="center"
rowGap="s"
class="empty-box"
>
<wui-icon-box
size="lg"
icon="wallet"
background="gray"
iconColor="fg-200"
backgroundColor="glass-002"
></wui-icon-box>
<wui-flex flexDirection="column" alignItems="center" justifyContent="center" gap="3xs">
<wui-text color="fg-100" variant="paragraph-500" data-testid="empty-state-text"
>No wallet connected</wui-text
>
<wui-text color="fg-200" variant="tiny-500" data-testid="empty-state-description"
>${description}</wui-text
>
</wui-flex>
<wui-button
variant="neutral"
size="md"
=${() => this.handleAddConnection(namespace)}
data-testid="empty-state-button"
>
<wui-icon color="inherit" slot="iconLeft" name="plus"></wui-icon>
${title}
</wui-button>
</wui-flex>
</wui-flex>
`;
}
handleTabChange(index) {
const nextNamespace = this.namespaces[index];
if (nextNamespace) {
this.chainListener?.();
this.currentTab = this.namespaces.indexOf(nextNamespace);
this.namespace = nextNamespace;
this.caipAddress = ChainController.getAccountData(nextNamespace)?.caipAddress;
this.profileName = ChainController.getAccountData(nextNamespace)?.profileName;
this.chainListener = ChainController.subscribeChainProp('accountState', accountState => {
this.caipAddress = accountState?.caipAddress;
}, nextNamespace);
}
}
async handleSwitchWallet(connection, address, namespace) {
try {
this.isSwitching = true;
this.lastSelectedConnectorId = connection.connectorId;
this.lastSelectedAddress = address;
await ConnectionController.switchConnection({
connection,
address,
namespace,
closeModalOnConnect: false,
onChange({ hasSwitchedAccount, hasSwitchedWallet }) {
if (hasSwitchedWallet) {
SnackController.showSuccess('Wallet switched');
}
else if (hasSwitchedAccount) {
SnackController.showSuccess('Account switched');
}
}
});
}
catch (error) {
SnackController.showError('Failed to switch wallet');
}
finally {
this.isSwitching = false;
}
}
handleWalletAction(params) {
const { connection, address, isRecentConnection, namespace } = params;
if (isRecentConnection) {
StorageUtil.deleteAddressFromConnection({
connectorId: connection.connectorId,
address,
namespace
});
ConnectionController.syncStorageConnections();
SnackController.showSuccess('Wallet deleted');
}
else {
this.handleDisconnect(namespace, { id: connection.connectorId });
}
}
async handleDisconnect(namespace, { id }) {
try {
await ConnectionController.disconnect({ id, namespace });
SnackController.showSuccess('Wallet disconnected');
}
catch {
SnackController.showError('Failed to disconnect wallet');
}
}
handleCopyAddress(address) {
CoreHelperUtil.copyToClopboard(address);
SnackController.showSuccess('Address copied');
}
handleMore() {
RouterController.push('AccountSettings');
}
handleExternalLink(address) {
const explorerUrl = this.caipNetwork?.blockExplorers?.default.url;
if (explorerUrl) {
CoreHelperUtil.openHref(`${explorerUrl}/address/${address}`, '_blank');
}
}
handleAddConnection(namespace) {
ConnectorController.setFilterByNamespace(namespace);
RouterController.push('Connect');
}
getChainLabelInfo(namespace) {
return (CHAIN_LABELS[namespace] ?? {
title: 'Add Wallet',
description: 'Add your first wallet'
});
}
isSmartAccount(address) {
if (!this.namespace) {
return false;
}
const smartAccount = this.user?.accounts?.find(account => account.type === 'smartAccount');
if (smartAccount && address) {
return HelpersUtil.isLowerCaseMatch(smartAccount.address, address);
}
return false;
}
getPlainAddress() {
return this.caipAddress ? CoreHelperUtil.getPlainAddress(this.caipAddress) : undefined;
}
getActiveConnections(namespace) {
const connectorId = this.activeConnectorIds[namespace];
const { connections } = ConnectionControllerUtil.getConnectionsData(namespace);
const [connectedConnection] = connections.filter(connection => HelpersUtil.isLowerCaseMatch(connection.connectorId, connectorId));
if (!connectorId) {
return connections;
}
const isBitcoin = namespace === CommonConstantsUtil.CHAIN.BITCOIN;
const { address } = this.caipAddress ? ParseUtil.parseCaipAddress(this.caipAddress) : {};
let addresses = [...(address ? [address] : [])];
if (isBitcoin && connectedConnection) {
addresses = connectedConnection.accounts.map(account => account.address) || [];
}
return ConnectionControllerUtil.excludeConnectorAddressFromConnections({
connectorId,
addresses,
connections
});
}
hasAnyConnections(namespace) {
const connections = this.getActiveConnections(namespace);
const { recentConnections } = ConnectionControllerUtil.getConnectionsData(namespace);
return Boolean(this.caipAddress) || connections.length > 0 || recentConnections.length > 0;
}
isAccountLoading(connectorId, address) {
return (HelpersUtil.isLowerCaseMatch(this.lastSelectedConnectorId, connectorId) &&
HelpersUtil.isLowerCaseMatch(this.lastSelectedAddress, address) &&
this.isSwitching);
}
getProfileContent(params) {
const { address, connections, connectorId, namespace } = params;
const [connectedConnection] = connections.filter(connection => HelpersUtil.isLowerCaseMatch(connection.connectorId, connectorId));
if (namespace === CommonConstantsUtil.CHAIN.BITCOIN &&
connectedConnection?.accounts.every(account => typeof account.type === 'string')) {
return this.getBitcoinProfileContent(connectedConnection.accounts, address);
}
const authData = ConnectionUtil.getAuthData({ connectorId, accounts: [] });
return [
{
address,
tagLabel: 'Active',
tagVariant: 'success',
enableButton: true,
profileName: this.profileName,
buttonType: 'disconnect',
buttonLabel: 'Disconnect',
buttonVariant: 'neutral',
...(authData.isAuth
? { description: this.isSmartAccount(address) ? 'Smart Account' : 'EOA Account' }
: {})
}
];
}
getBitcoinProfileContent(accounts, address) {
const hasMultipleAccounts = accounts.length > 1;
const plainAddress = this.getPlainAddress();
return accounts.map(account => {
const isConnected = HelpersUtil.isLowerCaseMatch(account.address, plainAddress);
let label = 'PAYMENT';
if (account.type === 'ordinal') {
label = 'ORDINALS';
}
return {
address: account.address,
tagLabel: HelpersUtil.isLowerCaseMatch(account.address, address) ? 'Active' : undefined,
tagVariant: HelpersUtil.isLowerCaseMatch(account.address, address) ? 'success' : undefined,
enableButton: true,
...(hasMultipleAccounts
? {
label,
alignItems: 'flex-end',
buttonType: isConnected ? 'disconnect' : 'switch',
buttonLabel: isConnected ? 'Disconnect' : 'Switch',
buttonVariant: isConnected ? 'neutral' : 'accent'
}
: {
alignItems: 'center',
buttonType: 'disconnect',
buttonLabel: 'Disconnect',
buttonVariant: 'neutral'
})
};
});
}
removeScrollListener() {
const connectEl = this.shadowRoot?.querySelector('.wallet-list');
if (connectEl) {
connectEl.removeEventListener('scroll', () => this.handleConnectListScroll());
}
}
handleConnectListScroll() {
const walletListEl = this.shadowRoot?.querySelector('.wallet-list');
if (walletListEl) {
this.updateScrollOpacity(walletListEl);
}
}
isMultiWalletEnabled() {
return Boolean(this.remoteFeatures?.multiWallet);
}
updateScrollOpacity(element) {
element.style.setProperty('--connect-scroll--top-opacity', MathUtil.interpolate([0, UI_CONFIG.SCROLL_THRESHOLD], UI_CONFIG.OPACITY_RANGE, element.scrollTop).toString());
element.style.setProperty('--connect-scroll--bottom-opacity', MathUtil.interpolate([0, UI_CONFIG.SCROLL_THRESHOLD], UI_CONFIG.OPACITY_RANGE, element.scrollHeight - element.scrollTop - element.offsetHeight).toString());
}
onConnectionsChange() {
if (this.isMultiWalletEnabled()) {
if (this.namespace) {
const { connections } = ConnectionControllerUtil.getConnectionsData(this.namespace);
if (connections.length === 0) {
RouterController.reset('ProfileWallets');
}
}
}
this.requestUpdate();
}
};
W3mProfileWalletsView.styles = styles;
__decorate([
state()
], W3mProfileWalletsView.prototype, "currentTab", void 0);
__decorate([
state()
], W3mProfileWalletsView.prototype, "namespace", void 0);
__decorate([
state()
], W3mProfileWalletsView.prototype, "namespaces", void 0);
__decorate([
state()
], W3mProfileWalletsView.prototype, "caipAddress", void 0);
__decorate([
state()
], W3mProfileWalletsView.prototype, "profileName", void 0);
__decorate([
state()
], W3mProfileWalletsView.prototype, "activeConnectorIds", void 0);
__decorate([
state()
], W3mProfileWalletsView.prototype, "lastSelectedAddress", void 0);
__decorate([
state()
], W3mProfileWalletsView.prototype, "lastSelectedConnectorId", void 0);
__decorate([
state()
], W3mProfileWalletsView.prototype, "isSwitching", void 0);
__decorate([
state()
], W3mProfileWalletsView.prototype, "caipNetwork", void 0);
__decorate([
state()
], W3mProfileWalletsView.prototype, "user", void 0);
__decorate([
state()
], W3mProfileWalletsView.prototype, "remoteFeatures", void 0);
__decorate([
state()
], W3mProfileWalletsView.prototype, "tabWidth", void 0);
W3mProfileWalletsView = __decorate([
customElement('w3m-profile-wallets-view')
], W3mProfileWalletsView);
export { W3mProfileWalletsView };
//# sourceMappingURL=index.js.map