Route the interactive Panama date menu
This commit is contained in:
@@ -24,7 +24,7 @@ Pill {
|
||||
return date + time;
|
||||
}
|
||||
|
||||
onActivated: ShellState.toggle("notifications")
|
||||
onActivated: ShellState.toggleDateMenu("agenda")
|
||||
|
||||
Text {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
@@ -4,17 +4,20 @@
|
||||
// months and the panel below it never jumps. Days belonging to the neighbouring
|
||||
// months fill the edges, dimmed. Today wears the accent.
|
||||
//
|
||||
// Deliberately non-interactive apart from the header: there is no calendar
|
||||
// backend behind this, so a day cell that highlighted on hover would be
|
||||
// promising a click that does nothing.
|
||||
// Days become real navigation targets now that Evolution Data Server backs the
|
||||
// panel. Up to three source-coloured markers show whether a date has events.
|
||||
|
||||
import Quickshell
|
||||
import QtQuick
|
||||
import qs.config
|
||||
import qs.services
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
signal dateActivated(date value)
|
||||
signal visibleMonthChanged(int year, int month)
|
||||
|
||||
implicitHeight: body.implicitHeight
|
||||
|
||||
// Cells divide the available width evenly; the height is fixed so the grid
|
||||
@@ -37,6 +40,7 @@ Item {
|
||||
// panel opens, so it never comes back showing wherever you paged off to.
|
||||
property int viewYear: root.todayYear
|
||||
property int viewMonth: root.todayMonth
|
||||
property date selectedDate: CalendarAgenda.selectedDate
|
||||
|
||||
readonly property bool onCurrentMonth: root.viewYear === root.todayYear && root.viewMonth === root.todayMonth
|
||||
|
||||
@@ -53,29 +57,28 @@ Item {
|
||||
root.viewMonth = d.getMonth();
|
||||
}
|
||||
|
||||
function activateCell(model: var): void {
|
||||
if (!model.current) {
|
||||
root.viewYear = model.date.getFullYear();
|
||||
root.viewMonth = model.date.getMonth();
|
||||
}
|
||||
root.dateActivated(model.date);
|
||||
}
|
||||
|
||||
onViewYearChanged: root.visibleMonthChanged(root.viewYear, root.viewMonth)
|
||||
onViewMonthChanged: root.visibleMonthChanged(root.viewYear, root.viewMonth)
|
||||
|
||||
readonly property var cells: {
|
||||
const first = new Date(root.viewYear, root.viewMonth, 1);
|
||||
const offset = first.getDay(); // 0 = Sunday, matching the header row
|
||||
const inThisMonth = new Date(root.viewYear, root.viewMonth + 1, 0).getDate();
|
||||
const inPrevMonth = new Date(root.viewYear, root.viewMonth, 0).getDate();
|
||||
|
||||
const out = [];
|
||||
for (let i = 0; i < 42; i++) {
|
||||
const n = i - offset + 1;
|
||||
if (n < 1)
|
||||
const date = new Date(root.viewYear, root.viewMonth, i - offset + 1);
|
||||
out.push({
|
||||
day: inPrevMonth + n,
|
||||
current: false
|
||||
});
|
||||
else if (n > inThisMonth)
|
||||
out.push({
|
||||
day: n - inThisMonth,
|
||||
current: false
|
||||
});
|
||||
else
|
||||
out.push({
|
||||
day: n,
|
||||
current: true
|
||||
date: date,
|
||||
day: date.getDate(),
|
||||
current: date.getMonth() === root.viewMonth && date.getFullYear() === root.viewYear
|
||||
});
|
||||
}
|
||||
return out;
|
||||
@@ -198,6 +201,8 @@ Item {
|
||||
required property var modelData
|
||||
|
||||
readonly property bool isToday: cell.modelData.current && cell.modelData.day === root.todayDay && root.onCurrentMonth
|
||||
readonly property bool isSelected: CalendarAgenda.dateKey(cell.modelData.date) === CalendarAgenda.dateKey(root.selectedDate)
|
||||
readonly property var markers: CalendarAgenda.markersForDate(cell.modelData.date).slice(0, 3)
|
||||
|
||||
width: root.cellWidth
|
||||
height: root.cellHeight
|
||||
@@ -208,8 +213,18 @@ Item {
|
||||
height: root.cellHeight - 4
|
||||
radius: width / 2
|
||||
border.width: 0
|
||||
visible: cell.isToday
|
||||
color: Theme.accent
|
||||
visible: cell.isToday || cell.isSelected || cellMouse.containsMouse
|
||||
color: {
|
||||
if (cell.isToday)
|
||||
return Theme.accent;
|
||||
if (cell.isSelected)
|
||||
return Theme.alpha(Theme.accent, 0.20);
|
||||
return Theme.alpha(Theme.fg, 0.08);
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation { duration: Theme.durFast }
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
@@ -223,11 +238,44 @@ Item {
|
||||
color: {
|
||||
if (cell.isToday)
|
||||
return Theme.bgDark;
|
||||
if (cell.isSelected)
|
||||
return Theme.accent;
|
||||
return cell.modelData.current ? Theme.fg : Theme.fgMuted;
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: 1
|
||||
spacing: 2
|
||||
|
||||
Repeater {
|
||||
model: cell.markers
|
||||
|
||||
Rectangle {
|
||||
required property var modelData
|
||||
|
||||
width: 3
|
||||
height: 3
|
||||
radius: 2
|
||||
border.width: 0
|
||||
color: cell.isToday ? Theme.bgDark : modelData.color
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: cellMouse
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: root.activateCell(cell.modelData)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: root.visibleMonthChanged(root.viewYear, root.viewMonth)
|
||||
}
|
||||
|
||||
@@ -35,6 +35,10 @@ Singleton {
|
||||
|
||||
readonly property bool anyOverlayOpen: activeOverlay !== ""
|
||||
|
||||
// The date menu is one surface with two deliberate entry points. The clock
|
||||
// opens the Daybook; Super+B and notification actions open message history.
|
||||
property string dateMenuPage: "agenda"
|
||||
|
||||
// 0 means "use the currently focused workspace". A positive value lets a
|
||||
// contextual surface, such as Focus, open Mission Control at its target.
|
||||
property int overviewWorkspaceId: 0
|
||||
@@ -48,6 +52,21 @@ Singleton {
|
||||
root.activeOverlay = name;
|
||||
}
|
||||
|
||||
function openDateMenu(page: string): void {
|
||||
root.dateMenuPage = page === "notifications" ? "notifications" : "agenda";
|
||||
root.activeOverlay = "notifications";
|
||||
}
|
||||
|
||||
function toggleDateMenu(page: string): void {
|
||||
const target = page === "notifications" ? "notifications" : "agenda";
|
||||
if (root.activeOverlay === "notifications" && root.dateMenuPage === target) {
|
||||
root.activeOverlay = "";
|
||||
return;
|
||||
}
|
||||
root.dateMenuPage = target;
|
||||
root.activeOverlay = "notifications";
|
||||
}
|
||||
|
||||
function openOverview(workspaceId: int): void {
|
||||
root.overviewWorkspaceId = Math.max(0, workspaceId);
|
||||
root.overviewQuery = "";
|
||||
|
||||
@@ -253,7 +253,8 @@ ShellRoot {
|
||||
|
||||
IpcHandler {
|
||||
target: "notifications"
|
||||
function toggle(): void { ShellState.toggle("notifications"); }
|
||||
function toggle(): void { ShellState.toggleDateMenu("notifications"); }
|
||||
function open(): void { ShellState.openDateMenu("notifications"); }
|
||||
function clear(): void { Notifs.dismissAll(); }
|
||||
function dnd(): bool {
|
||||
Notifs.doNotDisturb = !Notifs.doNotDisturb;
|
||||
@@ -266,13 +267,15 @@ ShellRoot {
|
||||
function fixture(name: string): void { CalendarAgenda.applyFixture(name); }
|
||||
function reset(): void { CalendarAgenda.clearFixture(); }
|
||||
function refresh(): void { CalendarAgenda.refresh(); }
|
||||
function open(): void { ShellState.open("notifications"); }
|
||||
function open(): void { ShellState.openDateMenu("agenda"); }
|
||||
function close(): void { ShellState.close(); }
|
||||
function select(date: string): void { CalendarAgenda.selectIsoDate(date); }
|
||||
function status(): string {
|
||||
return JSON.stringify({
|
||||
phase: CalendarAgenda.phase,
|
||||
fixture: CalendarAgenda.fixtureMode,
|
||||
open: ShellState.notificationsOpen,
|
||||
page: ShellState.dateMenuPage,
|
||||
sourceCount: CalendarAgenda.sources.length,
|
||||
eventCount: CalendarAgenda.events.length,
|
||||
selectedDate: CalendarAgenda.selectedDateKey,
|
||||
|
||||
@@ -417,7 +417,7 @@ git commit -m "Add live calendar state to Quickshell"
|
||||
- Produces: `ShellState.dateMenuPage`, `ShellState.toggleDateMenu(page)`, and `ShellState.openDateMenu(page)`.
|
||||
- Produces from CalendarGrid: signals `dateActivated(date)` and `visibleMonthChanged(year, month)`.
|
||||
|
||||
- [ ] **Step 1: Extend the failing contract with page-routing assertions**
|
||||
- [x] **Step 1: Extend the failing contract with page-routing assertions**
|
||||
|
||||
Append fixture assertions:
|
||||
|
||||
@@ -433,11 +433,11 @@ jq -e '.open == true and .page == "notifications"' <<<"$(qs ipc call calendar-ag
|
||||
|
||||
Update the calendar status response to include only `open: ShellState.notificationsOpen` and `page: ShellState.dateMenuPage` in addition to the non-sensitive fields from Task 2.
|
||||
|
||||
- [ ] **Step 2: Run the contract and verify RED**
|
||||
- [x] **Step 2: Run the contract and verify RED**
|
||||
|
||||
Expected: FAIL because `dateMenuPage` and notification `open()` do not exist.
|
||||
|
||||
- [ ] **Step 3: Implement explicit ShellState routing**
|
||||
- [x] **Step 3: Implement explicit ShellState routing**
|
||||
|
||||
Add:
|
||||
|
||||
@@ -462,7 +462,7 @@ function toggleDateMenu(page: string): void {
|
||||
|
||||
Change Clock activation to `ShellState.toggleDateMenu("agenda")`. Change notification IPC `toggle()` to `ShellState.toggleDateMenu("notifications")`, add `open()` using `openDateMenu("notifications")`, and leave `Super+B` routed through that IPC target.
|
||||
|
||||
- [ ] **Step 4: Make CalendarGrid date-aware and event-aware**
|
||||
- [x] **Step 4: Make CalendarGrid date-aware and event-aware**
|
||||
|
||||
Replace numeric-only cell objects with full local `Date` values and add:
|
||||
|
||||
@@ -484,7 +484,7 @@ Every cell receives `markers: CalendarAgenda.markersForDate(cell.date).slice(0,
|
||||
|
||||
Month changes call `visibleMonthChanged(viewYear, viewMonth)`. Adjacent-month date activation changes month and selection in one click.
|
||||
|
||||
- [ ] **Step 5: Verify routing and grid contracts**
|
||||
- [x] **Step 5: Verify routing and grid contracts**
|
||||
|
||||
Run:
|
||||
|
||||
@@ -496,7 +496,7 @@ rg -q 'dateActivated' config/dot/quickshell/modules/datemenu/CalendarGrid.qml
|
||||
|
||||
Expected: contract PASS and both interaction hooks present.
|
||||
|
||||
- [ ] **Step 6: Commit routing and grid behavior**
|
||||
- [x] **Step 6: Commit routing and grid behavior**
|
||||
|
||||
```bash
|
||||
git add config/dot/quickshell/services/ShellState.qml \
|
||||
|
||||
@@ -30,6 +30,16 @@ jq -e '.selectedDate == "2026-08-18" and .selectedEventCount == 1' \
|
||||
<<<"$(qs ipc call calendar-agenda status)" >/dev/null \
|
||||
|| fail 'date selection did not update events'
|
||||
|
||||
qs ipc call calendar-agenda open >/dev/null
|
||||
jq -e '.open == true and .page == "agenda"' \
|
||||
<<<"$(qs ipc call calendar-agenda status)" >/dev/null \
|
||||
|| fail 'calendar open did not route to Agenda'
|
||||
|
||||
qs ipc call notifications open >/dev/null
|
||||
jq -e '.open == true and .page == "notifications"' \
|
||||
<<<"$(qs ipc call calendar-agenda status)" >/dev/null \
|
||||
|| fail 'notification open did not route to Notifications'
|
||||
|
||||
qs ipc call calendar-agenda reset >/dev/null
|
||||
jq -e '.fixture == false' <<<"$(qs ipc call calendar-agenda status)" >/dev/null \
|
||||
|| fail 'fixture reset did not restore live mode'
|
||||
|
||||
Reference in New Issue
Block a user