import QtQuick 2.15 import QtQuick.Layouts 1.15 import QtQuick.Controls 2.15 import org.kde.plasma.plasmoid 2.0 import org.kde.plasma.core 2.0 as PlasmaCore 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 Item { width: PlasmaCore.Units.gridUnit * 20 height: PlasmaCore.Units.gridUnit * 40 clip: true readonly property string canvasUrl: plasmoid.configuration.canvasUrl readonly property string apiEndpoint: `${canvasUrl.replace(/\/$/, "")}/api/v1` readonly property string oauth2Token: plasmoid.configuration.oauth2Token readonly property string authHeader: `Bearer ${oauth2Token}` function callApi(path, amount, callback) { let xhr = new XMLHttpRequest() xhr.open("GET", `${apiEndpoint}${path}${path.includes("?") ? "&" : "?"}per_page=${amount}`) 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 => { return line.split(" ", 2) } ) const showSubmittedAssignments = plasmoid.configuration.showSubmittedAssignments announcementsModel.clear() assignmentsModel.clear() let importantCount = {announcements: 0, assignments: 0} for (let course of courses) { callApi(`/announcements?context_codes[]=course_${course[0]}`, 10, announcements => { announcements.forEach(announcement => { const info = { type: "announcement", activityId: announcement.id, course: course[1], title: announcement.title, url: announcement.html_url, important: plasmoid.configuration.importantAnnouncements.includes(announcement.id.toString()), } if (info.important) { announcementsModel.insert(importantCount.announcements++, info) // place above unimportant ones } else { announcementsModel.append(info) } }) }) callApi(`/courses/${course[0]}/assignments`, 10, assignments => { assignments.forEach(assignment => { const info = { type: "assignment", activityId: assignment.id, course: course[1], title: assignment.name, dueAt: assignment.due_at, submitted: assignment.has_submitted_submissions, url: assignment.html_url, important: plasmoid.configuration.importantAssignments.includes(assignment.id.toString()), } if (!info.submitted || showSubmittedAssignments) { if (info.important) { assignmentsModel.insert(importantCount.assignments++, info) } else { assignmentsModel.append(info) } } }) }) } } Timer { interval: plasmoid.configuration.refreshInterval * 60 * 1000 running: true; repeat: true; onTriggered: syncCanvas(); } ColumnLayout { id: main anchors.fill: parent PlasmaExtras.Heading { level: 1 text: "Kanvas" } PlasmaExtras.Heading { level: 2 text: "Announcements" } ListModel { id: announcementsModel ListElement { type: "announcement" course: "CS101" title: "Title" url: "https://xkcd.com" important: true activityId: 0 } } ScrollView { implicitHeight: PlasmaCore.Units.gridUnit * 20 Layout.margins: PlasmaCore.Units.smallSpacing Layout.fillWidth: true Layout.fillHeight: true ScrollBar.horizontal.policy: ScrollBar.AlwaysOff ListView { id: announcementsListView Layout.fillWidth: true spacing: PlasmaCore.Units.smallSpacing delegate: ActivityView {} model: announcementsModel } } PlasmaExtras.Heading { level: 2 text: "Assignments" } ListModel { id: assignmentsModel ListElement { type: "assignment" course: "EE210" title: "Title" dueAt: "2022-04-10T15:59:59Z" submitted: true url: "https://xkcd.com" important: true activityId: 1 } } ScrollView { implicitHeight: PlasmaCore.Units.gridUnit * 20 Layout.margins: PlasmaCore.Units.smallSpacing Layout.fillWidth: true Layout.fillHeight: true ScrollBar.horizontal.policy: ScrollBar.AlwaysOff ListView { id: assignmentsListView Layout.fillWidth: true spacing: PlasmaCore.Units.gridUnit delegate: ActivityView {} model: assignmentsModel } } PlasmaComponents3.Button { icon.name: "view-refresh" text: i18n("Refresh") onClicked: syncCanvas() } } }