diff options
Diffstat (limited to 'desktop')
| -rw-r--r-- | desktop/Prefs.qml | 14 | ||||
| -rw-r--r-- | desktop/island/calendar/Cal.js | 28 | ||||
| -rw-r--r-- | desktop/island/calendar/CalendarPanel.qml | 2 | ||||
| -rw-r--r-- | desktop/island/calendar/EventEditor.qml | 11 | ||||
| -rw-r--r-- | desktop/island/calendar/test_cal.js | 13 | ||||
| -rw-r--r-- | desktop/modules/island/IslandCalendarModule.qml | 76 | ||||
| -rw-r--r-- | desktop/shell.qml | 2 |
7 files changed, 135 insertions, 11 deletions
diff --git a/desktop/Prefs.qml b/desktop/Prefs.qml index ef2ac72..0861258 100644 --- a/desktop/Prefs.qml +++ b/desktop/Prefs.qml @@ -84,6 +84,15 @@ Singleton { root.write(Object.assign({}, root.doc, { bootlogo: root.bootlogo })); } + // The island bar's settings (modules/island): the length a new event gets, + // and that its end keeps from the start, in minutes. + readonly property var islandDefaults: ({ eventMinutes: 60 }) + property var island: islandDefaults + function setIsland(changes) { + root.island = Object.assign({}, root.island, changes); + root.write(Object.assign({}, root.doc, { island: root.island })); + } + // The whole parsed object, so a write keeps keys this version does not // know about. property var doc: ({}) @@ -154,6 +163,11 @@ Singleton { const bl = parsed.bootlogo && typeof parsed.bootlogo === "object" && !Array.isArray(parsed.bootlogo) ? parsed.bootlogo : {}; root.bootlogo = Object.assign({}, root.bootlogoDefaults, bl); + const isl = parsed.island && typeof parsed.island === "object" ? parsed.island : {}; + root.island = { + eventMinutes: Number.isInteger(isl.eventMinutes) && isl.eventMinutes >= 5 && isl.eventMinutes <= 1440 + ? isl.eventMinutes : 60, + }; } FileView { diff --git a/desktop/island/calendar/Cal.js b/desktop/island/calendar/Cal.js index 6cf492d..6ec7bb3 100644 --- a/desktop/island/calendar/Cal.js +++ b/desktop/island/calendar/Cal.js @@ -112,14 +112,28 @@ function stepTime(s, minutes) { return pad(Math.floor(m / 60)) + ":" + pad(m % 60); } -// A new event on `day`: the next whole hour, one hour long, alarm 1h (the -// most common alarm in the live calendars). Capped at 22:00 so the end stays -// on the same day. -function newDraft(day, now, calendar) { +// A new event on `day`: the next whole hour, `minutes` long (the island's +// default event length), alarm 1h (the most common alarm in the live +// calendars). Capped at 22:00 so a short event stays on the same day. +function newDraft(day, now, calendar, minutes) { const h = Math.min(22, now.getHours() + 1); - return { uid: "", source: "", calendar: calendar, title: "", allDay: false, - startDay: day, endDay: day, startTime: pad(h) + ":00", endTime: pad(h + 1) + ":00", - repeat: "none", until: "", alarm: "1h", location: "", notes: "", override: false }; + return withStart({ uid: "", source: "", calendar: calendar, title: "", allDay: false, + startDay: day, endDay: day, startTime: pad(h) + ":00", endTime: pad(h) + ":00", + repeat: "none", until: "", alarm: "1h", location: "", notes: "", override: false }, + pad(h) + ":00", minutes); +} + +// The draft with a new start time, as typed, the end following it `minutes` later, +// into the next day if need be. A multi-day event keeps its end: snapping it +// to the start's day would throw the other days away. +function withStart(draft, time, minutes) { + const d = Object.assign({}, draft, { startTime: time }); + const n = normTime(time); + if (!validTime(n) || d.startDay !== d.endDay) return d; + const t = +n.slice(0, 2) * 60 + +n.slice(3, 5) + (minutes > 0 ? minutes : 60); + d.endDay = addDays(d.startDay, Math.floor(t / 1440)); + d.endTime = pad(Math.floor(t % 1440 / 60)) + ":" + pad(t % 60); + return d; } // calctl's event JSON as an editor draft. `source` is the calendar the event diff --git a/desktop/island/calendar/CalendarPanel.qml b/desktop/island/calendar/CalendarPanel.qml index 4a0a011..d28a856 100644 --- a/desktop/island/calendar/CalendarPanel.qml +++ b/desktop/island/calendar/CalendarPanel.qml @@ -82,7 +82,7 @@ Item { } function newEvent() { - editor.load(Cal.newDraft(panel.selected, new Date(), Calendar.defaultCalendar)); + editor.load(Cal.newDraft(panel.selected, new Date(), Calendar.defaultCalendar, Prefs.island.eventMinutes)); panel.editing = true; } diff --git a/desktop/island/calendar/EventEditor.qml b/desktop/island/calendar/EventEditor.qml index 41815e3..727e1df 100644 --- a/desktop/island/calendar/EventEditor.qml +++ b/desktop/island/calendar/EventEditor.qml @@ -55,7 +55,14 @@ Column { function setTime(key, value) { (key === "startTime" ? startTime : endTime).input.text = value; - ed.set(key, value); + if (key === "startTime") ed.setStart(value); + else ed.set(key, value); + } + + // The end follows the start by the island's default event length. + function setStart(text) { + ed.draft = Cal.withStart(ed.draft, text, Prefs.island.eventMinutes); + endTime.input.text = ed.draft.endTime; } // A click in the time picker: an hour keeps the minutes, a minute keeps @@ -195,7 +202,7 @@ Column { id: startTime width: 62 visible: !ed.draft.allDay - input.onTextEdited: ed.set("startTime", input.text) + input.onTextEdited: ed.setStart(input.text) input.onEditingFinished: if (Cal.normTime(input.text) !== input.text) ed.setTime("startTime", Cal.normTime(input.text)) MouseArea { anchors.fill: parent diff --git a/desktop/island/calendar/test_cal.js b/desktop/island/calendar/test_cal.js index de3d1c7..853cbb6 100644 --- a/desktop/island/calendar/test_cal.js +++ b/desktop/island/calendar/test_cal.js @@ -17,7 +17,7 @@ const assert = require("assert"); const src = fs.readFileSync(__dirname + "/Cal.js", "utf8").replace(/^\.pragma library$/m, ""); const C = new Function(src + "\nreturn { pad, addDays, today, daysBetween, monthCells, shiftMonth," + - " lastDay, onDay, dayEvents, dots, timeLabel, validTime, normTime, stepTime, newDraft, fromEvent, pick," + + " lastDay, onDay, dayEvents, dots, timeLabel, validTime, normTime, stepTime, newDraft, withStart, fromEvent, pick," + " toPayload };")(); // Dates are plain YYYY-MM-DD strings, arithmetic in UTC so DST never moves a day. @@ -91,6 +91,17 @@ assert.strictEqual(d.alarm, "1h"); assert.strictEqual(d.calendar, "home"); assert.strictEqual(d.source, ""); assert.strictEqual(C.newDraft("2026-10-20", new Date(2026, 9, 13, 23, 10), "home").startTime, "22:00"); +assert.strictEqual(C.newDraft("2026-10-20", new Date(2026, 9, 13, 9, 20), "home", 90).endTime, "11:30"); + +// The end follows the start, by the default length, past midnight too. +const w = C.withStart(d, "23:30", 60); +assert.strictEqual(w.startTime, "23:30"); +assert.strictEqual(w.endTime, "00:30"); +assert.strictEqual(w.endDay, "2026-10-21"); +assert.strictEqual(C.withStart(d, "14:00", 45).endTime, "14:45"); +assert.strictEqual(C.withStart(d, "9.3", 60).endTime, "11:00"); +const multi = Object.assign({}, d, { endDay: "2026-10-22", endTime: "18:00" }); +assert.strictEqual(C.withStart(multi, "08:00", 60).endTime, "18:00"); // From calctl's JSON. const fe = C.fromEvent({ uid: "u", calendar: "home", title: "Trip", allDay: true, start: "2026-10-14", diff --git a/desktop/modules/island/IslandCalendarModule.qml b/desktop/modules/island/IslandCalendarModule.qml new file mode 100644 index 0000000..6525d11 --- /dev/null +++ b/desktop/modules/island/IslandCalendarModule.qml @@ -0,0 +1,76 @@ +// Copyright (C) 2026 Danilo M. <danix@danix.xyz> +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License version 2 as +// published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +import QtQuick +import "../.." +import "../appearance" + +// The island bar's settings group, starting with the calendar's. +Module { + id: mod + name: "island-calendar" + label: "Calendar" + // nf-md-calendar, present in Inconsolata Nerd Font. + icon: "\u{f00ed}" + settings: true + group: "Island bar" + keywords: ["island", "calendar", "event", "length", "duration", "end time"] + searchItems: [{ text: "Default event length", key: "eventMinutes" }] + + // The draft, as typed; dirty while it differs from what is saved. + property string draft: String(Prefs.island.eventMinutes) + readonly property bool valid: /^\d+$/.test(draft) && +draft >= 5 && +draft <= 1440 + dirty: draft !== String(Prefs.island.eventMinutes) + function refresh() { if (!mod.dirty) mod.draft = String(Prefs.island.eventMinutes); } + + page: Component { + Page { + title: "Calendar" + actions: Component { + SaveBar { + dirty: mod.dirty; valid: mod.valid + error: mod.valid ? "" : "Length is 5 to 1440 minutes." + onSave: Prefs.setIsland({ eventMinutes: +mod.draft }) + onDiscard: mod.draft = String(Prefs.island.eventMinutes) + } + } + Column { + objectName: "eventMinutes" + width: parent.width + spacing: 8 + Row { + spacing: 8 + Text { + anchors.verticalCenter: parent.verticalCenter + text: "Default event length" + color: Theme.subtext + font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 3 } + } + // Read on every change: a click on Save ends no editing. + Field { width: 60; value: mod.draft; onValueChanged: mod.draft = value } + Text { + anchors.verticalCenter: parent.verticalCenter + text: "minutes" + color: Theme.subtext + font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 3 } + } + } + Text { + width: parent.width + wrapMode: Text.WordWrap + text: "A new event gets this length, and its end keeps it when the start moves." + color: Theme.overlay + font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 4 } + } + } + } + } +} diff --git a/desktop/shell.qml b/desktop/shell.qml index f5ca7d4..dd440c0 100644 --- a/desktop/shell.qml +++ b/desktop/shell.qml @@ -22,6 +22,7 @@ import "modules/appearance" import "modules/assistant" import "modules/autostart" import "modules/deckit" +import "modules/island" import "modules/qtmaildir" import "modules/sbbuilder" import "modules/shells" @@ -112,6 +113,7 @@ ShellRoot { OptionsModule {}, MonitorsModule {}, ObsScenesModule {}, + IslandCalendarModule {}, // deck-it's four pages, one draft. DeckItModule {}, DeckItDashboardModule {}, |
