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 RowLayout { id: activityView width: parent.width Layout.fillWidth: true function sameDay(due, now) { return ( due.getFullYear() == now.getFullYear() && due.getMonth() == now.getMonth() && due.getDate() == now.getDate() ) } function dayDiff(due, now, stopAt) { // due and now are both Date objects // if due has the same day as now (i.e. due is today), return 0 // if due is tomorrow, return 1, the day after that 2, yesterday -1, etc. // when the absolute value of diff reaches stopAt, return stopAt let diff = 0 const step = (due > now) ? 1 : -1 while (!sameDay(due, now)) { now.setDate(now.getDate() + step) diff += step if (Math.abs(diff) >= stopAt) { return stopAt } } return diff } function humanDue(isoDue) { // converts ISO 8601 datetime string to relative time, e.g. tomorrow midnight const daysOfWeek = [ i18n("Sunday"), i18n("Monday"), i18n("Tuesday"), i18n("Wednesday"), i18n("Thursday"), i18n("Friday"), i18n("Saturday"), ] try { let due = new Date(isoDue) if (due.toString() == "Invalid Date") { console.warn(`Cannot parse due date: ${isoDue}`) return isoDue } let now = new Date() const pastDue = (due <= now) const dayDifference = dayDiff(due, now, 8) let humanDueString = "" if (dayDifference <= -2) { humanDateString = Qt.formatDate(due) } else if (dayDifference == -1) { humanDueString = i18n("Yesterday") } else if (dayDifference == 0) { humanDueString = i18n("Today") } else if (dayDifference == 1) { humanDueString = i18n("Tomorrow") } else if (dayDifference <= 6) { humanDueString = daysOfWeek[due.getDay()] } else { humanDueString = Qt.formatDate(due) } humanDueString += " " + Qt.formatTime(due) if (pastDue) { humanDueString += " " + i18n("(missed)") } return humanDueString } catch (e) { return isoDue // screw it } } RowLayout { Layout.fillWidth: true PlasmaComponents3.CheckBox { id: activityCheckbox onToggled: () => { activityLabel.font.strikeout = (checkState == Qt.Checked) } } ColumnLayout { PlasmaComponents3.Label { id: activityLabel text: `[${course}] ${title}` font.bold: important color: important ? PlasmaCore.Theme.negativeTextColor : PlasmaCore.Theme.textColor elide: Text.ElideRight Layout.fillWidth: true } PlasmaComponents3.Label { id: dueLabel visible: type == "assignment" text: dueAt ? ("Due: " + humanDue(dueAt)) : "" color: activityLabel.color Layout.fillWidth: true } MouseArea { anchors.fill: parent onClicked: () => { activityCheckbox.toggle() // toggle checkbox activityCheckbox.toggled() // and trigger the onToggled effects } } } } RowLayout { Layout.alignment: Qt.AlignRight PlasmaComponents3.ToolButton { icon.name: "view-visible" onClicked: () => { Qt.openUrlExternally(url) } } PlasmaComponents3.ToolButton { icon.name: "emblem-important-symbolic" checked: important onClicked: () => { important = !important activityLabel.font.bold = important activityLabel.color = important ? PlasmaCore.Theme.negativeTextColor : PlasmaCore.Theme.textColor const configKeys = { announcement: "importantAnnouncements", assignment: "importantAssignments", } let importantActivities = plasmoid.configuration[configKeys[type]] if (important) { if (!importantActivities.includes(activityId)) { importantActivities.push(activityId) } } else { // remove activityId from list importantActivities.splice(importantActivities.indexOf(activityId), 1) } // save config plasmoid.configuration[configKeys[type]] = importantActivities } } } }