diff --git a/chrome-extension/course_sort/course_sort.js b/chrome-extension/course_sort/course_sort.js new file mode 100644 index 0000000..dc0b400 --- /dev/null +++ b/chrome-extension/course_sort/course_sort.js @@ -0,0 +1,241 @@ +const courseSortingLogPrefix = '[Course Sorting Script] '; + +console.log(courseSortingLogPrefix + "Course Sorting script loaded"); + +// dropdown element +const dropdownTemplate = ` +
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(courseSortingLogPrefix + '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) => { + try { + 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); + } + } + + 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 b245809..2cd5640 100644 --- a/chrome-extension/sidebar/sidebar.js +++ b/chrome-extension/sidebar/sidebar.js @@ -48,6 +48,8 @@ const htmlToElement = (html) => { return template.content.firstChild; }; + + const iframeTemplate = `