Add theme preference (light/dark/system) stored in extension state. System mode follows prefers-color-scheme and listens for changes. Dark mode inverts the monochrome palette (white-on-black). Theme selector added to Display section in settings. Closes #125
266 lines
9.7 KiB
JavaScript
266 lines
9.7 KiB
JavaScript
const { $, showView, showFlash, escapeHtml } = require("./helpers");
|
|
const { applyTheme } = require("../theme");
|
|
const { state, saveState } = require("../../shared/state");
|
|
const { ETHEREUM_MAINNET_CHAIN_ID } = require("../../shared/constants");
|
|
const { log, debugFetch } = require("../../shared/log");
|
|
const deleteWallet = require("./deleteWallet");
|
|
|
|
const runtime =
|
|
typeof browser !== "undefined" ? browser.runtime : chrome.runtime;
|
|
|
|
function renderSiteList(containerId, siteMap, stateKey) {
|
|
const container = $(containerId);
|
|
const hostnames = [...new Set(Object.values(siteMap).flat())];
|
|
if (hostnames.length === 0) {
|
|
container.innerHTML = '<p class="text-xs text-muted">None</p>';
|
|
return;
|
|
}
|
|
let html = "";
|
|
hostnames.forEach((hostname) => {
|
|
html += `<div class="flex justify-between items-center text-xs py-1 border-b border-border-light">`;
|
|
html += `<span>${hostname}</span>`;
|
|
html += `<button class="btn-remove-site border border-border px-1 hover:bg-fg hover:text-bg cursor-pointer" data-key="${stateKey}" data-hostname="${hostname}">[x]</button>`;
|
|
html += `</div>`;
|
|
});
|
|
container.innerHTML = html;
|
|
container.querySelectorAll(".btn-remove-site").forEach((btn) => {
|
|
btn.addEventListener("click", async () => {
|
|
const key = btn.dataset.key;
|
|
const host = btn.dataset.hostname;
|
|
for (const addr of Object.keys(state[key])) {
|
|
state[key][addr] = state[key][addr].filter((h) => h !== host);
|
|
if (state[key][addr].length === 0) {
|
|
delete state[key][addr];
|
|
}
|
|
}
|
|
await saveState();
|
|
runtime.sendMessage({ type: "AUTISTMASK_REMOVE_SITE" });
|
|
renderSiteList(containerId, state[key], key);
|
|
});
|
|
});
|
|
}
|
|
|
|
function renderTrackedTokens() {
|
|
const container = $("settings-tracked-tokens");
|
|
if (state.trackedTokens.length === 0) {
|
|
container.innerHTML = '<p class="text-xs text-muted">None</p>';
|
|
return;
|
|
}
|
|
let html = "";
|
|
state.trackedTokens.forEach((token, idx) => {
|
|
const label = token.name
|
|
? escapeHtml(token.name) + " (" + escapeHtml(token.symbol) + ")"
|
|
: escapeHtml(token.symbol);
|
|
html += `<div class="flex justify-between items-center text-xs py-1 border-b border-border-light">`;
|
|
html += `<span>${label}</span>`;
|
|
html += `<button class="btn-remove-token border border-border px-1 hover:bg-fg hover:text-bg cursor-pointer" data-idx="${idx}">[x]</button>`;
|
|
html += `</div>`;
|
|
});
|
|
container.innerHTML = html;
|
|
container.querySelectorAll(".btn-remove-token").forEach((btn) => {
|
|
btn.addEventListener("click", async () => {
|
|
const idx = parseInt(btn.dataset.idx, 10);
|
|
state.trackedTokens.splice(idx, 1);
|
|
await saveState();
|
|
renderTrackedTokens();
|
|
});
|
|
});
|
|
}
|
|
|
|
function renderWalletListSettings() {
|
|
const container = $("settings-wallet-list");
|
|
if (state.wallets.length === 0) {
|
|
container.innerHTML = '<p class="text-xs text-muted">No wallets.</p>';
|
|
return;
|
|
}
|
|
let html = "";
|
|
state.wallets.forEach((wallet, idx) => {
|
|
const name = escapeHtml(wallet.name || "Wallet " + (idx + 1));
|
|
html += `<div class="flex justify-between items-center text-xs py-1 border-b border-border-light">`;
|
|
html += `<span class="settings-wallet-name cursor-pointer underline decoration-dashed" data-idx="${idx}">${name}</span>`;
|
|
html += `<button class="btn-delete-wallet border border-border px-1 hover:bg-fg hover:text-bg cursor-pointer" data-idx="${idx}">[x]</button>`;
|
|
html += `</div>`;
|
|
});
|
|
container.innerHTML = html;
|
|
container.querySelectorAll(".btn-delete-wallet").forEach((btn) => {
|
|
btn.addEventListener("click", () => {
|
|
const idx = parseInt(btn.dataset.idx, 10);
|
|
deleteWallet.show(idx);
|
|
});
|
|
});
|
|
|
|
// Inline rename on click
|
|
container.querySelectorAll(".settings-wallet-name").forEach((span) => {
|
|
span.addEventListener("click", () => {
|
|
const idx = parseInt(span.dataset.idx, 10);
|
|
const wallet = state.wallets[idx];
|
|
const input = document.createElement("input");
|
|
input.type = "text";
|
|
input.className =
|
|
"border border-border p-0 text-xs bg-bg text-fg w-full";
|
|
input.value = wallet.name || "Wallet " + (idx + 1);
|
|
span.replaceWith(input);
|
|
input.focus();
|
|
input.select();
|
|
const finish = async () => {
|
|
const val = input.value.trim();
|
|
if (val && val !== wallet.name) {
|
|
wallet.name = val;
|
|
await saveState();
|
|
}
|
|
renderWalletListSettings();
|
|
};
|
|
input.addEventListener("blur", finish);
|
|
input.addEventListener("keydown", (e) => {
|
|
if (e.key === "Enter") input.blur();
|
|
if (e.key === "Escape") {
|
|
input.value = wallet.name || "Wallet " + (idx + 1);
|
|
input.blur();
|
|
}
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
function show() {
|
|
$("settings-rpc").value = state.rpcUrl;
|
|
$("settings-blockscout").value = state.blockscoutUrl;
|
|
renderTrackedTokens();
|
|
renderSiteLists();
|
|
renderWalletListSettings();
|
|
|
|
showView("settings");
|
|
}
|
|
|
|
function renderSiteLists() {
|
|
renderSiteList(
|
|
"settings-allowed-sites",
|
|
state.allowedSites,
|
|
"allowedSites",
|
|
);
|
|
renderSiteList("settings-denied-sites", state.deniedSites, "deniedSites");
|
|
}
|
|
|
|
function init(ctx) {
|
|
deleteWallet.init(ctx);
|
|
|
|
$("btn-save-rpc").addEventListener("click", async () => {
|
|
const url = $("settings-rpc").value.trim();
|
|
if (!url) {
|
|
showFlash("Please enter an RPC URL.");
|
|
return;
|
|
}
|
|
showFlash("Testing endpoint...");
|
|
try {
|
|
const resp = await debugFetch(url, {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({
|
|
jsonrpc: "2.0",
|
|
id: 1,
|
|
method: "eth_chainId",
|
|
params: [],
|
|
}),
|
|
});
|
|
const json = await resp.json();
|
|
if (json.error) {
|
|
log.errorf("RPC validation error:", json.error);
|
|
showFlash("Endpoint returned error: " + json.error.message);
|
|
return;
|
|
}
|
|
if (json.result !== ETHEREUM_MAINNET_CHAIN_ID) {
|
|
showFlash(
|
|
"Wrong network (expected mainnet, got chain " +
|
|
json.result +
|
|
").",
|
|
);
|
|
return;
|
|
}
|
|
} catch (e) {
|
|
log.errorf("RPC validation fetch failed:", e.message);
|
|
showFlash("Could not reach endpoint.");
|
|
return;
|
|
}
|
|
state.rpcUrl = url;
|
|
await saveState();
|
|
showFlash("Saved.");
|
|
});
|
|
|
|
$("btn-save-blockscout").addEventListener("click", async () => {
|
|
const url = $("settings-blockscout").value.trim();
|
|
if (!url) {
|
|
showFlash("Please enter a Blockscout API URL.");
|
|
return;
|
|
}
|
|
showFlash("Testing endpoint...");
|
|
try {
|
|
const resp = await debugFetch(url + "/stats");
|
|
if (!resp.ok) {
|
|
showFlash("Endpoint returned HTTP " + resp.status + ".");
|
|
return;
|
|
}
|
|
} catch (e) {
|
|
log.errorf("Blockscout validation failed:", e.message);
|
|
showFlash("Could not reach endpoint.");
|
|
return;
|
|
}
|
|
state.blockscoutUrl = url;
|
|
await saveState();
|
|
showFlash("Saved.");
|
|
});
|
|
|
|
$("settings-show-zero-balances").checked = state.showZeroBalanceTokens;
|
|
$("settings-show-zero-balances").addEventListener("change", async () => {
|
|
state.showZeroBalanceTokens = $("settings-show-zero-balances").checked;
|
|
await saveState();
|
|
});
|
|
|
|
$("settings-theme").value = state.theme;
|
|
$("settings-theme").addEventListener("change", async () => {
|
|
state.theme = $("settings-theme").value;
|
|
await saveState();
|
|
applyTheme(state.theme);
|
|
});
|
|
|
|
$("settings-hide-low-holders").checked = state.hideLowHolderTokens;
|
|
$("settings-hide-low-holders").addEventListener("change", async () => {
|
|
state.hideLowHolderTokens = $("settings-hide-low-holders").checked;
|
|
await saveState();
|
|
});
|
|
|
|
$("settings-hide-fraud-contracts").checked = state.hideFraudContracts;
|
|
$("settings-hide-fraud-contracts").addEventListener("change", async () => {
|
|
state.hideFraudContracts = $("settings-hide-fraud-contracts").checked;
|
|
await saveState();
|
|
});
|
|
|
|
$("settings-hide-dust").checked = state.hideDustTransactions;
|
|
$("settings-hide-dust").addEventListener("change", async () => {
|
|
state.hideDustTransactions = $("settings-hide-dust").checked;
|
|
await saveState();
|
|
});
|
|
|
|
$("settings-dust-threshold").value = state.dustThresholdGwei;
|
|
$("settings-dust-threshold").addEventListener("change", async () => {
|
|
const val = parseInt($("settings-dust-threshold").value, 10);
|
|
if (!isNaN(val) && val >= 0) {
|
|
state.dustThresholdGwei = val;
|
|
await saveState();
|
|
}
|
|
});
|
|
|
|
$("btn-main-add-wallet").addEventListener("click", ctx.showAddWalletView);
|
|
|
|
$("btn-settings-add-token").addEventListener(
|
|
"click",
|
|
ctx.showSettingsAddTokenView,
|
|
);
|
|
|
|
$("btn-settings-back").addEventListener("click", () => {
|
|
ctx.renderWalletList();
|
|
showView("main");
|
|
});
|
|
}
|
|
|
|
module.exports = { init, show, renderSiteLists };
|