summaryrefslogtreecommitdiff
path: root/contents/ui/kanvas.js
diff options
context:
space:
mode:
authorFrederick Yin <fkfd@fkfd.me>2023-07-25 15:09:30 +0800
committerFrederick Yin <fkfd@fkfd.me>2023-07-25 15:09:30 +0800
commitf42fed01e1a9d7edea0282926cee3383cf05788e (patch)
treeacfce58f63690e5a8180595c3a8fcf82ce22012a /contents/ui/kanvas.js
parent33767a83f7ac32e182101bb0a9333a1f9395a158 (diff)
Separate JavaScript code from UI
Diffstat (limited to 'contents/ui/kanvas.js')
-rw-r--r--contents/ui/kanvas.js158
1 files changed, 158 insertions, 0 deletions
diff --git a/contents/ui/kanvas.js b/contents/ui/kanvas.js
new file mode 100644
index 0000000..6ed64ac
--- /dev/null
+++ b/contents/ui/kanvas.js
@@ -0,0 +1,158 @@
+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,
+ dueAt: assignment.due_at || "", // if null, use empty string to suppress errors
+ 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)
+ })
+ })
+ })
+ }
+}