import { addExtensionSettings } from "@/seqta/utils/Adders/AddExtensionSettings"; import { isSeqtaEngageExperience } from "@/seqta/utils/isSeqtaEngage"; import { loadHomePage } from "@/seqta/utils/Loaders/LoadHomePage"; import { SendNewsPage } from "@/seqta/utils/SendNewsPage"; import { setupSettingsButton } from "@/seqta/utils/setupSettingsButton"; import { GetThresholdOfColor } from "@/seqta/ui/colors/getThresholdColour"; import { appendBackgroundToUI } from "./ImageBackgrounds"; import stringToHTML from "@/seqta/utils/stringToHTML"; import { settingsState } from "@/seqta/utils/listeners/SettingsState"; import { updateAllColors } from "./colors/Manager"; import { delay } from "@/seqta/utils/delay"; let cachedUserInfo: any = null; let LightDarkModeSnakeEggButton = 0; let sidebarAccessibilityObserver: MutationObserver | null = null; let sidebarTabOrderAnimationFrame: number | null = null; let sidebarAccessibilityListenersAttached = false; export async function getUserInfo() { if (cachedUserInfo) return cachedUserInfo; try { const response = await fetch(`${location.origin}/seqta/student/login`, { method: "POST", headers: { "Content-Type": "application/json; charset=utf-8", }, body: JSON.stringify({ mode: "normal", query: null, redirect_url: location.origin, }), }); cachedUserInfo = (await response.json()).payload; return cachedUserInfo; } catch (error) { console.error("[BetterSEQTA+] Failed to get user info:", error); throw error; } } export async function AddBetterSEQTAElements() { if (isSeqtaEngageExperience()) { addExtensionSettings(); return; } if (settingsState.onoff) { if (settingsState.DarkMode) { document.documentElement.classList.add("dark"); } const fragment = document.createDocumentFragment(); const menu = document.getElementById("menu")!; const menuList = menu.firstChild as HTMLElement; createHomeButton(fragment, menuList); createNewsButton(fragment, menu); menuList.insertBefore(fragment, menuList.firstChild); try { await Promise.all([ appendBackgroundToUI(), handleUserInfo(), handleStudentData(), ]); } catch (error) { console.error("[BetterSEQTA+] Failed to initialize UI elements:", error); } setupEventListeners(); await addDarkLightToggle(); customizeMenuToggle(); setupSidebarAccessibility(); } addExtensionSettings(); await createSettingsButton(); setupSettingsButton(); } function createHomeButton(fragment: DocumentFragment, _: HTMLElement) { const container = document.getElementById("content")!; const div = document.createElement("div"); div.classList.add("titlebar"); container.append(div); fragment.appendChild( stringToHTML( /* html */ `
  • `, ).firstChild!, ); } async function handleUserInfo() { try { updateUserInfo(await getUserInfo()); } catch (error) { console.error("[BetterSEQTA+] Failed to handle user info:", error); } } function updateUserInfo(info: { basic: boolean; clientIP: string[] | null; email: string | null; id: number | null; lastAccessedTime: number | null; meta: { code: string | null; governmentID: string | null; }; personUUID: string | null; status: number | null; synergeticCommunityUrl: string | null; type: string | null; userCode: string | null; userDesc: string | null; userName: string | null; }) { const titlebar = document.getElementsByClassName("titlebar")[0]; const metadata = [info.meta.code, info.meta.governmentID] .filter((value): value is string => Boolean(value)) .join(" // "); const displayName = info.userDesc || info.userName || ""; titlebar.append( stringToHTML(/* html */ `
    `).firstChild!, ); titlebar.append( stringToHTML(/* html */ `
    ${displayName ? `

    ${displayName}

    ` : ""}
    ${metadata ? `

    ${metadata}

    ` : ""}
    `).firstChild!, ); document .getElementById("logouttooltip")! .appendChild(document.getElementsByClassName("logout")[0]); } async function handleStudentData() { try { const response = await fetch( `${location.origin}/seqta/student/load/message/people`, { method: "POST", headers: { "Content-Type": "application/json; charset=utf-8", }, body: JSON.stringify({ mode: "student" }), }, ); await updateStudentInfo((await response.json()).payload); } catch (error) { console.error("[BetterSEQTA+] Failed to handle student data:", error); } } async function updateStudentInfo(students: any) { const info = await getUserInfo(); const index = students.findIndex( (person: any) => person.firstname == info.userDesc.split(" ")[0] && person.surname == info.userDesc.split(" ")[1], ); const houseelement = document.getElementsByClassName( "userInfohouse", )[0] as HTMLElement | undefined; if (!houseelement) return; const student = students[index] ?? {}; let text = ""; if (student.house) { text = `${student.year ?? ""}${student.house}`; if (student.house_colour) { houseelement.style.background = student.house_colour; try { const colorresult = GetThresholdOfColor(student.house_colour); houseelement.style.color = colorresult && colorresult > 300 ? "black" : "white"; } catch { // Invalid color format, leave text color as default } } } else if (student.year) { text = student.year; } houseelement.innerText = text; houseelement.style.display = text ? "block" : "none"; } function createNewsButton(fragment: DocumentFragment, menu: HTMLElement) { fragment.appendChild( stringToHTML( '
  • ', ).firstChild!, ); const iconCover = document.createElement("div"); iconCover.classList.add("icon-cover"); iconCover.id = "icon-cover"; menu.appendChild(iconCover); } function setupEventListeners() { const menuCover = document.querySelector("#icon-cover"); const homebutton = document.getElementById("homebutton"); const newsbutton = document.getElementById("newsbutton"); const activateMenuAction = (button: HTMLElement, action: () => void) => { if ( button.classList.contains("draggable") || button.classList.contains("active") ) { return; } action(); }; homebutton?.addEventListener("click", function () { activateMenuAction(homebutton, () => { loadHomePage(); }); }); newsbutton?.addEventListener("click", function () { activateMenuAction(newsbutton, () => { SendNewsPage(); }); }); menuCover?.addEventListener("click", function () { location.href = "../#?page=/home"; loadHomePage(); ( document.getElementById("menu")!.firstChild! as HTMLElement ).classList.remove("noscroll"); }); } async function createSettingsButton() { document.getElementById("content")!.append( stringToHTML(/* html */ ` `).firstChild!, ); } function GetLightDarkModeString() { return settingsState.DarkMode ? "Switch to light theme" : "Switch to dark theme"; } async function addDarkLightToggle() { const SUN_ICON_SVG = /* html */ ``; const MOON_ICON_SVG = /* html */ ``; document.getElementById("content")!.append( stringToHTML(/* html */ ` `).firstChild!, ); updateAllColors(); const lightDarkModeButtonElement = document.getElementById( "LightDarkModeButton", )!; lightDarkModeButtonElement.addEventListener("click", async () => { const darklightText = document.getElementById("darklighttooliptext"); LightDarkModeSnakeEggButton += 1; if (LightDarkModeSnakeEggButton >= 10) { window.open("https://www.youtube.com/watch?v=dQw4w9WgXcQ", "_blank"); LightDarkModeSnakeEggButton = 0; } if ( settingsState.originalDarkMode !== undefined && settingsState.selectedTheme ) { darklightText!.innerText = "Locked by current theme"; await delay(1000); darklightText!.innerText = GetLightDarkModeString(); return; } settingsState.DarkMode = !settingsState.DarkMode; updateAllColors(); const svgElement = lightDarkModeButtonElement.querySelector("svg")!; svgElement.innerHTML = settingsState.DarkMode ? SUN_ICON_SVG : MOON_ICON_SVG; darklightText!.innerText = GetLightDarkModeString(); }); } function customizeMenuToggle() { const menuToggle = document.getElementById("menuToggle")!; menuToggle.innerHTML = ""; for (let i = 0; i < 3; i++) { const line = document.createElement("div"); line.className = "hamburger-line"; menuToggle.appendChild(line); } } function setupSidebarAccessibility() { updateSidebarAccessibility(); const menu = document.getElementById("menu"); if (!menu) return; sidebarAccessibilityObserver?.disconnect(); sidebarAccessibilityObserver = new MutationObserver(() => { scheduleSidebarAccessibilityUpdate(); }); sidebarAccessibilityObserver.observe(menu, { subtree: true, childList: true, attributes: true, attributeFilter: ["class", "style"], }); if (!sidebarAccessibilityListenersAttached) { document.addEventListener("keydown", handleSidebarKeyboardActivation); sidebarAccessibilityListenersAttached = true; } } function scheduleSidebarAccessibilityUpdate() { if (sidebarTabOrderAnimationFrame !== null) { cancelAnimationFrame(sidebarTabOrderAnimationFrame); } sidebarTabOrderAnimationFrame = requestAnimationFrame(() => { sidebarTabOrderAnimationFrame = null; updateSidebarAccessibility(); }); } function handleSidebarKeyboardActivation(event: KeyboardEvent) { const target = event.target; if (!(target instanceof HTMLElement)) return; const menuItem = target.closest("#menu li, #menu section") as | HTMLElement | null; if (!menuItem || target !== menuItem) return; if (event.key === "Tab") { const menu = document.getElementById("menu"); if (!menu) return; const visibleList = getVisibleSidebarList(menu); if (!visibleList) return; const visibleEntries = getDirectSidebarEntries(visibleList); if (visibleEntries.length === 0) return; const boundaryEntry = event.shiftKey ? visibleEntries[0] : visibleEntries[visibleEntries.length - 1]; if (boundaryEntry !== menuItem) return; const parentEntry = getSidebarListParentEntry(visibleList); if (!parentEntry) return; event.preventDefault(); parentEntry.classList.remove("active"); scheduleSidebarAccessibilityUpdate(); requestAnimationFrame(() => { parentEntry.focus(); }); return; } if (event.key !== "Enter" && event.key !== " ") return; event.preventDefault(); const childSubmenu = menuItem.querySelector(":scope > .sub > ul") as | HTMLElement | null; menuItem.click(); scheduleSidebarAccessibilityUpdate(); if (childSubmenu) { focusFirstSidebarSubmenuEntry(menuItem); } } function updateSidebarAccessibility() { const menu = document.getElementById("menu"); if (!menu) return; const visibleEntries = new Set(getVisibleSidebarEntries(menu)); const menuEntries = menu.querySelectorAll("li.item, section.item, li, section"); for (const entry of menuEntries) { if (!(entry instanceof HTMLElement)) continue; const label = entry.querySelector(":scope > label") as HTMLLabelElement | null; if (!label) continue; const childSubmenu = entry.querySelector(":scope > .sub") as HTMLElement | null; const isHidden = entry.offsetParent === null || window.getComputedStyle(entry).display === "none" || window.getComputedStyle(label).display === "none" || !visibleEntries.has(entry); if (isHidden) { entry.tabIndex = -1; label.tabIndex = -1; entry.setAttribute("aria-hidden", "true"); label.setAttribute("aria-hidden", "true"); if (childSubmenu) { childSubmenu.setAttribute("aria-hidden", "true"); } continue; } entry.tabIndex = 0; label.tabIndex = -1; entry.removeAttribute("aria-hidden"); label.removeAttribute("aria-hidden"); if (!entry.hasAttribute("role")) { entry.setAttribute("role", "button"); } const accessibleLabel = label.textContent?.trim(); if (accessibleLabel) { entry.setAttribute("aria-label", accessibleLabel); } if (childSubmenu) { const isExpanded = entry.classList.contains("active"); entry.setAttribute("aria-expanded", String(isExpanded)); childSubmenu.setAttribute("aria-hidden", String(!isExpanded)); } else { entry.removeAttribute("aria-expanded"); } } } function getVisibleSidebarEntries(menu = document.getElementById("menu")) { if (!menu) return [] as HTMLElement[]; const visibleList = getVisibleSidebarList(menu); if (!visibleList) return [] as HTMLElement[]; return getDirectSidebarEntries(visibleList); } function getDirectSidebarEntries(list: HTMLElement) { return Array.from(list.querySelectorAll(":scope > li, :scope > section")).filter( (entry): entry is HTMLElement => entry instanceof HTMLElement, ); } function getVisibleSidebarList(menu: HTMLElement) { let currentList = menu.querySelector(":scope > ul") as HTMLElement | null; while (currentList) { const activeSubmenuParent = currentList.querySelector( ":scope > li.hasChildren.active, :scope > section.hasChildren.active", ) as HTMLElement | null; if (!activeSubmenuParent) { return currentList; } const nextList = activeSubmenuParent.querySelector( ":scope > .sub > ul", ) as HTMLElement | null; if (!nextList) { return currentList; } currentList = nextList; } return null; } function getSidebarListParentEntry(list: HTMLElement) { return list.closest(".sub")?.parentElement instanceof HTMLElement ? (list.closest(".sub")!.parentElement as HTMLElement) : null; } function focusFirstSidebarSubmenuEntry(parentEntry: HTMLElement) { const menu = document.getElementById("menu"); if (!menu) return; requestAnimationFrame(() => { requestAnimationFrame(() => { if (!parentEntry.classList.contains("active")) return; const visibleList = getVisibleSidebarList(menu); if (!visibleList || getSidebarListParentEntry(visibleList) !== parentEntry) { return; } const firstEntry = getDirectSidebarEntries(visibleList).find( (entry) => entry.offsetParent !== null && window.getComputedStyle(entry).display !== "none", ); firstEntry?.focus({ preventScroll: true }); }); }); }