From 2fe842ca30bc1311534136f8bc0bc782d03da1a9 Mon Sep 17 00:00:00 2001 From: Divyesh Thirukonda Gowri Shankar Date: Wed, 19 Jun 2024 22:36:13 -0500 Subject: [PATCH 01/17] Update to Extension: Added course sort feature --- chrome-extension/sidebar/sidebar.js | 320 ++++++++++++++++++++++++++++ 1 file changed, 320 insertions(+) diff --git a/chrome-extension/sidebar/sidebar.js b/chrome-extension/sidebar/sidebar.js index b245809..45f1f05 100644 --- a/chrome-extension/sidebar/sidebar.js +++ b/chrome-extension/sidebar/sidebar.js @@ -48,6 +48,311 @@ const htmlToElement = (html) => { return template.content.firstChild; }; +// Begin code for sorting course list +document.addEventListener('change', function(event) { + if (event.target.matches('.size-dropdown')) { + handleDropdownChange(event); + } +}); + +const dropdownTemplate = ` +
+ +
+`; + +const handleDropdownChange = (event) => { + if (event.target.value === "A") { + location.reload(); + } + if (event.target.value === "B") { + const buttons = document.querySelectorAll('.action-sections.btn.btn-default'); + buttons.forEach(button => button.click()); + setTimeout(sortBySeatsAvailable, 1000); + } + if (event.target.value === "C") { + sortByMostCommonGrade(); + } + if (event.target.value === "D") { + sortByPopularity(); + } + if (event.target.value === "E") { + sortByUnits(); + } +}; + +const sortBySeatsAvailable = () => { + const courseSizeDict = {}; + + // Get the main container with class 'course-list-results' + let courseListResults = document.querySelector('.course-list-results'); + let courseDivs = Array.from(courseListResults.firstElementChild.children); + + for (let courseDiv of courseDivs) { + let courseName = courseDiv.querySelector('a').getAttribute('name'); + + // Navigate to the correct div + let targetDiv = courseDiv.querySelector('div:nth-child(2) > div:nth-child(3)'); + let trElements = targetDiv.querySelectorAll('tr'); + + let sum = 0; + trElements.forEach(tr => { + let lastChildText = tr.lastElementChild.innerText; + sum += parseFloat(extractDifference(lastChildText)) || 0; // Ensure the text is converted to a number + }); + + courseSizeDict[courseName] = sum; + } + + courseDivs.sort((a, b) => { + let nameA = a.querySelector('a').getAttribute('name'); + let nameB = b.querySelector('a').getAttribute('name'); + return courseSizeDict[nameB] - courseSizeDict[nameA]; + }); + + console.log(courseSizeDict); // For debugging purposes, display the updated dictionary + console.log(courseDivs); // For debugging purposes, display the updated dictionary + + + courseListResults.innerHTML = ''; + courseDivs.forEach(courseDiv => { + courseListResults.appendChild(courseDiv); + }); + + const buttons2 = document.querySelectorAll('.action-sections.btn.btn-default.pull-right'); + buttons2.forEach(button => button.click()); + + document.querySelector('.size-dropdown').value = 'B'; +}; + +function extractDifference(text) { + let matches = text.match(/(\d+)\s+of\s+(\d+)/g); + let totalDifference = 0; + + if (matches) { + matches.forEach(match => { + let parts = match.match(/(\d+)\s+of\s+(\d+)/); + let current = parseInt(parts[1]); + let total = parseInt(parts[2]); + totalDifference += (total - current); + }); + } + return totalDifference; +} + +const sortByMostCommonGrade = () => { + let courseMostCommonGradeDict = {}; + + // Get the main container with class 'course-list-results' + let courseListResults = document.querySelector('.course-list-results'); + let courseDivs = Array.from(courseListResults.firstElementChild.children); + + // Array to hold all fetch promises + let fetchPromises = []; + + for (let courseDiv of courseDivs) { + let courseName = courseDiv.querySelector('a').getAttribute('name'); + let fullURL = `https://corsproxy.io/?https://umn.lol/class/${courseName}?static=all`; + + let fetchPromise = fetch(fullURL, { + method: 'GET', + }) + .then(response => { + if (!response.ok) { + courseMostCommonGradeDict[courseName] = 0; + return null; + } + return response.text(); + }) + .then(html => { + if (html) { + const parser = new DOMParser(); + const doc = parser.parseFromString(html, 'text/html'); + const spans = doc.querySelectorAll('.css-sbue0t'); + + if (spans.length > 0) { + const lastSpan = spans[spans.length - 1]; + let myStr = lastSpan.innerHTML; + let myNum = parseFloat(myStr.match(/(\d+)%/)[1]); + courseMostCommonGradeDict[courseName] = myNum; + return myNum; + } else { + return null; + } + } + }) + .catch(error => { + console.log('Error: ' + error); + }); + + fetchPromises.push(fetchPromise); + } + + // Wait for all fetch promises to resolve + Promise.all(fetchPromises) + .then(() => { + // Once all promises have resolved (fetch requests are complete and data is populated), sort courseDivs + courseDivs.sort((a, b) => { + let nameA = a.querySelector('a').getAttribute('name'); + let nameB = b.querySelector('a').getAttribute('name'); + return courseMostCommonGradeDict[nameB] - courseMostCommonGradeDict[nameA]; + }); + + console.log(courseMostCommonGradeDict); + console.log(courseDivs); + + // Clear current content in courseListResults + courseListResults.innerHTML = ''; + + // Append sorted courseDivs back to courseListResults + courseDivs.forEach(courseDiv => { + courseListResults.appendChild(courseDiv); + }); + + // Optionally set dropdown value or perform other actions + document.querySelector('.size-dropdown').value = 'C'; + }) + .catch(error => { + console.error('Error waiting for fetch promises:', error); + }); +}; + + +const sortByPopularity = () => { + let coursePopularityDict = {}; + + // Get the main container with class 'course-list-results' + let courseListResults = document.querySelector('.course-list-results'); + let courseDivs = Array.from(courseListResults.firstElementChild.children); + + // Array to hold all fetch promises + let fetchPromises = []; + + for (let courseDiv of courseDivs) { + let courseName = courseDiv.querySelector('a').getAttribute('name'); + let fullURL = `https://corsproxy.io/?https://umn.lol/class/${courseName}?static=all`; + + let fetchPromise = fetch(fullURL, { + method: 'GET', + }) + .then(response => { + if (!response.ok) { + coursePopularityDict[courseName] = 0; + return null; + } + return response.text(); + }) + .then(html => { + if (html) { + const parser = new DOMParser(); + const doc = parser.parseFromString(html, 'text/html'); + const spans = doc.querySelectorAll('.chakra-badge.css-5h3htq'); + + if (spans.length > 0) { + const lastSpan = spans[spans.length - 1]; + let myStr = lastSpan.innerHTML; + console.log(myStr) + let myNum = parseFloat(myStr.match(/(\d+) students/)[1]); + coursePopularityDict[courseName] = myNum; + return myNum; + } else { + return null; + } + } + }) + .catch(error => { + console.log('Error: ' + error); + }); + + fetchPromises.push(fetchPromise); + } + + // Wait for all fetch promises to resolve + Promise.all(fetchPromises) + .then(() => { + // Once all promises have resolved (fetch requests are complete and data is populated), sort courseDivs + courseDivs.sort((a, b) => { + let nameA = a.querySelector('a').getAttribute('name'); + let nameB = b.querySelector('a').getAttribute('name'); + return coursePopularityDict[nameB] - coursePopularityDict[nameA]; + }); + + console.log(coursePopularityDict); + console.log(courseDivs); + + // Clear current content in courseListResults + courseListResults.innerHTML = ''; + + // Append sorted courseDivs back to courseListResults + courseDivs.forEach(courseDiv => { + courseListResults.appendChild(courseDiv); + }); + + // Optionally set dropdown value or perform other actions + document.querySelector('.size-dropdown').value = 'D'; + }) + .catch(error => { + console.error('Error waiting for fetch promises:', error); + }); +}; + + +const sortByUnits = () => { + const courseUnitsDict = {}; + + // Get the main container with class 'course-list-results' + let courseListResults = document.querySelector('.course-list-results'); + let courseDivs = Array.from(courseListResults.firstElementChild.children); + + for (let courseDiv of courseDivs) { + let courseName = courseDiv.querySelector('a').getAttribute('name'); + + // Navigate to the correct div + let targetDiv = courseDiv.querySelector('div:nth-child(2) > div:nth-child(2)').children; + let trElements = targetDiv[targetDiv.length - 2]; + + myCleanText = trElements.innerHTML.trim().replace(/\s+/g, ''); + numberMatch = (myCleanText.match(/(\d+(\.\d+)?)/)); + + if (numberMatch) { + console.log(parseFloat(numberMatch[0])); // Output: 2 + courseUnitsDict[courseName] = parseFloat(numberMatch[0]); + } else { + console.log('No number found'); + courseUnitsDict[courseName] = 0; + } + + + } + + courseDivs.sort((a, b) => { + let nameA = a.querySelector('a').getAttribute('name'); + let nameB = b.querySelector('a').getAttribute('name'); + return courseUnitsDict[nameB] - courseUnitsDict[nameA]; + }); + + console.log(courseUnitsDict); // For debugging purposes, display the updated dictionary + console.log(courseDivs); // For debugging purposes, display the updated dictionary + + + courseListResults.innerHTML = ''; + courseDivs.forEach(courseDiv => { + courseListResults.appendChild(courseDiv); + }); + + document.querySelector('.size-dropdown').value = 'E'; +}; + +// end sorting feature + + + const iframeTemplate = `
@@ -155,6 +460,15 @@ const loadCourseSchedule = (courseSchedule) => { } }; +const loadDropdown = () => { + const courseListOptions = document.querySelector(".course-list-options"); + const emptyDiv = courseListOptions.firstElementChild; + const dropdownElement = htmlToElement(dropdownTemplate); + const firstDiv = emptyDiv.firstElementChild; + const dropdown = emptyDiv.insertBefore(dropdownElement, firstDiv); + dropdown.setAttribute("id", "dropdown"); +}; + const onAppChange = async () => { const courseList = document.querySelector(".course-list-results"); const courseInfo = document.querySelector("#crse-info"); @@ -170,10 +484,16 @@ const onAppChange = async () => { if (courseList) loadCourses(courseList); else if (courseInfo) loadCourseInfo(courseInfo); else if (courseSchedule) loadCourseSchedule(courseSchedule); + + const dropdown = document.querySelector("#dropdown"); + if (!dropdown) loadDropdown(); }; + + let loaded = false; const onLoad = () => { + if (loaded) return; loaded = true; From 7816baa02db8ca4eac88d4c88a042c35839342a7 Mon Sep 17 00:00:00 2001 From: Divyesh Thirukonda Gowri Shankar Date: Wed, 19 Jun 2024 23:04:55 -0500 Subject: [PATCH 02/17] Update sidebar.js --- chrome-extension/sidebar/sidebar.js | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/chrome-extension/sidebar/sidebar.js b/chrome-extension/sidebar/sidebar.js index 45f1f05..4a1e810 100644 --- a/chrome-extension/sidebar/sidebar.js +++ b/chrome-extension/sidebar/sidebar.js @@ -120,9 +120,9 @@ const sortBySeatsAvailable = () => { console.log(courseDivs); // For debugging purposes, display the updated dictionary - courseListResults.innerHTML = ''; + courseListResults.firstElementChild.innerHTML = ''; courseDivs.forEach(courseDiv => { - courseListResults.appendChild(courseDiv); + courseListResults.firstElementChild.appendChild(courseDiv); }); const buttons2 = document.querySelectorAll('.action-sections.btn.btn-default.pull-right'); @@ -207,12 +207,9 @@ const sortByMostCommonGrade = () => { console.log(courseMostCommonGradeDict); console.log(courseDivs); - // Clear current content in courseListResults - courseListResults.innerHTML = ''; - - // Append sorted courseDivs back to courseListResults + courseListResults.firstElementChild.innerHTML = ''; courseDivs.forEach(courseDiv => { - courseListResults.appendChild(courseDiv); + courseListResults.firstElementChild.appendChild(courseDiv); }); // Optionally set dropdown value or perform other actions @@ -286,12 +283,9 @@ const sortByPopularity = () => { console.log(coursePopularityDict); console.log(courseDivs); - // Clear current content in courseListResults - courseListResults.innerHTML = ''; - - // Append sorted courseDivs back to courseListResults + courseListResults.firstElementChild.innerHTML = ''; courseDivs.forEach(courseDiv => { - courseListResults.appendChild(courseDiv); + courseListResults.firstElementChild.appendChild(courseDiv); }); // Optionally set dropdown value or perform other actions @@ -341,9 +335,9 @@ const sortByUnits = () => { console.log(courseDivs); // For debugging purposes, display the updated dictionary - courseListResults.innerHTML = ''; + courseListResults.firstElementChild.innerHTML = ''; courseDivs.forEach(courseDiv => { - courseListResults.appendChild(courseDiv); + courseListResults.firstElementChild.appendChild(courseDiv); }); document.querySelector('.size-dropdown').value = 'E'; From 294c48321501879c63ad2abb1cbe5109092ec386 Mon Sep 17 00:00:00 2001 From: Divyesh Thirukonda Gowri Shankar Date: Wed, 19 Jun 2024 23:21:08 -0500 Subject: [PATCH 03/17] bug fixes --- chrome-extension/sidebar/sidebar.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/chrome-extension/sidebar/sidebar.js b/chrome-extension/sidebar/sidebar.js index 4a1e810..3b4a119 100644 --- a/chrome-extension/sidebar/sidebar.js +++ b/chrome-extension/sidebar/sidebar.js @@ -55,9 +55,11 @@ document.addEventListener('change', function(event) { } }); +// padding: 6px 10px; +// padding: 4px 10px; const dropdownTemplate = ` -
- From 6d8257271d07766dce1c83983e50aff5bd5c4687 Mon Sep 17 00:00:00 2001 From: Divyesh Thirukonda Gowri Shankar Date: Thu, 20 Jun 2024 00:39:40 -0500 Subject: [PATCH 04/17] Update sidebar.js --- chrome-extension/sidebar/sidebar.js | 31 +++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/chrome-extension/sidebar/sidebar.js b/chrome-extension/sidebar/sidebar.js index 3b4a119..bc7f543 100644 --- a/chrome-extension/sidebar/sidebar.js +++ b/chrome-extension/sidebar/sidebar.js @@ -49,14 +49,14 @@ const htmlToElement = (html) => { }; // Begin code for sorting course list + document.addEventListener('change', function(event) { if (event.target.matches('.size-dropdown')) { handleDropdownChange(event); } }); -// padding: 6px 10px; -// padding: 4px 10px; +// dropdown element const dropdownTemplate = `
+ - - - - - + + + + +
`; // handle sorting options const handleDropdownChange = (event) => { - if (event.target.value === "A") { + var selectedValue = event.target.value; + console.log("my choice: " + selectedValue); + + if (selectedValue === "sortCourseCodeAsc") { location.reload(); } - if (event.target.value === "B") { + if (selectedValue === "sortSeatsAvailDes") { const buttons = document.querySelectorAll('.action-sections.btn.btn-default'); buttons.forEach(button => button.click()); setTimeout(sortBySeatsAvailable, 1000); } - if (event.target.value === "C") { + if (selectedValue === "sortMostCommonGradeDes") { sortByMostCommonGrade(); } - if (event.target.value === "D") { + if (selectedValue === "sortPopularityDes") { sortByPopularity(); } - if (event.target.value === "E") { + if (selectedValue === "sortUnitsDes") { sortByUnits(); } + // expand with more below... }; const sortBySeatsAvailable = () => { @@ -122,13 +126,13 @@ const sortBySeatsAvailable = () => { return courseSizeDict[nameB] - courseSizeDict[nameA]; }); - console.log(courseSizeDict); // For debugging purposes, display the updated dictionary - console.log(courseDivs); // For debugging purposes, display the updated divs + console.log("Sorted Seats Dict: ", courseSizeDict); // For debugging purposes, display the updated dictionary + console.log("Sorted Seats Course Divs: ", courseDivs); // For debugging purposes, display the updated divs // remove all the courses to add our own ordering of the courses courseListResults.firstElementChild.innerHTML = ''; courseDivs.forEach(courseDiv => { - courseListResults.firstElementChild.appendChild(courseDiv); + courseListResults.firstElementChild.appendChild(courseDiv); }); // click all the 'hide' buttons @@ -136,23 +140,23 @@ const sortBySeatsAvailable = () => { buttons2.forEach(button => button.click()); // ensure this value is selected - document.querySelector('.size-dropdown').value = 'B'; + document.querySelector('.size-dropdown').value = 'sortSeatsAvailDes'; }; // Helper function to determine seats available function extractDifference(text) { - let matches = text.match(/(\d+)\s+of\s+(\d+)/g); - let totalDifference = 0; - - if (matches) { - matches.forEach(match => { - let parts = match.match(/(\d+)\s+of\s+(\d+)/); - let current = parseInt(parts[1]); - let total = parseInt(parts[2]); - totalDifference += (total - current); - }); - } - return totalDifference; + let matches = text.match(/(\d+)\s+of\s+(\d+)/g); + let totalDifference = 0; + + if (matches) { + matches.forEach(match => { + let parts = match.match(/(\d+)\s+of\s+(\d+)/); + let current = parseInt(parts[1]); + let total = parseInt(parts[2]); + totalDifference += (total - current); + }); + } + return totalDifference; } const sortByMostCommonGrade = () => { @@ -214,15 +218,16 @@ const sortByMostCommonGrade = () => { return courseMostCommonGradeDict[nameB] - courseMostCommonGradeDict[nameA]; }); - console.log(courseMostCommonGradeDict); - console.log(courseDivs); + console.log("Most Common Grade Dict: ", courseMostCommonGradeDict); // For debugging purposes, display the updated dictionary + console.log("Sorted Most Common Grade Course Divs: ", courseDivs); // For debugging purposes, display the updated divs + courseListResults.firstElementChild.innerHTML = ''; courseDivs.forEach(courseDiv => { courseListResults.firstElementChild.appendChild(courseDiv); }); - document.querySelector('.size-dropdown').value = 'C'; + document.querySelector('.size-dropdown').value = 'sortMostCommonGradeDes'; }) .catch(error => { console.error('Error waiting for fetch promises:', error); @@ -293,8 +298,8 @@ const sortByPopularity = () => { return coursePopularityDict[nameB] - coursePopularityDict[nameA]; }); - console.log(coursePopularityDict); - console.log(courseDivs); + console.log("Course Popularity Dict: ", coursePopularityDict); // For debugging purposes, display the updated dictionary + console.log("Sorted Popularity Course Divs: ", courseDivs); // For debugging purposes, display the updated divs courseListResults.firstElementChild.innerHTML = ''; courseDivs.forEach(courseDiv => { @@ -302,7 +307,7 @@ const sortByPopularity = () => { }); // Optionally set dropdown value or perform other actions - document.querySelector('.size-dropdown').value = 'D'; + document.querySelector('.size-dropdown').value = 'sortPopularityDes'; }) .catch(error => { console.error('Error waiting for fetch promises:', error); @@ -331,7 +336,7 @@ const sortByUnits = () => { console.log(parseFloat(numberMatch[0])); // Output: 2 courseUnitsDict[courseName] = parseFloat(numberMatch[0]); } else { - console.log('No number found'); + // No number found courseUnitsDict[courseName] = 0; } @@ -344,16 +349,15 @@ const sortByUnits = () => { return courseUnitsDict[nameB] - courseUnitsDict[nameA]; }); - console.log(courseUnitsDict); // For debugging purposes, display the updated dictionary - console.log(courseDivs); // For debugging purposes, display the updated dictionary - + console.log("Course Units Dict: ", courseUnitsDict); // For debugging purposes, display the updated dictionary + console.log("Sorted Units Course Divs: ", courseDivs); // For debugging purposes, display the updated divs courseListResults.firstElementChild.innerHTML = ''; courseDivs.forEach(courseDiv => { courseListResults.firstElementChild.appendChild(courseDiv); }); - document.querySelector('.size-dropdown').value = 'E'; + document.querySelector('.size-dropdown').value = 'sortUnitsDes'; }; // end sorting feature @@ -492,8 +496,15 @@ const onAppChange = async () => { else if (courseInfo) loadCourseInfo(courseInfo); else if (courseSchedule) loadCourseSchedule(courseSchedule); - const dropdown = document.querySelector("#dropdown"); - if (!dropdown) loadDropdown(); + var courseSortDropdown = document.querySelector('.size-dropdown'); + if (courseSortDropdown) { + courseSortDropdown.addEventListener('change', handleDropdownChange); + } else { + console.log("Course-sort dropdown not found!"); + } + + // const dropdown = document.querySelector("#dropdown"); + if (!courseSortDropdown) loadDropdown(); }; From ae20a35d8f1482cb8dbcafce1b511820460208df Mon Sep 17 00:00:00 2001 From: Divyesh Thirukonda Gowri Shankar Date: Thu, 20 Jun 2024 01:51:01 -0500 Subject: [PATCH 07/17] Edits --- chrome-extension/sidebar/sidebar.js | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/chrome-extension/sidebar/sidebar.js b/chrome-extension/sidebar/sidebar.js index d517d66..07c4480 100644 --- a/chrome-extension/sidebar/sidebar.js +++ b/chrome-extension/sidebar/sidebar.js @@ -50,12 +50,6 @@ const htmlToElement = (html) => { // Begin code for sorting course list -// document.addEventListener('change', function(event) { -// if (event.target.matches('.size-dropdown')) { -// handleDropdownChange(event); -// } -// }); - // dropdown element const dropdownTemplate = `
@@ -476,8 +470,7 @@ const loadDropdown = () => { const emptyDiv = courseListOptions.firstElementChild; const dropdownElement = htmlToElement(dropdownTemplate); const firstDiv = emptyDiv.firstElementChild; - const dropdown = emptyDiv.insertBefore(dropdownElement, firstDiv); - dropdown.setAttribute("id", "dropdown"); + emptyDiv.insertBefore(dropdownElement, firstDiv); }; const onAppChange = async () => { @@ -503,7 +496,6 @@ const onAppChange = async () => { console.log("Course-sort dropdown not found!"); } - // const dropdown = document.querySelector("#dropdown"); if (!courseSortDropdown) loadDropdown(); }; From a916e3424d765fdd1d60fe1e796094642ea3971f Mon Sep 17 00:00:00 2001 From: Divyesh Thirukonda Gowri Shankar Date: Thu, 20 Jun 2024 12:31:51 -0500 Subject: [PATCH 08/17] Fixed CORS Issue --- chrome-extension/background.js | 18 +++++ chrome-extension/manifest.json | 3 +- chrome-extension/sidebar/sidebar.js | 106 ++++++++++------------------ frontend/next.config.js | 14 ++++ 4 files changed, 72 insertions(+), 69 deletions(-) diff --git a/chrome-extension/background.js b/chrome-extension/background.js index d69408c..bf0352c 100644 --- a/chrome-extension/background.js +++ b/chrome-extension/background.js @@ -68,3 +68,21 @@ chrome.runtime.onInstalled.addListener(async () => { await chrome.storage.sync.set({ settings: defaultSettingCodes }); }); + +// background script +chrome.runtime.onMessage.addListener(function (message, sender, senderResponse) { + if (message.type === "json") { + fetch(`https://umn.lol/api/class/${message.courseName}`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({source: {url: message.url}}) + }).then(res => { + return res.json(); + }).then(res => { + senderResponse(res); + }) + } + return true +}); \ No newline at end of file diff --git a/chrome-extension/manifest.json b/chrome-extension/manifest.json index 0cca606..18c10c6 100644 --- a/chrome-extension/manifest.json +++ b/chrome-extension/manifest.json @@ -65,7 +65,8 @@ "*://*.umn.lol/*", "*://umn.lol/*", "*://*.umn.edu/*", - "*://umn.edu/*" + "*://umn.edu/*", + "https://umn.lol/*" ], "permissions": [ "storage", diff --git a/chrome-extension/sidebar/sidebar.js b/chrome-extension/sidebar/sidebar.js index 07c4480..0d6cacf 100644 --- a/chrome-extension/sidebar/sidebar.js +++ b/chrome-extension/sidebar/sidebar.js @@ -56,7 +56,7 @@ const dropdownTemplate = ` @@ -165,38 +165,24 @@ const sortByMostCommonGrade = () => { for (let courseDiv of courseDivs) { let courseName = courseDiv.querySelector('a').getAttribute('name'); - let fullURL = `https://corsproxy.io/?https://umn.lol/class/${courseName}?static=all`; // we need to use corsproxy.io to bypass CORS restriction - let fetchPromise = fetch(fullURL, { - method: 'GET', - }) - .then(response => { - if (!response.ok) { - courseMostCommonGradeDict[courseName] = 0; // update dictionary value to 0 - return null; - } - return response.text(); - }) - .then(html => { - if (html) { - // Get the information we need from the html - const parser = new DOMParser(); - const doc = parser.parseFromString(html, 'text/html'); - const spans = doc.querySelectorAll('.css-sbue0t'); - - if (spans.length > 0) { - const lastSpan = spans[spans.length - 1]; - let myStr = lastSpan.innerHTML; - let myNum = parseFloat(myStr.match(/(\d+)%/)[1]); - courseMostCommonGradeDict[courseName] = myNum; // update dictionary to desired value - return myNum; + // Push the fetch promise into the array + let fetchPromise = new Promise((resolve, reject) => { + chrome.runtime.sendMessage({ type: 'json', url: window.location.href, courseName: courseName }, response => { + if (response.success && response.data) { + let totalStudents = response.data.total_students; + let totalGrades = response.data.total_grades; + let numberOfAs = totalGrades["A"]; + let val = (numberOfAs / totalStudents) * 100; + console.log(`Percentage of A's for ${courseName}: ${val}%`); + + // Store the calculated percentage in the dictionary + courseMostCommonGradeDict[courseName] = val; } else { - return null; + courseMostCommonGradeDict[courseName] = 0; } - } - }) - .catch(error => { - console.log('Error: ' + error); + resolve(); // Resolve the promise once processing is done + }); }); fetchPromises.push(fetchPromise); @@ -205,7 +191,7 @@ const sortByMostCommonGrade = () => { // Wait for all fetch promises to resolve Promise.all(fetchPromises) .then(() => { - // Once all promises have resolved (fetch requests are complete and data is populated), sort courseDivs using same process as before + // Once all promises have resolved (fetch requests are complete and data is populated), sort courseDivs courseDivs.sort((a, b) => { let nameA = a.querySelector('a').getAttribute('name'); let nameB = b.querySelector('a').getAttribute('name'); @@ -214,25 +200,28 @@ const sortByMostCommonGrade = () => { console.log("Most Common Grade Dict: ", courseMostCommonGradeDict); // For debugging purposes, display the updated dictionary console.log("Sorted Most Common Grade Course Divs: ", courseDivs); // For debugging purposes, display the updated divs - + // Clear and update the course list results courseListResults.firstElementChild.innerHTML = ''; courseDivs.forEach(courseDiv => { - courseListResults.firstElementChild.appendChild(courseDiv); + courseListResults.firstElementChild.appendChild(courseDiv); }); + // Set dropdown value after sorting document.querySelector('.size-dropdown').value = 'sortMostCommonGradeDes'; }) .catch(error => { - console.error('Error waiting for fetch promises:', error); + console.error('Error fetching course data:', error); }); }; + // NOTE: the rest of the functions follow very similar code from the last two. // The first one is to build dictionaries from information that is already on the schedule builder page (eg. seats available, course units) // The second one is to build dictionaries from information using an API (eg. popularity, most-common grade) // Refer to them if you are confused about something + const sortByPopularity = () => { let coursePopularityDict = {}; @@ -245,38 +234,18 @@ const sortByPopularity = () => { for (let courseDiv of courseDivs) { let courseName = courseDiv.querySelector('a').getAttribute('name'); - let fullURL = `https://corsproxy.io/?https://umn.lol/class/${courseName}?static=all`; - let fetchPromise = fetch(fullURL, { - method: 'GET', - }) - .then(response => { - if (!response.ok) { - coursePopularityDict[courseName] = 0; - return null; - } - return response.text(); - }) - .then(html => { - if (html) { - const parser = new DOMParser(); - const doc = parser.parseFromString(html, 'text/html'); - const spans = doc.querySelectorAll('.chakra-badge.css-5h3htq'); - - if (spans.length > 0) { - const lastSpan = spans[spans.length - 1]; - let myStr = lastSpan.innerHTML; - console.log(myStr) - let myNum = parseFloat(myStr.match(/(\d+) students/)[1]); - coursePopularityDict[courseName] = myNum; - return myNum; + // Push the fetch promise into the array + let fetchPromise = new Promise((resolve, reject) => { + chrome.runtime.sendMessage({ type: 'json', url: window.location.href, courseName: courseName }, response => { + if (response.success && response.data) { + let totalStudents = response.data.total_students; + coursePopularityDict[courseName] = totalStudents; } else { - return null; + coursePopularityDict[courseName] = 0; } - } - }) - .catch(error => { - console.log('Error: ' + error); + resolve(); // Resolve the promise once processing is done + }); }); fetchPromises.push(fetchPromise); @@ -292,19 +261,20 @@ const sortByPopularity = () => { return coursePopularityDict[nameB] - coursePopularityDict[nameA]; }); - console.log("Course Popularity Dict: ", coursePopularityDict); // For debugging purposes, display the updated dictionary - console.log("Sorted Popularity Course Divs: ", courseDivs); // For debugging purposes, display the updated divs + console.log("Most Common Grade Dict: ", coursePopularityDict); // For debugging purposes, display the updated dictionary + console.log("Sorted Most Common Grade Course Divs: ", courseDivs); // For debugging purposes, display the updated divs + // Clear and update the course list results courseListResults.firstElementChild.innerHTML = ''; courseDivs.forEach(courseDiv => { - courseListResults.firstElementChild.appendChild(courseDiv); + courseListResults.firstElementChild.appendChild(courseDiv); }); - // Optionally set dropdown value or perform other actions + // Set dropdown value after sorting document.querySelector('.size-dropdown').value = 'sortPopularityDes'; }) .catch(error => { - console.error('Error waiting for fetch promises:', error); + console.error('Error fetching course data:', error); }); }; diff --git a/frontend/next.config.js b/frontend/next.config.js index f6ec198..4740fe7 100644 --- a/frontend/next.config.js +++ b/frontend/next.config.js @@ -16,6 +16,20 @@ const nextConfig = { pageExtensions: ["js", "jsx", "ts", "tsx", "md", "mdx"], reactStrictMode: true, swcMinify: true, + async headers() { + return [ + { + // matching all API routes + source: "/api/:path*", + headers: [ + { key: "Access-Control-Allow-Credentials", value: "true" }, + { key: "Access-Control-Allow-Origin", value: "*" }, + { key: "Access-Control-Allow-Methods", value: "GET,DELETE,PATCH,POST,PUT" }, + { key: "Access-Control-Allow-Headers", value: "X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version" }, + ] + } + ] + }, rewrites: async () => { return [ { From ec0d17cd5ca7ea851a58e3d33b59d2c3b02afcbe Mon Sep 17 00:00:00 2001 From: Divyesh Thirukonda Gowri Shankar Date: Thu, 20 Jun 2024 15:28:30 -0500 Subject: [PATCH 09/17] Fixed batch of issues CORS workaround, code styling, improved readability, comments, and reduced redundancy --- chrome-extension/background.js | 39 +++++++++++++++++------------ chrome-extension/manifest.json | 3 +-- chrome-extension/sidebar/sidebar.js | 14 +++++++---- frontend/next.config.js | 24 +++++++++--------- 4 files changed, 45 insertions(+), 35 deletions(-) diff --git a/chrome-extension/background.js b/chrome-extension/background.js index bf0352c..3cc672d 100644 --- a/chrome-extension/background.js +++ b/chrome-extension/background.js @@ -53,11 +53,6 @@ const RuntimeMessages = { }, }; -chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { - const { type } = request; - RuntimeMessages[type](request); -}); - chrome.runtime.onInstalled.addListener(async () => { const defaultSettingCodes = defaultSettings.reduce((acc, section) => { section.settings.forEach((setting) => { @@ -69,20 +64,32 @@ chrome.runtime.onInstalled.addListener(async () => { await chrome.storage.sync.set({ settings: defaultSettingCodes }); }); -// background script -chrome.runtime.onMessage.addListener(function (message, sender, senderResponse) { - if (message.type === "json") { - fetch(`https://umn.lol/api/class/${message.courseName}`, { - method: 'POST', +chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { + if (message.type === "umnlolApiResponseJson") { + // Handle JSON message type with a GET request + // This is where we + fetch(`https://umn.lol/api/class/${message.courseName}?url=${encodeURIComponent(message.url)}`, { + method: 'GET', headers: { 'Content-Type': 'application/json' - }, - body: JSON.stringify({source: {url: message.url}}) + } }).then(res => { + if (!res.ok) { + throw new Error(`HTTP error! Status: ${res.status}`); + } return res.json(); }).then(res => { - senderResponse(res); - }) + sendResponse(res); // Send response back to the sender + }).catch(error => { + console.error('Error fetching data:', error); + sendResponse({ error: 'Error fetching data' }); // Send an error response + }); + + // Return true to indicate that sendResponse will be called asynchronously + return true; + } else { + // Handle other message types if necessary + const { type } = message; + RuntimeMessages[type](message); // Assuming RuntimeMessages is defined elsewhere } - return true -}); \ No newline at end of file +}); diff --git a/chrome-extension/manifest.json b/chrome-extension/manifest.json index 18c10c6..0cca606 100644 --- a/chrome-extension/manifest.json +++ b/chrome-extension/manifest.json @@ -65,8 +65,7 @@ "*://*.umn.lol/*", "*://umn.lol/*", "*://*.umn.edu/*", - "*://umn.edu/*", - "https://umn.lol/*" + "*://umn.edu/*" ], "permissions": [ "storage", diff --git a/chrome-extension/sidebar/sidebar.js b/chrome-extension/sidebar/sidebar.js index 0d6cacf..7e6a302 100644 --- a/chrome-extension/sidebar/sidebar.js +++ b/chrome-extension/sidebar/sidebar.js @@ -106,8 +106,8 @@ const sortBySeatsAvailable = () => { let sum = 0; trElements.forEach(tr => { - let lastChildText = tr.lastElementChild.innerText; - sum += parseFloat(extractDifference(lastChildText)) || 0; // Ensure the text is converted to a number + let lastChildText = tr.lastElementChild.innerText; + sum += parseFloat(extractDifference(lastChildText)) || 0; // Ensure the text is converted to a number }); courseSizeDict[courseName] = sum; @@ -168,7 +168,11 @@ const sortByMostCommonGrade = () => { // Push the fetch promise into the array let fetchPromise = new Promise((resolve, reject) => { - chrome.runtime.sendMessage({ type: 'json', url: window.location.href, courseName: courseName }, response => { + chrome.runtime.sendMessage({ type: 'umnlolApiResponseJson', url: window.location.href, courseName: courseName }, response => { + // If the response is consistently unsuccessful or there's no data + // Check background.js and ctrl+f for 'umnlolApiResponseJson' + // That is where I handled the request + if (response.success && response.data) { let totalStudents = response.data.total_students; let totalGrades = response.data.total_grades; @@ -237,7 +241,7 @@ const sortByPopularity = () => { // Push the fetch promise into the array let fetchPromise = new Promise((resolve, reject) => { - chrome.runtime.sendMessage({ type: 'json', url: window.location.href, courseName: courseName }, response => { + chrome.runtime.sendMessage({ type: 'umnlolApiResponseJson', url: window.location.href, courseName: courseName }, response => { if (response.success && response.data) { let totalStudents = response.data.total_students; coursePopularityDict[courseName] = totalStudents; @@ -463,7 +467,7 @@ const onAppChange = async () => { if (courseSortDropdown) { courseSortDropdown.addEventListener('change', handleDropdownChange); } else { - console.log("Course-sort dropdown not found!"); + console.log("Course-sort dropdown not found for whatever reason. This feature will not work."); } if (!courseSortDropdown) loadDropdown(); diff --git a/frontend/next.config.js b/frontend/next.config.js index 4740fe7..1e7615a 100644 --- a/frontend/next.config.js +++ b/frontend/next.config.js @@ -17,18 +17,18 @@ const nextConfig = { reactStrictMode: true, swcMinify: true, async headers() { - return [ - { - // matching all API routes - source: "/api/:path*", - headers: [ - { key: "Access-Control-Allow-Credentials", value: "true" }, - { key: "Access-Control-Allow-Origin", value: "*" }, - { key: "Access-Control-Allow-Methods", value: "GET,DELETE,PATCH,POST,PUT" }, - { key: "Access-Control-Allow-Headers", value: "X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version" }, - ] - } - ] + return [ + { + // matching all API routes + source: "/api/:path*", + headers: [ + { key: "Access-Control-Allow-Credentials", value: "true" }, + { key: "Access-Control-Allow-Origin", value: "*" }, + { key: "Access-Control-Allow-Methods", value: "GET,DELETE,PATCH,POST,PUT" }, + { key: "Access-Control-Allow-Headers", value: "X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version" }, + ] + } + ] }, rewrites: async () => { return [ From 283e2b2add7d93755e07b6cae08e1ed586fb14fe Mon Sep 17 00:00:00 2001 From: Divyesh Thirukonda Gowri Shankar Date: Sat, 22 Jun 2024 00:14:41 -0500 Subject: [PATCH 10/17] Updates --- chrome-extension/background.js | 1 - chrome-extension/course_sort/course_sort.js | 280 +++++++++++++++++ chrome-extension/manifest.json | 3 +- chrome-extension/sidebar/sidebar.js | 313 ++------------------ frontend/next.config.js | 2 +- 5 files changed, 306 insertions(+), 293 deletions(-) create mode 100644 chrome-extension/course_sort/course_sort.js diff --git a/chrome-extension/background.js b/chrome-extension/background.js index 3cc672d..67810cf 100644 --- a/chrome-extension/background.js +++ b/chrome-extension/background.js @@ -67,7 +67,6 @@ chrome.runtime.onInstalled.addListener(async () => { chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { if (message.type === "umnlolApiResponseJson") { // Handle JSON message type with a GET request - // This is where we fetch(`https://umn.lol/api/class/${message.courseName}?url=${encodeURIComponent(message.url)}`, { method: 'GET', headers: { diff --git a/chrome-extension/course_sort/course_sort.js b/chrome-extension/course_sort/course_sort.js new file mode 100644 index 0000000..5091881 --- /dev/null +++ b/chrome-extension/course_sort/course_sort.js @@ -0,0 +1,280 @@ +const courseSortingLogPrefix = '[Course Sorting Script] '; + +console.log(courseSortingLogPrefix + "Course Sorting script loaded"); + +// dropdown element +const dropdownTemplate = ` +
+ +
+`; + +// handle sorting options +const handleDropdownChange = (event) => { + var selectedValue = event.target.value; + console.log(courseSortingLogPrefix + "my choice: " + selectedValue); + + const sortingFunctions = { + sortCourseCodeAsc: () => location.reload(), + sortSeatsAvailDes: () => + { + const showSectionButtons = document.querySelectorAll('.action-sections.btn.btn-default'); + showSectionButtons.forEach(button => button.click()); + const loadingElement = `

Sorting...

`; + let courseListResults = document.querySelector('.course-list-results').parentElement; + courseListResults.insertBefore(htmlToElement(loadingElement), courseListResults.firstChild); + setTimeout(() => { + sortCourses(buildSeatsAvailableDict, 'sortSeatsAvailDes'); + }, 500); + }, + sortMostCommonGradeDes: () => sortCourses(buildMostCommonGradeDict, 'sortMostCommonGradeDes'), + sortAvgGrade: () => sortCourses(buildCourseAvgDict, 'sortAvgGrade'), + sortPopularityDes: () => sortCourses(buildPopularityDict, 'sortPopularityDes'), + sortUnitsDes: () => sortCourses(buildUnitsDict, 'sortUnitsDes') + }; + + sortingFunctions[selectedValue]?.(); +}; + +const sortCourses = (buildDictFunc, dropdownValue, additionalLogic = () => {}) => { + sortCoursesTemplate(buildDictFunc, additionalLogic).then(courseDict => { + let courseListResults = document.querySelector('.course-list-results'); + let courseDivs = Array.from(courseListResults.firstElementChild.children); + + courseDivs.sort((a, b) => { + let nameA = a.querySelector('a').getAttribute('name'); + let nameB = b.querySelector('a').getAttribute('name'); + return courseDict[nameB] - courseDict[nameA]; + }); + + console.log(courseSortingLogPrefix + "Sorted Course Dict: ", courseDict); // For debugging purposes, display the updated dictionary + console.log(courseSortingLogPrefix + "Sorted Course Divs: ", courseDivs); // For debugging purposes, display the updated divs + + courseListResults.firstElementChild.innerHTML = ''; + courseDivs.forEach(courseDiv => { + courseListResults.firstElementChild.appendChild(courseDiv); + }); + + document.querySelector('.size-dropdown').value = dropdownValue; + }).catch(error => { + console.error('Error building course dictionary:', error); + }); +}; + +const sortCoursesTemplate = (buildDictFunc, additionalLogic) => { + return buildDictFunc().then(courseDict => { + additionalLogic(); + return courseDict; + }); +}; + +let courseSizeDict = {}; + +const buildSeatsAvailableDict = () => { + return new Promise((resolve, reject) => { + let courseListResults = document.querySelector('.course-list-results'); + let courseDivs = Array.from(courseListResults.firstElementChild.children); + + if (Object.keys(courseSizeDict).length === 0) { + for (let courseDiv of courseDivs) { + let courseName = courseDiv.querySelector('a').getAttribute('name'); + let targetDiv = courseDiv.querySelector('div:nth-child(2) > div:nth-child(3)'); + let trElements = targetDiv.querySelectorAll('tr'); + + let sum = 0; + trElements.forEach(tr => { + let lastChildText = tr.lastElementChild.innerText; + + // Merged extractDifference logic + let matches = lastChildText.match(/(\d+)\s+of\s+(\d+)/g); + if (matches) { + matches.forEach(match => { + let parts = match.match(/(\d+)\s+of\s+(\d+)/); + let current = parseInt(parts[1]); + let total = parseInt(parts[2]); + sum += (total - current); + }); + } + }); + + courseSizeDict[courseName] = sum; + } + } + + const hideSectionButtons = document.querySelectorAll('.action-sections.btn.btn-default.pull-right'); + hideSectionButtons.forEach(button => button.click()); + document.querySelector('.loadingIndicator').remove(); + + resolve(courseSizeDict); + }); +}; + + +let courseMostCommonGradeDict = {}; +let courseAvgDict = {}; +let coursePopularityDict = {}; +let courseUnitsDict = {}; + +const buildDictionary = (fetchDataFunc, dictToUpdate) => { + const loadingElement = `

Sorting...

`; + let courseListResults = document.querySelector('.course-list-results').parentElement; + courseListResults.insertBefore(htmlToElement(loadingElement), courseListResults.firstChild); + + return new Promise((resolve, reject) => { + let courseListResults = document.querySelector('.course-list-results'); + let courseDivs = Array.from(courseListResults.firstElementChild.children); + let fetchPromises = []; + + if (Object.keys(dictToUpdate).length === 0) { + for (let courseDiv of courseDivs) { + let courseName = courseDiv.querySelector('a').getAttribute('name'); + let fetchPromise = new Promise((resolve, reject) => { + fetch(`https://gophergrades-git-fork-divyesh-thirukon-b17043-umn-social-coding.vercel.app/api/class/${courseName}?url=${encodeURIComponent(window.location.href)}`, { + method: 'GET', + headers: { + 'Content-Type': 'application/json' + } + }).then(res => { + if (!res.ok) { + throw new Error(`HTTP error! Status: ${res.status}`); + } + return res.json(); + }).then(response => { + if (response.success && response.data) { + let result = fetchDataFunc(response.data); + dictToUpdate[courseName] = result; + } else { + dictToUpdate[courseName] = 0; + } + }).catch(error => { + console.error('Error fetching data:', error); + }); + resolve(); + }); + + fetchPromises.push(fetchPromise); + } + } + + Promise.all(fetchPromises) + .then(() => { + document.querySelector('.loadingIndicator').remove(); + resolve(dictToUpdate); + }) + .catch(error => { + document.querySelector('.loadingIndicator').remove(); + reject(error); + }); + }); +}; + +// const buildDictionary = (fetchDataFunc, dictToUpdate) => { +// const loadingElement = `

Sorting...

`; +// let courseListResults = document.querySelector('.course-list-results').parentElement; +// courseListResults.insertBefore(htmlToElement(loadingElement), courseListResults.firstChild); + +// return new Promise((resolve, reject) => { +// let courseListResults = document.querySelector('.course-list-results'); +// let courseDivs = Array.from(courseListResults.firstElementChild.children); +// let fetchPromises = []; + +// if (Object.keys(dictToUpdate).length === 0) { +// for (let courseDiv of courseDivs) { +// let courseName = courseDiv.querySelector('a').getAttribute('name'); +// let fetchPromise = new Promise((resolve, reject) => { +// chrome.runtime.sendMessage({ type: 'umnlolApiResponseJson', url: window.location.href, courseName: courseName }, response => { +// if (response.success && response.data) { +// let result = fetchDataFunc(response.data); +// dictToUpdate[courseName] = result; +// } else { +// dictToUpdate[courseName] = 0; +// } +// resolve(); +// }); +// }); + +// fetchPromises.push(fetchPromise); +// } +// } + +// Promise.all(fetchPromises) +// .then(() => { +// document.querySelector('.loadingIndicator').remove(); +// resolve(dictToUpdate); +// }) +// .catch(error => { +// document.querySelector('.loadingIndicator').remove(); +// reject(error); +// }); +// }); +// }; + +const buildMostCommonGradeDict = () => { + return buildDictionary( + data => { + let totalStudents = data.total_students; + let totalGrades = data.total_grades; + let numberOfAs = totalGrades["A"]; + return (numberOfAs / totalStudents) * 100; + }, + courseMostCommonGradeDict + ); +}; + +const buildCourseAvgDict = () => { + return buildDictionary( + data => { + const gpaValues = { + "A": 4.0, "A-": 3.7, "B+": 3.3, "B": 3.0, + "B-": 2.7, "C+": 2.3, "C": 2.0, "C-": 1.7, + "D+": 1.3, "D": 1.0, "F": 0.0 + }; + + let allGrades = data.total_grades; + let totalStudents = 0; + let totalWeightedGpa = 0; + + for (let grade in allGrades) { + if (gpaValues.hasOwnProperty(grade)) { + let numberOfStudents = allGrades[grade]; + let gpaValue = gpaValues[grade]; + totalStudents += numberOfStudents; + totalWeightedGpa += numberOfStudents * gpaValue; + } + } + + return totalWeightedGpa / totalStudents; + }, + courseAvgDict + ); +}; + +const buildPopularityDict = () => { + return buildDictionary( + data => data.total_students, + coursePopularityDict + ); +}; + +const buildUnitsDict = () => { + return buildDictionary( + data => data.cred_min, + courseUnitsDict + ); +}; + + +function resetDictionaries() { + courseMostCommonGradeDict = {}; + courseAvgDict = {}; + coursePopularityDict = {}; + courseUnitsDict = {}; + courseSizeDict = {}; +} diff --git a/chrome-extension/manifest.json b/chrome-extension/manifest.json index e0ed348..ac8952b 100644 --- a/chrome-extension/manifest.json +++ b/chrome-extension/manifest.json @@ -19,7 +19,8 @@ "sidebar/sidebar.css" ], "js": [ - "sidebar/sidebar.js" + "sidebar/sidebar.js", + "course_sort/course_sort.js" ] }, { diff --git a/chrome-extension/sidebar/sidebar.js b/chrome-extension/sidebar/sidebar.js index 7e6a302..2cd5640 100644 --- a/chrome-extension/sidebar/sidebar.js +++ b/chrome-extension/sidebar/sidebar.js @@ -48,288 +48,6 @@ const htmlToElement = (html) => { return template.content.firstChild; }; -// Begin code for sorting course list - -// dropdown element -const dropdownTemplate = ` -
- -
-`; - -// handle sorting options -const handleDropdownChange = (event) => { - var selectedValue = event.target.value; - console.log("my choice: " + selectedValue); - - if (selectedValue === "sortCourseCodeAsc") { - location.reload(); - } - if (selectedValue === "sortSeatsAvailDes") { - const buttons = document.querySelectorAll('.action-sections.btn.btn-default'); - buttons.forEach(button => button.click()); - setTimeout(sortBySeatsAvailable, 1000); - } - if (selectedValue === "sortMostCommonGradeDes") { - sortByMostCommonGrade(); - } - if (selectedValue === "sortPopularityDes") { - sortByPopularity(); - } - if (selectedValue === "sortUnitsDes") { - sortByUnits(); - } - // expand with more below... -}; - -const sortBySeatsAvailable = () => { - // Looks like { "CSCI4011":26, "CSCI4041":37 ... } - const courseSizeDict = {}; - - // Get the main container with class 'course-list-results' - let courseListResults = document.querySelector('.course-list-results'); - let courseDivs = Array.from(courseListResults.firstElementChild.children); - - for (let courseDiv of courseDivs) { - // Get name of course - let courseName = courseDiv.querySelector('a').getAttribute('name'); - - // Get seats available value - let targetDiv = courseDiv.querySelector('div:nth-child(2) > div:nth-child(3)'); - let trElements = targetDiv.querySelectorAll('tr'); - - let sum = 0; - trElements.forEach(tr => { - let lastChildText = tr.lastElementChild.innerText; - sum += parseFloat(extractDifference(lastChildText)) || 0; // Ensure the text is converted to a number - }); - - courseSizeDict[courseName] = sum; - } - - // sort the courseDivs in the order we want - courseDivs.sort((a, b) => { - let nameA = a.querySelector('a').getAttribute('name'); - let nameB = b.querySelector('a').getAttribute('name'); - return courseSizeDict[nameB] - courseSizeDict[nameA]; - }); - - console.log("Sorted Seats Dict: ", courseSizeDict); // For debugging purposes, display the updated dictionary - console.log("Sorted Seats Course Divs: ", courseDivs); // For debugging purposes, display the updated divs - - // remove all the courses to add our own ordering of the courses - courseListResults.firstElementChild.innerHTML = ''; - courseDivs.forEach(courseDiv => { - courseListResults.firstElementChild.appendChild(courseDiv); - }); - - // click all the 'hide' buttons - const buttons2 = document.querySelectorAll('.action-sections.btn.btn-default.pull-right'); - buttons2.forEach(button => button.click()); - - // ensure this value is selected - document.querySelector('.size-dropdown').value = 'sortSeatsAvailDes'; -}; - -// Helper function to determine seats available -function extractDifference(text) { - let matches = text.match(/(\d+)\s+of\s+(\d+)/g); - let totalDifference = 0; - - if (matches) { - matches.forEach(match => { - let parts = match.match(/(\d+)\s+of\s+(\d+)/); - let current = parseInt(parts[1]); - let total = parseInt(parts[2]); - totalDifference += (total - current); - }); - } - return totalDifference; -} - -const sortByMostCommonGrade = () => { - let courseMostCommonGradeDict = {}; - - // Get the main container with class 'course-list-results' - let courseListResults = document.querySelector('.course-list-results'); - let courseDivs = Array.from(courseListResults.firstElementChild.children); - - // Array to hold all fetch promises - let fetchPromises = []; - - for (let courseDiv of courseDivs) { - let courseName = courseDiv.querySelector('a').getAttribute('name'); - - // Push the fetch promise into the array - let fetchPromise = new Promise((resolve, reject) => { - chrome.runtime.sendMessage({ type: 'umnlolApiResponseJson', url: window.location.href, courseName: courseName }, response => { - // If the response is consistently unsuccessful or there's no data - // Check background.js and ctrl+f for 'umnlolApiResponseJson' - // That is where I handled the request - - if (response.success && response.data) { - let totalStudents = response.data.total_students; - let totalGrades = response.data.total_grades; - let numberOfAs = totalGrades["A"]; - let val = (numberOfAs / totalStudents) * 100; - console.log(`Percentage of A's for ${courseName}: ${val}%`); - - // Store the calculated percentage in the dictionary - courseMostCommonGradeDict[courseName] = val; - } else { - courseMostCommonGradeDict[courseName] = 0; - } - resolve(); // Resolve the promise once processing is done - }); - }); - - fetchPromises.push(fetchPromise); - } - - // Wait for all fetch promises to resolve - Promise.all(fetchPromises) - .then(() => { - // Once all promises have resolved (fetch requests are complete and data is populated), sort courseDivs - courseDivs.sort((a, b) => { - let nameA = a.querySelector('a').getAttribute('name'); - let nameB = b.querySelector('a').getAttribute('name'); - return courseMostCommonGradeDict[nameB] - courseMostCommonGradeDict[nameA]; - }); - - console.log("Most Common Grade Dict: ", courseMostCommonGradeDict); // For debugging purposes, display the updated dictionary - console.log("Sorted Most Common Grade Course Divs: ", courseDivs); // For debugging purposes, display the updated divs - - // Clear and update the course list results - courseListResults.firstElementChild.innerHTML = ''; - courseDivs.forEach(courseDiv => { - courseListResults.firstElementChild.appendChild(courseDiv); - }); - - // Set dropdown value after sorting - document.querySelector('.size-dropdown').value = 'sortMostCommonGradeDes'; - }) - .catch(error => { - console.error('Error fetching course data:', error); - }); -}; - - -// NOTE: the rest of the functions follow very similar code from the last two. -// The first one is to build dictionaries from information that is already on the schedule builder page (eg. seats available, course units) -// The second one is to build dictionaries from information using an API (eg. popularity, most-common grade) -// Refer to them if you are confused about something - - -const sortByPopularity = () => { - let coursePopularityDict = {}; - - // Get the main container with class 'course-list-results' - let courseListResults = document.querySelector('.course-list-results'); - let courseDivs = Array.from(courseListResults.firstElementChild.children); - - // Array to hold all fetch promises - let fetchPromises = []; - - for (let courseDiv of courseDivs) { - let courseName = courseDiv.querySelector('a').getAttribute('name'); - - // Push the fetch promise into the array - let fetchPromise = new Promise((resolve, reject) => { - chrome.runtime.sendMessage({ type: 'umnlolApiResponseJson', url: window.location.href, courseName: courseName }, response => { - if (response.success && response.data) { - let totalStudents = response.data.total_students; - coursePopularityDict[courseName] = totalStudents; - } else { - coursePopularityDict[courseName] = 0; - } - resolve(); // Resolve the promise once processing is done - }); - }); - - fetchPromises.push(fetchPromise); - } - - // Wait for all fetch promises to resolve - Promise.all(fetchPromises) - .then(() => { - // Once all promises have resolved (fetch requests are complete and data is populated), sort courseDivs - courseDivs.sort((a, b) => { - let nameA = a.querySelector('a').getAttribute('name'); - let nameB = b.querySelector('a').getAttribute('name'); - return coursePopularityDict[nameB] - coursePopularityDict[nameA]; - }); - - console.log("Most Common Grade Dict: ", coursePopularityDict); // For debugging purposes, display the updated dictionary - console.log("Sorted Most Common Grade Course Divs: ", courseDivs); // For debugging purposes, display the updated divs - - // Clear and update the course list results - courseListResults.firstElementChild.innerHTML = ''; - courseDivs.forEach(courseDiv => { - courseListResults.firstElementChild.appendChild(courseDiv); - }); - - // Set dropdown value after sorting - document.querySelector('.size-dropdown').value = 'sortPopularityDes'; - }) - .catch(error => { - console.error('Error fetching course data:', error); - }); -}; - - -const sortByUnits = () => { - const courseUnitsDict = {}; - - // Get the main container with class 'course-list-results' - let courseListResults = document.querySelector('.course-list-results'); - let courseDivs = Array.from(courseListResults.firstElementChild.children); - - for (let courseDiv of courseDivs) { - let courseName = courseDiv.querySelector('a').getAttribute('name'); - - // Navigate to the correct div - let targetDiv = courseDiv.querySelector('div:nth-child(2) > div:nth-child(2)').children; - let trElements = targetDiv[targetDiv.length - 2]; - - myCleanText = trElements.innerHTML.trim().replace(/\s+/g, ''); - numberMatch = (myCleanText.match(/(\d+(\.\d+)?)/)); - - if (numberMatch) { - console.log(parseFloat(numberMatch[0])); // Output: 2 - courseUnitsDict[courseName] = parseFloat(numberMatch[0]); - } else { - // No number found - courseUnitsDict[courseName] = 0; - } - - - } - - courseDivs.sort((a, b) => { - let nameA = a.querySelector('a').getAttribute('name'); - let nameB = b.querySelector('a').getAttribute('name'); - return courseUnitsDict[nameB] - courseUnitsDict[nameA]; - }); - - console.log("Course Units Dict: ", courseUnitsDict); // For debugging purposes, display the updated dictionary - console.log("Sorted Units Course Divs: ", courseDivs); // For debugging purposes, display the updated divs - - courseListResults.firstElementChild.innerHTML = ''; - courseDivs.forEach(courseDiv => { - courseListResults.firstElementChild.appendChild(courseDiv); - }); - - document.querySelector('.size-dropdown').value = 'sortUnitsDes'; -}; - -// end sorting feature - const iframeTemplate = ` @@ -440,13 +158,18 @@ const loadCourseSchedule = (courseSchedule) => { }; const loadDropdown = () => { - const courseListOptions = document.querySelector(".course-list-options"); - const emptyDiv = courseListOptions.firstElementChild; - const dropdownElement = htmlToElement(dropdownTemplate); - const firstDiv = emptyDiv.firstElementChild; - emptyDiv.insertBefore(dropdownElement, firstDiv); + if ((window.location.host + window.location.pathname).startsWith('schedulebuilder.umn.edu/explore/')) { + const courseListOptions = document.querySelector(".course-list-options"); + const emptyDiv = courseListOptions.firstElementChild; + const dropdownElement = htmlToElement(dropdownTemplate); + const firstDiv = emptyDiv.firstElementChild; + emptyDiv.insertBefore(dropdownElement, firstDiv); + } }; +let currentPage = 0; +let lastPage = 0; + const onAppChange = async () => { const courseList = document.querySelector(".course-list-results"); const courseInfo = document.querySelector("#crse-info"); @@ -466,11 +189,21 @@ const onAppChange = async () => { var courseSortDropdown = document.querySelector('.size-dropdown'); if (courseSortDropdown) { courseSortDropdown.addEventListener('change', handleDropdownChange); - } else { - console.log("Course-sort dropdown not found for whatever reason. This feature will not work."); } - if (!courseSortDropdown) loadDropdown(); + + const activeItem = document.querySelector('.page-item.active').firstElementChild; + if (activeItem) { + lastPage = activeItem.getAttribute('page-value'); + } + if (currentPage != lastPage) { + if (currentPage != 0 && document.querySelector('.size-dropdown').value != 'sortCourseCodeAsc') { + location.reload(); + // we reload because of some weird bug that expands all sections + } + currentPage = lastPage; + resetDictionaries(); + } }; diff --git a/frontend/next.config.js b/frontend/next.config.js index 1e7615a..27abadd 100644 --- a/frontend/next.config.js +++ b/frontend/next.config.js @@ -25,7 +25,7 @@ const nextConfig = { { key: "Access-Control-Allow-Credentials", value: "true" }, { key: "Access-Control-Allow-Origin", value: "*" }, { key: "Access-Control-Allow-Methods", value: "GET,DELETE,PATCH,POST,PUT" }, - { key: "Access-Control-Allow-Headers", value: "X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version" }, + { key: "Access-Control-Allow-Headers", value: "Origin, X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version" }, ] } ] From 1bfe72f1907d3732a5aa7373e6f15b0b5077042c Mon Sep 17 00:00:00 2001 From: Divyesh Thirukonda Gowri Shankar Date: Sat, 22 Jun 2024 00:33:33 -0500 Subject: [PATCH 11/17] Fixed fetching --- chrome-extension/course_sort/course_sort.js | 86 ++++++--------------- 1 file changed, 24 insertions(+), 62 deletions(-) diff --git a/chrome-extension/course_sort/course_sort.js b/chrome-extension/course_sort/course_sort.js index 5091881..2db0672 100644 --- a/chrome-extension/course_sort/course_sort.js +++ b/chrome-extension/course_sort/course_sort.js @@ -136,29 +136,31 @@ const buildDictionary = (fetchDataFunc, dictToUpdate) => { for (let courseDiv of courseDivs) { let courseName = courseDiv.querySelector('a').getAttribute('name'); let fetchPromise = new Promise((resolve, reject) => { - fetch(`https://gophergrades-git-fork-divyesh-thirukon-b17043-umn-social-coding.vercel.app/api/class/${courseName}?url=${encodeURIComponent(window.location.href)}`, { - method: 'GET', - headers: { - 'Content-Type': 'application/json' - } - }).then(res => { - if (!res.ok) { - throw new Error(`HTTP error! Status: ${res.status}`); - } - return res.json(); - }).then(response => { - if (response.success && response.data) { - let result = fetchDataFunc(response.data); - dictToUpdate[courseName] = result; - } else { - dictToUpdate[courseName] = 0; - } - }).catch(error => { - console.error('Error fetching data:', error); - }); - resolve(); + try { + // For debuggging, use `https://gophergrades-9wharkgah-umn-social-coding.vercel.app/api/class/${courseName}` + fetch(`https://umn.lol/api/class/${courseName}`) + .then(res => { + if (!res.ok) { + dictToUpdate[courseName] = 0; + } + return res.json(); + }).then(response => { + if (response.success && response.data) { + let result = fetchDataFunc(response.data); + dictToUpdate[courseName] = result; + } else { + dictToUpdate[courseName] = 0; + } + resolve(); + }).catch(error => { + console.error(courseSortingLogPrefix + 'Error fetching data:', error); + }); + } catch { + dictToUpdate[courseName] = 0; + resolve(); + } + }); - fetchPromises.push(fetchPromise); } } @@ -175,46 +177,6 @@ const buildDictionary = (fetchDataFunc, dictToUpdate) => { }); }; -// const buildDictionary = (fetchDataFunc, dictToUpdate) => { -// const loadingElement = `

Sorting...

`; -// let courseListResults = document.querySelector('.course-list-results').parentElement; -// courseListResults.insertBefore(htmlToElement(loadingElement), courseListResults.firstChild); - -// return new Promise((resolve, reject) => { -// let courseListResults = document.querySelector('.course-list-results'); -// let courseDivs = Array.from(courseListResults.firstElementChild.children); -// let fetchPromises = []; - -// if (Object.keys(dictToUpdate).length === 0) { -// for (let courseDiv of courseDivs) { -// let courseName = courseDiv.querySelector('a').getAttribute('name'); -// let fetchPromise = new Promise((resolve, reject) => { -// chrome.runtime.sendMessage({ type: 'umnlolApiResponseJson', url: window.location.href, courseName: courseName }, response => { -// if (response.success && response.data) { -// let result = fetchDataFunc(response.data); -// dictToUpdate[courseName] = result; -// } else { -// dictToUpdate[courseName] = 0; -// } -// resolve(); -// }); -// }); - -// fetchPromises.push(fetchPromise); -// } -// } - -// Promise.all(fetchPromises) -// .then(() => { -// document.querySelector('.loadingIndicator').remove(); -// resolve(dictToUpdate); -// }) -// .catch(error => { -// document.querySelector('.loadingIndicator').remove(); -// reject(error); -// }); -// }); -// }; const buildMostCommonGradeDict = () => { return buildDictionary( From e3c2bcda8fedf8dc0fd62ab39e35b2654de5067c Mon Sep 17 00:00:00 2001 From: Divyesh Thirukonda Gowri Shankar Date: Sat, 22 Jun 2024 00:34:43 -0500 Subject: [PATCH 12/17] updated background script --- chrome-extension/background.js | 28 ++-------------------------- 1 file changed, 2 insertions(+), 26 deletions(-) diff --git a/chrome-extension/background.js b/chrome-extension/background.js index 67810cf..c4fd370 100644 --- a/chrome-extension/background.js +++ b/chrome-extension/background.js @@ -65,30 +65,6 @@ chrome.runtime.onInstalled.addListener(async () => { }); chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { - if (message.type === "umnlolApiResponseJson") { - // Handle JSON message type with a GET request - fetch(`https://umn.lol/api/class/${message.courseName}?url=${encodeURIComponent(message.url)}`, { - method: 'GET', - headers: { - 'Content-Type': 'application/json' - } - }).then(res => { - if (!res.ok) { - throw new Error(`HTTP error! Status: ${res.status}`); - } - return res.json(); - }).then(res => { - sendResponse(res); // Send response back to the sender - }).catch(error => { - console.error('Error fetching data:', error); - sendResponse({ error: 'Error fetching data' }); // Send an error response - }); - - // Return true to indicate that sendResponse will be called asynchronously - return true; - } else { - // Handle other message types if necessary - const { type } = message; - RuntimeMessages[type](message); // Assuming RuntimeMessages is defined elsewhere - } + const { type } = message; + RuntimeMessages[type](message); }); From 145ef3163a5cb9086d238364b859bcf5892e36be Mon Sep 17 00:00:00 2001 From: Divyesh Thirukonda Gowri Shankar Date: Sat, 22 Jun 2024 00:36:14 -0500 Subject: [PATCH 13/17] Added tag --- chrome-extension/course_sort/course_sort.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chrome-extension/course_sort/course_sort.js b/chrome-extension/course_sort/course_sort.js index 2db0672..875c89e 100644 --- a/chrome-extension/course_sort/course_sort.js +++ b/chrome-extension/course_sort/course_sort.js @@ -64,7 +64,7 @@ const sortCourses = (buildDictFunc, dropdownValue, additionalLogic = () => {}) = document.querySelector('.size-dropdown').value = dropdownValue; }).catch(error => { - console.error('Error building course dictionary:', error); + console.error(courseSortingLogPrefix + 'Error building course dictionary:', error); }); }; From b3580179bd37f76464b1bf5796cb384a7c62c8dc Mon Sep 17 00:00:00 2001 From: Divyesh Thirukonda Gowri Shankar Date: Sat, 22 Jun 2024 00:39:24 -0500 Subject: [PATCH 14/17] fixed background --- chrome-extension/background.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/chrome-extension/background.js b/chrome-extension/background.js index c4fd370..4876079 100644 --- a/chrome-extension/background.js +++ b/chrome-extension/background.js @@ -53,6 +53,11 @@ const RuntimeMessages = { }, }; +chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { + const { type } = message; + RuntimeMessages[type](message); +}); + chrome.runtime.onInstalled.addListener(async () => { const defaultSettingCodes = defaultSettings.reduce((acc, section) => { section.settings.forEach((setting) => { @@ -62,9 +67,4 @@ chrome.runtime.onInstalled.addListener(async () => { }, {}); await chrome.storage.sync.set({ settings: defaultSettingCodes }); -}); - -chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { - const { type } = message; - RuntimeMessages[type](message); -}); +}); \ No newline at end of file From f0306f786cfc76fab56fd26b2a6fd35d0bd11661 Mon Sep 17 00:00:00 2001 From: Divyesh Thirukonda Gowri Shankar Date: Sat, 22 Jun 2024 00:41:11 -0500 Subject: [PATCH 15/17] Update background.js --- chrome-extension/background.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/chrome-extension/background.js b/chrome-extension/background.js index 4876079..d69408c 100644 --- a/chrome-extension/background.js +++ b/chrome-extension/background.js @@ -53,9 +53,9 @@ const RuntimeMessages = { }, }; -chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { - const { type } = message; - RuntimeMessages[type](message); +chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { + const { type } = request; + RuntimeMessages[type](request); }); chrome.runtime.onInstalled.addListener(async () => { @@ -67,4 +67,4 @@ chrome.runtime.onInstalled.addListener(async () => { }, {}); await chrome.storage.sync.set({ settings: defaultSettingCodes }); -}); \ No newline at end of file +}); From 57a0400672d117d55d7bee7bee93e73a2b0c164d Mon Sep 17 00:00:00 2001 From: Divyesh Thirukonda Gowri Shankar Date: Sat, 6 Jul 2024 13:59:08 -0500 Subject: [PATCH 16/17] Removed uneccesary comment --- chrome-extension/course_sort/course_sort.js | 1 - 1 file changed, 1 deletion(-) diff --git a/chrome-extension/course_sort/course_sort.js b/chrome-extension/course_sort/course_sort.js index 875c89e..c9ef7e4 100644 --- a/chrome-extension/course_sort/course_sort.js +++ b/chrome-extension/course_sort/course_sort.js @@ -137,7 +137,6 @@ const buildDictionary = (fetchDataFunc, dictToUpdate) => { let courseName = courseDiv.querySelector('a').getAttribute('name'); let fetchPromise = new Promise((resolve, reject) => { try { - // For debuggging, use `https://gophergrades-9wharkgah-umn-social-coding.vercel.app/api/class/${courseName}` fetch(`https://umn.lol/api/class/${courseName}`) .then(res => { if (!res.ok) { From 3c81de1ac2315f582674228c738dfd627ac639ba Mon Sep 17 00:00:00 2001 From: Divyesh Thirukonda <72324738+Divyesh-Thirukonda@users.noreply.github.com> Date: Tue, 14 Jan 2025 21:11:42 -0600 Subject: [PATCH 17/17] Update course_sort.js --- chrome-extension/course_sort/course_sort.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chrome-extension/course_sort/course_sort.js b/chrome-extension/course_sort/course_sort.js index c9ef7e4..dc0b400 100644 --- a/chrome-extension/course_sort/course_sort.js +++ b/chrome-extension/course_sort/course_sort.js @@ -8,7 +8,7 @@ const dropdownTemplate = `