From 69fcbbf38cef8820900335a3b191a8895555c16c Mon Sep 17 00:00:00 2001 From: Lawrence Tong Date: Sat, 8 Aug 2026 22:41:43 -0400 Subject: [PATCH 1/2] feat: add ability to sort cols on set list page --- client/db/set-list/index.html | 12 ++--- client/db/set-list/index.js | 89 +++++++++++++++++++++++++++-------- 2 files changed, 76 insertions(+), 25 deletions(-) diff --git a/client/db/set-list/index.html b/client/db/set-list/index.html index b7009f877..228568889 100644 --- a/client/db/set-list/index.html +++ b/client/db/set-list/index.html @@ -18,12 +18,12 @@ - - - - - - + + + + + + diff --git a/client/db/set-list/index.js b/client/db/set-list/index.js index 1adb67261..f33cd1e5c 100644 --- a/client/db/set-list/index.js +++ b/client/db/set-list/index.js @@ -1,21 +1,67 @@ +let sets = []; +let sortKey = null; +let sortAscending = true; + +function renderTable () { + const table = document.getElementById('set-metadata-list'); + table.textContent = ''; + sets.forEach(({ _id, setName, difficulty, standard, packetsCount, tossupsCount, bonusesCount }) => { + const row = table.insertRow(-1); + const a = document.createElement('a'); + a.href = `../set/?_id=${_id}`; + a.textContent = setName; + row.insertCell(-1).appendChild(a); + row.insertCell(-1).textContent = difficulty; + row.insertCell(-1).textContent = standard; + row.insertCell(-1).textContent = packetsCount ?? '-'; + row.insertCell(-1).textContent = tossupsCount ?? '-'; + row.insertCell(-1).textContent = bonusesCount ?? '-'; + }); +} + +function updateSortIcons () { + document.querySelectorAll('th[data-sort-key]').forEach(th => { + const icon = th.querySelector('i.bi'); + if (th.dataset.sortKey !== sortKey) { + icon.className = 'bi bi-arrow-down-up text-muted'; + } else { + icon.className = sortAscending ? 'bi bi-caret-up-fill' : 'bi bi-caret-down-fill'; + } + }); +} + +function compareRows (a, b, key) { + const valueA = a[key]; + const valueB = b[key]; + if (valueA == null && valueB == null) return 0; + if (valueA == null) return -1; + if (valueB == null) return 1; + if (typeof valueA === 'string') return valueA.localeCompare(valueB); + if (valueA < valueB) return -1; + if (valueA > valueB) return 1; + return 0; +} + +function sortByKey (key) { + sortAscending = sortKey === key ? !sortAscending : true; + sortKey = key; + sets.sort((a, b) => compareRows(a, b, key) * (sortAscending ? 1 : -1)); + renderTable(); + updateSortIcons(); +} + +document.querySelectorAll('th[data-sort-key]').forEach(th => { + th.addEventListener('click', () => sortByKey(th.dataset.sortKey)); +}); +updateSortIcons(); + await fetch('/api/db-explorer/set-metadata?' + new URLSearchParams({ includeCounts: false })) .then(res => res.json()) .then(data => data.data) .then(data => { document.getElementById('spinner').classList.add('d-none'); - const table = document.getElementById('set-metadata-list'); - data.forEach(({ _id, setName, difficulty, standard }) => { - const row = table.insertRow(-1); - const a = document.createElement('a'); - a.href = `../set/?_id=${_id}`; - a.textContent = setName; - row.insertCell(-1).appendChild(a); - row.insertCell(-1).textContent = difficulty; - row.insertCell(-1).textContent = standard; - row.insertCell(-1).textContent = '-'; - row.insertCell(-1).textContent = '-'; - row.insertCell(-1).textContent = '-'; - }); + sets = data; + renderTable(); }); fetch('/api/db-explorer/set-metadata?' + new URLSearchParams({ includeCounts: true })) @@ -23,11 +69,16 @@ fetch('/api/db-explorer/set-metadata?' + new URLSearchParams({ includeCounts: tr .then(data => data.data) .then(data => { document.getElementById('spinner').classList.add('d-none'); - const table = document.getElementById('set-metadata-list'); - const rows = table.rows; - for (let i = 0; i < data.length; i++) { - rows[i].cells[3].textContent = data[i].packetsCount; - rows[i].cells[4].textContent = data[i].tossupsCount; - rows[i].cells[5].textContent = data[i].bonusesCount; + const countsById = new Map(data.map(set => [set._id, set])); + sets.forEach(set => { + const counts = countsById.get(set._id); + if (!counts) return; + set.packetsCount = counts.packetsCount; + set.tossupsCount = counts.tossupsCount; + set.bonusesCount = counts.bonusesCount; + }); + if (['packetsCount', 'tossupsCount', 'bonusesCount'].includes(sortKey)) { + sets.sort((a, b) => compareRows(a, b, sortKey) * (sortAscending ? 1 : -1)); } + renderTable(); }); From a476a2cd3bb3b01ffa288c513c942b9d03d1b340 Mon Sep 17 00:00:00 2001 From: Lawrence Tong Date: Tue, 18 Aug 2026 15:58:36 -0500 Subject: [PATCH 2/2] refactor: replace bidirectional sort with sortTable --- client/db/set-list/index.html | 12 ++-- client/db/set-list/index.js | 94 +++++++++--------------------- client/scripts/utilities/tables.js | 8 +-- 3 files changed, 38 insertions(+), 76 deletions(-) diff --git a/client/db/set-list/index.html b/client/db/set-list/index.html index 228568889..ff52e3d27 100644 --- a/client/db/set-list/index.html +++ b/client/db/set-list/index.html @@ -18,12 +18,12 @@
Set NameDifficultyStandard# of Packets# of Tossups# of BonusesSet Name Difficulty Standard # of Packets # of Tossups # of Bonuses
- - - - - - + + + + + + diff --git a/client/db/set-list/index.js b/client/db/set-list/index.js index f33cd1e5c..2201d73c8 100644 --- a/client/db/set-list/index.js +++ b/client/db/set-list/index.js @@ -1,67 +1,30 @@ -let sets = []; -let sortKey = null; -let sortAscending = true; +import sortTable from '../../scripts/utilities/tables.js'; -function renderTable () { - const table = document.getElementById('set-metadata-list'); - table.textContent = ''; - sets.forEach(({ _id, setName, difficulty, standard, packetsCount, tossupsCount, bonusesCount }) => { - const row = table.insertRow(-1); - const a = document.createElement('a'); - a.href = `../set/?_id=${_id}`; - a.textContent = setName; - row.insertCell(-1).appendChild(a); - row.insertCell(-1).textContent = difficulty; - row.insertCell(-1).textContent = standard; - row.insertCell(-1).textContent = packetsCount ?? '-'; - row.insertCell(-1).textContent = tossupsCount ?? '-'; - row.insertCell(-1).textContent = bonusesCount ?? '-'; - }); -} - -function updateSortIcons () { - document.querySelectorAll('th[data-sort-key]').forEach(th => { - const icon = th.querySelector('i.bi'); - if (th.dataset.sortKey !== sortKey) { - icon.className = 'bi bi-arrow-down-up text-muted'; - } else { - icon.className = sortAscending ? 'bi bi-caret-up-fill' : 'bi bi-caret-down-fill'; - } - }); -} +const isNumericColumn = [false, true, false, true, true, true]; -function compareRows (a, b, key) { - const valueA = a[key]; - const valueB = b[key]; - if (valueA == null && valueB == null) return 0; - if (valueA == null) return -1; - if (valueB == null) return 1; - if (typeof valueA === 'string') return valueA.localeCompare(valueB); - if (valueA < valueB) return -1; - if (valueA > valueB) return 1; - return 0; -} - -function sortByKey (key) { - sortAscending = sortKey === key ? !sortAscending : true; - sortKey = key; - sets.sort((a, b) => compareRows(a, b, key) * (sortAscending ? 1 : -1)); - renderTable(); - updateSortIcons(); -} - -document.querySelectorAll('th[data-sort-key]').forEach(th => { - th.addEventListener('click', () => sortByKey(th.dataset.sortKey)); +document.querySelectorAll('th').forEach((th, index) => { + th.addEventListener('click', () => sortTable(index, isNumericColumn[index], 'set-metadata-list', 0, 0)); }); -updateSortIcons(); await fetch('/api/db-explorer/set-metadata?' + new URLSearchParams({ includeCounts: false })) .then(res => res.json()) .then(data => data.data) .then(data => { document.getElementById('spinner').classList.add('d-none'); - sets = data; - renderTable(); + const table = document.getElementById('set-metadata-list'); + data.forEach(({ _id, setName, difficulty, standard }) => { + const row = table.insertRow(-1); + row.dataset.setId = _id; + const a = document.createElement('a'); + a.href = `../set/?_id=${_id}`; + a.textContent = setName; + row.insertCell(-1).appendChild(a); + row.insertCell(-1).textContent = difficulty; + row.insertCell(-1).textContent = standard; + row.insertCell(-1).textContent = '-'; + row.insertCell(-1).textContent = '-'; + row.insertCell(-1).textContent = '-'; + }); }); fetch('/api/db-explorer/set-metadata?' + new URLSearchParams({ includeCounts: true })) @@ -69,16 +32,15 @@ fetch('/api/db-explorer/set-metadata?' + new URLSearchParams({ includeCounts: tr .then(data => data.data) .then(data => { document.getElementById('spinner').classList.add('d-none'); - const countsById = new Map(data.map(set => [set._id, set])); - sets.forEach(set => { - const counts = countsById.get(set._id); - if (!counts) return; - set.packetsCount = counts.packetsCount; - set.tossupsCount = counts.tossupsCount; - set.bonusesCount = counts.bonusesCount; - }); - if (['packetsCount', 'tossupsCount', 'bonusesCount'].includes(sortKey)) { - sets.sort((a, b) => compareRows(a, b, sortKey) * (sortAscending ? 1 : -1)); + const table = document.getElementById('set-metadata-list'); + // Look rows up by set id instead of index: the user may have sorted the + // table while this request was in flight, which reorders the rows. + const rowsBySetId = new Map(Array.from(table.rows).map(row => [row.dataset.setId, row])); + for (const { _id, packetsCount, tossupsCount, bonusesCount } of data) { + const row = rowsBySetId.get(_id); + if (!row) { continue; } + row.cells[3].textContent = packetsCount; + row.cells[4].textContent = tossupsCount; + row.cells[5].textContent = bonusesCount; } - renderTable(); }); diff --git a/client/scripts/utilities/tables.js b/client/scripts/utilities/tables.js index 13ba079d8..48266c33d 100644 --- a/client/scripts/utilities/tables.js +++ b/client/scripts/utilities/tables.js @@ -31,13 +31,13 @@ export default function sortTable (n, numeric = false, tableId = 'table', header based on the direction, asc or desc: */ if (dir === 'asc') { if (numeric) { - if (parseFloat(x.innerHTML) < parseFloat(y.innerHTML)) { + if (parseFloat(x.textContent) < parseFloat(y.textContent)) { // If so, mark as a switch and break the loop: shouldSwitch = true; break; } } else { - if (x.innerHTML.toLowerCase() > y.innerHTML.toLowerCase()) { + if (x.textContent.toLowerCase() > y.textContent.toLowerCase()) { // If so, mark as a switch and break the loop: shouldSwitch = true; break; @@ -45,13 +45,13 @@ export default function sortTable (n, numeric = false, tableId = 'table', header } } else if (dir === 'desc') { if (numeric) { - if (parseFloat(x.innerHTML) > parseFloat(y.innerHTML)) { + if (parseFloat(x.textContent) > parseFloat(y.textContent)) { // If so, mark as a switch and break the loop: shouldSwitch = true; break; } } else { - if (x.innerHTML.toLowerCase() < y.innerHTML.toLowerCase()) { + if (x.textContent.toLowerCase() < y.textContent.toLowerCase()) { // If so, mark as a switch and break the loop: shouldSwitch = true; break;
Set Name Difficulty Standard # of Packets # of Tossups # of Bonuses Set NameDifficultyStandard# of Packets# of Tossups# of Bonuses