Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
- Set `NODE_ENV=production` for webpack `build` command
- Fixed dynamic graph centering bug by updating `parseTransform` in `app/Svg/Parser.hs` to parse multiple transform functions and removing `getShapesMinXY` in `js/components/graph/Graph.js`
- Switched CI provider from CircleCI to GitHub Actions
- Cleared up documentation for various graph-related front-end functions

## [0.8.1] - 2026-08-10

Expand Down
53 changes: 30 additions & 23 deletions js/components/common/react_modal.js.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -134,53 +134,60 @@ class CourseModal extends React.Component {
}
}

/** Helper function to format the time of a Time JSON object for display */
/**
* Helper function to format a Time data object for display in the course modal.
* @param {object} time A Time data object.
* @returns {string} The time formatted as a string (e.g. "Tuesday 11 - 13").
* */
formatTime(time) {
return DAY_TO_INT[time.weekDay] + " " + time.startHour + " - " + time.endHour
}

/**
* Generate the data needed for the course modal table based on the meeting times corresponding
* to a course in a given session.
* @param allMeetingTimes An array of MeetTime' objects corresponding to a particular course.
* @param session The session (F, S, Y) to query.
* @returns A map containing the table data that will appear in the course modal.
* @param {object[]} allMeetTimes An array of MeetTime' objects corresponding to a particular course.
* @param {string} session The session (F, S, Y) to query.
* @returns {object[]} An array of row data objects that will appear in the course modal table.
*/
getTable(allMeetingTimes, session) {
const sessions = allMeetingTimes.filter(lec => lec.meetData.session === session)
const sortedSessions = sessions.sort((firstLec, secondLec) =>
firstLec.meetData.section > secondLec.meetData.section ? 1 : -1
getTable(allMeetTimes, session) {
// Filter and sort the lecture sections in the specified session by their section code
const filteredMeetTimes = allMeetTimes.filter(meetTime => meetTime.meetData.session === session)
const sortedMeetTimes = filteredMeetTimes.sort((firstMeetTime, secondMeetTime) =>
firstMeetTime.meetData.section > secondMeetTime.meetData.section ? 1 : -1
)

return sortedSessions.map(lecture => {
return sortedMeetTimes.map(meetTime => {
// Sort each section's meeting time blocks and corresponding locations and store them in arrays.
// times2, locations2 are used for the extra columns that appear in the table when viewing a Y course.
const occurrences = { times1: [], locations1: [], times2: [], locations2: [] }
const sortedTimeData = lecture.timeData.sort((occ1, occ2) =>
occ1.weekDay > occ2.weekDay ? 1 : -1
const sortedTimeData = meetTime.timeData.sort((time1, time2) =>
time1.weekDay > time2.weekDay ? 1 : -1
)
sortedTimeData.map(occurrence => {
sortedTimeData.map(time => {
let location = " "
if (occurrence.timeLocation !== null && occurrence.timeLocation !== undefined) {
location = occurrence.timeLocation.buildingCode
if (time.timeLocation !== null && time.timeLocation !== undefined) {
location = time.timeLocation.buildingCode
}

if (session === "Y" && occurrence.timeSession.endsWith("1")) {
if (session === "Y" && time.timeSession.endsWith("1")) {
occurrences.locations2.push(location)
occurrences.times2.push(this.formatTime(occurrence))
occurrences.times2.push(this.formatTime(time))
} else {
occurrences.locations1.push(location)
occurrences.times1.push(this.formatTime(occurrence))
occurrences.times1.push(this.formatTime(time))
}
})
const rowData = {
activity: lecture.meetData.section,
instructor: lecture.meetData.instructor,
activity: meetTime.meetData.section,
instructor: meetTime.meetData.instructor,
availability:
lecture.meetData.cap -
lecture.meetData.enrol +
meetTime.meetData.cap -
meetTime.meetData.enrol +
" of " +
lecture.meetData.cap +
meetTime.meetData.cap +
" available",
waitList: lecture.meetData.wait + " students",
waitList: meetTime.meetData.wait + " students",
time1: occurrences.times1,
location1: occurrences.locations1,
time2: occurrences.times2,
Expand Down
31 changes: 20 additions & 11 deletions js/components/graph/Graph.js
Original file line number Diff line number Diff line change
Expand Up @@ -1218,6 +1218,11 @@ export class Graph extends React.Component {
})
}

/**
* Checks if a node or bool node is currently selected or active.
* @param {string} nodeId
* @returns {boolean}
*/
isSelected = nodeId => {
if (this.state.nodesStatus[nodeId]) {
return this.isSelectedNode(nodeId)
Expand Down Expand Up @@ -1248,7 +1253,8 @@ export class Graph extends React.Component {

/**
* Check if the prerequisite courses have been satisfied based on bool type.
* @returns {boolean} Whether any of the prereqs are satisfied.
* @param {string} boolId the ID of the bool node.
* @returns {boolean} Whether the bool is satisfied based on its prerequisite nodes.
*/
arePrereqsSatisfiedBool = boolId => {
const isAllTrue = element => {
Expand All @@ -1265,8 +1271,9 @@ export class Graph extends React.Component {
}

/**
* Checks whether all prerequisite/preceding nodes for the current one are satisfied
* @return {boolean}
* Recursively checks whether all prerequisite/preceding nodes for a node are satisfied.
* @param {string} nodeId the ID of the node.
* @return {boolean} whether all of the node's prerequisite nodes are satisfied by the selected courses.
*/
arePrereqsSatisfiedNode = nodeId => {
const parents = this.state.connections.parents[nodeId]
Expand All @@ -1291,8 +1298,9 @@ export class Graph extends React.Component {
}

/**
* Checks whether a hybrid node's prereq string is satisfied
* @return {boolean}
* Checks whether a hybrid node's prereq string is satisfied.
* @param {string} nodeId the ID of the hybrid node.
* @return {boolean} whether the node's text prerequisite string is satisfied by the selected courses.
*/
arePrereqsSatisfiedHybrid = nodeId => {
// Concatenate prereq string
Expand Down Expand Up @@ -1744,14 +1752,15 @@ export class Graph extends React.Component {

export { ZOOM_INCREMENT, KEYBOARD_PANNING_INCREMENT }

/** Helper function that adds parents of hybridNode to the parents object, and adds hybrid nodes as children of the Nodes they represent
*
/**
* Helper function to populate the parents object and children object with the connections of a hybrid node.
* A parent-child connection is added for each course that appears in the hybrid node's prereq string.
* @param {Node} hybridNode
* @param {Array} nodesJSON
* @param {Object} parents
* @param {Object} parentsObj
* @param {Object} childrenObj
*/
export function populateHybridRelatives(hybridNode, nodesJSON, parents, childrenObj) {
export function populateHybridRelatives(hybridNode, nodesJSON, parentsObj, childrenObj) {
// parse prereqs based on text
let hybridText = hybridNode.text.map(textTag => textTag.text).join("")
const nodeParents = []
Expand Down Expand Up @@ -1780,12 +1789,12 @@ export function populateHybridRelatives(hybridNode, nodesJSON, parents, children
console.error("Could not find prereq for ", hybridText)
}
}
parents[hybridNode.id_] = nodeParents
parentsObj[hybridNode.id_] = nodeParents
}

/**
* Helper for hybrid computation. Finds the node with the same course label as the hybrid.
* @param {string} course
* @param {string} course
* @param {Array} nodesJSON
* @return {Node}
*/
Expand Down
10 changes: 6 additions & 4 deletions js/util/util.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
/**
* Parse a logical prerequisite string as a conjunction of disjunctions.
* @param {string} s the prerequisite string
* @returns a nested list of courses as an AND of ORs, or the course itself if no splitting is made
* @returns {string|Array} a nested list of courses as an AND of ORs, or the course itself if no
* splitting is made
*/
export function parseAnd(s) {
// Base case: return the course if no splitting is to be made.
Expand All @@ -25,7 +26,8 @@ export function parseAnd(s) {
/**
* Parse a logical prerequisite string as a disjunction of conjunctions.
* @param {string} s the prerequisite string
* @returns a nested list of courses as an OR of ANDs, or the course itself if no splitting is made
* @returns {string|Array} a nested list of courses as an OR of ANDs, or the course itself if no
* splitting is made
*/
export function parseOr(s) {
// Base case: return the course if no splitting is to be made.
Expand All @@ -51,7 +53,7 @@ export function parseOr(s) {
* strip the result of top-level outer parentheses and spaces.
* @param {string} s the prerequisite string
* @param {string} separator the separator to split by (',' for and, '/' for or)
* @returns the resulting list of conjunctives/disjunctives
* @returns {string[]} the resulting list of conjunctives/disjunctives
*/
export function splitPrereqString(s, separator) {
let splitList = []
Expand Down Expand Up @@ -84,7 +86,7 @@ export function splitPrereqString(s, separator) {
/**
* Helper function to strip a string entirely contained within a pair of parentheses.
* @param {string} s the prerequisite string to strip parentheses from
* @returns the same string with all fully-enclosing pairs of parentheses removed
* @returns {string} the same string with all fully-enclosing pairs of parentheses removed
*/
export function removeOuterParens(s) {
if (s.length < 2 || s.charAt(0) !== "(" || s.charAt(s.length - 1) !== ")") {
Expand Down