<commit_before>var async = require('async');
var CheckEvent = require('../models/checkEvent');
var Check = require('../models/check');

var PAUSED = -1;
var DOWN = 0;
var UP = 1;

/*
 * Usage:
 *   var builder = new IntervalBuilder();
 *   builder.addTarget(check1);
 *   builder.addTarget(check2);
 *   builder.build(begin, end, function(err, intervals, downtime) {
 *     // do things
 *   });
 */
var IntervalBuilder = function() {
  this.objectIds = [];
  this.nbObjects = 0;
  this.states = {};
  this.intervals = []; // intervals are [begin, end, state]
  this.currentState = null;
  this.currentInterval = [];
  this.downtime = 0;
  this.duration = 0;
};

IntervalBuilder.prototype.addTarget = function(objectId) {
  this.objectIds.push(objectId._id ? objectId._id : objectId);
  this.nbObjects++;
};

IntervalBuilder.prototype.build = function(begin, end, callback) {
  var self = this;

  async.auto({
    initialState: function(next) {
        self.determineInitialState(begin, next);
    },
    interval: ['initialState', function(next) {
        self.buildIntervalsForPeriod(begin, end, next);
    }],
    check: ['initialState', 'interval', function (next) {
        self.getCurrentCheck(next)
    }],
    duration: ['initialState', 'interval', 'check', function (next, results) {
        if (check != null) {
          self.calculateDuration(results.check, begin, end, next);
        }
    }],
    downtime: ['initialState', 'interval', 'check', 'duration', function (next, results) {
        if (check != null) {
          self.calculateDowntime(results.check, begin, end, next);
        };
    }]
    }, function(err, results) {
      if (err) {
        return callback(err);
      }

      self.duration = results.duration;
      self.downtime = results.downtime;

      return callback(null, self.intervals, self.downtime, self.duration);
  });
};

IntervalBuilder.prototype.getCurrentCheck = function(callback) {
  Check.findOne({ _id: this.objectIds[0] }, callback);
};

IntervalBuilder.prototype.isMultiTarget = function() {
  return this.nbObjects > 1;
};

IntervalBuilder.prototype.determineInitialState = function(timestamp, callback) {
  var self = this;
  async.forEach(this.objectIds, function(objectId, next) {
    CheckEvent.find()
    .where('check').equals(objectId)
    .where('timestamp').lte(timestamp)
    .sort({ timestamp: -1 })
    .findOne(function(err, checkEvent) {
      if (err) return next(err);
      if (!checkEvent) {
        // No ping ever - start the period as paused
        self.addEvent(objectId, 'paused', timestamp);
        return next();
      }
      self.addEvent(objectId, checkEvent.message);
      next();
    });
  }, function(err) {
    if (err) return callback(err);
    self.updateCurrentState(timestamp);
    return callback(null);
  });
};

IntervalBuilder.prototype.calculateDuration = function(check, begin, end, callback) {
  var durationBegin = Math.max(begin, check.firstTested),
    durationEnd = Math.min(end, check.lastTested),
    duration = durationEnd - durationBegin;

  if (this.isMultiTarget()) {
    // it's a tag - no other way
    return callback(null, duration);
  }

  this.intervals.forEach(function(interval) {
    if (interval[2] != PAUSED || interval[1] < durationBegin || interval[0] > durationEnd) {
      return;
    }

    interval[0] = Math.max(durationBegin, interval[0]);
    interval[1] = Math.min(durationEnd, interval[1]);

    duration -= interval[1] - interval[0];
  });

  return callback(null, duration);
};

IntervalBuilder.prototype.calculateDowntime = function(check, begin, end, callback) {
  var durationBegin = Math.max(begin, check.firstTested);
  var durationEnd = Math.min(end, check.lastTested);

  if (this.isMultiTarget()) {
    // it's a tag - no other way
    return callback(null, this.currentState == DOWN ? durationEnd - durationBegin : 0);
  }

  var downtime = 0;
  var currentIntervalEnd = null;
  this.intervals.forEach(function(interval) {
    currentIntervalEnd = interval[1];
    if (interval[2] != DOWN || interval[1] < durationBegin || interval[0] > durationEnd) {
      return true; // will act as a continue
    }

    interval[0] = Math.max(durationBegin, interval[0]);
    interval[1] = Math.min(durationEnd, interval[1]);

    downtime += interval[1] - interval[0];
  });

  if (!check.isUp) {
    if(currentIntervalEnd === null) {
      downtime += durationEnd - durationBegin;
    }else {
      downtime += durationEnd - currentIntervalEnd;
    }
  }

  return callback(null, downtime);
};

IntervalBuilder.prototype.buildIntervalsForPeriod = function(begin, end, callback) {
  var self = this;
  CheckEvent.find()
  .where('check').in(this.objectIds)
  .where('timestamp').gt(begin).lte(end)
  .sort({ timestamp: 1 })
  .find(function(err, checkEvents) {
    if (err) return callback(err);
    checkEvents.forEach(function(checkEvent) {
      if (self.addEvent(checkEvent.check, checkEvent.message)) {
        self.updateCurrentState(checkEvent.timestamp);
      }
    });
    callback(null);
  });
};

/**
 * Add an event for a given check.
 *
 * Returns true if the event modifies the state of a given check, false otherwise
 */
IntervalBuilder.prototype.addEvent = function(objectId, message) {
  switch (message) {
    case 'up':
      if (!this.isUp(objectId)) {
        this.states[objectId] = UP;
        return true;
      }
      break;
    case 'down':
      if (!this.isDown(objectId)) {
        this.states[objectId] = DOWN;
        return true;
      }
      break;
    case 'paused':
    case 'restarted':
    default:
      if (!this.isPaused(objectId)) {
        this.states[objectId] = PAUSED;
        return true;
      }
      break;
  }
  return false;
};

/**
 * Return true if the global state was updated, false otherwise.
 */
IntervalBuilder.prototype.updateCurrentState = function(timestamp) {
  timestamp = this.getTimestamp(timestamp);
  var currentState = this.getGlobalState();
  if (currentState !== this.currentState) {
    this.completeCurrentInterval(timestamp);
    this.currentInterval = currentState == 1 ? [] : [timestamp];
    this.currentState = currentState;
    return true;
  }
  return false;
};

IntervalBuilder.prototype.getGlobalState = function() {
  var ups = 0;
  var paused = 0;
  for (var objectId in this.states) {
    if (this.isUp(objectId)) ups++;
    if (this.isPaused(objectId)) paused++;
  }
  if (!this.isMultiTarget() && paused > 0) {
    return PAUSED; // global state is paused because there is only one check
  }
  if ((ups + paused) == this.nbObjects) {
    return UP; // ignore paused in multiTarget
  }
  return DOWN; // at least one of the targets is paused
};

IntervalBuilder.prototype.isUp = function(objectId) {
  return this.states[objectId] == UP;
};

IntervalBuilder.prototype.isDown = function(objectId) {
  return this.states[objectId] == DOWN;
};

IntervalBuilder.prototype.isPaused = function(objectId) {
  return this.states[objectId] == PAUSED;
};

IntervalBuilder.prototype.getTimestamp = function(date) {
  return date.valueOf ? date.valueOf() : date;
};

IntervalBuilder.prototype.completeCurrentInterval = function(timestamp) {
  if (this.currentInterval.length != 1) {
    // no current interval - ignore
    return false;
  }
  if (this.currentState == DOWN) {
    this.downtime += timestamp - this.currentInterval[0];
  }

  this.intervals.push(this.currentInterval.concat([timestamp, this.currentState]));
  return true;
};

module.exports = IntervalBuilder;
<commit_msg>Add typeof check<commit_after>var async = require('async');
var CheckEvent = require('../models/checkEvent');
var Check = require('../models/check');

var PAUSED = -1;
var DOWN = 0;
var UP = 1;

/*
 * Usage:
 *   var builder = new IntervalBuilder();
 *   builder.addTarget(check1);
 *   builder.addTarget(check2);
 *   builder.build(begin, end, function(err, intervals, downtime) {
 *     // do things
 *   });
 */
var IntervalBuilder = function() {
  this.objectIds = [];
  this.nbObjects = 0;
  this.states = {};
  this.intervals = []; // intervals are [begin, end, state]
  this.currentState = null;
  this.currentInterval = [];
  this.downtime = 0;
  this.duration = 0;
};

IntervalBuilder.prototype.addTarget = function(objectId) {
  this.objectIds.push(objectId._id ? objectId._id : objectId);
  this.nbObjects++;
};

IntervalBuilder.prototype.build = function(begin, end, callback) {
  var self = this;

  async.auto({
    initialState: function(next) {
        self.determineInitialState(begin, next);
    },
    interval: ['initialState', function(next) {
        self.buildIntervalsForPeriod(begin, end, next);
    }],
    check: ['initialState', 'interval', function (next) {
        self.getCurrentCheck(next)
    }],
    duration: ['initialState', 'interval', 'check', function (next, results) {
        if (typeof check !== 'undefined' && check != null) {
          self.calculateDuration(results.check, begin, end, next);
        }
    }],
    downtime: ['initialState', 'interval', 'check', 'duration', function (next, results) {
        if (typeof check !== 'undefined' && check != null) {
          self.calculateDowntime(results.check, begin, end, next);
        };
    }]
    }, function(err, results) {
      if (err) {
        return callback(err);
      }

      self.duration = results.duration;
      self.downtime = results.downtime;

      return callback(null, self.intervals, self.downtime, self.duration);
  });
};

IntervalBuilder.prototype.getCurrentCheck = function(callback) {
  Check.findOne({ _id: this.objectIds[0] }, callback);
};

IntervalBuilder.prototype.isMultiTarget = function() {
  return this.nbObjects > 1;
};

IntervalBuilder.prototype.determineInitialState = function(timestamp, callback) {
  var self = this;
  async.forEach(this.objectIds, function(objectId, next) {
    CheckEvent.find()
    .where('check').equals(objectId)
    .where('timestamp').lte(timestamp)
    .sort({ timestamp: -1 })
    .findOne(function(err, checkEvent) {
      if (err) return next(err);
      if (!checkEvent) {
        // No ping ever - start the period as paused
        self.addEvent(objectId, 'paused', timestamp);
        return next();
      }
      self.addEvent(objectId, checkEvent.message);
      next();
    });
  }, function(err) {
    if (err) return callback(err);
    self.updateCurrentState(timestamp);
    return callback(null);
  });
};

IntervalBuilder.prototype.calculateDuration = function(check, begin, end, callback) {
  var durationBegin = Math.max(begin, check.firstTested),
    durationEnd = Math.min(end, check.lastTested),
    duration = durationEnd - durationBegin;

  if (this.isMultiTarget()) {
    // it's a tag - no other way
    return callback(null, duration);
  }

  this.intervals.forEach(function(interval) {
    if (interval[2] != PAUSED || interval[1] < durationBegin || interval[0] > durationEnd) {
      return;
    }

    interval[0] = Math.max(durationBegin, interval[0]);
    interval[1] = Math.min(durationEnd, interval[1]);

    duration -= interval[1] - interval[0];
  });

  return callback(null, duration);
};

IntervalBuilder.prototype.calculateDowntime = function(check, begin, end, callback) {
  var durationBegin = Math.max(begin, check.firstTested);
  var durationEnd = Math.min(end, check.lastTested);

  if (this.isMultiTarget()) {
    // it's a tag - no other way
    return callback(null, this.currentState == DOWN ? durationEnd - durationBegin : 0);
  }

  var downtime = 0;
  var currentIntervalEnd = null;
  this.intervals.forEach(function(interval) {
    currentIntervalEnd = interval[1];
    if (interval[2] != DOWN || interval[1] < durationBegin || interval[0] > durationEnd) {
      return true; // will act as a continue
    }

    interval[0] = Math.max(durationBegin, interval[0]);
    interval[1] = Math.min(durationEnd, interval[1]);

    downtime += interval[1] - interval[0];
  });

  if (!check.isUp) {
    if(currentIntervalEnd === null) {
      downtime += durationEnd - durationBegin;
    }else {
      downtime += durationEnd - currentIntervalEnd;
    }
  }

  return callback(null, downtime);
};

IntervalBuilder.prototype.buildIntervalsForPeriod = function(begin, end, callback) {
  var self = this;
  CheckEvent.find()
  .where('check').in(this.objectIds)
  .where('timestamp').gt(begin).lte(end)
  .sort({ timestamp: 1 })
  .find(function(err, checkEvents) {
    if (err) return callback(err);
    checkEvents.forEach(function(checkEvent) {
      if (self.addEvent(checkEvent.check, checkEvent.message)) {
        self.updateCurrentState(checkEvent.timestamp);
      }
    });
    callback(null);
  });
};

/**
 * Add an event for a given check.
 *
 * Returns true if the event modifies the state of a given check, false otherwise
 */
IntervalBuilder.prototype.addEvent = function(objectId, message) {
  switch (message) {
    case 'up':
      if (!this.isUp(objectId)) {
        this.states[objectId] = UP;
        return true;
      }
      break;
    case 'down':
      if (!this.isDown(objectId)) {
        this.states[objectId] = DOWN;
        return true;
      }
      break;
    case 'paused':
    case 'restarted':
    default:
      if (!this.isPaused(objectId)) {
        this.states[objectId] = PAUSED;
        return true;
      }
      break;
  }
  return false;
};

/**
 * Return true if the global state was updated, false otherwise.
 */
IntervalBuilder.prototype.updateCurrentState = function(timestamp) {
  timestamp = this.getTimestamp(timestamp);
  var currentState = this.getGlobalState();
  if (currentState !== this.currentState) {
    this.completeCurrentInterval(timestamp);
    this.currentInterval = currentState == 1 ? [] : [timestamp];
    this.currentState = currentState;
    return true;
  }
  return false;
};

IntervalBuilder.prototype.getGlobalState = function() {
  var ups = 0;
  var paused = 0;
  for (var objectId in this.states) {
    if (this.isUp(objectId)) ups++;
    if (this.isPaused(objectId)) paused++;
  }
  if (!this.isMultiTarget() && paused > 0) {
    return PAUSED; // global state is paused because there is only one check
  }
  if ((ups + paused) == this.nbObjects) {
    return UP; // ignore paused in multiTarget
  }
  return DOWN; // at least one of the targets is paused
};

IntervalBuilder.prototype.isUp = function(objectId) {
  return this.states[objectId] == UP;
};

IntervalBuilder.prototype.isDown = function(objectId) {
  return this.states[objectId] == DOWN;
};

IntervalBuilder.prototype.isPaused = function(objectId) {
  return this.states[objectId] == PAUSED;
};

IntervalBuilder.prototype.getTimestamp = function(date) {
  return date.valueOf ? date.valueOf() : date;
};

IntervalBuilder.prototype.completeCurrentInterval = function(timestamp) {
  if (this.currentInterval.length != 1) {
    // no current interval - ignore
    return false;
  }
  if (this.currentState == DOWN) {
    this.downtime += timestamp - this.currentInterval[0];
  }

  this.intervals.push(this.currentInterval.concat([timestamp, this.currentState]));
  return true;
};

module.exports = IntervalBuilder;
<|endoftext|>


<commit_before>/**
 * OpenFB is a micro-library that lets you integrate your JavaScript application with Facebook.
 * OpenFB works for both BROWSER-BASED apps and CORDOVA/PHONEGAP apps.
 * This library has no dependency: You don't need (and shouldn't use) the Facebook SDK with this library. Whe running in
 * Cordova, you also don't need the Facebook Cordova plugin. There is also no dependency on jQuery.
 * OpenFB allows you to login to Facebook and execute any Facebook Graph API request.
 * @author Christophe Coenraets @ccoenraets
 * @version 0.3
 */
var openFB = (function () {

    var FB_LOGIN_URL = 'https://www.facebook.com/dialog/oauth',

    // By default we store fbtoken in sessionStorage. This can be overridden in init()
        tokenStore = window.sessionStorage,

        fbAppId,
        oauthRedirectURL,

    // Because the OAuth login spans multiple processes, we need to keep the success/error handlers as variables
    // inside the module instead of keeping them local within the login function.
        loginSuccessHandler,
        loginErrorHandler,

    // Indicates if the app is running inside Cordova
        runningInCordova,

    // Used in the exit event handler to identify if the login has already been processed elsewhere (in the oauthCallback function)
        loginProcessed;

    document.addEventListener("deviceready", function () {
        runningInCordova = true;
    }, false);

    /**
     * Initialize the OpenFB module. You must use this function and initialize the module with an appId before you can
     * use any other function.
     * @param appId - The id of the Facebook app
     * @param redirectURL - The OAuth redirect URL. Optional. If not provided, we use sensible defaults.
     * @param store - The store used to save the Facebook token. Optional. If not provided, we use sessionStorage.
     */
    function init(appId, redirectURL, store) {
        fbAppId = appId;
        if (redirectURL) oauthRedirectURL = redirectURL;
        if (store) tokenStore = store;
    }

    /**
     * Login to Facebook using OAuth. If running in a Browser, the OAuth workflow happens in a a popup window.
     * If running in Cordova container, it happens using the In-App Browser. Don't forget to install the In-App Browser
     * plugin in your Cordova project: cordova plugins add org.apache.cordova.inappbrowser.
     * @param scope - The set of Facebook permissions requested
     * @param success - Callback function to invoke when the login process succeeds
     * @param error - Callback function to invoke when the login process fails
     * @returns {*}
     */
    function login(scope, success, error) {

        var loginWindow,
            startTime;

        function loginWindowLoadStart(event) {
            var url = event.url;
            if (url.indexOf("access_token=") > 0 || url.indexOf("error=") > 0) {
                // When we get the access token fast, the login window (inappbrowser) is still opening with animation
                // in the Cordova app, and trying to close it while it's animating generates an exception. Wait a little...
                var timeout = 600 - (new Date().getTime() - startTime);
                setTimeout(function () {
                    loginWindow.close();
                }, timeout > 0 ? timeout : 0);
                oauthCallback(url);
            }
        }

        function loginWindowExit() {
            console.log('exit and remove listeners');
            // Handle the situation where the user closes the login window manually before completing the login process
            deferredLogin.reject({error: 'user_cancelled', error_description: 'User cancelled login process', error_reason: "user_cancelled"});
            loginWindow.removeEventListener('loadstop', loginWindowLoadStart);
            loginWindow.removeEventListener('exit', loginWindowExit);
            loginWindow = null;
            console.log('done removing listeners');
        }

        if (!fbAppId) {
            return error({error: 'Facebook App Id not set.'});
        }

        scope = scope || '';

        loginSuccessHandler = success;
        loginErrorHandler = error;

        loginProcessed = false;
        logout();

        // Check if an explicit oauthRedirectURL has been provided in init(). If not, infer the appropriate value
        if (!oauthRedirectURL) {
            if (runningInCordova) {
                oauthRedirectURL = 'https://www.facebook.com/connect/login_success.html';
            } else {
                var origin = location.protocol + '//' + location.hostname + (location.port ? ':' + location.port : '');
                // could also use 'var origin = window.location.origin' when enough browsers support it
                oauthRedirectURL = origin + '/oauthcallback.html';
            }
        }

        startTime = new Date().getTime();
        loginWindow = window.open(FB_LOGIN_URL + '?client_id=' + fbAppId + '&redirect_uri=' + oauthRedirectURL +
            '&response_type=token&display=popup&scope=' + scope, '_blank', 'location=no');

        // If the app is running in Cordova, listen to URL changes in the InAppBrowser until we get a URL with an access_token or an error
        if (runningInCordova) {
            loginWindow.addEventListener('loadstart', loginWindowLoadStart);
            loginWindow.addEventListener('exit', loginWindowExit);
        }
        // Note: if the app is running in the browser the loginWindow dialog will call back by invoking the
        // oauthCallback() function. See oauthcallback.html for details.

    }

    /**
     * Called either by oauthcallback.html (when the app is running the browser) or by the loginWindow loadstart event
     * handler defined in the login() function (when the app is running in the Cordova/PhoneGap container).
     * @param url - The oautchRedictURL called by Facebook with the access_token in the querystring at the ned of the
     * OAuth workflow.
     */
    function oauthCallback(url) {
        // Parse the OAuth data received from Facebook
        var queryString,
            obj;

        loginProcessed = true;
        if (url.indexOf("access_token=") > 0) {
            queryString = url.substr(url.indexOf('#') + 1);
            obj = parseQueryString(queryString);
            tokenStore['fbtoken'] = obj['access_token'];
            if (loginSuccessHandler) loginSuccessHandler();
        } else if (url.indexOf("error=") > 0) {
            queryString = url.substring(url.indexOf('?') + 1, url.indexOf('#'));
            obj = parseQueryString(queryString);
            if (loginErrorHandler) loginErrorHandler(obj);
        } else {
            if (loginErrorHandler) loginErrorHandler();
        }
    }

    /**
     * Application-level logout: we simply discard the token.
     */
    function logout() {
        tokenStore['fbtoken'] = undefined;
    }

    /**
     * Lets you make any Facebook Graph API request.
     * @param obj - Request configuration object. Can include:
     *  method:  HTTP method: GET, POST, etc. Optional - Default is 'GET'
     *  path:    path in the Facebook graph: /me, /me.friends, etc. - Required
     *  params:  queryString parameters as a map - Optional
     *  success: callback function when operation succeeds - Optional
     *  error:   callback function when operation fails - Optional
     */
    function api(obj) {

        var method = obj.method || 'GET',
            params = obj.params || {},
            xhr = new XMLHttpRequest(),
            url;

        params['access_token'] = tokenStore['fbtoken'];

        url = 'https://graph.facebook.com' + obj.path + '?' + toQueryString(params);

        xhr.onreadystatechange = function () {
            if (xhr.readyState === 4) {
                if (xhr.status === 200) {
                    if (obj.success) obj.success(JSON.parse(xhr.responseText));
                } else {
                    var error = xhr.responseText ? JSON.parse(xhr.responseText).error : {message: 'An error has occurred'};
                    if (obj.error) obj.error(error);
                }
            }
        };

        xhr.open(method, url, true);
        xhr.send();
    }

    /**
     * Helper function to de-authorize the app
     * @param success
     * @param error
     * @returns {*}
     */
    function revokePermissions(success, error) {
        return api({method: 'DELETE',
            path: '/me/permissions',
            success: function () {
                tokenStore['fbtoken'] = undefined;
                success();
            },
            error: error});
    }

    function parseQueryString(queryString) {
        var qs = decodeURIComponent(queryString),
            obj = {},
            params = qs.split('&');
        params.forEach(function (param) {
            var splitter = param.split('=');
            obj[splitter[0]] = splitter[1];
        });
        return obj;
    }

    function toQueryString(obj) {
        var parts = [];
        for (var i in obj) {
            if (obj.hasOwnProperty(i)) {
                parts.push(encodeURIComponent(i) + "=" + encodeURIComponent(obj[i]));
            }
        }
        return parts.join("&");
    }

    // The public API
    return {
        init: init,
        login: login,
        logout: logout,
        revokePermissions: revokePermissions,
        api: api,
        oauthCallback: oauthCallback
    }

}());<commit_msg>Logout updates<commit_after>/**
 * OpenFB is a micro-library that lets you integrate your JavaScript application with Facebook.
 * OpenFB works for both BROWSER-BASED apps and CORDOVA/PHONEGAP apps.
 * This library has no dependency: You don't need (and shouldn't use) the Facebook SDK with this library. Whe running in
 * Cordova, you also don't need the Facebook Cordova plugin. There is also no dependency on jQuery.
 * OpenFB allows you to login to Facebook and execute any Facebook Graph API request.
 * @author Christophe Coenraets @ccoenraets
 * @version 0.4
 */
var openFB = (function () {

    var FB_LOGIN_URL = 'https://www.facebook.com/dialog/oauth',

    // By default we store fbtoken in sessionStorage. This can be overridden in init()
        tokenStore = window.sessionStorage,

        fbAppId,
        oauthRedirectURL,

    // Because the OAuth login spans multiple processes, we need to keep the success/error handlers as variables
    // inside the module instead of keeping them local within the login function.
        loginSuccessHandler,
        loginErrorHandler,

    // Indicates if the app is running inside Cordova
        runningInCordova,

    // Used in the exit event handler to identify if the login has already been processed elsewhere (in the oauthCallback function)
        loginProcessed;

        document.addEventListener("deviceready", function () {
            runningInCordova = true;
        }, false);

    /**
     * Initialize the OpenFB module. You must use this function and initialize the module with an appId before you can
     * use any other function.
     * @param appId - The id of the Facebook app
     * @param redirectURL - The OAuth redirect URL. Optional. If not provided, we use sensible defaults.
     * @param store - The store used to save the Facebook token. Optional. If not provided, we use sessionStorage.
     */
    function init(appId, redirectURL, store) {
        fbAppId = appId;
        if (redirectURL) oauthRedirectURL = redirectURL;
        if (store) tokenStore = store;
    };

    /**
     * Login to Facebook using OAuth. If running in a Browser, the OAuth workflow happens in a a popup window.
     * If running in Cordova container, it happens using the In-App Browser. Don't forget to install the In-App Browser
     * plugin in your Cordova project: cordova plugins add org.apache.cordova.inappbrowser.
     * @param scope - The set of Facebook permissions requested
     * @param success - Callback function to invoke when the login process succeeds
     * @param error - Callback function to invoke when the login process fails
     * @returns {*}
     */
    function login(scope, success, error) {

        var loginWindow,
            startTime;

        function loginWindowLoadStart(event) {
            var url = event.url;
            if (url.indexOf("access_token=") > 0 || url.indexOf("error=") > 0) {
                // When we get the access token fast, the login window (inappbrowser) is still opening with animation
                // in the Cordova app, and trying to close it while it's animating generates an exception. Wait a little...
                var timeout = 600 - (new Date().getTime() - startTime);
                setTimeout(function () {
                    loginWindow.close();
                }, timeout > 0 ? timeout : 0);
                oauthCallback(url);
            }
        };

        function loginWindowExit() {
            console.log('exit and remove listeners');
            // Handle the situation where the user closes the login window manually before completing the login process
            deferredLogin.reject({error: 'user_cancelled', error_description: 'User cancelled login process', error_reason: "user_cancelled"});
            loginWindow.removeEventListener('loadstop', loginWindowLoadStart);
            loginWindow.removeEventListener('exit', loginWindowExit);
            loginWindow = null;
            console.log('done removing listeners');
        };

        if (!fbAppId) {
            return error({error: 'Facebook App Id not set.'});
        };

        scope = scope || '';

        loginSuccessHandler = success;
        loginErrorHandler = error;

        loginProcessed = false;
        logout();

        // Check if an explicit oauthRedirectURL has been provided in init(). If not, infer the appropriate value
        if (!oauthRedirectURL) {
            if (runningInCordova) {
                oauthRedirectURL = 'https://www.facebook.com/connect/login_success.html';
            } else {
                var origin = location.protocol + '//' + location.hostname + (location.port ? ':' + location.port : '');
                // could also use 'var origin = window.location.origin' when enough browsers support it
                oauthRedirectURL = origin + '/oauthcallback.html';
            }
        };

        startTime = new Date().getTime();
        loginWindow = window.open(FB_LOGIN_URL + '?client_id=' + fbAppId + '&redirect_uri=' + oauthRedirectURL +
            '&response_type=token&display=popup&scope=' + scope, '_blank', 'location=no');

        // If the app is running in Cordova, listen to URL changes in the InAppBrowser until we get a URL with an access_token or an error
        if (runningInCordova) {
            loginWindow.addEventListener('loadstart', loginWindowLoadStart);
            loginWindow.addEventListener('exit', loginWindowExit);
        }
        // Note: if the app is running in the browser the loginWindow dialog will call back by invoking the
        // oauthCallback() function. See oauthcallback.html for details.

    };

    /**
     * Called either by oauthcallback.html (when the app is running the browser) or by the loginWindow loadstart event
     * handler defined in the login() function (when the app is running in the Cordova/PhoneGap container).
     * @param url - The oautchRedictURL called by Facebook with the access_token in the querystring at the ned of the
     * OAuth workflow.
     */
    function oauthCallback(url) {
        // Parse the OAuth data received from Facebook
        var queryString,
            obj;

        loginProcessed = true;
        if (url.indexOf("access_token=") > 0) {
            queryString = url.substr(url.indexOf('#') + 1);
            obj = parseQueryString(queryString);
            tokenStore['fbtoken'] = obj['access_token'];
            if (loginSuccessHandler) loginSuccessHandler();
        } else if (url.indexOf("error=") > 0) {
            queryString = url.substring(url.indexOf('?') + 1, url.indexOf('#'));
            obj = parseQueryString(queryString);
            if (loginErrorHandler) loginErrorHandler(obj);
        } else {
            if (loginErrorHandler) loginErrorHandler();
        }
    };

    /**
     * Application-level logout:
     * Discard token and call remote logout url with facebook
     * This allows the login process to fire correctly again without remembering the previous
     * login information
     *
     */
    function logout() {
        if(tokenStore.getItem('fbtoken')){
             window.open('https://www.facebook.com/logout.php?next=http://localhost/logoutcallback.html&access_token=' + tokenStore['fbtoken'], '_blank', 'location=no');
        }
        /* remove token completely. Will fail silently if does not exist */
        tokenStore.removeItem('fbtoken');
    };

    /**
     * Lets you make any Facebook Graph API request.
     * @param obj - Request configuration object. Can include:
     *  method:  HTTP method: GET, POST, etc. Optional - Default is 'GET'
     *  path:    path in the Facebook graph: /me, /me.friends, etc. - Required
     *  params:  queryString parameters as a map - Optional
     *  success: callback function when operation succeeds - Optional
     *  error:   callback function when operation fails - Optional
     */
    function api(obj) {

        var method = obj.method || 'GET',
            params = obj.params || {},
            xhr = new XMLHttpRequest(),
            url;

        params['access_token'] = tokenStore['fbtoken'];

        url = 'https://graph.facebook.com' + obj.path + '?' + toQueryString(params);

        xhr.onreadystatechange = function () {
            if (xhr.readyState === 4) {
                if (xhr.status === 200) {
                    if (obj.success) obj.success(JSON.parse(xhr.responseText));
                } else {
                    var error = xhr.responseText ? JSON.parse(xhr.responseText).error : {message: 'An error has occurred'};
                    if (obj.error) obj.error(error);
                }
            }
        };

        xhr.open(method, url, true);
        xhr.send();
    };

    /**
     * Helper function to de-authorize the app
     * @param success
     * @param error
     * @returns {*}
     */
    function revokePermissions(success, error) {
        return api({method: 'DELETE',
            path: '/me/permissions',
            success: function () {
                tokenStore.removeItem('fbtoken');
                success();
            },
            error: error});
    };

    function parseQueryString(queryString) {
        var qs = decodeURIComponent(queryString),
            obj = {},
            params = qs.split('&');
        params.forEach(function (param) {
            var splitter = param.split('=');
            obj[splitter[0]] = splitter[1];
        });
        return obj;
    };

    function toQueryString(obj) {
        var parts = [];
        for (var i in obj) {
            if (obj.hasOwnProperty(i)) {
                parts.push(encodeURIComponent(i) + "=" + encodeURIComponent(obj[i]));
            }
        }
        return parts.join("&");
    };

    // The public API
    return {
        init: init,
        login: login,
        logout: logout,
        revokePermissions: revokePermissions,
        api: api,
        oauthCallback: oauthCallback
    };

}());
<|endoftext|>


<commit_before>/*
  opinel.js

  A bunch of useful small functions & shortcuts for JavaScript.
  Use at your own risk. See the file ./LICENSE

  Guillaume Lathoud, 2016
  glat@glat.info
*/

/* getters: pure functions */

function gA( aname, /*?*/node )
{
    return (node  ||  document).getAttribute( aname );
}

function gEBCN( cname, /*?*/node )
{
    return (node  ||  document).getElementsByClassName( cname );
}

function gEBI( id, /*?*/node )
{
    return node  ?  qS( '#' + id, node )  :  document.getElementById( id );
}

function gEBTN( tname, /*?*/node )
{
    return (node  ||  document).getElementsByTagName( tname );
}

function gScrollTop( node )
{
    return node  &&  node.offsetParent
	?  (node.offsetTop)  +  gScrollTop( node.offsetParent )
	:  0
    ;
}

function idf( x )
{
    return x;
}

function qS( sel, /*?*/node )
{
    return (node  ||  document).querySelector( sel );
}

function qSA( sel, /*?*/node )
{
    return (node  ||  document).querySelectorAll( sel );
}

/* html getters */

function hh( /*string, e.g. 'div' or 'div class="myclass"'*/tname, /*?string | array of string?*/html /*... maybe more `html` arguments... */)
{
    if (arguments.length > 2)
        html = Array.prototype.slice.call( arguments, 1 );

    html = array_2_string( html );

    var tag = tname.replace( /\s[\S\s].*$/, '' ).replace( /^\s*|\s*$/g, '' );

    // detect HTML5 void elements:
    // http://www.w3.org/TR/html5/syntax.html#void-elements
    // http://stackoverflow.com/a/7854998

    return tag in { area:1, base:1, br:1, col:1, embed:1, hr:1, img:1, input:1,
                    keygen:1, link:1, meta:1, param:1, source:1, track:1, wbr:1 }
        ?  '<' + tname + '>'
	:  '<' + tname + '>' + (html  ||  '') + '</' + tag + '>'
    ;

    function array_2_string( x )
    {
        return x  &&  'string' !== typeof x
            ?  x.map( array_2_string ).join( '' )
            :  x
        ;
    }
}

hh.esc = function (s)
// If this is used too often, then it is probably better to switch
// over to js.yak
{
    return s
        .replace( /&/g, '&' )
        .replace( /</g, '<' )
        .replace( />/g, '>' )
        .replace( /"/g, '"' )
        .replace( /'/g, ''' );
};

/* Note: functions are objects, so we can define convenient shortcuts
 * e.g. `hh.div( <html> )`, `hh.span( <html> )` etc.
 */
[ 'a', 'blockquote', 'button', 'cite', 'code', 'dd', 'div', 'dl', 'dt'
  , 'em', 'form', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'
  , 'hr', 'iframe', 'img', 'input', 'li', 'ol', 'option', 'p', 'pre'
  , 'select', 'span', 'strong'
  , 'table', 'td', 'th', 'tr', 'ul'
]
    .forEach( function ( name ) { hh[ name ] = hh.bind( null, name ); } )
;

/* creators */

function cE( name, /*?*/node )
{
    return (node  ||  document).createElement( name );
}

/* getter-setters */

function gs( pname, obj, code )
{
    return pname in obj  ?  obj[ pname ]  :  (obj[ pname ] = run( 'return ' + code, obj ));
}

/* modifiers: setters returning their first parameter */

function aEL( node, ename, clientfun, /*?*/capture )
{
    (node  ||  document).addEventListener( ename, clientfun, capture );
}

function rA( node, aname, value )
{
    node.removeAttribute( aname, value );
    return node;
}

function sA( node, aname, value )
{
    node.setAttribute( aname, value );
    return node;
}

function sP( node, propname, value )
{
    node[ propname ] = value;
    return node;
}

/* other */

function and( arr )
{
    var a = true;
    for (var n = arr.length, b = 0; b < n; b++)
    {
        a = arr[ b ];
	if (!a)
            break;
    }
    return a;
}

function or( arr )
{
    var a = false;
    for (var n = arr.length, b = 0; b < n; b++)
    {
        var a = arr[ b ];
	if (a)
            break;
    }
    return a;
}

function oEquals( a, b )
{
    if (a === b) return true;

    var ta = typeof a
    ,   tb = typeof b
    ;
    if (ta !== tb) return false;

    // Basic types

    if ('number' === ta  && isNaN( a )) return isNaN( b );

    if (ta !== 'object') return a === b;

    // null objects

    if (a === null  ||  b === null)  return a === b;

    // non-null objects

    var a_is_arr = a instanceof Array
    ,   b_is_arr = b instanceof Array
    ;
    if (a_is_arr !== b_is_arr) return false;

    if (a_is_arr)
    {
        if (a.length !== b.length)  return false;
        for (var i = a.length; i--;)
            if (!oEquals( a[ i ], b[ i ] ))  return false;
    }
    else
    {
        for (var k in a)  if (!(k in b))  return false;
        for (var k in b)  if (!(k in a  &&  oEquals( a[ k ], b[ k ] )))  return false;
    }

    return true;
}

function pad( s, n, c )
{
    c  ||  (c = ' ');
    'string' === typeof s  ||  (s = '' + s);

    var missing = Math.round( (n - s.length) / c.length );
    if (missing > 0)
    {
        var arr = arr  ||  s.split( '' ).reverse();
        while (missing--)
            arr.push( c );

        return arr.reverse().join( '' );
    }

    return s;
}

function fun( /*string*/code )
{
    return code in fun  ?  fun[ code ]  :  (fun[ code ] = new Function( 'a', 'b', code )); /* a & b are optional */
}

function run( /*string*/code, /*?*/a, /*?*/b )
/* a & b are optional */
{
    return (code in fun  ?  fun[ code ]  :  fun( code ))( a, b );
}

function xhrGetSync( href )
/* yes, as of 2016 "sync" already deprecated, but still useful for quick test purposes */
{
    var xhr = new XMLHttpRequest();
    xhr.open( 'GET', href, false );
    xhr.send();
    return xhr.responseText;
}
<commit_msg>added: aC cTxN<commit_after>/*
  opinel.js

  A bunch of useful small functions & shortcuts for JavaScript.
  Use at your own risk. See the file ./LICENSE

  Guillaume Lathoud, 2016
  glat@glat.info
*/

/* getters: pure functions */

function gA( aname, /*?*/node )
{
    return (node  ||  document).getAttribute( aname );
}

function gEBCN( cname, /*?*/node )
{
    return (node  ||  document).getElementsByClassName( cname );
}

function gEBI( id, /*?*/node )
{
    return node  ?  qS( '#' + id, node )  :  document.getElementById( id );
}

function gEBTN( tname, /*?*/node )
{
    return (node  ||  document).getElementsByTagName( tname );
}

function gScrollTop( node )
{
    return node  &&  node.offsetParent
	?  (node.offsetTop)  +  gScrollTop( node.offsetParent )
	:  0
    ;
}

function idf( x )
{
    return x;
}

function qS( sel, /*?*/node )
{
    return (node  ||  document).querySelector( sel );
}

function qSA( sel, /*?*/node )
{
    return (node  ||  document).querySelectorAll( sel );
}

/* html getters */

function hh( /*string, e.g. 'div' or 'div class="myclass"'*/tname, /*?string | array of string?*/html /*... maybe more `html` arguments... */)
{
    if (arguments.length > 2)
        html = Array.prototype.slice.call( arguments, 1 );

    html = array_2_string( html );

    var tag = tname.replace( /\s[\S\s].*$/, '' ).replace( /^\s*|\s*$/g, '' );

    // detect HTML5 void elements:
    // http://www.w3.org/TR/html5/syntax.html#void-elements
    // http://stackoverflow.com/a/7854998

    return tag in { area:1, base:1, br:1, col:1, embed:1, hr:1, img:1, input:1,
                    keygen:1, link:1, meta:1, param:1, source:1, track:1, wbr:1 }
        ?  '<' + tname + '>'
	:  '<' + tname + '>' + (html  ||  '') + '</' + tag + '>'
    ;

    function array_2_string( x )
    {
        return x  &&  'string' !== typeof x
            ?  x.map( array_2_string ).join( '' )
            :  x
        ;
    }
}

hh.esc = function (s)
// If this is used too often, then it is probably better to switch
// over to js.yak
{
    return s
        .replace( /&/g, '&' )
        .replace( /</g, '<' )
        .replace( />/g, '>' )
        .replace( /"/g, '"' )
        .replace( /'/g, ''' );
};

/* Note: functions are objects, so we can define convenient shortcuts
 * e.g. `hh.div( <html> )`, `hh.span( <html> )` etc.
 */
[ 'a', 'blockquote', 'button', 'cite', 'code', 'dd', 'div', 'dl', 'dt'
  , 'em', 'form', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'
  , 'hr', 'iframe', 'img', 'input', 'li', 'ol', 'option', 'p', 'pre'
  , 'select', 'span', 'strong'
  , 'table', 'td', 'th', 'tr', 'ul'
]
    .forEach( function ( name ) { hh[ name ] = hh.bind( null, name ); } )
;

/* creators */

function cE( name, /*?*/node )
{
    return (node  ||  document).createElement( name );
}

function cTxN( text, /*?*/node )
{
    var ret = (node  ||  document).createTextNode( text );
    return ret;
}

/* getter-setters */

function gs( pname, obj, code )
{
    return pname in obj  ?  obj[ pname ]  :  (obj[ pname ] = run( 'return ' + code, obj ));
}

/* modifiers: setters returning their first parameter */

function aC( node, child )
{
    node.appendChild( child );
    return node;
}

function aEL( node, ename, clientfun, /*?*/capture )
{
    (node  ||  document).addEventListener( ename, clientfun, capture );
}

function rA( node, aname, value )
{
    node.removeAttribute( aname, value );
    return node;
}

function sA( node, aname, value )
{
    node.setAttribute( aname, value );
    return node;
}

function sP( node, propname, value )
{
    node[ propname ] = value;
    return node;
}

/* other */

function and( arr )
{
    var a = true;
    for (var n = arr.length, b = 0; b < n; b++)
    {
        a = arr[ b ];
	if (!a)
            break;
    }
    return a;
}

function or( arr )
{
    var a = false;
    for (var n = arr.length, b = 0; b < n; b++)
    {
        var a = arr[ b ];
	if (a)
            break;
    }
    return a;
}

function oEquals( a, b )
{
    if (a === b) return true;

    var ta = typeof a
    ,   tb = typeof b
    ;
    if (ta !== tb) return false;

    // Basic types

    if ('number' === ta  && isNaN( a )) return isNaN( b );

    if (ta !== 'object') return a === b;

    // null objects

    if (a === null  ||  b === null)  return a === b;

    // non-null objects

    var a_is_arr = a instanceof Array
    ,   b_is_arr = b instanceof Array
    ;
    if (a_is_arr !== b_is_arr) return false;

    if (a_is_arr)
    {
        if (a.length !== b.length)  return false;
        for (var i = a.length; i--;)
            if (!oEquals( a[ i ], b[ i ] ))  return false;
    }
    else
    {
        for (var k in a)  if (!(k in b))  return false;
        for (var k in b)  if (!(k in a  &&  oEquals( a[ k ], b[ k ] )))  return false;
    }

    return true;
}

function pad( s, n, c )
{
    c  ||  (c = ' ');
    'string' === typeof s  ||  (s = '' + s);

    var missing = Math.round( (n - s.length) / c.length );
    if (missing > 0)
    {
        var arr = arr  ||  s.split( '' ).reverse();
        while (missing--)
            arr.push( c );

        return arr.reverse().join( '' );
    }

    return s;
}

function fun( /*string*/code )
{
    return code in fun  ?  fun[ code ]  :  (fun[ code ] = new Function( 'a', 'b', code )); /* a & b are optional */
}

function run( /*string*/code, /*?*/a, /*?*/b )
/* a & b are optional */
{
    return (code in fun  ?  fun[ code ]  :  fun( code ))( a, b );
}

function xhrGetSync( href )
/* yes, as of 2016 "sync" already deprecated, but still useful for quick test purposes */
{
    var xhr = new XMLHttpRequest();
    xhr.open( 'GET', href, false );
    xhr.send();
    return xhr.responseText;
}
<|endoftext|>


<commit_before>'use strict';

const
	sinon = require('sinon');

require('chai')
	.use(require('sinon-chai'))
	.should();

const
	clientUserActivityEvents = require('../../src/plugins/user-activity-events/client');

let sendEvent = function() {};
let MockClient = function() {
	return { sendEvent: sendEvent };
};

describe('user-activity-events', () => {

	beforeEach(() => {
		global.document = {
		};
	});

	describe('client', () => {

		var client;
		beforeEach(() => {
			global.window = {};
			client = new MockClient();
		});

		afterEach(() => {
		});

		describe('user activity events client', () => {

			var addEventListener;
			var sendEvent;
			beforeEach(() => {
				addEventListener = sinon.spy();
				sendEvent = sinon.spy();
				global.document.addEventListener = addEventListener;
				client.sendEvent = sendEvent;
			});

			it('should add an click event listener which triggers a userIsActiveEvent when called', () => {
				clientUserActivityEvents(client);
				addEventListener.should.have.been.calledWith('click');
				//call the actual event handler function and test if it is actually triggering the userIsActive event when it is called
				addEventListener.getCall(0).args[1].apply();
				sendEvent.should.have.been.calledWith('userIsActive');
			});

			it('should add an keydown event listener', () => {
				clientUserActivityEvents(client);
				addEventListener.should.have.been.calledWith('keydown');
				//call the actual event handler function and test if it is actually triggering the userIsActive event when it is called
				addEventListener.getCall(0).args[1].apply();
				sendEvent.should.have.been.calledWith('userIsActive');
			});

			it('should add an click event listener which throttles clicks to only send periodically', () => {
				clientUserActivityEvents(client);
				addEventListener.should.have.been.calledWith('click');
				//call the actual event handler function and test if it is actually triggering the userIsActive event when it is called
				addEventListener.getCall(0).args[1].apply();
				addEventListener.getCall(0).args[1].apply();
				addEventListener.getCall(0).args[1].apply();
				sendEvent.should.have.callCount(1);
			});

		});

	});

});
<commit_msg>increase test coverage<commit_after>'use strict';

const
	sinon = require('sinon');

require('chai')
	.use(require('sinon-chai'))
	.should();

const
	clientUserActivityEvents = require('../../src/plugins/user-activity-events/client');

let sendEvent = function() {};
let MockClient = function() {
	return { sendEvent: sendEvent };
};

describe('user-activity-events', () => {

	beforeEach(() => {
		global.document = {
		};
	});

	describe('client', () => {

		var client;
		beforeEach(() => {
			global.window = {};
			client = new MockClient();
		});

		afterEach(() => {
		});

		describe('user activity events client', () => {
			var addEventListener;
			var sendEvent;
			beforeEach(() => {
				addEventListener = sinon.spy();
				sendEvent = sinon.spy();
				global.document.addEventListener = addEventListener;
				client.sendEvent = sendEvent;
			});

			it('should add an click event listener which triggers a userIsActiveEvent when called', () => {
				clientUserActivityEvents(client);
				addEventListener.should.have.been.calledWith('click');
				//call the actual event handler function and test if it is actually triggering the userIsActive event when it is called
				addEventListener.getCall(0).args[1].apply();
				sendEvent.should.have.been.calledWith('userIsActive');
			});

			it('should add an keydown event listener', () => {
				clientUserActivityEvents(client);
				addEventListener.should.have.been.calledWith('keydown');
				//call the actual event handler function and test if it is actually triggering the userIsActive event when it is called
				addEventListener.getCall(1).args[1].apply();
				sendEvent.should.have.been.calledWith('userIsActive');
			});

			it('should add an click event listener which throttles clicks to only send periodically', () => {
				clientUserActivityEvents(client);
				addEventListener.should.have.been.calledWith('click');
				//call the actual event handler function and test if it is actually triggering the userIsActive event when it is called
				addEventListener.getCall(0).args[1].apply();
				addEventListener.getCall(0).args[1].apply();
				addEventListener.getCall(0).args[1].apply();
				sendEvent.should.have.callCount(1);
			});

		});

		describe('user activity events with waiting for throttle buffer period', () => {
			var addEventListener;
			var sendEvent;
			beforeEach(function(done) {
				this.timeout(12000);
				addEventListener = sinon.spy();
				sendEvent = sinon.spy();
				global.document.addEventListener = addEventListener;
				client.sendEvent = sendEvent;
				clientUserActivityEvents(client);
				//call the actual event handler function three times and then wait for 10 seconds, for the throttle buffer
				addEventListener.getCall(0).args[1].apply();
				addEventListener.getCall(0).args[1].apply();
				addEventListener.getCall(0).args[1].apply();
				sendEvent.should.have.callCount(1);
				setTimeout(function(){
					// complete the async beforeEach
					done();
				}, 10000);

			});

			it('should add an click event listener which throttles clicks to only send periodically', () => {
				sendEvent.should.have.callCount(2);
			});

		});
	});

});
<|endoftext|>


<commit_before>
angular.module('cesium.settings.controllers', ['cesium.services', 'cesium.currency.controllers'])
  .config(function($stateProvider, $urlRouterProvider) {
    'ngInject';

    $stateProvider

      .state('app.settings', {
        url: "/settings",
        views: {
          'menuContent': {
            templateUrl: "templates/settings/settings.html",
            controller: 'SettingsCtrl'
          }
        }
      })
    ;
  })

  .controller('SettingsCtrl', SettingsController)
;

function SettingsController($scope, $state, UIUtils, Wallet, $translate, BMA, $q, $ionicPopup, $timeout, localStorage) {
  'ngInject';

  $scope.locales = [
      {id:'fr-FR', label:'Français'},
      {id:'en', label:'English'}
    ];
  $scope.formData = angular.copy(Wallet.defaultSettings);
  $scope.loading = true;

  $scope.$on('$ionicView.enter', function(e, $state) {
    $scope.loading = true; // to avoid the call of Wallet.store()
    $scope.formData.locale = _.findWhere($scope.locales, {id: $translate.use()});
    Wallet.restore()
    .then(function() {
      angular.merge($scope.formData, Wallet.data.settings);
      $scope.formData.locale = _.findWhere($scope.locales, {id: Wallet.data.settings.locale.id});
      UIUtils.loading.hide();
      $scope.loading = false;
    })
    .catch(function(err) {
      $scope.loading = false;
      UIUtils.loading.hide();
    });
  });

  $scope.setSettingsForm = function(settingsForm) {
    $scope.settingsForm = settingsForm;
  };

  $scope.changeLanguage = function(langKey) {
    $translate.use(langKey);
  };

  // Change node
  $scope.changeNode= function(node) {
    if (!node) {
      node = $scope.formData.node;
    }
    $scope.showNodePopup(node)
    .then(function(node) {
      if (node == $scope.formData.node) {
        return; // same node = nothing to do
      }
      UIUtils.loading.show();
      var nodeBMA = BMA.instance(node);
      nodeBMA.node.summary() // ping the node
      .then(function() {
        UIUtils.loading.hide();
        $scope.formData.node = node;
        BMA.copy(nodeBMA);
      })
      .catch(function(err){
         UIUtils.loading.hide();
         UIUtils.alert.error('ERROR.INVALID_NODE_SUMMARY')
         .then(function(){
           $scope.changeNode(node); // loop
         });
      });
    });
  };

  // Show node popup
  $scope.showNodePopup = function(node) {
    return $q(function(resolve, reject) {
      $scope.formData.newNode = node;
      if (!!$scope.settingsForm) {
        $scope.settingsForm.$setPristine();
      }
      $translate(['SETTINGS.POPUP_NODE.TITLE', 'SETTINGS.POPUP_NODE.HELP', 'COMMON.BTN_OK', 'COMMON.BTN_CANCEL'])
        .then(function (translations) {
          // Choose UID popup
          $ionicPopup.show({
            templateUrl: 'templates/settings/popup_node.html',
            title: translations['SETTINGS.POPUP_NODE.TITLE'],
            subTitle: translations['SETTINGS.POPUP_NODE.HELP'],
            scope: $scope,
            buttons: [
              { text: translations['COMMON.BTN_CANCEL'] },
              {
                text: translations['COMMON.BTN_OK'],
                type: 'button-positive',
                onTap: function(e) {
                  $scope.settingsForm.$submitted=true;
                  if(!$scope.settingsForm.$valid || !$scope.settingsForm.newNode) {
                    //don't allow the user to close unless he enters a node
                    e.preventDefault();
                  } else {
                    return $scope.formData.newNode;
                  }
                }
              }
            ]
          })
          .then(function(node) {
            if (!node) { // user cancel
              UIUtils.loading.hide();
              return;
            }
            resolve(node);
          });
        });
      });
    };

  $scope.onSettingsChanged = function() {
    if (!$scope.loading) {
      $scope.loading = true;
      angular.merge(Wallet.data.settings, $scope.formData);
      Wallet.store();
      $scope.loading = false;
    }
  };
  $scope.$watch('formData', $scope.onSettingsChanged, true);

  // Set Ink
  $timeout(function() {
    // Set Ink
    UIUtils.ink({selector: '.item'});
  }, 10);
}
<commit_msg>Settings: Fix default locale selection<commit_after>
angular.module('cesium.settings.controllers', ['cesium.services', 'cesium.currency.controllers'])
  .config(function($stateProvider, $urlRouterProvider) {
    'ngInject';

    $stateProvider

      .state('app.settings', {
        url: "/settings",
        views: {
          'menuContent': {
            templateUrl: "templates/settings/settings.html",
            controller: 'SettingsCtrl'
          }
        }
      })
    ;
  })

  .controller('SettingsCtrl', SettingsController)
;

function SettingsController($scope, $state, UIUtils, Wallet, $translate, BMA, $q, $ionicPopup, $timeout, localStorage) {
  'ngInject';

  $scope.locales = [
      {id:'fr-FR', label:'Français'},
      {id:'en', label:'English'}
    ];
  $scope.formData = angular.copy(Wallet.defaultSettings);
  $scope.loading = true;

  $scope.$on('$ionicView.enter', function(e, $state) {
    $scope.loading = true; // to avoid the call of Wallet.store()
    $scope.formData.locale = _.findWhere($scope.locales, {id: $translate.use()});
    Wallet.restore()
    .then(function() {
      angular.merge($scope.formData, Wallet.data.settings);
      if (Wallet.data.settings.locale && Wallet.data.settings.locale.id) {
        $scope.formData.locale = _.findWhere($scope.locales, {id: Wallet.data.settings.locale.id});
      }
      UIUtils.loading.hide();
      $scope.loading = false;
    })
    .catch(function(err) {
      $scope.loading = false;
      UIUtils.loading.hide();
    });
  });

  $scope.setSettingsForm = function(settingsForm) {
    $scope.settingsForm = settingsForm;
  };

  $scope.changeLanguage = function(langKey) {
    $translate.use(langKey);
  };

  // Change node
  $scope.changeNode= function(node) {
    if (!node) {
      node = $scope.formData.node;
    }
    $scope.showNodePopup(node)
    .then(function(node) {
      if (node == $scope.formData.node) {
        return; // same node = nothing to do
      }
      UIUtils.loading.show();
      var nodeBMA = BMA.instance(node);
      nodeBMA.node.summary() // ping the node
      .then(function() {
        UIUtils.loading.hide();
        $scope.formData.node = node;
        BMA.copy(nodeBMA);
      })
      .catch(function(err){
         UIUtils.loading.hide();
         UIUtils.alert.error('ERROR.INVALID_NODE_SUMMARY')
         .then(function(){
           $scope.changeNode(node); // loop
         });
      });
    });
  };

  // Show node popup
  $scope.showNodePopup = function(node) {
    return $q(function(resolve, reject) {
      $scope.formData.newNode = node;
      if (!!$scope.settingsForm) {
        $scope.settingsForm.$setPristine();
      }
      $translate(['SETTINGS.POPUP_NODE.TITLE', 'SETTINGS.POPUP_NODE.HELP', 'COMMON.BTN_OK', 'COMMON.BTN_CANCEL'])
        .then(function (translations) {
          // Choose UID popup
          $ionicPopup.show({
            templateUrl: 'templates/settings/popup_node.html',
            title: translations['SETTINGS.POPUP_NODE.TITLE'],
            subTitle: translations['SETTINGS.POPUP_NODE.HELP'],
            scope: $scope,
            buttons: [
              { text: translations['COMMON.BTN_CANCEL'] },
              {
                text: translations['COMMON.BTN_OK'],
                type: 'button-positive',
                onTap: function(e) {
                  $scope.settingsForm.$submitted=true;
                  if(!$scope.settingsForm.$valid || !$scope.settingsForm.newNode) {
                    //don't allow the user to close unless he enters a node
                    e.preventDefault();
                  } else {
                    return $scope.formData.newNode;
                  }
                }
              }
            ]
          })
          .then(function(node) {
            if (!node) { // user cancel
              UIUtils.loading.hide();
              return;
            }
            resolve(node);
          });
        });
      });
    };

  $scope.onSettingsChanged = function() {
    if (!$scope.loading) {
      $scope.loading = true;
      angular.merge(Wallet.data.settings, $scope.formData);
      Wallet.store();
      $scope.loading = false;
    }
  };
  $scope.$watch('formData', $scope.onSettingsChanged, true);

  // Set Ink
  $timeout(function() {
    // Set Ink
    UIUtils.ink({selector: '.item'});
  }, 10);
}
<|endoftext|>


<commit_before>import './3d-force-graph.css';

import './threeGlobal';
import 'three/examples/js/controls/TrackBallControls';

import * as d3 from 'd3-force-3d';
import graph from 'ngraph.graph';
import forcelayout from 'ngraph.forcelayout';
import forcelayout3d from 'ngraph.forcelayout3d';
const ngraph = { graph, forcelayout, forcelayout3d };

import * as SWC from 'swc';

//

const CAMERA_DISTANCE2NODES_FACTOR = 150;

export default SWC.createComponent({

	props: [
		new SWC.Prop('width', window.innerWidth),
		new SWC.Prop('height', window.innerHeight),
		new SWC.Prop('jsonUrl'),
		new SWC.Prop('graphData', {
			nodes: [],
			links: []
		}),
		new SWC.Prop('numDimensions', 3),
		new SWC.Prop('nodeRelSize', 4), // volume per val unit
		new SWC.Prop('lineOpacity', 0.2),
		new SWC.Prop('autoColorBy'),
		new SWC.Prop('idField', 'id'),
		new SWC.Prop('valField', 'val'),
		new SWC.Prop('nameField', 'name'),
		new SWC.Prop('colorField', 'color'),
		new SWC.Prop('linkSourceField', 'source'),
		new SWC.Prop('linkTargetField', 'target'),
		new SWC.Prop('forceEngine', 'd3'), // d3 or ngraph
		new SWC.Prop('warmupTicks', 0), // how many times to tick the force engine at init before starting to render
		new SWC.Prop('cooldownTicks', Infinity),
		new SWC.Prop('cooldownTime', 15000) // ms
	],

	init: (domNode, state) => {
		// Wipe DOM
		domNode.innerHTML = '';

		// Add nav info section
		let navInfo;
		domNode.appendChild(navInfo = document.createElement('div'));
		navInfo.className = 'graph-nav-info';
		navInfo.textContent = "MOVE mouse & press LEFT/A: rotate, MIDDLE/S: zoom, RIGHT/D: pan";

		// Add info space
		domNode.appendChild(state.infoElem = document.createElement('div'));
		state.infoElem.className = 'graph-info-msg';
		state.infoElem.textContent = '';

		// Setup tooltip
		const toolTipElem = document.createElement('div');
		toolTipElem.classList.add('graph-tooltip');
		domNode.appendChild(toolTipElem);

		// Capture mouse coords on move
		const raycaster = new THREE.Raycaster();
		const mousePos = new THREE.Vector2();
		mousePos.x = -2; // Initialize off canvas
		mousePos.y = -2;
		domNode.addEventListener("mousemove", ev => {
			// update the mouse pos
			const offset = getOffset(domNode),
				relPos = {
					x: ev.pageX - offset.left,
					y: ev.pageY - offset.top
				};
			mousePos.x = (relPos.x / state.width) * 2 - 1;
			mousePos.y = -(relPos.y / state.height) * 2 + 1;

			// Move tooltip
			toolTipElem.style.top = (relPos.y - 40) + 'px';
			toolTipElem.style.left = (relPos.x - 20) + 'px';

			function getOffset(el) {
				const rect = el.getBoundingClientRect(),
					scrollLeft = window.pageXOffset || document.documentElement.scrollLeft,
					scrollTop = window.pageYOffset || document.documentElement.scrollTop;
				return { top: rect.top + scrollTop, left: rect.left + scrollLeft };
			}
		}, false);

		// Setup renderer
		state.renderer = new THREE.WebGLRenderer();
		domNode.appendChild(state.renderer.domElement);

		// Setup scene
		const scene = new THREE.Scene();
		scene.background = new THREE.Color(0x0000A);
		scene.add(state.graphScene = new THREE.Group());

		// Add lights
		scene.add(new THREE.AmbientLight(0xbbbbbb));
		scene.add(new THREE.DirectionalLight(0xffffff, 0.6));

		// Setup camera
		state.camera = new THREE.PerspectiveCamera();
		state.camera.far = 20000;

		// Add camera interaction
		const tbControls = new THREE.TrackballControls(state.camera, state.renderer.domElement);

		// Add D3 force-directed layout
		state.d3ForceLayout = d3.forceSimulation()
			.force('link', d3.forceLink())
			.force('charge', d3.forceManyBody())
			.force('center', d3.forceCenter())
			.stop();

		//

		// Kick-off renderer
		(function animate() { // IIFE
			if(state.onFrame) state.onFrame();

			// Update tooltip
			raycaster.setFromCamera(mousePos, state.camera);
			const intersects = raycaster.intersectObjects(state.graphScene.children)
				.filter(o => o.object.name); // Check only objects with labels
			toolTipElem.textContent = intersects.length ? intersects[0].object.name : '';

			// Frame cycle
			tbControls.update();
			state.renderer.render(scene, state.camera);
			requestAnimationFrame(animate);
		})();
	},

	update: function updateFn(state) {
		resizeCanvas();

		state.onFrame = null; // Pause simulation
		state.infoElem.textContent = 'Loading...';

		if (state.graphData.nodes.length || state.graphData.links.length) {
			console.info('3d-force-graph loading', state.graphData.nodes.length + ' nodes', state.graphData.links.length + ' links');
		}

		if (!state.fetchingJson && state.jsonUrl && !state.graphData.nodes.length && !state.graphData.links.length) {
			// (Re-)load data
			state.fetchingJson = true;
			qwest.get(state.jsonUrl).then((_, json) => {
				state.fetchingJson = false;
				state.graphData = json;
				updateFn(state);  // Force re-update
			});
		}

		// Auto add color to uncolored nodes
		autoColorNodes(state.graphData.nodes, state.autoColorBy, state.colorField);

		// parse links
		state.graphData.links.forEach(link => {
			link.source = link[state.linkSourceField];
			link.target = link[state.linkTargetField];
		});

		// Add WebGL objects
		while (state.graphScene.children.length) { state.graphScene.remove(state.graphScene.children[0]) } // Clear the place

		state.graphData.nodes.forEach(node => {
			const sphere = new THREE.Mesh(
				new THREE.SphereGeometry(Math.cbrt(node[state.valField] || 1) * state.nodeRelSize, 8, 8),
				new THREE.MeshLambertMaterial({ color: node[state.colorField] || 0xffffaa, transparent: true, opacity: 0.75 })
			);

			sphere.name = node[state.nameField]; // Add label

			state.graphScene.add(node.__sphere = sphere);
		});

		const lineMaterial = new THREE.LineBasicMaterial({ color: 0xf0f0f0, transparent: true, opacity: state.lineOpacity });
		state.graphData.links.forEach(link => {
			const geometry = new THREE.BufferGeometry();
			geometry.addAttribute('position', new THREE.BufferAttribute(new Float32Array(2 * 3), 3));
			const line = new THREE.Line(geometry, lineMaterial);

			line.renderOrder = 10; // Prevent visual glitches of dark lines on top of spheres by rendering them last

			state.graphScene.add(link.__line = line);
		});

		state.camera.lookAt(state.graphScene.position);
		state.camera.position.z = Math.cbrt(state.graphData.nodes.length) * CAMERA_DISTANCE2NODES_FACTOR;

		// Feed data to force-directed layout
		const isD3Sim = state.forceEngine !== 'ngraph';
		let layout;
		if (isD3Sim) {
			// D3-force
			(layout = state.d3ForceLayout)
				.stop()
				.alpha(1)// re-heat the simulation
				.numDimensions(state.numDimensions)
				.nodes(state.graphData.nodes)
				.force('link')
					.id(d => d[state.idField])
					.links(state.graphData.links);
		} else {
			// ngraph
			const graph = ngraph.graph();
			state.graphData.nodes.forEach(node => { graph.addNode(node[state.idField]); });
			state.graphData.links.forEach(link => { graph.addLink(link.source, link.target); });
			layout = ngraph['forcelayout' + (state.numDimensions === 2 ? '' : '3d')](graph);
			layout.graph = graph; // Attach graph reference to layout
		}

		for (let i=0; i<state.warmupTicks; i++) { layout[isD3Sim?'tick':'step'](); } // Initial ticks before starting to render

		let cntTicks = 0;
		const startTickTime = new Date();
		state.onFrame = layoutTick;
		state.infoElem.textContent = '';

		//

		function resizeCanvas() {
			if (state.width && state.height) {
				state.renderer.setSize(state.width, state.height);
				state.camera.aspect = state.width/state.height;
				state.camera.updateProjectionMatrix();
			}
		}

		function layoutTick() {
			if (cntTicks++ > state.cooldownTicks || (new Date()) - startTickTime > state.cooldownTime) {
				state.onFrame = null; // Stop ticking graph
			}

			layout[isD3Sim?'tick':'step'](); // Tick it

			// Update nodes position
			state.graphData.nodes.forEach(node => {
				const sphere = node.__sphere,
					pos = isD3Sim ? node : layout.getNodePosition(node[state.idField]);

				sphere.position.x = pos.x;
				sphere.position.y = pos.y || 0;
				sphere.position.z = pos.z || 0;
			});

			// Update links position
			state.graphData.links.forEach(link => {
				const line = link.__line,
					pos = isD3Sim
						? link
						: layout.getLinkPosition(layout.graph.getLink(link.source, link.target).id),
					start = pos[isD3Sim ? 'source' : 'from'],
					end = pos[isD3Sim ? 'target' : 'to'],
					linePos = line.geometry.attributes.position;

				linePos.array[0] = start.x;
				linePos.array[1] = start.y || 0;
				linePos.array[2] = start.z || 0;
				linePos.array[3] = end.x;
				linePos.array[4] = end.y || 0;
				linePos.array[5] = end.z || 0;

				linePos.needsUpdate = true;
				line.geometry.computeBoundingSphere();
			});
		}

		function autoColorNodes(nodes, colorBy, colorField) {
			if (!colorBy) return;

			// Color brewer paired set
			const colors = ['#a6cee3','#1f78b4','#b2df8a','#33a02c','#fb9a99','#e31a1c','#fdbf6f','#ff7f00','#cab2d6','#6a3d9a','#ffff99','#b15928'];

			const uncoloredNodes = nodes.filter(node => !node[colorField]),
				nodeGroups = {};

			uncoloredNodes.forEach(node => { nodeGroups[node[colorBy]] = null });
			Object.keys(nodeGroups).forEach((group, idx) => { nodeGroups[group] = idx });

			uncoloredNodes.forEach(node => {
				node[colorField] = parseInt(colors[nodeGroups[node[colorBy]] % colors.length].slice(1), 16);
			});
		}
	}
});

<commit_msg>Lighten background color<commit_after>import './3d-force-graph.css';

import './threeGlobal';
import 'three/examples/js/controls/TrackBallControls';

import * as d3 from 'd3-force-3d';
import graph from 'ngraph.graph';
import forcelayout from 'ngraph.forcelayout';
import forcelayout3d from 'ngraph.forcelayout3d';
const ngraph = { graph, forcelayout, forcelayout3d };

import * as SWC from 'swc';

//

const CAMERA_DISTANCE2NODES_FACTOR = 150;

export default SWC.createComponent({

	props: [
		new SWC.Prop('width', window.innerWidth),
		new SWC.Prop('height', window.innerHeight),
		new SWC.Prop('jsonUrl'),
		new SWC.Prop('graphData', {
			nodes: [],
			links: []
		}),
		new SWC.Prop('numDimensions', 3),
		new SWC.Prop('nodeRelSize', 4), // volume per val unit
		new SWC.Prop('lineOpacity', 0.2),
		new SWC.Prop('autoColorBy'),
		new SWC.Prop('idField', 'id'),
		new SWC.Prop('valField', 'val'),
		new SWC.Prop('nameField', 'name'),
		new SWC.Prop('colorField', 'color'),
		new SWC.Prop('linkSourceField', 'source'),
		new SWC.Prop('linkTargetField', 'target'),
		new SWC.Prop('forceEngine', 'd3'), // d3 or ngraph
		new SWC.Prop('warmupTicks', 0), // how many times to tick the force engine at init before starting to render
		new SWC.Prop('cooldownTicks', Infinity),
		new SWC.Prop('cooldownTime', 15000) // ms
	],

	init: (domNode, state) => {
		// Wipe DOM
		domNode.innerHTML = '';

		// Add nav info section
		let navInfo;
		domNode.appendChild(navInfo = document.createElement('div'));
		navInfo.className = 'graph-nav-info';
		navInfo.textContent = "MOVE mouse & press LEFT/A: rotate, MIDDLE/S: zoom, RIGHT/D: pan";

		// Add info space
		domNode.appendChild(state.infoElem = document.createElement('div'));
		state.infoElem.className = 'graph-info-msg';
		state.infoElem.textContent = '';

		// Setup tooltip
		const toolTipElem = document.createElement('div');
		toolTipElem.classList.add('graph-tooltip');
		domNode.appendChild(toolTipElem);

		// Capture mouse coords on move
		const raycaster = new THREE.Raycaster();
		const mousePos = new THREE.Vector2();
		mousePos.x = -2; // Initialize off canvas
		mousePos.y = -2;
		domNode.addEventListener("mousemove", ev => {
			// update the mouse pos
			const offset = getOffset(domNode),
				relPos = {
					x: ev.pageX - offset.left,
					y: ev.pageY - offset.top
				};
			mousePos.x = (relPos.x / state.width) * 2 - 1;
			mousePos.y = -(relPos.y / state.height) * 2 + 1;

			// Move tooltip
			toolTipElem.style.top = (relPos.y - 40) + 'px';
			toolTipElem.style.left = (relPos.x - 20) + 'px';

			function getOffset(el) {
				const rect = el.getBoundingClientRect(),
					scrollLeft = window.pageXOffset || document.documentElement.scrollLeft,
					scrollTop = window.pageYOffset || document.documentElement.scrollTop;
				return { top: rect.top + scrollTop, left: rect.left + scrollLeft };
			}
		}, false);

		// Setup renderer
		state.renderer = new THREE.WebGLRenderer();
		domNode.appendChild(state.renderer.domElement);

		// Setup scene
		const scene = new THREE.Scene();
		scene.background = new THREE.Color(0x00022);
		scene.add(state.graphScene = new THREE.Group());

		// Add lights
		scene.add(new THREE.AmbientLight(0xbbbbbb));
		scene.add(new THREE.DirectionalLight(0xffffff, 0.6));

		// Setup camera
		state.camera = new THREE.PerspectiveCamera();
		state.camera.far = 20000;

		// Add camera interaction
		const tbControls = new THREE.TrackballControls(state.camera, state.renderer.domElement);

		// Add D3 force-directed layout
		state.d3ForceLayout = d3.forceSimulation()
			.force('link', d3.forceLink())
			.force('charge', d3.forceManyBody())
			.force('center', d3.forceCenter())
			.stop();

		//

		// Kick-off renderer
		(function animate() { // IIFE
			if(state.onFrame) state.onFrame();

			// Update tooltip
			raycaster.setFromCamera(mousePos, state.camera);
			const intersects = raycaster.intersectObjects(state.graphScene.children)
				.filter(o => o.object.name); // Check only objects with labels
			toolTipElem.textContent = intersects.length ? intersects[0].object.name : '';

			// Frame cycle
			tbControls.update();
			state.renderer.render(scene, state.camera);
			requestAnimationFrame(animate);
		})();
	},

	update: function updateFn(state) {
		resizeCanvas();

		state.onFrame = null; // Pause simulation
		state.infoElem.textContent = 'Loading...';

		if (state.graphData.nodes.length || state.graphData.links.length) {
			console.info('3d-force-graph loading', state.graphData.nodes.length + ' nodes', state.graphData.links.length + ' links');
		}

		if (!state.fetchingJson && state.jsonUrl && !state.graphData.nodes.length && !state.graphData.links.length) {
			// (Re-)load data
			state.fetchingJson = true;
			qwest.get(state.jsonUrl).then((_, json) => {
				state.fetchingJson = false;
				state.graphData = json;
				updateFn(state);  // Force re-update
			});
		}

		// Auto add color to uncolored nodes
		autoColorNodes(state.graphData.nodes, state.autoColorBy, state.colorField);

		// parse links
		state.graphData.links.forEach(link => {
			link.source = link[state.linkSourceField];
			link.target = link[state.linkTargetField];
		});

		// Add WebGL objects
		while (state.graphScene.children.length) { state.graphScene.remove(state.graphScene.children[0]) } // Clear the place

		state.graphData.nodes.forEach(node => {
			const sphere = new THREE.Mesh(
				new THREE.SphereGeometry(Math.cbrt(node[state.valField] || 1) * state.nodeRelSize, 8, 8),
				new THREE.MeshLambertMaterial({ color: node[state.colorField] || 0xffffaa, transparent: true, opacity: 0.75 })
			);

			sphere.name = node[state.nameField]; // Add label

			state.graphScene.add(node.__sphere = sphere);
		});

		const lineMaterial = new THREE.LineBasicMaterial({ color: 0xf0f0f0, transparent: true, opacity: state.lineOpacity });
		state.graphData.links.forEach(link => {
			const geometry = new THREE.BufferGeometry();
			geometry.addAttribute('position', new THREE.BufferAttribute(new Float32Array(2 * 3), 3));
			const line = new THREE.Line(geometry, lineMaterial);

			line.renderOrder = 10; // Prevent visual glitches of dark lines on top of spheres by rendering them last

			state.graphScene.add(link.__line = line);
		});

		state.camera.lookAt(state.graphScene.position);
		state.camera.position.z = Math.cbrt(state.graphData.nodes.length) * CAMERA_DISTANCE2NODES_FACTOR;

		// Feed data to force-directed layout
		const isD3Sim = state.forceEngine !== 'ngraph';
		let layout;
		if (isD3Sim) {
			// D3-force
			(layout = state.d3ForceLayout)
				.stop()
				.alpha(1)// re-heat the simulation
				.numDimensions(state.numDimensions)
				.nodes(state.graphData.nodes)
				.force('link')
					.id(d => d[state.idField])
					.links(state.graphData.links);
		} else {
			// ngraph
			const graph = ngraph.graph();
			state.graphData.nodes.forEach(node => { graph.addNode(node[state.idField]); });
			state.graphData.links.forEach(link => { graph.addLink(link.source, link.target); });
			layout = ngraph['forcelayout' + (state.numDimensions === 2 ? '' : '3d')](graph);
			layout.graph = graph; // Attach graph reference to layout
		}

		for (let i=0; i<state.warmupTicks; i++) { layout[isD3Sim?'tick':'step'](); } // Initial ticks before starting to render

		let cntTicks = 0;
		const startTickTime = new Date();
		state.onFrame = layoutTick;
		state.infoElem.textContent = '';

		//

		function resizeCanvas() {
			if (state.width && state.height) {
				state.renderer.setSize(state.width, state.height);
				state.camera.aspect = state.width/state.height;
				state.camera.updateProjectionMatrix();
			}
		}

		function layoutTick() {
			if (cntTicks++ > state.cooldownTicks || (new Date()) - startTickTime > state.cooldownTime) {
				state.onFrame = null; // Stop ticking graph
			}

			layout[isD3Sim?'tick':'step'](); // Tick it

			// Update nodes position
			state.graphData.nodes.forEach(node => {
				const sphere = node.__sphere,
					pos = isD3Sim ? node : layout.getNodePosition(node[state.idField]);

				sphere.position.x = pos.x;
				sphere.position.y = pos.y || 0;
				sphere.position.z = pos.z || 0;
			});

			// Update links position
			state.graphData.links.forEach(link => {
				const line = link.__line,
					pos = isD3Sim
						? link
						: layout.getLinkPosition(layout.graph.getLink(link.source, link.target).id),
					start = pos[isD3Sim ? 'source' : 'from'],
					end = pos[isD3Sim ? 'target' : 'to'],
					linePos = line.geometry.attributes.position;

				linePos.array[0] = start.x;
				linePos.array[1] = start.y || 0;
				linePos.array[2] = start.z || 0;
				linePos.array[3] = end.x;
				linePos.array[4] = end.y || 0;
				linePos.array[5] = end.z || 0;

				linePos.needsUpdate = true;
				line.geometry.computeBoundingSphere();
			});
		}

		function autoColorNodes(nodes, colorBy, colorField) {
			if (!colorBy) return;

			// Color brewer paired set
			const colors = ['#a6cee3','#1f78b4','#b2df8a','#33a02c','#fb9a99','#e31a1c','#fdbf6f','#ff7f00','#cab2d6','#6a3d9a','#ffff99','#b15928'];

			const uncoloredNodes = nodes.filter(node => !node[colorField]),
				nodeGroups = {};

			uncoloredNodes.forEach(node => { nodeGroups[node[colorBy]] = null });
			Object.keys(nodeGroups).forEach((group, idx) => { nodeGroups[group] = idx });

			uncoloredNodes.forEach(node => {
				node[colorField] = parseInt(colors[nodeGroups[node[colorBy]] % colors.length].slice(1), 16);
			});
		}
	}
});

<|endoftext|>


<commit_before>var util = require('./util.test');

module.exports = function() {
	util.request({
		url: 'https://resprepend.resbody.resappend.test.whistlejs.com/?resBody=',
		method: 'post'
	}, function(res, data) {
		data.body.should.equal('prependbodyappend');
	});

	util.request('http://resprepend.resbody.resappend.test.whistlejs.com/', function(res, data) {
		data.body.should.equal('prependbodyappend');
	});
};<commit_msg>test: res.prepend.body.append<commit_after>var util = require('./util.test');

module.exports = function() {
	util.request({
		url: 'https://resprepend.resbody.resappend.test.whistlejs.com/?resBody=',
		method: 'post'
	}, function(res, data) {
		data.body.should.equal('prependbodyappend');
	});

	util.request('http://resprepend.resbody.resappend.test.whistlejs.com/?resBody=', function(res, data) {
		data.body.should.equal('prependbodyappend');
	});
};<|endoftext|>


<commit_before>/* eslint-disable array-plural/array-plural */
const assert = require('assert');
const last = require('lodash/last');
const max = require('lodash/max');
const sample = require('lodash/sample');
const sampleSize = require('lodash/sampleSize');
const shuffle = require('lodash/shuffle');
const times = require('lodash/times');
const zip = require('lodash/zip');

const digits = [1, 2, 3, 4, 5, 6, 7, 8, 9];
const patterns = [
	'AA AA',
	'AA AB',
	'AA BA',
	'AA BB',
	'AA BC',
	'AB AA',
	'AB AB',
	'AB AC',
	'AB BA',
	'AB BB',
	'AB BC',
	'AB CA',
	'AB CB',
	'AB CC',
	'AB CD',
];

const WIDTH = 11;
const HEIGHT = 13;
const xy = (x, y) => y * WIDTH + x;

module.exports.getPrecedingIndices = (cellIndex) => {
	const x = cellIndex % WIDTH;
	const y = Math.floor(cellIndex / WIDTH);
	const precedings = [];
	if (x - 1 >= 0) {
		precedings.push(xy(x - 1, y));
	}
	if (x + 1 < WIDTH) {
		precedings.push(xy(x + 1, y));
	}
	if (y % 2 === 0) {
		if (x - 1 >= 0 && y - 1 >= 0) {
			precedings.push(xy(x - 1, y - 1));
		}
		if (y - 1 >= 0) {
			precedings.push(xy(x, y - 1));
		}
		if (x - 1 >= 0 && y + 1 < HEIGHT) {
			precedings.push(xy(x - 1, y + 1));
		}
		if (y + 1 < HEIGHT) {
			precedings.push(xy(x, y + 1));
		}
	} else {
		if (x + 1 < WIDTH && y - 1 >= 0) {
			precedings.push(xy(x + 1, y - 1));
		}
		if (y - 1 >= 0) {
			precedings.push(xy(x, y - 1));
		}
		if (x + 1 < WIDTH && y + 1 < HEIGHT) {
			precedings.push(xy(x + 1, y + 1));
		}
		if (y + 1 < HEIGHT) {
			precedings.push(xy(x, y + 1));
		}
	}
	return precedings;
};

const ngRegex = /(\d)\1\1/;

module.exports.generateInput = () => {
	const lines = [];
	for (const pattern of patterns) {
		const generated = [];
		while (generated.length < 2) {
			const selectedDigits = shuffle(sampleSize(digits, 4));
			const line = sample(digits).toString() + pattern
				.replace(/A/g, selectedDigits[0])
				.replace(/B/g, selectedDigits[1])
				.replace(/C/g, selectedDigits[2])
				.replace(/D/g, selectedDigits[3]) + sample(digits).toString();
			if (!line.match(ngRegex) && !generated.includes(line)) {
				generated.push(line);
			}
		}
		lines.push(...generated);
	}

	while (lines.length < 32) {
		const selectedDigits = times(6, () => sample(digits));
		const line = `${selectedDigits.slice(0, 3).join('')} ${selectedDigits.slice(3, 6).join('')}`;
		if (!line.match(ngRegex) && !lines.includes(line)) {
			lines.push(line);
		}
	}

	return shuffle(lines).map((line) => `${line}\n`).join('');
};

const countScore = (line, digit) => {
	const cells = [line[0], line[1], line[2], digit.toString(), line[4], line[5], line[6]];
	const continuousLength = [1];
	for (const [i, cell] of cells.entries()) {
		if (i === 0) {
			continue;
		}
		if (cells[i - 1] === cell) {
			continuousLength.push(last(continuousLength) + 1);
		} else {
			continuousLength.push(1);
		}
	}
	const maximumContinuation = max(continuousLength);
	return maximumContinuation >= 3 ? maximumContinuation : 0;
};

module.exports.isValidAnswer = (input, output) => {
	if (process.env.NODE_ENV !== 'production') {
		return true;
	}

	const trimmedOutput = output.toString().replace(/\s/g, '').split('');
	if (trimmedOutput.length !== 32) {
		console.log('info: length not valid');
		return false;
	}

	const inputLines = input.split('\n').filter((line) => line.length > 0);
	assert(inputLines.length === 32);

	for (const [char, line] of zip(trimmedOutput, inputLines)) {
		if (!digits.map((d) => d.toString()).includes(char)) {
			console.log('1');
			return false;
		}
		const digit = parseInt(char);
		const maximumScore = max(digits.map((d) => countScore(line, d)));
		if (countScore(line, digit) !== maximumScore) {
			console.log('2');
			return false;
		}
	}

	console.log('info:', {input, output, trimmedOutput});

	return true;
};
<commit_msg>implement problem<commit_after>/* eslint-disable array-plural/array-plural */
const assert = require('assert');
const last = require('lodash/last');
const max = require('lodash/max');
const sample = require('lodash/sample');
const sampleSize = require('lodash/sampleSize');
const shuffle = require('lodash/shuffle');
const times = require('lodash/times');
const zip = require('lodash/zip');

const WIDTH = 11;
const HEIGHT = 13;
const xy = (x, y) => y * WIDTH + x;

const patterns = [
['110', '000', '000'],
['100', '010', '000'],
['100', '100', '000'],
['111', '000', '000'],
['110', '001', '000'],
['110', '100', '000'],
['100', '010', '001'],
['100', '100', '100'],
['100', '100', '010'],
['111', '100', '000'],
['110', '110', '000'],
['110', '101', '000'],
['110', '100', '100'],
['110', '100', '010'],
['110', '100', '001'],
['110', '001', '001']];

module.exports.getPrecedingIndices = (cellIndex) => {
	const x = cellIndex % WIDTH;
	const y = Math.floor(cellIndex / WIDTH);
	const precedings = [];
	if (x - 1 >= 0) {
		precedings.push(xy(x - 1, y));
	}
	if (x + 1 < WIDTH) {
		precedings.push(xy(x + 1, y));
	}
	if (y % 2 === 0) {
		if (x - 1 >= 0 && y - 1 >= 0) {
			precedings.push(xy(x - 1, y - 1));
		}
		if (y - 1 >= 0) {
			precedings.push(xy(x, y - 1));
		}
		if (x - 1 >= 0 && y + 1 < HEIGHT) {
			precedings.push(xy(x - 1, y + 1));
		}
		if (y + 1 < HEIGHT) {
			precedings.push(xy(x, y + 1));
		}
	} else {
		if (x + 1 < WIDTH && y - 1 >= 0) {
			precedings.push(xy(x + 1, y - 1));
		}
		if (y - 1 >= 0) {
			precedings.push(xy(x, y - 1));
		}
		if (x + 1 < WIDTH && y + 1 < HEIGHT) {
			precedings.push(xy(x + 1, y + 1));
		}
		if (y + 1 < HEIGHT) {
			precedings.push(xy(x, y + 1));
		}
	}
	return precedings;
};

const ngRegex = /(\d)\1\1/;

count doubleShuffle = (pattern) => {
   const rowids = shuffle(Array.from({length: pattern.length}, (_,i) => i));
   return shuffle(pattern.map(col => rowids.map(d => col[d]).join('')));
};

module.exports.generateInput = () => {
	const lines = [];
	for (const pattern of patterns) {
           lines = lines.concat(doubleShuffle(pattern));
	}
};

const countScore = (line, digit) => {
	const cells = [line[0], line[1], line[2], digit.toString(), line[4], line[5], line[6]];
	const continuousLength = [1];
	for (const [i, cell] of cells.entries()) {
		if (i === 0) {
			continue;
		}
		if (cells[i - 1] === cell) {
			continuousLength.push(last(continuousLength) + 1);
		} else {
			continuousLength.push(1);
		}
	}
	const maximumContinuation = max(continuousLength);
	return maximumContinuation >= 3 ? maximumContinuation : 0;
};

module.exports.isValidAnswer = (input, output) => {
	if (process.env.NODE_ENV !== 'production') {
		return true;
	}

	const trimmedOutput = output.toString().replace(/\s/g, '').split('');
	if (trimmedOutput.length !== patterns.length) {
		console.log('info: length not valid');
		return false;
	}

	const inputLines = input.split('\n').filter((line) => line.length > 0);
        const onePatternLength = patterns[0].length
	assert(inputLines.length === patterns.length * onePatternLength);

        for (let i = 0; i < patterns.length; i++) {
           const patternAsInt = inputLines.slice(3*i, 3*i+3).parseInt(patterns[i][k], 2);
           const isNifu = patternAsInt.reduce((acc, item) => acc + item) > patternAsInt.reduce((acc, item) => acc | item);
           if (isNifu !== Boolean(Number(trimmedOutput[i]))) {
              console.log(patternAsInt.toString());
              return false;
           }
        }

	console.log('info:', {input, output, trimmedOutput});

	return true;
};
<|endoftext|>