fix(aegis): 0.9.8 — address row columns, resizable QR, inline copy
The row's grid template still described the pre-0.9.2 layout — six
columns including an address cell that no longer exists — while the row
renders five. Every cell therefore sat one column left of where it
belonged: copy landed in the label's space, the amount in the old label
column, and the ⋯ in the amount column instead of the edge. That is the
whole cause of "the copy button collides with the amount" and "the last
edit button is not on the edge".
Now five columns for five cells: icon · label · copy · amount · menu,
8px gaps. The label is the flexible one, left-aligned, so it takes the
slack and truncates rather than squeezing the number. Verified at 520,
400 and 300px: no cell overlap at any width, no row overflow, and the
amount never clips — only long labels give way.
The copy glyph was the 📋 emoji, which has no glyph in this platform's
font stack and rendered as a tofu box that read like a stray character
stuck to the balance. Replaced with an inline SVG in both the row and
the address card. Both confirmation flashes now swap innerHTML rather
than textContent, which would have deleted the SVG and left a blank
square.
Address card: the address and its copy button share one row, so the
button sits at the end of the value it copies. The address clamps to two
lines and truncates beyond that instead of growing the card in a narrow
sidebar.
The QR is always visible and the panel is drag-resizable from a grip
under it (pointer events, arrow keys as a non-mouse path, clamped
90–420px, capped against the panel's own width so a size set on a wide
sidebar cannot overflow a narrow one, persisted). That replaces the
0.9.2 show/hide toggle — a size set once beats a binary, and it frees
the row Copy was sharing with a QR button.
drawQr was setting cv.style.width/height, which would have reset the
panel to its intrinsic size on every redraw — i.e. every time the
address changed. It now sets only the backing store and re-asserts the
user's size after drawing.
"Next unused address" is correct at the adapter level on both mainnet
and chipnet (tested: the index advances and the address changes), so the
reported failure is elsewhere. The handler was discarding the error and
flashing a bare "Failed", which is why there was nothing to diagnose; it
now surfaces the real message.
This commit is contained in:
parent
36012e7c26
commit
4c7d8e3f56
3 changed files with 164 additions and 46 deletions
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "aegis",
|
||||
"name": "Aegis Wallet",
|
||||
"version": "0.9.7",
|
||||
"version": "0.9.8",
|
||||
"category": "plugin",
|
||||
"description": "Multi-chain wallet (BCH, BTC, TRX, ETH, SOL, SC, DGB) derived from your Theseus vault. Dapps get window.bitcoincash on .x sites; window.tronWeb / window.tronLink / window.ethereum / window.solana on any https page.",
|
||||
"author": "Silent Mode",
|
||||
|
|
|
|||
|
|
@ -220,6 +220,33 @@
|
|||
.actions { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 10px; }
|
||||
.qrwrap { display: grid; place-items: center; padding: 12px; background: #fff; border-radius: 10px; margin-top: 12px; }
|
||||
.qrwrap[hidden] { display: none; }
|
||||
/* The canvas scales to whatever height the user dragged the panel to,
|
||||
and never wider than the sidebar. aspect-ratio keeps it square. */
|
||||
.qrwrap canvas { width: auto; height: auto; max-width: 100%;
|
||||
aspect-ratio: 1 / 1; image-rendering: pixelated; }
|
||||
/* Drag handle under the QR. A thin strip rather than a corner grip, so
|
||||
it stays reachable at any sidebar width. */
|
||||
.qrgrip { height: 14px; margin: 2px 0 -4px; display: grid; place-items: center;
|
||||
cursor: ns-resize; touch-action: none; }
|
||||
.qrgrip span { display: block; width: 34px; height: 3px; border-radius: 999px;
|
||||
background: var(--line); transition: background .12s; }
|
||||
.qrgrip:hover span, .qrgrip.dragging span { background: var(--acid, #d6ff3d); }
|
||||
/* Address + inline copy. The value clamps to two lines; anything longer
|
||||
is cut rather than growing the card. */
|
||||
.addrrow { display: grid; grid-template-columns: minmax(0,1fr) 30px; gap: 6px;
|
||||
align-items: start; }
|
||||
.addrval { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
|
||||
overflow: hidden; word-break: break-all; line-height: 1.35; }
|
||||
.addrcopy { background: transparent; border: 1px solid var(--line); color: var(--dim);
|
||||
cursor: pointer; width: 30px; height: 30px; border-radius: 7px;
|
||||
display: inline-flex; align-items: center; justify-content: center;
|
||||
line-height: 0; transition: color .12s, border-color .12s, background .12s; }
|
||||
.addrcopy:hover { color: var(--acid, #d6ff3d); border-color: rgb(from var(--acid, #d6ff3d) r g b / .5); }
|
||||
.addrcopy.copied { color: var(--acid, #d6ff3d); border-color: rgb(from var(--acid, #d6ff3d) r g b / .6); }
|
||||
.addrcopy svg { width: 15px; height: 15px; display: block; }
|
||||
/* Hide the action row entirely when its only child is hidden, so it
|
||||
doesn't leave a gap under the address. */
|
||||
#addrActions:not(:has(> :not([hidden]))) { display: none; }
|
||||
canvas { image-rendering: pixelated; }
|
||||
input[type=text], input[type=number], input[type=password], textarea { width: 100%; padding: 7px 9px; border-radius: 7px; background: var(--panel);
|
||||
border: 1px solid var(--line); color: var(--ink); font: inherit; font-size: 13px; outline: none; }
|
||||
|
|
@ -415,31 +442,44 @@
|
|||
"a" and "anthem…" occupy the same slot, amount is fixed to 90px so
|
||||
"0" and "0.000123" right-align identically, actions are fixed to
|
||||
46px. Net result: columns look like a table, not a flex mess. */
|
||||
/* 0.9.8: five columns for the five cells the row actually renders —
|
||||
icon · label · copy · amount · menu. The template still described the
|
||||
pre-0.9.2 layout (six columns, including an address cell that no
|
||||
longer exists), so every cell sat one column left of where it
|
||||
belonged: copy landed in the label's space, the amount in the old
|
||||
label column, and the ⋯ in the amount column instead of the edge.
|
||||
That is what made copy and amount collide.
|
||||
The label is the flexible column so it takes the slack and truncates
|
||||
rather than squeezing the number, and the menu is pinned right. */
|
||||
.wstrip .warow { display: grid;
|
||||
grid-template-columns: 16px minmax(0,1fr) 22px 68px 90px 46px;
|
||||
gap: 6px; align-items: center; padding: 4px 4px;
|
||||
border-radius: 6px; border: 1px solid transparent; cursor: pointer; min-height: 28px; }
|
||||
grid-template-columns: 16px minmax(0,1fr) 24px auto 26px;
|
||||
gap: 8px; align-items: center; padding: 5px 6px;
|
||||
border-radius: 6px; border: 1px solid transparent; cursor: pointer; min-height: 30px; }
|
||||
.wstrip .warow:hover { background: rgba(255,255,255,.04); }
|
||||
.wstrip .warow.on { background: rgb(from var(--acid, #d6ff3d) r g b / .10);
|
||||
border-color: rgb(from var(--acid, #d6ff3d) r g b / .35); }
|
||||
.wstrip .warow .waaddr { font: 12px/1.15 ui-monospace, Consolas, monospace; color: var(--ink);
|
||||
overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
|
||||
/* Copy chip anchored right after the address. Fixed 22px column so the
|
||||
copy button sits at the same x on every row. */
|
||||
/* Copy chip: its own 24px column between label and amount, so it can
|
||||
never crowd the number. SVG rather than the 📋 emoji, which rendered
|
||||
as a tofu box here and read as a glyph stuck to the balance. */
|
||||
.wstrip .warow .wacopy { background: transparent; border: 0; color: var(--dim); cursor: pointer;
|
||||
padding: 2px 4px; border-radius: 4px; font-size: 11px; line-height: 1;
|
||||
transition: color .12s; justify-self: start; }
|
||||
padding: 0; border-radius: 5px; line-height: 0;
|
||||
width: 24px; height: 22px; display: inline-flex;
|
||||
align-items: center; justify-content: center;
|
||||
transition: color .12s, background .12s; justify-self: center; }
|
||||
.wstrip .warow .wacopy svg { width: 13px; height: 13px; display: block; }
|
||||
.wstrip .warow .wacopy:hover { color: var(--acid, #d6ff3d); background: rgba(255,255,255,.06); }
|
||||
.wstrip .warow .wacopy.copied { color: var(--acid, #d6ff3d); }
|
||||
/* Label pill: fixed 68px column. Even an empty label renders an
|
||||
invisible placeholder so the amount column stays put. Long labels
|
||||
truncate with ellipsis inside the pill (max-width: 100% of column). */
|
||||
.wstrip .warow .walabel { font-size: 11px; color: var(--mut);
|
||||
padding: 1px 6px; border-radius: 999px;
|
||||
background: rgba(255,255,255,.06);
|
||||
/* Label pill, left-aligned in the flexible column so a name reads as
|
||||
the row's identity rather than floating mid-row. Truncates with an
|
||||
ellipsis instead of pushing the number around. */
|
||||
.wstrip .warow .walabel { font-size: 11.5px; color: var(--ink);
|
||||
padding: 2px 8px; border-radius: 999px;
|
||||
background: rgba(255,255,255,.07);
|
||||
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
||||
max-width: 100%; box-sizing: border-box;
|
||||
justify-self: center; }
|
||||
justify-self: start; }
|
||||
.wstrip .warow .walabel:empty { visibility: hidden; }
|
||||
/* Balance shares a single line with everything else — no vertical
|
||||
amount/fiat stack. Ticker follows the number in a dim tone so the
|
||||
|
|
@ -451,7 +491,8 @@
|
|||
justify-self: end; overflow: hidden; }
|
||||
.wstrip .warow .waamt .watkr { color: var(--dim); font-size: 11px; font-weight: 500; }
|
||||
.wstrip .warow .wact { background: transparent; border: 0; color: var(--dim); cursor: pointer;
|
||||
padding: 2px 5px; border-radius: 4px; font-size: 12.5px; line-height: 1; }
|
||||
padding: 2px 4px; border-radius: 4px; font-size: 13px; line-height: 1;
|
||||
justify-self: end; }
|
||||
.wstrip .warow .wact:hover { color: var(--acid, #d6ff3d); background: rgba(255,255,255,.06); }
|
||||
/* Per-address asset list (0.8.8). The count chip expands the row into a
|
||||
nested list of what that ONE address holds beyond the native coin. */
|
||||
|
|
@ -627,13 +668,21 @@
|
|||
the fold. Explorer / Faucet moved to the header. -->
|
||||
<div class="card">
|
||||
<div class="lbl">Receiving address <span id="addrMeta"></span></div>
|
||||
<div class="mono" id="addr">—</div>
|
||||
<div class="actions">
|
||||
<button class="btn sm" id="copyAddr">Copy</button>
|
||||
<button class="btn sm" id="qrToggle" aria-expanded="false">QR</button>
|
||||
<!-- 0.9.8: address and its copy button share one row, so the
|
||||
button sits at the end of the value it copies instead of on a
|
||||
separate action line. The address clamps to two lines and
|
||||
truncates beyond that, rather than growing the card in a
|
||||
narrow sidebar. -->
|
||||
<div class="addrrow">
|
||||
<div class="mono addrval" id="addr">—</div>
|
||||
<button class="addrcopy" id="copyAddr" title="Copy this address"></button>
|
||||
</div>
|
||||
<div class="actions" id="addrActions">
|
||||
<button class="btn sm" id="nextAddr" hidden>Next unused address</button>
|
||||
</div>
|
||||
<div class="qrwrap" id="qrWrap" hidden><canvas id="qr" width="200" height="200"></canvas></div>
|
||||
<!-- Drag the bottom edge to resize the QR. Height persists. -->
|
||||
<div class="qrwrap" id="qrWrap"><canvas id="qr" width="200" height="200"></canvas></div>
|
||||
<div class="qrgrip" id="qrGrip" title="Drag to resize the QR code"><span></span></div>
|
||||
</div>
|
||||
<div class="card" id="tokensCard" hidden style="margin-top:12px">
|
||||
<div class="lbl">Assets <span class="hint" id="tokensCount"></span></div>
|
||||
|
|
|
|||
|
|
@ -2500,6 +2500,12 @@ function wireStripDragDrop(el, chainGroups) {
|
|||
});
|
||||
}
|
||||
|
||||
// Copy glyph as inline SVG. The 📋 emoji has no glyph in this platform's
|
||||
// font stack, so it rendered as a tofu box that read like a stray
|
||||
// character stuck to the balance beside it.
|
||||
const COPY_ICON = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="9" y="9" width="11" height="11" rx="2"/><path d="M5 15V5a2 2 0 0 1 2-2h8"/></svg>`;
|
||||
const CHECK_ICON = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 6 9 17l-5-5"/></svg>`;
|
||||
|
||||
// Inline replacement for the modal address list. Rendered directly into
|
||||
// the wallet strip element when stripView.mode === "addresses". Header
|
||||
// row has a back arrow (returns to the coins summary) and the coin's
|
||||
|
|
@ -2557,7 +2563,7 @@ function renderInlineCoinList(el, groupKey, group) {
|
|||
return `<div class="warow ${on}" data-listpick="${esc(w.id)}" title="${esc(w.label || fullAddr)}">
|
||||
<span class="wcell">${logoSvg(meta.logo, 14)}</span>
|
||||
<span class="walabel walabel-wide" title="${esc(w.label || "")}">${esc(w.label || shortName || "—")}</span>
|
||||
${fullAddr ? `<button class="wacopy" data-lpcopy="${esc(fullAddr)}" title="Copy full address (${esc(fullAddr)})">📋</button>` : `<span></span>`}
|
||||
${fullAddr ? `<button class="wacopy" data-lpcopy="${esc(fullAddr)}" title="Copy full address (${esc(fullAddr)})">${COPY_ICON}</button>` : `<span></span>`}
|
||||
<span class="waamt"><span>${esc(bal)}</span><span class="watkr">${esc(meta.ticker)}</span></span>
|
||||
<span style="display:inline-flex;gap:2px;justify-self:end;align-items:center">${rowMenu}</span>
|
||||
</div>`;
|
||||
|
|
@ -2633,9 +2639,11 @@ function renderInlineCoinList(el, groupKey, group) {
|
|||
document.body.appendChild(ta); ta.select();
|
||||
try { document.execCommand("copy"); } finally { ta.remove(); }
|
||||
}
|
||||
const prev = b.textContent;
|
||||
b.classList.add("copied"); b.textContent = "✓";
|
||||
setTimeout(() => { b.classList.remove("copied"); b.textContent = prev; }, 1000);
|
||||
// innerHTML, not textContent — the button holds an inline SVG now,
|
||||
// and assigning textContent would destroy it and leave a blank
|
||||
// square once the confirmation timed out.
|
||||
b.classList.add("copied"); b.innerHTML = CHECK_ICON;
|
||||
setTimeout(() => { b.classList.remove("copied"); b.innerHTML = COPY_ICON; }, 1000);
|
||||
} catch {}
|
||||
}));
|
||||
el.querySelectorAll("[data-lpedit]").forEach((b) => b.addEventListener("click", (e) => {
|
||||
|
|
@ -3403,42 +3411,103 @@ function drawQr(text) {
|
|||
try { q = window.QR.build(text); } catch { g.clearRect(0, 0, cv.width, cv.height); return; }
|
||||
const scale = Math.max(2, Math.floor(200 / (q.size + 2)));
|
||||
const px = (q.size + 2) * scale;
|
||||
// Backing store only. The DISPLAY size belongs to the user's dragged
|
||||
// preference — setting cv.style here would reset the panel to its
|
||||
// intrinsic size on every redraw (i.e. whenever the address changed).
|
||||
cv.width = cv.height = px;
|
||||
cv.style.width = cv.style.height = px + "px";
|
||||
g.fillStyle = "#fff"; g.fillRect(0, 0, px, px);
|
||||
g.fillStyle = "#000";
|
||||
for (let r = 0; r < q.size; r++) for (let c = 0; c < q.size; c++) if (q.modules[r][c]) g.fillRect((c + 1) * scale, (r + 1) * scale, scale, scale);
|
||||
applyQrSize();
|
||||
}
|
||||
|
||||
// ---- receive actions -------------------------------------------------------
|
||||
|
||||
// 0.9.2: the QR is collapsed by default — it was 200px of permanent
|
||||
// vertical space above the address, which pushed the asset list off
|
||||
// screen. The choice sticks per-user because someone who receives by
|
||||
// QR wants it every time, and someone who copies never wants it.
|
||||
(function wireQrToggle() {
|
||||
const btn = $("qrToggle"); if (!btn) return;
|
||||
let open = false;
|
||||
try { open = localStorage.getItem("aegis/qrOpen") === "1"; } catch {}
|
||||
const paint = () => {
|
||||
const wrap = $("qrWrap"); if (!wrap) return;
|
||||
wrap.hidden = !open;
|
||||
btn.setAttribute("aria-expanded", open ? "true" : "false");
|
||||
btn.classList.toggle("primary", open);
|
||||
// 0.9.8: the QR is always visible and the panel is drag-resizable, which
|
||||
// replaces the 0.9.2 show/hide toggle — a size the user sets once is a
|
||||
// better answer than a binary, and it means the QR button no longer
|
||||
// competes with Copy for the one row that gets used.
|
||||
let applyQrSize = () => {};
|
||||
(function wireQrResize() {
|
||||
const grip = $("qrGrip"), wrap = $("qrWrap");
|
||||
if (!grip || !wrap) return;
|
||||
const MIN = 90, MAX = 420;
|
||||
const clamp = (v) => Math.max(MIN, Math.min(MAX, Math.round(v)));
|
||||
let size = 200;
|
||||
try {
|
||||
const saved = Number(localStorage.getItem("aegis/qrSize"));
|
||||
if (Number.isFinite(saved) && saved > 0) size = clamp(saved);
|
||||
} catch {}
|
||||
const apply = () => {
|
||||
// Cap against the actual panel width too — a size dragged wide on a
|
||||
// roomy sidebar must not overflow when the sidebar is narrowed later.
|
||||
const room = Math.max(MIN, (wrap.clientWidth || MAX) - 24);
|
||||
const px = Math.min(size, room);
|
||||
const cv = $("qr");
|
||||
if (cv) { cv.style.width = px + "px"; cv.style.height = px + "px"; }
|
||||
};
|
||||
btn.addEventListener("click", () => {
|
||||
open = !open;
|
||||
try { localStorage.setItem("aegis/qrOpen", open ? "1" : "0"); } catch {}
|
||||
paint();
|
||||
applyQrSize = apply;
|
||||
apply();
|
||||
window.addEventListener("resize", apply);
|
||||
let startY = 0, startSize = 0, active = false;
|
||||
const onMove = (e) => {
|
||||
if (!active) return;
|
||||
e.preventDefault();
|
||||
size = clamp(startSize + (e.clientY - startY));
|
||||
apply();
|
||||
};
|
||||
const onUp = () => {
|
||||
if (!active) return;
|
||||
active = false;
|
||||
grip.classList.remove("dragging");
|
||||
try { grip.releasePointerCapture?.(grip._pid); } catch {}
|
||||
try { localStorage.setItem("aegis/qrSize", String(size)); } catch {}
|
||||
window.removeEventListener("pointermove", onMove);
|
||||
window.removeEventListener("pointerup", onUp);
|
||||
};
|
||||
grip.addEventListener("pointerdown", (e) => {
|
||||
active = true; startY = e.clientY;
|
||||
const cv = $("qr");
|
||||
startSize = cv ? (parseInt(cv.style.width, 10) || 200) : 200;
|
||||
grip._pid = e.pointerId;
|
||||
grip.classList.add("dragging");
|
||||
try { grip.setPointerCapture(e.pointerId); } catch {}
|
||||
window.addEventListener("pointermove", onMove);
|
||||
window.addEventListener("pointerup", onUp);
|
||||
e.preventDefault();
|
||||
});
|
||||
paint();
|
||||
// Keyboard affordance — a drag-only control is unusable without a mouse.
|
||||
grip.tabIndex = 0;
|
||||
grip.addEventListener("keydown", (e) => {
|
||||
const step = e.shiftKey ? 24 : 8;
|
||||
if (e.key === "ArrowUp") { size = clamp(size - step); }
|
||||
else if (e.key === "ArrowDown") { size = clamp(size + step); }
|
||||
else return;
|
||||
e.preventDefault(); apply();
|
||||
try { localStorage.setItem("aegis/qrSize", String(size)); } catch {}
|
||||
});
|
||||
})();
|
||||
// The inline copy button holds an SVG; set it once at load.
|
||||
(function paintCopyIcon() {
|
||||
const b = $("copyAddr");
|
||||
if (b && !b.firstElementChild) b.innerHTML = COPY_ICON;
|
||||
})();
|
||||
$("copyAddr").addEventListener("click", async () => {
|
||||
try { await navigator.clipboard.writeText(sel().address); flash($("copyAddr"), "Copied"); } catch {}
|
||||
const b = $("copyAddr");
|
||||
try {
|
||||
await navigator.clipboard.writeText(sel().address);
|
||||
// innerHTML, not flash() — flash() swaps textContent, which would
|
||||
// delete the inline SVG and leave an empty square behind.
|
||||
b.classList.add("copied"); b.innerHTML = CHECK_ICON;
|
||||
setTimeout(() => { b.classList.remove("copied"); b.innerHTML = COPY_ICON; }, 1000);
|
||||
} catch {}
|
||||
});
|
||||
$("nextAddr").addEventListener("click", async () => {
|
||||
try { const s = await S.invoke("nextAddress"); state.selected = { ...state.selected, ...s }; render(); }
|
||||
catch (e) { flash($("nextAddr"), "Failed"); }
|
||||
// The old handler discarded the error and flashed a bare "Failed", so
|
||||
// neither the user nor a maintainer could tell WHY — which is why
|
||||
// "next unused address doesn't work" had no diagnosable cause. Show it.
|
||||
catch (e) { showErr("Next address: " + cleanErr(e)); }
|
||||
});
|
||||
$("viewAddr").addEventListener("click", () => openUrl(explorerHref(sel().explorerAddr, sel().address)));
|
||||
$("openFaucet").addEventListener("click", () => sel().faucet && openUrl(sel().faucet));
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue