mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-08-07 14:08:29 -04:00
calendar: support tasks/VTODO from dank calendar
This commit is contained in:
@@ -941,15 +941,19 @@ Item {
|
|||||||
section: topBarContent.getWidgetSection(parent)
|
section: topBarContent.getWidgetSection(parent)
|
||||||
parentScreen: barWindow.screen
|
parentScreen: barWindow.screen
|
||||||
onClicked: {
|
onClicked: {
|
||||||
if (powerMenuModalLoader) {
|
if (!powerMenuModalLoader)
|
||||||
|
return;
|
||||||
powerMenuModalLoader.active = true;
|
powerMenuModalLoader.active = true;
|
||||||
if (powerMenuModalLoader.item) {
|
if (!powerMenuModalLoader.item)
|
||||||
|
return;
|
||||||
|
if (powerMenuModalLoader.item.shouldBeVisible) {
|
||||||
|
powerMenuModalLoader.item.close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
powerMenuModalLoader.item.openCentered();
|
powerMenuModalLoader.item.openCentered();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Component {
|
Component {
|
||||||
id: launcherButtonComponent
|
id: launcherButtonComponent
|
||||||
|
|||||||
@@ -86,12 +86,10 @@ Rectangle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function updateSelectedDateEvents() {
|
function updateSelectedDateEvents() {
|
||||||
if (CalendarService && CalendarService.calendarAvailable) {
|
const events = (CalendarService && CalendarService.calendarAvailable) ? CalendarService.getEventsForDate(selectedDate) : [];
|
||||||
const events = CalendarService.getEventsForDate(selectedDate);
|
if (JSON.stringify(events) === JSON.stringify(selectedDateEvents))
|
||||||
|
return;
|
||||||
selectedDateEvents = events;
|
selectedDateEvents = events;
|
||||||
} else {
|
|
||||||
selectedDateEvents = [];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadEventsForMonth() {
|
function loadEventsForMonth() {
|
||||||
@@ -276,59 +274,31 @@ Rectangle {
|
|||||||
height: 40
|
height: 40
|
||||||
visible: showEventDetails
|
visible: showEventDetails
|
||||||
|
|
||||||
Rectangle {
|
DankActionButton {
|
||||||
width: 32
|
buttonSize: 32
|
||||||
height: 32
|
iconSize: 14
|
||||||
|
iconName: "arrow_back"
|
||||||
|
iconColor: Theme.primary
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.leftMargin: Theme.spacingS
|
anchors.leftMargin: Theme.spacingS
|
||||||
radius: Theme.cornerRadius
|
|
||||||
color: backButtonArea.containsMouse ? Theme.primaryHover : Theme.withAlpha(Theme.primaryHover, 0)
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
name: "arrow_back"
|
|
||||||
size: 14
|
|
||||||
color: Theme.primary
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: backButtonArea
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: root.showEventDetails = false
|
onClicked: root.showEventDetails = false
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
DankActionButton {
|
||||||
width: 32
|
buttonSize: 32
|
||||||
height: 32
|
iconSize: 16
|
||||||
|
iconName: "event"
|
||||||
|
iconColor: Theme.primary
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.rightMargin: Theme.spacingS
|
anchors.rightMargin: Theme.spacingS
|
||||||
radius: Theme.cornerRadius
|
|
||||||
visible: CalendarService && CalendarService.canCreateEvents
|
visible: CalendarService && CalendarService.canCreateEvents
|
||||||
color: addEventArea.containsMouse ? Theme.primaryHover : Theme.withAlpha(Theme.primaryHover, 0)
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
name: "event"
|
|
||||||
size: 16
|
|
||||||
color: Theme.primary
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: addEventArea
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
root.editorEvent = null;
|
root.editorEvent = null;
|
||||||
root.showEditor = true;
|
root.showEditor = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
@@ -358,31 +328,12 @@ Rectangle {
|
|||||||
height: 28
|
height: 28
|
||||||
visible: !showEventDetails
|
visible: !showEventDetails
|
||||||
|
|
||||||
Rectangle {
|
DankActionButton {
|
||||||
width: 28
|
buttonSize: 28
|
||||||
height: 28
|
iconSize: 14
|
||||||
radius: Theme.cornerRadius
|
iconName: "chevron_left"
|
||||||
color: prevMonthArea.containsMouse ? Theme.primaryHover : Theme.withAlpha(Theme.primaryHover, 0)
|
iconColor: Theme.primary
|
||||||
|
onClicked: root.shiftMonth(-1)
|
||||||
DankIcon {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
name: "chevron_left"
|
|
||||||
size: 14
|
|
||||||
color: Theme.primary
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: prevMonthArea
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: {
|
|
||||||
let newDate = new Date(calendarGrid.displayDate);
|
|
||||||
newDate.setMonth(newDate.getMonth() - 1);
|
|
||||||
calendarGrid.displayDate = newDate;
|
|
||||||
loadEventsForMonth();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
@@ -396,53 +347,20 @@ Rectangle {
|
|||||||
verticalAlignment: Text.AlignVCenter
|
verticalAlignment: Text.AlignVCenter
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
DankActionButton {
|
||||||
width: 28
|
buttonSize: 28
|
||||||
height: 28
|
iconSize: 14
|
||||||
radius: Theme.cornerRadius
|
iconName: "today"
|
||||||
color: todayArea.containsMouse ? Theme.primaryHover : Theme.withAlpha(Theme.primaryHover, 0)
|
iconColor: Theme.primary
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
name: "today"
|
|
||||||
size: 14
|
|
||||||
color: Theme.primary
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: todayArea
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: root.goToToday()
|
onClicked: root.goToToday()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
DankActionButton {
|
||||||
width: 28
|
buttonSize: 28
|
||||||
height: 28
|
iconSize: 14
|
||||||
radius: Theme.cornerRadius
|
iconName: "chevron_right"
|
||||||
color: nextMonthArea.containsMouse ? Theme.primaryHover : Theme.withAlpha(Theme.primaryHover, 0)
|
iconColor: Theme.primary
|
||||||
|
onClicked: root.shiftMonth(1)
|
||||||
DankIcon {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
name: "chevron_right"
|
|
||||||
size: 14
|
|
||||||
color: Theme.primary
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: nextMonthArea
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: {
|
|
||||||
let newDate = new Date(calendarGrid.displayDate);
|
|
||||||
newDate.setMonth(newDate.getMonth() + 1);
|
|
||||||
calendarGrid.displayDate = newDate;
|
|
||||||
loadEventsForMonth();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -814,12 +732,16 @@ Rectangle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
readonly property bool isTask: modelData && modelData.id && modelData.id.startsWith("task_")
|
readonly property bool isLocalTask: taskId.startsWith("task_")
|
||||||
readonly property color accentColor: {
|
readonly property bool isDankTask: taskId.startsWith("vtodo_")
|
||||||
if (isTask)
|
readonly property bool isTask: isLocalTask || isDankTask
|
||||||
return modelData.completed ? Theme.withAlpha(Theme.primary, 0.4) : Theme.primary;
|
readonly property bool canModify: isLocalTask || (isDankTask && modelData && !modelData.readOnly)
|
||||||
return (modelData && modelData.color && modelData.color.length) ? modelData.color : Theme.primary;
|
readonly property color baseAccent: {
|
||||||
|
if (isLocalTask || !modelData || !modelData.color || !modelData.color.length)
|
||||||
|
return Theme.primary;
|
||||||
|
return modelData.color;
|
||||||
}
|
}
|
||||||
|
readonly property color accentColor: (isTask && modelData && modelData.completed) ? Theme.withAlpha(baseAccent, 0.4) : baseAccent
|
||||||
readonly property color surfaceColor: isDragging ? Theme.primaryPressed : (eventMouseArea.containsMouse ? Theme.primaryBackground : Theme.nestedSurface)
|
readonly property color surfaceColor: isDragging ? Theme.primaryPressed : (eventMouseArea.containsMouse ? Theme.primaryBackground : Theme.nestedSurface)
|
||||||
|
|
||||||
color: surfaceColor
|
color: surfaceColor
|
||||||
@@ -888,13 +810,13 @@ Rectangle {
|
|||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
color: "transparent"
|
color: "transparent"
|
||||||
visible: modelData && modelData.id && modelData.id.startsWith("task_") && !taskItem.isEditing
|
visible: taskItem.isLocalTask && !taskItem.isEditing
|
||||||
|
|
||||||
DankIcon {
|
DankIcon {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
name: "drag_indicator"
|
name: "drag_indicator"
|
||||||
size: 14
|
size: 14
|
||||||
color: dragMouseArea.containsMouse ? Theme.primary : Theme.surfaceTextAlpha
|
color: dragMouseArea.containsMouse ? Theme.primary : Theme.surfaceTextMedium
|
||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
@@ -937,34 +859,14 @@ Rectangle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Checkbox status icon
|
|
||||||
Rectangle {
|
|
||||||
id: checkboxContainer
|
|
||||||
width: 24
|
|
||||||
height: 24
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.leftMargin: (modelData && modelData.id && modelData.id.startsWith("task_")) ? (taskItem.isEditing ? 8 : 32) : 8
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
radius: Theme.cornerRadius
|
|
||||||
color: "transparent"
|
|
||||||
visible: modelData && modelData.id && modelData.id.startsWith("task_")
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
name: (modelData && modelData.completed) ? "check_box" : "check_box_outline_blank"
|
|
||||||
size: 16
|
|
||||||
color: (modelData && modelData.completed) ? Theme.primary : Theme.onSurface_38
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
id: eventContent
|
id: eventContent
|
||||||
|
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
anchors.leftMargin: (modelData && modelData.id && modelData.id.startsWith("task_")) ? 60 : (Theme.spacingS + 6)
|
anchors.leftMargin: taskItem.isLocalTask ? 60 : taskItem.isDankTask ? 36 : (Theme.spacingS + 6)
|
||||||
anchors.rightMargin: (modelData && modelData.id && modelData.id.startsWith("task_")) ? 64 : Theme.spacingXS
|
anchors.rightMargin: taskItem.canModify ? 64 : Theme.spacingXS
|
||||||
spacing: Theme.spacingXXS
|
spacing: Theme.spacingXXS
|
||||||
visible: !taskItem.isEditing
|
visible: !taskItem.isEditing
|
||||||
|
|
||||||
@@ -972,7 +874,7 @@ Rectangle {
|
|||||||
width: parent.width
|
width: parent.width
|
||||||
text: modelData ? modelData.title : ""
|
text: modelData ? modelData.title : ""
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
color: (modelData && modelData.id && modelData.id.startsWith("task_") && modelData.completed) ? Theme.surfaceTextSecondary : Theme.surfaceText
|
color: (taskItem.isTask && modelData && modelData.completed) ? Theme.surfaceTextSecondary : Theme.surfaceText
|
||||||
font.weight: Font.Medium
|
font.weight: Font.Medium
|
||||||
horizontalAlignment: Text.AlignLeft
|
horizontalAlignment: Text.AlignLeft
|
||||||
elide: Text.ElideRight
|
elide: Text.ElideRight
|
||||||
@@ -1000,7 +902,7 @@ Rectangle {
|
|||||||
color: Theme.surfaceTextMedium
|
color: Theme.surfaceTextMedium
|
||||||
font.weight: Font.Normal
|
font.weight: Font.Normal
|
||||||
horizontalAlignment: Text.AlignLeft
|
horizontalAlignment: Text.AlignLeft
|
||||||
visible: text !== "" && modelData && modelData.id && !modelData.id.startsWith("task_")
|
visible: text !== "" && !taskItem.isLocalTask
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1047,90 +949,73 @@ Rectangle {
|
|||||||
id: eventMouseArea
|
id: eventMouseArea
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.leftMargin: (modelData && modelData.id && modelData.id.startsWith("task_")) ? 32 : 6
|
anchors.leftMargin: taskItem.isLocalTask ? 32 : 6
|
||||||
anchors.rightMargin: (modelData && modelData.id && modelData.id.startsWith("task_")) ? 64 : 0
|
anchors.rightMargin: taskItem.canModify ? 64 : 0
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
cursorShape: modelData ? Qt.PointingHandCursor : Qt.ArrowCursor
|
cursorShape: modelData ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||||
enabled: modelData && !taskItem.isEditing
|
enabled: modelData && !taskItem.isEditing
|
||||||
onClicked: {
|
onClicked: {
|
||||||
if (modelData && modelData.id && modelData.id.startsWith("task_")) {
|
if (!modelData)
|
||||||
|
return;
|
||||||
|
if (taskItem.isTask && taskItem.canModify) {
|
||||||
CalendarService.toggleTask(modelData.id);
|
CalendarService.toggleTask(modelData.id);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (modelData)
|
|
||||||
root.detailEvent = modelData;
|
root.detailEvent = modelData;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete / Cancel Button
|
DankActionButton {
|
||||||
Rectangle {
|
buttonSize: 24
|
||||||
|
iconSize: 16
|
||||||
|
iconName: (modelData && modelData.completed) ? "check_box" : "check_box_outline_blank"
|
||||||
|
iconColor: (modelData && modelData.completed) ? Theme.primary : Theme.surfaceText
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.leftMargin: taskItem.isLocalTask ? (taskItem.isEditing ? 8 : 32) : 8
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
visible: taskItem.isTask
|
||||||
|
enabled: taskItem.canModify && !taskItem.isEditing
|
||||||
|
onClicked: CalendarService.toggleTask(modelData.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
DankActionButton {
|
||||||
id: deleteButton
|
id: deleteButton
|
||||||
width: 24
|
buttonSize: 24
|
||||||
height: 24
|
iconSize: 14
|
||||||
|
iconName: taskItem.isEditing ? "close" : "delete"
|
||||||
|
iconColor: taskItem.isEditing ? Theme.surfaceText : Theme.error
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.rightMargin: Theme.spacingS
|
anchors.rightMargin: Theme.spacingS
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
radius: Theme.cornerRadius
|
visible: taskItem.canModify
|
||||||
color: deleteMouseArea.containsMouse ? (taskItem.isEditing ? Theme.primaryHover : Qt.rgba(0.9, 0.2, 0.2, 0.15)) : Theme.withAlpha(Qt.rgba(0.9, 0.2, 0.2, 0.15), 0)
|
|
||||||
visible: modelData && modelData.id && modelData.id.startsWith("task_")
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
name: taskItem.isEditing ? "close" : "delete"
|
|
||||||
size: 14
|
|
||||||
color: deleteMouseArea.containsMouse ? (taskItem.isEditing ? Theme.primary : Qt.rgba(0.9, 0.2, 0.2, 1.0)) : Theme.onSurface_38
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: deleteMouseArea
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
if (taskItem.isEditing) {
|
if (taskItem.isEditing) {
|
||||||
taskItem.isEditing = false;
|
taskItem.isEditing = false;
|
||||||
} else if (modelData && modelData.id) {
|
return;
|
||||||
|
}
|
||||||
|
if (modelData && modelData.id)
|
||||||
CalendarService.removeTask(modelData.id);
|
CalendarService.removeTask(modelData.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Edit / Save Button
|
DankActionButton {
|
||||||
Rectangle {
|
buttonSize: 24
|
||||||
id: editButton
|
iconSize: 14
|
||||||
width: 24
|
iconName: taskItem.isEditing ? "check" : "edit"
|
||||||
height: 24
|
iconColor: taskItem.isEditing ? Theme.primary : Theme.surfaceText
|
||||||
anchors.right: deleteButton.left
|
anchors.right: deleteButton.left
|
||||||
anchors.rightMargin: Theme.spacingXS
|
anchors.rightMargin: Theme.spacingXS
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
radius: Theme.cornerRadius
|
visible: taskItem.canModify
|
||||||
color: editMouseArea.containsMouse ? Theme.primaryHover : Theme.withAlpha(Theme.primaryHover, 0)
|
|
||||||
visible: modelData && modelData.id && modelData.id.startsWith("task_")
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
name: taskItem.isEditing ? "check" : "edit"
|
|
||||||
size: 14
|
|
||||||
color: editMouseArea.containsMouse ? Theme.primary : Theme.onSurface_38
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: editMouseArea
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
if (taskItem.isEditing) {
|
if (!taskItem.isEditing) {
|
||||||
let txt = editInput.text.trim();
|
|
||||||
if (txt !== "" && modelData && modelData.id) {
|
|
||||||
CalendarService.editTask(modelData.id, txt);
|
|
||||||
}
|
|
||||||
taskItem.isEditing = false;
|
|
||||||
} else {
|
|
||||||
taskItem.isEditing = true;
|
taskItem.isEditing = true;
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
let txt = editInput.text.trim();
|
||||||
|
if (txt !== "" && modelData && modelData.id)
|
||||||
|
CalendarService.editTask(modelData.id, txt);
|
||||||
|
taskItem.isEditing = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,9 @@ Item {
|
|||||||
property var calendars: []
|
property var calendars: []
|
||||||
property var events: []
|
property var events: []
|
||||||
property var eventsByDate: ({})
|
property var eventsByDate: ({})
|
||||||
|
property var tasks: []
|
||||||
|
property var tasksByDate: ({})
|
||||||
|
property var _pendingTaskState: ({})
|
||||||
property string lastError: ""
|
property string lastError: ""
|
||||||
property date focusDate: new Date()
|
property date focusDate: new Date()
|
||||||
property var _loadedFrom: null
|
property var _loadedFrom: null
|
||||||
@@ -130,6 +133,7 @@ Item {
|
|||||||
root.log.info("connected to dankcal:", root.socketPath);
|
root.log.info("connected to dankcal:", root.socketPath);
|
||||||
root.refreshCalendars();
|
root.refreshCalendars();
|
||||||
root.reloadEvents();
|
root.reloadEvents();
|
||||||
|
root.reloadTasks();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!root.connected && !root.socketFound)
|
if (!root.connected && !root.socketFound)
|
||||||
@@ -193,12 +197,19 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: tasksDebounce
|
||||||
|
interval: 400
|
||||||
|
repeat: false
|
||||||
|
onTriggered: root.reloadTasks()
|
||||||
|
}
|
||||||
|
|
||||||
function _sendSubscribe() {
|
function _sendSubscribe() {
|
||||||
subscribeSocket.send({
|
subscribeSocket.send({
|
||||||
"id": _nextId(),
|
"id": _nextId(),
|
||||||
"method": "subscribe",
|
"method": "subscribe",
|
||||||
"params": {
|
"params": {
|
||||||
"topics": ["accounts", "calendars", "events", "sync"]
|
"topics": ["accounts", "calendars", "events", "tasks", "sync"]
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -243,8 +254,14 @@ Item {
|
|||||||
refreshDebounce.restart();
|
refreshDebounce.restart();
|
||||||
break;
|
break;
|
||||||
case "events":
|
case "events":
|
||||||
|
refreshDebounce.restart();
|
||||||
|
break;
|
||||||
|
case "tasks":
|
||||||
|
tasksDebounce.restart();
|
||||||
|
break;
|
||||||
case "sync":
|
case "sync":
|
||||||
refreshDebounce.restart();
|
refreshDebounce.restart();
|
||||||
|
tasksDebounce.restart();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -282,6 +299,7 @@ Item {
|
|||||||
}
|
}
|
||||||
calendars = list;
|
calendars = list;
|
||||||
_rebuildEventsByDate();
|
_rebuildEventsByDate();
|
||||||
|
_rebuildTasksByDate();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -353,7 +371,7 @@ Item {
|
|||||||
function _normalizeEvent(e) {
|
function _normalizeEvent(e) {
|
||||||
const allDay = !!e.allDay;
|
const allDay = !!e.allDay;
|
||||||
const id = e.id || "";
|
const id = e.id || "";
|
||||||
if (id.startsWith("task_"))
|
if (id.startsWith("task_") || id.startsWith("vtodo_"))
|
||||||
log.warn("daemon event id collides with task prefix:", id);
|
log.warn("daemon event id collides with task prefix:", id);
|
||||||
return {
|
return {
|
||||||
"id": id,
|
"id": id,
|
||||||
@@ -478,4 +496,175 @@ Item {
|
|||||||
callback(response);
|
callback(response);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function reloadTasks() {
|
||||||
|
if (!connected)
|
||||||
|
return;
|
||||||
|
sendRequest("tasks.list", {
|
||||||
|
"includeCompleted": true,
|
||||||
|
"limit": 5000
|
||||||
|
}, response => {
|
||||||
|
if (response.error) {
|
||||||
|
lastError = response.error;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const raw = (response.result || {}).tasks || [];
|
||||||
|
tasks = raw.map(t => _applyPendingState(_normalizeTask(t)));
|
||||||
|
_rebuildTasksByDate();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// A completion toggle is applied optimistically; slow providers can serve a
|
||||||
|
// reload that predates the write, so the desired state wins over a
|
||||||
|
// disagreeing reload until the daemon confirms it or the hold expires.
|
||||||
|
function _applyPendingState(t) {
|
||||||
|
const pending = _pendingTaskState[t.id];
|
||||||
|
if (!pending)
|
||||||
|
return t;
|
||||||
|
if (t.completed === pending.completed || Date.now() > pending.expires) {
|
||||||
|
delete _pendingTaskState[t.id];
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
return Object.assign({}, t, {
|
||||||
|
"completed": pending.completed,
|
||||||
|
"status": pending.completed ? "completed" : "needs_action"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function _normalizeTask(t) {
|
||||||
|
const allDay = !!t.allDay;
|
||||||
|
return {
|
||||||
|
"id": t.id || "",
|
||||||
|
"calendarId": t.calendarId || "",
|
||||||
|
"title": t.summary || "(untitled)",
|
||||||
|
"description": t.description || "",
|
||||||
|
"location": t.location || "",
|
||||||
|
"status": t.status || "needs_action",
|
||||||
|
"completed": t.status === "completed",
|
||||||
|
"priority": t.priority || 0,
|
||||||
|
"due": t.due ? (allDay ? _dayBoundary(t.due) : new Date(t.due)) : null,
|
||||||
|
"allDay": allDay
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function taskById(id) {
|
||||||
|
for (let i = 0; i < tasks.length; i++) {
|
||||||
|
if (tasks[i].id === id)
|
||||||
|
return tasks[i];
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function taskCalendars() {
|
||||||
|
return calendars.filter(c => c.holdsTasks && !c.readOnly);
|
||||||
|
}
|
||||||
|
|
||||||
|
function defaultTaskCalendar() {
|
||||||
|
const writable = taskCalendars().filter(c => !c.hidden);
|
||||||
|
return writable.length > 0 ? writable[0] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _taskAsEvent(t) {
|
||||||
|
const cal = calendarById(t.calendarId);
|
||||||
|
return {
|
||||||
|
"id": "vtodo_" + t.id,
|
||||||
|
"title": t.title,
|
||||||
|
"description": t.description,
|
||||||
|
"location": t.location,
|
||||||
|
"url": "",
|
||||||
|
"calendar": cal ? cal.name : "",
|
||||||
|
"color": cal ? cal.color : fallbackPalette[0],
|
||||||
|
"readOnly": cal ? !!cal.readOnly : false,
|
||||||
|
"start": t.due,
|
||||||
|
"end": t.due,
|
||||||
|
"allDay": t.allDay,
|
||||||
|
"completed": t.completed,
|
||||||
|
"status": t.status,
|
||||||
|
"isMultiDay": false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function _rebuildTasksByDate() {
|
||||||
|
const hidden = _hiddenCalendarIds();
|
||||||
|
const map = {};
|
||||||
|
for (const t of tasks) {
|
||||||
|
if (!t.due || t.status === "cancelled" || hidden[t.calendarId])
|
||||||
|
continue;
|
||||||
|
const key = Qt.formatDate(t.due, "yyyy-MM-dd");
|
||||||
|
if (!map[key])
|
||||||
|
map[key] = [];
|
||||||
|
map[key].push(_taskAsEvent(t));
|
||||||
|
}
|
||||||
|
tasksByDate = map;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _patchTask(id, changes) {
|
||||||
|
const next = tasks.slice();
|
||||||
|
for (let i = 0; i < next.length; i++) {
|
||||||
|
if (next[i].id !== id)
|
||||||
|
continue;
|
||||||
|
next[i] = Object.assign({}, next[i], changes);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
tasks = next;
|
||||||
|
_rebuildTasksByDate();
|
||||||
|
}
|
||||||
|
|
||||||
|
function createTask(fields, callback) {
|
||||||
|
sendRequest("tasks.create", fields, response => {
|
||||||
|
if (response.error)
|
||||||
|
lastError = response.error;
|
||||||
|
reloadTasks();
|
||||||
|
if (callback)
|
||||||
|
callback(response);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateTask(id, fields, callback) {
|
||||||
|
const params = Object.assign({
|
||||||
|
"id": id
|
||||||
|
}, fields);
|
||||||
|
sendRequest("tasks.update", params, response => {
|
||||||
|
if (response.error)
|
||||||
|
lastError = response.error;
|
||||||
|
reloadTasks();
|
||||||
|
if (callback)
|
||||||
|
callback(response);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function completeTask(id, completed, callback) {
|
||||||
|
_pendingTaskState[id] = {
|
||||||
|
"completed": completed,
|
||||||
|
"expires": Date.now() + 15000
|
||||||
|
};
|
||||||
|
_patchTask(id, {
|
||||||
|
"completed": completed,
|
||||||
|
"status": completed ? "completed" : "needs_action"
|
||||||
|
});
|
||||||
|
sendRequest("tasks.complete", {
|
||||||
|
"id": id,
|
||||||
|
"completed": completed
|
||||||
|
}, response => {
|
||||||
|
if (response.error) {
|
||||||
|
lastError = response.error;
|
||||||
|
delete _pendingTaskState[id];
|
||||||
|
reloadTasks();
|
||||||
|
}
|
||||||
|
if (callback)
|
||||||
|
callback(response);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteTask(id, callback) {
|
||||||
|
sendRequest("tasks.delete", {
|
||||||
|
"id": id
|
||||||
|
}, response => {
|
||||||
|
if (response.error)
|
||||||
|
lastError = response.error;
|
||||||
|
reloadTasks();
|
||||||
|
if (callback)
|
||||||
|
callback(response);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ Singleton {
|
|||||||
id: dankBackend
|
id: dankBackend
|
||||||
enabled: root.backendPref === "dankcal" || root.backendPref === "auto"
|
enabled: root.backendPref === "dankcal" || root.backendPref === "auto"
|
||||||
onEventsByDateChanged: root.mergeEvents()
|
onEventsByDateChanged: root.mergeEvents()
|
||||||
|
onTasksByDateChanged: root.mergeEvents()
|
||||||
onConnectedChanged: {
|
onConnectedChanged: {
|
||||||
if (connected && root._rangeSet)
|
if (connected && root._rangeSet)
|
||||||
root.loadEvents(root.lastStartDate, root.lastEndDate);
|
root.loadEvents(root.lastStartDate, root.lastEndDate);
|
||||||
@@ -198,6 +199,17 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function addTaskForDate(date, text) {
|
function addTaskForDate(date, text) {
|
||||||
|
const taskCal = isDankActive && dankBackend.connected ? dankBackend.defaultTaskCalendar() : null;
|
||||||
|
if (taskCal) {
|
||||||
|
const due = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()));
|
||||||
|
dankBackend.createTask({
|
||||||
|
"calendarId": taskCal.id,
|
||||||
|
"summary": text,
|
||||||
|
"allDay": true,
|
||||||
|
"due": due.toISOString()
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
let dateKey = Qt.formatDate(date, "yyyy-MM-dd");
|
let dateKey = Qt.formatDate(date, "yyyy-MM-dd");
|
||||||
let tasks = Object.assign({}, root.localTasks);
|
let tasks = Object.assign({}, root.localTasks);
|
||||||
if (!tasks[dateKey])
|
if (!tasks[dateKey])
|
||||||
@@ -214,6 +226,13 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function toggleTask(taskId) {
|
function toggleTask(taskId) {
|
||||||
|
if (taskId.startsWith("vtodo_")) {
|
||||||
|
const id = taskId.slice(6);
|
||||||
|
const task = dankBackend.taskById(id);
|
||||||
|
if (task)
|
||||||
|
dankBackend.completeTask(id, !task.completed);
|
||||||
|
return;
|
||||||
|
}
|
||||||
let cleanId = taskId.replace("task_", "");
|
let cleanId = taskId.replace("task_", "");
|
||||||
let tasks = Object.assign({}, root.localTasks);
|
let tasks = Object.assign({}, root.localTasks);
|
||||||
let updated = false;
|
let updated = false;
|
||||||
@@ -237,6 +256,10 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function removeTask(taskId) {
|
function removeTask(taskId) {
|
||||||
|
if (taskId.startsWith("vtodo_")) {
|
||||||
|
dankBackend.deleteTask(taskId.slice(6));
|
||||||
|
return;
|
||||||
|
}
|
||||||
let cleanId = taskId.replace("task_", "");
|
let cleanId = taskId.replace("task_", "");
|
||||||
let tasks = Object.assign({}, root.localTasks);
|
let tasks = Object.assign({}, root.localTasks);
|
||||||
let updated = false;
|
let updated = false;
|
||||||
@@ -283,6 +306,12 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function editTask(taskId, newText) {
|
function editTask(taskId, newText) {
|
||||||
|
if (taskId.startsWith("vtodo_")) {
|
||||||
|
dankBackend.updateTask(taskId.slice(6), {
|
||||||
|
"summary": newText
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
let cleanId = taskId.replace("task_", "");
|
let cleanId = taskId.replace("task_", "");
|
||||||
let tasks = Object.assign({}, root.localTasks);
|
let tasks = Object.assign({}, root.localTasks);
|
||||||
let updated = false;
|
let updated = false;
|
||||||
@@ -305,6 +334,17 @@ Singleton {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function _mergeInto(merged, byDate) {
|
||||||
|
for (let dateKey in byDate) {
|
||||||
|
if (!merged[dateKey])
|
||||||
|
merged[dateKey] = [];
|
||||||
|
for (let event of byDate[dateKey]) {
|
||||||
|
if (!merged[dateKey].some(e => e.id === event.id))
|
||||||
|
merged[dateKey].push(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function mergeEvents() {
|
function mergeEvents() {
|
||||||
let merged = {};
|
let merged = {};
|
||||||
let backendEvents = _activeBackendEventsByDate();
|
let backendEvents = _activeBackendEventsByDate();
|
||||||
@@ -312,14 +352,9 @@ Singleton {
|
|||||||
for (let dateKey in backendEvents)
|
for (let dateKey in backendEvents)
|
||||||
merged[dateKey] = [].concat(backendEvents[dateKey]);
|
merged[dateKey] = [].concat(backendEvents[dateKey]);
|
||||||
|
|
||||||
for (let dateKey in root.taskEventsByDate) {
|
_mergeInto(merged, root.taskEventsByDate);
|
||||||
if (!merged[dateKey])
|
if (isDankActive)
|
||||||
merged[dateKey] = [];
|
_mergeInto(merged, dankBackend.tasksByDate);
|
||||||
for (let event of root.taskEventsByDate[dateKey]) {
|
|
||||||
if (!merged[dateKey].some(e => e.id === event.id))
|
|
||||||
merged[dateKey].push(event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let dateKey in merged) {
|
for (let dateKey in merged) {
|
||||||
let list = merged[dateKey];
|
let list = merged[dateKey];
|
||||||
|
|||||||
Reference in New Issue
Block a user