<commit_before>/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.
*/
(function() {
    'use strict';
    /* global myApp */
    myApp.factory('AppsService', ['$q', 'ResourcesLoader', 'INSTALL_DIRECTORY', 'APPS_JSON', 'notifier', 'AppHarnessUI', function($q, ResourcesLoader, INSTALL_DIRECTORY, APPS_JSON, notifier, AppHarnessUI) {
        // Map of type -> installer.
        var _installerFactories = Object.create(null);
        // Array of installer objects.
        var _installers = null;
        // The app that is currently running.
        var activeInstaller = null;
        var lastAccessedInstaller = null;

        function readAppsJson() {
            var deferred = $q.defer();
            ResourcesLoader.readJSONFileContents(APPS_JSON)
            .then(function(result) {
                if (result['fileVersion'] !== 1) {
                    console.warn('Ignoring old version of apps.json');
                    result = {};
                }
                deferred.resolve(result);
            }, function() {
                // Error means first run.
                deferred.resolve({});
            });
            return deferred.promise;
        }

        function initHandlers() {
            if (_installers) {
                return $q.when();
            }

            return readAppsJson()
            .then(function(json) {
                var appList = json['appList'] || [];
                _installers = [];
                var i = -1;
                return $q.when()
                .then(function next() {
                    var entry = appList[++i];
                    if (!entry) {
                        return;
                    }
                    var Ctor = _installerFactories[entry['appType']];
                    return new Ctor().initFromJson(entry)
                    .then(function(app) {
                        _installers.push(app);
                        return next();
                    }, next);
                })
                .then(function() {
                    lastAccessedInstaller = _installers.filter(function(x) { return x.appId === json['lastAccessedAppId']; })[0] || null;
                });
            });
        }

        function createAppsJson() {
            var appsJson = {
                'fileVersion': 1,
                'lastAccessedAppId': lastAccessedInstaller ? lastAccessedInstaller.appId : null,
                'appList': []
            };
            for (var i = 0; i < _installers.length; ++i) {
                var installer = _installers[i];
                appsJson.appList.push(installer.toDiskJson());
            }
            return appsJson;
        }

        function writeAppsJson() {
            if (AppsService.onAppListChange) {
                AppsService.onAppListChange();
            }
            var appsJson = createAppsJson();
            var stringContents = JSON.stringify(appsJson, null, 4);
            return ResourcesLoader.writeFileContents(APPS_JSON, stringContents);
        }

        function updateLastAccessed(app) {
            if (lastAccessedInstaller != app) {
                lastAccessedInstaller = app;
                return writeAppsJson();
            }
            return $q.when();
        }

        var AppsService = {
            // return promise with the array of apps
            getAppList : function() {
                return initHandlers()
                .then(function() {
                    return _installers.slice();
                });
            },

            getLastAccessedApp: function() {
                return lastAccessedInstaller;
            },

            getActiveApp: function() {
                return activeInstaller;
            },

            getAppListAsJson : function() {
                return createAppsJson();
            },

            // If no appId, then return the first app.
            // If appId and appType, then create it if it doesn't exist.
            // Else: return null.
            getAppById : function(appId, /* optional */ appType) {
                return initHandlers()
                .then(function() {
                    var matches = _installers;
                    if (appId) {
                        matches = _installers.filter(function(x) {
                            return x.appId == appId;
                        });
                    }
                    if (matches.length > 0) {
                        if (appType) {
                            return updateLastAccessed(matches[0])
                            .then(function() {
                                return matches[0];
                            });
                        }
                        return matches[0];
                    }
                    if (appType) {
                        return AppsService.addApp(appType, appId);
                    }
                    return null;
                });
            },

            quitApp : function() {
                if (activeInstaller) {
                    activeInstaller.unlaunch();
                    activeInstaller = null;
                    return AppHarnessUI.destroy();
                }
                return $q.when();
            },

            launchApp : function(installer) {
                return AppsService.quitApp()
                .then(function() {
                    activeInstaller = installer;
                    return updateLastAccessed(installer);
                }).then(function() {
                    return installer.launch();
                }).then(function(launchUrl) {
                    // Don't just use ResourcesLoader.doesFileExist because remaps might make it exist.
                    return ResourcesLoader.xhrGet(launchUrl)
                    .then(function() {
                        return AppHarnessUI.create(launchUrl);
                    }, function() {
                        throw new Error('Start file does not exist: ' + launchUrl.replace(/.*?\/www\//, 'www/'));
                    }).then(function() {
                        if (AppsService.onAppListChange) {
                            AppsService.onAppListChange();
                        }
                    });
                });
            },

            addApp : function(appType, /* optional */ appId) {
                var installPath = INSTALL_DIRECTORY + 'app' + Math.floor(Math.random() * 0xFFFFFFFF).toString(36) + '/';
                return initHandlers().then(function() {
                    var Ctor = _installerFactories[appType];
                    return new Ctor().init(installPath, appId);
                }).then(function(installer) {
                    _installers.push(installer);
                    lastAccessedInstaller = installer;
                    return writeAppsJson()
                    .then(function() {
                        return installer;
                    });
                });
            },

            uninstallAllApps : function() {
                this.quitApp()
                .then(function() {
                    var deletePromises = [];
                    for (var i = 0; i < _installers.length; ++i) {
                        deletePromises.push(AppsService.uninstallApp(_installers[i]));
                    }
                    return $q.all(deletePromises);
                });
            },

            uninstallApp : function(installer) {
                var ret = $q.when();
                if (lastAccessedInstaller == installer) {
                    lastAccessedInstaller = null;
                }
                if (installer == activeInstaller) {
                    ret = this.quitApp();
                }
                return ret.then(function() {
                    return installer.deleteFiles();
                }).then(function() {
                    _installers.splice(_installers.indexOf(installer), 1);
                    return writeAppsJson();
                });
            },

            triggerAppListChange: function() {
                return writeAppsJson();
            },

            registerInstallerFactory : function(installerFactory) {
                 _installerFactories[installerFactory.type] = installerFactory;
            },

            onAppListChange: null
        };
        return AppsService;
    }]);
})();
<commit_msg>Fix deleteapp?all=1 returning a 500 due to forgetting to return promise<commit_after>/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.
*/
(function() {
    'use strict';
    /* global myApp */
    myApp.factory('AppsService', ['$q', 'ResourcesLoader', 'INSTALL_DIRECTORY', 'APPS_JSON', 'notifier', 'AppHarnessUI', function($q, ResourcesLoader, INSTALL_DIRECTORY, APPS_JSON, notifier, AppHarnessUI) {
        // Map of type -> installer.
        var _installerFactories = Object.create(null);
        // Array of installer objects.
        var _installers = null;
        // The app that is currently running.
        var activeInstaller = null;
        var lastAccessedInstaller = null;

        function readAppsJson() {
            var deferred = $q.defer();
            ResourcesLoader.readJSONFileContents(APPS_JSON)
            .then(function(result) {
                if (result['fileVersion'] !== 1) {
                    console.warn('Ignoring old version of apps.json');
                    result = {};
                }
                deferred.resolve(result);
            }, function() {
                // Error means first run.
                deferred.resolve({});
            });
            return deferred.promise;
        }

        function initHandlers() {
            if (_installers) {
                return $q.when();
            }

            return readAppsJson()
            .then(function(json) {
                var appList = json['appList'] || [];
                _installers = [];
                var i = -1;
                return $q.when()
                .then(function next() {
                    var entry = appList[++i];
                    if (!entry) {
                        return;
                    }
                    var Ctor = _installerFactories[entry['appType']];
                    return new Ctor().initFromJson(entry)
                    .then(function(app) {
                        _installers.push(app);
                        return next();
                    }, next);
                })
                .then(function() {
                    lastAccessedInstaller = _installers.filter(function(x) { return x.appId === json['lastAccessedAppId']; })[0] || null;
                });
            });
        }

        function createAppsJson() {
            var appsJson = {
                'fileVersion': 1,
                'lastAccessedAppId': lastAccessedInstaller ? lastAccessedInstaller.appId : null,
                'appList': []
            };
            for (var i = 0; i < _installers.length; ++i) {
                var installer = _installers[i];
                appsJson.appList.push(installer.toDiskJson());
            }
            return appsJson;
        }

        function writeAppsJson() {
            if (AppsService.onAppListChange) {
                AppsService.onAppListChange();
            }
            var appsJson = createAppsJson();
            var stringContents = JSON.stringify(appsJson, null, 4);
            return ResourcesLoader.writeFileContents(APPS_JSON, stringContents);
        }

        function updateLastAccessed(app) {
            if (lastAccessedInstaller != app) {
                lastAccessedInstaller = app;
                return writeAppsJson();
            }
            return $q.when();
        }

        var AppsService = {
            // return promise with the array of apps
            getAppList : function() {
                return initHandlers()
                .then(function() {
                    return _installers.slice();
                });
            },

            getLastAccessedApp: function() {
                return lastAccessedInstaller;
            },

            getActiveApp: function() {
                return activeInstaller;
            },

            getAppListAsJson : function() {
                return createAppsJson();
            },

            // If no appId, then return the first app.
            // If appId and appType, then create it if it doesn't exist.
            // Else: return null.
            getAppById : function(appId, /* optional */ appType) {
                return initHandlers()
                .then(function() {
                    var matches = _installers;
                    if (appId) {
                        matches = _installers.filter(function(x) {
                            return x.appId == appId;
                        });
                    }
                    if (matches.length > 0) {
                        if (appType) {
                            return updateLastAccessed(matches[0])
                            .then(function() {
                                return matches[0];
                            });
                        }
                        return matches[0];
                    }
                    if (appType) {
                        return AppsService.addApp(appType, appId);
                    }
                    return null;
                });
            },

            quitApp : function() {
                if (activeInstaller) {
                    activeInstaller.unlaunch();
                    activeInstaller = null;
                    return AppHarnessUI.destroy();
                }
                return $q.when();
            },

            launchApp : function(installer) {
                return AppsService.quitApp()
                .then(function() {
                    activeInstaller = installer;
                    return updateLastAccessed(installer);
                }).then(function() {
                    return installer.launch();
                }).then(function(launchUrl) {
                    // Don't just use ResourcesLoader.doesFileExist because remaps might make it exist.
                    return ResourcesLoader.xhrGet(launchUrl)
                    .then(function() {
                        return AppHarnessUI.create(launchUrl);
                    }, function() {
                        throw new Error('Start file does not exist: ' + launchUrl.replace(/.*?\/www\//, 'www/'));
                    }).then(function() {
                        if (AppsService.onAppListChange) {
                            AppsService.onAppListChange();
                        }
                    });
                });
            },

            addApp : function(appType, /* optional */ appId) {
                var installPath = INSTALL_DIRECTORY + 'app' + Math.floor(Math.random() * 0xFFFFFFFF).toString(36) + '/';
                return initHandlers().then(function() {
                    var Ctor = _installerFactories[appType];
                    return new Ctor().init(installPath, appId);
                }).then(function(installer) {
                    _installers.push(installer);
                    lastAccessedInstaller = installer;
                    return writeAppsJson()
                    .then(function() {
                        return installer;
                    });
                });
            },

            uninstallAllApps : function() {
                return this.quitApp()
                .then(function() {
                    var deletePromises = [];
                    for (var i = 0; i < _installers.length; ++i) {
                        deletePromises.push(AppsService.uninstallApp(_installers[i]));
                    }
                    return $q.all(deletePromises);
                });
            },

            uninstallApp : function(installer) {
                var ret = $q.when();
                if (lastAccessedInstaller == installer) {
                    lastAccessedInstaller = null;
                }
                if (installer == activeInstaller) {
                    ret = this.quitApp();
                }
                return ret.then(function() {
                    return installer.deleteFiles();
                }).then(function() {
                    _installers.splice(_installers.indexOf(installer), 1);
                    return writeAppsJson();
                });
            },

            triggerAppListChange: function() {
                return writeAppsJson();
            },

            registerInstallerFactory : function(installerFactory) {
                 _installerFactories[installerFactory.type] = installerFactory;
            },

            onAppListChange: null
        };
        return AppsService;
    }]);
})();
<|endoftext|>


<commit_before>'use strict';

var myApp = angular.module('dawgApp', ['ngSanitize', 'ui.router', 'firebase']);

myApp.config(function($stateProvider, $urlRouterProvider) {
        $urlRouterProvider.otherwise("/");
        $stateProvider
        .state('home', {
          url: "/",
          templateUrl: "/partials/home.html"
        })
        .state('cart', {
          url: "/cart",
          templateUrl: "/partials/cart.html"
        })
        .state('detail', {
          url: "/bean/{id}",
          templateUrl: "/partials/bean.html"
        })
        .state('order', {
          url: "/order",
          templateUrl: "/partials/order.html"
        });
    })
    .controller('CoffeeCtlr', ['$scope', '$http', '$firebaseObject', '$firebaseArray', function($scope, $http, $firebaseObject, $firebaseArray) {

        $scope.sortingCriteria = '';

        $http.get('data/products.json').then(function(response) {
            $scope.beans = response.data;
        });

        // Reference to firebase
        var cartRef = new Firebase('https://firedawgcoffee.firebaseio.com/Cart');

        // Array for holding all Cart contents
        if ($scope.cart === undefined) {
            $scope.cart = [];
        }

        // Makes sure cart page is loaded, then adds cart items
        cartRef.once("value", function(snapshot) {
            console.log(snapshot.val());
            snapshot.forEach(function(childSnapshot) {
                $scope.cart.push(childSnapshot.val());
                console.log($scope.cart);
            });

        // Checks to see if objects were able to load, throws error if not
        }, function (errorObject) {
            console.log("The read failed: " + errorObject.code);
        });

        //Add quantity functionality
        $scope.upQ = function(itemID) {
            var post = cartRef.$getRecord(itemID);
            console.log(itemID);
            Object.quantity += 1;
            cartRef.$save(Object);
        }

        //Lower quantity functionality
        $scope.downQ = function(itemID) {
            var post = cartRef.$getRecord(itemID);
            console.log(itemID);
            Object.quantity -= 1;
            cartRef.$save(Object);
        }

    }])

    //For details view
    .controller('BeanCtrl', ['$scope', '$http', '$firebaseObject', '$firebaseArray', '$stateParams', '$filter', function($scope, $http, $firebaseObject, $firebaseArray, $stateParams, $filter) {

        $scope.grinds = ['Whole Bean', 'Espresso', 'French Press', 'Cone Drip', 'Flat Bottom'];
        $scope.grind = 'Whole Bean'; //default
        $scope.quantities = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10'];
        $scope.quantity = '1'; //default

        $http.get('data/products.json').then(function(response) {
        $scope.bean = $filter('filter')(response.data, { //filter the array
          id: $stateParams.id //for items whose id property is targetId
        }, true)[0]; //save the 0th result
        });

        // Reference to firebase
    var ref = new Firebase('https://firedawgcoffee.firebaseio.com/Cart');

    // Stores new beans created on /cart page
    $scope.cartObject = {};

    // Sends to firebase
    $scope.addToCart = function(bean, price) {
        ref.push({
            bean: bean,
            price: price,
            grind: $scope.cartObject.grind,
            quantity: $scope.cartObject.quantity
        }, function(error, eData) {
            if(error) {
                console.log(error);
            } else {
                console.log("Success");
                window.location.reload();
            }
        })
    };
    }]);

//    .controller('CartCtrl', ['$scope', '$firebaseObject', '$firebaseArray', function($scope, $firebaseObject, $firebaseArray) {
//
//        // Reference to firebase
//        var cartRef = new Firebase('https://firedawgcoffee.firebaseio.com/Cart');
//
//        // Array for holding all Cart contents
//        if ($scope.cart === undefined) {
//            $scope.cart = [];
//        }
//
//        // Makes sure cart page is loaded, then adds cart items
//        cartRef.once("value", function(snapshot) {
//            console.log(snapshot.val());
//            snapshot.forEach(function(childSnapshot) {
//                $scope.cart.push(childSnapshot.val());
//                console.log($scope.cart);
//            });
//
//        // Checks to see if objects were able to load, throws error if not
//        }, function (errorObject) {
//            console.log("The read failed: " + errorObject.code);
//        });
//
//
//    }]);

<commit_msg>fixed<commit_after>'use strict';

var myApp = angular.module('dawgApp', ['ngSanitize', 'ui.router', 'firebase']);

myApp.config(function($stateProvider, $urlRouterProvider) {
        $urlRouterProvider.otherwise("/");
        $stateProvider
        .state('home', {
          url: "/",
          templateUrl: "partials/home.html"
        })
        .state('cart', {
          url: "/cart",
          templateUrl: "partials/cart.html"
        })
        .state('detail', {
          url: "/bean/{id}",
          templateUrl: "partials/bean.html"
        })
        .state('order', {
          url: "/order",
          templateUrl: "partials/order.html"
        });
    })
    .controller('CoffeeCtlr', ['$scope', '$http', '$firebaseObject', '$firebaseArray', function($scope, $http, $firebaseObject, $firebaseArray) {

        $scope.sortingCriteria = '';

        $http.get('data/products.json').then(function(response) {
            $scope.beans = response.data;
        });

        // Reference to firebase
        var cartRef = new Firebase('https://firedawgcoffee.firebaseio.com/Cart');

        // Array for holding all Cart contents
        if ($scope.cart === undefined) {
            $scope.cart = [];
        }

        // Makes sure cart page is loaded, then adds cart items
        cartRef.once("value", function(snapshot) {
            console.log(snapshot.val());
            snapshot.forEach(function(childSnapshot) {
                $scope.cart.push(childSnapshot.val());
                console.log($scope.cart);
            });

        // Checks to see if objects were able to load, throws error if not
        }, function (errorObject) {
            console.log("The read failed: " + errorObject.code);
        });

        //Add quantity functionality
        $scope.upQ = function(itemID) {
            var post = cartRef.$getRecord(itemID);
            console.log(itemID);
            Object.quantity += 1;
            cartRef.$save(Object);
        }

        //Lower quantity functionality
        $scope.downQ = function(itemID) {
            var post = cartRef.$getRecord(itemID);
            console.log(itemID);
            Object.quantity -= 1;
            cartRef.$save(Object);
        }

    }])

    //For details view
    .controller('BeanCtrl', ['$scope', '$http', '$firebaseObject', '$firebaseArray', '$stateParams', '$filter', function($scope, $http, $firebaseObject, $firebaseArray, $stateParams, $filter) {

        $scope.grinds = ['Whole Bean', 'Espresso', 'French Press', 'Cone Drip', 'Flat Bottom'];
        $scope.grind = 'Whole Bean'; //default
        $scope.quantities = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10'];
        $scope.quantity = '1'; //default

        $http.get('data/products.json').then(function(response) {
        $scope.bean = $filter('filter')(response.data, { //filter the array
          id: $stateParams.id //for items whose id property is targetId
        }, true)[0]; //save the 0th result
        });

        // Reference to firebase
    var ref = new Firebase('https://firedawgcoffee.firebaseio.com/Cart');

    // Stores new beans created on /cart page
    $scope.cartObject = {};

    // Sends to firebase
    $scope.addToCart = function(bean, price) {
        ref.push({
            bean: bean,
            price: price,
            grind: $scope.cartObject.grind,
            quantity: $scope.cartObject.quantity
        }, function(error, eData) {
            if(error) {
                console.log(error);
            } else {
                console.log("Success");
                window.location.reload();
            }
        })
    };
    }]);

//    .controller('CartCtrl', ['$scope', '$firebaseObject', '$firebaseArray', function($scope, $firebaseObject, $firebaseArray) {
//
//        // Reference to firebase
//        var cartRef = new Firebase('https://firedawgcoffee.firebaseio.com/Cart');
//
//        // Array for holding all Cart contents
//        if ($scope.cart === undefined) {
//            $scope.cart = [];
//        }
//
//        // Makes sure cart page is loaded, then adds cart items
//        cartRef.once("value", function(snapshot) {
//            console.log(snapshot.val());
//            snapshot.forEach(function(childSnapshot) {
//                $scope.cart.push(childSnapshot.val());
//                console.log($scope.cart);
//            });
//
//        // Checks to see if objects were able to load, throws error if not
//        }, function (errorObject) {
//            console.log("The read failed: " + errorObject.code);
//        });
//
//
//    }]);

<|endoftext|>


<commit_before>
// sfw_tbase.js

(function _init()
{
   if ((!("SFW" in window) && setTimeout(_init,100))
       || SFW.delay_init("sfw_tbase",_init,"iclass"))
      return;

   if (!SFW.derive(_tbase, "tbase", "iclass"))
      return;

   function _tbase(actors)
   {
      SFW.base.call(this,actors);
   }

   _tbase.prototype.result_from_match = function(match)
   {
      var result = null;
      if (match)
      {
         var tag = ("tagName" in match) ? match.tagName : match;
         var xpath = "/*/*[@rndx][@row-name='" + tag + "']";
         result = this.xmldoc().selectSingleNode(xpath);
      }
      return result;
   };

   // This method should be overridden, but here's an implementation just in case.
   _tbase.prototype.result = function(match)
   {
      return this.result_from_match(match);
   };

   _tbase.prototype.find_matching_data_row = function(cfobj)
   {
      var el =
         ("cdata" in cfobj && "xrow" in cfobj.cdata)
         ? cfobj.cdata.xrow
         : cfobj.update_row;

      if (el)
      {
         var id, res, xpath = "*/*[@rndx and @row-name='" + el.tagName + "']";
         res = this.xmldoc().selectSingleNode(xpath);
         id = el.getAttribute("id");
         if ((res=this.result(el)) && (id=el.getAttribute("id")))
         {
            function f(n) { return n.nodeType==1 && n.getAttribute("id")==id; }
            return SFW.find_child_matches(res, f, true, false);
         }
      }

      return null;
   };

   _tbase.prototype.replot = function(result)
   {
      console.error("This function should be overridden!");
   };

   _tbase.prototype.empty_node = function(node)
   {
      // Remove attributes separately, since they are not included in the child list:
      // Iterate in reverse or removing each attribute will change the position of
      // the remainder of the attributes.
      for (var s=node.attributes,i=s.length-1; i>=0; --i)
         node.removeAttribute(s[i].nodeName);

      var n, a = node.firstChild;
      while ((n=a))
      {
         a = a.nextSibling;
         node.removeChild(n);
      }
   };

   function _get_copied_node(target, source)
   {
      var doc = target.ownerDocument;
      function ca(target,source)
      {
         // Copy attribtes separately, since they are not included in the child list:
         for (var s,i=0,sa=source.attributes,stop=sa.length; i<stop && (s=sa[i]); ++i)
            target.setAttribute(s.nodeName,s.nodeValue);
      }

      function cp(target,source)
      {
         ca(target,source);

         var node, next = source.firstChild;
         while((node=next))
         {
            next = node.nextSibling;
            switch(node.nodeType)
            {
               case 1:  // element
                  cp(doc.createElement(node.tagName),node);
                  break;
               // no case 2: ca() copies attributes
               case 3:  // text
                  target.appendChild(doc.createTextNode(node.nodeValue));
                  break;
               case 5: // EntityReference
                  target.appendChild(doc.createEntityReference(n.nodeName));
                  break;
               default: break;
            }
         }
      }

      var newnode = doc.createElement(source.tagName);

      cp(newnode, source);
      return newnode;
   }

   _tbase.prototype.delete_row = function(xrow)
   {
      if (this.result(xrow) && xrow)
         xrow.parentNode.removeChild(xrow);
   };

   _tbase.prototype.get_result_to_update = function(cfobj)
   {
      var result = null;
      var tname = cfobj.target_name;
      if (tname)
         result = this.xmldocel().selectSingleNode(tname+"[@rndx]");
      return result;
   };

   _tbase.prototype.update_row = function(cfobj, preserve_result)
   {
      var cdata = "cdata" in cfobj ? cfobj.cdata : null;
      var xrow = (cdata && "xrow" in cdata) ? cdata.xrow : null;
      var urow = ("update_row" in cfobj) ? cfobj.update_row : null;

      if ("docel" in cfobj)
      {
         if (cfobj.mtype=="delete")
         {
            if (cfobj.confirm_delete() && xrow)
               this.delete_row(xrow);
         }
         else if (cfobj.rtype=="update" && urow)
         {
            var target = this.get_result_to_update(cfobj);
            if (!target)
            {
               if (xrow)
                  target = xrow.parentNode;
               else
                  target = this.xmldocel().selectSingleNode("*[@rndx=1][1]");
            }

            if (target)
            {
               if (preserve_result || target.ownerDocument!=urow.ownerDocument)
                  urow = _get_copied_node(target,urow);

               target.insertBefore(urow, xrow);

               if (xrow)
                  target.removeChild(xrow);
            }
         }
      }
      else if ("cmd" in cfobj)
      {
         switch(cfobj.cmd)
         {
            case "delete": this.delete_row(cfobj.cdata.xrow); break;
            case "fail":
               SFW.alert(cfobj.rtype + " operation failed.");
               return;   // skip replot() at end of function
         }
      }
   };

})();
<commit_msg>Update result() to be a working version (taking over from table object). Changed a couple of functions to search from docel rather than the document.<commit_after>
// sfw_tbase.js

(function _init()
{
   if ((!("SFW" in window) && setTimeout(_init,100))
       || SFW.delay_init("sfw_tbase",_init,"iclass"))
      return;

   if (!SFW.derive(_tbase, "tbase", "iclass"))
      return;

   function _tbase(actors)
   {
      SFW.base.call(this,actors);
   }

   _tbase.prototype.result_from_match = function(match)
   {
      var result = null;
      if (match)
      {
         var tag = ("tagName" in match) ? match.tagName : match;
         var xpath = "*[@rndx][@row-name='" + tag + "']";
         result = this.xmldocel().selectSingleNode(xpath);
      }
      return result;
   };

   _tbase.prototype.result = function(match)
   {
      if (match)
         return this.result_from_match(match);
      else
      {
         var xpath = this.get_result_path()
            || this.get_result_xpath_from_top()
            || "/*/*[@rndx=1][not(@merged)]";

         return this.xmldoc().selectSingleNode(xpath);
      }
   };

   _tbase.prototype.find_matching_data_row = function(cfobj)
   {
      var el =
         ("cdata" in cfobj && "xrow" in cfobj.cdata)
         ? cfobj.cdata.xrow
         : cfobj.update_row;

      if (el)
      {
         var id, res, xpath = "*[@rndx and @row-name='" + el.tagName + "']";
         res = this.xmldocel().selectSingleNode(xpath);
         id = el.getAttribute("id");
         if ((res=this.result(el)) && (id=el.getAttribute("id")))
         {
            function f(n) { return n.nodeType==1 && n.getAttribute("id")==id; }
            return SFW.find_child_matches(res, f, true, false);
         }
      }

      return null;
   };

   _tbase.prototype.replot = function(result)
   {
      console.error("This function should be overridden!");
   };

   _tbase.prototype.empty_node = function(node)
   {
      // Remove attributes separately, since they are not included in the child list:
      // Iterate in reverse or removing each attribute will change the position of
      // the remainder of the attributes.
      for (var s=node.attributes,i=s.length-1; i>=0; --i)
         node.removeAttribute(s[i].nodeName);

      var n, a = node.firstChild;
      while ((n=a))
      {
         a = a.nextSibling;
         node.removeChild(n);
      }
   };

   function _get_copied_node(target, source)
   {
      var doc = target.ownerDocument;
      function ca(target,source)
      {
         // Copy attribtes separately, since they are not included in the child list:
         for (var s,i=0,sa=source.attributes,stop=sa.length; i<stop && (s=sa[i]); ++i)
            target.setAttribute(s.nodeName,s.nodeValue);
      }

      function cp(target,source)
      {
         ca(target,source);

         var node, next = source.firstChild;
         while((node=next))
         {
            next = node.nextSibling;
            switch(node.nodeType)
            {
               case 1:  // element
                  cp(doc.createElement(node.tagName),node);
                  break;
               // no case 2: ca() copies attributes
               case 3:  // text
                  target.appendChild(doc.createTextNode(node.nodeValue));
                  break;
               case 5: // EntityReference
                  target.appendChild(doc.createEntityReference(n.nodeName));
                  break;
               default: break;
            }
         }
      }

      var newnode = doc.createElement(source.tagName);

      cp(newnode, source);
      return newnode;
   }

   _tbase.prototype.delete_row = function(xrow)
   {
      if (this.result(xrow) && xrow)
         xrow.parentNode.removeChild(xrow);
   };

   _tbase.prototype.get_result_to_update = function(cfobj)
   {
      var result = null;
      var tname = cfobj.target_name;
      if (tname)
         result = this.xmldocel().selectSingleNode(tname+"[@rndx]");
      return result;
   };

   _tbase.prototype.update_row = function(cfobj, preserve_result)
   {
      var cdata = "cdata" in cfobj ? cfobj.cdata : null;
      var xrow = (cdata && "xrow" in cdata) ? cdata.xrow : null;
      var urow = ("update_row" in cfobj) ? cfobj.update_row : null;

      if ("docel" in cfobj)
      {
         if (cfobj.mtype=="delete")
         {
            if (cfobj.confirm_delete() && xrow)
               this.delete_row(xrow);
         }
         else if (cfobj.rtype=="update" && urow)
         {
            var target = this.get_result_to_update(cfobj);
            if (!target)
            {
               if (xrow)
                  target = xrow.parentNode;
               else
                  target = this.xmldocel().selectSingleNode("*[@rndx=1][1]");
            }

            if (target)
            {
               if (preserve_result || target.ownerDocument!=urow.ownerDocument)
                  urow = _get_copied_node(target,urow);

               target.insertBefore(urow, xrow);

               if (xrow)
                  target.removeChild(xrow);
            }
         }
      }
      else if ("cmd" in cfobj)
      {
         switch(cfobj.cmd)
         {
            case "delete": this.delete_row(cfobj.cdata.xrow); break;
            case "fail":
               SFW.alert(cfobj.rtype + " operation failed.");
               return;   // skip replot() at end of function
         }
      }
   };

})();
<|endoftext|>


<commit_before>// The MIT License (MIT)
//
// Copyright (c) 2016-2020 Camptocamp SA
//
// 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.

import angular from 'angular';
import ngeoMiscWMSTime from 'ngeo/misc/WMSTime.js';
import ngeoMiscDebounce from 'ngeo/misc/debounce.js';

import 'jquery-ui/ui/widgets/slider.js';
import 'ngeo/sass/jquery-ui.scss';
import 'angular-ui-slider';
import './timeslider.scss';

/**
 * @type {angular.IModule}
 * @hidden
 */
const module = angular.module('gmfLayertreeTimeSliderComponent', [
  ngeoMiscWMSTime.name,
  ngeoMiscDebounce.name,
  'ui.slider',
]);

module.run(
  /**
   * @ngInject
   * @param {angular.ITemplateCacheService} $templateCache
   */
  ($templateCache) => {
    // @ts-ignore: webpack
    $templateCache.put('gmf/layertree/timesliderComponent', require('./timesliderComponent.html'));
  }
);

/**
 * Provide a directive to select a single date or a range of dates with a slider
 * Example:
 *
 *      <gmf-time-slider
 *          gmf-time-slider-time="{
 *            maxValue: '2013-12-31T00:00:00Z',
 *            minValue: '2006-01-01T00:00:00Z',
 *            mode: 'range'}"
 *          gmf-time-slider-on-date-selected="ctrl.onDateSelected(time)">
 *      </gmf-time-slider>
 *
 * @htmlAttribute {import('ngeo/datasource/OGC.js').TimeProperty} gmf-time-slider-time parameter
 *    for initialization.
 * @htmlAttribute {function()} gmf-time-slider-on-date-selected Expression evaluated after
 * date(s) changed
 * @return {angular.IDirective} The directive specs.
 * @ngInject
 * @ngdoc directive
 * @ngname gmfTimeSlider
 */
function layertreeTimeSliderComponent() {
  return {
    scope: {
      onDateSelected: '&gmfTimeSliderOnDateSelected',
      time: '=gmfTimeSliderTime',
    },
    bindToController: true,
    controller: 'gmfTimeSliderController as sliderCtrl',
    restrict: 'AE',
    templateUrl: 'gmf/layertree/timesliderComponent',
    link: {
      pre: function preLink(scope, element, attrs, ctrl) {
        if (!ctrl) {
          throw new Error('Missing ctrl');
        }
        ctrl.init();

        ctrl.sliderOptions.stop = onSliderReleased_;
        ctrl.sliderOptions.slide = ctrl.ngeoDebounce_(onSliderReleased_, 300, true);

        /**
         * @param {never} e
         * @param {never} sliderUi
         */
        function onSliderReleased_(e, sliderUi) {
          if (!ctrl) {
            throw new Error('Missing ctrl');
          }
          ctrl.onDateSelected({
            time: computeDates_(e, sliderUi),
          });
          scope.$apply();
        }

        /**
         * @param {never} e
         * @param {{value: ?string, values: ?string[]}} sliderUi
         */
        function computeDates_(e, sliderUi) {
          if (!ctrl) {
            throw new Error('Missing ctrl');
          }
          let sDate, eDate, wmstime;
          if (sliderUi.values) {
            sDate = new Date(ctrl.getClosestValue_(sliderUi.values[0]));
            eDate = new Date(ctrl.getClosestValue_(sliderUi.values[1]));
            ctrl.dates = [sDate, eDate];
            wmstime = {
              start: sDate.getTime(),
              end: eDate.getTime(),
            };
          } else {
            sDate = new Date(ctrl.getClosestValue_(sliderUi.value));
            ctrl.dates = sDate;
            wmstime = {
              start: sDate.getTime(),
            };
          }
          scope.$apply();
          return wmstime;
        }
      },
    },
  };
}

module.directive('gmfTimeSlider', layertreeTimeSliderComponent);

/**
 * TimeSliderController - directive controller
 * @param {import("ngeo/misc/WMSTime.js").WMSTime} ngeoWMSTime WMSTime service.
 * @param {import("ngeo/misc/debounce.js").miscDebounce<function(): void>} ngeoDebounce ngeo Debounce factory.
 * @constructor
 * @private
 * @hidden
 * @ngInject
 * @ngdoc controller
 * @ngname gmfTimeSliderController
 */
function Controller(ngeoWMSTime, ngeoDebounce) {
  /**
   * @type {import("ngeo/misc/WMSTime.js").WMSTime}
   */
  this.ngeoWMSTime_ = ngeoWMSTime;

  /**
   * @type {import("ngeo/misc/debounce.js").miscDebounce<function(): void>}
   */
  this.ngeoDebounce_ = ngeoDebounce;

  /**
   * Function called after date(s) changed/selected
   * @type {function(unknown): unknown}
   */
  this.onDateSelected = () => undefined;

  /**
   * A time object for directive initialization
   * @type {?import('ngeo/datasource/OGC.js').TimeProperty}
   */
  this.time = null;

  /**
   * If the component is used to select a date range
   * @type {boolean}
   */
  this.isModeRange = false;

  /**
   * Minimal value of the slider (time in ms)
   * @type {number}
   */
  this.minValue = -1;

  /**
   * Maximal value of the slider (time in ms)
   * @type {number}
   */
  this.maxValue = 999999;

  /**
   * Used when WMS time object has a property 'values' instead of an interval
   * @type {?number[]}
   */
  this.timeValueList = null;

  /**
   * Default Slider options (used by ui-slider directive)
   * @type {?{
   *  range : boolean,
   *  min : number,
   *  max : number
   * }}
   */
  this.sliderOptions = null;

  /**
   * Model for the ui-slider directive (date in ms format)
   * @type {number[]|number}
   */
  this.dates = [];
}

/**
 * Initialise the controller.
 */
Controller.prototype.init = function () {
  if (!this.time) {
    throw new Error('Missing time');
  }

  // Fetch the initial options for the component
  const initialOptions_ = this.ngeoWMSTime_.getOptions(this.time);
  this.isModeRange = this.time.mode === 'range';
  this.minValue = initialOptions_.minDate;
  this.maxValue = initialOptions_.maxDate;
  const values = initialOptions_.values;
  if (this.isModeRange) {
    if (!Array.isArray(values)) {
      throw new Error('Wrong Options values');
    }
    this.dates = [values[0], values[1]];
  } else {
    this.dates = initialOptions_.values;
  }
  this.timeValueList = this.getTimeValueList_();
  this.sliderOptions = {
    range: this.isModeRange,
    min: this.minValue,
    max: this.maxValue,
  };
};

/**
 * TimeSliderController.prototype.getTimeValueList_ - Get a list of time value instead
 * of using the wmstime interval as a list of possibles values
 * @return {number[]}  - List of timestamp representing possible values
 */
Controller.prototype.getTimeValueList_ = function () {
  if (!this.time) {
    throw new Error('Missing time');
  }
  const wmsTime = this.time;
  /** @type {number[]} */
  let timeValueList = [];
  const minDate = new Date(this.minValue);
  const maxDate = new Date(this.maxValue);

  if (wmsTime.values) {
    timeValueList = [];
    wmsTime.values.forEach((date) => {
      timeValueList.push(new Date(date).getTime());
    });
  } else {
    const maxNbValues = 1024;
    const endDate = new Date(minDate.getTime());
    endDate.setFullYear(minDate.getFullYear() + maxNbValues * wmsTime.interval[0]);
    endDate.setMonth(
      minDate.getMonth() + maxNbValues * wmsTime.interval[1],
      minDate.getDate() + maxNbValues * wmsTime.interval[2]
    );
    endDate.setSeconds(minDate.getSeconds() + maxNbValues * wmsTime.interval[3]);

    if (endDate > maxDate) {
      // Transform interval to a list of values when the number
      // of values is below a threshold (maxNbValues)
      timeValueList = [];
      for (let i = 0; ; i++) {
        const nextDate = new Date(minDate.getTime());
        nextDate.setFullYear(minDate.getFullYear() + i * wmsTime.interval[0]);
        nextDate.setMonth(
          minDate.getMonth() + i * wmsTime.interval[1],
          minDate.getDate() + i * wmsTime.interval[2]
        );
        nextDate.setSeconds(minDate.getSeconds() + i * wmsTime.interval[3]);
        if (nextDate <= maxDate) {
          timeValueList.push(nextDate.getTime());
        } else {
          break;
        }
      }
    }
  }
  return timeValueList;
};

/**
 * Compute the closest available date from the given timestamp
 * @param  {number} timestamp selected datetime (in ms format)
 * @return {number} the closest available datetime (in ms format) from the timestamp
 * @private
 */
Controller.prototype.getClosestValue_ = function (timestamp) {
  if (!this.time) {
    throw new Error('Missing time');
  }
  if (timestamp <= this.minValue) {
    return this.minValue;
  }

  if (timestamp >= this.maxValue) {
    return this.maxValue;
  }

  if (this.timeValueList) {
    // Time stops are defined as a list of values
    let index;
    let leftIndex = 0;
    let rightIndex = this.timeValueList.length - 1;

    while (rightIndex - leftIndex > 1) {
      index = Math.floor((leftIndex + rightIndex) / 2);
      if (this.timeValueList[index] >= timestamp) {
        rightIndex = index;
      } else {
        leftIndex = index;
      }
    }

    const leftDistance = Math.abs(this.timeValueList[leftIndex] - timestamp);
    const rightDistance = Math.abs(this.timeValueList[rightIndex] - timestamp);

    return this.timeValueList[leftDistance < rightDistance ? leftIndex : rightIndex];
  } else {
    // Time stops are defined by a start date plus an interval
    const targetDate = new Date(timestamp);
    const startDate = new Date(this.minValue);
    let bestDate = new Date(this.minValue);
    const maxDate = new Date(this.maxValue);
    let bestDistance = Math.abs(targetDate.getTime() - bestDate.getTime());

    for (let i = 1; ; i++) {
      // The start date should always be used as a reference
      // because adding a month twice could differ from adding
      // two months at once
      const next = new Date(startDate.getTime());
      next.setFullYear(startDate.getFullYear() + i * this.time.interval[0]);
      next.setMonth(
        startDate.getMonth() + i * this.time.interval[1],
        startDate.getDate() + i * this.time.interval[2]
      );
      next.setSeconds(startDate.getSeconds() + i * this.time.interval[3]);

      if (next > maxDate) {
        break;
      }

      const distance = Math.abs(targetDate.getTime() - next.getTime());
      if (distance <= bestDistance) {
        bestDate = next;
        bestDistance = distance;
      } else {
        break;
      }
    }

    return bestDate.getTime();
  }
};

/**
 * Format and localize time regarding a resolution.
 * @param {number} time (in ms format) timestamp to format and localize.
 * @return {string} Localized date string regarding the resolution.
 */
Controller.prototype.getLocalizedDate = function (time) {
  if (!this.time) {
    throw new Error('Missing time');
  }
  return this.ngeoWMSTime_.formatTimeValue(time, this.time.resolution);
};

module.controller('gmfTimeSliderController', Controller);

export default module;
<commit_msg>Interval timeslider with PD1<commit_after>// The MIT License (MIT)
//
// Copyright (c) 2016-2020 Camptocamp SA
//
// 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.

import angular from 'angular';
import ngeoMiscWMSTime from 'ngeo/misc/WMSTime.js';
import ngeoMiscDebounce from 'ngeo/misc/debounce.js';

import 'jquery-ui/ui/widgets/slider.js';
import 'ngeo/sass/jquery-ui.scss';
import 'angular-ui-slider';
import './timeslider.scss';

/**
 * @type {angular.IModule}
 * @hidden
 */
const module = angular.module('gmfLayertreeTimeSliderComponent', [
  ngeoMiscWMSTime.name,
  ngeoMiscDebounce.name,
  'ui.slider',
]);

module.run(
  /**
   * @ngInject
   * @param {angular.ITemplateCacheService} $templateCache
   */
  ($templateCache) => {
    // @ts-ignore: webpack
    $templateCache.put('gmf/layertree/timesliderComponent', require('./timesliderComponent.html'));
  }
);

/**
 * Provide a directive to select a single date or a range of dates with a slider
 * Example:
 *
 *      <gmf-time-slider
 *          gmf-time-slider-time="{
 *            maxValue: '2013-12-31T00:00:00Z',
 *            minValue: '2006-01-01T00:00:00Z',
 *            mode: 'range'}"
 *          gmf-time-slider-on-date-selected="ctrl.onDateSelected(time)">
 *      </gmf-time-slider>
 *
 * @htmlAttribute {import('ngeo/datasource/OGC.js').TimeProperty} gmf-time-slider-time parameter
 *    for initialization.
 * @htmlAttribute {function()} gmf-time-slider-on-date-selected Expression evaluated after
 * date(s) changed
 * @return {angular.IDirective} The directive specs.
 * @ngInject
 * @ngdoc directive
 * @ngname gmfTimeSlider
 */
function layertreeTimeSliderComponent() {
  return {
    scope: {
      onDateSelected: '&gmfTimeSliderOnDateSelected',
      time: '=gmfTimeSliderTime',
    },
    bindToController: true,
    controller: 'gmfTimeSliderController as sliderCtrl',
    restrict: 'AE',
    templateUrl: 'gmf/layertree/timesliderComponent',
    link: {
      pre: function preLink(scope, element, attrs, ctrl) {
        if (!ctrl) {
          throw new Error('Missing ctrl');
        }
        ctrl.init();

        ctrl.sliderOptions.stop = onSliderReleased_;
        ctrl.sliderOptions.slide = ctrl.ngeoDebounce_(onSliderReleased_, 300, true);

        /**
         * @param {never} e
         * @param {never} sliderUi
         */
        function onSliderReleased_(e, sliderUi) {
          if (!ctrl) {
            throw new Error('Missing ctrl');
          }
          ctrl.onDateSelected({
            time: computeDates_(e, sliderUi),
          });
          scope.$apply();
        }

        /**
         * @param {never} e
         * @param {{value: ?string, values: ?string[]}} sliderUi
         */
        function computeDates_(e, sliderUi) {
          if (!ctrl) {
            throw new Error('Missing ctrl');
          }
          let sDate, eDate, wmstime;
          if (sliderUi.values) {
            sDate = new Date(ctrl.getClosestValue_(sliderUi.values[0]));
            eDate = new Date(ctrl.getClosestValue_(sliderUi.values[1]));
            ctrl.dates = [sDate, eDate];
            wmstime = {
              start: sDate.getTime(),
              end: eDate.getTime(),
            };
          } else {
            sDate = new Date(ctrl.getClosestValue_(sliderUi.value));
            ctrl.dates = sDate;
            wmstime = {
              start: sDate.getTime(),
            };
          }
          scope.$apply();
          return wmstime;
        }
      },
    },
  };
}

module.directive('gmfTimeSlider', layertreeTimeSliderComponent);

/**
 * TimeSliderController - directive controller
 * @param {import("ngeo/misc/WMSTime.js").WMSTime} ngeoWMSTime WMSTime service.
 * @param {import("ngeo/misc/debounce.js").miscDebounce<function(): void>} ngeoDebounce ngeo Debounce factory.
 * @constructor
 * @private
 * @hidden
 * @ngInject
 * @ngdoc controller
 * @ngname gmfTimeSliderController
 */
function Controller(ngeoWMSTime, ngeoDebounce) {
  /**
   * @type {import("ngeo/misc/WMSTime.js").WMSTime}
   */
  this.ngeoWMSTime_ = ngeoWMSTime;

  /**
   * @type {import("ngeo/misc/debounce.js").miscDebounce<function(): void>}
   */
  this.ngeoDebounce_ = ngeoDebounce;

  /**
   * Function called after date(s) changed/selected
   * @type {function(unknown): unknown}
   */
  this.onDateSelected = () => undefined;

  /**
   * A time object for directive initialization
   * @type {?import('ngeo/datasource/OGC.js').TimeProperty}
   */
  this.time = null;

  /**
   * If the component is used to select a date range
   * @type {boolean}
   */
  this.isModeRange = false;

  /**
   * Minimal value of the slider (time in ms)
   * @type {number}
   */
  this.minValue = -1;

  /**
   * Maximal value of the slider (time in ms)
   * @type {number}
   */
  this.maxValue = 999999;

  /**
   * Used when WMS time object has a property 'values' instead of an interval
   * @type {?number[]}
   */
  this.timeValueList = null;

  /**
   * Default Slider options (used by ui-slider directive)
   * @type {?{
   *  range : boolean,
   *  min : number,
   *  max : number
   * }}
   */
  this.sliderOptions = null;

  /**
   * Model for the ui-slider directive (date in ms format)
   * @type {number[]|number}
   */
  this.dates = [];
}

/**
 * Initialise the controller.
 */
Controller.prototype.init = function () {
  if (!this.time) {
    throw new Error('Missing time');
  }

  // Fetch the initial options for the component
  const initialOptions_ = this.ngeoWMSTime_.getOptions(this.time);
  this.isModeRange = this.time.mode === 'range';
  this.minValue = initialOptions_.minDate;
  this.maxValue = initialOptions_.maxDate;
  const values = initialOptions_.values;
  if (this.isModeRange) {
    if (!Array.isArray(values)) {
      throw new Error('Wrong Options values');
    }
    this.dates = [values[0], values[1]];
  } else {
    this.dates = initialOptions_.values;
  }
  this.timeValueList = this.getTimeValueList_();
  this.sliderOptions = {
    range: this.isModeRange,
    min: this.minValue,
    max: this.maxValue,
  };
};

/**
 * TimeSliderController.prototype.getTimeValueList_ - Get a list of time value instead
 * of using the wmstime interval as a list of possibles values
 * @return {number[]}  - List of timestamp representing possible values
 */
Controller.prototype.getTimeValueList_ = function () {
  if (!this.time) {
    throw new Error('Missing time');
  }
  const wmsTime = this.time;
  /** @type {number[]} */
  let timeValueList = [];
  const minDate = new Date(this.minValue);
  const maxDate = new Date(this.maxValue);

  if (wmsTime.values) {
    timeValueList = [];
    wmsTime.values.forEach((date) => {
      timeValueList.push(new Date(date).getTime());
    });
  } else {
    let maxNbValues = 1024;
    // if maxNbOfValues is too small with day resolution the end date will be smaller than the maxDate and no values added to the timeValueList
    if (wmsTime.resolution == 'day') {
      maxNbValues *= 12;
    }
    const endDate = new Date(minDate.getTime());
    endDate.setFullYear(minDate.getFullYear() + maxNbValues * wmsTime.interval[0]);
    endDate.setMonth(
      minDate.getMonth() + maxNbValues * wmsTime.interval[1],
      minDate.getDate() + maxNbValues * wmsTime.interval[2]
    );
    endDate.setSeconds(minDate.getSeconds() + maxNbValues * wmsTime.interval[3]);

    if (endDate > maxDate) {
      // Transform interval to a list of values when the number
      // of values is below a threshold (maxNbValues)
      timeValueList = [];
      for (let i = 0; ; i++) {
        const nextDate = new Date(minDate.getTime());
        nextDate.setFullYear(minDate.getFullYear() + i * wmsTime.interval[0]);
        nextDate.setMonth(
          minDate.getMonth() + i * wmsTime.interval[1],
          minDate.getDate() + i * wmsTime.interval[2]
        );
        nextDate.setSeconds(minDate.getSeconds() + i * wmsTime.interval[3]);
        if (nextDate <= maxDate) {
          timeValueList.push(nextDate.getTime());
        } else {
          break;
        }
      }
    }
  }
  return timeValueList;
};

/**
 * Compute the closest available date from the given timestamp
 * @param  {number} timestamp selected datetime (in ms format)
 * @return {number} the closest available datetime (in ms format) from the timestamp
 * @private
 */
Controller.prototype.getClosestValue_ = function (timestamp) {
  if (!this.time) {
    throw new Error('Missing time');
  }
  if (timestamp <= this.minValue) {
    return this.minValue;
  }

  if (timestamp >= this.maxValue) {
    return this.maxValue;
  }

  if (this.timeValueList) {
    // Time stops are defined as a list of values
    let index;
    let leftIndex = 0;
    let rightIndex = this.timeValueList.length - 1;

    while (rightIndex - leftIndex > 1) {
      index = Math.floor((leftIndex + rightIndex) / 2);
      if (this.timeValueList[index] >= timestamp) {
        rightIndex = index;
      } else {
        leftIndex = index;
      }
    }

    const leftDistance = Math.abs(this.timeValueList[leftIndex] - timestamp);
    const rightDistance = Math.abs(this.timeValueList[rightIndex] - timestamp);

    return this.timeValueList[leftDistance < rightDistance ? leftIndex : rightIndex];
  } else {
    // Time stops are defined by a start date plus an interval
    const targetDate = new Date(timestamp);
    const startDate = new Date(this.minValue);
    let bestDate = new Date(this.minValue);
    const maxDate = new Date(this.maxValue);
    let bestDistance = Math.abs(targetDate.getTime() - bestDate.getTime());

    for (let i = 1; ; i++) {
      // The start date should always be used as a reference
      // because adding a month twice could differ from adding
      // two months at once
      const next = new Date(startDate.getTime());
      next.setFullYear(startDate.getFullYear() + i * this.time.interval[0]);
      next.setMonth(
        startDate.getMonth() + i * this.time.interval[1],
        startDate.getDate() + i * this.time.interval[2]
      );
      next.setSeconds(startDate.getSeconds() + i * this.time.interval[3]);

      if (next > maxDate) {
        break;
      }

      const distance = Math.abs(targetDate.getTime() - next.getTime());
      if (distance <= bestDistance) {
        bestDate = next;
        bestDistance = distance;
      } else {
        break;
      }
    }

    return bestDate.getTime();
  }
};

/**
 * Format and localize time regarding a resolution.
 * @param {number} time (in ms format) timestamp to format and localize.
 * @return {string} Localized date string regarding the resolution.
 */
Controller.prototype.getLocalizedDate = function (time) {
  if (!this.time) {
    throw new Error('Missing time');
  }
  return this.ngeoWMSTime_.formatTimeValue(time, this.time.resolution);
};

module.controller('gmfTimeSliderController', Controller);

export default module;
<|endoftext|>


<commit_before>'use strict';

var unitsForRegex = [
    "miles?",
    "foot",
    "feet",
    "ft",
    "fahrenheit",
    "yards?",
    "gallons?",
    "ounce",
    "oz",
    "pounds?",
    "inches",
    "inch",
    "in"
],
    pluralize = require('pluralize'),
    fsm = require('./fsmsearch.js');
pluralize.addIrregularRule('Celsius', 'Celsius');

function log(str, obj) {
    console.log(str + ": " + JSON.stringify(obj, null, 4));
}

function reduceImperialUnitNames(name) {
    var abbrevs = {
        "oz": "ounce",
        "in": "inch",
        "ft": "foot"
    };
    if (abbrevs.hasOwnProperty(name)) {
        return abbrevs[name];
    }
    return name;
}

function roundDecimal(decimalStr, pos) {
    var dec = decimalStr.substring(0, pos).replace(/0+$/g, ''),
        trailing = decimalStr.substring(pos, pos + 1),
        rounded,
        carry = 0;
    if (trailing && parseInt(trailing, 10) >= 5) {
        rounded = parseFloat("0." + dec) + Math.pow(10, -pos);
        if (rounded >= 1.0) {
            carry = 1;
        }
        return {
            decimal: rounded.toString().substring(2, 2 + pos),
            carry: carry
        };
    }
    return {
        decimal: dec,
        carry: 0
    };
}

function roundForReadability(num) {
    var strRepr = num.toString(),
        dot = strRepr.indexOf("."),
        parts,
        whole,
        numSigDigits = 0,
        roundResult,
        strResult;
    if (dot === -1) {
        return num;
    }
    parts = strRepr.split(".");
    whole = parseInt(parts[0], 10);
    if (whole < 100) {
        numSigDigits = 1;
    }
    if (whole < 10) {
        numSigDigits = 2;
    }
    roundResult = roundDecimal(parts[1], numSigDigits);
    strResult = (whole + roundResult.carry).toString();
    if (roundResult.decimal !== "") {
        strResult = strResult + "." + roundResult.decimal;
    }
    return strResult;
}

function pluralizeUnits(units, value) {
    return pluralize(units, value);
}

function makeReadable(value, unit) {
    var unitMap = {
        "mile": "kilometer",
        "foot": "meter",
        "fahrenheit": "Celsius",
        "yard": "meter",
        "gallon": "liter",
        "ounce": "gram",
        "pound": "kilogram",
        "inch": "centimeter"
    },
        siUnit = unitMap[unit];
    return roundForReadability(value) + " " + pluralizeUnits(siUnit, value);
}

function readableUnits(value, unit) {
    var unitMap = {
        "mile": "kilometer",
        "foot": "meter",
        "fahrenheit": "Celsius",
        "yard": "meter",
        "gallon": "liter",
        "ounce": "gram",
        "pound": "kilogram",
        "inch": "centimeter"
    },
        siUnit = unitMap[unit];
    return pluralizeUnits(siUnit, value);
}

function convertImperialToSI(units, value) {
    var converters = {
        "mile": function(value) {
            return value * 1.60934;
        },
        "foot": function(value) {
            return value * 0.3048;
        },
        "fahrenheit": function(value) {
            return Math.round((value - 32) / 1.8);
        },
        "yard": function(value) {
            return value * 0.9144;
        },
        "gallon": function(value) {
            return value * 3.78541;
        },
        "ounce": function(value) {
            return value * 28.3495;
        },
        "pound": function(value) {
            return value * 0.453592;
        },
        "inch": function(value) {
            return value * 2.54;
        }
    },
        reducedUnit = reduceImperialUnitNames(units),
        converter = converters[reducedUnit];
    if (converter) {
        return makeReadable(converter(value), reducedUnit);
    }
    return value + " " + units;
}

function convertValueToSI(units, value) {
    var converters = {
        "mile": function(value) {
            return value * 1.60934;
        },
        "foot": function(value) {
            return value * 0.3048;
        },
        "fahrenheit": function(value) {
            return Math.round((value - 32) / 1.8);
        },
        "yard": function(value) {
            return value * 0.9144;
        },
        "gallon": function(value) {
            return value * 3.78541;
        },
        "ounce": function(value) {
            return value * 28.3495;
        },
        "pound": function(value) {
            return value * 0.453592;
        },
        "inch": function(value) {
            return value * 2.54;
        }
    },
        reducedUnit = reduceImperialUnitNames(units),
        converter = converters[reducedUnit];
    if (converter) {
        return roundForReadability(converter(value));
    }
    return value;
}

function singularizeUnits(units) {
    return units.replace("miles", "mile")
        .replace("feet", "foot")
        .replace("fahrenheits", "fahrenheit")
        .replace("yards", "yard")
        .replace("gallons", "gallon")
        .replace("ounces", "ounce")
        .replace("pounds", "pound")
        .replace("inches", "inch");
}

function interpretNum(what) {
    what = what.replace(",", "");
    return parseFloat(what);
}

function multisearch(where) {
    var units = unitsForRegex.join("|"),
        numberRe = "([\\d,]*\\.?\\d+)",
        re = new RegExp(numberRe + "[\\s-]*(" + units + ")", "gi"),
        result,
        results = [],
        preceding;
    while (true) {
        result = re.exec(where);
        if (result === null) {
            break;
        }
        if (result.index > 0) {
            preceding = where.substring(result.index - 1, result.index);
            // Only whitespace and punctuation can immediately precede the
            // number. Make sure that's the case:
            if (preceding.search(/[\s,.?!'"+\-]/) !== 0) {
                continue;
            }
        }
        results.push({
            index: result.index,
            match: result[0],
            units: singularizeUnits(result[2].toLowerCase()),
            numeral: interpretNum(result[1])
        });
    }
    return results;
}

function splitBySearchResults(text, matches) {
    var i = 0,
        match,
        plainText,
        si,
        textIndex = 0,
        results = [];
    while (i < matches.length) {
        match = matches[i];
        plainText = text.substring(textIndex, match.index);
        results.push({
            text: plainText,
            altered: false
        });

        si = convertImperialToSI(match.units, match.numeral);
        textIndex = match.index + match.match.length;
        results.push({
            text: si,
            altered: true
        });

        i += 1;
    }
    if (textIndex < text.length) {
        results.push({
            text: text.substring(textIndex),
            altered: false
        });
    }
    return results;
}

function substituteBySearchResults(data, matches) {
    var results = [],
        textIndex = 0;
    matches.forEach(function(match) {
        var si = convertValueToSI(match.units, match.numeral),
            siUnit = readableUnits(si, reduceImperialUnitNames(match.units));
        match.fragments.forEach(function(frag) {
            var lastText = data[frag.origNode].text,
                plainText = "";
            if (textIndex < frag.index) {
                plainText = lastText.substring(textIndex, frag.index);
                console.log("plainText = '" + plainText + "' (" + textIndex + ", " + frag.index + ")");
                results.push({
                    origNode: frag.origNode,
                    replacement: {
                        text: plainText,
                        altered: false
                }});
            }
            textIndex = frag.index + frag.match.length;
            if (frag.fragType === "numeral") {
                results.push({
                    origNode: frag.origNode,
                    replacement: {
                        text: si.toString(),
                        altered: true
                }});
            } else {
                results.push({
                    origNode: frag.origNode,
                    replacement: {
                        text: siUnit,
                        altered: true
                }});
            }
            if (textIndex < lastText.length) {
                results.push({
                    origNode: frag.origNode,
                    replacement: {
                        text: lastText.substring(textIndex),
                        altered: false
                }});
            }
        });
    });
    return results;
}

function multisearchTextNodes(nodes) {
    var resultArray = [];
    nodes.forEach(function(node) {
        var text = node.text,
            searchResults = multisearch(text);
        if (searchResults.length !== 0) {
            resultArray.push({
                origNode: node.index,
                replacement: splitBySearchResults(text, searchResults)
            });
        }
    });
    return resultArray;
}

function coalesce(data) {
    var result = [],
        newItem = {
            origNode: -1,
            replacement: []
        };
    data.forEach(function(item) {
        if (newItem.origNode === -1) {
            newItem.origNode = item.origNode;
            newItem.replacement.push(item.replacement);
            return;
        }
        if (newItem.origNode === item.origNode) {
            newItem.replacement.push(item.replacement);
            return;
        }
        result.push(newItem);
        newItem = {
            origNode: item.origNode,
            replacement: []
        };
        newItem.replacement.push(item.replacement);
    });
    result.push(newItem);
    return result;
}

function patchSingleNode(node, nodeIndex, matches) {
    var results = [],
        textIndex = 0;
    matches.forEach(function(match) {
        var si = convertValueToSI(match.units, match.numeral),
            siUnit = readableUnits(si, reduceImperialUnitNames(match.units)),
            frag = match.fragments[0];
        // TODO: there's quite a bit of duplication going on here, should be
        // possible to DRY a bit
        if (match.continuous) {
            if (textIndex < frag.index) {
                results.push({
                    origNode: frag.origNode,
                    replacement: {
                        text: node.substring(textIndex, frag.index),
                    altered: false
                    }});
            }
            results.push({
                origNode: frag.origNode,
                replacement: {
                    text: si.toString() + " " + siUnit,
                    altered: true
            }});
            textIndex = frag.index + frag.match.length;
            return;
        }
        match.fragments.forEach(function(frag) {
            if (frag.origNode !== nodeIndex) {
                return;
            }
            if (textIndex < frag.index) {
                results.push({
                    origNode: frag.origNode,
                    replacement: {
                        text: node.substring(textIndex, frag.index),
                        altered: false
                }});
            }
            textIndex = frag.index + frag.match.length;
            if (frag.fragType === "numeral") {
                results.push({
                    origNode: frag.origNode,
                    replacement: {
                        text: si.toString(),
                        altered: true
                }});
            } else {
                results.push({
                    origNode: frag.origNode,
                    replacement: {
                        text: siUnit,
                        altered: true
                }});
            }
        });
    });
    if (textIndex < node.length) {
        results.push({
            origNode: nodeIndex,
            replacement: {
                text: node.substring(textIndex),
                altered: false
        }});
    }
    return results;
}

function substituteBySearchResults(nodes, nodeMap) {
    var results = [];
    nodes.forEach(function(node) {
        if (nodeMap.hasOwnProperty(node.index)) {
            var idx = node.index,
                arr = patchSingleNode(node.text, idx, nodeMap[idx]);
            Array.prototype.push.apply(results, arr);
        }
    });
    return results;
}

function resultsToNodeMap(fsmResults) {
    var dict = {};
    fsmResults.forEach(function(result) {
        result.fragments.forEach(function(frag) {
            if (!dict.hasOwnProperty(frag.origNode)) {
                dict[frag.origNode] = [result];
            } else {
                // XXX: this is quite inefficient:
                if (dict[frag.origNode].indexOf(result) === -1) {
                    dict[frag.origNode].push(result);
                }
            }
        });
    });
    return dict;
}

chrome.runtime.onMessage.addListener(function(rq, sender, sendResponse) {
    var value = {},
        fsmResults,
        tmp = null,
        tmp2 = null,
        tmp3 = null;
    if (rq.method === "checkbox-state") {
        value[rq.id] = rq.state;
        chrome.storage.local.set(value, function () {
            log("chrome.storage.local.set", rq);
        });
        sendResponse({success: true});
    } else if (rq.method === "get-ui-state") {
        // get(null) retrieves whole storage
        chrome.storage.local.get(null, function (result) {
            log("chrome.storage.local.get", result);
            sendResponse(result);
        });
    } else if (rq.method === "text-for-processing") {
        log("text-for-processing", rq.data);
        fsmResults = fsm.search(rq.data);
        log("fsm search results", fsmResults);
        tmp = multisearchTextNodes(rq.data);
        log("multisearchTextNodes", tmp);
        tmp2 = substituteBySearchResults(rq.data, resultsToNodeMap(fsmResults));
        log("fsm processed results", tmp2);
        tmp3 = coalesce(tmp2);
        log("coalesced results", tmp3);
        sendResponse(tmp3);
    } else {
        sendResponse({error: true});
    }
    return true;
});

module.exports = {
    reduceImperialUnitNames: reduceImperialUnitNames,
    unitsForRegex: unitsForRegex,
    convertImperialToSI: convertImperialToSI,
    singularizeUnits: singularizeUnits,
    multisearch: multisearch,
    multisearchTextNodes: multisearchTextNodes,
    roundForReadability: roundForReadability,
    roundDecimal: roundDecimal,
    splitBySearchResults: splitBySearchResults,
    pluralizeUnits: pluralizeUnits,
    splitWords: fsm.splitWords,
    fsmSearch: fsm.search,
    substituteBySearchResults: substituteBySearchResults,
    patchSingleNode: patchSingleNode,
    resultsToNodeMap: resultsToNodeMap
};
<commit_msg>Cut some duplication<commit_after>'use strict';

var unitsForRegex = [
    "miles?",
    "foot",
    "feet",
    "ft",
    "fahrenheit",
    "yards?",
    "gallons?",
    "ounce",
    "oz",
    "pounds?",
    "inches",
    "inch",
    "in"
],
    pluralize = require('pluralize'),
    fsm = require('./fsmsearch.js');
pluralize.addIrregularRule('Celsius', 'Celsius');

function log(str, obj) {
    console.log(str + ": " + JSON.stringify(obj, null, 4));
}

function reduceImperialUnitNames(name) {
    var abbrevs = {
        "oz": "ounce",
        "in": "inch",
        "ft": "foot"
    };
    if (abbrevs.hasOwnProperty(name)) {
        return abbrevs[name];
    }
    return name;
}

function roundDecimal(decimalStr, pos) {
    var dec = decimalStr.substring(0, pos).replace(/0+$/g, ''),
        trailing = decimalStr.substring(pos, pos + 1),
        rounded,
        carry = 0;
    if (trailing && parseInt(trailing, 10) >= 5) {
        rounded = parseFloat("0." + dec) + Math.pow(10, -pos);
        if (rounded >= 1.0) {
            carry = 1;
        }
        return {
            decimal: rounded.toString().substring(2, 2 + pos),
            carry: carry
        };
    }
    return {
        decimal: dec,
        carry: 0
    };
}

function roundForReadability(num) {
    var strRepr = num.toString(),
        dot = strRepr.indexOf("."),
        parts,
        whole,
        numSigDigits = 0,
        roundResult,
        strResult;
    if (dot === -1) {
        return num;
    }
    parts = strRepr.split(".");
    whole = parseInt(parts[0], 10);
    if (whole < 100) {
        numSigDigits = 1;
    }
    if (whole < 10) {
        numSigDigits = 2;
    }
    roundResult = roundDecimal(parts[1], numSigDigits);
    strResult = (whole + roundResult.carry).toString();
    if (roundResult.decimal !== "") {
        strResult = strResult + "." + roundResult.decimal;
    }
    return strResult;
}

function pluralizeUnits(units, value) {
    return pluralize(units, value);
}

function makeReadable(value, unit) {
    var unitMap = {
        "mile": "kilometer",
        "foot": "meter",
        "fahrenheit": "Celsius",
        "yard": "meter",
        "gallon": "liter",
        "ounce": "gram",
        "pound": "kilogram",
        "inch": "centimeter"
    },
        siUnit = unitMap[unit];
    return roundForReadability(value) + " " + pluralizeUnits(siUnit, value);
}

function readableUnits(value, unit) {
    var unitMap = {
        "mile": "kilometer",
        "foot": "meter",
        "fahrenheit": "Celsius",
        "yard": "meter",
        "gallon": "liter",
        "ounce": "gram",
        "pound": "kilogram",
        "inch": "centimeter"
    },
        siUnit = unitMap[unit];
    return pluralizeUnits(siUnit, value);
}

function convertImperialToSI(units, value) {
    var converters = {
        "mile": function(value) {
            return value * 1.60934;
        },
        "foot": function(value) {
            return value * 0.3048;
        },
        "fahrenheit": function(value) {
            return Math.round((value - 32) / 1.8);
        },
        "yard": function(value) {
            return value * 0.9144;
        },
        "gallon": function(value) {
            return value * 3.78541;
        },
        "ounce": function(value) {
            return value * 28.3495;
        },
        "pound": function(value) {
            return value * 0.453592;
        },
        "inch": function(value) {
            return value * 2.54;
        }
    },
        reducedUnit = reduceImperialUnitNames(units),
        converter = converters[reducedUnit];
    if (converter) {
        return makeReadable(converter(value), reducedUnit);
    }
    return value + " " + units;
}

function convertValueToSI(units, value) {
    var converters = {
        "mile": function(value) {
            return value * 1.60934;
        },
        "foot": function(value) {
            return value * 0.3048;
        },
        "fahrenheit": function(value) {
            return Math.round((value - 32) / 1.8);
        },
        "yard": function(value) {
            return value * 0.9144;
        },
        "gallon": function(value) {
            return value * 3.78541;
        },
        "ounce": function(value) {
            return value * 28.3495;
        },
        "pound": function(value) {
            return value * 0.453592;
        },
        "inch": function(value) {
            return value * 2.54;
        }
    },
        reducedUnit = reduceImperialUnitNames(units),
        converter = converters[reducedUnit];
    if (converter) {
        return roundForReadability(converter(value));
    }
    return value;
}

function singularizeUnits(units) {
    return units.replace("miles", "mile")
        .replace("feet", "foot")
        .replace("fahrenheits", "fahrenheit")
        .replace("yards", "yard")
        .replace("gallons", "gallon")
        .replace("ounces", "ounce")
        .replace("pounds", "pound")
        .replace("inches", "inch");
}

function interpretNum(what) {
    what = what.replace(",", "");
    return parseFloat(what);
}

function multisearch(where) {
    var units = unitsForRegex.join("|"),
        numberRe = "([\\d,]*\\.?\\d+)",
        re = new RegExp(numberRe + "[\\s-]*(" + units + ")", "gi"),
        result,
        results = [],
        preceding;
    while (true) {
        result = re.exec(where);
        if (result === null) {
            break;
        }
        if (result.index > 0) {
            preceding = where.substring(result.index - 1, result.index);
            // Only whitespace and punctuation can immediately precede the
            // number. Make sure that's the case:
            if (preceding.search(/[\s,.?!'"+\-]/) !== 0) {
                continue;
            }
        }
        results.push({
            index: result.index,
            match: result[0],
            units: singularizeUnits(result[2].toLowerCase()),
            numeral: interpretNum(result[1])
        });
    }
    return results;
}

function splitBySearchResults(text, matches) {
    var i = 0,
        match,
        plainText,
        si,
        textIndex = 0,
        results = [];
    while (i < matches.length) {
        match = matches[i];
        plainText = text.substring(textIndex, match.index);
        results.push({
            text: plainText,
            altered: false
        });

        si = convertImperialToSI(match.units, match.numeral);
        textIndex = match.index + match.match.length;
        results.push({
            text: si,
            altered: true
        });

        i += 1;
    }
    if (textIndex < text.length) {
        results.push({
            text: text.substring(textIndex),
            altered: false
        });
    }
    return results;
}

function substituteBySearchResults(data, matches) {
    var results = [],
        textIndex = 0;
    matches.forEach(function(match) {
        var si = convertValueToSI(match.units, match.numeral),
            siUnit = readableUnits(si, reduceImperialUnitNames(match.units));
        match.fragments.forEach(function(frag) {
            var lastText = data[frag.origNode].text,
                plainText = "";
            if (textIndex < frag.index) {
                plainText = lastText.substring(textIndex, frag.index);
                console.log("plainText = '" + plainText + "' (" + textIndex + ", " + frag.index + ")");
                results.push({
                    origNode: frag.origNode,
                    replacement: {
                        text: plainText,
                        altered: false
                }});
            }
            textIndex = frag.index + frag.match.length;
            if (frag.fragType === "numeral") {
                results.push({
                    origNode: frag.origNode,
                    replacement: {
                        text: si.toString(),
                        altered: true
                }});
            } else {
                results.push({
                    origNode: frag.origNode,
                    replacement: {
                        text: siUnit,
                        altered: true
                }});
            }
            if (textIndex < lastText.length) {
                results.push({
                    origNode: frag.origNode,
                    replacement: {
                        text: lastText.substring(textIndex),
                        altered: false
                }});
            }
        });
    });
    return results;
}

function multisearchTextNodes(nodes) {
    var resultArray = [];
    nodes.forEach(function(node) {
        var text = node.text,
            searchResults = multisearch(text);
        if (searchResults.length !== 0) {
            resultArray.push({
                origNode: node.index,
                replacement: splitBySearchResults(text, searchResults)
            });
        }
    });
    return resultArray;
}

function coalesce(data) {
    var result = [],
        newItem = {
            origNode: -1,
            replacement: []
        };
    data.forEach(function(item) {
        if (newItem.origNode === -1) {
            newItem.origNode = item.origNode;
            newItem.replacement.push(item.replacement);
            return;
        }
        if (newItem.origNode === item.origNode) {
            newItem.replacement.push(item.replacement);
            return;
        }
        result.push(newItem);
        newItem = {
            origNode: item.origNode,
            replacement: []
        };
        newItem.replacement.push(item.replacement);
    });
    result.push(newItem);
    return result;
}

function patchSingleNode(node, nodeIndex, matches) {
    var results = [],
        textIndex = 0;
    matches.forEach(function(match) {
        var si = convertValueToSI(match.units, match.numeral),
            siUnit = readableUnits(si, reduceImperialUnitNames(match.units));
        match.fragments.forEach(function(frag) {
            if (frag.origNode !== nodeIndex) {
                return;
            }
            if (textIndex < frag.index) {
                results.push({
                    origNode: frag.origNode,
                    replacement: {
                        text: node.substring(textIndex, frag.index),
                        altered: false
                }});
            }
            textIndex = frag.index + frag.match.length;
            if (match.continuous) {
                results.push({
                    origNode: frag.origNode,
                    replacement: {
                        text: si.toString() + " " + siUnit,
                        altered: true
                }});
                return;
            }
            if (frag.fragType === "numeral") {
                results.push({
                    origNode: frag.origNode,
                    replacement: {
                        text: si.toString(),
                        altered: true
                }});
            } else {
                results.push({
                    origNode: frag.origNode,
                    replacement: {
                        text: siUnit,
                        altered: true
                }});
            }
        });
    });
    if (textIndex < node.length) {
        results.push({
            origNode: nodeIndex,
            replacement: {
                text: node.substring(textIndex),
                altered: false
        }});
    }
    return results;
}

function substituteBySearchResults(nodes, nodeMap) {
    var results = [];
    nodes.forEach(function(node) {
        if (nodeMap.hasOwnProperty(node.index)) {
            var idx = node.index,
                arr = patchSingleNode(node.text, idx, nodeMap[idx]);
            Array.prototype.push.apply(results, arr);
        }
    });
    return results;
}

function resultsToNodeMap(fsmResults) {
    var dict = {};
    fsmResults.forEach(function(result) {
        result.fragments.forEach(function(frag) {
            if (!dict.hasOwnProperty(frag.origNode)) {
                dict[frag.origNode] = [result];
            } else {
                // XXX: this is quite inefficient:
                if (dict[frag.origNode].indexOf(result) === -1) {
                    dict[frag.origNode].push(result);
                }
            }
        });
    });
    return dict;
}

chrome.runtime.onMessage.addListener(function(rq, sender, sendResponse) {
    var value = {},
        fsmResults,
        tmp = null,
        tmp2 = null,
        tmp3 = null;
    if (rq.method === "checkbox-state") {
        value[rq.id] = rq.state;
        chrome.storage.local.set(value, function () {
            log("chrome.storage.local.set", rq);
        });
        sendResponse({success: true});
    } else if (rq.method === "get-ui-state") {
        // get(null) retrieves whole storage
        chrome.storage.local.get(null, function (result) {
            log("chrome.storage.local.get", result);
            sendResponse(result);
        });
    } else if (rq.method === "text-for-processing") {
        log("text-for-processing", rq.data);
        fsmResults = fsm.search(rq.data);
        log("fsm search results", fsmResults);
        tmp = multisearchTextNodes(rq.data);
        log("multisearchTextNodes", tmp);
        tmp2 = substituteBySearchResults(rq.data, resultsToNodeMap(fsmResults));
        log("fsm processed results", tmp2);
        tmp3 = coalesce(tmp2);
        log("coalesced results", tmp3);
        sendResponse(tmp3);
    } else {
        sendResponse({error: true});
    }
    return true;
});

module.exports = {
    reduceImperialUnitNames: reduceImperialUnitNames,
    unitsForRegex: unitsForRegex,
    convertImperialToSI: convertImperialToSI,
    singularizeUnits: singularizeUnits,
    multisearch: multisearch,
    multisearchTextNodes: multisearchTextNodes,
    roundForReadability: roundForReadability,
    roundDecimal: roundDecimal,
    splitBySearchResults: splitBySearchResults,
    pluralizeUnits: pluralizeUnits,
    splitWords: fsm.splitWords,
    fsmSearch: fsm.search,
    substituteBySearchResults: substituteBySearchResults,
    patchSingleNode: patchSingleNode,
    resultsToNodeMap: resultsToNodeMap
};
<|endoftext|>


<commit_before>var scene, camera, renderer;

//var WIDTH  = window.innerWidth;
//var HEIGHT = window.innerHeight;

var WIDTH  = 800;
var HEIGHT = 600;

var SPEED = 0.01;

var mesh = null;

function init() {
    scene = new THREE.Scene();

    //initCube();
    initGolfinho();
    initCamera();
    initRenderer();

    document.body.appendChild(renderer.domElement);
}

function initCamera() {
    camera = new THREE.PerspectiveCamera(70, WIDTH / HEIGHT, 1, 10);
    camera.position.set(0, 3.5, 5);
    camera.lookAt(scene.position);
}

function initRenderer() {
    renderer = new THREE.WebGLRenderer({ antialias: true });
    renderer.setSize(WIDTH, HEIGHT);
}

function initCube() {
    cube = new THREE.Mesh(new THREE.CubeGeometry(2, 2, 2), new THREE.MeshNormalMaterial());
    scene.add(cube);
}

function rotateCube() {
    cube.rotation.x -= SPEED * 2;
    cube.rotation.y -= SPEED;
    cube.rotation.z -= SPEED * 3;
}

function render() {
    requestAnimationFrame(render);
    rotateGolfinho();
    //rotateCube();
    renderer.render(scene, camera);
}

function initGolfinho(){
     var loader = new THREE.JSONLoader();
    loader.load('../mesh/golfinho.json', function(geometry, materials) {
        mesh = new THREE.Mesh(geometry, new THREE.MeshFaceMaterial(materials));
        mesh.scale.x = mesh.scale.y = mesh.scale.z = 0.75;
        mesh.translation = THREE.GeometryUtils.center(geometry);
        scene.add(mesh);
    });
}

function rotateGolfinho() {
    if (!mesh) {
        return;
    }

    mesh.rotation.x -= SPEED * 2;
    mesh.rotation.y -= SPEED;
    mesh.rotation.z -= SPEED * 3;
}

init();
render();<commit_msg>Alterando caminho do json<commit_after>var scene, camera, renderer;

//var WIDTH  = window.innerWidth;
//var HEIGHT = window.innerHeight;

var WIDTH  = 800;
var HEIGHT = 600;

var SPEED = 0.01;

var mesh = null;

function init() {
    scene = new THREE.Scene();

    //initCube();
    initGolfinho();
    initCamera();
    initRenderer();

    document.body.appendChild(renderer.domElement);
}

function initCamera() {
    camera = new THREE.PerspectiveCamera(70, WIDTH / HEIGHT, 1, 10);
    camera.position.set(0, 3.5, 5);
    camera.lookAt(scene.position);
}

function initRenderer() {
    renderer = new THREE.WebGLRenderer({ antialias: true });
    renderer.setSize(WIDTH, HEIGHT);
}

function initCube() {
    cube = new THREE.Mesh(new THREE.CubeGeometry(2, 2, 2), new THREE.MeshNormalMaterial());
    scene.add(cube);
}

function rotateCube() {
    cube.rotation.x -= SPEED * 2;
    cube.rotation.y -= SPEED;
    cube.rotation.z -= SPEED * 3;
}

function render() {
    requestAnimationFrame(render);
    rotateGolfinho();
    //rotateCube();
    renderer.render(scene, camera);
}

function initGolfinho(){
     var loader = new THREE.JSONLoader();
    loader.load('./mesh/golfinho.json', function(geometry, materials) {
        mesh = new THREE.Mesh(geometry, new THREE.MeshFaceMaterial(materials));
        mesh.scale.x = mesh.scale.y = mesh.scale.z = 0.75;
        mesh.translation = THREE.GeometryUtils.center(geometry);
        scene.add(mesh);
    });
}

function rotateGolfinho() {
    if (!mesh) {
        return;
    }

    mesh.rotation.x -= SPEED * 2;
    mesh.rotation.y -= SPEED;
    mesh.rotation.z -= SPEED * 3;
}

init();
render();<|endoftext|>