mirror of
https://github.com/EGroupware/egroupware.git
synced 2024-12-22 23:00:56 +01:00
Home: Weather portlet improvements
This commit is contained in:
parent
665dcc18da
commit
5cfe1cef58
@ -22,6 +22,7 @@ import {Et2Dialog} from "../Et2Dialog/Et2Dialog";
|
|||||||
import {et2_IResizeable} from "../et2_core_interfaces";
|
import {et2_IResizeable} from "../et2_core_interfaces";
|
||||||
import {HomeApp} from "../../../../home/js/app";
|
import {HomeApp} from "../../../../home/js/app";
|
||||||
import {etemplate2} from "../etemplate2";
|
import {etemplate2} from "../etemplate2";
|
||||||
|
import {SelectOption} from "../Et2Select/FindSelectOptions";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Participate in Home
|
* Participate in Home
|
||||||
@ -392,6 +393,17 @@ export class Et2Portlet extends Et2Widget(SlCard)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a list of user-configurable properties
|
||||||
|
* @returns {[{name : string, type : string, select_options? : [SelectOption]}]}
|
||||||
|
*/
|
||||||
|
get portletProperties() : { name : string, type : string, label : string, select_options? : SelectOption[] }[]
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
{name: 'color', label: "Color", type: 'et2-colorpicker'}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create & show a dialog for customizing this portlet
|
* Create & show a dialog for customizing this portlet
|
||||||
*
|
*
|
||||||
@ -404,7 +416,7 @@ export class Et2Portlet extends Et2Widget(SlCard)
|
|||||||
callback: this._process_edit.bind(this),
|
callback: this._process_edit.bind(this),
|
||||||
template: this.editTemplate,
|
template: this.editTemplate,
|
||||||
value: {
|
value: {
|
||||||
content: this.settings
|
content: {...this.settings, ...this.portletProperties}
|
||||||
},
|
},
|
||||||
buttons: [
|
buttons: [
|
||||||
{
|
{
|
||||||
@ -455,6 +467,7 @@ export class Et2Portlet extends Et2Widget(SlCard)
|
|||||||
{
|
{
|
||||||
this.settings = {...this.settings, value};
|
this.settings = {...this.settings, value};
|
||||||
}
|
}
|
||||||
|
this.requestUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
public update_settings(settings)
|
public update_settings(settings)
|
||||||
|
@ -11,6 +11,8 @@ import {ClassWithAttributes, ClassWithInterfaces} from "../et2_core_inheritance"
|
|||||||
import {css, dedupeMixin, LitElement, PropertyValues, unsafeCSS} from "@lion/core";
|
import {css, dedupeMixin, LitElement, PropertyValues, unsafeCSS} from "@lion/core";
|
||||||
import type {et2_container} from "../et2_core_baseWidget";
|
import type {et2_container} from "../et2_core_baseWidget";
|
||||||
import type {et2_DOMWidget} from "../et2_core_DOMWidget";
|
import type {et2_DOMWidget} from "../et2_core_DOMWidget";
|
||||||
|
import {egw_getActionManager, egw_getAppObjectManager} from "../../egw_action/egw_action.js";
|
||||||
|
import {EGW_AI_DRAG_OUT, EGW_AI_DRAG_OVER} from "../../egw_action/egw_action_constants";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This mixin will allow any LitElement to become an Et2Widget
|
* This mixin will allow any LitElement to become an Et2Widget
|
||||||
@ -84,6 +86,11 @@ const Et2WidgetMixin = <T extends Constructor>(superClass : T) =>
|
|||||||
*/
|
*/
|
||||||
protected _deferred_properties : { [key : string] : string } = {};
|
protected _deferred_properties : { [key : string] : string } = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* EGroupware action system action manager
|
||||||
|
*/
|
||||||
|
private _actionManager : egwAction;
|
||||||
|
|
||||||
|
|
||||||
/** WebComponent **/
|
/** WebComponent **/
|
||||||
static get styles()
|
static get styles()
|
||||||
@ -91,18 +98,20 @@ const Et2WidgetMixin = <T extends Constructor>(superClass : T) =>
|
|||||||
return [
|
return [
|
||||||
...(super.styles ? (Array.isArray(super.styles) ? super.styles : [super.styles]) : []),
|
...(super.styles ? (Array.isArray(super.styles) ? super.styles : [super.styles]) : []),
|
||||||
css`
|
css`
|
||||||
:host([disabled]) {
|
:host([disabled]) {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* CSS to align internal inputs according to box alignment */
|
/* CSS to align internal inputs according to box alignment */
|
||||||
:host([align="center"]) .input-group__input {
|
|
||||||
|
:host([align="center"]) .input-group__input {
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
:host([align="right"]) .input-group__input {
|
|
||||||
|
:host([align="right"]) .input-group__input {
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
}
|
}
|
||||||
`];
|
`];
|
||||||
}
|
}
|
||||||
|
|
||||||
static get properties()
|
static get properties()
|
||||||
@ -192,6 +201,9 @@ const Et2WidgetMixin = <T extends Constructor>(superClass : T) =>
|
|||||||
data: {
|
data: {
|
||||||
type: String,
|
type: String,
|
||||||
reflect: false
|
reflect: false
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
type: Object
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -1331,6 +1343,124 @@ const Et2WidgetMixin = <T extends Constructor>(superClass : T) =>
|
|||||||
// If we're the root object, return the phpgwapi API instance
|
// If we're the root object, return the phpgwapi API instance
|
||||||
return typeof egw === "function" ? egw('phpgwapi', wnd) : (window['egw'] ? window['egw'] : null);
|
return typeof egw === "function" ? egw('phpgwapi', wnd) : (window['egw'] ? window['egw'] : null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set Actions on the widget
|
||||||
|
*
|
||||||
|
* Each action is defined as an object:
|
||||||
|
*
|
||||||
|
* move: {
|
||||||
|
* type: "drop",
|
||||||
|
* acceptedTypes: "mail",
|
||||||
|
* icon: "move",
|
||||||
|
* caption: "Move to"
|
||||||
|
* onExecute: javascript:app.mail.drop_move"
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* This will turn the widget into a drop target for "mail" drag types. When "mail" drag types are dropped,
|
||||||
|
* the function drop_move(egwAction action, egwActionObject sender) will be called. The ID of the
|
||||||
|
* dragged "mail" will be in sender.id, some information about the sender will be in sender.context. The
|
||||||
|
* widget involved can typically be found in action.parent.data.widget, so your handler
|
||||||
|
* can operate in the widget context easily. The location varies depending on your action though. It
|
||||||
|
* might be action.parent.parent.data.widget
|
||||||
|
*
|
||||||
|
* To customise how the actions are handled for a particular widget, override _link_actions(). It handles
|
||||||
|
* the more widget-specific parts.
|
||||||
|
*
|
||||||
|
* @param {object} actions {ID: {attributes..}+} map of egw action information
|
||||||
|
*/
|
||||||
|
set actions(actions : egwAction[])
|
||||||
|
{
|
||||||
|
if(this.id == "" || typeof this.id == "undefined")
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Initialize the action manager and add some actions to it
|
||||||
|
// Only look 1 level deep
|
||||||
|
let gam = egw_getActionManager(this.egw().app_name(), true, 1);
|
||||||
|
if(typeof this._actionManager != "object" && actions.length > 0)
|
||||||
|
{
|
||||||
|
if(gam.getActionById(this.getInstanceManager().uniqueId, 1) !== null)
|
||||||
|
{
|
||||||
|
gam = gam.getActionById(this.getInstanceManager().uniqueId, 1);
|
||||||
|
}
|
||||||
|
if(gam.getActionById(this.id, 1) != null)
|
||||||
|
{
|
||||||
|
this._actionManager = gam.getActionById(this.id, 1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this._actionManager = gam.addAction("actionManager", this.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this._actionManager.updateActions(actions, this.egw().appName);
|
||||||
|
//if (this.options.default_execute) this._actionManager.setDefaultExecute(this.options.default_execute);
|
||||||
|
|
||||||
|
// Put a reference to the widget into the action stuff, so we can
|
||||||
|
// easily get back to widget context from the action handler
|
||||||
|
this._actionManager.data = {widget: this};
|
||||||
|
|
||||||
|
// Link the actions to the DOM
|
||||||
|
this._link_actions(actions);
|
||||||
|
}
|
||||||
|
|
||||||
|
get actions()
|
||||||
|
{
|
||||||
|
return this._actionManager?.children || []
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Link the EGroupware actions to the DOM nodes / widget bits.
|
||||||
|
*
|
||||||
|
* @param {object} actions {ID: {attributes..}+} map of egw action information
|
||||||
|
*/
|
||||||
|
protected _link_actions(actions)
|
||||||
|
{
|
||||||
|
// Get the top level element for the tree
|
||||||
|
const objectManager = egw_getAppObjectManager(true);
|
||||||
|
let widget_object = objectManager.getObjectById(this.id);
|
||||||
|
|
||||||
|
if(widget_object == null)
|
||||||
|
{
|
||||||
|
// Add a new container to the object manager which will hold the widget
|
||||||
|
// objects
|
||||||
|
widget_object = objectManager.insertObject(false, new egwActionObject(
|
||||||
|
this.id, objectManager, (new Et2ActionObjectForWidget(this)).getAOI(),
|
||||||
|
this._actionManager || objectManager.manager.getActionById(this.id) || objectManager.manager
|
||||||
|
));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
widget_object.setAOI((new Et2ActionObjectForWidget(this, this.getDOMNode())).getAOI());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete all old objects
|
||||||
|
widget_object.clear();
|
||||||
|
widget_object.unregisterActions();
|
||||||
|
|
||||||
|
// Go over the widget & add links - this is where we decide which actions are
|
||||||
|
// 'allowed' for this widget at this time
|
||||||
|
widget_object.updateActionLinks(this._get_action_links(actions));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all action-links / id's of 1.-level actions from a given action object
|
||||||
|
*
|
||||||
|
* This can be overwritten to not allow all actions, by not returning them here.
|
||||||
|
*
|
||||||
|
* @param actions
|
||||||
|
* @returns {Array}
|
||||||
|
*/
|
||||||
|
_get_action_links(actions)
|
||||||
|
{
|
||||||
|
const action_links = [];
|
||||||
|
for(let i in actions)
|
||||||
|
{
|
||||||
|
let action = actions[i];
|
||||||
|
action_links.push(typeof action.id != 'undefined' ? action.id : i);
|
||||||
|
}
|
||||||
|
return action_links;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add some more stuff in
|
// Add some more stuff in
|
||||||
@ -1627,3 +1757,61 @@ export function cssImage(image_name : string, app_name? : string)
|
|||||||
return css``;
|
return css``;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The egw_action system requires an egwActionObjectInterface Interface implementation
|
||||||
|
* to tie actions to DOM nodes. This one can be used by any widget.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @param {et2_DOMWidget} widget
|
||||||
|
* @param {Object} node
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export class Et2ActionObjectForWidget
|
||||||
|
{
|
||||||
|
aoi : egwActionObjectInterface;
|
||||||
|
|
||||||
|
constructor(_widget : LitElement, _node? : HTMLElement)
|
||||||
|
{
|
||||||
|
const widget = _widget;
|
||||||
|
const objectNode = _node;
|
||||||
|
this.aoi = new egwActionObjectInterface();
|
||||||
|
this.aoi['getWidget'] = function()
|
||||||
|
{
|
||||||
|
return widget;
|
||||||
|
};
|
||||||
|
this.aoi.doGetDOMNode = function()
|
||||||
|
{
|
||||||
|
return widget
|
||||||
|
};
|
||||||
|
|
||||||
|
// _outerCall may be used to determine, whether the state change has been
|
||||||
|
// evoked from the outside and the stateChangeCallback has to be called
|
||||||
|
// or not.
|
||||||
|
this.aoi.doSetState = function(_state, _outerCall)
|
||||||
|
{
|
||||||
|
};
|
||||||
|
|
||||||
|
// The doTiggerEvent function may be overritten by the aoi if it wants to
|
||||||
|
// support certain action implementation specific events like EGW_AI_DRAG_OVER
|
||||||
|
// or EGW_AI_DRAG_OUT
|
||||||
|
this.aoi.doTriggerEvent = function(_event, _data)
|
||||||
|
{
|
||||||
|
switch(_event)
|
||||||
|
{
|
||||||
|
case EGW_AI_DRAG_OVER:
|
||||||
|
this.widget.classList.add("ui-state-active");
|
||||||
|
break;
|
||||||
|
case EGW_AI_DRAG_OUT:
|
||||||
|
this.widget.classList.remove("ui-state-active");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
getAOI()
|
||||||
|
{
|
||||||
|
return this.aoi;
|
||||||
|
}
|
||||||
|
}
|
@ -189,7 +189,10 @@ class home_ui
|
|||||||
$settings = $portlet->get_properties();
|
$settings = $portlet->get_properties();
|
||||||
foreach($settings as $key => $setting)
|
foreach($settings as $key => $setting)
|
||||||
{
|
{
|
||||||
if(is_array($setting) && !array_key_exists('type',$setting)) unset($settings[$key]);
|
if(is_array($setting) && !array_key_exists('type', $setting))
|
||||||
|
{
|
||||||
|
unset($settings[$key]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
$settings += $context;
|
$settings += $context;
|
||||||
foreach(home_portlet::$common_attributes as $attr)
|
foreach(home_portlet::$common_attributes as $attr)
|
||||||
@ -197,6 +200,10 @@ class home_ui
|
|||||||
unset($settings[$attr]);
|
unset($settings[$attr]);
|
||||||
}
|
}
|
||||||
$portlet_content['settings'] = $settings;
|
$portlet_content['settings'] = $settings;
|
||||||
|
if(method_exists($portlet, "get_value"))
|
||||||
|
{
|
||||||
|
$portlet_content['value'] = $portlet->get_value();
|
||||||
|
}
|
||||||
|
|
||||||
// Set actions
|
// Set actions
|
||||||
// Must be after settings so actions can take settings into account
|
// Must be after settings so actions can take settings into account
|
||||||
|
@ -27,7 +27,6 @@ class home_weather_portlet extends home_portlet
|
|||||||
{
|
{
|
||||||
|
|
||||||
const API_URL = "http://api.openweathermap.org/data/2.5/";
|
const API_URL = "http://api.openweathermap.org/data/2.5/";
|
||||||
const ICON_URL = 'http://openweathermap.org/img/w/';
|
|
||||||
const API_KEY = '45484f039c5caa14d31aefe7f5514292';
|
const API_KEY = '45484f039c5caa14d31aefe7f5514292';
|
||||||
const CACHE_TIME = 3600; // Cache weather for an hour
|
const CACHE_TIME = 3600; // Cache weather for an hour
|
||||||
|
|
||||||
@ -51,22 +50,15 @@ class home_weather_portlet extends home_portlet
|
|||||||
$this->context = $context;
|
$this->context = $context;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function exec($id = null, Etemplate &$etemplate = null)
|
public function get_value()
|
||||||
{
|
{
|
||||||
// Allow to submit directly back here
|
$id = $this->context['id'];
|
||||||
if(is_array($id) && $id['id'])
|
$content = array();
|
||||||
{
|
|
||||||
$id = $id['id'];
|
|
||||||
}
|
|
||||||
$etemplate->read('home.weather');
|
|
||||||
|
|
||||||
$etemplate->set_dom_id($id);
|
|
||||||
$content = $this->context;
|
|
||||||
$request = array(
|
$request = array(
|
||||||
'units' => $this->context['units'] ? $this->context['units'] : 'metric',
|
'units' => $this->context['units'] ?: 'metric',
|
||||||
'lang' => $GLOBALS['egw_info']['user']['preferences']['common']['lang'],
|
'lang' => $GLOBALS['egw_info']['user']['preferences']['common']['lang'],
|
||||||
// Always get (& cache) 10 days, we'll cut down later
|
// Always get (& cache) 10 days, we'll cut down later
|
||||||
'cnt' => 10
|
'cnt' => 10
|
||||||
);
|
);
|
||||||
if($this->context['city_id'])
|
if($this->context['city_id'])
|
||||||
{
|
{
|
||||||
@ -78,9 +70,9 @@ class home_weather_portlet extends home_portlet
|
|||||||
$request['q'] = $this->context['city'];
|
$request['q'] = $this->context['city'];
|
||||||
$content += $this->get_weather($request);
|
$content += $this->get_weather($request);
|
||||||
}
|
}
|
||||||
elseif ($this->context['position'])
|
elseif($this->context['position'])
|
||||||
{
|
{
|
||||||
list($request['lat'],$request['lon']) = explode(',',$this->context['position']);
|
list($request['lat'], $request['lon']) = explode(',', $this->context['position']);
|
||||||
$content += $this->get_weather($request);
|
$content += $this->get_weather($request);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -94,34 +86,25 @@ class home_weather_portlet extends home_portlet
|
|||||||
// Save updated Api\Preferences
|
// Save updated Api\Preferences
|
||||||
$portlets[$id]['city_id'] = $content['city_id'];
|
$portlets[$id]['city_id'] = $content['city_id'];
|
||||||
$this->context['city'] = $portlets[$id]['city'] = $content['settings']['city'] =
|
$this->context['city'] = $portlets[$id]['city'] = $content['settings']['city'] =
|
||||||
$content['settings']['title'] = $content['city'] = is_array($content['city']) ? $content['city']['name'] : $content['city'];
|
$content['settings']['title'] = $content['city'] = is_array($content['city']) ? $content['city']['name'] : $content['city'];
|
||||||
unset($portlets[$id]['position']);
|
unset($portlets[$id]['position']);
|
||||||
$GLOBALS['egw']->preferences->add('home', $id, $portlets[$id]);
|
$GLOBALS['egw']->preferences->add('home', $id, $portlets[$id]);
|
||||||
$GLOBALS['egw']->preferences->save_repository(True);
|
$GLOBALS['egw']->preferences->save_repository(True);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Adjust data to match portlet size
|
|
||||||
if($this->context['height'] <= 2 && $this->context['width'] <= 3)
|
|
||||||
{
|
|
||||||
// Too small for the other days
|
|
||||||
unset($content['list']);
|
|
||||||
}
|
|
||||||
else if ($this->context['height'] == 2 && $this->context['width'] > 3)
|
|
||||||
{
|
|
||||||
// Wider, but not taller
|
|
||||||
unset($content['current']);
|
|
||||||
}
|
|
||||||
// Even too small for current high/low
|
|
||||||
if($this->context['width'] < 3)
|
|
||||||
{
|
|
||||||
$content['current']['no_current_temp'] = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Direct to full forecast page
|
// Direct to full forecast page
|
||||||
$content['attribution'] ='http://openweathermap.org/city/'.$content['city_id'];
|
$content['attribution'] = 'http://openweathermap.org/city/' . $content['city_id'];
|
||||||
|
|
||||||
$etemplate->exec('home.home_weather_portlet.exec',$content,array(),array('__ALL__'=>true),array('id' =>$id));
|
return [
|
||||||
|
'color' => $this->context['color'],
|
||||||
|
'city' => $this->context['city'],
|
||||||
|
'display' => $this->context['display'],
|
||||||
|
'weather' => $content
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function exec($id = null, Etemplate &$etemplate = null)
|
||||||
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -188,10 +171,10 @@ class home_weather_portlet extends home_portlet
|
|||||||
{
|
{
|
||||||
$massage =& $data['list'];
|
$massage =& $data['list'];
|
||||||
|
|
||||||
for($i = 0; $i < min(count($massage), $this->context['width']); $i++)
|
for($i = 0; $i < count($data['list']); $i++)
|
||||||
{
|
{
|
||||||
$forecast =& $massage[$i];
|
$forecast =& $massage[$i];
|
||||||
$forecast['day'] = Api\DateTime::to($forecast['dt'],'l');
|
$forecast['day'] = Api\DateTime::to($forecast['dt'], 'l');
|
||||||
self::format_forecast($forecast);
|
self::format_forecast($forecast);
|
||||||
}
|
}
|
||||||
// Chop data to fit into portlet
|
// Chop data to fit into portlet
|
||||||
@ -223,29 +206,49 @@ class home_weather_portlet extends home_portlet
|
|||||||
$weather =& $data['weather'] ? $data['weather'] : $data;
|
$weather =& $data['weather'] ? $data['weather'] : $data;
|
||||||
$temp =& $data['temp'] ? $data['temp'] : $data;
|
$temp =& $data['temp'] ? $data['temp'] : $data;
|
||||||
|
|
||||||
// Full URL for icon
|
// Find icon
|
||||||
if(is_array($weather))
|
if(is_array($weather))
|
||||||
{
|
{
|
||||||
foreach($weather as &$w)
|
foreach($weather as &$w)
|
||||||
{
|
{
|
||||||
$w['icon'] = static::ICON_URL . $w['icon'].'.png';
|
$w['icon'] = static::get_icon($w);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Round
|
// Round
|
||||||
foreach(array('temp','temp_min','temp_max','min','max') as $temp_name)
|
foreach(array('temp', 'temp_min', 'temp_max', 'min', 'max') as $temp_name)
|
||||||
{
|
{
|
||||||
if(array_key_exists($temp_name, $temp))
|
if(array_key_exists($temp_name, $temp))
|
||||||
{
|
{
|
||||||
$temp[$temp_name] = ''.round($temp[$temp_name]);
|
$temp[$temp_name] = '' . round($temp[$temp_name]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get an icon to represent the forecast
|
||||||
|
*
|
||||||
|
* We use icon names from shoelace
|
||||||
|
* @param $weather
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected static function get_icon(&$weather)
|
||||||
|
{
|
||||||
|
$icon = "question-lg";
|
||||||
|
switch(strtolower($weather['main']))
|
||||||
|
{
|
||||||
|
case 'clear' :
|
||||||
|
$icon = 'sun';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
$icon = strtolower($weather['main']);
|
||||||
|
}
|
||||||
|
return $icon;
|
||||||
|
}
|
||||||
|
|
||||||
public function get_actions()
|
public function get_actions()
|
||||||
{
|
{
|
||||||
$actions = array(
|
$actions = array();
|
||||||
);
|
|
||||||
return $actions;
|
return $actions;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -270,9 +273,9 @@ class home_weather_portlet extends home_portlet
|
|||||||
$properties = parent::get_properties();
|
$properties = parent::get_properties();
|
||||||
|
|
||||||
$properties[] = array(
|
$properties[] = array(
|
||||||
'name' => 'city',
|
'name' => 'city',
|
||||||
'type' => 'textbox',
|
'type' => 'textbox',
|
||||||
'label' => lang('Location'),
|
'label' => lang('Location'),
|
||||||
);
|
);
|
||||||
return $properties;
|
return $properties;
|
||||||
}
|
}
|
||||||
@ -280,9 +283,14 @@ class home_weather_portlet extends home_portlet
|
|||||||
public function get_description()
|
public function get_description()
|
||||||
{
|
{
|
||||||
return array(
|
return array(
|
||||||
'displayName'=> lang('Weather'),
|
'displayName' => lang('Weather'),
|
||||||
'title'=> $this->context['city'],
|
'title' => $this->context['city'],
|
||||||
'description'=> lang('Weather')
|
'description' => lang('Weather')
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function get_type()
|
||||||
|
{
|
||||||
|
return 'et2-portlet-weather';
|
||||||
|
}
|
||||||
}
|
}
|
173
home/js/Et2PortletWeather.ts
Normal file
173
home/js/Et2PortletWeather.ts
Normal file
@ -0,0 +1,173 @@
|
|||||||
|
import {Et2Portlet} from "../../api/js/etemplate/Et2Portlet/Et2Portlet";
|
||||||
|
import {classMap, css, html, nothing, repeat, TemplateResult} from "@lion/core";
|
||||||
|
import shoelace from "../../api/js/etemplate/Styles/shoelace";
|
||||||
|
import {SelectOption} from "../../api/js/etemplate/Et2Select/FindSelectOptions";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show current and forecast weather
|
||||||
|
*/
|
||||||
|
export class Et2PortletWeather extends Et2Portlet
|
||||||
|
{
|
||||||
|
static get styles()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
...shoelace,
|
||||||
|
...(super.styles || []),
|
||||||
|
css`
|
||||||
|
.portlet--weather {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.day__forecast {
|
||||||
|
width: fit-content;
|
||||||
|
min-width: 12ex;
|
||||||
|
max-width: 20ex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.temperature__day-label {
|
||||||
|
text-align: center;
|
||||||
|
font-size: 120%;
|
||||||
|
padding-bottom: var(--sl-spacing-medium);
|
||||||
|
}
|
||||||
|
|
||||||
|
.temperature {
|
||||||
|
font-size: 160%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.temperature__high_low {
|
||||||
|
}
|
||||||
|
|
||||||
|
sl-icon {
|
||||||
|
font-size: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.portlet--weather .temperature__current {
|
||||||
|
/* Make current day a little bigger */
|
||||||
|
font-size: 180%;
|
||||||
|
padding: var(--sl-spacing-large);
|
||||||
|
}
|
||||||
|
|
||||||
|
.temperature__current .day__forecast {
|
||||||
|
padding: var(--sl-spacing-medium) 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:host([style*="span 1"]) .temperature__current {
|
||||||
|
/* No padding if portlet is small */
|
||||||
|
padding: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.portlet--weather .temperature__current sl-icon {
|
||||||
|
font-size: 250%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.temperature__day-list {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
display: grid;
|
||||||
|
gap: var(--sl-spacing-x-large) var(--sl-spacing-medium);
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(12ex, 1fr));
|
||||||
|
padding-top: var(--sl-spacing-large);
|
||||||
|
}
|
||||||
|
|
||||||
|
.temperature__day-list .weather__day-forecast {
|
||||||
|
min-height: 12ex;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a list of user-configurable properties
|
||||||
|
* @returns {[{name : string, type : string, select_options? : [SelectOption]}]}
|
||||||
|
*/
|
||||||
|
get portletProperties() : { name : string, type : string, label : string, select_options? : SelectOption[] }[]
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
...super.portletProperties,
|
||||||
|
{
|
||||||
|
'name': 'city',
|
||||||
|
'type': 'et2-textbox',
|
||||||
|
'label': this.egw().lang('Location'),
|
||||||
|
}
|
||||||
|
/* Use size to control what we show
|
||||||
|
{
|
||||||
|
name: "display", label: "Display", type: "et2-select", select_options: [
|
||||||
|
{'value': 'today', 'label': this.egw().lang('today')},
|
||||||
|
{'value': '3', 'label': this.egw().lang('%1 day', 3)},
|
||||||
|
{'value': '10', 'label': this.egw().lang('%1 day', 10)},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Template for one day of the forecast
|
||||||
|
* @param day
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
protected forecastDayTemplate(day)
|
||||||
|
{
|
||||||
|
return html`
|
||||||
|
<div class="weather__day-forecast">
|
||||||
|
<et2-description class="temperature__day-label" value="${day.day}"></et2-description>
|
||||||
|
<et2-hbox part="day" class="day__forecast">
|
||||||
|
<sl-icon class="weather_icon" name="${day.weather[0].icon}"></sl-icon>
|
||||||
|
${(typeof day.temp?.temp != "undefined") ? html`
|
||||||
|
<et2-hbox class="temperature">
|
||||||
|
<span>${day.temp.temp}</span>
|
||||||
|
</et2-hbox>` : nothing
|
||||||
|
}
|
||||||
|
<et2-vbox class="temperature__high_low">
|
||||||
|
<span class="temperature__max">${day.temp.max}</span>
|
||||||
|
<span class="temperature__min">${day.temp.min}</span>
|
||||||
|
</et2-vbox>
|
||||||
|
</et2-hbox>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
bodyTemplate() : TemplateResult
|
||||||
|
{
|
||||||
|
const doList = parseInt(getComputedStyle(this).width) > 300;
|
||||||
|
const current = this.settings?.weather?.current || {weather: [{icon: "question-lg"}], temp: {temp: "?"}};
|
||||||
|
|
||||||
|
// Get the forecast, excluding today
|
||||||
|
let list = this.settings.weather && (Object.values(this.settings?.weather?.list).slice(1) || []);
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<div
|
||||||
|
part="base"
|
||||||
|
class=${classMap({
|
||||||
|
portlet: true,
|
||||||
|
"portlet--weather": true
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<div part="current" class="temperature__current">
|
||||||
|
${this.forecastDayTemplate({
|
||||||
|
...{
|
||||||
|
day: 'Today',
|
||||||
|
// Current has a different data format
|
||||||
|
temp: {
|
||||||
|
min: current.temp.temp_min,
|
||||||
|
max: current.temp.temp_max
|
||||||
|
}, ...current
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
${doList ? html`
|
||||||
|
<div part="list" class="temperature__day-list">
|
||||||
|
${repeat(list, (item, index) =>
|
||||||
|
{
|
||||||
|
return this.forecastDayTemplate(item);
|
||||||
|
})}
|
||||||
|
</div>` : nothing
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!customElements.get("et2-portlet-weather"))
|
||||||
|
{
|
||||||
|
customElements.define("et2-portlet-weather", Et2PortletWeather);
|
||||||
|
}
|
@ -16,6 +16,7 @@ import {Et2PortletFavorite} from "./Et2PortletFavorite";
|
|||||||
import {loadWebComponent} from "../../api/js/etemplate/Et2Widget/Et2Widget";
|
import {loadWebComponent} from "../../api/js/etemplate/Et2Widget/Et2Widget";
|
||||||
import "./Et2PortletList";
|
import "./Et2PortletList";
|
||||||
import "./Et2PortletNote";
|
import "./Et2PortletNote";
|
||||||
|
import './Et2PortletWeather';
|
||||||
import "../../calendar/js/Et2PortletCalendar"
|
import "../../calendar/js/Et2PortletCalendar"
|
||||||
import Sortable from "sortablejs/modular/sortable.complete.esm.js";
|
import Sortable from "sortablejs/modular/sortable.complete.esm.js";
|
||||||
|
|
||||||
|
@ -1,50 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!DOCTYPE overlay PUBLIC "-//EGroupware GmbH//eTemplate 2.0//EN" "https://www.egroupware.org/etemplate2.0.dtd">
|
|
||||||
<overlay>
|
|
||||||
<template id="home.weather" template="" lang="" group="0" version="1.9.001">
|
|
||||||
<grid id="current" disabled="!@current" width="100%">
|
|
||||||
<columns>
|
|
||||||
<column/>
|
|
||||||
<column/>
|
|
||||||
<column/>
|
|
||||||
<column/>
|
|
||||||
</columns>
|
|
||||||
<rows>
|
|
||||||
<row>
|
|
||||||
<et2-image class="weather_icon" src="weather[0][icon]"></et2-image>
|
|
||||||
<et2-hbox id="temp">
|
|
||||||
<et2-description class="current temperature" id="temp" noLang="true"></et2-description>
|
|
||||||
</et2-hbox>
|
|
||||||
<et2-vbox id="temp" disabled="@no_current_temp">
|
|
||||||
<et2-description class="high_low temperature" id="max" noLang="true"></et2-description>
|
|
||||||
<et2-description class="high_low temperature" id="min" noLang="true"></et2-description>
|
|
||||||
</et2-vbox>
|
|
||||||
</row>
|
|
||||||
<row disabled="!@weather[0][description]">
|
|
||||||
<et2-description id="weather[0][description]" noLang="true"></et2-description>
|
|
||||||
</row>
|
|
||||||
</rows>
|
|
||||||
</grid>
|
|
||||||
<et2-box id="list" class="forecast" disabled="!@list" width="100%">
|
|
||||||
<!-- Box wrapper needed to get box to auto-repeat -->
|
|
||||||
<et2-box id="${row}">
|
|
||||||
<grid width="100%">
|
|
||||||
<columns>
|
|
||||||
<column/>
|
|
||||||
</columns>
|
|
||||||
<rows>
|
|
||||||
<row><et2-description align="center" id="day"></et2-description></row>
|
|
||||||
<row class="weather_icon"><et2-image align="center" class="weather_icon" src="weather[0][icon]"></et2-image></row>
|
|
||||||
<row>
|
|
||||||
<et2-vbox align="center" id="temp">
|
|
||||||
<et2-description class="high_low temperature" id="max" noLang="true"></et2-description>
|
|
||||||
<et2-description class="high_low temperature" id="min" noLang="true"></et2-description>
|
|
||||||
</et2-vbox>
|
|
||||||
</row>
|
|
||||||
</rows>
|
|
||||||
</grid>
|
|
||||||
</et2-box>
|
|
||||||
</et2-box>
|
|
||||||
<et2-description align="center" class="attribution" href="@attribution" value="openweathermap.org" activateLinks="true" extraLinkTarget="_blank"></et2-description>
|
|
||||||
</template>
|
|
||||||
</overlay>
|
|
Loading…
Reference in New Issue
Block a user