finish peeps tables
This commit is contained in:
+138
-14
@@ -14,6 +14,12 @@
|
|||||||
section: "",
|
section: "",
|
||||||
search: "",
|
search: "",
|
||||||
},
|
},
|
||||||
|
sort: {
|
||||||
|
key: "source",
|
||||||
|
dir: "asc",
|
||||||
|
},
|
||||||
|
page: 1,
|
||||||
|
pageSize: 100,
|
||||||
};
|
};
|
||||||
|
|
||||||
const RARE_MODIFIER_VERSIONS = new Set(["v2", "bb"]);
|
const RARE_MODIFIER_VERSIONS = new Set(["v2", "bb"]);
|
||||||
@@ -184,6 +190,89 @@
|
|||||||
fillSelect(qs("#drops-section"), uniqueSorted(rows, "section_id"), "All Section IDs");
|
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() {
|
function visibleRows() {
|
||||||
const search = state.filters.search.trim().toLowerCase();
|
const search = state.filters.search.trim().toLowerCase();
|
||||||
|
|
||||||
@@ -216,9 +305,14 @@
|
|||||||
const box = qs("#drops-placeholder");
|
const box = qs("#drops-placeholder");
|
||||||
if (!box) return;
|
if (!box) return;
|
||||||
|
|
||||||
const rows = visibleRows();
|
const rows = sortedRows(visibleRows());
|
||||||
const tableLabel = state.table?.label || "Peeps";
|
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 body = shown.map((row) => {
|
||||||
const item = row.item || row.item_code || "—";
|
const item = row.item || row.item_code || "—";
|
||||||
@@ -238,8 +332,8 @@
|
|||||||
`;
|
`;
|
||||||
}).join("");
|
}).join("");
|
||||||
|
|
||||||
const truncation = rows.length > shown.length
|
const rangeText = rows.length
|
||||||
? ` Showing first ${shown.length.toLocaleString()}.`
|
? ` Showing ${Number(start + 1).toLocaleString()}-${Number(end).toLocaleString()}.`
|
||||||
: "";
|
: "";
|
||||||
const modifier = currentRareModifier();
|
const modifier = currentRareModifier();
|
||||||
const modifierNote = rareModifierEnabled() && modifier.pct > 0
|
const modifierNote = rareModifierEnabled() && modifier.pct > 0
|
||||||
@@ -250,7 +344,7 @@
|
|||||||
<div class="drops-summary">
|
<div class="drops-summary">
|
||||||
<div>
|
<div>
|
||||||
<strong>Peeps ${esc(tableLabel)} drop table</strong>
|
<strong>Peeps ${esc(tableLabel)} drop table</strong>
|
||||||
<span>${rows.length.toLocaleString()} matching rows.${truncation}</span>
|
<span>${rows.length.toLocaleString()} matching rows.${rangeText}</span>
|
||||||
${modifierNote}
|
${modifierNote}
|
||||||
</div>
|
</div>
|
||||||
<span>${state.rows.length.toLocaleString()} total rows</span>
|
<span>${state.rows.length.toLocaleString()} total rows</span>
|
||||||
@@ -259,19 +353,17 @@
|
|||||||
<table class="drops-table">
|
<table class="drops-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Mode</th>
|
${SORT_COLUMNS.map(([key, label]) => sortHeader(key, label)).join("")}
|
||||||
<th>Episode</th>
|
|
||||||
<th>Difficulty</th>
|
|
||||||
<th>Section ID</th>
|
|
||||||
<th>Source</th>
|
|
||||||
<th>Item</th>
|
|
||||||
<th>Item Code</th>
|
|
||||||
<th>Rate</th>
|
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>${body || `<tr><td colspan="8">No drops match these filters.</td></tr>`}</tbody>
|
<tbody>${body || `<tr><td colspan="8">No drops match these filters.</td></tr>`}</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</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.difficulty = "";
|
||||||
state.filters.section = "";
|
state.filters.section = "";
|
||||||
state.filters.search = "";
|
state.filters.search = "";
|
||||||
|
state.page = 1;
|
||||||
|
|
||||||
if (qs("#drops-search")) qs("#drops-search").value = "";
|
if (qs("#drops-search")) qs("#drops-search").value = "";
|
||||||
|
|
||||||
@@ -335,30 +428,61 @@
|
|||||||
document.addEventListener("DOMContentLoaded", () => {
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
qs("#drops-mode")?.addEventListener("change", updateMode);
|
qs("#drops-mode")?.addEventListener("change", updateMode);
|
||||||
qs("#drops-version")?.addEventListener("change", loadPeeps);
|
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) => {
|
qs("#drops-rare-mode")?.addEventListener("change", (event) => {
|
||||||
state.filters.mode = event.target.value;
|
state.filters.mode = event.target.value;
|
||||||
|
state.page = 1;
|
||||||
renderTable();
|
renderTable();
|
||||||
});
|
});
|
||||||
|
|
||||||
qs("#drops-episode")?.addEventListener("change", (event) => {
|
qs("#drops-episode")?.addEventListener("change", (event) => {
|
||||||
state.filters.episode = event.target.value;
|
state.filters.episode = event.target.value;
|
||||||
|
state.page = 1;
|
||||||
renderTable();
|
renderTable();
|
||||||
});
|
});
|
||||||
|
|
||||||
qs("#drops-difficulty")?.addEventListener("change", (event) => {
|
qs("#drops-difficulty")?.addEventListener("change", (event) => {
|
||||||
state.filters.difficulty = event.target.value;
|
state.filters.difficulty = event.target.value;
|
||||||
|
state.page = 1;
|
||||||
renderTable();
|
renderTable();
|
||||||
});
|
});
|
||||||
|
|
||||||
qs("#drops-section")?.addEventListener("change", (event) => {
|
qs("#drops-section")?.addEventListener("change", (event) => {
|
||||||
state.filters.section = event.target.value;
|
state.filters.section = event.target.value;
|
||||||
|
state.page = 1;
|
||||||
renderTable();
|
renderTable();
|
||||||
});
|
});
|
||||||
|
|
||||||
qs("#drops-search")?.addEventListener("input", (event) => {
|
qs("#drops-search")?.addEventListener("input", (event) => {
|
||||||
state.filters.search = event.target.value;
|
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();
|
renderTable();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -126,6 +126,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</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>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -2839,3 +2839,37 @@ button.inline-link,
|
|||||||
white-space: nowrap;
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user