function callApi(path, perPage, callback) { let xhr = new XMLHttpRequest() let apiUrl = `${apiEndpoint}${path}` if (perPage >= 1) { // add pagination parameter apiUrl += `${path.includes("?") ? "&" : "?"}per_page=${perPage}` } xhr.open("GET", apiUrl) xhr.setRequestHeader("Authorization", authHeader) xhr.onload = () => { if (xhr.status == 200) { try { let json = JSON.parse(xhr.responseText) if (callback) { callback(json) } } catch (e) { if (e instanceof SyntaxError) { console.error( `Cannot parse response for ${path} as JSON:\n` + xhr.responseText ) } else { throw e } } } else { console.error( `XHR failed when retrieving ${path} (status ${xhr.status}):\n` + xhr.responseText ) } } xhr.send() } function syncCanvas() { const courses = plasmoid.configuration.courses.split("\n").map( // each line in the "courses" config consists of // a numeric course id, a space, and a course code line => { const spaceIndex = line.indexOf(" ") return {id: line.slice(0, spaceIndex), code: line.slice(spaceIndex + 1)} } ) let courseIndices = {} // reverse look-up table to sort by courses for (let i = 0; i < courses.length; i++) { courseIndices[courses[i].id] = i } const showSubmittedAssignments = plasmoid.configuration.showSubmittedAssignments // we need user id to check submission status callApi("/users/self", 0, user => { syncCourses(courses, courseIndices, showSubmittedAssignments, user.id) }) } // fetch asynchronously, but display in this order: // important -> normal -> finished // when an activity is both important and finished, important takes priority function syncCourses(courses, courseIndices, showSubmittedAssignments, userId) { announcementsModel.clear() assignmentsModel.clear() let announcementIndices = { important: 0, // actually constant, kept for symmetry normal: 0, finished: 0, } let assignmentIndices = { important: 0, normal: 0, finished: 0, } for (let course of courses) { const courseIdx = courseIndices[course.id] callApi(`/announcements?context_codes[]=course_${course.id}`, 50, announcements => { announcements.forEach(announcement => { const info = { type: "announcement", activityId: announcement.id, courseId: course.id, course: course.code, title: announcement.title, url: announcement.html_url, important: plasmoid.configuration.importantAnnouncements .includes(announcement.id.toString()), finished: plasmoid.configuration.finishedAnnouncements .includes(announcement.id.toString()), } // figure out where we insert it into list let idx = 0 let endIdx = 0 // actually past the end if (info.important) { idx = announcementIndices.important endIdx = announcementIndices.normal announcementIndices.normal++ announcementIndices.finished++ } else if (!info.finished) { idx = announcementIndices.normal endIdx = announcementIndices.finished announcementIndices.finished++ } else { idx = announcementIndices.finished endIdx = announcementsModel.count } for (; idx < endIdx; idx++) { const annc = announcementsModel.get(idx) if (courseIndices[course.id] < courseIndices[annc.courseId]) { // we are just past the end of this course // insert this announcement here break } } announcementsModel.insert(idx, info) }) }) callApi(`/courses/${course.id}/assignments`, 50, assignments => { assignments.forEach(assignment => { callApi(`/courses/${course.id}/assignments/${assignment.id}` + `/submissions/${userId}`, 0, submission => { const submitted = submission.workflow_state == "submitted" || submission.workflow_state == "graded" if (submitted && !showSubmittedAssignments) return // discard this const info = { type: "assignment", activityId: assignment.id, courseId: course.id, course: course.code, title: assignment.name, // if null, use empty string to suppress errors dueAt: assignment.due_at || "", submitted: submitted, url: assignment.html_url, important: plasmoid.configuration.importantAssignments .includes(assignment.id.toString()), finished: plasmoid.configuration.finishedAssignments .includes(assignment.id.toString()), } let idx = 0 let endIdx = 0 if (info.important) { idx = assignmentIndices.important endIdx = assignmentIndices.normal assignmentIndices.normal++ assignmentIndices.finished++ } else if (!info.finished) { idx = assignmentIndices.normal endIdx = assignmentIndices.finished assignmentIndices.finished++ } else { idx = assignmentIndices.finished endIdx = assignmentsModel.count } for (; idx < endIdx; idx++) { const annc = assignmentsModel.get(idx) if (courseIndices[course.id] < courseIndices[annc.courseId]) { break } } assignmentsModel.insert(idx, info) }) }) }) } }