Personal noctalia plugins collection
1import QtQuick 2import QtQuick.Controls 3import QtQuick.Layouts 4import Quickshell 5import qs.Commons 6import qs.Services.Location 7import qs.Services.UI 8import qs.Widgets 9 10Item { 11 id: root 12 property var pluginApi: null 13 readonly property var mainInstance: pluginApi?.mainInstance 14 readonly property var geometryPlaceholder: panelContainer 15 property real contentPreferredWidth: 950 * Style.uiScaleRatio 16 property real contentPreferredHeight: 900 * Style.uiScaleRatio 17 property real topHeaderHeight: 60 * Style.uiScaleRatio 18 readonly property bool allowAttach: mainInstance ? mainInstance.panelModeSetting === "attached" : false 19 readonly property bool panelAnchorHorizontalCenter: mainInstance ? mainInstance.panelModeSetting === "centered" : false 20 readonly property bool panelAnchorVerticalCenter: mainInstance ? mainInstance.panelModeSetting === "centered" : false 21 anchors.fill: parent 22 23 property bool showCreateDialog: false 24 property bool showCreateTaskDialog: false 25 property bool showEventDetailDialog: false 26 property bool eventDetailEditMode: false 27 property bool showDeleteConfirmation: false 28 29 property real defaultHourHeight: 50 * Style.uiScaleRatio 30 property real minHourHeight: 32 * Style.uiScaleRatio 31 property real hourHeight: defaultHourHeight 32 property real timeColumnWidth: 65 * Style.uiScaleRatio 33 property real daySpacing: 1 * Style.uiScaleRatio 34 35 // Panel doesn't need its own CalendarService connection - Main.qml handles it. 36 // When panel opens, trigger a fresh load if needed. 37 Component.onCompleted: { 38 mainInstance?.initializePlugin() 39 Qt.callLater(root.adjustHourHeightForViewport) 40 } 41 onVisibleChanged: if (visible && mainInstance) { 42 mainInstance.refreshView() 43 mainInstance.goToToday() 44 Qt.callLater(root.scrollToCurrentTime) 45 mainInstance.loadTodos() 46 Qt.callLater(root.adjustHourHeightForViewport) 47 } 48 49 function adjustHourHeightForViewport() { 50 if (!calendarFlickable || calendarFlickable.height <= 0) return 51 // Target showing 08:30–24:00 (~15.5 hours) without scroll; fall back to min height if space is tight. 52 var target = calendarFlickable.height / 15.5 53 var newHeight = Math.max(minHourHeight, Math.min(defaultHourHeight, target)) 54 if (Math.abs(newHeight - hourHeight) > 0.5) hourHeight = newHeight 55 } 56 57 // Scroll to time indicator position 58 function scrollToCurrentTime() { 59 if (!mainInstance || !calendarFlickable) return 60 var now = new Date(), today = new Date(now.getFullYear(), now.getMonth(), now.getDate()) 61 var weekStart = new Date(mainInstance.weekStart) 62 var weekEnd = new Date(weekStart.getFullYear(), weekStart.getMonth(), weekStart.getDate() + 7) 63 64 if (today >= weekStart && today < weekEnd) { 65 var currentHour = now.getHours() + now.getMinutes() / 60 66 var scrollPos = (currentHour * hourHeight) - (calendarFlickable.height / 2) 67 var maxScroll = Math.max(0, (24 * hourHeight) - calendarFlickable.height) 68 scrollAnim.targetY = Math.max(0, Math.min(scrollPos, maxScroll)) 69 scrollAnim.start() 70 } 71 } 72 73 // Event creation dialog 74 Rectangle { 75 id: createEventOverlay 76 anchors.fill: parent 77 color: Qt.rgba(0, 0, 0, 0.5) 78 visible: showCreateDialog 79 z: 2000 80 81 MouseArea { anchors.fill: parent; onClicked: showCreateDialog = false } 82 83 Rectangle { 84 anchors.centerIn: parent 85 width: 400 * Style.uiScaleRatio 86 height: createDialogColumn.implicitHeight + 2 * Style.marginM 87 color: Color.mSurface 88 radius: Style.radiusM 89 90 MouseArea { anchors.fill: parent } // block clicks through 91 92 ColumnLayout { 93 id: createDialogColumn 94 anchors.fill: parent 95 anchors.margins: Style.marginM 96 spacing: Style.marginS 97 98 NText { 99 text: pluginApi.tr("panel.add_event") 100 font.pointSize: Style.fontSizeL; font.weight: Font.Bold 101 color: Color.mOnSurface 102 } 103 104 NText { text: pluginApi.tr("panel.summary"); color: Color.mOnSurfaceVariant; font.pointSize: Style.fontSizeS } 105 TextField { 106 id: createEventSummary 107 Layout.fillWidth: true 108 placeholderText: pluginApi.tr("panel.summary") 109 color: Color.mOnSurface 110 background: Rectangle { color: Color.mSurfaceVariant; radius: Style.radiusS } 111 } 112 113 NText { text: pluginApi.tr("panel.date"); color: Color.mOnSurfaceVariant; font.pointSize: Style.fontSizeS } 114 TextField { 115 id: createEventDate 116 Layout.fillWidth: true 117 placeholderText: "YYYY-MM-DD" 118 color: Color.mOnSurface 119 background: Rectangle { color: Color.mSurfaceVariant; radius: Style.radiusS } 120 } 121 122 RowLayout { 123 spacing: Style.marginS 124 ColumnLayout { 125 Layout.fillWidth: true 126 NText { text: pluginApi.tr("panel.start_time"); color: Color.mOnSurfaceVariant; font.pointSize: Style.fontSizeS } 127 TextField { 128 id: createEventStartTime 129 Layout.fillWidth: true 130 placeholderText: "HH:MM" 131 color: Color.mOnSurface 132 background: Rectangle { color: Color.mSurfaceVariant; radius: Style.radiusS } 133 } 134 } 135 ColumnLayout { 136 Layout.fillWidth: true 137 NText { text: pluginApi.tr("panel.end_time"); color: Color.mOnSurfaceVariant; font.pointSize: Style.fontSizeS } 138 TextField { 139 id: createEventEndTime 140 Layout.fillWidth: true 141 placeholderText: "HH:MM" 142 color: Color.mOnSurface 143 background: Rectangle { color: Color.mSurfaceVariant; radius: Style.radiusS } 144 } 145 } 146 } 147 148 NText { text: pluginApi.tr("panel.location"); color: Color.mOnSurfaceVariant; font.pointSize: Style.fontSizeS } 149 TextField { 150 id: createEventLocation 151 Layout.fillWidth: true 152 placeholderText: pluginApi.tr("panel.location") 153 color: Color.mOnSurface 154 background: Rectangle { color: Color.mSurfaceVariant; radius: Style.radiusS } 155 } 156 157 NText { text: pluginApi.tr("panel.description"); color: Color.mOnSurfaceVariant; font.pointSize: Style.fontSizeS } 158 TextField { 159 id: createEventDescription 160 Layout.fillWidth: true 161 placeholderText: pluginApi.tr("panel.description") 162 color: Color.mOnSurface 163 background: Rectangle { color: Color.mSurfaceVariant; radius: Style.radiusS } 164 } 165 166 NText { text: pluginApi.tr("panel.calendar_select"); color: Color.mOnSurfaceVariant; font.pointSize: Style.fontSizeS } 167 ComboBox { 168 id: calendarSelector 169 Layout.fillWidth: true 170 model: CalendarService.calendars || [] 171 textRole: "name" 172 background: Rectangle { color: Color.mSurfaceVariant; radius: Style.radiusS } 173 } 174 175 RowLayout { 176 Layout.fillWidth: true 177 spacing: Style.marginS 178 179 Item { Layout.fillWidth: true } 180 181 Rectangle { 182 Layout.preferredWidth: cancelBtn.implicitWidth + 2 * Style.marginM 183 Layout.preferredHeight: cancelBtn.implicitHeight + Style.marginS 184 color: Color.mSurfaceVariant; radius: Style.radiusS 185 NText { 186 id: cancelBtn; anchors.centerIn: parent 187 text: pluginApi.tr("panel.cancel"); color: Color.mOnSurfaceVariant 188 } 189 MouseArea { anchors.fill: parent; cursorShape: Qt.PointingHandCursor; onClicked: showCreateDialog = false } 190 } 191 192 Rectangle { 193 Layout.preferredWidth: createBtn.implicitWidth + 2 * Style.marginM 194 Layout.preferredHeight: createBtn.implicitHeight + Style.marginS 195 color: Color.mPrimary; radius: Style.radiusS 196 opacity: createEventSummary.text.trim() !== "" ? 1.0 : 0.5 197 NText { 198 id: createBtn; anchors.centerIn: parent 199 text: pluginApi.tr("panel.create"); color: Color.mOnPrimary; font.weight: Font.Bold 200 } 201 MouseArea { 202 anchors.fill: parent; cursorShape: Qt.PointingHandCursor 203 onClicked: { 204 if (createEventSummary.text.trim() === "") return 205 var cal = CalendarService.calendars?.[calendarSelector.currentIndex] 206 var calUid = cal?.uid || "" 207 var dateParts = createEventDate.text.split("-") 208 var startParts = createEventStartTime.text.split(":") 209 var endParts = createEventEndTime.text.split(":") 210 var startDate = new Date(parseInt(dateParts[0]), parseInt(dateParts[1])-1, parseInt(dateParts[2]), 211 parseInt(startParts[0]), parseInt(startParts[1]), 0) 212 var endDate = new Date(parseInt(dateParts[0]), parseInt(dateParts[1])-1, parseInt(dateParts[2]), 213 parseInt(endParts[0]), parseInt(endParts[1]), 0) 214 mainInstance?.createEvent(calUid, createEventSummary.text.trim(), 215 Math.floor(startDate.getTime()/1000), Math.floor(endDate.getTime()/1000), 216 createEventLocation.text.trim(), createEventDescription.text.trim()) 217 showCreateDialog = false 218 } 219 } 220 } 221 } 222 } 223 } 224 } 225 226 // Task creation dialog 227 Rectangle { 228 id: createTaskOverlay 229 anchors.fill: parent 230 color: Qt.rgba(0, 0, 0, 0.5) 231 visible: showCreateTaskDialog 232 z: 2000 233 234 MouseArea { anchors.fill: parent; onClicked: showCreateTaskDialog = false } 235 236 Rectangle { 237 anchors.centerIn: parent 238 width: 400 * Style.uiScaleRatio 239 height: createTaskDialogColumn.implicitHeight + 2 * Style.marginM 240 color: Color.mSurface 241 radius: Style.radiusM 242 243 MouseArea { anchors.fill: parent } 244 245 ColumnLayout { 246 id: createTaskDialogColumn 247 anchors.fill: parent 248 anchors.margins: Style.marginM 249 spacing: Style.marginS 250 251 NText { 252 text: pluginApi.tr("panel.add_task") 253 font.pointSize: Style.fontSizeL; font.weight: Font.Bold 254 color: Color.mOnSurface 255 } 256 257 NText { text: pluginApi.tr("panel.task_summary"); color: Color.mOnSurfaceVariant; font.pointSize: Style.fontSizeS } 258 TextField { 259 id: createTaskSummary 260 Layout.fillWidth: true 261 placeholderText: pluginApi.tr("panel.task_summary") 262 color: Color.mOnSurface 263 background: Rectangle { color: Color.mSurfaceVariant; radius: Style.radiusS } 264 } 265 266 NText { text: pluginApi.tr("panel.due_date"); color: Color.mOnSurfaceVariant; font.pointSize: Style.fontSizeS } 267 TextField { 268 id: createTaskDueDate 269 Layout.fillWidth: true 270 placeholderText: "YYYY-MM-DD" 271 color: Color.mOnSurface 272 background: Rectangle { color: Color.mSurfaceVariant; radius: Style.radiusS } 273 } 274 275 // Use end_time label to reflect deadline semantics 276 NText { text: pluginApi.tr("panel.end_time"); color: Color.mOnSurfaceVariant; font.pointSize: Style.fontSizeS } 277 TextField { 278 id: createTaskDueTime 279 Layout.fillWidth: true 280 placeholderText: "HH:MM" 281 color: Color.mOnSurface 282 background: Rectangle { color: Color.mSurfaceVariant; radius: Style.radiusS } 283 } 284 285 NText { text: pluginApi.tr("panel.description"); color: Color.mOnSurfaceVariant; font.pointSize: Style.fontSizeS } 286 TextField { 287 id: createTaskDescription 288 Layout.fillWidth: true 289 placeholderText: pluginApi.tr("panel.description") 290 color: Color.mOnSurface 291 background: Rectangle { color: Color.mSurfaceVariant; radius: Style.radiusS } 292 } 293 294 NText { text: pluginApi.tr("panel.priority"); color: Color.mOnSurfaceVariant; font.pointSize: Style.fontSizeS } 295 property int selectedPriority: 0 296 RowLayout { 297 spacing: Style.marginS 298 Repeater { 299 model: [ 300 { label: pluginApi.tr("panel.priority_high"), value: 1 }, 301 { label: pluginApi.tr("panel.priority_medium"), value: 5 }, 302 { label: pluginApi.tr("panel.priority_low"), value: 9 } 303 ] 304 Rectangle { 305 Layout.preferredWidth: priLabel.implicitWidth + 2 * Style.marginM 306 Layout.preferredHeight: priLabel.implicitHeight + Style.marginS 307 color: createTaskDialogColumn.selectedPriority === modelData.value ? Color.mPrimary : Color.mSurfaceVariant 308 radius: Style.radiusS 309 NText { 310 id: priLabel; anchors.centerIn: parent 311 text: modelData.label 312 color: createTaskDialogColumn.selectedPriority === modelData.value ? Color.mOnPrimary : Color.mOnSurfaceVariant 313 font.weight: Font.Medium 314 } 315 MouseArea { 316 anchors.fill: parent; cursorShape: Qt.PointingHandCursor 317 onClicked: createTaskDialogColumn.selectedPriority = 318 createTaskDialogColumn.selectedPriority === modelData.value ? 0 : modelData.value 319 } 320 } 321 } 322 } 323 324 NText { text: pluginApi.tr("panel.task_list_select"); color: Color.mOnSurfaceVariant; font.pointSize: Style.fontSizeS } 325 ComboBox { 326 id: taskListSelector 327 Layout.fillWidth: true 328 model: mainInstance?.taskLists || [] 329 textRole: "name" 330 background: Rectangle { color: Color.mSurfaceVariant; radius: Style.radiusS } 331 } 332 333 RowLayout { 334 Layout.fillWidth: true 335 spacing: Style.marginS 336 337 Item { Layout.fillWidth: true } 338 339 Rectangle { 340 Layout.preferredWidth: taskCancelBtn.implicitWidth + 2 * Style.marginM 341 Layout.preferredHeight: taskCancelBtn.implicitHeight + Style.marginS 342 color: Color.mSurfaceVariant; radius: Style.radiusS 343 NText { 344 id: taskCancelBtn; anchors.centerIn: parent 345 text: pluginApi.tr("panel.cancel"); color: Color.mOnSurfaceVariant 346 } 347 MouseArea { anchors.fill: parent; cursorShape: Qt.PointingHandCursor; onClicked: showCreateTaskDialog = false } 348 } 349 350 Rectangle { 351 Layout.preferredWidth: taskCreateBtn.implicitWidth + 2 * Style.marginM 352 Layout.preferredHeight: taskCreateBtn.implicitHeight + Style.marginS 353 color: Color.mPrimary; radius: Style.radiusS 354 opacity: createTaskSummary.text.trim() !== "" ? 1.0 : 0.5 355 NText { 356 id: taskCreateBtn; anchors.centerIn: parent 357 text: pluginApi.tr("panel.create"); color: Color.mOnPrimary; font.weight: Font.Bold 358 } 359 MouseArea { 360 anchors.fill: parent; cursorShape: Qt.PointingHandCursor 361 onClicked: { 362 if (createTaskSummary.text.trim() === "") return 363 var tl = mainInstance?.taskLists?.[taskListSelector.currentIndex] 364 var tlUid = tl?.uid || "" 365 var dueTs = 0 366 if (createTaskDueDate.text.trim() !== "") { 367 var dateParts = createTaskDueDate.text.split("-") 368 var timeParts = createTaskDueTime.text.split(":") 369 var h = createTaskDueTime.text.trim() === "" ? 0 : parseInt(timeParts[0]) 370 var m = createTaskDueTime.text.trim() === "" ? 0 : parseInt(timeParts[1] || "0") 371 var d = new Date(parseInt(dateParts[0]), parseInt(dateParts[1]) - 1, parseInt(dateParts[2]), h, m, 0) 372 if (!isNaN(d.getTime())) dueTs = Math.floor(d.getTime() / 1000) 373 } 374 mainInstance?.createTodo(tlUid, createTaskSummary.text.trim(), 375 dueTs, createTaskDialogColumn.selectedPriority, 376 createTaskDescription.text.trim()) 377 showCreateTaskDialog = false 378 } 379 } 380 } 381 } 382 } 383 } 384 } 385 386 // Event detail/edit popup 387 Rectangle { 388 id: eventDetailOverlay 389 anchors.fill: parent 390 color: Qt.rgba(0, 0, 0, 0.5) 391 visible: showEventDetailDialog 392 z: 2000 393 394 MouseArea { anchors.fill: parent; onClicked: { showEventDetailDialog = false; eventDetailEditMode = false; showDeleteConfirmation = false } } 395 396 Rectangle { 397 anchors.centerIn: parent 398 width: 420 * Style.uiScaleRatio 399 height: eventDetailColumn.implicitHeight + 2 * Style.marginM 400 color: Color.mSurface 401 radius: Style.radiusM 402 403 MouseArea { anchors.fill: parent } 404 405 ColumnLayout { 406 id: eventDetailColumn 407 anchors.fill: parent 408 anchors.margins: Style.marginM 409 spacing: Style.marginS 410 411 property var evt: mainInstance?.selectedEvent || {} 412 413 // View mode 414 ColumnLayout { 415 visible: !eventDetailEditMode && !showDeleteConfirmation 416 spacing: Style.marginS 417 Layout.fillWidth: true 418 419 NText { 420 text: eventDetailColumn.evt.title || "" 421 font.pointSize: Style.fontSizeL; font.weight: Font.Bold 422 color: Color.mOnSurface 423 wrapMode: Text.Wrap; Layout.fillWidth: true 424 } 425 426 RowLayout { 427 spacing: Style.marginS 428 NIcon { icon: "clock"; pointSize: Style.fontSizeS; color: Color.mOnSurfaceVariant } 429 NText { 430 text: { 431 var e = eventDetailColumn.evt 432 if (!e.startTime) return "" 433 return mainInstance?.formatDateTime(e.startTime) + " - " + mainInstance?.formatDateTime(e.endTime) 434 } 435 font.pointSize: Style.fontSizeS; color: Color.mOnSurfaceVariant 436 wrapMode: Text.Wrap; Layout.fillWidth: true 437 } 438 } 439 440 RowLayout { 441 visible: (eventDetailColumn.evt.location || "") !== "" 442 spacing: Style.marginS 443 NIcon { icon: "map-pin"; pointSize: Style.fontSizeS; color: Color.mOnSurfaceVariant } 444 NText { 445 text: eventDetailColumn.evt.location || "" 446 font.pointSize: Style.fontSizeS; color: Color.mOnSurfaceVariant 447 wrapMode: Text.Wrap; Layout.fillWidth: true 448 } 449 } 450 451 NText { 452 visible: (eventDetailColumn.evt.description || "") !== "" 453 text: eventDetailColumn.evt.description || "" 454 font.pointSize: Style.fontSizeS; color: Color.mOnSurfaceVariant 455 wrapMode: Text.Wrap; Layout.fillWidth: true 456 } 457 458 RowLayout { 459 Layout.fillWidth: true 460 spacing: Style.marginS 461 462 Item { Layout.fillWidth: true } 463 464 Rectangle { 465 Layout.preferredWidth: editEventBtn.implicitWidth + 2 * Style.marginM 466 Layout.preferredHeight: editEventBtn.implicitHeight + Style.marginS 467 color: Color.mPrimary; radius: Style.radiusS 468 visible: (eventDetailColumn.evt.eventUid || "") !== "" 469 NText { 470 id: editEventBtn; anchors.centerIn: parent 471 text: pluginApi.tr("panel.edit") || "Edit" 472 color: Color.mOnPrimary; font.weight: Font.Bold 473 } 474 MouseArea { 475 anchors.fill: parent; cursorShape: Qt.PointingHandCursor 476 onClicked: { 477 var e = eventDetailColumn.evt 478 editEventSummary.text = e.title || "" 479 editEventLocation.text = e.location || "" 480 editEventDescription.text = e.description || "" 481 if (e.startTime) { 482 var s = new Date(e.startTime) 483 editEventDate.text = s.getFullYear() + "-" + String(s.getMonth()+1).padStart(2,'0') + "-" + String(s.getDate()).padStart(2,'0') 484 editEventStartTime.text = String(s.getHours()).padStart(2,'0') + ":" + String(s.getMinutes()).padStart(2,'0') 485 } 486 if (e.endTime) { 487 var en = new Date(e.endTime) 488 editEventEndTime.text = String(en.getHours()).padStart(2,'0') + ":" + String(en.getMinutes()).padStart(2,'0') 489 } 490 eventDetailEditMode = true 491 } 492 } 493 } 494 495 Rectangle { 496 Layout.preferredWidth: deleteEventBtn.implicitWidth + 2 * Style.marginM 497 Layout.preferredHeight: deleteEventBtn.implicitHeight + Style.marginS 498 color: Color.mError; radius: Style.radiusS 499 visible: (eventDetailColumn.evt.eventUid || "") !== "" 500 NText { 501 id: deleteEventBtn; anchors.centerIn: parent 502 text: pluginApi.tr("panel.delete") || "Delete" 503 color: Color.mOnError; font.weight: Font.Bold 504 } 505 MouseArea { 506 anchors.fill: parent; cursorShape: Qt.PointingHandCursor 507 onClicked: showDeleteConfirmation = true 508 } 509 } 510 511 Rectangle { 512 Layout.preferredWidth: closeEventBtn.implicitWidth + 2 * Style.marginM 513 Layout.preferredHeight: closeEventBtn.implicitHeight + Style.marginS 514 color: Color.mSurfaceVariant; radius: Style.radiusS 515 NText { 516 id: closeEventBtn; anchors.centerIn: parent 517 text: pluginApi.tr("panel.close") || "Close" 518 color: Color.mOnSurfaceVariant 519 } 520 MouseArea { 521 anchors.fill: parent; cursorShape: Qt.PointingHandCursor 522 onClicked: { showEventDetailDialog = false; eventDetailEditMode = false } 523 } 524 } 525 } 526 } 527 528 // Delete confirmation mode 529 ColumnLayout { 530 visible: showDeleteConfirmation 531 spacing: Style.marginS 532 Layout.fillWidth: true 533 534 NText { 535 text: (pluginApi.tr("panel.delete_confirm") || "Delete this event?") 536 font.pointSize: Style.fontSizeM; font.weight: Font.Bold 537 color: Color.mOnSurface 538 } 539 NText { 540 text: eventDetailColumn.evt.title || "" 541 font.pointSize: Style.fontSizeS; color: Color.mOnSurfaceVariant 542 } 543 544 RowLayout { 545 Layout.fillWidth: true 546 spacing: Style.marginS 547 Item { Layout.fillWidth: true } 548 549 Rectangle { 550 Layout.preferredWidth: confirmDeleteBtn.implicitWidth + 2 * Style.marginM 551 Layout.preferredHeight: confirmDeleteBtn.implicitHeight + Style.marginS 552 color: Color.mError; radius: Style.radiusS 553 NText { 554 id: confirmDeleteBtn; anchors.centerIn: parent 555 text: pluginApi.tr("panel.delete") || "Delete" 556 color: Color.mOnError; font.weight: Font.Bold 557 } 558 MouseArea { 559 anchors.fill: parent; cursorShape: Qt.PointingHandCursor 560 onClicked: { 561 var e = eventDetailColumn.evt 562 mainInstance?.deleteEvent(e.calendarUid, e.eventUid) 563 showEventDetailDialog = false 564 showDeleteConfirmation = false 565 eventDetailEditMode = false 566 } 567 } 568 } 569 570 Rectangle { 571 Layout.preferredWidth: cancelDeleteBtn.implicitWidth + 2 * Style.marginM 572 Layout.preferredHeight: cancelDeleteBtn.implicitHeight + Style.marginS 573 color: Color.mSurfaceVariant; radius: Style.radiusS 574 NText { 575 id: cancelDeleteBtn; anchors.centerIn: parent 576 text: pluginApi.tr("panel.cancel"); color: Color.mOnSurfaceVariant 577 } 578 MouseArea { 579 anchors.fill: parent; cursorShape: Qt.PointingHandCursor 580 onClicked: showDeleteConfirmation = false 581 } 582 } 583 } 584 } 585 586 // Edit mode 587 ColumnLayout { 588 visible: eventDetailEditMode && !showDeleteConfirmation 589 spacing: Style.marginS 590 Layout.fillWidth: true 591 592 NText { 593 text: pluginApi.tr("panel.edit_event") || "Edit Event" 594 font.pointSize: Style.fontSizeL; font.weight: Font.Bold 595 color: Color.mOnSurface 596 } 597 598 NText { text: pluginApi.tr("panel.summary"); color: Color.mOnSurfaceVariant; font.pointSize: Style.fontSizeS } 599 TextField { 600 id: editEventSummary 601 Layout.fillWidth: true 602 color: Color.mOnSurface 603 background: Rectangle { color: Color.mSurfaceVariant; radius: Style.radiusS } 604 } 605 606 NText { text: pluginApi.tr("panel.date"); color: Color.mOnSurfaceVariant; font.pointSize: Style.fontSizeS } 607 TextField { 608 id: editEventDate 609 Layout.fillWidth: true 610 placeholderText: "YYYY-MM-DD" 611 color: Color.mOnSurface 612 background: Rectangle { color: Color.mSurfaceVariant; radius: Style.radiusS } 613 } 614 615 RowLayout { 616 spacing: Style.marginS 617 ColumnLayout { 618 Layout.fillWidth: true 619 NText { text: pluginApi.tr("panel.start_time"); color: Color.mOnSurfaceVariant; font.pointSize: Style.fontSizeS } 620 TextField { 621 id: editEventStartTime 622 Layout.fillWidth: true 623 placeholderText: "HH:MM" 624 color: Color.mOnSurface 625 background: Rectangle { color: Color.mSurfaceVariant; radius: Style.radiusS } 626 } 627 } 628 ColumnLayout { 629 Layout.fillWidth: true 630 NText { text: pluginApi.tr("panel.end_time"); color: Color.mOnSurfaceVariant; font.pointSize: Style.fontSizeS } 631 TextField { 632 id: editEventEndTime 633 Layout.fillWidth: true 634 placeholderText: "HH:MM" 635 color: Color.mOnSurface 636 background: Rectangle { color: Color.mSurfaceVariant; radius: Style.radiusS } 637 } 638 } 639 } 640 641 NText { text: pluginApi.tr("panel.location"); color: Color.mOnSurfaceVariant; font.pointSize: Style.fontSizeS } 642 TextField { 643 id: editEventLocation 644 Layout.fillWidth: true 645 color: Color.mOnSurface 646 background: Rectangle { color: Color.mSurfaceVariant; radius: Style.radiusS } 647 } 648 649 NText { text: pluginApi.tr("panel.description"); color: Color.mOnSurfaceVariant; font.pointSize: Style.fontSizeS } 650 TextField { 651 id: editEventDescription 652 Layout.fillWidth: true 653 color: Color.mOnSurface 654 background: Rectangle { color: Color.mSurfaceVariant; radius: Style.radiusS } 655 } 656 657 RowLayout { 658 Layout.fillWidth: true 659 spacing: Style.marginS 660 Item { Layout.fillWidth: true } 661 662 Rectangle { 663 Layout.preferredWidth: saveEventBtn.implicitWidth + 2 * Style.marginM 664 Layout.preferredHeight: saveEventBtn.implicitHeight + Style.marginS 665 color: Color.mPrimary; radius: Style.radiusS 666 NText { 667 id: saveEventBtn; anchors.centerIn: parent 668 text: pluginApi.tr("panel.save") || "Save" 669 color: Color.mOnPrimary; font.weight: Font.Bold 670 } 671 MouseArea { 672 anchors.fill: parent; cursorShape: Qt.PointingHandCursor 673 onClicked: { 674 var e = eventDetailColumn.evt 675 var dateParts = editEventDate.text.split("-") 676 var startParts = editEventStartTime.text.split(":") 677 var endParts = editEventEndTime.text.split(":") 678 var startDate = new Date(parseInt(dateParts[0]), parseInt(dateParts[1])-1, parseInt(dateParts[2]), 679 parseInt(startParts[0]), parseInt(startParts[1]), 0) 680 var endDate = new Date(parseInt(dateParts[0]), parseInt(dateParts[1])-1, parseInt(dateParts[2]), 681 parseInt(endParts[0]), parseInt(endParts[1]), 0) 682 mainInstance?.updateEvent( 683 e.calendarUid, e.eventUid, 684 editEventSummary.text.trim(), 685 editEventLocation.text.trim(), 686 editEventDescription.text.trim(), 687 Math.floor(startDate.getTime()/1000), 688 Math.floor(endDate.getTime()/1000)) 689 showEventDetailDialog = false 690 eventDetailEditMode = false 691 } 692 } 693 } 694 695 Rectangle { 696 Layout.preferredWidth: editCancelBtn.implicitWidth + 2 * Style.marginM 697 Layout.preferredHeight: editCancelBtn.implicitHeight + Style.marginS 698 color: Color.mSurfaceVariant; radius: Style.radiusS 699 NText { 700 id: editCancelBtn; anchors.centerIn: parent 701 text: pluginApi.tr("panel.cancel"); color: Color.mOnSurfaceVariant 702 } 703 MouseArea { 704 anchors.fill: parent; cursorShape: Qt.PointingHandCursor 705 onClicked: eventDetailEditMode = false 706 } 707 } 708 } 709 } 710 } 711 } 712 } 713 714 // UI 715 Rectangle { 716 id: panelContainer 717 anchors.fill: parent 718 color: "transparent" 719 720 ColumnLayout { 721 anchors.fill: parent 722 anchors.margins: Style.marginM 723 spacing: Style.marginM 724 725 //Header Section 726 Rectangle { 727 id: header 728 Layout.fillWidth: true 729 Layout.preferredHeight: topHeaderHeight 730 color: Color.mSurfaceVariant 731 radius: Style.radiusM 732 733 RowLayout { 734 anchors.margins: Style.marginM 735 anchors.fill: parent 736 737 NIcon { icon: "calendar-week"; pointSize: Style.fontSizeXXL; color: Color.mPrimary } 738 739 ColumnLayout { 740 Layout.fillHeight: true 741 spacing: 0 742 NText { 743 text: pluginApi.tr("panel.header") 744 font.pointSize: Style.fontSizeL; font.weight: Font.Bold; color: Color.mOnSurface 745 } 746 RowLayout { 747 spacing: Style.marginS 748 NText { 749 text: mainInstance?.monthRangeText || "" 750 font.pointSize: Style.fontSizeS; font.weight: Font.Medium; color: Color.mOnSurfaceVariant 751 } 752 Rectangle { 753 Layout.preferredWidth: 8; Layout.preferredHeight: 8; radius: 4 754 color: mainInstance?.isLoading ? Color.mError : 755 mainInstance?.syncStatus?.includes("No") ? Color.mError : Color.mOnSurfaceVariant 756 } 757 NText { 758 text: mainInstance?.syncStatus || "" 759 font.pointSize: Style.fontSizeS; color: Color.mOnSurfaceVariant 760 } 761 } 762 } 763 764 Item { Layout.fillWidth: true } 765 766 RowLayout { 767 spacing: Style.marginS 768 NIconButton { 769 icon: "plus"; tooltipText: pluginApi.tr("panel.add_event") 770 onClicked: { 771 createEventSummary.text = "" 772 createEventLocation.text = "" 773 createEventDescription.text = "" 774 var now = new Date() 775 var startH = now.getHours() + 1 776 createEventDate.text = now.getFullYear() + "-" + String(now.getMonth()+1).padStart(2,'0') + "-" + String(now.getDate()).padStart(2,'0') 777 createEventStartTime.text = String(startH).padStart(2,'0') + ":00" 778 createEventEndTime.text = String(startH+1).padStart(2,'0') + ":00" 779 showCreateDialog = true 780 } 781 } 782 NIconButton { 783 icon: "clipboard-check"; tooltipText: pluginApi.tr("panel.add_task") 784 onClicked: { 785 createTaskSummary.text = "" 786 var now = new Date() 787 var startH = now.getHours() + 1 788 createTaskDueDate.text = now.getFullYear() + "-" + String(now.getMonth()+1).padStart(2,'0') + "-" + String(now.getDate()).padStart(2,'0') 789 createTaskDueTime.text = String(startH).padStart(2,'0') + ":00" 790 createTaskDescription.text = "" 791 createTaskDialogColumn.selectedPriority = 0 792 showCreateTaskDialog = true 793 } 794 } 795 NIconButton { 796 icon: mainInstance?.showCompletedTodos ? "eye-off" : "eye" 797 tooltipText: pluginApi.tr("panel.show_completed") 798 onClicked: { 799 if (mainInstance) { 800 mainInstance.showCompletedTodos = !mainInstance.showCompletedTodos 801 mainInstance.loadTodos() 802 } 803 } 804 } 805 NIconButton { 806 icon: "chevron-left" 807 onClicked: mainInstance?.navigateWeek(-7) 808 } 809 NIconButton { 810 icon: "calendar"; tooltipText: pluginApi.tr("panel.today") 811 onClicked: { mainInstance?.goToToday(); Qt.callLater(root.scrollToCurrentTime) } 812 } 813 NIconButton { 814 icon: "chevron-right" 815 onClicked: mainInstance?.navigateWeek(7) 816 } 817 NIconButton { 818 icon: "refresh"; tooltipText: I18n.tr("common.refresh") 819 onClicked: { mainInstance?.loadEvents(); mainInstance?.loadTodos() } 820 enabled: mainInstance ? !mainInstance.isLoading : false 821 } 822 NIconButton { 823 icon: "close"; tooltipText: I18n.tr("common.close") 824 onClicked: pluginApi.closePanel(pluginApi.panelOpenScreen) 825 } 826 } 827 } 828 } 829 830 // Calendar View 831 Rectangle { 832 Layout.fillWidth: true 833 Layout.fillHeight: true 834 color: Color.mSurfaceVariant 835 radius: Style.radiusM 836 clip: true 837 838 Column { 839 anchors.fill: parent 840 spacing: 0 841 842 //Day Headers 843 Rectangle { 844 id: dayHeaders 845 width: parent.width 846 height: 56 847 color: Color.mSurfaceVariant 848 radius: Style.radiusM 849 850 Row { 851 anchors.fill: parent 852 anchors.leftMargin: root.timeColumnWidth 853 spacing: root.daySpacing 854 855 Repeater { 856 model: 7 857 Rectangle { 858 width: mainInstance?.dayColumnWidth 859 height: parent.height 860 color: "transparent" 861 property date dayDate: mainInstance?.weekDates?.[index] || new Date() 862 property bool isToday: { 863 var today = new Date() 864 return dayDate.getDate() === today.getDate() && 865 dayDate.getMonth() === today.getMonth() && 866 dayDate.getFullYear() === today.getFullYear() 867 } 868 Rectangle { 869 anchors.fill: parent 870 anchors.margins: 4 871 color: Color.mSurfaceVariant 872 border.color: isToday ? Color.mPrimary : "transparent" 873 border.width: 2 874 radius: Style.radiusM 875 Column { 876 anchors.centerIn: parent 877 spacing: 2 878 NText { 879 anchors.horizontalCenter: parent.horizontalCenter 880 text: dayDate ? I18n.locale.dayName(dayDate.getDay(), Locale.ShortFormat).toUpperCase() : "" 881 color: isToday ? Color.mPrimary : Color.mOnSurface 882 font.pointSize: Style.fontSizeS; font.weight: Font.Medium 883 } 884 NText { 885 anchors.horizontalCenter: parent.horizontalCenter 886 text: dayDate ? ((dayDate.getDate() < 10 ? "0" : "") + dayDate.getDate()) : "" 887 color: isToday ? Color.mPrimary : Color.mOnSurface 888 font.pointSize: Style.fontSizeM; font.weight: Font.Bold 889 } 890 } 891 } 892 } 893 } 894 } 895 } 896 // All-day row 897 Rectangle { 898 id: allDayEventsSection 899 width: parent.width 900 height: mainInstance ? Math.round(mainInstance.allDaySectionHeight * Style.uiScaleRatio) : 0 901 color: Color.mSurfaceVariant 902 visible: height > 0 903 904 Item { 905 id: allDayEventsContainer 906 anchors.fill: parent 907 anchors.leftMargin: root.timeColumnWidth 908 909 Repeater { 910 model: 6 911 delegate: Rectangle { 912 width: 1; height: parent.height 913 x: (index + 1) * ((mainInstance?.dayColumnWidth) + (root.daySpacing)) - ((root.daySpacing) / 2) 914 color: Qt.alpha(mainInstance?.lineColor || Color.mOutline, mainInstance?.dayLineOpacitySetting || 0.9) 915 } 916 } 917 918 Repeater { 919 model: mainInstance?.allDayEventsWithLayout || [] 920 delegate: Item { 921 property var eventData: modelData 922 property bool isTodoItem: eventData.isTodo || false 923 property bool isDeadline: eventData.isDeadlineMarker || false 924 x: eventData.startDay * ((mainInstance?.dayColumnWidth) + (root.daySpacing)) 925 y: eventData.lane * 25 926 width: (eventData.spanDays * ((mainInstance?.dayColumnWidth) + (root.daySpacing))) - (root.daySpacing) 927 height: isDeadline ? 10 : 24 928 929 Rectangle { 930 anchors.fill: parent 931 color: isDeadline ? Color.mSecondary : (isTodoItem ? Color.mSecondary : Color.mTertiary) 932 radius: Style.radiusS 933 opacity: isTodoItem && eventData.todoStatus === "COMPLETED" ? 0.5 : 1.0 934 NText { 935 anchors.fill: parent; anchors.margins: 4 936 text: isDeadline ? "" : (isTodoItem ? (eventData.todoStatus === "COMPLETED" ? "\u2611 " : "\u2610 ") : "") + eventData.title 937 color: isDeadline ? Color.mOnSecondary : (isTodoItem ? Color.mOnSecondary : Color.mOnTertiary) 938 font.pointSize: Style.fontSizeXXS; font.weight: Font.Medium 939 font.strikeout: isTodoItem && eventData.todoStatus === "COMPLETED" 940 elide: Text.ElideRight; verticalAlignment: Text.AlignVCenter 941 } 942 } 943 MouseArea { 944 anchors.fill: parent 945 hoverEnabled: true 946 cursorShape: Qt.PointingHandCursor 947 onEntered: { 948 var tip = mainInstance?.getEventTooltip(eventData) || "" 949 TooltipService.show(parent, tip, "auto", Style.tooltipDelay, Settings.data.ui.fontFixed) 950 } 951 onClicked: { 952 if (isTodoItem) { 953 if (eventData.todoStatus === "COMPLETED") 954 mainInstance?.uncompleteTodo(eventData.calendarUid, eventData.todoUid) 955 else 956 mainInstance?.completeTodo(eventData.calendarUid, eventData.todoUid) 957 } else { 958 mainInstance?.handleEventClick(eventData) 959 showEventDetailDialog = true 960 eventDetailEditMode = false 961 showDeleteConfirmation = false 962 } 963 } 964 onExited: TooltipService.hide() 965 } 966 } 967 } 968 } 969 } 970 // Calendar flickable 971 Rectangle { 972 width: parent.width 973 height: parent.height - dayHeaders.height - (allDayEventsSection.visible ? allDayEventsSection.height : 0) 974 color: Color.mSurfaceVariant 975 radius: Style.radiusM 976 clip: true 977 978 Flickable { 979 id: calendarFlickable 980 anchors.fill: parent 981 clip: true 982 contentHeight: 24 * (root.hourHeight) 983 boundsBehavior: Flickable.DragOverBounds 984 onHeightChanged: Qt.callLater(root.adjustHourHeightForViewport) 985 986 Component.onCompleted: { 987 calendarFlickable.forceActiveFocus() 988 } 989 990 // Keyboard interaction 991 Keys.onPressed: function(event) { 992 if (event.key === Qt.Key_Up || event.key === Qt.Key_Down) { 993 var step = root.hourHeight 994 var targetY = event.key === Qt.Key_Up ? Math.max(0, contentY - step) : 995 Math.min(Math.max(0, contentHeight - height), contentY + step) 996 scrollAnim.targetY = targetY 997 scrollAnim.start() 998 event.accepted = true 999 } else if (event.key === Qt.Key_Left || event.key === Qt.Key_Right) { 1000 if (mainInstance) { 1001 mainInstance.navigateWeek(event.key === Qt.Key_Left ? -7 : 7) 1002 } 1003 event.accepted = true 1004 } 1005 } 1006 1007 NumberAnimation { 1008 id: scrollAnim 1009 target: calendarFlickable; property: "contentY"; duration: 100 1010 easing.type: Easing.OutCubic; property real targetY: 0; to: targetY 1011 } 1012 1013 ScrollBar.vertical: ScrollBar { policy: ScrollBar.AsNeeded } 1014 1015 Row { 1016 width: parent.width 1017 height: parent.height 1018 1019 // Time Column 1020 Column { 1021 width: root.timeColumnWidth 1022 height: parent.height 1023 Repeater { 1024 model: 23 1025 Rectangle { 1026 width: root.timeColumnWidth 1027 height: root.hourHeight 1028 color: "transparent" 1029 NText { 1030 text: { 1031 var hour = index + 1 1032 if (mainInstance?.use12hourFormat) { 1033 var d = new Date(); d.setHours(hour, 0, 0, 0) 1034 return mainInstance.formatTime(d) 1035 } 1036 return (hour < 10 ? "0" : "") + hour + ':00' 1037 } 1038 anchors.right: parent.right 1039 anchors.rightMargin: Style.marginS 1040 anchors.verticalCenter: parent.top 1041 anchors.verticalCenterOffset: root.hourHeight 1042 font.pointSize: Style.fontSizeXS; color: Color.mOnSurfaceVariant 1043 } 1044 } 1045 } 1046 } 1047 1048 // Hour Rectangles 1049 Item { 1050 width: 7 * ((mainInstance?.dayColumnWidth) + (root.daySpacing)) 1051 height: parent.height 1052 1053 Row { 1054 anchors.fill: parent 1055 spacing: root.daySpacing 1056 Repeater { 1057 model: 7 1058 Column { 1059 width: mainInstance?.dayColumnWidth 1060 height: parent.height 1061 Repeater { 1062 model: 24 1063 Rectangle { width: parent.width; height: 1; color: Color.mSurfaceVariant } 1064 } 1065 } 1066 } 1067 } 1068 // Hour Lines 1069 Repeater { 1070 model: 24 1071 Rectangle { 1072 width: parent.width; height: 1 1073 y: index * (root.hourHeight) 1074 color: Qt.alpha(mainInstance?.lineColor || Color.mOutline, mainInstance?.hourLineOpacitySetting || 0.5) 1075 } 1076 } 1077 // Day Lines 1078 Repeater { 1079 model: 6 1080 Rectangle { 1081 width: 1; height: parent.height 1082 x: (index + 1) * ((mainInstance?.dayColumnWidth) + (root.daySpacing)) - ((root.daySpacing) / 2) 1083 color: Qt.alpha(mainInstance?.lineColor || Color.mOutline, mainInstance?.dayLineOpacitySetting || 0.9) 1084 } 1085 } 1086 1087 // Event positioning 1088 Repeater { 1089 model: mainInstance?.eventsModel 1090 delegate: Item { 1091 property var eventData: model 1092 property int dayIndex: mainInstance?.getDisplayDayIndexForDate(model.startTime) ?? -1 1093 property real startHour: model.startTime.getHours() + model.startTime.getMinutes() / 60 1094 property real endHour: model.endTime.getHours() + model.endTime.getMinutes() / 60 1095 property real duration: Math.max(0, (model.endTime - model.startTime) / 3600000) 1096 1097 property real exactHeight: Math.max(1, duration * (root.hourHeight) - 1) 1098 property bool isCompact: exactHeight < 40 1099 property var overlapInfo: mainInstance?.overlappingEventsData?.[index] ?? { 1100 xOffset: 0, width: (mainInstance?.dayColumnWidth) - 8, lane: 0, totalLanes: 1 1101 } 1102 property real eventWidth: overlapInfo.width - 1 1103 property real eventXOffset: overlapInfo.xOffset 1104 1105 property bool isTodoItem: model.isTodo || false 1106 property bool isDeadline: model.isDeadlineMarker || false 1107 property color eventColor: isDeadline ? Color.mSecondary : (isTodoItem ? Color.mSecondary : Color.mPrimary) 1108 property color eventTextColor: isDeadline ? Color.mOnSecondary : (isTodoItem ? Color.mOnSecondary : Color.mOnPrimary) 1109 1110 visible: dayIndex >= 0 && dayIndex < 7 && duration > 0 1111 width: eventWidth 1112 height: isDeadline ? Math.max(8, Math.min(12, exactHeight)) : exactHeight 1113 x: dayIndex * ((mainInstance?.dayColumnWidth) + (root.daySpacing)) + eventXOffset 1114 y: startHour * (root.hourHeight) 1115 z: 100 + overlapInfo.lane 1116 1117 Rectangle { 1118 anchors.fill: parent 1119 color: eventColor 1120 radius: Style.radiusS 1121 opacity: isDeadline ? 0.95 : (isTodoItem && model.todoStatus === "COMPLETED" ? 0.5 : 0.9) 1122 clip: true 1123 Rectangle { 1124 visible: exactHeight < 5 && overlapInfo.lane > 0 1125 anchors.fill: parent 1126 color: "transparent" 1127 radius: parent.radius 1128 border.width: 1 1129 border.color: eventColor 1130 } 1131 Loader { 1132 anchors.fill: parent 1133 anchors.margins: exactHeight < 10 ? 1 : Style.marginS 1134 anchors.leftMargin: exactHeight < 10 ? 1 : Style.marginS + 3 1135 sourceComponent: isDeadline ? deadlineLayout : (isCompact ? compactLayout : normalLayout) 1136 } 1137 } 1138 1139 Component { 1140 id: normalLayout 1141 Column { 1142 spacing: 2 1143 width: parent.width - 3 1144 NText { 1145 visible: exactHeight >= 20 1146 text: (isTodoItem ? (model.todoStatus === "COMPLETED" ? "\u2611 " : "\u2610 ") : "") + model.title 1147 color: eventTextColor 1148 font.pointSize: Style.fontSizeXS; font.weight: Font.Medium 1149 font.strikeout: isTodoItem && model.todoStatus === "COMPLETED" 1150 elide: Text.ElideRight; width: parent.width 1151 } 1152 NText { 1153 visible: exactHeight >= 30 && !isTodoItem 1154 text: mainInstance?.formatTimeRangeForDisplay(model) || "" 1155 color: eventTextColor 1156 font.pointSize: Style.fontSizeXXS; opacity: 0.9 1157 elide: Text.ElideRight; width: parent.width 1158 } 1159 NText { 1160 visible: exactHeight >= 45 && model.location && model.location !== "" 1161 text: "\u26B2 " + (model.location || "") 1162 color: eventTextColor 1163 font.pointSize: Style.fontSizeXXS; opacity: 0.8 1164 elide: Text.ElideRight; width: parent.width 1165 } 1166 } 1167 } 1168 1169 Component { 1170 id: compactLayout 1171 NText { 1172 text: { 1173 var prefix = isTodoItem ? (model.todoStatus === "COMPLETED" ? "\u2611 " : "\u2610 ") : "" 1174 if (exactHeight < 15) return prefix + model.title 1175 if (isTodoItem) return prefix + model.title 1176 return model.title + " \u2022 " + (mainInstance?.formatTimeRangeForDisplay(model) || "") 1177 } 1178 color: eventTextColor 1179 font.pointSize: exactHeight < 15 ? Style.fontSizeXXS : Style.fontSizeXS 1180 font.weight: Font.Medium 1181 font.strikeout: isTodoItem && model.todoStatus === "COMPLETED" 1182 elide: Text.ElideRight; verticalAlignment: Text.AlignVCenter 1183 width: parent.width - 3 1184 } 1185 } 1186 1187 Component { 1188 id: deadlineLayout 1189 Rectangle { 1190 anchors.fill: parent 1191 color: eventColor 1192 radius: parent.radius 1193 opacity: 0.95 1194 } 1195 } 1196 1197 MouseArea { 1198 anchors.fill: parent 1199 hoverEnabled: true 1200 cursorShape: Qt.PointingHandCursor 1201 onEntered: { 1202 var tip = mainInstance?.getEventTooltip(model) || "" 1203 TooltipService.show(parent, tip, "auto", Style.tooltipDelay, Settings.data.ui.fontFixed) 1204 } 1205 onClicked: { 1206 if (isTodoItem) { 1207 if (model.todoStatus === "COMPLETED") 1208 mainInstance?.uncompleteTodo(model.calendarUid, model.todoUid) 1209 else 1210 mainInstance?.completeTodo(model.calendarUid, model.todoUid) 1211 } else { 1212 mainInstance?.handleEventClick(eventData) 1213 showEventDetailDialog = true 1214 eventDetailEditMode = false 1215 showDeleteConfirmation = false 1216 } 1217 } 1218 onExited: TooltipService.hide() 1219 } 1220 } 1221 } 1222 1223 // Time Indicator 1224 Rectangle { 1225 property var now: new Date() 1226 property date today: new Date(now.getFullYear(), now.getMonth(), now.getDate()) 1227 property date weekStartDate: mainInstance?.weekStart ?? new Date() 1228 property date weekEndDate: mainInstance ? 1229 new Date(mainInstance.weekStart.getFullYear(), mainInstance.weekStart.getMonth(), mainInstance.weekStart.getDate() + 7) : new Date() 1230 property bool inCurrentWeek: today >= weekStartDate && today < weekEndDate 1231 property int currentDay: mainInstance?.getDayIndexForDate(now) ?? -1 1232 property real currentHour: now.getHours() + now.getMinutes() / 60 1233 1234 visible: inCurrentWeek && currentDay >= 0 1235 width: mainInstance?.dayColumnWidth 1236 height: 2 1237 x: currentDay * ((mainInstance?.dayColumnWidth) + (root.daySpacing)) 1238 y: currentHour * (root.hourHeight) 1239 color: Color.mError 1240 radius: 1 1241 z: 1000 1242 Rectangle { 1243 width: 8; height: 8; radius: 4; color: Color.mError 1244 anchors.verticalCenter: parent.verticalCenter; x: -4 1245 } 1246 Timer { 1247 interval: 60000; running: true; repeat: true 1248 onTriggered: parent.now = new Date() 1249 } 1250 } 1251 } 1252 } 1253 } 1254 } 1255 } 1256 } 1257 1258 } 1259 } 1260}