From 29ea99d96aa835a2a5aeee5ea4fe5cbca68e5e39 Mon Sep 17 00:00:00 2001
From: rudolfkoenig <>
Date: Sun, 11 Feb 2018 17:02:29 +0000
Subject: [PATCH] f18.js: weekly feature set (Forum #82351)

git-svn-id: https://svn.fhem.de/fhem/trunk@16153 2b470e98-0d58-463d-a4d8-8e2adae1ed80
---
 fhem/FHEM/01_FHEMWEB.pm    |  18 ++
 fhem/www/pgm2/f18.js       | 539 +++++++++++++++++++++++--------------
 fhem/www/pgm2/f18style.css |   1 +
 fhem/www/pgm2/fhemweb.js   |  16 +-
 4 files changed, 364 insertions(+), 210 deletions(-)

diff --git a/fhem/FHEM/01_FHEMWEB.pm b/fhem/FHEM/01_FHEMWEB.pm
index d1a12bad9..0dfd1fd2f 100644
--- a/fhem/FHEM/01_FHEMWEB.pm
+++ b/fhem/FHEM/01_FHEMWEB.pm
@@ -964,6 +964,24 @@ FW_answerCall($)
   FW_pO sprintf($cssTemplate, "pgm2/jquery-ui.min.css");
   map { FW_pO sprintf($cssTemplate, $_); }
                         split(" ", AttrVal($FW_wname, "CssFiles", ""));
+
+  my $sd = AttrVal($FW_wname, "styleData", ""); # Avoid flicker in f18
+  if($sd && $sd =~ m/"$FW_sp":/s) {
+    my $bg;
+    $bg = $1 if($FW_room && $sd =~ m/"Room\.$FW_room\.cols.bg": "([^"]*)"/s);
+    $bg = $1 if(!defined($bg) && $sd =~ m/"cols.bg": "([^"]*)"/s);
+
+    my $bgImg;
+    $bgImg = $1 if($FW_room && $sd =~ m/"Room\.$FW_room\.bgImg": "([^"]*)"/s);
+    $bgImg = $1 if(!defined($bgImg) && $sd =~ m/"bgImg": "([^"]*)"/s);
+
+    FW_pO "<style id='style_css'>";
+    FW_pO "body { background-color:#$bg; }" if($bg);
+    FW_pO "body { background-image:url($FW_ME/images/background/$bgImg); }"
+        if($bgImg);
+    FW_pO "</style>";
+  }
+
   my $css = AttrVal($FW_wname, "Css", "");
   FW_pO "<style id='fhemweb_css'>$css</style>\n" if($css);
 
diff --git a/fhem/www/pgm2/f18.js b/fhem/www/pgm2/f18.js
index 3d98f84bb..06a738d6a 100644
--- a/fhem/www/pgm2/f18.js
+++ b/fhem/www/pgm2/f18.js
@@ -1,19 +1,21 @@
 "use strict";
 FW_version["f18.js"] = "$Id$";
 
-// TODO: rewrite menu, floorplan
-var f18_attr, f18_aCol, f18_sd, f18_isMobile, f18_icon={}, f18_hasPos;
+// TODO: hierMenu,absPos,floorplan,f18style.css
+var f18_attr, f18_aCol, f18_sd, f18_isMobile, f18_icon={}, f18_hasPos, f18_room;
 var f18_small = (screen.width < 480 || screen.height < 480);
 
 $(window).resize(f18_resize);
 $(document).ready(function(){
 
+  f18_room  = $("div#content").attr("room");
   f18_sd = $("body").attr("data-styleData");
   if(f18_sd) {
     eval("f18_sd="+f18_sd);
     if(!f18_sd)
       f18_sd = {};
     f18_attr = f18_sd.f18;
+    delete(f18_attr.cols); // fix the past
 
   } else {
     f18_sd = {};
@@ -74,8 +76,9 @@ f18_menu()
   function
   fixMenu()
   {
-    $("#menuScrollArea #logo").css("display", f18_attr.hideLogo?"none":"block");
-    if(f18_attr["Pinned.menu"]) {
+    $("#menuScrollArea #logo").css("display", 
+        f18_getAttr("hideLogo") ? "none" : "block");
+    if(f18_getAttr("Pinned.menu")) {
       $("body").addClass("pinnedMenu");
       $("#menu").removeClass("visible");
       $("#content").css("left", (parseInt($("div#menu").width())+20)+"px");
@@ -98,24 +101,22 @@ f18_tables()
 
   $("#content .devType").each(function(){
     var el = this, grp = $(el).text();
-    f18_addPin(el, "room."+FW_urlParams.room+".grp."+grp, true,
+    f18_addPin(el, "Room."+FW_urlParams.room+".grp."+grp, true,
     function(isFixed){
       var ntr = $(el).closest("tr").next("tr");
       isFixed ? $(ntr).show() : $(ntr).hide();
     });
-    if(f18_attr.showDragger)
-      f18_addDragger(el);
     f18_setPos(el);
+    if(f18_getAttr("showDragger"))
+      f18_addDragger(el);
   });
 
   $("div.SVGlabel").each(function(){
-    if(f18_attr.showDragger)
-      f18_addDragger(this);
     f18_setPos(this);
+    if(f18_getAttr("showDragger"))
+      f18_addDragger(this);
   });
 
-  if(f18_hasPos || f18_attr.showDragger)
-    $("div.pinHeader:not(.menu) div.pin").hide();
 
   if(FW_urlParams.detail) {
     $("div.makeTable > span").each(function(){
@@ -130,163 +131,269 @@ f18_tables()
     });
   }
 
-  if(FW_urlParams.cmd == "style%20select") {
-    var row=0;
-
-    var addRow = function(name, desc, val)
-    {
-      $("table.f18colors")
-        .append("<tr class='ar_"+name+" "+(++row%2 ? "even":"odd")+"'>"+
-                  "<td "+(val ? "" : "colspan='2'")+">"+
-                        "<div class='col1'>"+desc+"</div></td>"+
-                  (val ? "<td><div class='col2'>"+val+"</div></div></td>" : '')+
-                "</tr>");
-    };
-
-    var addHider = function(name, desc, fn)
-    {
-      addRow(name, desc, "<input type='checkbox'>");
-      $("table.f18colors tr.ar_"+name+" input")
-        .prop("checked", f18_attr[name])
-        .click(function(){
-          var c = $(this).is(":checked");
-          f18_setAttr(name, c);
-          fn(c);
-        });
-    };
-
-    var addColorChooser = function(name, desc)
-    {
-      addRow(name, desc, "<div class='cp'></div>");
-      FW_replaceWidget("table.f18colors tr.ar_"+name+" div.col2 div.cp", name,
-        ["colorpicker","RGB"], f18_attr.cols[name], name, "rgb", undefined,
-        function(value) {
-          f18_attr.cols[name] = value;
-          f18_setAttr();
-          f18_setCss(name);
-        });
-    };
-
-
-    $("div#content > table").append("<tr class='f18'></tr>");
-
-    $("tr.f18").append("<div class='fileList f18colors'>f18 special</div>");
-    $("div.f18colors").css("margin-top", "20px");
-    $("tr.f18").append("<table class='block wide f18colors'></table>");
-
-    var addColors = function()
-    {
-      $("table.f18colors")
-        .append("<tr class='reset' "+(++row%2 ? "even":"odd")+"'>"+
-                  "<td colspan='2'><div class='col1'>Preset colors: "+
-                     "<a href='#'>default</a> "+
-                     "<a href='#'>light</a> "+
-                     "<a href='#'>dark</a> "+
-                  "</div></td>"+
-                "</tr>");
-      $("table.f18colors tr.reset a").click(function(){
-        row = 0;
-        $("table.f18colors").html("");
-        f18_resetCol($(this).text());
-        f18_setCss('preset');
-        f18_setAttr();
-        addColors();
-      });
-      addColorChooser("bg",      "Background");
-      addColorChooser("fg",      "Foreground");
-      addColorChooser("link",    "Link");
-      addColorChooser("evenrow", "Even row");
-      addColorChooser("oddrow",  "Odd row");
-      addColorChooser("header",  "Header row");
-      addColorChooser("menu",    "Menu");
-      addColorChooser("sel",     "Menu:Selected");
-      addColorChooser("inpBack", "Input bg");
-      $("table.f18colors input").attr("size", 8);
-
-      addRow("editStyle", "<a href='#'>Additional CSS</a>");
-      $("table.f18colors tr.ar_editStyle a").click(function(){
-        $('body').append(
-          '<div id="editdlg" style="display:none">'+
-            '<textarea id="f18_cssEd" rows="25" cols="60" style="width:99%"/>'+
-          '</div>');
-
-        $("#f18_cssEd").val($("head #fhemweb_css").html());
-        $('#editdlg').dialog(
-          { modal:true, closeOnEscape:true, width:$(window).width()*3/4,
-            height:$(window).height()*3/4, title:$(this).text(),
-            close:function(){ $('#editdlg').remove(); },
-            buttons:[
-            { text: "Cancel",click:function(){$(this).dialog('close')}},
-            { text: "OK", click:function(){
-
-              if(!$("head #fhemweb_css"))
-                $("head").append("<style id='fhemweb_css'>\n</style>");
-              var txt = $("#f18_cssEd").val();
-              $("head #fhemweb_css").html(txt);
-              var wn = $("body").attr("data-webName");
-              FW_cmd(FW_root+"?cmd=attr "+wn+" Css "+
-                     encodeURIComponent(txt.replace(/;/g,";;"))+"&XHR=1");
-              $(this).dialog('close');
-            }}]
-          });
-      });
-
-      addRow("empty", "&nbsp;");
-      addHider("hidePin", "Hide pin", function(c){
-        $("div.pinHeader div.pin").css("display", c ? "none":"block");
-      });
-      addHider("hideLogo", "Hide logo", f18_menu);
-      addHider("rightMenu", "MenuBtn right on SmallScreen", function(c){
-        $("body").toggleClass("rightMenu");
-      });
-      addHider("savePinChanges", "Save pin changes", function(){});
-      addHider("showDragger", "Dragging active", function(c){
-        if(c) {
-          $("div.fileList").each(function(){ f18_addDragger(this) });
-          $("div.pinHeader:not(.menu) div.pin").hide();
-        } else {
-          $("div.pinHeader div.dragger").remove();
-        }
-      });
-
-    };
-    loadScript("pgm2/fhemweb_colorpicker.js", addColors);
-  }
+  if(FW_urlParams.cmd == "style%20select") 
+    f18_special();
 
   if(FW_urlParams.cmd == "style%20list" ||
-     FW_urlParams.cmd == "style%20select") {
-    $("div.fileList").each(function(){
-      var el = this, grp = $(el).text();
-      f18_addPin(el, "style.list."+grp, true,
-      function(isFixed){
-        var ntr = $(el).next("table");
-        isFixed ? $(ntr).show() : $(ntr).hide();
-      });
-      if(f18_attr.showDragger)
-        f18_addDragger(el);
-      f18_setPos(el);
-    });
-    if(f18_hasPos || f18_attr.showDragger)
-      $("div.pinHeader:not(.menu) div.pin").hide();
-  }
+     FW_urlParams.cmd == "style%20select") 
+    $("div.fileList").each(function(){ f18_addPinToStyleDiv(this) });
+
+  if(f18_hasPos || f18_getAttr("showDragger"))
+    $("div.pinHeader:not(.menu) div.pin").hide();
 }
 
+function
+f18_special()
+{
+  var row, room='all', appendTo;
+
+  var attr = function(attrName, inRoom)
+  { 
+    if(inRoom && room != "all") {
+      var val = f18_attr["Room."+room+"."+attrName];
+      if(val != undefined)
+        return val;
+    }
+    return f18_attr[attrName];
+  };
+
+  var setAttr = function(attrName, attrVal, inRoom)
+  { 
+    if(inRoom && room != "all")
+      attrName = "Room."+room+"."+attrName;
+    f18_setAttr(attrName, attrVal);
+  };
+
+  var addRow = function(name, desc, val)
+  {
+    $(appendTo)
+      .append("<tr class='ar_"+name+" "+(++row%2 ? "even":"odd")+"'>"+
+                "<td "+(val ? "" : "colspan='2'")+">"+
+                      "<div class='col1'>"+desc+"</div></td>"+
+                (val ? "<td><div class='col2'>"+val+"</div></div></td>" : '')+
+              "</tr>");
+  };
+
+  var addHider = function(name, inRoom, desc, fn)
+  {
+    addRow(name, desc, "<input type='checkbox'>");
+    $(appendTo+" tr.ar_"+name+" input")
+      .prop("checked", attr(name, inRoom))
+      .click(function(){
+        var c = $(this).is(":checked");
+        setAttr(name, c, inRoom);
+        fn(c);
+      });
+  };
+
+  var addColorChooser = function(name, desc)
+  {
+    addRow(name, desc, "<div class='cp'></div>");
+    FW_replaceWidget(appendTo+" tr.ar_"+name+" div.col2 div.cp", name,
+      ["colorpicker","RGB"], attr("cols."+name, true), name, "rgb", undefined,
+      function(value) {
+        setAttr("cols."+name, value, true);
+        f18_setCss(name);
+      });
+  };
+
+// call drawspecial after got the roomlist...
+  var f18_drawSpecial = function()
+  {
+    var roomHash={};
+
+    var cleanRoom = function(){
+      for(var k in f18_attr) {
+        var m = k.match(/^room\.([^.]*)\..*/);
+        if(m && !roomHash[m[1]])
+          delete f18_attr[k];
+      }
+    };
+
+    row = 0;
+    $("div#content tr.f18").remove();
+
+    $("div#content > table").append("<tr id='f18rs' class='f18'></tr>");
+    $("tr#f18rs").append("<div class='fileList f18colors'>f18 special</div>");
+    $("tr#f18rs").append("<table id='f18ts' class='block wide'></table>");
+    appendTo = "table#f18ts";
+
+    addHider("rightMenu", false, "MenuBtn right<br>on SmallScreen", function(c){
+      $("body").toggleClass("rightMenu");
+    });
+    addHider("savePinChanges", false, "Save pin changes", function(){});
+    addHider("showDragger", false, "Dragging active", function(c){
+      if(c) {
+        $("div.fileList").each(function(){ f18_addDragger(this) });
+        $("div.pinHeader:not(.menu) div.pin").hide();
+      } else {
+        $("div.pinHeader div.dragger").remove();
+      }
+    });
+
+    addRow("editStyle", "<a href='#'>Additional CSS</a>");
+    $(appendTo+" tr.ar_editStyle a").click(function(){
+      $('body').append(
+        '<div id="editdlg" style="display:none">'+
+          '<textarea id="f18_cssEd" rows="25" cols="60" style="width:99%"/>'+
+        '</div>');
+
+      $("#f18_cssEd").val($("head #fhemweb_css").html());
+      $('#editdlg').dialog(
+        { modal:true, closeOnEscape:true, width:$(window).width()*3/4,
+          height:$(window).height()*3/4, title:$(this).text(),
+          close:function(){ $('#editdlg').remove(); },
+          buttons:[
+          { text: "Cancel",click:function(){$(this).dialog('close')}},
+          { text: "OK", click:function(){
+
+            if(!$("head #fhemweb_css"))
+              $("head").append("<style id='fhemweb_css'>\n</style>");
+            var txt = $("#f18_cssEd").val();
+            $("head #fhemweb_css").html(txt);
+            var wn = $("body").attr("data-webName");
+            FW_cmd(FW_root+"?cmd=attr "+wn+" Css "+
+                   encodeURIComponent(txt.replace(/;/g,";;"))+"&XHR=1");
+            $(this).dialog('close');
+          }}]
+        });
+    });
+
+
+    $("div#content > table").append("<tr id='f18rr' class='f18'></tr>");
+    $("tr#f18rr").append("<div class='fileList f18colors'>"+
+                         "f18: Room specific</div>");
+    $("tr#f18rr").append("<table id='f18tr' class='block wide'></table>");
+    appendTo = "table#f18tr";
+
+    addRow("room", "Target", '<select><option>all</option></select>');
+    FW_cmd(FW_root+"?cmd=JsonList2 .* room&XHR=1", function(data) {
+      var d = eval("JSON.parse(data);");
+      for(var i1=0; i1<d.Results.length; i1++) {
+        var rname = d.Results[i1].Attributes.room;
+        if(!rname || rname == "hidden")
+          continue;
+        var rl = rname.split(",")
+        for(var i2=0; i2<rl.length; i2++)
+          roomHash[rl[i2]] = true;
+      }
+      cleanRoom();
+      var rArr = Object.keys(roomHash); rArr.sort();
+      $(appendTo+" tr.ar_room select")
+        .html("<option>all</option><option>"+
+                rArr.join("</option><option>")+
+              "</option>")
+        .change(function(e){ 
+          room = $(e.target).val(); 
+          f18_drawSpecial();
+        });
+      $("tr.ar_room select").val(room);
+    });
+    addRow("reset", "Preset colors: "+
+                   "<a href='#'>default</a> "+
+                   "<a href='#'>light</a> "+
+                   "<a href='#'>dark</a> "+
+                   (room=='all' ? '': "<a href='#'>like:all</a>"));
+    $(appendTo+" tr.ar_reset a").click(function(){
+      var txt = $(this).text();
+      if(txt == "like:all") {
+        delete(roomHash[room]);
+        cleanRoom();
+      } else {
+        f18_resetCol(txt, room);
+        if(room == "all")
+          f18_setCss('preset');
+      }
+      f18_setAttr();
+      f18_drawSpecial();
+    });
+    addColorChooser("bg",      "Background");
+    addColorChooser("fg",      "Foreground");
+    addColorChooser("link",    "Link");
+    addColorChooser("evenrow", "Even row");
+    addColorChooser("oddrow",  "Odd row");
+    addColorChooser("header",  "Header row");
+    addColorChooser("menu",    "Menu");
+    addColorChooser("sel",     "Menu:Selected");
+    addColorChooser("inpBack", "Input bg");
+    $("table.f18colors input").attr("size", 8);
+
+    var bgImg = attr("bgImg", true);
+    addRow("bgImg", "<a href='#'>Background image: <span>"+
+                    (bgImg ? bgImg : "none")+"</span></a>");
+    $(appendTo+" tr.ar_bgImg a").click(function(){
+      FW_cmd(FW_root+'?cmd='+
+      '{ join("\\n",FW_fileList("$FW_icondir/background/*.(jpg|png)")) }&XHR=1',
+      function(data) {
+        if(data)
+          data += "none";
+        var imgList = data.split(/\n/);
+        FW_okDialog("List of files in www/images/background:<br><ul>"+
+              "<a href='#'>"+imgList.join("</a><br><a href='#'>")+'</a></ul>');
+        $("#FW_okDialog a").click(function(){
+          var txt = $(this).text();
+          setAttr("bgImg", txt == 'none' ? undefined : txt, true);
+          $(appendTo+" tr.ar_bgImg span").html(txt);
+          f18_setCss("bgImg");
+        });
+      });
+      
+    });
+
+    addHider("hideLogo", true, "Hide logo", f18_menu);
+    addHider("hideInput", true, "Hide input", f18_menu);
+    addHider("hidePin", true, "Hide pin", function(c){
+      if(f18_hasPos || f18_getAttr("showDragger"))
+        $("div.pinHeader.menu div.pin").css("display", c ? "none":"block");
+      else
+        $("div.pinHeader div.pin").css("display", c ? "none":"block");
+    });
+
+    $("div.f18colors").css("margin-top", "20px");
+    $("tr.f18 div.fileList").each(function(e){f18_addPinToStyleDiv(this)});
+  };
+  loadScript("pgm2/fhemweb_colorpicker.js", f18_drawSpecial);
+}
+
+function
+f18_addPinToStyleDiv(el)
+{
+  var grp = $(el).text();
+  f18_addPin(el, "style.list."+grp, true,
+  function(isFixed){
+    var ntr = $(el).next("table");
+    isFixed ? $(ntr).show() : $(ntr).hide();
+  });
+  if(f18_getAttr("showDragger"))
+    f18_addDragger(el);
+  f18_setPos(el);
+  if(f18_hasPos || f18_getAttr("showDragger"))
+    $("div.pinHeader:not(.menu) div.pin").hide();
+}
+
+
 function
 f18_resize()
 {
   var w=$(window).width();
   log("f18.js W:"+w+" S:"+screen.width);
+  var hl = f18_getAttr("hideLogo"),
+      hi = f18_getAttr("hideInput"),
+      pm = f18_getAttr("Pinned.menu");
 
   var diff = 0;
-  diff += f18_attr.hideLogo ? 0 : 40;
-  diff += f18_attr["Pinned.menu"] ? 0 : 44;
-  $("input.maininput").css("width", (w-(FW_isiOS ? 40 : 30)-diff)+'px');
+  diff += hl ? 0 : 40;
+  diff += pm ? 0 : 44;
+  $("input.maininput")
+    .css("width", (w-(FW_isiOS ? 40 : 30)-diff)+'px')
+    .css("display", hi ? "none":"block");
+  $("#menu,#content").css("top", (hi && pm && hl) ? "10px" : "50px");
 }
 
 function
 f18_addPin(el, name, defVal, fn, hidePin)
 {
-  var init = f18_attr["Pinned."+name];
+  var init = f18_getAttr("Pinned."+name);
   if(init == undefined)
     init = defVal;
   $("<div class='pin'></div>")
@@ -301,7 +408,7 @@ f18_addPin(el, name, defVal, fn, hidePin)
   $(el)
     .addClass(init ? "pinIn" : "")
     .css("cursor", "pointer")
-    .css("display", (f18_attr.hidePin || hidePin) ? "none" : "block")
+    .css("display", (f18_getAttr("hidePin") || hidePin) ? "none" : "block")
     .click(function(){
       var nextVal = !$(el).hasClass("pinIn");
       $(el).toggleClass("pinIn");
@@ -371,20 +478,34 @@ f18_setPos(el)
               left:pos.left, top:pos.top, right:"auto", bottom:"auto" });
 }
 
+function
+f18_getAttr(attrName)
+{
+  if(f18_room != undefined) {
+    var val = f18_attr["Room."+f18_room+"."+attrName];
+    if(val != undefined)
+      return val
+  }
+  return f18_attr[attrName];
+}
+
 function
 f18_setAttr(name, value)
 {
   if(name)
     f18_attr[name]=value;
+  if(name && value == undefined)
+    delete f18_attr[name];
   if(name && name.indexOf("Pinned.") == 0 && !f18_attr.savePinChanges)
     return;
+
   var wn = $("body").attr("data-webName");
   FW_cmd(FW_root+"?cmd=attr "+wn+" styleData "+
-        encodeURIComponent(JSON.stringify(f18_sd, null, 2))+"&XHR=1");
+         encodeURIComponent(JSON.stringify(f18_sd, undefined, 1))+"&XHR=1");
 }
 
 function
-f18_resetCol(name)
+f18_resetCol(name, room)
 {
   var cols = {
     "default":{ bg:     "FFFFE7", fg:    "000000", link:   "278727", 
@@ -397,66 +518,62 @@ f18_resetCol(name)
                 evenrow:"333333", oddrow:"111111", header: "222222",
                 menu:   "111111", sel:   "333333", inpBack:"444444" }
   };
-  f18_attr.cols = name ? cols[name] : cols["default"];
+  var col = (name ? cols[name] : cols["default"]);
+  var prefix = (room && room != 'all' ? "Room."+room+".cols." : "cols.");
+  for(var c in col)
+    f18_attr[prefix+c] = col[c];
 }
 
 // Put all the colors into a head style tag, send background changes to FHEM
 function
 f18_setCss(why)
 {
-  var cols = f18_attr.cols;
   var style = "";
+  function col(n) { return f18_getAttr("cols."+n, true) };
   function bg(c) { return "{ background:#"+c+"; fill:#"+c+"; }\n" }
   function fg(c) { return "{ color:#"+c+"; }\n" }
-  style += ".col_fg, body, input, textarea "+fg(cols.fg);
-  style += ".col_bg, body, #menu, textarea, input, option "+bg(cols.bg);
-  style += ".col_link, a, .handle, .fhemlog, input[type=submit], select "+
-                 "{color:#"+cols.link+"; stroke:#"+cols.link+";}\n";
-  style += "svg:not([fill]):not(.jssvg) { fill:#"+cols.link+"; }\n";
-  style += ".col_evenrow, table.block,div.block "+bg(cols.evenrow);
-  style += ".col_oddrow,table.block tr.odd,table.block tr.sel "+bg(cols.oddrow);
-  style += ".col_header "+bg(cols.header);
-  style += ".col_menu, table.room "+bg(cols.menu);
-  style += ".col_sel, table.room tr.sel "+bg(cols.sel);
-  style += ".col_inpBack, input "+bg(cols.inpBack);
-  if(cols.bg == "FFFFE7") // default
-    style += "div.pinHeader.menu {background:#"+cols.sel+";}\n";
+  style += ".col_fg, body, input, textarea "+fg(col("fg"));
+  style += ".col_bg, #menu, textarea, input, option "+bg(col("bg"));
+  style += ".col_link, a, .handle, .fhemlog, input[type=submit], select, "+
+           "div.ui-widget-content a "+
+           "{color:#"+col("link")+"!important; stroke:#"+col("link")+";}\n";
+  style += "svg:not([fill]):not(.jssvg) { fill:#"+col("link")+"; }\n";
+  style += ".col_evenrow, table.block,div.block "+bg(col("evenrow"));
+  style += ".col_oddrow,table.block tr.odd,table.block tr.sel "+
+        bg(col("oddrow"));
+  style += ".col_header "+bg(col("header"));
+  style += ".col_menu, table.room "+bg(col("menu"));
+  style += ".col_sel, table.room tr.sel "+bg(col("sel"));
+  style += ".col_inpBack, input "+bg(col("inpBack"));
+  if(col("bg") == "FFFFE7") // default
+    style += "div.pinHeader.menu {background:#"+col("sel")+";}\n";
 
-  style += "div.ui-dialog-titlebar "+bg(cols.header);
-  style += "div.ui-widget-content "+bg(cols.bg);
-  style += "div.ui-widget-content, .ui-button-text "+fg(cols.fg+"!important");
-  style += "div.ui-dialog { border:1px solid #"+cols.link+"; }";
-  style += "button.ui-button { background:#"+cols.oddrow+"!important; "+
-                              "border:1px solid #"+cols.link+"!important; }\n";
+  style += "div.ui-dialog-titlebar "+bg(col("header"));
+  style += "div.ui-widget-content "+bg(col("bg"));
+  style += "div.ui-widget-content, .ui-button-text "+fg(col("fg")+"!important");
+  style += "div.ui-dialog { border:1px solid #"+col("link")+"; }";
+  style += "button.ui-button { background:#"+col("oddrow")+"!important; "+
+                            "border:1px solid #"+col("link")+"!important; }\n";
 
   if(typeof DashboardDraggable  != "undefined") {
     var db = "#dashboard ";
-    style += db+".dashboard_widgetheader "+bg(cols.header);
-    style += db+".dashboard_tabnav "+bg(cols.menu+"!important");
-    style += db+".ui-widget-header .ui-state-default "+bg(cols.menu);
-    style += db+".ui-widget-header .ui-state-active "+bg(cols.sel);
-    style += db+".ui-widget-header "+fg(cols.fg+"!important;");
+    style += db+".dashboard_widgetheader "+bg(col("header"));
+    style += db+".dashboard_tabnav "+bg(col("menu")+"!important");
+    style += db+".ui-widget-header .ui-state-default "+bg(col("menu"));
+    style += db+".ui-widget-header .ui-state-active "+bg(col("sel"));
+    style += db+".ui-widget-header "+fg(col("fg")+"!important;");
     style += db+".ui-widget-header li { border:none!important; }";
-    style += db+".ui-widget-content a "+fg(cols.link+"!important" );
+    style += db+".ui-widget-content a "+fg(col("link")+"!important" );
+  }
+  var bgImg = f18_getAttr("bgImg", true);
+  if(bgImg) {
+    style += 'body { background-image: url('+FW_root+
+                     '/images/background/'+bgImg+');}';
+  } else {
+    style += "body "+bg(col("bg"));
   }
 
   $("head style#f18_css").remove();
-  if(why == 'preset' || why == 'bg') { // Add background to css to avoid flicker
-    if(!$("head #fhemweb_css").length)
-      $("head").append("<style id='fhemweb_css'>\n</style>");
-    var otxt = $("head #fhemweb_css").html(), ntxt = otxt;
-    if(!ntxt)
-      ntxt = "";
-    ntxt = ntxt.replace(/^body,#menu { background:[^;]*; }[\r\n]*/m,'');
-    ntxt += "body,#menu { background:#"+cols.bg+"; }\n";
-    if(ntxt != otxt) {
-      $("head #fhemweb_css").html(ntxt);
-      var wn = $("body").attr("data-webName");
-      FW_cmd(FW_root+"?cmd=attr "+wn+" Css "+
-             encodeURIComponent(ntxt.replace(/;/g,";;"))+"&XHR=1");
-    }
-  }
-
   style = "<style id='f18_css'>"+style+"</style>";
   if($("head style#fhemweb_css").length)
     $("head style#fhemweb_css").before(style);
@@ -464,20 +581,36 @@ f18_setCss(why)
     $("head").append(style);
 
   $("head meta[name=theme-color]").remove();
-  $("head").append('<meta name="theme-color" content="#'+cols.bg+'">');
+  $("head").append('<meta name="theme-color" content="#'+col("bg")+'">');
+
+  // Recolor the menu arrows. CSS does not apply to such SVGs :(
+  if(why=='init' || why=='preset' || why=='link') {
+    var a = $("a").get(0);
+    if(window.getComputedStyle && a) {
+      var col = getComputedStyle(a,null).getPropertyValue('color');
+      FW_arrowRight = FW_arrowRight.replace(/rgb[^)]*\)/,col);
+      FW_arrowDown  = FW_arrowDown.replace(/rgb[^)]*\)/,col);
+      $("div#menu table.room tr.menuTree > td > div > div")
+        .css("background-image", "url('"+FW_arrowRight+"')");
+      $("div#menu table.room tr.menuTree.open > td > div > div")
+        .css("background-image", "url('"+FW_arrowDown+"')");
+    }
+  }
+
 }
 
 // SVG color tuning
 function
 f18_svgSetCols(svg)
 {
+  function col(n) { return f18_getAttr("cols."+n, true) };
+
   if(!svg || !svg.getAttribute("data-origin"))
     return;
 
   var style = $(svg).find("> style").first();
   var sTxt = $(style).text();
-  var cols = f18_attr.cols;
-  sTxt = sTxt.replace(/font-family:Times/, "fill:#"+cols.fg);
+  sTxt = sTxt.replace(/font-family:Times/, "fill:#"+col("fg"));
   $(style).text(sTxt);
 
   function
@@ -500,8 +633,8 @@ f18_svgSetCols(svg)
   // SVG background gradient: .css does not work in Firefox, has to use .attr
   var stA = $(svg).find("> defs > #gr_bg").children();
   var so = "; stop-opacity:1;";
-  $(stA[0]).attr("style", "stop-color:#"+addCol(cols.bg,10)+so);
-  $(stA[1]).attr("style", "stop-color:#"+addCol(cols.bg,-10)+so);
+  $(stA[0]).attr("style", "stop-color:#"+addCol(col("bg"),10)+so);
+  $(stA[1]).attr("style", "stop-color:#"+addCol(col("bg"),-10)+so);
 }
 
 // font-awesome
diff --git a/fhem/www/pgm2/f18style.css b/fhem/www/pgm2/f18style.css
index beb52f74b..f65a5a546 100644
--- a/fhem/www/pgm2/f18style.css
+++ b/fhem/www/pgm2/f18style.css
@@ -63,5 +63,6 @@ body.pinnedMenu #logo { left:10px; }
 body.small.rightMenu #menuBtn { right:10px; left:auto; }
 body.small.rightMenu #logo { right:52px; left:auto; }
 body.small.rightMenu #hdr { left:10px; right:auto; }
+body { background-repeat: no-repeat; background-size:cover; }
 
 div.SVGlabel { display:inline-block; }
diff --git a/fhem/www/pgm2/fhemweb.js b/fhem/www/pgm2/fhemweb.js
index 4ebf88472..755cdffdf 100644
--- a/fhem/www/pgm2/fhemweb.js
+++ b/fhem/www/pgm2/fhemweb.js
@@ -817,16 +817,18 @@ FW_rawDef()
   });
 }
 
+var FW_arrowDown, FW_arrowRight;
 function
 FW_treeMenu()
 {
-  var col = 'rgb(39, 135, 38)';
   var a = $("a").get(0);
+  var col = 'rgb(39, 135, 38)';
   if(window.getComputedStyle && a)
     col = getComputedStyle(a,null).getPropertyValue('color'); 
-  var arrowRight='data:image/svg+xml;utf8,<svg viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg"><path fill="gray" d="M1171 960q0 13-10 23l-466 466q-10 10-23 10t-23-10l-50-50q-10-10-10-23t10-23l393-393-393-393q-10-10-10-23t10-23l50-50q10-10 23-10t23 10l466 466q10 10 10 23z"/></svg>'
-        .replace('gray', col);
-  var arrowDown=arrowRight.replace('/>',' transform="rotate(90,896,896)"/>');
+  FW_arrowRight = 'data:image/svg+xml;utf8,<svg viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg"><path fill="gray" d="M1171 960q0 13-10 23l-466 466q-10 10-23 10t-23-10l-50-50q-10-10-10-23t10-23l393-393-393-393q-10-10-10-23t10-23l50-50q10-10 23-10t23 10l466 466q10 10 10 23z"/></svg>'
+      .replace('gray', col);
+  FW_arrowDown =FW_arrowRight.replace('/>',' transform="rotate(90,896,896)"/>');
+
   var fnd;
 
   $("div#menu table.room").each(function(){     // one loop per Block
@@ -871,7 +873,7 @@ FW_treeMenu()
     $(t).find("tr[data-mTree]").not(".level0").hide();
     $(t).find("tr.menuTree").click(function(){treeClick(this)});
     $(t).find("tr.menuTree > td > div > div")
-        .css("background-image", "url('"+arrowRight+"')");
+        .css("background-image", "url('"+FW_arrowRight+"')");
     var selRoom = $("div#content").attr("room");
     if(selRoom) {
       var ta = selRoom.split("->"), nxt="";
@@ -888,11 +890,11 @@ FW_treeMenu()
     var tgt = FW_escapeSelector($(el).attr("data-nxt"));
     if($(el).hasClass("closed")) {
       $(el).closest("table").find("tr[data-mTree="+tgt+"]").show();
-      $(el).find("div>div").css("background-image", "url('"+arrowDown+"')");
+      $(el).find("div>div").css("background-image", "url('"+FW_arrowDown+"')");
     } else {
       $(el).closest("table").find("tr[data-mTree^="+tgt+"]")
         .hide().addClass("closed");
-      $(el).find("div>div").css("background-image", "url('"+arrowRight+"')");
+      $(el).find("div>div").css("background-image", "url('"+FW_arrowRight+"')");
     }
     $(el).toggleClass("closed");
     $(el).toggleClass("open");