mirror of
https://github.com/BetterSEQTA/BetterSEQTA-Plus.git
synced 2026-06-13 15:14:40 +00:00
Merge pull request #450 from Jaxx7594/assessmentsAverage-reindex
feat(assessmentsAverage): fingerprint-based reindex to harden against changed weightings
This commit is contained in:
@@ -15,11 +15,12 @@ import {
|
|||||||
letterToNumber,
|
letterToNumber,
|
||||||
parseAssessments,
|
parseAssessments,
|
||||||
processAssessments,
|
processAssessments,
|
||||||
|
type WeightingEntry,
|
||||||
} from "./utils.ts";
|
} from "./utils.ts";
|
||||||
import { injectRubricCopyButtons } from "./rubricCopy.ts";
|
import { injectRubricCopyButtons } from "./rubricCopy.ts";
|
||||||
|
|
||||||
interface weightingsStorage {
|
interface weightingsStorage {
|
||||||
weightings: Record<string, string>;
|
weightings: Record<string, WeightingEntry>;
|
||||||
assessments: Record<string, string>;
|
assessments: Record<string, string>;
|
||||||
weightingOverrides: Record<string, string>;
|
weightingOverrides: Record<string, string>;
|
||||||
}
|
}
|
||||||
@@ -61,8 +62,8 @@ const assessmentsAveragePlugin: Plugin<typeof settings, weightingsStorage> = {
|
|||||||
1000,
|
1000,
|
||||||
);
|
);
|
||||||
|
|
||||||
await parseAssessments(api);
|
// Wire listeners first so the very first re-render triggered by a
|
||||||
await renderSubjectAverage(api);
|
// background handleWeightings completion can find them.
|
||||||
overrideListenerController?.abort();
|
overrideListenerController?.abort();
|
||||||
overrideListenerController = new AbortController();
|
overrideListenerController = new AbortController();
|
||||||
document.addEventListener(
|
document.addEventListener(
|
||||||
@@ -70,6 +71,21 @@ const assessmentsAveragePlugin: Plugin<typeof settings, weightingsStorage> = {
|
|||||||
() => renderSubjectAverage(api),
|
() => renderSubjectAverage(api),
|
||||||
{ signal: overrideListenerController.signal },
|
{ 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");
|
const wrapper = document.querySelector(".assessmentsWrapper");
|
||||||
if (wrapper) {
|
if (wrapper) {
|
||||||
const observer = new MutationObserver(() => {
|
const observer = new MutationObserver(() => {
|
||||||
@@ -87,8 +103,15 @@ const assessmentsAveragePlugin: Plugin<typeof settings, weightingsStorage> = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let renderInFlight = false;
|
let renderInFlight = false;
|
||||||
|
let renderQueued = false;
|
||||||
async function renderSubjectAverage(api: any) {
|
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;
|
renderInFlight = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -141,8 +164,13 @@ async function renderSubjectAverage(api: any) {
|
|||||||
?.textContent?.includes("Subject Average"),
|
?.textContent?.includes("Subject Average"),
|
||||||
);
|
);
|
||||||
|
|
||||||
const { weightedTotal, totalWeight, hasInaccurateWeighting, count } =
|
const {
|
||||||
await processAssessments(api, assessmentItems);
|
weightedTotal,
|
||||||
|
totalWeight,
|
||||||
|
hasInaccurateWeighting,
|
||||||
|
hasRefreshingWeighting,
|
||||||
|
count,
|
||||||
|
} = await processAssessments(api, assessmentItems);
|
||||||
if (!count || totalWeight === 0) return;
|
if (!count || totalWeight === 0) return;
|
||||||
|
|
||||||
const thermoscoreElement = document.querySelector(
|
const thermoscoreElement = document.querySelector(
|
||||||
@@ -176,11 +204,22 @@ async function renderSubjectAverage(api: any) {
|
|||||||
let warningHTML = "";
|
let warningHTML = "";
|
||||||
if (hasInaccurateWeighting) {
|
if (hasInaccurateWeighting) {
|
||||||
warningHTML = /* html */ `
|
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
|
⚠ Some weightings unavailable
|
||||||
</div>
|
</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(
|
assessmentsList.insertBefore(
|
||||||
stringToHTML(/* html */ `
|
stringToHTML(/* html */ `
|
||||||
<div class="${assessmentItemClass}">
|
<div class="${assessmentItemClass}">
|
||||||
@@ -194,7 +233,7 @@ async function renderSubjectAverage(api: any) {
|
|||||||
</div>
|
</div>
|
||||||
<div class="${thermoscoreClass}">
|
<div class="${thermoscoreClass}">
|
||||||
<div class="${fillClass}" style="width: ${avg.toFixed(2)}%">
|
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -204,6 +243,10 @@ async function renderSubjectAverage(api: any) {
|
|||||||
applySubjectColourToOverallResult();
|
applySubjectColourToOverallResult();
|
||||||
} finally {
|
} finally {
|
||||||
renderInFlight = false;
|
renderInFlight = false;
|
||||||
|
if (renderQueued) {
|
||||||
|
renderQueued = false;
|
||||||
|
void renderSubjectAverage(api);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function applySubjectColourToOverallResult() {
|
function applySubjectColourToOverallResult() {
|
||||||
|
|||||||
@@ -14,6 +14,59 @@ import * as pdfjs from "pdfjs-dist";
|
|||||||
|
|
||||||
ensurePdfjsWorker();
|
ensurePdfjsWorker();
|
||||||
|
|
||||||
|
export const WEIGHTING_SCHEMA_VERSION = 1;
|
||||||
|
|
||||||
|
export interface WeightingEntry {
|
||||||
|
weight: string;
|
||||||
|
fingerprint: string;
|
||||||
|
pluginVersion: number;
|
||||||
|
refreshing?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type WeightingsMap = Record<string, WeightingEntry>;
|
||||||
|
|
||||||
|
export function computeFingerprint(mark: any): string {
|
||||||
|
const score =
|
||||||
|
mark?.results?.percentage ?? mark?.results?.score ?? null;
|
||||||
|
return JSON.stringify([
|
||||||
|
mark?.status ?? "",
|
||||||
|
Boolean(mark?.graded),
|
||||||
|
mark?.availability ?? "",
|
||||||
|
score,
|
||||||
|
mark?.due ?? "",
|
||||||
|
mark?.title ?? "",
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function migrateWeightings(api: any) {
|
||||||
|
const w = api.storage.weightings ?? {};
|
||||||
|
let dirty = false;
|
||||||
|
const out: WeightingsMap = {};
|
||||||
|
for (const [id, v] of Object.entries(w)) {
|
||||||
|
if (typeof v === "string") {
|
||||||
|
out[id] = { weight: v, fingerprint: "", pluginVersion: 0 };
|
||||||
|
dirty = true;
|
||||||
|
} else if (v && typeof v === "object") {
|
||||||
|
const entry = v as Partial<WeightingEntry>;
|
||||||
|
if (
|
||||||
|
typeof entry.weight === "string" &&
|
||||||
|
typeof entry.fingerprint === "string" &&
|
||||||
|
typeof entry.pluginVersion === "number"
|
||||||
|
) {
|
||||||
|
out[id] = entry as WeightingEntry;
|
||||||
|
} else {
|
||||||
|
out[id] = {
|
||||||
|
weight: String(entry.weight ?? "N/A"),
|
||||||
|
fingerprint: "",
|
||||||
|
pluginVersion: 0,
|
||||||
|
};
|
||||||
|
dirty = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (dirty) api.storage.weightings = out;
|
||||||
|
}
|
||||||
|
|
||||||
export async function initStorage(api: any) {
|
export async function initStorage(api: any) {
|
||||||
await api.storage.loaded;
|
await api.storage.loaded;
|
||||||
|
|
||||||
@@ -26,19 +79,34 @@ export async function initStorage(api: any) {
|
|||||||
if (!api.storage.weightingOverrides) {
|
if (!api.storage.weightingOverrides) {
|
||||||
api.storage.weightingOverrides = {};
|
api.storage.weightingOverrides = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
migrateWeightings(api);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function clearStuck(api: any) {
|
export function clearStuck(api: any) {
|
||||||
let hasStuckProcessing = false;
|
const map = (api.storage.weightings ?? {}) as WeightingsMap;
|
||||||
for (const key in api.storage.weightings) {
|
let dirty = false;
|
||||||
if (api.storage.weightings[key] === "processing") {
|
const out: WeightingsMap = {};
|
||||||
delete api.storage.weightings[key];
|
for (const [key, entry] of Object.entries(map)) {
|
||||||
hasStuckProcessing = true;
|
if (!entry || typeof entry !== "object") {
|
||||||
|
dirty = true;
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
if (entry.weight === "processing") {
|
||||||
|
// Stuck mid-fetch from a previous session: drop it so the next
|
||||||
|
// page load can re-run handleWeightings from scratch.
|
||||||
|
dirty = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (entry.refreshing) {
|
||||||
|
const { refreshing: _ignored, ...rest } = entry;
|
||||||
|
out[key] = rest;
|
||||||
|
dirty = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
out[key] = entry;
|
||||||
}
|
}
|
||||||
if (hasStuckProcessing) {
|
if (dirty) api.storage.weightings = out;
|
||||||
api.storage.weightings = { ...api.storage.weightings };
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper function to find actual class names by their base pattern
|
// Helper function to find actual class names by their base pattern
|
||||||
@@ -137,6 +205,7 @@ function updateWeightLabelContent(
|
|||||||
weighting: string | undefined,
|
weighting: string | undefined,
|
||||||
assessmentID: string | undefined,
|
assessmentID: string | undefined,
|
||||||
api: any,
|
api: any,
|
||||||
|
refreshing = false,
|
||||||
) {
|
) {
|
||||||
const existingInput = weightLabel.querySelector(
|
const existingInput = weightLabel.querySelector(
|
||||||
".betterseqta-weight-input",
|
".betterseqta-weight-input",
|
||||||
@@ -178,10 +247,15 @@ function updateWeightLabelContent(
|
|||||||
|
|
||||||
const span = document.createElement("span");
|
const span = document.createElement("span");
|
||||||
span.className = "betterseqta-weight-value";
|
span.className = "betterseqta-weight-value";
|
||||||
span.textContent =
|
const baseText =
|
||||||
weighting && weighting !== "N/A"
|
weighting && weighting !== "N/A"
|
||||||
? formatWeightDisplay(weighting)
|
? formatWeightDisplay(weighting)
|
||||||
: "N/A";
|
: "N/A";
|
||||||
|
span.textContent = refreshing ? `${baseText} ↻` : baseText;
|
||||||
|
if (refreshing) {
|
||||||
|
span.style.opacity = "0.7";
|
||||||
|
weightLabel.title = "Re-checking weighting…";
|
||||||
|
}
|
||||||
weightLabel.appendChild(span);
|
weightLabel.appendChild(span);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -189,6 +263,7 @@ function createWeightLabel(
|
|||||||
assessmentItem: Element,
|
assessmentItem: Element,
|
||||||
weighting: string | undefined,
|
weighting: string | undefined,
|
||||||
api: any,
|
api: any,
|
||||||
|
refreshing = false,
|
||||||
) {
|
) {
|
||||||
let statsContainer = assessmentItem.querySelector(
|
let statsContainer = assessmentItem.querySelector(
|
||||||
`[class*='AssessmentItem__stats___'], .betterseqta-stats-container`,
|
`[class*='AssessmentItem__stats___'], .betterseqta-stats-container`,
|
||||||
@@ -224,7 +299,13 @@ function createWeightLabel(
|
|||||||
) as HTMLElement | null;
|
) as HTMLElement | null;
|
||||||
|
|
||||||
if (existingLabel) {
|
if (existingLabel) {
|
||||||
updateWeightLabelContent(existingLabel, weighting, assessmentID, api);
|
updateWeightLabelContent(
|
||||||
|
existingLabel,
|
||||||
|
weighting,
|
||||||
|
assessmentID,
|
||||||
|
api,
|
||||||
|
refreshing,
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -256,7 +337,13 @@ function createWeightLabel(
|
|||||||
const innerTextDiv = weightLabel.querySelector(`[class*='Label__innerText___']`);
|
const innerTextDiv = weightLabel.querySelector(`[class*='Label__innerText___']`);
|
||||||
if (innerTextDiv) innerTextDiv.textContent = "Weight";
|
if (innerTextDiv) innerTextDiv.textContent = "Weight";
|
||||||
|
|
||||||
updateWeightLabelContent(weightLabel, weighting, assessmentID, api);
|
updateWeightLabelContent(
|
||||||
|
weightLabel,
|
||||||
|
weighting,
|
||||||
|
assessmentID,
|
||||||
|
api,
|
||||||
|
refreshing,
|
||||||
|
);
|
||||||
statsContainer.appendChild(weightLabel);
|
statsContainer.appendChild(weightLabel);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -563,16 +650,41 @@ async function handleWeightings(mark: any, api: any) {
|
|||||||
const metaclassID = mark.metaclassID;
|
const metaclassID = mark.metaclassID;
|
||||||
const title = mark.title;
|
const title = mark.title;
|
||||||
|
|
||||||
if (
|
const fingerprint = computeFingerprint(mark);
|
||||||
api.storage.weightings[assessmentID] != undefined &&
|
const existing = api.storage.weightings[assessmentID] as
|
||||||
api.storage.weightings[assessmentID] !== "processing"
|
| WeightingEntry
|
||||||
) {
|
| undefined;
|
||||||
return;
|
|
||||||
}
|
const isFresh =
|
||||||
|
existing &&
|
||||||
|
existing.weight !== "processing" &&
|
||||||
|
existing.fingerprint === fingerprint &&
|
||||||
|
existing.pluginVersion === WEIGHTING_SCHEMA_VERSION;
|
||||||
|
|
||||||
|
if (isFresh) return;
|
||||||
|
|
||||||
|
// If we have a previous usable value, keep showing it while we refetch
|
||||||
|
// by marking the entry as refreshing instead of wiping it. We claim the
|
||||||
|
// new fingerprint + version on the placeholder so a second parseAssessments
|
||||||
|
// pass (e.g. a fast re-mount of the wrapper) doesn't kick off a duplicate
|
||||||
|
// refetch for the same id while this one is still in flight.
|
||||||
|
const placeholder: WeightingEntry =
|
||||||
|
existing && existing.weight !== "processing"
|
||||||
|
? {
|
||||||
|
...existing,
|
||||||
|
fingerprint,
|
||||||
|
pluginVersion: WEIGHTING_SCHEMA_VERSION,
|
||||||
|
refreshing: true,
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
weight: "processing",
|
||||||
|
fingerprint,
|
||||||
|
pluginVersion: WEIGHTING_SCHEMA_VERSION,
|
||||||
|
};
|
||||||
|
|
||||||
api.storage.weightings = {
|
api.storage.weightings = {
|
||||||
...api.storage.weightings,
|
...api.storage.weightings,
|
||||||
[assessmentID]: "processing",
|
[assessmentID]: placeholder,
|
||||||
};
|
};
|
||||||
|
|
||||||
api.storage.assessments = {
|
api.storage.assessments = {
|
||||||
@@ -580,6 +692,10 @@ async function handleWeightings(mark: any, api: any) {
|
|||||||
[title.trim()]: assessmentID,
|
[title.trim()]: assessmentID,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Surface the refreshing indicator on the affected row immediately,
|
||||||
|
// without waiting for the PDF fetch to finish.
|
||||||
|
document.dispatchEvent(new CustomEvent("betterseqta:weightingsChanged"));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let pdfUrl: string;
|
let pdfUrl: string;
|
||||||
|
|
||||||
@@ -655,14 +771,24 @@ async function handleWeightings(mark: any, api: any) {
|
|||||||
|
|
||||||
api.storage.weightings = {
|
api.storage.weightings = {
|
||||||
...api.storage.weightings,
|
...api.storage.weightings,
|
||||||
[assessmentID]: match ? match[1] : "N/A",
|
[assessmentID]: {
|
||||||
|
weight: match ? match[1] : "N/A",
|
||||||
|
fingerprint,
|
||||||
|
pluginVersion: WEIGHTING_SCHEMA_VERSION,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
api.storage.weightings = {
|
api.storage.weightings = {
|
||||||
...api.storage.weightings,
|
...api.storage.weightings,
|
||||||
[assessmentID]: "N/A",
|
[assessmentID]: {
|
||||||
|
weight: "N/A",
|
||||||
|
fingerprint,
|
||||||
|
pluginVersion: WEIGHTING_SCHEMA_VERSION,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
document.dispatchEvent(new CustomEvent("betterseqta:weightingsChanged"));
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function parseAssessments(api: any) {
|
export async function parseAssessments(api: any) {
|
||||||
@@ -684,6 +810,7 @@ export async function processAssessments(api: any, assessmentItems: Element[]) {
|
|||||||
let weightedTotal = 0;
|
let weightedTotal = 0;
|
||||||
let totalWeight = 0;
|
let totalWeight = 0;
|
||||||
let hasInaccurateWeighting = false;
|
let hasInaccurateWeighting = false;
|
||||||
|
let hasRefreshingWeighting = false;
|
||||||
let count = 0;
|
let count = 0;
|
||||||
|
|
||||||
for (const assessmentItem of assessmentItems) {
|
for (const assessmentItem of assessmentItems) {
|
||||||
@@ -696,15 +823,17 @@ export async function processAssessments(api: any, assessmentItems: Element[]) {
|
|||||||
if (!title) continue;
|
if (!title) continue;
|
||||||
|
|
||||||
const assessmentID = api.storage.assessments?.[title];
|
const assessmentID = api.storage.assessments?.[title];
|
||||||
const autoWeighting = assessmentID
|
const entry = assessmentID
|
||||||
? api.storage.weightings?.[assessmentID]
|
? (api.storage.weightings?.[assessmentID] as WeightingEntry | undefined)
|
||||||
: undefined;
|
: undefined;
|
||||||
|
const autoWeighting = entry?.weight;
|
||||||
const override = assessmentID
|
const override = assessmentID
|
||||||
? api.storage.weightingOverrides?.[assessmentID]
|
? api.storage.weightingOverrides?.[assessmentID]
|
||||||
: undefined;
|
: undefined;
|
||||||
const weighting = override ?? autoWeighting;
|
const weighting = override ?? autoWeighting;
|
||||||
|
const refreshing = !override && Boolean(entry?.refreshing);
|
||||||
|
|
||||||
createWeightLabel(assessmentItem, weighting, api);
|
createWeightLabel(assessmentItem, weighting, api, refreshing);
|
||||||
|
|
||||||
const gradeElement = assessmentItem.querySelector(
|
const gradeElement = assessmentItem.querySelector(
|
||||||
`[class*='Thermoscore__text___']`,
|
`[class*='Thermoscore__text___']`,
|
||||||
@@ -727,6 +856,7 @@ export async function processAssessments(api: any, assessmentItems: Element[]) {
|
|||||||
if (!isNaN(weight) && weight >= 0) {
|
if (!isNaN(weight) && weight >= 0) {
|
||||||
weightedTotal += grade * weight;
|
weightedTotal += grade * weight;
|
||||||
totalWeight += weight;
|
totalWeight += weight;
|
||||||
|
if (refreshing) hasRefreshingWeighting = true;
|
||||||
} else {
|
} else {
|
||||||
weightedTotal += grade;
|
weightedTotal += grade;
|
||||||
totalWeight += 1;
|
totalWeight += 1;
|
||||||
@@ -740,6 +870,7 @@ export async function processAssessments(api: any, assessmentItems: Element[]) {
|
|||||||
weightedTotal,
|
weightedTotal,
|
||||||
totalWeight,
|
totalWeight,
|
||||||
hasInaccurateWeighting,
|
hasInaccurateWeighting,
|
||||||
|
hasRefreshingWeighting,
|
||||||
count,
|
count,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -811,9 +942,10 @@ function buildWeightingsTabContent(api: any, sheet: HTMLElement) {
|
|||||||
const title = titleEl?.textContent?.trim();
|
const title = titleEl?.textContent?.trim();
|
||||||
const assessmentID = title ? api.storage.assessments?.[title] : undefined;
|
const assessmentID = title ? api.storage.assessments?.[title] : undefined;
|
||||||
|
|
||||||
const rawWeight = assessmentID
|
const entry = assessmentID
|
||||||
? api.storage.weightings?.[assessmentID]
|
? (api.storage.weightings?.[assessmentID] as WeightingEntry | undefined)
|
||||||
: undefined;
|
: undefined;
|
||||||
|
const rawWeight = entry?.weight;
|
||||||
|
|
||||||
const weightingUnavailable = rawWeight === "N/A";
|
const weightingUnavailable = rawWeight === "N/A";
|
||||||
|
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ export function OpenWhatsNewPopup(onDismissed?: () => void) {
|
|||||||
<li>Added assessments overview and assessment weighting overrides for SEQTA Engage.</li>
|
<li>Added assessments overview and assessment weighting overrides for SEQTA Engage.</li>
|
||||||
<li>Added BetterSEQTA sidebar icons to SEQTA Engage.</li>
|
<li>Added BetterSEQTA sidebar icons to SEQTA Engage.</li>
|
||||||
<li>Added runtime handlers for upcoming interactive theme.</li>
|
<li>Added runtime handlers for upcoming interactive theme.</li>
|
||||||
|
<li>Added an automatic reindex of assessments if any of a series of tracked values change (title, release state, etc). Helps keep weightings up to date.</li>
|
||||||
<li>Fixed BetterSEQTA sidebar injection issues on some pages.</li>
|
<li>Fixed BetterSEQTA sidebar injection issues on some pages.</li>
|
||||||
<li>Tweak Theme of the Month popup making it more clear about dismissals and respecting “Don’t show again”.</li>
|
<li>Tweak Theme of the Month popup making it more clear about dismissals and respecting “Don’t show again”.</li>
|
||||||
<li>Fixed duplicate-result fixes.</li>
|
<li>Fixed duplicate-result fixes.</li>
|
||||||
|
|||||||
Reference in New Issue
Block a user