From 9e61e056e20a51416055e121578b819abb04f702 Mon Sep 17 00:00:00 2001 From: heartgg Date: Mon, 20 Mar 2023 23:39:16 -0500 Subject: [PATCH 1/9] restructure background and content --- src/{background/index.ts => background.ts} | 25 +++++++--------------- src/background/messages/getScrapeData.ts | 7 ++---- src/backgroundInterfaces.ts | 9 -------- src/{background => }/content.ts | 23 ++++++++++---------- src/popup.tsx | 10 ++++----- 5 files changed, 27 insertions(+), 47 deletions(-) rename src/{background/index.ts => background.ts} (67%) delete mode 100644 src/backgroundInterfaces.ts rename src/{background => }/content.ts (90%) diff --git a/src/background/index.ts b/src/background.ts similarity index 67% rename from src/background/index.ts rename to src/background.ts index 17fe89b..1224923 100644 --- a/src/background/index.ts +++ b/src/background.ts @@ -1,20 +1,14 @@ -import contentFile from 'url:./content.ts'; -import type { CourseHeader, ShowCourseTabPayload } from "../backgroundInterfaces"; -import ScrapeCourseData from "./content"; -import { redirect } from "react-router-dom"; +import { scrapeCourseData, CourseHeader } from "~content"; +export interface ShowCourseTabPayload { + courseData: CourseHeader; + professors: string[]; +} + +// State vars let courseTabId: number = null; let scrapedCourseData: ShowCourseTabPayload = null; -const messageType = { - SHOW_COURSE_TAB: "SHOW_COURSE_TAB", - SHOW_PROFESSOR_TAB: "SHOW_PROFESSOR_TAB", - REQUEST_PROFESSORS: "REQUEST_PROFESSORS", - GET_NEBULA_PROFESSOR: "GET_NEBULA_PROFESSOR", - GET_NEBULA_COURSE: "GET_NEBULA_COURSE", - GET_NEBULA_SECTIONS: "GET_NEBULA_SECTIONS" -}; - /** Injects the content script if we hit a course page */ chrome.webNavigation.onHistoryStateUpdated.addListener(details => { if (/^.*:\/\/utdallas\.collegescheduler\.com\/terms\/.*\/courses\/.+$/.test( @@ -26,15 +20,12 @@ chrome.webNavigation.onHistoryStateUpdated.addListener(details => { tabId: details.tabId, }, world: "MAIN", - // below is a gigamega hack from https://github.com/PlasmoHQ/plasmo/issues/150 // content script injection only works reliably on the prod packaged extension // b/c of the plasmo dev server connections - func: ScrapeCourseData, + func: scrapeCourseData, }, function (resolve) { if (resolve && resolve[0] && resolve[0].result) { const result: ShowCourseTabPayload = resolve[0].result; - - // Now let's save this scraped value. scrapedCourseData = result; }; }); diff --git a/src/background/messages/getScrapeData.ts b/src/background/messages/getScrapeData.ts index 5c950d4..ddd277a 100644 --- a/src/background/messages/getScrapeData.ts +++ b/src/background/messages/getScrapeData.ts @@ -1,12 +1,9 @@ import type { PlasmoMessaging } from "@plasmohq/messaging" -import {getScrapedCourseData} from "../index"; +import { getScrapedCourseData } from "../../background"; const handler: PlasmoMessaging.MessageHandler = async (req, res) => { const data = getScrapedCourseData(); - - res.send({ - data - }) + res.send(data) } export default handler \ No newline at end of file diff --git a/src/backgroundInterfaces.ts b/src/backgroundInterfaces.ts deleted file mode 100644 index be9a2e9..0000000 --- a/src/backgroundInterfaces.ts +++ /dev/null @@ -1,9 +0,0 @@ -export interface CourseHeader { - subjectPrefix: string; - courseNumber: string; -} - -export interface ShowCourseTabPayload { - courseData: CourseHeader; - professors: string[]; -} \ No newline at end of file diff --git a/src/background/content.ts b/src/content.ts similarity index 90% rename from src/background/content.ts rename to src/content.ts index 1ac064d..e6b6d09 100644 --- a/src/background/content.ts +++ b/src/content.ts @@ -1,20 +1,21 @@ + +export interface CourseHeader { + subjectPrefix: string; + courseNumber: string; +} + +// Plasmo CS config export +export const config = { + matches: ["https://utdallas.collegescheduler.com/terms/*/courses/*"] +} + /** * This script runs when we select a course in the Scheduler * - It scrapes the page for course data * - It scrapes the names of instructors * - It injects the instructor names into the section table */ -import type { CourseHeader } from "../backgroundInterfaces"; - -export default async function ScrapeCourseData() { - const config = { - matches: ["https://utdallas.collegescheduler.com/terms/*/courses/*"] - } - - var messageType = { - SHOW_COURSE_TAB: 'SHOW_COURSE_TAB', - SHOW_PROFESSOR_TAB: 'SHOW_PROFESSOR_TAB', - } +export async function scrapeCourseData() { let [courseData, professors] = await Promise.all([getCourseInfo(), injectAndGetProfessorNames()]); return {courseData: courseData, professors: professors}; diff --git a/src/popup.tsx b/src/popup.tsx index fe3fd94..41db85a 100644 --- a/src/popup.tsx +++ b/src/popup.tsx @@ -4,11 +4,11 @@ import { sendToBackground } from "@plasmohq/messaging" import "~/style.css" // Example of how to fetch the scraped data from the background script, given that it exists -let funct = async () => { -const resp = await sendToBackground({ - name: "getScrapeData", -}) -console.log("Response is ",resp) +async function funct () { + const resp = await sendToBackground({ + name: "getScrapeData", + }) + console.log("Response is ", resp) } funct(); From 8f9170f17efcc062b144eb42c6de6ff127d23a59 Mon Sep 17 00:00:00 2001 From: heartgg Date: Tue, 21 Mar 2023 00:12:43 -0500 Subject: [PATCH 2/9] fetch cleanup --- src/data/config.ts | 29 ++++++++++ src/data/fetch.ts | 103 ++++++++++++----------------------- src/data/fetchFromRmp.ts | 3 +- src/data/fetchSection.ts | 67 ----------------------- src/data/interfaces.ts | 88 ++++++++++++++++++++++++++++++ src/data/nebulaInterfaces.ts | 58 -------------------- 6 files changed, 153 insertions(+), 195 deletions(-) create mode 100644 src/data/config.ts delete mode 100644 src/data/fetchSection.ts create mode 100644 src/data/interfaces.ts delete mode 100644 src/data/nebulaInterfaces.ts diff --git a/src/data/config.ts b/src/data/config.ts new file mode 100644 index 0000000..41e1217 --- /dev/null +++ b/src/data/config.ts @@ -0,0 +1,29 @@ +export const HEADERS = { + "Authorization": "Basic dGVzdDp0ZXN0", + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36", + "Content-Type": "application/json" +} + +export const PROFESSOR_QUERY = { + "query":"query RatingsListQuery($id: ID!) {node(id: $id) {... on Teacher {legacyId school {id} courseCodes {courseName courseCount} firstName lastName numRatings avgDifficulty avgRating department wouldTakeAgainPercent teacherRatingTags { tagCount tagName } ratingsDistribution { total r1 r2 r3 r4 r5 } }}}", + "variables": {} +} + +export const NEBULA_FETCH_OPTIONS = { + method: "GET", + headers: { + "x-api-key": unRegister("EM~eW}G<}4qx41fp{H=I]OZ5MF6T:1x{ | null { + try { + const res = await fetch(`https://api.utdnebula.com/course?course_number=${params.courseNumber}&subject_prefix=${params.subjectPrefix}`, NEBULA_FETCH_OPTIONS); + const json = await res.json(); + if (json.data == null) throw new Error("Null data"); + const data: CourseInterface = json.data[0]; + return data; + } catch (error) { + return null; + } } -export const PROFESSOR_QUERY = { - "query":"query RatingsListQuery($id: ID!) {node(id: $id) {... on Teacher {legacyId school {id} courseCodes {courseName courseCount} firstName lastName numRatings avgDifficulty avgRating department wouldTakeAgainPercent teacherRatingTags { tagCount tagName } ratingsDistribution { total r1 r2 r3 r4 r5 } }}}", - "variables": {} +async function fetchNebulaProfessor(params: FetchProfessorParameters): Promise | null { + try { + const res = await fetch(`https://api.utdnebula.com/professor?first_name=${params.firstName}&last_name=${params.lastName}`, NEBULA_FETCH_OPTIONS); + const json = await res.json(); + if (json.data == null) throw new Error("Null data"); + const data: ProfessorInterface = json.data[0]; + return data; + } catch (error) { + return null; + } } -import type { fetchCourse_params, courseData, fetchProfessor_params, professorData } from "./nebulaInterfaces"; - -// Hash function to get the API key -function unRegister(myVar: string) -{ - let newVar = ""; - for (var i = 0; i < myVar.length; i++) - { - let a = myVar.charCodeAt(i); - a = ((a*2)-8)/2; - newVar = newVar.concat(String.fromCharCode(a)); - } - return newVar; -} - - -// TESTING INFO -// Install ts-node locally -// add "type": "module" to package.json - -// ts-node-esm fetch.ts - - - - -let NEBULA_API_KEY = "EM~eW}G<}4qx41fp{H=I]OZ5MF6T:1x{ | null { - try { - const res = await fetch(`https://api.utdnebula.com/course?course_number=${paramsObj.courseNumber}&subject_prefix=${paramsObj.subjectPrefix}`, fetchOptions); - const json = await res.json(); - if (json.data == null) throw new Error("Null data"); - const data: courseData = json.data[0]; - return data; - } catch (error) { - return null; - } -} - -async function fetchNebulaProfessor(paramsObj: fetchProfessor_params): Promise | null { - try { - const res = await fetch(`https://api.utdnebula.com/professor?first_name=${paramsObj.firstName}&last_name=${paramsObj.lastName}`, fetchOptions); - const json = await res.json(); - if (json.data == null) throw new Error("Null data"); - const data: professorData = json.data[0]; - return data; - } catch (error) { - return null; - } +async function fetchNebulaSections(params: FetchSectionParameters): Promise | null { + try { + const res = await fetch(`https://api.utdnebula.com/section?course_reference=${params.courseReference}&professors=${params.professorReference}`, NEBULA_FETCH_OPTIONS); + const json = await res.json(); + if(json.data == null) throw new Error("Null data"); + const data: SectionInterface[] = json.data; + return data; + } catch(error) { + return null; + } } // Test function. Commented out. Uncomment to test. console.log(await fetchNebulaCourse({courseNumber: "4337", subjectPrefix: "CS"})); -console.log(await fetchNebulaProfessor({firstName: "Scott", lastName: "Dollinger"})); \ No newline at end of file +console.log(await fetchNebulaProfessor({firstName: "Scott", lastName: "Dollinger"})); +console.log(await fetchNebulaSections({ courseReference: "623fedfabf28b6d88d6c7742", professorReference: "623fc346b8bc16815e8679a9" })) \ No newline at end of file diff --git a/src/data/fetchFromRmp.ts b/src/data/fetchFromRmp.ts index 127e1fb..327c0b3 100644 --- a/src/data/fetchFromRmp.ts +++ b/src/data/fetchFromRmp.ts @@ -1,5 +1,4 @@ -import fetch from "node-fetch" -import {HEADERS, PROFESSOR_QUERY} from "~data/fetch"; +import { HEADERS, PROFESSOR_QUERY } from "~data/config"; function getProfessorUrl(professorName: string, schoolId: string): string { return `https://www.ratemyprofessors.com/search/teachers?query=${encodeURIComponent(professorName)}&sid=${btoa(`School-${schoolId}`)}` diff --git a/src/data/fetchSection.ts b/src/data/fetchSection.ts deleted file mode 100644 index 8271f71..0000000 --- a/src/data/fetchSection.ts +++ /dev/null @@ -1,67 +0,0 @@ - -type FetchSection_params = { - courseReference: string; - professorReference: string; -}; - -type Section = { - term: string; - title: string; - course_number: string; - school: string; - location: string; - activity_type: string; - class_number: string; - days: string; - assistants: string; - times: string; - topic: string; - core_area: string; - department: string; - section_name: string; - course_prefix: string; - instructors: string; - section_number: string; -}; - -type SectionList = { - sections: Section[]; -}; - -//Hash to get API key -function unRegister(myVar: string) { - let newVar = ""; - for(var i = 0; i < myVar.length; i++) { - let a = myVar.charCodeAt(i); - a = ((a*2)-8)/2; - newVar = newVar.concat(String.fromCharCode(a)); - } - return newVar; -} - -//Nebula key -let NEBULA_API_KEY = "EM~eW}G<}4qx41fp{H=I]OZ5MF6T:1x{ { - try { - const res = await fetch(`https://api.utdnebula.com/section?course_reference=${paramsObj.courseReference}&professors=${paramsObj.professorReference}`, fetchOptions); - const json = await res.json(); - if(json.data == null) { - throw new Error("Null data"); - } - const data: SectionList = json.data; - return data; - } catch(error) { - return null; - } -} - -console.log((await fetchNebulaSections({courseReference: "3377", professorReference: "Peterson"}))); diff --git a/src/data/interfaces.ts b/src/data/interfaces.ts new file mode 100644 index 0000000..877eb9e --- /dev/null +++ b/src/data/interfaces.ts @@ -0,0 +1,88 @@ +export interface FetchProfessorParameters { + firstName: string; + lastName: string; +}; + +export interface FetchCourseParameters { + subjectPrefix: string; + courseNumber: string; +} + +export interface FetchSectionParameters { + courseReference: string; + professorReference: string; +}; + +interface Requisites { + options: any[]; + required: number; + type: string; +} + +export interface CourseInterface { + __v: number; + _id: string; + activity_type: string; + class_level: string; + co_or_pre_requisites: Requisites; + corequisites: Requisites; + course_number: string; + credit_hours: string; + description: string; + grading: string; + internal_course_number: string; + laboratory_contact_hours: string; + lecture_contact_hours: string; + offering_frequency: string; + prerequisites: Requisites; + school: string; + sections: string[]; + subject_prefix: string; + title: string; +} + +interface Office { + building: string; + room: string; + map_uri: string +} + +export interface ProfessorInterface { + __v: number; + _id: string; + email: string; + first_name: string; + image_uri: string; + last_name: string; + office: Office; + office_hours: any[]; + phone_number: string; + profile_uri: string; + sections: string[]; + titles: string[]; +} + +export interface SectionInterface { + __v: number; + _id: string; + academic_session: { + end_date: string; + name: string; + start_date: string; + }; + attributes: any[]; + core_flags: any[]; + course_reference: string; + grade_distribution: number[]; + instruction_mode: string; + internal_class_number: string; + meetings: any[]; + professors: string[]; + section_corequisites: { + options: any[]; + type: string; + }; + section_number: string; + syllabus_uri: string; + teaching_assistants: any[]; +}; \ No newline at end of file diff --git a/src/data/nebulaInterfaces.ts b/src/data/nebulaInterfaces.ts deleted file mode 100644 index 160ed3c..0000000 --- a/src/data/nebulaInterfaces.ts +++ /dev/null @@ -1,58 +0,0 @@ -export interface fetchProfessor_params { - firstName: string, - lastName: string -}; - -export interface fetchCourse_params { - subjectPrefix: string, - courseNumber: string -} - -export interface requisites { - options: Array, - required: number, - type: string -} - -export interface courseData { - __v: number, - _id: string, - activity_type: string, - class_level: string, - co_or_pre_requisites: requisites, - corequisites: requisites, - course_number: string, - credit_hours: string, - description: string, - grading: string, - internal_course_number: string, - laboratory_contact_hours: string, - lecture_contact_hours: string, - offering_frequency: string, - prerequisites: requisites, - school: string, - sections: Array, - subject_prefix: string, - title: string, -} - -export interface office { - building: string, - room: string, - map_uri: string -} - -export interface professorData { - __v: number, - _id: string, - email: string, - first_name: string, - image_uri: string, - last_name: string, - office: office, - office_hours: Array, - phone_number: string, - profile_uri: string, - sections: Array, - titles: Array, -} From 8e3b9da7b82c43c837a5879729203bdae179d773 Mon Sep 17 00:00:00 2001 From: heartgg Date: Tue, 21 Mar 2023 12:19:17 -0500 Subject: [PATCH 3/9] big data integration --- src/background.ts | 2 +- src/components/MiniGrades.tsx | 6 ++- src/components/MiniProfessor.tsx | 6 +-- src/components/MiniScore.tsx | 3 +- src/components/ProfileHeader.tsx | 5 ++- src/content.ts | 5 +-- src/data/builder.ts | 52 ++++++++++++++++++++++ src/data/config.ts | 2 + src/data/fetch.ts | 12 ++--- src/data/fetchFromRmp.ts | 32 +++++++++++++- src/popup.tsx | 11 ----- src/routes/CoursePage.tsx | 75 +++++++++++++++++++++++++++++++- src/routes/ProfessorProfile.tsx | 17 ++++---- src/routes/about.tsx | 38 ---------------- src/routes/home.tsx | 17 -------- src/routes/index.tsx | 7 +-- src/utils/styling.ts | 15 +++++++ 17 files changed, 205 insertions(+), 100 deletions(-) create mode 100644 src/data/builder.ts delete mode 100644 src/routes/home.tsx diff --git a/src/background.ts b/src/background.ts index 1224923..c0f2800 100644 --- a/src/background.ts +++ b/src/background.ts @@ -1,7 +1,7 @@ import { scrapeCourseData, CourseHeader } from "~content"; export interface ShowCourseTabPayload { - courseData: CourseHeader; + header: CourseHeader; professors: string[]; } diff --git a/src/components/MiniGrades.tsx b/src/components/MiniGrades.tsx index bc6ad23..832d707 100644 --- a/src/components/MiniGrades.tsx +++ b/src/components/MiniGrades.tsx @@ -1,12 +1,14 @@ +import { useState } from "react"; import Chart from "react-apexcharts" import { miniGradeChartOptions } from "~utils/styling"; import type { GradeDistribution } from "./ProfileGrades" export const MiniGrades = ({ gradeDistributionData } : { gradeDistributionData: GradeDistribution }) => { - miniGradeChartOptions.title.text = gradeDistributionData.name; + const config = JSON.parse(JSON.stringify(miniGradeChartOptions)) + config.title.text = gradeDistributionData.name; return ( <> - + ) } \ No newline at end of file diff --git a/src/components/MiniProfessor.tsx b/src/components/MiniProfessor.tsx index 86ee356..9eea130 100644 --- a/src/components/MiniProfessor.tsx +++ b/src/components/MiniProfessor.tsx @@ -1,15 +1,15 @@ import { FaUser } from "react-icons/fa" import { NavigateFunction, useNavigate } from "react-router-dom" -import type { ProfessorProfileInterface } from "~routes/about" +import type { ProfessorProfileInterface } from "~routes/CoursePage" import { Card } from "./Card" import { MiniGrades } from "./MiniGrades" import { MiniScore } from "./MiniScore" -export const MiniProfessor = ({ professorData } : { professorData: ProfessorProfileInterface }) => { +export const MiniProfessor = ({ professorData, profiles } : { professorData: ProfessorProfileInterface, profiles: ProfessorProfileInterface[] }) => { const navigation: NavigateFunction = useNavigate() const toProfessorProfile = (): void => { - navigation("/about", { state: professorData }) + navigation("/professor", { state: { professorData, profiles } }) } return( diff --git a/src/components/MiniScore.tsx b/src/components/MiniScore.tsx index 4ee2e8b..9f8d40b 100644 --- a/src/components/MiniScore.tsx +++ b/src/components/MiniScore.tsx @@ -12,6 +12,7 @@ export const MiniScore = ({ name, score, maxScore, inverted } : MiniScoreProps) return(

{name}

-

{name == "WTA" ? score : score.toFixed(1)}

+ {score !== undefined &&

{name === "WTA" ? Math.round(score) : score.toFixed(1)}

} + {score === undefined &&

NA

}
)} \ No newline at end of file diff --git a/src/components/ProfileHeader.tsx b/src/components/ProfileHeader.tsx index 0d16387..ea11313 100644 --- a/src/components/ProfileHeader.tsx +++ b/src/components/ProfileHeader.tsx @@ -1,12 +1,13 @@ import { TiArrowBack } from "react-icons/ti" import { FaExternalLinkAlt } from "react-icons/fa" import { NavigateFunction, useNavigate } from "react-router-dom" +import type { ProfessorProfileInterface } from "~routes/CoursePage"; -export const ProfileHeader = ({ name, profilePicUrl } : { name: string, profilePicUrl: string }) => { +export const ProfileHeader = ({ name, profilePicUrl, profiles } : { name: string, profilePicUrl: string, profiles: ProfessorProfileInterface[] }) => { const navigation: NavigateFunction = useNavigate(); const returnToSections = (): void => { - navigation(-1) + navigation("/", { state: profiles }) } return ( diff --git a/src/content.ts b/src/content.ts index e6b6d09..cd27a0f 100644 --- a/src/content.ts +++ b/src/content.ts @@ -1,4 +1,3 @@ - export interface CourseHeader { subjectPrefix: string; courseNumber: string; @@ -17,8 +16,8 @@ export const config = { */ export async function scrapeCourseData() { - let [courseData, professors] = await Promise.all([getCourseInfo(), injectAndGetProfessorNames()]); - return {courseData: courseData, professors: professors}; + let [ header, professors ] = await Promise.all([getCourseInfo(), injectAndGetProfessorNames()]); + return { header: header, professors: professors }; /** Gets the first element from the DOM specified by selector */ function waitForElement(selector: string): Promise { diff --git a/src/data/builder.ts b/src/data/builder.ts new file mode 100644 index 0000000..9e6b4e6 --- /dev/null +++ b/src/data/builder.ts @@ -0,0 +1,52 @@ +import type { ShowCourseTabPayload } from "~background"; +import { fetchNebulaCourse, fetchNebulaProfessor, fetchNebulaSections } from "./fetch"; +import { requestProfessorsFromRmp } from "~data/fetchFromRmp"; +import { SCHOOL_ID } from "./config"; + +interface ProfessorProfileInterface { + name: string; + profilePicUrl: string; + rmpId: number; + rmpScore: number; + diffScore: number; + wtaScore: number; + rmpTags: string[]; + gradeDistributions: GradeDistribution[]; + ratingsDistribution: number[]; // temp +} + +interface GradeDistribution { + name: string; + series: ApexAxisChartSeries; +} + +export async function buildProfessorProfiles (payload: ShowCourseTabPayload) { + const { header, professors } = payload; + const nebulaCourse = await fetchNebulaCourse(header) + const nebulaProfessors = await Promise.all(professors.map(professor => { + const nameArray = professor.split(' ') + const firstName = nameArray[0] + const lastName = nameArray[nameArray.length - 1] + return fetchNebulaProfessor({ firstName: firstName, lastName: lastName}) + })) + const nebulaSections = await Promise.all(nebulaProfessors.map(professor => { + if (professor?._id === undefined) return null + return fetchNebulaSections({ courseReference: nebulaCourse._id, professorReference: professor._id }) + })) + const rmps = await requestProfessorsFromRmp({ professorNames: nebulaProfessors.map(prof => prof?.first_name + " " + prof?.last_name), schoolId: SCHOOL_ID }) + let professorProfiles: ProfessorProfileInterface[] = [] + for (let i = 0; i < professors.length; i++) { + professorProfiles.push({ + name: professors[i], + profilePicUrl: nebulaProfessors[i]?.image_uri, + rmpId: rmps[i]?.legacyId, + rmpScore: rmps[i]?.avgRating, + diffScore: rmps[i]?.avgDifficulty, + wtaScore: rmps[i]?.wouldTakeAgainPercent, + rmpTags: rmps[i]?.teacherRatingTags.sort((a, b) => a.tagCount - b.tagCount).map(tag => tag.tagName), + gradeDistributions: nebulaSections[i] ? nebulaSections[i].map(section => ({ name: [nebulaCourse.subject_prefix, nebulaCourse.course_number, section.section_number, section.academic_session.name].join(' '), series: [{name: "Students", data: section.grade_distribution}] })) : [{name: "No Data", series: [{name: "Students", data: []}]}], + ratingsDistribution: rmps[i] ? Object.values(rmps[i].ratingsDistribution).reverse().slice(1) : [] + }) + } + return professorProfiles +} \ No newline at end of file diff --git a/src/data/config.ts b/src/data/config.ts index 41e1217..e38d06d 100644 --- a/src/data/config.ts +++ b/src/data/config.ts @@ -17,6 +17,8 @@ export const NEBULA_FETCH_OPTIONS = { }, } +export const SCHOOL_ID = "1273" + function unRegister(key: string) { let newVar = ""; for (var i = 0; i < key.length; i++) { diff --git a/src/data/fetch.ts b/src/data/fetch.ts index b5be853..5255f57 100644 --- a/src/data/fetch.ts +++ b/src/data/fetch.ts @@ -1,7 +1,7 @@ import { NEBULA_FETCH_OPTIONS } from "~data/config"; import type { CourseInterface, FetchCourseParameters, FetchProfessorParameters, FetchSectionParameters, ProfessorInterface, SectionInterface } from "~data/interfaces"; -async function fetchNebulaCourse(params: FetchCourseParameters): Promise | null { +export async function fetchNebulaCourse(params: FetchCourseParameters): Promise | null { try { const res = await fetch(`https://api.utdnebula.com/course?course_number=${params.courseNumber}&subject_prefix=${params.subjectPrefix}`, NEBULA_FETCH_OPTIONS); const json = await res.json(); @@ -13,7 +13,7 @@ async function fetchNebulaCourse(params: FetchCourseParameters): Promise | null { +export async function fetchNebulaProfessor(params: FetchProfessorParameters): Promise | null { try { const res = await fetch(`https://api.utdnebula.com/professor?first_name=${params.firstName}&last_name=${params.lastName}`, NEBULA_FETCH_OPTIONS); const json = await res.json(); @@ -25,7 +25,7 @@ async function fetchNebulaProfessor(params: FetchProfessorParameters): Promise

| null { +export async function fetchNebulaSections(params: FetchSectionParameters): Promise | null { try { const res = await fetch(`https://api.utdnebula.com/section?course_reference=${params.courseReference}&professors=${params.professorReference}`, NEBULA_FETCH_OPTIONS); const json = await res.json(); @@ -38,6 +38,6 @@ async function fetchNebulaSections(params: FetchSectionParameters): Promise { return new Promise((resolve, reject) => { console.log("Running request professors...") const startTime = Date.now(); @@ -87,4 +87,34 @@ export function requestProfessorsFromRmp(request: RmpRequest) { console.log(Date.now() - startTime); }) +} + +interface RMPInterface { + avgDifficulty: number; + avgRating: number; + courseCodes: { + courseCount: number; + courseName: string; + }[]; + department: string; + firstName: string; + lastName: string; + legacyId: number; + numRatings: number; + ratingsDistribution: { + r1: number; + r2: number; + r3: number; + r4: number; + r5: number; + total: number; + }; + school: { + id: string; + }; + teacherRatingTags: { + tagCount: number; + tagName: string; + }[]; + wouldTakeAgainPercent: number; } \ No newline at end of file diff --git a/src/popup.tsx b/src/popup.tsx index 41db85a..e9813de 100644 --- a/src/popup.tsx +++ b/src/popup.tsx @@ -1,18 +1,7 @@ import { MemoryRouter } from "react-router-dom" import { Routing } from "~/routes" -import { sendToBackground } from "@plasmohq/messaging" import "~/style.css" -// Example of how to fetch the scraped data from the background script, given that it exists -async function funct () { - const resp = await sendToBackground({ - name: "getScrapeData", - }) - console.log("Response is ", resp) -} - -funct(); - function IndexPopup() { return ( diff --git a/src/routes/CoursePage.tsx b/src/routes/CoursePage.tsx index db53296..3d66c9e 100644 --- a/src/routes/CoursePage.tsx +++ b/src/routes/CoursePage.tsx @@ -1,13 +1,84 @@ +import { sendToBackground } from "@plasmohq/messaging"; +import { useEffect, useState } from "react"; import { useLocation } from "react-router-dom"; +import type { ShowCourseTabPayload } from "~background"; import { MiniProfessor } from "~components/MiniProfessor" -import type { ProfessorProfileInterface } from "./about"; +import type { GradeDistribution } from "~components/ProfileGrades"; +import { buildProfessorProfiles } from "~data/builder"; +import { fetchNebulaProfessor } from "~data/fetch"; +import { requestProfessorsFromRmp } from "~data/fetchFromRmp"; + +export interface ProfessorProfileInterface { + name: string; + profilePicUrl: string; + rmpScore: number; + diffScore: number; + wtaScore: number; + rmpTags: string[]; + gradeDistributions: GradeDistribution[]; + ratingsDistribution: number[]; // temp +} + +/** Temporary for testing */ +export const mockData: ProfessorProfileInterface = { + name: "Johny Deere", + profilePicUrl: "https://encrypted-tbn2.gstatic.com/images?q=tbn:ANd9GcSUe2eaB9QYVkoJORkwnG2yfpPRqpqvRyUkWXOfvLOirm1mudvx", + rmpScore: 4.3, + diffScore: 2.55, + wtaScore: 100, + rmpTags: ["GREAT LECTURES", "SKIP CLASS? YOU WON'T PASS", "FRIENDLY", "TOUGH GRADER", "LOTS OF HOMEWORK"], + gradeDistributions: [ + { + name: "MATH 2418.003 (18S)", + series: [{ + name: 'Students', + data: [30, 40, 35, 50, 49, 60, 70, 79, 80, 10, 24, 65, 12, 50] + }] + }, + { + name: "MATH 2418.003 (17S)", + series: [{ + name: 'Students', + data: [10, 40, 35, 50, 29, 65, 70, 79, 30, 15, 24, 35, 12, 20] + }] + } + ], + ratingsDistribution: [20, 13, 20, 33, 7] +} + +// Example of how to fetch the scraped data from the background script, given that it exists +async function getCourseData () { + const response: ShowCourseTabPayload = await sendToBackground({ + name: "getScrapeData", + }) + return response; +} export const CoursePage = () => { // TODO: CHANGE INTERFACE const { state } : { state: ProfessorProfileInterface[] } = useLocation(); + const [ loading, setLoading ] = useState(true) + const [ profiles, setProfiles ] = useState(null) + + useEffect(() => { + if (!state) { + setLoading(true) + getCourseData().then(payload => { + buildProfessorProfiles(payload).then(profiles => { + console.log(profiles) + setProfiles(profiles) + }).finally(() => setLoading(false)) + }) + } else { + setProfiles(state) + setLoading(false) + } + }, []) return(

- {state.map((item, index) =>
)} + {!loading && + profiles.map((item, index) =>
) + }
) } \ No newline at end of file diff --git a/src/routes/ProfessorProfile.tsx b/src/routes/ProfessorProfile.tsx index 8694279..98afb8e 100644 --- a/src/routes/ProfessorProfile.tsx +++ b/src/routes/ProfessorProfile.tsx @@ -6,23 +6,24 @@ import { ProfileGrades } from "~components/ProfileGrades" import { ProfileHeader } from "~components/ProfileHeader" import { RmpRatings } from "~components/RmpRatings" import { RmpTag } from "~components/RmpTag" -import type { ProfessorProfileInterface } from "./about" +import type { ProfessorProfileInterface } from "./CoursePage" export const ProfessorProfile = () => { - const { state }: { state: ProfessorProfileInterface } = useLocation(); + const { state } : { state: { professorData: ProfessorProfileInterface, profiles: ProfessorProfileInterface[] } } = useLocation(); + const { professorData, profiles } = state; return (
- +
{/* spacer */} - +
{/* RMP Tag area */} - {state.rmpTags.sort((a, b) => b.length - a.length).map((item, index) => - + {professorData.rmpTags?.sort((a, b) => b.length - a.length).map((item: string, index: number) => + )}
- - + +
diff --git a/src/routes/about.tsx b/src/routes/about.tsx index ac42581..4b59a35 100644 --- a/src/routes/about.tsx +++ b/src/routes/about.tsx @@ -1,43 +1,5 @@ import { NavigateFunction, useNavigate } from "react-router-dom" -import type { GradeDistribution } from "~components/ProfileGrades"; -export interface ProfessorProfileInterface { - name: string; - profilePicUrl: string; - rmpScore: number; - diffScore: number; - wtaScore: number; - rmpTags: string[]; - gradeDistributions: GradeDistribution[]; - ratingsDistribution: number[]; // temp -} - -/** Temporary for testing */ -export const mockData: ProfessorProfileInterface = { - name: "Johny Deere", - profilePicUrl: "https://encrypted-tbn2.gstatic.com/images?q=tbn:ANd9GcSUe2eaB9QYVkoJORkwnG2yfpPRqpqvRyUkWXOfvLOirm1mudvx", - rmpScore: 4.3, - diffScore: 2.55, - wtaScore: 100, - rmpTags: ["GREAT LECTURES", "SKIP CLASS? YOU WON'T PASS", "FRIENDLY", "TOUGH GRADER", "LOTS OF HOMEWORK"], - gradeDistributions: [ - { - name: "MATH 2418.003 (18S)", - series: [{ - name: 'Students', - data: [30, 40, 35, 50, 49, 60, 70, 79, 80, 10, 24, 65, 12, 50] - }] - }, - { - name: "MATH 2418.003 (17S)", - series: [{ - name: 'Students', - data: [10, 40, 35, 50, 29, 65, 70, 79, 30, 15, 24, 35, 12, 20] - }] - } - ], - ratingsDistribution: [20, 13, 20, 33, 7] -} export const About = () => { const navigation: NavigateFunction = useNavigate() diff --git a/src/routes/home.tsx b/src/routes/home.tsx deleted file mode 100644 index 0f77396..0000000 --- a/src/routes/home.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { NavigateFunction, useNavigate } from "react-router-dom" -import { mockData } from "./about" - -export const Home = () => { - const navigation: NavigateFunction = useNavigate() - - const onNextPage = (): void => { - navigation("/test", { state: [mockData, mockData, mockData] }) - } - - return ( -
- Home page - -
- ) -} \ No newline at end of file diff --git a/src/routes/index.tsx b/src/routes/index.tsx index db630dd..7959470 100644 --- a/src/routes/index.tsx +++ b/src/routes/index.tsx @@ -1,14 +1,11 @@ import { Route, Routes } from "react-router-dom" -import { About } from "./about" import { CoursePage } from "./CoursePage" -import { Home } from "./home" import { ProfessorProfile } from "./ProfessorProfile" export const Routing = () => ( - } /> - } /> - } /> + } /> + } /> ) \ No newline at end of file diff --git a/src/utils/styling.ts b/src/utils/styling.ts index 6415a2a..74e2afe 100644 --- a/src/utils/styling.ts +++ b/src/utils/styling.ts @@ -14,6 +14,11 @@ export const gradeChartOptions: ApexOptions = { distributed: true } }, + noData: { + text: "No data found", + align: "center", + verticalAlign: "middle", + }, dataLabels: { enabled: false }, @@ -72,6 +77,11 @@ export const ratingsChartOptions: ApexOptions = { }, id: 'ratings-distribution' }, + noData: { + text: "No data found", + align: "center", + verticalAlign: "middle", + }, grid: { padding: { bottom: -95 @@ -114,6 +124,11 @@ export const miniGradeChartOptions: ApexOptions = { color: '#9B9B9B' }, }, + noData: { + text: "No data found", + align: "center", + verticalAlign: "middle", + }, dataLabels: { enabled: false }, From bae49353a36b8f5628cbad0b5e891734fb9ba977 Mon Sep 17 00:00:00 2001 From: heartgg Date: Tue, 21 Mar 2023 12:47:27 -0500 Subject: [PATCH 4/9] add loading component --- package-lock.json | 267 +++++++++++++++++++++++++++++++++++++ package.json | 1 + src/components/Loading.tsx | 18 +++ src/routes/CoursePage.tsx | 7 +- src/routes/about.tsx | 17 --- 5 files changed, 290 insertions(+), 20 deletions(-) create mode 100644 src/components/Loading.tsx delete mode 100644 src/routes/about.tsx diff --git a/package-lock.json b/package-lock.json index b90e3fa..f693c01 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,7 @@ "react-apexcharts": "^1.4.0", "react-dom": "18.2.0", "react-icons": "^4.7.1", + "react-loader-spinner": "^5.3.4", "react-router-dom": "^6.8.2" }, "devDependencies": { @@ -117,6 +118,17 @@ "node": ">=6.0.0" } }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz", + "integrity": "sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==", + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/helper-compilation-targets": { "version": "7.20.7", "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.7.tgz", @@ -499,6 +511,29 @@ "node": ">=6.9.0" } }, + "node_modules/@emotion/is-prop-valid": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.0.tgz", + "integrity": "sha512-3aDpDprjM0AwaxGE09bOPkNxHpBd+kA6jty3RnaEXdweX1DF1U3VQpPYb0g1IStAuK7SVQ1cy+bNBBKp4W3Fjg==", + "dependencies": { + "@emotion/memoize": "^0.8.0" + } + }, + "node_modules/@emotion/memoize": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.0.tgz", + "integrity": "sha512-G/YwXTkv7Den9mXDO7AhLWkE3q+I92B+VqAE+dYG4NGPaHZGvt3G8Q0p9vmE+sq7rTGphUbAvmQ9YpbfMQGGlA==" + }, + "node_modules/@emotion/stylis": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.8.5.tgz", + "integrity": "sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ==" + }, + "node_modules/@emotion/unitless": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz", + "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==" + }, "node_modules/@esbuild/android-arm": { "version": "0.17.11", "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.11.tgz", @@ -3725,6 +3760,26 @@ "postcss": "^8.1.0" } }, + "node_modules/babel-plugin-styled-components": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/babel-plugin-styled-components/-/babel-plugin-styled-components-2.0.7.tgz", + "integrity": "sha512-i7YhvPgVqRKfoQ66toiZ06jPNA3p6ierpfUuEWxNF+fV27Uv5gxBkf8KZLHUCc1nFA9j6+80pYoIpqCeyW3/bA==", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.16.0", + "@babel/helper-module-imports": "^7.16.0", + "babel-plugin-syntax-jsx": "^6.18.0", + "lodash": "^4.17.11", + "picomatch": "^2.3.0" + }, + "peerDependencies": { + "styled-components": ">= 2" + } + }, + "node_modules/babel-plugin-syntax-jsx": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz", + "integrity": "sha512-qrPaCSo9c8RHNRHIotaufGbuOBN8rtdC4QrrFFc43vyWCCz7Kl7GL1PGaXtMGQZUXrkCjNEgxDfmAuAabr/rlw==" + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -3952,6 +4007,14 @@ "node": ">= 6" } }, + "node_modules/camelize": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz", + "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/caniuse-lite": { "version": "1.0.30001466", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001466.tgz", @@ -4300,6 +4363,14 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/css-color-keywords": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", + "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==", + "engines": { + "node": ">=4" + } + }, "node_modules/css-select": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", @@ -4315,6 +4386,16 @@ "url": "https://github.com/sponsors/fb55" } }, + "node_modules/css-to-react-native": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz", + "integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==", + "dependencies": { + "camelize": "^1.0.0", + "css-color-keywords": "^1.0.0", + "postcss-value-parser": "^4.0.2" + } + }, "node_modules/css-tree": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz", @@ -5049,6 +5130,14 @@ "tslib": "^2.0.3" } }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "dependencies": { + "react-is": "^16.7.0" + } + }, "node_modules/htmlnano": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/htmlnano/-/htmlnano-2.0.3.tgz", @@ -7073,6 +7162,25 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, + "node_modules/react-loader-spinner": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/react-loader-spinner/-/react-loader-spinner-5.3.4.tgz", + "integrity": "sha512-G2vw4ssX+RDZ/vfaeva06yfNqyFViv/u+tVZ3kFLy5TKNlNx2DbuwreBSpRtPespQA+VxinxUJsigwLwG9erOg==", + "dependencies": { + "react-is": "^18.2.0", + "styled-components": "^5.3.5", + "styled-tools": "^1.7.2" + }, + "peerDependencies": { + "react": "^16.0.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/react-loader-spinner/node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" + }, "node_modules/react-refresh": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.9.0.tgz", @@ -7403,6 +7511,11 @@ "upper-case-first": "^2.0.2" } }, + "node_modules/shallowequal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", + "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==" + }, "node_modules/sharp": { "version": "0.31.1", "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.31.1.tgz", @@ -7667,6 +7780,35 @@ "node": ">=0.10.0" } }, + "node_modules/styled-components": { + "version": "5.3.9", + "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-5.3.9.tgz", + "integrity": "sha512-Aj3kb13B75DQBo2oRwRa/APdB5rSmwUfN5exyarpX+x/tlM/rwZA2vVk2vQgVSP6WKaZJHWwiFrzgHt+CLtB4A==", + "dependencies": { + "@babel/helper-module-imports": "^7.0.0", + "@babel/traverse": "^7.4.5", + "@emotion/is-prop-valid": "^1.1.0", + "@emotion/stylis": "^0.8.4", + "@emotion/unitless": "^0.7.4", + "babel-plugin-styled-components": ">= 1.12.0", + "css-to-react-native": "^3.0.0", + "hoist-non-react-statics": "^3.0.0", + "shallowequal": "^1.1.0", + "supports-color": "^5.5.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/styled-components" + }, + "peerDependencies": { + "react": ">= 16.8.0", + "react-dom": ">= 16.8.0", + "react-is": ">= 16.8.0" + } + }, "node_modules/styled-jsx": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.1.tgz", @@ -7689,6 +7831,11 @@ } } }, + "node_modules/styled-tools": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/styled-tools/-/styled-tools-1.7.2.tgz", + "integrity": "sha512-IjLxzM20RMwAsx8M1QoRlCG/Kmq8lKzCGyospjtSXt/BTIIcvgTonaxQAsKnBrsZNwhpHzO9ADx5te0h76ILVg==" + }, "node_modules/sucrase": { "version": "3.29.0", "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.29.0.tgz", @@ -8509,6 +8656,14 @@ } } }, + "@babel/helper-annotate-as-pure": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz", + "integrity": "sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==", + "requires": { + "@babel/types": "^7.18.6" + } + }, "@babel/helper-compilation-targets": { "version": "7.20.7", "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.7.tgz", @@ -8792,6 +8947,29 @@ "to-fast-properties": "^2.0.0" } }, + "@emotion/is-prop-valid": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.0.tgz", + "integrity": "sha512-3aDpDprjM0AwaxGE09bOPkNxHpBd+kA6jty3RnaEXdweX1DF1U3VQpPYb0g1IStAuK7SVQ1cy+bNBBKp4W3Fjg==", + "requires": { + "@emotion/memoize": "^0.8.0" + } + }, + "@emotion/memoize": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.0.tgz", + "integrity": "sha512-G/YwXTkv7Den9mXDO7AhLWkE3q+I92B+VqAE+dYG4NGPaHZGvt3G8Q0p9vmE+sq7rTGphUbAvmQ9YpbfMQGGlA==" + }, + "@emotion/stylis": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.8.5.tgz", + "integrity": "sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ==" + }, + "@emotion/unitless": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz", + "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==" + }, "@esbuild/android-arm": { "version": "0.17.11", "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.11.tgz", @@ -10765,6 +10943,23 @@ "postcss-value-parser": "^4.2.0" } }, + "babel-plugin-styled-components": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/babel-plugin-styled-components/-/babel-plugin-styled-components-2.0.7.tgz", + "integrity": "sha512-i7YhvPgVqRKfoQ66toiZ06jPNA3p6ierpfUuEWxNF+fV27Uv5gxBkf8KZLHUCc1nFA9j6+80pYoIpqCeyW3/bA==", + "requires": { + "@babel/helper-annotate-as-pure": "^7.16.0", + "@babel/helper-module-imports": "^7.16.0", + "babel-plugin-syntax-jsx": "^6.18.0", + "lodash": "^4.17.11", + "picomatch": "^2.3.0" + } + }, + "babel-plugin-syntax-jsx": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz", + "integrity": "sha512-qrPaCSo9c8RHNRHIotaufGbuOBN8rtdC4QrrFFc43vyWCCz7Kl7GL1PGaXtMGQZUXrkCjNEgxDfmAuAabr/rlw==" + }, "balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -10912,6 +11107,11 @@ "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", "dev": true }, + "camelize": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz", + "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==" + }, "caniuse-lite": { "version": "1.0.30001466", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001466.tgz", @@ -11174,6 +11374,11 @@ } } }, + "css-color-keywords": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", + "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==" + }, "css-select": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", @@ -11186,6 +11391,16 @@ "nth-check": "^2.0.1" } }, + "css-to-react-native": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz", + "integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==", + "requires": { + "camelize": "^1.0.0", + "css-color-keywords": "^1.0.0", + "postcss-value-parser": "^4.0.2" + } + }, "css-tree": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz", @@ -11710,6 +11925,14 @@ "tslib": "^2.0.3" } }, + "hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "requires": { + "react-is": "^16.7.0" + } + }, "htmlnano": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/htmlnano/-/htmlnano-2.0.3.tgz", @@ -13069,6 +13292,23 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, + "react-loader-spinner": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/react-loader-spinner/-/react-loader-spinner-5.3.4.tgz", + "integrity": "sha512-G2vw4ssX+RDZ/vfaeva06yfNqyFViv/u+tVZ3kFLy5TKNlNx2DbuwreBSpRtPespQA+VxinxUJsigwLwG9erOg==", + "requires": { + "react-is": "^18.2.0", + "styled-components": "^5.3.5", + "styled-tools": "^1.7.2" + }, + "dependencies": { + "react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" + } + } + }, "react-refresh": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.9.0.tgz", @@ -13294,6 +13534,11 @@ "upper-case-first": "^2.0.2" } }, + "shallowequal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", + "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==" + }, "sharp": { "version": "0.31.1", "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.31.1.tgz", @@ -13474,6 +13719,23 @@ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==" }, + "styled-components": { + "version": "5.3.9", + "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-5.3.9.tgz", + "integrity": "sha512-Aj3kb13B75DQBo2oRwRa/APdB5rSmwUfN5exyarpX+x/tlM/rwZA2vVk2vQgVSP6WKaZJHWwiFrzgHt+CLtB4A==", + "requires": { + "@babel/helper-module-imports": "^7.0.0", + "@babel/traverse": "^7.4.5", + "@emotion/is-prop-valid": "^1.1.0", + "@emotion/stylis": "^0.8.4", + "@emotion/unitless": "^0.7.4", + "babel-plugin-styled-components": ">= 1.12.0", + "css-to-react-native": "^3.0.0", + "hoist-non-react-statics": "^3.0.0", + "shallowequal": "^1.1.0", + "supports-color": "^5.5.0" + } + }, "styled-jsx": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.1.tgz", @@ -13482,6 +13744,11 @@ "client-only": "0.0.1" } }, + "styled-tools": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/styled-tools/-/styled-tools-1.7.2.tgz", + "integrity": "sha512-IjLxzM20RMwAsx8M1QoRlCG/Kmq8lKzCGyospjtSXt/BTIIcvgTonaxQAsKnBrsZNwhpHzO9ADx5te0h76ILVg==" + }, "sucrase": { "version": "3.29.0", "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.29.0.tgz", diff --git a/package.json b/package.json index ca6ba00..1690c33 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "react-apexcharts": "^1.4.0", "react-dom": "18.2.0", "react-icons": "^4.7.1", + "react-loader-spinner": "^5.3.4", "react-router-dom": "^6.8.2" }, "devDependencies": { diff --git a/src/components/Loading.tsx b/src/components/Loading.tsx new file mode 100644 index 0000000..ac9d8c3 --- /dev/null +++ b/src/components/Loading.tsx @@ -0,0 +1,18 @@ +import { Rings } from "react-loader-spinner" + +export const Loading = () => { + return ( +
+ +
+ ) +} diff --git a/src/routes/CoursePage.tsx b/src/routes/CoursePage.tsx index 3d66c9e..7a0895e 100644 --- a/src/routes/CoursePage.tsx +++ b/src/routes/CoursePage.tsx @@ -1,12 +1,12 @@ import { sendToBackground } from "@plasmohq/messaging"; import { useEffect, useState } from "react"; +import { Rings } from "react-loader-spinner"; import { useLocation } from "react-router-dom"; import type { ShowCourseTabPayload } from "~background"; +import { Loading } from "~components/Loading"; import { MiniProfessor } from "~components/MiniProfessor" import type { GradeDistribution } from "~components/ProfileGrades"; import { buildProfessorProfiles } from "~data/builder"; -import { fetchNebulaProfessor } from "~data/fetch"; -import { requestProfessorsFromRmp } from "~data/fetchFromRmp"; export interface ProfessorProfileInterface { name: string; @@ -76,9 +76,10 @@ export const CoursePage = () => { // TODO: CHANGE INTERFACE return(
- {!loading && + { !loading && profiles.map((item, index) =>
) } + { loading && }
) } \ No newline at end of file diff --git a/src/routes/about.tsx b/src/routes/about.tsx deleted file mode 100644 index 4b59a35..0000000 --- a/src/routes/about.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { NavigateFunction, useNavigate } from "react-router-dom" - - -export const About = () => { - const navigation: NavigateFunction = useNavigate() - - const onNextPage = (): void => { - navigation("/about", { state: mockData }) - } - - return ( -
- About page - -
- ) -} \ No newline at end of file From 33246a47cbd7185ef8c8135b7993753e39dc85db Mon Sep 17 00:00:00 2001 From: heartgg Date: Tue, 21 Mar 2023 14:06:53 -0500 Subject: [PATCH 5/9] add persistent background state --- package-lock.json | 43 ++++++++++++++++++++++++ package.json | 1 + src/background.ts | 18 ++++++++-- src/background/messages/getScrapeData.ts | 2 +- src/components/HorizontalScores.tsx | 6 ++-- src/components/ProfileHeader.tsx | 2 +- src/data/builder.ts | 12 ++++++- src/routes/CoursePage.tsx | 27 --------------- src/routes/ProfessorProfile.tsx | 2 +- 9 files changed, 77 insertions(+), 36 deletions(-) diff --git a/package-lock.json b/package-lock.json index f693c01..37dbe9b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "0.0.1", "dependencies": { "@plasmohq/messaging": "^0.1.6", + "@plasmohq/storage": "^1.3.1", "apexcharts": "^3.37.1", "next": "^13.1.5", "plasmo": "^0.67.3", @@ -3093,6 +3094,33 @@ "prettier": "2.x" } }, + "node_modules/@plasmohq/storage": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@plasmohq/storage/-/storage-1.3.1.tgz", + "integrity": "sha512-K+EsksYrpBBnI3ZgWhNKJoKfjCzyEOrhZOC6oxzU9p7iKhvpQOIELY8JlFmc+sUnof+Ta6XwMvLJnxhrlcXt8w==", + "dependencies": { + "pify": "6.1.0" + }, + "peerDependencies": { + "react": "^16.8.6 || ^17 || ^18" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + } + } + }, + "node_modules/@plasmohq/storage/node_modules/pify": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-6.1.0.tgz", + "integrity": "sha512-KocF8ve28eFjjuBKKGvzOBGzG8ew2OqOOSxTTZhirkzH7h3BI1vyzqlR0qbfcDBve1Yzo3FVlWUAtCRrbVN8Fw==", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@pnpm/network.ca-file": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@pnpm/network.ca-file/-/network.ca-file-1.0.2.tgz", @@ -10470,6 +10498,21 @@ "lodash.isequal": "4.5.0" } }, + "@plasmohq/storage": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@plasmohq/storage/-/storage-1.3.1.tgz", + "integrity": "sha512-K+EsksYrpBBnI3ZgWhNKJoKfjCzyEOrhZOC6oxzU9p7iKhvpQOIELY8JlFmc+sUnof+Ta6XwMvLJnxhrlcXt8w==", + "requires": { + "pify": "6.1.0" + }, + "dependencies": { + "pify": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-6.1.0.tgz", + "integrity": "sha512-KocF8ve28eFjjuBKKGvzOBGzG8ew2OqOOSxTTZhirkzH7h3BI1vyzqlR0qbfcDBve1Yzo3FVlWUAtCRrbVN8Fw==" + } + } + }, "@pnpm/network.ca-file": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@pnpm/network.ca-file/-/network.ca-file-1.0.2.tgz", diff --git a/package.json b/package.json index 1690c33..cda21f3 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@plasmohq/messaging": "^0.1.6", + "@plasmohq/storage": "^1.3.1", "apexcharts": "^3.37.1", "next": "^13.1.5", "plasmo": "^0.67.3", diff --git a/src/background.ts b/src/background.ts index c0f2800..af57b38 100644 --- a/src/background.ts +++ b/src/background.ts @@ -1,4 +1,5 @@ import { scrapeCourseData, CourseHeader } from "~content"; +import { Storage } from "@plasmohq/storage"; export interface ShowCourseTabPayload { header: CourseHeader; @@ -9,6 +10,9 @@ export interface ShowCourseTabPayload { let courseTabId: number = null; let scrapedCourseData: ShowCourseTabPayload = null; +// for persistent state +const storage = new Storage(); + /** Injects the content script if we hit a course page */ chrome.webNavigation.onHistoryStateUpdated.addListener(details => { if (/^.*:\/\/utdallas\.collegescheduler\.com\/terms\/.*\/courses\/.+$/.test( @@ -34,6 +38,8 @@ chrome.webNavigation.onHistoryStateUpdated.addListener(details => { courseTabId = details.tabId } else { chrome.action.setBadgeText({text: ""}); + scrapedCourseData = null + storage.clear() } }); @@ -44,9 +50,17 @@ chrome.tabs.onActivated.addListener(details => { chrome.action.setBadgeBackgroundColor({color: 'green'}); } else { chrome.action.setBadgeText({text: ""}); + scrapedCourseData = null + storage.clear() } }); -export function getScrapedCourseData() { - return scrapedCourseData; +export async function getScrapedCourseData() { + if (scrapedCourseData) { + await storage.set("scrapedCourseData", scrapedCourseData) + return scrapedCourseData; + } else { + const data = await storage.get("scrapedCourseData") + return data + } } \ No newline at end of file diff --git a/src/background/messages/getScrapeData.ts b/src/background/messages/getScrapeData.ts index ddd277a..d83e10e 100644 --- a/src/background/messages/getScrapeData.ts +++ b/src/background/messages/getScrapeData.ts @@ -2,7 +2,7 @@ import type { PlasmoMessaging } from "@plasmohq/messaging" import { getScrapedCourseData } from "../../background"; const handler: PlasmoMessaging.MessageHandler = async (req, res) => { - const data = getScrapedCourseData(); + const data = await getScrapedCourseData(); res.send(data) } diff --git a/src/components/HorizontalScores.tsx b/src/components/HorizontalScores.tsx index 3eea218..3b4cd59 100644 --- a/src/components/HorizontalScores.tsx +++ b/src/components/HorizontalScores.tsx @@ -12,9 +12,9 @@ export const HorizontalScores = ({ rmpScore, diffScore, wtaPercent } : Scores) =

RMP

DIFF

WTA

-

{rmpScore}

-

{diffScore}

-

{wtaPercent}%

+

{rmpScore ? rmpScore.toFixed(1) : "NA"}

+

{diffScore ? diffScore.toFixed(1) : "NA"}

+

{wtaPercent ? Math.round(wtaPercent) : "NA"}%

) } \ No newline at end of file diff --git a/src/components/ProfileHeader.tsx b/src/components/ProfileHeader.tsx index ea11313..fac07b9 100644 --- a/src/components/ProfileHeader.tsx +++ b/src/components/ProfileHeader.tsx @@ -22,7 +22,7 @@ export const ProfileHeader = ({ name, profilePicUrl, profiles } : { name: string
- +
) diff --git a/src/data/builder.ts b/src/data/builder.ts index 9e6b4e6..f4de5e4 100644 --- a/src/data/builder.ts +++ b/src/data/builder.ts @@ -20,6 +20,10 @@ interface GradeDistribution { series: ApexAxisChartSeries; } +const compareArrays = (a, b) => { + return JSON.stringify(a) === JSON.stringify(b); +}; + export async function buildProfessorProfiles (payload: ShowCourseTabPayload) { const { header, professors } = payload; const nebulaCourse = await fetchNebulaCourse(header) @@ -36,6 +40,12 @@ export async function buildProfessorProfiles (payload: ShowCourseTabPayload) { const rmps = await requestProfessorsFromRmp({ professorNames: nebulaProfessors.map(prof => prof?.first_name + " " + prof?.last_name), schoolId: SCHOOL_ID }) let professorProfiles: ProfessorProfileInterface[] = [] for (let i = 0; i < professors.length; i++) { + const sectionsWithGrades = [] + for (let j = 0; j < nebulaSections[i]?.length; j++) { + const section = nebulaSections[i][j]; + if (section.grade_distribution.length !== 0 && !compareArrays(section.grade_distribution, Array(14).fill(0))) + sectionsWithGrades.push(section) + } professorProfiles.push({ name: professors[i], profilePicUrl: nebulaProfessors[i]?.image_uri, @@ -44,7 +54,7 @@ export async function buildProfessorProfiles (payload: ShowCourseTabPayload) { diffScore: rmps[i]?.avgDifficulty, wtaScore: rmps[i]?.wouldTakeAgainPercent, rmpTags: rmps[i]?.teacherRatingTags.sort((a, b) => a.tagCount - b.tagCount).map(tag => tag.tagName), - gradeDistributions: nebulaSections[i] ? nebulaSections[i].map(section => ({ name: [nebulaCourse.subject_prefix, nebulaCourse.course_number, section.section_number, section.academic_session.name].join(' '), series: [{name: "Students", data: section.grade_distribution}] })) : [{name: "No Data", series: [{name: "Students", data: []}]}], + gradeDistributions: sectionsWithGrades.length > 0 ? sectionsWithGrades.map(section => ({ name: [nebulaCourse.subject_prefix, nebulaCourse.course_number, section.section_number, section.academic_session.name].join(' '), series: [{name: "Students", data: section.grade_distribution}] })) : [{name: "No Data", series: [{name: "Students", data: []}]}], ratingsDistribution: rmps[i] ? Object.values(rmps[i].ratingsDistribution).reverse().slice(1) : [] }) } diff --git a/src/routes/CoursePage.tsx b/src/routes/CoursePage.tsx index 7a0895e..ea0eff2 100644 --- a/src/routes/CoursePage.tsx +++ b/src/routes/CoursePage.tsx @@ -19,33 +19,6 @@ export interface ProfessorProfileInterface { ratingsDistribution: number[]; // temp } -/** Temporary for testing */ -export const mockData: ProfessorProfileInterface = { - name: "Johny Deere", - profilePicUrl: "https://encrypted-tbn2.gstatic.com/images?q=tbn:ANd9GcSUe2eaB9QYVkoJORkwnG2yfpPRqpqvRyUkWXOfvLOirm1mudvx", - rmpScore: 4.3, - diffScore: 2.55, - wtaScore: 100, - rmpTags: ["GREAT LECTURES", "SKIP CLASS? YOU WON'T PASS", "FRIENDLY", "TOUGH GRADER", "LOTS OF HOMEWORK"], - gradeDistributions: [ - { - name: "MATH 2418.003 (18S)", - series: [{ - name: 'Students', - data: [30, 40, 35, 50, 49, 60, 70, 79, 80, 10, 24, 65, 12, 50] - }] - }, - { - name: "MATH 2418.003 (17S)", - series: [{ - name: 'Students', - data: [10, 40, 35, 50, 29, 65, 70, 79, 30, 15, 24, 35, 12, 20] - }] - } - ], - ratingsDistribution: [20, 13, 20, 33, 7] -} - // Example of how to fetch the scraped data from the background script, given that it exists async function getCourseData () { const response: ShowCourseTabPayload = await sendToBackground({ diff --git a/src/routes/ProfessorProfile.tsx b/src/routes/ProfessorProfile.tsx index 98afb8e..1488185 100644 --- a/src/routes/ProfessorProfile.tsx +++ b/src/routes/ProfessorProfile.tsx @@ -18,7 +18,7 @@ export const ProfessorProfile = () => {
{/* spacer */}
{/* RMP Tag area */} - {professorData.rmpTags?.sort((a, b) => b.length - a.length).map((item: string, index: number) => + {professorData.rmpTags?.slice(0, 4).map((item: string, index: number) => )}
From 9cdf95cf2b1d29ba09a87545ebcf3a99ba45ec4d Mon Sep 17 00:00:00 2001 From: heartgg Date: Tue, 21 Mar 2023 14:32:00 -0500 Subject: [PATCH 6/9] fix rmp alignment --- src/background.ts | 14 ++++++++++++++ src/data/fetchFromRmp.ts | 3 +++ 2 files changed, 17 insertions(+) diff --git a/src/background.ts b/src/background.ts index af57b38..d0c8b10 100644 --- a/src/background.ts +++ b/src/background.ts @@ -48,6 +48,20 @@ chrome.tabs.onActivated.addListener(details => { if (details.tabId == courseTabId) { chrome.action.setBadgeText({text: "!"}); chrome.action.setBadgeBackgroundColor({color: 'green'}); + chrome.scripting.executeScript({ + target: { + tabId: details.tabId, + }, + world: "MAIN", + // content script injection only works reliably on the prod packaged extension + // b/c of the plasmo dev server connections + func: scrapeCourseData, + }, function (resolve) { + if (resolve && resolve[0] && resolve[0].result) { + const result: ShowCourseTabPayload = resolve[0].result; + scrapedCourseData = result; + }; + }); } else { chrome.action.setBadgeText({text: ""}); scrapedCourseData = null diff --git a/src/data/fetchFromRmp.ts b/src/data/fetchFromRmp.ts index 4fcf735..aad2148 100644 --- a/src/data/fetchFromRmp.ts +++ b/src/data/fetchFromRmp.ts @@ -14,12 +14,15 @@ function getProfessorUrls(professorNames: string[], schoolId: string): string[] function getProfessorIds(texts: string[], professorNames: string[]): string[] { const professorIds = [] texts.forEach(text => { + let matched = false; const regex = /"legacyId":(\d+).*?"firstName":"(\w+)","lastName":"(\w+)"/g; for (const match of text.matchAll(regex)) { if (professorNames.includes(match[2] + " " + match[3])) { professorIds.push(match[1]); + matched = true; } } + if (!matched) professorIds.push(null) }) return professorIds } From 2dd7e4802f3ef43663cdf06546815247aa090290 Mon Sep 17 00:00:00 2001 From: heartgg Date: Tue, 21 Mar 2023 15:37:13 -0500 Subject: [PATCH 7/9] functional fixing --- src/background.ts | 14 -------------- src/components/Landing.tsx | 18 ++++++++++++++++++ src/components/ProfileHeader.tsx | 10 +++++++--- src/data/builder.ts | 2 +- src/routes/CoursePage.tsx | 19 +++++-------------- src/routes/ProfessorProfile.tsx | 8 +++++--- src/utils/styling.ts | 4 ++-- 7 files changed, 38 insertions(+), 37 deletions(-) create mode 100644 src/components/Landing.tsx diff --git a/src/background.ts b/src/background.ts index d0c8b10..af57b38 100644 --- a/src/background.ts +++ b/src/background.ts @@ -48,20 +48,6 @@ chrome.tabs.onActivated.addListener(details => { if (details.tabId == courseTabId) { chrome.action.setBadgeText({text: "!"}); chrome.action.setBadgeBackgroundColor({color: 'green'}); - chrome.scripting.executeScript({ - target: { - tabId: details.tabId, - }, - world: "MAIN", - // content script injection only works reliably on the prod packaged extension - // b/c of the plasmo dev server connections - func: scrapeCourseData, - }, function (resolve) { - if (resolve && resolve[0] && resolve[0].result) { - const result: ShowCourseTabPayload = resolve[0].result; - scrapedCourseData = result; - }; - }); } else { chrome.action.setBadgeText({text: ""}); scrapedCourseData = null diff --git a/src/components/Landing.tsx b/src/components/Landing.tsx new file mode 100644 index 0000000..91f81d5 --- /dev/null +++ b/src/components/Landing.tsx @@ -0,0 +1,18 @@ +import { Card } from "./Card" + +export const Landing = () => { + + const navigativeToScheduler = (): void => { + window.open("https://utdallas.collegescheduler.com/", "_blank") + } + + return( + +
+ +
+
+ ) +} \ No newline at end of file diff --git a/src/components/ProfileHeader.tsx b/src/components/ProfileHeader.tsx index fac07b9..88331ae 100644 --- a/src/components/ProfileHeader.tsx +++ b/src/components/ProfileHeader.tsx @@ -1,15 +1,19 @@ import { TiArrowBack } from "react-icons/ti" import { FaExternalLinkAlt } from "react-icons/fa" import { NavigateFunction, useNavigate } from "react-router-dom" -import type { ProfessorProfileInterface } from "~routes/CoursePage"; +import type { ProfessorProfileInterface } from "~data/builder"; -export const ProfileHeader = ({ name, profilePicUrl, profiles } : { name: string, profilePicUrl: string, profiles: ProfessorProfileInterface[] }) => { +export const ProfileHeader = ({ name, profilePicUrl, rmpId, profiles } : { name: string, profilePicUrl: string, rmpId: number, profiles: ProfessorProfileInterface[] }) => { const navigation: NavigateFunction = useNavigate(); const returnToSections = (): void => { navigation("/", { state: profiles }) } + const navigativeToRmp = (): void => { + window.open('https://www.ratemyprofessors.com/professor/' + rmpId, '_blank') + } + return (
@@ -17,7 +21,7 @@ export const ProfileHeader = ({ name, profilePicUrl, profiles } : { name: string

{name}

-
diff --git a/src/data/builder.ts b/src/data/builder.ts index f4de5e4..6cbebeb 100644 --- a/src/data/builder.ts +++ b/src/data/builder.ts @@ -3,7 +3,7 @@ import { fetchNebulaCourse, fetchNebulaProfessor, fetchNebulaSections } from "./ import { requestProfessorsFromRmp } from "~data/fetchFromRmp"; import { SCHOOL_ID } from "./config"; -interface ProfessorProfileInterface { +export interface ProfessorProfileInterface { name: string; profilePicUrl: string; rmpId: number; diff --git a/src/routes/CoursePage.tsx b/src/routes/CoursePage.tsx index ea0eff2..5bd152c 100644 --- a/src/routes/CoursePage.tsx +++ b/src/routes/CoursePage.tsx @@ -3,21 +3,11 @@ import { useEffect, useState } from "react"; import { Rings } from "react-loader-spinner"; import { useLocation } from "react-router-dom"; import type { ShowCourseTabPayload } from "~background"; +import { Landing } from "~components/Landing"; import { Loading } from "~components/Loading"; import { MiniProfessor } from "~components/MiniProfessor" import type { GradeDistribution } from "~components/ProfileGrades"; -import { buildProfessorProfiles } from "~data/builder"; - -export interface ProfessorProfileInterface { - name: string; - profilePicUrl: string; - rmpScore: number; - diffScore: number; - wtaScore: number; - rmpTags: string[]; - gradeDistributions: GradeDistribution[]; - ratingsDistribution: number[]; // temp -} +import { buildProfessorProfiles, ProfessorProfileInterface } from "~data/builder"; // Example of how to fetch the scraped data from the background script, given that it exists async function getCourseData () { @@ -49,10 +39,11 @@ export const CoursePage = () => { // TODO: CHANGE INTERFACE return(
- { !loading && + { !loading && profiles && profiles.map((item, index) =>
) } - { loading && } + { loading && } + { !loading && !profiles && }
) } \ No newline at end of file diff --git a/src/routes/ProfessorProfile.tsx b/src/routes/ProfessorProfile.tsx index 1488185..779b828 100644 --- a/src/routes/ProfessorProfile.tsx +++ b/src/routes/ProfessorProfile.tsx @@ -6,14 +6,14 @@ import { ProfileGrades } from "~components/ProfileGrades" import { ProfileHeader } from "~components/ProfileHeader" import { RmpRatings } from "~components/RmpRatings" import { RmpTag } from "~components/RmpTag" -import type { ProfessorProfileInterface } from "./CoursePage" +import type { ProfessorProfileInterface } from "~data/builder" export const ProfessorProfile = () => { const { state } : { state: { professorData: ProfessorProfileInterface, profiles: ProfessorProfileInterface[] } } = useLocation(); const { professorData, profiles } = state; return (
- +
{/* spacer */} @@ -22,7 +22,9 @@ export const ProfessorProfile = () => { )}
- + { professorData.ratingsDistribution.length > 0 && + + } diff --git a/src/utils/styling.ts b/src/utils/styling.ts index 74e2afe..5fac1d4 100644 --- a/src/utils/styling.ts +++ b/src/utils/styling.ts @@ -15,7 +15,7 @@ export const gradeChartOptions: ApexOptions = { } }, noData: { - text: "No data found", + text: "No grade data found", align: "center", verticalAlign: "middle", }, @@ -125,7 +125,7 @@ export const miniGradeChartOptions: ApexOptions = { }, }, noData: { - text: "No data found", + text: "No grade data found", align: "center", verticalAlign: "middle", }, From 6ed06782ebca1bb7db63c3cf0efcee0d57f685a4 Mon Sep 17 00:00:00 2001 From: heartgg Date: Tue, 21 Mar 2023 19:04:09 -0500 Subject: [PATCH 8/9] styling + functionality --- src/background.ts | 33 +++++++++++++++++++------------- src/components/Footer.tsx | 17 ++++++++++++++++ src/components/Landing.tsx | 21 +++++++++++++++++--- src/components/MiniProfessor.tsx | 2 +- src/components/ProfileGrades.tsx | 8 ++++---- src/data/fetchFromRmp.ts | 4 ++-- 6 files changed, 62 insertions(+), 23 deletions(-) create mode 100644 src/components/Footer.tsx diff --git a/src/background.ts b/src/background.ts index af57b38..1b3cf20 100644 --- a/src/background.ts +++ b/src/background.ts @@ -27,40 +27,47 @@ chrome.webNavigation.onHistoryStateUpdated.addListener(details => { // content script injection only works reliably on the prod packaged extension // b/c of the plasmo dev server connections func: scrapeCourseData, - }, function (resolve) { + }, async function (resolve) { if (resolve && resolve[0] && resolve[0].result) { const result: ShowCourseTabPayload = resolve[0].result; scrapedCourseData = result; + await storage.set("scrapedCourseData", scrapedCourseData) }; }); chrome.action.setBadgeText({text: "!"}); chrome.action.setBadgeBackgroundColor({color: 'green'}); courseTabId = details.tabId + storage.set("courseTabId", courseTabId) + storage.set("courseTabUrl", details.url) } else { chrome.action.setBadgeText({text: ""}); - scrapedCourseData = null - storage.clear() } }); /** Sets the icon to be active if we're on a course tab */ -chrome.tabs.onActivated.addListener(details => { - if (details.tabId == courseTabId) { +chrome.tabs.onActivated.addListener(async details => { + const cachedTabUrl: string = await storage.get("courseTabUrl") + const currentTabUrl: string = (await getCurrentTab()).url + if (cachedTabUrl === currentTabUrl) { chrome.action.setBadgeText({text: "!"}); chrome.action.setBadgeBackgroundColor({color: 'green'}); } else { chrome.action.setBadgeText({text: ""}); - scrapedCourseData = null - storage.clear() } }); export async function getScrapedCourseData() { - if (scrapedCourseData) { - await storage.set("scrapedCourseData", scrapedCourseData) - return scrapedCourseData; - } else { - const data = await storage.get("scrapedCourseData") - return data + const cachedTabUrl: string = await storage.get("courseTabUrl") + const currentTabUrl: string = (await getCurrentTab()).url + if (cachedTabUrl === currentTabUrl) { + return await storage.get("scrapedCourseData"); } + return null +} + +async function getCurrentTab() { + let queryOptions = { active: true, lastFocusedWindow: true }; + // `tab` will either be a `tabs.Tab` instance or `undefined`. + let [tab] = await chrome.tabs.query(queryOptions); + return tab; } \ No newline at end of file diff --git a/src/components/Footer.tsx b/src/components/Footer.tsx new file mode 100644 index 0000000..8aa3f36 --- /dev/null +++ b/src/components/Footer.tsx @@ -0,0 +1,17 @@ +import nebulaLogo from "data-base64:../../assets/nebula-logo.svg" + +export const Footer = () => { + + const navigateToNebula = (): void => { + window.open("https://www.utdnebula.com/", "_blank") + } + + return ( +
+
+

Powered by Nebula Labs

+ Nebula Labs Logo +
+
+ ) +} \ No newline at end of file diff --git a/src/components/Landing.tsx b/src/components/Landing.tsx index 91f81d5..f2c021f 100644 --- a/src/components/Landing.tsx +++ b/src/components/Landing.tsx @@ -1,17 +1,32 @@ import { Card } from "./Card" +import { Footer } from "./Footer" +import skedgeLogo from "data-base64:../../assets/icon.png" + +const SURVEY_URL = "https://docs.google.com/forms/d/e/1FAIpQLScGIXzlYgsx1SxHYTTCwRaMNVYNRe6I67RingPRVzcT1tLwSg/viewform?usp=sf_link" +const GALAXY_URL = "https://www.utdallas.edu/galaxy/" export const Landing = () => { const navigativeToScheduler = (): void => { - window.open("https://utdallas.collegescheduler.com/", "_blank") + window.open(GALAXY_URL, "_blank") + } + + const navigateToSurvey = (): void => { + window.open(SURVEY_URL, "_blank") } return( -
- !

+ +
) diff --git a/src/components/MiniProfessor.tsx b/src/components/MiniProfessor.tsx index 9eea130..7a22ca0 100644 --- a/src/components/MiniProfessor.tsx +++ b/src/components/MiniProfessor.tsx @@ -1,6 +1,6 @@ import { FaUser } from "react-icons/fa" import { NavigateFunction, useNavigate } from "react-router-dom" -import type { ProfessorProfileInterface } from "~routes/CoursePage" +import type { ProfessorProfileInterface } from "~data/builder" import { Card } from "./Card" import { MiniGrades } from "./MiniGrades" import { MiniScore } from "./MiniScore" diff --git a/src/components/ProfileGrades.tsx b/src/components/ProfileGrades.tsx index 402588c..007193f 100644 --- a/src/components/ProfileGrades.tsx +++ b/src/components/ProfileGrades.tsx @@ -29,10 +29,10 @@ export const ProfileGrades = ({ gradeDistributionData } : { gradeDistributionDat return ( <> -
- -

{gradeDistributionData[page].name}

- +
+ +

{gradeDistributionData[page].name}

+
diff --git a/src/data/fetchFromRmp.ts b/src/data/fetchFromRmp.ts index aad2148..a048e23 100644 --- a/src/data/fetchFromRmp.ts +++ b/src/data/fetchFromRmp.ts @@ -15,9 +15,9 @@ function getProfessorIds(texts: string[], professorNames: string[]): string[] { const professorIds = [] texts.forEach(text => { let matched = false; - const regex = /"legacyId":(\d+).*?"firstName":"(\w+)","lastName":"(\w+)"/g; + const regex = /"legacyId":(\d+).*?"firstName":"(.*?)","lastName":"(.*?)"/g; for (const match of text.matchAll(regex)) { - if (professorNames.includes(match[2] + " " + match[3])) { + if (professorNames.includes(match[2].split(' ')[0] + " " + match[3])) { professorIds.push(match[1]); matched = true; } From 8e9344d1471c80ed91ee06667c92babd34ed54f4 Mon Sep 17 00:00:00 2001 From: heartgg Date: Tue, 21 Mar 2023 20:17:50 -0500 Subject: [PATCH 9/9] more minor fixes --- package.json | 2 +- src/components/Landing.tsx | 2 +- src/components/ProfileHeader.tsx | 2 +- src/data/builder.ts | 8 ++++---- src/data/fetchFromRmp.ts | 10 ++++------ src/routes/CoursePage.tsx | 1 - src/routes/ProfessorProfile.tsx | 7 ++++++- 7 files changed, 17 insertions(+), 15 deletions(-) diff --git a/package.json b/package.json index cda21f3..6683711 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "sk.edge", "displayName": "sk.edge", "version": "0.0.1", - "description": "the all-in-one registration tool by students, for students", + "description": "your registration assistant by students, for students", "author": "Nebula Labs", "packageManager": "npm@8.19.2", "scripts": { diff --git a/src/components/Landing.tsx b/src/components/Landing.tsx index f2c021f..a1f72b6 100644 --- a/src/components/Landing.tsx +++ b/src/components/Landing.tsx @@ -19,7 +19,7 @@ export const Landing = () => {

Welcome to sk.edge 👋

-
the all-in-one registration tool by students, for students
+
your registration assistant by students, for students

Log into Schedule Planner and click Options on a course to get started!

Got feedback? Let us know !

diff --git a/src/components/ProfileHeader.tsx b/src/components/ProfileHeader.tsx index 88331ae..2ee6622 100644 --- a/src/components/ProfileHeader.tsx +++ b/src/components/ProfileHeader.tsx @@ -20,7 +20,7 @@ export const ProfileHeader = ({ name, profilePicUrl, rmpId, profiles } : { name: -

{name}

+

{name.split(' ').at(0) + " " + name.split(' ').at(-1)}

diff --git a/src/data/builder.ts b/src/data/builder.ts index 6cbebeb..a00f8ab 100644 --- a/src/data/builder.ts +++ b/src/data/builder.ts @@ -37,7 +37,7 @@ export async function buildProfessorProfiles (payload: ShowCourseTabPayload) { if (professor?._id === undefined) return null return fetchNebulaSections({ courseReference: nebulaCourse._id, professorReference: professor._id }) })) - const rmps = await requestProfessorsFromRmp({ professorNames: nebulaProfessors.map(prof => prof?.first_name + " " + prof?.last_name), schoolId: SCHOOL_ID }) + const rmps = await requestProfessorsFromRmp({ professorNames: professors.map(prof => prof.split(' ')[0] + " " + prof.split(' ').at(-1)), schoolId: SCHOOL_ID }) let professorProfiles: ProfessorProfileInterface[] = [] for (let i = 0; i < professors.length; i++) { const sectionsWithGrades = [] @@ -50,9 +50,9 @@ export async function buildProfessorProfiles (payload: ShowCourseTabPayload) { name: professors[i], profilePicUrl: nebulaProfessors[i]?.image_uri, rmpId: rmps[i]?.legacyId, - rmpScore: rmps[i]?.avgRating, - diffScore: rmps[i]?.avgDifficulty, - wtaScore: rmps[i]?.wouldTakeAgainPercent, + rmpScore: rmps[i]?.avgRating ? (rmps[i]?.avgRating === 0 ? undefined : rmps[i]?.avgRating) : undefined, + diffScore: rmps[i]?.avgDifficulty ? (rmps[i].avgDifficulty === 0 ? undefined : rmps[i]?.avgDifficulty) : undefined, + wtaScore: rmps[i]?.wouldTakeAgainPercent ? (rmps[i]?.wouldTakeAgainPercent === -1 ? undefined : rmps[i]?.wouldTakeAgainPercent) : undefined, rmpTags: rmps[i]?.teacherRatingTags.sort((a, b) => a.tagCount - b.tagCount).map(tag => tag.tagName), gradeDistributions: sectionsWithGrades.length > 0 ? sectionsWithGrades.map(section => ({ name: [nebulaCourse.subject_prefix, nebulaCourse.course_number, section.section_number, section.academic_session.name].join(' '), series: [{name: "Students", data: section.grade_distribution}] })) : [{name: "No Data", series: [{name: "Students", data: []}]}], ratingsDistribution: rmps[i] ? Object.values(rmps[i].ratingsDistribution).reverse().slice(1) : [] diff --git a/src/data/fetchFromRmp.ts b/src/data/fetchFromRmp.ts index a048e23..ffa848b 100644 --- a/src/data/fetchFromRmp.ts +++ b/src/data/fetchFromRmp.ts @@ -13,11 +13,13 @@ function getProfessorUrls(professorNames: string[], schoolId: string): string[] function getProfessorIds(texts: string[], professorNames: string[]): string[] { const professorIds = [] + const lowerCaseProfessorNames = professorNames.map(name => name.toLowerCase()) texts.forEach(text => { let matched = false; const regex = /"legacyId":(\d+).*?"firstName":"(.*?)","lastName":"(.*?)"/g; for (const match of text.matchAll(regex)) { - if (professorNames.includes(match[2].split(' ')[0] + " " + match[3])) { + console.log(match[2].split(' ')[0].toLowerCase() + " " + match[3].toLowerCase()) + if (lowerCaseProfessorNames.includes(match[2].split(' ')[0].toLowerCase() + " " + match[3].toLowerCase())) { professorIds.push(match[1]); matched = true; } @@ -55,6 +57,7 @@ function fetchWithGraphQl(graphQlUrlProps: any[], resolve) { ratings[i] = ratings[i]["data"]["node"]; } } + console.log(ratings) resolve(ratings) }) } @@ -65,8 +68,6 @@ export interface RmpRequest { } export function requestProfessorsFromRmp(request: RmpRequest): Promise { return new Promise((resolve, reject) => { - console.log("Running request professors...") - const startTime = Date.now(); // make a list of urls for promises const professorUrls = getProfessorUrls(request.professorNames, request.schoolId) @@ -84,11 +85,8 @@ export function requestProfessorsFromRmp(request: RmpRequest): Promise { - console.log(error) reject(error); }); - - console.log(Date.now() - startTime); }) } diff --git a/src/routes/CoursePage.tsx b/src/routes/CoursePage.tsx index 5bd152c..47b0eb4 100644 --- a/src/routes/CoursePage.tsx +++ b/src/routes/CoursePage.tsx @@ -27,7 +27,6 @@ export const CoursePage = () => { // TODO: CHANGE INTERFACE setLoading(true) getCourseData().then(payload => { buildProfessorProfiles(payload).then(profiles => { - console.log(profiles) setProfiles(profiles) }).finally(() => setLoading(false)) }) diff --git a/src/routes/ProfessorProfile.tsx b/src/routes/ProfessorProfile.tsx index 779b828..a0c230e 100644 --- a/src/routes/ProfessorProfile.tsx +++ b/src/routes/ProfessorProfile.tsx @@ -11,6 +11,11 @@ import type { ProfessorProfileInterface } from "~data/builder" export const ProfessorProfile = () => { const { state } : { state: { professorData: ProfessorProfileInterface, profiles: ProfessorProfileInterface[] } } = useLocation(); const { professorData, profiles } = state; + + const compareArrays = (a, b) => { + return JSON.stringify(a) === JSON.stringify(b); + }; + return (
@@ -22,7 +27,7 @@ export const ProfessorProfile = () => { )}
- { professorData.ratingsDistribution.length > 0 && + { professorData.ratingsDistribution.length > 0 && !compareArrays(professorData.ratingsDistribution, Array(5).fill(0)) && }