diff options
Diffstat (limited to 'desktop/modules/user/UserPage.qml')
| -rw-r--r-- | desktop/modules/user/UserPage.qml | 74 |
1 files changed, 71 insertions, 3 deletions
diff --git a/desktop/modules/user/UserPage.qml b/desktop/modules/user/UserPage.qml index 5e2e448..9fb81a6 100644 --- a/desktop/modules/user/UserPage.qml +++ b/desktop/modules/user/UserPage.qml @@ -33,7 +33,7 @@ Column { readonly property var fields: [ { key: "name", label: "Name", kind: "text" }, { key: "email", label: "Email", kind: "text" }, - { key: "location", label: "Location", kind: "text" }, + { key: "location", label: "Location", kind: "place" }, { key: "language", label: "Language", kind: "locale" }, { key: "gitname", label: "Name", kind: "derived", section: "Git" }, { key: "gitemail", label: "Email", kind: "derived", section: "Git" }, @@ -137,6 +137,35 @@ Column { }); } + // Location suggestions from OpenStreetMap's Nominatim, the user's choice + // over an offline city list. Its policy allows one request a second and + // asks for an identifying User-Agent: the timer fires 1s after typing + // stops, and a reply to an older query is dropped by its sequence number. + property var places: [] + property string placeQuery: "" + property int placeSeq: 0 + + function lookup(text) { + form.places = []; + form.placeQuery = text.trim(); + placeTimer.restart(); + } + + Timer { + id: placeTimer + interval: 1000 + onTriggered: { + if (form.placeQuery.length < 3) return; + const seq = ++form.placeSeq; + const lang = (Quickshell.env("LANG") || "en").slice(0, 2); + form.run(["curl", "-sf", "--max-time", "5", + "-A", "quickshell-drawer/1.0 (user details page)", + "https://nominatim.openstreetmap.org/search?format=jsonv2&addressdetails=1&limit=6" + + "&accept-language=" + lang + "&q=" + encodeURIComponent(form.placeQuery)], + out => { if (seq === form.placeSeq) form.places = U.places(out); }); + } + } + Component.onCompleted: { form.load({}); form.run([form.ctl, "keys"], out => form.keys = U.keys(out)); @@ -250,7 +279,7 @@ Column { // Read-only fields are dimmer, but still selectable for copying. Rectangle { - visible: field.modelData.kind === "text" || field.derived + visible: field.modelData.kind === "text" || field.modelData.kind === "place" || field.derived width: parent.width height: 34 radius: 8 @@ -265,7 +294,46 @@ Column { font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 2 } text: field.derived ? form.derived(field.modelData.key) : form.draft[field.modelData.key] ?? "" - onTextEdited: form.set(field.modelData.key, text) + onTextEdited: { + form.set(field.modelData.key, text); + if (field.modelData.kind === "place") form.lookup(text); + } + onActiveFocusChanged: if (!activeFocus && field.modelData.kind === "place") form.places = [] + } + } + + // Suggestions under a place field; a click takes one. + Column { + visible: field.modelData.kind === "place" && form.places.length > 0 + width: parent.width + Repeater { + model: field.modelData.kind === "place" ? form.places : [] + delegate: Rectangle { + required property string modelData + width: parent.width + height: 30 + radius: 6 + color: placeArea.containsMouse ? Qt.alpha(Theme.accent, 0.22) : Qt.alpha(Theme.surface, 0.6) + Text { + anchors { fill: parent; leftMargin: 10; rightMargin: 10 } + verticalAlignment: Text.AlignVCenter + elide: Text.ElideRight + text: parent.modelData + color: Theme.text + font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 3 } + } + MouseArea { + id: placeArea + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: { + form.set(field.modelData.key, parent.modelData); + form.placeSeq++; + form.places = []; + } + } + } } } |
