import { animate, stagger } from "motion"; import browser from "webextension-polyfill"; import LogoLight from "@/resources/icons/betterseqta-light-icon.png"; import assessmentsicon from "@/seqta/icons/assessmentsIcon"; import coursesicon from "@/seqta/icons/coursesIcon"; import { GetThresholdOfColor } from "@/seqta/ui/colors/getThresholdColour"; import { convertTo12HourFormat } from "../convertTo12HourFormat"; import { delay } from "../delay"; import { settingsState } from "../listeners/SettingsState"; import stringToHTML from "../stringToHTML"; import { renderShortcuts } from "@/seqta/utils/Render/renderShortcuts"; import { CreateElement } from "@/seqta/utils/CreateEnable/CreateElement"; import { FilterUpcomingAssessments } from "@/seqta/utils/FilterUpcomingAssessments"; import { getMockNotices } from "@/seqta/ui/dev/hideSensitiveContent"; import { setupFixedTooltips } from "@/seqta/utils/fixedTooltip"; let LessonInterval: any; let currentSelectedDate = new Date(); let loadingTimeout: any; export async function loadHomePage() { console.info("[BetterSEQTA+] Started Loading Home Page"); currentSelectedDate = new Date(); await delay(10); document.title = "Home ― SEQTA Learn"; const element = document.querySelector("[data-key=home]"); element?.classList.add("active"); const main = document.getElementById("main"); if (!main) return; main.innerHTML = ""; main.appendChild( stringToHTML(`
`).firstChild!, ); const homeContainer = document.getElementById("home-root"); if (!homeContainer) return; const skeletonStructure = stringToHTML(/* html */ `

Today's Lessons

Upcoming Assessments

Notices

`); homeContainer.appendChild(skeletonStructure.firstChild!); if (settingsState.animations) { animate( ".home-container > div", { opacity: [0, 1], y: [10, 0], scale: [0.99, 1] }, { delay: stagger(0.15, { startDelay: 0.1 }), type: "spring", stiffness: 341, damping: 20, mass: 1, }, ); } const cleanup = setupTimetableListeners(); renderShortcuts(); const TodayFormatted = formatDate(new Date()); const [assessments, classes, prefs] = await Promise.all([ GetUpcomingAssessments(), GetActiveClasses(), fetch(`${location.origin}/seqta/student/load/prefs?`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ asArray: true, request: "userPrefs" }), }).then((res) => res.json()), ]); callHomeTimetable(TodayFormatted, true); const activeClass = classes.find((c: any) => c.hasOwnProperty("active")); const activeSubjects = activeClass?.subjects || []; const activeSubjectCodes = activeSubjects.map((s: any) => s.code); const currentAssessments = assessments .filter((a: any) => activeSubjectCodes.includes(a.code)) .sort(comparedate); const upcomingItems = document.getElementById("upcoming-items"); if (upcomingItems) { await CreateUpcomingSection(currentAssessments, activeSubjects); upcomingItems.classList.remove("loading"); } const labelArray = prefs.payload .filter((item: any) => item.name === "notices.filters") .map((item: any) => item.value); 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); } } return cleanup; } async function GetUpcomingAssessments() { try { return fetch(`${location.origin}/seqta/student/assessment/list/upcoming?`, { method: "POST", headers: { "Content-Type": "application/json; charset=utf-8", }, body: JSON.stringify({ student: 69 }), }) .then((result) => result.json()) .then((response) => response.payload); } catch (error) { console.error("[BetterSEQTA+] Failed to get upcoming assessments:", error); return []; } } function setupTimetableListeners() { const listeners: Array<() => void> = []; const timetableBack = document.getElementById("home-timetable-back"); const timetableForward = document.getElementById("home-timetable-forward"); function changeTimetable(value: number) { if (loadingTimeout) { clearTimeout(loadingTimeout); } loadingTimeout = setTimeout(() => { const dayContainer = document.getElementById("day-container"); if (dayContainer) { dayContainer.classList.add("loading"); dayContainer.innerHTML = ""; } }, 200); currentSelectedDate.setDate(currentSelectedDate.getDate() + value); const formattedDate = formatDate(currentSelectedDate); callHomeTimetable(formattedDate, true); SetTimetableSubtitle(); } const backHandler = () => changeTimetable(-1); const forwardHandler = () => changeTimetable(1); timetableBack?.addEventListener("click", backHandler); timetableForward?.addEventListener("click", forwardHandler); listeners.push( () => timetableBack?.removeEventListener("click", backHandler), () => timetableForward?.removeEventListener("click", forwardHandler), ); return () => listeners.forEach((cleanup) => cleanup()); } function formatDate(date: Date): string { const year = date.getFullYear(); const month = (date.getMonth() + 1).toString().padStart(2, "0"); const day = date.getDate().toString().padStart(2, "0"); return `${year}-${month}-${day}`; } async function GetActiveClasses() { try { const response = await fetch( `${location.origin}/seqta/student/load/subjects?`, { method: "POST", headers: { "Content-Type": "application/json; charset=utf-8" }, body: JSON.stringify({}), }, ); return (await response.json()).payload; } catch (error) { console.error("[BetterSEQTA+] Failed to get active classes:", error); return []; } } function setupNotices(labelArray: string[], date: string) { const dateControl = document.querySelector( 'input[type="date"]', ) as HTMLInputElement; const fetchNotices = async (date: string) => { try { const data = settingsState.mockNotices ? getMockNotices() : await ( await fetch(`${location.origin}/seqta/student/load/notices?`, { method: "POST", headers: { "Content-Type": "application/json; charset=utf-8" }, body: JSON.stringify({ date }), }) ).json(); processNotices(data, labelArray); } catch { // Notices failed to load; processNotices will show empty state if container exists processNotices({ payload: [] }, labelArray); } }; const debouncedInputChange = debounce((e: Event) => { fetchNotices((e.target as HTMLInputElement).value); }, 250); dateControl?.addEventListener("input", debouncedInputChange); fetchNotices(date); return () => dateControl?.removeEventListener("input", debouncedInputChange); } function debounce any>( func: T, wait: number, ): (...args: Parameters) => void { let timeout: any; return (...args: Parameters) => { clearTimeout(timeout); timeout = setTimeout(() => func(...args), wait); }; } function comparedate(obj1: any, obj2: any) { return obj1.date < obj2.date ? -1 : obj1.date > obj2.date ? 1 : 0; } function processNotices(response: any, labelArray: string[]) { const NoticeContainer = document.getElementById("notice-container"); if (!NoticeContainer) return; 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); 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); return; } const fragment = document.createDocumentFragment(); notices.forEach((notice: any) => { const shouldInclude = settingsState.mockNotices || labelArray.includes(JSON.stringify(notice.label)); if (shouldInclude) { const colour = processNoticeColor(notice.colour); const noticeElement = createNoticeElement(notice, colour); fragment.appendChild(noticeElement); } }); NoticeContainer.appendChild(fragment); } function processNoticeColor(colour: string): string | undefined { if (typeof colour === "string") { const rgb = GetThresholdOfColor(colour); if (rgb < 100 && settingsState.DarkMode) { return undefined; } } return colour; } function createNoticeElement(notice: any, colour: string | undefined): Node { const textPreview = notice.contents .replace(/<[^>]*>/g, "") .replace(/\[\[[\w]+[:][\w]+[\]\]]+/g, "") .replace(/\s+/g, " ") .trim() .substring(0, 150) + (notice.contents.length > 150 ? "..." : ""); const noticeId = `notice-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; const htmlContent = `
${notice.label_title || "General"} ${notice.staff}

${notice.title}

${textPreview}
`; const element = stringToHTML(htmlContent).firstChild as HTMLElement; element.addEventListener("click", () => openNoticeModal(notice, colour, element), ); return element; } function openNoticeModal( notice: any, colour: string | undefined, sourceElement: HTMLElement, ) { const cleanContent = notice.contents .replace(/\[\[[\w]+[:][\w]+[\]\]]+/g, "") .replace(/ +/, " "); document.getElementById("notice-modal")?.remove(); const sourceRect = sourceElement.getBoundingClientRect(); let scrollY = Math.round(window.scrollY); let scrollX = Math.round(window.scrollX); let sourceLeft = sourceRect.left; let sourceTop = sourceRect.top; let sourceWidth = sourceRect.width; let sourceHeight = sourceRect.height; const modalHtml = `
${notice.label_title || "General"} ${notice.staff}

${notice.title}

${cleanContent}
`; const modal = stringToHTML(modalHtml).firstChild as HTMLElement; const transitionContainer = modal.querySelector( ".notice-modal-transition", ) as HTMLElement; const unifiedContent = modal.querySelector( ".notice-unified-content", ) as HTMLElement; const closeBtn = modal.querySelector(".notice-close-btn") as HTMLElement; document.body.appendChild(modal); sourceElement.setAttribute("data-transitioning", "true"); sourceElement.style.opacity = "0"; sourceElement.style.transform = "scale(0.95)"; const viewportWidth = window.innerWidth; const viewportHeight = window.innerHeight; let targetWidth = Math.round( Math.min(Math.max(sourceWidth, 800), viewportWidth - 40), ); const tempMeasureDiv = document.createElement("div"); tempMeasureDiv.style.position = "absolute"; tempMeasureDiv.style.left = "-9999px"; tempMeasureDiv.style.width = targetWidth + "px"; tempMeasureDiv.style.visibility = "hidden"; tempMeasureDiv.innerHTML = `
${notice.label_title || "General"} ${notice.staff}

${notice.title}

${cleanContent}
`; document.body.appendChild(tempMeasureDiv); const measuredHeight = tempMeasureDiv.firstElementChild!.getBoundingClientRect().height; document.body.removeChild(tempMeasureDiv); let targetHeight = Math.round( Math.min(Math.max(measuredHeight + 32, 200), viewportHeight * 0.9), ); let targetLeft = Math.round((viewportWidth - targetWidth) / 2); let targetTop = Math.round((viewportHeight - targetHeight) / 2) + scrollY; const closeModal = () => { window.removeEventListener("resize", handleResize); document.removeEventListener("keydown", handleEscape); if (!settingsState.animations) { modal.remove(); sourceElement.style.opacity = "1"; sourceElement.style.transform = ""; sourceElement.removeAttribute("data-transitioning"); return; } animate( modal, { backgroundColor: ["rgba(0, 0, 0, 0.5)", "rgba(0, 0, 0, 0)"], backdropFilter: ["blur(4px)", "blur(0px)"], }, { duration: 0.2 }, ); animate( transitionContainer, { opacity: [1, 0] }, { duration: 0.2, delay: 0.3 }, ); sourceElement.style.opacity = "1"; sourceElement.style.transform = ""; modal.style.pointerEvents = "none"; animate( transitionContainer, { left: [targetLeft + scrollX, sourceLeft + scrollX], top: [targetTop, sourceTop + scrollY], width: [targetWidth, sourceWidth], height: [targetHeight, sourceHeight], scale: [1, 1], }, { duration: 0.35, type: "spring", stiffness: 400, damping: 35, }, ).finished.then(async () => { modal.remove(); sourceElement.removeAttribute("data-transitioning"); }); }; closeBtn?.addEventListener("click", closeModal); modal?.addEventListener("click", (e) => { if (e.target === modal) { closeModal(); } }); const handleEscape = (e: KeyboardEvent) => { if (e.key === "Escape") { closeModal(); document.removeEventListener("keydown", handleEscape); window.removeEventListener("resize", handleResize); } }; document.addEventListener("keydown", handleEscape); const handleResize = () => { const newSourceRect = sourceElement.getBoundingClientRect(); const newScrollY = Math.round(window.scrollY); const newScrollX = Math.round(window.scrollX); // Get the current scale applied to the source element and compensate for it const computedStyle = getComputedStyle(sourceElement); const transform = computedStyle.transform; let scaleX = 1, scaleY = 1; if (transform && transform !== "none") { const matrix = transform.match(/matrix.*\((.+)\)/); if (matrix) { const values = matrix[1].split(", "); scaleX = parseFloat(values[0]); scaleY = parseFloat(values[3]); } } // Apply inverse scale to get true original dimensions and positions const newSourceWidth = newSourceRect.width / scaleX; const newSourceHeight = newSourceRect.height / scaleY; // Calculate position shift due to center-based scaling const deltaX = (newSourceWidth - newSourceRect.width) / 2; const deltaY = (newSourceHeight - newSourceRect.height) / 2; const newSourceLeft = newSourceRect.left - deltaX; const newSourceTop = newSourceRect.top - deltaY; const newViewportWidth = window.innerWidth; const newViewportHeight = window.innerHeight; const newTargetWidth = Math.round( Math.min(Math.max(newSourceWidth, 800), newViewportWidth - 40), ); const currentHeight = unifiedContent.getBoundingClientRect().height; const newTargetHeight = Math.round( Math.min(Math.max(currentHeight + 32, 200), newViewportHeight * 0.9), ); const newTargetLeft = Math.round((newViewportWidth - newTargetWidth) / 2); const newTargetTop = Math.round((newViewportHeight - newTargetHeight) / 2) + newScrollY; transitionContainer.style.left = Math.round(newTargetLeft + newScrollX) + "px"; transitionContainer.style.top = Math.round(newTargetTop) + "px"; transitionContainer.style.width = Math.round(newTargetWidth) + "px"; transitionContainer.style.height = Math.round(newTargetHeight) + "px"; sourceLeft = newSourceLeft; sourceTop = newSourceTop; sourceWidth = newSourceWidth; sourceHeight = newSourceHeight; targetLeft = newTargetLeft; targetTop = newTargetTop; targetWidth = newTargetWidth; targetHeight = newTargetHeight; scrollY = newScrollY; scrollX = newScrollX; }; window.addEventListener("resize", handleResize); if (settingsState.animations) { animate(modal, { opacity: [0, 1] }, { duration: 0.2 }); animate( transitionContainer, { left: [sourceLeft + scrollX, targetLeft + scrollX], top: [sourceTop + scrollY, targetTop], width: [sourceWidth, targetWidth], height: [sourceHeight, targetHeight], scale: [1, 1], }, { duration: 0.5, type: "spring", stiffness: 280, damping: 24, }, ); unifiedContent.classList.remove("notice-card-state"); unifiedContent.classList.add("notice-modal-state"); } else { modal.style.opacity = "1"; transitionContainer.style.left = Math.round(targetLeft + scrollX) + "px"; transitionContainer.style.top = Math.round(targetTop) + "px"; transitionContainer.style.width = Math.round(targetWidth) + "px"; transitionContainer.style.height = Math.round(targetHeight) + "px"; unifiedContent.classList.remove("notice-card-state"); unifiedContent.classList.add("notice-modal-state"); } } function callHomeTimetable(date: string, change?: any) { var xhr = new XMLHttpRequest(); xhr.open("POST", `${location.origin}/seqta/student/load/timetable?`, true); xhr.setRequestHeader("Content-Type", "application/json; charset=utf-8"); xhr.onreadystatechange = function () { if (xhr.readyState !== 4) return; if (loadingTimeout) { clearTimeout(loadingTimeout); loadingTimeout = null; } const DayContainer = document.getElementById("day-container")!; var serverResponse = JSON.parse(xhr.response); let lessonArray: Array = []; if (serverResponse.payload.items.length > 0) { if (DayContainer.innerText || change) { for (let i = 0; i < serverResponse.payload.items.length; i++) { lessonArray.push(serverResponse.payload.items[i]); } lessonArray.sort(function (a, b) { return a.from.localeCompare(b.from); }); GetLessonColours().then((colours) => { for (let i = 0; i < lessonArray.length; i++) { let subjectname = lessonArray[i].type == "tutorial" ? `timetable.tutor.${lessonArray[i].tutorID}` : `timetable.subject.colour.${lessonArray[i].code}`; let subject = colours.find( (element: any) => element.name === subjectname, ); if (!subject) { lessonArray[i].colour = "--item-colour: #8e8e8e;"; } else { lessonArray[i].colour = `--item-colour: ${subject.value};`; if (GetThresholdOfColor(subject.value) > 300) { lessonArray[i].invert = true; } } lessonArray[i].from = lessonArray[i].from.substring(0, 5); lessonArray[i].until = lessonArray[i].until.substring(0, 5); if (settingsState.timeFormat === "12") { lessonArray[i].from = convertTo12HourFormat(lessonArray[i].from); lessonArray[i].until = convertTo12HourFormat( lessonArray[i].until, ); } lessonArray[i].attendanceTitle = CheckUnmarkedAttendance( lessonArray[i].attendance, ); } DayContainer.innerText = ""; for (let i = 0; i < lessonArray.length; i++) { const div = makeLessonDiv(lessonArray[i], i + 1); if (lessonArray[i].invert) { (div.firstChild! as HTMLElement).classList.add("day-inverted"); } DayContainer.append(div.firstChild as HTMLElement); } DayContainer.classList.remove("loading"); const today = new Date(); if (currentSelectedDate.getDate() == today.getDate()) { for (let i = 0; i < lessonArray.length; i++) { CheckCurrentLesson(lessonArray[i], i + 1); } CheckCurrentLessonAll(lessonArray); } }); } } else { DayContainer.innerHTML = ""; const dummyDay = document.createElement("div"); dummyDay.classList.add("day-empty"); const img = document.createElement("img"); img.src = browser.runtime.getURL(LogoLight); const text = document.createElement("p"); text.innerText = "No lessons available."; dummyDay.append(img, text); DayContainer.append(dummyDay); DayContainer.classList.remove("loading"); } }; xhr.send( JSON.stringify({ from: date, until: date, student: 69, }), ); } function CheckCurrentLessonAll(lessons: any) { LessonInterval = setInterval( function () { for (let i = 0; i < lessons.length; i++) { CheckCurrentLesson(lessons[i], i + 1); } }.bind(lessons), 60000, ); } async function CheckCurrentLesson(lesson: any, num: number) { const { from: startTime, until: endTime, code, description, room, staff, } = lesson; const currentDate = new Date(); const [startHour, startMinute] = startTime.split(":").map(Number); const [endHour, endMinute] = endTime.split(":").map(Number); const startDate = new Date(currentDate); startDate.setHours(startHour, startMinute, 0); const endDate = new Date(currentDate); endDate.setHours(endHour, endMinute, 0); const isValidTime = startDate < currentDate && endDate > currentDate; const elementId = `${code}${num}`; const element = document.getElementById(elementId); if (!element) { clearInterval(LessonInterval); return; } const isCurrentDate = currentSelectedDate.toLocaleDateString("en-au") === currentDate.toLocaleDateString("en-au"); if (isCurrentDate) { if (isValidTime) { element.classList.add("activelesson"); } else { element.classList.remove("activelesson"); } } const minutesUntilStart = Math.floor( (startDate.getTime() - currentDate.getTime()) / 60000, ); if ( minutesUntilStart !== 5 || settingsState.lessonalert || !window.Notification ) return; if (Notification.permission !== "granted") await Notification.requestPermission(); try { new Notification("Next Lesson in 5 Minutes:", { body: `Subject: ${description}${room ? `\nRoom: ${room}` : ""}${staff ? `\nTeacher: ${staff}` : ""}`, }); } catch (error) { console.error(error); } } function makeLessonDiv(lesson: any, num: number) { const { code, colour, description, staff, room, from, until, attendanceTitle, programmeID, metaID, assessments, type, } = lesson; let lessonString = `

${type == "class" ? description : type == "tutorial" ? "Tutorial" : "Unknown"}

${staff || "Unknown"}

${room || (type == "tutorial" ? "Unknown" : "N/A")}

${from || "Unknown"} - ${until || "Unknown"}

${attendanceTitle || "Unknown"}
`; if (type == "class") { if (programmeID !== 0) { lessonString += `
${assessmentsicon}
${coursesicon}
`; } if (assessments && assessments.length > 0) { const assessmentString = assessments .map( (element: any) => `

${element.title}

`, ) .join(""); lessonString += `
${assessmentString}
`; } } lessonString += "
"; const element = stringToHTML(lessonString); setupFixedTooltips(element); return element; } function buildAssessmentURL(programmeID: any, metaID: any, itemID = "") { const base = "../#?page=/assessments/"; return itemID ? `${base}${programmeID}:${metaID}&item=${itemID}` : `${base}${programmeID}:${metaID}`; } function CheckUnmarkedAttendance(lessonattendance: any) { return lessonattendance ? lessonattendance.label : " "; } async function CreateUpcomingSection(assessments: any, activeSubjects: any) { const upcomingitemcontainer = document.querySelector("#upcoming-items"); const overdueDates = []; const upcomingDates = {}; const Today = new Date(); for (let i = 0; i < assessments.length; i++) { const assessmentdue = new Date(assessments[i].due); if (assessmentdue < Today && !CheckSpecialDay(Today, assessmentdue)) { overdueDates.push(assessments[i]); assessments.splice(i, 1); i--; } } const colours = await GetLessonColours(); for (let i = 0; i < assessments.length; i++) { const subject = colours.find( (element: any) => element.name === `timetable.subject.colour.${assessments[i].code}`, ); if (!subject) { assessments[i].colour = "--item-colour: #8e8e8e;"; } else { assessments[i].colour = `--item-colour: ${subject.value};`; } } for (let i = 0; i < activeSubjects.length; i++) { const element = activeSubjects[i]; const colour = colours.find( (c: any) => c.name === `timetable.subject.colour.${element.code}`, ); if (!colour) { element.colour = "--item-colour: #8e8e8e;"; } else { element.colour = `--item-colour: ${colour.value};`; if (GetThresholdOfColor(colour.value) > 300) { element.invert = true; } } } CreateFilters(activeSubjects); for (let i = 0; i < assessments.length; i++) { const element: any = assessments[i]; if (!upcomingDates[element.due as keyof typeof upcomingDates]) { const dateObj: any = { div: CreateElement("div", "upcoming-date-container"), assessments: [], }; (upcomingDates[element.due as keyof typeof upcomingDates] as any) = dateObj; } const assessmentDateDiv = upcomingDates[element.due as keyof typeof upcomingDates]; if (assessmentDateDiv) { (assessmentDateDiv as any).assessments.push(element); } } for (var date in upcomingDates) { const assessmentdue = new Date( (upcomingDates[date as keyof typeof upcomingDates] as any).assessments[0] .due, ); const specialcase = CheckSpecialDay(Today, assessmentdue); const assessmentDate = createAssessmentDateDiv( date, upcomingDates[date as keyof typeof upcomingDates], specialcase, ); if (specialcase === "Yesterday") { upcomingitemcontainer!.insertBefore( assessmentDate, upcomingitemcontainer!.firstChild, ); } else { upcomingitemcontainer!.append(assessmentDate); } } FilterUpcomingAssessments(settingsState.subjectfilters); if (assessments.length === 0) { upcomingitemcontainer!.innerHTML = `

No assessments available.

`; } } function createAssessmentDateDiv(date: string, value: any, datecase?: any) { const options = { weekday: "long" as "long", month: "long" as "long", day: "numeric" as "numeric", }; const FormattedDate = new Date(date); const assessments = value.assessments; const container = value.div; const DateTitleDiv = document.createElement("div"); DateTitleDiv.classList.add("upcoming-date-title"); if (datecase) { const datetitle = document.createElement("h5"); datetitle.classList.add("upcoming-special-day"); datetitle.innerText = datecase; DateTitleDiv.append(datetitle); container.setAttribute("data-day", datecase); } const DateTitle = document.createElement("h5"); DateTitle.innerText = FormattedDate.toLocaleDateString("en-AU", options); DateTitleDiv.append(DateTitle); container.append(DateTitleDiv); const assessmentContainer = document.createElement("div"); assessmentContainer.classList.add("upcoming-date-assessments"); for (let i = 0; i < assessments.length; i++) { const element = assessments[i]; const item = document.createElement("div"); item.classList.add("upcoming-assessment"); item.setAttribute("data-subject", element.code); item.id = `assessment${element.id}`; item.style.cssText = element.colour; const titlediv = document.createElement("div"); titlediv.classList.add("upcoming-subject-title"); titlediv.append( stringToHTML(` `).firstChild!, ); const detailsdiv = document.createElement("div"); detailsdiv.classList.add("upcoming-details"); const detailstitle = document.createElement("h5"); detailstitle.innerText = `${element.subject} assessment`; const subject = document.createElement("p"); subject.innerText = element.title; subject.classList.add("upcoming-assessment-title"); subject.onclick = function () { document.querySelector("#menu ul")!.classList.add("noscroll"); location.href = `../#?page=/assessments/${element.programmeID}:${element.metaclassID}&item=${element.id}`; }; detailsdiv.append(detailstitle, subject); item.append(titlediv, detailsdiv); assessmentContainer.append(item); fetch(`${location.origin}/seqta/student/assessment/submissions/get`, { method: "POST", headers: { "Content-Type": "application/json; charset=utf-8" }, body: JSON.stringify({ assessment: element.id, metaclass: element.metaclassID, student: 69, }), }) .then((result) => result.json()) .then((response) => { if (response.payload.length > 0) { const assessment = document.querySelector(`#assessment${element.id}`); const submittedtext = document.createElement("div"); submittedtext.classList.add("upcoming-submittedtext"); submittedtext.innerText = "Submitted"; assessment!.append(submittedtext); } }); } container.append(assessmentContainer); return container; } function CheckSpecialDay(date1: Date, date2: Date) { if ( date1.getFullYear() === date2.getFullYear() && date1.getMonth() === date2.getMonth() && date1.getDate() - 1 === date2.getDate() ) { return "Yesterday"; } if ( date1.getFullYear() === date2.getFullYear() && date1.getMonth() === date2.getMonth() && date1.getDate() === date2.getDate() ) { return "Today"; } if ( date1.getFullYear() === date2.getFullYear() && date1.getMonth() === date2.getMonth() && date1.getDate() + 1 === date2.getDate() ) { return "Tomorrow"; } } async function GetLessonColours() { try { return fetch(`${location.origin}/seqta/student/load/prefs?`, { method: "POST", headers: { "Content-Type": "application/json; charset=utf-8" }, body: JSON.stringify({ request: "userPrefs", asArray: true, user: 69 }), }) .then((result) => result.json()) .then((response) => response.payload); } catch (error) { console.error("[BetterSEQTA+] Failed to get lesson colours:", error); return []; } } function CreateFilters(subjects: any) { const filteroptions = settingsState.subjectfilters; const filterdiv = document.querySelector("#upcoming-filters"); for (let i = 0; i < subjects.length; i++) { const element = subjects[i]; if (!Object.prototype.hasOwnProperty.call(filteroptions, element.code)) { filteroptions[element.code] = true; settingsState.subjectfilters = filteroptions; } filterdiv!.append( CreateSubjectFilter( element.code, element.colour, filteroptions[element.code], ), ); } } function CreateSubjectFilter( subjectcode: any, itemcolour: string, checked: any, ) { const label = CreateElement("label", "upcoming-checkbox-container"); label.innerText = subjectcode; const input = CreateElement("input") as HTMLInputElement; input.type = "checkbox"; input.checked = checked; input.id = `filter-${subjectcode}`; label.style.cssText = itemcolour; const span = CreateElement("span", "upcoming-checkmark"); label.append(input, span); input.addEventListener("change", function (change) { const filters = settingsState.subjectfilters; const id = (change.target as HTMLInputElement).id.split("-")[1]; filters[id] = (change.target as HTMLInputElement).checked; settingsState.subjectfilters = filters; }); return label; } function SetTimetableSubtitle() { const homelessonsubtitle = document.getElementById("home-lesson-subtitle"); if (!homelessonsubtitle) return; const date = new Date(); const isSameMonth = date.getFullYear() === currentSelectedDate.getFullYear() && date.getMonth() === currentSelectedDate.getMonth(); if (isSameMonth) { const dayDiff = date.getDate() - currentSelectedDate.getDate(); switch (dayDiff) { case 0: homelessonsubtitle.innerText = "Today's Lessons"; break; case 1: homelessonsubtitle.innerText = "Yesterday's Lessons"; break; case -1: homelessonsubtitle.innerText = "Tomorrow's Lessons"; break; default: homelessonsubtitle.innerText = formatDateString(currentSelectedDate); } } else { homelessonsubtitle.innerText = formatDateString(currentSelectedDate); } } function formatDateString(date: Date): string { return `${date.toLocaleString("en-us", { weekday: "short" })} ${date.toLocaleDateString("en-au")}`; }