This commit is contained in:
2026-08-05 22:46:02 -05:00
parent 47871d4bc5
commit 4f10991abc
5 changed files with 357 additions and 134 deletions

View File

@@ -1,12 +1,12 @@
body { body {
margin: 0; margin: 0;
font-family: Arial, sans-serif; font-family: Inter, "Segoe UI", Arial, sans-serif;
background: #0e1117; background: #070909;
color: #e6edf7; color: #e7f0e9;
} }
.history-page { .history-page {
max-width: 960px; max-width: 1000px;
margin: 0 auto; margin: 0 auto;
padding: 24px; padding: 24px;
} }
@@ -16,7 +16,7 @@ h1 {
} }
p { p {
color: #aeb7cc; color: #8ea292;
} }
#historyList { #historyList {
@@ -25,43 +25,98 @@ p {
padding: 0; padding: 0;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 12px; gap: 14px;
} }
.history-item { .history-item {
border: 1px solid #2a2f40; border: 1px solid #1d2620;
border-radius: 8px; border-radius: 12px;
padding: 12px; padding: 10px;
background: #151a26; background: #0d1210;
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.01);
}
.history-head {
display: flex;
gap: 10px;
align-items: center;
}
.history-info {
min-width: 0;
flex: 1;
} }
.history-cover-image { .history-cover-image {
width: 48px; width: 44px;
height: 48px; height: 44px;
border-radius: 6px; border-radius: 7px;
object-fit: cover; object-fit: cover;
display: block; border: 1px solid #243027;
margin-bottom: 10px; flex-shrink: 0;
} }
.history-url { .history-title {
margin: 0 0 8px; margin: 0;
word-break: break-all; color: #eff8f1;
color: #d2d9ea;
font-size: 13px; font-size: 13px;
font-weight: 600;
word-break: break-word;
line-height: 1.3;
} }
.history-meta { .history-subtitle {
margin: 0 0 10px; margin: 3px 0 0;
color: #98a5c0; color: #94a89a;
font-size: 12px; font-size: 11px;
} }
button { .history-badges {
border: 1px solid #2f66ff; margin-top: 8px;
background: #2f66ff; display: flex;
color: #fff; gap: 6px;
border-radius: 6px; }
.badge {
border: 1px solid #2d4f35;
background: #142219;
color: #bde6c6;
border-radius: 999px;
padding: 2px 8px;
font-size: 10px;
letter-spacing: 0.01em;
}
.history-tags {
margin: 4px 0 0;
color: #b3c9b8;
font-size: 11px;
line-height: 1.3;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.download-icon-btn {
width: 44px;
height: 44px;
border: 1px solid #2f9e5a;
background: #1b7f45;
color: #f1fff4;
border-radius: 10px;
display: inline-flex;
align-items: center;
justify-content: center;
cursor: pointer; cursor: pointer;
padding: 6px 10px; flex-shrink: 0;
transition: background 120ms ease, transform 120ms ease;
}
.download-icon-btn:hover {
background: #249452;
transform: translateY(-1px);
}
.download-icon-btn:active {
transform: translateY(0);
} }

View File

@@ -12,7 +12,6 @@
<main class="history-page"> <main class="history-page">
<header> <header>
<h1>S3 Audio Capture History</h1> <h1>S3 Audio Capture History</h1>
<p>All captured Splice S3 URLs.</p>
</header> </header>
<p id="status">Loading history...</p> <p id="status">Loading history...</p>
<ul id="historyList"></ul> <ul id="historyList"></ul>

View File

@@ -3,51 +3,101 @@
const statusEl = document.getElementById("status"); const statusEl = document.getElementById("status");
const listEl = document.getElementById("historyList"); const listEl = document.getElementById("historyList");
function formatDate(ms) {
try {
return new Date(ms).toLocaleString();
} catch (error) {
return "unknown time";
}
}
function formatMeta(entry) { function formatMeta(entry) {
const packName = entry && entry.packName ? entry.packName : "n/a"; const packName = entry && entry.packName ? entry.packName : "n/a";
const key = entry && entry.key ? entry.key : "n/a"; const key = entry && entry.key ? entry.key : "n/a";
const bpm = entry && entry.bpm ? entry.bpm : "n/a"; const bpm = entry && entry.bpm ? entry.bpm : "n/a";
const tags = entry && Array.isArray(entry.tags) && entry.tags.length ? entry.tags.join(", ") : "n/a"; return { packName, key, bpm };
return `Pack: ${packName} | Key: ${key} | BPM: ${bpm} | Tags: ${tags}`; }
function formatTags(entry) {
if (!entry || !Array.isArray(entry.tags) || !entry.tags.length) {
return "No tags";
}
return entry.tags.join(" • ");
}
function createDownloadIcon() {
const ns = "http://www.w3.org/2000/svg";
const svg = document.createElementNS(ns, "svg");
svg.setAttribute("viewBox", "0 0 24 24");
svg.setAttribute("width", "22");
svg.setAttribute("height", "22");
svg.setAttribute("aria-hidden", "true");
const arrow = document.createElementNS(ns, "path");
arrow.setAttribute("d", "M12 4v10m0 0l4-4m-4 4l-4-4");
arrow.setAttribute("fill", "none");
arrow.setAttribute("stroke", "currentColor");
arrow.setAttribute("stroke-width", "2");
arrow.setAttribute("stroke-linecap", "round");
arrow.setAttribute("stroke-linejoin", "round");
const tray = document.createElementNS(ns, "path");
tray.setAttribute("d", "M4 18v1a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-1");
tray.setAttribute("fill", "none");
tray.setAttribute("stroke", "currentColor");
tray.setAttribute("stroke-width", "2");
tray.setAttribute("stroke-linecap", "round");
tray.setAttribute("stroke-linejoin", "round");
svg.appendChild(arrow);
svg.appendChild(tray);
return svg;
} }
function buildItem(entry) { function buildItem(entry) {
const li = document.createElement("li"); const li = document.createElement("li");
li.className = "history-item"; li.className = "history-item";
const meta = formatMeta(entry);
if (entry.coverImageUrl) { const head = document.createElement("div");
const cover = document.createElement("img"); head.className = "history-head";
cover.className = "history-cover-image";
cover.src = entry.coverImageUrl;
cover.alt = "Cover art";
cover.loading = "lazy";
li.appendChild(cover);
}
const url = document.createElement("p"); const cover = document.createElement("img");
url.className = "history-url"; cover.className = "history-cover-image";
url.textContent = entry.fileName || entry.sampleId || "(unknown filename)"; cover.src = entry.coverImageUrl || "icons/icon-48.png";
cover.alt = "Cover art";
cover.loading = "lazy";
const meta = document.createElement("p"); const info = document.createElement("div");
meta.className = "history-meta"; info.className = "history-info";
const title = document.createElement("p");
title.className = "history-title";
title.textContent = entry.fileName || entry.sampleId || "(unknown filename)";
const subtitle = document.createElement("p");
subtitle.className = "history-subtitle";
const mode = entry.mode === "full" || entry.fullUrl ? "full candidate" : "preview fallback"; const mode = entry.mode === "full" || entry.fullUrl ? "full candidate" : "preview fallback";
meta.textContent = `Captured: ${formatDate(entry.capturedAt)} | Mode: ${mode} | Source: ${entry.source || "unknown"} | ID: ${entry.sampleId || "n/a"}`; subtitle.textContent = `${meta.packName}${mode}`;
const details = document.createElement("p"); const badges = document.createElement("div");
details.className = "history-meta"; badges.className = "history-badges";
details.textContent = formatMeta(entry); const keyBadge = document.createElement("span");
keyBadge.className = "badge";
keyBadge.textContent = `Key ${meta.key}`;
const bpmBadge = document.createElement("span");
bpmBadge.className = "badge";
bpmBadge.textContent = `BPM ${meta.bpm}`;
badges.appendChild(keyBadge);
badges.appendChild(bpmBadge);
const tags = document.createElement("p");
tags.className = "history-tags";
tags.textContent = formatTags(entry);
info.appendChild(title);
info.appendChild(subtitle);
info.appendChild(badges);
info.appendChild(tags);
const download = document.createElement("button"); const download = document.createElement("button");
download.type = "button"; download.type = "button";
download.textContent = "Download"; download.className = "download-icon-btn";
download.setAttribute("aria-label", "Download sample");
download.setAttribute("title", "Download sample");
download.appendChild(createDownloadIcon());
download.addEventListener("click", async () => { download.addEventListener("click", async () => {
const result = await browser.runtime.sendMessage({ const result = await browser.runtime.sendMessage({
type: "DOWNLOAD_ENTRY", type: "DOWNLOAD_ENTRY",
@@ -60,10 +110,10 @@ function buildItem(entry) {
statusEl.textContent = result.mode === "full" ? "Downloaded full file URL." : "Downloaded decoded preview fallback."; statusEl.textContent = result.mode === "full" ? "Downloaded full file URL." : "Downloaded decoded preview fallback.";
}); });
li.appendChild(url); head.appendChild(cover);
li.appendChild(meta); head.appendChild(info);
li.appendChild(details); head.appendChild(download);
li.appendChild(download); li.appendChild(head);
return li; return li;
} }

140
popup.css
View File

@@ -1,14 +1,14 @@
body { body {
margin: 0; margin: 0;
font-family: Arial, sans-serif; font-family: Inter, "Segoe UI", Arial, sans-serif;
background: #111319; background: #070909;
color: #e9ecf1; color: #e7f0e9;
} }
.popup { .popup {
width: 380px; width: 420px;
max-height: 520px; max-height: 560px;
padding: 12px; padding: 14px;
box-sizing: border-box; box-sizing: border-box;
} }
@@ -16,32 +16,37 @@ body {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
gap: 8px; gap: 10px;
margin-bottom: 10px;
} }
.popup-header h1 { .popup-header h1 {
margin: 0; margin: 0;
font-size: 15px; font-size: 15px;
font-weight: 600;
letter-spacing: 0.01em;
} }
button { #openHistoryBtn {
border: 1px solid #2f66ff; border: 1px solid #263329;
background: #2f66ff; background: #101513;
color: white; color: #b7cbbd;
border-radius: 6px; border-radius: 999px;
cursor: pointer; cursor: pointer;
padding: 6px 9px; padding: 6px 10px;
font-size: 12px; font-size: 12px;
transition: all 120ms ease;
} }
button.secondary { #openHistoryBtn:hover {
border-color: #445; border-color: #3c5342;
background: #1e2230; color: #e3f3e7;
background: #151d18;
} }
.status { .status {
margin: 12px 0; margin: 8px 0 12px;
color: #adb6cb; color: #829589;
font-size: 12px; font-size: 12px;
} }
@@ -51,33 +56,98 @@ button.secondary {
margin: 0; margin: 0;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 8px; gap: 10px;
} }
.url-item { .url-item {
border: 1px solid #2a2f40; border: 1px solid #1d2620;
border-radius: 8px; border-radius: 12px;
background: #171b26; background: #0d1210;
padding: 8px; padding: 10px;
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.01);
}
.item-head {
display: flex;
gap: 10px;
align-items: center;
}
.item-info {
min-width: 0;
flex: 1;
} }
.cover-image { .cover-image {
width: 36px; width: 44px;
height: 36px; height: 44px;
border-radius: 4px; border-radius: 7px;
object-fit: cover; object-fit: cover;
display: block; flex-shrink: 0;
margin-bottom: 8px; border: 1px solid #243027;
} }
.url-text { .item-title {
margin: 0 0 8px; margin: 0;
color: #eff8f1;
font-size: 13px;
font-weight: 600;
line-height: 1.3;
word-break: break-word;
}
.item-sub {
margin: 3px 0 0;
color: #94a89a;
font-size: 11px; font-size: 11px;
word-break: break-all;
color: #c6d0e5;
} }
.actions { .item-tags {
display: flex; margin: 4px 0 0;
gap: 8px; color: #b3c9b8;
font-size: 11px;
line-height: 1.3;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.item-badges {
margin-top: 8px;
display: flex;
gap: 6px;
}
.badge {
border: 1px solid #2d4f35;
background: #142219;
color: #bde6c6;
border-radius: 999px;
padding: 2px 8px;
font-size: 10px;
letter-spacing: 0.01em;
}
.download-icon-btn {
width: 44px;
height: 44px;
border: 1px solid #2f9e5a;
background: #1b7f45;
color: #f1fff4;
border-radius: 10px;
display: inline-flex;
align-items: center;
justify-content: center;
cursor: pointer;
flex-shrink: 0;
transition: background 120ms ease, transform 120ms ease;
}
.download-icon-btn:hover {
background: #249452;
transform: translateY(-1px);
}
.download-icon-btn:active {
transform: translateY(0);
} }

123
popup.js
View File

@@ -16,41 +16,101 @@ function formatMeta(entry) {
const packName = entry && entry.packName ? entry.packName : "n/a"; const packName = entry && entry.packName ? entry.packName : "n/a";
const key = entry && entry.key ? entry.key : "n/a"; const key = entry && entry.key ? entry.key : "n/a";
const bpm = entry && entry.bpm ? entry.bpm : "n/a"; const bpm = entry && entry.bpm ? entry.bpm : "n/a";
const tags = entry && Array.isArray(entry.tags) && entry.tags.length ? entry.tags.join(", ") : "n/a"; return { packName, key, bpm };
return `Pack: ${packName} | Key: ${key} | BPM: ${bpm} | Tags: ${tags}`; }
function formatTags(entry) {
if (!entry || !Array.isArray(entry.tags) || !entry.tags.length) {
return "No tags";
}
const maxTags = 5;
const tags = entry.tags.slice(0, maxTags);
const suffix = entry.tags.length > maxTags ? ` +${entry.tags.length - maxTags}` : "";
return tags.join(" • ") + suffix;
}
function createDownloadIcon() {
const ns = "http://www.w3.org/2000/svg";
const svg = document.createElementNS(ns, "svg");
svg.setAttribute("viewBox", "0 0 24 24");
svg.setAttribute("width", "22");
svg.setAttribute("height", "22");
svg.setAttribute("aria-hidden", "true");
const arrow = document.createElementNS(ns, "path");
arrow.setAttribute("d", "M12 4v10m0 0l4-4m-4 4l-4-4");
arrow.setAttribute("fill", "none");
arrow.setAttribute("stroke", "currentColor");
arrow.setAttribute("stroke-width", "2");
arrow.setAttribute("stroke-linecap", "round");
arrow.setAttribute("stroke-linejoin", "round");
const tray = document.createElementNS(ns, "path");
tray.setAttribute("d", "M4 18v1a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-1");
tray.setAttribute("fill", "none");
tray.setAttribute("stroke", "currentColor");
tray.setAttribute("stroke-width", "2");
tray.setAttribute("stroke-linecap", "round");
tray.setAttribute("stroke-linejoin", "round");
svg.appendChild(arrow);
svg.appendChild(tray);
return svg;
} }
function buildRow(entry) { function buildRow(entry) {
const li = document.createElement("li"); const li = document.createElement("li");
li.className = "url-item"; li.className = "url-item";
const meta = formatMeta(entry);
if (entry.coverImageUrl) { const head = document.createElement("div");
const cover = document.createElement("img"); head.className = "item-head";
cover.className = "cover-image";
cover.src = entry.coverImageUrl;
cover.alt = "Cover art";
cover.loading = "lazy";
li.appendChild(cover);
}
const p = document.createElement("p"); const cover = document.createElement("img");
p.className = "url-text"; cover.className = "cover-image";
p.textContent = entry.fileName || entry.sampleId || "(unknown filename)"; cover.src = entry.coverImageUrl || "icons/icon-48.png";
cover.alt = "Cover art";
cover.loading = "lazy";
const status = document.createElement("p"); const info = document.createElement("div");
status.className = "status"; info.className = "item-info";
status.textContent = `Source: ${entry.source || "unknown"} | Mode: ${entryLabel(entry)} | ID: ${entry.sampleId || "n/a"}`;
const meta = document.createElement("p"); const title = document.createElement("p");
meta.className = "status"; title.className = "item-title";
meta.textContent = formatMeta(entry); title.textContent = entry.fileName || entry.sampleId || "(unknown filename)";
const actions = document.createElement("div"); const sub = document.createElement("p");
actions.className = "actions"; sub.className = "item-sub";
sub.textContent = `${meta.packName}${entryLabel(entry)}`;
const tags = document.createElement("p");
tags.className = "item-tags";
tags.textContent = formatTags(entry);
const badges = document.createElement("div");
badges.className = "item-badges";
const keyBadge = document.createElement("span");
keyBadge.className = "badge";
keyBadge.textContent = `Key ${meta.key}`;
const bpmBadge = document.createElement("span");
bpmBadge.className = "badge";
bpmBadge.textContent = `BPM ${meta.bpm}`;
badges.appendChild(keyBadge);
badges.appendChild(bpmBadge);
info.appendChild(title);
info.appendChild(sub);
info.appendChild(tags);
info.appendChild(badges);
const download = document.createElement("button"); const download = document.createElement("button");
download.type = "button"; download.type = "button";
download.textContent = "Download"; download.className = "download-icon-btn";
download.setAttribute("aria-label", "Download sample");
download.setAttribute("title", "Download sample");
download.appendChild(createDownloadIcon());
download.addEventListener("click", async () => { download.addEventListener("click", async () => {
const result = await browser.runtime.sendMessage({ type: "DOWNLOAD_ENTRY", entry }); const result = await browser.runtime.sendMessage({ type: "DOWNLOAD_ENTRY", entry });
if (!result || !result.ok) { if (!result || !result.ok) {
@@ -60,21 +120,10 @@ function buildRow(entry) {
setStatus(result.mode === "full" ? "Downloaded full file URL." : "Downloaded decoded preview fallback."); setStatus(result.mode === "full" ? "Downloaded full file URL." : "Downloaded decoded preview fallback.");
}); });
const copy = document.createElement("button"); head.appendChild(cover);
copy.type = "button"; head.appendChild(info);
copy.className = "secondary"; head.appendChild(download);
copy.textContent = "Copy URL"; li.appendChild(head);
copy.addEventListener("click", async () => {
const url = entry.fullUrl || entry.previewUrl || "";
await navigator.clipboard.writeText(url);
});
actions.appendChild(download);
actions.appendChild(copy);
li.appendChild(p);
li.appendChild(status);
li.appendChild(meta);
li.appendChild(actions);
return li; return li;
} }