mirror of
https://github.com/BetterSEQTA/BetterSEQTA-Plus.git
synced 2026-08-28 09:11:06 +00:00
fix(home): notices via labels API
This commit is contained in:
@@ -18,6 +18,10 @@ import {
|
||||
toISODate,
|
||||
weekRangeContaining,
|
||||
} from "@/seqta/utils/Loaders/engageParentTimetable";
|
||||
import {
|
||||
noticeMatchesLabelFilter,
|
||||
resolveNoticeFilterTokens,
|
||||
} from "@/seqta/utils/notices/noticeLabelFilters";
|
||||
|
||||
export function updateEngageHomeMenuActive(isHome: boolean): void {
|
||||
const home = document.getElementById("homebutton");
|
||||
@@ -267,30 +271,17 @@ function processEngageNotices(response: any, labelArray: string[]): void {
|
||||
const noticeContainer = document.getElementById(ENGAGE_NOTICE_CONTAINER_ID);
|
||||
if (!noticeContainer) return;
|
||||
|
||||
noticeContainer.classList.remove("loading");
|
||||
noticeContainer.innerHTML = "";
|
||||
|
||||
const notices = response?.payload;
|
||||
if (!Array.isArray(notices)) {
|
||||
const emptyState = document.createElement("div");
|
||||
emptyState.classList.add("day-empty");
|
||||
const img = document.createElement("img");
|
||||
img.src = browser.runtime.getURL(LogoLight);
|
||||
const text = document.createElement("p");
|
||||
text.innerText = "No notices for today.";
|
||||
emptyState.append(img, text);
|
||||
noticeContainer.append(emptyState);
|
||||
appendEngageNoticeEmptyState(noticeContainer, "No notices for today.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!notices.length) {
|
||||
const emptyState = document.createElement("div");
|
||||
emptyState.classList.add("day-empty");
|
||||
const img = document.createElement("img");
|
||||
img.src = browser.runtime.getURL(LogoLight);
|
||||
const text = document.createElement("p");
|
||||
text.innerText = "No notices for today.";
|
||||
emptyState.append(img, text);
|
||||
noticeContainer.append(emptyState);
|
||||
appendEngageNoticeEmptyState(noticeContainer, "No notices for today.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -298,9 +289,7 @@ function processEngageNotices(response: any, labelArray: string[]): void {
|
||||
|
||||
notices.forEach((notice: any) => {
|
||||
const shouldInclude =
|
||||
settingsState.mockNotices ||
|
||||
labelArray.length === 0 ||
|
||||
labelArray.includes(JSON.stringify(notice.label));
|
||||
settingsState.mockNotices || noticeMatchesLabelFilter(notice, labelArray);
|
||||
|
||||
if (shouldInclude) {
|
||||
const colour = processEngageNoticeColor(notice.colour);
|
||||
@@ -309,9 +298,25 @@ function processEngageNotices(response: any, labelArray: string[]): void {
|
||||
}
|
||||
});
|
||||
|
||||
if (fragment.childNodes.length === 0) {
|
||||
appendEngageNoticeEmptyState(noticeContainer, "No notices for today.");
|
||||
return;
|
||||
}
|
||||
|
||||
noticeContainer.appendChild(fragment);
|
||||
}
|
||||
|
||||
function appendEngageNoticeEmptyState(container: HTMLElement, message: string) {
|
||||
const emptyState = document.createElement("div");
|
||||
emptyState.classList.add("day-empty");
|
||||
const img = document.createElement("img");
|
||||
img.src = browser.runtime.getURL(LogoLight);
|
||||
const text = document.createElement("p");
|
||||
text.innerText = message;
|
||||
emptyState.append(img, text);
|
||||
container.append(emptyState);
|
||||
}
|
||||
|
||||
function createEngageNoticeElement(
|
||||
notice: any,
|
||||
colour: string | undefined,
|
||||
@@ -613,6 +618,12 @@ async function fetchEngageNoticesFromApi(
|
||||
date: string,
|
||||
labelTokens: string[],
|
||||
): Promise<void> {
|
||||
const noticeContainer = document.getElementById(ENGAGE_NOTICE_CONTAINER_ID);
|
||||
if (noticeContainer) {
|
||||
noticeContainer.classList.add("loading");
|
||||
noticeContainer.innerHTML = "";
|
||||
}
|
||||
|
||||
try {
|
||||
const data = settingsState.mockNotices
|
||||
? getMockNotices()
|
||||
@@ -662,7 +673,7 @@ async function initEngageNoticesUi(todayFormatted: string): Promise<void> {
|
||||
const noticeContainer = document.getElementById(ENGAGE_NOTICE_CONTAINER_ID);
|
||||
if (!noticeContainer) return;
|
||||
|
||||
let labelFilterValues: string[] = [];
|
||||
let prefsPayload: unknown = [];
|
||||
try {
|
||||
const prefsRes = await fetch(`${location.origin}/seqta/parent/load/prefs?`, {
|
||||
method: "POST",
|
||||
@@ -671,21 +682,15 @@ async function initEngageNoticesUi(todayFormatted: string): Promise<void> {
|
||||
body: JSON.stringify({ asArray: true, request: "userPrefs" }),
|
||||
});
|
||||
const prefs = await prefsRes.json();
|
||||
const payload = prefs?.payload;
|
||||
if (Array.isArray(payload)) {
|
||||
labelFilterValues = payload
|
||||
.filter((item: { name?: string }) => item.name === "notices.filters")
|
||||
.map((item: { value?: string }) => item.value)
|
||||
.filter((v): v is string => typeof v === "string");
|
||||
}
|
||||
prefsPayload = prefs?.payload ?? [];
|
||||
} catch {
|
||||
labelFilterValues = [];
|
||||
prefsPayload = [];
|
||||
}
|
||||
|
||||
const labelTokens =
|
||||
labelFilterValues.length > 0
|
||||
? String(labelFilterValues[0]).split(" ").filter(Boolean)
|
||||
: [];
|
||||
const labelTokens = await resolveNoticeFilterTokens(
|
||||
prefsPayload,
|
||||
`${location.origin}/seqta/parent/load/notices`,
|
||||
);
|
||||
|
||||
const dateControl = document.getElementById(ENGAGE_NOTICES_DATE_ID);
|
||||
if (dateControl) {
|
||||
@@ -696,8 +701,6 @@ async function initEngageNoticesUi(todayFormatted: string): Promise<void> {
|
||||
|
||||
const cleanup = bindEngageNoticesDateInput(labelTokens, todayFormatted);
|
||||
engageMergeNoticeCleanup(cleanup);
|
||||
|
||||
noticeContainer.classList.remove("loading");
|
||||
}
|
||||
|
||||
function engageMergeNoticeCleanup(noticeCleanup: () => void): void {
|
||||
|
||||
@@ -20,6 +20,10 @@ import {
|
||||
filterAssessmentsForActiveSubjects,
|
||||
subjectsWithUpcomingAssessments,
|
||||
} from "@/plugins/built-in/assessmentsOverview/utils";
|
||||
import {
|
||||
noticeMatchesLabelFilter,
|
||||
resolveNoticeFilterTokens,
|
||||
} from "@/seqta/utils/notices/noticeLabelFilters";
|
||||
|
||||
let LessonInterval: any;
|
||||
let currentSelectedDate = new Date();
|
||||
@@ -131,33 +135,20 @@ export async function loadHomePage() {
|
||||
upcomingItems.classList.remove("loading");
|
||||
}
|
||||
|
||||
const labelArray = prefs.payload
|
||||
.filter((item: any) => item.name === "notices.filters")
|
||||
.map((item: any) => item.value);
|
||||
const labelTokens = await resolveNoticeFilterTokens(
|
||||
prefs.payload,
|
||||
`${location.origin}/seqta/student/load/notices?`,
|
||||
);
|
||||
|
||||
const noticeContainer = document.getElementById("notice-container");
|
||||
if (noticeContainer) {
|
||||
if (labelArray.length > 0) {
|
||||
const dateControl = document.querySelector(
|
||||
'input[type="date"]',
|
||||
) as HTMLInputElement;
|
||||
if (dateControl) {
|
||||
dateControl.value = TodayFormatted;
|
||||
setupNotices(labelArray[0].split(" "), TodayFormatted);
|
||||
}
|
||||
noticeContainer.classList.remove("loading");
|
||||
} else {
|
||||
noticeContainer.classList.remove("loading");
|
||||
noticeContainer.innerHTML = "";
|
||||
const emptyState = document.createElement("div");
|
||||
emptyState.classList.add("day-empty");
|
||||
const img = document.createElement("img");
|
||||
img.src = browser.runtime.getURL(LogoLight);
|
||||
const text = document.createElement("p");
|
||||
text.innerText = "No notices available.";
|
||||
emptyState.append(img, text);
|
||||
noticeContainer.append(emptyState);
|
||||
}
|
||||
setupNotices(labelTokens, TodayFormatted);
|
||||
}
|
||||
|
||||
return cleanup;
|
||||
@@ -248,6 +239,12 @@ function setupNotices(labelArray: string[], date: string) {
|
||||
) as HTMLInputElement;
|
||||
|
||||
const fetchNotices = async (date: string) => {
|
||||
const container = document.getElementById("notice-container");
|
||||
if (container) {
|
||||
container.classList.add("loading");
|
||||
container.innerHTML = "";
|
||||
}
|
||||
|
||||
try {
|
||||
const data = settingsState.mockNotices
|
||||
? getMockNotices()
|
||||
@@ -255,13 +252,13 @@ function setupNotices(labelArray: string[], date: string) {
|
||||
await fetch(`${location.origin}/seqta/student/load/notices?`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json; charset=utf-8" },
|
||||
credentials: "include",
|
||||
body: JSON.stringify({ date }),
|
||||
})
|
||||
).json();
|
||||
|
||||
processNotices(data, labelArray);
|
||||
} catch {
|
||||
// Notices failed to load; processNotices will show empty state if container exists
|
||||
processNotices({ payload: [] }, labelArray);
|
||||
}
|
||||
};
|
||||
@@ -294,30 +291,17 @@ function processNotices(response: any, labelArray: string[]) {
|
||||
const NoticeContainer = document.getElementById("notice-container");
|
||||
if (!NoticeContainer) return;
|
||||
|
||||
NoticeContainer.classList.remove("loading");
|
||||
NoticeContainer.innerHTML = "";
|
||||
|
||||
const notices = response?.payload;
|
||||
if (!Array.isArray(notices)) {
|
||||
const emptyState = document.createElement("div");
|
||||
emptyState.classList.add("day-empty");
|
||||
const img = document.createElement("img");
|
||||
img.src = browser.runtime.getURL(LogoLight);
|
||||
const text = document.createElement("p");
|
||||
text.innerText = "No notices for today.";
|
||||
emptyState.append(img, text);
|
||||
NoticeContainer.append(emptyState);
|
||||
appendNoticeEmptyState(NoticeContainer, "No notices for today.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!notices.length) {
|
||||
const emptyState = document.createElement("div");
|
||||
emptyState.classList.add("day-empty");
|
||||
const img = document.createElement("img");
|
||||
img.src = browser.runtime.getURL(LogoLight);
|
||||
const text = document.createElement("p");
|
||||
text.innerText = "No notices for today.";
|
||||
emptyState.append(img, text);
|
||||
NoticeContainer.append(emptyState);
|
||||
appendNoticeEmptyState(NoticeContainer, "No notices for today.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -325,8 +309,7 @@ function processNotices(response: any, labelArray: string[]) {
|
||||
|
||||
notices.forEach((notice: any) => {
|
||||
const shouldInclude =
|
||||
settingsState.mockNotices ||
|
||||
labelArray.includes(JSON.stringify(notice.label));
|
||||
settingsState.mockNotices || noticeMatchesLabelFilter(notice, labelArray);
|
||||
|
||||
if (shouldInclude) {
|
||||
const colour = processNoticeColor(notice.colour);
|
||||
@@ -335,9 +318,25 @@ function processNotices(response: any, labelArray: string[]) {
|
||||
}
|
||||
});
|
||||
|
||||
if (fragment.childNodes.length === 0) {
|
||||
appendNoticeEmptyState(NoticeContainer, "No notices for today.");
|
||||
return;
|
||||
}
|
||||
|
||||
NoticeContainer.appendChild(fragment);
|
||||
}
|
||||
|
||||
function appendNoticeEmptyState(container: HTMLElement, message: string) {
|
||||
const emptyState = document.createElement("div");
|
||||
emptyState.classList.add("day-empty");
|
||||
const img = document.createElement("img");
|
||||
img.src = browser.runtime.getURL(LogoLight);
|
||||
const text = document.createElement("p");
|
||||
text.innerText = message;
|
||||
emptyState.append(img, text);
|
||||
container.append(emptyState);
|
||||
}
|
||||
|
||||
function processNoticeColor(colour: string): string | undefined {
|
||||
if (typeof colour === "string") {
|
||||
const rgb = GetThresholdOfColor(colour);
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
type PrefEntry = { name?: string; value?: unknown };
|
||||
|
||||
/** Parse `notices.filters` pref (space-separated label IDs). */
|
||||
export function parseNoticesFilterPref(prefsPayload: unknown): string[] {
|
||||
if (!Array.isArray(prefsPayload)) return [];
|
||||
const values = (prefsPayload as PrefEntry[])
|
||||
.filter((item) => item?.name === "notices.filters")
|
||||
.map((item) => item?.value)
|
||||
.filter((v): v is string => typeof v === "string");
|
||||
if (values.length === 0) return [];
|
||||
return String(values[0]).split(" ").filter(Boolean);
|
||||
}
|
||||
|
||||
/** Label IDs from `load/notices` with `{ mode: "labels" }`. */
|
||||
export async function fetchNoticeLabelIds(noticesUrl: string): Promise<string[]> {
|
||||
try {
|
||||
const res = await fetch(noticesUrl, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json; charset=utf-8" },
|
||||
credentials: "include",
|
||||
body: JSON.stringify({ mode: "labels" }),
|
||||
});
|
||||
if (!res.ok) return [];
|
||||
const json = (await res.json()) as { payload?: Array<{ id?: number }> };
|
||||
const payload = json?.payload;
|
||||
if (!Array.isArray(payload)) return [];
|
||||
return payload
|
||||
.map((entry) => entry?.id)
|
||||
.filter((id): id is number => typeof id === "number" && !Number.isNaN(id))
|
||||
.map(String);
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/** Pref filters when set; otherwise all label IDs from the labels API (native SEQTA home). */
|
||||
export async function resolveNoticeFilterTokens(
|
||||
prefsPayload: unknown,
|
||||
noticesUrl: string,
|
||||
): Promise<string[]> {
|
||||
const fromPref = parseNoticesFilterPref(prefsPayload);
|
||||
if (fromPref.length > 0) return fromPref;
|
||||
return await fetchNoticeLabelIds(noticesUrl);
|
||||
}
|
||||
|
||||
export function normalizeNoticeLabelId(label: unknown): string | null {
|
||||
if (typeof label === "number" && !Number.isNaN(label)) {
|
||||
return String(label);
|
||||
}
|
||||
if (typeof label === "string" && label.trim()) {
|
||||
return label.trim();
|
||||
}
|
||||
if (label && typeof label === "object") {
|
||||
const obj = label as Record<string, unknown>;
|
||||
if (typeof obj.id === "number" && !Number.isNaN(obj.id)) {
|
||||
return String(obj.id);
|
||||
}
|
||||
if (typeof obj.id === "string" && obj.id.trim()) {
|
||||
return obj.id.trim();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function noticeMatchesLabelFilter(
|
||||
notice: { label?: unknown },
|
||||
filterTokens: string[],
|
||||
): boolean {
|
||||
if (filterTokens.length === 0) return true;
|
||||
const id = normalizeNoticeLabelId(notice?.label);
|
||||
if (id !== null && filterTokens.includes(id)) return true;
|
||||
return filterTokens.includes(JSON.stringify(notice?.label));
|
||||
}
|
||||
Reference in New Issue
Block a user