diff --git a/src/css/injected.scss b/src/css/injected.scss
index 6721c53b..c12ec16e 100644
--- a/src/css/injected.scss
+++ b/src/css/injected.scss
@@ -1175,11 +1175,6 @@ div > ol:has(.uiFileHandlerWrapper) {
font-size: 20px;
font-weight: 400;
}
-.notices-container h2 {
- margin: 20px;
- font-size: 20px;
- font-weight: 400;
-}
.notice {
position: relative;
padding: 20px;
@@ -3543,3 +3538,400 @@ body {
-ms-overflow-style: none;
scrollbar-width: none !important;
}
+
+.notice-modal-content {
+ border: none !important;
+}
+
+.notice-unified-content.notice-modal-state {
+ border: none !important;
+}
+
+// Notice card hover effects for main page cards
+.notice-unified-content.notice-card-state:not([data-transitioning]) {
+ cursor: pointer;
+
+ &:hover {
+ background: var(--background-secondary) !important;
+ border-color: rgba(255, 255, 255, 0.2) !important;
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15) !important;
+ }
+
+ &:last-child {
+ margin-bottom: 0;
+ }
+}
+
+.notice-badge {
+ padding: 4px 10px;
+ border-radius: 16px;
+ font-size: 12px;
+ font-weight: 500;
+ white-space: nowrap;
+}
+
+.notice-staff {
+ font-size: 12px;
+ color: var(--text-secondary);
+ opacity: 0.7;
+}
+
+.notice-preview {
+ font-size: 14px;
+ color: var(--text-secondary);
+ line-height: 1.4;
+ display: -webkit-box;
+ -webkit-line-clamp: 2;
+ line-clamp: 2;
+ -webkit-box-orient: vertical;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+// Modal styles
+.notice-modal-overlay {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: rgba(0, 0, 0, 0.5);
+ backdrop-filter: blur(4px);
+ z-index: 10000;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 20px;
+}
+
+.notice-modal-transition {
+ position: fixed;
+ z-index: 10001;
+ transition: none; // Controlled by motion animations
+}
+
+.notice-modal-content {
+ background: var(--background-primary);
+ border-radius: 16px;
+ max-width: 600px;
+ max-height: 80vh;
+ width: 100%;
+ overflow: hidden;
+ box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3);
+ border: 1px solid rgba(255, 255, 255, 0.1);
+
+ &.notice-transitioning {
+ max-width: none;
+ max-height: none;
+ width: 100%;
+ height: 100%;
+ position: relative;
+ }
+}
+
+.notice-unified-content {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: var(--background-primary);
+ display: flex;
+ flex-direction: column;
+ padding: 16px;
+ border: 1px solid rgba(255, 255, 255, 0.1);
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+}
+
+.notice-unified-content {
+ // Override any conflicting .notice styles - unified for both states
+ h1, h2, h3, h4, h5, h6 {
+ margin: 0 !important;
+ padding: 0 !important;
+ font-weight: inherit !important;
+ color: inherit !important;
+ text-shadow: none !important;
+ }
+
+ .notice-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: flex-start;
+ flex-shrink: 0;
+ margin-bottom: 12px;
+ gap: 16px;
+ }
+
+ .notice-content-title {
+ font-size: 20px !important; // Nice middle ground - not too big, not too small
+ font-weight: 600 !important;
+ color: var(--text-primary) !important;
+ margin: 0 0 12px 0 !important;
+ line-height: 1.3 !important;
+ flex-shrink: 0;
+ }
+
+ .notice-content-body {
+ font-size: 14px !important;
+ color: var(--text-secondary) !important;
+ line-height: 1.5 !important;
+ margin: 0 !important;
+ flex: 1;
+ display: block;
+ // Force stable layout dimensions - content renders at full size always
+ min-width: 600px; // Ensure tables have consistent width for layout
+ width: 100%;
+ }
+
+ // The ONLY difference between states is clipping!
+ &.notice-card-state {
+ .notice-content-body {
+ // Clip to show only 2 lines but keep full layout
+ overflow: hidden;
+ max-height: 3em; // ~2 lines worth of height
+ }
+ }
+
+ &.notice-modal-state {
+ .notice-close-btn {
+ opacity: 1;
+ }
+
+ .notice-content-body {
+ // Show full content with scrolling
+ overflow-y: auto;
+
+ // Custom scrollbar for long content
+ &::-webkit-scrollbar {
+ width: 6px;
+ }
+
+ &::-webkit-scrollbar-track {
+ background: transparent;
+ }
+
+ &::-webkit-scrollbar-thumb {
+ background: rgba(255, 255, 255, 0.2);
+ border-radius: 3px;
+ }
+
+ &::-webkit-scrollbar-thumb:hover {
+ background: rgba(255, 255, 255, 0.3);
+ }
+
+ // Style content elements nicely
+ p {
+ margin-bottom: 12px;
+
+ &:last-child {
+ margin-bottom: 0;
+ }
+ }
+
+ a {
+ color: var(--theme-primary);
+ text-decoration: none;
+
+ &:hover {
+ text-decoration: underline;
+ }
+ }
+
+ ul, ol {
+ margin: 12px 0;
+ padding-left: 20px;
+ }
+
+ li {
+ margin-bottom: 4px;
+ }
+ }
+ }
+ }
+
+.notice-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: flex-start;
+ gap: 16px;
+}
+
+.notice-badge-row {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ flex: 1;
+}
+
+.notice-close-btn {
+ position: absolute !important;
+ top: 12px;
+ right: 12px;
+ background: var(--background-secondary);
+ border: none;
+ width: 32px;
+ height: 32px;
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ cursor: pointer;
+ font-size: 18px;
+ color: var(--text-primary);
+ transition: all 0.2s ease !important;
+ flex-shrink: 0;
+ opacity: 0;
+
+ &:hover {
+ background: var(--background-tertiary);
+ transform: scale(1.1);
+ }
+}
+
+.notice-modal-badge-row {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ flex: 1;
+}
+
+.notice-modal-badge {
+ padding: 6px 12px;
+ border-radius: 20px;
+ font-size: 13px;
+ font-weight: 500;
+ white-space: nowrap;
+}
+
+.notice-modal-staff {
+ font-size: 14px;
+ color: var(--text-secondary);
+ opacity: 0.8;
+}
+
+.notice-modal-close {
+ background: var(--background-secondary);
+ border: none;
+ width: 32px;
+ height: 32px;
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ cursor: pointer;
+ font-size: 18px;
+ color: var(--text-primary);
+ transition: all 0.2s ease;
+ flex-shrink: 0;
+
+ &:hover {
+ background: var(--background-tertiary);
+ transform: scale(1.1);
+ }
+}
+
+.notice-modal-title {
+ font-size: 24px;
+ font-weight: 600;
+ color: var(--text-primary);
+ margin: 16px 20px 20px 20px;
+ line-height: 1.3;
+ flex-shrink: 0;
+}
+
+.notice-modal-body {
+ padding: 0 20px 20px 20px;
+ font-size: 15px;
+ line-height: 1.6;
+ color: var(--text-secondary);
+ flex: 1;
+ overflow-y: auto;
+
+ // Custom scrollbar
+ &::-webkit-scrollbar {
+ width: 6px;
+ }
+
+ &::-webkit-scrollbar-track {
+ background: transparent;
+ }
+
+ &::-webkit-scrollbar-thumb {
+ background: rgba(255, 255, 255, 0.2);
+ border-radius: 3px;
+ }
+
+ &::-webkit-scrollbar-thumb:hover {
+ background: rgba(255, 255, 255, 0.3);
+ }
+
+ // Style content elements
+ p {
+ margin-bottom: 12px;
+
+ &:last-child {
+ margin-bottom: 0;
+ }
+ }
+
+ a {
+ color: var(--theme-primary);
+ text-decoration: none;
+
+ &:hover {
+ text-decoration: underline;
+ }
+ }
+
+ ul, ol {
+ margin: 12px 0;
+ padding-left: 20px;
+ }
+
+ li {
+ margin-bottom: 4px;
+ }
+}
+
+// Dark mode adjustments
+.dark {
+ .notice-card {
+ border-color: rgba(255, 255, 255, 0.05);
+
+ &:hover {
+ border-color: rgba(255, 255, 255, 0.1);
+ }
+ }
+
+ .notice-modal-content {
+ border-color: rgba(255, 255, 255, 0.05);
+ }
+}
+
+// Mobile responsiveness
+@media (max-width: 768px) {
+ .notice-modal-overlay {
+ padding: 10px;
+ }
+
+ .notice-modal-content {
+ max-height: 90vh;
+ }
+
+ .notice-modal-title {
+ font-size: 20px;
+ margin: 12px 16px 16px 16px;
+ }
+
+ .notice-modal-body {
+ padding: 0 16px 16px 16px;
+ }
+
+ .notice-card {
+ padding: 12px;
+ }
+
+ .notice-preview {
+ font-size: 13px;
+ }
+}
diff --git a/src/interface/pages/settings/general.svelte b/src/interface/pages/settings/general.svelte
index 178caac9..1273d3e5 100644
--- a/src/interface/pages/settings/general.svelte
+++ b/src/interface/pages/settings/general.svelte
@@ -328,6 +328,18 @@
/>
+
No lessons available.
@@ -212,7 +191,6 @@ export async function loadHomePage() {
}
dayContainer?.classList.remove("loading");
- // Process assessments data
const activeClass = classes.find((c: any) => c.hasOwnProperty("active"));
const activeSubjects = activeClass?.subjects || [];
const activeSubjectCodes = activeSubjects.map((s: any) => s.code);
@@ -226,7 +204,6 @@ export async function loadHomePage() {
upcomingItems.classList.remove("loading");
}
- // Process notices data
const labelArray = prefs.payload
.filter((item: any) => item.name === "notices.filters")
.map((item: any) => item.value);
@@ -271,12 +248,10 @@ function setupTimetableListeners() {
const timetableForward = document.getElementById("home-timetable-forward");
function changeTimetable(value: number) {
- // Clear any existing loading timeout
if (loadingTimeout) {
clearTimeout(loadingTimeout);
}
-
- // Only show loading state after 200ms to avoid flicker on fast connections
+
loadingTimeout = setTimeout(() => {
const dayContainer = document.getElementById("day-container");
if (dayContainer) {
@@ -284,7 +259,7 @@ function setupTimetableListeners() {
dayContainer.innerHTML = "";
}
}, 200);
-
+
currentSelectedDate.setDate(currentSelectedDate.getDate() + value);
const formattedDate = formatDate(currentSelectedDate);
callHomeTimetable(formattedDate, true);
@@ -340,19 +315,25 @@ function setupNotices(labelArray: string[], date: string) {
) as HTMLInputElement;
const fetchNotices = async (date: string) => {
- const response = await fetch(
- `${location.origin}/seqta/student/load/notices?`,
- {
- method: "POST",
- headers: { "Content-Type": "application/json; charset=utf-8" },
- body: JSON.stringify({ date }),
- },
- );
- const data = await response.json();
+ let data;
+
+ if (settingsState.mockNotices) {
+ data = getMockNotices();
+ } else {
+ const response = await fetch(
+ `${location.origin}/seqta/student/load/notices?`,
+ {
+ method: "POST",
+ headers: { "Content-Type": "application/json; charset=utf-8" },
+ body: JSON.stringify({ date }),
+ },
+ );
+ data = await response.json();
+ }
+
processNotices(data, labelArray);
};
- // Debounce the input handler
const debouncedInputChange = debounce((e: Event) => {
const target = e.target as HTMLInputElement;
fetchNotices(target.value);
@@ -399,7 +380,6 @@ function processNotices(response: any, labelArray: string[]) {
const NoticeContainer = document.getElementById("notice-container");
if (!NoticeContainer) return;
- // Clear existing notices
NoticeContainer.innerHTML = "";
const notices = response.payload;
@@ -411,19 +391,20 @@ function processNotices(response: any, labelArray: string[]) {
return;
}
- // Create document fragment for batch DOM updates
const fragment = document.createDocumentFragment();
- // Process notices in batch
notices.forEach((notice: any) => {
- if (labelArray.includes(JSON.stringify(notice.label))) {
+ 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);
}
});
- // Single DOM update
NoticeContainer.appendChild(fragment);
}
@@ -438,137 +419,413 @@ function processNoticeColor(colour: string): string | undefined {
}
function createNoticeElement(notice: any, colour: string | undefined): Node {
- const htmlContent = `
-
-
${notice.title}
- ${notice.label_title !== undefined ? `
${notice.label_title}
` : ""}
-
${notice.staff}
- ${notice.contents.replace(/\[\[[\w]+[:][\w]+[\]\]]+/g, "").replace(/ +/, " ")}
-
-
`;
+ const cleanContent = notice.contents
+ .replace(/\[\[[\w]+[:][\w]+[\]\]]+/g, "")
+ .replace(/ +/, " ");
+ const noticeId = `notice-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
- const element = stringToHTML(htmlContent).firstChild;
- if (element instanceof HTMLElement) {
- element.style.setProperty("--colour", colour ?? "");
+ const htmlContent = `
+
+
+
${notice.title}
+
${cleanContent}
+
`;
+
+ const element = stringToHTML(htmlContent).firstChild as HTMLElement;
+ if (element) {
+ 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(/ +/, " ");
+
+ const existingModal = document.getElementById("notice-modal");
+ if (existingModal) {
+ existingModal.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.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.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, 200), viewportHeight * 0.85),
+ );
+
+ 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),
+ );
+
+ // Just measure the existing modal content
+ const currentHeight = unifiedContent.getBoundingClientRect().height;
+ const newTargetHeight = Math.round(
+ Math.min(Math.max(currentHeight, 200), newViewportHeight * 0.85),
+ );
+
+ 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) {
- // Creates a HTTP Post Request to the SEQTA page for the students timetable
var xhr = new XMLHttpRequest();
xhr.open("POST", `${location.origin}/seqta/student/load/timetable?`, true);
- // Sets the response type to json
+
xhr.setRequestHeader("Content-Type", "application/json; charset=utf-8");
xhr.onreadystatechange = function () {
- // Once the response is ready
if (xhr.readyState === 4) {
- // Clear the loading timeout since we got a response
if (loadingTimeout) {
clearTimeout(loadingTimeout);
loadingTimeout = null;
}
-
+
const DayContainer = document.getElementById("day-container")!;
-
+
try {
var serverResponse = JSON.parse(xhr.response);
let lessonArray: Array
= [];
- // If items in response:
- 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);
- });
- // If items in the response, set each corresponding value into divs
- // lessonArray = lessonArray.splice(1)
- GetLessonColours().then((colours) => {
- let subjects = colours;
- for (let i = 0; i < lessonArray.length; i++) {
- let subjectname = `timetable.subject.colour.${lessonArray[i].code}`;
- let subject = subjects.find(
- (element: any) => element.name === subjectname,
- );
- if (!subject) {
- lessonArray[i].colour = "--item-colour: #8e8e8e;";
- } else {
- lessonArray[i].colour = `--item-colour: ${subject.value};`;
- let result = GetThresholdOfColor(subject.value);
-
- if (result > 300) {
- lessonArray[i].invert = true;
- }
- }
- // Removes seconds from the start and end times
- 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,
- );
- }
-
- // Checks if attendance is unmarked, and sets the string to " ".
- lessonArray[i].attendanceTitle = CheckUnmarkedAttendance(
- lessonArray[i].attendance,
- );
+ 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]);
}
- // If on home page, apply each lesson to HTML with information in each div
- DayContainer.innerText = "";
- for (let i = 0; i < lessonArray.length; i++) {
- var div = makeLessonDiv(lessonArray[i], i + 1);
- // Append each of the lessons into the day-container
- if (lessonArray[i].invert) {
- const div1 = div.firstChild! as HTMLElement;
- div1.classList.add("day-inverted");
- }
+ lessonArray.sort(function (a, b) {
+ return a.from.localeCompare(b.from);
+ });
- DayContainer.append(div.firstChild as HTMLElement);
- }
-
- // Remove loading state after lessons are loaded
- DayContainer.classList.remove("loading");
-
- const today = new Date();
- if (currentSelectedDate.getDate() == today.getDate()) {
+ GetLessonColours().then((colours) => {
+ let subjects = colours;
for (let i = 0; i < lessonArray.length; i++) {
- CheckCurrentLesson(lessonArray[i], i + 1);
+ let subjectname = `timetable.subject.colour.${lessonArray[i].code}`;
+
+ let subject = subjects.find(
+ (element: any) => element.name === subjectname,
+ );
+ if (!subject) {
+ lessonArray[i].colour = "--item-colour: #8e8e8e;";
+ } else {
+ lessonArray[i].colour = `--item-colour: ${subject.value};`;
+ let result = GetThresholdOfColor(subject.value);
+
+ if (result > 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,
+ );
}
- // For each lesson, check the start and end times
- CheckCurrentLessonAll(lessonArray);
- }
- });
+
+ DayContainer.innerText = "";
+ for (let i = 0; i < lessonArray.length; i++) {
+ var div = makeLessonDiv(lessonArray[i], i + 1);
+
+ if (lessonArray[i].invert) {
+ const div1 = div.firstChild! as HTMLElement;
+ div1.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 = "";
+ var dummyDay = document.createElement("div");
+ dummyDay.classList.add("day-empty");
+ let img = document.createElement("img");
+ img.src = browser.runtime.getURL(LogoLight);
+ let text = document.createElement("p");
+ text.innerText = "No lessons available.";
+ dummyDay.append(img);
+ dummyDay.append(text);
+ DayContainer.append(dummyDay);
+
+ DayContainer.classList.remove("loading");
}
- } else {
- DayContainer.innerHTML = "";
- var dummyDay = document.createElement("div");
- dummyDay.classList.add("day-empty");
- let img = document.createElement("img");
- img.src = browser.runtime.getURL(LogoLight);
- let text = document.createElement("p");
- text.innerText = "No lessons available.";
- dummyDay.append(img);
- dummyDay.append(text);
- DayContainer.append(dummyDay);
-
- // Remove loading state when no lessons available
- DayContainer.classList.remove("loading");
- }
} catch (error) {
console.error("Error loading timetable data:", error);
- // Remove loading state even if there's an error
+
DayContainer.classList.remove("loading");
-
- // Show error message
+
DayContainer.innerHTML = "";
const errorDiv = document.createElement("div");
errorDiv.classList.add("day-empty");
@@ -582,17 +839,15 @@ function callHomeTimetable(date: string, change?: any) {
};
xhr.send(
JSON.stringify({
- // Information sent to SEQTA page as a request with the dates and student number
from: date,
until: date,
- // Funny number
+
student: 69,
}),
);
}
function CheckCurrentLessonAll(lessons: any) {
- // Checks each lesson and sets an interval to run every 60 seconds to continue updating
LessonInterval = setInterval(
function () {
for (let i = 0; i < lessons.length; i++) {
@@ -614,7 +869,6 @@ async function CheckCurrentLesson(lesson: any, num: number) {
} = lesson;
const currentDate = new Date();
- // Create Date objects for start and end times
const [startHour, startMinute] = startTime.split(":").map(Number);
const [endHour, endMinute] = endTime.split(":").map(Number);
@@ -624,7 +878,6 @@ async function CheckCurrentLesson(lesson: any, num: number) {
const endDate = new Date(currentDate);
endDate.setHours(endHour, endMinute, 0);
- // Check if the current time is within the lesson time range
const isValidTime = startDate < currentDate && endDate > currentDate;
const elementId = `${code}${num}`;
@@ -687,8 +940,7 @@ function makeLessonDiv(lesson: any, num: number) {
assessments,
} = lesson;
- // Construct the base lesson string with default values using ternary operators
- let lessonString = /* html */ `
+ let lessonString = `
${description || "Unknown"}
${staff || "Unknown"}
@@ -697,15 +949,13 @@ function makeLessonDiv(lesson: any, num: number) {
${attendanceTitle || "Unknown"}
`;
- // Add buttons for assessments and courses if applicable
if (programmeID !== 0) {
- lessonString += /* html */ `
+ lessonString += `
${assessmentsicon}
${coursesicon}
`;
}
- // Add assessments if they exist
if (assessments && assessments.length > 0) {
const assessmentString = assessments
.map(
@@ -714,7 +964,7 @@ function makeLessonDiv(lesson: any, num: number) {
)
.join("");
- lessonString += /* html */ `
+ lessonString += `