Synchronize weather location updates
This commit is contained in:
@@ -179,10 +179,25 @@ function openMeteoCurrentCondition(dailyForecastReport) {
|
|||||||
FeelsLikeF: roundedTemp(celsiusToFahrenheit(current.apparent_temperature)),
|
FeelsLikeF: roundedTemp(celsiusToFahrenheit(current.apparent_temperature)),
|
||||||
windspeedKmph: roundedTemp(current.wind_speed_10m),
|
windspeedKmph: roundedTemp(current.wind_speed_10m),
|
||||||
windspeedMiles: roundedTemp(current.wind_speed_10m * 0.621371),
|
windspeedMiles: roundedTemp(current.wind_speed_10m * 0.621371),
|
||||||
humidity: roundedTemp(current.relative_humidity_2m)
|
humidity: roundedTemp(current.relative_humidity_2m),
|
||||||
|
openMeteoWeatherCode: current.weather_code,
|
||||||
|
isDay: current.is_day
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function currentIcon(current, fallback) {
|
||||||
|
if (!current) return fallback || ""
|
||||||
|
if (current.openMeteoWeatherCode !== undefined && current.openMeteoWeatherCode !== null)
|
||||||
|
return iconForOpenMeteoCode(current.openMeteoWeatherCode, Number(current.isDay) === 0)
|
||||||
|
if (current.weatherCode !== undefined && current.weatherCode !== null)
|
||||||
|
return iconForCode(current.weatherCode, false)
|
||||||
|
return fallback || ""
|
||||||
|
}
|
||||||
|
|
||||||
|
function weatherResponseCompletesSave(hasConfiguredCoordinates, source) {
|
||||||
|
return hasConfiguredCoordinates ? source === "open-meteo" : source === "wttr"
|
||||||
|
}
|
||||||
|
|
||||||
function wttrNextForecastDays(report, todayString) {
|
function wttrNextForecastDays(report, todayString) {
|
||||||
var days = report && report.weather ? report.weather : []
|
var days = report && report.weather ? report.weather : []
|
||||||
var result = []
|
var result = []
|
||||||
@@ -225,17 +240,17 @@ function dayIcon(day) {
|
|||||||
return iconForCode(best.weatherCode, false)
|
return iconForCode(best.weatherCode, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
function iconForOpenMeteoCode(code) {
|
function iconForOpenMeteoCode(code, night) {
|
||||||
var c = parseInt(String(code || "0"), 10)
|
var c = parseInt(String(code || "0"), 10)
|
||||||
if (c === 0) return iconForCode(113, false)
|
if (c === 0) return iconForCode(113, night)
|
||||||
if (c === 1 || c === 2) return iconForCode(116, false)
|
if (c === 1 || c === 2) return iconForCode(116, night)
|
||||||
if (c === 3) return iconForCode(119, false)
|
if (c === 3) return iconForCode(119, night)
|
||||||
if (c === 45 || c === 48) return iconForCode(143, false)
|
if (c === 45 || c === 48) return iconForCode(143, night)
|
||||||
if (c === 51 || c === 53 || c === 55 || c === 56 || c === 57 || c === 61) return iconForCode(266, false)
|
if (c === 51 || c === 53 || c === 55 || c === 56 || c === 57 || c === 61) return iconForCode(266, night)
|
||||||
if (c === 63 || c === 65 || c === 66 || c === 67 || c === 80 || c === 81 || c === 82) return iconForCode(308, false)
|
if (c === 63 || c === 65 || c === 66 || c === 67 || c === 80 || c === 81 || c === 82) return iconForCode(308, night)
|
||||||
if (c === 71 || c === 73 || c === 75 || c === 77 || c === 85 || c === 86) return iconForCode(338, false)
|
if (c === 71 || c === 73 || c === 75 || c === 77 || c === 85 || c === 86) return iconForCode(338, night)
|
||||||
if (c === 95 || c === 96 || c === 99) return iconForCode(389, false)
|
if (c === 95 || c === 96 || c === 99) return iconForCode(389, night)
|
||||||
return iconForCode(119, false)
|
return iconForCode(119, night)
|
||||||
}
|
}
|
||||||
|
|
||||||
function iconForCode(code, night) {
|
function iconForCode(code, night) {
|
||||||
@@ -244,7 +259,7 @@ function iconForCode(code, night) {
|
|||||||
case 113: return night ? "" : ""
|
case 113: return night ? "" : ""
|
||||||
case 116: return night ? "" : ""
|
case 116: return night ? "" : ""
|
||||||
case 119: case 122: return ""
|
case 119: case 122: return ""
|
||||||
case 143: case 248: case 260: return ""
|
case 143: case 248: case 260: return night ? "\ue346" : "\ue313"
|
||||||
case 176: case 263: case 353: return night ? "" : ""
|
case 176: case 263: case 353: return night ? "" : ""
|
||||||
case 179: case 227: case 230: case 323: case 326: case 368: return night ? "" : ""
|
case 179: case 227: case 230: case 323: case 326: case 368: return night ? "" : ""
|
||||||
case 182: case 185: case 281: case 284: case 311: case 314:
|
case 182: case 185: case 281: case 284: case 311: case 314:
|
||||||
@@ -274,6 +289,8 @@ if (typeof module !== "undefined") {
|
|||||||
dayName: dayName,
|
dayName: dayName,
|
||||||
openMeteoForecastDays: openMeteoForecastDays,
|
openMeteoForecastDays: openMeteoForecastDays,
|
||||||
openMeteoCurrentCondition: openMeteoCurrentCondition,
|
openMeteoCurrentCondition: openMeteoCurrentCondition,
|
||||||
|
currentIcon: currentIcon,
|
||||||
|
weatherResponseCompletesSave: weatherResponseCompletesSave,
|
||||||
wttrNextForecastDays: wttrNextForecastDays,
|
wttrNextForecastDays: wttrNextForecastDays,
|
||||||
buildForecastDays: buildForecastDays,
|
buildForecastDays: buildForecastDays,
|
||||||
bareTempForDay: bareTempForDay,
|
bareTempForDay: bareTempForDay,
|
||||||
|
|||||||
@@ -58,13 +58,11 @@ Panel {
|
|||||||
readonly property string configuredLocation: configuredLocationState.name
|
readonly property string configuredLocation: configuredLocationState.name
|
||||||
readonly property string locationQuery: Model.wttrLocationQuery(configuredLocationState.name, configuredLocationState.latitude, configuredLocationState.longitude)
|
readonly property string locationQuery: Model.wttrLocationQuery(configuredLocationState.name, configuredLocationState.latitude, configuredLocationState.longitude)
|
||||||
|
|
||||||
// A location change makes the previous report misleading (the old city's
|
// Keep the previous report visible while the new location loads. The
|
||||||
// numbers under the new label), so drop it, abort any in-flight fetch for
|
// editor remains open with a spinner, so stale data is never presented
|
||||||
// the old location, and refetch from scratch.
|
// under the newly configured location label.
|
||||||
onLocationQueryChanged: {
|
onLocationQueryChanged: {
|
||||||
report = null
|
if (savingLocation) savingLocationQueryStarted = true
|
||||||
dailyForecastReport = null
|
|
||||||
wttrLocation = ""
|
|
||||||
forecastRetries = 0
|
forecastRetries = 0
|
||||||
forecastProc.running = false
|
forecastProc.running = false
|
||||||
dailyForecastProc.running = false
|
dailyForecastProc.running = false
|
||||||
@@ -94,24 +92,23 @@ Panel {
|
|||||||
|
|
||||||
// Click-to-edit state for the location label.
|
// Click-to-edit state for the location label.
|
||||||
property bool editingLocation: false
|
property bool editingLocation: false
|
||||||
|
property bool savingLocation: false
|
||||||
|
property bool savingLocationQueryStarted: false
|
||||||
property var locationSuggestions: []
|
property var locationSuggestions: []
|
||||||
property int suggestionIndex: 0
|
property int suggestionIndex: 0
|
||||||
property string geocodePendingQuery: ""
|
property string geocodePendingQuery: ""
|
||||||
property string geocodeActiveQuery: ""
|
property string geocodeActiveQuery: ""
|
||||||
|
|
||||||
// Bar pill state. Polled locally; populated by weatherProc below.
|
// Shared hero/bar icon state, updated with each successful weather response.
|
||||||
property string label: ""
|
property string label: ""
|
||||||
property string klass: ""
|
property string klass: ""
|
||||||
|
|
||||||
function updateWeather(raw) {
|
|
||||||
var data = Model.parseWeatherStatus(raw)
|
|
||||||
label = data.label
|
|
||||||
klass = data.klass
|
|
||||||
}
|
|
||||||
|
|
||||||
// wttr's current conditions when available; open-meteo's (bundled with the
|
// wttr's current conditions when available; open-meteo's (bundled with the
|
||||||
// much faster daily forecast fetch) fill the hero while wttr is in flight.
|
// much faster daily forecast fetch) fill the hero while wttr is in flight.
|
||||||
readonly property var current: (report && report.current_condition && report.current_condition[0]) ? report.current_condition[0] : Model.openMeteoCurrentCondition(dailyForecastReport)
|
readonly property bool hasConfiguredCoordinates: !isNaN(parseFloat(String(configuredLocationState.latitude))) && !isNaN(parseFloat(String(configuredLocationState.longitude)))
|
||||||
|
readonly property var openMeteoCurrent: Model.openMeteoCurrentCondition(dailyForecastReport)
|
||||||
|
readonly property var current: (hasConfiguredCoordinates && openMeteoCurrent) ? openMeteoCurrent : ((report && report.current_condition && report.current_condition[0]) ? report.current_condition[0] : openMeteoCurrent)
|
||||||
|
readonly property string currentIcon: Model.currentIcon(current, label)
|
||||||
readonly property var areaInfo: report && report.nearest_area && report.nearest_area[0] ? report.nearest_area[0] : null
|
readonly property var areaInfo: report && report.nearest_area && report.nearest_area[0] ? report.nearest_area[0] : null
|
||||||
readonly property var forecastDays: buildForecastDays()
|
readonly property var forecastDays: buildForecastDays()
|
||||||
readonly property string reportCountry: areaInfo && areaInfo.country && areaInfo.country[0] ? areaInfo.country[0].value : ""
|
readonly property string reportCountry: areaInfo && areaInfo.country && areaInfo.country[0] ? areaInfo.country[0].value : ""
|
||||||
@@ -154,7 +151,7 @@ Panel {
|
|||||||
+ "?latitude=" + encodeURIComponent(String(lat))
|
+ "?latitude=" + encodeURIComponent(String(lat))
|
||||||
+ "&longitude=" + encodeURIComponent(String(lon))
|
+ "&longitude=" + encodeURIComponent(String(lon))
|
||||||
+ "&daily=weather_code,temperature_2m_max,temperature_2m_min"
|
+ "&daily=weather_code,temperature_2m_max,temperature_2m_min"
|
||||||
+ "¤t=temperature_2m,apparent_temperature,relative_humidity_2m,wind_speed_10m"
|
+ "¤t=temperature_2m,apparent_temperature,relative_humidity_2m,wind_speed_10m,weather_code,is_day"
|
||||||
+ "&forecast_days=4"
|
+ "&forecast_days=4"
|
||||||
+ "&timezone=auto"
|
+ "&timezone=auto"
|
||||||
dailyForecastProc.command = ["curl", "-fsS", "--max-time", "5", url]
|
dailyForecastProc.command = ["curl", "-fsS", "--max-time", "5", url]
|
||||||
@@ -166,6 +163,8 @@ Panel {
|
|||||||
// the module's shell.json entry. An empty commit returns to auto.
|
// the module's shell.json entry. An empty commit returns to auto.
|
||||||
function startEditingLocation() {
|
function startEditingLocation() {
|
||||||
editingLocation = true
|
editingLocation = true
|
||||||
|
savingLocation = false
|
||||||
|
savingLocationQueryStarted = false
|
||||||
locationSuggestions = []
|
locationSuggestions = []
|
||||||
suggestionIndex = 0
|
suggestionIndex = 0
|
||||||
Qt.callLater(function() {
|
Qt.callLater(function() {
|
||||||
@@ -177,6 +176,8 @@ Panel {
|
|||||||
|
|
||||||
function cancelEditingLocation() {
|
function cancelEditingLocation() {
|
||||||
editingLocation = false
|
editingLocation = false
|
||||||
|
savingLocation = false
|
||||||
|
savingLocationQueryStarted = false
|
||||||
locationSuggestions = []
|
locationSuggestions = []
|
||||||
geocodeDebounce.stop()
|
geocodeDebounce.stop()
|
||||||
Qt.callLater(function() { if (keyCatcher) keyCatcher.forceActiveFocus() })
|
Qt.callLater(function() { if (keyCatcher) keyCatcher.forceActiveFocus() })
|
||||||
@@ -188,8 +189,14 @@ Panel {
|
|||||||
clearLocation()
|
clearLocation()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
savingLocation = true
|
||||||
|
savingLocationQueryStarted = false
|
||||||
|
configuredLocationState = {
|
||||||
|
name: location.name,
|
||||||
|
latitude: location.latitude,
|
||||||
|
longitude: location.longitude
|
||||||
|
}
|
||||||
persistLocation(location.name, location.latitude, location.longitude)
|
persistLocation(location.name, location.latitude, location.longitude)
|
||||||
cancelEditingLocation()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function clearLocation() {
|
function clearLocation() {
|
||||||
@@ -200,17 +207,28 @@ Panel {
|
|||||||
|
|
||||||
function pickSuggestion(suggestion) {
|
function pickSuggestion(suggestion) {
|
||||||
if (!suggestion) return
|
if (!suggestion) return
|
||||||
|
savingLocation = true
|
||||||
|
savingLocationQueryStarted = false
|
||||||
|
configuredLocationState = {
|
||||||
|
name: suggestion.name,
|
||||||
|
latitude: suggestion.latitude,
|
||||||
|
longitude: suggestion.longitude
|
||||||
|
}
|
||||||
persistLocation(suggestion.name, suggestion.latitude, suggestion.longitude)
|
persistLocation(suggestion.name, suggestion.latitude, suggestion.longitude)
|
||||||
cancelEditingLocation()
|
}
|
||||||
|
|
||||||
|
function finishSavingLocation() {
|
||||||
|
if (savingLocation && savingLocationQueryStarted) cancelEditingLocation()
|
||||||
}
|
}
|
||||||
|
|
||||||
function persistLocation(name, latitude, longitude) {
|
function persistLocation(name, latitude, longitude) {
|
||||||
if (name && latitude !== null && longitude !== null)
|
if (name && latitude !== null && longitude !== null)
|
||||||
Quickshell.execDetached(["omarchy-weather-location", "--set", name, latitude + "," + longitude])
|
locationSaveProc.command = ["omarchy-weather-location", "--set", name, latitude + "," + longitude]
|
||||||
else if (name)
|
else if (name)
|
||||||
Quickshell.execDetached(["omarchy-weather-location", "--set", name])
|
locationSaveProc.command = ["omarchy-weather-location", "--set", name]
|
||||||
else
|
else
|
||||||
Quickshell.execDetached(["omarchy-weather-location", "--clear"])
|
locationSaveProc.command = ["omarchy-weather-location", "--clear"]
|
||||||
|
locationSaveProc.running = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Debounced geocoding. Only one curl runs at a time; if the query moved on
|
// Debounced geocoding. Only one curl runs at a time; if the query moved on
|
||||||
@@ -297,7 +315,11 @@ Panel {
|
|||||||
try {
|
try {
|
||||||
var parsed = JSON.parse(raw)
|
var parsed = JSON.parse(raw)
|
||||||
root.report = parsed
|
root.report = parsed
|
||||||
|
if (!root.hasConfiguredCoordinates)
|
||||||
|
root.label = Model.currentIcon(parsed.current_condition && parsed.current_condition[0], root.label)
|
||||||
root.forecastRetries = 0
|
root.forecastRetries = 0
|
||||||
|
if (Model.weatherResponseCompletesSave(root.hasConfiguredCoordinates, "wttr"))
|
||||||
|
root.finishSavingLocation()
|
||||||
// Stored coordinates already drove the fast open-meteo fetch from
|
// Stored coordinates already drove the fast open-meteo fetch from
|
||||||
// refresh(); only auto-detect needs the area wttr reported.
|
// refresh(); only auto-detect needs the area wttr reported.
|
||||||
if (isNaN(parseFloat(String(root.configuredLocationState.latitude))))
|
if (isNaN(parseFloat(String(root.configuredLocationState.latitude))))
|
||||||
@@ -332,7 +354,12 @@ Panel {
|
|||||||
var raw = String(text || "").trim()
|
var raw = String(text || "").trim()
|
||||||
if (!raw) return
|
if (!raw) return
|
||||||
try {
|
try {
|
||||||
root.dailyForecastReport = JSON.parse(raw)
|
var parsed = JSON.parse(raw)
|
||||||
|
var parsedCurrent = Model.openMeteoCurrentCondition(parsed)
|
||||||
|
root.dailyForecastReport = parsed
|
||||||
|
root.label = Model.currentIcon(parsedCurrent, root.label)
|
||||||
|
if (Model.weatherResponseCompletesSave(root.hasConfiguredCoordinates, "open-meteo"))
|
||||||
|
root.finishSavingLocation()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Keep last-good daily forecast on parse failure.
|
// Keep last-good daily forecast on parse failure.
|
||||||
}
|
}
|
||||||
@@ -358,6 +385,24 @@ Panel {
|
|||||||
onTriggered: root.requestGeocode()
|
onTriggered: root.requestGeocode()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Process {
|
||||||
|
id: locationSaveProc
|
||||||
|
onExited: function(exitCode) {
|
||||||
|
if (exitCode !== 0 || !root.savingLocation) return
|
||||||
|
|
||||||
|
// FileView handles changed locations. Explicitly refresh here too so
|
||||||
|
// saving the already-active location cannot strand the spinner.
|
||||||
|
locationFile.reload()
|
||||||
|
if (!root.savingLocationQueryStarted) {
|
||||||
|
root.savingLocationQueryStarted = true
|
||||||
|
root.forecastRetries = 0
|
||||||
|
forecastProc.running = false
|
||||||
|
dailyForecastProc.running = false
|
||||||
|
Qt.callLater(root.refresh)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Process {
|
Process {
|
||||||
id: locationProc
|
id: locationProc
|
||||||
command: ["curl", "-fsS", "--max-time", "4", "https://wttr.in/?format=%l"]
|
command: ["curl", "-fsS", "--max-time", "4", "https://wttr.in/?format=%l"]
|
||||||
@@ -439,7 +484,7 @@ Panel {
|
|||||||
id: heroIcon
|
id: heroIcon
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
anchors.verticalCenterOffset: 5
|
anchors.verticalCenterOffset: 5
|
||||||
text: root.label || "—"
|
text: root.currentIcon || "—"
|
||||||
color: root.bar.foreground
|
color: root.bar.foreground
|
||||||
font.family: root.bar.fontFamily
|
font.family: root.bar.fontFamily
|
||||||
// Decorative condition emoji; intentionally larger than the
|
// Decorative condition emoji; intentionally larger than the
|
||||||
@@ -474,6 +519,7 @@ Panel {
|
|||||||
|
|
||||||
Column {
|
Column {
|
||||||
id: heroRight
|
id: heroRight
|
||||||
|
width: weatherStats.implicitWidth
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.rightMargin: Style.space(20)
|
anchors.rightMargin: Style.space(20)
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
@@ -514,11 +560,12 @@ Panel {
|
|||||||
TextField {
|
TextField {
|
||||||
id: locationField
|
id: locationField
|
||||||
width: Style.space(190)
|
width: Style.space(190)
|
||||||
|
enabled: !root.savingLocation
|
||||||
placeholderText: "Search city"
|
placeholderText: "Search city"
|
||||||
foreground: root.bar.foreground
|
foreground: root.bar.foreground
|
||||||
font.family: root.bar.fontFamily
|
font.family: root.bar.fontFamily
|
||||||
|
|
||||||
onTextChanged: if (root.editingLocation) geocodeDebounce.restart()
|
onTextChanged: if (root.editingLocation && !root.savingLocation) geocodeDebounce.restart()
|
||||||
|
|
||||||
Keys.onPressed: function(event) {
|
Keys.onPressed: function(event) {
|
||||||
if (event.key === Qt.Key_Escape) {
|
if (event.key === Qt.Key_Escape) {
|
||||||
@@ -537,34 +584,43 @@ Panel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear back to IP auto-detect. Committing an empty field does
|
// Clear back to IP auto-detect. While a committed location is
|
||||||
// the same for keyboard users.
|
// loading, this same compact affordance becomes a spinner.
|
||||||
Rectangle {
|
Rectangle {
|
||||||
width: Style.space(18)
|
width: Style.space(18)
|
||||||
height: Style.space(18)
|
height: Style.space(18)
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
radius: Math.min(4, Style.cornerRadius)
|
radius: Math.min(4, Style.cornerRadius)
|
||||||
color: clearLocationArea.containsMouse ? Style.hoverFillFor(root.bar.foreground, Color.accent) : "transparent"
|
color: !root.savingLocation && clearLocationArea.containsMouse ? Style.hoverFillFor(root.bar.foreground, Color.accent) : "transparent"
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
text: "✕"
|
text: root.savingLocation ? "" : "✕"
|
||||||
font.family: root.bar.fontFamily
|
font.family: root.bar.fontFamily
|
||||||
color: Qt.darker(root.bar.foreground, 1.4)
|
color: Qt.darker(root.bar.foreground, 1.4)
|
||||||
font.pixelSize: Style.font.bodySmall
|
font.pixelSize: Style.font.bodySmall
|
||||||
|
|
||||||
|
RotationAnimator on rotation {
|
||||||
|
running: root.savingLocation
|
||||||
|
from: 0; to: 360
|
||||||
|
duration: 800
|
||||||
|
loops: Animation.Infinite
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
id: clearLocationArea
|
id: clearLocationArea
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
enabled: !root.savingLocation
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||||
onClicked: root.clearLocation()
|
onClicked: root.clearLocation()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Row {
|
Row {
|
||||||
|
id: weatherStats
|
||||||
visible: !!root.current
|
visible: !!root.current
|
||||||
spacing: Style.space(36)
|
spacing: Style.space(36)
|
||||||
|
|
||||||
@@ -624,7 +680,7 @@ Panel {
|
|||||||
|
|
||||||
// ---- Geocoding suggestions while the location is being edited.
|
// ---- Geocoding suggestions while the location is being edited.
|
||||||
Column {
|
Column {
|
||||||
visible: root.editingLocation && root.locationSuggestions.length > 0
|
visible: root.editingLocation && !root.savingLocation && root.locationSuggestions.length > 0
|
||||||
width: parent.width
|
width: parent.width
|
||||||
spacing: 0
|
spacing: 0
|
||||||
|
|
||||||
@@ -757,21 +813,4 @@ Panel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Poll the weather pill text/class every minute. Local to this widget.
|
|
||||||
Process {
|
|
||||||
id: weatherProc
|
|
||||||
command: ["bash", "-lc", Util.shellQuote(root.omarchyPath + "/shell/plugins/panels/weather/status.sh")]
|
|
||||||
stdout: StdioCollector {
|
|
||||||
waitForEnd: true
|
|
||||||
onStreamFinished: root.updateWeather(text)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Timer {
|
|
||||||
interval: 60000
|
|
||||||
running: true
|
|
||||||
repeat: true
|
|
||||||
triggeredOnStart: true
|
|
||||||
onTriggered: if (!weatherProc.running) weatherProc.running = true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -108,6 +108,12 @@ assertEqual(weather.bareTempForDay({ maxtempC: '22', mintempC: '13', maxtempF: '
|
|||||||
assertEqual(weather.bareTempForDay({ maxtempC: '22', mintempC: '13', maxtempF: '72', mintempF: '55' }, 'min', true), '55°', 'weather formats forecast imperial lows')
|
assertEqual(weather.bareTempForDay({ maxtempC: '22', mintempC: '13', maxtempF: '72', mintempF: '55' }, 'min', true), '55°', 'weather formats forecast imperial lows')
|
||||||
|
|
||||||
assert(weather.dayIcon({ openMeteoWeatherCode: 95 }).length > 0, 'weather maps Open-Meteo weather icons')
|
assert(weather.dayIcon({ openMeteoWeatherCode: 95 }).length > 0, 'weather maps Open-Meteo weather icons')
|
||||||
|
assertEqual(weather.currentIcon({ openMeteoWeatherCode: 0, isDay: 1 }, ''), weather.iconForOpenMeteoCode(0), 'weather uses the current Open-Meteo icon with current values')
|
||||||
|
assertEqual(weather.currentIcon({ openMeteoWeatherCode: 0, isDay: 0 }, ''), weather.iconForCode(113, true), 'weather uses the nighttime Open-Meteo icon after sunset')
|
||||||
|
assert(weather.iconForOpenMeteoCode(45, true) !== weather.iconForOpenMeteoCode(45, false), 'weather distinguishes nighttime fog from daytime fog')
|
||||||
|
assert(!weather.weatherResponseCompletesSave(true, 'wttr'), 'weather keeps the spinner through a non-authoritative pinned-location response')
|
||||||
|
assert(weather.weatherResponseCompletesSave(true, 'open-meteo'), 'weather completes a pinned-location save with Open-Meteo data')
|
||||||
|
assert(weather.weatherResponseCompletesSave(false, 'wttr'), 'weather completes a name-only location save with wttr data')
|
||||||
assertEqual(
|
assertEqual(
|
||||||
weather.dayIcon({ hourly: [{ time: '900', weatherCode: 113 }, { time: '1200', weatherCode: 389 }, { time: '1800', weatherCode: 116 }] }),
|
weather.dayIcon({ hourly: [{ time: '900', weatherCode: 113 }, { time: '1200', weatherCode: 389 }, { time: '1800', weatherCode: 116 }] }),
|
||||||
weather.iconForCode(389, false),
|
weather.iconForCode(389, false),
|
||||||
|
|||||||
Reference in New Issue
Block a user