From f42fed01e1a9d7edea0282926cee3383cf05788e Mon Sep 17 00:00:00 2001 From: Frederick Yin Date: Tue, 25 Jul 2023 15:09:30 +0800 Subject: Separate JavaScript code from UI --- contents/ui/kanvas.js | 158 +++++++++++++++++++++++++++++++++++++++++++++++ contents/ui/main.qml | 167 ++------------------------------------------------ 2 files changed, 163 insertions(+), 162 deletions(-) create mode 100644 contents/ui/kanvas.js 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() } } } -- cgit v1.2.3