HPEx - HabPanel Extension

Looks like I spoke too soon. I changed the screen change time to get more time to troubleshoot and now everything seems to be working fine, including theater mode.

Hey Lucky,

Thanks for adding the Theater Mode; Iā€™ve just installed your panel for the first time and Iā€™m trying the ā€œbasicā€ screen save mode first and have discovered another ā€œfeature neededā€ at least for me ;-(

I have 3 tablets on the wall; one is vertically placed and the other two are horizontally placed.

The way I have the panels in HabPanel setup is; I have one for Horizontal and one for Vertical since the layout and widget sizes are so much different based on the orientation of the tablet.

For example, I have a panel called Weather and another called Weather-V which means a vertical layout. I have many panels setup like this . . .

The issue Iā€™m having is; if I enable the screen saver and choose the panels I want to rotate through, it applies to both my horizontal and vertical tablets which means its going to show panels that will not fit or look proper on one of the tablet orientations.

Iā€™m not sure if Iā€™m SOL or is there something we can do to address this scenario?

Vertical:

Horizontal:

Yes, those temps are accurate!

Best, Jay

Thatā€™s pretty much the issue with HabPanel in general. All settings are shared across ALL devices. Let me see if I can come up with a way to have different hpex configs per device, stored on different String items.

Chicago temperatureā€¦1ā€¦ yikes!!!
me is in floridaā€¦ and Iā€™m bummed because 60 F

Done. Get latest from the git

You can have as many device as you want, and all of them can be configured to have their own settings/configuration.

Thanks Lucky for allowing this change. It will be a HUGE help for everybody. My issue is getting the updated JSON into the panel. I have cleared the browser cache and tried another browser (Windows Chrome and Firefox) but it continues to bring in the same looking JSON for configuration.

I did download the latest ZIP off of GIT per your recommendation and the date of the JSON file is 1/29/19 @ 5:39 pm so Iā€™m pretty sure I got the last update of it.

Every time I try from scratch it seems to keep my OLD configuration (panel rotation ids) which means to me itā€™s NOT really getting deleted when I delete the panel and the JSON widget.

Any advise?

Best, Jay

Json config will look the same lol. You need to go to Advanced tab under the settings page and use a different Item string. You need to type ā€œI understoodā€ in the box to make it editable.

For some reason the image upload isnā€™t workingā€¦

Hereā€™s my screen shots of what Iā€™m seeing. Yup, the platform for displaying images is down.

Iā€™m not seeing anywhere to type in ā€œI understoodā€, I even typed it in the RAW white space but that didnā€™t work.

Best, Jay

Yup. Seems like youā€™re still on the older version. My commits can be seen from https://github.com/LuckyMallari/hpex/commits/master

You need to download from github and replace everything in the habpanelex folder with the files from the download.

The images seem to be working now. You can see the new version from the image in the above postā€¦

Yup, I did it again this morning and pulled it from this tree GitHub - LuckyMallari/hpex at facf703843f5014a5fdb211481005eec728d990c

I deleted the widget on Habpanelex, the panel itself and the JSON import.

I deleted the habpanelex folder in HTML and then put the new version in there and re-did the same process all over again.

Itā€™s still showing the OLD setup along with it remembering my panel configuration IDs in the order I want them in.

Do I need to blow away cache/tmp directories?

Best, Jay

Yeah try emptying your cache. It might be caching the js files and html

I cleared the cache & tmp directories along with a clean boot up with your latest download and itā€™s still showing the last version of the functionality.

Iā€™m not sure how to remove this from my system completely to get the updated version?

Best, Jay

Hey Lucky,

Just to confirm I have the latest version from GIT, what file can I look into and see what to confirm I have the actual changes?

Best, Jay

Can you post a screenshot of your habpanelex folder. It should be oh-conf-htmlā€¦ place the habpanelex there. If it ask you to replace old stuff, say yes. So it should be conf-html-habpanelex-the js files etc.

There is only ONE JS file in that folder.

Hereā€™s the content of it:

/*
    HPEx - (H)ab(P)anel(Ex)tension
    Extends HabPanel Functionality
    Lucky Mallari
    https://github.com/LuckyMallari/hpex

    MIT License

    Copyright (c) 2019 Lucky Mallari https://github.com/LuckyMallari/hpex

    Permission is hereby granted, free of charge, to any person obtaining a copy
    of this software and associated documentation files (the "Software"), to deal
    in the Software without restriction, including without limitation the rights
    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    copies of the Software, and to permit persons to whom the Software is
    furnished to do so, subject to the following conditions:

    The above copyright notice and this permission notice shall be included in all
    copies or substantial portions of the Software.

    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    SOFTWARE.

*/
(function () {
    'use strict';

    // Polyfills
    (function hpexpolyfills() {
        if (!Element.prototype.matches) {
            Element.prototype.matches =
                Element.prototype.matchesSelector ||
                Element.prototype.mozMatchesSelector ||
                Element.prototype.msMatchesSelector ||
                Element.prototype.oMatchesSelector ||
                Element.prototype.webkitMatchesSelector ||
                function (s) {
                    var matches = (this.document || this.ownerDocument).querySelectorAll(s),
                        i = matches.length;
                    while (--i >= 0 && matches.item(i) !== this) { }
                    return i > -1;
                };
        }
    })();

    var app = angular.module('app', []);

    app.constant('DEFAULTCONFIG', {
        "initComplete": true,
        "isShowInDrawer": false,
        "screensaver": {
            "isEnabled": true,
            "timeoutSeconds": 120,
            "dashboardList": "",
            "durationSeconds": 10,
            "isFullScreen": true
        },
        "theaterMode": {
            "isEnabled": true,
            "isOn": false,
            "color": "rgba(0,0,0,0.90)",
            "triggeringItem": null
        }
    });
    app.service('HPExUtils', ['DEFAULTCONFIG',
        function (DEFAULTCONFIG) {

            var logTag = 'HPExUtils';
            var serviceApi = {};

            var log = function (s) { console.log(logTag + ": " + s); }

            var synchrounousRestCall = function (method, itemName, body, mime) {
                var xhr = new XMLHttpRequest();
                var url = '/rest/items/' + itemName;
                if ((method || "").toUpperCase() === 'GET') {
                    url += '?lucky=' + Math.random().toString().substring(2, 10) + 'charms'
                }
                xhr.open(method, url, false);
                mime = mime || "application/json";
                if ((method || "").toUpperCase() !== "GET")
                    xhr.setRequestHeader("Content-Type", mime);
                xhr.setRequestHeader("Accept", "application/json");
                xhr.send(body && JSON.stringify(body) || null);
                if (xhr.status !== 200 && xhr.status !== 201)
                    return {
                        status: xhr.status,
                        result: null
                    };
                return {
                    status: xhr.status,
                    result: xhr.responseText
                };
            }

            serviceApi.getConfig = function (id) {
                var c = synchrounousRestCall('GET', id);
                if (c == null || c.status !== 200) {
                    // Config does not exist. Create an item for it.
                    log("Config item does not exist. Creating one..")
                    var data = {
                        "type": "String",
                        "name": id,
                        "label": "HPEx Config for " + id
                    };

                    var d = synchrounousRestCall('PUT', id, data);
                    // Then retrieve it
                    c = synchrounousRestCall('GET', id);
                }
                try {
                    c = c && c.result && JSON.parse(c.result);
                } catch (e) {
                    c = "{}"
                }
                c = c.state;
                try {
                    c = JSON.parse(c);
                } catch (e) {
                    c = "{}";
                }
                if (c.initComplete) {
                    log("Config item found.")
                    return c;
                }

                log("Initializing Config.")
                c = DEFAULTCONFIG;
                c.initComplete = true;
                synchrounousRestCall('POST', id, c, "text/plain");
                log("Config init complete.")
                return c;
            }

            serviceApi.saveConfig = function (id, configData) {
                return synchrounousRestCall('POST', id, configData, "text/plain");
            }

            var getDom = function (d) {
                return typeof (d) === "object" ? d : document.querySelector(d);
            };

            serviceApi.hasParent = function (childSelector, parentSelector) {
                return serviceApi.findParent(childSelector, parentSelector) !== null;
            };

            serviceApi.findParent = function (childSelector, parentSelector) {
                var child = getDom(childSelector);
                return child && child.closest(parentSelector);
            };

            var init = function () { log("Init!"); return true; };

            serviceApi.init = init();

            return serviceApi;
        }


    ]);

    app.service('HPExService', ['$rootScope', '$location', 'OHService', 'HPExUtils', '$compile',
        function ($rootScope, $location, OHService, HPExUtils, $compile) {

            var logTag = 'HPExService';
            var serviceApi = {};

            var log = function (s) { console.log(logTag + ": " + s); }
            var protectedConfigData = HPExUtils.getConfig("habpanelExConfig");

            serviceApi.goToDashboard = function (name) {
                $location.url('/view/' + name);
            };

            serviceApi.saveConfig = function (newConfig) {
                protectedConfigData = newConfig;
                return HPExUtils.saveConfig("habpanelExConfig", newConfig);
            };

            serviceApi.getConfig = function () {
                return angular.copy(protectedConfigData);
            }

            serviceApi.toast = function (message) {

            };

            var init = function () {
                log("Init!");
                return true;
            };

            serviceApi.init = init(); 1
            return serviceApi;
        }
    ]);

    app.service('HPExScrSaverService', ['HPExService', '$rootScope', 'HPExUtils', '$interval', '$timeout', 'PersistenceService',
        function (HPExService, $rootScope, HPExUtils, $interval, $timeout, PersistenceService) {
            var logTag = 'HPExScrSaverService';
            var log = function (s) { console.log(logTag + ": " + s); }
            var serviceApi = {};
            var eventsList = ["mousemove", "keyup", "keydown", "click"];
            var timers = {
                idleTimer: null,
                screenSaverTimer: null
            }
            var config = {};
            var currentIndex = 0;

            serviceApi.getAvailablePanelIds = function () {
                return extractIds(PersistenceService.getDashboards()).join(', ');
            }

            var resetTimer = function () {
                stopIdleTimer();
                startIdleTimer();
            };

            var idleHit = function () {
                var interval = config.durationSeconds || 10;
                timers.screenSaverTimer = $interval(next, interval * 1000);
            }

            var next = function () {
                var nextDb = config.dashboardList[currentIndex];
                HPExService.goToDashboard(nextDb);
                if (currentIndex == config.dashboardList.length - 1)
                    currentIndex = 0;
                else
                    currentIndex++;
            };

            var startIdleTimer = function () {
                var timeout = config.timeoutSeconds || 300;
                timers.idleTimer = $timeout(idleHit, timeout * 1000);
            };

            var stopIdleTimer = function () {
                $timeout.cancel(timers.idleTimer);
                $interval.cancel(timers.screenSaverTimer);
                timers.idleTime = null;
                timers.screenSaverTimer = null;
            };

            var listen = function () {
                for (var i = 0; i < eventsList.length; i++) {
                    var e = eventsList[i];
                    document.addEventListener(e, resetTimer);
                }
            };

            var unlisten = function () {
                for (var i = 0; i < eventsList.length; i++) {
                    var e = eventsList[i];
                    document.removeEventListener(e, resetTimer);
                }
            };

            var extractIds = function (a) {
                var l = [];
                if (!Array.isArray(a)) {
                    if (typeof (a) === "string") {
                        return a.split(",");
                    } else {
                        return null;
                    }
                }

                for (var i = 0; i < a.length; i++) {
                    var d = a[i];
                    d.id && d.id !== "HabPanelEx" && l.push(d.id);
                }
                return l;
            }

            var initConfig = function () {
                config = angular.copy(HPExService.getConfig());
                config = config && config.screensaver || {};
                if (!config.dashboardList) {
                    config.dashboardList = PersistenceService.getDashboards();
                }
                config.dashboardList = extractIds(config.dashboardList);
            };

            var ssMain = function () {
                if (config.isEnabled) {
                    listen();
                    resetTimer();
                } else {
                    unlisten();
                    stopIdleTimer();
                }
            };

            var onConfigChanged = function () {
                initConfig();
                ssMain();
            };

            var onInit = function () {
                initConfig();
                ssMain();
                log("Init");
            };

            $rootScope.$on('HPExEvent.configChanged', onConfigChanged);
            onInit();

            return serviceApi;
        }
    ]);

    app.controller('HPExDrawerCtrl', ['$scope', 'HPExService', '$rootScope', 'HPExUtils', 'HPExScrSaverService',
        function ($scope, HPExService, $rootScope, HPExUtils, HPExScrSaverService) {
            var logTag = 'HPExDrawerCtrl';
            var log = function (s) { console.log(logTag + ": " + s); }

            var onDestroy = function () {
                log("Destroyed!");
            };

            var showOrHide = function () {

                var found = HPExUtils.findParent('.hpex-drawer', 'li');

                if (found) {
                    if ($scope.config.isShowInDrawer) {
                        found.style.display = "block";
                        return;
                    }
                    found.style.display = "none";
                }
                return !!found;
            };

            var onInit = function () {
                log("Init");

                $scope.config = HPExService.getConfig();
                showOrHide();
            };


            $rootScope.$on('HPExEvent.configChanged', onInit);
            $scope.$on('$destroy', onDestroy);
            onInit();
        }]
    );

    app.service('HPExModalService', ['$uibModal',
        function ($uibModal) {
            var serviceApi = {};

            var bucket = { modalInstance: null };
            serviceApi.open = function (title, message) {
                bucket.modalInstance = $uibModal.open({
                    templateUrl: '/static/habpanelex/tpl/modal.html?lucky=' + Math.random().toString().substring(2, 7),
                    backdrop: 'static',
                    controller: function ($scope, $uibModalInstance) {
                        $scope.close = $uibModalInstance.close;
                        $scope.title = title;
                        $scope.message = message;
                    }
                })
            };
            serviceApi.close = function () {
                bucket.modalInstance.close();
            };
            return serviceApi;
        }
    ]);


    /*
        Main Settings Controller
    */
    app.controller('HPExCtrl', ['$scope', 'HPExService', 'HPExUtils', '$rootScope', 'HPExScrSaverService', 'HPExModalService',
        function ($scope, HPExService, HPExUtils, $rootScope, HPExScrSaverService, HPExModalService) {
            var logTag = 'HPExCtrl';
            $scope.header = "HPEx";

            $scope.availablePanelIds = HPExScrSaverService.getAvailablePanelIds();

            $scope.instanceId = Math.random().toString(36).substring(2);

            $scope.isDrawer = function () {
                var retVal = HPExUtils.hasParent('.hpex-main_' + $scope.instanceId, '.drawer');
                return retVal;
            };

            $scope.saveConfig = function () {
                var r = HPExService.saveConfig($scope.config);
                if (r.status === 201 || r.status === 200) {
                    $rootScope.$broadcast('HPExEvent.configChanged');
                    HPExModalService.open('Config', 'Saved');
                }
                else {
                    HPExModalService.open('Config', 'Failed');
                }

            }

            $scope.cancelConfig = function () {
                onInit();
                HPExModalService.open('Config', 'Canceled');
            }

            var log = function (s) { console.log(logTag + ": " + s); }

            var onInit = function () {
                $scope.config = HPExService.getConfig();
                log("Init");
            };

            var onDestroy = function () {
                log("Destroyed!");
            };

            $scope.$on('$destroy', onDestroy);
            onInit();
        }
    ]);

    app.service('HPExFSService', ['$rootScope', 'HPExService',
        function ($rootScope, HPExService) {
            var logTag = 'HPExFSService';
            var serviceApi = {};
            var config = {};

            var onConfigChanged = function () {
                config = HPExService.getConfig();
            };

            var log = function (s) { console.log(logTag + ": " + s); }

            var onInit = function () {
                config = HPExService.getConfig();
                log("Init");
            };

            serviceApi.startTheaterMode = function () {
                var el = document.getElementById('theaterModeMainEl');
                el && (el.style.display = "block");
            };

            serviceApi.stopTheaterMode = function () {
                var el = document.getElementById('theaterModeMainEl');
                el && (el.style.display = "none");
            };

            serviceApi.toggleTheaterMode = function () {
                var el = document.getElementById('theaterModeMainEl');
                var d = el && el.style.display;
                if (d) {
                    el.style.display = el.style.display == "none" ? "block" : "none";
                }
            };

            $rootScope.$on('HPExEvent.configChanged', onConfigChanged);
            onInit();

            return serviceApi;
        }
    ]);

    app.controller('HPExTheaterCtrl', ['$scope', 'HPExFSService',
        function ($scope, HPExFSService) {
            $scope.on = function () {
                HPExFSService.startTheaterMode();
            };
            $scope.off = function () {
                HPExFSService.stopTheaterMode();
            };
            $scope.toggle = function () {
                HPExFSService.toggleTheaterMode();
            };
        }
    ]);

    app.directive('habPanelEx', function () {
        return {
            templateUrl: '/static/habpanelex/tpl/main.html?lucky=' + Math.random().toString().substring(2, 7),
            restrict: 'A',
            controller: 'HPExCtrl',
            scope: false
        };
    });
})();

Best, Jay

I see. For some reason you are not downloading the latest version. Go back to github and see there are 2 js files now. Inside ctrl.js there should be a saveNewConfig function. I donā€™t see that in the one you pasted above.

Download it from the main github repo/url and not from a commit url. https://github.com/LuckyMallari/hpex

https://github.com/LuckyMallari/hpex/archive/master.zip

Iā€™ve downloaded it from both of these URLā€™s and there is still only ONE .JS in the habpanelex folder.

There is another .JS file but itā€™s outside of the habpenalex folder called polyfills.js

Should I just drag polyfills.js into the habpanelex folder?

Best, Jay

Then thereā€™s something wrong with your browser. I just tried it using my phone and files are there

Download the zip. Unzip everything to habpanelex folder.

I have that exact structure; but your instructions say ONLY to the Copy habpanelex folder to your OpenHABā€™s conf/html folder.

Iā€™m not copying the stuff outside of the habpanelex folder which is the TPL & IMG folder, README, Polyfills.js and ctrl.js folders to conf/html folder on OH.

What I have in the *habpanelex folder is: img, tpl folders and a ctrl.js file

Best, Jay