feat(ui): responsive mobile overhaul, single-column save forms, and wallet viewer polish
This commit is contained in:
@@ -137,8 +137,8 @@ function createWindow() {
|
||||
mainWindow = new BrowserWindow({
|
||||
width: 1200,
|
||||
height: 750,
|
||||
minWidth: 900,
|
||||
minHeight: 600,
|
||||
minWidth: 320,
|
||||
minHeight: 480,
|
||||
title: 'Wallet Generator',
|
||||
backgroundColor: '#0f1117',
|
||||
webPreferences: {
|
||||
|
||||
@@ -95,7 +95,7 @@ export default function HDWallet() {
|
||||
|
||||
<div className="card">
|
||||
<div className="card-title">Configuration</div>
|
||||
<div className="form-grid">
|
||||
<div className="form-grid save-form-grid">
|
||||
<div className="form-group">
|
||||
<label className="form-label">Network</label>
|
||||
<select className="form-select" value={form.network} onChange={e => set('network', e.target.value)}>
|
||||
@@ -145,7 +145,7 @@ export default function HDWallet() {
|
||||
{wallet && (
|
||||
<>
|
||||
<div className="card">
|
||||
<div className="card-title" style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<div className="card-title card-title-row">
|
||||
<span>Seed Phrase</span>
|
||||
<div className="btn-row">
|
||||
<CopyButton text={ks.seed} />
|
||||
@@ -194,13 +194,13 @@ export default function HDWallet() {
|
||||
<tbody>
|
||||
{wallet.addresses.receiving.map(a => (
|
||||
<tr key={a.index}>
|
||||
<td className="addr-index">{a.index}</td>
|
||||
<td className="addr-path">{a.path}</td>
|
||||
<td className="addr-mono">{a.address}</td>
|
||||
<td className="addr-mono" style={{ maxWidth: 160, overflow: 'hidden', textOverflow: 'ellipsis' }}>
|
||||
<td className="addr-index" data-label="Index">{a.index}</td>
|
||||
<td className="addr-path" data-label="Path">{a.path}</td>
|
||||
<td className="addr-mono" data-label="Address">{a.address}</td>
|
||||
<td className="addr-mono addr-clip" data-label="Public Key">
|
||||
{a.public_key}
|
||||
</td>
|
||||
<td>
|
||||
<td className="addr-actions" data-label="Actions">
|
||||
<div className="btn-row">
|
||||
<CopyButton text={a.address} />
|
||||
<CopyButton text={a.private_key_wif} />
|
||||
@@ -214,7 +214,7 @@ export default function HDWallet() {
|
||||
|
||||
<div className="card">
|
||||
<div className="card-title">Save Wallet</div>
|
||||
<div className="form-grid" style={{ marginBottom: 12 }}>
|
||||
<div className="form-grid save-form-grid" style={{ marginBottom: 12 }}>
|
||||
<div className="form-group">
|
||||
<label className="form-label">Filename</label>
|
||||
<input
|
||||
|
||||
@@ -146,7 +146,7 @@ export default function SingleAddress({ initialTab = 'p2pkh' }) {
|
||||
|
||||
<div className="card">
|
||||
<div className="card-title">Save</div>
|
||||
<div className="form-grid" style={{ marginBottom: 12 }}>
|
||||
<div className="form-grid save-form-grid" style={{ marginBottom: 12 }}>
|
||||
<div className="form-group">
|
||||
<label className="form-label">Filename</label>
|
||||
<input
|
||||
|
||||
@@ -101,14 +101,14 @@ export default function WalletViewer() {
|
||||
<div className="page-subtitle">Read and decrypt HD and Single wallet JSON files</div>
|
||||
|
||||
<div className="card">
|
||||
<div className="card-title" style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<div className="card-title card-title-row">
|
||||
<span>Wallet Files</span>
|
||||
<button className="btn btn-secondary" onClick={refreshWallets} disabled={loadingFiles}>
|
||||
{loadingFiles ? 'Refreshing…' : 'Refresh'}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="form-grid">
|
||||
<div className="viewer-file-columns">
|
||||
<FileColumn
|
||||
title="HD Wallets"
|
||||
files={files.hd}
|
||||
@@ -188,18 +188,11 @@ function FileColumn({ title, files, kind, selected, onOpen }) {
|
||||
return (
|
||||
<button
|
||||
key={file.name}
|
||||
className="btn btn-secondary"
|
||||
className={`btn btn-secondary wallet-file-btn${isActive ? ' active' : ''}`}
|
||||
onClick={() => onOpen(kind, file.name)}
|
||||
style={{
|
||||
width: '100%',
|
||||
justifyContent: 'space-between',
|
||||
borderColor: isActive ? 'var(--accent)' : undefined,
|
||||
color: isActive ? 'var(--accent)' : undefined,
|
||||
marginBottom: 6,
|
||||
}}
|
||||
>
|
||||
<span style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{file.name}</span>
|
||||
<span style={{ fontSize: 11, color: 'var(--text-xs)' }}>
|
||||
<span className="wallet-file-name">{file.name}</span>
|
||||
<span className="wallet-file-meta">
|
||||
{formatBytes(file.size)} · {formatDate(file.mtimeMs)}
|
||||
</span>
|
||||
</button>
|
||||
@@ -218,7 +211,7 @@ function HDWalletView({ wallet }) {
|
||||
return (
|
||||
<>
|
||||
<div className="card">
|
||||
<div className="card-title" style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<div className="card-title card-title-row">
|
||||
<span>Seed Phrase</span>
|
||||
<div className="btn-row">
|
||||
<CopyButton text={ks.seed} />
|
||||
@@ -267,13 +260,13 @@ function HDWalletView({ wallet }) {
|
||||
<tbody>
|
||||
{(wallet.addresses?.receiving || []).map(addr => (
|
||||
<tr key={addr.index}>
|
||||
<td className="addr-index">{addr.index}</td>
|
||||
<td className="addr-path">{addr.path}</td>
|
||||
<td className="addr-mono">{addr.address}</td>
|
||||
<td className="addr-mono" style={{ maxWidth: 160, overflow: 'hidden', textOverflow: 'ellipsis' }}>
|
||||
<td className="addr-index" data-label="Index">{addr.index}</td>
|
||||
<td className="addr-path" data-label="Path">{addr.path}</td>
|
||||
<td className="addr-mono" data-label="Address">{addr.address}</td>
|
||||
<td className="addr-mono addr-clip" data-label="WIF">
|
||||
{addr.private_key_wif}
|
||||
</td>
|
||||
<td>
|
||||
<td className="addr-actions" data-label="Actions">
|
||||
<div className="btn-row">
|
||||
<CopyButton text={String(addr.address || '')} />
|
||||
<CopyButton text={String(addr.private_key_wif || '')} />
|
||||
|
||||
@@ -121,6 +121,7 @@ html, body, #root {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 28px 32px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
@@ -153,6 +154,14 @@ html, body, #root {
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.card-title-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
/* ── Form ─────────────────────────────────────────────────────────────────── */
|
||||
.form-grid {
|
||||
display: grid;
|
||||
@@ -161,6 +170,16 @@ html, body, #root {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.save-form-grid {
|
||||
grid-template-columns: 1fr !important;
|
||||
}
|
||||
|
||||
.viewer-file-columns {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.form-group { display: flex; flex-direction: column; gap: 5px; }
|
||||
|
||||
.form-label {
|
||||
@@ -338,6 +357,7 @@ html, body, #root {
|
||||
.addr-mono { font-family: var(--mono); }
|
||||
.addr-index { color: var(--text-xs); font-family: var(--mono); }
|
||||
.addr-path { color: var(--text-dim); font-family: var(--mono); font-size: 11px; }
|
||||
.addr-clip { max-width: 160px; overflow: hidden; text-overflow: ellipsis; }
|
||||
|
||||
/* ── Tabs ─────────────────────────────────────────────────────────────────── */
|
||||
.tabs { display: flex; gap: 4px; margin-bottom: 20px; flex-wrap: wrap; }
|
||||
@@ -414,3 +434,273 @@ html, body, #root {
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.06em;
|
||||
}
|
||||
|
||||
.wallet-file-btn {
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 6px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.wallet-file-btn.active {
|
||||
border-color: var(--accent);
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.wallet-file-name {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.wallet-file-meta {
|
||||
font-size: 11px;
|
||||
color: var(--text-xs);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.main {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.page-subtitle {
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
|
||||
.card {
|
||||
padding: 16px;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.form-grid {
|
||||
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
|
||||
gap: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.app {
|
||||
flex-direction: column;
|
||||
height: 100dvh;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
border-right: none;
|
||||
border-bottom: 1px solid var(--border);
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.sidebar-logo {
|
||||
padding: 10px 12px;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.sidebar-logo svg {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.sidebar-logo span {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.sidebar-nav {
|
||||
flex: none;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
padding: 8px 12px 10px;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
border-top: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
width: auto;
|
||||
flex: 0 0 auto;
|
||||
border-radius: 999px;
|
||||
border: 1px solid var(--border);
|
||||
background: var(--panel);
|
||||
font-size: 12px;
|
||||
padding: 7px 12px;
|
||||
}
|
||||
|
||||
.nav-item.active {
|
||||
background: var(--accent);
|
||||
border-color: var(--accent);
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.main {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: 18px;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.page-subtitle {
|
||||
font-size: 12px;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.card {
|
||||
padding: 14px;
|
||||
margin-bottom: 12px;
|
||||
border-radius: 9px;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-size: 11px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.form-grid {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 10px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.tabs {
|
||||
gap: 6px;
|
||||
margin-bottom: 14px;
|
||||
flex-wrap: nowrap;
|
||||
overflow-x: auto;
|
||||
padding-bottom: 2px;
|
||||
}
|
||||
|
||||
.tab {
|
||||
flex: 0 0 auto;
|
||||
padding: 7px 10px;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 9px 14px;
|
||||
}
|
||||
|
||||
.card > .btn-primary,
|
||||
.card > .btn-secondary,
|
||||
.save-actions .btn {
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.checkbox-group {
|
||||
align-items: flex-start;
|
||||
padding: 6px 0;
|
||||
}
|
||||
|
||||
.kv-row {
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.kv-label {
|
||||
min-width: 0;
|
||||
padding-top: 0;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.kv-actions {
|
||||
align-self: flex-end;
|
||||
}
|
||||
|
||||
.seed-word {
|
||||
padding: 4px 10px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.wallet-file-btn {
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.wallet-file-name {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.wallet-file-meta {
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.addr-table {
|
||||
border-collapse: separate;
|
||||
border-spacing: 0;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.addr-table thead {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.addr-table tbody {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.addr-table tr {
|
||||
display: block;
|
||||
margin-bottom: 10px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
background: var(--panel);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.addr-table tr:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.addr-table td {
|
||||
display: block;
|
||||
padding: 8px 10px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.addr-table td:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.addr-table td::before {
|
||||
content: attr(data-label);
|
||||
display: block;
|
||||
margin-bottom: 4px;
|
||||
color: var(--text-xs);
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
.addr-table td[data-label=""]::before {
|
||||
content: none;
|
||||
}
|
||||
|
||||
.addr-table .addr-clip {
|
||||
max-width: none;
|
||||
overflow: visible;
|
||||
text-overflow: initial;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.addr-table .addr-mono,
|
||||
.addr-table .addr-path,
|
||||
.addr-table .addr-index {
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.addr-table .addr-actions .btn-row {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user