* Move Elsewhen into Omarchy as omarchy.elsewhen The world clock ships in the shell tree instead of its own package. A migration renames existing bar entries with their settings and removes the retired package. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Remove Elsewhen's shelved Earth row, overlap band and sky tint Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Tidy Elsewhen's models, data helper and docs, and drop currency Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Restructure Elsewhen's panel and globe on the shared shell components Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Show Elsewhen's bar globe upright Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Use the shared city search for Elsewhen's globe jump bar Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Keep legacy Elsewhen entries single and retry offline geocodes Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Fetch Elsewhen's weather with curl instead of a Python helper Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
99 lines
3.1 KiB
QML
99 lines
3.1 KiB
QML
import QtQuick
|
|
import qs.Commons
|
|
import qs.Ui
|
|
import "Model.js" as Model
|
|
|
|
// A button that opens into a city search: a field, then the matching cities
|
|
// with their zone and offset. Arrows move the selection, Return picks it,
|
|
// Escape backs out. Inline rather than a dropdown so the results grow the
|
|
// panel instead of running off the bottom of the screen.
|
|
Column {
|
|
id: search
|
|
|
|
// [{ label, value }] from Model.zoneOptions.
|
|
property var options: []
|
|
property int limit: 6
|
|
// Zone id -> offset text; read inside a binding, so its dependencies track.
|
|
property var offsetLabel: function(zoneId) { return "" }
|
|
property bool loading: false
|
|
property string buttonText: "+ Add a city"
|
|
property string placeholderText: "Search cities\u2026"
|
|
property string loadingText: "Loading zones\u2026"
|
|
property real fontSize: Style.font.bodySmall
|
|
property color foreground: Color.foreground
|
|
property color dim: Qt.darker(foreground, 1.55)
|
|
property color fainter: Qt.darker(foreground, 2.1)
|
|
property string fontFamily: Style.font.family
|
|
|
|
// False when the host shows CityMatches elsewhere.
|
|
property bool inlineResults: true
|
|
|
|
property bool active: false
|
|
readonly property string query: field.text
|
|
readonly property var matches: active ? Model.searchZones(options, query, limit) : []
|
|
// An index, not the match itself: the list is rebuilt on every keystroke.
|
|
property int selectedIndex: 0
|
|
|
|
signal picked(string label, string id)
|
|
signal dismissed()
|
|
|
|
onQueryChanged: selectedIndex = 0
|
|
onMatchesChanged: if (selectedIndex >= matches.length) selectedIndex = 0
|
|
|
|
function start() {
|
|
selectedIndex = 0
|
|
active = true
|
|
Qt.callLater(function() { field.text = ""; field.forceActiveFocus() })
|
|
}
|
|
|
|
// The hidden field would keep the keyboard, so the host takes it back on dismissed.
|
|
function stop() {
|
|
active = false
|
|
field.text = ""
|
|
dismissed()
|
|
}
|
|
|
|
function pick(match) {
|
|
picked(match.label, match.value)
|
|
stop()
|
|
}
|
|
|
|
function pickSelected() {
|
|
if (matches.length > 0) pick(matches[Util.clamp(selectedIndex, 0, matches.length - 1)])
|
|
}
|
|
|
|
spacing: Style.spacing.md
|
|
|
|
Button {
|
|
width: parent.width
|
|
// Matches the field, so opening the search does not shift the layout.
|
|
height: field.implicitHeight
|
|
visible: !search.active
|
|
text: search.buttonText
|
|
fontSize: search.fontSize
|
|
foreground: search.foreground
|
|
fontFamily: search.fontFamily
|
|
bordered: true
|
|
onClicked: search.start()
|
|
}
|
|
|
|
TextField {
|
|
id: field
|
|
visible: search.active
|
|
width: parent.width
|
|
placeholderText: search.placeholderText
|
|
foreground: search.foreground
|
|
Keys.onEscapePressed: search.stop()
|
|
Keys.onReturnPressed: search.pickSelected()
|
|
Keys.onEnterPressed: search.pickSelected()
|
|
Keys.onUpPressed: search.selectedIndex = Model.moveSelection(search.selectedIndex, -1, search.matches.length)
|
|
Keys.onDownPressed: search.selectedIndex = Model.moveSelection(search.selectedIndex, 1, search.matches.length)
|
|
}
|
|
|
|
CityMatches {
|
|
width: parent.width
|
|
visible: search.active && search.inlineResults
|
|
citySearch: search
|
|
}
|
|
}
|