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;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
.world-clocks-grid {
|
.world-clocks-grid,
|
||||||
|
.weather-grid {
|
||||||
spacing-rows: 0.4em;
|
spacing-rows: 0.4em;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -930,6 +931,19 @@ StScrollBar {
|
|||||||
spacing: 0.4em;
|
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 {
|
.calendar-month-label {
|
||||||
color: darken($fg_color,5%);
|
color: darken($fg_color,5%);
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
@ -22,6 +22,8 @@ const Calendar = imports.ui.calendar;
|
|||||||
const Weather = imports.misc.weather;
|
const Weather = imports.misc.weather;
|
||||||
const System = imports.system;
|
const System = imports.system;
|
||||||
|
|
||||||
|
const MAX_FORECASTS = 5;
|
||||||
|
|
||||||
function _isToday(date) {
|
function _isToday(date) {
|
||||||
let now = new Date();
|
let now = new Date();
|
||||||
return now.getYear() == date.getYear() &&
|
return now.getYear() == date.getYear() &&
|
||||||
@ -220,106 +222,101 @@ var WeatherSection = class WeatherSection {
|
|||||||
x_align: Clutter.ActorAlign.START,
|
x_align: Clutter.ActorAlign.START,
|
||||||
text: _("Weather") }));
|
text: _("Weather") }));
|
||||||
|
|
||||||
this._conditionsLabel = new St.Label({ style_class: 'weather-conditions',
|
let layout = new Clutter.GridLayout({ orientation: Clutter.Orientation.VERTICAL });
|
||||||
x_align: Clutter.ActorAlign.START });
|
this._forecastGrid = new St.Widget({ style_class: 'weather-grid',
|
||||||
this._conditionsLabel.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;
|
layout_manager: layout });
|
||||||
this._conditionsLabel.clutter_text.line_wrap = true;
|
layout.hookup_style(this._forecastGrid);
|
||||||
box.add_child(this._conditionsLabel);
|
box.add_child(this._forecastGrid);
|
||||||
|
|
||||||
this._weatherClient.connect('changed', this._sync.bind(this));
|
this._weatherClient.connect('changed', this._sync.bind(this));
|
||||||
this._sync();
|
this._sync();
|
||||||
}
|
}
|
||||||
|
|
||||||
_getSummary(info, capitalize=false) {
|
_getInfos() {
|
||||||
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() {
|
|
||||||
let info = this._weatherClient.info;
|
let info = this._weatherClient.info;
|
||||||
let forecasts = info.get_forecast_list();
|
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 current = info;
|
||||||
let infos = [info];
|
let infos = [info];
|
||||||
for (let i = 0; i < forecasts.length; i++) {
|
for (let i = 0; i < forecasts.length; i++) {
|
||||||
let [ok, timestamp] = forecasts[i].get_value_update();
|
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
|
continue; // Ignore forecasts from other days
|
||||||
|
|
||||||
if (this._sameSummary(current, forecasts[i]))
|
[ok, timestamp] = current.get_value_update();
|
||||||
continue; // Ignore consecutive runs of equal summaries
|
let currenttime = new Date(timestamp * 1000);
|
||||||
|
if (currenttime.getHours() == datetime.getHours())
|
||||||
|
continue; // Enforce a minimum interval of 1h
|
||||||
|
|
||||||
current = forecasts[i];
|
current = forecasts[i];
|
||||||
if (infos.push(current) == 3)
|
if (infos.push(current) == MAX_FORECASTS)
|
||||||
break; // Use a maximum of three summaries
|
break; // Use a maximum of five forecasts
|
||||||
|
}
|
||||||
|
return infos;
|
||||||
}
|
}
|
||||||
|
|
||||||
let fmt;
|
_addForecasts() {
|
||||||
switch(infos.length) {
|
let layout = this._forecastGrid.layout_manager;
|
||||||
/* 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
|
let infos = this._getInfos();
|
||||||
libgweather for the possible condition strings. If at all
|
if (this._forecastGrid.text_direction == Clutter.TextDirection.RTL)
|
||||||
possible, the sentence should match the grammatical case etc. of
|
infos.reverse();
|
||||||
the inserted conditions. */
|
|
||||||
case 2: fmt = _("%s, then %s later."); break;
|
|
||||||
|
|
||||||
/* Translators: %s is a weather condition like "Clear sky"; see
|
let col = 0;
|
||||||
libgweather for the possible condition strings. If at all
|
infos.forEach(fc => {
|
||||||
possible, the sentence should match the grammatical case etc. of
|
let [ok, timestamp] = fc.get_value_update();
|
||||||
the inserted conditions. */
|
let timeStr = Util.formatTime(new Date(timestamp * 1000), {
|
||||||
case 3: fmt = _("%s, then %s, followed by %s later."); break;
|
timeOnly: true
|
||||||
}
|
});
|
||||||
let summaries = infos.map((info, i) => {
|
|
||||||
let capitalize = i == 0 && fmt.startsWith('%s');
|
let icon = new St.Icon({ style_class: 'weather-forecast-icon',
|
||||||
return this._getSummary(info, capitalize);
|
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++;
|
||||||
});
|
});
|
||||||
return String.prototype.format.apply(fmt, summaries);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_getLabelText() {
|
_setStatusLabel(text) {
|
||||||
if (!this._weatherClient.hasLocation)
|
let layout = this._forecastGrid.layout_manager;
|
||||||
return _("Select a location…");
|
let label = new St.Label({ text });
|
||||||
|
layout.attach(label, 0, 0, 1, 1);
|
||||||
|
}
|
||||||
|
|
||||||
if (this._weatherClient.loading)
|
_updateForecasts() {
|
||||||
return _("Loading…");
|
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;
|
let info = this._weatherClient.info;
|
||||||
if (info.is_valid())
|
if (info.is_valid()) {
|
||||||
return this._getSummaryText() + ' ' +
|
this._addForecasts();
|
||||||
/* Translators: %s is a temperature with unit, e.g. "23℃" */
|
return;
|
||||||
_("Feels like %s.").format(info.get_apparent());
|
}
|
||||||
|
|
||||||
if (info.network_error())
|
if (info.network_error())
|
||||||
return _("Go online for weather information");
|
this._setStatusLabel(_("Go online for weather information"));
|
||||||
|
else
|
||||||
return _("Weather information is currently unavailable");
|
this._setStatusLabel(_("Weather information is currently unavailable"));
|
||||||
}
|
}
|
||||||
|
|
||||||
_sync() {
|
_sync() {
|
||||||
@ -328,7 +325,7 @@ var WeatherSection = class WeatherSection {
|
|||||||
if (!this.actor.visible)
|
if (!this.actor.visible)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
this._conditionsLabel.text = this._getLabelText();
|
this._updateForecasts();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user