mirror of
https://github.com/narrrl/pl8list-rewind.git
synced 2026-11-04 00:46:59 +01:00
Fix small front-end faults found in the audit
- A failed likes request shows the error in the panel instead of an empty list. - A failed sign-out says why instead of doing nothing. - Long toasts, like a copied link, wrap instead of running off narrow screens. - Keys no longer act on the feed behind the help; ? still toggles it, and focus goes into the help and back to where it was. - Phones reach the help from the account panel; the bar has no room for ?. - Cards next to the current one are inert, so Tab no longer walks into buttons that are off screen. - Keyboard focus is visible everywhere, and panels use the shared colour and radius tokens.
This commit is contained in:
1 parent
2dcca0bc94
commit
6e961d3e0e
4 files changed
+55
-28
No files matched your search
@@ -124,6 +124,7 @@
|
|||||||
</section>
|
</section>
|
||||||
<button type="button" id="logout-btn">sign out</button>
|
<button type="button" id="logout-btn">sign out</button>
|
||||||
<button type="button" id="logout-all-btn" class="link">sign out on all devices</button>
|
<button type="button" id="logout-all-btn" class="link">sign out on all devices</button>
|
||||||
|
<button type="button" id="help-open" class="link">gestures and shortcuts</button>
|
||||||
<a href="/account/export" download>download my data</a>
|
<a href="/account/export" download>download my data</a>
|
||||||
<button type="button" id="delete-open" class="link danger">delete account…</button>
|
<button type="button" id="delete-open" class="link danger">delete account…</button>
|
||||||
<form id="delete-form" hidden>
|
<form id="delete-form" hidden>
|
||||||
|
|||||||
+32
-15
@@ -439,9 +439,20 @@ volumeSlider.addEventListener("wheel", (e) => {
|
|||||||
setVolume(player.volume - Math.sign(e.deltaY) * 0.05);
|
setVolume(player.volume - Math.sign(e.deltaY) * 0.05);
|
||||||
}, { passive: false });
|
}, { passive: false });
|
||||||
|
|
||||||
const showHelp = () => { helpEl.hidden = false; };
|
let helpOpener: HTMLElement | null = null;
|
||||||
const hideHelp = () => { helpEl.hidden = true; };
|
const showHelp = () => {
|
||||||
|
helpOpener = document.activeElement as HTMLElement | null;
|
||||||
|
helpEl.hidden = false;
|
||||||
|
$("help-close").focus();
|
||||||
|
};
|
||||||
|
const hideHelp = () => {
|
||||||
|
if (helpEl.hidden) return;
|
||||||
|
helpEl.hidden = true;
|
||||||
|
helpOpener?.focus();
|
||||||
|
};
|
||||||
$("help-btn").addEventListener("click", showHelp);
|
$("help-btn").addEventListener("click", showHelp);
|
||||||
|
// The bar has no room for "?" on phones; the account panel has a way in.
|
||||||
|
$("help-open").addEventListener("click", () => { closePanels(); showHelp(); });
|
||||||
$("help-close").addEventListener("click", hideHelp);
|
$("help-close").addEventListener("click", hideHelp);
|
||||||
helpEl.addEventListener("click", (e) => { if (e.target === helpEl) hideHelp(); });
|
helpEl.addEventListener("click", (e) => { if (e.target === helpEl) hideHelp(); });
|
||||||
|
|
||||||
@@ -475,7 +486,13 @@ function activateAgain(card: Card): void {
|
|||||||
likesBtn.addEventListener("click", async () => {
|
likesBtn.addEventListener("click", async () => {
|
||||||
if (!togglePanel(likesPanel)) return;
|
if (!togglePanel(likesPanel)) return;
|
||||||
const list = $("likes-list");
|
const list = $("likes-list");
|
||||||
const { tracks } = await api.likes();
|
let tracks: Track[];
|
||||||
|
try {
|
||||||
|
({ tracks } = await api.likes());
|
||||||
|
} catch (err) {
|
||||||
|
list.textContent = `couldn't load your likes: ${(err as Error).message}`;
|
||||||
|
return;
|
||||||
|
}
|
||||||
list.replaceChildren(...tracks.map((t) => {
|
list.replaceChildren(...tracks.map((t) => {
|
||||||
const li = document.createElement("li");
|
const li = document.createElement("li");
|
||||||
const btn = document.createElement("button");
|
const btn = document.createElement("button");
|
||||||
@@ -646,8 +663,8 @@ document.addEventListener("keydown", (e) => {
|
|||||||
station.close();
|
station.close();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (station.isOpen) return;
|
if (k === "?") { helpEl.hidden ? showHelp() : hideHelp(); return; }
|
||||||
if (!loginEl.hidden) return;
|
if (station.isOpen || !helpEl.hidden || !loginEl.hidden) return;
|
||||||
if (!started) {
|
if (!started) {
|
||||||
if (k !== "Tab" && k !== "Shift") { e.preventDefault(); start(); }
|
if (k !== "Tab" && k !== "Shift") { e.preventDefault(); start(); }
|
||||||
return;
|
return;
|
||||||
@@ -669,7 +686,6 @@ document.addEventListener("keydown", (e) => {
|
|||||||
else if (k === "+" || k === "=") { setVolume(player.volume + 0.1); toast(`volume ${Math.round(player.volume * 100)}%`); }
|
else if (k === "+" || k === "=") { setVolume(player.volume + 0.1); toast(`volume ${Math.round(player.volume * 100)}%`); }
|
||||||
else if (k === "-") { setVolume(player.volume - 0.1); toast(`volume ${Math.round(player.volume * 100)}%`); }
|
else if (k === "-") { setVolume(player.volume - 0.1); toast(`volume ${Math.round(player.volume * 100)}%`); }
|
||||||
else if (k === "z") setZen(!document.body.classList.contains("zen"));
|
else if (k === "z") setZen(!document.body.classList.contains("zen"));
|
||||||
else if (k === "?") { helpEl.hidden ? showHelp() : hideHelp(); }
|
|
||||||
});
|
});
|
||||||
|
|
||||||
/// Zen: nothing but the drop and the music.
|
/// Zen: nothing but the drop and the music.
|
||||||
@@ -811,15 +827,16 @@ disconnectBtn.addEventListener("click", async () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
$("logout-btn").addEventListener("click", async () => {
|
for (const [id, url] of [["logout-btn", "/auth/logout"], ["logout-all-btn", "/account/logout-all"]]) {
|
||||||
await api.postJSON("/auth/logout");
|
$(id).addEventListener("click", async () => {
|
||||||
location.reload();
|
try {
|
||||||
});
|
await api.postJSON(url);
|
||||||
|
location.reload();
|
||||||
$("logout-all-btn").addEventListener("click", async () => {
|
} catch (err) {
|
||||||
await api.postJSON("/account/logout-all");
|
toast((err as Error).message, 4000);
|
||||||
location.reload();
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
$("delete-open").addEventListener("click", () => {
|
$("delete-open").addEventListener("click", () => {
|
||||||
deleteForm.hidden = false;
|
deleteForm.hidden = false;
|
||||||
|
|||||||
@@ -105,6 +105,8 @@ export class Pager {
|
|||||||
el.style.transform = `translate3d(0, ${(p * h).toFixed(2)}px, 0)`;
|
el.style.transform = `translate3d(0, ${(p * h).toFixed(2)}px, 0)`;
|
||||||
// 0 when resting in place, 1 one full page away; cards use it for depth effects.
|
// 0 when resting in place, 1 one full page away; cards use it for depth effects.
|
||||||
el.style.setProperty("--away", Math.min(1, Math.abs(p)).toFixed(3));
|
el.style.setProperty("--away", Math.min(1, Math.abs(p)).toFixed(3));
|
||||||
|
// Only the current card's buttons are reachable with the keyboard.
|
||||||
|
el.inert = i !== this.index;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+20
-13
@@ -9,6 +9,8 @@
|
|||||||
--accent: #ff4d6d;
|
--accent: #ff4d6d;
|
||||||
--panel: rgba(18, 18, 20, 0.94);
|
--panel: rgba(18, 18, 20, 0.94);
|
||||||
--line: #ffffff1f;
|
--line: #ffffff1f;
|
||||||
|
--line-strong: #333;
|
||||||
|
--scrim: #000b;
|
||||||
--glass: rgba(255, 255, 255, 0.1);
|
--glass: rgba(255, 255, 255, 0.1);
|
||||||
--glass-strong: rgba(255, 255, 255, 0.2);
|
--glass-strong: rgba(255, 255, 255, 0.2);
|
||||||
--radius-s: 8px;
|
--radius-s: 8px;
|
||||||
@@ -50,6 +52,9 @@ button {
|
|||||||
|
|
||||||
[hidden] { display: none !important; }
|
[hidden] { display: none !important; }
|
||||||
|
|
||||||
|
:focus { outline: none; }
|
||||||
|
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
|
||||||
|
|
||||||
.sprite { position: absolute; width: 0; height: 0; overflow: hidden; }
|
.sprite { position: absolute; width: 0; height: 0; overflow: hidden; }
|
||||||
|
|
||||||
/* Header and panels ------------------------------------------------------ */
|
/* Header and panels ------------------------------------------------------ */
|
||||||
@@ -114,10 +119,10 @@ button {
|
|||||||
top: calc(3rem + env(safe-area-inset-top));
|
top: calc(3rem + env(safe-area-inset-top));
|
||||||
right: 1rem;
|
right: 1rem;
|
||||||
background: var(--panel);
|
background: var(--panel);
|
||||||
border: 1px solid #333;
|
border: 1px solid var(--line-strong);
|
||||||
border-radius: 12px;
|
border-radius: var(--radius-m);
|
||||||
padding: 0.8rem 1rem;
|
padding: 0.8rem 1rem;
|
||||||
box-shadow: 0 16px 40px #000a;
|
box-shadow: var(--shadow);
|
||||||
}
|
}
|
||||||
|
|
||||||
#filters { display: flex; gap: 0.8rem; align-items: center; flex-wrap: wrap; max-width: 24rem; }
|
#filters { display: flex; gap: 0.8rem; align-items: center; flex-wrap: wrap; max-width: 24rem; }
|
||||||
@@ -301,7 +306,7 @@ button.danger:not(.link) { background: var(--accent); border: none; color: #fff;
|
|||||||
bottom: 0.7rem;
|
bottom: 0.7rem;
|
||||||
z-index: 30;
|
z-index: 30;
|
||||||
border-radius: var(--radius-s);
|
border-radius: var(--radius-s);
|
||||||
background: #000b;
|
background: var(--scrim);
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -492,7 +497,9 @@ button.danger:not(.link) { background: var(--accent); border: none; color: #fff;
|
|||||||
opacity: 0;
|
opacity: 0;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
transition: opacity 0.2s, transform 0.2s;
|
transition: opacity 0.2s, transform 0.2s;
|
||||||
white-space: nowrap;
|
max-width: calc(100vw - 2rem);
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
#toast.show { opacity: 1; transform: translate(-50%, 0); }
|
#toast.show { opacity: 1; transform: translate(-50%, 0); }
|
||||||
|
|
||||||
@@ -519,7 +526,7 @@ button.danger:not(.link) { background: var(--accent); border: none; color: #fff;
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
background: #000b;
|
background: var(--scrim);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
#start button { font-size: 1.3rem; padding: 0.7em 1.6em; background: var(--accent); border: none; color: #fff; }
|
#start button { font-size: 1.3rem; padding: 0.7em 1.6em; background: var(--accent); border: none; color: #fff; }
|
||||||
@@ -532,7 +539,7 @@ button.danger:not(.link) { background: var(--accent); border: none; color: #fff;
|
|||||||
display: grid;
|
display: grid;
|
||||||
place-items: center;
|
place-items: center;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
background: #000b;
|
background: var(--scrim);
|
||||||
animation: fade 0.2s ease;
|
animation: fade 0.2s ease;
|
||||||
}
|
}
|
||||||
.station-box {
|
.station-box {
|
||||||
@@ -541,7 +548,7 @@ button.danger:not(.link) { background: var(--accent); border: none; color: #fff;
|
|||||||
max-height: calc(100dvh - 2rem);
|
max-height: calc(100dvh - 2rem);
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
background: var(--panel);
|
background: var(--panel);
|
||||||
border: 1px solid #333;
|
border: 1px solid var(--line-strong);
|
||||||
border-radius: var(--radius-l);
|
border-radius: var(--radius-l);
|
||||||
padding: 1.4rem;
|
padding: 1.4rem;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -626,7 +633,7 @@ button.primary svg { width: 1em; height: 1em; }
|
|||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
border-radius: var(--radius-m);
|
border-radius: var(--radius-m);
|
||||||
background: var(--panel);
|
background: var(--panel);
|
||||||
border: 1px solid #333;
|
border: 1px solid var(--line-strong);
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
@@ -643,15 +650,15 @@ button.primary svg { width: 1em; height: 1em; }
|
|||||||
display: grid;
|
display: grid;
|
||||||
place-items: center;
|
place-items: center;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
background: #000b;
|
background: var(--scrim);
|
||||||
animation: fade 0.2s ease;
|
animation: fade 0.2s ease;
|
||||||
}
|
}
|
||||||
@keyframes fade { from { opacity: 0; } }
|
@keyframes fade { from { opacity: 0; } }
|
||||||
.help-box {
|
.help-box {
|
||||||
width: min(22rem, 100%);
|
width: min(22rem, 100%);
|
||||||
background: var(--panel);
|
background: var(--panel);
|
||||||
border: 1px solid #333;
|
border: 1px solid var(--line-strong);
|
||||||
border-radius: 16px;
|
border-radius: var(--radius-l);
|
||||||
padding: 1.2rem 1.4rem;
|
padding: 1.2rem 1.4rem;
|
||||||
}
|
}
|
||||||
.help-box h2 { margin: 0 0 0.8rem; font-size: 1.1rem; }
|
.help-box h2 { margin: 0 0 0.8rem; font-size: 1.1rem; }
|
||||||
@@ -695,7 +702,7 @@ button.primary svg { width: 1em; height: 1em; }
|
|||||||
.login-box input {
|
.login-box input {
|
||||||
padding: 0.7em 0.9em;
|
padding: 0.7em 0.9em;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
border: 1px solid #333;
|
border: 1px solid var(--line-strong);
|
||||||
background: #111;
|
background: #111;
|
||||||
}
|
}
|
||||||
#code { font-family: ui-monospace, monospace; font-size: 1.4rem; letter-spacing: 0.5em; text-align: center; }
|
#code { font-family: ui-monospace, monospace; font-size: 1.4rem; letter-spacing: 0.5em; text-align: center; }
|
||||||
|
|||||||
Reference in new issue
Block a user