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 });
});
});
}