dateMenu: Use graphical weather forecasts
While the current textual forecast is non-intrusive, it may be too much so, making it less effective to spot the current conditions at a glance. Refresh the section to use a more conventional graphical representation, similar to the one used by gnome-weather itself. https://gitlab.gnome.org/GNOME/gnome-shell/issues/262
This commit is contained in:
parent
4590094605
commit
5dedb97fcc
@ -922,7 +922,8 @@ StScrollBar {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.world-clocks-grid {
|
||||
.world-clocks-grid,
|
||||
.weather-grid {
|
||||
spacing-rows: 0.4em;
|
||||
}
|
||||
|
||||
@ -930,6 +931,19 @@ StScrollBar {
|
||||
spacing: 0.4em;
|
||||
}
|
||||
|
||||
.weather-grid {
|
||||
spacing-columns: 0.8em;
|
||||
}
|
||||
|
||||
.weather-forecast-icon {
|
||||
icon-size: 2.18em;
|
||||
}
|
||||
|
||||
.weather-forecast-time {
|
||||
color: darken($fg_color,40%);
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
.calendar-month-label {
|
||||
color: darken($fg_color,5%);
|
||||
font-weight: bold;
|
||||
|
@ -22,6 +22,8 @@ const Calendar = imports.ui.calendar;
|
||||
const Weather = imports.misc.weather;
|
||||
const System = imports.system;
|
||||
|
||||
const MAX_FORECASTS = 5;
|
||||
|
||||
function _isToday(date) {
|
||||
let now = new Date();
|
||||
return now.getYear() == date.getYear() &&
|
||||
@ -220,106 +222,101 @@ var WeatherSection = class WeatherSection {
|
||||
x_align: Clutter.ActorAlign.START,
|
||||
text: _("Weather") }));
|
||||
|
||||
this._conditionsLabel = new St.Label({ style_class: 'weather-conditions',
|
||||
x_align: Clutter.ActorAlign.START });
|
||||
this._conditionsLabel.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;
|
||||
this._conditionsLabel.clutter_text.line_wrap = true;
|
||||
box.add_child(this._conditionsLabel);
|
||||
let layout = new Clutter.GridLayout({ orientation: Clutter.Orientation.VERTICAL });
|
||||
this._forecastGrid = new St.Widget({ style_class: 'weather-grid',
|
||||
layout_manager: layout });
|
||||
layout.hookup_style(this._forecastGrid);
|
||||
box.add_child(this._forecastGrid);
|
||||
|
||||
this._weatherClient.connect('changed', this._sync.bind(this));
|
||||
this._sync();
|
||||
}
|
||||
|
||||
_getSummary(info, capitalize=false) {
|
||||
let options = capitalize ? GWeather.FormatOptions.SENTENCE_CAPITALIZATION
|
||||
: GWeather.FormatOptions.NO_CAPITALIZATION;
|
||||
|
||||
let [ok, phenomenon, qualifier] = info.get_value_conditions();
|
||||
if (ok)
|
||||
return new GWeather.Conditions({ significant: true,
|
||||
phenomenon,
|
||||
qualifier }).to_string_full(options);
|
||||
|
||||
let [, sky] = info.get_value_sky();
|
||||
return GWeather.Sky.to_string_full(sky, options);
|
||||
}
|
||||
|
||||
_sameSummary(info1, info2) {
|
||||
let [ok1, phenom1, qualifier1] = info1.get_value_conditions();
|
||||
let [ok2, phenom2, qualifier2] = info2.get_value_conditions();
|
||||
if (ok1 || ok2)
|
||||
return ok1 == ok2 && phenom1 == phenom2 && qualifier1 == qualifier2;
|
||||
|
||||
let [, sky1] = info1.get_value_sky();
|
||||
let [, sky2] = info2.get_value_sky();
|
||||
return sky1 == sky2;
|
||||
}
|
||||
|
||||
_getSummaryText() {
|
||||
_getInfos() {
|
||||
let info = this._weatherClient.info;
|
||||
let forecasts = info.get_forecast_list();
|
||||
if (forecasts.length == 0) // No forecasts, just current conditions
|
||||
return '%s.'.format(this._getSummary(info, true));
|
||||
|
||||
let current = info;
|
||||
let infos = [info];
|
||||
for (let i = 0; i < forecasts.length; i++) {
|
||||
let [ok, timestamp] = forecasts[i].get_value_update();
|
||||
if (!_isToday(new Date(timestamp * 1000)))
|
||||
let datetime = new Date(timestamp * 1000);
|
||||
if (!_isToday(datetime))
|
||||
continue; // Ignore forecasts from other days
|
||||
|
||||
if (this._sameSummary(current, forecasts[i]))
|
||||
continue; // Ignore consecutive runs of equal summaries
|
||||
[ok, timestamp] = current.get_value_update();
|
||||
let currenttime = new Date(timestamp * 1000);
|
||||
if (currenttime.getHours() == datetime.getHours())
|
||||
continue; // Enforce a minimum interval of 1h
|
||||
|
||||
current = forecasts[i];
|
||||
if (infos.push(current) == 3)
|
||||
break; // Use a maximum of three summaries
|
||||
if (infos.push(current) == MAX_FORECASTS)
|
||||
break; // Use a maximum of five forecasts
|
||||
}
|
||||
|
||||
let fmt;
|
||||
switch(infos.length) {
|
||||
/* Translators: %s is a weather condition like "Clear sky"; see
|
||||
libgweather for the possible condition strings. If at all
|
||||
possible, the sentence should match the grammatical case etc. of
|
||||
the inserted conditions. */
|
||||
case 1: fmt = _("%s all day."); break;
|
||||
|
||||
/* Translators: %s is a weather condition like "Clear sky"; see
|
||||
libgweather for the possible condition strings. If at all
|
||||
possible, the sentence should match the grammatical case etc. of
|
||||
the inserted conditions. */
|
||||
case 2: fmt = _("%s, then %s later."); break;
|
||||
|
||||
/* Translators: %s is a weather condition like "Clear sky"; see
|
||||
libgweather for the possible condition strings. If at all
|
||||
possible, the sentence should match the grammatical case etc. of
|
||||
the inserted conditions. */
|
||||
case 3: fmt = _("%s, then %s, followed by %s later."); break;
|
||||
}
|
||||
let summaries = infos.map((info, i) => {
|
||||
let capitalize = i == 0 && fmt.startsWith('%s');
|
||||
return this._getSummary(info, capitalize);
|
||||
});
|
||||
return String.prototype.format.apply(fmt, summaries);
|
||||
return infos;
|
||||
}
|
||||
|
||||
_getLabelText() {
|
||||
if (!this._weatherClient.hasLocation)
|
||||
return _("Select a location…");
|
||||
_addForecasts() {
|
||||
let layout = this._forecastGrid.layout_manager;
|
||||
|
||||
if (this._weatherClient.loading)
|
||||
return _("Loading…");
|
||||
let infos = this._getInfos();
|
||||
if (this._forecastGrid.text_direction == Clutter.TextDirection.RTL)
|
||||
infos.reverse();
|
||||
|
||||
let col = 0;
|
||||
infos.forEach(fc => {
|
||||
let [ok, timestamp] = fc.get_value_update();
|
||||
let timeStr = Util.formatTime(new Date(timestamp * 1000), {
|
||||
timeOnly: true
|
||||
});
|
||||
|
||||
let icon = new St.Icon({ style_class: 'weather-forecast-icon',
|
||||
icon_name: fc.get_symbolic_icon_name(),
|
||||
x_align: Clutter.ActorAlign.CENTER,
|
||||
x_expand: true });
|
||||
let temp = new St.Label({ style_class: 'weather-forecast-temp',
|
||||
text: fc.get_temp_summary(),
|
||||
x_align: Clutter.ActorAlign.CENTER });
|
||||
let time = new St.Label({ style_class: 'weather-forecast-time',
|
||||
text: timeStr,
|
||||
x_align: Clutter.ActorAlign.CENTER });
|
||||
|
||||
layout.attach(icon, col, 0, 1, 1);
|
||||
layout.attach(temp, col, 1, 1, 1);
|
||||
layout.attach(time, col, 2, 1, 1);
|
||||
col++;
|
||||
});
|
||||
}
|
||||
|
||||
_setStatusLabel(text) {
|
||||
let layout = this._forecastGrid.layout_manager;
|
||||
let label = new St.Label({ text });
|
||||
layout.attach(label, 0, 0, 1, 1);
|
||||
}
|
||||
|
||||
_updateForecasts() {
|
||||
this._forecastGrid.destroy_all_children();
|
||||
|
||||
if (!this._weatherClient.hasLocation) {
|
||||
this._setStatusLabel(_("Select a location…"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._weatherClient.loading) {
|
||||
this._setStatusLabel(_("Loading…"));
|
||||
return;
|
||||
}
|
||||
|
||||
let info = this._weatherClient.info;
|
||||
if (info.is_valid())
|
||||
return this._getSummaryText() + ' ' +
|
||||
/* Translators: %s is a temperature with unit, e.g. "23℃" */
|
||||
_("Feels like %s.").format(info.get_apparent());
|
||||
if (info.is_valid()) {
|
||||
this._addForecasts();
|
||||
return;
|
||||
}
|
||||
|
||||
if (info.network_error())
|
||||
return _("Go online for weather information");
|
||||
|
||||
return _("Weather information is currently unavailable");
|
||||
this._setStatusLabel(_("Go online for weather information"));
|
||||
else
|
||||
this._setStatusLabel(_("Weather information is currently unavailable"));
|
||||
}
|
||||
|
||||
_sync() {
|
||||
@ -328,7 +325,7 @@ var WeatherSection = class WeatherSection {
|
||||
if (!this.actor.visible)
|
||||
return;
|
||||
|
||||
this._conditionsLabel.text = this._getLabelText();
|
||||
this._updateForecasts();
|
||||
}
|
||||
};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user