Split popup into one file per view
All checks were successful
check / check (push) Successful in 4s

popup/index.js reduced to ~75 lines: loads state, builds a
shared context object, initializes all views, shows first screen.

Each view in popup/views/:
  helpers.js      — $(), showError, hideError, showView
  welcome.js      — welcome screen
  addWallet.js    — unified create/import recovery phrase
  importKey.js    — private key import
  home.js         — wallet list, total value, address derivation
  addressDetail.js — address view, token list, QR, copy
  send.js         — send form, ENS resolution, tx broadcast
  receive.js      — QR + copy
  addToken.js     — token lookup, common token picker
  settings.js     — RPC endpoint
  approval.js     — dApp approval (stub)

Views communicate via a ctx object with shared callbacks
(renderWalletList, showAddressDetail, doRefreshAndRender, etc).
This commit is contained in:
2026-02-25 18:51:41 +07:00
parent f50a2a0389
commit 023d8441bc
12 changed files with 673 additions and 581 deletions

88
src/popup/views/home.js Normal file
View File

@@ -0,0 +1,88 @@
const { $, showView } = require("./helpers");
const { state, saveState } = require("../../shared/state");
const { deriveAddressFromXpub } = require("../../shared/wallet");
const {
formatUsd,
getAddressValueUsd,
getTotalValueUsd,
} = require("../../shared/prices");
function renderTotalValue() {
const el = $("total-value");
if (!el) return;
el.textContent = formatUsd(getTotalValueUsd(state.wallets));
}
function render(ctx) {
const container = $("wallet-list");
if (state.wallets.length === 0) {
container.innerHTML =
'<p class="text-muted py-2">No wallets yet. Add one to get started.</p>';
renderTotalValue();
return;
}
let html = "";
state.wallets.forEach((wallet, wi) => {
html += `<div class="mb-3">`;
html += `<div class="flex justify-between items-center border-b border-border pb-1 mb-1">`;
html += `<span class="font-bold">${wallet.name}</span>`;
if (wallet.type === "hd") {
html += `<button class="btn-add-address border border-border px-1 hover:bg-fg hover:text-bg cursor-pointer text-xs" data-wallet="${wi}" title="Add another address to this wallet">+</button>`;
}
html += `</div>`;
wallet.addresses.forEach((addr, ai) => {
html += `<div class="address-row py-1 border-b border-border-light cursor-pointer hover:bg-hover px-1" data-wallet="${wi}" data-address="${ai}">`;
if (addr.ensName) {
html += `<div class="text-xs font-bold">${addr.ensName}</div>`;
}
html += `<div class="text-xs break-all">${addr.address}</div>`;
html += `<div class="flex justify-between items-center">`;
html += `<span class="text-xs">${addr.balance} ETH</span>`;
html += `<span class="text-xs text-muted">${formatUsd(getAddressValueUsd(addr))}</span>`;
html += `</div>`;
html += `</div>`;
});
html += `</div>`;
});
container.innerHTML = html;
container.querySelectorAll(".address-row").forEach((row) => {
row.addEventListener("click", () => {
state.selectedWallet = parseInt(row.dataset.wallet, 10);
state.selectedAddress = parseInt(row.dataset.address, 10);
ctx.showAddressDetail();
});
});
container.querySelectorAll(".btn-add-address").forEach((btn) => {
btn.addEventListener("click", async (e) => {
e.stopPropagation();
const wi = parseInt(btn.dataset.wallet, 10);
const wallet = state.wallets[wi];
wallet.addresses.push({
address: deriveAddressFromXpub(wallet.xpub, wallet.nextIndex),
balance: "0.0000",
tokenBalances: [],
});
wallet.nextIndex++;
await saveState();
render(ctx);
});
});
renderTotalValue();
}
function init(ctx) {
$("btn-settings").addEventListener("click", () => {
$("settings-rpc").value = state.rpcUrl;
showView("settings");
});
$("btn-main-add-wallet").addEventListener("click", ctx.showAddWalletView);
}
module.exports = { init, render };