diff --git a/apps/app_analyse_plots.html b/apps/app_analyse_plots.html new file mode 100644 index 000000000..4847472c0 --- /dev/null +++ b/apps/app_analyse_plots.html @@ -0,0 +1,464 @@ +/** +* ----------------------------------------------------------------------------- +* @package smartVISU +* @author Wolfram v. Hülsen +* @copyright 2012 - 2024 +* @license GPL [http://www.gnu.de] +* @version 1.0 +* +* @title Plot Analyser +* @category visu +* @icon icons/ws/measure_power_meter.svg +* @color #b00 +* @description Analyse plots with flexible configuration of series parameters +* @description_de Analysieren von Plots mit flexibler Eingabe von Parametern +* +* ----------------------------------------------------------------------------- +*/ + + +{% extends "apps.html" %} + +{% block sidebar %} + +
+
+
Plot Analyser
+
+

Plot Analyser

+
+ +

{{ lang('plot_analyser', 'Global') }}:

+
+
+ + +
+
+
+ + +
+
+ + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Y {{ lang('plot_analyser', 'Axis') }} 1Y {{ lang('plot_analyser', 'Axis') }} 2Y {{ lang('plot_analyser', 'Axis') }} 3Y {{ lang('plot_analyser', 'Axis') }} 4
{{ lang('plot_analyser', 'Axis') }} {{ lang('plot_analyser', 'active') }}
Y min
Y max
Y {{ lang('plot_analyser', 'Axis') }} Position
Y {{ lang('plot_analyser', 'Axis') }} {{ lang('plot_analyser', 'scale') }}
Y {{ lang('plot_analyser', 'Axis') }} {{ lang('plot_analyser', 'Unit') }}
+ +
+ +
+ help +
+ {{ lang('plot_analyser', 'Chartopts', 'hint') }} +
+ +
+
+
+

{{ lang('plot_analyser', 'itemSettings') }}:

+ {% + set sources = { + '1': 'Item 1', + '2': 'Item 2', + '3': 'Item 3', + '4': 'Item 4', + '5': 'Item 5' + } + %} + +
+
+
    + {% for source, title in sources %} +
  • {{ title }}
  • + {% endfor %} +
+
+
+ {% for source, title in sources %} +
+
+
+
+ +
+ help +
+ {{ lang('plot_analyser', 'Item', 'hint') }} +
+ +
+
+
+ + +
+
+ +
+ help +
+ {{ lang('plot_analyser', 'Count', 'hint') }} Default: 100 +
+ +
+
+
+ +
+ help +
+ {{ lang('plot_analyser', 'Exposure', 'hint') }} line, linestack, stair, spline, area, areastair, areaspline, areastack, column, columnstack. Default: line +
+ +
+
+
+ +
+ help +
+ {{ lang('plot_analyser', 'Color', 'hint') }} +
+ +
+
+
+ +
+ help +
+ {{ lang('plot_analyser', 'Assign', 'hint') }} +
+ +
+
+
+ +
+ help +
+ {{ lang('plot_analyser', 'Stacking', 'hint') }} normal, percent. Default: normal +
+ +
+
+
+ +
+ help +
+ {{ lang('plot_analyser', 'Stacks', 'hint') }} +
+ +
+
+ +
+ {% endfor %} +
+
+
+ + +{% endblock %} + + +{% block content %} + + + + {% set mode = "raw" %} /** just init to activate boost mode */ + {% set zoom = "advanced" %} /** enable navigator */ + {% set export = 2 %} /** activate export menu */ + +
+ + {{ plot.period('analyse-plotpopup', item, mode, tmin, tmax, ymin, ymax, count, label, color, exposure, axis, zoom, assign, opposite, ycolor, ytype, unit, chartoptions, stacking, stacks, export, source) }} + +
+ {{ lang('plot_analyser', 'Shift', 'label') }}:  {{ plot.timeshift('analyse-timeshift','analyse-plotpopup', '1d','mini', '- 1d +') }} +
+
+

{{ lang('plot_analyser', 'Code', 'label') }}:

+
+ {% filter trim|escape|nl2br %}{% verbatim %} + {{ plot.period(...) }} + {% endverbatim %}{% endfilter %} +
+
+ +/** options not (yet) used + data-axis = [ x axix name and y axis name(s) ] + data-ycolor = [colors of y axes ] +*/ + + + + +
+{% endblock %} + + diff --git a/apps/app_demo.html b/apps/app_demo.html index a79a19849..3ea6db14d 100644 --- a/apps/app_demo.html +++ b/apps/app_demo.html @@ -1,18 +1,20 @@ /** * ----------------------------------------------------------------------------- -* @package smartVISU -* @author YOUR NAME -* @copyright 2012 - 2015 -* @license GPL -* @version 1.0 +* @package smartVISU +* @author YOUR NAME +* @copyright 2012 - 2024 +* @license GPL +* @version 1.0 * -* @title Demo -* @category weather -* @icon icons/ws/scene_summerhouse.svg -* @color #222 -* @description Here is some description that explains what the app does. It will be shown if you hover over the app-icon. +* @title Demo +* @category weather +* @icon icons/ws/scene_summerhouse.svg +* @color #222 +* @description Here is some description that explains what the app does. It will be shown if you hover over the app-icon. +* @description_de Hier steht eine Beschreibung der Funktion der App. Sie wird angezeigt, wenn man mit der Maus über das App-Symbol fährt. * -* @info Additional information or copyright to then content +* @info Here is additional information, e.g. copyright +* @info_de Hier steht zusätzliche Informationen, z.B. Copyright * @link http://www.smartvisu.de * ----------------------------------------------------------------------------- */ diff --git a/apps/app_slideshow.html b/apps/app_slideshow.html index 4c9c83fa6..d6617441d 100644 --- a/apps/app_slideshow.html +++ b/apps/app_slideshow.html @@ -1,16 +1,17 @@ /** * ----------------------------------------------------------------------------- -* @package smartVISU -* @author Martin Gleiß -* @copyright 2012 - 2015 -* @license GPL -* @version 1.0 +* @package smartVISU +* @author Martin Gleiß +* @copyright 2012 - 2024 +* @license GPL +* @version 1.0 * -* @title Slideshow -* @category slideshow -* @icon icons/ws/control_zoom_in.svg -* @color #00a700 -* @description A slideshow +* @title Slideshow +* @category slideshow +* @icon icons/ws/control_zoom_in.svg +* @color #00a700 +* @description A slideshow of all images in ./pics/slideshow folder +* @description_de Eine Diaschau aller Bilder im Ordner ./pics/slideshow * ----------------------------------------------------------------------------- */ diff --git a/apps/app_tv_spielfilm.html b/apps/app_tv_spielfilm.html index 7a954d93c..31ad3fb54 100644 --- a/apps/app_tv_spielfilm.html +++ b/apps/app_tv_spielfilm.html @@ -2,7 +2,7 @@ * ----------------------------------------------------------------------------- * @package smartVISU * @author Martin Gleiß -* @copyright 2012 - 2022 +* @copyright 2012 - 2024 * @license GPL [http://www.gnu.de] * @version 1.1 * @@ -10,10 +10,10 @@ * @category tv * @icon icons/ws/it_television.svg * @color #b00 -* @description List of all tv-stations from www.TVSpielfilm.de +* @description Fernsehprogramme von www.TVSpielfilm.de * -* @info More feeds on -* @link http://www.tvspielfilm.de/services/widgets/rss-feeds/rss-feeds-im-ueberblick,3538128,ApplicationArticle.html +* @info Mehr Info auf +* @link http://www.tvspielfilm.de * ----------------------------------------------------------------------------- */ diff --git a/apps/app_weather.html b/apps/app_weather.html index 0ec1a7a93..9fd126b43 100644 --- a/apps/app_weather.html +++ b/apps/app_weather.html @@ -2,7 +2,7 @@ * ----------------------------------------------------------------------------- * @package smartVISU * @author Martin Gleiß, Wolfram v. Hülsen -* @copyright 2012 - 2023 +* @copyright 2012 - 2024 * @license GPL [http://www.gnu.de] * @version 1.2 * @@ -10,9 +10,9 @@ * @category weather * @icon icons/ws/weather_sun.svg * @color #2f5bc4 -* @description Weather prognosis and maps from Tagesschau.de +* @description Wetterprognosen und -karten von Tagesschau.de * -* @info More feeds on +* @info Mehr Info auf * @link http://www.tagesschau.de * ----------------------------------------------------------------------------- */ diff --git a/apps/app_webcam_f.html b/apps/app_webcam_f.html index 1a1b5edb6..4d4faf8b1 100644 --- a/apps/app_webcam_f.html +++ b/apps/app_webcam_f.html @@ -1,16 +1,17 @@ /** * ----------------------------------------------------------------------------- -* @package smartVISU -* @author Martin Gleiß -* @copyright 2012 - 2015 -* @license GPL -* @version 1.0 +* @package smartVISU +* @author Martin Gleiß +* @copyright 2012 - 2024 +* @license GPL +* @version 1.0 * -* @title Frankfurt -* @category webcam -* @icon icons/ws/it_camera.svg -* @color #da0 -* @description Some webcams with live-impressions from Frankfurt +* @title Frankfurt +* @category webcam +* @icon icons/ws/it_camera.svg +* @color #da0 +* @description Some webcams with live-impressions from Frankfurt +* @description_de Webcams mit Live-Impressionen aus Frankfurt * ----------------------------------------------------------------------------- */ diff --git a/apps/app_webcam_seiseralm.html b/apps/app_webcam_seiseralm.html index c5336cc1d..d4e62937b 100644 --- a/apps/app_webcam_seiseralm.html +++ b/apps/app_webcam_seiseralm.html @@ -1,19 +1,21 @@ /** * ----------------------------------------------------------------------------- -* @package smartVISU -* @author Frank Berlenz -* @copyright 2012 - 2015 -* @license GPL -* @version 1.1 +* @package smartVISU +* @author Frank Berlenz +* @copyright 2012 - 2024 +* @license GPL +* @version 1.1 * -* @title Seiser Alm -* @category webcam -* @icon icons/ws/it_camera.svg -* @color #da0 -* @description Webcam for Seiser Alm (Dolomitisuperski) +* @title Seiser Alm +* @category webcam +* @icon icons/ws/it_camera.svg +* @color #da0 +* @description Webcams and weather for Seiser Alm (Dolomitisuperski) +* @description_de Webcams und Wetter für die Seiser Alm (Dolomitisuperski) * -* @info Additional information to the Dolomitisuperski area -* @link http://www.dolomitisuperski.com +* @info Additional information on the Dolomitisuperski area +* @info_de Weitere Informationen zum Dolomitisuperski-Gebiet +* @link http://www.dolomitisuperski.com * ----------------------------------------------------------------------------- */ diff --git a/assets.php b/assets.php index 9012ee09a..fdd66826e 100644 --- a/assets.php +++ b/assets.php @@ -28,7 +28,7 @@ require_once $path . '/path-converter/src/ConverterInterface.php'; require_once $path . '/path-converter/src/Converter.php'; -if($type == 'javascript') +if($type == 'javascript' && \defined('config_debug') && !config_debug) array_unshift($request['files'], 'console.log = function() {};'); foreach($request['files'] as $fileName) { @@ -36,12 +36,10 @@ // if filename ends with '.php', evaluate it and add the result if(substr_compare($fileName, '.php', -4) == 0) { ob_start(); - //$wd_was = getcwd(); - chdir(dirname(const_path.$fileName)); - include const_path.$fileName; - //chdir($wd_was); - chdir(dirname($_SERVER['SCRIPT_FILENAME'])); - $rawcontent = ob_get_clean(); + chdir(\dirname(const_path.$fileName)); + include const_path.$fileName; + chdir(\dirname($_SERVER['SCRIPT_FILENAME'])); + $rawcontent = ob_get_clean(); } // otherwise just add it else @@ -59,8 +57,12 @@ $content = "\n/* ".$fileName." */\n"; - // get minified content - $content .= $minifier->execute("assets." . $type); + // avoid errors thrown by minifying already minified highcharts .js files + if ( $type != 'css' && substr_compare($fileName, 'vendor/plot.highcharts', 0, 22) == 0 ) + $content .= preg_replace('#\/\*\*.*?\*\/#s', '', file_get_contents($fileName), 1); + else + // get minified content and strip first multiline comment + $content .= preg_replace('#\/\*[\*!].*?\*\/\n?#s', '', $minifier->execute("assets." . $type), 1); if($type == 'javascript') $content .= ';'; diff --git a/changelog.md b/changelog.md index be13e915a..ad902d089 100755 --- a/changelog.md +++ b/changelog.md @@ -1,3 +1,93 @@ +## 3.5 +### Important for the current release to avoid breaking changes +- imports of widgets must be deleted from the visu pages or the namespace @widgets must be used. + Visit https://knx-user-forum.de/forum/supportforen/smartvisu/1920060-handlungsbedarf-ma%C3%9Fnahmen-gegen-seitenladefehler-ab-v3-4-a-v3-5 + +### New / Changed Widgets +- basic.tank is now able to change colors according to reached thresholds +- additional option for basic.select to support activity indicator like basic.stateswitch +- basic.icon accepts alpha values for rgb(a)/hsl(a)/hsv(a) and also hex rgb(a) values in items +- new app analyse_plots enables live parametrizing of plot.period plus travelling through time with plot.timeshift +- basic.print can be used to generate a tooltip on a linked widget (parameter href = widget-id, rel = 'tooltip') +- additional color and indicator options for basic.trigger +- plot.sparkline can now open links and popups. Tooltip shows parameter "id" as curve name (label) +- device.dimmer extended with an option "type" to display the switch as button (midi, mini, micro, icon) +- indicator option introduced in all quad design widgets using basic.stateswitch or basic.select +- clock.countdown now accepts ISO time strings, fixed countdown duration instead of an item and a new comparative value as starting condition +- calendar.list uses a new format called "calendardate" in the language file which can be defined to show "today" and "tomorrow" with other date information. +- device.uzsuicon, device.uzsugraph and device.uzsutable are prepared for one-time events (provided by smarthomeNG UZSU plugin as of version 2.1.0) + +### Other New Features +- improved spline display for the starting point in device.uzsugraph +- new icons scene_cooking_drink and scene_robovac_dock (thanks to @Pacifia15)) +- smarthomeNG and ioBroker drivers: new configurable option "signalBusy" lets "VISU" logo in the top-right corner blink after a new page is loaded (i.e. "monitor" command has been sent) until all subscribed items have been received. +- new weatherservice open-meteo.com +- ioBroker driver now supports subscribing and unsubscribing series for individual plots which enables usage of plot.timeshift with ioBroker (thanks to YellowFlash for testing!) +- new weather service VisualCrossing +- improved vertical alignment of slider track in device.dimmer when no text is configured +- ioBroker driver is now able to provide item properties from the "state" object using the keyword "property", e.g. "myItem.property.lc" to get the "last changed" property for that item +- digest authentication method integrated in CalDav calendar service. Logfile for cURL messages if service is called with debug parameter. +- new design "lightblue" (inspired by ramann) +- new design "darkmode_blue" as combination of darkblue.css and lightblue.css supports dark mode of the device. Custom darkmode combinations can be defined in ./dropins/designs. +- new date format "t" shows "today" and "tomorrow", if applicable. It replaces format definition given in brackets. Example: "t(l), d.m." shows "today, 23.09." or "Wednesday, 25.09." +- smarthomeNG driver has a new ping function to improve stability of websocket connections on sleepy devices. Set an optional config key "ping_interval" in seconds (0 = disabled) + +### Improvements +- allowed database modes for series moved into the individual backend drivers +- parameter type "mode" introduced. Templatechecker reads available database modes individually for configured backend. +- docu page design>icons shows all icons in dropins/icons/ws (or .../sw, whatever is configured) +- Highcharts module boost.js is activated on demand for series with 5000 or more data points to speed up rendering (plot.period, plot.sparkline). In plot.xyplot we can not do this since there is no size info. But most likely list items with xy-data are not so big. +- templatechecker now checks the twig syntax of html pages +- console.log is activated in cache mode if parameter "debug" is set to "1" in config.ini +- active elements are allowed on menu buttons in rooms_menu +- offline driver initializes UZSU data for items ending on ".uzsu" if no data are available +- import of black/white jQuery mobile mini icons moved from root.html into .css (for preparation of dark mode support) +- all examples adapted to support dark mode (v3.3 icon handling in menu.html and sides menus) +- use global namespace with native php functions profiting from OpCache (for faster code execution) +- added "advanced" zoom for multiple x axes +- avoid minifying of already minified highcharts files which throws errors in cache mode on new highcharts versions +- templatechecker now directly checks system requirements on page show - not any more only after starting the complete test program +- language support for system checks, lib.updatecheck(), templatechecker and widget assistant +- improved location search for weather services avoids display of company names instead of city names + +### Updated Libraries +- Twig template engine: manually inserted security fixes (v1.44.6 -> v1.44.8) and CS (Coding Standard) fixes as preparation for php 8.4 release +- ICS Parser v3.4.1 + +### Deprecated + +### Removed Features +- old widget pathnames w/o namespace (deprecated in v3.3) + +### Fixed Bugs +- system page was not shown due to missing infoblock.html if pages were configured to "Smarthome" but pages had not been created yet by the "smartvisu" plugin of smarthomeNG. +- behaviour of device.uzsugraph interpolation style was inconsistent if more than one uzsugraph widget was on a page. +- device.uzsugraph threw an error while a point was dragged +- status.activelist did not display texts if no svg icon was selected, i.e. the default icon "trans.png" was displayed +- corrected background image name "scale_pallets.png" to "scale_pellets.png" +- basic.print did not print timestamps correctly as dates and was not able to colorize them +- links on same page with anchor did not work under all conditions, e.g. href="index.php?page=myPage&anchor=myAnchor" +- config page overwrote the global driver configuration when called from docu or example pages +- small icons on tiles in quad design got too big in v3.3 due to missing CSS definitions for SVG icon +- template checker did not check parameters in double quotes correctly +- template checker gave faulty replacement hint for removed widget basic.text +- template checker threw errors if an item property was not in the properties class +- plot.period and plot.xyplot drawed the plots twice after series update +- notify.add() notification threw errors if message was not of type "text" +- basic.trigger showed oversized button instead of specified type "icon" +- device.uzsutable did not display correctly during and after supersize mode +- widgets used within listviews placed their icons not in the center +- clock.iconclock / icon.clock rounded the position of the short pointer to full hours. Now the pointer moves constantly like on any other analog clock. +- basic.shutter used same CSS classes for positive and negative blade angles in "half" mode. +- plot.period threw errors in advanced zoom mode if multiple x axes were given in the chartoptions parameter +- if item contained a stringified number with leading zero widget.set converted it back to numeric format +- some shortcuts in widget assistant did not work as expected + +### Known Bugs +- smartVISU versions 3.3.1 and older display incorrect version info in the update messages since the deprecated format has been removed from version-info.php +- background images defined with "url(myImageLocation)" - used e.g. on tabs - do not yet support dark mode. This can be individually corrected in visu.css. + + ## 3.4 ### New / Changed Widgets - quad.blind and quad.shutter can be configured to move the shutter on short- or longpress @@ -5,7 +95,7 @@ - plot.rtr now accepts Highcharts chartOptions object as parameter like plot.period - IDs are now optional in basic.roundslider and device.rtrslider - new dynamic icon "icon.slidinggate" -- new widget / dynamic icon "basic.skylight" for a roof window with closed / tilt status and shutter position (thanks to raman) +- new widget / dynamic icon "basic.skylight" for a roof window with closed / tilt status and shutter position (thanks to ramann) - id parameter is now fully optional in status toast, even with multiple toasts on a page - new "live" parameters in device.blind, device.dimmer, device.window, quad.dimmer, quad.color, quad.shutter, quad.blind and quad.playercontrol enable the usage of the live mode feture of basic.slider - new live / silent mode for basic.roundslider (similar to basic.slider) diff --git a/designs/FlatDarkBlue.css b/designs/FlatDarkBlue.css index 26954334e..6f5eebf71 100755 --- a/designs/FlatDarkBlue.css +++ b/designs/FlatDarkBlue.css @@ -12,6 +12,7 @@ * */ +@import '../icons/ws/jquery.mobile.icons.min.css'; /* Globals */ /* Font @@ -1239,7 +1240,6 @@ span.rs-number { top: -8px; left: -30px; font-size: 0.8em; - font-style: bold; font-family: Arial; color: #fff; } @@ -1298,5 +1298,4 @@ span.rs-number { } .rs-tooltip-text { font-size: 2.7em; - font-style: bold; } diff --git a/designs/cube.css b/designs/cube.css index e47cad5a1..5af57a327 100644 --- a/designs/cube.css +++ b/designs/cube.css @@ -12,6 +12,7 @@ * */ +@import '../icons/ws/jquery.mobile.icons.min.css'; /* Globals */ /* Font @@ -207,7 +208,7 @@ html .ui-alt-icon .ui-radio-off:after { width: 8px; height: 8px; border-width: 5px; - border-style: solid; + border-style: solid; } .ui-alt-icon.ui-btn.ui-radio-on:after, .ui-alt-icon .ui-btn.ui-radio-on:after { @@ -1077,7 +1078,7 @@ button[disabled], } /*********************************************************************************************************** -Non-themeroller styles +Non-themeroller styles ************************************************************************************************************/ /* Gradients as in jQuery Mobile 1.3.2 @@ -1777,7 +1778,7 @@ html .ui-btn.icon5, background-image: url(images/webkit_clock_ws.svg); } -/* jQuery Roundslider +/* jQuery Roundslider -----------------------------------------------------------------------------------------------------------*/ /*** SVG mode - theming - colors ***/ @@ -1806,14 +1807,13 @@ span.rs-number { top: -8px; left: -30px; font-size: 0.8em; - font-style: bold; font-family: Arial; color: #fff; } -.rs-bar.rs-custom .rs-seperator { - border: 2px solid #875010; - margin-left: -10px; +.rs-bar.rs-custom .rs-seperator { + border: 2px solid #875010; + margin-left: -10px; } .rs-bar.rs-custom_1 .rs-seperator { border: 1px solid #d7d7d7; @@ -1830,7 +1830,7 @@ span.rs-number { } .rs-handle { - background-color: #838383; + background-color: #838383; background-image: -webkit-gradient(linear, left top, left bottom, from( #444444 /*{a-bup-background-start}*/), to( #2d2d2d /*{a-bup-background-end}*/)); /* Saf4+, Chrome */ background-image: -webkit-linear-gradient( #444444 /*{a-bup-background-start}*/, #2d2d2d /*{a-bup-background-end}*/); /* Chrome 10+, Saf5.1+ */ background-image: -moz-linear-gradient( #444444 /*{a-bup-background-start}*/, #2d2d2d /*{a-bup-background-end}*/); /* FF3.6 */ @@ -1868,5 +1868,4 @@ span.rs-number { } .rs-tooltip-text { font-size: 2.7em; - font-style: bold; } diff --git a/designs/darkblue.css b/designs/darkblue.css index 74634ff4a..c5a0daf99 100755 --- a/designs/darkblue.css +++ b/designs/darkblue.css @@ -12,6 +12,7 @@ * */ +@import '../icons/ws/jquery.mobile.icons.min.css'; /* Globals */ /* Font @@ -101,10 +102,12 @@ label.ui-btn { .ui-btn { text-decoration: none !important; } +/** set button after checkbox to flat blue .ui-checkbox .ui-btn.ui-corner-all.ui-btn-inherit.ui-btn-icon-left.ui-checkbox-on { background-color: #22AADD; background-image: inherit; } +*/ /* Corner rounding -----------------------------------------------------------------------------------------------------------*/ @@ -1434,7 +1437,6 @@ span.rs-number { top: -8px; left: -30px; font-size: 0.8em; - font-style: bold; font-family: Arial; color: #fff; } @@ -1499,5 +1501,4 @@ span.rs-number { } .rs-tooltip-text { font-size: 2.7em; - font-style: bold; } diff --git a/designs/darkmode_blue.css b/designs/darkmode_blue.css new file mode 100644 index 000000000..fb43d7645 --- /dev/null +++ b/designs/darkmode_blue.css @@ -0,0 +1,12 @@ +/* +* +* This file activates the design 'darkblue.css' by default and switches to 'lightblue.css' if the device is not in dark mode. +* You can build your own pair of dark/light mode designs by changing the file names to the desired designs. +* It is recommended to place the file - e.g. 'mydarkmode.css' - in ./dropins/designs. Then, the folder structure has to be considered +* in the @import statements, e.g. @import '../../designs/greenhornet.css', (prefers-color-scheme: dark); +* +*/ + +@import 'darkblue.css'; +@import 'lightblue.css',(prefers-color-scheme: light); + diff --git a/designs/greenhornet.css b/designs/greenhornet.css index 6ec4a815f..74b10b465 100755 --- a/designs/greenhornet.css +++ b/designs/greenhornet.css @@ -12,6 +12,7 @@ * */ +@import '../icons/ws/jquery.mobile.icons.min.css'; /* Globals */ /* Font @@ -207,7 +208,7 @@ html .ui-alt-icon .ui-radio-off:after { width: 8px; height: 8px; border-width: 5px; - border-style: solid; + border-style: solid; } .ui-alt-icon.ui-btn.ui-radio-on:after, .ui-alt-icon .ui-btn.ui-radio-on:after { @@ -1749,7 +1750,7 @@ html .ui-btn.icon5, background-image: url(images/webkit_clock_ws.svg); } -/* jQuery Roundslider +/* jQuery Roundslider -----------------------------------------------------------------------------------------------------------*/ /*** SVG mode - theming - colors ***/ @@ -1778,14 +1779,13 @@ span.rs-number { top: -8px; left: -30px; font-size: 0.8em; - font-style: bold; font-family: Arial; color: #fff; } -.rs-bar.rs-custom .rs-seperator { - border: 2px solid #457703; - margin-left: -10px; +.rs-bar.rs-custom .rs-seperator { + border: 2px solid #457703; + margin-left: -10px; } .rs-bar.rs-custom_1 .rs-seperator { border: 1px solid #d7d7d7; @@ -1802,7 +1802,7 @@ span.rs-number { } .rs-handle { - background-color: #838383; + background-color: #838383; background-image: -webkit-gradient(linear, left top, left bottom, from( #444444 /*{a-bup-background-start}*/), to( #2d2d2d /*{a-bup-background-end}*/)); /* Saf4+, Chrome */ background-image: -webkit-linear-gradient( #444444 /*{a-bup-background-start}*/, #2d2d2d /*{a-bup-background-end}*/); /* Chrome 10+, Saf5.1+ */ background-image: -moz-linear-gradient( #444444 /*{a-bup-background-start}*/, #2d2d2d /*{a-bup-background-end}*/); /* FF3.6 */ @@ -1840,5 +1840,4 @@ span.rs-number { } .rs-tooltip-text { font-size: 2.7em; - font-style: bold; } diff --git a/designs/holo-inspired.css b/designs/holo-inspired.css index a9fcccb95..305965603 100644 --- a/designs/holo-inspired.css +++ b/designs/holo-inspired.css @@ -12,6 +12,7 @@ * */ +@import '../icons/sw/jquery.mobile.icons.min.css'; /* Globals */ /* Font @@ -207,7 +208,7 @@ html .ui-alt-icon .ui-radio-off:after { width: 8px; height: 8px; border-width: 5px; - border-style: solid; + border-style: solid; } .ui-alt-icon.ui-btn.ui-radio-on:after, .ui-alt-icon .ui-btn.ui-radio-on:after { @@ -952,9 +953,14 @@ html .ui-btn.icon5, .smartvisu { font-family: Dosis; } - -/* jQuery Roundslider +/** set color of selected autocomplete tooltip in widget assistant */ +#widget_assistant #selected_dict>strong { + color: #FFFFFF; +} + + +/* jQuery Roundslider -----------------------------------------------------------------------------------------------------------*/ /*** SVG mode - theming - colors ***/ @@ -985,14 +991,13 @@ span.rs-number { top: -8px; left: -30px; font-size: 0.8em; - font-style: bold; font-family: Arial; color: #000; } -.rs-bar.rs-custom .rs-seperator { - border: 2px solid #dddddd; - margin-left: -10px; +.rs-bar.rs-custom .rs-seperator { + border: 2px solid #dddddd; + margin-left: -10px; } .rs-bar.rs-custom_1 .rs-seperator { border: 1px solid #d7d7d7; @@ -1009,7 +1014,7 @@ span.rs-number { } .rs-handle { - background-color: #838383; + background-color: #838383; background-image: -webkit-gradient(linear, left top, left bottom, from( #FFFFFF /*{a-bup-background-start}*/), to( #d9d9d9 /*{a-bup-background-end}*/)); /* Saf4+, Chrome */ background-image: -webkit-linear-gradient( #FFFFFF /*{a-bup-background-start}*/, #d9d9d9 /*{a-bup-background-end}*/); /* Chrome 10+, Saf5.1+ */ background-image: -moz-linear-gradient( #FFFFFF /*{a-bup-background-start}*/, #d9d9d9 /*{a-bup-background-end}*/); /* FF3.6 */ @@ -1047,6 +1052,5 @@ span.rs-number { } .rs-tooltip-text { font-size: 2.7em; - font-style: bold; } - \ No newline at end of file + diff --git a/designs/ice.css b/designs/ice.css index 2d28d8558..9380c5cda 100644 --- a/designs/ice.css +++ b/designs/ice.css @@ -12,6 +12,7 @@ * */ +@import '../icons/sw/jquery.mobile.icons.min.css'; /* Globals */ /* Font @@ -1807,7 +1808,12 @@ html .ui-btn.icon5, background-image: url(images/webkit_clock_ws.svg); } -/* jQuery Roundslider +/** set color of selected autocomplete tooltip in widget assistant */ +#widget_assistant #selected_dict>strong { + color: #FFFFFF; +} + +/* jQuery Roundslider -----------------------------------------------------------------------------------------------------------*/ /*** SVG mode - theming - colors ***/ @@ -1836,14 +1842,13 @@ span.rs-number { top: -8px; left: -30px; font-size: 0.8em; - font-style: bold; font-family: Arial; color: #000; } -.rs-bar.rs-custom .rs-seperator { - border: 2px solid #456f9a; - margin-left: -10px; +.rs-bar.rs-custom .rs-seperator { + border: 2px solid #456f9a; + margin-left: -10px; } .rs-bar.rs-custom_1 .rs-seperator { border: 1px solid #d7d7d7; @@ -1860,7 +1865,7 @@ span.rs-number { } .rs-handle { - background-color: #838383; + background-color: #838383; background-image: -webkit-gradient(linear, left top, left bottom, from( #FFFFFF /*{a-bup-background-start}*/), to( #d9d9d9 /*{a-bup-background-end}*/)); /* Saf4+, Chrome */ background-image: -webkit-linear-gradient( #FFFFFF /*{a-bup-background-start}*/, #d9d9d9 /*{a-bup-background-end}*/); /* Chrome 10+, Saf5.1+ */ background-image: -moz-linear-gradient( #FFFFFF /*{a-bup-background-start}*/, #d9d9d9 /*{a-bup-background-end}*/); /* FF3.6 */ @@ -1898,5 +1903,4 @@ span.rs-number { } .rs-tooltip-text { font-size: 2.7em; - font-style: bold; } diff --git a/designs/lightblue.css b/designs/lightblue.css new file mode 100644 index 000000000..acda6ddac --- /dev/null +++ b/designs/lightblue.css @@ -0,0 +1,1565 @@ +/*! +* jQuery Mobile 1.4.5 +* Git HEAD hash: 68e55e78b292634d3991c795f06f5e37a512decc <> Date: Fri Oct 31 2014 17:33:30 UTC +* http://jquerymobile.com +* +* Copyright 2010, 2014 jQuery Foundation, Inc. and othercontributors +* Released under the MIT license. +* http://jquery.org/license +* +* @default design_icon0 icons/sw/ +* @default design_icon1 icons/bl/ +* +*/ + +@import '../icons/sw/jquery.mobile.icons.min.css'; + +/* Globals */ +/* Font +-----------------------------------------------------------------------------------------------------------*/ +.ui-body-a, +.ui-page-theme-a .ui-body-inherit, +html .ui-bar-a .ui-body-inherit, +html .ui-body-a .ui-body-inherit, +html body .ui-group-theme-a .ui-body-inherit, +html .ui-panel-page-container-a { + background: #ffffff /*{a-body-background-color}*/; + border-color: #dddddd /*{a-body-border}*/; + color: #333333 /*{a-body-color}*/; + text-shadow: 0 /*{a-body-shadow-x}*/ -1px /*{a-body-shadow-y}*/ 1px /*{a-body-shadow-radius}*/ #f3f3f3 /*{a-body-shadow-color}*/; + /* Copied from 1.3 custom theme: */ + background-image: -webkit-gradient(linear, left top, left bottom, from( #f8f8f8 ), to( #dddddd )); /* Saf4+, Chrome */ + background-image: -webkit-linear-gradient( #f8f8f8, #dddddd ); /* Chrome 10+, Saf5.1+ */ + background-image: -moz-linear-gradient( #f8f8f8, #dddddd ); /* FF3.6 */ + background-image: -ms-linear-gradient( #f8f8f8, #dddddd ); /* IE10 */ + background-image: -o-linear-gradient( #f8f8f8, #dddddd ); /* Opera 11.10+ */ + background-image: linear-gradient( #f8f8f8, #dddddd ); +} +html { + font-size: 100%; +} +body, +input, +select, +textarea, +button, +.ui-btn { + font-size: 1em; + line-height: 1.3; + font-family: Dosis, Helvetica, Arial, sans-serif /*{global-font-family}*/; +} +legend, +.ui-input-text input, +.ui-input-search input { + color: inherit; + text-shadow: inherit; +} +/* Form labels (overrides font-weight bold in bars, and mini font-size) */ +.ui-mobile label, +div.ui-controlgroup-label { + font-weight: normal; + font-size: 14px; +} +/* Separators +-----------------------------------------------------------------------------------------------------------*/ +/* Field contain separator (< 28em) */ +.ui-field-contain { + border-bottom-color: #828282; + border-bottom-color: rgba(0,0,0,.15); + border-bottom-width: 1px; + border-bottom-style: solid; +} +/* Table opt-in classes: strokes between each row, and alternating row stripes */ +/* Classes table-stroke and table-stripe are deprecated in 1.4. */ +.table-stroke thead th, +.table-stripe thead th, +.table-stripe tbody tr:last-child { + border-bottom: 1px solid #d6d6d6; /* non-RGBA fallback */ + border-bottom: 1px solid rgba(0,0,0,.1); +} +.table-stroke tbody th, +.table-stroke tbody td { + border-bottom: 1px solid #e6e6e6; /* non-RGBA fallback */ + border-bottom: 1px solid rgba(0,0,0,.05); +} +.table-stripe.table-stroke tbody tr:last-child th, +.table-stripe.table-stroke tbody tr:last-child td { + border-bottom: 0; +} +.table-stripe tbody tr:nth-child(odd) td, +.table-stripe tbody tr:nth-child(odd) th { + background-color: #eeeeee; /* non-RGBA fallback */ + background-color: rgba(0,0,0,.04); +} +/* Buttons +-----------------------------------------------------------------------------------------------------------*/ +.ui-btn, +label.ui-btn { + font-weight: bold; + border-width: 1px; + border-style: solid; +} +.ui-btn { + text-decoration: none !important; +} +/** set button after checkbox to flat blue +.ui-checkbox .ui-btn.ui-corner-all.ui-btn-inherit.ui-btn-icon-left.ui-checkbox-on { + background-color: #6BABFF; + background-image: inherit; +} +*/ + +/* Corner rounding +-----------------------------------------------------------------------------------------------------------*/ +/* Class ui-btn-corner-all deprecated in 1.4 */ +.ui-corner-all { + -webkit-border-radius: .6em /*{global-radii-blocks}*/; + border-radius: .6em /*{global-radii-blocks}*/; +} +/* Buttons */ +.ui-btn-corner-all, +.ui-btn.ui-corner-all, +/* Slider track */ +.ui-slider-track.ui-corner-all, +/* Flipswitch */ +.ui-flipswitch.ui-corner-all, +/* Count bubble */ +.ui-li-count { + -webkit-border-radius: .3125em /*{global-radii-buttons}*/; + border-radius: .3125em /*{global-radii-buttons}*/; +} +/* Icon-only buttons */ +.ui-btn-icon-notext.ui-btn-corner-all, +.ui-btn-icon-notext.ui-corner-all { + /*-webkit-border-radius: .5em;*/ + /*border-radius: .5em;*/ +} +/* Radius clip workaround for cleaning up corner trapping */ +.ui-btn-corner-all, +.ui-corner-all { + -webkit-background-clip: padding; + background-clip: padding-box; +} +/* Popup arrow */ +.ui-popup.ui-corner-all > .ui-popup-arrow-guide { + left: .6em /*{global-radii-blocks}*/; + right: .6em /*{global-radii-blocks}*/; + top: .6em /*{global-radii-blocks}*/; + bottom: .6em /*{global-radii-blocks}*/; +} +/* Shadow +-----------------------------------------------------------------------------------------------------------*/ +.ui-shadow { + -webkit-box-shadow: 0 1px 3px /*{global-box-shadow-size}*/ rgba(0,0,0,.2) /*{global-box-shadow-color}*/; + -moz-box-shadow: 0 1px 3px /*{global-box-shadow-size}*/ rgba(0,0,0,.2) /*{global-box-shadow-color}*/; + box-shadow: 0 1px 3px /*{global-box-shadow-size}*/ rgba(0,0,0,.2) /*{global-box-shadow-color}*/; +} +.ui-shadow-inset { + -webkit-box-shadow: inset 0 1px 3px /*{global-box-shadow-size}*/ rgba(0,0,0,.2) /*{global-box-shadow-color}*/; + -moz-box-shadow: inset 0 1px 3px /*{global-box-shadow-size}*/ rgba(0,0,0,.2) /*{global-box-shadow-color}*/; + box-shadow: inset 0 1px 3px /*{global-box-shadow-size}*/ rgba(0,0,0,.2) /*{global-box-shadow-color}*/; +} +.ui-overlay-shadow { + -webkit-box-shadow: 0 0 12px rgba(0,0,0,.6); + -moz-box-shadow: 0 0 12px rgba(0,0,0,.6); + box-shadow: 0 0 12px rgba(0,0,0,.6); +} +/* Icons +-----------------------------------------------------------------------------------------------------------*/ +.ui-btn-icon-left:after, +.ui-btn-icon-right:after, +.ui-btn-icon-top:after, +.ui-btn-icon-bottom:after, +.ui-btn-icon-notext:after { + background-color: #666 /*{global-icon-color}*/; + background-color: rgba(255,255,255,.04) /*{global-icon-disc}*/; + background-position: center center; + background-repeat: no-repeat; + -webkit-border-radius: .5em; + border-radius: .5em; +} +/* Alt icons */ +.ui-alt-icon.ui-btn:after, +.ui-alt-icon .ui-btn:after, +html .ui-alt-icon.ui-checkbox-off:after, +html .ui-alt-icon.ui-radio-off:after, +html .ui-alt-icon .ui-checkbox-off:after, +html .ui-alt-icon .ui-radio-off:after { + background-color: #666 /*{global-icon-color}*/; + background-color: rgba(25, 181, 240, 0.43) /*{global-icon-disc}*/; +} +/* No disc */ +.ui-nodisc-icon.ui-btn:after, +.ui-nodisc-icon .ui-btn:after { + background-color: transparent; +} +/* Icon shadow */ +.ui-shadow-icon.ui-btn:after, +.ui-shadow-icon .ui-btn:after { + -webkit-box-shadow: 0 1px 0 rgba(255,255,255,.3) /*{global-icon-shadow}*/; + -moz-box-shadow: 0 1px 0 rgba(255,255,255,.3) /*{global-icon-shadow}*/; + box-shadow: 0 1px 0 rgba(255,255,255,.3) /*{global-icon-shadow}*/; +} +/* Checkbox and radio */ +.ui-btn.ui-checkbox-off:after, +.ui-btn.ui-checkbox-on:after, +.ui-btn.ui-radio-off:after, +.ui-btn.ui-radio-on:after { + display: block; + width: 18px; + height: 18px; + margin: -9px 2px 0 2px; +} +.ui-checkbox-off:after, +.ui-btn.ui-radio-off:after { + filter: Alpha(Opacity=30); + opacity: .3; +} +.ui-btn.ui-checkbox-off:after, +.ui-btn.ui-checkbox-on:after { + -webkit-border-radius: .1875em; + border-radius: .1875em; +} +.ui-btn.ui-checkbox-off:after { + background-color: #666; + background-color: rgba(0,0,0,.3); +} +.ui-radio .ui-btn.ui-radio-on:after { + background-image: none; + background-color: #333333; + width: 8px; + height: 8px; + border-width: 5px; + border-style: solid; +} +.ui-alt-icon.ui-btn.ui-radio-on:after, +.ui-alt-icon .ui-btn.ui-radio-on:after { + background-color: #000; +} +/* Loader */ +.ui-icon-loading { + background: url("images/ajax-loader.gif"); + background-size: 2.875em 2.875em; +} +/* Swatches */ +/* A +-----------------------------------------------------------------------------------------------------------*/ +/* Bar: Toolbars, dividers, slider track */ +.ui-bar-a, +.ui-page-theme-a .ui-bar-inherit, +html .ui-bar-a .ui-bar-inherit, +html .ui-body-a .ui-bar-inherit, +html body .ui-group-theme-a .ui-bar-inherit { + background-color: #f5f5f5 /*{a-bar-background-color}*/; + border-color: #c9c9c9 /*{a-bar-border}*/; + color: #333333 /*{a-bar-color}*/; + text-shadow: 0 /*{a-bar-shadow-x}*/ 1px /*{a-bar-shadow-y}*/ 0 /*{a-bar-shadow-radius}*/ #dddddd /*{a-bar-shadow-color}*/; + font-weight: bold; + background-image: -webkit-gradient(linear, left top, left bottom, from( #f8f8f8 ), to( #dddddd )); /* Saf4+, Chrome */ + background-image: -webkit-linear-gradient( #f8f8f8, #dddddd ); /* Chrome 10+, Saf5.1+ */ + background-image: -moz-linear-gradient( #f8f8f8, #dddddd ); /* FF3.6 */ + background-image: -ms-linear-gradient( #f8f8f8, #dddddd ); /* IE10 */ + background-image: -o-linear-gradient( #f8f8f8, #dddddd ); /* Opera 11.10+ */ + background-image: linear-gradient( #f8f8f8, #dddddd ); +} +html .ui-slider-track.ui-shadow-inset.ui-bar-inherit.ui-corner-all.ui-vertical { + background-color: #f5f5f5 /*{a-bar-background-color}*/; + border-color: #c9c9c9 /*{a-bar-border}*/; + color: #333333 /*{a-bar-color}*/; + text-shadow: 0 /*{a-bar-shadow-x}*/ 1px /*{a-bar-shadow-y}*/ 0 /*{a-bar-shadow-radius}*/ #dddddd /*{a-bar-shadow-color}*/; + font-weight: bold; + background-image: -webkit-gradient(linear, left top, left bottom, from( #f8f8f8 ), to( #dddddd )); /* Saf4+, Chrome */ + background-image: -webkit-linear-gradient( left, #f8f8f8, #dddddd ); /* Chrome 10+, Saf5.1+ */ + background-image: -moz-linear-gradient( #f8f8f8, #dddddd ); /* FF3.6 */ + background-image: -ms-linear-gradient( right, #f8f8f8, #dddddd ); /* IE10 */ + background-image: -o-linear-gradient( right, #f8f8f8, #dddddd ); /* Opera 11.10+ */ + background-image: linear-gradient( to right, #f8f8f8, #dddddd ); +} +.ui-bar-a { + border-width: 1px; + border-style: solid; +} +/* Page and overlay */ +.ui-overlay-a, +.ui-page-theme-a, +.ui-page-theme-a .ui-panel-wrapper { + background-color: #f9f9f9 /*{a-page-background-color}*/; + border-color: #454545 /*{a-page-border}*/; + color: #333333 /*{a-page-color}*/; + text-shadow: 0 /*{a-page-shadow-x}*/ 1px /*{a-page-shadow-y}*/ 0 /*{a-page-shadow-radius}*/ #dddddd /*{a-page-shadow-color}*/; +} +/* Body: Read-only lists, text inputs, collapsible content */ +.ui-body-a, +.ui-page-theme-a .ui-body-inherit, +html .ui-bar-a .ui-body-inherit, +html .ui-body-a .ui-body-inherit, +html body .ui-group-theme-a .ui-body-inherit, +html .ui-panel-page-container-a { + background-color: #2a2a2a /*{a-body-background-color}*/; + border-color: #f5f5f5 /*{a-body-border}*/; + color: #333333 /*{a-body-color}*/; + text-shadow: 0 /*{a-body-shadow-x}*/ 1px /*{a-body-shadow-y}*/ 0 /*{a-body-shadow-radius}*/ #dddddd /*{a-body-shadow-color}*/; +} +.ui-body-a { + border-width: 1px; + border-style: solid; +} +/* Links */ +.ui-page-theme-a a, +html .ui-bar-a a, +html .ui-body-a a, +html body .ui-group-theme-a a { + color: #000000 /*{a-link-color}*/; + font-weight: bold; +} +.ui-page-theme-a a:visited, +html .ui-bar-a a:visited, +html .ui-body-a a:visited, +html body .ui-group-theme-a a:visited { + color: #000000 /*{a-link-visited}*/; +} +.ui-page-theme-a a:hover, +html .ui-bar-a a:hover, +html .ui-body-a a:hover, +html body .ui-group-theme-a a:hover { + color: #0088bb /*{a-link-hover}*/; +} +.ui-page-theme-a a:active, +html .ui-bar-a a:active, +html .ui-body-a a:active, +html body .ui-group-theme-a a:active { + color: #0088bb /*{a-link-active}*/; +} +/* Button up */ +.ui-page-theme-a .ui-btn, +html .ui-bar-a .ui-btn, +html .ui-body-a .ui-btn, +html body .ui-group-theme-a .ui-btn, +html head + body .ui-btn.ui-btn-a, +/* Button visited */ +.ui-page-theme-a .ui-btn:visited, +html .ui-bar-a .ui-btn:visited, +html .ui-body-a .ui-btn:visited, +html body .ui-group-theme-a .ui-btn:visited, +html head + body .ui-btn.ui-btn-a:visited { + background-color: #ffffff /*{a-bup-background-color}*/; + border-color: #dddddd /*{a-bup-border}*/; + color: #333333 /*{a-bup-color}*/; + text-shadow: 0 /*{a-bup-shadow-x}*/ 1px /*{a-bup-shadow-y}*/ 0 /*{a-bup-shadow-radius}*/ #dddddd /*{a-bup-shadow-color}*/; + background-image: -webkit-gradient(linear, left top, left bottom, from( #f9f9f9 /*{a-bup-background-start}*/), to( #a6a6a6 /*{a-bup-background-end}*/)); /* Saf4+, Chrome */ + background-image: -webkit-linear-gradient( #f9f9f9 /*{a-bup-background-start}*/, #a6a6a6 /*{a-bup-background-end}*/); /* Chrome 10+, Saf5.1+ */ + background-image: -moz-linear-gradient( #f9f9f9 /*{a-bup-background-start}*/, #a6a6a6 /*{a-bup-background-end}*/); /* FF3.6 */ + background-image: -ms-linear-gradient( #f9f9f9 /*{a-bup-background-start}*/, #a6a6a6 /*{a-bup-background-end}*/); /* IE10 */ + background-image: -o-linear-gradient( #f9f9f9 /*{a-bup-background-start}*/, #a6a6a6 /*{a-bup-background-end}*/); /* Opera 11.10+ */ + background-image: linear-gradient( #f9f9f9 /*{a-bup-background-start}*/, #a6a6a6 /*{a-bup-background-end}*/); +} +/* Button hover */ +.ui-page-theme-a .ui-btn:hover, +html .ui-bar-a .ui-btn:hover, +html .ui-body-a .ui-btn:hover, +html body .ui-group-theme-a .ui-btn:hover, +html head + body .ui-btn.ui-btn-a:hover { + background-color: #ededed /*{a-bhover-background-color}*/; + border-color: #dddddd /*{a-bhover-border}*/; + color: #333333 /*{a-bhover-color}*/; + text-shadow: 0 /*{a-bhover-shadow-x}*/ 1px /*{a-bhover-shadow-y}*/ 0 /*{a-bhover-shadow-radius}*/ #dddddd /*{a-bhover-shadow-color}*/; + background-image: -webkit-gradient(linear, left top, left bottom, from( #ededed /*{a-bhover-background-start}*/), to( #d3d3d3 /*{a-bhover-background-end}*/)); /* Saf4+, Chrome */ + background-image: -webkit-linear-gradient( #ededed /*{a-bhover-background-start}*/, #d3d3d3 /*{a-bhover-background-end}*/); /* Chrome 10+, Saf5.1+ */ + background-image: -moz-linear-gradient( #ededed /*{a-bhover-background-start}*/, #d3d3d3 /*{a-bhover-background-end}*/); /* FF3.6 */ + background-image: -ms-linear-gradient( #ededed /*{a-bhover-background-start}*/, #d3d3d3 /*{a-bhover-background-end}*/); /* IE10 */ + background-image: -o-linear-gradient( #ededed /*{a-bhover-background-start}*/, #d3d3d3 /*{a-bhover-background-end}*/); /* Opera 11.10+ */ + background-image: linear-gradient( #ededed /*{a-bhover-background-start}*/, #d3d3d3 /*{a-bhover-background-end}*/); +} +/* Button down */ +.ui-page-theme-a .ui-btn:active, +html .ui-bar-a .ui-btn:active, +html .ui-body-a .ui-btn:active, +html body .ui-group-theme-a .ui-btn:active, +html head + body .ui-btn.ui-btn-a:active { + background-color: #969696 /*{a-bdown-background-color}*/; + border-color: #dddddd /*{a-bdown-border}*/; + color: #333333 /*{a-bdown-color}*/; + text-shadow: 0 /*{a-bdown-shadow-x}*/ 1px /*{a-bdown-shadow-y}*/ 0 /*{a-bdown-shadow-radius}*/ #dddddd /*{a-bdown-shadow-color}*/; + background-image: -webkit-gradient(linear, left top, left bottom, from( #8e8e8e /*{a-bdown-background-start}*/), to( #a2a2a2 /*{a-bdown-background-end}*/)); /* Saf4+, Chrome */ + background-image: -webkit-linear-gradient( #8e8e8e /*{a-bdown-background-start}*/, #a2a2a2 /*{a-bdown-background-end}*/); /* Chrome 10+, Saf5.1+ */ + background-image: -moz-linear-gradient( #8e8e8e /*{a-bdown-background-start}*/, #a2a2a2 /*{a-bdown-background-end}*/); /* FF3.6 */ + background-image: -ms-linear-gradient( #8e8e8e /*{a-bdown-background-start}*/, #a2a2a2 /*{a-bdown-background-end}*/); /* IE10 */ + background-image: -o-linear-gradient( #8e8e8e /*{a-bdown-background-start}*/, #a2a2a2 /*{a-bdown-background-end}*/); /* Opera 11.10+ */ + background-image: linear-gradient( #8e8e8e /*{a-bdown-background-start}*/, #a2a2a2 /*{a-bdown-background-end}*/); +} +/* Active button */ +.ui-page-theme-a .ui-btn.ui-btn-active, +html .ui-bar-a .ui-btn.ui-btn-active, +html .ui-body-a .ui-btn.ui-btn-active, +html body .ui-group-theme-a .ui-btn.ui-btn-active, +html head + body .ui-btn.ui-btn-a.ui-btn-active, +/* Active checkbox icon */ +.ui-page-theme-a .ui-checkbox-on:after, +html .ui-bar-a .ui-checkbox-on:after, +html .ui-body-a .ui-checkbox-on:after, +html body .ui-group-theme-a .ui-checkbox-on:after, +.ui-btn.ui-checkbox-on.ui-btn-a:after, +/* Active flipswitch background */ +.ui-page-theme-a .ui-flipswitch-active, +html .ui-bar-a .ui-flipswitch-active, +html .ui-body-a .ui-flipswitch-active, +html body .ui-group-theme-a .ui-flipswitch-active, +html body .ui-flipswitch.ui-bar-a.ui-flipswitch-active, +/* Active slider track */ +.ui-page-theme-a .ui-slider-track .ui-btn-active, +html .ui-bar-a .ui-slider-track .ui-btn-active, +html .ui-body-a .ui-slider-track .ui-btn-active, +html body .ui-group-theme-a .ui-slider-track .ui-btn-active, +html body div.ui-slider-track.ui-body-a .ui-btn-active { + background-color: #6BABFF /*{a-active-background-color}*/; + /*border-color: #125d79;*/ + color: #333333 /*{a-active-color}*/; + /*text-shadow: 0 1px 0 #0088bb;*/ + background-image: none; +} +/* Active radio button icon */ +.ui-page-theme-a .ui-radio-on:after, +html .ui-bar-a .ui-radio-on:after, +html .ui-body-a .ui-radio-on:after, +html body .ui-group-theme-a .ui-radio-on:after, +.ui-btn.ui-radio-on.ui-btn-a:after { + border-color: #6BABFF /*{a-active-background-color}*/; +} +/* Focus */ +.ui-page-theme-a .ui-btn:focus, +html .ui-bar-a .ui-btn:focus, +html .ui-body-a .ui-btn:focus, +html body .ui-group-theme-a .ui-btn:focus, +html head + body .ui-btn.ui-btn-a:focus, +/* Focus buttons and text inputs with div wrap */ +.ui-page-theme-a .ui-focus, +html .ui-bar-a .ui-focus, +html .ui-body-a .ui-focus, +html body .ui-group-theme-a .ui-focus, +html head + body .ui-btn-a.ui-focus, +html head + body .ui-body-a.ui-focus { + -webkit-box-shadow: 0 0 12px #6BABFF /*{a-active-background-color}*/; + -moz-box-shadow: 0 0 12px #6BABFF /*{a-active-background-color}*/; + box-shadow: 0 0 12px #6BABFF /*{a-active-background-color}*/; +} + +/* B +-----------------------------------------------------------------------------------------------------------*/ +/* Bar: Toolbars, dividers, slider track */ +.ui-bar-b, +.ui-page-theme-b .ui-bar-inherit, +html .ui-bar-b .ui-bar-inherit, +html .ui-body-b .ui-bar-inherit, +html body .ui-group-theme-b .ui-bar-inherit { + background-color: #5e87b0 /*{b-bar-background-color}*/; + border-color: #456f9a /*{b-bar-border}*/; + color: #ffffff /*{b-bar-color}*/; + text-shadow: 0 /*{b-bar-shadow-x}*/ 1px /*{b-bar-shadow-y}*/ 1px /*{b-bar-shadow-radius}*/ #3e6790 /*{b-bar-shadow-color}*/; + font-weight: bold; +} +.ui-bar-b { + border-width: 1px; + border-style: solid; +} +/* Page and overlay */ +.ui-overlay-b, +.ui-page-theme-b, +.ui-page-theme-b .ui-panel-wrapper { + background-color: #f2f2f2 /*{b-page-background-color}*/; + border-color: #858585 /*{b-page-border}*/; + color: #f3f3f3 /*{b-page-color}*/; + text-shadow: 0 /*{b-page-shadow-x}*/ 0 /*{b-page-shadow-y}*/ 0 /*{b-page-shadow-radius}*/ #eeeeee /*{b-page-shadow-color}*/; +} +/* Body: Read-only lists, text inputs, collapsible content */ +.ui-body-b, +.ui-page-theme-b .ui-body-inherit, +html .ui-bar-b .ui-body-inherit, +html .ui-body-b .ui-body-inherit, +html body .ui-group-theme-b .ui-body-inherit, +html .ui-panel-page-container-b { + background-color: #f2f2f2 /*{b-body-background-color}*/; + border-color: #858585 /*{b-body-border}*/; + color: #000000 /*{b-body-color}*/; + text-shadow: 0 /*{b-body-shadow-x}*/ 0 /*{b-body-shadow-y}*/ 0 /*{b-body-shadow-radius}*/ #eeeeee /*{b-body-shadow-color}*/; +} +.ui-body-b { + border-width: 1px; + border-style: solid; +} +/* Links */ +.ui-page-theme-b a, +html .ui-bar-b a, +html .ui-body-b a, +html body .ui-group-theme-b a { + color: #2489CE /*{b-link-color}*/; + font-weight: bold; +} +.ui-page-theme-b a:visited, +html .ui-bar-b a:visited, +html .ui-body-b a:visited, +html body .ui-group-theme-b a:visited { + color: #2489CE /*{b-link-visited}*/; +} +.ui-page-theme-b a:hover, +html .ui-bar-b a:hover, +html .ui-body-b a:hover, +html body .ui-group-theme-b a:hover { + color: #2489CE /*{b-link-hover}*/; +} +.ui-page-theme-b a:active, +html .ui-bar-b a:active, +html .ui-body-b a:active, +html body .ui-group-theme-b a:active { + color: #2489CE /*{b-link-active}*/; +} +/* Button up */ +.ui-page-theme-b .ui-btn, +html .ui-bar-b .ui-btn, +html .ui-body-b .ui-btn, +html body .ui-group-theme-b .ui-btn, +html head + body .ui-btn.ui-btn-b, +/* Button visited */ +.ui-page-theme-b .ui-btn:visited, +html .ui-bar-b .ui-btn:visited, +html .ui-body-b .ui-btn:visited, +html body .ui-group-theme-b .ui-btn:visited, +html head + body .ui-btn.ui-btn-b:visited { + background-color: #396b9e /*{b-bup-background-color}*/; + border-color: #044062 /*{b-bup-border}*/; + color: #000000 /*{b-bup-color}*/; + text-shadow: 0 /*{b-bup-shadow-x}*/ 1px /*{b-bup-shadow-y}*/ 1px /*{b-bup-shadow-radius}*/ #194b7e /*{b-bup-shadow-color}*/; +} +/* Button hover */ +.ui-page-theme-b .ui-btn:hover, +html .ui-bar-b .ui-btn:hover, +html .ui-body-b .ui-btn:hover, +html body .ui-group-theme-b .ui-btn:hover, +html head + body .ui-btn.ui-btn-b:hover { + background-color: #4b88b6 /*{b-bhover-background-color}*/; + border-color: #00415e /*{b-bhover-border}*/; + color: #808080 /*{b-bhover-color}*/; + text-shadow: 0 /*{b-bhover-shadow-x}*/ 1px /*{b-bhover-shadow-y}*/ 1px /*{b-bhover-shadow-radius}*/ #194b7e /*{b-bhover-shadow-color}*/; +} +/* Button down */ +.ui-page-theme-b .ui-btn:active, +html .ui-bar-b .ui-btn:active, +html .ui-body-b .ui-btn:active, +html body .ui-group-theme-b .ui-btn:active, +html head + body .ui-btn.ui-btn-b:active { + background-color: #4e89c5 /*{b-bdown-background-color}*/; + border-color: #225377 /*{b-bdown-border}*/; + color: #ffffff /*{b-bdown-color}*/; + text-shadow: 0 /*{b-bdown-shadow-x}*/ 1px /*{b-bdown-shadow-y}*/ 1px /*{b-bdown-shadow-radius}*/ #194b7e /*{b-bdown-shadow-color}*/; +} +/* Active button */ +.ui-page-theme-b .ui-btn.ui-btn-active, +html .ui-bar-b .ui-btn.ui-btn-active, +html .ui-body-b .ui-btn.ui-btn-active, +html body .ui-group-theme-b .ui-btn.ui-btn-active, +html head + body .ui-btn.ui-btn-b.ui-btn-active, +/* Active checkbox icon */ +.ui-page-theme-b .ui-checkbox-on:after, +html .ui-bar-b .ui-checkbox-on:after, +html .ui-body-b .ui-checkbox-on:after, +html body .ui-group-theme-b .ui-checkbox-on:after, +.ui-btn.ui-checkbox-on.ui-btn-b:after, +/* Active flipswitch background */ +.ui-page-theme-b .ui-flipswitch-active, +html .ui-bar-b .ui-flipswitch-active, +html .ui-body-b .ui-flipswitch-active, +html body .ui-group-theme-b .ui-flipswitch-active, +html body .ui-flipswitch.ui-bar-b.ui-flipswitch-active, +/* Active slider track */ +.ui-page-theme-b .ui-slider-track .ui-btn-active, +html .ui-bar-b .ui-slider-track .ui-btn-active, +html .ui-body-b .ui-slider-track .ui-btn-active, +html body .ui-group-theme-b .ui-slider-track .ui-btn-active, +html body div.ui-slider-track.ui-body-b .ui-btn-active { + background-color: #387bbe /*{b-active-background-color}*/; + border-color: #2373a5 /*{b-active-border}*/; + color: #ffffff /*{b-active-color}*/; + text-shadow: 0 /*{b-active-shadow-x}*/ 1px /*{b-active-shadow-y}*/ 0 /*{b-active-shadow-radius}*/ #3373a5 /*{b-active-shadow-color}*/; +} +/* Active radio button icon */ +.ui-page-theme-b .ui-radio-on:after, +html .ui-bar-b .ui-radio-on:after, +html .ui-body-b .ui-radio-on:after, +html body .ui-group-theme-b .ui-radio-on:after, +.ui-btn.ui-radio-on.ui-btn-b:after { + border-color: #387bbe /*{b-active-background-color}*/; +} +/* Focus */ +.ui-page-theme-b .ui-btn:focus, +html .ui-bar-b .ui-btn:focus, +html .ui-body-b .ui-btn:focus, +html body .ui-group-theme-b .ui-btn:focus, +html head + body .ui-btn.ui-btn-b:focus, +/* Focus buttons and text inputs with div wrap */ +.ui-page-theme-b .ui-focus, +html .ui-bar-b .ui-focus, +html .ui-body-b .ui-focus, +html body .ui-group-theme-b .ui-focus, +html head + body .ui-btn-b.ui-focus, +html head + body .ui-body-b.ui-focus { + -webkit-box-shadow: 0 0 12px #387bbe /*{b-active-background-color}*/; + -moz-box-shadow: 0 0 12px #387bbe /*{b-active-background-color}*/; + box-shadow: 0 0 12px #387bbe /*{b-active-background-color}*/; +} + + +/* C +-----------------------------------------------------------------------------------------------------------*/ +/* Bar: Toolbars, dividers, slider track */ +.ui-bar-c, +.ui-page-theme-c .ui-bar-inherit, +html .ui-bar-c .ui-bar-inherit, +html .ui-body-c .ui-bar-inherit, +html body .ui-group-theme-c .ui-bar-inherit { + background-color: #44739e /*{c-bar-background-color}*/; + border-color: #464646 /*{c-bar-border}*/; + color: #ffffff /*{c-bar-color}*/; + text-shadow: 0 /*{c-bar-shadow-x}*/ 1px /*{c-bar-shadow-y}*/ 1px /*{c-bar-shadow-radius}*/ #f9f9f9 /*{c-bar-shadow-color}*/; + font-weight: bold; +} +.ui-bar-c { + border-width: 1px; + border-style: solid; +} +/* Page and overlay */ +.ui-overlay-c, +.ui-page-theme-c, +.ui-page-theme-c .ui-panel-wrapper { + background-color: #f9f9f9 /*{c-page-background-color}*/; + border-color: #aaaaaa /*{c-page-border}*/; + color: #333333 /*{c-page-color}*/; + text-shadow: 0 /*{c-page-shadow-x}*/ 0 /*{c-page-shadow-y}*/ 0 /*{c-page-shadow-radius}*/ #ffffff /*{c-page-shadow-color}*/; +} +/* Body: Read-only lists, text inputs, collapsible content */ +.ui-body-c, +.ui-page-theme-c .ui-body-inherit, +html .ui-bar-c .ui-body-inherit, +html .ui-body-c .ui-body-inherit, +html body .ui-group-theme-c .ui-body-inherit, +html .ui-panel-page-container-c { + background-color: #44739e /*{c-body-background-color}*/; + border-color: #aaaaaa /*{c-body-border}*/; + color: #eeeeee /*{c-body-color}*/; + text-shadow: 0 /*{c-body-shadow-x}*/ 0 /*{c-body-shadow-y}*/ 0 /*{c-body-shadow-radius}*/ #ffffff /*{c-body-shadow-color}*/; +} +.ui-body-c { + border-width: 1px; + border-style: solid; +} +/* Links */ +.ui-page-theme-c a, +html .ui-bar-c a, +html .ui-body-c a, +html body .ui-group-theme-c a { + color: #2489CE /*{c-link-color}*/; + font-weight: bold; +} +.ui-page-theme-c a:visited, +html .ui-bar-c a:visited, +html .ui-body-c a:visited, +html body .ui-group-theme-c a:visited { + color: #2489CE /*{c-link-visited}*/; +} +.ui-page-theme-c a:hover, +html .ui-bar-c a:hover, +html .ui-body-c a:hover, +html body .ui-group-theme-c a:hover { + color: #2489CE /*{c-link-hover}*/; +} +.ui-page-theme-c a:active, +html .ui-bar-c a:active, +html .ui-body-c a:active, +html body .ui-group-theme-c a:active { + color: #2489CE /*{c-link-active}*/; +} +/* Button up */ +.ui-page-theme-c .ui-btn, +html .ui-bar-c .ui-btn, +html .ui-body-c .ui-btn, +html body .ui-group-theme-c .ui-btn, +html head + body .ui-btn.ui-btn-c, +/* Button visited */ +.ui-page-theme-c .ui-btn:visited, +html .ui-bar-c .ui-btn:visited, +html .ui-body-c .ui-btn:visited, +html body .ui-group-theme-c .ui-btn:visited, +html head + body .ui-btn.ui-btn-c:visited { + background-color: #eeeeee /*{c-bup-background-color}*/; + border-color: #cccccc /*{c-bup-border}*/; + color: #2F3E46 /*{c-bup-color}*/; + text-shadow: 0 /*{c-bup-shadow-x}*/ 1px /*{c-bup-shadow-y}*/ 0 /*{c-bup-shadow-radius}*/ #ffffff /*{c-bup-shadow-color}*/; +} +/* Button hover */ +.ui-page-theme-c .ui-btn:hover, +html .ui-bar-c .ui-btn:hover, +html .ui-body-c .ui-btn:hover, +html body .ui-group-theme-c .ui-btn:hover, +html head + body .ui-btn.ui-btn-c:hover { + background-color: #dfdfdf /*{c-bhover-background-color}*/; + border-color: #bbbbbb /*{c-bhover-border}*/; + color: ##2F3E46 /*{c-bhover-color}*/; + text-shadow: 0 /*{c-bhover-shadow-x}*/ 1px /*{c-bhover-shadow-y}*/ 0 /*{c-bhover-shadow-radius}*/ #ffffff /*{c-bhover-shadow-color}*/; +} +/* Button down */ +.ui-page-theme-c .ui-btn:active, +html .ui-bar-c .ui-btn:active, +html .ui-body-c .ui-btn:active, +html body .ui-group-theme-c .ui-btn:active, +html head + body .ui-btn.ui-btn-c:active { + background-color: #d6d6d6 /*{c-bdown-background-color}*/; + border-color: #bbbbbb /*{c-bdown-border}*/; + color: #2F3E46 /*{c-bdown-color}*/; + text-shadow: 0 /*{c-bdown-shadow-x}*/ 1px /*{c-bdown-shadow-y}*/ 0 /*{c-bdown-shadow-radius}*/ #ffffff /*{c-bdown-shadow-color}*/; +} +/* Active button */ +.ui-page-theme-c .ui-btn.ui-btn-active, +html .ui-bar-c .ui-btn.ui-btn-active, +html .ui-body-c .ui-btn.ui-btn-active, +html body .ui-group-theme-c .ui-btn.ui-btn-active, +html head + body .ui-btn.ui-btn-c.ui-btn-active, +/* Active checkbox icon */ +.ui-page-theme-c .ui-checkbox-on:after, +html .ui-bar-c .ui-checkbox-on:after, +html .ui-body-c .ui-checkbox-on:after, +html body .ui-group-theme-c .ui-checkbox-on:after, +.ui-btn.ui-checkbox-on.ui-btn-c:after, +/* Active flipswitch background */ +.ui-page-theme-c .ui-flipswitch-active, +html .ui-bar-c .ui-flipswitch-active, +html .ui-body-c .ui-flipswitch-active, +html body .ui-group-theme-c .ui-flipswitch-active, +html body .ui-flipswitch.ui-bar-c.ui-flipswitch-active, +/* Active slider track */ +.ui-page-theme-c .ui-slider-track .ui-btn-active, +html .ui-bar-c .ui-slider-track .ui-btn-active, +html .ui-body-c .ui-slider-track .ui-btn-active, +html body .ui-group-theme-c .ui-slider-track .ui-btn-active, +html body div.ui-slider-track.ui-body-c .ui-btn-active { + background-color: #387bbe /*{c-active-background-color}*/; + border-color: #2373a5 /*{c-active-border}*/; + color: #ffffff /*{c-active-color}*/; + text-shadow: 0 /*{c-active-shadow-x}*/ 1px /*{c-active-shadow-y}*/ 0 /*{c-active-shadow-radius}*/ #3373a5 /*{c-active-shadow-color}*/; +} +/* Active radio button icon */ +.ui-page-theme-c .ui-radio-on:after, +html .ui-bar-c .ui-radio-on:after, +html .ui-body-c .ui-radio-on:after, +html body .ui-group-theme-c .ui-radio-on:after, +.ui-btn.ui-radio-on.ui-btn-c:after { + border-color: #387bbe /*{c-active-background-color}*/; +} +/* Focus */ +.ui-page-theme-c .ui-btn:focus, +html .ui-bar-c .ui-btn:focus, +html .ui-body-c .ui-btn:focus, +html body .ui-group-theme-c .ui-btn:focus, +html head + body .ui-btn.ui-btn-c:focus, +/* Focus buttons and text inputs with div wrap */ +.ui-page-theme-c .ui-focus, +html .ui-bar-c .ui-focus, +html .ui-body-c .ui-focus, +html body .ui-group-theme-c .ui-focus, +html head + body .ui-btn-c.ui-focus, +html head + body .ui-body-c.ui-focus { + -webkit-box-shadow: 0 0 12px #387bbe /*{c-active-background-color}*/; + -moz-box-shadow: 0 0 12px #387bbe /*{c-active-background-color}*/; + box-shadow: 0 0 12px #387bbe /*{c-active-background-color}*/; +} + + +/* D +-----------------------------------------------------------------------------------------------------------*/ +/* Bar: Toolbars, dividers, slider track */ +.ui-bar-d, +.ui-page-theme-d .ui-bar-inherit, +html .ui-bar-d .ui-bar-inherit, +html .ui-body-d .ui-bar-inherit, +html body .ui-group-theme-d .ui-bar-inherit { + background-color: #bbbbbb /*{d-bar-background-color}*/; + border-color: #666666 /*{d-bar-border}*/; + color: #333333 /*{d-bar-color}*/; + text-shadow: 0 /*{d-bar-shadow-x}*/ 1px /*{d-bar-shadow-y}*/ 0 /*{d-bar-shadow-radius}*/ #eeeeee /*{d-bar-shadow-color}*/; + font-weight: bold; +} +.ui-bar-d { + border-width: 1px; + border-style: solid; +} +/* Page and overlay */ +.ui-overlay-d, +.ui-page-theme-d, +.ui-page-theme-d .ui-panel-wrapper { + background-color: #ffffff /*{d-page-background-color}*/; + border-color: #bbbbbb /*{d-page-border}*/; + color: #333333 /*{d-page-color}*/; + text-shadow: 0 /*{d-page-shadow-x}*/ 0 /*{d-page-shadow-y}*/ 0 /*{d-page-shadow-radius}*/ #ffffff /*{d-page-shadow-color}*/; +} +/* Body: Read-only lists, text inputs, collapsible content */ +.ui-body-d, +.ui-page-theme-d .ui-body-inherit, +html .ui-bar-d .ui-body-inherit, +html .ui-body-d .ui-body-inherit, +html body .ui-group-theme-d .ui-body-inherit, +html .ui-panel-page-container-d { + background-color: #ffffff /*{d-body-background-color}*/; + border-color: #bbbbbb /*{d-body-border}*/; + color: #333333 /*{d-body-color}*/; + text-shadow: 0 /*{d-body-shadow-x}*/ 0 /*{d-body-shadow-y}*/ 0 /*{d-body-shadow-radius}*/ #ffffff /*{d-body-shadow-color}*/; +} +.ui-body-d { + border-width: 1px; + border-style: solid; +} +/* Links */ +.ui-page-theme-d a, +html .ui-bar-d a, +html .ui-body-d a, +html body .ui-group-theme-d a { + color: #2489CE /*{d-link-color}*/; + font-weight: bold; +} +.ui-page-theme-d a:visited, +html .ui-bar-d a:visited, +html .ui-body-d a:visited, +html body .ui-group-theme-d a:visited { + color: #2489CE /*{d-link-visited}*/; +} +.ui-page-theme-d a:hover, +html .ui-bar-d a:hover, +html .ui-body-d a:hover, +html body .ui-group-theme-d a:hover { + color: #2489CE /*{d-link-hover}*/; +} +.ui-page-theme-d a:active, +html .ui-bar-d a:active, +html .ui-body-d a:active, +html body .ui-group-theme-d a:active { + color: #2489CE /*{d-link-active}*/; +} +/* Button up */ +.ui-page-theme-d .ui-btn, +html .ui-bar-d .ui-btn, +html .ui-body-d .ui-btn, +html body .ui-group-theme-d .ui-btn, +html head + body .ui-btn.ui-btn-d, +/* Button visited */ +.ui-page-theme-d .ui-btn:visited, +html .ui-bar-d .ui-btn:visited, +html .ui-body-d .ui-btn:visited, +html body .ui-group-theme-d .ui-btn:visited, +html head + body .ui-btn.ui-btn-d:visited { + background-color: #ffffff /*{d-bup-background-color}*/; + border-color: #bbbbbb /*{d-bup-border}*/; + color: #333333 /*{d-bup-color}*/; + text-shadow: 0 /*{d-bup-shadow-x}*/ 1px /*{d-bup-shadow-y}*/ 0 /*{d-bup-shadow-radius}*/ #ffffff /*{d-bup-shadow-color}*/; +} +/* Button hover */ +.ui-page-theme-d .ui-btn:hover, +html .ui-bar-d .ui-btn:hover, +html .ui-body-d .ui-btn:hover, +html body .ui-group-theme-d .ui-btn:hover, +html head + body .ui-btn.ui-btn-d:hover { + background-color: #eeeeee /*{d-bhover-background-color}*/; + border-color: #aaaaaa /*{d-bhover-border}*/; + color: #333333 /*{d-bhover-color}*/; + text-shadow: 0 /*{d-bhover-shadow-x}*/ 1px /*{d-bhover-shadow-y}*/ 0 /*{d-bhover-shadow-radius}*/ #ffffff /*{d-bhover-shadow-color}*/; +} +/* Button down */ +.ui-page-theme-d .ui-btn:active, +html .ui-bar-d .ui-btn:active, +html .ui-body-d .ui-btn:active, +html body .ui-group-theme-d .ui-btn:active, +html head + body .ui-btn.ui-btn-d:active { + background-color: #eeeeee /*{d-bdown-background-color}*/; + border-color: #aaaaaa /*{d-bdown-border}*/; + color: #333333 /*{d-bdown-color}*/; + text-shadow: 0 /*{d-bdown-shadow-x}*/ 1px /*{d-bdown-shadow-y}*/ 0 /*{d-bdown-shadow-radius}*/ #ffffff /*{d-bdown-shadow-color}*/; +} +/* Active button */ +.ui-page-theme-d .ui-btn.ui-btn-active, +html .ui-bar-d .ui-btn.ui-btn-active, +html .ui-body-d .ui-btn.ui-btn-active, +html body .ui-group-theme-d .ui-btn.ui-btn-active, +html head + body .ui-btn.ui-btn-d.ui-btn-active, +/* Active checkbox icon */ +.ui-page-theme-d .ui-checkbox-on:after, +html .ui-bar-d .ui-checkbox-on:after, +html .ui-body-d .ui-checkbox-on:after, +html body .ui-group-theme-d .ui-checkbox-on:after, +.ui-btn.ui-checkbox-on.ui-btn-d:after, +/* Active flipswitch background */ +.ui-page-theme-d .ui-flipswitch-active, +html .ui-bar-d .ui-flipswitch-active, +html .ui-body-d .ui-flipswitch-active, +html body .ui-group-theme-d .ui-flipswitch-active, +html body .ui-flipswitch.ui-bar-d.ui-flipswitch-active, +/* Active slider track */ +.ui-page-theme-d .ui-slider-track .ui-btn-active, +html .ui-bar-d .ui-slider-track .ui-btn-active, +html .ui-body-d .ui-slider-track .ui-btn-active, +html body .ui-group-theme-d .ui-slider-track .ui-btn-active, +html body div.ui-slider-track.ui-body-d .ui-btn-active { + background-color: #387bbe /*{d-active-background-color}*/; + border-color: #2373a5 /*{d-active-border}*/; + color: #ffffff /*{d-active-color}*/; + text-shadow: 0 /*{d-active-shadow-x}*/ 1px /*{d-active-shadow-y}*/ 1px /*{d-active-shadow-radius}*/ #3373a5 /*{d-active-shadow-color}*/; +} +/* Active radio button icon */ +.ui-page-theme-d .ui-radio-on:after, +html .ui-bar-d .ui-radio-on:after, +html .ui-body-d .ui-radio-on:after, +html body .ui-group-theme-d .ui-radio-on:after, +.ui-btn.ui-radio-on.ui-btn-d:after { + border-color: #387bbe /*{d-active-background-color}*/; +} +/* Focus */ +.ui-page-theme-d .ui-btn:focus, +html .ui-bar-d .ui-btn:focus, +html .ui-body-d .ui-btn:focus, +html body .ui-group-theme-d .ui-btn:focus, +html head + body .ui-btn.ui-btn-d:focus, +/* Focus buttons and text inputs with div wrap */ +.ui-page-theme-d .ui-focus, +html .ui-bar-d .ui-focus, +html .ui-body-d .ui-focus, +html body .ui-group-theme-d .ui-focus, +html head + body .ui-btn-d.ui-focus, +html head + body .ui-body-d.ui-focus { + -webkit-box-shadow: 0 0 12px #387bbe /*{d-active-background-color}*/; + -moz-box-shadow: 0 0 12px #387bbe /*{d-active-background-color}*/; + box-shadow: 0 0 12px #387bbe /*{d-active-background-color}*/; +} + + +/* E +-----------------------------------------------------------------------------------------------------------*/ +/* Bar: Toolbars, dividers, slider track */ +.ui-bar-e, +.ui-page-theme-e .ui-bar-inherit, +html .ui-bar-e .ui-bar-inherit, +html .ui-body-e .ui-bar-inherit, +html body .ui-group-theme-e .ui-bar-inherit { + background-color: #44739e /*{e-bar-background-color}*/; + border-color: #F7C942 /*{e-bar-border}*/; + color: #eeeeee /*{e-bar-color}*/; + text-shadow: 0 /*{e-bar-shadow-x}*/ 1px /*{e-bar-shadow-y}*/ 0 /*{e-bar-shadow-radius}*/ #ffffff /*{e-bar-shadow-color}*/; + font-weight: bold; +} +.ui-bar-e { + border-width: 1px; + border-style: solid; +} +/* Page and overlay */ +.ui-overlay-e, +.ui-page-theme-e, +.ui-page-theme-e .ui-panel-wrapper { + background-color: #fff9df /*{e-page-background-color}*/; + border-color: #F7C942 /*{e-page-border}*/; + color: #333333 /*{e-page-color}*/; + text-shadow: 0 /*{e-page-shadow-x}*/ 0 /*{e-page-shadow-y}*/ 0 /*{e-page-shadow-radius}*/ #ffffff /*{e-page-shadow-color}*/; +} +/* Body: Read-only lists, text inputs, collapsible content */ +.ui-body-e, +.ui-page-theme-e .ui-body-inherit, +html .ui-bar-e .ui-body-inherit, +html .ui-body-e .ui-body-inherit, +html body .ui-group-theme-e .ui-body-inherit, +html .ui-panel-page-container-e { + background-color: #44739e /*{e-body-background-color}*/; + border-color: #F7C942 /*{e-body-border}*/; + color: #eeeeee /*{e-body-color}*/; + text-shadow: 0 /*{e-body-shadow-x}*/ 0 /*{e-body-shadow-y}*/ 0 /*{e-body-shadow-radius}*/ #ffffff /*{e-body-shadow-color}*/; +} +.ui-body-e { + border-width: 1px; + border-style: solid; +} +/* Links */ +.ui-page-theme-e a, +html .ui-bar-e a, +html .ui-body-e a, +html body .ui-group-theme-e a { + color: #2489CE /*{e-link-color}*/; + font-weight: bold; +} +.ui-page-theme-e a:visited, +html .ui-bar-e a:visited, +html .ui-body-e a:visited, +html body .ui-group-theme-e a:visited { + color: #2489CE /*{e-link-visited}*/; +} +.ui-page-theme-e a:hover, +html .ui-bar-e a:hover, +html .ui-body-e a:hover, +html body .ui-group-theme-e a:hover { + color: #2489CE /*{e-link-hover}*/; +} +.ui-page-theme-e a:active, +html .ui-bar-e a:active, +html .ui-body-e a:active, +html body .ui-group-theme-e a:active { + color: #2489CE /*{e-link-active}*/; +} +/* Button up */ +.ui-page-theme-e .ui-btn, +html .ui-bar-e .ui-btn, +html .ui-body-e .ui-btn, +html body .ui-group-theme-e .ui-btn, +html head + body .ui-btn.ui-btn-e, +/* Button visited */ +.ui-page-theme-e .ui-btn:visited, +html .ui-bar-e .ui-btn:visited, +html .ui-body-e .ui-btn:visited, +html body .ui-group-theme-e .ui-btn:visited, +html head + body .ui-btn.ui-btn-e:visited { + background-color: #fadb4e /*{e-bup-background-color}*/; + border-color: #F4C63f /*{e-bup-border}*/; + color: #222222 /*{e-bup-color}*/; + text-shadow: 0 /*{e-bup-shadow-x}*/ 1px /*{e-bup-shadow-y}*/ 0 /*{e-bup-shadow-radius}*/ #ffffff /*{e-bup-shadow-color}*/; +} +/* Button hover */ +.ui-page-theme-e .ui-btn:hover, +html .ui-bar-e .ui-btn:hover, +html .ui-body-e .ui-btn:hover, +html body .ui-group-theme-e .ui-btn:hover, +html head + body .ui-btn.ui-btn-e:hover { + background-color: #fbe26f /*{e-bhover-background-color}*/; + border-color: #F2C43d /*{e-bhover-border}*/; + color: #333333 /*{e-bhover-color}*/; + text-shadow: 0 /*{e-bhover-shadow-x}*/ 1px /*{e-bhover-shadow-y}*/ 0 /*{e-bhover-shadow-radius}*/ #ffffff /*{e-bhover-shadow-color}*/; +} +/* Button down */ +.ui-page-theme-e .ui-btn:active, +html .ui-bar-e .ui-btn:active, +html .ui-body-e .ui-btn:active, +html body .ui-group-theme-e .ui-btn:active, +html head + body .ui-btn.ui-btn-e:active { + background-color: #fceda7 /*{e-bdown-background-color}*/; + border-color: #F2C43d /*{e-bdown-border}*/; + color: #333333 /*{e-bdown-color}*/; + text-shadow: 0 /*{e-bdown-shadow-x}*/ 1px /*{e-bdown-shadow-y}*/ 0 /*{e-bdown-shadow-radius}*/ #ffffff /*{e-bdown-shadow-color}*/; +} +/* Active button */ +.ui-page-theme-e .ui-btn.ui-btn-active, +html .ui-bar-e .ui-btn.ui-btn-active, +html .ui-body-e .ui-btn.ui-btn-active, +html body .ui-group-theme-e .ui-btn.ui-btn-active, +html head + body .ui-btn.ui-btn-e.ui-btn-active, +/* Active checkbox icon */ +.ui-page-theme-e .ui-checkbox-on:after, +html .ui-bar-e .ui-checkbox-on:after, +html .ui-body-e .ui-checkbox-on:after, +html body .ui-group-theme-e .ui-checkbox-on:after, +.ui-btn.ui-checkbox-on.ui-btn-e:after, +/* Active flipswitch background */ +.ui-page-theme-e .ui-flipswitch-active, +html .ui-bar-e .ui-flipswitch-active, +html .ui-body-e .ui-flipswitch-active, +html body .ui-group-theme-e .ui-flipswitch-active, +html body .ui-flipswitch.ui-bar-e.ui-flipswitch-active, +/* Active slider track */ +.ui-page-theme-e .ui-slider-track .ui-btn-active, +html .ui-bar-e .ui-slider-track .ui-btn-active, +html .ui-body-e .ui-slider-track .ui-btn-active, +html body .ui-group-theme-e .ui-slider-track .ui-btn-active, +html body div.ui-slider-track.ui-body-e .ui-btn-active { + background-color: #387bbe /*{e-active-background-color}*/; + border-color: #2373a5 /*{e-active-border}*/; + color: #ffffff /*{e-active-color}*/; + text-shadow: 0 /*{e-active-shadow-x}*/ 1px /*{e-active-shadow-y}*/ 1px /*{e-active-shadow-radius}*/ #3373a5 /*{e-active-shadow-color}*/; +} +/* Active radio button icon */ +.ui-page-theme-e .ui-radio-on:after, +html .ui-bar-e .ui-radio-on:after, +html .ui-body-e .ui-radio-on:after, +html body .ui-group-theme-e .ui-radio-on:after, +.ui-btn.ui-radio-on.ui-btn-e:after { + border-color: #387bbe /*{e-active-background-color}*/; +} +/* Focus */ +.ui-page-theme-e .ui-btn:focus, +html .ui-bar-e .ui-btn:focus, +html .ui-body-e .ui-btn:focus, +html body .ui-group-theme-e .ui-btn:focus, +html head + body .ui-btn.ui-btn-e:focus, +/* Focus buttons and text inputs with div wrap */ +.ui-page-theme-e .ui-focus, +html .ui-bar-e .ui-focus, +html .ui-body-e .ui-focus, +html body .ui-group-theme-e .ui-focus, +html head + body .ui-btn-e.ui-focus, +html head + body .ui-body-e.ui-focus { + -webkit-box-shadow: 0 0 12px #387bbe /*{e-active-background-color}*/; + -moz-box-shadow: 0 0 12px #387bbe /*{e-active-background-color}*/; + box-shadow: 0 0 12px #387bbe /*{e-active-background-color}*/; +} + + +/* Structure */ +/* Disabled +-----------------------------------------------------------------------------------------------------------*/ +/* Class ui-disabled deprecated in 1.4. :disabled not supported by IE8 so we use [disabled] */ +.ui-disabled, +.ui-state-disabled, +button[disabled], +.ui-select .ui-btn.ui-state-disabled { + filter: Alpha(Opacity=30); + opacity: .3; + cursor: default !important; + pointer-events: none; +} +/* Focus state outline +-----------------------------------------------------------------------------------------------------------*/ +.ui-btn:focus, +.ui-btn.ui-focus { + outline: 0; +} +/* Unset box-shadow in browsers that don't do it right */ +.ui-noboxshadow .ui-shadow, +.ui-noboxshadow .ui-shadow-inset, +.ui-noboxshadow .ui-overlay-shadow, +.ui-noboxshadow .ui-shadow-icon.ui-btn:after, +.ui-noboxshadow .ui-shadow-icon .ui-btn:after, +.ui-noboxshadow .ui-focus, +.ui-noboxshadow .ui-btn:focus, +.ui-noboxshadow input:focus, +.ui-noboxshadow .ui-panel { + -webkit-box-shadow: none !important; + -moz-box-shadow: none !important; + box-shadow: none !important; +} +.ui-noboxshadow .ui-btn:focus, +.ui-noboxshadow .ui-focus { + outline-width: 1px; + outline-style: auto; +} + +/* Clock in header +-----------------------------------------------------------------------------------------------------------*/ +.ui-header .ui-right div.ui-grid-b { + margin-left: 1.3125em; +} + + +/* ui-slider value +-----------------------------------------------------------------------------------------------------------*/ +.ui-slider-track .ui-btn.ui-slider-handle { + line-height: 26px; +} + +/* bottomup / vertical slider +-----------------------------------------------------------------------------------------------------------*/ +html .ui-slider-track-bottomup.ui-shadow-inset.ui-bar-inherit.ui-corner-all, +html .ui-slider-track-vertical.ui-shadow-inset.ui-bar-inherit.ui-corner-all { + background-color: #f5f5f5 /*{a-bar-background-color}*/; + border-color: #c9c9c9 /*{a-bar-border}*/; + color: #333333 /*{a-bar-color}*/; + text-shadow: 0 /*{a-bar-shadow-x}*/ 1px /*{a-bar-shadow-y}*/ 0 /*{a-bar-shadow-radius}*/ #dddddd /*{a-bar-shadow-color}*/; + font-weight: bold; + background-image: -webkit-gradient(linear, left top, left bottom, from( #f8f8f8 ), to( #dddddd )); /* Saf4+, Chrome */ + background-image: -webkit-linear-gradient( left, #f8f8f8, #dddddd ); /* Chrome 10+, Saf5.1+ */ + background-image: -moz-linear-gradient( #f8f8f8, #dddddd ); /* FF3.6 */ + background-image: -ms-linear-gradient( right, #f8f8f8, #dddddd ); /* IE10 */ + background-image: -o-linear-gradient( right, #f8f8f8, #dddddd ); /* Opera 11.10+ */ + background-image: linear-gradient( to right, #f8f8f8, #dddddd ); +} +.ui-slider-track-bottomup .ui-btn.ui-slider-handle, +.ui-slider-track-vertical .ui-btn.ui-slider-handle { + height: 28px; + margin: -8.8px 0 0 -8.8px; + outline: 0 none; + padding: 0; + position: absolute; + top: 50%; + width: 28px; + z-index: 1; +} + +html .ui-slider-track-bottomup.ui-shadow-inset.ui-bar-inherit.ui-corner-all .ui-slider-bg.ui-btn-active, +html .ui-slider-track-vertical.ui-shadow-inset.ui-bar-inherit.ui-corner-all .ui-slider-bg.ui-btn-active { + background-color: #6BABFF; + border-color: #125d79; + -webkit-border-radius: inherit; + border-radius: inherit; + color: #333333; + text-shadow: 0 1px 0 #0088bb; +} + +.ui-slider-track-semicircle.ui-shadow-inset.ui-bar-inherit.ui-corner-all { + background-color: #f5f5f5 !important; + border-color: #c9c9c9 !important; + color: #333333 !important; + text-shadow: 0 1px 0 #dddddd !important; + font-weight: bold !important; + background-image: -webkit-gradient(radial, left top, left bottom, from( #dddddd ), to( #f8f8f8 )) !important; /* Saf4+, Chrome */ + background-image: -webkit-radial-gradient( circle, #dddddd 30%, #f8f8f8 70% ) !important; /* Chrome 10+, Saf5.1+ */ + background-image: -moz-radial-gradient( circle, #dddddd 30%, #f8f8f8 70% ) !important; /* FF3.6 */ + background-image: -ms-radial-gradient( circle, #dddddd 30%, #f8f8f8 70% ) !important; /* IE10 */ + background-image: -o-radial-gradient( circle, #dddddd 30%, #f8f8f8 70% ) !important; /* Opera 11.10+ */ + background-image: radial-gradient( circle, #dddddd 30%, #f8f8f8 70% ) !important; +} + +.ui-slider-track-semicircle.ui-btn-active { + background-color: #6BABFF !important; + border-color: #125d79 !important; + color: #333333 !important; + text-shadow: 0 1px 0 #0088bb !important; +} + +.ui-slider-track-semicircle .ui-slider-handle, +a.ui-slider-handle-semicircle { + -webkit-border-radius: .6em !important; + border-radius: .6em !important; +} + +.ui-slider-track-semicircle .ui-slider-bg.ui-bg-left { + background-color: #6BABFF; + border-color: #125d79; + color: #333333; + text-shadow: 0 1px 0 #0088bb; +} + +.ui-slider-track-semicircle .ui-slider-bg.ui-bg-right { + background-color: #6BABFF; + border-color: #125d79; + color: #333333; + text-shadow: 0 1px 0 #0088bb; +} + +.ui-slider-semicircle-cut { + border-top: 1px solid #6F6E6E; + border-left: 1px solid #6F6E6E; + border-right: 1px solid #6F6E6E; + border-bottom: 0; +} +.ui-slider-semicircle-box .ui-bottom-left{ + background: #6BABFF; + width: 15px; + border-top: 0; + border-left: 1px solid #6F6E6E; + border-right: 1px solid #6F6E6E; + border-bottom: 1px solid #6F6E6E; +} +.ui-slider-semicircle-box .ui-bottom-right{ + /*background: #f9f9f9;*/ + width: 14px; + border-top: 0; + border-left: 1px solid #6F6E6E; + border-right: 1px solid #6F6E6E; + border-bottom: 1px solid #6F6E6E; + + background-image: -webkit-gradient(linear, left, right, from( #2F2F2F ), to( #f8f8f8 )); /* Saf4+, Chrome */ + background-image: -webkit-linear-gradient(left, #2F2F2F 30%, #f8f8f8 70% ); /* Chrome 10+, Saf5.1+ */ + background-image: -moz-linear-gradient(left, #2F2F2F 30%, #f8f8f8 70% ); /* FF3.6 */ + background-image: -ms-linear-gradient(left, #2F2F2F 30%, #f8f8f8 70% ); /* IE10 */ + background-image: -o-linear-gradient(left, #2F2F2F 30%, #f8f8f8 70% ); /* Opera 11.10+ */ + background-image: linear-gradient(to right, #2F2F2F 30%, #f8f8f8 70%); +} + + +/* Highcharts +-----------------------------------------------------------------------------------------------------------*/ +.highcharts-title { + fill: #000; +} +.highcharts-subtitle { + fill: #666; +} + +.highcharts-axis .highcharts-axis-line { + stroke: #999; +} +.highcharts-tick { + stroke: #999; +} +.highcharts-axis-labels { + fill: #666; +} +.highcharts-axis-title { + fill: #999; +} + +.highcharts-legend-item text { + fill: #666; +} +.highcharts-legend-item:hover text { + fill: #333; +} +.highcharts-legend-item-hidden * { + fill: #ccc; + stroke: #ccc; +} + +.highcharts-markers path { + stroke: #fff; +} + +.highcharts-label text { + fill: #000; +} +.highcharts-data-labels { + fill: #000; + color: #000; +} + +.highcharts-tooltip-box { + fill: #dcdcdc; +} +.highcharts-tooltip text { + fill: #000; +} +.polarChart .highcharts-axis-line, +.solidgauge .highcharts-axis-line, +.solidgauge .highcharts-grid-line { + stroke: none; +} +.highcharts-grid .highcharts-grid-line { + stroke: rgba(0, 0, 0, .15); +} +.highcharts-color-0 { + fill: #6BABFF; + stroke: #6BABFF; + color: #6BABFF; +} +.highcharts-color-1 { + fill: #4176A9; + stroke: #4176A9; + color: #4176A9; +} +.highcharts-color-2 { + fill: #6640FF; + stroke: #6640FF; + color: #6640FF; +} +.highcharts-color-3 { + fill: #00bcd4; + stroke: #00bcd4; + color: #00bcd4; +} +.highcharts-color-4 { + fill: #03a9f4; + stroke: #03a9f4; + color: #03a9f4; +} +.highcharts-color-5 { + fill: #2196f3; + stroke: #2196f3; + color: #2196f3; +} +.highcharts-color-6 { + fill: #3f51b5; + stroke: #3f51b5; + color: #3f51b5; +} +.highcharts-color-7 { + fill: #673ab7; + stroke: #673ab7; + color: #673ab7; +} +.highcharts-color-8 { + fill: #673ab7; + stroke: #673ab7; + color: #673ab7; +} +.highcharts-color-9 { + fill: #009688; + stroke: #009688; + color: #009688; +} + +/* smartVISU +-----------------------------------------------------------------------------------------------------------*/ +.ui-right > .ui-link, +.ui-right > .ui-link:hover { + color: #333333; +} +.icon0, a.ui-link { + stroke: #333333; + fill: #333333; + stop-color: #333333; +} +.ui-btn-c a.ui-link .icon0 { + stroke: #333333; + fill: #333333; + stop-color: #333333; +} +.icon1, .icon1.ui-link, + a.ui-link:hover, a.ui-link:active { + stroke: #6BABFF; + fill: #6BABFF; + stop-color: #6BABFF; +} +.switch.icon1, +html .ui-btn.icon1, +[data-widget="basic.print"].icon1, +[data-widget="basic.symbol"] .icon1 { + color: #6BABFF; +} + +/* red icon */ +.icon2, +.icon2.ui-link { + stroke: red; + fill: red; + stop-color: red; +} +.switch.icon2, +html .ui-btn.icon2, +[data-widget="basic.print"].icon2, +[data-widget="basic.symbol"] .icon2 { + color: red; +} + +/* yellow icon */ +.icon3, +.icon3.ui-link { + stroke: #E1D500; + fill: #E1D500; + stop-color: #E1D500; +} +.switch.icon3, +html .ui-btn.icon3, +[data-widget="basic.print"].icon3, +[data-widget="basic.symbol"] .icon3 { + color: #E1D500; +} + +/* green icon */ +.icon4, +.icon4.ui-link { + stroke: limegreen; + fill: limegreen; + stop-color: limegreen; +} +.switch.icon4, +html .ui-btn.icon4, +[data-widget="basic.print"].icon4, +[data-widget="basic.symbol"] .icon4 { + color: limegreen; +} + +/* blue icon */ +.icon5, +.icon5.ui-link { + stroke: #4176a9; + fill: #4176a9; + stop-color: #4176a9; +} +.switch.icon5, +html .ui-btn.icon5, +[data-widget="basic.print"].icon5, +[data-widget="basic.symbol"] .icon5 { + color: #4176a9; +} + +/** set color of calendar picker symbol in webkit time input fields for UZSU */ +.uzsuTimeInput::-webkit-calendar-picker-indicator, +.uzsuTimeMaxMinInput::-webkit-calendar-picker-indicator { + background-image: url(images/webkit_clock_ws.svg); +} + +/** set color of selected autocomplete tooltip in widget assistant */ +#widget_assistant #selected_dict>strong { + color: #FFFFFF; +} + +/* jQuery Roundslider +-----------------------------------------------------------------------------------------------------------*/ + +/*** SVG mode - theming - colors ***/ +.rs-path-inherited .rs-path { + opacity: 0.2; +} +.rs-svg-mode .rs-path { + /* this will change the slider's path color + this can be also done via the property -> 'pathColor' */ + stroke: #ffffff; +} +.rs-svg-mode .rs-range { + /* this will change the slider's range color + this can be also done via the property -> 'rangeColor' */ + stroke: #6BABFF; +} +.rs-svg-mode .rs-border { + /* this will change the slider's border color + this can be also done via the property -> 'borderColor' + the border width can be changed via the property -> 'borderWidth' */ + stroke: #c9c9c9; +} +.outerslider.rs-svg-mode .rs-range { + stroke: #44739e; +} +span.rs-number { + position: absolute; + top: -8px; + left: -30px; + font-size: 0.8em; + font-family: Arial; + color: #333333; +} + +.rs-bar.rs-custom .rs-seperator { + border: 2px solid #c9c9c9; + margin-left: -10px; +} +.rs-bar.rs-custom_1 .rs-seperator { + border: 1px solid #d7d7d7; + width: 5px; + height: 1px; + margin-left: 1px; +} +.rs-bar.rs-custom_1.rs-seperator_1 .rs-seperator { + border: 1px solid #c9c9c9; +} +.rs-bar.rs-start .rs-seperator, +.rs-bar.rs-end .rs-seperator { + display: none; +} + +.rs-handle { + background-color: #838383; + background-image: -webkit-gradient(linear, left top, left bottom, from( #f9f9f9 /*{a-bup-background-start}*/), to( #a6a6a6 /*{a-bup-background-end}*/)); /* Saf4+, Chrome */ + background-image: -webkit-linear-gradient( #f9f9f9 /*{a-bup-background-start}*/, #a6a6a6 /*{a-bup-background-end}*/); /* Chrome 10+, Saf5.1+ */ + background-image: -moz-linear-gradient( #f9f9f9 /*{a-bup-background-start}*/, #a6a6a6 /*{a-bup-background-end}*/); /* FF3.6 */ + background-image: -ms-linear-gradient( #f9f9f9 /*{a-bup-background-start}*/, #a6a6a6 /*{a-bup-background-end}*/); /* IE10 */ + background-image: -o-linear-gradient( #f9f9f9 /*{a-bup-background-start}*/, #a6a6a6 /*{a-bup-background-end}*/); /* Opera 11.10+ */ + background-image: linear-gradient( #f9f9f9 /*{a-bup-background-start}*/, #a6a6a6 /*{a-bup-background-end}*/); + box-shadow: 0px 0px 15px #6BABFF; +} +.outerslider .rs-handle { + display:none; +} +.rs-handle-dot { + padding: 11px; + background-color: #FFFFFF; +} +.rs-handle-dot:after { background-color: #838383; } + +/*.cell { + border: 1px solid #bbb; + padding: 20px; + width: 33%; + float: left; + box-sizing: border-box; + background: white; +} +*/ + +.rs-overlay { + /* a small fix to hide the overlay */ + border-radius: 1000px 0 0 0; + margin-top: 1px; + margin-right: 1px; +} +.rs-control { + /*border: 1px solid #d7d7d7;*/ + border-radius: 50%; + padding: 1px; +} +.rs-tooltip-text { + font-size: 2.7em; +} diff --git a/designs/night.css b/designs/night.css index 6ec7937e1..462eb9533 100644 --- a/designs/night.css +++ b/designs/night.css @@ -12,6 +12,7 @@ * */ +@import '../icons/ws/jquery.mobile.icons.min.css'; /* Globals */ /* Font @@ -1749,7 +1750,7 @@ html .ui-btn.icon5, } -/* jQuery Roundslider +/* jQuery Roundslider -----------------------------------------------------------------------------------------------------------*/ /*** SVG mode - theming - colors ***/ @@ -1778,14 +1779,13 @@ span.rs-number { top: -8px; left: -30px; font-size: 0.8em; - font-style: bold; font-family: Arial; color: #fff; } -.rs-bar.rs-custom .rs-seperator { - border: 2px solid #875010; - margin-left: -10px; +.rs-bar.rs-custom .rs-seperator { + border: 2px solid #875010; + margin-left: -10px; } .rs-bar.rs-custom_1 .rs-seperator { border: 1px solid #d7d7d7; @@ -1802,7 +1802,7 @@ span.rs-number { } .rs-handle { - background-color: #838383; + background-color: #838383; background-image: -webkit-gradient(linear, left top, left bottom, from( #444444 /*{a-bup-background-start}*/), to( #2d2d2d /*{a-bup-background-end}*/)); /* Saf4+, Chrome */ background-image: -webkit-linear-gradient( #444444 /*{a-bup-background-start}*/, #2d2d2d /*{a-bup-background-end}*/); /* Chrome 10+, Saf5.1+ */ background-image: -moz-linear-gradient( #444444 /*{a-bup-background-start}*/, #2d2d2d /*{a-bup-background-end}*/); /* FF3.6 */ @@ -1840,5 +1840,4 @@ span.rs-number { } .rs-tooltip-text { font-size: 2.7em; - font-style: bold; } diff --git a/designs/sand.css b/designs/sand.css index 126e64e2c..249289cf2 100644 --- a/designs/sand.css +++ b/designs/sand.css @@ -12,6 +12,7 @@ * */ +@import '../icons/ws/jquery.mobile.icons.min.css'; /* Globals */ /* Font @@ -1752,7 +1753,7 @@ html .ui-btn.icon5, background-image: url(images/webkit_clock_ws.svg); } -/* jQuery Roundslider +/* jQuery Roundslider -----------------------------------------------------------------------------------------------------------*/ /*** SVG mode - theming - colors ***/ @@ -1781,14 +1782,13 @@ span.rs-number { top: -8px; left: -30px; font-size: 0.8em; - font-style: bold; font-family: Arial; color: #fff; } -.rs-bar.rs-custom .rs-seperator { - border: 2px solid #875010; - margin-left: -10px; +.rs-bar.rs-custom .rs-seperator { + border: 2px solid #875010; + margin-left: -10px; } .rs-bar.rs-custom_1 .rs-seperator { border: 1px solid #d7d7d7; @@ -1805,7 +1805,7 @@ span.rs-number { } .rs-handle { - background-color: #838383; + background-color: #838383; background-image: -webkit-gradient(linear, left top, left bottom, from( #444444 /*{a-bup-background-start}*/), to( #2d2d2d /*{a-bup-background-end}*/)); /* Saf4+, Chrome */ background-image: -webkit-linear-gradient( #444444 /*{a-bup-background-start}*/, #2d2d2d /*{a-bup-background-end}*/); /* Chrome 10+, Saf5.1+ */ background-image: -moz-linear-gradient( #444444 /*{a-bup-background-start}*/, #2d2d2d /*{a-bup-background-end}*/); /* FF3.6 */ @@ -1843,5 +1843,4 @@ span.rs-number { } .rs-tooltip-text { font-size: 2.7em; - font-style: bold; } diff --git a/driver/_io_template.js b/driver/_io_template.js index 7346c2bdb..b1fd259b7 100644 --- a/driver/_io_template.js +++ b/driver/_io_template.js @@ -81,6 +81,13 @@ var io = { // only be called from the public functions above. You may add or delete some // to fit your requirements and your connected system. + + /** + * supported aggregate functions in the backends database + * TODO: check which aggregates are valid for the backend and adapt the array + */ + aggregates: ['avg', 'min', 'max', 'sum', 'diff', 'rate', 'on', 'raw', 'count'], + // TODO /** diff --git a/driver/hints_smarthomeng.md b/driver/hints_smarthomeng.md index 68243e198..9c32dec55 100644 --- a/driver/hints_smarthomeng.md +++ b/driver/hints_smarthomeng.md @@ -2,6 +2,6 @@ In der aktuellen Version wurde der alte smarthome.py-Treiber entfernt und als Ba IP-Adresse / Hostname und die Websocket-Ports für shNG müssen jetzt *immer* angegeben werden. Zudem muss der Hostname des smartVISU-Servers in einem neuen Feld eingetragen werden, sofern der Hostname für den Aufruf der Visu verwendet wird, also z.B. "smarthome.local" anstatt "192.168.2.10". Der Treiber, der im Konfigurationsmenü als „smarthomeNG“ angezeigt wird, verbindet sich dann wie folgt mit dem shNG Websocket: - - Ruft der Benutzer die Visu mittels IPv4-Adresse auf (z. B. http://192.168.2.10/smartVISU), dann verwendet der Treiber die Adresse und die Ports aus der Konfiguration. - - Wird die Visu per Hostname aufgerufen (z.B. http://smarthome.local/smartVISU), dann prüft der Treiber, ob der verwendete Hostname (im Beispiel „smarthome.local“) der konfigurierte SV-Hostname ist und verwendet in diesem Fall wieder die konfigurierte Adresse und die Ports. - - entspricht der Hostname im Seitenaufruf nicht dem konfigurierten SV-Hostnamen, dann geht der Treiber von einer externen Verbindung aus und spricht den Websocket über den Hostnamen, Port 80/443 und das Protokoll ws: / wss: an. +- Ruft der Benutzer die Visu mittels IPv4-Adresse auf (z. B. http://192.168.2.10/smartVISU), dann verwendet der Treiber die Adresse und die Ports aus der Konfiguration. +- Wird die Visu per Hostname aufgerufen (z.B. http://smarthome.local/smartVISU), dann prüft der Treiber, ob der verwendete Hostname (im Beispiel „smarthome.local“) der konfigurierte SV-Hostname ist und verwendet in diesem Fall wieder die konfigurierte Adresse und die Ports. +- entspricht der Hostname im Seitenaufruf nicht dem konfigurierten SV-Hostnamen, dann geht der Treiber von einer externen Verbindung aus und spricht den Websocket über den Hostnamen, Port 80/443 und das Protokoll ws: / wss: an. diff --git a/driver/io_eibd.js b/driver/io_eibd.js index fd676016c..4492f6016 100644 --- a/driver/io_eibd.js +++ b/driver/io_eibd.js @@ -2,7 +2,7 @@ * ----------------------------------------------------------------------------- * @package smartVISU * @author Raik Alber and Martin Gleiß - * @copyright 2013 + * @copyright 2013 - 2024 * @license GPL [http://www.gnu.de] * @version 0.2 * ----------------------------------------------------------------------------- @@ -17,6 +17,7 @@ * @hide driver_username * @hide driver_password * @hide driver_loopback + * @hide driver_signalBusy * @hide sv_hostname */ @@ -110,6 +111,11 @@ var io = { lastRequestStarted: 0, reloadAllDataTime: 1800, restartRequestTime: 60, + + /** + * supported aggregate functions in the backends database + */ + aggregates: [], checkRequest: function () { @@ -374,14 +380,6 @@ var io = { requestItem = itemArray[0] + '/' + itemArray[1] + '/' + itemArray[2]; return requestItem; - }, - - /** - * stop all subscribed series - */ - stopseries: function () { - // TODO - $.noop; } - + }; diff --git a/driver/io_fhem.js b/driver/io_fhem.js index ef9d64058..420ecff77 100755 --- a/driver/io_fhem.js +++ b/driver/io_fhem.js @@ -3,7 +3,7 @@ * @package smartVISU / FHEM * @author HCS with adjustments by Julian Pawlowski, Stefan Widmer, raman and wvhn * original version by Martin Gleiß - * @copyright 2016 - 2022 + * @copyright 2016 - 2024 * @license GPL [http://www.gnu.de] * ----------------------------------------------------------------------------- * @label FHEM @@ -17,6 +17,7 @@ * @hide driver_username * @hide driver_password * @hide driver_loopback + * @hide driver_signalBusy * @hide sv_hostname * @default driver_port 2121 * @@ -153,6 +154,12 @@ var io = { addon: null, socketErrorNotification: null, + /** + * supported aggregate functions in the backends database + * modes implemented in the fronthem file "99_fronthemUtils.pm" + */ + aggregates: ['avg', 'min', 'max', 'sum', 'raw'], + log: function(level, text) { if (io.logLevel >= level) { console.log("[io.fhem]: " + text); diff --git a/driver/io_iobroker.js b/driver/io_iobroker.js index 6208b64c2..798e1b534 100644 --- a/driver/io_iobroker.js +++ b/driver/io_iobroker.js @@ -1,8 +1,8 @@ /** * ----------------------------------------------------------------------------- * @package smartVISU - * @author Stefan Widmer (inspired by https://github.com/ioBroker/ioBroker.socketio/blob/master/example/conn.js) - * @copyright 2017 + * @author Stefan Widmer (inspired by https://github.com/ioBroker/ioBroker.socketio/blob/master/example/conn.js), Wolfram v. Hülsen + * @copyright 2017 - 2024 * @license GPL [http://www.gnu.de] * ----------------------------------------------------------------------------- * @label ioBroker @@ -192,6 +192,27 @@ var io = { }, listeners: [], + /** + * array of items where a property is requested on the page with keyword "property": + * .property. e.g. kitchen.light.property.lc + * if any property is requested for an item the driver maps all properties for that item + */ + properties: [], + + /** + * supported aggregate functions in the backends database + * https://www.iobroker.net/docu/index-83.htm?page_id=4531&lang=en + * - minmax - is ordered as 2 series: min and max + * - min + * - max + * - avergage = avg + * - total = on + * - count + */ + aggregates: ['avg', 'average', 'min', 'max', 'total', 'on', 'count'], + monitorComplete: null, + openItems: [], + /** * Opens the connection and add some handlers @@ -248,6 +269,11 @@ var io = { io.socket.on('stateChange', function (id, state) { if (!id || state === null || typeof state !== 'object') return; io.stateChanged(id, state); + if (io.monitorCompleted == false && io.openItems.length == 0){ + io.monitorCompleted = true; + $('.smartvisu .visu').removeClass('blink'); + } + /* if (that._connCallbacks.onCommand && id === that.namespace + '.control.command') { if (state.ack) return; @@ -305,12 +331,23 @@ var io = { val.push([state.ts, state.val]); }); widget.update(plot[1], val); + io.openItems.removeEntry(plot[1]); }); }, - delayPlots: function() { + subscribePlots: function(plotitem) { + var plotItems = []; var items = Array(); - io.plots.forEach(plot => { + + if (plotitem == undefined) + plotItems = io.plots; + else + plotItems = plotitem; + + //DEBUG: + console.log('[io_iobroker] subscribing series: ', plotItems); + + plotItems.forEach(plot => { io.updatePlot(plot); plot[2] = true; // enable stateChanged items.push(plot[0]); @@ -323,46 +360,44 @@ var io = { }, monitor: function() { + io.monitorCompleted = false; io.listeners = []; + io.properties = []; var listeners = widget.listeners(); var listenItem; var listenItemEnd; for (var i=0; i < listeners.length; i++){ listenItemEnd = listeners[i].indexOf(':'); listenItem = (listenItemEnd == -1 ? listeners[i] : listeners[i].substring(0, listenItemEnd)); + + if (listenItem.indexOf('.property') != -1){ + listenItemEnd = listenItem.indexOf('.property'); + listenItem = listenItem.substring(0, listenItemEnd); + if (!io.properties.includes(listenItem)) + io.properties.push(listenItem); + } + if ( io.listeners[listenItem] == undefined || listenItem == io.listeners[listenItem]) - io.listeners[listenItem] = listeners[i]; + io.listeners[listenItem] = listeners[i].indexOf('.property') == -1 ? listeners[i] : listenItem; } var items = Object.keys(io.listeners); + io.openItems = Object.keys(io.listeners); + - if (io.checkConnected() && items.length) { - io.socket.emit('subscribe', items); - io.read(items); + if (io.checkConnected()) { + if (items.length) { + io.socket.emit('subscribe', items); + io.read(items); + } // plot io.plots = Array(); clearTimeout(io.firstPlotTimeout); io.firstPlotTimeout = 0; - var unique = Array(); - widget.plot().each(function (idx) { - var items = widget.explode($(this).attr('data-item')); - $.each(items, function() { - var item = String(this); - var pt = item.split('.'); - if (!unique[item] && (pt instanceof Array) && widget.checkseries(item)) { - unique[item] = 1; - if (io.plots.find(plot => plot[1] === item) === undefined) { - var id = item.substr(0, item.length - 4 - pt[pt.length - 4].length - pt[pt.length - 3].length - pt[pt.length - 2].length - pt[pt.length - 1].length); - io.plots.push([id, item, false]); - } - io.firstPlotTimeout = -1; - } - }); - }); + io.startseries(); - // Start subscribing and draw with a delay in the plots so the "normal" widgets are populated with data first. - // This speeds up page loading when there is a lot of data in the plots. - if (io.firstPlotTimeout == -1) io.firstPlotTimeout = setTimeout(io.delayPlots, 1000); + if (sv.config.driver.signalBusy) + $('.smartvisu .visu').addClass('blink'); } }, @@ -418,25 +453,99 @@ var io = { } widget.update(item, val); + io.openItems.removeEntry(item); if (item != io.listeners[item]) widget.update(io.listeners[item], val); + if (io.properties.includes(item)){ + for (var key in state){ + if (state.hasOwnProperty(key) && key != 'val') + widget.update(item + '.property.' + key, state[key] ); + } + } } }, /** - * stop all subscribed series + * start subscriptions for all plots in a page or a single specified plot */ - stopseries: function () { - if (io.isConnected) { - var items = io.listeners != [] ? Object.keys(io.listeners) : widget.listeners(); - io.plots.forEach(plot => { - items.push(plot[0]); + startseries: function(plotwidget){ + var unique = Array(); + var plotWidgets = []; + var plotsLength = io.plots.length; + var reorderPlots = []; + if (plotwidget === undefined) + plotWidgets = widget.plot(); + else + plotWidgets = plotwidget; + + plotWidgets.each(function (idx) { + var items = widget.explode($(this).attr('data-item')); + $.each(items, function() { + var item = String(this); + var pt = item.split('.'); + if (!unique[item] && (pt instanceof Array) && widget.checkseries(item)) { + unique[item] = 1; + var id = item.substr(0, item.length - 4 - pt[pt.length - 4].length - pt[pt.length - 3].length - pt[pt.length - 2].length - pt[pt.length - 1].length); + if (io.plots.find(plot => plot[1] === item) === undefined) { + io.plots.push([id, item, false]); + io.openItems.push(item); + } + else + reorderPlots.push([id, item, false]); + + io.firstPlotTimeout = -1; + } }); - io.socket.emit('unsubscribe', items); - io.read(items); + }); + if (plotwidget == undefined){ + // Start subscribing the plots with delay so the "normal" widgets are populated with data first. + // This speeds up page loading when there is a lot of data in the plots. + if (io.firstPlotTimeout == -1) + io.firstPlotTimeout = setTimeout(io.subscribePlots, 1000); + } + else { + io.subscribePlots(reorderPlots.concat(io.plots.slice(plotsLength))); } - valueType = Array(); // clear list - } + }, + + + /** + * stop subscriptions for all plots in a page or a single specified plot + * plotwidget is the jQuery object representing a specific plot widget + */ + stopseries: function (plotwidget) { + var items = Array(); + if (io.isConnected) { + if (plotwidget != undefined){ + var plotitems = widget.explode(plotwidget.attr('data-item')); + $.each(plotitems, function() { + if (widget.plot(this).length == 1){ // stop series if plotitem is used only once + var item = String(this); + var plotIndex = io.plots.findIndex(plot => plot[1] === item); + //DEBUG: + console.log('[io_iobroker] cancelling series: '+ item + ' at index: ' + plotIndex); + + // delete entry in the plots array so plot update is skipped even if base item is updated + io.plots.splice(plotIndex, 1); + delete widget.buffer[item]; + } + }) + } + else { + // all items - Cancelling on page change is OK but we should find a more suitable location (ToDo) + items = io.listeners != [] ? Object.keys(io.listeners) : widget.listeners(); + + io.valueType = Array(); // clear list + // all series items + io.plots.forEach(plot => { + items.push(plot[0]); + }); + io.socket.emit('unsubscribe', items); + io.read(items); + } + } + + }, /* logout: function() { diff --git a/driver/io_json.js b/driver/io_json.js index dc34e0a87..6e22d7f69 100644 --- a/driver/io_json.js +++ b/driver/io_json.js @@ -1,8 +1,8 @@ /** * ----------------------------------------------------------------------------- * @package smartVISU - * @author Martin Gleiss - * @copyright 2012 - 2015 + * @author Martin Gleiß + * @copyright 2012 - 2024 * @license GPL [http://www.gnu.de] * ----------------------------------------------------------------------------- * @label JSON @@ -16,6 +16,7 @@ * @hide driver_username * @hide driver_password * @hide driver_loopback + * @hide driver_signalBusy * @hide sv_hostname */ @@ -104,6 +105,12 @@ var io = { timer: 0, timer_run: false, errorNotification: null, + + /** + * supported aggregate functions in the backends database + */ + aggregates: [], + /** * The real-time polling loop, only if there are listeners @@ -221,12 +228,4 @@ var io = { } }, - /** - * stop all subscribed series - */ - stopseries: function () { - // TODO - $.noop; - } - }; diff --git a/driver/io_linknx.js b/driver/io_linknx.js index 44e415bf6..12d81b042 100644 --- a/driver/io_linknx.js +++ b/driver/io_linknx.js @@ -1,8 +1,8 @@ /** * ----------------------------------------------------------------------------- * @package smartVISU - * @author Martin Gleiss - * @copyright 2012 - 2015 + * @author Martin Gleiß + * @copyright 2012 - 2024 * @license GPL [http://www.gnu.de] * ----------------------------------------------------------------------------- * @default driver_port 1028 @@ -16,6 +16,7 @@ * @hide driver_username * @hide driver_password * @hide driver_loopback + * @hide driver_signalBusy * @hide sv_hostname */ @@ -103,6 +104,13 @@ var io = { timer: 0, timer_run: false, + + /** + * supported aggregate functions in the backends database + * TODO: check which aggregates are valid for ioBroker + */ + aggregates: [], + /** * The real-time polling loop, only if there are listeners @@ -202,12 +210,4 @@ var io = { } }, - /** - * stop all subscribed series - */ - stopseries: function () { - // TODO - $.noop; - } - }; diff --git a/driver/io_offline.js b/driver/io_offline.js index 89a44d3e4..56d5db3b4 100644 --- a/driver/io_offline.js +++ b/driver/io_offline.js @@ -17,6 +17,7 @@ * @hide driver_username * @hide driver_password * @hide driver_loopback + * @hide driver_signalBusy * @hide sv_hostname */ @@ -125,6 +126,12 @@ var io = { seriesTimer: [], listeners: [], + /** + * supported aggregate functions in the backends database + */ + aggregates: ['avg', 'min', 'max', 'sum', 'diff', 'rate', 'on', 'raw', 'count'], + + /** * The real-time polling loop, only if there are listeners */ @@ -170,6 +177,9 @@ var io = { val = JSON.parse(response[item]); } catch(e) {} + // initialize UZSU data if item is an UZSU item and no data available + if (item.slice(-5).toLowerCase() == '.uzsu' && val == null) + val = {"active":"false", "interpolation": {"type": "none", "initialized": false, "interval": 5, "initage": 0, "itemtype": "bool"}, "list": [], "plugin_version": "2.0.0"} console.log('[io.offline] receiving data: ["'+ item +' ": '+ val + ']'); widget.update(item, val); }) @@ -230,6 +240,9 @@ var io = { val = JSON.parse(response[item]); } catch(e) {} + // initialize UZSU data if item is an UZSU item and no data available + if (item.slice(-5).toLowerCase() == '.uzsu' && val == null) + val = {"active":"false", "interpolation": {"type": "none", "initialized": false, "interval": 5, "initage": 0, "itemtype": "bool"}, "list": [], "plugin_version": "2.0.0"} widget.update(item, val); if (item != io.listeners[item]) widget.update(io.listeners[item], val); diff --git a/driver/io_openhab.js b/driver/io_openhab.js index a30542c9d..78050fe3e 100644 --- a/driver/io_openhab.js +++ b/driver/io_openhab.js @@ -2,9 +2,9 @@ * ----------------------------------------------------------------------------- * @package smartVISU * @author Martin Gleiß, Patrik Germann - * @copyright 2012 - 2022 + * @copyright 2012 - 2024 * @license GPL [http://www.gnu.de] - * @version 2.5.1 + * @version 2.5.3 * ----------------------------------------------------------------------------- * @label openHAB * @@ -22,6 +22,7 @@ * @hide proxy_user * @hide proxy_password * @hide driver_loopback + * @hide driver_signalBusy */ /** @@ -189,6 +190,13 @@ var io = { auth : false, serverTimeout : 10, serverResponseTime : 0, + + /** + * supported aggregate functions in the backends database + * since this must be configured by the user in the backend we leave some common modes as dummies + */ + aggregates: ['avg', 'min', 'max', 'sum', 'diff', 'on', 'raw', 'count'], + /** * convert states from openHAB diff --git a/driver/io_smarthomeng.js b/driver/io_smarthomeng.js index 9647e9709..9c69719f4 100644 --- a/driver/io_smarthomeng.js +++ b/driver/io_smarthomeng.js @@ -2,7 +2,7 @@ * ----------------------------------------------------------------------------- * @package smartVISU * @author Martin Gleiß, Martin Sinn, Wolfram v. Hülsen - * @copyright 2012 - 2021 + * @copyright 2012 - 2024 * @license GPL [http://www.gnu.de] * ----------------------------------------------------------------------------- * @label SmartHomeNG @@ -10,14 +10,14 @@ * @default driver_autoreconnect true * @default driver_port 2424 * @default driver_tlsport 2425 - * @hide reverseproxy - * @hide driver_realtime + * @hide reverseproxy + * @hide driver_realtime * @hide driver_consoleport * @hide driver_consoleusername * @hide driver_consolepassword - * @hide driver_ssl - * @hide driver_username - * @hide driver_password + * @hide driver_ssl + * @hide driver_username + * @hide driver_password */ @@ -103,6 +103,8 @@ var io = { else if ( location.hostname != sv.config.svHostname ) io.address = ''; } + clearTimeout(io.pingTimer); + io.socketState = ''; io.open(); }, @@ -117,6 +119,20 @@ var io = { io.monitor(); }, + + pingTimer: null, + pingInterval: null, + socketState: '', + + ping: function(){ + io.socketState = 'pinging'; + console.log('[io.smarthomeng] starting ping'); + io.send({"cmd":"ping"}); + io.pingTimer = setTimeout(function(){ + io.socketState = 'offline'; + console.log('[io.smarthomeng] no answer on ping'); + },2000); + }, // ----------------------------------------------------------------------------- @@ -135,6 +151,13 @@ var io = { */ version: 4, shngProto: null, + + /** + * supported aggregate functions in the backends database + * https://smarthomeng.github.io/smarthome/plugins/database/README.html + * TODO: check how comparator for count can be implemented, e.g. "count>10" + */ + aggregates: ['avg', 'min', 'max', 'diff', 'sum', 'on', 'raw', 'count', 'countall', 'integrate', 'differentiate', 'duration'], /** * This is the websocket module / plugin and the websocket opening time @@ -149,6 +172,8 @@ var io = { triggerqueue: [], listeners: [], + monitorComplete: null, + openItems: [], /** * Opens the connection and add some handlers @@ -203,6 +228,12 @@ var io = { }; io.socket.onmessage = function (event) { + clearTimeout(io.pingTimer); + clearTimeout(io.pingInterval); + io.socketState = 'running'; + if (sv.config.pingInterval > 0) + io.pingInterval = setTimeout(io.ping, sv.config.pingInterval * 1000); + var item, val; var data = JSON.parse(event.data); // DEBUG: @@ -214,9 +245,8 @@ var io = { item = data.items[i][0]; val = data.items[i][1]; /* not supported: - if (data.items[i].length > 2) { - data.p[i][2] options for visu - }; + if (data.items[i].length > 2) + data.p[i][2] options for visu; */ // convert binary @@ -227,16 +257,16 @@ var io = { val = 1; } widget.update(item, val); + io.openItems.removeEntry(item); if (item != io.listeners[item]) widget.update(io.listeners[item], val); } break; case 'series': - //if (io.version <= 3) - // data.sid = data.sid + '.100'; - - widget.update(data.sid.replace(/\|/g, '\.'), data.series); + item = data.sid.replace(/\|/g, '\.'); + widget.update(item, data.series); + io.openItems.removeEntry(item); break; case 'dialog': @@ -246,10 +276,17 @@ var io = { case 'log': if (data.init) { widget.update(data.name, data.log); + io.openItems.removeEntry(data.name); } else { var log = widget.get(data.name); // only a reference - + + // workaround for shNG bug which sends updates for all registered memlogs instead of only the ones requested by smartVISU + if (log == undefined) { + console.log('[io.smarthomeng] ignoring data for not requested log "' + data.name + '"'); + break; + } + for (var i = 0; i < data.log.length; i++) { log.unshift(data.log[i]); @@ -273,7 +310,11 @@ var io = { case 'url': $.mobile.changePage(data.url); - break; + break; + } + if (io.monitorCompleted == false && io.openItems.length == 0){ + io.monitorCompleted = true; + $('.smartvisu .visu').removeClass('blink'); } }; @@ -312,9 +353,8 @@ var io = { * Monitors the items */ monitor: function () { - //if (widget.listeners().length) { - // subscribe all items used on the page - // or cancel subscription by sending an empty array + io.monitorCompleted = false; + // subscribe all items used on the page or cancel subscription by sending an empty array io.listeners = []; var listeners = widget.listeners(); var listenItem; @@ -326,17 +366,18 @@ var io = { io.listeners[listenItem] = listeners[i]; } io.send({'cmd': 'monitor', 'items': Object.keys(io.listeners)}); - //} + io.openItems = Object.keys(io.listeners); // subscribe all plots defined for the page - // types: avg, min, max, on io.startseries (); - // log + // subscribe all log items defined for the page widget.log().each(function (idx) { io.send({'cmd': 'log', 'name': $(this).attr('data-item'), 'max': $(this).attr('data-count')}); - + io.openItems.push($(this).attr('data-item')); }); + if (sv.config.driver.signalBusy) + $('.smartvisu .visu').addClass('blink'); }, /** @@ -374,6 +415,8 @@ var io = { if (!unique[items[i]] && definition != null) { io.send({'cmd': seriescmd, 'item': definition.item, 'series': definition.mode, 'start': definition.start, 'end': definition.end, 'count': definition.count}); unique[items[i]] = 1; + if (seriescmd == 'series') + io.openItems.push(items[i]); if (singleCancel == true) delete widget.buffer[items[i]]; } diff --git a/dropins/README.md b/dropins/README.md index 2f1aed3c4..3767ffe61 100644 --- a/dropins/README.md +++ b/dropins/README.md @@ -8,22 +8,24 @@ You may place custom Twig html templates in here and use them in any of your pag System templates can be overridden by creating a template with same name in here (e.g. `base.html` in dropins overrides `pages/base/base.html`). ## Widgets -The widget folder is being used by SmarthomeNG page generation plugin "visu-smartvisu". The plugin empties the folder and places plugin-specific widgets there. -Widgets placed there manually will be deleted. If you are using the plugin "visu-smartvisu" you should place your own widgets in the dropins folder. -There, you can add documentation files for these widgets. If no documentation is available / needed you can place the widgets also in "pages/(yourPage)/widgets". +The widget folder .dropins/shwidgets is being used by SmarthomeNG page generation in the "smartvisu" plugin. The plugin empties the folder and places plugin-specific widgets there. +Widgets placed there manually will be deleted. You should place your own widgets in the ./dropins/widgets folder. There, you can add documentation files for these widgets. +If no documentation is available / needed you can place the widgets also in "pages/(yourPage)/widgets". ### Twig Macros -Any valid .html file in widgets folder gets imported as widget library. E.g. a macro `bar()` defined in `widgets/foo.html` can be called in your pages by `{{ foo.bar() }}`. +Any valid .html file in widgets folders gets imported as widget library. E.g. a macro `bar()` defined in `./dropins/widgets/foo.html` can be called in your pages by `{{ foo.bar() }}`. Widget filenames have to be valid Twig/PHP variable names. They must not contain any non-alphanumeric characters (except underlines) and must not start with a number. -See the Wiki section on github for documentation on how to create your own widgets. +See the Wiki section on github for documentation on how to create your own widgets. An explicit import of custom widgets into the visu pages is not necessary and should be avoided. +On the opposite widgets have to be imported for use inside other widgets. Use the namespace for importing: `{% import "@widgets/mywidget.html" as mywidget %}`. +Avoid renaming of the widget during import as the template checker will not find renamed widgets. ## Icons -You may place your black icons in `icons/sw/` and their white counterpart in `icons/ws/`. +You may place your black icons in `./dropins/icons/sw/` and their white counterpart in `./dropins/icons/ws/`. Icons delivered by smartVISU get overridden if you use existing filenames. To make this work don't use icon0 nor any path in widgets, but just the filename. To make SVG tintable by smartVISU, set fill and stroke by dedicated attributes and not by style (e.g. ` + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/icons/sw/scene_robo_vac_cleaner_dock.svg b/icons/sw/scene_robo_vac_cleaner_dock.svg new file mode 100644 index 000000000..ab1d0cb84 --- /dev/null +++ b/icons/sw/scene_robo_vac_cleaner_dock.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/icons/ws/scene_cooking_drink.svg b/icons/ws/scene_cooking_drink.svg new file mode 100644 index 000000000..45de0dc59 --- /dev/null +++ b/icons/ws/scene_cooking_drink.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/icons/ws/scene_robo_vac_cleaner_dock.svg b/icons/ws/scene_robo_vac_cleaner_dock.svg new file mode 100644 index 000000000..23520ceef --- /dev/null +++ b/icons/ws/scene_robo_vac_cleaner_dock.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/index.php b/index.php index 661ffe91a..b3080b13e 100644 --- a/index.php +++ b/index.php @@ -2,8 +2,8 @@ /** * ----------------------------------------------------------------------------- * @package smartVISU - * @author Martin Gleiss - * @copyright 2012 - 2015 + * @author Martin Gleiß + * @copyright 2012 - 2024 * @license GPL [http://www.gnu.de] * ----------------------------------------------------------------------------- */ @@ -44,39 +44,39 @@ // init template engine require_once const_path.'vendor/autoload.php'; + //---------------------------------------------------------- + // create search path for all templates + //---------------------------------------------------------- $loader = new \Twig\Loader\FilesystemLoader(const_path.'apps'); if (is_dir(const_path.'pages/'.$actual_pages)) $loader->addPath(const_path.'pages/'.$actual_pages); - if (is_dir(const_path.'pages/'.$actual_pages.'/widgets')) - $loader->addPath(const_path.'pages/'.$actual_pages.'/widgets'); // deprecated as of v3.3 - remove in a later version - - if (dirname($request['page']) != '.' && is_dir(const_path.'pages/'.$actual_pages.'/'.dirname($request['page']))) - $loader->addPath(const_path.'pages/'.$actual_pages.'/'.dirname($request['page'])); + if (\dirname($request['page']) != '.' && is_dir(const_path.'pages/'.$actual_pages.'/'.\dirname($request['page']))) + $loader->addPath(const_path.'pages/'.$actual_pages.'/'.\dirname($request['page'])); - // add smarthome dir if it is not directly chosen. - // allows combination of custom pages with auto-generated pages from smarthomeNG + // add smarthome dir if it is not directly chosen - allows combination of custom pages with auto-generated pages from smarthomeNG if (substr(config_driver, 0, 9) == 'smarthome' and $actual_pages != 'smarthome' and is_dir(const_path."pages/smarthome")) $loader->addPath(const_path.'pages/smarthome'); - // make sure SV doesn't load stuff from dropins unless pages are configured + // make sure SV doesn't load stuff from dropins unless pages are configured if ($actual_pages != '') { $loader->addPath(const_path.'dropins'); - $loader->addPath(const_path.'dropins/widgets'); // deprecated as of v3.3 - remove in a later version - $loader->addPath(const_path.'dropins/shwidgets'); // deprecated as of v3.3 - remove in a later version } $loader->addPath(const_path.'pages/base'); - $loader->addPath(const_path.'widgets'); // deprecated as of v3.3 - remove in a later version + //---------------------------------------------------------- // create widgets path in namespace @widgets + //---------------------------------------------------------- $loader->addPath(const_path.'widgets', 'widgets'); $loader->addPath(const_path.'dropins/widgets', 'widgets'); $loader->addPath(const_path.'dropins/shwidgets', 'widgets'); if (is_dir(const_path.'pages/'.$actual_pages.'/widgets')) $loader->addPath(const_path.'pages/'.$actual_pages.'/widgets', 'widgets'); + //---------------------------------------------------------- // create icons path in namespace @icons + //---------------------------------------------------------- $loader->addPath(const_path.'icons/ws', 'icons'); $loader->addPath(const_path.'dropins/icons/ws', 'icons'); @@ -84,7 +84,7 @@ $twig = new \Twig\Environment($loader); $twig->addExtension(new \Twig\Extension\StringLoaderExtension()); - if (defined('config_debug')) { + if (\defined('config_debug')) { if (config_debug) { $twig->enableDebug(); $twig->addExtension(new \Twig\Extension\DebugExtension()); @@ -112,11 +112,10 @@ $twig->addGlobal($key, $val); } - // TO DO: better global concept for "config_pages" in order to distinguish between actual pages and configured pages - // today global value in php can be different from value with same name in Twig if the "pages" parameter is set + // attention: global value for config_pages in php can be different from value with same name in Twig if the "pages" parameter is set $twig->addGlobal('config_pages', $actual_pages); $twig->addGlobal('configured_pages', config_pages); - $twig->addGlobal('pagepath', dirname($request['page'])); + $twig->addGlobal('pagepath', \dirname($request['page'])); $twig->addGlobal('const_path', const_path); $twig->addGlobal('mbstring_available', function_exists('mb_get_info')); @@ -187,7 +186,7 @@ echo str_repeat(" ", 60).date('H:i, d.m').", v".config_version_full."\n"; echo str_repeat("-", 80)."\n\n"; echo "Error loading Page '".$request['page']."' !\n\n"; - echo "Check config.php -> 'config_pages' for correct Pages/Project configuration\n"; + echo "Check configuration page for correct pages / project configuration\n"; echo "or try the index page!\n\n"; echo str_repeat("-", 80)."\n\n"; echo "\n"; diff --git a/lang/de.ini b/lang/de.ini index c7ec6e908..30dd31cc9 100644 --- a/lang/de.ini +++ b/lang/de.ini @@ -21,6 +21,7 @@ w = "%01,2f W" kwh = "%01,2f kWh" date = "d.m.Y" +calendardate = "d.m.Y" day = "d.m." time = "H:i" short = "d.m.y H:i" @@ -72,6 +73,9 @@ wed = "Mi" thu = "Do" fri = "Fr" sat = "Sa" +[actualday] +today = "heute" +tomorrow = "morgen" [durations] year = "Jahr" @@ -113,6 +117,7 @@ axis = "Temperatur, Feuchtigkeit" ; ----------------------------------------------------------------------------- [weather] lang = "de" +units = "ca" n = "Nord" nne = "Nordnordost" @@ -131,6 +136,8 @@ wnw = "Westnordwest" nw = "Nordwest" nnw = "Nordnordwest" +clear sky = "klarer Himmel" + calm = "still" light air = "leiser Zug" light breeze = "leichte Brise" @@ -155,59 +162,6 @@ wind_gust = "Böen" wind_speed = "Geschwindigkeit" wind = "Wind" -[yr.no] -n = "Nord" -nne = "Nordnordost" -ne = "Nordost" -ene = "Ostnordost" -e = "Ost" -ese = "Ostsüdost" -se = "Südost" -sse = "Südsüdost" -s = "Süd" -ssw = "Südsüdwest" -sw = "Südwest" -wsw = "Westsüdwest" -w = "West" -wnw = "Westnordwest" -nw = "Nordwest" -nnw = "Nordnordwest" - -clear sky = "klarer Himmel" -fair = "Schönwetter" -partly cloudy = "leicht bewölkt" -cloudy = "bewölkt" -rain showers with thunder = "Regenschauer mit Gewitter" -rain showers = "Regenschauer" -sleet showers = "Graupelschauer" -snow showers = "Schneeschauer" -heavy rain = "starker Regen" -rain and thunder = "Regen und Gewitter" -light = "leichter" -rain = "Regen" -sleet showers and thunder = "Graupelschauer mit Gewitter" -sleet and thunder = "Graupel und Gewitter" -sleet = "Graupel" -snow and thunder = "Schnee und Gewitter" -snow = "Schnee" -fog = "Nebel" - -calm = "still" -light air = "leiser Zug" -light breeze = "leichte Brise" -gentle breeze = "schwache Brise" -moderate breeze = "mäßige Brise" -fresh breeze = "frische Brise" -strong breeze = "starker Wind" -near gale = "steifer Wind" -strong gale = "Sturm" -gale = "stürmischer Wind" -violent storm = "orkanartiger Sturm" -storm = "schwerer Sturm" -hurricane = "Orkan" - -from = "aus" - [met.no] units = "m" clearsky = "klarer Himmel" @@ -252,6 +206,13 @@ heavysleet = "starker Graupel" sleet = "Graupel" fog = "Nebel" +lightdrizzle = "leichter Sprühregen" +drizzle = "Sprühregen" +heavydrizzle = "starker Sprühregen" +freezing = "gefrierender" +thunderstorm = "Gewitter" +lighthailandthunder = "leichter Hagel mit Gewitter" +heavyhailandthunder = "schwerer Hagel mit Gewitter" [wunderground] lang = "DL" @@ -281,10 +242,6 @@ wind = "Wind" from = "aus" -[darksky] -lang = "de" -units = "si" - [openweathermap] lang = "de" units = "metric" @@ -304,6 +261,10 @@ rain = "Regen" sleet = "Graupel" snow = "Schnee" +[visualcrossing] +lang = "de" +units = "metric" + [phone] phonelist = "Telefonliste" incoming = "Eingehend" @@ -562,11 +523,14 @@ functions [label] = "Funktionen" updatecheck[label] = "Updateprüfung" updatecheck[hint] = "Auf smartvisu.de und github nach neuerer Version suchen." +collapsible_reset[label] = "Menüs zurücksetzen" +collapsible_reset[hint] = " Die einklappbaren Gruppen im Raum-Menü werden bei jeder Rückkehr auf eine Seite in den Ausgangszustand zurückgesetzt, (Deaktiviert: pro Seite wird jeweils die letzte Auswahl des Benutzers beibehalten.)" + driver_loopback [label] = "Item Bestätigung" driver_loopback [hint] = "Nach Senden von Befehlen aus der Visu wird das item erst durch die Antwort des Backends aktualisiert. (Ausgeschaltet: item wird innerhalb smartVISU aktualisiert.)" -collapsible_reset[label] = "Menüs zurücksetzen" -collapsible_reset[hint] = " Die einklappbaren Gruppen im Raum-Menü werden bei jeder Rückkehr auf eine Seite in den Ausgangszustand zurückgesetzt, (Deaktiviert: pro Seite wird jeweils die letzte Auswahl des Benutzers beibehalten.)" +driver_signalBusy [label] = "Aktivitätsanzeige" +driver_signalBusy [hint] = "Wenn aktiviert, blinkt das smartVISU-Logo, bis alle beim Backend abonnierten items empfangen wurden." save[label] = "Einstellungen speichern" @@ -587,6 +551,7 @@ expert = "Experte" del = "entf" act = "Akt" active = "Aktiv" +inactive = "Inaktiv" earliest = "frühestens" latest = "spätestens" sunset = "S-Untergang" @@ -613,6 +578,23 @@ seriesstart = "Startzeit" seriesintervall = "Intervall" seriesend = "Endzeit" seriescount = "Anzahl Zyklen" +once = "Einmal" + +[system] +system = "System" +help = "Hilfe" +config = "Einstellungen" +templatechecker = "Templatechecker" +backup = "Einstellungen sichern" +docu = "Dokumentation" +forum = "Unterstützung im knx-user-forum" +github = "Quellen auf Github" +newstuff = "Neue Widgets und Erweiterungen" +connected = "verbunden seit" +location = "Ortsinformationen" +moonphase = "Mondphase" +moonlight = "Mondlicht" + [backup] title = "Konfiguration sichern und wiederherstellen" export[head] = "Konfiguration exportieren" @@ -631,3 +613,90 @@ import[ownership_command] = "Bitte führen Sie folgenden Terminal-Befehl aus um import[forbidden_files] = "Die folgenden Dateien wurden aufgrund von Sicherheitsriskien nicht importiert, bitte fügen Sie diese händisch hinzu." unknown_command = "Unbekannter Befehl." zip_module_error = "PHP Zip-Modul nicht installiert. Bitte mit folgendem Befehl installieren und den Webserver neustarten:" + +[templatechecker] +system = "Prüfung Systemanforderungen" +pages = "Syntaxprüfung Visu-Seiten" +showEW = "Nur Warnungen und Fehler anzeigen" +subfolders = "Dateien in Unterordnern nicht prüfen" +timeout = "PHP Ausführungszeit für Skripte erhöhen - für langsame smartVISU Server" +items = "Itemtypen und -parameter nicht prüfen um Geschwindigkeit zu erhöhen" +filelist = "Dateien im gewählten Ordner" +author = "Autor" +file = "Datei" +is = "ist" +isnt = "ist nicht" +not = "nicht" +writeable = "schreibbar" +or = "oder" +can be created = "kann erstellt werden" +cant be created = "kann nicht erstellt werden" +directory = "Ordner" +extension = "Erweiterung" +loaded = "geladen" +run = "Alle Seiten prüfen" +reset = "Ergebnisse zurücksetzen" +oldversion = "Fehler: benötigt wird mindestens" +misuccess = "Masteritem Datei gelesen" +mifail = "Optionale masteritem Datei ist nicht verfügbar. Prüfung von itemtnamen und -typen deaktiviert" +errors = "Fehler" +warnings = "Warnungen" +infos = "Informationen" +notifications = "Anmerkungen" +nofile = "Keine Dateien für den aktuellen Anzeigemodus vorhanden" +nocheck = "Noch nicht geprüft" +checkfile ="Diese Datei prüfen" +recheck = "Datei erneut prüfen" + +[update] +ok = "smartVISU ist auf aktuellem Stand" +newer = "smartVISU Aktualisierung verfügbar!" +develop = "Diese Entwicklungsversion von smartVISU kann instabil sein." +skip = "Updateprüfung für 7 Tage ausgesetzt" +fail = "Keine Antwort von den Update-Servern" +github = "nur github" +local = "Aktuell installierte Version" +remote = "Letzte freigegebene Version" +visit = "Bitte gehen Sie auf " +latest = "um die neueste Version herunterzuladen" +view = "Liste der Änderungen" +recheck = "Die nächste Updateprüfung erfolgt in 7 Tagen" + +[widgetassi] +render = "Widget anzeigen und Code in die Zwischenablage kopieren" +preview ="Anzeige in neuem Fenster" +wildcard = "Joker Suche" +acUse = "Vervollständigen für" +colors = "Farben" +all = "Alle" +acOff = "Vervollständigen AUS" +waDocu = "Widget Anzeige / Anleitung" +description = "Beschreibung" +actual = "aktueller" + +[plot_analyser] +Global = "Einstellungen für den gesamten Plot" +Source = "Datenquelle" +Database = "Datenbank" +Time = "Zeitachse" +Axis = "Achse" +Unit = "Einheit" +active = "aktiv" +scale = "Skalierung" +right = "rechts" +left = "links" +Chartopts [hint] = "Highcharts-Objekt für Optionen. Schlüssel und Werte müssen jedoch in doppelten Anführungszeichen angegeben werden." +itemSettings = "Einstellungen für Items" +Item [hint] = "Für jede item-Gruppe Item 1 - Item 5 können mehrere items angegeben werden. Trennung durch Kommas." +Count [label] = "Anzahl Punkte" +Count [hint] = "Anzahl der Punkte pro item, getrennt durch Kommas." +Exposure [label] = "Diagrammtypen" +Exposure [hint] = "Diagrammtyp für jedes item. Gültige Werte:" +Color [label] = "Farben" +Color [hint] = "Farbe für jedes item, getrennt durch Kommas. Default: Highcharts-Farben" +Assign [label] = "zugehörige Y Achsen" +Assign [hint] = "Nummer der Y-Achse für jedes item. Default: 0" +Stacking [hint] = "Stapelmodus pro item. Gültige Werte:" +Stacks [hint] = "Nummer des Stapels pro item. Default: 0" +Shift [label] = "Intervall Zeitverschiebung" +Code [label] = "Erzeugter Widget-Code" diff --git a/lang/en-gb.ini b/lang/en-gb.ini index a863f16d4..a196b5776 100644 --- a/lang/en-gb.ini +++ b/lang/en-gb.ini @@ -16,7 +16,7 @@ day = "d/m" short = "d/m/y H:i" long = "d/m/Y H:i:s" -[darksky] +[weather] units = "uk2" [weather.com] @@ -30,3 +30,7 @@ units = "uk2" [openweathermap] units = "hybrid" + +[visualcrossing] +lang = "en" +units = "uk" diff --git a/lang/en.ini b/lang/en.ini index b586d09c0..623847352 100644 --- a/lang/en.ini +++ b/lang/en.ini @@ -25,6 +25,7 @@ kwh = "%01.2f kWh" lx = "%01.0f lx" date = "m/d/Y" +calendardate = "m/d/Y" day = "m/d" time = "H:i" short = "m/d/y H:i" @@ -76,6 +77,9 @@ wed = "Wed" thu = "Thu" fri = "Fri" sat = "Sat" +[actualday] +today = "today" +tomorrow = "tomorrow" [durations] year = "Year" @@ -119,6 +123,7 @@ axis = "Temperature, Humidity" [weather] lang = "en" +units = "us" n = "North" nne = "North-northeast" @@ -137,6 +142,8 @@ wnw = "West-northwest" nw = "Northwest" nnw = "North-northwest" +clear sky = "clear sky" + calm = "calm" light air = "light air" light breeze = "light breeze" @@ -160,58 +167,6 @@ precipitation = "precipitation" wind_gust = "wind gust" wind_speed = "wind speed" -[yr.no] -n = "North" -nne = "North-northeast" -ne = "Northeast" -ene = "East-northeast" -e = "East" -ese = "East-southeast" -se = "South-east" -sse = "South-southeast" -s = "South" -ssw = "South-southwest" -sw = "Southwest" -wsw = "West-southwest" -w = "West" -wnw = "West-northwest" -nw = "Northwest" -nnw = "North-northwest" - -clear sky = "clear sky" -fair = "fair" -partly cloudy = "partly cloudy" -cloudy = "cloudy" -rain showers with thunder = "rain showers with thunder" -rain showers = "rain showers" -sleet showers = "sleet showers" -snow showers = "snow showers" -heavy rain = "heavy rain" -rain and thunder = "rain and thunder" -rain = "rain" -sleet showers and thunder = "sleet showers and thunder" -sleet and thunder = "sleet and thunder" -sleet = "sleet" -snow and thunder = "snow and thunder" -snow = "snow" -fog = "fog" - -calm = "calm" -light air = "light air" -light breeze = "light breeze" -gentle breeze = "gentle breeze" -moderate breeze = "moderate breeze" -fresh breeze = "fresh breeze" -strong breeze = "strong breeze" -near gale = "near gale" -strong gale = "strong gale" -gale = "gale" -violent storm = "violent storm" -storm = "storm" -hurricane = "hurricane" - -from = "from" - [met.no] units = "e" clearsky = "clear sky" @@ -256,6 +211,14 @@ heavysleet = "heavy sleet" sleet = "sleet" fog = "fog" +lightdrizzle = "light drizzle" +drizzle = "drizzle" +heavydrizzle = "heavy drizzle" +freezing = "freezing" +thunderstorm = "thunderstorm" +lighthailandthunder = "light hail and thunder" +heavyhailandthunder = "heavy hail and thunder" + [wunderground] lang = "EN" @@ -284,10 +247,6 @@ wind = "Wind" from = "from" -[darksky] -lang = "en" -units = "us" - [openweathermap] lang = "en" units = "imperial" @@ -299,6 +258,10 @@ units = "e" lang = "en" units = "us" +[visualcrossing] +lang = "en" +units = "us" + [phone] phonelist = "Call list" incoming = "incoming" @@ -542,11 +505,14 @@ functions [label] = "Functions" updatecheck[label] = "Check for updates" updatecheck[hint] = "Look on smartvisu.de and github for a newer version." -driver_loopback [label] = "item loopback" +collapsible_reset[label] = "Reset menus" +collapsible_reset[hint] = " The collapsible groups in the rooms menu are returned to their default states on every return to a page. (Deactivated: last selection of the user is kept per page.)" + +driver_loopback [label] = "Item loopback" driver_loopback [hint] = "After sending commands from the visu, item gets updated only by an answer of the backend. (Deactivated: item gets updated immediatly inside smartVISU.)" -collapsible_reset[label] = "reset menus" -collapsible_reset[hint] = " The collapsible groups in the rooms menu are returned to their default states on every return to a page. (Deactivated: last selection of the user is kept per page.)" +driver_signalBusy [label] = "Websocket signal" +driver_signalBusy [hint] = "Lets the smartVISU logo blink until all subscribed items have been received from the backend." save[label] = "Save settings" @@ -567,6 +533,7 @@ expert = "Expert" del = "Del" act = "Act" active = "Active" +inactive = "Inactive" earliest = "earliest" latest = "latest" sunset = "sunset" @@ -593,6 +560,22 @@ seriesstart = "start time" seriesintervall = "interval" seriesend = "end time" seriescount = "cycles" +once = "once" + +[system] +system = "System" +help = "Help" +config = "Configuration" +templatechecker = "Template checker" +backup = "Backup config" +docu = "Documentation" +forum = "Support on knx-user-forum" +github = "Sources on Github" +newstuff = "New widgets and supplements" +connected = "connected since" +location = "location info" +moonphase = "moonphase" +moonlight = "moonlight" [backup] title = "Backup and restore configuration" @@ -612,3 +595,90 @@ import[ownership_command] = "Please run this terminal command to set the correct import[forbidden_files] = "The following files were not imported due to security risks, please add them manually." unknown_command = "Unknown command." zip_module_error = "PHP zip module not installed. Please install it with the following command and restart the webserver:" + +[templatechecker] +system = "System Requirements Checks" +pages = "Page Syntax Checks" +showEW = "Show warnings and errors only" +subfolders = "Don´t check files in subfolders" +timeout = "Extend PHP skript timeout - for slow smartVISU servers" +items = "Don´t check items / item parameter types - to increase performance" +filelist = "Files in template directory" +author = "author" +file = "file" +is = "is" +isnt = "is´nt" +not = "not" +writeable = "writeable" +or = "or" +can be created = "can be created" +cant be created = "can´t be created" +directory = "directory" +extension = "extension" +loaded = "loaded" +run = "Run all tests" +reset = "Reset results" +oldversion = "failure, you need at least" +misuccess = "Masteritem file read" +mifail = "Optional masteritem file is not available - check of item names and types deactivated" +errors = "Errors" +warnings = "Warnings" +infos = "Infos" +notifications = "Notifications" +nofile = "No files to display in current display mode" +nocheck = "Yet unchecked" +checkfile ="Check this file" +recheck = "Recheck this file" + +[update] +ok = "smartVISU is up to date" +newer = "smartVISU update available!" +develop = "This version from develop branch might be unstable" +skip = "update check skipped for 7 days" +fail = "no answer from update servers" +github = "github only" +local = "Your local version is" +remote = "Latest stable release is" +visit = "Please visit" +latest = "to download the latest version" +view = "View changelog" +recheck = "Update check will run again in 7 days from now" + +[widgetassi] +render = "Preview widget and copy final widget to clipboard" +preview ="Preview in new window" +wildcard = "Wildcard search" +acUse = "Autocomplete usage" +colors = "Colors" +all = "All" +acOff = "Autocomplete OFF" +waDocu = "Widget preview / assistant docu" +description = "Description" +actual = "actual" + +[plot_analyser] +Global = "Global Settings for the Plot" +Source = "Data Source" +Database = "Database" +Time = "Time Axis" +Axis = "Axis" +Unit = "Unit" +active = "active" +scale = "Scale Type" +right = "right" +left = "left" +Chartopts [hint] = "Highcharts object for options. Keys and values must however be entered in double quotes." +itemSettings = "Item specific Settings" +Item [hint] = "For every item group Item 1 - Item 5 several items can be defined, separated by commas." +Count [label] = "Count" +Count [hint] = "Number of points per item, separated by commas." +Exposure [label] = "Series types" +Exposure [hint] = "Series type for every item. Valid values:" +Color [label] = "Colors" +Color [hint] = "Color for every item, separated by commas. Default: Highcharts colors" +Assign [label] = "Related Y Axes" +Assign [hint] = "Number of the Y axis for every item. Default: 0" +Stacking [hint] = "Stacking mode per item. Valid values:" +Stacks [hint] = "Number of the assigned stack per item. Default: 0" +Shift [label] = "Timeline shift interval" +Code [label] = "Generated widget code" \ No newline at end of file diff --git a/lang/fr.ini b/lang/fr.ini index eaacd8e93..ec34072cb 100644 --- a/lang/fr.ini +++ b/lang/fr.ini @@ -21,6 +21,7 @@ w = "%01,2f W" kwh = "%01,2f kWh" date = "d/m/Y" +calendardate = "d/m/Y" day = "d/m" time = "H:i" short = "d/m/y H:i" @@ -72,6 +73,9 @@ wed = "Mer" thu = "Jeu" fri = "Ven" sat = "Sam" +[actualday] +today = "aujourd´hui" +tomorrow = "demain" [durations] year = "année" @@ -114,6 +118,7 @@ axis = "Température, Humidité" [weather] lang = "fr" +units = "ca" n = "nord" nne = "nord-nord-est" @@ -132,6 +137,8 @@ wnw = "ouest-nord-ouest" nw = "nord-ouest" nnw = "nord-nord-ouest" +clear sky = "ciel dégagé" + calm = "calme" light air = "très légère brise" light breeze = "légère brise" @@ -200,6 +207,14 @@ heavysleet = "neige fondue forte" sleet = "neige fondue" fog = "brouillard" +lightdrizzle = "bruine legère" +drizzle = "bruine" +heavydrizzle = "bruine forte" +freezing = "congélante" +thunderstorm = "orage" +lighthailandthunder = "grêle legère et orage" +heavyhailandthunder = "grêle forte et orage" + [wunderground] lang = "FR" @@ -231,10 +246,6 @@ from_ns = "de" from_ew = "d'" from = "de" -[darksky] -lang = "fr" -units = "si" - [openweathermap] lang = "fr" units = "metric" @@ -254,6 +265,10 @@ rain = "pluie" sleet = "neige fondue" snow = "neige" +[visualcrossing] +lang = "fr" +units = "metric" + [phone] phonelist = "Liste des coups de fil" unknown = "inconnu" @@ -338,3 +353,19 @@ sort = "sorte" cancel = "abandonner" ok = "OK" sun = "soleil" +once = "une fois" + +[system] +system = "Système" +help = "Aide" +config = "Configuration" +templatechecker = "Template checker" +backup = "Sauvegarde de configuration" +docu = "Documentation" +forum = "Support dans le knx-user-forum" +github = "Sources sur Github" +newstuff = "Nouveaux widgets et additifs" +connected = "connecté depuis" +location = "Informations du lieu" +moonphase = "phase de la lune" +moonlight = "lumière de la lune" diff --git a/lang/it.ini b/lang/it.ini index 529da34f7..923d61e03 100644 --- a/lang/it.ini +++ b/lang/it.ini @@ -21,6 +21,7 @@ w = "%01,2f W" kwh = "%01,2f kWh" date = "d.m.Y" +calendardate = "d.m.Y" day = "d.m." time = "H:i" short = "d.m.y H:i" @@ -72,6 +73,9 @@ wed = "Mer" thu = "Gio" fri = "Ven" sat = "Sab" +[actualday] +today = "oggi" +tomorrow = "domani" [durations] year = "Anno" @@ -113,6 +117,7 @@ axis = "Temperatura, Umidità" ; ----------------------------------------------------------------------------- [weather] lang = "de" +units = "ca" n = "Nord" nne = "Nord-Nord-Est" @@ -131,6 +136,8 @@ wnw = "Ovest-Nord-Ovest" nw = "Nord-Ovest" nnw = "Nord-Nord-Ovest" +clear sky = "cielo sereno" + calm = "calma" light air = "bava di vento" light breeze = "brezza leggera " @@ -155,59 +162,6 @@ wind_gust = "raffiche di vento" wind_speed = "velocità del vento" wind = "vento" -[yr.no] -n = "Nord" -nne = "Nord-Nord-Est" -ne = "Nord-Est" -ene = "Est-Nord-Est" -e = "Est" -ese = "Est-Sud-Est" -se = "Sud-Est" -sse = "Sud-Sud-Est" -s = "Sud" -ssw = "Sud-Sud-Ovest" -sw = "Sud-Ovest" -wsw = "Ovest-Sud-Ovest" -w = "Ovest" -wnw = "Ovest-Nord-Ovest" -nw = "Nord-Ovest" -nnw = "Nord-Nord-Ovest" - -clear sky = "cielo sereno" -fair = "bel tempo" -partly cloudy = "leggermente nuvoloso" -cloudy = "nuvoloso" -rain showers with thunder = "rovesci di pioggia con temporali" -rain showers = "rovesci di pioggia" -sleet showers = "rovesci di nevischio" -snow showers = "rovesci di neve" -heavy rain = "pioggia battente" -rain and thunder = "pioggia e temporali" -light = "leggera" -rain = "pioggia" -sleet showers and thunder = "rovesci di nevischio e temporali" -sleet and thunder = "nevischio e temporali" -sleet = "nevischio" -snow and thunder = "neve e tempoprali" -snow = "neve" -fog = "nebbia" - -calm = "calma" -light air = "bava di vento" -light breeze = "brezza leggera " -gentle breeze = "brezza tesa " -moderate breeze = "vento moderato" -fresh breeze = "vento teso" -strong breeze = "vento fresco" -near gale = "vento forte" -strong gale = "burrasca forte" -gale = "burrasca" -violent storm = "tempesta violenta" -storm = "tempesta" -hurricane = "uragano" - -from = "da" - [met.no] units = "m" clearsky = "cielo sereno" @@ -252,6 +206,13 @@ heavysleet = "nevischio forte" sleet = "nevischio" fog = "nebbia" +lightdrizzle = "pioggerella leggera" +drizzle = "pioggerella" +heavydrizzle = "pioggerella forte" +freezing = "congelante" +thunderstorm = "temporali" +lighthailandthunder = "grandine leggere e temporali" +heavyhailandthunder = "grandine forte e temporali" [wunderground] lang = "IT" @@ -281,10 +242,6 @@ wind = "vento" from = "da" -[darksky] -lang = "it" -units = "si" - [openweathermap] lang = "it" units = "metric" @@ -304,6 +261,10 @@ rain = "pioggia" sleet = "nevischio" snow = "nevicata" +[visualcrossing] +lang = "it" +units = "metric" + [phone] phonelist = "Registro chiamate" incoming = "in arrivo" @@ -578,6 +539,23 @@ seriesstart = "Ora di inizio" seriesintervall = "Intervallo" seriesend = "Tempo finale" seriescount = "Numero di cicli" +once = "una volta" + +[system] +system = "Sistema" +help = "Aiuti" +config = "Configurazione" +templatechecker = "Templatechecker" +backup = "Backup configurazione" +docu = "Documentazione" +forum = "Assistenza nel knx-user-forum" +github = "Codice sorgente su Github" +newstuff = "Nuovi widget e estensioni" +connected = "interconnettato da" +location = "Informazioni del luogo" +moonphase = "fase lunare" +moonlight = "luce lunare" + [backup] title = "Backup e ripristino della configurazione" export[head] = "Esporta configurazione" diff --git a/lang/nl.ini b/lang/nl.ini index 149e3c828..9b19af055 100644 --- a/lang/nl.ini +++ b/lang/nl.ini @@ -22,6 +22,7 @@ w = "%01,2f W" kwu = "%01,2f kWh" date = "d-m-Y" +calendardate = "d-m-Y" day = "d-m" time = "H:i" short = "d-m-y H:i" @@ -73,6 +74,9 @@ wed = "wo" thu = "do" fri = "vr" sat = "za" +[actualday] +today = "vandaag" +tomorrow = "morgen" [DateBox] lang = "nl" @@ -95,6 +99,7 @@ axis = "Temperatuur, Vochtigheid" ; ----------------------------------------------------------------------------- [weather] lang = "nl" +units = "ca" n = "Noord" nne = "Noordnoordoost" @@ -113,6 +118,8 @@ wnw = "Westnoordwest" nw = "Noordwest" nnw = "Noordnoordwest" +clear sky = "heldere hemel" + calm = "windstil" light air = "flauwe bries" light breeze = "lichte bries" @@ -180,6 +187,14 @@ heavysleet = "zware natte sneeuw" sleet = "natte sneeuw" fog = "mist" +lightdrizzle = "lichte motregen" +drizzle = "motregen" +heavydrizzle = "zware motregen" +freezing = "freezing" +thunderstorm = "onweer" +lighthailandthunder = "lichte hagel en onweer" +heavyhailandthunder = "zware hagel en onweer" + [wunderground] lang = "NL" @@ -207,10 +222,6 @@ wind = "Wind" from = "uit" -[darksky] -lang = "nl" -units = "si" - [openweathermap] lang = "nl" units = "metric" @@ -230,6 +241,10 @@ rain = "regenval" sleet = "natte sneeuw" snow = "sneeuw" +[visualcrossing] +lang = "nl" +units = "metric" + [phone] unknown = "onbekend" incoming = "inkomend" @@ -260,3 +275,4 @@ sort = "sorteren" cancel = "afbreken" ok = "OK" sun = "zon" +once = "eenmal" diff --git a/lib/base/base.js b/lib/base/base.js index 741bd02a3..4fb346b5a 100755 --- a/lib/base/base.js +++ b/lib/base/base.js @@ -2,7 +2,7 @@ * ----------------------------------------------------------------------------- * @package smartVISU * @author Martin Gleiß, Stefan Widmer, Wolfram v. Hülsen - * @copyright 2012 - 2023 + * @copyright 2012 - 2024 * @license GPL [http://www.gnu.de] * ----------------------------------------------------------------------------- */ @@ -110,7 +110,7 @@ function printf(format, val) { function activateAutoReconnect() { if(io.socket) { var autoReconnectIntervalId = window.setInterval(function () { - if(io.socket != null && io.socket.readyState == 3) { + if(io.socket != null && (io.socket.readyState == 3 || (io.ping && io.socketState == 'offline'))) { if(document.visibilityState && document.visibilityState != 'hidden'){ console.log("WebSocket closed, reconnect..."); io.init(); @@ -507,6 +507,22 @@ if(!Array.prototype.equals) { Object.defineProperty(Array.prototype, "equals", {enumerable: false}); } +/** + * Removes the first occurrence of a given array element + */ + if(!Array.prototype.removeEntry) { + Array.prototype.removeEntry = function(value) { + if (!value) + return false; + var index = this.indexOf(value); + if (index > -1) + this.splice(index,1); + return true; + } + // Hide method from for-in loops + Object.defineProperty(Array.prototype, "removeEntry", {enumerable: false}); +} + /** * Get values of an Object * (Polyfill for IE and older browsers) @@ -1016,7 +1032,7 @@ var notify = { * Add a new message */ add: function (level, signal, title, text, ackitem, ackval) { - if (text.toLowerCase().trim().substr(-16 ) == 'operation failed') + if (text.toString().toLowerCase().trim().substr(-16 ) == 'operation failed') text += '

Notice:
The message "operation failed" is thrown if a connect fails before the request can be executed. ' +'Very often, this is due to failed ssl communication. Try calling the service directly in debug mode for more information.

' +'Example:
YourIP/YourSmartvisuDir/lib/weather/service/YourService.php?debug=1'; @@ -1407,7 +1423,12 @@ var widget = { } } else if (value !== undefined) { - widget.buffer[item] = ( $.isNumeric(value) ? value * 1.0 : value); + // preserve string containing a number with leading zero + if (typeof value == 'string' && value.substring(0,1) == '0' && value.length > 1) + widget.buffer[item] = value; + else + // convert all other strings containing numbers to float / keep string with non-numeric content unchanged + widget.buffer[item] = ( $.isNumeric(value) ? value * 1.0 : value); } // DEBUG: console.log("[widget] '" + item, widget.buffer[item]); @@ -1476,7 +1497,7 @@ var widget = { aggregate = aggregate.substr(5); if(aggregate != undefined && aggregate.length > 5 && aggregate.substr(0,5) == 'count') aggregate = aggregate.substr(0,5); - return ($.inArray(aggregate, Array('avg', 'min', 'max', 'sum', 'diff', 'rate', 'on', 'raw', 'count')) >= 0); + return ($.inArray(aggregate, io.aggregates) >= 0); }, /** diff --git a/lib/base/base.php b/lib/base/base.php index 3b1a63239..2a4799156 100644 --- a/lib/base/base.php +++ b/lib/base/base.php @@ -2,8 +2,8 @@ /** * ----------------------------------------------------------------------------- * @package smartVISU - * @author Martin Gleiß, Stefan Widmer - * @copyright 2012 - 2017 + * @author Martin Gleiß, Stefan Widmer, Wolfram v. Hülsen + * @copyright 2012 - 2024 * @license GPL [http://www.gnu.de] * ----------------------------------------------------------------------------- */ @@ -71,6 +71,7 @@ + if (unit == '') { return this; @@ -82,10 +83,26 @@ // 259200000 var val = this; + + // replace a selectable format string with "today" or "tomorrow" if applicable + // format string to replace must be given in brackets (e.g. 't(d)' prints the short weekday if it is not today or tomorrow) + var tPos = ret.indexOf('t'); + if (tPos > -1){ + var daynow = ''; + var tomorrow = new Date(new Date().valueOf() + 24*60*60*1000).toDateString(); + if (val.toDateString() == new Date().toDateString()) + daynow = actualday[0]; + else if (val.toDateString() == tomorrow) + daynow = actualday[1]; + if (ret[tPos+1] == "(" ) + ret = daynow != '' ? ret.replace(/\(.*\)/, '') : ret.replace(/[\(\)]/g, ''); + } // iterate over each character ret = ret.replace(/./g, function(char) { switch (char) { + case 't': + return daynow; case 'l': return weekday[val.getDay()]; case 'D': @@ -222,6 +239,7 @@ ?> }, reconnectTime: parseInt(''), + pingInterval: parseInt(''), timezone: '' } }; diff --git a/lib/base/check_config.php b/lib/base/check_config.php index 4eacd3875..f88ea85fe 100644 --- a/lib/base/check_config.php +++ b/lib/base/check_config.php @@ -17,12 +17,12 @@ if ((!is_file(const_path.'config.ini') && is_writeable(const_path)) || (is_file(const_path.'config.ini') && is_writeable(const_path.'config.ini'))) { - $ret = array('icon' => 'message_ok.svg', 'text' => "'config.ini' file is writeable or can be created"); + $ret = array('icon' => 'message_ok.svg', 'text' => "'config.ini' ".translate('file is writeable or can be created', 'templatechecker')); } else { header("HTTP/1.0 600 smartVISU Config Error"); - $ret = array('icon' => 'message_attention.svg', 'text' => "'".const_path."config.ini' file isn't writeable or can't be created!"); + $ret = array('icon' => 'message_attention.svg', 'text' => "'".const_path."config.ini' ".translate('file isnt writeable or cant be created', 'templatechecker')); } echo json_encode($ret); diff --git a/lib/base/check_masteritem.php b/lib/base/check_masteritem.php index 9dc6a1941..e742c8a60 100644 --- a/lib/base/check_masteritem.php +++ b/lib/base/check_masteritem.php @@ -3,7 +3,7 @@ * ----------------------------------------------------------------------------- * @package smartVISU * @author Andre Kohler - * @copyright 2020 + * @copyright 2020 -2024 * @license GPL [http://www.gnu.de] * ----------------------------------------------------------------------------- */ @@ -53,12 +53,12 @@ function load_items () $items = load_items(); if (count($items)>1) { - $ret = array('icon' => 'message_ok.svg', 'text' => "Masteritem file read"); + $ret = array('icon' => 'message_ok.svg', 'text' => trans('templatechecker', 'misuccess')); } else { header("HTTP/1.1 500 Internal Server Error"); - $ret = array('icon' => 'message_attention.svg', 'text' => "Optional masteritem file is not available - check of item names and types deactivated"); + $ret = array('icon' => 'message_attention.svg', 'text' => trans('templatechecker', 'mifail')); } echo json_encode($ret); diff --git a/lib/base/check_php.php b/lib/base/check_php.php index 19e5089ec..59a2950b5 100644 --- a/lib/base/check_php.php +++ b/lib/base/check_php.php @@ -2,8 +2,8 @@ /** * ----------------------------------------------------------------------------- * @package smartVISU - * @author Martin Gleiss - * @copyright 2012 - 2015 + * @author Martin Gleiß + * @copyright 2012 - 2024 * @license GPL [http://www.gnu.de] * ----------------------------------------------------------------------------- */ @@ -20,12 +20,12 @@ if (version_compare(PHP_VERSION, $reqver, '>=')) { - $ret = array('icon' => 'message_ok.svg', 'text' => 'PHP v'.phpversion().' is ok'); + $ret = array('icon' => 'message_ok.svg', 'text' => 'PHP v'.phpversion().' '.translate(' is ok', 'templatechecker')); } else { header("HTTP/1.0 600 smartVISU Config Error"); - $ret = array('icon' => 'message_attention.svg', 'text' => 'PHP v'.phpversion().' failure, you need at least '.$reqver.'!'); + $ret = array('icon' => 'message_attention.svg', 'text' => 'PHP v'.phpversion().' '.trans('templatechecker', 'oldversion'.' '.$reqver.'!')); } echo json_encode($ret); diff --git a/lib/base/check_php_mbstring.php b/lib/base/check_php_mbstring.php index 02502cbbd..71d8a019f 100644 --- a/lib/base/check_php_mbstring.php +++ b/lib/base/check_php_mbstring.php @@ -3,7 +3,7 @@ * ----------------------------------------------------------------------------- * @package smartVISU * @author Martin Gleiß - * @copyright 2012 - 2015 + * @copyright 2012 - 2024 * @license GPL [http://www.gnu.de] * ----------------------------------------------------------------------------- */ @@ -17,12 +17,12 @@ if (extension_loaded("mbstring")) { - $ret = array('icon' => 'message_ok.svg', 'text' => "PHP extension 'mbstring' loaded"); + $ret = array('icon' => 'message_ok.svg', 'text' => translate("PHP extension 'mbstring' loaded", 'templatechecker')); } else { header("HTTP/1.0 600 smartVISU Config Error"); - $ret = array('icon' => 'message_attention.svg', 'text' => "PHP extension 'mbstring' not loaded!"); + $ret = array('icon' => 'message_attention.svg', 'text' => translate("PHP extension 'mbstring' not loaded!", 'templatechecker')); } echo json_encode($ret); diff --git a/lib/base/check_temp.php b/lib/base/check_temp.php index 4c7f26c1d..5e46e740c 100644 --- a/lib/base/check_temp.php +++ b/lib/base/check_temp.php @@ -17,12 +17,12 @@ if (is_writeable(const_path.'temp')) { - $ret = array('icon' => 'message_ok.svg', 'text' => "'temp' directory is writeable"); + $ret = array('icon' => 'message_ok.svg', 'text' => "'temp' ".translate('directory is writeable', 'templatechecker')); } else { header("HTTP/1.0 600 smartVISU Config Error"); - $ret = array('icon' => 'message_attention.svg', 'text' => "'".const_path."temp' directory is not writeable!"); + $ret = array('icon' => 'message_attention.svg', 'text' => "'".const_path."temp' ".translate('directory is not writeable', 'templatechecker')); } echo json_encode($ret); diff --git a/lib/base/check_update.php b/lib/base/check_update.php index d3a7c9a3b..21de1b4f5 100644 --- a/lib/base/check_update.php +++ b/lib/base/check_update.php @@ -2,8 +2,8 @@ /** * ----------------------------------------------------------------------------- * @package smartVISU - * @author Martin Gleiss, Wolfram v. Huelsen - * @copyright 2012 - 2015 + * @author Martin Gleiß, Wolfram v. Hülsen + * @copyright 2012 - 2024 * @license GPL [http://www.gnu.de] * ----------------------------------------------------------------------------- */ @@ -18,7 +18,7 @@ set_error_handler( function($errno, $errstr, $errfile, $errline) { - if (defined('config_debug') && config_debug == 1) + if (\defined('config_debug') && config_debug == 1) return false; // hand over to standard error reporting else return true; @@ -48,33 +48,33 @@ function($errno, $errstr, $errfile, $errline) $ret["update"] = (($VersionMajor > config_version_major) or ($VersionMinor > config_version_minor) or ($VersionRevision > config_version_revision) ? true : false); $extension = ""; if ((isset($data_sv) && !$data_sv->update) && $ret["update"]) - $extension = ' (github only)'; + $extension = ' ('.trans('templatechecker', 'github').')'; $ret["remote"] = $VersionMajor.".".$VersionMinor.".".$VersionRevision.$extension; if (config_version_revision >= "a") { $ret["update"] = true; $ret["icon"] = 'message_attention.svg'; - $ret["text"] = 'This version from develop branch might be unstable'; + $ret["text"] = trans('update','develop'); } else { if ( $ret["update"] ) { $ret["icon"] = 'message_attention.svg'; - $ret["text"] = 'smartVISU update available!'; + $ret["text"] = trans('update','newer'); } else { $ret["icon"] = 'message_ok.svg'; - $ret["text"] = 'smartVISU is up to date'; + $ret["text"] = trans('update','ok'); } } } else { $ret["update"] = false; $ret["icon"] = 'text_na.svg'; - $ret["text"] = 'no answer from update servers'; + $ret["text"] = trans('update','fail'); } if ($ret["update"]){ - $basepath = substr(parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH), 0, -strlen(substr($_SERVER['SCRIPT_FILENAME'], strlen(const_path)))); + $basepath = substr(parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH), 0, -\strlen(substr($_SERVER['SCRIPT_FILENAME'], \strlen(const_path)))); $exptime = time()+3600*24*7; // 1 week until next version check setcookie('updchk', 'version checked', ['expires' => $exptime, 'path' => $basepath, 'samesite' => 'Lax']); } @@ -83,7 +83,7 @@ function($errno, $errstr, $errfile, $errline) { $ret["update"] = false; $ret["icon"] = 'message_ok.svg'; - $ret["text"] = 'update check skipped for 7 days'; + $ret["text"] = trans('update','skip'); } echo json_encode($ret); ?> diff --git a/lib/calendar/ICal/Event.php b/lib/calendar/ICal/Event.php index 4657d271e..8845a66de 100644 --- a/lib/calendar/ICal/Event.php +++ b/lib/calendar/ICal/Event.php @@ -32,7 +32,7 @@ class Event /** * https://www.kanzaki.com/docs/ical/duration.html * - * @var string + * @var string|null */ public $duration; @@ -81,14 +81,14 @@ class Event /** * https://www.kanzaki.com/docs/ical/description.html * - * @var string + * @var string|null */ public $description; /** * https://www.kanzaki.com/docs/ical/location.html * - * @var string + * @var string|null */ public $location; @@ -132,7 +132,7 @@ class Event * * @var array */ - private $additionalProperties = array(); + public $additionalProperties = array(); /** * Creates the Event object @@ -250,10 +250,15 @@ public function printData($html = self::HTML_TEMPLATE) */ protected static function snakeCase($input, $glue = '_', $separator = '-') { - $input = preg_split('/(?<=[a-z])(?=[A-Z])/x', $input); - $input = implode($glue, $input); - $input = str_replace($separator, $glue, $input); + $inputSplit = preg_split('/(?<=[a-z])(?=[A-Z])/x', $input); + + if ($inputSplit === false) { + return $input; + } + + $inputSplit = implode($glue, $inputSplit); + $inputSplit = str_replace($separator, $glue, $inputSplit); - return strtolower($input); + return strtolower($inputSplit); } } diff --git a/lib/calendar/ICal/ICal.php b/lib/calendar/ICal/ICal.php index 9252975c5..f4aab166d 100644 --- a/lib/calendar/ICal/ICal.php +++ b/lib/calendar/ICal/ICal.php @@ -92,16 +92,18 @@ class ICal public $disableCharacterReplacement = false; /** - * With this being non-null the parser will ignore all events more than roughly this many days after now. + * If this value is an integer, the parser will ignore all events more than roughly this many days before now. + * If this value is a date, the parser will ignore all events occurring before this date. * - * @var integer|null + * @var \DateTimeInterface|integer|null */ public $filterDaysBefore; /** - * With this being non-null the parser will ignore all events more than roughly this many days before now. + * If this value is an integer, the parser will ignore all events more than roughly this many days after now. + * If this value is a date, the parser will ignore all events occurring after this date. * - * @var integer|null + * @var \DateTimeInterface|integer|null */ public $filterDaysAfter; @@ -517,14 +519,39 @@ public function __construct($files = false, array $options = array()) // Fallback to use the system default time zone if (!isset($this->defaultTimeZone) || !$this->isValidTimeZoneId($this->defaultTimeZone)) { - $this->defaultTimeZone = date_default_timezone_get(); + $this->defaultTimeZone = $this->getDefaultTimeZone(true); } // Ideally you would use `PHP_INT_MIN` from PHP 7 $php_int_min = -2147483648; - $this->windowMinTimestamp = is_null($this->filterDaysBefore) ? $php_int_min : (new \DateTime('now'))->sub(new \DateInterval('P' . $this->filterDaysBefore . 'D'))->getTimestamp(); - $this->windowMaxTimestamp = is_null($this->filterDaysAfter) ? PHP_INT_MAX : (new \DateTime('now'))->add(new \DateInterval('P' . $this->filterDaysAfter . 'D'))->getTimestamp(); + $this->windowMinTimestamp = $php_int_min; + + if (!is_null($this->filterDaysBefore)) { + if (is_int($this->filterDaysBefore)) { + $this->windowMinTimestamp = (new \DateTime('now')) + ->sub(new \DateInterval('P' . $this->filterDaysBefore . 'D')) + ->getTimestamp(); + } + + if ($this->filterDaysBefore instanceof \DateTimeInterface) { + $this->windowMinTimestamp = $this->filterDaysBefore->getTimestamp(); + } + } + + $this->windowMaxTimestamp = PHP_INT_MAX; + + if (!is_null($this->filterDaysAfter)) { + if (is_int($this->filterDaysAfter)) { + $this->windowMaxTimestamp = (new \DateTime('now')) + ->add(new \DateInterval('P' . $this->filterDaysAfter . 'D')) + ->getTimestamp(); + } + + if ($this->filterDaysAfter instanceof \DateTimeInterface) { + $this->windowMaxTimestamp = $this->filterDaysAfter->getTimestamp(); + } + } $this->shouldFilterByWindow = !is_null($this->filterDaysBefore) || !is_null($this->filterDaysAfter); @@ -553,7 +580,7 @@ public function initString($string) { $string = str_replace(array("\r\n", "\n\r", "\r"), "\n", $string); - if (empty($this->cal)) { + if ($this->cal === array()) { $lines = explode("\n", $string); $this->initLines($lines); @@ -572,7 +599,7 @@ public function initString($string) */ public function initFile($file) { - if (empty($this->cal)) { + if ($this->cal === array()) { $lines = $this->fileOrUrl($file); $this->initLines($lines); @@ -649,12 +676,7 @@ protected function initLines(array $lines) $line = $this->cleanCharacters($line); } - $add = $this->keyValueFromString($line); - - if ($add === false) { - continue; - } - + $add = $this->keyValueFromString($line); $keyword = $add[0]; $values = $add[1]; // May be an array containing multiple values @@ -760,7 +782,7 @@ protected function initLines(array $lines) $this->processRecurrences(); // Apply changes to altered recurrence instances - if (!empty($this->alteredRecurrenceInstances)) { + if ($this->alteredRecurrenceInstances !== array()) { $events = $this->cal['VEVENT']; foreach ($this->alteredRecurrenceInstances as $alteredRecurrenceInstance) { @@ -793,7 +815,7 @@ protected function removeLastEventIfOutsideWindowAndNonRecurring() { $events = $this->cal['VEVENT']; - if (!empty($events)) { + if ($events !== array()) { $lastIndex = count($events) - 1; $lastEvent = $events[$lastIndex]; @@ -816,7 +838,7 @@ protected function reduceEventsToMinMaxRange() { $events = (isset($this->cal['VEVENT'])) ? $this->cal['VEVENT'] : array(); - if (!empty($events)) { + if ($events !== array()) { foreach ($events as $key => $anEvent) { if ($anEvent === null) { unset($events[$key]); @@ -875,9 +897,10 @@ protected function unfold(array $lines) { $string = implode(PHP_EOL, $lines); $string = str_ireplace(' ', ' ', $string); - $string = preg_replace('/' . PHP_EOL . '[ \t]/', '', $string); - $lines = explode(PHP_EOL, $string); + $cleanedString = preg_replace('/' . PHP_EOL . '[ \t]/', '', $string); + + $lines = explode(PHP_EOL, $cleanedString ?: $string); return $lines; } @@ -953,7 +976,7 @@ protected function addCalendarComponentWithKeyAndValue($component, $keyword, $va } } - if ($this->cal[$key1][$key2][$keyword] !== $value) { + if (!is_array($value) && $this->cal[$key1][$key2][$keyword] !== $value) { $this->cal[$key1][$key2][$keyword] .= ',' . $value; } } @@ -992,14 +1015,16 @@ protected function addCalendarComponentWithKeyAndValue($component, $keyword, $va break; } - $this->lastKeyword = $keyword; + if (is_string($keyword)) { + $this->lastKeyword = $keyword; + } } /** * Gets the key value pair from an iCal string * * @param string $text - * @return array|boolean + * @return array */ public function keyValueFromString($text) { @@ -1107,11 +1132,29 @@ protected function parseLine($line) return $words; } + /** + * Returns the default time zone if set. + * Falls back to the system default if not set. + * + * @param boolean $forceReturnSystemDefault + * @return string + */ + private function getDefaultTimeZone($forceReturnSystemDefault = false) + { + $systemDefault = date_default_timezone_get(); + + if ($forceReturnSystemDefault) { + return $systemDefault; + } + + return $this->defaultTimeZone ?: $systemDefault; + } + /** * Returns a `DateTime` object from an iCal date time format * * @param string $icalDate - * @return \DateTime + * @return \DateTime|false * @throws \Exception */ public function iCalDateToDateTime($icalDate) @@ -1128,14 +1171,14 @@ public function iCalDateToDateTime($icalDate) */ $pattern = '/^(?:TZID=)?([^:]*|".*")'; // [1]: Time zone $pattern .= ':?'; // Time zone delimiter - $pattern .= '([0-9]{8})'; // [2]: YYYYMMDD + $pattern .= '(\d{8})'; // [2]: YYYYMMDD $pattern .= 'T?'; // Time delimiter - $pattern .= '(?(?<=T)([0-9]{6}))'; // [3]: HHMMSS (filled if delimiter present) + $pattern .= '(?(?<=T)(\d{6}))'; // [3]: HHMMSS (filled if delimiter present) $pattern .= '(Z?)/'; // [4]: UTC flag preg_match($pattern, $icalDate, $date); - if (empty($date)) { + if ($date === array()) { throw new \Exception('Invalid iCal date format.'); } @@ -1148,7 +1191,7 @@ public function iCalDateToDateTime($icalDate) } elseif (isset($date[1]) && $date[1] !== '') { $dateTimeZone = $this->timeZoneStringToDateTimeZone($date[1]); } else { - $dateTimeZone = new \DateTimeZone($this->defaultTimeZone); + $dateTimeZone = new \DateTimeZone($this->getDefaultTimeZone()); } // The exclamation mark at the start of the format string indicates that if a @@ -1172,7 +1215,15 @@ public function iCalDateToDateTime($icalDate) */ public function iCalDateToUnixTimestamp($icalDate) { - return $this->iCalDateToDateTime($icalDate)->getTimestamp(); + $iCalDateToDateTime = $this->iCalDateToDateTime($icalDate); + + if ($iCalDateToDateTime === false) { + trigger_error("ICal::iCalDateToUnixTimestamp: Invalid date passed ({$icalDate})", E_USER_NOTICE); + + return 0; + } + + return $iCalDateToDateTime->getTimestamp(); } /** @@ -1181,7 +1232,7 @@ public function iCalDateToUnixTimestamp($icalDate) * @param array $event * @param string $key * @param string|null $format - * @return string|boolean|\DateTime + * @return string|integer|boolean|\DateTime */ public function iCalDateWithTimeZone(array $event, $key, $format = self::DATE_TIME_FORMAT) { @@ -1192,14 +1243,24 @@ public function iCalDateWithTimeZone(array $event, $key, $format = self::DATE_TI $dateArray = $event["{$key}_array"]; if ($key === 'DURATION') { - $dateTime = $this->parseDuration($event['DTSTART'], $dateArray[2], null); + $dateTime = $this->parseDuration($event['DTSTART'], $dateArray[2]); + + if ($dateTime instanceof \DateTime === false) { + trigger_error("ICal::iCalDateWithTimeZone: Invalid date passed ({$event['DTSTART']})", E_USER_NOTICE); + + return false; + } } else { // When constructing from a Unix Timestamp, no time zone needs passing. $dateTime = new \DateTime("@{$dateArray[2]}"); } - // Set the time zone we wish to use when running `$dateTime->format`. - $dateTime->setTimezone(new \DateTimeZone($this->calendarTimeZone())); + $calendarTimeZone = $this->calendarTimeZone(); + + if (!is_null($calendarTimeZone)) { + // Set the time zone we wish to use when running `$dateTime->format`. + $dateTime->setTimezone(new \DateTimeZone($calendarTimeZone)); + } if (is_null($format)) { return $dateTime; @@ -1220,7 +1281,7 @@ protected function processEvents() $checks = null; $events = (isset($this->cal['VEVENT'])) ? $this->cal['VEVENT'] : array(); - if (!empty($events)) { + if ($events !== array()) { foreach ($events as $key => $anEvent) { foreach (array('DTSTART', 'DTEND', 'RECURRENCE-ID') as $type) { if (isset($anEvent[$type])) { @@ -1290,7 +1351,7 @@ protected function processRecurrences() $events = (isset($this->cal['VEVENT'])) ? $this->cal['VEVENT'] : array(); // If there are no events, then we have nothing to process. - if (empty($events)) { + if ($events === array()) { return; } @@ -1308,6 +1369,12 @@ protected function processRecurrences() // Create new initial starting point. $initialEventDate = $this->icalDateToDateTime($anEvent['DTSTART_array'][3]); + if ($initialEventDate === false) { + trigger_error("ICal::processRecurrences: Invalid date passed ({$anEvent['DTSTART_array'][3]})", E_USER_NOTICE); + + continue; + } + // Separate the RRULE stanzas, and explode the values that are lists. $rrules = array(); foreach (array_filter(explode(';', $anEvent['RRULE'])) as $s) { @@ -1319,9 +1386,14 @@ protected function processRecurrences() } } - // Get frequency $frequency = $rrules['FREQ']; + if (!is_string($frequency)) { + trigger_error('ICal::processRecurrences: Invalid frequency passed', E_USER_NOTICE); + + continue; + } + // Reject RRULE if BYDAY stanza is invalid: // > The BYDAY rule part MUST NOT be specified with a numeric value // > when the FREQ rule part is not set to MONTHLY or YEARLY. @@ -1333,21 +1405,20 @@ protected function processRecurrences() return $carry && substr($weekday, -2) === $weekday; }; if (!in_array($frequency, array('MONTHLY', 'YEARLY'))) { - if (!array_reduce($rrules['BYDAY'], $checkByDays, true)) { - error_log("ICal::ProcessRecurrences: A {$frequency} RRULE may not contain BYDAY values with numeric prefixes"); + if (is_array($rrules['BYDAY']) && !array_reduce($rrules['BYDAY'], $checkByDays, true)) { + trigger_error("ICal::processRecurrences: A {$frequency} RRULE may not contain BYDAY values with numeric prefixes", E_USER_NOTICE); continue; } } elseif ($frequency === 'YEARLY' && (isset($rrules['BYWEEKNO']) && ($rrules['BYWEEKNO'] !== '' && $rrules['BYWEEKNO'] !== array()))) { - if (!array_reduce($rrules['BYDAY'], $checkByDays, true)) { - error_log('ICal::ProcessRecurrences: A YEARLY RRULE with a BYWEEKNO part may not contain BYDAY values with numeric prefixes'); + if (is_array($rrules['BYDAY']) && !array_reduce($rrules['BYDAY'], $checkByDays, true)) { + trigger_error('ICal::processRecurrences: A YEARLY RRULE with a BYWEEKNO part may not contain BYDAY values with numeric prefixes', E_USER_NOTICE); continue; } } } - // Get Interval $interval = (empty($rrules['INTERVAL'])) ? 1 : (int) $rrules['INTERVAL']; // Throw an error if this isn't an integer. @@ -1384,12 +1455,17 @@ protected function processRecurrences() */ $count = 1; $countLimit = (isset($rrules['COUNT'])) ? intval($rrules['COUNT']) : PHP_INT_MAX; - $until = date_create()->modify("{$this->defaultSpan} years")->setTime(23, 59, 59)->getTimestamp(); + $now = date_create(); + + $until = $now === false + ? 0 + : $now->modify("{$this->defaultSpan} years")->setTime(23, 59, 59)->getTimestamp(); + $untilWhile = $until; - if (isset($rrules['UNTIL'])) { + if (isset($rrules['UNTIL']) && is_string($rrules['UNTIL'])) { $untilDT = $this->iCalDateToDateTime($rrules['UNTIL']); - $until = min($until, $untilDT->getTimestamp()); + $until = min($until, ($untilDT === false) ? $until : $untilDT->getTimestamp()); // There are certain edge cases where we need to go a little beyond the UNTIL to // ensure we get all events. Consider: @@ -1405,7 +1481,7 @@ protected function processRecurrences() // // And as the latter comes after the former, the while loop ends before any dates // in May have the chance to be considered. - $untilWhile = min($untilWhile, $untilDT->modify("+1 {$this->frequencyConversion[$frequency]}")->getTimestamp()); + $untilWhile = min($untilWhile, ($untilDT === false) ? $untilWhile : $untilDT->modify("+1 {$this->frequencyConversion[$frequency]}")->getTimestamp()); } $eventRecurrences = array(); @@ -1417,7 +1493,7 @@ protected function processRecurrences() // phpcs:ignore Squiz.ControlStructures.SwitchDeclaration.MissingDefault switch ($frequency) { case 'DAILY': - if (isset($rrules['BYMONTHDAY']) && ($rrules['BYMONTHDAY'] !== array() && $rrules['BYMONTHDAY'] !== '')) { + if (isset($rrules['BYMONTHDAY']) && (is_array($rrules['BYMONTHDAY']) && $rrules['BYMONTHDAY'] !== array())) { if (!isset($monthDays)) { // This variable is unset when we change months (see below) $monthDays = $this->getDaysOfMonthMatchingByMonthDayRRule($rrules['BYMONTHDAY'], $frequencyRecurringDateTime); @@ -1436,7 +1512,7 @@ protected function processRecurrences() $initialDayOfWeek = $frequencyRecurringDateTime->format('N'); $matchingDays = array($initialDayOfWeek); - if (isset($rrules['BYDAY']) && ($rrules['BYDAY'] !== array() && $rrules['BYDAY'] !== '')) { + if (isset($rrules['BYDAY']) && (is_array($rrules['BYDAY']) && $rrules['BYDAY'] !== array())) { // setISODate() below uses the ISO-8601 specification of weeks: start on // a Monday, end on a Sunday. However, RRULEs (or the caller of the // parser) may state an alternate WeeKSTart. @@ -1481,7 +1557,7 @@ function ($weekday) use ($initialDayOfWeek, $wkstTransition, $interval) { $candidateDateTimes[] = $clonedDateTime->setISODate( (int) $frequencyRecurringDateTime->format('o'), (int) $frequencyRecurringDateTime->format('W'), - $day + (int) $day ); } break; @@ -1489,9 +1565,9 @@ function ($weekday) use ($initialDayOfWeek, $wkstTransition, $interval) { case 'MONTHLY': $matchingDays = array(); - if (isset($rrules['BYMONTHDAY']) && ($rrules['BYMONTHDAY'] !== array() && $rrules['BYMONTHDAY'] !== '')) { + if (isset($rrules['BYMONTHDAY']) && (is_array($rrules['BYMONTHDAY']) && $rrules['BYMONTHDAY'] !== array())) { $matchingDays = $this->getDaysOfMonthMatchingByMonthDayRRule($rrules['BYMONTHDAY'], $frequencyRecurringDateTime); - if (isset($rrules['BYDAY']) && ($rrules['BYDAY'] !== '' && $rrules['BYDAY'] !== array())) { + if (isset($rrules['BYDAY']) && (is_array($rrules['BYDAY']) && $rrules['BYDAY'] !== array())) { $matchingDays = array_filter( $this->getDaysOfMonthMatchingByDayRRule($rrules['BYDAY'], $frequencyRecurringDateTime), function ($monthDay) use ($matchingDays) { @@ -1499,13 +1575,13 @@ function ($monthDay) use ($matchingDays) { } ); } - } elseif (isset($rrules['BYDAY']) && ($rrules['BYDAY'] !== array() && $rrules['BYDAY'] !== '')) { + } elseif (isset($rrules['BYDAY']) && (is_array($rrules['BYDAY']) && $rrules['BYDAY'] !== array())) { $matchingDays = $this->getDaysOfMonthMatchingByDayRRule($rrules['BYDAY'], $frequencyRecurringDateTime); } else { $matchingDays[] = $frequencyRecurringDateTime->format('d'); } - if (isset($rrules['BYSETPOS']) && ($rrules['BYSETPOS'] !== array() && $rrules['BYSETPOS'] !== '')) { + if (isset($rrules['BYSETPOS']) && (is_array($rrules['BYSETPOS']) && $rrules['BYSETPOS'] !== array())) { $matchingDays = $this->filterValuesUsingBySetPosRRule($rrules['BYSETPOS'], $matchingDays); } @@ -1527,7 +1603,7 @@ function ($monthDay) use ($matchingDays) { case 'YEARLY': $matchingDays = array(); - if (isset($rrules['BYMONTH']) && ($rrules['BYMONTH'] !== array() && $rrules['BYMONTH'] !== '')) { + if (isset($rrules['BYMONTH']) && (is_array($rrules['BYMONTH']) && $rrules['BYMONTH'] !== array())) { $bymonthRecurringDatetime = clone $frequencyRecurringDateTime; foreach ($rrules['BYMONTH'] as $byMonth) { $bymonthRecurringDatetime->setDate( @@ -1539,9 +1615,9 @@ function ($monthDay) use ($matchingDays) { // Determine the days of the month affected // (The interaction between BYMONTHDAY and BYDAY is resolved later.) $monthDays = array(); - if (isset($rrules['BYMONTHDAY']) && ($rrules['BYMONTHDAY'] !== '' && $rrules['BYMONTHDAY'] !== array())) { + if (isset($rrules['BYMONTHDAY']) && (is_array($rrules['BYMONTHDAY']) && $rrules['BYMONTHDAY'] !== array())) { $monthDays = $this->getDaysOfMonthMatchingByMonthDayRRule($rrules['BYMONTHDAY'], $bymonthRecurringDatetime); - } elseif (isset($rrules['BYDAY']) && ($rrules['BYDAY'] !== '' && $rrules['BYDAY'] !== array())) { + } elseif (isset($rrules['BYDAY']) && (is_array($rrules['BYDAY']) && $rrules['BYDAY'] !== array())) { $monthDays = $this->getDaysOfMonthMatchingByDayRRule($rrules['BYDAY'], $bymonthRecurringDatetime); } else { $monthDays[] = $bymonthRecurringDatetime->format('d'); @@ -1556,15 +1632,15 @@ function ($monthDay) use ($matchingDays) { )->format('z') + 1; } } - } elseif (isset($rrules['BYWEEKNO']) && ($rrules['BYWEEKNO'] !== array() && $rrules['BYWEEKNO'] !== '')) { + } elseif (isset($rrules['BYWEEKNO']) && (is_array($rrules['BYWEEKNO']) && $rrules['BYWEEKNO'] !== array())) { $matchingDays = $this->getDaysOfYearMatchingByWeekNoRRule($rrules['BYWEEKNO'], $frequencyRecurringDateTime); - } elseif (isset($rrules['BYYEARDAY']) && ($rrules['BYYEARDAY'] !== array() && $rrules['BYYEARDAY'] !== '')) { + } elseif (isset($rrules['BYYEARDAY']) && (is_array($rrules['BYYEARDAY']) && $rrules['BYYEARDAY'] !== array())) { $matchingDays = $this->getDaysOfYearMatchingByYearDayRRule($rrules['BYYEARDAY'], $frequencyRecurringDateTime); - } elseif (isset($rrules['BYMONTHDAY']) && ($rrules['BYMONTHDAY'] !== array() && $rrules['BYMONTHDAY'] !== '')) { + } elseif (isset($rrules['BYMONTHDAY']) && (is_array($rrules['BYMONTHDAY']) && $rrules['BYMONTHDAY'] !== array())) { $matchingDays = $this->getDaysOfYearMatchingByMonthDayRRule($rrules['BYMONTHDAY'], $frequencyRecurringDateTime); } - if (isset($rrules['BYDAY']) && ($rrules['BYDAY'] !== array() && $rrules['BYDAY'] !== '')) { + if (isset($rrules['BYDAY']) && (is_array($rrules['BYDAY']) && $rrules['BYDAY'] !== array())) { if (isset($rrules['BYYEARDAY']) && ($rrules['BYYEARDAY'] !== '' && $rrules['BYYEARDAY'] !== array()) || isset($rrules['BYMONTHDAY']) && ($rrules['BYMONTHDAY'] !== '' && $rrules['BYMONTHDAY'] !== array()) || isset($rrules['BYWEEKNO']) && ($rrules['BYWEEKNO'] !== '' && $rrules['BYWEEKNO'] !== array())) { $matchingDays = array_filter( $this->getDaysOfYearMatchingByDayRRule($rrules['BYDAY'], $frequencyRecurringDateTime), @@ -1583,7 +1659,7 @@ function ($yearDay) use ($matchingDays) { sort($matchingDays); } - if (isset($rrules['BYSETPOS']) && ($rrules['BYSETPOS'] !== '' && $rrules['BYSETPOS'] !== array())) { + if (isset($rrules['BYSETPOS']) && (is_array($rrules['BYSETPOS']) && $rrules['BYSETPOS'] !== array())) { $matchingDays = $this->filterValuesUsingBySetPosRRule($rrules['BYSETPOS'], $matchingDays); } @@ -1947,9 +2023,10 @@ protected function getDaysOfYearMatchingByYearDayRRule(array $byYearDays, $initi protected function getDaysOfYearMatchingByWeekNoRRule(array $byWeekNums, $initialDateTime) { // `\DateTime::format('L')` returns 1 if leap year, 0 if not. - $isLeapYear = $initialDateTime->format('L'); - $firstDayOfTheYear = date_create("first day of January {$initialDateTime->format('Y')}")->format('D'); - $weeksInThisYear = ($firstDayOfTheYear === 'Thu' || $isLeapYear && $firstDayOfTheYear === 'Wed') ? 53 : 52; + $isLeapYear = $initialDateTime->format('L'); + $initialYear = date_create("first day of January {$initialDateTime->format('Y')}"); + $firstDayOfTheYear = ($initialYear === false) ? null : $initialYear->format('D'); + $weeksInThisYear = ($firstDayOfTheYear === 'Thu' || $isLeapYear && $firstDayOfTheYear === 'Wed') ? 53 : 52; $matchingWeeks = $this->resolveIndicesOfRange($byWeekNums, $weeksInThisYear); $matchingDays = array(); @@ -2063,7 +2140,7 @@ protected function processDateConversions() { $events = (isset($this->cal['VEVENT'])) ? $this->cal['VEVENT'] : array(); - if (!empty($events)) { + if ($events !== array()) { foreach ($events as $key => $anEvent) { if (is_null($anEvent) || !$this->isValidDate($anEvent['DTSTART'])) { unset($events[$key]); @@ -2101,10 +2178,8 @@ public function events() $events = array(); - if (!empty($array)) { - foreach ($array as $event) { - $events[] = new Event($event); - } + foreach ($array as $event) { + $events[] = new Event($event); } return $events; @@ -2209,7 +2284,7 @@ public function eventsFromRange($rangeStart = null, $rangeEnd = null) // Sort events before processing range $events = $this->sortEventsWithOrder($this->events()); - if (empty($events)) { + if ($events === array()) { return array(); } @@ -2217,34 +2292,36 @@ public function eventsFromRange($rangeStart = null, $rangeEnd = null) if (!is_null($rangeStart)) { try { - $rangeStart = new \DateTime($rangeStart, new \DateTimeZone($this->defaultTimeZone)); + $rangeStart = new \DateTime($rangeStart, new \DateTimeZone($this->getDefaultTimeZone())); } catch (\Exception $exception) { error_log("ICal::eventsFromRange: Invalid date passed ({$rangeStart})"); $rangeStart = false; } } else { - $rangeStart = new \DateTime('now', new \DateTimeZone($this->defaultTimeZone)); + $rangeStart = new \DateTime('now', new \DateTimeZone($this->getDefaultTimeZone())); } if (!is_null($rangeEnd)) { try { - $rangeEnd = new \DateTime($rangeEnd, new \DateTimeZone($this->defaultTimeZone)); + $rangeEnd = new \DateTime($rangeEnd, new \DateTimeZone($this->getDefaultTimeZone())); } catch (\Exception $exception) { error_log("ICal::eventsFromRange: Invalid date passed ({$rangeEnd})"); $rangeEnd = false; } } else { - $rangeEnd = new \DateTime('now', new \DateTimeZone($this->defaultTimeZone)); + $rangeEnd = new \DateTime('now', new \DateTimeZone($this->getDefaultTimeZone())); $rangeEnd->modify('+20 years'); } - // If start and end are identical and are dates with no times... - if ($rangeEnd->format('His') == 0 && $rangeStart->getTimestamp() === $rangeEnd->getTimestamp()) { - $rangeEnd->modify('+1 day'); - } + if ($rangeEnd !== false && $rangeStart !== false) { + // If start and end are identical and are dates with no times... + if ($rangeEnd->format('His') == 0 && $rangeStart->getTimestamp() === $rangeEnd->getTimestamp()) { + $rangeEnd->modify('+1 day'); + } - $rangeStart = $rangeStart->getTimestamp(); - $rangeEnd = $rangeEnd->getTimestamp(); + $rangeStart = $rangeStart->getTimestamp(); + $rangeEnd = $rangeEnd->getTimestamp(); + } foreach ($events as $anEvent) { $eventStart = $anEvent->dtstart_array[2]; @@ -2264,10 +2341,6 @@ public function eventsFromRange($rangeStart = null, $rangeEnd = null) } } - if ($extendedEvents === array()) { - return array(); - } - return $extendedEvents; } @@ -2279,11 +2352,15 @@ public function eventsFromRange($rangeStart = null, $rangeEnd = null) */ public function eventsFromInterval($interval) { - $rangeStart = new \DateTime('now', new \DateTimeZone($this->defaultTimeZone)); - $rangeEnd = new \DateTime('now', new \DateTimeZone($this->defaultTimeZone)); + $timeZone = $this->getDefaultTimeZone(); + $rangeStart = new \DateTime('now', new \DateTimeZone($timeZone)); + $rangeEnd = new \DateTime('now', new \DateTimeZone($timeZone)); $dateInterval = \DateInterval::createFromDateString($interval); - $rangeEnd->add($dateInterval); + + if ($dateInterval instanceof \DateInterval) { + $rangeEnd->add($dateInterval); + } return $this->eventsFromRange($rangeStart->format('Y-m-d'), $rangeEnd->format('Y-m-d')); } @@ -2382,12 +2459,16 @@ public function isValidWindowsTimeZoneId($timeZone) * * @param string $date * @param \DateInterval $duration - * @param string|null $format - * @return integer|\DateTime + * @return \DateTime|false */ - protected function parseDuration($date, $duration, $format = self::UNIX_FORMAT) + protected function parseDuration($date, $duration) { $dateTime = date_create($date); + + if ($dateTime === false) { + return false; + } + $dateTime->modify("{$duration->y} year"); $dateTime->modify("{$duration->m} month"); $dateTime->modify("{$duration->d} day"); @@ -2395,26 +2476,18 @@ protected function parseDuration($date, $duration, $format = self::UNIX_FORMAT) $dateTime->modify("{$duration->i} minute"); $dateTime->modify("{$duration->s} second"); - if (is_null($format)) { - $output = $dateTime; - } elseif ($format === self::UNIX_FORMAT) { - $output = $dateTime->getTimestamp(); - } else { - $output = $dateTime->format($format); - } - - return $output; + return $dateTime; } /** * Removes unprintable ASCII and UTF-8 characters * * @param string $data - * @return string + * @return string|null */ protected function removeUnprintableChars($data) { - return preg_replace('/[\x00-\x1F\x7F\xA0]/u', '', $data); + return preg_replace('/[\x00-\x1F\x7F]/u', '', $data); } /** @@ -2509,7 +2582,7 @@ public function parseExdates(array $event) } $output = array(); - $currentTimeZone = new \DateTimeZone($this->defaultTimeZone); + $currentTimeZone = new \DateTimeZone($this->getDefaultTimeZone()); foreach ($exdates as $subArray) { end($subArray); @@ -2529,7 +2602,7 @@ public function parseExdates(array $event) if ($key === $finalKey) { // Reset to default - $currentTimeZone = new \DateTimeZone($this->defaultTimeZone); + $currentTimeZone = new \DateTimeZone($this->getDefaultTimeZone()); } } } @@ -2584,8 +2657,8 @@ protected function fileOrUrl($filename) $options['http'] = array(); $options['http']['header'] = array(); - if (!empty($this->httpBasicAuth) || !empty($this->httpUserAgent) || !empty($this->httpAcceptLanguage)) { - if (!empty($this->httpBasicAuth)) { + if ($this->httpBasicAuth !== array() || !empty($this->httpUserAgent) || !empty($this->httpAcceptLanguage)) { + if ($this->httpBasicAuth !== array()) { $username = $this->httpBasicAuth['username']; $password = $this->httpBasicAuth['password']; $basicAuth = base64_encode("{$username}:{$password}"); @@ -2653,6 +2726,6 @@ public function timeZoneStringToDateTimeZone($timeZoneString) return new \DateTimeZone(self::$windowsTimeZonesMap[$timeZoneString]); } - return new \DateTimeZone($this->defaultTimeZone); + return new \DateTimeZone($this->getDefaultTimeZone()); } } diff --git a/lib/calendar/ICal/README.md b/lib/calendar/ICal/README.md index 7fc2c7f17..2c1235a9a 100644 --- a/lib/calendar/ICal/README.md +++ b/lib/calendar/ICal/README.md @@ -177,6 +177,7 @@ need to be evaluated before non-fitting events can be dropped. | `getDaysOfYearMatchingByMonthDayRRule` | `$byMonthDays`, `$initialDateTime` | `protected` | Find all days of a year that match the BYMONTHDAY stanza of an RRULE | | `getDaysOfYearMatchingByWeekNoRRule` | `$byWeekNums`, `$initialDateTime` | `protected` | Find all days of a year that match the BYWEEKNO stanza of an RRULE | | `getDaysOfYearMatchingByYearDayRRule` | `$byYearDays`, `$initialDateTime` | `protected` | Find all days of a year that match the BYYEARDAY stanza of an RRULE | +| `getDefaultTimeZone` | `$forceReturnSystemDefault` | `private` | Returns the default time zone if set or falls back to the system default if not set | | `hasEvents` | - | `public` | Returns a boolean value whether the current calendar has events or not | | `iCalDateToDateTime` | `$icalDate` | `public` | Returns a `DateTime` object from an iCal date time format | | `iCalDateToUnixTimestamp` | `$icalDate` | `public` | Returns a Unix timestamp from an iCal date time format | @@ -193,7 +194,7 @@ need to be evaluated before non-fitting events can be dropped. | `parseLine` | `$line` | `protected` | Parses a line from an iCal file into an array of tokens | | `mb_chr` | `$code` | `protected` | Provides a polyfill for PHP 7.2's `mb_chr()`, which is a multibyte safe version of `chr()` | | `escapeParamText` | `$candidateText` | `protected` | Places double-quotes around texts that have characters not permitted in parameter-texts, but are permitted in quoted-texts. | -| `parseDuration` | `$date`, `$duration`, `$format = 'U'` | `protected` | Parses a duration and applies it to a date | +| `parseDuration` | `$date`, `$duration` | `protected` | Parses a duration and applies it to a date | | `parseExdates` | `$event` | `public` | Parses a list of excluded dates to be applied to an Event | | `processDateConversions` | - | `protected` | Processes date conversions using the time zone | | `processEvents` | - | `protected` | Performs admin tasks on all events as read from the iCal file | @@ -251,5 +252,5 @@ need to be evaluated before non-fitting events can be dropped. ## Tools for Testing - [iCal Validator](https://icalendar.org/validator.html) - - [Recurrence Rule Tester](https://jakubroztocil.github.io/rrule/) + - [Recurrence Rule Tester](https://jkbrzt.github.io/rrule/) - [Unix Timestamp Converter](https://www.unixtimestamp.com) diff --git a/lib/calendar/ICal/version_info.txt b/lib/calendar/ICal/version_info.txt index 4cba50ea9..6bcd175ef 100644 --- a/lib/calendar/ICal/version_info.txt +++ b/lib/calendar/ICal/version_info.txt @@ -1 +1,3 @@ -ICS-Parser v3.3.1 \ No newline at end of file +ICS-Parser v3.4.1 + +https://github.com/u01jmg3/ics-parser \ No newline at end of file diff --git a/lib/calendar/calendar.php b/lib/calendar/calendar.php index 1023fe2f6..6624d7e4b 100644 --- a/lib/calendar/calendar.php +++ b/lib/calendar/calendar.php @@ -3,7 +3,7 @@ * ----------------------------------------------------------------------------- * @package smartVISU * @author Martin Gleiß - * @copyright 2012 - 2015 + * @copyright 2012 - 2024 * @license GPL [http://www.gnu.de] * ----------------------------------------------------------------------------- */ @@ -49,7 +49,7 @@ protected function addData($line) { // find greatest index which has a lower date // (beginning on highest index because of greater chance of having a later date) - for($i = count($this->data)-1; $i >= 0; $i--) { + for($i = \count($this->data)-1; $i >= 0; $i--) { if($this->data[$i]['start'] < $line['start'] || $this->data[$i]['start'] == $line['start'] && $this->data[$i]['end'] < $line['end']) break; } @@ -58,7 +58,7 @@ protected function addData($line) // insert new line after found index array_splice($this->data, $i+1, 0, array($line)); // reduce size to max result size - if(count($this->data) > $this->count) + if(\count($this->data) > $this->count) array_pop($this->data); } } diff --git a/lib/calendar/service/CalDav.php b/lib/calendar/service/CalDav.php index 4c2aa7ef4..d86a68c47 100755 --- a/lib/calendar/service/CalDav.php +++ b/lib/calendar/service/CalDav.php @@ -2,8 +2,8 @@ /** * ----------------------------------------------------------------------------- * @package smartVISU - * @author Johannes Willnecker, Sebastian Helms, Serge Wagener, Stefan Widmer - * @copyright 2015-2017 + * @author Johannes Willnecker, Sebastian Helms, Serge Wagener, Stefan Widmer, Wolfram v. Hülsen + * @copyright 2015-2024 * @license GPL [http://www.gnu.de] * ----------------------------------------------------------------------------- * @label CalDav (e.g. ownCloud, Nextcloud, ...) @@ -25,34 +25,73 @@ class calendar_caldav extends calendar /** * Helper function to query CalDav */ - private function get_caldav_data($url, $method, $xmlquery, $depth = 0) + private function get_caldav_data($url, $method, $xmlquery, $depth = 0, $digest = 0) { $loadError = ''; - $ctxopts = array('http' => - array( - 'method' => $method, - 'header' => "Depth: ".$depth. - "\r\nContent-Type: text/xml; charset='UTF-8'". - "\r\nUser-Agent: DAVKit/4.0.1 (730); CalendarStore/4.0.1 (973); iCal/4.0.1 (1374)", - 'content' => $xmlquery - ) - ); - if(config_calendar_username != '' && config_calendar_password != '') - $ctxopts['http']['header'] .= "\r\nAuthorization: Basic " . base64_encode(config_calendar_username.':'.config_calendar_password); - - $context = stream_context_create($ctxopts); - $caldavresponse = file_get_contents($url, false, $context); - - if ($caldavresponse === false) { - if (substr($this->errorMessage, 0, 17) == 'file_get_contents') - $loadError = substr(strrchr($this->errorMessage, ':'), 2); - elseif (!empty($http_response_header)) - $loadError = $http_response_header[0]; - $this->error('Calendar: CalDav', 'Read request to "'.$url.'" failed with message: "'.$loadError.'"'); - if (!empty($http_response_header)) - $this->debug(implode("\n", $http_response_header)); - echo $this->json(); - exit; + + if ($digest == 1) { + $ch = curl_init(); + + // if calendar is in debug mode write cURL messages to a logfile + if ($this->debug) { + $fp = fopen(const_path.'/temp/curllog.txt', 'w'); + curl_Setopt($ch, CURLOPT_STDERR, $fp); + } + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_Setopt($ch, CURLOPT_VERBOSE, 1); + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method); + curl_setopt($ch, CURLOPT_HTTPHEADER, array( + "Depth: ".$depth, + "Content-Type: text/xml; charset='UTF-8'", + "User-Agent: DAVKit/4.0.1 (730); CalendarStore/4.0.1 (973); iCal/4.0.1 (1374)" + )); + curl_setopt($ch, CURLOPT_POSTFIELDS, $xmlquery); + + // Setting for Digest Authentication (CURLAUTH_DIGEST) does not work with Baikal / Sabre Dav -> use CURLAUTH_ANY + curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_ANY); + curl_setopt($ch, CURLOPT_USERPWD, config_calendar_username . ":" . config_calendar_password); + + $caldavresponse = curl_exec($ch); + + if ($caldavresponse === false) { + $loadError = curl_error($ch); + $this->error('Calendar: CalDav', 'Read request to "'.$url.'" failed with message: "'.$loadError.'"'); + echo $this->json(); + curl_close($ch); + exit; + } + curl_close($ch); + if ($this->debug) + fclose($fp); + + } else { + $ctxopts = array('http' => + array( + 'method' => $method, + 'header' => "Depth: ".$depth. + "\r\nContent-Type: text/xml; charset='UTF-8'". + "\r\nUser-Agent: DAVKit/4.0.1 (730); CalendarStore/4.0.1 (973); iCal/4.0.1 (1374)", + 'content' => $xmlquery + ) + ); + if(config_calendar_username != '' && config_calendar_password != '') + $ctxopts['http']['header'] .= "\r\nAuthorization: Basic " . base64_encode(config_calendar_username.':'.config_calendar_password); + + $context = stream_context_create($ctxopts); + $caldavresponse = file_get_contents($url, false, $context); + + if ($caldavresponse === false) { + if (substr($this->errorMessage, 0, 17) == 'file_get_contents') + $loadError = substr(strrchr($this->errorMessage, ':'), 2); + elseif (!empty($http_response_header)) + $loadError = $http_response_header[0]; + $this->error('Calendar: CalDav', 'Read request to "'.$url.'" failed with message: "'.$loadError.'"'); + if (!empty($http_response_header)) + $this->debug(implode("\n", $http_response_header)); + echo $this->json(); + exit; + } } $xml = simplexml_load_string($caldavresponse, null, LIBXML_NOCDATA, 'DAV:'); @@ -65,14 +104,14 @@ private function get_caldav_data($url, $method, $xmlquery, $depth = 0) * @param $davbaseurl may be the url of WebDav root (e.g. https://server/dav/), of a principal (e.g. https://server/dav/principals/users/USER/) or of user's calendar home (e.g. https://server/caldav/USER/calendars/) * @param $calnames array of calendar display names to load. if null or an array containing one empty string is passed, all calendars will be loaded */ - private function get_calendar_urls($davbaseurl, $calnames = array('')) { + private function get_calendar_urls($davbaseurl, $calnames = array(''), $digest = 0) { // extract server root url $urlparsed = parse_url($davbaseurl); $calserver = (isset($urlparsed['scheme']) ? $urlparsed['scheme'] : 'https') . '://' . (isset($urlparsed['host']) ? $urlparsed['host'] : '') . (isset($urlparsed['port']) ? ':'.$urlparsed['port'] : ''); // Get user principal $xmlquery = ''; - $xml = $this->get_caldav_data($davbaseurl, "PROPFIND", $xmlquery); + $xml = $this->get_caldav_data($davbaseurl, "PROPFIND", $xmlquery, 0, $digest); $principal_url = (!$xml ? "" : $xml->response->propstat->prop->{'current-user-principal'}->href); $this->debug((string)$principal_url, 'principal_url'); // use configured url if no current-user-principal returned @@ -86,7 +125,7 @@ private function get_calendar_urls($davbaseurl, $calnames = array('')) { // Get home url of user's calendars $xmlquery = ''; - $xml = $this->get_caldav_data($principal_url, "PROPFIND", $xmlquery); + $xml = $this->get_caldav_data($principal_url, "PROPFIND", $xmlquery, 0, $digest); $calendar_home_url = (!$xml ? "" : $xml->response->propstat->prop->children('urn:ietf:params:xml:ns:caldav')->{'calendar-home-set'}->children('DAV:')->href); $this->debug((string)$calendar_home_url, 'calendar_home_url'); // use configured url if no calendar-home-set returned @@ -112,7 +151,7 @@ private function get_calendar_urls($davbaseurl, $calnames = array('')) { XMLQUERY; - $xml = $this->get_caldav_data($calendar_home_url, 'PROPFIND', $xmlquery, 1); + $xml = $this->get_caldav_data($calendar_home_url, 'PROPFIND', $xmlquery, 1, $digest); $calurls = array(); if ($xml != false){ @@ -130,7 +169,7 @@ private function get_calendar_urls($davbaseurl, $calnames = array('')) { $displayname = strtolower($response->propstat->prop->displayname); $this->debug((string)$response->href, 'calendar_url of \''.$displayname.'\''); // add only requested (by URL parameter or configuration) calendars or all, if none requested - if(in_array($displayname, $calnames) || $calnames === array('')) { + if(\in_array($displayname, $calnames) || $calnames === array('')) { $description = $response->propstat->prop->children('urn:ietf:params:xml:ns:caldav')->{'calendar-description'}; $color = $response->propstat->prop->children('http://apple.com/ns/ical/')->{'calendar-color'}; $calendar_url = $response->href; @@ -154,7 +193,7 @@ private function get_calendar_urls($davbaseurl, $calnames = array('')) { /** * Get data of a calendar */ - private function get_calendar_data($calurl) + private function get_calendar_data($calurl, $digest = 0) { $calStart = gmdate("Ymd\THis\Z"); $calEnd = gmdate("Ymd\THis\Z", strtotime("+4 weeks")); @@ -178,7 +217,7 @@ private function get_calendar_data($calurl) XMLQUERY; - return $this->get_caldav_data($calurl, 'REPORT', $xmlquery, 1); + return $this->get_caldav_data($calurl, 'REPORT', $xmlquery, 1, $digest); } /** @@ -196,12 +235,22 @@ public function run() - https://p01-caldav.icloud.com/{user}/calendars/ */ $calbaseurl = str_replace('{user}', config_calendar_username, $this->url); - $calurls = $this->get_calendar_urls($calbaseurl, $this->calendar_names); + + // Check required authentification method + $digest = 0; + $wwwAuthHeader = get_headers($calbaseurl, true); + if (isset($wwwAuthHeader['WWW-Authenticate']) && substr($wwwAuthHeader['WWW-Authenticate'], 0, 6 ) == "Digest" ) + $digest = 1; + + $this->debug( $digest == 1 ? 'Digest Auth' : 'Basic Auth', 'Authentication Method of Base URL'); + // var_dump(get_headers($calbaseurl, true)); + + $calurls = $this->get_calendar_urls($calbaseurl, $this->calendar_names, $digest); // get plain ics foreach ($calurls as $calurl => $calmetadata) { - $xml = $this->get_calendar_data($calurl); + $xml = $this->get_calendar_data($calurl, $digest); // extract and parse ics data from CalDav response (= merged content of each node) $ical = new ICal(); $xml->registerXPathNamespace('C', 'urn:ietf:params:xml:ns:caldav'); diff --git a/lib/calendar/service/googleV3.php b/lib/calendar/service/googleV3.php index 72092fb9a..ed4bdfd4f 100644 --- a/lib/calendar/service/googleV3.php +++ b/lib/calendar/service/googleV3.php @@ -3,7 +3,7 @@ * ----------------------------------------------------------------------------- * @package smartVISU * @author Martin Gleiß (basic version), Carsten Gotschlich (google API V3), Thorsten Moll (cache, multiple calendars), Stefan Widmer (refactoring, made configurable) - * @copyright 2012 - 2017 + * @copyright 2012 - 2024 * @license GPL [http://www.gnu.de] * ----------------------------------------------------------------------------- * @label Google API @@ -117,7 +117,7 @@ public function run() { } // then retrieve the users calendars available and cache them, too - if(!isset($cache_content['calendarList']) || count(array_diff($this->calendar_names, array_keys($cache_content['calendarList']))) > 0) { + if(!isset($cache_content['calendarList']) || \count(array_diff($this->calendar_names, array_keys($cache_content['calendarList']))) > 0) { $resturl = 'https://www.googleapis.com/calendar/v3/users/me/calendarList?fields=items(id,summary,colorId,backgroundColor)'; $content = @file_get_contents($resturl, false, $context); if ($content !== false) { @@ -158,11 +158,11 @@ public function run() { foreach ($cache_content['calendarList'] as $calkey => $calmetadata) { // check if calendar is in selected list - if(!in_array($calkey, $this->calendar_names) && $this->calendar_names !== array('')) + if(!\in_array($calkey, $this->calendar_names) && $this->calendar_names !== array('')) continue; // if max event count is reached, just query events which end before last one - $last = count($this->data) == $this->count ? end($this->data) : null; + $last = \count($this->data) == $this->count ? end($this->data) : null; $resturl = 'https://www.googleapis.com/calendar/v3/calendars/'. urlencode($calmetadata['id']) . '/events?fields=items(start,end,colorId,summary,description,location,htmlLink)&singleEvents=true&q=-%22%40visu+no%22&orderBy=startTime&timeMin='.urlencode(date('c')) . ($last != null ? '&timeMax='.urlencode(date('c', $last['end'])) : '') . '&maxResults='. $this->count; $content = @file_get_contents($resturl, false, $context); diff --git a/lib/calendar/service/iCal_(e.g._Google).php b/lib/calendar/service/iCal_(e.g._Google).php index 6d70fd46b..f9072ad11 100644 --- a/lib/calendar/service/iCal_(e.g._Google).php +++ b/lib/calendar/service/iCal_(e.g._Google).php @@ -3,7 +3,7 @@ * ----------------------------------------------------------------------------- * @package smartVISU * @author Stefan Widmer - * @copyright 2016 + * @copyright 2016 - 2024 * @license GPL [http://www.gnu.de] * ----------------------------------------------------------------------------- * @label ICS/iCal (e.g. Google) @@ -29,7 +29,7 @@ public function run() $config_calendar_names = preg_split('/,\s*/m', strtolower(config_calendar_name)); try { foreach(preg_split('/[\s,]+/m', $this->url) as $url) { - if(count($this->calendar_names) == 1 && $this->calendar_names[0] == '' || in_array($config_calendar_names[$i], $this->calendar_names)) { + if(\count($this->calendar_names) == 1 && $this->calendar_names[0] == '' || \in_array($config_calendar_names[$i], $this->calendar_names)) { $ical = new ICal($url, array('defaultSpan' => 1)); $this->addFromIcs($ical, array('calendarname' => $config_calendar_names[$i])); } diff --git a/lib/calendar/service/offline.php b/lib/calendar/service/offline.php index 23e011c33..26ed05ed1 100644 --- a/lib/calendar/service/offline.php +++ b/lib/calendar/service/offline.php @@ -3,7 +3,7 @@ * ----------------------------------------------------------------------------- * @package smartVISU * @author Martin Gleiß - * @copyright 2012 - 2015 + * @copyright 2012 - 2024 * @license GPL [http://www.gnu.de] * ----------------------------------------------------------------------------- * @hide calendar_url @@ -29,9 +29,9 @@ class calendar_offline extends calendar public function run() { $event = array(); - if(in_array('personal', $this->calendar_names)) + if(\in_array('personal', $this->calendar_names)) $event += array_merge($event, array('Meeting', 'Doctor', 'Holidays', 'Trip')); - if(in_array('waste', $this->calendar_names)) { + if(\in_array('waste', $this->calendar_names)) { $waste = array('green bin', 'blue bin', 'yellow bin', 'black bin'); $event += array_merge($event, $waste); } @@ -40,9 +40,9 @@ public function run() { $tag = $tag + rand(1, 3); $colors = array('#44a', '#642', '#555', '#660'); - $title = $event[rand(0, count($event)-1)]; + $title = $event[rand(0, \count($event)-1)]; - if(isset($waste) && in_array($title, $waste)) { + if(isset($waste) && \in_array($title, $waste)) { $this->data[] = array( // single hole day event 'pos' => $i, 'start' => time() + $tag * 24 * 60 * 60, @@ -50,7 +50,7 @@ public function run() 'title' => $title, 'where' => '', 'icon' => 'icons/ws/message_garbage.svg', - 'color' => $colors[rand(0, count($colors)-1)], + 'color' => $colors[rand(0, \count($colors)-1)], 'calendarname' => 'waste' ); } diff --git a/lib/config.php b/lib/config.php index 32aef52b0..c7317ad15 100644 --- a/lib/config.php +++ b/lib/config.php @@ -3,7 +3,7 @@ * ----------------------------------------------------------------------------- * @package smartVISU * @author Stefan Widmer - * @copyright 2016 + * @copyright 2016 - 2024 * @license GPL [http://www.gnu.de] * ----------------------------------------------------------------------------- */ @@ -110,9 +110,9 @@ public function save($target, $options, $pages) { $success = write_ini_file($config, const_path.'pages/'.$pages.'/config.ini', false); break; case 'cookie': - $basepath = substr(parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH), 0, -strlen(substr($_SERVER['SCRIPT_FILENAME'], strlen(const_path)))); + $basepath = substr(parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH), 0, -\strlen(substr($_SERVER['SCRIPT_FILENAME'], \strlen(const_path)))); - if(count($config) > 0){ // some options are set + if(\count($config) > 0){ // some options are set foreach ($config as $key=>&$val) { $val = ($val == "true") ? "1" : $val; $val = ($val == "false") ? "" : $val; diff --git a/lib/defaults.ini b/lib/defaults.ini index 4bad185be..00fbb02a4 100644 --- a/lib/defaults.ini +++ b/lib/defaults.ini @@ -34,8 +34,10 @@ proxy_url = "" proxy_user = "" ; the proxy password if needed proxy_password = "" -; driver reconnect interval +; driver reconnect interval in milliseconds reconnect_time = 5000 +; optional ping interval in seconds (helps keeping connection on sleepy devices, 0 = disabled) +ping_interval = 0 ; ----------------------------------------------------------------------------- @@ -56,19 +58,17 @@ lang = "en" index = "index" ; The title of the html-pages title = "YOUR NAME [smartVISU]" -; Is the page-cache enabled? +; Is the smartVISU page cache enabled? cache = false -; Sub-dolder for page-cache (differs only in config cookie per client)? +; Sub-dolder for page cache (differs only in config cookie per client)? cachefolder = "global" -; Is the client-dom-cache enabled? +; Is jQuery mobile client-dom-cache enabled? cache_dom = true ; How is the page-transition made? ; 'none', 'fade' (default), 'slide' transition = "fade" ; Should some html-elements and widgets be animated? animation = false -; Shall auto reload for websocket be enabled? -autoreload = false ; the refresh-delay between glued widgets delay = 750 ; seconds until automatically return to home page @@ -84,27 +84,30 @@ timezone = "Europe/Berlin" updatecheck = true driver_loopback = true collapsible_reset = false +driver_signalBusy = true ; ----------------------------------------------------------------------------- ; W I D G E T S ; ----------------------------------------------------------------------------- ; Which service should be used? -; @value offline: random values -; @value google: google api (deprecated) -; @value wunderground: get key: http://www.wunderground.com/weather/api -; @value yr.no: http://www.yr.no +; @value offline: simulation with random values +; @value service_diabled: weather widget will not be displayed in infoblock.html +; @value openweathermap.org +; @value pirateweather.net +; @value weather.com +; @value met.no weather_service = "offline" ; What is the default location? weather_location = "Germany/Bayern/Würzburg" ; Is there a key for the service? weather_key = "" ; Which phone-system should by used? -; @value service_disabled: phone list will not be displayed in index.html -; @value offline: random values +; @value service_disabled: phone list will not be displayed in infoblock.html +; @value offline: simulation with random values ; @value auerswald: Auerswald VoiP 5010, 5020, Commander Basic.2 -; @value fritz!box: Fritzbox 7050 -; @value fritz!box_v5.20: Fritzbox 7390 with fw 5.20+ -; @value fritz!box_v5.50: Fritzbox 7390 with fw 5.50 +; @value fritz!box_TR-064: various Fritz!box models with Fritz!OS >= v6 +; @value ncid: Network Caller ID daemon (not tested since v2.9) +; @value speedport_w_724v_Type_B phone_service = "service_disabled" ; What is the ip-address of the phone-system? phone_server = "192.168.x.x" @@ -115,9 +118,12 @@ phone_user = "" ; Is there a password to authenticate? phone_pass = "" ; Which calender should be used? -; @value service_disabled: calendar list will not be displayed in index.html -; @value offline: random values -; @value google: Google Calendar +; @value service_disabled: calendar list will not be displayed in infoblock.html +; @value offline: simulation with random values +; @value CalDav +; @value iCloud +; @value iCal_(e.g._Google) +; @value googleV3: Google Calendar API calendar_service = "service_disabled" ; What is the private url? calendar_url = "" @@ -141,11 +147,11 @@ calendar_google_refresh_token = "" calendar_private = false ; ----------------------------------------------------------------------------- -; O P T I M A T I O N S +; O P T I M A T I O N S - not used any more -> remove in a later version ; ----------------------------------------------------------------------------- ; Normally, no need to change values here! ; Use the minimised files? ; @value 'js' or 'min.js' -js = "min.js" +;js = "min.js" ; Special design variable. Defines the eye cancer of using a flavour. -cvsucks = 10 +;cvsucks = 10 diff --git a/lib/functions.php b/lib/functions.php index eed0ed3e9..c8d2f4004 100644 --- a/lib/functions.php +++ b/lib/functions.php @@ -2,8 +2,8 @@ /** * ----------------------------------------------------------------------------- * @package smartVISU - * @author Martin Gleiß - * @copyright 2012 - 2015 + * @author Martin Gleiß, Wolfram v. Hülsen + * @copyright 2012 - 2024 * @license GPL [http://www.gnu.de] * ----------------------------------------------------------------------------- */ @@ -19,17 +19,20 @@ function get_lang($code = config_lang) { // does configured language file exist? config.ini could have been copied if (!is_file(const_path.'lang/'.$code.'.ini') && !is_file(const_path.'dropins/lang/'.$code.'.ini')) { $code = 'en'; + if (!is_file(const_path.'lang/en.ini')) return array(); }; - // read ini file from /dropins/lang if available - otherwise from /lang if (is_file(const_path.'dropins/lang/'.$code.'.ini')) $result = parse_ini_file(const_path.'dropins/lang/'.$code.'.ini', true); else $result = parse_ini_file(const_path.'lang/'.$code.'.ini', true); + if (\in_array($code, array('en', 'de', 'fr', 'it', 'nl') ) && !isset($result['baselang'])) + $result['baselang'] = $code; + // recursive call to read extended language file (if specified) if(isset($result['extends']) && !empty($result['extends'])){ - if (in_array($result['extends'], array('en', 'de', 'fr', 'nl') ) && !isset($result['baselang'])) + if (\in_array($result['extends'], array('en', 'de', 'fr', 'it', 'nl') ) && !isset($result['baselang'])) $result['baselang'] = $result['extends']; $result = array_replace_recursive(get_lang($result['extends']), $result); } @@ -53,7 +56,7 @@ function trans($subset, $key = '', $mode = '') if (!$lang) $lang = get_lang(); - if (is_array($lang[$subset]) && $key == '') + if (\is_array($lang[$subset]) && $key == '') { foreach (($lang[$subset]) as $key => $val) { @@ -92,7 +95,7 @@ function translate($text, $subset) if (!$lang) $lang = get_lang(); - if (is_array($lang[$subset])) + if (\is_array($lang[$subset])) { $keys = array(); $vals = array(); @@ -124,9 +127,9 @@ function transunit($unit, $value) $fmt = trans('format', $unit); if (strpos($fmt, ',') !== false) - return str_replace('.', ',', sprintf(str_replace(',', '.', $fmt), $value)); + return str_replace('.', ',', \sprintf(str_replace(',', '.', $fmt), $value)); else - return sprintf($fmt, $value); + return \sprintf($fmt, $value); } @@ -147,7 +150,7 @@ function transdate($format = '', $timestamp = null) $lang = get_lang(); //if ($lang['format'][$format] != '') // throws php notices if array key is not existing - if (array_key_exists($format, $lang['format'])) + if (\array_key_exists($format, $lang['format'])) $format = $lang['format'][$format]; if ($timestamp == '') @@ -219,10 +222,10 @@ function fileread($file) function filewrite($file, $ret) { // add base path if file does not already start with it - if(substr($file, 0, strlen(const_path)) !== const_path) + if(substr($file, 0, \strlen(const_path)) !== const_path) $file = const_path . $file; - $dir = dirname($file); + $dir = \dirname($file); if (!is_dir($dir)) mkdir($dir, 0777, true); @@ -265,7 +268,7 @@ function delTree($dir) { */ function write_ini_file($assoc_arr, $path, $has_sections=FALSE) { - $tmpFile = tempnam(dirname($path), basename($path)); + $tmpFile = tempnam(\dirname($path), basename($path)); if (!$handle = fopen($tmpFile, 'w')) return false; @@ -280,15 +283,15 @@ function write_ini_file($assoc_arr, $path, $has_sections=FALSE) { $success &= false !== fwrite($handle, '['.$section.']'.PHP_EOL); foreach($values as $key=>$elem) { - if(is_array($elem)) + if(\is_array($elem)) $key .= '[]'; else $elem = array($elem); foreach($elem as $val) { - $val = strval($val); - if ($val !== 'true' && $val !== 'false' && (!is_int($val) || $val !== '0' && substr($val, 0, 1) === '0')) + $val = \strval($val); + if ($val !== 'true' && $val !== 'false' && (!\is_int($val) || $val !== '0' && substr($val, 0, 1) === '0')) $val = '"'.preg_replace('/["\\\\]/', '\\\\$0', $val).'"'; $success &= false !== fwrite($handle, $key.' = '.$val.PHP_EOL); } @@ -318,7 +321,7 @@ function write_ini_file($assoc_arr, $path, $has_sections=FALSE) { function debug_to_console($data) { $data = '[PHP debug]: ' . $data; $output = 'console.log(' . json_encode($data) . ');'; - $output = sprintf('', $output); + $output = \sprintf('', $output); echo $output; } diff --git a/lib/functions_twig.php b/lib/functions_twig.php index f3cb8cc3c..3526fb2a8 100755 --- a/lib/functions_twig.php +++ b/lib/functions_twig.php @@ -3,7 +3,7 @@ * ----------------------------------------------------------------------------- * @package smartVISU * @author Martin Gleiß - * @copyright 2012 - 2015 + * @copyright 2012 - 2024 * @license GPL [http://www.gnu.de] * ----------------------------------------------------------------------------- */ @@ -45,7 +45,7 @@ function twig_smartdate($val, $format = 'date') function twig_deficon(Twig_Environment $env, $val, $def = '') { - if (!is_array($val)) + if (!\is_array($val)) $ret = $val == '' ? $def : $val; else { $ret = array(); @@ -142,7 +142,7 @@ function twig_docu($filenames = null) if(twig_isdir('pages/'.config_pages.'/widgets')) $filenames = array_merge($filenames, twig_dir('pages/'.config_pages.'/widgets', '(.*.\.html)')); } - elseif(!is_array($filenames)) + elseif(!\is_array($filenames)) if(twig_isfile($filenames) == false && $filenames != const_path.'widgets/icon.html' && $filenames != const_path.'widgets/basic.html') // basic.html needed for template-checker { $filenames = array($filenames); @@ -156,7 +156,7 @@ function twig_docu($filenames = null) foreach($filenames as $filename) { - if(is_array($filename)) + if(\is_array($filename)) $filename = const_path.$filename['path']; $file = file_get_contents($filename); @@ -167,7 +167,7 @@ function twig_docu($filenames = null) // Body preg_match_all('#\/\*\*[\r\n]+(.+?)\*\/\s+?\{\% *macro(.+?)\%\}.*?\{\% *endmacro *\%\}#is', strstr($file, '*/'), $widgets); - if (count($widgets[2]) > 0) + if (\count($widgets[2]) > 0) { foreach ($widgets[2] as $no => $macro) { @@ -231,7 +231,7 @@ function twig_docu($filenames = null) $p['valid_values'] = array_merge(SmartvisuButtonTypes, $p['valid_values']); if ($p['type'] == 'color') { - if (in_array('icon0to5', $p['valid_values'])) { + if (\in_array('icon0to5', $p['valid_values'])) { unset ($p['valid_values'][array_search('icon0to5',$p['valid_values'])]); $p['valid_values'] = array_merge(SmartvisuIconClasses, $p['valid_values']); } @@ -312,8 +312,8 @@ function twig_configmeta($filename) preg_match_all('#.+?@(.+?)\W+(.*)#i', $file, $header, PREG_SET_ORDER); $ret = array('label' => null, 'hide' => array(), 'default' => array(), 'deprecated' => null); foreach($header as $tag) { - if(array_key_exists($tag[1], $ret)) { - if(is_array($ret[$tag[1]])) { + if(\array_key_exists($tag[1], $ret)) { + if(\is_array($ret[$tag[1]])) { $data = preg_split('#\s+#', $tag[2], 2); $ret[$tag[1]][$data[0]] = (isset($data[1]) && $data[1] != '' ? $data[1] : null); //debug_to_console($tag[1].' '.$data[0].' '.$ret[$tag[1]][$data[0]]); @@ -371,7 +371,7 @@ function twig_read_config($source) } function twig_timezones() { - $inlist = timezone_identifiers_list(); + $inlist = timezone_identifiers_list(); $outlist = array(); foreach($inlist as $tz) { $tzparts = explode('/', $tz, 2); @@ -396,12 +396,12 @@ function twig_implode($mixed, $suffix = '', $delimiter = '.') { $ret = ''; - if (is_array($suffix)) + if (\is_array($suffix)) $suffix = $delimiter.implode($delimiter, $suffix); elseif ($suffix != '') $suffix = $delimiter.$suffix; - if (is_array($mixed)) + if (\is_array($mixed)) { foreach ($mixed as $value) { diff --git a/lib/getLocation.php b/lib/getLocation.php index 002c26b28..77cda3713 100644 --- a/lib/getLocation.php +++ b/lib/getLocation.php @@ -3,7 +3,7 @@ * ----------------------------------------------------------------------------- * @package smartVISU * @author Wolfram v. Hülsen - * @copyright 2012 - 2021 + * @copyright 2012 - 2024 * @license GPL [http://www.gnu.de] * ----------------------------------------------------------------------------- * @@ -17,7 +17,7 @@ function getLocation($lat, $lon) { if ($cache->hit(7*24*60)) //cache duration of one week should be sufficient for city names $content = $cache->read(); else { - $url = 'http://api.geonames.org/findNearbyPostalCodesJSON?'.$location.'&radius=2&username=smartvisu_location'; + $url = 'http://api.geonames.org/findNearbyPlaceNameJSON?'.$location.'&radius=2&cities=cities500&username=smartvisu_location'; $content = file_get_contents($url,false); if ($content === false) @@ -28,7 +28,7 @@ function getLocation($lat, $lon) { $parsed_json = json_decode($content); - $ret = $parsed_json->{'postalCodes'}['0']->{'placeName'}; + $ret = $parsed_json->{'geonames'}['0']->{'name'}; return $ret; } diff --git a/lib/includes.php b/lib/includes.php index 979990cd5..996c3452a 100644 --- a/lib/includes.php +++ b/lib/includes.php @@ -3,7 +3,7 @@ * ----------------------------------------------------------------------------- * @package smartVISU * @author Martin Gleiß - * @copyright 2012 - 2015 + * @copyright 2012 - 2024 * @license GPL [http://www.gnu.de] * ----------------------------------------------------------------------------- */ @@ -11,7 +11,7 @@ /** * Path of system-directory */ -define ('const_path_system', dirname(__FILE__).'/'); +define ('const_path_system', \dirname(__FILE__).'/'); /** * Path of smartVISU */ @@ -66,8 +66,8 @@ ) ); - if(strlen(proxy_user) > 0) { - $proxy_opts['http']['header'] = sprintf('Authorization: Basic %s:%s', base64_encode(config_proxy_user), base64_encode(config_proxy_password)); + if(\strlen(proxy_user) > 0) { + $proxy_opts['http']['header'] = \sprintf('Authorization: Basic %s:%s', base64_encode(config_proxy_user), base64_encode(config_proxy_password)); } $default = stream_context_get_default($proxy_opts); } @@ -84,7 +84,7 @@ set_error_handler( function($errno, $errstr, $errfile, $errline) { - if (defined('config_debug') && config_debug == 1) + if (\defined('config_debug') && config_debug == 1) return false; // hand over to standard error reporting else return true; // ignore warnings diff --git a/lib/pagecache.php b/lib/pagecache.php index b31c0a015..aa81418d1 100644 --- a/lib/pagecache.php +++ b/lib/pagecache.php @@ -3,7 +3,7 @@ * ----------------------------------------------------------------------------- * @package smartVISU * @author Stefan Widmer - * @copyright 2016 + * @copyright 2016 - 2024 * @license GPL [http://www.gnu.de] * ----------------------------------------------------------------------------- */ @@ -37,7 +37,7 @@ public function read() if(!empty($this->file) && file_exists($this->file)) { // requested file does exist in cache $mtime = (int)@filemtime($this->file); $gmt_mtime = gmdate('D, d M Y H:i:s', $mtime) . ' GMT'; - $etag = sprintf('%08x-%08x', crc32($this->file), $mtime); + $etag = \sprintf('%08x-%08x', crc32($this->file), $mtime); header('ETag: "' . $etag . '"'); header('Last-Modified: ' . $gmt_mtime); @@ -93,7 +93,7 @@ private function filewrite($content) if(!empty($this->file)) { if(empty($this->tmpFile)) { - $dir = dirname($this->file); + $dir = \dirname($this->file); if (!is_dir($dir)) mkdir($dir, 0775, true); diff --git a/lib/phone/service/fritz!box_TR-064.php b/lib/phone/service/fritz!box_TR-064.php index 3f32ac168..89006daaa 100644 --- a/lib/phone/service/fritz!box_TR-064.php +++ b/lib/phone/service/fritz!box_TR-064.php @@ -3,7 +3,7 @@ * ----------------------------------------------------------------------------- * @package smartVISU * @author Stefan Vonbrunn - * @copyright 2014 - 2018 + * @copyright 2014 - 2024 * @license GPL [http://www.gnu.de] * ----------------------------------------------------------------------------- */ @@ -35,9 +35,9 @@ public function __construct($http_vals) // maximum number of records fetched from phonesystem. $this->max_calls_to_fetch = 20; // use some default user if only password is set on smartvisu - if (strlen($this->user) == 0 && strlen($this->pass) > 0) + if (\strlen($this->user) == 0 && strlen($this->pass) > 0) $this->user = 'admin'; - if (strlen($this->port) == 0) + if (\strlen($this->port) == 0) $this->port = '49000'; } /** @@ -47,7 +47,7 @@ private function DoSOAPCall($content) { $header[] = 'Content-type: text/xml;charset="utf-8"\r\n'; $header[] = 'SOAPAction: urn:dslforum-org:service:X_AVM-DE_OnTel:1#GetCallList'; - $header[] = sprintf('Content-Length: %d', strlen($content)); + $header[] = \sprintf('Content-Length: %d', \strlen($content)); $context = array( 'http' => array( 'method' => 'POST', diff --git a/lib/service.php b/lib/service.php index 0f51902aa..4013b0f66 100644 --- a/lib/service.php +++ b/lib/service.php @@ -3,7 +3,7 @@ * ----------------------------------------------------------------------------- * @package smartVISU * @author Martin Gleiß - * @copyright 2012 - 2015 + * @copyright 2012 - 2024 * @license GPL [http://www.gnu.de] * ----------------------------------------------------------------------------- */ @@ -92,10 +92,10 @@ public function json() { $ret = ""; - if (count($this->error) == 0) + if (\count($this->error) == 0) $this->run(); - if (count($this->error) == 0) + if (\count($this->error) == 0) { $this->prepare(); $ret = $this->data; @@ -108,7 +108,8 @@ public function json() $this->debug($ret, "data"); - header('Content-Type: text/json'); + if (!$this->debug ) + header('Content-Type: application/json'); return json_encode($ret); } diff --git a/lib/templatechecker/class.Config.php b/lib/templatechecker/class.Config.php index dfe32474f..50a785672 100644 --- a/lib/templatechecker/class.Config.php +++ b/lib/templatechecker/class.Config.php @@ -66,5 +66,19 @@ class TemplateCheckerConfig { * of plot parameter sets which must be given */ const ArrayDimensionSetter = array ('quad.stateswitch' => 2, 'quad.select' => 2, 'quad.symbol' => 5, 'quad.print' => 1 ); + + /** + * Array of aggregation modes provided for plots by the individual backend systems + */ + const aggregationModes = array( + 'eibd' => array(), + 'fhem' => array('avg', 'min', 'max', 'minmax', 'minmaxavg', 'sum', 'raw'), + 'iobroker' => array('avg', 'min', 'max', 'minmax', 'minmaxavg', 'on', 'count'), + 'json' => array(), + 'linknx' => array(), + 'offline' => array('avg', 'min', 'max', 'minmax', 'minmaxavg', 'sum', 'diff', 'rate', 'on', 'raw', 'count'), + 'openhab' => array('avg', 'min', 'max', 'minmax', 'minmaxavg', 'sum', 'diff', 'on', 'raw', 'count'), + 'smarthomeng' => array('avg', 'min', 'max', 'minmax', 'minmaxavg', 'diff', 'sum', 'on', 'raw', 'count', 'countall', 'integrate', 'differentiate', 'duration') + ); } \ No newline at end of file diff --git a/lib/templatechecker/class.Items.php b/lib/templatechecker/class.Items.php index f90fdb6ff..12269bcd8 100644 --- a/lib/templatechecker/class.Items.php +++ b/lib/templatechecker/class.Items.php @@ -4,7 +4,7 @@ * ----------------------------------------------------------------------------- * @package smartVISU * @author Andre Kohler - * @copyright 2020 + * @copyright 2020 - 2024 * @license GPL [http://www.gnu.de] * ----------------------------------------------------------------------------- */ @@ -86,7 +86,7 @@ function __construct($path) { $this->items[trim(explode('|',$key)[0])] = trim(explode('|',$key)[1]); } } - if ($this->items != NULL && count($this->items) > 1) + if ($this->items != NULL && \count($this->items) > 1) $this->ready = TRUE; } catch (Exception $e) { diff --git a/lib/templatechecker/class.MessageCollection.php b/lib/templatechecker/class.MessageCollection.php index 0c3111e06..2f9384dfc 100644 --- a/lib/templatechecker/class.MessageCollection.php +++ b/lib/templatechecker/class.MessageCollection.php @@ -4,7 +4,7 @@ * ----------------------------------------------------------------------------- * @package smartVISU * @author Thomas Ernst - * @copyright 2016 + * @copyright 2016 - 2024 * @license GPL [http://www.gnu.de] * ----------------------------------------------------------------------------- */ @@ -136,7 +136,7 @@ public function getMessagesGrouped() { ); foreach($this->messages as $message) { - if (!array_key_exists($message['test'], $ret[$message['severity']])) + if (!\array_key_exists($message['test'], $ret[$message['severity']])) $ret[$message['severity']][$message['test']] = array(); $ret[$message['severity']][$message['test']][] = $message; } @@ -149,7 +149,7 @@ public function getMessagesGrouped() { * @return int number of messages */ public function getMessageCount($severity) { - if (array_key_exists($severity, $this->messageCount)) + if (\array_key_exists($severity, $this->messageCount)) return($this->messageCount[$severity]); return 0; } diff --git a/lib/templatechecker/class.OldWidgets.php b/lib/templatechecker/class.OldWidgets.php index f847f6dc1..89982bcf0 100644 --- a/lib/templatechecker/class.OldWidgets.php +++ b/lib/templatechecker/class.OldWidgets.php @@ -59,7 +59,7 @@ public static function getRemoved() { "name" => "text", "params" => "id, item, txt_on, txt_off, val_on, val_off", "removed" => true, - "replacement" => "basic.symbol(%1\$s, %2\$s, [%3\$s, %4\$s], [%5\$s, %6\$s])", + "replacement" => "basic.symbol(%1\$s, %2\$s, [%3\$s, %4\$s], '', [%5\$s, %6\$s])", ), "basic.value" => array ( "name" => "value", diff --git a/lib/templatechecker/class.TemplateChecker.php b/lib/templatechecker/class.TemplateChecker.php index 644e6e096..018872b86 100644 --- a/lib/templatechecker/class.TemplateChecker.php +++ b/lib/templatechecker/class.TemplateChecker.php @@ -30,6 +30,8 @@ class TemplateChecker { * @var type */ private $fileName; + + private $driver; /** * DOMDocument object for file @@ -41,6 +43,14 @@ class TemplateChecker { * Object for the Icons */ private $items; + + /** + * get configured driver + * @return string + */ + public function getDriver() { + return $this->driver; + } /** * Perform template checks for single file @@ -74,6 +84,11 @@ public function __construct($fileName, MessageCollection $messages, $checkItems) $this->items = new Items(pathinfo($fileName)["dirname"]); //new if ($checkItems == "false") { $this->items->setState(FALSE);} + + // some checks require info of the configured driver (e.g. database aggregation modes in backend) + // get the configured driver if templaechecker is called from the configured pages or use offline driver instead + $fileFolder = str_replace('/widgets', '', $widgetFolder); + $this->driver = (substr($fileFolder, 6, ) == config_pages ? config_driver : 'offline'); } /** @@ -106,12 +121,62 @@ public function performTests() { return; } + if (!$this->checkTwigTemplate($absFile)) + return; + if (!$this->readFile($absFile)) return; $this->checkNode($this->domDocument->documentElement); } + private function checkTwigTemplate($absFile) { + $templatePath = pathinfo($absFile, PATHINFO_DIRNAME); + $loader = new \Twig\Loader\FilesystemLoader($templatePath); + $twig = new \Twig\Environment($loader); + $twig->addFilter( new \Twig\TwigFilter('_', 'twig_concat')); + $twig->addFilter( new \Twig\TwigFilter('bit', 'twig_bit')); + $twig->addFilter( new \Twig\TwigFilter('substr', 'twig_substr')); + $twig->addFilter( new \Twig\TwigFilter('smartdate', 'twig_smartdate')); + $twig->addFilter( new \Twig\TwigFilter('deficon', 'twig_deficon', array('needs_environment' => true))); + $twig->addFilter( new \Twig\TwigFilter('md5', 'twig_md5')); + $twig->addFilter( new \Twig\TwigFilter('preg_replace', 'twig_preg_replace')); + + $twig->addFunction( new \Twig\TwigFunction('uid', 'twig_uid')); + $twig->addFunction( new \Twig\TwigFunction('once', 'twig_once')); + $twig->addFunction( new \Twig\TwigFunction('isfile', 'twig_isfile')); + $twig->addFunction( new \Twig\TwigFunction('isdir', 'twig_isdir')); + $twig->addFunction( new \Twig\TwigFunction('dir', 'twig_dir')); + $twig->addFunction( new \Twig\TwigFunction('docu', 'twig_docu')); + $twig->addFunction( new \Twig\TwigFunction('configmeta', 'twig_configmeta')); + $twig->addFunction( new \Twig\TwigFunction('lang', 'twig_lang')); + $twig->addFunction( new \Twig\TwigFunction('read_config', 'twig_read_config')); + $twig->addFunction( new \Twig\TwigFunction('timezones', 'twig_timezones')); + $twig->addFunction( new \Twig\TwigFunction('implode', 'twig_implode', array('is_safe' => array('html')))); + $twig->addFunction( new \Twig\TwigFunction('items', 'twig_items')); + $twig->addFunction( new \Twig\TwigFunction('asset_exists', 'twig_asset_exists')); + $twig->addFunction( new \Twig\TwigFunction('localize_svg', 'twig_localize_svg')); + + // init lexer comments + $lexer = new \Twig\Lexer($twig, array('tag_comment' => array('/**', '*/'))); + $twig->setLexer($lexer); + $templateName = pathinfo($absFile, PATHINFO_BASENAME); + + try { + // Lade das Template + $template = $twig->load($templateName); + + // Prüfe die Syntax des Templates + $template->getSourceContext()->getCode(); + + //if (Settings::SHOW_SUCCESS_TOO) + // $this->messages->addInfo('TWIG PARSER', 'Twig syntax is valid.'); + } catch (\Twig\Error\SyntaxError $e) { + $this->messages->addError('TWIG TEMPLATE PARSER', 'Twig syntax error: '. $e->getMessage(), $e->getLine()); + } + return true; + } + /** * Open file * @return \DOMDocument @@ -125,7 +190,7 @@ private function readFile($absFile) { $this->domDocument->loadHTML(htmlspecialchars_decode(htmlentities($content))); $errors = libxml_get_errors(); foreach ($errors as $error) { - if (array_key_exists($error->code, $this->ignore_html_error_code)) { + if (\array_key_exists($error->code, $this->ignore_html_error_code)) { $conditions = $this->ignore_html_error_code[$error->code]; $ignore = true; if (isset($conditions['maxline']) && $error->line > $conditions['maxline']) @@ -171,7 +236,7 @@ function checkNode($node, $level = 0) { // do not check code wrapped by {% verbatim %} or in twig comment /**...*/ $value = preg_replace('/{%\s*verbatim\s*%}.*?{%\s*endverbatim\s*%}|\/\*\*\s*.*?\s*\*\//', '', trim($node->textContent)); if (preg_match_all("/{{(.*?)}}/s", $value, $macros)) { - for ($i = 0; $i < count($macros[0]); $i++) + for ($i = 0; $i < \count($macros[0]); $i++) $this->checkWidget($node, trim($macros[1][$i])); } } @@ -229,22 +294,27 @@ public function checkWidget($node, $macro) { return; $widgetName = $widget->getName(); + + // strip calling macros name in recursive check if (strpos($widgetName, '->') > 0 ) $widgetName = substr($widgetName, strpos($widgetName, '->') + 2); + + // a test with twig tokenize as marcro syntax checker in this place showed no significant advantage + // https://stackoverflow.com/questions/27191916/check-if-string-has-valid-twig-syntax $widgetConfig = $this->getWidgetConfig($widgetName); - if ($widgetConfig === NULL || (!array_key_exists('param', $widgetConfig) && !array_key_exists('removed', $widgetConfig)) ) { + if ($widgetConfig === NULL || (!\array_key_exists('param', $widgetConfig) && !array_key_exists('removed', $widgetConfig)) ) { $this->messages->addWarning('WIDGET PARAM CHECK', 'Unknown widget found. Check manually!', $widget->getLineNumber(), $widget->getMacro(), $widget->getMessageData()); // var_dump ($this); //will show the whole widget array within a list item with "unknown widget" error return; } - if (array_key_exists('removed', $widgetConfig)) { + if (\array_key_exists('removed', $widgetConfig)) { $paramConfigs = explode (',', $widgetConfig['params']); // Parameters of widget macro - $paramConfigLen = count($paramConfigs); + $paramConfigLen = \count($paramConfigs); $messageData = $widget->getMessageData(); $messageParams = explode(',', $messageData['Parameters']); // Parameters in widget call - $messageParamsLen = count($messageParams); + $messageParamsLen = \count($messageParams); // fill missing parameters w/ empty quotes if ($paramConfigLen > $messageParamsLen){ do { @@ -253,7 +323,7 @@ public function checkWidget($node, $macro) { } while ($messageParamsLen < $paramConfigLen); } - if(array_key_exists('replacement', $widgetConfig)) { + if(\array_key_exists('replacement', $widgetConfig)) { $messageData['Replacement'] = preg_replace("/(\\s*,\\s*''\\s*)+(\\)\\s*}}\\s*)$/", '$2', vsprintf($widgetConfig['replacement'], $messageParams)); $messageData['Replacement'] = str_replace ("['', '']", "''", $messageData['Replacement']); } @@ -265,9 +335,9 @@ public function checkWidget($node, $macro) { foreach ($paramConfigs as $paramIndex => $paramConfig) { WidgetParameterChecker::performChecks($widget, $paramIndex, $paramConfig, $this->messages,$this->items, $this); //new: items } - if (array_key_exists('deprecated', $widgetConfig)) { + if (\array_key_exists('deprecated', $widgetConfig)) { $messageData = $widget->getMessageData(); - if(array_key_exists('replacement', $widgetConfig)) { + if(\array_key_exists('replacement', $widgetConfig)) { $messageData['Replacement'] = preg_replace("/(\\s*,\\s*''\\s*)+(\\)\\s*}}\\s*)$/", '$2', vsprintf($widgetConfig['replacement'], $widget->getParamArray() + array_map(function($element) { return isset($element['default']) ? "'" . $element['default']. "'" : null; }, $paramConfigs))); } $this->messages->addWarning('WIDGET DEPRECATION CHECK', 'Deprecated widget', $widget->getLineNumber(), $widget->getMacro(), $messageData); @@ -281,7 +351,7 @@ public function checkWidget($node, $macro) { * @return array parameter config for widget or NULL if widget is unknown */ private function getWidgetConfig($name) { - if (array_key_exists($name, $this->widgets)) { + if (\array_key_exists($name, $this->widgets)) { return $this->widgets[$name]; } else { return NULL; @@ -308,7 +378,7 @@ public static function isFileExisting(&$file) { // replace {{ icon0 }} and {{ icon1 }} if (preg_match_all("/{{(.*?)}}/s", $file, $match)) { - for ($i = 0; $i < count($match[0]); $i++) { + for ($i = 0; $i < \count($match[0]); $i++) { switch (strtolower(trim($match[1] [$i]))) { case 'icon0': $file = str_replace($match[0][$i], $settings->getIcon0(), $file); diff --git a/lib/templatechecker/class.Widget.php b/lib/templatechecker/class.Widget.php index 1ed47ade8..84947da78 100644 --- a/lib/templatechecker/class.Widget.php +++ b/lib/templatechecker/class.Widget.php @@ -203,7 +203,7 @@ private static function splitParameters($paramString, $name, $node, $macro, $mes } } else - $currentParam = trim($currentParam, " \t\n\r\0\x0B'"); + $currentParam = trim($currentParam, " \t\n\r\0\x0B'\""); $paramArray[] = $currentParam; if ($topLevel == 1) { @@ -231,7 +231,7 @@ private static function splitParameters($paramString, $name, $node, $macro, $mes } } else - $currentParam = trim($currentParam, " \t\n\r\0\x0B'"); + $currentParam = trim($currentParam, " \t\n\r\0\x0B'\""); $paramArray[] = $currentParam; if ($topLevel == 1) @@ -280,7 +280,7 @@ private function __construct($node, $macro, $name, $paramString, $paramArray, $p $this->name = $name; $this->paramString = $paramString; $this->paramArray = $paramArray; - $this->paramCount = count($paramArray); + $this->paramCount = \count($paramArray); $this->paramStringArray = $paramStringArray; } diff --git a/lib/templatechecker/class.WidgetParameterChecker.php b/lib/templatechecker/class.WidgetParameterChecker.php index 9f0b406ba..a10f36a3a 100644 --- a/lib/templatechecker/class.WidgetParameterChecker.php +++ b/lib/templatechecker/class.WidgetParameterChecker.php @@ -4,7 +4,7 @@ * ----------------------------------------------------------------------------- * @package smartVISU * @author Thomas Ernst - * @copyright 2016 + * @copyright 2016 - 2024 * @license GPL [http://www.gnu.de] * ----------------------------------------------------------------------------- */ @@ -83,7 +83,7 @@ function get_arrays($string, $start, $end, $single){ $string = str_replace("'", "", $string); $string = str_replace('"', '', $string); $p1 = explode($start, $string); - for($i=1; $i 1) + if (\count($p) > 1) array_shift($p); else return $p; @@ -119,7 +119,7 @@ public static function performChecks($widget, $paramIndex, $paramConfig, $messag * @param array $paramConfig Config for parameter to check * @param MessageCollection $messages Collection of messages to add messages to */ - private function __construct($widget, $paramIndex, $paramConfig, $messages,$items, $templateCecker) { + private function __construct($widget, $paramIndex, $paramConfig, $messages, $items, $templateCecker) { $this->widget = $widget; $this->paramIndex = $paramIndex; $this->paramConfig = $paramConfig; @@ -143,7 +143,7 @@ private function __construct($widget, $paramIndex, $paramConfig, $messages,$item * @return mixed parameter config setting */ private function getParamConfig($setting, $default = '') { - return array_key_exists($setting, $this->paramConfig) ? $this->paramConfig[$setting] : $default; + return \array_key_exists($setting, $this->paramConfig) ? $this->paramConfig[$setting] : $default; } /** @@ -165,10 +165,10 @@ private function run() { $uzsuitem = $this->widget->getParam($this->paramIndex - 1); if ($uzsuitem == null || $uzsuitem == '') return; - // we can check only one of the items as dummy in the recursive uzsu eidget check + // we can check only one of the items as dummy in the recursive uzsu widget check // so item names in the info lines may differ from the real items // items have been checked as individual parameters before - if (is_array($uzsuitem)){ + if (\is_array($uzsuitem)){ foreach($uzsuitem as $item) if ($item != '') break; $uzsuitem = "'". $item . "'"; @@ -177,7 +177,7 @@ private function run() { return; } - if (!is_array($values)) + if (!\is_array($values)) $values = array($values); foreach ($values as $value) { @@ -200,6 +200,9 @@ private function run() { case 'item': $this->checkParameterTypeItem($value); //new break; + case 'mode': + $this->checkParameterTypeMode($value); //new + break; case 'type': $this->checkParameterTypeType($value); break; @@ -277,25 +280,25 @@ private function getParameterValue($type) { // some quad widgets define parameter array sizes by certain parameters // we need to know these in order to parse parameter arrays correctly - if (array_key_exists($actualWidget, TemplateCheckerConfig::ArrayDimensionSetter)){ + if (\array_key_exists($actualWidget, TemplateCheckerConfig::ArrayDimensionSetter)){ $widgetElements = $this->widget->getParam(TemplateCheckerConfig::ArrayDimensionSetter[$actualWidget]); - $widgetElementsCount = (is_array($widgetElements) ? count($widgetElements) : 0); + $widgetElementsCount = (\is_array($widgetElements) ? \count($widgetElements) : 0); $test = json_decode(str_replace("'", '"', $value)); - $plotSpecial = ($type == 'plotparam' && is_array($test) && count($test) == 17 && in_array($actualWidget, ['quad.stateswitch', 'quad.select']) ); - $uzsuSpecial = ($type == 'uzsuparam' && !is_array($this->widget->getParam($this->paramIndex - 1)) && in_array($actualWidget, ['quad.stateswitch', 'quad.select']) ); - if (is_array($test) && $plotSpecial == false && $uzsuSpecial == false){ - if (count($test) == $widgetElementsCount || $widgetElementsCount == 0 ){ + $plotSpecial = ($type == 'plotparam' && \is_array($test) && \count($test) == 17 && \in_array($actualWidget, ['quad.stateswitch', 'quad.select']) ); + $uzsuSpecial = ($type == 'uzsuparam' && !\is_array($this->widget->getParam($this->paramIndex - 1)) && \in_array($actualWidget, ['quad.stateswitch', 'quad.select']) ); + if (\is_array($test) && $plotSpecial == false && $uzsuSpecial == false){ + if (\count($test) == $widgetElementsCount || $widgetElementsCount == 0 ){ $value = []; // prepare populated array elements / ignore empty elements for ($i = 0; $i < $widgetElementsCount; $i++){ if($test[$i] != ''){ - if (is_array($test[$i]) && $type == 'sliderparam'){ + if (\is_array($test[$i]) && $type == 'sliderparam'){ // adjust parameter sequence for basic.slider - if (count($test[$i]) > 4) - $test[$i] = array_merge(array_slice($test[$i], 0, 4, true), array('horizontal'), count($test[$i]) >= 6 ? array_slice($test[$i], 5, null, true) : null); + if (\count($test[$i]) > 4) + $test[$i] = array_merge(\array_slice($test[$i], 0, 4, true), array('horizontal'), \count($test[$i]) >= 6 ? array_slice($test[$i], 5, null, true) : null); } - if (is_array($test[$i]) || $widgetElementsCount == 0){ + if (\is_array($test[$i]) || $widgetElementsCount == 0){ $value[] = substr(str_replace('"', "'", json_encode($test[$i])), 1, -1);} else $value[] = str_replace('"', "'", json_encode($test[$i])); @@ -325,9 +328,9 @@ private function getParameterValue($type) { // missing optional parameter, return default value $value = $this->getParamConfig('default', ''); // convert into array if applicable - if (!is_array($value) && $stringParam == false) { + if (!\is_array($value) && $stringParam == false) { $test = explode(',', $value); - if (substr( $value, 0, 1 ) === "[" && count($test) > 1) + if (substr( $value, 0, 1 ) === "[" && \count($test) > 1) $value = $this->get_arrays($value, '[', ']', 'single'); } } else { @@ -344,7 +347,7 @@ private function getParameterValue($type) { return $value; $allowArray = $this->getParamConfig('array_form', 'no'); - $valueIsArray = is_array($value) || ($stringParam && is_array($this->widget->getParam($this->paramIndex))); + $valueIsArray = \is_array($value) || ($stringParam && \is_array($this->widget->getParam($this->paramIndex))); if ($valueIsArray && $allowArray != 'must' && $allowArray != 'may') { // array form $this->addError('WIDGET PARAM CHECK', 'Array form not allowed for parameter', $value); return NULL; @@ -380,7 +383,7 @@ private function checkParameterNotEmpty($value) { } private function checkParameterValidValues($value) { - return in_array($value, $this->paramConfig['valid_values'] ?? array()); + return \in_array($value, $this->paramConfig['valid_values'] ?? array()); } /** @@ -396,7 +399,7 @@ private function checkParameterTypeImage($value) { $value = substr($value, 0, -3); // inline picture - if (in_array($value, TemplateCheckerConfig::SmartvisuInlinePictures)) + if (\in_array($value, TemplateCheckerConfig::SmartvisuInlinePictures)) return; // additional widget-specific valid values @@ -408,7 +411,7 @@ private function checkParameterTypeImage($value) { $this->templateCecker->checkWidget($node, $this->widget->getName()." #". $this->paramIndex ."->".$value); $dyniconWiget = explode('(', $value, 2); - if (array_key_exists($dyniconWiget[0], $this->dynamicIcons)) { + if (\array_key_exists($dyniconWiget[0], $this->dynamicIcons)) { // existing dynamic icon if (Settings::SHOW_SUCCESS_TOO) $this->addInfo('WIDGET IMAGE PARAM CHECK', 'Existing dynamic image', $value); @@ -422,7 +425,7 @@ private function checkParameterTypeImage($value) { $node = $this->widget->getNode(); $this->templateCecker->checkWidget($node, $this->widget->getName()." #". $this->paramIndex ."->".$value); $dyniconWiget = explode('(', $value, 2); - if (array_key_exists($dyniconWiget[0], $this->dynamicIcons)) { + if (\array_key_exists($dyniconWiget[0], $this->dynamicIcons)) { // existing dynamic icon if (Settings::SHOW_SUCCESS_TOO) $this->addInfo('WIDGET IMAGE PARAM CHECK', 'Existing dynamic image', $value); @@ -468,7 +471,7 @@ private function checkParameterTypeId($value) { if (!$this->checkParameterNotEmpty($value)) return; - if (in_array($value, Settings::getInstance()->getUsedWidgetIds())) { + if (\in_array($value, Settings::getInstance()->getUsedWidgetIds())) { if ($this->getParamConfig('unique', TRUE)) $this->addError('WIDGET ID PARAM CHECK', 'Id already used', $value); else if (Settings::SHOW_SUCCESS_TOO) @@ -499,6 +502,25 @@ private function checkParameterTypeType($value) { $this->addError('WIDGET TYPE PARAM CHECK', 'Unknown type', $value); } + + /** + * Check widget parameter of type "mode" (database aggregations provided by the backend) + * @param $value mixed parameter value + */ + private function checkParameterTypeMode($value) { + if (!$this->checkParameterNotEmpty($value)) + return; + + $driver = $this->templateCecker->getDriver(); + if (\in_array($value, TemplateCheckerConfig::aggregationModes[$driver])) + return; + + // additional widget-specific valid values + if ($this->checkParameterValidValues($value)) + return; + + $this->addError('WIDGET MODE PARAM CHECK', 'Mode not supported by backend "'.$driver.'"', $value); + } /** * Check widget parameter of type "color" @@ -513,7 +535,7 @@ private function checkParameterTypeColor($value) { return; // known html colors - if (array_key_exists(strtolower($value), TemplateCheckerConfig::HtmlColors)) + if (\array_key_exists(strtolower($value), TemplateCheckerConfig::HtmlColors)) return; // basic.window and device.window and maybe other dynamic icons in future have a special color mode (variable / constant) @@ -531,7 +553,7 @@ private function checkParameterTypeColor($value) { } // anything else needs to start with '#' and be 4 (#+3), 5, 7 or 9 (#+8) characters long including RBGA colors - if (substr($value, 0, 1) == '#' && (strlen($value) == 4 || strlen($value) == 5 ||strlen($value) == 7 ||strlen($value) == 9) && ctype_xdigit(substr($value, 1))) + if (substr($value, 0, 1) == '#' && (\strlen($value) == 4 || \strlen($value) == 5 || \strlen($value) == 7 || \strlen($value) == 9) && ctype_xdigit(substr($value, 1))) return; // TODO: rgb() / rgba() / hsl() @@ -553,7 +575,7 @@ private function checkParameterTypeItem($value) { // check for combined status/control items, e.g. "item_status:item_control" if (strpos($value, ":") !== false){ $values = explode(':', $value); - if (count($values) != 2){ + if (\count($values) != 2){ $this->addError('ITEM-EXISTING CHECK', 'Combined status/control item must consist of two items', $value, array()); return FALSE; } @@ -572,7 +594,7 @@ private function checkParameterTypeItem($value) { if (isset($this->paramConfig['valid_values']) && $this->paramConfig['valid_values']) { if ($this->items->getItemType($value)) { - if (in_array($this->items->getItemType($value), $this->paramConfig['valid_values'])) { + if (\in_array($this->items->getItemType($value), $this->paramConfig['valid_values'])) { if (Settings::SHOW_SUCCESS_TOO) $this->addInfo('ITEM-TYPE CHECK', 'Type is valid', $this->items->getItemType($value), array('Valid Values' => $this->paramConfig['valid_values'])); return TRUE; @@ -603,7 +625,7 @@ private function checkParameterTypeText($value) { if (Settings::SHOW_SUCCESS_TOO) $this->addInfo('WIDGET TEXT PARAM CHECK', 'Value is valid', $value, array('Valid Values' => $this->paramConfig['valid_values'])); return TRUE; - } else if (is_numeric($value) && in_array('anynumber', $this->paramConfig['valid_values'])) { + } else if (\is_numeric($value) && \in_array('anynumber', $this->paramConfig['valid_values'])) { if (Settings::SHOW_SUCCESS_TOO) $this->addInfo('WIDGET TEXT PARAM CHECK', 'Value is valid', $value, array('Valid Values' => $this->paramConfig['valid_values'])); return TRUE; @@ -634,7 +656,7 @@ private function checkParameterTypeDuration($value) { foreach ($parts as $part) { // Last char: Interval $interval = substr($part, -1); - if (!in_array($interval, TemplateCheckerConfig::SmartvisuDurationIntervals)) { + if (!\in_array($interval, TemplateCheckerConfig::SmartvisuDurationIntervals)) { $this->addError('WIDGET DURATION PARAM CHECK', 'Invalid duration interval identifier', $value, array('Invalid Identifier' => $interval, 'Valid Identifiers' => TemplateCheckerConfig::SmartvisuDurationIntervals)); return; } @@ -659,7 +681,7 @@ private function checkParameterTypeValue($value) { if (!$this->checkParameterNotEmpty($value)) return; - if (!is_numeric($value)) { + if (!\is_numeric($value)) { $this->addError('WIDGET VALUE PARAM CHECK', 'Numeric value required', $value); return; } @@ -683,7 +705,7 @@ private function checkParameterTypePercent($value) { if (!$this->checkParameterNotEmpty($value)) return; $testPos = strpos($value, '%'); - if ($testPos === false || !is_numeric(substr($value, 0, $testPos)) || strlen($value) != $testPos + 1) { + if ($testPos === false || !\is_numeric(substr($value, 0, $testPos)) || \strlen($value) != $testPos + 1) { $this->addError('WIDGET VALUE PARAM CHECK', 'Percent value required', $value); return; } diff --git a/lib/templatechecker/class.itemProperties.php b/lib/templatechecker/class.itemProperties.php index f4361d55f..0da0243c9 100644 --- a/lib/templatechecker/class.itemProperties.php +++ b/lib/templatechecker/class.itemProperties.php @@ -23,19 +23,25 @@ class itemProperties { 'last_change' => 'datetime', 'last_change_age' => 'num', 'last_change_by' => 'str', + 'last_trigger' => 'datetime', + 'last_trigger_age' => 'num', + 'last_trigger_by' => 'str', 'last_update' => 'datetime', 'last_update_age' => 'num', 'last_update_by' => 'str', 'last_value' => 'str', 'name' => 'str', - 'on_change_unxpanded' => 'list', 'on_change' => 'list', + 'on_change_unexpanded' => 'list', 'on_update' => 'list', 'on_update_unexpanded' => 'list', 'path' => 'str', 'prev_change' => 'datetime', 'prev_change_age' => 'num', 'prev_change_by' => 'str', + 'prev_trigger' => 'datetime', + 'prev_trigger_age' => 'num', + 'prev_trigger_by' => 'str', 'prev_update' => 'datetime', 'prev_update_age' => 'num', 'prev_update_by' => 'str', @@ -47,7 +53,7 @@ class itemProperties { ); public static function propertyExists($name) { - return !self::properties[$name] == null; + return isset(self::properties[$name]); } public static function getPropertyType($name) { diff --git a/lib/templatechecker/templatechecker.js b/lib/templatechecker/templatechecker.js index b0dced2e4..2ea72231d 100644 --- a/lib/templatechecker/templatechecker.js +++ b/lib/templatechecker/templatechecker.js @@ -1,8 +1,8 @@ /** * ----------------------------------------------------------------------------- * @package smartVISU - * @author Thomas Ernst - * @copyright 2016 + * @author Thomas Ernst, Wolfram v. Hülsen + * @copyright 2016 - 2024 * @license GPL [http://www.gnu.de] * ----------------------------------------------------------------------------- */ @@ -41,7 +41,7 @@ var tplchk_displayMode = (function () { msgNoFilesAdd = function () { if ($('.tplchk_file_single:visible').length == 0 && $('.tplchk_file_noting').length == 0) - fileList.append('

No files to display in current display mode.

'); + $('.tplchk_file_list').append('

'+sv_lang['templatechecker']['nofile']+'.

'); }; msgNoFilesRemove = function () { @@ -73,6 +73,29 @@ var tplchk_status = (function () { var btn_subfolder; init = function () { + $('.check').each(function (index) { + + $.getJSON('lib/base/' + $(this).attr('data-check'),{'pages' : $('#tplchk_pages').val()}, function (data) { + $('.check').eq(index).children('img').attr('src', sv.config.icon0 + data.icon); + $('.check').eq(index).children('span').html(data.text); + //Show Checkbox for checking items + if ($('.check').eq(index)[0].dataset.check == "check_masteritem.php") + { + if ($('.check').eq(index)[0].children[0].attributes.src.nodeValue.includes('ok')) + { + $("#chk_items")[0].style.visibility = "visible"; + } else + { + $("#chk_items")[0].style.visibility = "hidden"; + } + } + }) + .fail(function (jqXHR) { + var data = jQuery.parseJSON(jqXHR.responseText); + $('.check').eq(index).children('img').attr('src', sv.config.icon0 + data.icon); + $('.check').eq(index).children('span').html(data.text); + }); + }); btn_run = $('#tplchk_btn_run').click(tplchk_checker.checkAll).closest('div'); btn_reset = $('#tplchk_btn_reset').click(tplchk_filelist.fill).closest('div'); btn_subfolder = $('#tplchk_chk_subfolders').on('change', tplchk_filelist.fill).closest('div'); @@ -124,8 +147,6 @@ var tplchk_filelist = (function () { fill = function (e) { //Show Checkbox for checking items $('.check').each(function (index) { - $('.check').eq(index).children('img').attr('src', 'pages/base/pics/trans.png'); - //$('.check').eq(index).children('span').html(data.text); if ($('.check').eq(index)[0].dataset.check == "check_masteritem.php") { if ($('.check').eq(index)[0].children[0].attributes.src.nodeValue.includes('ok')) @@ -184,8 +205,8 @@ var tplchk_filelist = (function () {
\
\
\ -
Checks not done, yet\ - \ +
'+sv_lang['templatechecker']['nocheck']+'\ + \
'; }); fileList.html(html).trigger('create'); @@ -220,32 +241,6 @@ var tplchk_checker = (function () { checkAll = function (e) { e.preventDefault(); - //Should items be checked - - $('.check').each(function (index) { - - $.getJSON('lib/base/' + $(this).attr('data-check'),{'pages' : $('#tplchk_pages').val()}, function (data) { - $('.check').eq(index).children('img').attr('src', sv.config.icon0 + data.icon); - $('.check').eq(index).children('span').html(data.text); - //Show Checkbox for checking items - if ($('.check').eq(index)[0].dataset.check == "check_masteritem.php") - { - if ($('.check').eq(index)[0].children[0].attributes.src.nodeValue.includes('ok')) - { - $("#chk_items")[0].style.visibility = "visible"; - } else - { - $("#chk_items")[0].style.visibility = "hidden"; - } - } - }) - .fail(function (jqXHR) { - var data = jQuery.parseJSON(jqXHR.responseText); - $('.check').eq(index).children('img').attr('src', sv.config.icon0 + data.icon); - $('.check').eq(index).children('span').html(data.text); - }); - }); - fileList.fadeOut('fast', function () { tplchk_status.setWorking(); var deferreds = []; @@ -280,10 +275,10 @@ var tplchk_checker = (function () { } else { content = ['
', btnRecheckAdd(fileId), - addGroup('Errors', 'alert', 'severity_E', data['messages']['E']), - addGroup('Warnings', 'info', 'severity_W', data['messages']['W']), - addGroup('Infos', 'check', 'severity_I', data['messages']['I']), - addGroup('Notifications', 'star', 'severity_N', data['messages']['N']), + addGroup(sv_lang['templatechecker']['errors'], 'alert', 'severity_E', data['messages']['E']), + addGroup(sv_lang['templatechecker']['warnings'], 'info', 'severity_W', data['messages']['W']), + addGroup(sv_lang['templatechecker']['infos'], 'check', 'severity_I', data['messages']['I']), + addGroup(sv_lang['templatechecker']['Notifications'], 'star', 'severity_N', data['messages']['N']), '
'].join(''); } result.html(content); @@ -371,7 +366,7 @@ var tplchk_checker = (function () { }; btnRecheckAdd = function (fileId) { - return [''].join(''); + return [''].join(''); }; btnRecheckComplete = function () { diff --git a/lib/templatechecker/templatechecker.php b/lib/templatechecker/templatechecker.php index 363ad5634..6f38df9cb 100644 --- a/lib/templatechecker/templatechecker.php +++ b/lib/templatechecker/templatechecker.php @@ -4,7 +4,7 @@ * ----------------------------------------------------------------------------- * @package smartVISU * @author Thomas Ernst - * @copyright 2016 + * @copyright 2016 - 2024 * @license GPL [http://www.gnu.de] * ----------------------------------------------------------------------------- */ @@ -19,7 +19,7 @@ require_once 'class.Items.php'; require_once 'class.OldWidgets.php'; require_once 'class.itemProperties.php'; - +require_once const_path.'vendor/autoload.php'; RequestHandler::run(); @@ -87,7 +87,7 @@ private static function getFileArray($dir, $suffix, $subfolders) { if (substr($item, 0, 1) != '.') { if (is_dir(const_path . $dir . '/' . $item) && $subfolders == "true") { $ret = array_merge($ret, self::getFileArray($dir . '/' . $item, $suffix, $subfolders)); - } else if (substr($item, -strlen($suffix)) == $suffix) { + } else if (substr($item, -\strlen($suffix)) == $suffix) { $id = str_replace('/', '-', $dir . '/' . $item); $id = str_replace('.', '-', $id); $ret[$id] = $dir . '/' . $item; diff --git a/lib/weather/classWMOWeatherCodes.php b/lib/weather/classWMOWeatherCodes.php new file mode 100644 index 000000000..77122042c --- /dev/null +++ b/lib/weather/classWMOWeatherCodes.php @@ -0,0 +1,157 @@ + array( + "condition" => "clearsky", + "icon" => "sun_1" + ), + 1 => array( + "condition" => "fair", + "icon" => "sun_2" + ), + 2 => array( + "condition" => "partlycloudy", + "icon" => "sun_3" + ), + 3 => array( + "condition" => "cloudy", + "icon" => "sun_5" + ), + 45 => array( + "condition" => "fog", + "icon" => "cloud_6" + ), + 48 => array( + "condition" => "fog", + "icon" => "cloud_6" + ), + 51 => array( + "condition" => "lightdrizzle", + "icon" => "cloud_7" + ), + + 53 => array( + "condition" => "drizzle", + "icon" => "cloud_7" + ), + + 55 => array( + "condition" => "heavydrizzle", + "icon" => "cloud_7" + ), + + 56 => array( + "condition" => "freezing lightdrizzle", + "icon" => "cloud_7" + ), + + 57 => array( + "condition" => "freezing heavydrizzle", + "icon" => "cloud_7" + ), + 61 => array( + "condition" => "lightrain", + "icon" => "cloud_7" + ), + + 63 => array( + "condition" => "rain", + "icon" => "cloud_7" + ), + + 65 => array( + "condition" => "heavyrain", + "icon" => "cloud_8" + ), + + 66 => array( + "condition" => "freezing lightrain", + "icon" => "sun_1" + ), + + 67 => array( + "condition" => "freezing heavyrain", + "icon" => "sun_1" + ), + + 71 => array( + "condition" => "lightsnow", + "icon" => "cloud_12" + ), + + 73 => array( + "condition" => "snow", + "icon" => "cloud_12" + ), + + 75 => array( + "condition" => "heavysnow", + "icon" => "cloud_13" + ), + + 77 => array( + "condition" => "sleet", + "icon" => "cloud_11" + ), + + 80 => array( + "condition" => "lightrainshowers", + "icon" => "sun_7" + ), + + 81 => array( + "condition" => "rainshowers", + "icon" => "sun_7" + ), + + 82 => array( + "condition" => "heavyrainshowers", + "icon" => "sun_8" + ), + + 85 => array( + "condition" => "lightsnowshowers", + "icon" => "sun_13" + ), + + 86 => array( + "condition" => "heavysnowshowers", + "icon" => "sun_13" + ), + + 95 => array( + "condition" => "thunderstorm", + "icon" => "sun_10" + ), + + 96 => array( + "condition" => "lighthailandthunder", + "icon" => "cloud_16" + ), + + 99 => array( + "condition" => "heavyhailandthunder", + "icon" => "cloud_17" + ) + ); +} \ No newline at end of file diff --git a/lib/weather/jdigiweather.css b/lib/weather/jdigiweather.css index d478cecfa..1266fa1ec 100644 --- a/lib/weather/jdigiweather.css +++ b/lib/weather/jdigiweather.css @@ -77,7 +77,7 @@ .weather .temp .unit { font-size: 14pt; - vertical-align: texttop; + vertical-align: text-top; } .weather .city { diff --git a/lib/weather/service/met.no.php b/lib/weather/service/met.no.php index e5b1b5f95..a59551555 100644 --- a/lib/weather/service/met.no.php +++ b/lib/weather/service/met.no.php @@ -3,7 +3,7 @@ * ----------------------------------------------------------------------------- * @package smartVISU * @author Martin Gleiß - * @copyright 2012 - 2023 + * @copyright 2012 - 2024 * @license GPL [http://www.gnu.de] * ----------------------------------------------------------------------------- * @hide weather_postal @@ -123,7 +123,7 @@ public function run() $searchTimes = array (0, 6, 12, 18); if ($day == $nextday && $dayready == 0) { - if (in_array ($actualTime, $searchTimes)) { + if (\in_array ($actualTime, $searchTimes)) { $mintemp_6h = (float)$dataset->{'data'}->{'next_6_hours'}->{'details'}->{'air_temperature_min'}; $maxtemp_6h = (float)$dataset->{'data'}->{'next_6_hours'}->{'details'}->{'air_temperature_max'}; if($maxtemp_6h > $maxtemp) $maxtemp = $maxtemp_6h; diff --git a/lib/weather/service/offline.php b/lib/weather/service/offline.php index 3428cf412..44af3b0c0 100644 --- a/lib/weather/service/offline.php +++ b/lib/weather/service/offline.php @@ -29,17 +29,12 @@ public function run() // today $this->data['city'] = ucfirst($this->location); - if (config_lang == 'de') - $windspeed = ' mit '.round(1 * 3.6, 1).' km/h'; - elseif (config_lang == 'nl') - $windspeed = ' met '.round(1 * 3.6, 1).' km/u'; - else - $windspeed = ' at '.round(1 * 2.24, 1).' MPH'; + $windspeed = ' '.translate('at','weather').' '.transunit('speed', 3.6); $this->data['current']['temp'] = '25°C'; - $this->data['current']['conditions'] = translate('clear sky', 'yr.no'); + $this->data['current']['conditions'] = translate('clear sky', 'weather'); $this->data['current']['icon'] = $this->icon_sm.'1'; - $this->data['current']['wind'] = translate('light breeze from N', 'yr.no').$windspeed; + $this->data['current']['wind'] = translate('light breeze from N', 'weather').$windspeed; $this->data['current']['more'] = '45%, 1050 hPa'; // forecast @@ -47,7 +42,7 @@ public function run() for ($i = 0; $i < $days; $i++) { $this->data['forecast'][$i]['date'] = date('Y-m-d', time() + 24 * 60 * 60 * ($i + 1)); - $this->data['forecast'][$i]['conditions'] = translate('clear sky', 'yr.no'); + $this->data['forecast'][$i]['conditions'] = translate('clear sky', 'weather'); $this->data['forecast'][$i]['icon'] = 'sun_'.rand(1, 5); $this->data['forecast'][$i]['temp'] = rand(22, 25).'°/'.rand(18, 20).'°'; } diff --git a/lib/weather/service/open-meteo.com.md b/lib/weather/service/open-meteo.com.md new file mode 100644 index 000000000..9ef515582 --- /dev/null +++ b/lib/weather/service/open-meteo.com.md @@ -0,0 +1,13 @@ +# Open-meteo.com weather service for smartVISU + +## Installation +1. in SmartVISU config page 1. choose open-meteo.com, 2. enter your location (i.e. Köln) + Location can be entered in the following ways: + * use latitude and longitude of your location e.g. `latitude=50.93331&longitude=6.95` + +## Troubleshooting/Debug: +1. check for file smartVISU/temp/open-meteo_YOURLOCATION.json + this is the api response from open-meteo.com after you have called the service once. + View it with a json viewer (i.e. addon to chrome) +2. debug the service by calling YOURSERVER/smartVISU/lib/weather/service/open-meteo.com.php?debug=1 +3. check for 'open-meteo.com' entries in var/log/nginx/error.log (or /var/log/apache2/) diff --git a/lib/weather/service/open-meteo.com.php b/lib/weather/service/open-meteo.com.php new file mode 100644 index 000000000..ca56c41f8 --- /dev/null +++ b/lib/weather/service/open-meteo.com.php @@ -0,0 +1,140 @@ + "&wind_speed_unit=ms", // other units are default from open-meteo.com + "ca" => "", + "us" => "&temperature_unit=fahrenheit&precipitation_unit=inch&wind_speed_unit=mph", + "uk2" => "&wind_speed_unit=mph" + ); + + /** + * retrieve the content + */ + public function run() + { + $units = self::unitString[trans('weather', 'units')]; + $timeZone = '&timezone='.config_timezone; + $requestCurrent = '¤t=weather_code,surface_pressure,temperature_2m,relative_humidity_2m,wind_speed_10m,wind_direction_10m,wind_gusts_10m,is_day'; + $requestForecast = '&daily=weather_code,temperature_2m_max,temperature_2m_min,wind_speed_10m_max'; + + // api call + $cache = new class_cache('openmeteo_' . $this->location . '.json'); + + if ($cache->hit($this->cache_duration_minutes)) { + $content = $cache->read(); + } else { + $loadError = ''; + $url = 'https://api.open-meteo.com/v1/forecast?'. $this->location.$requestCurrent.$requestForecast.$units.$timeZone; + + $content = file_get_contents($url); + + if (substr($this->errorMessage, 0, 17) != 'file_get_contents') + $cache->write($content); + else { + $loadError = substr(strrchr($this->errorMessage, ':'), 2); + $this->debug('loadError:' . $loadError ); + } + + } + + $parsed_json = json_decode($content); + if ($parsed_json != null && $parsed_json->{'daily'}) { + $this->debug($parsed_json); + + // today + $this->data['current']['temp'] = transunit('temp', (float)$parsed_json->{'current'}->{'temperature_2m'}); + + $this->data['current']['conditions'] = translate(WMO::weatherCode[$parsed_json->{'current'}->{'weather_code'}]['condition'], 'met.no'); + $this->data['current']['icon'] = WMO::weatherCode[$parsed_json->{'current'}->{'weather_code'}]['icon']; + if ($parsed_json->{'current'}->{'is_day'} == 0 and is_file(const_path_system.'weather/pics/'.str_replace('sun_', 'moon_', $this->data['current']['icon']).'.png') ) + $this->data['current']['icon'] = str_replace('sun_', 'moon_', $this->data['current']['icon']); + $wind_speed = transunit('speed', (float)$parsed_json->{'current'}->{'wind_speed_10m'}); + $wind_gust = transunit('speed', (float)$parsed_json->{'current'}->{'wind_gusts_10m'}); + $wind_dir = weather::getDirection((int)$parsed_json->{'current'}->{'wind_direction_10m'}); + + $this->data['current']['wind'] = translate('wind', 'weather') . " " . $wind_speed; + // when there is no wind, direction is blank + if ($parsed_json->{'current'}->{'wind_speed_10m'} != 0) + $this->data['current']['wind'] .= " " . translate('from', 'weather') . " " . $wind_dir; + if ($wind_gust > 0) + $this->data['current']['wind'] .= ", " . translate('wind_gust', 'weather') . " " . $wind_gust; + + $this->data['current']['more'] = translate('humidity', 'weather') . " " . transunit('%', (float)$parsed_json->{'current'}->{'relative_humidity_2m'}); + $this->data['current']['misc'] = translate('air pressure', 'weather') . " " . $parsed_json->{'current'}->{'surface_pressure'}.' hPa'; + + // forecast + //include current day before noon, then switch to next day + preg_match('/(.*)T(.*)/', $parsed_json->{'current'}->{'time'}, $currentTime); + $firstDay = $currentTime[2] <= '12:00' ? 1 : 0; + + $i = 0; + foreach ($parsed_json->{'daily'}->{'time'} as $index=>$day) { + + if ($index + $firstDay == 0 || $index + $firstDay > 4 ) // next 4 days only + continue; + + $this->data['forecast'][$i]['date'] = $day; + $this->data['forecast'][$i]['conditions'] = translate(WMO::weatherCode[$parsed_json->{'daily'}->{'weather_code'}[$index]]['condition'], 'met.no'); + $this->data['forecast'][$i]['icon'] = WMO::weatherCode[$parsed_json->{'daily'}->{'weather_code'}[$index]]['icon']; + $this->data['forecast'][$i]['temp'] = round((float)$parsed_json->{'daily'}->{'temperature_2m_max'}[$index], 0) . '°/' . round((float)$parsed_json->{'daily'}->{'temperature_2m_min'}[$index], 0) . '°'; + + $i++; + } + $location = explode('&',$this->location); + $this->data['city'] = getLocation(str_replace('latitude=','',$location[0],),str_replace('longitude=','',$location[1])); + + } else { + if ($loadError != '') + $add = $loadError; + else + $add = $parsed_json->{'reason'}; + $this->error('Weather: open-meteo.com', 'Read request failed'.($add ? ' with message:
'.$add : '!')); + } + } +} + + +// ----------------------------------------------------------------------------- +// call the service +// ----------------------------------------------------------------------------- + +$service = new weather_openmeteo(array_merge($_GET, $_POST)); +echo $service->json(); + +?> diff --git a/lib/weather/service/visualcrossing.com.md b/lib/weather/service/visualcrossing.com.md new file mode 100644 index 000000000..5928c9f2f --- /dev/null +++ b/lib/weather/service/visualcrossing.com.md @@ -0,0 +1,15 @@ +# VisualCrossing.com weather service for smartVISU + +## Installation +1. register at visualcrossing.com to get an api key +2. in SmartVISU config page 1. choose visualcrossing.com, 2. enter your location 3. enter your api key + Location can be entered in the following ways: + * use latitude and longitude of your location, separated by a comma e.g. `50.93331,6.95` + * the city name can be used, too, e.g. "Würzburg" or "Würzburg, Bayern, Deutschland" + +## Troubleshooting/Debug: +1. check for file smartVISU/temp/visualcrossing_YOURLOCATION.json + this is the api response from visualcrossing.com after you have called the service once. + View it with a json viewer (normally integrated in the browser) +2. debug the service by calling YOURSERVER/smartVISU/lib/weather/service/visualcrossing.com.php?debug=1 +3. check for 'visualcrossing.com' entries in var/log/nginx/error.log (or /var/log/apache2/) diff --git a/lib/weather/service/visualcrossing.com.php b/lib/weather/service/visualcrossing.com.php new file mode 100644 index 000000000..485083bc8 --- /dev/null +++ b/lib/weather/service/visualcrossing.com.php @@ -0,0 +1,141 @@ +location . '.json'); + + if ($cache->hit($this->cache_duration_minutes)) { + $content = $cache->read(); + } else { + $loadError = ''; + $url ='https://weather.visualcrossing.com/VisualCrossingWebServices/rest/services/timeline/'.$this->location.'?lang='.trans('visualcrossing', 'lang').'&unitGroup='.$units.'&include=days,current&key='.config_weather_key.'&contentType=json'; + $content = file_get_contents($url); + + if (substr($this->errorMessage, 0, 17) != 'file_get_contents') + $cache->write($content); + else { + $loadError = substr(strrchr($this->errorMessage, ':'), 2); + $this->debug('loadError:' . $loadError ); + } + + } + + $parsed_json = json_decode($content); + if ($parsed_json != null && $parsed_json->{'days'}) { + $this->debug($parsed_json); + + // today + $this->data['current']['temp'] = transunit('temp', (float)$parsed_json->{'currentConditions'}->{'temp'}); + + $this->data['current']['conditions'] = translate((string)$parsed_json->{'currentConditions'}->{'conditions'}, 'visualcrossing'); + $this->data['current']['icon'] = $this->icon((string)$parsed_json->{'currentConditions'}->{'icon'}, $this->icon_sm); + + $wind_speed = transunit('speed', (float)$parsed_json->{'currentConditions'}->{'windspeed'}); + $wind_gust = transunit('speed', (float)$parsed_json->{'currentConditions'}->{'windgust'}); + $wind_dir = weather::getDirection((int)$parsed_json->{'currentConditions'}->{'winddir'}); + + $this->data['current']['wind'] = translate('wind', 'weather') . " " . $wind_speed; + // when there is no wind, direction is blank + if ($parsed_json->{'currentConditions'}->{'windspeed'} != 0) + $this->data['current']['wind'] .= " " . translate('from', 'weather') . " " . $wind_dir; + if ($wind_gust > 0) + $this->data['current']['wind'] .= ", " . translate('wind_gust', 'weather') . " " . $wind_gust; + + $this->data['current']['more'] = translate('humidity', 'weather') . " " . transunit('%', (float)$parsed_json->{'currentConditions'}->{'humidity'}); + $this->data['current']['misc'] = translate('air pressure', 'weather') . " " . $parsed_json->{'currentConditions'}->{'pressure'}.' hPa'; + + // forecast + $i = 0; + foreach ($parsed_json->{'days'} as $day) { + if ((int)$day->{'datetimeEpoch'} < mktime(0, 0, 0) || (int)$day->{'datetimeEpoch'} > time() + 3 * 24 * 60 * 60) // next 4 days only + continue; + + $this->data['forecast'][$i]['date'] = date('Y-m-d', (int)$day->{'datetimeEpoch'}); + $this->data['forecast'][$i]['conditions'] = (string)$day->{'conditions'}; + $this->data['forecast'][$i]['icon'] = $this->icon((string)$day->{'icon'}); + + $this->data['forecast'][$i]['temp'] = round((float)$day->{'tempmax'}, 0) . '°/' . round((float)$day->{'tempmin'}, 0) . '°'; + + $i++; + } + if( preg_match('/[A-Za-z]/', $this->location)) + $this->data['city'] = $this->location; + else { + $location = explode(',',$this->location); + $this->data['city'] = getLocation($location[0],$location[1]); + } + + } else { + $add= ''; + if ($loadError != '') + $add = $loadError; + $this->error('Weather: visualcCrossing.com', 'Read request failed'.($add ? ' with message:
'.$add : '!')); + } + } + + /* + * Icon-Mapper + */ + function icon($name, $sm = 'sun_') + { + $ret = ''; + + $icon['clear-day'] = $sm . '1'; + $icon['clear-night'] = $sm . '1'; + $icon['partly-cloudy-day'] = $sm . '4'; + $icon['partly-cloudy-night'] = $sm . '4'; + $icon['fog'] = $sm . '6'; + $icon['rain'] = 'cloud_8'; + $icon['wind'] = $sm . '10'; + $icon['snow'] = $sm . '12'; + + $icon['cloudy'] = 'cloud_4'; + $icon['sleet'] = 'cloud_11'; + + $ret = $icon[$name]; + + return $ret; + } + +} + + +// ----------------------------------------------------------------------------- +// call the service +// ----------------------------------------------------------------------------- + +$service = new weather_visualcrossing(array_merge($_GET, $_POST)); +echo $service->json(); + +?> diff --git a/lib/widget_assistant/README.html b/lib/widget_assistant/README.html index d7429ced3..d40a9130f 100644 --- a/lib/widget_assistant/README.html +++ b/lib/widget_assistant/README.html @@ -54,91 +54,28 @@

smartvisu - Widget Assistant

Table of Content

  1. How it works
  2. -
  3. ChangeLog Update
  4. Hot-Keys
  5. Known issues
  6. -
  7. Logics to create masteritem.json from shNG new
  8. -
  9. Script to create masteritem.json for FHEM new
  10. +
  11. Logics to create masteritem.json from shNG
  12. +
  13. Script to create masteritem.json for FHEM

How it works

-

This tool should help you to create widgets for your smartvisu-Web-Interface. -It will assist you with autocompletes for widgets, items, icons, and colors. -After you have selected a widget (like basic.symbol...) you will get a tooltip for this kind of widget. The actual parameter is highlighted in red and all the available details will be shown in the tooltip-window. -For icons and colors you will see a preview in the tooltip-window. -After completing the wiget you can render it in a new window. The widget in the rendered window is fully working, except there is an error in the created widget, you will get a TWIG-Error. +

This tool should help you to create widgets for your smartvisu-Web-Interface. It will assist you with autocompletes for widgets, items, icons and colors. The autocomplete mode can be selected.

+Just start entering a widget name in the editor or press CTRL-Space to open the widget autocomplete. After you have selected a widget (like basic.symbol...) and enter the opening bracket you will get a tooltip for this widget. +The actual parameter is highlighted in red and all the available details will be shown in the tooltip-window. For icons and colors you will see a preview in the tooltip-window. Autocompletes will insert the parameters with quotes. +If no autocomplete is present or you do not use it make sure to enter the quotes manually. +After completing the wiget you can render it in a new window. The widget in the rendered window is fully working. If there is a syntax error in the created widget, you will get a TWIG-Error. The final widget including the brackets "{{" + "}}" will be stored on rendering to the clipboard. You can paste the widget-code directly to your html-file.

-

Right now you can render multiple widgets, you have to separate the different widgets -by a <br>-TAG

+

You can render multiple widgets by separating them by a <br>-TAG

Example :

-

-plot.period('', ['licht.og.terrasse.screens.warm.dimmen', 'licht.og.terrasse.screens.kalt.dimmen', 'licht.og.terrasse.decke.dimmen'], 'max', '1w', 'now', '0', '110', '1000', ['Screens warm', 'Screens kalt', 'Decke'], ['#daa', '#aad', '#955'], ['stair', 'stair', 'stair'], ['Uhrzeit', 'Helligkeit'], 'advanced', '', '', '', '', '', { yAxis: [ { tickInterval: 40 } ], legend: {align: 'right', verticalAlign: 'top', y: 50, layout: 'vertical'}, chart: { marginRight: 110} })
+
plot.period('', ['licht.og.terrasse.screens.warm.dimmen', 'licht.og.terrasse.screens.kalt.dimmen', 'licht.og.terrasse.decke.dimmen'], 'max', '1w', 'now', '0', '110', '1000', ['Screens warm', 'Screens kalt', 'Decke'], ['#daa', '#aad', '#955'], ['stair', 'stair', 'stair'], ['Uhrzeit', 'Helligkeit'], 'advanced', '', '', '', '', '', { yAxis: [ { tickInterval: 40 } ], legend: {align: 'right', verticalAlign: 'top', y: 50, layout: 'vertical'}, chart: { marginRight: 110} })
 <br>
 basic.stateswitch('','OG.Bad.Licht','midi','','light_ceiling_light','Licht Bad','','','','','')
 <br>
 quad.dimmer('Lueftungsstufe1', 'Lüftungsstufe', 'Haustechnik.Lueftung', 'Haustechnik.Lueftung.stufe', 0, 100, 5, icon.ventilation(), icon.ventilation('','','Haustechnik.Lueftung.stufe'), '', '', '', 0, 100, '', '', '', '',   '', '', '', '', '', '', '', '', '', ['switch', 'value_popup']) 
 
-

-

Change-Log

-

2021.05.01 - Version 1.2.0

-
    -
  • added support for all valid values of widget-types from twig-docu
  • -
  • some optical changes
  • -
  • added widget specific colours to autocomplete dict for colors (for example 'hidden','blank' for basic.print)
  • -
-

2021.01.18 - Version 1.1.0

-
    -
  • changed handling for "render in new window" - if there is no Window a new one will be opened, else the opened window would be refreshed, also if there is a twig error
  • -
  • more fixes for "nasty", "nested" widgets
  • -
  • some optical changes
  • -
-

2020.11.08 - Version 1.0.0

-
    -
  • added support for multi-widget creation (widgets have to be separated by <br>-TAG)
  • -
  • fixed problem for "nasty", "nested" widgets
  • -
  • added check for all brackets closed before rendering
  • -
-

2020.05.15 - Version 1.0.0

-
    -
  • added switching for autoclose quotes
  • -
  • added filter for item-autocomplete based on item data-type
  • -
  • added checkbox to open agiain new window for the preview
  • -
  • added data-types to items in basic-widgets (basic.html)
  • -
  • added alert when receiving "strange" widget-informations (most based on README.html-Files in /dropins/widgets-folder)
  • -
-

2020.05.03 - Version 1.0.0

-
    -
  • added autocomplete dict for parameters with the following names "type", "mode", "style", "colormodel", "orientation", "valueType"
  • -
-

2020.05.02 - Version 1.0.0

-
    -
  • set filter to *svg while collectin icons in /icon/ws/ ( There is a stylesheet in the folder)
  • -
  • until no widget is selected the Widget-only dict is acive
  • -
  • added Close-Button to the Tooltip-Window
  • -
  • added closeBrackets feature for CodeMirror and solved problem with CSS
  • -
  • added direct copy of the final widget to the clipboard when rendering the widget
  • -
-

2020.05.01 - Version 1.0.0

-
    -
  • added support for nested widgets like
    {{ basic.symbol('', 'bath.light.switch', '', icon.light('','','bath.light.value') ) }}
    -
  • -
  • added bracket-matching
  • -
  • added autoswitch to dict depending on parameter -> type / you can switch to a dict using the Hot-Keys
  • -
  • removed deprecated widgets from widget generator
  • -
-

2020.04.26 - Version 1.0.0

-
    -
  • added search in autocompletes with wildcard (a word with 3 chars will match all entries in the acitve dict like *your_chars*
  • -
  • added Hot-Key 'CTRL+Space' to activate the autocomplete-hint without entering a search key
  • -
  • added preview in tooltip for colors and icons
  • -
-

2020.04.25 - launch of Version 1.0.0

-
    -
  • Beta Version for tests distributed
  • -
-

Requirements

-

actual smartvisu-Version - you can find it here

Hot-Keys

@@ -150,12 +87,8 @@

Hot-Keys

- - - - - + @@ -178,14 +111,6 @@

Hot-Keys

- - - - - - - - @@ -203,8 +128,6 @@

Hot-Keys

STRG+0switch OFF autocomplete-Dict
STRG+1switch ON autocomplete-Dictonly Items in autocomplete-Dict
STRG+2 autocomplete-Dict is OFF
STRG+7switch ON/OFF autocomplete for Ouotes (1) - take some time to build the autocomplete
STRG+8only Items in autocomplete-Dict
STRG+9 switch ON/OFF Wildcard search in autocomplete dict
-

(1) when autocomplete for quotes is on - the autocomplete dict will return all values without quotes
- when autocomplete for quotes is off - the autocomplete dictionary will return all values with quotes

Known Issues

It is possible to render the new widget into the iframe on the actual page. @@ -215,8 +138,7 @@

Known Issues

Logic to create masteritem.json from shNG

Only needed for smarthomeNG <= v1.7.2. As of v1.8 the smartvisu plugin writes the file as default.

-

-#!/usr/bin/env python3
+
#!/usr/bin/env python3
 # create_master_item.py
 import json
 from lib.item import Items
diff --git a/lib/widget_assistant/README_DE.html b/lib/widget_assistant/README_DE.html
new file mode 100644
index 000000000..a672ceebc
--- /dev/null
+++ b/lib/widget_assistant/README_DE.html
@@ -0,0 +1,156 @@
+
+
+

smartvisu - Widget Assistant

+

Inhalt

+
    +
  1. Funktionsweise
  2. +
  3. Hot-Keys
  4. +
  5. Bekannte Probleme
  6. +
  7. Logik zum Erstellen der masteritem.json in shNG
  8. +
  9. Skript zum Erstellen der masteritem.json in FHEM
  10. +
+

+

Funktionsweise

+

Dieses Tool hilft bei der Erstellung von Widgets für Ihre Visualisierung mit smartVISU. +Es unterstützt mit Autovervollständigung vorhandener und im Kontext zulässiger Widgets, Items, Icons und Farben. Der Modus der Autovervollständigung kann ausgewählt werden.

+Beginnen Sie einfach im Editor mit der Eingabe eines Widget-Namens oder öffnen Sie die Liste mit CTRL-Leerzeichen. Nachdem Sie ein Widget ausgewählt haben (z.B. basic.symbol...) und die erste Klammer eintippen, +wird ein Fenster mit Erläuterungen (Tooltip) für dieses Widget angezeigt. Der jeweils zu bearbeitende Parameter wird in roter Schrift angezeigt und alle dazu verfügbaren Details werden darunter gelistet. +Für Icons und Farben wird eine Vorschau im Tooltip angezeigt. Alle Parameter müssen mit Anführungszeichen eingegeben werden, wobei die Autovervollständigung die Parameter automatisch in Anführungszeichen setzt.

+Nachdem das Widget vollständig eingegeben ist, können Sie es entweder auf dieser Seite, oder in einem neuen Fenster (empfohlen) anzeigen lassen und live testen. Das Widget ist voll funktionsfähig. +Es wird lediglich ein Twig-Error angezeigt, wenn ein Fehler in der Syntax ist. Zudem wird der eingegebene Widget Code einschließlich der Klammern "{{" + "}}" in die Zwischenablage +kopiert und kann direkt in Ihre Visu-Seiten eingefügt werden.

+

Man kann mehrere Widgets gleichezitig rendern, indem man sie mit einem <br>-TAG trennt.

+

Beispiel:

+
plot.period('', ['licht.og.terrasse.screens.warm.dimmen', 'licht.og.terrasse.screens.kalt.dimmen', 'licht.og.terrasse.decke.dimmen'], 'max', '1w', 'now', '0', '110', '1000', ['Screens warm', 'Screens kalt', 'Decke'], ['#daa', '#aad', '#955'], ['stair', 'stair', 'stair'], ['Uhrzeit', 'Helligkeit'], 'advanced', '', '', '', '', '', { yAxis: [ { tickInterval: 40 } ], legend: {align: 'right', verticalAlign: 'top', y: 50, layout: 'vertical'}, chart: { marginRight: 110} })
+<br>
+basic.stateswitch('','OG.Bad.Licht','midi','','light_ceiling_light','Licht Bad','','','','','')
+<br>
+quad.dimmer('Lueftungsstufe1', 'Lüftungsstufe', 'Haustechnik.Lueftung', 'Haustechnik.Lueftung.stufe', 0, 100, 5, icon.ventilation(), icon.ventilation('','','Haustechnik.Lueftung.stufe'), '', '', '', 0, 100, '', '', '', '',   '', '', '', '', '', '', '', '', '', ['switch', 'value_popup']) 
+
+

+

Hot-Keys

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
KurzbefehlFunktion
STRG+1Autovervollständigung nur für Items
STRG+2Autovervollständigung nur für Widgets
STRG+3Autovervollständigung nur für Icons
STRG+4Autovervollständigung für alles
STRG+5Autovervollständigung nur für Farben
STRG+6Autovervollständigung AUS
STRG+9Platzhaltersuche für Autovervollständigung ein/ausschalten
STRG+Spaceöffnet die aktuelle Liste zur Autovervollständigung
STRG+FAusdruck im Code suchen
STRG+SHIFT+RAusdruck im Code ersetzen - ENTER wechselt zum Ersatz-Ausdruck
+

+

Bekannte Probleme

+

Man kann das neue Widget in den iframe auf der aktuellen Seite rendern. Dies wird jedoch die geladene index.php zerstören, so dass Probleme in der weiteren Navigation in smartVISU entstehen. +Derzeit gibt es dafür nur die Lösung, die Seite neu zu laden (CTRL+F5 / CTRL + Shift + R).

+

+

Logik zum Erstellen der masteritem.json in shNG

+

Wird nur für shNG Versionen <= v1.7.2 benötigt. Ab v1.8 erstellt das smartvisu plugin die Datei standardmäßig.

+
#!/usr/bin/env python3
+# create_master_item.py
+import json
+from lib.item import Items
+items = Items.get_instance()
+items_sorted = sorted(items.return_items(), key=lambda k: str.lower(k['_path']), reverse=False)
+item_list = []
+for item in items_sorted:
+    item_list.append(item._path + "|" + item._type )
+f = open("/var/www/html/smartvisu/pages/YOUR_PAGES/masteritem.json", "w")
+f.write(json.dumps(item_list))
+f.close()
+
+

+

Skript zum Erstellen der masteritem.json in FHEM

+

Ein Skript ist im fronthem / smartVISU Forum verfügbar
+https://forum.fhem.de/index.php/topic,118508.msg1135044.html#msg1135044

+ diff --git a/lib/widget_assistant/tmpl_assistant_1.html b/lib/widget_assistant/tmpl_assistant_1.html index 621aa4509..a832ec95c 100644 --- a/lib/widget_assistant/tmpl_assistant_1.html +++ b/lib/widget_assistant/tmpl_assistant_1.html @@ -9,20 +9,6 @@ {% extends "base.html" %} -{% import "@widgets/lib.html" as lib %} -{% import "@widgets/basic.html" as basic %} -{% import "@widgets/calendar.html" as calendar %} -{% import "@widgets/clock.html" as clock %} -{% import "@widgets/device.html" as device %} -{% import "@widgets/icon.html" as icon %} -{% import "@widgets/multimedia.html" as multimedia %} -{% import "@widgets/phone.html" as phone %} -{% import "@widgets/plot.html" as plot %} -{% import "@widgets/popup.html" as popup %} -{% import "@widgets/status.html" as status %} -{% import "@widgets/weather.html" as weather %} -{% import "@widgets/quad.html" as quad %} - {% block content %}
diff --git a/lib/widget_assistant/tmpl_assistant_2.html b/lib/widget_assistant/tmpl_assistant_2.html index 02f9e8275..9f60fa8b3 100644 --- a/lib/widget_assistant/tmpl_assistant_2.html +++ b/lib/widget_assistant/tmpl_assistant_2.html @@ -9,20 +9,6 @@ {% extends "base.html" %} -{% import "@widgets/lib.html" as lib %} -{% import "@widgets/basic.html" as basic %} -{% import "@widgets/calendar.html" as calendar %} -{% import "@widgets/clock.html" as clock %} -{% import "@widgets/device.html" as device %} -{% import "@widgets/icon.html" as icon %} -{% import "@widgets/multimedia.html" as multimedia %} -{% import "@widgets/phone.html" as phone %} -{% import "@widgets/plot.html" as plot %} -{% import "@widgets/popup.html" as popup %} -{% import "@widgets/status.html" as status %} -{% import "@widgets/weather.html" as weather %} -{% import "@widgets/quad.html" as quad %} - {% block content %}
diff --git a/lib/widget_assistant/widget_assistant.js b/lib/widget_assistant/widget_assistant.js index a4ab1fbed..6629607cf 100644 --- a/lib/widget_assistant/widget_assistant.js +++ b/lib/widget_assistant/widget_assistant.js @@ -8,7 +8,7 @@ // in einem Popup angezeigt. // // -// (c) Andre Kohler - 2020 +// (c) Andre Kohler 2020 - 2024 // license GPL [http://www.gnu.de] // @@ -84,42 +84,42 @@ function removeBalancedBrackets(text) { lastpos = BraceArray.pop() txt2Replace = newText.substr(lastpos,i-lastpos+1) - console.log(txt2Replace) + // console.log(txt2Replace) replaceLength = txt2Replace.length newText = newText.replace(txt2Replace, "") i = i - replaceLength Laenge = Laenge - replaceLength - console.log(newText+"\n") + // console.log(newText+"\n") continue } if (newText.substr(i,1) == "]" && SquareArray.length >0) { lastpos = SquareArray.pop() txt2Replace = newText.substr(lastpos,i-lastpos+1) - console.log(txt2Replace) + // console.log(txt2Replace) replaceLength = txt2Replace.length newText = newText.replace(txt2Replace, "") i = i - replaceLength Laenge = Laenge - replaceLength - console.log(newText+"\n") + // console.log(newText+"\n") continue } if (newText.substr(i,1) == "}" && CurlyArray.length >0) { lastpos = CurlyArray.pop() txt2Replace = newText.substr(lastpos,i-lastpos+1) - console.log(txt2Replace) + // console.log(txt2Replace) replaceLength = txt2Replace.length newText = newText.replace(txt2Replace, "") i = i - replaceLength Laenge = Laenge - replaceLength - console.log(newText+"\n") + // console.log(newText+"\n") continue } } - console.log("\n"+"BraceCount : "+BraceArray.length) - console.log("SquareCount : "+SquareArray.length) - console.log("CurlyCount : "+CurlyArray.length) + // console.log("\n"+"BraceCount : "+BraceArray.length) + // console.log("SquareCount : "+SquareArray.length) + // console.log("CurlyCount : "+CurlyArray.length) if (BraceArray.length == 0 && SquareArray == 0 && CurlyArray.length == 0) { allBracketsClosed = true } else @@ -189,7 +189,7 @@ function ChangeDict(selectedDict,myKey,displayKey,sl_SendNoChange) last_Param = actParam - console.log('Change Dict') + // console.log('Change Dict') switch (selectedDict) { case 1: @@ -594,7 +594,7 @@ function TooltipChecker(cm) CursorPos = cm.getCursor() actLine = CursorPos.line actColumn = CursorPos.ch - console.log('Line : ' + actLine+ ' Column : '+actColumn) + // console.log('Line : ' + actLine+ ' Column : '+actColumn) txtCode = widgetCodeMirror.getValue() realLength = txtCode.length @@ -662,7 +662,7 @@ function TooltipChecker(cm) { try { - console.log('nothing to do for Preview of colors/icons in tooltip') + // console.log('nothing to do for Preview of colors/icons in tooltip') myIcon ="" myIconVisible = "hidden" myActColor = "" @@ -772,7 +772,7 @@ function TooltipChecker(cm) displayKey = '' for (type in myWidgetJson[actWidgetName]['param'][actParam]['valid_values']) { - console.log("Type : " + myWidgetJson[actWidgetName]['param'][actParam]['valid_values'][type]) + // console.log("Type : " + myWidgetJson[actWidgetName]['param'][actParam]['valid_values'][type]) myType = myWidgetJson[actWidgetName]['param'][actParam]['valid_values'][type] myRegex = myRegex + '\\| Item.*'+myType+'|' displayKey = displayKey + " " +myType +' |' @@ -786,7 +786,7 @@ function TooltipChecker(cm) if (actParam != last_Param && myWidgetJson[actWidgetName]['param'][actParam].hasOwnProperty("type")) { - console.log("found Parameter Type") + // console.log("found Parameter Type") switch (true) { @@ -863,9 +863,9 @@ function TooltipChecker(cm) myToolTipHdl.innerHTML +='

deprecated :'+ myWidgetJson[actWidgetName]['deprecated']+'


' } myToolTipHdl.innerHTML += myToolMacro.replace('%param%',newParam) - myToolTipHdl.innerHTML +='

Description


'+ myDescription + myToolTipHdl.innerHTML +='

'+ sv_lang['widgetassi']['description'] +'


'+ myDescription paramKey = ("00" + CommaCount).slice(-2); - myToolTipHdl.innerHTML +='

aktueller Parameter : '+actParam+'

' + myToolTipHdl.innerHTML +='

'+ sv_lang['widgetassi']['actual'] +'Parameter: '+actParam+'

' // Display all the parameters for (param in myWidgetJson[actWidgetName]['param'][actParam]) { @@ -914,7 +914,7 @@ function registerAutocompleteHelper(name, curDict) { var start = cur.ch, end = start; - console.log('Autocomplete called - autocompleteHint') + // console.log('Autocomplete called - autocompleteHint') var charexp = /[\w\.\'\"$]+/; while (end < curLine.length && charexp.test(curLine.charAt(end))) ++end; while (start && charexp.test(curLine.charAt(start - 1))) --start; @@ -1038,7 +1038,7 @@ function add_2_Dict(add2Dict,widget2check) myDisplayText = myValue + " " myCompleteDict.push({ text: ""+myValue+"", displayText: myDisplayText.substr(0,18)+" | " + myParamType+"@"+myWidget }); myCompleteDictwithQuotes.push({ text: "'"+myValue+"'", displayText: myDisplayText.substr(0,18)+" | " + myParamType+"@"+myWidget }); - console.log("added spec. Parameter to dict :" + myValue+" | " + myParamType+"@"+myWidget ) + // console.log("added spec. Parameter to dict :" + myValue+" | " + myParamType+"@"+myWidget ) } } @@ -1060,7 +1060,7 @@ function CheckValidValues() { if(myWidgetJson[myWidget]['param'][actParam]['type'] != 'item') { - console.log(myWidget + '-> Param : '+actParam + '-> valid_values :' + myWidgetJson[myWidget]['param'][actParam]['valid_values']+'|') + // console.log(myWidget + '-> Param : '+actParam + '-> valid_values :' + myWidgetJson[myWidget]['param'][actParam]['valid_values']+'|') myParamType = actParam add_2_Dict(true,myWidget) @@ -1071,9 +1071,9 @@ function CheckValidValues() } } -//************************************************************************ -//function to change Dict to Auto-close quotes -//*********************************************************************** +/******************************************************************************************* +* function to change Dict to Auto-close quotes - deactivated after improvement of autoclose +******************************************************************************************** function changeCloseBrackets(byKey) { quotestate = document.getElementById("switch_quotes").checked @@ -1117,3 +1117,4 @@ function changeCloseBrackets(byKey) } console.log("changeCloseBrackets") } +*/ \ No newline at end of file diff --git a/pages/_template/rooms_menu.html b/pages/_template/rooms_menu.html index c4373c386..5a6c64d33 100644 --- a/pages/_template/rooms_menu.html +++ b/pages/_template/rooms_menu.html @@ -2,7 +2,7 @@ * ----------------------------------------------------------------------------- * @package smartVISU * @author Martin Gleiß -* @copyright 2012 - 2023 +* @copyright 2012 - 2024 * @license GPL [http://www.gnu.de] * ----------------------------------------------------------------------------- */ @@ -19,9 +19,11 @@
  • {{ lib.svgimg ('', 'scene_sleeping_alternat.svg', 'icon0', '') }}

    Sleeping

    - -
    +
    +
    + /** place to insert widgets on the right end of the menu button */ +
  • /** @@ -30,9 +32,9 @@ *
  • * * Sleeping

    Sleeping

    +*
    * *
    -* *
  • */ diff --git a/pages/base/apps.html b/pages/base/apps.html index b81d9bcdc..b715e9153 100644 --- a/pages/base/apps.html +++ b/pages/base/apps.html @@ -1,8 +1,8 @@ /** * ----------------------------------------------------------------------------- * @package smartVISU -* @author Martin Gleiß -* @copyright 2012 - 2015 +* @author Martin Gleiß, Wolfram v. Hülsen +* @copyright 2012 - 2024 * @license GPL [http://www.gnu.de] * ----------------------------------------------------------------------------- */ @@ -14,6 +14,7 @@ {% if page=='apps' %} + {% set language = lang('baselang') %} {% for file in dir('apps', 'app_(.+?).html') %} {% set app = docu('apps/app_'~file.name~'.html') %} @@ -27,7 +28,11 @@

    {{ app.title|e }}

    - {{ app.description }} + {% if attribute(app, 'description_' ~ language) is defined %} + {{ attribute(app, 'description_'~language ) }} + {% else %} + {{ app.description }} + {% endif %}
    {% if app.info or app.link %} @@ -35,7 +40,12 @@

    {{ app.title|e }}

    {% endif %} {% if app.info %} - {{ app.info }}
    + {% if attribute(app, 'info_' ~ language) is defined %} + {{ attribute(app, 'info_'~language ) }} + {% else %} + {{ app.info }} + {% endif %} +
    {% endif %} {% if app.link %} {{ app.link|e }}
    @@ -46,9 +56,9 @@

    {{ app.title|e }}

    {% endif %} {% if app.author %} - Author: {{ app.author }}
    + {{ lang('templatechecker', 'author')|capitalize }}: {{ app.author }}
    {% endif %} -
    File: {{ file.file }}
    +
    {{ lang('templatechecker', 'file')|capitalize }}: {{ file.file }}
    {% if app.version %}
    Version: {{ app.version }}
    {% endif %} diff --git a/pages/base/base.css b/pages/base/base.css index 868851738..4673307d6 100644 --- a/pages/base/base.css +++ b/pages/base/base.css @@ -250,7 +250,8 @@ a:hover { .ui-listview .ui-li-has-thumb > svg:first-child, .ui-listview .ui-li-has-thumb>.ui-btn>svg:first-child, -.ui-listview .ui-btn>svg.icon +.ui-listview .ui-btn>svg.icon, +.ui-listview .ui-btn>img.icon { position: absolute; left: 0; @@ -259,6 +260,11 @@ a:hover { max-width: 5em; } +.ui-listview [data-widget].ui-btn>svg.icon, +.ui-listview [data-widget] .ui-btn>svg.icon { + position: unset; +} + .ui-listview .ui-li-has-thumb > img:first-child, .ui-listview .ui-li-has-thumb > .ui-btn > img:first-child, .ui-listview .ui-li-has-thumb .ui-li-thumb, @@ -278,6 +284,7 @@ a:hover { .ui-li-aside { font-size: 0.8em; /* 0.875em; */ right: .6em !important; + z-index: 3; } .ui-li-aside .icon { @@ -1192,7 +1199,7 @@ input.ui-slider-input.ui-slider-no-input[data-orientation='semicircle'] { -webkit-border-radius: 0.1em 0.1em 0.3em 0.3em; border-radius: 0.1em 0.1em 0.3em 0.3em; opacity: 0.85; - filter: alpha(opacity=85); + filter: alpha(opacity=85); /* IE7+ command for opacity */ } /** @@ -1272,17 +1279,31 @@ input.ui-slider-input.ui-slider-no-input[data-orientation='semicircle'] { } .dimmer .switch, -.dimmer-left .switch { +.dimmer-left .switch, +.dimmer-left .ui-midi, +.dimmer-left .ui-mini, +.dimmer-left .ui-micro { float: left; padding-left: 0; margin-right: 12px; } -.dimmer-right .switch { +.dimmer-right .switch, +.dimmer-right .ui-midi, +.dimmer-right .ui-mini, +.dimmer-right .ui-micro { float: right; padding-right: 0; margin-left: 12px; } +.dimmer-left .ui-btn.ui-midi, +.dimmer-right .ui-btn.ui-midi { + margin-top: -5px; +} +.dimmer-left .ui-btn.ui-micro, +.dimmer-right .ui-btn.ui-micro { + margin-top: 10px; +} .dimmer .icon, .dimmer-left .icon, @@ -1292,11 +1313,7 @@ input.ui-slider-input.ui-slider-no-input[data-orientation='semicircle'] { } .dimmer p, -.dimmer-left p { - margin: 0; - padding: 0 0 5px 15px; -} - +.dimmer-left p, .dimmer-right p { margin: 0; padding: 0 0 5px 15px; @@ -1305,11 +1322,35 @@ input.ui-slider-input.ui-slider-no-input[data-orientation='semicircle'] { .dimmer .ui-slider, .dimmer-left .ui-slider { margin-left: 45px; - } + .dimmer-right .ui-slider { margin-right: 45px; } +.dimmer-left.dimmer-midi .ui-slider { + margin-left: 64px; +} +.dimmer-right.dimmer-midi .ui-slider { + margin-right: 64px; +} +.dimmer-left.dimmer-mini .ui-slider { + margin-left: 48px; +} +.dimmer-right.dimmer-mini .ui-slider { + margin-right: 48px; +} +.dimmer-left.dimmer-micro .ui-slider { + margin-left: 38px; +} +.dimmer-right.dimmer-micro .ui-slider { + margin-right: 38px; +} +.dimmer-notext .ui-slider { + margin-top: 4px; +} +.dimmer-notext.dimmer-micro .ui-slider { + margin-top: 8px; +} .codepad { display: inline-block; @@ -1558,7 +1599,7 @@ div.ui-slider-switch{ width: 350px !important; } @media screen and (max-width: 625px){ - .uzsuRowExpert, .uzsuRowHoliday, .uzsuRowCondition, .uzsuRowDelayedExec, .uzsuCellExpert, .uzsuRowSeriesLine{ + .uzsuRowExpert, .uzsuRowHoliday, .uzsuRowCondition, .uzsuRowDelayedExec, .uzsuCellExpert, .uzsuRowSeriesLine, .uzsuCellSeries { display: none; } } @@ -1609,7 +1650,10 @@ div.ui-slider-switch{ .uzsuCell [data-tip]:after { left: -190px; } -.highcharts-root .uzsu-event-sunrise { +/** + * ---------- UZSU Graph ------------------------------------------------ + */ + .highcharts-root .uzsu-event-sunrise { fill: #ffffff; stroke: rgba(255, 255, 255, 0.5); } @@ -1629,6 +1673,16 @@ div.ui-slider-switch{ fill: #808080; stroke: #808080; } +.uzsu-all-inactive.uzsu-general-once .uzsu-active-toggler { + fill: #FF8080; + stroke: #FF8080; +} +[data-widget="device.uzsugraph"] .highcharts-navigator, +[data-widget="device.uzsugraph"] .highcharts-navigator-series, +[data-widget="device.uzsugraph"] .highcharts-navigator-xaxis, +[data-widget="device.uzsugraph"] .highcharts-navigator-yaxis { + display: none; +} /** * --- W i d g e t s : M U L T I M E D I A ---------------------------------- @@ -1742,10 +1796,6 @@ div.ui-slider-switch{ .plot.plot-highstock { height: 315px; } -.plot.plot-highstock { - height: 315px; -} - .block .ui-fixed .plot, .block .set-1 .plot { min-height: 14.5em; diff --git a/pages/base/config.html b/pages/base/config.html index 1a78f1a69..34a3ab1f2 100644 --- a/pages/base/config.html +++ b/pages/base/config.html @@ -41,7 +41,7 @@

    {{ lang('configuration_page', 'configuration', 'label') }}

    {% set cfg_data = read_config(source) %} /** if configured driver is not available and has been changed to 'offline' by root.html, show the active driver (which should be 'offline') */ - {% if cfg_data['driver'] is defined and cfg_data['driver'] != config_driver %} + {% if cfg_data['driver'] is defined and not isfile('driver/io_' ~ cfg_data['driver'] ~ '.js') %} {% set cfg_data = cfg_data | merge({'driver': config_driver }) %} {% endif %} @@ -181,8 +181,9 @@

    {{ lang('configuration_page', 'options', 'lab

    {{ lang('configuration_page', 'functions', 'label') }}

    {{ forms.config_flip(source, values, 'updatecheck') }} - {{ forms.config_flip(source, values, 'driver_loopback') }} {{ forms.config_flip(source, values, 'collapsible_reset') }} + {{ forms.config_flip(source, values, 'driver_loopback') }} + {{ forms.config_flip(source, values, 'driver_signalBusy') }}

    diff --git a/pages/base/configure.php b/pages/base/configure.php index c395a7b19..857c9600f 100755 --- a/pages/base/configure.php +++ b/pages/base/configure.php @@ -3,7 +3,7 @@ * ----------------------------------------------------------------------------- * @package smartVISU * @author Stefan Widmer - * @copyright 2016 + * @copyright 2016 - 2024 * @license GPL [http://www.gnu.de] * ----------------------------------------------------------------------------- */ @@ -55,7 +55,7 @@ $sources = array('all'); if(isset($_GET['source'])) $sources = $_GET['source']; - if(!is_array($sources)) $sources = array($sources); + if(!\is_array($sources)) $sources = array($sources); $result = array(); foreach($sources as $source) { diff --git a/pages/base/credits.html b/pages/base/credits.html index 25a75d984..b744f2839 100644 --- a/pages/base/credits.html +++ b/pages/base/credits.html @@ -2,7 +2,7 @@ * ----------------------------------------------------------------------------- * @package smartVISU * @author Martin Gleiß -* @copyright 2012 - 2015 +* @copyright 2012 - 2024 * @license GPL [http://www.gnu.de] * ----------------------------------------------------------------------------- */ @@ -14,11 +14,10 @@

    Credits

    - Thanks to all our supporters! Don't hesitate to contact me, if you have wishes or problems!
    + Thanks to all our supporters!

    Special thanks to

    -
    jetbrains
    @@ -36,7 +35,7 @@

    Special thanks to

    The jQuery Foundation for jQuery mobile
    Touch-Optimized Web Framework for Smartphones & Tablets
    - Licensed under the MIT + Licensed under the MIT License


    @@ -47,7 +46,7 @@

    Special thanks to

    Fabien Potencier for the TWIG template engine
    The flexible, fast, and secure template engine for PHP
    - Released under the new BSD license + Released under the BSD license


    @@ -113,12 +112,38 @@

    Special thanks to

    -
    codemirror
    +
    icsparser
    +
    +

    + Jonathan Goode and team for the PHP ICS Parser
    + Licensed under the MIT license +


    + +
    +
    codemirror

    Marijn Haverbeke and team for codemirror
    Licensed under the MIT license


    + + +
    +
    roundslider
    +
    +

    + Soundar for the jQuery roundslider plugin
    + Licensed under the MIT license +


    + + +
    +
    toast
    +
    +

    + Kamran Ahmed for the jQuery toast plugin
    + Licensed under the MIT license +


    {% endblock %} diff --git a/pages/base/menu.html b/pages/base/menu.html index 746673669..b185ff3f8 100644 --- a/pages/base/menu.html +++ b/pages/base/menu.html @@ -1,8 +1,8 @@ /** * ----------------------------------------------------------------------------- * @package smartVISU -* @author Martin Gleiss, Wolfram v. Hülsen -* @copyright 2012 - 2023 +* @author Martin Gleiß, Wolfram v. Hülsen +* @copyright 2012 - 2024 * @license GPL [http://www.gnu.de] * ----------------------------------------------------------------------------- */ @@ -23,7 +23,7 @@ apps {% else%} - + {% endif %} diff --git a/pages/base/pics/scale_pallets.png b/pages/base/pics/scale_pellets.png similarity index 100% rename from pages/base/pics/scale_pallets.png rename to pages/base/pics/scale_pellets.png diff --git a/pages/base/quad.css b/pages/base/quad.css index a874443c4..c12372a4b 100755 --- a/pages/base/quad.css +++ b/pages/base/quad.css @@ -2,12 +2,11 @@ * ----------------------------------------------------------------------------- * @package smartVISU * @author Martin Gleiß - * @copyright 2012 - * @license GPL + * @copyright 2012 - 2024 + * @license GPL [http://www.gnu.de] * ----------------------------------------------------------------------------- */ - @CHARSET "UTF-8"; .quad *, *:before, *:after { box-sizing: border-box; @@ -261,10 +260,10 @@ height: 65px; } -.quad_tiles .ui-ri-aside img.icon { +.quad_tiles .ui-ri-aside img.icon, +.quad_tiles .ui-ri-aside svg.icon { height: 24px; width: 24px; - } .quad_tiles .ui-btn { @@ -280,10 +279,12 @@ left: 7px; right: 0px; position: absolute; + z-index:3; } .quad_tiles .ui-li-aside { top: 0 !important; + z-index:3; } .quad_list a { @@ -293,7 +294,6 @@ .quad_list .ui-li-static { display: table-row; - } .quad_list li { @@ -306,28 +306,20 @@ .quad_list .ui-li-divider { padding: 0 0 0 10px; } + .quad_list .ui-li-static.ui-li { padding: 0; } + .quad_list li:nth-child(odd) { /**background-color: #1d1d1d;*/ } + .quad_list li:nth-child(even) { /**background-color: #212121;*/ } -.quad_list img.icon { - height: 32px; - width: 32px; - margin-top: -4px; -} -.quad_list svg.icon { - height: 32px; - width: 32px; - margin-top: -4px; -} - -.quad_list svg.fx-icon { +.quad_list img.icon, .quad_list svg.icon, .quad_list svg.fx-icon { height: 32px; width: 32px; margin-top: -4px; @@ -419,8 +411,7 @@ min-width: 300px; } -.quad_rtr-popup -{ +.quad_rtr-popup { height: 180px; min-width: 320px; text-align: center; @@ -523,7 +514,7 @@ margin: 15px 0 10px 0; } -//NOTIFYBADGE +/* NOTIFYBADGE */ /* make sure the element has position: relative */ [data-notifications] { position: relative; @@ -540,7 +531,7 @@ display: inline-block; height:1.5rem; left: 15px; -// top: 15px; +/* top: 15px; */ width:1.5rem; text-align: center; line-height: 1.5rem;; @@ -586,12 +577,10 @@ .quad_list .column_symbol{ height: 37px !important; - } .quad_list .column_pos{ width: 130px !important; - } .quad_list div.column_plot, div.column_stateengine, div.column_uzsu, div.column_move_up, div.column_move_down, div.column_stop, div.column_mute { @@ -655,13 +644,11 @@ div.quad_cover-popup img { margin: 5px 0 8px 0; } - -.column_value_slider, .column_pos_slider, .column_song_position, .column_volume_slider -{ +.column_value_slider, .column_pos_slider, .column_song_position, .column_volume_slider { vertical-align: middle; } -.docu_text{ +.docu_text { margin-top: 20px; margin-bottom: 10px; } diff --git a/pages/base/root.html b/pages/base/root.html index 338c60743..6d805d481 100755 --- a/pages/base/root.html +++ b/pages/base/root.html @@ -54,7 +54,6 @@ 'lib/weather/jdigiweather.css', 'pages/base/base.css', 'pages/base/quad.css', - icon0 ~ 'jquery.mobile.icons.min.css', design_css, 'pages/' ~ config_pages ~ '/visu.css' ] %} @@ -232,18 +231,24 @@ io.run(); }); - // Activate the info field in top right corner + // Do some actions on page show $(document).on('pagecontainershow', function () { + // Activate the info field in top right corner notify.display(); - {% if driver_error_msg is not empty %} + // show warning if driver error has been detected + {% if driver_error_msg is not empty %} if(io.socketErrorNotification == null || !notify.exists(io.socketErrorNotification)) io.socketErrorNotification = notify.message('warning', 'Backend Driver', "{{ driver_error_msg }}"); - {% endif %} - // workaround for jQM theme bug - part II + {% endif %} + + /* workaround for jQM theme bug in Firefox - obsolete after fix in Firefox $(this).find('.ui-collapsible-heading').each(function(){ var headerClass = 'ui-page-theme-'+ $(this).closest('div').attr('data-theme'); $(this).addClass(headerClass); }); + */ + + // reset all collapsibles in menu to original state {% if config_collapsible_reset %} $(sv.activePage).find('.secondary [data-collapsed="false"] .ui-collapsible-heading-collapsed').parents().first().collapsible('expand'); {% endif%} @@ -259,12 +264,36 @@ } //DEBUG: console.log('anchor clicked', event.target); }); + + // detect link target with anchor and scroll to it, e.g href="index.php?page=myPage&anchor=myAnchor" + $(document).on('click', 'a[href*="&anchor="]', function (event) { + event.preventDefault(); + if (event.target.href !== undefined) + var targetURI = event.target.href; + else + targetURI = $(event.target).parents('a.ui-link').first()[0].href; + + var anchorPos = targetURI.indexOf('&anchor'); + var anchor = targetURI.substring(anchorPos+8); + var target = targetURI.substring(0, anchorPos); + var targetOffset = $(sv.activePage).find('.ui-content').offset().top; - // scroll to anchor after page is called w/ anchor link e.g href="index.php?page=myPage&anchor=myAnchor" + if (location.href.indexOf(target) == 0){ + $.mobile.silentScroll($(sv.activePage).find('#'+ anchor).offset().top - targetOffset); + //DEBUG: console.log('same page link clicked: '+ anchor) + } + else { + $(':mobile-pagecontainer').pagecontainer('change', target); + window.anchorTarget = anchor + //DEBUG: console.log('new page link clicked: '+ anchor) + } + }); $(document).on('pagecontainerchange', function () { - var searchAnchor = location.search.indexOf('anchor'); - if (searchAnchor > 0) { - $.mobile.silentScroll($('#'+location.search.substring(searchAnchor+7)).offset().top); + if (window.anchorTarget != null) { + //DEBUG: console.log ('click on new page anchor target "'+ window.anchorTarget + '" detected'); + var targetOffset = $(sv.activePage).find('.ui-content').offset().top; + $.mobile.silentScroll($(sv.activePage).find('#'+ window.anchorTarget).offset().top - targetOffset); + window.anchorTarget = null; } }); diff --git a/pages/base/smarthomeNG.html b/pages/base/smarthomeNG.html index 90bb328cd..00eeef755 100644 --- a/pages/base/smarthomeNG.html +++ b/pages/base/smarthomeNG.html @@ -1,8 +1,8 @@ /** * ----------------------------------------------------------------------------- * @package smartVISU -* @author Martin Gleiss -* @copyright 2012 - 2015 +* @author Martin Gleiß +* @copyright 2012 - 2024 * @license GPL [http://www.gnu.de] * ----------------------------------------------------------------------------- */ @@ -46,11 +46,11 @@

    memory ({{ basic.print('memorynow', 'env.core.memory', 'kB', 'VAR / 1024') }

    - system start: {{ basic.print('systemstart', 'env.system.start', 'short') }}, - SmartHomeNG start: {{ basic.print('corestart', 'env.core.start', 'short') }}
    + System Start: {{ basic.print('systemstart', 'env.system.start', 'short') }},  + SmartHomeNG Start: {{ basic.print('corestart', 'env.core.start', 'short') }}
    {{ lib.connection() }}
    -

    Location

    +

    {{ lang('system', 'location')|capitalize }}

    sunrise {{ basic.print('sunrise', 'env.location.sunrise', 'time') }} @@ -60,7 +60,7 @@

    Location

    {{ basic.print('moonrise', 'env.location.moonrise', 'time') }} moonset {{ basic.print('moonset', 'env.location.moonset', 'time') }}, - moonphase: {{ basic.print('moonphase', 'env.location.moonphase') }}, moonlight: {{ basic.print('moonlight', 'env.location.moonlight', '%') }} + {{ lang('system', 'moonphase') }}: {{ basic.print('moonphase', 'env.location.moonphase') }}, {{ lang('system', 'moonlight') }}: {{ basic.print('moonlight', 'env.location.moonlight', '%') }}
    diff --git a/pages/base/system_menu.html b/pages/base/system_menu.html index 6c109b02f..48b80b239 100644 --- a/pages/base/system_menu.html +++ b/pages/base/system_menu.html @@ -2,7 +2,7 @@ * ----------------------------------------------------------------------------- * @package smartVISU * @author Wolfram v. Hülsen, ramann -* @copyright 2021 - 2023 +* @copyright 2021 - 2024 * @license GPL [http://www.gnu.de] * ----------------------------------------------------------------------------- */ @@ -10,19 +10,19 @@
      -
    • System
    • +
    • {{ lang('system', 'system') }}
    • {{ lib.svgimg ('', 'message_service.svg', 'icon0', '') }} -

      Configuration

      +

      {{ lang('system', 'config') }}

    • {{ lib.svgimg ('', 'control_clear.svg', 'icon0', '') }} -

      Template checker

      +

      {{ lang('system', 'templatechecker') }}

    • @@ -46,16 +46,16 @@

      SmartHomeNG

    • {{ lib.svgimg ('', 'edit_save.svg', 'icon0', '') }} -

      Backup config

      +

      {{ lang('system', 'backup') }}

    • -
    • Help
    • +
    • {{ lang('system', 'help') }}
    • {{ lib.svgimg ('', 'edit_copy.svg', 'icon0', '') }} -

      Documentation

      +

      {{ lang('system', 'docu') }}

    • @@ -63,7 +63,7 @@

      Documentation

    • {{ lib.svgimg ('', 'message_help.svg', 'icon0', '') }} -

      Kurzanleitung (German)

      +

      Kurzanleitung (Deutsch)

    • {% endif %} @@ -71,14 +71,21 @@

      Kurzanleitung (German)

    • {{ lib.svgimg ('', 'time_manual_mode.svg', 'icon0', '') }} -

      Support on knx-user-forum

      +

      {{ lang('system', 'forum') }}

    • {{ lib.svgimg ('', 'logo_GitHub.svg', 'icon0', '') }} -

      Source & Issues on GitHub

      +

      {{ lang('system', 'github') }}

      +
      +
    • + +
    • + + {{ lib.svgimg ('', 'edit_favorites', 'icon0', '') }} +

      {{ lang('system', 'newstuff') }}

    • diff --git a/pages/base/templatechecker.html b/pages/base/templatechecker.html index 4c9e80c93..682e25017 100644 --- a/pages/base/templatechecker.html +++ b/pages/base/templatechecker.html @@ -1,15 +1,13 @@ /** * ----------------------------------------------------------------------------- * @package smartVISU -* @author Thomas Ernst -* @copyright 2016 +* @author Thomas Ernst, Wolfram v. Hülsen +* @copyright 2016 - 2024 * @license GPL [http://www.gnu.de] * ----------------------------------------------------------------------------- */ - {% extends "system.html" %} - {% import "@widgets/forms.html" as forms %} {% block content %} @@ -17,42 +15,43 @@ +

      Template Checker

      +

      {{ lang('templatechecker', 'system') }}:

      +
      +
      + 'config.ini' {{ lang('templatechecker', 'file') }}
      + +
      + 'temp' {{ lang('templatechecker', 'directory') }}
      + +
      + php version
      + +
      + php {{ lang('templatechecker', 'extension') }} 'mbstring'
      + +
      + newer version
      +
      +
      +

      {{ lang('templatechecker', 'pages') }}:

      {{ forms.config_select('tplchk', { 'tplchk': {'pages': config_pages }}, 'pages', dir('pages', '^(?!base|apps)(.+?)') ) }} - - - +
      + Masteritem {{lang('templatechecker', 'file') }}
      + + + - - + +
      -

      System checks:

      -
      -
      - 'config.ini' file
      - -
      - 'temp' directory
      - -
      - php version
      - -
      - php extension 'mbstring'
      - -
      - newer version
      - -
      - Masteritem file
      - -
      -

      Files in template directory:

      +

      {{ lang('templatechecker', 'filelist') }}:

      {% endblock %} diff --git a/pages/base/widget_assistant.html b/pages/base/widget_assistant.html index 20ad4d476..330589292 100644 --- a/pages/base/widget_assistant.html +++ b/pages/base/widget_assistant.html @@ -60,10 +60,10 @@ } .CodeMirror-line{ text-align: left; - padding-left: 20px;!important + padding-left: 20px !important; } .CodeMirror pre { - padding-left: 30px; !important + padding-left: 30px !important; } .CodeMirror-wrap pre { @@ -107,100 +107,83 @@ {% set actPage = config_pages %} -
      - -
      - +
      + +
      - -
      + + +
      + +
      -
      - -
      -
      -
      -
      -

      Widget - Assistant

      - - - - - -
      - - - - -
      - -
      -
      - - - - - - - - - - - - - - - - - -
      - Items loaded -
      - - Colors loaded - -
      - Icons loaded -
      - Widgets loaded -
      - - - - -
      - -
      -
      - - -
      -
      +
      +
      +
      +

      Widget Editor

      + + + + + +
      + +
      + + + + + + + + + + + +
      + Items {{ lang('templatechecker', 'loaded') }}
      + {{ lang('widgetassi', 'colors') }} {{ lang('templatechecker', 'loaded') }}
      + Icons {{ lang('templatechecker', 'loaded') }}
      + Widgets {{ lang('templatechecker', 'loaded') }} +
      + + + + +
      + +
      +
      + +
      +
      + {% set readme = lang('baselang') != 'de' ? 'lib/widget_assistant/README.html' : 'lib/widget_assistant/README_DE.html' %}
      -

      Widget-Preview

      +

      {{ lang('widgetassi', 'waDocu') }}

      -
      @@ -243,10 +226,11 @@

      Widget-Preview

      actDictMode = 2 ChangeDict(6) }, - "Ctrl-7": function(cm) { - changeCloseBrackets(true) + /** "Ctrl-7": function(cm) { + changeCloseBrackets(true) }, - "Ctrl-9": function(cm) { + */ + "Ctrl-9": function(cm) { ChangeSearch() }, diff --git a/pages/docu/basic/widget_basic.icon.html b/pages/docu/basic/widget_basic.icon.html index 078f52c00..0aec953ec 100644 --- a/pages/docu/basic/widget_basic.icon.html +++ b/pages/docu/basic/widget_basic.icon.html @@ -2,7 +2,7 @@ * ----------------------------------------------------------------------------- * @package smartVISU * @author Martin Gleiß -* @copyright 2012 - 2015 +* @copyright 2012 - 2024 * @license GPL [http://www.gnu.de] * ----------------------------------------------------------------------------- */ @@ -52,7 +52,7 @@
      Examples
      {{ basic.icon(icon.light('','','bath.light.value'), 'rgb', ['bath.color.r', 'bath.color.g', 'bath.color.b']) }}
      - New in v3.0: icon sizes may be altered + Icon sizes may be altered:
      {% filter trim|escape|nl2br %}{% verbatim %} {{ basic.icon('light_light', 'rgb', ['bath.color.r', 'bath.color.g', 'bath.color.b'], 'micro') }} diff --git a/pages/docu/basic/widget_basic.offset.html b/pages/docu/basic/widget_basic.offset.html index 4292aff50..28a53a214 100644 --- a/pages/docu/basic/widget_basic.offset.html +++ b/pages/docu/basic/widget_basic.offset.html @@ -2,7 +2,7 @@ * ----------------------------------------------------------------------------- * @package smartVISU * @author Martin Gleiß -* @copyright 2012 - 2015 +* @copyright 2012 - 2024 * @license GPL [http://www.gnu.de] * ----------------------------------------------------------------------------- */ @@ -34,7 +34,7 @@
      Examples
      {{ basic.offset('', 'bath.rtr.set', -0.5, 'minus', '', 'micro') }}
      - New in v3.2: limits can be added + Limits can be applied as min / max values for the item:
      diff --git a/pages/docu/basic/widget_basic.print.html b/pages/docu/basic/widget_basic.print.html index d9a8a3ba3..b35f2c0f2 100755 --- a/pages/docu/basic/widget_basic.print.html +++ b/pages/docu/basic/widget_basic.print.html @@ -56,7 +56,19 @@
      Colors
      {{ basic.print('', 'bath.light.value', '', '', '', '#f00') }}
      {{ basic.print('', 'bath.light.value', '', '', [100,200], ['green', '', 'icon1']) }}
      - +
      Usage as Tooltip
      + By giving any other widget an id and referencing this in the parameter href, a dynamic tooltip can be created on that widget by passing the value 'tooltip' to the parameter "rel". Some widgets need to be wrapped in a span containing the id. + Hover with the mouse over the flipswitch to see the effect. (Note: Will not work on Android devices wich do not support hover effects) +
      + {% filter trim|escape|nl2br %}{% verbatim %} + {{ basic.flip('', 'bath.light.switch') }} + {{ basic.print('', 'bath.light.switch', 'text', 'VAR=="1"?"Schaltuhr ist an":"Schalter ist aus"','','','myFlip1','tooltip') }} {% endverbatim %}{% endfilter %} +
      +
      + {{ basic.flip('myFlip1', 'bath.light.switch') }} + {{ basic.print('', 'bath.light.switch', 'text', 'VAR=="1"?"Schalter ist an":"Schalter ist aus"','','','myFlip1','tooltip') }}
      +
      +
      Advanced Scripting
      basic.print can also be used for some "hacks".
      This example shows how to update the color of a widget text based on the value of ANOTHER item. Click on the heater icon and change the temperature. The temperature print will turn red if the heater is off and green if it is on.
      diff --git a/pages/docu/basic/widget_basic.select.html b/pages/docu/basic/widget_basic.select.html index 92a11a37c..e3505bc50 100644 --- a/pages/docu/basic/widget_basic.select.html +++ b/pages/docu/basic/widget_basic.select.html @@ -26,7 +26,6 @@
      Example
      -
      horizontal
      @@ -46,18 +45,20 @@
      Example





      -




      - -
      New
      - List items can be used as dynamic option lists for the menu type. If a list item "itemvals" is given it overrides the other value parameters given in the macro call. - Optional texts can be defined by a second item "itemtxts". If this is not defined the option values will be used as texts. -
      - {% filter trim|escape|nl2br %}{% verbatim %} - {{ basic.select('', 'bath.multistate', '', '', '', '', '', '', 'bath.list1', 'bath.list2') }} - {% endverbatim %}{% endfilter %} -
      +
      - +
      Dynamic Select Values
      + List items can be used as dynamic option lists for the menu type. If a list item is given in the parameter "itemvals" it overrides the other value parameters given in the macro call. + Optional texts can be defined by a second item in the parameter "itemtxts". If this is not defined the option values will be used as texts. +
      + {% filter trim|escape|nl2br %}{% verbatim %} + {{ basic.select('', 'bath.multistate', '', '', '', '', '', '', 'bath.list1', 'bath.list2') }} + {% endverbatim %}{% endfilter %} +
      +
      Activity Indicator
      + The parameter "indictor" can be used to show different icons / colors / animations while the widget is waiting for the item update by the backend. For the grouped switches the options are + the same as in basic.stateswitch. In "menu" mode the widget displays activity with a blinking select menu, only. + {% endblock %} diff --git a/pages/docu/basic/widget_basic.slider.html b/pages/docu/basic/widget_basic.slider.html index 30e2401a5..61a11fa5a 100644 --- a/pages/docu/basic/widget_basic.slider.html +++ b/pages/docu/basic/widget_basic.slider.html @@ -2,7 +2,7 @@ * ----------------------------------------------------------------------------- * @package smartVISU * @author Martin Gleiß -* @copyright 2012 - 2015 +* @copyright 2012 - 2024 * @license GPL [http://www.gnu.de] * ----------------------------------------------------------------------------- */ @@ -15,7 +15,7 @@
      Examples
      See the various scaling options in the following examples.

      - Silent mode (new in v3.1)
      + Silent mode
      Per default, sliders send their values constantly during operation. There is also a silent mode which limits value output to the end of the sliding phase. See the difference by moving different sliders. The bottom slider is switched to silent mode while the others send their values live. diff --git a/pages/docu/basic/widget_basic.stateswitch.html b/pages/docu/basic/widget_basic.stateswitch.html index 7585ab493..10d67f2f9 100644 --- a/pages/docu/basic/widget_basic.stateswitch.html +++ b/pages/docu/basic/widget_basic.stateswitch.html @@ -2,7 +2,7 @@ * ----------------------------------------------------------------------------- * @package smartVISU * @author WIDMER Stefan -* @copyright 2017 +* @copyright 2017 - 2024 * @license GPL [http://www.gnu.de] * ----------------------------------------------------------------------------- */ @@ -71,7 +71,7 @@
      Long press
      {{ basic.stateswitch('', 'bath.light.switch', '', '', '', '', ['green', '#f00'], '', 'bath.multistate', '+6', 5) }}
      -
      New in v3.2: Long press opening popup
      +
      Long press opening popup
      {% filter trim|escape|nl2br %}{% verbatim %} {{ basic.stateswitch('', 'bath.light.scene', '', 0, 'status_available', '', '', '', '#pop1') }} @@ -92,7 +92,7 @@

      This is a Popup


      Styling
      - New in v3.3: 6 classes 'icon0' to 'icon5' are available for coloring the icons. Pass them in the "color" parameter. + 6 classes 'icon0' to 'icon5' are available for coloring the icons. Pass them in the "color" parameter.

      Dynamic icons can be used by calling the icon.* widgets in the icon parameters:
      diff --git a/pages/docu/basic/widget_basic.symbol.html b/pages/docu/basic/widget_basic.symbol.html index 256180373..9ee512c3d 100644 --- a/pages/docu/basic/widget_basic.symbol.html +++ b/pages/docu/basic/widget_basic.symbol.html @@ -2,7 +2,7 @@ * ----------------------------------------------------------------------------- * @package smartVISU * @author Martin Gleiß -* @copyright 2012 - 2015 +* @copyright 2012 - 2024 * @license GPL [http://www.gnu.de] * ----------------------------------------------------------------------------- */ @@ -141,7 +141,7 @@
      Examples
      {{ basic.symbol('', 'bath.light.value', ['low','medium','high'], ['control_1','control_2','control_3'], [85,170], '>') }}
      - New in v3.0: size of symbols may be altered. As from v3.1 buttons are also provided and an additional text can be displayed on the + The size of symbols may be altered and button styles are provided. An additional text can be displayed on the button(flip the switch to "on" )
      {{ basic.flip('', 'bath.light.switch') }} diff --git a/pages/docu/basic/widget_basic.tank.html b/pages/docu/basic/widget_basic.tank.html index 5d19a29ec..f5f9c402f 100644 --- a/pages/docu/basic/widget_basic.tank.html +++ b/pages/docu/basic/widget_basic.tank.html @@ -21,8 +21,8 @@
      Examples
      {{ basic.tank('tank3', 'bath.value', 0, 255, 5, 'cylinder', '#f90' ) }} {{ basic.tank('tank4', 'bath.value', 0, 255, 5, 'water') }} {{ basic.tank('tank5', 'bath.value', 0, 255, 5, 'water', '#0c0') }} - {{ basic.tank('tank6', 'bath.value', 0, 255, 5, 'pallets') }} - + {{ basic.tank('tank6', 'bath.value', 0, 255, 5, 'pellets') }} + {{ basic.tank('tank7', 'bath.value', 0, 255, 5, 'cylinder', ['#c00','#cc0','#0c0','#cc0','#c00'], [80,100,155,200]) }} {% endverbatim %}{% endfilter %}

      @@ -32,7 +32,8 @@
      Examples
      {{ basic.slider('slider1', 'bath.value', 0, 255, 5) }}
      - From left to right: 'none', 'cylinder', 'cylinder' orange, 'water', 'water' green, 'pallets' + From left to right: 'none', 'cylinder', 'cylinder' orange, 'water', 'water' green, 'pellets', 'cylinder'
      + The last one will have different colors depending on the value <80: red, 80-99: yellow, 100-154: green, 155-199: yellow, >=200: red
      {{ basic.tank('tank1', 'bath.value') }} @@ -40,7 +41,8 @@
      Examples
      {{ basic.tank('tank3', 'bath.value', 0, 255, 5, 'cylinder', '#f90' ) }} {{ basic.tank('tank4', 'bath.value', 0, 255, 5, 'water') }} {{ basic.tank('tank5', 'bath.value', 0, 255, 5, 'water', '#0c0') }} - {{ basic.tank('tank6', 'bath.value', 0, 255, 5, 'pallets') }} + {{ basic.tank('tank6', 'bath.value', 0, 255, 5, 'pellets') }} + {{ basic.tank('tank7', 'bath.value', 0, 255, 5, 'cylinder', ['#c00','#cc0','#0c0','#cc0','#c00'], [80,100,155,200]) }}
      diff --git a/pages/docu/basic/widget_basic.trigger.html b/pages/docu/basic/widget_basic.trigger.html index 00e140442..99949b484 100644 --- a/pages/docu/basic/widget_basic.trigger.html +++ b/pages/docu/basic/widget_basic.trigger.html @@ -2,7 +2,7 @@ * ----------------------------------------------------------------------------- * @package smartVISU * @author Martin Gleiß -* @copyright 2012 - 2015 +* @copyright 2012 - 2024 * @license GPL [http://www.gnu.de] * ----------------------------------------------------------------------------- */ @@ -13,7 +13,7 @@ {% block example %} This widget triggers a logic on the backend given by the parameter 'name'. The default function will show a button for manual triggering.
      - NEW: with the parameter 'event' set to 'page', the logic is being triggered automatically during page create (button is hidden) while 'both' enables both triggenring methods. + With the parameter 'event' set to 'page', the logic is being triggered automatically during page create (button is hidden) while 'both' enables both triggenring methods.
      Examples
      diff --git a/pages/docu/calendar/widget_calendar.list.html b/pages/docu/calendar/widget_calendar.list.html index b171665ed..a7807ead0 100644 --- a/pages/docu/calendar/widget_calendar.list.html +++ b/pages/docu/calendar/widget_calendar.list.html @@ -37,4 +37,22 @@
      Examples
      {{ calendar.list('', '', 3, ['#993399','green'], '', ['Personal','Waste'], 'l') }}
      + You can specify your own icons and colors for calendar events in the language file , e.g. .dropins/lang/mylang.ini in the section "[calendar_event_format]". +
      + + blue bin[icon] = message_garbage
      + blue bin[color] = #0000FF +
      +
      + Attention: calendar.list searches for the specified keywords and applies icon and color to the event if it's title contains a keyword. With the above example it will display the same for "buy blue binoculars" + and "blue bin". Specify the more specific keywords in sequence behind the more general ones in order to optimize pattern matching.

      + + A default icon and color can be defined using "default_img_list". If no default is defined a transparent image "trans.png" will be displayed. +
      + + default_img_list[icon] = message_achtung
      + default_img_list[color] = "rgb(32, 178, 170)" +
      +
      + {% endblock %} diff --git a/pages/docu/calendar/widget_calendar.waste.html b/pages/docu/calendar/widget_calendar.waste.html index fbdc1c715..1cf4fd08d 100644 --- a/pages/docu/calendar/widget_calendar.waste.html +++ b/pages/docu/calendar/widget_calendar.waste.html @@ -27,5 +27,24 @@
      Examples
      + + You can specify your own icons and colors for calendar events in the language file , e.g. .dropins/lang/mylang.ini in the section "[calendar_event_format]". + If the icon is defined as "message_garbage" the widget converts it to "message_garbage_2" which is the roll-out garbage container. +
      + + blue bin[icon] = message_garbage
      + blue bin[color] = #0000FF +
      +
      + Attention: calendar.waste searches for the specified keywords and applies icon and color to the event if it's title starts with a keyword. With the above example it will display the same for "blue binoculars" + and "blue bin". Specify the more specific keywords in sequence behind the more general ones in order to optimize pattern matching.

      + + A default icon and color can be defined using "default_img_waste". If no default is defined a transparent image "trans.png" will be displayed. +
      + + default_img_waste[icon] = message_achtung
      + default_img_waste[color] = "rgb(32, 178, 170)" +
      +
      {% endblock %} diff --git a/pages/docu/clock/widget_clock.countdown.html b/pages/docu/clock/widget_clock.countdown.html index b7373ba21..da2648cb4 100644 --- a/pages/docu/clock/widget_clock.countdown.html +++ b/pages/docu/clock/widget_clock.countdown.html @@ -12,7 +12,7 @@ {% block example %} diff --git a/pages/docu/device/widget_device.uzsuicon.html b/pages/docu/device/widget_device.uzsuicon.html index 425f7764a..975d2652e 100755 --- a/pages/docu/device/widget_device.uzsuicon.html +++ b/pages/docu/device/widget_device.uzsuicon.html @@ -2,7 +2,7 @@ * ----------------------------------------------------------------------------- * @package smartVISU * @author Martin Gleiß -* @copyright 2012 - 2015 +* @copyright 2012 - 2024 * @license GPL [http://www.gnu.de] * ----------------------------------------------------------------------------- */ @@ -14,7 +14,7 @@ diff --git a/pages/docu/device/widget_device.uzsutable.html b/pages/docu/device/widget_device.uzsutable.html index 2cc4c0c01..9526c4ecf 100644 --- a/pages/docu/device/widget_device.uzsutable.html +++ b/pages/docu/device/widget_device.uzsutable.html @@ -11,9 +11,9 @@ {% block example %} {% endmacro %} diff --git a/widgets/plot.html b/widgets/plot.html index 050115369..881067e03 100644 --- a/widgets/plot.html +++ b/widgets/plot.html @@ -74,7 +74,7 @@ * * @param {id=} unique id for this widget. Add the string "plotpopup" to activate on-demand plot data loading, i.e. plot data are not loaded during page creation but when the popup containing the plot is opened. (optional) * @param {item[?](bool,num,list)} series of item(s); multiple items in array form: [ item1 , item2 ] -* @param {text[?](avg,sum,min,max,minmax,minmaxavg,raw,on)=avg} the aggregation mode (optional, default 'avg') +* @param {mode[?]=avg} the aggregation mode (optional, default 'avg') May be a single mode or an array with separate mode per item. Available modes depend on backend, common ones are 'avg', 'sum', 'min', 'max', 'minmax', 'minmaxavg', 'on', 'raw'. See backend's documentation for further information. * @param {duration=1h} the minimum time (x-axis): '1h', '2h'... (duration-format) Value is interpreted as timestamp if no unit is given (e.g. 1629109298731). @@ -90,10 +90,10 @@ * @param {text[?](line,linestack,stair,spline,area,areastair,areaspline,areastack,column,columnstack)=line} type of each serie: 'line', 'linestack', 'stair', 'spline', 'area', 'areastair', 'areaspline', 'areastack','column', 'columnstack'; multiples in array form (optional, default 'line') * @param {text[?]=} title for the x-axis and y-axes in array form: [title_x-axis, title_y-axis1, title_y-axis2, ...] (optional) * @param {duration(advanced,day)=} minimal time range while zooming or 'advanced' or 'day' (optional, duration-format) - By passing 'advanced' a seaparate range selector is shown and data grouping for large amount of data is enabled + By passing 'advanced' a separate range selector is shown and data grouping for large amount of data is enabled By passing 'day', the plot is shown from 0:00 until 24:00 on the day starting after tmin. Set tmin to '24h' or '1d' for today, '48h' or '2d' for yesterday and so on. tmax must be 'now' or at least 48 hours less than tmin. -* @param {value[]=} assignment of the series to the y-axes; multiples in array form (optional) -* @param {value[]=} y-axes setup for left '0' and right '1' hand in array form (optional) +* @param {value[?]=} assignment of the series to the y-axes; multiples in array form (optional) +* @param {value[?]=} y-axes setup for left '0' and right '1' hand in array form (optional) * @param {color[?]=} y-axes color; multiples in array form (optional) * @param {text[?](linear,logarithmic,boolean)=linear} y-axes type, one of 'linear', 'logarithmic' or 'boolean'; multiples in array form (optional, default 'linear') * @param {format[?]=} unit(s) for the y-axis. Either a unit of the language file, an individual format string (PHP sprintf like) or a simple string as suffix. Multiples in array form. (optional) @@ -108,51 +108,60 @@ */ {% macro period(id, item, mode, tmin, tmax, ymin, ymax, count, label, color, exposure, axis, zoom, assign, opposite, ycolor, ytype, unit, chartoptions, stacking, stacks, export, source) %} - {% if (not export is empty) and export > 0 %} - - - {% if export == 2 %} - - {% endif %} - {% endif %} - {% set source = source|default('database') %} - {% set tmax = (tmax|default('now')|lower=='0h' or tmax==0 or tmax is empty) ? 'now' : tmax %} - {% set tmin = tmin|default('1h') %} - - {% set mode = (mode is iterable ? mode : [mode|default('avg')]) %} - {% set count = (count is iterable ? count : [count|default(100)]) %} - {% set unit = (unit is iterable ? unit : [unit|default('')]) %} - {% set units = [] %} - {% for uniti in unit %} - {% set units = units|merge([uniti|replace({',' : ';'})]) %} /** hide comma before implode/explode to allow format strings, e.g. %01,02f% */ - {% endfor %} - {% set stacking = (stacking is iterable ? stacking : [stacking|default('normal')]) %} - {% set stacks = (stacks is iterable ? stacks : [stacks|default('0')]) %} - {% set item = (item is iterable ? item : [item]) %} - {% set seriesitems = [] %} - {% for itemi in item %} - {% if mode|length < loop.index %} - {% set mode = mode|merge([mode|last]) %} - {% endif %} - {% set modei = mode[loop.index0] %} - - {% if count|length < loop.index %} - {% set count = count|merge([count|last]) %} - {% endif %} - {% set counti = count[loop.index0] %} - - {% if source == 'database' %} - {% if modei == 'minmax' %} - {% set seriesitems = seriesitems|merge([ implode(itemi, ['min', tmin, tmax, counti]), implode(itemi, ['max', tmin, tmax, counti]) ]) %} - {% elseif modei == 'minmaxavg' %} - {% set seriesitems = seriesitems|merge([ implode(itemi, ['min', tmin, tmax, counti]), implode(itemi, ['max', tmin, tmax, counti]), implode(itemi, ['avg', tmin, tmax, counti]) ]) %} - {% else %} - {% set seriesitems = seriesitems|merge([implode(itemi, [modei, tmin, tmax, counti])]) %} - {% endif %} - {% else %} - {% set seriesitems = seriesitems|merge([itemi]) %} - {% endif %} - {% endfor %} + {%- if (not export is empty) and export > 0 -%} + {%- if once('plotimageexport') -%} + + + {%- endif -%} + {%- if export == 2 -%} + {%- if once('plotdataexport') -%} + + {%- endif -%} + {%- endif -%} + {%- endif -%} + + {%- set source = source|default('database') -%} + {%- set tmax = (tmax|default('now')|lower=='0h' or tmax==0 or tmax is empty) ? 'now' : tmax -%} + {%- set tmin = tmin|default('1h') -%} + + {%- set mode = (mode is iterable ? mode : [mode|default('avg')]) -%} + {%- set count = (count is iterable ? count : [count|default(100)]) -%} + {%- set unit = (unit is iterable ? unit : [unit|default('')]) -%} + {%- set units = [] -%} + {%- for uniti in unit -%} + {%- set units = units|merge([uniti|replace({',' : ';'})]) -%} /** hide comma before implode/explode to allow format strings, e.g. %01,02f% */ + {%- endfor -%} + {%- set stacking = (stacking is iterable ? stacking : [stacking|default('normal')]) -%} + {%- set stacks = (stacks is iterable ? stacks : [stacks|default('0')]) -%} + {%- set item = (item is iterable ? item : [item]) -%} + {%- set seriesitems = [] -%} + {%- for itemi in item -%} + {%- if mode|length < loop.index -%} + {%- set mode = mode|merge([mode|last]) -%} + {%- endif -%} + {%- set modei = mode[loop.index0] -%} + + {%- if count|length < loop.index -%} + {%- set count = count|merge([count|last]) -%} + {%- endif -%} + {%- set counti = count[loop.index0] -%} + {%- if mode == 'raw' or counti >= 5000 -%} + {%- if once('plotboost') -%} + + {%- endif -%} + {%- endif -%} + {%- if source == 'database' -%} + {%- if modei == 'minmax' -%} + {%- set seriesitems = seriesitems|merge([ implode(itemi, ['min', tmin, tmax, counti]), implode(itemi, ['max', tmin, tmax, counti]) ]) -%} + {%- elseif modei == 'minmaxavg' -%} + {%- set seriesitems = seriesitems|merge([ implode(itemi, ['min', tmin, tmax, counti]), implode(itemi, ['max', tmin, tmax, counti]), implode(itemi, ['avg', tmin, tmax, counti]) ]) -%} + {%- else -%} + {%- set seriesitems = seriesitems|merge([implode(itemi, [modei, tmin, tmax, counti])]) -%} + {%- endif -%} + {%- else -%} + {%- set seriesitems = seriesitems|merge([itemi]) -%} + {%- endif -%} + {%- endfor -%} + class="plot{% if zoom == 'advanced' %} plot-highstock{% endif %}"> + {% endmacro %} @@ -232,9 +242,9 @@ /** * Plots a chart of time series data * -* @param {id=} unique id for this widget (optional) -* @param {item(bool,num,list)} series of item(s); multiple items in array form: [ item1 , item2 ] -* @param {text(avg,sum,min,max,raw,on)=avg} the aggregation mode (optional, default 'avg') +* @param {id=} unique id for this widget (optional). Will be used as label / curve name in the tooltip, as well. +* @param {item(bool,num,list)} single item to plot +* @param {mode=avg} the aggregation mode (optional, default 'avg') Available modes depend on backend, common ones are 'avg', 'sum', 'min', 'max', 'minmax', 'minmaxavg', 'on', 'raw'. See backend's documentation for further information. * @param {duration=1h} the minimum time (x-axis): '1h', '2h'... (duration-format) Value is interpreted as timestamp if no unit is given (e.g. 1629109298731). * @param {duration=now} the maximum time (x-axis): '', '1h', '2h'... (duration-format, default: now). Value is interpreted as timestamp if no unit is given (e.g. 1629109298731). @@ -243,21 +253,27 @@ * @param {value=100} the number of datapoints in the series (optional, default 100) * @param {value=120} width of the chart in px (optional, default = 120 px) * @param {value=20} height of the chart in px (optional, default = 20 px) -* @param {text[?](line,stair,spline,area,areastair,areaspline,column)=line} type of each serie: 'line', 'stair', 'spline', 'area', 'areastair', 'areaspline', 'column' (optional, default 'line') -* @param {format[?]=} unit(s) for the y-axis. Either a unit of the language file, an individual format string (PHP sprintf like) or a simple string as suffix. Multiples in array form. (optional) -* @param {text[?]=} object with additional options for Highcharts, see https://api.highcharts.com/ (optional) -* @param {text(database,item)=database} source of the data. If set to 'item', data must be provided in a list item (optional, default=database) +* @param {text(line,stair,spline,area,areastair,areaspline,column)=line} type of each serie: 'line', 'stair', 'spline', 'area', 'areastair', 'areaspline', 'column' (optional, default 'line') +* @param {format=} unit for the y-axis. Either a unit of the language file, an individual format string (PHP sprintf like) or a simple string as suffix. (optional) +* @param {text(database,item)=database} source of the data. If set to 'item', data must be provided in a list item (optional, default=database) +* @param {text=} URL to use as link or id of a widget to add a tooltip (optional) +* @param {text=} use 'tooltip' in combination with a widget id as href in order to attach a tooltip to that widget. Any other text will interpret href as link to a popup (optional) * -* @see misc/fundamentals#Array-Form * @see misc/fundamentals#Duration-Format */ -{% macro sparkline(id, item, mode, tmin, tmax, ymin, ymax, count, width, height, exposure, unit, source) %} +{% macro sparkline(id, item, mode, tmin, tmax, ymin, ymax, count, width, height, exposure, unit, source, href, rel) %} {% set source = source|default('database') %} {% set tmax = (tmax|default('now')|lower=='0h' or tmax==0 or tmax is empty) ? 'now' : tmax %} {% set tmin = tmin|default('1h') %} {% set mode = mode|default('avg') %} {% set count = count|default(100) %} + {%- if mode == 'raw' or count >= 5000 -%} + {%- if once('plotboost') -%} + + {%- endif -%} + {%- endif -%} + /** there is no way to measure the series length from here like in plot.period. So we can't import boost.js on demand for big series. Load it explicitely on the visu page if needed. */ {% set width = width|default(120) %} {% set height = height|default(20) %} {% set unit = unit|default('') %} @@ -267,17 +283,19 @@ {% else %} {% set seriesitem = item %} {% endif %} + {% set label = id|default(' ') %} {% set chartoptions = {chart:{backgroundColor: null, borderWidth: 0, margin: [2, 0, 2, 0], width: width, height: height, style: {overflow: 'visible'}, skipClone: true}, title: {text: ''},credits: {enabled: false}, xAxis: [{labels: {enabled: false}, title: {text: null}, startOnTick: false, endOnTick: false, tickPositions: [], type: 'datetime'}], yAxis: [{endOnTick: false, startOnTick: false, labels: {enabled: false}, title: {text: null}, tickPositions: [0]}], legend: {enabled: false}, tooltip: {hideDelay: 0, outside: true, shared: true}, plotOptions: {series: {animation: false, lineWidth: 1, shadow: false, states: {hover: {lineWidth: 1}}, marker: {radius: 1, states: {hover: {radius: 2}}}, fillOpacity: 0.25}}} %} - + {% if href is not empty %}{% endif %} + {% if href is not empty %}{% endif %} {% endmacro %} @@ -378,20 +396,24 @@ */ {% macro xyplot(id, item, xitem, yitem, xmin, xmax, ymin, ymax, label, xylabel, color, exposure, axis, zoom, assign, opposite, ycolor, ytype, unit, chartoptions, stacking, stacks, export) %} - {% if (not export is empty) and export > 0 %} - - - {% if export == 2 %} - - {% endif %} - {% endif %} - {% set unit = (unit is iterable ? unit : [unit|default('')]) %} - {% set units = [] %} - {% for uniti in unit %} - {% set units = units|merge([uniti|replace({',' : ';'})]) %} /** hide comma before implode/explode to allow format strings, e.g. %01,02f% */ - {% endfor %} - {% set stacking = (stacking is iterable ? stacking : [stacking|default('normal')]) %} - {% set stacks = (stacks is iterable ? stacks : [stacks|default('0')]) %} + {%- if (not export is empty) and export > 0 -%} + {%- if once('plotimageexport') -%} + + + {%- endif -%} + {%- if export == 2 -%} + {%- if once('plotdataexport') -%} + + {%- endif -%} + {%- endif -%} + {%- endif -%} + {%- set unit = (unit is iterable ? unit : [unit|default('')]) -%} + {%- set units = [] -%} + {%- for uniti in unit -%} + {%- set units = units|merge([uniti|replace({',' : ';'})]) -%} /** hide comma before implode/explode to allow format strings, e.g. %01,02f% */ + {%- endfor -%} + {%- set stacking = (stacking is iterable ? stacking : [stacking|default('normal')]) -%} + {%- set stacks = (stacks is iterable ? stacks : [stacks|default('0')]) -%} = 1){ + chartOptions.xAxis[i].type = 'datetime'; + chartOptions.xAxis[i].ordinal = false; + } + } + chartOptions.navigator.xAxis.min = chartOptions.xAxis[0].min; + chartOptions.navigator.xAxis.max = chartOptions.xAxis[0].max; + + // register zoom events for multiple x axes + if (this.options.chartOptions.xAxis.length > 1){ + var xAxesCount = this.options.chartOptions.xAxis.length; + chartOptions.xAxis[0].events = { + setExtremes: function(event){ + for (var i= 1; i< xAxesCount; i++){ + that.element.highcharts().xAxis[i].setExtremes(event.min + chartOptions.xAxis[i].min - chartOptions.xAxis[0].min , event.max + chartOptions.xAxis[i].max - chartOptions.xAxis[0].max); + } + } + } + } + } + Highcharts.stockChart(this.element[0], chartOptions); } else { @@ -506,17 +541,18 @@ $.widget("sv.plot_period", $.sv.widget, { // window.servertimeoffset should be available now if (window.servertimeoffset != undefined && window.servertimeoffset != 0 && this.options.servertime == 'yes') chart.time.update({timezoneOffset: parseInt(-Number(sv.serverTimezone.offset)/60 + window.servertimeoffset/60000) }); + var actualDate = new Date(); if (this.options.chartOptions && this.options.chartOptions.xAxis != undefined && typeof this.options.chartOptions.xAxis == 'object' && this.options.chartOptions.xAxis[0].min && this.options.chartOptions.xAxis[0].max){ - for (var i = 0; i < this.options.chartOptions.xAxis.length; i++){ - var xMin = new Date() - new Date().duration(this.options.chartOptions.xAxis[i].min); - var xMax = new Date() - new Date().duration(this.options.chartOptions.xAxis[i].max); + for (var i = this.options.chartOptions.xAxis.length - 1; i > -1; i--){ + var xMin = actualDate - new Date().duration(this.options.chartOptions.xAxis[i].min); + var xMax = actualDate - new Date().duration(this.options.chartOptions.xAxis[i].max); chart.xAxis[i].update({ min: xMin, max: xMax }, false); } } else { - var xMin = new Date() - new Date().duration(this.options.tmin); - var xMax = new Date() - new Date().duration(this.options.tmax); + var xMin = actualDate - new Date().duration(this.options.tmin); + var xMax = actualDate - new Date().duration(this.options.tmax); var dayDuration = 24*3600*1000; if (this.options.zoom == "day"){ @@ -576,13 +612,17 @@ $.widget("sv.plot_period", $.sv.widget, { return [[ value[0], maxValue, minValue ]]; }); - chart.series[seriesIndex].setData(values, true, true, false); + chart.series[seriesIndex].setData(values, false); } else if (response[i]) { - chart.series[seriesIndex].setData(response[i], true, true, false); + chart.series[seriesIndex].setData(response[i], false); } + + // disable data grouping for series with 5000 points and more if boost mode is enabled in order to let boost mode work + if (response[i] && response[i].length >= 5000 && chart.series[seriesIndex].options.dataGrouping.enabled && chart.options.boost.enabled) + chart.series[seriesIndex].update({"dataGrouping": {"enabled": false}}, false); } - + //DEBUG: console.log('chart redraw _update() end'); chart.redraw(); }, @@ -1690,11 +1730,11 @@ $.widget("sv.plot_xyplot", $.sv.widget, { var exportmenu = (this.options.exportmenu >= 1); var styles = []; - // series - var series = []; - var seriesCount = this.items.length; + // series + var series = []; + var seriesCount = this.items.length; - for (var i = 0; i < seriesCount; i++) { + for (var i = 0; i < seriesCount; i++) { var stack = (stacks.length-1 >= i ? stacks[i]: stacks[stacks.length-1]); var stackingMode = (stacking[stack] ? stacking[stack] : stacking[0]); series.push({ @@ -1706,7 +1746,8 @@ $.widget("sv.plot_xyplot", $.sv.widget, { showInNavigator: true, stacking: (exposure[i] != null && exposure[i].toLowerCase().endsWith('stack') ? stackingMode : null), stack: (exposure[i] != null && exposure[i].toLowerCase().endsWith('stack') ? stack : null), - borderRadius: 0 + borderRadius: 0, + dataGrouping: {enabled: (exposure[i] != null && exposure[i].toLowerCase().endsWith('stair') ? false : true)}, }); } @@ -1754,25 +1795,33 @@ $.widget("sv.plot_xyplot", $.sv.widget, { max: xMax, }, stickToMax: false - }, + }, rangeSelector:{ enabled: false}, - yAxis: yaxis, - legend: { - enabled: label.length > 0, - align: 'center', - verticalAlign: 'top', - floating: true, - }, - tooltip: { - shared: true, - split: false, - pointFormatter: function() { - var unit = this.series.yAxis.userOptions.svUnit; - var value = (this.series.yAxis.categories) ? this.series.yAxis.categories[this.y] : parseFloat(this.y).transUnit(unit); - return '\u25CF ' + this.series.name + ': ' + value + '
      '; - } - }, - navigation: { // options for export context menu + yAxis: yaxis, + legend: { + enabled: label.length > 0, + align: 'center', + verticalAlign: 'top', + floating: true, + }, + boost: { + debug: { + timeRendering:false, + timeSeriesProcessing:false, + timeSetup:false + }, + enabled:true + }, + tooltip: { + shared: true, + split: false, + pointFormatter: function() { + var unit = this.series.yAxis.userOptions.svUnit; + var value = (this.series.yAxis.categories) ? this.series.yAxis.categories[this.y] : parseFloat(this.y).transUnit(unit); + return '\u25CF ' + this.series.name + ': ' + value + '
      '; + } + }, + navigation: { // options for export context menu buttonOptions: { enabled: exportmenu, height: 22, @@ -1851,8 +1900,12 @@ $.widget("sv.plot_xyplot", $.sv.widget, { var itemCount = response.length; for (var i = 0; i < itemCount; i++) { - if (response[i]) - chart.series[i].setData(response[i], true, true, false); + if (response[i]){ + chart.series[i].setData(response[i], false); + // disable data grouping for series with 5000 points and more if boost mode is enabled in order to let boost mode work + if (response[i].length >= 5000 && chart.series[i].options.dataGrouping.enabled && chart.options.boost.enabled) + chart.series[i].update({"dataGrouping": {"enabled": false}}, false); + } } chart.redraw(); }, @@ -1914,4 +1967,3 @@ $.widget("sv.plot_timeshift", $.sv.widget, { } } }); - diff --git a/widgets/quad.html b/widgets/quad.html index 5d87ef3b1..c8cadbc55 100755 --- a/widgets/quad.html +++ b/widgets/quad.html @@ -515,7 +515,7 @@ {{ _context['linetext_widget'] }} {% endif %} {% elseif item_auto and stateengine is defined and (not column_order or 'stateengine' in column_order) %}  - {{ clock.countdown('', item_auto~'.suspend.visu', item_auto~'.suspend_start.unix_timestamp', item_auto~'.settings.suspendduration.duration_format', '1s', '', ' ') }} + {{ clock.countdown('', item_auto~'.suspend.visu', item_auto~'.suspend_start.unix_timestamp', item_auto~'.settings.suspendduration.duration_format', '1s', '', ' ', 1) }} /** {{ basic.print(id~'dimmerprint', item_auto~'.suspend_end', 'text') }} */ @@ -733,8 +733,12 @@ possible elements are: 'colorpicker', 'ww_popup', 'ww_slider', 'values', 'sequencer', 'locks', 'stateengine', 'uzsu', 'plot' For empty columns either use ' ' or a number to define the column width (e.g. '40' = 40 pixels width) Combine elements in one column by putting them in arrays. Standard is [['locks', 'sequencer', 'colorpicker'],'values', 'stateengine', 'plot', 'uzsu'] + * @param {color(icon0to5,blink,simulate,:)=} activity indicator which is active until response (or a timeout is reached); pass either a color, icon class, 'blink' or 'simulate'. Timeout is 3s by default and can be set globally using the configuration key "indicator_duration". Add a colon and integer number to specify an individual timeout, e.g. 'blink:15' (optional) + You can also set an indicator icon, text and color by passing these as additional parameters in the icon, text and color arrays. Just provide one icon more than you have values to switch. A dynamic icon can be used also along with the 'simulate' option. + The indicator options will not be applied to the select element of type 'menu' which will convert all options to 'blink'. + */ -{% macro color(id, item_value_r, item_value_g, item_value_b, min, max, step, colors, style, colormodel, linetext, item_switch_r, item_switch_g, item_switch_b, min_display, max_display, item_uzsu, uzsu_attribs, popupcolor, item_switch_ww, item_value_ww, item_plot, icon_plot, item_auto, extpopup, locks, item_seq, icon_color, linetext_widget, live, column_order) %} +{% macro color(id, item_value_r, item_value_g, item_value_b, min, max, step, colors, style, colormodel, linetext, item_switch_r, item_switch_g, item_switch_b, min_display, max_display, item_uzsu, uzsu_attribs, popupcolor, item_switch_ww, item_value_ww, item_plot, icon_plot, item_auto, extpopup, locks, item_seq, icon_color, linetext_widget, live, column_order, indicator) %} {% import "@widgets/basic.html" as basic %} {% import "@widgets/plot.html" as plot %} {% import "@widgets/icon.html" as icon %} @@ -902,7 +906,7 @@ {{ _context['linetext_widget'] }} {% endif %} {% elseif item_auto and stateengine is defined and (not column_order or 'stateengine' in column_order) %}  - {{ clock.countdown('', item_auto~'.suspend.visu', item_auto~'.suspend_start.unix_timestamp', item_auto~'.settings.suspendduration.duration_format', '1s', '', ' ') }} + {{ clock.countdown('', item_auto~'.suspend.visu', item_auto~'.suspend_start.unix_timestamp', item_auto~'.settings.suspendduration.duration_format', '1s', '', ' ', 1) }} /** {{ basic.print(id~'dimmerprint', item_auto~'.suspend_end', 'text') }} */ @@ -1184,7 +1188,9 @@ [slider_item, slider_min, slider_max, slider_step, '', 'handle']] * @param {widget[?]=} Widget(s) to be shown right after linetext. Can be used to show a countdown or other additional information. Example: basic.symbol('', 'licht.og.essen.sa') - don't put basic.symbol() in high commas! (optional) - * @param {placeholder=} placeholder attributes for future features, etc. + * @param {color(icon0to5,blink,simulate,:)=} activity indicator which is active until response (or a timeout is reached); pass either a color, icon class, 'blink' or 'simulate'. Timeout is 3s by default and can be set globally using the configuration key "indicator_duration". Add a colon and integer number to specify an individual timeout, e.g. 'blink:15' (optional) + You can also set an indicator icon, text and color by passing these as additional parameters in the icon, text and color arrays. Just provide one icon more than you have values to switch. A dynamic icon can be used also along with the 'simulate' option. + The indicator options will not be applied to the select element of type 'menu' which will convert all options to 'blink'. * @param {text[](move_up,move_down,stop,pos_slider,pos_shutter,pos_shutter_ext,pos_popup_shutter,pos_popup_blind,pos1,pos2,stateengine,uzsu,plot,extpopup,anynumber)=} array with element description: Reorder elements to your liking (esp. relevant for smartphones as several columns might be too much) possible elements are: 'move_down', 'move_up', 'stop', 'pos_slider', 'pos_shutter', 'pos_shutter_ext' (with the two saved positions), 'pos_popup_shutter', 'pos_popup_blind', 'pos1', 'pos2', 'stateengine', 'uzsu', 'plot', 'extpopup' For empty columns either use ' ' or a number to define the column width (e.g. '40' = 40 pixels width) @@ -1193,7 +1199,7 @@ * @param {unspecified[]=} a list with positional attributes. Should be left untouched as it is set automatically (optional) */ -{% macro shutter(id, linetext, item_move, item_stop, item_pos, item_shift, item_angle, item_saved, min, max, step, mode, background, value_pos_1, value_pos_2, move_on_longpress, live, item_uzsu, uzsu_attribs, item_plot, icon_plot, item_auto, extpopup, linetext_widget, place4, column_order, _blind_or_shutter, _pos_attribs) %} +{% macro shutter(id, linetext, item_move, item_stop, item_pos, item_shift, item_angle, item_saved, min, max, step, mode, background, value_pos_1, value_pos_2, move_on_longpress, live, item_uzsu, uzsu_attribs, item_plot, icon_plot, item_auto, extpopup, linetext_widget, indicator, column_order, _blind_or_shutter, _pos_attribs) %} {% import "@widgets/basic.html" as basic %} {% import "@widgets/device.html" as device %} {% import "@widgets/icon.html" as icon %} @@ -1384,7 +1390,7 @@ {{ _context['linetext_widget'] }} {% endif %} {% elseif item_auto and stateengine is defined and (not column_order or 'stateengine' in column_order) %}  - {{ clock.countdown('', item_auto~'.suspend.visu', item_auto~'.suspend_start.unix_timestamp', item_auto~'.settings.suspendduration.duration_format', '1s', '', ' ') }} + {{ clock.countdown('', item_auto~'.suspend.visu', item_auto~'.suspend_start.unix_timestamp', item_auto~'.settings.suspendduration.duration_format', '1s', '', ' ', 1) }} /** {{ basic.print(id~'dimmerprint', item_auto~'.suspend_end', 'text') }} */ @@ -1416,9 +1422,9 @@ {% if column == 'move_down' and item_move %} {% if move_on_longpress is same as true or move_on_longpress == 'true' or move_on_longpress is empty %} - {{ basic.stateswitch(id~'_down', item_stop, '', max < min ? 0 : 1, 'control_arrow_down', '', '', 'blink', item_move, max < min ? 0 : 1) }} + {{ basic.stateswitch(id~'_down', item_stop, '', max < min ? 0 : 1, 'control_arrow_down', '', '', indicator, item_move, max < min ? 0 : 1) }} {% else %} - {{ basic.stateswitch(id~'_down', item_move, '', max < min ? 0 : 1, 'control_arrow_down', '', '', 'blink') }} + {{ basic.stateswitch(id~'_down', item_move, '', max < min ? 0 : 1, 'control_arrow_down', '', '', indicator) }} {% endif %} {% if elements[loop.index] == 'div' %} @@ -1427,9 +1433,9 @@ {% if column == 'move_up' and item_move %} {% if move_on_longpress is same as true or move_on_longpress == 'true' or move_on_longpress is empty %} - {{ basic.stateswitch(id~'_up', item_stop, '', max < min ? 1 : 0, 'control_arrow_up', '', '', 'blink', item_move, max < min ? 1 : 0) }} + {{ basic.stateswitch(id~'_up', item_stop, '', max < min ? 1 : 0, 'control_arrow_up', '', '', indicator, item_move, max < min ? 1 : 0) }} {% else %} - {{ basic.stateswitch(id~'_up', item_move, '', max < min ? 1 : 0, 'control_arrow_up', '', '', 'blink') }} + {{ basic.stateswitch(id~'_up', item_move, '', max < min ? 1 : 0, 'control_arrow_up', '', '', indicator) }} {% endif %} {% if elements[loop.index] == 'div' %} @@ -1437,7 +1443,7 @@ {% endif %} {% if column == 'stop' and item_stop %} - {{ basic.stateswitch(id~'_stop', item_stop, 'icon', '', 'control_cancel', '', 'icon0', 'blink') }} + {{ basic.stateswitch(id~'_stop', item_stop, 'icon', '', 'control_cancel', '', 'icon0', indicator) }} {% if elements[loop.index] == 'div' %} {% endif %} @@ -1487,7 +1493,7 @@ {% if item_saved %}
      - {{ basic.stateswitch(id~'_saved1_ext', item_saved, '', value_pos_1, '', text_pos_1, 'icon0', 'blink') }} + {{ basic.stateswitch(id~'_saved1_ext', item_saved, '', value_pos_1, '', text_pos_1, 'icon0', indicator) }}  
      {% endif %} @@ -1496,7 +1502,7 @@ {% if item_saved %}
      - {{ basic.stateswitch(id~'_saved2_ext', item_saved, '', value_pos_2, '', text_pos_2, 'icon0', 'blink') }} + {{ basic.stateswitch(id~'_saved2_ext', item_saved, '', value_pos_2, '', text_pos_2, 'icon0', indicator) }}  
      {% endif %} @@ -1508,19 +1514,19 @@ {% endif %} {% if column == 'pos' and item_saved and value_pos_2 and value_pos_1 %} - {{ basic.select(id~'pos', item_saved, 'mini', [value_pos_1, value_pos_2, 0], '', [text_pos_1, text_pos_2, 0], 'icon1', 'horizontal') }} + {{ basic.select(id~'pos', item_saved, 'mini', [value_pos_1, value_pos_2, 0], '', [text_pos_1, text_pos_2, 0], 'icon1', 'horizontal', '', '', indicator) }} {% if elements[loop.index] == 'div' %} {% endif %} {% elseif column == 'pos1' and item_saved and value_pos_1 and 'pos' not in elements %} - {{ basic.stateswitch(id~'_saved1', item_saved, '', [value_pos_1, 0], '', [0, text_pos_1], 'icon0', 'blink') }} + {{ basic.stateswitch(id~'_saved1', item_saved, '', [value_pos_1, 0], '', [0, text_pos_1], 'icon0', indicator) }} {% if elements[loop.index] == 'div' %} {% endif %} {% elseif column == 'pos2' and item_saved and value_pos_2 and 'pos' not in elements %} - {{ basic.stateswitch(id~'_saved2', item_saved, '', [value_pos_2, 0], '', [0, text_pos_2], 'icon0', 'blink') }} + {{ basic.stateswitch(id~'_saved2', item_saved, '', [value_pos_2, 0], '', [0, text_pos_2], 'icon0', indicator) }} {% if elements[loop.index] == 'div' %} {% endif %} @@ -1639,16 +1645,18 @@ * @param {unspecified[]=} a list with positional attributes: item_saved = an item for some saved positions, value_pos_1 = the value to send for position 1, value_pos_2 = the value to send for position 2, text_pos_1 = the text printed on button for position 1, text_pos_2 = the text printed on button for position 2 (optional) * @param {widget[?]=} Widget(s) to be shown right after linetext. Can be used to show a countdown or other additional information. Example: basic.symbol('', 'licht.og.essen.sa') - don't put basic.symbol() in high commas! (optional) - * @param {placeholder=} placeholder attributes for future features, etc. + * @param {color(icon0to5,blink,simulate,:)=} activity indicator which is active until response (or a timeout is reached); pass either a color, icon class, 'blink' or 'simulate'. Timeout is 3s by default and can be set globally using the configuration key "indicator_duration". Add a colon and integer number to specify an individual timeout, e.g. 'blink:15' (optional) + You can also set an indicator icon, text and color by passing these as additional parameters in the icon, text and color arrays. Just provide one icon more than you have values to switch. A dynamic icon can be used also along with the 'simulate' option. + The indicator options will not be applied to the select element of type 'menu' which will convert all options to 'blink'. * @param {text[](move_up,move_down,stop,pos_slider,pos_shutter,pos_shutter_ext,pos_popup_shutter,pos_popup_blind,pos1,pos2,stateengine,uzsu,plot,extpopup,anynumber)=[[move_down, move_up], pos_popup_shutter, stateengine, plot, uzsu]} array with element description: Reorder elements to your liking (esp. relevant for smartphones as several columns might be too much) possible elements are: 'move_down', 'move_up', 'stop', 'pos_slider', 'pos_shutter', 'pos_shutter_ext' (with the two saved positions), 'pos_popup_shutter', 'pos_popup_blind', 'pos1', 'pos2', 'stateengine', 'uzsu', 'plot', 'extpopup' For empty columns either use ' ' or a number to define the column width (e.g. '40' = 40 pixels width) Combine elements in one column by putting them in arrays. Standard is [['move_down', 'move_up'],'pos_popup_shutter', 'stateengine', 'plot', 'uzsu'] */ -{% macro blind(id, linetext, item_move, item_stop, item_pos, item_shift, item_angle, min, max, step, move_on_longpress, live, item_uzsu, uzsu_attribs, item_plot, icon_plot, item_auto, extpopup, pos_attribs, linetext_widget, place4, column_order) %} +{% macro blind(id, linetext, item_move, item_stop, item_pos, item_shift, item_angle, min, max, step, move_on_longpress, live, item_uzsu, uzsu_attribs, item_plot, icon_plot, item_auto, extpopup, pos_attribs, linetext_widget, indicator, column_order) %} {% import _self as newwidget %} - {{ newwidget.shutter(id, linetext, item_move, item_stop, item_pos, item_shift, item_angle, '', min, max, step, '', '', '', '', move_on_longpress, live, item_uzsu, uzsu_attribs, item_plot, icon_plot, item_auto, extpopup, linetext_widget, place4, column_order, 'blind', pos_attribs) }} + {{ newwidget.shutter(id, linetext, item_move, item_stop, item_pos, item_shift, item_angle, '', min, max, step, '', '', '', '', move_on_longpress, live, item_uzsu, uzsu_attribs, item_plot, icon_plot, item_auto, extpopup, linetext_widget, indicator, column_order, 'blind', pos_attribs) }} {% endmacro %} /** @@ -1697,14 +1705,16 @@ [slider_item, slider_min, slider_max, slider_step, '', 'handle']] * @param {text=} the path/url or item to the image. For squeezebox create an item with the following value: 'http://IP:PORT/music/current/cover.jpg?player=MACADDRESS' * @param {value=1} 'live mode': if enabled, values will be sent during sliding. '0' sends values only when sliding is stopped, after click into track or if value is edited in input field. (optional, default = 1) - * @param {placeholder=} placeholder attributes for future features, etc. + * @param {color(icon0to5,blink,simulate,:)=} activity indicator which is active until response (or a timeout is reached); pass either a color, icon class, 'blink' or 'simulate'. Timeout is 3s by default and can be set globally using the configuration key "indicator_duration". Add a colon and integer number to specify an individual timeout, e.g. 'blink:15' (optional) + You can also set an indicator icon, text and color by passing these as additional parameters in the icon, text and color arrays. Just provide one icon more than you have values to switch. A dynamic icon can be used also along with the 'simulate' option. + The indicator options will not be applied to the select element of type 'menu' which will convert all options to 'blink'. * @param {text[](previous,play,pause,stop,next,power,eject,mute,volume_slider,cover,volume_popup,volume_up,volume_down,song_position,uzsu,plot,stateengine,repeat,source,source_popup,speaker,playpause,playpausestop,playstop,shuffle,anynumber)=} array with element description: Reorder elements to your liking (esp. relevant for smartphones as several columns might be too much) possible elements are: 'previous', 'play', 'pause', 'stop', 'next', 'power', 'eject', 'mute', 'volume_slider', 'cover', 'volume_popup', 'volume_up', 'volume_down', 'song_position', 'uzsu', 'plot', 'stateengine', 'repeat', 'source', 'speaker', 'playpause', 'playstop', 'playpausestop', 'shuffle', 'source_popup' For empty columns either use ' ' or a number to define the column width (e.g. '40' = 40 pixels width) Combine elements in one column by putting them in arrays. */ -{% macro playercontrol(id, linetext, item_previous, item_play, item_pause, item_stop, item_next, item_power, item_eject, item_repeat, item_shuffle, source, item_mute, item_volume, item_volumeup, item_volumedown, volume_min, volume_max, volume_step, volume_min_display, volume_max_display, volume_threshold, item_album, item_artist, item_title, item_time, item_duration, item_speaker, item_uzsu, uzsu_attribs, item_plot, icon_plot, item_auto, extpopup, coverUrl, live, place2, column_order) %} +{% macro playercontrol(id, linetext, item_previous, item_play, item_pause, item_stop, item_next, item_power, item_eject, item_repeat, item_shuffle, source, item_mute, item_volume, item_volumeup, item_volumedown, volume_min, volume_max, volume_step, volume_min_display, volume_max_display, volume_threshold, item_album, item_artist, item_title, item_time, item_duration, item_speaker, item_uzsu, uzsu_attribs, item_plot, icon_plot, item_auto, extpopup, coverUrl, live, indicator, column_order) %} {% import "@widgets/basic.html" as basic %} {% import "@widgets/plot.html" as plot %} {% import "@widgets/device.html" as device %} @@ -1971,14 +1981,14 @@
      {% endif %} {% if column == 'power' and item_power %} - {{ basic.stateswitch(id~'_power', item_power, 'icon', [0,1], '', '', ['icon0','icon1'], 'blink') }} + {{ basic.stateswitch(id~'_power', item_power, 'icon', [0,1], '', '', ['icon0','icon1'], indicator) }} {% if elements[loop.index] == 'div' %}
      {% endif %} {% endif %} {% if column == 'eject' and item_eject %} - {{ basic.stateswitch(id~'_eject', item_eject, 'icon', '1', 'audio_eject', '', 'icon0', '') }} + {{ basic.stateswitch(id~'_eject', item_eject, 'icon', '1', 'audio_eject', '', 'icon0', indicator) }} {% if elements[loop.index] == 'div' %} {% endif %} @@ -2003,34 +2013,34 @@ {% endif %} {% if column == 'previous' and item_previous %} - {{ basic.stateswitch(id~'_prev', item_previous, 'icon', '1', 'audio_previous', '', 'icon0', '') }} + {{ basic.stateswitch(id~'_prev', item_previous, 'icon', '1', 'audio_previous', '', 'icon0', indicator) }} {% if elements[loop.index] == 'div' %} {% endif %} {% endif %} {% if column == 'next' and item_next %} - {{ basic.stateswitch(id~'_prev', item_next, 'icon', '1', 'audio_next', '', 'icon0', '') }} + {{ basic.stateswitch(id~'_prev', item_next, 'icon', '1', 'audio_next', '', 'icon0', indicator) }} {% if elements[loop.index] == 'div' %} {% endif %} {% endif %} {% if column == 'play' and item_play %} - {{ basic.stateswitch(id~'_play', item_play, 'icon', '1', 'audio_play', '', 'icon1', '') }} + {{ basic.stateswitch(id~'_play', item_play, 'icon', '1', 'audio_play', '', 'icon1', indicator) }} {% if elements[loop.index] == 'div' %} {% endif %} {% elseif column == 'pause' and item_pause %} - {{ basic.stateswitch(id~'_pause', item_pause, 'icon', '1', 'audio_pause', '', 'icon1', 'blink') }} + {{ basic.stateswitch(id~'_pause', item_pause, 'icon', '1', 'audio_pause', '', 'icon1', indicator) }} {% if elements[loop.index] == 'div' %} {% endif %} {% elseif column == 'playstop' and item_play %} - {{ basic.stateswitch(id~'_playstop', item_play, 'icon', ['0','1'], ['audio_play', 'audio_stop'], '', ['icon0', 'icon1'], 'blink') }} + {{ basic.stateswitch(id~'_playstop', item_play, 'icon', ['0','1'], ['audio_play', 'audio_stop'], '', ['icon0', 'icon1'], indicator) }} {% if elements[loop.index] == 'div' %} {% endif %} {% elseif column == 'playpause' and item_pause %} - {{ basic.stateswitch(id~'_playpause', item_play, 'icon', ['0','1'], ['audio_play', 'audio_pause'], '', ['icon0', 'icon1'], 'blink') }} + {{ basic.stateswitch(id~'_playpause', item_play, 'icon', ['0','1'], ['audio_play', 'audio_pause'], '', ['icon0', 'icon1'], indicator) }} {% if elements[loop.index] == 'div' %} {% endif %} @@ -2041,25 +2051,25 @@ {% endif %} {% endif %} {% if column == 'stop' and item_stop %} - {{ basic.stateswitch(id~'_stop', item_stop, 'icon', '1', 'audio_stop', '', 'icon0', '') }} + {{ basic.stateswitch(id~'_stop', item_stop, 'icon', '1', 'audio_stop', '', 'icon0', indicator) }} {% if elements[loop.index] == 'div' %} {% endif %} {% endif %} {% if column == 'mute' and item_mute %} - {{ basic.stateswitch(id~'_mute', item_mute, 'icon', [0,1], 'audio_volume_mute', '', '', '') }} + {{ basic.stateswitch(id~'_mute', item_mute, 'icon', [0,1], 'audio_volume_mute', '', '', indicator) }} {% if elements[loop.index] == 'div' %} {% endif %} {% endif %} {% if column == 'volume_up' and item_volumeup %} - {{ basic.stateswitch(id~'_volup', item_volumeup, 'icon', '1', 'audio_volume_high', '', 'icon0', '') }} + {{ basic.stateswitch(id~'_volup', item_volumeup, 'icon', '1', 'audio_volume_high', '', 'icon0', indicator) }} {% if elements[loop.index] == 'div' %} {% endif %} {% endif %} {% if column == 'volume_down' and item_volumedown %} - {{ basic.stateswitch(id~'_voldown', item_volumedown, 'icon', '1', 'audio_volume_low', '', 'icon0', '') }} + {{ basic.stateswitch(id~'_voldown', item_volumedown, 'icon', '1', 'audio_volume_low', '', 'icon0', indicator) }} {% if elements[loop.index] == 'div' %} {% endif %} @@ -2087,15 +2097,15 @@ {% set text_source = text_source|merge([item[2]|default('')]) %} {% endif %} {% endfor %} - {{ basic.select(id~'_select', item_source, type_source, value_source, pic_source, text_source, 'icon1') }} + {{ basic.select(id~'_select', item_source, type_source, value_source, pic_source, text_source, 'icon1', 'horizontal', '', '', indicator) }} {% if elements[loop.index] == 'div' %} {% endif %} {% endif %} {% if column == 'speaker' and item_speaker %} - {{ basic.stateswitch(id~'_speaker1', item_speaker[0], 'mini', [1,0], '', 'A', ['icon1', 'icon0'], '') }} - {{ basic.stateswitch(id~'_speaker2', item_speaker[1], 'mini', [1,0], '', 'B', ['icon1', 'icon0'], '') }} + {{ basic.stateswitch(id~'_speaker1', item_speaker[0], 'mini', [1,0], '', 'A', ['icon1', 'icon0'], indicator) }} + {{ basic.stateswitch(id~'_speaker2', item_speaker[1], 'mini', [1,0], '', 'B', ['icon1', 'icon0'], indicator) }} {% if elements[loop.index] == 'div' %} {% endif %} @@ -2103,13 +2113,13 @@ {% if column == 'repeat' and item_repeat %} - {{ basic.stateswitch(id~'_repeat', item_repeat, 'icon', [0,1,2], ['audio_repeat_song','audio_repeat_song','audio_repeat'], '', ['icon0','icon1','icon1'], '') }} + {{ basic.stateswitch(id~'_repeat', item_repeat, 'icon', [0,1,2], ['audio_repeat_song','audio_repeat_song','audio_repeat'], '', ['icon0','icon1','icon1'], indicator) }} {% if elements[loop.index] == 'div' %} {% endif %} {% endif %} {% if column == 'shuffle' and item_shuffle %} - {{ basic.stateswitch(id~'_shuffle', item_shuffle, 'icon', [0,1,2], ['audio_shuffle','audio_shuffle','audio_shuffle_album'], '', ['icon0','icon1','icon1'], '') }} + {{ basic.stateswitch(id~'_shuffle', item_shuffle, 'icon', [0,1,2], ['audio_shuffle','audio_shuffle','audio_shuffle_album'], '', ['icon0','icon1','icon1'], indicator) }} {% if elements[loop.index] == 'div' %} {% endif %} @@ -2236,17 +2246,17 @@ * @param {text=} the value to send on releasing after a longpress (optional) * @param {placeholder=} placeholder attributes for future features, etc. * @param {placeholder=} placeholder attributes for future features, etc. - If you want to implement two state switches for one single item (e.g. on/off and timer or restart, etc.) - you can provide the following attributes as single statements without arrays. + If you want to implement two state switches for one single item (e.g. on/off and timer or restart, etc.) + you can provide the following attributes as single statements without arrays. * @param {text=} text for the whole line (optional) * @param {text[?]=} text for each column (optional) * @param {item[?](dict)=} a gad/item for UZSU * @param {uzsuparam[]=} Array with standard UZSU parameters: pic_on, pic_off, valueType, valueParameterList, color_on, color_off. (optional) Normally the array must have the same dimension as the "type" parameter, e.g. "[['', '', 'num'], '', ['', '', 'bool']]" for a table of 3 columns. - Exception: if the parameter "item_uzsu" is a singe item in string format a new column with an uzsu icon is displayed behind the main column block. + Exception: if the parameter "item_uzsu" is a singe item in string format a new column with an uzsu icon is displayed behind the main column block. * @param {plotparam[?]=} array with all plot.period attributes to show a plot in popup. Alternatively just the item to be plotted. Normally the array must have the same dimension as the "type" parameter, e.g. "[['plotitem1', 'avg', '2d','now'], 'plotitem2', '']" for a table of 3 columns. - Exception: a plot parameter array with exactly 17 entries causes a new column with a plot icon to be displayed behind the main column block. Example: "['plotitem1', 'avg', '2d','now', '','','','','','','','','','','','','temp'] + Exception: a plot parameter array with exactly 17 entries causes a new column with a plot icon to be displayed behind the main column block. Example: "['plotitem1', 'avg', '2d','now', '','','','','','','','','','','','','temp'] * @param {image=measure_power_meter} icon triggering the plot popup * @param {item[?](foo)=} "root item" which holds stateengine information. show current state of stateengine/stateengine item. Adjust icons and states below accordingly * @param {unspecified[]=} Array of arrays for extended popup window. Use this to create an icon to open a popup with switches, sliders, flips or select menues. @@ -2258,7 +2268,7 @@ [slider_item, slider_min, slider_max, slider_step, '', 'handle']] * @param {item[](bool,num)=} array with items for locking. You have to be aware of the order: item_lock, item_bwmlock (presence sensor), item_force (force on/off/neutral) * @param {sliderparam[?]=} additional item for changing value (e.g. timer). A slider popup will be shown - You can provide additional attributes as an array: item, min, max, step, format, value_display (handle, etc.) + You can provide additional attributes as an array: item, min, max, step, format, value_display (handle, etc.) * @param {widget[?]=} Widget(s) to be shown right after linetext. Can be used to show a countdown or other additional information. Example: basic.symbol('', 'licht.og.essen.sa') - don't put basic.symbol() in high commas! (optional) * @param {placeholder=} placeholder attributes for future features, etc. * @param {text[](switch,stateengine,uzsu,plot,locks,slider,extpopup,anynumber)=['locks', 'switch', 'slider', 'stateengine', 'plot', 'uzsu']} array with element description: Reorder elements to your liking (esp. relevant for smartphones as several columns might be too much) @@ -2267,8 +2277,12 @@ Combine elements in one column by putting them in arrays. Standard is ['locks', 'switch', 'slider', 'stateengine', 'plot', 'uzsu'] Be aware that this only works with lines containing one switch only! For multiple switches leave column_order empty and rely on the defaults. * @param {text(switch,select)=} Should be left untouched as it is set automatically (optional) + * @param {item(list)=} Should be left untouched. an item containing all selectable values (optional) - overrides existing value definitions, available for select menu type only + * @param {item(list)=} Should be left untouched. an item containing the texts corresponding to the selectable values (optional even if itemvals is specified), available for select menu type only + */ -{% macro stateswitch(id, item_switch, type, value, pic, text, color, indicator, item_longpress, value_longpress, value_longrelease, place1, place2, linetext, columntext, item_uzsu, uzsu_attribs, item_plot, icon_plot, item_auto, extpopup, locks, item_slider, linetext_widget, place4, column_order, _switch_or_select) %} + +{% macro stateswitch(id, item_switch, type, value, pic, text, color, indicator, item_longpress, value_longpress, value_longrelease, place1, place2, linetext, columntext, item_uzsu, uzsu_attribs, item_plot, icon_plot, item_auto, extpopup, locks, item_slider, linetext_widget, place4, column_order, _switch_or_select, itemvals, itemtxts) %} {% import "@widgets/basic.html" as basic %} {% import "@widgets/device.html" as device %} {% import "@widgets/icon.html" as icon %} @@ -2495,7 +2509,7 @@ {% set sc_1 = item_auto~'.suspend_start.unix_timestamp' %} {% set sc_2 = item_auto~'.settings.suspendduration.duration_format' %} {% endif %} - {{ clock.countdown('', sc_0, sc_1, sc_2, '1s', '', ' ') }} + {{ clock.countdown('', sc_0, sc_1, sc_2, '1s', '', ' ', 1) }} /** {{ basic.print(id~'sw_widget', se, 'text') }} */ @@ -2537,7 +2551,7 @@ {% if column == 'switch' and item_switch[i] %} {% if columntext[i] %}{{ columntext[i]|e }}{% endif %} {% if _switch_or_select[i] == 'select' or _switch_or_select == 'select' %} - {{ basic.select(id~'_select'~z~i, item_switch[i], type[i], value[i]|default(value[i][0]), pic[i]|default(pic[i][0]), text[i]|default(text[i][0]), color_on[i]|default(''), indicator[i]) }} + {{ basic.select(id~'_select'~z~i, item_switch[i], type[i], value[i]|default(value[i][0]), pic[i]|default(pic[i][0]), text[i]|default(text[i][0]), color_on[i]|default(''), '', itemvals[i]|default(''), itemtxts[i]|default(''), indicator[i]) }} {% else %} {{ basic.stateswitch(id~'_stateswitch'~z~i, item_switch[i], type[i], value[i]|default(''), pic[i]|default(''), text[i]|default(''), color[i]|default(''), indicator[i]|default(''), item_longpress[i]|default(''), value_longpress[i]|default(''), value_longrelease[i]|default('')) }} {% endif %} @@ -2672,7 +2686,7 @@ {% if column == 'switch' and item_switch %} {% if columntext %}{{ columntext|e }}{% endif %} {% if _switch_or_select == 'select' %} - {{ basic.select(id~'_select'~z~i, item_switch, type, value|default(value[0]), pic|default(pic[0]), text|default(text[0]), color_on|default(''), indicator) }} + {{ basic.select(id~'_select'~z~i, item_switch, type, value|default(value[0]), pic|default(pic[0]), text|default(text[0]), color_on|default(''), '', itemvals|default(''), itemtxts|default(''), indicator) }} {% else %} {{ basic.stateswitch(id~'_stateswitch'~z~i, item_switch, type|default(''), value|default(''), pic|default(''), text|default(''), color|default(''), indicator|default(''), item_longpress|default(''), value_longpress|default(''), value_longrelease|default('')) }} {% endif %} @@ -2885,9 +2899,11 @@ * @param {image[?]=} list of icons for every button (optional) - not supported for type 'menu' * @param {text[?]=} list of texts for every menu entry or button (optional) * @param {color[?](icon0to5)=icon1} the color for the on state of the buttons, e.g. 'icon0'through'icon5' or '#f00' or 'red' (optional, default: icon1) - not supported for type 'menu' - * @param {text[?](horizontal,vertical,none)=horizontal} orientation of the controlgroup: 'horizontal', 'vertical' or 'none' for seperate buttons (optional, default: 'horizontal') - not supported for type 'menu' - * @param {placeholder=} placeholder attributes for future features, etc. - * @param {placeholder=} placeholder attributes for future features, etc. + * @param {item(list)=} an item containing all selectable values (optional) - overrides existing value definitions, available for menu type only + * @param {item(list)=} an item containing the texts corresponding to the selectable values (optional even if itemvals is specified), available for menu type only + * @param {color(icon0to5,blink,simulate,:)=} activity indicator which is active until response (or a timeout is reached); pass either a color, icon class, 'blink' or 'simulate'. Timeout is 3s by default and can be set globally using the configuration key "indicator_duration". Add a colon and integer number to specify an individual timeout, e.g. 'blink:15' (optional) + You can also set an indicator icon, text and color by passing these as additional parameters in the icon, text and color arrays. Just provide one icon more than you have values to switch. A dynamic icon can be used also along with the 'simulate' option. + The indicator options will not be applied to the select element of type 'menu' which will convert all options to 'blink'. * @param {text=} text for the whole line (optional) * @param {text[?]=} (array with) text for each column (optional) * @param {item[?](dict)=} a gad/item for UZSU @@ -2917,9 +2933,9 @@ Combine elements in one column by putting them in arrays. Standard is ['locks', 'select', 'stateengine', 'plot', 'uzsu'] Be aware that this only works with lines containing one switch only! For multiple switches leave column_order empty and rely on the defaults. */ -{% macro select(id, item_select, type, value, pic, text, color_on, group, place1, place2, linetext, columntext, item_uzsu, uzsu_attribs, item_plot, icon_plot, item_auto, extpopup, locks, item_slider, place3, place4, column_order) %} +{% macro select(id, item_select, type, value, pic, text, color_on, itemvals, itemtxts, indicator, linetext, columntext, item_uzsu, uzsu_attribs, item_plot, icon_plot, item_auto, extpopup, locks, item_slider, place3, place4, column_order) %} {% import _self as intern %} - {{ intern.stateswitch(id, item_select, type, value, pic, text, color_on, place0, place1, place2, place3, place4, place5, linetext, columntext, item_uzsu, uzsu_attribs, item_plot, icon_plot, item_auto, extpopup, locks, item_slider, place6, place7, column_order, 'select') }} + {{ intern.stateswitch(id, item_select, type, value, pic, text, color_on, indicator, place1, place2, place3, place4, place5, linetext, columntext, item_uzsu, uzsu_attribs, item_plot, icon_plot, item_auto, extpopup, locks, item_slider, place6, place7, column_order, 'select', itemvals, itemtxts) }} {% endmacro %} /** diff --git a/widgets/weather.html b/widgets/weather.html index ebdcdcb3a..af4df5d75 100644 --- a/widgets/weather.html +++ b/widgets/weather.html @@ -33,11 +33,6 @@ {% macro current(id, location, repeat, itemtemp, itemwind, windfmt, itemhumi, humitxt, humifmt, itemmisc, misctxt, miscfmt, itemmisc1, misc1txt, misc1fmt) %} {% set uid = uid(page, id) %} - - {% if once('digiweather') %} - - {% endif %} -
      - {% endif %} -
      @@ -98,10 +89,6 @@ {% macro forecastweek(id, location, repeat) %} {% set uid = uid(page, id) %} - {% if once('digiweather') %} - - {% endif %} -