settings: one long page of section cards behind a sticky index, segments and switches for the choices, and the Networks card moved in from Community

This commit is contained in:
KeelrLeek committed 2026-09-08 15:39:19 -05:00
1 parent 6c44f3e2ab
commit 7e291cfccc
2 files changed
+632 -815

No files matched your search

+4 -110
View File
@@ -434,7 +434,6 @@
let exportDone = $state(null);
let confirmRotate = $state(false);
let confirmRemove = $state(false);
let disclosuresOpen = $state(false);
async function doExport() {
const r = await exportPersona(passExport);
if (r) {
@@ -1473,7 +1472,7 @@
</div>
{#if community.busy["holdings:share"]}<span class="chip off">working…</span>{:else if sv.share_holdings}<span class="chip acc">sharing</span>{:else}<span class="chip off">not sharing</span>{/if}
</div>
<div class="disc"><Icon name="alert" size={14} /><span><b>What the room learns:</b> members can tell whether you hold any album they can name, and roughly how many you hold. They never see files, paths, or formats beyond a lossless/lossy word. Turning this off withdraws the list; copies members already read stay with them.</span></div>
<div class="holddisc"><Icon name="alert" size={14} /><span><b>What the room learns:</b> members can tell whether you hold any album they can name, and roughly how many you hold. They never see files, paths, or formats beyond a lossless/lossy word. Turning this off withdraws the list; copies members already read stay with them.</span></div>
<div class="panel" style="margin-top:12px">
<h3><Icon name="search" size={13} /> Ask this room{#if hv}<span class="chip up">{hv.sharing} of {sv.members.length} sharing</span>{/if}</h3>
<form class="inl askinl" style="margin-top:8px" onsubmit={(e) => { e.preventDefault(); if (askArtist.trim() && askAlbum.trim()) asked = { artist: askArtist.trim(), album: askAlbum.trim() }; }}>
@@ -1809,98 +1808,7 @@
<div class="yc c">
<div class="panel">
<h3>Networks</h3>
<div class="netcard">
<div class="nico slsk"><Icon name="globe" size={18} /></div>
<div class="ntx">
<div class="t">Soulseek <span class="chip {online ? 'ok' : 'off'}">{online ? "online" : "offline"}</span></div>
<div class="s">
{#if me}Signed in as <b>{me}</b> · {fmtCount(network.shareFiles)} files shared.{:else}Not signed in — see Settings.{/if}
<br />
{#if !key}
Create a SoulStream identity to link this account to it.
{:else if !me}
Sign in to Soulseek to link this account to your identity.
{:else if ownLinkCurrent}
<b>Linked to this identity</b> — proof verified {new Date(ownLink.verified_at * 1000).toLocaleString()} through a Soulseek message to yourself. Other SoulStream users who ask over Soulseek now get your signed ID; nothing is published.
{:else}
Not linked to your identity. Linking sends one message to yourself over Soulseek and verifies the signed answer — nothing is published, and from then on other SoulStream users who ask get your signed ID.
{/if}
</div>
{#if key && me}
<div class="noderow">
{#if ownLinkCurrent}
<button class="ax-ghost sm" onclick={() => unlinkSelf()} disabled={!!community.busy.linkself}>Unlink</button>
{:else}
<button class="ax-fill sm" onclick={() => linkSelf()} disabled={!online || !nodeUp || !!community.busy.linkself}><Icon name="link" size={12} /> {community.busy.linkself ? "Linking…" : "Link this account"}</button>
{#if !nodeUp}<span class="hint">needs the node connected</span>{/if}
{/if}
</div>
{/if}
</div>
</div>
<div class="netcard">
<div class="nico fn"><Icon name="disc" size={18} /></div>
<div class="ntx">
<div class="t">Freenet <span class="chip acc">experimental</span></div>
<div class="s">
{#if freenetOn}
Local node on 127.0.0.1:{community.freenet?.ws_port ?? 7509} · <span class="chip {status.tone}">{status.text}</span>
<button class="lnk" onclick={() => detectNode()} disabled={!!community.busy.detect}>{community.busy.detect ? "checking…" : "check again"}</button>
{#if statusKind(community.freenet?.status) === "node-absent"}
<div class="noderow">
{#if community.nodeInfo?.binary}
<button class="ax-fill sm" onclick={() => startNode()} disabled={!!community.busy.node}>
<Icon name="disc" size={12} /> {community.busy.node ? "Starting…" : "Start node"}
</button>
<span class="hint">runs the installed peer{#if community.nodeInfo.version} ({community.nodeInfo.version}){/if} in the background · telemetry pinned off · data in the app folder</span>
{:else}
<span class="hint warn">No Freenet peer found on this machine — install it (cargo install freenet) and check again.</span>
{/if}
</div>
{:else if community.nodeInfo?.started_by_app}
<div class="noderow">
<button class="ax-ghost sm" onclick={() => stopNode()} disabled={!!community.busy.node}>{community.busy.node ? "Stopping…" : "Stop node"}</button>
<span class="hint">started by SoulStream · keeps running when the app closes; this button stops it</span>
</div>
{/if}
{:else}
Off. Carries your identity and profile; later playlists, friend links and Spaces. Turning it on publishes nothing. Turning it off is instant and touches nothing else in the app.
{/if}
</div>
<details class="disc" bind:open={disclosuresOpen}>
<summary>Before you rely on it — seven things that are true</summary>
<ul>
<li>A desktop background peer must run; it uses network, disk, memory and CPU.</li>
<li>The network is alpha. Things you publish are replicated and cannot be guaranteed erased.</li>
<li>Freenet is not an anonymity service.</li>
<li>The node's own diagnostics are a node setting SoulStream pins off; verify after upgrades.</li>
<li>Nothing is published when you enable this. Publishing is always a button.</li>
<li>Nothing here ever says "online" — a successful fetch is not presence.</li>
<li>Phones and TVs cannot run the same peer today.</li>
</ul>
</details>
{#if freenetOn}
<label class="swrow">
<button class="tog" class:on={community.freenet?.load_remote_images ?? true} role="switch" aria-label="Load remote profile images" aria-checked={community.freenet?.load_remote_images ?? true} onclick={() => setLoadRemoteImages(!(community.freenet?.load_remote_images ?? true))} disabled={!!community.busy.images}></button>
<span>Load remote profile images</span>
<span class="hint r">verified, resized, cached by hash</span>
</label>
{#if key}
<div class="noderow">
{#if community.inbox?.published}
<span class="chip ok"><Icon name="lock" size={10} />encrypted inbox · key {community.inbox.box_key_id}</span>
<button class="ax-ghost sm" onclick={() => checkInbox()} disabled={!nodeUp || !!community.busy.inboxcheck}>{community.busy.inboxcheck ? "Checking…" : "Check now"}</button>
<span class="hint">{community.inbox.last_check ? `checked ${fmtWhen(community.inbox.last_check * 1000)} · ` : ""}friend requests and shares arrive sealed to a key kept on this device; a persona restored elsewhere publishes a new one</span>
{:else}
<button class="ax-fill sm" onclick={() => publishInbox()} disabled={!nodeUp || !!community.busy.inbox}><Icon name="lock" size={12} /> {community.busy.inbox ? "Publishing…" : "Publish an encrypted inbox"}</button>
<span class="hint">lets other SoulStream users send you friend requests and shares sealed, without Soulseek in between</span>
{/if}
</div>
{/if}
{/if}
</div>
<button class="tog big" class:on={freenetOn} role="switch" aria-checked={freenetOn} title={freenetOn ? "Turn the Freenet layer off" : "Turn the Freenet layer on"} onclick={() => setFreenetEnabled(!freenetOn)} disabled={!!community.busy.freenet}></button>
</div>
<p class="hint">The Soulseek link, the Freenet node, remote images and the encrypted inbox live in <button class="lnk" onclick={() => goToView("settings")}>Settings → Networks</button> now — every switch in one place.</p>
</div>
<div class="panel dim">
<h3><Icon name="devices" size={13} /> Devices <span class="chip off">later</span></h3>
@@ -2453,8 +2361,8 @@
.mem .role { font-size: 9.5px; font-weight: 900; letter-spacing: 0.08em; text-transform: uppercase; color: var(--text-muted); }
.mem .role.own { color: #d9a6ff; }
/* holdings: the disclosure and the who-shares list */
.disc { display: flex; gap: 8px; align-items: flex-start; padding: 9px 11px; border-radius: 9px; background: color-mix(in srgb, var(--warn) 7%, transparent); border: 1px solid color-mix(in srgb, var(--warn) 25%, transparent); font-size: 11.5px; color: var(--text-secondary); line-height: 1.5; }
.disc b { color: #f0c070; }
.holddisc { display: flex; gap: 8px; align-items: flex-start; padding: 9px 11px; border-radius: 9px; background: color-mix(in srgb, var(--warn) 7%, transparent); border: 1px solid color-mix(in srgb, var(--warn) 25%, transparent); font-size: 11.5px; color: var(--text-secondary); line-height: 1.5; }
.holddisc b { color: #f0c070; }
.askinl input { height: 30px; padding: 0 10px; border-radius: 8px; background: var(--surf-hi); border: none; color: var(--text-primary); font-size: 12px; outline: none; }
.askinl input::placeholder { color: var(--text-muted); }
.hlist { display: flex; flex-direction: column; gap: 2px; margin-top: 8px; }
@@ -2522,25 +2430,11 @@
.kv dt { color: var(--text-muted); font-weight: 700; }
.kv dd { margin: 0; color: var(--text-secondary); overflow-wrap: anywhere; }
.kv dd b { color: var(--text-primary); }
.netcard { display: flex; gap: 12px; align-items: flex-start; margin-top: 12px; }
.nico { width: 36px; height: 36px; border-radius: 10px; display: grid; place-items: center; flex: none; }
.nico.slsk { background: rgba(255, 255, 255, 0.08); color: #fff; }
.nico.fn { background: var(--accent-soft); color: var(--accent-hover); }
.ntx { min-width: 0; flex: 1; }
.netcard .t { font-size: 13.5px; font-weight: 800; display: flex; align-items: center; gap: 8px; }
.netcard .s { font-size: 11.5px; color: var(--text-muted); margin-top: 3px; line-height: 1.5; }
.netcard .s b { color: var(--text-secondary); font-weight: 700; }
.tog { display: inline-block; width: 36px; height: 20px; border-radius: 10px; background: rgba(255, 255, 255, 0.16); position: relative; flex: none; transition: background 0.15s; }
.tog::after { content: ""; position: absolute; top: 2px; left: 2px; width: 16px; height: 16px; border-radius: 50%; background: #fff; transition: left 0.15s; }
.tog.on { background: var(--accent); }
.tog.on::after { left: 18px; }
.tog:disabled { opacity: 0.5; cursor: default; }
.tog.big { margin-top: 6px; }
.swrow { display: flex; align-items: center; gap: 10px; margin-top: 12px; font-size: 12px; color: var(--text-secondary); }
.swrow .hint.r { margin-left: auto; }
.disc { margin-top: 10px; }
.disc summary { font-size: 11.5px; font-weight: 800; color: var(--text-secondary); cursor: pointer; }
.disc ul { margin: 8px 0 0; padding-left: 18px; font-size: 11.5px; color: var(--text-muted); line-height: 1.6; }
.danger { display: flex; gap: 8px; align-items: center; margin-top: 14px; padding-top: 12px; border-top: 1px solid var(--hair); flex-wrap: wrap; }
/* ── the person drawer (portaled to body — see portal()) ── */
+628 -705
View File
File diff suppressed because it is too large. Load diff