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