finish peeps tables

This commit is contained in:
Your Name
2026-06-13 17:39:44 -04:00
parent 32df06f583
commit 2d48838fe6
3 changed files with 173 additions and 15 deletions
+138 -14
View File
@@ -14,6 +14,12 @@
section: "",
search: "",
},
sort: {
key: "source",
dir: "asc",
},
page: 1,
pageSize: 100,
};
const RARE_MODIFIER_VERSIONS = new Set(["v2", "bb"]);
@@ -184,6 +190,89 @@
fillSelect(qs("#drops-section"), uniqueSorted(rows, "section_id"), "All Section IDs");
}
const SORT_COLUMNS = [
["mode", "Mode"],
["episode", "Episode"],
["difficulty", "Difficulty"],
["section_id", "SECID"],
["source", "Source"],
["item", "Item"],
["item_code", "Code"],
["rate", "Rate"],
];
const DIFFICULTY_SORT_ORDER = {
Normal: 0,
Hard: 1,
VeryHard: 2,
Ultimate: 3,
};
function rateSortValue(rate) {
const text = String(rate || "").replaceAll(",", "");
const match = text.match(/^(\d+(?:\.\d+)?)\/(\d+(?:\.\d+)?)$/);
if (!match) return Number.NEGATIVE_INFINITY;
const num = Number(match[1]);
const den = Number(match[2]);
if (!Number.isFinite(num) || !Number.isFinite(den) || den <= 0) {
return Number.NEGATIVE_INFINITY;
}
// Sort by actual probability, same as percentage conversion.
// 5/8 => 0.625, 1/8192 => 0.000122...
return num / den;
}
function sortValue(row, key) {
if (key === "difficulty") {
return DIFFICULTY_SORT_ORDER[row.difficulty] ?? 999;
}
if (key === "rate") {
return rateSortValue(adjustedRate(row.rate));
}
if (key === "item") {
return row.item || row.item_code || "";
}
return row[key] || "";
}
function sortedRows(rows) {
const { key, dir } = state.sort;
const factor = dir === "desc" ? -1 : 1;
return [...rows].sort((a, b) => {
const av = sortValue(a, key);
const bv = sortValue(b, key);
if (typeof av === "number" && typeof bv === "number") {
return (av - bv) * factor;
}
return String(av).localeCompare(String(bv), undefined, {
numeric: true,
sensitivity: "base",
}) * factor;
});
}
function sortHeader(key, label) {
const active = state.sort.key === key;
const arrow = active ? (state.sort.dir === "asc" ? "▲" : "▼") : "";
const ariaSort = active ? (state.sort.dir === "asc" ? "ascending" : "descending") : "none";
return `<th aria-sort="${ariaSort}">
<button class="drops-sort-button ${active ? "is-active" : ""}" type="button" data-drops-sort="${esc(key)}">
<span>${esc(label)}</span>
<span class="drops-sort-arrow" aria-hidden="true">${arrow}</span>
</button>
</th>`;
}
function visibleRows() {
const search = state.filters.search.trim().toLowerCase();
@@ -216,9 +305,14 @@
const box = qs("#drops-placeholder");
if (!box) return;
const rows = visibleRows();
const rows = sortedRows(visibleRows());
const tableLabel = state.table?.label || "Peeps";
const shown = rows.slice(0, 1000);
const totalPages = Math.max(1, Math.ceil(rows.length / state.pageSize));
state.page = Math.min(Math.max(1, state.page), totalPages);
const start = (state.page - 1) * state.pageSize;
const shown = rows.slice(start, start + state.pageSize);
const end = start + shown.length;
const body = shown.map((row) => {
const item = row.item || row.item_code || "—";
@@ -238,8 +332,8 @@
`;
}).join("");
const truncation = rows.length > shown.length
? ` Showing first ${shown.length.toLocaleString()}.`
const rangeText = rows.length
? ` Showing ${Number(start + 1).toLocaleString()}-${Number(end).toLocaleString()}.`
: "";
const modifier = currentRareModifier();
const modifierNote = rareModifierEnabled() && modifier.pct > 0
@@ -250,7 +344,7 @@
<div class="drops-summary">
<div>
<strong>Peeps ${esc(tableLabel)} drop table</strong>
<span>${rows.length.toLocaleString()} matching rows.${truncation}</span>
<span>${rows.length.toLocaleString()} matching rows.${rangeText}</span>
${modifierNote}
</div>
<span>${state.rows.length.toLocaleString()} total rows</span>
@@ -259,19 +353,17 @@
<table class="drops-table">
<thead>
<tr>
<th>Mode</th>
<th>Episode</th>
<th>Difficulty</th>
<th>Section ID</th>
<th>Source</th>
<th>Item</th>
<th>Item Code</th>
<th>Rate</th>
${SORT_COLUMNS.map(([key, label]) => sortHeader(key, label)).join("")}
</tr>
</thead>
<tbody>${body || `<tr><td colspan="8">No drops match these filters.</td></tr>`}</tbody>
</table>
</div>
<div class="leaderboard-pager drops-pager">
<button type="button" data-drops-page="prev" ${state.page <= 1 ? "disabled" : ""}>Previous</button>
<span>Page ${state.page} of ${totalPages}</span>
<button type="button" data-drops-page="next" ${state.page >= totalPages ? "disabled" : ""}>Next</button>
</div>
`;
}
@@ -299,6 +391,7 @@
state.filters.difficulty = "";
state.filters.section = "";
state.filters.search = "";
state.page = 1;
if (qs("#drops-search")) qs("#drops-search").value = "";
@@ -335,30 +428,61 @@
document.addEventListener("DOMContentLoaded", () => {
qs("#drops-mode")?.addEventListener("change", updateMode);
qs("#drops-version")?.addEventListener("change", loadPeeps);
qs("#drops-rare-modifier")?.addEventListener("change", renderTable);
qs("#drops-rare-modifier")?.addEventListener("change", () => {
state.page = 1;
state.page = 1;
renderTable();
});
qs("#drops-rare-mode")?.addEventListener("change", (event) => {
state.filters.mode = event.target.value;
state.page = 1;
renderTable();
});
qs("#drops-episode")?.addEventListener("change", (event) => {
state.filters.episode = event.target.value;
state.page = 1;
renderTable();
});
qs("#drops-difficulty")?.addEventListener("change", (event) => {
state.filters.difficulty = event.target.value;
state.page = 1;
renderTable();
});
qs("#drops-section")?.addEventListener("change", (event) => {
state.filters.section = event.target.value;
state.page = 1;
renderTable();
});
qs("#drops-search")?.addEventListener("input", (event) => {
state.filters.search = event.target.value;
state.page = 1;
renderTable();
});
qs("#drops-placeholder")?.addEventListener("click", (event) => {
const pageButton = event.target.closest("[data-drops-page]");
if (pageButton) {
state.page += pageButton.dataset.dropsPage === "next" ? 1 : -1;
renderTable();
return;
}
const button = event.target.closest("[data-drops-sort]");
if (!button) return;
const key = button.dataset.dropsSort;
if (state.sort.key === key) {
state.sort.dir = state.sort.dir === "asc" ? "desc" : "asc";
} else {
state.sort.key = key;
state.sort.dir = "asc";
}
renderTable();
});
+1 -1
View File
@@ -126,6 +126,6 @@
</div>
</footer>
</div>
<script src="drop-tables.js?v=drops-peeps-table-viewer-20260613-6" defer></script>
<script src="drop-tables.js?v=drops-peeps-table-viewer-20260613-11" defer></script>
</body>
</html>
+34
View File
@@ -2839,3 +2839,37 @@ button.inline-link,
white-space: nowrap;
}
.drops-sort-button {
display: inline-flex;
align-items: center;
justify-content: space-between;
gap: 0.4rem;
width: 100%;
padding: 0;
border: 0;
background: transparent;
color: inherit;
font: inherit;
letter-spacing: inherit;
text-align: left;
text-transform: inherit;
cursor: pointer;
}
.drops-sort-button:hover,
.drops-sort-button.is-active {
color: rgba(255, 255, 255, 0.96);
}
.drops-sort-arrow {
min-width: 0.75rem;
color: rgba(255, 255, 255, 0.62);
font-size: 0.66rem;
}
.drops-pager {
margin: 0.9rem 1rem 1rem;
}