Add Regrade: instant re-run when photos are stored, fallback re-pick otherwise
Stores the original photo(s) for every card graded from now on, so an existing history row can be re-graded in place without re-uploading -- most useful right after a grading-logic fix like the last few commits. Cards graded before this feature (or saved without images) fall back to the same photo picker as a fresh grade, then start getting instant regrades from then on. list_grades/get_grade now select an explicit column list rather than SELECT * so the stored images are never pulled into memory just to be discarded for the browser-facing response.
This commit is contained in:
parent
552f862f4c
commit
e586bc92e7
4 changed files with 302 additions and 39 deletions
|
|
@ -363,7 +363,11 @@ function renderHistory() {
|
|||
g.estimated_grade === null ? 'n/a' : `PSA ${g.estimated_grade}`}</span></td>
|
||||
<td class="cardcell-meta">${esc(g.confidence || '')}</td>
|
||||
<td class="cardcell-meta">${esc(fmtWhen(g.created_at))}</td>
|
||||
<td class="num"><button class="btn btn-quiet btn-sm" data-history-delete="${g.id}">Delete</button></td>
|
||||
<td class="num">
|
||||
<button class="btn btn-quiet btn-sm" data-history-regrade="${g.id}"
|
||||
data-history-has-images="${g.has_source_images ? '1' : '0'}">Regrade</button>
|
||||
<button class="btn btn-quiet btn-sm" data-history-delete="${g.id}">Delete</button>
|
||||
</td>
|
||||
</tr>`).join('');
|
||||
}
|
||||
|
||||
|
|
@ -380,6 +384,8 @@ function renderEntryModal(g) {
|
|||
<input class="input" id="entry-label" placeholder="Rename this card…" value="${esc(g.label || '')}" style="flex:1 1 200px">
|
||||
<button class="btn btn-quiet btn-sm" id="entry-save-label">Save name</button>
|
||||
<span class="spacer"></span>
|
||||
<button class="btn btn-quiet btn-sm" data-history-regrade="${g.id}"
|
||||
data-history-has-images="${g.has_source_images ? '1' : '0'}">Regrade</button>
|
||||
<button class="btn btn-danger btn-sm" data-history-delete="${g.id}">Delete</button>
|
||||
</div>`;
|
||||
$('#entry-modal').hidden = false;
|
||||
|
|
@ -391,6 +397,64 @@ function closeEntryModal() {
|
|||
$('#entry-scrim').hidden = true;
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------- regrade */
|
||||
//
|
||||
// Instant when the card's original photo(s) were saved (everything graded
|
||||
// from now on); otherwise falls back to picking the photo(s) again, same as
|
||||
// grading fresh, just updating this row instead of creating a new one.
|
||||
|
||||
let regradeTargetId = null;
|
||||
|
||||
function applyRegradeResult(g) {
|
||||
const idx = state.history.findIndex((row) => row.id === g.id);
|
||||
if (idx === -1) state.history.unshift(g); else state.history[idx] = g;
|
||||
// created_at was bumped server-side to reflect the just-finished regrade,
|
||||
// so re-sort to match — otherwise the card sits wherever it used to be
|
||||
// until the next full reload.
|
||||
state.history.sort((a, b) =>
|
||||
(b.created_at || '').localeCompare(a.created_at || '') || b.id - a.id);
|
||||
renderHistory();
|
||||
const openDelete = $('#entry-modal').querySelector('[data-history-delete]');
|
||||
if (!$('#entry-modal').hidden && openDelete && Number(openDelete.dataset.historyDelete) === g.id) {
|
||||
renderEntryModal(g);
|
||||
}
|
||||
}
|
||||
|
||||
async function runRegrade(id, body) {
|
||||
banner('Regrading…');
|
||||
try {
|
||||
const data = await api(`/api/history/${id}/regrade`, { method: 'POST', body });
|
||||
applyRegradeResult(data.grade);
|
||||
banner('Regraded.');
|
||||
} catch (err) {
|
||||
banner(`Regrade failed: ${err.message}`, true);
|
||||
}
|
||||
}
|
||||
|
||||
function startRegrade(id, hasImages) {
|
||||
if (hasImages) {
|
||||
runRegrade(id, {});
|
||||
} else {
|
||||
regradeTargetId = id;
|
||||
$('#regrade-file').click();
|
||||
}
|
||||
}
|
||||
|
||||
$('#regrade-file').addEventListener('change', async (e) => {
|
||||
const id = regradeTargetId;
|
||||
regradeTargetId = null;
|
||||
if (!id) return;
|
||||
let picked;
|
||||
try {
|
||||
picked = await readPickedImages(e.target, 6);
|
||||
} catch (err) {
|
||||
banner(err.message, true);
|
||||
return;
|
||||
}
|
||||
if (!picked.length) return;
|
||||
await runRegrade(id, { images: pickedToPayload(picked) });
|
||||
});
|
||||
|
||||
$('#history-body').addEventListener('click', async (e) => {
|
||||
const del = e.target.closest('[data-history-delete]');
|
||||
if (del) {
|
||||
|
|
@ -399,6 +463,12 @@ $('#history-body').addEventListener('click', async (e) => {
|
|||
await loadHistory();
|
||||
return;
|
||||
}
|
||||
const regrade = e.target.closest('[data-history-regrade]');
|
||||
if (regrade) {
|
||||
e.stopPropagation();
|
||||
startRegrade(Number(regrade.dataset.historyRegrade), regrade.dataset.historyHasImages === '1');
|
||||
return;
|
||||
}
|
||||
const row = e.target.closest('[data-history-row]');
|
||||
if (row) {
|
||||
try {
|
||||
|
|
@ -419,6 +489,11 @@ $('#entry-modal').addEventListener('click', async (e) => {
|
|||
await loadHistory();
|
||||
return;
|
||||
}
|
||||
const regrade = e.target.closest('[data-history-regrade]');
|
||||
if (regrade) {
|
||||
startRegrade(Number(regrade.dataset.historyRegrade), regrade.dataset.historyHasImages === '1');
|
||||
return;
|
||||
}
|
||||
if (e.target.closest('#entry-save-label')) {
|
||||
const id = $('#entry-modal').querySelector('[data-history-delete]').dataset.historyDelete;
|
||||
const label = $('#entry-label').value.trim();
|
||||
|
|
|
|||
|
|
@ -61,6 +61,11 @@
|
|||
<div id="grade-review"></div>
|
||||
</section>
|
||||
|
||||
<!-- Fallback photo picker for Regrade on a card saved before this feature
|
||||
existed (or otherwise missing its stored photo) — regradeTargetId in
|
||||
app.js tracks which row this fires for. -->
|
||||
<input id="regrade-file" type="file" accept="image/*" multiple hidden>
|
||||
|
||||
<!-- ----------------------------------------------------------- history -->
|
||||
<section class="panel">
|
||||
<div class="panel-head">
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue