summaryrefslogtreecommitdiff
path: root/contents
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
parent33767a83f7ac32e182101bb0a9333a1f9395a158 (diff)
Separate JavaScript code from UI
Diffstat (limited to 'contents')
-rw-r--r--contents/ui/kanvas.js158
-rw-r--r--contents/ui/main.qml167
2 files changed, 163 insertions, 162 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)
+ })
+ })
+ })
+ }
+}
diff --git a/contents/ui/main.qml b/contents/ui/main.qml
index 006e81b..8a5d7f0 100644
--- a/contents/ui/main.qml
+++ b/contents/ui/main.qml
@@ -8,6 +8,8 @@ import org.kde.plasma.components 2.0 as PlasmaComponents
import org.kde.plasma.components 3.0 as PlasmaComponents3
import org.kde.plasma.extras 2.0 as PlasmaExtras
+import "kanvas.js" as Kanvas
+
Item {
width: PlasmaCore.Units.gridUnit * 20
height: PlasmaCore.Units.gridUnit * 40
@@ -18,174 +20,15 @@ Item {
readonly property string oauth2Token: plasmoid.configuration.oauth2Token
readonly property string authHeader: `Bearer ${oauth2Token}`
- 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)
- })
- })
- })
- }
- }
-
// sync on initialization
- Component.onCompleted: syncCanvas()
+ Component.onCompleted: Kanvas.syncCanvas()
// update every refreshInterval minutes
Timer {
interval: plasmoid.configuration.refreshInterval * 60 * 1000
running: true
repeat: true
- onTriggered: syncCanvas()
+ onTriggered: Kanvas.syncCanvas()
}
// top level layout
@@ -291,7 +134,7 @@ Item {
PlasmaComponents3.Button {
icon.name: "view-refresh"
text: i18n("Refresh")
- onClicked: syncCanvas()
+ onClicked: Kanvas.syncCanvas()
}
}
}