All checks were successful
check / check (push) Successful in 25s
## Summary Fixes the view stack pop bug where pressing Back in Settings (or any view) always returned to Main instead of the previous view. Closes [issue #134](#134) ## Problem The popup UI had no navigation stack. Every back button was hardcoded to a specific destination (usually Main). The reported path: > Main → Address → Transaction → Settings (gear icon) → Back ...would go to Main instead of returning to the Transaction view. ## Solution Implemented a proper view navigation stack (like iOS) as already described in the README: - **`viewStack`** array added to persisted state — survives popup close/reopen - **`pushCurrentView()`** — pushes the current view name onto the stack before any forward navigation - **`goBack()`** — pops the stack and shows the previous view; falls back to Main if the stack is empty; re-renders the wallet list when returning to Main - **`clearViewStack()`** — resets the stack for root transitions (e.g., after adding/deleting a wallet) ### What Changed 1. **helpers.js** — Added navigation stack functions (`pushCurrentView`, `goBack`, `clearViewStack`, `setRenderMain`) 2. **state.js** — Added `viewStack` to persisted state 3. **index.js** — All `ctx.show*()` wrappers now push before navigating forward; gear button uses stack for toggle behavior 4. **All view back buttons** — Replaced hardcoded destinations with `goBack()` (settings, addressDetail, addressToken, transactionDetail, send, receive, addToken, confirmTx, addWallet, settingsAddToken, deleteWallet, export-privkey) 5. **Direct `showView()` forward navigations** — Added `pushCurrentView()` calls before `showView("send")` in addressDetail, addressToken, and home; before `showView("export-privkey")` in addressDetail; before `deleteWallet.show()` in settings 6. **Reset-to-root transitions** — `clearViewStack()` called after adding a wallet (all 3 import types), after deleting the last wallet, and after transaction completion (Done button) ### Navigation Paths Verified - **Main → Settings → Back** → returns to Main ✓ - **Main → Address → Settings → Back** → returns to Address ✓ - **Main → Address → Transaction → Settings → Back** → returns to Transaction ✓ (the reported bug) - **Main → Address → Token → Send → ConfirmTx → Back → Back → Back → Back** → unwinds correctly through each view back to Main ✓ - **Main → Address → Token → Transaction → Settings → Back** → returns to Transaction ✓ - **Settings → Add Wallet → (add) → Main** → stack cleared, fresh root ✓ - **Settings → Delete Wallet → Back** → returns to Settings ✓ - **Settings → Delete Wallet → (confirm)** → stack reset to [main], settings shown ✓ - **Address → Send → ConfirmTx → (broadcast) → SuccessTx → Done** → stack reset, returns to address context ✓ - **Popup close/reopen** → viewStack persisted, back navigation still works ✓ Co-authored-by: user <user@Mac.lan guest wan> Reviewed-on: #146 Co-authored-by: clawbot <clawbot@noreply.example.org> Co-committed-by: clawbot <clawbot@noreply.example.org>
290 lines
7.4 KiB
JavaScript
290 lines
7.4 KiB
JavaScript
// AutistMask popup entry point.
|
|
// Loads state, initializes views, triggers first render.
|
|
|
|
const { DEBUG } = require("../shared/constants");
|
|
const {
|
|
state,
|
|
saveState,
|
|
loadState,
|
|
currentNetwork,
|
|
} = require("../shared/state");
|
|
const { refreshPrices } = require("../shared/prices");
|
|
const { refreshBalances } = require("../shared/balances");
|
|
const {
|
|
$,
|
|
showView,
|
|
setRenderMain,
|
|
pushCurrentView,
|
|
goBack,
|
|
clearViewStack,
|
|
} = require("./views/helpers");
|
|
const { applyTheme } = require("./theme");
|
|
|
|
const home = require("./views/home");
|
|
const welcome = require("./views/welcome");
|
|
const addWallet = require("./views/addWallet");
|
|
const addressDetail = require("./views/addressDetail");
|
|
const addressToken = require("./views/addressToken");
|
|
const send = require("./views/send");
|
|
const confirmTx = require("./views/confirmTx");
|
|
const txStatus = require("./views/txStatus");
|
|
const transactionDetail = require("./views/transactionDetail");
|
|
const receive = require("./views/receive");
|
|
const addToken = require("./views/addToken");
|
|
const settings = require("./views/settings");
|
|
const settingsAddToken = require("./views/settingsAddToken");
|
|
const approval = require("./views/approval");
|
|
|
|
function renderWalletList() {
|
|
home.render(ctx);
|
|
}
|
|
|
|
let refreshInFlight = false;
|
|
|
|
async function doRefreshAndRender() {
|
|
if (refreshInFlight) return;
|
|
refreshInFlight = true;
|
|
try {
|
|
await Promise.all([
|
|
refreshPrices(),
|
|
refreshBalances(
|
|
state.wallets,
|
|
state.rpcUrl,
|
|
state.blockscoutUrl,
|
|
state.trackedTokens,
|
|
),
|
|
]);
|
|
state.lastBalanceRefresh = Date.now();
|
|
await saveState();
|
|
renderWalletList();
|
|
} finally {
|
|
refreshInFlight = false;
|
|
}
|
|
}
|
|
|
|
const ctx = {
|
|
renderWalletList,
|
|
doRefreshAndRender,
|
|
showAddWalletView: () => {
|
|
pushCurrentView();
|
|
addWallet.show();
|
|
},
|
|
showAddressDetail: () => {
|
|
pushCurrentView();
|
|
addressDetail.show();
|
|
},
|
|
showAddressToken: () => {
|
|
pushCurrentView();
|
|
addressToken.show();
|
|
},
|
|
showAddTokenView: () => {
|
|
pushCurrentView();
|
|
addToken.show();
|
|
},
|
|
showConfirmTx: (txInfo) => {
|
|
pushCurrentView();
|
|
confirmTx.show(txInfo);
|
|
},
|
|
showReceive: () => {
|
|
pushCurrentView();
|
|
receive.show();
|
|
},
|
|
showTransactionDetail: (tx) => {
|
|
pushCurrentView();
|
|
transactionDetail.show(tx);
|
|
},
|
|
showSettingsView: () => {
|
|
pushCurrentView();
|
|
settings.show();
|
|
},
|
|
showSettingsAddTokenView: () => {
|
|
pushCurrentView();
|
|
settingsAddToken.show();
|
|
},
|
|
};
|
|
|
|
// Views that can be fully re-rendered from persisted state.
|
|
// All others fall back to the nearest restorable parent.
|
|
const RESTORABLE_VIEWS = new Set([
|
|
"main",
|
|
"address",
|
|
"address-token",
|
|
"receive",
|
|
"settings",
|
|
"settings-addtoken",
|
|
"confirm-tx",
|
|
"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() {
|
|
const view = state.currentView;
|
|
if (!view || !RESTORABLE_VIEWS.has(view)) {
|
|
return fallbackView();
|
|
}
|
|
|
|
if (needsAddress(view) && !hasValidAddress()) {
|
|
return fallbackView();
|
|
}
|
|
|
|
if (view === "address-token" && !state.selectedToken) {
|
|
return fallbackView();
|
|
}
|
|
|
|
switch (view) {
|
|
case "address":
|
|
addressDetail.show();
|
|
break;
|
|
case "address-token":
|
|
addressToken.show();
|
|
break;
|
|
case "receive":
|
|
receive.show();
|
|
break;
|
|
case "settings":
|
|
settings.show();
|
|
break;
|
|
case "settings-addtoken":
|
|
settingsAddToken.show();
|
|
break;
|
|
case "confirm-tx":
|
|
if (state.viewData && state.viewData.pendingTx) {
|
|
confirmTx.restore();
|
|
} else {
|
|
fallbackView();
|
|
}
|
|
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:
|
|
fallbackView();
|
|
break;
|
|
}
|
|
}
|
|
|
|
function fallbackView() {
|
|
renderWalletList();
|
|
showView("main");
|
|
}
|
|
|
|
async function init() {
|
|
await loadState();
|
|
applyTheme(state.theme);
|
|
|
|
const net = currentNetwork();
|
|
if (DEBUG || net.isTestnet) {
|
|
const banner = document.createElement("div");
|
|
banner.id = "debug-banner";
|
|
if (DEBUG && net.isTestnet) {
|
|
banner.textContent = "DEBUG / INSECURE [TESTNET]";
|
|
} else if (net.isTestnet) {
|
|
banner.textContent = "[TESTNET]";
|
|
} else {
|
|
banner.textContent = "DEBUG / INSECURE";
|
|
}
|
|
banner.style.cssText =
|
|
"background:#c00;color:#fff;text-align:center;font-size:10px;padding:1px 0;font-family:monospace;position:sticky;top:0;z-index:9999;";
|
|
document.body.prepend(banner);
|
|
}
|
|
|
|
// Auto-default active address
|
|
if (
|
|
state.activeAddress === null &&
|
|
state.wallets.length > 0 &&
|
|
state.wallets[0].addresses.length > 0
|
|
) {
|
|
state.activeAddress = state.wallets[0].addresses[0].address;
|
|
await saveState();
|
|
}
|
|
|
|
// Always init approval and txStatus — they may run in the approval popup window
|
|
approval.init(ctx);
|
|
txStatus.init(ctx);
|
|
|
|
// Check for approval mode
|
|
const params = new URLSearchParams(window.location.search);
|
|
const approvalId = params.get("approval");
|
|
if (approvalId) {
|
|
approval.show(approvalId);
|
|
showView("approve-site");
|
|
return;
|
|
}
|
|
|
|
$("btn-settings").addEventListener("click", () => {
|
|
if (
|
|
!document
|
|
.getElementById("view-settings")
|
|
.classList.contains("hidden")
|
|
) {
|
|
goBack();
|
|
return;
|
|
}
|
|
pushCurrentView();
|
|
settings.show();
|
|
});
|
|
|
|
setRenderMain(renderWalletList);
|
|
|
|
welcome.init(ctx);
|
|
addWallet.init(ctx);
|
|
home.init(ctx);
|
|
addressDetail.init(ctx);
|
|
addressToken.init(ctx);
|
|
send.init(ctx);
|
|
confirmTx.init(ctx);
|
|
transactionDetail.init(ctx);
|
|
receive.init(ctx);
|
|
addToken.init(ctx);
|
|
settings.init(ctx);
|
|
settingsAddToken.init(ctx);
|
|
|
|
if (!state.hasWallet) {
|
|
showView("welcome");
|
|
} else {
|
|
renderWalletList();
|
|
restoreView();
|
|
doRefreshAndRender();
|
|
setInterval(doRefreshAndRender, 10000);
|
|
}
|
|
}
|
|
|
|
document.addEventListener("DOMContentLoaded", init);
|