diff options
| author | Danilo M. <danix@danix.xyz> | 2026-10-16 10:03:17 +0200 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-10-16 10:03:17 +0200 |
| commit | 147247a5aa30cae53c1152c4cc15226338def2f6 (patch) | |
| tree | 5919f0c829b9ccc6a91300177d432397292b7cdb | |
| parent | 66989fc6cdd5358131d0707bccb314e0d3050d1b (diff) | |
| download | quickshell-147247a5aa30cae53c1152c4cc15226338def2f6.tar.gz quickshell-147247a5aa30cae53c1152c4cc15226338def2f6.zip | |
fix(user): the page retries partial saves and survives leaving mid-save
A key whose write half-failed (AccountsService took it, git did not)
reloads as unchanged and Save went dead. A key with an error now counts
as changed, and editing a field clears its error.
Processes were parented to the page, so leaving it mid-save killed
userctl between stores. They hang off the module now, passed in as
owner (not the module's id, which would bind to itself).
busy now clears when the reload lands, so Save cannot be clicked again
before saved is fresh, and the fields and avatar picker are disabled
while busy.
Avatar paths with # or % broke the preview under a bare file:// prefix;
each segment is now URI-encoded for the Image source (page and row),
while draft.avatar stays a plain path for userctl. Dropped a redundant
saved clear in load.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
| -rw-r--r-- | desktop/modules/user/UserModule.qml | 3 | ||||
| -rw-r--r-- | desktop/modules/user/UserPage.qml | 17 | ||||
| -rw-r--r-- | desktop/modules/user/UserRow.qml | 2 |
3 files changed, 15 insertions, 7 deletions
diff --git a/desktop/modules/user/UserModule.qml b/desktop/modules/user/UserModule.qml index ef49829..9e5210c 100644 --- a/desktop/modules/user/UserModule.qml +++ b/desktop/modules/user/UserModule.qml @@ -14,6 +14,7 @@ import "../.." // The user's details. No tile: the drawer's greeting row opens the page. Module { + id: um name: "user" label: "User" // nf-fa-user, present in Inconsolata Nerd Font. @@ -23,7 +24,7 @@ Module { page: Component { Page { title: "User" - UserPage { width: parent.width } + UserPage { width: parent.width; owner: um } } } } diff --git a/desktop/modules/user/UserPage.qml b/desktop/modules/user/UserPage.qml index 024a63b..f559538 100644 --- a/desktop/modules/user/UserPage.qml +++ b/desktop/modules/user/UserPage.qml @@ -43,13 +43,19 @@ Column { property var keys: [] property var locales: [] property bool busy: false + // The module, which outlives the page: a save in flight finishes even if + // the page is closed. Not named `um`, the module's id (it would bind to itself). + required property QtObject owner property string loadError: "" - readonly property var changed: Object.keys(draft).filter(k => draft[k] !== saved[k]) + readonly property var changed: Object.keys(draft).filter(k => draft[k] !== saved[k] || !!errors[k]) function set(key, value) { const d = Object.assign({}, form.draft); d[key] = value; form.draft = d; + const e = Object.assign({}, form.errors); + delete e[key]; + form.errors = e; } // ComboBox entries per kind. The current language is kept even if @@ -67,7 +73,7 @@ Column { // One Process per call, so reads and writes never share one (a running // Process ignores a second start, AGENTS.md). cb gets stdout. function run(cmd, cb) { - op.createObject(form, { command: cmd, cb: cb }).running = true; + op.createObject(form.owner, { command: cmd, cb: cb }).running = true; } Component { id: op @@ -87,12 +93,12 @@ Column { function load(keep) { form.run([form.ctl, "get"], out => { const s = U.parse(out); + form.busy = false; if (s.error) { form.loadError = s.error; return; } form.loadError = ""; - form.saved = ({}); form.saved = s; form.draft = Object.assign({}, s, keep); }); @@ -106,7 +112,6 @@ Column { } function step(queue, failed) { if (queue.length === 0) { - form.busy = false; form.load(failed); return; } @@ -147,7 +152,7 @@ Column { Image { id: pic anchors.fill: parent - source: form.draft.avatar ? "file://" + form.draft.avatar : "" + source: form.draft.avatar ? "file://" + form.draft.avatar.split("/").map(encodeURIComponent).join("/") : "" fillMode: Image.PreserveAspectCrop sourceSize: Qt.size(192, 192) cache: false @@ -161,6 +166,7 @@ Column { } MouseArea { anchors.fill: parent + enabled: !form.busy cursorShape: Qt.PointingHandCursor onClicked: picker.open() } @@ -188,6 +194,7 @@ Column { delegate: Column { id: field + enabled: !form.busy required property var modelData width: form.width spacing: 4 diff --git a/desktop/modules/user/UserRow.qml b/desktop/modules/user/UserRow.qml index 106a490..bd1fe0b 100644 --- a/desktop/modules/user/UserRow.qml +++ b/desktop/modules/user/UserRow.qml @@ -59,7 +59,7 @@ Rectangle { Image { id: pic anchors.fill: parent - source: row.info.avatar ? "file://" + row.info.avatar : "" + source: row.info.avatar ? "file://" + row.info.avatar.split("/").map(encodeURIComponent).join("/") : "" fillMode: Image.PreserveAspectCrop sourceSize: Qt.size(88, 88) cache: false |
