<commit_before>(function() {
'use strict';

    angular
        .module('app.services')
        .service('OHService', OHService)
        .value('OH2ServiceConfiguration', {})
        .service('OH2StorageService', OH2StorageService);

    OHService.$inject = ['$rootScope', '$http', '$q', '$timeout', '$interval', '$filter', '$location', 'SpeechService', 'tmhDynamicLocale', '$translate'];
    function OHService($rootScope, $http, $q, $timeout, $interval, $filter, $location, SpeechService, tmhDynamicLocale, $translate) {
        this.getItem = getItem;
        this.getItems = getItems;
        this.getLocale = getLocale;
        this.onUpdate = onUpdate;
        this.sendCmd = sendCmd;
        this.sendVoice = sendVoice;
        this.reloadItems = reloadItems;

        var liveUpdatesEnabled = false, prevAudioUrl = '', locale = null, eventSource = null;

        ////////////////

        function onUpdate(scope, name, callback) {
            var handler = $rootScope.$on('openhab-update', callback);
            scope.$on('$destroy', handler);
            //watchItem(name);
            //longPollUpdates(name);
        }

        function loadItems() {
            $http.get('/rest/items')
            .then(function (data) {
                if (angular.isArray(data.data)) {
                    console.log("Loaded " + data.data.length + " openHAB items");
                    $rootScope.reconnecting = false;
                    $rootScope.items = data.data;
                    if (!liveUpdatesEnabled) registerEventSource();
                } else {
                    console.warn("Items not found? Retrying in 5 seconds");
                    $rootScope.reconnecting = true;
                    $rootScope.items = [];
                    $timeout(loadItems, 5000);
                }
                $rootScope.$emit('openhab-update');
            },
            function (err) {
                console.warn("Error loading openHAB items... retrying in 5 seconds");
                $rootScope.reconnecting = true;
                $timeout(loadItems, 5000);
            });
        }

        function getItem(name) {
            var item = $filter('filter')($rootScope.items, {name: name}, true);
            return (item) ? item[0] : null;
        }

        function getItems() {
            return $rootScope.items;
        }

        /**
         * Sends command to openHAB
         * @param  {string} item Item's id
         * @param  {string} cmd  Command
         */
        function sendCmd(item, cmd) {
            $http({
                method : 'POST',
                url    : '/rest/items/' + item,
                data   : cmd,
                headers: { 'Content-Type': 'text/plain' }
            }).then(function (data) {
                console.log('Command sent: ' + item + '=' + cmd);

                // should be handled by server push messages but their delivery is erratic
                // so perform a full refresh every time a command is sent
                //loadItems();
            });
        }

        /**
         * Returns a promise with the configured locale
         */
        function getLocale() {
            var deferred = $q.defer();

            if (locale) {
                deferred.resolve(locale);
            } else {
                $http.get('/rest/services/org.eclipse.smarthome.core.i18nprovider/config')
                .then(function (response) {
                    var language;
                    if (!response.data.language) {
                        if (navigator && navigator.languages) {
                            locale = navigator.languages[0];
                            language = locale.split('-')[0];
                        } else if (navigator && navigator.language) {
                            locale = navigator.language;
                            language = locale.split('-')[0];
                        } else {
                            locale = language = 'en';
                        }
                    } else {
                        language = response.data.language;
                        locale = response.data.language + ((response.data.region) ? '-' + response.data.region : '');
                    }

                    /* consider the region only for selected common exceptions where the date/number formats
                        are significantly different than the language's default.
                        If more are needed change the gulpfile.js too and run the 'vendor-angular-i18n' gulp task */
                    if (['es-ar', 'de-at', 'en-au', 'fr-be', 'es-bo', 'pt-br', 'en-ca',
                            'fr-ca', 'fr-ch', 'es-co', 'en-gb', 'en-hk', 'zh-hk', 'en-ie',
                            'en-in', 'fr-lu', 'es-mx', 'en-nz', 'en-sg', 'zh-sg',
                            'es-us', 'zh-tw', 'en-za'].indexOf(locale.toLowerCase()) < 0) {
                        locale = language;
                    }

                    if (language !== "en") {
                        console.log('Setting interface language to: ' + language);
                        $translate.use(language);
                    }

                    console.log('Setting locale to: ' + locale);
                    tmhDynamicLocale.set(locale.toLowerCase());

                    deferred.resolve(locale);
                }, function(error) {
                    console.warn('Couldn\'t retrieve locale settings. Setting default to "en-US"');
                    locale = 'en-US';
                    deferred.resolve(locale);
                });
            }

            return deferred.promise;
        }

        /**
         * Sends POST request to openHAB REST
         * voice interpreters
         * @param  {string} text - STT output
         */
        function sendVoice(text) {
            $http({
                method : 'POST',
                url    : '/rest/voice/interpreters',
                data   : text,
                headers: { 'Content-Type': 'text/plain' }
            }).then(function (data) {
                console.log('Voice command sent: "' + text + '"');
            }, function(error) {
                console.error('Error occured while sending voice command.');
            });
        }

        function reloadItems() {
            loadItems();
        }

        function registerEventSource() {
            if (typeof(EventSource) !== "undefined") {
                var source = new EventSource('/rest/events');
                liveUpdatesEnabled = true;

                source.onmessage = function (event) {
                    try {
                        var evtdata = JSON.parse(event.data);
                        var topicparts = evtdata.topic.split('/');

                        if (evtdata.type === 'ItemStateEvent' || evtdata.type === 'ItemStateChangedEvent' || evtdata.type === 'GroupItemStateChangedEvent') {
                            var payload = JSON.parse(evtdata.payload);
                            var newstate = payload.value;
                            var item = $filter('filter')($rootScope.items, {name: topicparts[2]}, true)[0];
                            if (item && item.state !== payload.value) {
                                $rootScope.$apply(function () {
                                    console.log("Updating " + item.name + " state from " + item.state + " to " + payload.value);
                                    item.state = payload.value;

                                    if (!item.transformedState) {
                                        // no transformation on state
                                        $rootScope.$emit('openhab-update', item);

                                        if (item.state && $rootScope.settings.speech_synthesis_item === item.name) {
                                            console.log('Speech synthesis item state changed! Speaking it now.');
                                            SpeechService.speak($rootScope.settings.speech_synthesis_voice, item.state);
                                        }
                                        if (item.state && $rootScope.settings.dashboard_control_item === item.name) {
                                            console.log('Dashboard control item state changed, attempting navigation to: ' + item.state);
                                            $location.url('/view/' + item.state);
                                        }
                                    } else {
                                        // fetch the new transformed state
                                        $http.get('/rest/items/' + item.name).then(function (response) {
                                            if (response.data && response.data.transformedState) {
                                                item.transformedState = response.data.transformedState;
                                                $rootScope.$emit('openhab-update', item);
                                            } else {
                                                console.error("Failed to retrieve the new transformedState of item: " + item.name);
                                                item.transformedState = null;
                                                $rootScope.$emit('openhab-update', item);
                                            }
                                        });
                                    }

                                });
                            }
                        } else if (evtdata.topic === "smarthome/webaudio/playurl") {
                            var context, audioBuffer;
                            try {
                                window.AudioContext = window.AudioContext || window.webkitAudioContext;
                                if (typeof (window.AudioContext) != "undefined") {
                                    context = new AudioContext();
                                }

                                var audioUrl = JSON.parse(evtdata.payload);
                                console.log("Audio event received: playing " + audioUrl);

                                if (prevAudioUrl !== audioUrl) {
                                    if (context) {
                                        $http({
                                            url : audioUrl,
                                            method : 'GET',
                                            responseType : 'arraybuffer'
                                        }).then(function(response) {
                                            context.decodeAudioData(response.data, function(buffer) {
                                                audioBuffer = buffer;
                                                var source = context.createBufferSource();
                                                source.buffer = buffer;
                                                source.connect(context.destination);
                                                source.onended = function () {
                                                    context.close();
                                                }
                                                source.start(0);
                                            });
                                        });
                                    } else {
                                        if (!angular.element(document).find("bgsound").length) {
                                            angular.element(document).find("body").append("<bgsound loop='1' />");
                                        }

                                        angular.element(document).find("bgsound").attr('src', audioUrl);
                                    }
                                    prevAudioUrl = audioUrl;
                                }
                            }
                            catch (e) {
                                console.warn("Error while handling audio event: " + e.toString());
                                if (context)
                                  context.close();
                            }
                        }
                    } catch (e) {
                        console.warn('SSE event issue: ' + e.message);
                    }
                }
                source.onerror = function (event) {
                    console.error('SSE error, closing EventSource');
                    liveUpdatesEnabled = false;
                    this.close();
                    $timeout(loadItems, 5000);
                }
            }
        }

    }

    OH2StorageService.$inject = ['OH2ServiceConfiguration', '$rootScope', '$http', '$q', 'localStorageService'];
    function OH2StorageService(OH2ServiceConfiguration, $rootScope, $http, $q, localStorageService) {
        var SERVICE_NAME = 'org.openhab.habpanel';

        this.tryGetServiceConfiguration = tryGetServiceConfiguration;
        this.saveServiceConfiguration = saveServiceConfiguration;
        this.saveCurrentPanelConfig = saveCurrentPanelConfig;
        this.setCurrentPanelConfig = setCurrentPanelConfig;
        this.getCurrentPanelConfig = getCurrentPanelConfig;
        this.useCurrentPanelConfig = useCurrentPanelConfig;
        this.useLocalStorage = useLocalStorage;

        function tryGetServiceConfiguration() {
            var deferred = $q.defer();

            $http.get('/rest/services/' + SERVICE_NAME + '/config').then(function (resp) {
                console.log('openHAB 2 service configuration loaded');
                OH2ServiceConfiguration = resp.data;
                if (!OH2ServiceConfiguration.panelsRegistry) {
                    $rootScope.panelsRegistry = OH2ServiceConfiguration.panelsRegistry = {};
                } else {
                    $rootScope.panelsRegistry = JSON.parse(resp.data.panelsRegistry);
                }
                if (OH2ServiceConfiguration.lockEditing === true) {
                    $rootScope.lockEditing = true;
                }
                // iterate over the config to find widgets added there
                $rootScope.configWidgets = {};
                angular.forEach(OH2ServiceConfiguration, function (value, key) {
                    if (key.indexOf("widget.") === 0) {
                        var widgetname = key.replace("widget.", "");
                        console.log("Adding widget from configuration: " + widgetname);
                        $rootScope.configWidgets[widgetname] = JSON.parse(value);
                    }
                });

                deferred.resolve();

            }, function (err) {
                console.error('Cannot load openHAB 2 service configuration: ' + JSON.stringify(err));

                deferred.reject();
            });

            return deferred.promise;
        }

        function saveServiceConfiguration() {
            var deferred = $q.defer();

            if ($rootScope.panelsRegistry) {
                OH2ServiceConfiguration.panelsRegistry = JSON.stringify($rootScope.panelsRegistry, null, 4);
            }

            $http({
                method: 'PUT',
                url: '/rest/services/' + SERVICE_NAME + '/config',
                data: OH2ServiceConfiguration,
                headers: { 'Content-Type': 'application/json' }
            }).then (function (resp) {
                console.log('openHAB 2 service configuration saved');
                deferred.resolve();
            }, function (err) {
                console.error('Error while saving openHAB 2 service configuration: ' + JSON.stringify(err));
                deferred.reject();
            });

            return deferred.promise;

        }

        function saveCurrentPanelConfig() {
            var deferred = $q.defer();

            var lastUpdatedTime = $rootScope.panelsRegistry[getCurrentPanelConfig()].updatedTime;

            // fetch the current configuration again (to perform optimistic concurrency on the current panel config only)
            tryGetServiceConfiguration().then(function () {
                var config = $rootScope.panelsRegistry[getCurrentPanelConfig()];
                if (!config) {
                    console.warn('Warning: creating new panel config!');
                    config = $rootScope.panelsRegistry[getCurrentPanelConfig()] = { };
                }
                var currentUpdatedTime = config.updatedTime;
                if (Date.parse(currentUpdatedTime) > Date.parse(lastUpdatedTime)) {
                    deferred.reject('Panel configuration has a newer version on the server updated on ' + currentUpdatedTime);
                    return;
                }
                config.updatedTime = new Date().toISOString();
                config.dashboards = angular.copy($rootScope.dashboards);
                config.menucolumns = $rootScope.menucolumns;
                config.settings = $rootScope.settings;
                config.customwidgets = $rootScope.customwidgets;
                return saveServiceConfiguration().then(function () {
                    deferred.resolve();
                }, function () {
                    deferred.reject();
                });
            });

            return deferred.promise;
        }

        function useLocalStorage() {
            $rootScope.currentPanelConfig = undefined;
            localStorageService.set("currentPanelConfig", $rootScope.currentPanelConfig);
        }

        function getCurrentPanelConfig() {
            if (!$rootScope.currentPanelConfig) {
                $rootScope.currentPanelConfig = localStorageService.get("currentPanelConfig");

                if (!$rootScope.currentPanelConfig) {
                    // if it's still not set and we have an initial panel config, switch to it
                    var initialPanelConfig = OH2ServiceConfiguration.initialPanelConfig;
                    if (initialPanelConfig && $rootScope.panelsRegistry[initialPanelConfig]) {
                        $rootScope.currentPanelConfig = initialPanelConfig;
                        localStorageService.set("currentPanelConfig", initialPanelConfig);
                    }
                }
            }

            return $rootScope.currentPanelConfig;
        }

        function useCurrentPanelConfig() {
            var currentPanelConfig = getCurrentPanelConfig();
            if (!currentPanelConfig || !$rootScope.panelsRegistry[currentPanelConfig]) {
                console.warn("Warning: current panel config not found, falling back to local storage!");
                useLocalStorage();
            } else {
                if ($rootScope.panelsRegistry[currentPanelConfig].dashboards)
                    $rootScope.dashboards = angular.copy($rootScope.panelsRegistry[currentPanelConfig].dashboards);
                else
                    $rootScope.dashboards = [];
                if ($rootScope.panelsRegistry[currentPanelConfig].menucolumns)
                    $rootScope.menucolumns = $rootScope.panelsRegistry[currentPanelConfig].menucolumns;
                else
                    $rootScope.menucolumns = 1;
                if ($rootScope.panelsRegistry[currentPanelConfig].settings)
                    $rootScope.settings = $rootScope.panelsRegistry[currentPanelConfig].settings;
                else
                    $rootScope.settings = {};
                if ($rootScope.panelsRegistry[currentPanelConfig].customwidgets)
                    $rootScope.customwidgets = $rootScope.panelsRegistry[currentPanelConfig].customwidgets;
                else
                    $rootScope.customwidgets = {};
            }
        }

        function setCurrentPanelConfig(name) {
            $rootScope.currentPanelConfig = name;
            localStorageService.set("currentPanelConfig", $rootScope.currentPanelConfig);
            useCurrentPanelConfig();
        }
    }
})();
<commit_msg>Pass unhandled openHAB events on for other components to use (#315)<commit_after>(function() {
'use strict';

    angular
        .module('app.services')
        .service('OHService', OHService)
        .value('OH2ServiceConfiguration', {})
        .service('OH2StorageService', OH2StorageService);

    OHService.$inject = ['$rootScope', '$http', '$q', '$timeout', '$interval', '$filter', '$location', 'SpeechService', 'tmhDynamicLocale', '$translate'];
    function OHService($rootScope, $http, $q, $timeout, $interval, $filter, $location, SpeechService, tmhDynamicLocale, $translate) {
        this.getItem = getItem;
        this.getItems = getItems;
        this.getLocale = getLocale;
        this.onUpdate = onUpdate;
        this.sendCmd = sendCmd;
        this.sendVoice = sendVoice;
        this.reloadItems = reloadItems;

        var liveUpdatesEnabled = false, prevAudioUrl = '', locale = null, eventSource = null;

        ////////////////

        function onUpdate(scope, name, callback) {
            var handler = $rootScope.$on('openhab-update', callback);
            scope.$on('$destroy', handler);
            //watchItem(name);
            //longPollUpdates(name);
        }

        function loadItems() {
            $http.get('/rest/items')
            .then(function (data) {
                if (angular.isArray(data.data)) {
                    console.log("Loaded " + data.data.length + " openHAB items");
                    $rootScope.reconnecting = false;
                    $rootScope.items = data.data;
                    if (!liveUpdatesEnabled) registerEventSource();
                } else {
                    console.warn("Items not found? Retrying in 5 seconds");
                    $rootScope.reconnecting = true;
                    $rootScope.items = [];
                    $timeout(loadItems, 5000);
                }
                $rootScope.$emit('openhab-update');
            },
            function (err) {
                console.warn("Error loading openHAB items... retrying in 5 seconds");
                $rootScope.reconnecting = true;
                $timeout(loadItems, 5000);
            });
        }

        function getItem(name) {
            var item = $filter('filter')($rootScope.items, {name: name}, true);
            return (item) ? item[0] : null;
        }

        function getItems() {
            return $rootScope.items;
        }

        /**
         * Sends command to openHAB
         * @param  {string} item Item's id
         * @param  {string} cmd  Command
         */
        function sendCmd(item, cmd) {
            $http({
                method : 'POST',
                url    : '/rest/items/' + item,
                data   : cmd,
                headers: { 'Content-Type': 'text/plain' }
            }).then(function (data) {
                console.log('Command sent: ' + item + '=' + cmd);

                // should be handled by server push messages but their delivery is erratic
                // so perform a full refresh every time a command is sent
                //loadItems();
            });
        }

        /**
         * Returns a promise with the configured locale
         */
        function getLocale() {
            var deferred = $q.defer();

            if (locale) {
                deferred.resolve(locale);
            } else {
                $http.get('/rest/services/org.eclipse.smarthome.core.i18nprovider/config')
                .then(function (response) {
                    var language;
                    if (!response.data.language) {
                        if (navigator && navigator.languages) {
                            locale = navigator.languages[0];
                            language = locale.split('-')[0];
                        } else if (navigator && navigator.language) {
                            locale = navigator.language;
                            language = locale.split('-')[0];
                        } else {
                            locale = language = 'en';
                        }
                    } else {
                        language = response.data.language;
                        locale = response.data.language + ((response.data.region) ? '-' + response.data.region : '');
                    }

                    /* consider the region only for selected common exceptions where the date/number formats
                        are significantly different than the language's default.
                        If more are needed change the gulpfile.js too and run the 'vendor-angular-i18n' gulp task */
                    if (['es-ar', 'de-at', 'en-au', 'fr-be', 'es-bo', 'pt-br', 'en-ca',
                            'fr-ca', 'fr-ch', 'es-co', 'en-gb', 'en-hk', 'zh-hk', 'en-ie',
                            'en-in', 'fr-lu', 'es-mx', 'en-nz', 'en-sg', 'zh-sg',
                            'es-us', 'zh-tw', 'en-za'].indexOf(locale.toLowerCase()) < 0) {
                        locale = language;
                    }

                    if (language !== "en") {
                        console.log('Setting interface language to: ' + language);
                        $translate.use(language);
                    }

                    console.log('Setting locale to: ' + locale);
                    tmhDynamicLocale.set(locale.toLowerCase());

                    deferred.resolve(locale);
                }, function(error) {
                    console.warn('Couldn\'t retrieve locale settings. Setting default to "en-US"');
                    locale = 'en-US';
                    deferred.resolve(locale);
                });
            }

            return deferred.promise;
        }

        /**
         * Sends POST request to openHAB REST
         * voice interpreters
         * @param  {string} text - STT output
         */
        function sendVoice(text) {
            $http({
                method : 'POST',
                url    : '/rest/voice/interpreters',
                data   : text,
                headers: { 'Content-Type': 'text/plain' }
            }).then(function (data) {
                console.log('Voice command sent: "' + text + '"');
            }, function(error) {
                console.error('Error occured while sending voice command.');
            });
        }

        function reloadItems() {
            loadItems();
        }

        function registerEventSource() {
            if (typeof(EventSource) !== "undefined") {
                var source = new EventSource('/rest/events');
                liveUpdatesEnabled = true;

                source.onmessage = function (event) {
                    try {
                        var evtdata = JSON.parse(event.data);
                        var topicparts = evtdata.topic.split('/');

                        if (evtdata.type === 'ItemStateEvent' || evtdata.type === 'ItemStateChangedEvent' || evtdata.type === 'GroupItemStateChangedEvent') {
                            var payload = JSON.parse(evtdata.payload);
                            var newstate = payload.value;
                            var item = $filter('filter')($rootScope.items, {name: topicparts[2]}, true)[0];
                            if (item && item.state !== payload.value) {
                                $rootScope.$apply(function () {
                                    console.log("Updating " + item.name + " state from " + item.state + " to " + payload.value);
                                    item.state = payload.value;

                                    if (!item.transformedState) {
                                        // no transformation on state
                                        $rootScope.$emit('openhab-update', item);

                                        if (item.state && $rootScope.settings.speech_synthesis_item === item.name) {
                                            console.log('Speech synthesis item state changed! Speaking it now.');
                                            SpeechService.speak($rootScope.settings.speech_synthesis_voice, item.state);
                                        }
                                        if (item.state && $rootScope.settings.dashboard_control_item === item.name) {
                                            console.log('Dashboard control item state changed, attempting navigation to: ' + item.state);
                                            $location.url('/view/' + item.state);
                                        }
                                    } else {
                                        // fetch the new transformed state
                                        $http.get('/rest/items/' + item.name).then(function (response) {
                                            if (response.data && response.data.transformedState) {
                                                item.transformedState = response.data.transformedState;
                                                $rootScope.$emit('openhab-update', item);
                                            } else {
                                                console.error("Failed to retrieve the new transformedState of item: " + item.name);
                                                item.transformedState = null;
                                                $rootScope.$emit('openhab-update', item);
                                            }
                                        });
                                    }

                                });
                            }
                        } else if (evtdata.topic === "smarthome/webaudio/playurl") {
                            var context, audioBuffer;
                            try {
                                window.AudioContext = window.AudioContext || window.webkitAudioContext;
                                if (typeof (window.AudioContext) != "undefined") {
                                    context = new AudioContext();
                                }

                                var audioUrl = JSON.parse(evtdata.payload);
                                console.log("Audio event received: playing " + audioUrl);

                                if (prevAudioUrl !== audioUrl) {
                                    if (context) {
                                        $http({
                                            url : audioUrl,
                                            method : 'GET',
                                            responseType : 'arraybuffer'
                                        }).then(function(response) {
                                            context.decodeAudioData(response.data, function(buffer) {
                                                audioBuffer = buffer;
                                                var source = context.createBufferSource();
                                                source.buffer = buffer;
                                                source.connect(context.destination);
                                                source.onended = function () {
                                                    context.close();
                                                }
                                                source.start(0);
                                            });
                                        });
                                    } else {
                                        if (!angular.element(document).find("bgsound").length) {
                                            angular.element(document).find("body").append("<bgsound loop='1' />");
                                        }

                                        angular.element(document).find("bgsound").attr('src', audioUrl);
                                    }
                                    prevAudioUrl = audioUrl;
                                }
                            }
                            catch (e) {
                                console.warn("Error while handling audio event: " + e.toString());
                                if (context)
                                  context.close();
                            }
                        } else {
                            var payload = JSON.parse(evtdata.payload);
                            var ohEvent = { topic: evtdata.topic, type: evtdata.type, payload: payload };
                            $rootScope.$apply(function () {
                                console.log("Emitting event type=" + ohEvent.type + ", topic=" + ohEvent.topic);
                                $rootScope.$emit('openhab-event', ohEvent);
                            });
                        }
                    } catch (e) {
                        console.warn('SSE event issue: ' + e.message);
                    }
                }
                source.onerror = function (event) {
                    console.error('SSE error, closing EventSource');
                    liveUpdatesEnabled = false;
                    this.close();
                    $timeout(loadItems, 5000);
                }
            }
        }

    }

    OH2StorageService.$inject = ['OH2ServiceConfiguration', '$rootScope', '$http', '$q', 'localStorageService'];
    function OH2StorageService(OH2ServiceConfiguration, $rootScope, $http, $q, localStorageService) {
        var SERVICE_NAME = 'org.openhab.habpanel';

        this.tryGetServiceConfiguration = tryGetServiceConfiguration;
        this.saveServiceConfiguration = saveServiceConfiguration;
        this.saveCurrentPanelConfig = saveCurrentPanelConfig;
        this.setCurrentPanelConfig = setCurrentPanelConfig;
        this.getCurrentPanelConfig = getCurrentPanelConfig;
        this.useCurrentPanelConfig = useCurrentPanelConfig;
        this.useLocalStorage = useLocalStorage;

        function tryGetServiceConfiguration() {
            var deferred = $q.defer();

            $http.get('/rest/services/' + SERVICE_NAME + '/config').then(function (resp) {
                console.log('openHAB 2 service configuration loaded');
                OH2ServiceConfiguration = resp.data;
                if (!OH2ServiceConfiguration.panelsRegistry) {
                    $rootScope.panelsRegistry = OH2ServiceConfiguration.panelsRegistry = {};
                } else {
                    $rootScope.panelsRegistry = JSON.parse(resp.data.panelsRegistry);
                }
                if (OH2ServiceConfiguration.lockEditing === true) {
                    $rootScope.lockEditing = true;
                }
                // iterate over the config to find widgets added there
                $rootScope.configWidgets = {};
                angular.forEach(OH2ServiceConfiguration, function (value, key) {
                    if (key.indexOf("widget.") === 0) {
                        var widgetname = key.replace("widget.", "");
                        console.log("Adding widget from configuration: " + widgetname);
                        $rootScope.configWidgets[widgetname] = JSON.parse(value);
                    }
                });

                deferred.resolve();

            }, function (err) {
                console.error('Cannot load openHAB 2 service configuration: ' + JSON.stringify(err));

                deferred.reject();
            });

            return deferred.promise;
        }

        function saveServiceConfiguration() {
            var deferred = $q.defer();

            if ($rootScope.panelsRegistry) {
                OH2ServiceConfiguration.panelsRegistry = JSON.stringify($rootScope.panelsRegistry, null, 4);
            }

            $http({
                method: 'PUT',
                url: '/rest/services/' + SERVICE_NAME + '/config',
                data: OH2ServiceConfiguration,
                headers: { 'Content-Type': 'application/json' }
            }).then (function (resp) {
                console.log('openHAB 2 service configuration saved');
                deferred.resolve();
            }, function (err) {
                console.error('Error while saving openHAB 2 service configuration: ' + JSON.stringify(err));
                deferred.reject();
            });

            return deferred.promise;

        }

        function saveCurrentPanelConfig() {
            var deferred = $q.defer();

            var lastUpdatedTime = $rootScope.panelsRegistry[getCurrentPanelConfig()].updatedTime;

            // fetch the current configuration again (to perform optimistic concurrency on the current panel config only)
            tryGetServiceConfiguration().then(function () {
                var config = $rootScope.panelsRegistry[getCurrentPanelConfig()];
                if (!config) {
                    console.warn('Warning: creating new panel config!');
                    config = $rootScope.panelsRegistry[getCurrentPanelConfig()] = { };
                }
                var currentUpdatedTime = config.updatedTime;
                if (Date.parse(currentUpdatedTime) > Date.parse(lastUpdatedTime)) {
                    deferred.reject('Panel configuration has a newer version on the server updated on ' + currentUpdatedTime);
                    return;
                }
                config.updatedTime = new Date().toISOString();
                config.dashboards = angular.copy($rootScope.dashboards);
                config.menucolumns = $rootScope.menucolumns;
                config.settings = $rootScope.settings;
                config.customwidgets = $rootScope.customwidgets;
                return saveServiceConfiguration().then(function () {
                    deferred.resolve();
                }, function () {
                    deferred.reject();
                });
            });

            return deferred.promise;
        }

        function useLocalStorage() {
            $rootScope.currentPanelConfig = undefined;
            localStorageService.set("currentPanelConfig", $rootScope.currentPanelConfig);
        }

        function getCurrentPanelConfig() {
            if (!$rootScope.currentPanelConfig) {
                $rootScope.currentPanelConfig = localStorageService.get("currentPanelConfig");

                if (!$rootScope.currentPanelConfig) {
                    // if it's still not set and we have an initial panel config, switch to it
                    var initialPanelConfig = OH2ServiceConfiguration.initialPanelConfig;
                    if (initialPanelConfig && $rootScope.panelsRegistry[initialPanelConfig]) {
                        $rootScope.currentPanelConfig = initialPanelConfig;
                        localStorageService.set("currentPanelConfig", initialPanelConfig);
                    }
                }
            }

            return $rootScope.currentPanelConfig;
        }

        function useCurrentPanelConfig() {
            var currentPanelConfig = getCurrentPanelConfig();
            if (!currentPanelConfig || !$rootScope.panelsRegistry[currentPanelConfig]) {
                console.warn("Warning: current panel config not found, falling back to local storage!");
                useLocalStorage();
            } else {
                if ($rootScope.panelsRegistry[currentPanelConfig].dashboards)
                    $rootScope.dashboards = angular.copy($rootScope.panelsRegistry[currentPanelConfig].dashboards);
                else
                    $rootScope.dashboards = [];
                if ($rootScope.panelsRegistry[currentPanelConfig].menucolumns)
                    $rootScope.menucolumns = $rootScope.panelsRegistry[currentPanelConfig].menucolumns;
                else
                    $rootScope.menucolumns = 1;
                if ($rootScope.panelsRegistry[currentPanelConfig].settings)
                    $rootScope.settings = $rootScope.panelsRegistry[currentPanelConfig].settings;
                else
                    $rootScope.settings = {};
                if ($rootScope.panelsRegistry[currentPanelConfig].customwidgets)
                    $rootScope.customwidgets = $rootScope.panelsRegistry[currentPanelConfig].customwidgets;
                else
                    $rootScope.customwidgets = {};
            }
        }

        function setCurrentPanelConfig(name) {
            $rootScope.currentPanelConfig = name;
            localStorageService.set("currentPanelConfig", $rootScope.currentPanelConfig);
            useCurrentPanelConfig();
        }
    }
})();
<|endoftext|>


<commit_before>import {allValuesTypes, testAllValues} from "unit-tests-values-iterators"

import {createStore, unlinkStore, createModule, RMCCtl} from "../src";

const VALID_CLASS = class SCtl extends RMCCtl {
  _stateDidUpdate() {}
};
const MODULE_REDUCER = () => {
  return {
    foo: 'bar',
    bar: 'foo',
  };
};

function creator(reducer, ctl, modulePath = "modulePath") {
  const module = createModule(reducer, ctl);

  function rootReducer(state = {}, action) {
    return {
      [modulePath]: module.integrator(modulePath)(state[modulePath], action),
    };
  }

  createStore(rootReducer);

  return module;
}

describe("module", () => {
  beforeEach(() => {
    try{
      unlinkStore();
    } catch(e) {}
  });

  it("should have access to the controller`s methods and properties directly", () => {
    class Ctl extends VALID_CLASS {
      getter0() {
        return 0;
      }

      setter1(value) {
        this.value1 = value;
      }

      getter1() {
        return this.value1;
      }
    }

    const module = creator(() => {}, Ctl);

    expect(module.getter0()).toBe(0);

    module.setter1(1);
    expect(module.getter1()).toBe(1);

    module.value1 = 2;
    expect(module.getter1()).toBe(2);
  });

  it("should use Module`s method if same named public methods exists in a module and a controller", () => {
    const integrator = jest.fn();

    class Ctl extends VALID_CLASS {
      integrator(...args) {
        integrator(...args);
      }
    }

    creator(() => {}, Ctl);

    expect(integrator).toHaveBeenCalledTimes(0);
  });

  it("should have access to controller`s private methods from outside", () => {
    const someFunc = jest.fn();

    class Ctl extends VALID_CLASS {
      _someMethod() {
        someFunc();
      }
    }
    const module = creator(() => {}, Ctl);
    module._someMethod();

    expect(someFunc).toHaveBeenCalled();
  });

  it("should have access to controller`s protected method from inside another controller`s methods", () => {
    const someFunc = jest.fn();

    class Ctl extends VALID_CLASS {
      someMethod() {
        this._protectedMethod();
      }

      _protectedMethod() {
        someFunc();
      }
    }
    const module = creator(() => {}, Ctl);

    module.someMethod();

    expect(someFunc).toHaveBeenCalled();
  });

  it("should have access to own state inside controller`s methods", () => {
    const ownState = {
      foo: 'bar',
      bar: 'foo',
    };
    const reducer = (state = {}, action) => {
      return ownState;
    };

    class Ctl extends VALID_CLASS {
      getter() {
        return this.ownState;
      }
    }

    const module = creator(reducer, Ctl);

    expect(module.getter()).toBe(ownState);
  });
});

describe('module.integrator()', () => {
  testAllValues((path, type) => {
    const desc_100 = `should throw an error if single argument "${path}" of type` +
      ` "${type}" is not compatible with path type`;
    it(desc_100, () => {
      const module = createModule(() => {}, VALID_CLASS);

      expect(() => {
        module.integrator(path);
      }).toThrow();
    });
  }, {exclude: [allValuesTypes.STRING, allValuesTypes.ARRAY]});

  it('should throw an error if path is empty string', () => {
    const module = createModule(MODULE_REDUCER, VALID_CLASS);

    expect(() => {
      module.integrator('');
    }).toThrow();
  });

  it('should throw an error if path is empty array', () => {
    const module = createModule(MODULE_REDUCER, VALID_CLASS);

    expect(() => {
      module.integrator([]);
    }).toThrow();
  });

  it('should throw an error if path is array with at least one nonestring value', () => {
    const module = createModule(MODULE_REDUCER, VALID_CLASS);

    expect(() => {
      module.integrator(['foo', 1, 'bar']);
    }).toThrow();
  });

  it("should throw an error if path is changed", () => {
    const module = createModule(MODULE_REDUCER, VALID_CLASS);

    module.integrator('path0');

    expect(() => {
      module.integrator('path1');
    }).toThrow();
  });

  it('should return a function the same as a reducer in the arguments list', () => {
    const module = createModule(MODULE_REDUCER, VALID_CLASS);
    const resultReducer = module.integrator('path');

    expect(resultReducer).toBe(MODULE_REDUCER);
  });
});

describe('module.dispatch', () => {
  const action0 = 'action0';
  const action1 = 'action1';
  const payload0 = 'payload0';
  const payload1 = 'payload1';
  const initialData = 'initialData';

  it("does not affect deinitialized module", () => {
    const stateDidUpdate = jest.fn();
    let module;

    beforeAll(() => {
      try{
        unlinkStore();
      } catch(e) {}

      function reducer(state = initialData, action) {
        switch(action.type) {
          case action0:
            return action.payload;

          default:
            return state;
        }
      }
      class Ctl extends RMCCtl {
        _stateDidUpdate(...args) {
          stateDidUpdate(...args);
        }
      }
      module = creator(reducer, Ctl);

      module.deinitialize();
      module.dispatch({type: action0, payload: payload0});
    })

    expect.assertions(1);
    expect(stateDidUpdate).toHaveBeenCalledTimes(0);
  });

  describe('called single time', () => {
    const stateDidUpdate = jest.fn();
    let module;

    beforeAll(() => {
      try{
        unlinkStore();
      } catch(e) {}

      function reducer(state = initialData, action) {
        switch (action.type) {
          case action0:
            return action.payload;

          default:
            return state;
        }
      }
      class Ctl extends RMCCtl {
        _stateDidUpdate(...args) {
          stateDidUpdate(...args);
        }
      }
      module = creator(reducer, Ctl);

      module.dispatch({type: action0, payload: payload0});
    });

    it('should call `_stateDidUpdate`', () => {
      expect.assertions(1);
      expect(stateDidUpdate).toHaveBeenCalledTimes(1);
    });

    it('should call `_stateDidUpdate` with previousState', () => {
      expect.assertions(1);
      expect(stateDidUpdate).toHaveBeenCalledWith(initialData);
    });

    it('should change own state property to new state', () => {
      expect.assertions(1);
      expect(module.ownState).toBe(payload0);
    });
  });

  describe('called several times with a same action but different payloads', () => {
    const stateDidUpdate = jest.fn();
    let module;

    beforeAll(() => {
      try{
        unlinkStore();
      } catch(e) {}

      function reducer(state = initialData, action) {
        switch(action.type) {
          case action0:
            return action.payload;

          default:
            return state;
        }
      }
      class Ctl extends RMCCtl {
        _stateDidUpdate(...args) {
          stateDidUpdate(...args);
        }
      }
      module = creator(reducer, Ctl);

      module.dispatch({type: action0, payload: payload0});
      module.dispatch({type: action0, payload: payload1});
    });

    it('should call `stateDidUpdate` each time', () => {
      expect.assertions(1);
      expect(stateDidUpdate).toHaveBeenCalledTimes(2);
    });

    it('should call `stateDidUpdate` with a result of handling a last action', () => {
      expect.assertions(1);
      expect(stateDidUpdate).toHaveBeenLastCalledWith(payload0); // previous state
    });

    it('should change own state property a result of handling a last action', () => {
      expect.assertions(1);
      expect(module.ownState).toBe(payload1);
    });
  });

  describe('called several times with totally same actions including payloads', () => {
    const stateDidUpdate = jest.fn();
    let module;

    beforeAll(() => {
      try{
        unlinkStore();
      } catch(e) {}

      function reducer(state = initialData, action) {
        switch(action.type) {
          case action0:
            return action.payload;

          default:
            return state;
        }
      }
      class Ctl extends RMCCtl {
        _stateDidUpdate(...args) {
          stateDidUpdate(...args);
        }
      }
      module = creator(reducer, Ctl);

      module.dispatch({type: action0, payload: payload0});
      module.dispatch({type: action0, payload: payload0});
    });

    it('should call `stateDidUpdate` just single time', () => {
      expect.assertions(1);
      expect(stateDidUpdate).toHaveBeenCalledTimes(1);
    });
  });

  describe('called with an action that shouldn`t affect module`s state', () => {
    const stateDidUpdate = jest.fn();
    let module;

    beforeAll(() => {
      try {
        unlinkStore();
      } catch (e) {}

      function reducer(state = initialData, action) {
        switch(action.type) {
          case action0:
            return action.payload;

          default:
            return state;
        }
      }
      class Ctl extends RMCCtl {
        _stateDidUpdate(...args) {
          stateDidUpdate(...args);
        }
      }
      module = creator(reducer, Ctl);

      module.dispatch({type: action1, payload: payload1});
    });

    it('should not call `stateDidUpdate` at all', () => {
      expect.assertions(1);
      expect(stateDidUpdate).toHaveBeenCalledTimes(0);
    });
  });
});
<commit_msg>Add test for the case from the issue #2.<commit_after>import {allValuesTypes, testAllValues} from "unit-tests-values-iterators"
import {cloneDeep} from 'lodash';

import {createStore, unlinkStore, createModule, RMCCtl} from "../src";

const VALID_CLASS = class SCtl extends RMCCtl {
  _stateDidUpdate() {}
};
const MODULE_REDUCER = () => {
  return {
    foo: 'bar',
    bar: 'foo',
  };
};

function creator(reducer, ctl, modulePath = "modulePath") {
  const module = createModule(reducer, ctl);

  function rootReducer(state = {}, action) {
    return {
      [modulePath]: module.integrator(modulePath)(state[modulePath], action),
    };
  }

  createStore(rootReducer);

  return module;
}

describe("module", () => {
  beforeEach(() => {
    try{
      unlinkStore();
    } catch(e) {}
  });

  it("should have access to the controller`s methods and properties directly", () => {
    class Ctl extends VALID_CLASS {
      getter0() {
        return 0;
      }

      setter1(value) {
        this.value1 = value;
      }

      getter1() {
        return this.value1;
      }
    }

    const module = creator(() => {}, Ctl);

    expect(module.getter0()).toBe(0);

    module.setter1(1);
    expect(module.getter1()).toBe(1);

    module.value1 = 2;
    expect(module.getter1()).toBe(2);
  });

  it("should use Module`s method if same named public methods exists in a module and a controller", () => {
    const integrator = jest.fn();

    class Ctl extends VALID_CLASS {
      integrator(...args) {
        integrator(...args);
      }
    }

    creator(() => {}, Ctl);

    expect(integrator).toHaveBeenCalledTimes(0);
  });

  it("should have access to controller`s private methods from outside", () => {
    const someFunc = jest.fn();

    class Ctl extends VALID_CLASS {
      _someMethod() {
        someFunc();
      }
    }
    const module = creator(() => {}, Ctl);
    module._someMethod();

    expect(someFunc).toHaveBeenCalled();
  });

  it("should have access to controller`s protected method from inside another controller`s methods", () => {
    const someFunc = jest.fn();

    class Ctl extends VALID_CLASS {
      someMethod() {
        this._protectedMethod();
      }

      _protectedMethod() {
        someFunc();
      }
    }
    const module = creator(() => {}, Ctl);

    module.someMethod();

    expect(someFunc).toHaveBeenCalled();
  });

  it("should have access to get own state inside controller`s methods", () => {
    const ownState = {
      foo: 'bar',
      bar: 'foo',
    };
    const reducer = (state = {}, action) => {
      return ownState;
    };

    class Ctl extends VALID_CLASS {
      getter() {
        return this.ownState;
      }
    }

    const module = creator(reducer, Ctl);

    expect(module.getter()).toEqual(ownState);
  });

  it("should haven`t access to change original own state", () => {
    const ownState = {
      foo: {
        bar: 'baz',
      },
    };
    const expectedState = cloneDeep(ownState);
    const reducer = (state = {}, action) => {
      return ownState;
    };
    class Ctl extends VALID_CLASS {
      getState() {
        return this.ownState;
      }
    }
    const module = creator(reducer, Ctl);

    const firstData = module.getState();
    firstData.bar = "foo";
    firstData.foo.bar = "newBaz";

    const secondData = module.getState();

    expect(secondData).toEqual(expectedState);
  });
});

describe('module.integrator()', () => {
  testAllValues((path, type) => {
    const desc_100 = `should throw an error if single argument "${path}" of type` +
      ` "${type}" is not compatible with path type`;
    it(desc_100, () => {
      const module = createModule(() => {}, VALID_CLASS);

      expect(() => {
        module.integrator(path);
      }).toThrow();
    });
  }, {exclude: [allValuesTypes.STRING, allValuesTypes.ARRAY]});

  it('should throw an error if path is empty string', () => {
    const module = createModule(MODULE_REDUCER, VALID_CLASS);

    expect(() => {
      module.integrator('');
    }).toThrow();
  });

  it('should throw an error if path is empty array', () => {
    const module = createModule(MODULE_REDUCER, VALID_CLASS);

    expect(() => {
      module.integrator([]);
    }).toThrow();
  });

  it('should throw an error if path is array with at least one nonestring value', () => {
    const module = createModule(MODULE_REDUCER, VALID_CLASS);

    expect(() => {
      module.integrator(['foo', 1, 'bar']);
    }).toThrow();
  });

  it("should throw an error if path is changed", () => {
    const module = createModule(MODULE_REDUCER, VALID_CLASS);

    module.integrator('path0');

    expect(() => {
      module.integrator('path1');
    }).toThrow();
  });

  it('should return a function the same as a reducer in the arguments list', () => {
    const module = createModule(MODULE_REDUCER, VALID_CLASS);
    const resultReducer = module.integrator('path');

    expect(resultReducer).toBe(MODULE_REDUCER);
  });
});

describe('module.dispatch', () => {
  const action0 = 'action0';
  const action1 = 'action1';
  const payload0 = 'payload0';
  const payload1 = 'payload1';
  const initialData = 'initialData';

  it("does not affect deinitialized module", () => {
    const stateDidUpdate = jest.fn();
    let module;

    beforeAll(() => {
      try{
        unlinkStore();
      } catch(e) {}

      function reducer(state = initialData, action) {
        switch(action.type) {
          case action0:
            return action.payload;

          default:
            return state;
        }
      }
      class Ctl extends RMCCtl {
        _stateDidUpdate(...args) {
          stateDidUpdate(...args);
        }
      }
      module = creator(reducer, Ctl);

      module.deinitialize();
      module.dispatch({type: action0, payload: payload0});
    })

    expect.assertions(1);
    expect(stateDidUpdate).toHaveBeenCalledTimes(0);
  });

  describe('called single time', () => {
    const stateDidUpdate = jest.fn();
    let module;

    beforeAll(() => {
      try{
        unlinkStore();
      } catch(e) {}

      function reducer(state = initialData, action) {
        switch (action.type) {
          case action0:
            return action.payload;

          default:
            return state;
        }
      }
      class Ctl extends RMCCtl {
        _stateDidUpdate(...args) {
          stateDidUpdate(...args);
        }
      }
      module = creator(reducer, Ctl);

      module.dispatch({type: action0, payload: payload0});
    });

    it('should call `_stateDidUpdate`', () => {
      expect.assertions(1);
      expect(stateDidUpdate).toHaveBeenCalledTimes(1);
    });

    it('should call `_stateDidUpdate` with previousState', () => {
      expect.assertions(1);
      expect(stateDidUpdate).toHaveBeenCalledWith(initialData);
    });

    it('should change own state property to new state', () => {
      expect.assertions(1);
      expect(module.ownState).toBe(payload0);
    });
  });

  describe('called several times with a same action but different payloads', () => {
    const stateDidUpdate = jest.fn();
    let module;

    beforeAll(() => {
      try{
        unlinkStore();
      } catch(e) {}

      function reducer(state = initialData, action) {
        switch(action.type) {
          case action0:
            return action.payload;

          default:
            return state;
        }
      }
      class Ctl extends RMCCtl {
        _stateDidUpdate(...args) {
          stateDidUpdate(...args);
        }
      }
      module = creator(reducer, Ctl);

      module.dispatch({type: action0, payload: payload0});
      module.dispatch({type: action0, payload: payload1});
    });

    it('should call `stateDidUpdate` each time', () => {
      expect.assertions(1);
      expect(stateDidUpdate).toHaveBeenCalledTimes(2);
    });

    it('should call `stateDidUpdate` with a result of handling a last action', () => {
      expect.assertions(1);
      expect(stateDidUpdate).toHaveBeenLastCalledWith(payload0); // previous state
    });

    it('should change own state property a result of handling a last action', () => {
      expect.assertions(1);
      expect(module.ownState).toBe(payload1);
    });
  });

  describe('called several times with totally same actions including payloads', () => {
    const stateDidUpdate = jest.fn();
    let module;

    beforeAll(() => {
      try{
        unlinkStore();
      } catch(e) {}

      function reducer(state = initialData, action) {
        switch(action.type) {
          case action0:
            return action.payload;

          default:
            return state;
        }
      }
      class Ctl extends RMCCtl {
        _stateDidUpdate(...args) {
          stateDidUpdate(...args);
        }
      }
      module = creator(reducer, Ctl);

      module.dispatch({type: action0, payload: payload0});
      module.dispatch({type: action0, payload: payload0});
    });

    it('should call `stateDidUpdate` just single time', () => {
      expect.assertions(1);
      expect(stateDidUpdate).toHaveBeenCalledTimes(1);
    });
  });

  describe('called with an action that shouldn`t affect module`s state', () => {
    const stateDidUpdate = jest.fn();
    let module;

    beforeAll(() => {
      try {
        unlinkStore();
      } catch (e) {}

      function reducer(state = initialData, action) {
        switch(action.type) {
          case action0:
            return action.payload;

          default:
            return state;
        }
      }
      class Ctl extends RMCCtl {
        _stateDidUpdate(...args) {
          stateDidUpdate(...args);
        }
      }
      module = creator(reducer, Ctl);

      module.dispatch({type: action1, payload: payload1});
    });

    it('should not call `stateDidUpdate` at all', () => {
      expect.assertions(1);
      expect(stateDidUpdate).toHaveBeenCalledTimes(0);
    });
  });
});
<|endoftext|>


<commit_before>import React from 'react';
import { Grid, Row, Col, ListGroup, ListGroupItem, ProgressBar } from 'react-bootstrap';
import { Pie } from 'react-chartjs-2';

import Utility from '../../../../shared/util/Utility';
import * as Lessons from '../../../../shared/reducers/Lessons';
import * as Security from '../../../../shared/reducers/Security';

export const Reducers = [Lessons, Security];

export class homeScreen extends React.Component {
  static getChartOptions() {
    return {
      responsive: true,
      maintainAspectRatio: true,
      legend: {
        onClick: () => { /* Do nothing */ }
      },
      tooltips: {
        callbacks: {
          label(tooltipItem, data) {
            return `${data.labels[tooltipItem.index]}: ${data.datasets[0].data[tooltipItem.index]}%`;
          }
        }
      }
    };
  }

  constructor(props) {
    super(props);
    this.getChartData = this.getChartData.bind(this);
  }

  componentWillMount() {
    this.props.fetchUserSuccessRate(this.props.loggedInUser);
    this.props.fetchUserStarredLessons();
    this.props.fetchaddressedQuestionsInLessons();
  }

  getChartData() {
    const theLabels = [
      'Andel rätt svar!', 'Andel fel svar'
    ];

    return {
      labels: theLabels,
      datasets: [
        {
          label: theLabels,
          backgroundColor: [
            'rgba(130,200,130,1.0)', 'rgba(130,170,130,0.4)'
          ],
          data: [
            this.props.successRate, 100 - this.props.successRate
          ]
        }
      ]
    };
  }

  setLessonAndGo(lesson, format = 'guess') {
    this.props.setSelectedLesson(lesson);
    this.props.setPageByName(`/select/${format}`);
  }

  showFavorites() {
    const headerText = 'Dina favoritlektioner';

    if (this.props.starredLessons.length > 0 && this.props.addressedQuestionsInLessons) {
      const starredLessons = this.props.starredLessons.map((userLesson) => {
        if (userLesson.lesson.description !== 'quiz') {
          const totalWordCount = this.props.addressedQuestionsInLessons[userLesson.lesson.name][1];
          const completeCount = ((this.props.addressedQuestionsInLessons[userLesson.lesson.name][1] -
            this.props.addressedQuestionsInLessons[userLesson.lesson.name][0])
            / this.props.addressedQuestionsInLessons[userLesson.lesson.name][1]);
          const completePercentage = ((completeCount / totalWordCount) * 100).toFixed();
          return (
            <ListGroupItem
              header={`${userLesson.lesson.name}`}
              key={userLesson.lesson.name}
              onClick={() => this.setLessonAndGo(userLesson.lesson)}
            >
              <ProgressBar now={parseInt(completePercentage, 10)} label={`${completePercentage}% avklarat`} srOnly />
              Du har klarat {completeCount} av {totalWordCount} ord
            </ListGroupItem>);
        }
        return (
          <ListGroupItem
            header={`Quiz: ${userLesson.lesson.name}`}
            key={userLesson.lesson.name}
            onClick={() => this.setLessonAndGo(userLesson.lesson, 'quiz')}
          >

            <ProgressBar bsStyle="warning" now={100} srOnly />
          </ListGroupItem>);
      });

      return (

{headerText}

        <ListGroup>
          {starredLessons}
        </ListGroup>
);
    }
    return (

{headerText}

Navigera till speltyperna i menyn för att lägga till lektioner här.

);
  }

  render() {
    return (
      <Grid className="text-center">

Välkommen till Gakusei, {this.props.loggedInUser}!

Din svarsstatistik:

        <Row>
          <Col xs={12} xsOffset={0} md={6} mdOffset={3}>
            { this.props.requestingSuccessRate === false ?
              <Pie data={this.getChartData()} options={homeScreen.getChartOptions()} /> :

Loading...
 }
          </Col>
        </Row>
        {this.showFavorites()}
      </Grid>
    );
  }
}

homeScreen.defaultProps = Utility.reduxEnabledDefaultProps({

}, Reducers);

homeScreen.propTypes = Utility.reduxEnabledPropTypes({

}, Reducers);

export default Utility.superConnect(this, Reducers)(homeScreen);

<commit_msg>header caused DOMValidation issues<commit_after>import React from 'react';
import { Grid, Row, Col, ListGroup, ListGroupItem, ProgressBar } from 'react-bootstrap';
import { Pie } from 'react-chartjs-2';

import Utility from '../../../../shared/util/Utility';
import * as Lessons from '../../../../shared/reducers/Lessons';
import * as Security from '../../../../shared/reducers/Security';

export const Reducers = [Lessons, Security];

export class homeScreen extends React.Component {
  static getChartOptions() {
    return {
      responsive: true,
      maintainAspectRatio: true,
      legend: {
        onClick: () => { /* Do nothing */ }
      },
      tooltips: {
        callbacks: {
          label(tooltipItem, data) {
            return `${data.labels[tooltipItem.index]}: ${data.datasets[0].data[tooltipItem.index]}%`;
          }
        }
      }
    };
  }

  constructor(props) {
    super(props);
    this.getChartData = this.getChartData.bind(this);
  }

  componentWillMount() {
    this.props.fetchUserSuccessRate(this.props.loggedInUser);
    this.props.fetchUserStarredLessons();
    this.props.fetchaddressedQuestionsInLessons();
  }

  getChartData() {
    const theLabels = [
      'Andel rätt svar!', 'Andel fel svar'
    ];

    return {
      labels: theLabels,
      datasets: [
        {
          label: theLabels,
          backgroundColor: [
            'rgba(130,200,130,1.0)', 'rgba(130,170,130,0.4)'
          ],
          data: [
            this.props.successRate, 100 - this.props.successRate
          ]
        }
      ]
    };
  }

  setLessonAndGo(lesson, format = 'guess') {
    this.props.setSelectedLesson(lesson);
    this.props.setPageByName(`/select/${format}`);
  }

  showFavorites() {
    const headerText = 'Dina favoritlektioner';

    if (this.props.starredLessons.length > 0 && this.props.addressedQuestionsInLessons) {
      const starredLessons = this.props.starredLessons.map((userLesson) => {
        if (userLesson.lesson.description !== 'quiz') {
          const totalWordCount = this.props.addressedQuestionsInLessons[userLesson.lesson.name][1];
          const completeCount = ((this.props.addressedQuestionsInLessons[userLesson.lesson.name][1] -
            this.props.addressedQuestionsInLessons[userLesson.lesson.name][0])
            / this.props.addressedQuestionsInLessons[userLesson.lesson.name][1]);
          const completePercentage = ((completeCount / totalWordCount) * 100).toFixed();
          return (
            <ListGroupItem
              key={userLesson.lesson.name}
              onClick={() => this.setLessonAndGo(userLesson.lesson)}
            >

{userLesson.lesson.name}

              <ProgressBar now={parseInt(completePercentage, 10)} label={`${completePercentage}% avklarat`} srOnly />
              Du har klarat {completeCount} av {totalWordCount} ord
            </ListGroupItem>);
        }
        return (
          <ListGroupItem
            key={userLesson.lesson.name}
            onClick={() => this.setLessonAndGo(userLesson.lesson, 'quiz')}
          >

Quiz: {userLesson.lesson.name}

            <ProgressBar bsStyle="warning" now={100} srOnly />
          </ListGroupItem>);
      });

      return (

{headerText}

        <ListGroup>
          {starredLessons}
        </ListGroup>
);
    }
    return (

{headerText}

Navigera till speltyperna i menyn för att lägga till lektioner här.

);
  }

  render() {
    return (
      <Grid className="text-center">

Välkommen till Gakusei, {this.props.loggedInUser}!

Din svarsstatistik:

        <Row>
          <Col xs={12} xsOffset={0} md={6} mdOffset={3}>
            { this.props.requestingSuccessRate === false ?
              <Pie data={this.getChartData()} options={homeScreen.getChartOptions()} /> :

Loading...
 }
          </Col>
        </Row>
        {this.showFavorites()}
      </Grid>
    );
  }
}

homeScreen.defaultProps = Utility.reduxEnabledDefaultProps({

}, Reducers);

homeScreen.propTypes = Utility.reduxEnabledPropTypes({

}, Reducers);

export default Utility.superConnect(this, Reducers)(homeScreen);

<|endoftext|>


<commit_before>/* jshint camelcase:false */

describe('schema()', function () {
    'use strict';

    var assert = require('assert'),
        schema = require('../src/schema');

    describe('defined functions and values', function () {
        var s;

        beforeEach(function () {
            s = schema('nl_NL');
        });

        it('include the locale', function () {
            assert(/nl_NL/.test(s.lib.locale));
        });

        it('include the language', function () {
            assert(/nl/.test(s.lib.language));
        });

        it('include lib/messageformat', function () {
            assert(/function/.test(s.lib.messageformat));
        });

        it('include lib/pluralFunc', function () {
            assert(/function/.test(s.lib.pluralFunc));
        });

        it('include lib/compileJs', function () {
            assert(/function/.test(s.lib.compileJs));
        });

        it('include lib/compilePo', function () {
            assert(/function/.test(s.lib.compilePo));
        });

        it('include views/lib/md5', function () {
            assert(/function/.test(s.views.lib.md5));
        });

        it('include views/lib/hash', function () {
            assert(/function/.test(s.views.lib.hash));
        });

        it('include views/lib/emitNamespaces', function () {
            assert(/function/.test(s.views.lib.emitNamespaces));
        });

        it('include views/lib/emitByNamespace', function () {
            assert(/function/.test(s.views.lib.emitByNamespace));
        });

        it('include views/lib/emitSortedByNamespace', function () {
            assert(/function/.test(s.views.lib.emitSortedByNamespace));
        });

        it('include views/all_namespaces/map', function () {
            assert(s.views.all_namespaces.map);
        });

        it('include views/all_namespaces/reduce', function () {
            assert(s.views.all_namespaces.reduce);
        });

        it('include views/translations/map', function () {
            assert(s.views.translations.map);
        });

        it('include views/sorted_translations/map', function () {
            assert(s.views.sorted_translations.map);
        });

        it('include views/find/map', function () {
            assert(s.views.find.map);
        });

        it('include lists/js', function () {
            assert(s.lists.js);
        });

        it('include lists/po', function () {
            assert(s.lists.po);
        });
    });
});
<commit_msg>country-less locale test<commit_after>/* jshint camelcase:false */

describe('schema', function () {
    'use strict';

    var assert = require('assert'),
        schema = require('../src/schema');

    describe('defined functions and values', function () {
        var s;

        beforeEach(function () {
            s = schema('nl_NL');
        });

        it('include the locale', function () {
            assert(/nl_NL/.test(s.lib.locale));
        });

        it('include the language', function () {
            assert(/nl/.test(s.lib.language));
        });

        it('include lib/messageformat', function () {
            assert(/function/.test(s.lib.messageformat));
        });

        it('include lib/pluralFunc', function () {
            assert(/function/.test(s.lib.pluralFunc));
        });

        it('include lib/compileJs', function () {
            assert(/function/.test(s.lib.compileJs));
        });

        it('include lib/compilePo', function () {
            assert(/function/.test(s.lib.compilePo));
        });

        it('include views/lib/md5', function () {
            assert(/function/.test(s.views.lib.md5));
        });

        it('include views/lib/hash', function () {
            assert(/function/.test(s.views.lib.hash));
        });

        it('include views/lib/emitNamespaces', function () {
            assert(/function/.test(s.views.lib.emitNamespaces));
        });

        it('include views/lib/emitByNamespace', function () {
            assert(/function/.test(s.views.lib.emitByNamespace));
        });

        it('include views/lib/emitSortedByNamespace', function () {
            assert(/function/.test(s.views.lib.emitSortedByNamespace));
        });

        it('include views/all_namespaces/map', function () {
            assert(s.views.all_namespaces.map);
        });

        it('include views/all_namespaces/reduce', function () {
            assert(s.views.all_namespaces.reduce);
        });

        it('include views/translations/map', function () {
            assert(s.views.translations.map);
        });

        it('include views/sorted_translations/map', function () {
            assert(s.views.sorted_translations.map);
        });

        it('include views/find/map', function () {
            assert(s.views.find.map);
        });

        it('include lists/js', function () {
            assert(s.lists.js);
        });

        it('include lists/po', function () {
            assert(s.lists.po);
        });
    });

    describe('for country-less locale', function () {
        it('parses out the language as well', function () {
            assert(/"nl"/.test(schema('nl').lib.language));
        });
    });
});
<|endoftext|>


<commit_before>/*
 * Copyright (C) 2015 Jeeva Kandasamy (jkandasa@gmail.com)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
myControllerModule.controller('ForwardPayloadController', function(alertService,
$scope, $filter, TimersFactory, ForwardPayloadFactory, $uibModal, $stateParams, displayRestError) {

  $scope.sensor = TimersFactory.getSensorData({"id":$stateParams.id});

  $scope.filteredList=[];
  $scope.orgList=[];
  $scope.config = {
    itemsPerPage: 10,
    maxPages:10,
    fillLastPage: false
  }

  //Filter
  $scope.updateFilteredList = function() {
    $scope.filteredList = $filter("filter")($scope.orgList, $scope.query);
  };

  // Call and Run function every second
  $scope.orgList = ForwardPayloadFactory.getAll({"id":$stateParams.id},function(response) {
                    },function(error){
                      displayRestError.display(error);
                    });
  $scope.filteredList = $scope.orgList;
  //setInterval($scope.getTimers, 1000*30);

  //Add new
  $scope.add = function (size) {
    var addModalInstance = $uibModal.open({
    templateUrl: 'partials/forwardPayload/addModal.html',
    controller: 'PPMaddController',
    size: size,
    resolve: {sensor: function () {return $scope.sensor;}}
    });

    addModalInstance.result.then(function (plProxy) {
      ForwardPayloadFactory.create(plProxy, function(response) {
        alertService.success("Added new entry");
        //Update display table
        $scope.orgList = ForwardPayloadFactory.getAll({"id":$stateParams.id}, function(response) {
        },function(error){
          displayRestError.display(error);
        });
        $scope.filteredList = $scope.orgList;
      },function(error){
        displayRestError.display(error);
      });
    }),
    function () {
      //console.log('Modal dismissed at: ' + new Date());
    }
  };

  //Delete timer
  $scope.delete = function (plProxy, size) {
    var modalInstance = $uibModal.open({
    templateUrl: 'partials/models/deleteModal.html',
    controller: 'PPMdeleteController',
    size: size,
    resolve: {
      plProxy: function () {return plProxy;}
      }
    });
    modalInstance.result.then(function (plProxy) {
      ForwardPayloadFactory.delete({id: plProxy.id},function(response) {
        alertService.success("Deleted an entry["+plProxy.sensorDestination.nameWithNode+"]");
        //Update display table
        $scope.orgList = ForwardPayloadFactory.getAll({"id":$stateParams.id}, function(response) {
        },function(error){
          displayRestError.display(error);
        });
        $scope.filteredList = $scope.orgList;
      },function(error){
        displayRestError.display(error);
      });
    }),
    function () {
      //console.log('Modal dismissed at: ' + new Date());
    }
  };
});
myControllerModule.controller('PPMaddController', function ($scope, $modalInstance, TypesFactory, sensor) {
  $scope.plProxy = {};
  $scope.plProxy.sensorSource = {};
  $scope.plProxy.sensorDestination = {};
  $scope.plProxy.sensorDestination.node = {};
  $scope.plProxy.sensorSource.id = sensor.id;
  $scope.nodes = TypesFactory.getNodes();
  $scope.sourceSensorVariableTypes = TypesFactory.getSensorVariableTypesBySensorRefId({id: sensor.id});

  //Update sensors
  $scope.refreshSensors = function(nodeId){
      return TypesFactory.getSensors({id: nodeId});
  };

  //Updated Variable Types
  $scope.refreshVariableTypes = function(sensorRefId){
      return TypesFactory.getSensorVariableTypesBySensorRefId({id:sensorRefId});
  };

  $scope.header = "New Forward Payload entry for '"+sensor.nameWithNode+"'";
  $scope.add = function() {$modalInstance.close($scope.plProxy); }
  $scope.cancel = function () { $modalInstance.dismiss('cancel'); }
});

//Delete Modal
myControllerModule.controller('PPMdeleteController', function ($scope, $modalInstance, $sce, plProxy) {
  $scope.header = "Delete a Forward Payload entry";
  $scope.deleteMsg = $sce.trustAsHtml("You are about to delete a forward entry"
    +"
Click 'Delete' to proceed."
    +"
Sensor Name: ["+plProxy.sensorDestination.nameWithNode+"]");
  $scope.remove = function() {
    $modalInstance.close(plProxy);
  };
  $scope.cancel = function () { $modalInstance.dismiss('cancel'); }
});
<commit_msg>last seen time display issue fixed in forwardPayload page<commit_after>/*
 * Copyright (C) 2015 Jeeva Kandasamy (jkandasa@gmail.com)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
myControllerModule.controller('ForwardPayloadController', function(alertService,
$scope, $filter, TimersFactory, ForwardPayloadFactory, $uibModal, $stateParams, displayRestError, about) {

  $scope.sensor = TimersFactory.getSensorData({"id":$stateParams.id});

  $scope.filteredList=[];
  $scope.orgList=[];
  $scope.config = {
    itemsPerPage: 10,
    maxPages:10,
    fillLastPage: false
  }

  //about, Timezone, etc.,
  $scope.about = about;

  //Filter
  $scope.updateFilteredList = function() {
    $scope.filteredList = $filter("filter")($scope.orgList, $scope.query);
  };

  // Call and Run function every second
  $scope.orgList = ForwardPayloadFactory.getAll({"id":$stateParams.id},function(response) {
                    },function(error){
                      displayRestError.display(error);
                    });
  $scope.filteredList = $scope.orgList;
  //setInterval($scope.getTimers, 1000*30);

  //Add new
  $scope.add = function (size) {
    var addModalInstance = $uibModal.open({
    templateUrl: 'partials/forwardPayload/addModal.html',
    controller: 'PPMaddController',
    size: size,
    resolve: {sensor: function () {return $scope.sensor;}}
    });

    addModalInstance.result.then(function (plProxy) {
      ForwardPayloadFactory.create(plProxy, function(response) {
        alertService.success("Added new entry");
        //Update display table
        $scope.orgList = ForwardPayloadFactory.getAll({"id":$stateParams.id}, function(response) {
        },function(error){
          displayRestError.display(error);
        });
        $scope.filteredList = $scope.orgList;
      },function(error){
        displayRestError.display(error);
      });
    }),
    function () {
      //console.log('Modal dismissed at: ' + new Date());
    }
  };

  //Delete timer
  $scope.delete = function (plProxy, size) {
    var modalInstance = $uibModal.open({
    templateUrl: 'partials/models/deleteModal.html',
    controller: 'PPMdeleteController',
    size: size,
    resolve: {
      plProxy: function () {return plProxy;}
      }
    });
    modalInstance.result.then(function (plProxy) {
      ForwardPayloadFactory.delete({id: plProxy.id},function(response) {
        alertService.success("Deleted an entry["+plProxy.sensorDestination.nameWithNode+"]");
        //Update display table
        $scope.orgList = ForwardPayloadFactory.getAll({"id":$stateParams.id}, function(response) {
        },function(error){
          displayRestError.display(error);
        });
        $scope.filteredList = $scope.orgList;
      },function(error){
        displayRestError.display(error);
      });
    }),
    function () {
      //console.log('Modal dismissed at: ' + new Date());
    }
  };
});
myControllerModule.controller('PPMaddController', function ($scope, $modalInstance, TypesFactory, sensor) {
  $scope.plProxy = {};
  $scope.plProxy.sensorSource = {};
  $scope.plProxy.sensorDestination = {};
  $scope.plProxy.sensorDestination.node = {};
  $scope.plProxy.sensorSource.id = sensor.id;
  $scope.nodes = TypesFactory.getNodes();
  $scope.sourceSensorVariableTypes = TypesFactory.getSensorVariableTypesBySensorRefId({id: sensor.id});

  //Update sensors
  $scope.refreshSensors = function(nodeId){
      return TypesFactory.getSensors({id: nodeId});
  };

  //Updated Variable Types
  $scope.refreshVariableTypes = function(sensorRefId){
      return TypesFactory.getSensorVariableTypesBySensorRefId({id:sensorRefId});
  };

  $scope.header = "New Forward Payload entry for '"+sensor.nameWithNode+"'";
  $scope.add = function() {$modalInstance.close($scope.plProxy); }
  $scope.cancel = function () { $modalInstance.dismiss('cancel'); }
});

//Delete Modal
myControllerModule.controller('PPMdeleteController', function ($scope, $modalInstance, $sce, plProxy) {
  $scope.header = "Delete a Forward Payload entry";
  $scope.deleteMsg = $sce.trustAsHtml("You are about to delete a forward entry"
    +"
Click 'Delete' to proceed."
    +"
Sensor Name: ["+plProxy.sensorDestination.nameWithNode+"]");
  $scope.remove = function() {
    $modalInstance.close(plProxy);
  };
  $scope.cancel = function () { $modalInstance.dismiss('cancel'); }
});
<|endoftext|>