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: 700 * 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 25 property real hourHeight: 50 * Style.uiScaleRatio 26 property real timeColumnWidth: 65 * Style.uiScaleRatio 27 property real daySpacing: 1 * Style.uiScaleRatio 28 29 // Panel doesn't need its own CalendarService connection - Main.qml handles it. 30 // When panel opens, trigger a fresh load if needed. 31 Component.onCompleted: mainInstance?.initializePlugin() 32 onVisibleChanged: if (visible && mainInstance) { 33 mainInstance.loadEvents() 34 mainInstance.goToToday() 35 Qt.callLater(root.scrollToCurrentTime) 36 } 37 38 // Scroll to time indicator position 39 function scrollToCurrentTime() { 40 if (!mainInstance || !calendarFlickable) return 41 var now = new Date(), today = new Date(now.getFullYear(), now.getMonth(), now.getDate()) 42 var weekStart = new Date(mainInstance.weekStart) 43 var weekEnd = new Date(weekStart.getFullYear(), weekStart.getMonth(), weekStart.getDate() + 7) 44 45 if (today >= weekStart && today < weekEnd) { 46 var currentHour = now.getHours() + now.getMinutes() / 60 47 var scrollPos = (currentHour * hourHeight) - (calendarFlickable.height / 2) 48 var maxScroll = Math.max(0, (24 * hourHeight) - calendarFlickable.height) 49 scrollAnim.targetY = Math.max(0, Math.min(scrollPos, maxScroll)) 50 scrollAnim.start() 51 } 52 } 53 54 // Event creation dialog 55 Rectangle { 56 id: createEventOverlay 57 anchors.fill: parent 58 color: Qt.rgba(0, 0, 0, 0.5) 59 visible: showCreateDialog 60 z: 2000 61 62 MouseArea { anchors.fill: parent; onClicked: showCreateDialog = false } 63 64 Rectangle { 65 anchors.centerIn: parent 66 width: 400 * Style.uiScaleRatio 67 height: createDialogColumn.implicitHeight + 2 * Style.marginM 68 color: Color.mSurface 69 radius: Style.radiusM 70 71 MouseArea { anchors.fill: parent } // block clicks through 72 73 ColumnLayout { 74 id: createDialogColumn 75 anchors.fill: parent 76 anchors.margins: Style.marginM 77 spacing: Style.marginS 78 79 NText { 80 text: pluginApi.tr("panel.add_event") 81 font.pointSize: Style.fontSizeL; font.weight: Font.Bold 82 color: Color.mOnSurface 83 } 84 85 NText { text: pluginApi.tr("panel.summary"); color: Color.mOnSurfaceVariant; font.pointSize: Style.fontSizeS } 86 TextField { 87 id: createEventSummary 88 Layout.fillWidth: true 89 placeholderText: pluginApi.tr("panel.summary") 90 color: Color.mOnSurface 91 background: Rectangle { color: Color.mSurfaceVariant; radius: Style.radiusS } 92 } 93 94 NText { text: pluginApi.tr("panel.date"); color: Color.mOnSurfaceVariant; font.pointSize: Style.fontSizeS } 95 TextField { 96 id: createEventDate 97 Layout.fillWidth: true 98 placeholderText: "YYYY-MM-DD" 99 color: Color.mOnSurface 100 background: Rectangle { color: Color.mSurfaceVariant; radius: Style.radiusS } 101 } 102 103 RowLayout { 104 spacing: Style.marginS 105 ColumnLayout { 106 Layout.fillWidth: true 107 NText { text: pluginApi.tr("panel.start_time"); color: Color.mOnSurfaceVariant; font.pointSize: Style.fontSizeS } 108 TextField { 109 id: createEventStartTime 110 Layout.fillWidth: true 111 placeholderText: "HH:MM" 112 color: Color.mOnSurface 113 background: Rectangle { color: Color.mSurfaceVariant; radius: Style.radiusS } 114 } 115 } 116 ColumnLayout { 117 Layout.fillWidth: true 118 NText { text: pluginApi.tr("panel.end_time"); color: Color.mOnSurfaceVariant; font.pointSize: Style.fontSizeS } 119 TextField { 120 id: createEventEndTime 121 Layout.fillWidth: true 122 placeholderText: "HH:MM" 123 color: Color.mOnSurface 124 background: Rectangle { color: Color.mSurfaceVariant; radius: Style.radiusS } 125 } 126 } 127 } 128 129 NText { text: pluginApi.tr("panel.location"); color: Color.mOnSurfaceVariant; font.pointSize: Style.fontSizeS } 130 TextField { 131 id: createEventLocation 132 Layout.fillWidth: true 133 placeholderText: pluginApi.tr("panel.location") 134 color: Color.mOnSurface 135 background: Rectangle { color: Color.mSurfaceVariant; radius: Style.radiusS } 136 } 137 138 NText { text: pluginApi.tr("panel.description"); color: Color.mOnSurfaceVariant; font.pointSize: Style.fontSizeS } 139 TextField { 140 id: createEventDescription 141 Layout.fillWidth: true 142 placeholderText: pluginApi.tr("panel.description") 143 color: Color.mOnSurface 144 background: Rectangle { color: Color.mSurfaceVariant; radius: Style.radiusS } 145 } 146 147 NText { text: pluginApi.tr("panel.calendar_select"); color: Color.mOnSurfaceVariant; font.pointSize: Style.fontSizeS } 148 ComboBox { 149 id: calendarSelector 150 Layout.fillWidth: true 151 model: CalendarService.calendars || [] 152 textRole: "name" 153 background: Rectangle { color: Color.mSurfaceVariant; radius: Style.radiusS } 154 } 155 156 RowLayout { 157 Layout.fillWidth: true 158 spacing: Style.marginS 159 160 Item { Layout.fillWidth: true } 161 162 Rectangle { 163 Layout.preferredWidth: cancelBtn.implicitWidth + 2 * Style.marginM 164 Layout.preferredHeight: cancelBtn.implicitHeight + Style.marginS 165 color: Color.mSurfaceVariant; radius: Style.radiusS 166 NText { 167 id: cancelBtn; anchors.centerIn: parent 168 text: pluginApi.tr("panel.cancel"); color: Color.mOnSurfaceVariant 169 } 170 MouseArea { anchors.fill: parent; cursorShape: Qt.PointingHandCursor; onClicked: showCreateDialog = false } 171 } 172 173 Rectangle { 174 Layout.preferredWidth: createBtn.implicitWidth + 2 * Style.marginM 175 Layout.preferredHeight: createBtn.implicitHeight + Style.marginS 176 color: Color.mPrimary; radius: Style.radiusS 177 opacity: createEventSummary.text.trim() !== "" ? 1.0 : 0.5 178 NText { 179 id: createBtn; anchors.centerIn: parent 180 text: pluginApi.tr("panel.create"); color: Color.mOnPrimary; font.weight: Font.Bold 181 } 182 MouseArea { 183 anchors.fill: parent; cursorShape: Qt.PointingHandCursor 184 onClicked: { 185 if (createEventSummary.text.trim() === "") return 186 var cal = CalendarService.calendars?.[calendarSelector.currentIndex] 187 var calUid = cal?.uid || "" 188 var dateParts = createEventDate.text.split("-") 189 var startParts = createEventStartTime.text.split(":") 190 var endParts = createEventEndTime.text.split(":") 191 var startDate = new Date(parseInt(dateParts[0]), parseInt(dateParts[1])-1, parseInt(dateParts[2]), 192 parseInt(startParts[0]), parseInt(startParts[1]), 0) 193 var endDate = new Date(parseInt(dateParts[0]), parseInt(dateParts[1])-1, parseInt(dateParts[2]), 194 parseInt(endParts[0]), parseInt(endParts[1]), 0) 195 mainInstance?.createEvent(calUid, createEventSummary.text.trim(), 196 Math.floor(startDate.getTime()/1000), Math.floor(endDate.getTime()/1000), 197 createEventLocation.text.trim(), createEventDescription.text.trim()) 198 showCreateDialog = false 199 } 200 } 201 } 202 } 203 } 204 } 205 } 206 207 // UI 208 Rectangle { 209 id: panelContainer 210 anchors.fill: parent 211 color: "transparent" 212 213 ColumnLayout { 214 anchors.fill: parent 215 anchors.margins: Style.marginM 216 spacing: Style.marginM 217 218 //Header Section 219 Rectangle { 220 id: header 221 Layout.fillWidth: true 222 Layout.preferredHeight: topHeaderHeight 223 color: Color.mSurfaceVariant 224 radius: Style.radiusM 225 226 RowLayout { 227 anchors.margins: Style.marginM 228 anchors.fill: parent 229 230 NIcon { icon: "calendar-week"; pointSize: Style.fontSizeXXL; color: Color.mPrimary } 231 232 ColumnLayout { 233 Layout.fillHeight: true 234 spacing: 0 235 NText { 236 text: pluginApi.tr("panel.header") 237 font.pointSize: Style.fontSizeL; font.weight: Font.Bold; color: Color.mOnSurface 238 } 239 RowLayout { 240 spacing: Style.marginS 241 NText { 242 text: mainInstance?.monthRangeText || "" 243 font.pointSize: Style.fontSizeS; font.weight: Font.Medium; color: Color.mOnSurfaceVariant 244 } 245 Rectangle { 246 Layout.preferredWidth: 8; Layout.preferredHeight: 8; radius: 4 247 color: mainInstance?.isLoading ? Color.mError : 248 mainInstance?.syncStatus?.includes("No") ? Color.mError : Color.mOnSurfaceVariant 249 } 250 NText { 251 text: mainInstance?.syncStatus || "" 252 font.pointSize: Style.fontSizeS; color: Color.mOnSurfaceVariant 253 } 254 } 255 } 256 257 Item { Layout.fillWidth: true } 258 259 RowLayout { 260 spacing: Style.marginS 261 NIconButton { 262 icon: "plus"; tooltipText: pluginApi.tr("panel.add_event") 263 onClicked: { 264 createEventSummary.text = "" 265 createEventLocation.text = "" 266 createEventDescription.text = "" 267 var now = new Date() 268 var startH = now.getHours() + 1 269 createEventDate.text = now.getFullYear() + "-" + String(now.getMonth()+1).padStart(2,'0') + "-" + String(now.getDate()).padStart(2,'0') 270 createEventStartTime.text = String(startH).padStart(2,'0') + ":00" 271 createEventEndTime.text = String(startH+1).padStart(2,'0') + ":00" 272 showCreateDialog = true 273 } 274 } 275 NIconButton { 276 icon: "chevron-left" 277 onClicked: { mainInstance?.navigateWeek(-7); mainInstance?.loadEvents() } 278 } 279 NIconButton { 280 icon: "calendar"; tooltipText: pluginApi.tr("panel.today") 281 onClicked: { mainInstance?.goToToday(); mainInstance?.loadEvents(); Qt.callLater(root.scrollToCurrentTime) } 282 } 283 NIconButton { 284 icon: "chevron-right" 285 onClicked: { mainInstance?.navigateWeek(7); mainInstance?.loadEvents() } 286 } 287 NIconButton { 288 icon: "refresh"; tooltipText: I18n.tr("common.refresh") 289 onClicked: mainInstance?.loadEvents() 290 enabled: mainInstance ? !mainInstance.isLoading : false 291 } 292 NIconButton { 293 icon: "close"; tooltipText: I18n.tr("common.close") 294 onClicked: pluginApi.closePanel(pluginApi.panelOpenScreen) 295 } 296 } 297 } 298 } 299 300 // Calendar 301 Rectangle { 302 Layout.fillWidth: true 303 Layout.fillHeight: true 304 color: Color.mSurfaceVariant 305 radius: Style.radiusM 306 clip: true 307 308 Column { 309 anchors.fill: parent 310 spacing: 0 311 312 //Day Headers 313 Rectangle { 314 id: dayHeaders 315 width: parent.width 316 height: 56 317 color: Color.mSurfaceVariant 318 radius: Style.radiusM 319 320 Row { 321 anchors.fill: parent 322 anchors.leftMargin: root.timeColumnWidth 323 spacing: root.daySpacing 324 325 Repeater { 326 model: 7 327 Rectangle { 328 width: mainInstance?.dayColumnWidth 329 height: parent.height 330 color: "transparent" 331 property date dayDate: mainInstance?.weekDates?.[index] || new Date() 332 property bool isToday: { 333 var today = new Date() 334 return dayDate.getDate() === today.getDate() && 335 dayDate.getMonth() === today.getMonth() && 336 dayDate.getFullYear() === today.getFullYear() 337 } 338 Rectangle { 339 anchors.fill: parent 340 anchors.margins: 4 341 color: Color.mSurfaceVariant 342 border.color: isToday ? Color.mPrimary : "transparent" 343 border.width: 2 344 radius: Style.radiusM 345 Column { 346 anchors.centerIn: parent 347 spacing: 2 348 NText { 349 anchors.horizontalCenter: parent.horizontalCenter 350 text: dayDate ? I18n.locale.dayName(dayDate.getDay(), Locale.ShortFormat).toUpperCase() : "" 351 color: isToday ? Color.mPrimary : Color.mOnSurface 352 font.pointSize: Style.fontSizeS; font.weight: Font.Medium 353 } 354 NText { 355 anchors.horizontalCenter: parent.horizontalCenter 356 text: dayDate ? ((dayDate.getDate() < 10 ? "0" : "") + dayDate.getDate()) : "" 357 color: isToday ? Color.mPrimary : Color.mOnSurface 358 font.pointSize: Style.fontSizeM; font.weight: Font.Bold 359 } 360 } 361 } 362 } 363 } 364 } 365 } 366 // All-day row 367 Rectangle { 368 id: allDayEventsSection 369 width: parent.width 370 height: mainInstance ? Math.round(mainInstance.allDaySectionHeight * Style.uiScaleRatio) : 0 371 color: Color.mSurfaceVariant 372 visible: height > 0 373 374 Item { 375 id: allDayEventsContainer 376 anchors.fill: parent 377 anchors.leftMargin: root.timeColumnWidth 378 379 Repeater { 380 model: 6 381 delegate: Rectangle { 382 width: 1; height: parent.height 383 x: (index + 1) * ((mainInstance?.dayColumnWidth) + (root.daySpacing)) - ((root.daySpacing) / 2) 384 color: Qt.alpha(mainInstance?.lineColor || Color.mOutline, mainInstance?.dayLineOpacitySetting || 0.9) 385 } 386 } 387 388 Repeater { 389 model: mainInstance?.allDayEventsWithLayout || [] 390 delegate: Item { 391 property var eventData: modelData 392 x: eventData.startDay * ((mainInstance?.dayColumnWidth) + (root.daySpacing)) 393 y: eventData.lane * 25 394 width: (eventData.spanDays * ((mainInstance?.dayColumnWidth) + (root.daySpacing))) - (root.daySpacing) 395 height: 24 396 397 Rectangle { 398 anchors.fill: parent 399 color: Color.mTertiary 400 radius: Style.radiusS 401 NText { 402 anchors.fill: parent; anchors.margins: 4 403 text: eventData.title 404 color: Color.mOnTertiary 405 font.pointSize: Style.fontSizeXXS; font.weight: Font.Medium 406 elide: Text.ElideRight; verticalAlignment: Text.AlignVCenter 407 } 408 } 409 MouseArea { 410 anchors.fill: parent 411 hoverEnabled: true 412 cursorShape: Qt.PointingHandCursor 413 onEntered: { 414 var tip = mainInstance?.getEventTooltip(eventData) || "" 415 TooltipService.show(parent, tip, "auto", Style.tooltipDelay, Settings.data.ui.fontFixed) 416 } 417 onClicked: mainInstance?.handleEventClick(eventData) 418 onExited: TooltipService.hide() 419 } 420 } 421 } 422 } 423 } 424 // Calendar flickable 425 Rectangle { 426 width: parent.width 427 height: parent.height - dayHeaders.height - (allDayEventsSection.visible ? allDayEventsSection.height : 0) 428 color: Color.mSurfaceVariant 429 radius: Style.radiusM 430 clip: true 431 432 Flickable { 433 id: calendarFlickable 434 anchors.fill: parent 435 clip: true 436 contentHeight: 24 * (root.hourHeight) 437 boundsBehavior: Flickable.DragOverBounds 438 439 Component.onCompleted: { 440 calendarFlickable.forceActiveFocus() 441 } 442 443 // Keyboard interaction 444 Keys.onPressed: function(event) { 445 if (event.key === Qt.Key_Up || event.key === Qt.Key_Down) { 446 var step = root.hourHeight 447 var targetY = event.key === Qt.Key_Up ? Math.max(0, contentY - step) : 448 Math.min(Math.max(0, contentHeight - height), contentY + step) 449 scrollAnim.targetY = targetY 450 scrollAnim.start() 451 event.accepted = true 452 } else if (event.key === Qt.Key_Left || event.key === Qt.Key_Right) { 453 if (mainInstance) { 454 mainInstance.navigateWeek(event.key === Qt.Key_Left ? -7 : 7) 455 mainInstance.loadEvents() 456 } 457 event.accepted = true 458 } 459 } 460 461 NumberAnimation { 462 id: scrollAnim 463 target: calendarFlickable; property: "contentY"; duration: 100 464 easing.type: Easing.OutCubic; property real targetY: 0; to: targetY 465 } 466 467 ScrollBar.vertical: ScrollBar { policy: ScrollBar.AsNeeded } 468 469 Row { 470 width: parent.width 471 height: parent.height 472 473 // Time Column 474 Column { 475 width: root.timeColumnWidth 476 height: parent.height 477 Repeater { 478 model: 23 479 Rectangle { 480 width: root.timeColumnWidth 481 height: root.hourHeight 482 color: "transparent" 483 NText { 484 text: { 485 var hour = index + 1 486 if (mainInstance?.use12hourFormat) { 487 var d = new Date(); d.setHours(hour, 0, 0, 0) 488 return mainInstance.formatTime(d) 489 } 490 return (hour < 10 ? "0" : "") + hour + ':00' 491 } 492 anchors.right: parent.right 493 anchors.rightMargin: Style.marginS 494 anchors.verticalCenter: parent.top 495 anchors.verticalCenterOffset: root.hourHeight 496 font.pointSize: Style.fontSizeXS; color: Color.mOnSurfaceVariant 497 } 498 } 499 } 500 } 501 502 // Hour Rectangles 503 Item { 504 width: 7 * ((mainInstance?.dayColumnWidth) + (root.daySpacing)) 505 height: parent.height 506 507 Row { 508 anchors.fill: parent 509 spacing: root.daySpacing 510 Repeater { 511 model: 7 512 Column { 513 width: mainInstance?.dayColumnWidth 514 height: parent.height 515 Repeater { 516 model: 24 517 Rectangle { width: parent.width; height: 1; color: Color.mSurfaceVariant } 518 } 519 } 520 } 521 } 522 // Hour Lines 523 Repeater { 524 model: 24 525 Rectangle { 526 width: parent.width; height: 1 527 y: index * (root.hourHeight) 528 color: Qt.alpha(mainInstance?.lineColor || Color.mOutline, mainInstance?.hourLineOpacitySetting || 0.5) 529 } 530 } 531 // Day Lines 532 Repeater { 533 model: 6 534 Rectangle { 535 width: 1; height: parent.height 536 x: (index + 1) * ((mainInstance?.dayColumnWidth) + (root.daySpacing)) - ((root.daySpacing) / 2) 537 color: Qt.alpha(mainInstance?.lineColor || Color.mOutline, mainInstance?.dayLineOpacitySetting || 0.9) 538 } 539 } 540 541 // Event positioning 542 Repeater { 543 model: mainInstance?.eventsModel 544 delegate: Item { 545 property var eventData: model 546 property int dayIndex: mainInstance?.getDisplayDayIndexForDate(model.startTime) ?? -1 547 property real startHour: model.startTime.getHours() + model.startTime.getMinutes() / 60 548 property real endHour: model.endTime.getHours() + model.endTime.getMinutes() / 60 549 property real duration: Math.max(0, (model.endTime - model.startTime) / 3600000) 550 551 property real exactHeight: Math.max(1, duration * (mainInstance?.hourHeight || 50) - 1) 552 property bool isCompact: exactHeight < 40 553 property var overlapInfo: mainInstance?.overlappingEventsData?.[index] ?? { 554 xOffset: 0, width: (mainInstance?.dayColumnWidth) - 8, lane: 0, totalLanes: 1 555 } 556 property real eventWidth: overlapInfo.width - 1 557 property real eventXOffset: overlapInfo.xOffset 558 559 visible: dayIndex >= 0 && dayIndex < 7 && duration > 0 560 width: eventWidth 561 height: exactHeight 562 x: dayIndex * ((mainInstance?.dayColumnWidth) + (root.daySpacing)) + eventXOffset 563 y: startHour * (mainInstance?.hourHeight || 50) 564 z: 100 + overlapInfo.lane 565 566 Rectangle { 567 anchors.fill: parent 568 color: Color.mPrimary 569 radius: Style.radiusS 570 opacity: 0.9 571 clip: true 572 Rectangle { 573 visible: exactHeight < 5 && overlapInfo.lane > 0 574 anchors.fill: parent 575 color: "transparent" 576 radius: parent.radius 577 border.width: 1 578 border.color: Color.mPrimary 579 } 580 Loader { 581 anchors.fill: parent 582 anchors.margins: exactHeight < 10 ? 1 : Style.marginS 583 anchors.leftMargin: exactHeight < 10 ? 1 : Style.marginS + 3 584 sourceComponent: isCompact ? compactLayout : normalLayout 585 } 586 } 587 588 Component { 589 id: normalLayout 590 Column { 591 spacing: 2 592 width: parent.width - 3 593 NText { 594 visible: exactHeight >= 20 595 text: model.title 596 color: Color.mOnPrimary 597 font.pointSize: Style.fontSizeXS; font.weight: Font.Medium 598 elide: Text.ElideRight; width: parent.width 599 } 600 NText { 601 visible: exactHeight >= 30 602 text: mainInstance?.formatTimeRangeForDisplay(model) || "" 603 color: Color.mOnPrimary 604 font.pointSize: Style.fontSizeXXS; opacity: 0.9 605 elide: Text.ElideRight; width: parent.width 606 } 607 NText { 608 visible: exactHeight >= 45 && model.location && model.location !== "" 609 text: "\u26B2 " + (model.location || "") 610 color: Color.mOnPrimary 611 font.pointSize: Style.fontSizeXXS; opacity: 0.8 612 elide: Text.ElideRight; width: parent.width 613 } 614 } 615 } 616 617 Component { 618 id: compactLayout 619 NText { 620 text: exactHeight < 15 ? model.title : 621 model.title + " • " + (mainInstance?.formatTimeRangeForDisplay(model) || "") 622 color: Color.mOnPrimary 623 font.pointSize: exactHeight < 15 ? Style.fontSizeXXS : Style.fontSizeXS 624 font.weight: Font.Medium 625 elide: Text.ElideRight; verticalAlignment: Text.AlignVCenter 626 width: parent.width - 3 627 } 628 } 629 630 MouseArea { 631 anchors.fill: parent 632 hoverEnabled: true 633 cursorShape: Qt.PointingHandCursor 634 onEntered: { 635 var tip = mainInstance?.getEventTooltip(model) || "" 636 TooltipService.show(parent, tip, "auto", Style.tooltipDelay, Settings.data.ui.fontFixed) 637 } 638 onClicked: mainInstance?.handleEventClick(eventData) 639 onExited: TooltipService.hide() 640 } 641 } 642 } 643 644 // Time Indicator 645 Rectangle { 646 property var now: new Date() 647 property date today: new Date(now.getFullYear(), now.getMonth(), now.getDate()) 648 property date weekStartDate: mainInstance?.weekStart ?? new Date() 649 property date weekEndDate: mainInstance ? 650 new Date(mainInstance.weekStart.getFullYear(), mainInstance.weekStart.getMonth(), mainInstance.weekStart.getDate() + 7) : new Date() 651 property bool inCurrentWeek: today >= weekStartDate && today < weekEndDate 652 property int currentDay: mainInstance?.getDayIndexForDate(now) ?? -1 653 property real currentHour: now.getHours() + now.getMinutes() / 60 654 655 visible: inCurrentWeek && currentDay >= 0 656 width: mainInstance?.dayColumnWidth 657 height: 2 658 x: currentDay * ((mainInstance?.dayColumnWidth) + (root.daySpacing)) 659 y: currentHour * (root.hourHeight) 660 color: Color.mError 661 radius: 1 662 z: 1000 663 Rectangle { 664 width: 8; height: 8; radius: 4; color: Color.mError 665 anchors.verticalCenter: parent.verticalCenter; x: -4 666 } 667 Timer { 668 interval: 60000; running: true; repeat: true 669 onTriggered: parent.now = new Date() 670 } 671 } 672 } 673 } 674 } 675 } 676 } 677 } 678 } 679 } 680}