Centralize view state into app ctx with viewData persistence
All checks were successful
check / check (push) Successful in 17s
All checks were successful
check / check (push) Successful in 17s
Creates a centralized transactionDetail.js view module, replacing the duplicated showTxDetail/copyableHtml/blockieHtml/txDetailAddressHtml code that was in both addressDetail.js and addressToken.js (~120 lines removed). Transaction data is stored in state.viewData and persisted, so the transaction detail view survives popup close/reopen. Adds viewData to persisted state. Each view that needs data for restore stores it in state.viewData before rendering. The ctx object now has showTransactionDetail() alongside all other show methods. Restorable views expanded to include: transaction (via viewData.tx), success-tx (via viewData.hash/blockNumber), error-tx (via viewData.message). txStatus.js split into show (sets data) + render (reads data) for each screen, enabling restore. Non-restorable views (send, confirm-tx, wait-tx, add-wallet, import-key, add-token) fall back to the nearest parent since they involve active form state or network polling.
This commit is contained in:
@@ -16,6 +16,7 @@ const addressToken = require("./views/addressToken");
|
|||||||
const send = require("./views/send");
|
const send = require("./views/send");
|
||||||
const confirmTx = require("./views/confirmTx");
|
const confirmTx = require("./views/confirmTx");
|
||||||
const txStatus = require("./views/txStatus");
|
const txStatus = require("./views/txStatus");
|
||||||
|
const transactionDetail = require("./views/transactionDetail");
|
||||||
const receive = require("./views/receive");
|
const receive = require("./views/receive");
|
||||||
const addToken = require("./views/addToken");
|
const addToken = require("./views/addToken");
|
||||||
const settings = require("./views/settings");
|
const settings = require("./views/settings");
|
||||||
@@ -53,6 +54,7 @@ const ctx = {
|
|||||||
showAddTokenView: () => addToken.show(),
|
showAddTokenView: () => addToken.show(),
|
||||||
showConfirmTx: (txInfo) => confirmTx.show(txInfo),
|
showConfirmTx: (txInfo) => confirmTx.show(txInfo),
|
||||||
showReceive: () => receive.show(),
|
showReceive: () => receive.show(),
|
||||||
|
showTransactionDetail: (tx) => transactionDetail.show(tx),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Views that can be fully re-rendered from persisted state.
|
// Views that can be fully re-rendered from persisted state.
|
||||||
@@ -63,26 +65,37 @@ const RESTORABLE_VIEWS = new Set([
|
|||||||
"address-token",
|
"address-token",
|
||||||
"receive",
|
"receive",
|
||||||
"settings",
|
"settings",
|
||||||
|
"transaction",
|
||||||
|
"success-tx",
|
||||||
|
"error-tx",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
function needsAddress(view) {
|
||||||
|
return (
|
||||||
|
view === "address" ||
|
||||||
|
view === "address-token" ||
|
||||||
|
view === "receive" ||
|
||||||
|
view === "transaction"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasValidAddress() {
|
||||||
|
return (
|
||||||
|
state.selectedWallet !== null &&
|
||||||
|
state.selectedAddress !== null &&
|
||||||
|
state.wallets[state.selectedWallet] &&
|
||||||
|
state.wallets[state.selectedWallet].addresses[state.selectedAddress]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function restoreView() {
|
function restoreView() {
|
||||||
const view = state.currentView;
|
const view = state.currentView;
|
||||||
if (!view || !RESTORABLE_VIEWS.has(view)) {
|
if (!view || !RESTORABLE_VIEWS.has(view)) {
|
||||||
return fallbackView();
|
return fallbackView();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate that selectedWallet/selectedAddress still point to valid data
|
if (needsAddress(view) && !hasValidAddress()) {
|
||||||
if (view === "address" || view === "address-token" || view === "receive") {
|
return fallbackView();
|
||||||
if (
|
|
||||||
state.selectedWallet === null ||
|
|
||||||
state.selectedAddress === null ||
|
|
||||||
!state.wallets[state.selectedWallet] ||
|
|
||||||
!state.wallets[state.selectedWallet].addresses[
|
|
||||||
state.selectedAddress
|
|
||||||
]
|
|
||||||
) {
|
|
||||||
return fallbackView();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (view === "address-token" && !state.selectedToken) {
|
if (view === "address-token" && !state.selectedToken) {
|
||||||
@@ -102,6 +115,27 @@ function restoreView() {
|
|||||||
case "settings":
|
case "settings":
|
||||||
settings.show();
|
settings.show();
|
||||||
break;
|
break;
|
||||||
|
case "transaction":
|
||||||
|
if (state.viewData && state.viewData.tx) {
|
||||||
|
transactionDetail.render();
|
||||||
|
} else {
|
||||||
|
fallbackView();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "success-tx":
|
||||||
|
if (state.viewData && state.viewData.hash) {
|
||||||
|
txStatus.renderSuccess();
|
||||||
|
} else {
|
||||||
|
fallbackView();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "error-tx":
|
||||||
|
if (state.viewData && state.viewData.message) {
|
||||||
|
txStatus.renderError();
|
||||||
|
} else {
|
||||||
|
fallbackView();
|
||||||
|
}
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
fallbackView();
|
fallbackView();
|
||||||
break;
|
break;
|
||||||
@@ -169,6 +203,7 @@ async function init() {
|
|||||||
send.init(ctx);
|
send.init(ctx);
|
||||||
confirmTx.init(ctx);
|
confirmTx.init(ctx);
|
||||||
txStatus.init(ctx);
|
txStatus.init(ctx);
|
||||||
|
transactionDetail.init(ctx);
|
||||||
receive.init(ctx);
|
receive.init(ctx);
|
||||||
addToken.init(ctx);
|
addToken.init(ctx);
|
||||||
settings.init(ctx);
|
settings.init(ctx);
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ const {
|
|||||||
balanceLinesForAddress,
|
balanceLinesForAddress,
|
||||||
addressDotHtml,
|
addressDotHtml,
|
||||||
escapeHtml,
|
escapeHtml,
|
||||||
formatAddressHtml,
|
|
||||||
truncateMiddle,
|
truncateMiddle,
|
||||||
} = require("./helpers");
|
} = require("./helpers");
|
||||||
const { state, currentAddress, saveState } = require("../../shared/state");
|
const { state, currentAddress, saveState } = require("../../shared/state");
|
||||||
@@ -32,10 +31,6 @@ function etherscanAddressLink(address) {
|
|||||||
return `https://etherscan.io/address/${address}`;
|
return `https://etherscan.io/address/${address}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function etherscanTxLink(hash) {
|
|
||||||
return `https://etherscan.io/tx/${hash}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function show() {
|
function show() {
|
||||||
state.selectedToken = null;
|
state.selectedToken = null;
|
||||||
const wallet = state.wallets[state.selectedWallet];
|
const wallet = state.wallets[state.selectedWallet];
|
||||||
@@ -211,77 +206,15 @@ function renderTransactions(txs) {
|
|||||||
list.querySelectorAll(".tx-row").forEach((row) => {
|
list.querySelectorAll(".tx-row").forEach((row) => {
|
||||||
row.addEventListener("click", () => {
|
row.addEventListener("click", () => {
|
||||||
const idx = parseInt(row.dataset.tx, 10);
|
const idx = parseInt(row.dataset.tx, 10);
|
||||||
showTxDetail(loadedTxs[idx]);
|
const tx = loadedTxs[idx];
|
||||||
|
const counterparty = tx.direction === "sent" ? tx.to : tx.from;
|
||||||
|
tx.fromEns = ensNameMap.get(tx.from) || null;
|
||||||
|
tx.toEns = ensNameMap.get(tx.to) || null;
|
||||||
|
ctx.showTransactionDetail(tx);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function copyableHtml(text, extraClass) {
|
|
||||||
const cls =
|
|
||||||
"underline decoration-dashed cursor-pointer" +
|
|
||||||
(extraClass ? " " + extraClass : "");
|
|
||||||
return `<span class="${cls}" data-copy="${escapeHtml(text)}">${escapeHtml(text)}</span>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function blockieHtml(address) {
|
|
||||||
const src = makeBlockie(address);
|
|
||||||
return `<img src="${src}" width="48" height="48" style="image-rendering:pixelated;border-radius:50%;display:inline-block">`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function txDetailAddressHtml(address) {
|
|
||||||
const ensName = ensNameMap.get(address) || null;
|
|
||||||
const blockie = blockieHtml(address);
|
|
||||||
const dot = addressDotHtml(address);
|
|
||||||
const link = etherscanAddressLink(address);
|
|
||||||
const extLink = `<a href="${link}" target="_blank" rel="noopener" class="inline-flex items-center">${EXT_ICON}</a>`;
|
|
||||||
let html = `<div class="mb-1">${blockie}</div>`;
|
|
||||||
if (ensName) {
|
|
||||||
html +=
|
|
||||||
`<div class="flex items-center">${dot}` +
|
|
||||||
copyableHtml(ensName, "") +
|
|
||||||
extLink +
|
|
||||||
`</div>` +
|
|
||||||
`<div class="break-all">` +
|
|
||||||
copyableHtml(address, "break-all") +
|
|
||||||
`</div>`;
|
|
||||||
} else {
|
|
||||||
html +=
|
|
||||||
`<div class="flex items-center">${dot}` +
|
|
||||||
copyableHtml(address, "break-all") +
|
|
||||||
extLink +
|
|
||||||
`</div>`;
|
|
||||||
}
|
|
||||||
return html;
|
|
||||||
}
|
|
||||||
|
|
||||||
function txDetailHashHtml(hash) {
|
|
||||||
const link = etherscanTxLink(hash);
|
|
||||||
const extLink = `<a href="${link}" target="_blank" rel="noopener" class="inline-flex items-center">${EXT_ICON}</a>`;
|
|
||||||
return copyableHtml(hash, "break-all") + extLink;
|
|
||||||
}
|
|
||||||
|
|
||||||
function showTxDetail(tx) {
|
|
||||||
$("tx-detail-hash").innerHTML = txDetailHashHtml(tx.hash);
|
|
||||||
$("tx-detail-from").innerHTML = txDetailAddressHtml(tx.from);
|
|
||||||
$("tx-detail-to").innerHTML = txDetailAddressHtml(tx.to);
|
|
||||||
$("tx-detail-value").textContent = tx.value + " " + tx.symbol;
|
|
||||||
$("tx-detail-time").textContent =
|
|
||||||
isoDate(tx.timestamp) + " (" + timeAgo(tx.timestamp) + ")";
|
|
||||||
$("tx-detail-status").textContent = tx.isError ? "Failed" : "Success";
|
|
||||||
showView("transaction");
|
|
||||||
|
|
||||||
// Attach copy handlers
|
|
||||||
document
|
|
||||||
.getElementById("view-transaction")
|
|
||||||
.querySelectorAll("[data-copy]")
|
|
||||||
.forEach((el) => {
|
|
||||||
el.onclick = () => {
|
|
||||||
navigator.clipboard.writeText(el.dataset.copy);
|
|
||||||
showFlash("Copied!");
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function init(_ctx) {
|
function init(_ctx) {
|
||||||
ctx = _ctx;
|
ctx = _ctx;
|
||||||
$("address-full").addEventListener("click", () => {
|
$("address-full").addEventListener("click", () => {
|
||||||
@@ -319,14 +252,6 @@ function init(_ctx) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
$("btn-add-token").addEventListener("click", ctx.showAddTokenView);
|
$("btn-add-token").addEventListener("click", ctx.showAddTokenView);
|
||||||
|
|
||||||
$("btn-tx-back").addEventListener("click", () => {
|
|
||||||
if (state.selectedToken) {
|
|
||||||
ctx.showAddressToken();
|
|
||||||
} else {
|
|
||||||
show();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { init, show };
|
module.exports = { init, show };
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ const { updateSendBalance, renderSendTokenSelect } = require("./send");
|
|||||||
const { log } = require("../../shared/log");
|
const { log } = require("../../shared/log");
|
||||||
const makeBlockie = require("ethereum-blockies-base64");
|
const makeBlockie = require("ethereum-blockies-base64");
|
||||||
|
|
||||||
|
let ctx;
|
||||||
|
|
||||||
const EXT_ICON =
|
const EXT_ICON =
|
||||||
`<span style="display:inline-block;width:10px;height:10px;margin-left:4px;vertical-align:middle">` +
|
`<span style="display:inline-block;width:10px;height:10px;margin-left:4px;vertical-align:middle">` +
|
||||||
`<svg viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1.5">` +
|
`<svg viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1.5">` +
|
||||||
@@ -36,10 +38,6 @@ function etherscanAddressLink(address) {
|
|||||||
return `https://etherscan.io/address/${address}`;
|
return `https://etherscan.io/address/${address}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function etherscanTxLink(hash) {
|
|
||||||
return `https://etherscan.io/tx/${hash}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function isoDate(timestamp) {
|
function isoDate(timestamp) {
|
||||||
const d = new Date(timestamp * 1000);
|
const d = new Date(timestamp * 1000);
|
||||||
const pad = (n) => String(n).padStart(2, "0");
|
const pad = (n) => String(n).padStart(2, "0");
|
||||||
@@ -233,78 +231,16 @@ function renderTransactions(txs) {
|
|||||||
list.querySelectorAll(".tx-row").forEach((row) => {
|
list.querySelectorAll(".tx-row").forEach((row) => {
|
||||||
row.addEventListener("click", () => {
|
row.addEventListener("click", () => {
|
||||||
const idx = parseInt(row.dataset.tx, 10);
|
const idx = parseInt(row.dataset.tx, 10);
|
||||||
showTxDetail(loadedTxs[idx]);
|
const tx = loadedTxs[idx];
|
||||||
|
tx.fromEns = ensNameMap.get(tx.from) || null;
|
||||||
|
tx.toEns = ensNameMap.get(tx.to) || null;
|
||||||
|
ctx.showTransactionDetail(tx);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function copyableHtml(text, extraClass) {
|
function init(_ctx) {
|
||||||
const cls =
|
ctx = _ctx;
|
||||||
"underline decoration-dashed cursor-pointer" +
|
|
||||||
(extraClass ? " " + extraClass : "");
|
|
||||||
return `<span class="${cls}" data-copy="${escapeHtml(text)}">${escapeHtml(text)}</span>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function blockieHtml(address) {
|
|
||||||
const src = makeBlockie(address);
|
|
||||||
return `<img src="${src}" width="48" height="48" style="image-rendering:pixelated;border-radius:50%;display:inline-block">`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function txDetailAddressHtml(address) {
|
|
||||||
const ensName = ensNameMap.get(address) || null;
|
|
||||||
const blockie = blockieHtml(address);
|
|
||||||
const dot = addressDotHtml(address);
|
|
||||||
const link = etherscanAddressLink(address);
|
|
||||||
const extLink = `<a href="${link}" target="_blank" rel="noopener" class="inline-flex items-center">${EXT_ICON}</a>`;
|
|
||||||
let html = `<div class="mb-1">${blockie}</div>`;
|
|
||||||
if (ensName) {
|
|
||||||
html +=
|
|
||||||
`<div class="flex items-center">${dot}` +
|
|
||||||
copyableHtml(ensName, "") +
|
|
||||||
extLink +
|
|
||||||
`</div>` +
|
|
||||||
`<div class="break-all">` +
|
|
||||||
copyableHtml(address, "break-all") +
|
|
||||||
`</div>`;
|
|
||||||
} else {
|
|
||||||
html +=
|
|
||||||
`<div class="flex items-center">${dot}` +
|
|
||||||
copyableHtml(address, "break-all") +
|
|
||||||
extLink +
|
|
||||||
`</div>`;
|
|
||||||
}
|
|
||||||
return html;
|
|
||||||
}
|
|
||||||
|
|
||||||
function txDetailHashHtml(hash) {
|
|
||||||
const link = etherscanTxLink(hash);
|
|
||||||
const extLink = `<a href="${link}" target="_blank" rel="noopener" class="inline-flex items-center">${EXT_ICON}</a>`;
|
|
||||||
return copyableHtml(hash, "break-all") + extLink;
|
|
||||||
}
|
|
||||||
|
|
||||||
function showTxDetail(tx) {
|
|
||||||
$("tx-detail-hash").innerHTML = txDetailHashHtml(tx.hash);
|
|
||||||
$("tx-detail-from").innerHTML = txDetailAddressHtml(tx.from);
|
|
||||||
$("tx-detail-to").innerHTML = txDetailAddressHtml(tx.to);
|
|
||||||
$("tx-detail-value").textContent = tx.value + " " + tx.symbol;
|
|
||||||
$("tx-detail-time").textContent =
|
|
||||||
isoDate(tx.timestamp) + " (" + timeAgo(tx.timestamp) + ")";
|
|
||||||
$("tx-detail-status").textContent = tx.isError ? "Failed" : "Success";
|
|
||||||
showView("transaction");
|
|
||||||
|
|
||||||
// Attach copy handlers
|
|
||||||
document
|
|
||||||
.getElementById("view-transaction")
|
|
||||||
.querySelectorAll("[data-copy]")
|
|
||||||
.forEach((el) => {
|
|
||||||
el.onclick = () => {
|
|
||||||
navigator.clipboard.writeText(el.dataset.copy);
|
|
||||||
showFlash("Copied!");
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function init(ctx) {
|
|
||||||
$("address-token-full").addEventListener("click", () => {
|
$("address-token-full").addEventListener("click", () => {
|
||||||
const addr = $("address-token-full").dataset.full;
|
const addr = $("address-token-full").dataset.full;
|
||||||
if (addr) {
|
if (addr) {
|
||||||
|
|||||||
152
src/popup/views/transactionDetail.js
Normal file
152
src/popup/views/transactionDetail.js
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
// Transaction detail view — shows full details for a single transaction.
|
||||||
|
// Shared by addressDetail and addressToken via ctx.showTransactionDetail().
|
||||||
|
|
||||||
|
const {
|
||||||
|
$,
|
||||||
|
showView,
|
||||||
|
showFlash,
|
||||||
|
addressDotHtml,
|
||||||
|
escapeHtml,
|
||||||
|
} = require("./helpers");
|
||||||
|
const { state } = require("../../shared/state");
|
||||||
|
const makeBlockie = require("ethereum-blockies-base64");
|
||||||
|
|
||||||
|
const EXT_ICON =
|
||||||
|
`<span style="display:inline-block;width:10px;height:10px;margin-left:4px;vertical-align:middle">` +
|
||||||
|
`<svg viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1.5">` +
|
||||||
|
`<path d="M4.5 1.5H2a.5.5 0 00-.5.5v8a.5.5 0 00.5.5h8a.5.5 0 00.5-.5V7.5"/>` +
|
||||||
|
`<path d="M7 1.5h3.5V5M7 5.5L10.5 1.5"/>` +
|
||||||
|
`</svg></span>`;
|
||||||
|
|
||||||
|
let ctx;
|
||||||
|
|
||||||
|
function isoDate(timestamp) {
|
||||||
|
const d = new Date(timestamp * 1000);
|
||||||
|
const pad = (n) => String(n).padStart(2, "0");
|
||||||
|
return (
|
||||||
|
d.getFullYear() +
|
||||||
|
"-" +
|
||||||
|
pad(d.getMonth() + 1) +
|
||||||
|
"-" +
|
||||||
|
pad(d.getDate()) +
|
||||||
|
" " +
|
||||||
|
pad(d.getHours()) +
|
||||||
|
":" +
|
||||||
|
pad(d.getMinutes()) +
|
||||||
|
":" +
|
||||||
|
pad(d.getSeconds())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function timeAgo(timestamp) {
|
||||||
|
const seconds = Math.floor(Date.now() / 1000 - timestamp);
|
||||||
|
if (seconds < 60) return seconds + " seconds ago";
|
||||||
|
const minutes = Math.floor(seconds / 60);
|
||||||
|
if (minutes < 60)
|
||||||
|
return minutes + " minute" + (minutes !== 1 ? "s" : "") + " ago";
|
||||||
|
const hours = Math.floor(minutes / 60);
|
||||||
|
if (hours < 24) return hours + " hour" + (hours !== 1 ? "s" : "") + " ago";
|
||||||
|
const days = Math.floor(hours / 24);
|
||||||
|
if (days < 30) return days + " day" + (days !== 1 ? "s" : "") + " ago";
|
||||||
|
const months = Math.floor(days / 30);
|
||||||
|
if (months < 12)
|
||||||
|
return months + " month" + (months !== 1 ? "s" : "") + " ago";
|
||||||
|
const years = Math.floor(days / 365);
|
||||||
|
return years + " year" + (years !== 1 ? "s" : "") + " ago";
|
||||||
|
}
|
||||||
|
|
||||||
|
function copyableHtml(text, extraClass) {
|
||||||
|
const cls =
|
||||||
|
"underline decoration-dashed cursor-pointer" +
|
||||||
|
(extraClass ? " " + extraClass : "");
|
||||||
|
return `<span class="${cls}" data-copy="${escapeHtml(text)}">${escapeHtml(text)}</span>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function blockieHtml(address) {
|
||||||
|
const src = makeBlockie(address);
|
||||||
|
return `<img src="${src}" width="48" height="48" style="image-rendering:pixelated;border-radius:50%;display:inline-block">`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function txAddressHtml(address, ensName) {
|
||||||
|
const blockie = blockieHtml(address);
|
||||||
|
const dot = addressDotHtml(address);
|
||||||
|
const link = `https://etherscan.io/address/${address}`;
|
||||||
|
const extLink = `<a href="${link}" target="_blank" rel="noopener" class="inline-flex items-center">${EXT_ICON}</a>`;
|
||||||
|
let html = `<div class="mb-1">${blockie}</div>`;
|
||||||
|
if (ensName) {
|
||||||
|
html +=
|
||||||
|
`<div class="flex items-center">${dot}` +
|
||||||
|
copyableHtml(ensName, "") +
|
||||||
|
extLink +
|
||||||
|
`</div>` +
|
||||||
|
`<div class="break-all">` +
|
||||||
|
copyableHtml(address, "break-all") +
|
||||||
|
`</div>`;
|
||||||
|
} else {
|
||||||
|
html +=
|
||||||
|
`<div class="flex items-center">${dot}` +
|
||||||
|
copyableHtml(address, "break-all") +
|
||||||
|
extLink +
|
||||||
|
`</div>`;
|
||||||
|
}
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
|
||||||
|
function txHashHtml(hash) {
|
||||||
|
const link = `https://etherscan.io/tx/${hash}`;
|
||||||
|
const extLink = `<a href="${link}" target="_blank" rel="noopener" class="inline-flex items-center">${EXT_ICON}</a>`;
|
||||||
|
return copyableHtml(hash, "break-all") + extLink;
|
||||||
|
}
|
||||||
|
|
||||||
|
function show(tx) {
|
||||||
|
state.viewData = {
|
||||||
|
tx: {
|
||||||
|
hash: tx.hash,
|
||||||
|
from: tx.from,
|
||||||
|
to: tx.to,
|
||||||
|
value: tx.value,
|
||||||
|
symbol: tx.symbol,
|
||||||
|
timestamp: tx.timestamp,
|
||||||
|
isError: tx.isError,
|
||||||
|
fromEns: tx.fromEns || null,
|
||||||
|
toEns: tx.toEns || null,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
render();
|
||||||
|
}
|
||||||
|
|
||||||
|
function render() {
|
||||||
|
const tx = state.viewData.tx;
|
||||||
|
if (!tx) return;
|
||||||
|
$("tx-detail-hash").innerHTML = txHashHtml(tx.hash);
|
||||||
|
$("tx-detail-from").innerHTML = txAddressHtml(tx.from, tx.fromEns);
|
||||||
|
$("tx-detail-to").innerHTML = txAddressHtml(tx.to, tx.toEns);
|
||||||
|
$("tx-detail-value").textContent = tx.value + " " + tx.symbol;
|
||||||
|
$("tx-detail-time").textContent =
|
||||||
|
isoDate(tx.timestamp) + " (" + timeAgo(tx.timestamp) + ")";
|
||||||
|
$("tx-detail-status").textContent = tx.isError ? "Failed" : "Success";
|
||||||
|
showView("transaction");
|
||||||
|
|
||||||
|
document
|
||||||
|
.getElementById("view-transaction")
|
||||||
|
.querySelectorAll("[data-copy]")
|
||||||
|
.forEach((el) => {
|
||||||
|
el.onclick = () => {
|
||||||
|
navigator.clipboard.writeText(el.dataset.copy);
|
||||||
|
showFlash("Copied!");
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function init(_ctx) {
|
||||||
|
ctx = _ctx;
|
||||||
|
$("btn-tx-back").addEventListener("click", () => {
|
||||||
|
if (state.selectedToken) {
|
||||||
|
ctx.showAddressToken();
|
||||||
|
} else {
|
||||||
|
ctx.showAddressDetail();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { init, show, render };
|
||||||
@@ -7,7 +7,7 @@ const {
|
|||||||
addressDotHtml,
|
addressDotHtml,
|
||||||
escapeHtml,
|
escapeHtml,
|
||||||
} = require("./helpers");
|
} = require("./helpers");
|
||||||
const { state } = require("../../shared/state");
|
const { state, saveState } = require("../../shared/state");
|
||||||
const { getProvider } = require("../../shared/balances");
|
const { getProvider } = require("../../shared/balances");
|
||||||
const { log } = require("../../shared/log");
|
const { log } = require("../../shared/log");
|
||||||
|
|
||||||
@@ -107,26 +107,51 @@ function showSuccess(txInfo, txHash, blockNumber) {
|
|||||||
clearTimers();
|
clearTimers();
|
||||||
|
|
||||||
const symbol = txInfo.token === "ETH" ? "ETH" : txInfo.tokenSymbol || "?";
|
const symbol = txInfo.token === "ETH" ? "ETH" : txInfo.tokenSymbol || "?";
|
||||||
$("success-tx-summary").textContent = txInfo.amount + " " + symbol;
|
state.viewData = {
|
||||||
$("success-tx-to").innerHTML = toAddressHtml(txInfo.to);
|
amount: txInfo.amount,
|
||||||
$("success-tx-block").textContent = String(blockNumber);
|
symbol: symbol,
|
||||||
$("success-tx-hash").innerHTML = txHashHtml(txHash);
|
to: txInfo.to,
|
||||||
attachCopyHandlers("view-success-tx");
|
hash: txHash,
|
||||||
|
blockNumber: blockNumber,
|
||||||
showView("success-tx");
|
};
|
||||||
|
renderSuccess();
|
||||||
ctx.doRefreshAndRender();
|
ctx.doRefreshAndRender();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function renderSuccess() {
|
||||||
|
const d = state.viewData;
|
||||||
|
if (!d || !d.hash) return;
|
||||||
|
$("success-tx-summary").textContent = d.amount + " " + d.symbol;
|
||||||
|
$("success-tx-to").innerHTML = toAddressHtml(d.to);
|
||||||
|
$("success-tx-block").textContent = String(d.blockNumber);
|
||||||
|
$("success-tx-hash").innerHTML = txHashHtml(d.hash);
|
||||||
|
attachCopyHandlers("view-success-tx");
|
||||||
|
showView("success-tx");
|
||||||
|
}
|
||||||
|
|
||||||
function showError(txInfo, txHash, message) {
|
function showError(txInfo, txHash, message) {
|
||||||
clearTimers();
|
clearTimers();
|
||||||
|
|
||||||
const symbol = txInfo.token === "ETH" ? "ETH" : txInfo.tokenSymbol || "?";
|
const symbol = txInfo.token === "ETH" ? "ETH" : txInfo.tokenSymbol || "?";
|
||||||
$("error-tx-summary").textContent = txInfo.amount + " " + symbol;
|
state.viewData = {
|
||||||
$("error-tx-to").innerHTML = toAddressHtml(txInfo.to);
|
amount: txInfo.amount,
|
||||||
$("error-tx-message").textContent = message;
|
symbol: symbol,
|
||||||
|
to: txInfo.to,
|
||||||
|
hash: txHash || null,
|
||||||
|
message: message,
|
||||||
|
};
|
||||||
|
renderError();
|
||||||
|
}
|
||||||
|
|
||||||
if (txHash) {
|
function renderError() {
|
||||||
$("error-tx-hash").innerHTML = txHashHtml(txHash);
|
const d = state.viewData;
|
||||||
|
if (!d || !d.message) return;
|
||||||
|
$("error-tx-summary").textContent = d.amount + " " + d.symbol;
|
||||||
|
$("error-tx-to").innerHTML = toAddressHtml(d.to);
|
||||||
|
$("error-tx-message").textContent = d.message;
|
||||||
|
|
||||||
|
if (d.hash) {
|
||||||
|
$("error-tx-hash").innerHTML = txHashHtml(d.hash);
|
||||||
$("error-tx-hash-section").classList.remove("hidden");
|
$("error-tx-hash-section").classList.remove("hidden");
|
||||||
attachCopyHandlers("view-error-tx");
|
attachCopyHandlers("view-error-tx");
|
||||||
} else {
|
} else {
|
||||||
@@ -151,4 +176,4 @@ function init(_ctx) {
|
|||||||
$("btn-error-tx-done").addEventListener("click", navigateBack);
|
$("btn-error-tx-done").addEventListener("click", navigateBack);
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { init, showWait, showError };
|
module.exports = { init, showWait, showError, renderSuccess, renderError };
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ const state = {
|
|||||||
selectedWallet: null,
|
selectedWallet: null,
|
||||||
selectedAddress: null,
|
selectedAddress: null,
|
||||||
selectedToken: null,
|
selectedToken: null,
|
||||||
|
viewData: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
async function saveState() {
|
async function saveState() {
|
||||||
@@ -58,6 +59,7 @@ async function saveState() {
|
|||||||
selectedWallet: state.selectedWallet,
|
selectedWallet: state.selectedWallet,
|
||||||
selectedAddress: state.selectedAddress,
|
selectedAddress: state.selectedAddress,
|
||||||
selectedToken: state.selectedToken,
|
selectedToken: state.selectedToken,
|
||||||
|
viewData: state.viewData,
|
||||||
};
|
};
|
||||||
await storageApi.set({ autistmask: persisted });
|
await storageApi.set({ autistmask: persisted });
|
||||||
}
|
}
|
||||||
@@ -114,6 +116,7 @@ async function loadState() {
|
|||||||
state.selectedAddress =
|
state.selectedAddress =
|
||||||
saved.selectedAddress !== undefined ? saved.selectedAddress : null;
|
saved.selectedAddress !== undefined ? saved.selectedAddress : null;
|
||||||
state.selectedToken = saved.selectedToken || null;
|
state.selectedToken = saved.selectedToken || null;
|
||||||
|
state.viewData = saved.viewData || {};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user