feat(faucet): add an Order toggle (Default / A→Z / Market cap)

New "Order" control in the header reorders both the chain cards/tabs and the EVM
network dropdown:
- Default — the curated order (embeddable-first).
- A→Z — alphabetical by chain name.
- Market cap — approximate native-coin rank (BTC > ETH > BNB > SOL > TRX > AVAX >
  POL > ARB > BASE > OP > BCH); the EVM group sorts by its top member (ETH).
Choice persists (faucet.order); applied on load. Ordering re-renders cards/tabs
and re-applies collapse / active-tab / auto-refresh state.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Local Dev 2026-07-19 17:07:50 +02:00
parent a9b11e81cb
commit a5183cf1a1

View file

@ -208,6 +208,13 @@
<button data-mode="multi" class="active">▤ Multiple open</button>
<button data-mode="accordion">▬ One open</button>
</div>
<label class="auto" id="order-wrap">Order
<select id="order-sel">
<option value="default">Default</option>
<option value="alpha">A → Z</option>
<option value="mcap">Market cap</option>
</select>
</label>
</div>
</header>
@ -421,6 +428,12 @@ window.startFaucetApp = (function(){
let evmActive = ls.get(EVMKEY);
if(!EVM_CHAINS.includes(evmActive)) evmActive = EVM_CHAINS[0];
// chain ordering: default (curated) · alpha · market cap (approx native-coin rank)
const ORDERKEY = 'faucet.order';
let orderMode = ls.get(ORDERKEY) || 'default';
const MCAP_RANK = { btc:1, eth:2, bsc:3, sol:4, tron:5, avax:6, polygon:7, arbitrum:8, base:9, optimism:10, bch:11 };
const rankOf = id => MCAP_RANK[id] ?? 99;
// discovered tokens persist globally (contract defs are chain-wide, not per-profile)
const TOKENS_KEY = 'faucet.tokens.v1';
const discoveredTokens = parseObj(TOKENS_KEY);
@ -761,6 +774,21 @@ window.startFaucetApp = (function(){
// the chain a render unit currently shows (group → the active EVM network)
const unitChain = u => u.group ? CHAIN_BY_ID[evmActive] : u.chain;
// ordering (default / alpha / market cap) applied to both cards and the EVM dropdown
const unitName = u => u.group ? 'EVM Networks' : u.chain.name;
const unitRank = u => u.group ? Math.min(...u.chains.map(c => rankOf(c.id))) : rankOf(u.chain.id);
function orderedUnits(){
const arr = RENDER_UNITS.slice();
if(orderMode === 'alpha') arr.sort((a,b) => unitName(a).localeCompare(unitName(b)));
else if(orderMode === 'mcap') arr.sort((a,b) => unitRank(a) - unitRank(b));
return arr;
}
function sortedChains(chains){
const arr = chains.slice();
if(orderMode === 'alpha') arr.sort((a,b) => a.name.localeCompare(b.name));
else if(orderMode === 'mcap') arr.sort((a,b) => rankOf(a.id) - rankOf(b.id));
return arr;
}
const autoCtlHTML = unit => `<div class="head-ctls">
<label class="auto-lbl">Auto-refresh
<select class="chain-auto" data-unit="${unit}" title="Auto-refresh this card's balances">
@ -770,7 +798,7 @@ window.startFaucetApp = (function(){
function cardHTML(u){
const c = unitChain(u);
const titleEl = u.group
? `<select class="evm-net" title="Choose EVM network">${u.chains.map(x => `<option value="${x.id}"${x.id===evmActive?' selected':''}>${esc(x.name)}</option>`).join('')}</select>`
? `<select class="evm-net" title="Choose EVM network">${sortedChains(u.chains).map(x => `<option value="${x.id}"${x.id===evmActive?' selected':''}>${esc(x.name)}</option>`).join('')}</select>`
: `<h2>${esc(c.name)}</h2>`;
return `<div class="card" data-chain="${c.id}" data-unit="${u.unit}">
<div class="card-head">
@ -794,8 +822,9 @@ window.startFaucetApp = (function(){
return `<div class="tab${active?' active':''}" data-unit="${u.unit}" data-chain="${c.id}"><span class="cicon sm" style="color:${c.color}">${ICONS[c.id]||''}</span>${esc(short)}</div>`;
}
function buildPanels(){
panels.innerHTML = RENDER_UNITS.map(cardHTML).join('');
tabbar.innerHTML = RENDER_UNITS.map((u,i) => tabHTML(u, i===0)).join('');
const units = orderedUnits();
panels.innerHTML = units.map(cardHTML).join('');
tabbar.innerHTML = units.map((u,i) => tabHTML(u, i===0)).join('');
wireEmbeds();
}
// rebuild a single unit's card + tab (used when the EVM network dropdown changes)
@ -1033,6 +1062,21 @@ window.startFaucetApp = (function(){
}
viewSeg.addEventListener('click', e => { const b = e.target.closest('button'); if(b) setView(b.dataset.view); });
/* ---- chain order (default / alpha / market cap) ---- */
const orderSel = document.getElementById('order-sel');
function applyOrder(mode){
orderMode = mode; ls.set(ORDERKEY, mode); orderSel.value = mode;
buildPanels(); // re-render cards + tabs in new order
applyCollapsed();
setActiveTab(ls.get(TKEY) || RENDER_UNITS[0].unit);
RENDER_UNITS.forEach(u => { // restore auto-refresh select display
const s = panels.querySelector(`.card[data-unit="${u.unit}"] .chain-auto`);
if(s){ const v = autoByChain[u.unit] || 0; s.value = String(v); s.classList.toggle('live', v > 0); }
});
reloadSet(document.querySelectorAll('.bal[data-addr]'));
}
orderSel.addEventListener('change', () => applyOrder(orderSel.value));
const UNIT_IDS = new Set(RENDER_UNITS.map(u => u.unit));
function setActiveTab(unit){
if(!UNIT_IDS.has(unit)) unit = RENDER_UNITS[0].unit;
@ -1128,6 +1172,7 @@ window.startFaucetApp = (function(){
document.getElementById('profile-del').addEventListener('click', deleteProfile);
/* ===================== init ===================== */
orderSel.value = orderMode;
buildPanels();
updateProfileUI();
setPanelMode(panelMode);