mirror of
https://github.com/BetterSEQTA/BetterSEQTA-Plus.git
synced 2026-06-16 16:37:07 +00:00
feat(assessmentsAverage): fingerprint-based reindex with non-blocking refresh
- Add WEIGHTING_SCHEMA_VERSION constant; bump to force a global lazy reindex
- Migrate legacy Record<id, string> storage to { weight, fingerprint, pluginVersion }
- Fingerprint per-assessment on status, graded, availability, score, due, title (sourced from the React fiber)
- Refetch weighting only when fingerprint or schema version mismatches
- Preserve previous weight as refreshing placeholder during background refetch
- Render subject average immediately from cache; run parseAssessments off the critical path
- Coalesce concurrent renderSubjectAverage calls instead of dropping them
- Dispatch betterseqta:weightingsChanged on refetch start and completion
- Show row-level refresh indicator and "Refreshing weightings" notice while refreshing
- Leave weightingOverrides untouched by all reindex paths
This commit is contained in:
@@ -15,11 +15,12 @@ import {
|
||||
letterToNumber,
|
||||
parseAssessments,
|
||||
processAssessments,
|
||||
type WeightingEntry,
|
||||
} from "./utils.ts";
|
||||
import { injectRubricCopyButtons } from "./rubricCopy.ts";
|
||||
|
||||
interface weightingsStorage {
|
||||
weightings: Record<string, string>;
|
||||
weightings: Record<string, WeightingEntry>;
|
||||
assessments: Record<string, string>;
|
||||
weightingOverrides: Record<string, string>;
|
||||
}
|
||||
@@ -61,8 +62,8 @@ const assessmentsAveragePlugin: Plugin<typeof settings, weightingsStorage> = {
|
||||
1000,
|
||||
);
|
||||
|
||||
await parseAssessments(api);
|
||||
await renderSubjectAverage(api);
|
||||
// Wire listeners first so the very first re-render triggered by a
|
||||
// background handleWeightings completion can find them.
|
||||
overrideListenerController?.abort();
|
||||
overrideListenerController = new AbortController();
|
||||
document.addEventListener(
|
||||
@@ -70,6 +71,21 @@ const assessmentsAveragePlugin: Plugin<typeof settings, weightingsStorage> = {
|
||||
() => renderSubjectAverage(api),
|
||||
{ signal: overrideListenerController.signal },
|
||||
);
|
||||
document.addEventListener(
|
||||
"betterseqta:weightingsChanged",
|
||||
() => renderSubjectAverage(api),
|
||||
{ signal: overrideListenerController.signal },
|
||||
);
|
||||
|
||||
// Render immediately with whatever is already cached. Fresh entries
|
||||
// and stale-with-previous-value entries both contribute their numeric
|
||||
// weights, so the subject average appears without waiting on any
|
||||
// background PDF refetches.
|
||||
await renderSubjectAverage(api);
|
||||
|
||||
// Kick off indexing in the background. Each completion dispatches
|
||||
// betterseqta:weightingsChanged, which triggers a fresh render.
|
||||
void parseAssessments(api);
|
||||
const wrapper = document.querySelector(".assessmentsWrapper");
|
||||
if (wrapper) {
|
||||
const observer = new MutationObserver(() => {
|
||||
@@ -87,8 +103,15 @@ const assessmentsAveragePlugin: Plugin<typeof settings, weightingsStorage> = {
|
||||
};
|
||||
|
||||
let renderInFlight = false;
|
||||
let renderQueued = false;
|
||||
async function renderSubjectAverage(api: any) {
|
||||
if (renderInFlight) return;
|
||||
if (renderInFlight) {
|
||||
// Coalesce: remember that fresh data arrived during this render and
|
||||
// re-run once the current pass finishes, so the UI catches up to the
|
||||
// latest storage state instead of silently dropping the event.
|
||||
renderQueued = true;
|
||||
return;
|
||||
}
|
||||
renderInFlight = true;
|
||||
|
||||
try {
|
||||
@@ -141,8 +164,13 @@ async function renderSubjectAverage(api: any) {
|
||||
?.textContent?.includes("Subject Average"),
|
||||
);
|
||||
|
||||
const { weightedTotal, totalWeight, hasInaccurateWeighting, count } =
|
||||
await processAssessments(api, assessmentItems);
|
||||
const {
|
||||
weightedTotal,
|
||||
totalWeight,
|
||||
hasInaccurateWeighting,
|
||||
hasRefreshingWeighting,
|
||||
count,
|
||||
} = await processAssessments(api, assessmentItems);
|
||||
if (!count || totalWeight === 0) return;
|
||||
|
||||
const thermoscoreElement = document.querySelector(
|
||||
@@ -176,11 +204,22 @@ async function renderSubjectAverage(api: any) {
|
||||
let warningHTML = "";
|
||||
if (hasInaccurateWeighting) {
|
||||
warningHTML = /* html */ `
|
||||
<div style="margin-top: 4px; font-size: 11px; color: rgba(255, 255, 255, 0.6); opacity: 0.8; line-height: 1.3;">
|
||||
<div style="margin-top: 4px; font-size: 11px; color: rgba(255, 255, 255, 0.6); opacity: 0.8; line-height: 1.3; white-space: nowrap;">
|
||||
⚠ Some weightings unavailable
|
||||
</div>
|
||||
`;
|
||||
} else if (hasRefreshingWeighting) {
|
||||
warningHTML = /* html */ `
|
||||
<div style="margin-top: 4px; font-size: 11px; color: rgba(255, 255, 255, 0.55); opacity: 0.8; line-height: 1.3; white-space: nowrap;" title="Some weightings are being re-checked; the average may change shortly">
|
||||
↻ Refreshing weightings
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
const thermoscoreTitle = hasInaccurateWeighting
|
||||
? `${display} (some weightings unavailable)`
|
||||
: hasRefreshingWeighting
|
||||
? `${display} (re-checking weightings)`
|
||||
: display;
|
||||
assessmentsList.insertBefore(
|
||||
stringToHTML(/* html */ `
|
||||
<div class="${assessmentItemClass}">
|
||||
@@ -194,7 +233,7 @@ async function renderSubjectAverage(api: any) {
|
||||
</div>
|
||||
<div class="${thermoscoreClass}">
|
||||
<div class="${fillClass}" style="width: ${avg.toFixed(2)}%">
|
||||
<div class="${textClass}" title="${hasInaccurateWeighting ? display + " (some weightings unavailable)" : display}">${display}</div>
|
||||
<div class="${textClass}" title="${thermoscoreTitle}">${display}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -204,6 +243,10 @@ async function renderSubjectAverage(api: any) {
|
||||
applySubjectColourToOverallResult();
|
||||
} finally {
|
||||
renderInFlight = false;
|
||||
if (renderQueued) {
|
||||
renderQueued = false;
|
||||
void renderSubjectAverage(api);
|
||||
}
|
||||
}
|
||||
}
|
||||
function applySubjectColourToOverallResult() {
|
||||
|
||||
Reference in New Issue
Block a user